|
| 1 | +-- Fixtures to test schema rename traversal |
| 2 | +-- These exercise complex scenarios with multiple schema references across different contexts |
| 3 | + |
| 4 | +-- Test 1: Function with schema-qualified table references in SELECT |
| 5 | +CREATE FUNCTION app_public.get_user_stats(p_user_id int) |
| 6 | +RETURNS int |
| 7 | +LANGUAGE plpgsql AS $$ |
| 8 | +DECLARE |
| 9 | + total_count int; |
| 10 | +BEGIN |
| 11 | + SELECT count(*) INTO total_count |
| 12 | + FROM app_public.users u |
| 13 | + JOIN app_public.orders o ON o.user_id = u.id |
| 14 | + WHERE u.id = p_user_id; |
| 15 | + RETURN total_count; |
| 16 | +END$$; |
| 17 | + |
| 18 | +-- Test 2: Trigger function with INSERT into schema-qualified table |
| 19 | +CREATE FUNCTION app_public.audit_changes() |
| 20 | +RETURNS trigger |
| 21 | +LANGUAGE plpgsql AS $$ |
| 22 | +BEGIN |
| 23 | + INSERT INTO app_public.audit_log (table_name, operation, old_data, new_data, changed_at) |
| 24 | + VALUES (TG_TABLE_NAME, TG_OP, to_json(OLD), to_json(NEW), now()); |
| 25 | + |
| 26 | + IF TG_OP = 'DELETE' THEN |
| 27 | + RETURN OLD; |
| 28 | + END IF; |
| 29 | + RETURN NEW; |
| 30 | +END$$; |
| 31 | + |
| 32 | +-- Test 3: Function with UPDATE to schema-qualified table |
| 33 | +CREATE FUNCTION app_public.update_user_status(p_user_id int, p_status text) |
| 34 | +RETURNS void |
| 35 | +LANGUAGE plpgsql AS $$ |
| 36 | +BEGIN |
| 37 | + UPDATE app_public.users |
| 38 | + SET status = p_status, updated_at = now() |
| 39 | + WHERE id = p_user_id; |
| 40 | + |
| 41 | + INSERT INTO app_public.status_history (user_id, status, changed_at) |
| 42 | + VALUES (p_user_id, p_status, now()); |
| 43 | +END$$; |
| 44 | + |
| 45 | +-- Test 4: Function with DELETE from schema-qualified table |
| 46 | +CREATE FUNCTION app_public.cleanup_old_sessions(p_days int) |
| 47 | +RETURNS int |
| 48 | +LANGUAGE plpgsql AS $$ |
| 49 | +DECLARE |
| 50 | + deleted_count int; |
| 51 | +BEGIN |
| 52 | + DELETE FROM app_public.sessions |
| 53 | + WHERE created_at < now() - (p_days || ' days')::interval; |
| 54 | + |
| 55 | + GET DIAGNOSTICS deleted_count = ROW_COUNT; |
| 56 | + RETURN deleted_count; |
| 57 | +END$$; |
| 58 | + |
| 59 | +-- Test 5: SETOF function with RETURN QUERY and schema-qualified tables |
| 60 | +CREATE FUNCTION app_public.get_active_orders(p_status text) |
| 61 | +RETURNS SETOF int |
| 62 | +LANGUAGE plpgsql AS $$ |
| 63 | +BEGIN |
| 64 | + RETURN QUERY |
| 65 | + SELECT o.id |
| 66 | + FROM app_public.orders o |
| 67 | + JOIN app_public.users u ON u.id = o.user_id |
| 68 | + WHERE o.status = p_status |
| 69 | + AND u.is_active = true; |
| 70 | + RETURN; |
| 71 | +END$$; |
| 72 | + |
| 73 | +-- Test 6: Function with schema-qualified function calls in expressions |
| 74 | +CREATE FUNCTION app_public.calculate_order_total(p_order_id int) |
| 75 | +RETURNS numeric |
| 76 | +LANGUAGE plpgsql AS $$ |
| 77 | +DECLARE |
| 78 | + subtotal numeric; |
| 79 | + tax_amount numeric; |
| 80 | + discount numeric; |
| 81 | +BEGIN |
| 82 | + SELECT sum(quantity * price) INTO subtotal |
| 83 | + FROM app_public.order_items |
| 84 | + WHERE order_id = p_order_id; |
| 85 | + |
| 86 | + tax_amount := app_public.get_tax_rate() * subtotal; |
| 87 | + discount := app_public.get_discount(p_order_id); |
| 88 | + |
| 89 | + RETURN subtotal + tax_amount - discount; |
| 90 | +END$$; |
| 91 | + |
| 92 | +-- Test 7: Function with multiple schema references in complex query |
| 93 | +CREATE FUNCTION app_public.get_user_dashboard(p_user_id int) |
| 94 | +RETURNS TABLE(metric_name text, metric_value numeric) |
| 95 | +LANGUAGE plpgsql AS $$ |
| 96 | +BEGIN |
| 97 | + RETURN QUERY |
| 98 | + SELECT 'total_orders'::text, count(*)::numeric |
| 99 | + FROM app_public.orders |
| 100 | + WHERE user_id = p_user_id |
| 101 | + UNION ALL |
| 102 | + SELECT 'total_spent'::text, coalesce(sum(total), 0)::numeric |
| 103 | + FROM app_public.orders |
| 104 | + WHERE user_id = p_user_id |
| 105 | + UNION ALL |
| 106 | + SELECT 'active_subscriptions'::text, count(*)::numeric |
| 107 | + FROM app_public.subscriptions |
| 108 | + WHERE user_id = p_user_id AND status = 'active'; |
| 109 | + RETURN; |
| 110 | +END$$; |
| 111 | + |
| 112 | +-- Test 8: Trigger function with conditional logic and multiple tables |
| 113 | +CREATE FUNCTION app_public.sync_user_profile() |
| 114 | +RETURNS trigger |
| 115 | +LANGUAGE plpgsql AS $$ |
| 116 | +DECLARE |
| 117 | + profile_exists boolean; |
| 118 | +BEGIN |
| 119 | + SELECT EXISTS( |
| 120 | + SELECT 1 FROM app_public.profiles WHERE user_id = NEW.id |
| 121 | + ) INTO profile_exists; |
| 122 | + |
| 123 | + IF NOT profile_exists THEN |
| 124 | + INSERT INTO app_public.profiles (user_id, created_at) |
| 125 | + VALUES (NEW.id, now()); |
| 126 | + ELSE |
| 127 | + UPDATE app_public.profiles |
| 128 | + SET updated_at = now() |
| 129 | + WHERE user_id = NEW.id; |
| 130 | + END IF; |
| 131 | + |
| 132 | + PERFORM app_public.notify_profile_change(NEW.id); |
| 133 | + RETURN NEW; |
| 134 | +END$$; |
| 135 | + |
| 136 | +-- Test 9: Function with CTE and schema-qualified references |
| 137 | +CREATE FUNCTION app_public.get_top_customers(p_limit int) |
| 138 | +RETURNS SETOF int |
| 139 | +LANGUAGE plpgsql AS $$ |
| 140 | +BEGIN |
| 141 | + RETURN QUERY |
| 142 | + WITH customer_totals AS ( |
| 143 | + SELECT user_id, sum(total) as total_spent |
| 144 | + FROM app_public.orders |
| 145 | + WHERE status = 'completed' |
| 146 | + GROUP BY user_id |
| 147 | + ) |
| 148 | + SELECT ct.user_id |
| 149 | + FROM customer_totals ct |
| 150 | + JOIN app_public.users u ON u.id = ct.user_id |
| 151 | + WHERE u.is_active = true |
| 152 | + ORDER BY ct.total_spent DESC |
| 153 | + LIMIT p_limit; |
| 154 | + RETURN; |
| 155 | +END$$; |
| 156 | + |
| 157 | +-- Test 10: Function with subquery in WHERE clause |
| 158 | +CREATE FUNCTION app_public.get_users_with_orders() |
| 159 | +RETURNS SETOF int |
| 160 | +LANGUAGE plpgsql AS $$ |
| 161 | +BEGIN |
| 162 | + RETURN QUERY |
| 163 | + SELECT u.id |
| 164 | + FROM app_public.users u |
| 165 | + WHERE EXISTS ( |
| 166 | + SELECT 1 FROM app_public.orders o |
| 167 | + WHERE o.user_id = u.id |
| 168 | + ); |
| 169 | + RETURN; |
| 170 | +END$$; |
| 171 | + |
| 172 | +-- Test 11: Function referencing multiple schemas |
| 173 | +CREATE FUNCTION app_public.cross_schema_report(p_date date) |
| 174 | +RETURNS TABLE(source text, count bigint) |
| 175 | +LANGUAGE plpgsql AS $$ |
| 176 | +BEGIN |
| 177 | + RETURN QUERY |
| 178 | + SELECT 'public_users'::text, count(*) |
| 179 | + FROM app_public.users |
| 180 | + WHERE created_at::date = p_date |
| 181 | + UNION ALL |
| 182 | + SELECT 'private_logs'::text, count(*) |
| 183 | + FROM app_private.activity_logs |
| 184 | + WHERE logged_at::date = p_date |
| 185 | + UNION ALL |
| 186 | + SELECT 'internal_metrics'::text, count(*) |
| 187 | + FROM app_internal.metrics |
| 188 | + WHERE recorded_at::date = p_date; |
| 189 | + RETURN; |
| 190 | +END$$; |
| 191 | + |
| 192 | +-- Test 12: Procedure with schema-qualified references |
| 193 | +CREATE PROCEDURE app_public.process_batch(p_batch_id int) |
| 194 | +LANGUAGE plpgsql AS $$ |
| 195 | +DECLARE |
| 196 | + item record; |
| 197 | +BEGIN |
| 198 | + FOR item IN |
| 199 | + SELECT * FROM app_public.batch_items |
| 200 | + WHERE batch_id = p_batch_id |
| 201 | + LOOP |
| 202 | + INSERT INTO app_public.processed_items (item_id, processed_at) |
| 203 | + VALUES (item.id, now()); |
| 204 | + |
| 205 | + UPDATE app_public.batch_items |
| 206 | + SET status = 'processed' |
| 207 | + WHERE id = item.id; |
| 208 | + END LOOP; |
| 209 | + |
| 210 | + UPDATE app_public.batches |
| 211 | + SET status = 'completed', completed_at = now() |
| 212 | + WHERE id = p_batch_id; |
| 213 | +END$$; |
0 commit comments