-- The ids of the integer keys of a group (agg/intids.c): a table of ids that is pages while the keys are in a small or dense range and a hash otherwise. -- The table against a plain hash over many shapes of keys, and the groups of keys of every integer type (negative, NULL, the ends of int64, dense and -- sparse, an expression of a column, a numeric expression beyond int64, NaN and -0 floats) equal to the normal plan's rows. CREATE EXTENSION pg_sift; CREATE EXTENSION pg_sift_helpers; CREATE EXTENSION pg_sift_test; SELECT bad, paged > 0 AS paged, hashed > 0 AS hashed, up > 0 AS up, down > 0 AS down FROM sift._test_intids(7, 400); bad | paged | hashed | up | down -----+-------+--------+----+------ 0 | t | t | t | t (1 row) SET max_parallel_workers_per_gather = 0; SET work_mem = '256MB'; CREATE TABLE d (id int, i4 int, i8 bigint, sp bigint, ext bigint, dt date, ts timestamptz, f8 float8, nu numeric(12,2), v int) WITH (fillfactor = 20, autovacuum_enabled = false); INSERT INTO d SELECT i, (i * 37 % 700) - 350, CASE WHEN i % 11 = 0 THEN NULL ELSE 4000000000 + (i * 37 % 5000) END, i::bigint * 1000003 * 7919, CASE i % 4 WHEN 0 THEN 9223372036854775807 WHEN 1 THEN -9223372036854775808 WHEN 2 THEN 9223372036854775800 + (i % 5) ELSE -9223372036854775800 - (i % 5) END, date '2020-01-01' + (i % 900), timestamptz '2020-01-01 00:00:00+00' + (i * 61 % 20000) * interval '1 second', CASE i % 7 WHEN 0 THEN 'NaN'::float8 WHEN 1 THEN '-0'::float8 WHEN 2 THEN 0 WHEN 3 THEN 'Infinity'::float8 WHEN 4 THEN '-Infinity'::float8 ELSE (i % 50) * 0.5 END, (i % 3000) / 100.0, (i * 7) % 1000 FROM generate_series(1, 12000) i; CREATE INDEX d_col ON d USING sift_col (id, i4, i8, sp, ext, dt, ts, f8, nu, v); NOTICE: sift_col index "d_col": 1 advice (track_changes) DETAIL: advice: track_changes = the table has no one-column primary key HINT: Reasons, costs and how to turn each off: SELECT * FROM sift.index_choices('d_col'); VACUUM ANALYZE d; CREATE FUNCTION d_rows(q text, push bool) RETURNS text LANGUAGE plpgsql AS $$ DECLARE r text; BEGIN PERFORM set_config('sift.enable_agg', push::text, true); EXECUTE format('SELECT md5(string_agg(q::text, E''\n'' ORDER BY q::text)) || '' '' || count(*) FROM (%s) q', q) INTO r; RETURN r; END $$; CREATE FUNCTION d_scan(q text) RETURNS bool LANGUAGE sql AS $$ SELECT sift_helpers.plan_has_siftagg_pushdown_on(q) $$; CREATE TABLE d_q (n int, q text); INSERT INTO d_q VALUES (1, 'SELECT i4, count(*) AS n, sum(v) AS s FROM d GROUP BY i4'), (2, 'SELECT i8, count(*) AS n, sum(v) AS s FROM d GROUP BY i8'), (3, 'SELECT sp, count(*) AS n, sum(v) AS s FROM d GROUP BY sp'), (4, 'SELECT ext, count(*) AS n, sum(v) AS s FROM d GROUP BY ext'), (5, 'SELECT dt, count(*) AS n, sum(v) AS s FROM d GROUP BY dt'), (6, 'SELECT ts, count(*) AS n, sum(v) AS s FROM d GROUP BY ts'), (7, 'SELECT f8, count(*) AS n, sum(v) AS s FROM d GROUP BY f8'), (8, 'SELECT nu, count(*) AS n, sum(v) AS s FROM d GROUP BY nu'), (9, 'SELECT i8 / 3 AS g, count(*) AS n, sum(v) AS s FROM d GROUP BY 1'), (10, 'SELECT i4 / 2 AS g, count(*) AS n, sum(v) AS s FROM d GROUP BY 1'), (11, 'SELECT i4 % 100 AS g, count(*) AS n, sum(v) AS s FROM d GROUP BY 1'), (12, 'SELECT ext / 4 AS g, count(*) AS n, sum(v) AS s FROM d GROUP BY 1'), (13, 'SELECT sp % 997 AS g, count(*) AS n, sum(v) AS s FROM d GROUP BY 1'), (14, 'SELECT dt - 5 AS g, count(*) AS n, sum(v) AS s FROM d GROUP BY 1'), (15, 'SELECT date_trunc(''hour'', ts) AS g, count(*) AS n, sum(v) AS s FROM d GROUP BY 1'), (16, 'SELECT i4, i8, count(*) AS n, sum(v) AS s FROM d GROUP BY i4, i8'), (17, 'SELECT i8 / 3 AS g, i4 / 2 AS h, count(*) AS n, min(v) AS lo FROM d GROUP BY 1, 2'), (18, 'SELECT i8 / 3 AS g, count(*) AS n FROM d WHERE id <= 3000 GROUP BY 1'), (19, 'SELECT i4, count(DISTINCT i8) AS n FROM d GROUP BY i4'), (20, 'SELECT ext / 4 AS g, count(*) AS n FROM d WHERE id % 3 = 0 GROUP BY 1'), (21, 'SELECT nu * 100000000000000000000 AS g, count(*) AS n, sum(v) AS s FROM d GROUP BY 1'), (22, 'SELECT ext * 4.0 AS g, count(*) AS n FROM d GROUP BY 1'); -- the plan is the scan, and the rows are the normal plan's SELECT n, d_scan(q) AS scan, d_rows(q, true) = d_rows(q, false) AS same FROM d_q ORDER BY n; n | scan | same ----+------+------ 1 | t | t 2 | t | t 3 | t | t 4 | t | t 5 | t | t 6 | t | t 7 | t | t 8 | t | t 9 | t | t 10 | t | t 11 | t | t 12 | t | t 13 | t | t 14 | t | t 15 | t | t 16 | t | t 17 | t | t 18 | t | t 19 | t | t 20 | t | t 21 | t | t 22 | t | t (22 rows) -- the groups in hash mode (the arrays of group ids forced too small), whatever the table of key ids SET sift.test_direct_max = 64; SELECT n, d_rows(q, true) = d_rows(q, false) AS hash FROM d_q ORDER BY n; n | hash ----+------ 1 | t 2 | t 3 | t 4 | t 5 | t 6 | t 7 | t 8 | t 9 | t 10 | t 11 | t 12 | t 13 | t 14 | t 15 | t 16 | t 17 | t 18 | t 19 | t 20 | t 21 | t 22 | t (22 rows) RESET sift.test_direct_max; DROP TABLE d_q; DROP FUNCTION d_rows(text, bool), d_scan(text); DROP TABLE d; DROP EXTENSION pg_sift_helpers; DROP EXTENSION pg_sift_test, pg_sift CASCADE;