-- -- Phase 6 safety hardening tests -- -- Covers: -- 1. Pattern complexity guard (NFA state limits) -- 2. Per-index reloptions -- 3. Query cancellation during extraction (manual test only) -- -- Setup: ensure extension is loaded CREATE EXTENSION IF NOT EXISTS pg_tre; SET client_min_messages = WARNING; -- -- Test 1: Reloptions parsing and application -- -- Create table for reloption tests CREATE TABLE relopts_test (id int, body text); INSERT INTO relopts_test VALUES (1, 'hello world'), (2, 'goodbye world'); -- Create index with custom reloptions CREATE INDEX relopts_idx ON relopts_test USING tre (body) WITH (fastupdate=on, range_size_blocks=256); -- Verify index exists \d+ relopts_idx -- Test that index works with custom options SELECT id FROM relopts_test WHERE body %~~ tre_pattern('hello', 0) ORDER BY id; -- Cleanup DROP INDEX relopts_idx; DROP TABLE relopts_test; -- -- Test 2: Pattern complexity guard -- -- This pattern has moderate complexity (should pass) SELECT tre_amatch('test string for matching', 'test.*matching', 0); -- Test GUC for max_nfa_states SHOW pg_tre.max_nfa_states; -- Lower the limit temporarily to test rejection SET pg_tre.max_nfa_states = 100; -- This pattern should be simple enough even with the low limit SELECT tre_amatch('hello', 'hello', 0); -- Try a more complex pattern (may or may not exceed limit depending on TRE internals) -- We can't reliably trigger the limit without knowing TRE's exact NFA construction, -- so we just verify the GUC works SET pg_tre.max_nfa_states = 10000; -- restore default SHOW pg_tre.max_nfa_states; -- -- Test 3: Verify GUC defaults -- SHOW pg_tre.pending_list_limit; SHOW pg_tre.range_size_blocks; SHOW pg_tre.fastupdate; SHOW pg_tre.max_extraction_fanout; SHOW pg_tre.compile_timeout_ms; SHOW pg_tre.match_timeout_ms; -- -- Test 4: Reloptions validation -- CREATE TABLE relopts_validation_test (id int, body text); -- Valid reloptions CREATE INDEX relopts_valid_idx ON relopts_validation_test USING tre (body) WITH (range_size_blocks=64); -- valid value DROP INDEX relopts_valid_idx; -- q must be 3 until Phase 8 CREATE INDEX relopts_q_idx ON relopts_validation_test USING tre (body) WITH (q=3); DROP INDEX relopts_q_idx; -- Cleanup DROP TABLE relopts_validation_test; -- -- Test 5: Multiple indexes with different options -- CREATE TABLE multi_opts_test (id int, body text); INSERT INTO multi_opts_test SELECT i, 'text' || i FROM generate_series(1, 100) i; -- Index 1: default options CREATE INDEX multi_opts_idx1 ON multi_opts_test USING tre (body); -- Index 2: custom range size CREATE INDEX multi_opts_idx2 ON multi_opts_test USING tre (body) WITH (range_size_blocks=256); -- Index 3: pending list merged on INSERT past 128 kB CREATE INDEX multi_opts_idx3 ON multi_opts_test USING tre (body) WITH (pending_list_limit=128); -- All indexes should work; the planner can use one for a selective -- pattern. Version-robust: assert plan-node category via a single -- text token (no exact plan-shape dump, which differs across PG -- majors and across which of the three indexes the planner picks). CREATE FUNCTION multi_opts_uses_index(q text) RETURNS boolean LANGUAGE plpgsql AS $$ DECLARE r record; node text; BEGIN -- Silence the version-independent "reloptions not initialized" -- WARNING that planning these indexes emits, so the expected output -- is a single deterministic token. SET LOCAL client_min_messages = error; FOR r IN EXECUTE 'EXPLAIN (FORMAT JSON) ' || q LOOP node := r."QUERY PLAN"->0->'Plan'->>'Node Type'; END LOOP; RETURN node ILIKE '%Index%' OR node ILIKE '%Bitmap%'; END $$; SET enable_seqscan = off; SELECT CASE WHEN multi_opts_uses_index($$SELECT * FROM multi_opts_test WHERE body %~~ tre_pattern('text1', 0)$$) THEN 'multi_opts_uses_index' ELSE 'multi_opts_seq' END AS multi_opts_plan; SET enable_seqscan = on; DROP FUNCTION multi_opts_uses_index(text); -- Cleanup DROP TABLE multi_opts_test CASCADE; -- -- Test 6: Pattern cache behavior under complexity guard -- -- Cache should work normally for simple patterns SELECT tre_amatch('test', 'test', 0); SELECT tre_amatch('test', 'test', 0); -- cache hit -- Different pattern SELECT tre_amatch('another', 'another', 0); -- -- Test 7: CHECK_FOR_INTERRUPTS (documentation only) -- -- Manual test procedure: -- 1. Run a complex query with a pattern that generates deep recursion: -- SELECT * FROM large_table WHERE body %~~ tre_pattern('(a+)+', 0); -- 2. Press Ctrl-C during execution -- 3. Verify the query is canceled within 1-2 seconds -- This cannot be tested in pg_regress without external tooling RESET ALL;