-- The group leaf a set-returning function reads (the jsonb array behind jsonb_array_elements, the object behind jsonb_each) is found by its attribute, its path and
-- its kind alone, with none of the tests a scalar leaf gets (not lossy, no '#' in its name): a group leaf is never lossy and has no name, and the virtual leaves
-- (the presence leaf '
#present', the members read as expressions) are scalars, which the kind keeps out. This pins it: an index with a lossy scalar leaf (char
-- without a length), the presence leaf and both group kinds gives the rows of the normal plan.
CREATE EXTENSION pg_sift;
SET max_parallel_workers_per_gather = 0;
SET sift.agg_force = on;
CREATE TABLE gl (id int PRIMARY KEY, g int, c bpchar, doc jsonb, m jsonb) WITH (autovacuum_enabled = false);
INSERT INTO gl SELECT i, i % 7, CASE i % 4 WHEN 0 THEN 'a' WHEN 1 THEN 'a ' WHEN 2 THEN 'b' ELSE NULL END,
CASE WHEN i % 11 = 0 THEN NULL WHEN i % 13 = 0 THEN '{}'
ELSE jsonb_build_object('items', jsonb_build_array(jsonb_build_object('tag', 't' || i % 5, 'n', i % 9), jsonb_build_object('tag', 't' || (i + 1) % 5, 'n', NULL),
jsonb_build_object('n', i % 3))) END,
CASE WHEN i % 17 = 0 THEN NULL ELSE jsonb_build_object('x', i % 5 + 0.5, 'y', i % 3 + 0.25) END
FROM generate_series(1, 2000) i;
SET client_min_messages = warning;
CREATE INDEX gl_col ON gl USING sift_col (id, g, c, doc, m) WITH (paths = 'doc.items[]{tag text, n int4}, m.* float8');
RESET client_min_messages;
VACUUM (FREEZE, ANALYZE) gl;
CREATE FUNCTION rows_of(q text, agg bool) RETURNS text LANGUAGE plpgsql AS $$
DECLARE r text;
BEGIN
PERFORM set_config('sift.enable_agg', agg::text, true);
EXECUTE format('SELECT coalesce(string_agg(x::text, E''\n'' ORDER BY x::text), '''') FROM (%s) x', q) INTO r;
RETURN r;
END $$;
CREATE FUNCTION uses_sift(q text) RETURNS bool LANGUAGE plpgsql AS $$
DECLARE l text;
BEGIN
PERFORM set_config('sift.enable_agg', 'true', true);
FOR l IN EXECUTE 'EXPLAIN (COSTS OFF) ' || q LOOP
IF l LIKE '%SiftAgg%' THEN RETURN true; END IF;
END LOOP;
RETURN false;
END $$;
CREATE TABLE shapes (q text PRIMARY KEY, sql text);
INSERT INTO shapes VALUES
('elements by tag', $q$SELECT e->>'tag' AS tag, count(*) AS n, sum((e->>'n')::int) AS s FROM gl, jsonb_array_elements(doc->'items') e GROUP BY 1$q$),
('elements by the lossy column', $q$SELECT c, count(*) AS n, sum((e->>'n')::int) AS s FROM gl, jsonb_array_elements(doc->'items') e GROUP BY 1$q$),
('elements of a filtered row set', $q$SELECT g, count(*) AS n FROM gl, jsonb_array_elements(doc->'items') e WHERE c = 'a' AND e->>'tag' = 't1' GROUP BY 1$q$),
('exists over the elements', $q$SELECT g, count(*) AS n FROM gl WHERE EXISTS (SELECT 1 FROM jsonb_array_elements(doc->'items') e WHERE e->>'tag' = 't2') GROUP BY 1$q$),
('document is null beside the elements', $q$SELECT count(*) FILTER (WHERE doc IS NULL) AS nulls, count(*) FILTER (WHERE doc = '{}') AS empty, count(*) AS n FROM gl$q$),
('each of the keys', $q$SELECT key, count(*) AS n, sum(value::float8) AS s FROM gl, jsonb_each(m) GROUP BY 1$q$),
('each by the lossy column', $q$SELECT c, count(*) AS n FROM gl, jsonb_each(m) GROUP BY 1$q$),
('each of a filtered row set', $q$SELECT key, count(*) AS n FROM gl, jsonb_each(m) WHERE c IS NOT NULL AND g < 4 GROUP BY 1$q$),
('elements of rows with a column', $q$SELECT g, count(*) AS n FROM gl, jsonb_array_elements(doc->'items') e WHERE c IS NOT NULL GROUP BY 1$q$);
SELECT q, uses_sift(sql) AS on_index, rows_of(sql, true) = rows_of(sql, false) AS same_rows FROM shapes ORDER BY q;
DROP TABLE shapes;
DROP FUNCTION rows_of(text, bool), uses_sift(text);
DROP TABLE gl;
DROP EXTENSION pg_sift;