-- 0.8.0: the coarse index (find_ivf). Probing every list it answers exactly what find answers; it -- never serves a reading whose text changed; and a reading recorded after the build is found. \set VERBOSITY terse CREATE EXTENSION pg_phasevec; SELECT extversion FROM pg_extension WHERE extname = 'pg_phasevec'; extversion ------------ 0.16.0 (1 row) SELECT phasevec.reader_register('r256', repeat('a', 64), 256) > 0 AS r256; r256 ------ t (1 row) CREATE TABLE docs (id int PRIMARY KEY, body text); INSERT INTO docs SELECT g, 'doc ' || g FROM generate_series(1, 2000) g; SELECT phasevec.watch('docs', 'docs'::regclass, 'id', 'r.body', 'r256') > 0 AS watch; watch ------- t (1 row) -- deterministic codes of 256 dimensions; code256 = their signs CREATE FUNCTION code_of(g int) RETURNS smallint[] LANGUAGE sql IMMUTABLE AS $$ SELECT array(SELECT (((g * 31 + j * 17 * (g % 7 + 1) + (g / 13) * j) % 255) - 127)::smallint FROM generate_series(1, 256) j) $$; CREATE FUNCTION signs_of(c smallint[]) RETURNS bit(256) LANGUAGE sql IMMUTABLE AS $$ SELECT string_agg(CASE WHEN x > 0 THEN '1' ELSE '0' END, '' ORDER BY o)::bit(256) FROM unnest(c) WITH ORDINALITY u(x, o) $$; SELECT count(phasevec.record('docs', s.key, 'r256', s.text_hash, 0::bigint, code_of(s.key::int), NULL, signs_of(code_of(s.key::int)))) FROM phasevec.sources s; count ------- 2000 (1 row) -- no index yet: find_ivf says so, it does not fall back to anything SELECT * FROM phasevec.find_ivf('docs', 0, code_of(100), 10, false, 200, signs_of(code_of(100)), 4); ERROR: phasevec: watch docs has no coarse index for its reader SELECT phasevec.ivf_build('docs') AS lists; lists ------- 45 (1 row) -- every list probed and m = every row: exactly find's answer SELECT (SELECT array_agg(key ORDER BY score DESC, key) FROM phasevec.find('docs', 0, code_of(100), 10, false, 2000, signs_of(code_of(100)))) = (SELECT array_agg(key ORDER BY score DESC, key) FROM phasevec.find_ivf('docs', 0, code_of(100), 10, false, 2000, signs_of(code_of(100)), 1000)) AS same_as_find; same_as_find -------------- t (1 row) -- the question's own row comes first, with a few lists SELECT key, round(score::numeric, 4) AS score, state FROM phasevec.find_ivf('docs', 0, code_of(100), 1, false, 200, signs_of(code_of(100)), 8); key | score | state -----+--------+------- 100 | 1.0000 | fresh (1 row) -- its text changes: in the same transaction it is no longer served (its reading is stale) BEGIN; UPDATE docs SET body = 'changed' WHERE id = 100; SELECT count(*) AS served_stale FROM phasevec.find_ivf('docs', 0, code_of(100), 10, false, 2000, signs_of(code_of(100)), 1000) WHERE key = '100'; served_stale -------------- 0 (1 row) ROLLBACK; -- a row written and read after the build: found without rebuilding (the delta) INSERT INTO docs VALUES (5000, 'doc 5000'); SELECT count(phasevec.record('docs', s.key, 'r256', s.text_hash, 0::bigint, code_of(100), NULL, signs_of(code_of(100)))) FROM phasevec.sources s WHERE s.key = '5000'; count ------- 1 (1 row) SELECT key FROM phasevec.find_ivf('docs', 0, code_of(100), 2, false, 200, signs_of(code_of(100)), 8) ORDER BY key; key ------ 100 5000 (2 rows) -- coverage still refused below the declared minimum, as find does INSERT INTO docs SELECT g, 'new ' || g FROM generate_series(6000, 6500) g; SELECT count(*) FROM phasevec.find_ivf('docs', 0, code_of(100), 10, false, 200, signs_of(code_of(100)), 8); ERROR: phasevec: watch docs covers 80.0% of its rows and declared 95.0%: pending 20.0% DROP TABLE docs; DROP FUNCTION code_of(int); DROP FUNCTION signs_of(smallint[]); DROP EXTENSION pg_phasevec;