-- C2 residual (1.5.4): ambulkdelete cleans INLINE postings. -- -- Small posting lists (< PG_TRE_INLINE_POSTING_MAX TIDs per trigram) -- are stored inline in the upper-tree leaf entry rather than in an -- out-of-line posting tree. Before 1.5.4 ambulkdelete skipped inline -- postings; now it repacks the leaf's inline region in place. This -- test deletes rows, VACUUMs (driving ambulkdelete over inline -- postings), and verifies (a) the index-scan row set still equals the -- seq-scan row set, (b) deleted rows are gone, and (c) the in-place -- inline rewrite did not corrupt the index (subsequent scans succeed). CREATE EXTENSION IF NOT EXISTS pg_tre; DROP TABLE IF EXISTS vinl; CREATE TABLE vinl (id serial PRIMARY KEY, body text); -- A modest row count keeps every trigram's posting list inline. INSERT INTO vinl(body) SELECT (ARRAY['apple pie','cherry tart','apple fritter','banana split', 'apple cobbler','grape jelly','cherry cordial','plum cake'])[1 + (g % 8)] FROM generate_series(1, 400) g; SET client_min_messages = 'warning'; CREATE INDEX vinl_idx ON vinl USING tre (body); RESET client_min_messages; -- Drain anything pending into the inline postings first. VACUUM vinl; CREATE OR REPLACE FUNCTION vinl_diff(pat text) RETURNS text LANGUAGE plpgsql AS $$ DECLARE seq_ids int[]; idx_ids int[]; BEGIN SET LOCAL enable_indexscan=off; SET LOCAL enable_bitmapscan=off; SET LOCAL enable_seqscan=on; EXECUTE format('SELECT array_agg(id ORDER BY id) FROM vinl ' 'WHERE tre_amatch(body, %L, 0)', pat) INTO seq_ids; SET LOCAL enable_seqscan=off; SET LOCAL enable_indexscan=on; SET LOCAL enable_bitmapscan=on; EXECUTE format('SELECT array_agg(id ORDER BY id) FROM vinl ' 'WHERE body %%~~ tre_pattern(%L, 0)', pat) INTO idx_ids; IF seq_ids IS NOT DISTINCT FROM idx_ids THEN RETURN 'OK ' || pat; END IF; RETURN format('BAD %s seq=%s idx=%s', pat, seq_ids, idx_ids); END$$; -- Baseline: index agrees with the heap. SELECT vinl_diff('apple'); SELECT vinl_diff('cherry'); SELECT vinl_diff('grape'); -- Delete a large fraction of rows, then VACUUM: ambulkdelete must strip -- the dead TIDs from the inline postings. DELETE FROM vinl WHERE id % 2 = 0; VACUUM vinl; -- After the inline rewrite, the index still agrees with the heap and -- the deleted (even-id) rows are absent from both sides. SELECT vinl_diff('apple'); SELECT vinl_diff('cherry'); SELECT vinl_diff('grape'); SELECT vinl_diff('banana'); SELECT vinl_diff('plum'); -- Delete everything matching one term, VACUUM, and confirm the emptied -- inline posting matches nothing (and does not error). DELETE FROM vinl WHERE body LIKE 'grape%'; VACUUM vinl; SELECT vinl_diff('grape'); -- both sides empty SELECT vinl_diff('apple'); -- unaffected term still consistent DROP FUNCTION vinl_diff(text); DROP INDEX vinl_idx; DROP TABLE vinl;