-- pg_plan_guard regression tests -- -- The point of these tests is not that the functions run: it is that drift is -- actually DETECTED, and that the failure modes behave. A plan guard that -- reports "ok" no matter what is worse than none, because it is trusted. CREATE EXTENSION pg_plan_guard; CREATE SCHEMA guard_test; CREATE TABLE guard_test.t (id int, val text); INSERT INTO guard_test.t SELECT g, 'v' || g FROM generate_series(1, 20000) g; CREATE INDEX t_id_idx ON guard_test.t (id); ANALYZE guard_test.t; -- Capture the good plan (index scan on a selective lookup). SELECT plan_guard.capture( 'lookup', 'SELECT val FROM guard_test.t WHERE id = 42', 'point lookup that must use the index') LIKE '%INDEX_SCAN%' AS captured_index_scan; captured_index_scan --------------------- t (1 row) -- Nothing changed: must be ok. SELECT name, state FROM plan_guard.verify('lookup'); name | state --------+------- lookup | ok (1 row) -- Take the index away from the planner. The query still returns the same row — -- this is the silent regression the extension exists to catch. SET enable_indexscan = off; SET enable_bitmapscan = off; SELECT name, state FROM plan_guard.verify('lookup'); name | state --------+--------- lookup | drifted (1 row) -- The drift must be recorded with both sides of the change. SELECT baseline_name, expected_advice LIKE '%INDEX_SCAN%' AS expected_was_index, actual_advice LIKE '%SEQ_SCAN%' AS actual_is_seqscan FROM plan_guard.drift_log WHERE baseline_name = 'lookup'; baseline_name | expected_was_index | actual_is_seqscan ---------------+--------------------+------------------- lookup | t | t (1 row) -- Re-verifying while still drifted must NOT append another log row: a baseline -- that has been broken for a week should not produce one row per cron run. SELECT name, state FROM plan_guard.verify('lookup'); name | state --------+--------- lookup | drifted (1 row) SELECT count(*) AS drift_rows FROM plan_guard.drift_log WHERE baseline_name = 'lookup'; drift_rows ------------ 1 (1 row) -- Restore the planner: state must go back to ok on its own. RESET enable_indexscan; RESET enable_bitmapscan; SELECT name, state FROM plan_guard.verify('lookup'); name | state --------+------- lookup | ok (1 row) -- The view a monitor would poll. SELECT name, state, drift_events FROM plan_guard.status WHERE name = 'lookup'; name | state | drift_events --------+-------+-------------- lookup | ok | 1 (1 row) -- A baseline whose query no longer plans must be reported as 'error' WITHOUT -- aborting the run: one broken baseline must not stop the others from being -- checked. A monitor that dies on the first problem stops working exactly when -- something is wrong. SELECT plan_guard.capture('broken', 'SELECT * FROM guard_test.t WHERE id = 1') IS NOT NULL AS ok; ok ---- t (1 row) DROP TABLE guard_test.t CASCADE; SELECT name, state FROM plan_guard.verify() ORDER BY name; name | state --------+------- broken | error lookup | error (2 rows) -- Capturing a query that cannot be planned must fail loudly, not store garbage. SELECT plan_guard.capture('nope', 'SELECT * FROM guard_test.does_not_exist'); ERROR: relation "guard_test.does_not_exist" does not exist LINE 1: EXPLAIN (COSTS OFF, PLAN_ADVICE) SELECT * FROM guard_test.do... ^ QUERY: EXPLAIN (COSTS OFF, PLAN_ADVICE) SELECT * FROM guard_test.does_not_exist CONTEXT: PL/pgSQL function plan_guard.advice_for(text) line 15 at FOR over EXECUTE statement PL/pgSQL function plan_guard.capture(text,text,text) line 11 at assignment DROP SCHEMA guard_test CASCADE; DROP EXTENSION pg_plan_guard CASCADE;