diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/triggers.out /tmp/cirrus-ci-build/build/testrun/regress/regress/results/triggers.out --- /tmp/cirrus-ci-build/src/test/regress/expected/triggers.out 2024-09-20 21:41:45.762024524 +0000 +++ /tmp/cirrus-ci-build/build/testrun/regress/regress/results/triggers.out 2024-09-20 21:46:05.185625512 +0000 @@ -2499,1259 +2499,10 @@ return new; end; $$; -insert into parted values (1, 1, 'uno uno'); -- works -update parted set c = c || ' v6'; -- works -select tableoid::regclass, * from parted; - tableoid | a | b | c -------------+---+---+---------------------------------- - parted_1_1 | 1 | 1 | uno uno v1 v6 did UPDATE - parted_1_1 | 1 | 1 | uno uno did INSERT v6 did UPDATE -(2 rows) - --- update itself moves tuple to new partition; trigger still works -truncate table parted; -create table parted_2 partition of parted for values in (2); -insert into parted values (1, 1, 'uno uno v5'); -update parted set a = 2; -select tableoid::regclass, * from parted; - tableoid | a | b | c -----------+---+---+--------------------------------------------- - parted_2 | 2 | 1 | uno uno v5 did INSERT did UPDATE did INSERT -(1 row) - --- both trigger and update change the partition -create or replace function parted_trigfunc2() returns trigger language plpgsql as $$ -begin - new.a = new.a + 1; - return new; -end; -$$; -create trigger t2 before update on parted - for each row execute function parted_trigfunc2(); -truncate table parted; -insert into parted values (1, 1, 'uno uno v6'); -create table parted_3 partition of parted for values in (3); -update parted set a = a + 1; -select tableoid::regclass, * from parted; - tableoid | a | b | c -----------+---+---+--------------------------------------------- - parted_3 | 3 | 1 | uno uno v6 did INSERT did UPDATE did INSERT -(1 row) - --- there's no partition for a=0, but this update works anyway because --- the trigger causes the tuple to be routed to another partition -update parted set a = 0; -select tableoid::regclass, * from parted; - tableoid | a | b | c -------------+---+---+------------------------------------------------------------------- - parted_1_1 | 1 | 1 | uno uno v6 did INSERT did UPDATE did INSERT did UPDATE did INSERT -(1 row) - -drop table parted; -create table parted (a int, b int, c text) partition by list ((a + b)); -create or replace function parted_trigfunc() returns trigger language plpgsql as $$ -begin - new.a = new.a + new.b; - return new; -end; -$$; -create table parted_1 partition of parted for values in (1, 2); -create table parted_2 partition of parted for values in (3, 4); -create trigger t before insert or update on parted - for each row execute function parted_trigfunc(); -insert into parted values (0, 1, 'zero win'); -insert into parted values (1, 1, 'one fail'); -ERROR: moving row to another partition during a BEFORE FOR EACH ROW trigger is not supported -DETAIL: Before executing trigger "t", the row was to be in partition "public.parted_1". -insert into parted values (1, 2, 'two fail'); -ERROR: moving row to another partition during a BEFORE FOR EACH ROW trigger is not supported -DETAIL: Before executing trigger "t", the row was to be in partition "public.parted_2". -select * from parted; - a | b | c ----+---+---------- - 1 | 1 | zero win -(1 row) - -drop table parted; -drop function parted_trigfunc(); --- --- Constraint triggers and partitioned tables -create table parted_constr_ancestor (a int, b text) - partition by range (b); -create table parted_constr (a int, b text) - partition by range (b); -alter table parted_constr_ancestor attach partition parted_constr - for values from ('aaaa') to ('zzzz'); -create table parted1_constr (a int, b text); -alter table parted_constr attach partition parted1_constr - for values from ('aaaa') to ('bbbb'); -create constraint trigger parted_trig after insert on parted_constr_ancestor - deferrable - for each row execute procedure trigger_notice_ab(); -create constraint trigger parted_trig_two after insert on parted_constr - deferrable initially deferred - for each row when (bark(new.b) AND new.a % 2 = 1) - execute procedure trigger_notice_ab(); --- The immediate constraint is fired immediately; the WHEN clause of the --- deferred constraint is also called immediately. The deferred constraint --- is fired at commit time. -begin; -insert into parted_constr values (1, 'aardvark'); -NOTICE: aardvark <- woof! -NOTICE: trigger parted_trig on parted1_constr AFTER INSERT for ROW: (a,b)=(1,aardvark) -insert into parted1_constr values (2, 'aardwolf'); -NOTICE: aardwolf <- woof! -NOTICE: trigger parted_trig on parted1_constr AFTER INSERT for ROW: (a,b)=(2,aardwolf) -insert into parted_constr_ancestor values (3, 'aasvogel'); -NOTICE: aasvogel <- woof! -NOTICE: trigger parted_trig on parted1_constr AFTER INSERT for ROW: (a,b)=(3,aasvogel) -commit; -NOTICE: trigger parted_trig_two on parted1_constr AFTER INSERT for ROW: (a,b)=(1,aardvark) -NOTICE: trigger parted_trig_two on parted1_constr AFTER INSERT for ROW: (a,b)=(3,aasvogel) --- The WHEN clause is immediate, and both constraint triggers are fired at --- commit time. -begin; -set constraints parted_trig deferred; -insert into parted_constr values (1, 'aardvark'); -NOTICE: aardvark <- woof! -insert into parted1_constr values (2, 'aardwolf'), (3, 'aasvogel'); -NOTICE: aardwolf <- woof! -NOTICE: aasvogel <- woof! -commit; -NOTICE: trigger parted_trig on parted1_constr AFTER INSERT for ROW: (a,b)=(1,aardvark) -NOTICE: trigger parted_trig_two on parted1_constr AFTER INSERT for ROW: (a,b)=(1,aardvark) -NOTICE: trigger parted_trig on parted1_constr AFTER INSERT for ROW: (a,b)=(2,aardwolf) -NOTICE: trigger parted_trig on parted1_constr AFTER INSERT for ROW: (a,b)=(3,aasvogel) -NOTICE: trigger parted_trig_two on parted1_constr AFTER INSERT for ROW: (a,b)=(3,aasvogel) -drop table parted_constr_ancestor; -drop function bark(text); --- Test that the WHEN clause is set properly to partitions -create table parted_trigger (a int, b text) partition by range (a); -create table parted_trigger_1 partition of parted_trigger for values from (0) to (1000); -create table parted_trigger_2 (drp int, a int, b text); -alter table parted_trigger_2 drop column drp; -alter table parted_trigger attach partition parted_trigger_2 for values from (1000) to (2000); -create trigger parted_trigger after update on parted_trigger - for each row when (new.a % 2 = 1 and length(old.b) >= 2) execute procedure trigger_notice_ab(); -create table parted_trigger_3 (b text, a int) partition by range (length(b)); -create table parted_trigger_3_1 partition of parted_trigger_3 for values from (1) to (3); -create table parted_trigger_3_2 partition of parted_trigger_3 for values from (3) to (5); -alter table parted_trigger attach partition parted_trigger_3 for values from (2000) to (3000); -insert into parted_trigger values - (0, 'a'), (1, 'bbb'), (2, 'bcd'), (3, 'c'), - (1000, 'c'), (1001, 'ddd'), (1002, 'efg'), (1003, 'f'), - (2000, 'e'), (2001, 'fff'), (2002, 'ghi'), (2003, 'h'); -update parted_trigger set a = a + 2; -- notice for odd 'a' values, long 'b' values -NOTICE: trigger parted_trigger on parted_trigger_1 AFTER UPDATE for ROW: (a,b)=(3,bbb) -NOTICE: trigger parted_trigger on parted_trigger_2 AFTER UPDATE for ROW: (a,b)=(1003,ddd) -NOTICE: trigger parted_trigger on parted_trigger_3_2 AFTER UPDATE for ROW: (a,b)=(2003,fff) -drop table parted_trigger; --- try a constraint trigger, also -create table parted_referenced (a int); -create table unparted_trigger (a int, b text); -- for comparison purposes -create table parted_trigger (a int, b text) partition by range (a); -create table parted_trigger_1 partition of parted_trigger for values from (0) to (1000); -create table parted_trigger_2 (drp int, a int, b text); -alter table parted_trigger_2 drop column drp; -alter table parted_trigger attach partition parted_trigger_2 for values from (1000) to (2000); -create constraint trigger parted_trigger after update on parted_trigger - from parted_referenced - for each row execute procedure trigger_notice_ab(); -create constraint trigger parted_trigger after update on unparted_trigger - from parted_referenced - for each row execute procedure trigger_notice_ab(); -create table parted_trigger_3 (b text, a int) partition by range (length(b)); -create table parted_trigger_3_1 partition of parted_trigger_3 for values from (1) to (3); -create table parted_trigger_3_2 partition of parted_trigger_3 for values from (3) to (5); -alter table parted_trigger attach partition parted_trigger_3 for values from (2000) to (3000); -select tgname, conname, t.tgrelid::regclass, t.tgconstrrelid::regclass, - c.conrelid::regclass, c.confrelid::regclass - from pg_trigger t join pg_constraint c on (t.tgconstraint = c.oid) - where tgname = 'parted_trigger' - order by t.tgrelid::regclass::text; - tgname | conname | tgrelid | tgconstrrelid | conrelid | confrelid -----------------+----------------+--------------------+-------------------+--------------------+----------- - parted_trigger | parted_trigger | parted_trigger | parted_referenced | parted_trigger | - - parted_trigger | parted_trigger | parted_trigger_1 | parted_referenced | parted_trigger_1 | - - parted_trigger | parted_trigger | parted_trigger_2 | parted_referenced | parted_trigger_2 | - - parted_trigger | parted_trigger | parted_trigger_3 | parted_referenced | parted_trigger_3 | - - parted_trigger | parted_trigger | parted_trigger_3_1 | parted_referenced | parted_trigger_3_1 | - - parted_trigger | parted_trigger | parted_trigger_3_2 | parted_referenced | parted_trigger_3_2 | - - parted_trigger | parted_trigger | unparted_trigger | parted_referenced | unparted_trigger | - -(7 rows) - -drop table parted_referenced, parted_trigger, unparted_trigger; --- verify that the "AFTER UPDATE OF columns" event is propagated correctly -create table parted_trigger (a int, b text) partition by range (a); -create table parted_trigger_1 partition of parted_trigger for values from (0) to (1000); -create table parted_trigger_2 (drp int, a int, b text); -alter table parted_trigger_2 drop column drp; -alter table parted_trigger attach partition parted_trigger_2 for values from (1000) to (2000); -create trigger parted_trigger after update of b on parted_trigger - for each row execute procedure trigger_notice_ab(); -create table parted_trigger_3 (b text, a int) partition by range (length(b)); -create table parted_trigger_3_1 partition of parted_trigger_3 for values from (1) to (4); -create table parted_trigger_3_2 partition of parted_trigger_3 for values from (4) to (8); -alter table parted_trigger attach partition parted_trigger_3 for values from (2000) to (3000); -insert into parted_trigger values (0, 'a'), (1000, 'c'), (2000, 'e'), (2001, 'eeee'); -update parted_trigger set a = a + 2; -- no notices here -update parted_trigger set b = b || 'b'; -- all triggers should fire -NOTICE: trigger parted_trigger on parted_trigger_1 AFTER UPDATE for ROW: (a,b)=(2,ab) -NOTICE: trigger parted_trigger on parted_trigger_2 AFTER UPDATE for ROW: (a,b)=(1002,cb) -NOTICE: trigger parted_trigger on parted_trigger_3_1 AFTER UPDATE for ROW: (a,b)=(2002,eb) -NOTICE: trigger parted_trigger on parted_trigger_3_2 AFTER UPDATE for ROW: (a,b)=(2003,eeeeb) -drop table parted_trigger; -drop function trigger_notice_ab(); --- Make sure we don't end up with unnecessary copies of triggers, when --- cloning them. -create table trg_clone (a int) partition by range (a); -create table trg_clone1 partition of trg_clone for values from (0) to (1000); -alter table trg_clone add constraint uniq unique (a) deferrable; -create table trg_clone2 partition of trg_clone for values from (1000) to (2000); -create table trg_clone3 partition of trg_clone for values from (2000) to (3000) - partition by range (a); -create table trg_clone_3_3 partition of trg_clone3 for values from (2000) to (2100); -select tgrelid::regclass, count(*) from pg_trigger - where tgrelid::regclass in ('trg_clone', 'trg_clone1', 'trg_clone2', - 'trg_clone3', 'trg_clone_3_3') - group by tgrelid::regclass order by tgrelid::regclass; - tgrelid | count ----------------+------- - trg_clone | 1 - trg_clone1 | 1 - trg_clone2 | 1 - trg_clone3 | 1 - trg_clone_3_3 | 1 -(5 rows) - -drop table trg_clone; --- Test the interaction between ALTER TABLE .. DISABLE TRIGGER and --- both kinds of inheritance. Historically, legacy inheritance has --- not recursed to children, so that behavior is preserved. -create table parent (a int); -create table child1 () inherits (parent); -create function trig_nothing() returns trigger language plpgsql - as $$ begin return null; end $$; -create trigger tg after insert on parent - for each row execute function trig_nothing(); -create trigger tg after insert on child1 - for each row execute function trig_nothing(); -alter table parent disable trigger tg; -select tgrelid::regclass, tgname, tgenabled from pg_trigger - where tgrelid in ('parent'::regclass, 'child1'::regclass) - order by tgrelid::regclass::text; - tgrelid | tgname | tgenabled ----------+--------+----------- - child1 | tg | O - parent | tg | D -(2 rows) - -alter table only parent enable always trigger tg; -select tgrelid::regclass, tgname, tgenabled from pg_trigger - where tgrelid in ('parent'::regclass, 'child1'::regclass) - order by tgrelid::regclass::text; - tgrelid | tgname | tgenabled ----------+--------+----------- - child1 | tg | O - parent | tg | A -(2 rows) - -drop table parent, child1; -create table parent (a int) partition by list (a); -create table child1 partition of parent for values in (1); -create trigger tg after insert on parent - for each row execute procedure trig_nothing(); -create trigger tg_stmt after insert on parent - for statement execute procedure trig_nothing(); -select tgrelid::regclass, tgname, tgenabled from pg_trigger - where tgrelid in ('parent'::regclass, 'child1'::regclass) - order by tgrelid::regclass::text, tgname; - tgrelid | tgname | tgenabled ----------+---------+----------- - child1 | tg | O - parent | tg | O - parent | tg_stmt | O -(3 rows) - -alter table only parent enable always trigger tg; -- no recursion because ONLY -alter table parent enable always trigger tg_stmt; -- no recursion because statement trigger -select tgrelid::regclass, tgname, tgenabled from pg_trigger - where tgrelid in ('parent'::regclass, 'child1'::regclass) - order by tgrelid::regclass::text, tgname; - tgrelid | tgname | tgenabled ----------+---------+----------- - child1 | tg | O - parent | tg | A - parent | tg_stmt | A -(3 rows) - --- The following is a no-op for the parent trigger but not so --- for the child trigger, so recursion should be applied. -alter table parent enable always trigger tg; -select tgrelid::regclass, tgname, tgenabled from pg_trigger - where tgrelid in ('parent'::regclass, 'child1'::regclass) - order by tgrelid::regclass::text, tgname; - tgrelid | tgname | tgenabled ----------+---------+----------- - child1 | tg | A - parent | tg | A - parent | tg_stmt | A -(3 rows) - --- This variant malfunctioned in some releases. -alter table parent disable trigger user; -select tgrelid::regclass, tgname, tgenabled from pg_trigger - where tgrelid in ('parent'::regclass, 'child1'::regclass) - order by tgrelid::regclass::text, tgname; - tgrelid | tgname | tgenabled ----------+---------+----------- - child1 | tg | D - parent | tg | D - parent | tg_stmt | D -(3 rows) - -drop table parent, child1; --- Check processing of foreign key triggers -create table parent (a int primary key, f int references parent) - partition by list (a); -create table child1 partition of parent for values in (1); -select tgrelid::regclass, rtrim(tgname, '0123456789') as tgname, - tgfoid::regproc, tgenabled - from pg_trigger where tgrelid in ('parent'::regclass, 'child1'::regclass) - order by tgrelid::regclass::text, tgfoid; - tgrelid | tgname | tgfoid | tgenabled ----------+-------------------------+------------------------+----------- - child1 | RI_ConstraintTrigger_c_ | "RI_FKey_check_ins" | O - child1 | RI_ConstraintTrigger_c_ | "RI_FKey_check_upd" | O - parent | RI_ConstraintTrigger_c_ | "RI_FKey_check_ins" | O - parent | RI_ConstraintTrigger_c_ | "RI_FKey_check_upd" | O - parent | RI_ConstraintTrigger_a_ | "RI_FKey_noaction_del" | O - parent | RI_ConstraintTrigger_a_ | "RI_FKey_noaction_upd" | O -(6 rows) - -alter table parent disable trigger all; -select tgrelid::regclass, rtrim(tgname, '0123456789') as tgname, - tgfoid::regproc, tgenabled - from pg_trigger where tgrelid in ('parent'::regclass, 'child1'::regclass) - order by tgrelid::regclass::text, tgfoid; - tgrelid | tgname | tgfoid | tgenabled ----------+-------------------------+------------------------+----------- - child1 | RI_ConstraintTrigger_c_ | "RI_FKey_check_ins" | D - child1 | RI_ConstraintTrigger_c_ | "RI_FKey_check_upd" | D - parent | RI_ConstraintTrigger_c_ | "RI_FKey_check_ins" | D - parent | RI_ConstraintTrigger_c_ | "RI_FKey_check_upd" | D - parent | RI_ConstraintTrigger_a_ | "RI_FKey_noaction_del" | D - parent | RI_ConstraintTrigger_a_ | "RI_FKey_noaction_upd" | D -(6 rows) - -drop table parent, child1; --- Verify that firing state propagates correctly on creation, too -CREATE TABLE trgfire (i int) PARTITION BY RANGE (i); -CREATE TABLE trgfire1 PARTITION OF trgfire FOR VALUES FROM (1) TO (10); -CREATE OR REPLACE FUNCTION tgf() RETURNS trigger LANGUAGE plpgsql - AS $$ begin raise exception 'except'; end $$; -CREATE TRIGGER tg AFTER INSERT ON trgfire FOR EACH ROW EXECUTE FUNCTION tgf(); -INSERT INTO trgfire VALUES (1); -ERROR: except -CONTEXT: PL/pgSQL function tgf() line 1 at RAISE -ALTER TABLE trgfire DISABLE TRIGGER tg; -INSERT INTO trgfire VALUES (1); -CREATE TABLE trgfire2 PARTITION OF trgfire FOR VALUES FROM (10) TO (20); -INSERT INTO trgfire VALUES (11); -CREATE TABLE trgfire3 (LIKE trgfire); -ALTER TABLE trgfire ATTACH PARTITION trgfire3 FOR VALUES FROM (20) TO (30); -INSERT INTO trgfire VALUES (21); -CREATE TABLE trgfire4 PARTITION OF trgfire FOR VALUES FROM (30) TO (40) PARTITION BY LIST (i); -CREATE TABLE trgfire4_30 PARTITION OF trgfire4 FOR VALUES IN (30); -INSERT INTO trgfire VALUES (30); -CREATE TABLE trgfire5 (LIKE trgfire) PARTITION BY LIST (i); -CREATE TABLE trgfire5_40 PARTITION OF trgfire5 FOR VALUES IN (40); -ALTER TABLE trgfire ATTACH PARTITION trgfire5 FOR VALUES FROM (40) TO (50); -INSERT INTO trgfire VALUES (40); -SELECT tgrelid::regclass, tgenabled FROM pg_trigger - WHERE tgrelid::regclass IN (SELECT oid from pg_class where relname LIKE 'trgfire%') - ORDER BY tgrelid::regclass::text; - tgrelid | tgenabled --------------+----------- - trgfire | D - trgfire1 | D - trgfire2 | D - trgfire3 | D - trgfire4 | D - trgfire4_30 | D - trgfire5 | D - trgfire5_40 | D -(8 rows) - -ALTER TABLE trgfire ENABLE TRIGGER tg; -INSERT INTO trgfire VALUES (1); -ERROR: except -CONTEXT: PL/pgSQL function tgf() line 1 at RAISE -INSERT INTO trgfire VALUES (11); -ERROR: except -CONTEXT: PL/pgSQL function tgf() line 1 at RAISE -INSERT INTO trgfire VALUES (21); -ERROR: except -CONTEXT: PL/pgSQL function tgf() line 1 at RAISE -INSERT INTO trgfire VALUES (30); -ERROR: except -CONTEXT: PL/pgSQL function tgf() line 1 at RAISE -INSERT INTO trgfire VALUES (40); -ERROR: except -CONTEXT: PL/pgSQL function tgf() line 1 at RAISE -DROP TABLE trgfire; -DROP FUNCTION tgf(); --- --- Test the interaction between transition tables and both kinds of --- inheritance. We'll dump the contents of the transition tables in a --- format that shows the attribute order, so that we can distinguish --- tuple formats (though not dropped attributes). --- -create or replace function dump_insert() returns trigger language plpgsql as -$$ - begin - raise notice 'trigger = %, new table = %', - TG_NAME, - (select string_agg(new_table::text, ', ' order by a) from new_table); - return null; - end; -$$; -create or replace function dump_update() returns trigger language plpgsql as -$$ - begin - raise notice 'trigger = %, old table = %, new table = %', - TG_NAME, - (select string_agg(old_table::text, ', ' order by a) from old_table), - (select string_agg(new_table::text, ', ' order by a) from new_table); - return null; - end; -$$; -create or replace function dump_delete() returns trigger language plpgsql as -$$ - begin - raise notice 'trigger = %, old table = %', - TG_NAME, - (select string_agg(old_table::text, ', ' order by a) from old_table); - return null; - end; -$$; --- --- Verify behavior of statement triggers on partition hierarchy with --- transition tables. Tuples should appear to each trigger in the --- format of the relation the trigger is attached to. --- --- set up a partition hierarchy with some different TupleDescriptors -create table parent (a text, b int) partition by list (a); --- a child matching parent -create table child1 partition of parent for values in ('AAA'); --- a child with a dropped column -create table child2 (x int, a text, b int); -alter table child2 drop column x; -alter table parent attach partition child2 for values in ('BBB'); --- a child with a different column order -create table child3 (b int, a text); -alter table parent attach partition child3 for values in ('CCC'); -create trigger parent_insert_trig - after insert on parent referencing new table as new_table - for each statement execute procedure dump_insert(); -create trigger parent_update_trig - after update on parent referencing old table as old_table new table as new_table - for each statement execute procedure dump_update(); -create trigger parent_delete_trig - after delete on parent referencing old table as old_table - for each statement execute procedure dump_delete(); -create trigger child1_insert_trig - after insert on child1 referencing new table as new_table - for each statement execute procedure dump_insert(); -create trigger child1_update_trig - after update on child1 referencing old table as old_table new table as new_table - for each statement execute procedure dump_update(); -create trigger child1_delete_trig - after delete on child1 referencing old table as old_table - for each statement execute procedure dump_delete(); -create trigger child2_insert_trig - after insert on child2 referencing new table as new_table - for each statement execute procedure dump_insert(); -create trigger child2_update_trig - after update on child2 referencing old table as old_table new table as new_table - for each statement execute procedure dump_update(); -create trigger child2_delete_trig - after delete on child2 referencing old table as old_table - for each statement execute procedure dump_delete(); -create trigger child3_insert_trig - after insert on child3 referencing new table as new_table - for each statement execute procedure dump_insert(); -create trigger child3_update_trig - after update on child3 referencing old table as old_table new table as new_table - for each statement execute procedure dump_update(); -create trigger child3_delete_trig - after delete on child3 referencing old table as old_table - for each statement execute procedure dump_delete(); -SELECT trigger_name, event_manipulation, event_object_schema, event_object_table, - action_order, action_condition, action_orientation, action_timing, - action_reference_old_table, action_reference_new_table - FROM information_schema.triggers - WHERE event_object_table IN ('parent', 'child1', 'child2', 'child3') - ORDER BY trigger_name COLLATE "C", 2; - trigger_name | event_manipulation | event_object_schema | event_object_table | action_order | action_condition | action_orientation | action_timing | action_reference_old_table | action_reference_new_table ---------------------+--------------------+---------------------+--------------------+--------------+------------------+--------------------+---------------+----------------------------+---------------------------- - child1_delete_trig | DELETE | public | child1 | 1 | | STATEMENT | AFTER | old_table | - child1_insert_trig | INSERT | public | child1 | 1 | | STATEMENT | AFTER | | new_table - child1_update_trig | UPDATE | public | child1 | 1 | | STATEMENT | AFTER | old_table | new_table - child2_delete_trig | DELETE | public | child2 | 1 | | STATEMENT | AFTER | old_table | - child2_insert_trig | INSERT | public | child2 | 1 | | STATEMENT | AFTER | | new_table - child2_update_trig | UPDATE | public | child2 | 1 | | STATEMENT | AFTER | old_table | new_table - child3_delete_trig | DELETE | public | child3 | 1 | | STATEMENT | AFTER | old_table | - child3_insert_trig | INSERT | public | child3 | 1 | | STATEMENT | AFTER | | new_table - child3_update_trig | UPDATE | public | child3 | 1 | | STATEMENT | AFTER | old_table | new_table - parent_delete_trig | DELETE | public | parent | 1 | | STATEMENT | AFTER | old_table | - parent_insert_trig | INSERT | public | parent | 1 | | STATEMENT | AFTER | | new_table - parent_update_trig | UPDATE | public | parent | 1 | | STATEMENT | AFTER | old_table | new_table -(12 rows) - --- insert directly into children sees respective child-format tuples -insert into child1 values ('AAA', 42); -NOTICE: trigger = child1_insert_trig, new table = (AAA,42) -insert into child2 values ('BBB', 42); -NOTICE: trigger = child2_insert_trig, new table = (BBB,42) -insert into child3 values (42, 'CCC'); -NOTICE: trigger = child3_insert_trig, new table = (42,CCC) --- update via parent sees parent-format tuples -update parent set b = b + 1; -NOTICE: trigger = parent_update_trig, old table = (AAA,42), (BBB,42), (CCC,42), new table = (AAA,43), (BBB,43), (CCC,43) --- delete via parent sees parent-format tuples -delete from parent; -NOTICE: trigger = parent_delete_trig, old table = (AAA,43), (BBB,43), (CCC,43) --- insert into parent sees parent-format tuples -insert into parent values ('AAA', 42); -NOTICE: trigger = parent_insert_trig, new table = (AAA,42) -insert into parent values ('BBB', 42); -NOTICE: trigger = parent_insert_trig, new table = (BBB,42) -insert into parent values ('CCC', 42); -NOTICE: trigger = parent_insert_trig, new table = (CCC,42) --- delete from children sees respective child-format tuples -delete from child1; -NOTICE: trigger = child1_delete_trig, old table = (AAA,42) -delete from child2; -NOTICE: trigger = child2_delete_trig, old table = (BBB,42) -delete from child3; -NOTICE: trigger = child3_delete_trig, old table = (42,CCC) --- copy into parent sees parent-format tuples -copy parent (a, b) from stdin; -NOTICE: trigger = parent_insert_trig, new table = (AAA,42), (BBB,42), (CCC,42) --- DML affecting parent sees tuples collected from children even if --- there is no transition table trigger on the children -drop trigger child1_insert_trig on child1; -drop trigger child1_update_trig on child1; -drop trigger child1_delete_trig on child1; -drop trigger child2_insert_trig on child2; -drop trigger child2_update_trig on child2; -drop trigger child2_delete_trig on child2; -drop trigger child3_insert_trig on child3; -drop trigger child3_update_trig on child3; -drop trigger child3_delete_trig on child3; -delete from parent; -NOTICE: trigger = parent_delete_trig, old table = (AAA,42), (BBB,42), (CCC,42) --- copy into parent sees tuples collected from children even if there --- is no transition-table trigger on the children -copy parent (a, b) from stdin; -NOTICE: trigger = parent_insert_trig, new table = (AAA,42), (BBB,42), (CCC,42) --- insert into parent with a before trigger on a child tuple before --- insertion, and we capture the newly modified row in parent format -create or replace function intercept_insert() returns trigger language plpgsql as -$$ - begin - new.b = new.b + 1000; - return new; - end; -$$; -create trigger intercept_insert_child3 - before insert on child3 - for each row execute procedure intercept_insert(); --- insert, parent trigger sees post-modification parent-format tuple -insert into parent values ('AAA', 42), ('BBB', 42), ('CCC', 66); -NOTICE: trigger = parent_insert_trig, new table = (AAA,42), (BBB,42), (CCC,1066) --- copy, parent trigger sees post-modification parent-format tuple -copy parent (a, b) from stdin; -NOTICE: trigger = parent_insert_trig, new table = (AAA,42), (BBB,42), (CCC,1234) -drop table child1, child2, child3, parent; -drop function intercept_insert(); --- --- Verify prohibition of row triggers with transition triggers on --- partitions --- -create table parent (a text, b int) partition by list (a); -create table child partition of parent for values in ('AAA'); --- adding row trigger with transition table fails -create trigger child_row_trig - after insert on child referencing new table as new_table - for each row execute procedure dump_insert(); -ERROR: ROW triggers with transition tables are not supported on partitions --- detaching it first works -alter table parent detach partition child; -create trigger child_row_trig - after insert on child referencing new table as new_table - for each row execute procedure dump_insert(); --- but now we're not allowed to reattach it -alter table parent attach partition child for values in ('AAA'); -ERROR: trigger "child_row_trig" prevents table "child" from becoming a partition -DETAIL: ROW triggers with transition tables are not supported on partitions. --- drop the trigger, and now we're allowed to attach it again -drop trigger child_row_trig on child; -alter table parent attach partition child for values in ('AAA'); -drop table child, parent; --- --- Verify behavior of statement triggers on (non-partition) --- inheritance hierarchy with transition tables; similar to the --- partition case, except there is no rerouting on insertion and child --- tables can have extra columns --- --- set up inheritance hierarchy with different TupleDescriptors -create table parent (a text, b int); --- a child matching parent -create table child1 () inherits (parent); --- a child with a different column order -create table child2 (b int, a text); -alter table child2 inherit parent; --- a child with an extra column -create table child3 (c text) inherits (parent); -create trigger parent_insert_trig - after insert on parent referencing new table as new_table - for each statement execute procedure dump_insert(); -create trigger parent_update_trig - after update on parent referencing old table as old_table new table as new_table - for each statement execute procedure dump_update(); -create trigger parent_delete_trig - after delete on parent referencing old table as old_table - for each statement execute procedure dump_delete(); -create trigger child1_insert_trig - after insert on child1 referencing new table as new_table - for each statement execute procedure dump_insert(); -create trigger child1_update_trig - after update on child1 referencing old table as old_table new table as new_table - for each statement execute procedure dump_update(); -create trigger child1_delete_trig - after delete on child1 referencing old table as old_table - for each statement execute procedure dump_delete(); -create trigger child2_insert_trig - after insert on child2 referencing new table as new_table - for each statement execute procedure dump_insert(); -create trigger child2_update_trig - after update on child2 referencing old table as old_table new table as new_table - for each statement execute procedure dump_update(); -create trigger child2_delete_trig - after delete on child2 referencing old table as old_table - for each statement execute procedure dump_delete(); -create trigger child3_insert_trig - after insert on child3 referencing new table as new_table - for each statement execute procedure dump_insert(); -create trigger child3_update_trig - after update on child3 referencing old table as old_table new table as new_table - for each statement execute procedure dump_update(); -create trigger child3_delete_trig - after delete on child3 referencing old table as old_table - for each statement execute procedure dump_delete(); --- insert directly into children sees respective child-format tuples -insert into child1 values ('AAA', 42); -NOTICE: trigger = child1_insert_trig, new table = (AAA,42) -insert into child2 values (42, 'BBB'); -NOTICE: trigger = child2_insert_trig, new table = (42,BBB) -insert into child3 values ('CCC', 42, 'foo'); -NOTICE: trigger = child3_insert_trig, new table = (CCC,42,foo) --- update via parent sees parent-format tuples -update parent set b = b + 1; -NOTICE: trigger = parent_update_trig, old table = (AAA,42), (BBB,42), (CCC,42), new table = (AAA,43), (BBB,43), (CCC,43) --- delete via parent sees parent-format tuples -delete from parent; -NOTICE: trigger = parent_delete_trig, old table = (AAA,43), (BBB,43), (CCC,43) --- reinsert values into children for next test... -insert into child1 values ('AAA', 42); -NOTICE: trigger = child1_insert_trig, new table = (AAA,42) -insert into child2 values (42, 'BBB'); -NOTICE: trigger = child2_insert_trig, new table = (42,BBB) -insert into child3 values ('CCC', 42, 'foo'); -NOTICE: trigger = child3_insert_trig, new table = (CCC,42,foo) --- delete from children sees respective child-format tuples -delete from child1; -NOTICE: trigger = child1_delete_trig, old table = (AAA,42) -delete from child2; -NOTICE: trigger = child2_delete_trig, old table = (42,BBB) -delete from child3; -NOTICE: trigger = child3_delete_trig, old table = (CCC,42,foo) --- copy into parent sees parent-format tuples (no rerouting, so these --- are really inserted into the parent) -copy parent (a, b) from stdin; -NOTICE: trigger = parent_insert_trig, new table = (AAA,42), (BBB,42), (CCC,42) --- same behavior for copy if there is an index (interesting because rows are --- captured by a different code path in copyfrom.c if there are indexes) -create index on parent(b); -copy parent (a, b) from stdin; -NOTICE: trigger = parent_insert_trig, new table = (DDD,42) --- DML affecting parent sees tuples collected from children even if --- there is no transition table trigger on the children -drop trigger child1_insert_trig on child1; -drop trigger child1_update_trig on child1; -drop trigger child1_delete_trig on child1; -drop trigger child2_insert_trig on child2; -drop trigger child2_update_trig on child2; -drop trigger child2_delete_trig on child2; -drop trigger child3_insert_trig on child3; -drop trigger child3_update_trig on child3; -drop trigger child3_delete_trig on child3; -delete from parent; -NOTICE: trigger = parent_delete_trig, old table = (AAA,42), (BBB,42), (CCC,42), (DDD,42) -drop table child1, child2, child3, parent; --- --- Verify prohibition of row triggers with transition triggers on --- inheritance children --- -create table parent (a text, b int); -create table child () inherits (parent); --- adding row trigger with transition table fails -create trigger child_row_trig - after insert on child referencing new table as new_table - for each row execute procedure dump_insert(); -ERROR: ROW triggers with transition tables are not supported on inheritance children --- disinheriting it first works -alter table child no inherit parent; -create trigger child_row_trig - after insert on child referencing new table as new_table - for each row execute procedure dump_insert(); --- but now we're not allowed to make it inherit anymore -alter table child inherit parent; -ERROR: trigger "child_row_trig" prevents table "child" from becoming an inheritance child -DETAIL: ROW triggers with transition tables are not supported in inheritance hierarchies. --- drop the trigger, and now we're allowed to make it inherit again -drop trigger child_row_trig on child; -alter table child inherit parent; -drop table child, parent; --- --- Verify behavior of queries with wCTEs, where multiple transition --- tuplestores can be active at the same time because there are --- multiple DML statements that might fire triggers with transition --- tables --- -create table table1 (a int); -create table table2 (a text); -create trigger table1_trig - after insert on table1 referencing new table as new_table - for each statement execute procedure dump_insert(); -create trigger table2_trig - after insert on table2 referencing new table as new_table - for each statement execute procedure dump_insert(); -with wcte as (insert into table1 values (42)) - insert into table2 values ('hello world'); -NOTICE: trigger = table2_trig, new table = ("hello world") -NOTICE: trigger = table1_trig, new table = (42) -with wcte as (insert into table1 values (43)) - insert into table1 values (44); -NOTICE: trigger = table1_trig, new table = (43), (44) -select * from table1; - a ----- - 42 - 44 - 43 -(3 rows) - -select * from table2; - a -------------- - hello world -(1 row) - -drop table table1; -drop table table2; --- --- Verify behavior of INSERT ... ON CONFLICT DO UPDATE ... with --- transition tables. --- -create table my_table (a int primary key, b text); -create trigger my_table_insert_trig - after insert on my_table referencing new table as new_table - for each statement execute procedure dump_insert(); -create trigger my_table_update_trig - after update on my_table referencing old table as old_table new table as new_table - for each statement execute procedure dump_update(); --- inserts only -insert into my_table values (1, 'AAA'), (2, 'BBB') - on conflict (a) do - update set b = my_table.b || ':' || excluded.b; -NOTICE: trigger = my_table_update_trig, old table = , new table = -NOTICE: trigger = my_table_insert_trig, new table = (1,AAA), (2,BBB) --- mixture of inserts and updates -insert into my_table values (1, 'AAA'), (2, 'BBB'), (3, 'CCC'), (4, 'DDD') - on conflict (a) do - update set b = my_table.b || ':' || excluded.b; -NOTICE: trigger = my_table_update_trig, old table = (1,AAA), (2,BBB), new table = (1,AAA:AAA), (2,BBB:BBB) -NOTICE: trigger = my_table_insert_trig, new table = (3,CCC), (4,DDD) --- updates only -insert into my_table values (3, 'CCC'), (4, 'DDD') - on conflict (a) do - update set b = my_table.b || ':' || excluded.b; -NOTICE: trigger = my_table_update_trig, old table = (3,CCC), (4,DDD), new table = (3,CCC:CCC), (4,DDD:DDD) -NOTICE: trigger = my_table_insert_trig, new table = --- --- now using a partitioned table --- -create table iocdu_tt_parted (a int primary key, b text) partition by list (a); -create table iocdu_tt_parted1 partition of iocdu_tt_parted for values in (1); -create table iocdu_tt_parted2 partition of iocdu_tt_parted for values in (2); -create table iocdu_tt_parted3 partition of iocdu_tt_parted for values in (3); -create table iocdu_tt_parted4 partition of iocdu_tt_parted for values in (4); -create trigger iocdu_tt_parted_insert_trig - after insert on iocdu_tt_parted referencing new table as new_table - for each statement execute procedure dump_insert(); -create trigger iocdu_tt_parted_update_trig - after update on iocdu_tt_parted referencing old table as old_table new table as new_table - for each statement execute procedure dump_update(); --- inserts only -insert into iocdu_tt_parted values (1, 'AAA'), (2, 'BBB') - on conflict (a) do - update set b = iocdu_tt_parted.b || ':' || excluded.b; -NOTICE: trigger = iocdu_tt_parted_update_trig, old table = , new table = -NOTICE: trigger = iocdu_tt_parted_insert_trig, new table = (1,AAA), (2,BBB) --- mixture of inserts and updates -insert into iocdu_tt_parted values (1, 'AAA'), (2, 'BBB'), (3, 'CCC'), (4, 'DDD') - on conflict (a) do - update set b = iocdu_tt_parted.b || ':' || excluded.b; -NOTICE: trigger = iocdu_tt_parted_update_trig, old table = (1,AAA), (2,BBB), new table = (1,AAA:AAA), (2,BBB:BBB) -NOTICE: trigger = iocdu_tt_parted_insert_trig, new table = (3,CCC), (4,DDD) --- updates only -insert into iocdu_tt_parted values (3, 'CCC'), (4, 'DDD') - on conflict (a) do - update set b = iocdu_tt_parted.b || ':' || excluded.b; -NOTICE: trigger = iocdu_tt_parted_update_trig, old table = (3,CCC), (4,DDD), new table = (3,CCC:CCC), (4,DDD:DDD) -NOTICE: trigger = iocdu_tt_parted_insert_trig, new table = -drop table iocdu_tt_parted; --- --- Verify that you can't create a trigger with transition tables for --- more than one event. --- -create trigger my_table_multievent_trig - after insert or update on my_table referencing new table as new_table - for each statement execute procedure dump_insert(); -ERROR: transition tables cannot be specified for triggers with more than one event --- --- Verify that you can't create a trigger with transition tables with --- a column list. --- -create trigger my_table_col_update_trig - after update of b on my_table referencing new table as new_table - for each statement execute procedure dump_insert(); -ERROR: transition tables cannot be specified for triggers with column lists -drop table my_table; --- --- Test firing of triggers with transition tables by foreign key cascades --- -create table refd_table (a int primary key, b text); -create table trig_table (a int, b text, - foreign key (a) references refd_table on update cascade on delete cascade -); -create trigger trig_table_before_trig - before insert or update or delete on trig_table - for each statement execute procedure trigger_func('trig_table'); -create trigger trig_table_insert_trig - after insert on trig_table referencing new table as new_table - for each statement execute procedure dump_insert(); -create trigger trig_table_update_trig - after update on trig_table referencing old table as old_table new table as new_table - for each statement execute procedure dump_update(); -create trigger trig_table_delete_trig - after delete on trig_table referencing old table as old_table - for each statement execute procedure dump_delete(); -insert into refd_table values - (1, 'one'), - (2, 'two'), - (3, 'three'); -insert into trig_table values - (1, 'one a'), - (1, 'one b'), - (2, 'two a'), - (2, 'two b'), - (3, 'three a'), - (3, 'three b'); -NOTICE: trigger_func(trig_table) called: action = INSERT, when = BEFORE, level = STATEMENT -NOTICE: trigger = trig_table_insert_trig, new table = (1,"one a"), (1,"one b"), (2,"two a"), (2,"two b"), (3,"three a"), (3,"three b") -update refd_table set a = 11 where b = 'one'; -NOTICE: trigger_func(trig_table) called: action = UPDATE, when = BEFORE, level = STATEMENT -NOTICE: trigger = trig_table_update_trig, old table = (1,"one a"), (1,"one b"), new table = (11,"one a"), (11,"one b") -select * from trig_table; - a | b -----+--------- - 2 | two a - 2 | two b - 3 | three a - 3 | three b - 11 | one a - 11 | one b -(6 rows) - -delete from refd_table where length(b) = 3; -NOTICE: trigger_func(trig_table) called: action = DELETE, when = BEFORE, level = STATEMENT -NOTICE: trigger = trig_table_delete_trig, old table = (2,"two a"), (2,"two b"), (11,"one a"), (11,"one b") -select * from trig_table; - a | b ----+--------- - 3 | three a - 3 | three b -(2 rows) - -drop table refd_table, trig_table; --- --- Test that we can drop a not-yet-fired deferred trigger --- -create table refd_table (id int primary key); -create table trig_table (fk int references refd_table initially deferred); -begin; -insert into trig_table values (1); -drop table refd_table cascade; -NOTICE: drop cascades to constraint trig_table_fk_fkey on table trig_table -commit; -drop table trig_table; --- --- self-referential FKs are even more fun --- -create table self_ref (a int primary key, - b int references self_ref(a) on delete cascade); -create trigger self_ref_before_trig - before delete on self_ref - for each statement execute procedure trigger_func('self_ref'); -create trigger self_ref_r_trig - after delete on self_ref referencing old table as old_table - for each row execute procedure dump_delete(); -create trigger self_ref_s_trig - after delete on self_ref referencing old table as old_table - for each statement execute procedure dump_delete(); -insert into self_ref values (1, null), (2, 1), (3, 2); -delete from self_ref where a = 1; -NOTICE: trigger_func(self_ref) called: action = DELETE, when = BEFORE, level = STATEMENT -NOTICE: trigger = self_ref_r_trig, old table = (1,), (2,1) -NOTICE: trigger_func(self_ref) called: action = DELETE, when = BEFORE, level = STATEMENT -NOTICE: trigger = self_ref_r_trig, old table = (1,), (2,1) -NOTICE: trigger = self_ref_s_trig, old table = (1,), (2,1) -NOTICE: trigger = self_ref_r_trig, old table = (3,2) -NOTICE: trigger = self_ref_s_trig, old table = (3,2) --- without AR trigger, cascaded deletes all end up in one transition table -drop trigger self_ref_r_trig on self_ref; -insert into self_ref values (1, null), (2, 1), (3, 2), (4, 3); -delete from self_ref where a = 1; -NOTICE: trigger_func(self_ref) called: action = DELETE, when = BEFORE, level = STATEMENT -NOTICE: trigger = self_ref_s_trig, old table = (1,), (2,1), (3,2), (4,3) -drop table self_ref; --- --- test transition tables with MERGE --- -create table merge_target_table (a int primary key, b text); -create trigger merge_target_table_insert_trig - after insert on merge_target_table referencing new table as new_table - for each statement execute procedure dump_insert(); -create trigger merge_target_table_update_trig - after update on merge_target_table referencing old table as old_table new table as new_table - for each statement execute procedure dump_update(); -create trigger merge_target_table_delete_trig - after delete on merge_target_table referencing old table as old_table - for each statement execute procedure dump_delete(); -create table merge_source_table (a int, b text); -insert into merge_source_table - values (1, 'initial1'), (2, 'initial2'), - (3, 'initial3'), (4, 'initial4'); -merge into merge_target_table t -using merge_source_table s -on t.a = s.a -when not matched then - insert values (a, b); -NOTICE: trigger = merge_target_table_insert_trig, new table = (1,initial1), (2,initial2), (3,initial3), (4,initial4) -merge into merge_target_table t -using merge_source_table s -on t.a = s.a -when matched and s.a <= 2 then - update set b = t.b || ' updated by merge' -when matched and s.a > 2 then - delete -when not matched then - insert values (a, b); -NOTICE: trigger = merge_target_table_delete_trig, old table = (3,initial3), (4,initial4) -NOTICE: trigger = merge_target_table_update_trig, old table = (1,initial1), (2,initial2), new table = (1,"initial1 updated by merge"), (2,"initial2 updated by merge") -NOTICE: trigger = merge_target_table_insert_trig, new table = -merge into merge_target_table t -using merge_source_table s -on t.a = s.a -when matched and s.a <= 2 then - update set b = t.b || ' updated again by merge' -when matched and s.a > 2 then - delete -when not matched then - insert values (a, b); -NOTICE: trigger = merge_target_table_delete_trig, old table = -NOTICE: trigger = merge_target_table_update_trig, old table = (1,"initial1 updated by merge"), (2,"initial2 updated by merge"), new table = (1,"initial1 updated by merge updated again by merge"), (2,"initial2 updated by merge updated again by merge") -NOTICE: trigger = merge_target_table_insert_trig, new table = (3,initial3), (4,initial4) -drop table merge_source_table, merge_target_table; --- cleanup -drop function dump_insert(); -drop function dump_update(); -drop function dump_delete(); --- --- Tests for CREATE OR REPLACE TRIGGER --- -create table my_table (id integer); -create function funcA() returns trigger as $$ -begin - raise notice 'hello from funcA'; - return null; -end; $$ language plpgsql; -create function funcB() returns trigger as $$ -begin - raise notice 'hello from funcB'; - return null; -end; $$ language plpgsql; -create trigger my_trig - after insert on my_table - for each row execute procedure funcA(); -create trigger my_trig - before insert on my_table - for each row execute procedure funcB(); -- should fail -ERROR: trigger "my_trig" for relation "my_table" already exists -insert into my_table values (1); -NOTICE: hello from funcA -create or replace trigger my_trig - before insert on my_table - for each row execute procedure funcB(); -- OK -insert into my_table values (2); -- this insert should become a no-op -NOTICE: hello from funcB -table my_table; - id ----- - 1 -(1 row) - -drop table my_table; --- test CREATE OR REPLACE TRIGGER on partition table -create table parted_trig (a int) partition by range (a); -create table parted_trig_1 partition of parted_trig - for values from (0) to (1000) partition by range (a); -create table parted_trig_1_1 partition of parted_trig_1 for values from (0) to (100); -create table parted_trig_2 partition of parted_trig for values from (1000) to (2000); -create table default_parted_trig partition of parted_trig default; --- test that trigger can be replaced by another one --- at the same level of partition table -create or replace trigger my_trig - after insert on parted_trig - for each row execute procedure funcA(); -insert into parted_trig (a) values (50); -NOTICE: hello from funcA -create or replace trigger my_trig - after insert on parted_trig - for each row execute procedure funcB(); -insert into parted_trig (a) values (50); -NOTICE: hello from funcB --- test that child trigger cannot be replaced directly -create or replace trigger my_trig - after insert on parted_trig - for each row execute procedure funcA(); -insert into parted_trig (a) values (50); -NOTICE: hello from funcA -create or replace trigger my_trig - after insert on parted_trig_1 - for each row execute procedure funcB(); -- should fail -ERROR: trigger "my_trig" for relation "parted_trig_1" is an internal or a child trigger -insert into parted_trig (a) values (50); -NOTICE: hello from funcA -drop trigger my_trig on parted_trig; -insert into parted_trig (a) values (50); --- test that user trigger can be overwritten by one defined at upper level -create trigger my_trig - after insert on parted_trig_1 - for each row execute procedure funcA(); -insert into parted_trig (a) values (50); -NOTICE: hello from funcA -create trigger my_trig - after insert on parted_trig - for each row execute procedure funcB(); -- should fail -ERROR: trigger "my_trig" for relation "parted_trig_1" already exists -insert into parted_trig (a) values (50); -NOTICE: hello from funcA -create or replace trigger my_trig - after insert on parted_trig - for each row execute procedure funcB(); -insert into parted_trig (a) values (50); -NOTICE: hello from funcB --- cleanup -drop table parted_trig; -drop function funcA(); -drop function funcB(); --- Leave around some objects for other tests -create table trigger_parted (a int primary key) partition by list (a); -create function trigger_parted_trigfunc() returns trigger language plpgsql as - $$ begin end; $$; -create trigger aft_row after insert or update on trigger_parted - for each row execute function trigger_parted_trigfunc(); -create table trigger_parted_p1 partition of trigger_parted for values in (1) - partition by list (a); -create table trigger_parted_p1_1 partition of trigger_parted_p1 for values in (1); -create table trigger_parted_p2 partition of trigger_parted for values in (2) - partition by list (a); -create table trigger_parted_p2_2 partition of trigger_parted_p2 for values in (2); -alter table only trigger_parted_p2 disable trigger aft_row; -alter table trigger_parted_p2_2 enable always trigger aft_row; --- verify transition table conversion slot's lifetime --- https://postgr.es/m/39a71864-b120-5a5c-8cc5-c632b6f16761@amazon.com -create table convslot_test_parent (col1 text primary key); -create table convslot_test_child (col1 text primary key, - foreign key (col1) references convslot_test_parent(col1) on delete cascade on update cascade -); -alter table convslot_test_child add column col2 text not null default 'tutu'; -insert into convslot_test_parent(col1) values ('1'); -insert into convslot_test_child(col1) values ('1'); -insert into convslot_test_parent(col1) values ('3'); -insert into convslot_test_child(col1) values ('3'); -create function convslot_trig1() -returns trigger -language plpgsql -AS $$ -begin -raise notice 'trigger = %, old_table = %', - TG_NAME, - (select string_agg(old_table::text, ', ' order by col1) from old_table); -return null; -end; $$; -create function convslot_trig2() -returns trigger -language plpgsql -AS $$ -begin -raise notice 'trigger = %, new table = %', - TG_NAME, - (select string_agg(new_table::text, ', ' order by col1) from new_table); -return null; -end; $$; -create trigger but_trigger after update on convslot_test_child -referencing new table as new_table -for each statement execute function convslot_trig2(); -update convslot_test_parent set col1 = col1 || '1'; -NOTICE: trigger = but_trigger, new table = (11,tutu), (31,tutu) -create function convslot_trig3() -returns trigger -language plpgsql -AS $$ -begin -raise notice 'trigger = %, old_table = %, new table = %', - TG_NAME, - (select string_agg(old_table::text, ', ' order by col1) from old_table), - (select string_agg(new_table::text, ', ' order by col1) from new_table); -return null; -end; $$; -create trigger but_trigger2 after update on convslot_test_child -referencing old table as old_table new table as new_table -for each statement execute function convslot_trig3(); -update convslot_test_parent set col1 = col1 || '1'; -NOTICE: trigger = but_trigger, new table = (111,tutu), (311,tutu) -NOTICE: trigger = but_trigger2, old_table = (11,tutu), (31,tutu), new table = (111,tutu), (311,tutu) -create trigger bdt_trigger after delete on convslot_test_child -referencing old table as old_table -for each statement execute function convslot_trig1(); -delete from convslot_test_parent; -NOTICE: trigger = bdt_trigger, old_table = (111,tutu), (311,tutu) -drop table convslot_test_child, convslot_test_parent; -drop function convslot_trig1(); -drop function convslot_trig2(); -drop function convslot_trig3(); --- Bug #17607: variant of above in which trigger function raises an error; --- we don't see any ill effects unless trigger tuple requires mapping -create table convslot_test_parent (id int primary key, val int) -partition by range (id); -create table convslot_test_part (val int, id int not null); -alter table convslot_test_parent - attach partition convslot_test_part for values from (1) to (1000); -create function convslot_trig4() returns trigger as -$$begin raise exception 'BOOM!'; end$$ language plpgsql; -create trigger convslot_test_parent_update - after update on convslot_test_parent - referencing old table as old_rows new table as new_rows - for each statement execute procedure convslot_trig4(); -insert into convslot_test_parent (id, val) values (1, 2); -begin; -savepoint svp; -update convslot_test_parent set val = 3; -- error expected -ERROR: BOOM! -CONTEXT: PL/pgSQL function convslot_trig4() line 1 at RAISE -rollback to savepoint svp; -rollback; -drop table convslot_test_parent; -drop function convslot_trig4(); --- Test trigger renaming on partitioned tables -create table grandparent (id int, primary key (id)) partition by range (id); -create table middle partition of grandparent for values from (1) to (10) -partition by range (id); -create table chi partition of middle for values from (1) to (5); -create table cho partition of middle for values from (6) to (10); -create function f () returns trigger as -$$ begin return new; end; $$ -language plpgsql; -create trigger a after insert on grandparent -for each row execute procedure f(); -alter trigger a on grandparent rename to b; -select tgrelid::regclass, tgname, -(select tgname from pg_trigger tr where tr.oid = pg_trigger.tgparentid) parent_tgname -from pg_trigger where tgrelid in (select relid from pg_partition_tree('grandparent')) -order by tgname, tgrelid::regclass::text COLLATE "C"; - tgrelid | tgname | parent_tgname --------------+--------+--------------- - chi | b | b - cho | b | b - grandparent | b | - middle | b | b -(4 rows) - -alter trigger a on only grandparent rename to b; -- ONLY not supported -ERROR: syntax error at or near "only" -LINE 1: alter trigger a on only grandparent rename to b; - ^ -alter trigger b on middle rename to c; -- can't rename trigger on partition -ERROR: cannot rename trigger "b" on table "middle" -HINT: Rename the trigger on the partitioned table "grandparent" instead. -create trigger c after insert on middle -for each row execute procedure f(); -alter trigger b on grandparent rename to c; -ERROR: trigger "c" for relation "middle" already exists --- Rename cascading does not affect statement triggers -create trigger p after insert on grandparent for each statement execute function f(); -create trigger p after insert on middle for each statement execute function f(); -alter trigger p on grandparent rename to q; -select tgrelid::regclass, tgname, -(select tgname from pg_trigger tr where tr.oid = pg_trigger.tgparentid) parent_tgname -from pg_trigger where tgrelid in (select relid from pg_partition_tree('grandparent')) -order by tgname, tgrelid::regclass::text COLLATE "C"; - tgrelid | tgname | parent_tgname --------------+--------+--------------- - chi | b | b - cho | b | b - grandparent | b | - middle | b | b - chi | c | c - cho | c | c - middle | c | - middle | p | - grandparent | q | -(9 rows) - -drop table grandparent; --- Trigger renaming does not recurse on legacy inheritance -create table parent (a int); -create table child () inherits (parent); -create trigger parenttrig after insert on parent -for each row execute procedure f(); -create trigger parenttrig after insert on child -for each row execute procedure f(); -alter trigger parenttrig on parent rename to anothertrig; -\d+ child - Table "public.child" - Column | Type | Collation | Nullable | Default | Storage | Stats target | Description ---------+---------+-----------+----------+---------+---------+--------------+------------- - a | integer | | | | plain | | -Triggers: - parenttrig AFTER INSERT ON child FOR EACH ROW EXECUTE FUNCTION f() -Inherits: parent - -drop table parent, child; -drop function f(); +WARNING: terminating connection because of crash of another server process +DETAIL: The postmaster has commanded this server process to roll back the current transaction and exit, because another server process exited abnormally and possibly corrupted shared memory. +HINT: In a moment you should be able to reconnect to the database and repeat your command. +server closed the connection unexpectedly + This probably means the server terminated abnormally + before or while processing the request. +connection to server was lost diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/inherit.out /tmp/cirrus-ci-build/build/testrun/regress/regress/results/inherit.out --- /tmp/cirrus-ci-build/src/test/regress/expected/inherit.out 2024-09-20 21:41:45.698024521 +0000 +++ /tmp/cirrus-ci-build/build/testrun/regress/regress/results/inherit.out 2024-09-20 21:46:05.193625501 +0000 @@ -2580,370 +2580,7 @@ (10 rows) explain (costs off) select * from mclparted where a in(0,1,2,4) order by a; - QUERY PLAN ------------------------------------------------------------------------------------- - Merge Append - Sort Key: mclparted.a - -> Index Only Scan using mclparted_0_null_a_idx on mclparted_0_null mclparted_1 - Index Cond: (a = ANY ('{0,1,2,4}'::integer[])) - -> Index Only Scan using mclparted1_a_idx on mclparted1 mclparted_2 - Index Cond: (a = ANY ('{0,1,2,4}'::integer[])) - -> Index Only Scan using mclparted2_a_idx on mclparted2 mclparted_3 - Index Cond: (a = ANY ('{0,1,2,4}'::integer[])) - -> Index Only Scan using mclparted4_a_idx on mclparted4 mclparted_4 - Index Cond: (a = ANY ('{0,1,2,4}'::integer[])) -(10 rows) - --- Ensure Append is used when the null partition is pruned -explain (costs off) select * from mclparted where a in(1,2,4) order by a; - QUERY PLAN ------------------------------------------------------------------------- - Append - -> Index Only Scan using mclparted1_a_idx on mclparted1 mclparted_1 - Index Cond: (a = ANY ('{1,2,4}'::integer[])) - -> Index Only Scan using mclparted2_a_idx on mclparted2 mclparted_2 - Index Cond: (a = ANY ('{1,2,4}'::integer[])) - -> Index Only Scan using mclparted4_a_idx on mclparted4 mclparted_3 - Index Cond: (a = ANY ('{1,2,4}'::integer[])) -(7 rows) - --- Ensure MergeAppend is used when the default partition is not pruned -explain (costs off) select * from mclparted where a in(1,2,4,100) order by a; - QUERY PLAN ------------------------------------------------------------------------------- - Merge Append - Sort Key: mclparted.a - -> Index Only Scan using mclparted1_a_idx on mclparted1 mclparted_1 - Index Cond: (a = ANY ('{1,2,4,100}'::integer[])) - -> Index Only Scan using mclparted2_a_idx on mclparted2 mclparted_2 - Index Cond: (a = ANY ('{1,2,4,100}'::integer[])) - -> Index Only Scan using mclparted4_a_idx on mclparted4 mclparted_3 - Index Cond: (a = ANY ('{1,2,4,100}'::integer[])) - -> Index Only Scan using mclparted_def_a_idx on mclparted_def mclparted_4 - Index Cond: (a = ANY ('{1,2,4,100}'::integer[])) -(10 rows) - -drop table mclparted; -reset enable_sort; -reset enable_bitmapscan; --- Ensure subplans which don't have a path with the correct pathkeys get --- sorted correctly. -drop index mcrparted_a_abs_c_idx; -create index on mcrparted1 (a, abs(b), c); -create index on mcrparted2 (a, abs(b), c); -create index on mcrparted3 (a, abs(b), c); -create index on mcrparted4 (a, abs(b), c); -explain (costs off) select * from mcrparted where a < 20 order by a, abs(b), c limit 1; - QUERY PLAN -------------------------------------------------------------------------------- - Limit - -> Append - -> Sort - Sort Key: mcrparted_1.a, (abs(mcrparted_1.b)), mcrparted_1.c - -> Seq Scan on mcrparted0 mcrparted_1 - Filter: (a < 20) - -> Index Scan using mcrparted1_a_abs_c_idx on mcrparted1 mcrparted_2 - Index Cond: (a < 20) - -> Index Scan using mcrparted2_a_abs_c_idx on mcrparted2 mcrparted_3 - Index Cond: (a < 20) - -> Index Scan using mcrparted3_a_abs_c_idx on mcrparted3 mcrparted_4 - Index Cond: (a < 20) -(12 rows) - -set enable_bitmapscan = 0; --- Ensure Append node can be used when the partition is ordered by some --- pathkeys which were deemed redundant. -explain (costs off) select * from mcrparted where a = 10 order by a, abs(b), c; - QUERY PLAN -------------------------------------------------------------------------- - Append - -> Index Scan using mcrparted1_a_abs_c_idx on mcrparted1 mcrparted_1 - Index Cond: (a = 10) - -> Index Scan using mcrparted2_a_abs_c_idx on mcrparted2 mcrparted_2 - Index Cond: (a = 10) -(5 rows) - -reset enable_bitmapscan; -drop table mcrparted; --- Ensure LIST partitions allow an Append to be used instead of a MergeAppend -create table bool_lp (b bool) partition by list(b); -create table bool_lp_true partition of bool_lp for values in(true); -create table bool_lp_false partition of bool_lp for values in(false); -create index on bool_lp (b); -explain (costs off) select * from bool_lp order by b; - QUERY PLAN ----------------------------------------------------------------------------- - Append - -> Index Only Scan using bool_lp_false_b_idx on bool_lp_false bool_lp_1 - -> Index Only Scan using bool_lp_true_b_idx on bool_lp_true bool_lp_2 -(3 rows) - -drop table bool_lp; --- Ensure const bool quals can be properly detected as redundant -create table bool_rp (b bool, a int) partition by range(b,a); -create table bool_rp_false_1k partition of bool_rp for values from (false,0) to (false,1000); -create table bool_rp_true_1k partition of bool_rp for values from (true,0) to (true,1000); -create table bool_rp_false_2k partition of bool_rp for values from (false,1000) to (false,2000); -create table bool_rp_true_2k partition of bool_rp for values from (true,1000) to (true,2000); -create index on bool_rp (b,a); -explain (costs off) select * from bool_rp where b = true order by b,a; - QUERY PLAN ----------------------------------------------------------------------------------- - Append - -> Index Only Scan using bool_rp_true_1k_b_a_idx on bool_rp_true_1k bool_rp_1 - Index Cond: (b = true) - -> Index Only Scan using bool_rp_true_2k_b_a_idx on bool_rp_true_2k bool_rp_2 - Index Cond: (b = true) -(5 rows) - -explain (costs off) select * from bool_rp where b = false order by b,a; - QUERY PLAN ------------------------------------------------------------------------------------- - Append - -> Index Only Scan using bool_rp_false_1k_b_a_idx on bool_rp_false_1k bool_rp_1 - Index Cond: (b = false) - -> Index Only Scan using bool_rp_false_2k_b_a_idx on bool_rp_false_2k bool_rp_2 - Index Cond: (b = false) -(5 rows) - -explain (costs off) select * from bool_rp where b = true order by a; - QUERY PLAN ----------------------------------------------------------------------------------- - Append - -> Index Only Scan using bool_rp_true_1k_b_a_idx on bool_rp_true_1k bool_rp_1 - Index Cond: (b = true) - -> Index Only Scan using bool_rp_true_2k_b_a_idx on bool_rp_true_2k bool_rp_2 - Index Cond: (b = true) -(5 rows) - -explain (costs off) select * from bool_rp where b = false order by a; - QUERY PLAN ------------------------------------------------------------------------------------- - Append - -> Index Only Scan using bool_rp_false_1k_b_a_idx on bool_rp_false_1k bool_rp_1 - Index Cond: (b = false) - -> Index Only Scan using bool_rp_false_2k_b_a_idx on bool_rp_false_2k bool_rp_2 - Index Cond: (b = false) -(5 rows) - -drop table bool_rp; --- Ensure an Append scan is chosen when the partition order is a subset of --- the required order. -create table range_parted (a int, b int, c int) partition by range(a, b); -create table range_parted1 partition of range_parted for values from (0,0) to (10,10); -create table range_parted2 partition of range_parted for values from (10,10) to (20,20); -create index on range_parted (a,b,c); -explain (costs off) select * from range_parted order by a,b,c; - QUERY PLAN -------------------------------------------------------------------------------------- - Append - -> Index Only Scan using range_parted1_a_b_c_idx on range_parted1 range_parted_1 - -> Index Only Scan using range_parted2_a_b_c_idx on range_parted2 range_parted_2 -(3 rows) - -explain (costs off) select * from range_parted order by a desc,b desc,c desc; - QUERY PLAN ----------------------------------------------------------------------------------------------- - Append - -> Index Only Scan Backward using range_parted2_a_b_c_idx on range_parted2 range_parted_2 - -> Index Only Scan Backward using range_parted1_a_b_c_idx on range_parted1 range_parted_1 -(3 rows) - -drop table range_parted; --- Check that we allow access to a child table's statistics when the user --- has permissions only for the parent table. -create table permtest_parent (a int, b text, c text) partition by list (a); -create table permtest_child (b text, c text, a int) partition by list (b); -create table permtest_grandchild (c text, b text, a int); -alter table permtest_child attach partition permtest_grandchild for values in ('a'); -alter table permtest_parent attach partition permtest_child for values in (1); -create index on permtest_parent (left(c, 3)); -insert into permtest_parent - select 1, 'a', left(fipshash(i::text), 5) from generate_series(0, 100) i; -analyze permtest_parent; -create role regress_no_child_access; -revoke all on permtest_grandchild from regress_no_child_access; -grant select on permtest_parent to regress_no_child_access; -set session authorization regress_no_child_access; --- without stats access, these queries would produce hash join plans: -explain (costs off) - select * from permtest_parent p1 inner join permtest_parent p2 - on p1.a = p2.a and p1.c ~ 'a1$'; - QUERY PLAN ------------------------------------------- - Nested Loop - Join Filter: (p1.a = p2.a) - -> Seq Scan on permtest_grandchild p1 - Filter: (c ~ 'a1$'::text) - -> Seq Scan on permtest_grandchild p2 -(5 rows) - -explain (costs off) - select * from permtest_parent p1 inner join permtest_parent p2 - on p1.a = p2.a and left(p1.c, 3) ~ 'a1$'; - QUERY PLAN ----------------------------------------------- - Nested Loop - Join Filter: (p1.a = p2.a) - -> Seq Scan on permtest_grandchild p1 - Filter: ("left"(c, 3) ~ 'a1$'::text) - -> Seq Scan on permtest_grandchild p2 -(5 rows) - -reset session authorization; -revoke all on permtest_parent from regress_no_child_access; -grant select(a,c) on permtest_parent to regress_no_child_access; -set session authorization regress_no_child_access; -explain (costs off) - select p2.a, p1.c from permtest_parent p1 inner join permtest_parent p2 - on p1.a = p2.a and p1.c ~ 'a1$'; - QUERY PLAN ------------------------------------------- - Nested Loop - Join Filter: (p1.a = p2.a) - -> Seq Scan on permtest_grandchild p1 - Filter: (c ~ 'a1$'::text) - -> Seq Scan on permtest_grandchild p2 -(5 rows) - --- we will not have access to the expression index's stats here: -explain (costs off) - select p2.a, p1.c from permtest_parent p1 inner join permtest_parent p2 - on p1.a = p2.a and left(p1.c, 3) ~ 'a1$'; - QUERY PLAN ----------------------------------------------------- - Hash Join - Hash Cond: (p2.a = p1.a) - -> Seq Scan on permtest_grandchild p2 - -> Hash - -> Seq Scan on permtest_grandchild p1 - Filter: ("left"(c, 3) ~ 'a1$'::text) -(6 rows) - -reset session authorization; -revoke all on permtest_parent from regress_no_child_access; -drop role regress_no_child_access; -drop table permtest_parent; --- Verify that constraint errors across partition root / child are --- handled correctly (Bug #16293) -CREATE TABLE errtst_parent ( - partid int not null, - shdata int not null, - data int NOT NULL DEFAULT 0, - CONSTRAINT shdata_small CHECK(shdata < 3) -) PARTITION BY RANGE (partid); --- fast defaults lead to attribute mapping being used in one --- direction, but not the other -CREATE TABLE errtst_child_fastdef ( - partid int not null, - shdata int not null, - CONSTRAINT shdata_small CHECK(shdata < 3) -); --- no remapping in either direction necessary -CREATE TABLE errtst_child_plaindef ( - partid int not null, - shdata int not null, - data int NOT NULL DEFAULT 0, - CONSTRAINT shdata_small CHECK(shdata < 3), - CHECK(data < 10) -); --- remapping in both direction -CREATE TABLE errtst_child_reorder ( - data int NOT NULL DEFAULT 0, - shdata int not null, - partid int not null, - CONSTRAINT shdata_small CHECK(shdata < 3), - CHECK(data < 10) -); -ALTER TABLE errtst_child_fastdef ADD COLUMN data int NOT NULL DEFAULT 0; -ALTER TABLE errtst_child_fastdef ADD CONSTRAINT errtest_child_fastdef_data_check CHECK (data < 10); -ALTER TABLE errtst_parent ATTACH PARTITION errtst_child_fastdef FOR VALUES FROM (0) TO (10); -ALTER TABLE errtst_parent ATTACH PARTITION errtst_child_plaindef FOR VALUES FROM (10) TO (20); -ALTER TABLE errtst_parent ATTACH PARTITION errtst_child_reorder FOR VALUES FROM (20) TO (30); --- insert without child check constraint error -INSERT INTO errtst_parent(partid, shdata, data) VALUES ( '0', '1', '5'); -INSERT INTO errtst_parent(partid, shdata, data) VALUES ('10', '1', '5'); -INSERT INTO errtst_parent(partid, shdata, data) VALUES ('20', '1', '5'); --- insert with child check constraint error -INSERT INTO errtst_parent(partid, shdata, data) VALUES ( '0', '1', '10'); -ERROR: new row for relation "errtst_child_fastdef" violates check constraint "errtest_child_fastdef_data_check" -DETAIL: Failing row contains (0, 1, 10). -INSERT INTO errtst_parent(partid, shdata, data) VALUES ('10', '1', '10'); -ERROR: new row for relation "errtst_child_plaindef" violates check constraint "errtst_child_plaindef_data_check" -DETAIL: Failing row contains (10, 1, 10). -INSERT INTO errtst_parent(partid, shdata, data) VALUES ('20', '1', '10'); -ERROR: new row for relation "errtst_child_reorder" violates check constraint "errtst_child_reorder_data_check" -DETAIL: Failing row contains (20, 1, 10). --- insert with child not null constraint error -INSERT INTO errtst_parent(partid, shdata, data) VALUES ( '0', '1', NULL); -ERROR: null value in column "data" of relation "errtst_child_fastdef" violates not-null constraint -DETAIL: Failing row contains (0, 1, null). -INSERT INTO errtst_parent(partid, shdata, data) VALUES ('10', '1', NULL); -ERROR: null value in column "data" of relation "errtst_child_plaindef" violates not-null constraint -DETAIL: Failing row contains (10, 1, null). -INSERT INTO errtst_parent(partid, shdata, data) VALUES ('20', '1', NULL); -ERROR: null value in column "data" of relation "errtst_child_reorder" violates not-null constraint -DETAIL: Failing row contains (20, 1, null). --- insert with shared check constraint error -INSERT INTO errtst_parent(partid, shdata, data) VALUES ( '0', '5', '5'); -ERROR: new row for relation "errtst_child_fastdef" violates check constraint "shdata_small" -DETAIL: Failing row contains (0, 5, 5). -INSERT INTO errtst_parent(partid, shdata, data) VALUES ('10', '5', '5'); -ERROR: new row for relation "errtst_child_plaindef" violates check constraint "shdata_small" -DETAIL: Failing row contains (10, 5, 5). -INSERT INTO errtst_parent(partid, shdata, data) VALUES ('20', '5', '5'); -ERROR: new row for relation "errtst_child_reorder" violates check constraint "shdata_small" -DETAIL: Failing row contains (20, 5, 5). --- within partition update without child check constraint violation -BEGIN; -UPDATE errtst_parent SET data = data + 1 WHERE partid = 0; -UPDATE errtst_parent SET data = data + 1 WHERE partid = 10; -UPDATE errtst_parent SET data = data + 1 WHERE partid = 20; -ROLLBACK; --- within partition update with child check constraint violation -UPDATE errtst_parent SET data = data + 10 WHERE partid = 0; -ERROR: new row for relation "errtst_child_fastdef" violates check constraint "errtest_child_fastdef_data_check" -DETAIL: Failing row contains (0, 1, 15). -UPDATE errtst_parent SET data = data + 10 WHERE partid = 10; -ERROR: new row for relation "errtst_child_plaindef" violates check constraint "errtst_child_plaindef_data_check" -DETAIL: Failing row contains (10, 1, 15). -UPDATE errtst_parent SET data = data + 10 WHERE partid = 20; -ERROR: new row for relation "errtst_child_reorder" violates check constraint "errtst_child_reorder_data_check" -DETAIL: Failing row contains (20, 1, 15). --- direct leaf partition update, without partition id violation -BEGIN; -UPDATE errtst_child_fastdef SET partid = 1 WHERE partid = 0; -UPDATE errtst_child_plaindef SET partid = 11 WHERE partid = 10; -UPDATE errtst_child_reorder SET partid = 21 WHERE partid = 20; -ROLLBACK; --- direct leaf partition update, with partition id violation -UPDATE errtst_child_fastdef SET partid = partid + 10 WHERE partid = 0; -ERROR: new row for relation "errtst_child_fastdef" violates partition constraint -DETAIL: Failing row contains (10, 1, 5). -UPDATE errtst_child_plaindef SET partid = partid + 10 WHERE partid = 10; -ERROR: new row for relation "errtst_child_plaindef" violates partition constraint -DETAIL: Failing row contains (20, 1, 5). -UPDATE errtst_child_reorder SET partid = partid + 10 WHERE partid = 20; -ERROR: new row for relation "errtst_child_reorder" violates partition constraint -DETAIL: Failing row contains (5, 1, 30). --- partition move, without child check constraint violation -BEGIN; -UPDATE errtst_parent SET partid = 10, data = data + 1 WHERE partid = 0; -UPDATE errtst_parent SET partid = 20, data = data + 1 WHERE partid = 10; -UPDATE errtst_parent SET partid = 0, data = data + 1 WHERE partid = 20; -ROLLBACK; --- partition move, with child check constraint violation -UPDATE errtst_parent SET partid = 10, data = data + 10 WHERE partid = 0; -ERROR: new row for relation "errtst_child_plaindef" violates check constraint "errtst_child_plaindef_data_check" -DETAIL: Failing row contains (10, 1, 15). -UPDATE errtst_parent SET partid = 20, data = data + 10 WHERE partid = 10; -ERROR: new row for relation "errtst_child_reorder" violates check constraint "errtst_child_reorder_data_check" -DETAIL: Failing row contains (20, 1, 15). -UPDATE errtst_parent SET partid = 0, data = data + 10 WHERE partid = 20; -ERROR: new row for relation "errtst_child_fastdef" violates check constraint "errtest_child_fastdef_data_check" -DETAIL: Failing row contains (0, 1, 15). --- partition move, without target partition -UPDATE errtst_parent SET partid = 30, data = data + 10 WHERE partid = 20; -ERROR: no partition of relation "errtst_parent" found for row -DETAIL: Partition key of the failing row contains (partid) = (30). -DROP TABLE errtst_parent; +server closed the connection unexpectedly + This probably means the server terminated abnormally + before or while processing the request. +connection to server was lost diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/updatable_views.out /tmp/cirrus-ci-build/build/testrun/regress/regress/results/updatable_views.out --- /tmp/cirrus-ci-build/src/test/regress/expected/updatable_views.out 2024-09-20 21:41:45.766024524 +0000 +++ /tmp/cirrus-ci-build/build/testrun/regress/regress/results/updatable_views.out 2024-09-20 21:46:04.625626291 +0000 @@ -1160,2850 +1160,7 @@ WHEN MATCHED THEN UPDATE SET b = s.b WHEN NOT MATCHED AND s.a > 0 THEN INSERT VALUES (s.a, s.b) RETURNING merge_action(), s.*, t.*; - merge_action | a | b | a | b ---------------+---+----+---+------- - DELETE | 1 | R1 | 1 | Row 1 - UPDATE | 2 | R2 | 2 | R2 - INSERT | 3 | R3 | 3 | R3 -(3 rows) - -SELECT * FROM base_tbl ORDER BY a; - a | b -----+-------- - -2 | Row -2 - -1 | Row -1 - 0 | Row 0 - 2 | R2 - 3 | R3 -(5 rows) - -MERGE INTO rw_view2 t - USING (SELECT x, 'r'||x FROM generate_series(0,2) x) AS s(a,b) ON t.a = s.a - WHEN MATCHED THEN UPDATE SET b = s.b - WHEN NOT MATCHED AND s.a > 0 THEN INSERT VALUES (s.a, s.b) - WHEN NOT MATCHED BY SOURCE THEN UPDATE SET b = 'Not matched by source' - RETURNING merge_action(), s.*, t.*; - merge_action | a | b | a | b ---------------+---+----+---+----------------------- - UPDATE | 2 | r2 | 2 | r2 - UPDATE | | | 3 | Not matched by source - INSERT | 1 | r1 | 1 | r1 -(3 rows) - -SELECT * FROM base_tbl ORDER BY a; - a | b -----+----------------------- - -2 | Row -2 - -1 | Row -1 - 0 | Row 0 - 1 | r1 - 2 | r2 - 3 | Not matched by source -(6 rows) - -EXPLAIN (costs off) UPDATE rw_view2 SET a=3 WHERE a=2; - QUERY PLAN ----------------------------------------------------------- - Update on rw_view1 rw_view1_1 - -> Subquery Scan on rw_view1 - Filter: ((rw_view1.a < 10) AND (rw_view1.a = 2)) - -> Bitmap Heap Scan on base_tbl - Recheck Cond: (a > 0) - -> Bitmap Index Scan on base_tbl_pkey - Index Cond: (a > 0) -(7 rows) - -EXPLAIN (costs off) DELETE FROM rw_view2 WHERE a=2; - QUERY PLAN ----------------------------------------------------------- - Delete on rw_view1 rw_view1_1 - -> Subquery Scan on rw_view1 - Filter: ((rw_view1.a < 10) AND (rw_view1.a = 2)) - -> Bitmap Heap Scan on base_tbl - Recheck Cond: (a > 0) - -> Bitmap Index Scan on base_tbl_pkey - Index Cond: (a > 0) -(7 rows) - -EXPLAIN (costs off) -MERGE INTO rw_view2 t - USING (SELECT x, 'R'||x FROM generate_series(0,3) x) AS s(a,b) ON t.a = s.a - WHEN MATCHED AND t.a <= 1 THEN DELETE - WHEN MATCHED THEN UPDATE SET b = s.b - WHEN NOT MATCHED AND s.a > 0 THEN INSERT VALUES (s.a, s.b); - QUERY PLAN ------------------------------------------------------------- - Merge on rw_view1 rw_view1_1 - -> Hash Right Join - Hash Cond: (rw_view1.a = x.x) - -> Subquery Scan on rw_view1 - Filter: (rw_view1.a < 10) - -> Bitmap Heap Scan on base_tbl - Recheck Cond: (a > 0) - -> Bitmap Index Scan on base_tbl_pkey - Index Cond: (a > 0) - -> Hash - -> Function Scan on generate_series x -(11 rows) - --- MERGE with incomplete set of INSTEAD OF triggers -DROP TRIGGER rw_view1_del_trig ON rw_view1; -MERGE INTO rw_view2 t - USING (SELECT x, 'R'||x FROM generate_series(0,3) x) AS s(a,b) ON t.a = s.a - WHEN MATCHED AND t.a <= 1 THEN DELETE - WHEN MATCHED THEN UPDATE SET b = s.b - WHEN NOT MATCHED AND s.a > 0 THEN INSERT VALUES (s.a, s.b); -- should fail -ERROR: cannot delete from view "rw_view1" -DETAIL: Views containing LIMIT or OFFSET are not automatically updatable. -HINT: To enable deleting from the view using MERGE, provide an INSTEAD OF DELETE trigger. -MERGE INTO rw_view2 t - USING (SELECT x, 'R'||x FROM generate_series(0,3) x) AS s(a,b) ON t.a = s.a - WHEN MATCHED THEN UPDATE SET b = s.b - WHEN NOT MATCHED AND s.a > 0 THEN INSERT VALUES (s.a, s.b); -- ok -DROP TRIGGER rw_view1_ins_trig ON rw_view1; -MERGE INTO rw_view2 t - USING (SELECT x, 'R'||x FROM generate_series(0,3) x) AS s(a,b) ON t.a = s.a - WHEN MATCHED THEN UPDATE SET b = s.b - WHEN NOT MATCHED AND s.a > 0 THEN INSERT VALUES (s.a, s.b); -- should fail -ERROR: cannot insert into view "rw_view1" -DETAIL: Views containing LIMIT or OFFSET are not automatically updatable. -HINT: To enable inserting into the view using MERGE, provide an INSTEAD OF INSERT trigger. -MERGE INTO rw_view2 t - USING (SELECT x, 'R'||x FROM generate_series(0,3) x) AS s(a,b) ON t.a = s.a - WHEN MATCHED THEN UPDATE SET b = s.b; -- ok --- MERGE with INSTEAD OF triggers on auto-updatable view -CREATE TRIGGER rw_view2_upd_trig INSTEAD OF UPDATE ON rw_view2 - FOR EACH ROW EXECUTE PROCEDURE rw_view1_trig_fn(); -MERGE INTO rw_view2 t - USING (SELECT x, 'R'||x FROM generate_series(0,3) x) AS s(a,b) ON t.a = s.a - WHEN MATCHED THEN UPDATE SET b = s.b - WHEN NOT MATCHED AND s.a > 0 THEN INSERT VALUES (s.a, s.b); -- should fail -ERROR: cannot merge into view "rw_view2" -DETAIL: MERGE is not supported for views with INSTEAD OF triggers for some actions but not all. -HINT: To enable merging into the view, either provide a full set of INSTEAD OF triggers or drop the existing INSTEAD OF triggers. -MERGE INTO rw_view2 t - USING (SELECT x, 'R'||x FROM generate_series(0,3) x) AS s(a,b) ON t.a = s.a - WHEN MATCHED THEN UPDATE SET b = s.b; -- ok -SELECT * FROM base_tbl ORDER BY a; - a | b -----+-------- - -2 | Row -2 - -1 | Row -1 - 0 | Row 0 - 1 | R1 - 2 | R2 - 3 | R3 -(6 rows) - -DROP TABLE base_tbl CASCADE; -NOTICE: drop cascades to 2 other objects -DETAIL: drop cascades to view rw_view1 -drop cascades to view rw_view2 -DROP FUNCTION rw_view1_trig_fn(); --- update using whole row from view -CREATE TABLE base_tbl (a int PRIMARY KEY, b text DEFAULT 'Unspecified'); -INSERT INTO base_tbl SELECT i, 'Row ' || i FROM generate_series(-2, 2) g(i); -CREATE VIEW rw_view1 AS SELECT b AS bb, a AS aa FROM base_tbl; -CREATE FUNCTION rw_view1_aa(x rw_view1) - RETURNS int AS $$ SELECT x.aa $$ LANGUAGE sql; -UPDATE rw_view1 v SET bb='Updated row 2' WHERE rw_view1_aa(v)=2 - RETURNING rw_view1_aa(v), v.bb; - rw_view1_aa | bb --------------+--------------- - 2 | Updated row 2 -(1 row) - -SELECT * FROM base_tbl; - a | b -----+--------------- - -2 | Row -2 - -1 | Row -1 - 0 | Row 0 - 1 | Row 1 - 2 | Updated row 2 -(5 rows) - -EXPLAIN (costs off) -UPDATE rw_view1 v SET bb='Updated row 2' WHERE rw_view1_aa(v)=2 - RETURNING rw_view1_aa(v), v.bb; - QUERY PLAN --------------------------------------------------- - Update on base_tbl - -> Index Scan using base_tbl_pkey on base_tbl - Index Cond: (a = 2) -(3 rows) - -DROP TABLE base_tbl CASCADE; -NOTICE: drop cascades to 2 other objects -DETAIL: drop cascades to view rw_view1 -drop cascades to function rw_view1_aa(rw_view1) --- permissions checks -CREATE USER regress_view_user1; -CREATE USER regress_view_user2; -CREATE USER regress_view_user3; -SET SESSION AUTHORIZATION regress_view_user1; -CREATE TABLE base_tbl(a int, b text, c float); -INSERT INTO base_tbl VALUES (1, 'Row 1', 1.0); -CREATE VIEW rw_view1 AS SELECT b AS bb, c AS cc, a AS aa FROM base_tbl; -INSERT INTO rw_view1 VALUES ('Row 2', 2.0, 2); -GRANT SELECT ON base_tbl TO regress_view_user2; -GRANT SELECT ON rw_view1 TO regress_view_user2; -GRANT UPDATE (a,c) ON base_tbl TO regress_view_user2; -GRANT UPDATE (bb,cc) ON rw_view1 TO regress_view_user2; -RESET SESSION AUTHORIZATION; -SET SESSION AUTHORIZATION regress_view_user2; -CREATE VIEW rw_view2 AS SELECT b AS bb, c AS cc, a AS aa FROM base_tbl; -SELECT * FROM base_tbl; -- ok - a | b | c ----+-------+--- - 1 | Row 1 | 1 - 2 | Row 2 | 2 -(2 rows) - -SELECT * FROM rw_view1; -- ok - bb | cc | aa --------+----+---- - Row 1 | 1 | 1 - Row 2 | 2 | 2 -(2 rows) - -SELECT * FROM rw_view2; -- ok - bb | cc | aa --------+----+---- - Row 1 | 1 | 1 - Row 2 | 2 | 2 -(2 rows) - -INSERT INTO base_tbl VALUES (3, 'Row 3', 3.0); -- not allowed -ERROR: permission denied for table base_tbl -INSERT INTO rw_view1 VALUES ('Row 3', 3.0, 3); -- not allowed -ERROR: permission denied for view rw_view1 -INSERT INTO rw_view2 VALUES ('Row 3', 3.0, 3); -- not allowed -ERROR: permission denied for table base_tbl -MERGE INTO rw_view1 t - USING (VALUES ('Row 3', 3.0, 3)) AS v(b,c,a) ON t.aa = v.a - WHEN NOT MATCHED THEN INSERT VALUES (v.b, v.c, v.a); -- not allowed -ERROR: permission denied for view rw_view1 -MERGE INTO rw_view2 t - USING (VALUES ('Row 3', 3.0, 3)) AS v(b,c,a) ON t.aa = v.a - WHEN NOT MATCHED THEN INSERT VALUES (v.b, v.c, v.a); -- not allowed -ERROR: permission denied for table base_tbl -UPDATE base_tbl SET a=a, c=c; -- ok -UPDATE base_tbl SET b=b; -- not allowed -ERROR: permission denied for table base_tbl -UPDATE rw_view1 SET bb=bb, cc=cc; -- ok -UPDATE rw_view1 SET aa=aa; -- not allowed -ERROR: permission denied for view rw_view1 -UPDATE rw_view2 SET aa=aa, cc=cc; -- ok -UPDATE rw_view2 SET bb=bb; -- not allowed -ERROR: permission denied for table base_tbl -MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a - WHEN MATCHED THEN UPDATE SET bb = bb, cc = cc; -- ok -MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a - WHEN MATCHED THEN UPDATE SET aa = aa; -- not allowed -ERROR: permission denied for view rw_view1 -MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aa = v.a - WHEN MATCHED THEN UPDATE SET aa = aa, cc = cc; -- ok -MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aa = v.a - WHEN MATCHED THEN UPDATE SET bb = bb; -- not allowed -ERROR: permission denied for table base_tbl -DELETE FROM base_tbl; -- not allowed -ERROR: permission denied for table base_tbl -DELETE FROM rw_view1; -- not allowed -ERROR: permission denied for view rw_view1 -DELETE FROM rw_view2; -- not allowed -ERROR: permission denied for table base_tbl -RESET SESSION AUTHORIZATION; -SET SESSION AUTHORIZATION regress_view_user1; -GRANT INSERT, DELETE ON base_tbl TO regress_view_user2; -RESET SESSION AUTHORIZATION; -SET SESSION AUTHORIZATION regress_view_user2; -INSERT INTO base_tbl VALUES (3, 'Row 3', 3.0); -- ok -INSERT INTO rw_view1 VALUES ('Row 4', 4.0, 4); -- not allowed -ERROR: permission denied for view rw_view1 -INSERT INTO rw_view2 VALUES ('Row 4', 4.0, 4); -- ok -DELETE FROM base_tbl WHERE a=1; -- ok -DELETE FROM rw_view1 WHERE aa=2; -- not allowed -ERROR: permission denied for view rw_view1 -DELETE FROM rw_view2 WHERE aa=2; -- ok -MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a - WHEN MATCHED AND bb = 'xxx' THEN DELETE; -- not allowed -ERROR: permission denied for view rw_view1 -MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aa = v.a - WHEN MATCHED AND bb = 'xxx' THEN DELETE; -- ok -SELECT * FROM base_tbl; - a | b | c ----+-------+--- - 3 | Row 3 | 3 - 4 | Row 4 | 4 -(2 rows) - -RESET SESSION AUTHORIZATION; -SET SESSION AUTHORIZATION regress_view_user1; -REVOKE INSERT, DELETE ON base_tbl FROM regress_view_user2; -GRANT INSERT, DELETE ON rw_view1 TO regress_view_user2; -RESET SESSION AUTHORIZATION; -SET SESSION AUTHORIZATION regress_view_user2; -INSERT INTO base_tbl VALUES (5, 'Row 5', 5.0); -- not allowed -ERROR: permission denied for table base_tbl -INSERT INTO rw_view1 VALUES ('Row 5', 5.0, 5); -- ok -INSERT INTO rw_view2 VALUES ('Row 6', 6.0, 6); -- not allowed -ERROR: permission denied for table base_tbl -DELETE FROM base_tbl WHERE a=3; -- not allowed -ERROR: permission denied for table base_tbl -DELETE FROM rw_view1 WHERE aa=3; -- ok -DELETE FROM rw_view2 WHERE aa=4; -- not allowed -ERROR: permission denied for table base_tbl -MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a - WHEN MATCHED AND bb = 'xxx' THEN DELETE; -- ok -MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aa = v.a - WHEN MATCHED AND bb = 'xxx' THEN DELETE; -- not allowed -ERROR: permission denied for table base_tbl -SELECT * FROM base_tbl; - a | b | c ----+-------+--- - 4 | Row 4 | 4 - 5 | Row 5 | 5 -(2 rows) - -RESET SESSION AUTHORIZATION; -DROP TABLE base_tbl CASCADE; -NOTICE: drop cascades to 2 other objects -DETAIL: drop cascades to view rw_view1 -drop cascades to view rw_view2 --- nested-view permissions -CREATE TABLE base_tbl(a int, b text, c float); -INSERT INTO base_tbl VALUES (1, 'Row 1', 1.0); -SET SESSION AUTHORIZATION regress_view_user1; -CREATE VIEW rw_view1 AS SELECT * FROM base_tbl; -SELECT * FROM rw_view1; -- not allowed -ERROR: permission denied for table base_tbl -SELECT * FROM rw_view1 FOR UPDATE; -- not allowed -ERROR: permission denied for table base_tbl -UPDATE rw_view1 SET b = 'foo' WHERE a = 1; -- not allowed -ERROR: permission denied for table base_tbl -MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.a = v.a - WHEN MATCHED THEN UPDATE SET b = 'foo'; -- not allowed -ERROR: permission denied for table base_tbl -SET SESSION AUTHORIZATION regress_view_user2; -CREATE VIEW rw_view2 AS SELECT * FROM rw_view1; -SELECT * FROM rw_view2; -- not allowed -ERROR: permission denied for view rw_view1 -SELECT * FROM rw_view2 FOR UPDATE; -- not allowed -ERROR: permission denied for view rw_view1 -UPDATE rw_view2 SET b = 'bar' WHERE a = 1; -- not allowed -ERROR: permission denied for view rw_view1 -MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.a = v.a - WHEN MATCHED THEN UPDATE SET b = 'foo'; -- not allowed -ERROR: permission denied for view rw_view1 -RESET SESSION AUTHORIZATION; -GRANT SELECT ON base_tbl TO regress_view_user1; -SET SESSION AUTHORIZATION regress_view_user1; -SELECT * FROM rw_view1; - a | b | c ----+-------+--- - 1 | Row 1 | 1 -(1 row) - -SELECT * FROM rw_view1 FOR UPDATE; -- not allowed -ERROR: permission denied for table base_tbl -UPDATE rw_view1 SET b = 'foo' WHERE a = 1; -- not allowed -ERROR: permission denied for table base_tbl -MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.a = v.a - WHEN MATCHED THEN UPDATE SET b = 'foo'; -- not allowed -ERROR: permission denied for table base_tbl -SET SESSION AUTHORIZATION regress_view_user2; -SELECT * FROM rw_view2; -- not allowed -ERROR: permission denied for view rw_view1 -SELECT * FROM rw_view2 FOR UPDATE; -- not allowed -ERROR: permission denied for view rw_view1 -UPDATE rw_view2 SET b = 'bar' WHERE a = 1; -- not allowed -ERROR: permission denied for view rw_view1 -MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.a = v.a - WHEN MATCHED THEN UPDATE SET b = 'foo'; -- not allowed -ERROR: permission denied for view rw_view1 -SET SESSION AUTHORIZATION regress_view_user1; -GRANT SELECT ON rw_view1 TO regress_view_user2; -SET SESSION AUTHORIZATION regress_view_user2; -SELECT * FROM rw_view2; - a | b | c ----+-------+--- - 1 | Row 1 | 1 -(1 row) - -SELECT * FROM rw_view2 FOR UPDATE; -- not allowed -ERROR: permission denied for view rw_view1 -UPDATE rw_view2 SET b = 'bar' WHERE a = 1; -- not allowed -ERROR: permission denied for view rw_view1 -MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.a = v.a - WHEN MATCHED THEN UPDATE SET b = 'foo'; -- not allowed -ERROR: permission denied for view rw_view1 -RESET SESSION AUTHORIZATION; -GRANT UPDATE ON base_tbl TO regress_view_user1; -SET SESSION AUTHORIZATION regress_view_user1; -SELECT * FROM rw_view1; - a | b | c ----+-------+--- - 1 | Row 1 | 1 -(1 row) - -SELECT * FROM rw_view1 FOR UPDATE; - a | b | c ----+-------+--- - 1 | Row 1 | 1 -(1 row) - -UPDATE rw_view1 SET b = 'foo' WHERE a = 1; -MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.a = v.a - WHEN MATCHED THEN UPDATE SET b = 'foo'; -SET SESSION AUTHORIZATION regress_view_user2; -SELECT * FROM rw_view2; - a | b | c ----+-----+--- - 1 | foo | 1 -(1 row) - -SELECT * FROM rw_view2 FOR UPDATE; -- not allowed -ERROR: permission denied for view rw_view1 -UPDATE rw_view2 SET b = 'bar' WHERE a = 1; -- not allowed -ERROR: permission denied for view rw_view1 -MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.a = v.a - WHEN MATCHED THEN UPDATE SET b = 'bar'; -- not allowed -ERROR: permission denied for view rw_view1 -SET SESSION AUTHORIZATION regress_view_user1; -GRANT UPDATE ON rw_view1 TO regress_view_user2; -SET SESSION AUTHORIZATION regress_view_user2; -SELECT * FROM rw_view2; - a | b | c ----+-----+--- - 1 | foo | 1 -(1 row) - -SELECT * FROM rw_view2 FOR UPDATE; - a | b | c ----+-----+--- - 1 | foo | 1 -(1 row) - -UPDATE rw_view2 SET b = 'bar' WHERE a = 1; -MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.a = v.a - WHEN MATCHED THEN UPDATE SET b = 'fud'; -RESET SESSION AUTHORIZATION; -REVOKE UPDATE ON base_tbl FROM regress_view_user1; -SET SESSION AUTHORIZATION regress_view_user1; -SELECT * FROM rw_view1; - a | b | c ----+-----+--- - 1 | fud | 1 -(1 row) - -SELECT * FROM rw_view1 FOR UPDATE; -- not allowed -ERROR: permission denied for table base_tbl -UPDATE rw_view1 SET b = 'foo' WHERE a = 1; -- not allowed -ERROR: permission denied for table base_tbl -MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.a = v.a - WHEN MATCHED THEN UPDATE SET b = 'foo'; -- not allowed -ERROR: permission denied for table base_tbl -SET SESSION AUTHORIZATION regress_view_user2; -SELECT * FROM rw_view2; - a | b | c ----+-----+--- - 1 | fud | 1 -(1 row) - -SELECT * FROM rw_view2 FOR UPDATE; -- not allowed -ERROR: permission denied for table base_tbl -UPDATE rw_view2 SET b = 'bar' WHERE a = 1; -- not allowed -ERROR: permission denied for table base_tbl -MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.a = v.a - WHEN MATCHED THEN UPDATE SET b = 'foo'; -- not allowed -ERROR: permission denied for table base_tbl -RESET SESSION AUTHORIZATION; -DROP TABLE base_tbl CASCADE; -NOTICE: drop cascades to 2 other objects -DETAIL: drop cascades to view rw_view1 -drop cascades to view rw_view2 --- security invoker view permissions -SET SESSION AUTHORIZATION regress_view_user1; -CREATE TABLE base_tbl(a int, b text, c float); -INSERT INTO base_tbl VALUES (1, 'Row 1', 1.0); -CREATE VIEW rw_view1 AS SELECT b AS bb, c AS cc, a AS aa FROM base_tbl; -ALTER VIEW rw_view1 SET (security_invoker = true); -INSERT INTO rw_view1 VALUES ('Row 2', 2.0, 2); -GRANT SELECT ON rw_view1 TO regress_view_user2; -GRANT UPDATE (bb,cc) ON rw_view1 TO regress_view_user2; -SET SESSION AUTHORIZATION regress_view_user2; -SELECT * FROM base_tbl; -- not allowed -ERROR: permission denied for table base_tbl -SELECT * FROM rw_view1; -- not allowed -ERROR: permission denied for table base_tbl -INSERT INTO base_tbl VALUES (3, 'Row 3', 3.0); -- not allowed -ERROR: permission denied for table base_tbl -INSERT INTO rw_view1 VALUES ('Row 3', 3.0, 3); -- not allowed -ERROR: permission denied for view rw_view1 -UPDATE base_tbl SET a=a; -- not allowed -ERROR: permission denied for table base_tbl -UPDATE rw_view1 SET bb=bb, cc=cc; -- not allowed -ERROR: permission denied for table base_tbl -MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a - WHEN MATCHED THEN UPDATE SET bb = bb; -- not allowed -ERROR: permission denied for table base_tbl -DELETE FROM base_tbl; -- not allowed -ERROR: permission denied for table base_tbl -DELETE FROM rw_view1; -- not allowed -ERROR: permission denied for view rw_view1 -MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a - WHEN MATCHED THEN DELETE; -- not allowed -ERROR: permission denied for view rw_view1 -SET SESSION AUTHORIZATION regress_view_user1; -GRANT SELECT ON base_tbl TO regress_view_user2; -GRANT UPDATE (a,c) ON base_tbl TO regress_view_user2; -SET SESSION AUTHORIZATION regress_view_user2; -SELECT * FROM base_tbl; -- ok - a | b | c ----+-------+--- - 1 | Row 1 | 1 - 2 | Row 2 | 2 -(2 rows) - -SELECT * FROM rw_view1; -- ok - bb | cc | aa --------+----+---- - Row 1 | 1 | 1 - Row 2 | 2 | 2 -(2 rows) - -UPDATE base_tbl SET a=a, c=c; -- ok -UPDATE base_tbl SET b=b; -- not allowed -ERROR: permission denied for table base_tbl -UPDATE rw_view1 SET cc=cc; -- ok -MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a - WHEN MATCHED THEN UPDATE SET cc = cc; -- ok -UPDATE rw_view1 SET aa=aa; -- not allowed -ERROR: permission denied for view rw_view1 -UPDATE rw_view1 SET bb=bb; -- not allowed -ERROR: permission denied for table base_tbl -MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a - WHEN MATCHED THEN UPDATE SET aa = aa; -- not allowed -ERROR: permission denied for view rw_view1 -MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a - WHEN MATCHED THEN UPDATE SET bb = bb; -- not allowed -ERROR: permission denied for table base_tbl -SET SESSION AUTHORIZATION regress_view_user1; -GRANT INSERT, DELETE ON base_tbl TO regress_view_user2; -SET SESSION AUTHORIZATION regress_view_user2; -INSERT INTO base_tbl VALUES (3, 'Row 3', 3.0); -- ok -INSERT INTO rw_view1 VALUES ('Row 4', 4.0, 4); -- not allowed -ERROR: permission denied for view rw_view1 -DELETE FROM base_tbl WHERE a=1; -- ok -DELETE FROM rw_view1 WHERE aa=2; -- not allowed -ERROR: permission denied for view rw_view1 -MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a - WHEN MATCHED THEN DELETE; -- not allowed -ERROR: permission denied for view rw_view1 -SET SESSION AUTHORIZATION regress_view_user1; -REVOKE INSERT, DELETE ON base_tbl FROM regress_view_user2; -GRANT INSERT, DELETE ON rw_view1 TO regress_view_user2; -SET SESSION AUTHORIZATION regress_view_user2; -INSERT INTO rw_view1 VALUES ('Row 4', 4.0, 4); -- not allowed -ERROR: permission denied for table base_tbl -DELETE FROM rw_view1 WHERE aa=2; -- not allowed -ERROR: permission denied for table base_tbl -MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a - WHEN MATCHED THEN DELETE; -- not allowed -ERROR: permission denied for table base_tbl -SET SESSION AUTHORIZATION regress_view_user1; -GRANT INSERT, DELETE ON base_tbl TO regress_view_user2; -SET SESSION AUTHORIZATION regress_view_user2; -INSERT INTO rw_view1 VALUES ('Row 4', 4.0, 4); -- ok -DELETE FROM rw_view1 WHERE aa=2; -- ok -MERGE INTO rw_view1 t USING (VALUES (3)) AS v(a) ON t.aa = v.a - WHEN MATCHED THEN DELETE; -- ok -SELECT * FROM base_tbl; -- ok - a | b | c ----+-------+--- - 4 | Row 4 | 4 -(1 row) - -RESET SESSION AUTHORIZATION; -DROP TABLE base_tbl CASCADE; -NOTICE: drop cascades to view rw_view1 --- ordinary view on top of security invoker view permissions -CREATE TABLE base_tbl(a int, b text, c float); -INSERT INTO base_tbl VALUES (1, 'Row 1', 1.0); -SET SESSION AUTHORIZATION regress_view_user1; -CREATE VIEW rw_view1 AS SELECT b AS bb, c AS cc, a AS aa FROM base_tbl; -ALTER VIEW rw_view1 SET (security_invoker = true); -SELECT * FROM rw_view1; -- not allowed -ERROR: permission denied for table base_tbl -UPDATE rw_view1 SET aa=aa; -- not allowed -ERROR: permission denied for table base_tbl -MERGE INTO rw_view1 t USING (VALUES (2, 'Row 2', 2.0)) AS v(a,b,c) ON t.aa = v.a - WHEN NOT MATCHED THEN INSERT VALUES (v.b, v.c, v.a); -- not allowed -ERROR: permission denied for table base_tbl -SET SESSION AUTHORIZATION regress_view_user2; -CREATE VIEW rw_view2 AS SELECT cc AS ccc, aa AS aaa, bb AS bbb FROM rw_view1; -GRANT SELECT, UPDATE ON rw_view2 TO regress_view_user3; -SELECT * FROM rw_view2; -- not allowed -ERROR: permission denied for view rw_view1 -UPDATE rw_view2 SET aaa=aaa; -- not allowed -ERROR: permission denied for view rw_view1 -MERGE INTO rw_view2 t USING (VALUES (2, 'Row 2', 2.0)) AS v(a,b,c) ON t.aaa = v.a - WHEN NOT MATCHED THEN INSERT VALUES (v.c, v.a, v.b); -- not allowed -ERROR: permission denied for view rw_view1 -RESET SESSION AUTHORIZATION; -GRANT SELECT ON base_tbl TO regress_view_user1; -GRANT UPDATE (a, b) ON base_tbl TO regress_view_user1; -SET SESSION AUTHORIZATION regress_view_user1; -SELECT * FROM rw_view1; -- ok - bb | cc | aa --------+----+---- - Row 1 | 1 | 1 -(1 row) - -UPDATE rw_view1 SET aa=aa, bb=bb; -- ok -UPDATE rw_view1 SET cc=cc; -- not allowed -ERROR: permission denied for table base_tbl -MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a - WHEN MATCHED THEN UPDATE SET aa = aa, bb = bb; -- ok -MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a - WHEN MATCHED THEN UPDATE SET cc = cc; -- not allowed -ERROR: permission denied for table base_tbl -SET SESSION AUTHORIZATION regress_view_user2; -SELECT * FROM rw_view2; -- not allowed -ERROR: permission denied for view rw_view1 -UPDATE rw_view2 SET aaa=aaa; -- not allowed -ERROR: permission denied for view rw_view1 -MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a - WHEN MATCHED THEN UPDATE SET aaa = aaa; -- not allowed -ERROR: permission denied for view rw_view1 -SET SESSION AUTHORIZATION regress_view_user3; -SELECT * FROM rw_view2; -- not allowed -ERROR: permission denied for view rw_view1 -UPDATE rw_view2 SET aaa=aaa; -- not allowed -ERROR: permission denied for view rw_view1 -MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a - WHEN MATCHED THEN UPDATE SET aaa = aaa; -- not allowed -ERROR: permission denied for view rw_view1 -SET SESSION AUTHORIZATION regress_view_user1; -GRANT SELECT ON rw_view1 TO regress_view_user2; -GRANT UPDATE (bb, cc) ON rw_view1 TO regress_view_user2; -SET SESSION AUTHORIZATION regress_view_user2; -SELECT * FROM rw_view2; -- not allowed -ERROR: permission denied for table base_tbl -UPDATE rw_view2 SET bbb=bbb; -- not allowed -ERROR: permission denied for table base_tbl -MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a - WHEN MATCHED THEN UPDATE SET bbb = bbb; -- not allowed -ERROR: permission denied for table base_tbl -SET SESSION AUTHORIZATION regress_view_user3; -SELECT * FROM rw_view2; -- not allowed -ERROR: permission denied for table base_tbl -UPDATE rw_view2 SET bbb=bbb; -- not allowed -ERROR: permission denied for table base_tbl -MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a - WHEN MATCHED THEN UPDATE SET bbb = bbb; -- not allowed -ERROR: permission denied for table base_tbl -RESET SESSION AUTHORIZATION; -GRANT SELECT ON base_tbl TO regress_view_user2; -GRANT UPDATE (a, c) ON base_tbl TO regress_view_user2; -SET SESSION AUTHORIZATION regress_view_user2; -SELECT * FROM rw_view2; -- ok - ccc | aaa | bbb ------+-----+------- - 1 | 1 | Row 1 -(1 row) - -UPDATE rw_view2 SET aaa=aaa; -- not allowed -ERROR: permission denied for view rw_view1 -UPDATE rw_view2 SET bbb=bbb; -- not allowed -ERROR: permission denied for table base_tbl -UPDATE rw_view2 SET ccc=ccc; -- ok -MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a - WHEN MATCHED THEN UPDATE SET aaa = aaa; -- not allowed -ERROR: permission denied for view rw_view1 -MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a - WHEN MATCHED THEN UPDATE SET bbb = bbb; -- not allowed -ERROR: permission denied for table base_tbl -MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a - WHEN MATCHED THEN UPDATE SET ccc = ccc; -- ok -SET SESSION AUTHORIZATION regress_view_user3; -SELECT * FROM rw_view2; -- not allowed -ERROR: permission denied for table base_tbl -UPDATE rw_view2 SET aaa=aaa; -- not allowed -ERROR: permission denied for view rw_view1 -UPDATE rw_view2 SET bbb=bbb; -- not allowed -ERROR: permission denied for table base_tbl -UPDATE rw_view2 SET ccc=ccc; -- not allowed -ERROR: permission denied for table base_tbl -MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a - WHEN MATCHED THEN UPDATE SET aaa = aaa; -- not allowed -ERROR: permission denied for view rw_view1 -MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a - WHEN MATCHED THEN UPDATE SET bbb = bbb; -- not allowed -ERROR: permission denied for table base_tbl -MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a - WHEN MATCHED THEN UPDATE SET ccc = ccc; -- not allowed -ERROR: permission denied for table base_tbl -RESET SESSION AUTHORIZATION; -GRANT SELECT ON base_tbl TO regress_view_user3; -GRANT UPDATE (a, c) ON base_tbl TO regress_view_user3; -SET SESSION AUTHORIZATION regress_view_user3; -SELECT * FROM rw_view2; -- ok - ccc | aaa | bbb ------+-----+------- - 1 | 1 | Row 1 -(1 row) - -UPDATE rw_view2 SET aaa=aaa; -- not allowed -ERROR: permission denied for view rw_view1 -UPDATE rw_view2 SET bbb=bbb; -- not allowed -ERROR: permission denied for table base_tbl -UPDATE rw_view2 SET ccc=ccc; -- ok -MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a - WHEN MATCHED THEN UPDATE SET aaa = aaa; -- not allowed -ERROR: permission denied for view rw_view1 -MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a - WHEN MATCHED THEN UPDATE SET bbb = bbb; -- not allowed -ERROR: permission denied for table base_tbl -MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a - WHEN MATCHED THEN UPDATE SET ccc = ccc; -- ok -RESET SESSION AUTHORIZATION; -REVOKE SELECT, UPDATE ON base_tbl FROM regress_view_user1; -SET SESSION AUTHORIZATION regress_view_user1; -SELECT * FROM rw_view1; -- not allowed -ERROR: permission denied for table base_tbl -UPDATE rw_view1 SET aa=aa; -- not allowed -ERROR: permission denied for table base_tbl -MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a - WHEN MATCHED THEN UPDATE SET aa = aa; -- not allowed -ERROR: permission denied for table base_tbl -SET SESSION AUTHORIZATION regress_view_user2; -SELECT * FROM rw_view2; -- ok - ccc | aaa | bbb ------+-----+------- - 1 | 1 | Row 1 -(1 row) - -UPDATE rw_view2 SET aaa=aaa; -- not allowed -ERROR: permission denied for view rw_view1 -UPDATE rw_view2 SET bbb=bbb; -- not allowed -ERROR: permission denied for table base_tbl -UPDATE rw_view2 SET ccc=ccc; -- ok -MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a - WHEN MATCHED THEN UPDATE SET aaa = aaa; -- not allowed -ERROR: permission denied for view rw_view1 -MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a - WHEN MATCHED THEN UPDATE SET bbb = bbb; -- not allowed -ERROR: permission denied for table base_tbl -MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a - WHEN MATCHED THEN UPDATE SET ccc = ccc; -- ok -SET SESSION AUTHORIZATION regress_view_user3; -SELECT * FROM rw_view2; -- ok - ccc | aaa | bbb ------+-----+------- - 1 | 1 | Row 1 -(1 row) - -UPDATE rw_view2 SET aaa=aaa; -- not allowed -ERROR: permission denied for view rw_view1 -UPDATE rw_view2 SET bbb=bbb; -- not allowed -ERROR: permission denied for table base_tbl -UPDATE rw_view2 SET ccc=ccc; -- ok -MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a - WHEN MATCHED THEN UPDATE SET aaa = aaa; -- not allowed -ERROR: permission denied for view rw_view1 -MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a - WHEN MATCHED THEN UPDATE SET bbb = bbb; -- not allowed -ERROR: permission denied for table base_tbl -MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a - WHEN MATCHED THEN UPDATE SET ccc = ccc; -- ok -RESET SESSION AUTHORIZATION; -REVOKE SELECT, UPDATE ON base_tbl FROM regress_view_user2; -SET SESSION AUTHORIZATION regress_view_user2; -SELECT * FROM rw_view2; -- not allowed -ERROR: permission denied for table base_tbl -UPDATE rw_view2 SET aaa=aaa; -- not allowed -ERROR: permission denied for view rw_view1 -UPDATE rw_view2 SET bbb=bbb; -- not allowed -ERROR: permission denied for table base_tbl -UPDATE rw_view2 SET ccc=ccc; -- not allowed -ERROR: permission denied for table base_tbl -MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a - WHEN MATCHED THEN UPDATE SET aaa = aaa; -- not allowed -ERROR: permission denied for view rw_view1 -MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a - WHEN MATCHED THEN UPDATE SET bbb = bbb; -- not allowed -ERROR: permission denied for table base_tbl -MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a - WHEN MATCHED THEN UPDATE SET ccc = ccc; -- not allowed -ERROR: permission denied for table base_tbl -SET SESSION AUTHORIZATION regress_view_user3; -SELECT * FROM rw_view2; -- ok - ccc | aaa | bbb ------+-----+------- - 1 | 1 | Row 1 -(1 row) - -UPDATE rw_view2 SET aaa=aaa; -- not allowed -ERROR: permission denied for view rw_view1 -UPDATE rw_view2 SET bbb=bbb; -- not allowed -ERROR: permission denied for table base_tbl -UPDATE rw_view2 SET ccc=ccc; -- ok -MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a - WHEN MATCHED THEN UPDATE SET aaa = aaa; -- not allowed -ERROR: permission denied for view rw_view1 -MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a - WHEN MATCHED THEN UPDATE SET bbb = bbb; -- not allowed -ERROR: permission denied for table base_tbl -MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a - WHEN MATCHED THEN UPDATE SET ccc = ccc; -- ok -RESET SESSION AUTHORIZATION; -DROP TABLE base_tbl CASCADE; -NOTICE: drop cascades to 2 other objects -DETAIL: drop cascades to view rw_view1 -drop cascades to view rw_view2 -DROP USER regress_view_user1; -DROP USER regress_view_user2; -DROP USER regress_view_user3; --- column defaults -CREATE TABLE base_tbl (a int PRIMARY KEY, b text DEFAULT 'Unspecified', c serial); -INSERT INTO base_tbl VALUES (1, 'Row 1'); -INSERT INTO base_tbl VALUES (2, 'Row 2'); -INSERT INTO base_tbl VALUES (3); -CREATE VIEW rw_view1 AS SELECT a AS aa, b AS bb FROM base_tbl; -ALTER VIEW rw_view1 ALTER COLUMN bb SET DEFAULT 'View default'; -INSERT INTO rw_view1 VALUES (4, 'Row 4'); -INSERT INTO rw_view1 (aa) VALUES (5); -MERGE INTO rw_view1 t USING (VALUES (6)) AS v(a) ON t.aa = v.a - WHEN NOT MATCHED THEN INSERT (aa) VALUES (v.a); -SELECT * FROM base_tbl; - a | b | c ----+--------------+--- - 1 | Row 1 | 1 - 2 | Row 2 | 2 - 3 | Unspecified | 3 - 4 | Row 4 | 4 - 5 | View default | 5 - 6 | View default | 6 -(6 rows) - -DROP TABLE base_tbl CASCADE; -NOTICE: drop cascades to view rw_view1 --- Table having triggers -CREATE TABLE base_tbl (a int PRIMARY KEY, b text DEFAULT 'Unspecified'); -INSERT INTO base_tbl VALUES (1, 'Row 1'); -INSERT INTO base_tbl VALUES (2, 'Row 2'); -CREATE FUNCTION rw_view1_trig_fn() -RETURNS trigger AS -$$ -BEGIN - IF TG_OP = 'INSERT' THEN - UPDATE base_tbl SET b=NEW.b WHERE a=1; - RETURN NULL; - END IF; - RETURN NULL; -END; -$$ -LANGUAGE plpgsql; -CREATE TRIGGER rw_view1_ins_trig AFTER INSERT ON base_tbl - FOR EACH ROW EXECUTE PROCEDURE rw_view1_trig_fn(); -CREATE VIEW rw_view1 AS SELECT a AS aa, b AS bb FROM base_tbl; -INSERT INTO rw_view1 VALUES (3, 'Row 3'); -select * from base_tbl; - a | b ----+------- - 2 | Row 2 - 3 | Row 3 - 1 | Row 3 -(3 rows) - -DROP VIEW rw_view1; -DROP TRIGGER rw_view1_ins_trig on base_tbl; -DROP FUNCTION rw_view1_trig_fn(); -DROP TABLE base_tbl; --- view with ORDER BY -CREATE TABLE base_tbl (a int, b int); -INSERT INTO base_tbl VALUES (1,2), (4,5), (3,-3); -CREATE VIEW rw_view1 AS SELECT * FROM base_tbl ORDER BY a+b; -SELECT * FROM rw_view1; - a | b ----+---- - 3 | -3 - 1 | 2 - 4 | 5 -(3 rows) - -INSERT INTO rw_view1 VALUES (7,-8); -SELECT * FROM rw_view1; - a | b ----+---- - 7 | -8 - 3 | -3 - 1 | 2 - 4 | 5 -(4 rows) - -EXPLAIN (verbose, costs off) UPDATE rw_view1 SET b = b + 1 RETURNING *; - QUERY PLAN -------------------------------------------------- - Update on public.base_tbl - Output: base_tbl.a, base_tbl.b - -> Seq Scan on public.base_tbl - Output: (base_tbl.b + 1), base_tbl.ctid -(4 rows) - -UPDATE rw_view1 SET b = b + 1 RETURNING *; - a | b ----+---- - 1 | 3 - 4 | 6 - 3 | -2 - 7 | -7 -(4 rows) - -SELECT * FROM rw_view1; - a | b ----+---- - 7 | -7 - 3 | -2 - 1 | 3 - 4 | 6 -(4 rows) - -DROP TABLE base_tbl CASCADE; -NOTICE: drop cascades to view rw_view1 --- multiple array-column updates -CREATE TABLE base_tbl (a int, arr int[]); -INSERT INTO base_tbl VALUES (1,ARRAY[2]), (3,ARRAY[4]); -CREATE VIEW rw_view1 AS SELECT * FROM base_tbl; -UPDATE rw_view1 SET arr[1] = 42, arr[2] = 77 WHERE a = 3; -SELECT * FROM rw_view1; - a | arr ----+--------- - 1 | {2} - 3 | {42,77} -(2 rows) - -DROP TABLE base_tbl CASCADE; -NOTICE: drop cascades to view rw_view1 --- views with updatable and non-updatable columns -CREATE TABLE base_tbl(a float); -INSERT INTO base_tbl SELECT i/10.0 FROM generate_series(1,10) g(i); -CREATE VIEW rw_view1 AS - SELECT ctid, sin(a) s, a, cos(a) c - FROM base_tbl - WHERE a != 0 - ORDER BY abs(a); -INSERT INTO rw_view1 VALUES (null, null, 1.1, null); -- should fail -ERROR: cannot insert into column "ctid" of view "rw_view1" -DETAIL: View columns that refer to system columns are not updatable. -INSERT INTO rw_view1 (s, c, a) VALUES (null, null, 1.1); -- should fail -ERROR: cannot insert into column "s" of view "rw_view1" -DETAIL: View columns that are not columns of their base relation are not updatable. -INSERT INTO rw_view1 (s, c, a) VALUES (default, default, 1.1); -- should fail -ERROR: cannot insert into column "s" of view "rw_view1" -DETAIL: View columns that are not columns of their base relation are not updatable. -INSERT INTO rw_view1 (a) VALUES (1.1) RETURNING a, s, c; -- OK - a | s | c ------+-------------------+------------------- - 1.1 | 0.891207360061435 | 0.453596121425577 -(1 row) - -UPDATE rw_view1 SET s = s WHERE a = 1.1; -- should fail -ERROR: cannot update column "s" of view "rw_view1" -DETAIL: View columns that are not columns of their base relation are not updatable. -UPDATE rw_view1 SET a = 1.05 WHERE a = 1.1 RETURNING s; -- OK - s -------------------- - 0.867423225594017 -(1 row) - -DELETE FROM rw_view1 WHERE a = 1.05; -- OK -CREATE VIEW rw_view2 AS - SELECT s, c, s/c t, a base_a, ctid - FROM rw_view1; -INSERT INTO rw_view2 VALUES (null, null, null, 1.1, null); -- should fail -ERROR: cannot insert into column "t" of view "rw_view2" -DETAIL: View columns that are not columns of their base relation are not updatable. -INSERT INTO rw_view2(s, c, base_a) VALUES (null, null, 1.1); -- should fail -ERROR: cannot insert into column "s" of view "rw_view1" -DETAIL: View columns that are not columns of their base relation are not updatable. -INSERT INTO rw_view2(base_a) VALUES (1.1) RETURNING t; -- OK - t ------------------- - 1.96475965724865 -(1 row) - -UPDATE rw_view2 SET s = s WHERE base_a = 1.1; -- should fail -ERROR: cannot update column "s" of view "rw_view1" -DETAIL: View columns that are not columns of their base relation are not updatable. -UPDATE rw_view2 SET t = t WHERE base_a = 1.1; -- should fail -ERROR: cannot update column "t" of view "rw_view2" -DETAIL: View columns that are not columns of their base relation are not updatable. -UPDATE rw_view2 SET base_a = 1.05 WHERE base_a = 1.1; -- OK -DELETE FROM rw_view2 WHERE base_a = 1.05 RETURNING base_a, s, c, t; -- OK - base_a | s | c | t ---------+-------------------+-------------------+------------------ - 1.05 | 0.867423225594017 | 0.497571047891727 | 1.74331530998317 -(1 row) - -CREATE VIEW rw_view3 AS - SELECT s, c, s/c t, ctid - FROM rw_view1; -INSERT INTO rw_view3 VALUES (null, null, null, null); -- should fail -ERROR: cannot insert into column "t" of view "rw_view3" -DETAIL: View columns that are not columns of their base relation are not updatable. -INSERT INTO rw_view3(s) VALUES (null); -- should fail -ERROR: cannot insert into column "s" of view "rw_view1" -DETAIL: View columns that are not columns of their base relation are not updatable. -UPDATE rw_view3 SET s = s; -- should fail -ERROR: cannot update column "s" of view "rw_view1" -DETAIL: View columns that are not columns of their base relation are not updatable. -DELETE FROM rw_view3 WHERE s = sin(0.1); -- should be OK -SELECT * FROM base_tbl ORDER BY a; - a ------ - 0.2 - 0.3 - 0.4 - 0.5 - 0.6 - 0.7 - 0.8 - 0.9 - 1 -(9 rows) - -SELECT table_name, is_insertable_into - FROM information_schema.tables - WHERE table_name LIKE E'r_\\_view%' - ORDER BY table_name; - table_name | is_insertable_into -------------+-------------------- - rw_view1 | YES - rw_view2 | YES - rw_view3 | NO -(3 rows) - -SELECT table_name, is_updatable, is_insertable_into - FROM information_schema.views - WHERE table_name LIKE E'r_\\_view%' - ORDER BY table_name; - table_name | is_updatable | is_insertable_into -------------+--------------+-------------------- - rw_view1 | YES | YES - rw_view2 | YES | YES - rw_view3 | NO | NO -(3 rows) - -SELECT table_name, column_name, is_updatable - FROM information_schema.columns - WHERE table_name LIKE E'r_\\_view%' - ORDER BY table_name, ordinal_position; - table_name | column_name | is_updatable -------------+-------------+-------------- - rw_view1 | ctid | NO - rw_view1 | s | NO - rw_view1 | a | YES - rw_view1 | c | NO - rw_view2 | s | NO - rw_view2 | c | NO - rw_view2 | t | NO - rw_view2 | base_a | YES - rw_view2 | ctid | NO - rw_view3 | s | NO - rw_view3 | c | NO - rw_view3 | t | NO - rw_view3 | ctid | NO -(13 rows) - -SELECT events & 4 != 0 AS upd, - events & 8 != 0 AS ins, - events & 16 != 0 AS del - FROM pg_catalog.pg_relation_is_updatable('rw_view3'::regclass, false) t(events); - upd | ins | del ------+-----+----- - f | f | t -(1 row) - -DROP TABLE base_tbl CASCADE; -NOTICE: drop cascades to 3 other objects -DETAIL: drop cascades to view rw_view1 -drop cascades to view rw_view2 -drop cascades to view rw_view3 --- view on table with GENERATED columns -CREATE TABLE base_tbl (id int, idplus1 int GENERATED ALWAYS AS (id + 1) STORED); -CREATE VIEW rw_view1 AS SELECT * FROM base_tbl; -INSERT INTO base_tbl (id) VALUES (1); -INSERT INTO rw_view1 (id) VALUES (2); -INSERT INTO base_tbl (id, idplus1) VALUES (3, DEFAULT); -INSERT INTO rw_view1 (id, idplus1) VALUES (4, DEFAULT); -INSERT INTO base_tbl (id, idplus1) VALUES (5, 6); -- error -ERROR: cannot insert a non-DEFAULT value into column "idplus1" -DETAIL: Column "idplus1" is a generated column. -INSERT INTO rw_view1 (id, idplus1) VALUES (6, 7); -- error -ERROR: cannot insert a non-DEFAULT value into column "idplus1" -DETAIL: Column "idplus1" is a generated column. -SELECT * FROM base_tbl; - id | idplus1 -----+--------- - 1 | 2 - 2 | 3 - 3 | 4 - 4 | 5 -(4 rows) - -UPDATE base_tbl SET id = 2000 WHERE id = 2; -UPDATE rw_view1 SET id = 3000 WHERE id = 3; -SELECT * FROM base_tbl; - id | idplus1 -------+--------- - 1 | 2 - 4 | 5 - 2000 | 2001 - 3000 | 3001 -(4 rows) - -DROP TABLE base_tbl CASCADE; -NOTICE: drop cascades to view rw_view1 --- inheritance tests -CREATE TABLE base_tbl_parent (a int); -CREATE TABLE base_tbl_child (CHECK (a > 0)) INHERITS (base_tbl_parent); -INSERT INTO base_tbl_parent SELECT * FROM generate_series(-8, -1); -INSERT INTO base_tbl_child SELECT * FROM generate_series(1, 8); -CREATE VIEW rw_view1 AS SELECT * FROM base_tbl_parent; -CREATE VIEW rw_view2 AS SELECT * FROM ONLY base_tbl_parent; -SELECT * FROM rw_view1 ORDER BY a; - a ----- - -8 - -7 - -6 - -5 - -4 - -3 - -2 - -1 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 -(16 rows) - -SELECT * FROM ONLY rw_view1 ORDER BY a; - a ----- - -8 - -7 - -6 - -5 - -4 - -3 - -2 - -1 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 -(16 rows) - -SELECT * FROM rw_view2 ORDER BY a; - a ----- - -8 - -7 - -6 - -5 - -4 - -3 - -2 - -1 -(8 rows) - -INSERT INTO rw_view1 VALUES (-100), (100); -INSERT INTO rw_view2 VALUES (-200), (200); -UPDATE rw_view1 SET a = a*10 WHERE a IN (-1, 1); -- Should produce -10 and 10 -UPDATE ONLY rw_view1 SET a = a*10 WHERE a IN (-2, 2); -- Should produce -20 and 20 -UPDATE rw_view2 SET a = a*10 WHERE a IN (-3, 3); -- Should produce -30 only -UPDATE ONLY rw_view2 SET a = a*10 WHERE a IN (-4, 4); -- Should produce -40 only -DELETE FROM rw_view1 WHERE a IN (-5, 5); -- Should delete -5 and 5 -DELETE FROM ONLY rw_view1 WHERE a IN (-6, 6); -- Should delete -6 and 6 -DELETE FROM rw_view2 WHERE a IN (-7, 7); -- Should delete -7 only -DELETE FROM ONLY rw_view2 WHERE a IN (-8, 8); -- Should delete -8 only -SELECT * FROM ONLY base_tbl_parent ORDER BY a; - a ------- - -200 - -100 - -40 - -30 - -20 - -10 - 100 - 200 -(8 rows) - -SELECT * FROM base_tbl_child ORDER BY a; - a ----- - 3 - 4 - 7 - 8 - 10 - 20 -(6 rows) - -MERGE INTO rw_view1 t USING (VALUES (-200), (10)) AS v(a) ON t.a = v.a - WHEN MATCHED THEN UPDATE SET a = t.a+1; -- Should produce -199 and 11 -MERGE INTO ONLY rw_view1 t USING (VALUES (-100), (20)) AS v(a) ON t.a = v.a - WHEN MATCHED THEN UPDATE SET a = t.a+1; -- Should produce -99 and 21 -MERGE INTO rw_view2 t USING (VALUES (-40), (3)) AS v(a) ON t.a = v.a - WHEN MATCHED THEN UPDATE SET a = t.a+1; -- Should produce -39 only -MERGE INTO ONLY rw_view2 t USING (VALUES (-30), (4)) AS v(a) ON t.a = v.a - WHEN MATCHED THEN UPDATE SET a = t.a+1; -- Should produce -29 only -SELECT * FROM ONLY base_tbl_parent ORDER BY a; - a ------- - -199 - -99 - -39 - -29 - -20 - -10 - 100 - 200 -(8 rows) - -SELECT * FROM base_tbl_child ORDER BY a; - a ----- - 3 - 4 - 7 - 8 - 11 - 21 -(6 rows) - -CREATE TABLE other_tbl_parent (id int); -CREATE TABLE other_tbl_child () INHERITS (other_tbl_parent); -INSERT INTO other_tbl_parent VALUES (7),(200); -INSERT INTO other_tbl_child VALUES (8),(100); -EXPLAIN (costs off) -UPDATE rw_view1 SET a = a + 1000 FROM other_tbl_parent WHERE a = id; - QUERY PLAN -------------------------------------------------------------------------- - Update on base_tbl_parent - Update on base_tbl_parent base_tbl_parent_1 - Update on base_tbl_child base_tbl_parent_2 - -> Merge Join - Merge Cond: (base_tbl_parent.a = other_tbl_parent.id) - -> Sort - Sort Key: base_tbl_parent.a - -> Append - -> Seq Scan on base_tbl_parent base_tbl_parent_1 - -> Seq Scan on base_tbl_child base_tbl_parent_2 - -> Sort - Sort Key: other_tbl_parent.id - -> Append - -> Seq Scan on other_tbl_parent other_tbl_parent_1 - -> Seq Scan on other_tbl_child other_tbl_parent_2 -(15 rows) - -UPDATE rw_view1 SET a = a + 1000 FROM other_tbl_parent WHERE a = id; -SELECT * FROM ONLY base_tbl_parent ORDER BY a; - a ------- - -199 - -99 - -39 - -29 - -20 - -10 - 1100 - 1200 -(8 rows) - -SELECT * FROM base_tbl_child ORDER BY a; - a ------- - 3 - 4 - 11 - 21 - 1007 - 1008 -(6 rows) - -DROP TABLE base_tbl_parent, base_tbl_child CASCADE; -NOTICE: drop cascades to 2 other objects -DETAIL: drop cascades to view rw_view1 -drop cascades to view rw_view2 -DROP TABLE other_tbl_parent CASCADE; -NOTICE: drop cascades to table other_tbl_child --- simple WITH CHECK OPTION -CREATE TABLE base_tbl (a int, b int DEFAULT 10); -INSERT INTO base_tbl VALUES (1,2), (2,3), (1,-1); -CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a < b - WITH LOCAL CHECK OPTION; -\d+ rw_view1 - View "public.rw_view1" - Column | Type | Collation | Nullable | Default | Storage | Description ---------+---------+-----------+----------+---------+---------+------------- - a | integer | | | | plain | - b | integer | | | | plain | -View definition: - SELECT a, - b - FROM base_tbl - WHERE a < b; -Options: check_option=local - -SELECT * FROM information_schema.views WHERE table_name = 'rw_view1'; - table_catalog | table_schema | table_name | view_definition | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into ----------------+--------------+------------+------------------+--------------+--------------+--------------------+----------------------+----------------------+---------------------------- - regression | public | rw_view1 | SELECT a, +| LOCAL | YES | YES | NO | NO | NO - | | | b +| | | | | | - | | | FROM base_tbl+| | | | | | - | | | WHERE (a < b); | | | | | | -(1 row) - -INSERT INTO rw_view1 VALUES(3,4); -- ok -INSERT INTO rw_view1 VALUES(4,3); -- should fail -ERROR: new row violates check option for view "rw_view1" -DETAIL: Failing row contains (4, 3). -INSERT INTO rw_view1 VALUES(5,null); -- should fail -ERROR: new row violates check option for view "rw_view1" -DETAIL: Failing row contains (5, null). -UPDATE rw_view1 SET b = 5 WHERE a = 3; -- ok -UPDATE rw_view1 SET b = -5 WHERE a = 3; -- should fail -ERROR: new row violates check option for view "rw_view1" -DETAIL: Failing row contains (3, -5). -INSERT INTO rw_view1(a) VALUES (9); -- ok -INSERT INTO rw_view1(a) VALUES (10); -- should fail -ERROR: new row violates check option for view "rw_view1" -DETAIL: Failing row contains (10, 10). -SELECT * FROM base_tbl ORDER BY a, b; - a | b ----+---- - 1 | -1 - 1 | 2 - 2 | 3 - 3 | 5 - 9 | 10 -(5 rows) - -MERGE INTO rw_view1 t USING (VALUES (10)) AS v(a) ON t.a = v.a - WHEN NOT MATCHED THEN INSERT VALUES (v.a, v.a + 1); -- ok -MERGE INTO rw_view1 t USING (VALUES (11)) AS v(a) ON t.a = v.a - WHEN NOT MATCHED THEN INSERT VALUES (v.a, v.a - 1); -- should fail -ERROR: new row violates check option for view "rw_view1" -DETAIL: Failing row contains (11, 10). -MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.a = v.a - WHEN MATCHED THEN UPDATE SET a = t.a - 1; -- ok -MERGE INTO rw_view1 t USING (VALUES (2)) AS v(a) ON t.a = v.a - WHEN MATCHED THEN UPDATE SET a = t.a + 1; -- should fail -ERROR: new row violates check option for view "rw_view1" -DETAIL: Failing row contains (3, 3). -SELECT * FROM base_tbl ORDER BY a, b; - a | b -----+---- - 0 | 2 - 1 | -1 - 2 | 3 - 3 | 5 - 9 | 10 - 10 | 11 -(6 rows) - -DROP TABLE base_tbl CASCADE; -NOTICE: drop cascades to view rw_view1 --- WITH LOCAL/CASCADED CHECK OPTION -CREATE TABLE base_tbl (a int); -CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a > 0; -CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a < 10 - WITH CHECK OPTION; -- implicitly cascaded -\d+ rw_view2 - View "public.rw_view2" - Column | Type | Collation | Nullable | Default | Storage | Description ---------+---------+-----------+----------+---------+---------+------------- - a | integer | | | | plain | -View definition: - SELECT a - FROM rw_view1 - WHERE a < 10; -Options: check_option=cascaded - -SELECT * FROM information_schema.views WHERE table_name = 'rw_view2'; - table_catalog | table_schema | table_name | view_definition | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into ----------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+---------------------------- - regression | public | rw_view2 | SELECT a +| CASCADED | YES | YES | NO | NO | NO - | | | FROM rw_view1 +| | | | | | - | | | WHERE (a < 10); | | | | | | -(1 row) - -INSERT INTO rw_view2 VALUES (-5); -- should fail -ERROR: new row violates check option for view "rw_view1" -DETAIL: Failing row contains (-5). -INSERT INTO rw_view2 VALUES (5); -- ok -INSERT INTO rw_view2 VALUES (15); -- should fail -ERROR: new row violates check option for view "rw_view2" -DETAIL: Failing row contains (15). -SELECT * FROM base_tbl; - a ---- - 5 -(1 row) - -UPDATE rw_view2 SET a = a - 10; -- should fail -ERROR: new row violates check option for view "rw_view1" -DETAIL: Failing row contains (-5). -UPDATE rw_view2 SET a = a + 10; -- should fail -ERROR: new row violates check option for view "rw_view2" -DETAIL: Failing row contains (15). -CREATE OR REPLACE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a < 10 - WITH LOCAL CHECK OPTION; -\d+ rw_view2 - View "public.rw_view2" - Column | Type | Collation | Nullable | Default | Storage | Description ---------+---------+-----------+----------+---------+---------+------------- - a | integer | | | | plain | -View definition: - SELECT a - FROM rw_view1 - WHERE a < 10; -Options: check_option=local - -SELECT * FROM information_schema.views WHERE table_name = 'rw_view2'; - table_catalog | table_schema | table_name | view_definition | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into ----------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+---------------------------- - regression | public | rw_view2 | SELECT a +| LOCAL | YES | YES | NO | NO | NO - | | | FROM rw_view1 +| | | | | | - | | | WHERE (a < 10); | | | | | | -(1 row) - -INSERT INTO rw_view2 VALUES (-10); -- ok, but not in view -INSERT INTO rw_view2 VALUES (20); -- should fail -ERROR: new row violates check option for view "rw_view2" -DETAIL: Failing row contains (20). -SELECT * FROM base_tbl; - a ------ - 5 - -10 -(2 rows) - -ALTER VIEW rw_view1 SET (check_option=here); -- invalid -ERROR: invalid value for enum option "check_option": here -DETAIL: Valid values are "local" and "cascaded". -ALTER VIEW rw_view1 SET (check_option=local); -INSERT INTO rw_view2 VALUES (-20); -- should fail -ERROR: new row violates check option for view "rw_view1" -DETAIL: Failing row contains (-20). -INSERT INTO rw_view2 VALUES (30); -- should fail -ERROR: new row violates check option for view "rw_view2" -DETAIL: Failing row contains (30). -ALTER VIEW rw_view2 RESET (check_option); -\d+ rw_view2 - View "public.rw_view2" - Column | Type | Collation | Nullable | Default | Storage | Description ---------+---------+-----------+----------+---------+---------+------------- - a | integer | | | | plain | -View definition: - SELECT a - FROM rw_view1 - WHERE a < 10; - -SELECT * FROM information_schema.views WHERE table_name = 'rw_view2'; - table_catalog | table_schema | table_name | view_definition | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into ----------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+---------------------------- - regression | public | rw_view2 | SELECT a +| NONE | YES | YES | NO | NO | NO - | | | FROM rw_view1 +| | | | | | - | | | WHERE (a < 10); | | | | | | -(1 row) - -INSERT INTO rw_view2 VALUES (30); -- ok, but not in view -SELECT * FROM base_tbl; - a ------ - 5 - -10 - 30 -(3 rows) - -DROP TABLE base_tbl CASCADE; -NOTICE: drop cascades to 2 other objects -DETAIL: drop cascades to view rw_view1 -drop cascades to view rw_view2 --- WITH CHECK OPTION with no local view qual -CREATE TABLE base_tbl (a int); -CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WITH CHECK OPTION; -CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a > 0; -CREATE VIEW rw_view3 AS SELECT * FROM rw_view2 WITH CHECK OPTION; -SELECT * FROM information_schema.views WHERE table_name LIKE E'rw\\_view_' ORDER BY table_name; - table_catalog | table_schema | table_name | view_definition | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into ----------------+--------------+------------+-------------------+--------------+--------------+--------------------+----------------------+----------------------+---------------------------- - regression | public | rw_view1 | SELECT a +| CASCADED | YES | YES | NO | NO | NO - | | | FROM base_tbl; | | | | | | - regression | public | rw_view2 | SELECT a +| NONE | YES | YES | NO | NO | NO - | | | FROM rw_view1 +| | | | | | - | | | WHERE (a > 0); | | | | | | - regression | public | rw_view3 | SELECT a +| CASCADED | YES | YES | NO | NO | NO - | | | FROM rw_view2; | | | | | | -(3 rows) - -INSERT INTO rw_view1 VALUES (-1); -- ok -INSERT INTO rw_view1 VALUES (1); -- ok -INSERT INTO rw_view2 VALUES (-2); -- ok, but not in view -INSERT INTO rw_view2 VALUES (2); -- ok -INSERT INTO rw_view3 VALUES (-3); -- should fail -ERROR: new row violates check option for view "rw_view2" -DETAIL: Failing row contains (-3). -INSERT INTO rw_view3 VALUES (3); -- ok -DROP TABLE base_tbl CASCADE; -NOTICE: drop cascades to 3 other objects -DETAIL: drop cascades to view rw_view1 -drop cascades to view rw_view2 -drop cascades to view rw_view3 --- WITH CHECK OPTION with scalar array ops -CREATE TABLE base_tbl (a int, b int[]); -CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a = ANY (b) - WITH CHECK OPTION; -INSERT INTO rw_view1 VALUES (1, ARRAY[1,2,3]); -- ok -INSERT INTO rw_view1 VALUES (10, ARRAY[4,5]); -- should fail -ERROR: new row violates check option for view "rw_view1" -DETAIL: Failing row contains (10, {4,5}). -UPDATE rw_view1 SET b[2] = -b[2] WHERE a = 1; -- ok -UPDATE rw_view1 SET b[1] = -b[1] WHERE a = 1; -- should fail -ERROR: new row violates check option for view "rw_view1" -DETAIL: Failing row contains (1, {-1,-2,3}). -PREPARE ins(int, int[]) AS INSERT INTO rw_view1 VALUES($1, $2); -EXECUTE ins(2, ARRAY[1,2,3]); -- ok -EXECUTE ins(10, ARRAY[4,5]); -- should fail -ERROR: new row violates check option for view "rw_view1" -DETAIL: Failing row contains (10, {4,5}). -DEALLOCATE PREPARE ins; -DROP TABLE base_tbl CASCADE; -NOTICE: drop cascades to view rw_view1 --- WITH CHECK OPTION with subquery -CREATE TABLE base_tbl (a int); -CREATE TABLE ref_tbl (a int PRIMARY KEY); -INSERT INTO ref_tbl SELECT * FROM generate_series(1,10); -CREATE VIEW rw_view1 AS - SELECT * FROM base_tbl b - WHERE EXISTS(SELECT 1 FROM ref_tbl r WHERE r.a = b.a) - WITH CHECK OPTION; -INSERT INTO rw_view1 VALUES (5); -- ok -INSERT INTO rw_view1 VALUES (15); -- should fail -ERROR: new row violates check option for view "rw_view1" -DETAIL: Failing row contains (15). -UPDATE rw_view1 SET a = a + 5; -- ok -UPDATE rw_view1 SET a = a + 5; -- should fail -ERROR: new row violates check option for view "rw_view1" -DETAIL: Failing row contains (15). -EXPLAIN (costs off) INSERT INTO rw_view1 VALUES (5); - QUERY PLAN ---------------------------------------------------------- - Insert on base_tbl b - -> Result - SubPlan 1 - -> Index Only Scan using ref_tbl_pkey on ref_tbl r - Index Cond: (a = b.a) -(5 rows) - -EXPLAIN (costs off) UPDATE rw_view1 SET a = a + 5; - QUERY PLAN ------------------------------------------------------------ - Update on base_tbl b - -> Hash Join - Hash Cond: (b.a = r.a) - -> Seq Scan on base_tbl b - -> Hash - -> Seq Scan on ref_tbl r - SubPlan 1 - -> Index Only Scan using ref_tbl_pkey on ref_tbl r_1 - Index Cond: (a = b.a) -(9 rows) - -DROP TABLE base_tbl, ref_tbl CASCADE; -NOTICE: drop cascades to view rw_view1 --- WITH CHECK OPTION with BEFORE trigger on base table -CREATE TABLE base_tbl (a int, b int); -CREATE FUNCTION base_tbl_trig_fn() -RETURNS trigger AS -$$ -BEGIN - NEW.b := 10; - RETURN NEW; -END; -$$ -LANGUAGE plpgsql; -CREATE TRIGGER base_tbl_trig BEFORE INSERT OR UPDATE ON base_tbl - FOR EACH ROW EXECUTE PROCEDURE base_tbl_trig_fn(); -CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a < b WITH CHECK OPTION; -INSERT INTO rw_view1 VALUES (5,0); -- ok -INSERT INTO rw_view1 VALUES (15, 20); -- should fail -ERROR: new row violates check option for view "rw_view1" -DETAIL: Failing row contains (15, 10). -UPDATE rw_view1 SET a = 20, b = 30; -- should fail -ERROR: new row violates check option for view "rw_view1" -DETAIL: Failing row contains (20, 10). -DROP TABLE base_tbl CASCADE; -NOTICE: drop cascades to view rw_view1 -DROP FUNCTION base_tbl_trig_fn(); --- WITH LOCAL CHECK OPTION with INSTEAD OF trigger on base view -CREATE TABLE base_tbl (a int, b int); -CREATE VIEW rw_view1 AS SELECT a FROM base_tbl WHERE a < b; -CREATE FUNCTION rw_view1_trig_fn() -RETURNS trigger AS -$$ -BEGIN - IF TG_OP = 'INSERT' THEN - INSERT INTO base_tbl VALUES (NEW.a, 10); - RETURN NEW; - ELSIF TG_OP = 'UPDATE' THEN - UPDATE base_tbl SET a=NEW.a WHERE a=OLD.a; - RETURN NEW; - ELSIF TG_OP = 'DELETE' THEN - DELETE FROM base_tbl WHERE a=OLD.a; - RETURN OLD; - END IF; -END; -$$ -LANGUAGE plpgsql; -CREATE TRIGGER rw_view1_trig - INSTEAD OF INSERT OR UPDATE OR DELETE ON rw_view1 - FOR EACH ROW EXECUTE PROCEDURE rw_view1_trig_fn(); -CREATE VIEW rw_view2 AS - SELECT * FROM rw_view1 WHERE a > 0 WITH LOCAL CHECK OPTION; -INSERT INTO rw_view2 VALUES (-5); -- should fail -ERROR: new row violates check option for view "rw_view2" -DETAIL: Failing row contains (-5). -MERGE INTO rw_view2 t USING (VALUES (-5)) AS v(a) ON t.a = v.a - WHEN NOT MATCHED THEN INSERT VALUES (v.a); -- should fail -ERROR: new row violates check option for view "rw_view2" -DETAIL: Failing row contains (-5). -INSERT INTO rw_view2 VALUES (5); -- ok -MERGE INTO rw_view2 t USING (VALUES (6)) AS v(a) ON t.a = v.a - WHEN NOT MATCHED THEN INSERT VALUES (v.a); -- ok -INSERT INTO rw_view2 VALUES (50); -- ok, but not in view -MERGE INTO rw_view2 t USING (VALUES (60)) AS v(a) ON t.a = v.a - WHEN NOT MATCHED THEN INSERT VALUES (v.a); -- ok, but not in view -UPDATE rw_view2 SET a = a - 10; -- should fail -ERROR: new row violates check option for view "rw_view2" -DETAIL: Failing row contains (-5). -MERGE INTO rw_view2 t USING (VALUES (6)) AS v(a) ON t.a = v.a - WHEN MATCHED THEN UPDATE SET a = t.a - 10; -- should fail -ERROR: new row violates check option for view "rw_view2" -DETAIL: Failing row contains (-4). -SELECT * FROM base_tbl; - a | b -----+---- - 5 | 10 - 6 | 10 - 50 | 10 - 60 | 10 -(4 rows) - --- Check option won't cascade down to base view with INSTEAD OF triggers -ALTER VIEW rw_view2 SET (check_option=cascaded); -INSERT INTO rw_view2 VALUES (100); -- ok, but not in view (doesn't fail rw_view1's check) -UPDATE rw_view2 SET a = 200 WHERE a = 5; -- ok, but not in view (doesn't fail rw_view1's check) -SELECT * FROM base_tbl; - a | b ------+---- - 6 | 10 - 50 | 10 - 60 | 10 - 100 | 10 - 200 | 10 -(5 rows) - --- Neither local nor cascaded check options work with INSTEAD rules -DROP TRIGGER rw_view1_trig ON rw_view1; -CREATE RULE rw_view1_ins_rule AS ON INSERT TO rw_view1 - DO INSTEAD INSERT INTO base_tbl VALUES (NEW.a, 10); -CREATE RULE rw_view1_upd_rule AS ON UPDATE TO rw_view1 - DO INSTEAD UPDATE base_tbl SET a=NEW.a WHERE a=OLD.a; -INSERT INTO rw_view2 VALUES (-10); -- ok, but not in view (doesn't fail rw_view2's check) -INSERT INTO rw_view2 VALUES (5); -- ok -INSERT INTO rw_view2 VALUES (20); -- ok, but not in view (doesn't fail rw_view1's check) -UPDATE rw_view2 SET a = 30 WHERE a = 5; -- ok, but not in view (doesn't fail rw_view1's check) -INSERT INTO rw_view2 VALUES (5); -- ok -UPDATE rw_view2 SET a = -5 WHERE a = 5; -- ok, but not in view (doesn't fail rw_view2's check) -SELECT * FROM base_tbl; - a | b ------+---- - 6 | 10 - 50 | 10 - 60 | 10 - 100 | 10 - 200 | 10 - -10 | 10 - 20 | 10 - 30 | 10 - -5 | 10 -(9 rows) - -DROP TABLE base_tbl CASCADE; -NOTICE: drop cascades to 2 other objects -DETAIL: drop cascades to view rw_view1 -drop cascades to view rw_view2 -DROP FUNCTION rw_view1_trig_fn(); -CREATE TABLE base_tbl (a int); -CREATE VIEW rw_view1 AS SELECT a,10 AS b FROM base_tbl; -CREATE RULE rw_view1_ins_rule AS ON INSERT TO rw_view1 - DO INSTEAD INSERT INTO base_tbl VALUES (NEW.a); -CREATE VIEW rw_view2 AS - SELECT * FROM rw_view1 WHERE a > b WITH LOCAL CHECK OPTION; -INSERT INTO rw_view2 VALUES (2,3); -- ok, but not in view (doesn't fail rw_view2's check) -DROP TABLE base_tbl CASCADE; -NOTICE: drop cascades to 2 other objects -DETAIL: drop cascades to view rw_view1 -drop cascades to view rw_view2 --- security barrier view -CREATE TABLE base_tbl (person text, visibility text); -INSERT INTO base_tbl VALUES ('Tom', 'public'), - ('Dick', 'private'), - ('Harry', 'public'); -CREATE VIEW rw_view1 AS - SELECT person FROM base_tbl WHERE visibility = 'public'; -CREATE FUNCTION snoop(anyelement) -RETURNS boolean AS -$$ -BEGIN - RAISE NOTICE 'snooped value: %', $1; - RETURN true; -END; -$$ -LANGUAGE plpgsql COST 0.000001; -CREATE OR REPLACE FUNCTION leakproof(anyelement) -RETURNS boolean AS -$$ -BEGIN - RETURN true; -END; -$$ -LANGUAGE plpgsql STRICT IMMUTABLE LEAKPROOF; -SELECT * FROM rw_view1 WHERE snoop(person); -NOTICE: snooped value: Tom -NOTICE: snooped value: Dick -NOTICE: snooped value: Harry - person --------- - Tom - Harry -(2 rows) - -UPDATE rw_view1 SET person=person WHERE snoop(person); -NOTICE: snooped value: Tom -NOTICE: snooped value: Dick -NOTICE: snooped value: Harry -DELETE FROM rw_view1 WHERE NOT snoop(person); -NOTICE: snooped value: Dick -NOTICE: snooped value: Tom -NOTICE: snooped value: Harry -ALTER VIEW rw_view1 SET (security_barrier = true); -SELECT table_name, is_insertable_into - FROM information_schema.tables - WHERE table_name = 'rw_view1'; - table_name | is_insertable_into -------------+-------------------- - rw_view1 | YES -(1 row) - -SELECT table_name, is_updatable, is_insertable_into - FROM information_schema.views - WHERE table_name = 'rw_view1'; - table_name | is_updatable | is_insertable_into -------------+--------------+-------------------- - rw_view1 | YES | YES -(1 row) - -SELECT table_name, column_name, is_updatable - FROM information_schema.columns - WHERE table_name = 'rw_view1' - ORDER BY ordinal_position; - table_name | column_name | is_updatable -------------+-------------+-------------- - rw_view1 | person | YES -(1 row) - -SELECT * FROM rw_view1 WHERE snoop(person); -NOTICE: snooped value: Tom -NOTICE: snooped value: Harry - person --------- - Tom - Harry -(2 rows) - -UPDATE rw_view1 SET person=person WHERE snoop(person); -NOTICE: snooped value: Tom -NOTICE: snooped value: Harry -DELETE FROM rw_view1 WHERE NOT snoop(person); -NOTICE: snooped value: Tom -NOTICE: snooped value: Harry -MERGE INTO rw_view1 t - USING (VALUES ('Tom'), ('Dick'), ('Harry')) AS v(person) ON t.person = v.person - WHEN MATCHED AND snoop(t.person) THEN UPDATE SET person = v.person; -NOTICE: snooped value: Tom -NOTICE: snooped value: Harry -EXPLAIN (costs off) SELECT * FROM rw_view1 WHERE snoop(person); - QUERY PLAN ------------------------------------------------ - Subquery Scan on rw_view1 - Filter: snoop(rw_view1.person) - -> Seq Scan on base_tbl - Filter: (visibility = 'public'::text) -(4 rows) - -EXPLAIN (costs off) UPDATE rw_view1 SET person=person WHERE snoop(person); - QUERY PLAN -------------------------------------------------------------------- - Update on base_tbl - -> Seq Scan on base_tbl - Filter: ((visibility = 'public'::text) AND snoop(person)) -(3 rows) - -EXPLAIN (costs off) DELETE FROM rw_view1 WHERE NOT snoop(person); - QUERY PLAN -------------------------------------------------------------------------- - Delete on base_tbl - -> Seq Scan on base_tbl - Filter: ((visibility = 'public'::text) AND (NOT snoop(person))) -(3 rows) - -EXPLAIN (costs off) -MERGE INTO rw_view1 t - USING (VALUES ('Tom'), ('Dick'), ('Harry')) AS v(person) ON t.person = v.person - WHEN MATCHED AND snoop(t.person) THEN UPDATE SET person = v.person; - QUERY PLAN -------------------------------------------------------------- - Merge on base_tbl - -> Nested Loop - Join Filter: (base_tbl.person = "*VALUES*".column1) - -> Seq Scan on base_tbl - Filter: (visibility = 'public'::text) - -> Materialize - -> Values Scan on "*VALUES*" -(7 rows) - --- security barrier view on top of security barrier view -CREATE VIEW rw_view2 WITH (security_barrier = true) AS - SELECT * FROM rw_view1 WHERE snoop(person); -SELECT table_name, is_insertable_into - FROM information_schema.tables - WHERE table_name = 'rw_view2'; - table_name | is_insertable_into -------------+-------------------- - rw_view2 | YES -(1 row) - -SELECT table_name, is_updatable, is_insertable_into - FROM information_schema.views - WHERE table_name = 'rw_view2'; - table_name | is_updatable | is_insertable_into -------------+--------------+-------------------- - rw_view2 | YES | YES -(1 row) - -SELECT table_name, column_name, is_updatable - FROM information_schema.columns - WHERE table_name = 'rw_view2' - ORDER BY ordinal_position; - table_name | column_name | is_updatable -------------+-------------+-------------- - rw_view2 | person | YES -(1 row) - -SELECT * FROM rw_view2 WHERE snoop(person); -NOTICE: snooped value: Tom -NOTICE: snooped value: Tom -NOTICE: snooped value: Harry -NOTICE: snooped value: Harry - person --------- - Tom - Harry -(2 rows) - -UPDATE rw_view2 SET person=person WHERE snoop(person); -NOTICE: snooped value: Tom -NOTICE: snooped value: Tom -NOTICE: snooped value: Harry -NOTICE: snooped value: Harry -DELETE FROM rw_view2 WHERE NOT snoop(person); -NOTICE: snooped value: Tom -NOTICE: snooped value: Tom -NOTICE: snooped value: Harry -NOTICE: snooped value: Harry -MERGE INTO rw_view2 t - USING (VALUES ('Tom'), ('Dick'), ('Harry')) AS v(person) ON t.person = v.person - WHEN MATCHED AND snoop(t.person) THEN UPDATE SET person = v.person; -NOTICE: snooped value: Tom -NOTICE: snooped value: Tom -NOTICE: snooped value: Harry -NOTICE: snooped value: Harry -EXPLAIN (costs off) SELECT * FROM rw_view2 WHERE snoop(person); - QUERY PLAN ------------------------------------------------------ - Subquery Scan on rw_view2 - Filter: snoop(rw_view2.person) - -> Subquery Scan on rw_view1 - Filter: snoop(rw_view1.person) - -> Seq Scan on base_tbl - Filter: (visibility = 'public'::text) -(6 rows) - -EXPLAIN (costs off) UPDATE rw_view2 SET person=person WHERE snoop(person); - QUERY PLAN -------------------------------------------------------------------------------------- - Update on base_tbl - -> Seq Scan on base_tbl - Filter: ((visibility = 'public'::text) AND snoop(person) AND snoop(person)) -(3 rows) - -EXPLAIN (costs off) DELETE FROM rw_view2 WHERE NOT snoop(person); - QUERY PLAN -------------------------------------------------------------------------------------------- - Delete on base_tbl - -> Seq Scan on base_tbl - Filter: ((visibility = 'public'::text) AND snoop(person) AND (NOT snoop(person))) -(3 rows) - -EXPLAIN (costs off) -MERGE INTO rw_view2 t - USING (VALUES ('Tom'), ('Dick'), ('Harry')) AS v(person) ON t.person = v.person - WHEN MATCHED AND snoop(t.person) THEN UPDATE SET person = v.person; - QUERY PLAN -------------------------------------------------------------------------- - Merge on base_tbl - -> Nested Loop - Join Filter: (base_tbl.person = "*VALUES*".column1) - -> Seq Scan on base_tbl - Filter: ((visibility = 'public'::text) AND snoop(person)) - -> Values Scan on "*VALUES*" -(6 rows) - -DROP TABLE base_tbl CASCADE; -NOTICE: drop cascades to 2 other objects -DETAIL: drop cascades to view rw_view1 -drop cascades to view rw_view2 --- security barrier view on top of table with rules -CREATE TABLE base_tbl(id int PRIMARY KEY, data text, deleted boolean); -INSERT INTO base_tbl VALUES (1, 'Row 1', false), (2, 'Row 2', true); -CREATE RULE base_tbl_ins_rule AS ON INSERT TO base_tbl - WHERE EXISTS (SELECT 1 FROM base_tbl t WHERE t.id = new.id) - DO INSTEAD - UPDATE base_tbl SET data = new.data, deleted = false WHERE id = new.id; -CREATE RULE base_tbl_del_rule AS ON DELETE TO base_tbl - DO INSTEAD - UPDATE base_tbl SET deleted = true WHERE id = old.id; -CREATE VIEW rw_view1 WITH (security_barrier=true) AS - SELECT id, data FROM base_tbl WHERE NOT deleted; -SELECT * FROM rw_view1; - id | data -----+------- - 1 | Row 1 -(1 row) - -EXPLAIN (costs off) DELETE FROM rw_view1 WHERE id = 1 AND snoop(data); - QUERY PLAN -------------------------------------------------------------------- - Update on base_tbl base_tbl_1 - -> Nested Loop - -> Index Scan using base_tbl_pkey on base_tbl base_tbl_1 - Index Cond: (id = 1) - -> Index Scan using base_tbl_pkey on base_tbl - Index Cond: (id = 1) - Filter: ((NOT deleted) AND snoop(data)) -(7 rows) - -DELETE FROM rw_view1 WHERE id = 1 AND snoop(data); -NOTICE: snooped value: Row 1 -EXPLAIN (costs off) INSERT INTO rw_view1 VALUES (2, 'New row 2'); - QUERY PLAN ------------------------------------------------------------ - Insert on base_tbl - InitPlan 1 - -> Index Only Scan using base_tbl_pkey on base_tbl t - Index Cond: (id = 2) - -> Result - One-Time Filter: ((InitPlan 1).col1 IS NOT TRUE) - - Update on base_tbl - InitPlan 1 - -> Index Only Scan using base_tbl_pkey on base_tbl t - Index Cond: (id = 2) - -> Result - One-Time Filter: (InitPlan 1).col1 - -> Index Scan using base_tbl_pkey on base_tbl - Index Cond: (id = 2) -(15 rows) - -INSERT INTO rw_view1 VALUES (2, 'New row 2'); -SELECT * FROM base_tbl; - id | data | deleted -----+-----------+--------- - 1 | Row 1 | t - 2 | New row 2 | f -(2 rows) - -DROP TABLE base_tbl CASCADE; -NOTICE: drop cascades to view rw_view1 --- security barrier view based on inheritance set -CREATE TABLE t1 (a int, b float, c text); -CREATE INDEX t1_a_idx ON t1(a); -INSERT INTO t1 -SELECT i,i,'t1' FROM generate_series(1,10) g(i); -ANALYZE t1; -CREATE TABLE t11 (d text) INHERITS (t1); -CREATE INDEX t11_a_idx ON t11(a); -INSERT INTO t11 -SELECT i,i,'t11','t11d' FROM generate_series(1,10) g(i); -ANALYZE t11; -CREATE TABLE t12 (e int[]) INHERITS (t1); -CREATE INDEX t12_a_idx ON t12(a); -INSERT INTO t12 -SELECT i,i,'t12','{1,2}'::int[] FROM generate_series(1,10) g(i); -ANALYZE t12; -CREATE TABLE t111 () INHERITS (t11, t12); -NOTICE: merging multiple inherited definitions of column "a" -NOTICE: merging multiple inherited definitions of column "b" -NOTICE: merging multiple inherited definitions of column "c" -CREATE INDEX t111_a_idx ON t111(a); -INSERT INTO t111 -SELECT i,i,'t111','t111d','{1,1,1}'::int[] FROM generate_series(1,10) g(i); -ANALYZE t111; -CREATE VIEW v1 WITH (security_barrier=true) AS -SELECT *, (SELECT d FROM t11 WHERE t11.a = t1.a LIMIT 1) AS d -FROM t1 -WHERE a > 5 AND EXISTS(SELECT 1 FROM t12 WHERE t12.a = t1.a); -SELECT * FROM v1 WHERE a=3; -- should not see anything - a | b | c | d ----+---+---+--- -(0 rows) - -SELECT * FROM v1 WHERE a=8; - a | b | c | d ----+---+------+------ - 8 | 8 | t1 | t11d - 8 | 8 | t11 | t11d - 8 | 8 | t12 | t11d - 8 | 8 | t111 | t11d -(4 rows) - -EXPLAIN (VERBOSE, COSTS OFF) -UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a < 7 AND a != 6; - QUERY PLAN ------------------------------------------------------------------------------------------------------------ - Update on public.t1 - Update on public.t1 t1_1 - Update on public.t11 t1_2 - Update on public.t12 t1_3 - Update on public.t111 t1_4 - -> Result - Output: 100, t1.tableoid, t1.ctid - -> Append - -> Index Scan using t1_a_idx on public.t1 t1_1 - Output: t1_1.tableoid, t1_1.ctid - Index Cond: ((t1_1.a > 5) AND (t1_1.a < 7)) - Filter: ((t1_1.a <> 6) AND EXISTS(SubPlan 1) AND snoop(t1_1.a) AND leakproof(t1_1.a)) - SubPlan 1 - -> Append - -> Seq Scan on public.t12 t12_1 - Filter: (t12_1.a = t1_1.a) - -> Seq Scan on public.t111 t12_2 - Filter: (t12_2.a = t1_1.a) - -> Index Scan using t11_a_idx on public.t11 t1_2 - Output: t1_2.tableoid, t1_2.ctid - Index Cond: ((t1_2.a > 5) AND (t1_2.a < 7)) - Filter: ((t1_2.a <> 6) AND EXISTS(SubPlan 1) AND snoop(t1_2.a) AND leakproof(t1_2.a)) - -> Index Scan using t12_a_idx on public.t12 t1_3 - Output: t1_3.tableoid, t1_3.ctid - Index Cond: ((t1_3.a > 5) AND (t1_3.a < 7)) - Filter: ((t1_3.a <> 6) AND EXISTS(SubPlan 1) AND snoop(t1_3.a) AND leakproof(t1_3.a)) - -> Index Scan using t111_a_idx on public.t111 t1_4 - Output: t1_4.tableoid, t1_4.ctid - Index Cond: ((t1_4.a > 5) AND (t1_4.a < 7)) - Filter: ((t1_4.a <> 6) AND EXISTS(SubPlan 1) AND snoop(t1_4.a) AND leakproof(t1_4.a)) -(30 rows) - -UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a < 7 AND a != 6; -SELECT * FROM v1 WHERE a=100; -- Nothing should have been changed to 100 - a | b | c | d ----+---+---+--- -(0 rows) - -SELECT * FROM t1 WHERE a=100; -- Nothing should have been changed to 100 - a | b | c ----+---+--- -(0 rows) - -EXPLAIN (VERBOSE, COSTS OFF) -UPDATE v1 SET a=a+1 WHERE snoop(a) AND leakproof(a) AND a = 8; - QUERY PLAN ------------------------------------------------------------------------------------------ - Update on public.t1 - Update on public.t1 t1_1 - Update on public.t11 t1_2 - Update on public.t12 t1_3 - Update on public.t111 t1_4 - -> Result - Output: (t1.a + 1), t1.tableoid, t1.ctid - -> Append - -> Index Scan using t1_a_idx on public.t1 t1_1 - Output: t1_1.a, t1_1.tableoid, t1_1.ctid - Index Cond: ((t1_1.a > 5) AND (t1_1.a = 8)) - Filter: (EXISTS(SubPlan 1) AND snoop(t1_1.a) AND leakproof(t1_1.a)) - SubPlan 1 - -> Append - -> Seq Scan on public.t12 t12_1 - Filter: (t12_1.a = t1_1.a) - -> Seq Scan on public.t111 t12_2 - Filter: (t12_2.a = t1_1.a) - -> Index Scan using t11_a_idx on public.t11 t1_2 - Output: t1_2.a, t1_2.tableoid, t1_2.ctid - Index Cond: ((t1_2.a > 5) AND (t1_2.a = 8)) - Filter: (EXISTS(SubPlan 1) AND snoop(t1_2.a) AND leakproof(t1_2.a)) - -> Index Scan using t12_a_idx on public.t12 t1_3 - Output: t1_3.a, t1_3.tableoid, t1_3.ctid - Index Cond: ((t1_3.a > 5) AND (t1_3.a = 8)) - Filter: (EXISTS(SubPlan 1) AND snoop(t1_3.a) AND leakproof(t1_3.a)) - -> Index Scan using t111_a_idx on public.t111 t1_4 - Output: t1_4.a, t1_4.tableoid, t1_4.ctid - Index Cond: ((t1_4.a > 5) AND (t1_4.a = 8)) - Filter: (EXISTS(SubPlan 1) AND snoop(t1_4.a) AND leakproof(t1_4.a)) -(30 rows) - -UPDATE v1 SET a=a+1 WHERE snoop(a) AND leakproof(a) AND a = 8; -NOTICE: snooped value: 8 -NOTICE: snooped value: 8 -NOTICE: snooped value: 8 -NOTICE: snooped value: 8 -SELECT * FROM v1 WHERE b=8; - a | b | c | d ----+---+------+------ - 9 | 8 | t111 | t11d - 9 | 8 | t12 | t11d - 9 | 8 | t11 | t11d - 9 | 8 | t1 | t11d -(4 rows) - -DELETE FROM v1 WHERE snoop(a) AND leakproof(a); -- should not delete everything, just where a>5 -NOTICE: snooped value: 6 -NOTICE: snooped value: 7 -NOTICE: snooped value: 9 -NOTICE: snooped value: 10 -NOTICE: snooped value: 9 -NOTICE: snooped value: 6 -NOTICE: snooped value: 7 -NOTICE: snooped value: 9 -NOTICE: snooped value: 10 -NOTICE: snooped value: 9 -NOTICE: snooped value: 6 -NOTICE: snooped value: 7 -NOTICE: snooped value: 9 -NOTICE: snooped value: 10 -NOTICE: snooped value: 9 -NOTICE: snooped value: 6 -NOTICE: snooped value: 7 -NOTICE: snooped value: 9 -NOTICE: snooped value: 10 -NOTICE: snooped value: 9 -TABLE t1; -- verify all a<=5 are intact - a | b | c ----+---+------ - 1 | 1 | t1 - 2 | 2 | t1 - 3 | 3 | t1 - 4 | 4 | t1 - 5 | 5 | t1 - 1 | 1 | t11 - 2 | 2 | t11 - 3 | 3 | t11 - 4 | 4 | t11 - 5 | 5 | t11 - 1 | 1 | t12 - 2 | 2 | t12 - 3 | 3 | t12 - 4 | 4 | t12 - 5 | 5 | t12 - 1 | 1 | t111 - 2 | 2 | t111 - 3 | 3 | t111 - 4 | 4 | t111 - 5 | 5 | t111 -(20 rows) - -DROP TABLE t1, t11, t12, t111 CASCADE; -NOTICE: drop cascades to view v1 -DROP FUNCTION snoop(anyelement); -DROP FUNCTION leakproof(anyelement); -CREATE TABLE tx1 (a integer); -CREATE TABLE tx2 (b integer); -CREATE TABLE tx3 (c integer); -CREATE VIEW vx1 AS SELECT a FROM tx1 WHERE EXISTS(SELECT 1 FROM tx2 JOIN tx3 ON b=c); -INSERT INTO vx1 values (1); -SELECT * FROM tx1; - a ---- - 1 -(1 row) - -SELECT * FROM vx1; - a ---- -(0 rows) - -DROP VIEW vx1; -DROP TABLE tx1; -DROP TABLE tx2; -DROP TABLE tx3; -CREATE TABLE tx1 (a integer); -CREATE TABLE tx2 (b integer); -CREATE TABLE tx3 (c integer); -CREATE VIEW vx1 AS SELECT a FROM tx1 WHERE EXISTS(SELECT 1 FROM tx2 JOIN tx3 ON b=c); -INSERT INTO vx1 VALUES (1); -INSERT INTO vx1 VALUES (1); -SELECT * FROM tx1; - a ---- - 1 - 1 -(2 rows) - -SELECT * FROM vx1; - a ---- -(0 rows) - -DROP VIEW vx1; -DROP TABLE tx1; -DROP TABLE tx2; -DROP TABLE tx3; -CREATE TABLE tx1 (a integer, b integer); -CREATE TABLE tx2 (b integer, c integer); -CREATE TABLE tx3 (c integer, d integer); -ALTER TABLE tx1 DROP COLUMN b; -ALTER TABLE tx2 DROP COLUMN c; -ALTER TABLE tx3 DROP COLUMN d; -CREATE VIEW vx1 AS SELECT a FROM tx1 WHERE EXISTS(SELECT 1 FROM tx2 JOIN tx3 ON b=c); -INSERT INTO vx1 VALUES (1); -INSERT INTO vx1 VALUES (1); -SELECT * FROM tx1; - a ---- - 1 - 1 -(2 rows) - -SELECT * FROM vx1; - a ---- -(0 rows) - -DROP VIEW vx1; -DROP TABLE tx1; -DROP TABLE tx2; -DROP TABLE tx3; --- --- Test handling of vars from correlated subqueries in quals from outer --- security barrier views, per bug #13988 --- -CREATE TABLE t1 (a int, b text, c int); -INSERT INTO t1 VALUES (1, 'one', 10); -CREATE TABLE t2 (cc int); -INSERT INTO t2 VALUES (10), (20); -CREATE VIEW v1 WITH (security_barrier = true) AS - SELECT * FROM t1 WHERE (a > 0) - WITH CHECK OPTION; -CREATE VIEW v2 WITH (security_barrier = true) AS - SELECT * FROM v1 WHERE EXISTS (SELECT 1 FROM t2 WHERE t2.cc = v1.c) - WITH CHECK OPTION; -INSERT INTO v2 VALUES (2, 'two', 20); -- ok -INSERT INTO v2 VALUES (-2, 'minus two', 20); -- not allowed -ERROR: new row violates check option for view "v1" -DETAIL: Failing row contains (-2, minus two, 20). -INSERT INTO v2 VALUES (3, 'three', 30); -- not allowed -ERROR: new row violates check option for view "v2" -DETAIL: Failing row contains (3, three, 30). -UPDATE v2 SET b = 'ONE' WHERE a = 1; -- ok -UPDATE v2 SET a = -1 WHERE a = 1; -- not allowed -ERROR: new row violates check option for view "v1" -DETAIL: Failing row contains (-1, ONE, 10). -UPDATE v2 SET c = 30 WHERE a = 1; -- not allowed -ERROR: new row violates check option for view "v2" -DETAIL: Failing row contains (1, ONE, 30). -DELETE FROM v2 WHERE a = 2; -- ok -SELECT * FROM v2; - a | b | c ----+-----+---- - 1 | ONE | 10 -(1 row) - -DROP VIEW v2; -DROP VIEW v1; -DROP TABLE t2; -DROP TABLE t1; --- --- Test sub-select in nested security barrier views, per bug #17972 --- -CREATE TABLE t1 (a int); -CREATE VIEW v1 WITH (security_barrier = true) AS - SELECT * FROM t1; -CREATE RULE v1_upd_rule AS ON UPDATE TO v1 DO INSTEAD - UPDATE t1 SET a = NEW.a WHERE a = OLD.a; -CREATE VIEW v2 WITH (security_barrier = true) AS - SELECT * FROM v1 WHERE EXISTS (SELECT 1); -EXPLAIN (COSTS OFF) UPDATE v2 SET a = 1; - QUERY PLAN --------------------------------------------------------------- - Update on t1 - InitPlan 1 - -> Result - -> Merge Join - Merge Cond: (t1.a = v1.a) - -> Sort - Sort Key: t1.a - -> Seq Scan on t1 - -> Sort - Sort Key: v1.a - -> Subquery Scan on v1 - -> Result - One-Time Filter: (InitPlan 1).col1 - -> Seq Scan on t1 t1_1 -(14 rows) - -DROP VIEW v2; -DROP VIEW v1; -DROP TABLE t1; --- --- Test CREATE OR REPLACE VIEW turning a non-updatable view into an --- auto-updatable view and adding check options in a single step --- -CREATE TABLE t1 (a int, b text); -CREATE VIEW v1 AS SELECT null::int AS a; -CREATE OR REPLACE VIEW v1 AS SELECT * FROM t1 WHERE a > 0 WITH CHECK OPTION; -INSERT INTO v1 VALUES (1, 'ok'); -- ok -INSERT INTO v1 VALUES (-1, 'invalid'); -- should fail -ERROR: new row violates check option for view "v1" -DETAIL: Failing row contains (-1, invalid). -DROP VIEW v1; -DROP TABLE t1; --- check that an auto-updatable view on a partitioned table works correctly -create table uv_pt (a int, b int, v varchar) partition by range (a, b); -create table uv_pt1 (b int not null, v varchar, a int not null) partition by range (b); -create table uv_pt11 (like uv_pt1); -alter table uv_pt11 drop a; -alter table uv_pt11 add a int; -alter table uv_pt11 drop a; -alter table uv_pt11 add a int not null; -alter table uv_pt1 attach partition uv_pt11 for values from (2) to (5); -alter table uv_pt attach partition uv_pt1 for values from (1, 2) to (1, 10); -create view uv_ptv as select * from uv_pt; -select events & 4 != 0 AS upd, - events & 8 != 0 AS ins, - events & 16 != 0 AS del - from pg_catalog.pg_relation_is_updatable('uv_pt'::regclass, false) t(events); - upd | ins | del ------+-----+----- - t | t | t -(1 row) - -select pg_catalog.pg_column_is_updatable('uv_pt'::regclass, 1::smallint, false); - pg_column_is_updatable ------------------------- - t -(1 row) - -select pg_catalog.pg_column_is_updatable('uv_pt'::regclass, 2::smallint, false); - pg_column_is_updatable ------------------------- - t -(1 row) - -select table_name, is_updatable, is_insertable_into - from information_schema.views where table_name = 'uv_ptv'; - table_name | is_updatable | is_insertable_into -------------+--------------+-------------------- - uv_ptv | YES | YES -(1 row) - -select table_name, column_name, is_updatable - from information_schema.columns where table_name = 'uv_ptv' order by column_name; - table_name | column_name | is_updatable -------------+-------------+-------------- - uv_ptv | a | YES - uv_ptv | b | YES - uv_ptv | v | YES -(3 rows) - -insert into uv_ptv values (1, 2); -select tableoid::regclass, * from uv_pt; - tableoid | a | b | v -----------+---+---+--- - uv_pt11 | 1 | 2 | -(1 row) - -create view uv_ptv_wco as select * from uv_pt where a = 0 with check option; -insert into uv_ptv_wco values (1, 2); -ERROR: new row violates check option for view "uv_ptv_wco" -DETAIL: Failing row contains (1, 2, null). -merge into uv_ptv t - using (values (1,2), (1,4)) as v(a,b) on t.a = v.a -- fail: matches 2 src rows - when matched then update set b = t.b + 1 - when not matched then insert values (v.a, v.b + 1); -ERROR: MERGE command cannot affect row a second time -HINT: Ensure that not more than one source row matches any one target row. -merge into uv_ptv t - using (values (1,2), (1,4)) as v(a,b) on t.a = v.a and t.b = v.b - when matched then update set b = t.b + 1 - when not matched then insert values (v.a, v.b + 1); -- fail: no partition for b=5 -ERROR: no partition of relation "uv_pt1" found for row -DETAIL: Partition key of the failing row contains (b) = (5). -merge into uv_ptv t - using (values (1,2), (1,3)) as v(a,b) on t.a = v.a and t.b = v.b - when matched then update set b = t.b + 1 - when not matched then insert values (v.a, v.b + 1); -- ok -select tableoid::regclass, * from uv_pt order by a, b; - tableoid | a | b | v -----------+---+---+--- - uv_pt11 | 1 | 3 | - uv_pt11 | 1 | 4 | -(2 rows) - -drop view uv_ptv, uv_ptv_wco; -drop table uv_pt, uv_pt1, uv_pt11; --- check that wholerow vars appearing in WITH CHECK OPTION constraint expressions --- work fine with partitioned tables -create table wcowrtest (a int) partition by list (a); -create table wcowrtest1 partition of wcowrtest for values in (1); -create view wcowrtest_v as select * from wcowrtest where wcowrtest = '(2)'::wcowrtest with check option; -insert into wcowrtest_v values (1); -ERROR: new row violates check option for view "wcowrtest_v" -DETAIL: Failing row contains (1). -alter table wcowrtest add b text; -create table wcowrtest2 (b text, c int, a int); -alter table wcowrtest2 drop c; -alter table wcowrtest attach partition wcowrtest2 for values in (2); -create table sometable (a int, b text); -insert into sometable values (1, 'a'), (2, 'b'); -create view wcowrtest_v2 as - select * - from wcowrtest r - where r in (select s from sometable s where r.a = s.a) -with check option; --- WITH CHECK qual will be processed with wcowrtest2's --- rowtype after tuple-routing -insert into wcowrtest_v2 values (2, 'no such row in sometable'); -ERROR: new row violates check option for view "wcowrtest_v2" -DETAIL: Failing row contains (2, no such row in sometable). -drop view wcowrtest_v, wcowrtest_v2; -drop table wcowrtest, sometable; --- Check INSERT .. ON CONFLICT DO UPDATE works correctly when the view's --- columns are named and ordered differently than the underlying table's. -create table uv_iocu_tab (a text unique, b float); -insert into uv_iocu_tab values ('xyxyxy', 0); -create view uv_iocu_view as - select b, b+1 as c, a, '2.0'::text as two from uv_iocu_tab; -insert into uv_iocu_view (a, b) values ('xyxyxy', 1) - on conflict (a) do update set b = uv_iocu_view.b; -select * from uv_iocu_tab; - a | b ---------+--- - xyxyxy | 0 -(1 row) - -insert into uv_iocu_view (a, b) values ('xyxyxy', 1) - on conflict (a) do update set b = excluded.b; -select * from uv_iocu_tab; - a | b ---------+--- - xyxyxy | 1 -(1 row) - --- OK to access view columns that are not present in underlying base --- relation in the ON CONFLICT portion of the query -insert into uv_iocu_view (a, b) values ('xyxyxy', 3) - on conflict (a) do update set b = cast(excluded.two as float); -select * from uv_iocu_tab; - a | b ---------+--- - xyxyxy | 2 -(1 row) - -explain (costs off) -insert into uv_iocu_view (a, b) values ('xyxyxy', 3) - on conflict (a) do update set b = excluded.b where excluded.c > 0; - QUERY PLAN ------------------------------------------------------------------------------------ - Insert on uv_iocu_tab - Conflict Resolution: UPDATE - Conflict Arbiter Indexes: uv_iocu_tab_a_key - Conflict Filter: ((excluded.b + '1'::double precision) > '0'::double precision) - -> Result -(5 rows) - -insert into uv_iocu_view (a, b) values ('xyxyxy', 3) - on conflict (a) do update set b = excluded.b where excluded.c > 0; -select * from uv_iocu_tab; - a | b ---------+--- - xyxyxy | 3 -(1 row) - -drop view uv_iocu_view; -drop table uv_iocu_tab; --- Test whole-row references to the view -create table uv_iocu_tab (a int unique, b text); -create view uv_iocu_view as - select b as bb, a as aa, uv_iocu_tab::text as cc from uv_iocu_tab; -insert into uv_iocu_view (aa,bb) values (1,'x'); -explain (costs off) -insert into uv_iocu_view (aa,bb) values (1,'y') - on conflict (aa) do update set bb = 'Rejected: '||excluded.* - where excluded.aa > 0 - and excluded.bb != '' - and excluded.cc is not null; - QUERY PLAN ---------------------------------------------------------------------------------------------------------- - Insert on uv_iocu_tab - Conflict Resolution: UPDATE - Conflict Arbiter Indexes: uv_iocu_tab_a_key - Conflict Filter: ((excluded.a > 0) AND (excluded.b <> ''::text) AND ((excluded.*)::text IS NOT NULL)) - -> Result -(5 rows) - -insert into uv_iocu_view (aa,bb) values (1,'y') - on conflict (aa) do update set bb = 'Rejected: '||excluded.* - where excluded.aa > 0 - and excluded.bb != '' - and excluded.cc is not null; -select * from uv_iocu_view; - bb | aa | cc --------------------------+----+--------------------------------- - Rejected: (y,1,"(1,y)") | 1 | (1,"Rejected: (y,1,""(1,y)"")") -(1 row) - --- Test omitting a column of the base relation -delete from uv_iocu_view; -insert into uv_iocu_view (aa,bb) values (1,'x'); -insert into uv_iocu_view (aa) values (1) - on conflict (aa) do update set bb = 'Rejected: '||excluded.*; -select * from uv_iocu_view; - bb | aa | cc ------------------------+----+------------------------------- - Rejected: (,1,"(1,)") | 1 | (1,"Rejected: (,1,""(1,)"")") -(1 row) - -alter table uv_iocu_tab alter column b set default 'table default'; -insert into uv_iocu_view (aa) values (1) - on conflict (aa) do update set bb = 'Rejected: '||excluded.*; -select * from uv_iocu_view; - bb | aa | cc --------------------------------------------------------+----+--------------------------------------------------------------------- - Rejected: ("table default",1,"(1,""table default"")") | 1 | (1,"Rejected: (""table default"",1,""(1,""""table default"""")"")") -(1 row) - -alter view uv_iocu_view alter column bb set default 'view default'; -insert into uv_iocu_view (aa) values (1) - on conflict (aa) do update set bb = 'Rejected: '||excluded.*; -select * from uv_iocu_view; - bb | aa | cc ------------------------------------------------------+----+------------------------------------------------------------------- - Rejected: ("view default",1,"(1,""view default"")") | 1 | (1,"Rejected: (""view default"",1,""(1,""""view default"""")"")") -(1 row) - --- Should fail to update non-updatable columns -insert into uv_iocu_view (aa) values (1) - on conflict (aa) do update set cc = 'XXX'; -ERROR: cannot insert into column "cc" of view "uv_iocu_view" -DETAIL: View columns that are not columns of their base relation are not updatable. -drop view uv_iocu_view; -drop table uv_iocu_tab; --- ON CONFLICT DO UPDATE permissions checks -create user regress_view_user1; -create user regress_view_user2; -set session authorization regress_view_user1; -create table base_tbl(a int unique, b text, c float); -insert into base_tbl values (1,'xxx',1.0); -create view rw_view1 as select b as bb, c as cc, a as aa from base_tbl; -grant select (aa,bb) on rw_view1 to regress_view_user2; -grant insert on rw_view1 to regress_view_user2; -grant update (bb) on rw_view1 to regress_view_user2; -set session authorization regress_view_user2; -insert into rw_view1 values ('yyy',2.0,1) - on conflict (aa) do update set bb = excluded.cc; -- Not allowed -ERROR: permission denied for view rw_view1 -insert into rw_view1 values ('yyy',2.0,1) - on conflict (aa) do update set bb = rw_view1.cc; -- Not allowed -ERROR: permission denied for view rw_view1 -insert into rw_view1 values ('yyy',2.0,1) - on conflict (aa) do update set bb = excluded.bb; -- OK -insert into rw_view1 values ('zzz',2.0,1) - on conflict (aa) do update set bb = rw_view1.bb||'xxx'; -- OK -insert into rw_view1 values ('zzz',2.0,1) - on conflict (aa) do update set cc = 3.0; -- Not allowed -ERROR: permission denied for view rw_view1 -reset session authorization; -select * from base_tbl; - a | b | c ----+--------+--- - 1 | yyyxxx | 1 -(1 row) - -set session authorization regress_view_user1; -grant select (a,b) on base_tbl to regress_view_user2; -grant insert (a,b) on base_tbl to regress_view_user2; -grant update (a,b) on base_tbl to regress_view_user2; -set session authorization regress_view_user2; -create view rw_view2 as select b as bb, c as cc, a as aa from base_tbl; -insert into rw_view2 (aa,bb) values (1,'xxx') - on conflict (aa) do update set bb = excluded.bb; -- Not allowed -ERROR: permission denied for table base_tbl -create view rw_view3 as select b as bb, a as aa from base_tbl; -insert into rw_view3 (aa,bb) values (1,'xxx') - on conflict (aa) do update set bb = excluded.bb; -- OK -reset session authorization; -select * from base_tbl; - a | b | c ----+-----+--- - 1 | xxx | 1 -(1 row) - -set session authorization regress_view_user2; -create view rw_view4 as select aa, bb, cc FROM rw_view1; -insert into rw_view4 (aa,bb) values (1,'yyy') - on conflict (aa) do update set bb = excluded.bb; -- Not allowed -ERROR: permission denied for view rw_view1 -create view rw_view5 as select aa, bb FROM rw_view1; -insert into rw_view5 (aa,bb) values (1,'yyy') - on conflict (aa) do update set bb = excluded.bb; -- OK -reset session authorization; -select * from base_tbl; - a | b | c ----+-----+--- - 1 | yyy | 1 -(1 row) - -drop view rw_view5; -drop view rw_view4; -drop view rw_view3; -drop view rw_view2; -drop view rw_view1; -drop table base_tbl; -drop user regress_view_user1; -drop user regress_view_user2; --- Test single- and multi-row inserts with table and view defaults. --- Table defaults should be used, unless overridden by view defaults. -create table base_tab_def (a int, b text default 'Table default', - c text default 'Table default', d text, e text); -create view base_tab_def_view as select * from base_tab_def; -alter view base_tab_def_view alter b set default 'View default'; -alter view base_tab_def_view alter d set default 'View default'; -insert into base_tab_def values (1); -insert into base_tab_def values (2), (3); -insert into base_tab_def values (4, default, default, default, default); -insert into base_tab_def values (5, default, default, default, default), - (6, default, default, default, default); -insert into base_tab_def_view values (11); -insert into base_tab_def_view values (12), (13); -insert into base_tab_def_view values (14, default, default, default, default); -insert into base_tab_def_view values (15, default, default, default, default), - (16, default, default, default, default); -insert into base_tab_def_view values (17), (default); -select * from base_tab_def order by a; - a | b | c | d | e -----+---------------+---------------+--------------+--- - 1 | Table default | Table default | | - 2 | Table default | Table default | | - 3 | Table default | Table default | | - 4 | Table default | Table default | | - 5 | Table default | Table default | | - 6 | Table default | Table default | | - 11 | View default | Table default | View default | - 12 | View default | Table default | View default | - 13 | View default | Table default | View default | - 14 | View default | Table default | View default | - 15 | View default | Table default | View default | - 16 | View default | Table default | View default | - 17 | View default | Table default | View default | - | View default | Table default | View default | -(14 rows) - --- Adding an INSTEAD OF trigger should cause NULLs to be inserted instead of --- table defaults, where there are no view defaults. -create function base_tab_def_view_instrig_func() returns trigger -as -$$ -begin - insert into base_tab_def values (new.a, new.b, new.c, new.d, new.e); - return new; -end; -$$ -language plpgsql; -create trigger base_tab_def_view_instrig instead of insert on base_tab_def_view - for each row execute function base_tab_def_view_instrig_func(); -truncate base_tab_def; -insert into base_tab_def values (1); -insert into base_tab_def values (2), (3); -insert into base_tab_def values (4, default, default, default, default); -insert into base_tab_def values (5, default, default, default, default), - (6, default, default, default, default); -insert into base_tab_def_view values (11); -insert into base_tab_def_view values (12), (13); -insert into base_tab_def_view values (14, default, default, default, default); -insert into base_tab_def_view values (15, default, default, default, default), - (16, default, default, default, default); -insert into base_tab_def_view values (17), (default); -select * from base_tab_def order by a; - a | b | c | d | e -----+---------------+---------------+--------------+--- - 1 | Table default | Table default | | - 2 | Table default | Table default | | - 3 | Table default | Table default | | - 4 | Table default | Table default | | - 5 | Table default | Table default | | - 6 | Table default | Table default | | - 11 | View default | | View default | - 12 | View default | | View default | - 13 | View default | | View default | - 14 | View default | | View default | - 15 | View default | | View default | - 16 | View default | | View default | - 17 | View default | | View default | - | View default | | View default | -(14 rows) - --- Using an unconditional DO INSTEAD rule should also cause NULLs to be --- inserted where there are no view defaults. -drop trigger base_tab_def_view_instrig on base_tab_def_view; -drop function base_tab_def_view_instrig_func; -create rule base_tab_def_view_ins_rule as on insert to base_tab_def_view - do instead insert into base_tab_def values (new.a, new.b, new.c, new.d, new.e); -truncate base_tab_def; -insert into base_tab_def values (1); -insert into base_tab_def values (2), (3); -insert into base_tab_def values (4, default, default, default, default); -insert into base_tab_def values (5, default, default, default, default), - (6, default, default, default, default); -insert into base_tab_def_view values (11); -insert into base_tab_def_view values (12), (13); -insert into base_tab_def_view values (14, default, default, default, default); -insert into base_tab_def_view values (15, default, default, default, default), - (16, default, default, default, default); -insert into base_tab_def_view values (17), (default); -select * from base_tab_def order by a; - a | b | c | d | e -----+---------------+---------------+--------------+--- - 1 | Table default | Table default | | - 2 | Table default | Table default | | - 3 | Table default | Table default | | - 4 | Table default | Table default | | - 5 | Table default | Table default | | - 6 | Table default | Table default | | - 11 | View default | | View default | - 12 | View default | | View default | - 13 | View default | | View default | - 14 | View default | | View default | - 15 | View default | | View default | - 16 | View default | | View default | - 17 | View default | | View default | - | View default | | View default | -(14 rows) - --- A DO ALSO rule should cause each row to be inserted twice. The first --- insert should behave the same as an auto-updatable view (using table --- defaults, unless overridden by view defaults). The second insert should --- behave the same as a rule-updatable view (inserting NULLs where there are --- no view defaults). -drop rule base_tab_def_view_ins_rule on base_tab_def_view; -create rule base_tab_def_view_ins_rule as on insert to base_tab_def_view - do also insert into base_tab_def values (new.a, new.b, new.c, new.d, new.e); -truncate base_tab_def; -insert into base_tab_def values (1); -insert into base_tab_def values (2), (3); -insert into base_tab_def values (4, default, default, default, default); -insert into base_tab_def values (5, default, default, default, default), - (6, default, default, default, default); -insert into base_tab_def_view values (11); -insert into base_tab_def_view values (12), (13); -insert into base_tab_def_view values (14, default, default, default, default); -insert into base_tab_def_view values (15, default, default, default, default), - (16, default, default, default, default); -insert into base_tab_def_view values (17), (default); -select * from base_tab_def order by a, c NULLS LAST; - a | b | c | d | e -----+---------------+---------------+--------------+--- - 1 | Table default | Table default | | - 2 | Table default | Table default | | - 3 | Table default | Table default | | - 4 | Table default | Table default | | - 5 | Table default | Table default | | - 6 | Table default | Table default | | - 11 | View default | Table default | View default | - 11 | View default | | View default | - 12 | View default | Table default | View default | - 12 | View default | | View default | - 13 | View default | Table default | View default | - 13 | View default | | View default | - 14 | View default | Table default | View default | - 14 | View default | | View default | - 15 | View default | Table default | View default | - 15 | View default | | View default | - 16 | View default | Table default | View default | - 16 | View default | | View default | - 17 | View default | Table default | View default | - 17 | View default | | View default | - | View default | Table default | View default | - | View default | | View default | -(22 rows) - --- Test a DO ALSO INSERT ... SELECT rule -drop rule base_tab_def_view_ins_rule on base_tab_def_view; -create rule base_tab_def_view_ins_rule as on insert to base_tab_def_view - do also insert into base_tab_def (a, b, e) select new.a, new.b, 'xxx'; -truncate base_tab_def; -insert into base_tab_def_view values (1, default, default, default, default); -insert into base_tab_def_view values (2, default, default, default, default), - (3, default, default, default, default); -select * from base_tab_def order by a, e nulls first; - a | b | c | d | e ----+--------------+---------------+--------------+----- - 1 | View default | Table default | View default | - 1 | View default | Table default | | xxx - 2 | View default | Table default | View default | - 2 | View default | Table default | | xxx - 3 | View default | Table default | View default | - 3 | View default | Table default | | xxx -(6 rows) - -drop view base_tab_def_view; -drop table base_tab_def; --- Test defaults with array assignments -create table base_tab (a serial, b int[], c text, d text default 'Table default'); -create view base_tab_view as select c, a, b from base_tab; -alter view base_tab_view alter column c set default 'View default'; -insert into base_tab_view (b[1], b[2], c, b[5], b[4], a, b[3]) -values (1, 2, default, 5, 4, default, 3), (10, 11, 'C value', 14, 13, 100, 12); -select * from base_tab order by a; - a | b | c | d ------+------------------+--------------+--------------- - 1 | {1,2,3,4,5} | View default | Table default - 100 | {10,11,12,13,14} | C value | Table default -(2 rows) - -drop view base_tab_view; -drop table base_tab; +server closed the connection unexpectedly + This probably means the server terminated abnormally + before or while processing the request. +connection to server was lost diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/sanity_check.out /tmp/cirrus-ci-build/build/testrun/regress/regress/results/sanity_check.out --- /tmp/cirrus-ci-build/src/test/regress/expected/sanity_check.out 2024-09-20 21:41:45.746024523 +0000 +++ /tmp/cirrus-ci-build/build/testrun/regress/regress/results/sanity_check.out 2024-09-20 21:46:05.409625202 +0000 @@ -1,27 +1,2 @@ -VACUUM; --- --- Sanity check: every system catalog that has OIDs should have --- a unique index on OID. This ensures that the OIDs will be unique, --- even after the OID counter wraps around. --- We exclude non-system tables from the check by looking at nspname. --- -SELECT relname, nspname - FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = relnamespace JOIN pg_attribute a ON (attrelid = c.oid AND attname = 'oid') - WHERE relkind = 'r' and c.oid < 16384 - AND ((nspname ~ '^pg_') IS NOT FALSE) - AND NOT EXISTS (SELECT 1 FROM pg_index i WHERE indrelid = c.oid - AND indkey[0] = a.attnum AND indnatts = 1 - AND indisunique AND indimmediate); - relname | nspname ----------+--------- -(0 rows) - --- check that relations without storage don't have relfilenode -SELECT relname, relkind - FROM pg_class - WHERE relkind IN ('v', 'c', 'f', 'p', 'I') - AND relfilenode <> 0; - relname | relkind ----------+--------- -(0 rows) - +psql: error: connection to server on socket "/tmp/pg_regress-03Az7d/.s.PGSQL.40051" failed: FATAL: the database system is not yet accepting connections +DETAIL: Consistent recovery state has not been yet reached. diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/select_into.out /tmp/cirrus-ci-build/build/testrun/regress/regress/results/select_into.out --- /tmp/cirrus-ci-build/src/test/regress/expected/select_into.out 2024-09-20 21:41:45.746024523 +0000 +++ /tmp/cirrus-ci-build/build/testrun/regress/regress/results/select_into.out 2024-09-20 21:46:05.493625086 +0000 @@ -1,222 +1,2 @@ --- --- SELECT_INTO --- -SELECT * - INTO TABLE sitmp1 - FROM onek - WHERE onek.unique1 < 2; -DROP TABLE sitmp1; -SELECT * - INTO TABLE sitmp1 - FROM onek2 - WHERE onek2.unique1 < 2; -DROP TABLE sitmp1; --- --- SELECT INTO and INSERT permission, if owner is not allowed to insert. --- -CREATE SCHEMA selinto_schema; -CREATE USER regress_selinto_user; -ALTER DEFAULT PRIVILEGES FOR ROLE regress_selinto_user - REVOKE INSERT ON TABLES FROM regress_selinto_user; -GRANT ALL ON SCHEMA selinto_schema TO public; -SET SESSION AUTHORIZATION regress_selinto_user; --- WITH DATA, passes. -CREATE TABLE selinto_schema.tbl_withdata1 (a) - AS SELECT generate_series(1,3) WITH DATA; -INSERT INTO selinto_schema.tbl_withdata1 VALUES (4); -ERROR: permission denied for table tbl_withdata1 -EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) - CREATE TABLE selinto_schema.tbl_withdata2 (a) AS - SELECT generate_series(1,3) WITH DATA; - QUERY PLAN --------------------------------------- - ProjectSet (actual rows=3 loops=1) - -> Result (actual rows=1 loops=1) -(2 rows) - --- WITH NO DATA, passes. -CREATE TABLE selinto_schema.tbl_nodata1 (a) AS - SELECT generate_series(1,3) WITH NO DATA; -EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) - CREATE TABLE selinto_schema.tbl_nodata2 (a) AS - SELECT generate_series(1,3) WITH NO DATA; - QUERY PLAN -------------------------------- - ProjectSet (never executed) - -> Result (never executed) -(2 rows) - --- EXECUTE and WITH DATA, passes. -PREPARE data_sel AS SELECT generate_series(1,3); -CREATE TABLE selinto_schema.tbl_withdata3 (a) AS - EXECUTE data_sel WITH DATA; -EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) - CREATE TABLE selinto_schema.tbl_withdata4 (a) AS - EXECUTE data_sel WITH DATA; - QUERY PLAN --------------------------------------- - ProjectSet (actual rows=3 loops=1) - -> Result (actual rows=1 loops=1) -(2 rows) - --- EXECUTE and WITH NO DATA, passes. -CREATE TABLE selinto_schema.tbl_nodata3 (a) AS - EXECUTE data_sel WITH NO DATA; -EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) - CREATE TABLE selinto_schema.tbl_nodata4 (a) AS - EXECUTE data_sel WITH NO DATA; - QUERY PLAN -------------------------------- - ProjectSet (never executed) - -> Result (never executed) -(2 rows) - -RESET SESSION AUTHORIZATION; -ALTER DEFAULT PRIVILEGES FOR ROLE regress_selinto_user - GRANT INSERT ON TABLES TO regress_selinto_user; -SET SESSION AUTHORIZATION regress_selinto_user; -RESET SESSION AUTHORIZATION; -DEALLOCATE data_sel; -DROP SCHEMA selinto_schema CASCADE; -NOTICE: drop cascades to 8 other objects -DETAIL: drop cascades to table selinto_schema.tbl_withdata1 -drop cascades to table selinto_schema.tbl_withdata2 -drop cascades to table selinto_schema.tbl_nodata1 -drop cascades to table selinto_schema.tbl_nodata2 -drop cascades to table selinto_schema.tbl_withdata3 -drop cascades to table selinto_schema.tbl_withdata4 -drop cascades to table selinto_schema.tbl_nodata3 -drop cascades to table selinto_schema.tbl_nodata4 -DROP USER regress_selinto_user; --- Tests for WITH NO DATA and column name consistency -CREATE TABLE ctas_base (i int, j int); -INSERT INTO ctas_base VALUES (1, 2); -CREATE TABLE ctas_nodata (ii, jj, kk) AS SELECT i, j FROM ctas_base; -- Error -ERROR: too many column names were specified -CREATE TABLE ctas_nodata (ii, jj, kk) AS SELECT i, j FROM ctas_base WITH NO DATA; -- Error -ERROR: too many column names were specified -CREATE TABLE ctas_nodata (ii, jj) AS SELECT i, j FROM ctas_base; -- OK -CREATE TABLE ctas_nodata_2 (ii, jj) AS SELECT i, j FROM ctas_base WITH NO DATA; -- OK -CREATE TABLE ctas_nodata_3 (ii) AS SELECT i, j FROM ctas_base; -- OK -CREATE TABLE ctas_nodata_4 (ii) AS SELECT i, j FROM ctas_base WITH NO DATA; -- OK -SELECT * FROM ctas_nodata; - ii | jj -----+---- - 1 | 2 -(1 row) - -SELECT * FROM ctas_nodata_2; - ii | jj -----+---- -(0 rows) - -SELECT * FROM ctas_nodata_3; - ii | j -----+--- - 1 | 2 -(1 row) - -SELECT * FROM ctas_nodata_4; - ii | j -----+--- -(0 rows) - -DROP TABLE ctas_base; -DROP TABLE ctas_nodata; -DROP TABLE ctas_nodata_2; -DROP TABLE ctas_nodata_3; -DROP TABLE ctas_nodata_4; --- --- CREATE TABLE AS/SELECT INTO as last command in a SQL function --- have been known to cause problems --- -CREATE FUNCTION make_table() RETURNS VOID -AS $$ - CREATE TABLE created_table AS SELECT * FROM int8_tbl; -$$ LANGUAGE SQL; -SELECT make_table(); - make_table ------------- - -(1 row) - -SELECT * FROM created_table; - q1 | q2 -------------------+------------------- - 123 | 456 - 123 | 4567890123456789 - 4567890123456789 | 123 - 4567890123456789 | 4567890123456789 - 4567890123456789 | -4567890123456789 -(5 rows) - --- Try EXPLAIN ANALYZE SELECT INTO and EXPLAIN ANALYZE CREATE TABLE AS --- WITH NO DATA, but hide the outputs since they won't be stable. -DO $$ -BEGIN - EXECUTE 'EXPLAIN ANALYZE SELECT * INTO TABLE easi FROM int8_tbl'; - EXECUTE 'EXPLAIN ANALYZE CREATE TABLE easi2 AS SELECT * FROM int8_tbl WITH NO DATA'; -END$$; -DROP TABLE created_table; -DROP TABLE easi, easi2; --- --- Disallowed uses of SELECT ... INTO. All should fail --- -DECLARE foo CURSOR FOR SELECT 1 INTO int4_tbl; -ERROR: SELECT ... INTO is not allowed here -LINE 1: DECLARE foo CURSOR FOR SELECT 1 INTO int4_tbl; - ^ -COPY (SELECT 1 INTO frak UNION SELECT 2) TO 'blob'; -ERROR: COPY (SELECT INTO) is not supported -SELECT * FROM (SELECT 1 INTO f) bar; -ERROR: SELECT ... INTO is not allowed here -LINE 1: SELECT * FROM (SELECT 1 INTO f) bar; - ^ -CREATE VIEW foo AS SELECT 1 INTO int4_tbl; -ERROR: views must not contain SELECT INTO -INSERT INTO int4_tbl SELECT 1 INTO f; -ERROR: SELECT ... INTO is not allowed here -LINE 1: INSERT INTO int4_tbl SELECT 1 INTO f; - ^ --- Test CREATE TABLE AS ... IF NOT EXISTS -CREATE TABLE ctas_ine_tbl AS SELECT 1; -CREATE TABLE ctas_ine_tbl AS SELECT 1 / 0; -- error -ERROR: relation "ctas_ine_tbl" already exists -CREATE TABLE IF NOT EXISTS ctas_ine_tbl AS SELECT 1 / 0; -- ok -NOTICE: relation "ctas_ine_tbl" already exists, skipping -CREATE TABLE ctas_ine_tbl AS SELECT 1 / 0 WITH NO DATA; -- error -ERROR: relation "ctas_ine_tbl" already exists -CREATE TABLE IF NOT EXISTS ctas_ine_tbl AS SELECT 1 / 0 WITH NO DATA; -- ok -NOTICE: relation "ctas_ine_tbl" already exists, skipping -EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) - CREATE TABLE ctas_ine_tbl AS SELECT 1 / 0; -- error -ERROR: relation "ctas_ine_tbl" already exists -EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) - CREATE TABLE IF NOT EXISTS ctas_ine_tbl AS SELECT 1 / 0; -- ok -NOTICE: relation "ctas_ine_tbl" already exists, skipping - QUERY PLAN ------------- -(0 rows) - -EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) - CREATE TABLE ctas_ine_tbl AS SELECT 1 / 0 WITH NO DATA; -- error -ERROR: relation "ctas_ine_tbl" already exists -EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) - CREATE TABLE IF NOT EXISTS ctas_ine_tbl AS SELECT 1 / 0 WITH NO DATA; -- ok -NOTICE: relation "ctas_ine_tbl" already exists, skipping - QUERY PLAN ------------- -(0 rows) - -PREPARE ctas_ine_query AS SELECT 1 / 0; -EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) - CREATE TABLE ctas_ine_tbl AS EXECUTE ctas_ine_query; -- error -ERROR: relation "ctas_ine_tbl" already exists -EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) - CREATE TABLE IF NOT EXISTS ctas_ine_tbl AS EXECUTE ctas_ine_query; -- ok -NOTICE: relation "ctas_ine_tbl" already exists, skipping - QUERY PLAN ------------- -(0 rows) - -DROP TABLE ctas_ine_tbl; +psql: error: connection to server on socket "/tmp/pg_regress-03Az7d/.s.PGSQL.40051" failed: FATAL: the database system is not yet accepting connections +DETAIL: Consistent recovery state has not been yet reached. diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/select_distinct.out /tmp/cirrus-ci-build/build/testrun/regress/regress/results/select_distinct.out --- /tmp/cirrus-ci-build/src/test/regress/expected/select_distinct.out 2024-09-20 21:41:45.746024523 +0000 +++ /tmp/cirrus-ci-build/build/testrun/regress/regress/results/select_distinct.out 2024-09-20 21:46:05.505625069 +0000 @@ -1,466 +1,2 @@ --- --- SELECT_DISTINCT --- --- --- awk '{print $3;}' onek.data | sort -n | uniq --- -SELECT DISTINCT two FROM onek ORDER BY 1; - two ------ - 0 - 1 -(2 rows) - --- --- awk '{print $5;}' onek.data | sort -n | uniq --- -SELECT DISTINCT ten FROM onek ORDER BY 1; - ten ------ - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 -(10 rows) - --- --- awk '{print $16;}' onek.data | sort -d | uniq --- -SELECT DISTINCT string4 FROM onek ORDER BY 1; - string4 ---------- - AAAAxx - HHHHxx - OOOOxx - VVVVxx -(4 rows) - --- --- awk '{print $3,$16,$5;}' onek.data | sort -d | uniq | --- sort +0n -1 +1d -2 +2n -3 --- -SELECT DISTINCT two, string4, ten - FROM onek - ORDER BY two using <, string4 using <, ten using <; - two | string4 | ten ------+---------+----- - 0 | AAAAxx | 0 - 0 | AAAAxx | 2 - 0 | AAAAxx | 4 - 0 | AAAAxx | 6 - 0 | AAAAxx | 8 - 0 | HHHHxx | 0 - 0 | HHHHxx | 2 - 0 | HHHHxx | 4 - 0 | HHHHxx | 6 - 0 | HHHHxx | 8 - 0 | OOOOxx | 0 - 0 | OOOOxx | 2 - 0 | OOOOxx | 4 - 0 | OOOOxx | 6 - 0 | OOOOxx | 8 - 0 | VVVVxx | 0 - 0 | VVVVxx | 2 - 0 | VVVVxx | 4 - 0 | VVVVxx | 6 - 0 | VVVVxx | 8 - 1 | AAAAxx | 1 - 1 | AAAAxx | 3 - 1 | AAAAxx | 5 - 1 | AAAAxx | 7 - 1 | AAAAxx | 9 - 1 | HHHHxx | 1 - 1 | HHHHxx | 3 - 1 | HHHHxx | 5 - 1 | HHHHxx | 7 - 1 | HHHHxx | 9 - 1 | OOOOxx | 1 - 1 | OOOOxx | 3 - 1 | OOOOxx | 5 - 1 | OOOOxx | 7 - 1 | OOOOxx | 9 - 1 | VVVVxx | 1 - 1 | VVVVxx | 3 - 1 | VVVVxx | 5 - 1 | VVVVxx | 7 - 1 | VVVVxx | 9 -(40 rows) - --- --- awk '{print $2;}' person.data | --- awk '{if(NF!=1){print $2;}else{print;}}' - emp.data | --- awk '{if(NF!=1){print $2;}else{print;}}' - student.data | --- awk 'BEGIN{FS=" ";}{if(NF!=1){print $5;}else{print;}}' - stud_emp.data | --- sort -n -r | uniq --- -SELECT DISTINCT p.age FROM person* p ORDER BY age using >; - age ------ - 98 - 88 - 78 - 68 - 60 - 58 - 50 - 48 - 40 - 38 - 34 - 30 - 28 - 25 - 24 - 23 - 20 - 19 - 18 - 8 -(20 rows) - --- --- Check mentioning same column more than once --- -EXPLAIN (VERBOSE, COSTS OFF) -SELECT count(*) FROM - (SELECT DISTINCT two, four, two FROM tenk1) ss; - QUERY PLAN --------------------------------------------------------- - Aggregate - Output: count(*) - -> HashAggregate - Output: tenk1.two, tenk1.four, tenk1.two - Group Key: tenk1.two, tenk1.four - -> Seq Scan on public.tenk1 - Output: tenk1.two, tenk1.four, tenk1.two -(7 rows) - -SELECT count(*) FROM - (SELECT DISTINCT two, four, two FROM tenk1) ss; - count -------- - 4 -(1 row) - --- --- Compare results between plans using sorting and plans using hash --- aggregation. Force spilling in both cases by setting work_mem low. --- -SET work_mem='64kB'; --- Produce results with sorting. -SET enable_hashagg=FALSE; -SET jit_above_cost=0; -EXPLAIN (costs off) -SELECT DISTINCT g%1000 FROM generate_series(0,9999) g; - QUERY PLAN ------------------------------------------------- - Unique - -> Sort - Sort Key: ((g % 1000)) - -> Function Scan on generate_series g -(4 rows) - -CREATE TABLE distinct_group_1 AS -SELECT DISTINCT g%1000 FROM generate_series(0,9999) g; -SET jit_above_cost TO DEFAULT; -CREATE TABLE distinct_group_2 AS -SELECT DISTINCT (g%1000)::text FROM generate_series(0,9999) g; -SET enable_seqscan = 0; --- Check to see we get an incremental sort plan -EXPLAIN (costs off) -SELECT DISTINCT hundred, two FROM tenk1; - QUERY PLAN ------------------------------------------------------ - Unique - -> Incremental Sort - Sort Key: hundred, two - Presorted Key: hundred - -> Index Scan using tenk1_hundred on tenk1 -(5 rows) - -RESET enable_seqscan; -SET enable_hashagg=TRUE; --- Produce results with hash aggregation. -SET enable_sort=FALSE; -SET jit_above_cost=0; -EXPLAIN (costs off) -SELECT DISTINCT g%1000 FROM generate_series(0,9999) g; - QUERY PLAN ------------------------------------------- - HashAggregate - Group Key: (g % 1000) - -> Function Scan on generate_series g -(3 rows) - -CREATE TABLE distinct_hash_1 AS -SELECT DISTINCT g%1000 FROM generate_series(0,9999) g; -SET jit_above_cost TO DEFAULT; -CREATE TABLE distinct_hash_2 AS -SELECT DISTINCT (g%1000)::text FROM generate_series(0,9999) g; -SET enable_sort=TRUE; -SET work_mem TO DEFAULT; --- Compare results -(SELECT * FROM distinct_hash_1 EXCEPT SELECT * FROM distinct_group_1) - UNION ALL -(SELECT * FROM distinct_group_1 EXCEPT SELECT * FROM distinct_hash_1); - ?column? ----------- -(0 rows) - -(SELECT * FROM distinct_hash_1 EXCEPT SELECT * FROM distinct_group_1) - UNION ALL -(SELECT * FROM distinct_group_1 EXCEPT SELECT * FROM distinct_hash_1); - ?column? ----------- -(0 rows) - -DROP TABLE distinct_hash_1; -DROP TABLE distinct_hash_2; -DROP TABLE distinct_group_1; -DROP TABLE distinct_group_2; --- Test parallel DISTINCT -SET parallel_tuple_cost=0; -SET parallel_setup_cost=0; -SET min_parallel_table_scan_size=0; -SET max_parallel_workers_per_gather=2; --- Ensure we get a parallel plan -EXPLAIN (costs off) -SELECT DISTINCT four FROM tenk1; - QUERY PLAN ----------------------------------------------------- - Unique - -> Gather Merge - Workers Planned: 2 - -> Sort - Sort Key: four - -> HashAggregate - Group Key: four - -> Parallel Seq Scan on tenk1 -(8 rows) - --- Ensure the parallel plan produces the correct results -SELECT DISTINCT four FROM tenk1; - four ------- - 0 - 1 - 2 - 3 -(4 rows) - -CREATE OR REPLACE FUNCTION distinct_func(a INT) RETURNS INT AS $$ - BEGIN - RETURN a; - END; -$$ LANGUAGE plpgsql PARALLEL UNSAFE; --- Ensure we don't do parallel distinct with a parallel unsafe function -EXPLAIN (COSTS OFF) -SELECT DISTINCT distinct_func(1) FROM tenk1; - QUERY PLAN ----------------------------------------------------------- - Unique - -> Sort - Sort Key: (distinct_func(1)) - -> Index Only Scan using tenk1_hundred on tenk1 -(4 rows) - --- make the function parallel safe -CREATE OR REPLACE FUNCTION distinct_func(a INT) RETURNS INT AS $$ - BEGIN - RETURN a; - END; -$$ LANGUAGE plpgsql PARALLEL SAFE; --- Ensure we do parallel distinct now that the function is parallel safe -EXPLAIN (COSTS OFF) -SELECT DISTINCT distinct_func(1) FROM tenk1; - QUERY PLAN ----------------------------------------------------- - Unique - -> Gather Merge - Workers Planned: 2 - -> Unique - -> Sort - Sort Key: (distinct_func(1)) - -> Parallel Seq Scan on tenk1 -(7 rows) - -RESET max_parallel_workers_per_gather; -RESET min_parallel_table_scan_size; -RESET parallel_setup_cost; -RESET parallel_tuple_cost; --- --- Test the planner's ability to use a LIMIT 1 instead of a Unique node when --- all of the distinct_pathkeys have been marked as redundant --- --- Ensure we get a plan with a Limit 1 -EXPLAIN (COSTS OFF) -SELECT DISTINCT four FROM tenk1 WHERE four = 0; - QUERY PLAN ----------------------------- - Limit - -> Seq Scan on tenk1 - Filter: (four = 0) -(3 rows) - --- Ensure the above gives us the correct result -SELECT DISTINCT four FROM tenk1 WHERE four = 0; - four ------- - 0 -(1 row) - --- Ensure we get a plan with a Limit 1 -EXPLAIN (COSTS OFF) -SELECT DISTINCT four FROM tenk1 WHERE four = 0 AND two <> 0; - QUERY PLAN ---------------------------------------------- - Limit - -> Seq Scan on tenk1 - Filter: ((two <> 0) AND (four = 0)) -(3 rows) - --- Ensure no rows are returned -SELECT DISTINCT four FROM tenk1 WHERE four = 0 AND two <> 0; - four ------- -(0 rows) - --- Ensure we get a plan with a Limit 1 when the SELECT list contains constants -EXPLAIN (COSTS OFF) -SELECT DISTINCT four,1,2,3 FROM tenk1 WHERE four = 0; - QUERY PLAN ----------------------------- - Limit - -> Seq Scan on tenk1 - Filter: (four = 0) -(3 rows) - --- Ensure we only get 1 row -SELECT DISTINCT four,1,2,3 FROM tenk1 WHERE four = 0; - four | ?column? | ?column? | ?column? -------+----------+----------+---------- - 0 | 1 | 2 | 3 -(1 row) - -SET parallel_setup_cost=0; -SET min_parallel_table_scan_size=0; -SET max_parallel_workers_per_gather=2; --- Ensure we get a plan with a Limit 1 in both partial distinct and final --- distinct -EXPLAIN (COSTS OFF) -SELECT DISTINCT four FROM tenk1 WHERE four = 10; - QUERY PLAN ----------------------------------------------- - Limit - -> Gather - Workers Planned: 2 - -> Limit - -> Parallel Seq Scan on tenk1 - Filter: (four = 10) -(6 rows) - -RESET max_parallel_workers_per_gather; -RESET min_parallel_table_scan_size; -RESET parallel_setup_cost; --- --- Also, some tests of IS DISTINCT FROM, which doesn't quite deserve its --- very own regression file. --- -CREATE TEMP TABLE disttable (f1 integer); -INSERT INTO DISTTABLE VALUES(1); -INSERT INTO DISTTABLE VALUES(2); -INSERT INTO DISTTABLE VALUES(3); -INSERT INTO DISTTABLE VALUES(NULL); --- basic cases -SELECT f1, f1 IS DISTINCT FROM 2 as "not 2" FROM disttable; - f1 | not 2 -----+------- - 1 | t - 2 | f - 3 | t - | t -(4 rows) - -SELECT f1, f1 IS DISTINCT FROM NULL as "not null" FROM disttable; - f1 | not null -----+---------- - 1 | t - 2 | t - 3 | t - | f -(4 rows) - -SELECT f1, f1 IS DISTINCT FROM f1 as "false" FROM disttable; - f1 | false -----+------- - 1 | f - 2 | f - 3 | f - | f -(4 rows) - -SELECT f1, f1 IS DISTINCT FROM f1+1 as "not null" FROM disttable; - f1 | not null -----+---------- - 1 | t - 2 | t - 3 | t - | f -(4 rows) - --- check that optimizer constant-folds it properly -SELECT 1 IS DISTINCT FROM 2 as "yes"; - yes ------ - t -(1 row) - -SELECT 2 IS DISTINCT FROM 2 as "no"; - no ----- - f -(1 row) - -SELECT 2 IS DISTINCT FROM null as "yes"; - yes ------ - t -(1 row) - -SELECT null IS DISTINCT FROM null as "no"; - no ----- - f -(1 row) - --- negated form -SELECT 1 IS NOT DISTINCT FROM 2 as "no"; - no ----- - f -(1 row) - -SELECT 2 IS NOT DISTINCT FROM 2 as "yes"; - yes ------ - t -(1 row) - -SELECT 2 IS NOT DISTINCT FROM null as "no"; - no ----- - f -(1 row) - -SELECT null IS NOT DISTINCT FROM null as "yes"; - yes ------ - t -(1 row) - +psql: error: connection to server on socket "/tmp/pg_regress-03Az7d/.s.PGSQL.40051" failed: FATAL: the database system is not yet accepting connections +DETAIL: Consistent recovery state has not been yet reached. diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/select_distinct_on.out /tmp/cirrus-ci-build/build/testrun/regress/regress/results/select_distinct_on.out --- /tmp/cirrus-ci-build/src/test/regress/expected/select_distinct_on.out 2024-09-20 21:41:45.746024523 +0000 +++ /tmp/cirrus-ci-build/build/testrun/regress/regress/results/select_distinct_on.out 2024-09-20 21:46:05.493625086 +0000 @@ -1,123 +1,2 @@ --- --- SELECT_DISTINCT_ON --- -SELECT DISTINCT ON (string4) string4, two, ten - FROM onek - ORDER BY string4 using <, two using >, ten using <; - string4 | two | ten ----------+-----+----- - AAAAxx | 1 | 1 - HHHHxx | 1 | 1 - OOOOxx | 1 | 1 - VVVVxx | 1 | 1 -(4 rows) - --- this will fail due to conflict of ordering requirements -SELECT DISTINCT ON (string4, ten) string4, two, ten - FROM onek - ORDER BY string4 using <, two using <, ten using <; -ERROR: SELECT DISTINCT ON expressions must match initial ORDER BY expressions -LINE 1: SELECT DISTINCT ON (string4, ten) string4, two, ten - ^ -SELECT DISTINCT ON (string4, ten) string4, ten, two - FROM onek - ORDER BY string4 using <, ten using >, two using <; - string4 | ten | two ----------+-----+----- - AAAAxx | 9 | 1 - AAAAxx | 8 | 0 - AAAAxx | 7 | 1 - AAAAxx | 6 | 0 - AAAAxx | 5 | 1 - AAAAxx | 4 | 0 - AAAAxx | 3 | 1 - AAAAxx | 2 | 0 - AAAAxx | 1 | 1 - AAAAxx | 0 | 0 - HHHHxx | 9 | 1 - HHHHxx | 8 | 0 - HHHHxx | 7 | 1 - HHHHxx | 6 | 0 - HHHHxx | 5 | 1 - HHHHxx | 4 | 0 - HHHHxx | 3 | 1 - HHHHxx | 2 | 0 - HHHHxx | 1 | 1 - HHHHxx | 0 | 0 - OOOOxx | 9 | 1 - OOOOxx | 8 | 0 - OOOOxx | 7 | 1 - OOOOxx | 6 | 0 - OOOOxx | 5 | 1 - OOOOxx | 4 | 0 - OOOOxx | 3 | 1 - OOOOxx | 2 | 0 - OOOOxx | 1 | 1 - OOOOxx | 0 | 0 - VVVVxx | 9 | 1 - VVVVxx | 8 | 0 - VVVVxx | 7 | 1 - VVVVxx | 6 | 0 - VVVVxx | 5 | 1 - VVVVxx | 4 | 0 - VVVVxx | 3 | 1 - VVVVxx | 2 | 0 - VVVVxx | 1 | 1 - VVVVxx | 0 | 0 -(40 rows) - --- bug #5049: early 8.4.x chokes on volatile DISTINCT ON clauses -select distinct on (1) floor(random()) as r, f1 from int4_tbl order by 1,2; - r | f1 ----+------------- - 0 | -2147483647 -(1 row) - --- --- Test the planner's ability to use a LIMIT 1 instead of a Unique node when --- all of the distinct_pathkeys have been marked as redundant --- --- Ensure we also get a LIMIT plan with DISTINCT ON -EXPLAIN (COSTS OFF) -SELECT DISTINCT ON (four) four,two - FROM tenk1 WHERE four = 0 ORDER BY 1; - QUERY PLAN ----------------------------- - Limit - -> Seq Scan on tenk1 - Filter: (four = 0) -(3 rows) - --- and check the result of the above query is correct -SELECT DISTINCT ON (four) four,two - FROM tenk1 WHERE four = 0 ORDER BY 1; - four | two -------+----- - 0 | 0 -(1 row) - --- Ensure a Sort -> Limit is used when the ORDER BY contains additional cols -EXPLAIN (COSTS OFF) -SELECT DISTINCT ON (four) four,two - FROM tenk1 WHERE four = 0 ORDER BY 1,2; - QUERY PLAN ----------------------------------- - Limit - -> Sort - Sort Key: two - -> Seq Scan on tenk1 - Filter: (four = 0) -(5 rows) - --- Same again but use a column that is indexed so that we get an index scan --- then a limit -EXPLAIN (COSTS OFF) -SELECT DISTINCT ON (four) four,hundred - FROM tenk1 WHERE four = 0 ORDER BY 1,2; - QUERY PLAN ------------------------------------------------ - Limit - -> Index Scan using tenk1_hundred on tenk1 - Filter: (four = 0) -(3 rows) - +psql: error: connection to server on socket "/tmp/pg_regress-03Az7d/.s.PGSQL.40051" failed: FATAL: the database system is not yet accepting connections +DETAIL: Consistent recovery state has not been yet reached. diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/select_implicit.out /tmp/cirrus-ci-build/build/testrun/regress/regress/results/select_implicit.out --- /tmp/cirrus-ci-build/src/test/regress/expected/select_implicit.out 2024-09-20 21:41:45.746024523 +0000 +++ /tmp/cirrus-ci-build/build/testrun/regress/regress/results/select_implicit.out 2024-09-20 21:46:05.501625074 +0000 @@ -1,338 +1,2 @@ --- --- SELECT_IMPLICIT --- Test cases for queries with ordering terms missing from the target list. --- This used to be called "junkfilter.sql". --- The parser uses the term "resjunk" to handle these cases. --- - thomas 1998-07-09 --- --- load test data -CREATE TABLE test_missing_target (a int, b int, c char(8), d char); -INSERT INTO test_missing_target VALUES (0, 1, 'XXXX', 'A'); -INSERT INTO test_missing_target VALUES (1, 2, 'ABAB', 'b'); -INSERT INTO test_missing_target VALUES (2, 2, 'ABAB', 'c'); -INSERT INTO test_missing_target VALUES (3, 3, 'BBBB', 'D'); -INSERT INTO test_missing_target VALUES (4, 3, 'BBBB', 'e'); -INSERT INTO test_missing_target VALUES (5, 3, 'bbbb', 'F'); -INSERT INTO test_missing_target VALUES (6, 4, 'cccc', 'g'); -INSERT INTO test_missing_target VALUES (7, 4, 'cccc', 'h'); -INSERT INTO test_missing_target VALUES (8, 4, 'CCCC', 'I'); -INSERT INTO test_missing_target VALUES (9, 4, 'CCCC', 'j'); --- w/ existing GROUP BY target -SELECT c, count(*) FROM test_missing_target GROUP BY test_missing_target.c ORDER BY c; - c | count -----------+------- - ABAB | 2 - BBBB | 2 - CCCC | 2 - XXXX | 1 - bbbb | 1 - cccc | 2 -(6 rows) - --- w/o existing GROUP BY target using a relation name in GROUP BY clause -SELECT count(*) FROM test_missing_target GROUP BY test_missing_target.c ORDER BY c; - count -------- - 2 - 2 - 2 - 1 - 1 - 2 -(6 rows) - --- w/o existing GROUP BY target and w/o existing a different ORDER BY target --- failure expected -SELECT count(*) FROM test_missing_target GROUP BY a ORDER BY b; -ERROR: column "test_missing_target.b" must appear in the GROUP BY clause or be used in an aggregate function -LINE 1: ...ECT count(*) FROM test_missing_target GROUP BY a ORDER BY b; - ^ --- w/o existing GROUP BY target and w/o existing same ORDER BY target -SELECT count(*) FROM test_missing_target GROUP BY b ORDER BY b; - count -------- - 1 - 2 - 3 - 4 -(4 rows) - --- w/ existing GROUP BY target using a relation name in target -SELECT test_missing_target.b, count(*) - FROM test_missing_target GROUP BY b ORDER BY b; - b | count ----+------- - 1 | 1 - 2 | 2 - 3 | 3 - 4 | 4 -(4 rows) - --- w/o existing GROUP BY target -SELECT c FROM test_missing_target ORDER BY a; - c ----------- - XXXX - ABAB - ABAB - BBBB - BBBB - bbbb - cccc - cccc - CCCC - CCCC -(10 rows) - --- w/o existing ORDER BY target -SELECT count(*) FROM test_missing_target GROUP BY b ORDER BY b desc; - count -------- - 4 - 3 - 2 - 1 -(4 rows) - --- group using reference number -SELECT count(*) FROM test_missing_target ORDER BY 1 desc; - count -------- - 10 -(1 row) - --- order using reference number -SELECT c, count(*) FROM test_missing_target GROUP BY 1 ORDER BY 1; - c | count -----------+------- - ABAB | 2 - BBBB | 2 - CCCC | 2 - XXXX | 1 - bbbb | 1 - cccc | 2 -(6 rows) - --- group using reference number out of range --- failure expected -SELECT c, count(*) FROM test_missing_target GROUP BY 3; -ERROR: GROUP BY position 3 is not in select list -LINE 1: SELECT c, count(*) FROM test_missing_target GROUP BY 3; - ^ --- group w/o existing GROUP BY and ORDER BY target under ambiguous condition --- failure expected -SELECT count(*) FROM test_missing_target x, test_missing_target y - WHERE x.a = y.a - GROUP BY b ORDER BY b; -ERROR: column reference "b" is ambiguous -LINE 3: GROUP BY b ORDER BY b; - ^ --- order w/ target under ambiguous condition --- failure NOT expected -SELECT a, a FROM test_missing_target - ORDER BY a; - a | a ----+--- - 0 | 0 - 1 | 1 - 2 | 2 - 3 | 3 - 4 | 4 - 5 | 5 - 6 | 6 - 7 | 7 - 8 | 8 - 9 | 9 -(10 rows) - --- order expression w/ target under ambiguous condition --- failure NOT expected -SELECT a/2, a/2 FROM test_missing_target - ORDER BY a/2; - ?column? | ?column? -----------+---------- - 0 | 0 - 0 | 0 - 1 | 1 - 1 | 1 - 2 | 2 - 2 | 2 - 3 | 3 - 3 | 3 - 4 | 4 - 4 | 4 -(10 rows) - --- group expression w/ target under ambiguous condition --- failure NOT expected -SELECT a/2, a/2 FROM test_missing_target - GROUP BY a/2 ORDER BY a/2; - ?column? | ?column? -----------+---------- - 0 | 0 - 1 | 1 - 2 | 2 - 3 | 3 - 4 | 4 -(5 rows) - --- group w/ existing GROUP BY target under ambiguous condition -SELECT x.b, count(*) FROM test_missing_target x, test_missing_target y - WHERE x.a = y.a - GROUP BY x.b ORDER BY x.b; - b | count ----+------- - 1 | 1 - 2 | 2 - 3 | 3 - 4 | 4 -(4 rows) - --- group w/o existing GROUP BY target under ambiguous condition -SELECT count(*) FROM test_missing_target x, test_missing_target y - WHERE x.a = y.a - GROUP BY x.b ORDER BY x.b; - count -------- - 1 - 2 - 3 - 4 -(4 rows) - --- group w/o existing GROUP BY target under ambiguous condition --- into a table -CREATE TABLE test_missing_target2 AS -SELECT count(*) -FROM test_missing_target x, test_missing_target y - WHERE x.a = y.a - GROUP BY x.b ORDER BY x.b; -SELECT * FROM test_missing_target2; - count -------- - 1 - 2 - 3 - 4 -(4 rows) - --- Functions and expressions --- w/ existing GROUP BY target -SELECT a%2, count(b) FROM test_missing_target -GROUP BY test_missing_target.a%2 -ORDER BY test_missing_target.a%2; - ?column? | count -----------+------- - 0 | 5 - 1 | 5 -(2 rows) - --- w/o existing GROUP BY target using a relation name in GROUP BY clause -SELECT count(c) FROM test_missing_target -GROUP BY lower(test_missing_target.c) -ORDER BY lower(test_missing_target.c); - count -------- - 2 - 3 - 4 - 1 -(4 rows) - --- w/o existing GROUP BY target and w/o existing a different ORDER BY target --- failure expected -SELECT count(a) FROM test_missing_target GROUP BY a ORDER BY b; -ERROR: column "test_missing_target.b" must appear in the GROUP BY clause or be used in an aggregate function -LINE 1: ...ECT count(a) FROM test_missing_target GROUP BY a ORDER BY b; - ^ --- w/o existing GROUP BY target and w/o existing same ORDER BY target -SELECT count(b) FROM test_missing_target GROUP BY b/2 ORDER BY b/2; - count -------- - 1 - 5 - 4 -(3 rows) - --- w/ existing GROUP BY target using a relation name in target -SELECT lower(test_missing_target.c), count(c) - FROM test_missing_target GROUP BY lower(c) ORDER BY lower(c); - lower | count --------+------- - abab | 2 - bbbb | 3 - cccc | 4 - xxxx | 1 -(4 rows) - --- w/o existing GROUP BY target -SELECT a FROM test_missing_target ORDER BY upper(d); - a ---- - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 -(10 rows) - --- w/o existing ORDER BY target -SELECT count(b) FROM test_missing_target - GROUP BY (b + 1) / 2 ORDER BY (b + 1) / 2 desc; - count -------- - 7 - 3 -(2 rows) - --- group w/o existing GROUP BY and ORDER BY target under ambiguous condition --- failure expected -SELECT count(x.a) FROM test_missing_target x, test_missing_target y - WHERE x.a = y.a - GROUP BY b/2 ORDER BY b/2; -ERROR: column reference "b" is ambiguous -LINE 3: GROUP BY b/2 ORDER BY b/2; - ^ --- group w/ existing GROUP BY target under ambiguous condition -SELECT x.b/2, count(x.b) FROM test_missing_target x, test_missing_target y - WHERE x.a = y.a - GROUP BY x.b/2 ORDER BY x.b/2; - ?column? | count -----------+------- - 0 | 1 - 1 | 5 - 2 | 4 -(3 rows) - --- group w/o existing GROUP BY target under ambiguous condition --- failure expected due to ambiguous b in count(b) -SELECT count(b) FROM test_missing_target x, test_missing_target y - WHERE x.a = y.a - GROUP BY x.b/2; -ERROR: column reference "b" is ambiguous -LINE 1: SELECT count(b) FROM test_missing_target x, test_missing_tar... - ^ --- group w/o existing GROUP BY target under ambiguous condition --- into a table -CREATE TABLE test_missing_target3 AS -SELECT count(x.b) -FROM test_missing_target x, test_missing_target y - WHERE x.a = y.a - GROUP BY x.b/2 ORDER BY x.b/2; -SELECT * FROM test_missing_target3; - count -------- - 1 - 5 - 4 -(3 rows) - --- Cleanup -DROP TABLE test_missing_target; -DROP TABLE test_missing_target2; -DROP TABLE test_missing_target3; +psql: error: connection to server on socket "/tmp/pg_regress-03Az7d/.s.PGSQL.40051" failed: FATAL: the database system is not yet accepting connections +DETAIL: Consistent recovery state has not been yet reached. diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/select_having.out /tmp/cirrus-ci-build/build/testrun/regress/regress/results/select_having.out --- /tmp/cirrus-ci-build/src/test/regress/expected/select_having.out 2024-09-20 21:41:45.746024523 +0000 +++ /tmp/cirrus-ci-build/build/testrun/regress/regress/results/select_having.out 2024-09-20 21:46:05.449625147 +0000 @@ -1,93 +1,2 @@ --- --- SELECT_HAVING --- --- load test data -CREATE TABLE test_having (a int, b int, c char(8), d char); -INSERT INTO test_having VALUES (0, 1, 'XXXX', 'A'); -INSERT INTO test_having VALUES (1, 2, 'AAAA', 'b'); -INSERT INTO test_having VALUES (2, 2, 'AAAA', 'c'); -INSERT INTO test_having VALUES (3, 3, 'BBBB', 'D'); -INSERT INTO test_having VALUES (4, 3, 'BBBB', 'e'); -INSERT INTO test_having VALUES (5, 3, 'bbbb', 'F'); -INSERT INTO test_having VALUES (6, 4, 'cccc', 'g'); -INSERT INTO test_having VALUES (7, 4, 'cccc', 'h'); -INSERT INTO test_having VALUES (8, 4, 'CCCC', 'I'); -INSERT INTO test_having VALUES (9, 4, 'CCCC', 'j'); -SELECT b, c FROM test_having - GROUP BY b, c HAVING count(*) = 1 ORDER BY b, c; - b | c ----+---------- - 1 | XXXX - 3 | bbbb -(2 rows) - --- HAVING is effectively equivalent to WHERE in this case -SELECT b, c FROM test_having - GROUP BY b, c HAVING b = 3 ORDER BY b, c; - b | c ----+---------- - 3 | BBBB - 3 | bbbb -(2 rows) - -SELECT lower(c), count(c) FROM test_having - GROUP BY lower(c) HAVING count(*) > 2 OR min(a) = max(a) - ORDER BY lower(c); - lower | count --------+------- - bbbb | 3 - cccc | 4 - xxxx | 1 -(3 rows) - -SELECT c, max(a) FROM test_having - GROUP BY c HAVING count(*) > 2 OR min(a) = max(a) - ORDER BY c; - c | max -----------+----- - XXXX | 0 - bbbb | 5 -(2 rows) - --- test degenerate cases involving HAVING without GROUP BY --- Per SQL spec, these should generate 0 or 1 row, even without aggregates -SELECT min(a), max(a) FROM test_having HAVING min(a) = max(a); - min | max ------+----- -(0 rows) - -SELECT min(a), max(a) FROM test_having HAVING min(a) < max(a); - min | max ------+----- - 0 | 9 -(1 row) - --- errors: ungrouped column references -SELECT a FROM test_having HAVING min(a) < max(a); -ERROR: column "test_having.a" must appear in the GROUP BY clause or be used in an aggregate function -LINE 1: SELECT a FROM test_having HAVING min(a) < max(a); - ^ -SELECT 1 AS one FROM test_having HAVING a > 1; -ERROR: column "test_having.a" must appear in the GROUP BY clause or be used in an aggregate function -LINE 1: SELECT 1 AS one FROM test_having HAVING a > 1; - ^ --- the really degenerate case: need not scan table at all -SELECT 1 AS one FROM test_having HAVING 1 > 2; - one ------ -(0 rows) - -SELECT 1 AS one FROM test_having HAVING 1 < 2; - one ------ - 1 -(1 row) - --- and just to prove that we aren't scanning the table: -SELECT 1 AS one FROM test_having WHERE 1/a = 1 HAVING 1 < 2; - one ------ - 1 -(1 row) - -DROP TABLE test_having; +psql: error: connection to server on socket "/tmp/pg_regress-03Az7d/.s.PGSQL.40051" failed: FATAL: the database system is not yet accepting connections +DETAIL: Consistent recovery state has not been yet reached. diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/subselect.out /tmp/cirrus-ci-build/build/testrun/regress/regress/results/subselect.out --- /tmp/cirrus-ci-build/src/test/regress/expected/subselect.out 2024-09-20 21:41:45.754024524 +0000 +++ /tmp/cirrus-ci-build/build/testrun/regress/regress/results/subselect.out 2024-09-20 21:46:05.485625097 +0000 @@ -1,2136 +1,2 @@ --- --- SUBSELECT --- -SELECT 1 AS one WHERE 1 IN (SELECT 1); - one ------ - 1 -(1 row) - -SELECT 1 AS zero WHERE 1 NOT IN (SELECT 1); - zero ------- -(0 rows) - -SELECT 1 AS zero WHERE 1 IN (SELECT 2); - zero ------- -(0 rows) - --- Check grammar's handling of extra parens in assorted contexts -SELECT * FROM (SELECT 1 AS x) ss; - x ---- - 1 -(1 row) - -SELECT * FROM ((SELECT 1 AS x)) ss; - x ---- - 1 -(1 row) - -SELECT * FROM ((SELECT 1 AS x)), ((SELECT * FROM ((SELECT 2 AS y)))); - x | y ----+--- - 1 | 2 -(1 row) - -(SELECT 2) UNION SELECT 2; - ?column? ----------- - 2 -(1 row) - -((SELECT 2)) UNION SELECT 2; - ?column? ----------- - 2 -(1 row) - -SELECT ((SELECT 2) UNION SELECT 2); - ?column? ----------- - 2 -(1 row) - -SELECT (((SELECT 2)) UNION SELECT 2); - ?column? ----------- - 2 -(1 row) - -SELECT (SELECT ARRAY[1,2,3])[1]; - array -------- - 1 -(1 row) - -SELECT ((SELECT ARRAY[1,2,3]))[2]; - array -------- - 2 -(1 row) - -SELECT (((SELECT ARRAY[1,2,3])))[3]; - array -------- - 3 -(1 row) - --- Set up some simple test tables -CREATE TABLE SUBSELECT_TBL ( - f1 integer, - f2 integer, - f3 float -); -INSERT INTO SUBSELECT_TBL VALUES (1, 2, 3); -INSERT INTO SUBSELECT_TBL VALUES (2, 3, 4); -INSERT INTO SUBSELECT_TBL VALUES (3, 4, 5); -INSERT INTO SUBSELECT_TBL VALUES (1, 1, 1); -INSERT INTO SUBSELECT_TBL VALUES (2, 2, 2); -INSERT INTO SUBSELECT_TBL VALUES (3, 3, 3); -INSERT INTO SUBSELECT_TBL VALUES (6, 7, 8); -INSERT INTO SUBSELECT_TBL VALUES (8, 9, NULL); -SELECT * FROM SUBSELECT_TBL; - f1 | f2 | f3 -----+----+---- - 1 | 2 | 3 - 2 | 3 | 4 - 3 | 4 | 5 - 1 | 1 | 1 - 2 | 2 | 2 - 3 | 3 | 3 - 6 | 7 | 8 - 8 | 9 | -(8 rows) - --- Uncorrelated subselects -SELECT f1 AS "Constant Select" FROM SUBSELECT_TBL - WHERE f1 IN (SELECT 1); - Constant Select ------------------ - 1 - 1 -(2 rows) - -SELECT f1 AS "Uncorrelated Field" FROM SUBSELECT_TBL - WHERE f1 IN (SELECT f2 FROM SUBSELECT_TBL); - Uncorrelated Field --------------------- - 1 - 2 - 3 - 1 - 2 - 3 -(6 rows) - -SELECT f1 AS "Uncorrelated Field" FROM SUBSELECT_TBL - WHERE f1 IN (SELECT f2 FROM SUBSELECT_TBL WHERE - f2 IN (SELECT f1 FROM SUBSELECT_TBL)); - Uncorrelated Field --------------------- - 1 - 2 - 3 - 1 - 2 - 3 -(6 rows) - -SELECT f1, f2 - FROM SUBSELECT_TBL - WHERE (f1, f2) NOT IN (SELECT f2, CAST(f3 AS int4) FROM SUBSELECT_TBL - WHERE f3 IS NOT NULL); - f1 | f2 -----+---- - 1 | 2 - 6 | 7 - 8 | 9 -(3 rows) - --- Correlated subselects -SELECT f1 AS "Correlated Field", f2 AS "Second Field" - FROM SUBSELECT_TBL upper - WHERE f1 IN (SELECT f2 FROM SUBSELECT_TBL WHERE f1 = upper.f1); - Correlated Field | Second Field -------------------+-------------- - 1 | 2 - 2 | 3 - 3 | 4 - 1 | 1 - 2 | 2 - 3 | 3 -(6 rows) - -SELECT f1 AS "Correlated Field", f3 AS "Second Field" - FROM SUBSELECT_TBL upper - WHERE f1 IN - (SELECT f2 FROM SUBSELECT_TBL WHERE CAST(upper.f2 AS float) = f3); - Correlated Field | Second Field -------------------+-------------- - 2 | 4 - 3 | 5 - 1 | 1 - 2 | 2 - 3 | 3 -(5 rows) - -SELECT f1 AS "Correlated Field", f3 AS "Second Field" - FROM SUBSELECT_TBL upper - WHERE f3 IN (SELECT upper.f1 + f2 FROM SUBSELECT_TBL - WHERE f2 = CAST(f3 AS integer)); - Correlated Field | Second Field -------------------+-------------- - 1 | 3 - 2 | 4 - 3 | 5 - 6 | 8 -(4 rows) - -SELECT f1 AS "Correlated Field" - FROM SUBSELECT_TBL - WHERE (f1, f2) IN (SELECT f2, CAST(f3 AS int4) FROM SUBSELECT_TBL - WHERE f3 IS NOT NULL); - Correlated Field ------------------- - 2 - 3 - 1 - 2 - 3 -(5 rows) - --- Check ROWCOMPARE cases, both correlated and not -EXPLAIN (VERBOSE, COSTS OFF) -SELECT ROW(1, 2) = (SELECT f1, f2) AS eq FROM SUBSELECT_TBL; - QUERY PLAN ------------------------------------------------------------------ - Seq Scan on public.subselect_tbl - Output: (((1 = (SubPlan 1).col1) AND (2 = (SubPlan 1).col2))) - SubPlan 1 - -> Result - Output: subselect_tbl.f1, subselect_tbl.f2 -(5 rows) - -SELECT ROW(1, 2) = (SELECT f1, f2) AS eq FROM SUBSELECT_TBL; - eq ----- - t - f - f - f - f - f - f - f -(8 rows) - -EXPLAIN (VERBOSE, COSTS OFF) -SELECT ROW(1, 2) = (SELECT 3, 4) AS eq FROM SUBSELECT_TBL; - QUERY PLAN ------------------------------------------------------------------ - Seq Scan on public.subselect_tbl - Output: ((1 = (InitPlan 1).col1) AND (2 = (InitPlan 1).col2)) - InitPlan 1 - -> Result - Output: 3, 4 -(5 rows) - -SELECT ROW(1, 2) = (SELECT 3, 4) AS eq FROM SUBSELECT_TBL; - eq ----- - f - f - f - f - f - f - f - f -(8 rows) - -SELECT ROW(1, 2) = (SELECT f1, f2 FROM SUBSELECT_TBL); -- error -ERROR: more than one row returned by a subquery used as an expression --- Subselects without aliases -SELECT count FROM (SELECT COUNT(DISTINCT name) FROM road); - count -------- - 2911 -(1 row) - -SELECT COUNT(*) FROM (SELECT DISTINCT name FROM road); - count -------- - 2911 -(1 row) - -SELECT * FROM (SELECT * FROM int4_tbl), (VALUES (123456)) WHERE f1 = column1; - f1 | column1 ---------+--------- - 123456 | 123456 -(1 row) - -CREATE VIEW view_unnamed_ss AS -SELECT * FROM (SELECT * FROM (SELECT abs(f1) AS a1 FROM int4_tbl)), - (SELECT * FROM int8_tbl) - WHERE a1 < 10 AND q1 > a1 ORDER BY q1, q2; -SELECT * FROM view_unnamed_ss; - a1 | q1 | q2 -----+------------------+------------------- - 0 | 123 | 456 - 0 | 123 | 4567890123456789 - 0 | 4567890123456789 | -4567890123456789 - 0 | 4567890123456789 | 123 - 0 | 4567890123456789 | 4567890123456789 -(5 rows) - -\sv view_unnamed_ss -CREATE OR REPLACE VIEW public.view_unnamed_ss AS - SELECT unnamed_subquery.a1, - unnamed_subquery_1.q1, - unnamed_subquery_1.q2 - FROM ( SELECT unnamed_subquery_2.a1 - FROM ( SELECT abs(int4_tbl.f1) AS a1 - FROM int4_tbl) unnamed_subquery_2) unnamed_subquery, - ( SELECT int8_tbl.q1, - int8_tbl.q2 - FROM int8_tbl) unnamed_subquery_1 - WHERE unnamed_subquery.a1 < 10 AND unnamed_subquery_1.q1 > unnamed_subquery.a1 - ORDER BY unnamed_subquery_1.q1, unnamed_subquery_1.q2 -DROP VIEW view_unnamed_ss; --- Test matching of locking clause to correct alias -CREATE VIEW view_unnamed_ss_locking AS -SELECT * FROM (SELECT * FROM int4_tbl), int8_tbl AS unnamed_subquery - WHERE f1 = q1 - FOR UPDATE OF unnamed_subquery; -\sv view_unnamed_ss_locking -CREATE OR REPLACE VIEW public.view_unnamed_ss_locking AS - SELECT unnamed_subquery.f1, - unnamed_subquery_1.q1, - unnamed_subquery_1.q2 - FROM ( SELECT int4_tbl.f1 - FROM int4_tbl) unnamed_subquery, - int8_tbl unnamed_subquery_1 - WHERE unnamed_subquery.f1 = unnamed_subquery_1.q1 - FOR UPDATE OF unnamed_subquery_1 -DROP VIEW view_unnamed_ss_locking; --- --- Use some existing tables in the regression test --- -SELECT ss.f1 AS "Correlated Field", ss.f3 AS "Second Field" - FROM SUBSELECT_TBL ss - WHERE f1 NOT IN (SELECT f1+1 FROM INT4_TBL - WHERE f1 != ss.f1 AND f1 < 2147483647); - Correlated Field | Second Field -------------------+-------------- - 2 | 4 - 3 | 5 - 2 | 2 - 3 | 3 - 6 | 8 - 8 | -(6 rows) - -select q1, float8(count(*)) / (select count(*) from int8_tbl) -from int8_tbl group by q1 order by q1; - q1 | ?column? -------------------+---------- - 123 | 0.4 - 4567890123456789 | 0.6 -(2 rows) - --- Unspecified-type literals in output columns should resolve as text -SELECT *, pg_typeof(f1) FROM - (SELECT 'foo' AS f1 FROM generate_series(1,3)) ss ORDER BY 1; - f1 | pg_typeof ------+----------- - foo | text - foo | text - foo | text -(3 rows) - --- ... unless there's context to suggest differently -explain (verbose, costs off) select '42' union all select '43'; - QUERY PLAN ----------------------------- - Append - -> Result - Output: '42'::text - -> Result - Output: '43'::text -(5 rows) - -explain (verbose, costs off) select '42' union all select 43; - QUERY PLAN --------------------- - Append - -> Result - Output: 42 - -> Result - Output: 43 -(5 rows) - --- check materialization of an initplan reference (bug #14524) -explain (verbose, costs off) -select 1 = all (select (select 1)); - QUERY PLAN -------------------------------------------- - Result - Output: (ALL (1 = (SubPlan 2).col1)) - SubPlan 2 - -> Materialize - Output: ((InitPlan 1).col1) - InitPlan 1 - -> Result - Output: 1 - -> Result - Output: (InitPlan 1).col1 -(10 rows) - -select 1 = all (select (select 1)); - ?column? ----------- - t -(1 row) - --- --- Check EXISTS simplification with LIMIT --- -explain (costs off) -select * from int4_tbl o where exists - (select 1 from int4_tbl i where i.f1=o.f1 limit null); - QUERY PLAN ------------------------------------- - Hash Semi Join - Hash Cond: (o.f1 = i.f1) - -> Seq Scan on int4_tbl o - -> Hash - -> Seq Scan on int4_tbl i -(5 rows) - -explain (costs off) -select * from int4_tbl o where not exists - (select 1 from int4_tbl i where i.f1=o.f1 limit 1); - QUERY PLAN ------------------------------------- - Hash Anti Join - Hash Cond: (o.f1 = i.f1) - -> Seq Scan on int4_tbl o - -> Hash - -> Seq Scan on int4_tbl i -(5 rows) - -explain (costs off) -select * from int4_tbl o where exists - (select 1 from int4_tbl i where i.f1=o.f1 limit 0); - QUERY PLAN --------------------------------------- - Seq Scan on int4_tbl o - Filter: EXISTS(SubPlan 1) - SubPlan 1 - -> Limit - -> Seq Scan on int4_tbl i - Filter: (f1 = o.f1) -(6 rows) - --- --- Test cases to catch unpleasant interactions between IN-join processing --- and subquery pullup. --- -select count(*) from - (select 1 from tenk1 a - where unique1 IN (select hundred from tenk1 b)) ss; - count -------- - 100 -(1 row) - -select count(distinct ss.ten) from - (select ten from tenk1 a - where unique1 IN (select hundred from tenk1 b)) ss; - count -------- - 10 -(1 row) - -select count(*) from - (select 1 from tenk1 a - where unique1 IN (select distinct hundred from tenk1 b)) ss; - count -------- - 100 -(1 row) - -select count(distinct ss.ten) from - (select ten from tenk1 a - where unique1 IN (select distinct hundred from tenk1 b)) ss; - count -------- - 10 -(1 row) - --- --- Test cases to check for overenthusiastic optimization of --- "IN (SELECT DISTINCT ...)" and related cases. Per example from --- Luca Pireddu and Michael Fuhr. --- -CREATE TEMP TABLE foo (id integer); -CREATE TEMP TABLE bar (id1 integer, id2 integer); -INSERT INTO foo VALUES (1); -INSERT INTO bar VALUES (1, 1); -INSERT INTO bar VALUES (2, 2); -INSERT INTO bar VALUES (3, 1); --- These cases require an extra level of distinct-ing above subquery s -SELECT * FROM foo WHERE id IN - (SELECT id2 FROM (SELECT DISTINCT id1, id2 FROM bar) AS s); - id ----- - 1 -(1 row) - -SELECT * FROM foo WHERE id IN - (SELECT id2 FROM (SELECT id1,id2 FROM bar GROUP BY id1,id2) AS s); - id ----- - 1 -(1 row) - -SELECT * FROM foo WHERE id IN - (SELECT id2 FROM (SELECT id1, id2 FROM bar UNION - SELECT id1, id2 FROM bar) AS s); - id ----- - 1 -(1 row) - --- These cases do not -SELECT * FROM foo WHERE id IN - (SELECT id2 FROM (SELECT DISTINCT ON (id2) id1, id2 FROM bar) AS s); - id ----- - 1 -(1 row) - -SELECT * FROM foo WHERE id IN - (SELECT id2 FROM (SELECT id2 FROM bar GROUP BY id2) AS s); - id ----- - 1 -(1 row) - -SELECT * FROM foo WHERE id IN - (SELECT id2 FROM (SELECT id2 FROM bar UNION - SELECT id2 FROM bar) AS s); - id ----- - 1 -(1 row) - --- --- Test case to catch problems with multiply nested sub-SELECTs not getting --- recalculated properly. Per bug report from Didier Moens. --- -CREATE TABLE orderstest ( - approver_ref integer, - po_ref integer, - ordercanceled boolean -); -INSERT INTO orderstest VALUES (1, 1, false); -INSERT INTO orderstest VALUES (66, 5, false); -INSERT INTO orderstest VALUES (66, 6, false); -INSERT INTO orderstest VALUES (66, 7, false); -INSERT INTO orderstest VALUES (66, 1, true); -INSERT INTO orderstest VALUES (66, 8, false); -INSERT INTO orderstest VALUES (66, 1, false); -INSERT INTO orderstest VALUES (77, 1, false); -INSERT INTO orderstest VALUES (1, 1, false); -INSERT INTO orderstest VALUES (66, 1, false); -INSERT INTO orderstest VALUES (1, 1, false); -CREATE VIEW orders_view AS -SELECT *, -(SELECT CASE - WHEN ord.approver_ref=1 THEN '---' ELSE 'Approved' - END) AS "Approved", -(SELECT CASE - WHEN ord.ordercanceled - THEN 'Canceled' - ELSE - (SELECT CASE - WHEN ord.po_ref=1 - THEN - (SELECT CASE - WHEN ord.approver_ref=1 - THEN '---' - ELSE 'Approved' - END) - ELSE 'PO' - END) -END) AS "Status", -(CASE - WHEN ord.ordercanceled - THEN 'Canceled' - ELSE - (CASE - WHEN ord.po_ref=1 - THEN - (CASE - WHEN ord.approver_ref=1 - THEN '---' - ELSE 'Approved' - END) - ELSE 'PO' - END) -END) AS "Status_OK" -FROM orderstest ord; -SELECT * FROM orders_view; - approver_ref | po_ref | ordercanceled | Approved | Status | Status_OK ---------------+--------+---------------+----------+----------+----------- - 1 | 1 | f | --- | --- | --- - 66 | 5 | f | Approved | PO | PO - 66 | 6 | f | Approved | PO | PO - 66 | 7 | f | Approved | PO | PO - 66 | 1 | t | Approved | Canceled | Canceled - 66 | 8 | f | Approved | PO | PO - 66 | 1 | f | Approved | Approved | Approved - 77 | 1 | f | Approved | Approved | Approved - 1 | 1 | f | --- | --- | --- - 66 | 1 | f | Approved | Approved | Approved - 1 | 1 | f | --- | --- | --- -(11 rows) - -DROP TABLE orderstest cascade; -NOTICE: drop cascades to view orders_view --- --- Test cases to catch situations where rule rewriter fails to propagate --- hasSubLinks flag correctly. Per example from Kyle Bateman. --- -create temp table parts ( - partnum text, - cost float8 -); -create temp table shipped ( - ttype char(2), - ordnum int4, - partnum text, - value float8 -); -create temp view shipped_view as - select * from shipped where ttype = 'wt'; -create rule shipped_view_insert as on insert to shipped_view do instead - insert into shipped values('wt', new.ordnum, new.partnum, new.value); -insert into parts (partnum, cost) values (1, 1234.56); -insert into shipped_view (ordnum, partnum, value) - values (0, 1, (select cost from parts where partnum = '1')); -select * from shipped_view; - ttype | ordnum | partnum | value --------+--------+---------+--------- - wt | 0 | 1 | 1234.56 -(1 row) - -create rule shipped_view_update as on update to shipped_view do instead - update shipped set partnum = new.partnum, value = new.value - where ttype = new.ttype and ordnum = new.ordnum; -update shipped_view set value = 11 - from int4_tbl a join int4_tbl b - on (a.f1 = (select f1 from int4_tbl c where c.f1=b.f1)) - where ordnum = a.f1; -select * from shipped_view; - ttype | ordnum | partnum | value --------+--------+---------+------- - wt | 0 | 1 | 11 -(1 row) - -select f1, ss1 as relabel from - (select *, (select sum(f1) from int4_tbl b where f1 >= a.f1) as ss1 - from int4_tbl a) ss; - f1 | relabel --------------+------------ - 0 | 2147607103 - 123456 | 2147607103 - -123456 | 2147483647 - 2147483647 | 2147483647 - -2147483647 | 0 -(5 rows) - --- --- Test cases involving PARAM_EXEC parameters and min/max index optimizations. --- Per bug report from David Sanchez i Gregori. --- -select * from ( - select max(unique1) from tenk1 as a - where exists (select 1 from tenk1 as b where b.thousand = a.unique2) -) ss; - max ------- - 9997 -(1 row) - -select * from ( - select min(unique1) from tenk1 as a - where not exists (select 1 from tenk1 as b where b.unique2 = 10000) -) ss; - min ------ - 0 -(1 row) - --- --- Test that an IN implemented using a UniquePath does unique-ification --- with the right semantics, as per bug #4113. (Unfortunately we have --- no simple way to ensure that this test case actually chooses that type --- of plan, but it does in releases 7.4-8.3. Note that an ordering difference --- here might mean that some other plan type is being used, rendering the test --- pointless.) --- -create temp table numeric_table (num_col numeric); -insert into numeric_table values (1), (1.000000000000000000001), (2), (3); -create temp table float_table (float_col float8); -insert into float_table values (1), (2), (3); -select * from float_table - where float_col in (select num_col from numeric_table); - float_col ------------ - 1 - 2 - 3 -(3 rows) - -select * from numeric_table - where num_col in (select float_col from float_table); - num_col -------------------------- - 1 - 1.000000000000000000001 - 2 - 3 -(4 rows) - --- --- Test case for bug #4290: bogus calculation of subplan param sets --- -create temp table ta (id int primary key, val int); -insert into ta values(1,1); -insert into ta values(2,2); -create temp table tb (id int primary key, aval int); -insert into tb values(1,1); -insert into tb values(2,1); -insert into tb values(3,2); -insert into tb values(4,2); -create temp table tc (id int primary key, aid int); -insert into tc values(1,1); -insert into tc values(2,2); -select - ( select min(tb.id) from tb - where tb.aval = (select ta.val from ta where ta.id = tc.aid) ) as min_tb_id -from tc; - min_tb_id ------------ - 1 - 3 -(2 rows) - --- --- Test case for 8.3 "failed to locate grouping columns" bug --- -create temp table t1 (f1 numeric(14,0), f2 varchar(30)); -select * from - (select distinct f1, f2, (select f2 from t1 x where x.f1 = up.f1) as fs - from t1 up) ss -group by f1,f2,fs; - f1 | f2 | fs -----+----+---- -(0 rows) - --- --- Test case for bug #5514 (mishandling of whole-row Vars in subselects) --- -create temp table table_a(id integer); -insert into table_a values (42); -create temp view view_a as select * from table_a; -select view_a from view_a; - view_a --------- - (42) -(1 row) - -select (select view_a) from view_a; - view_a --------- - (42) -(1 row) - -select (select (select view_a)) from view_a; - view_a --------- - (42) -(1 row) - -select (select (a.*)::text) from view_a a; - a ------- - (42) -(1 row) - --- --- Check that whole-row Vars reading the result of a subselect don't include --- any junk columns therein --- -select q from (select max(f1) from int4_tbl group by f1 order by f1) q; - q ---------------- - (-2147483647) - (-123456) - (0) - (123456) - (2147483647) -(5 rows) - -with q as (select max(f1) from int4_tbl group by f1 order by f1) - select q from q; - q ---------------- - (-2147483647) - (-123456) - (0) - (123456) - (2147483647) -(5 rows) - --- --- Test case for sublinks pulled up into joinaliasvars lists in an --- inherited update/delete query --- -begin; -- this shouldn't delete anything, but be safe -delete from road -where exists ( - select 1 - from - int4_tbl cross join - ( select f1, array(select q1 from int8_tbl) as arr - from text_tbl ) ss - where road.name = ss.f1 ); -rollback; --- --- Test case for sublinks pushed down into subselects via join alias expansion --- -select - (select sq1) as qq1 -from - (select exists(select 1 from int4_tbl where f1 = q2) as sq1, 42 as dummy - from int8_tbl) sq0 - join - int4_tbl i4 on dummy = i4.f1; - qq1 ------ -(0 rows) - --- --- Test case for subselect within UPDATE of INSERT...ON CONFLICT DO UPDATE --- -create temp table upsert(key int4 primary key, val text); -insert into upsert values(1, 'val') on conflict (key) do update set val = 'not seen'; -insert into upsert values(1, 'val') on conflict (key) do update set val = 'seen with subselect ' || (select f1 from int4_tbl where f1 != 0 limit 1)::text; -select * from upsert; - key | val ------+---------------------------- - 1 | seen with subselect 123456 -(1 row) - -with aa as (select 'int4_tbl' u from int4_tbl limit 1) -insert into upsert values (1, 'x'), (999, 'y') -on conflict (key) do update set val = (select u from aa) -returning *; - key | val ------+---------- - 1 | int4_tbl - 999 | y -(2 rows) - --- --- Test case for cross-type partial matching in hashed subplan (bug #7597) --- -create temp table outer_7597 (f1 int4, f2 int4); -insert into outer_7597 values (0, 0); -insert into outer_7597 values (1, 0); -insert into outer_7597 values (0, null); -insert into outer_7597 values (1, null); -create temp table inner_7597(c1 int8, c2 int8); -insert into inner_7597 values(0, null); -select * from outer_7597 where (f1, f2) not in (select * from inner_7597); - f1 | f2 -----+---- - 1 | 0 - 1 | -(2 rows) - --- --- Similar test case using text that verifies that collation --- information is passed through by execTuplesEqual() in nodeSubplan.c --- (otherwise it would error in texteq()) --- -create temp table outer_text (f1 text, f2 text); -insert into outer_text values ('a', 'a'); -insert into outer_text values ('b', 'a'); -insert into outer_text values ('a', null); -insert into outer_text values ('b', null); -create temp table inner_text (c1 text, c2 text); -insert into inner_text values ('a', null); -insert into inner_text values ('123', '456'); -select * from outer_text where (f1, f2) not in (select * from inner_text); - f1 | f2 -----+---- - b | a - b | -(2 rows) - --- --- Another test case for cross-type hashed subplans: comparison of --- inner-side values must be done with appropriate operator --- -explain (verbose, costs off) -select 'foo'::text in (select 'bar'::name union all select 'bar'::name); - QUERY PLAN ---------------------------------------------------------- - Result - Output: (ANY ('foo'::text = (hashed SubPlan 1).col1)) - SubPlan 1 - -> Append - -> Result - Output: 'bar'::name - -> Result - Output: 'bar'::name -(8 rows) - -select 'foo'::text in (select 'bar'::name union all select 'bar'::name); - ?column? ----------- - f -(1 row) - --- --- Test that we don't try to hash nested records (bug #17363) --- (Hashing could be supported, but for now we don't) --- -explain (verbose, costs off) -select row(row(row(1))) = any (select row(row(1))); - QUERY PLAN --------------------------------------------------------- - Result - Output: (ANY ('("(1)")'::record = (SubPlan 1).col1)) - SubPlan 1 - -> Materialize - Output: '("(1)")'::record - -> Result - Output: '("(1)")'::record -(7 rows) - -select row(row(row(1))) = any (select row(row(1))); - ?column? ----------- - t -(1 row) - --- --- Test case for premature memory release during hashing of subplan output --- -select '1'::text in (select '1'::name union all select '1'::name); - ?column? ----------- - t -(1 row) - --- --- Test that we don't try to use a hashed subplan if the simplified --- testexpr isn't of the right shape --- --- this fails by default, of course -select * from int8_tbl where q1 in (select c1 from inner_text); -ERROR: operator does not exist: bigint = text -LINE 1: select * from int8_tbl where q1 in (select c1 from inner_tex... - ^ -HINT: No operator matches the given name and argument types. You might need to add explicit type casts. -begin; --- make an operator to allow it to succeed -create function bogus_int8_text_eq(int8, text) returns boolean -language sql as 'select $1::text = $2'; -create operator = (procedure=bogus_int8_text_eq, leftarg=int8, rightarg=text); -explain (costs off) -select * from int8_tbl where q1 in (select c1 from inner_text); - QUERY PLAN --------------------------------------------------------- - Seq Scan on int8_tbl - Filter: (ANY ((q1)::text = (hashed SubPlan 1).col1)) - SubPlan 1 - -> Seq Scan on inner_text -(4 rows) - -select * from int8_tbl where q1 in (select c1 from inner_text); - q1 | q2 ------+------------------ - 123 | 456 - 123 | 4567890123456789 -(2 rows) - --- inlining of this function results in unusual number of hash clauses, --- which we can still cope with -create or replace function bogus_int8_text_eq(int8, text) returns boolean -language sql as 'select $1::text = $2 and $1::text = $2'; -explain (costs off) -select * from int8_tbl where q1 in (select c1 from inner_text); - QUERY PLAN ------------------------------------------------------------------------------------------------------ - Seq Scan on int8_tbl - Filter: (ANY (((q1)::text = (hashed SubPlan 1).col1) AND ((q1)::text = (hashed SubPlan 1).col1))) - SubPlan 1 - -> Seq Scan on inner_text -(4 rows) - -select * from int8_tbl where q1 in (select c1 from inner_text); - q1 | q2 ------+------------------ - 123 | 456 - 123 | 4567890123456789 -(2 rows) - --- inlining of this function causes LHS and RHS to be switched, --- which we can't cope with, so hashing should be abandoned -create or replace function bogus_int8_text_eq(int8, text) returns boolean -language sql as 'select $2 = $1::text'; -explain (costs off) -select * from int8_tbl where q1 in (select c1 from inner_text); - QUERY PLAN -------------------------------------------------- - Seq Scan on int8_tbl - Filter: (ANY ((SubPlan 1).col1 = (q1)::text)) - SubPlan 1 - -> Materialize - -> Seq Scan on inner_text -(5 rows) - -select * from int8_tbl where q1 in (select c1 from inner_text); - q1 | q2 ------+------------------ - 123 | 456 - 123 | 4567890123456789 -(2 rows) - -rollback; -- to get rid of the bogus operator --- --- Test resolution of hashed vs non-hashed implementation of EXISTS subplan --- -explain (costs off) -select count(*) from tenk1 t -where (exists(select 1 from tenk1 k where k.unique1 = t.unique2) or ten < 0); - QUERY PLAN --------------------------------------------------------------------------- - Aggregate - -> Seq Scan on tenk1 t - Filter: ((ANY (unique2 = (hashed SubPlan 2).col1)) OR (ten < 0)) - SubPlan 2 - -> Index Only Scan using tenk1_unique1 on tenk1 k -(5 rows) - -select count(*) from tenk1 t -where (exists(select 1 from tenk1 k where k.unique1 = t.unique2) or ten < 0); - count -------- - 10000 -(1 row) - -explain (costs off) -select count(*) from tenk1 t -where (exists(select 1 from tenk1 k where k.unique1 = t.unique2) or ten < 0) - and thousand = 1; - QUERY PLAN --------------------------------------------------------------- - Aggregate - -> Bitmap Heap Scan on tenk1 t - Recheck Cond: (thousand = 1) - Filter: (EXISTS(SubPlan 1) OR (ten < 0)) - -> Bitmap Index Scan on tenk1_thous_tenthous - Index Cond: (thousand = 1) - SubPlan 1 - -> Index Only Scan using tenk1_unique1 on tenk1 k - Index Cond: (unique1 = t.unique2) -(9 rows) - -select count(*) from tenk1 t -where (exists(select 1 from tenk1 k where k.unique1 = t.unique2) or ten < 0) - and thousand = 1; - count -------- - 10 -(1 row) - --- It's possible for the same EXISTS to get resolved both ways -create temp table exists_tbl (c1 int, c2 int, c3 int) partition by list (c1); -create temp table exists_tbl_null partition of exists_tbl for values in (null); -create temp table exists_tbl_def partition of exists_tbl default; -insert into exists_tbl select x, x/2, x+1 from generate_series(0,10) x; -analyze exists_tbl; -explain (costs off) -select * from exists_tbl t1 - where (exists(select 1 from exists_tbl t2 where t1.c1 = t2.c2) or c3 < 0); - QUERY PLAN --------------------------------------------------------------------- - Append - -> Seq Scan on exists_tbl_null t1_1 - Filter: (EXISTS(SubPlan 1) OR (c3 < 0)) - SubPlan 1 - -> Append - -> Seq Scan on exists_tbl_null t2_1 - Filter: (t1_1.c1 = c2) - -> Seq Scan on exists_tbl_def t2_2 - Filter: (t1_1.c1 = c2) - -> Seq Scan on exists_tbl_def t1_2 - Filter: ((ANY (c1 = (hashed SubPlan 2).col1)) OR (c3 < 0)) - SubPlan 2 - -> Append - -> Seq Scan on exists_tbl_null t2_4 - -> Seq Scan on exists_tbl_def t2_5 -(15 rows) - -select * from exists_tbl t1 - where (exists(select 1 from exists_tbl t2 where t1.c1 = t2.c2) or c3 < 0); - c1 | c2 | c3 -----+----+---- - 0 | 0 | 1 - 1 | 0 | 2 - 2 | 1 | 3 - 3 | 1 | 4 - 4 | 2 | 5 - 5 | 2 | 6 -(6 rows) - --- --- Test case for planner bug with nested EXISTS handling --- -select a.thousand from tenk1 a, tenk1 b -where a.thousand = b.thousand - and exists ( select 1 from tenk1 c where b.hundred = c.hundred - and not exists ( select 1 from tenk1 d - where a.thousand = d.thousand ) ); - thousand ----------- -(0 rows) - --- --- Check that nested sub-selects are not pulled up if they contain volatiles --- -explain (verbose, costs off) - select x, x from - (select (select now()) as x from (values(1),(2)) v(y)) ss; - QUERY PLAN ------------------------------------------------- - Values Scan on "*VALUES*" - Output: (InitPlan 1).col1, (InitPlan 2).col1 - InitPlan 1 - -> Result - Output: now() - InitPlan 2 - -> Result - Output: now() -(8 rows) - -explain (verbose, costs off) - select x, x from - (select (select random()) as x from (values(1),(2)) v(y)) ss; - QUERY PLAN ------------------------------------ - Subquery Scan on ss - Output: ss.x, ss.x - -> Values Scan on "*VALUES*" - Output: (InitPlan 1).col1 - InitPlan 1 - -> Result - Output: random() -(7 rows) - -explain (verbose, costs off) - select x, x from - (select (select now() where y=y) as x from (values(1),(2)) v(y)) ss; - QUERY PLAN ----------------------------------------------------------------------- - Values Scan on "*VALUES*" - Output: (SubPlan 1), (SubPlan 2) - SubPlan 1 - -> Result - Output: now() - One-Time Filter: ("*VALUES*".column1 = "*VALUES*".column1) - SubPlan 2 - -> Result - Output: now() - One-Time Filter: ("*VALUES*".column1 = "*VALUES*".column1) -(10 rows) - -explain (verbose, costs off) - select x, x from - (select (select random() where y=y) as x from (values(1),(2)) v(y)) ss; - QUERY PLAN ----------------------------------------------------------------------------- - Subquery Scan on ss - Output: ss.x, ss.x - -> Values Scan on "*VALUES*" - Output: (SubPlan 1) - SubPlan 1 - -> Result - Output: random() - One-Time Filter: ("*VALUES*".column1 = "*VALUES*".column1) -(8 rows) - --- --- Test rescan of a hashed subplan (the use of random() is to prevent the --- sub-select from being pulled up, which would result in not hashing) --- -explain (verbose, costs off) -select sum(ss.tst::int) from - onek o cross join lateral ( - select i.ten in (select f1 from int4_tbl where f1 <= o.hundred) as tst, - random() as r - from onek i where i.unique1 = o.unique1 ) ss -where o.ten = 0; - QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - Aggregate - Output: sum((((ANY (i.ten = (hashed SubPlan 1).col1))))::integer) - -> Nested Loop - Output: ((ANY (i.ten = (hashed SubPlan 1).col1))) - -> Seq Scan on public.onek o - Output: o.unique1, o.unique2, o.two, o.four, o.ten, o.twenty, o.hundred, o.thousand, o.twothousand, o.fivethous, o.tenthous, o.odd, o.even, o.stringu1, o.stringu2, o.string4 - Filter: (o.ten = 0) - -> Index Scan using onek_unique1 on public.onek i - Output: (ANY (i.ten = (hashed SubPlan 1).col1)), random() - Index Cond: (i.unique1 = o.unique1) - SubPlan 1 - -> Seq Scan on public.int4_tbl - Output: int4_tbl.f1 - Filter: (int4_tbl.f1 <= o.hundred) -(14 rows) - -select sum(ss.tst::int) from - onek o cross join lateral ( - select i.ten in (select f1 from int4_tbl where f1 <= o.hundred) as tst, - random() as r - from onek i where i.unique1 = o.unique1 ) ss -where o.ten = 0; - sum ------ - 100 -(1 row) - --- --- Test rescan of a SetOp node --- -explain (costs off) -select count(*) from - onek o cross join lateral ( - select * from onek i1 where i1.unique1 = o.unique1 - except - select * from onek i2 where i2.unique1 = o.unique2 - ) ss -where o.ten = 1; - QUERY PLAN ------------------------------------------------------------------------------- - Aggregate - -> Nested Loop - -> Seq Scan on onek o - Filter: (ten = 1) - -> Subquery Scan on ss - -> HashSetOp Except - -> Append - -> Subquery Scan on "*SELECT* 1" - -> Index Scan using onek_unique1 on onek i1 - Index Cond: (unique1 = o.unique1) - -> Subquery Scan on "*SELECT* 2" - -> Index Scan using onek_unique1 on onek i2 - Index Cond: (unique1 = o.unique2) -(13 rows) - -select count(*) from - onek o cross join lateral ( - select * from onek i1 where i1.unique1 = o.unique1 - except - select * from onek i2 where i2.unique1 = o.unique2 - ) ss -where o.ten = 1; - count -------- - 100 -(1 row) - --- --- Test rescan of a RecursiveUnion node --- -explain (costs off) -select sum(o.four), sum(ss.a) from - onek o cross join lateral ( - with recursive x(a) as - (select o.four as a - union - select a + 1 from x - where a < 10) - select * from x - ) ss -where o.ten = 1; - QUERY PLAN ---------------------------------------------------------- - Aggregate - -> Nested Loop - -> Seq Scan on onek o - Filter: (ten = 1) - -> Memoize - Cache Key: o.four - Cache Mode: binary - -> CTE Scan on x - CTE x - -> Recursive Union - -> Result - -> WorkTable Scan on x x_1 - Filter: (a < 10) -(13 rows) - -select sum(o.four), sum(ss.a) from - onek o cross join lateral ( - with recursive x(a) as - (select o.four as a - union - select a + 1 from x - where a < 10) - select * from x - ) ss -where o.ten = 1; - sum | sum -------+------ - 1700 | 5350 -(1 row) - --- --- Check we don't misoptimize a NOT IN where the subquery returns no rows. --- -create temp table notinouter (a int); -create temp table notininner (b int not null); -insert into notinouter values (null), (1); -select * from notinouter where a not in (select b from notininner); - a ---- - - 1 -(2 rows) - --- --- Check we behave sanely in corner case of empty SELECT list (bug #8648) --- -create temp table nocolumns(); -select exists(select * from nocolumns); - exists --------- - f -(1 row) - --- --- Check behavior with a SubPlan in VALUES (bug #14924) --- -select val.x - from generate_series(1,10) as s(i), - lateral ( - values ((select s.i + 1)), (s.i + 101) - ) as val(x) -where s.i < 10 and (select val.x) < 110; - x ------ - 2 - 102 - 3 - 103 - 4 - 104 - 5 - 105 - 6 - 106 - 7 - 107 - 8 - 108 - 9 - 109 - 10 -(17 rows) - --- another variant of that (bug #16213) -explain (verbose, costs off) -select * from -(values - (3 not in (select * from (values (1), (2)) ss1)), - (false) -) ss; - QUERY PLAN ----------------------------------------- - Values Scan on "*VALUES*" - Output: "*VALUES*".column1 - SubPlan 1 - -> Values Scan on "*VALUES*_1" - Output: "*VALUES*_1".column1 -(5 rows) - -select * from -(values - (3 not in (select * from (values (1), (2)) ss1)), - (false) -) ss; - column1 ---------- - t - f -(2 rows) - --- --- Check sane behavior with nested IN SubLinks --- -explain (verbose, costs off) -select * from int4_tbl where - (case when f1 in (select unique1 from tenk1 a) then f1 else null end) in - (select ten from tenk1 b); - QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - Nested Loop Semi Join - Output: int4_tbl.f1 - Join Filter: (CASE WHEN (ANY (int4_tbl.f1 = (hashed SubPlan 1).col1)) THEN int4_tbl.f1 ELSE NULL::integer END = b.ten) - -> Seq Scan on public.int4_tbl - Output: int4_tbl.f1 - -> Seq Scan on public.tenk1 b - Output: b.unique1, b.unique2, b.two, b.four, b.ten, b.twenty, b.hundred, b.thousand, b.twothousand, b.fivethous, b.tenthous, b.odd, b.even, b.stringu1, b.stringu2, b.string4 - SubPlan 1 - -> Index Only Scan using tenk1_unique1 on public.tenk1 a - Output: a.unique1 -(10 rows) - -select * from int4_tbl where - (case when f1 in (select unique1 from tenk1 a) then f1 else null end) in - (select ten from tenk1 b); - f1 ----- - 0 -(1 row) - --- --- Check for incorrect optimization when IN subquery contains a SRF --- -explain (verbose, costs off) -select * from int4_tbl o where (f1, f1) in - (select f1, generate_series(1,50) / 10 g from int4_tbl i group by f1); - QUERY PLAN -------------------------------------------------------------------- - Nested Loop Semi Join - Output: o.f1 - Join Filter: (o.f1 = "ANY_subquery".f1) - -> Seq Scan on public.int4_tbl o - Output: o.f1 - -> Materialize - Output: "ANY_subquery".f1, "ANY_subquery".g - -> Subquery Scan on "ANY_subquery" - Output: "ANY_subquery".f1, "ANY_subquery".g - Filter: ("ANY_subquery".f1 = "ANY_subquery".g) - -> Result - Output: i.f1, ((generate_series(1, 50)) / 10) - -> ProjectSet - Output: generate_series(1, 50), i.f1 - -> HashAggregate - Output: i.f1 - Group Key: i.f1 - -> Seq Scan on public.int4_tbl i - Output: i.f1 -(19 rows) - -select * from int4_tbl o where (f1, f1) in - (select f1, generate_series(1,50) / 10 g from int4_tbl i group by f1); - f1 ----- - 0 -(1 row) - --- --- check for over-optimization of whole-row Var referencing an Append plan --- -select (select q from - (select 1,2,3 where f1 > 0 - union all - select 4,5,6.0 where f1 <= 0 - ) q ) -from int4_tbl; - q ------------ - (4,5,6.0) - (1,2,3) - (4,5,6.0) - (1,2,3) - (4,5,6.0) -(5 rows) - --- --- Check for sane handling of a lateral reference in a subquery's quals --- (most of the complication here is to prevent the test case from being --- flattened too much) --- -explain (verbose, costs off) -select * from - int4_tbl i4, - lateral ( - select i4.f1 > 1 as b, 1 as id - from (select random() order by 1) as t1 - union all - select true as b, 2 as id - ) as t2 -where b and f1 >= 0; - QUERY PLAN --------------------------------------------- - Nested Loop - Output: i4.f1, ((i4.f1 > 1)), (1) - -> Seq Scan on public.int4_tbl i4 - Output: i4.f1 - Filter: (i4.f1 >= 0) - -> Append - -> Subquery Scan on t1 - Output: (i4.f1 > 1), 1 - Filter: (i4.f1 > 1) - -> Sort - Output: (random()) - Sort Key: (random()) - -> Result - Output: random() - -> Result - Output: true, 2 -(16 rows) - -select * from - int4_tbl i4, - lateral ( - select i4.f1 > 1 as b, 1 as id - from (select random() order by 1) as t1 - union all - select true as b, 2 as id - ) as t2 -where b and f1 >= 0; - f1 | b | id -------------+---+---- - 0 | t | 2 - 123456 | t | 1 - 123456 | t | 2 - 2147483647 | t | 1 - 2147483647 | t | 2 -(5 rows) - --- --- Check that volatile quals aren't pushed down past a DISTINCT: --- nextval() should not be called more than the nominal number of times --- -create temp sequence ts1; -select * from - (select distinct ten from tenk1) ss - where ten < 10 + nextval('ts1') - order by 1; - ten ------ - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 -(10 rows) - -select nextval('ts1'); - nextval ---------- - 11 -(1 row) - --- --- Check that volatile quals aren't pushed down past a set-returning function; --- while a nonvolatile qual can be, if it doesn't reference the SRF. --- -create function tattle(x int, y int) returns bool -volatile language plpgsql as $$ -begin - raise notice 'x = %, y = %', x, y; - return x > y; -end$$; -explain (verbose, costs off) -select * from - (select 9 as x, unnest(array[1,2,3,11,12,13]) as u) ss - where tattle(x, 8); - QUERY PLAN ----------------------------------------------------------- - Subquery Scan on ss - Output: ss.x, ss.u - Filter: tattle(ss.x, 8) - -> ProjectSet - Output: 9, unnest('{1,2,3,11,12,13}'::integer[]) - -> Result -(6 rows) - -select * from - (select 9 as x, unnest(array[1,2,3,11,12,13]) as u) ss - where tattle(x, 8); -NOTICE: x = 9, y = 8 -NOTICE: x = 9, y = 8 -NOTICE: x = 9, y = 8 -NOTICE: x = 9, y = 8 -NOTICE: x = 9, y = 8 -NOTICE: x = 9, y = 8 - x | u ----+---- - 9 | 1 - 9 | 2 - 9 | 3 - 9 | 11 - 9 | 12 - 9 | 13 -(6 rows) - --- if we pretend it's stable, we get different results: -alter function tattle(x int, y int) stable; -explain (verbose, costs off) -select * from - (select 9 as x, unnest(array[1,2,3,11,12,13]) as u) ss - where tattle(x, 8); - QUERY PLAN ----------------------------------------------------- - ProjectSet - Output: 9, unnest('{1,2,3,11,12,13}'::integer[]) - -> Result - One-Time Filter: tattle(9, 8) -(4 rows) - -select * from - (select 9 as x, unnest(array[1,2,3,11,12,13]) as u) ss - where tattle(x, 8); -NOTICE: x = 9, y = 8 - x | u ----+---- - 9 | 1 - 9 | 2 - 9 | 3 - 9 | 11 - 9 | 12 - 9 | 13 -(6 rows) - --- although even a stable qual should not be pushed down if it references SRF -explain (verbose, costs off) -select * from - (select 9 as x, unnest(array[1,2,3,11,12,13]) as u) ss - where tattle(x, u); - QUERY PLAN ----------------------------------------------------------- - Subquery Scan on ss - Output: ss.x, ss.u - Filter: tattle(ss.x, ss.u) - -> ProjectSet - Output: 9, unnest('{1,2,3,11,12,13}'::integer[]) - -> Result -(6 rows) - -select * from - (select 9 as x, unnest(array[1,2,3,11,12,13]) as u) ss - where tattle(x, u); -NOTICE: x = 9, y = 1 -NOTICE: x = 9, y = 2 -NOTICE: x = 9, y = 3 -NOTICE: x = 9, y = 11 -NOTICE: x = 9, y = 12 -NOTICE: x = 9, y = 13 - x | u ----+--- - 9 | 1 - 9 | 2 - 9 | 3 -(3 rows) - -drop function tattle(x int, y int); --- --- Test that LIMIT can be pushed to SORT through a subquery that just projects --- columns. We check for that having happened by looking to see if EXPLAIN --- ANALYZE shows that a top-N sort was used. We must suppress or filter away --- all the non-invariant parts of the EXPLAIN ANALYZE output. --- -create table sq_limit (pk int primary key, c1 int, c2 int); -insert into sq_limit values - (1, 1, 1), - (2, 2, 2), - (3, 3, 3), - (4, 4, 4), - (5, 1, 1), - (6, 2, 2), - (7, 3, 3), - (8, 4, 4); -create function explain_sq_limit() returns setof text language plpgsql as -$$ -declare ln text; -begin - for ln in - explain (analyze, summary off, timing off, costs off) - select * from (select pk,c2 from sq_limit order by c1,pk) as x limit 3 - loop - ln := regexp_replace(ln, 'Memory: \S*', 'Memory: xxx'); - return next ln; - end loop; -end; -$$; -select * from explain_sq_limit(); - explain_sq_limit ----------------------------------------------------------------- - Limit (actual rows=3 loops=1) - -> Subquery Scan on x (actual rows=3 loops=1) - -> Sort (actual rows=3 loops=1) - Sort Key: sq_limit.c1, sq_limit.pk - Sort Method: top-N heapsort Memory: xxx - -> Seq Scan on sq_limit (actual rows=8 loops=1) -(6 rows) - -select * from (select pk,c2 from sq_limit order by c1,pk) as x limit 3; - pk | c2 -----+---- - 1 | 1 - 5 | 1 - 2 | 2 -(3 rows) - -drop function explain_sq_limit(); -drop table sq_limit; --- --- Ensure that backward scan direction isn't propagated into --- expression subqueries (bug #15336) --- -begin; -declare c1 scroll cursor for - select * from generate_series(1,4) i - where i <> all (values (2),(3)); -move forward all in c1; -fetch backward all in c1; - i ---- - 4 - 1 -(2 rows) - -commit; --- --- Verify that we correctly flatten cases involving a subquery output --- expression that doesn't need to be wrapped in a PlaceHolderVar --- -explain (costs off) -select tname, attname from ( -select relname::information_schema.sql_identifier as tname, * from - (select * from pg_class c) ss1) ss2 - right join pg_attribute a on a.attrelid = ss2.oid -where tname = 'tenk1' and attnum = 1; - QUERY PLAN --------------------------------------------------------------------------- - Nested Loop - -> Index Scan using pg_class_relname_nsp_index on pg_class c - Index Cond: (relname = 'tenk1'::name) - -> Index Scan using pg_attribute_relid_attnum_index on pg_attribute a - Index Cond: ((attrelid = c.oid) AND (attnum = 1)) -(5 rows) - -select tname, attname from ( -select relname::information_schema.sql_identifier as tname, * from - (select * from pg_class c) ss1) ss2 - right join pg_attribute a on a.attrelid = ss2.oid -where tname = 'tenk1' and attnum = 1; - tname | attname --------+--------- - tenk1 | unique1 -(1 row) - --- --- Tests for CTE inlining behavior --- --- Basic subquery that can be inlined -explain (verbose, costs off) -with x as (select * from (select f1 from subselect_tbl) ss) -select * from x where f1 = 1; - QUERY PLAN ----------------------------------- - Seq Scan on public.subselect_tbl - Output: subselect_tbl.f1 - Filter: (subselect_tbl.f1 = 1) -(3 rows) - --- Explicitly request materialization -explain (verbose, costs off) -with x as materialized (select * from (select f1 from subselect_tbl) ss) -select * from x where f1 = 1; - QUERY PLAN ------------------------------------------- - CTE Scan on x - Output: x.f1 - Filter: (x.f1 = 1) - CTE x - -> Seq Scan on public.subselect_tbl - Output: subselect_tbl.f1 -(6 rows) - --- Stable functions are safe to inline -explain (verbose, costs off) -with x as (select * from (select f1, now() from subselect_tbl) ss) -select * from x where f1 = 1; - QUERY PLAN ------------------------------------ - Seq Scan on public.subselect_tbl - Output: subselect_tbl.f1, now() - Filter: (subselect_tbl.f1 = 1) -(3 rows) - --- Volatile functions prevent inlining -explain (verbose, costs off) -with x as (select * from (select f1, random() from subselect_tbl) ss) -select * from x where f1 = 1; - QUERY PLAN ----------------------------------------------- - CTE Scan on x - Output: x.f1, x.random - Filter: (x.f1 = 1) - CTE x - -> Seq Scan on public.subselect_tbl - Output: subselect_tbl.f1, random() -(6 rows) - --- SELECT FOR UPDATE cannot be inlined -explain (verbose, costs off) -with x as (select * from (select f1 from subselect_tbl for update) ss) -select * from x where f1 = 1; - QUERY PLAN --------------------------------------------------------------------- - CTE Scan on x - Output: x.f1 - Filter: (x.f1 = 1) - CTE x - -> Subquery Scan on ss - Output: ss.f1 - -> LockRows - Output: subselect_tbl.f1, subselect_tbl.ctid - -> Seq Scan on public.subselect_tbl - Output: subselect_tbl.f1, subselect_tbl.ctid -(10 rows) - --- Multiply-referenced CTEs are inlined only when requested -explain (verbose, costs off) -with x as (select * from (select f1, now() as n from subselect_tbl) ss) -select * from x, x x2 where x.n = x2.n; - QUERY PLAN -------------------------------------------- - Merge Join - Output: x.f1, x.n, x2.f1, x2.n - Merge Cond: (x.n = x2.n) - CTE x - -> Seq Scan on public.subselect_tbl - Output: subselect_tbl.f1, now() - -> Sort - Output: x.f1, x.n - Sort Key: x.n - -> CTE Scan on x - Output: x.f1, x.n - -> Sort - Output: x2.f1, x2.n - Sort Key: x2.n - -> CTE Scan on x x2 - Output: x2.f1, x2.n -(16 rows) - -explain (verbose, costs off) -with x as not materialized (select * from (select f1, now() as n from subselect_tbl) ss) -select * from x, x x2 where x.n = x2.n; - QUERY PLAN ----------------------------------------------------------------------------- - Result - Output: subselect_tbl.f1, now(), subselect_tbl_1.f1, now() - One-Time Filter: (now() = now()) - -> Nested Loop - Output: subselect_tbl.f1, subselect_tbl_1.f1 - -> Seq Scan on public.subselect_tbl - Output: subselect_tbl.f1, subselect_tbl.f2, subselect_tbl.f3 - -> Materialize - Output: subselect_tbl_1.f1 - -> Seq Scan on public.subselect_tbl subselect_tbl_1 - Output: subselect_tbl_1.f1 -(11 rows) - --- Multiply-referenced CTEs can't be inlined if they contain outer self-refs -explain (verbose, costs off) -with recursive x(a) as - ((values ('a'), ('b')) - union all - (with z as not materialized (select * from x) - select z.a || z1.a as a from z cross join z as z1 - where length(z.a || z1.a) < 5)) -select * from x; - QUERY PLAN ----------------------------------------------------------- - CTE Scan on x - Output: x.a - CTE x - -> Recursive Union - -> Values Scan on "*VALUES*" - Output: "*VALUES*".column1 - -> Nested Loop - Output: (z.a || z1.a) - Join Filter: (length((z.a || z1.a)) < 5) - CTE z - -> WorkTable Scan on x x_1 - Output: x_1.a - -> CTE Scan on z - Output: z.a - -> CTE Scan on z z1 - Output: z1.a -(16 rows) - -with recursive x(a) as - ((values ('a'), ('b')) - union all - (with z as not materialized (select * from x) - select z.a || z1.a as a from z cross join z as z1 - where length(z.a || z1.a) < 5)) -select * from x; - a ------- - a - b - aa - ab - ba - bb - aaaa - aaab - aaba - aabb - abaa - abab - abba - abbb - baaa - baab - baba - babb - bbaa - bbab - bbba - bbbb -(22 rows) - -explain (verbose, costs off) -with recursive x(a) as - ((values ('a'), ('b')) - union all - (with z as not materialized (select * from x) - select z.a || z.a as a from z - where length(z.a || z.a) < 5)) -select * from x; - QUERY PLAN --------------------------------------------------------- - CTE Scan on x - Output: x.a - CTE x - -> Recursive Union - -> Values Scan on "*VALUES*" - Output: "*VALUES*".column1 - -> WorkTable Scan on x x_1 - Output: (x_1.a || x_1.a) - Filter: (length((x_1.a || x_1.a)) < 5) -(9 rows) - -with recursive x(a) as - ((values ('a'), ('b')) - union all - (with z as not materialized (select * from x) - select z.a || z.a as a from z - where length(z.a || z.a) < 5)) -select * from x; - a ------- - a - b - aa - bb - aaaa - bbbb -(6 rows) - --- Check handling of outer references -explain (verbose, costs off) -with x as (select * from int4_tbl) -select * from (with y as (select * from x) select * from y) ss; - QUERY PLAN ------------------------------ - Seq Scan on public.int4_tbl - Output: int4_tbl.f1 -(2 rows) - -explain (verbose, costs off) -with x as materialized (select * from int4_tbl) -select * from (with y as (select * from x) select * from y) ss; - QUERY PLAN -------------------------------------- - CTE Scan on x - Output: x.f1 - CTE x - -> Seq Scan on public.int4_tbl - Output: int4_tbl.f1 -(5 rows) - --- Ensure that we inline the correct CTE when there are --- multiple CTEs with the same name -explain (verbose, costs off) -with x as (select 1 as y) -select * from (with x as (select 2 as y) select * from x) ss; - QUERY PLAN -------------- - Result - Output: 2 -(2 rows) - --- Row marks are not pushed into CTEs -explain (verbose, costs off) -with x as (select * from subselect_tbl) -select * from x for update; - QUERY PLAN ----------------------------------------------------------------- - Seq Scan on public.subselect_tbl - Output: subselect_tbl.f1, subselect_tbl.f2, subselect_tbl.f3 -(2 rows) - --- Pull up direct-correlated ANY_SUBLINKs -explain (costs off) -select * from tenk1 A where hundred in (select hundred from tenk2 B where B.odd = A.odd); - QUERY PLAN ------------------------------------------------------------- - Hash Join - Hash Cond: ((a.odd = b.odd) AND (a.hundred = b.hundred)) - -> Seq Scan on tenk1 a - -> Hash - -> HashAggregate - Group Key: b.odd, b.hundred - -> Seq Scan on tenk2 b -(7 rows) - -explain (costs off) -select * from tenk1 A where exists -(select 1 from tenk2 B -where A.hundred in (select C.hundred FROM tenk2 C -WHERE c.odd = b.odd)); - QUERY PLAN ------------------------------------------------------ - Nested Loop Semi Join - Join Filter: (ANY (a.hundred = (SubPlan 1).col1)) - -> Seq Scan on tenk1 a - -> Materialize - -> Seq Scan on tenk2 b - SubPlan 1 - -> Seq Scan on tenk2 c - Filter: (odd = b.odd) -(8 rows) - --- we should only try to pull up the sublink into RHS of a left join --- but a.hundred is not available. -explain (costs off) -SELECT * FROM tenk1 A LEFT JOIN tenk2 B -ON A.hundred in (SELECT c.hundred FROM tenk2 C WHERE c.odd = b.odd); - QUERY PLAN ------------------------------------------------------ - Nested Loop Left Join - Join Filter: (ANY (a.hundred = (SubPlan 1).col1)) - -> Seq Scan on tenk1 a - -> Materialize - -> Seq Scan on tenk2 b - SubPlan 1 - -> Seq Scan on tenk2 c - Filter: (odd = b.odd) -(8 rows) - --- we should only try to pull up the sublink into RHS of a left join --- but a.odd is not available for this. -explain (costs off) -SELECT * FROM tenk1 A LEFT JOIN tenk2 B -ON B.hundred in (SELECT c.hundred FROM tenk2 C WHERE c.odd = a.odd); - QUERY PLAN ------------------------------------------------------ - Nested Loop Left Join - Join Filter: (ANY (b.hundred = (SubPlan 1).col1)) - -> Seq Scan on tenk1 a - -> Materialize - -> Seq Scan on tenk2 b - SubPlan 1 - -> Seq Scan on tenk2 c - Filter: (odd = a.odd) -(8 rows) - --- should be able to pull up since all the references are available. -explain (costs off) -SELECT * FROM tenk1 A LEFT JOIN tenk2 B -ON B.hundred in (SELECT c.hundred FROM tenk2 C WHERE c.odd = b.odd); - QUERY PLAN ------------------------------------------------------------------------- - Nested Loop Left Join - -> Seq Scan on tenk1 a - -> Materialize - -> Hash Join - Hash Cond: ((b.odd = c.odd) AND (b.hundred = c.hundred)) - -> Seq Scan on tenk2 b - -> Hash - -> HashAggregate - Group Key: c.odd, c.hundred - -> Seq Scan on tenk2 c -(10 rows) - --- we can pull up the sublink into the inner JoinExpr. -explain (costs off) -SELECT * FROM tenk1 A INNER JOIN tenk2 B -ON A.hundred in (SELECT c.hundred FROM tenk2 C WHERE c.odd = b.odd) -WHERE a.thousand < 750; - QUERY PLAN -------------------------------------------------- - Hash Join - Hash Cond: (c.odd = b.odd) - -> Hash Join - Hash Cond: (a.hundred = c.hundred) - -> Seq Scan on tenk1 a - Filter: (thousand < 750) - -> Hash - -> HashAggregate - Group Key: c.odd, c.hundred - -> Seq Scan on tenk2 c - -> Hash - -> Seq Scan on tenk2 b -(12 rows) - --- we can pull up the aggregate sublink into RHS of a left join. -explain (costs off) -SELECT * FROM tenk1 A LEFT JOIN tenk2 B -ON B.hundred in (SELECT min(c.hundred) FROM tenk2 C WHERE c.odd = b.odd); - QUERY PLAN ---------------------------------------------------------------------------------------- - Nested Loop Left Join - -> Seq Scan on tenk1 a - -> Materialize - -> Nested Loop - -> Seq Scan on tenk2 b - -> Memoize - Cache Key: b.hundred, b.odd - Cache Mode: binary - -> Subquery Scan on "ANY_subquery" - Filter: (b.hundred = "ANY_subquery".min) - -> Result - InitPlan 1 - -> Limit - -> Index Scan using tenk2_hundred on tenk2 c - Index Cond: (hundred IS NOT NULL) - Filter: (odd = b.odd) -(16 rows) - +psql: error: connection to server on socket "/tmp/pg_regress-03Az7d/.s.PGSQL.40051" failed: FATAL: the database system is not yet accepting connections +DETAIL: Consistent recovery state has not been yet reached. diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/union.out /tmp/cirrus-ci-build/build/testrun/regress/regress/results/union.out --- /tmp/cirrus-ci-build/src/test/regress/expected/union.out 2024-09-20 21:41:45.766024524 +0000 +++ /tmp/cirrus-ci-build/build/testrun/regress/regress/results/union.out 2024-09-20 21:46:05.501625074 +0000 @@ -1,1492 +1,2 @@ --- --- UNION (also INTERSECT, EXCEPT) --- --- Simple UNION constructs -SELECT 1 AS two UNION SELECT 2 ORDER BY 1; - two ------ - 1 - 2 -(2 rows) - -SELECT 1 AS one UNION SELECT 1 ORDER BY 1; - one ------ - 1 -(1 row) - -SELECT 1 AS two UNION ALL SELECT 2; - two ------ - 1 - 2 -(2 rows) - -SELECT 1 AS two UNION ALL SELECT 1; - two ------ - 1 - 1 -(2 rows) - -SELECT 1 AS three UNION SELECT 2 UNION SELECT 3 ORDER BY 1; - three -------- - 1 - 2 - 3 -(3 rows) - -SELECT 1 AS two UNION SELECT 2 UNION SELECT 2 ORDER BY 1; - two ------ - 1 - 2 -(2 rows) - -SELECT 1 AS three UNION SELECT 2 UNION ALL SELECT 2 ORDER BY 1; - three -------- - 1 - 2 - 2 -(3 rows) - -SELECT 1.1 AS two UNION SELECT 2.2 ORDER BY 1; - two ------ - 1.1 - 2.2 -(2 rows) - --- Mixed types -SELECT 1.1 AS two UNION SELECT 2 ORDER BY 1; - two ------ - 1.1 - 2 -(2 rows) - -SELECT 1 AS two UNION SELECT 2.2 ORDER BY 1; - two ------ - 1 - 2.2 -(2 rows) - -SELECT 1 AS one UNION SELECT 1.0::float8 ORDER BY 1; - one ------ - 1 -(1 row) - -SELECT 1.1 AS two UNION ALL SELECT 2 ORDER BY 1; - two ------ - 1.1 - 2 -(2 rows) - -SELECT 1.0::float8 AS two UNION ALL SELECT 1 ORDER BY 1; - two ------ - 1 - 1 -(2 rows) - -SELECT 1.1 AS three UNION SELECT 2 UNION SELECT 3 ORDER BY 1; - three -------- - 1.1 - 2 - 3 -(3 rows) - -SELECT 1.1::float8 AS two UNION SELECT 2 UNION SELECT 2.0::float8 ORDER BY 1; - two ------ - 1.1 - 2 -(2 rows) - -SELECT 1.1 AS three UNION SELECT 2 UNION ALL SELECT 2 ORDER BY 1; - three -------- - 1.1 - 2 - 2 -(3 rows) - -SELECT 1.1 AS two UNION (SELECT 2 UNION ALL SELECT 2) ORDER BY 1; - two ------ - 1.1 - 2 -(2 rows) - --- --- Try testing from tables... --- -SELECT f1 AS five FROM FLOAT8_TBL -UNION -SELECT f1 FROM FLOAT8_TBL -ORDER BY 1; - five ------------------------ - -1.2345678901234e+200 - -1004.3 - -34.84 - -1.2345678901234e-200 - 0 -(5 rows) - -SELECT f1 AS ten FROM FLOAT8_TBL -UNION ALL -SELECT f1 FROM FLOAT8_TBL; - ten ------------------------ - 0 - -34.84 - -1004.3 - -1.2345678901234e+200 - -1.2345678901234e-200 - 0 - -34.84 - -1004.3 - -1.2345678901234e+200 - -1.2345678901234e-200 -(10 rows) - -SELECT f1 AS nine FROM FLOAT8_TBL -UNION -SELECT f1 FROM INT4_TBL -ORDER BY 1; - nine ------------------------ - -1.2345678901234e+200 - -2147483647 - -123456 - -1004.3 - -34.84 - -1.2345678901234e-200 - 0 - 123456 - 2147483647 -(9 rows) - -SELECT f1 AS ten FROM FLOAT8_TBL -UNION ALL -SELECT f1 FROM INT4_TBL; - ten ------------------------ - 0 - -34.84 - -1004.3 - -1.2345678901234e+200 - -1.2345678901234e-200 - 0 - 123456 - -123456 - 2147483647 - -2147483647 -(10 rows) - -SELECT f1 AS five FROM FLOAT8_TBL - WHERE f1 BETWEEN -1e6 AND 1e6 -UNION -SELECT f1 FROM INT4_TBL - WHERE f1 BETWEEN 0 AND 1000000 -ORDER BY 1; - five ------------------------ - -1004.3 - -34.84 - -1.2345678901234e-200 - 0 - 123456 -(5 rows) - -SELECT CAST(f1 AS char(4)) AS three FROM VARCHAR_TBL -UNION -SELECT f1 FROM CHAR_TBL -ORDER BY 1; - three -------- - a - ab - abcd -(3 rows) - -SELECT f1 AS three FROM VARCHAR_TBL -UNION -SELECT CAST(f1 AS varchar) FROM CHAR_TBL -ORDER BY 1; - three -------- - a - ab - abcd -(3 rows) - -SELECT f1 AS eight FROM VARCHAR_TBL -UNION ALL -SELECT f1 FROM CHAR_TBL; - eight -------- - a - ab - abcd - abcd - a - ab - abcd - abcd -(8 rows) - -SELECT f1 AS five FROM TEXT_TBL -UNION -SELECT f1 FROM VARCHAR_TBL -UNION -SELECT TRIM(TRAILING FROM f1) FROM CHAR_TBL -ORDER BY 1; - five -------------------- - a - ab - abcd - doh! - hi de ho neighbor -(5 rows) - --- --- INTERSECT and EXCEPT --- -SELECT q2 FROM int8_tbl INTERSECT SELECT q1 FROM int8_tbl ORDER BY 1; - q2 ------------------- - 123 - 4567890123456789 -(2 rows) - -SELECT q2 FROM int8_tbl INTERSECT ALL SELECT q1 FROM int8_tbl ORDER BY 1; - q2 ------------------- - 123 - 4567890123456789 - 4567890123456789 -(3 rows) - -SELECT q2 FROM int8_tbl EXCEPT SELECT q1 FROM int8_tbl ORDER BY 1; - q2 -------------------- - -4567890123456789 - 456 -(2 rows) - -SELECT q2 FROM int8_tbl EXCEPT ALL SELECT q1 FROM int8_tbl ORDER BY 1; - q2 -------------------- - -4567890123456789 - 456 -(2 rows) - -SELECT q2 FROM int8_tbl EXCEPT ALL SELECT DISTINCT q1 FROM int8_tbl ORDER BY 1; - q2 -------------------- - -4567890123456789 - 456 - 4567890123456789 -(3 rows) - -SELECT q1 FROM int8_tbl EXCEPT SELECT q2 FROM int8_tbl ORDER BY 1; - q1 ----- -(0 rows) - -SELECT q1 FROM int8_tbl EXCEPT ALL SELECT q2 FROM int8_tbl ORDER BY 1; - q1 ------------------- - 123 - 4567890123456789 -(2 rows) - -SELECT q1 FROM int8_tbl EXCEPT ALL SELECT DISTINCT q2 FROM int8_tbl ORDER BY 1; - q1 ------------------- - 123 - 4567890123456789 - 4567890123456789 -(3 rows) - -SELECT q1 FROM int8_tbl EXCEPT ALL SELECT q1 FROM int8_tbl FOR NO KEY UPDATE; -ERROR: FOR NO KEY UPDATE is not allowed with UNION/INTERSECT/EXCEPT --- nested cases -(SELECT 1,2,3 UNION SELECT 4,5,6) INTERSECT SELECT 4,5,6; - ?column? | ?column? | ?column? -----------+----------+---------- - 4 | 5 | 6 -(1 row) - -(SELECT 1,2,3 UNION SELECT 4,5,6 ORDER BY 1,2) INTERSECT SELECT 4,5,6; - ?column? | ?column? | ?column? -----------+----------+---------- - 4 | 5 | 6 -(1 row) - -(SELECT 1,2,3 UNION SELECT 4,5,6) EXCEPT SELECT 4,5,6; - ?column? | ?column? | ?column? -----------+----------+---------- - 1 | 2 | 3 -(1 row) - -(SELECT 1,2,3 UNION SELECT 4,5,6 ORDER BY 1,2) EXCEPT SELECT 4,5,6; - ?column? | ?column? | ?column? -----------+----------+---------- - 1 | 2 | 3 -(1 row) - --- exercise both hashed and sorted implementations of UNION/INTERSECT/EXCEPT -set enable_hashagg to on; -explain (costs off) -select count(*) from - ( select unique1 from tenk1 union select fivethous from tenk1 ) ss; - QUERY PLAN ----------------------------------------------------------------- - Aggregate - -> HashAggregate - Group Key: tenk1.unique1 - -> Append - -> Index Only Scan using tenk1_unique1 on tenk1 - -> Seq Scan on tenk1 tenk1_1 -(6 rows) - -select count(*) from - ( select unique1 from tenk1 union select fivethous from tenk1 ) ss; - count -------- - 10000 -(1 row) - -explain (costs off) -select count(*) from - ( select unique1 from tenk1 intersect select fivethous from tenk1 ) ss; - QUERY PLAN ------------------------------------------------------------------------------------- - Aggregate - -> Subquery Scan on ss - -> HashSetOp Intersect - -> Append - -> Subquery Scan on "*SELECT* 2" - -> Seq Scan on tenk1 - -> Subquery Scan on "*SELECT* 1" - -> Index Only Scan using tenk1_unique1 on tenk1 tenk1_1 -(8 rows) - -select count(*) from - ( select unique1 from tenk1 intersect select fivethous from tenk1 ) ss; - count -------- - 5000 -(1 row) - -explain (costs off) -select unique1 from tenk1 except select unique2 from tenk1 where unique2 != 10; - QUERY PLAN ------------------------------------------------------------------------- - HashSetOp Except - -> Append - -> Subquery Scan on "*SELECT* 1" - -> Index Only Scan using tenk1_unique1 on tenk1 - -> Subquery Scan on "*SELECT* 2" - -> Index Only Scan using tenk1_unique2 on tenk1 tenk1_1 - Filter: (unique2 <> 10) -(7 rows) - -select unique1 from tenk1 except select unique2 from tenk1 where unique2 != 10; - unique1 ---------- - 10 -(1 row) - -set enable_hashagg to off; -explain (costs off) -select count(*) from - ( select unique1 from tenk1 union select fivethous from tenk1 ) ss; - QUERY PLAN ----------------------------------------------------------------- - Aggregate - -> Unique - -> Merge Append - Sort Key: tenk1.unique1 - -> Index Only Scan using tenk1_unique1 on tenk1 - -> Sort - Sort Key: tenk1_1.fivethous - -> Seq Scan on tenk1 tenk1_1 -(8 rows) - -select count(*) from - ( select unique1 from tenk1 union select fivethous from tenk1 ) ss; - count -------- - 10000 -(1 row) - -explain (costs off) -select count(*) from - ( select unique1 from tenk1 intersect select fivethous from tenk1 ) ss; - QUERY PLAN ------------------------------------------------------------------------------------------- - Aggregate - -> Subquery Scan on ss - -> SetOp Intersect - -> Sort - Sort Key: "*SELECT* 2".fivethous - -> Append - -> Subquery Scan on "*SELECT* 2" - -> Seq Scan on tenk1 - -> Subquery Scan on "*SELECT* 1" - -> Index Only Scan using tenk1_unique1 on tenk1 tenk1_1 -(10 rows) - -select count(*) from - ( select unique1 from tenk1 intersect select fivethous from tenk1 ) ss; - count -------- - 5000 -(1 row) - -explain (costs off) -select unique1 from tenk1 except select unique2 from tenk1 where unique2 != 10; - QUERY PLAN ------------------------------------------------------------------------------- - SetOp Except - -> Sort - Sort Key: "*SELECT* 1".unique1 - -> Append - -> Subquery Scan on "*SELECT* 1" - -> Index Only Scan using tenk1_unique1 on tenk1 - -> Subquery Scan on "*SELECT* 2" - -> Index Only Scan using tenk1_unique2 on tenk1 tenk1_1 - Filter: (unique2 <> 10) -(9 rows) - -select unique1 from tenk1 except select unique2 from tenk1 where unique2 != 10; - unique1 ---------- - 10 -(1 row) - -reset enable_hashagg; --- non-hashable type -set enable_hashagg to on; -explain (costs off) -select x from (values ('11'::varbit), ('10'::varbit)) _(x) union select x from (values ('11'::varbit), ('10'::varbit)) _(x); - QUERY PLAN ------------------------------------------------ - Unique - -> Sort - Sort Key: "*VALUES*".column1 - -> Append - -> Values Scan on "*VALUES*" - -> Values Scan on "*VALUES*_1" -(6 rows) - -set enable_hashagg to off; -explain (costs off) -select x from (values ('11'::varbit), ('10'::varbit)) _(x) union select x from (values ('11'::varbit), ('10'::varbit)) _(x); - QUERY PLAN ------------------------------------------------ - Unique - -> Sort - Sort Key: "*VALUES*".column1 - -> Append - -> Values Scan on "*VALUES*" - -> Values Scan on "*VALUES*_1" -(6 rows) - -reset enable_hashagg; --- arrays -set enable_hashagg to on; -explain (costs off) -select x from (values (array[1, 2]), (array[1, 3])) _(x) union select x from (values (array[1, 2]), (array[1, 4])) _(x); - QUERY PLAN ------------------------------------------ - HashAggregate - Group Key: "*VALUES*".column1 - -> Append - -> Values Scan on "*VALUES*" - -> Values Scan on "*VALUES*_1" -(5 rows) - -select x from (values (array[1, 2]), (array[1, 3])) _(x) union select x from (values (array[1, 2]), (array[1, 4])) _(x); - x -------- - {1,4} - {1,2} - {1,3} -(3 rows) - -explain (costs off) -select x from (values (array[1, 2]), (array[1, 3])) _(x) intersect select x from (values (array[1, 2]), (array[1, 4])) _(x); - QUERY PLAN ------------------------------------------------ - HashSetOp Intersect - -> Append - -> Subquery Scan on "*SELECT* 1" - -> Values Scan on "*VALUES*" - -> Subquery Scan on "*SELECT* 2" - -> Values Scan on "*VALUES*_1" -(6 rows) - -select x from (values (array[1, 2]), (array[1, 3])) _(x) intersect select x from (values (array[1, 2]), (array[1, 4])) _(x); - x -------- - {1,2} -(1 row) - -explain (costs off) -select x from (values (array[1, 2]), (array[1, 3])) _(x) except select x from (values (array[1, 2]), (array[1, 4])) _(x); - QUERY PLAN ------------------------------------------------ - HashSetOp Except - -> Append - -> Subquery Scan on "*SELECT* 1" - -> Values Scan on "*VALUES*" - -> Subquery Scan on "*SELECT* 2" - -> Values Scan on "*VALUES*_1" -(6 rows) - -select x from (values (array[1, 2]), (array[1, 3])) _(x) except select x from (values (array[1, 2]), (array[1, 4])) _(x); - x -------- - {1,3} -(1 row) - --- non-hashable type -explain (costs off) -select x from (values (array['10'::varbit]), (array['11'::varbit])) _(x) union select x from (values (array['10'::varbit]), (array['01'::varbit])) _(x); - QUERY PLAN ------------------------------------------------ - Unique - -> Sort - Sort Key: "*VALUES*".column1 - -> Append - -> Values Scan on "*VALUES*" - -> Values Scan on "*VALUES*_1" -(6 rows) - -select x from (values (array['10'::varbit]), (array['11'::varbit])) _(x) union select x from (values (array['10'::varbit]), (array['01'::varbit])) _(x); - x ------- - {01} - {10} - {11} -(3 rows) - -set enable_hashagg to off; -explain (costs off) -select x from (values (array[1, 2]), (array[1, 3])) _(x) union select x from (values (array[1, 2]), (array[1, 4])) _(x); - QUERY PLAN ------------------------------------------------ - Unique - -> Sort - Sort Key: "*VALUES*".column1 - -> Append - -> Values Scan on "*VALUES*" - -> Values Scan on "*VALUES*_1" -(6 rows) - -select x from (values (array[1, 2]), (array[1, 3])) _(x) union select x from (values (array[1, 2]), (array[1, 4])) _(x); - x -------- - {1,2} - {1,3} - {1,4} -(3 rows) - -explain (costs off) -select x from (values (array[1, 2]), (array[1, 3])) _(x) intersect select x from (values (array[1, 2]), (array[1, 4])) _(x); - QUERY PLAN ------------------------------------------------------ - SetOp Intersect - -> Sort - Sort Key: "*SELECT* 1".x - -> Append - -> Subquery Scan on "*SELECT* 1" - -> Values Scan on "*VALUES*" - -> Subquery Scan on "*SELECT* 2" - -> Values Scan on "*VALUES*_1" -(8 rows) - -select x from (values (array[1, 2]), (array[1, 3])) _(x) intersect select x from (values (array[1, 2]), (array[1, 4])) _(x); - x -------- - {1,2} -(1 row) - -explain (costs off) -select x from (values (array[1, 2]), (array[1, 3])) _(x) except select x from (values (array[1, 2]), (array[1, 4])) _(x); - QUERY PLAN ------------------------------------------------------ - SetOp Except - -> Sort - Sort Key: "*SELECT* 1".x - -> Append - -> Subquery Scan on "*SELECT* 1" - -> Values Scan on "*VALUES*" - -> Subquery Scan on "*SELECT* 2" - -> Values Scan on "*VALUES*_1" -(8 rows) - -select x from (values (array[1, 2]), (array[1, 3])) _(x) except select x from (values (array[1, 2]), (array[1, 4])) _(x); - x -------- - {1,3} -(1 row) - -reset enable_hashagg; --- records -set enable_hashagg to on; -explain (costs off) -select x from (values (row(1, 2)), (row(1, 3))) _(x) union select x from (values (row(1, 2)), (row(1, 4))) _(x); - QUERY PLAN ------------------------------------------------ - Unique - -> Sort - Sort Key: "*VALUES*".column1 - -> Append - -> Values Scan on "*VALUES*" - -> Values Scan on "*VALUES*_1" -(6 rows) - -select x from (values (row(1, 2)), (row(1, 3))) _(x) union select x from (values (row(1, 2)), (row(1, 4))) _(x); - x -------- - (1,2) - (1,3) - (1,4) -(3 rows) - -explain (costs off) -select x from (values (row(1, 2)), (row(1, 3))) _(x) intersect select x from (values (row(1, 2)), (row(1, 4))) _(x); - QUERY PLAN ------------------------------------------------------ - SetOp Intersect - -> Sort - Sort Key: "*SELECT* 1".x - -> Append - -> Subquery Scan on "*SELECT* 1" - -> Values Scan on "*VALUES*" - -> Subquery Scan on "*SELECT* 2" - -> Values Scan on "*VALUES*_1" -(8 rows) - -select x from (values (row(1, 2)), (row(1, 3))) _(x) intersect select x from (values (row(1, 2)), (row(1, 4))) _(x); - x -------- - (1,2) -(1 row) - -explain (costs off) -select x from (values (row(1, 2)), (row(1, 3))) _(x) except select x from (values (row(1, 2)), (row(1, 4))) _(x); - QUERY PLAN ------------------------------------------------------ - SetOp Except - -> Sort - Sort Key: "*SELECT* 1".x - -> Append - -> Subquery Scan on "*SELECT* 1" - -> Values Scan on "*VALUES*" - -> Subquery Scan on "*SELECT* 2" - -> Values Scan on "*VALUES*_1" -(8 rows) - -select x from (values (row(1, 2)), (row(1, 3))) _(x) except select x from (values (row(1, 2)), (row(1, 4))) _(x); - x -------- - (1,3) -(1 row) - --- non-hashable type --- With an anonymous row type, the typcache does not report that the --- type is hashable. (Otherwise, this would fail at execution time.) -explain (costs off) -select x from (values (row('10'::varbit)), (row('11'::varbit))) _(x) union select x from (values (row('10'::varbit)), (row('01'::varbit))) _(x); - QUERY PLAN ------------------------------------------------ - Unique - -> Sort - Sort Key: "*VALUES*".column1 - -> Append - -> Values Scan on "*VALUES*" - -> Values Scan on "*VALUES*_1" -(6 rows) - -select x from (values (row('10'::varbit)), (row('11'::varbit))) _(x) union select x from (values (row('10'::varbit)), (row('01'::varbit))) _(x); - x ------- - (01) - (10) - (11) -(3 rows) - --- With a defined row type, the typcache can inspect the type's fields --- for hashability. -create type ct1 as (f1 varbit); -explain (costs off) -select x from (values (row('10'::varbit)::ct1), (row('11'::varbit)::ct1)) _(x) union select x from (values (row('10'::varbit)::ct1), (row('01'::varbit)::ct1)) _(x); - QUERY PLAN ------------------------------------------------ - Unique - -> Sort - Sort Key: "*VALUES*".column1 - -> Append - -> Values Scan on "*VALUES*" - -> Values Scan on "*VALUES*_1" -(6 rows) - -select x from (values (row('10'::varbit)::ct1), (row('11'::varbit)::ct1)) _(x) union select x from (values (row('10'::varbit)::ct1), (row('01'::varbit)::ct1)) _(x); - x ------- - (01) - (10) - (11) -(3 rows) - -drop type ct1; -set enable_hashagg to off; -explain (costs off) -select x from (values (row(1, 2)), (row(1, 3))) _(x) union select x from (values (row(1, 2)), (row(1, 4))) _(x); - QUERY PLAN ------------------------------------------------ - Unique - -> Sort - Sort Key: "*VALUES*".column1 - -> Append - -> Values Scan on "*VALUES*" - -> Values Scan on "*VALUES*_1" -(6 rows) - -select x from (values (row(1, 2)), (row(1, 3))) _(x) union select x from (values (row(1, 2)), (row(1, 4))) _(x); - x -------- - (1,2) - (1,3) - (1,4) -(3 rows) - -explain (costs off) -select x from (values (row(1, 2)), (row(1, 3))) _(x) intersect select x from (values (row(1, 2)), (row(1, 4))) _(x); - QUERY PLAN ------------------------------------------------------ - SetOp Intersect - -> Sort - Sort Key: "*SELECT* 1".x - -> Append - -> Subquery Scan on "*SELECT* 1" - -> Values Scan on "*VALUES*" - -> Subquery Scan on "*SELECT* 2" - -> Values Scan on "*VALUES*_1" -(8 rows) - -select x from (values (row(1, 2)), (row(1, 3))) _(x) intersect select x from (values (row(1, 2)), (row(1, 4))) _(x); - x -------- - (1,2) -(1 row) - -explain (costs off) -select x from (values (row(1, 2)), (row(1, 3))) _(x) except select x from (values (row(1, 2)), (row(1, 4))) _(x); - QUERY PLAN ------------------------------------------------------ - SetOp Except - -> Sort - Sort Key: "*SELECT* 1".x - -> Append - -> Subquery Scan on "*SELECT* 1" - -> Values Scan on "*VALUES*" - -> Subquery Scan on "*SELECT* 2" - -> Values Scan on "*VALUES*_1" -(8 rows) - -select x from (values (row(1, 2)), (row(1, 3))) _(x) except select x from (values (row(1, 2)), (row(1, 4))) _(x); - x -------- - (1,3) -(1 row) - --- non-sortable type --- Ensure we get a HashAggregate plan. Keep enable_hashagg=off to ensure --- there's no chance of a sort. -explain (costs off) select '123'::xid union select '123'::xid; - QUERY PLAN ---------------------------- - HashAggregate - Disabled Nodes: 1 - Group Key: ('123'::xid) - -> Append - -> Result - -> Result -(6 rows) - -reset enable_hashagg; --- --- Mixed types --- -SELECT f1 FROM float8_tbl INTERSECT SELECT f1 FROM int4_tbl ORDER BY 1; - f1 ----- - 0 -(1 row) - -SELECT f1 FROM float8_tbl EXCEPT SELECT f1 FROM int4_tbl ORDER BY 1; - f1 ------------------------ - -1.2345678901234e+200 - -1004.3 - -34.84 - -1.2345678901234e-200 -(4 rows) - --- --- Operator precedence and (((((extra))))) parentheses --- -SELECT q1 FROM int8_tbl INTERSECT SELECT q2 FROM int8_tbl UNION ALL SELECT q2 FROM int8_tbl ORDER BY 1; - q1 -------------------- - -4567890123456789 - 123 - 123 - 456 - 4567890123456789 - 4567890123456789 - 4567890123456789 -(7 rows) - -SELECT q1 FROM int8_tbl INTERSECT (((SELECT q2 FROM int8_tbl UNION ALL SELECT q2 FROM int8_tbl))) ORDER BY 1; - q1 ------------------- - 123 - 4567890123456789 -(2 rows) - -(((SELECT q1 FROM int8_tbl INTERSECT SELECT q2 FROM int8_tbl ORDER BY 1))) UNION ALL SELECT q2 FROM int8_tbl; - q1 -------------------- - 123 - 4567890123456789 - 456 - 4567890123456789 - 123 - 4567890123456789 - -4567890123456789 -(7 rows) - -SELECT q1 FROM int8_tbl UNION ALL SELECT q2 FROM int8_tbl EXCEPT SELECT q1 FROM int8_tbl ORDER BY 1; - q1 -------------------- - -4567890123456789 - 456 -(2 rows) - -SELECT q1 FROM int8_tbl UNION ALL (((SELECT q2 FROM int8_tbl EXCEPT SELECT q1 FROM int8_tbl ORDER BY 1))); - q1 -------------------- - 123 - 123 - 4567890123456789 - 4567890123456789 - 4567890123456789 - -4567890123456789 - 456 -(7 rows) - -(((SELECT q1 FROM int8_tbl UNION ALL SELECT q2 FROM int8_tbl))) EXCEPT SELECT q1 FROM int8_tbl ORDER BY 1; - q1 -------------------- - -4567890123456789 - 456 -(2 rows) - --- --- Subqueries with ORDER BY & LIMIT clauses --- --- In this syntax, ORDER BY/LIMIT apply to the result of the EXCEPT -SELECT q1,q2 FROM int8_tbl EXCEPT SELECT q2,q1 FROM int8_tbl -ORDER BY q2,q1; - q1 | q2 -------------------+------------------- - 4567890123456789 | -4567890123456789 - 123 | 456 -(2 rows) - --- This should fail, because q2 isn't a name of an EXCEPT output column -SELECT q1 FROM int8_tbl EXCEPT SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1; -ERROR: column "q2" does not exist -LINE 1: ... int8_tbl EXCEPT SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1... - ^ -DETAIL: There is a column named "q2" in table "*SELECT* 2", but it cannot be referenced from this part of the query. --- But this should work: -SELECT q1 FROM int8_tbl EXCEPT (((SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1))) ORDER BY 1; - q1 ------------------- - 123 - 4567890123456789 -(2 rows) - --- --- New syntaxes (7.1) permit new tests --- -(((((select * from int8_tbl))))); - q1 | q2 -------------------+------------------- - 123 | 456 - 123 | 4567890123456789 - 4567890123456789 | 123 - 4567890123456789 | 4567890123456789 - 4567890123456789 | -4567890123456789 -(5 rows) - --- --- Check behavior with empty select list (allowed since 9.4) --- -select union select; --- -(1 row) - -select intersect select; --- -(1 row) - -select except select; --- -(0 rows) - --- check hashed implementation -set enable_hashagg = true; -set enable_sort = false; --- We've no way to check hashed UNION as the empty pathkeys in the Append are --- fine to make use of Unique, which is cheaper than HashAggregate and we've --- no means to disable Unique. -explain (costs off) -select from generate_series(1,5) intersect select from generate_series(1,3); - QUERY PLAN ----------------------------------------------------------------------- - HashSetOp Intersect - -> Append - -> Subquery Scan on "*SELECT* 1" - -> Function Scan on generate_series - -> Subquery Scan on "*SELECT* 2" - -> Function Scan on generate_series generate_series_1 -(6 rows) - -select from generate_series(1,5) union all select from generate_series(1,3); --- -(8 rows) - -select from generate_series(1,5) intersect select from generate_series(1,3); --- -(1 row) - -select from generate_series(1,5) intersect all select from generate_series(1,3); --- -(3 rows) - -select from generate_series(1,5) except select from generate_series(1,3); --- -(0 rows) - -select from generate_series(1,5) except all select from generate_series(1,3); --- -(2 rows) - --- check sorted implementation -set enable_hashagg = false; -set enable_sort = true; -explain (costs off) -select from generate_series(1,5) union select from generate_series(1,3); - QUERY PLAN ----------------------------------------------------------------- - Unique - -> Append - -> Function Scan on generate_series - -> Function Scan on generate_series generate_series_1 -(4 rows) - -explain (costs off) -select from generate_series(1,5) intersect select from generate_series(1,3); - QUERY PLAN ----------------------------------------------------------------------- - SetOp Intersect - -> Append - -> Subquery Scan on "*SELECT* 1" - -> Function Scan on generate_series - -> Subquery Scan on "*SELECT* 2" - -> Function Scan on generate_series generate_series_1 -(6 rows) - -select from generate_series(1,5) union select from generate_series(1,3); --- -(1 row) - -select from generate_series(1,5) union all select from generate_series(1,3); --- -(8 rows) - -select from generate_series(1,5) intersect select from generate_series(1,3); --- -(1 row) - -select from generate_series(1,5) intersect all select from generate_series(1,3); --- -(3 rows) - -select from generate_series(1,5) except select from generate_series(1,3); --- -(0 rows) - -select from generate_series(1,5) except all select from generate_series(1,3); --- -(2 rows) - --- Try a variation of the above but with a CTE which contains a column, again --- with an empty final select list. --- Ensure we get the expected 1 row with 0 columns -with cte as materialized (select s from generate_series(1,5) s) -select from cte union select from cte; --- -(1 row) - --- Ensure we get the same result as the above. -with cte as not materialized (select s from generate_series(1,5) s) -select from cte union select from cte; --- -(1 row) - -reset enable_hashagg; -reset enable_sort; --- --- Check handling of a case with unknown constants. We don't guarantee --- an undecorated constant will work in all cases, but historically this --- usage has worked, so test we don't break it. --- -SELECT a.f1 FROM (SELECT 'test' AS f1 FROM varchar_tbl) a -UNION -SELECT b.f1 FROM (SELECT f1 FROM varchar_tbl) b -ORDER BY 1; - f1 ------- - a - ab - abcd - test -(4 rows) - --- This should fail, but it should produce an error cursor -SELECT '3.4'::numeric UNION SELECT 'foo'; -ERROR: invalid input syntax for type numeric: "foo" -LINE 1: SELECT '3.4'::numeric UNION SELECT 'foo'; - ^ --- --- Test that expression-index constraints can be pushed down through --- UNION or UNION ALL --- -CREATE TEMP TABLE t1 (a text, b text); -CREATE INDEX t1_ab_idx on t1 ((a || b)); -CREATE TEMP TABLE t2 (ab text primary key); -INSERT INTO t1 VALUES ('a', 'b'), ('x', 'y'); -INSERT INTO t2 VALUES ('ab'), ('xy'); -set enable_seqscan = off; -set enable_indexscan = on; -set enable_bitmapscan = off; -set enable_sort = off; -explain (costs off) - SELECT * FROM - (SELECT a || b AS ab FROM t1 - UNION ALL - SELECT * FROM t2) t - WHERE ab = 'ab'; - QUERY PLAN ---------------------------------------------- - Append - -> Index Scan using t1_ab_idx on t1 - Index Cond: ((a || b) = 'ab'::text) - -> Index Only Scan using t2_pkey on t2 - Index Cond: (ab = 'ab'::text) -(5 rows) - -explain (costs off) - SELECT * FROM - (SELECT a || b AS ab FROM t1 - UNION - SELECT * FROM t2) t - WHERE ab = 'ab'; - QUERY PLAN ---------------------------------------------------- - HashAggregate - Group Key: ((t1.a || t1.b)) - -> Append - -> Index Scan using t1_ab_idx on t1 - Index Cond: ((a || b) = 'ab'::text) - -> Index Only Scan using t2_pkey on t2 - Index Cond: (ab = 'ab'::text) -(7 rows) - --- --- Test that ORDER BY for UNION ALL can be pushed down to inheritance --- children. --- -CREATE TEMP TABLE t1c (b text, a text); -ALTER TABLE t1c INHERIT t1; -CREATE TEMP TABLE t2c (primary key (ab)) INHERITS (t2); -INSERT INTO t1c VALUES ('v', 'w'), ('c', 'd'), ('m', 'n'), ('e', 'f'); -INSERT INTO t2c VALUES ('vw'), ('cd'), ('mn'), ('ef'); -CREATE INDEX t1c_ab_idx on t1c ((a || b)); -set enable_seqscan = on; -set enable_indexonlyscan = off; -explain (costs off) - SELECT * FROM - (SELECT a || b AS ab FROM t1 - UNION ALL - SELECT ab FROM t2) t - ORDER BY 1 LIMIT 8; - QUERY PLAN ------------------------------------------------------ - Limit - -> Merge Append - Sort Key: ((t1.a || t1.b)) - -> Index Scan using t1_ab_idx on t1 - -> Index Scan using t1c_ab_idx on t1c t1_1 - -> Index Scan using t2_pkey on t2 - -> Index Scan using t2c_pkey on t2c t2_1 -(7 rows) - - SELECT * FROM - (SELECT a || b AS ab FROM t1 - UNION ALL - SELECT ab FROM t2) t - ORDER BY 1 LIMIT 8; - ab ----- - ab - ab - cd - dc - ef - fe - mn - nm -(8 rows) - -reset enable_seqscan; -reset enable_indexscan; -reset enable_bitmapscan; -reset enable_sort; --- This simpler variant of the above test has been observed to fail differently -create table events (event_id int primary key); -create table other_events (event_id int primary key); -create table events_child () inherits (events); -explain (costs off) -select event_id - from (select event_id from events - union all - select event_id from other_events) ss - order by event_id; - QUERY PLAN ----------------------------------------------------------- - Merge Append - Sort Key: events.event_id - -> Index Scan using events_pkey on events - -> Sort - Sort Key: events_1.event_id - -> Seq Scan on events_child events_1 - -> Index Scan using other_events_pkey on other_events -(7 rows) - -drop table events_child, events, other_events; -reset enable_indexonlyscan; --- Test constraint exclusion of UNION ALL subqueries -explain (costs off) - SELECT * FROM - (SELECT 1 AS t, * FROM tenk1 a - UNION ALL - SELECT 2 AS t, * FROM tenk1 b) c - WHERE t = 2; - QUERY PLAN ---------------------- - Seq Scan on tenk1 b -(1 row) - --- Test that we push quals into UNION sub-selects only when it's safe -explain (costs off) -SELECT * FROM - (SELECT 1 AS t, 2 AS x - UNION - SELECT 2 AS t, 4 AS x) ss -WHERE x < 4 -ORDER BY x; - QUERY PLAN --------------------------------------------------- - Sort - Sort Key: (2) - -> Unique - -> Sort - Sort Key: (1), (2) - -> Append - -> Result - -> Result - One-Time Filter: false -(9 rows) - -SELECT * FROM - (SELECT 1 AS t, 2 AS x - UNION - SELECT 2 AS t, 4 AS x) ss -WHERE x < 4 -ORDER BY x; - t | x ----+--- - 1 | 2 -(1 row) - -explain (costs off) -SELECT * FROM - (SELECT 1 AS t, generate_series(1,10) AS x - UNION - SELECT 2 AS t, 4 AS x) ss -WHERE x < 4 -ORDER BY x; - QUERY PLAN --------------------------------------------------------- - Sort - Sort Key: ss.x - -> Subquery Scan on ss - Filter: (ss.x < 4) - -> HashAggregate - Group Key: (1), (generate_series(1, 10)) - -> Append - -> ProjectSet - -> Result - -> Result -(10 rows) - -SELECT * FROM - (SELECT 1 AS t, generate_series(1,10) AS x - UNION - SELECT 2 AS t, 4 AS x) ss -WHERE x < 4 -ORDER BY x; - t | x ----+--- - 1 | 1 - 1 | 2 - 1 | 3 -(3 rows) - -explain (costs off) -SELECT * FROM - (SELECT 1 AS t, (random()*3)::int AS x - UNION - SELECT 2 AS t, 4 AS x) ss -WHERE x > 3 -ORDER BY x; - QUERY PLAN ------------------------------------------------------------------------------------- - Sort - Sort Key: ss.x - -> Subquery Scan on ss - Filter: (ss.x > 3) - -> Unique - -> Sort - Sort Key: (1), (((random() * '3'::double precision))::integer) - -> Append - -> Result - -> Result -(10 rows) - -SELECT * FROM - (SELECT 1 AS t, (random()*3)::int AS x - UNION - SELECT 2 AS t, 4 AS x) ss -WHERE x > 3 -ORDER BY x; - t | x ----+--- - 2 | 4 -(1 row) - --- Test cases where the native ordering of a sub-select has more pathkeys --- than the outer query cares about -explain (costs off) -select distinct q1 from - (select distinct * from int8_tbl i81 - union all - select distinct * from int8_tbl i82) ss -where q2 = q2; - QUERY PLAN ----------------------------------------------------------- - Unique - -> Merge Append - Sort Key: "*SELECT* 1".q1 - -> Subquery Scan on "*SELECT* 1" - -> Unique - -> Sort - Sort Key: i81.q1, i81.q2 - -> Seq Scan on int8_tbl i81 - Filter: (q2 IS NOT NULL) - -> Subquery Scan on "*SELECT* 2" - -> Unique - -> Sort - Sort Key: i82.q1, i82.q2 - -> Seq Scan on int8_tbl i82 - Filter: (q2 IS NOT NULL) -(15 rows) - -select distinct q1 from - (select distinct * from int8_tbl i81 - union all - select distinct * from int8_tbl i82) ss -where q2 = q2; - q1 ------------------- - 123 - 4567890123456789 -(2 rows) - -explain (costs off) -select distinct q1 from - (select distinct * from int8_tbl i81 - union all - select distinct * from int8_tbl i82) ss -where -q1 = q2; - QUERY PLAN --------------------------------------------------------- - Unique - -> Merge Append - Sort Key: "*SELECT* 1".q1 - -> Subquery Scan on "*SELECT* 1" - -> Unique - -> Sort - Sort Key: i81.q1, i81.q2 - -> Seq Scan on int8_tbl i81 - Filter: ((- q1) = q2) - -> Subquery Scan on "*SELECT* 2" - -> Unique - -> Sort - Sort Key: i82.q1, i82.q2 - -> Seq Scan on int8_tbl i82 - Filter: ((- q1) = q2) -(15 rows) - -select distinct q1 from - (select distinct * from int8_tbl i81 - union all - select distinct * from int8_tbl i82) ss -where -q1 = q2; - q1 ------------------- - 4567890123456789 -(1 row) - --- Test proper handling of parameterized appendrel paths when the --- potential join qual is expensive -create function expensivefunc(int) returns int -language plpgsql immutable strict cost 10000 -as $$begin return $1; end$$; -create temp table t3 as select generate_series(-1000,1000) as x; -create index t3i on t3 (expensivefunc(x)); -analyze t3; -explain (costs off) -select * from - (select * from t3 a union all select * from t3 b) ss - join int4_tbl on f1 = expensivefunc(x); - QUERY PLAN ------------------------------------------------------------- - Nested Loop - -> Seq Scan on int4_tbl - -> Append - -> Index Scan using t3i on t3 a - Index Cond: (expensivefunc(x) = int4_tbl.f1) - -> Index Scan using t3i on t3 b - Index Cond: (expensivefunc(x) = int4_tbl.f1) -(7 rows) - -select * from - (select * from t3 a union all select * from t3 b) ss - join int4_tbl on f1 = expensivefunc(x); - x | f1 ----+---- - 0 | 0 - 0 | 0 -(2 rows) - -drop table t3; -drop function expensivefunc(int); --- Test handling of appendrel quals that const-simplify into an AND -explain (costs off) -select * from - (select *, 0 as x from int8_tbl a - union all - select *, 1 as x from int8_tbl b) ss -where (x = 0) or (q1 >= q2 and q1 <= q2); - QUERY PLAN ---------------------------------------------- - Append - -> Seq Scan on int8_tbl a - -> Seq Scan on int8_tbl b - Filter: ((q1 >= q2) AND (q1 <= q2)) -(4 rows) - -select * from - (select *, 0 as x from int8_tbl a - union all - select *, 1 as x from int8_tbl b) ss -where (x = 0) or (q1 >= q2 and q1 <= q2); - q1 | q2 | x -------------------+-------------------+--- - 123 | 456 | 0 - 123 | 4567890123456789 | 0 - 4567890123456789 | 123 | 0 - 4567890123456789 | 4567890123456789 | 0 - 4567890123456789 | -4567890123456789 | 0 - 4567890123456789 | 4567890123456789 | 1 -(6 rows) - --- --- Test the planner's ability to produce cheap startup plans with Append nodes --- --- Ensure we get a Nested Loop join between tenk1 and tenk2 -explain (costs off) -select t1.unique1 from tenk1 t1 -inner join tenk2 t2 on t1.tenthous = t2.tenthous and t2.thousand = 0 - union all -(values(1)) limit 1; - QUERY PLAN --------------------------------------------------------- - Limit - -> Append - -> Nested Loop - Join Filter: (t1.tenthous = t2.tenthous) - -> Seq Scan on tenk1 t1 - -> Materialize - -> Seq Scan on tenk2 t2 - Filter: (thousand = 0) - -> Result -(9 rows) - --- Ensure there is no problem if cheapest_startup_path is NULL -explain (costs off) -select * from tenk1 t1 -left join lateral - (select t1.tenthous from tenk2 t2 union all (values(1))) -on true limit 1; - QUERY PLAN -------------------------------------------------------------------- - Limit - -> Nested Loop Left Join - -> Seq Scan on tenk1 t1 - -> Append - -> Index Only Scan using tenk2_hundred on tenk2 t2 - -> Result -(6 rows) - +psql: error: connection to server on socket "/tmp/pg_regress-03Az7d/.s.PGSQL.40051" failed: FATAL: the database system is not yet accepting connections +DETAIL: Consistent recovery state has not been yet reached. diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/case.out /tmp/cirrus-ci-build/build/testrun/regress/regress/results/case.out --- /tmp/cirrus-ci-build/src/test/regress/expected/case.out 2024-09-20 21:41:45.670024520 +0000 +++ /tmp/cirrus-ci-build/build/testrun/regress/regress/results/case.out 2024-09-20 21:46:05.477625108 +0000 @@ -1,419 +1,2 @@ --- --- CASE --- Test the case statement --- -CREATE TABLE CASE_TBL ( - i integer, - f double precision -); -CREATE TABLE CASE2_TBL ( - i integer, - j integer -); -INSERT INTO CASE_TBL VALUES (1, 10.1); -INSERT INTO CASE_TBL VALUES (2, 20.2); -INSERT INTO CASE_TBL VALUES (3, -30.3); -INSERT INTO CASE_TBL VALUES (4, NULL); -INSERT INTO CASE2_TBL VALUES (1, -1); -INSERT INTO CASE2_TBL VALUES (2, -2); -INSERT INTO CASE2_TBL VALUES (3, -3); -INSERT INTO CASE2_TBL VALUES (2, -4); -INSERT INTO CASE2_TBL VALUES (1, NULL); -INSERT INTO CASE2_TBL VALUES (NULL, -6); --- --- Simplest examples without tables --- -SELECT '3' AS "One", - CASE - WHEN 1 < 2 THEN 3 - END AS "Simple WHEN"; - One | Simple WHEN ------+------------- - 3 | 3 -(1 row) - -SELECT '' AS "One", - CASE - WHEN 1 > 2 THEN 3 - END AS "Simple default"; - One | Simple default ---------+---------------- - | -(1 row) - -SELECT '3' AS "One", - CASE - WHEN 1 < 2 THEN 3 - ELSE 4 - END AS "Simple ELSE"; - One | Simple ELSE ------+------------- - 3 | 3 -(1 row) - -SELECT '4' AS "One", - CASE - WHEN 1 > 2 THEN 3 - ELSE 4 - END AS "ELSE default"; - One | ELSE default ------+-------------- - 4 | 4 -(1 row) - -SELECT '6' AS "One", - CASE - WHEN 1 > 2 THEN 3 - WHEN 4 < 5 THEN 6 - ELSE 7 - END AS "Two WHEN with default"; - One | Two WHEN with default ------+----------------------- - 6 | 6 -(1 row) - -SELECT '7' AS "None", - CASE WHEN random() < 0 THEN 1 - END AS "NULL on no matches"; - None | NULL on no matches -------+-------------------- - 7 | -(1 row) - --- Constant-expression folding shouldn't evaluate unreachable subexpressions -SELECT CASE WHEN 1=0 THEN 1/0 WHEN 1=1 THEN 1 ELSE 2/0 END; - case ------- - 1 -(1 row) - -SELECT CASE 1 WHEN 0 THEN 1/0 WHEN 1 THEN 1 ELSE 2/0 END; - case ------- - 1 -(1 row) - --- However we do not currently suppress folding of potentially --- reachable subexpressions -SELECT CASE WHEN i > 100 THEN 1/0 ELSE 0 END FROM case_tbl; -ERROR: division by zero --- Test for cases involving untyped literals in test expression -SELECT CASE 'a' WHEN 'a' THEN 1 ELSE 2 END; - case ------- - 1 -(1 row) - --- --- Examples of targets involving tables --- -SELECT - CASE - WHEN i >= 3 THEN i - END AS ">= 3 or Null" - FROM CASE_TBL; - >= 3 or Null --------------- - - - 3 - 4 -(4 rows) - -SELECT - CASE WHEN i >= 3 THEN (i + i) - ELSE i - END AS "Simplest Math" - FROM CASE_TBL; - Simplest Math ---------------- - 1 - 2 - 6 - 8 -(4 rows) - -SELECT i AS "Value", - CASE WHEN (i < 0) THEN 'small' - WHEN (i = 0) THEN 'zero' - WHEN (i = 1) THEN 'one' - WHEN (i = 2) THEN 'two' - ELSE 'big' - END AS "Category" - FROM CASE_TBL; - Value | Category --------+---------- - 1 | one - 2 | two - 3 | big - 4 | big -(4 rows) - -SELECT - CASE WHEN ((i < 0) or (i < 0)) THEN 'small' - WHEN ((i = 0) or (i = 0)) THEN 'zero' - WHEN ((i = 1) or (i = 1)) THEN 'one' - WHEN ((i = 2) or (i = 2)) THEN 'two' - ELSE 'big' - END AS "Category" - FROM CASE_TBL; - Category ----------- - one - two - big - big -(4 rows) - --- --- Examples of qualifications involving tables --- --- --- NULLIF() and COALESCE() --- Shorthand forms for typical CASE constructs --- defined in the SQL standard. --- -SELECT * FROM CASE_TBL WHERE COALESCE(f,i) = 4; - i | f ----+--- - 4 | -(1 row) - -SELECT * FROM CASE_TBL WHERE NULLIF(f,i) = 2; - i | f ----+--- -(0 rows) - -SELECT COALESCE(a.f, b.i, b.j) - FROM CASE_TBL a, CASE2_TBL b; - coalesce ----------- - 10.1 - 20.2 - -30.3 - 1 - 10.1 - 20.2 - -30.3 - 2 - 10.1 - 20.2 - -30.3 - 3 - 10.1 - 20.2 - -30.3 - 2 - 10.1 - 20.2 - -30.3 - 1 - 10.1 - 20.2 - -30.3 - -6 -(24 rows) - -SELECT * - FROM CASE_TBL a, CASE2_TBL b - WHERE COALESCE(a.f, b.i, b.j) = 2; - i | f | i | j ----+---+---+---- - 4 | | 2 | -2 - 4 | | 2 | -4 -(2 rows) - -SELECT NULLIF(a.i,b.i) AS "NULLIF(a.i,b.i)", - NULLIF(b.i, 4) AS "NULLIF(b.i,4)" - FROM CASE_TBL a, CASE2_TBL b; - NULLIF(a.i,b.i) | NULLIF(b.i,4) ------------------+--------------- - | 1 - 2 | 1 - 3 | 1 - 4 | 1 - 1 | 2 - | 2 - 3 | 2 - 4 | 2 - 1 | 3 - 2 | 3 - | 3 - 4 | 3 - 1 | 2 - | 2 - 3 | 2 - 4 | 2 - | 1 - 2 | 1 - 3 | 1 - 4 | 1 - 1 | - 2 | - 3 | - 4 | -(24 rows) - -SELECT * - FROM CASE_TBL a, CASE2_TBL b - WHERE COALESCE(f,b.i) = 2; - i | f | i | j ----+---+---+---- - 4 | | 2 | -2 - 4 | | 2 | -4 -(2 rows) - --- Tests for constant subexpression simplification -explain (costs off) -SELECT * FROM CASE_TBL WHERE NULLIF(1, 2) = 2; - QUERY PLAN --------------------------- - Result - One-Time Filter: false -(2 rows) - -explain (costs off) -SELECT * FROM CASE_TBL WHERE NULLIF(1, 1) IS NOT NULL; - QUERY PLAN --------------------------- - Result - One-Time Filter: false -(2 rows) - -explain (costs off) -SELECT * FROM CASE_TBL WHERE NULLIF(1, null) = 2; - QUERY PLAN --------------------------- - Result - One-Time Filter: false -(2 rows) - --- --- Examples of updates involving tables --- -UPDATE CASE_TBL - SET i = CASE WHEN i >= 3 THEN (- i) - ELSE (2 * i) END; -SELECT * FROM CASE_TBL; - i | f -----+------- - 2 | 10.1 - 4 | 20.2 - -3 | -30.3 - -4 | -(4 rows) - -UPDATE CASE_TBL - SET i = CASE WHEN i >= 2 THEN (2 * i) - ELSE (3 * i) END; -SELECT * FROM CASE_TBL; - i | f ------+------- - 4 | 10.1 - 8 | 20.2 - -9 | -30.3 - -12 | -(4 rows) - -UPDATE CASE_TBL - SET i = CASE WHEN b.i >= 2 THEN (2 * j) - ELSE (3 * j) END - FROM CASE2_TBL b - WHERE j = -CASE_TBL.i; -SELECT * FROM CASE_TBL; - i | f ------+------- - 8 | 20.2 - -9 | -30.3 - -12 | - -8 | 10.1 -(4 rows) - --- --- Nested CASE expressions --- --- This test exercises a bug caused by aliasing econtext->caseValue_isNull --- with the isNull argument of the inner CASE's CaseExpr evaluation. After --- evaluating the vol(null) expression in the inner CASE's second WHEN-clause, --- the isNull flag for the case test value incorrectly became true, causing --- the third WHEN-clause not to match. The volatile function calls are needed --- to prevent constant-folding in the planner, which would hide the bug. --- Wrap this in a single transaction so the transient '=' operator doesn't --- cause problems in concurrent sessions -BEGIN; -CREATE FUNCTION vol(text) returns text as - 'begin return $1; end' language plpgsql volatile; -SELECT CASE - (CASE vol('bar') - WHEN 'foo' THEN 'it was foo!' - WHEN vol(null) THEN 'null input' - WHEN 'bar' THEN 'it was bar!' END - ) - WHEN 'it was foo!' THEN 'foo recognized' - WHEN 'it was bar!' THEN 'bar recognized' - ELSE 'unrecognized' END; - case ----------------- - bar recognized -(1 row) - --- In this case, we can't inline the SQL function without confusing things. -CREATE DOMAIN foodomain AS text; -CREATE FUNCTION volfoo(text) returns foodomain as - 'begin return $1::foodomain; end' language plpgsql volatile; -CREATE FUNCTION inline_eq(foodomain, foodomain) returns boolean as - 'SELECT CASE $2::text WHEN $1::text THEN true ELSE false END' language sql; -CREATE OPERATOR = (procedure = inline_eq, - leftarg = foodomain, rightarg = foodomain); -SELECT CASE volfoo('bar') WHEN 'foo'::foodomain THEN 'is foo' ELSE 'is not foo' END; - case ------------- - is not foo -(1 row) - -ROLLBACK; --- Test multiple evaluation of a CASE arg that is a read/write object (#14472) --- Wrap this in a single transaction so the transient '=' operator doesn't --- cause problems in concurrent sessions -BEGIN; -CREATE DOMAIN arrdomain AS int[]; -CREATE FUNCTION make_ad(int,int) returns arrdomain as - 'declare x arrdomain; - begin - x := array[$1,$2]; - return x; - end' language plpgsql volatile; -CREATE FUNCTION ad_eq(arrdomain, arrdomain) returns boolean as - 'begin return array_eq($1, $2); end' language plpgsql; -CREATE OPERATOR = (procedure = ad_eq, - leftarg = arrdomain, rightarg = arrdomain); -SELECT CASE make_ad(1,2) - WHEN array[2,4]::arrdomain THEN 'wrong' - WHEN array[2,5]::arrdomain THEN 'still wrong' - WHEN array[1,2]::arrdomain THEN 'right' - END; - case -------- - right -(1 row) - -ROLLBACK; --- Test interaction of CASE with ArrayCoerceExpr (bug #15471) -BEGIN; -CREATE TYPE casetestenum AS ENUM ('e', 'f', 'g'); -SELECT - CASE 'foo'::text - WHEN 'foo' THEN ARRAY['a', 'b', 'c', 'd'] || enum_range(NULL::casetestenum)::text[] - ELSE ARRAY['x', 'y'] - END; - array ------------------ - {a,b,c,d,e,f,g} -(1 row) - -ROLLBACK; --- --- Clean up --- -DROP TABLE CASE_TBL; -DROP TABLE CASE2_TBL; +psql: error: connection to server on socket "/tmp/pg_regress-03Az7d/.s.PGSQL.40051" failed: FATAL: the database system is not yet accepting connections +DETAIL: Consistent recovery state has not been yet reached. diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/join.out /tmp/cirrus-ci-build/build/testrun/regress/regress/results/join.out --- /tmp/cirrus-ci-build/src/test/regress/expected/join.out 2024-09-20 21:41:45.702024522 +0000 +++ /tmp/cirrus-ci-build/build/testrun/regress/regress/results/join.out 2024-09-20 21:46:05.473625113 +0000 @@ -1,8019 +1,2 @@ --- --- JOIN --- Test JOIN clauses --- -CREATE TABLE J1_TBL ( - i integer, - j integer, - t text -); -CREATE TABLE J2_TBL ( - i integer, - k integer -); -INSERT INTO J1_TBL VALUES (1, 4, 'one'); -INSERT INTO J1_TBL VALUES (2, 3, 'two'); -INSERT INTO J1_TBL VALUES (3, 2, 'three'); -INSERT INTO J1_TBL VALUES (4, 1, 'four'); -INSERT INTO J1_TBL VALUES (5, 0, 'five'); -INSERT INTO J1_TBL VALUES (6, 6, 'six'); -INSERT INTO J1_TBL VALUES (7, 7, 'seven'); -INSERT INTO J1_TBL VALUES (8, 8, 'eight'); -INSERT INTO J1_TBL VALUES (0, NULL, 'zero'); -INSERT INTO J1_TBL VALUES (NULL, NULL, 'null'); -INSERT INTO J1_TBL VALUES (NULL, 0, 'zero'); -INSERT INTO J2_TBL VALUES (1, -1); -INSERT INTO J2_TBL VALUES (2, 2); -INSERT INTO J2_TBL VALUES (3, -3); -INSERT INTO J2_TBL VALUES (2, 4); -INSERT INTO J2_TBL VALUES (5, -5); -INSERT INTO J2_TBL VALUES (5, -5); -INSERT INTO J2_TBL VALUES (0, NULL); -INSERT INTO J2_TBL VALUES (NULL, NULL); -INSERT INTO J2_TBL VALUES (NULL, 0); --- useful in some tests below -create temp table onerow(); -insert into onerow default values; -analyze onerow; --- --- CORRELATION NAMES --- Make sure that table/column aliases are supported --- before diving into more complex join syntax. --- -SELECT * - FROM J1_TBL AS tx; - i | j | t ----+---+------- - 1 | 4 | one - 2 | 3 | two - 3 | 2 | three - 4 | 1 | four - 5 | 0 | five - 6 | 6 | six - 7 | 7 | seven - 8 | 8 | eight - 0 | | zero - | | null - | 0 | zero -(11 rows) - -SELECT * - FROM J1_TBL tx; - i | j | t ----+---+------- - 1 | 4 | one - 2 | 3 | two - 3 | 2 | three - 4 | 1 | four - 5 | 0 | five - 6 | 6 | six - 7 | 7 | seven - 8 | 8 | eight - 0 | | zero - | | null - | 0 | zero -(11 rows) - -SELECT * - FROM J1_TBL AS t1 (a, b, c); - a | b | c ----+---+------- - 1 | 4 | one - 2 | 3 | two - 3 | 2 | three - 4 | 1 | four - 5 | 0 | five - 6 | 6 | six - 7 | 7 | seven - 8 | 8 | eight - 0 | | zero - | | null - | 0 | zero -(11 rows) - -SELECT * - FROM J1_TBL t1 (a, b, c); - a | b | c ----+---+------- - 1 | 4 | one - 2 | 3 | two - 3 | 2 | three - 4 | 1 | four - 5 | 0 | five - 6 | 6 | six - 7 | 7 | seven - 8 | 8 | eight - 0 | | zero - | | null - | 0 | zero -(11 rows) - -SELECT * - FROM J1_TBL t1 (a, b, c), J2_TBL t2 (d, e); - a | b | c | d | e ----+---+-------+---+---- - 1 | 4 | one | 1 | -1 - 2 | 3 | two | 1 | -1 - 3 | 2 | three | 1 | -1 - 4 | 1 | four | 1 | -1 - 5 | 0 | five | 1 | -1 - 6 | 6 | six | 1 | -1 - 7 | 7 | seven | 1 | -1 - 8 | 8 | eight | 1 | -1 - 0 | | zero | 1 | -1 - | | null | 1 | -1 - | 0 | zero | 1 | -1 - 1 | 4 | one | 2 | 2 - 2 | 3 | two | 2 | 2 - 3 | 2 | three | 2 | 2 - 4 | 1 | four | 2 | 2 - 5 | 0 | five | 2 | 2 - 6 | 6 | six | 2 | 2 - 7 | 7 | seven | 2 | 2 - 8 | 8 | eight | 2 | 2 - 0 | | zero | 2 | 2 - | | null | 2 | 2 - | 0 | zero | 2 | 2 - 1 | 4 | one | 3 | -3 - 2 | 3 | two | 3 | -3 - 3 | 2 | three | 3 | -3 - 4 | 1 | four | 3 | -3 - 5 | 0 | five | 3 | -3 - 6 | 6 | six | 3 | -3 - 7 | 7 | seven | 3 | -3 - 8 | 8 | eight | 3 | -3 - 0 | | zero | 3 | -3 - | | null | 3 | -3 - | 0 | zero | 3 | -3 - 1 | 4 | one | 2 | 4 - 2 | 3 | two | 2 | 4 - 3 | 2 | three | 2 | 4 - 4 | 1 | four | 2 | 4 - 5 | 0 | five | 2 | 4 - 6 | 6 | six | 2 | 4 - 7 | 7 | seven | 2 | 4 - 8 | 8 | eight | 2 | 4 - 0 | | zero | 2 | 4 - | | null | 2 | 4 - | 0 | zero | 2 | 4 - 1 | 4 | one | 5 | -5 - 2 | 3 | two | 5 | -5 - 3 | 2 | three | 5 | -5 - 4 | 1 | four | 5 | -5 - 5 | 0 | five | 5 | -5 - 6 | 6 | six | 5 | -5 - 7 | 7 | seven | 5 | -5 - 8 | 8 | eight | 5 | -5 - 0 | | zero | 5 | -5 - | | null | 5 | -5 - | 0 | zero | 5 | -5 - 1 | 4 | one | 5 | -5 - 2 | 3 | two | 5 | -5 - 3 | 2 | three | 5 | -5 - 4 | 1 | four | 5 | -5 - 5 | 0 | five | 5 | -5 - 6 | 6 | six | 5 | -5 - 7 | 7 | seven | 5 | -5 - 8 | 8 | eight | 5 | -5 - 0 | | zero | 5 | -5 - | | null | 5 | -5 - | 0 | zero | 5 | -5 - 1 | 4 | one | 0 | - 2 | 3 | two | 0 | - 3 | 2 | three | 0 | - 4 | 1 | four | 0 | - 5 | 0 | five | 0 | - 6 | 6 | six | 0 | - 7 | 7 | seven | 0 | - 8 | 8 | eight | 0 | - 0 | | zero | 0 | - | | null | 0 | - | 0 | zero | 0 | - 1 | 4 | one | | - 2 | 3 | two | | - 3 | 2 | three | | - 4 | 1 | four | | - 5 | 0 | five | | - 6 | 6 | six | | - 7 | 7 | seven | | - 8 | 8 | eight | | - 0 | | zero | | - | | null | | - | 0 | zero | | - 1 | 4 | one | | 0 - 2 | 3 | two | | 0 - 3 | 2 | three | | 0 - 4 | 1 | four | | 0 - 5 | 0 | five | | 0 - 6 | 6 | six | | 0 - 7 | 7 | seven | | 0 - 8 | 8 | eight | | 0 - 0 | | zero | | 0 - | | null | | 0 - | 0 | zero | | 0 -(99 rows) - -SELECT t1.a, t2.e - FROM J1_TBL t1 (a, b, c), J2_TBL t2 (d, e) - WHERE t1.a = t2.d; - a | e ----+---- - 0 | - 1 | -1 - 2 | 2 - 2 | 4 - 3 | -3 - 5 | -5 - 5 | -5 -(7 rows) - --- --- CROSS JOIN --- Qualifications are not allowed on cross joins, --- which degenerate into a standard unqualified inner join. --- -SELECT * - FROM J1_TBL CROSS JOIN J2_TBL; - i | j | t | i | k ----+---+-------+---+---- - 1 | 4 | one | 1 | -1 - 2 | 3 | two | 1 | -1 - 3 | 2 | three | 1 | -1 - 4 | 1 | four | 1 | -1 - 5 | 0 | five | 1 | -1 - 6 | 6 | six | 1 | -1 - 7 | 7 | seven | 1 | -1 - 8 | 8 | eight | 1 | -1 - 0 | | zero | 1 | -1 - | | null | 1 | -1 - | 0 | zero | 1 | -1 - 1 | 4 | one | 2 | 2 - 2 | 3 | two | 2 | 2 - 3 | 2 | three | 2 | 2 - 4 | 1 | four | 2 | 2 - 5 | 0 | five | 2 | 2 - 6 | 6 | six | 2 | 2 - 7 | 7 | seven | 2 | 2 - 8 | 8 | eight | 2 | 2 - 0 | | zero | 2 | 2 - | | null | 2 | 2 - | 0 | zero | 2 | 2 - 1 | 4 | one | 3 | -3 - 2 | 3 | two | 3 | -3 - 3 | 2 | three | 3 | -3 - 4 | 1 | four | 3 | -3 - 5 | 0 | five | 3 | -3 - 6 | 6 | six | 3 | -3 - 7 | 7 | seven | 3 | -3 - 8 | 8 | eight | 3 | -3 - 0 | | zero | 3 | -3 - | | null | 3 | -3 - | 0 | zero | 3 | -3 - 1 | 4 | one | 2 | 4 - 2 | 3 | two | 2 | 4 - 3 | 2 | three | 2 | 4 - 4 | 1 | four | 2 | 4 - 5 | 0 | five | 2 | 4 - 6 | 6 | six | 2 | 4 - 7 | 7 | seven | 2 | 4 - 8 | 8 | eight | 2 | 4 - 0 | | zero | 2 | 4 - | | null | 2 | 4 - | 0 | zero | 2 | 4 - 1 | 4 | one | 5 | -5 - 2 | 3 | two | 5 | -5 - 3 | 2 | three | 5 | -5 - 4 | 1 | four | 5 | -5 - 5 | 0 | five | 5 | -5 - 6 | 6 | six | 5 | -5 - 7 | 7 | seven | 5 | -5 - 8 | 8 | eight | 5 | -5 - 0 | | zero | 5 | -5 - | | null | 5 | -5 - | 0 | zero | 5 | -5 - 1 | 4 | one | 5 | -5 - 2 | 3 | two | 5 | -5 - 3 | 2 | three | 5 | -5 - 4 | 1 | four | 5 | -5 - 5 | 0 | five | 5 | -5 - 6 | 6 | six | 5 | -5 - 7 | 7 | seven | 5 | -5 - 8 | 8 | eight | 5 | -5 - 0 | | zero | 5 | -5 - | | null | 5 | -5 - | 0 | zero | 5 | -5 - 1 | 4 | one | 0 | - 2 | 3 | two | 0 | - 3 | 2 | three | 0 | - 4 | 1 | four | 0 | - 5 | 0 | five | 0 | - 6 | 6 | six | 0 | - 7 | 7 | seven | 0 | - 8 | 8 | eight | 0 | - 0 | | zero | 0 | - | | null | 0 | - | 0 | zero | 0 | - 1 | 4 | one | | - 2 | 3 | two | | - 3 | 2 | three | | - 4 | 1 | four | | - 5 | 0 | five | | - 6 | 6 | six | | - 7 | 7 | seven | | - 8 | 8 | eight | | - 0 | | zero | | - | | null | | - | 0 | zero | | - 1 | 4 | one | | 0 - 2 | 3 | two | | 0 - 3 | 2 | three | | 0 - 4 | 1 | four | | 0 - 5 | 0 | five | | 0 - 6 | 6 | six | | 0 - 7 | 7 | seven | | 0 - 8 | 8 | eight | | 0 - 0 | | zero | | 0 - | | null | | 0 - | 0 | zero | | 0 -(99 rows) - --- ambiguous column -SELECT i, k, t - FROM J1_TBL CROSS JOIN J2_TBL; -ERROR: column reference "i" is ambiguous -LINE 1: SELECT i, k, t - ^ --- resolve previous ambiguity by specifying the table name -SELECT t1.i, k, t - FROM J1_TBL t1 CROSS JOIN J2_TBL t2; - i | k | t ----+----+------- - 1 | -1 | one - 2 | -1 | two - 3 | -1 | three - 4 | -1 | four - 5 | -1 | five - 6 | -1 | six - 7 | -1 | seven - 8 | -1 | eight - 0 | -1 | zero - | -1 | null - | -1 | zero - 1 | 2 | one - 2 | 2 | two - 3 | 2 | three - 4 | 2 | four - 5 | 2 | five - 6 | 2 | six - 7 | 2 | seven - 8 | 2 | eight - 0 | 2 | zero - | 2 | null - | 2 | zero - 1 | -3 | one - 2 | -3 | two - 3 | -3 | three - 4 | -3 | four - 5 | -3 | five - 6 | -3 | six - 7 | -3 | seven - 8 | -3 | eight - 0 | -3 | zero - | -3 | null - | -3 | zero - 1 | 4 | one - 2 | 4 | two - 3 | 4 | three - 4 | 4 | four - 5 | 4 | five - 6 | 4 | six - 7 | 4 | seven - 8 | 4 | eight - 0 | 4 | zero - | 4 | null - | 4 | zero - 1 | -5 | one - 2 | -5 | two - 3 | -5 | three - 4 | -5 | four - 5 | -5 | five - 6 | -5 | six - 7 | -5 | seven - 8 | -5 | eight - 0 | -5 | zero - | -5 | null - | -5 | zero - 1 | -5 | one - 2 | -5 | two - 3 | -5 | three - 4 | -5 | four - 5 | -5 | five - 6 | -5 | six - 7 | -5 | seven - 8 | -5 | eight - 0 | -5 | zero - | -5 | null - | -5 | zero - 1 | | one - 2 | | two - 3 | | three - 4 | | four - 5 | | five - 6 | | six - 7 | | seven - 8 | | eight - 0 | | zero - | | null - | | zero - 1 | | one - 2 | | two - 3 | | three - 4 | | four - 5 | | five - 6 | | six - 7 | | seven - 8 | | eight - 0 | | zero - | | null - | | zero - 1 | 0 | one - 2 | 0 | two - 3 | 0 | three - 4 | 0 | four - 5 | 0 | five - 6 | 0 | six - 7 | 0 | seven - 8 | 0 | eight - 0 | 0 | zero - | 0 | null - | 0 | zero -(99 rows) - -SELECT ii, tt, kk - FROM (J1_TBL CROSS JOIN J2_TBL) - AS tx (ii, jj, tt, ii2, kk); - ii | tt | kk -----+-------+---- - 1 | one | -1 - 2 | two | -1 - 3 | three | -1 - 4 | four | -1 - 5 | five | -1 - 6 | six | -1 - 7 | seven | -1 - 8 | eight | -1 - 0 | zero | -1 - | null | -1 - | zero | -1 - 1 | one | 2 - 2 | two | 2 - 3 | three | 2 - 4 | four | 2 - 5 | five | 2 - 6 | six | 2 - 7 | seven | 2 - 8 | eight | 2 - 0 | zero | 2 - | null | 2 - | zero | 2 - 1 | one | -3 - 2 | two | -3 - 3 | three | -3 - 4 | four | -3 - 5 | five | -3 - 6 | six | -3 - 7 | seven | -3 - 8 | eight | -3 - 0 | zero | -3 - | null | -3 - | zero | -3 - 1 | one | 4 - 2 | two | 4 - 3 | three | 4 - 4 | four | 4 - 5 | five | 4 - 6 | six | 4 - 7 | seven | 4 - 8 | eight | 4 - 0 | zero | 4 - | null | 4 - | zero | 4 - 1 | one | -5 - 2 | two | -5 - 3 | three | -5 - 4 | four | -5 - 5 | five | -5 - 6 | six | -5 - 7 | seven | -5 - 8 | eight | -5 - 0 | zero | -5 - | null | -5 - | zero | -5 - 1 | one | -5 - 2 | two | -5 - 3 | three | -5 - 4 | four | -5 - 5 | five | -5 - 6 | six | -5 - 7 | seven | -5 - 8 | eight | -5 - 0 | zero | -5 - | null | -5 - | zero | -5 - 1 | one | - 2 | two | - 3 | three | - 4 | four | - 5 | five | - 6 | six | - 7 | seven | - 8 | eight | - 0 | zero | - | null | - | zero | - 1 | one | - 2 | two | - 3 | three | - 4 | four | - 5 | five | - 6 | six | - 7 | seven | - 8 | eight | - 0 | zero | - | null | - | zero | - 1 | one | 0 - 2 | two | 0 - 3 | three | 0 - 4 | four | 0 - 5 | five | 0 - 6 | six | 0 - 7 | seven | 0 - 8 | eight | 0 - 0 | zero | 0 - | null | 0 - | zero | 0 -(99 rows) - -SELECT tx.ii, tx.jj, tx.kk - FROM (J1_TBL t1 (a, b, c) CROSS JOIN J2_TBL t2 (d, e)) - AS tx (ii, jj, tt, ii2, kk); - ii | jj | kk -----+----+---- - 1 | 4 | -1 - 2 | 3 | -1 - 3 | 2 | -1 - 4 | 1 | -1 - 5 | 0 | -1 - 6 | 6 | -1 - 7 | 7 | -1 - 8 | 8 | -1 - 0 | | -1 - | | -1 - | 0 | -1 - 1 | 4 | 2 - 2 | 3 | 2 - 3 | 2 | 2 - 4 | 1 | 2 - 5 | 0 | 2 - 6 | 6 | 2 - 7 | 7 | 2 - 8 | 8 | 2 - 0 | | 2 - | | 2 - | 0 | 2 - 1 | 4 | -3 - 2 | 3 | -3 - 3 | 2 | -3 - 4 | 1 | -3 - 5 | 0 | -3 - 6 | 6 | -3 - 7 | 7 | -3 - 8 | 8 | -3 - 0 | | -3 - | | -3 - | 0 | -3 - 1 | 4 | 4 - 2 | 3 | 4 - 3 | 2 | 4 - 4 | 1 | 4 - 5 | 0 | 4 - 6 | 6 | 4 - 7 | 7 | 4 - 8 | 8 | 4 - 0 | | 4 - | | 4 - | 0 | 4 - 1 | 4 | -5 - 2 | 3 | -5 - 3 | 2 | -5 - 4 | 1 | -5 - 5 | 0 | -5 - 6 | 6 | -5 - 7 | 7 | -5 - 8 | 8 | -5 - 0 | | -5 - | | -5 - | 0 | -5 - 1 | 4 | -5 - 2 | 3 | -5 - 3 | 2 | -5 - 4 | 1 | -5 - 5 | 0 | -5 - 6 | 6 | -5 - 7 | 7 | -5 - 8 | 8 | -5 - 0 | | -5 - | | -5 - | 0 | -5 - 1 | 4 | - 2 | 3 | - 3 | 2 | - 4 | 1 | - 5 | 0 | - 6 | 6 | - 7 | 7 | - 8 | 8 | - 0 | | - | | - | 0 | - 1 | 4 | - 2 | 3 | - 3 | 2 | - 4 | 1 | - 5 | 0 | - 6 | 6 | - 7 | 7 | - 8 | 8 | - 0 | | - | | - | 0 | - 1 | 4 | 0 - 2 | 3 | 0 - 3 | 2 | 0 - 4 | 1 | 0 - 5 | 0 | 0 - 6 | 6 | 0 - 7 | 7 | 0 - 8 | 8 | 0 - 0 | | 0 - | | 0 - | 0 | 0 -(99 rows) - -SELECT * - FROM J1_TBL CROSS JOIN J2_TBL a CROSS JOIN J2_TBL b; - i | j | t | i | k | i | k ----+---+-------+---+----+---+---- - 1 | 4 | one | 1 | -1 | 1 | -1 - 1 | 4 | one | 1 | -1 | 2 | 2 - 1 | 4 | one | 1 | -1 | 3 | -3 - 1 | 4 | one | 1 | -1 | 2 | 4 - 1 | 4 | one | 1 | -1 | 5 | -5 - 1 | 4 | one | 1 | -1 | 5 | -5 - 1 | 4 | one | 1 | -1 | 0 | - 1 | 4 | one | 1 | -1 | | - 1 | 4 | one | 1 | -1 | | 0 - 2 | 3 | two | 1 | -1 | 1 | -1 - 2 | 3 | two | 1 | -1 | 2 | 2 - 2 | 3 | two | 1 | -1 | 3 | -3 - 2 | 3 | two | 1 | -1 | 2 | 4 - 2 | 3 | two | 1 | -1 | 5 | -5 - 2 | 3 | two | 1 | -1 | 5 | -5 - 2 | 3 | two | 1 | -1 | 0 | - 2 | 3 | two | 1 | -1 | | - 2 | 3 | two | 1 | -1 | | 0 - 3 | 2 | three | 1 | -1 | 1 | -1 - 3 | 2 | three | 1 | -1 | 2 | 2 - 3 | 2 | three | 1 | -1 | 3 | -3 - 3 | 2 | three | 1 | -1 | 2 | 4 - 3 | 2 | three | 1 | -1 | 5 | -5 - 3 | 2 | three | 1 | -1 | 5 | -5 - 3 | 2 | three | 1 | -1 | 0 | - 3 | 2 | three | 1 | -1 | | - 3 | 2 | three | 1 | -1 | | 0 - 4 | 1 | four | 1 | -1 | 1 | -1 - 4 | 1 | four | 1 | -1 | 2 | 2 - 4 | 1 | four | 1 | -1 | 3 | -3 - 4 | 1 | four | 1 | -1 | 2 | 4 - 4 | 1 | four | 1 | -1 | 5 | -5 - 4 | 1 | four | 1 | -1 | 5 | -5 - 4 | 1 | four | 1 | -1 | 0 | - 4 | 1 | four | 1 | -1 | | - 4 | 1 | four | 1 | -1 | | 0 - 5 | 0 | five | 1 | -1 | 1 | -1 - 5 | 0 | five | 1 | -1 | 2 | 2 - 5 | 0 | five | 1 | -1 | 3 | -3 - 5 | 0 | five | 1 | -1 | 2 | 4 - 5 | 0 | five | 1 | -1 | 5 | -5 - 5 | 0 | five | 1 | -1 | 5 | -5 - 5 | 0 | five | 1 | -1 | 0 | - 5 | 0 | five | 1 | -1 | | - 5 | 0 | five | 1 | -1 | | 0 - 6 | 6 | six | 1 | -1 | 1 | -1 - 6 | 6 | six | 1 | -1 | 2 | 2 - 6 | 6 | six | 1 | -1 | 3 | -3 - 6 | 6 | six | 1 | -1 | 2 | 4 - 6 | 6 | six | 1 | -1 | 5 | -5 - 6 | 6 | six | 1 | -1 | 5 | -5 - 6 | 6 | six | 1 | -1 | 0 | - 6 | 6 | six | 1 | -1 | | - 6 | 6 | six | 1 | -1 | | 0 - 7 | 7 | seven | 1 | -1 | 1 | -1 - 7 | 7 | seven | 1 | -1 | 2 | 2 - 7 | 7 | seven | 1 | -1 | 3 | -3 - 7 | 7 | seven | 1 | -1 | 2 | 4 - 7 | 7 | seven | 1 | -1 | 5 | -5 - 7 | 7 | seven | 1 | -1 | 5 | -5 - 7 | 7 | seven | 1 | -1 | 0 | - 7 | 7 | seven | 1 | -1 | | - 7 | 7 | seven | 1 | -1 | | 0 - 8 | 8 | eight | 1 | -1 | 1 | -1 - 8 | 8 | eight | 1 | -1 | 2 | 2 - 8 | 8 | eight | 1 | -1 | 3 | -3 - 8 | 8 | eight | 1 | -1 | 2 | 4 - 8 | 8 | eight | 1 | -1 | 5 | -5 - 8 | 8 | eight | 1 | -1 | 5 | -5 - 8 | 8 | eight | 1 | -1 | 0 | - 8 | 8 | eight | 1 | -1 | | - 8 | 8 | eight | 1 | -1 | | 0 - 0 | | zero | 1 | -1 | 1 | -1 - 0 | | zero | 1 | -1 | 2 | 2 - 0 | | zero | 1 | -1 | 3 | -3 - 0 | | zero | 1 | -1 | 2 | 4 - 0 | | zero | 1 | -1 | 5 | -5 - 0 | | zero | 1 | -1 | 5 | -5 - 0 | | zero | 1 | -1 | 0 | - 0 | | zero | 1 | -1 | | - 0 | | zero | 1 | -1 | | 0 - | | null | 1 | -1 | 1 | -1 - | | null | 1 | -1 | 2 | 2 - | | null | 1 | -1 | 3 | -3 - | | null | 1 | -1 | 2 | 4 - | | null | 1 | -1 | 5 | -5 - | | null | 1 | -1 | 5 | -5 - | | null | 1 | -1 | 0 | - | | null | 1 | -1 | | - | | null | 1 | -1 | | 0 - | 0 | zero | 1 | -1 | 1 | -1 - | 0 | zero | 1 | -1 | 2 | 2 - | 0 | zero | 1 | -1 | 3 | -3 - | 0 | zero | 1 | -1 | 2 | 4 - | 0 | zero | 1 | -1 | 5 | -5 - | 0 | zero | 1 | -1 | 5 | -5 - | 0 | zero | 1 | -1 | 0 | - | 0 | zero | 1 | -1 | | - | 0 | zero | 1 | -1 | | 0 - 1 | 4 | one | 2 | 2 | 1 | -1 - 1 | 4 | one | 2 | 2 | 2 | 2 - 1 | 4 | one | 2 | 2 | 3 | -3 - 1 | 4 | one | 2 | 2 | 2 | 4 - 1 | 4 | one | 2 | 2 | 5 | -5 - 1 | 4 | one | 2 | 2 | 5 | -5 - 1 | 4 | one | 2 | 2 | 0 | - 1 | 4 | one | 2 | 2 | | - 1 | 4 | one | 2 | 2 | | 0 - 2 | 3 | two | 2 | 2 | 1 | -1 - 2 | 3 | two | 2 | 2 | 2 | 2 - 2 | 3 | two | 2 | 2 | 3 | -3 - 2 | 3 | two | 2 | 2 | 2 | 4 - 2 | 3 | two | 2 | 2 | 5 | -5 - 2 | 3 | two | 2 | 2 | 5 | -5 - 2 | 3 | two | 2 | 2 | 0 | - 2 | 3 | two | 2 | 2 | | - 2 | 3 | two | 2 | 2 | | 0 - 3 | 2 | three | 2 | 2 | 1 | -1 - 3 | 2 | three | 2 | 2 | 2 | 2 - 3 | 2 | three | 2 | 2 | 3 | -3 - 3 | 2 | three | 2 | 2 | 2 | 4 - 3 | 2 | three | 2 | 2 | 5 | -5 - 3 | 2 | three | 2 | 2 | 5 | -5 - 3 | 2 | three | 2 | 2 | 0 | - 3 | 2 | three | 2 | 2 | | - 3 | 2 | three | 2 | 2 | | 0 - 4 | 1 | four | 2 | 2 | 1 | -1 - 4 | 1 | four | 2 | 2 | 2 | 2 - 4 | 1 | four | 2 | 2 | 3 | -3 - 4 | 1 | four | 2 | 2 | 2 | 4 - 4 | 1 | four | 2 | 2 | 5 | -5 - 4 | 1 | four | 2 | 2 | 5 | -5 - 4 | 1 | four | 2 | 2 | 0 | - 4 | 1 | four | 2 | 2 | | - 4 | 1 | four | 2 | 2 | | 0 - 5 | 0 | five | 2 | 2 | 1 | -1 - 5 | 0 | five | 2 | 2 | 2 | 2 - 5 | 0 | five | 2 | 2 | 3 | -3 - 5 | 0 | five | 2 | 2 | 2 | 4 - 5 | 0 | five | 2 | 2 | 5 | -5 - 5 | 0 | five | 2 | 2 | 5 | -5 - 5 | 0 | five | 2 | 2 | 0 | - 5 | 0 | five | 2 | 2 | | - 5 | 0 | five | 2 | 2 | | 0 - 6 | 6 | six | 2 | 2 | 1 | -1 - 6 | 6 | six | 2 | 2 | 2 | 2 - 6 | 6 | six | 2 | 2 | 3 | -3 - 6 | 6 | six | 2 | 2 | 2 | 4 - 6 | 6 | six | 2 | 2 | 5 | -5 - 6 | 6 | six | 2 | 2 | 5 | -5 - 6 | 6 | six | 2 | 2 | 0 | - 6 | 6 | six | 2 | 2 | | - 6 | 6 | six | 2 | 2 | | 0 - 7 | 7 | seven | 2 | 2 | 1 | -1 - 7 | 7 | seven | 2 | 2 | 2 | 2 - 7 | 7 | seven | 2 | 2 | 3 | -3 - 7 | 7 | seven | 2 | 2 | 2 | 4 - 7 | 7 | seven | 2 | 2 | 5 | -5 - 7 | 7 | seven | 2 | 2 | 5 | -5 - 7 | 7 | seven | 2 | 2 | 0 | - 7 | 7 | seven | 2 | 2 | | - 7 | 7 | seven | 2 | 2 | | 0 - 8 | 8 | eight | 2 | 2 | 1 | -1 - 8 | 8 | eight | 2 | 2 | 2 | 2 - 8 | 8 | eight | 2 | 2 | 3 | -3 - 8 | 8 | eight | 2 | 2 | 2 | 4 - 8 | 8 | eight | 2 | 2 | 5 | -5 - 8 | 8 | eight | 2 | 2 | 5 | -5 - 8 | 8 | eight | 2 | 2 | 0 | - 8 | 8 | eight | 2 | 2 | | - 8 | 8 | eight | 2 | 2 | | 0 - 0 | | zero | 2 | 2 | 1 | -1 - 0 | | zero | 2 | 2 | 2 | 2 - 0 | | zero | 2 | 2 | 3 | -3 - 0 | | zero | 2 | 2 | 2 | 4 - 0 | | zero | 2 | 2 | 5 | -5 - 0 | | zero | 2 | 2 | 5 | -5 - 0 | | zero | 2 | 2 | 0 | - 0 | | zero | 2 | 2 | | - 0 | | zero | 2 | 2 | | 0 - | | null | 2 | 2 | 1 | -1 - | | null | 2 | 2 | 2 | 2 - | | null | 2 | 2 | 3 | -3 - | | null | 2 | 2 | 2 | 4 - | | null | 2 | 2 | 5 | -5 - | | null | 2 | 2 | 5 | -5 - | | null | 2 | 2 | 0 | - | | null | 2 | 2 | | - | | null | 2 | 2 | | 0 - | 0 | zero | 2 | 2 | 1 | -1 - | 0 | zero | 2 | 2 | 2 | 2 - | 0 | zero | 2 | 2 | 3 | -3 - | 0 | zero | 2 | 2 | 2 | 4 - | 0 | zero | 2 | 2 | 5 | -5 - | 0 | zero | 2 | 2 | 5 | -5 - | 0 | zero | 2 | 2 | 0 | - | 0 | zero | 2 | 2 | | - | 0 | zero | 2 | 2 | | 0 - 1 | 4 | one | 3 | -3 | 1 | -1 - 1 | 4 | one | 3 | -3 | 2 | 2 - 1 | 4 | one | 3 | -3 | 3 | -3 - 1 | 4 | one | 3 | -3 | 2 | 4 - 1 | 4 | one | 3 | -3 | 5 | -5 - 1 | 4 | one | 3 | -3 | 5 | -5 - 1 | 4 | one | 3 | -3 | 0 | - 1 | 4 | one | 3 | -3 | | - 1 | 4 | one | 3 | -3 | | 0 - 2 | 3 | two | 3 | -3 | 1 | -1 - 2 | 3 | two | 3 | -3 | 2 | 2 - 2 | 3 | two | 3 | -3 | 3 | -3 - 2 | 3 | two | 3 | -3 | 2 | 4 - 2 | 3 | two | 3 | -3 | 5 | -5 - 2 | 3 | two | 3 | -3 | 5 | -5 - 2 | 3 | two | 3 | -3 | 0 | - 2 | 3 | two | 3 | -3 | | - 2 | 3 | two | 3 | -3 | | 0 - 3 | 2 | three | 3 | -3 | 1 | -1 - 3 | 2 | three | 3 | -3 | 2 | 2 - 3 | 2 | three | 3 | -3 | 3 | -3 - 3 | 2 | three | 3 | -3 | 2 | 4 - 3 | 2 | three | 3 | -3 | 5 | -5 - 3 | 2 | three | 3 | -3 | 5 | -5 - 3 | 2 | three | 3 | -3 | 0 | - 3 | 2 | three | 3 | -3 | | - 3 | 2 | three | 3 | -3 | | 0 - 4 | 1 | four | 3 | -3 | 1 | -1 - 4 | 1 | four | 3 | -3 | 2 | 2 - 4 | 1 | four | 3 | -3 | 3 | -3 - 4 | 1 | four | 3 | -3 | 2 | 4 - 4 | 1 | four | 3 | -3 | 5 | -5 - 4 | 1 | four | 3 | -3 | 5 | -5 - 4 | 1 | four | 3 | -3 | 0 | - 4 | 1 | four | 3 | -3 | | - 4 | 1 | four | 3 | -3 | | 0 - 5 | 0 | five | 3 | -3 | 1 | -1 - 5 | 0 | five | 3 | -3 | 2 | 2 - 5 | 0 | five | 3 | -3 | 3 | -3 - 5 | 0 | five | 3 | -3 | 2 | 4 - 5 | 0 | five | 3 | -3 | 5 | -5 - 5 | 0 | five | 3 | -3 | 5 | -5 - 5 | 0 | five | 3 | -3 | 0 | - 5 | 0 | five | 3 | -3 | | - 5 | 0 | five | 3 | -3 | | 0 - 6 | 6 | six | 3 | -3 | 1 | -1 - 6 | 6 | six | 3 | -3 | 2 | 2 - 6 | 6 | six | 3 | -3 | 3 | -3 - 6 | 6 | six | 3 | -3 | 2 | 4 - 6 | 6 | six | 3 | -3 | 5 | -5 - 6 | 6 | six | 3 | -3 | 5 | -5 - 6 | 6 | six | 3 | -3 | 0 | - 6 | 6 | six | 3 | -3 | | - 6 | 6 | six | 3 | -3 | | 0 - 7 | 7 | seven | 3 | -3 | 1 | -1 - 7 | 7 | seven | 3 | -3 | 2 | 2 - 7 | 7 | seven | 3 | -3 | 3 | -3 - 7 | 7 | seven | 3 | -3 | 2 | 4 - 7 | 7 | seven | 3 | -3 | 5 | -5 - 7 | 7 | seven | 3 | -3 | 5 | -5 - 7 | 7 | seven | 3 | -3 | 0 | - 7 | 7 | seven | 3 | -3 | | - 7 | 7 | seven | 3 | -3 | | 0 - 8 | 8 | eight | 3 | -3 | 1 | -1 - 8 | 8 | eight | 3 | -3 | 2 | 2 - 8 | 8 | eight | 3 | -3 | 3 | -3 - 8 | 8 | eight | 3 | -3 | 2 | 4 - 8 | 8 | eight | 3 | -3 | 5 | -5 - 8 | 8 | eight | 3 | -3 | 5 | -5 - 8 | 8 | eight | 3 | -3 | 0 | - 8 | 8 | eight | 3 | -3 | | - 8 | 8 | eight | 3 | -3 | | 0 - 0 | | zero | 3 | -3 | 1 | -1 - 0 | | zero | 3 | -3 | 2 | 2 - 0 | | zero | 3 | -3 | 3 | -3 - 0 | | zero | 3 | -3 | 2 | 4 - 0 | | zero | 3 | -3 | 5 | -5 - 0 | | zero | 3 | -3 | 5 | -5 - 0 | | zero | 3 | -3 | 0 | - 0 | | zero | 3 | -3 | | - 0 | | zero | 3 | -3 | | 0 - | | null | 3 | -3 | 1 | -1 - | | null | 3 | -3 | 2 | 2 - | | null | 3 | -3 | 3 | -3 - | | null | 3 | -3 | 2 | 4 - | | null | 3 | -3 | 5 | -5 - | | null | 3 | -3 | 5 | -5 - | | null | 3 | -3 | 0 | - | | null | 3 | -3 | | - | | null | 3 | -3 | | 0 - | 0 | zero | 3 | -3 | 1 | -1 - | 0 | zero | 3 | -3 | 2 | 2 - | 0 | zero | 3 | -3 | 3 | -3 - | 0 | zero | 3 | -3 | 2 | 4 - | 0 | zero | 3 | -3 | 5 | -5 - | 0 | zero | 3 | -3 | 5 | -5 - | 0 | zero | 3 | -3 | 0 | - | 0 | zero | 3 | -3 | | - | 0 | zero | 3 | -3 | | 0 - 1 | 4 | one | 2 | 4 | 1 | -1 - 1 | 4 | one | 2 | 4 | 2 | 2 - 1 | 4 | one | 2 | 4 | 3 | -3 - 1 | 4 | one | 2 | 4 | 2 | 4 - 1 | 4 | one | 2 | 4 | 5 | -5 - 1 | 4 | one | 2 | 4 | 5 | -5 - 1 | 4 | one | 2 | 4 | 0 | - 1 | 4 | one | 2 | 4 | | - 1 | 4 | one | 2 | 4 | | 0 - 2 | 3 | two | 2 | 4 | 1 | -1 - 2 | 3 | two | 2 | 4 | 2 | 2 - 2 | 3 | two | 2 | 4 | 3 | -3 - 2 | 3 | two | 2 | 4 | 2 | 4 - 2 | 3 | two | 2 | 4 | 5 | -5 - 2 | 3 | two | 2 | 4 | 5 | -5 - 2 | 3 | two | 2 | 4 | 0 | - 2 | 3 | two | 2 | 4 | | - 2 | 3 | two | 2 | 4 | | 0 - 3 | 2 | three | 2 | 4 | 1 | -1 - 3 | 2 | three | 2 | 4 | 2 | 2 - 3 | 2 | three | 2 | 4 | 3 | -3 - 3 | 2 | three | 2 | 4 | 2 | 4 - 3 | 2 | three | 2 | 4 | 5 | -5 - 3 | 2 | three | 2 | 4 | 5 | -5 - 3 | 2 | three | 2 | 4 | 0 | - 3 | 2 | three | 2 | 4 | | - 3 | 2 | three | 2 | 4 | | 0 - 4 | 1 | four | 2 | 4 | 1 | -1 - 4 | 1 | four | 2 | 4 | 2 | 2 - 4 | 1 | four | 2 | 4 | 3 | -3 - 4 | 1 | four | 2 | 4 | 2 | 4 - 4 | 1 | four | 2 | 4 | 5 | -5 - 4 | 1 | four | 2 | 4 | 5 | -5 - 4 | 1 | four | 2 | 4 | 0 | - 4 | 1 | four | 2 | 4 | | - 4 | 1 | four | 2 | 4 | | 0 - 5 | 0 | five | 2 | 4 | 1 | -1 - 5 | 0 | five | 2 | 4 | 2 | 2 - 5 | 0 | five | 2 | 4 | 3 | -3 - 5 | 0 | five | 2 | 4 | 2 | 4 - 5 | 0 | five | 2 | 4 | 5 | -5 - 5 | 0 | five | 2 | 4 | 5 | -5 - 5 | 0 | five | 2 | 4 | 0 | - 5 | 0 | five | 2 | 4 | | - 5 | 0 | five | 2 | 4 | | 0 - 6 | 6 | six | 2 | 4 | 1 | -1 - 6 | 6 | six | 2 | 4 | 2 | 2 - 6 | 6 | six | 2 | 4 | 3 | -3 - 6 | 6 | six | 2 | 4 | 2 | 4 - 6 | 6 | six | 2 | 4 | 5 | -5 - 6 | 6 | six | 2 | 4 | 5 | -5 - 6 | 6 | six | 2 | 4 | 0 | - 6 | 6 | six | 2 | 4 | | - 6 | 6 | six | 2 | 4 | | 0 - 7 | 7 | seven | 2 | 4 | 1 | -1 - 7 | 7 | seven | 2 | 4 | 2 | 2 - 7 | 7 | seven | 2 | 4 | 3 | -3 - 7 | 7 | seven | 2 | 4 | 2 | 4 - 7 | 7 | seven | 2 | 4 | 5 | -5 - 7 | 7 | seven | 2 | 4 | 5 | -5 - 7 | 7 | seven | 2 | 4 | 0 | - 7 | 7 | seven | 2 | 4 | | - 7 | 7 | seven | 2 | 4 | | 0 - 8 | 8 | eight | 2 | 4 | 1 | -1 - 8 | 8 | eight | 2 | 4 | 2 | 2 - 8 | 8 | eight | 2 | 4 | 3 | -3 - 8 | 8 | eight | 2 | 4 | 2 | 4 - 8 | 8 | eight | 2 | 4 | 5 | -5 - 8 | 8 | eight | 2 | 4 | 5 | -5 - 8 | 8 | eight | 2 | 4 | 0 | - 8 | 8 | eight | 2 | 4 | | - 8 | 8 | eight | 2 | 4 | | 0 - 0 | | zero | 2 | 4 | 1 | -1 - 0 | | zero | 2 | 4 | 2 | 2 - 0 | | zero | 2 | 4 | 3 | -3 - 0 | | zero | 2 | 4 | 2 | 4 - 0 | | zero | 2 | 4 | 5 | -5 - 0 | | zero | 2 | 4 | 5 | -5 - 0 | | zero | 2 | 4 | 0 | - 0 | | zero | 2 | 4 | | - 0 | | zero | 2 | 4 | | 0 - | | null | 2 | 4 | 1 | -1 - | | null | 2 | 4 | 2 | 2 - | | null | 2 | 4 | 3 | -3 - | | null | 2 | 4 | 2 | 4 - | | null | 2 | 4 | 5 | -5 - | | null | 2 | 4 | 5 | -5 - | | null | 2 | 4 | 0 | - | | null | 2 | 4 | | - | | null | 2 | 4 | | 0 - | 0 | zero | 2 | 4 | 1 | -1 - | 0 | zero | 2 | 4 | 2 | 2 - | 0 | zero | 2 | 4 | 3 | -3 - | 0 | zero | 2 | 4 | 2 | 4 - | 0 | zero | 2 | 4 | 5 | -5 - | 0 | zero | 2 | 4 | 5 | -5 - | 0 | zero | 2 | 4 | 0 | - | 0 | zero | 2 | 4 | | - | 0 | zero | 2 | 4 | | 0 - 1 | 4 | one | 5 | -5 | 1 | -1 - 1 | 4 | one | 5 | -5 | 2 | 2 - 1 | 4 | one | 5 | -5 | 3 | -3 - 1 | 4 | one | 5 | -5 | 2 | 4 - 1 | 4 | one | 5 | -5 | 5 | -5 - 1 | 4 | one | 5 | -5 | 5 | -5 - 1 | 4 | one | 5 | -5 | 0 | - 1 | 4 | one | 5 | -5 | | - 1 | 4 | one | 5 | -5 | | 0 - 2 | 3 | two | 5 | -5 | 1 | -1 - 2 | 3 | two | 5 | -5 | 2 | 2 - 2 | 3 | two | 5 | -5 | 3 | -3 - 2 | 3 | two | 5 | -5 | 2 | 4 - 2 | 3 | two | 5 | -5 | 5 | -5 - 2 | 3 | two | 5 | -5 | 5 | -5 - 2 | 3 | two | 5 | -5 | 0 | - 2 | 3 | two | 5 | -5 | | - 2 | 3 | two | 5 | -5 | | 0 - 3 | 2 | three | 5 | -5 | 1 | -1 - 3 | 2 | three | 5 | -5 | 2 | 2 - 3 | 2 | three | 5 | -5 | 3 | -3 - 3 | 2 | three | 5 | -5 | 2 | 4 - 3 | 2 | three | 5 | -5 | 5 | -5 - 3 | 2 | three | 5 | -5 | 5 | -5 - 3 | 2 | three | 5 | -5 | 0 | - 3 | 2 | three | 5 | -5 | | - 3 | 2 | three | 5 | -5 | | 0 - 4 | 1 | four | 5 | -5 | 1 | -1 - 4 | 1 | four | 5 | -5 | 2 | 2 - 4 | 1 | four | 5 | -5 | 3 | -3 - 4 | 1 | four | 5 | -5 | 2 | 4 - 4 | 1 | four | 5 | -5 | 5 | -5 - 4 | 1 | four | 5 | -5 | 5 | -5 - 4 | 1 | four | 5 | -5 | 0 | - 4 | 1 | four | 5 | -5 | | - 4 | 1 | four | 5 | -5 | | 0 - 5 | 0 | five | 5 | -5 | 1 | -1 - 5 | 0 | five | 5 | -5 | 2 | 2 - 5 | 0 | five | 5 | -5 | 3 | -3 - 5 | 0 | five | 5 | -5 | 2 | 4 - 5 | 0 | five | 5 | -5 | 5 | -5 - 5 | 0 | five | 5 | -5 | 5 | -5 - 5 | 0 | five | 5 | -5 | 0 | - 5 | 0 | five | 5 | -5 | | - 5 | 0 | five | 5 | -5 | | 0 - 6 | 6 | six | 5 | -5 | 1 | -1 - 6 | 6 | six | 5 | -5 | 2 | 2 - 6 | 6 | six | 5 | -5 | 3 | -3 - 6 | 6 | six | 5 | -5 | 2 | 4 - 6 | 6 | six | 5 | -5 | 5 | -5 - 6 | 6 | six | 5 | -5 | 5 | -5 - 6 | 6 | six | 5 | -5 | 0 | - 6 | 6 | six | 5 | -5 | | - 6 | 6 | six | 5 | -5 | | 0 - 7 | 7 | seven | 5 | -5 | 1 | -1 - 7 | 7 | seven | 5 | -5 | 2 | 2 - 7 | 7 | seven | 5 | -5 | 3 | -3 - 7 | 7 | seven | 5 | -5 | 2 | 4 - 7 | 7 | seven | 5 | -5 | 5 | -5 - 7 | 7 | seven | 5 | -5 | 5 | -5 - 7 | 7 | seven | 5 | -5 | 0 | - 7 | 7 | seven | 5 | -5 | | - 7 | 7 | seven | 5 | -5 | | 0 - 8 | 8 | eight | 5 | -5 | 1 | -1 - 8 | 8 | eight | 5 | -5 | 2 | 2 - 8 | 8 | eight | 5 | -5 | 3 | -3 - 8 | 8 | eight | 5 | -5 | 2 | 4 - 8 | 8 | eight | 5 | -5 | 5 | -5 - 8 | 8 | eight | 5 | -5 | 5 | -5 - 8 | 8 | eight | 5 | -5 | 0 | - 8 | 8 | eight | 5 | -5 | | - 8 | 8 | eight | 5 | -5 | | 0 - 0 | | zero | 5 | -5 | 1 | -1 - 0 | | zero | 5 | -5 | 2 | 2 - 0 | | zero | 5 | -5 | 3 | -3 - 0 | | zero | 5 | -5 | 2 | 4 - 0 | | zero | 5 | -5 | 5 | -5 - 0 | | zero | 5 | -5 | 5 | -5 - 0 | | zero | 5 | -5 | 0 | - 0 | | zero | 5 | -5 | | - 0 | | zero | 5 | -5 | | 0 - | | null | 5 | -5 | 1 | -1 - | | null | 5 | -5 | 2 | 2 - | | null | 5 | -5 | 3 | -3 - | | null | 5 | -5 | 2 | 4 - | | null | 5 | -5 | 5 | -5 - | | null | 5 | -5 | 5 | -5 - | | null | 5 | -5 | 0 | - | | null | 5 | -5 | | - | | null | 5 | -5 | | 0 - | 0 | zero | 5 | -5 | 1 | -1 - | 0 | zero | 5 | -5 | 2 | 2 - | 0 | zero | 5 | -5 | 3 | -3 - | 0 | zero | 5 | -5 | 2 | 4 - | 0 | zero | 5 | -5 | 5 | -5 - | 0 | zero | 5 | -5 | 5 | -5 - | 0 | zero | 5 | -5 | 0 | - | 0 | zero | 5 | -5 | | - | 0 | zero | 5 | -5 | | 0 - 1 | 4 | one | 5 | -5 | 1 | -1 - 1 | 4 | one | 5 | -5 | 2 | 2 - 1 | 4 | one | 5 | -5 | 3 | -3 - 1 | 4 | one | 5 | -5 | 2 | 4 - 1 | 4 | one | 5 | -5 | 5 | -5 - 1 | 4 | one | 5 | -5 | 5 | -5 - 1 | 4 | one | 5 | -5 | 0 | - 1 | 4 | one | 5 | -5 | | - 1 | 4 | one | 5 | -5 | | 0 - 2 | 3 | two | 5 | -5 | 1 | -1 - 2 | 3 | two | 5 | -5 | 2 | 2 - 2 | 3 | two | 5 | -5 | 3 | -3 - 2 | 3 | two | 5 | -5 | 2 | 4 - 2 | 3 | two | 5 | -5 | 5 | -5 - 2 | 3 | two | 5 | -5 | 5 | -5 - 2 | 3 | two | 5 | -5 | 0 | - 2 | 3 | two | 5 | -5 | | - 2 | 3 | two | 5 | -5 | | 0 - 3 | 2 | three | 5 | -5 | 1 | -1 - 3 | 2 | three | 5 | -5 | 2 | 2 - 3 | 2 | three | 5 | -5 | 3 | -3 - 3 | 2 | three | 5 | -5 | 2 | 4 - 3 | 2 | three | 5 | -5 | 5 | -5 - 3 | 2 | three | 5 | -5 | 5 | -5 - 3 | 2 | three | 5 | -5 | 0 | - 3 | 2 | three | 5 | -5 | | - 3 | 2 | three | 5 | -5 | | 0 - 4 | 1 | four | 5 | -5 | 1 | -1 - 4 | 1 | four | 5 | -5 | 2 | 2 - 4 | 1 | four | 5 | -5 | 3 | -3 - 4 | 1 | four | 5 | -5 | 2 | 4 - 4 | 1 | four | 5 | -5 | 5 | -5 - 4 | 1 | four | 5 | -5 | 5 | -5 - 4 | 1 | four | 5 | -5 | 0 | - 4 | 1 | four | 5 | -5 | | - 4 | 1 | four | 5 | -5 | | 0 - 5 | 0 | five | 5 | -5 | 1 | -1 - 5 | 0 | five | 5 | -5 | 2 | 2 - 5 | 0 | five | 5 | -5 | 3 | -3 - 5 | 0 | five | 5 | -5 | 2 | 4 - 5 | 0 | five | 5 | -5 | 5 | -5 - 5 | 0 | five | 5 | -5 | 5 | -5 - 5 | 0 | five | 5 | -5 | 0 | - 5 | 0 | five | 5 | -5 | | - 5 | 0 | five | 5 | -5 | | 0 - 6 | 6 | six | 5 | -5 | 1 | -1 - 6 | 6 | six | 5 | -5 | 2 | 2 - 6 | 6 | six | 5 | -5 | 3 | -3 - 6 | 6 | six | 5 | -5 | 2 | 4 - 6 | 6 | six | 5 | -5 | 5 | -5 - 6 | 6 | six | 5 | -5 | 5 | -5 - 6 | 6 | six | 5 | -5 | 0 | - 6 | 6 | six | 5 | -5 | | - 6 | 6 | six | 5 | -5 | | 0 - 7 | 7 | seven | 5 | -5 | 1 | -1 - 7 | 7 | seven | 5 | -5 | 2 | 2 - 7 | 7 | seven | 5 | -5 | 3 | -3 - 7 | 7 | seven | 5 | -5 | 2 | 4 - 7 | 7 | seven | 5 | -5 | 5 | -5 - 7 | 7 | seven | 5 | -5 | 5 | -5 - 7 | 7 | seven | 5 | -5 | 0 | - 7 | 7 | seven | 5 | -5 | | - 7 | 7 | seven | 5 | -5 | | 0 - 8 | 8 | eight | 5 | -5 | 1 | -1 - 8 | 8 | eight | 5 | -5 | 2 | 2 - 8 | 8 | eight | 5 | -5 | 3 | -3 - 8 | 8 | eight | 5 | -5 | 2 | 4 - 8 | 8 | eight | 5 | -5 | 5 | -5 - 8 | 8 | eight | 5 | -5 | 5 | -5 - 8 | 8 | eight | 5 | -5 | 0 | - 8 | 8 | eight | 5 | -5 | | - 8 | 8 | eight | 5 | -5 | | 0 - 0 | | zero | 5 | -5 | 1 | -1 - 0 | | zero | 5 | -5 | 2 | 2 - 0 | | zero | 5 | -5 | 3 | -3 - 0 | | zero | 5 | -5 | 2 | 4 - 0 | | zero | 5 | -5 | 5 | -5 - 0 | | zero | 5 | -5 | 5 | -5 - 0 | | zero | 5 | -5 | 0 | - 0 | | zero | 5 | -5 | | - 0 | | zero | 5 | -5 | | 0 - | | null | 5 | -5 | 1 | -1 - | | null | 5 | -5 | 2 | 2 - | | null | 5 | -5 | 3 | -3 - | | null | 5 | -5 | 2 | 4 - | | null | 5 | -5 | 5 | -5 - | | null | 5 | -5 | 5 | -5 - | | null | 5 | -5 | 0 | - | | null | 5 | -5 | | - | | null | 5 | -5 | | 0 - | 0 | zero | 5 | -5 | 1 | -1 - | 0 | zero | 5 | -5 | 2 | 2 - | 0 | zero | 5 | -5 | 3 | -3 - | 0 | zero | 5 | -5 | 2 | 4 - | 0 | zero | 5 | -5 | 5 | -5 - | 0 | zero | 5 | -5 | 5 | -5 - | 0 | zero | 5 | -5 | 0 | - | 0 | zero | 5 | -5 | | - | 0 | zero | 5 | -5 | | 0 - 1 | 4 | one | 0 | | 1 | -1 - 1 | 4 | one | 0 | | 2 | 2 - 1 | 4 | one | 0 | | 3 | -3 - 1 | 4 | one | 0 | | 2 | 4 - 1 | 4 | one | 0 | | 5 | -5 - 1 | 4 | one | 0 | | 5 | -5 - 1 | 4 | one | 0 | | 0 | - 1 | 4 | one | 0 | | | - 1 | 4 | one | 0 | | | 0 - 2 | 3 | two | 0 | | 1 | -1 - 2 | 3 | two | 0 | | 2 | 2 - 2 | 3 | two | 0 | | 3 | -3 - 2 | 3 | two | 0 | | 2 | 4 - 2 | 3 | two | 0 | | 5 | -5 - 2 | 3 | two | 0 | | 5 | -5 - 2 | 3 | two | 0 | | 0 | - 2 | 3 | two | 0 | | | - 2 | 3 | two | 0 | | | 0 - 3 | 2 | three | 0 | | 1 | -1 - 3 | 2 | three | 0 | | 2 | 2 - 3 | 2 | three | 0 | | 3 | -3 - 3 | 2 | three | 0 | | 2 | 4 - 3 | 2 | three | 0 | | 5 | -5 - 3 | 2 | three | 0 | | 5 | -5 - 3 | 2 | three | 0 | | 0 | - 3 | 2 | three | 0 | | | - 3 | 2 | three | 0 | | | 0 - 4 | 1 | four | 0 | | 1 | -1 - 4 | 1 | four | 0 | | 2 | 2 - 4 | 1 | four | 0 | | 3 | -3 - 4 | 1 | four | 0 | | 2 | 4 - 4 | 1 | four | 0 | | 5 | -5 - 4 | 1 | four | 0 | | 5 | -5 - 4 | 1 | four | 0 | | 0 | - 4 | 1 | four | 0 | | | - 4 | 1 | four | 0 | | | 0 - 5 | 0 | five | 0 | | 1 | -1 - 5 | 0 | five | 0 | | 2 | 2 - 5 | 0 | five | 0 | | 3 | -3 - 5 | 0 | five | 0 | | 2 | 4 - 5 | 0 | five | 0 | | 5 | -5 - 5 | 0 | five | 0 | | 5 | -5 - 5 | 0 | five | 0 | | 0 | - 5 | 0 | five | 0 | | | - 5 | 0 | five | 0 | | | 0 - 6 | 6 | six | 0 | | 1 | -1 - 6 | 6 | six | 0 | | 2 | 2 - 6 | 6 | six | 0 | | 3 | -3 - 6 | 6 | six | 0 | | 2 | 4 - 6 | 6 | six | 0 | | 5 | -5 - 6 | 6 | six | 0 | | 5 | -5 - 6 | 6 | six | 0 | | 0 | - 6 | 6 | six | 0 | | | - 6 | 6 | six | 0 | | | 0 - 7 | 7 | seven | 0 | | 1 | -1 - 7 | 7 | seven | 0 | | 2 | 2 - 7 | 7 | seven | 0 | | 3 | -3 - 7 | 7 | seven | 0 | | 2 | 4 - 7 | 7 | seven | 0 | | 5 | -5 - 7 | 7 | seven | 0 | | 5 | -5 - 7 | 7 | seven | 0 | | 0 | - 7 | 7 | seven | 0 | | | - 7 | 7 | seven | 0 | | | 0 - 8 | 8 | eight | 0 | | 1 | -1 - 8 | 8 | eight | 0 | | 2 | 2 - 8 | 8 | eight | 0 | | 3 | -3 - 8 | 8 | eight | 0 | | 2 | 4 - 8 | 8 | eight | 0 | | 5 | -5 - 8 | 8 | eight | 0 | | 5 | -5 - 8 | 8 | eight | 0 | | 0 | - 8 | 8 | eight | 0 | | | - 8 | 8 | eight | 0 | | | 0 - 0 | | zero | 0 | | 1 | -1 - 0 | | zero | 0 | | 2 | 2 - 0 | | zero | 0 | | 3 | -3 - 0 | | zero | 0 | | 2 | 4 - 0 | | zero | 0 | | 5 | -5 - 0 | | zero | 0 | | 5 | -5 - 0 | | zero | 0 | | 0 | - 0 | | zero | 0 | | | - 0 | | zero | 0 | | | 0 - | | null | 0 | | 1 | -1 - | | null | 0 | | 2 | 2 - | | null | 0 | | 3 | -3 - | | null | 0 | | 2 | 4 - | | null | 0 | | 5 | -5 - | | null | 0 | | 5 | -5 - | | null | 0 | | 0 | - | | null | 0 | | | - | | null | 0 | | | 0 - | 0 | zero | 0 | | 1 | -1 - | 0 | zero | 0 | | 2 | 2 - | 0 | zero | 0 | | 3 | -3 - | 0 | zero | 0 | | 2 | 4 - | 0 | zero | 0 | | 5 | -5 - | 0 | zero | 0 | | 5 | -5 - | 0 | zero | 0 | | 0 | - | 0 | zero | 0 | | | - | 0 | zero | 0 | | | 0 - 1 | 4 | one | | | 1 | -1 - 1 | 4 | one | | | 2 | 2 - 1 | 4 | one | | | 3 | -3 - 1 | 4 | one | | | 2 | 4 - 1 | 4 | one | | | 5 | -5 - 1 | 4 | one | | | 5 | -5 - 1 | 4 | one | | | 0 | - 1 | 4 | one | | | | - 1 | 4 | one | | | | 0 - 2 | 3 | two | | | 1 | -1 - 2 | 3 | two | | | 2 | 2 - 2 | 3 | two | | | 3 | -3 - 2 | 3 | two | | | 2 | 4 - 2 | 3 | two | | | 5 | -5 - 2 | 3 | two | | | 5 | -5 - 2 | 3 | two | | | 0 | - 2 | 3 | two | | | | - 2 | 3 | two | | | | 0 - 3 | 2 | three | | | 1 | -1 - 3 | 2 | three | | | 2 | 2 - 3 | 2 | three | | | 3 | -3 - 3 | 2 | three | | | 2 | 4 - 3 | 2 | three | | | 5 | -5 - 3 | 2 | three | | | 5 | -5 - 3 | 2 | three | | | 0 | - 3 | 2 | three | | | | - 3 | 2 | three | | | | 0 - 4 | 1 | four | | | 1 | -1 - 4 | 1 | four | | | 2 | 2 - 4 | 1 | four | | | 3 | -3 - 4 | 1 | four | | | 2 | 4 - 4 | 1 | four | | | 5 | -5 - 4 | 1 | four | | | 5 | -5 - 4 | 1 | four | | | 0 | - 4 | 1 | four | | | | - 4 | 1 | four | | | | 0 - 5 | 0 | five | | | 1 | -1 - 5 | 0 | five | | | 2 | 2 - 5 | 0 | five | | | 3 | -3 - 5 | 0 | five | | | 2 | 4 - 5 | 0 | five | | | 5 | -5 - 5 | 0 | five | | | 5 | -5 - 5 | 0 | five | | | 0 | - 5 | 0 | five | | | | - 5 | 0 | five | | | | 0 - 6 | 6 | six | | | 1 | -1 - 6 | 6 | six | | | 2 | 2 - 6 | 6 | six | | | 3 | -3 - 6 | 6 | six | | | 2 | 4 - 6 | 6 | six | | | 5 | -5 - 6 | 6 | six | | | 5 | -5 - 6 | 6 | six | | | 0 | - 6 | 6 | six | | | | - 6 | 6 | six | | | | 0 - 7 | 7 | seven | | | 1 | -1 - 7 | 7 | seven | | | 2 | 2 - 7 | 7 | seven | | | 3 | -3 - 7 | 7 | seven | | | 2 | 4 - 7 | 7 | seven | | | 5 | -5 - 7 | 7 | seven | | | 5 | -5 - 7 | 7 | seven | | | 0 | - 7 | 7 | seven | | | | - 7 | 7 | seven | | | | 0 - 8 | 8 | eight | | | 1 | -1 - 8 | 8 | eight | | | 2 | 2 - 8 | 8 | eight | | | 3 | -3 - 8 | 8 | eight | | | 2 | 4 - 8 | 8 | eight | | | 5 | -5 - 8 | 8 | eight | | | 5 | -5 - 8 | 8 | eight | | | 0 | - 8 | 8 | eight | | | | - 8 | 8 | eight | | | | 0 - 0 | | zero | | | 1 | -1 - 0 | | zero | | | 2 | 2 - 0 | | zero | | | 3 | -3 - 0 | | zero | | | 2 | 4 - 0 | | zero | | | 5 | -5 - 0 | | zero | | | 5 | -5 - 0 | | zero | | | 0 | - 0 | | zero | | | | - 0 | | zero | | | | 0 - | | null | | | 1 | -1 - | | null | | | 2 | 2 - | | null | | | 3 | -3 - | | null | | | 2 | 4 - | | null | | | 5 | -5 - | | null | | | 5 | -5 - | | null | | | 0 | - | | null | | | | - | | null | | | | 0 - | 0 | zero | | | 1 | -1 - | 0 | zero | | | 2 | 2 - | 0 | zero | | | 3 | -3 - | 0 | zero | | | 2 | 4 - | 0 | zero | | | 5 | -5 - | 0 | zero | | | 5 | -5 - | 0 | zero | | | 0 | - | 0 | zero | | | | - | 0 | zero | | | | 0 - 1 | 4 | one | | 0 | 1 | -1 - 1 | 4 | one | | 0 | 2 | 2 - 1 | 4 | one | | 0 | 3 | -3 - 1 | 4 | one | | 0 | 2 | 4 - 1 | 4 | one | | 0 | 5 | -5 - 1 | 4 | one | | 0 | 5 | -5 - 1 | 4 | one | | 0 | 0 | - 1 | 4 | one | | 0 | | - 1 | 4 | one | | 0 | | 0 - 2 | 3 | two | | 0 | 1 | -1 - 2 | 3 | two | | 0 | 2 | 2 - 2 | 3 | two | | 0 | 3 | -3 - 2 | 3 | two | | 0 | 2 | 4 - 2 | 3 | two | | 0 | 5 | -5 - 2 | 3 | two | | 0 | 5 | -5 - 2 | 3 | two | | 0 | 0 | - 2 | 3 | two | | 0 | | - 2 | 3 | two | | 0 | | 0 - 3 | 2 | three | | 0 | 1 | -1 - 3 | 2 | three | | 0 | 2 | 2 - 3 | 2 | three | | 0 | 3 | -3 - 3 | 2 | three | | 0 | 2 | 4 - 3 | 2 | three | | 0 | 5 | -5 - 3 | 2 | three | | 0 | 5 | -5 - 3 | 2 | three | | 0 | 0 | - 3 | 2 | three | | 0 | | - 3 | 2 | three | | 0 | | 0 - 4 | 1 | four | | 0 | 1 | -1 - 4 | 1 | four | | 0 | 2 | 2 - 4 | 1 | four | | 0 | 3 | -3 - 4 | 1 | four | | 0 | 2 | 4 - 4 | 1 | four | | 0 | 5 | -5 - 4 | 1 | four | | 0 | 5 | -5 - 4 | 1 | four | | 0 | 0 | - 4 | 1 | four | | 0 | | - 4 | 1 | four | | 0 | | 0 - 5 | 0 | five | | 0 | 1 | -1 - 5 | 0 | five | | 0 | 2 | 2 - 5 | 0 | five | | 0 | 3 | -3 - 5 | 0 | five | | 0 | 2 | 4 - 5 | 0 | five | | 0 | 5 | -5 - 5 | 0 | five | | 0 | 5 | -5 - 5 | 0 | five | | 0 | 0 | - 5 | 0 | five | | 0 | | - 5 | 0 | five | | 0 | | 0 - 6 | 6 | six | | 0 | 1 | -1 - 6 | 6 | six | | 0 | 2 | 2 - 6 | 6 | six | | 0 | 3 | -3 - 6 | 6 | six | | 0 | 2 | 4 - 6 | 6 | six | | 0 | 5 | -5 - 6 | 6 | six | | 0 | 5 | -5 - 6 | 6 | six | | 0 | 0 | - 6 | 6 | six | | 0 | | - 6 | 6 | six | | 0 | | 0 - 7 | 7 | seven | | 0 | 1 | -1 - 7 | 7 | seven | | 0 | 2 | 2 - 7 | 7 | seven | | 0 | 3 | -3 - 7 | 7 | seven | | 0 | 2 | 4 - 7 | 7 | seven | | 0 | 5 | -5 - 7 | 7 | seven | | 0 | 5 | -5 - 7 | 7 | seven | | 0 | 0 | - 7 | 7 | seven | | 0 | | - 7 | 7 | seven | | 0 | | 0 - 8 | 8 | eight | | 0 | 1 | -1 - 8 | 8 | eight | | 0 | 2 | 2 - 8 | 8 | eight | | 0 | 3 | -3 - 8 | 8 | eight | | 0 | 2 | 4 - 8 | 8 | eight | | 0 | 5 | -5 - 8 | 8 | eight | | 0 | 5 | -5 - 8 | 8 | eight | | 0 | 0 | - 8 | 8 | eight | | 0 | | - 8 | 8 | eight | | 0 | | 0 - 0 | | zero | | 0 | 1 | -1 - 0 | | zero | | 0 | 2 | 2 - 0 | | zero | | 0 | 3 | -3 - 0 | | zero | | 0 | 2 | 4 - 0 | | zero | | 0 | 5 | -5 - 0 | | zero | | 0 | 5 | -5 - 0 | | zero | | 0 | 0 | - 0 | | zero | | 0 | | - 0 | | zero | | 0 | | 0 - | | null | | 0 | 1 | -1 - | | null | | 0 | 2 | 2 - | | null | | 0 | 3 | -3 - | | null | | 0 | 2 | 4 - | | null | | 0 | 5 | -5 - | | null | | 0 | 5 | -5 - | | null | | 0 | 0 | - | | null | | 0 | | - | | null | | 0 | | 0 - | 0 | zero | | 0 | 1 | -1 - | 0 | zero | | 0 | 2 | 2 - | 0 | zero | | 0 | 3 | -3 - | 0 | zero | | 0 | 2 | 4 - | 0 | zero | | 0 | 5 | -5 - | 0 | zero | | 0 | 5 | -5 - | 0 | zero | | 0 | 0 | - | 0 | zero | | 0 | | - | 0 | zero | | 0 | | 0 -(891 rows) - --- --- --- Inner joins (equi-joins) --- --- --- --- Inner joins (equi-joins) with USING clause --- The USING syntax changes the shape of the resulting table --- by including a column in the USING clause only once in the result. --- --- Inner equi-join on specified column -SELECT * - FROM J1_TBL INNER JOIN J2_TBL USING (i); - i | j | t | k ----+---+-------+---- - 0 | | zero | - 1 | 4 | one | -1 - 2 | 3 | two | 2 - 2 | 3 | two | 4 - 3 | 2 | three | -3 - 5 | 0 | five | -5 - 5 | 0 | five | -5 -(7 rows) - --- Same as above, slightly different syntax -SELECT * - FROM J1_TBL JOIN J2_TBL USING (i); - i | j | t | k ----+---+-------+---- - 0 | | zero | - 1 | 4 | one | -1 - 2 | 3 | two | 2 - 2 | 3 | two | 4 - 3 | 2 | three | -3 - 5 | 0 | five | -5 - 5 | 0 | five | -5 -(7 rows) - -SELECT * - FROM J1_TBL t1 (a, b, c) JOIN J2_TBL t2 (a, d) USING (a) - ORDER BY a, d; - a | b | c | d ----+---+-------+---- - 0 | | zero | - 1 | 4 | one | -1 - 2 | 3 | two | 2 - 2 | 3 | two | 4 - 3 | 2 | three | -3 - 5 | 0 | five | -5 - 5 | 0 | five | -5 -(7 rows) - -SELECT * - FROM J1_TBL t1 (a, b, c) JOIN J2_TBL t2 (a, b) USING (b) - ORDER BY b, t1.a; - b | a | c | a ----+---+-------+--- - 0 | 5 | five | - 0 | | zero | - 2 | 3 | three | 2 - 4 | 1 | one | 2 -(4 rows) - --- test join using aliases -SELECT * FROM J1_TBL JOIN J2_TBL USING (i) WHERE J1_TBL.t = 'one'; -- ok - i | j | t | k ----+---+-----+---- - 1 | 4 | one | -1 -(1 row) - -SELECT * FROM J1_TBL JOIN J2_TBL USING (i) AS x WHERE J1_TBL.t = 'one'; -- ok - i | j | t | k ----+---+-----+---- - 1 | 4 | one | -1 -(1 row) - -SELECT * FROM (J1_TBL JOIN J2_TBL USING (i)) AS x WHERE J1_TBL.t = 'one'; -- error -ERROR: invalid reference to FROM-clause entry for table "j1_tbl" -LINE 1: ... * FROM (J1_TBL JOIN J2_TBL USING (i)) AS x WHERE J1_TBL.t =... - ^ -DETAIL: There is an entry for table "j1_tbl", but it cannot be referenced from this part of the query. -SELECT * FROM J1_TBL JOIN J2_TBL USING (i) AS x WHERE x.i = 1; -- ok - i | j | t | k ----+---+-----+---- - 1 | 4 | one | -1 -(1 row) - -SELECT * FROM J1_TBL JOIN J2_TBL USING (i) AS x WHERE x.t = 'one'; -- error -ERROR: column x.t does not exist -LINE 1: ...CT * FROM J1_TBL JOIN J2_TBL USING (i) AS x WHERE x.t = 'one... - ^ -SELECT * FROM (J1_TBL JOIN J2_TBL USING (i) AS x) AS xx WHERE x.i = 1; -- error (XXX could use better hint) -ERROR: missing FROM-clause entry for table "x" -LINE 1: ...ROM (J1_TBL JOIN J2_TBL USING (i) AS x) AS xx WHERE x.i = 1; - ^ -SELECT * FROM J1_TBL a1 JOIN J2_TBL a2 USING (i) AS a1; -- error -ERROR: table name "a1" specified more than once -SELECT x.* FROM J1_TBL JOIN J2_TBL USING (i) AS x WHERE J1_TBL.t = 'one'; - i ---- - 1 -(1 row) - -SELECT ROW(x.*) FROM J1_TBL JOIN J2_TBL USING (i) AS x WHERE J1_TBL.t = 'one'; - row ------ - (1) -(1 row) - -SELECT row_to_json(x.*) FROM J1_TBL JOIN J2_TBL USING (i) AS x WHERE J1_TBL.t = 'one'; - row_to_json -------------- - {"i":1} -(1 row) - --- --- NATURAL JOIN --- Inner equi-join on all columns with the same name --- -SELECT * - FROM J1_TBL NATURAL JOIN J2_TBL; - i | j | t | k ----+---+-------+---- - 0 | | zero | - 1 | 4 | one | -1 - 2 | 3 | two | 2 - 2 | 3 | two | 4 - 3 | 2 | three | -3 - 5 | 0 | five | -5 - 5 | 0 | five | -5 -(7 rows) - -SELECT * - FROM J1_TBL t1 (a, b, c) NATURAL JOIN J2_TBL t2 (a, d); - a | b | c | d ----+---+-------+---- - 0 | | zero | - 1 | 4 | one | -1 - 2 | 3 | two | 2 - 2 | 3 | two | 4 - 3 | 2 | three | -3 - 5 | 0 | five | -5 - 5 | 0 | five | -5 -(7 rows) - -SELECT * - FROM J1_TBL t1 (a, b, c) NATURAL JOIN J2_TBL t2 (d, a); - a | b | c | d ----+---+------+--- - 0 | | zero | - 2 | 3 | two | 2 - 4 | 1 | four | 2 -(3 rows) - --- mismatch number of columns --- currently, Postgres will fill in with underlying names -SELECT * - FROM J1_TBL t1 (a, b) NATURAL JOIN J2_TBL t2 (a); - a | b | t | k ----+---+-------+---- - 0 | | zero | - 1 | 4 | one | -1 - 2 | 3 | two | 2 - 2 | 3 | two | 4 - 3 | 2 | three | -3 - 5 | 0 | five | -5 - 5 | 0 | five | -5 -(7 rows) - --- --- Inner joins (equi-joins) --- -SELECT * - FROM J1_TBL JOIN J2_TBL ON (J1_TBL.i = J2_TBL.i); - i | j | t | i | k ----+---+-------+---+---- - 0 | | zero | 0 | - 1 | 4 | one | 1 | -1 - 2 | 3 | two | 2 | 2 - 2 | 3 | two | 2 | 4 - 3 | 2 | three | 3 | -3 - 5 | 0 | five | 5 | -5 - 5 | 0 | five | 5 | -5 -(7 rows) - -SELECT * - FROM J1_TBL JOIN J2_TBL ON (J1_TBL.i = J2_TBL.k); - i | j | t | i | k ----+---+------+---+--- - 0 | | zero | | 0 - 2 | 3 | two | 2 | 2 - 4 | 1 | four | 2 | 4 -(3 rows) - --- --- Non-equi-joins --- -SELECT * - FROM J1_TBL JOIN J2_TBL ON (J1_TBL.i <= J2_TBL.k); - i | j | t | i | k ----+---+-------+---+--- - 1 | 4 | one | 2 | 2 - 2 | 3 | two | 2 | 2 - 0 | | zero | 2 | 2 - 1 | 4 | one | 2 | 4 - 2 | 3 | two | 2 | 4 - 3 | 2 | three | 2 | 4 - 4 | 1 | four | 2 | 4 - 0 | | zero | 2 | 4 - 0 | | zero | | 0 -(9 rows) - --- --- Outer joins --- Note that OUTER is a noise word --- -SELECT * - FROM J1_TBL LEFT OUTER JOIN J2_TBL USING (i) - ORDER BY i, k, t; - i | j | t | k ----+---+-------+---- - 0 | | zero | - 1 | 4 | one | -1 - 2 | 3 | two | 2 - 2 | 3 | two | 4 - 3 | 2 | three | -3 - 4 | 1 | four | - 5 | 0 | five | -5 - 5 | 0 | five | -5 - 6 | 6 | six | - 7 | 7 | seven | - 8 | 8 | eight | - | | null | - | 0 | zero | -(13 rows) - -SELECT * - FROM J1_TBL LEFT JOIN J2_TBL USING (i) - ORDER BY i, k, t; - i | j | t | k ----+---+-------+---- - 0 | | zero | - 1 | 4 | one | -1 - 2 | 3 | two | 2 - 2 | 3 | two | 4 - 3 | 2 | three | -3 - 4 | 1 | four | - 5 | 0 | five | -5 - 5 | 0 | five | -5 - 6 | 6 | six | - 7 | 7 | seven | - 8 | 8 | eight | - | | null | - | 0 | zero | -(13 rows) - -SELECT * - FROM J1_TBL RIGHT OUTER JOIN J2_TBL USING (i); - i | j | t | k ----+---+-------+---- - 0 | | zero | - 1 | 4 | one | -1 - 2 | 3 | two | 2 - 2 | 3 | two | 4 - 3 | 2 | three | -3 - 5 | 0 | five | -5 - 5 | 0 | five | -5 - | | | - | | | 0 -(9 rows) - -SELECT * - FROM J1_TBL RIGHT JOIN J2_TBL USING (i); - i | j | t | k ----+---+-------+---- - 0 | | zero | - 1 | 4 | one | -1 - 2 | 3 | two | 2 - 2 | 3 | two | 4 - 3 | 2 | three | -3 - 5 | 0 | five | -5 - 5 | 0 | five | -5 - | | | - | | | 0 -(9 rows) - -SELECT * - FROM J1_TBL FULL OUTER JOIN J2_TBL USING (i) - ORDER BY i, k, t; - i | j | t | k ----+---+-------+---- - 0 | | zero | - 1 | 4 | one | -1 - 2 | 3 | two | 2 - 2 | 3 | two | 4 - 3 | 2 | three | -3 - 4 | 1 | four | - 5 | 0 | five | -5 - 5 | 0 | five | -5 - 6 | 6 | six | - 7 | 7 | seven | - 8 | 8 | eight | - | | | 0 - | | null | - | 0 | zero | - | | | -(15 rows) - -SELECT * - FROM J1_TBL FULL JOIN J2_TBL USING (i) - ORDER BY i, k, t; - i | j | t | k ----+---+-------+---- - 0 | | zero | - 1 | 4 | one | -1 - 2 | 3 | two | 2 - 2 | 3 | two | 4 - 3 | 2 | three | -3 - 4 | 1 | four | - 5 | 0 | five | -5 - 5 | 0 | five | -5 - 6 | 6 | six | - 7 | 7 | seven | - 8 | 8 | eight | - | | | 0 - | | null | - | 0 | zero | - | | | -(15 rows) - -SELECT * - FROM J1_TBL LEFT JOIN J2_TBL USING (i) WHERE (k = 1); - i | j | t | k ----+---+---+--- -(0 rows) - -SELECT * - FROM J1_TBL LEFT JOIN J2_TBL USING (i) WHERE (i = 1); - i | j | t | k ----+---+-----+---- - 1 | 4 | one | -1 -(1 row) - --- --- semijoin selectivity for <> --- -explain (costs off) -select * from tenk1 a, tenk1 b -where exists(select * from tenk1 c - where b.twothousand = c.twothousand and b.fivethous <> c.fivethous) - and a.tenthous = b.tenthous and a.tenthous < 5000; - QUERY PLAN ------------------------------------------------ - Hash Semi Join - Hash Cond: (b.twothousand = c.twothousand) - Join Filter: (b.fivethous <> c.fivethous) - -> Hash Join - Hash Cond: (b.tenthous = a.tenthous) - -> Seq Scan on tenk1 b - -> Hash - -> Seq Scan on tenk1 a - Filter: (tenthous < 5000) - -> Hash - -> Seq Scan on tenk1 c -(11 rows) - --- --- More complicated constructs --- --- --- Multiway full join --- -CREATE TABLE t1 (name TEXT, n INTEGER); -CREATE TABLE t2 (name TEXT, n INTEGER); -CREATE TABLE t3 (name TEXT, n INTEGER); -INSERT INTO t1 VALUES ( 'bb', 11 ); -INSERT INTO t2 VALUES ( 'bb', 12 ); -INSERT INTO t2 VALUES ( 'cc', 22 ); -INSERT INTO t2 VALUES ( 'ee', 42 ); -INSERT INTO t3 VALUES ( 'bb', 13 ); -INSERT INTO t3 VALUES ( 'cc', 23 ); -INSERT INTO t3 VALUES ( 'dd', 33 ); -SELECT * FROM t1 FULL JOIN t2 USING (name) FULL JOIN t3 USING (name); - name | n | n | n -------+----+----+---- - bb | 11 | 12 | 13 - cc | | 22 | 23 - dd | | | 33 - ee | | 42 | -(4 rows) - --- --- Test interactions of join syntax and subqueries --- --- Basic cases (we expect planner to pull up the subquery here) -SELECT * FROM -(SELECT * FROM t2) as s2 -INNER JOIN -(SELECT * FROM t3) s3 -USING (name); - name | n | n -------+----+---- - bb | 12 | 13 - cc | 22 | 23 -(2 rows) - -SELECT * FROM -(SELECT * FROM t2) as s2 -LEFT JOIN -(SELECT * FROM t3) s3 -USING (name); - name | n | n -------+----+---- - bb | 12 | 13 - cc | 22 | 23 - ee | 42 | -(3 rows) - -SELECT * FROM -(SELECT * FROM t2) as s2 -FULL JOIN -(SELECT * FROM t3) s3 -USING (name); - name | n | n -------+----+---- - bb | 12 | 13 - cc | 22 | 23 - dd | | 33 - ee | 42 | -(4 rows) - --- Cases with non-nullable expressions in subquery results; --- make sure these go to null as expected -SELECT * FROM -(SELECT name, n as s2_n, 2 as s2_2 FROM t2) as s2 -NATURAL INNER JOIN -(SELECT name, n as s3_n, 3 as s3_2 FROM t3) s3; - name | s2_n | s2_2 | s3_n | s3_2 -------+------+------+------+------ - bb | 12 | 2 | 13 | 3 - cc | 22 | 2 | 23 | 3 -(2 rows) - -SELECT * FROM -(SELECT name, n as s2_n, 2 as s2_2 FROM t2) as s2 -NATURAL LEFT JOIN -(SELECT name, n as s3_n, 3 as s3_2 FROM t3) s3; - name | s2_n | s2_2 | s3_n | s3_2 -------+------+------+------+------ - bb | 12 | 2 | 13 | 3 - cc | 22 | 2 | 23 | 3 - ee | 42 | 2 | | -(3 rows) - -SELECT * FROM -(SELECT name, n as s2_n, 2 as s2_2 FROM t2) as s2 -NATURAL FULL JOIN -(SELECT name, n as s3_n, 3 as s3_2 FROM t3) s3; - name | s2_n | s2_2 | s3_n | s3_2 -------+------+------+------+------ - bb | 12 | 2 | 13 | 3 - cc | 22 | 2 | 23 | 3 - dd | | | 33 | 3 - ee | 42 | 2 | | -(4 rows) - -SELECT * FROM -(SELECT name, n as s1_n, 1 as s1_1 FROM t1) as s1 -NATURAL INNER JOIN -(SELECT name, n as s2_n, 2 as s2_2 FROM t2) as s2 -NATURAL INNER JOIN -(SELECT name, n as s3_n, 3 as s3_2 FROM t3) s3; - name | s1_n | s1_1 | s2_n | s2_2 | s3_n | s3_2 -------+------+------+------+------+------+------ - bb | 11 | 1 | 12 | 2 | 13 | 3 -(1 row) - -SELECT * FROM -(SELECT name, n as s1_n, 1 as s1_1 FROM t1) as s1 -NATURAL FULL JOIN -(SELECT name, n as s2_n, 2 as s2_2 FROM t2) as s2 -NATURAL FULL JOIN -(SELECT name, n as s3_n, 3 as s3_2 FROM t3) s3; - name | s1_n | s1_1 | s2_n | s2_2 | s3_n | s3_2 -------+------+------+------+------+------+------ - bb | 11 | 1 | 12 | 2 | 13 | 3 - cc | | | 22 | 2 | 23 | 3 - dd | | | | | 33 | 3 - ee | | | 42 | 2 | | -(4 rows) - -SELECT * FROM -(SELECT name, n as s1_n FROM t1) as s1 -NATURAL FULL JOIN - (SELECT * FROM - (SELECT name, n as s2_n FROM t2) as s2 - NATURAL FULL JOIN - (SELECT name, n as s3_n FROM t3) as s3 - ) ss2; - name | s1_n | s2_n | s3_n -------+------+------+------ - bb | 11 | 12 | 13 - cc | | 22 | 23 - dd | | | 33 - ee | | 42 | -(4 rows) - -SELECT * FROM -(SELECT name, n as s1_n FROM t1) as s1 -NATURAL FULL JOIN - (SELECT * FROM - (SELECT name, n as s2_n, 2 as s2_2 FROM t2) as s2 - NATURAL FULL JOIN - (SELECT name, n as s3_n FROM t3) as s3 - ) ss2; - name | s1_n | s2_n | s2_2 | s3_n -------+------+------+------+------ - bb | 11 | 12 | 2 | 13 - cc | | 22 | 2 | 23 - dd | | | | 33 - ee | | 42 | 2 | -(4 rows) - --- Constants as join keys can also be problematic -SELECT * FROM - (SELECT name, n as s1_n FROM t1) as s1 -FULL JOIN - (SELECT name, 2 as s2_n FROM t2) as s2 -ON (s1_n = s2_n); - name | s1_n | name | s2_n -------+------+------+------ - | | bb | 2 - | | cc | 2 - | | ee | 2 - bb | 11 | | -(4 rows) - --- Test for propagation of nullability constraints into sub-joins -create temp table x (x1 int, x2 int); -insert into x values (1,11); -insert into x values (2,22); -insert into x values (3,null); -insert into x values (4,44); -insert into x values (5,null); -create temp table y (y1 int, y2 int); -insert into y values (1,111); -insert into y values (2,222); -insert into y values (3,333); -insert into y values (4,null); -select * from x; - x1 | x2 -----+---- - 1 | 11 - 2 | 22 - 3 | - 4 | 44 - 5 | -(5 rows) - -select * from y; - y1 | y2 -----+----- - 1 | 111 - 2 | 222 - 3 | 333 - 4 | -(4 rows) - -select * from x left join y on (x1 = y1 and x2 is not null); - x1 | x2 | y1 | y2 -----+----+----+----- - 1 | 11 | 1 | 111 - 2 | 22 | 2 | 222 - 3 | | | - 4 | 44 | 4 | - 5 | | | -(5 rows) - -select * from x left join y on (x1 = y1 and y2 is not null); - x1 | x2 | y1 | y2 -----+----+----+----- - 1 | 11 | 1 | 111 - 2 | 22 | 2 | 222 - 3 | | 3 | 333 - 4 | 44 | | - 5 | | | -(5 rows) - -select * from (x left join y on (x1 = y1)) left join x xx(xx1,xx2) -on (x1 = xx1); - x1 | x2 | y1 | y2 | xx1 | xx2 -----+----+----+-----+-----+----- - 1 | 11 | 1 | 111 | 1 | 11 - 2 | 22 | 2 | 222 | 2 | 22 - 3 | | 3 | 333 | 3 | - 4 | 44 | 4 | | 4 | 44 - 5 | | | | 5 | -(5 rows) - -select * from (x left join y on (x1 = y1)) left join x xx(xx1,xx2) -on (x1 = xx1 and x2 is not null); - x1 | x2 | y1 | y2 | xx1 | xx2 -----+----+----+-----+-----+----- - 1 | 11 | 1 | 111 | 1 | 11 - 2 | 22 | 2 | 222 | 2 | 22 - 3 | | 3 | 333 | | - 4 | 44 | 4 | | 4 | 44 - 5 | | | | | -(5 rows) - -select * from (x left join y on (x1 = y1)) left join x xx(xx1,xx2) -on (x1 = xx1 and y2 is not null); - x1 | x2 | y1 | y2 | xx1 | xx2 -----+----+----+-----+-----+----- - 1 | 11 | 1 | 111 | 1 | 11 - 2 | 22 | 2 | 222 | 2 | 22 - 3 | | 3 | 333 | 3 | - 4 | 44 | 4 | | | - 5 | | | | | -(5 rows) - -select * from (x left join y on (x1 = y1)) left join x xx(xx1,xx2) -on (x1 = xx1 and xx2 is not null); - x1 | x2 | y1 | y2 | xx1 | xx2 -----+----+----+-----+-----+----- - 1 | 11 | 1 | 111 | 1 | 11 - 2 | 22 | 2 | 222 | 2 | 22 - 3 | | 3 | 333 | | - 4 | 44 | 4 | | 4 | 44 - 5 | | | | | -(5 rows) - --- these should NOT give the same answers as above -select * from (x left join y on (x1 = y1)) left join x xx(xx1,xx2) -on (x1 = xx1) where (x2 is not null); - x1 | x2 | y1 | y2 | xx1 | xx2 -----+----+----+-----+-----+----- - 1 | 11 | 1 | 111 | 1 | 11 - 2 | 22 | 2 | 222 | 2 | 22 - 4 | 44 | 4 | | 4 | 44 -(3 rows) - -select * from (x left join y on (x1 = y1)) left join x xx(xx1,xx2) -on (x1 = xx1) where (y2 is not null); - x1 | x2 | y1 | y2 | xx1 | xx2 -----+----+----+-----+-----+----- - 1 | 11 | 1 | 111 | 1 | 11 - 2 | 22 | 2 | 222 | 2 | 22 - 3 | | 3 | 333 | 3 | -(3 rows) - -select * from (x left join y on (x1 = y1)) left join x xx(xx1,xx2) -on (x1 = xx1) where (xx2 is not null); - x1 | x2 | y1 | y2 | xx1 | xx2 -----+----+----+-----+-----+----- - 1 | 11 | 1 | 111 | 1 | 11 - 2 | 22 | 2 | 222 | 2 | 22 - 4 | 44 | 4 | | 4 | 44 -(3 rows) - --- --- regression test: check for bug with propagation of implied equality --- to outside an IN --- -select count(*) from tenk1 a where unique1 in - (select unique1 from tenk1 b join tenk1 c using (unique1) - where b.unique2 = 42); - count -------- - 1 -(1 row) - --- --- regression test: check for failure to generate a plan with multiple --- degenerate IN clauses --- -select count(*) from tenk1 x where - x.unique1 in (select a.f1 from int4_tbl a,float8_tbl b where a.f1=b.f1) and - x.unique1 = 0 and - x.unique1 in (select aa.f1 from int4_tbl aa,float8_tbl bb where aa.f1=bb.f1); - count -------- - 1 -(1 row) - --- try that with GEQO too -begin; -set geqo = on; -set geqo_threshold = 2; -select count(*) from tenk1 x where - x.unique1 in (select a.f1 from int4_tbl a,float8_tbl b where a.f1=b.f1) and - x.unique1 = 0 and - x.unique1 in (select aa.f1 from int4_tbl aa,float8_tbl bb where aa.f1=bb.f1); - count -------- - 1 -(1 row) - -rollback; --- --- regression test: be sure we cope with proven-dummy append rels --- -explain (costs off) -select aa, bb, unique1, unique1 - from tenk1 right join b_star on aa = unique1 - where bb < bb and bb is null; - QUERY PLAN --------------------------- - Result - One-Time Filter: false -(2 rows) - -select aa, bb, unique1, unique1 - from tenk1 right join b_star on aa = unique1 - where bb < bb and bb is null; - aa | bb | unique1 | unique1 -----+----+---------+--------- -(0 rows) - --- --- regression test: check handling of empty-FROM subquery underneath outer join --- -explain (costs off) -select * from int8_tbl i1 left join (int8_tbl i2 join - (select 123 as x) ss on i2.q1 = x) on i1.q2 = i2.q2 -order by 1, 2; - QUERY PLAN -------------------------------------------- - Sort - Sort Key: i1.q1, i1.q2 - -> Hash Left Join - Hash Cond: (i1.q2 = i2.q2) - -> Seq Scan on int8_tbl i1 - -> Hash - -> Seq Scan on int8_tbl i2 - Filter: (q1 = 123) -(8 rows) - -select * from int8_tbl i1 left join (int8_tbl i2 join - (select 123 as x) ss on i2.q1 = x) on i1.q2 = i2.q2 -order by 1, 2; - q1 | q2 | q1 | q2 | x -------------------+-------------------+-----+------------------+----- - 123 | 456 | 123 | 456 | 123 - 123 | 4567890123456789 | 123 | 4567890123456789 | 123 - 4567890123456789 | -4567890123456789 | | | - 4567890123456789 | 123 | | | - 4567890123456789 | 4567890123456789 | 123 | 4567890123456789 | 123 -(5 rows) - --- --- regression test: check a case where join_clause_is_movable_into() --- used to give an imprecise result, causing an assertion failure --- -select count(*) -from - (select t3.tenthous as x1, coalesce(t1.stringu1, t2.stringu1) as x2 - from tenk1 t1 - left join tenk1 t2 on t1.unique1 = t2.unique1 - join tenk1 t3 on t1.unique2 = t3.unique2) ss, - tenk1 t4, - tenk1 t5 -where t4.thousand = t5.unique1 and ss.x1 = t4.tenthous and ss.x2 = t5.stringu1; - count -------- - 1000 -(1 row) - --- --- regression test: check a case where we formerly missed including an EC --- enforcement clause because it was expected to be handled at scan level --- -explain (costs off) -select a.f1, b.f1, t.thousand, t.tenthous from - tenk1 t, - (select sum(f1)+1 as f1 from int4_tbl i4a) a, - (select sum(f1) as f1 from int4_tbl i4b) b -where b.f1 = t.thousand and a.f1 = b.f1 and (a.f1+b.f1+999) = t.tenthous; - QUERY PLAN ------------------------------------------------------------------------------------------------------------------ - Nested Loop - -> Nested Loop - Join Filter: ((sum(i4b.f1)) = ((sum(i4a.f1) + 1))) - -> Aggregate - -> Seq Scan on int4_tbl i4a - -> Aggregate - -> Seq Scan on int4_tbl i4b - -> Index Only Scan using tenk1_thous_tenthous on tenk1 t - Index Cond: ((thousand = (sum(i4b.f1))) AND (tenthous = ((((sum(i4a.f1) + 1)) + (sum(i4b.f1))) + 999))) -(9 rows) - -select a.f1, b.f1, t.thousand, t.tenthous from - tenk1 t, - (select sum(f1)+1 as f1 from int4_tbl i4a) a, - (select sum(f1) as f1 from int4_tbl i4b) b -where b.f1 = t.thousand and a.f1 = b.f1 and (a.f1+b.f1+999) = t.tenthous; - f1 | f1 | thousand | tenthous -----+----+----------+---------- -(0 rows) - --- --- checks for correct handling of quals in multiway outer joins --- -explain (costs off) -select t1.f1 -from int4_tbl t1, int4_tbl t2 - left join int4_tbl t3 on t3.f1 > 0 - left join int4_tbl t4 on t3.f1 > 1 -where t4.f1 is null; - QUERY PLAN -------------------------------------------------------- - Nested Loop - -> Nested Loop Left Join - Filter: (t4.f1 IS NULL) - -> Seq Scan on int4_tbl t2 - -> Materialize - -> Nested Loop Left Join - Join Filter: (t3.f1 > 1) - -> Seq Scan on int4_tbl t3 - Filter: (f1 > 0) - -> Materialize - -> Seq Scan on int4_tbl t4 - -> Seq Scan on int4_tbl t1 -(12 rows) - -select t1.f1 -from int4_tbl t1, int4_tbl t2 - left join int4_tbl t3 on t3.f1 > 0 - left join int4_tbl t4 on t3.f1 > 1 -where t4.f1 is null; - f1 ----- -(0 rows) - -explain (costs off) -select * -from int4_tbl t1 left join int4_tbl t2 on true - left join int4_tbl t3 on t2.f1 > 0 - left join int4_tbl t4 on t3.f1 > 0; - QUERY PLAN -------------------------------------------------------- - Nested Loop Left Join - -> Seq Scan on int4_tbl t1 - -> Materialize - -> Nested Loop Left Join - Join Filter: (t3.f1 > 0) - -> Nested Loop Left Join - Join Filter: (t2.f1 > 0) - -> Seq Scan on int4_tbl t2 - -> Materialize - -> Seq Scan on int4_tbl t3 - -> Materialize - -> Seq Scan on int4_tbl t4 -(12 rows) - -explain (costs off) -select * from onek t1 - left join onek t2 on t1.unique1 = t2.unique1 - left join onek t3 on t2.unique1 != t3.unique1 - left join onek t4 on t3.unique1 = t4.unique1; - QUERY PLAN ----------------------------------------------------- - Nested Loop Left Join - Join Filter: (t2.unique1 <> t3.unique1) - -> Hash Left Join - Hash Cond: (t1.unique1 = t2.unique1) - -> Seq Scan on onek t1 - -> Hash - -> Seq Scan on onek t2 - -> Materialize - -> Hash Left Join - Hash Cond: (t3.unique1 = t4.unique1) - -> Seq Scan on onek t3 - -> Hash - -> Seq Scan on onek t4 -(13 rows) - -explain (costs off) -select * from int4_tbl t1 - left join (select now() from int4_tbl t2 - left join int4_tbl t3 on t2.f1 = t3.f1 - left join int4_tbl t4 on t3.f1 = t4.f1) s on true - inner join int4_tbl t5 on true; - QUERY PLAN -------------------------------------------------------------- - Nested Loop - -> Nested Loop Left Join - -> Seq Scan on int4_tbl t1 - -> Materialize - -> Hash Left Join - Hash Cond: (t3.f1 = t4.f1) - -> Hash Left Join - Hash Cond: (t2.f1 = t3.f1) - -> Seq Scan on int4_tbl t2 - -> Hash - -> Seq Scan on int4_tbl t3 - -> Hash - -> Seq Scan on int4_tbl t4 - -> Materialize - -> Seq Scan on int4_tbl t5 -(15 rows) - -explain (costs off) -select * from int4_tbl t1 - left join int4_tbl t2 on true - left join int4_tbl t3 on true - left join int4_tbl t4 on t2.f1 = t3.f1; - QUERY PLAN -------------------------------------------------- - Nested Loop Left Join - Join Filter: (t2.f1 = t3.f1) - -> Nested Loop Left Join - -> Nested Loop Left Join - -> Seq Scan on int4_tbl t1 - -> Materialize - -> Seq Scan on int4_tbl t2 - -> Materialize - -> Seq Scan on int4_tbl t3 - -> Materialize - -> Seq Scan on int4_tbl t4 -(11 rows) - -explain (costs off) -select * from int4_tbl t1 - left join int4_tbl t2 on true - left join int4_tbl t3 on t2.f1 = t3.f1 - left join int4_tbl t4 on t3.f1 != t4.f1; - QUERY PLAN -------------------------------------------------------- - Nested Loop Left Join - -> Seq Scan on int4_tbl t1 - -> Materialize - -> Nested Loop Left Join - Join Filter: (t3.f1 <> t4.f1) - -> Hash Left Join - Hash Cond: (t2.f1 = t3.f1) - -> Seq Scan on int4_tbl t2 - -> Hash - -> Seq Scan on int4_tbl t3 - -> Materialize - -> Seq Scan on int4_tbl t4 -(12 rows) - -explain (costs off) -select * from int4_tbl t1 - left join (int4_tbl t2 left join int4_tbl t3 on t2.f1 > 0) on t2.f1 > 1 - left join int4_tbl t4 on t2.f1 > 2 and t3.f1 > 3 -where t1.f1 = coalesce(t2.f1, 1); - QUERY PLAN ----------------------------------------------------- - Nested Loop Left Join - Join Filter: ((t2.f1 > 2) AND (t3.f1 > 3)) - -> Nested Loop Left Join - Join Filter: (t2.f1 > 0) - -> Nested Loop Left Join - Filter: (t1.f1 = COALESCE(t2.f1, 1)) - -> Seq Scan on int4_tbl t1 - -> Materialize - -> Seq Scan on int4_tbl t2 - Filter: (f1 > 1) - -> Seq Scan on int4_tbl t3 - -> Materialize - -> Seq Scan on int4_tbl t4 -(13 rows) - -explain (costs off) -select * from int4_tbl t1 - left join ((select t2.f1 from int4_tbl t2 - left join int4_tbl t3 on t2.f1 > 0 - where t3.f1 is null) s - left join tenk1 t4 on s.f1 > 1) - on s.f1 = t1.f1; - QUERY PLAN -------------------------------------------------- - Hash Right Join - Hash Cond: (t2.f1 = t1.f1) - -> Nested Loop Left Join - Join Filter: (t2.f1 > 1) - -> Nested Loop Left Join - Join Filter: (t2.f1 > 0) - Filter: (t3.f1 IS NULL) - -> Seq Scan on int4_tbl t2 - -> Materialize - -> Seq Scan on int4_tbl t3 - -> Seq Scan on tenk1 t4 - -> Hash - -> Seq Scan on int4_tbl t1 -(13 rows) - -explain (costs off) -select * from int4_tbl t1 - left join ((select t2.f1 from int4_tbl t2 - left join int4_tbl t3 on t2.f1 > 0 - where t2.f1 <> coalesce(t3.f1, -1)) s - left join tenk1 t4 on s.f1 > 1) - on s.f1 = t1.f1; - QUERY PLAN ------------------------------------------------------------------ - Nested Loop Left Join - Join Filter: (t2.f1 > 1) - -> Hash Right Join - Hash Cond: (t2.f1 = t1.f1) - -> Nested Loop Left Join - Join Filter: (t2.f1 > 0) - Filter: (t2.f1 <> COALESCE(t3.f1, '-1'::integer)) - -> Seq Scan on int4_tbl t2 - -> Materialize - -> Seq Scan on int4_tbl t3 - -> Hash - -> Seq Scan on int4_tbl t1 - -> Materialize - -> Seq Scan on tenk1 t4 -(14 rows) - -explain (costs off) -select * from onek t1 - left join onek t2 on t1.unique1 = t2.unique1 - left join onek t3 on t2.unique1 = t3.unique1 - left join onek t4 on t3.unique1 = t4.unique1 and t2.unique2 = t4.unique2; - QUERY PLAN ------------------------------------------------------------------------- - Hash Left Join - Hash Cond: ((t3.unique1 = t4.unique1) AND (t2.unique2 = t4.unique2)) - -> Hash Left Join - Hash Cond: (t2.unique1 = t3.unique1) - -> Hash Left Join - Hash Cond: (t1.unique1 = t2.unique1) - -> Seq Scan on onek t1 - -> Hash - -> Seq Scan on onek t2 - -> Hash - -> Seq Scan on onek t3 - -> Hash - -> Seq Scan on onek t4 -(13 rows) - -explain (costs off) -select * from int8_tbl t1 left join - (int8_tbl t2 left join int8_tbl t3 full join int8_tbl t4 on false on false) - left join int8_tbl t5 on t2.q1 = t5.q1 -on t2.q2 = 123; - QUERY PLAN --------------------------------------------------- - Nested Loop Left Join - -> Seq Scan on int8_tbl t1 - -> Materialize - -> Nested Loop Left Join - Join Filter: (t2.q1 = t5.q1) - -> Nested Loop Left Join - Join Filter: false - -> Seq Scan on int8_tbl t2 - Filter: (q2 = 123) - -> Result - One-Time Filter: false - -> Seq Scan on int8_tbl t5 -(12 rows) - -explain (costs off) -select * from int8_tbl t1 - left join int8_tbl t2 on true - left join lateral - (select * from int8_tbl t3 where t3.q1 = t2.q1 offset 0) s - on t2.q1 = 1; - QUERY PLAN -------------------------------------------- - Nested Loop Left Join - -> Seq Scan on int8_tbl t1 - -> Materialize - -> Nested Loop Left Join - Join Filter: (t2.q1 = 1) - -> Seq Scan on int8_tbl t2 - -> Seq Scan on int8_tbl t3 - Filter: (q1 = t2.q1) -(8 rows) - -explain (costs off) -select * from int8_tbl t1 - left join int8_tbl t2 on true - left join lateral - (select * from generate_series(t2.q1, 100)) s - on t2.q1 = 1; - QUERY PLAN ----------------------------------------------------- - Nested Loop Left Join - -> Seq Scan on int8_tbl t1 - -> Materialize - -> Nested Loop Left Join - Join Filter: (t2.q1 = 1) - -> Seq Scan on int8_tbl t2 - -> Function Scan on generate_series -(7 rows) - -explain (costs off) -select * from int8_tbl t1 - left join int8_tbl t2 on true - left join lateral - (select t2.q1 from int8_tbl t3) s - on t2.q1 = 1; - QUERY PLAN -------------------------------------------- - Nested Loop Left Join - -> Seq Scan on int8_tbl t1 - -> Materialize - -> Nested Loop Left Join - Join Filter: (t2.q1 = 1) - -> Seq Scan on int8_tbl t2 - -> Seq Scan on int8_tbl t3 -(7 rows) - -explain (costs off) -select * from onek t1 - left join onek t2 on true - left join lateral - (select * from onek t3 where t3.two = t2.two offset 0) s - on t2.unique1 = 1; - QUERY PLAN --------------------------------------------------- - Nested Loop Left Join - -> Seq Scan on onek t1 - -> Materialize - -> Nested Loop Left Join - Join Filter: (t2.unique1 = 1) - -> Seq Scan on onek t2 - -> Memoize - Cache Key: t2.two - Cache Mode: binary - -> Seq Scan on onek t3 - Filter: (two = t2.two) -(11 rows) - --- --- check a case where we formerly got confused by conflicting sort orders --- in redundant merge join path keys --- -explain (costs off) -select * from - j1_tbl full join - (select * from j2_tbl order by j2_tbl.i desc, j2_tbl.k asc) j2_tbl - on j1_tbl.i = j2_tbl.i and j1_tbl.i = j2_tbl.k; - QUERY PLAN ------------------------------------------------------------------ - Merge Full Join - Merge Cond: ((j2_tbl.i = j1_tbl.i) AND (j2_tbl.k = j1_tbl.i)) - -> Sort - Sort Key: j2_tbl.i DESC, j2_tbl.k - -> Seq Scan on j2_tbl - -> Sort - Sort Key: j1_tbl.i DESC - -> Seq Scan on j1_tbl -(8 rows) - -select * from - j1_tbl full join - (select * from j2_tbl order by j2_tbl.i desc, j2_tbl.k asc) j2_tbl - on j1_tbl.i = j2_tbl.i and j1_tbl.i = j2_tbl.k; - i | j | t | i | k ----+---+-------+---+---- - | | | | 0 - | | | | - | 0 | zero | | - | | null | | - 8 | 8 | eight | | - 7 | 7 | seven | | - 6 | 6 | six | | - | | | 5 | -5 - | | | 5 | -5 - 5 | 0 | five | | - 4 | 1 | four | | - | | | 3 | -3 - 3 | 2 | three | | - 2 | 3 | two | 2 | 2 - | | | 2 | 4 - | | | 1 | -1 - | | | 0 | - 1 | 4 | one | | - 0 | | zero | | -(19 rows) - --- --- a different check for handling of redundant sort keys in merge joins --- -explain (costs off) -select count(*) from - (select * from tenk1 x order by x.thousand, x.twothousand, x.fivethous) x - left join - (select * from tenk1 y order by y.unique2) y - on x.thousand = y.unique2 and x.twothousand = y.hundred and x.fivethous = y.unique2; - QUERY PLAN ----------------------------------------------------------------------------------- - Aggregate - -> Merge Left Join - Merge Cond: (x.thousand = y.unique2) - Join Filter: ((x.twothousand = y.hundred) AND (x.fivethous = y.unique2)) - -> Sort - Sort Key: x.thousand, x.twothousand, x.fivethous - -> Seq Scan on tenk1 x - -> Materialize - -> Index Scan using tenk1_unique2 on tenk1 y -(9 rows) - -select count(*) from - (select * from tenk1 x order by x.thousand, x.twothousand, x.fivethous) x - left join - (select * from tenk1 y order by y.unique2) y - on x.thousand = y.unique2 and x.twothousand = y.hundred and x.fivethous = y.unique2; - count -------- - 10000 -(1 row) - -set enable_hashjoin = 0; -set enable_nestloop = 0; -set enable_hashagg = 0; --- --- Check that we use the pathkeys from a prefix of the group by / order by --- clause for the join pathkeys when that prefix covers all join quals. We --- expect this to lead to an incremental sort for the group by / order by. --- -explain (costs off) -select x.thousand, x.twothousand, count(*) -from tenk1 x inner join tenk1 y on x.thousand = y.thousand -group by x.thousand, x.twothousand -order by x.thousand desc, x.twothousand; - QUERY PLAN ----------------------------------------------------------------------------------- - GroupAggregate - Group Key: x.thousand, x.twothousand - -> Incremental Sort - Sort Key: x.thousand DESC, x.twothousand - Presorted Key: x.thousand - -> Merge Join - Merge Cond: (y.thousand = x.thousand) - -> Index Only Scan Backward using tenk1_thous_tenthous on tenk1 y - -> Sort - Sort Key: x.thousand DESC - -> Seq Scan on tenk1 x -(11 rows) - -reset enable_hashagg; -reset enable_nestloop; -reset enable_hashjoin; --- --- Clean up --- -DROP TABLE t1; -DROP TABLE t2; -DROP TABLE t3; -DROP TABLE J1_TBL; -DROP TABLE J2_TBL; --- Both DELETE and UPDATE allow the specification of additional tables --- to "join" against to determine which rows should be modified. -CREATE TEMP TABLE t1 (a int, b int); -CREATE TEMP TABLE t2 (a int, b int); -CREATE TEMP TABLE t3 (x int, y int); -INSERT INTO t1 VALUES (5, 10); -INSERT INTO t1 VALUES (15, 20); -INSERT INTO t1 VALUES (100, 100); -INSERT INTO t1 VALUES (200, 1000); -INSERT INTO t2 VALUES (200, 2000); -INSERT INTO t3 VALUES (5, 20); -INSERT INTO t3 VALUES (6, 7); -INSERT INTO t3 VALUES (7, 8); -INSERT INTO t3 VALUES (500, 100); -DELETE FROM t3 USING t1 table1 WHERE t3.x = table1.a; -SELECT * FROM t3; - x | y ------+----- - 6 | 7 - 7 | 8 - 500 | 100 -(3 rows) - -DELETE FROM t3 USING t1 JOIN t2 USING (a) WHERE t3.x > t1.a; -SELECT * FROM t3; - x | y ----+--- - 6 | 7 - 7 | 8 -(2 rows) - -DELETE FROM t3 USING t3 t3_other WHERE t3.x = t3_other.x AND t3.y = t3_other.y; -SELECT * FROM t3; - x | y ----+--- -(0 rows) - --- Test join against inheritance tree -create temp table t2a () inherits (t2); -insert into t2a values (200, 2001); -select * from t1 left join t2 on (t1.a = t2.a); - a | b | a | b ------+------+-----+------ - 5 | 10 | | - 15 | 20 | | - 100 | 100 | | - 200 | 1000 | 200 | 2000 - 200 | 1000 | 200 | 2001 -(5 rows) - --- Test matching of column name with wrong alias -select t1.x from t1 join t3 on (t1.a = t3.x); -ERROR: column t1.x does not exist -LINE 1: select t1.x from t1 join t3 on (t1.a = t3.x); - ^ -HINT: Perhaps you meant to reference the column "t3.x". --- Test matching of locking clause with wrong alias -select t1.*, t2.*, unnamed_join.* from - t1 join t2 on (t1.a = t2.a), t3 as unnamed_join - for update of unnamed_join; - a | b | a | b | x | y ----+---+---+---+---+--- -(0 rows) - -select foo.*, unnamed_join.* from - t1 join t2 using (a) as foo, t3 as unnamed_join - for update of unnamed_join; - a | x | y ----+---+--- -(0 rows) - -select foo.*, unnamed_join.* from - t1 join t2 using (a) as foo, t3 as unnamed_join - for update of foo; -ERROR: FOR UPDATE cannot be applied to a join -LINE 3: for update of foo; - ^ -select bar.*, unnamed_join.* from - (t1 join t2 using (a) as foo) as bar, t3 as unnamed_join - for update of foo; -ERROR: relation "foo" in FOR UPDATE clause not found in FROM clause -LINE 3: for update of foo; - ^ -select bar.*, unnamed_join.* from - (t1 join t2 using (a) as foo) as bar, t3 as unnamed_join - for update of bar; -ERROR: FOR UPDATE cannot be applied to a join -LINE 3: for update of bar; - ^ --- --- regression test for 8.1 merge right join bug --- -CREATE TEMP TABLE tt1 ( tt1_id int4, joincol int4 ); -INSERT INTO tt1 VALUES (1, 11); -INSERT INTO tt1 VALUES (2, NULL); -CREATE TEMP TABLE tt2 ( tt2_id int4, joincol int4 ); -INSERT INTO tt2 VALUES (21, 11); -INSERT INTO tt2 VALUES (22, 11); -set enable_hashjoin to off; -set enable_nestloop to off; --- these should give the same results -select tt1.*, tt2.* from tt1 left join tt2 on tt1.joincol = tt2.joincol; - tt1_id | joincol | tt2_id | joincol ---------+---------+--------+--------- - 1 | 11 | 21 | 11 - 1 | 11 | 22 | 11 - 2 | | | -(3 rows) - -select tt1.*, tt2.* from tt2 right join tt1 on tt1.joincol = tt2.joincol; - tt1_id | joincol | tt2_id | joincol ---------+---------+--------+--------- - 1 | 11 | 21 | 11 - 1 | 11 | 22 | 11 - 2 | | | -(3 rows) - -reset enable_hashjoin; -reset enable_nestloop; --- --- regression test for bug #18522 (merge-right-anti-join in inner_unique cases) --- -create temp table tbl_ra(a int unique, b int); -insert into tbl_ra select i, i%100 from generate_series(1,1000)i; -create index on tbl_ra (b); -analyze tbl_ra; -set enable_hashjoin to off; -set enable_nestloop to off; --- ensure we get a merge right anti join -explain (costs off) -select * from tbl_ra t1 -where not exists (select 1 from tbl_ra t2 where t2.b = t1.a) and t1.b < 2; - QUERY PLAN -------------------------------------------------------- - Merge Right Anti Join - Merge Cond: (t2.b = t1.a) - -> Index Only Scan using tbl_ra_b_idx on tbl_ra t2 - -> Sort - Sort Key: t1.a - -> Bitmap Heap Scan on tbl_ra t1 - Recheck Cond: (b < 2) - -> Bitmap Index Scan on tbl_ra_b_idx - Index Cond: (b < 2) -(9 rows) - --- and check we get the expected results -select * from tbl_ra t1 -where not exists (select 1 from tbl_ra t2 where t2.b = t1.a) and t1.b < 2; - a | b -------+--- - 100 | 0 - 101 | 1 - 200 | 0 - 201 | 1 - 300 | 0 - 301 | 1 - 400 | 0 - 401 | 1 - 500 | 0 - 501 | 1 - 600 | 0 - 601 | 1 - 700 | 0 - 701 | 1 - 800 | 0 - 801 | 1 - 900 | 0 - 901 | 1 - 1000 | 0 -(19 rows) - -reset enable_hashjoin; -reset enable_nestloop; --- --- regression test for bug #13908 (hash join with skew tuples & nbatch increase) --- -set work_mem to '64kB'; -set enable_mergejoin to off; -set enable_memoize to off; -explain (costs off) -select count(*) from tenk1 a, tenk1 b - where a.hundred = b.thousand and (b.fivethous % 10) < 10; - QUERY PLAN ------------------------------------------------------------- - Aggregate - -> Hash Join - Hash Cond: (a.hundred = b.thousand) - -> Index Only Scan using tenk1_hundred on tenk1 a - -> Hash - -> Seq Scan on tenk1 b - Filter: ((fivethous % 10) < 10) -(7 rows) - -select count(*) from tenk1 a, tenk1 b - where a.hundred = b.thousand and (b.fivethous % 10) < 10; - count --------- - 100000 -(1 row) - -reset work_mem; -reset enable_mergejoin; -reset enable_memoize; --- --- regression test for 8.2 bug with improper re-ordering of left joins --- -create temp table tt3(f1 int, f2 text); -insert into tt3 select x, repeat('xyzzy', 100) from generate_series(1,10000) x; -analyze tt3; -create temp table tt4(f1 int); -insert into tt4 values (0),(1),(9999); -analyze tt4; -set enable_nestloop to off; -EXPLAIN (COSTS OFF) -SELECT a.f1 -FROM tt4 a -LEFT JOIN ( - SELECT b.f1 - FROM tt3 b LEFT JOIN tt3 c ON (b.f1 = c.f1) - WHERE COALESCE(c.f1, 0) = 0 -) AS d ON (a.f1 = d.f1) -WHERE COALESCE(d.f1, 0) = 0 -ORDER BY 1; - QUERY PLAN ------------------------------------------------ - Sort - Sort Key: a.f1 - -> Hash Right Join - Hash Cond: (b.f1 = a.f1) - Filter: (COALESCE(b.f1, 0) = 0) - -> Hash Left Join - Hash Cond: (b.f1 = c.f1) - Filter: (COALESCE(c.f1, 0) = 0) - -> Seq Scan on tt3 b - -> Hash - -> Seq Scan on tt3 c - -> Hash - -> Seq Scan on tt4 a -(13 rows) - -SELECT a.f1 -FROM tt4 a -LEFT JOIN ( - SELECT b.f1 - FROM tt3 b LEFT JOIN tt3 c ON (b.f1 = c.f1) - WHERE COALESCE(c.f1, 0) = 0 -) AS d ON (a.f1 = d.f1) -WHERE COALESCE(d.f1, 0) = 0 -ORDER BY 1; - f1 ------- - 0 - 1 - 9999 -(3 rows) - -reset enable_nestloop; --- --- basic semijoin and antijoin recognition tests --- -explain (costs off) -select a.* from tenk1 a -where unique1 in (select unique2 from tenk1 b); - QUERY PLAN ------------------------------------------------------------- - Hash Semi Join - Hash Cond: (a.unique1 = b.unique2) - -> Seq Scan on tenk1 a - -> Hash - -> Index Only Scan using tenk1_unique2 on tenk1 b -(5 rows) - --- sadly, this is not an antijoin -explain (costs off) -select a.* from tenk1 a -where unique1 not in (select unique2 from tenk1 b); - QUERY PLAN ------------------------------------------------------------ - Seq Scan on tenk1 a - Filter: (NOT (ANY (unique1 = (hashed SubPlan 1).col1))) - SubPlan 1 - -> Index Only Scan using tenk1_unique2 on tenk1 b -(4 rows) - -explain (costs off) -select a.* from tenk1 a -where exists (select 1 from tenk1 b where a.unique1 = b.unique2); - QUERY PLAN ------------------------------------------------------------- - Hash Semi Join - Hash Cond: (a.unique1 = b.unique2) - -> Seq Scan on tenk1 a - -> Hash - -> Index Only Scan using tenk1_unique2 on tenk1 b -(5 rows) - -explain (costs off) -select a.* from tenk1 a -where not exists (select 1 from tenk1 b where a.unique1 = b.unique2); - QUERY PLAN ------------------------------------------------------------- - Hash Anti Join - Hash Cond: (a.unique1 = b.unique2) - -> Seq Scan on tenk1 a - -> Hash - -> Index Only Scan using tenk1_unique2 on tenk1 b -(5 rows) - -explain (costs off) -select a.* from tenk1 a left join tenk1 b on a.unique1 = b.unique2 -where b.unique2 is null; - QUERY PLAN ------------------------------------------------------------- - Hash Anti Join - Hash Cond: (a.unique1 = b.unique2) - -> Seq Scan on tenk1 a - -> Hash - -> Index Only Scan using tenk1_unique2 on tenk1 b -(5 rows) - --- --- regression test for proper handling of outer joins within antijoins --- -create temp table tt4x(c1 int, c2 int, c3 int); -explain (costs off) -select * from tt4x t1 -where not exists ( - select 1 from tt4x t2 - left join tt4x t3 on t2.c3 = t3.c1 - left join ( select t5.c1 as c1 - from tt4x t4 left join tt4x t5 on t4.c2 = t5.c1 - ) a1 on t3.c2 = a1.c1 - where t1.c1 = t2.c2 -); - QUERY PLAN ---------------------------------------------------------- - Hash Anti Join - Hash Cond: (t1.c1 = t2.c2) - -> Seq Scan on tt4x t1 - -> Hash - -> Merge Right Join - Merge Cond: (t5.c1 = t3.c2) - -> Merge Join - Merge Cond: (t4.c2 = t5.c1) - -> Sort - Sort Key: t4.c2 - -> Seq Scan on tt4x t4 - -> Sort - Sort Key: t5.c1 - -> Seq Scan on tt4x t5 - -> Sort - Sort Key: t3.c2 - -> Merge Left Join - Merge Cond: (t2.c3 = t3.c1) - -> Sort - Sort Key: t2.c3 - -> Seq Scan on tt4x t2 - -> Sort - Sort Key: t3.c1 - -> Seq Scan on tt4x t3 -(24 rows) - --- --- regression test for problems of the sort depicted in bug #3494 --- -create temp table tt5(f1 int, f2 int); -create temp table tt6(f1 int, f2 int); -insert into tt5 values(1, 10); -insert into tt5 values(1, 11); -insert into tt6 values(1, 9); -insert into tt6 values(1, 2); -insert into tt6 values(2, 9); -select * from tt5,tt6 where tt5.f1 = tt6.f1 and tt5.f1 = tt5.f2 - tt6.f2; - f1 | f2 | f1 | f2 -----+----+----+---- - 1 | 10 | 1 | 9 -(1 row) - --- --- regression test for problems of the sort depicted in bug #3588 --- -create temp table xx (pkxx int); -create temp table yy (pkyy int, pkxx int); -insert into xx values (1); -insert into xx values (2); -insert into xx values (3); -insert into yy values (101, 1); -insert into yy values (201, 2); -insert into yy values (301, NULL); -select yy.pkyy as yy_pkyy, yy.pkxx as yy_pkxx, yya.pkyy as yya_pkyy, - xxa.pkxx as xxa_pkxx, xxb.pkxx as xxb_pkxx -from yy - left join (SELECT * FROM yy where pkyy = 101) as yya ON yy.pkyy = yya.pkyy - left join xx xxa on yya.pkxx = xxa.pkxx - left join xx xxb on coalesce (xxa.pkxx, 1) = xxb.pkxx; - yy_pkyy | yy_pkxx | yya_pkyy | xxa_pkxx | xxb_pkxx ----------+---------+----------+----------+---------- - 101 | 1 | 101 | 1 | 1 - 201 | 2 | | | 1 - 301 | | | | 1 -(3 rows) - --- --- regression test for improper pushing of constants across outer-join clauses --- (as seen in early 8.2.x releases) --- -create temp table zt1 (f1 int primary key); -create temp table zt2 (f2 int primary key); -create temp table zt3 (f3 int primary key); -insert into zt1 values(53); -insert into zt2 values(53); -select * from - zt2 left join zt3 on (f2 = f3) - left join zt1 on (f3 = f1) -where f2 = 53; - f2 | f3 | f1 -----+----+---- - 53 | | -(1 row) - -create temp view zv1 as select *,'dummy'::text AS junk from zt1; -select * from - zt2 left join zt3 on (f2 = f3) - left join zv1 on (f3 = f1) -where f2 = 53; - f2 | f3 | f1 | junk -----+----+----+------ - 53 | | | -(1 row) - --- --- regression test for improper extraction of OR indexqual conditions --- (as seen in early 8.3.x releases) --- -select a.unique2, a.ten, b.tenthous, b.unique2, b.hundred -from tenk1 a left join tenk1 b on a.unique2 = b.tenthous -where a.unique1 = 42 and - ((b.unique2 is null and a.ten = 2) or b.hundred = 3); - unique2 | ten | tenthous | unique2 | hundred ----------+-----+----------+---------+--------- -(0 rows) - --- --- test proper positioning of one-time quals in EXISTS (8.4devel bug) --- -prepare foo(bool) as - select count(*) from tenk1 a left join tenk1 b - on (a.unique2 = b.unique1 and exists - (select 1 from tenk1 c where c.thousand = b.unique2 and $1)); -execute foo(true); - count -------- - 10000 -(1 row) - -execute foo(false); - count -------- - 10000 -(1 row) - --- --- test for sane behavior with noncanonical merge clauses, per bug #4926 --- -begin; -set enable_mergejoin = 1; -set enable_hashjoin = 0; -set enable_nestloop = 0; -create temp table a (i integer); -create temp table b (x integer, y integer); -select * from a left join b on i = x and i = y and x = i; - i | x | y ----+---+--- -(0 rows) - -rollback; --- --- test handling of merge clauses using record_ops --- -begin; -create type mycomptype as (id int, v bigint); -create temp table tidv (idv mycomptype); -create index on tidv (idv); -explain (costs off) -select a.idv, b.idv from tidv a, tidv b where a.idv = b.idv; - QUERY PLAN ----------------------------------------------------------- - Merge Join - Merge Cond: (a.idv = b.idv) - -> Index Only Scan using tidv_idv_idx on tidv a - -> Materialize - -> Index Only Scan using tidv_idv_idx on tidv b -(5 rows) - -set enable_mergejoin = 0; -set enable_hashjoin = 0; -explain (costs off) -select a.idv, b.idv from tidv a, tidv b where a.idv = b.idv; - QUERY PLAN ----------------------------------------------------- - Nested Loop - -> Seq Scan on tidv a - -> Index Only Scan using tidv_idv_idx on tidv b - Index Cond: (idv = a.idv) -(4 rows) - -rollback; --- --- test NULL behavior of whole-row Vars, per bug #5025 --- -select t1.q2, count(t2.*) -from int8_tbl t1 left join int8_tbl t2 on (t1.q2 = t2.q1) -group by t1.q2 order by 1; - q2 | count --------------------+------- - -4567890123456789 | 0 - 123 | 2 - 456 | 0 - 4567890123456789 | 6 -(4 rows) - -select t1.q2, count(t2.*) -from int8_tbl t1 left join (select * from int8_tbl) t2 on (t1.q2 = t2.q1) -group by t1.q2 order by 1; - q2 | count --------------------+------- - -4567890123456789 | 0 - 123 | 2 - 456 | 0 - 4567890123456789 | 6 -(4 rows) - -select t1.q2, count(t2.*) -from int8_tbl t1 left join (select * from int8_tbl offset 0) t2 on (t1.q2 = t2.q1) -group by t1.q2 order by 1; - q2 | count --------------------+------- - -4567890123456789 | 0 - 123 | 2 - 456 | 0 - 4567890123456789 | 6 -(4 rows) - -select t1.q2, count(t2.*) -from int8_tbl t1 left join - (select q1, case when q2=1 then 1 else q2 end as q2 from int8_tbl) t2 - on (t1.q2 = t2.q1) -group by t1.q2 order by 1; - q2 | count --------------------+------- - -4567890123456789 | 0 - 123 | 2 - 456 | 0 - 4567890123456789 | 6 -(4 rows) - --- --- test incorrect failure to NULL pulled-up subexpressions --- -begin; -create temp table a ( - code char not null, - constraint a_pk primary key (code) -); -create temp table b ( - a char not null, - num integer not null, - constraint b_pk primary key (a, num) -); -create temp table c ( - name char not null, - a char, - constraint c_pk primary key (name) -); -insert into a (code) values ('p'); -insert into a (code) values ('q'); -insert into b (a, num) values ('p', 1); -insert into b (a, num) values ('p', 2); -insert into c (name, a) values ('A', 'p'); -insert into c (name, a) values ('B', 'q'); -insert into c (name, a) values ('C', null); -select c.name, ss.code, ss.b_cnt, ss.const -from c left join - (select a.code, coalesce(b_grp.cnt, 0) as b_cnt, -1 as const - from a left join - (select count(1) as cnt, b.a from b group by b.a) as b_grp - on a.code = b_grp.a - ) as ss - on (c.a = ss.code) -order by c.name; - name | code | b_cnt | const -------+------+-------+------- - A | p | 2 | -1 - B | q | 0 | -1 - C | | | -(3 rows) - -rollback; --- --- test incorrect handling of placeholders that only appear in targetlists, --- per bug #6154 --- -SELECT * FROM -( SELECT 1 as key1 ) sub1 -LEFT JOIN -( SELECT sub3.key3, sub4.value2, COALESCE(sub4.value2, 66) as value3 FROM - ( SELECT 1 as key3 ) sub3 - LEFT JOIN - ( SELECT sub5.key5, COALESCE(sub6.value1, 1) as value2 FROM - ( SELECT 1 as key5 ) sub5 - LEFT JOIN - ( SELECT 2 as key6, 42 as value1 ) sub6 - ON sub5.key5 = sub6.key6 - ) sub4 - ON sub4.key5 = sub3.key3 -) sub2 -ON sub1.key1 = sub2.key3; - key1 | key3 | value2 | value3 -------+------+--------+-------- - 1 | 1 | 1 | 1 -(1 row) - --- test the path using join aliases, too -SELECT * FROM -( SELECT 1 as key1 ) sub1 -LEFT JOIN -( SELECT sub3.key3, value2, COALESCE(value2, 66) as value3 FROM - ( SELECT 1 as key3 ) sub3 - LEFT JOIN - ( SELECT sub5.key5, COALESCE(sub6.value1, 1) as value2 FROM - ( SELECT 1 as key5 ) sub5 - LEFT JOIN - ( SELECT 2 as key6, 42 as value1 ) sub6 - ON sub5.key5 = sub6.key6 - ) sub4 - ON sub4.key5 = sub3.key3 -) sub2 -ON sub1.key1 = sub2.key3; - key1 | key3 | value2 | value3 -------+------+--------+-------- - 1 | 1 | 1 | 1 -(1 row) - --- --- test case where a PlaceHolderVar is used as a nestloop parameter --- -EXPLAIN (COSTS OFF) -SELECT qq, unique1 - FROM - ( SELECT COALESCE(q1, 0) AS qq FROM int8_tbl a ) AS ss1 - FULL OUTER JOIN - ( SELECT COALESCE(q2, -1) AS qq FROM int8_tbl b ) AS ss2 - USING (qq) - INNER JOIN tenk1 c ON qq = unique2; - QUERY PLAN ---------------------------------------------------------------------------------------------------------- - Nested Loop - -> Hash Full Join - Hash Cond: ((COALESCE(a.q1, '0'::bigint)) = (COALESCE(b.q2, '-1'::bigint))) - -> Seq Scan on int8_tbl a - -> Hash - -> Seq Scan on int8_tbl b - -> Index Scan using tenk1_unique2 on tenk1 c - Index Cond: (unique2 = COALESCE((COALESCE(a.q1, '0'::bigint)), (COALESCE(b.q2, '-1'::bigint)))) -(8 rows) - -SELECT qq, unique1 - FROM - ( SELECT COALESCE(q1, 0) AS qq FROM int8_tbl a ) AS ss1 - FULL OUTER JOIN - ( SELECT COALESCE(q2, -1) AS qq FROM int8_tbl b ) AS ss2 - USING (qq) - INNER JOIN tenk1 c ON qq = unique2; - qq | unique1 ------+--------- - 123 | 4596 - 123 | 4596 - 456 | 7318 -(3 rows) - --- --- nested nestloops can require nested PlaceHolderVars --- -create temp table nt1 ( - id int primary key, - a1 boolean, - a2 boolean -); -create temp table nt2 ( - id int primary key, - nt1_id int, - b1 boolean, - b2 boolean, - foreign key (nt1_id) references nt1(id) -); -create temp table nt3 ( - id int primary key, - nt2_id int, - c1 boolean, - foreign key (nt2_id) references nt2(id) -); -insert into nt1 values (1,true,true); -insert into nt1 values (2,true,false); -insert into nt1 values (3,false,false); -insert into nt2 values (1,1,true,true); -insert into nt2 values (2,2,true,false); -insert into nt2 values (3,3,false,false); -insert into nt3 values (1,1,true); -insert into nt3 values (2,2,false); -insert into nt3 values (3,3,true); -explain (costs off) -select nt3.id -from nt3 as nt3 - left join - (select nt2.*, (nt2.b1 and ss1.a3) AS b3 - from nt2 as nt2 - left join - (select nt1.*, (nt1.id is not null) as a3 from nt1) as ss1 - on ss1.id = nt2.nt1_id - ) as ss2 - on ss2.id = nt3.nt2_id -where nt3.id = 1 and ss2.b3; - QUERY PLAN ------------------------------------------------ - Nested Loop - -> Nested Loop - -> Index Scan using nt3_pkey on nt3 - Index Cond: (id = 1) - -> Index Scan using nt2_pkey on nt2 - Index Cond: (id = nt3.nt2_id) - -> Index Only Scan using nt1_pkey on nt1 - Index Cond: (id = nt2.nt1_id) - Filter: (nt2.b1 AND (id IS NOT NULL)) -(9 rows) - -select nt3.id -from nt3 as nt3 - left join - (select nt2.*, (nt2.b1 and ss1.a3) AS b3 - from nt2 as nt2 - left join - (select nt1.*, (nt1.id is not null) as a3 from nt1) as ss1 - on ss1.id = nt2.nt1_id - ) as ss2 - on ss2.id = nt3.nt2_id -where nt3.id = 1 and ss2.b3; - id ----- - 1 -(1 row) - --- --- test case where a PlaceHolderVar is propagated into a subquery --- -explain (costs off) -select * from - int8_tbl t1 left join - (select q1 as x, 42 as y from int8_tbl t2) ss - on t1.q2 = ss.x -where - 1 = (select 1 from int8_tbl t3 where ss.y is not null limit 1) -order by 1,2; - QUERY PLAN ------------------------------------------------------------ - Sort - Sort Key: t1.q1, t1.q2 - -> Hash Left Join - Hash Cond: (t1.q2 = t2.q1) - Filter: (1 = (SubPlan 1)) - -> Seq Scan on int8_tbl t1 - -> Hash - -> Seq Scan on int8_tbl t2 - SubPlan 1 - -> Limit - -> Result - One-Time Filter: ((42) IS NOT NULL) - -> Seq Scan on int8_tbl t3 -(13 rows) - -select * from - int8_tbl t1 left join - (select q1 as x, 42 as y from int8_tbl t2) ss - on t1.q2 = ss.x -where - 1 = (select 1 from int8_tbl t3 where ss.y is not null limit 1) -order by 1,2; - q1 | q2 | x | y -------------------+------------------+------------------+---- - 123 | 4567890123456789 | 4567890123456789 | 42 - 123 | 4567890123456789 | 4567890123456789 | 42 - 123 | 4567890123456789 | 4567890123456789 | 42 - 4567890123456789 | 123 | 123 | 42 - 4567890123456789 | 123 | 123 | 42 - 4567890123456789 | 4567890123456789 | 4567890123456789 | 42 - 4567890123456789 | 4567890123456789 | 4567890123456789 | 42 - 4567890123456789 | 4567890123456789 | 4567890123456789 | 42 -(8 rows) - --- --- variant where a PlaceHolderVar is needed at a join, but not above the join --- -explain (costs off) -select * from - int4_tbl as i41, - lateral - (select 1 as x from - (select i41.f1 as lat, - i42.f1 as loc from - int8_tbl as i81, int4_tbl as i42) as ss1 - right join int4_tbl as i43 on (i43.f1 > 1) - where ss1.loc = ss1.lat) as ss2 -where i41.f1 > 0; - QUERY PLAN --------------------------------------------------- - Nested Loop - -> Nested Loop - -> Seq Scan on int4_tbl i41 - Filter: (f1 > 0) - -> Nested Loop - Join Filter: (i42.f1 = i41.f1) - -> Seq Scan on int8_tbl i81 - -> Materialize - -> Seq Scan on int4_tbl i42 - -> Materialize - -> Seq Scan on int4_tbl i43 - Filter: (f1 > 1) -(12 rows) - -select * from - int4_tbl as i41, - lateral - (select 1 as x from - (select i41.f1 as lat, - i42.f1 as loc from - int8_tbl as i81, int4_tbl as i42) as ss1 - right join int4_tbl as i43 on (i43.f1 > 1) - where ss1.loc = ss1.lat) as ss2 -where i41.f1 > 0; - f1 | x -------------+--- - 123456 | 1 - 123456 | 1 - 123456 | 1 - 123456 | 1 - 123456 | 1 - 123456 | 1 - 123456 | 1 - 123456 | 1 - 123456 | 1 - 123456 | 1 - 2147483647 | 1 - 2147483647 | 1 - 2147483647 | 1 - 2147483647 | 1 - 2147483647 | 1 - 2147483647 | 1 - 2147483647 | 1 - 2147483647 | 1 - 2147483647 | 1 - 2147483647 | 1 -(20 rows) - --- --- test the corner cases FULL JOIN ON TRUE and FULL JOIN ON FALSE --- -select * from int4_tbl a full join int4_tbl b on true; - f1 | f1 --------------+------------- - 0 | 0 - 0 | 123456 - 0 | -123456 - 0 | 2147483647 - 0 | -2147483647 - 123456 | 0 - 123456 | 123456 - 123456 | -123456 - 123456 | 2147483647 - 123456 | -2147483647 - -123456 | 0 - -123456 | 123456 - -123456 | -123456 - -123456 | 2147483647 - -123456 | -2147483647 - 2147483647 | 0 - 2147483647 | 123456 - 2147483647 | -123456 - 2147483647 | 2147483647 - 2147483647 | -2147483647 - -2147483647 | 0 - -2147483647 | 123456 - -2147483647 | -123456 - -2147483647 | 2147483647 - -2147483647 | -2147483647 -(25 rows) - -select * from int4_tbl a full join int4_tbl b on false; - f1 | f1 --------------+------------- - | 0 - | 123456 - | -123456 - | 2147483647 - | -2147483647 - 0 | - 123456 | - -123456 | - 2147483647 | - -2147483647 | -(10 rows) - --- --- test for ability to use a cartesian join when necessary --- -create temp table q1 as select 1 as q1; -create temp table q2 as select 0 as q2; -analyze q1; -analyze q2; -explain (costs off) -select * from - tenk1 join int4_tbl on f1 = twothousand, - q1, q2 -where q1 = thousand or q2 = thousand; - QUERY PLAN ------------------------------------------------------------------------- - Hash Join - Hash Cond: (tenk1.twothousand = int4_tbl.f1) - -> Nested Loop - -> Nested Loop - -> Seq Scan on q1 - -> Seq Scan on q2 - -> Bitmap Heap Scan on tenk1 - Recheck Cond: ((q1.q1 = thousand) OR (q2.q2 = thousand)) - -> BitmapOr - -> Bitmap Index Scan on tenk1_thous_tenthous - Index Cond: (thousand = q1.q1) - -> Bitmap Index Scan on tenk1_thous_tenthous - Index Cond: (thousand = q2.q2) - -> Hash - -> Seq Scan on int4_tbl -(15 rows) - -explain (costs off) -select * from - tenk1 join int4_tbl on f1 = twothousand, - q1, q2 -where thousand = (q1 + q2); - QUERY PLAN --------------------------------------------------------------- - Hash Join - Hash Cond: (tenk1.twothousand = int4_tbl.f1) - -> Nested Loop - -> Nested Loop - -> Seq Scan on q1 - -> Seq Scan on q2 - -> Bitmap Heap Scan on tenk1 - Recheck Cond: (thousand = (q1.q1 + q2.q2)) - -> Bitmap Index Scan on tenk1_thous_tenthous - Index Cond: (thousand = (q1.q1 + q2.q2)) - -> Hash - -> Seq Scan on int4_tbl -(12 rows) - --- --- test ability to generate a suitable plan for a star-schema query --- -explain (costs off) -select * from - tenk1, int8_tbl a, int8_tbl b -where thousand = a.q1 and tenthous = b.q1 and a.q2 = 1 and b.q2 = 2; - QUERY PLAN ---------------------------------------------------------------------- - Nested Loop - -> Seq Scan on int8_tbl b - Filter: (q2 = 2) - -> Nested Loop - -> Seq Scan on int8_tbl a - Filter: (q2 = 1) - -> Index Scan using tenk1_thous_tenthous on tenk1 - Index Cond: ((thousand = a.q1) AND (tenthous = b.q1)) -(8 rows) - --- --- test a corner case in which we shouldn't apply the star-schema optimization --- -explain (costs off) -select t1.unique2, t1.stringu1, t2.unique1, t2.stringu2 from - tenk1 t1 - inner join int4_tbl i1 - left join (select v1.x2, v2.y1, 11 AS d1 - from (select 1,0 from onerow) v1(x1,x2) - left join (select 3,1 from onerow) v2(y1,y2) - on v1.x1 = v2.y2) subq1 - on (i1.f1 = subq1.x2) - on (t1.unique2 = subq1.d1) - left join tenk1 t2 - on (subq1.y1 = t2.unique1) -where t1.unique2 < 42 and t1.stringu1 > t2.stringu2; - QUERY PLAN ------------------------------------------------------------------------ - Nested Loop - -> Nested Loop - Join Filter: (t1.stringu1 > t2.stringu2) - -> Nested Loop - -> Nested Loop - -> Seq Scan on onerow - -> Seq Scan on onerow onerow_1 - -> Index Scan using tenk1_unique2 on tenk1 t1 - Index Cond: ((unique2 = (11)) AND (unique2 < 42)) - -> Index Scan using tenk1_unique1 on tenk1 t2 - Index Cond: (unique1 = (3)) - -> Seq Scan on int4_tbl i1 - Filter: (f1 = 0) -(13 rows) - -select t1.unique2, t1.stringu1, t2.unique1, t2.stringu2 from - tenk1 t1 - inner join int4_tbl i1 - left join (select v1.x2, v2.y1, 11 AS d1 - from (select 1,0 from onerow) v1(x1,x2) - left join (select 3,1 from onerow) v2(y1,y2) - on v1.x1 = v2.y2) subq1 - on (i1.f1 = subq1.x2) - on (t1.unique2 = subq1.d1) - left join tenk1 t2 - on (subq1.y1 = t2.unique1) -where t1.unique2 < 42 and t1.stringu1 > t2.stringu2; - unique2 | stringu1 | unique1 | stringu2 ----------+----------+---------+---------- - 11 | WFAAAA | 3 | LKIAAA -(1 row) - --- variant that isn't quite a star-schema case -select ss1.d1 from - tenk1 as t1 - inner join tenk1 as t2 - on t1.tenthous = t2.ten - inner join - int8_tbl as i8 - left join int4_tbl as i4 - inner join (select 64::information_schema.cardinal_number as d1 - from tenk1 t3, - lateral (select abs(t3.unique1) + random()) ss0(x) - where t3.fivethous < 0) as ss1 - on i4.f1 = ss1.d1 - on i8.q1 = i4.f1 - on t1.tenthous = ss1.d1 -where t1.unique1 < i4.f1; - d1 ----- -(0 rows) - --- this variant is foldable by the remove-useless-RESULT-RTEs code -explain (costs off) -select t1.unique2, t1.stringu1, t2.unique1, t2.stringu2 from - tenk1 t1 - inner join int4_tbl i1 - left join (select v1.x2, v2.y1, 11 AS d1 - from (values(1,0)) v1(x1,x2) - left join (values(3,1)) v2(y1,y2) - on v1.x1 = v2.y2) subq1 - on (i1.f1 = subq1.x2) - on (t1.unique2 = subq1.d1) - left join tenk1 t2 - on (subq1.y1 = t2.unique1) -where t1.unique2 < 42 and t1.stringu1 > t2.stringu2; - QUERY PLAN ------------------------------------------------------------------ - Nested Loop - Join Filter: (t1.stringu1 > t2.stringu2) - -> Nested Loop - -> Seq Scan on int4_tbl i1 - Filter: (f1 = 0) - -> Index Scan using tenk1_unique2 on tenk1 t1 - Index Cond: ((unique2 = (11)) AND (unique2 < 42)) - -> Index Scan using tenk1_unique1 on tenk1 t2 - Index Cond: (unique1 = (3)) -(9 rows) - -select t1.unique2, t1.stringu1, t2.unique1, t2.stringu2 from - tenk1 t1 - inner join int4_tbl i1 - left join (select v1.x2, v2.y1, 11 AS d1 - from (values(1,0)) v1(x1,x2) - left join (values(3,1)) v2(y1,y2) - on v1.x1 = v2.y2) subq1 - on (i1.f1 = subq1.x2) - on (t1.unique2 = subq1.d1) - left join tenk1 t2 - on (subq1.y1 = t2.unique1) -where t1.unique2 < 42 and t1.stringu1 > t2.stringu2; - unique2 | stringu1 | unique1 | stringu2 ----------+----------+---------+---------- - 11 | WFAAAA | 3 | LKIAAA -(1 row) - --- Here's a variant that we can't fold too aggressively, though, --- or we end up with noplace to evaluate the lateral PHV -explain (verbose, costs off) -select * from - (select 1 as x) ss1 left join (select 2 as y) ss2 on (true), - lateral (select ss2.y as z limit 1) ss3; - QUERY PLAN ---------------------------- - Nested Loop - Output: 1, (2), ((2)) - -> Result - Output: 2 - -> Limit - Output: ((2)) - -> Result - Output: (2) -(8 rows) - -select * from - (select 1 as x) ss1 left join (select 2 as y) ss2 on (true), - lateral (select ss2.y as z limit 1) ss3; - x | y | z ----+---+--- - 1 | 2 | 2 -(1 row) - --- Test proper handling of appendrel PHVs during useless-RTE removal -explain (costs off) -select * from - (select 0 as z) as t1 - left join - (select true as a) as t2 - on true, - lateral (select true as b - union all - select a as b) as t3 -where b; - QUERY PLAN ---------------------------------------- - Nested Loop - -> Result - -> Append - -> Result - -> Result - One-Time Filter: (true) -(6 rows) - -select * from - (select 0 as z) as t1 - left join - (select true as a) as t2 - on true, - lateral (select true as b - union all - select a as b) as t3 -where b; - z | a | b ----+---+--- - 0 | t | t - 0 | t | t -(2 rows) - --- Test PHV in a semijoin qual, which confused useless-RTE removal (bug #17700) -explain (verbose, costs off) -with ctetable as not materialized ( select 1 as f1 ) -select * from ctetable c1 -where f1 in ( select c3.f1 from ctetable c2 full join ctetable c3 on true ); - QUERY PLAN ----------------------------- - Result - Output: 1 - One-Time Filter: (1 = 1) -(3 rows) - -with ctetable as not materialized ( select 1 as f1 ) -select * from ctetable c1 -where f1 in ( select c3.f1 from ctetable c2 full join ctetable c3 on true ); - f1 ----- - 1 -(1 row) - --- Test PHV that winds up in a Result node, despite having nonempty nullingrels -explain (verbose, costs off) -select table_catalog, table_name -from int4_tbl t1 - inner join (int8_tbl t2 - left join information_schema.column_udt_usage on null) - on null; - QUERY PLAN -------------------------------------------------------------------------------------------------------------------- - Result - Output: (current_database())::information_schema.sql_identifier, (c.relname)::information_schema.sql_identifier - One-Time Filter: false -(3 rows) - --- Test handling of qual pushdown to appendrel members with non-Var outputs -explain (verbose, costs off) -select * from int4_tbl left join ( - select text 'foo' union all select text 'bar' -) ss(x) on true -where ss.x is null; - QUERY PLAN ------------------------------------------ - Nested Loop Left Join - Output: int4_tbl.f1, ('foo'::text) - Filter: (('foo'::text) IS NULL) - -> Seq Scan on public.int4_tbl - Output: int4_tbl.f1 - -> Materialize - Output: ('foo'::text) - -> Append - -> Result - Output: 'foo'::text - -> Result - Output: 'bar'::text -(12 rows) - --- --- test inlining of immutable functions --- -create function f_immutable_int4(i integer) returns integer as -$$ begin return i; end; $$ language plpgsql immutable; --- check optimization of function scan with join -explain (costs off) -select unique1 from tenk1, (select * from f_immutable_int4(1) x) x -where x = unique1; - QUERY PLAN ----------------------------------------------- - Index Only Scan using tenk1_unique1 on tenk1 - Index Cond: (unique1 = 1) -(2 rows) - -explain (verbose, costs off) -select unique1, x.* -from tenk1, (select *, random() from f_immutable_int4(1) x) x -where x = unique1; - QUERY PLAN ------------------------------------------------------------ - Nested Loop - Output: tenk1.unique1, (1), (random()) - -> Result - Output: 1, random() - -> Index Only Scan using tenk1_unique1 on public.tenk1 - Output: tenk1.unique1 - Index Cond: (tenk1.unique1 = (1)) -(7 rows) - -explain (costs off) -select unique1 from tenk1, f_immutable_int4(1) x where x = unique1; - QUERY PLAN ----------------------------------------------- - Index Only Scan using tenk1_unique1 on tenk1 - Index Cond: (unique1 = 1) -(2 rows) - -explain (costs off) -select unique1 from tenk1, lateral f_immutable_int4(1) x where x = unique1; - QUERY PLAN ----------------------------------------------- - Index Only Scan using tenk1_unique1 on tenk1 - Index Cond: (unique1 = 1) -(2 rows) - -explain (costs off) -select unique1 from tenk1, lateral f_immutable_int4(1) x where x in (select 17); - QUERY PLAN --------------------------- - Result - One-Time Filter: false -(2 rows) - -explain (costs off) -select unique1, x from tenk1 join f_immutable_int4(1) x on unique1 = x; - QUERY PLAN ----------------------------------------------- - Index Only Scan using tenk1_unique1 on tenk1 - Index Cond: (unique1 = 1) -(2 rows) - -explain (costs off) -select unique1, x from tenk1 left join f_immutable_int4(1) x on unique1 = x; - QUERY PLAN ----------------------------------------------------- - Nested Loop Left Join - Join Filter: (tenk1.unique1 = 1) - -> Index Only Scan using tenk1_unique1 on tenk1 - -> Materialize - -> Result -(5 rows) - -explain (costs off) -select unique1, x from tenk1 right join f_immutable_int4(1) x on unique1 = x; - QUERY PLAN ----------------------------------------------------- - Nested Loop Left Join - -> Result - -> Index Only Scan using tenk1_unique1 on tenk1 - Index Cond: (unique1 = 1) -(4 rows) - -explain (costs off) -select unique1, x from tenk1 full join f_immutable_int4(1) x on unique1 = x; - QUERY PLAN ----------------------------------------------------- - Merge Full Join - Merge Cond: (tenk1.unique1 = (1)) - -> Index Only Scan using tenk1_unique1 on tenk1 - -> Sort - Sort Key: (1) - -> Result -(6 rows) - --- check that pullup of a const function allows further const-folding -explain (costs off) -select unique1 from tenk1, f_immutable_int4(1) x where x = 42; - QUERY PLAN --------------------------- - Result - One-Time Filter: false -(2 rows) - --- test inlining of immutable functions with PlaceHolderVars -explain (costs off) -select nt3.id -from nt3 as nt3 - left join - (select nt2.*, (nt2.b1 or i4 = 42) AS b3 - from nt2 as nt2 - left join - f_immutable_int4(0) i4 - on i4 = nt2.nt1_id - ) as ss2 - on ss2.id = nt3.nt2_id -where nt3.id = 1 and ss2.b3; - QUERY PLAN ----------------------------------------------- - Nested Loop Left Join - Filter: ((nt2.b1 OR ((0) = 42))) - -> Index Scan using nt3_pkey on nt3 - Index Cond: (id = 1) - -> Nested Loop Left Join - Join Filter: (0 = nt2.nt1_id) - -> Index Scan using nt2_pkey on nt2 - Index Cond: (id = nt3.nt2_id) - -> Result -(9 rows) - -drop function f_immutable_int4(int); --- test inlining when function returns composite -create function mki8(bigint, bigint) returns int8_tbl as -$$select row($1,$2)::int8_tbl$$ language sql; -create function mki4(int) returns int4_tbl as -$$select row($1)::int4_tbl$$ language sql; -explain (verbose, costs off) -select * from mki8(1,2); - QUERY PLAN ------------------------------------- - Function Scan on mki8 - Output: q1, q2 - Function Call: '(1,2)'::int8_tbl -(3 rows) - -select * from mki8(1,2); - q1 | q2 -----+---- - 1 | 2 -(1 row) - -explain (verbose, costs off) -select * from mki4(42); - QUERY PLAN ------------------------------------ - Function Scan on mki4 - Output: f1 - Function Call: '(42)'::int4_tbl -(3 rows) - -select * from mki4(42); - f1 ----- - 42 -(1 row) - -drop function mki8(bigint, bigint); -drop function mki4(int); --- test const-folding of a whole-row Var into a per-field Var --- (need to inline a function to reach this case, else parser does it) -create function f_field_select(t onek) returns int4 as -$$ select t.unique2; $$ language sql immutable; -explain (verbose, costs off) -select (t2.*).unique1, f_field_select(t2) from tenk1 t1 - left join onek t2 on t1.unique1 = t2.unique1 - left join int8_tbl t3 on true; - QUERY PLAN --------------------------------------------------------------------- - Nested Loop Left Join - Output: t2.unique1, t2.unique2 - -> Hash Left Join - Output: t2.unique1, t2.unique2 - Hash Cond: (t1.unique1 = t2.unique1) - -> Index Only Scan using tenk1_unique1 on public.tenk1 t1 - Output: t1.unique1 - -> Hash - Output: t2.unique1, t2.unique2 - -> Seq Scan on public.onek t2 - Output: t2.unique1, t2.unique2 - -> Materialize - -> Seq Scan on public.int8_tbl t3 -(13 rows) - -drop function f_field_select(t onek); --- --- test extraction of restriction OR clauses from join OR clause --- (we used to only do this for indexable clauses) --- -explain (costs off) -select * from tenk1 a join tenk1 b on - (a.unique1 = 1 and b.unique1 = 2) or (a.unique2 = 3 and b.hundred = 4); - QUERY PLAN -------------------------------------------------------------------------------------------------- - Nested Loop - Join Filter: (((a.unique1 = 1) AND (b.unique1 = 2)) OR ((a.unique2 = 3) AND (b.hundred = 4))) - -> Bitmap Heap Scan on tenk1 b - Recheck Cond: ((unique1 = 2) OR (hundred = 4)) - -> BitmapOr - -> Bitmap Index Scan on tenk1_unique1 - Index Cond: (unique1 = 2) - -> Bitmap Index Scan on tenk1_hundred - Index Cond: (hundred = 4) - -> Materialize - -> Bitmap Heap Scan on tenk1 a - Recheck Cond: ((unique1 = 1) OR (unique2 = 3)) - -> BitmapOr - -> Bitmap Index Scan on tenk1_unique1 - Index Cond: (unique1 = 1) - -> Bitmap Index Scan on tenk1_unique2 - Index Cond: (unique2 = 3) -(17 rows) - -explain (costs off) -select * from tenk1 a join tenk1 b on - (a.unique1 = 1 and b.unique1 = 2) or (a.unique2 = 3 and b.ten = 4); - QUERY PLAN ---------------------------------------------------------------------------------------------- - Nested Loop - Join Filter: (((a.unique1 = 1) AND (b.unique1 = 2)) OR ((a.unique2 = 3) AND (b.ten = 4))) - -> Seq Scan on tenk1 b - Filter: ((unique1 = 2) OR (ten = 4)) - -> Materialize - -> Bitmap Heap Scan on tenk1 a - Recheck Cond: ((unique1 = 1) OR (unique2 = 3)) - -> BitmapOr - -> Bitmap Index Scan on tenk1_unique1 - Index Cond: (unique1 = 1) - -> Bitmap Index Scan on tenk1_unique2 - Index Cond: (unique2 = 3) -(12 rows) - -explain (costs off) -select * from tenk1 a join tenk1 b on - (a.unique1 = 1 and b.unique1 = 2) or - ((a.unique2 = 3 or a.unique2 = 7) and b.hundred = 4); - QUERY PLAN ----------------------------------------------------------------------------------------------------------------------- - Nested Loop - Join Filter: (((a.unique1 = 1) AND (b.unique1 = 2)) OR (((a.unique2 = 3) OR (a.unique2 = 7)) AND (b.hundred = 4))) - -> Bitmap Heap Scan on tenk1 b - Recheck Cond: ((unique1 = 2) OR (hundred = 4)) - -> BitmapOr - -> Bitmap Index Scan on tenk1_unique1 - Index Cond: (unique1 = 2) - -> Bitmap Index Scan on tenk1_hundred - Index Cond: (hundred = 4) - -> Materialize - -> Bitmap Heap Scan on tenk1 a - Recheck Cond: ((unique1 = 1) OR (unique2 = 3) OR (unique2 = 7)) - -> BitmapOr - -> Bitmap Index Scan on tenk1_unique1 - Index Cond: (unique1 = 1) - -> Bitmap Index Scan on tenk1_unique2 - Index Cond: (unique2 = 3) - -> Bitmap Index Scan on tenk1_unique2 - Index Cond: (unique2 = 7) -(19 rows) - --- --- test placement of movable quals in a parameterized join tree --- -explain (costs off) -select * from tenk1 t1 left join - (tenk1 t2 join tenk1 t3 on t2.thousand = t3.unique2) - on t1.hundred = t2.hundred and t1.ten = t3.ten -where t1.unique1 = 1; - QUERY PLAN --------------------------------------------------------- - Nested Loop Left Join - -> Index Scan using tenk1_unique1 on tenk1 t1 - Index Cond: (unique1 = 1) - -> Nested Loop - Join Filter: (t1.ten = t3.ten) - -> Bitmap Heap Scan on tenk1 t2 - Recheck Cond: (t1.hundred = hundred) - -> Bitmap Index Scan on tenk1_hundred - Index Cond: (hundred = t1.hundred) - -> Index Scan using tenk1_unique2 on tenk1 t3 - Index Cond: (unique2 = t2.thousand) -(11 rows) - -explain (costs off) -select * from tenk1 t1 left join - (tenk1 t2 join tenk1 t3 on t2.thousand = t3.unique2) - on t1.hundred = t2.hundred and t1.ten + t2.ten = t3.ten -where t1.unique1 = 1; - QUERY PLAN --------------------------------------------------------- - Nested Loop Left Join - -> Index Scan using tenk1_unique1 on tenk1 t1 - Index Cond: (unique1 = 1) - -> Nested Loop - Join Filter: ((t1.ten + t2.ten) = t3.ten) - -> Bitmap Heap Scan on tenk1 t2 - Recheck Cond: (t1.hundred = hundred) - -> Bitmap Index Scan on tenk1_hundred - Index Cond: (hundred = t1.hundred) - -> Index Scan using tenk1_unique2 on tenk1 t3 - Index Cond: (unique2 = t2.thousand) -(11 rows) - -explain (costs off) -select count(*) from - tenk1 a join tenk1 b on a.unique1 = b.unique2 - left join tenk1 c on a.unique2 = b.unique1 and c.thousand = a.thousand - join int4_tbl on b.thousand = f1; - QUERY PLAN -------------------------------------------------------------------------- - Aggregate - -> Nested Loop Left Join - Join Filter: (a.unique2 = b.unique1) - -> Nested Loop - -> Nested Loop - -> Seq Scan on int4_tbl - -> Bitmap Heap Scan on tenk1 b - Recheck Cond: (thousand = int4_tbl.f1) - -> Bitmap Index Scan on tenk1_thous_tenthous - Index Cond: (thousand = int4_tbl.f1) - -> Index Scan using tenk1_unique1 on tenk1 a - Index Cond: (unique1 = b.unique2) - -> Index Only Scan using tenk1_thous_tenthous on tenk1 c - Index Cond: (thousand = a.thousand) -(14 rows) - -select count(*) from - tenk1 a join tenk1 b on a.unique1 = b.unique2 - left join tenk1 c on a.unique2 = b.unique1 and c.thousand = a.thousand - join int4_tbl on b.thousand = f1; - count -------- - 10 -(1 row) - -explain (costs off) -select b.unique1 from - tenk1 a join tenk1 b on a.unique1 = b.unique2 - left join tenk1 c on b.unique1 = 42 and c.thousand = a.thousand - join int4_tbl i1 on b.thousand = f1 - right join int4_tbl i2 on i2.f1 = b.tenthous - order by 1; - QUERY PLAN ------------------------------------------------------------------------------------------ - Sort - Sort Key: b.unique1 - -> Nested Loop Left Join - -> Seq Scan on int4_tbl i2 - -> Nested Loop Left Join - Join Filter: (b.unique1 = 42) - -> Nested Loop - -> Nested Loop - -> Seq Scan on int4_tbl i1 - -> Index Scan using tenk1_thous_tenthous on tenk1 b - Index Cond: ((thousand = i1.f1) AND (tenthous = i2.f1)) - -> Index Scan using tenk1_unique1 on tenk1 a - Index Cond: (unique1 = b.unique2) - -> Index Only Scan using tenk1_thous_tenthous on tenk1 c - Index Cond: (thousand = a.thousand) -(15 rows) - -select b.unique1 from - tenk1 a join tenk1 b on a.unique1 = b.unique2 - left join tenk1 c on b.unique1 = 42 and c.thousand = a.thousand - join int4_tbl i1 on b.thousand = f1 - right join int4_tbl i2 on i2.f1 = b.tenthous - order by 1; - unique1 ---------- - 0 - - - - -(5 rows) - -explain (costs off) -select * from -( - select unique1, q1, coalesce(unique1, -1) + q1 as fault - from int8_tbl left join tenk1 on (q2 = unique2) -) ss -where fault = 122 -order by fault; - QUERY PLAN --------------------------------------------------------------------------- - Nested Loop Left Join - Filter: ((COALESCE(tenk1.unique1, '-1'::integer) + int8_tbl.q1) = 122) - -> Seq Scan on int8_tbl - -> Index Scan using tenk1_unique2 on tenk1 - Index Cond: (unique2 = int8_tbl.q2) -(5 rows) - -select * from -( - select unique1, q1, coalesce(unique1, -1) + q1 as fault - from int8_tbl left join tenk1 on (q2 = unique2) -) ss -where fault = 122 -order by fault; - unique1 | q1 | fault ----------+-----+------- - | 123 | 122 -(1 row) - -explain (costs off) -select * from -(values (1, array[10,20]), (2, array[20,30])) as v1(v1x,v1ys) -left join (values (1, 10), (2, 20)) as v2(v2x,v2y) on v2x = v1x -left join unnest(v1ys) as u1(u1y) on u1y = v2y; - QUERY PLAN -------------------------------------------------------------- - Nested Loop Left Join - -> Values Scan on "*VALUES*" - -> Hash Right Join - Hash Cond: (u1.u1y = "*VALUES*_1".column2) - Filter: ("*VALUES*_1".column1 = "*VALUES*".column1) - -> Function Scan on unnest u1 - -> Hash - -> Values Scan on "*VALUES*_1" -(8 rows) - -select * from -(values (1, array[10,20]), (2, array[20,30])) as v1(v1x,v1ys) -left join (values (1, 10), (2, 20)) as v2(v2x,v2y) on v2x = v1x -left join unnest(v1ys) as u1(u1y) on u1y = v2y; - v1x | v1ys | v2x | v2y | u1y ------+---------+-----+-----+----- - 1 | {10,20} | 1 | 10 | 10 - 2 | {20,30} | 2 | 20 | 20 -(2 rows) - --- --- test handling of potential equivalence clauses above outer joins --- -explain (costs off) -select q1, unique2, thousand, hundred - from int8_tbl a left join tenk1 b on q1 = unique2 - where coalesce(thousand,123) = q1 and q1 = coalesce(hundred,123); - QUERY PLAN ----------------------------------------------------------------------------------------------------------- - Nested Loop Left Join - Filter: ((COALESCE(b.thousand, 123) = COALESCE(b.hundred, 123)) AND (a.q1 = COALESCE(b.hundred, 123))) - -> Seq Scan on int8_tbl a - -> Index Scan using tenk1_unique2 on tenk1 b - Index Cond: (unique2 = a.q1) -(5 rows) - -select q1, unique2, thousand, hundred - from int8_tbl a left join tenk1 b on q1 = unique2 - where coalesce(thousand,123) = q1 and q1 = coalesce(hundred,123); - q1 | unique2 | thousand | hundred -----+---------+----------+--------- -(0 rows) - -explain (costs off) -select f1, unique2, case when unique2 is null then f1 else 0 end - from int4_tbl a left join tenk1 b on f1 = unique2 - where (case when unique2 is null then f1 else 0 end) = 0; - QUERY PLAN --------------------------------------------------------------------- - Nested Loop Left Join - Filter: (CASE WHEN (b.unique2 IS NULL) THEN a.f1 ELSE 0 END = 0) - -> Seq Scan on int4_tbl a - -> Index Only Scan using tenk1_unique2 on tenk1 b - Index Cond: (unique2 = a.f1) -(5 rows) - -select f1, unique2, case when unique2 is null then f1 else 0 end - from int4_tbl a left join tenk1 b on f1 = unique2 - where (case when unique2 is null then f1 else 0 end) = 0; - f1 | unique2 | case -----+---------+------ - 0 | 0 | 0 -(1 row) - --- --- another case with equivalence clauses above outer joins (bug #8591) --- -explain (costs off) -select a.unique1, b.unique1, c.unique1, coalesce(b.twothousand, a.twothousand) - from tenk1 a left join tenk1 b on b.thousand = a.unique1 left join tenk1 c on c.unique2 = coalesce(b.twothousand, a.twothousand) - where a.unique2 < 10 and coalesce(b.twothousand, a.twothousand) = 44; - QUERY PLAN ---------------------------------------------------------------- - Nested Loop Left Join - -> Nested Loop Left Join - Filter: (COALESCE(b.twothousand, a.twothousand) = 44) - -> Index Scan using tenk1_unique2 on tenk1 a - Index Cond: (unique2 < 10) - -> Bitmap Heap Scan on tenk1 b - Recheck Cond: (thousand = a.unique1) - -> Bitmap Index Scan on tenk1_thous_tenthous - Index Cond: (thousand = a.unique1) - -> Index Scan using tenk1_unique2 on tenk1 c - Index Cond: (unique2 = 44) -(11 rows) - -select a.unique1, b.unique1, c.unique1, coalesce(b.twothousand, a.twothousand) - from tenk1 a left join tenk1 b on b.thousand = a.unique1 left join tenk1 c on c.unique2 = coalesce(b.twothousand, a.twothousand) - where a.unique2 < 10 and coalesce(b.twothousand, a.twothousand) = 44; - unique1 | unique1 | unique1 | coalesce ----------+---------+---------+---------- -(0 rows) - --- related case -explain (costs off) -select * from int8_tbl t1 left join int8_tbl t2 on t1.q2 = t2.q1, - lateral (select * from int8_tbl t3 where t2.q1 = t2.q2) ss; - QUERY PLAN -------------------------------------------- - Nested Loop - -> Hash Left Join - Hash Cond: (t1.q2 = t2.q1) - Filter: (t2.q1 = t2.q2) - -> Seq Scan on int8_tbl t1 - -> Hash - -> Seq Scan on int8_tbl t2 - -> Seq Scan on int8_tbl t3 -(8 rows) - -select * from int8_tbl t1 left join int8_tbl t2 on t1.q2 = t2.q1, - lateral (select * from int8_tbl t3 where t2.q1 = t2.q2) ss; - q1 | q2 | q1 | q2 | q1 | q2 -------------------+------------------+------------------+------------------+------------------+------------------- - 123 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 | 456 - 123 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 | 4567890123456789 - 123 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 - 123 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 - 123 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | -4567890123456789 - 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 | 456 - 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 | 4567890123456789 - 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 - 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 - 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | -4567890123456789 -(10 rows) - --- --- check handling of join aliases when flattening multiple levels of subquery --- -explain (verbose, costs off) -select foo1.join_key as foo1_id, foo3.join_key AS foo3_id, bug_field from - (values (0),(1)) foo1(join_key) -left join - (select join_key, bug_field from - (select ss1.join_key, ss1.bug_field from - (select f1 as join_key, 666 as bug_field from int4_tbl i1) ss1 - ) foo2 - left join - (select unique2 as join_key from tenk1 i2) ss2 - using (join_key) - ) foo3 -using (join_key); - QUERY PLAN --------------------------------------------------------------------------- - Nested Loop Left Join - Output: "*VALUES*".column1, i1.f1, (666) - Join Filter: ("*VALUES*".column1 = i1.f1) - -> Values Scan on "*VALUES*" - Output: "*VALUES*".column1 - -> Materialize - Output: i1.f1, (666) - -> Nested Loop Left Join - Output: i1.f1, 666 - -> Seq Scan on public.int4_tbl i1 - Output: i1.f1 - -> Index Only Scan using tenk1_unique2 on public.tenk1 i2 - Output: i2.unique2 - Index Cond: (i2.unique2 = i1.f1) -(14 rows) - -select foo1.join_key as foo1_id, foo3.join_key AS foo3_id, bug_field from - (values (0),(1)) foo1(join_key) -left join - (select join_key, bug_field from - (select ss1.join_key, ss1.bug_field from - (select f1 as join_key, 666 as bug_field from int4_tbl i1) ss1 - ) foo2 - left join - (select unique2 as join_key from tenk1 i2) ss2 - using (join_key) - ) foo3 -using (join_key); - foo1_id | foo3_id | bug_field ----------+---------+----------- - 0 | 0 | 666 - 1 | | -(2 rows) - --- --- check handling of a variable-free join alias --- -explain (verbose, costs off) -select * from -int4_tbl i0 left join -( (select *, 123 as x from int4_tbl i1) ss1 - left join - (select *, q2 as x from int8_tbl i2) ss2 - using (x) -) ss0 -on (i0.f1 = ss0.f1) -order by i0.f1, x; - QUERY PLAN -------------------------------------------------------------- - Sort - Output: i0.f1, ('123'::bigint), i1.f1, i2.q1, i2.q2 - Sort Key: i0.f1, ('123'::bigint) - -> Hash Right Join - Output: i0.f1, ('123'::bigint), i1.f1, i2.q1, i2.q2 - Hash Cond: (i1.f1 = i0.f1) - -> Nested Loop Left Join - Output: i1.f1, i2.q1, i2.q2, '123'::bigint - -> Seq Scan on public.int4_tbl i1 - Output: i1.f1 - -> Materialize - Output: i2.q1, i2.q2 - -> Seq Scan on public.int8_tbl i2 - Output: i2.q1, i2.q2 - Filter: (123 = i2.q2) - -> Hash - Output: i0.f1 - -> Seq Scan on public.int4_tbl i0 - Output: i0.f1 -(19 rows) - -select * from -int4_tbl i0 left join -( (select *, 123 as x from int4_tbl i1) ss1 - left join - (select *, q2 as x from int8_tbl i2) ss2 - using (x) -) ss0 -on (i0.f1 = ss0.f1) -order by i0.f1, x; - f1 | x | f1 | q1 | q2 --------------+-----+-------------+------------------+----- - -2147483647 | 123 | -2147483647 | 4567890123456789 | 123 - -123456 | 123 | -123456 | 4567890123456789 | 123 - 0 | 123 | 0 | 4567890123456789 | 123 - 123456 | 123 | 123456 | 4567890123456789 | 123 - 2147483647 | 123 | 2147483647 | 4567890123456789 | 123 -(5 rows) - --- --- test successful handling of nested outer joins with degenerate join quals --- -explain (verbose, costs off) -select t1.* from - text_tbl t1 - left join (select *, '***'::text as d1 from int8_tbl i8b1) b1 - left join int8_tbl i8 - left join (select *, null::int as d2 from int8_tbl i8b2) b2 - on (i8.q1 = b2.q1) - on (b2.d2 = b1.q2) - on (t1.f1 = b1.d1) - left join int4_tbl i4 - on (i8.q2 = i4.f1); - QUERY PLAN ----------------------------------------------------------------------- - Hash Left Join - Output: t1.f1 - Hash Cond: (i8.q2 = i4.f1) - -> Nested Loop Left Join - Output: t1.f1, i8.q2 - Join Filter: (t1.f1 = '***'::text) - -> Seq Scan on public.text_tbl t1 - Output: t1.f1 - -> Materialize - Output: i8.q2 - -> Hash Right Join - Output: i8.q2 - Hash Cond: ((NULL::integer) = i8b1.q2) - -> Hash Join - Output: i8.q2, (NULL::integer) - Hash Cond: (i8.q1 = i8b2.q1) - -> Seq Scan on public.int8_tbl i8 - Output: i8.q1, i8.q2 - -> Hash - Output: i8b2.q1, (NULL::integer) - -> Seq Scan on public.int8_tbl i8b2 - Output: i8b2.q1, NULL::integer - -> Hash - Output: i8b1.q2 - -> Seq Scan on public.int8_tbl i8b1 - Output: i8b1.q2 - -> Hash - Output: i4.f1 - -> Seq Scan on public.int4_tbl i4 - Output: i4.f1 -(30 rows) - -select t1.* from - text_tbl t1 - left join (select *, '***'::text as d1 from int8_tbl i8b1) b1 - left join int8_tbl i8 - left join (select *, null::int as d2 from int8_tbl i8b2) b2 - on (i8.q1 = b2.q1) - on (b2.d2 = b1.q2) - on (t1.f1 = b1.d1) - left join int4_tbl i4 - on (i8.q2 = i4.f1); - f1 -------------------- - doh! - hi de ho neighbor -(2 rows) - -explain (verbose, costs off) -select t1.* from - text_tbl t1 - left join (select *, '***'::text as d1 from int8_tbl i8b1) b1 - left join int8_tbl i8 - left join (select *, null::int as d2 from int8_tbl i8b2, int4_tbl i4b2) b2 - on (i8.q1 = b2.q1) - on (b2.d2 = b1.q2) - on (t1.f1 = b1.d1) - left join int4_tbl i4 - on (i8.q2 = i4.f1); - QUERY PLAN ----------------------------------------------------------------------------- - Hash Left Join - Output: t1.f1 - Hash Cond: (i8.q2 = i4.f1) - -> Nested Loop Left Join - Output: t1.f1, i8.q2 - Join Filter: (t1.f1 = '***'::text) - -> Seq Scan on public.text_tbl t1 - Output: t1.f1 - -> Materialize - Output: i8.q2 - -> Hash Right Join - Output: i8.q2 - Hash Cond: ((NULL::integer) = i8b1.q2) - -> Hash Right Join - Output: i8.q2, (NULL::integer) - Hash Cond: (i8b2.q1 = i8.q1) - -> Nested Loop - Output: i8b2.q1, NULL::integer - -> Seq Scan on public.int8_tbl i8b2 - Output: i8b2.q1, i8b2.q2 - -> Materialize - -> Seq Scan on public.int4_tbl i4b2 - -> Hash - Output: i8.q1, i8.q2 - -> Seq Scan on public.int8_tbl i8 - Output: i8.q1, i8.q2 - -> Hash - Output: i8b1.q2 - -> Seq Scan on public.int8_tbl i8b1 - Output: i8b1.q2 - -> Hash - Output: i4.f1 - -> Seq Scan on public.int4_tbl i4 - Output: i4.f1 -(34 rows) - -select t1.* from - text_tbl t1 - left join (select *, '***'::text as d1 from int8_tbl i8b1) b1 - left join int8_tbl i8 - left join (select *, null::int as d2 from int8_tbl i8b2, int4_tbl i4b2) b2 - on (i8.q1 = b2.q1) - on (b2.d2 = b1.q2) - on (t1.f1 = b1.d1) - left join int4_tbl i4 - on (i8.q2 = i4.f1); - f1 -------------------- - doh! - hi de ho neighbor -(2 rows) - -explain (verbose, costs off) -select t1.* from - text_tbl t1 - left join (select *, '***'::text as d1 from int8_tbl i8b1) b1 - left join int8_tbl i8 - left join (select *, null::int as d2 from int8_tbl i8b2, int4_tbl i4b2 - where q1 = f1) b2 - on (i8.q1 = b2.q1) - on (b2.d2 = b1.q2) - on (t1.f1 = b1.d1) - left join int4_tbl i4 - on (i8.q2 = i4.f1); - QUERY PLAN ----------------------------------------------------------------------------- - Hash Left Join - Output: t1.f1 - Hash Cond: (i8.q2 = i4.f1) - -> Nested Loop Left Join - Output: t1.f1, i8.q2 - Join Filter: (t1.f1 = '***'::text) - -> Seq Scan on public.text_tbl t1 - Output: t1.f1 - -> Materialize - Output: i8.q2 - -> Hash Right Join - Output: i8.q2 - Hash Cond: ((NULL::integer) = i8b1.q2) - -> Hash Right Join - Output: i8.q2, (NULL::integer) - Hash Cond: (i8b2.q1 = i8.q1) - -> Hash Join - Output: i8b2.q1, NULL::integer - Hash Cond: (i8b2.q1 = i4b2.f1) - -> Seq Scan on public.int8_tbl i8b2 - Output: i8b2.q1, i8b2.q2 - -> Hash - Output: i4b2.f1 - -> Seq Scan on public.int4_tbl i4b2 - Output: i4b2.f1 - -> Hash - Output: i8.q1, i8.q2 - -> Seq Scan on public.int8_tbl i8 - Output: i8.q1, i8.q2 - -> Hash - Output: i8b1.q2 - -> Seq Scan on public.int8_tbl i8b1 - Output: i8b1.q2 - -> Hash - Output: i4.f1 - -> Seq Scan on public.int4_tbl i4 - Output: i4.f1 -(37 rows) - -select t1.* from - text_tbl t1 - left join (select *, '***'::text as d1 from int8_tbl i8b1) b1 - left join int8_tbl i8 - left join (select *, null::int as d2 from int8_tbl i8b2, int4_tbl i4b2 - where q1 = f1) b2 - on (i8.q1 = b2.q1) - on (b2.d2 = b1.q2) - on (t1.f1 = b1.d1) - left join int4_tbl i4 - on (i8.q2 = i4.f1); - f1 -------------------- - doh! - hi de ho neighbor -(2 rows) - -explain (verbose, costs off) -select * from - text_tbl t1 - inner join int8_tbl i8 - on i8.q2 = 456 - right join text_tbl t2 - on t1.f1 = 'doh!' - left join int4_tbl i4 - on i8.q1 = i4.f1; - QUERY PLAN --------------------------------------------------------- - Nested Loop Left Join - Output: t1.f1, i8.q1, i8.q2, t2.f1, i4.f1 - -> Seq Scan on public.text_tbl t2 - Output: t2.f1 - -> Materialize - Output: i8.q1, i8.q2, i4.f1, t1.f1 - -> Nested Loop - Output: i8.q1, i8.q2, i4.f1, t1.f1 - -> Nested Loop Left Join - Output: i8.q1, i8.q2, i4.f1 - Join Filter: (i8.q1 = i4.f1) - -> Seq Scan on public.int8_tbl i8 - Output: i8.q1, i8.q2 - Filter: (i8.q2 = 456) - -> Seq Scan on public.int4_tbl i4 - Output: i4.f1 - -> Seq Scan on public.text_tbl t1 - Output: t1.f1 - Filter: (t1.f1 = 'doh!'::text) -(19 rows) - -select * from - text_tbl t1 - inner join int8_tbl i8 - on i8.q2 = 456 - right join text_tbl t2 - on t1.f1 = 'doh!' - left join int4_tbl i4 - on i8.q1 = i4.f1; - f1 | q1 | q2 | f1 | f1 -------+-----+-----+-------------------+---- - doh! | 123 | 456 | doh! | - doh! | 123 | 456 | hi de ho neighbor | -(2 rows) - --- check handling of a variable-free qual for a non-commutable outer join -explain (costs off) -select nspname -from (select 1 as x) ss1 -left join -( select n.nspname, c.relname - from pg_class c left join pg_namespace n on n.oid = c.relnamespace - where c.relkind = 'r' -) ss2 on false; - QUERY PLAN --------------------------------- - Nested Loop Left Join - Join Filter: false - -> Result - -> Result - One-Time Filter: false -(5 rows) - --- check handling of apparently-commutable outer joins with non-commutable --- joins between them -explain (costs off) -select 1 from - int4_tbl i4 - left join int8_tbl i8 on i4.f1 is not null - left join (select 1 as a) ss1 on null - join int4_tbl i42 on ss1.a is null or i8.q1 <> i8.q2 - right join (select 2 as b) ss2 - on ss2.b < i4.f1; - QUERY PLAN ------------------------------------------------------------ - Nested Loop Left Join - -> Result - -> Nested Loop - -> Nested Loop Left Join - Join Filter: NULL::boolean - Filter: (((1) IS NULL) OR (i8.q1 <> i8.q2)) - -> Nested Loop Left Join - Join Filter: (i4.f1 IS NOT NULL) - -> Seq Scan on int4_tbl i4 - Filter: (2 < f1) - -> Materialize - -> Seq Scan on int8_tbl i8 - -> Result - One-Time Filter: false - -> Materialize - -> Seq Scan on int4_tbl i42 -(16 rows) - --- --- test for appropriate join order in the presence of lateral references --- -explain (verbose, costs off) -select * from - text_tbl t1 - left join int8_tbl i8 - on i8.q2 = 123, - lateral (select i8.q1, t2.f1 from text_tbl t2 limit 1) as ss -where t1.f1 = ss.f1; - QUERY PLAN --------------------------------------------------- - Nested Loop - Output: t1.f1, i8.q1, i8.q2, (i8.q1), t2.f1 - Join Filter: (t1.f1 = t2.f1) - -> Nested Loop Left Join - Output: t1.f1, i8.q1, i8.q2 - -> Seq Scan on public.text_tbl t1 - Output: t1.f1 - -> Materialize - Output: i8.q1, i8.q2 - -> Seq Scan on public.int8_tbl i8 - Output: i8.q1, i8.q2 - Filter: (i8.q2 = 123) - -> Memoize - Output: (i8.q1), t2.f1 - Cache Key: i8.q1 - Cache Mode: binary - -> Limit - Output: (i8.q1), t2.f1 - -> Seq Scan on public.text_tbl t2 - Output: i8.q1, t2.f1 -(20 rows) - -select * from - text_tbl t1 - left join int8_tbl i8 - on i8.q2 = 123, - lateral (select i8.q1, t2.f1 from text_tbl t2 limit 1) as ss -where t1.f1 = ss.f1; - f1 | q1 | q2 | q1 | f1 -------+------------------+-----+------------------+------ - doh! | 4567890123456789 | 123 | 4567890123456789 | doh! -(1 row) - -explain (verbose, costs off) -select * from - text_tbl t1 - left join int8_tbl i8 - on i8.q2 = 123, - lateral (select i8.q1, t2.f1 from text_tbl t2 limit 1) as ss1, - lateral (select ss1.* from text_tbl t3 limit 1) as ss2 -where t1.f1 = ss2.f1; - QUERY PLAN -------------------------------------------------------------------- - Nested Loop - Output: t1.f1, i8.q1, i8.q2, (i8.q1), t2.f1, ((i8.q1)), (t2.f1) - Join Filter: (t1.f1 = (t2.f1)) - -> Nested Loop - Output: t1.f1, i8.q1, i8.q2, (i8.q1), t2.f1 - -> Nested Loop Left Join - Output: t1.f1, i8.q1, i8.q2 - -> Seq Scan on public.text_tbl t1 - Output: t1.f1 - -> Materialize - Output: i8.q1, i8.q2 - -> Seq Scan on public.int8_tbl i8 - Output: i8.q1, i8.q2 - Filter: (i8.q2 = 123) - -> Memoize - Output: (i8.q1), t2.f1 - Cache Key: i8.q1 - Cache Mode: binary - -> Limit - Output: (i8.q1), t2.f1 - -> Seq Scan on public.text_tbl t2 - Output: i8.q1, t2.f1 - -> Memoize - Output: ((i8.q1)), (t2.f1) - Cache Key: (i8.q1), t2.f1 - Cache Mode: binary - -> Limit - Output: ((i8.q1)), (t2.f1) - -> Seq Scan on public.text_tbl t3 - Output: (i8.q1), t2.f1 -(30 rows) - -select * from - text_tbl t1 - left join int8_tbl i8 - on i8.q2 = 123, - lateral (select i8.q1, t2.f1 from text_tbl t2 limit 1) as ss1, - lateral (select ss1.* from text_tbl t3 limit 1) as ss2 -where t1.f1 = ss2.f1; - f1 | q1 | q2 | q1 | f1 | q1 | f1 -------+------------------+-----+------------------+------+------------------+------ - doh! | 4567890123456789 | 123 | 4567890123456789 | doh! | 4567890123456789 | doh! -(1 row) - -explain (verbose, costs off) -select 1 from - text_tbl as tt1 - inner join text_tbl as tt2 on (tt1.f1 = 'foo') - left join text_tbl as tt3 on (tt3.f1 = 'foo') - left join text_tbl as tt4 on (tt3.f1 = tt4.f1), - lateral (select tt4.f1 as c0 from text_tbl as tt5 limit 1) as ss1 -where tt1.f1 = ss1.c0; - QUERY PLAN ----------------------------------------------------------- - Nested Loop - Output: 1 - -> Nested Loop Left Join - Output: tt1.f1, tt4.f1 - -> Nested Loop - Output: tt1.f1 - -> Seq Scan on public.text_tbl tt1 - Output: tt1.f1 - Filter: (tt1.f1 = 'foo'::text) - -> Seq Scan on public.text_tbl tt2 - Output: tt2.f1 - -> Materialize - Output: tt4.f1 - -> Nested Loop Left Join - Output: tt4.f1 - -> Seq Scan on public.text_tbl tt3 - Output: tt3.f1 - Filter: (tt3.f1 = 'foo'::text) - -> Seq Scan on public.text_tbl tt4 - Output: tt4.f1 - Filter: (tt4.f1 = 'foo'::text) - -> Memoize - Output: ss1.c0 - Cache Key: tt4.f1 - Cache Mode: binary - -> Subquery Scan on ss1 - Output: ss1.c0 - Filter: (ss1.c0 = 'foo'::text) - -> Limit - Output: (tt4.f1) - -> Seq Scan on public.text_tbl tt5 - Output: tt4.f1 -(32 rows) - -select 1 from - text_tbl as tt1 - inner join text_tbl as tt2 on (tt1.f1 = 'foo') - left join text_tbl as tt3 on (tt3.f1 = 'foo') - left join text_tbl as tt4 on (tt3.f1 = tt4.f1), - lateral (select tt4.f1 as c0 from text_tbl as tt5 limit 1) as ss1 -where tt1.f1 = ss1.c0; - ?column? ----------- -(0 rows) - -explain (verbose, costs off) -select 1 from - int4_tbl as i4 - inner join - ((select 42 as n from int4_tbl x1 left join int8_tbl x2 on f1 = q1) as ss1 - right join (select 1 as z) as ss2 on true) - on false, - lateral (select i4.f1, ss1.n from int8_tbl as i8 limit 1) as ss3; - QUERY PLAN --------------------------- - Result - Output: 1 - One-Time Filter: false -(3 rows) - -select 1 from - int4_tbl as i4 - inner join - ((select 42 as n from int4_tbl x1 left join int8_tbl x2 on f1 = q1) as ss1 - right join (select 1 as z) as ss2 on true) - on false, - lateral (select i4.f1, ss1.n from int8_tbl as i8 limit 1) as ss3; - ?column? ----------- -(0 rows) - --- --- check a case where we formerly generated invalid parameterized paths --- -begin; -create temp table t (a int unique); -explain (costs off) -select 1 from t t1 - join lateral (select t1.a from (select 1) foo offset 0) as s1 on true - join - (select 1 from t t2 - inner join (t t3 - left join (t t4 left join t t5 on t4.a = 1) - on t3.a = t4.a) - on false - where t3.a = coalesce(t5.a,1)) as s2 - on true; - QUERY PLAN --------------------------- - Result - One-Time Filter: false -(2 rows) - -rollback; --- --- check a case in which a PlaceHolderVar forces join order --- -explain (verbose, costs off) -select ss2.* from - int4_tbl i41 - left join int8_tbl i8 - join (select i42.f1 as c1, i43.f1 as c2, 42 as c3 - from int4_tbl i42, int4_tbl i43) ss1 - on i8.q1 = ss1.c2 - on i41.f1 = ss1.c1, - lateral (select i41.*, i8.*, ss1.* from text_tbl limit 1) ss2 -where ss1.c2 = 0; - QUERY PLAN ------------------------------------------------------------------------- - Nested Loop - Output: (i41.f1), (i8.q1), (i8.q2), (i42.f1), (i43.f1), ((42)) - -> Hash Join - Output: i41.f1, i42.f1, i8.q1, i8.q2, i43.f1, 42 - Hash Cond: (i41.f1 = i42.f1) - -> Nested Loop - Output: i8.q1, i8.q2, i43.f1, i41.f1 - -> Nested Loop - Output: i8.q1, i8.q2, i43.f1 - -> Seq Scan on public.int8_tbl i8 - Output: i8.q1, i8.q2 - Filter: (i8.q1 = 0) - -> Seq Scan on public.int4_tbl i43 - Output: i43.f1 - Filter: (i43.f1 = 0) - -> Seq Scan on public.int4_tbl i41 - Output: i41.f1 - -> Hash - Output: i42.f1 - -> Seq Scan on public.int4_tbl i42 - Output: i42.f1 - -> Limit - Output: (i41.f1), (i8.q1), (i8.q2), (i42.f1), (i43.f1), ((42)) - -> Seq Scan on public.text_tbl - Output: i41.f1, i8.q1, i8.q2, i42.f1, i43.f1, (42) -(25 rows) - -select ss2.* from - int4_tbl i41 - left join int8_tbl i8 - join (select i42.f1 as c1, i43.f1 as c2, 42 as c3 - from int4_tbl i42, int4_tbl i43) ss1 - on i8.q1 = ss1.c2 - on i41.f1 = ss1.c1, - lateral (select i41.*, i8.*, ss1.* from text_tbl limit 1) ss2 -where ss1.c2 = 0; - f1 | q1 | q2 | c1 | c2 | c3 -----+----+----+----+----+---- -(0 rows) - --- --- test successful handling of full join underneath left join (bug #14105) --- -explain (costs off) -select * from - (select 1 as id) as xx - left join - (tenk1 as a1 full join (select 1 as id) as yy on (a1.unique1 = yy.id)) - on (xx.id = coalesce(yy.id)); - QUERY PLAN ---------------------------------------- - Nested Loop Left Join - -> Result - -> Hash Full Join - Hash Cond: (a1.unique1 = (1)) - Filter: (1 = COALESCE((1))) - -> Seq Scan on tenk1 a1 - -> Hash - -> Result -(8 rows) - -select * from - (select 1 as id) as xx - left join - (tenk1 as a1 full join (select 1 as id) as yy on (a1.unique1 = yy.id)) - on (xx.id = coalesce(yy.id)); - id | unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 | id -----+---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+---------+---- - 1 | 1 | 2838 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 2 | 3 | BAAAAA | EFEAAA | OOOOxx | 1 -(1 row) - --- --- test ability to push constants through outer join clauses --- -explain (costs off) - select * from int4_tbl a left join tenk1 b on f1 = unique2 where f1 = 0; - QUERY PLAN -------------------------------------------------- - Nested Loop Left Join - -> Seq Scan on int4_tbl a - Filter: (f1 = 0) - -> Index Scan using tenk1_unique2 on tenk1 b - Index Cond: (unique2 = 0) -(5 rows) - -explain (costs off) - select * from tenk1 a full join tenk1 b using(unique2) where unique2 = 42; - QUERY PLAN -------------------------------------------------- - Merge Full Join - -> Index Scan using tenk1_unique2 on tenk1 a - Index Cond: (unique2 = 42) - -> Index Scan using tenk1_unique2 on tenk1 b - Index Cond: (unique2 = 42) -(5 rows) - --- --- test that quals attached to an outer join have correct semantics, --- specifically that they don't re-use expressions computed below the join; --- we force a mergejoin so that coalesce(b.q1, 1) appears as a join input --- -set enable_hashjoin to off; -set enable_nestloop to off; -explain (verbose, costs off) - select a.q2, b.q1 - from int8_tbl a left join int8_tbl b on a.q2 = coalesce(b.q1, 1) - where coalesce(b.q1, 1) > 0; - QUERY PLAN ---------------------------------------------------------- - Merge Left Join - Output: a.q2, b.q1 - Merge Cond: (a.q2 = (COALESCE(b.q1, '1'::bigint))) - Filter: (COALESCE(b.q1, '1'::bigint) > 0) - -> Sort - Output: a.q2 - Sort Key: a.q2 - -> Seq Scan on public.int8_tbl a - Output: a.q2 - -> Sort - Output: b.q1, (COALESCE(b.q1, '1'::bigint)) - Sort Key: (COALESCE(b.q1, '1'::bigint)) - -> Seq Scan on public.int8_tbl b - Output: b.q1, COALESCE(b.q1, '1'::bigint) -(14 rows) - -select a.q2, b.q1 - from int8_tbl a left join int8_tbl b on a.q2 = coalesce(b.q1, 1) - where coalesce(b.q1, 1) > 0; - q2 | q1 --------------------+------------------ - -4567890123456789 | - 123 | 123 - 123 | 123 - 456 | - 4567890123456789 | 4567890123456789 - 4567890123456789 | 4567890123456789 - 4567890123456789 | 4567890123456789 - 4567890123456789 | 4567890123456789 - 4567890123456789 | 4567890123456789 - 4567890123456789 | 4567890123456789 -(10 rows) - -reset enable_hashjoin; -reset enable_nestloop; --- --- test join strength reduction with a SubPlan providing the proof --- -explain (costs off) -select a.unique1, b.unique2 - from onek a left join onek b on a.unique1 = b.unique2 - where (b.unique2, random() > 0) = any (select q1, random() > 0 from int8_tbl c where c.q1 < b.unique1); - QUERY PLAN ------------------------------------------------------------------------------------------------------------------- - Hash Join - Hash Cond: (b.unique2 = a.unique1) - -> Seq Scan on onek b - Filter: (ANY ((unique2 = (SubPlan 1).col1) AND ((random() > '0'::double precision) = (SubPlan 1).col2))) - SubPlan 1 - -> Seq Scan on int8_tbl c - Filter: (q1 < b.unique1) - -> Hash - -> Index Only Scan using onek_unique1 on onek a -(9 rows) - -select a.unique1, b.unique2 - from onek a left join onek b on a.unique1 = b.unique2 - where (b.unique2, random() > 0) = any (select q1, random() > 0 from int8_tbl c where c.q1 < b.unique1); - unique1 | unique2 ----------+--------- - 123 | 123 -(1 row) - --- --- test full-join strength reduction --- -explain (costs off) -select a.unique1, b.unique2 - from onek a full join onek b on a.unique1 = b.unique2 - where a.unique1 = 42; - QUERY PLAN ----------------------------------------------------- - Nested Loop Left Join - -> Index Only Scan using onek_unique1 on onek a - Index Cond: (unique1 = 42) - -> Index Only Scan using onek_unique2 on onek b - Index Cond: (unique2 = 42) -(5 rows) - -select a.unique1, b.unique2 - from onek a full join onek b on a.unique1 = b.unique2 - where a.unique1 = 42; - unique1 | unique2 ----------+--------- - 42 | 42 -(1 row) - -explain (costs off) -select a.unique1, b.unique2 - from onek a full join onek b on a.unique1 = b.unique2 - where b.unique2 = 43; - QUERY PLAN ----------------------------------------------------- - Nested Loop Left Join - -> Index Only Scan using onek_unique2 on onek b - Index Cond: (unique2 = 43) - -> Index Only Scan using onek_unique1 on onek a - Index Cond: (unique1 = 43) -(5 rows) - -select a.unique1, b.unique2 - from onek a full join onek b on a.unique1 = b.unique2 - where b.unique2 = 43; - unique1 | unique2 ----------+--------- - 43 | 43 -(1 row) - -explain (costs off) -select a.unique1, b.unique2 - from onek a full join onek b on a.unique1 = b.unique2 - where a.unique1 = 42 and b.unique2 = 42; - QUERY PLAN ----------------------------------------------------- - Nested Loop - -> Index Only Scan using onek_unique1 on onek a - Index Cond: (unique1 = 42) - -> Index Only Scan using onek_unique2 on onek b - Index Cond: (unique2 = 42) -(5 rows) - -select a.unique1, b.unique2 - from onek a full join onek b on a.unique1 = b.unique2 - where a.unique1 = 42 and b.unique2 = 42; - unique1 | unique2 ----------+--------- - 42 | 42 -(1 row) - --- --- test result-RTE removal underneath a full join --- -explain (costs off) -select * from - (select * from int8_tbl i81 join (values(123,2)) v(v1,v2) on q2=v1) ss1 -full join - (select * from (values(456,2)) w(v1,v2) join int8_tbl i82 on q2=v1) ss2 -on true; - QUERY PLAN --------------------------------------- - Merge Full Join - -> Seq Scan on int8_tbl i81 - Filter: (q2 = 123) - -> Materialize - -> Seq Scan on int8_tbl i82 - Filter: (q2 = 456) -(6 rows) - -select * from - (select * from int8_tbl i81 join (values(123,2)) v(v1,v2) on q2=v1) ss1 -full join - (select * from (values(456,2)) w(v1,v2) join int8_tbl i82 on q2=v1) ss2 -on true; - q1 | q2 | v1 | v2 | v1 | v2 | q1 | q2 -------------------+-----+-----+----+-----+----+-----+----- - 4567890123456789 | 123 | 123 | 2 | 456 | 2 | 123 | 456 -(1 row) - --- --- test join removal --- -begin; -CREATE TEMP TABLE a (id int PRIMARY KEY, b_id int); -CREATE TEMP TABLE b (id int PRIMARY KEY, c_id int); -CREATE TEMP TABLE c (id int PRIMARY KEY); -CREATE TEMP TABLE d (a int, b int); -INSERT INTO a VALUES (0, 0), (1, NULL); -INSERT INTO b VALUES (0, 0), (1, NULL); -INSERT INTO c VALUES (0), (1); -INSERT INTO d VALUES (1,3), (2,2), (3,1); --- all three cases should be optimizable into a simple seqscan -explain (costs off) SELECT a.* FROM a LEFT JOIN b ON a.b_id = b.id; - QUERY PLAN ---------------- - Seq Scan on a -(1 row) - -explain (costs off) SELECT b.* FROM b LEFT JOIN c ON b.c_id = c.id; - QUERY PLAN ---------------- - Seq Scan on b -(1 row) - -explain (costs off) - SELECT a.* FROM a LEFT JOIN (b left join c on b.c_id = c.id) - ON (a.b_id = b.id); - QUERY PLAN ---------------- - Seq Scan on a -(1 row) - --- check optimization of outer join within another special join -explain (costs off) -select id from a where id in ( - select b.id from b left join c on b.id = c.id -); - QUERY PLAN ----------------------------- - Hash Join - Hash Cond: (a.id = b.id) - -> Seq Scan on a - -> Hash - -> Seq Scan on b -(5 rows) - --- check optimization with oddly-nested outer joins -explain (costs off) -select a1.id from - (a a1 left join a a2 on true) - left join - (a a3 left join a a4 on a3.id = a4.id) - on a2.id = a3.id; - QUERY PLAN ------------------------------- - Nested Loop Left Join - -> Seq Scan on a a1 - -> Materialize - -> Seq Scan on a a2 -(4 rows) - -explain (costs off) -select a1.id from - (a a1 left join a a2 on a1.id = a2.id) - left join - (a a3 left join a a4 on a3.id = a4.id) - on a2.id = a3.id; - QUERY PLAN ------------------- - Seq Scan on a a1 -(1 row) - -explain (costs off) -select 1 from a t1 - left join a t2 on true - inner join a t3 on true - left join a t4 on t2.id = t4.id and t2.id = t3.id; - QUERY PLAN ------------------------------------- - Nested Loop - -> Nested Loop Left Join - -> Seq Scan on a t1 - -> Materialize - -> Seq Scan on a t2 - -> Materialize - -> Seq Scan on a t3 -(7 rows) - --- another example (bug #17781) -explain (costs off) -select ss1.f1 -from int4_tbl as t1 - left join (int4_tbl as t2 - right join int4_tbl as t3 on null - left join (int4_tbl as t4 - right join int8_tbl as t5 on null) - on t2.f1 = t4.f1 - left join ((select null as f1 from int4_tbl as t6) as ss1 - inner join int8_tbl as t7 on null) - on t5.q1 = t7.q2) - on false; - QUERY PLAN --------------------------------- - Nested Loop Left Join - Join Filter: false - -> Seq Scan on int4_tbl t1 - -> Result - One-Time Filter: false -(5 rows) - --- variant with Var rather than PHV coming from t6 -explain (costs off) -select ss1.f1 -from int4_tbl as t1 - left join (int4_tbl as t2 - right join int4_tbl as t3 on null - left join (int4_tbl as t4 - right join int8_tbl as t5 on null) - on t2.f1 = t4.f1 - left join ((select f1 from int4_tbl as t6) as ss1 - inner join int8_tbl as t7 on null) - on t5.q1 = t7.q2) - on false; - QUERY PLAN --------------------------------- - Nested Loop Left Join - Join Filter: false - -> Seq Scan on int4_tbl t1 - -> Result - One-Time Filter: false -(5 rows) - --- per further discussion of bug #17781 -explain (costs off) -select ss1.x -from (select f1/2 as x from int4_tbl i4 left join a on a.id = i4.f1) ss1 - right join int8_tbl i8 on true -where current_user is not null; -- this is to add a Result node - QUERY PLAN ------------------------------------------------ - Result - One-Time Filter: (CURRENT_USER IS NOT NULL) - -> Nested Loop Left Join - -> Seq Scan on int8_tbl i8 - -> Materialize - -> Seq Scan on int4_tbl i4 -(6 rows) - --- and further discussion of bug #17781 -explain (costs off) -select * -from int8_tbl t1 - left join (int8_tbl t2 left join onek t3 on t2.q1 > t3.unique1) - on t1.q2 = t2.q2 - left join onek t4 - on t2.q2 < t3.unique2; - QUERY PLAN -------------------------------------------------- - Nested Loop Left Join - Join Filter: (t2.q2 < t3.unique2) - -> Nested Loop Left Join - Join Filter: (t2.q1 > t3.unique1) - -> Hash Left Join - Hash Cond: (t1.q2 = t2.q2) - -> Seq Scan on int8_tbl t1 - -> Hash - -> Seq Scan on int8_tbl t2 - -> Materialize - -> Seq Scan on onek t3 - -> Materialize - -> Seq Scan on onek t4 -(13 rows) - --- More tests of correct placement of pseudoconstant quals --- simple constant-false condition -explain (costs off) -select * from int8_tbl t1 left join - (int8_tbl t2 inner join int8_tbl t3 on false - left join int8_tbl t4 on t2.q2 = t4.q2) -on t1.q1 = t2.q1; - QUERY PLAN --------------------------------------- - Hash Left Join - Hash Cond: (t1.q1 = q1) - -> Seq Scan on int8_tbl t1 - -> Hash - -> Result - One-Time Filter: false -(6 rows) - --- deduce constant-false from an EquivalenceClass -explain (costs off) -select * from int8_tbl t1 left join - (int8_tbl t2 inner join int8_tbl t3 on (t2.q1-t3.q2) = 0 and (t2.q1-t3.q2) = 1 - left join int8_tbl t4 on t2.q2 = t4.q2) -on t1.q1 = t2.q1; - QUERY PLAN --------------------------------------- - Hash Left Join - Hash Cond: (t1.q1 = q1) - -> Seq Scan on int8_tbl t1 - -> Hash - -> Result - One-Time Filter: false -(6 rows) - --- pseudoconstant based on an outer-level Param -explain (costs off) -select exists( - select * from int8_tbl t1 left join - (int8_tbl t2 inner join int8_tbl t3 on x0.f1 = 1 - left join int8_tbl t4 on t2.q2 = t4.q2) - on t1.q1 = t2.q1 -) from int4_tbl x0; - QUERY PLAN ---------------------------------------------------------------------- - Seq Scan on int4_tbl x0 - SubPlan 1 - -> Nested Loop Left Join - Join Filter: (t2.q2 = t4.q2) - -> Nested Loop Left Join - Join Filter: (t1.q1 = t2.q1) - -> Seq Scan on int8_tbl t1 - -> Materialize - -> Result - One-Time Filter: (x0.f1 = 1) - -> Nested Loop - -> Seq Scan on int8_tbl t2 - -> Materialize - -> Seq Scan on int8_tbl t3 - -> Materialize - -> Seq Scan on int8_tbl t4 -(16 rows) - --- check that join removal works for a left join when joining a subquery --- that is guaranteed to be unique by its GROUP BY clause -explain (costs off) -select d.* from d left join (select * from b group by b.id, b.c_id) s - on d.a = s.id and d.b = s.c_id; - QUERY PLAN ---------------- - Seq Scan on d -(1 row) - --- similarly, but keying off a DISTINCT clause -explain (costs off) -select d.* from d left join (select distinct * from b) s - on d.a = s.id and d.b = s.c_id; - QUERY PLAN ---------------- - Seq Scan on d -(1 row) - --- join removal is not possible when the GROUP BY contains a column that is --- not in the join condition. (Note: as of 9.6, we notice that b.id is a --- primary key and so drop b.c_id from the GROUP BY of the resulting plan; --- but this happens too late for join removal in the outer plan level.) -explain (costs off) -select d.* from d left join (select * from b group by b.id, b.c_id) s - on d.a = s.id; - QUERY PLAN ------------------------------------------- - Merge Right Join - Merge Cond: (b.id = d.a) - -> Group - Group Key: b.id - -> Index Scan using b_pkey on b - -> Sort - Sort Key: d.a - -> Seq Scan on d -(8 rows) - --- similarly, but keying off a DISTINCT clause -explain (costs off) -select d.* from d left join (select distinct * from b) s - on d.a = s.id; - QUERY PLAN --------------------------------------- - Merge Right Join - Merge Cond: (b.id = d.a) - -> Unique - -> Sort - Sort Key: b.id, b.c_id - -> Seq Scan on b - -> Sort - Sort Key: d.a - -> Seq Scan on d -(9 rows) - --- join removal is not possible here -explain (costs off) -select 1 from a t1 - left join (a t2 left join a t3 on t2.id = 1) on t2.id = 1; - QUERY PLAN --------------------------------------------------------- - Nested Loop Left Join - -> Seq Scan on a t1 - -> Materialize - -> Nested Loop Left Join - Join Filter: (t2.id = 1) - -> Index Only Scan using a_pkey on a t2 - Index Cond: (id = 1) - -> Seq Scan on a t3 -(8 rows) - --- check join removal works when uniqueness of the join condition is enforced --- by a UNION -explain (costs off) -select d.* from d left join (select id from a union select id from b) s - on d.a = s.id; - QUERY PLAN ---------------- - Seq Scan on d -(1 row) - --- check join removal with a cross-type comparison operator -explain (costs off) -select i8.* from int8_tbl i8 left join (select f1 from int4_tbl group by f1) i4 - on i8.q1 = i4.f1; - QUERY PLAN -------------------------- - Seq Scan on int8_tbl i8 -(1 row) - --- check join removal with lateral references -explain (costs off) -select 1 from (select a.id FROM a left join b on a.b_id = b.id) q, - lateral generate_series(1, q.id) gs(i) where q.id = gs.i; - QUERY PLAN -------------------------------------------- - Nested Loop - -> Seq Scan on a - -> Function Scan on generate_series gs - Filter: (a.id = i) -(4 rows) - --- check join removal within RHS of an outer join -explain (costs off) -select c.id, ss.a from c - left join (select d.a from onerow, d left join b on d.a = b.id) ss - on c.id = ss.a; - QUERY PLAN --------------------------------- - Hash Right Join - Hash Cond: (d.a = c.id) - -> Nested Loop - -> Seq Scan on onerow - -> Seq Scan on d - -> Hash - -> Seq Scan on c -(7 rows) - -CREATE TEMP TABLE parted_b (id int PRIMARY KEY) partition by range(id); -CREATE TEMP TABLE parted_b1 partition of parted_b for values from (0) to (10); --- test join removals on a partitioned table -explain (costs off) -select a.* from a left join parted_b pb on a.b_id = pb.id; - QUERY PLAN ---------------- - Seq Scan on a -(1 row) - -rollback; -create temp table parent (k int primary key, pd int); -create temp table child (k int unique, cd int); -insert into parent values (1, 10), (2, 20), (3, 30); -insert into child values (1, 100), (4, 400); --- this case is optimizable -select p.* from parent p left join child c on (p.k = c.k); - k | pd ----+---- - 1 | 10 - 2 | 20 - 3 | 30 -(3 rows) - -explain (costs off) - select p.* from parent p left join child c on (p.k = c.k); - QUERY PLAN ----------------------- - Seq Scan on parent p -(1 row) - --- this case is not -select p.*, linked from parent p - left join (select c.*, true as linked from child c) as ss - on (p.k = ss.k); - k | pd | linked ----+----+-------- - 1 | 10 | t - 2 | 20 | - 3 | 30 | -(3 rows) - -explain (costs off) - select p.*, linked from parent p - left join (select c.*, true as linked from child c) as ss - on (p.k = ss.k); - QUERY PLAN ---------------------------------- - Hash Left Join - Hash Cond: (p.k = c.k) - -> Seq Scan on parent p - -> Hash - -> Seq Scan on child c -(5 rows) - --- check for a 9.0rc1 bug: join removal breaks pseudoconstant qual handling -select p.* from - parent p left join child c on (p.k = c.k) - where p.k = 1 and p.k = 2; - k | pd ----+---- -(0 rows) - -explain (costs off) -select p.* from - parent p left join child c on (p.k = c.k) - where p.k = 1 and p.k = 2; - QUERY PLAN --------------------------- - Result - One-Time Filter: false -(2 rows) - -select p.* from - (parent p left join child c on (p.k = c.k)) join parent x on p.k = x.k - where p.k = 1 and p.k = 2; - k | pd ----+---- -(0 rows) - -explain (costs off) -select p.* from - (parent p left join child c on (p.k = c.k)) join parent x on p.k = x.k - where p.k = 1 and p.k = 2; - QUERY PLAN --------------------------- - Result - One-Time Filter: false -(2 rows) - --- bug 5255: this is not optimizable by join removal -begin; -CREATE TEMP TABLE a (id int PRIMARY KEY); -CREATE TEMP TABLE b (id int PRIMARY KEY, a_id int); -INSERT INTO a VALUES (0), (1); -INSERT INTO b VALUES (0, 0), (1, NULL); -SELECT * FROM b LEFT JOIN a ON (b.a_id = a.id) WHERE (a.id IS NULL OR a.id > 0); - id | a_id | id -----+------+---- - 1 | | -(1 row) - -SELECT b.* FROM b LEFT JOIN a ON (b.a_id = a.id) WHERE (a.id IS NULL OR a.id > 0); - id | a_id -----+------ - 1 | -(1 row) - -rollback; --- another join removal bug: this is not optimizable, either -begin; -create temp table innertab (id int8 primary key, dat1 int8); -insert into innertab values(123, 42); -SELECT * FROM - (SELECT 1 AS x) ss1 - LEFT JOIN - (SELECT q1, q2, COALESCE(dat1, q1) AS y - FROM int8_tbl LEFT JOIN innertab ON q2 = id) ss2 - ON true; - x | q1 | q2 | y ----+------------------+-------------------+------------------ - 1 | 123 | 456 | 123 - 1 | 123 | 4567890123456789 | 123 - 1 | 4567890123456789 | 123 | 42 - 1 | 4567890123456789 | 4567890123456789 | 4567890123456789 - 1 | 4567890123456789 | -4567890123456789 | 4567890123456789 -(5 rows) - --- join removal bug #17769: can't remove if there's a pushed-down reference -EXPLAIN (COSTS OFF) -SELECT q2 FROM - (SELECT * - FROM int8_tbl LEFT JOIN innertab ON q2 = id) ss - WHERE COALESCE(dat1, 0) = q1; - QUERY PLAN ----------------------------------------------------------------- - Nested Loop Left Join - Filter: (COALESCE(innertab.dat1, '0'::bigint) = int8_tbl.q1) - -> Seq Scan on int8_tbl - -> Index Scan using innertab_pkey on innertab - Index Cond: (id = int8_tbl.q2) -(5 rows) - --- join removal bug #17773: otherwise-removable PHV appears in a qual condition -EXPLAIN (VERBOSE, COSTS OFF) -SELECT q2 FROM - (SELECT q2, 'constant'::text AS x - FROM int8_tbl LEFT JOIN innertab ON q2 = id) ss - RIGHT JOIN int4_tbl ON NULL - WHERE x >= x; - QUERY PLAN ------------------------------------------------------- - Nested Loop Left Join - Output: q2 - Join Filter: NULL::boolean - Filter: (('constant'::text) >= ('constant'::text)) - -> Seq Scan on public.int4_tbl - Output: int4_tbl.f1 - -> Result - Output: q2, 'constant'::text - One-Time Filter: false -(9 rows) - --- join removal bug #17786: check that OR conditions are cleaned up -EXPLAIN (COSTS OFF) -SELECT f1, x -FROM int4_tbl - JOIN ((SELECT 42 AS x FROM int8_tbl LEFT JOIN innertab ON q1 = id) AS ss1 - RIGHT JOIN tenk1 ON NULL) - ON tenk1.unique1 = ss1.x OR tenk1.unique2 = ss1.x; - QUERY PLAN --------------------------------------------------------------------------- - Nested Loop - -> Seq Scan on int4_tbl - -> Materialize - -> Nested Loop Left Join - Join Filter: NULL::boolean - Filter: ((tenk1.unique1 = (42)) OR (tenk1.unique2 = (42))) - -> Seq Scan on tenk1 - -> Result - One-Time Filter: false -(9 rows) - -rollback; --- another join removal bug: we must clean up correctly when removing a PHV -begin; -create temp table uniquetbl (f1 text unique); -explain (costs off) -select t1.* from - uniquetbl as t1 - left join (select *, '***'::text as d1 from uniquetbl) t2 - on t1.f1 = t2.f1 - left join uniquetbl t3 - on t2.d1 = t3.f1; - QUERY PLAN --------------------------- - Seq Scan on uniquetbl t1 -(1 row) - -explain (costs off) -select t0.* -from - text_tbl t0 - left join - (select case t1.ten when 0 then 'doh!'::text else null::text end as case1, - t1.stringu2 - from tenk1 t1 - join int4_tbl i4 ON i4.f1 = t1.unique2 - left join uniquetbl u1 ON u1.f1 = t1.string4) ss - on t0.f1 = ss.case1 -where ss.stringu2 !~* ss.case1; - QUERY PLAN --------------------------------------------------------------------------------------------- - Nested Loop - Join Filter: (t0.f1 = CASE t1.ten WHEN 0 THEN 'doh!'::text ELSE NULL::text END) - -> Nested Loop - -> Seq Scan on int4_tbl i4 - -> Index Scan using tenk1_unique2 on tenk1 t1 - Index Cond: (unique2 = i4.f1) - Filter: (stringu2 !~* CASE ten WHEN 0 THEN 'doh!'::text ELSE NULL::text END) - -> Materialize - -> Seq Scan on text_tbl t0 -(9 rows) - -select t0.* -from - text_tbl t0 - left join - (select case t1.ten when 0 then 'doh!'::text else null::text end as case1, - t1.stringu2 - from tenk1 t1 - join int4_tbl i4 ON i4.f1 = t1.unique2 - left join uniquetbl u1 ON u1.f1 = t1.string4) ss - on t0.f1 = ss.case1 -where ss.stringu2 !~* ss.case1; - f1 ------- - doh! -(1 row) - -rollback; --- another join removal bug: we must clean up EquivalenceClasses too -begin; -create temp table t (a int unique); -insert into t values (1); -explain (costs off) -select 1 -from t t1 - left join (select 2 as c - from t t2 left join t t3 on t2.a = t3.a) s - on true -where t1.a = s.c; - QUERY PLAN ------------------------------- - Nested Loop Left Join - Filter: (t1.a = (2)) - -> Seq Scan on t t1 - -> Materialize - -> Seq Scan on t t2 -(5 rows) - -select 1 -from t t1 - left join (select 2 as c - from t t2 left join t t3 on t2.a = t3.a) s - on true -where t1.a = s.c; - ?column? ----------- -(0 rows) - -rollback; --- test cases where we can remove a join, but not a PHV computed at it -begin; -create temp table t (a int unique, b int); -insert into t values (1,1), (2,2); -explain (costs off) -select 1 -from t t1 - left join (select t2.a, 1 as c - from t t2 left join t t3 on t2.a = t3.a) s - on true - left join t t4 on true -where s.a < s.c; - QUERY PLAN -------------------------------------- - Nested Loop Left Join - -> Nested Loop - -> Seq Scan on t t1 - -> Materialize - -> Seq Scan on t t2 - Filter: (a < 1) - -> Materialize - -> Seq Scan on t t4 -(8 rows) - -explain (costs off) -select t1.a, s.* -from t t1 - left join lateral (select t2.a, coalesce(t1.a, 1) as c - from t t2 left join t t3 on t2.a = t3.a) s - on true - left join t t4 on true -where s.a < s.c; - QUERY PLAN ------------------------------------------------ - Nested Loop Left Join - -> Nested Loop - -> Seq Scan on t t1 - -> Seq Scan on t t2 - Filter: (a < COALESCE(t1.a, 1)) - -> Materialize - -> Seq Scan on t t4 -(7 rows) - -select t1.a, s.* -from t t1 - left join lateral (select t2.a, coalesce(t1.a, 1) as c - from t t2 left join t t3 on t2.a = t3.a) s - on true - left join t t4 on true -where s.a < s.c; - a | a | c ----+---+--- - 2 | 1 | 2 - 2 | 1 | 2 -(2 rows) - -rollback; --- test case to expose miscomputation of required relid set for a PHV -explain (verbose, costs off) -select i8.*, ss.v, t.unique2 - from int8_tbl i8 - left join int4_tbl i4 on i4.f1 = 1 - left join lateral (select i4.f1 + 1 as v) as ss on true - left join tenk1 t on t.unique2 = ss.v -where q2 = 456; - QUERY PLAN -------------------------------------------------------------- - Nested Loop Left Join - Output: i8.q1, i8.q2, ((i4.f1 + 1)), t.unique2 - -> Nested Loop Left Join - Output: i8.q1, i8.q2, (i4.f1 + 1) - -> Seq Scan on public.int8_tbl i8 - Output: i8.q1, i8.q2 - Filter: (i8.q2 = 456) - -> Seq Scan on public.int4_tbl i4 - Output: i4.f1 - Filter: (i4.f1 = 1) - -> Index Only Scan using tenk1_unique2 on public.tenk1 t - Output: t.unique2 - Index Cond: (t.unique2 = ((i4.f1 + 1))) -(13 rows) - -select i8.*, ss.v, t.unique2 - from int8_tbl i8 - left join int4_tbl i4 on i4.f1 = 1 - left join lateral (select i4.f1 + 1 as v) as ss on true - left join tenk1 t on t.unique2 = ss.v -where q2 = 456; - q1 | q2 | v | unique2 ------+-----+---+--------- - 123 | 456 | | -(1 row) - --- and check a related issue where we miscompute required relids for --- a PHV that's been translated to a child rel -create temp table parttbl (a integer primary key) partition by range (a); -create temp table parttbl1 partition of parttbl for values from (1) to (100); -insert into parttbl values (11), (12); -explain (costs off) -select * from - (select *, 12 as phv from parttbl) as ss - right join int4_tbl on true -where ss.a = ss.phv and f1 = 0; - QUERY PLAN ------------------------------------- - Nested Loop - -> Seq Scan on int4_tbl - Filter: (f1 = 0) - -> Seq Scan on parttbl1 parttbl - Filter: (a = 12) -(5 rows) - -select * from - (select *, 12 as phv from parttbl) as ss - right join int4_tbl on true -where ss.a = ss.phv and f1 = 0; - a | phv | f1 -----+-----+---- - 12 | 12 | 0 -(1 row) - --- bug #8444: we've historically allowed duplicate aliases within aliased JOINs -select * from - int8_tbl x join (int4_tbl x cross join int4_tbl y) j on q1 = f1; -- error -ERROR: column reference "f1" is ambiguous -LINE 2: ..._tbl x join (int4_tbl x cross join int4_tbl y) j on q1 = f1; - ^ -select * from - int8_tbl x join (int4_tbl x cross join int4_tbl y) j on q1 = y.f1; -- error -ERROR: invalid reference to FROM-clause entry for table "y" -LINE 2: ...bl x join (int4_tbl x cross join int4_tbl y) j on q1 = y.f1; - ^ -DETAIL: There is an entry for table "y", but it cannot be referenced from this part of the query. -select * from - int8_tbl x join (int4_tbl x cross join int4_tbl y(ff)) j on q1 = f1; -- ok - q1 | q2 | f1 | ff -----+----+----+---- -(0 rows) - --- --- Test hints given on incorrect column references are useful --- -select t1.uunique1 from - tenk1 t1 join tenk2 t2 on t1.two = t2.two; -- error, prefer "t1" suggestion -ERROR: column t1.uunique1 does not exist -LINE 1: select t1.uunique1 from - ^ -HINT: Perhaps you meant to reference the column "t1.unique1". -select t2.uunique1 from - tenk1 t1 join tenk2 t2 on t1.two = t2.two; -- error, prefer "t2" suggestion -ERROR: column t2.uunique1 does not exist -LINE 1: select t2.uunique1 from - ^ -HINT: Perhaps you meant to reference the column "t2.unique1". -select uunique1 from - tenk1 t1 join tenk2 t2 on t1.two = t2.two; -- error, suggest both at once -ERROR: column "uunique1" does not exist -LINE 1: select uunique1 from - ^ -HINT: Perhaps you meant to reference the column "t1.unique1" or the column "t2.unique1". -select ctid from - tenk1 t1 join tenk2 t2 on t1.two = t2.two; -- error, need qualification -ERROR: column "ctid" does not exist -LINE 1: select ctid from - ^ -DETAIL: There are columns named "ctid", but they are in tables that cannot be referenced from this part of the query. -HINT: Try using a table-qualified name. --- --- Take care to reference the correct RTE --- -select atts.relid::regclass, s.* from pg_stats s join - pg_attribute a on s.attname = a.attname and s.tablename = - a.attrelid::regclass::text join (select unnest(indkey) attnum, - indexrelid from pg_index i) atts on atts.attnum = a.attnum where - schemaname != 'pg_catalog'; -ERROR: column atts.relid does not exist -LINE 1: select atts.relid::regclass, s.* from pg_stats s join - ^ --- Test bug in rangetable flattening -explain (verbose, costs off) -select 1 from - (select * from int8_tbl where q1 <> (select 42) offset 0) ss -where false; - QUERY PLAN --------------------------- - Result - Output: 1 - One-Time Filter: false -(3 rows) - --- --- Test LATERAL --- -select unique2, x.* -from tenk1 a, lateral (select * from int4_tbl b where f1 = a.unique1) x; - unique2 | f1 ----------+---- - 9998 | 0 -(1 row) - -explain (costs off) - select unique2, x.* - from tenk1 a, lateral (select * from int4_tbl b where f1 = a.unique1) x; - QUERY PLAN -------------------------------------------------- - Nested Loop - -> Seq Scan on int4_tbl b - -> Index Scan using tenk1_unique1 on tenk1 a - Index Cond: (unique1 = b.f1) -(4 rows) - -select unique2, x.* -from int4_tbl x, lateral (select unique2 from tenk1 where f1 = unique1) ss; - unique2 | f1 ----------+---- - 9998 | 0 -(1 row) - -explain (costs off) - select unique2, x.* - from int4_tbl x, lateral (select unique2 from tenk1 where f1 = unique1) ss; - QUERY PLAN ------------------------------------------------ - Nested Loop - -> Seq Scan on int4_tbl x - -> Index Scan using tenk1_unique1 on tenk1 - Index Cond: (unique1 = x.f1) -(4 rows) - -explain (costs off) - select unique2, x.* - from int4_tbl x cross join lateral (select unique2 from tenk1 where f1 = unique1) ss; - QUERY PLAN ------------------------------------------------ - Nested Loop - -> Seq Scan on int4_tbl x - -> Index Scan using tenk1_unique1 on tenk1 - Index Cond: (unique1 = x.f1) -(4 rows) - -select unique2, x.* -from int4_tbl x left join lateral (select unique1, unique2 from tenk1 where f1 = unique1) ss on true; - unique2 | f1 ----------+------------- - 9998 | 0 - | 123456 - | -123456 - | 2147483647 - | -2147483647 -(5 rows) - -explain (costs off) - select unique2, x.* - from int4_tbl x left join lateral (select unique1, unique2 from tenk1 where f1 = unique1) ss on true; - QUERY PLAN ------------------------------------------------ - Nested Loop Left Join - -> Seq Scan on int4_tbl x - -> Index Scan using tenk1_unique1 on tenk1 - Index Cond: (unique1 = x.f1) -(4 rows) - --- check scoping of lateral versus parent references --- the first of these should return int8_tbl.q2, the second int8_tbl.q1 -select *, (select r from (select q1 as q2) x, (select q2 as r) y) from int8_tbl; - q1 | q2 | r -------------------+-------------------+------------------- - 123 | 456 | 456 - 123 | 4567890123456789 | 4567890123456789 - 4567890123456789 | 123 | 123 - 4567890123456789 | 4567890123456789 | 4567890123456789 - 4567890123456789 | -4567890123456789 | -4567890123456789 -(5 rows) - -select *, (select r from (select q1 as q2) x, lateral (select q2 as r) y) from int8_tbl; - q1 | q2 | r -------------------+-------------------+------------------ - 123 | 456 | 123 - 123 | 4567890123456789 | 123 - 4567890123456789 | 123 | 4567890123456789 - 4567890123456789 | 4567890123456789 | 4567890123456789 - 4567890123456789 | -4567890123456789 | 4567890123456789 -(5 rows) - --- lateral with function in FROM -select count(*) from tenk1 a, lateral generate_series(1,two) g; - count -------- - 5000 -(1 row) - -explain (costs off) - select count(*) from tenk1 a, lateral generate_series(1,two) g; - QUERY PLAN ------------------------------------------------------- - Aggregate - -> Nested Loop - -> Seq Scan on tenk1 a - -> Memoize - Cache Key: a.two - Cache Mode: binary - -> Function Scan on generate_series g -(7 rows) - -explain (costs off) - select count(*) from tenk1 a cross join lateral generate_series(1,two) g; - QUERY PLAN ------------------------------------------------------- - Aggregate - -> Nested Loop - -> Seq Scan on tenk1 a - -> Memoize - Cache Key: a.two - Cache Mode: binary - -> Function Scan on generate_series g -(7 rows) - --- don't need the explicit LATERAL keyword for functions -explain (costs off) - select count(*) from tenk1 a, generate_series(1,two) g; - QUERY PLAN ------------------------------------------------------- - Aggregate - -> Nested Loop - -> Seq Scan on tenk1 a - -> Memoize - Cache Key: a.two - Cache Mode: binary - -> Function Scan on generate_series g -(7 rows) - --- lateral with UNION ALL subselect -explain (costs off) - select * from generate_series(100,200) g, - lateral (select * from int8_tbl a where g = q1 union all - select * from int8_tbl b where g = q2) ss; - QUERY PLAN ------------------------------------------- - Nested Loop - -> Function Scan on generate_series g - -> Append - -> Seq Scan on int8_tbl a - Filter: (g.g = q1) - -> Seq Scan on int8_tbl b - Filter: (g.g = q2) -(7 rows) - -select * from generate_series(100,200) g, - lateral (select * from int8_tbl a where g = q1 union all - select * from int8_tbl b where g = q2) ss; - g | q1 | q2 ------+------------------+------------------ - 123 | 123 | 456 - 123 | 123 | 4567890123456789 - 123 | 4567890123456789 | 123 -(3 rows) - --- lateral with VALUES -explain (costs off) - select count(*) from tenk1 a, - tenk1 b join lateral (values(a.unique1)) ss(x) on b.unique2 = ss.x; - QUERY PLAN ------------------------------------------------------------- - Aggregate - -> Merge Join - Merge Cond: (a.unique1 = b.unique2) - -> Index Only Scan using tenk1_unique1 on tenk1 a - -> Index Only Scan using tenk1_unique2 on tenk1 b -(5 rows) - -select count(*) from tenk1 a, - tenk1 b join lateral (values(a.unique1)) ss(x) on b.unique2 = ss.x; - count -------- - 10000 -(1 row) - --- lateral with VALUES, no flattening possible -explain (costs off) - select count(*) from tenk1 a, - tenk1 b join lateral (values(a.unique1),(-1)) ss(x) on b.unique2 = ss.x; - QUERY PLAN ------------------------------------------------------------------- - Aggregate - -> Nested Loop - -> Nested Loop - -> Index Only Scan using tenk1_unique1 on tenk1 a - -> Values Scan on "*VALUES*" - -> Memoize - Cache Key: "*VALUES*".column1 - Cache Mode: logical - -> Index Only Scan using tenk1_unique2 on tenk1 b - Index Cond: (unique2 = "*VALUES*".column1) -(10 rows) - -select count(*) from tenk1 a, - tenk1 b join lateral (values(a.unique1),(-1)) ss(x) on b.unique2 = ss.x; - count -------- - 10000 -(1 row) - --- lateral injecting a strange outer join condition -explain (costs off) - select * from int8_tbl a, - int8_tbl x left join lateral (select a.q1 from int4_tbl y) ss(z) - on x.q2 = ss.z - order by a.q1, a.q2, x.q1, x.q2, ss.z; - QUERY PLAN ------------------------------------------------- - Sort - Sort Key: a.q1, a.q2, x.q1, x.q2, (a.q1) - -> Nested Loop - -> Seq Scan on int8_tbl a - -> Hash Left Join - Hash Cond: (x.q2 = (a.q1)) - -> Seq Scan on int8_tbl x - -> Hash - -> Seq Scan on int4_tbl y -(9 rows) - -select * from int8_tbl a, - int8_tbl x left join lateral (select a.q1 from int4_tbl y) ss(z) - on x.q2 = ss.z - order by a.q1, a.q2, x.q1, x.q2, ss.z; - q1 | q2 | q1 | q2 | z -------------------+-------------------+------------------+-------------------+------------------ - 123 | 456 | 123 | 456 | - 123 | 456 | 123 | 4567890123456789 | - 123 | 456 | 4567890123456789 | -4567890123456789 | - 123 | 456 | 4567890123456789 | 123 | 123 - 123 | 456 | 4567890123456789 | 123 | 123 - 123 | 456 | 4567890123456789 | 123 | 123 - 123 | 456 | 4567890123456789 | 123 | 123 - 123 | 456 | 4567890123456789 | 123 | 123 - 123 | 456 | 4567890123456789 | 4567890123456789 | - 123 | 4567890123456789 | 123 | 456 | - 123 | 4567890123456789 | 123 | 4567890123456789 | - 123 | 4567890123456789 | 4567890123456789 | -4567890123456789 | - 123 | 4567890123456789 | 4567890123456789 | 123 | 123 - 123 | 4567890123456789 | 4567890123456789 | 123 | 123 - 123 | 4567890123456789 | 4567890123456789 | 123 | 123 - 123 | 4567890123456789 | 4567890123456789 | 123 | 123 - 123 | 4567890123456789 | 4567890123456789 | 123 | 123 - 123 | 4567890123456789 | 4567890123456789 | 4567890123456789 | - 4567890123456789 | -4567890123456789 | 123 | 456 | - 4567890123456789 | -4567890123456789 | 123 | 4567890123456789 | 4567890123456789 - 4567890123456789 | -4567890123456789 | 123 | 4567890123456789 | 4567890123456789 - 4567890123456789 | -4567890123456789 | 123 | 4567890123456789 | 4567890123456789 - 4567890123456789 | -4567890123456789 | 123 | 4567890123456789 | 4567890123456789 - 4567890123456789 | -4567890123456789 | 123 | 4567890123456789 | 4567890123456789 - 4567890123456789 | -4567890123456789 | 4567890123456789 | -4567890123456789 | - 4567890123456789 | -4567890123456789 | 4567890123456789 | 123 | - 4567890123456789 | -4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 - 4567890123456789 | -4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 - 4567890123456789 | -4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 - 4567890123456789 | -4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 - 4567890123456789 | -4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 - 4567890123456789 | 123 | 123 | 456 | - 4567890123456789 | 123 | 123 | 4567890123456789 | 4567890123456789 - 4567890123456789 | 123 | 123 | 4567890123456789 | 4567890123456789 - 4567890123456789 | 123 | 123 | 4567890123456789 | 4567890123456789 - 4567890123456789 | 123 | 123 | 4567890123456789 | 4567890123456789 - 4567890123456789 | 123 | 123 | 4567890123456789 | 4567890123456789 - 4567890123456789 | 123 | 4567890123456789 | -4567890123456789 | - 4567890123456789 | 123 | 4567890123456789 | 123 | - 4567890123456789 | 123 | 4567890123456789 | 4567890123456789 | 4567890123456789 - 4567890123456789 | 123 | 4567890123456789 | 4567890123456789 | 4567890123456789 - 4567890123456789 | 123 | 4567890123456789 | 4567890123456789 | 4567890123456789 - 4567890123456789 | 123 | 4567890123456789 | 4567890123456789 | 4567890123456789 - 4567890123456789 | 123 | 4567890123456789 | 4567890123456789 | 4567890123456789 - 4567890123456789 | 4567890123456789 | 123 | 456 | - 4567890123456789 | 4567890123456789 | 123 | 4567890123456789 | 4567890123456789 - 4567890123456789 | 4567890123456789 | 123 | 4567890123456789 | 4567890123456789 - 4567890123456789 | 4567890123456789 | 123 | 4567890123456789 | 4567890123456789 - 4567890123456789 | 4567890123456789 | 123 | 4567890123456789 | 4567890123456789 - 4567890123456789 | 4567890123456789 | 123 | 4567890123456789 | 4567890123456789 - 4567890123456789 | 4567890123456789 | 4567890123456789 | -4567890123456789 | - 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 | - 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 - 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 - 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 - 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 - 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 -(57 rows) - --- lateral reference to a join alias variable -select * from (select f1/2 as x from int4_tbl) ss1 join int4_tbl i4 on x = f1, - lateral (select x) ss2(y); - x | f1 | y ----+----+--- - 0 | 0 | 0 -(1 row) - -select * from (select f1 as x from int4_tbl) ss1 join int4_tbl i4 on x = f1, - lateral (values(x)) ss2(y); - x | f1 | y --------------+-------------+------------- - 0 | 0 | 0 - 123456 | 123456 | 123456 - -123456 | -123456 | -123456 - 2147483647 | 2147483647 | 2147483647 - -2147483647 | -2147483647 | -2147483647 -(5 rows) - -select * from ((select f1/2 as x from int4_tbl) ss1 join int4_tbl i4 on x = f1) j, - lateral (select x) ss2(y); - x | f1 | y ----+----+--- - 0 | 0 | 0 -(1 row) - --- lateral references requiring pullup -select * from (values(1)) x(lb), - lateral generate_series(lb,4) x4; - lb | x4 -----+---- - 1 | 1 - 1 | 2 - 1 | 3 - 1 | 4 -(4 rows) - -select * from (select f1/1000000000 from int4_tbl) x(lb), - lateral generate_series(lb,4) x4; - lb | x4 -----+---- - 0 | 0 - 0 | 1 - 0 | 2 - 0 | 3 - 0 | 4 - 0 | 0 - 0 | 1 - 0 | 2 - 0 | 3 - 0 | 4 - 0 | 0 - 0 | 1 - 0 | 2 - 0 | 3 - 0 | 4 - 2 | 2 - 2 | 3 - 2 | 4 - -2 | -2 - -2 | -1 - -2 | 0 - -2 | 1 - -2 | 2 - -2 | 3 - -2 | 4 -(25 rows) - -select * from (values(1)) x(lb), - lateral (values(lb)) y(lbcopy); - lb | lbcopy -----+-------- - 1 | 1 -(1 row) - -select * from (values(1)) x(lb), - lateral (select lb from int4_tbl) y(lbcopy); - lb | lbcopy -----+-------- - 1 | 1 - 1 | 1 - 1 | 1 - 1 | 1 - 1 | 1 -(5 rows) - -select * from - int8_tbl x left join (select q1,coalesce(q2,0) q2 from int8_tbl) y on x.q2 = y.q1, - lateral (values(x.q1,y.q1,y.q2)) v(xq1,yq1,yq2); - q1 | q2 | q1 | q2 | xq1 | yq1 | yq2 -------------------+-------------------+------------------+-------------------+------------------+------------------+------------------- - 123 | 456 | | | 123 | | - 123 | 4567890123456789 | 4567890123456789 | -4567890123456789 | 123 | 4567890123456789 | -4567890123456789 - 123 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 | 4567890123456789 | 4567890123456789 - 123 | 4567890123456789 | 4567890123456789 | 123 | 123 | 4567890123456789 | 123 - 4567890123456789 | 123 | 123 | 4567890123456789 | 4567890123456789 | 123 | 4567890123456789 - 4567890123456789 | 123 | 123 | 456 | 4567890123456789 | 123 | 456 - 4567890123456789 | 4567890123456789 | 4567890123456789 | -4567890123456789 | 4567890123456789 | 4567890123456789 | -4567890123456789 - 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 - 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 | 4567890123456789 | 4567890123456789 | 123 - 4567890123456789 | -4567890123456789 | | | 4567890123456789 | | -(10 rows) - -select * from - int8_tbl x left join (select q1,coalesce(q2,0) q2 from int8_tbl) y on x.q2 = y.q1, - lateral (select x.q1,y.q1,y.q2) v(xq1,yq1,yq2); - q1 | q2 | q1 | q2 | xq1 | yq1 | yq2 -------------------+-------------------+------------------+-------------------+------------------+------------------+------------------- - 123 | 456 | | | 123 | | - 123 | 4567890123456789 | 4567890123456789 | -4567890123456789 | 123 | 4567890123456789 | -4567890123456789 - 123 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 | 4567890123456789 | 4567890123456789 - 123 | 4567890123456789 | 4567890123456789 | 123 | 123 | 4567890123456789 | 123 - 4567890123456789 | 123 | 123 | 4567890123456789 | 4567890123456789 | 123 | 4567890123456789 - 4567890123456789 | 123 | 123 | 456 | 4567890123456789 | 123 | 456 - 4567890123456789 | 4567890123456789 | 4567890123456789 | -4567890123456789 | 4567890123456789 | 4567890123456789 | -4567890123456789 - 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 - 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 | 4567890123456789 | 4567890123456789 | 123 - 4567890123456789 | -4567890123456789 | | | 4567890123456789 | | -(10 rows) - -select x.* from - int8_tbl x left join (select q1,coalesce(q2,0) q2 from int8_tbl) y on x.q2 = y.q1, - lateral (select x.q1,y.q1,y.q2) v(xq1,yq1,yq2); - q1 | q2 -------------------+------------------- - 123 | 456 - 123 | 4567890123456789 - 123 | 4567890123456789 - 123 | 4567890123456789 - 4567890123456789 | 123 - 4567890123456789 | 123 - 4567890123456789 | 4567890123456789 - 4567890123456789 | 4567890123456789 - 4567890123456789 | 4567890123456789 - 4567890123456789 | -4567890123456789 -(10 rows) - -select v.* from - (int8_tbl x left join (select q1,coalesce(q2,0) q2 from int8_tbl) y on x.q2 = y.q1) - left join int4_tbl z on z.f1 = x.q2, - lateral (select x.q1,y.q1 union all select x.q2,y.q2) v(vx,vy); - vx | vy --------------------+------------------- - 123 | - 456 | - 123 | 4567890123456789 - 4567890123456789 | -4567890123456789 - 123 | 4567890123456789 - 4567890123456789 | 4567890123456789 - 123 | 4567890123456789 - 4567890123456789 | 123 - 4567890123456789 | 123 - 123 | 4567890123456789 - 4567890123456789 | 123 - 123 | 456 - 4567890123456789 | 4567890123456789 - 4567890123456789 | -4567890123456789 - 4567890123456789 | 4567890123456789 - 4567890123456789 | 4567890123456789 - 4567890123456789 | 4567890123456789 - 4567890123456789 | 123 - 4567890123456789 | - -4567890123456789 | -(20 rows) - -select v.* from - (int8_tbl x left join (select q1,(select coalesce(q2,0)) q2 from int8_tbl) y on x.q2 = y.q1) - left join int4_tbl z on z.f1 = x.q2, - lateral (select x.q1,y.q1 union all select x.q2,y.q2) v(vx,vy); - vx | vy --------------------+------------------- - 4567890123456789 | 123 - 123 | 456 - 4567890123456789 | 123 - 123 | 4567890123456789 - 4567890123456789 | 4567890123456789 - 4567890123456789 | 123 - 123 | 4567890123456789 - 4567890123456789 | 123 - 4567890123456789 | 4567890123456789 - 4567890123456789 | 4567890123456789 - 123 | 4567890123456789 - 4567890123456789 | 4567890123456789 - 4567890123456789 | 4567890123456789 - 4567890123456789 | -4567890123456789 - 123 | 4567890123456789 - 4567890123456789 | -4567890123456789 - 123 | - 456 | - 4567890123456789 | - -4567890123456789 | -(20 rows) - -select v.* from - (int8_tbl x left join (select q1,(select coalesce(q2,0)) q2 from int8_tbl) y on x.q2 = y.q1) - left join int4_tbl z on z.f1 = x.q2, - lateral (select x.q1,y.q1 from onerow union all select x.q2,y.q2 from onerow) v(vx,vy); - vx | vy --------------------+------------------- - 4567890123456789 | 123 - 123 | 456 - 4567890123456789 | 123 - 123 | 4567890123456789 - 4567890123456789 | 4567890123456789 - 4567890123456789 | 123 - 123 | 4567890123456789 - 4567890123456789 | 123 - 4567890123456789 | 4567890123456789 - 4567890123456789 | 4567890123456789 - 123 | 4567890123456789 - 4567890123456789 | 4567890123456789 - 4567890123456789 | 4567890123456789 - 4567890123456789 | -4567890123456789 - 123 | 4567890123456789 - 4567890123456789 | -4567890123456789 - 123 | - 456 | - 4567890123456789 | - -4567890123456789 | -(20 rows) - -explain (verbose, costs off) -select * from - int8_tbl a left join - lateral (select *, a.q2 as x from int8_tbl b) ss on a.q2 = ss.q1; - QUERY PLAN ------------------------------------------- - Nested Loop Left Join - Output: a.q1, a.q2, b.q1, b.q2, (a.q2) - -> Seq Scan on public.int8_tbl a - Output: a.q1, a.q2 - -> Seq Scan on public.int8_tbl b - Output: b.q1, b.q2, a.q2 - Filter: (a.q2 = b.q1) -(7 rows) - -select * from - int8_tbl a left join - lateral (select *, a.q2 as x from int8_tbl b) ss on a.q2 = ss.q1; - q1 | q2 | q1 | q2 | x -------------------+-------------------+------------------+-------------------+------------------ - 123 | 456 | | | - 123 | 4567890123456789 | 4567890123456789 | 123 | 4567890123456789 - 123 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 - 123 | 4567890123456789 | 4567890123456789 | -4567890123456789 | 4567890123456789 - 4567890123456789 | 123 | 123 | 456 | 123 - 4567890123456789 | 123 | 123 | 4567890123456789 | 123 - 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 | 4567890123456789 - 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 - 4567890123456789 | 4567890123456789 | 4567890123456789 | -4567890123456789 | 4567890123456789 - 4567890123456789 | -4567890123456789 | | | -(10 rows) - -explain (verbose, costs off) -select * from - int8_tbl a left join - lateral (select *, coalesce(a.q2, 42) as x from int8_tbl b) ss on a.q2 = ss.q1; - QUERY PLAN ------------------------------------------------------------------- - Nested Loop Left Join - Output: a.q1, a.q2, b.q1, b.q2, (COALESCE(a.q2, '42'::bigint)) - -> Seq Scan on public.int8_tbl a - Output: a.q1, a.q2 - -> Seq Scan on public.int8_tbl b - Output: b.q1, b.q2, COALESCE(a.q2, '42'::bigint) - Filter: (a.q2 = b.q1) -(7 rows) - -select * from - int8_tbl a left join - lateral (select *, coalesce(a.q2, 42) as x from int8_tbl b) ss on a.q2 = ss.q1; - q1 | q2 | q1 | q2 | x -------------------+-------------------+------------------+-------------------+------------------ - 123 | 456 | | | - 123 | 4567890123456789 | 4567890123456789 | 123 | 4567890123456789 - 123 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 - 123 | 4567890123456789 | 4567890123456789 | -4567890123456789 | 4567890123456789 - 4567890123456789 | 123 | 123 | 456 | 123 - 4567890123456789 | 123 | 123 | 4567890123456789 | 123 - 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 | 4567890123456789 - 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 - 4567890123456789 | 4567890123456789 | 4567890123456789 | -4567890123456789 | 4567890123456789 - 4567890123456789 | -4567890123456789 | | | -(10 rows) - --- lateral can result in join conditions appearing below their --- real semantic level -explain (verbose, costs off) -select * from int4_tbl i left join - lateral (select * from int2_tbl j where i.f1 = j.f1) k on true; - QUERY PLAN -------------------------------------------- - Hash Left Join - Output: i.f1, j.f1 - Hash Cond: (i.f1 = j.f1) - -> Seq Scan on public.int4_tbl i - Output: i.f1 - -> Hash - Output: j.f1 - -> Seq Scan on public.int2_tbl j - Output: j.f1 -(9 rows) - -select * from int4_tbl i left join - lateral (select * from int2_tbl j where i.f1 = j.f1) k on true; - f1 | f1 --------------+---- - 0 | 0 - 123456 | - -123456 | - 2147483647 | - -2147483647 | -(5 rows) - -explain (verbose, costs off) -select * from int4_tbl i left join - lateral (select coalesce(i) from int2_tbl j where i.f1 = j.f1) k on true; - QUERY PLAN -------------------------------------- - Nested Loop Left Join - Output: i.f1, (COALESCE(i.*)) - -> Seq Scan on public.int4_tbl i - Output: i.f1, i.* - -> Seq Scan on public.int2_tbl j - Output: j.f1, COALESCE(i.*) - Filter: (i.f1 = j.f1) -(7 rows) - -select * from int4_tbl i left join - lateral (select coalesce(i) from int2_tbl j where i.f1 = j.f1) k on true; - f1 | coalesce --------------+---------- - 0 | (0) - 123456 | - -123456 | - 2147483647 | - -2147483647 | -(5 rows) - -explain (verbose, costs off) -select * from int4_tbl a, - lateral ( - select * from int4_tbl b left join int8_tbl c on (b.f1 = q1 and a.f1 = q2) - ) ss; - QUERY PLAN -------------------------------------------------- - Nested Loop - Output: a.f1, b.f1, c.q1, c.q2 - -> Seq Scan on public.int4_tbl a - Output: a.f1 - -> Hash Left Join - Output: b.f1, c.q1, c.q2 - Hash Cond: (b.f1 = c.q1) - -> Seq Scan on public.int4_tbl b - Output: b.f1 - -> Hash - Output: c.q1, c.q2 - -> Seq Scan on public.int8_tbl c - Output: c.q1, c.q2 - Filter: (a.f1 = c.q2) -(14 rows) - -select * from int4_tbl a, - lateral ( - select * from int4_tbl b left join int8_tbl c on (b.f1 = q1 and a.f1 = q2) - ) ss; - f1 | f1 | q1 | q2 --------------+-------------+----+---- - 0 | 0 | | - 0 | 123456 | | - 0 | -123456 | | - 0 | 2147483647 | | - 0 | -2147483647 | | - 123456 | 0 | | - 123456 | 123456 | | - 123456 | -123456 | | - 123456 | 2147483647 | | - 123456 | -2147483647 | | - -123456 | 0 | | - -123456 | 123456 | | - -123456 | -123456 | | - -123456 | 2147483647 | | - -123456 | -2147483647 | | - 2147483647 | 0 | | - 2147483647 | 123456 | | - 2147483647 | -123456 | | - 2147483647 | 2147483647 | | - 2147483647 | -2147483647 | | - -2147483647 | 0 | | - -2147483647 | 123456 | | - -2147483647 | -123456 | | - -2147483647 | 2147483647 | | - -2147483647 | -2147483647 | | -(25 rows) - --- lateral reference in a PlaceHolderVar evaluated at join level -explain (verbose, costs off) -select * from - int8_tbl a left join lateral - (select b.q1 as bq1, c.q1 as cq1, least(a.q1,b.q1,c.q1) from - int8_tbl b cross join int8_tbl c) ss - on a.q2 = ss.bq1; - QUERY PLAN -------------------------------------------------------------- - Nested Loop Left Join - Output: a.q1, a.q2, b.q1, c.q1, (LEAST(a.q1, b.q1, c.q1)) - -> Seq Scan on public.int8_tbl a - Output: a.q1, a.q2 - -> Nested Loop - Output: b.q1, c.q1, LEAST(a.q1, b.q1, c.q1) - -> Seq Scan on public.int8_tbl b - Output: b.q1, b.q2 - Filter: (a.q2 = b.q1) - -> Seq Scan on public.int8_tbl c - Output: c.q1, c.q2 -(11 rows) - -select * from - int8_tbl a left join lateral - (select b.q1 as bq1, c.q1 as cq1, least(a.q1,b.q1,c.q1) from - int8_tbl b cross join int8_tbl c) ss - on a.q2 = ss.bq1; - q1 | q2 | bq1 | cq1 | least -------------------+-------------------+------------------+------------------+------------------ - 123 | 456 | | | - 123 | 4567890123456789 | 4567890123456789 | 123 | 123 - 123 | 4567890123456789 | 4567890123456789 | 123 | 123 - 123 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 - 123 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 - 123 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 - 123 | 4567890123456789 | 4567890123456789 | 123 | 123 - 123 | 4567890123456789 | 4567890123456789 | 123 | 123 - 123 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 - 123 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 - 123 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 - 123 | 4567890123456789 | 4567890123456789 | 123 | 123 - 123 | 4567890123456789 | 4567890123456789 | 123 | 123 - 123 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 - 123 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 - 123 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 - 4567890123456789 | 123 | 123 | 123 | 123 - 4567890123456789 | 123 | 123 | 123 | 123 - 4567890123456789 | 123 | 123 | 4567890123456789 | 123 - 4567890123456789 | 123 | 123 | 4567890123456789 | 123 - 4567890123456789 | 123 | 123 | 4567890123456789 | 123 - 4567890123456789 | 123 | 123 | 123 | 123 - 4567890123456789 | 123 | 123 | 123 | 123 - 4567890123456789 | 123 | 123 | 4567890123456789 | 123 - 4567890123456789 | 123 | 123 | 4567890123456789 | 123 - 4567890123456789 | 123 | 123 | 4567890123456789 | 123 - 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 | 123 - 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 | 123 - 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 - 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 - 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 - 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 | 123 - 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 | 123 - 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 - 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 - 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 - 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 | 123 - 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 | 123 - 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 - 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 - 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 - 4567890123456789 | -4567890123456789 | | | -(42 rows) - --- case requiring nested PlaceHolderVars -explain (verbose, costs off) -select * from - int8_tbl c left join ( - int8_tbl a left join (select q1, coalesce(q2,42) as x from int8_tbl b) ss1 - on a.q2 = ss1.q1 - cross join - lateral (select q1, coalesce(ss1.x,q2) as y from int8_tbl d) ss2 - ) on c.q2 = ss2.q1, - lateral (select ss2.y offset 0) ss3; - QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - Nested Loop - Output: c.q1, c.q2, a.q1, a.q2, b.q1, (COALESCE(b.q2, '42'::bigint)), d.q1, (COALESCE((COALESCE(b.q2, '42'::bigint)), d.q2)), ((COALESCE((COALESCE(b.q2, '42'::bigint)), d.q2))) - -> Hash Right Join - Output: c.q1, c.q2, a.q1, a.q2, b.q1, (COALESCE(b.q2, '42'::bigint)), d.q1, (COALESCE((COALESCE(b.q2, '42'::bigint)), d.q2)) - Hash Cond: (d.q1 = c.q2) - -> Nested Loop - Output: a.q1, a.q2, b.q1, (COALESCE(b.q2, '42'::bigint)), d.q1, (COALESCE((COALESCE(b.q2, '42'::bigint)), d.q2)) - -> Hash Left Join - Output: a.q1, a.q2, b.q1, (COALESCE(b.q2, '42'::bigint)) - Hash Cond: (a.q2 = b.q1) - -> Seq Scan on public.int8_tbl a - Output: a.q1, a.q2 - -> Hash - Output: b.q1, (COALESCE(b.q2, '42'::bigint)) - -> Seq Scan on public.int8_tbl b - Output: b.q1, COALESCE(b.q2, '42'::bigint) - -> Seq Scan on public.int8_tbl d - Output: d.q1, COALESCE((COALESCE(b.q2, '42'::bigint)), d.q2) - -> Hash - Output: c.q1, c.q2 - -> Seq Scan on public.int8_tbl c - Output: c.q1, c.q2 - -> Result - Output: (COALESCE((COALESCE(b.q2, '42'::bigint)), d.q2)) -(24 rows) - --- another case requiring nested PlaceHolderVars -explain (verbose, costs off) -select * from - (select 0 as val0) as ss0 - left join (select 1 as val) as ss1 on true - left join lateral (select ss1.val as val_filtered where false) as ss2 on true; - QUERY PLAN --------------------------------- - Nested Loop Left Join - Output: 0, (1), ((1)) - Join Filter: false - -> Result - Output: 1 - -> Result - Output: (1) - One-Time Filter: false -(8 rows) - -select * from - (select 0 as val0) as ss0 - left join (select 1 as val) as ss1 on true - left join lateral (select ss1.val as val_filtered where false) as ss2 on true; - val0 | val | val_filtered -------+-----+-------------- - 0 | 1 | -(1 row) - --- case that breaks the old ph_may_need optimization -explain (verbose, costs off) -select c.*,a.*,ss1.q1,ss2.q1,ss3.* from - int8_tbl c left join ( - int8_tbl a left join - (select q1, coalesce(q2,f1) as x from int8_tbl b, int4_tbl b2 - where q1 < f1) ss1 - on a.q2 = ss1.q1 - cross join - lateral (select q1, coalesce(ss1.x,q2) as y from int8_tbl d) ss2 - ) on c.q2 = ss2.q1, - lateral (select * from int4_tbl i where ss2.y > f1) ss3; - QUERY PLAN ---------------------------------------------------------------------------------------------------------- - Nested Loop - Output: c.q1, c.q2, a.q1, a.q2, b.q1, d.q1, i.f1 - Join Filter: ((COALESCE((COALESCE(b.q2, (b2.f1)::bigint)), d.q2)) > i.f1) - -> Hash Right Join - Output: c.q1, c.q2, a.q1, a.q2, b.q1, d.q1, (COALESCE((COALESCE(b.q2, (b2.f1)::bigint)), d.q2)) - Hash Cond: (d.q1 = c.q2) - -> Nested Loop - Output: a.q1, a.q2, b.q1, d.q1, (COALESCE((COALESCE(b.q2, (b2.f1)::bigint)), d.q2)) - -> Hash Right Join - Output: a.q1, a.q2, b.q1, (COALESCE(b.q2, (b2.f1)::bigint)) - Hash Cond: (b.q1 = a.q2) - -> Nested Loop - Output: b.q1, COALESCE(b.q2, (b2.f1)::bigint) - Join Filter: (b.q1 < b2.f1) - -> Seq Scan on public.int8_tbl b - Output: b.q1, b.q2 - -> Materialize - Output: b2.f1 - -> Seq Scan on public.int4_tbl b2 - Output: b2.f1 - -> Hash - Output: a.q1, a.q2 - -> Seq Scan on public.int8_tbl a - Output: a.q1, a.q2 - -> Seq Scan on public.int8_tbl d - Output: d.q1, COALESCE((COALESCE(b.q2, (b2.f1)::bigint)), d.q2) - -> Hash - Output: c.q1, c.q2 - -> Seq Scan on public.int8_tbl c - Output: c.q1, c.q2 - -> Materialize - Output: i.f1 - -> Seq Scan on public.int4_tbl i - Output: i.f1 -(34 rows) - --- check processing of postponed quals (bug #9041) -explain (verbose, costs off) -select * from - (select 1 as x offset 0) x cross join (select 2 as y offset 0) y - left join lateral ( - select * from (select 3 as z offset 0) z where z.z = x.x - ) zz on zz.z = y.y; - QUERY PLAN ----------------------------------------------- - Nested Loop Left Join - Output: (1), (2), (3) - Join Filter: (((3) = (1)) AND ((3) = (2))) - -> Nested Loop - Output: (1), (2) - -> Result - Output: 1 - -> Result - Output: 2 - -> Result - Output: 3 -(11 rows) - --- a new postponed-quals issue (bug #17768) -explain (costs off) -select * from int4_tbl t1, - lateral (select * from int4_tbl t2 inner join int4_tbl t3 on t1.f1 = 1 - inner join (int4_tbl t4 left join int4_tbl t5 on true) on true) ss; - QUERY PLAN -------------------------------------------------- - Nested Loop Left Join - -> Nested Loop - -> Nested Loop - -> Nested Loop - -> Seq Scan on int4_tbl t1 - Filter: (f1 = 1) - -> Seq Scan on int4_tbl t2 - -> Materialize - -> Seq Scan on int4_tbl t3 - -> Materialize - -> Seq Scan on int4_tbl t4 - -> Materialize - -> Seq Scan on int4_tbl t5 -(13 rows) - --- check dummy rels with lateral references (bug #15694) -explain (verbose, costs off) -select * from int8_tbl i8 left join lateral - (select *, i8.q2 from int4_tbl where false) ss on true; - QUERY PLAN --------------------------------------- - Nested Loop Left Join - Output: i8.q1, i8.q2, f1, (i8.q2) - Join Filter: false - -> Seq Scan on public.int8_tbl i8 - Output: i8.q1, i8.q2 - -> Result - Output: f1, i8.q2 - One-Time Filter: false -(8 rows) - -explain (verbose, costs off) -select * from int8_tbl i8 left join lateral - (select *, i8.q2 from int4_tbl i1, int4_tbl i2 where false) ss on true; - QUERY PLAN ------------------------------------------ - Nested Loop Left Join - Output: i8.q1, i8.q2, f1, f1, (i8.q2) - -> Seq Scan on public.int8_tbl i8 - Output: i8.q1, i8.q2 - -> Result - Output: f1, f1, i8.q2 - One-Time Filter: false -(7 rows) - --- check handling of nested appendrels inside LATERAL -select * from - ((select 2 as v) union all (select 3 as v)) as q1 - cross join lateral - ((select * from - ((select 4 as v) union all (select 5 as v)) as q3) - union all - (select q1.v) - ) as q2; - v | v ----+--- - 2 | 4 - 2 | 5 - 2 | 2 - 3 | 4 - 3 | 5 - 3 | 3 -(6 rows) - --- check the number of columns specified -SELECT * FROM (int8_tbl i cross join int4_tbl j) ss(a,b,c,d); -ERROR: join expression "ss" has 3 columns available but 4 columns specified --- check we don't try to do a unique-ified semijoin with LATERAL -explain (verbose, costs off) -select * from - (values (0,9998), (1,1000)) v(id,x), - lateral (select f1 from int4_tbl - where f1 = any (select unique1 from tenk1 - where unique2 = v.x offset 0)) ss; - QUERY PLAN ----------------------------------------------------------------------- - Nested Loop - Output: "*VALUES*".column1, "*VALUES*".column2, int4_tbl.f1 - -> Values Scan on "*VALUES*" - Output: "*VALUES*".column1, "*VALUES*".column2 - -> Nested Loop Semi Join - Output: int4_tbl.f1 - Join Filter: (int4_tbl.f1 = tenk1.unique1) - -> Seq Scan on public.int4_tbl - Output: int4_tbl.f1 - -> Materialize - Output: tenk1.unique1 - -> Index Scan using tenk1_unique2 on public.tenk1 - Output: tenk1.unique1 - Index Cond: (tenk1.unique2 = "*VALUES*".column2) -(14 rows) - -select * from - (values (0,9998), (1,1000)) v(id,x), - lateral (select f1 from int4_tbl - where f1 = any (select unique1 from tenk1 - where unique2 = v.x offset 0)) ss; - id | x | f1 -----+------+---- - 0 | 9998 | 0 -(1 row) - --- check proper extParam/allParam handling (this isn't exactly a LATERAL issue, --- but we can make the test case much more compact with LATERAL) -explain (verbose, costs off) -select * from (values (0), (1)) v(id), -lateral (select * from int8_tbl t1, - lateral (select * from - (select * from int8_tbl t2 - where (q1, random() > 0) = any (select q2, random() > 0 from int8_tbl t3 - where q2 = (select greatest(t1.q1,t2.q2)) - and (select v.id=0)) offset 0) ss2) ss - where t1.q1 = ss.q2) ss0; - QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------- - Nested Loop - Output: "*VALUES*".column1, t1.q1, t1.q2, ss2.q1, ss2.q2 - -> Seq Scan on public.int8_tbl t1 - Output: t1.q1, t1.q2 - -> Nested Loop - Output: "*VALUES*".column1, ss2.q1, ss2.q2 - -> Values Scan on "*VALUES*" - Output: "*VALUES*".column1 - -> Subquery Scan on ss2 - Output: ss2.q1, ss2.q2 - Filter: (t1.q1 = ss2.q2) - -> Seq Scan on public.int8_tbl t2 - Output: t2.q1, t2.q2 - Filter: (ANY ((t2.q1 = (SubPlan 3).col1) AND ((random() > '0'::double precision) = (SubPlan 3).col2))) - SubPlan 3 - -> Result - Output: t3.q2, (random() > '0'::double precision) - One-Time Filter: (InitPlan 2).col1 - InitPlan 1 - -> Result - Output: GREATEST(t1.q1, t2.q2) - InitPlan 2 - -> Result - Output: ("*VALUES*".column1 = 0) - -> Seq Scan on public.int8_tbl t3 - Output: t3.q1, t3.q2 - Filter: (t3.q2 = (InitPlan 1).col1) -(27 rows) - -select * from (values (0), (1)) v(id), -lateral (select * from int8_tbl t1, - lateral (select * from - (select * from int8_tbl t2 - where (q1, random() > 0) = any (select q2, random() > 0 from int8_tbl t3 - where q2 = (select greatest(t1.q1,t2.q2)) - and (select v.id=0)) offset 0) ss2) ss - where t1.q1 = ss.q2) ss0; - id | q1 | q2 | q1 | q2 -----+------------------+-------------------+------------------+------------------ - 0 | 4567890123456789 | 123 | 4567890123456789 | 4567890123456789 - 0 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 - 0 | 4567890123456789 | -4567890123456789 | 4567890123456789 | 4567890123456789 -(3 rows) - --- test some error cases where LATERAL should have been used but wasn't -select f1,g from int4_tbl a, (select f1 as g) ss; -ERROR: column "f1" does not exist -LINE 1: select f1,g from int4_tbl a, (select f1 as g) ss; - ^ -DETAIL: There is a column named "f1" in table "a", but it cannot be referenced from this part of the query. -HINT: To reference that column, you must mark this subquery with LATERAL. -select f1,g from int4_tbl a, (select a.f1 as g) ss; -ERROR: invalid reference to FROM-clause entry for table "a" -LINE 1: select f1,g from int4_tbl a, (select a.f1 as g) ss; - ^ -DETAIL: There is an entry for table "a", but it cannot be referenced from this part of the query. -HINT: To reference that table, you must mark this subquery with LATERAL. -select f1,g from int4_tbl a cross join (select f1 as g) ss; -ERROR: column "f1" does not exist -LINE 1: select f1,g from int4_tbl a cross join (select f1 as g) ss; - ^ -DETAIL: There is a column named "f1" in table "a", but it cannot be referenced from this part of the query. -HINT: To reference that column, you must mark this subquery with LATERAL. -select f1,g from int4_tbl a cross join (select a.f1 as g) ss; -ERROR: invalid reference to FROM-clause entry for table "a" -LINE 1: select f1,g from int4_tbl a cross join (select a.f1 as g) ss... - ^ -DETAIL: There is an entry for table "a", but it cannot be referenced from this part of the query. -HINT: To reference that table, you must mark this subquery with LATERAL. --- SQL:2008 says the left table is in scope but illegal to access here -select f1,g from int4_tbl a right join lateral generate_series(0, a.f1) g on true; -ERROR: invalid reference to FROM-clause entry for table "a" -LINE 1: ... int4_tbl a right join lateral generate_series(0, a.f1) g on... - ^ -DETAIL: The combining JOIN type must be INNER or LEFT for a LATERAL reference. -select f1,g from int4_tbl a full join lateral generate_series(0, a.f1) g on true; -ERROR: invalid reference to FROM-clause entry for table "a" -LINE 1: ...m int4_tbl a full join lateral generate_series(0, a.f1) g on... - ^ -DETAIL: The combining JOIN type must be INNER or LEFT for a LATERAL reference. --- check we complain about ambiguous table references -select * from - int8_tbl x cross join (int4_tbl x cross join lateral (select x.f1) ss); -ERROR: table reference "x" is ambiguous -LINE 2: ...cross join (int4_tbl x cross join lateral (select x.f1) ss); - ^ --- LATERAL can be used to put an aggregate into the FROM clause of its query -select 1 from tenk1 a, lateral (select max(a.unique1) from int4_tbl b) ss; -ERROR: aggregate functions are not allowed in FROM clause of their own query level -LINE 1: select 1 from tenk1 a, lateral (select max(a.unique1) from i... - ^ --- check behavior of LATERAL in UPDATE/DELETE -create temp table xx1 as select f1 as x1, -f1 as x2 from int4_tbl; --- error, can't do this: -update xx1 set x2 = f1 from (select * from int4_tbl where f1 = x1) ss; -ERROR: column "x1" does not exist -LINE 1: ... set x2 = f1 from (select * from int4_tbl where f1 = x1) ss; - ^ -DETAIL: There is a column named "x1" in table "xx1", but it cannot be referenced from this part of the query. -update xx1 set x2 = f1 from (select * from int4_tbl where f1 = xx1.x1) ss; -ERROR: invalid reference to FROM-clause entry for table "xx1" -LINE 1: ...t x2 = f1 from (select * from int4_tbl where f1 = xx1.x1) ss... - ^ -DETAIL: There is an entry for table "xx1", but it cannot be referenced from this part of the query. --- can't do it even with LATERAL: -update xx1 set x2 = f1 from lateral (select * from int4_tbl where f1 = x1) ss; -ERROR: invalid reference to FROM-clause entry for table "xx1" -LINE 1: ...= f1 from lateral (select * from int4_tbl where f1 = x1) ss; - ^ -HINT: There is an entry for table "xx1", but it cannot be referenced from this part of the query. --- we might in future allow something like this, but for now it's an error: -update xx1 set x2 = f1 from xx1, lateral (select * from int4_tbl where f1 = x1) ss; -ERROR: table name "xx1" specified more than once --- also errors: -delete from xx1 using (select * from int4_tbl where f1 = x1) ss; -ERROR: column "x1" does not exist -LINE 1: ...te from xx1 using (select * from int4_tbl where f1 = x1) ss; - ^ -DETAIL: There is a column named "x1" in table "xx1", but it cannot be referenced from this part of the query. -delete from xx1 using (select * from int4_tbl where f1 = xx1.x1) ss; -ERROR: invalid reference to FROM-clause entry for table "xx1" -LINE 1: ...from xx1 using (select * from int4_tbl where f1 = xx1.x1) ss... - ^ -DETAIL: There is an entry for table "xx1", but it cannot be referenced from this part of the query. -delete from xx1 using lateral (select * from int4_tbl where f1 = x1) ss; -ERROR: invalid reference to FROM-clause entry for table "xx1" -LINE 1: ...xx1 using lateral (select * from int4_tbl where f1 = x1) ss; - ^ -HINT: There is an entry for table "xx1", but it cannot be referenced from this part of the query. --- --- test LATERAL reference propagation down a multi-level inheritance hierarchy --- produced for a multi-level partitioned table hierarchy. --- -create table join_pt1 (a int, b int, c varchar) partition by range(a); -create table join_pt1p1 partition of join_pt1 for values from (0) to (100) partition by range(b); -create table join_pt1p2 partition of join_pt1 for values from (100) to (200); -create table join_pt1p1p1 partition of join_pt1p1 for values from (0) to (100); -insert into join_pt1 values (1, 1, 'x'), (101, 101, 'y'); -create table join_ut1 (a int, b int, c varchar); -insert into join_ut1 values (101, 101, 'y'), (2, 2, 'z'); -explain (verbose, costs off) -select t1.b, ss.phv from join_ut1 t1 left join lateral - (select t2.a as t2a, t3.a t3a, least(t1.a, t2.a, t3.a) phv - from join_pt1 t2 join join_ut1 t3 on t2.a = t3.b) ss - on t1.a = ss.t2a order by t1.a; - QUERY PLAN --------------------------------------------------------------------- - Sort - Output: t1.b, (LEAST(t1.a, t2.a, t3.a)), t1.a - Sort Key: t1.a - -> Nested Loop Left Join - Output: t1.b, (LEAST(t1.a, t2.a, t3.a)), t1.a - -> Seq Scan on public.join_ut1 t1 - Output: t1.a, t1.b, t1.c - -> Hash Join - Output: t2.a, LEAST(t1.a, t2.a, t3.a) - Hash Cond: (t3.b = t2.a) - -> Seq Scan on public.join_ut1 t3 - Output: t3.a, t3.b, t3.c - -> Hash - Output: t2.a - -> Append - -> Seq Scan on public.join_pt1p1p1 t2_1 - Output: t2_1.a - Filter: (t1.a = t2_1.a) - -> Seq Scan on public.join_pt1p2 t2_2 - Output: t2_2.a - Filter: (t1.a = t2_2.a) -(21 rows) - -select t1.b, ss.phv from join_ut1 t1 left join lateral - (select t2.a as t2a, t3.a t3a, least(t1.a, t2.a, t3.a) phv - from join_pt1 t2 join join_ut1 t3 on t2.a = t3.b) ss - on t1.a = ss.t2a order by t1.a; - b | phv ------+----- - 2 | - 101 | 101 -(2 rows) - -drop table join_pt1; -drop table join_ut1; --- --- test estimation behavior with multi-column foreign key and constant qual --- -begin; -create table fkest (x integer, x10 integer, x10b integer, x100 integer); -insert into fkest select x, x/10, x/10, x/100 from generate_series(1,1000) x; -create unique index on fkest(x, x10, x100); -analyze fkest; -explain (costs off) -select * from fkest f1 - join fkest f2 on (f1.x = f2.x and f1.x10 = f2.x10b and f1.x100 = f2.x100) - join fkest f3 on f1.x = f3.x - where f1.x100 = 2; - QUERY PLAN ------------------------------------------------------------ - Nested Loop - -> Hash Join - Hash Cond: ((f2.x = f1.x) AND (f2.x10b = f1.x10)) - -> Seq Scan on fkest f2 - Filter: (x100 = 2) - -> Hash - -> Seq Scan on fkest f1 - Filter: (x100 = 2) - -> Index Scan using fkest_x_x10_x100_idx on fkest f3 - Index Cond: (x = f1.x) -(10 rows) - -alter table fkest add constraint fk - foreign key (x, x10b, x100) references fkest (x, x10, x100); -explain (costs off) -select * from fkest f1 - join fkest f2 on (f1.x = f2.x and f1.x10 = f2.x10b and f1.x100 = f2.x100) - join fkest f3 on f1.x = f3.x - where f1.x100 = 2; - QUERY PLAN ------------------------------------------------------ - Hash Join - Hash Cond: ((f2.x = f1.x) AND (f2.x10b = f1.x10)) - -> Hash Join - Hash Cond: (f3.x = f2.x) - -> Seq Scan on fkest f3 - -> Hash - -> Seq Scan on fkest f2 - Filter: (x100 = 2) - -> Hash - -> Seq Scan on fkest f1 - Filter: (x100 = 2) -(11 rows) - -rollback; --- --- test that foreign key join estimation performs sanely for outer joins --- -begin; -create table fkest (a int, b int, c int unique, primary key(a,b)); -create table fkest1 (a int, b int, primary key(a,b)); -insert into fkest select x/10, x%10, x from generate_series(1,1000) x; -insert into fkest1 select x/10, x%10 from generate_series(1,1000) x; -alter table fkest1 - add constraint fkest1_a_b_fkey foreign key (a,b) references fkest; -analyze fkest; -analyze fkest1; -explain (costs off) -select * -from fkest f - left join fkest1 f1 on f.a = f1.a and f.b = f1.b - left join fkest1 f2 on f.a = f2.a and f.b = f2.b - left join fkest1 f3 on f.a = f3.a and f.b = f3.b -where f.c = 1; - QUERY PLAN ------------------------------------------------------------------- - Nested Loop Left Join - -> Nested Loop Left Join - -> Nested Loop Left Join - -> Index Scan using fkest_c_key on fkest f - Index Cond: (c = 1) - -> Index Only Scan using fkest1_pkey on fkest1 f1 - Index Cond: ((a = f.a) AND (b = f.b)) - -> Index Only Scan using fkest1_pkey on fkest1 f2 - Index Cond: ((a = f.a) AND (b = f.b)) - -> Index Only Scan using fkest1_pkey on fkest1 f3 - Index Cond: ((a = f.a) AND (b = f.b)) -(11 rows) - -rollback; --- --- test planner's ability to mark joins as unique --- -create table j1 (id int primary key); -create table j2 (id int primary key); -create table j3 (id int); -insert into j1 values(1),(2),(3); -insert into j2 values(1),(2),(3); -insert into j3 values(1),(1); -analyze j1; -analyze j2; -analyze j3; --- ensure join is properly marked as unique -explain (verbose, costs off) -select * from j1 inner join j2 on j1.id = j2.id; - QUERY PLAN ------------------------------------ - Hash Join - Output: j1.id, j2.id - Inner Unique: true - Hash Cond: (j1.id = j2.id) - -> Seq Scan on public.j1 - Output: j1.id - -> Hash - Output: j2.id - -> Seq Scan on public.j2 - Output: j2.id -(10 rows) - --- ensure join is not unique when not an equi-join -explain (verbose, costs off) -select * from j1 inner join j2 on j1.id > j2.id; - QUERY PLAN ------------------------------------ - Nested Loop - Output: j1.id, j2.id - Join Filter: (j1.id > j2.id) - -> Seq Scan on public.j1 - Output: j1.id - -> Materialize - Output: j2.id - -> Seq Scan on public.j2 - Output: j2.id -(9 rows) - --- ensure non-unique rel is not chosen as inner -explain (verbose, costs off) -select * from j1 inner join j3 on j1.id = j3.id; - QUERY PLAN ------------------------------------ - Hash Join - Output: j1.id, j3.id - Inner Unique: true - Hash Cond: (j3.id = j1.id) - -> Seq Scan on public.j3 - Output: j3.id - -> Hash - Output: j1.id - -> Seq Scan on public.j1 - Output: j1.id -(10 rows) - --- ensure left join is marked as unique -explain (verbose, costs off) -select * from j1 left join j2 on j1.id = j2.id; - QUERY PLAN ------------------------------------ - Hash Left Join - Output: j1.id, j2.id - Inner Unique: true - Hash Cond: (j1.id = j2.id) - -> Seq Scan on public.j1 - Output: j1.id - -> Hash - Output: j2.id - -> Seq Scan on public.j2 - Output: j2.id -(10 rows) - --- ensure right join is marked as unique -explain (verbose, costs off) -select * from j1 right join j2 on j1.id = j2.id; - QUERY PLAN ------------------------------------ - Hash Left Join - Output: j1.id, j2.id - Inner Unique: true - Hash Cond: (j2.id = j1.id) - -> Seq Scan on public.j2 - Output: j2.id - -> Hash - Output: j1.id - -> Seq Scan on public.j1 - Output: j1.id -(10 rows) - --- ensure full join is marked as unique -explain (verbose, costs off) -select * from j1 full join j2 on j1.id = j2.id; - QUERY PLAN ------------------------------------ - Hash Full Join - Output: j1.id, j2.id - Inner Unique: true - Hash Cond: (j1.id = j2.id) - -> Seq Scan on public.j1 - Output: j1.id - -> Hash - Output: j2.id - -> Seq Scan on public.j2 - Output: j2.id -(10 rows) - --- a clauseless (cross) join can't be unique -explain (verbose, costs off) -select * from j1 cross join j2; - QUERY PLAN ------------------------------------ - Nested Loop - Output: j1.id, j2.id - -> Seq Scan on public.j1 - Output: j1.id - -> Materialize - Output: j2.id - -> Seq Scan on public.j2 - Output: j2.id -(8 rows) - --- ensure a natural join is marked as unique -explain (verbose, costs off) -select * from j1 natural join j2; - QUERY PLAN ------------------------------------ - Hash Join - Output: j1.id - Inner Unique: true - Hash Cond: (j1.id = j2.id) - -> Seq Scan on public.j1 - Output: j1.id - -> Hash - Output: j2.id - -> Seq Scan on public.j2 - Output: j2.id -(10 rows) - --- ensure a distinct clause allows the inner to become unique -explain (verbose, costs off) -select * from j1 -inner join (select distinct id from j3) j3 on j1.id = j3.id; - QUERY PLAN ------------------------------------------ - Nested Loop - Output: j1.id, j3.id - Inner Unique: true - Join Filter: (j1.id = j3.id) - -> Unique - Output: j3.id - -> Sort - Output: j3.id - Sort Key: j3.id - -> Seq Scan on public.j3 - Output: j3.id - -> Seq Scan on public.j1 - Output: j1.id -(13 rows) - --- ensure group by clause allows the inner to become unique -explain (verbose, costs off) -select * from j1 -inner join (select id from j3 group by id) j3 on j1.id = j3.id; - QUERY PLAN ------------------------------------------ - Nested Loop - Output: j1.id, j3.id - Inner Unique: true - Join Filter: (j1.id = j3.id) - -> Group - Output: j3.id - Group Key: j3.id - -> Sort - Output: j3.id - Sort Key: j3.id - -> Seq Scan on public.j3 - Output: j3.id - -> Seq Scan on public.j1 - Output: j1.id -(14 rows) - -drop table j1; -drop table j2; -drop table j3; --- test more complex permutations of unique joins -create table j1 (id1 int, id2 int, primary key(id1,id2)); -create table j2 (id1 int, id2 int, primary key(id1,id2)); -create table j3 (id1 int, id2 int, primary key(id1,id2)); -insert into j1 values(1,1),(1,2); -insert into j2 values(1,1); -insert into j3 values(1,1); -analyze j1; -analyze j2; -analyze j3; --- ensure there's no unique join when not all columns which are part of the --- unique index are seen in the join clause -explain (verbose, costs off) -select * from j1 -inner join j2 on j1.id1 = j2.id1; - QUERY PLAN ------------------------------------------- - Nested Loop - Output: j1.id1, j1.id2, j2.id1, j2.id2 - Join Filter: (j1.id1 = j2.id1) - -> Seq Scan on public.j2 - Output: j2.id1, j2.id2 - -> Seq Scan on public.j1 - Output: j1.id1, j1.id2 -(7 rows) - --- ensure proper unique detection with multiple join quals -explain (verbose, costs off) -select * from j1 -inner join j2 on j1.id1 = j2.id1 and j1.id2 = j2.id2; - QUERY PLAN ----------------------------------------------------------- - Nested Loop - Output: j1.id1, j1.id2, j2.id1, j2.id2 - Inner Unique: true - Join Filter: ((j1.id1 = j2.id1) AND (j1.id2 = j2.id2)) - -> Seq Scan on public.j2 - Output: j2.id1, j2.id2 - -> Seq Scan on public.j1 - Output: j1.id1, j1.id2 -(8 rows) - --- ensure we don't detect the join to be unique when quals are not part of the --- join condition -explain (verbose, costs off) -select * from j1 -inner join j2 on j1.id1 = j2.id1 where j1.id2 = 1; - QUERY PLAN ------------------------------------------- - Nested Loop - Output: j1.id1, j1.id2, j2.id1, j2.id2 - Join Filter: (j1.id1 = j2.id1) - -> Seq Scan on public.j1 - Output: j1.id1, j1.id2 - Filter: (j1.id2 = 1) - -> Seq Scan on public.j2 - Output: j2.id1, j2.id2 -(8 rows) - --- as above, but for left joins. -explain (verbose, costs off) -select * from j1 -left join j2 on j1.id1 = j2.id1 where j1.id2 = 1; - QUERY PLAN ------------------------------------------- - Nested Loop Left Join - Output: j1.id1, j1.id2, j2.id1, j2.id2 - Join Filter: (j1.id1 = j2.id1) - -> Seq Scan on public.j1 - Output: j1.id1, j1.id2 - Filter: (j1.id2 = 1) - -> Seq Scan on public.j2 - Output: j2.id1, j2.id2 -(8 rows) - -create unique index j1_id2_idx on j1(id2) where id2 is not null; --- ensure we don't use a partial unique index as unique proofs -explain (verbose, costs off) -select * from j1 -inner join j2 on j1.id2 = j2.id2; - QUERY PLAN ------------------------------------------- - Nested Loop - Output: j1.id1, j1.id2, j2.id1, j2.id2 - Join Filter: (j2.id2 = j1.id2) - -> Seq Scan on public.j2 - Output: j2.id1, j2.id2 - -> Seq Scan on public.j1 - Output: j1.id1, j1.id2 -(7 rows) - -drop index j1_id2_idx; --- validate logic in merge joins which skips mark and restore. --- it should only do this if all quals which were used to detect the unique --- are present as join quals, and not plain quals. -set enable_nestloop to 0; -set enable_hashjoin to 0; -set enable_sort to 0; --- create indexes that will be preferred over the PKs to perform the join -create index j1_id1_idx on j1 (id1) where id1 % 1000 = 1; -create index j2_id1_idx on j2 (id1) where id1 % 1000 = 1; --- need an additional row in j2, if we want j2_id1_idx to be preferred -insert into j2 values(1,2); -analyze j2; -explain (costs off) select * from j1 -inner join j2 on j1.id1 = j2.id1 and j1.id2 = j2.id2 -where j1.id1 % 1000 = 1 and j2.id1 % 1000 = 1; - QUERY PLAN ------------------------------------------ - Merge Join - Merge Cond: (j1.id1 = j2.id1) - Join Filter: (j2.id2 = j1.id2) - -> Index Scan using j1_id1_idx on j1 - -> Index Scan using j2_id1_idx on j2 -(5 rows) - -select * from j1 -inner join j2 on j1.id1 = j2.id1 and j1.id2 = j2.id2 -where j1.id1 % 1000 = 1 and j2.id1 % 1000 = 1; - id1 | id2 | id1 | id2 ------+-----+-----+----- - 1 | 1 | 1 | 1 - 1 | 2 | 1 | 2 -(2 rows) - --- Exercise array keys mark/restore B-Tree code -explain (costs off) select * from j1 -inner join j2 on j1.id1 = j2.id1 and j1.id2 = j2.id2 -where j1.id1 % 1000 = 1 and j2.id1 % 1000 = 1 and j2.id1 = any (array[1]); - QUERY PLAN ----------------------------------------------------- - Merge Join - Merge Cond: (j1.id1 = j2.id1) - Join Filter: (j2.id2 = j1.id2) - -> Index Scan using j1_id1_idx on j1 - -> Index Scan using j2_id1_idx on j2 - Index Cond: (id1 = ANY ('{1}'::integer[])) -(6 rows) - -select * from j1 -inner join j2 on j1.id1 = j2.id1 and j1.id2 = j2.id2 -where j1.id1 % 1000 = 1 and j2.id1 % 1000 = 1 and j2.id1 = any (array[1]); - id1 | id2 | id1 | id2 ------+-----+-----+----- - 1 | 1 | 1 | 1 - 1 | 2 | 1 | 2 -(2 rows) - --- Exercise array keys "find extreme element" B-Tree code -explain (costs off) select * from j1 -inner join j2 on j1.id1 = j2.id1 and j1.id2 = j2.id2 -where j1.id1 % 1000 = 1 and j2.id1 % 1000 = 1 and j2.id1 >= any (array[1,5]); - QUERY PLAN -------------------------------------------------------- - Merge Join - Merge Cond: (j1.id1 = j2.id1) - Join Filter: (j2.id2 = j1.id2) - -> Index Scan using j1_id1_idx on j1 - -> Index Scan using j2_id1_idx on j2 - Index Cond: (id1 >= ANY ('{1,5}'::integer[])) -(6 rows) - -select * from j1 -inner join j2 on j1.id1 = j2.id1 and j1.id2 = j2.id2 -where j1.id1 % 1000 = 1 and j2.id1 % 1000 = 1 and j2.id1 >= any (array[1,5]); - id1 | id2 | id1 | id2 ------+-----+-----+----- - 1 | 1 | 1 | 1 - 1 | 2 | 1 | 2 -(2 rows) - -reset enable_nestloop; -reset enable_hashjoin; -reset enable_sort; -drop table j1; -drop table j2; -drop table j3; --- check that semijoin inner is not seen as unique for a portion of the outerrel -explain (verbose, costs off) -select t1.unique1, t2.hundred -from onek t1, tenk1 t2 -where exists (select 1 from tenk1 t3 - where t3.thousand = t1.unique1 and t3.tenthous = t2.hundred) - and t1.unique1 < 1; - QUERY PLAN ---------------------------------------------------------------------------------- - Nested Loop - Output: t1.unique1, t2.hundred - -> Hash Join - Output: t1.unique1, t3.tenthous - Hash Cond: (t3.thousand = t1.unique1) - -> HashAggregate - Output: t3.thousand, t3.tenthous - Group Key: t3.thousand, t3.tenthous - -> Index Only Scan using tenk1_thous_tenthous on public.tenk1 t3 - Output: t3.thousand, t3.tenthous - -> Hash - Output: t1.unique1 - -> Index Only Scan using onek_unique1 on public.onek t1 - Output: t1.unique1 - Index Cond: (t1.unique1 < 1) - -> Index Only Scan using tenk1_hundred on public.tenk1 t2 - Output: t2.hundred - Index Cond: (t2.hundred = t3.tenthous) -(18 rows) - --- ... unless it actually is unique -create table j3 as select unique1, tenthous from onek; -vacuum analyze j3; -create unique index on j3(unique1, tenthous); -explain (verbose, costs off) -select t1.unique1, t2.hundred -from onek t1, tenk1 t2 -where exists (select 1 from j3 - where j3.unique1 = t1.unique1 and j3.tenthous = t2.hundred) - and t1.unique1 < 1; - QUERY PLAN ------------------------------------------------------------------------- - Nested Loop - Output: t1.unique1, t2.hundred - -> Nested Loop - Output: t1.unique1, j3.tenthous - -> Index Only Scan using onek_unique1 on public.onek t1 - Output: t1.unique1 - Index Cond: (t1.unique1 < 1) - -> Index Only Scan using j3_unique1_tenthous_idx on public.j3 - Output: j3.unique1, j3.tenthous - Index Cond: (j3.unique1 = t1.unique1) - -> Index Only Scan using tenk1_hundred on public.tenk1 t2 - Output: t2.hundred - Index Cond: (t2.hundred = j3.tenthous) -(13 rows) - -drop table j3; --- Exercise the "skip fetch" Bitmap Heap Scan optimization when candidate --- tuples are discarded. This may occur when: --- 1. A join doesn't require all inner tuples to be scanned for each outer --- tuple, and --- 2. The inner side is scanned using a bitmap heap scan, and --- 3. The bitmap heap scan is eligible for the "skip fetch" optimization. --- This optimization is usable when no data from the underlying table is --- needed. Use a temp table so it is only visible to this backend and --- vacuum may reliably mark all blocks in the table all visible in the --- visibility map. -CREATE TEMP TABLE skip_fetch (a INT, b INT) WITH (fillfactor=10); -INSERT INTO skip_fetch SELECT i % 3, i FROM generate_series(0,30) i; -CREATE INDEX ON skip_fetch(a); -VACUUM (ANALYZE) skip_fetch; -SET enable_indexonlyscan = off; -SET enable_seqscan = off; -EXPLAIN (COSTS OFF) -SELECT t1.a FROM skip_fetch t1 LEFT JOIN skip_fetch t2 ON t2.a = 1 WHERE t2.a IS NULL; - QUERY PLAN ---------------------------------------------------------- - Nested Loop Anti Join - Disabled Nodes: 1 - -> Seq Scan on skip_fetch t1 - Disabled Nodes: 1 - -> Materialize - -> Bitmap Heap Scan on skip_fetch t2 - Recheck Cond: (a = 1) - -> Bitmap Index Scan on skip_fetch_a_idx - Index Cond: (a = 1) -(9 rows) - -SELECT t1.a FROM skip_fetch t1 LEFT JOIN skip_fetch t2 ON t2.a = 1 WHERE t2.a IS NULL; - a ---- -(0 rows) - -RESET enable_indexonlyscan; -RESET enable_seqscan; +psql: error: connection to server on socket "/tmp/pg_regress-03Az7d/.s.PGSQL.40051" failed: FATAL: the database system is not yet accepting connections +DETAIL: Consistent recovery state has not been yet reached. diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/aggregates.out /tmp/cirrus-ci-build/build/testrun/regress/regress/results/aggregates.out --- /tmp/cirrus-ci-build/src/test/regress/expected/aggregates.out 2024-09-20 21:41:45.662024520 +0000 +++ /tmp/cirrus-ci-build/build/testrun/regress/regress/results/aggregates.out 2024-09-20 21:46:05.481625102 +0000 @@ -1,3446 +1,2 @@ --- --- AGGREGATES --- --- directory paths are passed to us in environment variables -\getenv abs_srcdir PG_ABS_SRCDIR --- avoid bit-exact output here because operations may not be bit-exact. -SET extra_float_digits = 0; --- prepare some test data -CREATE TABLE aggtest ( - a int2, - b float4 -); -\set filename :abs_srcdir '/data/agg.data' -COPY aggtest FROM :'filename'; -ANALYZE aggtest; -SELECT avg(four) AS avg_1 FROM onek; - avg_1 --------------------- - 1.5000000000000000 -(1 row) - -SELECT avg(a) AS avg_32 FROM aggtest WHERE a < 100; - avg_32 ---------------------- - 32.6666666666666667 -(1 row) - -SELECT any_value(v) FROM (VALUES (1), (2), (3)) AS v (v); - any_value ------------ - 1 -(1 row) - -SELECT any_value(v) FROM (VALUES (NULL)) AS v (v); - any_value ------------ - -(1 row) - -SELECT any_value(v) FROM (VALUES (NULL), (1), (2)) AS v (v); - any_value ------------ - 1 -(1 row) - -SELECT any_value(v) FROM (VALUES (array['hello', 'world'])) AS v (v); - any_value ---------------- - {hello,world} -(1 row) - --- In 7.1, avg(float4) is computed using float8 arithmetic. --- Round the result to 3 digits to avoid platform-specific results. -SELECT avg(b)::numeric(10,3) AS avg_107_943 FROM aggtest; - avg_107_943 -------------- - 107.943 -(1 row) - -SELECT avg(gpa) AS avg_3_4 FROM ONLY student; - avg_3_4 ---------- - 3.4 -(1 row) - -SELECT sum(four) AS sum_1500 FROM onek; - sum_1500 ----------- - 1500 -(1 row) - -SELECT sum(a) AS sum_198 FROM aggtest; - sum_198 ---------- - 198 -(1 row) - -SELECT sum(b) AS avg_431_773 FROM aggtest; - avg_431_773 -------------- - 431.773 -(1 row) - -SELECT sum(gpa) AS avg_6_8 FROM ONLY student; - avg_6_8 ---------- - 6.8 -(1 row) - -SELECT max(four) AS max_3 FROM onek; - max_3 -------- - 3 -(1 row) - -SELECT max(a) AS max_100 FROM aggtest; - max_100 ---------- - 100 -(1 row) - -SELECT max(aggtest.b) AS max_324_78 FROM aggtest; - max_324_78 ------------- - 324.78 -(1 row) - -SELECT max(student.gpa) AS max_3_7 FROM student; - max_3_7 ---------- - 3.7 -(1 row) - -SELECT stddev_pop(b) FROM aggtest; - stddev_pop ------------------ - 131.10703231895 -(1 row) - -SELECT stddev_samp(b) FROM aggtest; - stddev_samp ------------------- - 151.389360803998 -(1 row) - -SELECT var_pop(b) FROM aggtest; - var_pop ------------------- - 17189.0539234823 -(1 row) - -SELECT var_samp(b) FROM aggtest; - var_samp ------------------- - 22918.7385646431 -(1 row) - -SELECT stddev_pop(b::numeric) FROM aggtest; - stddev_pop ------------------- - 131.107032862199 -(1 row) - -SELECT stddev_samp(b::numeric) FROM aggtest; - stddev_samp ------------------- - 151.389361431288 -(1 row) - -SELECT var_pop(b::numeric) FROM aggtest; - var_pop --------------------- - 17189.054065929769 -(1 row) - -SELECT var_samp(b::numeric) FROM aggtest; - var_samp --------------------- - 22918.738754573025 -(1 row) - --- population variance is defined for a single tuple, sample variance --- is not -SELECT var_pop(1.0::float8), var_samp(2.0::float8); - var_pop | var_samp ----------+---------- - 0 | -(1 row) - -SELECT stddev_pop(3.0::float8), stddev_samp(4.0::float8); - stddev_pop | stddev_samp -------------+------------- - 0 | -(1 row) - -SELECT var_pop('inf'::float8), var_samp('inf'::float8); - var_pop | var_samp ----------+---------- - NaN | -(1 row) - -SELECT stddev_pop('inf'::float8), stddev_samp('inf'::float8); - stddev_pop | stddev_samp -------------+------------- - NaN | -(1 row) - -SELECT var_pop('nan'::float8), var_samp('nan'::float8); - var_pop | var_samp ----------+---------- - NaN | -(1 row) - -SELECT stddev_pop('nan'::float8), stddev_samp('nan'::float8); - stddev_pop | stddev_samp -------------+------------- - NaN | -(1 row) - -SELECT var_pop(1.0::float4), var_samp(2.0::float4); - var_pop | var_samp ----------+---------- - 0 | -(1 row) - -SELECT stddev_pop(3.0::float4), stddev_samp(4.0::float4); - stddev_pop | stddev_samp -------------+------------- - 0 | -(1 row) - -SELECT var_pop('inf'::float4), var_samp('inf'::float4); - var_pop | var_samp ----------+---------- - NaN | -(1 row) - -SELECT stddev_pop('inf'::float4), stddev_samp('inf'::float4); - stddev_pop | stddev_samp -------------+------------- - NaN | -(1 row) - -SELECT var_pop('nan'::float4), var_samp('nan'::float4); - var_pop | var_samp ----------+---------- - NaN | -(1 row) - -SELECT stddev_pop('nan'::float4), stddev_samp('nan'::float4); - stddev_pop | stddev_samp -------------+------------- - NaN | -(1 row) - -SELECT var_pop(1.0::numeric), var_samp(2.0::numeric); - var_pop | var_samp ----------+---------- - 0 | -(1 row) - -SELECT stddev_pop(3.0::numeric), stddev_samp(4.0::numeric); - stddev_pop | stddev_samp -------------+------------- - 0 | -(1 row) - -SELECT var_pop('inf'::numeric), var_samp('inf'::numeric); - var_pop | var_samp ----------+---------- - NaN | -(1 row) - -SELECT stddev_pop('inf'::numeric), stddev_samp('inf'::numeric); - stddev_pop | stddev_samp -------------+------------- - NaN | -(1 row) - -SELECT var_pop('nan'::numeric), var_samp('nan'::numeric); - var_pop | var_samp ----------+---------- - NaN | -(1 row) - -SELECT stddev_pop('nan'::numeric), stddev_samp('nan'::numeric); - stddev_pop | stddev_samp -------------+------------- - NaN | -(1 row) - --- verify correct results for min(record) and max(record) aggregates -SELECT max(row(a,b)) FROM aggtest; - max --------------- - (100,99.097) -(1 row) - -SELECT max(row(b,a)) FROM aggtest; - max -------------- - (324.78,42) -(1 row) - -SELECT min(row(a,b)) FROM aggtest; - min -------------- - (0,0.09561) -(1 row) - -SELECT min(row(b,a)) FROM aggtest; - min -------------- - (0.09561,0) -(1 row) - --- verify correct results for null and NaN inputs -select sum(null::int4) from generate_series(1,3); - sum ------ - -(1 row) - -select sum(null::int8) from generate_series(1,3); - sum ------ - -(1 row) - -select sum(null::numeric) from generate_series(1,3); - sum ------ - -(1 row) - -select sum(null::float8) from generate_series(1,3); - sum ------ - -(1 row) - -select avg(null::int4) from generate_series(1,3); - avg ------ - -(1 row) - -select avg(null::int8) from generate_series(1,3); - avg ------ - -(1 row) - -select avg(null::numeric) from generate_series(1,3); - avg ------ - -(1 row) - -select avg(null::float8) from generate_series(1,3); - avg ------ - -(1 row) - -select sum('NaN'::numeric) from generate_series(1,3); - sum ------ - NaN -(1 row) - -select avg('NaN'::numeric) from generate_series(1,3); - avg ------ - NaN -(1 row) - --- verify correct results for infinite inputs -SELECT sum(x::float8), avg(x::float8), var_pop(x::float8) -FROM (VALUES ('1'), ('infinity')) v(x); - sum | avg | var_pop -----------+----------+--------- - Infinity | Infinity | NaN -(1 row) - -SELECT sum(x::float8), avg(x::float8), var_pop(x::float8) -FROM (VALUES ('infinity'), ('1')) v(x); - sum | avg | var_pop -----------+----------+--------- - Infinity | Infinity | NaN -(1 row) - -SELECT sum(x::float8), avg(x::float8), var_pop(x::float8) -FROM (VALUES ('infinity'), ('infinity')) v(x); - sum | avg | var_pop -----------+----------+--------- - Infinity | Infinity | NaN -(1 row) - -SELECT sum(x::float8), avg(x::float8), var_pop(x::float8) -FROM (VALUES ('-infinity'), ('infinity')) v(x); - sum | avg | var_pop ------+-----+--------- - NaN | NaN | NaN -(1 row) - -SELECT sum(x::float8), avg(x::float8), var_pop(x::float8) -FROM (VALUES ('-infinity'), ('-infinity')) v(x); - sum | avg | var_pop ------------+-----------+--------- - -Infinity | -Infinity | NaN -(1 row) - -SELECT sum(x::numeric), avg(x::numeric), var_pop(x::numeric) -FROM (VALUES ('1'), ('infinity')) v(x); - sum | avg | var_pop -----------+----------+--------- - Infinity | Infinity | NaN -(1 row) - -SELECT sum(x::numeric), avg(x::numeric), var_pop(x::numeric) -FROM (VALUES ('infinity'), ('1')) v(x); - sum | avg | var_pop -----------+----------+--------- - Infinity | Infinity | NaN -(1 row) - -SELECT sum(x::numeric), avg(x::numeric), var_pop(x::numeric) -FROM (VALUES ('infinity'), ('infinity')) v(x); - sum | avg | var_pop -----------+----------+--------- - Infinity | Infinity | NaN -(1 row) - -SELECT sum(x::numeric), avg(x::numeric), var_pop(x::numeric) -FROM (VALUES ('-infinity'), ('infinity')) v(x); - sum | avg | var_pop ------+-----+--------- - NaN | NaN | NaN -(1 row) - -SELECT sum(x::numeric), avg(x::numeric), var_pop(x::numeric) -FROM (VALUES ('-infinity'), ('-infinity')) v(x); - sum | avg | var_pop ------------+-----------+--------- - -Infinity | -Infinity | NaN -(1 row) - --- test accuracy with a large input offset -SELECT avg(x::float8), var_pop(x::float8) -FROM (VALUES (100000003), (100000004), (100000006), (100000007)) v(x); - avg | var_pop ------------+--------- - 100000005 | 2.5 -(1 row) - -SELECT avg(x::float8), var_pop(x::float8) -FROM (VALUES (7000000000005), (7000000000007)) v(x); - avg | var_pop ----------------+--------- - 7000000000006 | 1 -(1 row) - --- SQL2003 binary aggregates -SELECT regr_count(b, a) FROM aggtest; - regr_count ------------- - 4 -(1 row) - -SELECT regr_sxx(b, a) FROM aggtest; - regr_sxx ----------- - 5099 -(1 row) - -SELECT regr_syy(b, a) FROM aggtest; - regr_syy ------------------- - 68756.2156939293 -(1 row) - -SELECT regr_sxy(b, a) FROM aggtest; - regr_sxy ------------------- - 2614.51582155004 -(1 row) - -SELECT regr_avgx(b, a), regr_avgy(b, a) FROM aggtest; - regr_avgx | regr_avgy ------------+------------------ - 49.5 | 107.943152273074 -(1 row) - -SELECT regr_r2(b, a) FROM aggtest; - regr_r2 --------------------- - 0.0194977982031803 -(1 row) - -SELECT regr_slope(b, a), regr_intercept(b, a) FROM aggtest; - regr_slope | regr_intercept --------------------+------------------ - 0.512750700441271 | 82.5619926012309 -(1 row) - -SELECT covar_pop(b, a), covar_samp(b, a) FROM aggtest; - covar_pop | covar_samp ------------------+------------------ - 653.62895538751 | 871.505273850014 -(1 row) - -SELECT corr(b, a) FROM aggtest; - corr -------------------- - 0.139634516517873 -(1 row) - --- check single-tuple behavior -SELECT covar_pop(1::float8,2::float8), covar_samp(3::float8,4::float8); - covar_pop | covar_samp ------------+------------ - 0 | -(1 row) - -SELECT covar_pop(1::float8,'inf'::float8), covar_samp(3::float8,'inf'::float8); - covar_pop | covar_samp ------------+------------ - NaN | -(1 row) - -SELECT covar_pop(1::float8,'nan'::float8), covar_samp(3::float8,'nan'::float8); - covar_pop | covar_samp ------------+------------ - NaN | -(1 row) - --- test accum and combine functions directly -CREATE TABLE regr_test (x float8, y float8); -INSERT INTO regr_test VALUES (10,150),(20,250),(30,350),(80,540),(100,200); -SELECT count(*), sum(x), regr_sxx(y,x), sum(y),regr_syy(y,x), regr_sxy(y,x) -FROM regr_test WHERE x IN (10,20,30,80); - count | sum | regr_sxx | sum | regr_syy | regr_sxy --------+-----+----------+------+----------+---------- - 4 | 140 | 2900 | 1290 | 83075 | 15050 -(1 row) - -SELECT count(*), sum(x), regr_sxx(y,x), sum(y),regr_syy(y,x), regr_sxy(y,x) -FROM regr_test; - count | sum | regr_sxx | sum | regr_syy | regr_sxy --------+-----+----------+------+----------+---------- - 5 | 240 | 6280 | 1490 | 95080 | 8680 -(1 row) - -SELECT float8_accum('{4,140,2900}'::float8[], 100); - float8_accum --------------- - {5,240,6280} -(1 row) - -SELECT float8_regr_accum('{4,140,2900,1290,83075,15050}'::float8[], 200, 100); - float8_regr_accum ------------------------------- - {5,240,6280,1490,95080,8680} -(1 row) - -SELECT count(*), sum(x), regr_sxx(y,x), sum(y),regr_syy(y,x), regr_sxy(y,x) -FROM regr_test WHERE x IN (10,20,30); - count | sum | regr_sxx | sum | regr_syy | regr_sxy --------+-----+----------+-----+----------+---------- - 3 | 60 | 200 | 750 | 20000 | 2000 -(1 row) - -SELECT count(*), sum(x), regr_sxx(y,x), sum(y),regr_syy(y,x), regr_sxy(y,x) -FROM regr_test WHERE x IN (80,100); - count | sum | regr_sxx | sum | regr_syy | regr_sxy --------+-----+----------+-----+----------+---------- - 2 | 180 | 200 | 740 | 57800 | -3400 -(1 row) - -SELECT float8_combine('{3,60,200}'::float8[], '{0,0,0}'::float8[]); - float8_combine ----------------- - {3,60,200} -(1 row) - -SELECT float8_combine('{0,0,0}'::float8[], '{2,180,200}'::float8[]); - float8_combine ----------------- - {2,180,200} -(1 row) - -SELECT float8_combine('{3,60,200}'::float8[], '{2,180,200}'::float8[]); - float8_combine ----------------- - {5,240,6280} -(1 row) - -SELECT float8_regr_combine('{3,60,200,750,20000,2000}'::float8[], - '{0,0,0,0,0,0}'::float8[]); - float8_regr_combine ---------------------------- - {3,60,200,750,20000,2000} -(1 row) - -SELECT float8_regr_combine('{0,0,0,0,0,0}'::float8[], - '{2,180,200,740,57800,-3400}'::float8[]); - float8_regr_combine ------------------------------ - {2,180,200,740,57800,-3400} -(1 row) - -SELECT float8_regr_combine('{3,60,200,750,20000,2000}'::float8[], - '{2,180,200,740,57800,-3400}'::float8[]); - float8_regr_combine ------------------------------- - {5,240,6280,1490,95080,8680} -(1 row) - -DROP TABLE regr_test; --- test count, distinct -SELECT count(four) AS cnt_1000 FROM onek; - cnt_1000 ----------- - 1000 -(1 row) - -SELECT count(DISTINCT four) AS cnt_4 FROM onek; - cnt_4 -------- - 4 -(1 row) - -select ten, count(*), sum(four) from onek -group by ten order by ten; - ten | count | sum ------+-------+----- - 0 | 100 | 100 - 1 | 100 | 200 - 2 | 100 | 100 - 3 | 100 | 200 - 4 | 100 | 100 - 5 | 100 | 200 - 6 | 100 | 100 - 7 | 100 | 200 - 8 | 100 | 100 - 9 | 100 | 200 -(10 rows) - -select ten, count(four), sum(DISTINCT four) from onek -group by ten order by ten; - ten | count | sum ------+-------+----- - 0 | 100 | 2 - 1 | 100 | 4 - 2 | 100 | 2 - 3 | 100 | 4 - 4 | 100 | 2 - 5 | 100 | 4 - 6 | 100 | 2 - 7 | 100 | 4 - 8 | 100 | 2 - 9 | 100 | 4 -(10 rows) - --- user-defined aggregates -SELECT newavg(four) AS avg_1 FROM onek; - avg_1 --------------------- - 1.5000000000000000 -(1 row) - -SELECT newsum(four) AS sum_1500 FROM onek; - sum_1500 ----------- - 1500 -(1 row) - -SELECT newcnt(four) AS cnt_1000 FROM onek; - cnt_1000 ----------- - 1000 -(1 row) - -SELECT newcnt(*) AS cnt_1000 FROM onek; - cnt_1000 ----------- - 1000 -(1 row) - -SELECT oldcnt(*) AS cnt_1000 FROM onek; - cnt_1000 ----------- - 1000 -(1 row) - -SELECT sum2(q1,q2) FROM int8_tbl; - sum2 -------------------- - 18271560493827981 -(1 row) - --- test for outer-level aggregates --- this should work -select ten, sum(distinct four) from onek a -group by ten -having exists (select 1 from onek b where sum(distinct a.four) = b.four); - ten | sum ------+----- - 0 | 2 - 2 | 2 - 4 | 2 - 6 | 2 - 8 | 2 -(5 rows) - --- this should fail because subquery has an agg of its own in WHERE -select ten, sum(distinct four) from onek a -group by ten -having exists (select 1 from onek b - where sum(distinct a.four + b.four) = b.four); -ERROR: aggregate functions are not allowed in WHERE -LINE 4: where sum(distinct a.four + b.four) = b.four)... - ^ --- Test handling of sublinks within outer-level aggregates. --- Per bug report from Daniel Grace. -select - (select max((select i.unique2 from tenk1 i where i.unique1 = o.unique1))) -from tenk1 o; - max ------- - 9999 -(1 row) - --- Test handling of Params within aggregate arguments in hashed aggregation. --- Per bug report from Jeevan Chalke. -explain (verbose, costs off) -select s1, s2, sm -from generate_series(1, 3) s1, - lateral (select s2, sum(s1 + s2) sm - from generate_series(1, 3) s2 group by s2) ss -order by 1, 2; - QUERY PLAN ------------------------------------------------------------------- - Sort - Output: s1.s1, s2.s2, (sum((s1.s1 + s2.s2))) - Sort Key: s1.s1, s2.s2 - -> Nested Loop - Output: s1.s1, s2.s2, (sum((s1.s1 + s2.s2))) - -> Function Scan on pg_catalog.generate_series s1 - Output: s1.s1 - Function Call: generate_series(1, 3) - -> HashAggregate - Output: s2.s2, sum((s1.s1 + s2.s2)) - Group Key: s2.s2 - -> Function Scan on pg_catalog.generate_series s2 - Output: s2.s2 - Function Call: generate_series(1, 3) -(14 rows) - -select s1, s2, sm -from generate_series(1, 3) s1, - lateral (select s2, sum(s1 + s2) sm - from generate_series(1, 3) s2 group by s2) ss -order by 1, 2; - s1 | s2 | sm -----+----+---- - 1 | 1 | 2 - 1 | 2 | 3 - 1 | 3 | 4 - 2 | 1 | 3 - 2 | 2 | 4 - 2 | 3 | 5 - 3 | 1 | 4 - 3 | 2 | 5 - 3 | 3 | 6 -(9 rows) - -explain (verbose, costs off) -select array(select sum(x+y) s - from generate_series(1,3) y group by y order by s) - from generate_series(1,3) x; - QUERY PLAN -------------------------------------------------------------------- - Function Scan on pg_catalog.generate_series x - Output: ARRAY(SubPlan 1) - Function Call: generate_series(1, 3) - SubPlan 1 - -> Sort - Output: (sum((x.x + y.y))), y.y - Sort Key: (sum((x.x + y.y))) - -> HashAggregate - Output: sum((x.x + y.y)), y.y - Group Key: y.y - -> Function Scan on pg_catalog.generate_series y - Output: y.y - Function Call: generate_series(1, 3) -(13 rows) - -select array(select sum(x+y) s - from generate_series(1,3) y group by y order by s) - from generate_series(1,3) x; - array ---------- - {2,3,4} - {3,4,5} - {4,5,6} -(3 rows) - --- --- test for bitwise integer aggregates --- -CREATE TEMPORARY TABLE bitwise_test( - i2 INT2, - i4 INT4, - i8 INT8, - i INTEGER, - x INT2, - y BIT(4) -); --- empty case -SELECT - BIT_AND(i2) AS "?", - BIT_OR(i4) AS "?", - BIT_XOR(i8) AS "?" -FROM bitwise_test; - ? | ? | ? ----+---+--- - | | -(1 row) - -COPY bitwise_test FROM STDIN NULL 'null'; -SELECT - BIT_AND(i2) AS "1", - BIT_AND(i4) AS "1", - BIT_AND(i8) AS "1", - BIT_AND(i) AS "?", - BIT_AND(x) AS "0", - BIT_AND(y) AS "0100", - BIT_OR(i2) AS "7", - BIT_OR(i4) AS "7", - BIT_OR(i8) AS "7", - BIT_OR(i) AS "?", - BIT_OR(x) AS "7", - BIT_OR(y) AS "1101", - BIT_XOR(i2) AS "5", - BIT_XOR(i4) AS "5", - BIT_XOR(i8) AS "5", - BIT_XOR(i) AS "?", - BIT_XOR(x) AS "7", - BIT_XOR(y) AS "1101" -FROM bitwise_test; - 1 | 1 | 1 | ? | 0 | 0100 | 7 | 7 | 7 | ? | 7 | 1101 | 5 | 5 | 5 | ? | 7 | 1101 ----+---+---+---+---+------+---+---+---+---+---+------+---+---+---+---+---+------ - 1 | 1 | 1 | 1 | 0 | 0100 | 7 | 7 | 7 | 3 | 7 | 1101 | 5 | 5 | 5 | 2 | 7 | 1101 -(1 row) - --- --- test boolean aggregates --- --- first test all possible transition and final states -SELECT - -- boolean and transitions - -- null because strict - booland_statefunc(NULL, NULL) IS NULL AS "t", - booland_statefunc(TRUE, NULL) IS NULL AS "t", - booland_statefunc(FALSE, NULL) IS NULL AS "t", - booland_statefunc(NULL, TRUE) IS NULL AS "t", - booland_statefunc(NULL, FALSE) IS NULL AS "t", - -- and actual computations - booland_statefunc(TRUE, TRUE) AS "t", - NOT booland_statefunc(TRUE, FALSE) AS "t", - NOT booland_statefunc(FALSE, TRUE) AS "t", - NOT booland_statefunc(FALSE, FALSE) AS "t"; - t | t | t | t | t | t | t | t | t ----+---+---+---+---+---+---+---+--- - t | t | t | t | t | t | t | t | t -(1 row) - -SELECT - -- boolean or transitions - -- null because strict - boolor_statefunc(NULL, NULL) IS NULL AS "t", - boolor_statefunc(TRUE, NULL) IS NULL AS "t", - boolor_statefunc(FALSE, NULL) IS NULL AS "t", - boolor_statefunc(NULL, TRUE) IS NULL AS "t", - boolor_statefunc(NULL, FALSE) IS NULL AS "t", - -- actual computations - boolor_statefunc(TRUE, TRUE) AS "t", - boolor_statefunc(TRUE, FALSE) AS "t", - boolor_statefunc(FALSE, TRUE) AS "t", - NOT boolor_statefunc(FALSE, FALSE) AS "t"; - t | t | t | t | t | t | t | t | t ----+---+---+---+---+---+---+---+--- - t | t | t | t | t | t | t | t | t -(1 row) - -CREATE TEMPORARY TABLE bool_test( - b1 BOOL, - b2 BOOL, - b3 BOOL, - b4 BOOL); --- empty case -SELECT - BOOL_AND(b1) AS "n", - BOOL_OR(b3) AS "n" -FROM bool_test; - n | n ----+--- - | -(1 row) - -COPY bool_test FROM STDIN NULL 'null'; -SELECT - BOOL_AND(b1) AS "f", - BOOL_AND(b2) AS "t", - BOOL_AND(b3) AS "f", - BOOL_AND(b4) AS "n", - BOOL_AND(NOT b2) AS "f", - BOOL_AND(NOT b3) AS "t" -FROM bool_test; - f | t | f | n | f | t ----+---+---+---+---+--- - f | t | f | | f | t -(1 row) - -SELECT - EVERY(b1) AS "f", - EVERY(b2) AS "t", - EVERY(b3) AS "f", - EVERY(b4) AS "n", - EVERY(NOT b2) AS "f", - EVERY(NOT b3) AS "t" -FROM bool_test; - f | t | f | n | f | t ----+---+---+---+---+--- - f | t | f | | f | t -(1 row) - -SELECT - BOOL_OR(b1) AS "t", - BOOL_OR(b2) AS "t", - BOOL_OR(b3) AS "f", - BOOL_OR(b4) AS "n", - BOOL_OR(NOT b2) AS "f", - BOOL_OR(NOT b3) AS "t" -FROM bool_test; - t | t | f | n | f | t ----+---+---+---+---+--- - t | t | f | | f | t -(1 row) - --- --- Test cases that should be optimized into indexscans instead of --- the generic aggregate implementation. --- --- Basic cases -explain (costs off) - select min(unique1) from tenk1; - QUERY PLAN ------------------------------------------------------------- - Result - InitPlan 1 - -> Limit - -> Index Only Scan using tenk1_unique1 on tenk1 - Index Cond: (unique1 IS NOT NULL) -(5 rows) - -select min(unique1) from tenk1; - min ------ - 0 -(1 row) - -explain (costs off) - select max(unique1) from tenk1; - QUERY PLAN ---------------------------------------------------------------------- - Result - InitPlan 1 - -> Limit - -> Index Only Scan Backward using tenk1_unique1 on tenk1 - Index Cond: (unique1 IS NOT NULL) -(5 rows) - -select max(unique1) from tenk1; - max ------- - 9999 -(1 row) - -explain (costs off) - select max(unique1) from tenk1 where unique1 < 42; - QUERY PLAN ------------------------------------------------------------------------- - Result - InitPlan 1 - -> Limit - -> Index Only Scan Backward using tenk1_unique1 on tenk1 - Index Cond: ((unique1 IS NOT NULL) AND (unique1 < 42)) -(5 rows) - -select max(unique1) from tenk1 where unique1 < 42; - max ------ - 41 -(1 row) - -explain (costs off) - select max(unique1) from tenk1 where unique1 > 42; - QUERY PLAN ------------------------------------------------------------------------- - Result - InitPlan 1 - -> Limit - -> Index Only Scan Backward using tenk1_unique1 on tenk1 - Index Cond: ((unique1 IS NOT NULL) AND (unique1 > 42)) -(5 rows) - -select max(unique1) from tenk1 where unique1 > 42; - max ------- - 9999 -(1 row) - --- the planner may choose a generic aggregate here if parallel query is --- enabled, since that plan will be parallel safe and the "optimized" --- plan, which has almost identical cost, will not be. we want to test --- the optimized plan, so temporarily disable parallel query. -begin; -set local max_parallel_workers_per_gather = 0; -explain (costs off) - select max(unique1) from tenk1 where unique1 > 42000; - QUERY PLAN ---------------------------------------------------------------------------- - Result - InitPlan 1 - -> Limit - -> Index Only Scan Backward using tenk1_unique1 on tenk1 - Index Cond: ((unique1 IS NOT NULL) AND (unique1 > 42000)) -(5 rows) - -select max(unique1) from tenk1 where unique1 > 42000; - max ------ - -(1 row) - -rollback; --- multi-column index (uses tenk1_thous_tenthous) -explain (costs off) - select max(tenthous) from tenk1 where thousand = 33; - QUERY PLAN ----------------------------------------------------------------------------- - Result - InitPlan 1 - -> Limit - -> Index Only Scan Backward using tenk1_thous_tenthous on tenk1 - Index Cond: ((thousand = 33) AND (tenthous IS NOT NULL)) -(5 rows) - -select max(tenthous) from tenk1 where thousand = 33; - max ------- - 9033 -(1 row) - -explain (costs off) - select min(tenthous) from tenk1 where thousand = 33; - QUERY PLAN --------------------------------------------------------------------------- - Result - InitPlan 1 - -> Limit - -> Index Only Scan using tenk1_thous_tenthous on tenk1 - Index Cond: ((thousand = 33) AND (tenthous IS NOT NULL)) -(5 rows) - -select min(tenthous) from tenk1 where thousand = 33; - min ------ - 33 -(1 row) - --- check parameter propagation into an indexscan subquery -explain (costs off) - select f1, (select min(unique1) from tenk1 where unique1 > f1) AS gt - from int4_tbl; - QUERY PLAN ------------------------------------------------------------------------------------------ - Seq Scan on int4_tbl - SubPlan 2 - -> Result - InitPlan 1 - -> Limit - -> Index Only Scan using tenk1_unique1 on tenk1 - Index Cond: ((unique1 IS NOT NULL) AND (unique1 > int4_tbl.f1)) -(7 rows) - -select f1, (select min(unique1) from tenk1 where unique1 > f1) AS gt - from int4_tbl; - f1 | gt --------------+---- - 0 | 1 - 123456 | - -123456 | 0 - 2147483647 | - -2147483647 | 0 -(5 rows) - --- check some cases that were handled incorrectly in 8.3.0 -explain (costs off) - select distinct max(unique2) from tenk1; - QUERY PLAN ---------------------------------------------------------------------- - HashAggregate - Group Key: (InitPlan 1).col1 - InitPlan 1 - -> Limit - -> Index Only Scan Backward using tenk1_unique2 on tenk1 - Index Cond: (unique2 IS NOT NULL) - -> Result -(7 rows) - -select distinct max(unique2) from tenk1; - max ------- - 9999 -(1 row) - -explain (costs off) - select max(unique2) from tenk1 order by 1; - QUERY PLAN ---------------------------------------------------------------------- - Sort - Sort Key: ((InitPlan 1).col1) - InitPlan 1 - -> Limit - -> Index Only Scan Backward using tenk1_unique2 on tenk1 - Index Cond: (unique2 IS NOT NULL) - -> Result -(7 rows) - -select max(unique2) from tenk1 order by 1; - max ------- - 9999 -(1 row) - -explain (costs off) - select max(unique2) from tenk1 order by max(unique2); - QUERY PLAN ---------------------------------------------------------------------- - Sort - Sort Key: ((InitPlan 1).col1) - InitPlan 1 - -> Limit - -> Index Only Scan Backward using tenk1_unique2 on tenk1 - Index Cond: (unique2 IS NOT NULL) - -> Result -(7 rows) - -select max(unique2) from tenk1 order by max(unique2); - max ------- - 9999 -(1 row) - -explain (costs off) - select max(unique2) from tenk1 order by max(unique2)+1; - QUERY PLAN ---------------------------------------------------------------------- - Sort - Sort Key: (((InitPlan 1).col1 + 1)) - InitPlan 1 - -> Limit - -> Index Only Scan Backward using tenk1_unique2 on tenk1 - Index Cond: (unique2 IS NOT NULL) - -> Result -(7 rows) - -select max(unique2) from tenk1 order by max(unique2)+1; - max ------- - 9999 -(1 row) - -explain (costs off) - select max(unique2), generate_series(1,3) as g from tenk1 order by g desc; - QUERY PLAN ---------------------------------------------------------------------- - Sort - Sort Key: (generate_series(1, 3)) DESC - InitPlan 1 - -> Limit - -> Index Only Scan Backward using tenk1_unique2 on tenk1 - Index Cond: (unique2 IS NOT NULL) - -> ProjectSet - -> Result -(8 rows) - -select max(unique2), generate_series(1,3) as g from tenk1 order by g desc; - max | g -------+--- - 9999 | 3 - 9999 | 2 - 9999 | 1 -(3 rows) - --- interesting corner case: constant gets optimized into a seqscan -explain (costs off) - select max(100) from tenk1; - QUERY PLAN ----------------------------------------------------- - Result - InitPlan 1 - -> Limit - -> Result - One-Time Filter: (100 IS NOT NULL) - -> Seq Scan on tenk1 -(6 rows) - -select max(100) from tenk1; - max ------ - 100 -(1 row) - --- try it on an inheritance tree -create table minmaxtest(f1 int); -create table minmaxtest1() inherits (minmaxtest); -create table minmaxtest2() inherits (minmaxtest); -create table minmaxtest3() inherits (minmaxtest); -create index minmaxtesti on minmaxtest(f1); -create index minmaxtest1i on minmaxtest1(f1); -create index minmaxtest2i on minmaxtest2(f1 desc); -create index minmaxtest3i on minmaxtest3(f1) where f1 is not null; -insert into minmaxtest values(11), (12); -insert into minmaxtest1 values(13), (14); -insert into minmaxtest2 values(15), (16); -insert into minmaxtest3 values(17), (18); -explain (costs off) - select min(f1), max(f1) from minmaxtest; - QUERY PLAN ---------------------------------------------------------------------------------------------- - Result - InitPlan 1 - -> Limit - -> Merge Append - Sort Key: minmaxtest.f1 - -> Index Only Scan using minmaxtesti on minmaxtest minmaxtest_1 - Index Cond: (f1 IS NOT NULL) - -> Index Only Scan using minmaxtest1i on minmaxtest1 minmaxtest_2 - Index Cond: (f1 IS NOT NULL) - -> Index Only Scan Backward using minmaxtest2i on minmaxtest2 minmaxtest_3 - Index Cond: (f1 IS NOT NULL) - -> Index Only Scan using minmaxtest3i on minmaxtest3 minmaxtest_4 - InitPlan 2 - -> Limit - -> Merge Append - Sort Key: minmaxtest_5.f1 DESC - -> Index Only Scan Backward using minmaxtesti on minmaxtest minmaxtest_6 - Index Cond: (f1 IS NOT NULL) - -> Index Only Scan Backward using minmaxtest1i on minmaxtest1 minmaxtest_7 - Index Cond: (f1 IS NOT NULL) - -> Index Only Scan using minmaxtest2i on minmaxtest2 minmaxtest_8 - Index Cond: (f1 IS NOT NULL) - -> Index Only Scan Backward using minmaxtest3i on minmaxtest3 minmaxtest_9 -(23 rows) - -select min(f1), max(f1) from minmaxtest; - min | max ------+----- - 11 | 18 -(1 row) - --- DISTINCT doesn't do anything useful here, but it shouldn't fail -explain (costs off) - select distinct min(f1), max(f1) from minmaxtest; - QUERY PLAN ---------------------------------------------------------------------------------------------- - Unique - InitPlan 1 - -> Limit - -> Merge Append - Sort Key: minmaxtest.f1 - -> Index Only Scan using minmaxtesti on minmaxtest minmaxtest_1 - Index Cond: (f1 IS NOT NULL) - -> Index Only Scan using minmaxtest1i on minmaxtest1 minmaxtest_2 - Index Cond: (f1 IS NOT NULL) - -> Index Only Scan Backward using minmaxtest2i on minmaxtest2 minmaxtest_3 - Index Cond: (f1 IS NOT NULL) - -> Index Only Scan using minmaxtest3i on minmaxtest3 minmaxtest_4 - InitPlan 2 - -> Limit - -> Merge Append - Sort Key: minmaxtest_5.f1 DESC - -> Index Only Scan Backward using minmaxtesti on minmaxtest minmaxtest_6 - Index Cond: (f1 IS NOT NULL) - -> Index Only Scan Backward using minmaxtest1i on minmaxtest1 minmaxtest_7 - Index Cond: (f1 IS NOT NULL) - -> Index Only Scan using minmaxtest2i on minmaxtest2 minmaxtest_8 - Index Cond: (f1 IS NOT NULL) - -> Index Only Scan Backward using minmaxtest3i on minmaxtest3 minmaxtest_9 - -> Sort - Sort Key: ((InitPlan 1).col1), ((InitPlan 2).col1) - -> Result -(26 rows) - -select distinct min(f1), max(f1) from minmaxtest; - min | max ------+----- - 11 | 18 -(1 row) - -drop table minmaxtest cascade; -NOTICE: drop cascades to 3 other objects -DETAIL: drop cascades to table minmaxtest1 -drop cascades to table minmaxtest2 -drop cascades to table minmaxtest3 --- DISTINCT can also trigger wrong answers with hash aggregation (bug #18465) -begin; -set local enable_sort = off; -explain (costs off) - select f1, (select distinct min(t1.f1) from int4_tbl t1 where t1.f1 = t0.f1) - from int4_tbl t0; - QUERY PLAN ---------------------------------------------------------------------- - Seq Scan on int4_tbl t0 - SubPlan 2 - -> HashAggregate - Group Key: (InitPlan 1).col1 - InitPlan 1 - -> Limit - -> Seq Scan on int4_tbl t1 - Filter: ((f1 IS NOT NULL) AND (f1 = t0.f1)) - -> Result -(9 rows) - -select f1, (select distinct min(t1.f1) from int4_tbl t1 where t1.f1 = t0.f1) -from int4_tbl t0; - f1 | min --------------+------------- - 0 | 0 - 123456 | 123456 - -123456 | -123456 - 2147483647 | 2147483647 - -2147483647 | -2147483647 -(5 rows) - -rollback; --- check for correct detection of nested-aggregate errors -select max(min(unique1)) from tenk1; -ERROR: aggregate function calls cannot be nested -LINE 1: select max(min(unique1)) from tenk1; - ^ -select (select max(min(unique1)) from int8_tbl) from tenk1; -ERROR: aggregate function calls cannot be nested -LINE 1: select (select max(min(unique1)) from int8_tbl) from tenk1; - ^ -select avg((select avg(a1.col1 order by (select avg(a2.col2) from tenk1 a3)) - from tenk1 a1(col1))) -from tenk1 a2(col2); -ERROR: aggregate function calls cannot be nested -LINE 1: select avg((select avg(a1.col1 order by (select avg(a2.col2)... - ^ --- --- Test removal of redundant GROUP BY columns --- -create temp table t1 (a int, b int, c int, d int, primary key (a, b)); -create temp table t2 (x int, y int, z int, primary key (x, y)); -create temp table t3 (a int, b int, c int, primary key(a, b) deferrable); --- Non-primary-key columns can be removed from GROUP BY -explain (costs off) select * from t1 group by a,b,c,d; - QUERY PLAN ----------------------- - HashAggregate - Group Key: a, b - -> Seq Scan on t1 -(3 rows) - --- No removal can happen if the complete PK is not present in GROUP BY -explain (costs off) select a,c from t1 group by a,c,d; - QUERY PLAN ----------------------- - HashAggregate - Group Key: a, c, d - -> Seq Scan on t1 -(3 rows) - --- Test removal across multiple relations -explain (costs off) select * -from t1 inner join t2 on t1.a = t2.x and t1.b = t2.y -group by t1.a,t1.b,t1.c,t1.d,t2.x,t2.y,t2.z; - QUERY PLAN ------------------------------------------------------- - HashAggregate - Group Key: t1.a, t1.b - -> Hash Join - Hash Cond: ((t2.x = t1.a) AND (t2.y = t1.b)) - -> Seq Scan on t2 - -> Hash - -> Seq Scan on t1 -(7 rows) - --- Test case where t1 can be optimized but not t2 -explain (costs off) select t1.*,t2.x,t2.z -from t1 inner join t2 on t1.a = t2.x and t1.b = t2.y -group by t1.a,t1.b,t1.c,t1.d,t2.x,t2.z; - QUERY PLAN ------------------------------------------------------- - HashAggregate - Group Key: t1.a, t1.b, t2.z - -> Hash Join - Hash Cond: ((t2.x = t1.a) AND (t2.y = t1.b)) - -> Seq Scan on t2 - -> Hash - -> Seq Scan on t1 -(7 rows) - --- Cannot optimize when PK is deferrable -explain (costs off) select * from t3 group by a,b,c; - QUERY PLAN ----------------------- - HashAggregate - Group Key: a, b, c - -> Seq Scan on t3 -(3 rows) - -create temp table t1c () inherits (t1); --- Ensure we don't remove any columns when t1 has a child table -explain (costs off) select * from t1 group by a,b,c,d; - QUERY PLAN -------------------------------------- - HashAggregate - Group Key: t1.a, t1.b, t1.c, t1.d - -> Append - -> Seq Scan on t1 t1_1 - -> Seq Scan on t1c t1_2 -(5 rows) - --- Okay to remove columns if we're only querying the parent. -explain (costs off) select * from only t1 group by a,b,c,d; - QUERY PLAN ----------------------- - HashAggregate - Group Key: a, b - -> Seq Scan on t1 -(3 rows) - -create temp table p_t1 ( - a int, - b int, - c int, - d int, - primary key(a,b) -) partition by list(a); -create temp table p_t1_1 partition of p_t1 for values in(1); -create temp table p_t1_2 partition of p_t1 for values in(2); --- Ensure we can remove non-PK columns for partitioned tables. -explain (costs off) select * from p_t1 group by a,b,c,d; - QUERY PLAN --------------------------------- - HashAggregate - Group Key: p_t1.a, p_t1.b - -> Append - -> Seq Scan on p_t1_1 - -> Seq Scan on p_t1_2 -(5 rows) - -drop table t1 cascade; -NOTICE: drop cascades to table t1c -drop table t2; -drop table t3; -drop table p_t1; --- --- Test GROUP BY matching of join columns that are type-coerced due to USING --- -create temp table t1(f1 int, f2 int); -create temp table t2(f1 bigint, f2 oid); -select f1 from t1 left join t2 using (f1) group by f1; - f1 ----- -(0 rows) - -select f1 from t1 left join t2 using (f1) group by t1.f1; - f1 ----- -(0 rows) - -select t1.f1 from t1 left join t2 using (f1) group by t1.f1; - f1 ----- -(0 rows) - --- only this one should fail: -select t1.f1 from t1 left join t2 using (f1) group by f1; -ERROR: column "t1.f1" must appear in the GROUP BY clause or be used in an aggregate function -LINE 1: select t1.f1 from t1 left join t2 using (f1) group by f1; - ^ --- check case where we have to inject nullingrels into coerced join alias -select f1, count(*) from -t1 x(x0,x1) left join (t1 left join t2 using(f1)) on (x0 = 0) -group by f1; - f1 | count -----+------- -(0 rows) - --- same, for a RelabelType coercion -select f2, count(*) from -t1 x(x0,x1) left join (t1 left join t2 using(f2)) on (x0 = 0) -group by f2; - f2 | count -----+------- -(0 rows) - -drop table t1, t2; --- --- Test planner's selection of pathkeys for ORDER BY aggregates --- --- Ensure we order by four. This suits the most aggregate functions. -explain (costs off) -select sum(two order by two),max(four order by four), min(four order by four) -from tenk1; - QUERY PLAN -------------------------------- - Aggregate - -> Sort - Sort Key: four - -> Seq Scan on tenk1 -(4 rows) - --- Ensure we order by two. It's a tie between ordering by two and four but --- we tiebreak on the aggregate's position. -explain (costs off) -select - sum(two order by two), max(four order by four), - min(four order by four), max(two order by two) -from tenk1; - QUERY PLAN -------------------------------- - Aggregate - -> Sort - Sort Key: two - -> Seq Scan on tenk1 -(4 rows) - --- Similar to above, but tiebreak on ordering by four -explain (costs off) -select - max(four order by four), sum(two order by two), - min(four order by four), max(two order by two) -from tenk1; - QUERY PLAN -------------------------------- - Aggregate - -> Sort - Sort Key: four - -> Seq Scan on tenk1 -(4 rows) - --- Ensure this one orders by ten since there are 3 aggregates that require ten --- vs two that suit two and four. -explain (costs off) -select - max(four order by four), sum(two order by two), - min(four order by four), max(two order by two), - sum(ten order by ten), min(ten order by ten), max(ten order by ten) -from tenk1; - QUERY PLAN -------------------------------- - Aggregate - -> Sort - Sort Key: ten - -> Seq Scan on tenk1 -(4 rows) - --- Try a case involving a GROUP BY clause where the GROUP BY column is also --- part of an aggregate's ORDER BY clause. We want a sort order that works --- for the GROUP BY along with the first and the last aggregate. -explain (costs off) -select - sum(unique1 order by ten, two), sum(unique1 order by four), - sum(unique1 order by two, four) -from tenk1 -group by ten; - QUERY PLAN ----------------------------------- - GroupAggregate - Group Key: ten - -> Sort - Sort Key: ten, two, four - -> Seq Scan on tenk1 -(5 rows) - --- Ensure that we never choose to provide presorted input to an Aggref with --- a volatile function in the ORDER BY / DISTINCT clause. We want to ensure --- these sorts are performed individually rather than at the query level. -explain (costs off) -select - sum(unique1 order by two), sum(unique1 order by four), - sum(unique1 order by four, two), sum(unique1 order by two, random()), - sum(unique1 order by two, random(), random() + 1) -from tenk1 -group by ten; - QUERY PLAN ----------------------------------- - GroupAggregate - Group Key: ten - -> Sort - Sort Key: ten, four, two - -> Seq Scan on tenk1 -(5 rows) - --- Ensure consecutive NULLs are properly treated as distinct from each other -select array_agg(distinct val) -from (select null as val from generate_series(1, 2)); - array_agg ------------ - {NULL} -(1 row) - --- Ensure no ordering is requested when enable_presorted_aggregate is off -set enable_presorted_aggregate to off; -explain (costs off) -select sum(two order by two) from tenk1; - QUERY PLAN -------------------------- - Aggregate - -> Seq Scan on tenk1 -(2 rows) - -reset enable_presorted_aggregate; --- --- Test combinations of DISTINCT and/or ORDER BY --- -select array_agg(a order by b) - from (values (1,4),(2,3),(3,1),(4,2)) v(a,b); - array_agg ------------ - {3,4,2,1} -(1 row) - -select array_agg(a order by a) - from (values (1,4),(2,3),(3,1),(4,2)) v(a,b); - array_agg ------------ - {1,2,3,4} -(1 row) - -select array_agg(a order by a desc) - from (values (1,4),(2,3),(3,1),(4,2)) v(a,b); - array_agg ------------ - {4,3,2,1} -(1 row) - -select array_agg(b order by a desc) - from (values (1,4),(2,3),(3,1),(4,2)) v(a,b); - array_agg ------------ - {2,1,3,4} -(1 row) - -select array_agg(distinct a) - from (values (1),(2),(1),(3),(null),(2)) v(a); - array_agg --------------- - {1,2,3,NULL} -(1 row) - -select array_agg(distinct a order by a) - from (values (1),(2),(1),(3),(null),(2)) v(a); - array_agg --------------- - {1,2,3,NULL} -(1 row) - -select array_agg(distinct a order by a desc) - from (values (1),(2),(1),(3),(null),(2)) v(a); - array_agg --------------- - {NULL,3,2,1} -(1 row) - -select array_agg(distinct a order by a desc nulls last) - from (values (1),(2),(1),(3),(null),(2)) v(a); - array_agg --------------- - {3,2,1,NULL} -(1 row) - --- multi-arg aggs, strict/nonstrict, distinct/order by -select aggfstr(a,b,c) - from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c); - aggfstr ---------------------------------------- - {"(1,3,foo)","(2,2,bar)","(3,1,baz)"} -(1 row) - -select aggfns(a,b,c) - from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c); - aggfns ------------------------------------------------ - {"(1,3,foo)","(0,,)","(2,2,bar)","(3,1,baz)"} -(1 row) - -select aggfstr(distinct a,b,c) - from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c), - generate_series(1,3) i; - aggfstr ---------------------------------------- - {"(1,3,foo)","(2,2,bar)","(3,1,baz)"} -(1 row) - -select aggfns(distinct a,b,c) - from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c), - generate_series(1,3) i; - aggfns ------------------------------------------------ - {"(0,,)","(1,3,foo)","(2,2,bar)","(3,1,baz)"} -(1 row) - -select aggfstr(distinct a,b,c order by b) - from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c), - generate_series(1,3) i; - aggfstr ---------------------------------------- - {"(3,1,baz)","(2,2,bar)","(1,3,foo)"} -(1 row) - -select aggfns(distinct a,b,c order by b) - from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c), - generate_series(1,3) i; - aggfns ------------------------------------------------ - {"(3,1,baz)","(2,2,bar)","(1,3,foo)","(0,,)"} -(1 row) - --- test specific code paths -select aggfns(distinct a,a,c order by c using ~<~,a) - from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c), - generate_series(1,2) i; - aggfns ------------------------------------------------- - {"(2,2,bar)","(3,3,baz)","(1,1,foo)","(0,0,)"} -(1 row) - -select aggfns(distinct a,a,c order by c using ~<~) - from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c), - generate_series(1,2) i; - aggfns ------------------------------------------------- - {"(2,2,bar)","(3,3,baz)","(1,1,foo)","(0,0,)"} -(1 row) - -select aggfns(distinct a,a,c order by a) - from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c), - generate_series(1,2) i; - aggfns ------------------------------------------------- - {"(0,0,)","(1,1,foo)","(2,2,bar)","(3,3,baz)"} -(1 row) - -select aggfns(distinct a,b,c order by a,c using ~<~,b) - from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c), - generate_series(1,2) i; - aggfns ------------------------------------------------ - {"(0,,)","(1,3,foo)","(2,2,bar)","(3,1,baz)"} -(1 row) - --- test a more complex permutation that has previous caused issues -select - string_agg(distinct 'a', ','), - sum(( - select sum(1) - from (values(1)) b(id) - where a.id = b.id -)) from unnest(array[1]) a(id); - string_agg | sum -------------+----- - a | 1 -(1 row) - --- check node I/O via view creation and usage, also deparsing logic -create view agg_view1 as - select aggfns(a,b,c) - from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c); -select * from agg_view1; - aggfns ------------------------------------------------ - {"(1,3,foo)","(0,,)","(2,2,bar)","(3,1,baz)"} -(1 row) - -select pg_get_viewdef('agg_view1'::regclass); - pg_get_viewdef ---------------------------------------------------------------------------------------------------------------------- - SELECT aggfns(a, b, c) AS aggfns + - FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c); -(1 row) - -create or replace view agg_view1 as - select aggfns(distinct a,b,c) - from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c), - generate_series(1,3) i; -select * from agg_view1; - aggfns ------------------------------------------------ - {"(0,,)","(1,3,foo)","(2,2,bar)","(3,1,baz)"} -(1 row) - -select pg_get_viewdef('agg_view1'::regclass); - pg_get_viewdef ---------------------------------------------------------------------------------------------------------------------- - SELECT aggfns(DISTINCT v.a, v.b, v.c) AS aggfns + - FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c),+ - generate_series(1, 3) i(i); -(1 row) - -create or replace view agg_view1 as - select aggfns(distinct a,b,c order by b) - from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c), - generate_series(1,3) i; -select * from agg_view1; - aggfns ------------------------------------------------ - {"(3,1,baz)","(2,2,bar)","(1,3,foo)","(0,,)"} -(1 row) - -select pg_get_viewdef('agg_view1'::regclass); - pg_get_viewdef ---------------------------------------------------------------------------------------------------------------------- - SELECT aggfns(DISTINCT v.a, v.b, v.c ORDER BY v.b) AS aggfns + - FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c),+ - generate_series(1, 3) i(i); -(1 row) - -create or replace view agg_view1 as - select aggfns(a,b,c order by b+1) - from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c); -select * from agg_view1; - aggfns ------------------------------------------------ - {"(3,1,baz)","(2,2,bar)","(1,3,foo)","(0,,)"} -(1 row) - -select pg_get_viewdef('agg_view1'::regclass); - pg_get_viewdef ---------------------------------------------------------------------------------------------------------------------- - SELECT aggfns(a, b, c ORDER BY (b + 1)) AS aggfns + - FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c); -(1 row) - -create or replace view agg_view1 as - select aggfns(a,a,c order by b) - from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c); -select * from agg_view1; - aggfns ------------------------------------------------- - {"(3,3,baz)","(2,2,bar)","(1,1,foo)","(0,0,)"} -(1 row) - -select pg_get_viewdef('agg_view1'::regclass); - pg_get_viewdef ---------------------------------------------------------------------------------------------------------------------- - SELECT aggfns(a, a, c ORDER BY b) AS aggfns + - FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c); -(1 row) - -create or replace view agg_view1 as - select aggfns(a,b,c order by c using ~<~) - from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c); -select * from agg_view1; - aggfns ------------------------------------------------ - {"(2,2,bar)","(3,1,baz)","(1,3,foo)","(0,,)"} -(1 row) - -select pg_get_viewdef('agg_view1'::regclass); - pg_get_viewdef ---------------------------------------------------------------------------------------------------------------------- - SELECT aggfns(a, b, c ORDER BY c USING ~<~ NULLS LAST) AS aggfns + - FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c); -(1 row) - -create or replace view agg_view1 as - select aggfns(distinct a,b,c order by a,c using ~<~,b) - from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c), - generate_series(1,2) i; -select * from agg_view1; - aggfns ------------------------------------------------ - {"(0,,)","(1,3,foo)","(2,2,bar)","(3,1,baz)"} -(1 row) - -select pg_get_viewdef('agg_view1'::regclass); - pg_get_viewdef ---------------------------------------------------------------------------------------------------------------------- - SELECT aggfns(DISTINCT v.a, v.b, v.c ORDER BY v.a, v.c USING ~<~ NULLS LAST, v.b) AS aggfns + - FROM ( VALUES (1,3,'foo'::text), (0,NULL::integer,NULL::text), (2,2,'bar'::text), (3,1,'baz'::text)) v(a, b, c),+ - generate_series(1, 2) i(i); -(1 row) - -drop view agg_view1; --- incorrect DISTINCT usage errors -select aggfns(distinct a,b,c order by i) - from (values (1,1,'foo')) v(a,b,c), generate_series(1,2) i; -ERROR: in an aggregate with DISTINCT, ORDER BY expressions must appear in argument list -LINE 1: select aggfns(distinct a,b,c order by i) - ^ -select aggfns(distinct a,b,c order by a,b+1) - from (values (1,1,'foo')) v(a,b,c), generate_series(1,2) i; -ERROR: in an aggregate with DISTINCT, ORDER BY expressions must appear in argument list -LINE 1: select aggfns(distinct a,b,c order by a,b+1) - ^ -select aggfns(distinct a,b,c order by a,b,i,c) - from (values (1,1,'foo')) v(a,b,c), generate_series(1,2) i; -ERROR: in an aggregate with DISTINCT, ORDER BY expressions must appear in argument list -LINE 1: select aggfns(distinct a,b,c order by a,b,i,c) - ^ -select aggfns(distinct a,a,c order by a,b) - from (values (1,1,'foo')) v(a,b,c), generate_series(1,2) i; -ERROR: in an aggregate with DISTINCT, ORDER BY expressions must appear in argument list -LINE 1: select aggfns(distinct a,a,c order by a,b) - ^ --- string_agg tests -select string_agg(a,',') from (values('aaaa'),('bbbb'),('cccc')) g(a); - string_agg ----------------- - aaaa,bbbb,cccc -(1 row) - -select string_agg(a,',') from (values('aaaa'),(null),('bbbb'),('cccc')) g(a); - string_agg ----------------- - aaaa,bbbb,cccc -(1 row) - -select string_agg(a,'AB') from (values(null),(null),('bbbb'),('cccc')) g(a); - string_agg ------------- - bbbbABcccc -(1 row) - -select string_agg(a,',') from (values(null),(null)) g(a); - string_agg ------------- - -(1 row) - --- check some implicit casting cases, as per bug #5564 -select string_agg(distinct f1, ',' order by f1) from varchar_tbl; -- ok - string_agg ------------- - a,ab,abcd -(1 row) - -select string_agg(distinct f1::text, ',' order by f1) from varchar_tbl; -- not ok -ERROR: in an aggregate with DISTINCT, ORDER BY expressions must appear in argument list -LINE 1: select string_agg(distinct f1::text, ',' order by f1) from v... - ^ -select string_agg(distinct f1, ',' order by f1::text) from varchar_tbl; -- not ok -ERROR: in an aggregate with DISTINCT, ORDER BY expressions must appear in argument list -LINE 1: select string_agg(distinct f1, ',' order by f1::text) from v... - ^ -select string_agg(distinct f1::text, ',' order by f1::text) from varchar_tbl; -- ok - string_agg ------------- - a,ab,abcd -(1 row) - --- string_agg bytea tests -create table bytea_test_table(v bytea); -select string_agg(v, '') from bytea_test_table; - string_agg ------------- - -(1 row) - -insert into bytea_test_table values(decode('ff','hex')); -select string_agg(v, '') from bytea_test_table; - string_agg ------------- - \xff -(1 row) - -insert into bytea_test_table values(decode('aa','hex')); -select string_agg(v, '') from bytea_test_table; - string_agg ------------- - \xffaa -(1 row) - -select string_agg(v, NULL) from bytea_test_table; - string_agg ------------- - \xffaa -(1 row) - -select string_agg(v, decode('ee', 'hex')) from bytea_test_table; - string_agg ------------- - \xffeeaa -(1 row) - -drop table bytea_test_table; --- Test parallel string_agg and array_agg -create table pagg_test (x int, y int) with (autovacuum_enabled = off); -insert into pagg_test -select (case x % 4 when 1 then null else x end), x % 10 -from generate_series(1,5000) x; -set parallel_setup_cost TO 0; -set parallel_tuple_cost TO 0; -set parallel_leader_participation TO 0; -set min_parallel_table_scan_size = 0; -set bytea_output = 'escape'; -set max_parallel_workers_per_gather = 2; --- create a view as we otherwise have to repeat this query a few times. -create view v_pagg_test AS -select - y, - min(t) AS tmin,max(t) AS tmax,count(distinct t) AS tndistinct, - min(b) AS bmin,max(b) AS bmax,count(distinct b) AS bndistinct, - min(a) AS amin,max(a) AS amax,count(distinct a) AS andistinct, - min(aa) AS aamin,max(aa) AS aamax,count(distinct aa) AS aandistinct -from ( - select - y, - unnest(regexp_split_to_array(a1.t, ','))::int AS t, - unnest(regexp_split_to_array(a1.b::text, ',')) AS b, - unnest(a1.a) AS a, - unnest(a1.aa) AS aa - from ( - select - y, - string_agg(x::text, ',') AS t, - string_agg(x::text::bytea, ',') AS b, - array_agg(x) AS a, - array_agg(ARRAY[x]) AS aa - from pagg_test - group by y - ) a1 -) a2 -group by y; --- Ensure results are correct. -select * from v_pagg_test order by y; - y | tmin | tmax | tndistinct | bmin | bmax | bndistinct | amin | amax | andistinct | aamin | aamax | aandistinct ----+------+------+------------+------+------+------------+------+------+------------+-------+-------+------------- - 0 | 10 | 5000 | 500 | 10 | 990 | 500 | 10 | 5000 | 500 | 10 | 5000 | 500 - 1 | 11 | 4991 | 250 | 1011 | 991 | 250 | 11 | 4991 | 250 | 11 | 4991 | 250 - 2 | 2 | 4992 | 500 | 1002 | 992 | 500 | 2 | 4992 | 500 | 2 | 4992 | 500 - 3 | 3 | 4983 | 250 | 1003 | 983 | 250 | 3 | 4983 | 250 | 3 | 4983 | 250 - 4 | 4 | 4994 | 500 | 1004 | 994 | 500 | 4 | 4994 | 500 | 4 | 4994 | 500 - 5 | 15 | 4995 | 250 | 1015 | 995 | 250 | 15 | 4995 | 250 | 15 | 4995 | 250 - 6 | 6 | 4996 | 500 | 1006 | 996 | 500 | 6 | 4996 | 500 | 6 | 4996 | 500 - 7 | 7 | 4987 | 250 | 1007 | 987 | 250 | 7 | 4987 | 250 | 7 | 4987 | 250 - 8 | 8 | 4998 | 500 | 1008 | 998 | 500 | 8 | 4998 | 500 | 8 | 4998 | 500 - 9 | 19 | 4999 | 250 | 1019 | 999 | 250 | 19 | 4999 | 250 | 19 | 4999 | 250 -(10 rows) - --- Ensure parallel aggregation is actually being used. -explain (costs off) select * from v_pagg_test order by y; - QUERY PLAN --------------------------------------------------------------------------------------------------------------------------------------- - GroupAggregate - Group Key: pagg_test.y - -> Sort - Sort Key: pagg_test.y, (((unnest(regexp_split_to_array((string_agg((pagg_test.x)::text, ','::text)), ','::text))))::integer) - -> Result - -> ProjectSet - -> Finalize HashAggregate - Group Key: pagg_test.y - -> Gather - Workers Planned: 2 - -> Partial HashAggregate - Group Key: pagg_test.y - -> Parallel Seq Scan on pagg_test -(13 rows) - -set max_parallel_workers_per_gather = 0; --- Ensure results are the same without parallel aggregation. -select * from v_pagg_test order by y; - y | tmin | tmax | tndistinct | bmin | bmax | bndistinct | amin | amax | andistinct | aamin | aamax | aandistinct ----+------+------+------------+------+------+------------+------+------+------------+-------+-------+------------- - 0 | 10 | 5000 | 500 | 10 | 990 | 500 | 10 | 5000 | 500 | 10 | 5000 | 500 - 1 | 11 | 4991 | 250 | 1011 | 991 | 250 | 11 | 4991 | 250 | 11 | 4991 | 250 - 2 | 2 | 4992 | 500 | 1002 | 992 | 500 | 2 | 4992 | 500 | 2 | 4992 | 500 - 3 | 3 | 4983 | 250 | 1003 | 983 | 250 | 3 | 4983 | 250 | 3 | 4983 | 250 - 4 | 4 | 4994 | 500 | 1004 | 994 | 500 | 4 | 4994 | 500 | 4 | 4994 | 500 - 5 | 15 | 4995 | 250 | 1015 | 995 | 250 | 15 | 4995 | 250 | 15 | 4995 | 250 - 6 | 6 | 4996 | 500 | 1006 | 996 | 500 | 6 | 4996 | 500 | 6 | 4996 | 500 - 7 | 7 | 4987 | 250 | 1007 | 987 | 250 | 7 | 4987 | 250 | 7 | 4987 | 250 - 8 | 8 | 4998 | 500 | 1008 | 998 | 500 | 8 | 4998 | 500 | 8 | 4998 | 500 - 9 | 19 | 4999 | 250 | 1019 | 999 | 250 | 19 | 4999 | 250 | 19 | 4999 | 250 -(10 rows) - --- Clean up -reset max_parallel_workers_per_gather; -reset bytea_output; -reset min_parallel_table_scan_size; -reset parallel_leader_participation; -reset parallel_tuple_cost; -reset parallel_setup_cost; -drop view v_pagg_test; -drop table pagg_test; --- FILTER tests -select min(unique1) filter (where unique1 > 100) from tenk1; - min ------ - 101 -(1 row) - -select sum(1/ten) filter (where ten > 0) from tenk1; - sum ------- - 1000 -(1 row) - -select ten, sum(distinct four) filter (where four::text ~ '123') from onek a -group by ten; - ten | sum ------+----- - 0 | - 1 | - 2 | - 3 | - 4 | - 5 | - 6 | - 7 | - 8 | - 9 | -(10 rows) - -select ten, sum(distinct four) filter (where four > 10) from onek a -group by ten -having exists (select 1 from onek b where sum(distinct a.four) = b.four); - ten | sum ------+----- - 0 | - 2 | - 4 | - 6 | - 8 | -(5 rows) - -select max(foo COLLATE "C") filter (where (bar collate "POSIX") > '0') -from (values ('a', 'b')) AS v(foo,bar); - max ------ - a -(1 row) - -select any_value(v) filter (where v > 2) from (values (1), (2), (3)) as v (v); - any_value ------------ - 3 -(1 row) - --- outer reference in FILTER (PostgreSQL extension) -select (select count(*) - from (values (1)) t0(inner_c)) -from (values (2),(3)) t1(outer_c); -- inner query is aggregation query - count -------- - 1 - 1 -(2 rows) - -select (select count(*) filter (where outer_c <> 0) - from (values (1)) t0(inner_c)) -from (values (2),(3)) t1(outer_c); -- outer query is aggregation query - count -------- - 2 -(1 row) - -select (select count(inner_c) filter (where outer_c <> 0) - from (values (1)) t0(inner_c)) -from (values (2),(3)) t1(outer_c); -- inner query is aggregation query - count -------- - 1 - 1 -(2 rows) - -select - (select max((select i.unique2 from tenk1 i where i.unique1 = o.unique1)) - filter (where o.unique1 < 10)) -from tenk1 o; -- outer query is aggregation query - max ------- - 9998 -(1 row) - --- subquery in FILTER clause (PostgreSQL extension) -select sum(unique1) FILTER (WHERE - unique1 IN (SELECT unique1 FROM onek where unique1 < 100)) FROM tenk1; - sum ------- - 4950 -(1 row) - --- exercise lots of aggregate parts with FILTER -select aggfns(distinct a,b,c order by a,c using ~<~,b) filter (where a > 1) - from (values (1,3,'foo'),(0,null,null),(2,2,'bar'),(3,1,'baz')) v(a,b,c), - generate_series(1,2) i; - aggfns ---------------------------- - {"(2,2,bar)","(3,1,baz)"} -(1 row) - --- check handling of bare boolean Var in FILTER -select max(0) filter (where b1) from bool_test; - max ------ - 0 -(1 row) - -select (select max(0) filter (where b1)) from bool_test; - max ------ - 0 -(1 row) - --- check for correct detection of nested-aggregate errors in FILTER -select max(unique1) filter (where sum(ten) > 0) from tenk1; -ERROR: aggregate functions are not allowed in FILTER -LINE 1: select max(unique1) filter (where sum(ten) > 0) from tenk1; - ^ -select (select max(unique1) filter (where sum(ten) > 0) from int8_tbl) from tenk1; -ERROR: aggregate function calls cannot be nested -LINE 1: select (select max(unique1) filter (where sum(ten) > 0) from... - ^ -select max(unique1) filter (where bool_or(ten > 0)) from tenk1; -ERROR: aggregate functions are not allowed in FILTER -LINE 1: select max(unique1) filter (where bool_or(ten > 0)) from ten... - ^ -select (select max(unique1) filter (where bool_or(ten > 0)) from int8_tbl) from tenk1; -ERROR: aggregate function calls cannot be nested -LINE 1: select (select max(unique1) filter (where bool_or(ten > 0)) ... - ^ --- ordered-set aggregates -select p, percentile_cont(p) within group (order by x::float8) -from generate_series(1,5) x, - (values (0::float8),(0.1),(0.25),(0.4),(0.5),(0.6),(0.75),(0.9),(1)) v(p) -group by p order by p; - p | percentile_cont -------+----------------- - 0 | 1 - 0.1 | 1.4 - 0.25 | 2 - 0.4 | 2.6 - 0.5 | 3 - 0.6 | 3.4 - 0.75 | 4 - 0.9 | 4.6 - 1 | 5 -(9 rows) - -select p, percentile_cont(p order by p) within group (order by x) -- error -from generate_series(1,5) x, - (values (0::float8),(0.1),(0.25),(0.4),(0.5),(0.6),(0.75),(0.9),(1)) v(p) -group by p order by p; -ERROR: cannot use multiple ORDER BY clauses with WITHIN GROUP -LINE 1: select p, percentile_cont(p order by p) within group (order ... - ^ -select p, sum() within group (order by x::float8) -- error -from generate_series(1,5) x, - (values (0::float8),(0.1),(0.25),(0.4),(0.5),(0.6),(0.75),(0.9),(1)) v(p) -group by p order by p; -ERROR: sum is not an ordered-set aggregate, so it cannot have WITHIN GROUP -LINE 1: select p, sum() within group (order by x::float8) -- error - ^ -select p, percentile_cont(p,p) -- error -from generate_series(1,5) x, - (values (0::float8),(0.1),(0.25),(0.4),(0.5),(0.6),(0.75),(0.9),(1)) v(p) -group by p order by p; -ERROR: WITHIN GROUP is required for ordered-set aggregate percentile_cont -LINE 1: select p, percentile_cont(p,p) -- error - ^ -select percentile_cont(0.5) within group (order by b) from aggtest; - percentile_cont ------------------- - 53.4485001564026 -(1 row) - -select percentile_cont(0.5) within group (order by b), sum(b) from aggtest; - percentile_cont | sum -------------------+--------- - 53.4485001564026 | 431.773 -(1 row) - -select percentile_cont(0.5) within group (order by thousand) from tenk1; - percentile_cont ------------------ - 499.5 -(1 row) - -select percentile_disc(0.5) within group (order by thousand) from tenk1; - percentile_disc ------------------ - 499 -(1 row) - -select rank(3) within group (order by x) -from (values (1),(1),(2),(2),(3),(3),(4)) v(x); - rank ------- - 5 -(1 row) - -select cume_dist(3) within group (order by x) -from (values (1),(1),(2),(2),(3),(3),(4)) v(x); - cume_dist ------------ - 0.875 -(1 row) - -select percent_rank(3) within group (order by x) -from (values (1),(1),(2),(2),(3),(3),(4),(5)) v(x); - percent_rank --------------- - 0.5 -(1 row) - -select dense_rank(3) within group (order by x) -from (values (1),(1),(2),(2),(3),(3),(4)) v(x); - dense_rank ------------- - 3 -(1 row) - -select percentile_disc(array[0,0.1,0.25,0.5,0.75,0.9,1]) within group (order by thousand) -from tenk1; - percentile_disc ----------------------------- - {0,99,249,499,749,899,999} -(1 row) - -select percentile_cont(array[0,0.25,0.5,0.75,1]) within group (order by thousand) -from tenk1; - percentile_cont ------------------------------ - {0,249.75,499.5,749.25,999} -(1 row) - -select percentile_disc(array[[null,1,0.5],[0.75,0.25,null]]) within group (order by thousand) -from tenk1; - percentile_disc ---------------------------------- - {{NULL,999,499},{749,249,NULL}} -(1 row) - -select percentile_cont(array[0,1,0.25,0.75,0.5,1,0.3,0.32,0.35,0.38,0.4]) within group (order by x) -from generate_series(1,6) x; - percentile_cont ------------------------------------------- - {1,6,2.25,4.75,3.5,6,2.5,2.6,2.75,2.9,3} -(1 row) - -select ten, mode() within group (order by string4) from tenk1 group by ten; - ten | mode ------+-------- - 0 | HHHHxx - 1 | OOOOxx - 2 | VVVVxx - 3 | OOOOxx - 4 | HHHHxx - 5 | HHHHxx - 6 | OOOOxx - 7 | AAAAxx - 8 | VVVVxx - 9 | VVVVxx -(10 rows) - -select percentile_disc(array[0.25,0.5,0.75]) within group (order by x) -from unnest('{fred,jim,fred,jack,jill,fred,jill,jim,jim,sheila,jim,sheila}'::text[]) u(x); - percentile_disc ------------------ - {fred,jill,jim} -(1 row) - --- check collation propagates up in suitable cases: -select pg_collation_for(percentile_disc(1) within group (order by x collate "POSIX")) - from (values ('fred'),('jim')) v(x); - pg_collation_for ------------------- - "POSIX" -(1 row) - --- ordered-set aggs created with CREATE AGGREGATE -select test_rank(3) within group (order by x) -from (values (1),(1),(2),(2),(3),(3),(4)) v(x); - test_rank ------------ - 5 -(1 row) - -select test_percentile_disc(0.5) within group (order by thousand) from tenk1; - test_percentile_disc ----------------------- - 499 -(1 row) - --- ordered-set aggs can't use ungrouped vars in direct args: -select rank(x) within group (order by x) from generate_series(1,5) x; -ERROR: column "x.x" must appear in the GROUP BY clause or be used in an aggregate function -LINE 1: select rank(x) within group (order by x) from generate_serie... - ^ -DETAIL: Direct arguments of an ordered-set aggregate must use only grouped columns. --- outer-level agg can't use a grouped arg of a lower level, either: -select array(select percentile_disc(a) within group (order by x) - from (values (0.3),(0.7)) v(a) group by a) - from generate_series(1,5) g(x); -ERROR: outer-level aggregate cannot contain a lower-level variable in its direct arguments -LINE 1: select array(select percentile_disc(a) within group (order b... - ^ --- agg in the direct args is a grouping violation, too: -select rank(sum(x)) within group (order by x) from generate_series(1,5) x; -ERROR: aggregate function calls cannot be nested -LINE 1: select rank(sum(x)) within group (order by x) from generate_... - ^ --- hypothetical-set type unification and argument-count failures: -select rank(3) within group (order by x) from (values ('fred'),('jim')) v(x); -ERROR: WITHIN GROUP types text and integer cannot be matched -LINE 1: select rank(3) within group (order by x) from (values ('fred... - ^ -select rank(3) within group (order by stringu1,stringu2) from tenk1; -ERROR: function rank(integer, name, name) does not exist -LINE 1: select rank(3) within group (order by stringu1,stringu2) fro... - ^ -HINT: To use the hypothetical-set aggregate rank, the number of hypothetical direct arguments (here 1) must match the number of ordering columns (here 2). -select rank('fred') within group (order by x) from generate_series(1,5) x; -ERROR: invalid input syntax for type integer: "fred" -LINE 1: select rank('fred') within group (order by x) from generate_... - ^ -select rank('adam'::text collate "C") within group (order by x collate "POSIX") - from (values ('fred'),('jim')) v(x); -ERROR: collation mismatch between explicit collations "C" and "POSIX" -LINE 1: ...adam'::text collate "C") within group (order by x collate "P... - ^ --- hypothetical-set type unification successes: -select rank('adam'::varchar) within group (order by x) from (values ('fred'),('jim')) v(x); - rank ------- - 1 -(1 row) - -select rank('3') within group (order by x) from generate_series(1,5) x; - rank ------- - 3 -(1 row) - --- divide by zero check -select percent_rank(0) within group (order by x) from generate_series(1,0) x; - percent_rank --------------- - 0 -(1 row) - --- deparse and multiple features: -create view aggordview1 as -select ten, - percentile_disc(0.5) within group (order by thousand) as p50, - percentile_disc(0.5) within group (order by thousand) filter (where hundred=1) as px, - rank(5,'AZZZZ',50) within group (order by hundred, string4 desc, hundred) - from tenk1 - group by ten order by ten; -select pg_get_viewdef('aggordview1'); - pg_get_viewdef -------------------------------------------------------------------------------------------------------------------- - SELECT ten, + - percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY thousand) AS p50, + - percentile_disc((0.5)::double precision) WITHIN GROUP (ORDER BY thousand) FILTER (WHERE (hundred = 1)) AS px,+ - rank(5, 'AZZZZ'::name, 50) WITHIN GROUP (ORDER BY hundred, string4 DESC, hundred) AS rank + - FROM tenk1 + - GROUP BY ten + - ORDER BY ten; -(1 row) - -select * from aggordview1 order by ten; - ten | p50 | px | rank ------+-----+-----+------ - 0 | 490 | | 101 - 1 | 491 | 401 | 101 - 2 | 492 | | 101 - 3 | 493 | | 101 - 4 | 494 | | 101 - 5 | 495 | | 67 - 6 | 496 | | 1 - 7 | 497 | | 1 - 8 | 498 | | 1 - 9 | 499 | | 1 -(10 rows) - -drop view aggordview1; --- variadic aggregates -select least_agg(q1,q2) from int8_tbl; - least_agg -------------------- - -4567890123456789 -(1 row) - -select least_agg(variadic array[q1,q2]) from int8_tbl; - least_agg -------------------- - -4567890123456789 -(1 row) - -select cleast_agg(q1,q2) from int8_tbl; - cleast_agg -------------------- - -4567890123456789 -(1 row) - -select cleast_agg(4.5,f1) from int4_tbl; - cleast_agg -------------- - -2147483647 -(1 row) - -select cleast_agg(variadic array[4.5,f1]) from int4_tbl; - cleast_agg -------------- - -2147483647 -(1 row) - -select pg_typeof(cleast_agg(variadic array[4.5,f1])) from int4_tbl; - pg_typeof ------------ - numeric -(1 row) - --- test aggregates with common transition functions share the same states -begin work; -create type avg_state as (total bigint, count bigint); -create or replace function avg_transfn(state avg_state, n int) returns avg_state as -$$ -declare new_state avg_state; -begin - raise notice 'avg_transfn called with %', n; - if state is null then - if n is not null then - new_state.total := n; - new_state.count := 1; - return new_state; - end if; - return null; - elsif n is not null then - state.total := state.total + n; - state.count := state.count + 1; - return state; - end if; - - return null; -end -$$ language plpgsql; -create function avg_finalfn(state avg_state) returns int4 as -$$ -begin - if state is null then - return NULL; - else - return state.total / state.count; - end if; -end -$$ language plpgsql; -create function sum_finalfn(state avg_state) returns int4 as -$$ -begin - if state is null then - return NULL; - else - return state.total; - end if; -end -$$ language plpgsql; -create aggregate my_avg(int4) -( - stype = avg_state, - sfunc = avg_transfn, - finalfunc = avg_finalfn -); -create aggregate my_sum(int4) -( - stype = avg_state, - sfunc = avg_transfn, - finalfunc = sum_finalfn -); --- aggregate state should be shared as aggs are the same. -select my_avg(one),my_avg(one) from (values(1),(3)) t(one); -NOTICE: avg_transfn called with 1 -NOTICE: avg_transfn called with 3 - my_avg | my_avg ---------+-------- - 2 | 2 -(1 row) - --- aggregate state should be shared as transfn is the same for both aggs. -select my_avg(one),my_sum(one) from (values(1),(3)) t(one); -NOTICE: avg_transfn called with 1 -NOTICE: avg_transfn called with 3 - my_avg | my_sum ---------+-------- - 2 | 4 -(1 row) - --- same as previous one, but with DISTINCT, which requires sorting the input. -select my_avg(distinct one),my_sum(distinct one) from (values(1),(3),(1)) t(one); -NOTICE: avg_transfn called with 1 -NOTICE: avg_transfn called with 3 - my_avg | my_sum ---------+-------- - 2 | 4 -(1 row) - --- shouldn't share states due to the distinctness not matching. -select my_avg(distinct one),my_sum(one) from (values(1),(3)) t(one); -NOTICE: avg_transfn called with 1 -NOTICE: avg_transfn called with 1 -NOTICE: avg_transfn called with 3 -NOTICE: avg_transfn called with 3 - my_avg | my_sum ---------+-------- - 2 | 4 -(1 row) - --- shouldn't share states due to the filter clause not matching. -select my_avg(one) filter (where one > 1),my_sum(one) from (values(1),(3)) t(one); -NOTICE: avg_transfn called with 1 -NOTICE: avg_transfn called with 3 -NOTICE: avg_transfn called with 3 - my_avg | my_sum ---------+-------- - 3 | 4 -(1 row) - --- this should not share the state due to different input columns. -select my_avg(one),my_sum(two) from (values(1,2),(3,4)) t(one,two); -NOTICE: avg_transfn called with 1 -NOTICE: avg_transfn called with 2 -NOTICE: avg_transfn called with 3 -NOTICE: avg_transfn called with 4 - my_avg | my_sum ---------+-------- - 2 | 6 -(1 row) - --- exercise cases where OSAs share state -select - percentile_cont(0.5) within group (order by a), - percentile_disc(0.5) within group (order by a) -from (values(1::float8),(3),(5),(7)) t(a); - percentile_cont | percentile_disc ------------------+----------------- - 4 | 3 -(1 row) - -select - percentile_cont(0.25) within group (order by a), - percentile_disc(0.5) within group (order by a) -from (values(1::float8),(3),(5),(7)) t(a); - percentile_cont | percentile_disc ------------------+----------------- - 2.5 | 3 -(1 row) - --- these can't share state currently -select - rank(4) within group (order by a), - dense_rank(4) within group (order by a) -from (values(1),(3),(5),(7)) t(a); - rank | dense_rank -------+------------ - 3 | 3 -(1 row) - --- test that aggs with the same sfunc and initcond share the same agg state -create aggregate my_sum_init(int4) -( - stype = avg_state, - sfunc = avg_transfn, - finalfunc = sum_finalfn, - initcond = '(10,0)' -); -create aggregate my_avg_init(int4) -( - stype = avg_state, - sfunc = avg_transfn, - finalfunc = avg_finalfn, - initcond = '(10,0)' -); -create aggregate my_avg_init2(int4) -( - stype = avg_state, - sfunc = avg_transfn, - finalfunc = avg_finalfn, - initcond = '(4,0)' -); --- state should be shared if INITCONDs are matching -select my_sum_init(one),my_avg_init(one) from (values(1),(3)) t(one); -NOTICE: avg_transfn called with 1 -NOTICE: avg_transfn called with 3 - my_sum_init | my_avg_init --------------+------------- - 14 | 7 -(1 row) - --- Varying INITCONDs should cause the states not to be shared. -select my_sum_init(one),my_avg_init2(one) from (values(1),(3)) t(one); -NOTICE: avg_transfn called with 1 -NOTICE: avg_transfn called with 1 -NOTICE: avg_transfn called with 3 -NOTICE: avg_transfn called with 3 - my_sum_init | my_avg_init2 --------------+-------------- - 14 | 4 -(1 row) - -rollback; --- test aggregate state sharing to ensure it works if one aggregate has a --- finalfn and the other one has none. -begin work; -create or replace function sum_transfn(state int4, n int4) returns int4 as -$$ -declare new_state int4; -begin - raise notice 'sum_transfn called with %', n; - if state is null then - if n is not null then - new_state := n; - return new_state; - end if; - return null; - elsif n is not null then - state := state + n; - return state; - end if; - - return null; -end -$$ language plpgsql; -create function halfsum_finalfn(state int4) returns int4 as -$$ -begin - if state is null then - return NULL; - else - return state / 2; - end if; -end -$$ language plpgsql; -create aggregate my_sum(int4) -( - stype = int4, - sfunc = sum_transfn -); -create aggregate my_half_sum(int4) -( - stype = int4, - sfunc = sum_transfn, - finalfunc = halfsum_finalfn -); --- Agg state should be shared even though my_sum has no finalfn -select my_sum(one),my_half_sum(one) from (values(1),(2),(3),(4)) t(one); -NOTICE: sum_transfn called with 1 -NOTICE: sum_transfn called with 2 -NOTICE: sum_transfn called with 3 -NOTICE: sum_transfn called with 4 - my_sum | my_half_sum ---------+------------- - 10 | 5 -(1 row) - -rollback; --- test that the aggregate transition logic correctly handles --- transition / combine functions returning NULL --- First test the case of a normal transition function returning NULL -BEGIN; -CREATE FUNCTION balkifnull(int8, int4) -RETURNS int8 -STRICT -LANGUAGE plpgsql AS $$ -BEGIN - IF $1 IS NULL THEN - RAISE 'erroneously called with NULL argument'; - END IF; - RETURN NULL; -END$$; -CREATE AGGREGATE balk(int4) -( - SFUNC = balkifnull(int8, int4), - STYPE = int8, - PARALLEL = SAFE, - INITCOND = '0' -); -SELECT balk(hundred) FROM tenk1; - balk ------- - -(1 row) - -ROLLBACK; --- GROUP BY optimization by reordering GROUP BY clauses -CREATE TABLE btg AS SELECT - i % 10 AS x, - i % 10 AS y, - 'abc' || i % 10 AS z, - i AS w -FROM generate_series(1, 100) AS i; -CREATE INDEX btg_x_y_idx ON btg(x, y); -ANALYZE btg; -SET enable_hashagg = off; -SET enable_seqscan = off; --- Utilize the ordering of index scan to avoid a Sort operation -EXPLAIN (COSTS OFF) -SELECT count(*) FROM btg GROUP BY y, x; - QUERY PLAN ------------------------------------------------- - GroupAggregate - Group Key: x, y - -> Index Only Scan using btg_x_y_idx on btg -(3 rows) - --- Engage incremental sort -EXPLAIN (COSTS OFF) -SELECT count(*) FROM btg GROUP BY z, y, w, x; - QUERY PLAN -------------------------------------------------- - GroupAggregate - Group Key: x, y, z, w - -> Incremental Sort - Sort Key: x, y, z, w - Presorted Key: x, y - -> Index Scan using btg_x_y_idx on btg -(6 rows) - --- Utilize the ordering of subquery scan to avoid a Sort operation -EXPLAIN (COSTS OFF) SELECT count(*) -FROM (SELECT * FROM btg ORDER BY x, y, w, z) AS q1 -GROUP BY w, x, z, y; - QUERY PLAN -------------------------------------------------- - GroupAggregate - Group Key: btg.x, btg.y, btg.w, btg.z - -> Incremental Sort - Sort Key: btg.x, btg.y, btg.w, btg.z - Presorted Key: btg.x, btg.y - -> Index Scan using btg_x_y_idx on btg -(6 rows) - --- Utilize the ordering of merge join to avoid a full Sort operation -SET enable_hashjoin = off; -SET enable_nestloop = off; -EXPLAIN (COSTS OFF) -SELECT count(*) - FROM btg t1 JOIN btg t2 ON t1.z = t2.z AND t1.w = t2.w AND t1.x = t2.x - GROUP BY t1.x, t1.y, t1.z, t1.w; - QUERY PLAN -------------------------------------------------------------------------------- - GroupAggregate - Group Key: t1.z, t1.w, t1.x, t1.y - -> Incremental Sort - Sort Key: t1.z, t1.w, t1.x, t1.y - Presorted Key: t1.z, t1.w, t1.x - -> Merge Join - Merge Cond: ((t1.z = t2.z) AND (t1.w = t2.w) AND (t1.x = t2.x)) - -> Sort - Sort Key: t1.z, t1.w, t1.x - -> Index Scan using btg_x_y_idx on btg t1 - -> Sort - Sort Key: t2.z, t2.w, t2.x - -> Index Scan using btg_x_y_idx on btg t2 -(13 rows) - -RESET enable_nestloop; -RESET enable_hashjoin; --- Should work with and without GROUP-BY optimization -EXPLAIN (COSTS OFF) -SELECT count(*) FROM btg GROUP BY w, x, z, y ORDER BY y, x, z, w; - QUERY PLAN -------------------------------------------------- - GroupAggregate - Group Key: y, x, z, w - -> Sort - Sort Key: y, x, z, w - -> Index Scan using btg_x_y_idx on btg -(5 rows) - --- Utilize incremental sort to make the ORDER BY rule a bit cheaper -EXPLAIN (COSTS OFF) -SELECT count(*) FROM btg GROUP BY w, x, y, z ORDER BY x*x, z; - QUERY PLAN -------------------------------------------------------- - Sort - Sort Key: ((x * x)), z - -> GroupAggregate - Group Key: x, y, w, z - -> Incremental Sort - Sort Key: x, y, w, z - Presorted Key: x, y - -> Index Scan using btg_x_y_idx on btg -(8 rows) - --- Test the case where the number of incoming subtree path keys is more than --- the number of grouping keys. -CREATE INDEX btg_y_x_w_idx ON btg(y, x, w); -EXPLAIN (VERBOSE, COSTS OFF) -SELECT y, x, array_agg(distinct w) - FROM btg WHERE y < 0 GROUP BY x, y; - QUERY PLAN ---------------------------------------------------------- - GroupAggregate - Output: y, x, array_agg(DISTINCT w) - Group Key: btg.y, btg.x - -> Index Only Scan using btg_y_x_w_idx on public.btg - Output: y, x, w - Index Cond: (btg.y < 0) -(6 rows) - --- Ensure that we do not select the aggregate pathkeys instead of the grouping --- pathkeys -CREATE TABLE group_agg_pk AS SELECT - i % 10 AS x, - i % 2 AS y, - i % 2 AS z, - 2 AS w, - i % 10 AS f -FROM generate_series(1,100) AS i; -ANALYZE group_agg_pk; -SET enable_nestloop = off; -SET enable_hashjoin = off; -EXPLAIN (COSTS OFF) -SELECT avg(c1.f ORDER BY c1.x, c1.y) -FROM group_agg_pk c1 JOIN group_agg_pk c2 ON c1.x = c2.x -GROUP BY c1.w, c1.z; - QUERY PLAN ------------------------------------------------------ - GroupAggregate - Disabled Nodes: 2 - Group Key: c1.w, c1.z - -> Sort - Disabled Nodes: 2 - Sort Key: c1.w, c1.z, c1.x, c1.y - -> Merge Join - Disabled Nodes: 2 - Merge Cond: (c1.x = c2.x) - -> Sort - Sort Key: c1.x - -> Seq Scan on group_agg_pk c1 - Disabled Nodes: 1 - -> Sort - Sort Key: c2.x - -> Seq Scan on group_agg_pk c2 - Disabled Nodes: 1 -(17 rows) - -SELECT avg(c1.f ORDER BY c1.x, c1.y) -FROM group_agg_pk c1 JOIN group_agg_pk c2 ON c1.x = c2.x -GROUP BY c1.w, c1.z; - avg --------------------- - 4.0000000000000000 - 5.0000000000000000 -(2 rows) - --- Pathkeys, built in a subtree, can be used to optimize GROUP-BY clause --- ordering. Also, here we check that it doesn't depend on the initial clause --- order in the GROUP-BY list. -EXPLAIN (COSTS OFF) -SELECT c1.y,c1.x FROM group_agg_pk c1 - JOIN group_agg_pk c2 - ON c1.x = c2.x -GROUP BY c1.y,c1.x,c2.x; - QUERY PLAN ------------------------------------------------------ - Group - Disabled Nodes: 2 - Group Key: c1.x, c1.y - -> Incremental Sort - Disabled Nodes: 2 - Sort Key: c1.x, c1.y - Presorted Key: c1.x - -> Merge Join - Disabled Nodes: 2 - Merge Cond: (c1.x = c2.x) - -> Sort - Sort Key: c1.x - -> Seq Scan on group_agg_pk c1 - Disabled Nodes: 1 - -> Sort - Sort Key: c2.x - -> Seq Scan on group_agg_pk c2 - Disabled Nodes: 1 -(18 rows) - -EXPLAIN (COSTS OFF) -SELECT c1.y,c1.x FROM group_agg_pk c1 - JOIN group_agg_pk c2 - ON c1.x = c2.x -GROUP BY c1.y,c2.x,c1.x; - QUERY PLAN ------------------------------------------------------ - Group - Disabled Nodes: 2 - Group Key: c2.x, c1.y - -> Incremental Sort - Disabled Nodes: 2 - Sort Key: c2.x, c1.y - Presorted Key: c2.x - -> Merge Join - Disabled Nodes: 2 - Merge Cond: (c1.x = c2.x) - -> Sort - Sort Key: c1.x - -> Seq Scan on group_agg_pk c1 - Disabled Nodes: 1 - -> Sort - Sort Key: c2.x - -> Seq Scan on group_agg_pk c2 - Disabled Nodes: 1 -(18 rows) - -RESET enable_nestloop; -RESET enable_hashjoin; -DROP TABLE group_agg_pk; --- Test the case where the ordering of the scan matches the ordering within the --- aggregate but cannot be found in the group-by list -CREATE TABLE agg_sort_order (c1 int PRIMARY KEY, c2 int); -CREATE UNIQUE INDEX agg_sort_order_c2_idx ON agg_sort_order(c2); -INSERT INTO agg_sort_order SELECT i, i FROM generate_series(1,100)i; -ANALYZE agg_sort_order; -EXPLAIN (COSTS OFF) -SELECT array_agg(c1 ORDER BY c2),c2 -FROM agg_sort_order WHERE c2 < 100 GROUP BY c1 ORDER BY 2; - QUERY PLAN ----------------------------------------------------------------------------- - Sort - Sort Key: c2 - -> GroupAggregate - Group Key: c1 - -> Sort - Sort Key: c1, c2 - -> Index Scan using agg_sort_order_c2_idx on agg_sort_order - Index Cond: (c2 < 100) -(8 rows) - -DROP TABLE agg_sort_order CASCADE; -DROP TABLE btg; -RESET enable_hashagg; -RESET enable_seqscan; --- Secondly test the case of a parallel aggregate combiner function --- returning NULL. For that use normal transition function, but a --- combiner function returning NULL. -BEGIN; -CREATE FUNCTION balkifnull(int8, int8) -RETURNS int8 -PARALLEL SAFE -STRICT -LANGUAGE plpgsql AS $$ -BEGIN - IF $1 IS NULL THEN - RAISE 'erroneously called with NULL argument'; - END IF; - RETURN NULL; -END$$; -CREATE AGGREGATE balk(int4) -( - SFUNC = int4_sum(int8, int4), - STYPE = int8, - COMBINEFUNC = balkifnull(int8, int8), - PARALLEL = SAFE, - INITCOND = '0' -); --- force use of parallelism -ALTER TABLE tenk1 set (parallel_workers = 4); -SET LOCAL parallel_setup_cost=0; -SET LOCAL max_parallel_workers_per_gather=4; -EXPLAIN (COSTS OFF) SELECT balk(hundred) FROM tenk1; - QUERY PLAN -------------------------------------------------------------------------- - Finalize Aggregate - -> Gather - Workers Planned: 4 - -> Partial Aggregate - -> Parallel Index Only Scan using tenk1_hundred on tenk1 -(5 rows) - -SELECT balk(hundred) FROM tenk1; - balk ------- - -(1 row) - -ROLLBACK; --- test multiple usage of an aggregate whose finalfn returns a R/W datum -BEGIN; -CREATE FUNCTION rwagg_sfunc(x anyarray, y anyarray) RETURNS anyarray -LANGUAGE plpgsql IMMUTABLE AS $$ -BEGIN - RETURN array_fill(y[1], ARRAY[4]); -END; -$$; -CREATE FUNCTION rwagg_finalfunc(x anyarray) RETURNS anyarray -LANGUAGE plpgsql STRICT IMMUTABLE AS $$ -DECLARE - res x%TYPE; -BEGIN - -- assignment is essential for this test, it expands the array to R/W - res := array_fill(x[1], ARRAY[4]); - RETURN res; -END; -$$; -CREATE AGGREGATE rwagg(anyarray) ( - STYPE = anyarray, - SFUNC = rwagg_sfunc, - FINALFUNC = rwagg_finalfunc -); -CREATE FUNCTION eatarray(x real[]) RETURNS real[] -LANGUAGE plpgsql STRICT IMMUTABLE AS $$ -BEGIN - x[1] := x[1] + 1; - RETURN x; -END; -$$; -SELECT eatarray(rwagg(ARRAY[1.0::real])), eatarray(rwagg(ARRAY[1.0::real])); - eatarray | eatarray ------------+----------- - {2,1,1,1} | {2,1,1,1} -(1 row) - -ROLLBACK; --- test coverage for aggregate combine/serial/deserial functions -BEGIN; -SET parallel_setup_cost = 0; -SET parallel_tuple_cost = 0; -SET min_parallel_table_scan_size = 0; -SET max_parallel_workers_per_gather = 4; -SET parallel_leader_participation = off; -SET enable_indexonlyscan = off; --- variance(int4) covers numeric_poly_combine --- sum(int8) covers int8_avg_combine --- regr_count(float8, float8) covers int8inc_float8_float8 and aggregates with > 1 arg -EXPLAIN (COSTS OFF, VERBOSE) -SELECT variance(unique1::int4), sum(unique1::int8), regr_count(unique1::float8, unique1::float8) -FROM (SELECT * FROM tenk1 - UNION ALL SELECT * FROM tenk1 - UNION ALL SELECT * FROM tenk1 - UNION ALL SELECT * FROM tenk1) u; - QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - Finalize Aggregate - Output: variance(tenk1.unique1), sum((tenk1.unique1)::bigint), regr_count((tenk1.unique1)::double precision, (tenk1.unique1)::double precision) - -> Gather - Output: (PARTIAL variance(tenk1.unique1)), (PARTIAL sum((tenk1.unique1)::bigint)), (PARTIAL regr_count((tenk1.unique1)::double precision, (tenk1.unique1)::double precision)) - Workers Planned: 4 - -> Partial Aggregate - Output: PARTIAL variance(tenk1.unique1), PARTIAL sum((tenk1.unique1)::bigint), PARTIAL regr_count((tenk1.unique1)::double precision, (tenk1.unique1)::double precision) - -> Parallel Append - -> Parallel Seq Scan on public.tenk1 - Output: tenk1.unique1 - -> Parallel Seq Scan on public.tenk1 tenk1_1 - Output: tenk1_1.unique1 - -> Parallel Seq Scan on public.tenk1 tenk1_2 - Output: tenk1_2.unique1 - -> Parallel Seq Scan on public.tenk1 tenk1_3 - Output: tenk1_3.unique1 -(16 rows) - -SELECT variance(unique1::int4), sum(unique1::int8), regr_count(unique1::float8, unique1::float8) -FROM (SELECT * FROM tenk1 - UNION ALL SELECT * FROM tenk1 - UNION ALL SELECT * FROM tenk1 - UNION ALL SELECT * FROM tenk1) u; - variance | sum | regr_count -----------------------+-----------+------------ - 8333541.588539713493 | 199980000 | 40000 -(1 row) - --- variance(int8) covers numeric_combine --- avg(numeric) covers numeric_avg_combine -EXPLAIN (COSTS OFF, VERBOSE) -SELECT variance(unique1::int8), avg(unique1::numeric) -FROM (SELECT * FROM tenk1 - UNION ALL SELECT * FROM tenk1 - UNION ALL SELECT * FROM tenk1 - UNION ALL SELECT * FROM tenk1) u; - QUERY PLAN --------------------------------------------------------------------------------------------------------- - Finalize Aggregate - Output: variance((tenk1.unique1)::bigint), avg((tenk1.unique1)::numeric) - -> Gather - Output: (PARTIAL variance((tenk1.unique1)::bigint)), (PARTIAL avg((tenk1.unique1)::numeric)) - Workers Planned: 4 - -> Partial Aggregate - Output: PARTIAL variance((tenk1.unique1)::bigint), PARTIAL avg((tenk1.unique1)::numeric) - -> Parallel Append - -> Parallel Seq Scan on public.tenk1 - Output: tenk1.unique1 - -> Parallel Seq Scan on public.tenk1 tenk1_1 - Output: tenk1_1.unique1 - -> Parallel Seq Scan on public.tenk1 tenk1_2 - Output: tenk1_2.unique1 - -> Parallel Seq Scan on public.tenk1 tenk1_3 - Output: tenk1_3.unique1 -(16 rows) - -SELECT variance(unique1::int8), avg(unique1::numeric) -FROM (SELECT * FROM tenk1 - UNION ALL SELECT * FROM tenk1 - UNION ALL SELECT * FROM tenk1 - UNION ALL SELECT * FROM tenk1) u; - variance | avg -----------------------+----------------------- - 8333541.588539713493 | 4999.5000000000000000 -(1 row) - -ROLLBACK; --- test coverage for dense_rank -SELECT dense_rank(x) WITHIN GROUP (ORDER BY x) FROM (VALUES (1),(1),(2),(2),(3),(3)) v(x) GROUP BY (x) ORDER BY 1; - dense_rank ------------- - 1 - 1 - 1 -(3 rows) - --- Ensure that the STRICT checks for aggregates does not take NULLness --- of ORDER BY columns into account. See bug report around --- 2a505161-2727-2473-7c46-591ed108ac52@email.cz -SELECT min(x ORDER BY y) FROM (VALUES(1, NULL)) AS d(x,y); - min ------ - 1 -(1 row) - -SELECT min(x ORDER BY y) FROM (VALUES(1, 2)) AS d(x,y); - min ------ - 1 -(1 row) - --- check collation-sensitive matching between grouping expressions -select v||'a', case v||'a' when 'aa' then 1 else 0 end, count(*) - from unnest(array['a','b']) u(v) - group by v||'a' order by 1; - ?column? | case | count -----------+------+------- - aa | 1 | 1 - ba | 0 | 1 -(2 rows) - -select v||'a', case when v||'a' = 'aa' then 1 else 0 end, count(*) - from unnest(array['a','b']) u(v) - group by v||'a' order by 1; - ?column? | case | count -----------+------+------- - aa | 1 | 1 - ba | 0 | 1 -(2 rows) - --- Make sure that generation of HashAggregate for uniqification purposes --- does not lead to array overflow due to unexpected duplicate hash keys --- see CAFeeJoKKu0u+A_A9R9316djW-YW3-+Gtgvy3ju655qRHR3jtdA@mail.gmail.com -set enable_memoize to off; -explain (costs off) - select 1 from tenk1 - where (hundred, thousand) in (select twothousand, twothousand from onek); - QUERY PLAN -------------------------------------------------------------- - Hash Join - Hash Cond: (tenk1.hundred = onek.twothousand) - -> Seq Scan on tenk1 - Filter: (hundred = thousand) - -> Hash - -> HashAggregate - Group Key: onek.twothousand, onek.twothousand - -> Seq Scan on onek -(8 rows) - -reset enable_memoize; --- --- Hash Aggregation Spill tests --- -set enable_sort=false; -set work_mem='64kB'; -select unique1, count(*), sum(twothousand) from tenk1 -group by unique1 -having sum(fivethous) > 4975 -order by sum(twothousand); - unique1 | count | sum ----------+-------+------ - 4976 | 1 | 976 - 4977 | 1 | 977 - 4978 | 1 | 978 - 4979 | 1 | 979 - 4980 | 1 | 980 - 4981 | 1 | 981 - 4982 | 1 | 982 - 4983 | 1 | 983 - 4984 | 1 | 984 - 4985 | 1 | 985 - 4986 | 1 | 986 - 4987 | 1 | 987 - 4988 | 1 | 988 - 4989 | 1 | 989 - 4990 | 1 | 990 - 4991 | 1 | 991 - 4992 | 1 | 992 - 4993 | 1 | 993 - 4994 | 1 | 994 - 4995 | 1 | 995 - 4996 | 1 | 996 - 4997 | 1 | 997 - 4998 | 1 | 998 - 4999 | 1 | 999 - 9976 | 1 | 1976 - 9977 | 1 | 1977 - 9978 | 1 | 1978 - 9979 | 1 | 1979 - 9980 | 1 | 1980 - 9981 | 1 | 1981 - 9982 | 1 | 1982 - 9983 | 1 | 1983 - 9984 | 1 | 1984 - 9985 | 1 | 1985 - 9986 | 1 | 1986 - 9987 | 1 | 1987 - 9988 | 1 | 1988 - 9989 | 1 | 1989 - 9990 | 1 | 1990 - 9991 | 1 | 1991 - 9992 | 1 | 1992 - 9993 | 1 | 1993 - 9994 | 1 | 1994 - 9995 | 1 | 1995 - 9996 | 1 | 1996 - 9997 | 1 | 1997 - 9998 | 1 | 1998 - 9999 | 1 | 1999 -(48 rows) - -set work_mem to default; -set enable_sort to default; --- --- Compare results between plans using sorting and plans using hash --- aggregation. Force spilling in both cases by setting work_mem low. --- -set work_mem='64kB'; -create table agg_data_2k as -select g from generate_series(0, 1999) g; -analyze agg_data_2k; -create table agg_data_20k as -select g from generate_series(0, 19999) g; -analyze agg_data_20k; --- Produce results with sorting. -set enable_hashagg = false; -set jit_above_cost = 0; -explain (costs off) -select g%10000 as c1, sum(g::numeric) as c2, count(*) as c3 - from agg_data_20k group by g%10000; - QUERY PLAN --------------------------------------- - GroupAggregate - Group Key: ((g % 10000)) - -> Sort - Sort Key: ((g % 10000)) - -> Seq Scan on agg_data_20k -(5 rows) - -create table agg_group_1 as -select g%10000 as c1, sum(g::numeric) as c2, count(*) as c3 - from agg_data_20k group by g%10000; -create table agg_group_2 as -select * from - (values (100), (300), (500)) as r(a), - lateral ( - select (g/2)::numeric as c1, - array_agg(g::numeric) as c2, - count(*) as c3 - from agg_data_2k - where g < r.a - group by g/2) as s; -set jit_above_cost to default; -create table agg_group_3 as -select (g/2)::numeric as c1, sum(7::int4) as c2, count(*) as c3 - from agg_data_2k group by g/2; -create table agg_group_4 as -select (g/2)::numeric as c1, array_agg(g::numeric) as c2, count(*) as c3 - from agg_data_2k group by g/2; --- Produce results with hash aggregation -set enable_hashagg = true; -set enable_sort = false; -set jit_above_cost = 0; -explain (costs off) -select g%10000 as c1, sum(g::numeric) as c2, count(*) as c3 - from agg_data_20k group by g%10000; - QUERY PLAN --------------------------------- - HashAggregate - Group Key: (g % 10000) - -> Seq Scan on agg_data_20k -(3 rows) - -create table agg_hash_1 as -select g%10000 as c1, sum(g::numeric) as c2, count(*) as c3 - from agg_data_20k group by g%10000; -create table agg_hash_2 as -select * from - (values (100), (300), (500)) as r(a), - lateral ( - select (g/2)::numeric as c1, - array_agg(g::numeric) as c2, - count(*) as c3 - from agg_data_2k - where g < r.a - group by g/2) as s; -set jit_above_cost to default; -create table agg_hash_3 as -select (g/2)::numeric as c1, sum(7::int4) as c2, count(*) as c3 - from agg_data_2k group by g/2; -create table agg_hash_4 as -select (g/2)::numeric as c1, array_agg(g::numeric) as c2, count(*) as c3 - from agg_data_2k group by g/2; -set enable_sort = true; -set work_mem to default; --- Compare group aggregation results to hash aggregation results -(select * from agg_hash_1 except select * from agg_group_1) - union all -(select * from agg_group_1 except select * from agg_hash_1); - c1 | c2 | c3 -----+----+---- -(0 rows) - -(select * from agg_hash_2 except select * from agg_group_2) - union all -(select * from agg_group_2 except select * from agg_hash_2); - a | c1 | c2 | c3 ----+----+----+---- -(0 rows) - -(select * from agg_hash_3 except select * from agg_group_3) - union all -(select * from agg_group_3 except select * from agg_hash_3); - c1 | c2 | c3 -----+----+---- -(0 rows) - -(select * from agg_hash_4 except select * from agg_group_4) - union all -(select * from agg_group_4 except select * from agg_hash_4); - c1 | c2 | c3 -----+----+---- -(0 rows) - -drop table agg_group_1; -drop table agg_group_2; -drop table agg_group_3; -drop table agg_group_4; -drop table agg_hash_1; -drop table agg_hash_2; -drop table agg_hash_3; -drop table agg_hash_4; +psql: error: connection to server on socket "/tmp/pg_regress-03Az7d/.s.PGSQL.40051" failed: FATAL: the database system is not yet accepting connections +DETAIL: Consistent recovery state has not been yet reached. diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/transactions.out /tmp/cirrus-ci-build/build/testrun/regress/regress/results/transactions.out --- /tmp/cirrus-ci-build/src/test/regress/expected/transactions.out 2024-09-20 21:41:45.758024524 +0000 +++ /tmp/cirrus-ci-build/build/testrun/regress/regress/results/transactions.out 2024-09-20 21:46:05.497625080 +0000 @@ -1,1209 +1,2 @@ --- --- TRANSACTIONS --- -BEGIN; -CREATE TABLE xacttest (a smallint, b real); -INSERT INTO xacttest VALUES - (56, 7.8), - (100, 99.097), - (0, 0.09561), - (42, 324.78); -INSERT INTO xacttest (a, b) VALUES (777, 777.777); -END; --- should retrieve one value-- -SELECT a FROM xacttest WHERE a > 100; - a ------ - 777 -(1 row) - -BEGIN; -CREATE TABLE disappear (a int4); -DELETE FROM xacttest; --- should be empty -SELECT * FROM xacttest; - a | b ----+--- -(0 rows) - -ABORT; --- should not exist -SELECT oid FROM pg_class WHERE relname = 'disappear'; - oid ------ -(0 rows) - --- should have members again -SELECT * FROM xacttest; - a | b ------+--------- - 56 | 7.8 - 100 | 99.097 - 0 | 0.09561 - 42 | 324.78 - 777 | 777.777 -(5 rows) - --- Test that transaction characteristics cannot be reset. -BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; -SELECT COUNT(*) FROM xacttest; - count -------- - 5 -(1 row) - -RESET transaction_isolation; -- error -ERROR: parameter "transaction_isolation" cannot be reset -END; -BEGIN TRANSACTION READ ONLY; -SELECT COUNT(*) FROM xacttest; - count -------- - 5 -(1 row) - -RESET transaction_read_only; -- error -ERROR: parameter "transaction_read_only" cannot be reset -END; -BEGIN TRANSACTION DEFERRABLE; -SELECT COUNT(*) FROM xacttest; - count -------- - 5 -(1 row) - -RESET transaction_deferrable; -- error -ERROR: parameter "transaction_deferrable" cannot be reset -END; -CREATE FUNCTION errfunc() RETURNS int LANGUAGE SQL AS 'SELECT 1' -SET transaction_read_only = on; -- error -ERROR: parameter "transaction_read_only" cannot be set locally in functions --- Read-only tests -CREATE TABLE writetest (a int); -CREATE TEMPORARY TABLE temptest (a int); -BEGIN; -SET TRANSACTION ISOLATION LEVEL SERIALIZABLE, READ ONLY, DEFERRABLE; -- ok -SELECT * FROM writetest; -- ok - a ---- -(0 rows) - -SET TRANSACTION READ WRITE; --fail -ERROR: transaction read-write mode must be set before any query -COMMIT; -BEGIN; -SET TRANSACTION READ ONLY; -- ok -SET TRANSACTION READ WRITE; -- ok -SET TRANSACTION READ ONLY; -- ok -SELECT * FROM writetest; -- ok - a ---- -(0 rows) - -SAVEPOINT x; -SET TRANSACTION READ ONLY; -- ok -SELECT * FROM writetest; -- ok - a ---- -(0 rows) - -SET TRANSACTION READ ONLY; -- ok -SET TRANSACTION READ WRITE; --fail -ERROR: cannot set transaction read-write mode inside a read-only transaction -COMMIT; -BEGIN; -SET TRANSACTION READ WRITE; -- ok -SAVEPOINT x; -SET TRANSACTION READ WRITE; -- ok -SET TRANSACTION READ ONLY; -- ok -SELECT * FROM writetest; -- ok - a ---- -(0 rows) - -SET TRANSACTION READ ONLY; -- ok -SET TRANSACTION READ WRITE; --fail -ERROR: cannot set transaction read-write mode inside a read-only transaction -COMMIT; -BEGIN; -SET TRANSACTION READ WRITE; -- ok -SAVEPOINT x; -SET TRANSACTION READ ONLY; -- ok -SELECT * FROM writetest; -- ok - a ---- -(0 rows) - -ROLLBACK TO SAVEPOINT x; -SHOW transaction_read_only; -- off - transaction_read_only ------------------------ - off -(1 row) - -SAVEPOINT y; -SET TRANSACTION READ ONLY; -- ok -SELECT * FROM writetest; -- ok - a ---- -(0 rows) - -RELEASE SAVEPOINT y; -SHOW transaction_read_only; -- off - transaction_read_only ------------------------ - off -(1 row) - -COMMIT; -SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY; -DROP TABLE writetest; -- fail -ERROR: cannot execute DROP TABLE in a read-only transaction -INSERT INTO writetest VALUES (1); -- fail -ERROR: cannot execute INSERT in a read-only transaction -SELECT * FROM writetest; -- ok - a ---- -(0 rows) - -DELETE FROM temptest; -- ok -UPDATE temptest SET a = 0 FROM writetest WHERE temptest.a = 1 AND writetest.a = temptest.a; -- ok -PREPARE test AS UPDATE writetest SET a = 0; -- ok -EXECUTE test; -- fail -ERROR: cannot execute UPDATE in a read-only transaction -SELECT * FROM writetest, temptest; -- ok - a | a ----+--- -(0 rows) - -CREATE TABLE test AS SELECT * FROM writetest; -- fail -ERROR: cannot execute CREATE TABLE AS in a read-only transaction -START TRANSACTION READ WRITE; -DROP TABLE writetest; -- ok -COMMIT; --- Subtransactions, basic tests --- create & drop tables -SET SESSION CHARACTERISTICS AS TRANSACTION READ WRITE; -CREATE TABLE trans_foobar (a int); -BEGIN; - CREATE TABLE trans_foo (a int); - SAVEPOINT one; - DROP TABLE trans_foo; - CREATE TABLE trans_bar (a int); - ROLLBACK TO SAVEPOINT one; - RELEASE SAVEPOINT one; - SAVEPOINT two; - CREATE TABLE trans_baz (a int); - RELEASE SAVEPOINT two; - drop TABLE trans_foobar; - CREATE TABLE trans_barbaz (a int); -COMMIT; --- should exist: trans_barbaz, trans_baz, trans_foo -SELECT * FROM trans_foo; -- should be empty - a ---- -(0 rows) - -SELECT * FROM trans_bar; -- shouldn't exist -ERROR: relation "trans_bar" does not exist -LINE 1: SELECT * FROM trans_bar; - ^ -SELECT * FROM trans_barbaz; -- should be empty - a ---- -(0 rows) - -SELECT * FROM trans_baz; -- should be empty - a ---- -(0 rows) - --- inserts -BEGIN; - INSERT INTO trans_foo VALUES (1); - SAVEPOINT one; - INSERT into trans_bar VALUES (1); -ERROR: relation "trans_bar" does not exist -LINE 1: INSERT into trans_bar VALUES (1); - ^ - ROLLBACK TO one; - RELEASE SAVEPOINT one; - SAVEPOINT two; - INSERT into trans_barbaz VALUES (1); - RELEASE two; - SAVEPOINT three; - SAVEPOINT four; - INSERT INTO trans_foo VALUES (2); - RELEASE SAVEPOINT four; - ROLLBACK TO SAVEPOINT three; - RELEASE SAVEPOINT three; - INSERT INTO trans_foo VALUES (3); -COMMIT; -SELECT * FROM trans_foo; -- should have 1 and 3 - a ---- - 1 - 3 -(2 rows) - -SELECT * FROM trans_barbaz; -- should have 1 - a ---- - 1 -(1 row) - --- test whole-tree commit -BEGIN; - SAVEPOINT one; - SELECT trans_foo; -ERROR: column "trans_foo" does not exist -LINE 1: SELECT trans_foo; - ^ - ROLLBACK TO SAVEPOINT one; - RELEASE SAVEPOINT one; - SAVEPOINT two; - CREATE TABLE savepoints (a int); - SAVEPOINT three; - INSERT INTO savepoints VALUES (1); - SAVEPOINT four; - INSERT INTO savepoints VALUES (2); - SAVEPOINT five; - INSERT INTO savepoints VALUES (3); - ROLLBACK TO SAVEPOINT five; -COMMIT; -COMMIT; -- should not be in a transaction block -WARNING: there is no transaction in progress -SELECT * FROM savepoints; - a ---- - 1 - 2 -(2 rows) - --- test whole-tree rollback -BEGIN; - SAVEPOINT one; - DELETE FROM savepoints WHERE a=1; - RELEASE SAVEPOINT one; - SAVEPOINT two; - DELETE FROM savepoints WHERE a=1; - SAVEPOINT three; - DELETE FROM savepoints WHERE a=2; -ROLLBACK; -COMMIT; -- should not be in a transaction block -WARNING: there is no transaction in progress -SELECT * FROM savepoints; - a ---- - 1 - 2 -(2 rows) - --- test whole-tree commit on an aborted subtransaction -BEGIN; - INSERT INTO savepoints VALUES (4); - SAVEPOINT one; - INSERT INTO savepoints VALUES (5); - SELECT trans_foo; -ERROR: column "trans_foo" does not exist -LINE 1: SELECT trans_foo; - ^ -COMMIT; -SELECT * FROM savepoints; - a ---- - 1 - 2 -(2 rows) - -BEGIN; - INSERT INTO savepoints VALUES (6); - SAVEPOINT one; - INSERT INTO savepoints VALUES (7); - RELEASE SAVEPOINT one; - INSERT INTO savepoints VALUES (8); -COMMIT; --- rows 6 and 8 should have been created by the same xact -SELECT a.xmin = b.xmin FROM savepoints a, savepoints b WHERE a.a=6 AND b.a=8; - ?column? ----------- - t -(1 row) - --- rows 6 and 7 should have been created by different xacts -SELECT a.xmin = b.xmin FROM savepoints a, savepoints b WHERE a.a=6 AND b.a=7; - ?column? ----------- - f -(1 row) - -BEGIN; - INSERT INTO savepoints VALUES (9); - SAVEPOINT one; - INSERT INTO savepoints VALUES (10); - ROLLBACK TO SAVEPOINT one; - INSERT INTO savepoints VALUES (11); -COMMIT; -SELECT a FROM savepoints WHERE a in (9, 10, 11); - a ----- - 9 - 11 -(2 rows) - --- rows 9 and 11 should have been created by different xacts -SELECT a.xmin = b.xmin FROM savepoints a, savepoints b WHERE a.a=9 AND b.a=11; - ?column? ----------- - f -(1 row) - -BEGIN; - INSERT INTO savepoints VALUES (12); - SAVEPOINT one; - INSERT INTO savepoints VALUES (13); - SAVEPOINT two; - INSERT INTO savepoints VALUES (14); - ROLLBACK TO SAVEPOINT one; - INSERT INTO savepoints VALUES (15); - SAVEPOINT two; - INSERT INTO savepoints VALUES (16); - SAVEPOINT three; - INSERT INTO savepoints VALUES (17); -COMMIT; -SELECT a FROM savepoints WHERE a BETWEEN 12 AND 17; - a ----- - 12 - 15 - 16 - 17 -(4 rows) - -BEGIN; - INSERT INTO savepoints VALUES (18); - SAVEPOINT one; - INSERT INTO savepoints VALUES (19); - SAVEPOINT two; - INSERT INTO savepoints VALUES (20); - ROLLBACK TO SAVEPOINT one; - INSERT INTO savepoints VALUES (21); - ROLLBACK TO SAVEPOINT one; - INSERT INTO savepoints VALUES (22); -COMMIT; -SELECT a FROM savepoints WHERE a BETWEEN 18 AND 22; - a ----- - 18 - 22 -(2 rows) - -DROP TABLE savepoints; --- only in a transaction block: -SAVEPOINT one; -ERROR: SAVEPOINT can only be used in transaction blocks -ROLLBACK TO SAVEPOINT one; -ERROR: ROLLBACK TO SAVEPOINT can only be used in transaction blocks -RELEASE SAVEPOINT one; -ERROR: RELEASE SAVEPOINT can only be used in transaction blocks --- Only "rollback to" allowed in aborted state -BEGIN; - SAVEPOINT one; - SELECT 0/0; -ERROR: division by zero - SAVEPOINT two; -- ignored till the end of ... -ERROR: current transaction is aborted, commands ignored until end of transaction block - RELEASE SAVEPOINT one; -- ignored till the end of ... -ERROR: current transaction is aborted, commands ignored until end of transaction block - ROLLBACK TO SAVEPOINT one; - SELECT 1; - ?column? ----------- - 1 -(1 row) - -COMMIT; -SELECT 1; -- this should work - ?column? ----------- - 1 -(1 row) - --- check non-transactional behavior of cursors -BEGIN; - DECLARE c CURSOR FOR SELECT unique2 FROM tenk1 ORDER BY unique2; - SAVEPOINT one; - FETCH 10 FROM c; - unique2 ---------- - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 -(10 rows) - - ROLLBACK TO SAVEPOINT one; - FETCH 10 FROM c; - unique2 ---------- - 10 - 11 - 12 - 13 - 14 - 15 - 16 - 17 - 18 - 19 -(10 rows) - - RELEASE SAVEPOINT one; - FETCH 10 FROM c; - unique2 ---------- - 20 - 21 - 22 - 23 - 24 - 25 - 26 - 27 - 28 - 29 -(10 rows) - - CLOSE c; - DECLARE c CURSOR FOR SELECT unique2/0 FROM tenk1 ORDER BY unique2; - SAVEPOINT two; - FETCH 10 FROM c; -ERROR: division by zero - ROLLBACK TO SAVEPOINT two; - -- c is now dead to the world ... - FETCH 10 FROM c; -ERROR: portal "c" cannot be run - ROLLBACK TO SAVEPOINT two; - RELEASE SAVEPOINT two; - FETCH 10 FROM c; -ERROR: portal "c" cannot be run -COMMIT; --- --- Check that "stable" functions are really stable. They should not be --- able to see the partial results of the calling query. (Ideally we would --- also check that they don't see commits of concurrent transactions, but --- that's a mite hard to do within the limitations of pg_regress.) --- -select * from xacttest; - a | b ------+--------- - 56 | 7.8 - 100 | 99.097 - 0 | 0.09561 - 42 | 324.78 - 777 | 777.777 -(5 rows) - -create or replace function max_xacttest() returns smallint language sql as -'select max(a) from xacttest' stable; -begin; -update xacttest set a = max_xacttest() + 10 where a > 0; -select * from xacttest; - a | b ------+--------- - 0 | 0.09561 - 787 | 7.8 - 787 | 99.097 - 787 | 324.78 - 787 | 777.777 -(5 rows) - -rollback; --- But a volatile function can see the partial results of the calling query -create or replace function max_xacttest() returns smallint language sql as -'select max(a) from xacttest' volatile; -begin; -update xacttest set a = max_xacttest() + 10 where a > 0; -select * from xacttest; - a | b ------+--------- - 0 | 0.09561 - 787 | 7.8 - 797 | 99.097 - 807 | 324.78 - 817 | 777.777 -(5 rows) - -rollback; --- Now the same test with plpgsql (since it depends on SPI which is different) -create or replace function max_xacttest() returns smallint language plpgsql as -'begin return max(a) from xacttest; end' stable; -begin; -update xacttest set a = max_xacttest() + 10 where a > 0; -select * from xacttest; - a | b ------+--------- - 0 | 0.09561 - 787 | 7.8 - 787 | 99.097 - 787 | 324.78 - 787 | 777.777 -(5 rows) - -rollback; -create or replace function max_xacttest() returns smallint language plpgsql as -'begin return max(a) from xacttest; end' volatile; -begin; -update xacttest set a = max_xacttest() + 10 where a > 0; -select * from xacttest; - a | b ------+--------- - 0 | 0.09561 - 787 | 7.8 - 797 | 99.097 - 807 | 324.78 - 817 | 777.777 -(5 rows) - -rollback; --- test case for problems with dropping an open relation during abort -BEGIN; - savepoint x; - CREATE TABLE koju (a INT UNIQUE); - INSERT INTO koju VALUES (1); - INSERT INTO koju VALUES (1); -ERROR: duplicate key value violates unique constraint "koju_a_key" -DETAIL: Key (a)=(1) already exists. - rollback to x; - CREATE TABLE koju (a INT UNIQUE); - INSERT INTO koju VALUES (1); - INSERT INTO koju VALUES (1); -ERROR: duplicate key value violates unique constraint "koju_a_key" -DETAIL: Key (a)=(1) already exists. -ROLLBACK; -DROP TABLE trans_foo; -DROP TABLE trans_baz; -DROP TABLE trans_barbaz; --- test case for problems with revalidating an open relation during abort -create function inverse(int) returns float8 as -$$ -begin - analyze revalidate_bug; - return 1::float8/$1; -exception - when division_by_zero then return 0; -end$$ language plpgsql volatile; -create table revalidate_bug (c float8 unique); -insert into revalidate_bug values (1); -insert into revalidate_bug values (inverse(0)); -drop table revalidate_bug; -drop function inverse(int); --- verify that cursors created during an aborted subtransaction are --- closed, but that we do not rollback the effect of any FETCHs --- performed in the aborted subtransaction -begin; -savepoint x; -create table trans_abc (a int); -insert into trans_abc values (5); -insert into trans_abc values (10); -declare foo cursor for select * from trans_abc; -fetch from foo; - a ---- - 5 -(1 row) - -rollback to x; --- should fail -fetch from foo; -ERROR: cursor "foo" does not exist -commit; -begin; -create table trans_abc (a int); -insert into trans_abc values (5); -insert into trans_abc values (10); -insert into trans_abc values (15); -declare foo cursor for select * from trans_abc; -fetch from foo; - a ---- - 5 -(1 row) - -savepoint x; -fetch from foo; - a ----- - 10 -(1 row) - -rollback to x; -fetch from foo; - a ----- - 15 -(1 row) - -abort; --- Test for proper cleanup after a failure in a cursor portal --- that was created in an outer subtransaction -CREATE FUNCTION invert(x float8) RETURNS float8 LANGUAGE plpgsql AS -$$ begin return 1/x; end $$; -CREATE FUNCTION create_temp_tab() RETURNS text -LANGUAGE plpgsql AS $$ -BEGIN - CREATE TEMP TABLE new_table (f1 float8); - -- case of interest is that we fail while holding an open - -- relcache reference to new_table - INSERT INTO new_table SELECT invert(0.0); - RETURN 'foo'; -END $$; -BEGIN; -DECLARE ok CURSOR FOR SELECT * FROM int8_tbl; -DECLARE ctt CURSOR FOR SELECT create_temp_tab(); -FETCH ok; - q1 | q2 ------+----- - 123 | 456 -(1 row) - -SAVEPOINT s1; -FETCH ok; -- should work - q1 | q2 ------+------------------ - 123 | 4567890123456789 -(1 row) - -FETCH ctt; -- error occurs here -ERROR: division by zero -CONTEXT: PL/pgSQL function invert(double precision) line 1 at RETURN -SQL statement "INSERT INTO new_table SELECT invert(0.0)" -PL/pgSQL function create_temp_tab() line 6 at SQL statement -ROLLBACK TO s1; -FETCH ok; -- should work - q1 | q2 -------------------+----- - 4567890123456789 | 123 -(1 row) - -FETCH ctt; -- must be rejected -ERROR: portal "ctt" cannot be run -COMMIT; -DROP FUNCTION create_temp_tab(); -DROP FUNCTION invert(x float8); --- Tests for AND CHAIN -CREATE TABLE trans_abc (a int); --- set nondefault value so we have something to override below -SET default_transaction_read_only = on; -START TRANSACTION ISOLATION LEVEL REPEATABLE READ, READ WRITE, DEFERRABLE; -SHOW transaction_isolation; - transaction_isolation ------------------------ - repeatable read -(1 row) - -SHOW transaction_read_only; - transaction_read_only ------------------------ - off -(1 row) - -SHOW transaction_deferrable; - transaction_deferrable ------------------------- - on -(1 row) - -INSERT INTO trans_abc VALUES (1); -INSERT INTO trans_abc VALUES (2); -COMMIT AND CHAIN; -- TBLOCK_END -SHOW transaction_isolation; - transaction_isolation ------------------------ - repeatable read -(1 row) - -SHOW transaction_read_only; - transaction_read_only ------------------------ - off -(1 row) - -SHOW transaction_deferrable; - transaction_deferrable ------------------------- - on -(1 row) - -INSERT INTO trans_abc VALUES ('error'); -ERROR: invalid input syntax for type integer: "error" -LINE 1: INSERT INTO trans_abc VALUES ('error'); - ^ -INSERT INTO trans_abc VALUES (3); -- check it's really aborted -ERROR: current transaction is aborted, commands ignored until end of transaction block -COMMIT AND CHAIN; -- TBLOCK_ABORT_END -SHOW transaction_isolation; - transaction_isolation ------------------------ - repeatable read -(1 row) - -SHOW transaction_read_only; - transaction_read_only ------------------------ - off -(1 row) - -SHOW transaction_deferrable; - transaction_deferrable ------------------------- - on -(1 row) - -INSERT INTO trans_abc VALUES (4); -COMMIT; -START TRANSACTION ISOLATION LEVEL REPEATABLE READ, READ WRITE, DEFERRABLE; -SHOW transaction_isolation; - transaction_isolation ------------------------ - repeatable read -(1 row) - -SHOW transaction_read_only; - transaction_read_only ------------------------ - off -(1 row) - -SHOW transaction_deferrable; - transaction_deferrable ------------------------- - on -(1 row) - -SAVEPOINT x; -INSERT INTO trans_abc VALUES ('error'); -ERROR: invalid input syntax for type integer: "error" -LINE 1: INSERT INTO trans_abc VALUES ('error'); - ^ -COMMIT AND CHAIN; -- TBLOCK_ABORT_PENDING -SHOW transaction_isolation; - transaction_isolation ------------------------ - repeatable read -(1 row) - -SHOW transaction_read_only; - transaction_read_only ------------------------ - off -(1 row) - -SHOW transaction_deferrable; - transaction_deferrable ------------------------- - on -(1 row) - -INSERT INTO trans_abc VALUES (5); -COMMIT; -START TRANSACTION ISOLATION LEVEL REPEATABLE READ, READ WRITE, DEFERRABLE; -SHOW transaction_isolation; - transaction_isolation ------------------------ - repeatable read -(1 row) - -SHOW transaction_read_only; - transaction_read_only ------------------------ - off -(1 row) - -SHOW transaction_deferrable; - transaction_deferrable ------------------------- - on -(1 row) - -SAVEPOINT x; -COMMIT AND CHAIN; -- TBLOCK_SUBCOMMIT -SHOW transaction_isolation; - transaction_isolation ------------------------ - repeatable read -(1 row) - -SHOW transaction_read_only; - transaction_read_only ------------------------ - off -(1 row) - -SHOW transaction_deferrable; - transaction_deferrable ------------------------- - on -(1 row) - -COMMIT; -START TRANSACTION ISOLATION LEVEL READ COMMITTED, READ WRITE, DEFERRABLE; -SHOW transaction_isolation; - transaction_isolation ------------------------ - read committed -(1 row) - -SHOW transaction_read_only; - transaction_read_only ------------------------ - off -(1 row) - -SHOW transaction_deferrable; - transaction_deferrable ------------------------- - on -(1 row) - -SAVEPOINT x; -COMMIT AND CHAIN; -- TBLOCK_SUBCOMMIT -SHOW transaction_isolation; - transaction_isolation ------------------------ - read committed -(1 row) - -SHOW transaction_read_only; - transaction_read_only ------------------------ - off -(1 row) - -SHOW transaction_deferrable; - transaction_deferrable ------------------------- - on -(1 row) - -COMMIT; --- different mix of options just for fun -START TRANSACTION ISOLATION LEVEL SERIALIZABLE, READ WRITE, NOT DEFERRABLE; -SHOW transaction_isolation; - transaction_isolation ------------------------ - serializable -(1 row) - -SHOW transaction_read_only; - transaction_read_only ------------------------ - off -(1 row) - -SHOW transaction_deferrable; - transaction_deferrable ------------------------- - off -(1 row) - -INSERT INTO trans_abc VALUES (6); -ROLLBACK AND CHAIN; -- TBLOCK_ABORT_PENDING -SHOW transaction_isolation; - transaction_isolation ------------------------ - serializable -(1 row) - -SHOW transaction_read_only; - transaction_read_only ------------------------ - off -(1 row) - -SHOW transaction_deferrable; - transaction_deferrable ------------------------- - off -(1 row) - -INSERT INTO trans_abc VALUES ('error'); -ERROR: invalid input syntax for type integer: "error" -LINE 1: INSERT INTO trans_abc VALUES ('error'); - ^ -ROLLBACK AND CHAIN; -- TBLOCK_ABORT_END -SHOW transaction_isolation; - transaction_isolation ------------------------ - serializable -(1 row) - -SHOW transaction_read_only; - transaction_read_only ------------------------ - off -(1 row) - -SHOW transaction_deferrable; - transaction_deferrable ------------------------- - off -(1 row) - -ROLLBACK; --- not allowed outside a transaction block -COMMIT AND CHAIN; -- error -ERROR: COMMIT AND CHAIN can only be used in transaction blocks -ROLLBACK AND CHAIN; -- error -ERROR: ROLLBACK AND CHAIN can only be used in transaction blocks -SELECT * FROM trans_abc ORDER BY 1; - a ---- - 1 - 2 - 4 - 5 -(4 rows) - -RESET default_transaction_read_only; -DROP TABLE trans_abc; --- Test assorted behaviors around the implicit transaction block created --- when multiple SQL commands are sent in a single Query message. These --- tests rely on the fact that psql will not break SQL commands apart at a --- backslash-quoted semicolon, but will send them as one Query. -create temp table i_table (f1 int); --- psql will show all results of a multi-statement Query -SELECT 1\; SELECT 2\; SELECT 3; - ?column? ----------- - 1 -(1 row) - - ?column? ----------- - 2 -(1 row) - - ?column? ----------- - 3 -(1 row) - --- this implicitly commits: -insert into i_table values(1)\; select * from i_table; - f1 ----- - 1 -(1 row) - --- 1/0 error will cause rolling back the whole implicit transaction -insert into i_table values(2)\; select * from i_table\; select 1/0; - f1 ----- - 1 - 2 -(2 rows) - -ERROR: division by zero -select * from i_table; - f1 ----- - 1 -(1 row) - -rollback; -- we are not in a transaction at this point -WARNING: there is no transaction in progress --- can use regular begin/commit/rollback within a single Query -begin\; insert into i_table values(3)\; commit; -rollback; -- we are not in a transaction at this point -WARNING: there is no transaction in progress -begin\; insert into i_table values(4)\; rollback; -rollback; -- we are not in a transaction at this point -WARNING: there is no transaction in progress --- begin converts implicit transaction into a regular one that --- can extend past the end of the Query -select 1\; begin\; insert into i_table values(5); - ?column? ----------- - 1 -(1 row) - -commit; -select 1\; begin\; insert into i_table values(6); - ?column? ----------- - 1 -(1 row) - -rollback; --- commit in implicit-transaction state commits but issues a warning. -insert into i_table values(7)\; commit\; insert into i_table values(8)\; select 1/0; -WARNING: there is no transaction in progress -ERROR: division by zero --- similarly, rollback aborts but issues a warning. -insert into i_table values(9)\; rollback\; select 2; -WARNING: there is no transaction in progress - ?column? ----------- - 2 -(1 row) - -select * from i_table; - f1 ----- - 1 - 3 - 5 - 7 -(4 rows) - -rollback; -- we are not in a transaction at this point -WARNING: there is no transaction in progress --- implicit transaction block is still a transaction block, for e.g. VACUUM -SELECT 1\; VACUUM; - ?column? ----------- - 1 -(1 row) - -ERROR: VACUUM cannot run inside a transaction block -SELECT 1\; COMMIT\; VACUUM; -WARNING: there is no transaction in progress - ?column? ----------- - 1 -(1 row) - -ERROR: VACUUM cannot run inside a transaction block --- we disallow savepoint-related commands in implicit-transaction state -SELECT 1\; SAVEPOINT sp; - ?column? ----------- - 1 -(1 row) - -ERROR: SAVEPOINT can only be used in transaction blocks -SELECT 1\; COMMIT\; SAVEPOINT sp; -WARNING: there is no transaction in progress - ?column? ----------- - 1 -(1 row) - -ERROR: SAVEPOINT can only be used in transaction blocks -ROLLBACK TO SAVEPOINT sp\; SELECT 2; -ERROR: ROLLBACK TO SAVEPOINT can only be used in transaction blocks -SELECT 2\; RELEASE SAVEPOINT sp\; SELECT 3; - ?column? ----------- - 2 -(1 row) - -ERROR: RELEASE SAVEPOINT can only be used in transaction blocks --- but this is OK, because the BEGIN converts it to a regular xact -SELECT 1\; BEGIN\; SAVEPOINT sp\; ROLLBACK TO SAVEPOINT sp\; COMMIT; - ?column? ----------- - 1 -(1 row) - --- Tests for AND CHAIN in implicit transaction blocks -SET TRANSACTION READ ONLY\; COMMIT AND CHAIN; -- error -ERROR: COMMIT AND CHAIN can only be used in transaction blocks -SHOW transaction_read_only; - transaction_read_only ------------------------ - off -(1 row) - -SET TRANSACTION READ ONLY\; ROLLBACK AND CHAIN; -- error -ERROR: ROLLBACK AND CHAIN can only be used in transaction blocks -SHOW transaction_read_only; - transaction_read_only ------------------------ - off -(1 row) - -CREATE TABLE trans_abc (a int); --- COMMIT/ROLLBACK + COMMIT/ROLLBACK AND CHAIN -INSERT INTO trans_abc VALUES (7)\; COMMIT\; INSERT INTO trans_abc VALUES (8)\; COMMIT AND CHAIN; -- 7 commit, 8 error -WARNING: there is no transaction in progress -ERROR: COMMIT AND CHAIN can only be used in transaction blocks -INSERT INTO trans_abc VALUES (9)\; ROLLBACK\; INSERT INTO trans_abc VALUES (10)\; ROLLBACK AND CHAIN; -- 9 rollback, 10 error -WARNING: there is no transaction in progress -ERROR: ROLLBACK AND CHAIN can only be used in transaction blocks --- COMMIT/ROLLBACK AND CHAIN + COMMIT/ROLLBACK -INSERT INTO trans_abc VALUES (11)\; COMMIT AND CHAIN\; INSERT INTO trans_abc VALUES (12)\; COMMIT; -- 11 error, 12 not reached -ERROR: COMMIT AND CHAIN can only be used in transaction blocks -INSERT INTO trans_abc VALUES (13)\; ROLLBACK AND CHAIN\; INSERT INTO trans_abc VALUES (14)\; ROLLBACK; -- 13 error, 14 not reached -ERROR: ROLLBACK AND CHAIN can only be used in transaction blocks --- START TRANSACTION + COMMIT/ROLLBACK AND CHAIN -START TRANSACTION ISOLATION LEVEL REPEATABLE READ\; INSERT INTO trans_abc VALUES (15)\; COMMIT AND CHAIN; -- 15 ok -SHOW transaction_isolation; -- transaction is active at this point - transaction_isolation ------------------------ - repeatable read -(1 row) - -COMMIT; -START TRANSACTION ISOLATION LEVEL REPEATABLE READ\; INSERT INTO trans_abc VALUES (16)\; ROLLBACK AND CHAIN; -- 16 ok -SHOW transaction_isolation; -- transaction is active at this point - transaction_isolation ------------------------ - repeatable read -(1 row) - -ROLLBACK; -SET default_transaction_isolation = 'read committed'; --- START TRANSACTION + COMMIT/ROLLBACK + COMMIT/ROLLBACK AND CHAIN -START TRANSACTION ISOLATION LEVEL REPEATABLE READ\; INSERT INTO trans_abc VALUES (17)\; COMMIT\; INSERT INTO trans_abc VALUES (18)\; COMMIT AND CHAIN; -- 17 commit, 18 error -ERROR: COMMIT AND CHAIN can only be used in transaction blocks -SHOW transaction_isolation; -- out of transaction block - transaction_isolation ------------------------ - read committed -(1 row) - -START TRANSACTION ISOLATION LEVEL REPEATABLE READ\; INSERT INTO trans_abc VALUES (19)\; ROLLBACK\; INSERT INTO trans_abc VALUES (20)\; ROLLBACK AND CHAIN; -- 19 rollback, 20 error -ERROR: ROLLBACK AND CHAIN can only be used in transaction blocks -SHOW transaction_isolation; -- out of transaction block - transaction_isolation ------------------------ - read committed -(1 row) - -RESET default_transaction_isolation; -SELECT * FROM trans_abc ORDER BY 1; - a ----- - 7 - 15 - 17 -(3 rows) - -DROP TABLE trans_abc; --- TRANSACTION SNAPSHOT --- Incorrect identifier. -BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ; -SET TRANSACTION SNAPSHOT 'Incorrect Identifier'; -ERROR: invalid snapshot identifier: "Incorrect Identifier" -ROLLBACK; --- Correct identifier, missing file. -BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ; -SET TRANSACTION SNAPSHOT 'FFF-FFF-F'; -ERROR: snapshot "FFF-FFF-F" does not exist -ROLLBACK; --- Test for successful cleanup of an aborted transaction at session exit. --- THIS MUST BE THE LAST TEST IN THIS FILE. -begin; -select 1/0; -ERROR: division by zero -rollback to X; -ERROR: savepoint "x" does not exist --- DO NOT ADD ANYTHING HERE. +psql: error: connection to server on socket "/tmp/pg_regress-03Az7d/.s.PGSQL.40051" failed: FATAL: the database system is not yet accepting connections +DETAIL: Consistent recovery state has not been yet reached. diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/random.out /tmp/cirrus-ci-build/build/testrun/regress/regress/results/random.out --- /tmp/cirrus-ci-build/src/test/regress/expected/random.out 2024-09-20 21:41:45.738024523 +0000 +++ /tmp/cirrus-ci-build/build/testrun/regress/regress/results/random.out 2024-09-20 21:46:05.485625097 +0000 @@ -1,538 +1,2 @@ --- --- RANDOM --- Test random() and allies --- --- Tests in this file may have a small probability of failure, --- since we are dealing with randomness. Try to keep the failure --- risk for any one test case under 1e-9. --- --- There should be no duplicates in 1000 random() values. --- (Assuming 52 random bits in the float8 results, we could --- take as many as 3000 values and still have less than 1e-9 chance --- of failure, per https://en.wikipedia.org/wiki/Birthday_problem) -SELECT r, count(*) -FROM (SELECT random() r FROM generate_series(1, 1000)) ss -GROUP BY r HAVING count(*) > 1; - r | count ----+------- -(0 rows) - --- The range should be [0, 1). We can expect that at least one out of 2000 --- random values is in the lowest or highest 1% of the range with failure --- probability less than about 1e-9. -SELECT count(*) FILTER (WHERE r < 0 OR r >= 1) AS out_of_range, - (count(*) FILTER (WHERE r < 0.01)) > 0 AS has_small, - (count(*) FILTER (WHERE r > 0.99)) > 0 AS has_large -FROM (SELECT random() r FROM generate_series(1, 2000)) ss; - out_of_range | has_small | has_large ---------------+-----------+----------- - 0 | t | t -(1 row) - --- Check for uniform distribution using the Kolmogorov-Smirnov test. -CREATE FUNCTION ks_test_uniform_random() -RETURNS boolean AS -$$ -DECLARE - n int := 1000; -- Number of samples - c float8 := 1.94947; -- Critical value for 99.9% confidence - ok boolean; -BEGIN - ok := ( - WITH samples AS ( - SELECT random() r FROM generate_series(1, n) ORDER BY 1 - ), indexed_samples AS ( - SELECT (row_number() OVER())-1.0 i, r FROM samples - ) - SELECT max(abs(i/n-r)) < c / sqrt(n) FROM indexed_samples - ); - RETURN ok; -END -$$ -LANGUAGE plpgsql; --- As written, ks_test_uniform_random() returns true about 99.9% --- of the time. To get down to a roughly 1e-9 test failure rate, --- just run it 3 times and accept if any one of them passes. -SELECT ks_test_uniform_random() OR - ks_test_uniform_random() OR - ks_test_uniform_random() AS uniform; - uniform ---------- - t -(1 row) - --- now test random_normal() --- As above, there should be no duplicates in 1000 random_normal() values. -SELECT r, count(*) -FROM (SELECT random_normal() r FROM generate_series(1, 1000)) ss -GROUP BY r HAVING count(*) > 1; - r | count ----+------- -(0 rows) - --- ... unless we force the range (standard deviation) to zero. --- This is a good place to check that the mean input does something, too. -SELECT r, count(*) -FROM (SELECT random_normal(10, 0) r FROM generate_series(1, 100)) ss -GROUP BY r; - r | count -----+------- - 10 | 100 -(1 row) - -SELECT r, count(*) -FROM (SELECT random_normal(-10, 0) r FROM generate_series(1, 100)) ss -GROUP BY r; - r | count ------+------- - -10 | 100 -(1 row) - --- Check standard normal distribution using the Kolmogorov-Smirnov test. -CREATE FUNCTION ks_test_normal_random() -RETURNS boolean AS -$$ -DECLARE - n int := 1000; -- Number of samples - c float8 := 1.94947; -- Critical value for 99.9% confidence - ok boolean; -BEGIN - ok := ( - WITH samples AS ( - SELECT random_normal() r FROM generate_series(1, n) ORDER BY 1 - ), indexed_samples AS ( - SELECT (row_number() OVER())-1.0 i, r FROM samples - ) - SELECT max(abs((1+erf(r/sqrt(2)))/2 - i/n)) < c / sqrt(n) - FROM indexed_samples - ); - RETURN ok; -END -$$ -LANGUAGE plpgsql; --- As above, ks_test_normal_random() returns true about 99.9% --- of the time, so try it 3 times and accept if any test passes. -SELECT ks_test_normal_random() OR - ks_test_normal_random() OR - ks_test_normal_random() AS standard_normal; - standard_normal ------------------ - t -(1 row) - --- Test random(min, max) --- invalid range bounds -SELECT random(1, 0); -ERROR: lower bound must be less than or equal to upper bound -SELECT random(1000000000001, 1000000000000); -ERROR: lower bound must be less than or equal to upper bound -SELECT random(-2.0, -3.0); -ERROR: lower bound must be less than or equal to upper bound -SELECT random('NaN'::numeric, 10); -ERROR: lower bound cannot be NaN -SELECT random('-Inf'::numeric, 0); -ERROR: lower bound cannot be infinity -SELECT random(0, 'NaN'::numeric); -ERROR: upper bound cannot be NaN -SELECT random(0, 'Inf'::numeric); -ERROR: upper bound cannot be infinity --- empty range is OK -SELECT random(101, 101); - random --------- - 101 -(1 row) - -SELECT random(1000000000001, 1000000000001); - random ---------------- - 1000000000001 -(1 row) - -SELECT random(3.14, 3.14); - random --------- - 3.14 -(1 row) - --- There should be no triple duplicates in 1000 full-range 32-bit random() --- values. (Each of the C(1000, 3) choices of triplets from the 1000 values --- has a probability of 1/(2^32)^2 of being a triple duplicate, so the --- average number of triple duplicates is 1000 * 999 * 998 / 6 / 2^64, which --- is roughly 9e-12.) -SELECT r, count(*) -FROM (SELECT random(-2147483648, 2147483647) r - FROM generate_series(1, 1000)) ss -GROUP BY r HAVING count(*) > 2; - r | count ----+------- -(0 rows) - --- There should be no duplicates in 1000 full-range 64-bit random() values. -SELECT r, count(*) -FROM (SELECT random_normal(-9223372036854775808, 9223372036854775807) r - FROM generate_series(1, 1000)) ss -GROUP BY r HAVING count(*) > 1; - r | count ----+------- -(0 rows) - --- There should be no duplicates in 1000 15-digit random() numeric values. -SELECT r, count(*) -FROM (SELECT random_normal(0, 1 - 1e-15) r - FROM generate_series(1, 1000)) ss -GROUP BY r HAVING count(*) > 1; - r | count ----+------- -(0 rows) - --- Expect at least one out of 2000 random values to be in the lowest and --- highest 1% of the range. -SELECT (count(*) FILTER (WHERE r < -2104533975)) > 0 AS has_small, - (count(*) FILTER (WHERE r > 2104533974)) > 0 AS has_large -FROM (SELECT random(-2147483648, 2147483647) r FROM generate_series(1, 2000)) ss; - has_small | has_large ------------+----------- - t | t -(1 row) - -SELECT count(*) FILTER (WHERE r < -1500000000 OR r > 1500000000) AS out_of_range, - (count(*) FILTER (WHERE r < -1470000000)) > 0 AS has_small, - (count(*) FILTER (WHERE r > 1470000000)) > 0 AS has_large -FROM (SELECT random(-1500000000, 1500000000) r FROM generate_series(1, 2000)) ss; - out_of_range | has_small | has_large ---------------+-----------+----------- - 0 | t | t -(1 row) - -SELECT (count(*) FILTER (WHERE r < -9038904596117680292)) > 0 AS has_small, - (count(*) FILTER (WHERE r > 9038904596117680291)) > 0 AS has_large -FROM (SELECT random(-9223372036854775808, 9223372036854775807) r - FROM generate_series(1, 2000)) ss; - has_small | has_large ------------+----------- - t | t -(1 row) - -SELECT count(*) FILTER (WHERE r < -1500000000000000 OR r > 1500000000000000) AS out_of_range, - (count(*) FILTER (WHERE r < -1470000000000000)) > 0 AS has_small, - (count(*) FILTER (WHERE r > 1470000000000000)) > 0 AS has_large -FROM (SELECT random(-1500000000000000, 1500000000000000) r - FROM generate_series(1, 2000)) ss; - out_of_range | has_small | has_large ---------------+-----------+----------- - 0 | t | t -(1 row) - -SELECT count(*) FILTER (WHERE r < -1.5 OR r > 1.5) AS out_of_range, - (count(*) FILTER (WHERE r < -1.47)) > 0 AS has_small, - (count(*) FILTER (WHERE r > 1.47)) > 0 AS has_large -FROM (SELECT random(-1.500000000000000, 1.500000000000000) r - FROM generate_series(1, 2000)) ss; - out_of_range | has_small | has_large ---------------+-----------+----------- - 0 | t | t -(1 row) - --- Every possible value should occur at least once in 2500 random() values --- chosen from a range with 100 distinct values. -SELECT min(r), max(r), count(r) FROM ( - SELECT DISTINCT random(-50, 49) r FROM generate_series(1, 2500)); - min | max | count ------+-----+------- - -50 | 49 | 100 -(1 row) - -SELECT min(r), max(r), count(r) FROM ( - SELECT DISTINCT random(123000000000, 123000000099) r - FROM generate_series(1, 2500)); - min | max | count ---------------+--------------+------- - 123000000000 | 123000000099 | 100 -(1 row) - -SELECT min(r), max(r), count(r) FROM ( - SELECT DISTINCT random(-0.5, 0.49) r FROM generate_series(1, 2500)); - min | max | count --------+------+------- - -0.50 | 0.49 | 100 -(1 row) - --- Check for uniform distribution using the Kolmogorov-Smirnov test. -CREATE FUNCTION ks_test_uniform_random_int_in_range() -RETURNS boolean AS -$$ -DECLARE - n int := 1000; -- Number of samples - c float8 := 1.94947; -- Critical value for 99.9% confidence - ok boolean; -BEGIN - ok := ( - WITH samples AS ( - SELECT random(0, 999999) / 1000000.0 r FROM generate_series(1, n) ORDER BY 1 - ), indexed_samples AS ( - SELECT (row_number() OVER())-1.0 i, r FROM samples - ) - SELECT max(abs(i/n-r)) < c / sqrt(n) FROM indexed_samples - ); - RETURN ok; -END -$$ -LANGUAGE plpgsql; -SELECT ks_test_uniform_random_int_in_range() OR - ks_test_uniform_random_int_in_range() OR - ks_test_uniform_random_int_in_range() AS uniform_int; - uniform_int -------------- - t -(1 row) - -CREATE FUNCTION ks_test_uniform_random_bigint_in_range() -RETURNS boolean AS -$$ -DECLARE - n int := 1000; -- Number of samples - c float8 := 1.94947; -- Critical value for 99.9% confidence - ok boolean; -BEGIN - ok := ( - WITH samples AS ( - SELECT random(0, 999999999999) / 1000000000000.0 r FROM generate_series(1, n) ORDER BY 1 - ), indexed_samples AS ( - SELECT (row_number() OVER())-1.0 i, r FROM samples - ) - SELECT max(abs(i/n-r)) < c / sqrt(n) FROM indexed_samples - ); - RETURN ok; -END -$$ -LANGUAGE plpgsql; -SELECT ks_test_uniform_random_bigint_in_range() OR - ks_test_uniform_random_bigint_in_range() OR - ks_test_uniform_random_bigint_in_range() AS uniform_bigint; - uniform_bigint ----------------- - t -(1 row) - -CREATE FUNCTION ks_test_uniform_random_numeric_in_range() -RETURNS boolean AS -$$ -DECLARE - n int := 1000; -- Number of samples - c float8 := 1.94947; -- Critical value for 99.9% confidence - ok boolean; -BEGIN - ok := ( - WITH samples AS ( - SELECT random(0, 0.999999) r FROM generate_series(1, n) ORDER BY 1 - ), indexed_samples AS ( - SELECT (row_number() OVER())-1.0 i, r FROM samples - ) - SELECT max(abs(i/n-r)) < c / sqrt(n) FROM indexed_samples - ); - RETURN ok; -END -$$ -LANGUAGE plpgsql; -SELECT ks_test_uniform_random_numeric_in_range() OR - ks_test_uniform_random_numeric_in_range() OR - ks_test_uniform_random_numeric_in_range() AS uniform_numeric; - uniform_numeric ------------------ - t -(1 row) - --- setseed() should produce a reproducible series of random() values. -SELECT setseed(0.5); - setseed ---------- - -(1 row) - -SELECT random() FROM generate_series(1, 10); - random ---------------------- - 0.9851677175347999 - 0.825301858027981 - 0.12974610012450416 - 0.16356291958601088 - 0.6476186144084 - 0.8822771983038762 - 0.1404566845227775 - 0.15619865764623442 - 0.5145227426983392 - 0.7712969548127826 -(10 rows) - --- Likewise for random_normal(); however, since its implementation relies --- on libm functions that have different roundoff behaviors on different --- machines, we have to round off the results a bit to get consistent output. -SET extra_float_digits = -1; -SELECT random_normal() FROM generate_series(1, 10); - random_normal -------------------- - 0.20853464493838 - 0.26453024054096 - -0.60675246790043 - 0.82579942785265 - 1.7011161173536 - -0.22344546371619 - 0.249712419191 - -1.2494722990669 - 0.12562715204368 - 0.47539161454401 -(10 rows) - -SELECT random_normal(mean => 1, stddev => 0.1) r FROM generate_series(1, 10); - r ------------------- - 1.0060597281173 - 1.09685453015 - 1.0286920613201 - 0.90947567671234 - 0.98372476313426 - 0.93934454957762 - 1.1871350020636 - 0.96225768429293 - 0.91444120680041 - 0.96403105557543 -(10 rows) - --- Reproducible random(min, max) values. -SELECT random(1, 6) FROM generate_series(1, 10); - random --------- - 5 - 4 - 5 - 1 - 6 - 1 - 1 - 3 - 6 - 5 -(10 rows) - -SELECT random(-2147483648, 2147483647) FROM generate_series(1, 10); - random -------------- - -84380014 - 1287883594 - -1927252904 - 13516867 - -1902961616 - -1824286201 - -871264469 - -1225880415 - 229836730 - -116039023 -(10 rows) - -SELECT random(-9223372036854775808, 9223372036854775807) FROM generate_series(1, 10); - random ----------------------- - -6205280962992680052 - -3583519428011353337 - 511801786318122700 - 4672737727839409655 - -6674868801536280768 - -7816052100626646489 - -4340613370136007199 - -5873174504107419786 - -2249910101649817824 - -4493828993910792325 -(10 rows) - -SELECT random(-1e30, 1e30) FROM generate_series(1, 10); - random ---------------------------------- - -732116469803315942112255539315 - 794641423514877972798449289857 - -576932746026123093304638334719 - 420625067723533225139761854757 - -339227806779403187811001078919 - -77667951539418104959241732636 - 239810941795708162629328071599 - 820784371155896967052141946697 - -377084684544126871150439048352 - -979773225250716295007225086726 -(10 rows) - -SELECT random(-0.4, 0.4) FROM generate_series(1, 10); - random --------- - 0.1 - 0.0 - 0.4 - -0.2 - 0.1 - 0.2 - 0.3 - 0.0 - -0.2 - 0.2 -(10 rows) - -SELECT random(0, 1 - 1e-30) FROM generate_series(1, 10); - random ----------------------------------- - 0.676442053784930109917469287265 - 0.221310454098356723569995592911 - 0.060101338174419259555193956224 - 0.509960354695248239243002172364 - 0.248680813394555793693952296993 - 0.353262552880008646603494668901 - 0.760692600450339509843044233719 - 0.554987655310094483449494782510 - 0.330890988458592995280347745733 - 0.665435298280470361228607881507 -(10 rows) - -SELECT n, random(0, trim_scale(abs(1 - 10.0^(-n)))) FROM generate_series(-20, 20) n; - n | random ------+------------------------ - -20 | 94174615760837282445 - -19 | 6692559888531296894 - -18 | 801114552709125931 - -17 | 44091460959939971 - -16 | 2956109297383113 - -15 | 783332278684523 - -14 | 81534303241440 - -13 | 2892623140500 - -12 | 269397605141 - -11 | 13027512296 - -10 | 9178377775 - -9 | 323534150 - -8 | 91897803 - -7 | 6091383 - -6 | 13174 - -5 | 92714 - -4 | 8079 - -3 | 429 - -2 | 30 - -1 | 3 - 0 | 0 - 1 | 0.1 - 2 | 0.69 - 3 | 0.492 - 4 | 0.7380 - 5 | 0.77078 - 6 | 0.738142 - 7 | 0.1808815 - 8 | 0.14908933 - 9 | 0.222654042 - 10 | 0.2281295170 - 11 | 0.73655782966 - 12 | 0.056357256884 - 13 | 0.8998407524375 - 14 | 0.28198400530206 - 15 | 0.713478222805230 - 16 | 0.0415046850936909 - 17 | 0.45946350291315119 - 18 | 0.310966980367873753 - 19 | 0.4967623661709676512 - 20 | 0.60795101234744211935 -(41 rows) - +psql: error: connection to server on socket "/tmp/pg_regress-03Az7d/.s.PGSQL.40051" failed: FATAL: the database system is not yet accepting connections +DETAIL: Consistent recovery state has not been yet reached. diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/portals.out /tmp/cirrus-ci-build/build/testrun/regress/regress/results/portals.out --- /tmp/cirrus-ci-build/src/test/regress/expected/portals.out 2024-09-20 21:41:45.734024523 +0000 +++ /tmp/cirrus-ci-build/build/testrun/regress/regress/results/portals.out 2024-09-20 21:46:05.485625097 +0000 @@ -1,1563 +1,2 @@ --- --- Cursor regression tests --- -BEGIN; -DECLARE foo1 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2; -DECLARE foo2 SCROLL CURSOR FOR SELECT * FROM tenk2; -DECLARE foo3 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2; -DECLARE foo4 SCROLL CURSOR FOR SELECT * FROM tenk2; -DECLARE foo5 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2; -DECLARE foo6 SCROLL CURSOR FOR SELECT * FROM tenk2; -DECLARE foo7 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2; -DECLARE foo8 SCROLL CURSOR FOR SELECT * FROM tenk2; -DECLARE foo9 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2; -DECLARE foo10 SCROLL CURSOR FOR SELECT * FROM tenk2; -DECLARE foo11 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2; -DECLARE foo12 SCROLL CURSOR FOR SELECT * FROM tenk2; -DECLARE foo13 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2; -DECLARE foo14 SCROLL CURSOR FOR SELECT * FROM tenk2; -DECLARE foo15 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2; -DECLARE foo16 SCROLL CURSOR FOR SELECT * FROM tenk2; -DECLARE foo17 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2; -DECLARE foo18 SCROLL CURSOR FOR SELECT * FROM tenk2; -DECLARE foo19 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2; -DECLARE foo20 SCROLL CURSOR FOR SELECT * FROM tenk2; -DECLARE foo21 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2; -DECLARE foo22 SCROLL CURSOR FOR SELECT * FROM tenk2; -DECLARE foo23 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2; -FETCH 1 in foo1; - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- - 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx -(1 row) - -FETCH 2 in foo2; - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- - 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx - 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx -(2 rows) - -FETCH 3 in foo3; - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- - 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx - 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx - 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx -(3 rows) - -FETCH 4 in foo4; - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- - 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx - 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx - 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx - 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx -(4 rows) - -FETCH 5 in foo5; - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- - 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx - 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx - 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx - 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx - 7164 | 4 | 0 | 0 | 4 | 4 | 64 | 164 | 1164 | 2164 | 7164 | 128 | 129 | OPAAAA | EAAAAA | AAAAxx -(5 rows) - -FETCH 6 in foo6; - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- - 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx - 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx - 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx - 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx - 7164 | 4 | 0 | 0 | 4 | 4 | 64 | 164 | 1164 | 2164 | 7164 | 128 | 129 | OPAAAA | EAAAAA | AAAAxx - 8009 | 5 | 1 | 1 | 9 | 9 | 9 | 9 | 9 | 3009 | 8009 | 18 | 19 | BWAAAA | FAAAAA | HHHHxx -(6 rows) - -FETCH 7 in foo7; - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- - 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx - 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx - 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx - 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx - 7164 | 4 | 0 | 0 | 4 | 4 | 64 | 164 | 1164 | 2164 | 7164 | 128 | 129 | OPAAAA | EAAAAA | AAAAxx - 8009 | 5 | 1 | 1 | 9 | 9 | 9 | 9 | 9 | 3009 | 8009 | 18 | 19 | BWAAAA | FAAAAA | HHHHxx - 5057 | 6 | 1 | 1 | 7 | 17 | 57 | 57 | 1057 | 57 | 5057 | 114 | 115 | NMAAAA | GAAAAA | OOOOxx -(7 rows) - -FETCH 8 in foo8; - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- - 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx - 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx - 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx - 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx - 7164 | 4 | 0 | 0 | 4 | 4 | 64 | 164 | 1164 | 2164 | 7164 | 128 | 129 | OPAAAA | EAAAAA | AAAAxx - 8009 | 5 | 1 | 1 | 9 | 9 | 9 | 9 | 9 | 3009 | 8009 | 18 | 19 | BWAAAA | FAAAAA | HHHHxx - 5057 | 6 | 1 | 1 | 7 | 17 | 57 | 57 | 1057 | 57 | 5057 | 114 | 115 | NMAAAA | GAAAAA | OOOOxx - 6701 | 7 | 1 | 1 | 1 | 1 | 1 | 701 | 701 | 1701 | 6701 | 2 | 3 | TXAAAA | HAAAAA | VVVVxx -(8 rows) - -FETCH 9 in foo9; - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- - 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx - 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx - 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx - 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx - 7164 | 4 | 0 | 0 | 4 | 4 | 64 | 164 | 1164 | 2164 | 7164 | 128 | 129 | OPAAAA | EAAAAA | AAAAxx - 8009 | 5 | 1 | 1 | 9 | 9 | 9 | 9 | 9 | 3009 | 8009 | 18 | 19 | BWAAAA | FAAAAA | HHHHxx - 5057 | 6 | 1 | 1 | 7 | 17 | 57 | 57 | 1057 | 57 | 5057 | 114 | 115 | NMAAAA | GAAAAA | OOOOxx - 6701 | 7 | 1 | 1 | 1 | 1 | 1 | 701 | 701 | 1701 | 6701 | 2 | 3 | TXAAAA | HAAAAA | VVVVxx - 4321 | 8 | 1 | 1 | 1 | 1 | 21 | 321 | 321 | 4321 | 4321 | 42 | 43 | FKAAAA | IAAAAA | AAAAxx -(9 rows) - -FETCH 10 in foo10; - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- - 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx - 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx - 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx - 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx - 7164 | 4 | 0 | 0 | 4 | 4 | 64 | 164 | 1164 | 2164 | 7164 | 128 | 129 | OPAAAA | EAAAAA | AAAAxx - 8009 | 5 | 1 | 1 | 9 | 9 | 9 | 9 | 9 | 3009 | 8009 | 18 | 19 | BWAAAA | FAAAAA | HHHHxx - 5057 | 6 | 1 | 1 | 7 | 17 | 57 | 57 | 1057 | 57 | 5057 | 114 | 115 | NMAAAA | GAAAAA | OOOOxx - 6701 | 7 | 1 | 1 | 1 | 1 | 1 | 701 | 701 | 1701 | 6701 | 2 | 3 | TXAAAA | HAAAAA | VVVVxx - 4321 | 8 | 1 | 1 | 1 | 1 | 21 | 321 | 321 | 4321 | 4321 | 42 | 43 | FKAAAA | IAAAAA | AAAAxx - 3043 | 9 | 1 | 3 | 3 | 3 | 43 | 43 | 1043 | 3043 | 3043 | 86 | 87 | BNAAAA | JAAAAA | HHHHxx -(10 rows) - -FETCH 11 in foo11; - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- - 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx - 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx - 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx - 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx - 7164 | 4 | 0 | 0 | 4 | 4 | 64 | 164 | 1164 | 2164 | 7164 | 128 | 129 | OPAAAA | EAAAAA | AAAAxx - 8009 | 5 | 1 | 1 | 9 | 9 | 9 | 9 | 9 | 3009 | 8009 | 18 | 19 | BWAAAA | FAAAAA | HHHHxx - 5057 | 6 | 1 | 1 | 7 | 17 | 57 | 57 | 1057 | 57 | 5057 | 114 | 115 | NMAAAA | GAAAAA | OOOOxx - 6701 | 7 | 1 | 1 | 1 | 1 | 1 | 701 | 701 | 1701 | 6701 | 2 | 3 | TXAAAA | HAAAAA | VVVVxx - 4321 | 8 | 1 | 1 | 1 | 1 | 21 | 321 | 321 | 4321 | 4321 | 42 | 43 | FKAAAA | IAAAAA | AAAAxx - 3043 | 9 | 1 | 3 | 3 | 3 | 43 | 43 | 1043 | 3043 | 3043 | 86 | 87 | BNAAAA | JAAAAA | HHHHxx - 1314 | 10 | 0 | 2 | 4 | 14 | 14 | 314 | 1314 | 1314 | 1314 | 28 | 29 | OYAAAA | KAAAAA | OOOOxx -(11 rows) - -FETCH 12 in foo12; - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- - 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx - 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx - 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx - 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx - 7164 | 4 | 0 | 0 | 4 | 4 | 64 | 164 | 1164 | 2164 | 7164 | 128 | 129 | OPAAAA | EAAAAA | AAAAxx - 8009 | 5 | 1 | 1 | 9 | 9 | 9 | 9 | 9 | 3009 | 8009 | 18 | 19 | BWAAAA | FAAAAA | HHHHxx - 5057 | 6 | 1 | 1 | 7 | 17 | 57 | 57 | 1057 | 57 | 5057 | 114 | 115 | NMAAAA | GAAAAA | OOOOxx - 6701 | 7 | 1 | 1 | 1 | 1 | 1 | 701 | 701 | 1701 | 6701 | 2 | 3 | TXAAAA | HAAAAA | VVVVxx - 4321 | 8 | 1 | 1 | 1 | 1 | 21 | 321 | 321 | 4321 | 4321 | 42 | 43 | FKAAAA | IAAAAA | AAAAxx - 3043 | 9 | 1 | 3 | 3 | 3 | 43 | 43 | 1043 | 3043 | 3043 | 86 | 87 | BNAAAA | JAAAAA | HHHHxx - 1314 | 10 | 0 | 2 | 4 | 14 | 14 | 314 | 1314 | 1314 | 1314 | 28 | 29 | OYAAAA | KAAAAA | OOOOxx - 1504 | 11 | 0 | 0 | 4 | 4 | 4 | 504 | 1504 | 1504 | 1504 | 8 | 9 | WFAAAA | LAAAAA | VVVVxx -(12 rows) - -FETCH 13 in foo13; - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- - 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx - 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx - 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx - 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx - 7164 | 4 | 0 | 0 | 4 | 4 | 64 | 164 | 1164 | 2164 | 7164 | 128 | 129 | OPAAAA | EAAAAA | AAAAxx - 8009 | 5 | 1 | 1 | 9 | 9 | 9 | 9 | 9 | 3009 | 8009 | 18 | 19 | BWAAAA | FAAAAA | HHHHxx - 5057 | 6 | 1 | 1 | 7 | 17 | 57 | 57 | 1057 | 57 | 5057 | 114 | 115 | NMAAAA | GAAAAA | OOOOxx - 6701 | 7 | 1 | 1 | 1 | 1 | 1 | 701 | 701 | 1701 | 6701 | 2 | 3 | TXAAAA | HAAAAA | VVVVxx - 4321 | 8 | 1 | 1 | 1 | 1 | 21 | 321 | 321 | 4321 | 4321 | 42 | 43 | FKAAAA | IAAAAA | AAAAxx - 3043 | 9 | 1 | 3 | 3 | 3 | 43 | 43 | 1043 | 3043 | 3043 | 86 | 87 | BNAAAA | JAAAAA | HHHHxx - 1314 | 10 | 0 | 2 | 4 | 14 | 14 | 314 | 1314 | 1314 | 1314 | 28 | 29 | OYAAAA | KAAAAA | OOOOxx - 1504 | 11 | 0 | 0 | 4 | 4 | 4 | 504 | 1504 | 1504 | 1504 | 8 | 9 | WFAAAA | LAAAAA | VVVVxx - 5222 | 12 | 0 | 2 | 2 | 2 | 22 | 222 | 1222 | 222 | 5222 | 44 | 45 | WSAAAA | MAAAAA | AAAAxx -(13 rows) - -FETCH 14 in foo14; - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- - 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx - 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx - 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx - 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx - 7164 | 4 | 0 | 0 | 4 | 4 | 64 | 164 | 1164 | 2164 | 7164 | 128 | 129 | OPAAAA | EAAAAA | AAAAxx - 8009 | 5 | 1 | 1 | 9 | 9 | 9 | 9 | 9 | 3009 | 8009 | 18 | 19 | BWAAAA | FAAAAA | HHHHxx - 5057 | 6 | 1 | 1 | 7 | 17 | 57 | 57 | 1057 | 57 | 5057 | 114 | 115 | NMAAAA | GAAAAA | OOOOxx - 6701 | 7 | 1 | 1 | 1 | 1 | 1 | 701 | 701 | 1701 | 6701 | 2 | 3 | TXAAAA | HAAAAA | VVVVxx - 4321 | 8 | 1 | 1 | 1 | 1 | 21 | 321 | 321 | 4321 | 4321 | 42 | 43 | FKAAAA | IAAAAA | AAAAxx - 3043 | 9 | 1 | 3 | 3 | 3 | 43 | 43 | 1043 | 3043 | 3043 | 86 | 87 | BNAAAA | JAAAAA | HHHHxx - 1314 | 10 | 0 | 2 | 4 | 14 | 14 | 314 | 1314 | 1314 | 1314 | 28 | 29 | OYAAAA | KAAAAA | OOOOxx - 1504 | 11 | 0 | 0 | 4 | 4 | 4 | 504 | 1504 | 1504 | 1504 | 8 | 9 | WFAAAA | LAAAAA | VVVVxx - 5222 | 12 | 0 | 2 | 2 | 2 | 22 | 222 | 1222 | 222 | 5222 | 44 | 45 | WSAAAA | MAAAAA | AAAAxx - 6243 | 13 | 1 | 3 | 3 | 3 | 43 | 243 | 243 | 1243 | 6243 | 86 | 87 | DGAAAA | NAAAAA | HHHHxx -(14 rows) - -FETCH 15 in foo15; - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- - 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx - 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx - 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx - 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx - 7164 | 4 | 0 | 0 | 4 | 4 | 64 | 164 | 1164 | 2164 | 7164 | 128 | 129 | OPAAAA | EAAAAA | AAAAxx - 8009 | 5 | 1 | 1 | 9 | 9 | 9 | 9 | 9 | 3009 | 8009 | 18 | 19 | BWAAAA | FAAAAA | HHHHxx - 5057 | 6 | 1 | 1 | 7 | 17 | 57 | 57 | 1057 | 57 | 5057 | 114 | 115 | NMAAAA | GAAAAA | OOOOxx - 6701 | 7 | 1 | 1 | 1 | 1 | 1 | 701 | 701 | 1701 | 6701 | 2 | 3 | TXAAAA | HAAAAA | VVVVxx - 4321 | 8 | 1 | 1 | 1 | 1 | 21 | 321 | 321 | 4321 | 4321 | 42 | 43 | FKAAAA | IAAAAA | AAAAxx - 3043 | 9 | 1 | 3 | 3 | 3 | 43 | 43 | 1043 | 3043 | 3043 | 86 | 87 | BNAAAA | JAAAAA | HHHHxx - 1314 | 10 | 0 | 2 | 4 | 14 | 14 | 314 | 1314 | 1314 | 1314 | 28 | 29 | OYAAAA | KAAAAA | OOOOxx - 1504 | 11 | 0 | 0 | 4 | 4 | 4 | 504 | 1504 | 1504 | 1504 | 8 | 9 | WFAAAA | LAAAAA | VVVVxx - 5222 | 12 | 0 | 2 | 2 | 2 | 22 | 222 | 1222 | 222 | 5222 | 44 | 45 | WSAAAA | MAAAAA | AAAAxx - 6243 | 13 | 1 | 3 | 3 | 3 | 43 | 243 | 243 | 1243 | 6243 | 86 | 87 | DGAAAA | NAAAAA | HHHHxx - 5471 | 14 | 1 | 3 | 1 | 11 | 71 | 471 | 1471 | 471 | 5471 | 142 | 143 | LCAAAA | OAAAAA | OOOOxx -(15 rows) - -FETCH 16 in foo16; - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- - 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx - 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx - 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx - 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx - 7164 | 4 | 0 | 0 | 4 | 4 | 64 | 164 | 1164 | 2164 | 7164 | 128 | 129 | OPAAAA | EAAAAA | AAAAxx - 8009 | 5 | 1 | 1 | 9 | 9 | 9 | 9 | 9 | 3009 | 8009 | 18 | 19 | BWAAAA | FAAAAA | HHHHxx - 5057 | 6 | 1 | 1 | 7 | 17 | 57 | 57 | 1057 | 57 | 5057 | 114 | 115 | NMAAAA | GAAAAA | OOOOxx - 6701 | 7 | 1 | 1 | 1 | 1 | 1 | 701 | 701 | 1701 | 6701 | 2 | 3 | TXAAAA | HAAAAA | VVVVxx - 4321 | 8 | 1 | 1 | 1 | 1 | 21 | 321 | 321 | 4321 | 4321 | 42 | 43 | FKAAAA | IAAAAA | AAAAxx - 3043 | 9 | 1 | 3 | 3 | 3 | 43 | 43 | 1043 | 3043 | 3043 | 86 | 87 | BNAAAA | JAAAAA | HHHHxx - 1314 | 10 | 0 | 2 | 4 | 14 | 14 | 314 | 1314 | 1314 | 1314 | 28 | 29 | OYAAAA | KAAAAA | OOOOxx - 1504 | 11 | 0 | 0 | 4 | 4 | 4 | 504 | 1504 | 1504 | 1504 | 8 | 9 | WFAAAA | LAAAAA | VVVVxx - 5222 | 12 | 0 | 2 | 2 | 2 | 22 | 222 | 1222 | 222 | 5222 | 44 | 45 | WSAAAA | MAAAAA | AAAAxx - 6243 | 13 | 1 | 3 | 3 | 3 | 43 | 243 | 243 | 1243 | 6243 | 86 | 87 | DGAAAA | NAAAAA | HHHHxx - 5471 | 14 | 1 | 3 | 1 | 11 | 71 | 471 | 1471 | 471 | 5471 | 142 | 143 | LCAAAA | OAAAAA | OOOOxx - 5006 | 15 | 0 | 2 | 6 | 6 | 6 | 6 | 1006 | 6 | 5006 | 12 | 13 | OKAAAA | PAAAAA | VVVVxx -(16 rows) - -FETCH 17 in foo17; - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- - 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx - 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx - 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx - 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx - 7164 | 4 | 0 | 0 | 4 | 4 | 64 | 164 | 1164 | 2164 | 7164 | 128 | 129 | OPAAAA | EAAAAA | AAAAxx - 8009 | 5 | 1 | 1 | 9 | 9 | 9 | 9 | 9 | 3009 | 8009 | 18 | 19 | BWAAAA | FAAAAA | HHHHxx - 5057 | 6 | 1 | 1 | 7 | 17 | 57 | 57 | 1057 | 57 | 5057 | 114 | 115 | NMAAAA | GAAAAA | OOOOxx - 6701 | 7 | 1 | 1 | 1 | 1 | 1 | 701 | 701 | 1701 | 6701 | 2 | 3 | TXAAAA | HAAAAA | VVVVxx - 4321 | 8 | 1 | 1 | 1 | 1 | 21 | 321 | 321 | 4321 | 4321 | 42 | 43 | FKAAAA | IAAAAA | AAAAxx - 3043 | 9 | 1 | 3 | 3 | 3 | 43 | 43 | 1043 | 3043 | 3043 | 86 | 87 | BNAAAA | JAAAAA | HHHHxx - 1314 | 10 | 0 | 2 | 4 | 14 | 14 | 314 | 1314 | 1314 | 1314 | 28 | 29 | OYAAAA | KAAAAA | OOOOxx - 1504 | 11 | 0 | 0 | 4 | 4 | 4 | 504 | 1504 | 1504 | 1504 | 8 | 9 | WFAAAA | LAAAAA | VVVVxx - 5222 | 12 | 0 | 2 | 2 | 2 | 22 | 222 | 1222 | 222 | 5222 | 44 | 45 | WSAAAA | MAAAAA | AAAAxx - 6243 | 13 | 1 | 3 | 3 | 3 | 43 | 243 | 243 | 1243 | 6243 | 86 | 87 | DGAAAA | NAAAAA | HHHHxx - 5471 | 14 | 1 | 3 | 1 | 11 | 71 | 471 | 1471 | 471 | 5471 | 142 | 143 | LCAAAA | OAAAAA | OOOOxx - 5006 | 15 | 0 | 2 | 6 | 6 | 6 | 6 | 1006 | 6 | 5006 | 12 | 13 | OKAAAA | PAAAAA | VVVVxx - 5387 | 16 | 1 | 3 | 7 | 7 | 87 | 387 | 1387 | 387 | 5387 | 174 | 175 | FZAAAA | QAAAAA | AAAAxx -(17 rows) - -FETCH 18 in foo18; - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- - 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx - 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx - 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx - 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx - 7164 | 4 | 0 | 0 | 4 | 4 | 64 | 164 | 1164 | 2164 | 7164 | 128 | 129 | OPAAAA | EAAAAA | AAAAxx - 8009 | 5 | 1 | 1 | 9 | 9 | 9 | 9 | 9 | 3009 | 8009 | 18 | 19 | BWAAAA | FAAAAA | HHHHxx - 5057 | 6 | 1 | 1 | 7 | 17 | 57 | 57 | 1057 | 57 | 5057 | 114 | 115 | NMAAAA | GAAAAA | OOOOxx - 6701 | 7 | 1 | 1 | 1 | 1 | 1 | 701 | 701 | 1701 | 6701 | 2 | 3 | TXAAAA | HAAAAA | VVVVxx - 4321 | 8 | 1 | 1 | 1 | 1 | 21 | 321 | 321 | 4321 | 4321 | 42 | 43 | FKAAAA | IAAAAA | AAAAxx - 3043 | 9 | 1 | 3 | 3 | 3 | 43 | 43 | 1043 | 3043 | 3043 | 86 | 87 | BNAAAA | JAAAAA | HHHHxx - 1314 | 10 | 0 | 2 | 4 | 14 | 14 | 314 | 1314 | 1314 | 1314 | 28 | 29 | OYAAAA | KAAAAA | OOOOxx - 1504 | 11 | 0 | 0 | 4 | 4 | 4 | 504 | 1504 | 1504 | 1504 | 8 | 9 | WFAAAA | LAAAAA | VVVVxx - 5222 | 12 | 0 | 2 | 2 | 2 | 22 | 222 | 1222 | 222 | 5222 | 44 | 45 | WSAAAA | MAAAAA | AAAAxx - 6243 | 13 | 1 | 3 | 3 | 3 | 43 | 243 | 243 | 1243 | 6243 | 86 | 87 | DGAAAA | NAAAAA | HHHHxx - 5471 | 14 | 1 | 3 | 1 | 11 | 71 | 471 | 1471 | 471 | 5471 | 142 | 143 | LCAAAA | OAAAAA | OOOOxx - 5006 | 15 | 0 | 2 | 6 | 6 | 6 | 6 | 1006 | 6 | 5006 | 12 | 13 | OKAAAA | PAAAAA | VVVVxx - 5387 | 16 | 1 | 3 | 7 | 7 | 87 | 387 | 1387 | 387 | 5387 | 174 | 175 | FZAAAA | QAAAAA | AAAAxx - 5785 | 17 | 1 | 1 | 5 | 5 | 85 | 785 | 1785 | 785 | 5785 | 170 | 171 | NOAAAA | RAAAAA | HHHHxx -(18 rows) - -FETCH 19 in foo19; - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- - 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx - 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx - 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx - 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx - 7164 | 4 | 0 | 0 | 4 | 4 | 64 | 164 | 1164 | 2164 | 7164 | 128 | 129 | OPAAAA | EAAAAA | AAAAxx - 8009 | 5 | 1 | 1 | 9 | 9 | 9 | 9 | 9 | 3009 | 8009 | 18 | 19 | BWAAAA | FAAAAA | HHHHxx - 5057 | 6 | 1 | 1 | 7 | 17 | 57 | 57 | 1057 | 57 | 5057 | 114 | 115 | NMAAAA | GAAAAA | OOOOxx - 6701 | 7 | 1 | 1 | 1 | 1 | 1 | 701 | 701 | 1701 | 6701 | 2 | 3 | TXAAAA | HAAAAA | VVVVxx - 4321 | 8 | 1 | 1 | 1 | 1 | 21 | 321 | 321 | 4321 | 4321 | 42 | 43 | FKAAAA | IAAAAA | AAAAxx - 3043 | 9 | 1 | 3 | 3 | 3 | 43 | 43 | 1043 | 3043 | 3043 | 86 | 87 | BNAAAA | JAAAAA | HHHHxx - 1314 | 10 | 0 | 2 | 4 | 14 | 14 | 314 | 1314 | 1314 | 1314 | 28 | 29 | OYAAAA | KAAAAA | OOOOxx - 1504 | 11 | 0 | 0 | 4 | 4 | 4 | 504 | 1504 | 1504 | 1504 | 8 | 9 | WFAAAA | LAAAAA | VVVVxx - 5222 | 12 | 0 | 2 | 2 | 2 | 22 | 222 | 1222 | 222 | 5222 | 44 | 45 | WSAAAA | MAAAAA | AAAAxx - 6243 | 13 | 1 | 3 | 3 | 3 | 43 | 243 | 243 | 1243 | 6243 | 86 | 87 | DGAAAA | NAAAAA | HHHHxx - 5471 | 14 | 1 | 3 | 1 | 11 | 71 | 471 | 1471 | 471 | 5471 | 142 | 143 | LCAAAA | OAAAAA | OOOOxx - 5006 | 15 | 0 | 2 | 6 | 6 | 6 | 6 | 1006 | 6 | 5006 | 12 | 13 | OKAAAA | PAAAAA | VVVVxx - 5387 | 16 | 1 | 3 | 7 | 7 | 87 | 387 | 1387 | 387 | 5387 | 174 | 175 | FZAAAA | QAAAAA | AAAAxx - 5785 | 17 | 1 | 1 | 5 | 5 | 85 | 785 | 1785 | 785 | 5785 | 170 | 171 | NOAAAA | RAAAAA | HHHHxx - 6621 | 18 | 1 | 1 | 1 | 1 | 21 | 621 | 621 | 1621 | 6621 | 42 | 43 | RUAAAA | SAAAAA | OOOOxx -(19 rows) - -FETCH 20 in foo20; - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- - 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx - 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx - 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx - 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx - 7164 | 4 | 0 | 0 | 4 | 4 | 64 | 164 | 1164 | 2164 | 7164 | 128 | 129 | OPAAAA | EAAAAA | AAAAxx - 8009 | 5 | 1 | 1 | 9 | 9 | 9 | 9 | 9 | 3009 | 8009 | 18 | 19 | BWAAAA | FAAAAA | HHHHxx - 5057 | 6 | 1 | 1 | 7 | 17 | 57 | 57 | 1057 | 57 | 5057 | 114 | 115 | NMAAAA | GAAAAA | OOOOxx - 6701 | 7 | 1 | 1 | 1 | 1 | 1 | 701 | 701 | 1701 | 6701 | 2 | 3 | TXAAAA | HAAAAA | VVVVxx - 4321 | 8 | 1 | 1 | 1 | 1 | 21 | 321 | 321 | 4321 | 4321 | 42 | 43 | FKAAAA | IAAAAA | AAAAxx - 3043 | 9 | 1 | 3 | 3 | 3 | 43 | 43 | 1043 | 3043 | 3043 | 86 | 87 | BNAAAA | JAAAAA | HHHHxx - 1314 | 10 | 0 | 2 | 4 | 14 | 14 | 314 | 1314 | 1314 | 1314 | 28 | 29 | OYAAAA | KAAAAA | OOOOxx - 1504 | 11 | 0 | 0 | 4 | 4 | 4 | 504 | 1504 | 1504 | 1504 | 8 | 9 | WFAAAA | LAAAAA | VVVVxx - 5222 | 12 | 0 | 2 | 2 | 2 | 22 | 222 | 1222 | 222 | 5222 | 44 | 45 | WSAAAA | MAAAAA | AAAAxx - 6243 | 13 | 1 | 3 | 3 | 3 | 43 | 243 | 243 | 1243 | 6243 | 86 | 87 | DGAAAA | NAAAAA | HHHHxx - 5471 | 14 | 1 | 3 | 1 | 11 | 71 | 471 | 1471 | 471 | 5471 | 142 | 143 | LCAAAA | OAAAAA | OOOOxx - 5006 | 15 | 0 | 2 | 6 | 6 | 6 | 6 | 1006 | 6 | 5006 | 12 | 13 | OKAAAA | PAAAAA | VVVVxx - 5387 | 16 | 1 | 3 | 7 | 7 | 87 | 387 | 1387 | 387 | 5387 | 174 | 175 | FZAAAA | QAAAAA | AAAAxx - 5785 | 17 | 1 | 1 | 5 | 5 | 85 | 785 | 1785 | 785 | 5785 | 170 | 171 | NOAAAA | RAAAAA | HHHHxx - 6621 | 18 | 1 | 1 | 1 | 1 | 21 | 621 | 621 | 1621 | 6621 | 42 | 43 | RUAAAA | SAAAAA | OOOOxx - 6969 | 19 | 1 | 1 | 9 | 9 | 69 | 969 | 969 | 1969 | 6969 | 138 | 139 | BIAAAA | TAAAAA | VVVVxx -(20 rows) - -FETCH 21 in foo21; - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- - 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx - 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx - 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx - 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx - 7164 | 4 | 0 | 0 | 4 | 4 | 64 | 164 | 1164 | 2164 | 7164 | 128 | 129 | OPAAAA | EAAAAA | AAAAxx - 8009 | 5 | 1 | 1 | 9 | 9 | 9 | 9 | 9 | 3009 | 8009 | 18 | 19 | BWAAAA | FAAAAA | HHHHxx - 5057 | 6 | 1 | 1 | 7 | 17 | 57 | 57 | 1057 | 57 | 5057 | 114 | 115 | NMAAAA | GAAAAA | OOOOxx - 6701 | 7 | 1 | 1 | 1 | 1 | 1 | 701 | 701 | 1701 | 6701 | 2 | 3 | TXAAAA | HAAAAA | VVVVxx - 4321 | 8 | 1 | 1 | 1 | 1 | 21 | 321 | 321 | 4321 | 4321 | 42 | 43 | FKAAAA | IAAAAA | AAAAxx - 3043 | 9 | 1 | 3 | 3 | 3 | 43 | 43 | 1043 | 3043 | 3043 | 86 | 87 | BNAAAA | JAAAAA | HHHHxx - 1314 | 10 | 0 | 2 | 4 | 14 | 14 | 314 | 1314 | 1314 | 1314 | 28 | 29 | OYAAAA | KAAAAA | OOOOxx - 1504 | 11 | 0 | 0 | 4 | 4 | 4 | 504 | 1504 | 1504 | 1504 | 8 | 9 | WFAAAA | LAAAAA | VVVVxx - 5222 | 12 | 0 | 2 | 2 | 2 | 22 | 222 | 1222 | 222 | 5222 | 44 | 45 | WSAAAA | MAAAAA | AAAAxx - 6243 | 13 | 1 | 3 | 3 | 3 | 43 | 243 | 243 | 1243 | 6243 | 86 | 87 | DGAAAA | NAAAAA | HHHHxx - 5471 | 14 | 1 | 3 | 1 | 11 | 71 | 471 | 1471 | 471 | 5471 | 142 | 143 | LCAAAA | OAAAAA | OOOOxx - 5006 | 15 | 0 | 2 | 6 | 6 | 6 | 6 | 1006 | 6 | 5006 | 12 | 13 | OKAAAA | PAAAAA | VVVVxx - 5387 | 16 | 1 | 3 | 7 | 7 | 87 | 387 | 1387 | 387 | 5387 | 174 | 175 | FZAAAA | QAAAAA | AAAAxx - 5785 | 17 | 1 | 1 | 5 | 5 | 85 | 785 | 1785 | 785 | 5785 | 170 | 171 | NOAAAA | RAAAAA | HHHHxx - 6621 | 18 | 1 | 1 | 1 | 1 | 21 | 621 | 621 | 1621 | 6621 | 42 | 43 | RUAAAA | SAAAAA | OOOOxx - 6969 | 19 | 1 | 1 | 9 | 9 | 69 | 969 | 969 | 1969 | 6969 | 138 | 139 | BIAAAA | TAAAAA | VVVVxx - 9460 | 20 | 0 | 0 | 0 | 0 | 60 | 460 | 1460 | 4460 | 9460 | 120 | 121 | WZAAAA | UAAAAA | AAAAxx -(21 rows) - -FETCH 22 in foo22; - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- - 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx - 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx - 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx - 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx - 7164 | 4 | 0 | 0 | 4 | 4 | 64 | 164 | 1164 | 2164 | 7164 | 128 | 129 | OPAAAA | EAAAAA | AAAAxx - 8009 | 5 | 1 | 1 | 9 | 9 | 9 | 9 | 9 | 3009 | 8009 | 18 | 19 | BWAAAA | FAAAAA | HHHHxx - 5057 | 6 | 1 | 1 | 7 | 17 | 57 | 57 | 1057 | 57 | 5057 | 114 | 115 | NMAAAA | GAAAAA | OOOOxx - 6701 | 7 | 1 | 1 | 1 | 1 | 1 | 701 | 701 | 1701 | 6701 | 2 | 3 | TXAAAA | HAAAAA | VVVVxx - 4321 | 8 | 1 | 1 | 1 | 1 | 21 | 321 | 321 | 4321 | 4321 | 42 | 43 | FKAAAA | IAAAAA | AAAAxx - 3043 | 9 | 1 | 3 | 3 | 3 | 43 | 43 | 1043 | 3043 | 3043 | 86 | 87 | BNAAAA | JAAAAA | HHHHxx - 1314 | 10 | 0 | 2 | 4 | 14 | 14 | 314 | 1314 | 1314 | 1314 | 28 | 29 | OYAAAA | KAAAAA | OOOOxx - 1504 | 11 | 0 | 0 | 4 | 4 | 4 | 504 | 1504 | 1504 | 1504 | 8 | 9 | WFAAAA | LAAAAA | VVVVxx - 5222 | 12 | 0 | 2 | 2 | 2 | 22 | 222 | 1222 | 222 | 5222 | 44 | 45 | WSAAAA | MAAAAA | AAAAxx - 6243 | 13 | 1 | 3 | 3 | 3 | 43 | 243 | 243 | 1243 | 6243 | 86 | 87 | DGAAAA | NAAAAA | HHHHxx - 5471 | 14 | 1 | 3 | 1 | 11 | 71 | 471 | 1471 | 471 | 5471 | 142 | 143 | LCAAAA | OAAAAA | OOOOxx - 5006 | 15 | 0 | 2 | 6 | 6 | 6 | 6 | 1006 | 6 | 5006 | 12 | 13 | OKAAAA | PAAAAA | VVVVxx - 5387 | 16 | 1 | 3 | 7 | 7 | 87 | 387 | 1387 | 387 | 5387 | 174 | 175 | FZAAAA | QAAAAA | AAAAxx - 5785 | 17 | 1 | 1 | 5 | 5 | 85 | 785 | 1785 | 785 | 5785 | 170 | 171 | NOAAAA | RAAAAA | HHHHxx - 6621 | 18 | 1 | 1 | 1 | 1 | 21 | 621 | 621 | 1621 | 6621 | 42 | 43 | RUAAAA | SAAAAA | OOOOxx - 6969 | 19 | 1 | 1 | 9 | 9 | 69 | 969 | 969 | 1969 | 6969 | 138 | 139 | BIAAAA | TAAAAA | VVVVxx - 9460 | 20 | 0 | 0 | 0 | 0 | 60 | 460 | 1460 | 4460 | 9460 | 120 | 121 | WZAAAA | UAAAAA | AAAAxx - 59 | 21 | 1 | 3 | 9 | 19 | 59 | 59 | 59 | 59 | 59 | 118 | 119 | HCAAAA | VAAAAA | HHHHxx -(22 rows) - -FETCH 23 in foo23; - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- - 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx - 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx - 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx - 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx - 7164 | 4 | 0 | 0 | 4 | 4 | 64 | 164 | 1164 | 2164 | 7164 | 128 | 129 | OPAAAA | EAAAAA | AAAAxx - 8009 | 5 | 1 | 1 | 9 | 9 | 9 | 9 | 9 | 3009 | 8009 | 18 | 19 | BWAAAA | FAAAAA | HHHHxx - 5057 | 6 | 1 | 1 | 7 | 17 | 57 | 57 | 1057 | 57 | 5057 | 114 | 115 | NMAAAA | GAAAAA | OOOOxx - 6701 | 7 | 1 | 1 | 1 | 1 | 1 | 701 | 701 | 1701 | 6701 | 2 | 3 | TXAAAA | HAAAAA | VVVVxx - 4321 | 8 | 1 | 1 | 1 | 1 | 21 | 321 | 321 | 4321 | 4321 | 42 | 43 | FKAAAA | IAAAAA | AAAAxx - 3043 | 9 | 1 | 3 | 3 | 3 | 43 | 43 | 1043 | 3043 | 3043 | 86 | 87 | BNAAAA | JAAAAA | HHHHxx - 1314 | 10 | 0 | 2 | 4 | 14 | 14 | 314 | 1314 | 1314 | 1314 | 28 | 29 | OYAAAA | KAAAAA | OOOOxx - 1504 | 11 | 0 | 0 | 4 | 4 | 4 | 504 | 1504 | 1504 | 1504 | 8 | 9 | WFAAAA | LAAAAA | VVVVxx - 5222 | 12 | 0 | 2 | 2 | 2 | 22 | 222 | 1222 | 222 | 5222 | 44 | 45 | WSAAAA | MAAAAA | AAAAxx - 6243 | 13 | 1 | 3 | 3 | 3 | 43 | 243 | 243 | 1243 | 6243 | 86 | 87 | DGAAAA | NAAAAA | HHHHxx - 5471 | 14 | 1 | 3 | 1 | 11 | 71 | 471 | 1471 | 471 | 5471 | 142 | 143 | LCAAAA | OAAAAA | OOOOxx - 5006 | 15 | 0 | 2 | 6 | 6 | 6 | 6 | 1006 | 6 | 5006 | 12 | 13 | OKAAAA | PAAAAA | VVVVxx - 5387 | 16 | 1 | 3 | 7 | 7 | 87 | 387 | 1387 | 387 | 5387 | 174 | 175 | FZAAAA | QAAAAA | AAAAxx - 5785 | 17 | 1 | 1 | 5 | 5 | 85 | 785 | 1785 | 785 | 5785 | 170 | 171 | NOAAAA | RAAAAA | HHHHxx - 6621 | 18 | 1 | 1 | 1 | 1 | 21 | 621 | 621 | 1621 | 6621 | 42 | 43 | RUAAAA | SAAAAA | OOOOxx - 6969 | 19 | 1 | 1 | 9 | 9 | 69 | 969 | 969 | 1969 | 6969 | 138 | 139 | BIAAAA | TAAAAA | VVVVxx - 9460 | 20 | 0 | 0 | 0 | 0 | 60 | 460 | 1460 | 4460 | 9460 | 120 | 121 | WZAAAA | UAAAAA | AAAAxx - 59 | 21 | 1 | 3 | 9 | 19 | 59 | 59 | 59 | 59 | 59 | 118 | 119 | HCAAAA | VAAAAA | HHHHxx - 8020 | 22 | 0 | 0 | 0 | 0 | 20 | 20 | 20 | 3020 | 8020 | 40 | 41 | MWAAAA | WAAAAA | OOOOxx -(23 rows) - -FETCH backward 1 in foo23; - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- - 59 | 21 | 1 | 3 | 9 | 19 | 59 | 59 | 59 | 59 | 59 | 118 | 119 | HCAAAA | VAAAAA | HHHHxx -(1 row) - -FETCH backward 2 in foo22; - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- - 9460 | 20 | 0 | 0 | 0 | 0 | 60 | 460 | 1460 | 4460 | 9460 | 120 | 121 | WZAAAA | UAAAAA | AAAAxx - 6969 | 19 | 1 | 1 | 9 | 9 | 69 | 969 | 969 | 1969 | 6969 | 138 | 139 | BIAAAA | TAAAAA | VVVVxx -(2 rows) - -FETCH backward 3 in foo21; - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- - 6969 | 19 | 1 | 1 | 9 | 9 | 69 | 969 | 969 | 1969 | 6969 | 138 | 139 | BIAAAA | TAAAAA | VVVVxx - 6621 | 18 | 1 | 1 | 1 | 1 | 21 | 621 | 621 | 1621 | 6621 | 42 | 43 | RUAAAA | SAAAAA | OOOOxx - 5785 | 17 | 1 | 1 | 5 | 5 | 85 | 785 | 1785 | 785 | 5785 | 170 | 171 | NOAAAA | RAAAAA | HHHHxx -(3 rows) - -FETCH backward 4 in foo20; - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- - 6621 | 18 | 1 | 1 | 1 | 1 | 21 | 621 | 621 | 1621 | 6621 | 42 | 43 | RUAAAA | SAAAAA | OOOOxx - 5785 | 17 | 1 | 1 | 5 | 5 | 85 | 785 | 1785 | 785 | 5785 | 170 | 171 | NOAAAA | RAAAAA | HHHHxx - 5387 | 16 | 1 | 3 | 7 | 7 | 87 | 387 | 1387 | 387 | 5387 | 174 | 175 | FZAAAA | QAAAAA | AAAAxx - 5006 | 15 | 0 | 2 | 6 | 6 | 6 | 6 | 1006 | 6 | 5006 | 12 | 13 | OKAAAA | PAAAAA | VVVVxx -(4 rows) - -FETCH backward 5 in foo19; - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- - 5785 | 17 | 1 | 1 | 5 | 5 | 85 | 785 | 1785 | 785 | 5785 | 170 | 171 | NOAAAA | RAAAAA | HHHHxx - 5387 | 16 | 1 | 3 | 7 | 7 | 87 | 387 | 1387 | 387 | 5387 | 174 | 175 | FZAAAA | QAAAAA | AAAAxx - 5006 | 15 | 0 | 2 | 6 | 6 | 6 | 6 | 1006 | 6 | 5006 | 12 | 13 | OKAAAA | PAAAAA | VVVVxx - 5471 | 14 | 1 | 3 | 1 | 11 | 71 | 471 | 1471 | 471 | 5471 | 142 | 143 | LCAAAA | OAAAAA | OOOOxx - 6243 | 13 | 1 | 3 | 3 | 3 | 43 | 243 | 243 | 1243 | 6243 | 86 | 87 | DGAAAA | NAAAAA | HHHHxx -(5 rows) - -FETCH backward 6 in foo18; - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- - 5387 | 16 | 1 | 3 | 7 | 7 | 87 | 387 | 1387 | 387 | 5387 | 174 | 175 | FZAAAA | QAAAAA | AAAAxx - 5006 | 15 | 0 | 2 | 6 | 6 | 6 | 6 | 1006 | 6 | 5006 | 12 | 13 | OKAAAA | PAAAAA | VVVVxx - 5471 | 14 | 1 | 3 | 1 | 11 | 71 | 471 | 1471 | 471 | 5471 | 142 | 143 | LCAAAA | OAAAAA | OOOOxx - 6243 | 13 | 1 | 3 | 3 | 3 | 43 | 243 | 243 | 1243 | 6243 | 86 | 87 | DGAAAA | NAAAAA | HHHHxx - 5222 | 12 | 0 | 2 | 2 | 2 | 22 | 222 | 1222 | 222 | 5222 | 44 | 45 | WSAAAA | MAAAAA | AAAAxx - 1504 | 11 | 0 | 0 | 4 | 4 | 4 | 504 | 1504 | 1504 | 1504 | 8 | 9 | WFAAAA | LAAAAA | VVVVxx -(6 rows) - -FETCH backward 7 in foo17; - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- - 5006 | 15 | 0 | 2 | 6 | 6 | 6 | 6 | 1006 | 6 | 5006 | 12 | 13 | OKAAAA | PAAAAA | VVVVxx - 5471 | 14 | 1 | 3 | 1 | 11 | 71 | 471 | 1471 | 471 | 5471 | 142 | 143 | LCAAAA | OAAAAA | OOOOxx - 6243 | 13 | 1 | 3 | 3 | 3 | 43 | 243 | 243 | 1243 | 6243 | 86 | 87 | DGAAAA | NAAAAA | HHHHxx - 5222 | 12 | 0 | 2 | 2 | 2 | 22 | 222 | 1222 | 222 | 5222 | 44 | 45 | WSAAAA | MAAAAA | AAAAxx - 1504 | 11 | 0 | 0 | 4 | 4 | 4 | 504 | 1504 | 1504 | 1504 | 8 | 9 | WFAAAA | LAAAAA | VVVVxx - 1314 | 10 | 0 | 2 | 4 | 14 | 14 | 314 | 1314 | 1314 | 1314 | 28 | 29 | OYAAAA | KAAAAA | OOOOxx - 3043 | 9 | 1 | 3 | 3 | 3 | 43 | 43 | 1043 | 3043 | 3043 | 86 | 87 | BNAAAA | JAAAAA | HHHHxx -(7 rows) - -FETCH backward 8 in foo16; - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- - 5471 | 14 | 1 | 3 | 1 | 11 | 71 | 471 | 1471 | 471 | 5471 | 142 | 143 | LCAAAA | OAAAAA | OOOOxx - 6243 | 13 | 1 | 3 | 3 | 3 | 43 | 243 | 243 | 1243 | 6243 | 86 | 87 | DGAAAA | NAAAAA | HHHHxx - 5222 | 12 | 0 | 2 | 2 | 2 | 22 | 222 | 1222 | 222 | 5222 | 44 | 45 | WSAAAA | MAAAAA | AAAAxx - 1504 | 11 | 0 | 0 | 4 | 4 | 4 | 504 | 1504 | 1504 | 1504 | 8 | 9 | WFAAAA | LAAAAA | VVVVxx - 1314 | 10 | 0 | 2 | 4 | 14 | 14 | 314 | 1314 | 1314 | 1314 | 28 | 29 | OYAAAA | KAAAAA | OOOOxx - 3043 | 9 | 1 | 3 | 3 | 3 | 43 | 43 | 1043 | 3043 | 3043 | 86 | 87 | BNAAAA | JAAAAA | HHHHxx - 4321 | 8 | 1 | 1 | 1 | 1 | 21 | 321 | 321 | 4321 | 4321 | 42 | 43 | FKAAAA | IAAAAA | AAAAxx - 6701 | 7 | 1 | 1 | 1 | 1 | 1 | 701 | 701 | 1701 | 6701 | 2 | 3 | TXAAAA | HAAAAA | VVVVxx -(8 rows) - -FETCH backward 9 in foo15; - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- - 6243 | 13 | 1 | 3 | 3 | 3 | 43 | 243 | 243 | 1243 | 6243 | 86 | 87 | DGAAAA | NAAAAA | HHHHxx - 5222 | 12 | 0 | 2 | 2 | 2 | 22 | 222 | 1222 | 222 | 5222 | 44 | 45 | WSAAAA | MAAAAA | AAAAxx - 1504 | 11 | 0 | 0 | 4 | 4 | 4 | 504 | 1504 | 1504 | 1504 | 8 | 9 | WFAAAA | LAAAAA | VVVVxx - 1314 | 10 | 0 | 2 | 4 | 14 | 14 | 314 | 1314 | 1314 | 1314 | 28 | 29 | OYAAAA | KAAAAA | OOOOxx - 3043 | 9 | 1 | 3 | 3 | 3 | 43 | 43 | 1043 | 3043 | 3043 | 86 | 87 | BNAAAA | JAAAAA | HHHHxx - 4321 | 8 | 1 | 1 | 1 | 1 | 21 | 321 | 321 | 4321 | 4321 | 42 | 43 | FKAAAA | IAAAAA | AAAAxx - 6701 | 7 | 1 | 1 | 1 | 1 | 1 | 701 | 701 | 1701 | 6701 | 2 | 3 | TXAAAA | HAAAAA | VVVVxx - 5057 | 6 | 1 | 1 | 7 | 17 | 57 | 57 | 1057 | 57 | 5057 | 114 | 115 | NMAAAA | GAAAAA | OOOOxx - 8009 | 5 | 1 | 1 | 9 | 9 | 9 | 9 | 9 | 3009 | 8009 | 18 | 19 | BWAAAA | FAAAAA | HHHHxx -(9 rows) - -FETCH backward 10 in foo14; - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- - 5222 | 12 | 0 | 2 | 2 | 2 | 22 | 222 | 1222 | 222 | 5222 | 44 | 45 | WSAAAA | MAAAAA | AAAAxx - 1504 | 11 | 0 | 0 | 4 | 4 | 4 | 504 | 1504 | 1504 | 1504 | 8 | 9 | WFAAAA | LAAAAA | VVVVxx - 1314 | 10 | 0 | 2 | 4 | 14 | 14 | 314 | 1314 | 1314 | 1314 | 28 | 29 | OYAAAA | KAAAAA | OOOOxx - 3043 | 9 | 1 | 3 | 3 | 3 | 43 | 43 | 1043 | 3043 | 3043 | 86 | 87 | BNAAAA | JAAAAA | HHHHxx - 4321 | 8 | 1 | 1 | 1 | 1 | 21 | 321 | 321 | 4321 | 4321 | 42 | 43 | FKAAAA | IAAAAA | AAAAxx - 6701 | 7 | 1 | 1 | 1 | 1 | 1 | 701 | 701 | 1701 | 6701 | 2 | 3 | TXAAAA | HAAAAA | VVVVxx - 5057 | 6 | 1 | 1 | 7 | 17 | 57 | 57 | 1057 | 57 | 5057 | 114 | 115 | NMAAAA | GAAAAA | OOOOxx - 8009 | 5 | 1 | 1 | 9 | 9 | 9 | 9 | 9 | 3009 | 8009 | 18 | 19 | BWAAAA | FAAAAA | HHHHxx - 7164 | 4 | 0 | 0 | 4 | 4 | 64 | 164 | 1164 | 2164 | 7164 | 128 | 129 | OPAAAA | EAAAAA | AAAAxx - 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx -(10 rows) - -FETCH backward 11 in foo13; - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- - 1504 | 11 | 0 | 0 | 4 | 4 | 4 | 504 | 1504 | 1504 | 1504 | 8 | 9 | WFAAAA | LAAAAA | VVVVxx - 1314 | 10 | 0 | 2 | 4 | 14 | 14 | 314 | 1314 | 1314 | 1314 | 28 | 29 | OYAAAA | KAAAAA | OOOOxx - 3043 | 9 | 1 | 3 | 3 | 3 | 43 | 43 | 1043 | 3043 | 3043 | 86 | 87 | BNAAAA | JAAAAA | HHHHxx - 4321 | 8 | 1 | 1 | 1 | 1 | 21 | 321 | 321 | 4321 | 4321 | 42 | 43 | FKAAAA | IAAAAA | AAAAxx - 6701 | 7 | 1 | 1 | 1 | 1 | 1 | 701 | 701 | 1701 | 6701 | 2 | 3 | TXAAAA | HAAAAA | VVVVxx - 5057 | 6 | 1 | 1 | 7 | 17 | 57 | 57 | 1057 | 57 | 5057 | 114 | 115 | NMAAAA | GAAAAA | OOOOxx - 8009 | 5 | 1 | 1 | 9 | 9 | 9 | 9 | 9 | 3009 | 8009 | 18 | 19 | BWAAAA | FAAAAA | HHHHxx - 7164 | 4 | 0 | 0 | 4 | 4 | 64 | 164 | 1164 | 2164 | 7164 | 128 | 129 | OPAAAA | EAAAAA | AAAAxx - 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx - 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx - 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx -(11 rows) - -FETCH backward 12 in foo12; - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- - 1314 | 10 | 0 | 2 | 4 | 14 | 14 | 314 | 1314 | 1314 | 1314 | 28 | 29 | OYAAAA | KAAAAA | OOOOxx - 3043 | 9 | 1 | 3 | 3 | 3 | 43 | 43 | 1043 | 3043 | 3043 | 86 | 87 | BNAAAA | JAAAAA | HHHHxx - 4321 | 8 | 1 | 1 | 1 | 1 | 21 | 321 | 321 | 4321 | 4321 | 42 | 43 | FKAAAA | IAAAAA | AAAAxx - 6701 | 7 | 1 | 1 | 1 | 1 | 1 | 701 | 701 | 1701 | 6701 | 2 | 3 | TXAAAA | HAAAAA | VVVVxx - 5057 | 6 | 1 | 1 | 7 | 17 | 57 | 57 | 1057 | 57 | 5057 | 114 | 115 | NMAAAA | GAAAAA | OOOOxx - 8009 | 5 | 1 | 1 | 9 | 9 | 9 | 9 | 9 | 3009 | 8009 | 18 | 19 | BWAAAA | FAAAAA | HHHHxx - 7164 | 4 | 0 | 0 | 4 | 4 | 64 | 164 | 1164 | 2164 | 7164 | 128 | 129 | OPAAAA | EAAAAA | AAAAxx - 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx - 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx - 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx - 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx -(11 rows) - -FETCH backward 13 in foo11; - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- - 3043 | 9 | 1 | 3 | 3 | 3 | 43 | 43 | 1043 | 3043 | 3043 | 86 | 87 | BNAAAA | JAAAAA | HHHHxx - 4321 | 8 | 1 | 1 | 1 | 1 | 21 | 321 | 321 | 4321 | 4321 | 42 | 43 | FKAAAA | IAAAAA | AAAAxx - 6701 | 7 | 1 | 1 | 1 | 1 | 1 | 701 | 701 | 1701 | 6701 | 2 | 3 | TXAAAA | HAAAAA | VVVVxx - 5057 | 6 | 1 | 1 | 7 | 17 | 57 | 57 | 1057 | 57 | 5057 | 114 | 115 | NMAAAA | GAAAAA | OOOOxx - 8009 | 5 | 1 | 1 | 9 | 9 | 9 | 9 | 9 | 3009 | 8009 | 18 | 19 | BWAAAA | FAAAAA | HHHHxx - 7164 | 4 | 0 | 0 | 4 | 4 | 64 | 164 | 1164 | 2164 | 7164 | 128 | 129 | OPAAAA | EAAAAA | AAAAxx - 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx - 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx - 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx - 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx -(10 rows) - -FETCH backward 14 in foo10; - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- - 4321 | 8 | 1 | 1 | 1 | 1 | 21 | 321 | 321 | 4321 | 4321 | 42 | 43 | FKAAAA | IAAAAA | AAAAxx - 6701 | 7 | 1 | 1 | 1 | 1 | 1 | 701 | 701 | 1701 | 6701 | 2 | 3 | TXAAAA | HAAAAA | VVVVxx - 5057 | 6 | 1 | 1 | 7 | 17 | 57 | 57 | 1057 | 57 | 5057 | 114 | 115 | NMAAAA | GAAAAA | OOOOxx - 8009 | 5 | 1 | 1 | 9 | 9 | 9 | 9 | 9 | 3009 | 8009 | 18 | 19 | BWAAAA | FAAAAA | HHHHxx - 7164 | 4 | 0 | 0 | 4 | 4 | 64 | 164 | 1164 | 2164 | 7164 | 128 | 129 | OPAAAA | EAAAAA | AAAAxx - 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx - 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx - 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx - 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx -(9 rows) - -FETCH backward 15 in foo9; - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- - 6701 | 7 | 1 | 1 | 1 | 1 | 1 | 701 | 701 | 1701 | 6701 | 2 | 3 | TXAAAA | HAAAAA | VVVVxx - 5057 | 6 | 1 | 1 | 7 | 17 | 57 | 57 | 1057 | 57 | 5057 | 114 | 115 | NMAAAA | GAAAAA | OOOOxx - 8009 | 5 | 1 | 1 | 9 | 9 | 9 | 9 | 9 | 3009 | 8009 | 18 | 19 | BWAAAA | FAAAAA | HHHHxx - 7164 | 4 | 0 | 0 | 4 | 4 | 64 | 164 | 1164 | 2164 | 7164 | 128 | 129 | OPAAAA | EAAAAA | AAAAxx - 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx - 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx - 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx - 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx -(8 rows) - -FETCH backward 16 in foo8; - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- - 5057 | 6 | 1 | 1 | 7 | 17 | 57 | 57 | 1057 | 57 | 5057 | 114 | 115 | NMAAAA | GAAAAA | OOOOxx - 8009 | 5 | 1 | 1 | 9 | 9 | 9 | 9 | 9 | 3009 | 8009 | 18 | 19 | BWAAAA | FAAAAA | HHHHxx - 7164 | 4 | 0 | 0 | 4 | 4 | 64 | 164 | 1164 | 2164 | 7164 | 128 | 129 | OPAAAA | EAAAAA | AAAAxx - 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx - 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx - 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx - 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx -(7 rows) - -FETCH backward 17 in foo7; - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- - 8009 | 5 | 1 | 1 | 9 | 9 | 9 | 9 | 9 | 3009 | 8009 | 18 | 19 | BWAAAA | FAAAAA | HHHHxx - 7164 | 4 | 0 | 0 | 4 | 4 | 64 | 164 | 1164 | 2164 | 7164 | 128 | 129 | OPAAAA | EAAAAA | AAAAxx - 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx - 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx - 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx - 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx -(6 rows) - -FETCH backward 18 in foo6; - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- - 7164 | 4 | 0 | 0 | 4 | 4 | 64 | 164 | 1164 | 2164 | 7164 | 128 | 129 | OPAAAA | EAAAAA | AAAAxx - 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx - 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx - 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx - 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx -(5 rows) - -FETCH backward 19 in foo5; - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- - 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx - 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx - 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx - 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx -(4 rows) - -FETCH backward 20 in foo4; - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- - 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx - 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx - 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx -(3 rows) - -FETCH backward 21 in foo3; - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- - 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx - 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx -(2 rows) - -FETCH backward 22 in foo2; - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- - 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx -(1 row) - -FETCH backward 23 in foo1; - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- -(0 rows) - -CLOSE foo1; -CLOSE foo2; -CLOSE foo3; -CLOSE foo4; -CLOSE foo5; -CLOSE foo6; -CLOSE foo7; -CLOSE foo8; -CLOSE foo9; -CLOSE foo10; -CLOSE foo11; -CLOSE foo12; --- leave some cursors open, to test that auto-close works. --- record this in the system view as well (don't query the time field there --- however) -SELECT name, statement, is_holdable, is_binary, is_scrollable FROM pg_cursors ORDER BY 1; - name | statement | is_holdable | is_binary | is_scrollable --------+-----------------------------------------------------------------------+-------------+-----------+--------------- - foo13 | DECLARE foo13 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2; | f | f | t - foo14 | DECLARE foo14 SCROLL CURSOR FOR SELECT * FROM tenk2; | f | f | t - foo15 | DECLARE foo15 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2; | f | f | t - foo16 | DECLARE foo16 SCROLL CURSOR FOR SELECT * FROM tenk2; | f | f | t - foo17 | DECLARE foo17 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2; | f | f | t - foo18 | DECLARE foo18 SCROLL CURSOR FOR SELECT * FROM tenk2; | f | f | t - foo19 | DECLARE foo19 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2; | f | f | t - foo20 | DECLARE foo20 SCROLL CURSOR FOR SELECT * FROM tenk2; | f | f | t - foo21 | DECLARE foo21 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2; | f | f | t - foo22 | DECLARE foo22 SCROLL CURSOR FOR SELECT * FROM tenk2; | f | f | t - foo23 | DECLARE foo23 SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2; | f | f | t -(11 rows) - -END; -SELECT name, statement, is_holdable, is_binary, is_scrollable FROM pg_cursors; - name | statement | is_holdable | is_binary | is_scrollable -------+-----------+-------------+-----------+--------------- -(0 rows) - --- --- NO SCROLL disallows backward fetching --- -BEGIN; -DECLARE foo24 NO SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2; -FETCH 1 FROM foo24; - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- - 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx -(1 row) - -FETCH BACKWARD 1 FROM foo24; -- should fail -ERROR: cursor can only scan forward -HINT: Declare it with SCROLL option to enable backward scan. -END; -BEGIN; -DECLARE foo24 NO SCROLL CURSOR FOR SELECT * FROM tenk1 ORDER BY unique2; -FETCH 1 FROM foo24; - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- - 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx -(1 row) - -FETCH ABSOLUTE 2 FROM foo24; -- allowed - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- - 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx -(1 row) - -FETCH ABSOLUTE 1 FROM foo24; -- should fail -ERROR: cursor can only scan forward -HINT: Declare it with SCROLL option to enable backward scan. -END; --- --- Cursors outside transaction blocks --- -SELECT name, statement, is_holdable, is_binary, is_scrollable FROM pg_cursors; - name | statement | is_holdable | is_binary | is_scrollable -------+-----------+-------------+-----------+--------------- -(0 rows) - -BEGIN; -DECLARE foo25 SCROLL CURSOR WITH HOLD FOR SELECT * FROM tenk2; -FETCH FROM foo25; - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- - 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx -(1 row) - -FETCH FROM foo25; - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- - 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx -(1 row) - -COMMIT; -FETCH FROM foo25; - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- - 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx -(1 row) - -FETCH BACKWARD FROM foo25; - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- - 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx -(1 row) - -FETCH ABSOLUTE -1 FROM foo25; - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- - 2968 | 9999 | 0 | 0 | 8 | 8 | 68 | 968 | 968 | 2968 | 2968 | 136 | 137 | EKAAAA | PUOAAA | VVVVxx -(1 row) - -SELECT name, statement, is_holdable, is_binary, is_scrollable FROM pg_cursors; - name | statement | is_holdable | is_binary | is_scrollable --------+----------------------------------------------------------------+-------------+-----------+--------------- - foo25 | DECLARE foo25 SCROLL CURSOR WITH HOLD FOR SELECT * FROM tenk2; | t | f | t -(1 row) - -CLOSE foo25; -BEGIN; -DECLARE foo25ns NO SCROLL CURSOR WITH HOLD FOR SELECT * FROM tenk2; -FETCH FROM foo25ns; - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- - 8800 | 0 | 0 | 0 | 0 | 0 | 0 | 800 | 800 | 3800 | 8800 | 0 | 1 | MAAAAA | AAAAAA | AAAAxx -(1 row) - -FETCH FROM foo25ns; - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- - 1891 | 1 | 1 | 3 | 1 | 11 | 91 | 891 | 1891 | 1891 | 1891 | 182 | 183 | TUAAAA | BAAAAA | HHHHxx -(1 row) - -COMMIT; -FETCH FROM foo25ns; - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- - 3420 | 2 | 0 | 0 | 0 | 0 | 20 | 420 | 1420 | 3420 | 3420 | 40 | 41 | OBAAAA | CAAAAA | OOOOxx -(1 row) - -FETCH ABSOLUTE 4 FROM foo25ns; - unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 ----------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+--------- - 9850 | 3 | 0 | 2 | 0 | 10 | 50 | 850 | 1850 | 4850 | 9850 | 100 | 101 | WOAAAA | DAAAAA | VVVVxx -(1 row) - -FETCH ABSOLUTE 4 FROM foo25ns; -- fail -ERROR: cursor can only scan forward -HINT: Declare it with SCROLL option to enable backward scan. -SELECT name, statement, is_holdable, is_binary, is_scrollable FROM pg_cursors; - name | statement | is_holdable | is_binary | is_scrollable ----------+---------------------------------------------------------------------+-------------+-----------+--------------- - foo25ns | DECLARE foo25ns NO SCROLL CURSOR WITH HOLD FOR SELECT * FROM tenk2; | t | f | f -(1 row) - -CLOSE foo25ns; --- --- ROLLBACK should close holdable cursors --- -BEGIN; -DECLARE foo26 CURSOR WITH HOLD FOR SELECT * FROM tenk1 ORDER BY unique2; -ROLLBACK; --- should fail -FETCH FROM foo26; -ERROR: cursor "foo26" does not exist --- --- Parameterized DECLARE needs to insert param values into the cursor portal --- -BEGIN; -CREATE FUNCTION declares_cursor(text) - RETURNS void - AS 'DECLARE c CURSOR FOR SELECT stringu1 FROM tenk1 WHERE stringu1 LIKE $1;' - LANGUAGE SQL; -SELECT declares_cursor('AB%'); - declares_cursor ------------------ - -(1 row) - -FETCH ALL FROM c; - stringu1 ----------- - ABAAAA - ABAAAA - ABAAAA - ABAAAA - ABAAAA - ABAAAA - ABAAAA - ABAAAA - ABAAAA - ABAAAA - ABAAAA - ABAAAA - ABAAAA - ABAAAA - ABAAAA -(15 rows) - -ROLLBACK; --- --- Test behavior of both volatile and stable functions inside a cursor; --- in particular we want to see what happens during commit of a holdable --- cursor --- -create temp table tt1(f1 int); -create function count_tt1_v() returns int8 as -'select count(*) from tt1' language sql volatile; -create function count_tt1_s() returns int8 as -'select count(*) from tt1' language sql stable; -begin; -insert into tt1 values(1); -declare c1 cursor for select count_tt1_v(), count_tt1_s(); -insert into tt1 values(2); -fetch all from c1; - count_tt1_v | count_tt1_s --------------+------------- - 2 | 1 -(1 row) - -rollback; -begin; -insert into tt1 values(1); -declare c2 cursor with hold for select count_tt1_v(), count_tt1_s(); -insert into tt1 values(2); -commit; -delete from tt1; -fetch all from c2; - count_tt1_v | count_tt1_s --------------+------------- - 2 | 1 -(1 row) - -drop function count_tt1_v(); -drop function count_tt1_s(); --- Create a cursor with the BINARY option and check the pg_cursors view -BEGIN; -SELECT name, statement, is_holdable, is_binary, is_scrollable FROM pg_cursors; - name | statement | is_holdable | is_binary | is_scrollable -------+----------------------------------------------------------------------+-------------+-----------+--------------- - c2 | declare c2 cursor with hold for select count_tt1_v(), count_tt1_s(); | t | f | f -(1 row) - -DECLARE bc BINARY CURSOR FOR SELECT * FROM tenk1; -SELECT name, statement, is_holdable, is_binary, is_scrollable FROM pg_cursors ORDER BY 1; - name | statement | is_holdable | is_binary | is_scrollable -------+----------------------------------------------------------------------+-------------+-----------+--------------- - bc | DECLARE bc BINARY CURSOR FOR SELECT * FROM tenk1; | f | t | t - c2 | declare c2 cursor with hold for select count_tt1_v(), count_tt1_s(); | t | f | f -(2 rows) - -ROLLBACK; --- We should not see the portal that is created internally to --- implement EXECUTE in pg_cursors -PREPARE cprep AS - SELECT name, statement, is_holdable, is_binary, is_scrollable FROM pg_cursors; -EXECUTE cprep; - name | statement | is_holdable | is_binary | is_scrollable -------+----------------------------------------------------------------------+-------------+-----------+--------------- - c2 | declare c2 cursor with hold for select count_tt1_v(), count_tt1_s(); | t | f | f -(1 row) - --- test CLOSE ALL; -SELECT name FROM pg_cursors ORDER BY 1; - name ------- - c2 -(1 row) - -CLOSE ALL; -SELECT name FROM pg_cursors ORDER BY 1; - name ------- -(0 rows) - -BEGIN; -DECLARE foo1 CURSOR WITH HOLD FOR SELECT 1; -DECLARE foo2 CURSOR WITHOUT HOLD FOR SELECT 1; -SELECT name FROM pg_cursors ORDER BY 1; - name ------- - foo1 - foo2 -(2 rows) - -CLOSE ALL; -SELECT name FROM pg_cursors ORDER BY 1; - name ------- -(0 rows) - -COMMIT; --- --- Tests for updatable cursors --- -CREATE TEMP TABLE uctest(f1 int, f2 text); -INSERT INTO uctest VALUES (1, 'one'), (2, 'two'), (3, 'three'); -SELECT * FROM uctest; - f1 | f2 -----+------- - 1 | one - 2 | two - 3 | three -(3 rows) - --- Check DELETE WHERE CURRENT -BEGIN; -DECLARE c1 CURSOR FOR SELECT * FROM uctest; -FETCH 2 FROM c1; - f1 | f2 -----+----- - 1 | one - 2 | two -(2 rows) - -DELETE FROM uctest WHERE CURRENT OF c1; --- should show deletion -SELECT * FROM uctest; - f1 | f2 -----+------- - 1 | one - 3 | three -(2 rows) - --- cursor did not move -FETCH ALL FROM c1; - f1 | f2 -----+------- - 3 | three -(1 row) - --- cursor is insensitive -MOVE BACKWARD ALL IN c1; -FETCH ALL FROM c1; - f1 | f2 -----+------- - 1 | one - 2 | two - 3 | three -(3 rows) - -COMMIT; --- should still see deletion -SELECT * FROM uctest; - f1 | f2 -----+------- - 1 | one - 3 | three -(2 rows) - --- Check UPDATE WHERE CURRENT; this time use FOR UPDATE -BEGIN; -DECLARE c1 CURSOR FOR SELECT * FROM uctest FOR UPDATE; -FETCH c1; - f1 | f2 -----+----- - 1 | one -(1 row) - -UPDATE uctest SET f1 = 8 WHERE CURRENT OF c1; -SELECT * FROM uctest; - f1 | f2 -----+------- - 3 | three - 8 | one -(2 rows) - -COMMIT; -SELECT * FROM uctest; - f1 | f2 -----+------- - 3 | three - 8 | one -(2 rows) - --- Check repeated-update and update-then-delete cases -BEGIN; -DECLARE c1 CURSOR FOR SELECT * FROM uctest; -FETCH c1; - f1 | f2 -----+------- - 3 | three -(1 row) - -UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; -SELECT * FROM uctest; - f1 | f2 -----+------- - 8 | one - 13 | three -(2 rows) - -UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; -SELECT * FROM uctest; - f1 | f2 -----+------- - 8 | one - 23 | three -(2 rows) - --- insensitive cursor should not show effects of updates or deletes -FETCH RELATIVE 0 FROM c1; - f1 | f2 -----+------- - 3 | three -(1 row) - -DELETE FROM uctest WHERE CURRENT OF c1; -SELECT * FROM uctest; - f1 | f2 -----+----- - 8 | one -(1 row) - -DELETE FROM uctest WHERE CURRENT OF c1; -- no-op -SELECT * FROM uctest; - f1 | f2 -----+----- - 8 | one -(1 row) - -UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; -- no-op -SELECT * FROM uctest; - f1 | f2 -----+----- - 8 | one -(1 row) - -FETCH RELATIVE 0 FROM c1; - f1 | f2 -----+------- - 3 | three -(1 row) - -ROLLBACK; -SELECT * FROM uctest; - f1 | f2 -----+------- - 3 | three - 8 | one -(2 rows) - -BEGIN; -DECLARE c1 CURSOR FOR SELECT * FROM uctest FOR UPDATE; -FETCH c1; - f1 | f2 -----+------- - 3 | three -(1 row) - -UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; -SELECT * FROM uctest; - f1 | f2 -----+------- - 8 | one - 13 | three -(2 rows) - -UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; -SELECT * FROM uctest; - f1 | f2 -----+------- - 8 | one - 23 | three -(2 rows) - -DELETE FROM uctest WHERE CURRENT OF c1; -SELECT * FROM uctest; - f1 | f2 -----+----- - 8 | one -(1 row) - -DELETE FROM uctest WHERE CURRENT OF c1; -- no-op -SELECT * FROM uctest; - f1 | f2 -----+----- - 8 | one -(1 row) - -UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; -- no-op -SELECT * FROM uctest; - f1 | f2 -----+----- - 8 | one -(1 row) - ---- FOR UPDATE cursors can't currently scroll back, so this is an error: -FETCH RELATIVE 0 FROM c1; -ERROR: cursor can only scan forward -HINT: Declare it with SCROLL option to enable backward scan. -ROLLBACK; -SELECT * FROM uctest; - f1 | f2 -----+------- - 3 | three - 8 | one -(2 rows) - --- Check insensitive cursor with INSERT --- (The above tests don't test the SQL notion of an insensitive cursor --- correctly, because per SQL standard, changes from WHERE CURRENT OF --- commands should be visible in the cursor. So here we make the --- changes with a command that is independent of the cursor.) -BEGIN; -DECLARE c1 INSENSITIVE CURSOR FOR SELECT * FROM uctest; -INSERT INTO uctest VALUES (10, 'ten'); -FETCH NEXT FROM c1; - f1 | f2 -----+------- - 3 | three -(1 row) - -FETCH NEXT FROM c1; - f1 | f2 -----+----- - 8 | one -(1 row) - -FETCH NEXT FROM c1; -- insert not visible - f1 | f2 -----+---- -(0 rows) - -COMMIT; -SELECT * FROM uctest; - f1 | f2 -----+------- - 3 | three - 8 | one - 10 | ten -(3 rows) - -DELETE FROM uctest WHERE f1 = 10; -- restore test table state --- Check inheritance cases -CREATE TEMP TABLE ucchild () inherits (uctest); -INSERT INTO ucchild values(100, 'hundred'); -SELECT * FROM uctest; - f1 | f2 ------+--------- - 3 | three - 8 | one - 100 | hundred -(3 rows) - -BEGIN; -DECLARE c1 CURSOR FOR SELECT * FROM uctest FOR UPDATE; -FETCH 1 FROM c1; - f1 | f2 -----+------- - 3 | three -(1 row) - -UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; -FETCH 1 FROM c1; - f1 | f2 -----+----- - 8 | one -(1 row) - -UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; -FETCH 1 FROM c1; - f1 | f2 ------+--------- - 100 | hundred -(1 row) - -UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; -FETCH 1 FROM c1; - f1 | f2 -----+---- -(0 rows) - -COMMIT; -SELECT * FROM uctest; - f1 | f2 ------+--------- - 13 | three - 18 | one - 110 | hundred -(3 rows) - --- Can update from a self-join, but only if FOR UPDATE says which to use -BEGIN; -DECLARE c1 CURSOR FOR SELECT * FROM uctest a, uctest b WHERE a.f1 = b.f1 + 5; -FETCH 1 FROM c1; - f1 | f2 | f1 | f2 -----+-----+----+------- - 18 | one | 13 | three -(1 row) - -UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; -- fail -ERROR: cursor "c1" is not a simply updatable scan of table "uctest" -ROLLBACK; -BEGIN; -DECLARE c1 CURSOR FOR SELECT * FROM uctest a, uctest b WHERE a.f1 = b.f1 + 5 FOR UPDATE; -FETCH 1 FROM c1; - f1 | f2 | f1 | f2 -----+-----+----+------- - 18 | one | 13 | three -(1 row) - -UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; -- fail -ERROR: cursor "c1" has multiple FOR UPDATE/SHARE references to table "uctest" -ROLLBACK; -BEGIN; -DECLARE c1 CURSOR FOR SELECT * FROM uctest a, uctest b WHERE a.f1 = b.f1 + 5 FOR SHARE OF a; -FETCH 1 FROM c1; - f1 | f2 | f1 | f2 -----+-----+----+------- - 18 | one | 13 | three -(1 row) - -UPDATE uctest SET f1 = f1 + 10 WHERE CURRENT OF c1; -SELECT * FROM uctest; - f1 | f2 ------+--------- - 13 | three - 28 | one - 110 | hundred -(3 rows) - -ROLLBACK; --- Check various error cases -DELETE FROM uctest WHERE CURRENT OF c1; -- fail, no such cursor -ERROR: cursor "c1" does not exist -DECLARE cx CURSOR WITH HOLD FOR SELECT * FROM uctest; -DELETE FROM uctest WHERE CURRENT OF cx; -- fail, can't use held cursor -ERROR: cursor "cx" is held from a previous transaction -BEGIN; -DECLARE c CURSOR FOR SELECT * FROM tenk2; -DELETE FROM uctest WHERE CURRENT OF c; -- fail, cursor on wrong table -ERROR: cursor "c" is not a simply updatable scan of table "uctest" -ROLLBACK; -BEGIN; -DECLARE c CURSOR FOR SELECT * FROM tenk2 FOR SHARE; -DELETE FROM uctest WHERE CURRENT OF c; -- fail, cursor on wrong table -ERROR: cursor "c" does not have a FOR UPDATE/SHARE reference to table "uctest" -ROLLBACK; -BEGIN; -DECLARE c CURSOR FOR SELECT * FROM tenk1 JOIN tenk2 USING (unique1); -DELETE FROM tenk1 WHERE CURRENT OF c; -- fail, cursor is on a join -ERROR: cursor "c" is not a simply updatable scan of table "tenk1" -ROLLBACK; -BEGIN; -DECLARE c CURSOR FOR SELECT f1,count(*) FROM uctest GROUP BY f1; -DELETE FROM uctest WHERE CURRENT OF c; -- fail, cursor is on aggregation -ERROR: cursor "c" is not a simply updatable scan of table "uctest" -ROLLBACK; -BEGIN; -DECLARE c1 CURSOR FOR SELECT * FROM uctest; -DELETE FROM uctest WHERE CURRENT OF c1; -- fail, no current row -ERROR: cursor "c1" is not positioned on a row -ROLLBACK; -BEGIN; -DECLARE c1 CURSOR FOR SELECT MIN(f1) FROM uctest FOR UPDATE; -ERROR: FOR UPDATE is not allowed with aggregate functions -ROLLBACK; --- WHERE CURRENT OF may someday work with views, but today is not that day. --- For now, just make sure it errors out cleanly. -CREATE TEMP VIEW ucview AS SELECT * FROM uctest; -CREATE RULE ucrule AS ON DELETE TO ucview DO INSTEAD - DELETE FROM uctest WHERE f1 = OLD.f1; -BEGIN; -DECLARE c1 CURSOR FOR SELECT * FROM ucview; -FETCH FROM c1; - f1 | f2 -----+------- - 13 | three -(1 row) - -DELETE FROM ucview WHERE CURRENT OF c1; -- fail, views not supported -ERROR: WHERE CURRENT OF on a view is not implemented -ROLLBACK; --- Check WHERE CURRENT OF with an index-only scan -BEGIN; -EXPLAIN (costs off) -DECLARE c1 CURSOR FOR SELECT stringu1 FROM onek WHERE stringu1 = 'DZAAAA'; - QUERY PLAN ---------------------------------------------- - Index Only Scan using onek_stringu1 on onek - Index Cond: (stringu1 = 'DZAAAA'::name) -(2 rows) - -DECLARE c1 CURSOR FOR SELECT stringu1 FROM onek WHERE stringu1 = 'DZAAAA'; -FETCH FROM c1; - stringu1 ----------- - DZAAAA -(1 row) - -DELETE FROM onek WHERE CURRENT OF c1; -SELECT stringu1 FROM onek WHERE stringu1 = 'DZAAAA'; - stringu1 ----------- -(0 rows) - -ROLLBACK; --- Check behavior with rewinding to a previous child scan node, --- as per bug #15395 -BEGIN; -CREATE TABLE current_check (currentid int, payload text); -CREATE TABLE current_check_1 () INHERITS (current_check); -CREATE TABLE current_check_2 () INHERITS (current_check); -INSERT INTO current_check_1 SELECT i, 'p' || i FROM generate_series(1,9) i; -INSERT INTO current_check_2 SELECT i, 'P' || i FROM generate_series(10,19) i; -DECLARE c1 SCROLL CURSOR FOR SELECT * FROM current_check; --- This tests the fetch-backwards code path -FETCH ABSOLUTE 12 FROM c1; - currentid | payload ------------+--------- - 12 | P12 -(1 row) - -FETCH ABSOLUTE 8 FROM c1; - currentid | payload ------------+--------- - 8 | p8 -(1 row) - -DELETE FROM current_check WHERE CURRENT OF c1 RETURNING *; - currentid | payload ------------+--------- - 8 | p8 -(1 row) - --- This tests the ExecutorRewind code path -FETCH ABSOLUTE 13 FROM c1; - currentid | payload ------------+--------- - 13 | P13 -(1 row) - -FETCH ABSOLUTE 1 FROM c1; - currentid | payload ------------+--------- - 1 | p1 -(1 row) - -DELETE FROM current_check WHERE CURRENT OF c1 RETURNING *; - currentid | payload ------------+--------- - 1 | p1 -(1 row) - -SELECT * FROM current_check; - currentid | payload ------------+--------- - 2 | p2 - 3 | p3 - 4 | p4 - 5 | p5 - 6 | p6 - 7 | p7 - 9 | p9 - 10 | P10 - 11 | P11 - 12 | P12 - 13 | P13 - 14 | P14 - 15 | P15 - 16 | P16 - 17 | P17 - 18 | P18 - 19 | P19 -(17 rows) - -ROLLBACK; --- Make sure snapshot management works okay, per bug report in --- 235395b90909301035v7228ce63q392931f15aa74b31@mail.gmail.com -BEGIN; -SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; -CREATE TABLE cursor (a int); -INSERT INTO cursor VALUES (1); -DECLARE c1 NO SCROLL CURSOR FOR SELECT * FROM cursor FOR UPDATE; -UPDATE cursor SET a = 2; -FETCH ALL FROM c1; - a ---- -(0 rows) - -COMMIT; -DROP TABLE cursor; --- Check rewinding a cursor containing a stable function in LIMIT, --- per bug report in 8336843.9833.1399385291498.JavaMail.root@quick -begin; -create function nochange(int) returns int - as 'select $1 limit 1' language sql stable; -declare c cursor for select * from int8_tbl limit nochange(3); -fetch all from c; - q1 | q2 -------------------+------------------ - 123 | 456 - 123 | 4567890123456789 - 4567890123456789 | 123 -(3 rows) - -move backward all in c; -fetch all from c; - q1 | q2 -------------------+------------------ - 123 | 456 - 123 | 4567890123456789 - 4567890123456789 | 123 -(3 rows) - -rollback; --- Check handling of non-backwards-scan-capable plans with scroll cursors -begin; -explain (costs off) declare c1 cursor for select (select 42) as x; - QUERY PLAN ----------------- - Result - InitPlan 1 - -> Result -(3 rows) - -explain (costs off) declare c1 scroll cursor for select (select 42) as x; - QUERY PLAN ----------------- - Materialize - InitPlan 1 - -> Result - -> Result -(4 rows) - -declare c1 scroll cursor for select (select 42) as x; -fetch all in c1; - x ----- - 42 -(1 row) - -fetch backward all in c1; - x ----- - 42 -(1 row) - -rollback; -begin; -explain (costs off) declare c2 cursor for select generate_series(1,3) as g; - QUERY PLAN --------------- - ProjectSet - -> Result -(2 rows) - -explain (costs off) declare c2 scroll cursor for select generate_series(1,3) as g; - QUERY PLAN --------------------- - Materialize - -> ProjectSet - -> Result -(3 rows) - -declare c2 scroll cursor for select generate_series(1,3) as g; -fetch all in c2; - g ---- - 1 - 2 - 3 -(3 rows) - -fetch backward all in c2; - g ---- - 3 - 2 - 1 -(3 rows) - -rollback; --- Check fetching of toasted datums via cursors. -begin; --- Other compression algorithms may cause the compressed data to be stored --- inline. Use pglz to ensure consistent results. -set default_toast_compression = 'pglz'; -create table toasted_data (f1 int[]); -insert into toasted_data - select array_agg(i) from generate_series(12345678, 12345678 + 1000) i; -declare local_portal cursor for select * from toasted_data; -fetch all in local_portal; - f1 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - {12345678,12345679,12345680,12345681,12345682,12345683,12345684,12345685,12345686,12345687,12345688,12345689,12345690,12345691,12345692,12345693,12345694,12345695,12345696,12345697,12345698,12345699,12345700,12345701,12345702,12345703,12345704,12345705,12345706,12345707,12345708,12345709,12345710,12345711,12345712,12345713,12345714,12345715,12345716,12345717,12345718,12345719,12345720,12345721,12345722,12345723,12345724,12345725,12345726,12345727,12345728,12345729,12345730,12345731,12345732,12345733,12345734,12345735,12345736,12345737,12345738,12345739,12345740,12345741,12345742,12345743,12345744,12345745,12345746,12345747,12345748,12345749,12345750,12345751,12345752,12345753,12345754,12345755,12345756,12345757,12345758,12345759,12345760,12345761,12345762,12345763,12345764,12345765,12345766,12345767,12345768,12345769,12345770,12345771,12345772,12345773,12345774,12345775,12345776,12345777,12345778,12345779,12345780,12345781,12345782,12345783,12345784,12345785,12345786,12345787,12345788,12345789,12345790,12345791,12345792,12345793,12345794,12345795,12345796,12345797,12345798,12345799,12345800,12345801,12345802,12345803,12345804,12345805,12345806,12345807,12345808,12345809,12345810,12345811,12345812,12345813,12345814,12345815,12345816,12345817,12345818,12345819,12345820,12345821,12345822,12345823,12345824,12345825,12345826,12345827,12345828,12345829,12345830,12345831,12345832,12345833,12345834,12345835,12345836,12345837,12345838,12345839,12345840,12345841,12345842,12345843,12345844,12345845,12345846,12345847,12345848,12345849,12345850,12345851,12345852,12345853,12345854,12345855,12345856,12345857,12345858,12345859,12345860,12345861,12345862,12345863,12345864,12345865,12345866,12345867,12345868,12345869,12345870,12345871,12345872,12345873,12345874,12345875,12345876,12345877,12345878,12345879,12345880,12345881,12345882,12345883,12345884,12345885,12345886,12345887,12345888,12345889,12345890,12345891,12345892,12345893,12345894,12345895,12345896,12345897,12345898,12345899,12345900,12345901,12345902,12345903,12345904,12345905,12345906,12345907,12345908,12345909,12345910,12345911,12345912,12345913,12345914,12345915,12345916,12345917,12345918,12345919,12345920,12345921,12345922,12345923,12345924,12345925,12345926,12345927,12345928,12345929,12345930,12345931,12345932,12345933,12345934,12345935,12345936,12345937,12345938,12345939,12345940,12345941,12345942,12345943,12345944,12345945,12345946,12345947,12345948,12345949,12345950,12345951,12345952,12345953,12345954,12345955,12345956,12345957,12345958,12345959,12345960,12345961,12345962,12345963,12345964,12345965,12345966,12345967,12345968,12345969,12345970,12345971,12345972,12345973,12345974,12345975,12345976,12345977,12345978,12345979,12345980,12345981,12345982,12345983,12345984,12345985,12345986,12345987,12345988,12345989,12345990,12345991,12345992,12345993,12345994,12345995,12345996,12345997,12345998,12345999,12346000,12346001,12346002,12346003,12346004,12346005,12346006,12346007,12346008,12346009,12346010,12346011,12346012,12346013,12346014,12346015,12346016,12346017,12346018,12346019,12346020,12346021,12346022,12346023,12346024,12346025,12346026,12346027,12346028,12346029,12346030,12346031,12346032,12346033,12346034,12346035,12346036,12346037,12346038,12346039,12346040,12346041,12346042,12346043,12346044,12346045,12346046,12346047,12346048,12346049,12346050,12346051,12346052,12346053,12346054,12346055,12346056,12346057,12346058,12346059,12346060,12346061,12346062,12346063,12346064,12346065,12346066,12346067,12346068,12346069,12346070,12346071,12346072,12346073,12346074,12346075,12346076,12346077,12346078,12346079,12346080,12346081,12346082,12346083,12346084,12346085,12346086,12346087,12346088,12346089,12346090,12346091,12346092,12346093,12346094,12346095,12346096,12346097,12346098,12346099,12346100,12346101,12346102,12346103,12346104,12346105,12346106,12346107,12346108,12346109,12346110,12346111,12346112,12346113,12346114,12346115,12346116,12346117,12346118,12346119,12346120,12346121,12346122,12346123,12346124,12346125,12346126,12346127,12346128,12346129,12346130,12346131,12346132,12346133,12346134,12346135,12346136,12346137,12346138,12346139,12346140,12346141,12346142,12346143,12346144,12346145,12346146,12346147,12346148,12346149,12346150,12346151,12346152,12346153,12346154,12346155,12346156,12346157,12346158,12346159,12346160,12346161,12346162,12346163,12346164,12346165,12346166,12346167,12346168,12346169,12346170,12346171,12346172,12346173,12346174,12346175,12346176,12346177,12346178,12346179,12346180,12346181,12346182,12346183,12346184,12346185,12346186,12346187,12346188,12346189,12346190,12346191,12346192,12346193,12346194,12346195,12346196,12346197,12346198,12346199,12346200,12346201,12346202,12346203,12346204,12346205,12346206,12346207,12346208,12346209,12346210,12346211,12346212,12346213,12346214,12346215,12346216,12346217,12346218,12346219,12346220,12346221,12346222,12346223,12346224,12346225,12346226,12346227,12346228,12346229,12346230,12346231,12346232,12346233,12346234,12346235,12346236,12346237,12346238,12346239,12346240,12346241,12346242,12346243,12346244,12346245,12346246,12346247,12346248,12346249,12346250,12346251,12346252,12346253,12346254,12346255,12346256,12346257,12346258,12346259,12346260,12346261,12346262,12346263,12346264,12346265,12346266,12346267,12346268,12346269,12346270,12346271,12346272,12346273,12346274,12346275,12346276,12346277,12346278,12346279,12346280,12346281,12346282,12346283,12346284,12346285,12346286,12346287,12346288,12346289,12346290,12346291,12346292,12346293,12346294,12346295,12346296,12346297,12346298,12346299,12346300,12346301,12346302,12346303,12346304,12346305,12346306,12346307,12346308,12346309,12346310,12346311,12346312,12346313,12346314,12346315,12346316,12346317,12346318,12346319,12346320,12346321,12346322,12346323,12346324,12346325,12346326,12346327,12346328,12346329,12346330,12346331,12346332,12346333,12346334,12346335,12346336,12346337,12346338,12346339,12346340,12346341,12346342,12346343,12346344,12346345,12346346,12346347,12346348,12346349,12346350,12346351,12346352,12346353,12346354,12346355,12346356,12346357,12346358,12346359,12346360,12346361,12346362,12346363,12346364,12346365,12346366,12346367,12346368,12346369,12346370,12346371,12346372,12346373,12346374,12346375,12346376,12346377,12346378,12346379,12346380,12346381,12346382,12346383,12346384,12346385,12346386,12346387,12346388,12346389,12346390,12346391,12346392,12346393,12346394,12346395,12346396,12346397,12346398,12346399,12346400,12346401,12346402,12346403,12346404,12346405,12346406,12346407,12346408,12346409,12346410,12346411,12346412,12346413,12346414,12346415,12346416,12346417,12346418,12346419,12346420,12346421,12346422,12346423,12346424,12346425,12346426,12346427,12346428,12346429,12346430,12346431,12346432,12346433,12346434,12346435,12346436,12346437,12346438,12346439,12346440,12346441,12346442,12346443,12346444,12346445,12346446,12346447,12346448,12346449,12346450,12346451,12346452,12346453,12346454,12346455,12346456,12346457,12346458,12346459,12346460,12346461,12346462,12346463,12346464,12346465,12346466,12346467,12346468,12346469,12346470,12346471,12346472,12346473,12346474,12346475,12346476,12346477,12346478,12346479,12346480,12346481,12346482,12346483,12346484,12346485,12346486,12346487,12346488,12346489,12346490,12346491,12346492,12346493,12346494,12346495,12346496,12346497,12346498,12346499,12346500,12346501,12346502,12346503,12346504,12346505,12346506,12346507,12346508,12346509,12346510,12346511,12346512,12346513,12346514,12346515,12346516,12346517,12346518,12346519,12346520,12346521,12346522,12346523,12346524,12346525,12346526,12346527,12346528,12346529,12346530,12346531,12346532,12346533,12346534,12346535,12346536,12346537,12346538,12346539,12346540,12346541,12346542,12346543,12346544,12346545,12346546,12346547,12346548,12346549,12346550,12346551,12346552,12346553,12346554,12346555,12346556,12346557,12346558,12346559,12346560,12346561,12346562,12346563,12346564,12346565,12346566,12346567,12346568,12346569,12346570,12346571,12346572,12346573,12346574,12346575,12346576,12346577,12346578,12346579,12346580,12346581,12346582,12346583,12346584,12346585,12346586,12346587,12346588,12346589,12346590,12346591,12346592,12346593,12346594,12346595,12346596,12346597,12346598,12346599,12346600,12346601,12346602,12346603,12346604,12346605,12346606,12346607,12346608,12346609,12346610,12346611,12346612,12346613,12346614,12346615,12346616,12346617,12346618,12346619,12346620,12346621,12346622,12346623,12346624,12346625,12346626,12346627,12346628,12346629,12346630,12346631,12346632,12346633,12346634,12346635,12346636,12346637,12346638,12346639,12346640,12346641,12346642,12346643,12346644,12346645,12346646,12346647,12346648,12346649,12346650,12346651,12346652,12346653,12346654,12346655,12346656,12346657,12346658,12346659,12346660,12346661,12346662,12346663,12346664,12346665,12346666,12346667,12346668,12346669,12346670,12346671,12346672,12346673,12346674,12346675,12346676,12346677,12346678} -(1 row) - -declare held_portal cursor with hold for select * from toasted_data; -commit; -drop table toasted_data; -fetch all in held_portal; - f1 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - {12345678,12345679,12345680,12345681,12345682,12345683,12345684,12345685,12345686,12345687,12345688,12345689,12345690,12345691,12345692,12345693,12345694,12345695,12345696,12345697,12345698,12345699,12345700,12345701,12345702,12345703,12345704,12345705,12345706,12345707,12345708,12345709,12345710,12345711,12345712,12345713,12345714,12345715,12345716,12345717,12345718,12345719,12345720,12345721,12345722,12345723,12345724,12345725,12345726,12345727,12345728,12345729,12345730,12345731,12345732,12345733,12345734,12345735,12345736,12345737,12345738,12345739,12345740,12345741,12345742,12345743,12345744,12345745,12345746,12345747,12345748,12345749,12345750,12345751,12345752,12345753,12345754,12345755,12345756,12345757,12345758,12345759,12345760,12345761,12345762,12345763,12345764,12345765,12345766,12345767,12345768,12345769,12345770,12345771,12345772,12345773,12345774,12345775,12345776,12345777,12345778,12345779,12345780,12345781,12345782,12345783,12345784,12345785,12345786,12345787,12345788,12345789,12345790,12345791,12345792,12345793,12345794,12345795,12345796,12345797,12345798,12345799,12345800,12345801,12345802,12345803,12345804,12345805,12345806,12345807,12345808,12345809,12345810,12345811,12345812,12345813,12345814,12345815,12345816,12345817,12345818,12345819,12345820,12345821,12345822,12345823,12345824,12345825,12345826,12345827,12345828,12345829,12345830,12345831,12345832,12345833,12345834,12345835,12345836,12345837,12345838,12345839,12345840,12345841,12345842,12345843,12345844,12345845,12345846,12345847,12345848,12345849,12345850,12345851,12345852,12345853,12345854,12345855,12345856,12345857,12345858,12345859,12345860,12345861,12345862,12345863,12345864,12345865,12345866,12345867,12345868,12345869,12345870,12345871,12345872,12345873,12345874,12345875,12345876,12345877,12345878,12345879,12345880,12345881,12345882,12345883,12345884,12345885,12345886,12345887,12345888,12345889,12345890,12345891,12345892,12345893,12345894,12345895,12345896,12345897,12345898,12345899,12345900,12345901,12345902,12345903,12345904,12345905,12345906,12345907,12345908,12345909,12345910,12345911,12345912,12345913,12345914,12345915,12345916,12345917,12345918,12345919,12345920,12345921,12345922,12345923,12345924,12345925,12345926,12345927,12345928,12345929,12345930,12345931,12345932,12345933,12345934,12345935,12345936,12345937,12345938,12345939,12345940,12345941,12345942,12345943,12345944,12345945,12345946,12345947,12345948,12345949,12345950,12345951,12345952,12345953,12345954,12345955,12345956,12345957,12345958,12345959,12345960,12345961,12345962,12345963,12345964,12345965,12345966,12345967,12345968,12345969,12345970,12345971,12345972,12345973,12345974,12345975,12345976,12345977,12345978,12345979,12345980,12345981,12345982,12345983,12345984,12345985,12345986,12345987,12345988,12345989,12345990,12345991,12345992,12345993,12345994,12345995,12345996,12345997,12345998,12345999,12346000,12346001,12346002,12346003,12346004,12346005,12346006,12346007,12346008,12346009,12346010,12346011,12346012,12346013,12346014,12346015,12346016,12346017,12346018,12346019,12346020,12346021,12346022,12346023,12346024,12346025,12346026,12346027,12346028,12346029,12346030,12346031,12346032,12346033,12346034,12346035,12346036,12346037,12346038,12346039,12346040,12346041,12346042,12346043,12346044,12346045,12346046,12346047,12346048,12346049,12346050,12346051,12346052,12346053,12346054,12346055,12346056,12346057,12346058,12346059,12346060,12346061,12346062,12346063,12346064,12346065,12346066,12346067,12346068,12346069,12346070,12346071,12346072,12346073,12346074,12346075,12346076,12346077,12346078,12346079,12346080,12346081,12346082,12346083,12346084,12346085,12346086,12346087,12346088,12346089,12346090,12346091,12346092,12346093,12346094,12346095,12346096,12346097,12346098,12346099,12346100,12346101,12346102,12346103,12346104,12346105,12346106,12346107,12346108,12346109,12346110,12346111,12346112,12346113,12346114,12346115,12346116,12346117,12346118,12346119,12346120,12346121,12346122,12346123,12346124,12346125,12346126,12346127,12346128,12346129,12346130,12346131,12346132,12346133,12346134,12346135,12346136,12346137,12346138,12346139,12346140,12346141,12346142,12346143,12346144,12346145,12346146,12346147,12346148,12346149,12346150,12346151,12346152,12346153,12346154,12346155,12346156,12346157,12346158,12346159,12346160,12346161,12346162,12346163,12346164,12346165,12346166,12346167,12346168,12346169,12346170,12346171,12346172,12346173,12346174,12346175,12346176,12346177,12346178,12346179,12346180,12346181,12346182,12346183,12346184,12346185,12346186,12346187,12346188,12346189,12346190,12346191,12346192,12346193,12346194,12346195,12346196,12346197,12346198,12346199,12346200,12346201,12346202,12346203,12346204,12346205,12346206,12346207,12346208,12346209,12346210,12346211,12346212,12346213,12346214,12346215,12346216,12346217,12346218,12346219,12346220,12346221,12346222,12346223,12346224,12346225,12346226,12346227,12346228,12346229,12346230,12346231,12346232,12346233,12346234,12346235,12346236,12346237,12346238,12346239,12346240,12346241,12346242,12346243,12346244,12346245,12346246,12346247,12346248,12346249,12346250,12346251,12346252,12346253,12346254,12346255,12346256,12346257,12346258,12346259,12346260,12346261,12346262,12346263,12346264,12346265,12346266,12346267,12346268,12346269,12346270,12346271,12346272,12346273,12346274,12346275,12346276,12346277,12346278,12346279,12346280,12346281,12346282,12346283,12346284,12346285,12346286,12346287,12346288,12346289,12346290,12346291,12346292,12346293,12346294,12346295,12346296,12346297,12346298,12346299,12346300,12346301,12346302,12346303,12346304,12346305,12346306,12346307,12346308,12346309,12346310,12346311,12346312,12346313,12346314,12346315,12346316,12346317,12346318,12346319,12346320,12346321,12346322,12346323,12346324,12346325,12346326,12346327,12346328,12346329,12346330,12346331,12346332,12346333,12346334,12346335,12346336,12346337,12346338,12346339,12346340,12346341,12346342,12346343,12346344,12346345,12346346,12346347,12346348,12346349,12346350,12346351,12346352,12346353,12346354,12346355,12346356,12346357,12346358,12346359,12346360,12346361,12346362,12346363,12346364,12346365,12346366,12346367,12346368,12346369,12346370,12346371,12346372,12346373,12346374,12346375,12346376,12346377,12346378,12346379,12346380,12346381,12346382,12346383,12346384,12346385,12346386,12346387,12346388,12346389,12346390,12346391,12346392,12346393,12346394,12346395,12346396,12346397,12346398,12346399,12346400,12346401,12346402,12346403,12346404,12346405,12346406,12346407,12346408,12346409,12346410,12346411,12346412,12346413,12346414,12346415,12346416,12346417,12346418,12346419,12346420,12346421,12346422,12346423,12346424,12346425,12346426,12346427,12346428,12346429,12346430,12346431,12346432,12346433,12346434,12346435,12346436,12346437,12346438,12346439,12346440,12346441,12346442,12346443,12346444,12346445,12346446,12346447,12346448,12346449,12346450,12346451,12346452,12346453,12346454,12346455,12346456,12346457,12346458,12346459,12346460,12346461,12346462,12346463,12346464,12346465,12346466,12346467,12346468,12346469,12346470,12346471,12346472,12346473,12346474,12346475,12346476,12346477,12346478,12346479,12346480,12346481,12346482,12346483,12346484,12346485,12346486,12346487,12346488,12346489,12346490,12346491,12346492,12346493,12346494,12346495,12346496,12346497,12346498,12346499,12346500,12346501,12346502,12346503,12346504,12346505,12346506,12346507,12346508,12346509,12346510,12346511,12346512,12346513,12346514,12346515,12346516,12346517,12346518,12346519,12346520,12346521,12346522,12346523,12346524,12346525,12346526,12346527,12346528,12346529,12346530,12346531,12346532,12346533,12346534,12346535,12346536,12346537,12346538,12346539,12346540,12346541,12346542,12346543,12346544,12346545,12346546,12346547,12346548,12346549,12346550,12346551,12346552,12346553,12346554,12346555,12346556,12346557,12346558,12346559,12346560,12346561,12346562,12346563,12346564,12346565,12346566,12346567,12346568,12346569,12346570,12346571,12346572,12346573,12346574,12346575,12346576,12346577,12346578,12346579,12346580,12346581,12346582,12346583,12346584,12346585,12346586,12346587,12346588,12346589,12346590,12346591,12346592,12346593,12346594,12346595,12346596,12346597,12346598,12346599,12346600,12346601,12346602,12346603,12346604,12346605,12346606,12346607,12346608,12346609,12346610,12346611,12346612,12346613,12346614,12346615,12346616,12346617,12346618,12346619,12346620,12346621,12346622,12346623,12346624,12346625,12346626,12346627,12346628,12346629,12346630,12346631,12346632,12346633,12346634,12346635,12346636,12346637,12346638,12346639,12346640,12346641,12346642,12346643,12346644,12346645,12346646,12346647,12346648,12346649,12346650,12346651,12346652,12346653,12346654,12346655,12346656,12346657,12346658,12346659,12346660,12346661,12346662,12346663,12346664,12346665,12346666,12346667,12346668,12346669,12346670,12346671,12346672,12346673,12346674,12346675,12346676,12346677,12346678} -(1 row) - -reset default_toast_compression; +psql: error: connection to server on socket "/tmp/pg_regress-03Az7d/.s.PGSQL.40051" failed: FATAL: the database system is not yet accepting connections +DETAIL: Consistent recovery state has not been yet reached. diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/arrays.out /tmp/cirrus-ci-build/build/testrun/regress/regress/results/arrays.out --- /tmp/cirrus-ci-build/src/test/regress/expected/arrays.out 2024-09-20 21:41:45.666024520 +0000 +++ /tmp/cirrus-ci-build/build/testrun/regress/regress/results/arrays.out 2024-09-20 21:46:05.457625135 +0000 @@ -1,2705 +1,2 @@ --- --- ARRAYS --- --- directory paths are passed to us in environment variables -\getenv abs_srcdir PG_ABS_SRCDIR -CREATE TABLE arrtest ( - a int2[], - b int4[][][], - c name[], - d text[][], - e float8[], - f char(5)[], - g varchar(5)[] -); -CREATE TABLE array_op_test ( - seqno int4, - i int4[], - t text[] -); -\set filename :abs_srcdir '/data/array.data' -COPY array_op_test FROM :'filename'; -ANALYZE array_op_test; --- --- only the 'e' array is 0-based, the others are 1-based. --- -INSERT INTO arrtest (a[1:5], b[1:1][1:2][1:2], c, d, f, g) - VALUES ('{1,2,3,4,5}', '{{{0,0},{1,2}}}', '{}', '{}', '{}', '{}'); -UPDATE arrtest SET e[0] = '1.1'; -UPDATE arrtest SET e[1] = '2.2'; -INSERT INTO arrtest (f) - VALUES ('{"too long"}'); -ERROR: value too long for type character(5) -INSERT INTO arrtest (a, b[1:2][1:2], c, d, e, f, g) - VALUES ('{11,12,23}', '{{3,4},{4,5}}', '{"foobar"}', - '{{"elt1", "elt2"}}', '{"3.4", "6.7"}', - '{"abc","abcde"}', '{"abc","abcde"}'); -INSERT INTO arrtest (a, b[1:2], c, d[1:2]) - VALUES ('{}', '{3,4}', '{foo,bar}', '{bar,foo}'); -INSERT INTO arrtest (b[2]) VALUES(now()); -- error, type mismatch -ERROR: subscripted assignment to "b" requires type integer but expression is of type timestamp with time zone -LINE 1: INSERT INTO arrtest (b[2]) VALUES(now()); - ^ -HINT: You will need to rewrite or cast the expression. -INSERT INTO arrtest (b[1:2]) VALUES(now()); -- error, type mismatch -ERROR: subscripted assignment to "b" requires type integer[] but expression is of type timestamp with time zone -LINE 1: INSERT INTO arrtest (b[1:2]) VALUES(now()); - ^ -HINT: You will need to rewrite or cast the expression. -SELECT * FROM arrtest; - a | b | c | d | e | f | g --------------+-----------------+-----------+---------------+-----------------+-----------------+------------- - {1,2,3,4,5} | {{{0,0},{1,2}}} | {} | {} | [0:1]={1.1,2.2} | {} | {} - {11,12,23} | {{3,4},{4,5}} | {foobar} | {{elt1,elt2}} | {3.4,6.7} | {"abc ",abcde} | {abc,abcde} - {} | {3,4} | {foo,bar} | {bar,foo} | | | -(3 rows) - -SELECT arrtest.a[1], - arrtest.b[1][1][1], - arrtest.c[1], - arrtest.d[1][1], - arrtest.e[0] - FROM arrtest; - a | b | c | d | e -----+---+--------+------+----- - 1 | 0 | | | 1.1 - 11 | | foobar | elt1 | - | | foo | | -(3 rows) - -SELECT a[1], b[1][1][1], c[1], d[1][1], e[0] - FROM arrtest; - a | b | c | d | e -----+---+--------+------+----- - 1 | 0 | | | 1.1 - 11 | | foobar | elt1 | - | | foo | | -(3 rows) - -SELECT a[1:3], - b[1:1][1:2][1:2], - c[1:2], - d[1:1][1:2] - FROM arrtest; - a | b | c | d -------------+-----------------+-----------+--------------- - {1,2,3} | {{{0,0},{1,2}}} | {} | {} - {11,12,23} | {} | {foobar} | {{elt1,elt2}} - {} | {} | {foo,bar} | {} -(3 rows) - -SELECT array_ndims(a) AS a,array_ndims(b) AS b,array_ndims(c) AS c - FROM arrtest; - a | b | c ----+---+--- - 1 | 3 | - 1 | 2 | 1 - | 1 | 1 -(3 rows) - -SELECT array_dims(a) AS a,array_dims(b) AS b,array_dims(c) AS c - FROM arrtest; - a | b | c --------+-----------------+------- - [1:5] | [1:1][1:2][1:2] | - [1:3] | [1:2][1:2] | [1:1] - | [1:2] | [1:2] -(3 rows) - --- returns nothing -SELECT * - FROM arrtest - WHERE a[1] < 5 and - c = '{"foobar"}'::_name; - a | b | c | d | e | f | g ----+---+---+---+---+---+--- -(0 rows) - -UPDATE arrtest - SET a[1:2] = '{16,25}' - WHERE NOT a = '{}'::_int2; -UPDATE arrtest - SET b[1:1][1:1][1:2] = '{113, 117}', - b[1:1][1:2][2:2] = '{142, 147}' - WHERE array_dims(b) = '[1:1][1:2][1:2]'; -UPDATE arrtest - SET c[2:2] = '{"new_word"}' - WHERE array_dims(c) is not null; -SELECT a,b,c FROM arrtest; - a | b | c ----------------+-----------------------+------------------- - {16,25,3,4,5} | {{{113,142},{1,147}}} | {} - {} | {3,4} | {foo,new_word} - {16,25,23} | {{3,4},{4,5}} | {foobar,new_word} -(3 rows) - -SELECT a[1:3], - b[1:1][1:2][1:2], - c[1:2], - d[1:1][2:2] - FROM arrtest; - a | b | c | d -------------+-----------------------+-------------------+---------- - {16,25,3} | {{{113,142},{1,147}}} | {} | {} - {} | {} | {foo,new_word} | {} - {16,25,23} | {} | {foobar,new_word} | {{elt2}} -(3 rows) - -SELECT b[1:1][2][2], - d[1:1][2] - FROM arrtest; - b | d ------------------------+--------------- - {{{113,142},{1,147}}} | {} - {} | {} - {} | {{elt1,elt2}} -(3 rows) - -INSERT INTO arrtest(a) VALUES('{1,null,3}'); -SELECT a FROM arrtest; - a ---------------- - {16,25,3,4,5} - {} - {16,25,23} - {1,NULL,3} -(4 rows) - -UPDATE arrtest SET a[4] = NULL WHERE a[2] IS NULL; -SELECT a FROM arrtest WHERE a[2] IS NULL; - a ------------------ - [4:4]={NULL} - {1,NULL,3,NULL} -(2 rows) - -DELETE FROM arrtest WHERE a[2] IS NULL AND b IS NULL; -SELECT a,b,c FROM arrtest; - a | b | c ----------------+-----------------------+------------------- - {16,25,3,4,5} | {{{113,142},{1,147}}} | {} - {16,25,23} | {{3,4},{4,5}} | {foobar,new_word} - [4:4]={NULL} | {3,4} | {foo,new_word} -(3 rows) - --- test non-error-throwing API -SELECT pg_input_is_valid('{1,2,3}', 'integer[]'); - pg_input_is_valid -------------------- - t -(1 row) - -SELECT pg_input_is_valid('{1,2', 'integer[]'); - pg_input_is_valid -------------------- - f -(1 row) - -SELECT pg_input_is_valid('{1,zed}', 'integer[]'); - pg_input_is_valid -------------------- - f -(1 row) - -SELECT * FROM pg_input_error_info('{1,zed}', 'integer[]'); - message | detail | hint | sql_error_code -----------------------------------------------+--------+------+---------------- - invalid input syntax for type integer: "zed" | | | 22P02 -(1 row) - --- test mixed slice/scalar subscripting -select '{{1,2,3},{4,5,6},{7,8,9}}'::int[]; - int4 ---------------------------- - {{1,2,3},{4,5,6},{7,8,9}} -(1 row) - -select ('{{1,2,3},{4,5,6},{7,8,9}}'::int[])[1:2][2]; - int4 ---------------- - {{1,2},{4,5}} -(1 row) - -select '[0:2][0:2]={{1,2,3},{4,5,6},{7,8,9}}'::int[]; - int4 --------------------------------------- - [0:2][0:2]={{1,2,3},{4,5,6},{7,8,9}} -(1 row) - -select ('[0:2][0:2]={{1,2,3},{4,5,6},{7,8,9}}'::int[])[1:2][2]; - int4 ---------------- - {{5,6},{8,9}} -(1 row) - --- --- check subscription corner cases --- --- More subscripts than MAXDIM (6) -SELECT ('{}'::int[])[1][2][3][4][5][6][7]; -ERROR: number of array dimensions (7) exceeds the maximum allowed (6) --- NULL index yields NULL when selecting -SELECT ('{{{1},{2},{3}},{{4},{5},{6}}}'::int[])[1][NULL][1]; - int4 ------- - -(1 row) - -SELECT ('{{{1},{2},{3}},{{4},{5},{6}}}'::int[])[1][NULL:1][1]; - int4 ------- - -(1 row) - -SELECT ('{{{1},{2},{3}},{{4},{5},{6}}}'::int[])[1][1:NULL][1]; - int4 ------- - -(1 row) - --- NULL index in assignment is an error -UPDATE arrtest - SET c[NULL] = '{"can''t assign"}' - WHERE array_dims(c) is not null; -ERROR: array subscript in assignment must not be null -UPDATE arrtest - SET c[NULL:1] = '{"can''t assign"}' - WHERE array_dims(c) is not null; -ERROR: array subscript in assignment must not be null -UPDATE arrtest - SET c[1:NULL] = '{"can''t assign"}' - WHERE array_dims(c) is not null; -ERROR: array subscript in assignment must not be null --- Un-subscriptable type -SELECT (now())[1]; -ERROR: cannot subscript type timestamp with time zone because it does not support subscripting -LINE 1: SELECT (now())[1]; - ^ --- test slices with empty lower and/or upper index -CREATE TEMP TABLE arrtest_s ( - a int2[], - b int2[][] -); -INSERT INTO arrtest_s VALUES ('{1,2,3,4,5}', '{{1,2,3}, {4,5,6}, {7,8,9}}'); -INSERT INTO arrtest_s VALUES ('[0:4]={1,2,3,4,5}', '[0:2][0:2]={{1,2,3}, {4,5,6}, {7,8,9}}'); -SELECT * FROM arrtest_s; - a | b --------------------+-------------------------------------- - {1,2,3,4,5} | {{1,2,3},{4,5,6},{7,8,9}} - [0:4]={1,2,3,4,5} | [0:2][0:2]={{1,2,3},{4,5,6},{7,8,9}} -(2 rows) - -SELECT a[:3], b[:2][:2] FROM arrtest_s; - a | b ------------+--------------------------- - {1,2,3} | {{1,2},{4,5}} - {1,2,3,4} | {{1,2,3},{4,5,6},{7,8,9}} -(2 rows) - -SELECT a[2:], b[2:][2:] FROM arrtest_s; - a | b ------------+--------------- - {2,3,4,5} | {{5,6},{8,9}} - {3,4,5} | {{9}} -(2 rows) - -SELECT a[:], b[:] FROM arrtest_s; - a | b --------------+--------------------------- - {1,2,3,4,5} | {{1,2,3},{4,5,6},{7,8,9}} - {1,2,3,4,5} | {{1,2,3},{4,5,6},{7,8,9}} -(2 rows) - --- updates -UPDATE arrtest_s SET a[:3] = '{11, 12, 13}', b[:2][:2] = '{{11,12}, {14,15}}' - WHERE array_lower(a,1) = 1; -SELECT * FROM arrtest_s; - a | b --------------------+-------------------------------------- - [0:4]={1,2,3,4,5} | [0:2][0:2]={{1,2,3},{4,5,6},{7,8,9}} - {11,12,13,4,5} | {{11,12,3},{14,15,6},{7,8,9}} -(2 rows) - -UPDATE arrtest_s SET a[3:] = '{23, 24, 25}', b[2:][2:] = '{{25,26}, {28,29}}'; -SELECT * FROM arrtest_s; - a | b ----------------------+--------------------------------------- - [0:4]={1,2,3,23,24} | [0:2][0:2]={{1,2,3},{4,5,6},{7,8,25}} - {11,12,23,24,25} | {{11,12,3},{14,25,26},{7,28,29}} -(2 rows) - -UPDATE arrtest_s SET a[:] = '{11, 12, 13, 14, 15}'; -SELECT * FROM arrtest_s; - a | b -------------------------+--------------------------------------- - [0:4]={11,12,13,14,15} | [0:2][0:2]={{1,2,3},{4,5,6},{7,8,25}} - {11,12,13,14,15} | {{11,12,3},{14,25,26},{7,28,29}} -(2 rows) - -UPDATE arrtest_s SET a[:] = '{23, 24, 25}'; -- fail, too small -ERROR: source array too small -INSERT INTO arrtest_s VALUES(NULL, NULL); -UPDATE arrtest_s SET a[:] = '{11, 12, 13, 14, 15}'; -- fail, no good with null -ERROR: array slice subscript must provide both boundaries -DETAIL: When assigning to a slice of an empty array value, slice boundaries must be fully specified. --- we want to work with a point_tbl that includes a null -CREATE TEMP TABLE point_tbl AS SELECT * FROM public.point_tbl; -INSERT INTO POINT_TBL(f1) VALUES (NULL); --- check with fixed-length-array type, such as point -SELECT f1[0:1] FROM POINT_TBL; -ERROR: slices of fixed-length arrays not implemented -SELECT f1[0:] FROM POINT_TBL; -ERROR: slices of fixed-length arrays not implemented -SELECT f1[:1] FROM POINT_TBL; -ERROR: slices of fixed-length arrays not implemented -SELECT f1[:] FROM POINT_TBL; -ERROR: slices of fixed-length arrays not implemented --- subscript assignments to fixed-width result in NULL if previous value is NULL -UPDATE point_tbl SET f1[0] = 10 WHERE f1 IS NULL RETURNING *; - f1 ----- - -(1 row) - -INSERT INTO point_tbl(f1[0]) VALUES(0) RETURNING *; - f1 ----- - -(1 row) - --- NULL assignments get ignored -UPDATE point_tbl SET f1[0] = NULL WHERE f1::text = '(10,10)'::point::text RETURNING *; - f1 ---------- - (10,10) -(1 row) - --- but non-NULL subscript assignments work -UPDATE point_tbl SET f1[0] = -10, f1[1] = -10 WHERE f1::text = '(10,10)'::point::text RETURNING *; - f1 ------------ - (-10,-10) -(1 row) - --- but not to expand the range -UPDATE point_tbl SET f1[3] = 10 WHERE f1::text = '(-10,-10)'::point::text RETURNING *; -ERROR: array subscript out of range --- --- test array extension --- -CREATE TEMP TABLE arrtest1 (i int[], t text[]); -insert into arrtest1 values(array[1,2,null,4], array['one','two',null,'four']); -select * from arrtest1; - i | t ---------------+--------------------- - {1,2,NULL,4} | {one,two,NULL,four} -(1 row) - -update arrtest1 set i[2] = 22, t[2] = 'twenty-two'; -select * from arrtest1; - i | t ----------------+---------------------------- - {1,22,NULL,4} | {one,twenty-two,NULL,four} -(1 row) - -update arrtest1 set i[5] = 5, t[5] = 'five'; -select * from arrtest1; - i | t ------------------+--------------------------------- - {1,22,NULL,4,5} | {one,twenty-two,NULL,four,five} -(1 row) - -update arrtest1 set i[8] = 8, t[8] = 'eight'; -select * from arrtest1; - i | t ------------------------------+------------------------------------------------- - {1,22,NULL,4,5,NULL,NULL,8} | {one,twenty-two,NULL,four,five,NULL,NULL,eight} -(1 row) - -update arrtest1 set i[0] = 0, t[0] = 'zero'; -select * from arrtest1; - i | t --------------------------------------+------------------------------------------------------------ - [0:8]={0,1,22,NULL,4,5,NULL,NULL,8} | [0:8]={zero,one,twenty-two,NULL,four,five,NULL,NULL,eight} -(1 row) - -update arrtest1 set i[-3] = -3, t[-3] = 'minus-three'; -select * from arrtest1; - i | t ----------------------------------------------------+----------------------------------------------------------------------------------- - [-3:8]={-3,NULL,NULL,0,1,22,NULL,4,5,NULL,NULL,8} | [-3:8]={minus-three,NULL,NULL,zero,one,twenty-two,NULL,four,five,NULL,NULL,eight} -(1 row) - -update arrtest1 set i[0:2] = array[10,11,12], t[0:2] = array['ten','eleven','twelve']; -select * from arrtest1; - i | t ------------------------------------------------------+--------------------------------------------------------------------------------- - [-3:8]={-3,NULL,NULL,10,11,12,NULL,4,5,NULL,NULL,8} | [-3:8]={minus-three,NULL,NULL,ten,eleven,twelve,NULL,four,five,NULL,NULL,eight} -(1 row) - -update arrtest1 set i[8:10] = array[18,null,20], t[8:10] = array['p18',null,'p20']; -select * from arrtest1; - i | t ----------------------------------------------------------------+----------------------------------------------------------------------------------------- - [-3:10]={-3,NULL,NULL,10,11,12,NULL,4,5,NULL,NULL,18,NULL,20} | [-3:10]={minus-three,NULL,NULL,ten,eleven,twelve,NULL,four,five,NULL,NULL,p18,NULL,p20} -(1 row) - -update arrtest1 set i[11:12] = array[null,22], t[11:12] = array[null,'p22']; -select * from arrtest1; - i | t ------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------- - [-3:12]={-3,NULL,NULL,10,11,12,NULL,4,5,NULL,NULL,18,NULL,20,NULL,22} | [-3:12]={minus-three,NULL,NULL,ten,eleven,twelve,NULL,four,five,NULL,NULL,p18,NULL,p20,NULL,p22} -(1 row) - -update arrtest1 set i[15:16] = array[null,26], t[15:16] = array[null,'p26']; -select * from arrtest1; - i | t ------------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------- - [-3:16]={-3,NULL,NULL,10,11,12,NULL,4,5,NULL,NULL,18,NULL,20,NULL,22,NULL,NULL,NULL,26} | [-3:16]={minus-three,NULL,NULL,ten,eleven,twelve,NULL,four,five,NULL,NULL,p18,NULL,p20,NULL,p22,NULL,NULL,NULL,p26} -(1 row) - -update arrtest1 set i[-5:-3] = array[-15,-14,-13], t[-5:-3] = array['m15','m14','m13']; -select * from arrtest1; - i | t ---------------------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------- - [-5:16]={-15,-14,-13,NULL,NULL,10,11,12,NULL,4,5,NULL,NULL,18,NULL,20,NULL,22,NULL,NULL,NULL,26} | [-5:16]={m15,m14,m13,NULL,NULL,ten,eleven,twelve,NULL,four,five,NULL,NULL,p18,NULL,p20,NULL,p22,NULL,NULL,NULL,p26} -(1 row) - -update arrtest1 set i[-7:-6] = array[-17,null], t[-7:-6] = array['m17',null]; -select * from arrtest1; - i | t ------------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------ - [-7:16]={-17,NULL,-15,-14,-13,NULL,NULL,10,11,12,NULL,4,5,NULL,NULL,18,NULL,20,NULL,22,NULL,NULL,NULL,26} | [-7:16]={m17,NULL,m15,m14,m13,NULL,NULL,ten,eleven,twelve,NULL,four,five,NULL,NULL,p18,NULL,p20,NULL,p22,NULL,NULL,NULL,p26} -(1 row) - -update arrtest1 set i[-12:-10] = array[-22,null,-20], t[-12:-10] = array['m22',null,'m20']; -select * from arrtest1; - i | t ------------------------------------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------ - [-12:16]={-22,NULL,-20,NULL,NULL,-17,NULL,-15,-14,-13,NULL,NULL,10,11,12,NULL,4,5,NULL,NULL,18,NULL,20,NULL,22,NULL,NULL,NULL,26} | [-12:16]={m22,NULL,m20,NULL,NULL,m17,NULL,m15,m14,m13,NULL,NULL,ten,eleven,twelve,NULL,four,five,NULL,NULL,p18,NULL,p20,NULL,p22,NULL,NULL,NULL,p26} -(1 row) - -delete from arrtest1; -insert into arrtest1 values(array[1,2,null,4], array['one','two',null,'four']); -select * from arrtest1; - i | t ---------------+--------------------- - {1,2,NULL,4} | {one,two,NULL,four} -(1 row) - -update arrtest1 set i[0:5] = array[0,1,2,null,4,5], t[0:5] = array['z','p1','p2',null,'p4','p5']; -select * from arrtest1; - i | t -------------------------+---------------------------- - [0:5]={0,1,2,NULL,4,5} | [0:5]={z,p1,p2,NULL,p4,p5} -(1 row) - --- --- array expressions and operators --- --- table creation and INSERTs -CREATE TEMP TABLE arrtest2 (i integer ARRAY[4], f float8[], n numeric[], t text[], d timestamp[]); -INSERT INTO arrtest2 VALUES( - ARRAY[[[113,142],[1,147]]], - ARRAY[1.1,1.2,1.3]::float8[], - ARRAY[1.1,1.2,1.3], - ARRAY[[['aaa','aab'],['aba','abb'],['aca','acb']],[['baa','bab'],['bba','bbb'],['bca','bcb']]], - ARRAY['19620326','19931223','19970117']::timestamp[] -); --- some more test data -CREATE TEMP TABLE arrtest_f (f0 int, f1 text, f2 float8); -insert into arrtest_f values(1,'cat1',1.21); -insert into arrtest_f values(2,'cat1',1.24); -insert into arrtest_f values(3,'cat1',1.18); -insert into arrtest_f values(4,'cat1',1.26); -insert into arrtest_f values(5,'cat1',1.15); -insert into arrtest_f values(6,'cat2',1.15); -insert into arrtest_f values(7,'cat2',1.26); -insert into arrtest_f values(8,'cat2',1.32); -insert into arrtest_f values(9,'cat2',1.30); -CREATE TEMP TABLE arrtest_i (f0 int, f1 text, f2 int); -insert into arrtest_i values(1,'cat1',21); -insert into arrtest_i values(2,'cat1',24); -insert into arrtest_i values(3,'cat1',18); -insert into arrtest_i values(4,'cat1',26); -insert into arrtest_i values(5,'cat1',15); -insert into arrtest_i values(6,'cat2',15); -insert into arrtest_i values(7,'cat2',26); -insert into arrtest_i values(8,'cat2',32); -insert into arrtest_i values(9,'cat2',30); --- expressions -SELECT t.f[1][3][1] AS "131", t.f[2][2][1] AS "221" FROM ( - SELECT ARRAY[[[111,112],[121,122],[131,132]],[[211,212],[221,122],[231,232]]] AS f -) AS t; - 131 | 221 ------+----- - 131 | 221 -(1 row) - -SELECT ARRAY[[[[[['hello'],['world']]]]]]; - array ---------------------------- - {{{{{{hello},{world}}}}}} -(1 row) - -SELECT ARRAY[ARRAY['hello'],ARRAY['world']]; - array -------------------- - {{hello},{world}} -(1 row) - -SELECT ARRAY(select f2 from arrtest_f order by f2) AS "ARRAY"; - ARRAY ------------------------------------------------ - {1.15,1.15,1.18,1.21,1.24,1.26,1.26,1.3,1.32} -(1 row) - --- with nulls -SELECT '{1,null,3}'::int[]; - int4 ------------- - {1,NULL,3} -(1 row) - -SELECT ARRAY[1,NULL,3]; - array ------------- - {1,NULL,3} -(1 row) - --- functions -SELECT array_append(array[42], 6) AS "{42,6}"; - {42,6} --------- - {42,6} -(1 row) - -SELECT array_prepend(6, array[42]) AS "{6,42}"; - {6,42} --------- - {6,42} -(1 row) - -SELECT array_cat(ARRAY[1,2], ARRAY[3,4]) AS "{1,2,3,4}"; - {1,2,3,4} ------------ - {1,2,3,4} -(1 row) - -SELECT array_cat(ARRAY[1,2], ARRAY[[3,4],[5,6]]) AS "{{1,2},{3,4},{5,6}}"; - {{1,2},{3,4},{5,6}} ---------------------- - {{1,2},{3,4},{5,6}} -(1 row) - -SELECT array_cat(ARRAY[[3,4],[5,6]], ARRAY[1,2]) AS "{{3,4},{5,6},{1,2}}"; - {{3,4},{5,6},{1,2}} ---------------------- - {{3,4},{5,6},{1,2}} -(1 row) - -SELECT array_position(ARRAY[1,2,3,4,5], 4); - array_position ----------------- - 4 -(1 row) - -SELECT array_position(ARRAY[5,3,4,2,1], 4); - array_position ----------------- - 3 -(1 row) - -SELECT array_position(ARRAY[[1,2],[3,4]], 3); -ERROR: searching for elements in multidimensional arrays is not supported -SELECT array_position(ARRAY['sun','mon','tue','wed','thu','fri','sat'], 'mon'); - array_position ----------------- - 2 -(1 row) - -SELECT array_position(ARRAY['sun','mon','tue','wed','thu','fri','sat'], 'sat'); - array_position ----------------- - 7 -(1 row) - -SELECT array_position(ARRAY['sun','mon','tue','wed','thu','fri','sat'], NULL); - array_position ----------------- - -(1 row) - -SELECT array_position(ARRAY['sun','mon','tue','wed','thu',NULL,'fri','sat'], NULL); - array_position ----------------- - 6 -(1 row) - -SELECT array_position(ARRAY['sun','mon','tue','wed','thu',NULL,'fri','sat'], 'sat'); - array_position ----------------- - 8 -(1 row) - -SELECT array_positions(NULL, 10); - array_positions ------------------ - -(1 row) - -SELECT array_positions(NULL, NULL::int); - array_positions ------------------ - -(1 row) - -SELECT array_positions(ARRAY[1,2,3,4,5,6,1,2,3,4,5,6], 4); - array_positions ------------------ - {4,10} -(1 row) - -SELECT array_positions(ARRAY[[1,2],[3,4]], 4); -ERROR: searching for elements in multidimensional arrays is not supported -SELECT array_positions(ARRAY[1,2,3,4,5,6,1,2,3,4,5,6], NULL); - array_positions ------------------ - {} -(1 row) - -SELECT array_positions(ARRAY[1,2,3,NULL,5,6,1,2,3,NULL,5,6], NULL); - array_positions ------------------ - {4,10} -(1 row) - -SELECT array_length(array_positions(ARRAY(SELECT 'AAAAAAAAAAAAAAAAAAAAAAAAA'::text || i % 10 - FROM generate_series(1,100) g(i)), - 'AAAAAAAAAAAAAAAAAAAAAAAAA5'), 1); - array_length --------------- - 10 -(1 row) - -DO $$ -DECLARE - o int; - a int[] := ARRAY[1,2,3,2,3,1,2]; -BEGIN - o := array_position(a, 2); - WHILE o IS NOT NULL - LOOP - RAISE NOTICE '%', o; - o := array_position(a, 2, o + 1); - END LOOP; -END -$$ LANGUAGE plpgsql; -NOTICE: 2 -NOTICE: 4 -NOTICE: 7 -SELECT array_position('[2:4]={1,2,3}'::int[], 1); - array_position ----------------- - 2 -(1 row) - -SELECT array_positions('[2:4]={1,2,3}'::int[], 1); - array_positions ------------------ - {2} -(1 row) - -SELECT - array_position(ids, (1, 1)), - array_positions(ids, (1, 1)) - FROM -(VALUES - (ARRAY[(0, 0), (1, 1)]), - (ARRAY[(1, 1)]) -) AS f (ids); - array_position | array_positions -----------------+----------------- - 2 | {2} - 1 | {1} -(2 rows) - --- operators -SELECT a FROM arrtest WHERE b = ARRAY[[[113,142],[1,147]]]; - a ---------------- - {16,25,3,4,5} -(1 row) - -SELECT NOT ARRAY[1.1,1.2,1.3] = ARRAY[1.1,1.2,1.3] AS "FALSE"; - FALSE -------- - f -(1 row) - -SELECT ARRAY[1,2] || 3 AS "{1,2,3}"; - {1,2,3} ---------- - {1,2,3} -(1 row) - -SELECT 0 || ARRAY[1,2] AS "{0,1,2}"; - {0,1,2} ---------- - {0,1,2} -(1 row) - -SELECT ARRAY[1,2] || ARRAY[3,4] AS "{1,2,3,4}"; - {1,2,3,4} ------------ - {1,2,3,4} -(1 row) - -SELECT ARRAY[[['hello','world']]] || ARRAY[[['happy','birthday']]] AS "ARRAY"; - ARRAY --------------------------------------- - {{{hello,world}},{{happy,birthday}}} -(1 row) - -SELECT ARRAY[[1,2],[3,4]] || ARRAY[5,6] AS "{{1,2},{3,4},{5,6}}"; - {{1,2},{3,4},{5,6}} ---------------------- - {{1,2},{3,4},{5,6}} -(1 row) - -SELECT ARRAY[0,0] || ARRAY[1,1] || ARRAY[2,2] AS "{0,0,1,1,2,2}"; - {0,0,1,1,2,2} ---------------- - {0,0,1,1,2,2} -(1 row) - -SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}"; - {0,1,2,3} ------------ - {0,1,2,3} -(1 row) - -SELECT ARRAY[1.1] || ARRAY[2,3,4]; - ?column? -------------- - {1.1,2,3,4} -(1 row) - -SELECT array_agg(x) || array_agg(x) FROM (VALUES (ROW(1,2)), (ROW(3,4))) v(x); - ?column? ------------------------------------ - {"(1,2)","(3,4)","(1,2)","(3,4)"} -(1 row) - -SELECT ROW(1,2) || array_agg(x) FROM (VALUES (ROW(3,4)), (ROW(5,6))) v(x); - ?column? ---------------------------- - {"(1,2)","(3,4)","(5,6)"} -(1 row) - -SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; - seqno | i | t --------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ - 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} - 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} - 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} - 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} - 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} - 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} -(6 rows) - -SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; - seqno | i | t --------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ - 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} - 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} - 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} - 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} - 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} - 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} -(6 rows) - -SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; - seqno | i | t --------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ - 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} - 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} - 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} - 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} - 53 | {38,17} | {AAAAAAAAAAA21658} - 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} - 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} - 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} -(8 rows) - -SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; - seqno | i | t --------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ - 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} - 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} - 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} - 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} - 53 | {38,17} | {AAAAAAAAAAA21658} - 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} - 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} - 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} -(8 rows) - -SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno; - seqno | i | t --------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ - 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} - 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} - 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} -(3 rows) - -SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno; - seqno | i | t --------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ - 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} - 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} - 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} - 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} - 53 | {38,17} | {AAAAAAAAAAA21658} - 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} - 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} - 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} - 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} - 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} - 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} -(11 rows) - -SELECT * FROM array_op_test WHERE i <@ '{38,34,32,89}' ORDER BY seqno; - seqno | i | t --------+---------------+---------------------------------------------------------------------------------------------------------------------------- - 40 | {34} | {AAAAAAAAAAAAAA10611,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAA50956,AAAAAAAAAAAAAAAA31334,AAAAA70466,AAAAAAAA81587,AAAAAAA74623} - 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} - 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} - 101 | {} | {} -(4 rows) - -SELECT * FROM array_op_test WHERE i = '{}' ORDER BY seqno; - seqno | i | t --------+----+---- - 101 | {} | {} -(1 row) - -SELECT * FROM array_op_test WHERE i @> '{}' ORDER BY seqno; - seqno | i | t --------+---------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - 1 | {92,75,71,52,64,83} | {AAAAAAAA44066,AAAAAA1059,AAAAAAAAAAA176,AAAAAAA48038} - 2 | {3,6} | {AAAAAA98232,AAAAAAAA79710,AAAAAAAAAAAAAAAAA69675,AAAAAAAAAAAAAAAA55798,AAAAAAAAA12793} - 3 | {37,64,95,43,3,41,13,30,11,43} | {AAAAAAAAAA48845,AAAAA75968,AAAAA95309,AAA54451,AAAAAAAAAA22292,AAAAAAA99836,A96617,AA17009,AAAAAAAAAAAAAA95246} - 4 | {71,39,99,55,33,75,45} | {AAAAAAAAA53663,AAAAAAAAAAAAAAA67062,AAAAAAAAAA64777,AAA99043,AAAAAAAAAAAAAAAAAAA91804,39557} - 5 | {50,42,77,50,4} | {AAAAAAAAAAAAAAAAA26540,AAAAAAA79710,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAA176,AAAAA95309,AAAAAAAAAAA46154,AAAAAA66777,AAAAAAAAA27249,AAAAAAAAAA64777,AAAAAAAAAAAAAAAAAAA70104} - 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} - 7 | {12,51,88,64,8} | {AAAAAAAAAAAAAAAAAA12591,AAAAAAAAAAAAAAAAA50407,AAAAAAAAAAAA67946} - 8 | {60,84} | {AAAAAAA81898,AAAAAA1059,AAAAAAAAAAAA81511,AAAAA961,AAAAAAAAAAAAAAAA31334,AAAAA64741,AA6416,AAAAAAAAAAAAAAAAAA32918,AAAAAAAAAAAAAAAAA50407} - 9 | {56,52,35,27,80,44,81,22} | {AAAAAAAAAAAAAAA73034,AAAAAAAAAAAAA7929,AAAAAAA66161,AA88409,39557,A27153,AAAAAAAA9523,AAAAAAAAAAA99000} - 10 | {71,5,45} | {AAAAAAAAAAA21658,AAAAAAAAAAAA21089,AAA54451,AAAAAAAAAAAAAAAAAA54141,AAAAAAAAAAAAAA28620,AAAAAAAAAAA21658,AAAAAAAAAAA74076,AAAAAAAAA27249} - 11 | {41,86,74,48,22,74,47,50} | {AAAAAAAA9523,AAAAAAAAAAAA37562,AAAAAAAAAAAAAAAA14047,AAAAAAAAAAA46154,AAAA41702,AAAAAAAAAAAAAAAAA764,AAAAA62737,39557} - 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} - 13 | {3,52,34,23} | {AAAAAA98232,AAAA49534,AAAAAAAAAAA21658} - 14 | {78,57,19} | {AAAA8857,AAAAAAAAAAAAAAA73034,AAAAAAAA81587,AAAAAAAAAAAAAAA68526,AAAAA75968,AAAAAAAAAAAAAA65909,AAAAAAAAA10012,AAAAAAAAAAAAAA65909} - 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} - 16 | {14,63,85,11} | {AAAAAA66777} - 17 | {7,10,81,85} | {AAAAAA43678,AAAAAAA12144,AAAAAAAAAAA50956,AAAAAAAAAAAAAAAAAAA15356} - 18 | {1} | {AAAAAAAAAAA33576,AAAAA95309,64261,AAA59323,AAAAAAAAAAAAAA95246,55847,AAAAAAAAAAAA67946,AAAAAAAAAAAAAAAAAA64374} - 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} - 20 | {72,89,70,51,54,37,8,49,79} | {AAAAAA58494} - 21 | {2,8,65,10,5,79,43} | {AAAAAAAAAAAAAAAAA88852,AAAAAAAAAAAAAAAAAAA91804,AAAAA64669,AAAAAAAAAAAAAAAA1443,AAAAAAAAAAAAAAAA23657,AAAAA12179,AAAAAAAAAAAAAAAAA88852,AAAAAAAAAAAAAAAA31334,AAAAAAAAAAAAAAAA41303,AAAAAAAAAAAAAAAAAAA85420} - 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} - 23 | {40,90,5,38,72,40,30,10,43,55} | {A6053,AAAAAAAAAAA6119,AA44673,AAAAAAAAAAAAAAAAA764,AA17009,AAAAA17383,AAAAA70514,AAAAA33250,AAAAA95309,AAAAAAAAAAAA37562} - 24 | {94,61,99,35,48} | {AAAAAAAAAAA50956,AAAAAAAAAAA15165,AAAA85070,AAAAAAAAAAAAAAA36627,AAAAA961,AAAAAAAAAA55219} - 25 | {31,1,10,11,27,79,38} | {AAAAAAAAAAAAAAAAAA59334,45449} - 26 | {71,10,9,69,75} | {47735,AAAAAAA21462,AAAAAAAAAAAAAAAAA6897,AAAAAAAAAAAAAAAAAAA91804,AAAAAAAAA72121,AAAAAAAAAAAAAAAAAAA1205,AAAAA41597,AAAA8857,AAAAAAAAAAAAAAAAAAA15356,AA17009} - 27 | {94} | {AA6416,A6053,AAAAAAA21462,AAAAAAA57334,AAAAAAAAAAAAAAAAAA12591,AA88409,AAAAAAAAAAAAA70254} - 28 | {14,33,6,34,14} | {AAAAAAAAAAAAAAA13198,AAAAAAAA69452,AAAAAAAAAAA82945,AAAAAAA12144,AAAAAAAAA72121,AAAAAAAAAA18601} - 29 | {39,21} | {AAAAAAAAAAAAAAAAA6897,AAAAAAAAAAAAAAAAAAA38885,AAAA85070,AAAAAAAAAAAAAAAAAAA70104,AAAAA66674,AAAAAAAAAAAAA62007,AAAAAAAA69452,AAAAAAA1242,AAAAAAAAAAAAAAAA1729,AAAA35194} - 30 | {26,81,47,91,34} | {AAAAAAAAAAAAAAAAAAA70104,AAAAAAA80240} - 31 | {80,24,18,21,54} | {AAAAAAAAAAAAAAA13198,AAAAAAAAAAAAAAAAAAA70415,A27153,AAAAAAAAA53663,AAAAAAAAAAAAAAAAA50407,A68938} - 32 | {58,79,82,80,67,75,98,10,41} | {AAAAAAAAAAAAAAAAAA61286,AAA54451,AAAAAAAAAAAAAAAAAAA87527,A96617,51533} - 33 | {74,73} | {A85417,AAAAAAA56483,AAAAA17383,AAAAAAAAAAAAA62159,AAAAAAAAAAAA52814,AAAAAAAAAAAAA85723,AAAAAAAAAAAAAAAAAA55796} - 34 | {70,45} | {AAAAAAAAAAAAAAAAAA71621,AAAAAAAAAAAAAA28620,AAAAAAAAAA55219,AAAAAAAA23648,AAAAAAAAAA22292,AAAAAAA1242} - 35 | {23,40} | {AAAAAAAAAAAA52814,AAAA48949,AAAAAAAAA34727,AAAA8857,AAAAAAAAAAAAAAAAAAA62179,AAAAAAAAAAAAAAA68526,AAAAAAA99836,AAAAAAAA50094,AAAA91194,AAAAAAAAAAAAA73084} - 36 | {79,82,14,52,30,5,79} | {AAAAAAAAA53663,AAAAAAAAAAAAAAAA55798,AAAAAAAAAAAAAAAAAAA89194,AA88409,AAAAAAAAAAAAAAA81326,AAAAAAAAAAAAAAAAA63050,AAAAAAAAAAAAAAAA33598} - 37 | {53,11,81,39,3,78,58,64,74} | {AAAAAAAAAAAAAAAAAAA17075,AAAAAAA66161,AAAAAAAA23648,AAAAAAAAAAAAAA10611} - 38 | {59,5,4,95,28} | {AAAAAAAAAAA82945,A96617,47735,AAAAA12179,AAAAA64669,AAAAAA99807,AA74433,AAAAAAAAAAAAAAAAA59387} - 39 | {82,43,99,16,74} | {AAAAAAAAAAAAAAA67062,AAAAAAA57334,AAAAAAAAAAAAAA65909,A27153,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAAAA43052,AAAAAAAAAA64777,AAAAAAAAAAAA81511,AAAAAAAAAAAAAA65909,AAAAAAAAAAAAAA28620} - 40 | {34} | {AAAAAAAAAAAAAA10611,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAA50956,AAAAAAAAAAAAAAAA31334,AAAAA70466,AAAAAAAA81587,AAAAAAA74623} - 41 | {19,26,63,12,93,73,27,94} | {AAAAAAA79710,AAAAAAAAAA55219,AAAA41702,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAAAAA71621,AAAAAAAAAAAAAAAAA63050,AAAAAAA99836,AAAAAAAAAAAAAA8666} - 42 | {15,76,82,75,8,91} | {AAAAAAAAAAA176,AAAAAA38063,45449,AAAAAA54032,AAAAAAA81898,AA6416,AAAAAAAAAAAAAAAAAAA62179,45449,AAAAA60038,AAAAAAAA81587} - 43 | {39,87,91,97,79,28} | {AAAAAAAAAAA74076,A96617,AAAAAAAAAAAAAAAAAAA89194,AAAAAAAAAAAAAAAAAA55796,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAA67946} - 44 | {40,58,68,29,54} | {AAAAAAA81898,AAAAAA66777,AAAAAA98232} - 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} - 46 | {53,24} | {AAAAAAAAAAA53908,AAAAAA54032,AAAAA17383,AAAA48949,AAAAAAAAAA18601,AAAAA64669,45449,AAAAAAAAAAA98051,AAAAAAAAAAAAAAAAAA71621} - 47 | {98,23,64,12,75,61} | {AAA59323,AAAAA95309,AAAAAAAAAAAAAAAA31334,AAAAAAAAA27249,AAAAA17383,AAAAAAAAAAAA37562,AAAAAA1059,A84822,55847,AAAAA70466} - 48 | {76,14} | {AAAAAAAAAAAAA59671,AAAAAAAAAAAAAAAAAAA91804,AAAAAA66777,AAAAAAAAAAAAAAAAAAA89194,AAAAAAAAAAAAAAA36627,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAA73084,AAAAAAA79710,AAAAAAAAAAAAAAA40402,AAAAAAAAAAAAAAAAAAA65037} - 49 | {56,5,54,37,49} | {AA21643,AAAAAAAAAAA92631,AAAAAAAA81587} - 50 | {20,12,37,64,93} | {AAAAAAAAAA5483,AAAAAAAAAAAAAAAAAAA1205,AA6416,AAAAAAAAAAAAAAAAA63050,AAAAAAAAAAAAAAAAAA47955} - 51 | {47} | {AAAAAAAAAAAAAA96505,AAAAAAAAAAAAAAAAAA36842,AAAAA95309,AAAAAAAA81587,AA6416,AAAA91194,AAAAAA58494,AAAAAA1059,AAAAAAAA69452} - 52 | {89,0} | {AAAAAAAAAAAAAAAAAA47955,AAAAAAA48038,AAAAAAAAAAAAAAAAA43052,AAAAAAAAAAAAA73084,AAAAA70466,AAAAAAAAAAAAAAAAA764,AAAAAAAAAAA46154,AA66862} - 53 | {38,17} | {AAAAAAAAAAA21658} - 54 | {70,47} | {AAAAAAAAAAAAAAAAAA54141,AAAAA40681,AAAAAAA48038,AAAAAAAAAAAAAAAA29150,AAAAA41597,AAAAAAAAAAAAAAAAAA59334,AA15322} - 55 | {47,79,47,64,72,25,71,24,93} | {AAAAAAAAAAAAAAAAAA55796,AAAAA62737} - 56 | {33,7,60,54,93,90,77,85,39} | {AAAAAAAAAAAAAAAAAA32918,AA42406} - 57 | {23,45,10,42,36,21,9,96} | {AAAAAAAAAAAAAAAAAAA70415} - 58 | {92} | {AAAAAAAAAAAAAAAA98414,AAAAAAAA23648,AAAAAAAAAAAAAAAAAA55796,AA25381,AAAAAAAAAAA6119} - 59 | {9,69,46,77} | {39557,AAAAAAA89932,AAAAAAAAAAAAAAAAA43052,AAAAAAAAAAAAAAAAA26540,AAA20874,AA6416,AAAAAAAAAAAAAAAAAA47955} - 60 | {62,2,59,38,89} | {AAAAAAA89932,AAAAAAAAAAAAAAAAAAA15356,AA99927,AA17009,AAAAAAAAAAAAAAA35875} - 61 | {72,2,44,95,54,54,13} | {AAAAAAAAAAAAAAAAAAA91804} - 62 | {83,72,29,73} | {AAAAAAAAAAAAA15097,AAAA8857,AAAAAAAAAAAA35809,AAAAAAAAAAAA52814,AAAAAAAAAAAAAAAAAAA38885,AAAAAAAAAAAAAAAAAA24183,AAAAAA43678,A96617} - 63 | {11,4,61,87} | {AAAAAAAAA27249,AAAAAAAAAAAAAAAAAA32918,AAAAAAAAAAAAAAA13198,AAA20874,39557,51533,AAAAAAAAAAA53908,AAAAAAAAAAAAAA96505,AAAAAAAA78938} - 64 | {26,19,34,24,81,78} | {A96617,AAAAAAAAAAAAAAAAAAA70104,A68938,AAAAAAAAAAA53908,AAAAAAAAAAAAAAA453,AA17009,AAAAAAA80240} - 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} - 66 | {31,23,70,52,4,33,48,25} | {AAAAAAAAAAAAAAAAA69675,AAAAAAAA50094,AAAAAAAAAAA92631,AAAA35194,39557,AAAAAAA99836} - 67 | {31,94,7,10} | {AAAAAA38063,A96617,AAAA35194,AAAAAAAAAAAA67946} - 68 | {90,43,38} | {AA75092,AAAAAAAAAAAAAAAAA69675,AAAAAAAAAAA92631,AAAAAAAAA10012,AAAAAAAAAAAAA7929,AA21643} - 69 | {67,35,99,85,72,86,44} | {AAAAAAAAAAAAAAAAAAA1205,AAAAAAAA50094,AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAAAAAAA47955} - 70 | {56,70,83} | {AAAA41702,AAAAAAAAAAA82945,AA21643,AAAAAAAAAAA99000,A27153,AA25381,AAAAAAAAAAAAAA96505,AAAAAAA1242} - 71 | {74,26} | {AAAAAAAAAAA50956,AA74433,AAAAAAA21462,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAA36627,AAAAAAAAAAAAA70254,AAAAAAAAAA43419,39557} - 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} - 73 | {88,25,96,78,65,15,29,19} | {AAA54451,AAAAAAAAA27249,AAAAAAA9228,AAAAAAAAAAAAAAA67062,AAAAAAAAAAAAAAAAAAA70415,AAAAA17383,AAAAAAAAAAAAAAAA33598} - 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} - 75 | {12,96,83,24,71,89,55} | {AAAA48949,AAAAAAAA29716,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAAA67946,AAAAAAAAAAAAAAAA29150,AAA28075,AAAAAAAAAAAAAAAAA43052} - 76 | {92,55,10,7} | {AAAAAAAAAAAAAAA67062} - 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} - 78 | {55,89,44,84,34} | {AAAAAAAAAAA6119,AAAAAAAAAAAAAA8666,AA99927,AA42406,AAAAAAA81898,AAAAAAA9228,AAAAAAAAAAA92631,AA21643,AAAAAAAAAAAAAA28620} - 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} - 80 | {74,89,44,80,0} | {AAAA35194,AAAAAAAA79710,AAA20874,AAAAAAAAAAAAAAAAAAA70104,AAAAAAAAAAAAA73084,AAAAAAA57334,AAAAAAA9228,AAAAAAAAAAAAA62007} - 81 | {63,77,54,48,61,53,97} | {AAAAAAAAAAAAAAA81326,AAAAAAAAAA22292,AA25381,AAAAAAAAAAA74076,AAAAAAA81898,AAAAAAAAA72121} - 82 | {34,60,4,79,78,16,86,89,42,50} | {AAAAA40681,AAAAAAAAAAAAAAAAAA12591,AAAAAAA80240,AAAAAAAAAAAAAAAA55798,AAAAAAAAAAAAAAAAAAA70104} - 83 | {14,10} | {AAAAAAAAAA22292,AAAAAAAAAAAAA70254,AAAAAAAAAAA6119} - 84 | {11,83,35,13,96,94} | {AAAAA95309,AAAAAAAAAAAAAAAAAA32918,AAAAAAAAAAAAAAAAAA24183} - 85 | {39,60} | {AAAAAAAAAAAAAAAA55798,AAAAAAAAAA22292,AAAAAAA66161,AAAAAAA21462,AAAAAAAAAAAAAAAAAA12591,55847,AAAAAA98232,AAAAAAAAAAA46154} - 86 | {33,81,72,74,45,36,82} | {AAAAAAAA81587,AAAAAAAAAAAAAA96505,45449,AAAA80176} - 87 | {57,27,50,12,97,68} | {AAAAAAAAAAAAAAAAA26540,AAAAAAAAA10012,AAAAAAAAAAAA35809,AAAAAAAAAAAAAAAA29150,AAAAAAAAAAA82945,AAAAAA66777,31228,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAA96505} - 88 | {41,90,77,24,6,24} | {AAAA35194,AAAA35194,AAAAAAA80240,AAAAAAAAAAA46154,AAAAAA58494,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAAAAA59334,AAAAAAAAAAAAAAAAAAA91804,AA74433} - 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} - 90 | {88,75} | {AAAAA60038,AAAAAAAA23648,AAAAAAAAAAA99000,AAAA41702,AAAAAAAAAAAAA22860,AAAAAAAAAAAAAAA68526} - 91 | {78} | {AAAAAAAAAAAAA62007,AAA99043} - 92 | {85,63,49,45} | {AAAAAAA89932,AAAAAAAAAAAAA22860,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAAA21089} - 93 | {11} | {AAAAAAAAAAA176,AAAAAAAAAAAAAA8666,AAAAAAAAAAAAAAA453,AAAAAAAAAAAAA85723,A68938,AAAAAAAAAAAAA9821,AAAAAAA48038,AAAAAAAAAAAAAAAAA59387,AA99927,AAAAA17383} - 94 | {98,9,85,62,88,91,60,61,38,86} | {AAAAAAAA81587,AAAAA17383,AAAAAAAA81587} - 95 | {47,77} | {AAAAAAAAAAAAAAAAA764,AAAAAAAAAAA74076,AAAAAAAAAA18107,AAAAA40681,AAAAAAAAAAAAAAA35875,AAAAA60038,AAAAAAA56483} - 96 | {23,97,43} | {AAAAAAAAAA646,A87088} - 97 | {54,2,86,65} | {47735,AAAAAAA99836,AAAAAAAAAAAAAAAAA6897,AAAAAAAAAAAAAAAA29150,AAAAAAA80240,AAAAAAAAAAAAAAAA98414,AAAAAAA56483,AAAAAAAAAAAAAAAA29150,AAAAAAA39692,AA21643} - 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} - 99 | {37,86} | {AAAAAAAAAAAAAAAAAA32918,AAAAA70514,AAAAAAAAA10012,AAAAAAAAAAAAAAAAA59387,AAAAAAAAAA64777,AAAAAAAAAAAAAAAAAAA15356} - 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} - 101 | {} | {} - 102 | {NULL} | {NULL} -(102 rows) - -SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno; - seqno | i | t --------+---+--- -(0 rows) - -SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno; - seqno | i | t --------+----+---- - 101 | {} | {} -(1 row) - -SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno; - seqno | i | t --------+--------+-------- - 102 | {NULL} | {NULL} -(1 row) - -SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; - seqno | i | t --------+---+--- -(0 rows) - -SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; - seqno | i | t --------+---+--- -(0 rows) - -SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno; - seqno | i | t --------+----+---- - 101 | {} | {} -(1 row) - -SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; - seqno | i | t --------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- - 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} - 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} - 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} - 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} -(4 rows) - -SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; - seqno | i | t --------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- - 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} - 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} - 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} - 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} -(4 rows) - -SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; - seqno | i | t --------+------------------+-------------------------------------------------------------------- - 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} - 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} - 96 | {23,97,43} | {AAAAAAAAAA646,A87088} -(3 rows) - -SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; - seqno | i | t --------+------------------+-------------------------------------------------------------------- - 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} - 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} - 96 | {23,97,43} | {AAAAAAAAAA646,A87088} -(3 rows) - -SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; - seqno | i | t --------+------+-------------------------------------------------------------------- - 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} -(1 row) - -SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; - seqno | i | t --------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- - 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} - 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} - 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} - 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} - 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} - 96 | {23,97,43} | {AAAAAAAAAA646,A87088} -(6 rows) - -SELECT * FROM array_op_test WHERE t <@ '{AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}' ORDER BY seqno; - seqno | i | t --------+--------------------+----------------------------------------------------------------------------------------------------------- - 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} - 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} - 101 | {} | {} -(3 rows) - -SELECT * FROM array_op_test WHERE t = '{}' ORDER BY seqno; - seqno | i | t --------+----+---- - 101 | {} | {} -(1 row) - -SELECT * FROM array_op_test WHERE t @> '{}' ORDER BY seqno; - seqno | i | t --------+---------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - 1 | {92,75,71,52,64,83} | {AAAAAAAA44066,AAAAAA1059,AAAAAAAAAAA176,AAAAAAA48038} - 2 | {3,6} | {AAAAAA98232,AAAAAAAA79710,AAAAAAAAAAAAAAAAA69675,AAAAAAAAAAAAAAAA55798,AAAAAAAAA12793} - 3 | {37,64,95,43,3,41,13,30,11,43} | {AAAAAAAAAA48845,AAAAA75968,AAAAA95309,AAA54451,AAAAAAAAAA22292,AAAAAAA99836,A96617,AA17009,AAAAAAAAAAAAAA95246} - 4 | {71,39,99,55,33,75,45} | {AAAAAAAAA53663,AAAAAAAAAAAAAAA67062,AAAAAAAAAA64777,AAA99043,AAAAAAAAAAAAAAAAAAA91804,39557} - 5 | {50,42,77,50,4} | {AAAAAAAAAAAAAAAAA26540,AAAAAAA79710,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAA176,AAAAA95309,AAAAAAAAAAA46154,AAAAAA66777,AAAAAAAAA27249,AAAAAAAAAA64777,AAAAAAAAAAAAAAAAAAA70104} - 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} - 7 | {12,51,88,64,8} | {AAAAAAAAAAAAAAAAAA12591,AAAAAAAAAAAAAAAAA50407,AAAAAAAAAAAA67946} - 8 | {60,84} | {AAAAAAA81898,AAAAAA1059,AAAAAAAAAAAA81511,AAAAA961,AAAAAAAAAAAAAAAA31334,AAAAA64741,AA6416,AAAAAAAAAAAAAAAAAA32918,AAAAAAAAAAAAAAAAA50407} - 9 | {56,52,35,27,80,44,81,22} | {AAAAAAAAAAAAAAA73034,AAAAAAAAAAAAA7929,AAAAAAA66161,AA88409,39557,A27153,AAAAAAAA9523,AAAAAAAAAAA99000} - 10 | {71,5,45} | {AAAAAAAAAAA21658,AAAAAAAAAAAA21089,AAA54451,AAAAAAAAAAAAAAAAAA54141,AAAAAAAAAAAAAA28620,AAAAAAAAAAA21658,AAAAAAAAAAA74076,AAAAAAAAA27249} - 11 | {41,86,74,48,22,74,47,50} | {AAAAAAAA9523,AAAAAAAAAAAA37562,AAAAAAAAAAAAAAAA14047,AAAAAAAAAAA46154,AAAA41702,AAAAAAAAAAAAAAAAA764,AAAAA62737,39557} - 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} - 13 | {3,52,34,23} | {AAAAAA98232,AAAA49534,AAAAAAAAAAA21658} - 14 | {78,57,19} | {AAAA8857,AAAAAAAAAAAAAAA73034,AAAAAAAA81587,AAAAAAAAAAAAAAA68526,AAAAA75968,AAAAAAAAAAAAAA65909,AAAAAAAAA10012,AAAAAAAAAAAAAA65909} - 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} - 16 | {14,63,85,11} | {AAAAAA66777} - 17 | {7,10,81,85} | {AAAAAA43678,AAAAAAA12144,AAAAAAAAAAA50956,AAAAAAAAAAAAAAAAAAA15356} - 18 | {1} | {AAAAAAAAAAA33576,AAAAA95309,64261,AAA59323,AAAAAAAAAAAAAA95246,55847,AAAAAAAAAAAA67946,AAAAAAAAAAAAAAAAAA64374} - 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} - 20 | {72,89,70,51,54,37,8,49,79} | {AAAAAA58494} - 21 | {2,8,65,10,5,79,43} | {AAAAAAAAAAAAAAAAA88852,AAAAAAAAAAAAAAAAAAA91804,AAAAA64669,AAAAAAAAAAAAAAAA1443,AAAAAAAAAAAAAAAA23657,AAAAA12179,AAAAAAAAAAAAAAAAA88852,AAAAAAAAAAAAAAAA31334,AAAAAAAAAAAAAAAA41303,AAAAAAAAAAAAAAAAAAA85420} - 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} - 23 | {40,90,5,38,72,40,30,10,43,55} | {A6053,AAAAAAAAAAA6119,AA44673,AAAAAAAAAAAAAAAAA764,AA17009,AAAAA17383,AAAAA70514,AAAAA33250,AAAAA95309,AAAAAAAAAAAA37562} - 24 | {94,61,99,35,48} | {AAAAAAAAAAA50956,AAAAAAAAAAA15165,AAAA85070,AAAAAAAAAAAAAAA36627,AAAAA961,AAAAAAAAAA55219} - 25 | {31,1,10,11,27,79,38} | {AAAAAAAAAAAAAAAAAA59334,45449} - 26 | {71,10,9,69,75} | {47735,AAAAAAA21462,AAAAAAAAAAAAAAAAA6897,AAAAAAAAAAAAAAAAAAA91804,AAAAAAAAA72121,AAAAAAAAAAAAAAAAAAA1205,AAAAA41597,AAAA8857,AAAAAAAAAAAAAAAAAAA15356,AA17009} - 27 | {94} | {AA6416,A6053,AAAAAAA21462,AAAAAAA57334,AAAAAAAAAAAAAAAAAA12591,AA88409,AAAAAAAAAAAAA70254} - 28 | {14,33,6,34,14} | {AAAAAAAAAAAAAAA13198,AAAAAAAA69452,AAAAAAAAAAA82945,AAAAAAA12144,AAAAAAAAA72121,AAAAAAAAAA18601} - 29 | {39,21} | {AAAAAAAAAAAAAAAAA6897,AAAAAAAAAAAAAAAAAAA38885,AAAA85070,AAAAAAAAAAAAAAAAAAA70104,AAAAA66674,AAAAAAAAAAAAA62007,AAAAAAAA69452,AAAAAAA1242,AAAAAAAAAAAAAAAA1729,AAAA35194} - 30 | {26,81,47,91,34} | {AAAAAAAAAAAAAAAAAAA70104,AAAAAAA80240} - 31 | {80,24,18,21,54} | {AAAAAAAAAAAAAAA13198,AAAAAAAAAAAAAAAAAAA70415,A27153,AAAAAAAAA53663,AAAAAAAAAAAAAAAAA50407,A68938} - 32 | {58,79,82,80,67,75,98,10,41} | {AAAAAAAAAAAAAAAAAA61286,AAA54451,AAAAAAAAAAAAAAAAAAA87527,A96617,51533} - 33 | {74,73} | {A85417,AAAAAAA56483,AAAAA17383,AAAAAAAAAAAAA62159,AAAAAAAAAAAA52814,AAAAAAAAAAAAA85723,AAAAAAAAAAAAAAAAAA55796} - 34 | {70,45} | {AAAAAAAAAAAAAAAAAA71621,AAAAAAAAAAAAAA28620,AAAAAAAAAA55219,AAAAAAAA23648,AAAAAAAAAA22292,AAAAAAA1242} - 35 | {23,40} | {AAAAAAAAAAAA52814,AAAA48949,AAAAAAAAA34727,AAAA8857,AAAAAAAAAAAAAAAAAAA62179,AAAAAAAAAAAAAAA68526,AAAAAAA99836,AAAAAAAA50094,AAAA91194,AAAAAAAAAAAAA73084} - 36 | {79,82,14,52,30,5,79} | {AAAAAAAAA53663,AAAAAAAAAAAAAAAA55798,AAAAAAAAAAAAAAAAAAA89194,AA88409,AAAAAAAAAAAAAAA81326,AAAAAAAAAAAAAAAAA63050,AAAAAAAAAAAAAAAA33598} - 37 | {53,11,81,39,3,78,58,64,74} | {AAAAAAAAAAAAAAAAAAA17075,AAAAAAA66161,AAAAAAAA23648,AAAAAAAAAAAAAA10611} - 38 | {59,5,4,95,28} | {AAAAAAAAAAA82945,A96617,47735,AAAAA12179,AAAAA64669,AAAAAA99807,AA74433,AAAAAAAAAAAAAAAAA59387} - 39 | {82,43,99,16,74} | {AAAAAAAAAAAAAAA67062,AAAAAAA57334,AAAAAAAAAAAAAA65909,A27153,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAAAA43052,AAAAAAAAAA64777,AAAAAAAAAAAA81511,AAAAAAAAAAAAAA65909,AAAAAAAAAAAAAA28620} - 40 | {34} | {AAAAAAAAAAAAAA10611,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAA50956,AAAAAAAAAAAAAAAA31334,AAAAA70466,AAAAAAAA81587,AAAAAAA74623} - 41 | {19,26,63,12,93,73,27,94} | {AAAAAAA79710,AAAAAAAAAA55219,AAAA41702,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAAAAA71621,AAAAAAAAAAAAAAAAA63050,AAAAAAA99836,AAAAAAAAAAAAAA8666} - 42 | {15,76,82,75,8,91} | {AAAAAAAAAAA176,AAAAAA38063,45449,AAAAAA54032,AAAAAAA81898,AA6416,AAAAAAAAAAAAAAAAAAA62179,45449,AAAAA60038,AAAAAAAA81587} - 43 | {39,87,91,97,79,28} | {AAAAAAAAAAA74076,A96617,AAAAAAAAAAAAAAAAAAA89194,AAAAAAAAAAAAAAAAAA55796,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAA67946} - 44 | {40,58,68,29,54} | {AAAAAAA81898,AAAAAA66777,AAAAAA98232} - 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} - 46 | {53,24} | {AAAAAAAAAAA53908,AAAAAA54032,AAAAA17383,AAAA48949,AAAAAAAAAA18601,AAAAA64669,45449,AAAAAAAAAAA98051,AAAAAAAAAAAAAAAAAA71621} - 47 | {98,23,64,12,75,61} | {AAA59323,AAAAA95309,AAAAAAAAAAAAAAAA31334,AAAAAAAAA27249,AAAAA17383,AAAAAAAAAAAA37562,AAAAAA1059,A84822,55847,AAAAA70466} - 48 | {76,14} | {AAAAAAAAAAAAA59671,AAAAAAAAAAAAAAAAAAA91804,AAAAAA66777,AAAAAAAAAAAAAAAAAAA89194,AAAAAAAAAAAAAAA36627,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAA73084,AAAAAAA79710,AAAAAAAAAAAAAAA40402,AAAAAAAAAAAAAAAAAAA65037} - 49 | {56,5,54,37,49} | {AA21643,AAAAAAAAAAA92631,AAAAAAAA81587} - 50 | {20,12,37,64,93} | {AAAAAAAAAA5483,AAAAAAAAAAAAAAAAAAA1205,AA6416,AAAAAAAAAAAAAAAAA63050,AAAAAAAAAAAAAAAAAA47955} - 51 | {47} | {AAAAAAAAAAAAAA96505,AAAAAAAAAAAAAAAAAA36842,AAAAA95309,AAAAAAAA81587,AA6416,AAAA91194,AAAAAA58494,AAAAAA1059,AAAAAAAA69452} - 52 | {89,0} | {AAAAAAAAAAAAAAAAAA47955,AAAAAAA48038,AAAAAAAAAAAAAAAAA43052,AAAAAAAAAAAAA73084,AAAAA70466,AAAAAAAAAAAAAAAAA764,AAAAAAAAAAA46154,AA66862} - 53 | {38,17} | {AAAAAAAAAAA21658} - 54 | {70,47} | {AAAAAAAAAAAAAAAAAA54141,AAAAA40681,AAAAAAA48038,AAAAAAAAAAAAAAAA29150,AAAAA41597,AAAAAAAAAAAAAAAAAA59334,AA15322} - 55 | {47,79,47,64,72,25,71,24,93} | {AAAAAAAAAAAAAAAAAA55796,AAAAA62737} - 56 | {33,7,60,54,93,90,77,85,39} | {AAAAAAAAAAAAAAAAAA32918,AA42406} - 57 | {23,45,10,42,36,21,9,96} | {AAAAAAAAAAAAAAAAAAA70415} - 58 | {92} | {AAAAAAAAAAAAAAAA98414,AAAAAAAA23648,AAAAAAAAAAAAAAAAAA55796,AA25381,AAAAAAAAAAA6119} - 59 | {9,69,46,77} | {39557,AAAAAAA89932,AAAAAAAAAAAAAAAAA43052,AAAAAAAAAAAAAAAAA26540,AAA20874,AA6416,AAAAAAAAAAAAAAAAAA47955} - 60 | {62,2,59,38,89} | {AAAAAAA89932,AAAAAAAAAAAAAAAAAAA15356,AA99927,AA17009,AAAAAAAAAAAAAAA35875} - 61 | {72,2,44,95,54,54,13} | {AAAAAAAAAAAAAAAAAAA91804} - 62 | {83,72,29,73} | {AAAAAAAAAAAAA15097,AAAA8857,AAAAAAAAAAAA35809,AAAAAAAAAAAA52814,AAAAAAAAAAAAAAAAAAA38885,AAAAAAAAAAAAAAAAAA24183,AAAAAA43678,A96617} - 63 | {11,4,61,87} | {AAAAAAAAA27249,AAAAAAAAAAAAAAAAAA32918,AAAAAAAAAAAAAAA13198,AAA20874,39557,51533,AAAAAAAAAAA53908,AAAAAAAAAAAAAA96505,AAAAAAAA78938} - 64 | {26,19,34,24,81,78} | {A96617,AAAAAAAAAAAAAAAAAAA70104,A68938,AAAAAAAAAAA53908,AAAAAAAAAAAAAAA453,AA17009,AAAAAAA80240} - 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} - 66 | {31,23,70,52,4,33,48,25} | {AAAAAAAAAAAAAAAAA69675,AAAAAAAA50094,AAAAAAAAAAA92631,AAAA35194,39557,AAAAAAA99836} - 67 | {31,94,7,10} | {AAAAAA38063,A96617,AAAA35194,AAAAAAAAAAAA67946} - 68 | {90,43,38} | {AA75092,AAAAAAAAAAAAAAAAA69675,AAAAAAAAAAA92631,AAAAAAAAA10012,AAAAAAAAAAAAA7929,AA21643} - 69 | {67,35,99,85,72,86,44} | {AAAAAAAAAAAAAAAAAAA1205,AAAAAAAA50094,AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAAAAAAA47955} - 70 | {56,70,83} | {AAAA41702,AAAAAAAAAAA82945,AA21643,AAAAAAAAAAA99000,A27153,AA25381,AAAAAAAAAAAAAA96505,AAAAAAA1242} - 71 | {74,26} | {AAAAAAAAAAA50956,AA74433,AAAAAAA21462,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAA36627,AAAAAAAAAAAAA70254,AAAAAAAAAA43419,39557} - 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} - 73 | {88,25,96,78,65,15,29,19} | {AAA54451,AAAAAAAAA27249,AAAAAAA9228,AAAAAAAAAAAAAAA67062,AAAAAAAAAAAAAAAAAAA70415,AAAAA17383,AAAAAAAAAAAAAAAA33598} - 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} - 75 | {12,96,83,24,71,89,55} | {AAAA48949,AAAAAAAA29716,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAAA67946,AAAAAAAAAAAAAAAA29150,AAA28075,AAAAAAAAAAAAAAAAA43052} - 76 | {92,55,10,7} | {AAAAAAAAAAAAAAA67062} - 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} - 78 | {55,89,44,84,34} | {AAAAAAAAAAA6119,AAAAAAAAAAAAAA8666,AA99927,AA42406,AAAAAAA81898,AAAAAAA9228,AAAAAAAAAAA92631,AA21643,AAAAAAAAAAAAAA28620} - 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} - 80 | {74,89,44,80,0} | {AAAA35194,AAAAAAAA79710,AAA20874,AAAAAAAAAAAAAAAAAAA70104,AAAAAAAAAAAAA73084,AAAAAAA57334,AAAAAAA9228,AAAAAAAAAAAAA62007} - 81 | {63,77,54,48,61,53,97} | {AAAAAAAAAAAAAAA81326,AAAAAAAAAA22292,AA25381,AAAAAAAAAAA74076,AAAAAAA81898,AAAAAAAAA72121} - 82 | {34,60,4,79,78,16,86,89,42,50} | {AAAAA40681,AAAAAAAAAAAAAAAAAA12591,AAAAAAA80240,AAAAAAAAAAAAAAAA55798,AAAAAAAAAAAAAAAAAAA70104} - 83 | {14,10} | {AAAAAAAAAA22292,AAAAAAAAAAAAA70254,AAAAAAAAAAA6119} - 84 | {11,83,35,13,96,94} | {AAAAA95309,AAAAAAAAAAAAAAAAAA32918,AAAAAAAAAAAAAAAAAA24183} - 85 | {39,60} | {AAAAAAAAAAAAAAAA55798,AAAAAAAAAA22292,AAAAAAA66161,AAAAAAA21462,AAAAAAAAAAAAAAAAAA12591,55847,AAAAAA98232,AAAAAAAAAAA46154} - 86 | {33,81,72,74,45,36,82} | {AAAAAAAA81587,AAAAAAAAAAAAAA96505,45449,AAAA80176} - 87 | {57,27,50,12,97,68} | {AAAAAAAAAAAAAAAAA26540,AAAAAAAAA10012,AAAAAAAAAAAA35809,AAAAAAAAAAAAAAAA29150,AAAAAAAAAAA82945,AAAAAA66777,31228,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAA96505} - 88 | {41,90,77,24,6,24} | {AAAA35194,AAAA35194,AAAAAAA80240,AAAAAAAAAAA46154,AAAAAA58494,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAAAAA59334,AAAAAAAAAAAAAAAAAAA91804,AA74433} - 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} - 90 | {88,75} | {AAAAA60038,AAAAAAAA23648,AAAAAAAAAAA99000,AAAA41702,AAAAAAAAAAAAA22860,AAAAAAAAAAAAAAA68526} - 91 | {78} | {AAAAAAAAAAAAA62007,AAA99043} - 92 | {85,63,49,45} | {AAAAAAA89932,AAAAAAAAAAAAA22860,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAAA21089} - 93 | {11} | {AAAAAAAAAAA176,AAAAAAAAAAAAAA8666,AAAAAAAAAAAAAAA453,AAAAAAAAAAAAA85723,A68938,AAAAAAAAAAAAA9821,AAAAAAA48038,AAAAAAAAAAAAAAAAA59387,AA99927,AAAAA17383} - 94 | {98,9,85,62,88,91,60,61,38,86} | {AAAAAAAA81587,AAAAA17383,AAAAAAAA81587} - 95 | {47,77} | {AAAAAAAAAAAAAAAAA764,AAAAAAAAAAA74076,AAAAAAAAAA18107,AAAAA40681,AAAAAAAAAAAAAAA35875,AAAAA60038,AAAAAAA56483} - 96 | {23,97,43} | {AAAAAAAAAA646,A87088} - 97 | {54,2,86,65} | {47735,AAAAAAA99836,AAAAAAAAAAAAAAAAA6897,AAAAAAAAAAAAAAAA29150,AAAAAAA80240,AAAAAAAAAAAAAAAA98414,AAAAAAA56483,AAAAAAAAAAAAAAAA29150,AAAAAAA39692,AA21643} - 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} - 99 | {37,86} | {AAAAAAAAAAAAAAAAAA32918,AAAAA70514,AAAAAAAAA10012,AAAAAAAAAAAAAAAAA59387,AAAAAAAAAA64777,AAAAAAAAAAAAAAAAAAA15356} - 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} - 101 | {} | {} - 102 | {NULL} | {NULL} -(102 rows) - -SELECT * FROM array_op_test WHERE t && '{}' ORDER BY seqno; - seqno | i | t --------+---+--- -(0 rows) - -SELECT * FROM array_op_test WHERE t <@ '{}' ORDER BY seqno; - seqno | i | t --------+----+---- - 101 | {} | {} -(1 row) - --- array casts -SELECT ARRAY[1,2,3]::text[]::int[]::float8[] AS "{1,2,3}"; - {1,2,3} ---------- - {1,2,3} -(1 row) - -SELECT pg_typeof(ARRAY[1,2,3]::text[]::int[]::float8[]) AS "double precision[]"; - double precision[] --------------------- - double precision[] -(1 row) - -SELECT ARRAY[['a','bc'],['def','hijk']]::text[]::varchar[] AS "{{a,bc},{def,hijk}}"; - {{a,bc},{def,hijk}} ---------------------- - {{a,bc},{def,hijk}} -(1 row) - -SELECT pg_typeof(ARRAY[['a','bc'],['def','hijk']]::text[]::varchar[]) AS "character varying[]"; - character varying[] ---------------------- - character varying[] -(1 row) - -SELECT CAST(ARRAY[[[[[['a','bb','ccc']]]]]] as text[]) as "{{{{{{a,bb,ccc}}}}}}"; - {{{{{{a,bb,ccc}}}}}} ----------------------- - {{{{{{a,bb,ccc}}}}}} -(1 row) - -SELECT NULL::text[]::int[] AS "NULL"; - NULL ------- - -(1 row) - --- scalar op any/all (array) -select 33 = any ('{1,2,3}'); - ?column? ----------- - f -(1 row) - -select 33 = any ('{1,2,33}'); - ?column? ----------- - t -(1 row) - -select 33 = all ('{1,2,33}'); - ?column? ----------- - f -(1 row) - -select 33 >= all ('{1,2,33}'); - ?column? ----------- - t -(1 row) - --- boundary cases -select null::int >= all ('{1,2,33}'); - ?column? ----------- - -(1 row) - -select null::int >= all ('{}'); - ?column? ----------- - t -(1 row) - -select null::int >= any ('{}'); - ?column? ----------- - f -(1 row) - --- cross-datatype -select 33.4 = any (array[1,2,3]); - ?column? ----------- - f -(1 row) - -select 33.4 > all (array[1,2,3]); - ?column? ----------- - t -(1 row) - --- errors -select 33 * any ('{1,2,3}'); -ERROR: op ANY/ALL (array) requires operator to yield boolean -LINE 1: select 33 * any ('{1,2,3}'); - ^ -select 33 * any (44); -ERROR: op ANY/ALL (array) requires array on right side -LINE 1: select 33 * any (44); - ^ --- nulls -select 33 = any (null::int[]); - ?column? ----------- - -(1 row) - -select null::int = any ('{1,2,3}'); - ?column? ----------- - -(1 row) - -select 33 = any ('{1,null,3}'); - ?column? ----------- - -(1 row) - -select 33 = any ('{1,null,33}'); - ?column? ----------- - t -(1 row) - -select 33 = all (null::int[]); - ?column? ----------- - -(1 row) - -select null::int = all ('{1,2,3}'); - ?column? ----------- - -(1 row) - -select 33 = all ('{1,null,3}'); - ?column? ----------- - f -(1 row) - -select 33 = all ('{33,null,33}'); - ?column? ----------- - -(1 row) - --- nulls later in the bitmap -SELECT -1 != ALL(ARRAY(SELECT NULLIF(g.i, 900) FROM generate_series(1,1000) g(i))); - ?column? ----------- - -(1 row) - --- test indexes on arrays -create temp table arr_tbl (f1 int[] unique); -insert into arr_tbl values ('{1,2,3}'); -insert into arr_tbl values ('{1,2}'); --- failure expected: -insert into arr_tbl values ('{1,2,3}'); -ERROR: duplicate key value violates unique constraint "arr_tbl_f1_key" -DETAIL: Key (f1)=({1,2,3}) already exists. -insert into arr_tbl values ('{2,3,4}'); -insert into arr_tbl values ('{1,5,3}'); -insert into arr_tbl values ('{1,2,10}'); -set enable_seqscan to off; -set enable_bitmapscan to off; -select * from arr_tbl where f1 > '{1,2,3}' and f1 <= '{1,5,3}'; - f1 ----------- - {1,2,10} - {1,5,3} -(2 rows) - -select * from arr_tbl where f1 >= '{1,2,3}' and f1 < '{1,5,3}'; - f1 ----------- - {1,2,3} - {1,2,10} -(2 rows) - --- test ON CONFLICT DO UPDATE with arrays -create temp table arr_pk_tbl (pk int4 primary key, f1 int[]); -insert into arr_pk_tbl values (1, '{1,2,3}'); -insert into arr_pk_tbl values (1, '{3,4,5}') on conflict (pk) - do update set f1[1] = excluded.f1[1], f1[3] = excluded.f1[3] - returning pk, f1; - pk | f1 -----+--------- - 1 | {3,2,5} -(1 row) - -insert into arr_pk_tbl(pk, f1[1:2]) values (1, '{6,7,8}') on conflict (pk) - do update set f1[1] = excluded.f1[1], - f1[2] = excluded.f1[2], - f1[3] = excluded.f1[3] - returning pk, f1; - pk | f1 -----+------------ - 1 | {6,7,NULL} -(1 row) - --- note: if above selects don't produce the expected tuple order, --- then you didn't get an indexscan plan, and something is busted. -reset enable_seqscan; -reset enable_bitmapscan; --- test subscript overflow detection --- The normal error message includes a platform-dependent limit, --- so suppress it to avoid needing multiple expected-files. -\set VERBOSITY sqlstate -insert into arr_pk_tbl values(10, '[-2147483648:-2147483647]={1,2}'); -update arr_pk_tbl set f1[2147483647] = 42 where pk = 10; -ERROR: 54000 -update arr_pk_tbl set f1[2147483646:2147483647] = array[4,2] where pk = 10; -ERROR: 54000 -insert into arr_pk_tbl(pk, f1[0:2147483647]) values (2, '{}'); -ERROR: 54000 -insert into arr_pk_tbl(pk, f1[-2147483648:2147483647]) values (2, '{}'); -ERROR: 54000 --- also exercise the expanded-array case -do $$ declare a int[]; -begin - a := '[-2147483648:-2147483647]={1,2}'::int[]; - a[2147483647] := 42; -end $$; -ERROR: 54000 -\set VERBOSITY default --- test [not] (like|ilike) (any|all) (...) -select 'foo' like any (array['%a', '%o']); -- t - ?column? ----------- - t -(1 row) - -select 'foo' like any (array['%a', '%b']); -- f - ?column? ----------- - f -(1 row) - -select 'foo' like all (array['f%', '%o']); -- t - ?column? ----------- - t -(1 row) - -select 'foo' like all (array['f%', '%b']); -- f - ?column? ----------- - f -(1 row) - -select 'foo' not like any (array['%a', '%b']); -- t - ?column? ----------- - t -(1 row) - -select 'foo' not like all (array['%a', '%o']); -- f - ?column? ----------- - f -(1 row) - -select 'foo' ilike any (array['%A', '%O']); -- t - ?column? ----------- - t -(1 row) - -select 'foo' ilike all (array['F%', '%O']); -- t - ?column? ----------- - t -(1 row) - --- --- General array parser tests --- --- none of the following should be accepted -select '{{1,{2}},{2,3}}'::text[]; -ERROR: malformed array literal: "{{1,{2}},{2,3}}" -LINE 1: select '{{1,{2}},{2,3}}'::text[]; - ^ -DETAIL: Multidimensional arrays must have sub-arrays with matching dimensions. -select E'{{1,2},\\{2,3}}'::text[]; -ERROR: malformed array literal: "{{1,2},\{2,3}}" -LINE 1: select E'{{1,2},\\{2,3}}'::text[]; - ^ -DETAIL: Multidimensional arrays must have sub-arrays with matching dimensions. -select '{"a"b}'::text[]; -ERROR: malformed array literal: "{"a"b}" -LINE 1: select '{"a"b}'::text[]; - ^ -DETAIL: Incorrectly quoted array element. -select '{a"b"}'::text[]; -ERROR: malformed array literal: "{a"b"}" -LINE 1: select '{a"b"}'::text[]; - ^ -DETAIL: Incorrectly quoted array element. -select '{"a""b"}'::text[]; -ERROR: malformed array literal: "{"a""b"}" -LINE 1: select '{"a""b"}'::text[]; - ^ -DETAIL: Incorrectly quoted array element. -select '{{"1 2" x},{3}}'::text[]; -ERROR: malformed array literal: "{{"1 2" x},{3}}" -LINE 1: select '{{"1 2" x},{3}}'::text[]; - ^ -DETAIL: Incorrectly quoted array element. -select '{{"1 2"} x,{3}}'::text[]; -ERROR: malformed array literal: "{{"1 2"} x,{3}}" -LINE 1: select '{{"1 2"} x,{3}}'::text[]; - ^ -DETAIL: Unexpected array element. -select '{}}'::text[]; -ERROR: malformed array literal: "{}}" -LINE 1: select '{}}'::text[]; - ^ -DETAIL: Junk after closing right brace. -select '{ }}'::text[]; -ERROR: malformed array literal: "{ }}" -LINE 1: select '{ }}'::text[]; - ^ -DETAIL: Junk after closing right brace. -select '}{'::text[]; -ERROR: malformed array literal: "}{" -LINE 1: select '}{'::text[]; - ^ -DETAIL: Array value must start with "{" or dimension information. -select '{foo{}}'::text[]; -ERROR: malformed array literal: "{foo{}}" -LINE 1: select '{foo{}}'::text[]; - ^ -DETAIL: Unexpected "{" character. -select '{"foo"{}}'::text[]; -ERROR: malformed array literal: "{"foo"{}}" -LINE 1: select '{"foo"{}}'::text[]; - ^ -DETAIL: Unexpected "{" character. -select '{foo,,bar}'::text[]; -ERROR: malformed array literal: "{foo,,bar}" -LINE 1: select '{foo,,bar}'::text[]; - ^ -DETAIL: Unexpected "," character. -select '{{1},{{2}}}'::text[]; -ERROR: malformed array literal: "{{1},{{2}}}" -LINE 1: select '{{1},{{2}}}'::text[]; - ^ -DETAIL: Multidimensional arrays must have sub-arrays with matching dimensions. -select '{{{1}},{2}}'::text[]; -ERROR: malformed array literal: "{{{1}},{2}}" -LINE 1: select '{{{1}},{2}}'::text[]; - ^ -DETAIL: Multidimensional arrays must have sub-arrays with matching dimensions. -select '{{},{{}}}'::text[]; -ERROR: malformed array literal: "{{},{{}}}" -LINE 1: select '{{},{{}}}'::text[]; - ^ -DETAIL: Multidimensional arrays must have sub-arrays with matching dimensions. -select '{{{}},{}}'::text[]; -ERROR: malformed array literal: "{{{}},{}}" -LINE 1: select '{{{}},{}}'::text[]; - ^ -DETAIL: Multidimensional arrays must have sub-arrays with matching dimensions. -select '{{1},{}}'::text[]; -ERROR: malformed array literal: "{{1},{}}" -LINE 1: select '{{1},{}}'::text[]; - ^ -DETAIL: Multidimensional arrays must have sub-arrays with matching dimensions. -select '{{},{1}}'::text[]; -ERROR: malformed array literal: "{{},{1}}" -LINE 1: select '{{},{1}}'::text[]; - ^ -DETAIL: Multidimensional arrays must have sub-arrays with matching dimensions. -select '[1:0]={}'::int[]; -ERROR: upper bound cannot be less than lower bound -LINE 1: select '[1:0]={}'::int[]; - ^ -select '[2147483646:2147483647]={1,2}'::int[]; -ERROR: array upper bound is too large: 2147483647 -LINE 1: select '[2147483646:2147483647]={1,2}'::int[]; - ^ -select '[1:-1]={}'::int[]; -ERROR: upper bound cannot be less than lower bound -LINE 1: select '[1:-1]={}'::int[]; - ^ -select '[2]={1}'::int[]; -ERROR: malformed array literal: "[2]={1}" -LINE 1: select '[2]={1}'::int[]; - ^ -DETAIL: Specified array dimensions do not match array contents. -select '[1:]={1}'::int[]; -ERROR: malformed array literal: "[1:]={1}" -LINE 1: select '[1:]={1}'::int[]; - ^ -DETAIL: Missing array dimension value. -select '[:1]={1}'::int[]; -ERROR: malformed array literal: "[:1]={1}" -LINE 1: select '[:1]={1}'::int[]; - ^ -DETAIL: "[" must introduce explicitly-specified array dimensions. -select array[]; -ERROR: cannot determine type of empty array -LINE 1: select array[]; - ^ -HINT: Explicitly cast to the desired type, for example ARRAY[]::integer[]. -select '{{1,},{1},}'::text[]; -ERROR: malformed array literal: "{{1,},{1},}" -LINE 1: select '{{1,},{1},}'::text[]; - ^ -DETAIL: Unexpected "}" character. -select '{{1,},{1}}'::text[]; -ERROR: malformed array literal: "{{1,},{1}}" -LINE 1: select '{{1,},{1}}'::text[]; - ^ -DETAIL: Unexpected "}" character. -select '{{1,}}'::text[]; -ERROR: malformed array literal: "{{1,}}" -LINE 1: select '{{1,}}'::text[]; - ^ -DETAIL: Unexpected "}" character. -select '{1,}'::text[]; -ERROR: malformed array literal: "{1,}" -LINE 1: select '{1,}'::text[]; - ^ -DETAIL: Unexpected "}" character. -select '[21474836488:21474836489]={1,2}'::int[]; -ERROR: array bound is out of integer range -LINE 1: select '[21474836488:21474836489]={1,2}'::int[]; - ^ -select '[-2147483649:-2147483648]={1,2}'::int[]; -ERROR: array bound is out of integer range -LINE 1: select '[-2147483649:-2147483648]={1,2}'::int[]; - ^ --- none of the above should be accepted --- all of the following should be accepted -select '{}'::text[]; - text ------- - {} -(1 row) - -select '{{},{}}'::text[]; - text ------- - {} -(1 row) - -select '{{{1,2,3,4},{2,3,4,5}},{{3,4,5,6},{4,5,6,7}}}'::text[]; - text ------------------------------------------------ - {{{1,2,3,4},{2,3,4,5}},{{3,4,5,6},{4,5,6,7}}} -(1 row) - -select '{null,n\ull,"null"}'::text[]; - text ----------------------- - {NULL,"null","null"} -(1 row) - -select '{ ab\c , "ab\"c" }'::text[]; - text ---------------- - {abc,"ab\"c"} -(1 row) - -select '{0 second ,0 second}'::interval[]; - interval ---------------- - {"@ 0","@ 0"} -(1 row) - -select '{ { "," } , { 3 } }'::text[]; - text -------------- - {{","},{3}} -(1 row) - -select ' { { " 0 second " , 0 second } }'::text[]; - text -------------------------------- - {{" 0 second ","0 second"}} -(1 row) - -select '{ - 0 second, - @ 1 hour @ 42 minutes @ 20 seconds - }'::interval[]; - interval ------------------------------------- - {"@ 0","@ 1 hour 42 mins 20 secs"} -(1 row) - -select array[]::text[]; - array -------- - {} -(1 row) - -select '[2]={1,7}'::int[]; - int4 -------- - {1,7} -(1 row) - -select '[0:1]={1.1,2.2}'::float8[]; - float8 ------------------ - [0:1]={1.1,2.2} -(1 row) - -select '[2147483646:2147483646]={1}'::int[]; - int4 ------------------------------ - [2147483646:2147483646]={1} -(1 row) - -select '[-2147483648:-2147483647]={1,2}'::int[]; - int4 ---------------------------------- - [-2147483648:-2147483647]={1,2} -(1 row) - --- all of the above should be accepted --- tests for array aggregates -CREATE TEMP TABLE arraggtest ( f1 INT[], f2 TEXT[][], f3 FLOAT[]); -INSERT INTO arraggtest (f1, f2, f3) VALUES -('{1,2,3,4}','{{grey,red},{blue,blue}}','{1.6, 0.0}'); -INSERT INTO arraggtest (f1, f2, f3) VALUES -('{1,2,3}','{{grey,red},{grey,blue}}','{1.6}'); -SELECT max(f1), min(f1), max(f2), min(f2), max(f3), min(f3) FROM arraggtest; - max | min | max | min | max | min ------------+---------+--------------------------+--------------------------+---------+------- - {1,2,3,4} | {1,2,3} | {{grey,red},{grey,blue}} | {{grey,red},{blue,blue}} | {1.6,0} | {1.6} -(1 row) - -INSERT INTO arraggtest (f1, f2, f3) VALUES -('{3,3,2,4,5,6}','{{white,yellow},{pink,orange}}','{2.1,3.3,1.8,1.7,1.6}'); -SELECT max(f1), min(f1), max(f2), min(f2), max(f3), min(f3) FROM arraggtest; - max | min | max | min | max | min ----------------+---------+--------------------------------+--------------------------+-----------------------+------- - {3,3,2,4,5,6} | {1,2,3} | {{white,yellow},{pink,orange}} | {{grey,red},{blue,blue}} | {2.1,3.3,1.8,1.7,1.6} | {1.6} -(1 row) - -INSERT INTO arraggtest (f1, f2, f3) VALUES -('{2}','{{black,red},{green,orange}}','{1.6,2.2,2.6,0.4}'); -SELECT max(f1), min(f1), max(f2), min(f2), max(f3), min(f3) FROM arraggtest; - max | min | max | min | max | min ----------------+---------+--------------------------------+------------------------------+-----------------------+------- - {3,3,2,4,5,6} | {1,2,3} | {{white,yellow},{pink,orange}} | {{black,red},{green,orange}} | {2.1,3.3,1.8,1.7,1.6} | {1.6} -(1 row) - -INSERT INTO arraggtest (f1, f2, f3) VALUES -('{4,2,6,7,8,1}','{{red},{black},{purple},{blue},{blue}}',NULL); -SELECT max(f1), min(f1), max(f2), min(f2), max(f3), min(f3) FROM arraggtest; - max | min | max | min | max | min ----------------+---------+--------------------------------+------------------------------+-----------------------+------- - {4,2,6,7,8,1} | {1,2,3} | {{white,yellow},{pink,orange}} | {{black,red},{green,orange}} | {2.1,3.3,1.8,1.7,1.6} | {1.6} -(1 row) - -INSERT INTO arraggtest (f1, f2, f3) VALUES -('{}','{{pink,white,blue,red,grey,orange}}','{2.1,1.87,1.4,2.2}'); -SELECT max(f1), min(f1), max(f2), min(f2), max(f3), min(f3) FROM arraggtest; - max | min | max | min | max | min ----------------+-----+--------------------------------+------------------------------+-----------------------+------- - {4,2,6,7,8,1} | {} | {{white,yellow},{pink,orange}} | {{black,red},{green,orange}} | {2.1,3.3,1.8,1.7,1.6} | {1.6} -(1 row) - --- A few simple tests for arrays of composite types -create type comptype as (f1 int, f2 text); -create table comptable (c1 comptype, c2 comptype[]); --- XXX would like to not have to specify row() construct types here ... -insert into comptable - values (row(1,'foo'), array[row(2,'bar')::comptype, row(3,'baz')::comptype]); --- check that implicitly named array type _comptype isn't a problem -create type _comptype as enum('fooey'); -select * from comptable; - c1 | c2 ----------+----------------------- - (1,foo) | {"(2,bar)","(3,baz)"} -(1 row) - -select c2[2].f2 from comptable; - f2 ------ - baz -(1 row) - -drop type _comptype; -drop table comptable; -drop type comptype; -create or replace function unnest1(anyarray) -returns setof anyelement as $$ -select $1[s] from generate_subscripts($1,1) g(s); -$$ language sql immutable; -create or replace function unnest2(anyarray) -returns setof anyelement as $$ -select $1[s1][s2] from generate_subscripts($1,1) g1(s1), - generate_subscripts($1,2) g2(s2); -$$ language sql immutable; -select * from unnest1(array[1,2,3]); - unnest1 ---------- - 1 - 2 - 3 -(3 rows) - -select * from unnest2(array[[1,2,3],[4,5,6]]); - unnest2 ---------- - 1 - 2 - 3 - 4 - 5 - 6 -(6 rows) - -drop function unnest1(anyarray); -drop function unnest2(anyarray); -select array_fill(null::integer, array[3,3],array[2,2]); - array_fill ------------------------------------------------------------------ - [2:4][2:4]={{NULL,NULL,NULL},{NULL,NULL,NULL},{NULL,NULL,NULL}} -(1 row) - -select array_fill(null::integer, array[3,3]); - array_fill ------------------------------------------------------- - {{NULL,NULL,NULL},{NULL,NULL,NULL},{NULL,NULL,NULL}} -(1 row) - -select array_fill(null::text, array[3,3],array[2,2]); - array_fill ------------------------------------------------------------------ - [2:4][2:4]={{NULL,NULL,NULL},{NULL,NULL,NULL},{NULL,NULL,NULL}} -(1 row) - -select array_fill(null::text, array[3,3]); - array_fill ------------------------------------------------------- - {{NULL,NULL,NULL},{NULL,NULL,NULL},{NULL,NULL,NULL}} -(1 row) - -select array_fill(7, array[3,3],array[2,2]); - array_fill --------------------------------------- - [2:4][2:4]={{7,7,7},{7,7,7},{7,7,7}} -(1 row) - -select array_fill(7, array[3,3]); - array_fill ---------------------------- - {{7,7,7},{7,7,7},{7,7,7}} -(1 row) - -select array_fill('juhu'::text, array[3,3],array[2,2]); - array_fill ------------------------------------------------------------------ - [2:4][2:4]={{juhu,juhu,juhu},{juhu,juhu,juhu},{juhu,juhu,juhu}} -(1 row) - -select array_fill('juhu'::text, array[3,3]); - array_fill ------------------------------------------------------- - {{juhu,juhu,juhu},{juhu,juhu,juhu},{juhu,juhu,juhu}} -(1 row) - -select a, a = '{}' as is_eq, array_dims(a) - from (select array_fill(42, array[0]) as a) ss; - a | is_eq | array_dims -----+-------+------------ - {} | t | -(1 row) - -select a, a = '{}' as is_eq, array_dims(a) - from (select array_fill(42, '{}') as a) ss; - a | is_eq | array_dims -----+-------+------------ - {} | t | -(1 row) - -select a, a = '{}' as is_eq, array_dims(a) - from (select array_fill(42, '{}', '{}') as a) ss; - a | is_eq | array_dims -----+-------+------------ - {} | t | -(1 row) - --- raise exception -select array_fill(1, null, array[2,2]); -ERROR: dimension array or low bound array cannot be null -select array_fill(1, array[2,2], null); -ERROR: dimension array or low bound array cannot be null -select array_fill(1, array[2,2], '{}'); -ERROR: wrong number of array subscripts -DETAIL: Low bound array has different size than dimensions array. -select array_fill(1, array[3,3], array[1,1,1]); -ERROR: wrong number of array subscripts -DETAIL: Low bound array has different size than dimensions array. -select array_fill(1, array[1,2,null]); -ERROR: dimension values cannot be null -select array_fill(1, array[[1,2],[3,4]]); -ERROR: wrong number of array subscripts -DETAIL: Dimension array must be one dimensional. -select string_to_array('1|2|3', '|'); - string_to_array ------------------ - {1,2,3} -(1 row) - -select string_to_array('1|2|3|', '|'); - string_to_array ------------------ - {1,2,3,""} -(1 row) - -select string_to_array('1||2|3||', '||'); - string_to_array ------------------ - {1,2|3,""} -(1 row) - -select string_to_array('1|2|3', ''); - string_to_array ------------------ - {1|2|3} -(1 row) - -select string_to_array('', '|'); - string_to_array ------------------ - {} -(1 row) - -select string_to_array('1|2|3', NULL); - string_to_array ------------------ - {1,|,2,|,3} -(1 row) - -select string_to_array(NULL, '|') IS NULL; - ?column? ----------- - t -(1 row) - -select string_to_array('abc', ''); - string_to_array ------------------ - {abc} -(1 row) - -select string_to_array('abc', '', 'abc'); - string_to_array ------------------ - {NULL} -(1 row) - -select string_to_array('abc', ','); - string_to_array ------------------ - {abc} -(1 row) - -select string_to_array('abc', ',', 'abc'); - string_to_array ------------------ - {NULL} -(1 row) - -select string_to_array('1,2,3,4,,6', ','); - string_to_array ------------------ - {1,2,3,4,"",6} -(1 row) - -select string_to_array('1,2,3,4,,6', ',', ''); - string_to_array ------------------- - {1,2,3,4,NULL,6} -(1 row) - -select string_to_array('1,2,3,4,*,6', ',', '*'); - string_to_array ------------------- - {1,2,3,4,NULL,6} -(1 row) - -select v, v is null as "is null" from string_to_table('1|2|3', '|') g(v); - v | is null ----+--------- - 1 | f - 2 | f - 3 | f -(3 rows) - -select v, v is null as "is null" from string_to_table('1|2|3|', '|') g(v); - v | is null ----+--------- - 1 | f - 2 | f - 3 | f - | f -(4 rows) - -select v, v is null as "is null" from string_to_table('1||2|3||', '||') g(v); - v | is null ------+--------- - 1 | f - 2|3 | f - | f -(3 rows) - -select v, v is null as "is null" from string_to_table('1|2|3', '') g(v); - v | is null --------+--------- - 1|2|3 | f -(1 row) - -select v, v is null as "is null" from string_to_table('', '|') g(v); - v | is null ----+--------- -(0 rows) - -select v, v is null as "is null" from string_to_table('1|2|3', NULL) g(v); - v | is null ----+--------- - 1 | f - | | f - 2 | f - | | f - 3 | f -(5 rows) - -select v, v is null as "is null" from string_to_table(NULL, '|') g(v); - v | is null ----+--------- -(0 rows) - -select v, v is null as "is null" from string_to_table('abc', '') g(v); - v | is null ------+--------- - abc | f -(1 row) - -select v, v is null as "is null" from string_to_table('abc', '', 'abc') g(v); - v | is null ----+--------- - | t -(1 row) - -select v, v is null as "is null" from string_to_table('abc', ',') g(v); - v | is null ------+--------- - abc | f -(1 row) - -select v, v is null as "is null" from string_to_table('abc', ',', 'abc') g(v); - v | is null ----+--------- - | t -(1 row) - -select v, v is null as "is null" from string_to_table('1,2,3,4,,6', ',') g(v); - v | is null ----+--------- - 1 | f - 2 | f - 3 | f - 4 | f - | f - 6 | f -(6 rows) - -select v, v is null as "is null" from string_to_table('1,2,3,4,,6', ',', '') g(v); - v | is null ----+--------- - 1 | f - 2 | f - 3 | f - 4 | f - | t - 6 | f -(6 rows) - -select v, v is null as "is null" from string_to_table('1,2,3,4,*,6', ',', '*') g(v); - v | is null ----+--------- - 1 | f - 2 | f - 3 | f - 4 | f - | t - 6 | f -(6 rows) - -select array_to_string(NULL::int4[], ',') IS NULL; - ?column? ----------- - t -(1 row) - -select array_to_string('{}'::int4[], ','); - array_to_string ------------------ - -(1 row) - -select array_to_string(array[1,2,3,4,NULL,6], ','); - array_to_string ------------------ - 1,2,3,4,6 -(1 row) - -select array_to_string(array[1,2,3,4,NULL,6], ',', '*'); - array_to_string ------------------ - 1,2,3,4,*,6 -(1 row) - -select array_to_string(array[1,2,3,4,NULL,6], NULL); - array_to_string ------------------ - -(1 row) - -select array_to_string(array[1,2,3,4,NULL,6], ',', NULL); - array_to_string ------------------ - 1,2,3,4,6 -(1 row) - -select array_to_string(string_to_array('1|2|3', '|'), '|'); - array_to_string ------------------ - 1|2|3 -(1 row) - -select array_length(array[1,2,3], 1); - array_length --------------- - 3 -(1 row) - -select array_length(array[[1,2,3], [4,5,6]], 0); - array_length --------------- - -(1 row) - -select array_length(array[[1,2,3], [4,5,6]], 1); - array_length --------------- - 2 -(1 row) - -select array_length(array[[1,2,3], [4,5,6]], 2); - array_length --------------- - 3 -(1 row) - -select array_length(array[[1,2,3], [4,5,6]], 3); - array_length --------------- - -(1 row) - -select cardinality(NULL::int[]); - cardinality -------------- - -(1 row) - -select cardinality('{}'::int[]); - cardinality -------------- - 0 -(1 row) - -select cardinality(array[1,2,3]); - cardinality -------------- - 3 -(1 row) - -select cardinality('[2:4]={5,6,7}'::int[]); - cardinality -------------- - 3 -(1 row) - -select cardinality('{{1,2}}'::int[]); - cardinality -------------- - 2 -(1 row) - -select cardinality('{{1,2},{3,4},{5,6}}'::int[]); - cardinality -------------- - 6 -(1 row) - -select cardinality('{{{1,9},{5,6}},{{2,3},{3,4}}}'::int[]); - cardinality -------------- - 8 -(1 row) - --- array_agg(anynonarray) -select array_agg(unique1) from (select unique1 from tenk1 where unique1 < 15 order by unique1) ss; - array_agg --------------------------------------- - {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14} -(1 row) - -select array_agg(ten) from (select ten from tenk1 where unique1 < 15 order by unique1) ss; - array_agg ---------------------------------- - {0,1,2,3,4,5,6,7,8,9,0,1,2,3,4} -(1 row) - -select array_agg(nullif(ten, 4)) from (select ten from tenk1 where unique1 < 15 order by unique1) ss; - array_agg ---------------------------------------- - {0,1,2,3,NULL,5,6,7,8,9,0,1,2,3,NULL} -(1 row) - -select array_agg(unique1) from tenk1 where unique1 < -15; - array_agg ------------ - -(1 row) - --- array_agg(anyarray) -select array_agg(ar) - from (values ('{1,2}'::int[]), ('{3,4}'::int[])) v(ar); - array_agg ---------------- - {{1,2},{3,4}} -(1 row) - -select array_agg(distinct ar order by ar desc) - from (select array[i / 2] from generate_series(1,10) a(i)) b(ar); - array_agg ---------------------------- - {{5},{4},{3},{2},{1},{0}} -(1 row) - -select array_agg(ar) - from (select array_agg(array[i, i+1, i-1]) - from generate_series(1,2) a(i)) b(ar); - array_agg ---------------------- - {{{1,2,0},{2,3,1}}} -(1 row) - -select array_agg(array[i+1.2, i+1.3, i+1.4]) from generate_series(1,3) g(i); - array_agg ---------------------------------------------- - {{2.2,2.3,2.4},{3.2,3.3,3.4},{4.2,4.3,4.4}} -(1 row) - -select array_agg(array['Hello', i::text]) from generate_series(9,11) g(i); - array_agg ------------------------------------ - {{Hello,9},{Hello,10},{Hello,11}} -(1 row) - -select array_agg(array[i, nullif(i, 3), i+1]) from generate_series(1,4) g(i); - array_agg --------------------------------------- - {{1,1,2},{2,2,3},{3,NULL,4},{4,4,5}} -(1 row) - --- errors -select array_agg('{}'::int[]) from generate_series(1,2); -ERROR: cannot accumulate empty arrays -select array_agg(null::int[]) from generate_series(1,2); -ERROR: cannot accumulate null arrays -select array_agg(ar) - from (values ('{1,2}'::int[]), ('{3}'::int[])) v(ar); -ERROR: cannot accumulate arrays of different dimensionality -select unnest(array[1,2,3]); - unnest --------- - 1 - 2 - 3 -(3 rows) - -select * from unnest(array[1,2,3]); - unnest --------- - 1 - 2 - 3 -(3 rows) - -select unnest(array[1,2,3,4.5]::float8[]); - unnest --------- - 1 - 2 - 3 - 4.5 -(4 rows) - -select unnest(array[1,2,3,4.5]::numeric[]); - unnest --------- - 1 - 2 - 3 - 4.5 -(4 rows) - -select unnest(array[1,2,3,null,4,null,null,5,6]); - unnest --------- - 1 - 2 - 3 - - 4 - - - 5 - 6 -(9 rows) - -select unnest(array[1,2,3,null,4,null,null,5,6]::text[]); - unnest --------- - 1 - 2 - 3 - - 4 - - - 5 - 6 -(9 rows) - -select abs(unnest(array[1,2,null,-3])); - abs ------ - 1 - 2 - - 3 -(4 rows) - -select array_remove(array[1,2,2,3], 2); - array_remove --------------- - {1,3} -(1 row) - -select array_remove(array[1,2,2,3], 5); - array_remove --------------- - {1,2,2,3} -(1 row) - -select array_remove(array[1,NULL,NULL,3], NULL); - array_remove --------------- - {1,3} -(1 row) - -select array_remove(array['A','CC','D','C','RR'], 'RR'); - array_remove --------------- - {A,CC,D,C} -(1 row) - -select array_remove(array[1.0, 2.1, 3.3], 1); - array_remove --------------- - {2.1,3.3} -(1 row) - -select array_remove('{{1,2,2},{1,4,3}}', 2); -- not allowed -ERROR: removing elements from multidimensional arrays is not supported -select array_remove(array['X','X','X'], 'X') = '{}'; - ?column? ----------- - t -(1 row) - -select array_replace(array[1,2,5,4],5,3); - array_replace ---------------- - {1,2,3,4} -(1 row) - -select array_replace(array[1,2,5,4],5,NULL); - array_replace ---------------- - {1,2,NULL,4} -(1 row) - -select array_replace(array[1,2,NULL,4,NULL],NULL,5); - array_replace ---------------- - {1,2,5,4,5} -(1 row) - -select array_replace(array['A','B','DD','B'],'B','CC'); - array_replace ---------------- - {A,CC,DD,CC} -(1 row) - -select array_replace(array[1,NULL,3],NULL,NULL); - array_replace ---------------- - {1,NULL,3} -(1 row) - -select array_replace(array['AB',NULL,'CDE'],NULL,'12'); - array_replace ---------------- - {AB,12,CDE} -(1 row) - --- array(select array-value ...) -select array(select array[i,i/2] from generate_series(1,5) i); - array ---------------------------------- - {{1,0},{2,1},{3,1},{4,2},{5,2}} -(1 row) - -select array(select array['Hello', i::text] from generate_series(9,11) i); - array ------------------------------------ - {{Hello,9},{Hello,10},{Hello,11}} -(1 row) - --- Insert/update on a column that is array of composite -create temp table t1 (f1 int8_tbl[]); -insert into t1 (f1[5].q1) values(42); -select * from t1; - f1 ------------------ - [5:5]={"(42,)"} -(1 row) - -update t1 set f1[5].q2 = 43; -select * from t1; - f1 -------------------- - [5:5]={"(42,43)"} -(1 row) - --- Check that arrays of composites are safely detoasted when needed -create temp table src (f1 text); -insert into src - select string_agg(random()::text,'') from generate_series(1,10000); -create type textandtext as (c1 text, c2 text); -create temp table dest (f1 textandtext[]); -insert into dest select array[row(f1,f1)::textandtext] from src; -select length(fipshash((f1[1]).c2)) from dest; - length --------- - 32 -(1 row) - -delete from src; -select length(fipshash((f1[1]).c2)) from dest; - length --------- - 32 -(1 row) - -truncate table src; -drop table src; -select length(fipshash((f1[1]).c2)) from dest; - length --------- - 32 -(1 row) - -drop table dest; -drop type textandtext; --- Tests for polymorphic-array form of width_bucket() --- this exercises the varwidth and float8 code paths -SELECT - op, - width_bucket(op::numeric, ARRAY[1, 3, 5, 10.0]::numeric[]) AS wb_n1, - width_bucket(op::numeric, ARRAY[0, 5.5, 9.99]::numeric[]) AS wb_n2, - width_bucket(op::numeric, ARRAY[-6, -5, 2.0]::numeric[]) AS wb_n3, - width_bucket(op::float8, ARRAY[1, 3, 5, 10.0]::float8[]) AS wb_f1, - width_bucket(op::float8, ARRAY[0, 5.5, 9.99]::float8[]) AS wb_f2, - width_bucket(op::float8, ARRAY[-6, -5, 2.0]::float8[]) AS wb_f3 -FROM (VALUES - (-5.2), - (-0.0000000001), - (0.000000000001), - (1), - (1.99999999999999), - (2), - (2.00000000000001), - (3), - (4), - (4.5), - (5), - (5.5), - (6), - (7), - (8), - (9), - (9.99999999999999), - (10), - (10.0000000000001) -) v(op); - op | wb_n1 | wb_n2 | wb_n3 | wb_f1 | wb_f2 | wb_f3 -------------------+-------+-------+-------+-------+-------+------- - -5.2 | 0 | 0 | 1 | 0 | 0 | 1 - -0.0000000001 | 0 | 0 | 2 | 0 | 0 | 2 - 0.000000000001 | 0 | 1 | 2 | 0 | 1 | 2 - 1 | 1 | 1 | 2 | 1 | 1 | 2 - 1.99999999999999 | 1 | 1 | 2 | 1 | 1 | 2 - 2 | 1 | 1 | 3 | 1 | 1 | 3 - 2.00000000000001 | 1 | 1 | 3 | 1 | 1 | 3 - 3 | 2 | 1 | 3 | 2 | 1 | 3 - 4 | 2 | 1 | 3 | 2 | 1 | 3 - 4.5 | 2 | 1 | 3 | 2 | 1 | 3 - 5 | 3 | 1 | 3 | 3 | 1 | 3 - 5.5 | 3 | 2 | 3 | 3 | 2 | 3 - 6 | 3 | 2 | 3 | 3 | 2 | 3 - 7 | 3 | 2 | 3 | 3 | 2 | 3 - 8 | 3 | 2 | 3 | 3 | 2 | 3 - 9 | 3 | 2 | 3 | 3 | 2 | 3 - 9.99999999999999 | 3 | 3 | 3 | 3 | 3 | 3 - 10 | 4 | 3 | 3 | 4 | 3 | 3 - 10.0000000000001 | 4 | 3 | 3 | 4 | 3 | 3 -(19 rows) - --- ensure float8 path handles NaN properly -SELECT - op, - width_bucket(op, ARRAY[1, 3, 9, 'NaN', 'NaN']::float8[]) AS wb -FROM (VALUES - (-5.2::float8), - (4::float8), - (77::float8), - ('NaN'::float8) -) v(op); - op | wb -------+---- - -5.2 | 0 - 4 | 2 - 77 | 3 - NaN | 5 -(4 rows) - --- these exercise the generic fixed-width code path -SELECT - op, - width_bucket(op, ARRAY[1, 3, 5, 10]) AS wb_1 -FROM generate_series(0,11) as op; - op | wb_1 -----+------ - 0 | 0 - 1 | 1 - 2 | 1 - 3 | 2 - 4 | 2 - 5 | 3 - 6 | 3 - 7 | 3 - 8 | 3 - 9 | 3 - 10 | 4 - 11 | 4 -(12 rows) - -SELECT width_bucket(now(), - array['yesterday', 'today', 'tomorrow']::timestamptz[]); - width_bucket --------------- - 2 -(1 row) - --- corner cases -SELECT width_bucket(5, ARRAY[3]); - width_bucket --------------- - 1 -(1 row) - -SELECT width_bucket(5, '{}'); - width_bucket --------------- - 0 -(1 row) - --- error cases -SELECT width_bucket('5'::text, ARRAY[3, 4]::integer[]); -ERROR: function width_bucket(text, integer[]) does not exist -LINE 1: SELECT width_bucket('5'::text, ARRAY[3, 4]::integer[]); - ^ -HINT: No function matches the given name and argument types. You might need to add explicit type casts. -SELECT width_bucket(5, ARRAY[3, 4, NULL]); -ERROR: thresholds array must not contain NULLs -SELECT width_bucket(5, ARRAY[ARRAY[1, 2], ARRAY[3, 4]]); -ERROR: thresholds must be one-dimensional array --- trim_array -SELECT arr, trim_array(arr, 2) -FROM -(VALUES ('{1,2,3,4,5,6}'::bigint[]), - ('{1,2}'), - ('[10:16]={1,2,3,4,5,6,7}'), - ('[-15:-10]={1,2,3,4,5,6}'), - ('{{1,10},{2,20},{3,30},{4,40}}')) v(arr); - arr | trim_array --------------------------------+----------------- - {1,2,3,4,5,6} | {1,2,3,4} - {1,2} | {} - [10:16]={1,2,3,4,5,6,7} | {1,2,3,4,5} - [-15:-10]={1,2,3,4,5,6} | {1,2,3,4} - {{1,10},{2,20},{3,30},{4,40}} | {{1,10},{2,20}} -(5 rows) - -SELECT trim_array(ARRAY[1, 2, 3], -1); -- fail -ERROR: number of elements to trim must be between 0 and 3 -SELECT trim_array(ARRAY[1, 2, 3], 10); -- fail -ERROR: number of elements to trim must be between 0 and 3 -SELECT trim_array(ARRAY[]::int[], 1); -- fail -ERROR: number of elements to trim must be between 0 and 0 --- array_shuffle -SELECT array_shuffle('{1,2,3,4,5,6}'::int[]) <@ '{1,2,3,4,5,6}'; - ?column? ----------- - t -(1 row) - -SELECT array_shuffle('{1,2,3,4,5,6}'::int[]) @> '{1,2,3,4,5,6}'; - ?column? ----------- - t -(1 row) - -SELECT array_dims(array_shuffle('[-1:2][2:3]={{1,2},{3,NULL},{5,6},{7,8}}'::int[])); - array_dims -------------- - [-1:2][2:3] -(1 row) - -SELECT array_dims(array_shuffle('{{{1,2},{3,NULL}},{{5,6},{7,8}},{{9,10},{11,12}}}'::int[])); - array_dims ------------------ - [1:3][1:2][1:2] -(1 row) - --- array_sample -SELECT array_sample('{1,2,3,4,5,6}'::int[], 3) <@ '{1,2,3,4,5,6}'; - ?column? ----------- - t -(1 row) - -SELECT array_length(array_sample('{1,2,3,4,5,6}'::int[], 3), 1); - array_length --------------- - 3 -(1 row) - -SELECT array_dims(array_sample('[-1:2][2:3]={{1,2},{3,NULL},{5,6},{7,8}}'::int[], 3)); - array_dims ------------- - [1:3][2:3] -(1 row) - -SELECT array_dims(array_sample('{{{1,2},{3,NULL}},{{5,6},{7,8}},{{9,10},{11,12}}}'::int[], 2)); - array_dims ------------------ - [1:2][1:2][1:2] -(1 row) - -SELECT array_sample('{1,2,3,4,5,6}'::int[], -1); -- fail -ERROR: sample size must be between 0 and 6 -SELECT array_sample('{1,2,3,4,5,6}'::int[], 7); --fail -ERROR: sample size must be between 0 and 6 +psql: error: connection to server on socket "/tmp/pg_regress-03Az7d/.s.PGSQL.40051" failed: FATAL: the database system is not yet accepting connections +DETAIL: Consistent recovery state has not been yet reached. diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/btree_index.out /tmp/cirrus-ci-build/build/testrun/regress/regress/results/btree_index.out --- /tmp/cirrus-ci-build/src/test/regress/expected/btree_index.out 2024-09-20 21:41:45.670024520 +0000 +++ /tmp/cirrus-ci-build/build/testrun/regress/regress/results/btree_index.out 2024-09-20 21:46:05.489625091 +0000 @@ -1,492 +1,2 @@ --- --- BTREE_INDEX --- --- directory paths are passed to us in environment variables -\getenv abs_srcdir PG_ABS_SRCDIR -CREATE TABLE bt_i4_heap ( - seqno int4, - random int4 -); -CREATE TABLE bt_name_heap ( - seqno name, - random int4 -); -CREATE TABLE bt_txt_heap ( - seqno text, - random int4 -); -CREATE TABLE bt_f8_heap ( - seqno float8, - random int4 -); -\set filename :abs_srcdir '/data/desc.data' -COPY bt_i4_heap FROM :'filename'; -\set filename :abs_srcdir '/data/hash.data' -COPY bt_name_heap FROM :'filename'; -\set filename :abs_srcdir '/data/desc.data' -COPY bt_txt_heap FROM :'filename'; -\set filename :abs_srcdir '/data/hash.data' -COPY bt_f8_heap FROM :'filename'; -ANALYZE bt_i4_heap; -ANALYZE bt_name_heap; -ANALYZE bt_txt_heap; -ANALYZE bt_f8_heap; --- --- BTREE ascending/descending cases --- --- we load int4/text from pure descending data (each key is a new --- low key) and name/f8 from pure ascending data (each key is a new --- high key). we had a bug where new low keys would sometimes be --- "lost". --- -CREATE INDEX bt_i4_index ON bt_i4_heap USING btree (seqno int4_ops); -CREATE INDEX bt_name_index ON bt_name_heap USING btree (seqno name_ops); -CREATE INDEX bt_txt_index ON bt_txt_heap USING btree (seqno text_ops); -CREATE INDEX bt_f8_index ON bt_f8_heap USING btree (seqno float8_ops); --- --- test retrieval of min/max keys for each index --- -SELECT b.* - FROM bt_i4_heap b - WHERE b.seqno < 1; - seqno | random --------+------------ - 0 | 1935401906 -(1 row) - -SELECT b.* - FROM bt_i4_heap b - WHERE b.seqno >= 9999; - seqno | random --------+------------ - 9999 | 1227676208 -(1 row) - -SELECT b.* - FROM bt_i4_heap b - WHERE b.seqno = 4500; - seqno | random --------+------------ - 4500 | 2080851358 -(1 row) - -SELECT b.* - FROM bt_name_heap b - WHERE b.seqno < '1'::name; - seqno | random --------+------------ - 0 | 1935401906 -(1 row) - -SELECT b.* - FROM bt_name_heap b - WHERE b.seqno >= '9999'::name; - seqno | random --------+------------ - 9999 | 1227676208 -(1 row) - -SELECT b.* - FROM bt_name_heap b - WHERE b.seqno = '4500'::name; - seqno | random --------+------------ - 4500 | 2080851358 -(1 row) - -SELECT b.* - FROM bt_txt_heap b - WHERE b.seqno < '1'::text; - seqno | random --------+------------ - 0 | 1935401906 -(1 row) - -SELECT b.* - FROM bt_txt_heap b - WHERE b.seqno >= '9999'::text; - seqno | random --------+------------ - 9999 | 1227676208 -(1 row) - -SELECT b.* - FROM bt_txt_heap b - WHERE b.seqno = '4500'::text; - seqno | random --------+------------ - 4500 | 2080851358 -(1 row) - -SELECT b.* - FROM bt_f8_heap b - WHERE b.seqno < '1'::float8; - seqno | random --------+------------ - 0 | 1935401906 -(1 row) - -SELECT b.* - FROM bt_f8_heap b - WHERE b.seqno >= '9999'::float8; - seqno | random --------+------------ - 9999 | 1227676208 -(1 row) - -SELECT b.* - FROM bt_f8_heap b - WHERE b.seqno = '4500'::float8; - seqno | random --------+------------ - 4500 | 2080851358 -(1 row) - --- --- Add coverage for optimization of backwards scan index descents --- --- Here we expect _bt_search to descend straight to a leaf page containing a --- non-pivot tuple with the value '47', which comes last (after 11 similar --- non-pivot tuples). Query execution should only need to visit a single --- leaf page here. --- --- Test case relies on tenk1_hundred index having a leaf page whose high key --- is '(48, -inf)'. We use a low cardinality index to make our test case less --- sensitive to implementation details that may change in the future. -set enable_seqscan to false; -set enable_indexscan to true; -set enable_bitmapscan to false; -explain (costs off) -select hundred, twenty from tenk1 where hundred < 48 order by hundred desc limit 1; - QUERY PLAN --------------------------------------------------------- - Limit - -> Index Scan Backward using tenk1_hundred on tenk1 - Index Cond: (hundred < 48) -(3 rows) - -select hundred, twenty from tenk1 where hundred < 48 order by hundred desc limit 1; - hundred | twenty ----------+-------- - 47 | 7 -(1 row) - --- This variant of the query need only return a single tuple located to the immediate --- right of the '(48, -inf)' high key. It also only needs to scan one single --- leaf page (the right sibling of the page scanned by the last test case): -explain (costs off) -select hundred, twenty from tenk1 where hundred <= 48 order by hundred desc limit 1; - QUERY PLAN --------------------------------------------------------- - Limit - -> Index Scan Backward using tenk1_hundred on tenk1 - Index Cond: (hundred <= 48) -(3 rows) - -select hundred, twenty from tenk1 where hundred <= 48 order by hundred desc limit 1; - hundred | twenty ----------+-------- - 48 | 8 -(1 row) - --- --- Add coverage for ScalarArrayOp btree quals with pivot tuple constants --- -explain (costs off) -select distinct hundred from tenk1 where hundred in (47, 48, 72, 82); - QUERY PLAN ------------------------------------------------------------------- - Unique - -> Index Only Scan using tenk1_hundred on tenk1 - Index Cond: (hundred = ANY ('{47,48,72,82}'::integer[])) -(3 rows) - -select distinct hundred from tenk1 where hundred in (47, 48, 72, 82); - hundred ---------- - 47 - 48 - 72 - 82 -(4 rows) - -explain (costs off) -select distinct hundred from tenk1 where hundred in (47, 48, 72, 82) order by hundred desc; - QUERY PLAN ------------------------------------------------------------------- - Unique - -> Index Only Scan Backward using tenk1_hundred on tenk1 - Index Cond: (hundred = ANY ('{47,48,72,82}'::integer[])) -(3 rows) - -select distinct hundred from tenk1 where hundred in (47, 48, 72, 82) order by hundred desc; - hundred ---------- - 82 - 72 - 48 - 47 -(4 rows) - -explain (costs off) -select thousand from tenk1 where thousand in (364, 366,380) and tenthous = 200000; - QUERY PLAN ---------------------------------------------------------------------------------------- - Index Only Scan using tenk1_thous_tenthous on tenk1 - Index Cond: ((thousand = ANY ('{364,366,380}'::integer[])) AND (tenthous = 200000)) -(2 rows) - -select thousand from tenk1 where thousand in (364, 366,380) and tenthous = 200000; - thousand ----------- -(0 rows) - --- --- Check correct optimization of LIKE (special index operator support) --- for both indexscan and bitmapscan cases --- -set enable_seqscan to false; -set enable_indexscan to true; -set enable_bitmapscan to false; -explain (costs off) -select proname from pg_proc where proname like E'RI\\_FKey%del' order by 1; - QUERY PLAN ------------------------------------------------------------------------------- - Index Only Scan using pg_proc_proname_args_nsp_index on pg_proc - Index Cond: ((proname >= 'RI_FKey'::text) AND (proname < 'RI_FKez'::text)) - Filter: (proname ~~ 'RI\_FKey%del'::text) -(3 rows) - -select proname from pg_proc where proname like E'RI\\_FKey%del' order by 1; - proname ------------------------- - RI_FKey_cascade_del - RI_FKey_noaction_del - RI_FKey_restrict_del - RI_FKey_setdefault_del - RI_FKey_setnull_del -(5 rows) - -explain (costs off) -select proname from pg_proc where proname ilike '00%foo' order by 1; - QUERY PLAN --------------------------------------------------------------------- - Index Only Scan using pg_proc_proname_args_nsp_index on pg_proc - Index Cond: ((proname >= '00'::text) AND (proname < '01'::text)) - Filter: (proname ~~* '00%foo'::text) -(3 rows) - -select proname from pg_proc where proname ilike '00%foo' order by 1; - proname ---------- -(0 rows) - -explain (costs off) -select proname from pg_proc where proname ilike 'ri%foo' order by 1; - QUERY PLAN ------------------------------------------------------------------ - Index Only Scan using pg_proc_proname_args_nsp_index on pg_proc - Filter: (proname ~~* 'ri%foo'::text) -(2 rows) - -set enable_indexscan to false; -set enable_bitmapscan to true; -explain (costs off) -select proname from pg_proc where proname like E'RI\\_FKey%del' order by 1; - QUERY PLAN ------------------------------------------------------------------------------------------- - Sort - Sort Key: proname - -> Bitmap Heap Scan on pg_proc - Filter: (proname ~~ 'RI\_FKey%del'::text) - -> Bitmap Index Scan on pg_proc_proname_args_nsp_index - Index Cond: ((proname >= 'RI_FKey'::text) AND (proname < 'RI_FKez'::text)) -(6 rows) - -select proname from pg_proc where proname like E'RI\\_FKey%del' order by 1; - proname ------------------------- - RI_FKey_cascade_del - RI_FKey_noaction_del - RI_FKey_restrict_del - RI_FKey_setdefault_del - RI_FKey_setnull_del -(5 rows) - -explain (costs off) -select proname from pg_proc where proname ilike '00%foo' order by 1; - QUERY PLAN --------------------------------------------------------------------------------- - Sort - Sort Key: proname - -> Bitmap Heap Scan on pg_proc - Filter: (proname ~~* '00%foo'::text) - -> Bitmap Index Scan on pg_proc_proname_args_nsp_index - Index Cond: ((proname >= '00'::text) AND (proname < '01'::text)) -(6 rows) - -select proname from pg_proc where proname ilike '00%foo' order by 1; - proname ---------- -(0 rows) - -explain (costs off) -select proname from pg_proc where proname ilike 'ri%foo' order by 1; - QUERY PLAN ----------------------------------------------- - Sort - Disabled Nodes: 1 - Sort Key: proname - -> Seq Scan on pg_proc - Disabled Nodes: 1 - Filter: (proname ~~* 'ri%foo'::text) -(6 rows) - -reset enable_seqscan; -reset enable_indexscan; -reset enable_bitmapscan; --- Also check LIKE optimization with binary-compatible cases -create temp table btree_bpchar (f1 text collate "C"); -create index on btree_bpchar(f1 bpchar_ops) WITH (deduplicate_items=on); -insert into btree_bpchar values ('foo'), ('fool'), ('bar'), ('quux'); --- doesn't match index: -explain (costs off) -select * from btree_bpchar where f1 like 'foo'; - QUERY PLAN -------------------------------- - Seq Scan on btree_bpchar - Filter: (f1 ~~ 'foo'::text) -(2 rows) - -select * from btree_bpchar where f1 like 'foo'; - f1 ------ - foo -(1 row) - -explain (costs off) -select * from btree_bpchar where f1 like 'foo%'; - QUERY PLAN --------------------------------- - Seq Scan on btree_bpchar - Filter: (f1 ~~ 'foo%'::text) -(2 rows) - -select * from btree_bpchar where f1 like 'foo%'; - f1 ------- - foo - fool -(2 rows) - --- these do match the index: -explain (costs off) -select * from btree_bpchar where f1::bpchar like 'foo'; - QUERY PLAN ----------------------------------------------------- - Bitmap Heap Scan on btree_bpchar - Filter: ((f1)::bpchar ~~ 'foo'::text) - -> Bitmap Index Scan on btree_bpchar_f1_idx - Index Cond: ((f1)::bpchar = 'foo'::bpchar) -(4 rows) - -select * from btree_bpchar where f1::bpchar like 'foo'; - f1 ------ - foo -(1 row) - -explain (costs off) -select * from btree_bpchar where f1::bpchar like 'foo%'; - QUERY PLAN ------------------------------------------------------------------------------------------- - Bitmap Heap Scan on btree_bpchar - Filter: ((f1)::bpchar ~~ 'foo%'::text) - -> Bitmap Index Scan on btree_bpchar_f1_idx - Index Cond: (((f1)::bpchar >= 'foo'::bpchar) AND ((f1)::bpchar < 'fop'::bpchar)) -(4 rows) - -select * from btree_bpchar where f1::bpchar like 'foo%'; - f1 ------- - foo - fool -(2 rows) - --- get test coverage for "single value" deduplication strategy: -insert into btree_bpchar select 'foo' from generate_series(1,1500); --- --- Perform unique checking, with and without the use of deduplication --- -CREATE TABLE dedup_unique_test_table (a int) WITH (autovacuum_enabled=false); -CREATE UNIQUE INDEX dedup_unique ON dedup_unique_test_table (a) WITH (deduplicate_items=on); -CREATE UNIQUE INDEX plain_unique ON dedup_unique_test_table (a) WITH (deduplicate_items=off); --- Generate enough garbage tuples in index to ensure that even the unique index --- with deduplication enabled has to check multiple leaf pages during unique --- checking (at least with a BLCKSZ of 8192 or less) -DO $$ -BEGIN - FOR r IN 1..1350 LOOP - DELETE FROM dedup_unique_test_table; - INSERT INTO dedup_unique_test_table SELECT 1; - END LOOP; -END$$; --- Exercise the LP_DEAD-bit-set tuple deletion code with a posting list tuple. --- The implementation prefers deleting existing items to merging any duplicate --- tuples into a posting list, so we need an explicit test to make sure we get --- coverage (note that this test also assumes BLCKSZ is 8192 or less): -DROP INDEX plain_unique; -DELETE FROM dedup_unique_test_table WHERE a = 1; -INSERT INTO dedup_unique_test_table SELECT i FROM generate_series(0,450) i; --- --- Test B-tree fast path (cache rightmost leaf page) optimization. --- --- First create a tree that's at least three levels deep (i.e. has one level --- between the root and leaf levels). The text inserted is long. It won't be --- TOAST compressed because we use plain storage in the table. Only a few --- index tuples fit on each internal page, allowing us to get a tall tree with --- few pages. (A tall tree is required to trigger caching.) --- --- The text column must be the leading column in the index, since suffix --- truncation would otherwise truncate tuples on internal pages, leaving us --- with a short tree. -create table btree_tall_tbl(id int4, t text); -alter table btree_tall_tbl alter COLUMN t set storage plain; -create index btree_tall_idx on btree_tall_tbl (t, id) with (fillfactor = 10); -insert into btree_tall_tbl select g, repeat('x', 250) -from generate_series(1, 130) g; --- --- Test for multilevel page deletion --- -CREATE TABLE delete_test_table (a bigint, b bigint, c bigint, d bigint); -INSERT INTO delete_test_table SELECT i, 1, 2, 3 FROM generate_series(1,80000) i; -ALTER TABLE delete_test_table ADD PRIMARY KEY (a,b,c,d); --- Delete most entries, and vacuum, deleting internal pages and creating "fast --- root" -DELETE FROM delete_test_table WHERE a < 79990; -VACUUM delete_test_table; --- --- Test B-tree insertion with a metapage update (XLOG_BTREE_INSERT_META --- WAL record type). This happens when a "fast root" page is split. This --- also creates coverage for nbtree FSM page recycling. --- --- The vacuum above should've turned the leaf page into a fast root. We just --- need to insert some rows to cause the fast root page to split. -INSERT INTO delete_test_table SELECT i, 1, 2, 3 FROM generate_series(1,1000) i; --- Test unsupported btree opclass parameters -create index on btree_tall_tbl (id int4_ops(foo=1)); -ERROR: operator class int4_ops has no options --- Test case of ALTER INDEX with abuse of column names for indexes. --- This grammar is not officially supported, but the parser allows it. -CREATE INDEX btree_tall_idx2 ON btree_tall_tbl (id); -ALTER INDEX btree_tall_idx2 ALTER COLUMN id SET (n_distinct=100); -ERROR: ALTER action ALTER COLUMN ... SET cannot be performed on relation "btree_tall_idx2" -DETAIL: This operation is not supported for indexes. -DROP INDEX btree_tall_idx2; --- Partitioned index -CREATE TABLE btree_part (id int4) PARTITION BY RANGE (id); -CREATE INDEX btree_part_idx ON btree_part(id); -ALTER INDEX btree_part_idx ALTER COLUMN id SET (n_distinct=100); -ERROR: ALTER action ALTER COLUMN ... SET cannot be performed on relation "btree_part_idx" -DETAIL: This operation is not supported for partitioned indexes. -DROP TABLE btree_part; +psql: error: connection to server on socket "/tmp/pg_regress-03Az7d/.s.PGSQL.40051" failed: FATAL: the database system is not yet accepting connections +DETAIL: Consistent recovery state has not been yet reached. diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/hash_index.out /tmp/cirrus-ci-build/build/testrun/regress/regress/results/hash_index.out --- /tmp/cirrus-ci-build/src/test/regress/expected/hash_index.out 2024-09-20 21:41:45.690024521 +0000 +++ /tmp/cirrus-ci-build/build/testrun/regress/regress/results/hash_index.out 2024-09-20 21:46:05.509625064 +0000 @@ -1,335 +1,2 @@ --- --- HASH_INDEX --- --- directory paths are passed to us in environment variables -\getenv abs_srcdir PG_ABS_SRCDIR -CREATE TABLE hash_i4_heap ( - seqno int4, - random int4 -); -CREATE TABLE hash_name_heap ( - seqno int4, - random name -); -CREATE TABLE hash_txt_heap ( - seqno int4, - random text -); -CREATE TABLE hash_f8_heap ( - seqno int4, - random float8 -); -\set filename :abs_srcdir '/data/hash.data' -COPY hash_i4_heap FROM :'filename'; -COPY hash_name_heap FROM :'filename'; -COPY hash_txt_heap FROM :'filename'; -COPY hash_f8_heap FROM :'filename'; --- the data in this file has a lot of duplicates in the index key --- fields, leading to long bucket chains and lots of table expansion. --- this is therefore a stress test of the bucket overflow code (unlike --- the data in hash.data, which has unique index keys). --- --- \set filename :abs_srcdir '/data/hashovfl.data' --- COPY hash_ovfl_heap FROM :'filename'; -ANALYZE hash_i4_heap; -ANALYZE hash_name_heap; -ANALYZE hash_txt_heap; -ANALYZE hash_f8_heap; -CREATE INDEX hash_i4_index ON hash_i4_heap USING hash (random int4_ops); -CREATE INDEX hash_name_index ON hash_name_heap USING hash (random name_ops); -CREATE INDEX hash_txt_index ON hash_txt_heap USING hash (random text_ops); -CREATE INDEX hash_f8_index ON hash_f8_heap USING hash (random float8_ops) - WITH (fillfactor=60); --- --- Also try building functional, expressional, and partial indexes on --- tables that already contain data. --- -create unique index hash_f8_index_1 on hash_f8_heap(abs(random)); -create unique index hash_f8_index_2 on hash_f8_heap((seqno + 1), random); -create unique index hash_f8_index_3 on hash_f8_heap(random) where seqno > 1000; --- --- hash index --- grep 843938989 hash.data --- -SELECT * FROM hash_i4_heap - WHERE hash_i4_heap.random = 843938989; - seqno | random --------+----------- - 15 | 843938989 -(1 row) - --- --- hash index --- grep 66766766 hash.data --- -SELECT * FROM hash_i4_heap - WHERE hash_i4_heap.random = 66766766; - seqno | random --------+-------- -(0 rows) - --- --- hash index --- grep 1505703298 hash.data --- -SELECT * FROM hash_name_heap - WHERE hash_name_heap.random = '1505703298'::name; - seqno | random --------+------------ - 9838 | 1505703298 -(1 row) - --- --- hash index --- grep 7777777 hash.data --- -SELECT * FROM hash_name_heap - WHERE hash_name_heap.random = '7777777'::name; - seqno | random --------+-------- -(0 rows) - --- --- hash index --- grep 1351610853 hash.data --- -SELECT * FROM hash_txt_heap - WHERE hash_txt_heap.random = '1351610853'::text; - seqno | random --------+------------ - 5677 | 1351610853 -(1 row) - --- --- hash index --- grep 111111112222222233333333 hash.data --- -SELECT * FROM hash_txt_heap - WHERE hash_txt_heap.random = '111111112222222233333333'::text; - seqno | random --------+-------- -(0 rows) - --- --- hash index --- grep 444705537 hash.data --- -SELECT * FROM hash_f8_heap - WHERE hash_f8_heap.random = '444705537'::float8; - seqno | random --------+----------- - 7853 | 444705537 -(1 row) - --- --- hash index --- grep 88888888 hash.data --- -SELECT * FROM hash_f8_heap - WHERE hash_f8_heap.random = '88888888'::float8; - seqno | random --------+-------- -(0 rows) - --- --- hash index --- grep '^90[^0-9]' hashovfl.data --- --- SELECT count(*) AS i988 FROM hash_ovfl_heap --- WHERE x = 90; --- --- hash index --- grep '^1000[^0-9]' hashovfl.data --- --- SELECT count(*) AS i0 FROM hash_ovfl_heap --- WHERE x = 1000; --- --- HASH --- -UPDATE hash_i4_heap - SET random = 1 - WHERE hash_i4_heap.seqno = 1492; -SELECT h.seqno AS i1492, h.random AS i1 - FROM hash_i4_heap h - WHERE h.random = 1; - i1492 | i1 --------+---- - 1492 | 1 -(1 row) - -UPDATE hash_i4_heap - SET seqno = 20000 - WHERE hash_i4_heap.random = 1492795354; -SELECT h.seqno AS i20000 - FROM hash_i4_heap h - WHERE h.random = 1492795354; - i20000 --------- - 20000 -(1 row) - -UPDATE hash_name_heap - SET random = '0123456789abcdef'::name - WHERE hash_name_heap.seqno = 6543; -SELECT h.seqno AS i6543, h.random AS c0_to_f - FROM hash_name_heap h - WHERE h.random = '0123456789abcdef'::name; - i6543 | c0_to_f --------+------------------ - 6543 | 0123456789abcdef -(1 row) - -UPDATE hash_name_heap - SET seqno = 20000 - WHERE hash_name_heap.random = '76652222'::name; --- --- this is the row we just replaced; index scan should return zero rows --- -SELECT h.seqno AS emptyset - FROM hash_name_heap h - WHERE h.random = '76652222'::name; - emptyset ----------- -(0 rows) - -UPDATE hash_txt_heap - SET random = '0123456789abcdefghijklmnop'::text - WHERE hash_txt_heap.seqno = 4002; -SELECT h.seqno AS i4002, h.random AS c0_to_p - FROM hash_txt_heap h - WHERE h.random = '0123456789abcdefghijklmnop'::text; - i4002 | c0_to_p --------+---------------------------- - 4002 | 0123456789abcdefghijklmnop -(1 row) - -UPDATE hash_txt_heap - SET seqno = 20000 - WHERE hash_txt_heap.random = '959363399'::text; -SELECT h.seqno AS t20000 - FROM hash_txt_heap h - WHERE h.random = '959363399'::text; - t20000 --------- - 20000 -(1 row) - -UPDATE hash_f8_heap - SET random = '-1234.1234'::float8 - WHERE hash_f8_heap.seqno = 8906; -SELECT h.seqno AS i8096, h.random AS f1234_1234 - FROM hash_f8_heap h - WHERE h.random = '-1234.1234'::float8; - i8096 | f1234_1234 --------+------------ - 8906 | -1234.1234 -(1 row) - -UPDATE hash_f8_heap - SET seqno = 20000 - WHERE hash_f8_heap.random = '488912369'::float8; -SELECT h.seqno AS f20000 - FROM hash_f8_heap h - WHERE h.random = '488912369'::float8; - f20000 --------- - 20000 -(1 row) - --- UPDATE hash_ovfl_heap --- SET x = 1000 --- WHERE x = 90; --- this vacuums the index as well --- VACUUM hash_ovfl_heap; --- SELECT count(*) AS i0 FROM hash_ovfl_heap --- WHERE x = 90; --- SELECT count(*) AS i988 FROM hash_ovfl_heap --- WHERE x = 1000; --- --- Cause some overflow insert and splits. --- -CREATE TABLE hash_split_heap (keycol INT); -INSERT INTO hash_split_heap SELECT 1 FROM generate_series(1, 500) a; -CREATE INDEX hash_split_index on hash_split_heap USING HASH (keycol); -INSERT INTO hash_split_heap SELECT 1 FROM generate_series(1, 5000) a; --- Let's do a backward scan. -BEGIN; -SET enable_seqscan = OFF; -SET enable_bitmapscan = OFF; -DECLARE c CURSOR FOR SELECT * from hash_split_heap WHERE keycol = 1; -MOVE FORWARD ALL FROM c; -MOVE BACKWARD 10000 FROM c; -MOVE BACKWARD ALL FROM c; -CLOSE c; -END; --- DELETE, INSERT, VACUUM. -DELETE FROM hash_split_heap WHERE keycol = 1; -INSERT INTO hash_split_heap SELECT a/2 FROM generate_series(1, 25000) a; -VACUUM hash_split_heap; --- Rebuild the index using a different fillfactor -ALTER INDEX hash_split_index SET (fillfactor = 10); -REINDEX INDEX hash_split_index; --- Clean up. -DROP TABLE hash_split_heap; --- Testcases for removing overflow pages. -CREATE TABLE hash_cleanup_heap(keycol INT); -CREATE INDEX hash_cleanup_index on hash_cleanup_heap USING HASH (keycol); --- Insert tuples to both the primary bucket page and overflow pages. -INSERT INTO hash_cleanup_heap SELECT 1 FROM generate_series(1, 500) as i; --- Fill overflow pages by "dead" tuples. -BEGIN; -INSERT INTO hash_cleanup_heap SELECT 1 FROM generate_series(1, 1000) as i; -ROLLBACK; --- Checkpoint will ensure that all hash buffers are cleaned before we try --- to remove overflow pages. -CHECKPOINT; --- This will squeeze the bucket and remove overflow pages. -VACUUM hash_cleanup_heap; -TRUNCATE hash_cleanup_heap; --- Insert a few tuples so that the primary bucket page doesn't get full and --- tuples can be moved to it. -INSERT INTO hash_cleanup_heap SELECT 1 FROM generate_series(1, 50) as i; --- Fill overflow pages by "dead" tuples. -BEGIN; -INSERT INTO hash_cleanup_heap SELECT 1 FROM generate_series(1, 1500) as i; -ROLLBACK; --- And insert some tuples again. During squeeze operation, these will be moved --- to the primary bucket allowing to test freeing intermediate overflow pages. -INSERT INTO hash_cleanup_heap SELECT 1 FROM generate_series(1, 500) as i; -CHECKPOINT; -VACUUM hash_cleanup_heap; -TRUNCATE hash_cleanup_heap; --- Insert tuples to both the primary bucket page and overflow pages. -INSERT INTO hash_cleanup_heap SELECT 1 FROM generate_series(1, 500) as i; --- Fill overflow pages by "dead" tuples. -BEGIN; -INSERT INTO hash_cleanup_heap SELECT 1 FROM generate_series(1, 1500) as i; -ROLLBACK; --- And insert some tuples again. During squeeze operation, these will be moved --- to other overflow pages and also allow overflow pages filled by dead tuples --- to be freed. Note the main purpose of this test is to test the case where --- we don't need to move any tuple from the overflow page being freed. -INSERT INTO hash_cleanup_heap SELECT 1 FROM generate_series(1, 50) as i; -CHECKPOINT; -VACUUM hash_cleanup_heap; --- Clean up. -DROP TABLE hash_cleanup_heap; --- Index on temp table. -CREATE TEMP TABLE hash_temp_heap (x int, y int); -INSERT INTO hash_temp_heap VALUES (1,1); -CREATE INDEX hash_idx ON hash_temp_heap USING hash (x); -DROP TABLE hash_temp_heap CASCADE; --- Float4 type. -CREATE TABLE hash_heap_float4 (x float4, y int); -INSERT INTO hash_heap_float4 VALUES (1.1,1); -CREATE INDEX hash_idx ON hash_heap_float4 USING hash (x); -DROP TABLE hash_heap_float4 CASCADE; --- Test out-of-range fillfactor values -CREATE INDEX hash_f8_index2 ON hash_f8_heap USING hash (random float8_ops) - WITH (fillfactor=9); -ERROR: value 9 out of bounds for option "fillfactor" -DETAIL: Valid values are between "10" and "100". -CREATE INDEX hash_f8_index2 ON hash_f8_heap USING hash (random float8_ops) - WITH (fillfactor=101); -ERROR: value 101 out of bounds for option "fillfactor" -DETAIL: Valid values are between "10" and "100". +psql: error: connection to server on socket "/tmp/pg_regress-03Az7d/.s.PGSQL.40051" failed: FATAL: the database system is not yet accepting connections +DETAIL: Consistent recovery state has not been yet reached. diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/update.out /tmp/cirrus-ci-build/build/testrun/regress/regress/results/update.out --- /tmp/cirrus-ci-build/src/test/regress/expected/update.out 2024-09-20 21:41:45.766024524 +0000 +++ /tmp/cirrus-ci-build/build/testrun/regress/regress/results/update.out 2024-09-20 21:46:05.557624997 +0000 @@ -1,1034 +1,2 @@ --- --- UPDATE syntax tests --- -CREATE TABLE update_test ( - a INT DEFAULT 10, - b INT, - c TEXT -); -CREATE TABLE upsert_test ( - a INT PRIMARY KEY, - b TEXT -); -INSERT INTO update_test VALUES (5, 10, 'foo'); -INSERT INTO update_test(b, a) VALUES (15, 10); -SELECT * FROM update_test; - a | b | c -----+----+----- - 5 | 10 | foo - 10 | 15 | -(2 rows) - -UPDATE update_test SET a = DEFAULT, b = DEFAULT; -SELECT * FROM update_test; - a | b | c -----+---+----- - 10 | | foo - 10 | | -(2 rows) - --- aliases for the UPDATE target table -UPDATE update_test AS t SET b = 10 WHERE t.a = 10; -SELECT * FROM update_test; - a | b | c -----+----+----- - 10 | 10 | foo - 10 | 10 | -(2 rows) - -UPDATE update_test t SET b = t.b + 10 WHERE t.a = 10; -SELECT * FROM update_test; - a | b | c -----+----+----- - 10 | 20 | foo - 10 | 20 | -(2 rows) - --- error, you're not supposed to qualify the target column -UPDATE update_test t SET t.b = t.b + 10 WHERE t.a = 10; -ERROR: column "t" of relation "update_test" does not exist -LINE 1: UPDATE update_test t SET t.b = t.b + 10 WHERE t.a = 10; - ^ -HINT: SET target columns cannot be qualified with the relation name. --- --- Test VALUES in FROM --- -UPDATE update_test SET a=v.i FROM (VALUES(100, 20)) AS v(i, j) - WHERE update_test.b = v.j; -SELECT * FROM update_test; - a | b | c ------+----+----- - 100 | 20 | foo - 100 | 20 | -(2 rows) - --- fail, wrong data type: -UPDATE update_test SET a = v.* FROM (VALUES(100, 20)) AS v(i, j) - WHERE update_test.b = v.j; -ERROR: column "a" is of type integer but expression is of type record -LINE 1: UPDATE update_test SET a = v.* FROM (VALUES(100, 20)) AS v(i... - ^ -HINT: You will need to rewrite or cast the expression. --- --- Test multiple-set-clause syntax --- -INSERT INTO update_test SELECT a,b+1,c FROM update_test; -SELECT * FROM update_test; - a | b | c ------+----+----- - 100 | 20 | foo - 100 | 20 | - 100 | 21 | foo - 100 | 21 | -(4 rows) - -UPDATE update_test SET (c,b,a) = ('bugle', b+11, DEFAULT) WHERE c = 'foo'; -SELECT * FROM update_test; - a | b | c ------+----+------- - 100 | 20 | - 100 | 21 | - 10 | 31 | bugle - 10 | 32 | bugle -(4 rows) - -UPDATE update_test SET (c,b) = ('car', a+b), a = a + 1 WHERE a = 10; -SELECT * FROM update_test; - a | b | c ------+----+----- - 100 | 20 | - 100 | 21 | - 11 | 41 | car - 11 | 42 | car -(4 rows) - --- fail, multi assignment to same column: -UPDATE update_test SET (c,b) = ('car', a+b), b = a + 1 WHERE a = 10; -ERROR: multiple assignments to same column "b" --- uncorrelated sub-select: -UPDATE update_test - SET (b,a) = (select a,b from update_test where b = 41 and c = 'car') - WHERE a = 100 AND b = 20; -SELECT * FROM update_test; - a | b | c ------+----+----- - 100 | 21 | - 11 | 41 | car - 11 | 42 | car - 41 | 11 | -(4 rows) - --- correlated sub-select: -UPDATE update_test o - SET (b,a) = (select a+1,b from update_test i - where i.a=o.a and i.b=o.b and i.c is not distinct from o.c); -SELECT * FROM update_test; - a | b | c -----+-----+----- - 21 | 101 | - 41 | 12 | car - 42 | 12 | car - 11 | 42 | -(4 rows) - --- fail, multiple rows supplied: -UPDATE update_test SET (b,a) = (select a+1,b from update_test); -ERROR: more than one row returned by a subquery used as an expression --- set to null if no rows supplied: -UPDATE update_test SET (b,a) = (select a+1,b from update_test where a = 1000) - WHERE a = 11; -SELECT * FROM update_test; - a | b | c -----+-----+----- - 21 | 101 | - 41 | 12 | car - 42 | 12 | car - | | -(4 rows) - --- *-expansion should work in this context: -UPDATE update_test SET (a,b) = ROW(v.*) FROM (VALUES(21, 100)) AS v(i, j) - WHERE update_test.a = v.i; --- you might expect this to work, but syntactically it's not a RowExpr: -UPDATE update_test SET (a,b) = (v.*) FROM (VALUES(21, 101)) AS v(i, j) - WHERE update_test.a = v.i; -ERROR: source for a multiple-column UPDATE item must be a sub-SELECT or ROW() expression -LINE 1: UPDATE update_test SET (a,b) = (v.*) FROM (VALUES(21, 101)) ... - ^ --- if an alias for the target table is specified, don't allow references --- to the original table name -UPDATE update_test AS t SET b = update_test.b + 10 WHERE t.a = 10; -ERROR: invalid reference to FROM-clause entry for table "update_test" -LINE 1: UPDATE update_test AS t SET b = update_test.b + 10 WHERE t.a... - ^ -HINT: Perhaps you meant to reference the table alias "t". --- Make sure that we can update to a TOASTed value. -UPDATE update_test SET c = repeat('x', 10000) WHERE c = 'car'; -SELECT a, b, char_length(c) FROM update_test; - a | b | char_length -----+-----+------------- - | | - 21 | 100 | - 41 | 12 | 10000 - 42 | 12 | 10000 -(4 rows) - --- Check multi-assignment with a Result node to handle a one-time filter. -EXPLAIN (VERBOSE, COSTS OFF) -UPDATE update_test t - SET (a, b) = (SELECT b, a FROM update_test s WHERE s.a = t.a) - WHERE CURRENT_USER = SESSION_USER; - QUERY PLAN --------------------------------------------------------------------------------- - Update on public.update_test t - -> Result - Output: (SubPlan 1).col1, (SubPlan 1).col2, (rescan SubPlan 1), t.ctid - One-Time Filter: (CURRENT_USER = SESSION_USER) - -> Seq Scan on public.update_test t - Output: t.a, t.ctid - SubPlan 1 - -> Seq Scan on public.update_test s - Output: s.b, s.a - Filter: (s.a = t.a) -(10 rows) - -UPDATE update_test t - SET (a, b) = (SELECT b, a FROM update_test s WHERE s.a = t.a) - WHERE CURRENT_USER = SESSION_USER; -SELECT a, b, char_length(c) FROM update_test; - a | b | char_length ------+----+------------- - | | - 100 | 21 | - 12 | 41 | 10000 - 12 | 42 | 10000 -(4 rows) - --- Test ON CONFLICT DO UPDATE -INSERT INTO upsert_test VALUES(1, 'Boo'), (3, 'Zoo'); --- uncorrelated sub-select: -WITH aaa AS (SELECT 1 AS a, 'Foo' AS b) INSERT INTO upsert_test - VALUES (1, 'Bar') ON CONFLICT(a) - DO UPDATE SET (b, a) = (SELECT b, a FROM aaa) RETURNING *; - a | b ----+----- - 1 | Foo -(1 row) - --- correlated sub-select: -INSERT INTO upsert_test VALUES (1, 'Baz'), (3, 'Zaz') ON CONFLICT(a) - DO UPDATE SET (b, a) = (SELECT b || ', Correlated', a from upsert_test i WHERE i.a = upsert_test.a) - RETURNING *; - a | b ----+----------------- - 1 | Foo, Correlated - 3 | Zoo, Correlated -(2 rows) - --- correlated sub-select (EXCLUDED.* alias): -INSERT INTO upsert_test VALUES (1, 'Bat'), (3, 'Zot') ON CONFLICT(a) - DO UPDATE SET (b, a) = (SELECT b || ', Excluded', a from upsert_test i WHERE i.a = excluded.a) - RETURNING *; - a | b ----+--------------------------- - 1 | Foo, Correlated, Excluded - 3 | Zoo, Correlated, Excluded -(2 rows) - --- ON CONFLICT using system attributes in RETURNING, testing both the --- inserting and updating paths. See bug report at: --- https://www.postgresql.org/message-id/73436355-6432-49B1-92ED-1FE4F7E7E100%40finefun.com.au -INSERT INTO upsert_test VALUES (2, 'Beeble') ON CONFLICT(a) - DO UPDATE SET (b, a) = (SELECT b || ', Excluded', a from upsert_test i WHERE i.a = excluded.a) - RETURNING tableoid::regclass, xmin = pg_current_xact_id()::xid AS xmin_correct, xmax = 0 AS xmax_correct; - tableoid | xmin_correct | xmax_correct --------------+--------------+-------------- - upsert_test | t | t -(1 row) - --- currently xmax is set after a conflict - that's probably not good, --- but it seems worthwhile to have to be explicit if that changes. -INSERT INTO upsert_test VALUES (2, 'Brox') ON CONFLICT(a) - DO UPDATE SET (b, a) = (SELECT b || ', Excluded', a from upsert_test i WHERE i.a = excluded.a) - RETURNING tableoid::regclass, xmin = pg_current_xact_id()::xid AS xmin_correct, xmax = pg_current_xact_id()::xid AS xmax_correct; - tableoid | xmin_correct | xmax_correct --------------+--------------+-------------- - upsert_test | t | t -(1 row) - -DROP TABLE update_test; -DROP TABLE upsert_test; --- Test ON CONFLICT DO UPDATE with partitioned table and non-identical children -CREATE TABLE upsert_test ( - a INT PRIMARY KEY, - b TEXT -) PARTITION BY LIST (a); -CREATE TABLE upsert_test_1 PARTITION OF upsert_test FOR VALUES IN (1); -CREATE TABLE upsert_test_2 (b TEXT, a INT PRIMARY KEY); -ALTER TABLE upsert_test ATTACH PARTITION upsert_test_2 FOR VALUES IN (2); -INSERT INTO upsert_test VALUES(1, 'Boo'), (2, 'Zoo'); --- uncorrelated sub-select: -WITH aaa AS (SELECT 1 AS a, 'Foo' AS b) INSERT INTO upsert_test - VALUES (1, 'Bar') ON CONFLICT(a) - DO UPDATE SET (b, a) = (SELECT b, a FROM aaa) RETURNING *; - a | b ----+----- - 1 | Foo -(1 row) - --- correlated sub-select: -WITH aaa AS (SELECT 1 AS ctea, ' Foo' AS cteb) INSERT INTO upsert_test - VALUES (1, 'Bar'), (2, 'Baz') ON CONFLICT(a) - DO UPDATE SET (b, a) = (SELECT upsert_test.b||cteb, upsert_test.a FROM aaa) RETURNING *; - a | b ----+--------- - 1 | Foo Foo - 2 | Zoo Foo -(2 rows) - -DROP TABLE upsert_test; ---------------------------- --- UPDATE with row movement ---------------------------- --- When a partitioned table receives an UPDATE to the partitioned key and the --- new values no longer meet the partition's bound, the row must be moved to --- the correct partition for the new partition key (if one exists). We must --- also ensure that updatable views on partitioned tables properly enforce any --- WITH CHECK OPTION that is defined. The situation with triggers in this case --- also requires thorough testing as partition key updates causing row --- movement convert UPDATEs into DELETE+INSERT. -CREATE TABLE range_parted ( - a text, - b bigint, - c numeric, - d int, - e varchar -) PARTITION BY RANGE (a, b); --- Create partitions intentionally in descending bound order, so as to test --- that update-row-movement works with the leaf partitions not in bound order. -CREATE TABLE part_b_20_b_30 (e varchar, c numeric, a text, b bigint, d int); -ALTER TABLE range_parted ATTACH PARTITION part_b_20_b_30 FOR VALUES FROM ('b', 20) TO ('b', 30); -CREATE TABLE part_b_10_b_20 (e varchar, c numeric, a text, b bigint, d int) PARTITION BY RANGE (c); -CREATE TABLE part_b_1_b_10 PARTITION OF range_parted FOR VALUES FROM ('b', 1) TO ('b', 10); -ALTER TABLE range_parted ATTACH PARTITION part_b_10_b_20 FOR VALUES FROM ('b', 10) TO ('b', 20); -CREATE TABLE part_a_10_a_20 PARTITION OF range_parted FOR VALUES FROM ('a', 10) TO ('a', 20); -CREATE TABLE part_a_1_a_10 PARTITION OF range_parted FOR VALUES FROM ('a', 1) TO ('a', 10); --- Check that partition-key UPDATE works sanely on a partitioned table that --- does not have any child partitions. -UPDATE part_b_10_b_20 set b = b - 6; --- Create some more partitions following the above pattern of descending bound --- order, but let's make the situation a bit more complex by having the --- attribute numbers of the columns vary from their parent partition. -CREATE TABLE part_c_100_200 (e varchar, c numeric, a text, b bigint, d int) PARTITION BY range (abs(d)); -ALTER TABLE part_c_100_200 DROP COLUMN e, DROP COLUMN c, DROP COLUMN a; -ALTER TABLE part_c_100_200 ADD COLUMN c numeric, ADD COLUMN e varchar, ADD COLUMN a text; -ALTER TABLE part_c_100_200 DROP COLUMN b; -ALTER TABLE part_c_100_200 ADD COLUMN b bigint; -CREATE TABLE part_d_1_15 PARTITION OF part_c_100_200 FOR VALUES FROM (1) TO (15); -CREATE TABLE part_d_15_20 PARTITION OF part_c_100_200 FOR VALUES FROM (15) TO (20); -ALTER TABLE part_b_10_b_20 ATTACH PARTITION part_c_100_200 FOR VALUES FROM (100) TO (200); -CREATE TABLE part_c_1_100 (e varchar, d int, c numeric, b bigint, a text); -ALTER TABLE part_b_10_b_20 ATTACH PARTITION part_c_1_100 FOR VALUES FROM (1) TO (100); -\set init_range_parted 'truncate range_parted; insert into range_parted VALUES (''a'', 1, 1, 1), (''a'', 10, 200, 1), (''b'', 12, 96, 1), (''b'', 13, 97, 2), (''b'', 15, 105, 16), (''b'', 17, 105, 19)' -\set show_data 'select tableoid::regclass::text COLLATE "C" partname, * from range_parted ORDER BY 1, 2, 3, 4, 5, 6' -:init_range_parted; -:show_data; - partname | a | b | c | d | e -----------------+---+----+-----+----+--- - part_a_10_a_20 | a | 10 | 200 | 1 | - part_a_1_a_10 | a | 1 | 1 | 1 | - part_c_1_100 | b | 12 | 96 | 1 | - part_c_1_100 | b | 13 | 97 | 2 | - part_d_15_20 | b | 15 | 105 | 16 | - part_d_15_20 | b | 17 | 105 | 19 | -(6 rows) - --- The order of subplans should be in bound order -EXPLAIN (costs off) UPDATE range_parted set c = c - 50 WHERE c > 97; - QUERY PLAN -------------------------------------------------------- - Update on range_parted - Update on part_a_1_a_10 range_parted_1 - Update on part_a_10_a_20 range_parted_2 - Update on part_b_1_b_10 range_parted_3 - Update on part_c_1_100 range_parted_4 - Update on part_d_1_15 range_parted_5 - Update on part_d_15_20 range_parted_6 - Update on part_b_20_b_30 range_parted_7 - -> Append - -> Seq Scan on part_a_1_a_10 range_parted_1 - Filter: (c > '97'::numeric) - -> Seq Scan on part_a_10_a_20 range_parted_2 - Filter: (c > '97'::numeric) - -> Seq Scan on part_b_1_b_10 range_parted_3 - Filter: (c > '97'::numeric) - -> Seq Scan on part_c_1_100 range_parted_4 - Filter: (c > '97'::numeric) - -> Seq Scan on part_d_1_15 range_parted_5 - Filter: (c > '97'::numeric) - -> Seq Scan on part_d_15_20 range_parted_6 - Filter: (c > '97'::numeric) - -> Seq Scan on part_b_20_b_30 range_parted_7 - Filter: (c > '97'::numeric) -(23 rows) - --- fail, row movement happens only within the partition subtree. -UPDATE part_c_100_200 set c = c - 20, d = c WHERE c = 105; -ERROR: new row for relation "part_c_100_200" violates partition constraint -DETAIL: Failing row contains (105, 85, null, b, 15). --- fail, no partition key update, so no attempt to move tuple, --- but "a = 'a'" violates partition constraint enforced by root partition) -UPDATE part_b_10_b_20 set a = 'a'; -ERROR: new row for relation "part_b_10_b_20" violates partition constraint -DETAIL: Failing row contains (null, 96, a, 12, 1). --- ok, partition key update, no constraint violation -UPDATE range_parted set d = d - 10 WHERE d > 10; --- ok, no partition key update, no constraint violation -UPDATE range_parted set e = d; --- No row found -UPDATE part_c_1_100 set c = c + 20 WHERE c = 98; --- ok, row movement -UPDATE part_b_10_b_20 set c = c + 20 returning c, b, a; - c | b | a ------+----+--- - 116 | 12 | b - 117 | 13 | b - 125 | 15 | b - 125 | 17 | b -(4 rows) - -:show_data; - partname | a | b | c | d | e -----------------+---+----+-----+---+--- - part_a_10_a_20 | a | 10 | 200 | 1 | 1 - part_a_1_a_10 | a | 1 | 1 | 1 | 1 - part_d_1_15 | b | 12 | 116 | 1 | 1 - part_d_1_15 | b | 13 | 117 | 2 | 2 - part_d_1_15 | b | 15 | 125 | 6 | 6 - part_d_1_15 | b | 17 | 125 | 9 | 9 -(6 rows) - --- fail, row movement happens only within the partition subtree. -UPDATE part_b_10_b_20 set b = b - 6 WHERE c > 116 returning *; -ERROR: new row for relation "part_b_10_b_20" violates partition constraint -DETAIL: Failing row contains (2, 117, b, 7, 2). --- ok, row movement, with subset of rows moved into different partition. -UPDATE range_parted set b = b - 6 WHERE c > 116 returning a, b + c; - a | ?column? ----+---------- - a | 204 - b | 124 - b | 134 - b | 136 -(4 rows) - -:show_data; - partname | a | b | c | d | e ----------------+---+----+-----+---+--- - part_a_1_a_10 | a | 1 | 1 | 1 | 1 - part_a_1_a_10 | a | 4 | 200 | 1 | 1 - part_b_1_b_10 | b | 7 | 117 | 2 | 2 - part_b_1_b_10 | b | 9 | 125 | 6 | 6 - part_d_1_15 | b | 11 | 125 | 9 | 9 - part_d_1_15 | b | 12 | 116 | 1 | 1 -(6 rows) - --- Common table needed for multiple test scenarios. -CREATE TABLE mintab(c1 int); -INSERT into mintab VALUES (120); --- update partition key using updatable view. -CREATE VIEW upview AS SELECT * FROM range_parted WHERE (select c > c1 FROM mintab) WITH CHECK OPTION; --- ok -UPDATE upview set c = 199 WHERE b = 4; --- fail, check option violation -UPDATE upview set c = 120 WHERE b = 4; -ERROR: new row violates check option for view "upview" -DETAIL: Failing row contains (a, 4, 120, 1, 1). --- fail, row movement with check option violation -UPDATE upview set a = 'b', b = 15, c = 120 WHERE b = 4; -ERROR: new row violates check option for view "upview" -DETAIL: Failing row contains (b, 15, 120, 1, 1). --- ok, row movement, check option passes -UPDATE upview set a = 'b', b = 15 WHERE b = 4; -:show_data; - partname | a | b | c | d | e ----------------+---+----+-----+---+--- - part_a_1_a_10 | a | 1 | 1 | 1 | 1 - part_b_1_b_10 | b | 7 | 117 | 2 | 2 - part_b_1_b_10 | b | 9 | 125 | 6 | 6 - part_d_1_15 | b | 11 | 125 | 9 | 9 - part_d_1_15 | b | 12 | 116 | 1 | 1 - part_d_1_15 | b | 15 | 199 | 1 | 1 -(6 rows) - --- cleanup -DROP VIEW upview; --- RETURNING having whole-row vars. -:init_range_parted; -UPDATE range_parted set c = 95 WHERE a = 'b' and b > 10 and c > 100 returning (range_parted), *; - range_parted | a | b | c | d | e ----------------+---+----+----+----+--- - (b,15,95,16,) | b | 15 | 95 | 16 | - (b,17,95,19,) | b | 17 | 95 | 19 | -(2 rows) - -:show_data; - partname | a | b | c | d | e -----------------+---+----+-----+----+--- - part_a_10_a_20 | a | 10 | 200 | 1 | - part_a_1_a_10 | a | 1 | 1 | 1 | - part_c_1_100 | b | 12 | 96 | 1 | - part_c_1_100 | b | 13 | 97 | 2 | - part_c_1_100 | b | 15 | 95 | 16 | - part_c_1_100 | b | 17 | 95 | 19 | -(6 rows) - --- Transition tables with update row movement -:init_range_parted; -CREATE FUNCTION trans_updatetrigfunc() RETURNS trigger LANGUAGE plpgsql AS -$$ - begin - raise notice 'trigger = %, old table = %, new table = %', - TG_NAME, - (select string_agg(old_table::text, ', ' ORDER BY a) FROM old_table), - (select string_agg(new_table::text, ', ' ORDER BY a) FROM new_table); - return null; - end; -$$; -CREATE TRIGGER trans_updatetrig - AFTER UPDATE ON range_parted REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table - FOR EACH STATEMENT EXECUTE PROCEDURE trans_updatetrigfunc(); -UPDATE range_parted set c = (case when c = 96 then 110 else c + 1 end ) WHERE a = 'b' and b > 10 and c >= 96; -NOTICE: trigger = trans_updatetrig, old table = (b,12,96,1,), (b,13,97,2,), (b,15,105,16,), (b,17,105,19,), new table = (b,12,110,1,), (b,13,98,2,), (b,15,106,16,), (b,17,106,19,) -:show_data; - partname | a | b | c | d | e -----------------+---+----+-----+----+--- - part_a_10_a_20 | a | 10 | 200 | 1 | - part_a_1_a_10 | a | 1 | 1 | 1 | - part_c_1_100 | b | 13 | 98 | 2 | - part_d_15_20 | b | 15 | 106 | 16 | - part_d_15_20 | b | 17 | 106 | 19 | - part_d_1_15 | b | 12 | 110 | 1 | -(6 rows) - -:init_range_parted; --- Enabling OLD TABLE capture for both DELETE as well as UPDATE stmt triggers --- should not cause DELETEd rows to be captured twice. Similar thing for --- INSERT triggers and inserted rows. -CREATE TRIGGER trans_deletetrig - AFTER DELETE ON range_parted REFERENCING OLD TABLE AS old_table - FOR EACH STATEMENT EXECUTE PROCEDURE trans_updatetrigfunc(); -CREATE TRIGGER trans_inserttrig - AFTER INSERT ON range_parted REFERENCING NEW TABLE AS new_table - FOR EACH STATEMENT EXECUTE PROCEDURE trans_updatetrigfunc(); -UPDATE range_parted set c = c + 50 WHERE a = 'b' and b > 10 and c >= 96; -NOTICE: trigger = trans_updatetrig, old table = (b,12,96,1,), (b,13,97,2,), (b,15,105,16,), (b,17,105,19,), new table = (b,12,146,1,), (b,13,147,2,), (b,15,155,16,), (b,17,155,19,) -:show_data; - partname | a | b | c | d | e -----------------+---+----+-----+----+--- - part_a_10_a_20 | a | 10 | 200 | 1 | - part_a_1_a_10 | a | 1 | 1 | 1 | - part_d_15_20 | b | 15 | 155 | 16 | - part_d_15_20 | b | 17 | 155 | 19 | - part_d_1_15 | b | 12 | 146 | 1 | - part_d_1_15 | b | 13 | 147 | 2 | -(6 rows) - -DROP TRIGGER trans_deletetrig ON range_parted; -DROP TRIGGER trans_inserttrig ON range_parted; --- Don't drop trans_updatetrig yet. It is required below. --- Test with transition tuple conversion happening for rows moved into the --- new partition. This requires a trigger that references transition table --- (we already have trans_updatetrig). For inserted rows, the conversion --- is not usually needed, because the original tuple is already compatible with --- the desired transition tuple format. But conversion happens when there is a --- BR trigger because the trigger can change the inserted row. So install a --- BR triggers on those child partitions where the rows will be moved. -CREATE FUNCTION func_parted_mod_b() RETURNS trigger AS $$ -BEGIN - NEW.b = NEW.b + 1; - return NEW; -END $$ language plpgsql; -CREATE TRIGGER trig_c1_100 BEFORE UPDATE OR INSERT ON part_c_1_100 - FOR EACH ROW EXECUTE PROCEDURE func_parted_mod_b(); -CREATE TRIGGER trig_d1_15 BEFORE UPDATE OR INSERT ON part_d_1_15 - FOR EACH ROW EXECUTE PROCEDURE func_parted_mod_b(); -CREATE TRIGGER trig_d15_20 BEFORE UPDATE OR INSERT ON part_d_15_20 - FOR EACH ROW EXECUTE PROCEDURE func_parted_mod_b(); -:init_range_parted; -UPDATE range_parted set c = (case when c = 96 then 110 else c + 1 end) WHERE a = 'b' and b > 10 and c >= 96; -NOTICE: trigger = trans_updatetrig, old table = (b,13,96,1,), (b,14,97,2,), (b,16,105,16,), (b,18,105,19,), new table = (b,15,110,1,), (b,15,98,2,), (b,17,106,16,), (b,19,106,19,) -:show_data; - partname | a | b | c | d | e -----------------+---+----+-----+----+--- - part_a_10_a_20 | a | 10 | 200 | 1 | - part_a_1_a_10 | a | 1 | 1 | 1 | - part_c_1_100 | b | 15 | 98 | 2 | - part_d_15_20 | b | 17 | 106 | 16 | - part_d_15_20 | b | 19 | 106 | 19 | - part_d_1_15 | b | 15 | 110 | 1 | -(6 rows) - -:init_range_parted; -UPDATE range_parted set c = c + 50 WHERE a = 'b' and b > 10 and c >= 96; -NOTICE: trigger = trans_updatetrig, old table = (b,13,96,1,), (b,14,97,2,), (b,16,105,16,), (b,18,105,19,), new table = (b,15,146,1,), (b,16,147,2,), (b,17,155,16,), (b,19,155,19,) -:show_data; - partname | a | b | c | d | e -----------------+---+----+-----+----+--- - part_a_10_a_20 | a | 10 | 200 | 1 | - part_a_1_a_10 | a | 1 | 1 | 1 | - part_d_15_20 | b | 17 | 155 | 16 | - part_d_15_20 | b | 19 | 155 | 19 | - part_d_1_15 | b | 15 | 146 | 1 | - part_d_1_15 | b | 16 | 147 | 2 | -(6 rows) - --- Case where per-partition tuple conversion map array is allocated, but the --- map is not required for the particular tuple that is routed, thanks to --- matching table attributes of the partition and the target table. -:init_range_parted; -UPDATE range_parted set b = 15 WHERE b = 1; -NOTICE: trigger = trans_updatetrig, old table = (a,1,1,1,), new table = (a,15,1,1,) -:show_data; - partname | a | b | c | d | e -----------------+---+----+-----+----+--- - part_a_10_a_20 | a | 10 | 200 | 1 | - part_a_10_a_20 | a | 15 | 1 | 1 | - part_c_1_100 | b | 13 | 96 | 1 | - part_c_1_100 | b | 14 | 97 | 2 | - part_d_15_20 | b | 16 | 105 | 16 | - part_d_15_20 | b | 18 | 105 | 19 | -(6 rows) - -DROP TRIGGER trans_updatetrig ON range_parted; -DROP TRIGGER trig_c1_100 ON part_c_1_100; -DROP TRIGGER trig_d1_15 ON part_d_1_15; -DROP TRIGGER trig_d15_20 ON part_d_15_20; -DROP FUNCTION func_parted_mod_b(); --- RLS policies with update-row-movement ------------------------------------------ -ALTER TABLE range_parted ENABLE ROW LEVEL SECURITY; -CREATE USER regress_range_parted_user; -GRANT ALL ON range_parted, mintab TO regress_range_parted_user; -CREATE POLICY seeall ON range_parted AS PERMISSIVE FOR SELECT USING (true); -CREATE POLICY policy_range_parted ON range_parted for UPDATE USING (true) WITH CHECK (c % 2 = 0); -:init_range_parted; -SET SESSION AUTHORIZATION regress_range_parted_user; --- This should fail with RLS violation error while moving row from --- part_a_10_a_20 to part_d_1_15, because we are setting 'c' to an odd number. -UPDATE range_parted set a = 'b', c = 151 WHERE a = 'a' and c = 200; -ERROR: new row violates row-level security policy for table "range_parted" -RESET SESSION AUTHORIZATION; --- Create a trigger on part_d_1_15 -CREATE FUNCTION func_d_1_15() RETURNS trigger AS $$ -BEGIN - NEW.c = NEW.c + 1; -- Make even numbers odd, or vice versa - return NEW; -END $$ LANGUAGE plpgsql; -CREATE TRIGGER trig_d_1_15 BEFORE INSERT ON part_d_1_15 - FOR EACH ROW EXECUTE PROCEDURE func_d_1_15(); -:init_range_parted; -SET SESSION AUTHORIZATION regress_range_parted_user; --- Here, RLS checks should succeed while moving row from part_a_10_a_20 to --- part_d_1_15. Even though the UPDATE is setting 'c' to an odd number, the --- trigger at the destination partition again makes it an even number. -UPDATE range_parted set a = 'b', c = 151 WHERE a = 'a' and c = 200; -RESET SESSION AUTHORIZATION; -:init_range_parted; -SET SESSION AUTHORIZATION regress_range_parted_user; --- This should fail with RLS violation error. Even though the UPDATE is setting --- 'c' to an even number, the trigger at the destination partition again makes --- it an odd number. -UPDATE range_parted set a = 'b', c = 150 WHERE a = 'a' and c = 200; -ERROR: new row violates row-level security policy for table "range_parted" --- Cleanup -RESET SESSION AUTHORIZATION; -DROP TRIGGER trig_d_1_15 ON part_d_1_15; -DROP FUNCTION func_d_1_15(); --- Policy expression contains SubPlan -RESET SESSION AUTHORIZATION; -:init_range_parted; -CREATE POLICY policy_range_parted_subplan on range_parted - AS RESTRICTIVE for UPDATE USING (true) - WITH CHECK ((SELECT range_parted.c <= c1 FROM mintab)); -SET SESSION AUTHORIZATION regress_range_parted_user; --- fail, mintab has row with c1 = 120 -UPDATE range_parted set a = 'b', c = 122 WHERE a = 'a' and c = 200; -ERROR: new row violates row-level security policy "policy_range_parted_subplan" for table "range_parted" --- ok -UPDATE range_parted set a = 'b', c = 120 WHERE a = 'a' and c = 200; --- RLS policy expression contains whole row. -RESET SESSION AUTHORIZATION; -:init_range_parted; -CREATE POLICY policy_range_parted_wholerow on range_parted AS RESTRICTIVE for UPDATE USING (true) - WITH CHECK (range_parted = row('b', 10, 112, 1, NULL)::range_parted); -SET SESSION AUTHORIZATION regress_range_parted_user; --- ok, should pass the RLS check -UPDATE range_parted set a = 'b', c = 112 WHERE a = 'a' and c = 200; -RESET SESSION AUTHORIZATION; -:init_range_parted; -SET SESSION AUTHORIZATION regress_range_parted_user; --- fail, the whole row RLS check should fail -UPDATE range_parted set a = 'b', c = 116 WHERE a = 'a' and c = 200; -ERROR: new row violates row-level security policy "policy_range_parted_wholerow" for table "range_parted" --- Cleanup -RESET SESSION AUTHORIZATION; -DROP POLICY policy_range_parted ON range_parted; -DROP POLICY policy_range_parted_subplan ON range_parted; -DROP POLICY policy_range_parted_wholerow ON range_parted; -REVOKE ALL ON range_parted, mintab FROM regress_range_parted_user; -DROP USER regress_range_parted_user; -DROP TABLE mintab; --- statement triggers with update row movement ---------------------------------------------------- -:init_range_parted; -CREATE FUNCTION trigfunc() returns trigger language plpgsql as -$$ - begin - raise notice 'trigger = % fired on table % during %', - TG_NAME, TG_TABLE_NAME, TG_OP; - return null; - end; -$$; --- Triggers on root partition -CREATE TRIGGER parent_delete_trig - AFTER DELETE ON range_parted for each statement execute procedure trigfunc(); -CREATE TRIGGER parent_update_trig - AFTER UPDATE ON range_parted for each statement execute procedure trigfunc(); -CREATE TRIGGER parent_insert_trig - AFTER INSERT ON range_parted for each statement execute procedure trigfunc(); --- Triggers on leaf partition part_c_1_100 -CREATE TRIGGER c1_delete_trig - AFTER DELETE ON part_c_1_100 for each statement execute procedure trigfunc(); -CREATE TRIGGER c1_update_trig - AFTER UPDATE ON part_c_1_100 for each statement execute procedure trigfunc(); -CREATE TRIGGER c1_insert_trig - AFTER INSERT ON part_c_1_100 for each statement execute procedure trigfunc(); --- Triggers on leaf partition part_d_1_15 -CREATE TRIGGER d1_delete_trig - AFTER DELETE ON part_d_1_15 for each statement execute procedure trigfunc(); -CREATE TRIGGER d1_update_trig - AFTER UPDATE ON part_d_1_15 for each statement execute procedure trigfunc(); -CREATE TRIGGER d1_insert_trig - AFTER INSERT ON part_d_1_15 for each statement execute procedure trigfunc(); --- Triggers on leaf partition part_d_15_20 -CREATE TRIGGER d15_delete_trig - AFTER DELETE ON part_d_15_20 for each statement execute procedure trigfunc(); -CREATE TRIGGER d15_update_trig - AFTER UPDATE ON part_d_15_20 for each statement execute procedure trigfunc(); -CREATE TRIGGER d15_insert_trig - AFTER INSERT ON part_d_15_20 for each statement execute procedure trigfunc(); --- Move all rows from part_c_100_200 to part_c_1_100. None of the delete or --- insert statement triggers should be fired. -UPDATE range_parted set c = c - 50 WHERE c > 97; -NOTICE: trigger = parent_update_trig fired on table range_parted during UPDATE -:show_data; - partname | a | b | c | d | e -----------------+---+----+-----+----+--- - part_a_10_a_20 | a | 10 | 150 | 1 | - part_a_1_a_10 | a | 1 | 1 | 1 | - part_c_1_100 | b | 12 | 96 | 1 | - part_c_1_100 | b | 13 | 97 | 2 | - part_c_1_100 | b | 15 | 55 | 16 | - part_c_1_100 | b | 17 | 55 | 19 | -(6 rows) - -DROP TRIGGER parent_delete_trig ON range_parted; -DROP TRIGGER parent_update_trig ON range_parted; -DROP TRIGGER parent_insert_trig ON range_parted; -DROP TRIGGER c1_delete_trig ON part_c_1_100; -DROP TRIGGER c1_update_trig ON part_c_1_100; -DROP TRIGGER c1_insert_trig ON part_c_1_100; -DROP TRIGGER d1_delete_trig ON part_d_1_15; -DROP TRIGGER d1_update_trig ON part_d_1_15; -DROP TRIGGER d1_insert_trig ON part_d_1_15; -DROP TRIGGER d15_delete_trig ON part_d_15_20; -DROP TRIGGER d15_update_trig ON part_d_15_20; -DROP TRIGGER d15_insert_trig ON part_d_15_20; --- Creating default partition for range -:init_range_parted; -create table part_def partition of range_parted default; -\d+ part_def - Table "public.part_def" - Column | Type | Collation | Nullable | Default | Storage | Stats target | Description ---------+-------------------+-----------+----------+---------+----------+--------------+------------- - a | text | | | | extended | | - b | bigint | | | | plain | | - c | numeric | | | | main | | - d | integer | | | | plain | | - e | character varying | | | | extended | | -Partition of: range_parted DEFAULT -Partition constraint: (NOT ((a IS NOT NULL) AND (b IS NOT NULL) AND (((a = 'a'::text) AND (b >= '1'::bigint) AND (b < '10'::bigint)) OR ((a = 'a'::text) AND (b >= '10'::bigint) AND (b < '20'::bigint)) OR ((a = 'b'::text) AND (b >= '1'::bigint) AND (b < '10'::bigint)) OR ((a = 'b'::text) AND (b >= '10'::bigint) AND (b < '20'::bigint)) OR ((a = 'b'::text) AND (b >= '20'::bigint) AND (b < '30'::bigint))))) - -insert into range_parted values ('c', 9); --- ok -update part_def set a = 'd' where a = 'c'; --- fail -update part_def set a = 'a' where a = 'd'; -ERROR: new row for relation "part_def" violates partition constraint -DETAIL: Failing row contains (a, 9, null, null, null). -:show_data; - partname | a | b | c | d | e -----------------+---+----+-----+----+--- - part_a_10_a_20 | a | 10 | 200 | 1 | - part_a_1_a_10 | a | 1 | 1 | 1 | - part_c_1_100 | b | 12 | 96 | 1 | - part_c_1_100 | b | 13 | 97 | 2 | - part_d_15_20 | b | 15 | 105 | 16 | - part_d_15_20 | b | 17 | 105 | 19 | - part_def | d | 9 | | | -(7 rows) - --- Update row movement from non-default to default partition. --- fail, default partition is not under part_a_10_a_20; -UPDATE part_a_10_a_20 set a = 'ad' WHERE a = 'a'; -ERROR: new row for relation "part_a_10_a_20" violates partition constraint -DETAIL: Failing row contains (ad, 10, 200, 1, null). --- ok -UPDATE range_parted set a = 'ad' WHERE a = 'a'; -UPDATE range_parted set a = 'bd' WHERE a = 'b'; -:show_data; - partname | a | b | c | d | e -----------+----+----+-----+----+--- - part_def | ad | 1 | 1 | 1 | - part_def | ad | 10 | 200 | 1 | - part_def | bd | 12 | 96 | 1 | - part_def | bd | 13 | 97 | 2 | - part_def | bd | 15 | 105 | 16 | - part_def | bd | 17 | 105 | 19 | - part_def | d | 9 | | | -(7 rows) - --- Update row movement from default to non-default partitions. --- ok -UPDATE range_parted set a = 'a' WHERE a = 'ad'; -UPDATE range_parted set a = 'b' WHERE a = 'bd'; -:show_data; - partname | a | b | c | d | e -----------------+---+----+-----+----+--- - part_a_10_a_20 | a | 10 | 200 | 1 | - part_a_1_a_10 | a | 1 | 1 | 1 | - part_c_1_100 | b | 12 | 96 | 1 | - part_c_1_100 | b | 13 | 97 | 2 | - part_d_15_20 | b | 15 | 105 | 16 | - part_d_15_20 | b | 17 | 105 | 19 | - part_def | d | 9 | | | -(7 rows) - --- Cleanup: range_parted no longer needed. -DROP TABLE range_parted; -CREATE TABLE list_parted ( - a text, - b int -) PARTITION BY list (a); -CREATE TABLE list_part1 PARTITION OF list_parted for VALUES in ('a', 'b'); -CREATE TABLE list_default PARTITION OF list_parted default; -INSERT into list_part1 VALUES ('a', 1); -INSERT into list_default VALUES ('d', 10); --- fail -UPDATE list_default set a = 'a' WHERE a = 'd'; -ERROR: new row for relation "list_default" violates partition constraint -DETAIL: Failing row contains (a, 10). --- ok -UPDATE list_default set a = 'x' WHERE a = 'd'; -DROP TABLE list_parted; --- Test retrieval of system columns with non-consistent partition row types. --- This is only partially supported, as seen in the results. -create table utrtest (a int, b text) partition by list (a); -create table utr1 (a int check (a in (1)), q text, b text); -create table utr2 (a int check (a in (2)), b text); -alter table utr1 drop column q; -alter table utrtest attach partition utr1 for values in (1); -alter table utrtest attach partition utr2 for values in (2); -insert into utrtest values (1, 'foo') - returning *, tableoid::regclass, xmin = pg_current_xact_id()::xid as xmin_ok; - a | b | tableoid | xmin_ok ----+-----+----------+--------- - 1 | foo | utr1 | t -(1 row) - -insert into utrtest values (2, 'bar') - returning *, tableoid::regclass, xmin = pg_current_xact_id()::xid as xmin_ok; -- fails -ERROR: cannot retrieve a system column in this context -insert into utrtest values (2, 'bar') - returning *, tableoid::regclass; - a | b | tableoid ----+-----+---------- - 2 | bar | utr2 -(1 row) - -update utrtest set b = b || b from (values (1), (2)) s(x) where a = s.x - returning *, tableoid::regclass, xmin = pg_current_xact_id()::xid as xmin_ok; - a | b | x | tableoid | xmin_ok ----+--------+---+----------+--------- - 1 | foofoo | 1 | utr1 | t - 2 | barbar | 2 | utr2 | t -(2 rows) - -update utrtest set a = 3 - a from (values (1), (2)) s(x) where a = s.x - returning *, tableoid::regclass, xmin = pg_current_xact_id()::xid as xmin_ok; -- fails -ERROR: cannot retrieve a system column in this context -update utrtest set a = 3 - a from (values (1), (2)) s(x) where a = s.x - returning *, tableoid::regclass; - a | b | x | tableoid ----+--------+---+---------- - 2 | foofoo | 1 | utr2 - 1 | barbar | 2 | utr1 -(2 rows) - -delete from utrtest - returning *, tableoid::regclass, xmax = pg_current_xact_id()::xid as xmax_ok; - a | b | tableoid | xmax_ok ----+--------+----------+--------- - 1 | barbar | utr1 | t - 2 | foofoo | utr2 | t -(2 rows) - -drop table utrtest; --------------- --- Some more update-partition-key test scenarios below. This time use list --- partitions. --------------- --- Setup for list partitions -CREATE TABLE list_parted (a numeric, b int, c int8) PARTITION BY list (a); -CREATE TABLE sub_parted PARTITION OF list_parted for VALUES in (1) PARTITION BY list (b); -CREATE TABLE sub_part1(b int, c int8, a numeric); -ALTER TABLE sub_parted ATTACH PARTITION sub_part1 for VALUES in (1); -CREATE TABLE sub_part2(b int, c int8, a numeric); -ALTER TABLE sub_parted ATTACH PARTITION sub_part2 for VALUES in (2); -CREATE TABLE list_part1(a numeric, b int, c int8); -ALTER TABLE list_parted ATTACH PARTITION list_part1 for VALUES in (2,3); -INSERT into list_parted VALUES (2,5,50); -INSERT into list_parted VALUES (3,6,60); -INSERT into sub_parted VALUES (1,1,60); -INSERT into sub_parted VALUES (1,2,10); --- Test partition constraint violation when intermediate ancestor is used and --- constraint is inherited from upper root. -UPDATE sub_parted set a = 2 WHERE c = 10; -ERROR: new row for relation "sub_parted" violates partition constraint -DETAIL: Failing row contains (2, 2, 10). --- Test update-partition-key, where the unpruned partitions do not have their --- partition keys updated. -SELECT tableoid::regclass::text, * FROM list_parted WHERE a = 2 ORDER BY 1; - tableoid | a | b | c -------------+---+---+---- - list_part1 | 2 | 5 | 50 -(1 row) - -UPDATE list_parted set b = c + a WHERE a = 2; -SELECT tableoid::regclass::text, * FROM list_parted WHERE a = 2 ORDER BY 1; - tableoid | a | b | c -------------+---+----+---- - list_part1 | 2 | 52 | 50 -(1 row) - --- Test the case where BR UPDATE triggers change the partition key. -CREATE FUNCTION func_parted_mod_b() returns trigger as $$ -BEGIN - NEW.b = 2; -- This is changing partition key column. - return NEW; -END $$ LANGUAGE plpgsql; -CREATE TRIGGER parted_mod_b before update on sub_part1 - for each row execute procedure func_parted_mod_b(); -SELECT tableoid::regclass::text, * FROM list_parted ORDER BY 1, 2, 3, 4; - tableoid | a | b | c -------------+---+----+---- - list_part1 | 2 | 52 | 50 - list_part1 | 3 | 6 | 60 - sub_part1 | 1 | 1 | 60 - sub_part2 | 1 | 2 | 10 -(4 rows) - --- This should do the tuple routing even though there is no explicit --- partition-key update, because there is a trigger on sub_part1. -UPDATE list_parted set c = 70 WHERE b = 1; -SELECT tableoid::regclass::text, * FROM list_parted ORDER BY 1, 2, 3, 4; - tableoid | a | b | c -------------+---+----+---- - list_part1 | 2 | 52 | 50 - list_part1 | 3 | 6 | 60 - sub_part2 | 1 | 2 | 10 - sub_part2 | 1 | 2 | 70 -(4 rows) - -DROP TRIGGER parted_mod_b ON sub_part1; --- If BR DELETE trigger prevented DELETE from happening, we should also skip --- the INSERT if that delete is part of UPDATE=>DELETE+INSERT. -CREATE OR REPLACE FUNCTION func_parted_mod_b() returns trigger as $$ -BEGIN - raise notice 'Trigger: Got OLD row %, but returning NULL', OLD; - return NULL; -END $$ LANGUAGE plpgsql; -CREATE TRIGGER trig_skip_delete before delete on sub_part2 - for each row execute procedure func_parted_mod_b(); -UPDATE list_parted set b = 1 WHERE c = 70; -NOTICE: Trigger: Got OLD row (2,70,1), but returning NULL -SELECT tableoid::regclass::text, * FROM list_parted ORDER BY 1, 2, 3, 4; - tableoid | a | b | c -------------+---+----+---- - list_part1 | 2 | 52 | 50 - list_part1 | 3 | 6 | 60 - sub_part2 | 1 | 2 | 10 - sub_part2 | 1 | 2 | 70 -(4 rows) - --- Drop the trigger. Now the row should be moved. -DROP TRIGGER trig_skip_delete ON sub_part2; -UPDATE list_parted set b = 1 WHERE c = 70; -SELECT tableoid::regclass::text, * FROM list_parted ORDER BY 1, 2, 3, 4; - tableoid | a | b | c -------------+---+----+---- - list_part1 | 2 | 52 | 50 - list_part1 | 3 | 6 | 60 - sub_part1 | 1 | 1 | 70 - sub_part2 | 1 | 2 | 10 -(4 rows) - -DROP FUNCTION func_parted_mod_b(); --- UPDATE partition-key with FROM clause. If join produces multiple output --- rows for the same row to be modified, we should tuple-route the row only --- once. There should not be any rows inserted. -CREATE TABLE non_parted (id int); -INSERT into non_parted VALUES (1), (1), (1), (2), (2), (2), (3), (3), (3); -UPDATE list_parted t1 set a = 2 FROM non_parted t2 WHERE t1.a = t2.id and a = 1; -SELECT tableoid::regclass::text, * FROM list_parted ORDER BY 1, 2, 3, 4; - tableoid | a | b | c -------------+---+----+---- - list_part1 | 2 | 1 | 70 - list_part1 | 2 | 2 | 10 - list_part1 | 2 | 52 | 50 - list_part1 | 3 | 6 | 60 -(4 rows) - -DROP TABLE non_parted; --- Cleanup: list_parted no longer needed. -DROP TABLE list_parted; --- create custom operator class and hash function, for the same reason --- explained in alter_table.sql -create or replace function dummy_hashint4(a int4, seed int8) returns int8 as -$$ begin return (a + seed); end; $$ language 'plpgsql' immutable; -create operator class custom_opclass for type int4 using hash as -operator 1 = , function 2 dummy_hashint4(int4, int8); -create table hash_parted ( - a int, - b int -) partition by hash (a custom_opclass, b custom_opclass); -create table hpart1 partition of hash_parted for values with (modulus 2, remainder 1); -create table hpart2 partition of hash_parted for values with (modulus 4, remainder 2); -create table hpart3 partition of hash_parted for values with (modulus 8, remainder 0); -create table hpart4 partition of hash_parted for values with (modulus 8, remainder 4); -insert into hpart1 values (1, 1); -insert into hpart2 values (2, 5); -insert into hpart4 values (3, 4); --- fail -update hpart1 set a = 3, b=4 where a = 1; -ERROR: new row for relation "hpart1" violates partition constraint -DETAIL: Failing row contains (3, 4). --- ok, row movement -update hash_parted set b = b - 1 where b = 1; --- ok -update hash_parted set b = b + 8 where b = 1; --- cleanup -drop table hash_parted; -drop operator class custom_opclass using hash; -drop function dummy_hashint4(a int4, seed int8); +psql: error: connection to server on socket "/tmp/pg_regress-03Az7d/.s.PGSQL.40051" failed: FATAL: the database system is not yet accepting connections +DETAIL: Consistent recovery state has not been yet reached. diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/delete.out /tmp/cirrus-ci-build/build/testrun/regress/regress/results/delete.out --- /tmp/cirrus-ci-build/src/test/regress/expected/delete.out 2024-09-20 21:41:45.678024521 +0000 +++ /tmp/cirrus-ci-build/build/testrun/regress/regress/results/delete.out 2024-09-20 21:46:05.541625019 +0000 @@ -1,33 +1,2 @@ -CREATE TABLE delete_test ( - id SERIAL PRIMARY KEY, - a INT, - b text -); -INSERT INTO delete_test (a) VALUES (10); -INSERT INTO delete_test (a, b) VALUES (50, repeat('x', 10000)); -INSERT INTO delete_test (a) VALUES (100); --- allow an alias to be specified for DELETE's target table -DELETE FROM delete_test AS dt WHERE dt.a > 75; --- if an alias is specified, don't allow the original table name --- to be referenced -DELETE FROM delete_test dt WHERE delete_test.a > 25; -ERROR: invalid reference to FROM-clause entry for table "delete_test" -LINE 1: DELETE FROM delete_test dt WHERE delete_test.a > 25; - ^ -HINT: Perhaps you meant to reference the table alias "dt". -SELECT id, a, char_length(b) FROM delete_test; - id | a | char_length -----+----+------------- - 1 | 10 | - 2 | 50 | 10000 -(2 rows) - --- delete a row with a TOASTed value -DELETE FROM delete_test WHERE a > 25; -SELECT id, a, char_length(b) FROM delete_test; - id | a | char_length -----+----+------------- - 1 | 10 | -(1 row) - -DROP TABLE delete_test; +psql: error: connection to server on socket "/tmp/pg_regress-03Az7d/.s.PGSQL.40051" failed: FATAL: the database system is not yet accepting connections +DETAIL: Consistent recovery state has not been yet reached. diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/namespace.out /tmp/cirrus-ci-build/build/testrun/regress/regress/results/namespace.out --- /tmp/cirrus-ci-build/src/test/regress/expected/namespace.out 2024-09-20 21:41:45.714024522 +0000 +++ /tmp/cirrus-ci-build/build/testrun/regress/regress/results/namespace.out 2024-09-20 21:46:05.485625097 +0000 @@ -1,160 +1,2 @@ --- --- Regression tests for schemas (namespaces) --- --- set the whitespace-only search_path to test that the --- GUC list syntax is preserved during a schema creation -SELECT pg_catalog.set_config('search_path', ' ', false); - set_config ------------- - -(1 row) - -CREATE SCHEMA test_ns_schema_1 - CREATE UNIQUE INDEX abc_a_idx ON abc (a) - CREATE VIEW abc_view AS - SELECT a+1 AS a, b+1 AS b FROM abc - CREATE TABLE abc ( - a serial, - b int UNIQUE - ); --- verify that the correct search_path restored on abort -SET search_path to public; -BEGIN; -SET search_path to public, test_ns_schema_1; -CREATE SCHEMA test_ns_schema_2 - CREATE VIEW abc_view AS SELECT c FROM abc; -ERROR: column "c" does not exist -LINE 2: CREATE VIEW abc_view AS SELECT c FROM abc; - ^ -COMMIT; -SHOW search_path; - search_path -------------- - public -(1 row) - --- verify that the correct search_path preserved --- after creating the schema and on commit -BEGIN; -SET search_path to public, test_ns_schema_1; -CREATE SCHEMA test_ns_schema_2 - CREATE VIEW abc_view AS SELECT a FROM abc; -SHOW search_path; - search_path --------------------------- - public, test_ns_schema_1 -(1 row) - -COMMIT; -SHOW search_path; - search_path --------------------------- - public, test_ns_schema_1 -(1 row) - -DROP SCHEMA test_ns_schema_2 CASCADE; -NOTICE: drop cascades to view test_ns_schema_2.abc_view --- verify that the objects were created -SELECT COUNT(*) FROM pg_class WHERE relnamespace = - (SELECT oid FROM pg_namespace WHERE nspname = 'test_ns_schema_1'); - count -------- - 5 -(1 row) - -INSERT INTO test_ns_schema_1.abc DEFAULT VALUES; -INSERT INTO test_ns_schema_1.abc DEFAULT VALUES; -INSERT INTO test_ns_schema_1.abc DEFAULT VALUES; -SELECT * FROM test_ns_schema_1.abc; - a | b ----+--- - 1 | - 2 | - 3 | -(3 rows) - -SELECT * FROM test_ns_schema_1.abc_view; - a | b ----+--- - 2 | - 3 | - 4 | -(3 rows) - -ALTER SCHEMA test_ns_schema_1 RENAME TO test_ns_schema_renamed; -SELECT COUNT(*) FROM pg_class WHERE relnamespace = - (SELECT oid FROM pg_namespace WHERE nspname = 'test_ns_schema_1'); - count -------- - 0 -(1 row) - --- test IF NOT EXISTS cases -CREATE SCHEMA test_ns_schema_renamed; -- fail, already exists -ERROR: schema "test_ns_schema_renamed" already exists -CREATE SCHEMA IF NOT EXISTS test_ns_schema_renamed; -- ok with notice -NOTICE: schema "test_ns_schema_renamed" already exists, skipping -CREATE SCHEMA IF NOT EXISTS test_ns_schema_renamed -- fail, disallowed - CREATE TABLE abc ( - a serial, - b int UNIQUE - ); -ERROR: CREATE SCHEMA IF NOT EXISTS cannot include schema elements -LINE 2: CREATE TABLE abc ( - ^ -DROP SCHEMA test_ns_schema_renamed CASCADE; -NOTICE: drop cascades to 2 other objects -DETAIL: drop cascades to table test_ns_schema_renamed.abc -drop cascades to view test_ns_schema_renamed.abc_view --- verify that the objects were dropped -SELECT COUNT(*) FROM pg_class WHERE relnamespace = - (SELECT oid FROM pg_namespace WHERE nspname = 'test_ns_schema_renamed'); - count -------- - 0 -(1 row) - --- --- Verify that search_path is set to a safe value during maintenance --- commands. --- -CREATE SCHEMA test_maint_search_path; -SET search_path = test_maint_search_path; -CREATE FUNCTION fn(INT) RETURNS INT IMMUTABLE LANGUAGE plpgsql AS $$ - BEGIN - RAISE NOTICE 'current search_path: %', current_setting('search_path'); - RETURN $1; - END; -$$; -CREATE TABLE test_maint(i INT); -INSERT INTO test_maint VALUES (1), (2); -CREATE MATERIALIZED VIEW test_maint_mv AS SELECT fn(i) FROM test_maint; -NOTICE: current search_path: pg_catalog, pg_temp -NOTICE: current search_path: pg_catalog, pg_temp --- the following commands should see search_path as pg_catalog, pg_temp -CREATE INDEX test_maint_idx ON test_maint_search_path.test_maint (fn(i)); -NOTICE: current search_path: pg_catalog, pg_temp -NOTICE: current search_path: pg_catalog, pg_temp -REINDEX TABLE test_maint_search_path.test_maint; -NOTICE: current search_path: pg_catalog, pg_temp -NOTICE: current search_path: pg_catalog, pg_temp -ANALYZE test_maint_search_path.test_maint; -NOTICE: current search_path: pg_catalog, pg_temp -NOTICE: current search_path: pg_catalog, pg_temp -VACUUM FULL test_maint_search_path.test_maint; -NOTICE: current search_path: pg_catalog, pg_temp -NOTICE: current search_path: pg_catalog, pg_temp -CLUSTER test_maint_search_path.test_maint USING test_maint_idx; -NOTICE: current search_path: pg_catalog, pg_temp -NOTICE: current search_path: pg_catalog, pg_temp -NOTICE: current search_path: pg_catalog, pg_temp -NOTICE: current search_path: pg_catalog, pg_temp -REFRESH MATERIALIZED VIEW test_maint_search_path.test_maint_mv; -NOTICE: current search_path: pg_catalog, pg_temp -NOTICE: current search_path: pg_catalog, pg_temp -RESET search_path; -DROP SCHEMA test_maint_search_path CASCADE; -NOTICE: drop cascades to 3 other objects -DETAIL: drop cascades to function test_maint_search_path.fn(integer) -drop cascades to table test_maint_search_path.test_maint -drop cascades to materialized view test_maint_search_path.test_maint_mv +psql: error: connection to server on socket "/tmp/pg_regress-03Az7d/.s.PGSQL.40051" failed: FATAL: the database system is not yet accepting connections +DETAIL: Consistent recovery state has not been yet reached. diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/prepared_xacts_1.out /tmp/cirrus-ci-build/build/testrun/regress/regress/results/prepared_xacts.out --- /tmp/cirrus-ci-build/src/test/regress/expected/prepared_xacts_1.out 2024-09-20 21:41:45.734024523 +0000 +++ /tmp/cirrus-ci-build/build/testrun/regress/regress/results/prepared_xacts.out 2024-09-20 21:46:05.477625108 +0000 @@ -1,266 +1,2 @@ --- --- PREPARED TRANSACTIONS (two-phase commit) --- --- We can't readily test persistence of prepared xacts within the --- regression script framework, unfortunately. Note that a crash --- isn't really needed ... stopping and starting the postmaster would --- be enough, but we can't even do that here. --- create a simple table that we'll use in the tests -CREATE TABLE pxtest1 (foobar VARCHAR(10)); -INSERT INTO pxtest1 VALUES ('aaa'); --- Test PREPARE TRANSACTION -BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; -UPDATE pxtest1 SET foobar = 'bbb' WHERE foobar = 'aaa'; -SELECT * FROM pxtest1; - foobar --------- - bbb -(1 row) - -PREPARE TRANSACTION 'regress_foo1'; -ERROR: prepared transactions are disabled -HINT: Set "max_prepared_transactions" to a nonzero value. -SELECT * FROM pxtest1; - foobar --------- - aaa -(1 row) - --- Test pg_prepared_xacts system view -SELECT gid FROM pg_prepared_xacts WHERE gid ~ '^regress_' ORDER BY gid; - gid ------ -(0 rows) - --- Test ROLLBACK PREPARED -ROLLBACK PREPARED 'regress_foo1'; -ERROR: prepared transaction with identifier "regress_foo1" does not exist -SELECT * FROM pxtest1; - foobar --------- - aaa -(1 row) - -SELECT gid FROM pg_prepared_xacts WHERE gid ~ '^regress_' ORDER BY gid; - gid ------ -(0 rows) - --- Test COMMIT PREPARED -BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; -INSERT INTO pxtest1 VALUES ('ddd'); -SELECT * FROM pxtest1; - foobar --------- - aaa - ddd -(2 rows) - -PREPARE TRANSACTION 'regress_foo2'; -ERROR: prepared transactions are disabled -HINT: Set "max_prepared_transactions" to a nonzero value. -SELECT * FROM pxtest1; - foobar --------- - aaa -(1 row) - -COMMIT PREPARED 'regress_foo2'; -ERROR: prepared transaction with identifier "regress_foo2" does not exist -SELECT * FROM pxtest1; - foobar --------- - aaa -(1 row) - --- Test duplicate gids -BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; -UPDATE pxtest1 SET foobar = 'eee' WHERE foobar = 'ddd'; -SELECT * FROM pxtest1; - foobar --------- - aaa -(1 row) - -PREPARE TRANSACTION 'regress_foo3'; -ERROR: prepared transactions are disabled -HINT: Set "max_prepared_transactions" to a nonzero value. -SELECT gid FROM pg_prepared_xacts WHERE gid ~ '^regress_' ORDER BY gid; - gid ------ -(0 rows) - -BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; -INSERT INTO pxtest1 VALUES ('fff'); --- This should fail, because the gid foo3 is already in use -PREPARE TRANSACTION 'regress_foo3'; -ERROR: prepared transactions are disabled -HINT: Set "max_prepared_transactions" to a nonzero value. -SELECT * FROM pxtest1; - foobar --------- - aaa -(1 row) - -ROLLBACK PREPARED 'regress_foo3'; -ERROR: prepared transaction with identifier "regress_foo3" does not exist -SELECT * FROM pxtest1; - foobar --------- - aaa -(1 row) - --- Test serialization failure (SSI) -BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; -UPDATE pxtest1 SET foobar = 'eee' WHERE foobar = 'ddd'; -SELECT * FROM pxtest1; - foobar --------- - aaa -(1 row) - -PREPARE TRANSACTION 'regress_foo4'; -ERROR: prepared transactions are disabled -HINT: Set "max_prepared_transactions" to a nonzero value. -SELECT gid FROM pg_prepared_xacts WHERE gid ~ '^regress_' ORDER BY gid; - gid ------ -(0 rows) - -BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; -SELECT * FROM pxtest1; - foobar --------- - aaa -(1 row) - --- This should fail, because the two transactions have a write-skew anomaly -INSERT INTO pxtest1 VALUES ('fff'); -PREPARE TRANSACTION 'regress_foo5'; -ERROR: prepared transactions are disabled -HINT: Set "max_prepared_transactions" to a nonzero value. -SELECT gid FROM pg_prepared_xacts WHERE gid ~ '^regress_' ORDER BY gid; - gid ------ -(0 rows) - -ROLLBACK PREPARED 'regress_foo4'; -ERROR: prepared transaction with identifier "regress_foo4" does not exist -SELECT gid FROM pg_prepared_xacts WHERE gid ~ '^regress_' ORDER BY gid; - gid ------ -(0 rows) - --- Clean up -DROP TABLE pxtest1; --- Test detection of session-level and xact-level locks on same object -BEGIN; -SELECT pg_advisory_lock(1); - pg_advisory_lock ------------------- - -(1 row) - -SELECT pg_advisory_xact_lock_shared(1); - pg_advisory_xact_lock_shared ------------------------------- - -(1 row) - -PREPARE TRANSACTION 'regress_foo6'; -- fails -ERROR: prepared transactions are disabled -HINT: Set "max_prepared_transactions" to a nonzero value. --- Test subtransactions -BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; - CREATE TABLE pxtest2 (a int); - INSERT INTO pxtest2 VALUES (1); - SAVEPOINT a; - INSERT INTO pxtest2 VALUES (2); - ROLLBACK TO a; - SAVEPOINT b; - INSERT INTO pxtest2 VALUES (3); -PREPARE TRANSACTION 'regress_sub1'; -ERROR: prepared transactions are disabled -HINT: Set "max_prepared_transactions" to a nonzero value. -CREATE TABLE pxtest3(fff int); --- Test shared invalidation -BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; - DROP TABLE pxtest3; - CREATE TABLE pxtest4 (a int); - INSERT INTO pxtest4 VALUES (1); - INSERT INTO pxtest4 VALUES (2); - DECLARE foo CURSOR FOR SELECT * FROM pxtest4; - -- Fetch 1 tuple, keeping the cursor open - FETCH 1 FROM foo; - a ---- - 1 -(1 row) - -PREPARE TRANSACTION 'regress_sub2'; -ERROR: prepared transactions are disabled -HINT: Set "max_prepared_transactions" to a nonzero value. --- No such cursor -FETCH 1 FROM foo; -ERROR: cursor "foo" does not exist --- Table doesn't exist, the creation hasn't been committed yet -SELECT * FROM pxtest2; -ERROR: relation "pxtest2" does not exist -LINE 1: SELECT * FROM pxtest2; - ^ --- There should be two prepared transactions -SELECT gid FROM pg_prepared_xacts WHERE gid ~ '^regress_' ORDER BY gid; - gid ------ -(0 rows) - --- pxtest3 should be locked because of the pending DROP -begin; -lock table pxtest3 in access share mode nowait; -rollback; --- Disconnect, we will continue testing in a different backend -\c - --- There should still be two prepared transactions -SELECT gid FROM pg_prepared_xacts WHERE gid ~ '^regress_' ORDER BY gid; - gid ------ -(0 rows) - --- pxtest3 should still be locked because of the pending DROP -begin; -lock table pxtest3 in access share mode nowait; -rollback; --- Commit table creation -COMMIT PREPARED 'regress_sub1'; -ERROR: prepared transaction with identifier "regress_sub1" does not exist -\d pxtest2 -SELECT * FROM pxtest2; -ERROR: relation "pxtest2" does not exist -LINE 1: SELECT * FROM pxtest2; - ^ --- There should be one prepared transaction -SELECT gid FROM pg_prepared_xacts WHERE gid ~ '^regress_' ORDER BY gid; - gid ------ -(0 rows) - --- Commit table drop -COMMIT PREPARED 'regress_sub2'; -ERROR: prepared transaction with identifier "regress_sub2" does not exist -SELECT * FROM pxtest3; - fff ------ -(0 rows) - --- There should be no prepared transactions -SELECT gid FROM pg_prepared_xacts WHERE gid ~ '^regress_' ORDER BY gid; - gid ------ -(0 rows) - --- Clean up -DROP TABLE pxtest2; -ERROR: table "pxtest2" does not exist -DROP TABLE pxtest3; -- will still be there if prepared xacts are disabled -DROP TABLE pxtest4; -ERROR: table "pxtest4" does not exist +psql: error: connection to server on socket "/tmp/pg_regress-03Az7d/.s.PGSQL.40051" failed: FATAL: the database system is not yet accepting connections +DETAIL: Consistent recovery state has not been yet reached. diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/brin.out /tmp/cirrus-ci-build/build/testrun/regress/regress/results/brin.out --- /tmp/cirrus-ci-build/src/test/regress/expected/brin.out 2024-09-20 21:41:45.666024520 +0000 +++ /tmp/cirrus-ci-build/build/testrun/regress/regress/results/brin.out 2024-09-20 21:46:06.645623497 +0000 @@ -1,581 +1,2 @@ -CREATE TABLE brintest (byteacol bytea, - charcol "char", - namecol name, - int8col bigint, - int2col smallint, - int4col integer, - textcol text, - oidcol oid, - tidcol tid, - float4col real, - float8col double precision, - macaddrcol macaddr, - inetcol inet, - cidrcol cidr, - bpcharcol character, - datecol date, - timecol time without time zone, - timestampcol timestamp without time zone, - timestamptzcol timestamp with time zone, - intervalcol interval, - timetzcol time with time zone, - bitcol bit(10), - varbitcol bit varying(16), - numericcol numeric, - uuidcol uuid, - int4rangecol int4range, - lsncol pg_lsn, - boxcol box -) WITH (fillfactor=10, autovacuum_enabled=off); -INSERT INTO brintest SELECT - repeat(stringu1, 8)::bytea, - substr(stringu1, 1, 1)::"char", - stringu1::name, 142857 * tenthous, - thousand, - twothousand, - repeat(stringu1, 8), - unique1::oid, - format('(%s,%s)', tenthous, twenty)::tid, - (four + 1.0)/(hundred+1), - odd::float8 / (tenthous + 1), - format('%s:00:%s:00:%s:00', to_hex(odd), to_hex(even), to_hex(hundred))::macaddr, - inet '10.2.3.4/24' + tenthous, - cidr '10.2.3/24' + tenthous, - substr(stringu1, 1, 1)::bpchar, - date '1995-08-15' + tenthous, - time '01:20:30' + thousand * interval '18.5 second', - timestamp '1942-07-23 03:05:09' + tenthous * interval '36.38 hours', - timestamptz '1972-10-10 03:00' + thousand * interval '1 hour', - justify_days(justify_hours(tenthous * interval '12 minutes')), - timetz '01:30:20+02' + hundred * interval '15 seconds', - thousand::bit(10), - tenthous::bit(16)::varbit, - tenthous::numeric(36,30) * fivethous * even / (hundred + 1), - format('%s%s-%s-%s-%s-%s%s%s', to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'))::uuid, - int4range(thousand, twothousand), - format('%s/%s%s', odd, even, tenthous)::pg_lsn, - box(point(odd, even), point(thousand, twothousand)) -FROM tenk1 ORDER BY unique2 LIMIT 100; --- throw in some NULL's and different values -INSERT INTO brintest (inetcol, cidrcol, int4rangecol) SELECT - inet 'fe80::6e40:8ff:fea9:8c46' + tenthous, - cidr 'fe80::6e40:8ff:fea9:8c46' + tenthous, - 'empty'::int4range -FROM tenk1 ORDER BY thousand, tenthous LIMIT 25; -CREATE INDEX brinidx ON brintest USING brin ( - byteacol, - charcol, - namecol, - int8col, - int2col, - int4col, - textcol, - oidcol, - tidcol, - float4col, - float8col, - macaddrcol, - inetcol inet_inclusion_ops, - inetcol inet_minmax_ops, - cidrcol inet_inclusion_ops, - cidrcol inet_minmax_ops, - bpcharcol, - datecol, - timecol, - timestampcol, - timestamptzcol, - intervalcol, - timetzcol, - bitcol, - varbitcol, - numericcol, - uuidcol, - int4rangecol, - lsncol, - boxcol -) with (pages_per_range = 1); -CREATE TABLE brinopers (colname name, typ text, - op text[], value text[], matches int[], - check (cardinality(op) = cardinality(value)), - check (cardinality(op) = cardinality(matches))); -INSERT INTO brinopers VALUES - ('byteacol', 'bytea', - '{>, >=, =, <=, <}', - '{AAAAAA, AAAAAA, BNAAAABNAAAABNAAAABNAAAABNAAAABNAAAABNAAAABNAAAA, ZZZZZZ, ZZZZZZ}', - '{100, 100, 1, 100, 100}'), - ('charcol', '"char"', - '{>, >=, =, <=, <}', - '{A, A, M, Z, Z}', - '{97, 100, 6, 100, 98}'), - ('namecol', 'name', - '{>, >=, =, <=, <}', - '{AAAAAA, AAAAAA, MAAAAA, ZZAAAA, ZZAAAA}', - '{100, 100, 2, 100, 100}'), - ('int2col', 'int2', - '{>, >=, =, <=, <}', - '{0, 0, 800, 999, 999}', - '{100, 100, 1, 100, 100}'), - ('int2col', 'int4', - '{>, >=, =, <=, <}', - '{0, 0, 800, 999, 1999}', - '{100, 100, 1, 100, 100}'), - ('int2col', 'int8', - '{>, >=, =, <=, <}', - '{0, 0, 800, 999, 1428427143}', - '{100, 100, 1, 100, 100}'), - ('int4col', 'int2', - '{>, >=, =, <=, <}', - '{0, 0, 800, 1999, 1999}', - '{100, 100, 1, 100, 100}'), - ('int4col', 'int4', - '{>, >=, =, <=, <}', - '{0, 0, 800, 1999, 1999}', - '{100, 100, 1, 100, 100}'), - ('int4col', 'int8', - '{>, >=, =, <=, <}', - '{0, 0, 800, 1999, 1428427143}', - '{100, 100, 1, 100, 100}'), - ('int8col', 'int2', - '{>, >=}', - '{0, 0}', - '{100, 100}'), - ('int8col', 'int4', - '{>, >=}', - '{0, 0}', - '{100, 100}'), - ('int8col', 'int8', - '{>, >=, =, <=, <}', - '{0, 0, 1257141600, 1428427143, 1428427143}', - '{100, 100, 1, 100, 100}'), - ('textcol', 'text', - '{>, >=, =, <=, <}', - '{ABABAB, ABABAB, BNAAAABNAAAABNAAAABNAAAABNAAAABNAAAABNAAAABNAAAA, ZZAAAA, ZZAAAA}', - '{100, 100, 1, 100, 100}'), - ('oidcol', 'oid', - '{>, >=, =, <=, <}', - '{0, 0, 8800, 9999, 9999}', - '{100, 100, 1, 100, 100}'), - ('tidcol', 'tid', - '{>, >=, =, <=, <}', - '{"(0,0)", "(0,0)", "(8800,0)", "(9999,19)", "(9999,19)"}', - '{100, 100, 1, 100, 100}'), - ('float4col', 'float4', - '{>, >=, =, <=, <}', - '{0.0103093, 0.0103093, 1, 1, 1}', - '{100, 100, 4, 100, 96}'), - ('float4col', 'float8', - '{>, >=, =, <=, <}', - '{0.0103093, 0.0103093, 1, 1, 1}', - '{100, 100, 4, 100, 96}'), - ('float8col', 'float4', - '{>, >=, =, <=, <}', - '{0, 0, 0, 1.98, 1.98}', - '{99, 100, 1, 100, 100}'), - ('float8col', 'float8', - '{>, >=, =, <=, <}', - '{0, 0, 0, 1.98, 1.98}', - '{99, 100, 1, 100, 100}'), - ('macaddrcol', 'macaddr', - '{>, >=, =, <=, <}', - '{00:00:01:00:00:00, 00:00:01:00:00:00, 2c:00:2d:00:16:00, ff:fe:00:00:00:00, ff:fe:00:00:00:00}', - '{99, 100, 2, 100, 100}'), - ('inetcol', 'inet', - '{&&, =, <, <=, >, >=, >>=, >>, <<=, <<}', - '{10/8, 10.2.14.231/24, 255.255.255.255, 255.255.255.255, 0.0.0.0, 0.0.0.0, 10.2.14.231/24, 10.2.14.231/25, 10.2.14.231/8, 0/0}', - '{100, 1, 100, 100, 125, 125, 2, 2, 100, 100}'), - ('inetcol', 'inet', - '{&&, >>=, <<=, =}', - '{fe80::6e40:8ff:fea9:a673/32, fe80::6e40:8ff:fea9:8c46, fe80::6e40:8ff:fea9:a673/32, fe80::6e40:8ff:fea9:8c46}', - '{25, 1, 25, 1}'), - ('inetcol', 'cidr', - '{&&, <, <=, >, >=, >>=, >>, <<=, <<}', - '{10/8, 255.255.255.255, 255.255.255.255, 0.0.0.0, 0.0.0.0, 10.2.14/24, 10.2.14/25, 10/8, 0/0}', - '{100, 100, 100, 125, 125, 2, 2, 100, 100}'), - ('inetcol', 'cidr', - '{&&, >>=, <<=, =}', - '{fe80::/32, fe80::6e40:8ff:fea9:8c46, fe80::/32, fe80::6e40:8ff:fea9:8c46}', - '{25, 1, 25, 1}'), - ('cidrcol', 'inet', - '{&&, =, <, <=, >, >=, >>=, >>, <<=, <<}', - '{10/8, 10.2.14/24, 255.255.255.255, 255.255.255.255, 0.0.0.0, 0.0.0.0, 10.2.14.231/24, 10.2.14.231/25, 10.2.14.231/8, 0/0}', - '{100, 2, 100, 100, 125, 125, 2, 2, 100, 100}'), - ('cidrcol', 'inet', - '{&&, >>=, <<=, =}', - '{fe80::6e40:8ff:fea9:a673/32, fe80::6e40:8ff:fea9:8c46, fe80::6e40:8ff:fea9:a673/32, fe80::6e40:8ff:fea9:8c46}', - '{25, 1, 25, 1}'), - ('cidrcol', 'cidr', - '{&&, =, <, <=, >, >=, >>=, >>, <<=, <<}', - '{10/8, 10.2.14/24, 255.255.255.255, 255.255.255.255, 0.0.0.0, 0.0.0.0, 10.2.14/24, 10.2.14/25, 10/8, 0/0}', - '{100, 2, 100, 100, 125, 125, 2, 2, 100, 100}'), - ('cidrcol', 'cidr', - '{&&, >>=, <<=, =}', - '{fe80::/32, fe80::6e40:8ff:fea9:8c46, fe80::/32, fe80::6e40:8ff:fea9:8c46}', - '{25, 1, 25, 1}'), - ('bpcharcol', 'bpchar', - '{>, >=, =, <=, <}', - '{A, A, W, Z, Z}', - '{97, 100, 6, 100, 98}'), - ('datecol', 'date', - '{>, >=, =, <=, <}', - '{1995-08-15, 1995-08-15, 2009-12-01, 2022-12-30, 2022-12-30}', - '{100, 100, 1, 100, 100}'), - ('timecol', 'time', - '{>, >=, =, <=, <}', - '{01:20:30, 01:20:30, 02:28:57, 06:28:31.5, 06:28:31.5}', - '{100, 100, 1, 100, 100}'), - ('timestampcol', 'timestamp', - '{>, >=, =, <=, <}', - '{1942-07-23 03:05:09, 1942-07-23 03:05:09, 1964-03-24 19:26:45, 1984-01-20 22:42:21, 1984-01-20 22:42:21}', - '{100, 100, 1, 100, 100}'), - ('timestampcol', 'timestamptz', - '{>, >=, =, <=, <}', - '{1942-07-23 03:05:09, 1942-07-23 03:05:09, 1964-03-24 19:26:45, 1984-01-20 22:42:21, 1984-01-20 22:42:21}', - '{100, 100, 1, 100, 100}'), - ('timestamptzcol', 'timestamptz', - '{>, >=, =, <=, <}', - '{1972-10-10 03:00:00-04, 1972-10-10 03:00:00-04, 1972-10-19 09:00:00-07, 1972-11-20 19:00:00-03, 1972-11-20 19:00:00-03}', - '{100, 100, 1, 100, 100}'), - ('intervalcol', 'interval', - '{>, >=, =, <=, <}', - '{00:00:00, 00:00:00, 1 mons 13 days 12:24, 2 mons 23 days 07:48:00, 1 year}', - '{100, 100, 1, 100, 100}'), - ('timetzcol', 'timetz', - '{>, >=, =, <=, <}', - '{01:30:20+02, 01:30:20+02, 01:35:50+02, 23:55:05+02, 23:55:05+02}', - '{99, 100, 2, 100, 100}'), - ('bitcol', 'bit(10)', - '{>, >=, =, <=, <}', - '{0000000010, 0000000010, 0011011110, 1111111000, 1111111000}', - '{100, 100, 1, 100, 100}'), - ('varbitcol', 'varbit(16)', - '{>, >=, =, <=, <}', - '{0000000000000100, 0000000000000100, 0001010001100110, 1111111111111000, 1111111111111000}', - '{100, 100, 1, 100, 100}'), - ('numericcol', 'numeric', - '{>, >=, =, <=, <}', - '{0.00, 0.01, 2268164.347826086956521739130434782609, 99470151.9, 99470151.9}', - '{100, 100, 1, 100, 100}'), - ('uuidcol', 'uuid', - '{>, >=, =, <=, <}', - '{00040004-0004-0004-0004-000400040004, 00040004-0004-0004-0004-000400040004, 52225222-5222-5222-5222-522252225222, 99989998-9998-9998-9998-999899989998, 99989998-9998-9998-9998-999899989998}', - '{100, 100, 1, 100, 100}'), - ('int4rangecol', 'int4range', - '{<<, &<, &&, &>, >>, @>, <@, =, <, <=, >, >=}', - '{"[10000,)","[10000,)","(,]","[3,4)","[36,44)","(1500,1501]","[3,4)","[222,1222)","[36,44)","[43,1043)","[367,4466)","[519,)"}', - '{53, 53, 53, 53, 50, 22, 72, 1, 74, 75, 34, 21}'), - ('int4rangecol', 'int4range', - '{@>, <@, =, <=, >, >=}', - '{empty, empty, empty, empty, empty, empty}', - '{125, 72, 72, 72, 53, 125}'), - ('int4rangecol', 'int4', - '{@>}', - '{1500}', - '{22}'), - ('lsncol', 'pg_lsn', - '{>, >=, =, <=, <, IS, IS NOT}', - '{0/1200, 0/1200, 44/455222, 198/1999799, 198/1999799, NULL, NULL}', - '{100, 100, 1, 100, 100, 25, 100}'), - ('boxcol', 'point', - '{@>}', - '{"(500,43)"}', - '{11}'), - ('boxcol', 'box', - '{<<, &<, &&, &>, >>, <<|, &<|, |&>, |>>, @>, <@, ~=}', - '{"((1000,2000),(3000,4000))","((1,2),(3000,4000))","((1,2),(3000,4000))","((1,2),(3000,4000))","((1,2),(3,4))","((1000,2000),(3000,4000))","((1,2000),(3,4000))","((1000,2),(3000,4))","((1,2),(3,4))","((1,2),(300,400))","((1,2),(3000,4000))","((222,1222),(44,45))"}', - '{100, 100, 100, 99, 96, 100, 100, 99, 96, 1, 99, 1}'); -DO $x$ -DECLARE - r record; - r2 record; - cond text; - idx_ctids tid[]; - ss_ctids tid[]; - count int; - plan_ok bool; - plan_line text; -BEGIN - FOR r IN SELECT colname, oper, typ, value[ordinality], matches[ordinality] FROM brinopers, unnest(op) WITH ORDINALITY AS oper LOOP - - -- prepare the condition - IF r.value IS NULL THEN - cond := format('%I %s %L', r.colname, r.oper, r.value); - ELSE - cond := format('%I %s %L::%s', r.colname, r.oper, r.value, r.typ); - END IF; - - -- run the query using the brin index - SET enable_seqscan = 0; - SET enable_bitmapscan = 1; - - plan_ok := false; - FOR plan_line IN EXECUTE format($y$EXPLAIN SELECT array_agg(ctid) FROM brintest WHERE %s $y$, cond) LOOP - IF plan_line LIKE '%Bitmap Heap Scan on brintest%' THEN - plan_ok := true; - END IF; - END LOOP; - IF NOT plan_ok THEN - RAISE WARNING 'did not get bitmap indexscan plan for %', r; - END IF; - - EXECUTE format($y$SELECT array_agg(ctid) FROM brintest WHERE %s $y$, cond) - INTO idx_ctids; - - -- run the query using a seqscan - SET enable_seqscan = 1; - SET enable_bitmapscan = 0; - - plan_ok := false; - FOR plan_line IN EXECUTE format($y$EXPLAIN SELECT array_agg(ctid) FROM brintest WHERE %s $y$, cond) LOOP - IF plan_line LIKE '%Seq Scan on brintest%' THEN - plan_ok := true; - END IF; - END LOOP; - IF NOT plan_ok THEN - RAISE WARNING 'did not get seqscan plan for %', r; - END IF; - - EXECUTE format($y$SELECT array_agg(ctid) FROM brintest WHERE %s $y$, cond) - INTO ss_ctids; - - -- make sure both return the same results - count := array_length(idx_ctids, 1); - - IF NOT (count = array_length(ss_ctids, 1) AND - idx_ctids @> ss_ctids AND - idx_ctids <@ ss_ctids) THEN - -- report the results of each scan to make the differences obvious - RAISE WARNING 'something not right in %: count %', r, count; - SET enable_seqscan = 1; - SET enable_bitmapscan = 0; - FOR r2 IN EXECUTE 'SELECT ' || r.colname || ' FROM brintest WHERE ' || cond LOOP - RAISE NOTICE 'seqscan: %', r2; - END LOOP; - - SET enable_seqscan = 0; - SET enable_bitmapscan = 1; - FOR r2 IN EXECUTE 'SELECT ' || r.colname || ' FROM brintest WHERE ' || cond LOOP - RAISE NOTICE 'bitmapscan: %', r2; - END LOOP; - END IF; - - -- make sure we found expected number of matches - IF count != r.matches THEN RAISE WARNING 'unexpected number of results % for %', count, r; END IF; - END LOOP; -END; -$x$; -RESET enable_seqscan; -RESET enable_bitmapscan; -INSERT INTO brintest SELECT - repeat(stringu1, 42)::bytea, - substr(stringu1, 1, 1)::"char", - stringu1::name, 142857 * tenthous, - thousand, - twothousand, - repeat(stringu1, 42), - unique1::oid, - format('(%s,%s)', tenthous, twenty)::tid, - (four + 1.0)/(hundred+1), - odd::float8 / (tenthous + 1), - format('%s:00:%s:00:%s:00', to_hex(odd), to_hex(even), to_hex(hundred))::macaddr, - inet '10.2.3.4' + tenthous, - cidr '10.2.3/24' + tenthous, - substr(stringu1, 1, 1)::bpchar, - date '1995-08-15' + tenthous, - time '01:20:30' + thousand * interval '18.5 second', - timestamp '1942-07-23 03:05:09' + tenthous * interval '36.38 hours', - timestamptz '1972-10-10 03:00' + thousand * interval '1 hour', - justify_days(justify_hours(tenthous * interval '12 minutes')), - timetz '01:30:20' + hundred * interval '15 seconds', - thousand::bit(10), - tenthous::bit(16)::varbit, - tenthous::numeric(36,30) * fivethous * even / (hundred + 1), - format('%s%s-%s-%s-%s-%s%s%s', to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'))::uuid, - int4range(thousand, twothousand), - format('%s/%s%s', odd, even, tenthous)::pg_lsn, - box(point(odd, even), point(thousand, twothousand)) -FROM tenk1 ORDER BY unique2 LIMIT 5 OFFSET 5; -SELECT brin_desummarize_range('brinidx', 0); - brin_desummarize_range ------------------------- - -(1 row) - -VACUUM brintest; -- force a summarization cycle in brinidx -UPDATE brintest SET int8col = int8col * int4col; -UPDATE brintest SET textcol = '' WHERE textcol IS NOT NULL; --- Tests for brin_summarize_new_values -SELECT brin_summarize_new_values('brintest'); -- error, not an index -ERROR: "brintest" is not an index -SELECT brin_summarize_new_values('tenk1_unique1'); -- error, not a BRIN index -ERROR: "tenk1_unique1" is not a BRIN index -SELECT brin_summarize_new_values('brinidx'); -- ok, no change expected - brin_summarize_new_values ---------------------------- - 0 -(1 row) - --- Tests for brin_desummarize_range -SELECT brin_desummarize_range('brinidx', -1); -- error, invalid range -ERROR: block number out of range: -1 -SELECT brin_desummarize_range('brinidx', 0); - brin_desummarize_range ------------------------- - -(1 row) - -SELECT brin_desummarize_range('brinidx', 0); - brin_desummarize_range ------------------------- - -(1 row) - -SELECT brin_desummarize_range('brinidx', 100000000); - brin_desummarize_range ------------------------- - -(1 row) - --- Test brin_summarize_range -CREATE TABLE brin_summarize ( - value int -) WITH (fillfactor=10, autovacuum_enabled=false); -CREATE INDEX brin_summarize_idx ON brin_summarize USING brin (value) WITH (pages_per_range=2); --- Fill a few pages -DO $$ -DECLARE curtid tid; -BEGIN - LOOP - INSERT INTO brin_summarize VALUES (1) RETURNING ctid INTO curtid; - EXIT WHEN curtid > tid '(2, 0)'; - END LOOP; -END; -$$; --- summarize one range -SELECT brin_summarize_range('brin_summarize_idx', 0); - brin_summarize_range ----------------------- - 0 -(1 row) - --- nothing: already summarized -SELECT brin_summarize_range('brin_summarize_idx', 1); - brin_summarize_range ----------------------- - 0 -(1 row) - --- summarize one range -SELECT brin_summarize_range('brin_summarize_idx', 2); - brin_summarize_range ----------------------- - 1 -(1 row) - --- nothing: page doesn't exist in table -SELECT brin_summarize_range('brin_summarize_idx', 4294967295); - brin_summarize_range ----------------------- - 0 -(1 row) - --- invalid block number values -SELECT brin_summarize_range('brin_summarize_idx', -1); -ERROR: block number out of range: -1 -SELECT brin_summarize_range('brin_summarize_idx', 4294967296); -ERROR: block number out of range: 4294967296 --- test value merging in add_value -CREATE TABLE brintest_2 (n numrange); -CREATE INDEX brinidx_2 ON brintest_2 USING brin (n); -INSERT INTO brintest_2 VALUES ('empty'); -INSERT INTO brintest_2 VALUES (numrange(0, 2^1000::numeric)); -INSERT INTO brintest_2 VALUES ('(-1, 0)'); -SELECT brin_desummarize_range('brinidx', 0); - brin_desummarize_range ------------------------- - -(1 row) - -SELECT brin_summarize_range('brinidx', 0); - brin_summarize_range ----------------------- - 1 -(1 row) - -DROP TABLE brintest_2; --- test brin cost estimates behave sanely based on correlation of values -CREATE TABLE brin_test (a INT, b INT); -INSERT INTO brin_test SELECT x/100,x%100 FROM generate_series(1,10000) x(x); -CREATE INDEX brin_test_a_idx ON brin_test USING brin (a) WITH (pages_per_range = 2); -CREATE INDEX brin_test_b_idx ON brin_test USING brin (b) WITH (pages_per_range = 2); -VACUUM ANALYZE brin_test; --- Ensure brin index is used when columns are perfectly correlated -EXPLAIN (COSTS OFF) SELECT * FROM brin_test WHERE a = 1; - QUERY PLAN --------------------------------------------- - Bitmap Heap Scan on brin_test - Recheck Cond: (a = 1) - -> Bitmap Index Scan on brin_test_a_idx - Index Cond: (a = 1) -(4 rows) - --- Ensure brin index is not used when values are not correlated -EXPLAIN (COSTS OFF) SELECT * FROM brin_test WHERE b = 1; - QUERY PLAN ------------------------ - Seq Scan on brin_test - Filter: (b = 1) -(2 rows) - --- make sure data are properly de-toasted in BRIN index -CREATE TABLE brintest_3 (a text, b text, c text, d text); --- long random strings (~2000 chars each, so ~6kB for min/max on two --- columns) to trigger toasting -WITH rand_value AS (SELECT string_agg(fipshash(i::text),'') AS val FROM generate_series(1,60) s(i)) -INSERT INTO brintest_3 -SELECT val, val, val, val FROM rand_value; -CREATE INDEX brin_test_toast_idx ON brintest_3 USING brin (b, c); -DELETE FROM brintest_3; --- We need to wait a bit for all transactions to complete, so that the --- vacuum actually removes the TOAST rows. Creating an index concurrently --- is a one way to achieve that, because it does exactly such wait. -CREATE INDEX CONCURRENTLY brin_test_temp_idx ON brintest_3(a); -DROP INDEX brin_test_temp_idx; --- vacuum the table, to discard TOAST data -VACUUM brintest_3; --- retry insert with a different random-looking (but deterministic) value --- the value is different, and so should replace either min or max in the --- brin summary -WITH rand_value AS (SELECT string_agg(fipshash((-i)::text),'') AS val FROM generate_series(1,60) s(i)) -INSERT INTO brintest_3 -SELECT val, val, val, val FROM rand_value; --- now try some queries, accessing the brin index -SET enable_seqscan = off; -EXPLAIN (COSTS OFF) -SELECT * FROM brintest_3 WHERE b < '0'; - QUERY PLAN ------------------------------------------------- - Bitmap Heap Scan on brintest_3 - Recheck Cond: (b < '0'::text) - -> Bitmap Index Scan on brin_test_toast_idx - Index Cond: (b < '0'::text) -(4 rows) - -SELECT * FROM brintest_3 WHERE b < '0'; - a | b | c | d ----+---+---+--- -(0 rows) - -DROP TABLE brintest_3; -RESET enable_seqscan; --- test an unlogged table, mostly to get coverage of brinbuildempty -CREATE UNLOGGED TABLE brintest_unlogged (n numrange); -CREATE INDEX brinidx_unlogged ON brintest_unlogged USING brin (n); -INSERT INTO brintest_unlogged VALUES (numrange(0, 2^1000::numeric)); -DROP TABLE brintest_unlogged; --- test that the insert optimization works if no rows end up inserted -CREATE TABLE brin_insert_optimization (a int); -INSERT INTO brin_insert_optimization VALUES (1); -CREATE INDEX brin_insert_optimization_idx ON brin_insert_optimization USING brin (a); -UPDATE brin_insert_optimization SET a = a; -REINDEX INDEX CONCURRENTLY brin_insert_optimization_idx; -DROP TABLE brin_insert_optimization; +psql: error: connection to server on socket "/tmp/pg_regress-03Az7d/.s.PGSQL.40051" failed: FATAL: the database system is not yet accepting connections +DETAIL: Consistent recovery state has not been yet reached. diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/gin.out /tmp/cirrus-ci-build/build/testrun/regress/regress/results/gin.out --- /tmp/cirrus-ci-build/src/test/regress/expected/gin.out 2024-09-20 21:41:45.690024521 +0000 +++ /tmp/cirrus-ci-build/build/testrun/regress/regress/results/gin.out 2024-09-20 21:46:06.633623513 +0000 @@ -1,299 +1,2 @@ --- --- Test GIN indexes. --- --- There are other tests to test different GIN opclasses. This is for testing --- GIN itself. --- Create and populate a test table with a GIN index. -create table gin_test_tbl(i int4[]) with (autovacuum_enabled = off); -create index gin_test_idx on gin_test_tbl using gin (i) - with (fastupdate = on, gin_pending_list_limit = 4096); -insert into gin_test_tbl select array[1, 2, g] from generate_series(1, 20000) g; -insert into gin_test_tbl select array[1, 3, g] from generate_series(1, 1000) g; -select gin_clean_pending_list('gin_test_idx')>10 as many; -- flush the fastupdate buffers - many ------- - t -(1 row) - -insert into gin_test_tbl select array[3, 1, g] from generate_series(1, 1000) g; -vacuum gin_test_tbl; -- flush the fastupdate buffers -select gin_clean_pending_list('gin_test_idx'); -- nothing to flush - gin_clean_pending_list ------------------------- - 0 -(1 row) - --- Test vacuuming -delete from gin_test_tbl where i @> array[2]; -vacuum gin_test_tbl; --- Disable fastupdate, and do more insertions. With fastupdate enabled, most --- insertions (by flushing the list pages) cause page splits. Without --- fastupdate, we get more churn in the GIN data leaf pages, and exercise the --- recompression codepaths. -alter index gin_test_idx set (fastupdate = off); -insert into gin_test_tbl select array[1, 2, g] from generate_series(1, 1000) g; -insert into gin_test_tbl select array[1, 3, g] from generate_series(1, 1000) g; -delete from gin_test_tbl where i @> array[2]; -vacuum gin_test_tbl; --- Test for "rare && frequent" searches -explain (costs off) -select count(*) from gin_test_tbl where i @> array[1, 999]; - QUERY PLAN -------------------------------------------------------- - Aggregate - -> Bitmap Heap Scan on gin_test_tbl - Recheck Cond: (i @> '{1,999}'::integer[]) - -> Bitmap Index Scan on gin_test_idx - Index Cond: (i @> '{1,999}'::integer[]) -(5 rows) - -select count(*) from gin_test_tbl where i @> array[1, 999]; - count -------- - 3 -(1 row) - --- Very weak test for gin_fuzzy_search_limit -set gin_fuzzy_search_limit = 1000; -explain (costs off) -select count(*) > 0 as ok from gin_test_tbl where i @> array[1]; - QUERY PLAN ---------------------------------------------------- - Aggregate - -> Bitmap Heap Scan on gin_test_tbl - Recheck Cond: (i @> '{1}'::integer[]) - -> Bitmap Index Scan on gin_test_idx - Index Cond: (i @> '{1}'::integer[]) -(5 rows) - -select count(*) > 0 as ok from gin_test_tbl where i @> array[1]; - ok ----- - t -(1 row) - -reset gin_fuzzy_search_limit; --- Test optimization of empty queries -create temp table t_gin_test_tbl(i int4[], j int4[]); -create index on t_gin_test_tbl using gin (i, j); -insert into t_gin_test_tbl -values - (null, null), - ('{}', null), - ('{1}', null), - ('{1,2}', null), - (null, '{}'), - (null, '{10}'), - ('{1,2}', '{10}'), - ('{2}', '{10}'), - ('{1,3}', '{}'), - ('{1,1}', '{10}'); -set enable_seqscan = off; -explain (costs off) -select * from t_gin_test_tbl where array[0] <@ i; - QUERY PLAN ---------------------------------------------------- - Bitmap Heap Scan on t_gin_test_tbl - Recheck Cond: ('{0}'::integer[] <@ i) - -> Bitmap Index Scan on t_gin_test_tbl_i_j_idx - Index Cond: (i @> '{0}'::integer[]) -(4 rows) - -select * from t_gin_test_tbl where array[0] <@ i; - i | j ----+--- -(0 rows) - -select * from t_gin_test_tbl where array[0] <@ i and '{}'::int4[] <@ j; - i | j ----+--- -(0 rows) - -explain (costs off) -select * from t_gin_test_tbl where i @> '{}'; - QUERY PLAN ---------------------------------------------------- - Bitmap Heap Scan on t_gin_test_tbl - Recheck Cond: (i @> '{}'::integer[]) - -> Bitmap Index Scan on t_gin_test_tbl_i_j_idx - Index Cond: (i @> '{}'::integer[]) -(4 rows) - -select * from t_gin_test_tbl where i @> '{}'; - i | j --------+------ - {} | - {1} | - {1,2} | - {1,2} | {10} - {2} | {10} - {1,3} | {} - {1,1} | {10} -(7 rows) - -create function explain_query_json(query_sql text) -returns table (explain_line json) -language plpgsql as -$$ -begin - set enable_seqscan = off; - set enable_bitmapscan = on; - return query execute 'EXPLAIN (ANALYZE, FORMAT json) ' || query_sql; -end; -$$; -create function execute_text_query_index(query_sql text) -returns setof text -language plpgsql -as -$$ -begin - set enable_seqscan = off; - set enable_bitmapscan = on; - return query execute query_sql; -end; -$$; -create function execute_text_query_heap(query_sql text) -returns setof text -language plpgsql -as -$$ -begin - set enable_seqscan = on; - set enable_bitmapscan = off; - return query execute query_sql; -end; -$$; --- check number of rows returned by index and removed by recheck -select - query, - js->0->'Plan'->'Plans'->0->'Actual Rows' as "return by index", - js->0->'Plan'->'Rows Removed by Index Recheck' as "removed by recheck", - (res_index = res_heap) as "match" -from - (values - ($$ i @> '{}' $$), - ($$ j @> '{}' $$), - ($$ i @> '{}' and j @> '{}' $$), - ($$ i @> '{1}' $$), - ($$ i @> '{1}' and j @> '{}' $$), - ($$ i @> '{1}' and i @> '{}' and j @> '{}' $$), - ($$ j @> '{10}' $$), - ($$ j @> '{10}' and i @> '{}' $$), - ($$ j @> '{10}' and j @> '{}' and i @> '{}' $$), - ($$ i @> '{1}' and j @> '{10}' $$) - ) q(query), - lateral explain_query_json($$select * from t_gin_test_tbl where $$ || query) js, - lateral execute_text_query_index($$select string_agg((i, j)::text, ' ') from t_gin_test_tbl where $$ || query) res_index, - lateral execute_text_query_heap($$select string_agg((i, j)::text, ' ') from t_gin_test_tbl where $$ || query) res_heap; - query | return by index | removed by recheck | match --------------------------------------------+-----------------+--------------------+------- - i @> '{}' | 7 | 0 | t - j @> '{}' | 6 | 0 | t - i @> '{}' and j @> '{}' | 4 | 0 | t - i @> '{1}' | 5 | 0 | t - i @> '{1}' and j @> '{}' | 3 | 0 | t - i @> '{1}' and i @> '{}' and j @> '{}' | 3 | 0 | t - j @> '{10}' | 4 | 0 | t - j @> '{10}' and i @> '{}' | 3 | 0 | t - j @> '{10}' and j @> '{}' and i @> '{}' | 3 | 0 | t - i @> '{1}' and j @> '{10}' | 2 | 0 | t -(10 rows) - -reset enable_seqscan; -reset enable_bitmapscan; --- re-purpose t_gin_test_tbl to test scans involving posting trees -insert into t_gin_test_tbl select array[1, g, g/10], array[2, g, g/10] - from generate_series(1, 20000) g; -select gin_clean_pending_list('t_gin_test_tbl_i_j_idx') is not null; - ?column? ----------- - t -(1 row) - -analyze t_gin_test_tbl; -set enable_seqscan = off; -set enable_bitmapscan = on; -explain (costs off) -select count(*) from t_gin_test_tbl where j @> array[50]; - QUERY PLAN ---------------------------------------------------------- - Aggregate - -> Bitmap Heap Scan on t_gin_test_tbl - Recheck Cond: (j @> '{50}'::integer[]) - -> Bitmap Index Scan on t_gin_test_tbl_i_j_idx - Index Cond: (j @> '{50}'::integer[]) -(5 rows) - -select count(*) from t_gin_test_tbl where j @> array[50]; - count -------- - 11 -(1 row) - -explain (costs off) -select count(*) from t_gin_test_tbl where j @> array[2]; - QUERY PLAN ---------------------------------------------------------- - Aggregate - -> Bitmap Heap Scan on t_gin_test_tbl - Recheck Cond: (j @> '{2}'::integer[]) - -> Bitmap Index Scan on t_gin_test_tbl_i_j_idx - Index Cond: (j @> '{2}'::integer[]) -(5 rows) - -select count(*) from t_gin_test_tbl where j @> array[2]; - count -------- - 20000 -(1 row) - -explain (costs off) -select count(*) from t_gin_test_tbl where j @> '{}'::int[]; - QUERY PLAN ---------------------------------------------------------- - Aggregate - -> Bitmap Heap Scan on t_gin_test_tbl - Recheck Cond: (j @> '{}'::integer[]) - -> Bitmap Index Scan on t_gin_test_tbl_i_j_idx - Index Cond: (j @> '{}'::integer[]) -(5 rows) - -select count(*) from t_gin_test_tbl where j @> '{}'::int[]; - count -------- - 20006 -(1 row) - --- test vacuuming of posting trees -delete from t_gin_test_tbl where j @> array[2]; -vacuum t_gin_test_tbl; -select count(*) from t_gin_test_tbl where j @> array[50]; - count -------- - 0 -(1 row) - -select count(*) from t_gin_test_tbl where j @> array[2]; - count -------- - 0 -(1 row) - -select count(*) from t_gin_test_tbl where j @> '{}'::int[]; - count -------- - 6 -(1 row) - -reset enable_seqscan; -reset enable_bitmapscan; -drop table t_gin_test_tbl; --- test an unlogged table, mostly to get coverage of ginbuildempty -create unlogged table t_gin_test_tbl(i int4[], j int4[]); -create index on t_gin_test_tbl using gin (i, j); -insert into t_gin_test_tbl -values - (null, null), - ('{}', null), - ('{1}', '{2,3}'); -drop table t_gin_test_tbl; +psql: error: connection to server on socket "/tmp/pg_regress-03Az7d/.s.PGSQL.40051" failed: FATAL: the database system is not yet accepting connections +DETAIL: Consistent recovery state has not been yet reached. diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/gist.out /tmp/cirrus-ci-build/build/testrun/regress/regress/results/gist.out --- /tmp/cirrus-ci-build/src/test/regress/expected/gist.out 2024-09-20 21:41:45.690024521 +0000 +++ /tmp/cirrus-ci-build/build/testrun/regress/regress/results/gist.out 2024-09-20 21:46:06.613623540 +0000 @@ -1,403 +1,2 @@ --- --- Test GiST indexes. --- --- There are other tests to test different GiST opclasses. This is for --- testing GiST code itself. Vacuuming in particular. -create table gist_point_tbl(id int4, p point); -create index gist_pointidx on gist_point_tbl using gist(p); --- Verify the fillfactor and buffering options -create index gist_pointidx2 on gist_point_tbl using gist(p) with (buffering = on, fillfactor=50); -create index gist_pointidx3 on gist_point_tbl using gist(p) with (buffering = off); -create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = auto); -drop index gist_pointidx2, gist_pointidx3, gist_pointidx4; --- Make sure bad values are refused -create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value); -ERROR: invalid value for enum option "buffering": invalid_value -DETAIL: Valid values are "on", "off", and "auto". -create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9); -ERROR: value 9 out of bounds for option "fillfactor" -DETAIL: Valid values are between "10" and "100". -create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=101); -ERROR: value 101 out of bounds for option "fillfactor" -DETAIL: Valid values are between "10" and "100". --- Insert enough data to create a tree that's a couple of levels deep. -insert into gist_point_tbl (id, p) -select g, point(g*10, g*10) from generate_series(1, 10000) g; -insert into gist_point_tbl (id, p) -select g+100000, point(g*10+1, g*10+1) from generate_series(1, 10000) g; --- To test vacuum, delete some entries from all over the index. -delete from gist_point_tbl where id % 2 = 1; --- And also delete some concentration of values. -delete from gist_point_tbl where id > 5000; -vacuum analyze gist_point_tbl; --- rebuild the index with a different fillfactor -alter index gist_pointidx SET (fillfactor = 40); -reindex index gist_pointidx; --- --- Test Index-only plans on GiST indexes --- -create table gist_tbl (b box, p point, c circle); -insert into gist_tbl -select box(point(0.05*i, 0.05*i), point(0.05*i, 0.05*i)), - point(0.05*i, 0.05*i), - circle(point(0.05*i, 0.05*i), 1.0) -from generate_series(0,10000) as i; -vacuum analyze gist_tbl; -set enable_seqscan=off; -set enable_bitmapscan=off; -set enable_indexonlyscan=on; --- Test index-only scan with point opclass -create index gist_tbl_point_index on gist_tbl using gist (p); --- check that the planner chooses an index-only scan -explain (costs off) -select p from gist_tbl where p <@ box(point(0,0), point(0.5, 0.5)); - QUERY PLAN --------------------------------------------------------- - Index Only Scan using gist_tbl_point_index on gist_tbl - Index Cond: (p <@ '(0.5,0.5),(0,0)'::box) -(2 rows) - --- execute the same -select p from gist_tbl where p <@ box(point(0,0), point(0.5, 0.5)); - p -------------- - (0,0) - (0.05,0.05) - (0.1,0.1) - (0.15,0.15) - (0.2,0.2) - (0.25,0.25) - (0.3,0.3) - (0.35,0.35) - (0.4,0.4) - (0.45,0.45) - (0.5,0.5) -(11 rows) - --- Also test an index-only knn-search -explain (costs off) -select p from gist_tbl where p <@ box(point(0,0), point(0.5, 0.5)) -order by p <-> point(0.201, 0.201); - QUERY PLAN --------------------------------------------------------- - Index Only Scan using gist_tbl_point_index on gist_tbl - Index Cond: (p <@ '(0.5,0.5),(0,0)'::box) - Order By: (p <-> '(0.201,0.201)'::point) -(3 rows) - -select p from gist_tbl where p <@ box(point(0,0), point(0.5, 0.5)) -order by p <-> point(0.201, 0.201); - p -------------- - (0.2,0.2) - (0.25,0.25) - (0.15,0.15) - (0.3,0.3) - (0.1,0.1) - (0.35,0.35) - (0.05,0.05) - (0.4,0.4) - (0,0) - (0.45,0.45) - (0.5,0.5) -(11 rows) - --- Check commuted case as well -explain (costs off) -select p from gist_tbl where p <@ box(point(0,0), point(0.5, 0.5)) -order by point(0.101, 0.101) <-> p; - QUERY PLAN --------------------------------------------------------- - Index Only Scan using gist_tbl_point_index on gist_tbl - Index Cond: (p <@ '(0.5,0.5),(0,0)'::box) - Order By: (p <-> '(0.101,0.101)'::point) -(3 rows) - -select p from gist_tbl where p <@ box(point(0,0), point(0.5, 0.5)) -order by point(0.101, 0.101) <-> p; - p -------------- - (0.1,0.1) - (0.15,0.15) - (0.05,0.05) - (0.2,0.2) - (0,0) - (0.25,0.25) - (0.3,0.3) - (0.35,0.35) - (0.4,0.4) - (0.45,0.45) - (0.5,0.5) -(11 rows) - --- Check case with multiple rescans (bug #14641) -explain (costs off) -select p from - (values (box(point(0,0), point(0.5,0.5))), - (box(point(0.5,0.5), point(0.75,0.75))), - (box(point(0.8,0.8), point(1.0,1.0)))) as v(bb) -cross join lateral - (select p from gist_tbl where p <@ bb order by p <-> bb[0] limit 2) ss; - QUERY PLAN --------------------------------------------------------------------- - Nested Loop - -> Values Scan on "*VALUES*" - -> Limit - -> Index Only Scan using gist_tbl_point_index on gist_tbl - Index Cond: (p <@ "*VALUES*".column1) - Order By: (p <-> ("*VALUES*".column1)[0]) -(6 rows) - -select p from - (values (box(point(0,0), point(0.5,0.5))), - (box(point(0.5,0.5), point(0.75,0.75))), - (box(point(0.8,0.8), point(1.0,1.0)))) as v(bb) -cross join lateral - (select p from gist_tbl where p <@ bb order by p <-> bb[0] limit 2) ss; - p -------------- - (0.5,0.5) - (0.45,0.45) - (0.75,0.75) - (0.7,0.7) - (1,1) - (0.95,0.95) -(6 rows) - -drop index gist_tbl_point_index; --- Test index-only scan with box opclass -create index gist_tbl_box_index on gist_tbl using gist (b); --- check that the planner chooses an index-only scan -explain (costs off) -select b from gist_tbl where b <@ box(point(5,5), point(6,6)); - QUERY PLAN ------------------------------------------------------- - Index Only Scan using gist_tbl_box_index on gist_tbl - Index Cond: (b <@ '(6,6),(5,5)'::box) -(2 rows) - --- execute the same -select b from gist_tbl where b <@ box(point(5,5), point(6,6)); - b -------------------------- - (5,5),(5,5) - (5.05,5.05),(5.05,5.05) - (5.1,5.1),(5.1,5.1) - (5.15,5.15),(5.15,5.15) - (5.2,5.2),(5.2,5.2) - (5.25,5.25),(5.25,5.25) - (5.3,5.3),(5.3,5.3) - (5.35,5.35),(5.35,5.35) - (5.4,5.4),(5.4,5.4) - (5.45,5.45),(5.45,5.45) - (5.5,5.5),(5.5,5.5) - (5.55,5.55),(5.55,5.55) - (5.6,5.6),(5.6,5.6) - (5.65,5.65),(5.65,5.65) - (5.7,5.7),(5.7,5.7) - (5.75,5.75),(5.75,5.75) - (5.8,5.8),(5.8,5.8) - (5.85,5.85),(5.85,5.85) - (5.9,5.9),(5.9,5.9) - (5.95,5.95),(5.95,5.95) - (6,6),(6,6) -(21 rows) - --- Also test an index-only knn-search -explain (costs off) -select b from gist_tbl where b <@ box(point(5,5), point(6,6)) -order by b <-> point(5.2, 5.91); - QUERY PLAN ------------------------------------------------------- - Index Only Scan using gist_tbl_box_index on gist_tbl - Index Cond: (b <@ '(6,6),(5,5)'::box) - Order By: (b <-> '(5.2,5.91)'::point) -(3 rows) - -select b from gist_tbl where b <@ box(point(5,5), point(6,6)) -order by b <-> point(5.2, 5.91); - b -------------------------- - (5.55,5.55),(5.55,5.55) - (5.6,5.6),(5.6,5.6) - (5.5,5.5),(5.5,5.5) - (5.65,5.65),(5.65,5.65) - (5.45,5.45),(5.45,5.45) - (5.7,5.7),(5.7,5.7) - (5.4,5.4),(5.4,5.4) - (5.75,5.75),(5.75,5.75) - (5.35,5.35),(5.35,5.35) - (5.8,5.8),(5.8,5.8) - (5.3,5.3),(5.3,5.3) - (5.85,5.85),(5.85,5.85) - (5.25,5.25),(5.25,5.25) - (5.9,5.9),(5.9,5.9) - (5.2,5.2),(5.2,5.2) - (5.95,5.95),(5.95,5.95) - (5.15,5.15),(5.15,5.15) - (6,6),(6,6) - (5.1,5.1),(5.1,5.1) - (5.05,5.05),(5.05,5.05) - (5,5),(5,5) -(21 rows) - --- Check commuted case as well -explain (costs off) -select b from gist_tbl where b <@ box(point(5,5), point(6,6)) -order by point(5.2, 5.91) <-> b; - QUERY PLAN ------------------------------------------------------- - Index Only Scan using gist_tbl_box_index on gist_tbl - Index Cond: (b <@ '(6,6),(5,5)'::box) - Order By: (b <-> '(5.2,5.91)'::point) -(3 rows) - -select b from gist_tbl where b <@ box(point(5,5), point(6,6)) -order by point(5.2, 5.91) <-> b; - b -------------------------- - (5.55,5.55),(5.55,5.55) - (5.6,5.6),(5.6,5.6) - (5.5,5.5),(5.5,5.5) - (5.65,5.65),(5.65,5.65) - (5.45,5.45),(5.45,5.45) - (5.7,5.7),(5.7,5.7) - (5.4,5.4),(5.4,5.4) - (5.75,5.75),(5.75,5.75) - (5.35,5.35),(5.35,5.35) - (5.8,5.8),(5.8,5.8) - (5.3,5.3),(5.3,5.3) - (5.85,5.85),(5.85,5.85) - (5.25,5.25),(5.25,5.25) - (5.9,5.9),(5.9,5.9) - (5.2,5.2),(5.2,5.2) - (5.95,5.95),(5.95,5.95) - (5.15,5.15),(5.15,5.15) - (6,6),(6,6) - (5.1,5.1),(5.1,5.1) - (5.05,5.05),(5.05,5.05) - (5,5),(5,5) -(21 rows) - -drop index gist_tbl_box_index; --- Test that an index-only scan is not chosen, when the query involves the --- circle column (the circle opclass does not support index-only scans). -create index gist_tbl_multi_index on gist_tbl using gist (p, c); -explain (costs off) -select p, c from gist_tbl -where p <@ box(point(5,5), point(6, 6)); - QUERY PLAN ---------------------------------------------------- - Index Scan using gist_tbl_multi_index on gist_tbl - Index Cond: (p <@ '(6,6),(5,5)'::box) -(2 rows) - --- execute the same -select b, p from gist_tbl -where b <@ box(point(4.5, 4.5), point(5.5, 5.5)) -and p <@ box(point(5,5), point(6, 6)); - b | p --------------------------+------------- - (5,5),(5,5) | (5,5) - (5.05,5.05),(5.05,5.05) | (5.05,5.05) - (5.1,5.1),(5.1,5.1) | (5.1,5.1) - (5.15,5.15),(5.15,5.15) | (5.15,5.15) - (5.2,5.2),(5.2,5.2) | (5.2,5.2) - (5.25,5.25),(5.25,5.25) | (5.25,5.25) - (5.3,5.3),(5.3,5.3) | (5.3,5.3) - (5.35,5.35),(5.35,5.35) | (5.35,5.35) - (5.4,5.4),(5.4,5.4) | (5.4,5.4) - (5.45,5.45),(5.45,5.45) | (5.45,5.45) - (5.5,5.5),(5.5,5.5) | (5.5,5.5) -(11 rows) - -drop index gist_tbl_multi_index; --- Test that we don't try to return the value of a non-returnable --- column in an index-only scan. (This isn't GIST-specific, but --- it only applies to index AMs that can return some columns and not --- others, so GIST with appropriate opclasses is a convenient test case.) -create index gist_tbl_multi_index on gist_tbl using gist (circle(p,1), p); -explain (verbose, costs off) -select circle(p,1) from gist_tbl -where p <@ box(point(5, 5), point(5.3, 5.3)); - QUERY PLAN ---------------------------------------------------------------- - Index Only Scan using gist_tbl_multi_index on public.gist_tbl - Output: circle(p, '1'::double precision) - Index Cond: (gist_tbl.p <@ '(5.3,5.3),(5,5)'::box) -(3 rows) - -select circle(p,1) from gist_tbl -where p <@ box(point(5, 5), point(5.3, 5.3)); - circle ------------------ - <(5,5),1> - <(5.05,5.05),1> - <(5.1,5.1),1> - <(5.15,5.15),1> - <(5.2,5.2),1> - <(5.25,5.25),1> - <(5.3,5.3),1> -(7 rows) - --- Similarly, test that index rechecks involving a non-returnable column --- are done correctly. -explain (verbose, costs off) -select p from gist_tbl where circle(p,1) @> circle(point(0,0),0.95); - QUERY PLAN ---------------------------------------------------------------------------------------- - Index Only Scan using gist_tbl_multi_index on public.gist_tbl - Output: p - Index Cond: ((circle(gist_tbl.p, '1'::double precision)) @> '<(0,0),0.95>'::circle) -(3 rows) - -select p from gist_tbl where circle(p,1) @> circle(point(0,0),0.95); - p -------- - (0,0) -(1 row) - --- Also check that use_physical_tlist doesn't trigger in such cases. -explain (verbose, costs off) -select count(*) from gist_tbl; - QUERY PLAN ---------------------------------------------------------------------- - Aggregate - Output: count(*) - -> Index Only Scan using gist_tbl_multi_index on public.gist_tbl -(3 rows) - -select count(*) from gist_tbl; - count -------- - 10001 -(1 row) - --- This case isn't supported, but it should at least EXPLAIN correctly. -explain (verbose, costs off) -select p from gist_tbl order by circle(p,1) <-> point(0,0) limit 1; - QUERY PLAN ------------------------------------------------------------------------------------- - Limit - Output: p, ((circle(p, '1'::double precision) <-> '(0,0)'::point)) - -> Index Only Scan using gist_tbl_multi_index on public.gist_tbl - Output: p, (circle(p, '1'::double precision) <-> '(0,0)'::point) - Order By: ((circle(gist_tbl.p, '1'::double precision)) <-> '(0,0)'::point) -(5 rows) - -select p from gist_tbl order by circle(p,1) <-> point(0,0) limit 1; -ERROR: lossy distance functions are not supported in index-only scans --- Force an index build using buffering. -create index gist_tbl_box_index_forcing_buffering on gist_tbl using gist (p) - with (buffering=on, fillfactor=50); --- Clean up -reset enable_seqscan; -reset enable_bitmapscan; -reset enable_indexonlyscan; -drop table gist_tbl; --- test an unlogged table, mostly to get coverage of gistbuildempty -create unlogged table gist_tbl (b box); -create index gist_tbl_box_index on gist_tbl using gist (b); -insert into gist_tbl - select box(point(0.05*i, 0.05*i)) from generate_series(0,10) as i; -drop table gist_tbl; +psql: error: connection to server on socket "/tmp/pg_regress-03Az7d/.s.PGSQL.40051" failed: FATAL: the database system is not yet accepting connections +DETAIL: Consistent recovery state has not been yet reached. diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/spgist.out /tmp/cirrus-ci-build/build/testrun/regress/regress/results/spgist.out --- /tmp/cirrus-ci-build/src/test/regress/expected/spgist.out 2024-09-20 21:41:45.750024523 +0000 +++ /tmp/cirrus-ci-build/build/testrun/regress/regress/results/spgist.out 2024-09-20 21:46:06.629623519 +0000 @@ -1,96 +1,2 @@ --- --- Test SP-GiST indexes. --- --- There are other tests to test different SP-GiST opclasses. This is for --- testing SP-GiST code itself. -create table spgist_point_tbl(id int4, p point); -create index spgist_point_idx on spgist_point_tbl using spgist(p) with (fillfactor = 75); --- Test vacuum-root operation. It gets invoked when the root is also a leaf, --- i.e. the index is very small. -insert into spgist_point_tbl (id, p) -select g, point(g*10, g*10) from generate_series(1, 10) g; -delete from spgist_point_tbl where id < 5; -vacuum spgist_point_tbl; --- Insert more data, to make the index a few levels deep. -insert into spgist_point_tbl (id, p) -select g, point(g*10, g*10) from generate_series(1, 10000) g; -insert into spgist_point_tbl (id, p) -select g+100000, point(g*10+1, g*10+1) from generate_series(1, 10000) g; --- To test vacuum, delete some entries from all over the index. -delete from spgist_point_tbl where id % 2 = 1; --- And also delete some concentration of values. (SP-GiST doesn't currently --- attempt to delete pages even when they become empty, but if it did, this --- would exercise it) -delete from spgist_point_tbl where id < 10000; -vacuum spgist_point_tbl; --- Test rescan paths (cf. bug #15378) --- use box and && rather than point, so that rescan happens when the --- traverse stack is non-empty -create table spgist_box_tbl(id serial, b box); -insert into spgist_box_tbl(b) -select box(point(i,j),point(i+s,j+s)) - from generate_series(1,100,5) i, - generate_series(1,100,5) j, - generate_series(1,10) s; -create index spgist_box_idx on spgist_box_tbl using spgist (b); -select count(*) - from (values (point(5,5)),(point(8,8)),(point(12,12))) v(p) - where exists(select * from spgist_box_tbl b where b.b && box(v.p,v.p)); - count -------- - 3 -(1 row) - --- The point opclass's choose method only uses the spgMatchNode action, --- so the other actions are not tested by the above. Create an index using --- text opclass, which uses the others actions. -create table spgist_text_tbl(id int4, t text); -create index spgist_text_idx on spgist_text_tbl using spgist(t); -insert into spgist_text_tbl (id, t) -select g, 'f' || repeat('o', 100) || g from generate_series(1, 10000) g -union all -select g, 'baaaaaaaaaaaaaar' || g from generate_series(1, 1000) g; --- Do a lot of insertions that have to split an existing node. Hopefully --- one of these will cause the page to run out of space, causing the inner --- tuple to be moved to another page. -insert into spgist_text_tbl (id, t) -select -g, 'f' || repeat('o', 100-g) || 'surprise' from generate_series(1, 100) g; --- Test out-of-range fillfactor values -create index spgist_point_idx2 on spgist_point_tbl using spgist(p) with (fillfactor = 9); -ERROR: value 9 out of bounds for option "fillfactor" -DETAIL: Valid values are between "10" and "100". -create index spgist_point_idx2 on spgist_point_tbl using spgist(p) with (fillfactor = 101); -ERROR: value 101 out of bounds for option "fillfactor" -DETAIL: Valid values are between "10" and "100". --- Modify fillfactor in existing index -alter index spgist_point_idx set (fillfactor = 90); -reindex index spgist_point_idx; --- Test index over a domain -create domain spgist_text as varchar; -create table spgist_domain_tbl (f1 spgist_text); -create index spgist_domain_idx on spgist_domain_tbl using spgist(f1); -insert into spgist_domain_tbl values('fee'), ('fi'), ('fo'), ('fum'); -explain (costs off) -select * from spgist_domain_tbl where f1 = 'fo'; - QUERY PLAN ------------------------------------------------ - Bitmap Heap Scan on spgist_domain_tbl - Recheck Cond: ((f1)::text = 'fo'::text) - -> Bitmap Index Scan on spgist_domain_idx - Index Cond: ((f1)::text = 'fo'::text) -(4 rows) - -select * from spgist_domain_tbl where f1 = 'fo'; - f1 ----- - fo -(1 row) - --- test an unlogged table, mostly to get coverage of spgistbuildempty -create unlogged table spgist_unlogged_tbl(id serial, b box); -create index spgist_unlogged_idx on spgist_unlogged_tbl using spgist (b); -insert into spgist_unlogged_tbl(b) -select box(point(i,j)) - from generate_series(1,100,5) i, - generate_series(1,10,5) j; --- leave this table around, to help in testing dump/restore +psql: error: connection to server on socket "/tmp/pg_regress-03Az7d/.s.PGSQL.40051" failed: FATAL: the database system is not yet accepting connections +DETAIL: Consistent recovery state has not been yet reached. diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/privileges.out /tmp/cirrus-ci-build/build/testrun/regress/regress/results/privileges.out --- /tmp/cirrus-ci-build/src/test/regress/expected/privileges.out 2024-09-20 21:41:45.734024523 +0000 +++ /tmp/cirrus-ci-build/build/testrun/regress/regress/results/privileges.out 2024-09-20 21:46:06.617623536 +0000 @@ -1,3191 +1,2 @@ --- --- Test access privileges --- --- Clean up in case a prior regression run failed --- Suppress NOTICE messages when users/groups don't exist -SET client_min_messages TO 'warning'; -DROP ROLE IF EXISTS regress_priv_group1; -DROP ROLE IF EXISTS regress_priv_group2; -DROP ROLE IF EXISTS regress_priv_user1; -DROP ROLE IF EXISTS regress_priv_user2; -DROP ROLE IF EXISTS regress_priv_user3; -DROP ROLE IF EXISTS regress_priv_user4; -DROP ROLE IF EXISTS regress_priv_user5; -DROP ROLE IF EXISTS regress_priv_user6; -DROP ROLE IF EXISTS regress_priv_user7; -SELECT lo_unlink(oid) FROM pg_largeobject_metadata WHERE oid >= 1000 AND oid < 3000 ORDER BY oid; - lo_unlink ------------ -(0 rows) - -RESET client_min_messages; --- test proper begins here -CREATE USER regress_priv_user1; -CREATE USER regress_priv_user2; -CREATE USER regress_priv_user3; -CREATE USER regress_priv_user4; -CREATE USER regress_priv_user5; -CREATE USER regress_priv_user5; -- duplicate -ERROR: role "regress_priv_user5" already exists -CREATE USER regress_priv_user6; -CREATE USER regress_priv_user7; -CREATE USER regress_priv_user8; -CREATE USER regress_priv_user9; -CREATE USER regress_priv_user10; -CREATE ROLE regress_priv_role; --- circular ADMIN OPTION grants should be disallowed -GRANT regress_priv_user1 TO regress_priv_user2 WITH ADMIN OPTION; -GRANT regress_priv_user1 TO regress_priv_user3 WITH ADMIN OPTION GRANTED BY regress_priv_user2; -GRANT regress_priv_user1 TO regress_priv_user2 WITH ADMIN OPTION GRANTED BY regress_priv_user3; -ERROR: ADMIN option cannot be granted back to your own grantor --- need CASCADE to revoke grant or admin option if dependent grants exist -REVOKE ADMIN OPTION FOR regress_priv_user1 FROM regress_priv_user2; -- fail -ERROR: dependent privileges exist -HINT: Use CASCADE to revoke them too. -REVOKE regress_priv_user1 FROM regress_priv_user2; -- fail -ERROR: dependent privileges exist -HINT: Use CASCADE to revoke them too. -SELECT member::regrole, admin_option FROM pg_auth_members WHERE roleid = 'regress_priv_user1'::regrole; - member | admin_option ---------------------+-------------- - regress_priv_user2 | t - regress_priv_user3 | t -(2 rows) - -BEGIN; -REVOKE ADMIN OPTION FOR regress_priv_user1 FROM regress_priv_user2 CASCADE; -SELECT member::regrole, admin_option FROM pg_auth_members WHERE roleid = 'regress_priv_user1'::regrole; - member | admin_option ---------------------+-------------- - regress_priv_user2 | f -(1 row) - -ROLLBACK; -REVOKE regress_priv_user1 FROM regress_priv_user2 CASCADE; -SELECT member::regrole, admin_option FROM pg_auth_members WHERE roleid = 'regress_priv_user1'::regrole; - member | admin_option ---------+-------------- -(0 rows) - --- inferred grantor must be a role with ADMIN OPTION -GRANT regress_priv_user1 TO regress_priv_user2 WITH ADMIN OPTION; -GRANT regress_priv_user2 TO regress_priv_user3; -SET ROLE regress_priv_user3; -GRANT regress_priv_user1 TO regress_priv_user4; -SELECT grantor::regrole FROM pg_auth_members WHERE roleid = 'regress_priv_user1'::regrole and member = 'regress_priv_user4'::regrole; - grantor --------------------- - regress_priv_user2 -(1 row) - -RESET ROLE; -REVOKE regress_priv_user2 FROM regress_priv_user3; -REVOKE regress_priv_user1 FROM regress_priv_user2 CASCADE; --- test GRANTED BY with DROP OWNED and REASSIGN OWNED -GRANT regress_priv_user1 TO regress_priv_user2 WITH ADMIN OPTION; -GRANT regress_priv_user1 TO regress_priv_user3 GRANTED BY regress_priv_user2; -DROP ROLE regress_priv_user2; -- fail, dependency -ERROR: role "regress_priv_user2" cannot be dropped because some objects depend on it -DETAIL: privileges for membership of role regress_priv_user3 in role regress_priv_user1 -REASSIGN OWNED BY regress_priv_user2 TO regress_priv_user4; -DROP ROLE regress_priv_user2; -- still fail, REASSIGN OWNED doesn't help -ERROR: role "regress_priv_user2" cannot be dropped because some objects depend on it -DETAIL: privileges for membership of role regress_priv_user3 in role regress_priv_user1 -DROP OWNED BY regress_priv_user2; -DROP ROLE regress_priv_user2; -- ok now, DROP OWNED does the job --- test that removing granted role or grantee role removes dependency -GRANT regress_priv_user1 TO regress_priv_user3 WITH ADMIN OPTION; -GRANT regress_priv_user1 TO regress_priv_user4 GRANTED BY regress_priv_user3; -DROP ROLE regress_priv_user3; -- should fail, dependency -ERROR: role "regress_priv_user3" cannot be dropped because some objects depend on it -DETAIL: privileges for membership of role regress_priv_user4 in role regress_priv_user1 -DROP ROLE regress_priv_user4; -- ok -DROP ROLE regress_priv_user3; -- ok now -GRANT regress_priv_user1 TO regress_priv_user5 WITH ADMIN OPTION; -GRANT regress_priv_user1 TO regress_priv_user6 GRANTED BY regress_priv_user5; -DROP ROLE regress_priv_user5; -- should fail, dependency -ERROR: role "regress_priv_user5" cannot be dropped because some objects depend on it -DETAIL: privileges for membership of role regress_priv_user6 in role regress_priv_user1 -DROP ROLE regress_priv_user1, regress_priv_user5; -- ok, despite order --- recreate the roles we just dropped -CREATE USER regress_priv_user1; -CREATE USER regress_priv_user2; -CREATE USER regress_priv_user3; -CREATE USER regress_priv_user4; -CREATE USER regress_priv_user5; -GRANT pg_read_all_data TO regress_priv_user6; -GRANT pg_write_all_data TO regress_priv_user7; -GRANT pg_read_all_settings TO regress_priv_user8 WITH ADMIN OPTION; -GRANT regress_priv_user9 TO regress_priv_user8; -SET SESSION AUTHORIZATION regress_priv_user8; -GRANT pg_read_all_settings TO regress_priv_user9 WITH ADMIN OPTION; -SET SESSION AUTHORIZATION regress_priv_user9; -GRANT pg_read_all_settings TO regress_priv_user10; -SET SESSION AUTHORIZATION regress_priv_user8; -REVOKE pg_read_all_settings FROM regress_priv_user10 GRANTED BY regress_priv_user9; -REVOKE ADMIN OPTION FOR pg_read_all_settings FROM regress_priv_user9; -REVOKE pg_read_all_settings FROM regress_priv_user9; -RESET SESSION AUTHORIZATION; -REVOKE regress_priv_user9 FROM regress_priv_user8; -REVOKE ADMIN OPTION FOR pg_read_all_settings FROM regress_priv_user8; -SET SESSION AUTHORIZATION regress_priv_user8; -SET ROLE pg_read_all_settings; -RESET ROLE; -RESET SESSION AUTHORIZATION; -REVOKE SET OPTION FOR pg_read_all_settings FROM regress_priv_user8; -GRANT pg_read_all_stats TO regress_priv_user8 WITH SET FALSE; -SET SESSION AUTHORIZATION regress_priv_user8; -SET ROLE pg_read_all_settings; -- fail, no SET option any more -ERROR: permission denied to set role "pg_read_all_settings" -SET ROLE pg_read_all_stats; -- fail, granted without SET option -ERROR: permission denied to set role "pg_read_all_stats" -RESET ROLE; -RESET SESSION AUTHORIZATION; -REVOKE pg_read_all_settings FROM regress_priv_user8; -DROP USER regress_priv_user10; -DROP USER regress_priv_user9; -DROP USER regress_priv_user8; -CREATE GROUP regress_priv_group1; -CREATE GROUP regress_priv_group2 WITH ADMIN regress_priv_user1 USER regress_priv_user2; -ALTER GROUP regress_priv_group1 ADD USER regress_priv_user4; -GRANT regress_priv_group2 TO regress_priv_user2 GRANTED BY regress_priv_user1; -SET SESSION AUTHORIZATION regress_priv_user1; -ALTER GROUP regress_priv_group2 ADD USER regress_priv_user2; -NOTICE: role "regress_priv_user2" has already been granted membership in role "regress_priv_group2" by role "regress_priv_user1" -ALTER GROUP regress_priv_group2 ADD USER regress_priv_user2; -- duplicate -NOTICE: role "regress_priv_user2" has already been granted membership in role "regress_priv_group2" by role "regress_priv_user1" -ALTER GROUP regress_priv_group2 DROP USER regress_priv_user2; -ALTER USER regress_priv_user2 PASSWORD 'verysecret'; -- not permitted -ERROR: permission denied to alter role -DETAIL: To change another role's password, the current user must have the CREATEROLE attribute and the ADMIN option on the role. -RESET SESSION AUTHORIZATION; -ALTER GROUP regress_priv_group2 DROP USER regress_priv_user2; -REVOKE ADMIN OPTION FOR regress_priv_group2 FROM regress_priv_user1; -GRANT regress_priv_group2 TO regress_priv_user4 WITH ADMIN OPTION; --- prepare non-leakproof function for later -CREATE FUNCTION leak(integer,integer) RETURNS boolean - AS 'int4lt' - LANGUAGE internal IMMUTABLE STRICT; -- but deliberately not LEAKPROOF -ALTER FUNCTION leak(integer,integer) OWNER TO regress_priv_user1; --- test owner privileges -GRANT regress_priv_role TO regress_priv_user1 WITH ADMIN OPTION GRANTED BY regress_priv_role; -- error, doesn't have ADMIN OPTION -ERROR: permission denied to grant privileges as role "regress_priv_role" -DETAIL: The grantor must have the ADMIN option on role "regress_priv_role". -GRANT regress_priv_role TO regress_priv_user1 WITH ADMIN OPTION GRANTED BY CURRENT_ROLE; -REVOKE ADMIN OPTION FOR regress_priv_role FROM regress_priv_user1 GRANTED BY foo; -- error -ERROR: role "foo" does not exist -REVOKE ADMIN OPTION FOR regress_priv_role FROM regress_priv_user1 GRANTED BY regress_priv_user2; -- warning, noop -WARNING: role "regress_priv_user1" has not been granted membership in role "regress_priv_role" by role "regress_priv_user2" -REVOKE ADMIN OPTION FOR regress_priv_role FROM regress_priv_user1 GRANTED BY CURRENT_USER; -REVOKE regress_priv_role FROM regress_priv_user1 GRANTED BY CURRENT_ROLE; -DROP ROLE regress_priv_role; -SET SESSION AUTHORIZATION regress_priv_user1; -SELECT session_user, current_user; - session_user | current_user ---------------------+-------------------- - regress_priv_user1 | regress_priv_user1 -(1 row) - -CREATE TABLE atest1 ( a int, b text ); -SELECT * FROM atest1; - a | b ----+--- -(0 rows) - -INSERT INTO atest1 VALUES (1, 'one'); -DELETE FROM atest1; -UPDATE atest1 SET a = 1 WHERE b = 'blech'; -TRUNCATE atest1; -BEGIN; -LOCK atest1 IN ACCESS EXCLUSIVE MODE; -COMMIT; -REVOKE ALL ON atest1 FROM PUBLIC; -SELECT * FROM atest1; - a | b ----+--- -(0 rows) - -GRANT ALL ON atest1 TO regress_priv_user2; -GRANT SELECT ON atest1 TO regress_priv_user3, regress_priv_user4; -SELECT * FROM atest1; - a | b ----+--- -(0 rows) - -CREATE TABLE atest2 (col1 varchar(10), col2 boolean); -SELECT pg_get_acl('pg_class'::regclass, 'atest2'::regclass::oid, 0); - pg_get_acl ------------- - -(1 row) - -GRANT SELECT ON atest2 TO regress_priv_user2; -GRANT UPDATE ON atest2 TO regress_priv_user3; -GRANT INSERT ON atest2 TO regress_priv_user4 GRANTED BY CURRENT_USER; -GRANT TRUNCATE ON atest2 TO regress_priv_user5 GRANTED BY CURRENT_ROLE; -SELECT unnest(pg_get_acl('pg_class'::regclass, 'atest2'::regclass::oid, 0)); - unnest ------------------------------------------------- - regress_priv_user1=arwdDxtm/regress_priv_user1 - regress_priv_user2=r/regress_priv_user1 - regress_priv_user3=w/regress_priv_user1 - regress_priv_user4=a/regress_priv_user1 - regress_priv_user5=D/regress_priv_user1 -(5 rows) - --- Invalid inputs -SELECT pg_get_acl('pg_class'::regclass, 0, 0); -- null - pg_get_acl ------------- - -(1 row) - -SELECT pg_get_acl(0, 0, 0); -- null - pg_get_acl ------------- - -(1 row) - -GRANT TRUNCATE ON atest2 TO regress_priv_user4 GRANTED BY regress_priv_user5; -- error -ERROR: grantor must be current user -SET SESSION AUTHORIZATION regress_priv_user2; -SELECT session_user, current_user; - session_user | current_user ---------------------+-------------------- - regress_priv_user2 | regress_priv_user2 -(1 row) - --- try various combinations of queries on atest1 and atest2 -SELECT * FROM atest1; -- ok - a | b ----+--- -(0 rows) - -SELECT * FROM atest2; -- ok - col1 | col2 -------+------ -(0 rows) - -INSERT INTO atest1 VALUES (2, 'two'); -- ok -INSERT INTO atest2 VALUES ('foo', true); -- fail -ERROR: permission denied for table atest2 -INSERT INTO atest1 SELECT 1, b FROM atest1; -- ok -UPDATE atest1 SET a = 1 WHERE a = 2; -- ok -UPDATE atest2 SET col2 = NOT col2; -- fail -ERROR: permission denied for table atest2 -SELECT * FROM atest1 FOR UPDATE; -- ok - a | b ----+----- - 1 | two - 1 | two -(2 rows) - -SELECT * FROM atest2 FOR UPDATE; -- fail -ERROR: permission denied for table atest2 -DELETE FROM atest2; -- fail -ERROR: permission denied for table atest2 -TRUNCATE atest2; -- fail -ERROR: permission denied for table atest2 -BEGIN; -LOCK atest2 IN ACCESS EXCLUSIVE MODE; -- fail -ERROR: permission denied for table atest2 -COMMIT; -COPY atest2 FROM stdin; -- fail -ERROR: permission denied for table atest2 -GRANT ALL ON atest1 TO PUBLIC; -- fail -WARNING: no privileges were granted for "atest1" --- checks in subquery, both ok -SELECT * FROM atest1 WHERE ( b IN ( SELECT col1 FROM atest2 ) ); - a | b ----+--- -(0 rows) - -SELECT * FROM atest2 WHERE ( col1 IN ( SELECT b FROM atest1 ) ); - col1 | col2 -------+------ -(0 rows) - -SET SESSION AUTHORIZATION regress_priv_user6; -SELECT * FROM atest1; -- ok - a | b ----+----- - 1 | two - 1 | two -(2 rows) - -SELECT * FROM atest2; -- ok - col1 | col2 -------+------ -(0 rows) - -INSERT INTO atest2 VALUES ('foo', true); -- fail -ERROR: permission denied for table atest2 -SET SESSION AUTHORIZATION regress_priv_user7; -SELECT * FROM atest1; -- fail -ERROR: permission denied for table atest1 -SELECT * FROM atest2; -- fail -ERROR: permission denied for table atest2 -INSERT INTO atest2 VALUES ('foo', true); -- ok -UPDATE atest2 SET col2 = true; -- ok -DELETE FROM atest2; -- ok --- Make sure we are not able to modify system catalogs -UPDATE pg_catalog.pg_class SET relname = '123'; -- fail -ERROR: permission denied for table pg_class -DELETE FROM pg_catalog.pg_class; -- fail -ERROR: permission denied for table pg_class -UPDATE pg_toast.pg_toast_1213 SET chunk_id = 1; -- fail -ERROR: permission denied for table pg_toast_1213 -SET SESSION AUTHORIZATION regress_priv_user3; -SELECT session_user, current_user; - session_user | current_user ---------------------+-------------------- - regress_priv_user3 | regress_priv_user3 -(1 row) - -SELECT * FROM atest1; -- ok - a | b ----+----- - 1 | two - 1 | two -(2 rows) - -SELECT * FROM atest2; -- fail -ERROR: permission denied for table atest2 -INSERT INTO atest1 VALUES (2, 'two'); -- fail -ERROR: permission denied for table atest1 -INSERT INTO atest2 VALUES ('foo', true); -- fail -ERROR: permission denied for table atest2 -INSERT INTO atest1 SELECT 1, b FROM atest1; -- fail -ERROR: permission denied for table atest1 -UPDATE atest1 SET a = 1 WHERE a = 2; -- fail -ERROR: permission denied for table atest1 -UPDATE atest2 SET col2 = NULL; -- ok -UPDATE atest2 SET col2 = NOT col2; -- fails; requires SELECT on atest2 -ERROR: permission denied for table atest2 -UPDATE atest2 SET col2 = true FROM atest1 WHERE atest1.a = 5; -- ok -SELECT * FROM atest1 FOR UPDATE; -- fail -ERROR: permission denied for table atest1 -SELECT * FROM atest2 FOR UPDATE; -- fail -ERROR: permission denied for table atest2 -DELETE FROM atest2; -- fail -ERROR: permission denied for table atest2 -TRUNCATE atest2; -- fail -ERROR: permission denied for table atest2 -BEGIN; -LOCK atest2 IN ACCESS EXCLUSIVE MODE; -- ok -COMMIT; -COPY atest2 FROM stdin; -- fail -ERROR: permission denied for table atest2 --- checks in subquery, both fail -SELECT * FROM atest1 WHERE ( b IN ( SELECT col1 FROM atest2 ) ); -ERROR: permission denied for table atest2 -SELECT * FROM atest2 WHERE ( col1 IN ( SELECT b FROM atest1 ) ); -ERROR: permission denied for table atest2 -SET SESSION AUTHORIZATION regress_priv_user4; -COPY atest2 FROM stdin; -- ok -SELECT * FROM atest1; -- ok - a | b ----+----- - 1 | two - 1 | two -(2 rows) - --- test leaky-function protections in selfuncs --- regress_priv_user1 will own a table and provide views for it. -SET SESSION AUTHORIZATION regress_priv_user1; -CREATE TABLE atest12 as - SELECT x AS a, 10001 - x AS b FROM generate_series(1,10000) x; -CREATE INDEX ON atest12 (a); -CREATE INDEX ON atest12 (abs(a)); --- results below depend on having quite accurate stats for atest12, so... -ALTER TABLE atest12 SET (autovacuum_enabled = off); -SET default_statistics_target = 10000; -VACUUM ANALYZE atest12; -RESET default_statistics_target; -CREATE OPERATOR <<< (procedure = leak, leftarg = integer, rightarg = integer, - restrict = scalarltsel); --- views with leaky operator -CREATE VIEW atest12v AS - SELECT * FROM atest12 WHERE b <<< 5; -CREATE VIEW atest12sbv WITH (security_barrier=true) AS - SELECT * FROM atest12 WHERE b <<< 5; -GRANT SELECT ON atest12v TO PUBLIC; -GRANT SELECT ON atest12sbv TO PUBLIC; --- This plan should use nestloop, knowing that few rows will be selected. -EXPLAIN (COSTS OFF) SELECT * FROM atest12v x, atest12v y WHERE x.a = y.b; - QUERY PLAN -------------------------------------------------- - Nested Loop - -> Seq Scan on atest12 atest12_1 - Filter: (b <<< 5) - -> Index Scan using atest12_a_idx on atest12 - Index Cond: (a = atest12_1.b) - Filter: (b <<< 5) -(6 rows) - --- And this one. -EXPLAIN (COSTS OFF) SELECT * FROM atest12 x, atest12 y - WHERE x.a = y.b and abs(y.a) <<< 5; - QUERY PLAN ---------------------------------------------------- - Nested Loop - -> Seq Scan on atest12 y - Filter: (abs(a) <<< 5) - -> Index Scan using atest12_a_idx on atest12 x - Index Cond: (a = y.b) -(5 rows) - --- This should also be a nestloop, but the security barrier forces the inner --- scan to be materialized -EXPLAIN (COSTS OFF) SELECT * FROM atest12sbv x, atest12sbv y WHERE x.a = y.b; - QUERY PLAN -------------------------------------------- - Nested Loop - Join Filter: (atest12.a = atest12_1.b) - -> Seq Scan on atest12 - Filter: (b <<< 5) - -> Materialize - -> Seq Scan on atest12 atest12_1 - Filter: (b <<< 5) -(7 rows) - --- Check if regress_priv_user2 can break security. -SET SESSION AUTHORIZATION regress_priv_user2; -CREATE FUNCTION leak2(integer,integer) RETURNS boolean - AS $$begin raise notice 'leak % %', $1, $2; return $1 > $2; end$$ - LANGUAGE plpgsql immutable; -CREATE OPERATOR >>> (procedure = leak2, leftarg = integer, rightarg = integer, - restrict = scalargtsel); --- This should not show any "leak" notices before failing. -EXPLAIN (COSTS OFF) SELECT * FROM atest12 WHERE a >>> 0; -ERROR: permission denied for table atest12 --- These plans should continue to use a nestloop, since they execute with the --- privileges of the view owner. -EXPLAIN (COSTS OFF) SELECT * FROM atest12v x, atest12v y WHERE x.a = y.b; - QUERY PLAN -------------------------------------------------- - Nested Loop - -> Seq Scan on atest12 atest12_1 - Filter: (b <<< 5) - -> Index Scan using atest12_a_idx on atest12 - Index Cond: (a = atest12_1.b) - Filter: (b <<< 5) -(6 rows) - -EXPLAIN (COSTS OFF) SELECT * FROM atest12sbv x, atest12sbv y WHERE x.a = y.b; - QUERY PLAN -------------------------------------------- - Nested Loop - Join Filter: (atest12.a = atest12_1.b) - -> Seq Scan on atest12 - Filter: (b <<< 5) - -> Materialize - -> Seq Scan on atest12 atest12_1 - Filter: (b <<< 5) -(7 rows) - --- A non-security barrier view does not guard against information leakage. -EXPLAIN (COSTS OFF) SELECT * FROM atest12v x, atest12v y - WHERE x.a = y.b and abs(y.a) <<< 5; - QUERY PLAN -------------------------------------------------- - Nested Loop - -> Seq Scan on atest12 atest12_1 - Filter: ((b <<< 5) AND (abs(a) <<< 5)) - -> Index Scan using atest12_a_idx on atest12 - Index Cond: (a = atest12_1.b) - Filter: (b <<< 5) -(6 rows) - --- But a security barrier view isolates the leaky operator. -EXPLAIN (COSTS OFF) SELECT * FROM atest12sbv x, atest12sbv y - WHERE x.a = y.b and abs(y.a) <<< 5; - QUERY PLAN -------------------------------------- - Nested Loop - Join Filter: (atest12_1.a = y.b) - -> Subquery Scan on y - Filter: (abs(y.a) <<< 5) - -> Seq Scan on atest12 - Filter: (b <<< 5) - -> Seq Scan on atest12 atest12_1 - Filter: (b <<< 5) -(8 rows) - --- Now regress_priv_user1 grants sufficient access to regress_priv_user2. -SET SESSION AUTHORIZATION regress_priv_user1; -GRANT SELECT (a, b) ON atest12 TO PUBLIC; -SET SESSION AUTHORIZATION regress_priv_user2; --- regress_priv_user2 should continue to get a good row estimate. -EXPLAIN (COSTS OFF) SELECT * FROM atest12v x, atest12v y WHERE x.a = y.b; - QUERY PLAN -------------------------------------------------- - Nested Loop - -> Seq Scan on atest12 atest12_1 - Filter: (b <<< 5) - -> Index Scan using atest12_a_idx on atest12 - Index Cond: (a = atest12_1.b) - Filter: (b <<< 5) -(6 rows) - --- But not for this, due to lack of table-wide permissions needed --- to make use of the expression index's statistics. -EXPLAIN (COSTS OFF) SELECT * FROM atest12 x, atest12 y - WHERE x.a = y.b and abs(y.a) <<< 5; - QUERY PLAN --------------------------------------- - Hash Join - Hash Cond: (x.a = y.b) - -> Seq Scan on atest12 x - -> Hash - -> Seq Scan on atest12 y - Filter: (abs(a) <<< 5) -(6 rows) - --- clean up (regress_priv_user1's objects are all dropped later) -DROP FUNCTION leak2(integer, integer) CASCADE; -NOTICE: drop cascades to operator >>>(integer,integer) --- groups -SET SESSION AUTHORIZATION regress_priv_user3; -CREATE TABLE atest3 (one int, two int, three int); -GRANT DELETE ON atest3 TO GROUP regress_priv_group2; -SET SESSION AUTHORIZATION regress_priv_user1; -SELECT * FROM atest3; -- fail -ERROR: permission denied for table atest3 -DELETE FROM atest3; -- ok -BEGIN; -RESET SESSION AUTHORIZATION; -ALTER ROLE regress_priv_user1 NOINHERIT; -SET SESSION AUTHORIZATION regress_priv_user1; -SAVEPOINT s1; -DELETE FROM atest3; -- ok because grant-level option is unchanged -ROLLBACK TO s1; -RESET SESSION AUTHORIZATION; -GRANT regress_priv_group2 TO regress_priv_user1 WITH INHERIT FALSE; -SET SESSION AUTHORIZATION regress_priv_user1; -DELETE FROM atest3; -- fail -ERROR: permission denied for table atest3 -ROLLBACK TO s1; -RESET SESSION AUTHORIZATION; -REVOKE INHERIT OPTION FOR regress_priv_group2 FROM regress_priv_user1; -SET SESSION AUTHORIZATION regress_priv_user1; -DELETE FROM atest3; -- also fail -ERROR: permission denied for table atest3 -ROLLBACK; --- views -SET SESSION AUTHORIZATION regress_priv_user3; -CREATE VIEW atestv1 AS SELECT * FROM atest1; -- ok -/* The next *should* fail, but it's not implemented that way yet. */ -CREATE VIEW atestv2 AS SELECT * FROM atest2; -CREATE VIEW atestv3 AS SELECT * FROM atest3; -- ok -/* Empty view is a corner case that failed in 9.2. */ -CREATE VIEW atestv0 AS SELECT 0 as x WHERE false; -- ok -SELECT * FROM atestv1; -- ok - a | b ----+----- - 1 | two - 1 | two -(2 rows) - -SELECT * FROM atestv2; -- fail -ERROR: permission denied for table atest2 -GRANT SELECT ON atestv1, atestv3 TO regress_priv_user4; -GRANT SELECT ON atestv2 TO regress_priv_user2; -SET SESSION AUTHORIZATION regress_priv_user4; -SELECT * FROM atestv1; -- ok - a | b ----+----- - 1 | two - 1 | two -(2 rows) - -SELECT * FROM atestv2; -- fail -ERROR: permission denied for view atestv2 -SELECT * FROM atestv3; -- ok - one | two | three ------+-----+------- -(0 rows) - -SELECT * FROM atestv0; -- fail -ERROR: permission denied for view atestv0 --- Appendrels excluded by constraints failed to check permissions in 8.4-9.2. -select * from - ((select a.q1 as x from int8_tbl a offset 0) - union all - (select b.q2 as x from int8_tbl b offset 0)) ss -where false; -ERROR: permission denied for table int8_tbl -set constraint_exclusion = on; -select * from - ((select a.q1 as x, random() from int8_tbl a where q1 > 0) - union all - (select b.q2 as x, random() from int8_tbl b where q2 > 0)) ss -where x < 0; -ERROR: permission denied for table int8_tbl -reset constraint_exclusion; -CREATE VIEW atestv4 AS SELECT * FROM atestv3; -- nested view -SELECT * FROM atestv4; -- ok - one | two | three ------+-----+------- -(0 rows) - -GRANT SELECT ON atestv4 TO regress_priv_user2; -SET SESSION AUTHORIZATION regress_priv_user2; --- Two complex cases: -SELECT * FROM atestv3; -- fail -ERROR: permission denied for view atestv3 -SELECT * FROM atestv4; -- ok (even though regress_priv_user2 cannot access underlying atestv3) - one | two | three ------+-----+------- -(0 rows) - -SELECT * FROM atest2; -- ok - col1 | col2 -------+------ - bar | t -(1 row) - -SELECT * FROM atestv2; -- fail (even though regress_priv_user2 can access underlying atest2) -ERROR: permission denied for table atest2 --- Test column level permissions -SET SESSION AUTHORIZATION regress_priv_user1; -CREATE TABLE atest5 (one int, two int unique, three int, four int unique); -CREATE TABLE atest6 (one int, two int, blue int); -GRANT SELECT (one), INSERT (two), UPDATE (three) ON atest5 TO regress_priv_user4; -GRANT ALL (one) ON atest5 TO regress_priv_user3; -SELECT unnest(pg_get_acl('pg_class'::regclass, 'atest5'::regclass::oid, 1)); - unnest --------------------------------------------- - regress_priv_user4=r/regress_priv_user1 - regress_priv_user3=arwx/regress_priv_user1 -(2 rows) - -SELECT unnest(pg_get_acl('pg_class'::regclass, 'atest5'::regclass::oid, 2)); - unnest ------------------------------------------ - regress_priv_user4=a/regress_priv_user1 -(1 row) - -SELECT unnest(pg_get_acl('pg_class'::regclass, 'atest5'::regclass::oid, 3)); - unnest ------------------------------------------ - regress_priv_user4=w/regress_priv_user1 -(1 row) - -SELECT unnest(pg_get_acl('pg_class'::regclass, 'atest5'::regclass::oid, 4)); - unnest --------- -(0 rows) - -INSERT INTO atest5 VALUES (1,2,3); -SET SESSION AUTHORIZATION regress_priv_user4; -SELECT * FROM atest5; -- fail -ERROR: permission denied for table atest5 -SELECT one FROM atest5; -- ok - one ------ - 1 -(1 row) - -COPY atest5 (one) TO stdout; -- ok -1 -SELECT two FROM atest5; -- fail -ERROR: permission denied for table atest5 -COPY atest5 (two) TO stdout; -- fail -ERROR: permission denied for table atest5 -SELECT atest5 FROM atest5; -- fail -ERROR: permission denied for table atest5 -COPY atest5 (one,two) TO stdout; -- fail -ERROR: permission denied for table atest5 -SELECT 1 FROM atest5; -- ok - ?column? ----------- - 1 -(1 row) - -SELECT 1 FROM atest5 a JOIN atest5 b USING (one); -- ok - ?column? ----------- - 1 -(1 row) - -SELECT 1 FROM atest5 a JOIN atest5 b USING (two); -- fail -ERROR: permission denied for table atest5 -SELECT 1 FROM atest5 a NATURAL JOIN atest5 b; -- fail -ERROR: permission denied for table atest5 -SELECT * FROM (atest5 a JOIN atest5 b USING (one)) j; -- fail -ERROR: permission denied for table atest5 -SELECT j.* FROM (atest5 a JOIN atest5 b USING (one)) j; -- fail -ERROR: permission denied for table atest5 -SELECT (j.*) IS NULL FROM (atest5 a JOIN atest5 b USING (one)) j; -- fail -ERROR: permission denied for table atest5 -SELECT one FROM (atest5 a JOIN atest5 b(one,x,y,z) USING (one)) j; -- ok - one ------ - 1 -(1 row) - -SELECT j.one FROM (atest5 a JOIN atest5 b(one,x,y,z) USING (one)) j; -- ok - one ------ - 1 -(1 row) - -SELECT two FROM (atest5 a JOIN atest5 b(one,x,y,z) USING (one)) j; -- fail -ERROR: permission denied for table atest5 -SELECT j.two FROM (atest5 a JOIN atest5 b(one,x,y,z) USING (one)) j; -- fail -ERROR: permission denied for table atest5 -SELECT y FROM (atest5 a JOIN atest5 b(one,x,y,z) USING (one)) j; -- fail -ERROR: permission denied for table atest5 -SELECT j.y FROM (atest5 a JOIN atest5 b(one,x,y,z) USING (one)) j; -- fail -ERROR: permission denied for table atest5 -SELECT * FROM (atest5 a JOIN atest5 b USING (one)); -- fail -ERROR: permission denied for table atest5 -SELECT a.* FROM (atest5 a JOIN atest5 b USING (one)); -- fail -ERROR: permission denied for table atest5 -SELECT (a.*) IS NULL FROM (atest5 a JOIN atest5 b USING (one)); -- fail -ERROR: permission denied for table atest5 -SELECT two FROM (atest5 a JOIN atest5 b(one,x,y,z) USING (one)); -- fail -ERROR: permission denied for table atest5 -SELECT a.two FROM (atest5 a JOIN atest5 b(one,x,y,z) USING (one)); -- fail -ERROR: permission denied for table atest5 -SELECT y FROM (atest5 a JOIN atest5 b(one,x,y,z) USING (one)); -- fail -ERROR: permission denied for table atest5 -SELECT b.y FROM (atest5 a JOIN atest5 b(one,x,y,z) USING (one)); -- fail -ERROR: permission denied for table atest5 -SELECT y FROM (atest5 a LEFT JOIN atest5 b(one,x,y,z) USING (one)); -- fail -ERROR: permission denied for table atest5 -SELECT b.y FROM (atest5 a LEFT JOIN atest5 b(one,x,y,z) USING (one)); -- fail -ERROR: permission denied for table atest5 -SELECT y FROM (atest5 a FULL JOIN atest5 b(one,x,y,z) USING (one)); -- fail -ERROR: permission denied for table atest5 -SELECT b.y FROM (atest5 a FULL JOIN atest5 b(one,x,y,z) USING (one)); -- fail -ERROR: permission denied for table atest5 -SELECT 1 FROM atest5 WHERE two = 2; -- fail -ERROR: permission denied for table atest5 -SELECT * FROM atest1, atest5; -- fail -ERROR: permission denied for table atest5 -SELECT atest1.* FROM atest1, atest5; -- ok - a | b ----+----- - 1 | two - 1 | two -(2 rows) - -SELECT atest1.*,atest5.one FROM atest1, atest5; -- ok - a | b | one ----+-----+----- - 1 | two | 1 - 1 | two | 1 -(2 rows) - -SELECT atest1.*,atest5.one FROM atest1 JOIN atest5 ON (atest1.a = atest5.two); -- fail -ERROR: permission denied for table atest5 -SELECT atest1.*,atest5.one FROM atest1 JOIN atest5 ON (atest1.a = atest5.one); -- ok - a | b | one ----+-----+----- - 1 | two | 1 - 1 | two | 1 -(2 rows) - -SELECT one, two FROM atest5; -- fail -ERROR: permission denied for table atest5 -SET SESSION AUTHORIZATION regress_priv_user1; -GRANT SELECT (one,two) ON atest6 TO regress_priv_user4; -SET SESSION AUTHORIZATION regress_priv_user4; -SELECT one, two FROM atest5 NATURAL JOIN atest6; -- fail still -ERROR: permission denied for table atest5 -SET SESSION AUTHORIZATION regress_priv_user1; -GRANT SELECT (two) ON atest5 TO regress_priv_user4; -SET SESSION AUTHORIZATION regress_priv_user4; -SELECT one, two FROM atest5 NATURAL JOIN atest6; -- ok now - one | two ------+----- -(0 rows) - --- test column-level privileges for INSERT and UPDATE -INSERT INTO atest5 (two) VALUES (3); -- ok -COPY atest5 FROM stdin; -- fail -ERROR: permission denied for table atest5 -COPY atest5 (two) FROM stdin; -- ok -INSERT INTO atest5 (three) VALUES (4); -- fail -ERROR: permission denied for table atest5 -INSERT INTO atest5 VALUES (5,5,5); -- fail -ERROR: permission denied for table atest5 -UPDATE atest5 SET three = 10; -- ok -UPDATE atest5 SET one = 8; -- fail -ERROR: permission denied for table atest5 -UPDATE atest5 SET three = 5, one = 2; -- fail -ERROR: permission denied for table atest5 --- Check that column level privs are enforced in RETURNING --- Ok. -INSERT INTO atest5(two) VALUES (6) ON CONFLICT (two) DO UPDATE set three = 10; --- Error. No SELECT on column three. -INSERT INTO atest5(two) VALUES (6) ON CONFLICT (two) DO UPDATE set three = 10 RETURNING atest5.three; -ERROR: permission denied for table atest5 --- Ok. May SELECT on column "one": -INSERT INTO atest5(two) VALUES (6) ON CONFLICT (two) DO UPDATE set three = 10 RETURNING atest5.one; - one ------ - -(1 row) - --- Check that column level privileges are enforced for EXCLUDED --- Ok. we may select one -INSERT INTO atest5(two) VALUES (6) ON CONFLICT (two) DO UPDATE set three = EXCLUDED.one; --- Error. No select rights on three -INSERT INTO atest5(two) VALUES (6) ON CONFLICT (two) DO UPDATE set three = EXCLUDED.three; -ERROR: permission denied for table atest5 -INSERT INTO atest5(two) VALUES (6) ON CONFLICT (two) DO UPDATE set one = 8; -- fails (due to UPDATE) -ERROR: permission denied for table atest5 -INSERT INTO atest5(three) VALUES (4) ON CONFLICT (two) DO UPDATE set three = 10; -- fails (due to INSERT) -ERROR: permission denied for table atest5 --- Check that the columns in the inference require select privileges -INSERT INTO atest5(four) VALUES (4); -- fail -ERROR: permission denied for table atest5 -SET SESSION AUTHORIZATION regress_priv_user1; -GRANT INSERT (four) ON atest5 TO regress_priv_user4; -SET SESSION AUTHORIZATION regress_priv_user4; -INSERT INTO atest5(four) VALUES (4) ON CONFLICT (four) DO UPDATE set three = 3; -- fails (due to SELECT) -ERROR: permission denied for table atest5 -INSERT INTO atest5(four) VALUES (4) ON CONFLICT ON CONSTRAINT atest5_four_key DO UPDATE set three = 3; -- fails (due to SELECT) -ERROR: permission denied for table atest5 -INSERT INTO atest5(four) VALUES (4); -- ok -SET SESSION AUTHORIZATION regress_priv_user1; -GRANT SELECT (four) ON atest5 TO regress_priv_user4; -SET SESSION AUTHORIZATION regress_priv_user4; -INSERT INTO atest5(four) VALUES (4) ON CONFLICT (four) DO UPDATE set three = 3; -- ok -INSERT INTO atest5(four) VALUES (4) ON CONFLICT ON CONSTRAINT atest5_four_key DO UPDATE set three = 3; -- ok -SET SESSION AUTHORIZATION regress_priv_user1; -REVOKE ALL (one) ON atest5 FROM regress_priv_user4; -GRANT SELECT (one,two,blue) ON atest6 TO regress_priv_user4; -SET SESSION AUTHORIZATION regress_priv_user4; -SELECT one FROM atest5; -- fail -ERROR: permission denied for table atest5 -UPDATE atest5 SET one = 1; -- fail -ERROR: permission denied for table atest5 -SELECT atest6 FROM atest6; -- ok - atest6 --------- -(0 rows) - -COPY atest6 TO stdout; -- ok --- test column privileges with MERGE -SET SESSION AUTHORIZATION regress_priv_user1; -CREATE TABLE mtarget (a int, b text); -CREATE TABLE msource (a int, b text); -INSERT INTO mtarget VALUES (1, 'init1'), (2, 'init2'); -INSERT INTO msource VALUES (1, 'source1'), (2, 'source2'), (3, 'source3'); -GRANT SELECT (a) ON msource TO regress_priv_user4; -GRANT SELECT (a) ON mtarget TO regress_priv_user4; -GRANT INSERT (a,b) ON mtarget TO regress_priv_user4; -GRANT UPDATE (b) ON mtarget TO regress_priv_user4; -SET SESSION AUTHORIZATION regress_priv_user4; --- --- test source privileges --- --- fail (no SELECT priv on s.b) -MERGE INTO mtarget t USING msource s ON t.a = s.a -WHEN MATCHED THEN - UPDATE SET b = s.b -WHEN NOT MATCHED THEN - INSERT VALUES (a, NULL); -ERROR: permission denied for table msource --- fail (s.b used in the INSERTed values) -MERGE INTO mtarget t USING msource s ON t.a = s.a -WHEN MATCHED THEN - UPDATE SET b = 'x' -WHEN NOT MATCHED THEN - INSERT VALUES (a, b); -ERROR: permission denied for table msource --- fail (s.b used in the WHEN quals) -MERGE INTO mtarget t USING msource s ON t.a = s.a -WHEN MATCHED AND s.b = 'x' THEN - UPDATE SET b = 'x' -WHEN NOT MATCHED THEN - INSERT VALUES (a, NULL); -ERROR: permission denied for table msource --- this should be ok since only s.a is accessed -BEGIN; -MERGE INTO mtarget t USING msource s ON t.a = s.a -WHEN MATCHED THEN - UPDATE SET b = 'ok' -WHEN NOT MATCHED THEN - INSERT VALUES (a, NULL); -ROLLBACK; -SET SESSION AUTHORIZATION regress_priv_user1; -GRANT SELECT (b) ON msource TO regress_priv_user4; -SET SESSION AUTHORIZATION regress_priv_user4; --- should now be ok -BEGIN; -MERGE INTO mtarget t USING msource s ON t.a = s.a -WHEN MATCHED THEN - UPDATE SET b = s.b -WHEN NOT MATCHED THEN - INSERT VALUES (a, b); -ROLLBACK; --- --- test target privileges --- --- fail (no SELECT priv on t.b) -MERGE INTO mtarget t USING msource s ON t.a = s.a -WHEN MATCHED THEN - UPDATE SET b = t.b -WHEN NOT MATCHED THEN - INSERT VALUES (a, NULL); -ERROR: permission denied for table mtarget --- fail (no UPDATE on t.a) -MERGE INTO mtarget t USING msource s ON t.a = s.a -WHEN MATCHED THEN - UPDATE SET b = s.b, a = t.a + 1 -WHEN NOT MATCHED THEN - INSERT VALUES (a, b); -ERROR: permission denied for table mtarget --- fail (no SELECT on t.b) -MERGE INTO mtarget t USING msource s ON t.a = s.a -WHEN MATCHED AND t.b IS NOT NULL THEN - UPDATE SET b = s.b -WHEN NOT MATCHED THEN - INSERT VALUES (a, b); -ERROR: permission denied for table mtarget --- ok -BEGIN; -MERGE INTO mtarget t USING msource s ON t.a = s.a -WHEN MATCHED THEN - UPDATE SET b = s.b; -ROLLBACK; --- fail (no DELETE) -MERGE INTO mtarget t USING msource s ON t.a = s.a -WHEN MATCHED AND t.b IS NOT NULL THEN - DELETE; -ERROR: permission denied for table mtarget --- grant delete privileges -SET SESSION AUTHORIZATION regress_priv_user1; -GRANT DELETE ON mtarget TO regress_priv_user4; --- should be ok now -BEGIN; -MERGE INTO mtarget t USING msource s ON t.a = s.a -WHEN MATCHED AND t.b IS NOT NULL THEN - DELETE; -ROLLBACK; --- check error reporting with column privs -SET SESSION AUTHORIZATION regress_priv_user1; -CREATE TABLE t1 (c1 int, c2 int, c3 int check (c3 < 5), primary key (c1, c2)); -GRANT SELECT (c1) ON t1 TO regress_priv_user2; -GRANT INSERT (c1, c2, c3) ON t1 TO regress_priv_user2; -GRANT UPDATE (c1, c2, c3) ON t1 TO regress_priv_user2; --- seed data -INSERT INTO t1 VALUES (1, 1, 1); -INSERT INTO t1 VALUES (1, 2, 1); -INSERT INTO t1 VALUES (2, 1, 2); -INSERT INTO t1 VALUES (2, 2, 2); -INSERT INTO t1 VALUES (3, 1, 3); -SET SESSION AUTHORIZATION regress_priv_user2; -INSERT INTO t1 (c1, c2) VALUES (1, 1); -- fail, but row not shown -ERROR: duplicate key value violates unique constraint "t1_pkey" -UPDATE t1 SET c2 = 1; -- fail, but row not shown -ERROR: duplicate key value violates unique constraint "t1_pkey" -INSERT INTO t1 (c1, c2) VALUES (null, null); -- fail, but see columns being inserted -ERROR: null value in column "c1" of relation "t1" violates not-null constraint -DETAIL: Failing row contains (c1, c2) = (null, null). -INSERT INTO t1 (c3) VALUES (null); -- fail, but see columns being inserted or have SELECT -ERROR: null value in column "c1" of relation "t1" violates not-null constraint -DETAIL: Failing row contains (c1, c3) = (null, null). -INSERT INTO t1 (c1) VALUES (5); -- fail, but see columns being inserted or have SELECT -ERROR: null value in column "c2" of relation "t1" violates not-null constraint -DETAIL: Failing row contains (c1) = (5). -UPDATE t1 SET c3 = 10; -- fail, but see columns with SELECT rights, or being modified -ERROR: new row for relation "t1" violates check constraint "t1_c3_check" -DETAIL: Failing row contains (c1, c3) = (1, 10). -SET SESSION AUTHORIZATION regress_priv_user1; -DROP TABLE t1; --- check error reporting with column privs on a partitioned table -CREATE TABLE errtst(a text, b text NOT NULL, c text, secret1 text, secret2 text) PARTITION BY LIST (a); -CREATE TABLE errtst_part_1(secret2 text, c text, a text, b text NOT NULL, secret1 text); -CREATE TABLE errtst_part_2(secret1 text, secret2 text, a text, c text, b text NOT NULL); -ALTER TABLE errtst ATTACH PARTITION errtst_part_1 FOR VALUES IN ('aaa'); -ALTER TABLE errtst ATTACH PARTITION errtst_part_2 FOR VALUES IN ('aaaa'); -GRANT SELECT (a, b, c) ON TABLE errtst TO regress_priv_user2; -GRANT UPDATE (a, b, c) ON TABLE errtst TO regress_priv_user2; -GRANT INSERT (a, b, c) ON TABLE errtst TO regress_priv_user2; -INSERT INTO errtst_part_1 (a, b, c, secret1, secret2) -VALUES ('aaa', 'bbb', 'ccc', 'the body', 'is in the attic'); -SET SESSION AUTHORIZATION regress_priv_user2; --- Perform a few updates that violate the NOT NULL constraint. Make sure --- the error messages don't leak the secret fields. --- simple insert. -INSERT INTO errtst (a, b) VALUES ('aaa', NULL); -ERROR: null value in column "b" of relation "errtst_part_1" violates not-null constraint -DETAIL: Failing row contains (a, b, c) = (aaa, null, null). --- simple update. -UPDATE errtst SET b = NULL; -ERROR: null value in column "b" of relation "errtst_part_1" violates not-null constraint -DETAIL: Failing row contains (a, b, c) = (aaa, null, ccc). --- partitioning key is updated, doesn't move the row. -UPDATE errtst SET a = 'aaa', b = NULL; -ERROR: null value in column "b" of relation "errtst_part_1" violates not-null constraint -DETAIL: Failing row contains (a, b, c) = (aaa, null, ccc). --- row is moved to another partition. -UPDATE errtst SET a = 'aaaa', b = NULL; -ERROR: null value in column "b" of relation "errtst_part_2" violates not-null constraint -DETAIL: Failing row contains (a, b, c) = (aaaa, null, ccc). --- row is moved to another partition. This differs from the previous case in --- that the new partition is excluded by constraint exclusion, so its --- ResultRelInfo is not created at ExecInitModifyTable, but needs to be --- constructed on the fly when the updated tuple is routed to it. -UPDATE errtst SET a = 'aaaa', b = NULL WHERE a = 'aaa'; -ERROR: null value in column "b" of relation "errtst_part_2" violates not-null constraint -DETAIL: Failing row contains (a, b, c) = (aaaa, null, ccc). -SET SESSION AUTHORIZATION regress_priv_user1; -DROP TABLE errtst; --- test column-level privileges when involved with DELETE -SET SESSION AUTHORIZATION regress_priv_user1; -ALTER TABLE atest6 ADD COLUMN three integer; -GRANT DELETE ON atest5 TO regress_priv_user3; -GRANT SELECT (two) ON atest5 TO regress_priv_user3; -REVOKE ALL (one) ON atest5 FROM regress_priv_user3; -GRANT SELECT (one) ON atest5 TO regress_priv_user4; -SET SESSION AUTHORIZATION regress_priv_user4; -SELECT atest6 FROM atest6; -- fail -ERROR: permission denied for table atest6 -SELECT one FROM atest5 NATURAL JOIN atest6; -- fail -ERROR: permission denied for table atest5 -SET SESSION AUTHORIZATION regress_priv_user1; -ALTER TABLE atest6 DROP COLUMN three; -SET SESSION AUTHORIZATION regress_priv_user4; -SELECT atest6 FROM atest6; -- ok - atest6 --------- -(0 rows) - -SELECT one FROM atest5 NATURAL JOIN atest6; -- ok - one ------ -(0 rows) - -SET SESSION AUTHORIZATION regress_priv_user1; -ALTER TABLE atest6 DROP COLUMN two; -REVOKE SELECT (one,blue) ON atest6 FROM regress_priv_user4; -SET SESSION AUTHORIZATION regress_priv_user4; -SELECT * FROM atest6; -- fail -ERROR: permission denied for table atest6 -SELECT 1 FROM atest6; -- fail -ERROR: permission denied for table atest6 -SET SESSION AUTHORIZATION regress_priv_user3; -DELETE FROM atest5 WHERE one = 1; -- fail -ERROR: permission denied for table atest5 -DELETE FROM atest5 WHERE two = 2; -- ok --- check inheritance cases -SET SESSION AUTHORIZATION regress_priv_user1; -CREATE TABLE atestp1 (f1 int, f2 int); -CREATE TABLE atestp2 (fx int, fy int); -CREATE TABLE atestc (fz int) INHERITS (atestp1, atestp2); -GRANT SELECT(fx,fy,tableoid) ON atestp2 TO regress_priv_user2; -GRANT SELECT(fx) ON atestc TO regress_priv_user2; -SET SESSION AUTHORIZATION regress_priv_user2; -SELECT fx FROM atestp2; -- ok - fx ----- -(0 rows) - -SELECT fy FROM atestp2; -- ok - fy ----- -(0 rows) - -SELECT atestp2 FROM atestp2; -- ok - atestp2 ---------- -(0 rows) - -SELECT tableoid FROM atestp2; -- ok - tableoid ----------- -(0 rows) - -SELECT fy FROM atestc; -- fail -ERROR: permission denied for table atestc -SET SESSION AUTHORIZATION regress_priv_user1; -GRANT SELECT(fy,tableoid) ON atestc TO regress_priv_user2; -SET SESSION AUTHORIZATION regress_priv_user2; -SELECT fx FROM atestp2; -- still ok - fx ----- -(0 rows) - -SELECT fy FROM atestp2; -- ok - fy ----- -(0 rows) - -SELECT atestp2 FROM atestp2; -- ok - atestp2 ---------- -(0 rows) - -SELECT tableoid FROM atestp2; -- ok - tableoid ----------- -(0 rows) - --- child's permissions do not apply when operating on parent -SET SESSION AUTHORIZATION regress_priv_user1; -REVOKE ALL ON atestc FROM regress_priv_user2; -GRANT ALL ON atestp1 TO regress_priv_user2; -SET SESSION AUTHORIZATION regress_priv_user2; -SELECT f2 FROM atestp1; -- ok - f2 ----- -(0 rows) - -SELECT f2 FROM atestc; -- fail -ERROR: permission denied for table atestc -DELETE FROM atestp1; -- ok -DELETE FROM atestc; -- fail -ERROR: permission denied for table atestc -UPDATE atestp1 SET f1 = 1; -- ok -UPDATE atestc SET f1 = 1; -- fail -ERROR: permission denied for table atestc -TRUNCATE atestp1; -- ok -TRUNCATE atestc; -- fail -ERROR: permission denied for table atestc -BEGIN; -LOCK atestp1; -END; -BEGIN; -LOCK atestc; -ERROR: permission denied for table atestc -END; --- privileges on functions, languages --- switch to superuser -\c - -REVOKE ALL PRIVILEGES ON LANGUAGE sql FROM PUBLIC; -GRANT USAGE ON LANGUAGE sql TO regress_priv_user1; -- ok -GRANT USAGE ON LANGUAGE c TO PUBLIC; -- fail -ERROR: language "c" is not trusted -DETAIL: GRANT and REVOKE are not allowed on untrusted languages, because only superusers can use untrusted languages. -SET SESSION AUTHORIZATION regress_priv_user1; -GRANT USAGE ON LANGUAGE sql TO regress_priv_user2; -- fail -WARNING: no privileges were granted for "sql" -CREATE FUNCTION priv_testfunc1(int) RETURNS int AS 'select 2 * $1;' LANGUAGE sql; -CREATE FUNCTION priv_testfunc2(int) RETURNS int AS 'select 3 * $1;' LANGUAGE sql; -CREATE AGGREGATE priv_testagg1(int) (sfunc = int4pl, stype = int4); -CREATE PROCEDURE priv_testproc1(int) AS 'select $1;' LANGUAGE sql; -REVOKE ALL ON FUNCTION priv_testfunc1(int), priv_testfunc2(int), priv_testagg1(int) FROM PUBLIC; -GRANT EXECUTE ON FUNCTION priv_testfunc1(int), priv_testfunc2(int), priv_testagg1(int) TO regress_priv_user2; -REVOKE ALL ON FUNCTION priv_testproc1(int) FROM PUBLIC; -- fail, not a function -ERROR: priv_testproc1(integer) is not a function -REVOKE ALL ON PROCEDURE priv_testproc1(int) FROM PUBLIC; -GRANT EXECUTE ON PROCEDURE priv_testproc1(int) TO regress_priv_user2; -GRANT USAGE ON FUNCTION priv_testfunc1(int) TO regress_priv_user3; -- semantic error -ERROR: invalid privilege type USAGE for function -GRANT USAGE ON FUNCTION priv_testagg1(int) TO regress_priv_user3; -- semantic error -ERROR: invalid privilege type USAGE for function -GRANT USAGE ON PROCEDURE priv_testproc1(int) TO regress_priv_user3; -- semantic error -ERROR: invalid privilege type USAGE for procedure -GRANT ALL PRIVILEGES ON FUNCTION priv_testfunc1(int) TO regress_priv_user4; -GRANT ALL PRIVILEGES ON FUNCTION priv_testfunc_nosuch(int) TO regress_priv_user4; -ERROR: function priv_testfunc_nosuch(integer) does not exist -GRANT ALL PRIVILEGES ON FUNCTION priv_testagg1(int) TO regress_priv_user4; -GRANT ALL PRIVILEGES ON PROCEDURE priv_testproc1(int) TO regress_priv_user4; -CREATE FUNCTION priv_testfunc4(boolean) RETURNS text - AS 'select col1 from atest2 where col2 = $1;' - LANGUAGE sql SECURITY DEFINER; -GRANT EXECUTE ON FUNCTION priv_testfunc4(boolean) TO regress_priv_user3; -SET SESSION AUTHORIZATION regress_priv_user2; -SELECT priv_testfunc1(5), priv_testfunc2(5); -- ok - priv_testfunc1 | priv_testfunc2 -----------------+---------------- - 10 | 15 -(1 row) - -CREATE FUNCTION priv_testfunc3(int) RETURNS int AS 'select 2 * $1;' LANGUAGE sql; -- fail -ERROR: permission denied for language sql -SELECT priv_testagg1(x) FROM (VALUES (1), (2), (3)) _(x); -- ok - priv_testagg1 ---------------- - 6 -(1 row) - -CALL priv_testproc1(6); -- ok -SET SESSION AUTHORIZATION regress_priv_user3; -SELECT priv_testfunc1(5); -- fail -ERROR: permission denied for function priv_testfunc1 -SELECT priv_testagg1(x) FROM (VALUES (1), (2), (3)) _(x); -- fail -ERROR: permission denied for aggregate priv_testagg1 -CALL priv_testproc1(6); -- fail -ERROR: permission denied for procedure priv_testproc1 -SELECT col1 FROM atest2 WHERE col2 = true; -- fail -ERROR: permission denied for table atest2 -SELECT priv_testfunc4(true); -- ok - priv_testfunc4 ----------------- - bar -(1 row) - -SET SESSION AUTHORIZATION regress_priv_user4; -SELECT priv_testfunc1(5); -- ok - priv_testfunc1 ----------------- - 10 -(1 row) - -SELECT priv_testagg1(x) FROM (VALUES (1), (2), (3)) _(x); -- ok - priv_testagg1 ---------------- - 6 -(1 row) - -CALL priv_testproc1(6); -- ok -DROP FUNCTION priv_testfunc1(int); -- fail -ERROR: must be owner of function priv_testfunc1 -DROP AGGREGATE priv_testagg1(int); -- fail -ERROR: must be owner of aggregate priv_testagg1 -DROP PROCEDURE priv_testproc1(int); -- fail -ERROR: must be owner of procedure priv_testproc1 -\c - -DROP FUNCTION priv_testfunc1(int); -- ok --- restore to sanity -GRANT ALL PRIVILEGES ON LANGUAGE sql TO PUBLIC; --- verify privilege checks on array-element coercions -BEGIN; -SELECT '{1}'::int4[]::int8[]; - int8 ------- - {1} -(1 row) - -REVOKE ALL ON FUNCTION int8(integer) FROM PUBLIC; -SELECT '{1}'::int4[]::int8[]; --superuser, succeed - int8 ------- - {1} -(1 row) - -SET SESSION AUTHORIZATION regress_priv_user4; -SELECT '{1}'::int4[]::int8[]; --other user, fail -ERROR: permission denied for function int8 -ROLLBACK; --- privileges on types --- switch to superuser -\c - -CREATE TYPE priv_testtype1 AS (a int, b text); -REVOKE USAGE ON TYPE priv_testtype1 FROM PUBLIC; -GRANT USAGE ON TYPE priv_testtype1 TO regress_priv_user2; -GRANT USAGE ON TYPE _priv_testtype1 TO regress_priv_user2; -- fail -ERROR: cannot set privileges of array types -HINT: Set the privileges of the element type instead. -GRANT USAGE ON DOMAIN priv_testtype1 TO regress_priv_user2; -- fail -ERROR: "priv_testtype1" is not a domain -CREATE DOMAIN priv_testdomain1 AS int; -REVOKE USAGE on DOMAIN priv_testdomain1 FROM PUBLIC; -GRANT USAGE ON DOMAIN priv_testdomain1 TO regress_priv_user2; -GRANT USAGE ON TYPE priv_testdomain1 TO regress_priv_user2; -- ok -SET SESSION AUTHORIZATION regress_priv_user1; --- commands that should fail -CREATE AGGREGATE priv_testagg1a(priv_testdomain1) (sfunc = int4_sum, stype = bigint); -ERROR: permission denied for type priv_testdomain1 -CREATE DOMAIN priv_testdomain2a AS priv_testdomain1; -ERROR: permission denied for type priv_testdomain1 -CREATE DOMAIN priv_testdomain3a AS int; -CREATE FUNCTION castfunc(int) RETURNS priv_testdomain3a AS $$ SELECT $1::priv_testdomain3a $$ LANGUAGE SQL; -CREATE CAST (priv_testdomain1 AS priv_testdomain3a) WITH FUNCTION castfunc(int); -ERROR: permission denied for type priv_testdomain1 -DROP FUNCTION castfunc(int) CASCADE; -DROP DOMAIN priv_testdomain3a; -CREATE FUNCTION priv_testfunc5a(a priv_testdomain1) RETURNS int LANGUAGE SQL AS $$ SELECT $1 $$; -ERROR: permission denied for type priv_testdomain1 -CREATE FUNCTION priv_testfunc6a(b int) RETURNS priv_testdomain1 LANGUAGE SQL AS $$ SELECT $1::priv_testdomain1 $$; -ERROR: permission denied for type priv_testdomain1 -CREATE OPERATOR !+! (PROCEDURE = int4pl, LEFTARG = priv_testdomain1, RIGHTARG = priv_testdomain1); -ERROR: permission denied for type priv_testdomain1 -CREATE TABLE test5a (a int, b priv_testdomain1); -ERROR: permission denied for type priv_testdomain1 -CREATE TABLE test6a OF priv_testtype1; -ERROR: permission denied for type priv_testtype1 -CREATE TABLE test10a (a int[], b priv_testtype1[]); -ERROR: permission denied for type priv_testtype1 -CREATE TABLE test9a (a int, b int); -ALTER TABLE test9a ADD COLUMN c priv_testdomain1; -ERROR: permission denied for type priv_testdomain1 -ALTER TABLE test9a ALTER COLUMN b TYPE priv_testdomain1; -ERROR: permission denied for type priv_testdomain1 -CREATE TYPE test7a AS (a int, b priv_testdomain1); -ERROR: permission denied for type priv_testdomain1 -CREATE TYPE test8a AS (a int, b int); -ALTER TYPE test8a ADD ATTRIBUTE c priv_testdomain1; -ERROR: permission denied for type priv_testdomain1 -ALTER TYPE test8a ALTER ATTRIBUTE b TYPE priv_testdomain1; -ERROR: permission denied for type priv_testdomain1 -CREATE TABLE test11a AS (SELECT 1::priv_testdomain1 AS a); -ERROR: permission denied for type priv_testdomain1 -REVOKE ALL ON TYPE priv_testtype1 FROM PUBLIC; -ERROR: permission denied for type priv_testtype1 -SET SESSION AUTHORIZATION regress_priv_user2; --- commands that should succeed -CREATE AGGREGATE priv_testagg1b(priv_testdomain1) (sfunc = int4_sum, stype = bigint); -CREATE DOMAIN priv_testdomain2b AS priv_testdomain1; -CREATE DOMAIN priv_testdomain3b AS int; -CREATE FUNCTION castfunc(int) RETURNS priv_testdomain3b AS $$ SELECT $1::priv_testdomain3b $$ LANGUAGE SQL; -CREATE CAST (priv_testdomain1 AS priv_testdomain3b) WITH FUNCTION castfunc(int); -WARNING: cast will be ignored because the source data type is a domain -CREATE FUNCTION priv_testfunc5b(a priv_testdomain1) RETURNS int LANGUAGE SQL AS $$ SELECT $1 $$; -CREATE FUNCTION priv_testfunc6b(b int) RETURNS priv_testdomain1 LANGUAGE SQL AS $$ SELECT $1::priv_testdomain1 $$; -CREATE OPERATOR !! (PROCEDURE = priv_testfunc5b, RIGHTARG = priv_testdomain1); -CREATE TABLE test5b (a int, b priv_testdomain1); -CREATE TABLE test6b OF priv_testtype1; -CREATE TABLE test10b (a int[], b priv_testtype1[]); -CREATE TABLE test9b (a int, b int); -ALTER TABLE test9b ADD COLUMN c priv_testdomain1; -ALTER TABLE test9b ALTER COLUMN b TYPE priv_testdomain1; -CREATE TYPE test7b AS (a int, b priv_testdomain1); -CREATE TYPE test8b AS (a int, b int); -ALTER TYPE test8b ADD ATTRIBUTE c priv_testdomain1; -ALTER TYPE test8b ALTER ATTRIBUTE b TYPE priv_testdomain1; -CREATE TABLE test11b AS (SELECT 1::priv_testdomain1 AS a); -REVOKE ALL ON TYPE priv_testtype1 FROM PUBLIC; -WARNING: no privileges could be revoked for "priv_testtype1" -\c - -DROP AGGREGATE priv_testagg1b(priv_testdomain1); -DROP DOMAIN priv_testdomain2b; -DROP OPERATOR !! (NONE, priv_testdomain1); -DROP FUNCTION priv_testfunc5b(a priv_testdomain1); -DROP FUNCTION priv_testfunc6b(b int); -DROP TABLE test5b; -DROP TABLE test6b; -DROP TABLE test9b; -DROP TABLE test10b; -DROP TYPE test7b; -DROP TYPE test8b; -DROP CAST (priv_testdomain1 AS priv_testdomain3b); -DROP FUNCTION castfunc(int) CASCADE; -DROP DOMAIN priv_testdomain3b; -DROP TABLE test11b; -DROP TYPE priv_testtype1; -- ok -DROP DOMAIN priv_testdomain1; -- ok --- truncate -SET SESSION AUTHORIZATION regress_priv_user5; -TRUNCATE atest2; -- ok -TRUNCATE atest3; -- fail -ERROR: permission denied for table atest3 --- has_table_privilege function --- bad-input checks -select has_table_privilege(NULL,'pg_authid','select'); - has_table_privilege ---------------------- - -(1 row) - -select has_table_privilege('pg_shad','select'); -ERROR: relation "pg_shad" does not exist -select has_table_privilege('nosuchuser','pg_authid','select'); -ERROR: role "nosuchuser" does not exist -select has_table_privilege('pg_authid','sel'); -ERROR: unrecognized privilege type: "sel" -select has_table_privilege(-999999,'pg_authid','update'); - has_table_privilege ---------------------- - f -(1 row) - -select has_table_privilege(1,'select'); - has_table_privilege ---------------------- - -(1 row) - --- superuser -\c - -select has_table_privilege(current_user,'pg_authid','select'); - has_table_privilege ---------------------- - t -(1 row) - -select has_table_privilege(current_user,'pg_authid','insert'); - has_table_privilege ---------------------- - t -(1 row) - -select has_table_privilege(t2.oid,'pg_authid','update') -from (select oid from pg_roles where rolname = current_user) as t2; - has_table_privilege ---------------------- - t -(1 row) - -select has_table_privilege(t2.oid,'pg_authid','delete') -from (select oid from pg_roles where rolname = current_user) as t2; - has_table_privilege ---------------------- - t -(1 row) - -select has_table_privilege(current_user,t1.oid,'references') -from (select oid from pg_class where relname = 'pg_authid') as t1; - has_table_privilege ---------------------- - t -(1 row) - -select has_table_privilege(t2.oid,t1.oid,'select') -from (select oid from pg_class where relname = 'pg_authid') as t1, - (select oid from pg_roles where rolname = current_user) as t2; - has_table_privilege ---------------------- - t -(1 row) - -select has_table_privilege(t2.oid,t1.oid,'insert') -from (select oid from pg_class where relname = 'pg_authid') as t1, - (select oid from pg_roles where rolname = current_user) as t2; - has_table_privilege ---------------------- - t -(1 row) - -select has_table_privilege('pg_authid','update'); - has_table_privilege ---------------------- - t -(1 row) - -select has_table_privilege('pg_authid','delete'); - has_table_privilege ---------------------- - t -(1 row) - -select has_table_privilege('pg_authid','truncate'); - has_table_privilege ---------------------- - t -(1 row) - -select has_table_privilege(t1.oid,'select') -from (select oid from pg_class where relname = 'pg_authid') as t1; - has_table_privilege ---------------------- - t -(1 row) - -select has_table_privilege(t1.oid,'trigger') -from (select oid from pg_class where relname = 'pg_authid') as t1; - has_table_privilege ---------------------- - t -(1 row) - --- non-superuser -SET SESSION AUTHORIZATION regress_priv_user3; -select has_table_privilege(current_user,'pg_class','select'); - has_table_privilege ---------------------- - t -(1 row) - -select has_table_privilege(current_user,'pg_class','insert'); - has_table_privilege ---------------------- - f -(1 row) - -select has_table_privilege(t2.oid,'pg_class','update') -from (select oid from pg_roles where rolname = current_user) as t2; - has_table_privilege ---------------------- - f -(1 row) - -select has_table_privilege(t2.oid,'pg_class','delete') -from (select oid from pg_roles where rolname = current_user) as t2; - has_table_privilege ---------------------- - f -(1 row) - -select has_table_privilege(current_user,t1.oid,'references') -from (select oid from pg_class where relname = 'pg_class') as t1; - has_table_privilege ---------------------- - f -(1 row) - -select has_table_privilege(t2.oid,t1.oid,'select') -from (select oid from pg_class where relname = 'pg_class') as t1, - (select oid from pg_roles where rolname = current_user) as t2; - has_table_privilege ---------------------- - t -(1 row) - -select has_table_privilege(t2.oid,t1.oid,'insert') -from (select oid from pg_class where relname = 'pg_class') as t1, - (select oid from pg_roles where rolname = current_user) as t2; - has_table_privilege ---------------------- - f -(1 row) - -select has_table_privilege('pg_class','update'); - has_table_privilege ---------------------- - f -(1 row) - -select has_table_privilege('pg_class','delete'); - has_table_privilege ---------------------- - f -(1 row) - -select has_table_privilege('pg_class','truncate'); - has_table_privilege ---------------------- - f -(1 row) - -select has_table_privilege(t1.oid,'select') -from (select oid from pg_class where relname = 'pg_class') as t1; - has_table_privilege ---------------------- - t -(1 row) - -select has_table_privilege(t1.oid,'trigger') -from (select oid from pg_class where relname = 'pg_class') as t1; - has_table_privilege ---------------------- - f -(1 row) - -select has_table_privilege(current_user,'atest1','select'); - has_table_privilege ---------------------- - t -(1 row) - -select has_table_privilege(current_user,'atest1','insert'); - has_table_privilege ---------------------- - f -(1 row) - -select has_table_privilege(t2.oid,'atest1','update') -from (select oid from pg_roles where rolname = current_user) as t2; - has_table_privilege ---------------------- - f -(1 row) - -select has_table_privilege(t2.oid,'atest1','delete') -from (select oid from pg_roles where rolname = current_user) as t2; - has_table_privilege ---------------------- - f -(1 row) - -select has_table_privilege(current_user,t1.oid,'references') -from (select oid from pg_class where relname = 'atest1') as t1; - has_table_privilege ---------------------- - f -(1 row) - -select has_table_privilege(t2.oid,t1.oid,'select') -from (select oid from pg_class where relname = 'atest1') as t1, - (select oid from pg_roles where rolname = current_user) as t2; - has_table_privilege ---------------------- - t -(1 row) - -select has_table_privilege(t2.oid,t1.oid,'insert') -from (select oid from pg_class where relname = 'atest1') as t1, - (select oid from pg_roles where rolname = current_user) as t2; - has_table_privilege ---------------------- - f -(1 row) - -select has_table_privilege('atest1','update'); - has_table_privilege ---------------------- - f -(1 row) - -select has_table_privilege('atest1','delete'); - has_table_privilege ---------------------- - f -(1 row) - -select has_table_privilege('atest1','truncate'); - has_table_privilege ---------------------- - f -(1 row) - -select has_table_privilege(t1.oid,'select') -from (select oid from pg_class where relname = 'atest1') as t1; - has_table_privilege ---------------------- - t -(1 row) - -select has_table_privilege(t1.oid,'trigger') -from (select oid from pg_class where relname = 'atest1') as t1; - has_table_privilege ---------------------- - f -(1 row) - --- has_column_privilege function --- bad-input checks (as non-super-user) -select has_column_privilege('pg_authid',NULL,'select'); - has_column_privilege ----------------------- - -(1 row) - -select has_column_privilege('pg_authid','nosuchcol','select'); -ERROR: column "nosuchcol" of relation "pg_authid" does not exist -select has_column_privilege(9999,'nosuchcol','select'); - has_column_privilege ----------------------- - -(1 row) - -select has_column_privilege(9999,99::int2,'select'); - has_column_privilege ----------------------- - -(1 row) - -select has_column_privilege('pg_authid',99::int2,'select'); - has_column_privilege ----------------------- - -(1 row) - -select has_column_privilege(9999,99::int2,'select'); - has_column_privilege ----------------------- - -(1 row) - -create temp table mytable(f1 int, f2 int, f3 int); -alter table mytable drop column f2; -select has_column_privilege('mytable','f2','select'); -ERROR: column "f2" of relation "mytable" does not exist -select has_column_privilege('mytable','........pg.dropped.2........','select'); - has_column_privilege ----------------------- - -(1 row) - -select has_column_privilege('mytable',2::int2,'select'); - has_column_privilege ----------------------- - -(1 row) - -select has_column_privilege('mytable',99::int2,'select'); - has_column_privilege ----------------------- - -(1 row) - -revoke select on table mytable from regress_priv_user3; -select has_column_privilege('mytable',2::int2,'select'); - has_column_privilege ----------------------- - -(1 row) - -select has_column_privilege('mytable',99::int2,'select'); - has_column_privilege ----------------------- - -(1 row) - -drop table mytable; --- Grant options -SET SESSION AUTHORIZATION regress_priv_user1; -CREATE TABLE atest4 (a int); -GRANT SELECT ON atest4 TO regress_priv_user2 WITH GRANT OPTION; -GRANT UPDATE ON atest4 TO regress_priv_user2; -GRANT SELECT ON atest4 TO GROUP regress_priv_group1 WITH GRANT OPTION; -SET SESSION AUTHORIZATION regress_priv_user2; -GRANT SELECT ON atest4 TO regress_priv_user3; -GRANT UPDATE ON atest4 TO regress_priv_user3; -- fail -WARNING: no privileges were granted for "atest4" -SET SESSION AUTHORIZATION regress_priv_user1; -REVOKE SELECT ON atest4 FROM regress_priv_user3; -- does nothing -SELECT has_table_privilege('regress_priv_user3', 'atest4', 'SELECT'); -- true - has_table_privilege ---------------------- - t -(1 row) - -REVOKE SELECT ON atest4 FROM regress_priv_user2; -- fail -ERROR: dependent privileges exist -HINT: Use CASCADE to revoke them too. -REVOKE GRANT OPTION FOR SELECT ON atest4 FROM regress_priv_user2 CASCADE; -- ok -SELECT has_table_privilege('regress_priv_user2', 'atest4', 'SELECT'); -- true - has_table_privilege ---------------------- - t -(1 row) - -SELECT has_table_privilege('regress_priv_user3', 'atest4', 'SELECT'); -- false - has_table_privilege ---------------------- - f -(1 row) - -SELECT has_table_privilege('regress_priv_user1', 'atest4', 'SELECT WITH GRANT OPTION'); -- true - has_table_privilege ---------------------- - t -(1 row) - --- security-restricted operations -\c - -CREATE ROLE regress_sro_user; --- Check that index expressions and predicates are run as the table's owner --- A dummy index function checking current_user -CREATE FUNCTION sro_ifun(int) RETURNS int AS $$ -BEGIN - -- Below we set the table's owner to regress_sro_user - ASSERT current_user = 'regress_sro_user', - format('sro_ifun(%s) called by %s', $1, current_user); - RETURN $1; -END; -$$ LANGUAGE plpgsql IMMUTABLE; --- Create a table owned by regress_sro_user -CREATE TABLE sro_tab (a int); -ALTER TABLE sro_tab OWNER TO regress_sro_user; -INSERT INTO sro_tab VALUES (1), (2), (3); --- Create an expression index with a predicate -CREATE INDEX sro_idx ON sro_tab ((sro_ifun(a) + sro_ifun(0))) - WHERE sro_ifun(a + 10) > sro_ifun(10); -DROP INDEX sro_idx; --- Do the same concurrently -CREATE INDEX CONCURRENTLY sro_idx ON sro_tab ((sro_ifun(a) + sro_ifun(0))) - WHERE sro_ifun(a + 10) > sro_ifun(10); --- REINDEX -REINDEX TABLE sro_tab; -REINDEX INDEX sro_idx; -REINDEX TABLE CONCURRENTLY sro_tab; -DROP INDEX sro_idx; --- CLUSTER -CREATE INDEX sro_cluster_idx ON sro_tab ((sro_ifun(a) + sro_ifun(0))); -CLUSTER sro_tab USING sro_cluster_idx; -DROP INDEX sro_cluster_idx; --- BRIN index -CREATE INDEX sro_brin ON sro_tab USING brin ((sro_ifun(a) + sro_ifun(0))); -SELECT brin_desummarize_range('sro_brin', 0); - brin_desummarize_range ------------------------- - -(1 row) - -SELECT brin_summarize_range('sro_brin', 0); - brin_summarize_range ----------------------- - 1 -(1 row) - -DROP TABLE sro_tab; --- Check with a partitioned table -CREATE TABLE sro_ptab (a int) PARTITION BY RANGE (a); -ALTER TABLE sro_ptab OWNER TO regress_sro_user; -CREATE TABLE sro_part PARTITION OF sro_ptab FOR VALUES FROM (1) TO (10); -ALTER TABLE sro_part OWNER TO regress_sro_user; -INSERT INTO sro_ptab VALUES (1), (2), (3); -CREATE INDEX sro_pidx ON sro_ptab ((sro_ifun(a) + sro_ifun(0))) - WHERE sro_ifun(a + 10) > sro_ifun(10); -REINDEX TABLE sro_ptab; -REINDEX INDEX CONCURRENTLY sro_pidx; -SET SESSION AUTHORIZATION regress_sro_user; -CREATE FUNCTION unwanted_grant() RETURNS void LANGUAGE sql AS - 'GRANT regress_priv_group2 TO regress_sro_user'; -CREATE FUNCTION mv_action() RETURNS bool LANGUAGE sql AS - 'DECLARE c CURSOR WITH HOLD FOR SELECT public.unwanted_grant(); SELECT true'; --- REFRESH of this MV will queue a GRANT at end of transaction -CREATE MATERIALIZED VIEW sro_mv AS SELECT mv_action() WITH NO DATA; -REFRESH MATERIALIZED VIEW sro_mv; -ERROR: cannot create a cursor WITH HOLD within security-restricted operation -CONTEXT: SQL function "mv_action" statement 1 -\c - -REFRESH MATERIALIZED VIEW sro_mv; -ERROR: cannot create a cursor WITH HOLD within security-restricted operation -CONTEXT: SQL function "mv_action" statement 1 -SET SESSION AUTHORIZATION regress_sro_user; --- INSERT to this table will queue a GRANT at end of transaction -CREATE TABLE sro_trojan_table (); -CREATE FUNCTION sro_trojan() RETURNS trigger LANGUAGE plpgsql AS - 'BEGIN PERFORM public.unwanted_grant(); RETURN NULL; END'; -CREATE CONSTRAINT TRIGGER t AFTER INSERT ON sro_trojan_table - INITIALLY DEFERRED FOR EACH ROW EXECUTE PROCEDURE sro_trojan(); --- Now, REFRESH will issue such an INSERT, queueing the GRANT -CREATE OR REPLACE FUNCTION mv_action() RETURNS bool LANGUAGE sql AS - 'INSERT INTO public.sro_trojan_table DEFAULT VALUES; SELECT true'; -REFRESH MATERIALIZED VIEW sro_mv; -ERROR: cannot fire deferred trigger within security-restricted operation -CONTEXT: SQL function "mv_action" statement 1 -\c - -REFRESH MATERIALIZED VIEW sro_mv; -ERROR: cannot fire deferred trigger within security-restricted operation -CONTEXT: SQL function "mv_action" statement 1 -BEGIN; SET CONSTRAINTS ALL IMMEDIATE; REFRESH MATERIALIZED VIEW sro_mv; COMMIT; -ERROR: permission denied to grant role "regress_priv_group2" -DETAIL: Only roles with the ADMIN option on role "regress_priv_group2" may grant this role. -CONTEXT: SQL function "unwanted_grant" statement 1 -SQL statement "SELECT public.unwanted_grant()" -PL/pgSQL function public.sro_trojan() line 1 at PERFORM -SQL function "mv_action" statement 1 --- REFRESH MATERIALIZED VIEW CONCURRENTLY use of eval_const_expressions() -SET SESSION AUTHORIZATION regress_sro_user; -CREATE FUNCTION unwanted_grant_nofail(int) RETURNS int - IMMUTABLE LANGUAGE plpgsql AS $$ -BEGIN - PERFORM public.unwanted_grant(); - RAISE WARNING 'owned'; - RETURN 1; -EXCEPTION WHEN OTHERS THEN - RETURN 2; -END$$; -CREATE MATERIALIZED VIEW sro_index_mv AS SELECT 1 AS c; -CREATE UNIQUE INDEX ON sro_index_mv (c) WHERE unwanted_grant_nofail(1) > 0; -\c - -REFRESH MATERIALIZED VIEW CONCURRENTLY sro_index_mv; -REFRESH MATERIALIZED VIEW sro_index_mv; -DROP OWNED BY regress_sro_user; -DROP ROLE regress_sro_user; --- Admin options -SET SESSION AUTHORIZATION regress_priv_user4; -CREATE FUNCTION dogrant_ok() RETURNS void LANGUAGE sql SECURITY DEFINER AS - 'GRANT regress_priv_group2 TO regress_priv_user5'; -GRANT regress_priv_group2 TO regress_priv_user5; -- ok: had ADMIN OPTION -SET ROLE regress_priv_group2; -GRANT regress_priv_group2 TO regress_priv_user5; -- fails: SET ROLE suspended privilege -ERROR: permission denied to grant role "regress_priv_group2" -DETAIL: Only roles with the ADMIN option on role "regress_priv_group2" may grant this role. -SET SESSION AUTHORIZATION regress_priv_user1; -GRANT regress_priv_group2 TO regress_priv_user5; -- fails: no ADMIN OPTION -ERROR: permission denied to grant role "regress_priv_group2" -DETAIL: Only roles with the ADMIN option on role "regress_priv_group2" may grant this role. -SELECT dogrant_ok(); -- ok: SECURITY DEFINER conveys ADMIN -NOTICE: role "regress_priv_user5" has already been granted membership in role "regress_priv_group2" by role "regress_priv_user4" - dogrant_ok ------------- - -(1 row) - -SET ROLE regress_priv_group2; -GRANT regress_priv_group2 TO regress_priv_user5; -- fails: SET ROLE did not help -ERROR: permission denied to grant role "regress_priv_group2" -DETAIL: Only roles with the ADMIN option on role "regress_priv_group2" may grant this role. -SET SESSION AUTHORIZATION regress_priv_group2; -GRANT regress_priv_group2 TO regress_priv_user5; -- fails: no self-admin -ERROR: permission denied to grant role "regress_priv_group2" -DETAIL: Only roles with the ADMIN option on role "regress_priv_group2" may grant this role. -SET SESSION AUTHORIZATION regress_priv_user4; -DROP FUNCTION dogrant_ok(); -REVOKE regress_priv_group2 FROM regress_priv_user5; --- has_sequence_privilege tests -\c - -CREATE SEQUENCE x_seq; -GRANT USAGE on x_seq to regress_priv_user2; -SELECT has_sequence_privilege('regress_priv_user1', 'atest1', 'SELECT'); -ERROR: "atest1" is not a sequence -SELECT has_sequence_privilege('regress_priv_user1', 'x_seq', 'INSERT'); -ERROR: unrecognized privilege type: "INSERT" -SELECT has_sequence_privilege('regress_priv_user1', 'x_seq', 'SELECT'); - has_sequence_privilege ------------------------- - f -(1 row) - -SET SESSION AUTHORIZATION regress_priv_user2; -SELECT has_sequence_privilege('x_seq', 'USAGE'); - has_sequence_privilege ------------------------- - t -(1 row) - --- largeobject privilege tests -\c - -SET SESSION AUTHORIZATION regress_priv_user1; -SELECT lo_create(1001); - lo_create ------------ - 1001 -(1 row) - -SELECT lo_create(1002); - lo_create ------------ - 1002 -(1 row) - -SELECT lo_create(1003); - lo_create ------------ - 1003 -(1 row) - -SELECT lo_create(1004); - lo_create ------------ - 1004 -(1 row) - -SELECT lo_create(1005); - lo_create ------------ - 1005 -(1 row) - -GRANT ALL ON LARGE OBJECT 1001 TO PUBLIC; -GRANT SELECT ON LARGE OBJECT 1003 TO regress_priv_user2; -GRANT SELECT,UPDATE ON LARGE OBJECT 1004 TO regress_priv_user2; -GRANT ALL ON LARGE OBJECT 1005 TO regress_priv_user2; -GRANT SELECT ON LARGE OBJECT 1005 TO regress_priv_user2 WITH GRANT OPTION; -GRANT SELECT, INSERT ON LARGE OBJECT 1001 TO PUBLIC; -- to be failed -ERROR: invalid privilege type INSERT for large object -GRANT SELECT, UPDATE ON LARGE OBJECT 1001 TO nosuchuser; -- to be failed -ERROR: role "nosuchuser" does not exist -GRANT SELECT, UPDATE ON LARGE OBJECT 999 TO PUBLIC; -- to be failed -ERROR: large object 999 does not exist -\c - -SET SESSION AUTHORIZATION regress_priv_user2; -SELECT lo_create(2001); - lo_create ------------ - 2001 -(1 row) - -SELECT lo_create(2002); - lo_create ------------ - 2002 -(1 row) - -SELECT loread(lo_open(1001, x'20000'::int), 32); -- allowed, for now - loread --------- - \x -(1 row) - -SELECT lowrite(lo_open(1001, x'40000'::int), 'abcd'); -- fail, wrong mode -ERROR: large object descriptor 0 was not opened for writing -SELECT loread(lo_open(1001, x'40000'::int), 32); - loread --------- - \x -(1 row) - -SELECT loread(lo_open(1002, x'40000'::int), 32); -- to be denied -ERROR: permission denied for large object 1002 -SELECT loread(lo_open(1003, x'40000'::int), 32); - loread --------- - \x -(1 row) - -SELECT loread(lo_open(1004, x'40000'::int), 32); - loread --------- - \x -(1 row) - -SELECT lowrite(lo_open(1001, x'20000'::int), 'abcd'); - lowrite ---------- - 4 -(1 row) - -SELECT lowrite(lo_open(1002, x'20000'::int), 'abcd'); -- to be denied -ERROR: permission denied for large object 1002 -SELECT lowrite(lo_open(1003, x'20000'::int), 'abcd'); -- to be denied -ERROR: permission denied for large object 1003 -SELECT lowrite(lo_open(1004, x'20000'::int), 'abcd'); - lowrite ---------- - 4 -(1 row) - -GRANT SELECT ON LARGE OBJECT 1005 TO regress_priv_user3; -GRANT UPDATE ON LARGE OBJECT 1006 TO regress_priv_user3; -- to be denied -ERROR: large object 1006 does not exist -REVOKE ALL ON LARGE OBJECT 2001, 2002 FROM PUBLIC; -GRANT ALL ON LARGE OBJECT 2001 TO regress_priv_user3; -SELECT lo_unlink(1001); -- to be denied -ERROR: must be owner of large object 1001 -SELECT lo_unlink(2002); - lo_unlink ------------ - 1 -(1 row) - -\c - --- confirm ACL setting -SELECT oid, pg_get_userbyid(lomowner) ownername, lomacl FROM pg_largeobject_metadata WHERE oid >= 1000 AND oid < 3000 ORDER BY oid; - oid | ownername | lomacl -------+--------------------+------------------------------------------------------------------------------------------------------------------------------ - 1001 | regress_priv_user1 | {regress_priv_user1=rw/regress_priv_user1,=rw/regress_priv_user1} - 1002 | regress_priv_user1 | - 1003 | regress_priv_user1 | {regress_priv_user1=rw/regress_priv_user1,regress_priv_user2=r/regress_priv_user1} - 1004 | regress_priv_user1 | {regress_priv_user1=rw/regress_priv_user1,regress_priv_user2=rw/regress_priv_user1} - 1005 | regress_priv_user1 | {regress_priv_user1=rw/regress_priv_user1,regress_priv_user2=r*w/regress_priv_user1,regress_priv_user3=r/regress_priv_user2} - 2001 | regress_priv_user2 | {regress_priv_user2=rw/regress_priv_user2,regress_priv_user3=rw/regress_priv_user2} -(6 rows) - -SET SESSION AUTHORIZATION regress_priv_user3; -SELECT loread(lo_open(1001, x'40000'::int), 32); - loread ------------- - \x61626364 -(1 row) - -SELECT loread(lo_open(1003, x'40000'::int), 32); -- to be denied -ERROR: permission denied for large object 1003 -SELECT loread(lo_open(1005, x'40000'::int), 32); - loread --------- - \x -(1 row) - -SELECT lo_truncate(lo_open(1005, x'20000'::int), 10); -- to be denied -ERROR: permission denied for large object 1005 -SELECT lo_truncate(lo_open(2001, x'20000'::int), 10); - lo_truncate -------------- - 0 -(1 row) - --- has_largeobject_privilege function --- superuser -\c - -SELECT has_largeobject_privilege(1001, 'SELECT'); - has_largeobject_privilege ---------------------------- - t -(1 row) - -SELECT has_largeobject_privilege(1002, 'SELECT'); - has_largeobject_privilege ---------------------------- - t -(1 row) - -SELECT has_largeobject_privilege(1003, 'SELECT'); - has_largeobject_privilege ---------------------------- - t -(1 row) - -SELECT has_largeobject_privilege(1004, 'SELECT'); - has_largeobject_privilege ---------------------------- - t -(1 row) - -SELECT has_largeobject_privilege(1001, 'UPDATE'); - has_largeobject_privilege ---------------------------- - t -(1 row) - -SELECT has_largeobject_privilege(1002, 'UPDATE'); - has_largeobject_privilege ---------------------------- - t -(1 row) - -SELECT has_largeobject_privilege(1003, 'UPDATE'); - has_largeobject_privilege ---------------------------- - t -(1 row) - -SELECT has_largeobject_privilege(1004, 'UPDATE'); - has_largeobject_privilege ---------------------------- - t -(1 row) - --- not-existing large object -SELECT has_largeobject_privilege(9999, 'SELECT'); -- NULL - has_largeobject_privilege ---------------------------- - -(1 row) - --- non-superuser -SET SESSION AUTHORIZATION regress_priv_user2; -SELECT has_largeobject_privilege(1001, 'SELECT'); - has_largeobject_privilege ---------------------------- - t -(1 row) - -SELECT has_largeobject_privilege(1002, 'SELECT'); -- false - has_largeobject_privilege ---------------------------- - f -(1 row) - -SELECT has_largeobject_privilege(1003, 'SELECT'); - has_largeobject_privilege ---------------------------- - t -(1 row) - -SELECT has_largeobject_privilege(1004, 'SELECT'); - has_largeobject_privilege ---------------------------- - t -(1 row) - -SELECT has_largeobject_privilege(1001, 'UPDATE'); - has_largeobject_privilege ---------------------------- - t -(1 row) - -SELECT has_largeobject_privilege(1002, 'UPDATE'); -- false - has_largeobject_privilege ---------------------------- - f -(1 row) - -SELECT has_largeobject_privilege(1003, 'UPDATE'); -- false - has_largeobject_privilege ---------------------------- - f -(1 row) - -SELECT has_largeobject_privilege(1004, 'UPDATE'); - has_largeobject_privilege ---------------------------- - t -(1 row) - -SELECT has_largeobject_privilege('regress_priv_user3', 1001, 'SELECT'); - has_largeobject_privilege ---------------------------- - t -(1 row) - -SELECT has_largeobject_privilege('regress_priv_user3', 1003, 'SELECT'); -- false - has_largeobject_privilege ---------------------------- - f -(1 row) - -SELECT has_largeobject_privilege('regress_priv_user3', 1005, 'SELECT'); - has_largeobject_privilege ---------------------------- - t -(1 row) - -SELECT has_largeobject_privilege('regress_priv_user3', 1005, 'UPDATE'); -- false - has_largeobject_privilege ---------------------------- - f -(1 row) - -SELECT has_largeobject_privilege('regress_priv_user3', 2001, 'UPDATE'); - has_largeobject_privilege ---------------------------- - t -(1 row) - --- compatibility mode in largeobject permission -\c - -SET lo_compat_privileges = false; -- default setting -SET SESSION AUTHORIZATION regress_priv_user4; -SELECT has_largeobject_privilege(1002, 'SELECT'); -- false - has_largeobject_privilege ---------------------------- - f -(1 row) - -SELECT has_largeobject_privilege(1002, 'UPDATE'); -- false - has_largeobject_privilege ---------------------------- - f -(1 row) - -SELECT loread(lo_open(1002, x'40000'::int), 32); -- to be denied -ERROR: permission denied for large object 1002 -SELECT lowrite(lo_open(1002, x'20000'::int), 'abcd'); -- to be denied -ERROR: permission denied for large object 1002 -SELECT lo_truncate(lo_open(1002, x'20000'::int), 10); -- to be denied -ERROR: permission denied for large object 1002 -SELECT lo_put(1002, 1, 'abcd'); -- to be denied -ERROR: permission denied for large object 1002 -SELECT lo_unlink(1002); -- to be denied -ERROR: must be owner of large object 1002 -SELECT lo_export(1001, '/dev/null'); -- to be denied -ERROR: permission denied for function lo_export -SELECT lo_import('/dev/null'); -- to be denied -ERROR: permission denied for function lo_import -SELECT lo_import('/dev/null', 2003); -- to be denied -ERROR: permission denied for function lo_import -\c - -SET lo_compat_privileges = true; -- compatibility mode -SET SESSION AUTHORIZATION regress_priv_user4; -SELECT has_largeobject_privilege(1002, 'SELECT'); -- true - has_largeobject_privilege ---------------------------- - t -(1 row) - -SELECT has_largeobject_privilege(1002, 'UPDATE'); -- true - has_largeobject_privilege ---------------------------- - t -(1 row) - -SELECT loread(lo_open(1002, x'40000'::int), 32); - loread --------- - \x -(1 row) - -SELECT lowrite(lo_open(1002, x'20000'::int), 'abcd'); - lowrite ---------- - 4 -(1 row) - -SELECT lo_truncate(lo_open(1002, x'20000'::int), 10); - lo_truncate -------------- - 0 -(1 row) - -SELECT lo_unlink(1002); - lo_unlink ------------ - 1 -(1 row) - -SELECT lo_export(1001, '/dev/null'); -- to be denied -ERROR: permission denied for function lo_export --- don't allow unpriv users to access pg_largeobject contents -\c - -SELECT * FROM pg_largeobject LIMIT 0; - loid | pageno | data -------+--------+------ -(0 rows) - -SET SESSION AUTHORIZATION regress_priv_user1; -SELECT * FROM pg_largeobject LIMIT 0; -- to be denied -ERROR: permission denied for table pg_largeobject --- pg_signal_backend can't signal superusers -RESET SESSION AUTHORIZATION; -BEGIN; -CREATE OR REPLACE FUNCTION terminate_nothrow(pid int) RETURNS bool - LANGUAGE plpgsql SECURITY DEFINER SET client_min_messages = error AS $$ -BEGIN - RETURN pg_terminate_backend($1); -EXCEPTION WHEN OTHERS THEN - RETURN false; -END$$; -ALTER FUNCTION terminate_nothrow OWNER TO pg_signal_backend; -SELECT backend_type FROM pg_stat_activity -WHERE CASE WHEN COALESCE(usesysid, 10) = 10 THEN terminate_nothrow(pid) END; - backend_type --------------- -(0 rows) - -ROLLBACK; --- test pg_database_owner -RESET SESSION AUTHORIZATION; -GRANT pg_database_owner TO regress_priv_user1; -ERROR: role "pg_database_owner" cannot have explicit members -GRANT regress_priv_user1 TO pg_database_owner; -ERROR: role "pg_database_owner" cannot be a member of any role -CREATE TABLE datdba_only (); -ALTER TABLE datdba_only OWNER TO pg_database_owner; -REVOKE DELETE ON datdba_only FROM pg_database_owner; -SELECT - pg_has_role('regress_priv_user1', 'pg_database_owner', 'USAGE') as priv, - pg_has_role('regress_priv_user1', 'pg_database_owner', 'MEMBER') as mem, - pg_has_role('regress_priv_user1', 'pg_database_owner', - 'MEMBER WITH ADMIN OPTION') as admin; - priv | mem | admin -------+-----+------- - f | f | f -(1 row) - -BEGIN; -DO $$BEGIN EXECUTE format( - 'ALTER DATABASE %I OWNER TO regress_priv_group2', current_catalog); END$$; -SELECT - pg_has_role('regress_priv_user1', 'pg_database_owner', 'USAGE') as priv, - pg_has_role('regress_priv_user1', 'pg_database_owner', 'MEMBER') as mem, - pg_has_role('regress_priv_user1', 'pg_database_owner', - 'MEMBER WITH ADMIN OPTION') as admin; - priv | mem | admin -------+-----+------- - t | t | f -(1 row) - -SET SESSION AUTHORIZATION regress_priv_user1; -TABLE information_schema.enabled_roles ORDER BY role_name COLLATE "C"; - role_name ---------------------- - pg_database_owner - regress_priv_group2 - regress_priv_user1 -(3 rows) - -TABLE information_schema.applicable_roles ORDER BY role_name COLLATE "C"; - grantee | role_name | is_grantable ----------------------+---------------------+-------------- - regress_priv_group2 | pg_database_owner | NO - regress_priv_user1 | regress_priv_group2 | NO -(2 rows) - -INSERT INTO datdba_only DEFAULT VALUES; -SAVEPOINT q; DELETE FROM datdba_only; ROLLBACK TO q; -ERROR: permission denied for table datdba_only -SET SESSION AUTHORIZATION regress_priv_user2; -TABLE information_schema.enabled_roles; - role_name --------------------- - regress_priv_user2 -(1 row) - -INSERT INTO datdba_only DEFAULT VALUES; -ERROR: permission denied for table datdba_only -ROLLBACK; --- test default ACLs -\c - -CREATE SCHEMA testns; -GRANT ALL ON SCHEMA testns TO regress_priv_user1; -CREATE TABLE testns.acltest1 (x int); -SELECT has_table_privilege('regress_priv_user1', 'testns.acltest1', 'SELECT'); -- no - has_table_privilege ---------------------- - f -(1 row) - -SELECT has_table_privilege('regress_priv_user1', 'testns.acltest1', 'INSERT'); -- no - has_table_privilege ---------------------- - f -(1 row) - --- placeholder for test with duplicated schema and role names -ALTER DEFAULT PRIVILEGES IN SCHEMA testns,testns GRANT SELECT ON TABLES TO public,public; -SELECT has_table_privilege('regress_priv_user1', 'testns.acltest1', 'SELECT'); -- no - has_table_privilege ---------------------- - f -(1 row) - -SELECT has_table_privilege('regress_priv_user1', 'testns.acltest1', 'INSERT'); -- no - has_table_privilege ---------------------- - f -(1 row) - -DROP TABLE testns.acltest1; -CREATE TABLE testns.acltest1 (x int); -SELECT has_table_privilege('regress_priv_user1', 'testns.acltest1', 'SELECT'); -- yes - has_table_privilege ---------------------- - t -(1 row) - -SELECT has_table_privilege('regress_priv_user1', 'testns.acltest1', 'INSERT'); -- no - has_table_privilege ---------------------- - f -(1 row) - -ALTER DEFAULT PRIVILEGES IN SCHEMA testns GRANT INSERT ON TABLES TO regress_priv_user1; -DROP TABLE testns.acltest1; -CREATE TABLE testns.acltest1 (x int); -SELECT has_table_privilege('regress_priv_user1', 'testns.acltest1', 'SELECT'); -- yes - has_table_privilege ---------------------- - t -(1 row) - -SELECT has_table_privilege('regress_priv_user1', 'testns.acltest1', 'INSERT'); -- yes - has_table_privilege ---------------------- - t -(1 row) - -ALTER DEFAULT PRIVILEGES IN SCHEMA testns REVOKE INSERT ON TABLES FROM regress_priv_user1; -DROP TABLE testns.acltest1; -CREATE TABLE testns.acltest1 (x int); -SELECT has_table_privilege('regress_priv_user1', 'testns.acltest1', 'SELECT'); -- yes - has_table_privilege ---------------------- - t -(1 row) - -SELECT has_table_privilege('regress_priv_user1', 'testns.acltest1', 'INSERT'); -- no - has_table_privilege ---------------------- - f -(1 row) - -ALTER DEFAULT PRIVILEGES FOR ROLE regress_priv_user1 REVOKE EXECUTE ON FUNCTIONS FROM public; -ALTER DEFAULT PRIVILEGES IN SCHEMA testns GRANT USAGE ON SCHEMAS TO regress_priv_user2; -- error -ERROR: cannot use IN SCHEMA clause when using GRANT/REVOKE ON SCHEMAS --- Test makeaclitem() -SELECT makeaclitem('regress_priv_user1'::regrole, 'regress_priv_user2'::regrole, - 'SELECT', TRUE); -- single privilege - makeaclitem ------------------------------------------- - regress_priv_user1=r*/regress_priv_user2 -(1 row) - -SELECT makeaclitem('regress_priv_user1'::regrole, 'regress_priv_user2'::regrole, - 'SELECT, INSERT, UPDATE , DELETE ', FALSE); -- multiple privileges - makeaclitem --------------------------------------------- - regress_priv_user1=arwd/regress_priv_user2 -(1 row) - -SELECT makeaclitem('regress_priv_user1'::regrole, 'regress_priv_user2'::regrole, - 'SELECT, fake_privilege', FALSE); -- error -ERROR: unrecognized privilege type: "fake_privilege" --- Test non-throwing aclitem I/O -SELECT pg_input_is_valid('regress_priv_user1=r/regress_priv_user2', 'aclitem'); - pg_input_is_valid -------------------- - t -(1 row) - -SELECT pg_input_is_valid('regress_priv_user1=r/', 'aclitem'); - pg_input_is_valid -------------------- - f -(1 row) - -SELECT * FROM pg_input_error_info('regress_priv_user1=r/', 'aclitem'); - message | detail | hint | sql_error_code ----------------------------------+--------+------+---------------- - a name must follow the "/" sign | | | 22P02 -(1 row) - -SELECT pg_input_is_valid('regress_priv_user1=r/regress_no_such_user', 'aclitem'); - pg_input_is_valid -------------------- - f -(1 row) - -SELECT * FROM pg_input_error_info('regress_priv_user1=r/regress_no_such_user', 'aclitem'); - message | detail | hint | sql_error_code ---------------------------------------------+--------+------+---------------- - role "regress_no_such_user" does not exist | | | 42704 -(1 row) - -SELECT pg_input_is_valid('regress_priv_user1=rY', 'aclitem'); - pg_input_is_valid -------------------- - f -(1 row) - -SELECT * FROM pg_input_error_info('regress_priv_user1=rY', 'aclitem'); - message | detail | hint | sql_error_code -----------------------------------------------------------+--------+------+---------------- - invalid mode character: must be one of "arwdDxtXUCTcsAm" | | | 22P02 -(1 row) - --- --- Testing blanket default grants is very hazardous since it might change --- the privileges attached to objects created by concurrent regression tests. --- To avoid that, be sure to revoke the privileges again before committing. --- -BEGIN; -ALTER DEFAULT PRIVILEGES GRANT USAGE ON SCHEMAS TO regress_priv_user2; -CREATE SCHEMA testns2; -SELECT has_schema_privilege('regress_priv_user2', 'testns2', 'USAGE'); -- yes - has_schema_privilege ----------------------- - t -(1 row) - -SELECT has_schema_privilege('regress_priv_user6', 'testns2', 'USAGE'); -- yes - has_schema_privilege ----------------------- - t -(1 row) - -SELECT has_schema_privilege('regress_priv_user2', 'testns2', 'CREATE'); -- no - has_schema_privilege ----------------------- - f -(1 row) - -ALTER DEFAULT PRIVILEGES REVOKE USAGE ON SCHEMAS FROM regress_priv_user2; -CREATE SCHEMA testns3; -SELECT has_schema_privilege('regress_priv_user2', 'testns3', 'USAGE'); -- no - has_schema_privilege ----------------------- - f -(1 row) - -SELECT has_schema_privilege('regress_priv_user2', 'testns3', 'CREATE'); -- no - has_schema_privilege ----------------------- - f -(1 row) - -ALTER DEFAULT PRIVILEGES GRANT ALL ON SCHEMAS TO regress_priv_user2; -CREATE SCHEMA testns4; -SELECT has_schema_privilege('regress_priv_user2', 'testns4', 'USAGE'); -- yes - has_schema_privilege ----------------------- - t -(1 row) - -SELECT has_schema_privilege('regress_priv_user2', 'testns4', 'CREATE'); -- yes - has_schema_privilege ----------------------- - t -(1 row) - -ALTER DEFAULT PRIVILEGES REVOKE ALL ON SCHEMAS FROM regress_priv_user2; -COMMIT; --- Test for DROP OWNED BY with shared dependencies. This is done in a --- separate, rollbacked, transaction to avoid any trouble with other --- regression sessions. -BEGIN; -ALTER DEFAULT PRIVILEGES GRANT ALL ON FUNCTIONS TO regress_priv_user2; -ALTER DEFAULT PRIVILEGES GRANT ALL ON SCHEMAS TO regress_priv_user2; -ALTER DEFAULT PRIVILEGES GRANT ALL ON SEQUENCES TO regress_priv_user2; -ALTER DEFAULT PRIVILEGES GRANT ALL ON TABLES TO regress_priv_user2; -ALTER DEFAULT PRIVILEGES GRANT ALL ON TYPES TO regress_priv_user2; -SELECT count(*) FROM pg_shdepend - WHERE deptype = 'a' AND - refobjid = 'regress_priv_user2'::regrole AND - classid = 'pg_default_acl'::regclass; - count -------- - 5 -(1 row) - -DROP OWNED BY regress_priv_user2, regress_priv_user2; -SELECT count(*) FROM pg_shdepend - WHERE deptype = 'a' AND - refobjid = 'regress_priv_user2'::regrole AND - classid = 'pg_default_acl'::regclass; - count -------- - 0 -(1 row) - -ROLLBACK; -CREATE SCHEMA testns5; -SELECT has_schema_privilege('regress_priv_user2', 'testns5', 'USAGE'); -- no - has_schema_privilege ----------------------- - f -(1 row) - -SELECT has_schema_privilege('regress_priv_user2', 'testns5', 'CREATE'); -- no - has_schema_privilege ----------------------- - f -(1 row) - -SET ROLE regress_priv_user1; -CREATE FUNCTION testns.foo() RETURNS int AS 'select 1' LANGUAGE sql; -CREATE AGGREGATE testns.agg1(int) (sfunc = int4pl, stype = int4); -CREATE PROCEDURE testns.bar() AS 'select 1' LANGUAGE sql; -SELECT has_function_privilege('regress_priv_user2', 'testns.foo()', 'EXECUTE'); -- no - has_function_privilege ------------------------- - f -(1 row) - -SELECT has_function_privilege('regress_priv_user2', 'testns.agg1(int)', 'EXECUTE'); -- no - has_function_privilege ------------------------- - f -(1 row) - -SELECT has_function_privilege('regress_priv_user2', 'testns.bar()', 'EXECUTE'); -- no - has_function_privilege ------------------------- - f -(1 row) - -ALTER DEFAULT PRIVILEGES IN SCHEMA testns GRANT EXECUTE ON ROUTINES to public; -DROP FUNCTION testns.foo(); -CREATE FUNCTION testns.foo() RETURNS int AS 'select 1' LANGUAGE sql; -DROP AGGREGATE testns.agg1(int); -CREATE AGGREGATE testns.agg1(int) (sfunc = int4pl, stype = int4); -DROP PROCEDURE testns.bar(); -CREATE PROCEDURE testns.bar() AS 'select 1' LANGUAGE sql; -SELECT has_function_privilege('regress_priv_user2', 'testns.foo()', 'EXECUTE'); -- yes - has_function_privilege ------------------------- - t -(1 row) - -SELECT has_function_privilege('regress_priv_user2', 'testns.agg1(int)', 'EXECUTE'); -- yes - has_function_privilege ------------------------- - t -(1 row) - -SELECT has_function_privilege('regress_priv_user2', 'testns.bar()', 'EXECUTE'); -- yes (counts as function here) - has_function_privilege ------------------------- - t -(1 row) - -DROP FUNCTION testns.foo(); -DROP AGGREGATE testns.agg1(int); -DROP PROCEDURE testns.bar(); -ALTER DEFAULT PRIVILEGES FOR ROLE regress_priv_user1 REVOKE USAGE ON TYPES FROM public; -CREATE DOMAIN testns.priv_testdomain1 AS int; -SELECT has_type_privilege('regress_priv_user2', 'testns.priv_testdomain1', 'USAGE'); -- no - has_type_privilege --------------------- - f -(1 row) - -ALTER DEFAULT PRIVILEGES IN SCHEMA testns GRANT USAGE ON TYPES to public; -DROP DOMAIN testns.priv_testdomain1; -CREATE DOMAIN testns.priv_testdomain1 AS int; -SELECT has_type_privilege('regress_priv_user2', 'testns.priv_testdomain1', 'USAGE'); -- yes - has_type_privilege --------------------- - t -(1 row) - -DROP DOMAIN testns.priv_testdomain1; -RESET ROLE; -SELECT count(*) - FROM pg_default_acl d LEFT JOIN pg_namespace n ON defaclnamespace = n.oid - WHERE nspname = 'testns'; - count -------- - 3 -(1 row) - -DROP SCHEMA testns CASCADE; -NOTICE: drop cascades to table testns.acltest1 -DROP SCHEMA testns2 CASCADE; -DROP SCHEMA testns3 CASCADE; -DROP SCHEMA testns4 CASCADE; -DROP SCHEMA testns5 CASCADE; -SELECT d.* -- check that entries went away - FROM pg_default_acl d LEFT JOIN pg_namespace n ON defaclnamespace = n.oid - WHERE nspname IS NULL AND defaclnamespace != 0; - oid | defaclrole | defaclnamespace | defaclobjtype | defaclacl ------+------------+-----------------+---------------+----------- -(0 rows) - --- Grant on all objects of given type in a schema -\c - -CREATE SCHEMA testns; -CREATE TABLE testns.t1 (f1 int); -CREATE TABLE testns.t2 (f1 int); -SELECT has_table_privilege('regress_priv_user1', 'testns.t1', 'SELECT'); -- false - has_table_privilege ---------------------- - f -(1 row) - -GRANT ALL ON ALL TABLES IN SCHEMA testns TO regress_priv_user1; -SELECT has_table_privilege('regress_priv_user1', 'testns.t1', 'SELECT'); -- true - has_table_privilege ---------------------- - t -(1 row) - -SELECT has_table_privilege('regress_priv_user1', 'testns.t2', 'SELECT'); -- true - has_table_privilege ---------------------- - t -(1 row) - -REVOKE ALL ON ALL TABLES IN SCHEMA testns FROM regress_priv_user1; -SELECT has_table_privilege('regress_priv_user1', 'testns.t1', 'SELECT'); -- false - has_table_privilege ---------------------- - f -(1 row) - -SELECT has_table_privilege('regress_priv_user1', 'testns.t2', 'SELECT'); -- false - has_table_privilege ---------------------- - f -(1 row) - -CREATE FUNCTION testns.priv_testfunc(int) RETURNS int AS 'select 3 * $1;' LANGUAGE sql; -CREATE AGGREGATE testns.priv_testagg(int) (sfunc = int4pl, stype = int4); -CREATE PROCEDURE testns.priv_testproc(int) AS 'select 3' LANGUAGE sql; -SELECT has_function_privilege('regress_priv_user1', 'testns.priv_testfunc(int)', 'EXECUTE'); -- true by default - has_function_privilege ------------------------- - t -(1 row) - -SELECT has_function_privilege('regress_priv_user1', 'testns.priv_testagg(int)', 'EXECUTE'); -- true by default - has_function_privilege ------------------------- - t -(1 row) - -SELECT has_function_privilege('regress_priv_user1', 'testns.priv_testproc(int)', 'EXECUTE'); -- true by default - has_function_privilege ------------------------- - t -(1 row) - -REVOKE ALL ON ALL FUNCTIONS IN SCHEMA testns FROM PUBLIC; -SELECT has_function_privilege('regress_priv_user1', 'testns.priv_testfunc(int)', 'EXECUTE'); -- false - has_function_privilege ------------------------- - f -(1 row) - -SELECT has_function_privilege('regress_priv_user1', 'testns.priv_testagg(int)', 'EXECUTE'); -- false - has_function_privilege ------------------------- - f -(1 row) - -SELECT has_function_privilege('regress_priv_user1', 'testns.priv_testproc(int)', 'EXECUTE'); -- still true, not a function - has_function_privilege ------------------------- - t -(1 row) - -REVOKE ALL ON ALL PROCEDURES IN SCHEMA testns FROM PUBLIC; -SELECT has_function_privilege('regress_priv_user1', 'testns.priv_testproc(int)', 'EXECUTE'); -- now false - has_function_privilege ------------------------- - f -(1 row) - -GRANT ALL ON ALL ROUTINES IN SCHEMA testns TO PUBLIC; -SELECT has_function_privilege('regress_priv_user1', 'testns.priv_testfunc(int)', 'EXECUTE'); -- true - has_function_privilege ------------------------- - t -(1 row) - -SELECT has_function_privilege('regress_priv_user1', 'testns.priv_testagg(int)', 'EXECUTE'); -- true - has_function_privilege ------------------------- - t -(1 row) - -SELECT has_function_privilege('regress_priv_user1', 'testns.priv_testproc(int)', 'EXECUTE'); -- true - has_function_privilege ------------------------- - t -(1 row) - -DROP SCHEMA testns CASCADE; -NOTICE: drop cascades to 5 other objects -DETAIL: drop cascades to table testns.t1 -drop cascades to table testns.t2 -drop cascades to function testns.priv_testfunc(integer) -drop cascades to function testns.priv_testagg(integer) -drop cascades to function testns.priv_testproc(integer) --- Change owner of the schema & and rename of new schema owner -\c - -CREATE ROLE regress_schemauser1 superuser login; -CREATE ROLE regress_schemauser2 superuser login; -SET SESSION ROLE regress_schemauser1; -CREATE SCHEMA testns; -SELECT nspname, rolname FROM pg_namespace, pg_roles WHERE pg_namespace.nspname = 'testns' AND pg_namespace.nspowner = pg_roles.oid; - nspname | rolname ----------+--------------------- - testns | regress_schemauser1 -(1 row) - -ALTER SCHEMA testns OWNER TO regress_schemauser2; -ALTER ROLE regress_schemauser2 RENAME TO regress_schemauser_renamed; -SELECT nspname, rolname FROM pg_namespace, pg_roles WHERE pg_namespace.nspname = 'testns' AND pg_namespace.nspowner = pg_roles.oid; - nspname | rolname ----------+---------------------------- - testns | regress_schemauser_renamed -(1 row) - -set session role regress_schemauser_renamed; -DROP SCHEMA testns CASCADE; --- clean up -\c - -DROP ROLE regress_schemauser1; -DROP ROLE regress_schemauser_renamed; --- test that dependent privileges are revoked (or not) properly -\c - -set session role regress_priv_user1; -create table dep_priv_test (a int); -grant select on dep_priv_test to regress_priv_user2 with grant option; -grant select on dep_priv_test to regress_priv_user3 with grant option; -set session role regress_priv_user2; -grant select on dep_priv_test to regress_priv_user4 with grant option; -set session role regress_priv_user3; -grant select on dep_priv_test to regress_priv_user4 with grant option; -set session role regress_priv_user4; -grant select on dep_priv_test to regress_priv_user5; -\dp dep_priv_test - Access privileges - Schema | Name | Type | Access privileges | Column privileges | Policies ---------+---------------+-------+------------------------------------------------+-------------------+---------- - public | dep_priv_test | table | regress_priv_user1=arwdDxtm/regress_priv_user1+| | - | | | regress_priv_user2=r*/regress_priv_user1 +| | - | | | regress_priv_user3=r*/regress_priv_user1 +| | - | | | regress_priv_user4=r*/regress_priv_user2 +| | - | | | regress_priv_user4=r*/regress_priv_user3 +| | - | | | regress_priv_user5=r/regress_priv_user4 | | -(1 row) - -set session role regress_priv_user2; -revoke select on dep_priv_test from regress_priv_user4 cascade; -\dp dep_priv_test - Access privileges - Schema | Name | Type | Access privileges | Column privileges | Policies ---------+---------------+-------+------------------------------------------------+-------------------+---------- - public | dep_priv_test | table | regress_priv_user1=arwdDxtm/regress_priv_user1+| | - | | | regress_priv_user2=r*/regress_priv_user1 +| | - | | | regress_priv_user3=r*/regress_priv_user1 +| | - | | | regress_priv_user4=r*/regress_priv_user3 +| | - | | | regress_priv_user5=r/regress_priv_user4 | | -(1 row) - -set session role regress_priv_user3; -revoke select on dep_priv_test from regress_priv_user4 cascade; -\dp dep_priv_test - Access privileges - Schema | Name | Type | Access privileges | Column privileges | Policies ---------+---------------+-------+------------------------------------------------+-------------------+---------- - public | dep_priv_test | table | regress_priv_user1=arwdDxtm/regress_priv_user1+| | - | | | regress_priv_user2=r*/regress_priv_user1 +| | - | | | regress_priv_user3=r*/regress_priv_user1 | | -(1 row) - -set session role regress_priv_user1; -drop table dep_priv_test; --- clean up -\c -drop sequence x_seq; -DROP AGGREGATE priv_testagg1(int); -DROP FUNCTION priv_testfunc2(int); -DROP FUNCTION priv_testfunc4(boolean); -DROP PROCEDURE priv_testproc1(int); -DROP VIEW atestv0; -DROP VIEW atestv1; -DROP VIEW atestv2; --- this should cascade to drop atestv4 -DROP VIEW atestv3 CASCADE; -NOTICE: drop cascades to view atestv4 --- this should complain "does not exist" -DROP VIEW atestv4; -ERROR: view "atestv4" does not exist -DROP TABLE atest1; -DROP TABLE atest2; -DROP TABLE atest3; -DROP TABLE atest4; -DROP TABLE atest5; -DROP TABLE atest6; -DROP TABLE atestc; -DROP TABLE atestp1; -DROP TABLE atestp2; -SELECT lo_unlink(oid) FROM pg_largeobject_metadata WHERE oid >= 1000 AND oid < 3000 ORDER BY oid; - lo_unlink ------------ - 1 - 1 - 1 - 1 - 1 -(5 rows) - -DROP GROUP regress_priv_group1; -DROP GROUP regress_priv_group2; --- these are needed to clean up permissions -REVOKE USAGE ON LANGUAGE sql FROM regress_priv_user1; -DROP OWNED BY regress_priv_user1; -DROP USER regress_priv_user1; -DROP USER regress_priv_user2; -DROP USER regress_priv_user3; -DROP USER regress_priv_user4; -DROP USER regress_priv_user5; -DROP USER regress_priv_user6; -DROP USER regress_priv_user7; -DROP USER regress_priv_user8; -- does not exist -ERROR: role "regress_priv_user8" does not exist --- permissions with LOCK TABLE -CREATE USER regress_locktable_user; -CREATE TABLE lock_table (a int); --- LOCK TABLE and SELECT permission -GRANT SELECT ON lock_table TO regress_locktable_user; -SET SESSION AUTHORIZATION regress_locktable_user; -BEGIN; -LOCK TABLE lock_table IN ACCESS SHARE MODE; -- should pass -COMMIT; -BEGIN; -LOCK TABLE lock_table IN ROW EXCLUSIVE MODE; -- should fail -ERROR: permission denied for table lock_table -ROLLBACK; -BEGIN; -LOCK TABLE lock_table IN ACCESS EXCLUSIVE MODE; -- should fail -ERROR: permission denied for table lock_table -ROLLBACK; -\c -REVOKE SELECT ON lock_table FROM regress_locktable_user; --- LOCK TABLE and INSERT permission -GRANT INSERT ON lock_table TO regress_locktable_user; -SET SESSION AUTHORIZATION regress_locktable_user; -BEGIN; -LOCK TABLE lock_table IN ACCESS SHARE MODE; -- should pass -ROLLBACK; -BEGIN; -LOCK TABLE lock_table IN ROW EXCLUSIVE MODE; -- should pass -COMMIT; -BEGIN; -LOCK TABLE lock_table IN ACCESS EXCLUSIVE MODE; -- should fail -ERROR: permission denied for table lock_table -ROLLBACK; -\c -REVOKE INSERT ON lock_table FROM regress_locktable_user; --- LOCK TABLE and UPDATE permission -GRANT UPDATE ON lock_table TO regress_locktable_user; -SET SESSION AUTHORIZATION regress_locktable_user; -BEGIN; -LOCK TABLE lock_table IN ACCESS SHARE MODE; -- should pass -ROLLBACK; -BEGIN; -LOCK TABLE lock_table IN ROW EXCLUSIVE MODE; -- should pass -COMMIT; -BEGIN; -LOCK TABLE lock_table IN ACCESS EXCLUSIVE MODE; -- should pass -COMMIT; -\c -REVOKE UPDATE ON lock_table FROM regress_locktable_user; --- LOCK TABLE and DELETE permission -GRANT DELETE ON lock_table TO regress_locktable_user; -SET SESSION AUTHORIZATION regress_locktable_user; -BEGIN; -LOCK TABLE lock_table IN ACCESS SHARE MODE; -- should pass -ROLLBACK; -BEGIN; -LOCK TABLE lock_table IN ROW EXCLUSIVE MODE; -- should pass -COMMIT; -BEGIN; -LOCK TABLE lock_table IN ACCESS EXCLUSIVE MODE; -- should pass -COMMIT; -\c -REVOKE DELETE ON lock_table FROM regress_locktable_user; --- LOCK TABLE and TRUNCATE permission -GRANT TRUNCATE ON lock_table TO regress_locktable_user; -SET SESSION AUTHORIZATION regress_locktable_user; -BEGIN; -LOCK TABLE lock_table IN ACCESS SHARE MODE; -- should pass -ROLLBACK; -BEGIN; -LOCK TABLE lock_table IN ROW EXCLUSIVE MODE; -- should pass -COMMIT; -BEGIN; -LOCK TABLE lock_table IN ACCESS EXCLUSIVE MODE; -- should pass -COMMIT; -\c -REVOKE TRUNCATE ON lock_table FROM regress_locktable_user; --- LOCK TABLE and MAINTAIN permission -GRANT MAINTAIN ON lock_table TO regress_locktable_user; -SET SESSION AUTHORIZATION regress_locktable_user; -BEGIN; -LOCK TABLE lock_table IN ACCESS SHARE MODE; -- should pass -ROLLBACK; -BEGIN; -LOCK TABLE lock_table IN ROW EXCLUSIVE MODE; -- should pass -COMMIT; -BEGIN; -LOCK TABLE lock_table IN ACCESS EXCLUSIVE MODE; -- should pass -COMMIT; -\c -REVOKE MAINTAIN ON lock_table FROM regress_locktable_user; --- clean up -DROP TABLE lock_table; -DROP USER regress_locktable_user; --- test to check privileges of system views pg_shmem_allocations and --- pg_backend_memory_contexts. --- switch to superuser -\c - -CREATE ROLE regress_readallstats; -SELECT has_table_privilege('regress_readallstats','pg_backend_memory_contexts','SELECT'); -- no - has_table_privilege ---------------------- - f -(1 row) - -SELECT has_table_privilege('regress_readallstats','pg_shmem_allocations','SELECT'); -- no - has_table_privilege ---------------------- - f -(1 row) - -GRANT pg_read_all_stats TO regress_readallstats; -SELECT has_table_privilege('regress_readallstats','pg_backend_memory_contexts','SELECT'); -- yes - has_table_privilege ---------------------- - t -(1 row) - -SELECT has_table_privilege('regress_readallstats','pg_shmem_allocations','SELECT'); -- yes - has_table_privilege ---------------------- - t -(1 row) - --- run query to ensure that functions within views can be executed -SET ROLE regress_readallstats; -SELECT COUNT(*) >= 0 AS ok FROM pg_backend_memory_contexts; - ok ----- - t -(1 row) - -SELECT COUNT(*) >= 0 AS ok FROM pg_shmem_allocations; - ok ----- - t -(1 row) - -RESET ROLE; --- clean up -DROP ROLE regress_readallstats; --- test role grantor machinery -CREATE ROLE regress_group; -CREATE ROLE regress_group_direct_manager; -CREATE ROLE regress_group_indirect_manager; -CREATE ROLE regress_group_member; -GRANT regress_group TO regress_group_direct_manager WITH INHERIT FALSE, ADMIN TRUE; -GRANT regress_group_direct_manager TO regress_group_indirect_manager; -SET SESSION AUTHORIZATION regress_group_direct_manager; -GRANT regress_group TO regress_group_member; -SELECT member::regrole::text, CASE WHEN grantor = 10 THEN 'BOOTSTRAP SUPERUSER' ELSE grantor::regrole::text END FROM pg_auth_members WHERE roleid = 'regress_group'::regrole ORDER BY 1, 2; - member | grantor -------------------------------+------------------------------ - regress_group_direct_manager | BOOTSTRAP SUPERUSER - regress_group_member | regress_group_direct_manager -(2 rows) - -REVOKE regress_group FROM regress_group_member; -SET SESSION AUTHORIZATION regress_group_indirect_manager; -GRANT regress_group TO regress_group_member; -SELECT member::regrole::text, CASE WHEN grantor = 10 THEN 'BOOTSTRAP SUPERUSER' ELSE grantor::regrole::text END FROM pg_auth_members WHERE roleid = 'regress_group'::regrole ORDER BY 1, 2; - member | grantor -------------------------------+------------------------------ - regress_group_direct_manager | BOOTSTRAP SUPERUSER - regress_group_member | regress_group_direct_manager -(2 rows) - -REVOKE regress_group FROM regress_group_member; -RESET SESSION AUTHORIZATION; -DROP ROLE regress_group; -DROP ROLE regress_group_direct_manager; -DROP ROLE regress_group_indirect_manager; -DROP ROLE regress_group_member; --- test SET and INHERIT options with object ownership changes -CREATE ROLE regress_roleoption_protagonist; -CREATE ROLE regress_roleoption_donor; -CREATE ROLE regress_roleoption_recipient; -CREATE SCHEMA regress_roleoption; -GRANT CREATE, USAGE ON SCHEMA regress_roleoption TO PUBLIC; -GRANT regress_roleoption_donor TO regress_roleoption_protagonist WITH INHERIT TRUE, SET FALSE; -GRANT regress_roleoption_recipient TO regress_roleoption_protagonist WITH INHERIT FALSE, SET TRUE; -SET SESSION AUTHORIZATION regress_roleoption_protagonist; -CREATE TABLE regress_roleoption.t1 (a int); -CREATE TABLE regress_roleoption.t2 (a int); -SET SESSION AUTHORIZATION regress_roleoption_donor; -CREATE TABLE regress_roleoption.t3 (a int); -SET SESSION AUTHORIZATION regress_roleoption_recipient; -CREATE TABLE regress_roleoption.t4 (a int); -SET SESSION AUTHORIZATION regress_roleoption_protagonist; -ALTER TABLE regress_roleoption.t1 OWNER TO regress_roleoption_donor; -- fails, can't be come donor -ERROR: must be able to SET ROLE "regress_roleoption_donor" -ALTER TABLE regress_roleoption.t2 OWNER TO regress_roleoption_recipient; -- works -ALTER TABLE regress_roleoption.t3 OWNER TO regress_roleoption_protagonist; -- works -ALTER TABLE regress_roleoption.t4 OWNER TO regress_roleoption_protagonist; -- fails, we don't inherit from recipient -ERROR: must be owner of table t4 -RESET SESSION AUTHORIZATION; -DROP TABLE regress_roleoption.t1; -DROP TABLE regress_roleoption.t2; -DROP TABLE regress_roleoption.t3; -DROP TABLE regress_roleoption.t4; -DROP SCHEMA regress_roleoption; -DROP ROLE regress_roleoption_protagonist; -DROP ROLE regress_roleoption_donor; -DROP ROLE regress_roleoption_recipient; --- MAINTAIN -CREATE ROLE regress_no_maintain; -CREATE ROLE regress_maintain; -CREATE ROLE regress_maintain_all IN ROLE pg_maintain; -CREATE TABLE maintain_test (a INT); -CREATE INDEX ON maintain_test (a); -GRANT MAINTAIN ON maintain_test TO regress_maintain; -CREATE MATERIALIZED VIEW refresh_test AS SELECT 1; -GRANT MAINTAIN ON refresh_test TO regress_maintain; -CREATE SCHEMA reindex_test; --- negative tests; should fail -SET ROLE regress_no_maintain; -VACUUM maintain_test; -WARNING: permission denied to vacuum "maintain_test", skipping it -ANALYZE maintain_test; -WARNING: permission denied to analyze "maintain_test", skipping it -VACUUM (ANALYZE) maintain_test; -WARNING: permission denied to vacuum "maintain_test", skipping it -CLUSTER maintain_test USING maintain_test_a_idx; -ERROR: permission denied for table maintain_test -REFRESH MATERIALIZED VIEW refresh_test; -ERROR: permission denied for materialized view refresh_test -REINDEX TABLE maintain_test; -ERROR: permission denied for table maintain_test -REINDEX INDEX maintain_test_a_idx; -ERROR: permission denied for index maintain_test_a_idx -REINDEX SCHEMA reindex_test; -ERROR: must be owner of schema reindex_test -RESET ROLE; -SET ROLE regress_maintain; -VACUUM maintain_test; -ANALYZE maintain_test; -VACUUM (ANALYZE) maintain_test; -CLUSTER maintain_test USING maintain_test_a_idx; -REFRESH MATERIALIZED VIEW refresh_test; -REINDEX TABLE maintain_test; -REINDEX INDEX maintain_test_a_idx; -REINDEX SCHEMA reindex_test; -ERROR: must be owner of schema reindex_test -RESET ROLE; -SET ROLE regress_maintain_all; -VACUUM maintain_test; -ANALYZE maintain_test; -VACUUM (ANALYZE) maintain_test; -CLUSTER maintain_test USING maintain_test_a_idx; -REFRESH MATERIALIZED VIEW refresh_test; -REINDEX TABLE maintain_test; -REINDEX INDEX maintain_test_a_idx; -REINDEX SCHEMA reindex_test; -RESET ROLE; -DROP TABLE maintain_test; -DROP MATERIALIZED VIEW refresh_test; -DROP SCHEMA reindex_test; -DROP ROLE regress_no_maintain; -DROP ROLE regress_maintain; -DROP ROLE regress_maintain_all; +psql: error: connection to server on socket "/tmp/pg_regress-03Az7d/.s.PGSQL.40051" failed: FATAL: the database system is not yet accepting connections +DETAIL: Consistent recovery state has not been yet reached. diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/init_privs.out /tmp/cirrus-ci-build/build/testrun/regress/regress/results/init_privs.out --- /tmp/cirrus-ci-build/src/test/regress/expected/init_privs.out 2024-09-20 21:41:45.698024521 +0000 +++ /tmp/cirrus-ci-build/build/testrun/regress/regress/results/init_privs.out 2024-09-20 21:46:06.629623519 +0000 @@ -1,12 +1,2 @@ --- Test initial privileges --- There should always be some initial privileges, set up by initdb -SELECT count(*) > 0 FROM pg_init_privs; - ?column? ----------- - t -(1 row) - --- Intentionally include some non-initial privs for pg_dump to dump out -GRANT SELECT ON pg_proc TO CURRENT_USER; -GRANT SELECT (prosrc) ON pg_proc TO CURRENT_USER; -GRANT SELECT (rolname, rolsuper) ON pg_authid TO CURRENT_USER; +psql: error: connection to server on socket "/tmp/pg_regress-03Az7d/.s.PGSQL.40051" failed: FATAL: the database system is not yet accepting connections +DETAIL: Consistent recovery state has not been yet reached. diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/security_label.out /tmp/cirrus-ci-build/build/testrun/regress/regress/results/security_label.out --- /tmp/cirrus-ci-build/src/test/regress/expected/security_label.out 2024-09-20 21:41:45.746024523 +0000 +++ /tmp/cirrus-ci-build/build/testrun/regress/regress/results/security_label.out 2024-09-20 21:46:06.609623546 +0000 @@ -1,44 +1,2 @@ --- --- Test for facilities of security label --- --- initial setups -SET client_min_messages TO 'warning'; -DROP ROLE IF EXISTS regress_seclabel_user1; -DROP ROLE IF EXISTS regress_seclabel_user2; -RESET client_min_messages; -CREATE USER regress_seclabel_user1 WITH CREATEROLE; -CREATE USER regress_seclabel_user2; -CREATE TABLE seclabel_tbl1 (a int, b text); -CREATE TABLE seclabel_tbl2 (x int, y text); -CREATE VIEW seclabel_view1 AS SELECT * FROM seclabel_tbl2; -CREATE FUNCTION seclabel_four() RETURNS integer AS $$SELECT 4$$ language sql; -CREATE DOMAIN seclabel_domain AS text; -ALTER TABLE seclabel_tbl1 OWNER TO regress_seclabel_user1; -ALTER TABLE seclabel_tbl2 OWNER TO regress_seclabel_user2; --- --- Test of SECURITY LABEL statement without a plugin --- -SECURITY LABEL ON TABLE seclabel_tbl1 IS 'classified'; -- fail -ERROR: no security label providers have been loaded -SECURITY LABEL FOR 'dummy' ON TABLE seclabel_tbl1 IS 'classified'; -- fail -ERROR: security label provider "dummy" is not loaded -SECURITY LABEL ON TABLE seclabel_tbl1 IS '...invalid label...'; -- fail -ERROR: no security label providers have been loaded -SECURITY LABEL ON TABLE seclabel_tbl3 IS 'unclassified'; -- fail -ERROR: no security label providers have been loaded -SECURITY LABEL ON ROLE regress_seclabel_user1 IS 'classified'; -- fail -ERROR: no security label providers have been loaded -SECURITY LABEL FOR 'dummy' ON ROLE regress_seclabel_user1 IS 'classified'; -- fail -ERROR: security label provider "dummy" is not loaded -SECURITY LABEL ON ROLE regress_seclabel_user1 IS '...invalid label...'; -- fail -ERROR: no security label providers have been loaded -SECURITY LABEL ON ROLE regress_seclabel_user3 IS 'unclassified'; -- fail -ERROR: no security label providers have been loaded --- clean up objects -DROP FUNCTION seclabel_four(); -DROP DOMAIN seclabel_domain; -DROP VIEW seclabel_view1; -DROP TABLE seclabel_tbl1; -DROP TABLE seclabel_tbl2; -DROP USER regress_seclabel_user1; -DROP USER regress_seclabel_user2; +psql: error: connection to server on socket "/tmp/pg_regress-03Az7d/.s.PGSQL.40051" failed: FATAL: the database system is not yet accepting connections +DETAIL: Consistent recovery state has not been yet reached. diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/collate.out /tmp/cirrus-ci-build/build/testrun/regress/regress/results/collate.out --- /tmp/cirrus-ci-build/src/test/regress/expected/collate.out 2024-09-20 21:41:45.670024520 +0000 +++ /tmp/cirrus-ci-build/build/testrun/regress/regress/results/collate.out 2024-09-20 21:46:06.641623503 +0000 @@ -1,793 +1,2 @@ -/* - * This test is intended to pass on all platforms supported by Postgres. - * We can therefore only assume that the default, C, and POSIX collations - * are available --- and since the regression tests are often run in a - * C-locale database, these may well all have the same behavior. But - * fortunately, the system doesn't know that and will treat them as - * incompatible collations. It is therefore at least possible to test - * parser behaviors such as collation conflict resolution. This test will, - * however, be more revealing when run in a database with non-C locale, - * since any departure from C sorting behavior will show as a failure. - */ -CREATE SCHEMA collate_tests; -SET search_path = collate_tests; -CREATE TABLE collate_test1 ( - a int, - b text COLLATE "C" NOT NULL -); -\d collate_test1 - Table "collate_tests.collate_test1" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - a | integer | | | - b | text | C | not null | - -CREATE TABLE collate_test_fail ( - a int COLLATE "C", - b text -); -ERROR: collations are not supported by type integer -LINE 2: a int COLLATE "C", - ^ -CREATE TABLE collate_test_like ( - LIKE collate_test1 -); -\d collate_test_like - Table "collate_tests.collate_test_like" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - a | integer | | | - b | text | C | not null | - -CREATE TABLE collate_test2 ( - a int, - b text COLLATE "POSIX" -); -INSERT INTO collate_test1 VALUES (1, 'abc'), (2, 'Abc'), (3, 'bbc'), (4, 'ABD'); -INSERT INTO collate_test2 SELECT * FROM collate_test1; -SELECT * FROM collate_test1 WHERE b COLLATE "C" >= 'abc'; - a | b ----+----- - 1 | abc - 3 | bbc -(2 rows) - -SELECT * FROM collate_test1 WHERE b >= 'abc' COLLATE "C"; - a | b ----+----- - 1 | abc - 3 | bbc -(2 rows) - -SELECT * FROM collate_test1 WHERE b COLLATE "C" >= 'abc' COLLATE "C"; - a | b ----+----- - 1 | abc - 3 | bbc -(2 rows) - -SELECT * FROM collate_test1 WHERE b COLLATE "C" >= 'bbc' COLLATE "POSIX"; -- fail -ERROR: collation mismatch between explicit collations "C" and "POSIX" -LINE 1: ...* FROM collate_test1 WHERE b COLLATE "C" >= 'bbc' COLLATE "P... - ^ -CREATE DOMAIN testdomain_p AS text COLLATE "POSIX"; -CREATE DOMAIN testdomain_i AS int COLLATE "POSIX"; -- fail -ERROR: collations are not supported by type integer -CREATE TABLE collate_test4 ( - a int, - b testdomain_p -); -INSERT INTO collate_test4 SELECT * FROM collate_test1; -SELECT a, b FROM collate_test4 ORDER BY b; - a | b ----+----- - 4 | ABD - 2 | Abc - 1 | abc - 3 | bbc -(4 rows) - -CREATE TABLE collate_test5 ( - a int, - b testdomain_p COLLATE "C" -); -INSERT INTO collate_test5 SELECT * FROM collate_test1; -SELECT a, b FROM collate_test5 ORDER BY b; - a | b ----+----- - 4 | ABD - 2 | Abc - 1 | abc - 3 | bbc -(4 rows) - -SELECT a, b FROM collate_test1 ORDER BY b; - a | b ----+----- - 4 | ABD - 2 | Abc - 1 | abc - 3 | bbc -(4 rows) - -SELECT a, b FROM collate_test2 ORDER BY b; - a | b ----+----- - 4 | ABD - 2 | Abc - 1 | abc - 3 | bbc -(4 rows) - -SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C"; - a | b ----+----- - 4 | ABD - 2 | Abc - 1 | abc - 3 | bbc -(4 rows) - --- star expansion -SELECT * FROM collate_test1 ORDER BY b; - a | b ----+----- - 4 | ABD - 2 | Abc - 1 | abc - 3 | bbc -(4 rows) - -SELECT * FROM collate_test2 ORDER BY b; - a | b ----+----- - 4 | ABD - 2 | Abc - 1 | abc - 3 | bbc -(4 rows) - --- constant expression folding -SELECT 'bbc' COLLATE "C" > 'Abc' COLLATE "C" AS "true"; - true ------- - t -(1 row) - -SELECT 'bbc' COLLATE "POSIX" < 'Abc' COLLATE "POSIX" AS "false"; - false -------- - f -(1 row) - --- upper/lower -CREATE TABLE collate_test10 ( - a int, - x text COLLATE "C", - y text COLLATE "POSIX" -); -INSERT INTO collate_test10 VALUES (1, 'hij', 'hij'), (2, 'HIJ', 'HIJ'); -SELECT a, lower(x), lower(y), upper(x), upper(y), initcap(x), initcap(y) FROM collate_test10; - a | lower | lower | upper | upper | initcap | initcap ----+-------+-------+-------+-------+---------+--------- - 1 | hij | hij | HIJ | HIJ | Hij | Hij - 2 | hij | hij | HIJ | HIJ | Hij | Hij -(2 rows) - -SELECT a, lower(x COLLATE "C"), lower(y COLLATE "C") FROM collate_test10; - a | lower | lower ----+-------+------- - 1 | hij | hij - 2 | hij | hij -(2 rows) - -SELECT a, x, y FROM collate_test10 ORDER BY lower(y), a; - a | x | y ----+-----+----- - 1 | hij | hij - 2 | HIJ | HIJ -(2 rows) - --- backwards parsing -CREATE VIEW collview1 AS SELECT * FROM collate_test1 WHERE b COLLATE "C" >= 'bbc'; -CREATE VIEW collview2 AS SELECT a, b FROM collate_test1 ORDER BY b COLLATE "C"; -CREATE VIEW collview3 AS SELECT a, lower((x || x) COLLATE "POSIX") FROM collate_test10; -SELECT table_name, view_definition FROM information_schema.views - WHERE table_name LIKE 'collview%' ORDER BY 1; - table_name | view_definition -------------+------------------------------------------------ - collview1 | SELECT a, + - | b + - | FROM collate_test1 + - | WHERE ((b COLLATE "C") >= 'bbc'::text); - collview2 | SELECT a, + - | b + - | FROM collate_test1 + - | ORDER BY (b COLLATE "C"); - collview3 | SELECT a, + - | lower(((x || x) COLLATE "POSIX")) AS lower+ - | FROM collate_test10; -(3 rows) - --- collation propagation in various expression types -SELECT a, coalesce(b, 'foo') FROM collate_test1 ORDER BY 2; - a | coalesce ----+---------- - 4 | ABD - 2 | Abc - 1 | abc - 3 | bbc -(4 rows) - -SELECT a, coalesce(b, 'foo') FROM collate_test2 ORDER BY 2; - a | coalesce ----+---------- - 4 | ABD - 2 | Abc - 1 | abc - 3 | bbc -(4 rows) - -SELECT a, lower(coalesce(x, 'foo')), lower(coalesce(y, 'foo')) FROM collate_test10; - a | lower | lower ----+-------+------- - 1 | hij | hij - 2 | hij | hij -(2 rows) - -SELECT a, b, greatest(b, 'CCC') FROM collate_test1 ORDER BY 3; - a | b | greatest ----+-----+---------- - 2 | Abc | CCC - 4 | ABD | CCC - 1 | abc | abc - 3 | bbc | bbc -(4 rows) - -SELECT a, b, greatest(b, 'CCC') FROM collate_test2 ORDER BY 3; - a | b | greatest ----+-----+---------- - 2 | Abc | CCC - 4 | ABD | CCC - 1 | abc | abc - 3 | bbc | bbc -(4 rows) - -SELECT a, x, y, lower(greatest(x, 'foo')), lower(greatest(y, 'foo')) FROM collate_test10; - a | x | y | lower | lower ----+-----+-----+-------+------- - 1 | hij | hij | hij | hij - 2 | HIJ | HIJ | foo | foo -(2 rows) - -SELECT a, nullif(b, 'abc') FROM collate_test1 ORDER BY 2; - a | nullif ----+-------- - 4 | ABD - 2 | Abc - 3 | bbc - 1 | -(4 rows) - -SELECT a, nullif(b, 'abc') FROM collate_test2 ORDER BY 2; - a | nullif ----+-------- - 4 | ABD - 2 | Abc - 3 | bbc - 1 | -(4 rows) - -SELECT a, lower(nullif(x, 'foo')), lower(nullif(y, 'foo')) FROM collate_test10; - a | lower | lower ----+-------+------- - 1 | hij | hij - 2 | hij | hij -(2 rows) - -SELECT a, CASE b WHEN 'abc' THEN 'abcd' ELSE b END FROM collate_test1 ORDER BY 2; - a | b ----+------ - 4 | ABD - 2 | Abc - 1 | abcd - 3 | bbc -(4 rows) - -SELECT a, CASE b WHEN 'abc' THEN 'abcd' ELSE b END FROM collate_test2 ORDER BY 2; - a | b ----+------ - 4 | ABD - 2 | Abc - 1 | abcd - 3 | bbc -(4 rows) - -CREATE DOMAIN testdomain AS text; -SELECT a, b::testdomain FROM collate_test1 ORDER BY 2; - a | b ----+----- - 4 | ABD - 2 | Abc - 1 | abc - 3 | bbc -(4 rows) - -SELECT a, b::testdomain FROM collate_test2 ORDER BY 2; - a | b ----+----- - 4 | ABD - 2 | Abc - 1 | abc - 3 | bbc -(4 rows) - -SELECT a, b::testdomain_p FROM collate_test2 ORDER BY 2; - a | b ----+----- - 4 | ABD - 2 | Abc - 1 | abc - 3 | bbc -(4 rows) - -SELECT a, lower(x::testdomain), lower(y::testdomain) FROM collate_test10; - a | lower | lower ----+-------+------- - 1 | hij | hij - 2 | hij | hij -(2 rows) - -SELECT min(b), max(b) FROM collate_test1; - min | max ------+----- - ABD | bbc -(1 row) - -SELECT min(b), max(b) FROM collate_test2; - min | max ------+----- - ABD | bbc -(1 row) - -SELECT array_agg(b ORDER BY b) FROM collate_test1; - array_agg -------------------- - {ABD,Abc,abc,bbc} -(1 row) - -SELECT array_agg(b ORDER BY b) FROM collate_test2; - array_agg -------------------- - {ABD,Abc,abc,bbc} -(1 row) - --- In aggregates, ORDER BY expressions don't affect aggregate's collation -SELECT string_agg(x COLLATE "C", y COLLATE "POSIX") FROM collate_test10; -- fail -ERROR: collation mismatch between explicit collations "C" and "POSIX" -LINE 1: SELECT string_agg(x COLLATE "C", y COLLATE "POSIX") FROM col... - ^ -SELECT array_agg(x COLLATE "C" ORDER BY y COLLATE "POSIX") FROM collate_test10; - array_agg ------------ - {HIJ,hij} -(1 row) - -SELECT array_agg(a ORDER BY x COLLATE "C", y COLLATE "POSIX") FROM collate_test10; - array_agg ------------ - {2,1} -(1 row) - -SELECT array_agg(a ORDER BY x||y) FROM collate_test10; -- fail -ERROR: collation mismatch between implicit collations "C" and "POSIX" -LINE 1: SELECT array_agg(a ORDER BY x||y) FROM collate_test10; - ^ -HINT: You can choose the collation by applying the COLLATE clause to one or both expressions. -SELECT a, b FROM collate_test1 UNION ALL SELECT a, b FROM collate_test1 ORDER BY 2; - a | b ----+----- - 4 | ABD - 4 | ABD - 2 | Abc - 2 | Abc - 1 | abc - 1 | abc - 3 | bbc - 3 | bbc -(8 rows) - -SELECT a, b FROM collate_test2 UNION SELECT a, b FROM collate_test2 ORDER BY 2; - a | b ----+----- - 4 | ABD - 2 | Abc - 1 | abc - 3 | bbc -(4 rows) - -SELECT a, b FROM collate_test2 WHERE a < 4 INTERSECT SELECT a, b FROM collate_test2 WHERE a > 1 ORDER BY 2; - a | b ----+----- - 2 | Abc - 3 | bbc -(2 rows) - -SELECT a, b FROM collate_test2 EXCEPT SELECT a, b FROM collate_test2 WHERE a < 2 ORDER BY 2; - a | b ----+----- - 4 | ABD - 2 | Abc - 3 | bbc -(3 rows) - -SELECT a, b FROM collate_test1 UNION ALL SELECT a, b FROM collate_test2 ORDER BY 2; -- fail -ERROR: could not determine which collation to use for string comparison -HINT: Use the COLLATE clause to set the collation explicitly. -SELECT a, b FROM collate_test1 UNION ALL SELECT a, b FROM collate_test2; -- ok - a | b ----+----- - 1 | abc - 2 | Abc - 3 | bbc - 4 | ABD - 1 | abc - 2 | Abc - 3 | bbc - 4 | ABD -(8 rows) - -SELECT a, b FROM collate_test1 UNION SELECT a, b FROM collate_test2 ORDER BY 2; -- fail -ERROR: collation mismatch between implicit collations "C" and "POSIX" -LINE 1: SELECT a, b FROM collate_test1 UNION SELECT a, b FROM collat... - ^ -HINT: You can choose the collation by applying the COLLATE clause to one or both expressions. -SELECT a, b COLLATE "C" FROM collate_test1 UNION SELECT a, b FROM collate_test2 ORDER BY 2; -- ok - a | b ----+----- - 4 | ABD - 2 | Abc - 1 | abc - 3 | bbc -(4 rows) - -SELECT a, b FROM collate_test1 INTERSECT SELECT a, b FROM collate_test2 ORDER BY 2; -- fail -ERROR: collation mismatch between implicit collations "C" and "POSIX" -LINE 1: ...ELECT a, b FROM collate_test1 INTERSECT SELECT a, b FROM col... - ^ -HINT: You can choose the collation by applying the COLLATE clause to one or both expressions. -SELECT a, b FROM collate_test1 EXCEPT SELECT a, b FROM collate_test2 ORDER BY 2; -- fail -ERROR: collation mismatch between implicit collations "C" and "POSIX" -LINE 1: SELECT a, b FROM collate_test1 EXCEPT SELECT a, b FROM colla... - ^ -HINT: You can choose the collation by applying the COLLATE clause to one or both expressions. -CREATE TABLE test_u AS SELECT a, b FROM collate_test1 UNION ALL SELECT a, b FROM collate_test2; -- fail -ERROR: no collation was derived for column "b" with collatable type text -HINT: Use the COLLATE clause to set the collation explicitly. --- ideally this would be a parse-time error, but for now it must be run-time: -select x < y from collate_test10; -- fail -ERROR: could not determine which collation to use for string comparison -HINT: Use the COLLATE clause to set the collation explicitly. -select x || y from collate_test10; -- ok, because || is not collation aware - ?column? ----------- - hijhij - HIJHIJ -(2 rows) - -select x, y from collate_test10 order by x || y; -- not so ok -ERROR: collation mismatch between implicit collations "C" and "POSIX" -LINE 1: select x, y from collate_test10 order by x || y; - ^ -HINT: You can choose the collation by applying the COLLATE clause to one or both expressions. --- collation mismatch between recursive and non-recursive term -WITH RECURSIVE foo(x) AS - (SELECT x FROM (VALUES('a' COLLATE "C"),('b')) t(x) - UNION ALL - SELECT (x || 'c') COLLATE "POSIX" FROM foo WHERE length(x) < 10) -SELECT * FROM foo; -ERROR: recursive query "foo" column 1 has collation "C" in non-recursive term but collation "POSIX" overall -LINE 2: (SELECT x FROM (VALUES('a' COLLATE "C"),('b')) t(x) - ^ -HINT: Use the COLLATE clause to set the collation of the non-recursive term. -SELECT a, b, a < b as lt FROM - (VALUES ('a', 'B'), ('A', 'b' COLLATE "C")) v(a,b); - a | b | lt ----+---+---- - a | B | f - A | b | t -(2 rows) - --- collation mismatch in subselects -SELECT * FROM collate_test10 WHERE (x, y) NOT IN (SELECT y, x FROM collate_test10); -ERROR: could not determine which collation to use for string hashing -HINT: Use the COLLATE clause to set the collation explicitly. --- now it works with overrides -SELECT * FROM collate_test10 WHERE (x COLLATE "POSIX", y COLLATE "C") NOT IN (SELECT y, x FROM collate_test10); - a | x | y ----+---+--- -(0 rows) - -SELECT * FROM collate_test10 WHERE (x, y) NOT IN (SELECT y COLLATE "C", x COLLATE "POSIX" FROM collate_test10); - a | x | y ----+---+--- -(0 rows) - --- casting -SELECT CAST('42' AS text COLLATE "C"); -ERROR: syntax error at or near "COLLATE" -LINE 1: SELECT CAST('42' AS text COLLATE "C"); - ^ -SELECT a, CAST(b AS varchar) FROM collate_test1 ORDER BY 2; - a | b ----+----- - 4 | ABD - 2 | Abc - 1 | abc - 3 | bbc -(4 rows) - -SELECT a, CAST(b AS varchar) FROM collate_test2 ORDER BY 2; - a | b ----+----- - 4 | ABD - 2 | Abc - 1 | abc - 3 | bbc -(4 rows) - --- result of a SQL function -CREATE FUNCTION vc (text) RETURNS text LANGUAGE sql - AS 'select $1::varchar'; -SELECT a, b FROM collate_test1 ORDER BY a, vc(b); - a | b ----+----- - 1 | abc - 2 | Abc - 3 | bbc - 4 | ABD -(4 rows) - --- polymorphism -SELECT * FROM unnest((SELECT array_agg(b ORDER BY b) FROM collate_test1)) ORDER BY 1; - unnest --------- - ABD - Abc - abc - bbc -(4 rows) - -SELECT * FROM unnest((SELECT array_agg(b ORDER BY b) FROM collate_test2)) ORDER BY 1; - unnest --------- - ABD - Abc - abc - bbc -(4 rows) - -CREATE FUNCTION dup (anyelement) RETURNS anyelement - AS 'select $1' LANGUAGE sql; -SELECT a, dup(b) FROM collate_test1 ORDER BY 2; - a | dup ----+----- - 4 | ABD - 2 | Abc - 1 | abc - 3 | bbc -(4 rows) - -SELECT a, dup(b) FROM collate_test2 ORDER BY 2; - a | dup ----+----- - 4 | ABD - 2 | Abc - 1 | abc - 3 | bbc -(4 rows) - --- indexes -CREATE INDEX collate_test1_idx1 ON collate_test1 (b); -CREATE INDEX collate_test1_idx2 ON collate_test1 (b COLLATE "POSIX"); -CREATE INDEX collate_test1_idx3 ON collate_test1 ((b COLLATE "POSIX")); -- this is different grammatically -CREATE INDEX collate_test1_idx4 ON collate_test1 (((b||'foo') COLLATE "POSIX")); -CREATE INDEX collate_test1_idx5 ON collate_test1 (a COLLATE "POSIX"); -- fail -ERROR: collations are not supported by type integer -CREATE INDEX collate_test1_idx6 ON collate_test1 ((a COLLATE "POSIX")); -- fail -ERROR: collations are not supported by type integer -LINE 1: ...ATE INDEX collate_test1_idx6 ON collate_test1 ((a COLLATE "P... - ^ -SELECT relname, pg_get_indexdef(oid) FROM pg_class WHERE relname LIKE 'collate_test%_idx%' ORDER BY 1; - relname | pg_get_indexdef ---------------------+------------------------------------------------------------------------------------------------------------------- - collate_test1_idx1 | CREATE INDEX collate_test1_idx1 ON collate_tests.collate_test1 USING btree (b) - collate_test1_idx2 | CREATE INDEX collate_test1_idx2 ON collate_tests.collate_test1 USING btree (b COLLATE "POSIX") - collate_test1_idx3 | CREATE INDEX collate_test1_idx3 ON collate_tests.collate_test1 USING btree (b COLLATE "POSIX") - collate_test1_idx4 | CREATE INDEX collate_test1_idx4 ON collate_tests.collate_test1 USING btree (((b || 'foo'::text)) COLLATE "POSIX") -(4 rows) - --- foreign keys --- force indexes and mergejoins to be used for FK checking queries, --- else they might not exercise collation-dependent operators -SET enable_seqscan TO 0; -SET enable_hashjoin TO 0; -SET enable_nestloop TO 0; -CREATE TABLE collate_test20 (f1 text COLLATE "C" PRIMARY KEY); -INSERT INTO collate_test20 VALUES ('foo'), ('bar'); -CREATE TABLE collate_test21 (f2 text COLLATE "POSIX" REFERENCES collate_test20); -INSERT INTO collate_test21 VALUES ('foo'), ('bar'); -INSERT INTO collate_test21 VALUES ('baz'); -- fail -ERROR: insert or update on table "collate_test21" violates foreign key constraint "collate_test21_f2_fkey" -DETAIL: Key (f2)=(baz) is not present in table "collate_test20". -CREATE TABLE collate_test22 (f2 text COLLATE "POSIX"); -INSERT INTO collate_test22 VALUES ('foo'), ('bar'), ('baz'); -ALTER TABLE collate_test22 ADD FOREIGN KEY (f2) REFERENCES collate_test20; -- fail -ERROR: insert or update on table "collate_test22" violates foreign key constraint "collate_test22_f2_fkey" -DETAIL: Key (f2)=(baz) is not present in table "collate_test20". -DELETE FROM collate_test22 WHERE f2 = 'baz'; -ALTER TABLE collate_test22 ADD FOREIGN KEY (f2) REFERENCES collate_test20; -RESET enable_seqscan; -RESET enable_hashjoin; -RESET enable_nestloop; --- EXPLAIN -EXPLAIN (COSTS OFF) - SELECT * FROM collate_test10 ORDER BY x, y; - QUERY PLAN ----------------------------------------------- - Sort - Sort Key: x COLLATE "C", y COLLATE "POSIX" - -> Seq Scan on collate_test10 -(3 rows) - -EXPLAIN (COSTS OFF) - SELECT * FROM collate_test10 ORDER BY x DESC, y COLLATE "C" ASC NULLS FIRST; - QUERY PLAN ------------------------------------------------------------ - Sort - Sort Key: x COLLATE "C" DESC, y COLLATE "C" NULLS FIRST - -> Seq Scan on collate_test10 -(3 rows) - --- CREATE/DROP COLLATION -CREATE COLLATION builtin_c ( PROVIDER = builtin, LOCALE = "C" ); -SELECT b FROM collate_test1 ORDER BY b COLLATE builtin_c; - b ------ - ABD - Abc - abc - bbc -(4 rows) - -CREATE COLLATION builtin2 ( PROVIDER = builtin ); -- fails -ERROR: parameter "locale" must be specified -CREATE COLLATION builtin2 ( PROVIDER = builtin, LOCALE = "en_US" ); -- fails -ERROR: invalid locale name "en_US" for builtin provider -CREATE COLLATION builtin2 ( PROVIDER = builtin, LC_CTYPE = "C", LC_COLLATE = "C" ); -- fails -ERROR: parameter "locale" must be specified -CREATE COLLATION mycoll1 FROM "C"; -CREATE COLLATION mycoll2 ( LC_COLLATE = "POSIX", LC_CTYPE = "POSIX" ); -CREATE COLLATION mycoll3 FROM "default"; -- intentionally unsupported -ERROR: collation "default" cannot be copied -DROP COLLATION mycoll1; -CREATE TABLE collate_test23 (f1 text collate mycoll2); -DROP COLLATION mycoll2; -- fail -ERROR: cannot drop collation mycoll2 because other objects depend on it -DETAIL: column f1 of table collate_test23 depends on collation mycoll2 -HINT: Use DROP ... CASCADE to drop the dependent objects too. --- invalid: non-lowercase quoted identifiers -CREATE COLLATION case_coll ("Lc_Collate" = "POSIX", "Lc_Ctype" = "POSIX"); -ERROR: collation attribute "Lc_Collate" not recognized -LINE 1: CREATE COLLATION case_coll ("Lc_Collate" = "POSIX", "Lc_Ctyp... - ^ --- 9.1 bug with useless COLLATE in an expression subject to length coercion -CREATE TEMP TABLE vctable (f1 varchar(25)); -INSERT INTO vctable VALUES ('foo' COLLATE "C"); -SELECT collation for ('foo'); -- unknown type - null - pg_collation_for ------------------- - -(1 row) - -SELECT collation for ('foo'::text); - pg_collation_for ------------------- - "default" -(1 row) - -SELECT collation for ((SELECT a FROM collate_test1 LIMIT 1)); -- non-collatable type - error -ERROR: collations are not supported by type integer -SELECT collation for ((SELECT b FROM collate_test1 LIMIT 1)); - pg_collation_for ------------------- - "C" -(1 row) - --- old bug with not dropping COLLATE when coercing to non-collatable type -CREATE VIEW collate_on_int AS -SELECT c1+1 AS c1p FROM - (SELECT ('4' COLLATE "C")::INT AS c1) ss; -\d+ collate_on_int - View "collate_tests.collate_on_int" - Column | Type | Collation | Nullable | Default | Storage | Description ---------+---------+-----------+----------+---------+---------+------------- - c1p | integer | | | | plain | -View definition: - SELECT c1 + 1 AS c1p - FROM ( SELECT 4 AS c1) ss; - --- Check conflicting or redundant options in CREATE COLLATION --- LC_COLLATE -CREATE COLLATION coll_dup_chk (LC_COLLATE = "POSIX", LC_COLLATE = "NONSENSE", LC_CTYPE = "POSIX"); -ERROR: conflicting or redundant options -LINE 1: ...ATE COLLATION coll_dup_chk (LC_COLLATE = "POSIX", LC_COLLATE... - ^ --- LC_CTYPE -CREATE COLLATION coll_dup_chk (LC_CTYPE = "POSIX", LC_CTYPE = "NONSENSE", LC_COLLATE = "POSIX"); -ERROR: conflicting or redundant options -LINE 1: ...REATE COLLATION coll_dup_chk (LC_CTYPE = "POSIX", LC_CTYPE =... - ^ --- PROVIDER -CREATE COLLATION coll_dup_chk (PROVIDER = icu, PROVIDER = NONSENSE, LC_COLLATE = "POSIX", LC_CTYPE = "POSIX"); -ERROR: conflicting or redundant options -LINE 1: CREATE COLLATION coll_dup_chk (PROVIDER = icu, PROVIDER = NO... - ^ --- LOCALE -CREATE COLLATION case_sensitive (LOCALE = '', LOCALE = "NONSENSE"); -ERROR: conflicting or redundant options -LINE 1: CREATE COLLATION case_sensitive (LOCALE = '', LOCALE = "NONS... - ^ --- DETERMINISTIC -CREATE COLLATION coll_dup_chk (DETERMINISTIC = TRUE, DETERMINISTIC = NONSENSE, LOCALE = ''); -ERROR: conflicting or redundant options -LINE 1: ...ATE COLLATION coll_dup_chk (DETERMINISTIC = TRUE, DETERMINIS... - ^ --- VERSION -CREATE COLLATION coll_dup_chk (VERSION = '1', VERSION = "NONSENSE", LOCALE = ''); -ERROR: conflicting or redundant options -LINE 1: CREATE COLLATION coll_dup_chk (VERSION = '1', VERSION = "NON... - ^ --- LOCALE conflicts with LC_COLLATE and LC_CTYPE -CREATE COLLATION coll_dup_chk (LC_COLLATE = "POSIX", LC_CTYPE = "POSIX", LOCALE = ''); -ERROR: conflicting or redundant options -DETAIL: LOCALE cannot be specified together with LC_COLLATE or LC_CTYPE. --- LOCALE conflicts with LC_COLLATE -CREATE COLLATION coll_dup_chk (LC_COLLATE = "POSIX", LOCALE = ''); -ERROR: conflicting or redundant options -DETAIL: LOCALE cannot be specified together with LC_COLLATE or LC_CTYPE. --- LOCALE conflicts with LC_CTYPE -CREATE COLLATION coll_dup_chk (LC_CTYPE = "POSIX", LOCALE = ''); -ERROR: conflicting or redundant options -DETAIL: LOCALE cannot be specified together with LC_COLLATE or LC_CTYPE. --- FROM conflicts with any other option -CREATE COLLATION coll_dup_chk (FROM = "C", VERSION = "1"); -ERROR: conflicting or redundant options -DETAIL: FROM cannot be specified together with any other options. --- --- Clean up. Many of these table names will be re-used if the user is --- trying to run any platform-specific collation tests later, so we --- must get rid of them. --- -DROP SCHEMA collate_tests CASCADE; -NOTICE: drop cascades to 20 other objects -DETAIL: drop cascades to table collate_test1 -drop cascades to table collate_test_like -drop cascades to table collate_test2 -drop cascades to type testdomain_p -drop cascades to table collate_test4 -drop cascades to table collate_test5 -drop cascades to table collate_test10 -drop cascades to view collview1 -drop cascades to view collview2 -drop cascades to view collview3 -drop cascades to type testdomain -drop cascades to function vc(text) -drop cascades to function dup(anyelement) -drop cascades to table collate_test20 -drop cascades to table collate_test21 -drop cascades to table collate_test22 -drop cascades to collation builtin_c -drop cascades to collation mycoll2 -drop cascades to table collate_test23 -drop cascades to view collate_on_int +psql: error: connection to server on socket "/tmp/pg_regress-03Az7d/.s.PGSQL.40051" failed: FATAL: the database system is not yet accepting connections +DETAIL: Consistent recovery state has not been yet reached. diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/matview.out /tmp/cirrus-ci-build/build/testrun/regress/regress/results/matview.out --- /tmp/cirrus-ci-build/src/test/regress/expected/matview.out 2024-09-20 21:41:45.710024522 +0000 +++ /tmp/cirrus-ci-build/build/testrun/regress/regress/results/matview.out 2024-09-20 21:46:06.629623519 +0000 @@ -1,696 +1,2 @@ --- create a table to use as a basis for views and materialized views in various combinations -CREATE TABLE mvtest_t (id int NOT NULL PRIMARY KEY, type text NOT NULL, amt numeric NOT NULL); -INSERT INTO mvtest_t VALUES - (1, 'x', 2), - (2, 'x', 3), - (3, 'y', 5), - (4, 'y', 7), - (5, 'z', 11); --- we want a view based on the table, too, since views present additional challenges -CREATE VIEW mvtest_tv AS SELECT type, sum(amt) AS totamt FROM mvtest_t GROUP BY type; -SELECT * FROM mvtest_tv ORDER BY type; - type | totamt -------+-------- - x | 5 - y | 12 - z | 11 -(3 rows) - --- create a materialized view with no data, and confirm correct behavior -EXPLAIN (costs off) - CREATE MATERIALIZED VIEW mvtest_tm AS SELECT type, sum(amt) AS totamt FROM mvtest_t GROUP BY type WITH NO DATA; - QUERY PLAN ----------------------------- - HashAggregate - Group Key: type - -> Seq Scan on mvtest_t -(3 rows) - -CREATE MATERIALIZED VIEW mvtest_tm AS SELECT type, sum(amt) AS totamt FROM mvtest_t GROUP BY type WITH NO DATA; -SELECT relispopulated FROM pg_class WHERE oid = 'mvtest_tm'::regclass; - relispopulated ----------------- - f -(1 row) - -SELECT * FROM mvtest_tm ORDER BY type; -ERROR: materialized view "mvtest_tm" has not been populated -HINT: Use the REFRESH MATERIALIZED VIEW command. -REFRESH MATERIALIZED VIEW mvtest_tm; -SELECT relispopulated FROM pg_class WHERE oid = 'mvtest_tm'::regclass; - relispopulated ----------------- - t -(1 row) - -CREATE UNIQUE INDEX mvtest_tm_type ON mvtest_tm (type); -SELECT * FROM mvtest_tm ORDER BY type; - type | totamt -------+-------- - x | 5 - y | 12 - z | 11 -(3 rows) - --- create various views -EXPLAIN (costs off) - CREATE MATERIALIZED VIEW mvtest_tvm AS SELECT * FROM mvtest_tv ORDER BY type; - QUERY PLAN ----------------------------------- - Sort - Sort Key: mvtest_t.type - -> HashAggregate - Group Key: mvtest_t.type - -> Seq Scan on mvtest_t -(5 rows) - -CREATE MATERIALIZED VIEW mvtest_tvm AS SELECT * FROM mvtest_tv ORDER BY type; -SELECT * FROM mvtest_tvm; - type | totamt -------+-------- - x | 5 - y | 12 - z | 11 -(3 rows) - -CREATE MATERIALIZED VIEW mvtest_tmm AS SELECT sum(totamt) AS grandtot FROM mvtest_tm; -CREATE MATERIALIZED VIEW mvtest_tvmm AS SELECT sum(totamt) AS grandtot FROM mvtest_tvm; -CREATE UNIQUE INDEX mvtest_tvmm_expr ON mvtest_tvmm ((grandtot > 0)); -CREATE UNIQUE INDEX mvtest_tvmm_pred ON mvtest_tvmm (grandtot) WHERE grandtot < 0; -CREATE VIEW mvtest_tvv AS SELECT sum(totamt) AS grandtot FROM mvtest_tv; -EXPLAIN (costs off) - CREATE MATERIALIZED VIEW mvtest_tvvm AS SELECT * FROM mvtest_tvv; - QUERY PLAN ----------------------------------- - Aggregate - -> HashAggregate - Group Key: mvtest_t.type - -> Seq Scan on mvtest_t -(4 rows) - -CREATE MATERIALIZED VIEW mvtest_tvvm AS SELECT * FROM mvtest_tvv; -CREATE VIEW mvtest_tvvmv AS SELECT * FROM mvtest_tvvm; -CREATE MATERIALIZED VIEW mvtest_bb AS SELECT * FROM mvtest_tvvmv; -CREATE INDEX mvtest_aa ON mvtest_bb (grandtot); --- check that plans seem reasonable -\d+ mvtest_tvm - Materialized view "public.mvtest_tvm" - Column | Type | Collation | Nullable | Default | Storage | Stats target | Description ---------+---------+-----------+----------+---------+----------+--------------+------------- - type | text | | | | extended | | - totamt | numeric | | | | main | | -View definition: - SELECT type, - totamt - FROM mvtest_tv - ORDER BY type; - -\d+ mvtest_tvm - Materialized view "public.mvtest_tvm" - Column | Type | Collation | Nullable | Default | Storage | Stats target | Description ---------+---------+-----------+----------+---------+----------+--------------+------------- - type | text | | | | extended | | - totamt | numeric | | | | main | | -View definition: - SELECT type, - totamt - FROM mvtest_tv - ORDER BY type; - -\d+ mvtest_tvvm - Materialized view "public.mvtest_tvvm" - Column | Type | Collation | Nullable | Default | Storage | Stats target | Description -----------+---------+-----------+----------+---------+---------+--------------+------------- - grandtot | numeric | | | | main | | -View definition: - SELECT grandtot - FROM mvtest_tvv; - -\d+ mvtest_bb - Materialized view "public.mvtest_bb" - Column | Type | Collation | Nullable | Default | Storage | Stats target | Description -----------+---------+-----------+----------+---------+---------+--------------+------------- - grandtot | numeric | | | | main | | -Indexes: - "mvtest_aa" btree (grandtot) -View definition: - SELECT grandtot - FROM mvtest_tvvmv; - --- test schema behavior -CREATE SCHEMA mvtest_mvschema; -ALTER MATERIALIZED VIEW mvtest_tvm SET SCHEMA mvtest_mvschema; -\d+ mvtest_tvm -\d+ mvtest_tvmm - Materialized view "public.mvtest_tvmm" - Column | Type | Collation | Nullable | Default | Storage | Stats target | Description -----------+---------+-----------+----------+---------+---------+--------------+------------- - grandtot | numeric | | | | main | | -Indexes: - "mvtest_tvmm_expr" UNIQUE, btree ((grandtot > 0::numeric)) - "mvtest_tvmm_pred" UNIQUE, btree (grandtot) WHERE grandtot < 0::numeric -View definition: - SELECT sum(totamt) AS grandtot - FROM mvtest_mvschema.mvtest_tvm; - -SET search_path = mvtest_mvschema, public; -\d+ mvtest_tvm - Materialized view "mvtest_mvschema.mvtest_tvm" - Column | Type | Collation | Nullable | Default | Storage | Stats target | Description ---------+---------+-----------+----------+---------+----------+--------------+------------- - type | text | | | | extended | | - totamt | numeric | | | | main | | -View definition: - SELECT type, - totamt - FROM mvtest_tv - ORDER BY type; - --- modify the underlying table data -INSERT INTO mvtest_t VALUES (6, 'z', 13); --- confirm pre- and post-refresh contents of fairly simple materialized views -SELECT * FROM mvtest_tm ORDER BY type; - type | totamt -------+-------- - x | 5 - y | 12 - z | 11 -(3 rows) - -SELECT * FROM mvtest_tvm ORDER BY type; - type | totamt -------+-------- - x | 5 - y | 12 - z | 11 -(3 rows) - -REFRESH MATERIALIZED VIEW CONCURRENTLY mvtest_tm; -REFRESH MATERIALIZED VIEW mvtest_tvm; -SELECT * FROM mvtest_tm ORDER BY type; - type | totamt -------+-------- - x | 5 - y | 12 - z | 24 -(3 rows) - -SELECT * FROM mvtest_tvm ORDER BY type; - type | totamt -------+-------- - x | 5 - y | 12 - z | 24 -(3 rows) - -RESET search_path; --- confirm pre- and post-refresh contents of nested materialized views -EXPLAIN (costs off) - SELECT * FROM mvtest_tmm; - QUERY PLAN ------------------------- - Seq Scan on mvtest_tmm -(1 row) - -EXPLAIN (costs off) - SELECT * FROM mvtest_tvmm; - QUERY PLAN -------------------------- - Seq Scan on mvtest_tvmm -(1 row) - -EXPLAIN (costs off) - SELECT * FROM mvtest_tvvm; - QUERY PLAN -------------------------- - Seq Scan on mvtest_tvvm -(1 row) - -SELECT * FROM mvtest_tmm; - grandtot ----------- - 28 -(1 row) - -SELECT * FROM mvtest_tvmm; - grandtot ----------- - 28 -(1 row) - -SELECT * FROM mvtest_tvvm; - grandtot ----------- - 28 -(1 row) - -REFRESH MATERIALIZED VIEW mvtest_tmm; -REFRESH MATERIALIZED VIEW CONCURRENTLY mvtest_tvmm; -ERROR: cannot refresh materialized view "public.mvtest_tvmm" concurrently -HINT: Create a unique index with no WHERE clause on one or more columns of the materialized view. -REFRESH MATERIALIZED VIEW mvtest_tvmm; -REFRESH MATERIALIZED VIEW mvtest_tvvm; -EXPLAIN (costs off) - SELECT * FROM mvtest_tmm; - QUERY PLAN ------------------------- - Seq Scan on mvtest_tmm -(1 row) - -EXPLAIN (costs off) - SELECT * FROM mvtest_tvmm; - QUERY PLAN -------------------------- - Seq Scan on mvtest_tvmm -(1 row) - -EXPLAIN (costs off) - SELECT * FROM mvtest_tvvm; - QUERY PLAN -------------------------- - Seq Scan on mvtest_tvvm -(1 row) - -SELECT * FROM mvtest_tmm; - grandtot ----------- - 41 -(1 row) - -SELECT * FROM mvtest_tvmm; - grandtot ----------- - 41 -(1 row) - -SELECT * FROM mvtest_tvvm; - grandtot ----------- - 41 -(1 row) - --- test diemv when the mv does not exist -DROP MATERIALIZED VIEW IF EXISTS no_such_mv; -NOTICE: materialized view "no_such_mv" does not exist, skipping --- make sure invalid combination of options is prohibited -REFRESH MATERIALIZED VIEW CONCURRENTLY mvtest_tvmm WITH NO DATA; -ERROR: CONCURRENTLY and WITH NO DATA options cannot be used together --- no tuple locks on materialized views -SELECT * FROM mvtest_tvvm FOR SHARE; -ERROR: cannot lock rows in materialized view "mvtest_tvvm" --- test join of mv and view -SELECT type, m.totamt AS mtot, v.totamt AS vtot FROM mvtest_tm m LEFT JOIN mvtest_tv v USING (type) ORDER BY type; - type | mtot | vtot -------+------+------ - x | 5 | 5 - y | 12 | 12 - z | 24 | 24 -(3 rows) - --- make sure that dependencies are reported properly when they block the drop -DROP TABLE mvtest_t; -ERROR: cannot drop table mvtest_t because other objects depend on it -DETAIL: view mvtest_tv depends on table mvtest_t -materialized view mvtest_mvschema.mvtest_tvm depends on view mvtest_tv -materialized view mvtest_tvmm depends on materialized view mvtest_mvschema.mvtest_tvm -view mvtest_tvv depends on view mvtest_tv -materialized view mvtest_tvvm depends on view mvtest_tvv -view mvtest_tvvmv depends on materialized view mvtest_tvvm -materialized view mvtest_bb depends on view mvtest_tvvmv -materialized view mvtest_tm depends on table mvtest_t -materialized view mvtest_tmm depends on materialized view mvtest_tm -HINT: Use DROP ... CASCADE to drop the dependent objects too. --- make sure dependencies are dropped and reported --- and make sure that transactional behavior is correct on rollback --- incidentally leaving some interesting materialized views for pg_dump testing -BEGIN; -DROP TABLE mvtest_t CASCADE; -NOTICE: drop cascades to 9 other objects -DETAIL: drop cascades to view mvtest_tv -drop cascades to materialized view mvtest_mvschema.mvtest_tvm -drop cascades to materialized view mvtest_tvmm -drop cascades to view mvtest_tvv -drop cascades to materialized view mvtest_tvvm -drop cascades to view mvtest_tvvmv -drop cascades to materialized view mvtest_bb -drop cascades to materialized view mvtest_tm -drop cascades to materialized view mvtest_tmm -ROLLBACK; --- some additional tests not using base tables -CREATE VIEW mvtest_vt1 AS SELECT 1 moo; -CREATE VIEW mvtest_vt2 AS SELECT moo, 2*moo FROM mvtest_vt1 UNION ALL SELECT moo, 3*moo FROM mvtest_vt1; -\d+ mvtest_vt2 - View "public.mvtest_vt2" - Column | Type | Collation | Nullable | Default | Storage | Description -----------+---------+-----------+----------+---------+---------+------------- - moo | integer | | | | plain | - ?column? | integer | | | | plain | -View definition: - SELECT mvtest_vt1.moo, - 2 * mvtest_vt1.moo AS "?column?" - FROM mvtest_vt1 -UNION ALL - SELECT mvtest_vt1.moo, - 3 * mvtest_vt1.moo - FROM mvtest_vt1; - -CREATE MATERIALIZED VIEW mv_test2 AS SELECT moo, 2*moo FROM mvtest_vt2 UNION ALL SELECT moo, 3*moo FROM mvtest_vt2; -\d+ mv_test2 - Materialized view "public.mv_test2" - Column | Type | Collation | Nullable | Default | Storage | Stats target | Description -----------+---------+-----------+----------+---------+---------+--------------+------------- - moo | integer | | | | plain | | - ?column? | integer | | | | plain | | -View definition: - SELECT mvtest_vt2.moo, - 2 * mvtest_vt2.moo AS "?column?" - FROM mvtest_vt2 -UNION ALL - SELECT mvtest_vt2.moo, - 3 * mvtest_vt2.moo - FROM mvtest_vt2; - -CREATE MATERIALIZED VIEW mv_test3 AS SELECT * FROM mv_test2 WHERE moo = 12345; -SELECT relispopulated FROM pg_class WHERE oid = 'mv_test3'::regclass; - relispopulated ----------------- - t -(1 row) - -DROP VIEW mvtest_vt1 CASCADE; -NOTICE: drop cascades to 3 other objects -DETAIL: drop cascades to view mvtest_vt2 -drop cascades to materialized view mv_test2 -drop cascades to materialized view mv_test3 --- test that duplicate values on unique index prevent refresh -CREATE TABLE mvtest_foo(a, b) AS VALUES(1, 10); -CREATE MATERIALIZED VIEW mvtest_mv AS SELECT * FROM mvtest_foo; -CREATE UNIQUE INDEX ON mvtest_mv(a); -INSERT INTO mvtest_foo SELECT * FROM mvtest_foo; -REFRESH MATERIALIZED VIEW mvtest_mv; -ERROR: could not create unique index "mvtest_mv_a_idx" -DETAIL: Key (a)=(1) is duplicated. -REFRESH MATERIALIZED VIEW CONCURRENTLY mvtest_mv; -ERROR: new data for materialized view "mvtest_mv" contains duplicate rows without any null columns -DETAIL: Row: (1,10) -DROP TABLE mvtest_foo CASCADE; -NOTICE: drop cascades to materialized view mvtest_mv --- make sure that all columns covered by unique indexes works -CREATE TABLE mvtest_foo(a, b, c) AS VALUES(1, 2, 3); -CREATE MATERIALIZED VIEW mvtest_mv AS SELECT * FROM mvtest_foo; -CREATE UNIQUE INDEX ON mvtest_mv (a); -CREATE UNIQUE INDEX ON mvtest_mv (b); -CREATE UNIQUE INDEX on mvtest_mv (c); -INSERT INTO mvtest_foo VALUES(2, 3, 4); -INSERT INTO mvtest_foo VALUES(3, 4, 5); -REFRESH MATERIALIZED VIEW mvtest_mv; -REFRESH MATERIALIZED VIEW CONCURRENTLY mvtest_mv; -DROP TABLE mvtest_foo CASCADE; -NOTICE: drop cascades to materialized view mvtest_mv --- allow subquery to reference unpopulated matview if WITH NO DATA is specified -CREATE MATERIALIZED VIEW mvtest_mv1 AS SELECT 1 AS col1 WITH NO DATA; -CREATE MATERIALIZED VIEW mvtest_mv2 AS SELECT * FROM mvtest_mv1 - WHERE col1 = (SELECT LEAST(col1) FROM mvtest_mv1) WITH NO DATA; -DROP MATERIALIZED VIEW mvtest_mv1 CASCADE; -NOTICE: drop cascades to materialized view mvtest_mv2 --- make sure that types with unusual equality tests work -CREATE TABLE mvtest_boxes (id serial primary key, b box); -INSERT INTO mvtest_boxes (b) VALUES - ('(32,32),(31,31)'), - ('(2.0000004,2.0000004),(1,1)'), - ('(1.9999996,1.9999996),(1,1)'); -CREATE MATERIALIZED VIEW mvtest_boxmv AS SELECT * FROM mvtest_boxes; -CREATE UNIQUE INDEX mvtest_boxmv_id ON mvtest_boxmv (id); -UPDATE mvtest_boxes SET b = '(2,2),(1,1)' WHERE id = 2; -REFRESH MATERIALIZED VIEW CONCURRENTLY mvtest_boxmv; -SELECT * FROM mvtest_boxmv ORDER BY id; - id | b -----+----------------------------- - 1 | (32,32),(31,31) - 2 | (2,2),(1,1) - 3 | (1.9999996,1.9999996),(1,1) -(3 rows) - -DROP TABLE mvtest_boxes CASCADE; -NOTICE: drop cascades to materialized view mvtest_boxmv --- make sure that column names are handled correctly -CREATE TABLE mvtest_v (i int, j int); -CREATE MATERIALIZED VIEW mvtest_mv_v (ii, jj, kk) AS SELECT i, j FROM mvtest_v; -- error -ERROR: too many column names were specified -CREATE MATERIALIZED VIEW mvtest_mv_v (ii, jj) AS SELECT i, j FROM mvtest_v; -- ok -CREATE MATERIALIZED VIEW mvtest_mv_v_2 (ii) AS SELECT i, j FROM mvtest_v; -- ok -CREATE MATERIALIZED VIEW mvtest_mv_v_3 (ii, jj, kk) AS SELECT i, j FROM mvtest_v WITH NO DATA; -- error -ERROR: too many column names were specified -CREATE MATERIALIZED VIEW mvtest_mv_v_3 (ii, jj) AS SELECT i, j FROM mvtest_v WITH NO DATA; -- ok -CREATE MATERIALIZED VIEW mvtest_mv_v_4 (ii) AS SELECT i, j FROM mvtest_v WITH NO DATA; -- ok -ALTER TABLE mvtest_v RENAME COLUMN i TO x; -INSERT INTO mvtest_v values (1, 2); -CREATE UNIQUE INDEX mvtest_mv_v_ii ON mvtest_mv_v (ii); -REFRESH MATERIALIZED VIEW mvtest_mv_v; -UPDATE mvtest_v SET j = 3 WHERE x = 1; -REFRESH MATERIALIZED VIEW CONCURRENTLY mvtest_mv_v; -REFRESH MATERIALIZED VIEW mvtest_mv_v_2; -REFRESH MATERIALIZED VIEW mvtest_mv_v_3; -REFRESH MATERIALIZED VIEW mvtest_mv_v_4; -SELECT * FROM mvtest_v; - x | j ----+--- - 1 | 3 -(1 row) - -SELECT * FROM mvtest_mv_v; - ii | jj -----+---- - 1 | 3 -(1 row) - -SELECT * FROM mvtest_mv_v_2; - ii | j -----+--- - 1 | 3 -(1 row) - -SELECT * FROM mvtest_mv_v_3; - ii | jj -----+---- - 1 | 3 -(1 row) - -SELECT * FROM mvtest_mv_v_4; - ii | j -----+--- - 1 | 3 -(1 row) - -DROP TABLE mvtest_v CASCADE; -NOTICE: drop cascades to 4 other objects -DETAIL: drop cascades to materialized view mvtest_mv_v -drop cascades to materialized view mvtest_mv_v_2 -drop cascades to materialized view mvtest_mv_v_3 -drop cascades to materialized view mvtest_mv_v_4 --- Check that unknown literals are converted to "text" in CREATE MATVIEW, --- so that we don't end up with unknown-type columns. -CREATE MATERIALIZED VIEW mv_unspecified_types AS - SELECT 42 as i, 42.5 as num, 'foo' as u, 'foo'::unknown as u2, null as n; -\d+ mv_unspecified_types - Materialized view "public.mv_unspecified_types" - Column | Type | Collation | Nullable | Default | Storage | Stats target | Description ---------+---------+-----------+----------+---------+----------+--------------+------------- - i | integer | | | | plain | | - num | numeric | | | | main | | - u | text | | | | extended | | - u2 | text | | | | extended | | - n | text | | | | extended | | -View definition: - SELECT 42 AS i, - 42.5 AS num, - 'foo'::text AS u, - 'foo'::text AS u2, - NULL::text AS n; - -SELECT * FROM mv_unspecified_types; - i | num | u | u2 | n -----+------+-----+-----+--- - 42 | 42.5 | foo | foo | -(1 row) - -DROP MATERIALIZED VIEW mv_unspecified_types; --- make sure that create WITH NO DATA does not plan the query (bug #13907) -create materialized view mvtest_error as select 1/0 as x; -- fail -ERROR: division by zero -create materialized view mvtest_error as select 1/0 as x with no data; -refresh materialized view mvtest_error; -- fail here -ERROR: division by zero -drop materialized view mvtest_error; --- make sure that matview rows can be referenced as source rows (bug #9398) -CREATE TABLE mvtest_v AS SELECT generate_series(1,10) AS a; -CREATE MATERIALIZED VIEW mvtest_mv_v AS SELECT a FROM mvtest_v WHERE a <= 5; -DELETE FROM mvtest_v WHERE EXISTS ( SELECT * FROM mvtest_mv_v WHERE mvtest_mv_v.a = mvtest_v.a ); -SELECT * FROM mvtest_v; - a ----- - 6 - 7 - 8 - 9 - 10 -(5 rows) - -SELECT * FROM mvtest_mv_v; - a ---- - 1 - 2 - 3 - 4 - 5 -(5 rows) - -DROP TABLE mvtest_v CASCADE; -NOTICE: drop cascades to materialized view mvtest_mv_v --- make sure running as superuser works when MV owned by another role (bug #11208) -CREATE ROLE regress_user_mvtest; -SET ROLE regress_user_mvtest; --- this test case also checks for ambiguity in the queries issued by --- refresh_by_match_merge(), by choosing column names that intentionally --- duplicate all the aliases used in those queries -CREATE TABLE mvtest_foo_data AS SELECT i, - i+1 AS tid, - fipshash(random()::text) AS mv, - fipshash(random()::text) AS newdata, - fipshash(random()::text) AS newdata2, - fipshash(random()::text) AS diff - FROM generate_series(1, 10) i; -CREATE MATERIALIZED VIEW mvtest_mv_foo AS SELECT * FROM mvtest_foo_data; -CREATE MATERIALIZED VIEW mvtest_mv_foo AS SELECT * FROM mvtest_foo_data; -ERROR: relation "mvtest_mv_foo" already exists -CREATE MATERIALIZED VIEW IF NOT EXISTS mvtest_mv_foo AS SELECT * FROM mvtest_foo_data; -NOTICE: relation "mvtest_mv_foo" already exists, skipping -CREATE UNIQUE INDEX ON mvtest_mv_foo (i); -RESET ROLE; -REFRESH MATERIALIZED VIEW mvtest_mv_foo; -REFRESH MATERIALIZED VIEW CONCURRENTLY mvtest_mv_foo; -DROP OWNED BY regress_user_mvtest CASCADE; -DROP ROLE regress_user_mvtest; --- Concurrent refresh requires a unique index on the materialized --- view. Test what happens if it's dropped during the refresh. -SET search_path = mvtest_mvschema, public; -CREATE OR REPLACE FUNCTION mvtest_drop_the_index() - RETURNS bool AS $$ -BEGIN - EXECUTE 'DROP INDEX IF EXISTS mvtest_mvschema.mvtest_drop_idx'; - RETURN true; -END; -$$ LANGUAGE plpgsql; -CREATE MATERIALIZED VIEW drop_idx_matview AS - SELECT 1 as i WHERE mvtest_drop_the_index(); -NOTICE: index "mvtest_drop_idx" does not exist, skipping -CREATE UNIQUE INDEX mvtest_drop_idx ON drop_idx_matview (i); -REFRESH MATERIALIZED VIEW CONCURRENTLY drop_idx_matview; -ERROR: could not find suitable unique index on materialized view -DROP MATERIALIZED VIEW drop_idx_matview; -- clean up -RESET search_path; --- make sure that create WITH NO DATA works via SPI -BEGIN; -CREATE FUNCTION mvtest_func() - RETURNS void AS $$ -BEGIN - CREATE MATERIALIZED VIEW mvtest1 AS SELECT 1 AS x; - CREATE MATERIALIZED VIEW mvtest2 AS SELECT 1 AS x WITH NO DATA; -END; -$$ LANGUAGE plpgsql; -SELECT mvtest_func(); - mvtest_func -------------- - -(1 row) - -SELECT * FROM mvtest1; - x ---- - 1 -(1 row) - -SELECT * FROM mvtest2; -ERROR: materialized view "mvtest2" has not been populated -HINT: Use the REFRESH MATERIALIZED VIEW command. -ROLLBACK; --- INSERT privileges if relation owner is not allowed to insert. -CREATE SCHEMA matview_schema; -CREATE USER regress_matview_user; -ALTER DEFAULT PRIVILEGES FOR ROLE regress_matview_user - REVOKE INSERT ON TABLES FROM regress_matview_user; -GRANT ALL ON SCHEMA matview_schema TO public; -SET SESSION AUTHORIZATION regress_matview_user; -CREATE MATERIALIZED VIEW matview_schema.mv_withdata1 (a) AS - SELECT generate_series(1, 10) WITH DATA; -EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) - CREATE MATERIALIZED VIEW matview_schema.mv_withdata2 (a) AS - SELECT generate_series(1, 10) WITH DATA; - QUERY PLAN --------------------------------------- - ProjectSet (actual rows=10 loops=1) - -> Result (actual rows=1 loops=1) -(2 rows) - -REFRESH MATERIALIZED VIEW matview_schema.mv_withdata2; -CREATE MATERIALIZED VIEW matview_schema.mv_nodata1 (a) AS - SELECT generate_series(1, 10) WITH NO DATA; -EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) - CREATE MATERIALIZED VIEW matview_schema.mv_nodata2 (a) AS - SELECT generate_series(1, 10) WITH NO DATA; - QUERY PLAN -------------------------------- - ProjectSet (never executed) - -> Result (never executed) -(2 rows) - -REFRESH MATERIALIZED VIEW matview_schema.mv_nodata2; -RESET SESSION AUTHORIZATION; -ALTER DEFAULT PRIVILEGES FOR ROLE regress_matview_user - GRANT INSERT ON TABLES TO regress_matview_user; -DROP SCHEMA matview_schema CASCADE; -NOTICE: drop cascades to 4 other objects -DETAIL: drop cascades to materialized view matview_schema.mv_withdata1 -drop cascades to materialized view matview_schema.mv_withdata2 -drop cascades to materialized view matview_schema.mv_nodata1 -drop cascades to materialized view matview_schema.mv_nodata2 -DROP USER regress_matview_user; --- CREATE MATERIALIZED VIEW ... IF NOT EXISTS -CREATE MATERIALIZED VIEW matview_ine_tab AS SELECT 1; -CREATE MATERIALIZED VIEW matview_ine_tab AS SELECT 1 / 0; -- error -ERROR: relation "matview_ine_tab" already exists -CREATE MATERIALIZED VIEW IF NOT EXISTS matview_ine_tab AS - SELECT 1 / 0; -- ok -NOTICE: relation "matview_ine_tab" already exists, skipping -CREATE MATERIALIZED VIEW matview_ine_tab AS - SELECT 1 / 0 WITH NO DATA; -- error -ERROR: relation "matview_ine_tab" already exists -CREATE MATERIALIZED VIEW IF NOT EXISTS matview_ine_tab AS - SELECT 1 / 0 WITH NO DATA; -- ok -NOTICE: relation "matview_ine_tab" already exists, skipping -EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) - CREATE MATERIALIZED VIEW matview_ine_tab AS - SELECT 1 / 0; -- error -ERROR: relation "matview_ine_tab" already exists -EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) - CREATE MATERIALIZED VIEW IF NOT EXISTS matview_ine_tab AS - SELECT 1 / 0; -- ok -NOTICE: relation "matview_ine_tab" already exists, skipping - QUERY PLAN ------------- -(0 rows) - -EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) - CREATE MATERIALIZED VIEW matview_ine_tab AS - SELECT 1 / 0 WITH NO DATA; -- error -ERROR: relation "matview_ine_tab" already exists -EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) - CREATE MATERIALIZED VIEW IF NOT EXISTS matview_ine_tab AS - SELECT 1 / 0 WITH NO DATA; -- ok -NOTICE: relation "matview_ine_tab" already exists, skipping - QUERY PLAN ------------- -(0 rows) - -DROP MATERIALIZED VIEW matview_ine_tab; +psql: error: connection to server on socket "/tmp/pg_regress-03Az7d/.s.PGSQL.40051" failed: FATAL: the database system is not yet accepting connections +DETAIL: Consistent recovery state has not been yet reached. diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/lock.out /tmp/cirrus-ci-build/build/testrun/regress/regress/results/lock.out --- /tmp/cirrus-ci-build/src/test/regress/expected/lock.out 2024-09-20 21:41:45.710024522 +0000 +++ /tmp/cirrus-ci-build/build/testrun/regress/regress/results/lock.out 2024-09-20 21:46:06.625623524 +0000 @@ -1,252 +1,2 @@ --- --- Test the LOCK statement --- --- directory paths and dlsuffix are passed to us in environment variables -\getenv libdir PG_LIBDIR -\getenv dlsuffix PG_DLSUFFIX -\set regresslib :libdir '/regress' :dlsuffix --- Setup -CREATE SCHEMA lock_schema1; -SET search_path = lock_schema1; -CREATE TABLE lock_tbl1 (a BIGINT); -CREATE TABLE lock_tbl1a (a BIGINT); -CREATE VIEW lock_view1 AS SELECT * FROM lock_tbl1; -CREATE VIEW lock_view2(a,b) AS SELECT * FROM lock_tbl1, lock_tbl1a; -CREATE VIEW lock_view3 AS SELECT * from lock_view2; -CREATE VIEW lock_view4 AS SELECT (select a from lock_tbl1a limit 1) from lock_tbl1; -CREATE VIEW lock_view5 AS SELECT * from lock_tbl1 where a in (select * from lock_tbl1a); -CREATE VIEW lock_view6 AS SELECT * from (select * from lock_tbl1) sub; -CREATE ROLE regress_rol_lock1; -ALTER ROLE regress_rol_lock1 SET search_path = lock_schema1; -GRANT USAGE ON SCHEMA lock_schema1 TO regress_rol_lock1; --- Try all valid lock options; also try omitting the optional TABLE keyword. -BEGIN TRANSACTION; -LOCK TABLE lock_tbl1 IN ACCESS SHARE MODE; -LOCK lock_tbl1 IN ROW SHARE MODE; -LOCK TABLE lock_tbl1 IN ROW EXCLUSIVE MODE; -LOCK TABLE lock_tbl1 IN SHARE UPDATE EXCLUSIVE MODE; -LOCK TABLE lock_tbl1 IN SHARE MODE; -LOCK lock_tbl1 IN SHARE ROW EXCLUSIVE MODE; -LOCK TABLE lock_tbl1 IN EXCLUSIVE MODE; -LOCK TABLE lock_tbl1 IN ACCESS EXCLUSIVE MODE; -ROLLBACK; --- Try using NOWAIT along with valid options. -BEGIN TRANSACTION; -LOCK TABLE lock_tbl1 IN ACCESS SHARE MODE NOWAIT; -LOCK TABLE lock_tbl1 IN ROW SHARE MODE NOWAIT; -LOCK TABLE lock_tbl1 IN ROW EXCLUSIVE MODE NOWAIT; -LOCK TABLE lock_tbl1 IN SHARE UPDATE EXCLUSIVE MODE NOWAIT; -LOCK TABLE lock_tbl1 IN SHARE MODE NOWAIT; -LOCK TABLE lock_tbl1 IN SHARE ROW EXCLUSIVE MODE NOWAIT; -LOCK TABLE lock_tbl1 IN EXCLUSIVE MODE NOWAIT; -LOCK TABLE lock_tbl1 IN ACCESS EXCLUSIVE MODE NOWAIT; -ROLLBACK; --- Verify that we can lock views. -BEGIN TRANSACTION; -LOCK TABLE lock_view1 IN EXCLUSIVE MODE; --- lock_view1 and lock_tbl1 are locked. -select relname from pg_locks l, pg_class c - where l.relation = c.oid and relname like '%lock_%' and mode = 'ExclusiveLock' - order by relname; - relname ------------- - lock_tbl1 - lock_view1 -(2 rows) - -ROLLBACK; -BEGIN TRANSACTION; -LOCK TABLE lock_view2 IN EXCLUSIVE MODE; --- lock_view1, lock_tbl1, and lock_tbl1a are locked. -select relname from pg_locks l, pg_class c - where l.relation = c.oid and relname like '%lock_%' and mode = 'ExclusiveLock' - order by relname; - relname ------------- - lock_tbl1 - lock_tbl1a - lock_view2 -(3 rows) - -ROLLBACK; -BEGIN TRANSACTION; -LOCK TABLE lock_view3 IN EXCLUSIVE MODE; --- lock_view3, lock_view2, lock_tbl1, and lock_tbl1a are locked recursively. -select relname from pg_locks l, pg_class c - where l.relation = c.oid and relname like '%lock_%' and mode = 'ExclusiveLock' - order by relname; - relname ------------- - lock_tbl1 - lock_tbl1a - lock_view2 - lock_view3 -(4 rows) - -ROLLBACK; -BEGIN TRANSACTION; -LOCK TABLE lock_view4 IN EXCLUSIVE MODE; --- lock_view4, lock_tbl1, and lock_tbl1a are locked. -select relname from pg_locks l, pg_class c - where l.relation = c.oid and relname like '%lock_%' and mode = 'ExclusiveLock' - order by relname; - relname ------------- - lock_tbl1 - lock_tbl1a - lock_view4 -(3 rows) - -ROLLBACK; -BEGIN TRANSACTION; -LOCK TABLE lock_view5 IN EXCLUSIVE MODE; --- lock_view5, lock_tbl1, and lock_tbl1a are locked. -select relname from pg_locks l, pg_class c - where l.relation = c.oid and relname like '%lock_%' and mode = 'ExclusiveLock' - order by relname; - relname ------------- - lock_tbl1 - lock_tbl1a - lock_view5 -(3 rows) - -ROLLBACK; -BEGIN TRANSACTION; -LOCK TABLE lock_view6 IN EXCLUSIVE MODE; --- lock_view6 an lock_tbl1 are locked. -select relname from pg_locks l, pg_class c - where l.relation = c.oid and relname like '%lock_%' and mode = 'ExclusiveLock' - order by relname; - relname ------------- - lock_tbl1 - lock_view6 -(2 rows) - -ROLLBACK; --- Verify that we cope with infinite recursion in view definitions. -CREATE OR REPLACE VIEW lock_view2 AS SELECT * from lock_view3; -BEGIN TRANSACTION; -LOCK TABLE lock_view2 IN EXCLUSIVE MODE; -ROLLBACK; -CREATE VIEW lock_view7 AS SELECT * from lock_view2; -BEGIN TRANSACTION; -LOCK TABLE lock_view7 IN EXCLUSIVE MODE; -ROLLBACK; --- Verify that we can lock a table with inheritance children. -CREATE TABLE lock_tbl2 (b BIGINT) INHERITS (lock_tbl1); -CREATE TABLE lock_tbl3 () INHERITS (lock_tbl2); -BEGIN TRANSACTION; -LOCK TABLE lock_tbl1 * IN ACCESS EXCLUSIVE MODE; -ROLLBACK; --- Child tables are locked without granting explicit permission to do so as --- long as we have permission to lock the parent. -GRANT UPDATE ON TABLE lock_tbl1 TO regress_rol_lock1; -SET ROLE regress_rol_lock1; --- fail when child locked directly -BEGIN; -LOCK TABLE lock_tbl2; -ERROR: permission denied for table lock_tbl2 -ROLLBACK; -BEGIN; -LOCK TABLE lock_tbl1 * IN ACCESS EXCLUSIVE MODE; -ROLLBACK; -BEGIN; -LOCK TABLE ONLY lock_tbl1; -ROLLBACK; -RESET ROLE; -REVOKE UPDATE ON TABLE lock_tbl1 FROM regress_rol_lock1; --- Tables referred to by views are locked without explicit permission to do so --- as long as we have permission to lock the view itself. -SET ROLE regress_rol_lock1; --- fail without permissions on the view -BEGIN; -LOCK TABLE lock_view1; -ERROR: permission denied for view lock_view1 -ROLLBACK; -RESET ROLE; -GRANT UPDATE ON TABLE lock_view1 TO regress_rol_lock1; -SET ROLE regress_rol_lock1; -BEGIN; -LOCK TABLE lock_view1 IN ACCESS EXCLUSIVE MODE; --- lock_view1 and lock_tbl1 (plus children lock_tbl2 and lock_tbl3) are locked. -select relname from pg_locks l, pg_class c - where l.relation = c.oid and relname like '%lock_%' and mode = 'AccessExclusiveLock' - order by relname; - relname ------------- - lock_tbl1 - lock_tbl2 - lock_tbl3 - lock_view1 -(4 rows) - -ROLLBACK; -RESET ROLE; -REVOKE UPDATE ON TABLE lock_view1 FROM regress_rol_lock1; --- Tables referred to by security invoker views require explicit permission to --- be locked. -CREATE VIEW lock_view8 WITH (security_invoker) AS SELECT * FROM lock_tbl1; -SET ROLE regress_rol_lock1; --- fail without permissions on the view -BEGIN; -LOCK TABLE lock_view8; -ERROR: permission denied for view lock_view8 -ROLLBACK; -RESET ROLE; -GRANT UPDATE ON TABLE lock_view8 TO regress_rol_lock1; -SET ROLE regress_rol_lock1; --- fail without permissions on the table referenced by the view -BEGIN; -LOCK TABLE lock_view8; -ERROR: permission denied for table lock_tbl1 -ROLLBACK; -RESET ROLE; -GRANT UPDATE ON TABLE lock_tbl1 TO regress_rol_lock1; -BEGIN; -LOCK TABLE lock_view8 IN ACCESS EXCLUSIVE MODE; --- lock_view8 and lock_tbl1 (plus children lock_tbl2 and lock_tbl3) are locked. -select relname from pg_locks l, pg_class c - where l.relation = c.oid and relname like '%lock_%' and mode = 'AccessExclusiveLock' - order by relname; - relname ------------- - lock_tbl1 - lock_tbl2 - lock_tbl3 - lock_view8 -(4 rows) - -ROLLBACK; -RESET ROLE; -REVOKE UPDATE ON TABLE lock_view8 FROM regress_rol_lock1; --- --- Clean up --- -DROP VIEW lock_view8; -DROP VIEW lock_view7; -DROP VIEW lock_view6; -DROP VIEW lock_view5; -DROP VIEW lock_view4; -DROP VIEW lock_view3 CASCADE; -NOTICE: drop cascades to view lock_view2 -DROP VIEW lock_view1; -DROP TABLE lock_tbl3; -DROP TABLE lock_tbl2; -DROP TABLE lock_tbl1; -DROP TABLE lock_tbl1a; -DROP SCHEMA lock_schema1 CASCADE; -DROP ROLE regress_rol_lock1; --- atomic ops tests -RESET search_path; -CREATE FUNCTION test_atomic_ops() - RETURNS bool - AS :'regresslib' - LANGUAGE C; -SELECT test_atomic_ops(); - test_atomic_ops ------------------ - t -(1 row) - +psql: error: connection to server on socket "/tmp/pg_regress-03Az7d/.s.PGSQL.40051" failed: FATAL: the database system is not yet accepting connections +DETAIL: Consistent recovery state has not been yet reached. diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/replica_identity.out /tmp/cirrus-ci-build/build/testrun/regress/regress/results/replica_identity.out --- /tmp/cirrus-ci-build/src/test/regress/expected/replica_identity.out 2024-09-20 21:41:45.742024523 +0000 +++ /tmp/cirrus-ci-build/build/testrun/regress/regress/results/replica_identity.out 2024-09-20 21:46:06.625623524 +0000 @@ -1,275 +1,2 @@ -CREATE TABLE test_replica_identity ( - id serial primary key, - keya text not null, - keyb text not null, - nonkey text, - CONSTRAINT test_replica_identity_unique_defer UNIQUE (keya, keyb) DEFERRABLE, - CONSTRAINT test_replica_identity_unique_nondefer UNIQUE (keya, keyb) -) ; -CREATE TABLE test_replica_identity_othertable (id serial primary key); -CREATE TABLE test_replica_identity_t3 (id serial constraint pk primary key deferrable); -CREATE INDEX test_replica_identity_keyab ON test_replica_identity (keya, keyb); -CREATE UNIQUE INDEX test_replica_identity_keyab_key ON test_replica_identity (keya, keyb); -CREATE UNIQUE INDEX test_replica_identity_nonkey ON test_replica_identity (keya, nonkey); -CREATE INDEX test_replica_identity_hash ON test_replica_identity USING hash (nonkey); -CREATE UNIQUE INDEX test_replica_identity_expr ON test_replica_identity (keya, keyb, (3)); -CREATE UNIQUE INDEX test_replica_identity_partial ON test_replica_identity (keya, keyb) WHERE keyb != '3'; --- default is 'd'/DEFAULT for user created tables -SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass; - relreplident --------------- - d -(1 row) - --- but 'none' for system tables -SELECT relreplident FROM pg_class WHERE oid = 'pg_class'::regclass; - relreplident --------------- - n -(1 row) - -SELECT relreplident FROM pg_class WHERE oid = 'pg_constraint'::regclass; - relreplident --------------- - n -(1 row) - ----- --- Make sure we detect ineligible indexes ----- --- fail, not unique -ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_keyab; -ERROR: cannot use non-unique index "test_replica_identity_keyab" as replica identity --- fail, not a candidate key, nullable column -ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_nonkey; -ERROR: index "test_replica_identity_nonkey" cannot be used as replica identity because column "nonkey" is nullable --- fail, hash indexes cannot do uniqueness -ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_hash; -ERROR: cannot use non-unique index "test_replica_identity_hash" as replica identity --- fail, expression index -ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_expr; -ERROR: cannot use expression index "test_replica_identity_expr" as replica identity --- fail, partial index -ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_partial; -ERROR: cannot use partial index "test_replica_identity_partial" as replica identity --- fail, not our index -ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_othertable_pkey; -ERROR: "test_replica_identity_othertable_pkey" is not an index for table "test_replica_identity" --- fail, deferrable -ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_unique_defer; -ERROR: cannot use non-immediate index "test_replica_identity_unique_defer" as replica identity --- fail, deferrable -ALTER TABLE test_replica_identity_t3 REPLICA IDENTITY USING INDEX pk; -ERROR: cannot use non-immediate index "pk" as replica identity -SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass; - relreplident --------------- - d -(1 row) - ----- --- Make sure index cases succeed ----- --- succeed, primary key -ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_pkey; -SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass; - relreplident --------------- - i -(1 row) - -\d test_replica_identity - Table "public.test_replica_identity" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------------------------------------------------- - id | integer | | not null | nextval('test_replica_identity_id_seq'::regclass) - keya | text | | not null | - keyb | text | | not null | - nonkey | text | | | -Indexes: - "test_replica_identity_pkey" PRIMARY KEY, btree (id) REPLICA IDENTITY - "test_replica_identity_expr" UNIQUE, btree (keya, keyb, (3)) - "test_replica_identity_hash" hash (nonkey) - "test_replica_identity_keyab" btree (keya, keyb) - "test_replica_identity_keyab_key" UNIQUE, btree (keya, keyb) - "test_replica_identity_nonkey" UNIQUE, btree (keya, nonkey) - "test_replica_identity_partial" UNIQUE, btree (keya, keyb) WHERE keyb <> '3'::text - "test_replica_identity_unique_defer" UNIQUE CONSTRAINT, btree (keya, keyb) DEFERRABLE - "test_replica_identity_unique_nondefer" UNIQUE CONSTRAINT, btree (keya, keyb) - --- succeed, nondeferrable unique constraint over nonnullable cols -ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_unique_nondefer; --- succeed unique index over nonnullable cols -ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_keyab_key; -ALTER TABLE test_replica_identity REPLICA IDENTITY USING INDEX test_replica_identity_keyab_key; -SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass; - relreplident --------------- - i -(1 row) - -\d test_replica_identity - Table "public.test_replica_identity" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------------------------------------------------- - id | integer | | not null | nextval('test_replica_identity_id_seq'::regclass) - keya | text | | not null | - keyb | text | | not null | - nonkey | text | | | -Indexes: - "test_replica_identity_pkey" PRIMARY KEY, btree (id) - "test_replica_identity_expr" UNIQUE, btree (keya, keyb, (3)) - "test_replica_identity_hash" hash (nonkey) - "test_replica_identity_keyab" btree (keya, keyb) - "test_replica_identity_keyab_key" UNIQUE, btree (keya, keyb) REPLICA IDENTITY - "test_replica_identity_nonkey" UNIQUE, btree (keya, nonkey) - "test_replica_identity_partial" UNIQUE, btree (keya, keyb) WHERE keyb <> '3'::text - "test_replica_identity_unique_defer" UNIQUE CONSTRAINT, btree (keya, keyb) DEFERRABLE - "test_replica_identity_unique_nondefer" UNIQUE CONSTRAINT, btree (keya, keyb) - -SELECT count(*) FROM pg_index WHERE indrelid = 'test_replica_identity'::regclass AND indisreplident; - count -------- - 1 -(1 row) - ----- --- Make sure non index cases work ----- -ALTER TABLE test_replica_identity REPLICA IDENTITY DEFAULT; -SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass; - relreplident --------------- - d -(1 row) - -SELECT count(*) FROM pg_index WHERE indrelid = 'test_replica_identity'::regclass AND indisreplident; - count -------- - 0 -(1 row) - -ALTER TABLE test_replica_identity REPLICA IDENTITY FULL; -SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass; - relreplident --------------- - f -(1 row) - -\d+ test_replica_identity - Table "public.test_replica_identity" - Column | Type | Collation | Nullable | Default | Storage | Stats target | Description ---------+---------+-----------+----------+---------------------------------------------------+----------+--------------+------------- - id | integer | | not null | nextval('test_replica_identity_id_seq'::regclass) | plain | | - keya | text | | not null | | extended | | - keyb | text | | not null | | extended | | - nonkey | text | | | | extended | | -Indexes: - "test_replica_identity_pkey" PRIMARY KEY, btree (id) - "test_replica_identity_expr" UNIQUE, btree (keya, keyb, (3)) - "test_replica_identity_hash" hash (nonkey) - "test_replica_identity_keyab" btree (keya, keyb) - "test_replica_identity_keyab_key" UNIQUE, btree (keya, keyb) - "test_replica_identity_nonkey" UNIQUE, btree (keya, nonkey) - "test_replica_identity_partial" UNIQUE, btree (keya, keyb) WHERE keyb <> '3'::text - "test_replica_identity_unique_defer" UNIQUE CONSTRAINT, btree (keya, keyb) DEFERRABLE - "test_replica_identity_unique_nondefer" UNIQUE CONSTRAINT, btree (keya, keyb) -Replica Identity: FULL - -ALTER TABLE test_replica_identity REPLICA IDENTITY NOTHING; -SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass; - relreplident --------------- - n -(1 row) - ---- --- Test that ALTER TABLE rewrite preserves nondefault replica identity ---- --- constraint variant -CREATE TABLE test_replica_identity2 (id int UNIQUE NOT NULL); -ALTER TABLE test_replica_identity2 REPLICA IDENTITY USING INDEX test_replica_identity2_id_key; -\d test_replica_identity2 - Table "public.test_replica_identity2" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - id | integer | | not null | -Indexes: - "test_replica_identity2_id_key" UNIQUE CONSTRAINT, btree (id) REPLICA IDENTITY - -ALTER TABLE test_replica_identity2 ALTER COLUMN id TYPE bigint; -\d test_replica_identity2 - Table "public.test_replica_identity2" - Column | Type | Collation | Nullable | Default ---------+--------+-----------+----------+--------- - id | bigint | | not null | -Indexes: - "test_replica_identity2_id_key" UNIQUE CONSTRAINT, btree (id) REPLICA IDENTITY - --- straight index variant -CREATE TABLE test_replica_identity3 (id int NOT NULL); -CREATE UNIQUE INDEX test_replica_identity3_id_key ON test_replica_identity3 (id); -ALTER TABLE test_replica_identity3 REPLICA IDENTITY USING INDEX test_replica_identity3_id_key; -\d test_replica_identity3 - Table "public.test_replica_identity3" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - id | integer | | not null | -Indexes: - "test_replica_identity3_id_key" UNIQUE, btree (id) REPLICA IDENTITY - -ALTER TABLE test_replica_identity3 ALTER COLUMN id TYPE bigint; -\d test_replica_identity3 - Table "public.test_replica_identity3" - Column | Type | Collation | Nullable | Default ---------+--------+-----------+----------+--------- - id | bigint | | not null | -Indexes: - "test_replica_identity3_id_key" UNIQUE, btree (id) REPLICA IDENTITY - --- ALTER TABLE DROP NOT NULL is not allowed for columns part of an index --- used as replica identity. -ALTER TABLE test_replica_identity3 ALTER COLUMN id DROP NOT NULL; -ERROR: column "id" is in index used as replica identity --- --- Test that replica identity can be set on an index that's not yet valid. --- (This matches the way pg_dump will try to dump a partitioned table.) --- -CREATE TABLE test_replica_identity4(id integer NOT NULL) PARTITION BY LIST (id); -CREATE TABLE test_replica_identity4_1(id integer NOT NULL); -ALTER TABLE ONLY test_replica_identity4 - ATTACH PARTITION test_replica_identity4_1 FOR VALUES IN (1); -ALTER TABLE ONLY test_replica_identity4 - ADD CONSTRAINT test_replica_identity4_pkey PRIMARY KEY (id); -ALTER TABLE ONLY test_replica_identity4 - REPLICA IDENTITY USING INDEX test_replica_identity4_pkey; -ALTER TABLE ONLY test_replica_identity4_1 - ADD CONSTRAINT test_replica_identity4_1_pkey PRIMARY KEY (id); -\d+ test_replica_identity4 - Partitioned table "public.test_replica_identity4" - Column | Type | Collation | Nullable | Default | Storage | Stats target | Description ---------+---------+-----------+----------+---------+---------+--------------+------------- - id | integer | | not null | | plain | | -Partition key: LIST (id) -Indexes: - "test_replica_identity4_pkey" PRIMARY KEY, btree (id) INVALID REPLICA IDENTITY -Partitions: test_replica_identity4_1 FOR VALUES IN (1) - -ALTER INDEX test_replica_identity4_pkey - ATTACH PARTITION test_replica_identity4_1_pkey; -\d+ test_replica_identity4 - Partitioned table "public.test_replica_identity4" - Column | Type | Collation | Nullable | Default | Storage | Stats target | Description ---------+---------+-----------+----------+---------+---------+--------------+------------- - id | integer | | not null | | plain | | -Partition key: LIST (id) -Indexes: - "test_replica_identity4_pkey" PRIMARY KEY, btree (id) REPLICA IDENTITY -Partitions: test_replica_identity4_1 FOR VALUES IN (1) - -DROP TABLE test_replica_identity; -DROP TABLE test_replica_identity2; -DROP TABLE test_replica_identity3; -DROP TABLE test_replica_identity4; -DROP TABLE test_replica_identity_othertable; -DROP TABLE test_replica_identity_t3; +psql: error: connection to server on socket "/tmp/pg_regress-03Az7d/.s.PGSQL.40051" failed: FATAL: the database system is not yet accepting connections +DETAIL: Consistent recovery state has not been yet reached. diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/rowsecurity.out /tmp/cirrus-ci-build/build/testrun/regress/regress/results/rowsecurity.out --- /tmp/cirrus-ci-build/src/test/regress/expected/rowsecurity.out 2024-09-20 21:41:45.742024523 +0000 +++ /tmp/cirrus-ci-build/build/testrun/regress/regress/results/rowsecurity.out 2024-09-20 21:46:06.637623507 +0000 @@ -1,4618 +1,2 @@ --- --- Test of Row-level security feature --- --- Clean up in case a prior regression run failed --- Suppress NOTICE messages when users/groups don't exist -SET client_min_messages TO 'warning'; -DROP USER IF EXISTS regress_rls_alice; -DROP USER IF EXISTS regress_rls_bob; -DROP USER IF EXISTS regress_rls_carol; -DROP USER IF EXISTS regress_rls_dave; -DROP USER IF EXISTS regress_rls_exempt_user; -DROP ROLE IF EXISTS regress_rls_group1; -DROP ROLE IF EXISTS regress_rls_group2; -DROP SCHEMA IF EXISTS regress_rls_schema CASCADE; -RESET client_min_messages; --- initial setup -CREATE USER regress_rls_alice NOLOGIN; -CREATE USER regress_rls_bob NOLOGIN; -CREATE USER regress_rls_carol NOLOGIN; -CREATE USER regress_rls_dave NOLOGIN; -CREATE USER regress_rls_exempt_user BYPASSRLS NOLOGIN; -CREATE ROLE regress_rls_group1 NOLOGIN; -CREATE ROLE regress_rls_group2 NOLOGIN; -GRANT regress_rls_group1 TO regress_rls_bob; -GRANT regress_rls_group2 TO regress_rls_carol; -CREATE SCHEMA regress_rls_schema; -GRANT ALL ON SCHEMA regress_rls_schema to public; -SET search_path = regress_rls_schema; --- setup of malicious function -CREATE OR REPLACE FUNCTION f_leak(text) RETURNS bool - COST 0.0000001 LANGUAGE plpgsql - AS 'BEGIN RAISE NOTICE ''f_leak => %'', $1; RETURN true; END'; -GRANT EXECUTE ON FUNCTION f_leak(text) TO public; --- BASIC Row-Level Security Scenario -SET SESSION AUTHORIZATION regress_rls_alice; -CREATE TABLE uaccount ( - pguser name primary key, - seclv int -); -GRANT SELECT ON uaccount TO public; -INSERT INTO uaccount VALUES - ('regress_rls_alice', 99), - ('regress_rls_bob', 1), - ('regress_rls_carol', 2), - ('regress_rls_dave', 3); -CREATE TABLE category ( - cid int primary key, - cname text -); -GRANT ALL ON category TO public; -INSERT INTO category VALUES - (11, 'novel'), - (22, 'science fiction'), - (33, 'technology'), - (44, 'manga'); -CREATE TABLE document ( - did int primary key, - cid int references category(cid), - dlevel int not null, - dauthor name, - dtitle text -); -GRANT ALL ON document TO public; -INSERT INTO document VALUES - ( 1, 11, 1, 'regress_rls_bob', 'my first novel'), - ( 2, 11, 2, 'regress_rls_bob', 'my second novel'), - ( 3, 22, 2, 'regress_rls_bob', 'my science fiction'), - ( 4, 44, 1, 'regress_rls_bob', 'my first manga'), - ( 5, 44, 2, 'regress_rls_bob', 'my second manga'), - ( 6, 22, 1, 'regress_rls_carol', 'great science fiction'), - ( 7, 33, 2, 'regress_rls_carol', 'great technology book'), - ( 8, 44, 1, 'regress_rls_carol', 'great manga'), - ( 9, 22, 1, 'regress_rls_dave', 'awesome science fiction'), - (10, 33, 2, 'regress_rls_dave', 'awesome technology book'); -ALTER TABLE document ENABLE ROW LEVEL SECURITY; --- user's security level must be higher than or equal to document's -CREATE POLICY p1 ON document AS PERMISSIVE - USING (dlevel <= (SELECT seclv FROM uaccount WHERE pguser = current_user)); --- try to create a policy of bogus type -CREATE POLICY p1 ON document AS UGLY - USING (dlevel <= (SELECT seclv FROM uaccount WHERE pguser = current_user)); -ERROR: unrecognized row security option "ugly" -LINE 1: CREATE POLICY p1 ON document AS UGLY - ^ -HINT: Only PERMISSIVE or RESTRICTIVE policies are supported currently. --- but Dave isn't allowed to anything at cid 50 or above --- this is to make sure that we sort the policies by name first --- when applying WITH CHECK, a later INSERT by Dave should fail due --- to p1r first -CREATE POLICY p2r ON document AS RESTRICTIVE TO regress_rls_dave - USING (cid <> 44 AND cid < 50); --- and Dave isn't allowed to see manga documents -CREATE POLICY p1r ON document AS RESTRICTIVE TO regress_rls_dave - USING (cid <> 44); -\dp - Access privileges - Schema | Name | Type | Access privileges | Column privileges | Policies ---------------------+----------+-------+----------------------------------------------+-------------------+-------------------------------------------- - regress_rls_schema | category | table | regress_rls_alice=arwdDxtm/regress_rls_alice+| | - | | | =arwdDxtm/regress_rls_alice | | - regress_rls_schema | document | table | regress_rls_alice=arwdDxtm/regress_rls_alice+| | p1: + - | | | =arwdDxtm/regress_rls_alice | | (u): (dlevel <= ( SELECT uaccount.seclv + - | | | | | FROM uaccount + - | | | | | WHERE (uaccount.pguser = CURRENT_USER)))+ - | | | | | p2r (RESTRICTIVE): + - | | | | | (u): ((cid <> 44) AND (cid < 50)) + - | | | | | to: regress_rls_dave + - | | | | | p1r (RESTRICTIVE): + - | | | | | (u): (cid <> 44) + - | | | | | to: regress_rls_dave - regress_rls_schema | uaccount | table | regress_rls_alice=arwdDxtm/regress_rls_alice+| | - | | | =r/regress_rls_alice | | -(3 rows) - -\d document - Table "regress_rls_schema.document" - Column | Type | Collation | Nullable | Default ----------+---------+-----------+----------+--------- - did | integer | | not null | - cid | integer | | | - dlevel | integer | | not null | - dauthor | name | | | - dtitle | text | | | -Indexes: - "document_pkey" PRIMARY KEY, btree (did) -Foreign-key constraints: - "document_cid_fkey" FOREIGN KEY (cid) REFERENCES category(cid) -Policies: - POLICY "p1" - USING ((dlevel <= ( SELECT uaccount.seclv - FROM uaccount - WHERE (uaccount.pguser = CURRENT_USER)))) - POLICY "p1r" AS RESTRICTIVE - TO regress_rls_dave - USING ((cid <> 44)) - POLICY "p2r" AS RESTRICTIVE - TO regress_rls_dave - USING (((cid <> 44) AND (cid < 50))) - -SELECT * FROM pg_policies WHERE schemaname = 'regress_rls_schema' AND tablename = 'document' ORDER BY policyname; - schemaname | tablename | policyname | permissive | roles | cmd | qual | with_check ---------------------+-----------+------------+-------------+--------------------+-----+--------------------------------------------+------------ - regress_rls_schema | document | p1 | PERMISSIVE | {public} | ALL | (dlevel <= ( SELECT uaccount.seclv +| - | | | | | | FROM uaccount +| - | | | | | | WHERE (uaccount.pguser = CURRENT_USER))) | - regress_rls_schema | document | p1r | RESTRICTIVE | {regress_rls_dave} | ALL | (cid <> 44) | - regress_rls_schema | document | p2r | RESTRICTIVE | {regress_rls_dave} | ALL | ((cid <> 44) AND (cid < 50)) | -(3 rows) - --- viewpoint from regress_rls_bob -SET SESSION AUTHORIZATION regress_rls_bob; -SET row_security TO ON; -SELECT * FROM document WHERE f_leak(dtitle) ORDER BY did; -NOTICE: f_leak => my first novel -NOTICE: f_leak => my first manga -NOTICE: f_leak => great science fiction -NOTICE: f_leak => great manga -NOTICE: f_leak => awesome science fiction - did | cid | dlevel | dauthor | dtitle ------+-----+--------+-------------------+------------------------- - 1 | 11 | 1 | regress_rls_bob | my first novel - 4 | 44 | 1 | regress_rls_bob | my first manga - 6 | 22 | 1 | regress_rls_carol | great science fiction - 8 | 44 | 1 | regress_rls_carol | great manga - 9 | 22 | 1 | regress_rls_dave | awesome science fiction -(5 rows) - -SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER BY did; -NOTICE: f_leak => my first novel -NOTICE: f_leak => my first manga -NOTICE: f_leak => great science fiction -NOTICE: f_leak => great manga -NOTICE: f_leak => awesome science fiction - cid | did | dlevel | dauthor | dtitle | cname ------+-----+--------+-------------------+-------------------------+----------------- - 11 | 1 | 1 | regress_rls_bob | my first novel | novel - 44 | 4 | 1 | regress_rls_bob | my first manga | manga - 22 | 6 | 1 | regress_rls_carol | great science fiction | science fiction - 44 | 8 | 1 | regress_rls_carol | great manga | manga - 22 | 9 | 1 | regress_rls_dave | awesome science fiction | science fiction -(5 rows) - --- try a sampled version -SELECT * FROM document TABLESAMPLE BERNOULLI(50) REPEATABLE(0) - WHERE f_leak(dtitle) ORDER BY did; -NOTICE: f_leak => my first manga -NOTICE: f_leak => great science fiction -NOTICE: f_leak => great manga -NOTICE: f_leak => awesome science fiction - did | cid | dlevel | dauthor | dtitle ------+-----+--------+-------------------+------------------------- - 4 | 44 | 1 | regress_rls_bob | my first manga - 6 | 22 | 1 | regress_rls_carol | great science fiction - 8 | 44 | 1 | regress_rls_carol | great manga - 9 | 22 | 1 | regress_rls_dave | awesome science fiction -(4 rows) - --- viewpoint from regress_rls_carol -SET SESSION AUTHORIZATION regress_rls_carol; -SELECT * FROM document WHERE f_leak(dtitle) ORDER BY did; -NOTICE: f_leak => my first novel -NOTICE: f_leak => my second novel -NOTICE: f_leak => my science fiction -NOTICE: f_leak => my first manga -NOTICE: f_leak => my second manga -NOTICE: f_leak => great science fiction -NOTICE: f_leak => great technology book -NOTICE: f_leak => great manga -NOTICE: f_leak => awesome science fiction -NOTICE: f_leak => awesome technology book - did | cid | dlevel | dauthor | dtitle ------+-----+--------+-------------------+------------------------- - 1 | 11 | 1 | regress_rls_bob | my first novel - 2 | 11 | 2 | regress_rls_bob | my second novel - 3 | 22 | 2 | regress_rls_bob | my science fiction - 4 | 44 | 1 | regress_rls_bob | my first manga - 5 | 44 | 2 | regress_rls_bob | my second manga - 6 | 22 | 1 | regress_rls_carol | great science fiction - 7 | 33 | 2 | regress_rls_carol | great technology book - 8 | 44 | 1 | regress_rls_carol | great manga - 9 | 22 | 1 | regress_rls_dave | awesome science fiction - 10 | 33 | 2 | regress_rls_dave | awesome technology book -(10 rows) - -SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER BY did; -NOTICE: f_leak => my first novel -NOTICE: f_leak => my second novel -NOTICE: f_leak => my science fiction -NOTICE: f_leak => my first manga -NOTICE: f_leak => my second manga -NOTICE: f_leak => great science fiction -NOTICE: f_leak => great technology book -NOTICE: f_leak => great manga -NOTICE: f_leak => awesome science fiction -NOTICE: f_leak => awesome technology book - cid | did | dlevel | dauthor | dtitle | cname ------+-----+--------+-------------------+-------------------------+----------------- - 11 | 1 | 1 | regress_rls_bob | my first novel | novel - 11 | 2 | 2 | regress_rls_bob | my second novel | novel - 22 | 3 | 2 | regress_rls_bob | my science fiction | science fiction - 44 | 4 | 1 | regress_rls_bob | my first manga | manga - 44 | 5 | 2 | regress_rls_bob | my second manga | manga - 22 | 6 | 1 | regress_rls_carol | great science fiction | science fiction - 33 | 7 | 2 | regress_rls_carol | great technology book | technology - 44 | 8 | 1 | regress_rls_carol | great manga | manga - 22 | 9 | 1 | regress_rls_dave | awesome science fiction | science fiction - 33 | 10 | 2 | regress_rls_dave | awesome technology book | technology -(10 rows) - --- try a sampled version -SELECT * FROM document TABLESAMPLE BERNOULLI(50) REPEATABLE(0) - WHERE f_leak(dtitle) ORDER BY did; -NOTICE: f_leak => my first manga -NOTICE: f_leak => my second manga -NOTICE: f_leak => great science fiction -NOTICE: f_leak => great manga -NOTICE: f_leak => awesome science fiction - did | cid | dlevel | dauthor | dtitle ------+-----+--------+-------------------+------------------------- - 4 | 44 | 1 | regress_rls_bob | my first manga - 5 | 44 | 2 | regress_rls_bob | my second manga - 6 | 22 | 1 | regress_rls_carol | great science fiction - 8 | 44 | 1 | regress_rls_carol | great manga - 9 | 22 | 1 | regress_rls_dave | awesome science fiction -(5 rows) - -EXPLAIN (COSTS OFF) SELECT * FROM document WHERE f_leak(dtitle); - QUERY PLAN --------------------------------------------------------------- - Seq Scan on document - Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle)) - InitPlan 1 - -> Index Scan using uaccount_pkey on uaccount - Index Cond: (pguser = CURRENT_USER) -(5 rows) - -EXPLAIN (COSTS OFF) SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle); - QUERY PLAN --------------------------------------------------------------------------- - Hash Join - Hash Cond: (category.cid = document.cid) - InitPlan 1 - -> Index Scan using uaccount_pkey on uaccount - Index Cond: (pguser = CURRENT_USER) - -> Seq Scan on category - -> Hash - -> Seq Scan on document - Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle)) -(9 rows) - --- viewpoint from regress_rls_dave -SET SESSION AUTHORIZATION regress_rls_dave; -SELECT * FROM document WHERE f_leak(dtitle) ORDER BY did; -NOTICE: f_leak => my first novel -NOTICE: f_leak => my second novel -NOTICE: f_leak => my science fiction -NOTICE: f_leak => great science fiction -NOTICE: f_leak => great technology book -NOTICE: f_leak => awesome science fiction -NOTICE: f_leak => awesome technology book - did | cid | dlevel | dauthor | dtitle ------+-----+--------+-------------------+------------------------- - 1 | 11 | 1 | regress_rls_bob | my first novel - 2 | 11 | 2 | regress_rls_bob | my second novel - 3 | 22 | 2 | regress_rls_bob | my science fiction - 6 | 22 | 1 | regress_rls_carol | great science fiction - 7 | 33 | 2 | regress_rls_carol | great technology book - 9 | 22 | 1 | regress_rls_dave | awesome science fiction - 10 | 33 | 2 | regress_rls_dave | awesome technology book -(7 rows) - -SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER BY did; -NOTICE: f_leak => my first novel -NOTICE: f_leak => my second novel -NOTICE: f_leak => my science fiction -NOTICE: f_leak => great science fiction -NOTICE: f_leak => great technology book -NOTICE: f_leak => awesome science fiction -NOTICE: f_leak => awesome technology book - cid | did | dlevel | dauthor | dtitle | cname ------+-----+--------+-------------------+-------------------------+----------------- - 11 | 1 | 1 | regress_rls_bob | my first novel | novel - 11 | 2 | 2 | regress_rls_bob | my second novel | novel - 22 | 3 | 2 | regress_rls_bob | my science fiction | science fiction - 22 | 6 | 1 | regress_rls_carol | great science fiction | science fiction - 33 | 7 | 2 | regress_rls_carol | great technology book | technology - 22 | 9 | 1 | regress_rls_dave | awesome science fiction | science fiction - 33 | 10 | 2 | regress_rls_dave | awesome technology book | technology -(7 rows) - -EXPLAIN (COSTS OFF) SELECT * FROM document WHERE f_leak(dtitle); - QUERY PLAN -------------------------------------------------------------------------------------------------------------- - Seq Scan on document - Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle)) - InitPlan 1 - -> Index Scan using uaccount_pkey on uaccount - Index Cond: (pguser = CURRENT_USER) -(5 rows) - -EXPLAIN (COSTS OFF) SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle); - QUERY PLAN -------------------------------------------------------------------------------------------------------------------------- - Hash Join - Hash Cond: (category.cid = document.cid) - InitPlan 1 - -> Index Scan using uaccount_pkey on uaccount - Index Cond: (pguser = CURRENT_USER) - -> Seq Scan on category - -> Hash - -> Seq Scan on document - Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle)) -(9 rows) - --- 44 would technically fail for both p2r and p1r, but we should get an error --- back from p1r for this because it sorts first -INSERT INTO document VALUES (100, 44, 1, 'regress_rls_dave', 'testing sorting of policies'); -- fail -ERROR: new row violates row-level security policy "p1r" for table "document" --- Just to see a p2r error -INSERT INTO document VALUES (100, 55, 1, 'regress_rls_dave', 'testing sorting of policies'); -- fail -ERROR: new row violates row-level security policy "p2r" for table "document" --- only owner can change policies -ALTER POLICY p1 ON document USING (true); --fail -ERROR: must be owner of table document -DROP POLICY p1 ON document; --fail -ERROR: must be owner of relation document -SET SESSION AUTHORIZATION regress_rls_alice; -ALTER POLICY p1 ON document USING (dauthor = current_user); --- viewpoint from regress_rls_bob again -SET SESSION AUTHORIZATION regress_rls_bob; -SELECT * FROM document WHERE f_leak(dtitle) ORDER BY did; -NOTICE: f_leak => my first novel -NOTICE: f_leak => my second novel -NOTICE: f_leak => my science fiction -NOTICE: f_leak => my first manga -NOTICE: f_leak => my second manga - did | cid | dlevel | dauthor | dtitle ------+-----+--------+-----------------+-------------------- - 1 | 11 | 1 | regress_rls_bob | my first novel - 2 | 11 | 2 | regress_rls_bob | my second novel - 3 | 22 | 2 | regress_rls_bob | my science fiction - 4 | 44 | 1 | regress_rls_bob | my first manga - 5 | 44 | 2 | regress_rls_bob | my second manga -(5 rows) - -SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER by did; -NOTICE: f_leak => my first novel -NOTICE: f_leak => my second novel -NOTICE: f_leak => my science fiction -NOTICE: f_leak => my first manga -NOTICE: f_leak => my second manga - cid | did | dlevel | dauthor | dtitle | cname ------+-----+--------+-----------------+--------------------+----------------- - 11 | 1 | 1 | regress_rls_bob | my first novel | novel - 11 | 2 | 2 | regress_rls_bob | my second novel | novel - 22 | 3 | 2 | regress_rls_bob | my science fiction | science fiction - 44 | 4 | 1 | regress_rls_bob | my first manga | manga - 44 | 5 | 2 | regress_rls_bob | my second manga | manga -(5 rows) - --- viewpoint from rls_regres_carol again -SET SESSION AUTHORIZATION regress_rls_carol; -SELECT * FROM document WHERE f_leak(dtitle) ORDER BY did; -NOTICE: f_leak => great science fiction -NOTICE: f_leak => great technology book -NOTICE: f_leak => great manga - did | cid | dlevel | dauthor | dtitle ------+-----+--------+-------------------+----------------------- - 6 | 22 | 1 | regress_rls_carol | great science fiction - 7 | 33 | 2 | regress_rls_carol | great technology book - 8 | 44 | 1 | regress_rls_carol | great manga -(3 rows) - -SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER by did; -NOTICE: f_leak => great science fiction -NOTICE: f_leak => great technology book -NOTICE: f_leak => great manga - cid | did | dlevel | dauthor | dtitle | cname ------+-----+--------+-------------------+-----------------------+----------------- - 22 | 6 | 1 | regress_rls_carol | great science fiction | science fiction - 33 | 7 | 2 | regress_rls_carol | great technology book | technology - 44 | 8 | 1 | regress_rls_carol | great manga | manga -(3 rows) - -EXPLAIN (COSTS OFF) SELECT * FROM document WHERE f_leak(dtitle); - QUERY PLAN ---------------------------------------------------------- - Seq Scan on document - Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle)) -(2 rows) - -EXPLAIN (COSTS OFF) SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle); - QUERY PLAN ---------------------------------------------------------------- - Nested Loop - -> Seq Scan on document - Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle)) - -> Index Scan using category_pkey on category - Index Cond: (cid = document.cid) -(5 rows) - --- interaction of FK/PK constraints -SET SESSION AUTHORIZATION regress_rls_alice; -CREATE POLICY p2 ON category - USING (CASE WHEN current_user = 'regress_rls_bob' THEN cid IN (11, 33) - WHEN current_user = 'regress_rls_carol' THEN cid IN (22, 44) - ELSE false END); -ALTER TABLE category ENABLE ROW LEVEL SECURITY; --- cannot delete PK referenced by invisible FK -SET SESSION AUTHORIZATION regress_rls_bob; -SELECT * FROM document d FULL OUTER JOIN category c on d.cid = c.cid ORDER BY d.did, c.cid; - did | cid | dlevel | dauthor | dtitle | cid | cname ------+-----+--------+-----------------+--------------------+-----+------------ - 1 | 11 | 1 | regress_rls_bob | my first novel | 11 | novel - 2 | 11 | 2 | regress_rls_bob | my second novel | 11 | novel - 3 | 22 | 2 | regress_rls_bob | my science fiction | | - 4 | 44 | 1 | regress_rls_bob | my first manga | | - 5 | 44 | 2 | regress_rls_bob | my second manga | | - | | | | | 33 | technology -(6 rows) - -DELETE FROM category WHERE cid = 33; -- fails with FK violation -ERROR: update or delete on table "category" violates foreign key constraint "document_cid_fkey" on table "document" -DETAIL: Key is still referenced from table "document". --- can insert FK referencing invisible PK -SET SESSION AUTHORIZATION regress_rls_carol; -SELECT * FROM document d FULL OUTER JOIN category c on d.cid = c.cid ORDER BY d.did, c.cid; - did | cid | dlevel | dauthor | dtitle | cid | cname ------+-----+--------+-------------------+-----------------------+-----+----------------- - 6 | 22 | 1 | regress_rls_carol | great science fiction | 22 | science fiction - 7 | 33 | 2 | regress_rls_carol | great technology book | | - 8 | 44 | 1 | regress_rls_carol | great manga | 44 | manga -(3 rows) - -INSERT INTO document VALUES (11, 33, 1, current_user, 'hoge'); --- UNIQUE or PRIMARY KEY constraint violation DOES reveal presence of row -SET SESSION AUTHORIZATION regress_rls_bob; -INSERT INTO document VALUES (8, 44, 1, 'regress_rls_bob', 'my third manga'); -- Must fail with unique violation, revealing presence of did we can't see -ERROR: duplicate key value violates unique constraint "document_pkey" -SELECT * FROM document WHERE did = 8; -- and confirm we can't see it - did | cid | dlevel | dauthor | dtitle ------+-----+--------+---------+-------- -(0 rows) - --- RLS policies are checked before constraints -INSERT INTO document VALUES (8, 44, 1, 'regress_rls_carol', 'my third manga'); -- Should fail with RLS check violation, not duplicate key violation -ERROR: new row violates row-level security policy for table "document" -UPDATE document SET did = 8, dauthor = 'regress_rls_carol' WHERE did = 5; -- Should fail with RLS check violation, not duplicate key violation -ERROR: new row violates row-level security policy for table "document" --- database superuser does bypass RLS policy when enabled -RESET SESSION AUTHORIZATION; -SET row_security TO ON; -SELECT * FROM document; - did | cid | dlevel | dauthor | dtitle ------+-----+--------+-------------------+------------------------- - 1 | 11 | 1 | regress_rls_bob | my first novel - 2 | 11 | 2 | regress_rls_bob | my second novel - 3 | 22 | 2 | regress_rls_bob | my science fiction - 4 | 44 | 1 | regress_rls_bob | my first manga - 5 | 44 | 2 | regress_rls_bob | my second manga - 6 | 22 | 1 | regress_rls_carol | great science fiction - 7 | 33 | 2 | regress_rls_carol | great technology book - 8 | 44 | 1 | regress_rls_carol | great manga - 9 | 22 | 1 | regress_rls_dave | awesome science fiction - 10 | 33 | 2 | regress_rls_dave | awesome technology book - 11 | 33 | 1 | regress_rls_carol | hoge -(11 rows) - -SELECT * FROM category; - cid | cname ------+----------------- - 11 | novel - 22 | science fiction - 33 | technology - 44 | manga -(4 rows) - --- database superuser does bypass RLS policy when disabled -RESET SESSION AUTHORIZATION; -SET row_security TO OFF; -SELECT * FROM document; - did | cid | dlevel | dauthor | dtitle ------+-----+--------+-------------------+------------------------- - 1 | 11 | 1 | regress_rls_bob | my first novel - 2 | 11 | 2 | regress_rls_bob | my second novel - 3 | 22 | 2 | regress_rls_bob | my science fiction - 4 | 44 | 1 | regress_rls_bob | my first manga - 5 | 44 | 2 | regress_rls_bob | my second manga - 6 | 22 | 1 | regress_rls_carol | great science fiction - 7 | 33 | 2 | regress_rls_carol | great technology book - 8 | 44 | 1 | regress_rls_carol | great manga - 9 | 22 | 1 | regress_rls_dave | awesome science fiction - 10 | 33 | 2 | regress_rls_dave | awesome technology book - 11 | 33 | 1 | regress_rls_carol | hoge -(11 rows) - -SELECT * FROM category; - cid | cname ------+----------------- - 11 | novel - 22 | science fiction - 33 | technology - 44 | manga -(4 rows) - --- database non-superuser with bypass privilege can bypass RLS policy when disabled -SET SESSION AUTHORIZATION regress_rls_exempt_user; -SET row_security TO OFF; -SELECT * FROM document; - did | cid | dlevel | dauthor | dtitle ------+-----+--------+-------------------+------------------------- - 1 | 11 | 1 | regress_rls_bob | my first novel - 2 | 11 | 2 | regress_rls_bob | my second novel - 3 | 22 | 2 | regress_rls_bob | my science fiction - 4 | 44 | 1 | regress_rls_bob | my first manga - 5 | 44 | 2 | regress_rls_bob | my second manga - 6 | 22 | 1 | regress_rls_carol | great science fiction - 7 | 33 | 2 | regress_rls_carol | great technology book - 8 | 44 | 1 | regress_rls_carol | great manga - 9 | 22 | 1 | regress_rls_dave | awesome science fiction - 10 | 33 | 2 | regress_rls_dave | awesome technology book - 11 | 33 | 1 | regress_rls_carol | hoge -(11 rows) - -SELECT * FROM category; - cid | cname ------+----------------- - 11 | novel - 22 | science fiction - 33 | technology - 44 | manga -(4 rows) - --- RLS policy does not apply to table owner when RLS enabled. -SET SESSION AUTHORIZATION regress_rls_alice; -SET row_security TO ON; -SELECT * FROM document; - did | cid | dlevel | dauthor | dtitle ------+-----+--------+-------------------+------------------------- - 1 | 11 | 1 | regress_rls_bob | my first novel - 2 | 11 | 2 | regress_rls_bob | my second novel - 3 | 22 | 2 | regress_rls_bob | my science fiction - 4 | 44 | 1 | regress_rls_bob | my first manga - 5 | 44 | 2 | regress_rls_bob | my second manga - 6 | 22 | 1 | regress_rls_carol | great science fiction - 7 | 33 | 2 | regress_rls_carol | great technology book - 8 | 44 | 1 | regress_rls_carol | great manga - 9 | 22 | 1 | regress_rls_dave | awesome science fiction - 10 | 33 | 2 | regress_rls_dave | awesome technology book - 11 | 33 | 1 | regress_rls_carol | hoge -(11 rows) - -SELECT * FROM category; - cid | cname ------+----------------- - 11 | novel - 22 | science fiction - 33 | technology - 44 | manga -(4 rows) - --- RLS policy does not apply to table owner when RLS disabled. -SET SESSION AUTHORIZATION regress_rls_alice; -SET row_security TO OFF; -SELECT * FROM document; - did | cid | dlevel | dauthor | dtitle ------+-----+--------+-------------------+------------------------- - 1 | 11 | 1 | regress_rls_bob | my first novel - 2 | 11 | 2 | regress_rls_bob | my second novel - 3 | 22 | 2 | regress_rls_bob | my science fiction - 4 | 44 | 1 | regress_rls_bob | my first manga - 5 | 44 | 2 | regress_rls_bob | my second manga - 6 | 22 | 1 | regress_rls_carol | great science fiction - 7 | 33 | 2 | regress_rls_carol | great technology book - 8 | 44 | 1 | regress_rls_carol | great manga - 9 | 22 | 1 | regress_rls_dave | awesome science fiction - 10 | 33 | 2 | regress_rls_dave | awesome technology book - 11 | 33 | 1 | regress_rls_carol | hoge -(11 rows) - -SELECT * FROM category; - cid | cname ------+----------------- - 11 | novel - 22 | science fiction - 33 | technology - 44 | manga -(4 rows) - --- --- Table inheritance and RLS policy --- -SET SESSION AUTHORIZATION regress_rls_alice; -SET row_security TO ON; -CREATE TABLE t1 (id int not null primary key, a int, junk1 text, b text); -ALTER TABLE t1 DROP COLUMN junk1; -- just a disturbing factor -GRANT ALL ON t1 TO public; -COPY t1 FROM stdin WITH ; -CREATE TABLE t2 (c float) INHERITS (t1); -GRANT ALL ON t2 TO public; -COPY t2 FROM stdin; -CREATE TABLE t3 (id int not null primary key, c text, b text, a int); -ALTER TABLE t3 INHERIT t1; -GRANT ALL ON t3 TO public; -COPY t3(id, a,b,c) FROM stdin; -CREATE POLICY p1 ON t1 FOR ALL TO PUBLIC USING (a % 2 = 0); -- be even number -CREATE POLICY p2 ON t2 FOR ALL TO PUBLIC USING (a % 2 = 1); -- be odd number -ALTER TABLE t1 ENABLE ROW LEVEL SECURITY; -ALTER TABLE t2 ENABLE ROW LEVEL SECURITY; -SET SESSION AUTHORIZATION regress_rls_bob; -SELECT * FROM t1; - id | a | b ------+---+----- - 102 | 2 | bbb - 104 | 4 | dad - 202 | 2 | bcd - 204 | 4 | def - 302 | 2 | yyy -(5 rows) - -EXPLAIN (COSTS OFF) SELECT * FROM t1; - QUERY PLAN -------------------------------- - Append - -> Seq Scan on t1 t1_1 - Filter: ((a % 2) = 0) - -> Seq Scan on t2 t1_2 - Filter: ((a % 2) = 0) - -> Seq Scan on t3 t1_3 - Filter: ((a % 2) = 0) -(7 rows) - -SELECT * FROM t1 WHERE f_leak(b); -NOTICE: f_leak => bbb -NOTICE: f_leak => dad -NOTICE: f_leak => bcd -NOTICE: f_leak => def -NOTICE: f_leak => yyy - id | a | b ------+---+----- - 102 | 2 | bbb - 104 | 4 | dad - 202 | 2 | bcd - 204 | 4 | def - 302 | 2 | yyy -(5 rows) - -EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b); - QUERY PLAN ------------------------------------------------ - Append - -> Seq Scan on t1 t1_1 - Filter: (((a % 2) = 0) AND f_leak(b)) - -> Seq Scan on t2 t1_2 - Filter: (((a % 2) = 0) AND f_leak(b)) - -> Seq Scan on t3 t1_3 - Filter: (((a % 2) = 0) AND f_leak(b)) -(7 rows) - --- reference to system column -SELECT tableoid::regclass, * FROM t1; - tableoid | id | a | b -----------+-----+---+----- - t1 | 102 | 2 | bbb - t1 | 104 | 4 | dad - t2 | 202 | 2 | bcd - t2 | 204 | 4 | def - t3 | 302 | 2 | yyy -(5 rows) - -EXPLAIN (COSTS OFF) SELECT *, t1 FROM t1; - QUERY PLAN -------------------------------- - Append - -> Seq Scan on t1 t1_1 - Filter: ((a % 2) = 0) - -> Seq Scan on t2 t1_2 - Filter: ((a % 2) = 0) - -> Seq Scan on t3 t1_3 - Filter: ((a % 2) = 0) -(7 rows) - --- reference to whole-row reference -SELECT *, t1 FROM t1; - id | a | b | t1 ------+---+-----+------------- - 102 | 2 | bbb | (102,2,bbb) - 104 | 4 | dad | (104,4,dad) - 202 | 2 | bcd | (202,2,bcd) - 204 | 4 | def | (204,4,def) - 302 | 2 | yyy | (302,2,yyy) -(5 rows) - -EXPLAIN (COSTS OFF) SELECT *, t1 FROM t1; - QUERY PLAN -------------------------------- - Append - -> Seq Scan on t1 t1_1 - Filter: ((a % 2) = 0) - -> Seq Scan on t2 t1_2 - Filter: ((a % 2) = 0) - -> Seq Scan on t3 t1_3 - Filter: ((a % 2) = 0) -(7 rows) - --- for share/update lock -SELECT * FROM t1 FOR SHARE; - id | a | b ------+---+----- - 102 | 2 | bbb - 104 | 4 | dad - 202 | 2 | bcd - 204 | 4 | def - 302 | 2 | yyy -(5 rows) - -EXPLAIN (COSTS OFF) SELECT * FROM t1 FOR SHARE; - QUERY PLAN -------------------------------------- - LockRows - -> Append - -> Seq Scan on t1 t1_1 - Filter: ((a % 2) = 0) - -> Seq Scan on t2 t1_2 - Filter: ((a % 2) = 0) - -> Seq Scan on t3 t1_3 - Filter: ((a % 2) = 0) -(8 rows) - -SELECT * FROM t1 WHERE f_leak(b) FOR SHARE; -NOTICE: f_leak => bbb -NOTICE: f_leak => dad -NOTICE: f_leak => bcd -NOTICE: f_leak => def -NOTICE: f_leak => yyy - id | a | b ------+---+----- - 102 | 2 | bbb - 104 | 4 | dad - 202 | 2 | bcd - 204 | 4 | def - 302 | 2 | yyy -(5 rows) - -EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b) FOR SHARE; - QUERY PLAN ------------------------------------------------------ - LockRows - -> Append - -> Seq Scan on t1 t1_1 - Filter: (((a % 2) = 0) AND f_leak(b)) - -> Seq Scan on t2 t1_2 - Filter: (((a % 2) = 0) AND f_leak(b)) - -> Seq Scan on t3 t1_3 - Filter: (((a % 2) = 0) AND f_leak(b)) -(8 rows) - --- union all query -SELECT a, b, tableoid::regclass FROM t2 UNION ALL SELECT a, b, tableoid::regclass FROM t3; - a | b | tableoid ----+-----+---------- - 1 | abc | t2 - 3 | cde | t2 - 1 | xxx | t3 - 2 | yyy | t3 - 3 | zzz | t3 -(5 rows) - -EXPLAIN (COSTS OFF) SELECT a, b, tableoid::regclass FROM t2 UNION ALL SELECT a, b, tableoid::regclass FROM t3; - QUERY PLAN -------------------------------- - Append - -> Seq Scan on t2 - Filter: ((a % 2) = 1) - -> Seq Scan on t3 -(4 rows) - --- superuser is allowed to bypass RLS checks -RESET SESSION AUTHORIZATION; -SET row_security TO OFF; -SELECT * FROM t1 WHERE f_leak(b); -NOTICE: f_leak => aba -NOTICE: f_leak => bbb -NOTICE: f_leak => ccc -NOTICE: f_leak => dad -NOTICE: f_leak => abc -NOTICE: f_leak => bcd -NOTICE: f_leak => cde -NOTICE: f_leak => def -NOTICE: f_leak => xxx -NOTICE: f_leak => yyy -NOTICE: f_leak => zzz - id | a | b ------+---+----- - 101 | 1 | aba - 102 | 2 | bbb - 103 | 3 | ccc - 104 | 4 | dad - 201 | 1 | abc - 202 | 2 | bcd - 203 | 3 | cde - 204 | 4 | def - 301 | 1 | xxx - 302 | 2 | yyy - 303 | 3 | zzz -(11 rows) - -EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b); - QUERY PLAN ---------------------------- - Append - -> Seq Scan on t1 t1_1 - Filter: f_leak(b) - -> Seq Scan on t2 t1_2 - Filter: f_leak(b) - -> Seq Scan on t3 t1_3 - Filter: f_leak(b) -(7 rows) - --- non-superuser with bypass privilege can bypass RLS policy when disabled -SET SESSION AUTHORIZATION regress_rls_exempt_user; -SET row_security TO OFF; -SELECT * FROM t1 WHERE f_leak(b); -NOTICE: f_leak => aba -NOTICE: f_leak => bbb -NOTICE: f_leak => ccc -NOTICE: f_leak => dad -NOTICE: f_leak => abc -NOTICE: f_leak => bcd -NOTICE: f_leak => cde -NOTICE: f_leak => def -NOTICE: f_leak => xxx -NOTICE: f_leak => yyy -NOTICE: f_leak => zzz - id | a | b ------+---+----- - 101 | 1 | aba - 102 | 2 | bbb - 103 | 3 | ccc - 104 | 4 | dad - 201 | 1 | abc - 202 | 2 | bcd - 203 | 3 | cde - 204 | 4 | def - 301 | 1 | xxx - 302 | 2 | yyy - 303 | 3 | zzz -(11 rows) - -EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b); - QUERY PLAN ---------------------------- - Append - -> Seq Scan on t1 t1_1 - Filter: f_leak(b) - -> Seq Scan on t2 t1_2 - Filter: f_leak(b) - -> Seq Scan on t3 t1_3 - Filter: f_leak(b) -(7 rows) - --- --- Partitioned Tables --- -SET SESSION AUTHORIZATION regress_rls_alice; -CREATE TABLE part_document ( - did int, - cid int, - dlevel int not null, - dauthor name, - dtitle text -) PARTITION BY RANGE (cid); -GRANT ALL ON part_document TO public; --- Create partitions for document categories -CREATE TABLE part_document_fiction PARTITION OF part_document FOR VALUES FROM (11) to (12); -CREATE TABLE part_document_satire PARTITION OF part_document FOR VALUES FROM (55) to (56); -CREATE TABLE part_document_nonfiction PARTITION OF part_document FOR VALUES FROM (99) to (100); -GRANT ALL ON part_document_fiction TO public; -GRANT ALL ON part_document_satire TO public; -GRANT ALL ON part_document_nonfiction TO public; -INSERT INTO part_document VALUES - ( 1, 11, 1, 'regress_rls_bob', 'my first novel'), - ( 2, 11, 2, 'regress_rls_bob', 'my second novel'), - ( 3, 99, 2, 'regress_rls_bob', 'my science textbook'), - ( 4, 55, 1, 'regress_rls_bob', 'my first satire'), - ( 5, 99, 2, 'regress_rls_bob', 'my history book'), - ( 6, 11, 1, 'regress_rls_carol', 'great science fiction'), - ( 7, 99, 2, 'regress_rls_carol', 'great technology book'), - ( 8, 55, 2, 'regress_rls_carol', 'great satire'), - ( 9, 11, 1, 'regress_rls_dave', 'awesome science fiction'), - (10, 99, 2, 'regress_rls_dave', 'awesome technology book'); -ALTER TABLE part_document ENABLE ROW LEVEL SECURITY; --- Create policy on parent --- user's security level must be higher than or equal to document's -CREATE POLICY pp1 ON part_document AS PERMISSIVE - USING (dlevel <= (SELECT seclv FROM uaccount WHERE pguser = current_user)); --- Dave is only allowed to see cid < 55 -CREATE POLICY pp1r ON part_document AS RESTRICTIVE TO regress_rls_dave - USING (cid < 55); -\d+ part_document - Partitioned table "regress_rls_schema.part_document" - Column | Type | Collation | Nullable | Default | Storage | Stats target | Description ----------+---------+-----------+----------+---------+----------+--------------+------------- - did | integer | | | | plain | | - cid | integer | | | | plain | | - dlevel | integer | | not null | | plain | | - dauthor | name | | | | plain | | - dtitle | text | | | | extended | | -Partition key: RANGE (cid) -Policies: - POLICY "pp1" - USING ((dlevel <= ( SELECT uaccount.seclv - FROM uaccount - WHERE (uaccount.pguser = CURRENT_USER)))) - POLICY "pp1r" AS RESTRICTIVE - TO regress_rls_dave - USING ((cid < 55)) -Partitions: part_document_fiction FOR VALUES FROM (11) TO (12), - part_document_nonfiction FOR VALUES FROM (99) TO (100), - part_document_satire FOR VALUES FROM (55) TO (56) - -SELECT * FROM pg_policies WHERE schemaname = 'regress_rls_schema' AND tablename like '%part_document%' ORDER BY policyname; - schemaname | tablename | policyname | permissive | roles | cmd | qual | with_check ---------------------+---------------+------------+-------------+--------------------+-----+--------------------------------------------+------------ - regress_rls_schema | part_document | pp1 | PERMISSIVE | {public} | ALL | (dlevel <= ( SELECT uaccount.seclv +| - | | | | | | FROM uaccount +| - | | | | | | WHERE (uaccount.pguser = CURRENT_USER))) | - regress_rls_schema | part_document | pp1r | RESTRICTIVE | {regress_rls_dave} | ALL | (cid < 55) | -(2 rows) - --- viewpoint from regress_rls_bob -SET SESSION AUTHORIZATION regress_rls_bob; -SET row_security TO ON; -SELECT * FROM part_document WHERE f_leak(dtitle) ORDER BY did; -NOTICE: f_leak => my first novel -NOTICE: f_leak => great science fiction -NOTICE: f_leak => awesome science fiction -NOTICE: f_leak => my first satire - did | cid | dlevel | dauthor | dtitle ------+-----+--------+-------------------+------------------------- - 1 | 11 | 1 | regress_rls_bob | my first novel - 4 | 55 | 1 | regress_rls_bob | my first satire - 6 | 11 | 1 | regress_rls_carol | great science fiction - 9 | 11 | 1 | regress_rls_dave | awesome science fiction -(4 rows) - -EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle); - QUERY PLAN --------------------------------------------------------------------- - Append - InitPlan 1 - -> Index Scan using uaccount_pkey on uaccount - Index Cond: (pguser = CURRENT_USER) - -> Seq Scan on part_document_fiction part_document_1 - Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle)) - -> Seq Scan on part_document_satire part_document_2 - Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle)) - -> Seq Scan on part_document_nonfiction part_document_3 - Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle)) -(10 rows) - --- viewpoint from regress_rls_carol -SET SESSION AUTHORIZATION regress_rls_carol; -SELECT * FROM part_document WHERE f_leak(dtitle) ORDER BY did; -NOTICE: f_leak => my first novel -NOTICE: f_leak => my second novel -NOTICE: f_leak => great science fiction -NOTICE: f_leak => awesome science fiction -NOTICE: f_leak => my first satire -NOTICE: f_leak => great satire -NOTICE: f_leak => my science textbook -NOTICE: f_leak => my history book -NOTICE: f_leak => great technology book -NOTICE: f_leak => awesome technology book - did | cid | dlevel | dauthor | dtitle ------+-----+--------+-------------------+------------------------- - 1 | 11 | 1 | regress_rls_bob | my first novel - 2 | 11 | 2 | regress_rls_bob | my second novel - 3 | 99 | 2 | regress_rls_bob | my science textbook - 4 | 55 | 1 | regress_rls_bob | my first satire - 5 | 99 | 2 | regress_rls_bob | my history book - 6 | 11 | 1 | regress_rls_carol | great science fiction - 7 | 99 | 2 | regress_rls_carol | great technology book - 8 | 55 | 2 | regress_rls_carol | great satire - 9 | 11 | 1 | regress_rls_dave | awesome science fiction - 10 | 99 | 2 | regress_rls_dave | awesome technology book -(10 rows) - -EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle); - QUERY PLAN --------------------------------------------------------------------- - Append - InitPlan 1 - -> Index Scan using uaccount_pkey on uaccount - Index Cond: (pguser = CURRENT_USER) - -> Seq Scan on part_document_fiction part_document_1 - Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle)) - -> Seq Scan on part_document_satire part_document_2 - Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle)) - -> Seq Scan on part_document_nonfiction part_document_3 - Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle)) -(10 rows) - --- viewpoint from regress_rls_dave -SET SESSION AUTHORIZATION regress_rls_dave; -SELECT * FROM part_document WHERE f_leak(dtitle) ORDER BY did; -NOTICE: f_leak => my first novel -NOTICE: f_leak => my second novel -NOTICE: f_leak => great science fiction -NOTICE: f_leak => awesome science fiction - did | cid | dlevel | dauthor | dtitle ------+-----+--------+-------------------+------------------------- - 1 | 11 | 1 | regress_rls_bob | my first novel - 2 | 11 | 2 | regress_rls_bob | my second novel - 6 | 11 | 1 | regress_rls_carol | great science fiction - 9 | 11 | 1 | regress_rls_dave | awesome science fiction -(4 rows) - -EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle); - QUERY PLAN ------------------------------------------------------------------------------ - Seq Scan on part_document_fiction part_document - Filter: ((cid < 55) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle)) - InitPlan 1 - -> Index Scan using uaccount_pkey on uaccount - Index Cond: (pguser = CURRENT_USER) -(5 rows) - --- pp1 ERROR -INSERT INTO part_document VALUES (100, 11, 5, 'regress_rls_dave', 'testing pp1'); -- fail -ERROR: new row violates row-level security policy for table "part_document" --- pp1r ERROR -INSERT INTO part_document VALUES (100, 99, 1, 'regress_rls_dave', 'testing pp1r'); -- fail -ERROR: new row violates row-level security policy "pp1r" for table "part_document" --- Show that RLS policy does not apply for direct inserts to children --- This should fail with RLS POLICY pp1r violation. -INSERT INTO part_document VALUES (100, 55, 1, 'regress_rls_dave', 'testing RLS with partitions'); -- fail -ERROR: new row violates row-level security policy "pp1r" for table "part_document" --- But this should succeed. -INSERT INTO part_document_satire VALUES (100, 55, 1, 'regress_rls_dave', 'testing RLS with partitions'); -- success --- We still cannot see the row using the parent -SELECT * FROM part_document WHERE f_leak(dtitle) ORDER BY did; -NOTICE: f_leak => my first novel -NOTICE: f_leak => my second novel -NOTICE: f_leak => great science fiction -NOTICE: f_leak => awesome science fiction - did | cid | dlevel | dauthor | dtitle ------+-----+--------+-------------------+------------------------- - 1 | 11 | 1 | regress_rls_bob | my first novel - 2 | 11 | 2 | regress_rls_bob | my second novel - 6 | 11 | 1 | regress_rls_carol | great science fiction - 9 | 11 | 1 | regress_rls_dave | awesome science fiction -(4 rows) - --- But we can if we look directly -SELECT * FROM part_document_satire WHERE f_leak(dtitle) ORDER BY did; -NOTICE: f_leak => my first satire -NOTICE: f_leak => great satire -NOTICE: f_leak => testing RLS with partitions - did | cid | dlevel | dauthor | dtitle ------+-----+--------+-------------------+----------------------------- - 4 | 55 | 1 | regress_rls_bob | my first satire - 8 | 55 | 2 | regress_rls_carol | great satire - 100 | 55 | 1 | regress_rls_dave | testing RLS with partitions -(3 rows) - --- Turn on RLS and create policy on child to show RLS is checked before constraints -SET SESSION AUTHORIZATION regress_rls_alice; -ALTER TABLE part_document_satire ENABLE ROW LEVEL SECURITY; -CREATE POLICY pp3 ON part_document_satire AS RESTRICTIVE - USING (cid < 55); --- This should fail with RLS violation now. -SET SESSION AUTHORIZATION regress_rls_dave; -INSERT INTO part_document_satire VALUES (101, 55, 1, 'regress_rls_dave', 'testing RLS with partitions'); -- fail -ERROR: new row violates row-level security policy for table "part_document_satire" --- And now we cannot see directly into the partition either, due to RLS -SELECT * FROM part_document_satire WHERE f_leak(dtitle) ORDER BY did; - did | cid | dlevel | dauthor | dtitle ------+-----+--------+---------+-------- -(0 rows) - --- The parent looks same as before --- viewpoint from regress_rls_dave -SELECT * FROM part_document WHERE f_leak(dtitle) ORDER BY did; -NOTICE: f_leak => my first novel -NOTICE: f_leak => my second novel -NOTICE: f_leak => great science fiction -NOTICE: f_leak => awesome science fiction - did | cid | dlevel | dauthor | dtitle ------+-----+--------+-------------------+------------------------- - 1 | 11 | 1 | regress_rls_bob | my first novel - 2 | 11 | 2 | regress_rls_bob | my second novel - 6 | 11 | 1 | regress_rls_carol | great science fiction - 9 | 11 | 1 | regress_rls_dave | awesome science fiction -(4 rows) - -EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle); - QUERY PLAN ------------------------------------------------------------------------------ - Seq Scan on part_document_fiction part_document - Filter: ((cid < 55) AND (dlevel <= (InitPlan 1).col1) AND f_leak(dtitle)) - InitPlan 1 - -> Index Scan using uaccount_pkey on uaccount - Index Cond: (pguser = CURRENT_USER) -(5 rows) - --- viewpoint from regress_rls_carol -SET SESSION AUTHORIZATION regress_rls_carol; -SELECT * FROM part_document WHERE f_leak(dtitle) ORDER BY did; -NOTICE: f_leak => my first novel -NOTICE: f_leak => my second novel -NOTICE: f_leak => great science fiction -NOTICE: f_leak => awesome science fiction -NOTICE: f_leak => my first satire -NOTICE: f_leak => great satire -NOTICE: f_leak => testing RLS with partitions -NOTICE: f_leak => my science textbook -NOTICE: f_leak => my history book -NOTICE: f_leak => great technology book -NOTICE: f_leak => awesome technology book - did | cid | dlevel | dauthor | dtitle ------+-----+--------+-------------------+----------------------------- - 1 | 11 | 1 | regress_rls_bob | my first novel - 2 | 11 | 2 | regress_rls_bob | my second novel - 3 | 99 | 2 | regress_rls_bob | my science textbook - 4 | 55 | 1 | regress_rls_bob | my first satire - 5 | 99 | 2 | regress_rls_bob | my history book - 6 | 11 | 1 | regress_rls_carol | great science fiction - 7 | 99 | 2 | regress_rls_carol | great technology book - 8 | 55 | 2 | regress_rls_carol | great satire - 9 | 11 | 1 | regress_rls_dave | awesome science fiction - 10 | 99 | 2 | regress_rls_dave | awesome technology book - 100 | 55 | 1 | regress_rls_dave | testing RLS with partitions -(11 rows) - -EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle); - QUERY PLAN --------------------------------------------------------------------- - Append - InitPlan 1 - -> Index Scan using uaccount_pkey on uaccount - Index Cond: (pguser = CURRENT_USER) - -> Seq Scan on part_document_fiction part_document_1 - Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle)) - -> Seq Scan on part_document_satire part_document_2 - Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle)) - -> Seq Scan on part_document_nonfiction part_document_3 - Filter: ((dlevel <= (InitPlan 1).col1) AND f_leak(dtitle)) -(10 rows) - --- only owner can change policies -ALTER POLICY pp1 ON part_document USING (true); --fail -ERROR: must be owner of table part_document -DROP POLICY pp1 ON part_document; --fail -ERROR: must be owner of relation part_document -SET SESSION AUTHORIZATION regress_rls_alice; -ALTER POLICY pp1 ON part_document USING (dauthor = current_user); --- viewpoint from regress_rls_bob again -SET SESSION AUTHORIZATION regress_rls_bob; -SELECT * FROM part_document WHERE f_leak(dtitle) ORDER BY did; -NOTICE: f_leak => my first novel -NOTICE: f_leak => my second novel -NOTICE: f_leak => my first satire -NOTICE: f_leak => my science textbook -NOTICE: f_leak => my history book - did | cid | dlevel | dauthor | dtitle ------+-----+--------+-----------------+--------------------- - 1 | 11 | 1 | regress_rls_bob | my first novel - 2 | 11 | 2 | regress_rls_bob | my second novel - 3 | 99 | 2 | regress_rls_bob | my science textbook - 4 | 55 | 1 | regress_rls_bob | my first satire - 5 | 99 | 2 | regress_rls_bob | my history book -(5 rows) - --- viewpoint from rls_regres_carol again -SET SESSION AUTHORIZATION regress_rls_carol; -SELECT * FROM part_document WHERE f_leak(dtitle) ORDER BY did; -NOTICE: f_leak => great science fiction -NOTICE: f_leak => great satire -NOTICE: f_leak => great technology book - did | cid | dlevel | dauthor | dtitle ------+-----+--------+-------------------+----------------------- - 6 | 11 | 1 | regress_rls_carol | great science fiction - 7 | 99 | 2 | regress_rls_carol | great technology book - 8 | 55 | 2 | regress_rls_carol | great satire -(3 rows) - -EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle); - QUERY PLAN ---------------------------------------------------------------- - Append - -> Seq Scan on part_document_fiction part_document_1 - Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle)) - -> Seq Scan on part_document_satire part_document_2 - Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle)) - -> Seq Scan on part_document_nonfiction part_document_3 - Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle)) -(7 rows) - --- database superuser does bypass RLS policy when enabled -RESET SESSION AUTHORIZATION; -SET row_security TO ON; -SELECT * FROM part_document ORDER BY did; - did | cid | dlevel | dauthor | dtitle ------+-----+--------+-------------------+----------------------------- - 1 | 11 | 1 | regress_rls_bob | my first novel - 2 | 11 | 2 | regress_rls_bob | my second novel - 3 | 99 | 2 | regress_rls_bob | my science textbook - 4 | 55 | 1 | regress_rls_bob | my first satire - 5 | 99 | 2 | regress_rls_bob | my history book - 6 | 11 | 1 | regress_rls_carol | great science fiction - 7 | 99 | 2 | regress_rls_carol | great technology book - 8 | 55 | 2 | regress_rls_carol | great satire - 9 | 11 | 1 | regress_rls_dave | awesome science fiction - 10 | 99 | 2 | regress_rls_dave | awesome technology book - 100 | 55 | 1 | regress_rls_dave | testing RLS with partitions -(11 rows) - -SELECT * FROM part_document_satire ORDER by did; - did | cid | dlevel | dauthor | dtitle ------+-----+--------+-------------------+----------------------------- - 4 | 55 | 1 | regress_rls_bob | my first satire - 8 | 55 | 2 | regress_rls_carol | great satire - 100 | 55 | 1 | regress_rls_dave | testing RLS with partitions -(3 rows) - --- database non-superuser with bypass privilege can bypass RLS policy when disabled -SET SESSION AUTHORIZATION regress_rls_exempt_user; -SET row_security TO OFF; -SELECT * FROM part_document ORDER BY did; - did | cid | dlevel | dauthor | dtitle ------+-----+--------+-------------------+----------------------------- - 1 | 11 | 1 | regress_rls_bob | my first novel - 2 | 11 | 2 | regress_rls_bob | my second novel - 3 | 99 | 2 | regress_rls_bob | my science textbook - 4 | 55 | 1 | regress_rls_bob | my first satire - 5 | 99 | 2 | regress_rls_bob | my history book - 6 | 11 | 1 | regress_rls_carol | great science fiction - 7 | 99 | 2 | regress_rls_carol | great technology book - 8 | 55 | 2 | regress_rls_carol | great satire - 9 | 11 | 1 | regress_rls_dave | awesome science fiction - 10 | 99 | 2 | regress_rls_dave | awesome technology book - 100 | 55 | 1 | regress_rls_dave | testing RLS with partitions -(11 rows) - -SELECT * FROM part_document_satire ORDER by did; - did | cid | dlevel | dauthor | dtitle ------+-----+--------+-------------------+----------------------------- - 4 | 55 | 1 | regress_rls_bob | my first satire - 8 | 55 | 2 | regress_rls_carol | great satire - 100 | 55 | 1 | regress_rls_dave | testing RLS with partitions -(3 rows) - --- RLS policy does not apply to table owner when RLS enabled. -SET SESSION AUTHORIZATION regress_rls_alice; -SET row_security TO ON; -SELECT * FROM part_document ORDER by did; - did | cid | dlevel | dauthor | dtitle ------+-----+--------+-------------------+----------------------------- - 1 | 11 | 1 | regress_rls_bob | my first novel - 2 | 11 | 2 | regress_rls_bob | my second novel - 3 | 99 | 2 | regress_rls_bob | my science textbook - 4 | 55 | 1 | regress_rls_bob | my first satire - 5 | 99 | 2 | regress_rls_bob | my history book - 6 | 11 | 1 | regress_rls_carol | great science fiction - 7 | 99 | 2 | regress_rls_carol | great technology book - 8 | 55 | 2 | regress_rls_carol | great satire - 9 | 11 | 1 | regress_rls_dave | awesome science fiction - 10 | 99 | 2 | regress_rls_dave | awesome technology book - 100 | 55 | 1 | regress_rls_dave | testing RLS with partitions -(11 rows) - -SELECT * FROM part_document_satire ORDER by did; - did | cid | dlevel | dauthor | dtitle ------+-----+--------+-------------------+----------------------------- - 4 | 55 | 1 | regress_rls_bob | my first satire - 8 | 55 | 2 | regress_rls_carol | great satire - 100 | 55 | 1 | regress_rls_dave | testing RLS with partitions -(3 rows) - --- When RLS disabled, other users get ERROR. -SET SESSION AUTHORIZATION regress_rls_dave; -SET row_security TO OFF; -SELECT * FROM part_document ORDER by did; -ERROR: query would be affected by row-level security policy for table "part_document" -SELECT * FROM part_document_satire ORDER by did; -ERROR: query would be affected by row-level security policy for table "part_document_satire" --- Check behavior with a policy that uses a SubPlan not an InitPlan. -SET SESSION AUTHORIZATION regress_rls_alice; -SET row_security TO ON; -CREATE POLICY pp3 ON part_document AS RESTRICTIVE - USING ((SELECT dlevel <= seclv FROM uaccount WHERE pguser = current_user)); -SET SESSION AUTHORIZATION regress_rls_carol; -INSERT INTO part_document VALUES (100, 11, 5, 'regress_rls_carol', 'testing pp3'); -- fail -ERROR: new row violates row-level security policy "pp3" for table "part_document" ------ Dependencies ----- -SET SESSION AUTHORIZATION regress_rls_alice; -SET row_security TO ON; -CREATE TABLE dependee (x integer, y integer); -CREATE TABLE dependent (x integer, y integer); -CREATE POLICY d1 ON dependent FOR ALL - TO PUBLIC - USING (x = (SELECT d.x FROM dependee d WHERE d.y = y)); -DROP TABLE dependee; -- Should fail without CASCADE due to dependency on row security qual? -ERROR: cannot drop table dependee because other objects depend on it -DETAIL: policy d1 on table dependent depends on table dependee -HINT: Use DROP ... CASCADE to drop the dependent objects too. -DROP TABLE dependee CASCADE; -NOTICE: drop cascades to policy d1 on table dependent -EXPLAIN (COSTS OFF) SELECT * FROM dependent; -- After drop, should be unqualified - QUERY PLAN ------------------------ - Seq Scan on dependent -(1 row) - ------ RECURSION ---- --- --- Simple recursion --- -SET SESSION AUTHORIZATION regress_rls_alice; -CREATE TABLE rec1 (x integer, y integer); -CREATE POLICY r1 ON rec1 USING (x = (SELECT r.x FROM rec1 r WHERE y = r.y)); -ALTER TABLE rec1 ENABLE ROW LEVEL SECURITY; -SET SESSION AUTHORIZATION regress_rls_bob; -SELECT * FROM rec1; -- fail, direct recursion -ERROR: infinite recursion detected in policy for relation "rec1" --- --- Mutual recursion --- -SET SESSION AUTHORIZATION regress_rls_alice; -CREATE TABLE rec2 (a integer, b integer); -ALTER POLICY r1 ON rec1 USING (x = (SELECT a FROM rec2 WHERE b = y)); -CREATE POLICY r2 ON rec2 USING (a = (SELECT x FROM rec1 WHERE y = b)); -ALTER TABLE rec2 ENABLE ROW LEVEL SECURITY; -SET SESSION AUTHORIZATION regress_rls_bob; -SELECT * FROM rec1; -- fail, mutual recursion -ERROR: infinite recursion detected in policy for relation "rec1" --- --- Mutual recursion via views --- -SET SESSION AUTHORIZATION regress_rls_bob; -CREATE VIEW rec1v AS SELECT * FROM rec1; -CREATE VIEW rec2v AS SELECT * FROM rec2; -SET SESSION AUTHORIZATION regress_rls_alice; -ALTER POLICY r1 ON rec1 USING (x = (SELECT a FROM rec2v WHERE b = y)); -ALTER POLICY r2 ON rec2 USING (a = (SELECT x FROM rec1v WHERE y = b)); -SET SESSION AUTHORIZATION regress_rls_bob; -SELECT * FROM rec1; -- fail, mutual recursion via views -ERROR: infinite recursion detected in policy for relation "rec1" --- --- Mutual recursion via .s.b views --- -SET SESSION AUTHORIZATION regress_rls_bob; -DROP VIEW rec1v, rec2v CASCADE; -NOTICE: drop cascades to 2 other objects -DETAIL: drop cascades to policy r1 on table rec1 -drop cascades to policy r2 on table rec2 -CREATE VIEW rec1v WITH (security_barrier) AS SELECT * FROM rec1; -CREATE VIEW rec2v WITH (security_barrier) AS SELECT * FROM rec2; -SET SESSION AUTHORIZATION regress_rls_alice; -CREATE POLICY r1 ON rec1 USING (x = (SELECT a FROM rec2v WHERE b = y)); -CREATE POLICY r2 ON rec2 USING (a = (SELECT x FROM rec1v WHERE y = b)); -SET SESSION AUTHORIZATION regress_rls_bob; -SELECT * FROM rec1; -- fail, mutual recursion via s.b. views -ERROR: infinite recursion detected in policy for relation "rec1" --- --- recursive RLS and VIEWs in policy --- -SET SESSION AUTHORIZATION regress_rls_alice; -CREATE TABLE s1 (a int, b text); -INSERT INTO s1 (SELECT x, public.fipshash(x::text) FROM generate_series(-10,10) x); -CREATE TABLE s2 (x int, y text); -INSERT INTO s2 (SELECT x, public.fipshash(x::text) FROM generate_series(-6,6) x); -GRANT SELECT ON s1, s2 TO regress_rls_bob; -CREATE POLICY p1 ON s1 USING (a in (select x from s2 where y like '%2f%')); -CREATE POLICY p2 ON s2 USING (x in (select a from s1 where b like '%22%')); -CREATE POLICY p3 ON s1 FOR INSERT WITH CHECK (a = (SELECT a FROM s1)); -ALTER TABLE s1 ENABLE ROW LEVEL SECURITY; -ALTER TABLE s2 ENABLE ROW LEVEL SECURITY; -SET SESSION AUTHORIZATION regress_rls_bob; -CREATE VIEW v2 AS SELECT * FROM s2 WHERE y like '%af%'; -SELECT * FROM s1 WHERE f_leak(b); -- fail (infinite recursion) -ERROR: infinite recursion detected in policy for relation "s1" -INSERT INTO s1 VALUES (1, 'foo'); -- fail (infinite recursion) -ERROR: infinite recursion detected in policy for relation "s1" -SET SESSION AUTHORIZATION regress_rls_alice; -DROP POLICY p3 on s1; -ALTER POLICY p2 ON s2 USING (x % 2 = 0); -SET SESSION AUTHORIZATION regress_rls_bob; -SELECT * FROM s1 WHERE f_leak(b); -- OK -NOTICE: f_leak => 03b26944890929ff751653acb2f2af79 - a | b -----+---------------------------------- - -6 | 03b26944890929ff751653acb2f2af79 -(1 row) - -EXPLAIN (COSTS OFF) SELECT * FROM only s1 WHERE f_leak(b); - QUERY PLAN ---------------------------------------------------------------- - Seq Scan on s1 - Filter: ((ANY (a = (hashed SubPlan 1).col1)) AND f_leak(b)) - SubPlan 1 - -> Seq Scan on s2 - Filter: (((x % 2) = 0) AND (y ~~ '%2f%'::text)) -(5 rows) - -SET SESSION AUTHORIZATION regress_rls_alice; -ALTER POLICY p1 ON s1 USING (a in (select x from v2)); -- using VIEW in RLS policy -SET SESSION AUTHORIZATION regress_rls_bob; -SELECT * FROM s1 WHERE f_leak(b); -- OK -NOTICE: f_leak => 03b26944890929ff751653acb2f2af79 - a | b -----+---------------------------------- - -6 | 03b26944890929ff751653acb2f2af79 -(1 row) - -EXPLAIN (COSTS OFF) SELECT * FROM s1 WHERE f_leak(b); - QUERY PLAN ---------------------------------------------------------------- - Seq Scan on s1 - Filter: ((ANY (a = (hashed SubPlan 1).col1)) AND f_leak(b)) - SubPlan 1 - -> Seq Scan on s2 - Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text)) -(5 rows) - -SELECT (SELECT x FROM s1 LIMIT 1) xx, * FROM s2 WHERE y like '%28%'; - xx | x | y -----+----+---------------------------------- - -4 | -4 | e5e0093f285a4fb94c3fcc2ad7fd04ed -(1 row) - -EXPLAIN (COSTS OFF) SELECT (SELECT x FROM s1 LIMIT 1) xx, * FROM s2 WHERE y like '%28%'; - QUERY PLAN -------------------------------------------------------------------------- - Seq Scan on s2 - Filter: (((x % 2) = 0) AND (y ~~ '%28%'::text)) - SubPlan 2 - -> Limit - -> Seq Scan on s1 - Filter: (ANY (a = (hashed SubPlan 1).col1)) - SubPlan 1 - -> Seq Scan on s2 s2_1 - Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text)) -(9 rows) - -SET SESSION AUTHORIZATION regress_rls_alice; -ALTER POLICY p2 ON s2 USING (x in (select a from s1 where b like '%d2%')); -SET SESSION AUTHORIZATION regress_rls_bob; -SELECT * FROM s1 WHERE f_leak(b); -- fail (infinite recursion via view) -ERROR: infinite recursion detected in policy for relation "s1" --- prepared statement with regress_rls_alice privilege -PREPARE p1(int) AS SELECT * FROM t1 WHERE a <= $1; -EXECUTE p1(2); - id | a | b ------+---+----- - 102 | 2 | bbb - 202 | 2 | bcd - 302 | 2 | yyy -(3 rows) - -EXPLAIN (COSTS OFF) EXECUTE p1(2); - QUERY PLAN ----------------------------------------------- - Append - -> Seq Scan on t1 t1_1 - Filter: ((a <= 2) AND ((a % 2) = 0)) - -> Seq Scan on t2 t1_2 - Filter: ((a <= 2) AND ((a % 2) = 0)) - -> Seq Scan on t3 t1_3 - Filter: ((a <= 2) AND ((a % 2) = 0)) -(7 rows) - --- superuser is allowed to bypass RLS checks -RESET SESSION AUTHORIZATION; -SET row_security TO OFF; -SELECT * FROM t1 WHERE f_leak(b); -NOTICE: f_leak => aba -NOTICE: f_leak => bbb -NOTICE: f_leak => ccc -NOTICE: f_leak => dad -NOTICE: f_leak => abc -NOTICE: f_leak => bcd -NOTICE: f_leak => cde -NOTICE: f_leak => def -NOTICE: f_leak => xxx -NOTICE: f_leak => yyy -NOTICE: f_leak => zzz - id | a | b ------+---+----- - 101 | 1 | aba - 102 | 2 | bbb - 103 | 3 | ccc - 104 | 4 | dad - 201 | 1 | abc - 202 | 2 | bcd - 203 | 3 | cde - 204 | 4 | def - 301 | 1 | xxx - 302 | 2 | yyy - 303 | 3 | zzz -(11 rows) - -EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b); - QUERY PLAN ---------------------------- - Append - -> Seq Scan on t1 t1_1 - Filter: f_leak(b) - -> Seq Scan on t2 t1_2 - Filter: f_leak(b) - -> Seq Scan on t3 t1_3 - Filter: f_leak(b) -(7 rows) - --- plan cache should be invalidated -EXECUTE p1(2); - id | a | b ------+---+----- - 101 | 1 | aba - 102 | 2 | bbb - 201 | 1 | abc - 202 | 2 | bcd - 301 | 1 | xxx - 302 | 2 | yyy -(6 rows) - -EXPLAIN (COSTS OFF) EXECUTE p1(2); - QUERY PLAN ---------------------------- - Append - -> Seq Scan on t1 t1_1 - Filter: (a <= 2) - -> Seq Scan on t2 t1_2 - Filter: (a <= 2) - -> Seq Scan on t3 t1_3 - Filter: (a <= 2) -(7 rows) - -PREPARE p2(int) AS SELECT * FROM t1 WHERE a = $1; -EXECUTE p2(2); - id | a | b ------+---+----- - 102 | 2 | bbb - 202 | 2 | bcd - 302 | 2 | yyy -(3 rows) - -EXPLAIN (COSTS OFF) EXECUTE p2(2); - QUERY PLAN ---------------------------- - Append - -> Seq Scan on t1 t1_1 - Filter: (a = 2) - -> Seq Scan on t2 t1_2 - Filter: (a = 2) - -> Seq Scan on t3 t1_3 - Filter: (a = 2) -(7 rows) - --- also, case when privilege switch from superuser -SET SESSION AUTHORIZATION regress_rls_bob; -SET row_security TO ON; -EXECUTE p2(2); - id | a | b ------+---+----- - 102 | 2 | bbb - 202 | 2 | bcd - 302 | 2 | yyy -(3 rows) - -EXPLAIN (COSTS OFF) EXECUTE p2(2); - QUERY PLAN ---------------------------------------------- - Append - -> Seq Scan on t1 t1_1 - Filter: ((a = 2) AND ((a % 2) = 0)) - -> Seq Scan on t2 t1_2 - Filter: ((a = 2) AND ((a % 2) = 0)) - -> Seq Scan on t3 t1_3 - Filter: ((a = 2) AND ((a % 2) = 0)) -(7 rows) - --- --- UPDATE / DELETE and Row-level security --- -SET SESSION AUTHORIZATION regress_rls_bob; -EXPLAIN (COSTS OFF) UPDATE t1 SET b = b || b WHERE f_leak(b); - QUERY PLAN ------------------------------------------------------------ - Update on t1 - Update on t1 t1_1 - Update on t2 t1_2 - Update on t3 t1_3 - -> Result - -> Append - -> Seq Scan on t1 t1_1 - Filter: (((a % 2) = 0) AND f_leak(b)) - -> Seq Scan on t2 t1_2 - Filter: (((a % 2) = 0) AND f_leak(b)) - -> Seq Scan on t3 t1_3 - Filter: (((a % 2) = 0) AND f_leak(b)) -(12 rows) - -UPDATE t1 SET b = b || b WHERE f_leak(b); -NOTICE: f_leak => bbb -NOTICE: f_leak => dad -NOTICE: f_leak => bcd -NOTICE: f_leak => def -NOTICE: f_leak => yyy -EXPLAIN (COSTS OFF) UPDATE only t1 SET b = b || '_updt' WHERE f_leak(b); - QUERY PLAN ------------------------------------------------ - Update on t1 - -> Seq Scan on t1 - Filter: (((a % 2) = 0) AND f_leak(b)) -(3 rows) - -UPDATE only t1 SET b = b || '_updt' WHERE f_leak(b); -NOTICE: f_leak => bbbbbb -NOTICE: f_leak => daddad --- returning clause with system column -UPDATE only t1 SET b = b WHERE f_leak(b) RETURNING tableoid::regclass, *, t1; -NOTICE: f_leak => bbbbbb_updt -NOTICE: f_leak => daddad_updt - tableoid | id | a | b | t1 -----------+-----+---+-------------+--------------------- - t1 | 102 | 2 | bbbbbb_updt | (102,2,bbbbbb_updt) - t1 | 104 | 4 | daddad_updt | (104,4,daddad_updt) -(2 rows) - -UPDATE t1 SET b = b WHERE f_leak(b) RETURNING *; -NOTICE: f_leak => bbbbbb_updt -NOTICE: f_leak => daddad_updt -NOTICE: f_leak => bcdbcd -NOTICE: f_leak => defdef -NOTICE: f_leak => yyyyyy - id | a | b ------+---+------------- - 102 | 2 | bbbbbb_updt - 104 | 4 | daddad_updt - 202 | 2 | bcdbcd - 204 | 4 | defdef - 302 | 2 | yyyyyy -(5 rows) - -UPDATE t1 SET b = b WHERE f_leak(b) RETURNING tableoid::regclass, *, t1; -NOTICE: f_leak => bbbbbb_updt -NOTICE: f_leak => daddad_updt -NOTICE: f_leak => bcdbcd -NOTICE: f_leak => defdef -NOTICE: f_leak => yyyyyy - tableoid | id | a | b | t1 -----------+-----+---+-------------+--------------------- - t1 | 102 | 2 | bbbbbb_updt | (102,2,bbbbbb_updt) - t1 | 104 | 4 | daddad_updt | (104,4,daddad_updt) - t2 | 202 | 2 | bcdbcd | (202,2,bcdbcd) - t2 | 204 | 4 | defdef | (204,4,defdef) - t3 | 302 | 2 | yyyyyy | (302,2,yyyyyy) -(5 rows) - --- updates with from clause -EXPLAIN (COSTS OFF) UPDATE t2 SET b=t2.b FROM t3 -WHERE t2.a = 3 and t3.a = 2 AND f_leak(t2.b) AND f_leak(t3.b); - QUERY PLAN ------------------------------------------------------------------ - Update on t2 - -> Nested Loop - -> Seq Scan on t2 - Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b)) - -> Seq Scan on t3 - Filter: ((a = 2) AND f_leak(b)) -(6 rows) - -UPDATE t2 SET b=t2.b FROM t3 -WHERE t2.a = 3 and t3.a = 2 AND f_leak(t2.b) AND f_leak(t3.b); -NOTICE: f_leak => cde -NOTICE: f_leak => yyyyyy -EXPLAIN (COSTS OFF) UPDATE t1 SET b=t1.b FROM t2 -WHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b); - QUERY PLAN ------------------------------------------------------------------------ - Update on t1 - Update on t1 t1_1 - Update on t2 t1_2 - Update on t3 t1_3 - -> Nested Loop - -> Seq Scan on t2 - Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b)) - -> Append - -> Seq Scan on t1 t1_1 - Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b)) - -> Seq Scan on t2 t1_2 - Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b)) - -> Seq Scan on t3 t1_3 - Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b)) -(14 rows) - -UPDATE t1 SET b=t1.b FROM t2 -WHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b); -NOTICE: f_leak => cde -EXPLAIN (COSTS OFF) UPDATE t2 SET b=t2.b FROM t1 -WHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b); - QUERY PLAN ------------------------------------------------------------------------ - Update on t2 - -> Nested Loop - -> Seq Scan on t2 - Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b)) - -> Append - -> Seq Scan on t1 t1_1 - Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b)) - -> Seq Scan on t2 t1_2 - Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b)) - -> Seq Scan on t3 t1_3 - Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b)) -(11 rows) - -UPDATE t2 SET b=t2.b FROM t1 -WHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b); -NOTICE: f_leak => cde --- updates with from clause self join -EXPLAIN (COSTS OFF) UPDATE t2 t2_1 SET b = t2_2.b FROM t2 t2_2 -WHERE t2_1.a = 3 AND t2_2.a = t2_1.a AND t2_2.b = t2_1.b -AND f_leak(t2_1.b) AND f_leak(t2_2.b) RETURNING *, t2_1, t2_2; - QUERY PLAN ------------------------------------------------------------------ - Update on t2 t2_1 - -> Nested Loop - Join Filter: (t2_1.b = t2_2.b) - -> Seq Scan on t2 t2_1 - Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b)) - -> Seq Scan on t2 t2_2 - Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b)) -(7 rows) - -UPDATE t2 t2_1 SET b = t2_2.b FROM t2 t2_2 -WHERE t2_1.a = 3 AND t2_2.a = t2_1.a AND t2_2.b = t2_1.b -AND f_leak(t2_1.b) AND f_leak(t2_2.b) RETURNING *, t2_1, t2_2; -NOTICE: f_leak => cde -NOTICE: f_leak => cde - id | a | b | c | id | a | b | c | t2_1 | t2_2 ------+---+-----+-----+-----+---+-----+-----+-----------------+----------------- - 203 | 3 | cde | 3.3 | 203 | 3 | cde | 3.3 | (203,3,cde,3.3) | (203,3,cde,3.3) -(1 row) - -EXPLAIN (COSTS OFF) UPDATE t1 t1_1 SET b = t1_2.b FROM t1 t1_2 -WHERE t1_1.a = 4 AND t1_2.a = t1_1.a AND t1_2.b = t1_1.b -AND f_leak(t1_1.b) AND f_leak(t1_2.b) RETURNING *, t1_1, t1_2; - QUERY PLAN ------------------------------------------------------------------------------ - Update on t1 t1_1 - Update on t1 t1_1_1 - Update on t2 t1_1_2 - Update on t3 t1_1_3 - -> Nested Loop - Join Filter: (t1_1.b = t1_2.b) - -> Append - -> Seq Scan on t1 t1_1_1 - Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) - -> Seq Scan on t2 t1_1_2 - Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) - -> Seq Scan on t3 t1_1_3 - Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) - -> Materialize - -> Append - -> Seq Scan on t1 t1_2_1 - Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) - -> Seq Scan on t2 t1_2_2 - Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) - -> Seq Scan on t3 t1_2_3 - Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b)) -(21 rows) - -UPDATE t1 t1_1 SET b = t1_2.b FROM t1 t1_2 -WHERE t1_1.a = 4 AND t1_2.a = t1_1.a AND t1_2.b = t1_1.b -AND f_leak(t1_1.b) AND f_leak(t1_2.b) RETURNING *, t1_1, t1_2; -NOTICE: f_leak => daddad_updt -NOTICE: f_leak => daddad_updt -NOTICE: f_leak => defdef -NOTICE: f_leak => defdef - id | a | b | id | a | b | t1_1 | t1_2 ------+---+-------------+-----+---+-------------+---------------------+--------------------- - 104 | 4 | daddad_updt | 104 | 4 | daddad_updt | (104,4,daddad_updt) | (104,4,daddad_updt) - 204 | 4 | defdef | 204 | 4 | defdef | (204,4,defdef) | (204,4,defdef) -(2 rows) - -RESET SESSION AUTHORIZATION; -SET row_security TO OFF; -SELECT * FROM t1 ORDER BY a,b; - id | a | b ------+---+------------- - 101 | 1 | aba - 201 | 1 | abc - 301 | 1 | xxx - 102 | 2 | bbbbbb_updt - 202 | 2 | bcdbcd - 302 | 2 | yyyyyy - 103 | 3 | ccc - 203 | 3 | cde - 303 | 3 | zzz - 104 | 4 | daddad_updt - 204 | 4 | defdef -(11 rows) - -SET SESSION AUTHORIZATION regress_rls_bob; -SET row_security TO ON; -EXPLAIN (COSTS OFF) DELETE FROM only t1 WHERE f_leak(b); - QUERY PLAN ------------------------------------------------ - Delete on t1 - -> Seq Scan on t1 - Filter: (((a % 2) = 0) AND f_leak(b)) -(3 rows) - -EXPLAIN (COSTS OFF) DELETE FROM t1 WHERE f_leak(b); - QUERY PLAN ------------------------------------------------------ - Delete on t1 - Delete on t1 t1_1 - Delete on t2 t1_2 - Delete on t3 t1_3 - -> Append - -> Seq Scan on t1 t1_1 - Filter: (((a % 2) = 0) AND f_leak(b)) - -> Seq Scan on t2 t1_2 - Filter: (((a % 2) = 0) AND f_leak(b)) - -> Seq Scan on t3 t1_3 - Filter: (((a % 2) = 0) AND f_leak(b)) -(11 rows) - -DELETE FROM only t1 WHERE f_leak(b) RETURNING tableoid::regclass, *, t1; -NOTICE: f_leak => bbbbbb_updt -NOTICE: f_leak => daddad_updt - tableoid | id | a | b | t1 -----------+-----+---+-------------+--------------------- - t1 | 102 | 2 | bbbbbb_updt | (102,2,bbbbbb_updt) - t1 | 104 | 4 | daddad_updt | (104,4,daddad_updt) -(2 rows) - -DELETE FROM t1 WHERE f_leak(b) RETURNING tableoid::regclass, *, t1; -NOTICE: f_leak => bcdbcd -NOTICE: f_leak => defdef -NOTICE: f_leak => yyyyyy - tableoid | id | a | b | t1 -----------+-----+---+--------+---------------- - t2 | 202 | 2 | bcdbcd | (202,2,bcdbcd) - t2 | 204 | 4 | defdef | (204,4,defdef) - t3 | 302 | 2 | yyyyyy | (302,2,yyyyyy) -(3 rows) - --- --- S.b. view on top of Row-level security --- -SET SESSION AUTHORIZATION regress_rls_alice; -CREATE TABLE b1 (a int, b text); -INSERT INTO b1 (SELECT x, public.fipshash(x::text) FROM generate_series(-10,10) x); -CREATE POLICY p1 ON b1 USING (a % 2 = 0); -ALTER TABLE b1 ENABLE ROW LEVEL SECURITY; -GRANT ALL ON b1 TO regress_rls_bob; -SET SESSION AUTHORIZATION regress_rls_bob; -CREATE VIEW bv1 WITH (security_barrier) AS SELECT * FROM b1 WHERE a > 0 WITH CHECK OPTION; -GRANT ALL ON bv1 TO regress_rls_carol; -SET SESSION AUTHORIZATION regress_rls_carol; -EXPLAIN (COSTS OFF) SELECT * FROM bv1 WHERE f_leak(b); - QUERY PLAN ---------------------------------------------- - Subquery Scan on bv1 - Filter: f_leak(bv1.b) - -> Seq Scan on b1 - Filter: ((a > 0) AND ((a % 2) = 0)) -(4 rows) - -SELECT * FROM bv1 WHERE f_leak(b); -NOTICE: f_leak => d4735e3a265e16eee03f59718b9b5d03 -NOTICE: f_leak => 4b227777d4dd1fc61c6f884f48641d02 -NOTICE: f_leak => e7f6c011776e8db7cd330b54174fd76f -NOTICE: f_leak => 2c624232cdd221771294dfbb310aca00 -NOTICE: f_leak => 4a44dc15364204a80fe80e9039455cc1 - a | b -----+---------------------------------- - 2 | d4735e3a265e16eee03f59718b9b5d03 - 4 | 4b227777d4dd1fc61c6f884f48641d02 - 6 | e7f6c011776e8db7cd330b54174fd76f - 8 | 2c624232cdd221771294dfbb310aca00 - 10 | 4a44dc15364204a80fe80e9039455cc1 -(5 rows) - -INSERT INTO bv1 VALUES (-1, 'xxx'); -- should fail view WCO -ERROR: new row violates row-level security policy for table "b1" -INSERT INTO bv1 VALUES (11, 'xxx'); -- should fail RLS check -ERROR: new row violates row-level security policy for table "b1" -INSERT INTO bv1 VALUES (12, 'xxx'); -- ok -EXPLAIN (COSTS OFF) UPDATE bv1 SET b = 'yyy' WHERE a = 4 AND f_leak(b); - QUERY PLAN ------------------------------------------------------------------------ - Update on b1 - -> Seq Scan on b1 - Filter: ((a > 0) AND (a = 4) AND ((a % 2) = 0) AND f_leak(b)) -(3 rows) - -UPDATE bv1 SET b = 'yyy' WHERE a = 4 AND f_leak(b); -NOTICE: f_leak => 4b227777d4dd1fc61c6f884f48641d02 -EXPLAIN (COSTS OFF) DELETE FROM bv1 WHERE a = 6 AND f_leak(b); - QUERY PLAN ------------------------------------------------------------------------ - Delete on b1 - -> Seq Scan on b1 - Filter: ((a > 0) AND (a = 6) AND ((a % 2) = 0) AND f_leak(b)) -(3 rows) - -DELETE FROM bv1 WHERE a = 6 AND f_leak(b); -NOTICE: f_leak => e7f6c011776e8db7cd330b54174fd76f -SET SESSION AUTHORIZATION regress_rls_alice; -SELECT * FROM b1; - a | b ------+---------------------------------- - -10 | c171d4ec282b23db89a99880cd624e9b - -9 | d5c534fde62beb89c745a59952c8efed - -8 | e91592205d3881e3ea35d66973bb4898 - -7 | a770d3270c9dcdedf12ed9fd70444f7c - -6 | 03b26944890929ff751653acb2f2af79 - -5 | 37aa1ccf80e481832b2db282d4d4f895 - -4 | e5e0093f285a4fb94c3fcc2ad7fd04ed - -3 | 615bdd17c2556f82f384392ea8557f8c - -2 | cf3bae39dd692048a8bf961182e6a34d - -1 | 1bad6b8cf97131fceab8543e81f77571 - 0 | 5feceb66ffc86f38d952786c6d696c79 - 1 | 6b86b273ff34fce19d6b804eff5a3f57 - 2 | d4735e3a265e16eee03f59718b9b5d03 - 3 | 4e07408562bedb8b60ce05c1decfe3ad - 5 | ef2d127de37b942baad06145e54b0c61 - 7 | 7902699be42c8a8e46fbbb4501726517 - 8 | 2c624232cdd221771294dfbb310aca00 - 9 | 19581e27de7ced00ff1ce50b2047e7a5 - 10 | 4a44dc15364204a80fe80e9039455cc1 - 12 | xxx - 4 | yyy -(21 rows) - --- --- INSERT ... ON CONFLICT DO UPDATE and Row-level security --- -SET SESSION AUTHORIZATION regress_rls_alice; -DROP POLICY p1 ON document; -DROP POLICY p1r ON document; -CREATE POLICY p1 ON document FOR SELECT USING (true); -CREATE POLICY p2 ON document FOR INSERT WITH CHECK (dauthor = current_user); -CREATE POLICY p3 ON document FOR UPDATE - USING (cid = (SELECT cid from category WHERE cname = 'novel')) - WITH CHECK (dauthor = current_user); -SET SESSION AUTHORIZATION regress_rls_bob; --- Exists... -SELECT * FROM document WHERE did = 2; - did | cid | dlevel | dauthor | dtitle ------+-----+--------+-----------------+----------------- - 2 | 11 | 2 | regress_rls_bob | my second novel -(1 row) - --- ...so violates actual WITH CHECK OPTION within UPDATE (not INSERT, since --- alternative UPDATE path happens to be taken): -INSERT INTO document VALUES (2, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_carol', 'my first novel') - ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle, dauthor = EXCLUDED.dauthor; -ERROR: new row violates row-level security policy for table "document" --- Violates USING qual for UPDATE policy p3. --- --- UPDATE path is taken, but UPDATE fails purely because *existing* row to be --- updated is not a "novel"/cid 11 (row is not leaked, even though we have --- SELECT privileges sufficient to see the row in this instance): -INSERT INTO document VALUES (33, 22, 1, 'regress_rls_bob', 'okay science fiction'); -- preparation for next statement -INSERT INTO document VALUES (33, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'Some novel, replaces sci-fi') -- takes UPDATE path - ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle; -ERROR: new row violates row-level security policy (USING expression) for table "document" --- Fine (we UPDATE, since INSERT WCOs and UPDATE security barrier quals + WCOs --- not violated): -INSERT INTO document VALUES (2, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'my first novel') - ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle RETURNING *; - did | cid | dlevel | dauthor | dtitle ------+-----+--------+-----------------+---------------- - 2 | 11 | 2 | regress_rls_bob | my first novel -(1 row) - --- Fine (we INSERT, so "cid = 33" ("technology") isn't evaluated): -INSERT INTO document VALUES (78, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'some technology novel') - ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle, cid = 33 RETURNING *; - did | cid | dlevel | dauthor | dtitle ------+-----+--------+-----------------+----------------------- - 78 | 11 | 1 | regress_rls_bob | some technology novel -(1 row) - --- Fine (same query, but we UPDATE, so "cid = 33", ("technology") is not the --- case in respect of *existing* tuple): -INSERT INTO document VALUES (78, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'some technology novel') - ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle, cid = 33 RETURNING *; - did | cid | dlevel | dauthor | dtitle ------+-----+--------+-----------------+----------------------- - 78 | 33 | 1 | regress_rls_bob | some technology novel -(1 row) - --- Same query a third time, but now fails due to existing tuple finally not --- passing quals: -INSERT INTO document VALUES (78, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'some technology novel') - ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle, cid = 33 RETURNING *; -ERROR: new row violates row-level security policy (USING expression) for table "document" --- Don't fail just because INSERT doesn't satisfy WITH CHECK option that --- originated as a barrier/USING() qual from the UPDATE. Note that the UPDATE --- path *isn't* taken, and so UPDATE-related policy does not apply: -INSERT INTO document VALUES (79, (SELECT cid from category WHERE cname = 'technology'), 1, 'regress_rls_bob', 'technology book, can only insert') - ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle RETURNING *; - did | cid | dlevel | dauthor | dtitle ------+-----+--------+-----------------+---------------------------------- - 79 | 33 | 1 | regress_rls_bob | technology book, can only insert -(1 row) - --- But this time, the same statement fails, because the UPDATE path is taken, --- and updating the row just inserted falls afoul of security barrier qual --- (enforced as WCO) -- what we might have updated target tuple to is --- irrelevant, in fact. -INSERT INTO document VALUES (79, (SELECT cid from category WHERE cname = 'technology'), 1, 'regress_rls_bob', 'technology book, can only insert') - ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle RETURNING *; -ERROR: new row violates row-level security policy (USING expression) for table "document" --- Test default USING qual enforced as WCO -SET SESSION AUTHORIZATION regress_rls_alice; -DROP POLICY p1 ON document; -DROP POLICY p2 ON document; -DROP POLICY p3 ON document; -CREATE POLICY p3_with_default ON document FOR UPDATE - USING (cid = (SELECT cid from category WHERE cname = 'novel')); -SET SESSION AUTHORIZATION regress_rls_bob; --- Just because WCO-style enforcement of USING quals occurs with --- existing/target tuple does not mean that the implementation can be allowed --- to fail to also enforce this qual against the final tuple appended to --- relation (since in the absence of an explicit WCO, this is also interpreted --- as an UPDATE/ALL WCO in general). --- --- UPDATE path is taken here (fails due to existing tuple). Note that this is --- not reported as a "USING expression", because it's an RLS UPDATE check that originated as --- a USING qual for the purposes of RLS in general, as opposed to an explicit --- USING qual that is ordinarily a security barrier. We leave it up to the --- UPDATE to make this fail: -INSERT INTO document VALUES (79, (SELECT cid from category WHERE cname = 'technology'), 1, 'regress_rls_bob', 'technology book, can only insert') - ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle RETURNING *; -ERROR: new row violates row-level security policy for table "document" --- UPDATE path is taken here. Existing tuple passes, since its cid --- corresponds to "novel", but default USING qual is enforced against --- post-UPDATE tuple too (as always when updating with a policy that lacks an --- explicit WCO), and so this fails: -INSERT INTO document VALUES (2, (SELECT cid from category WHERE cname = 'technology'), 1, 'regress_rls_bob', 'my first novel') - ON CONFLICT (did) DO UPDATE SET cid = EXCLUDED.cid, dtitle = EXCLUDED.dtitle RETURNING *; -ERROR: new row violates row-level security policy for table "document" -SET SESSION AUTHORIZATION regress_rls_alice; -DROP POLICY p3_with_default ON document; --- --- Test ALL policies with ON CONFLICT DO UPDATE (much the same as existing UPDATE --- tests) --- -CREATE POLICY p3_with_all ON document FOR ALL - USING (cid = (SELECT cid from category WHERE cname = 'novel')) - WITH CHECK (dauthor = current_user); -SET SESSION AUTHORIZATION regress_rls_bob; --- Fails, since ALL WCO is enforced in insert path: -INSERT INTO document VALUES (80, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_carol', 'my first novel') - ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle, cid = 33; -ERROR: new row violates row-level security policy for table "document" --- Fails, since ALL policy USING qual is enforced (existing, target tuple is in --- violation, since it has the "manga" cid): -INSERT INTO document VALUES (4, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'my first novel') - ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle; -ERROR: new row violates row-level security policy (USING expression) for table "document" --- Fails, since ALL WCO are enforced: -INSERT INTO document VALUES (1, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'my first novel') - ON CONFLICT (did) DO UPDATE SET dauthor = 'regress_rls_carol'; -ERROR: new row violates row-level security policy for table "document" --- --- MERGE --- -RESET SESSION AUTHORIZATION; -DROP POLICY p3_with_all ON document; -ALTER TABLE document ADD COLUMN dnotes text DEFAULT ''; --- all documents are readable -CREATE POLICY p1 ON document FOR SELECT USING (true); --- one may insert documents only authored by them -CREATE POLICY p2 ON document FOR INSERT WITH CHECK (dauthor = current_user); --- one may only update documents in 'novel' category and new dlevel must be > 0 -CREATE POLICY p3 ON document FOR UPDATE - USING (cid = (SELECT cid from category WHERE cname = 'novel')) - WITH CHECK (dlevel > 0); --- one may only delete documents in 'manga' category -CREATE POLICY p4 ON document FOR DELETE - USING (cid = (SELECT cid from category WHERE cname = 'manga')); -SELECT * FROM document; - did | cid | dlevel | dauthor | dtitle | dnotes ------+-----+--------+-------------------+----------------------------------+-------- - 1 | 11 | 1 | regress_rls_bob | my first novel | - 3 | 22 | 2 | regress_rls_bob | my science fiction | - 4 | 44 | 1 | regress_rls_bob | my first manga | - 5 | 44 | 2 | regress_rls_bob | my second manga | - 6 | 22 | 1 | regress_rls_carol | great science fiction | - 7 | 33 | 2 | regress_rls_carol | great technology book | - 8 | 44 | 1 | regress_rls_carol | great manga | - 9 | 22 | 1 | regress_rls_dave | awesome science fiction | - 10 | 33 | 2 | regress_rls_dave | awesome technology book | - 11 | 33 | 1 | regress_rls_carol | hoge | - 33 | 22 | 1 | regress_rls_bob | okay science fiction | - 2 | 11 | 2 | regress_rls_bob | my first novel | - 78 | 33 | 1 | regress_rls_bob | some technology novel | - 79 | 33 | 1 | regress_rls_bob | technology book, can only insert | -(14 rows) - -SET SESSION AUTHORIZATION regress_rls_bob; --- Fails, since update violates WITH CHECK qual on dlevel -MERGE INTO document d -USING (SELECT 1 as sdid) s -ON did = s.sdid -WHEN MATCHED THEN - UPDATE SET dnotes = dnotes || ' notes added by merge1 ', dlevel = 0; -ERROR: new row violates row-level security policy for table "document" --- Should be OK since USING and WITH CHECK quals pass -MERGE INTO document d -USING (SELECT 1 as sdid) s -ON did = s.sdid -WHEN MATCHED THEN - UPDATE SET dnotes = dnotes || ' notes added by merge2 '; --- Even when dlevel is updated explicitly, but to the existing value -MERGE INTO document d -USING (SELECT 1 as sdid) s -ON did = s.sdid -WHEN MATCHED THEN - UPDATE SET dnotes = dnotes || ' notes added by merge3 ', dlevel = 1; --- There is a MATCH for did = 3, but UPDATE's USING qual does not allow --- updating an item in category 'science fiction' -MERGE INTO document d -USING (SELECT 3 as sdid) s -ON did = s.sdid -WHEN MATCHED THEN - UPDATE SET dnotes = dnotes || ' notes added by merge '; -ERROR: target row violates row-level security policy (USING expression) for table "document" --- The same thing with DELETE action, but fails again because no permissions --- to delete items in 'science fiction' category that did 3 belongs to. -MERGE INTO document d -USING (SELECT 3 as sdid) s -ON did = s.sdid -WHEN MATCHED THEN - DELETE; -ERROR: target row violates row-level security policy (USING expression) for table "document" --- Document with did 4 belongs to 'manga' category which is allowed for --- deletion. But this fails because the UPDATE action is matched first and --- UPDATE policy does not allow updation in the category. -MERGE INTO document d -USING (SELECT 4 as sdid) s -ON did = s.sdid -WHEN MATCHED AND dnotes = '' THEN - UPDATE SET dnotes = dnotes || ' notes added by merge ' -WHEN MATCHED THEN - DELETE; -ERROR: target row violates row-level security policy (USING expression) for table "document" --- UPDATE action is not matched this time because of the WHEN qual. --- DELETE still fails because role regress_rls_bob does not have SELECT --- privileges on 'manga' category row in the category table. -MERGE INTO document d -USING (SELECT 4 as sdid) s -ON did = s.sdid -WHEN MATCHED AND dnotes <> '' THEN - UPDATE SET dnotes = dnotes || ' notes added by merge ' -WHEN MATCHED THEN - DELETE; -ERROR: target row violates row-level security policy (USING expression) for table "document" --- OK if DELETE is replaced with DO NOTHING -MERGE INTO document d -USING (SELECT 4 as sdid) s -ON did = s.sdid -WHEN MATCHED AND dnotes <> '' THEN - UPDATE SET dnotes = dnotes || ' notes added by merge ' -WHEN MATCHED THEN - DO NOTHING; -SELECT * FROM document WHERE did = 4; - did | cid | dlevel | dauthor | dtitle | dnotes ------+-----+--------+-----------------+----------------+-------- - 4 | 44 | 1 | regress_rls_bob | my first manga | -(1 row) - --- Switch to regress_rls_carol role and try the DELETE again. It should succeed --- this time -RESET SESSION AUTHORIZATION; -SET SESSION AUTHORIZATION regress_rls_carol; -MERGE INTO document d -USING (SELECT 4 as sdid) s -ON did = s.sdid -WHEN MATCHED AND dnotes <> '' THEN - UPDATE SET dnotes = dnotes || ' notes added by merge ' -WHEN MATCHED THEN - DELETE; --- Switch back to regress_rls_bob role -RESET SESSION AUTHORIZATION; -SET SESSION AUTHORIZATION regress_rls_bob; --- Try INSERT action. This fails because we are trying to insert --- dauthor = regress_rls_dave and INSERT's WITH CHECK does not allow --- that -MERGE INTO document d -USING (SELECT 12 as sdid) s -ON did = s.sdid -WHEN MATCHED THEN - DELETE -WHEN NOT MATCHED THEN - INSERT VALUES (12, 11, 1, 'regress_rls_dave', 'another novel'); -ERROR: new row violates row-level security policy for table "document" --- This should be fine -MERGE INTO document d -USING (SELECT 12 as sdid) s -ON did = s.sdid -WHEN MATCHED THEN - DELETE -WHEN NOT MATCHED THEN - INSERT VALUES (12, 11, 1, 'regress_rls_bob', 'another novel'); --- ok -MERGE INTO document d -USING (SELECT 1 as sdid) s -ON did = s.sdid -WHEN MATCHED THEN - UPDATE SET dnotes = dnotes || ' notes added by merge4 ' -WHEN NOT MATCHED THEN - INSERT VALUES (12, 11, 1, 'regress_rls_bob', 'another novel'); --- drop and create a new SELECT policy which prevents us from reading --- any document except with category 'novel' -RESET SESSION AUTHORIZATION; -DROP POLICY p1 ON document; -CREATE POLICY p1 ON document FOR SELECT - USING (cid = (SELECT cid from category WHERE cname = 'novel')); -SET SESSION AUTHORIZATION regress_rls_bob; --- MERGE can no longer see the matching row and hence attempts the --- NOT MATCHED action, which results in unique key violation -MERGE INTO document d -USING (SELECT 7 as sdid) s -ON did = s.sdid -WHEN MATCHED THEN - UPDATE SET dnotes = dnotes || ' notes added by merge5 ' -WHEN NOT MATCHED THEN - INSERT VALUES (12, 11, 1, 'regress_rls_bob', 'another novel'); -ERROR: duplicate key value violates unique constraint "document_pkey" --- UPDATE action fails if new row is not visible -MERGE INTO document d -USING (SELECT 1 as sdid) s -ON did = s.sdid -WHEN MATCHED THEN - UPDATE SET dnotes = dnotes || ' notes added by merge6 ', - cid = (SELECT cid from category WHERE cname = 'technology'); -ERROR: new row violates row-level security policy for table "document" --- but OK if new row is visible -MERGE INTO document d -USING (SELECT 1 as sdid) s -ON did = s.sdid -WHEN MATCHED THEN - UPDATE SET dnotes = dnotes || ' notes added by merge7 ', - cid = (SELECT cid from category WHERE cname = 'novel'); --- OK to insert a new row that is not visible -MERGE INTO document d -USING (SELECT 13 as sdid) s -ON did = s.sdid -WHEN MATCHED THEN - UPDATE SET dnotes = dnotes || ' notes added by merge8 ' -WHEN NOT MATCHED THEN - INSERT VALUES (13, 44, 1, 'regress_rls_bob', 'new manga'); -SELECT * FROM document WHERE did = 13; - did | cid | dlevel | dauthor | dtitle | dnotes ------+-----+--------+---------+--------+-------- -(0 rows) - --- but not OK if RETURNING is used -MERGE INTO document d -USING (SELECT 14 as sdid) s -ON did = s.sdid -WHEN MATCHED THEN - UPDATE SET dnotes = dnotes || ' notes added by merge9 ' -WHEN NOT MATCHED THEN - INSERT VALUES (14, 44, 1, 'regress_rls_bob', 'new manga') -RETURNING *; -ERROR: new row violates row-level security policy for table "document" --- but OK if new row is visible -MERGE INTO document d -USING (SELECT 14 as sdid) s -ON did = s.sdid -WHEN MATCHED THEN - UPDATE SET dnotes = dnotes || ' notes added by merge10 ' -WHEN NOT MATCHED THEN - INSERT VALUES (14, 11, 1, 'regress_rls_bob', 'new novel') -RETURNING *; - sdid | did | cid | dlevel | dauthor | dtitle | dnotes -------+-----+-----+--------+-----------------+-----------+-------- - 14 | 14 | 11 | 1 | regress_rls_bob | new novel | -(1 row) - -RESET SESSION AUTHORIZATION; --- drop the restrictive SELECT policy so that we can look at the --- final state of the table -DROP POLICY p1 ON document; --- Just check everything went per plan -SELECT * FROM document; - did | cid | dlevel | dauthor | dtitle | dnotes ------+-----+--------+-------------------+----------------------------------+---------------------------------------------------------------------------------------------- - 3 | 22 | 2 | regress_rls_bob | my science fiction | - 5 | 44 | 2 | regress_rls_bob | my second manga | - 6 | 22 | 1 | regress_rls_carol | great science fiction | - 7 | 33 | 2 | regress_rls_carol | great technology book | - 8 | 44 | 1 | regress_rls_carol | great manga | - 9 | 22 | 1 | regress_rls_dave | awesome science fiction | - 10 | 33 | 2 | regress_rls_dave | awesome technology book | - 11 | 33 | 1 | regress_rls_carol | hoge | - 33 | 22 | 1 | regress_rls_bob | okay science fiction | - 2 | 11 | 2 | regress_rls_bob | my first novel | - 78 | 33 | 1 | regress_rls_bob | some technology novel | - 79 | 33 | 1 | regress_rls_bob | technology book, can only insert | - 12 | 11 | 1 | regress_rls_bob | another novel | - 1 | 11 | 1 | regress_rls_bob | my first novel | notes added by merge2 notes added by merge3 notes added by merge4 notes added by merge7 - 13 | 44 | 1 | regress_rls_bob | new manga | - 14 | 11 | 1 | regress_rls_bob | new novel | -(16 rows) - --- --- ROLE/GROUP --- -SET SESSION AUTHORIZATION regress_rls_alice; -CREATE TABLE z1 (a int, b text); -CREATE TABLE z2 (a int, b text); -GRANT SELECT ON z1,z2 TO regress_rls_group1, regress_rls_group2, - regress_rls_bob, regress_rls_carol; -INSERT INTO z1 VALUES - (1, 'aba'), - (2, 'bbb'), - (3, 'ccc'), - (4, 'dad'); -CREATE POLICY p1 ON z1 TO regress_rls_group1 USING (a % 2 = 0); -CREATE POLICY p2 ON z1 TO regress_rls_group2 USING (a % 2 = 1); -ALTER TABLE z1 ENABLE ROW LEVEL SECURITY; -SET SESSION AUTHORIZATION regress_rls_bob; -SELECT * FROM z1 WHERE f_leak(b); -NOTICE: f_leak => bbb -NOTICE: f_leak => dad - a | b ----+----- - 2 | bbb - 4 | dad -(2 rows) - -EXPLAIN (COSTS OFF) SELECT * FROM z1 WHERE f_leak(b); - QUERY PLAN ------------------------------------------ - Seq Scan on z1 - Filter: (((a % 2) = 0) AND f_leak(b)) -(2 rows) - -PREPARE plancache_test AS SELECT * FROM z1 WHERE f_leak(b); -EXPLAIN (COSTS OFF) EXECUTE plancache_test; - QUERY PLAN ------------------------------------------ - Seq Scan on z1 - Filter: (((a % 2) = 0) AND f_leak(b)) -(2 rows) - -PREPARE plancache_test2 AS WITH q AS MATERIALIZED (SELECT * FROM z1 WHERE f_leak(b)) SELECT * FROM q,z2; -EXPLAIN (COSTS OFF) EXECUTE plancache_test2; - QUERY PLAN -------------------------------------------------- - Nested Loop - CTE q - -> Seq Scan on z1 - Filter: (((a % 2) = 0) AND f_leak(b)) - -> CTE Scan on q - -> Materialize - -> Seq Scan on z2 -(7 rows) - -PREPARE plancache_test3 AS WITH q AS MATERIALIZED (SELECT * FROM z2) SELECT * FROM q,z1 WHERE f_leak(z1.b); -EXPLAIN (COSTS OFF) EXECUTE plancache_test3; - QUERY PLAN ------------------------------------------------------ - Nested Loop - CTE q - -> Seq Scan on z2 - -> CTE Scan on q - -> Materialize - -> Seq Scan on z1 - Filter: (((a % 2) = 0) AND f_leak(b)) -(7 rows) - -SET ROLE regress_rls_group1; -SELECT * FROM z1 WHERE f_leak(b); -NOTICE: f_leak => bbb -NOTICE: f_leak => dad - a | b ----+----- - 2 | bbb - 4 | dad -(2 rows) - -EXPLAIN (COSTS OFF) SELECT * FROM z1 WHERE f_leak(b); - QUERY PLAN ------------------------------------------ - Seq Scan on z1 - Filter: (((a % 2) = 0) AND f_leak(b)) -(2 rows) - -EXPLAIN (COSTS OFF) EXECUTE plancache_test; - QUERY PLAN ------------------------------------------ - Seq Scan on z1 - Filter: (((a % 2) = 0) AND f_leak(b)) -(2 rows) - -EXPLAIN (COSTS OFF) EXECUTE plancache_test2; - QUERY PLAN -------------------------------------------------- - Nested Loop - CTE q - -> Seq Scan on z1 - Filter: (((a % 2) = 0) AND f_leak(b)) - -> CTE Scan on q - -> Materialize - -> Seq Scan on z2 -(7 rows) - -EXPLAIN (COSTS OFF) EXECUTE plancache_test3; - QUERY PLAN ------------------------------------------------------ - Nested Loop - CTE q - -> Seq Scan on z2 - -> CTE Scan on q - -> Materialize - -> Seq Scan on z1 - Filter: (((a % 2) = 0) AND f_leak(b)) -(7 rows) - -SET SESSION AUTHORIZATION regress_rls_carol; -SELECT * FROM z1 WHERE f_leak(b); -NOTICE: f_leak => aba -NOTICE: f_leak => ccc - a | b ----+----- - 1 | aba - 3 | ccc -(2 rows) - -EXPLAIN (COSTS OFF) SELECT * FROM z1 WHERE f_leak(b); - QUERY PLAN ------------------------------------------ - Seq Scan on z1 - Filter: (((a % 2) = 1) AND f_leak(b)) -(2 rows) - -EXPLAIN (COSTS OFF) EXECUTE plancache_test; - QUERY PLAN ------------------------------------------ - Seq Scan on z1 - Filter: (((a % 2) = 1) AND f_leak(b)) -(2 rows) - -EXPLAIN (COSTS OFF) EXECUTE plancache_test2; - QUERY PLAN -------------------------------------------------- - Nested Loop - CTE q - -> Seq Scan on z1 - Filter: (((a % 2) = 1) AND f_leak(b)) - -> CTE Scan on q - -> Materialize - -> Seq Scan on z2 -(7 rows) - -EXPLAIN (COSTS OFF) EXECUTE plancache_test3; - QUERY PLAN ------------------------------------------------------ - Nested Loop - CTE q - -> Seq Scan on z2 - -> CTE Scan on q - -> Materialize - -> Seq Scan on z1 - Filter: (((a % 2) = 1) AND f_leak(b)) -(7 rows) - -SET ROLE regress_rls_group2; -SELECT * FROM z1 WHERE f_leak(b); -NOTICE: f_leak => aba -NOTICE: f_leak => ccc - a | b ----+----- - 1 | aba - 3 | ccc -(2 rows) - -EXPLAIN (COSTS OFF) SELECT * FROM z1 WHERE f_leak(b); - QUERY PLAN ------------------------------------------ - Seq Scan on z1 - Filter: (((a % 2) = 1) AND f_leak(b)) -(2 rows) - -EXPLAIN (COSTS OFF) EXECUTE plancache_test; - QUERY PLAN ------------------------------------------ - Seq Scan on z1 - Filter: (((a % 2) = 1) AND f_leak(b)) -(2 rows) - -EXPLAIN (COSTS OFF) EXECUTE plancache_test2; - QUERY PLAN -------------------------------------------------- - Nested Loop - CTE q - -> Seq Scan on z1 - Filter: (((a % 2) = 1) AND f_leak(b)) - -> CTE Scan on q - -> Materialize - -> Seq Scan on z2 -(7 rows) - -EXPLAIN (COSTS OFF) EXECUTE plancache_test3; - QUERY PLAN ------------------------------------------------------ - Nested Loop - CTE q - -> Seq Scan on z2 - -> CTE Scan on q - -> Materialize - -> Seq Scan on z1 - Filter: (((a % 2) = 1) AND f_leak(b)) -(7 rows) - --- --- Views should follow policy for view owner. --- --- View and Table owner are the same. -SET SESSION AUTHORIZATION regress_rls_alice; -CREATE VIEW rls_view AS SELECT * FROM z1 WHERE f_leak(b); -GRANT SELECT ON rls_view TO regress_rls_bob; --- Query as role that is not owner of view or table. Should return all records. -SET SESSION AUTHORIZATION regress_rls_bob; -SELECT * FROM rls_view; -NOTICE: f_leak => aba -NOTICE: f_leak => bbb -NOTICE: f_leak => ccc -NOTICE: f_leak => dad - a | b ----+----- - 1 | aba - 2 | bbb - 3 | ccc - 4 | dad -(4 rows) - -EXPLAIN (COSTS OFF) SELECT * FROM rls_view; - QUERY PLAN ---------------------- - Seq Scan on z1 - Filter: f_leak(b) -(2 rows) - --- Query as view/table owner. Should return all records. -SET SESSION AUTHORIZATION regress_rls_alice; -SELECT * FROM rls_view; -NOTICE: f_leak => aba -NOTICE: f_leak => bbb -NOTICE: f_leak => ccc -NOTICE: f_leak => dad - a | b ----+----- - 1 | aba - 2 | bbb - 3 | ccc - 4 | dad -(4 rows) - -EXPLAIN (COSTS OFF) SELECT * FROM rls_view; - QUERY PLAN ---------------------- - Seq Scan on z1 - Filter: f_leak(b) -(2 rows) - -DROP VIEW rls_view; --- View and Table owners are different. -SET SESSION AUTHORIZATION regress_rls_bob; -CREATE VIEW rls_view AS SELECT * FROM z1 WHERE f_leak(b); -GRANT SELECT ON rls_view TO regress_rls_alice; --- Query as role that is not owner of view but is owner of table. --- Should return records based on view owner policies. -SET SESSION AUTHORIZATION regress_rls_alice; -SELECT * FROM rls_view; -NOTICE: f_leak => bbb -NOTICE: f_leak => dad - a | b ----+----- - 2 | bbb - 4 | dad -(2 rows) - -EXPLAIN (COSTS OFF) SELECT * FROM rls_view; - QUERY PLAN ------------------------------------------ - Seq Scan on z1 - Filter: (((a % 2) = 0) AND f_leak(b)) -(2 rows) - --- Query as role that is not owner of table but is owner of view. --- Should return records based on view owner policies. -SET SESSION AUTHORIZATION regress_rls_bob; -SELECT * FROM rls_view; -NOTICE: f_leak => bbb -NOTICE: f_leak => dad - a | b ----+----- - 2 | bbb - 4 | dad -(2 rows) - -EXPLAIN (COSTS OFF) SELECT * FROM rls_view; - QUERY PLAN ------------------------------------------ - Seq Scan on z1 - Filter: (((a % 2) = 0) AND f_leak(b)) -(2 rows) - --- Query as role that is not the owner of the table or view without permissions. -SET SESSION AUTHORIZATION regress_rls_carol; -SELECT * FROM rls_view; --fail - permission denied. -ERROR: permission denied for view rls_view -EXPLAIN (COSTS OFF) SELECT * FROM rls_view; --fail - permission denied. -ERROR: permission denied for view rls_view --- Query as role that is not the owner of the table or view with permissions. -SET SESSION AUTHORIZATION regress_rls_bob; -GRANT SELECT ON rls_view TO regress_rls_carol; -SET SESSION AUTHORIZATION regress_rls_carol; -SELECT * FROM rls_view; -NOTICE: f_leak => bbb -NOTICE: f_leak => dad - a | b ----+----- - 2 | bbb - 4 | dad -(2 rows) - -EXPLAIN (COSTS OFF) SELECT * FROM rls_view; - QUERY PLAN ------------------------------------------ - Seq Scan on z1 - Filter: (((a % 2) = 0) AND f_leak(b)) -(2 rows) - --- Policy requiring access to another table. -SET SESSION AUTHORIZATION regress_rls_alice; -CREATE TABLE z1_blacklist (a int); -INSERT INTO z1_blacklist VALUES (3), (4); -CREATE POLICY p3 ON z1 AS RESTRICTIVE USING (a NOT IN (SELECT a FROM z1_blacklist)); --- Query as role that is not owner of table but is owner of view without permissions. -SET SESSION AUTHORIZATION regress_rls_bob; -SELECT * FROM rls_view; --fail - permission denied. -ERROR: permission denied for table z1_blacklist -EXPLAIN (COSTS OFF) SELECT * FROM rls_view; --fail - permission denied. -ERROR: permission denied for table z1_blacklist --- Query as role that is not the owner of the table or view without permissions. -SET SESSION AUTHORIZATION regress_rls_carol; -SELECT * FROM rls_view; --fail - permission denied. -ERROR: permission denied for table z1_blacklist -EXPLAIN (COSTS OFF) SELECT * FROM rls_view; --fail - permission denied. -ERROR: permission denied for table z1_blacklist --- Query as role that is not owner of table but is owner of view with permissions. -SET SESSION AUTHORIZATION regress_rls_alice; -GRANT SELECT ON z1_blacklist TO regress_rls_bob; -SET SESSION AUTHORIZATION regress_rls_bob; -SELECT * FROM rls_view; -NOTICE: f_leak => bbb - a | b ----+----- - 2 | bbb -(1 row) - -EXPLAIN (COSTS OFF) SELECT * FROM rls_view; - QUERY PLAN ---------------------------------------------------------------------------------------- - Seq Scan on z1 - Filter: ((NOT (ANY (a = (hashed SubPlan 1).col1))) AND ((a % 2) = 0) AND f_leak(b)) - SubPlan 1 - -> Seq Scan on z1_blacklist -(4 rows) - --- Query as role that is not the owner of the table or view with permissions. -SET SESSION AUTHORIZATION regress_rls_carol; -SELECT * FROM rls_view; -NOTICE: f_leak => bbb - a | b ----+----- - 2 | bbb -(1 row) - -EXPLAIN (COSTS OFF) SELECT * FROM rls_view; - QUERY PLAN ---------------------------------------------------------------------------------------- - Seq Scan on z1 - Filter: ((NOT (ANY (a = (hashed SubPlan 1).col1))) AND ((a % 2) = 0) AND f_leak(b)) - SubPlan 1 - -> Seq Scan on z1_blacklist -(4 rows) - -SET SESSION AUTHORIZATION regress_rls_alice; -REVOKE SELECT ON z1_blacklist FROM regress_rls_bob; -DROP POLICY p3 ON z1; -SET SESSION AUTHORIZATION regress_rls_bob; -DROP VIEW rls_view; --- --- Security invoker views should follow policy for current user. --- --- View and table owner are the same. -SET SESSION AUTHORIZATION regress_rls_alice; -CREATE VIEW rls_view WITH (security_invoker) AS - SELECT * FROM z1 WHERE f_leak(b); -GRANT SELECT ON rls_view TO regress_rls_bob; -GRANT SELECT ON rls_view TO regress_rls_carol; --- Query as table owner. Should return all records. -SELECT * FROM rls_view; -NOTICE: f_leak => aba -NOTICE: f_leak => bbb -NOTICE: f_leak => ccc -NOTICE: f_leak => dad - a | b ----+----- - 1 | aba - 2 | bbb - 3 | ccc - 4 | dad -(4 rows) - -EXPLAIN (COSTS OFF) SELECT * FROM rls_view; - QUERY PLAN ---------------------- - Seq Scan on z1 - Filter: f_leak(b) -(2 rows) - --- Queries as other users. --- Should return records based on current user's policies. -SET SESSION AUTHORIZATION regress_rls_bob; -SELECT * FROM rls_view; -NOTICE: f_leak => bbb -NOTICE: f_leak => dad - a | b ----+----- - 2 | bbb - 4 | dad -(2 rows) - -EXPLAIN (COSTS OFF) SELECT * FROM rls_view; - QUERY PLAN ------------------------------------------ - Seq Scan on z1 - Filter: (((a % 2) = 0) AND f_leak(b)) -(2 rows) - -SET SESSION AUTHORIZATION regress_rls_carol; -SELECT * FROM rls_view; -NOTICE: f_leak => aba -NOTICE: f_leak => ccc - a | b ----+----- - 1 | aba - 3 | ccc -(2 rows) - -EXPLAIN (COSTS OFF) SELECT * FROM rls_view; - QUERY PLAN ------------------------------------------ - Seq Scan on z1 - Filter: (((a % 2) = 1) AND f_leak(b)) -(2 rows) - --- View and table owners are different. -SET SESSION AUTHORIZATION regress_rls_alice; -DROP VIEW rls_view; -SET SESSION AUTHORIZATION regress_rls_bob; -CREATE VIEW rls_view WITH (security_invoker) AS - SELECT * FROM z1 WHERE f_leak(b); -GRANT SELECT ON rls_view TO regress_rls_alice; -GRANT SELECT ON rls_view TO regress_rls_carol; --- Query as table owner. Should return all records. -SET SESSION AUTHORIZATION regress_rls_alice; -SELECT * FROM rls_view; -NOTICE: f_leak => aba -NOTICE: f_leak => bbb -NOTICE: f_leak => ccc -NOTICE: f_leak => dad - a | b ----+----- - 1 | aba - 2 | bbb - 3 | ccc - 4 | dad -(4 rows) - -EXPLAIN (COSTS OFF) SELECT * FROM rls_view; - QUERY PLAN ---------------------- - Seq Scan on z1 - Filter: f_leak(b) -(2 rows) - --- Queries as other users. --- Should return records based on current user's policies. -SET SESSION AUTHORIZATION regress_rls_bob; -SELECT * FROM rls_view; -NOTICE: f_leak => bbb -NOTICE: f_leak => dad - a | b ----+----- - 2 | bbb - 4 | dad -(2 rows) - -EXPLAIN (COSTS OFF) SELECT * FROM rls_view; - QUERY PLAN ------------------------------------------ - Seq Scan on z1 - Filter: (((a % 2) = 0) AND f_leak(b)) -(2 rows) - -SET SESSION AUTHORIZATION regress_rls_carol; -SELECT * FROM rls_view; -NOTICE: f_leak => aba -NOTICE: f_leak => ccc - a | b ----+----- - 1 | aba - 3 | ccc -(2 rows) - -EXPLAIN (COSTS OFF) SELECT * FROM rls_view; - QUERY PLAN ------------------------------------------ - Seq Scan on z1 - Filter: (((a % 2) = 1) AND f_leak(b)) -(2 rows) - --- Policy requiring access to another table. -SET SESSION AUTHORIZATION regress_rls_alice; -CREATE POLICY p3 ON z1 AS RESTRICTIVE USING (a NOT IN (SELECT a FROM z1_blacklist)); --- Query as role that is not owner of table but is owner of view without permissions. -SET SESSION AUTHORIZATION regress_rls_bob; -SELECT * FROM rls_view; --fail - permission denied. -ERROR: permission denied for table z1_blacklist -EXPLAIN (COSTS OFF) SELECT * FROM rls_view; --fail - permission denied. -ERROR: permission denied for table z1_blacklist --- Query as role that is not the owner of the table or view without permissions. -SET SESSION AUTHORIZATION regress_rls_carol; -SELECT * FROM rls_view; --fail - permission denied. -ERROR: permission denied for table z1_blacklist -EXPLAIN (COSTS OFF) SELECT * FROM rls_view; --fail - permission denied. -ERROR: permission denied for table z1_blacklist --- Query as role that is not owner of table but is owner of view with permissions. -SET SESSION AUTHORIZATION regress_rls_alice; -GRANT SELECT ON z1_blacklist TO regress_rls_bob; -SET SESSION AUTHORIZATION regress_rls_bob; -SELECT * FROM rls_view; -NOTICE: f_leak => bbb - a | b ----+----- - 2 | bbb -(1 row) - -EXPLAIN (COSTS OFF) SELECT * FROM rls_view; - QUERY PLAN ---------------------------------------------------------------------------------------- - Seq Scan on z1 - Filter: ((NOT (ANY (a = (hashed SubPlan 1).col1))) AND ((a % 2) = 0) AND f_leak(b)) - SubPlan 1 - -> Seq Scan on z1_blacklist -(4 rows) - --- Query as role that is not the owner of the table or view without permissions. -SET SESSION AUTHORIZATION regress_rls_carol; -SELECT * FROM rls_view; --fail - permission denied. -ERROR: permission denied for table z1_blacklist -EXPLAIN (COSTS OFF) SELECT * FROM rls_view; --fail - permission denied. -ERROR: permission denied for table z1_blacklist --- Query as role that is not the owner of the table or view with permissions. -SET SESSION AUTHORIZATION regress_rls_alice; -GRANT SELECT ON z1_blacklist TO regress_rls_carol; -SET SESSION AUTHORIZATION regress_rls_carol; -SELECT * FROM rls_view; -NOTICE: f_leak => aba - a | b ----+----- - 1 | aba -(1 row) - -EXPLAIN (COSTS OFF) SELECT * FROM rls_view; - QUERY PLAN ---------------------------------------------------------------------------------------- - Seq Scan on z1 - Filter: ((NOT (ANY (a = (hashed SubPlan 1).col1))) AND ((a % 2) = 1) AND f_leak(b)) - SubPlan 1 - -> Seq Scan on z1_blacklist -(4 rows) - -SET SESSION AUTHORIZATION regress_rls_bob; -DROP VIEW rls_view; --- --- Command specific --- -SET SESSION AUTHORIZATION regress_rls_alice; -CREATE TABLE x1 (a int, b text, c text); -GRANT ALL ON x1 TO PUBLIC; -INSERT INTO x1 VALUES - (1, 'abc', 'regress_rls_bob'), - (2, 'bcd', 'regress_rls_bob'), - (3, 'cde', 'regress_rls_carol'), - (4, 'def', 'regress_rls_carol'), - (5, 'efg', 'regress_rls_bob'), - (6, 'fgh', 'regress_rls_bob'), - (7, 'fgh', 'regress_rls_carol'), - (8, 'fgh', 'regress_rls_carol'); -CREATE POLICY p0 ON x1 FOR ALL USING (c = current_user); -CREATE POLICY p1 ON x1 FOR SELECT USING (a % 2 = 0); -CREATE POLICY p2 ON x1 FOR INSERT WITH CHECK (a % 2 = 1); -CREATE POLICY p3 ON x1 FOR UPDATE USING (a % 2 = 0); -CREATE POLICY p4 ON x1 FOR DELETE USING (a < 8); -ALTER TABLE x1 ENABLE ROW LEVEL SECURITY; -SET SESSION AUTHORIZATION regress_rls_bob; -SELECT * FROM x1 WHERE f_leak(b) ORDER BY a ASC; -NOTICE: f_leak => abc -NOTICE: f_leak => bcd -NOTICE: f_leak => def -NOTICE: f_leak => efg -NOTICE: f_leak => fgh -NOTICE: f_leak => fgh - a | b | c ----+-----+------------------- - 1 | abc | regress_rls_bob - 2 | bcd | regress_rls_bob - 4 | def | regress_rls_carol - 5 | efg | regress_rls_bob - 6 | fgh | regress_rls_bob - 8 | fgh | regress_rls_carol -(6 rows) - -UPDATE x1 SET b = b || '_updt' WHERE f_leak(b) RETURNING *; -NOTICE: f_leak => abc -NOTICE: f_leak => bcd -NOTICE: f_leak => def -NOTICE: f_leak => efg -NOTICE: f_leak => fgh -NOTICE: f_leak => fgh - a | b | c ----+----------+------------------- - 1 | abc_updt | regress_rls_bob - 2 | bcd_updt | regress_rls_bob - 4 | def_updt | regress_rls_carol - 5 | efg_updt | regress_rls_bob - 6 | fgh_updt | regress_rls_bob - 8 | fgh_updt | regress_rls_carol -(6 rows) - -SET SESSION AUTHORIZATION regress_rls_carol; -SELECT * FROM x1 WHERE f_leak(b) ORDER BY a ASC; -NOTICE: f_leak => cde -NOTICE: f_leak => fgh -NOTICE: f_leak => bcd_updt -NOTICE: f_leak => def_updt -NOTICE: f_leak => fgh_updt -NOTICE: f_leak => fgh_updt - a | b | c ----+----------+------------------- - 2 | bcd_updt | regress_rls_bob - 3 | cde | regress_rls_carol - 4 | def_updt | regress_rls_carol - 6 | fgh_updt | regress_rls_bob - 7 | fgh | regress_rls_carol - 8 | fgh_updt | regress_rls_carol -(6 rows) - -UPDATE x1 SET b = b || '_updt' WHERE f_leak(b) RETURNING *; -NOTICE: f_leak => cde -NOTICE: f_leak => fgh -NOTICE: f_leak => bcd_updt -NOTICE: f_leak => def_updt -NOTICE: f_leak => fgh_updt -NOTICE: f_leak => fgh_updt - a | b | c ----+---------------+------------------- - 3 | cde_updt | regress_rls_carol - 7 | fgh_updt | regress_rls_carol - 2 | bcd_updt_updt | regress_rls_bob - 4 | def_updt_updt | regress_rls_carol - 6 | fgh_updt_updt | regress_rls_bob - 8 | fgh_updt_updt | regress_rls_carol -(6 rows) - -DELETE FROM x1 WHERE f_leak(b) RETURNING *; -NOTICE: f_leak => cde_updt -NOTICE: f_leak => fgh_updt -NOTICE: f_leak => bcd_updt_updt -NOTICE: f_leak => def_updt_updt -NOTICE: f_leak => fgh_updt_updt -NOTICE: f_leak => fgh_updt_updt - a | b | c ----+---------------+------------------- - 3 | cde_updt | regress_rls_carol - 7 | fgh_updt | regress_rls_carol - 2 | bcd_updt_updt | regress_rls_bob - 4 | def_updt_updt | regress_rls_carol - 6 | fgh_updt_updt | regress_rls_bob - 8 | fgh_updt_updt | regress_rls_carol -(6 rows) - --- --- Duplicate Policy Names --- -SET SESSION AUTHORIZATION regress_rls_alice; -CREATE TABLE y1 (a int, b text); -CREATE TABLE y2 (a int, b text); -GRANT ALL ON y1, y2 TO regress_rls_bob; -CREATE POLICY p1 ON y1 FOR ALL USING (a % 2 = 0); -CREATE POLICY p2 ON y1 FOR SELECT USING (a > 2); -CREATE POLICY p1 ON y1 FOR SELECT USING (a % 2 = 1); --fail -ERROR: policy "p1" for table "y1" already exists -CREATE POLICY p1 ON y2 FOR ALL USING (a % 2 = 0); --OK -ALTER TABLE y1 ENABLE ROW LEVEL SECURITY; -ALTER TABLE y2 ENABLE ROW LEVEL SECURITY; --- --- Expression structure with SBV --- --- Create view as table owner. RLS should NOT be applied. -SET SESSION AUTHORIZATION regress_rls_alice; -CREATE VIEW rls_sbv WITH (security_barrier) AS - SELECT * FROM y1 WHERE f_leak(b); -EXPLAIN (COSTS OFF) SELECT * FROM rls_sbv WHERE (a = 1); - QUERY PLAN ------------------------------------ - Seq Scan on y1 - Filter: (f_leak(b) AND (a = 1)) -(2 rows) - -DROP VIEW rls_sbv; --- Create view as role that does not own table. RLS should be applied. -SET SESSION AUTHORIZATION regress_rls_bob; -CREATE VIEW rls_sbv WITH (security_barrier) AS - SELECT * FROM y1 WHERE f_leak(b); -EXPLAIN (COSTS OFF) SELECT * FROM rls_sbv WHERE (a = 1); - QUERY PLAN ------------------------------------------------------------------- - Seq Scan on y1 - Filter: ((a = 1) AND ((a > 2) OR ((a % 2) = 0)) AND f_leak(b)) -(2 rows) - -DROP VIEW rls_sbv; --- --- Expression structure --- -SET SESSION AUTHORIZATION regress_rls_alice; -INSERT INTO y2 (SELECT x, public.fipshash(x::text) FROM generate_series(0,20) x); -CREATE POLICY p2 ON y2 USING (a % 3 = 0); -CREATE POLICY p3 ON y2 USING (a % 4 = 0); -SET SESSION AUTHORIZATION regress_rls_bob; -SELECT * FROM y2 WHERE f_leak(b); -NOTICE: f_leak => 5feceb66ffc86f38d952786c6d696c79 -NOTICE: f_leak => d4735e3a265e16eee03f59718b9b5d03 -NOTICE: f_leak => 4e07408562bedb8b60ce05c1decfe3ad -NOTICE: f_leak => 4b227777d4dd1fc61c6f884f48641d02 -NOTICE: f_leak => e7f6c011776e8db7cd330b54174fd76f -NOTICE: f_leak => 2c624232cdd221771294dfbb310aca00 -NOTICE: f_leak => 19581e27de7ced00ff1ce50b2047e7a5 -NOTICE: f_leak => 4a44dc15364204a80fe80e9039455cc1 -NOTICE: f_leak => 6b51d431df5d7f141cbececcf79edf3d -NOTICE: f_leak => 8527a891e224136950ff32ca212b45bc -NOTICE: f_leak => e629fa6598d732768f7c726b4b621285 -NOTICE: f_leak => b17ef6d19c7a5b1ee83b907c595526dc -NOTICE: f_leak => 4ec9599fc203d176a301536c2e091a19 -NOTICE: f_leak => f5ca38f748a1d6eaf726b8a42fb575c3 - a | b -----+---------------------------------- - 0 | 5feceb66ffc86f38d952786c6d696c79 - 2 | d4735e3a265e16eee03f59718b9b5d03 - 3 | 4e07408562bedb8b60ce05c1decfe3ad - 4 | 4b227777d4dd1fc61c6f884f48641d02 - 6 | e7f6c011776e8db7cd330b54174fd76f - 8 | 2c624232cdd221771294dfbb310aca00 - 9 | 19581e27de7ced00ff1ce50b2047e7a5 - 10 | 4a44dc15364204a80fe80e9039455cc1 - 12 | 6b51d431df5d7f141cbececcf79edf3d - 14 | 8527a891e224136950ff32ca212b45bc - 15 | e629fa6598d732768f7c726b4b621285 - 16 | b17ef6d19c7a5b1ee83b907c595526dc - 18 | 4ec9599fc203d176a301536c2e091a19 - 20 | f5ca38f748a1d6eaf726b8a42fb575c3 -(14 rows) - -EXPLAIN (COSTS OFF) SELECT * FROM y2 WHERE f_leak(b); - QUERY PLAN ------------------------------------------------------------------------------ - Seq Scan on y2 - Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b)) -(2 rows) - --- --- Qual push-down of leaky functions, when not referring to table --- -SELECT * FROM y2 WHERE f_leak('abc'); -NOTICE: f_leak => abc -NOTICE: f_leak => abc -NOTICE: f_leak => abc -NOTICE: f_leak => abc -NOTICE: f_leak => abc -NOTICE: f_leak => abc -NOTICE: f_leak => abc -NOTICE: f_leak => abc -NOTICE: f_leak => abc -NOTICE: f_leak => abc -NOTICE: f_leak => abc -NOTICE: f_leak => abc -NOTICE: f_leak => abc -NOTICE: f_leak => abc -NOTICE: f_leak => abc -NOTICE: f_leak => abc -NOTICE: f_leak => abc -NOTICE: f_leak => abc -NOTICE: f_leak => abc -NOTICE: f_leak => abc -NOTICE: f_leak => abc - a | b -----+---------------------------------- - 0 | 5feceb66ffc86f38d952786c6d696c79 - 2 | d4735e3a265e16eee03f59718b9b5d03 - 3 | 4e07408562bedb8b60ce05c1decfe3ad - 4 | 4b227777d4dd1fc61c6f884f48641d02 - 6 | e7f6c011776e8db7cd330b54174fd76f - 8 | 2c624232cdd221771294dfbb310aca00 - 9 | 19581e27de7ced00ff1ce50b2047e7a5 - 10 | 4a44dc15364204a80fe80e9039455cc1 - 12 | 6b51d431df5d7f141cbececcf79edf3d - 14 | 8527a891e224136950ff32ca212b45bc - 15 | e629fa6598d732768f7c726b4b621285 - 16 | b17ef6d19c7a5b1ee83b907c595526dc - 18 | 4ec9599fc203d176a301536c2e091a19 - 20 | f5ca38f748a1d6eaf726b8a42fb575c3 -(14 rows) - -EXPLAIN (COSTS OFF) SELECT * FROM y2 WHERE f_leak('abc'); - QUERY PLAN ---------------------------------------------------------------------------------------- - Seq Scan on y2 - Filter: (f_leak('abc'::text) AND (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))) -(2 rows) - -CREATE TABLE test_qual_pushdown ( - abc text -); -INSERT INTO test_qual_pushdown VALUES ('abc'),('def'); -SELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(abc); -NOTICE: f_leak => abc -NOTICE: f_leak => def - a | b | abc ----+---+----- -(0 rows) - -EXPLAIN (COSTS OFF) SELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(abc); - QUERY PLAN -------------------------------------------------------------------------- - Hash Join - Hash Cond: (test_qual_pushdown.abc = y2.b) - -> Seq Scan on test_qual_pushdown - Filter: f_leak(abc) - -> Hash - -> Seq Scan on y2 - Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) -(7 rows) - -SELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(b); -NOTICE: f_leak => 5feceb66ffc86f38d952786c6d696c79 -NOTICE: f_leak => d4735e3a265e16eee03f59718b9b5d03 -NOTICE: f_leak => 4e07408562bedb8b60ce05c1decfe3ad -NOTICE: f_leak => 4b227777d4dd1fc61c6f884f48641d02 -NOTICE: f_leak => e7f6c011776e8db7cd330b54174fd76f -NOTICE: f_leak => 2c624232cdd221771294dfbb310aca00 -NOTICE: f_leak => 19581e27de7ced00ff1ce50b2047e7a5 -NOTICE: f_leak => 4a44dc15364204a80fe80e9039455cc1 -NOTICE: f_leak => 6b51d431df5d7f141cbececcf79edf3d -NOTICE: f_leak => 8527a891e224136950ff32ca212b45bc -NOTICE: f_leak => e629fa6598d732768f7c726b4b621285 -NOTICE: f_leak => b17ef6d19c7a5b1ee83b907c595526dc -NOTICE: f_leak => 4ec9599fc203d176a301536c2e091a19 -NOTICE: f_leak => f5ca38f748a1d6eaf726b8a42fb575c3 - a | b | abc ----+---+----- -(0 rows) - -EXPLAIN (COSTS OFF) SELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(b); - QUERY PLAN ------------------------------------------------------------------------------------------ - Hash Join - Hash Cond: (test_qual_pushdown.abc = y2.b) - -> Seq Scan on test_qual_pushdown - -> Hash - -> Seq Scan on y2 - Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b)) -(6 rows) - -DROP TABLE test_qual_pushdown; --- --- Plancache invalidate on user change. --- -RESET SESSION AUTHORIZATION; -DROP TABLE t1 CASCADE; -NOTICE: drop cascades to 2 other objects -DETAIL: drop cascades to table t2 -drop cascades to table t3 -CREATE TABLE t1 (a integer); -GRANT SELECT ON t1 TO regress_rls_bob, regress_rls_carol; -CREATE POLICY p1 ON t1 TO regress_rls_bob USING ((a % 2) = 0); -CREATE POLICY p2 ON t1 TO regress_rls_carol USING ((a % 4) = 0); -ALTER TABLE t1 ENABLE ROW LEVEL SECURITY; --- Prepare as regress_rls_bob -SET ROLE regress_rls_bob; -PREPARE role_inval AS SELECT * FROM t1; --- Check plan -EXPLAIN (COSTS OFF) EXECUTE role_inval; - QUERY PLAN -------------------------- - Seq Scan on t1 - Filter: ((a % 2) = 0) -(2 rows) - --- Change to regress_rls_carol -SET ROLE regress_rls_carol; --- Check plan- should be different -EXPLAIN (COSTS OFF) EXECUTE role_inval; - QUERY PLAN -------------------------- - Seq Scan on t1 - Filter: ((a % 4) = 0) -(2 rows) - --- Change back to regress_rls_bob -SET ROLE regress_rls_bob; --- Check plan- should be back to original -EXPLAIN (COSTS OFF) EXECUTE role_inval; - QUERY PLAN -------------------------- - Seq Scan on t1 - Filter: ((a % 2) = 0) -(2 rows) - --- --- CTE and RLS --- -RESET SESSION AUTHORIZATION; -DROP TABLE t1 CASCADE; -CREATE TABLE t1 (a integer, b text); -CREATE POLICY p1 ON t1 USING (a % 2 = 0); -ALTER TABLE t1 ENABLE ROW LEVEL SECURITY; -GRANT ALL ON t1 TO regress_rls_bob; -INSERT INTO t1 (SELECT x, public.fipshash(x::text) FROM generate_series(0,20) x); -SET SESSION AUTHORIZATION regress_rls_bob; -WITH cte1 AS MATERIALIZED (SELECT * FROM t1 WHERE f_leak(b)) SELECT * FROM cte1; -NOTICE: f_leak => 5feceb66ffc86f38d952786c6d696c79 -NOTICE: f_leak => d4735e3a265e16eee03f59718b9b5d03 -NOTICE: f_leak => 4b227777d4dd1fc61c6f884f48641d02 -NOTICE: f_leak => e7f6c011776e8db7cd330b54174fd76f -NOTICE: f_leak => 2c624232cdd221771294dfbb310aca00 -NOTICE: f_leak => 4a44dc15364204a80fe80e9039455cc1 -NOTICE: f_leak => 6b51d431df5d7f141cbececcf79edf3d -NOTICE: f_leak => 8527a891e224136950ff32ca212b45bc -NOTICE: f_leak => b17ef6d19c7a5b1ee83b907c595526dc -NOTICE: f_leak => 4ec9599fc203d176a301536c2e091a19 -NOTICE: f_leak => f5ca38f748a1d6eaf726b8a42fb575c3 - a | b -----+---------------------------------- - 0 | 5feceb66ffc86f38d952786c6d696c79 - 2 | d4735e3a265e16eee03f59718b9b5d03 - 4 | 4b227777d4dd1fc61c6f884f48641d02 - 6 | e7f6c011776e8db7cd330b54174fd76f - 8 | 2c624232cdd221771294dfbb310aca00 - 10 | 4a44dc15364204a80fe80e9039455cc1 - 12 | 6b51d431df5d7f141cbececcf79edf3d - 14 | 8527a891e224136950ff32ca212b45bc - 16 | b17ef6d19c7a5b1ee83b907c595526dc - 18 | 4ec9599fc203d176a301536c2e091a19 - 20 | f5ca38f748a1d6eaf726b8a42fb575c3 -(11 rows) - -EXPLAIN (COSTS OFF) -WITH cte1 AS MATERIALIZED (SELECT * FROM t1 WHERE f_leak(b)) SELECT * FROM cte1; - QUERY PLAN -------------------------------------------------- - CTE Scan on cte1 - CTE cte1 - -> Seq Scan on t1 - Filter: (((a % 2) = 0) AND f_leak(b)) -(4 rows) - -WITH cte1 AS (UPDATE t1 SET a = a + 1 RETURNING *) SELECT * FROM cte1; --fail -ERROR: new row violates row-level security policy for table "t1" -WITH cte1 AS (UPDATE t1 SET a = a RETURNING *) SELECT * FROM cte1; --ok - a | b -----+---------------------------------- - 0 | 5feceb66ffc86f38d952786c6d696c79 - 2 | d4735e3a265e16eee03f59718b9b5d03 - 4 | 4b227777d4dd1fc61c6f884f48641d02 - 6 | e7f6c011776e8db7cd330b54174fd76f - 8 | 2c624232cdd221771294dfbb310aca00 - 10 | 4a44dc15364204a80fe80e9039455cc1 - 12 | 6b51d431df5d7f141cbececcf79edf3d - 14 | 8527a891e224136950ff32ca212b45bc - 16 | b17ef6d19c7a5b1ee83b907c595526dc - 18 | 4ec9599fc203d176a301536c2e091a19 - 20 | f5ca38f748a1d6eaf726b8a42fb575c3 -(11 rows) - -WITH cte1 AS (INSERT INTO t1 VALUES (21, 'Fail') RETURNING *) SELECT * FROM cte1; --fail -ERROR: new row violates row-level security policy for table "t1" -WITH cte1 AS (INSERT INTO t1 VALUES (20, 'Success') RETURNING *) SELECT * FROM cte1; --ok - a | b -----+--------- - 20 | Success -(1 row) - --- --- Rename Policy --- -RESET SESSION AUTHORIZATION; -ALTER POLICY p1 ON t1 RENAME TO p1; --fail -ERROR: policy "p1" for table "t1" already exists -SELECT polname, relname - FROM pg_policy pol - JOIN pg_class pc ON (pc.oid = pol.polrelid) - WHERE relname = 't1'; - polname | relname ----------+--------- - p1 | t1 -(1 row) - -ALTER POLICY p1 ON t1 RENAME TO p2; --ok -SELECT polname, relname - FROM pg_policy pol - JOIN pg_class pc ON (pc.oid = pol.polrelid) - WHERE relname = 't1'; - polname | relname ----------+--------- - p2 | t1 -(1 row) - --- --- Check INSERT SELECT --- -SET SESSION AUTHORIZATION regress_rls_bob; -CREATE TABLE t2 (a integer, b text); -INSERT INTO t2 (SELECT * FROM t1); -EXPLAIN (COSTS OFF) INSERT INTO t2 (SELECT * FROM t1); - QUERY PLAN -------------------------------- - Insert on t2 - -> Seq Scan on t1 - Filter: ((a % 2) = 0) -(3 rows) - -SELECT * FROM t2; - a | b -----+---------------------------------- - 0 | 5feceb66ffc86f38d952786c6d696c79 - 2 | d4735e3a265e16eee03f59718b9b5d03 - 4 | 4b227777d4dd1fc61c6f884f48641d02 - 6 | e7f6c011776e8db7cd330b54174fd76f - 8 | 2c624232cdd221771294dfbb310aca00 - 10 | 4a44dc15364204a80fe80e9039455cc1 - 12 | 6b51d431df5d7f141cbececcf79edf3d - 14 | 8527a891e224136950ff32ca212b45bc - 16 | b17ef6d19c7a5b1ee83b907c595526dc - 18 | 4ec9599fc203d176a301536c2e091a19 - 20 | f5ca38f748a1d6eaf726b8a42fb575c3 - 20 | Success -(12 rows) - -EXPLAIN (COSTS OFF) SELECT * FROM t2; - QUERY PLAN ----------------- - Seq Scan on t2 -(1 row) - -CREATE TABLE t3 AS SELECT * FROM t1; -SELECT * FROM t3; - a | b -----+---------------------------------- - 0 | 5feceb66ffc86f38d952786c6d696c79 - 2 | d4735e3a265e16eee03f59718b9b5d03 - 4 | 4b227777d4dd1fc61c6f884f48641d02 - 6 | e7f6c011776e8db7cd330b54174fd76f - 8 | 2c624232cdd221771294dfbb310aca00 - 10 | 4a44dc15364204a80fe80e9039455cc1 - 12 | 6b51d431df5d7f141cbececcf79edf3d - 14 | 8527a891e224136950ff32ca212b45bc - 16 | b17ef6d19c7a5b1ee83b907c595526dc - 18 | 4ec9599fc203d176a301536c2e091a19 - 20 | f5ca38f748a1d6eaf726b8a42fb575c3 - 20 | Success -(12 rows) - -SELECT * INTO t4 FROM t1; -SELECT * FROM t4; - a | b -----+---------------------------------- - 0 | 5feceb66ffc86f38d952786c6d696c79 - 2 | d4735e3a265e16eee03f59718b9b5d03 - 4 | 4b227777d4dd1fc61c6f884f48641d02 - 6 | e7f6c011776e8db7cd330b54174fd76f - 8 | 2c624232cdd221771294dfbb310aca00 - 10 | 4a44dc15364204a80fe80e9039455cc1 - 12 | 6b51d431df5d7f141cbececcf79edf3d - 14 | 8527a891e224136950ff32ca212b45bc - 16 | b17ef6d19c7a5b1ee83b907c595526dc - 18 | 4ec9599fc203d176a301536c2e091a19 - 20 | f5ca38f748a1d6eaf726b8a42fb575c3 - 20 | Success -(12 rows) - --- --- RLS with JOIN --- -SET SESSION AUTHORIZATION regress_rls_alice; -CREATE TABLE blog (id integer, author text, post text); -CREATE TABLE comment (blog_id integer, message text); -GRANT ALL ON blog, comment TO regress_rls_bob; -CREATE POLICY blog_1 ON blog USING (id % 2 = 0); -ALTER TABLE blog ENABLE ROW LEVEL SECURITY; -INSERT INTO blog VALUES - (1, 'alice', 'blog #1'), - (2, 'bob', 'blog #1'), - (3, 'alice', 'blog #2'), - (4, 'alice', 'blog #3'), - (5, 'john', 'blog #1'); -INSERT INTO comment VALUES - (1, 'cool blog'), - (1, 'fun blog'), - (3, 'crazy blog'), - (5, 'what?'), - (4, 'insane!'), - (2, 'who did it?'); -SET SESSION AUTHORIZATION regress_rls_bob; --- Check RLS JOIN with Non-RLS. -SELECT id, author, message FROM blog JOIN comment ON id = blog_id; - id | author | message -----+--------+------------- - 4 | alice | insane! - 2 | bob | who did it? -(2 rows) - --- Check Non-RLS JOIN with RLS. -SELECT id, author, message FROM comment JOIN blog ON id = blog_id; - id | author | message -----+--------+------------- - 4 | alice | insane! - 2 | bob | who did it? -(2 rows) - -SET SESSION AUTHORIZATION regress_rls_alice; -CREATE POLICY comment_1 ON comment USING (blog_id < 4); -ALTER TABLE comment ENABLE ROW LEVEL SECURITY; -SET SESSION AUTHORIZATION regress_rls_bob; --- Check RLS JOIN RLS -SELECT id, author, message FROM blog JOIN comment ON id = blog_id; - id | author | message -----+--------+------------- - 2 | bob | who did it? -(1 row) - -SELECT id, author, message FROM comment JOIN blog ON id = blog_id; - id | author | message -----+--------+------------- - 2 | bob | who did it? -(1 row) - -SET SESSION AUTHORIZATION regress_rls_alice; -DROP TABLE blog, comment; --- --- Default Deny Policy --- -RESET SESSION AUTHORIZATION; -DROP POLICY p2 ON t1; -ALTER TABLE t1 OWNER TO regress_rls_alice; --- Check that default deny does not apply to superuser. -RESET SESSION AUTHORIZATION; -SELECT * FROM t1; - a | b -----+---------------------------------- - 1 | 6b86b273ff34fce19d6b804eff5a3f57 - 3 | 4e07408562bedb8b60ce05c1decfe3ad - 5 | ef2d127de37b942baad06145e54b0c61 - 7 | 7902699be42c8a8e46fbbb4501726517 - 9 | 19581e27de7ced00ff1ce50b2047e7a5 - 11 | 4fc82b26aecb47d2868c4efbe3581732 - 13 | 3fdba35f04dc8c462986c992bcf87554 - 15 | e629fa6598d732768f7c726b4b621285 - 17 | 4523540f1504cd17100c4835e85b7eef - 19 | 9400f1b21cb527d7fa3d3eabba93557a - 0 | 5feceb66ffc86f38d952786c6d696c79 - 2 | d4735e3a265e16eee03f59718b9b5d03 - 4 | 4b227777d4dd1fc61c6f884f48641d02 - 6 | e7f6c011776e8db7cd330b54174fd76f - 8 | 2c624232cdd221771294dfbb310aca00 - 10 | 4a44dc15364204a80fe80e9039455cc1 - 12 | 6b51d431df5d7f141cbececcf79edf3d - 14 | 8527a891e224136950ff32ca212b45bc - 16 | b17ef6d19c7a5b1ee83b907c595526dc - 18 | 4ec9599fc203d176a301536c2e091a19 - 20 | f5ca38f748a1d6eaf726b8a42fb575c3 - 20 | Success -(22 rows) - -EXPLAIN (COSTS OFF) SELECT * FROM t1; - QUERY PLAN ----------------- - Seq Scan on t1 -(1 row) - --- Check that default deny does not apply to table owner. -SET SESSION AUTHORIZATION regress_rls_alice; -SELECT * FROM t1; - a | b -----+---------------------------------- - 1 | 6b86b273ff34fce19d6b804eff5a3f57 - 3 | 4e07408562bedb8b60ce05c1decfe3ad - 5 | ef2d127de37b942baad06145e54b0c61 - 7 | 7902699be42c8a8e46fbbb4501726517 - 9 | 19581e27de7ced00ff1ce50b2047e7a5 - 11 | 4fc82b26aecb47d2868c4efbe3581732 - 13 | 3fdba35f04dc8c462986c992bcf87554 - 15 | e629fa6598d732768f7c726b4b621285 - 17 | 4523540f1504cd17100c4835e85b7eef - 19 | 9400f1b21cb527d7fa3d3eabba93557a - 0 | 5feceb66ffc86f38d952786c6d696c79 - 2 | d4735e3a265e16eee03f59718b9b5d03 - 4 | 4b227777d4dd1fc61c6f884f48641d02 - 6 | e7f6c011776e8db7cd330b54174fd76f - 8 | 2c624232cdd221771294dfbb310aca00 - 10 | 4a44dc15364204a80fe80e9039455cc1 - 12 | 6b51d431df5d7f141cbececcf79edf3d - 14 | 8527a891e224136950ff32ca212b45bc - 16 | b17ef6d19c7a5b1ee83b907c595526dc - 18 | 4ec9599fc203d176a301536c2e091a19 - 20 | f5ca38f748a1d6eaf726b8a42fb575c3 - 20 | Success -(22 rows) - -EXPLAIN (COSTS OFF) SELECT * FROM t1; - QUERY PLAN ----------------- - Seq Scan on t1 -(1 row) - --- Check that default deny applies to non-owner/non-superuser when RLS on. -SET SESSION AUTHORIZATION regress_rls_bob; -SET row_security TO ON; -SELECT * FROM t1; - a | b ----+--- -(0 rows) - -EXPLAIN (COSTS OFF) SELECT * FROM t1; - QUERY PLAN --------------------------- - Result - One-Time Filter: false -(2 rows) - -SET SESSION AUTHORIZATION regress_rls_bob; -SELECT * FROM t1; - a | b ----+--- -(0 rows) - -EXPLAIN (COSTS OFF) SELECT * FROM t1; - QUERY PLAN --------------------------- - Result - One-Time Filter: false -(2 rows) - --- --- COPY TO/FROM --- -RESET SESSION AUTHORIZATION; -DROP TABLE copy_t CASCADE; -ERROR: table "copy_t" does not exist -CREATE TABLE copy_t (a integer, b text); -CREATE POLICY p1 ON copy_t USING (a % 2 = 0); -ALTER TABLE copy_t ENABLE ROW LEVEL SECURITY; -GRANT ALL ON copy_t TO regress_rls_bob, regress_rls_exempt_user; -INSERT INTO copy_t (SELECT x, public.fipshash(x::text) FROM generate_series(0,10) x); --- Check COPY TO as Superuser/owner. -RESET SESSION AUTHORIZATION; -SET row_security TO OFF; -COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; -0,5feceb66ffc86f38d952786c6d696c79 -1,6b86b273ff34fce19d6b804eff5a3f57 -2,d4735e3a265e16eee03f59718b9b5d03 -3,4e07408562bedb8b60ce05c1decfe3ad -4,4b227777d4dd1fc61c6f884f48641d02 -5,ef2d127de37b942baad06145e54b0c61 -6,e7f6c011776e8db7cd330b54174fd76f -7,7902699be42c8a8e46fbbb4501726517 -8,2c624232cdd221771294dfbb310aca00 -9,19581e27de7ced00ff1ce50b2047e7a5 -10,4a44dc15364204a80fe80e9039455cc1 -SET row_security TO ON; -COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; -0,5feceb66ffc86f38d952786c6d696c79 -1,6b86b273ff34fce19d6b804eff5a3f57 -2,d4735e3a265e16eee03f59718b9b5d03 -3,4e07408562bedb8b60ce05c1decfe3ad -4,4b227777d4dd1fc61c6f884f48641d02 -5,ef2d127de37b942baad06145e54b0c61 -6,e7f6c011776e8db7cd330b54174fd76f -7,7902699be42c8a8e46fbbb4501726517 -8,2c624232cdd221771294dfbb310aca00 -9,19581e27de7ced00ff1ce50b2047e7a5 -10,4a44dc15364204a80fe80e9039455cc1 --- Check COPY TO as user with permissions. -SET SESSION AUTHORIZATION regress_rls_bob; -SET row_security TO OFF; -COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --fail - would be affected by RLS -ERROR: query would be affected by row-level security policy for table "copy_t" -SET row_security TO ON; -COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --ok -0,5feceb66ffc86f38d952786c6d696c79 -2,d4735e3a265e16eee03f59718b9b5d03 -4,4b227777d4dd1fc61c6f884f48641d02 -6,e7f6c011776e8db7cd330b54174fd76f -8,2c624232cdd221771294dfbb310aca00 -10,4a44dc15364204a80fe80e9039455cc1 --- Check COPY TO as user with permissions and BYPASSRLS -SET SESSION AUTHORIZATION regress_rls_exempt_user; -SET row_security TO OFF; -COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --ok -0,5feceb66ffc86f38d952786c6d696c79 -1,6b86b273ff34fce19d6b804eff5a3f57 -2,d4735e3a265e16eee03f59718b9b5d03 -3,4e07408562bedb8b60ce05c1decfe3ad -4,4b227777d4dd1fc61c6f884f48641d02 -5,ef2d127de37b942baad06145e54b0c61 -6,e7f6c011776e8db7cd330b54174fd76f -7,7902699be42c8a8e46fbbb4501726517 -8,2c624232cdd221771294dfbb310aca00 -9,19581e27de7ced00ff1ce50b2047e7a5 -10,4a44dc15364204a80fe80e9039455cc1 -SET row_security TO ON; -COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --ok -0,5feceb66ffc86f38d952786c6d696c79 -1,6b86b273ff34fce19d6b804eff5a3f57 -2,d4735e3a265e16eee03f59718b9b5d03 -3,4e07408562bedb8b60ce05c1decfe3ad -4,4b227777d4dd1fc61c6f884f48641d02 -5,ef2d127de37b942baad06145e54b0c61 -6,e7f6c011776e8db7cd330b54174fd76f -7,7902699be42c8a8e46fbbb4501726517 -8,2c624232cdd221771294dfbb310aca00 -9,19581e27de7ced00ff1ce50b2047e7a5 -10,4a44dc15364204a80fe80e9039455cc1 --- Check COPY TO as user without permissions. SET row_security TO OFF; -SET SESSION AUTHORIZATION regress_rls_carol; -SET row_security TO OFF; -COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --fail - would be affected by RLS -ERROR: query would be affected by row-level security policy for table "copy_t" -SET row_security TO ON; -COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --fail - permission denied -ERROR: permission denied for table copy_t --- Check COPY relation TO; keep it just one row to avoid reordering issues -RESET SESSION AUTHORIZATION; -SET row_security TO ON; -CREATE TABLE copy_rel_to (a integer, b text); -CREATE POLICY p1 ON copy_rel_to USING (a % 2 = 0); -ALTER TABLE copy_rel_to ENABLE ROW LEVEL SECURITY; -GRANT ALL ON copy_rel_to TO regress_rls_bob, regress_rls_exempt_user; -INSERT INTO copy_rel_to VALUES (1, public.fipshash('1')); --- Check COPY TO as Superuser/owner. -RESET SESSION AUTHORIZATION; -SET row_security TO OFF; -COPY copy_rel_to TO STDOUT WITH DELIMITER ','; -1,6b86b273ff34fce19d6b804eff5a3f57 -SET row_security TO ON; -COPY copy_rel_to TO STDOUT WITH DELIMITER ','; -1,6b86b273ff34fce19d6b804eff5a3f57 --- Check COPY TO as user with permissions. -SET SESSION AUTHORIZATION regress_rls_bob; -SET row_security TO OFF; -COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --fail - would be affected by RLS -ERROR: query would be affected by row-level security policy for table "copy_rel_to" -SET row_security TO ON; -COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --ok --- Check COPY TO as user with permissions and BYPASSRLS -SET SESSION AUTHORIZATION regress_rls_exempt_user; -SET row_security TO OFF; -COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --ok -1,6b86b273ff34fce19d6b804eff5a3f57 -SET row_security TO ON; -COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --ok -1,6b86b273ff34fce19d6b804eff5a3f57 --- Check COPY TO as user without permissions. SET row_security TO OFF; -SET SESSION AUTHORIZATION regress_rls_carol; -SET row_security TO OFF; -COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --fail - permission denied -ERROR: permission denied for table copy_rel_to -SET row_security TO ON; -COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --fail - permission denied -ERROR: permission denied for table copy_rel_to --- Check behavior with a child table. -RESET SESSION AUTHORIZATION; -SET row_security TO ON; -CREATE TABLE copy_rel_to_child () INHERITS (copy_rel_to); -INSERT INTO copy_rel_to_child VALUES (1, 'one'), (2, 'two'); --- Check COPY TO as Superuser/owner. -RESET SESSION AUTHORIZATION; -SET row_security TO OFF; -COPY copy_rel_to TO STDOUT WITH DELIMITER ','; -1,6b86b273ff34fce19d6b804eff5a3f57 -SET row_security TO ON; -COPY copy_rel_to TO STDOUT WITH DELIMITER ','; -1,6b86b273ff34fce19d6b804eff5a3f57 --- Check COPY TO as user with permissions. -SET SESSION AUTHORIZATION regress_rls_bob; -SET row_security TO OFF; -COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --fail - would be affected by RLS -ERROR: query would be affected by row-level security policy for table "copy_rel_to" -SET row_security TO ON; -COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --ok --- Check COPY TO as user with permissions and BYPASSRLS -SET SESSION AUTHORIZATION regress_rls_exempt_user; -SET row_security TO OFF; -COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --ok -1,6b86b273ff34fce19d6b804eff5a3f57 -SET row_security TO ON; -COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --ok -1,6b86b273ff34fce19d6b804eff5a3f57 --- Check COPY TO as user without permissions. SET row_security TO OFF; -SET SESSION AUTHORIZATION regress_rls_carol; -SET row_security TO OFF; -COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --fail - permission denied -ERROR: permission denied for table copy_rel_to -SET row_security TO ON; -COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --fail - permission denied -ERROR: permission denied for table copy_rel_to --- Check COPY FROM as Superuser/owner. -RESET SESSION AUTHORIZATION; -SET row_security TO OFF; -COPY copy_t FROM STDIN; --ok -SET row_security TO ON; -COPY copy_t FROM STDIN; --ok --- Check COPY FROM as user with permissions. -SET SESSION AUTHORIZATION regress_rls_bob; -SET row_security TO OFF; -COPY copy_t FROM STDIN; --fail - would be affected by RLS. -ERROR: query would be affected by row-level security policy for table "copy_t" -SET row_security TO ON; -COPY copy_t FROM STDIN; --fail - COPY FROM not supported by RLS. -ERROR: COPY FROM not supported with row-level security -HINT: Use INSERT statements instead. --- Check COPY FROM as user with permissions and BYPASSRLS -SET SESSION AUTHORIZATION regress_rls_exempt_user; -SET row_security TO ON; -COPY copy_t FROM STDIN; --ok --- Check COPY FROM as user without permissions. -SET SESSION AUTHORIZATION regress_rls_carol; -SET row_security TO OFF; -COPY copy_t FROM STDIN; --fail - permission denied. -ERROR: permission denied for table copy_t -SET row_security TO ON; -COPY copy_t FROM STDIN; --fail - permission denied. -ERROR: permission denied for table copy_t -RESET SESSION AUTHORIZATION; -DROP TABLE copy_t; -DROP TABLE copy_rel_to CASCADE; -NOTICE: drop cascades to table copy_rel_to_child --- Check WHERE CURRENT OF -SET SESSION AUTHORIZATION regress_rls_alice; -CREATE TABLE current_check (currentid int, payload text, rlsuser text); -GRANT ALL ON current_check TO PUBLIC; -INSERT INTO current_check VALUES - (1, 'abc', 'regress_rls_bob'), - (2, 'bcd', 'regress_rls_bob'), - (3, 'cde', 'regress_rls_bob'), - (4, 'def', 'regress_rls_bob'); -CREATE POLICY p1 ON current_check FOR SELECT USING (currentid % 2 = 0); -CREATE POLICY p2 ON current_check FOR DELETE USING (currentid = 4 AND rlsuser = current_user); -CREATE POLICY p3 ON current_check FOR UPDATE USING (currentid = 4) WITH CHECK (rlsuser = current_user); -ALTER TABLE current_check ENABLE ROW LEVEL SECURITY; -SET SESSION AUTHORIZATION regress_rls_bob; --- Can SELECT even rows -SELECT * FROM current_check; - currentid | payload | rlsuser ------------+---------+----------------- - 2 | bcd | regress_rls_bob - 4 | def | regress_rls_bob -(2 rows) - --- Cannot UPDATE row 2 -UPDATE current_check SET payload = payload || '_new' WHERE currentid = 2 RETURNING *; - currentid | payload | rlsuser ------------+---------+--------- -(0 rows) - -BEGIN; -DECLARE current_check_cursor SCROLL CURSOR FOR SELECT * FROM current_check; --- Returns rows that can be seen according to SELECT policy, like plain SELECT --- above (even rows) -FETCH ABSOLUTE 1 FROM current_check_cursor; - currentid | payload | rlsuser ------------+---------+----------------- - 2 | bcd | regress_rls_bob -(1 row) - --- Still cannot UPDATE row 2 through cursor -UPDATE current_check SET payload = payload || '_new' WHERE CURRENT OF current_check_cursor RETURNING *; - currentid | payload | rlsuser ------------+---------+--------- -(0 rows) - --- Can update row 4 through cursor, which is the next visible row -FETCH RELATIVE 1 FROM current_check_cursor; - currentid | payload | rlsuser ------------+---------+----------------- - 4 | def | regress_rls_bob -(1 row) - -UPDATE current_check SET payload = payload || '_new' WHERE CURRENT OF current_check_cursor RETURNING *; - currentid | payload | rlsuser ------------+---------+----------------- - 4 | def_new | regress_rls_bob -(1 row) - -SELECT * FROM current_check; - currentid | payload | rlsuser ------------+---------+----------------- - 2 | bcd | regress_rls_bob - 4 | def_new | regress_rls_bob -(2 rows) - --- Plan should be a subquery TID scan -EXPLAIN (COSTS OFF) UPDATE current_check SET payload = payload WHERE CURRENT OF current_check_cursor; - QUERY PLAN -------------------------------------------------------------- - Update on current_check - -> Tid Scan on current_check - TID Cond: CURRENT OF current_check_cursor - Filter: ((currentid = 4) AND ((currentid % 2) = 0)) -(4 rows) - --- Similarly can only delete row 4 -FETCH ABSOLUTE 1 FROM current_check_cursor; - currentid | payload | rlsuser ------------+---------+----------------- - 2 | bcd | regress_rls_bob -(1 row) - -DELETE FROM current_check WHERE CURRENT OF current_check_cursor RETURNING *; - currentid | payload | rlsuser ------------+---------+--------- -(0 rows) - -FETCH RELATIVE 1 FROM current_check_cursor; - currentid | payload | rlsuser ------------+---------+----------------- - 4 | def | regress_rls_bob -(1 row) - -DELETE FROM current_check WHERE CURRENT OF current_check_cursor RETURNING *; - currentid | payload | rlsuser ------------+---------+----------------- - 4 | def_new | regress_rls_bob -(1 row) - -SELECT * FROM current_check; - currentid | payload | rlsuser ------------+---------+----------------- - 2 | bcd | regress_rls_bob -(1 row) - -COMMIT; --- Check that RLS filters that are tidquals don't override WHERE CURRENT OF -BEGIN; -CREATE TABLE current_check_2 (a int, b text); -INSERT INTO current_check_2 VALUES (1, 'Apple'); -ALTER TABLE current_check_2 ENABLE ROW LEVEL SECURITY; -ALTER TABLE current_check_2 FORCE ROW LEVEL SECURITY; --- policy must accept ctid = (InvalidBlockNumber,0) since updates check it --- before assigning a ctid to the new row -CREATE POLICY p1 ON current_check_2 AS PERMISSIVE - USING (ctid IN ('(0,1)', '(0,2)', '(4294967295,0)')); -SELECT ctid, * FROM current_check_2; - ctid | a | b --------+---+------- - (0,1) | 1 | Apple -(1 row) - -DECLARE current_check_cursor CURSOR FOR SELECT * FROM current_check_2; -FETCH FROM current_check_cursor; - a | b ----+------- - 1 | Apple -(1 row) - -EXPLAIN (COSTS OFF) -UPDATE current_check_2 SET b = 'Manzana' WHERE CURRENT OF current_check_cursor; - QUERY PLAN ----------------------------------------------------------------------------- - Update on current_check_2 - -> Tid Scan on current_check_2 - TID Cond: CURRENT OF current_check_cursor - Filter: (ctid = ANY ('{"(0,1)","(0,2)","(4294967295,0)"}'::tid[])) -(4 rows) - -UPDATE current_check_2 SET b = 'Manzana' WHERE CURRENT OF current_check_cursor; -SELECT ctid, * FROM current_check_2; - ctid | a | b --------+---+--------- - (0,2) | 1 | Manzana -(1 row) - -ROLLBACK; --- --- check pg_stats view filtering --- -SET row_security TO ON; -SET SESSION AUTHORIZATION regress_rls_alice; -ANALYZE current_check; --- Stats visible -SELECT row_security_active('current_check'); - row_security_active ---------------------- - f -(1 row) - -SELECT attname, most_common_vals FROM pg_stats - WHERE tablename = 'current_check' - ORDER BY 1; - attname | most_common_vals ------------+------------------- - currentid | - payload | - rlsuser | {regress_rls_bob} -(3 rows) - -SET SESSION AUTHORIZATION regress_rls_bob; --- Stats not visible -SELECT row_security_active('current_check'); - row_security_active ---------------------- - t -(1 row) - -SELECT attname, most_common_vals FROM pg_stats - WHERE tablename = 'current_check' - ORDER BY 1; - attname | most_common_vals ----------+------------------ -(0 rows) - --- --- Collation support --- -BEGIN; -CREATE TABLE coll_t (c) AS VALUES ('bar'::text); -CREATE POLICY coll_p ON coll_t USING (c < ('foo'::text COLLATE "C")); -ALTER TABLE coll_t ENABLE ROW LEVEL SECURITY; -GRANT SELECT ON coll_t TO regress_rls_alice; -SELECT (string_to_array(polqual, ':'))[7] AS inputcollid FROM pg_policy WHERE polrelid = 'coll_t'::regclass; - inputcollid ------------------- - inputcollid 950 -(1 row) - -SET SESSION AUTHORIZATION regress_rls_alice; -SELECT * FROM coll_t; - c ------ - bar -(1 row) - -ROLLBACK; --- --- Shared Object Dependencies --- -RESET SESSION AUTHORIZATION; -BEGIN; -CREATE ROLE regress_rls_eve; -CREATE ROLE regress_rls_frank; -CREATE TABLE tbl1 (c) AS VALUES ('bar'::text); -GRANT SELECT ON TABLE tbl1 TO regress_rls_eve; -CREATE POLICY P ON tbl1 TO regress_rls_eve, regress_rls_frank USING (true); -SELECT refclassid::regclass, deptype - FROM pg_depend - WHERE classid = 'pg_policy'::regclass - AND refobjid = 'tbl1'::regclass; - refclassid | deptype -------------+--------- - pg_class | a -(1 row) - -SELECT refclassid::regclass, deptype - FROM pg_shdepend - WHERE classid = 'pg_policy'::regclass - AND refobjid IN ('regress_rls_eve'::regrole, 'regress_rls_frank'::regrole); - refclassid | deptype -------------+--------- - pg_authid | r - pg_authid | r -(2 rows) - -SAVEPOINT q; -DROP ROLE regress_rls_eve; --fails due to dependency on POLICY p -ERROR: role "regress_rls_eve" cannot be dropped because some objects depend on it -DETAIL: privileges for table tbl1 -target of policy p on table tbl1 -ROLLBACK TO q; -ALTER POLICY p ON tbl1 TO regress_rls_frank USING (true); -SAVEPOINT q; -DROP ROLE regress_rls_eve; --fails due to dependency on GRANT SELECT -ERROR: role "regress_rls_eve" cannot be dropped because some objects depend on it -DETAIL: privileges for table tbl1 -ROLLBACK TO q; -REVOKE ALL ON TABLE tbl1 FROM regress_rls_eve; -SAVEPOINT q; -DROP ROLE regress_rls_eve; --succeeds -ROLLBACK TO q; -SAVEPOINT q; -DROP ROLE regress_rls_frank; --fails due to dependency on POLICY p -ERROR: role "regress_rls_frank" cannot be dropped because some objects depend on it -DETAIL: target of policy p on table tbl1 -ROLLBACK TO q; -DROP POLICY p ON tbl1; -SAVEPOINT q; -DROP ROLE regress_rls_frank; -- succeeds -ROLLBACK TO q; -ROLLBACK; -- cleanup --- --- Policy expression handling --- -BEGIN; -CREATE TABLE t (c) AS VALUES ('bar'::text); -CREATE POLICY p ON t USING (max(c)); -- fails: aggregate functions are not allowed in policy expressions -ERROR: aggregate functions are not allowed in policy expressions -ROLLBACK; --- --- Non-target relations are only subject to SELECT policies --- -SET SESSION AUTHORIZATION regress_rls_alice; -CREATE TABLE r1 (a int); -CREATE TABLE r2 (a int); -INSERT INTO r1 VALUES (10), (20); -INSERT INTO r2 VALUES (10), (20); -GRANT ALL ON r1, r2 TO regress_rls_bob; -CREATE POLICY p1 ON r1 USING (true); -ALTER TABLE r1 ENABLE ROW LEVEL SECURITY; -CREATE POLICY p1 ON r2 FOR SELECT USING (true); -CREATE POLICY p2 ON r2 FOR INSERT WITH CHECK (false); -CREATE POLICY p3 ON r2 FOR UPDATE USING (false); -CREATE POLICY p4 ON r2 FOR DELETE USING (false); -ALTER TABLE r2 ENABLE ROW LEVEL SECURITY; -SET SESSION AUTHORIZATION regress_rls_bob; -SELECT * FROM r1; - a ----- - 10 - 20 -(2 rows) - -SELECT * FROM r2; - a ----- - 10 - 20 -(2 rows) - --- r2 is read-only -INSERT INTO r2 VALUES (2); -- Not allowed -ERROR: new row violates row-level security policy for table "r2" -UPDATE r2 SET a = 2 RETURNING *; -- Updates nothing - a ---- -(0 rows) - -DELETE FROM r2 RETURNING *; -- Deletes nothing - a ---- -(0 rows) - --- r2 can be used as a non-target relation in DML -INSERT INTO r1 SELECT a + 1 FROM r2 RETURNING *; -- OK - a ----- - 11 - 21 -(2 rows) - -UPDATE r1 SET a = r2.a + 2 FROM r2 WHERE r1.a = r2.a RETURNING *; -- OK - a | a -----+---- - 12 | 10 - 22 | 20 -(2 rows) - -DELETE FROM r1 USING r2 WHERE r1.a = r2.a + 2 RETURNING *; -- OK - a | a -----+---- - 12 | 10 - 22 | 20 -(2 rows) - -SELECT * FROM r1; - a ----- - 11 - 21 -(2 rows) - -SELECT * FROM r2; - a ----- - 10 - 20 -(2 rows) - -SET SESSION AUTHORIZATION regress_rls_alice; -DROP TABLE r1; -DROP TABLE r2; --- --- FORCE ROW LEVEL SECURITY applies RLS to owners too --- -SET SESSION AUTHORIZATION regress_rls_alice; -SET row_security = on; -CREATE TABLE r1 (a int); -INSERT INTO r1 VALUES (10), (20); -CREATE POLICY p1 ON r1 USING (false); -ALTER TABLE r1 ENABLE ROW LEVEL SECURITY; -ALTER TABLE r1 FORCE ROW LEVEL SECURITY; --- No error, but no rows -TABLE r1; - a ---- -(0 rows) - --- RLS error -INSERT INTO r1 VALUES (1); -ERROR: new row violates row-level security policy for table "r1" --- No error (unable to see any rows to update) -UPDATE r1 SET a = 1; -TABLE r1; - a ---- -(0 rows) - --- No error (unable to see any rows to delete) -DELETE FROM r1; -TABLE r1; - a ---- -(0 rows) - -SET row_security = off; --- these all fail, would be affected by RLS -TABLE r1; -ERROR: query would be affected by row-level security policy for table "r1" -HINT: To disable the policy for the table's owner, use ALTER TABLE NO FORCE ROW LEVEL SECURITY. -UPDATE r1 SET a = 1; -ERROR: query would be affected by row-level security policy for table "r1" -HINT: To disable the policy for the table's owner, use ALTER TABLE NO FORCE ROW LEVEL SECURITY. -DELETE FROM r1; -ERROR: query would be affected by row-level security policy for table "r1" -HINT: To disable the policy for the table's owner, use ALTER TABLE NO FORCE ROW LEVEL SECURITY. -DROP TABLE r1; --- --- FORCE ROW LEVEL SECURITY does not break RI --- -SET SESSION AUTHORIZATION regress_rls_alice; -SET row_security = on; -CREATE TABLE r1 (a int PRIMARY KEY); -CREATE TABLE r2 (a int REFERENCES r1); -INSERT INTO r1 VALUES (10), (20); -INSERT INTO r2 VALUES (10), (20); --- Create policies on r2 which prevent the --- owner from seeing any rows, but RI should --- still see them. -CREATE POLICY p1 ON r2 USING (false); -ALTER TABLE r2 ENABLE ROW LEVEL SECURITY; -ALTER TABLE r2 FORCE ROW LEVEL SECURITY; --- Errors due to rows in r2 -DELETE FROM r1; -ERROR: update or delete on table "r1" violates foreign key constraint "r2_a_fkey" on table "r2" -DETAIL: Key (a)=(10) is still referenced from table "r2". --- Reset r2 to no-RLS -DROP POLICY p1 ON r2; -ALTER TABLE r2 NO FORCE ROW LEVEL SECURITY; -ALTER TABLE r2 DISABLE ROW LEVEL SECURITY; --- clean out r2 for INSERT test below -DELETE FROM r2; --- Change r1 to not allow rows to be seen -CREATE POLICY p1 ON r1 USING (false); -ALTER TABLE r1 ENABLE ROW LEVEL SECURITY; -ALTER TABLE r1 FORCE ROW LEVEL SECURITY; --- No rows seen -TABLE r1; - a ---- -(0 rows) - --- No error, RI still sees that row exists in r1 -INSERT INTO r2 VALUES (10); -DROP TABLE r2; -DROP TABLE r1; --- Ensure cascaded DELETE works -CREATE TABLE r1 (a int PRIMARY KEY); -CREATE TABLE r2 (a int REFERENCES r1 ON DELETE CASCADE); -INSERT INTO r1 VALUES (10), (20); -INSERT INTO r2 VALUES (10), (20); --- Create policies on r2 which prevent the --- owner from seeing any rows, but RI should --- still see them. -CREATE POLICY p1 ON r2 USING (false); -ALTER TABLE r2 ENABLE ROW LEVEL SECURITY; -ALTER TABLE r2 FORCE ROW LEVEL SECURITY; --- Deletes all records from both -DELETE FROM r1; --- Remove FORCE from r2 -ALTER TABLE r2 NO FORCE ROW LEVEL SECURITY; --- As owner, we now bypass RLS --- verify no rows in r2 now -TABLE r2; - a ---- -(0 rows) - -DROP TABLE r2; -DROP TABLE r1; --- Ensure cascaded UPDATE works -CREATE TABLE r1 (a int PRIMARY KEY); -CREATE TABLE r2 (a int REFERENCES r1 ON UPDATE CASCADE); -INSERT INTO r1 VALUES (10), (20); -INSERT INTO r2 VALUES (10), (20); --- Create policies on r2 which prevent the --- owner from seeing any rows, but RI should --- still see them. -CREATE POLICY p1 ON r2 USING (false); -ALTER TABLE r2 ENABLE ROW LEVEL SECURITY; -ALTER TABLE r2 FORCE ROW LEVEL SECURITY; --- Updates records in both -UPDATE r1 SET a = a+5; --- Remove FORCE from r2 -ALTER TABLE r2 NO FORCE ROW LEVEL SECURITY; --- As owner, we now bypass RLS --- verify records in r2 updated -TABLE r2; - a ----- - 15 - 25 -(2 rows) - -DROP TABLE r2; -DROP TABLE r1; --- --- Test INSERT+RETURNING applies SELECT policies as --- WithCheckOptions (meaning an error is thrown) --- -SET SESSION AUTHORIZATION regress_rls_alice; -SET row_security = on; -CREATE TABLE r1 (a int); -CREATE POLICY p1 ON r1 FOR SELECT USING (false); -CREATE POLICY p2 ON r1 FOR INSERT WITH CHECK (true); -ALTER TABLE r1 ENABLE ROW LEVEL SECURITY; -ALTER TABLE r1 FORCE ROW LEVEL SECURITY; --- Works fine -INSERT INTO r1 VALUES (10), (20); --- No error, but no rows -TABLE r1; - a ---- -(0 rows) - -SET row_security = off; --- fail, would be affected by RLS -TABLE r1; -ERROR: query would be affected by row-level security policy for table "r1" -HINT: To disable the policy for the table's owner, use ALTER TABLE NO FORCE ROW LEVEL SECURITY. -SET row_security = on; --- Error -INSERT INTO r1 VALUES (10), (20) RETURNING *; -ERROR: new row violates row-level security policy for table "r1" -DROP TABLE r1; --- --- Test UPDATE+RETURNING applies SELECT policies as --- WithCheckOptions (meaning an error is thrown) --- -SET SESSION AUTHORIZATION regress_rls_alice; -SET row_security = on; -CREATE TABLE r1 (a int PRIMARY KEY); -CREATE POLICY p1 ON r1 FOR SELECT USING (a < 20); -CREATE POLICY p2 ON r1 FOR UPDATE USING (a < 20) WITH CHECK (true); -CREATE POLICY p3 ON r1 FOR INSERT WITH CHECK (true); -INSERT INTO r1 VALUES (10); -ALTER TABLE r1 ENABLE ROW LEVEL SECURITY; -ALTER TABLE r1 FORCE ROW LEVEL SECURITY; --- Works fine -UPDATE r1 SET a = 30; --- Show updated rows -ALTER TABLE r1 NO FORCE ROW LEVEL SECURITY; -TABLE r1; - a ----- - 30 -(1 row) - --- reset value in r1 for test with RETURNING -UPDATE r1 SET a = 10; --- Verify row reset -TABLE r1; - a ----- - 10 -(1 row) - -ALTER TABLE r1 FORCE ROW LEVEL SECURITY; --- Error -UPDATE r1 SET a = 30 RETURNING *; -ERROR: new row violates row-level security policy for table "r1" --- UPDATE path of INSERT ... ON CONFLICT DO UPDATE should also error out -INSERT INTO r1 VALUES (10) - ON CONFLICT (a) DO UPDATE SET a = 30 RETURNING *; -ERROR: new row violates row-level security policy for table "r1" --- Should still error out without RETURNING (use of arbiter always requires --- SELECT permissions) -INSERT INTO r1 VALUES (10) - ON CONFLICT (a) DO UPDATE SET a = 30; -ERROR: new row violates row-level security policy for table "r1" -INSERT INTO r1 VALUES (10) - ON CONFLICT ON CONSTRAINT r1_pkey DO UPDATE SET a = 30; -ERROR: new row violates row-level security policy for table "r1" -DROP TABLE r1; --- Check dependency handling -RESET SESSION AUTHORIZATION; -CREATE TABLE dep1 (c1 int); -CREATE TABLE dep2 (c1 int); -CREATE POLICY dep_p1 ON dep1 TO regress_rls_bob USING (c1 > (select max(dep2.c1) from dep2)); -ALTER POLICY dep_p1 ON dep1 TO regress_rls_bob,regress_rls_carol; --- Should return one -SELECT count(*) = 1 FROM pg_depend - WHERE objid = (SELECT oid FROM pg_policy WHERE polname = 'dep_p1') - AND refobjid = (SELECT oid FROM pg_class WHERE relname = 'dep2'); - ?column? ----------- - t -(1 row) - -ALTER POLICY dep_p1 ON dep1 USING (true); --- Should return one -SELECT count(*) = 1 FROM pg_shdepend - WHERE objid = (SELECT oid FROM pg_policy WHERE polname = 'dep_p1') - AND refobjid = (SELECT oid FROM pg_authid WHERE rolname = 'regress_rls_bob'); - ?column? ----------- - t -(1 row) - --- Should return one -SELECT count(*) = 1 FROM pg_shdepend - WHERE objid = (SELECT oid FROM pg_policy WHERE polname = 'dep_p1') - AND refobjid = (SELECT oid FROM pg_authid WHERE rolname = 'regress_rls_carol'); - ?column? ----------- - t -(1 row) - --- Should return zero -SELECT count(*) = 0 FROM pg_depend - WHERE objid = (SELECT oid FROM pg_policy WHERE polname = 'dep_p1') - AND refobjid = (SELECT oid FROM pg_class WHERE relname = 'dep2'); - ?column? ----------- - t -(1 row) - --- DROP OWNED BY testing -RESET SESSION AUTHORIZATION; -CREATE ROLE regress_rls_dob_role1; -CREATE ROLE regress_rls_dob_role2; -CREATE TABLE dob_t1 (c1 int); -CREATE TABLE dob_t2 (c1 int) PARTITION BY RANGE (c1); -CREATE POLICY p1 ON dob_t1 TO regress_rls_dob_role1 USING (true); -DROP OWNED BY regress_rls_dob_role1; -DROP POLICY p1 ON dob_t1; -- should fail, already gone -ERROR: policy "p1" for table "dob_t1" does not exist -CREATE POLICY p1 ON dob_t1 TO regress_rls_dob_role1,regress_rls_dob_role2 USING (true); -DROP OWNED BY regress_rls_dob_role1; -DROP POLICY p1 ON dob_t1; -- should succeed --- same cases with duplicate polroles entries -CREATE POLICY p1 ON dob_t1 TO regress_rls_dob_role1,regress_rls_dob_role1 USING (true); -DROP OWNED BY regress_rls_dob_role1; -DROP POLICY p1 ON dob_t1; -- should fail, already gone -ERROR: policy "p1" for table "dob_t1" does not exist -CREATE POLICY p1 ON dob_t1 TO regress_rls_dob_role1,regress_rls_dob_role1,regress_rls_dob_role2 USING (true); -DROP OWNED BY regress_rls_dob_role1; -DROP POLICY p1 ON dob_t1; -- should succeed --- partitioned target -CREATE POLICY p1 ON dob_t2 TO regress_rls_dob_role1,regress_rls_dob_role2 USING (true); -DROP OWNED BY regress_rls_dob_role1; -DROP POLICY p1 ON dob_t2; -- should succeed -DROP USER regress_rls_dob_role1; -DROP USER regress_rls_dob_role2; --- Bug #15708: view + table with RLS should check policies as view owner -CREATE TABLE ref_tbl (a int); -INSERT INTO ref_tbl VALUES (1); -CREATE TABLE rls_tbl (a int); -INSERT INTO rls_tbl VALUES (10); -ALTER TABLE rls_tbl ENABLE ROW LEVEL SECURITY; -CREATE POLICY p1 ON rls_tbl USING (EXISTS (SELECT 1 FROM ref_tbl)); -GRANT SELECT ON ref_tbl TO regress_rls_bob; -GRANT SELECT ON rls_tbl TO regress_rls_bob; -CREATE VIEW rls_view AS SELECT * FROM rls_tbl; -ALTER VIEW rls_view OWNER TO regress_rls_bob; -GRANT SELECT ON rls_view TO regress_rls_alice; -SET SESSION AUTHORIZATION regress_rls_alice; -SELECT * FROM ref_tbl; -- Permission denied -ERROR: permission denied for table ref_tbl -SELECT * FROM rls_tbl; -- Permission denied -ERROR: permission denied for table rls_tbl -SELECT * FROM rls_view; -- OK - a ----- - 10 -(1 row) - -RESET SESSION AUTHORIZATION; -DROP VIEW rls_view; -DROP TABLE rls_tbl; -DROP TABLE ref_tbl; --- Leaky operator test -CREATE TABLE rls_tbl (a int); -INSERT INTO rls_tbl SELECT x/10 FROM generate_series(1, 100) x; -ANALYZE rls_tbl; -ALTER TABLE rls_tbl ENABLE ROW LEVEL SECURITY; -GRANT SELECT ON rls_tbl TO regress_rls_alice; -SET SESSION AUTHORIZATION regress_rls_alice; -CREATE FUNCTION op_leak(int, int) RETURNS bool - AS 'BEGIN RAISE NOTICE ''op_leak => %, %'', $1, $2; RETURN $1 < $2; END' - LANGUAGE plpgsql; -CREATE OPERATOR <<< (procedure = op_leak, leftarg = int, rightarg = int, - restrict = scalarltsel); -SELECT * FROM rls_tbl WHERE a <<< 1000; - a ---- -(0 rows) - -DROP OPERATOR <<< (int, int); -DROP FUNCTION op_leak(int, int); -RESET SESSION AUTHORIZATION; -DROP TABLE rls_tbl; --- Bug #16006: whole-row Vars in a policy don't play nice with sub-selects -SET SESSION AUTHORIZATION regress_rls_alice; -CREATE TABLE rls_tbl (a int, b int, c int); -CREATE POLICY p1 ON rls_tbl USING (rls_tbl >= ROW(1,1,1)); -ALTER TABLE rls_tbl ENABLE ROW LEVEL SECURITY; -ALTER TABLE rls_tbl FORCE ROW LEVEL SECURITY; -INSERT INTO rls_tbl SELECT 10, 20, 30; -EXPLAIN (VERBOSE, COSTS OFF) -INSERT INTO rls_tbl - SELECT * FROM (SELECT b, c FROM rls_tbl ORDER BY a) ss; - QUERY PLAN --------------------------------------------------------------------- - Insert on regress_rls_schema.rls_tbl - -> Subquery Scan on ss - Output: ss.b, ss.c, NULL::integer - -> Sort - Output: rls_tbl_1.b, rls_tbl_1.c, rls_tbl_1.a - Sort Key: rls_tbl_1.a - -> Seq Scan on regress_rls_schema.rls_tbl rls_tbl_1 - Output: rls_tbl_1.b, rls_tbl_1.c, rls_tbl_1.a - Filter: (rls_tbl_1.* >= '(1,1,1)'::record) -(9 rows) - -INSERT INTO rls_tbl - SELECT * FROM (SELECT b, c FROM rls_tbl ORDER BY a) ss; -SELECT * FROM rls_tbl; - a | b | c -----+----+---- - 10 | 20 | 30 - 20 | 30 | -(2 rows) - -DROP TABLE rls_tbl; -RESET SESSION AUTHORIZATION; --- CVE-2023-2455: inlining an SRF may introduce an RLS dependency -create table rls_t (c text); -insert into rls_t values ('invisible to bob'); -alter table rls_t enable row level security; -grant select on rls_t to regress_rls_alice, regress_rls_bob; -create policy p1 on rls_t for select to regress_rls_alice using (true); -create policy p2 on rls_t for select to regress_rls_bob using (false); -create function rls_f () returns setof rls_t - stable language sql - as $$ select * from rls_t $$; -prepare q as select current_user, * from rls_f(); -set role regress_rls_alice; -execute q; - current_user | c --------------------+------------------ - regress_rls_alice | invisible to bob -(1 row) - -set role regress_rls_bob; -execute q; - current_user | c ---------------+--- -(0 rows) - -RESET ROLE; -DROP FUNCTION rls_f(); -DROP TABLE rls_t; --- --- Clean up objects --- -RESET SESSION AUTHORIZATION; -DROP SCHEMA regress_rls_schema CASCADE; -NOTICE: drop cascades to 30 other objects -DETAIL: drop cascades to function f_leak(text) -drop cascades to table uaccount -drop cascades to table category -drop cascades to table document -drop cascades to table part_document -drop cascades to table dependent -drop cascades to table rec1 -drop cascades to table rec2 -drop cascades to view rec1v -drop cascades to view rec2v -drop cascades to table s1 -drop cascades to table s2 -drop cascades to view v2 -drop cascades to table b1 -drop cascades to view bv1 -drop cascades to table z1 -drop cascades to table z2 -drop cascades to table z1_blacklist -drop cascades to table x1 -drop cascades to table y1 -drop cascades to table y2 -drop cascades to table t1 -drop cascades to table t2 -drop cascades to table t3 -drop cascades to table t4 -drop cascades to table current_check -drop cascades to table dep1 -drop cascades to table dep2 -drop cascades to table dob_t1 -drop cascades to table dob_t2 -DROP USER regress_rls_alice; -DROP USER regress_rls_bob; -DROP USER regress_rls_carol; -DROP USER regress_rls_dave; -DROP USER regress_rls_exempt_user; -DROP ROLE regress_rls_group1; -DROP ROLE regress_rls_group2; --- Arrange to have a few policies left over, for testing --- pg_dump/pg_restore -CREATE SCHEMA regress_rls_schema; -CREATE TABLE rls_tbl (c1 int); -ALTER TABLE rls_tbl ENABLE ROW LEVEL SECURITY; -CREATE POLICY p1 ON rls_tbl USING (c1 > 5); -CREATE POLICY p2 ON rls_tbl FOR SELECT USING (c1 <= 3); -CREATE POLICY p3 ON rls_tbl FOR UPDATE USING (c1 <= 3) WITH CHECK (c1 > 5); -CREATE POLICY p4 ON rls_tbl FOR DELETE USING (c1 <= 3); -CREATE TABLE rls_tbl_force (c1 int); -ALTER TABLE rls_tbl_force ENABLE ROW LEVEL SECURITY; -ALTER TABLE rls_tbl_force FORCE ROW LEVEL SECURITY; -CREATE POLICY p1 ON rls_tbl_force USING (c1 = 5) WITH CHECK (c1 < 5); -CREATE POLICY p2 ON rls_tbl_force FOR SELECT USING (c1 = 8); -CREATE POLICY p3 ON rls_tbl_force FOR UPDATE USING (c1 = 8) WITH CHECK (c1 >= 5); -CREATE POLICY p4 ON rls_tbl_force FOR DELETE USING (c1 = 8); +psql: error: connection to server on socket "/tmp/pg_regress-03Az7d/.s.PGSQL.40051" failed: FATAL: the database system is not yet accepting connections +DETAIL: Consistent recovery state has not been yet reached. diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/object_address.out /tmp/cirrus-ci-build/build/testrun/regress/regress/results/object_address.out --- /tmp/cirrus-ci-build/src/test/regress/expected/object_address.out 2024-09-20 21:41:45.722024522 +0000 +++ /tmp/cirrus-ci-build/build/testrun/regress/regress/results/object_address.out 2024-09-20 21:46:06.621623530 +0000 @@ -1,638 +1,2 @@ --- --- Test for pg_get_object_address --- --- Clean up in case a prior regression run failed -SET client_min_messages TO 'warning'; -DROP ROLE IF EXISTS regress_addr_user; -RESET client_min_messages; -CREATE USER regress_addr_user; --- Test generic object addressing/identification functions -CREATE SCHEMA addr_nsp; -SET search_path TO 'addr_nsp'; -CREATE FOREIGN DATA WRAPPER addr_fdw; -CREATE SERVER addr_fserv FOREIGN DATA WRAPPER addr_fdw; -CREATE TEXT SEARCH DICTIONARY addr_ts_dict (template=simple); -CREATE TEXT SEARCH CONFIGURATION addr_ts_conf (copy=english); -CREATE TEXT SEARCH TEMPLATE addr_ts_temp (lexize=dsimple_lexize); -CREATE TEXT SEARCH PARSER addr_ts_prs - (start = prsd_start, gettoken = prsd_nexttoken, end = prsd_end, lextypes = prsd_lextype); -CREATE TABLE addr_nsp.gentable ( - a serial primary key CONSTRAINT a_chk CHECK (a > 0), - b text DEFAULT 'hello' -); -CREATE TABLE addr_nsp.parttable ( - a int PRIMARY KEY -) PARTITION BY RANGE (a); -CREATE VIEW addr_nsp.genview AS SELECT * from addr_nsp.gentable; -CREATE MATERIALIZED VIEW addr_nsp.genmatview AS SELECT * FROM addr_nsp.gentable; -CREATE TYPE addr_nsp.gencomptype AS (a int); -CREATE TYPE addr_nsp.genenum AS ENUM ('one', 'two'); -CREATE FOREIGN TABLE addr_nsp.genftable (a int) SERVER addr_fserv; -CREATE AGGREGATE addr_nsp.genaggr(int4) (sfunc = int4pl, stype = int4); -CREATE DOMAIN addr_nsp.gendomain AS int4 CONSTRAINT domconstr CHECK (value > 0); -CREATE FUNCTION addr_nsp.trig() RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN END; $$; -CREATE TRIGGER t BEFORE INSERT ON addr_nsp.gentable FOR EACH ROW EXECUTE PROCEDURE addr_nsp.trig(); -CREATE POLICY genpol ON addr_nsp.gentable; -CREATE PROCEDURE addr_nsp.proc(int4) LANGUAGE SQL AS $$ $$; -CREATE SERVER "integer" FOREIGN DATA WRAPPER addr_fdw; -CREATE USER MAPPING FOR regress_addr_user SERVER "integer"; -ALTER DEFAULT PRIVILEGES FOR ROLE regress_addr_user IN SCHEMA public GRANT ALL ON TABLES TO regress_addr_user; -ALTER DEFAULT PRIVILEGES FOR ROLE regress_addr_user REVOKE DELETE ON TABLES FROM regress_addr_user; --- this transform would be quite unsafe to leave lying around, --- except that the SQL language pays no attention to transforms: -CREATE TRANSFORM FOR int LANGUAGE SQL ( - FROM SQL WITH FUNCTION prsd_lextype(internal), - TO SQL WITH FUNCTION int4recv(internal)); --- suppress warning that depends on wal_level -SET client_min_messages = 'ERROR'; -CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable; -CREATE PUBLICATION addr_pub_schema FOR TABLES IN SCHEMA addr_nsp; -RESET client_min_messages; -CREATE SUBSCRIPTION regress_addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE); -WARNING: subscription was created, but is not connected -HINT: To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription. -CREATE STATISTICS addr_nsp.gentable_stat ON a, b FROM addr_nsp.gentable; --- test some error cases -SELECT pg_get_object_address('stone', '{}', '{}'); -ERROR: unrecognized object type "stone" -SELECT pg_get_object_address('table', '{}', '{}'); -ERROR: name list length must be at least 1 -SELECT pg_get_object_address('table', '{NULL}', '{}'); -ERROR: name or argument lists may not contain nulls --- unrecognized object types -DO $$ -DECLARE - objtype text; -BEGIN - FOR objtype IN VALUES ('toast table'), ('index column'), ('sequence column'), - ('toast table column'), ('view column'), ('materialized view column') - LOOP - BEGIN - PERFORM pg_get_object_address(objtype, '{one}', '{}'); - EXCEPTION WHEN invalid_parameter_value THEN - RAISE WARNING 'error for %: %', objtype, sqlerrm; - END; - END LOOP; -END; -$$; -WARNING: error for toast table: unsupported object type "toast table" -WARNING: error for index column: unsupported object type "index column" -WARNING: error for sequence column: unsupported object type "sequence column" -WARNING: error for toast table column: unsupported object type "toast table column" -WARNING: error for view column: unsupported object type "view column" -WARNING: error for materialized view column: unsupported object type "materialized view column" --- miscellaneous other errors -select * from pg_get_object_address('operator of access method', '{btree,integer_ops,1}', '{int4,bool}'); -ERROR: operator 1 (int4, bool) of operator family integer_ops for access method btree does not exist -select * from pg_get_object_address('operator of access method', '{btree,integer_ops,99}', '{int4,int4}'); -ERROR: operator 99 (int4, int4) of operator family integer_ops for access method btree does not exist -select * from pg_get_object_address('function of access method', '{btree,integer_ops,1}', '{int4,bool}'); -ERROR: function 1 (int4, bool) of operator family integer_ops for access method btree does not exist -select * from pg_get_object_address('function of access method', '{btree,integer_ops,99}', '{int4,int4}'); -ERROR: function 99 (int4, int4) of operator family integer_ops for access method btree does not exist -DO $$ -DECLARE - objtype text; - names text[]; - args text[]; -BEGIN - FOR objtype IN VALUES - ('table'), ('index'), ('sequence'), ('view'), - ('materialized view'), ('foreign table'), - ('table column'), ('foreign table column'), - ('aggregate'), ('function'), ('procedure'), ('type'), ('cast'), - ('table constraint'), ('domain constraint'), ('conversion'), ('default value'), - ('operator'), ('operator class'), ('operator family'), ('rule'), ('trigger'), - ('text search parser'), ('text search dictionary'), - ('text search template'), ('text search configuration'), - ('policy'), ('user mapping'), ('default acl'), ('transform'), - ('operator of access method'), ('function of access method'), - ('publication namespace'), ('publication relation') - LOOP - FOR names IN VALUES ('{eins}'), ('{addr_nsp, zwei}'), ('{eins, zwei, drei}') - LOOP - FOR args IN VALUES ('{}'), ('{integer}') - LOOP - BEGIN - PERFORM pg_get_object_address(objtype, names, args); - EXCEPTION WHEN OTHERS THEN - RAISE WARNING 'error for %,%,%: %', objtype, names, args, sqlerrm; - END; - END LOOP; - END LOOP; - END LOOP; -END; -$$; -WARNING: error for table,{eins},{}: relation "eins" does not exist -WARNING: error for table,{eins},{integer}: relation "eins" does not exist -WARNING: error for table,{addr_nsp,zwei},{}: relation "addr_nsp.zwei" does not exist -WARNING: error for table,{addr_nsp,zwei},{integer}: relation "addr_nsp.zwei" does not exist -WARNING: error for table,{eins,zwei,drei},{}: cross-database references are not implemented: "eins.zwei.drei" -WARNING: error for table,{eins,zwei,drei},{integer}: cross-database references are not implemented: "eins.zwei.drei" -WARNING: error for index,{eins},{}: relation "eins" does not exist -WARNING: error for index,{eins},{integer}: relation "eins" does not exist -WARNING: error for index,{addr_nsp,zwei},{}: relation "addr_nsp.zwei" does not exist -WARNING: error for index,{addr_nsp,zwei},{integer}: relation "addr_nsp.zwei" does not exist -WARNING: error for index,{eins,zwei,drei},{}: cross-database references are not implemented: "eins.zwei.drei" -WARNING: error for index,{eins,zwei,drei},{integer}: cross-database references are not implemented: "eins.zwei.drei" -WARNING: error for sequence,{eins},{}: relation "eins" does not exist -WARNING: error for sequence,{eins},{integer}: relation "eins" does not exist -WARNING: error for sequence,{addr_nsp,zwei},{}: relation "addr_nsp.zwei" does not exist -WARNING: error for sequence,{addr_nsp,zwei},{integer}: relation "addr_nsp.zwei" does not exist -WARNING: error for sequence,{eins,zwei,drei},{}: cross-database references are not implemented: "eins.zwei.drei" -WARNING: error for sequence,{eins,zwei,drei},{integer}: cross-database references are not implemented: "eins.zwei.drei" -WARNING: error for view,{eins},{}: relation "eins" does not exist -WARNING: error for view,{eins},{integer}: relation "eins" does not exist -WARNING: error for view,{addr_nsp,zwei},{}: relation "addr_nsp.zwei" does not exist -WARNING: error for view,{addr_nsp,zwei},{integer}: relation "addr_nsp.zwei" does not exist -WARNING: error for view,{eins,zwei,drei},{}: cross-database references are not implemented: "eins.zwei.drei" -WARNING: error for view,{eins,zwei,drei},{integer}: cross-database references are not implemented: "eins.zwei.drei" -WARNING: error for materialized view,{eins},{}: relation "eins" does not exist -WARNING: error for materialized view,{eins},{integer}: relation "eins" does not exist -WARNING: error for materialized view,{addr_nsp,zwei},{}: relation "addr_nsp.zwei" does not exist -WARNING: error for materialized view,{addr_nsp,zwei},{integer}: relation "addr_nsp.zwei" does not exist -WARNING: error for materialized view,{eins,zwei,drei},{}: cross-database references are not implemented: "eins.zwei.drei" -WARNING: error for materialized view,{eins,zwei,drei},{integer}: cross-database references are not implemented: "eins.zwei.drei" -WARNING: error for foreign table,{eins},{}: relation "eins" does not exist -WARNING: error for foreign table,{eins},{integer}: relation "eins" does not exist -WARNING: error for foreign table,{addr_nsp,zwei},{}: relation "addr_nsp.zwei" does not exist -WARNING: error for foreign table,{addr_nsp,zwei},{integer}: relation "addr_nsp.zwei" does not exist -WARNING: error for foreign table,{eins,zwei,drei},{}: cross-database references are not implemented: "eins.zwei.drei" -WARNING: error for foreign table,{eins,zwei,drei},{integer}: cross-database references are not implemented: "eins.zwei.drei" -WARNING: error for table column,{eins},{}: column name must be qualified -WARNING: error for table column,{eins},{integer}: column name must be qualified -WARNING: error for table column,{addr_nsp,zwei},{}: relation "addr_nsp" does not exist -WARNING: error for table column,{addr_nsp,zwei},{integer}: relation "addr_nsp" does not exist -WARNING: error for table column,{eins,zwei,drei},{}: schema "eins" does not exist -WARNING: error for table column,{eins,zwei,drei},{integer}: schema "eins" does not exist -WARNING: error for foreign table column,{eins},{}: column name must be qualified -WARNING: error for foreign table column,{eins},{integer}: column name must be qualified -WARNING: error for foreign table column,{addr_nsp,zwei},{}: relation "addr_nsp" does not exist -WARNING: error for foreign table column,{addr_nsp,zwei},{integer}: relation "addr_nsp" does not exist -WARNING: error for foreign table column,{eins,zwei,drei},{}: schema "eins" does not exist -WARNING: error for foreign table column,{eins,zwei,drei},{integer}: schema "eins" does not exist -WARNING: error for aggregate,{eins},{}: aggregate eins(*) does not exist -WARNING: error for aggregate,{eins},{integer}: aggregate eins(integer) does not exist -WARNING: error for aggregate,{addr_nsp,zwei},{}: aggregate addr_nsp.zwei(*) does not exist -WARNING: error for aggregate,{addr_nsp,zwei},{integer}: aggregate addr_nsp.zwei(integer) does not exist -WARNING: error for aggregate,{eins,zwei,drei},{}: cross-database references are not implemented: eins.zwei.drei -WARNING: error for aggregate,{eins,zwei,drei},{integer}: cross-database references are not implemented: eins.zwei.drei -WARNING: error for function,{eins},{}: function eins() does not exist -WARNING: error for function,{eins},{integer}: function eins(integer) does not exist -WARNING: error for function,{addr_nsp,zwei},{}: function addr_nsp.zwei() does not exist -WARNING: error for function,{addr_nsp,zwei},{integer}: function addr_nsp.zwei(integer) does not exist -WARNING: error for function,{eins,zwei,drei},{}: cross-database references are not implemented: eins.zwei.drei -WARNING: error for function,{eins,zwei,drei},{integer}: cross-database references are not implemented: eins.zwei.drei -WARNING: error for procedure,{eins},{}: procedure eins() does not exist -WARNING: error for procedure,{eins},{integer}: procedure eins(integer) does not exist -WARNING: error for procedure,{addr_nsp,zwei},{}: procedure addr_nsp.zwei() does not exist -WARNING: error for procedure,{addr_nsp,zwei},{integer}: procedure addr_nsp.zwei(integer) does not exist -WARNING: error for procedure,{eins,zwei,drei},{}: cross-database references are not implemented: eins.zwei.drei -WARNING: error for procedure,{eins,zwei,drei},{integer}: cross-database references are not implemented: eins.zwei.drei -WARNING: error for type,{eins},{}: type "eins" does not exist -WARNING: error for type,{eins},{integer}: type "eins" does not exist -WARNING: error for type,{addr_nsp,zwei},{}: name list length must be exactly 1 -WARNING: error for type,{addr_nsp,zwei},{integer}: name list length must be exactly 1 -WARNING: error for type,{eins,zwei,drei},{}: name list length must be exactly 1 -WARNING: error for type,{eins,zwei,drei},{integer}: name list length must be exactly 1 -WARNING: error for cast,{eins},{}: argument list length must be exactly 1 -WARNING: error for cast,{eins},{integer}: type "eins" does not exist -WARNING: error for cast,{addr_nsp,zwei},{}: name list length must be exactly 1 -WARNING: error for cast,{addr_nsp,zwei},{integer}: name list length must be exactly 1 -WARNING: error for cast,{eins,zwei,drei},{}: name list length must be exactly 1 -WARNING: error for cast,{eins,zwei,drei},{integer}: name list length must be exactly 1 -WARNING: error for table constraint,{eins},{}: must specify relation and object name -WARNING: error for table constraint,{eins},{integer}: must specify relation and object name -WARNING: error for table constraint,{addr_nsp,zwei},{}: relation "addr_nsp" does not exist -WARNING: error for table constraint,{addr_nsp,zwei},{integer}: relation "addr_nsp" does not exist -WARNING: error for table constraint,{eins,zwei,drei},{}: schema "eins" does not exist -WARNING: error for table constraint,{eins,zwei,drei},{integer}: schema "eins" does not exist -WARNING: error for domain constraint,{eins},{}: argument list length must be exactly 1 -WARNING: error for domain constraint,{eins},{integer}: type "eins" does not exist -WARNING: error for domain constraint,{addr_nsp,zwei},{}: name list length must be exactly 1 -WARNING: error for domain constraint,{addr_nsp,zwei},{integer}: name list length must be exactly 1 -WARNING: error for domain constraint,{eins,zwei,drei},{}: name list length must be exactly 1 -WARNING: error for domain constraint,{eins,zwei,drei},{integer}: name list length must be exactly 1 -WARNING: error for conversion,{eins},{}: conversion "eins" does not exist -WARNING: error for conversion,{eins},{integer}: conversion "eins" does not exist -WARNING: error for conversion,{addr_nsp,zwei},{}: conversion "addr_nsp.zwei" does not exist -WARNING: error for conversion,{addr_nsp,zwei},{integer}: conversion "addr_nsp.zwei" does not exist -WARNING: error for conversion,{eins,zwei,drei},{}: cross-database references are not implemented: eins.zwei.drei -WARNING: error for conversion,{eins,zwei,drei},{integer}: cross-database references are not implemented: eins.zwei.drei -WARNING: error for default value,{eins},{}: column name must be qualified -WARNING: error for default value,{eins},{integer}: column name must be qualified -WARNING: error for default value,{addr_nsp,zwei},{}: relation "addr_nsp" does not exist -WARNING: error for default value,{addr_nsp,zwei},{integer}: relation "addr_nsp" does not exist -WARNING: error for default value,{eins,zwei,drei},{}: schema "eins" does not exist -WARNING: error for default value,{eins,zwei,drei},{integer}: schema "eins" does not exist -WARNING: error for operator,{eins},{}: argument list length must be exactly 2 -WARNING: error for operator,{eins},{integer}: argument list length must be exactly 2 -WARNING: error for operator,{addr_nsp,zwei},{}: argument list length must be exactly 2 -WARNING: error for operator,{addr_nsp,zwei},{integer}: argument list length must be exactly 2 -WARNING: error for operator,{eins,zwei,drei},{}: argument list length must be exactly 2 -WARNING: error for operator,{eins,zwei,drei},{integer}: argument list length must be exactly 2 -WARNING: error for operator class,{eins},{}: name list length must be at least 2 -WARNING: error for operator class,{eins},{integer}: name list length must be at least 2 -WARNING: error for operator class,{addr_nsp,zwei},{}: access method "addr_nsp" does not exist -WARNING: error for operator class,{addr_nsp,zwei},{integer}: access method "addr_nsp" does not exist -WARNING: error for operator class,{eins,zwei,drei},{}: access method "eins" does not exist -WARNING: error for operator class,{eins,zwei,drei},{integer}: access method "eins" does not exist -WARNING: error for operator family,{eins},{}: name list length must be at least 2 -WARNING: error for operator family,{eins},{integer}: name list length must be at least 2 -WARNING: error for operator family,{addr_nsp,zwei},{}: access method "addr_nsp" does not exist -WARNING: error for operator family,{addr_nsp,zwei},{integer}: access method "addr_nsp" does not exist -WARNING: error for operator family,{eins,zwei,drei},{}: access method "eins" does not exist -WARNING: error for operator family,{eins,zwei,drei},{integer}: access method "eins" does not exist -WARNING: error for rule,{eins},{}: must specify relation and object name -WARNING: error for rule,{eins},{integer}: must specify relation and object name -WARNING: error for rule,{addr_nsp,zwei},{}: relation "addr_nsp" does not exist -WARNING: error for rule,{addr_nsp,zwei},{integer}: relation "addr_nsp" does not exist -WARNING: error for rule,{eins,zwei,drei},{}: schema "eins" does not exist -WARNING: error for rule,{eins,zwei,drei},{integer}: schema "eins" does not exist -WARNING: error for trigger,{eins},{}: must specify relation and object name -WARNING: error for trigger,{eins},{integer}: must specify relation and object name -WARNING: error for trigger,{addr_nsp,zwei},{}: relation "addr_nsp" does not exist -WARNING: error for trigger,{addr_nsp,zwei},{integer}: relation "addr_nsp" does not exist -WARNING: error for trigger,{eins,zwei,drei},{}: schema "eins" does not exist -WARNING: error for trigger,{eins,zwei,drei},{integer}: schema "eins" does not exist -WARNING: error for text search parser,{eins},{}: text search parser "eins" does not exist -WARNING: error for text search parser,{eins},{integer}: text search parser "eins" does not exist -WARNING: error for text search parser,{addr_nsp,zwei},{}: text search parser "addr_nsp.zwei" does not exist -WARNING: error for text search parser,{addr_nsp,zwei},{integer}: text search parser "addr_nsp.zwei" does not exist -WARNING: error for text search parser,{eins,zwei,drei},{}: cross-database references are not implemented: eins.zwei.drei -WARNING: error for text search parser,{eins,zwei,drei},{integer}: cross-database references are not implemented: eins.zwei.drei -WARNING: error for text search dictionary,{eins},{}: text search dictionary "eins" does not exist -WARNING: error for text search dictionary,{eins},{integer}: text search dictionary "eins" does not exist -WARNING: error for text search dictionary,{addr_nsp,zwei},{}: text search dictionary "addr_nsp.zwei" does not exist -WARNING: error for text search dictionary,{addr_nsp,zwei},{integer}: text search dictionary "addr_nsp.zwei" does not exist -WARNING: error for text search dictionary,{eins,zwei,drei},{}: cross-database references are not implemented: eins.zwei.drei -WARNING: error for text search dictionary,{eins,zwei,drei},{integer}: cross-database references are not implemented: eins.zwei.drei -WARNING: error for text search template,{eins},{}: text search template "eins" does not exist -WARNING: error for text search template,{eins},{integer}: text search template "eins" does not exist -WARNING: error for text search template,{addr_nsp,zwei},{}: text search template "addr_nsp.zwei" does not exist -WARNING: error for text search template,{addr_nsp,zwei},{integer}: text search template "addr_nsp.zwei" does not exist -WARNING: error for text search template,{eins,zwei,drei},{}: cross-database references are not implemented: eins.zwei.drei -WARNING: error for text search template,{eins,zwei,drei},{integer}: cross-database references are not implemented: eins.zwei.drei -WARNING: error for text search configuration,{eins},{}: text search configuration "eins" does not exist -WARNING: error for text search configuration,{eins},{integer}: text search configuration "eins" does not exist -WARNING: error for text search configuration,{addr_nsp,zwei},{}: text search configuration "addr_nsp.zwei" does not exist -WARNING: error for text search configuration,{addr_nsp,zwei},{integer}: text search configuration "addr_nsp.zwei" does not exist -WARNING: error for text search configuration,{eins,zwei,drei},{}: cross-database references are not implemented: eins.zwei.drei -WARNING: error for text search configuration,{eins,zwei,drei},{integer}: cross-database references are not implemented: eins.zwei.drei -WARNING: error for policy,{eins},{}: must specify relation and object name -WARNING: error for policy,{eins},{integer}: must specify relation and object name -WARNING: error for policy,{addr_nsp,zwei},{}: relation "addr_nsp" does not exist -WARNING: error for policy,{addr_nsp,zwei},{integer}: relation "addr_nsp" does not exist -WARNING: error for policy,{eins,zwei,drei},{}: schema "eins" does not exist -WARNING: error for policy,{eins,zwei,drei},{integer}: schema "eins" does not exist -WARNING: error for user mapping,{eins},{}: argument list length must be exactly 1 -WARNING: error for user mapping,{eins},{integer}: user mapping for user "eins" on server "integer" does not exist -WARNING: error for user mapping,{addr_nsp,zwei},{}: name list length must be exactly 1 -WARNING: error for user mapping,{addr_nsp,zwei},{integer}: name list length must be exactly 1 -WARNING: error for user mapping,{eins,zwei,drei},{}: name list length must be exactly 1 -WARNING: error for user mapping,{eins,zwei,drei},{integer}: name list length must be exactly 1 -WARNING: error for default acl,{eins},{}: argument list length must be exactly 1 -WARNING: error for default acl,{eins},{integer}: unrecognized default ACL object type "i" -WARNING: error for default acl,{addr_nsp,zwei},{}: argument list length must be exactly 1 -WARNING: error for default acl,{addr_nsp,zwei},{integer}: unrecognized default ACL object type "i" -WARNING: error for default acl,{eins,zwei,drei},{}: argument list length must be exactly 1 -WARNING: error for default acl,{eins,zwei,drei},{integer}: unrecognized default ACL object type "i" -WARNING: error for transform,{eins},{}: argument list length must be exactly 1 -WARNING: error for transform,{eins},{integer}: type "eins" does not exist -WARNING: error for transform,{addr_nsp,zwei},{}: name list length must be exactly 1 -WARNING: error for transform,{addr_nsp,zwei},{integer}: name list length must be exactly 1 -WARNING: error for transform,{eins,zwei,drei},{}: name list length must be exactly 1 -WARNING: error for transform,{eins,zwei,drei},{integer}: name list length must be exactly 1 -WARNING: error for operator of access method,{eins},{}: name list length must be at least 3 -WARNING: error for operator of access method,{eins},{integer}: name list length must be at least 3 -WARNING: error for operator of access method,{addr_nsp,zwei},{}: name list length must be at least 3 -WARNING: error for operator of access method,{addr_nsp,zwei},{integer}: name list length must be at least 3 -WARNING: error for operator of access method,{eins,zwei,drei},{}: argument list length must be exactly 2 -WARNING: error for operator of access method,{eins,zwei,drei},{integer}: argument list length must be exactly 2 -WARNING: error for function of access method,{eins},{}: name list length must be at least 3 -WARNING: error for function of access method,{eins},{integer}: name list length must be at least 3 -WARNING: error for function of access method,{addr_nsp,zwei},{}: name list length must be at least 3 -WARNING: error for function of access method,{addr_nsp,zwei},{integer}: name list length must be at least 3 -WARNING: error for function of access method,{eins,zwei,drei},{}: argument list length must be exactly 2 -WARNING: error for function of access method,{eins,zwei,drei},{integer}: argument list length must be exactly 2 -WARNING: error for publication namespace,{eins},{}: argument list length must be exactly 1 -WARNING: error for publication namespace,{eins},{integer}: schema "eins" does not exist -WARNING: error for publication namespace,{addr_nsp,zwei},{}: name list length must be exactly 1 -WARNING: error for publication namespace,{addr_nsp,zwei},{integer}: name list length must be exactly 1 -WARNING: error for publication namespace,{eins,zwei,drei},{}: name list length must be exactly 1 -WARNING: error for publication namespace,{eins,zwei,drei},{integer}: name list length must be exactly 1 -WARNING: error for publication relation,{eins},{}: argument list length must be exactly 1 -WARNING: error for publication relation,{eins},{integer}: relation "eins" does not exist -WARNING: error for publication relation,{addr_nsp,zwei},{}: argument list length must be exactly 1 -WARNING: error for publication relation,{addr_nsp,zwei},{integer}: relation "addr_nsp.zwei" does not exist -WARNING: error for publication relation,{eins,zwei,drei},{}: argument list length must be exactly 1 -WARNING: error for publication relation,{eins,zwei,drei},{integer}: cross-database references are not implemented: "eins.zwei.drei" --- these object types cannot be qualified names -SELECT pg_get_object_address('language', '{one}', '{}'); -ERROR: language "one" does not exist -SELECT pg_get_object_address('language', '{one,two}', '{}'); -ERROR: name list length must be exactly 1 -SELECT pg_get_object_address('large object', '{123}', '{}'); -ERROR: large object 123 does not exist -SELECT pg_get_object_address('large object', '{123,456}', '{}'); -ERROR: name list length must be exactly 1 -SELECT pg_get_object_address('large object', '{blargh}', '{}'); -ERROR: invalid input syntax for type oid: "blargh" -SELECT pg_get_object_address('schema', '{one}', '{}'); -ERROR: schema "one" does not exist -SELECT pg_get_object_address('schema', '{one,two}', '{}'); -ERROR: name list length must be exactly 1 -SELECT pg_get_object_address('role', '{one}', '{}'); -ERROR: role "one" does not exist -SELECT pg_get_object_address('role', '{one,two}', '{}'); -ERROR: name list length must be exactly 1 -SELECT pg_get_object_address('database', '{one}', '{}'); -ERROR: database "one" does not exist -SELECT pg_get_object_address('database', '{one,two}', '{}'); -ERROR: name list length must be exactly 1 -SELECT pg_get_object_address('tablespace', '{one}', '{}'); -ERROR: tablespace "one" does not exist -SELECT pg_get_object_address('tablespace', '{one,two}', '{}'); -ERROR: name list length must be exactly 1 -SELECT pg_get_object_address('foreign-data wrapper', '{one}', '{}'); -ERROR: foreign-data wrapper "one" does not exist -SELECT pg_get_object_address('foreign-data wrapper', '{one,two}', '{}'); -ERROR: name list length must be exactly 1 -SELECT pg_get_object_address('server', '{one}', '{}'); -ERROR: server "one" does not exist -SELECT pg_get_object_address('server', '{one,two}', '{}'); -ERROR: name list length must be exactly 1 -SELECT pg_get_object_address('extension', '{one}', '{}'); -ERROR: extension "one" does not exist -SELECT pg_get_object_address('extension', '{one,two}', '{}'); -ERROR: name list length must be exactly 1 -SELECT pg_get_object_address('event trigger', '{one}', '{}'); -ERROR: event trigger "one" does not exist -SELECT pg_get_object_address('event trigger', '{one,two}', '{}'); -ERROR: name list length must be exactly 1 -SELECT pg_get_object_address('access method', '{one}', '{}'); -ERROR: access method "one" does not exist -SELECT pg_get_object_address('access method', '{one,two}', '{}'); -ERROR: name list length must be exactly 1 -SELECT pg_get_object_address('publication', '{one}', '{}'); -ERROR: publication "one" does not exist -SELECT pg_get_object_address('publication', '{one,two}', '{}'); -ERROR: name list length must be exactly 1 -SELECT pg_get_object_address('subscription', '{one}', '{}'); -ERROR: subscription "one" does not exist -SELECT pg_get_object_address('subscription', '{one,two}', '{}'); -ERROR: name list length must be exactly 1 --- Make sure that NULL handling is correct. -\pset null 'NULL' --- Temporarily disable fancy output, so as future additions never create --- a large amount of diffs. -\a\t --- test successful cases -WITH objects (type, name, args) AS (VALUES - ('table', '{addr_nsp, gentable}'::text[], '{}'::text[]), - ('table', '{addr_nsp, parttable}'::text[], '{}'::text[]), - ('index', '{addr_nsp, gentable_pkey}', '{}'), - ('index', '{addr_nsp, parttable_pkey}', '{}'), - ('sequence', '{addr_nsp, gentable_a_seq}', '{}'), - -- toast table - ('view', '{addr_nsp, genview}', '{}'), - ('materialized view', '{addr_nsp, genmatview}', '{}'), - ('foreign table', '{addr_nsp, genftable}', '{}'), - ('table column', '{addr_nsp, gentable, b}', '{}'), - ('foreign table column', '{addr_nsp, genftable, a}', '{}'), - ('aggregate', '{addr_nsp, genaggr}', '{int4}'), - ('function', '{pg_catalog, pg_identify_object}', '{pg_catalog.oid, pg_catalog.oid, int4}'), - ('procedure', '{addr_nsp, proc}', '{int4}'), - ('type', '{pg_catalog._int4}', '{}'), - ('type', '{addr_nsp.gendomain}', '{}'), - ('type', '{addr_nsp.gencomptype}', '{}'), - ('type', '{addr_nsp.genenum}', '{}'), - ('cast', '{int8}', '{int4}'), - ('collation', '{default}', '{}'), - ('table constraint', '{addr_nsp, gentable, a_chk}', '{}'), - ('domain constraint', '{addr_nsp.gendomain}', '{domconstr}'), - ('conversion', '{pg_catalog, koi8_r_to_mic}', '{}'), - ('default value', '{addr_nsp, gentable, b}', '{}'), - ('language', '{plpgsql}', '{}'), - -- large object - ('operator', '{+}', '{int4, int4}'), - ('operator class', '{btree, int4_ops}', '{}'), - ('operator family', '{btree, integer_ops}', '{}'), - ('operator of access method', '{btree,integer_ops,1}', '{integer,integer}'), - ('function of access method', '{btree,integer_ops,2}', '{integer,integer}'), - ('rule', '{addr_nsp, genview, _RETURN}', '{}'), - ('trigger', '{addr_nsp, gentable, t}', '{}'), - ('schema', '{addr_nsp}', '{}'), - ('text search parser', '{addr_ts_prs}', '{}'), - ('text search dictionary', '{addr_ts_dict}', '{}'), - ('text search template', '{addr_ts_temp}', '{}'), - ('text search configuration', '{addr_ts_conf}', '{}'), - ('role', '{regress_addr_user}', '{}'), - -- database - -- tablespace - ('foreign-data wrapper', '{addr_fdw}', '{}'), - ('server', '{addr_fserv}', '{}'), - ('user mapping', '{regress_addr_user}', '{integer}'), - ('default acl', '{regress_addr_user,public}', '{r}'), - ('default acl', '{regress_addr_user}', '{r}'), - -- extension - -- event trigger - ('policy', '{addr_nsp, gentable, genpol}', '{}'), - ('transform', '{int}', '{sql}'), - ('access method', '{btree}', '{}'), - ('publication', '{addr_pub}', '{}'), - ('publication namespace', '{addr_nsp}', '{addr_pub_schema}'), - ('publication relation', '{addr_nsp, gentable}', '{addr_pub}'), - ('subscription', '{regress_addr_sub}', '{}'), - ('statistics object', '{addr_nsp, gentable_stat}', '{}') - ) -SELECT (pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)).*, - -- test roundtrip through pg_identify_object_as_address - ROW(pg_identify_object(addr1.classid, addr1.objid, addr1.objsubid)) = - ROW(pg_identify_object(addr2.classid, addr2.objid, addr2.objsubid)) AS roundtrip -FROM objects, - pg_get_object_address(type, name, args) AS addr1, - pg_identify_object_as_address(classid, objid, objsubid) AS ioa (typ, nms, args), - pg_get_object_address(typ, nms, ioa.args) AS addr2 -ORDER BY addr1.classid, addr1.objid, addr1.objsubid; -default acl|NULL|NULL|for role regress_addr_user in schema public on tables|t -default acl|NULL|NULL|for role regress_addr_user on tables|t -type|pg_catalog|_int4|integer[]|t -type|addr_nsp|gencomptype|addr_nsp.gencomptype|t -type|addr_nsp|genenum|addr_nsp.genenum|t -type|addr_nsp|gendomain|addr_nsp.gendomain|t -function|pg_catalog|NULL|pg_catalog.pg_identify_object(pg_catalog.oid,pg_catalog.oid,integer)|t -aggregate|addr_nsp|NULL|addr_nsp.genaggr(integer)|t -procedure|addr_nsp|NULL|addr_nsp.proc(integer)|t -sequence|addr_nsp|gentable_a_seq|addr_nsp.gentable_a_seq|t -table|addr_nsp|gentable|addr_nsp.gentable|t -table column|addr_nsp|gentable|addr_nsp.gentable.b|t -index|addr_nsp|gentable_pkey|addr_nsp.gentable_pkey|t -table|addr_nsp|parttable|addr_nsp.parttable|t -index|addr_nsp|parttable_pkey|addr_nsp.parttable_pkey|t -view|addr_nsp|genview|addr_nsp.genview|t -materialized view|addr_nsp|genmatview|addr_nsp.genmatview|t -foreign table|addr_nsp|genftable|addr_nsp.genftable|t -foreign table column|addr_nsp|genftable|addr_nsp.genftable.a|t -role|NULL|regress_addr_user|regress_addr_user|t -server|NULL|addr_fserv|addr_fserv|t -user mapping|NULL|NULL|regress_addr_user on server integer|t -foreign-data wrapper|NULL|addr_fdw|addr_fdw|t -access method|NULL|btree|btree|t -operator of access method|NULL|NULL|operator 1 (integer, integer) of pg_catalog.integer_ops USING btree|t -function of access method|NULL|NULL|function 2 (integer, integer) of pg_catalog.integer_ops USING btree|t -default value|NULL|NULL|for addr_nsp.gentable.b|t -cast|NULL|NULL|(bigint AS integer)|t -table constraint|addr_nsp|NULL|a_chk on addr_nsp.gentable|t -domain constraint|addr_nsp|NULL|domconstr on addr_nsp.gendomain|t -conversion|pg_catalog|koi8_r_to_mic|pg_catalog.koi8_r_to_mic|t -language|NULL|plpgsql|plpgsql|t -schema|NULL|addr_nsp|addr_nsp|t -operator class|pg_catalog|int4_ops|pg_catalog.int4_ops USING btree|t -operator|pg_catalog|NULL|pg_catalog.+(integer,integer)|t -rule|NULL|NULL|"_RETURN" on addr_nsp.genview|t -trigger|NULL|NULL|t on addr_nsp.gentable|t -operator family|pg_catalog|integer_ops|pg_catalog.integer_ops USING btree|t -policy|NULL|NULL|genpol on addr_nsp.gentable|t -statistics object|addr_nsp|gentable_stat|addr_nsp.gentable_stat|t -collation|pg_catalog|"default"|pg_catalog."default"|t -transform|NULL|NULL|for integer language sql|t -text search dictionary|addr_nsp|addr_ts_dict|addr_nsp.addr_ts_dict|t -text search parser|addr_nsp|addr_ts_prs|addr_nsp.addr_ts_prs|t -text search configuration|addr_nsp|addr_ts_conf|addr_nsp.addr_ts_conf|t -text search template|addr_nsp|addr_ts_temp|addr_nsp.addr_ts_temp|t -subscription|NULL|regress_addr_sub|regress_addr_sub|t -publication|NULL|addr_pub|addr_pub|t -publication relation|NULL|NULL|addr_nsp.gentable in publication addr_pub|t -publication namespace|NULL|NULL|addr_nsp in publication addr_pub_schema|t ---- ---- Cleanup resources ---- -DROP FOREIGN DATA WRAPPER addr_fdw CASCADE; -NOTICE: drop cascades to 4 other objects -DETAIL: drop cascades to server addr_fserv -drop cascades to foreign table genftable -drop cascades to server integer -drop cascades to user mapping for regress_addr_user on server integer -DROP PUBLICATION addr_pub; -DROP PUBLICATION addr_pub_schema; -DROP SUBSCRIPTION regress_addr_sub; -DROP SCHEMA addr_nsp CASCADE; -NOTICE: drop cascades to 14 other objects -DETAIL: drop cascades to text search dictionary addr_ts_dict -drop cascades to text search configuration addr_ts_conf -drop cascades to text search template addr_ts_temp -drop cascades to text search parser addr_ts_prs -drop cascades to table gentable -drop cascades to table parttable -drop cascades to view genview -drop cascades to materialized view genmatview -drop cascades to type gencomptype -drop cascades to type genenum -drop cascades to function genaggr(integer) -drop cascades to type gendomain -drop cascades to function trig() -drop cascades to function proc(integer) -DROP OWNED BY regress_addr_user; -DROP USER regress_addr_user; --- --- Checks for invalid objects --- --- Keep this list in the same order as getObjectIdentityParts() --- in objectaddress.c. -WITH objects (classid, objid, objsubid) AS (VALUES - ('pg_class'::regclass, 0, 0), -- no relation - ('pg_class'::regclass, 'pg_class'::regclass, 100), -- no column for relation - ('pg_proc'::regclass, 0, 0), -- no function - ('pg_type'::regclass, 0, 0), -- no type - ('pg_cast'::regclass, 0, 0), -- no cast - ('pg_collation'::regclass, 0, 0), -- no collation - ('pg_constraint'::regclass, 0, 0), -- no constraint - ('pg_conversion'::regclass, 0, 0), -- no conversion - ('pg_attrdef'::regclass, 0, 0), -- no default attribute - ('pg_language'::regclass, 0, 0), -- no language - ('pg_largeobject'::regclass, 0, 0), -- no large object, no error - ('pg_operator'::regclass, 0, 0), -- no operator - ('pg_opclass'::regclass, 0, 0), -- no opclass, no need to check for no access method - ('pg_opfamily'::regclass, 0, 0), -- no opfamily - ('pg_am'::regclass, 0, 0), -- no access method - ('pg_amop'::regclass, 0, 0), -- no AM operator - ('pg_amproc'::regclass, 0, 0), -- no AM proc - ('pg_rewrite'::regclass, 0, 0), -- no rewrite - ('pg_trigger'::regclass, 0, 0), -- no trigger - ('pg_namespace'::regclass, 0, 0), -- no schema - ('pg_statistic_ext'::regclass, 0, 0), -- no statistics - ('pg_ts_parser'::regclass, 0, 0), -- no TS parser - ('pg_ts_dict'::regclass, 0, 0), -- no TS dictionary - ('pg_ts_template'::regclass, 0, 0), -- no TS template - ('pg_ts_config'::regclass, 0, 0), -- no TS configuration - ('pg_authid'::regclass, 0, 0), -- no role - ('pg_auth_members'::regclass, 0, 0), -- no role membership - ('pg_database'::regclass, 0, 0), -- no database - ('pg_tablespace'::regclass, 0, 0), -- no tablespace - ('pg_foreign_data_wrapper'::regclass, 0, 0), -- no FDW - ('pg_foreign_server'::regclass, 0, 0), -- no server - ('pg_user_mapping'::regclass, 0, 0), -- no user mapping - ('pg_default_acl'::regclass, 0, 0), -- no default ACL - ('pg_extension'::regclass, 0, 0), -- no extension - ('pg_event_trigger'::regclass, 0, 0), -- no event trigger - ('pg_parameter_acl'::regclass, 0, 0), -- no parameter ACL - ('pg_policy'::regclass, 0, 0), -- no policy - ('pg_publication'::regclass, 0, 0), -- no publication - ('pg_publication_namespace'::regclass, 0, 0), -- no publication namespace - ('pg_publication_rel'::regclass, 0, 0), -- no publication relation - ('pg_subscription'::regclass, 0, 0), -- no subscription - ('pg_transform'::regclass, 0, 0) -- no transformation - ) -SELECT ROW(pg_identify_object(objects.classid, objects.objid, objects.objsubid)) - AS ident, - ROW(pg_identify_object_as_address(objects.classid, objects.objid, objects.objsubid)) - AS addr, - pg_describe_object(objects.classid, objects.objid, objects.objsubid) - AS descr -FROM objects -ORDER BY objects.classid, objects.objid, objects.objsubid; -("(""default acl"",,,)")|("(""default acl"",,)")|NULL -("(tablespace,,,)")|("(tablespace,,)")|NULL -("(type,,,)")|("(type,,)")|NULL -("(routine,,,)")|("(routine,,)")|NULL -("(relation,,,)")|("(relation,,)")|NULL -("(""table column"",,,)")|("(""table column"",,)")|NULL -("(role,,,)")|("(role,,)")|NULL -("(""role membership"",,,)")|("(""role membership"",,)")|NULL -("(database,,,)")|("(database,,)")|NULL -("(server,,,)")|("(server,,)")|NULL -("(""user mapping"",,,)")|("(""user mapping"",,)")|NULL -("(""foreign-data wrapper"",,,)")|("(""foreign-data wrapper"",,)")|NULL -("(""access method"",,,)")|("(""access method"",,)")|NULL -("(""operator of access method"",,,)")|("(""operator of access method"",,)")|NULL -("(""function of access method"",,,)")|("(""function of access method"",,)")|NULL -("(""default value"",,,)")|("(""default value"",,)")|NULL -("(cast,,,)")|("(cast,,)")|NULL -("(constraint,,,)")|("(constraint,,)")|NULL -("(conversion,,,)")|("(conversion,,)")|NULL -("(language,,,)")|("(language,,)")|NULL -("(""large object"",,,)")|("(""large object"",,)")|NULL -("(schema,,,)")|("(schema,,)")|NULL -("(""operator class"",,,)")|("(""operator class"",,)")|NULL -("(operator,,,)")|("(operator,,)")|NULL -("(rule,,,)")|("(rule,,)")|NULL -("(trigger,,,)")|("(trigger,,)")|NULL -("(""operator family"",,,)")|("(""operator family"",,)")|NULL -("(extension,,,)")|("(extension,,)")|NULL -("(policy,,,)")|("(policy,,)")|NULL -("(""statistics object"",,,)")|("(""statistics object"",,)")|NULL -("(collation,,,)")|("(collation,,)")|NULL -("(""event trigger"",,,)")|("(""event trigger"",,)")|NULL -("(transform,,,)")|("(transform,,)")|NULL -("(""text search dictionary"",,,)")|("(""text search dictionary"",,)")|NULL -("(""text search parser"",,,)")|("(""text search parser"",,)")|NULL -("(""text search configuration"",,,)")|("(""text search configuration"",,)")|NULL -("(""text search template"",,,)")|("(""text search template"",,)")|NULL -("(subscription,,,)")|("(subscription,,)")|NULL -("(publication,,,)")|("(publication,,)")|NULL -("(""publication relation"",,,)")|("(""publication relation"",,)")|NULL -("(""publication namespace"",,,)")|("(""publication namespace"",,)")|NULL -("(""parameter ACL"",,,)")|("(""parameter ACL"",,)")|NULL --- restore normal output mode -\a\t +psql: error: connection to server on socket "/tmp/pg_regress-03Az7d/.s.PGSQL.40051" failed: FATAL: the database system is not yet accepting connections +DETAIL: Consistent recovery state has not been yet reached. diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/tablesample.out /tmp/cirrus-ci-build/build/testrun/regress/regress/results/tablesample.out --- /tmp/cirrus-ci-build/src/test/regress/expected/tablesample.out 2024-09-20 21:41:45.754024524 +0000 +++ /tmp/cirrus-ci-build/build/testrun/regress/regress/results/tablesample.out 2024-09-20 21:46:06.645623497 +0000 @@ -1,331 +1,2 @@ -CREATE TABLE test_tablesample (id int, name text) WITH (fillfactor=10); --- use fillfactor so we don't have to load too much data to get multiple pages -INSERT INTO test_tablesample - SELECT i, repeat(i::text, 200) FROM generate_series(0, 9) s(i); -SELECT t.id FROM test_tablesample AS t TABLESAMPLE SYSTEM (50) REPEATABLE (0); - id ----- - 3 - 4 - 5 - 6 - 7 - 8 -(6 rows) - -SELECT id FROM test_tablesample TABLESAMPLE SYSTEM (100.0/11) REPEATABLE (0); - id ----- -(0 rows) - -SELECT id FROM test_tablesample TABLESAMPLE SYSTEM (50) REPEATABLE (0); - id ----- - 3 - 4 - 5 - 6 - 7 - 8 -(6 rows) - -SELECT id FROM test_tablesample TABLESAMPLE BERNOULLI (50) REPEATABLE (0); - id ----- - 4 - 5 - 6 - 7 - 8 -(5 rows) - -SELECT id FROM test_tablesample TABLESAMPLE BERNOULLI (5.5) REPEATABLE (0); - id ----- - 7 -(1 row) - --- 100% should give repeatable count results (ie, all rows) in any case -SELECT count(*) FROM test_tablesample TABLESAMPLE SYSTEM (100); - count -------- - 10 -(1 row) - -SELECT count(*) FROM test_tablesample TABLESAMPLE SYSTEM (100) REPEATABLE (1+2); - count -------- - 10 -(1 row) - -SELECT count(*) FROM test_tablesample TABLESAMPLE SYSTEM (100) REPEATABLE (0.4); - count -------- - 10 -(1 row) - -CREATE VIEW test_tablesample_v1 AS - SELECT id FROM test_tablesample TABLESAMPLE SYSTEM (10*2) REPEATABLE (2); -CREATE VIEW test_tablesample_v2 AS - SELECT id FROM test_tablesample TABLESAMPLE SYSTEM (99); -\d+ test_tablesample_v1 - View "public.test_tablesample_v1" - Column | Type | Collation | Nullable | Default | Storage | Description ---------+---------+-----------+----------+---------+---------+------------- - id | integer | | | | plain | -View definition: - SELECT id - FROM test_tablesample TABLESAMPLE system ((10 * 2)) REPEATABLE (2); - -\d+ test_tablesample_v2 - View "public.test_tablesample_v2" - Column | Type | Collation | Nullable | Default | Storage | Description ---------+---------+-----------+----------+---------+---------+------------- - id | integer | | | | plain | -View definition: - SELECT id - FROM test_tablesample TABLESAMPLE system (99); - --- check a sampled query doesn't affect cursor in progress -BEGIN; -DECLARE tablesample_cur SCROLL CURSOR FOR - SELECT id FROM test_tablesample TABLESAMPLE SYSTEM (50) REPEATABLE (0); -FETCH FIRST FROM tablesample_cur; - id ----- - 3 -(1 row) - -FETCH NEXT FROM tablesample_cur; - id ----- - 4 -(1 row) - -FETCH NEXT FROM tablesample_cur; - id ----- - 5 -(1 row) - -SELECT id FROM test_tablesample TABLESAMPLE SYSTEM (50) REPEATABLE (0); - id ----- - 3 - 4 - 5 - 6 - 7 - 8 -(6 rows) - -FETCH NEXT FROM tablesample_cur; - id ----- - 6 -(1 row) - -FETCH NEXT FROM tablesample_cur; - id ----- - 7 -(1 row) - -FETCH NEXT FROM tablesample_cur; - id ----- - 8 -(1 row) - -FETCH FIRST FROM tablesample_cur; - id ----- - 3 -(1 row) - -FETCH NEXT FROM tablesample_cur; - id ----- - 4 -(1 row) - -FETCH NEXT FROM tablesample_cur; - id ----- - 5 -(1 row) - -FETCH NEXT FROM tablesample_cur; - id ----- - 6 -(1 row) - -FETCH NEXT FROM tablesample_cur; - id ----- - 7 -(1 row) - -FETCH NEXT FROM tablesample_cur; - id ----- - 8 -(1 row) - -CLOSE tablesample_cur; -END; -EXPLAIN (COSTS OFF) - SELECT id FROM test_tablesample TABLESAMPLE SYSTEM (50) REPEATABLE (2); - QUERY PLAN --------------------------------------------------------------------- - Sample Scan on test_tablesample - Sampling: system ('50'::real) REPEATABLE ('2'::double precision) -(2 rows) - -EXPLAIN (COSTS OFF) - SELECT * FROM test_tablesample_v1; - QUERY PLAN --------------------------------------------------------------------- - Sample Scan on test_tablesample - Sampling: system ('20'::real) REPEATABLE ('2'::double precision) -(2 rows) - --- check inheritance behavior -explain (costs off) - select count(*) from person tablesample bernoulli (100); - QUERY PLAN -------------------------------------------------- - Aggregate - -> Append - -> Sample Scan on person person_1 - Sampling: bernoulli ('100'::real) - -> Sample Scan on emp person_2 - Sampling: bernoulli ('100'::real) - -> Sample Scan on student person_3 - Sampling: bernoulli ('100'::real) - -> Sample Scan on stud_emp person_4 - Sampling: bernoulli ('100'::real) -(10 rows) - -select count(*) from person tablesample bernoulli (100); - count -------- - 58 -(1 row) - -select count(*) from person; - count -------- - 58 -(1 row) - --- check that collations get assigned within the tablesample arguments -SELECT count(*) FROM test_tablesample TABLESAMPLE bernoulli (('1'::text < '0'::text)::int); - count -------- - 0 -(1 row) - --- check behavior during rescans, as well as correct handling of min/max pct -select * from - (values (0),(100)) v(pct), - lateral (select count(*) from tenk1 tablesample bernoulli (pct)) ss; - pct | count ------+------- - 0 | 0 - 100 | 10000 -(2 rows) - -select * from - (values (0),(100)) v(pct), - lateral (select count(*) from tenk1 tablesample system (pct)) ss; - pct | count ------+------- - 0 | 0 - 100 | 10000 -(2 rows) - -explain (costs off) -select pct, count(unique1) from - (values (0),(100)) v(pct), - lateral (select * from tenk1 tablesample bernoulli (pct)) ss - group by pct; - QUERY PLAN --------------------------------------------------------- - HashAggregate - Group Key: "*VALUES*".column1 - -> Nested Loop - -> Values Scan on "*VALUES*" - -> Sample Scan on tenk1 - Sampling: bernoulli ("*VALUES*".column1) -(6 rows) - -select pct, count(unique1) from - (values (0),(100)) v(pct), - lateral (select * from tenk1 tablesample bernoulli (pct)) ss - group by pct; - pct | count ------+------- - 100 | 10000 -(1 row) - -select pct, count(unique1) from - (values (0),(100)) v(pct), - lateral (select * from tenk1 tablesample system (pct)) ss - group by pct; - pct | count ------+------- - 100 | 10000 -(1 row) - --- errors -SELECT id FROM test_tablesample TABLESAMPLE FOOBAR (1); -ERROR: tablesample method foobar does not exist -LINE 1: SELECT id FROM test_tablesample TABLESAMPLE FOOBAR (1); - ^ -SELECT id FROM test_tablesample TABLESAMPLE SYSTEM (NULL); -ERROR: TABLESAMPLE parameter cannot be null -SELECT id FROM test_tablesample TABLESAMPLE SYSTEM (50) REPEATABLE (NULL); -ERROR: TABLESAMPLE REPEATABLE parameter cannot be null -SELECT id FROM test_tablesample TABLESAMPLE BERNOULLI (-1); -ERROR: sample percentage must be between 0 and 100 -SELECT id FROM test_tablesample TABLESAMPLE BERNOULLI (200); -ERROR: sample percentage must be between 0 and 100 -SELECT id FROM test_tablesample TABLESAMPLE SYSTEM (-1); -ERROR: sample percentage must be between 0 and 100 -SELECT id FROM test_tablesample TABLESAMPLE SYSTEM (200); -ERROR: sample percentage must be between 0 and 100 -SELECT id FROM test_tablesample_v1 TABLESAMPLE BERNOULLI (1); -ERROR: TABLESAMPLE clause can only be applied to tables and materialized views -LINE 1: SELECT id FROM test_tablesample_v1 TABLESAMPLE BERNOULLI (1)... - ^ -INSERT INTO test_tablesample_v1 VALUES(1); -ERROR: cannot insert into view "test_tablesample_v1" -DETAIL: Views containing TABLESAMPLE are not automatically updatable. -HINT: To enable inserting into the view, provide an INSTEAD OF INSERT trigger or an unconditional ON INSERT DO INSTEAD rule. -WITH query_select AS (SELECT * FROM test_tablesample) -SELECT * FROM query_select TABLESAMPLE BERNOULLI (5.5) REPEATABLE (1); -ERROR: TABLESAMPLE clause can only be applied to tables and materialized views -LINE 2: SELECT * FROM query_select TABLESAMPLE BERNOULLI (5.5) REPEA... - ^ -SELECT q.* FROM (SELECT * FROM test_tablesample) as q TABLESAMPLE BERNOULLI (5); -ERROR: syntax error at or near "TABLESAMPLE" -LINE 1: ...CT q.* FROM (SELECT * FROM test_tablesample) as q TABLESAMPL... - ^ --- check partitioned tables support tablesample -create table parted_sample (a int) partition by list (a); -create table parted_sample_1 partition of parted_sample for values in (1); -create table parted_sample_2 partition of parted_sample for values in (2); -explain (costs off) - select * from parted_sample tablesample bernoulli (100); - QUERY PLAN -------------------------------------------- - Append - -> Sample Scan on parted_sample_1 - Sampling: bernoulli ('100'::real) - -> Sample Scan on parted_sample_2 - Sampling: bernoulli ('100'::real) -(5 rows) - -drop table parted_sample, parted_sample_1, parted_sample_2; +psql: error: connection to server on socket "/tmp/pg_regress-03Az7d/.s.PGSQL.40051" failed: FATAL: the database system is not yet accepting connections +DETAIL: Consistent recovery state has not been yet reached. diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/groupingsets.out /tmp/cirrus-ci-build/build/testrun/regress/regress/results/groupingsets.out --- /tmp/cirrus-ci-build/src/test/regress/expected/groupingsets.out 2024-09-20 21:41:45.690024521 +0000 +++ /tmp/cirrus-ci-build/build/testrun/regress/regress/results/groupingsets.out 2024-09-20 21:46:06.625623524 +0000 @@ -1,2432 +1,2 @@ --- --- grouping sets --- --- test data sources -create temp view gstest1(a,b,v) - as values (1,1,10),(1,1,11),(1,2,12),(1,2,13),(1,3,14), - (2,3,15), - (3,3,16),(3,4,17), - (4,1,18),(4,1,19); -create temp table gstest2 (a integer, b integer, c integer, d integer, - e integer, f integer, g integer, h integer); -copy gstest2 from stdin; -create temp table gstest3 (a integer, b integer, c integer, d integer); -copy gstest3 from stdin; -alter table gstest3 add primary key (a); -create temp table gstest4(id integer, v integer, - unhashable_col bit(4), unsortable_col xid); -insert into gstest4 -values (1,1,b'0000','1'), (2,2,b'0001','1'), - (3,4,b'0010','2'), (4,8,b'0011','2'), - (5,16,b'0000','2'), (6,32,b'0001','2'), - (7,64,b'0010','1'), (8,128,b'0011','1'); -create temp table gstest_empty (a integer, b integer, v integer); -create function gstest_data(v integer, out a integer, out b integer) - returns setof record - as $f$ - begin - return query select v, i from generate_series(1,3) i; - end; - $f$ language plpgsql; --- basic functionality -set enable_hashagg = false; -- test hashing explicitly later --- simple rollup with multiple plain aggregates, with and without ordering --- (and with ordering differing from grouping) -select a, b, grouping(a,b), sum(v), count(*), max(v) - from gstest1 group by rollup (a,b); - a | b | grouping | sum | count | max ----+---+----------+-----+-------+----- - 1 | 1 | 0 | 21 | 2 | 11 - 1 | 2 | 0 | 25 | 2 | 13 - 1 | 3 | 0 | 14 | 1 | 14 - 1 | | 1 | 60 | 5 | 14 - 2 | 3 | 0 | 15 | 1 | 15 - 2 | | 1 | 15 | 1 | 15 - 3 | 3 | 0 | 16 | 1 | 16 - 3 | 4 | 0 | 17 | 1 | 17 - 3 | | 1 | 33 | 2 | 17 - 4 | 1 | 0 | 37 | 2 | 19 - 4 | | 1 | 37 | 2 | 19 - | | 3 | 145 | 10 | 19 -(12 rows) - -select a, b, grouping(a,b), sum(v), count(*), max(v) - from gstest1 group by rollup (a,b) order by a,b; - a | b | grouping | sum | count | max ----+---+----------+-----+-------+----- - 1 | 1 | 0 | 21 | 2 | 11 - 1 | 2 | 0 | 25 | 2 | 13 - 1 | 3 | 0 | 14 | 1 | 14 - 1 | | 1 | 60 | 5 | 14 - 2 | 3 | 0 | 15 | 1 | 15 - 2 | | 1 | 15 | 1 | 15 - 3 | 3 | 0 | 16 | 1 | 16 - 3 | 4 | 0 | 17 | 1 | 17 - 3 | | 1 | 33 | 2 | 17 - 4 | 1 | 0 | 37 | 2 | 19 - 4 | | 1 | 37 | 2 | 19 - | | 3 | 145 | 10 | 19 -(12 rows) - -select a, b, grouping(a,b), sum(v), count(*), max(v) - from gstest1 group by rollup (a,b) order by b desc, a; - a | b | grouping | sum | count | max ----+---+----------+-----+-------+----- - 1 | | 1 | 60 | 5 | 14 - 2 | | 1 | 15 | 1 | 15 - 3 | | 1 | 33 | 2 | 17 - 4 | | 1 | 37 | 2 | 19 - | | 3 | 145 | 10 | 19 - 3 | 4 | 0 | 17 | 1 | 17 - 1 | 3 | 0 | 14 | 1 | 14 - 2 | 3 | 0 | 15 | 1 | 15 - 3 | 3 | 0 | 16 | 1 | 16 - 1 | 2 | 0 | 25 | 2 | 13 - 1 | 1 | 0 | 21 | 2 | 11 - 4 | 1 | 0 | 37 | 2 | 19 -(12 rows) - -select a, b, grouping(a,b), sum(v), count(*), max(v) - from gstest1 group by rollup (a,b) order by coalesce(a,0)+coalesce(b,0); - a | b | grouping | sum | count | max ----+---+----------+-----+-------+----- - | | 3 | 145 | 10 | 19 - 1 | | 1 | 60 | 5 | 14 - 1 | 1 | 0 | 21 | 2 | 11 - 2 | | 1 | 15 | 1 | 15 - 3 | | 1 | 33 | 2 | 17 - 1 | 2 | 0 | 25 | 2 | 13 - 1 | 3 | 0 | 14 | 1 | 14 - 4 | | 1 | 37 | 2 | 19 - 4 | 1 | 0 | 37 | 2 | 19 - 2 | 3 | 0 | 15 | 1 | 15 - 3 | 3 | 0 | 16 | 1 | 16 - 3 | 4 | 0 | 17 | 1 | 17 -(12 rows) - --- various types of ordered aggs -select a, b, grouping(a,b), - array_agg(v order by v), - string_agg(v::text, ':' order by v desc), - percentile_disc(0.5) within group (order by v), - rank(1,2,12) within group (order by a,b,v) - from gstest1 group by rollup (a,b) order by a,b; - a | b | grouping | array_agg | string_agg | percentile_disc | rank ----+---+----------+---------------------------------+-------------------------------+-----------------+------ - 1 | 1 | 0 | {10,11} | 11:10 | 10 | 3 - 1 | 2 | 0 | {12,13} | 13:12 | 12 | 1 - 1 | 3 | 0 | {14} | 14 | 14 | 1 - 1 | | 1 | {10,11,12,13,14} | 14:13:12:11:10 | 12 | 3 - 2 | 3 | 0 | {15} | 15 | 15 | 1 - 2 | | 1 | {15} | 15 | 15 | 1 - 3 | 3 | 0 | {16} | 16 | 16 | 1 - 3 | 4 | 0 | {17} | 17 | 17 | 1 - 3 | | 1 | {16,17} | 17:16 | 16 | 1 - 4 | 1 | 0 | {18,19} | 19:18 | 18 | 1 - 4 | | 1 | {18,19} | 19:18 | 18 | 1 - | | 3 | {10,11,12,13,14,15,16,17,18,19} | 19:18:17:16:15:14:13:12:11:10 | 14 | 3 -(12 rows) - --- test usage of grouped columns in direct args of aggs -select grouping(a), a, array_agg(b), - rank(a) within group (order by b nulls first), - rank(a) within group (order by b nulls last) - from (values (1,1),(1,4),(1,5),(3,1),(3,2)) v(a,b) - group by rollup (a) order by a; - grouping | a | array_agg | rank | rank -----------+---+-------------+------+------ - 0 | 1 | {1,4,5} | 1 | 1 - 0 | 3 | {1,2} | 3 | 3 - 1 | | {1,4,5,1,2} | 1 | 6 -(3 rows) - --- nesting with window functions -select a, b, sum(c), sum(sum(c)) over (order by a,b) as rsum - from gstest2 group by rollup (a,b) order by rsum, a, b; - a | b | sum | rsum ----+---+-----+------ - 1 | 1 | 8 | 8 - 1 | 2 | 2 | 10 - 1 | | 10 | 20 - 2 | 2 | 2 | 22 - 2 | | 2 | 24 - | | 12 | 36 -(6 rows) - --- nesting with grouping sets -select sum(c) from gstest2 - group by grouping sets((), grouping sets((), grouping sets(()))) - order by 1 desc; - sum ------ - 12 - 12 - 12 -(3 rows) - -select sum(c) from gstest2 - group by grouping sets((), grouping sets((), grouping sets(((a, b))))) - order by 1 desc; - sum ------ - 12 - 12 - 8 - 2 - 2 -(5 rows) - -select sum(c) from gstest2 - group by grouping sets(grouping sets(rollup(c), grouping sets(cube(c)))) - order by 1 desc; - sum ------ - 12 - 12 - 6 - 6 - 6 - 6 -(6 rows) - -select sum(c) from gstest2 - group by grouping sets(a, grouping sets(a, cube(b))) - order by 1 desc; - sum ------ - 12 - 10 - 10 - 8 - 4 - 2 - 2 -(7 rows) - -select sum(c) from gstest2 - group by grouping sets(grouping sets((a, (b)))) - order by 1 desc; - sum ------ - 8 - 2 - 2 -(3 rows) - -select sum(c) from gstest2 - group by grouping sets(grouping sets((a, b))) - order by 1 desc; - sum ------ - 8 - 2 - 2 -(3 rows) - -select sum(c) from gstest2 - group by grouping sets(grouping sets(a, grouping sets(a), a)) - order by 1 desc; - sum ------ - 10 - 10 - 10 - 2 - 2 - 2 -(6 rows) - -select sum(c) from gstest2 - group by grouping sets(grouping sets(a, grouping sets(a, grouping sets(a), ((a)), a, grouping sets(a), (a)), a)) - order by 1 desc; - sum ------ - 10 - 10 - 10 - 10 - 10 - 10 - 10 - 10 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 -(16 rows) - -select sum(c) from gstest2 - group by grouping sets((a,(a,b)), grouping sets((a,(a,b)),a)) - order by 1 desc; - sum ------ - 10 - 8 - 8 - 2 - 2 - 2 - 2 - 2 -(8 rows) - --- empty input: first is 0 rows, second 1, third 3 etc. -select a, b, sum(v), count(*) from gstest_empty group by grouping sets ((a,b),a); - a | b | sum | count ----+---+-----+------- -(0 rows) - -select a, b, sum(v), count(*) from gstest_empty group by grouping sets ((a,b),()); - a | b | sum | count ----+---+-----+------- - | | | 0 -(1 row) - -select a, b, sum(v), count(*) from gstest_empty group by grouping sets ((a,b),(),(),()); - a | b | sum | count ----+---+-----+------- - | | | 0 - | | | 0 - | | | 0 -(3 rows) - -select sum(v), count(*) from gstest_empty group by grouping sets ((),(),()); - sum | count ------+------- - | 0 - | 0 - | 0 -(3 rows) - --- empty input with joins tests some important code paths -select t1.a, t2.b, sum(t1.v), count(*) from gstest_empty t1, gstest_empty t2 - group by grouping sets ((t1.a,t2.b),()); - a | b | sum | count ----+---+-----+------- - | | | 0 -(1 row) - --- simple joins, var resolution, GROUPING on join vars -select t1.a, t2.b, grouping(t1.a, t2.b), sum(t1.v), max(t2.a) - from gstest1 t1, gstest2 t2 - group by grouping sets ((t1.a, t2.b), ()); - a | b | grouping | sum | max ----+---+----------+------+----- - 1 | 1 | 0 | 420 | 1 - 1 | 2 | 0 | 120 | 2 - 2 | 1 | 0 | 105 | 1 - 2 | 2 | 0 | 30 | 2 - 3 | 1 | 0 | 231 | 1 - 3 | 2 | 0 | 66 | 2 - 4 | 1 | 0 | 259 | 1 - 4 | 2 | 0 | 74 | 2 - | | 3 | 1305 | 2 -(9 rows) - -select t1.a, t2.b, grouping(t1.a, t2.b), sum(t1.v), max(t2.a) - from gstest1 t1 join gstest2 t2 on (t1.a=t2.a) - group by grouping sets ((t1.a, t2.b), ()); - a | b | grouping | sum | max ----+---+----------+-----+----- - 1 | 1 | 0 | 420 | 1 - 1 | 2 | 0 | 60 | 1 - 2 | 2 | 0 | 15 | 2 - | | 3 | 495 | 2 -(4 rows) - -select a, b, grouping(a, b), sum(t1.v), max(t2.c) - from gstest1 t1 join gstest2 t2 using (a,b) - group by grouping sets ((a, b), ()); - a | b | grouping | sum | max ----+---+----------+-----+----- - 1 | 1 | 0 | 147 | 2 - 1 | 2 | 0 | 25 | 2 - | | 3 | 172 | 2 -(3 rows) - --- check that functionally dependent cols are not nulled -select a, d, grouping(a,b,c) - from gstest3 - group by grouping sets ((a,b), (a,c)); - a | d | grouping ----+---+---------- - 1 | 1 | 1 - 2 | 2 | 1 - 1 | 1 | 2 - 2 | 2 | 2 -(4 rows) - --- check that distinct grouping columns are kept separate --- even if they are equal() -explain (costs off) -select g as alias1, g as alias2 - from generate_series(1,3) g - group by alias1, rollup(alias2); - QUERY PLAN ------------------------------------------------- - GroupAggregate - Group Key: g, g - Group Key: g - -> Sort - Sort Key: g - -> Function Scan on generate_series g -(6 rows) - -select g as alias1, g as alias2 - from generate_series(1,3) g - group by alias1, rollup(alias2); - alias1 | alias2 ---------+-------- - 1 | 1 - 1 | - 2 | 2 - 2 | - 3 | 3 - 3 | -(6 rows) - --- check that pulled-up subquery outputs still go to null when appropriate -select four, x - from (select four, ten, 'foo'::text as x from tenk1) as t - group by grouping sets (four, x) - having x = 'foo'; - four | x -------+----- - | foo -(1 row) - -select four, x || 'x' - from (select four, ten, 'foo'::text as x from tenk1) as t - group by grouping sets (four, x) - order by four; - four | ?column? -------+---------- - 0 | - 1 | - 2 | - 3 | - | foox -(5 rows) - -select (x+y)*1, sum(z) - from (select 1 as x, 2 as y, 3 as z) s - group by grouping sets (x+y, x); - ?column? | sum -----------+----- - 3 | 3 - | 3 -(2 rows) - -select x, not x as not_x, q2 from - (select *, q1 = 1 as x from int8_tbl i1) as t - group by grouping sets(x, q2) - order by x, q2; - x | not_x | q2 ----+-------+------------------- - f | t | - | | -4567890123456789 - | | 123 - | | 456 - | | 4567890123456789 -(5 rows) - --- check qual push-down rules for a subquery with grouping sets -explain (verbose, costs off) -select * from ( - select 1 as x, q1, sum(q2) - from int8_tbl i1 - group by grouping sets(1, 2) -) ss -where x = 1 and q1 = 123; - QUERY PLAN --------------------------------------------------- - Subquery Scan on ss - Output: ss.x, ss.q1, ss.sum - Filter: ((ss.x = 1) AND (ss.q1 = 123)) - -> GroupAggregate - Output: (1), i1.q1, sum(i1.q2) - Group Key: (1) - Sort Key: i1.q1 - Group Key: i1.q1 - -> Sort - Output: (1), i1.q1, i1.q2 - Sort Key: (1) - -> Seq Scan on public.int8_tbl i1 - Output: 1, i1.q1, i1.q2 -(13 rows) - -select * from ( - select 1 as x, q1, sum(q2) - from int8_tbl i1 - group by grouping sets(1, 2) -) ss -where x = 1 and q1 = 123; - x | q1 | sum ----+----+----- -(0 rows) - --- check handling of pulled-up SubPlan in GROUPING() argument (bug #17479) -explain (verbose, costs off) -select grouping(ss.x) -from int8_tbl i1 -cross join lateral (select (select i1.q1) as x) ss -group by ss.x; - QUERY PLAN ------------------------------------------------- - GroupAggregate - Output: GROUPING((SubPlan 1)), ((SubPlan 2)) - Group Key: ((SubPlan 2)) - -> Sort - Output: ((SubPlan 2)), i1.q1 - Sort Key: ((SubPlan 2)) - -> Seq Scan on public.int8_tbl i1 - Output: (SubPlan 2), i1.q1 - SubPlan 2 - -> Result - Output: i1.q1 -(11 rows) - -select grouping(ss.x) -from int8_tbl i1 -cross join lateral (select (select i1.q1) as x) ss -group by ss.x; - grouping ----------- - 0 - 0 -(2 rows) - -explain (verbose, costs off) -select (select grouping(ss.x)) -from int8_tbl i1 -cross join lateral (select (select i1.q1) as x) ss -group by ss.x; - QUERY PLAN --------------------------------------------- - GroupAggregate - Output: (SubPlan 2), ((SubPlan 3)) - Group Key: ((SubPlan 3)) - -> Sort - Output: ((SubPlan 3)), i1.q1 - Sort Key: ((SubPlan 3)) - -> Seq Scan on public.int8_tbl i1 - Output: (SubPlan 3), i1.q1 - SubPlan 3 - -> Result - Output: i1.q1 - SubPlan 2 - -> Result - Output: GROUPING((SubPlan 1)) -(14 rows) - -select (select grouping(ss.x)) -from int8_tbl i1 -cross join lateral (select (select i1.q1) as x) ss -group by ss.x; - grouping ----------- - 0 - 0 -(2 rows) - --- simple rescan tests -select a, b, sum(v.x) - from (values (1),(2)) v(x), gstest_data(v.x) - group by rollup (a,b); - a | b | sum ----+---+----- - 1 | 1 | 1 - 1 | 2 | 1 - 1 | 3 | 1 - 1 | | 3 - 2 | 1 | 2 - 2 | 2 | 2 - 2 | 3 | 2 - 2 | | 6 - | | 9 -(9 rows) - -select * - from (values (1),(2)) v(x), - lateral (select a, b, sum(v.x) from gstest_data(v.x) group by rollup (a,b)) s; -ERROR: aggregate functions are not allowed in FROM clause of their own query level -LINE 3: lateral (select a, b, sum(v.x) from gstest_data(v.x) ... - ^ --- min max optimization should still work with GROUP BY () -explain (costs off) - select min(unique1) from tenk1 GROUP BY (); - QUERY PLAN ------------------------------------------------------------- - Result - InitPlan 1 - -> Limit - -> Index Only Scan using tenk1_unique1 on tenk1 - Index Cond: (unique1 IS NOT NULL) -(5 rows) - --- Views with GROUPING SET queries -CREATE VIEW gstest_view AS select a, b, grouping(a,b), sum(c), count(*), max(c) - from gstest2 group by rollup ((a,b,c),(c,d)); -NOTICE: view "gstest_view" will be a temporary view -select pg_get_viewdef('gstest_view'::regclass, true); - pg_get_viewdef ---------------------------------------- - SELECT a, + - b, + - GROUPING(a, b) AS "grouping", + - sum(c) AS sum, + - count(*) AS count, + - max(c) AS max + - FROM gstest2 + - GROUP BY ROLLUP((a, b, c), (c, d)); -(1 row) - --- Nested queries with 3 or more levels of nesting -select(select (select grouping(a,b) from (values (1)) v2(c)) from (values (1,2)) v1(a,b) group by (a,b)) from (values(6,7)) v3(e,f) GROUP BY ROLLUP(e,f); - grouping ----------- - 0 - 0 - 0 -(3 rows) - -select(select (select grouping(e,f) from (values (1)) v2(c)) from (values (1,2)) v1(a,b) group by (a,b)) from (values(6,7)) v3(e,f) GROUP BY ROLLUP(e,f); - grouping ----------- - 0 - 1 - 3 -(3 rows) - -select(select (select grouping(c) from (values (1)) v2(c) GROUP BY c) from (values (1,2)) v1(a,b) group by (a,b)) from (values(6,7)) v3(e,f) GROUP BY ROLLUP(e,f); - grouping ----------- - 0 - 0 - 0 -(3 rows) - --- Combinations of operations -select a, b, c, d from gstest2 group by rollup(a,b),grouping sets(c,d); - a | b | c | d ----+---+---+--- - 1 | 1 | 1 | - 1 | | 1 | - | | 1 | - 1 | 1 | 2 | - 1 | 2 | 2 | - 1 | | 2 | - 2 | 2 | 2 | - 2 | | 2 | - | | 2 | - 1 | 1 | | 1 - 1 | | | 1 - | | | 1 - 1 | 1 | | 2 - 1 | 2 | | 2 - 1 | | | 2 - 2 | 2 | | 2 - 2 | | | 2 - | | | 2 -(18 rows) - -select a, b from (values (1,2),(2,3)) v(a,b) group by a,b, grouping sets(a); - a | b ----+--- - 1 | 2 - 2 | 3 -(2 rows) - --- Tests for chained aggregates -select a, b, grouping(a,b), sum(v), count(*), max(v) - from gstest1 group by grouping sets ((a,b),(a+1,b+1),(a+2,b+2)) order by 3,6; - a | b | grouping | sum | count | max ----+---+----------+-----+-------+----- - 1 | 1 | 0 | 21 | 2 | 11 - 1 | 2 | 0 | 25 | 2 | 13 - 1 | 3 | 0 | 14 | 1 | 14 - 2 | 3 | 0 | 15 | 1 | 15 - 3 | 3 | 0 | 16 | 1 | 16 - 3 | 4 | 0 | 17 | 1 | 17 - 4 | 1 | 0 | 37 | 2 | 19 - | | 3 | 21 | 2 | 11 - | | 3 | 21 | 2 | 11 - | | 3 | 25 | 2 | 13 - | | 3 | 25 | 2 | 13 - | | 3 | 14 | 1 | 14 - | | 3 | 14 | 1 | 14 - | | 3 | 15 | 1 | 15 - | | 3 | 15 | 1 | 15 - | | 3 | 16 | 1 | 16 - | | 3 | 16 | 1 | 16 - | | 3 | 17 | 1 | 17 - | | 3 | 17 | 1 | 17 - | | 3 | 37 | 2 | 19 - | | 3 | 37 | 2 | 19 -(21 rows) - -select(select (select grouping(a,b) from (values (1)) v2(c)) from (values (1,2)) v1(a,b) group by (a,b)) from (values(6,7)) v3(e,f) GROUP BY ROLLUP((e+1),(f+1)); - grouping ----------- - 0 - 0 - 0 -(3 rows) - -select(select (select grouping(a,b) from (values (1)) v2(c)) from (values (1,2)) v1(a,b) group by (a,b)) from (values(6,7)) v3(e,f) GROUP BY CUBE((e+1),(f+1)) ORDER BY (e+1),(f+1); - grouping ----------- - 0 - 0 - 0 - 0 -(4 rows) - -select a, b, sum(c), sum(sum(c)) over (order by a,b) as rsum - from gstest2 group by cube (a,b) order by rsum, a, b; - a | b | sum | rsum ----+---+-----+------ - 1 | 1 | 8 | 8 - 1 | 2 | 2 | 10 - 1 | | 10 | 20 - 2 | 2 | 2 | 22 - 2 | | 2 | 24 - | 1 | 8 | 32 - | 2 | 4 | 36 - | | 12 | 48 -(8 rows) - -select a, b, sum(c) from (values (1,1,10),(1,1,11),(1,2,12),(1,2,13),(1,3,14),(2,3,15),(3,3,16),(3,4,17),(4,1,18),(4,1,19)) v(a,b,c) group by rollup (a,b); - a | b | sum ----+---+----- - 1 | 1 | 21 - 1 | 2 | 25 - 1 | 3 | 14 - 1 | | 60 - 2 | 3 | 15 - 2 | | 15 - 3 | 3 | 16 - 3 | 4 | 17 - 3 | | 33 - 4 | 1 | 37 - 4 | | 37 - | | 145 -(12 rows) - -select a, b, sum(v.x) - from (values (1),(2)) v(x), gstest_data(v.x) - group by cube (a,b) order by a,b; - a | b | sum ----+---+----- - 1 | 1 | 1 - 1 | 2 | 1 - 1 | 3 | 1 - 1 | | 3 - 2 | 1 | 2 - 2 | 2 | 2 - 2 | 3 | 2 - 2 | | 6 - | 1 | 3 - | 2 | 3 - | 3 | 3 - | | 9 -(12 rows) - --- Test reordering of grouping sets -explain (costs off) -select * from gstest1 group by grouping sets((a,b,v),(v)) order by v,b,a; - QUERY PLAN ------------------------------------------------------------------------------------- - Incremental Sort - Sort Key: "*VALUES*".column3, "*VALUES*".column2, "*VALUES*".column1 - Presorted Key: "*VALUES*".column3 - -> GroupAggregate - Group Key: "*VALUES*".column3, "*VALUES*".column2, "*VALUES*".column1 - Group Key: "*VALUES*".column3 - -> Sort - Sort Key: "*VALUES*".column3, "*VALUES*".column2, "*VALUES*".column1 - -> Values Scan on "*VALUES*" -(9 rows) - --- Agg level check. This query should error out. -select (select grouping(a,b) from gstest2) from gstest2 group by a,b; -ERROR: arguments to GROUPING must be grouping expressions of the associated query level -LINE 1: select (select grouping(a,b) from gstest2) from gstest2 grou... - ^ ---Nested queries -select a, b, sum(c), count(*) from gstest2 group by grouping sets (rollup(a,b),a); - a | b | sum | count ----+---+-----+------- - 1 | 1 | 8 | 7 - 1 | 2 | 2 | 1 - 1 | | 10 | 8 - 1 | | 10 | 8 - 2 | 2 | 2 | 1 - 2 | | 2 | 1 - 2 | | 2 | 1 - | | 12 | 9 -(8 rows) - --- HAVING queries -select ten, sum(distinct four) from onek a -group by grouping sets((ten,four),(ten)) -having exists (select 1 from onek b where sum(distinct a.four) = b.four); - ten | sum ------+----- - 0 | 0 - 0 | 2 - 0 | 2 - 1 | 1 - 1 | 3 - 2 | 0 - 2 | 2 - 2 | 2 - 3 | 1 - 3 | 3 - 4 | 0 - 4 | 2 - 4 | 2 - 5 | 1 - 5 | 3 - 6 | 0 - 6 | 2 - 6 | 2 - 7 | 1 - 7 | 3 - 8 | 0 - 8 | 2 - 8 | 2 - 9 | 1 - 9 | 3 -(25 rows) - --- Tests around pushdown of HAVING clauses, partially testing against previous bugs -select a,count(*) from gstest2 group by rollup(a) order by a; - a | count ----+------- - 1 | 8 - 2 | 1 - | 9 -(3 rows) - -select a,count(*) from gstest2 group by rollup(a) having a is distinct from 1 order by a; - a | count ----+------- - 2 | 1 - | 9 -(2 rows) - -explain (costs off) - select a,count(*) from gstest2 group by rollup(a) having a is distinct from 1 order by a; - QUERY PLAN ----------------------------------------- - Sort - Sort Key: a - -> GroupAggregate - Group Key: a - Group Key: () - Filter: (a IS DISTINCT FROM 1) - -> Sort - Sort Key: a - -> Seq Scan on gstest2 -(9 rows) - -select v.c, (select count(*) from gstest2 group by () having v.c) - from (values (false),(true)) v(c) order by v.c; - c | count ----+------- - f | - t | 9 -(2 rows) - -explain (costs off) - select v.c, (select count(*) from gstest2 group by () having v.c) - from (values (false),(true)) v(c) order by v.c; - QUERY PLAN ------------------------------------------------------------ - Sort - Sort Key: "*VALUES*".column1 - -> Values Scan on "*VALUES*" - SubPlan 1 - -> Aggregate - Group Key: () - Filter: "*VALUES*".column1 - -> Result - One-Time Filter: "*VALUES*".column1 - -> Seq Scan on gstest2 -(10 rows) - --- HAVING with GROUPING queries -select ten, grouping(ten) from onek -group by grouping sets(ten) having grouping(ten) >= 0 -order by 2,1; - ten | grouping ------+---------- - 0 | 0 - 1 | 0 - 2 | 0 - 3 | 0 - 4 | 0 - 5 | 0 - 6 | 0 - 7 | 0 - 8 | 0 - 9 | 0 -(10 rows) - -select ten, grouping(ten) from onek -group by grouping sets(ten, four) having grouping(ten) > 0 -order by 2,1; - ten | grouping ------+---------- - | 1 - | 1 - | 1 - | 1 -(4 rows) - -select ten, grouping(ten) from onek -group by rollup(ten) having grouping(ten) > 0 -order by 2,1; - ten | grouping ------+---------- - | 1 -(1 row) - -select ten, grouping(ten) from onek -group by cube(ten) having grouping(ten) > 0 -order by 2,1; - ten | grouping ------+---------- - | 1 -(1 row) - -select ten, grouping(ten) from onek -group by (ten) having grouping(ten) >= 0 -order by 2,1; - ten | grouping ------+---------- - 0 | 0 - 1 | 0 - 2 | 0 - 3 | 0 - 4 | 0 - 5 | 0 - 6 | 0 - 7 | 0 - 8 | 0 - 9 | 0 -(10 rows) - --- FILTER queries -select ten, sum(distinct four) filter (where four::text ~ '123') from onek a -group by rollup(ten); - ten | sum ------+----- - 0 | - 1 | - 2 | - 3 | - 4 | - 5 | - 6 | - 7 | - 8 | - 9 | - | -(11 rows) - --- More rescan tests -select * from (values (1),(2)) v(a) left join lateral (select v.a, four, ten, count(*) from onek group by cube(four,ten)) s on true order by v.a,four,ten; - a | a | four | ten | count ----+---+------+-----+------- - 1 | 1 | 0 | 0 | 50 - 1 | 1 | 0 | 2 | 50 - 1 | 1 | 0 | 4 | 50 - 1 | 1 | 0 | 6 | 50 - 1 | 1 | 0 | 8 | 50 - 1 | 1 | 0 | | 250 - 1 | 1 | 1 | 1 | 50 - 1 | 1 | 1 | 3 | 50 - 1 | 1 | 1 | 5 | 50 - 1 | 1 | 1 | 7 | 50 - 1 | 1 | 1 | 9 | 50 - 1 | 1 | 1 | | 250 - 1 | 1 | 2 | 0 | 50 - 1 | 1 | 2 | 2 | 50 - 1 | 1 | 2 | 4 | 50 - 1 | 1 | 2 | 6 | 50 - 1 | 1 | 2 | 8 | 50 - 1 | 1 | 2 | | 250 - 1 | 1 | 3 | 1 | 50 - 1 | 1 | 3 | 3 | 50 - 1 | 1 | 3 | 5 | 50 - 1 | 1 | 3 | 7 | 50 - 1 | 1 | 3 | 9 | 50 - 1 | 1 | 3 | | 250 - 1 | 1 | | 0 | 100 - 1 | 1 | | 1 | 100 - 1 | 1 | | 2 | 100 - 1 | 1 | | 3 | 100 - 1 | 1 | | 4 | 100 - 1 | 1 | | 5 | 100 - 1 | 1 | | 6 | 100 - 1 | 1 | | 7 | 100 - 1 | 1 | | 8 | 100 - 1 | 1 | | 9 | 100 - 1 | 1 | | | 1000 - 2 | 2 | 0 | 0 | 50 - 2 | 2 | 0 | 2 | 50 - 2 | 2 | 0 | 4 | 50 - 2 | 2 | 0 | 6 | 50 - 2 | 2 | 0 | 8 | 50 - 2 | 2 | 0 | | 250 - 2 | 2 | 1 | 1 | 50 - 2 | 2 | 1 | 3 | 50 - 2 | 2 | 1 | 5 | 50 - 2 | 2 | 1 | 7 | 50 - 2 | 2 | 1 | 9 | 50 - 2 | 2 | 1 | | 250 - 2 | 2 | 2 | 0 | 50 - 2 | 2 | 2 | 2 | 50 - 2 | 2 | 2 | 4 | 50 - 2 | 2 | 2 | 6 | 50 - 2 | 2 | 2 | 8 | 50 - 2 | 2 | 2 | | 250 - 2 | 2 | 3 | 1 | 50 - 2 | 2 | 3 | 3 | 50 - 2 | 2 | 3 | 5 | 50 - 2 | 2 | 3 | 7 | 50 - 2 | 2 | 3 | 9 | 50 - 2 | 2 | 3 | | 250 - 2 | 2 | | 0 | 100 - 2 | 2 | | 1 | 100 - 2 | 2 | | 2 | 100 - 2 | 2 | | 3 | 100 - 2 | 2 | | 4 | 100 - 2 | 2 | | 5 | 100 - 2 | 2 | | 6 | 100 - 2 | 2 | | 7 | 100 - 2 | 2 | | 8 | 100 - 2 | 2 | | 9 | 100 - 2 | 2 | | | 1000 -(70 rows) - -select array(select row(v.a,s1.*) from (select two,four, count(*) from onek group by cube(two,four) order by two,four) s1) from (values (1),(2)) v(a); - array ------------------------------------------------------------------------------------------------------------------------------------------------------- - {"(1,0,0,250)","(1,0,2,250)","(1,0,,500)","(1,1,1,250)","(1,1,3,250)","(1,1,,500)","(1,,0,250)","(1,,1,250)","(1,,2,250)","(1,,3,250)","(1,,,1000)"} - {"(2,0,0,250)","(2,0,2,250)","(2,0,,500)","(2,1,1,250)","(2,1,3,250)","(2,1,,500)","(2,,0,250)","(2,,1,250)","(2,,2,250)","(2,,3,250)","(2,,,1000)"} -(2 rows) - --- Grouping on text columns -select sum(ten) from onek group by two, rollup(four::text) order by 1; - sum ------- - 1000 - 1000 - 1250 - 1250 - 2000 - 2500 -(6 rows) - -select sum(ten) from onek group by rollup(four::text), two order by 1; - sum ------- - 1000 - 1000 - 1250 - 1250 - 2000 - 2500 -(6 rows) - --- hashing support -set enable_hashagg = true; --- failure cases -select count(*) from gstest4 group by rollup(unhashable_col,unsortable_col); -ERROR: could not implement GROUP BY -DETAIL: Some of the datatypes only support hashing, while others only support sorting. -select array_agg(v order by v) from gstest4 group by grouping sets ((id,unsortable_col),(id)); -ERROR: could not implement GROUP BY -DETAIL: Some of the datatypes only support hashing, while others only support sorting. --- simple cases -select a, b, grouping(a,b), sum(v), count(*), max(v) - from gstest1 group by grouping sets ((a),(b)) order by 3,1,2; - a | b | grouping | sum | count | max ----+---+----------+-----+-------+----- - 1 | | 1 | 60 | 5 | 14 - 2 | | 1 | 15 | 1 | 15 - 3 | | 1 | 33 | 2 | 17 - 4 | | 1 | 37 | 2 | 19 - | 1 | 2 | 58 | 4 | 19 - | 2 | 2 | 25 | 2 | 13 - | 3 | 2 | 45 | 3 | 16 - | 4 | 2 | 17 | 1 | 17 -(8 rows) - -explain (costs off) select a, b, grouping(a,b), sum(v), count(*), max(v) - from gstest1 group by grouping sets ((a),(b)) order by 3,1,2; - QUERY PLAN --------------------------------------------------------------------------------------------------------- - Sort - Sort Key: (GROUPING("*VALUES*".column1, "*VALUES*".column2)), "*VALUES*".column1, "*VALUES*".column2 - -> HashAggregate - Hash Key: "*VALUES*".column1 - Hash Key: "*VALUES*".column2 - -> Values Scan on "*VALUES*" -(6 rows) - -select a, b, grouping(a,b), sum(v), count(*), max(v) - from gstest1 group by cube(a,b) order by 3,1,2; - a | b | grouping | sum | count | max ----+---+----------+-----+-------+----- - 1 | 1 | 0 | 21 | 2 | 11 - 1 | 2 | 0 | 25 | 2 | 13 - 1 | 3 | 0 | 14 | 1 | 14 - 2 | 3 | 0 | 15 | 1 | 15 - 3 | 3 | 0 | 16 | 1 | 16 - 3 | 4 | 0 | 17 | 1 | 17 - 4 | 1 | 0 | 37 | 2 | 19 - 1 | | 1 | 60 | 5 | 14 - 2 | | 1 | 15 | 1 | 15 - 3 | | 1 | 33 | 2 | 17 - 4 | | 1 | 37 | 2 | 19 - | 1 | 2 | 58 | 4 | 19 - | 2 | 2 | 25 | 2 | 13 - | 3 | 2 | 45 | 3 | 16 - | 4 | 2 | 17 | 1 | 17 - | | 3 | 145 | 10 | 19 -(16 rows) - -explain (costs off) select a, b, grouping(a,b), sum(v), count(*), max(v) - from gstest1 group by cube(a,b) order by 3,1,2; - QUERY PLAN --------------------------------------------------------------------------------------------------------- - Sort - Sort Key: (GROUPING("*VALUES*".column1, "*VALUES*".column2)), "*VALUES*".column1, "*VALUES*".column2 - -> MixedAggregate - Hash Key: "*VALUES*".column1, "*VALUES*".column2 - Hash Key: "*VALUES*".column1 - Hash Key: "*VALUES*".column2 - Group Key: () - -> Values Scan on "*VALUES*" -(8 rows) - --- shouldn't try and hash -explain (costs off) - select a, b, grouping(a,b), array_agg(v order by v) - from gstest1 group by cube(a,b); - QUERY PLAN ----------------------------------------------------------- - GroupAggregate - Group Key: "*VALUES*".column1, "*VALUES*".column2 - Group Key: "*VALUES*".column1 - Group Key: () - Sort Key: "*VALUES*".column2 - Group Key: "*VALUES*".column2 - -> Sort - Sort Key: "*VALUES*".column1, "*VALUES*".column2 - -> Values Scan on "*VALUES*" -(9 rows) - --- unsortable cases -select unsortable_col, count(*) - from gstest4 group by grouping sets ((unsortable_col),(unsortable_col)) - order by unsortable_col::text; - unsortable_col | count -----------------+------- - 1 | 4 - 1 | 4 - 2 | 4 - 2 | 4 -(4 rows) - --- mixed hashable/sortable cases -select unhashable_col, unsortable_col, - grouping(unhashable_col, unsortable_col), - count(*), sum(v) - from gstest4 group by grouping sets ((unhashable_col),(unsortable_col)) - order by 3, 5; - unhashable_col | unsortable_col | grouping | count | sum -----------------+----------------+----------+-------+----- - 0000 | | 1 | 2 | 17 - 0001 | | 1 | 2 | 34 - 0010 | | 1 | 2 | 68 - 0011 | | 1 | 2 | 136 - | 2 | 2 | 4 | 60 - | 1 | 2 | 4 | 195 -(6 rows) - -explain (costs off) - select unhashable_col, unsortable_col, - grouping(unhashable_col, unsortable_col), - count(*), sum(v) - from gstest4 group by grouping sets ((unhashable_col),(unsortable_col)) - order by 3,5; - QUERY PLAN ------------------------------------------------------------------- - Sort - Sort Key: (GROUPING(unhashable_col, unsortable_col)), (sum(v)) - -> MixedAggregate - Hash Key: unsortable_col - Group Key: unhashable_col - -> Sort - Sort Key: unhashable_col - -> Seq Scan on gstest4 -(8 rows) - -select unhashable_col, unsortable_col, - grouping(unhashable_col, unsortable_col), - count(*), sum(v) - from gstest4 group by grouping sets ((v,unhashable_col),(v,unsortable_col)) - order by 3,5; - unhashable_col | unsortable_col | grouping | count | sum -----------------+----------------+----------+-------+----- - 0000 | | 1 | 1 | 1 - 0001 | | 1 | 1 | 2 - 0010 | | 1 | 1 | 4 - 0011 | | 1 | 1 | 8 - 0000 | | 1 | 1 | 16 - 0001 | | 1 | 1 | 32 - 0010 | | 1 | 1 | 64 - 0011 | | 1 | 1 | 128 - | 1 | 2 | 1 | 1 - | 1 | 2 | 1 | 2 - | 2 | 2 | 1 | 4 - | 2 | 2 | 1 | 8 - | 2 | 2 | 1 | 16 - | 2 | 2 | 1 | 32 - | 1 | 2 | 1 | 64 - | 1 | 2 | 1 | 128 -(16 rows) - -explain (costs off) - select unhashable_col, unsortable_col, - grouping(unhashable_col, unsortable_col), - count(*), sum(v) - from gstest4 group by grouping sets ((v,unhashable_col),(v,unsortable_col)) - order by 3,5; - QUERY PLAN ------------------------------------------------------------------- - Sort - Sort Key: (GROUPING(unhashable_col, unsortable_col)), (sum(v)) - -> MixedAggregate - Hash Key: v, unsortable_col - Group Key: v, unhashable_col - -> Sort - Sort Key: v, unhashable_col - -> Seq Scan on gstest4 -(8 rows) - --- empty input: first is 0 rows, second 1, third 3 etc. -select a, b, sum(v), count(*) from gstest_empty group by grouping sets ((a,b),a); - a | b | sum | count ----+---+-----+------- -(0 rows) - -explain (costs off) - select a, b, sum(v), count(*) from gstest_empty group by grouping sets ((a,b),a); - QUERY PLAN --------------------------------- - HashAggregate - Hash Key: a, b - Hash Key: a - -> Seq Scan on gstest_empty -(4 rows) - -select a, b, sum(v), count(*) from gstest_empty group by grouping sets ((a,b),()); - a | b | sum | count ----+---+-----+------- - | | | 0 -(1 row) - -select a, b, sum(v), count(*) from gstest_empty group by grouping sets ((a,b),(),(),()); - a | b | sum | count ----+---+-----+------- - | | | 0 - | | | 0 - | | | 0 -(3 rows) - -explain (costs off) - select a, b, sum(v), count(*) from gstest_empty group by grouping sets ((a,b),(),(),()); - QUERY PLAN --------------------------------- - MixedAggregate - Hash Key: a, b - Group Key: () - Group Key: () - Group Key: () - -> Seq Scan on gstest_empty -(6 rows) - -select sum(v), count(*) from gstest_empty group by grouping sets ((),(),()); - sum | count ------+------- - | 0 - | 0 - | 0 -(3 rows) - -explain (costs off) - select sum(v), count(*) from gstest_empty group by grouping sets ((),(),()); - QUERY PLAN --------------------------------- - Aggregate - Group Key: () - Group Key: () - Group Key: () - -> Seq Scan on gstest_empty -(5 rows) - --- check that functionally dependent cols are not nulled -select a, d, grouping(a,b,c) - from gstest3 - group by grouping sets ((a,b), (a,c)); - a | d | grouping ----+---+---------- - 1 | 1 | 1 - 2 | 2 | 1 - 1 | 1 | 2 - 2 | 2 | 2 -(4 rows) - -explain (costs off) - select a, d, grouping(a,b,c) - from gstest3 - group by grouping sets ((a,b), (a,c)); - QUERY PLAN ---------------------------- - HashAggregate - Hash Key: a, b - Hash Key: a, c - -> Seq Scan on gstest3 -(4 rows) - --- simple rescan tests -select a, b, sum(v.x) - from (values (1),(2)) v(x), gstest_data(v.x) - group by grouping sets (a,b) - order by 1, 2, 3; - a | b | sum ----+---+----- - 1 | | 3 - 2 | | 6 - | 1 | 3 - | 2 | 3 - | 3 | 3 -(5 rows) - -explain (costs off) - select a, b, sum(v.x) - from (values (1),(2)) v(x), gstest_data(v.x) - group by grouping sets (a,b) - order by 3, 1, 2; - QUERY PLAN ---------------------------------------------------------------------- - Sort - Sort Key: (sum("*VALUES*".column1)), gstest_data.a, gstest_data.b - -> HashAggregate - Hash Key: gstest_data.a - Hash Key: gstest_data.b - -> Nested Loop - -> Values Scan on "*VALUES*" - -> Function Scan on gstest_data -(8 rows) - -select * - from (values (1),(2)) v(x), - lateral (select a, b, sum(v.x) from gstest_data(v.x) group by grouping sets (a,b)) s; -ERROR: aggregate functions are not allowed in FROM clause of their own query level -LINE 3: lateral (select a, b, sum(v.x) from gstest_data(v.x) ... - ^ -explain (costs off) - select * - from (values (1),(2)) v(x), - lateral (select a, b, sum(v.x) from gstest_data(v.x) group by grouping sets (a,b)) s; -ERROR: aggregate functions are not allowed in FROM clause of their own query level -LINE 4: lateral (select a, b, sum(v.x) from gstest_data(v.x... - ^ --- Tests for chained aggregates -select a, b, grouping(a,b), sum(v), count(*), max(v) - from gstest1 group by grouping sets ((a,b),(a+1,b+1),(a+2,b+2)) order by 3,6; - a | b | grouping | sum | count | max ----+---+----------+-----+-------+----- - 1 | 1 | 0 | 21 | 2 | 11 - 1 | 2 | 0 | 25 | 2 | 13 - 1 | 3 | 0 | 14 | 1 | 14 - 2 | 3 | 0 | 15 | 1 | 15 - 3 | 3 | 0 | 16 | 1 | 16 - 3 | 4 | 0 | 17 | 1 | 17 - 4 | 1 | 0 | 37 | 2 | 19 - | | 3 | 21 | 2 | 11 - | | 3 | 21 | 2 | 11 - | | 3 | 25 | 2 | 13 - | | 3 | 25 | 2 | 13 - | | 3 | 14 | 1 | 14 - | | 3 | 14 | 1 | 14 - | | 3 | 15 | 1 | 15 - | | 3 | 15 | 1 | 15 - | | 3 | 16 | 1 | 16 - | | 3 | 16 | 1 | 16 - | | 3 | 17 | 1 | 17 - | | 3 | 17 | 1 | 17 - | | 3 | 37 | 2 | 19 - | | 3 | 37 | 2 | 19 -(21 rows) - -explain (costs off) - select a, b, grouping(a,b), sum(v), count(*), max(v) - from gstest1 group by grouping sets ((a,b),(a+1,b+1),(a+2,b+2)) order by 3,6; - QUERY PLAN -------------------------------------------------------------------------------------------- - Sort - Sort Key: (GROUPING("*VALUES*".column1, "*VALUES*".column2)), (max("*VALUES*".column3)) - -> HashAggregate - Hash Key: "*VALUES*".column1, "*VALUES*".column2 - Hash Key: ("*VALUES*".column1 + 1), ("*VALUES*".column2 + 1) - Hash Key: ("*VALUES*".column1 + 2), ("*VALUES*".column2 + 2) - -> Values Scan on "*VALUES*" -(7 rows) - -select a, b, sum(c), sum(sum(c)) over (order by a,b) as rsum - from gstest2 group by cube (a,b) order by rsum, a, b; - a | b | sum | rsum ----+---+-----+------ - 1 | 1 | 8 | 8 - 1 | 2 | 2 | 10 - 1 | | 10 | 20 - 2 | 2 | 2 | 22 - 2 | | 2 | 24 - | 1 | 8 | 32 - | 2 | 4 | 36 - | | 12 | 48 -(8 rows) - -explain (costs off) - select a, b, sum(c), sum(sum(c)) over (order by a,b) as rsum - from gstest2 group by cube (a,b) order by rsum, a, b; - QUERY PLAN ---------------------------------------------- - Sort - Sort Key: (sum((sum(c))) OVER (?)), a, b - -> WindowAgg - -> Sort - Sort Key: a, b - -> MixedAggregate - Hash Key: a, b - Hash Key: a - Hash Key: b - Group Key: () - -> Seq Scan on gstest2 -(11 rows) - -select a, b, sum(v.x) - from (values (1),(2)) v(x), gstest_data(v.x) - group by cube (a,b) order by a,b; - a | b | sum ----+---+----- - 1 | 1 | 1 - 1 | 2 | 1 - 1 | 3 | 1 - 1 | | 3 - 2 | 1 | 2 - 2 | 2 | 2 - 2 | 3 | 2 - 2 | | 6 - | 1 | 3 - | 2 | 3 - | 3 | 3 - | | 9 -(12 rows) - -explain (costs off) - select a, b, sum(v.x) - from (values (1),(2)) v(x), gstest_data(v.x) - group by cube (a,b) order by a,b; - QUERY PLAN ------------------------------------------------- - Sort - Sort Key: gstest_data.a, gstest_data.b - -> MixedAggregate - Hash Key: gstest_data.a, gstest_data.b - Hash Key: gstest_data.a - Hash Key: gstest_data.b - Group Key: () - -> Nested Loop - -> Values Scan on "*VALUES*" - -> Function Scan on gstest_data -(10 rows) - --- Verify that we correctly handle the child node returning a --- non-minimal slot, which happens if the input is pre-sorted, --- e.g. due to an index scan. -BEGIN; -SET LOCAL enable_hashagg = false; -EXPLAIN (COSTS OFF) SELECT a, b, count(*), max(a), max(b) FROM gstest3 GROUP BY GROUPING SETS(a, b,()) ORDER BY a, b; - QUERY PLAN ---------------------------------------- - Sort - Sort Key: a, b - -> GroupAggregate - Group Key: a - Group Key: () - Sort Key: b - Group Key: b - -> Sort - Sort Key: a - -> Seq Scan on gstest3 -(10 rows) - -SELECT a, b, count(*), max(a), max(b) FROM gstest3 GROUP BY GROUPING SETS(a, b,()) ORDER BY a, b; - a | b | count | max | max ----+---+-------+-----+----- - 1 | | 1 | 1 | 1 - 2 | | 1 | 2 | 2 - | 1 | 1 | 1 | 1 - | 2 | 1 | 2 | 2 - | | 2 | 2 | 2 -(5 rows) - -SET LOCAL enable_seqscan = false; -EXPLAIN (COSTS OFF) SELECT a, b, count(*), max(a), max(b) FROM gstest3 GROUP BY GROUPING SETS(a, b,()) ORDER BY a, b; - QUERY PLAN ------------------------------------------------------- - Sort - Sort Key: a, b - -> GroupAggregate - Group Key: a - Group Key: () - Sort Key: b - Group Key: b - -> Index Scan using gstest3_pkey on gstest3 -(8 rows) - -SELECT a, b, count(*), max(a), max(b) FROM gstest3 GROUP BY GROUPING SETS(a, b,()) ORDER BY a, b; - a | b | count | max | max ----+---+-------+-----+----- - 1 | | 1 | 1 | 1 - 2 | | 1 | 2 | 2 - | 1 | 1 | 1 | 1 - | 2 | 1 | 2 | 2 - | | 2 | 2 | 2 -(5 rows) - -COMMIT; --- More rescan tests -select * from (values (1),(2)) v(a) left join lateral (select v.a, four, ten, count(*) from onek group by cube(four,ten)) s on true order by v.a,four,ten; - a | a | four | ten | count ----+---+------+-----+------- - 1 | 1 | 0 | 0 | 50 - 1 | 1 | 0 | 2 | 50 - 1 | 1 | 0 | 4 | 50 - 1 | 1 | 0 | 6 | 50 - 1 | 1 | 0 | 8 | 50 - 1 | 1 | 0 | | 250 - 1 | 1 | 1 | 1 | 50 - 1 | 1 | 1 | 3 | 50 - 1 | 1 | 1 | 5 | 50 - 1 | 1 | 1 | 7 | 50 - 1 | 1 | 1 | 9 | 50 - 1 | 1 | 1 | | 250 - 1 | 1 | 2 | 0 | 50 - 1 | 1 | 2 | 2 | 50 - 1 | 1 | 2 | 4 | 50 - 1 | 1 | 2 | 6 | 50 - 1 | 1 | 2 | 8 | 50 - 1 | 1 | 2 | | 250 - 1 | 1 | 3 | 1 | 50 - 1 | 1 | 3 | 3 | 50 - 1 | 1 | 3 | 5 | 50 - 1 | 1 | 3 | 7 | 50 - 1 | 1 | 3 | 9 | 50 - 1 | 1 | 3 | | 250 - 1 | 1 | | 0 | 100 - 1 | 1 | | 1 | 100 - 1 | 1 | | 2 | 100 - 1 | 1 | | 3 | 100 - 1 | 1 | | 4 | 100 - 1 | 1 | | 5 | 100 - 1 | 1 | | 6 | 100 - 1 | 1 | | 7 | 100 - 1 | 1 | | 8 | 100 - 1 | 1 | | 9 | 100 - 1 | 1 | | | 1000 - 2 | 2 | 0 | 0 | 50 - 2 | 2 | 0 | 2 | 50 - 2 | 2 | 0 | 4 | 50 - 2 | 2 | 0 | 6 | 50 - 2 | 2 | 0 | 8 | 50 - 2 | 2 | 0 | | 250 - 2 | 2 | 1 | 1 | 50 - 2 | 2 | 1 | 3 | 50 - 2 | 2 | 1 | 5 | 50 - 2 | 2 | 1 | 7 | 50 - 2 | 2 | 1 | 9 | 50 - 2 | 2 | 1 | | 250 - 2 | 2 | 2 | 0 | 50 - 2 | 2 | 2 | 2 | 50 - 2 | 2 | 2 | 4 | 50 - 2 | 2 | 2 | 6 | 50 - 2 | 2 | 2 | 8 | 50 - 2 | 2 | 2 | | 250 - 2 | 2 | 3 | 1 | 50 - 2 | 2 | 3 | 3 | 50 - 2 | 2 | 3 | 5 | 50 - 2 | 2 | 3 | 7 | 50 - 2 | 2 | 3 | 9 | 50 - 2 | 2 | 3 | | 250 - 2 | 2 | | 0 | 100 - 2 | 2 | | 1 | 100 - 2 | 2 | | 2 | 100 - 2 | 2 | | 3 | 100 - 2 | 2 | | 4 | 100 - 2 | 2 | | 5 | 100 - 2 | 2 | | 6 | 100 - 2 | 2 | | 7 | 100 - 2 | 2 | | 8 | 100 - 2 | 2 | | 9 | 100 - 2 | 2 | | | 1000 -(70 rows) - -select array(select row(v.a,s1.*) from (select two,four, count(*) from onek group by cube(two,four) order by two,four) s1) from (values (1),(2)) v(a); - array ------------------------------------------------------------------------------------------------------------------------------------------------------- - {"(1,0,0,250)","(1,0,2,250)","(1,0,,500)","(1,1,1,250)","(1,1,3,250)","(1,1,,500)","(1,,0,250)","(1,,1,250)","(1,,2,250)","(1,,3,250)","(1,,,1000)"} - {"(2,0,0,250)","(2,0,2,250)","(2,0,,500)","(2,1,1,250)","(2,1,3,250)","(2,1,,500)","(2,,0,250)","(2,,1,250)","(2,,2,250)","(2,,3,250)","(2,,,1000)"} -(2 rows) - --- Rescan logic changes when there are no empty grouping sets, so test --- that too: -select * from (values (1),(2)) v(a) left join lateral (select v.a, four, ten, count(*) from onek group by grouping sets(four,ten)) s on true order by v.a,four,ten; - a | a | four | ten | count ----+---+------+-----+------- - 1 | 1 | 0 | | 250 - 1 | 1 | 1 | | 250 - 1 | 1 | 2 | | 250 - 1 | 1 | 3 | | 250 - 1 | 1 | | 0 | 100 - 1 | 1 | | 1 | 100 - 1 | 1 | | 2 | 100 - 1 | 1 | | 3 | 100 - 1 | 1 | | 4 | 100 - 1 | 1 | | 5 | 100 - 1 | 1 | | 6 | 100 - 1 | 1 | | 7 | 100 - 1 | 1 | | 8 | 100 - 1 | 1 | | 9 | 100 - 2 | 2 | 0 | | 250 - 2 | 2 | 1 | | 250 - 2 | 2 | 2 | | 250 - 2 | 2 | 3 | | 250 - 2 | 2 | | 0 | 100 - 2 | 2 | | 1 | 100 - 2 | 2 | | 2 | 100 - 2 | 2 | | 3 | 100 - 2 | 2 | | 4 | 100 - 2 | 2 | | 5 | 100 - 2 | 2 | | 6 | 100 - 2 | 2 | | 7 | 100 - 2 | 2 | | 8 | 100 - 2 | 2 | | 9 | 100 -(28 rows) - -select array(select row(v.a,s1.*) from (select two,four, count(*) from onek group by grouping sets(two,four) order by two,four) s1) from (values (1),(2)) v(a); - array ---------------------------------------------------------------------------------- - {"(1,0,,500)","(1,1,,500)","(1,,0,250)","(1,,1,250)","(1,,2,250)","(1,,3,250)"} - {"(2,0,,500)","(2,1,,500)","(2,,0,250)","(2,,1,250)","(2,,2,250)","(2,,3,250)"} -(2 rows) - --- test the knapsack -set enable_indexscan = false; -set hash_mem_multiplier = 1.0; -set work_mem = '64kB'; -explain (costs off) - select unique1, - count(two), count(four), count(ten), - count(hundred), count(thousand), count(twothousand), - count(*) - from tenk1 group by grouping sets (unique1,twothousand,thousand,hundred,ten,four,two); - QUERY PLAN -------------------------------- - MixedAggregate - Hash Key: two - Hash Key: four - Hash Key: ten - Hash Key: hundred - Group Key: unique1 - Sort Key: twothousand - Group Key: twothousand - Sort Key: thousand - Group Key: thousand - -> Sort - Sort Key: unique1 - -> Seq Scan on tenk1 -(13 rows) - -explain (costs off) - select unique1, - count(two), count(four), count(ten), - count(hundred), count(thousand), count(twothousand), - count(*) - from tenk1 group by grouping sets (unique1,hundred,ten,four,two); - QUERY PLAN -------------------------------- - MixedAggregate - Hash Key: two - Hash Key: four - Hash Key: ten - Hash Key: hundred - Group Key: unique1 - -> Sort - Sort Key: unique1 - -> Seq Scan on tenk1 -(9 rows) - -set work_mem = '384kB'; -explain (costs off) - select unique1, - count(two), count(four), count(ten), - count(hundred), count(thousand), count(twothousand), - count(*) - from tenk1 group by grouping sets (unique1,twothousand,thousand,hundred,ten,four,two); - QUERY PLAN -------------------------------- - MixedAggregate - Hash Key: two - Hash Key: four - Hash Key: ten - Hash Key: hundred - Hash Key: thousand - Group Key: unique1 - Sort Key: twothousand - Group Key: twothousand - -> Sort - Sort Key: unique1 - -> Seq Scan on tenk1 -(12 rows) - --- check collation-sensitive matching between grouping expressions --- (similar to a check for aggregates, but there are additional code --- paths for GROUPING, so check again here) -select v||'a', case grouping(v||'a') when 1 then 1 else 0 end, count(*) - from unnest(array[1,1], array['a','b']) u(i,v) - group by rollup(i, v||'a') order by 1,3; - ?column? | case | count -----------+------+------- - aa | 0 | 1 - ba | 0 | 1 - | 1 | 2 - | 1 | 2 -(4 rows) - -select v||'a', case when grouping(v||'a') = 1 then 1 else 0 end, count(*) - from unnest(array[1,1], array['a','b']) u(i,v) - group by rollup(i, v||'a') order by 1,3; - ?column? | case | count -----------+------+------- - aa | 0 | 1 - ba | 0 | 1 - | 1 | 2 - | 1 | 2 -(4 rows) - --- Bug #16784 -create table bug_16784(i int, j int); -analyze bug_16784; -alter table bug_16784 set (autovacuum_enabled = 'false'); -update pg_class set reltuples = 10 where relname='bug_16784'; -insert into bug_16784 select g/10, g from generate_series(1,40) g; -set work_mem='64kB'; -set enable_sort = false; -select * from - (values (1),(2)) v(a), - lateral (select a, i, j, count(*) from - bug_16784 group by cube(i,j)) s - order by v.a, i, j; - a | a | i | j | count ----+---+---+----+------- - 1 | 1 | 0 | 1 | 1 - 1 | 1 | 0 | 2 | 1 - 1 | 1 | 0 | 3 | 1 - 1 | 1 | 0 | 4 | 1 - 1 | 1 | 0 | 5 | 1 - 1 | 1 | 0 | 6 | 1 - 1 | 1 | 0 | 7 | 1 - 1 | 1 | 0 | 8 | 1 - 1 | 1 | 0 | 9 | 1 - 1 | 1 | 0 | | 9 - 1 | 1 | 1 | 10 | 1 - 1 | 1 | 1 | 11 | 1 - 1 | 1 | 1 | 12 | 1 - 1 | 1 | 1 | 13 | 1 - 1 | 1 | 1 | 14 | 1 - 1 | 1 | 1 | 15 | 1 - 1 | 1 | 1 | 16 | 1 - 1 | 1 | 1 | 17 | 1 - 1 | 1 | 1 | 18 | 1 - 1 | 1 | 1 | 19 | 1 - 1 | 1 | 1 | | 10 - 1 | 1 | 2 | 20 | 1 - 1 | 1 | 2 | 21 | 1 - 1 | 1 | 2 | 22 | 1 - 1 | 1 | 2 | 23 | 1 - 1 | 1 | 2 | 24 | 1 - 1 | 1 | 2 | 25 | 1 - 1 | 1 | 2 | 26 | 1 - 1 | 1 | 2 | 27 | 1 - 1 | 1 | 2 | 28 | 1 - 1 | 1 | 2 | 29 | 1 - 1 | 1 | 2 | | 10 - 1 | 1 | 3 | 30 | 1 - 1 | 1 | 3 | 31 | 1 - 1 | 1 | 3 | 32 | 1 - 1 | 1 | 3 | 33 | 1 - 1 | 1 | 3 | 34 | 1 - 1 | 1 | 3 | 35 | 1 - 1 | 1 | 3 | 36 | 1 - 1 | 1 | 3 | 37 | 1 - 1 | 1 | 3 | 38 | 1 - 1 | 1 | 3 | 39 | 1 - 1 | 1 | 3 | | 10 - 1 | 1 | 4 | 40 | 1 - 1 | 1 | 4 | | 1 - 1 | 1 | | 1 | 1 - 1 | 1 | | 2 | 1 - 1 | 1 | | 3 | 1 - 1 | 1 | | 4 | 1 - 1 | 1 | | 5 | 1 - 1 | 1 | | 6 | 1 - 1 | 1 | | 7 | 1 - 1 | 1 | | 8 | 1 - 1 | 1 | | 9 | 1 - 1 | 1 | | 10 | 1 - 1 | 1 | | 11 | 1 - 1 | 1 | | 12 | 1 - 1 | 1 | | 13 | 1 - 1 | 1 | | 14 | 1 - 1 | 1 | | 15 | 1 - 1 | 1 | | 16 | 1 - 1 | 1 | | 17 | 1 - 1 | 1 | | 18 | 1 - 1 | 1 | | 19 | 1 - 1 | 1 | | 20 | 1 - 1 | 1 | | 21 | 1 - 1 | 1 | | 22 | 1 - 1 | 1 | | 23 | 1 - 1 | 1 | | 24 | 1 - 1 | 1 | | 25 | 1 - 1 | 1 | | 26 | 1 - 1 | 1 | | 27 | 1 - 1 | 1 | | 28 | 1 - 1 | 1 | | 29 | 1 - 1 | 1 | | 30 | 1 - 1 | 1 | | 31 | 1 - 1 | 1 | | 32 | 1 - 1 | 1 | | 33 | 1 - 1 | 1 | | 34 | 1 - 1 | 1 | | 35 | 1 - 1 | 1 | | 36 | 1 - 1 | 1 | | 37 | 1 - 1 | 1 | | 38 | 1 - 1 | 1 | | 39 | 1 - 1 | 1 | | 40 | 1 - 1 | 1 | | | 40 - 2 | 2 | 0 | 1 | 1 - 2 | 2 | 0 | 2 | 1 - 2 | 2 | 0 | 3 | 1 - 2 | 2 | 0 | 4 | 1 - 2 | 2 | 0 | 5 | 1 - 2 | 2 | 0 | 6 | 1 - 2 | 2 | 0 | 7 | 1 - 2 | 2 | 0 | 8 | 1 - 2 | 2 | 0 | 9 | 1 - 2 | 2 | 0 | | 9 - 2 | 2 | 1 | 10 | 1 - 2 | 2 | 1 | 11 | 1 - 2 | 2 | 1 | 12 | 1 - 2 | 2 | 1 | 13 | 1 - 2 | 2 | 1 | 14 | 1 - 2 | 2 | 1 | 15 | 1 - 2 | 2 | 1 | 16 | 1 - 2 | 2 | 1 | 17 | 1 - 2 | 2 | 1 | 18 | 1 - 2 | 2 | 1 | 19 | 1 - 2 | 2 | 1 | | 10 - 2 | 2 | 2 | 20 | 1 - 2 | 2 | 2 | 21 | 1 - 2 | 2 | 2 | 22 | 1 - 2 | 2 | 2 | 23 | 1 - 2 | 2 | 2 | 24 | 1 - 2 | 2 | 2 | 25 | 1 - 2 | 2 | 2 | 26 | 1 - 2 | 2 | 2 | 27 | 1 - 2 | 2 | 2 | 28 | 1 - 2 | 2 | 2 | 29 | 1 - 2 | 2 | 2 | | 10 - 2 | 2 | 3 | 30 | 1 - 2 | 2 | 3 | 31 | 1 - 2 | 2 | 3 | 32 | 1 - 2 | 2 | 3 | 33 | 1 - 2 | 2 | 3 | 34 | 1 - 2 | 2 | 3 | 35 | 1 - 2 | 2 | 3 | 36 | 1 - 2 | 2 | 3 | 37 | 1 - 2 | 2 | 3 | 38 | 1 - 2 | 2 | 3 | 39 | 1 - 2 | 2 | 3 | | 10 - 2 | 2 | 4 | 40 | 1 - 2 | 2 | 4 | | 1 - 2 | 2 | | 1 | 1 - 2 | 2 | | 2 | 1 - 2 | 2 | | 3 | 1 - 2 | 2 | | 4 | 1 - 2 | 2 | | 5 | 1 - 2 | 2 | | 6 | 1 - 2 | 2 | | 7 | 1 - 2 | 2 | | 8 | 1 - 2 | 2 | | 9 | 1 - 2 | 2 | | 10 | 1 - 2 | 2 | | 11 | 1 - 2 | 2 | | 12 | 1 - 2 | 2 | | 13 | 1 - 2 | 2 | | 14 | 1 - 2 | 2 | | 15 | 1 - 2 | 2 | | 16 | 1 - 2 | 2 | | 17 | 1 - 2 | 2 | | 18 | 1 - 2 | 2 | | 19 | 1 - 2 | 2 | | 20 | 1 - 2 | 2 | | 21 | 1 - 2 | 2 | | 22 | 1 - 2 | 2 | | 23 | 1 - 2 | 2 | | 24 | 1 - 2 | 2 | | 25 | 1 - 2 | 2 | | 26 | 1 - 2 | 2 | | 27 | 1 - 2 | 2 | | 28 | 1 - 2 | 2 | | 29 | 1 - 2 | 2 | | 30 | 1 - 2 | 2 | | 31 | 1 - 2 | 2 | | 32 | 1 - 2 | 2 | | 33 | 1 - 2 | 2 | | 34 | 1 - 2 | 2 | | 35 | 1 - 2 | 2 | | 36 | 1 - 2 | 2 | | 37 | 1 - 2 | 2 | | 38 | 1 - 2 | 2 | | 39 | 1 - 2 | 2 | | 40 | 1 - 2 | 2 | | | 40 -(172 rows) - --- --- Compare results between plans using sorting and plans using hash --- aggregation. Force spilling in both cases by setting work_mem low --- and altering the statistics. --- -create table gs_data_1 as -select g%1000 as g1000, g%100 as g100, g%10 as g10, g - from generate_series(0,1999) g; -analyze gs_data_1; -alter table gs_data_1 set (autovacuum_enabled = 'false'); -update pg_class set reltuples = 10 where relname='gs_data_1'; -set work_mem='64kB'; --- Produce results with sorting. -set enable_sort = true; -set enable_hashagg = false; -set jit_above_cost = 0; -explain (costs off) -select g100, g10, sum(g::numeric), count(*), max(g::text) -from gs_data_1 group by cube (g1000, g100,g10); - QUERY PLAN ------------------------------------- - GroupAggregate - Group Key: g1000, g100, g10 - Group Key: g1000, g100 - Group Key: g1000 - Group Key: () - Sort Key: g100, g10 - Group Key: g100, g10 - Group Key: g100 - Sort Key: g10, g1000 - Group Key: g10, g1000 - Group Key: g10 - -> Sort - Sort Key: g1000, g100, g10 - -> Seq Scan on gs_data_1 -(14 rows) - -create table gs_group_1 as -select g100, g10, sum(g::numeric), count(*), max(g::text) -from gs_data_1 group by cube (g1000, g100,g10); --- Produce results with hash aggregation. -set enable_hashagg = true; -set enable_sort = false; -explain (costs off) -select g100, g10, sum(g::numeric), count(*), max(g::text) -from gs_data_1 group by cube (g1000, g100,g10); - QUERY PLAN ------------------------------- - MixedAggregate - Hash Key: g1000, g100, g10 - Hash Key: g1000, g100 - Hash Key: g1000 - Hash Key: g100, g10 - Hash Key: g100 - Hash Key: g10, g1000 - Hash Key: g10 - Group Key: () - -> Seq Scan on gs_data_1 -(10 rows) - -create table gs_hash_1 as -select g100, g10, sum(g::numeric), count(*), max(g::text) -from gs_data_1 group by cube (g1000, g100,g10); -set enable_sort = true; -set work_mem to default; -set hash_mem_multiplier to default; --- Compare results -(select * from gs_hash_1 except select * from gs_group_1) - union all -(select * from gs_group_1 except select * from gs_hash_1); - g100 | g10 | sum | count | max -------+-----+-----+-------+----- -(0 rows) - -drop table gs_group_1; -drop table gs_hash_1; --- GROUP BY DISTINCT --- "normal" behavior... -select a, b, c -from (values (1, 2, 3), (4, null, 6), (7, 8, 9)) as t (a, b, c) -group by all rollup(a, b), rollup(a, c) -order by a, b, c; - a | b | c ----+---+--- - 1 | 2 | 3 - 1 | 2 | - 1 | 2 | - 1 | | 3 - 1 | | 3 - 1 | | - 1 | | - 1 | | - 4 | | 6 - 4 | | 6 - 4 | | 6 - 4 | | - 4 | | - 4 | | - 4 | | - 4 | | - 7 | 8 | 9 - 7 | 8 | - 7 | 8 | - 7 | | 9 - 7 | | 9 - 7 | | - 7 | | - 7 | | - | | -(25 rows) - --- ...which is also the default -select a, b, c -from (values (1, 2, 3), (4, null, 6), (7, 8, 9)) as t (a, b, c) -group by rollup(a, b), rollup(a, c) -order by a, b, c; - a | b | c ----+---+--- - 1 | 2 | 3 - 1 | 2 | - 1 | 2 | - 1 | | 3 - 1 | | 3 - 1 | | - 1 | | - 1 | | - 4 | | 6 - 4 | | 6 - 4 | | 6 - 4 | | - 4 | | - 4 | | - 4 | | - 4 | | - 7 | 8 | 9 - 7 | 8 | - 7 | 8 | - 7 | | 9 - 7 | | 9 - 7 | | - 7 | | - 7 | | - | | -(25 rows) - --- "group by distinct" behavior... -select a, b, c -from (values (1, 2, 3), (4, null, 6), (7, 8, 9)) as t (a, b, c) -group by distinct rollup(a, b), rollup(a, c) -order by a, b, c; - a | b | c ----+---+--- - 1 | 2 | 3 - 1 | 2 | - 1 | | 3 - 1 | | - 4 | | 6 - 4 | | 6 - 4 | | - 4 | | - 7 | 8 | 9 - 7 | 8 | - 7 | | 9 - 7 | | - | | -(13 rows) - --- ...which is not the same as "select distinct" -select distinct a, b, c -from (values (1, 2, 3), (4, null, 6), (7, 8, 9)) as t (a, b, c) -group by rollup(a, b), rollup(a, c) -order by a, b, c; - a | b | c ----+---+--- - 1 | 2 | 3 - 1 | 2 | - 1 | | 3 - 1 | | - 4 | | 6 - 4 | | - 7 | 8 | 9 - 7 | 8 | - 7 | | 9 - 7 | | - | | -(11 rows) - --- test handling of outer GroupingFunc within subqueries -explain (costs off) -select (select grouping(v1)) from (values ((select 1))) v(v1) group by cube(v1); - QUERY PLAN -------------------------------- - MixedAggregate - Hash Key: (InitPlan 3).col1 - Group Key: () - InitPlan 1 - -> Result - InitPlan 3 - -> Result - -> Result - SubPlan 2 - -> Result -(10 rows) - -select (select grouping(v1)) from (values ((select 1))) v(v1) group by cube(v1); - grouping ----------- - 1 - 0 -(2 rows) - -explain (costs off) -select (select grouping(v1)) from (values ((select 1))) v(v1) group by v1; - QUERY PLAN ----------------- - GroupAggregate - InitPlan 1 - -> Result - InitPlan 3 - -> Result - -> Result - SubPlan 2 - -> Result -(8 rows) - -select (select grouping(v1)) from (values ((select 1))) v(v1) group by v1; - grouping ----------- - 0 -(1 row) - --- test handling of subqueries in grouping sets -create temp table gstest5(id integer primary key, v integer); -insert into gstest5 select i, i from generate_series(1,5)i; -explain (verbose, costs off) -select grouping((select t1.v from gstest5 t2 where id = t1.id)), - (select t1.v from gstest5 t2 where id = t1.id) as s -from gstest5 t1 -group by grouping sets(v, s) -order by case when grouping((select t1.v from gstest5 t2 where id = t1.id)) = 0 - then (select t1.v from gstest5 t2 where id = t1.id) - else null end - nulls first; - QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------- - Sort - Output: (GROUPING((SubPlan 1))), ((SubPlan 3)), (CASE WHEN (GROUPING((SubPlan 2)) = 0) THEN ((SubPlan 3)) ELSE NULL::integer END), t1.v - Sort Key: (CASE WHEN (GROUPING((SubPlan 2)) = 0) THEN ((SubPlan 3)) ELSE NULL::integer END) NULLS FIRST - -> HashAggregate - Output: GROUPING((SubPlan 1)), ((SubPlan 3)), CASE WHEN (GROUPING((SubPlan 2)) = 0) THEN ((SubPlan 3)) ELSE NULL::integer END, t1.v - Hash Key: t1.v - Hash Key: (SubPlan 3) - -> Seq Scan on pg_temp.gstest5 t1 - Output: (SubPlan 3), t1.v, t1.id - SubPlan 3 - -> Bitmap Heap Scan on pg_temp.gstest5 t2 - Output: t1.v - Recheck Cond: (t2.id = t1.id) - -> Bitmap Index Scan on gstest5_pkey - Index Cond: (t2.id = t1.id) -(15 rows) - -select grouping((select t1.v from gstest5 t2 where id = t1.id)), - (select t1.v from gstest5 t2 where id = t1.id) as s -from gstest5 t1 -group by grouping sets(v, s) -order by case when grouping((select t1.v from gstest5 t2 where id = t1.id)) = 0 - then (select t1.v from gstest5 t2 where id = t1.id) - else null end - nulls first; - grouping | s -----------+--- - 1 | - 1 | - 1 | - 1 | - 1 | - 0 | 1 - 0 | 2 - 0 | 3 - 0 | 4 - 0 | 5 -(10 rows) - -explain (verbose, costs off) -select grouping((select t1.v from gstest5 t2 where id = t1.id)), - (select t1.v from gstest5 t2 where id = t1.id) as s, - case when grouping((select t1.v from gstest5 t2 where id = t1.id)) = 0 - then (select t1.v from gstest5 t2 where id = t1.id) - else null end as o -from gstest5 t1 -group by grouping sets(v, s) -order by o nulls first; - QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------- - Sort - Output: (GROUPING((SubPlan 1))), ((SubPlan 3)), (CASE WHEN (GROUPING((SubPlan 2)) = 0) THEN ((SubPlan 3)) ELSE NULL::integer END), t1.v - Sort Key: (CASE WHEN (GROUPING((SubPlan 2)) = 0) THEN ((SubPlan 3)) ELSE NULL::integer END) NULLS FIRST - -> HashAggregate - Output: GROUPING((SubPlan 1)), ((SubPlan 3)), CASE WHEN (GROUPING((SubPlan 2)) = 0) THEN ((SubPlan 3)) ELSE NULL::integer END, t1.v - Hash Key: t1.v - Hash Key: (SubPlan 3) - -> Seq Scan on pg_temp.gstest5 t1 - Output: (SubPlan 3), t1.v, t1.id - SubPlan 3 - -> Bitmap Heap Scan on pg_temp.gstest5 t2 - Output: t1.v - Recheck Cond: (t2.id = t1.id) - -> Bitmap Index Scan on gstest5_pkey - Index Cond: (t2.id = t1.id) -(15 rows) - -select grouping((select t1.v from gstest5 t2 where id = t1.id)), - (select t1.v from gstest5 t2 where id = t1.id) as s, - case when grouping((select t1.v from gstest5 t2 where id = t1.id)) = 0 - then (select t1.v from gstest5 t2 where id = t1.id) - else null end as o -from gstest5 t1 -group by grouping sets(v, s) -order by o nulls first; - grouping | s | o -----------+---+--- - 1 | | - 1 | | - 1 | | - 1 | | - 1 | | - 0 | 1 | 1 - 0 | 2 | 2 - 0 | 3 | 3 - 0 | 4 | 4 - 0 | 5 | 5 -(10 rows) - --- test handling of expressions that should match lower target items -explain (costs off) -select a < b and b < 3 from (values (1, 2)) t(a, b) group by rollup(a < b and b < 3) having a < b and b < 3; - QUERY PLAN ------------------------------------ - MixedAggregate - Hash Key: ((1 < 2) AND (2 < 3)) - Group Key: () - Filter: (((1 < 2) AND (2 < 3))) - -> Result -(5 rows) - -select a < b and b < 3 from (values (1, 2)) t(a, b) group by rollup(a < b and b < 3) having a < b and b < 3; - ?column? ----------- - t -(1 row) - -explain (costs off) -select not a from (values(true)) t(a) group by rollup(not a) having not not a; - QUERY PLAN ------------------------------- - MixedAggregate - Hash Key: (NOT true) - Group Key: () - Filter: (NOT ((NOT true))) - -> Result -(5 rows) - -select not a from (values(true)) t(a) group by rollup(not a) having not not a; - ?column? ----------- - f -(1 row) - --- test handling of expressions nullable by grouping sets -explain (costs off) -select distinct on (a, b) a, b -from (values (1, 1), (2, 2)) as t (a, b) where a = b -group by grouping sets((a, b), (a)) -order by a, b; - QUERY PLAN ----------------------------------------------------------------- - Unique - -> Sort - Sort Key: "*VALUES*".column1, "*VALUES*".column2 - -> HashAggregate - Hash Key: "*VALUES*".column1, "*VALUES*".column2 - Hash Key: "*VALUES*".column1 - -> Values Scan on "*VALUES*" - Filter: (column1 = column2) -(8 rows) - -select distinct on (a, b) a, b -from (values (1, 1), (2, 2)) as t (a, b) where a = b -group by grouping sets((a, b), (a)) -order by a, b; - a | b ----+--- - 1 | 1 - 1 | - 2 | 2 - 2 | -(4 rows) - -explain (costs off) -select distinct on (a, b+1) a, b+1 -from (values (1, 0), (2, 1)) as t (a, b) where a = b+1 -group by grouping sets((a, b+1), (a)) -order by a, b+1; - QUERY PLAN ----------------------------------------------------------------------- - Unique - -> Sort - Sort Key: "*VALUES*".column1, (("*VALUES*".column2 + 1)) - -> HashAggregate - Hash Key: "*VALUES*".column1, ("*VALUES*".column2 + 1) - Hash Key: "*VALUES*".column1 - -> Values Scan on "*VALUES*" - Filter: (column1 = (column2 + 1)) -(8 rows) - -select distinct on (a, b+1) a, b+1 -from (values (1, 0), (2, 1)) as t (a, b) where a = b+1 -group by grouping sets((a, b+1), (a)) -order by a, b+1; - a | ?column? ----+---------- - 1 | 1 - 1 | - 2 | 2 - 2 | -(4 rows) - -explain (costs off) -select a, b -from (values (1, 1), (2, 2)) as t (a, b) where a = b -group by grouping sets((a, b), (a)) -order by a, b nulls first; - QUERY PLAN ----------------------------------------------------------------- - Sort - Sort Key: "*VALUES*".column1, "*VALUES*".column2 NULLS FIRST - -> HashAggregate - Hash Key: "*VALUES*".column1, "*VALUES*".column2 - Hash Key: "*VALUES*".column1 - -> Values Scan on "*VALUES*" - Filter: (column1 = column2) -(7 rows) - -select a, b -from (values (1, 1), (2, 2)) as t (a, b) where a = b -group by grouping sets((a, b), (a)) -order by a, b nulls first; - a | b ----+--- - 1 | - 1 | 1 - 2 | - 2 | 2 -(4 rows) - -explain (costs off) -select 1 as one group by rollup(one) order by one nulls first; - QUERY PLAN ------------------------------ - Sort - Sort Key: (1) NULLS FIRST - -> MixedAggregate - Hash Key: 1 - Group Key: () - -> Result -(6 rows) - -select 1 as one group by rollup(one) order by one nulls first; - one ------ - - 1 -(2 rows) - -explain (costs off) -select a, b, row_number() over (order by a, b nulls first) -from (values (1, 1), (2, 2)) as t (a, b) where a = b -group by grouping sets((a, b), (a)); - QUERY PLAN ----------------------------------------------------------------------- - WindowAgg - -> Sort - Sort Key: "*VALUES*".column1, "*VALUES*".column2 NULLS FIRST - -> HashAggregate - Hash Key: "*VALUES*".column1, "*VALUES*".column2 - Hash Key: "*VALUES*".column1 - -> Values Scan on "*VALUES*" - Filter: (column1 = column2) -(8 rows) - -select a, b, row_number() over (order by a, b nulls first) -from (values (1, 1), (2, 2)) as t (a, b) where a = b -group by grouping sets((a, b), (a)); - a | b | row_number ----+---+------------ - 1 | | 1 - 1 | 1 | 2 - 2 | | 3 - 2 | 2 | 4 -(4 rows) - --- end +psql: error: connection to server on socket "/tmp/pg_regress-03Az7d/.s.PGSQL.40051" failed: FATAL: the database system is not yet accepting connections +DETAIL: Consistent recovery state has not been yet reached. diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/drop_operator.out /tmp/cirrus-ci-build/build/testrun/regress/regress/results/drop_operator.out --- /tmp/cirrus-ci-build/src/test/regress/expected/drop_operator.out 2024-09-20 21:41:45.682024521 +0000 +++ /tmp/cirrus-ci-build/build/testrun/regress/regress/results/drop_operator.out 2024-09-20 21:46:06.601623557 +0000 @@ -1,61 +1,2 @@ -CREATE OPERATOR === ( - PROCEDURE = int8eq, - LEFTARG = bigint, - RIGHTARG = bigint, - COMMUTATOR = === -); -CREATE OPERATOR !== ( - PROCEDURE = int8ne, - LEFTARG = bigint, - RIGHTARG = bigint, - NEGATOR = ===, - COMMUTATOR = !== -); -DROP OPERATOR !==(bigint, bigint); -SELECT ctid, oprcom -FROM pg_catalog.pg_operator fk -WHERE oprcom != 0 AND - NOT EXISTS(SELECT 1 FROM pg_catalog.pg_operator pk WHERE pk.oid = fk.oprcom); - ctid | oprcom -------+-------- -(0 rows) - -SELECT ctid, oprnegate -FROM pg_catalog.pg_operator fk -WHERE oprnegate != 0 AND - NOT EXISTS(SELECT 1 FROM pg_catalog.pg_operator pk WHERE pk.oid = fk.oprnegate); - ctid | oprnegate -------+----------- -(0 rows) - -DROP OPERATOR ===(bigint, bigint); -CREATE OPERATOR <| ( - PROCEDURE = int8lt, - LEFTARG = bigint, - RIGHTARG = bigint -); -CREATE OPERATOR |> ( - PROCEDURE = int8gt, - LEFTARG = bigint, - RIGHTARG = bigint, - NEGATOR = <|, - COMMUTATOR = <| -); -DROP OPERATOR |>(bigint, bigint); -SELECT ctid, oprcom -FROM pg_catalog.pg_operator fk -WHERE oprcom != 0 AND - NOT EXISTS(SELECT 1 FROM pg_catalog.pg_operator pk WHERE pk.oid = fk.oprcom); - ctid | oprcom -------+-------- -(0 rows) - -SELECT ctid, oprnegate -FROM pg_catalog.pg_operator fk -WHERE oprnegate != 0 AND - NOT EXISTS(SELECT 1 FROM pg_catalog.pg_operator pk WHERE pk.oid = fk.oprnegate); - ctid | oprnegate -------+----------- -(0 rows) - -DROP OPERATOR <|(bigint, bigint); +psql: error: connection to server on socket "/tmp/pg_regress-03Az7d/.s.PGSQL.40051" failed: FATAL: the database system is not yet accepting connections +DETAIL: Consistent recovery state has not been yet reached. diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/password.out /tmp/cirrus-ci-build/build/testrun/regress/regress/results/password.out --- /tmp/cirrus-ci-build/src/test/regress/expected/password.out 2024-09-20 21:41:45.730024523 +0000 +++ /tmp/cirrus-ci-build/build/testrun/regress/regress/results/password.out 2024-09-20 21:46:06.609623546 +0000 @@ -1,151 +1,2 @@ --- --- Tests for password types --- --- Tests for GUC password_encryption -SET password_encryption = 'novalue'; -- error -ERROR: invalid value for parameter "password_encryption": "novalue" -HINT: Available values: md5, scram-sha-256. -SET password_encryption = true; -- error -ERROR: invalid value for parameter "password_encryption": "true" -HINT: Available values: md5, scram-sha-256. -SET password_encryption = 'md5'; -- ok -SET password_encryption = 'scram-sha-256'; -- ok --- consistency of password entries -SET password_encryption = 'md5'; -CREATE ROLE regress_passwd1; -ALTER ROLE regress_passwd1 PASSWORD 'role_pwd1'; -CREATE ROLE regress_passwd2; -ALTER ROLE regress_passwd2 PASSWORD 'role_pwd2'; -SET password_encryption = 'scram-sha-256'; -CREATE ROLE regress_passwd3 PASSWORD 'role_pwd3'; -CREATE ROLE regress_passwd4 PASSWORD NULL; --- check list of created entries --- --- The scram secret will look something like: --- SCRAM-SHA-256$4096:E4HxLGtnRzsYwg==$6YtlR4t69SguDiwFvbVgVZtuz6gpJQQqUMZ7IQJK5yI=:ps75jrHeYU4lXCcXI4O8oIdJ3eO8o2jirjruw9phBTo= --- --- Since the salt is random, the exact value stored will be different on every test --- run. Use a regular expression to mask the changing parts. -SELECT rolname, regexp_replace(rolpassword, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+/=]+)\$([a-zA-Z0-9+=/]+):([a-zA-Z0-9+/=]+)', '\1$\2:$:') as rolpassword_masked - FROM pg_authid - WHERE rolname LIKE 'regress_passwd%' - ORDER BY rolname, rolpassword; - rolname | rolpassword_masked ------------------+--------------------------------------------------- - regress_passwd1 | md5783277baca28003b33453252be4dbb34 - regress_passwd2 | md54044304ba511dd062133eb5b4b84a2a3 - regress_passwd3 | SCRAM-SHA-256$4096:$: - regress_passwd4 | -(4 rows) - --- Rename a role -ALTER ROLE regress_passwd2 RENAME TO regress_passwd2_new; -NOTICE: MD5 password cleared because of role rename --- md5 entry should have been removed -SELECT rolname, rolpassword - FROM pg_authid - WHERE rolname LIKE 'regress_passwd2_new' - ORDER BY rolname, rolpassword; - rolname | rolpassword ----------------------+------------- - regress_passwd2_new | -(1 row) - -ALTER ROLE regress_passwd2_new RENAME TO regress_passwd2; --- Change passwords with ALTER USER. With plaintext or already-encrypted --- passwords. -SET password_encryption = 'md5'; --- encrypt with MD5 -ALTER ROLE regress_passwd2 PASSWORD 'foo'; --- already encrypted, use as they are -ALTER ROLE regress_passwd1 PASSWORD 'md5cd3578025fe2c3d7ed1b9a9b26238b70'; -ALTER ROLE regress_passwd3 PASSWORD 'SCRAM-SHA-256$4096:VLK4RMaQLCvNtQ==$6YtlR4t69SguDiwFvbVgVZtuz6gpJQQqUMZ7IQJK5yI=:ps75jrHeYU4lXCcXI4O8oIdJ3eO8o2jirjruw9phBTo='; -SET password_encryption = 'scram-sha-256'; --- create SCRAM secret -ALTER ROLE regress_passwd4 PASSWORD 'foo'; --- already encrypted with MD5, use as it is -CREATE ROLE regress_passwd5 PASSWORD 'md5e73a4b11df52a6068f8b39f90be36023'; --- This looks like a valid SCRAM-SHA-256 secret, but it is not --- so it should be hashed with SCRAM-SHA-256. -CREATE ROLE regress_passwd6 PASSWORD 'SCRAM-SHA-256$1234'; --- These may look like valid MD5 secrets, but they are not, so they --- should be hashed with SCRAM-SHA-256. --- trailing garbage at the end -CREATE ROLE regress_passwd7 PASSWORD 'md5012345678901234567890123456789zz'; --- invalid length -CREATE ROLE regress_passwd8 PASSWORD 'md501234567890123456789012345678901zz'; --- Changing the SCRAM iteration count -SET scram_iterations = 1024; -CREATE ROLE regress_passwd9 PASSWORD 'alterediterationcount'; -SELECT rolname, regexp_replace(rolpassword, '(SCRAM-SHA-256)\$(\d+):([a-zA-Z0-9+/=]+)\$([a-zA-Z0-9+=/]+):([a-zA-Z0-9+/=]+)', '\1$\2:$:') as rolpassword_masked - FROM pg_authid - WHERE rolname LIKE 'regress_passwd%' - ORDER BY rolname, rolpassword; - rolname | rolpassword_masked ------------------+--------------------------------------------------- - regress_passwd1 | md5cd3578025fe2c3d7ed1b9a9b26238b70 - regress_passwd2 | md5dfa155cadd5f4ad57860162f3fab9cdb - regress_passwd3 | SCRAM-SHA-256$4096:$: - regress_passwd4 | SCRAM-SHA-256$4096:$: - regress_passwd5 | md5e73a4b11df52a6068f8b39f90be36023 - regress_passwd6 | SCRAM-SHA-256$4096:$: - regress_passwd7 | SCRAM-SHA-256$4096:$: - regress_passwd8 | SCRAM-SHA-256$4096:$: - regress_passwd9 | SCRAM-SHA-256$1024:$: -(9 rows) - --- An empty password is not allowed, in any form -CREATE ROLE regress_passwd_empty PASSWORD ''; -NOTICE: empty string is not a valid password, clearing password -ALTER ROLE regress_passwd_empty PASSWORD 'md585939a5ce845f1a1b620742e3c659e0a'; -NOTICE: empty string is not a valid password, clearing password -ALTER ROLE regress_passwd_empty PASSWORD 'SCRAM-SHA-256$4096:hpFyHTUsSWcR7O9P$LgZFIt6Oqdo27ZFKbZ2nV+vtnYM995pDh9ca6WSi120=:qVV5NeluNfUPkwm7Vqat25RjSPLkGeoZBQs6wVv+um4='; -NOTICE: empty string is not a valid password, clearing password -SELECT rolpassword FROM pg_authid WHERE rolname='regress_passwd_empty'; - rolpassword -------------- - -(1 row) - --- Test with invalid stored and server keys. --- --- The first is valid, to act as a control. The others have too long --- stored/server keys. They will be re-hashed. -CREATE ROLE regress_passwd_sha_len0 PASSWORD 'SCRAM-SHA-256$4096:A6xHKoH/494E941doaPOYg==$Ky+A30sewHIH3VHQLRN9vYsuzlgNyGNKCh37dy96Rqw=:COPdlNiIkrsacU5QoxydEuOH6e/KfiipeETb/bPw8ZI='; -CREATE ROLE regress_passwd_sha_len1 PASSWORD 'SCRAM-SHA-256$4096:A6xHKoH/494E941doaPOYg==$Ky+A30sewHIH3VHQLRN9vYsuzlgNyGNKCh37dy96RqwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=:COPdlNiIkrsacU5QoxydEuOH6e/KfiipeETb/bPw8ZI='; -CREATE ROLE regress_passwd_sha_len2 PASSWORD 'SCRAM-SHA-256$4096:A6xHKoH/494E941doaPOYg==$Ky+A30sewHIH3VHQLRN9vYsuzlgNyGNKCh37dy96Rqw=:COPdlNiIkrsacU5QoxydEuOH6e/KfiipeETb/bPw8ZIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA='; --- Check that the invalid secrets were re-hashed. A re-hashed secret --- should not contain the original salt. -SELECT rolname, rolpassword not like '%A6xHKoH/494E941doaPOYg==%' as is_rolpassword_rehashed - FROM pg_authid - WHERE rolname LIKE 'regress_passwd_sha_len%' - ORDER BY rolname; - rolname | is_rolpassword_rehashed --------------------------+------------------------- - regress_passwd_sha_len0 | f - regress_passwd_sha_len1 | t - regress_passwd_sha_len2 | t -(3 rows) - -DROP ROLE regress_passwd1; -DROP ROLE regress_passwd2; -DROP ROLE regress_passwd3; -DROP ROLE regress_passwd4; -DROP ROLE regress_passwd5; -DROP ROLE regress_passwd6; -DROP ROLE regress_passwd7; -DROP ROLE regress_passwd8; -DROP ROLE regress_passwd9; -DROP ROLE regress_passwd_empty; -DROP ROLE regress_passwd_sha_len0; -DROP ROLE regress_passwd_sha_len1; -DROP ROLE regress_passwd_sha_len2; --- all entries should have been removed -SELECT rolname, rolpassword - FROM pg_authid - WHERE rolname LIKE 'regress_passwd%' - ORDER BY rolname, rolpassword; - rolname | rolpassword ----------+------------- -(0 rows) - +psql: error: connection to server on socket "/tmp/pg_regress-03Az7d/.s.PGSQL.40051" failed: FATAL: the database system is not yet accepting connections +DETAIL: Consistent recovery state has not been yet reached. diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/identity.out /tmp/cirrus-ci-build/build/testrun/regress/regress/results/identity.out --- /tmp/cirrus-ci-build/src/test/regress/expected/identity.out 2024-09-20 21:41:45.694024521 +0000 +++ /tmp/cirrus-ci-build/build/testrun/regress/regress/results/identity.out 2024-09-20 21:46:06.641623503 +0000 @@ -1,923 +1,2 @@ --- sanity check of system catalog -SELECT attrelid, attname, attidentity FROM pg_attribute WHERE attidentity NOT IN ('', 'a', 'd'); - attrelid | attname | attidentity -----------+---------+------------- -(0 rows) - -CREATE TABLE itest1 (a int generated by default as identity, b text); -CREATE TABLE itest2 (a bigint generated always as identity, b text); -CREATE TABLE itest3 (a smallint generated by default as identity (start with 7 increment by 5), b text); -ALTER TABLE itest3 ALTER COLUMN a ADD GENERATED ALWAYS AS IDENTITY; -- error -ERROR: column "a" of relation "itest3" is already an identity column -SELECT table_name, column_name, column_default, is_nullable, is_identity, identity_generation, identity_start, identity_increment, identity_maximum, identity_minimum, identity_cycle FROM information_schema.columns WHERE table_name LIKE 'itest_' ORDER BY 1, 2; - table_name | column_name | column_default | is_nullable | is_identity | identity_generation | identity_start | identity_increment | identity_maximum | identity_minimum | identity_cycle -------------+-------------+----------------+-------------+-------------+---------------------+----------------+--------------------+---------------------+------------------+---------------- - itest1 | a | | NO | YES | BY DEFAULT | 1 | 1 | 2147483647 | 1 | NO - itest1 | b | | YES | NO | | | | | | NO - itest2 | a | | NO | YES | ALWAYS | 1 | 1 | 9223372036854775807 | 1 | NO - itest2 | b | | YES | NO | | | | | | NO - itest3 | a | | NO | YES | BY DEFAULT | 7 | 5 | 32767 | 1 | NO - itest3 | b | | YES | NO | | | | | | NO -(6 rows) - --- internal sequences should not be shown here -SELECT sequence_name FROM information_schema.sequences WHERE sequence_name LIKE 'itest%'; - sequence_name ---------------- -(0 rows) - -SELECT pg_get_serial_sequence('itest1', 'a'); - pg_get_serial_sequence ------------------------- - public.itest1_a_seq -(1 row) - -\d itest1_a_seq - Sequence "public.itest1_a_seq" - Type | Start | Minimum | Maximum | Increment | Cycles? | Cache ----------+-------+---------+------------+-----------+---------+------- - integer | 1 | 1 | 2147483647 | 1 | no | 1 -Sequence for identity column: public.itest1.a - -CREATE TABLE itest4 (a int, b text); -ALTER TABLE itest4 ALTER COLUMN a ADD GENERATED ALWAYS AS IDENTITY; -- error, requires NOT NULL -ERROR: column "a" of relation "itest4" must be declared NOT NULL before identity can be added -ALTER TABLE itest4 ALTER COLUMN a SET NOT NULL; -ALTER TABLE itest4 ALTER COLUMN a ADD GENERATED ALWAYS AS IDENTITY; -- ok -ALTER TABLE itest4 ALTER COLUMN a DROP NOT NULL; -- error, disallowed -ERROR: column "a" of relation "itest4" is an identity column -ALTER TABLE itest4 ALTER COLUMN a ADD GENERATED ALWAYS AS IDENTITY; -- error, already set -ERROR: column "a" of relation "itest4" is already an identity column -ALTER TABLE itest4 ALTER COLUMN b ADD GENERATED ALWAYS AS IDENTITY; -- error, wrong data type -ERROR: identity column type must be smallint, integer, or bigint --- for later -ALTER TABLE itest4 ALTER COLUMN b SET DEFAULT ''; --- invalid column type -CREATE TABLE itest_err_1 (a text generated by default as identity); -ERROR: identity column type must be smallint, integer, or bigint --- duplicate identity -CREATE TABLE itest_err_2 (a int generated always as identity generated by default as identity); -ERROR: multiple identity specifications for column "a" of table "itest_err_2" -LINE 1: ...E itest_err_2 (a int generated always as identity generated ... - ^ --- cannot have default and identity -CREATE TABLE itest_err_3 (a int default 5 generated by default as identity); -ERROR: both default and identity specified for column "a" of table "itest_err_3" -LINE 1: CREATE TABLE itest_err_3 (a int default 5 generated by defau... - ^ --- cannot combine serial and identity -CREATE TABLE itest_err_4 (a serial generated by default as identity); -ERROR: both default and identity specified for column "a" of table "itest_err_4" -INSERT INTO itest1 DEFAULT VALUES; -INSERT INTO itest1 DEFAULT VALUES; -INSERT INTO itest2 DEFAULT VALUES; -INSERT INTO itest2 DEFAULT VALUES; -INSERT INTO itest3 DEFAULT VALUES; -INSERT INTO itest3 DEFAULT VALUES; -INSERT INTO itest4 DEFAULT VALUES; -INSERT INTO itest4 DEFAULT VALUES; -SELECT * FROM itest1; - a | b ----+--- - 1 | - 2 | -(2 rows) - -SELECT * FROM itest2; - a | b ----+--- - 1 | - 2 | -(2 rows) - -SELECT * FROM itest3; - a | b -----+--- - 7 | - 12 | -(2 rows) - -SELECT * FROM itest4; - a | b ----+--- - 1 | - 2 | -(2 rows) - --- VALUES RTEs -CREATE TABLE itest5 (a int generated always as identity, b text); -INSERT INTO itest5 VALUES (1, 'a'); -- error -ERROR: cannot insert a non-DEFAULT value into column "a" -DETAIL: Column "a" is an identity column defined as GENERATED ALWAYS. -HINT: Use OVERRIDING SYSTEM VALUE to override. -INSERT INTO itest5 VALUES (DEFAULT, 'a'); -- ok -INSERT INTO itest5 VALUES (2, 'b'), (3, 'c'); -- error -ERROR: cannot insert a non-DEFAULT value into column "a" -DETAIL: Column "a" is an identity column defined as GENERATED ALWAYS. -HINT: Use OVERRIDING SYSTEM VALUE to override. -INSERT INTO itest5 VALUES (DEFAULT, 'b'), (3, 'c'); -- error -ERROR: cannot insert a non-DEFAULT value into column "a" -DETAIL: Column "a" is an identity column defined as GENERATED ALWAYS. -HINT: Use OVERRIDING SYSTEM VALUE to override. -INSERT INTO itest5 VALUES (2, 'b'), (DEFAULT, 'c'); -- error -ERROR: cannot insert a non-DEFAULT value into column "a" -DETAIL: Column "a" is an identity column defined as GENERATED ALWAYS. -HINT: Use OVERRIDING SYSTEM VALUE to override. -INSERT INTO itest5 VALUES (DEFAULT, 'b'), (DEFAULT, 'c'); -- ok -INSERT INTO itest5 OVERRIDING SYSTEM VALUE VALUES (-1, 'aa'); -INSERT INTO itest5 OVERRIDING SYSTEM VALUE VALUES (-2, 'bb'), (-3, 'cc'); -INSERT INTO itest5 OVERRIDING SYSTEM VALUE VALUES (DEFAULT, 'dd'), (-4, 'ee'); -INSERT INTO itest5 OVERRIDING SYSTEM VALUE VALUES (-5, 'ff'), (DEFAULT, 'gg'); -INSERT INTO itest5 OVERRIDING SYSTEM VALUE VALUES (DEFAULT, 'hh'), (DEFAULT, 'ii'); -INSERT INTO itest5 OVERRIDING USER VALUE VALUES (-1, 'aaa'); -INSERT INTO itest5 OVERRIDING USER VALUE VALUES (-2, 'bbb'), (-3, 'ccc'); -INSERT INTO itest5 OVERRIDING USER VALUE VALUES (DEFAULT, 'ddd'), (-4, 'eee'); -INSERT INTO itest5 OVERRIDING USER VALUE VALUES (-5, 'fff'), (DEFAULT, 'ggg'); -INSERT INTO itest5 OVERRIDING USER VALUE VALUES (DEFAULT, 'hhh'), (DEFAULT, 'iii'); -SELECT * FROM itest5; - a | b -----+----- - 1 | a - 2 | b - 3 | c - -1 | aa - -2 | bb - -3 | cc - 4 | dd - -4 | ee - -5 | ff - 5 | gg - 6 | hh - 7 | ii - 8 | aaa - 9 | bbb - 10 | ccc - 11 | ddd - 12 | eee - 13 | fff - 14 | ggg - 15 | hhh - 16 | iii -(21 rows) - -DROP TABLE itest5; -INSERT INTO itest3 VALUES (DEFAULT, 'a'); -INSERT INTO itest3 VALUES (DEFAULT, 'b'), (DEFAULT, 'c'); -SELECT * FROM itest3; - a | b -----+--- - 7 | - 12 | - 17 | a - 22 | b - 27 | c -(5 rows) - --- OVERRIDING tests --- GENERATED BY DEFAULT --- This inserts the row as presented: -INSERT INTO itest1 VALUES (10, 'xyz'); --- With GENERATED BY DEFAULT, OVERRIDING SYSTEM VALUE is not allowed --- by the standard, but we allow it as a no-op, since it is of use if --- there are multiple identity columns in a table, which is also an --- extension. -INSERT INTO itest1 OVERRIDING SYSTEM VALUE VALUES (20, 'xyz'); --- This ignores the 30 and uses the sequence value instead: -INSERT INTO itest1 OVERRIDING USER VALUE VALUES (30, 'xyz'); -SELECT * FROM itest1; - a | b -----+----- - 1 | - 2 | - 10 | xyz - 20 | xyz - 3 | xyz -(5 rows) - --- GENERATED ALWAYS --- This is an error: -INSERT INTO itest2 VALUES (10, 'xyz'); -ERROR: cannot insert a non-DEFAULT value into column "a" -DETAIL: Column "a" is an identity column defined as GENERATED ALWAYS. -HINT: Use OVERRIDING SYSTEM VALUE to override. --- This inserts the row as presented: -INSERT INTO itest2 OVERRIDING SYSTEM VALUE VALUES (20, 'xyz'); --- This ignores the 30 and uses the sequence value instead: -INSERT INTO itest2 OVERRIDING USER VALUE VALUES (30, 'xyz'); -SELECT * FROM itest2; - a | b -----+----- - 1 | - 2 | - 20 | xyz - 3 | xyz -(4 rows) - --- UPDATE tests --- GENERATED BY DEFAULT is not restricted. -UPDATE itest1 SET a = 101 WHERE a = 1; -UPDATE itest1 SET a = DEFAULT WHERE a = 2; -SELECT * FROM itest1; - a | b ------+----- - 10 | xyz - 20 | xyz - 3 | xyz - 101 | - 4 | -(5 rows) - --- GENERATED ALWAYS allows only DEFAULT. -UPDATE itest2 SET a = 101 WHERE a = 1; -- error -ERROR: column "a" can only be updated to DEFAULT -DETAIL: Column "a" is an identity column defined as GENERATED ALWAYS. -UPDATE itest2 SET a = DEFAULT WHERE a = 2; -- ok -SELECT * FROM itest2; - a | b -----+----- - 1 | - 20 | xyz - 3 | xyz - 4 | -(4 rows) - --- COPY tests -CREATE TABLE itest9 (a int GENERATED ALWAYS AS IDENTITY, b text, c bigint); -COPY itest9 FROM stdin; -COPY itest9 (b, c) FROM stdin; -SELECT * FROM itest9 ORDER BY c; - a | b | c ------+------+----- - 100 | foo | 200 - 101 | bar | 201 - 1 | foo2 | 202 - 2 | bar2 | 203 -(4 rows) - --- DROP IDENTITY tests -ALTER TABLE itest4 ALTER COLUMN a DROP IDENTITY; -ALTER TABLE itest4 ALTER COLUMN a DROP IDENTITY; -- error -ERROR: column "a" of relation "itest4" is not an identity column -ALTER TABLE itest4 ALTER COLUMN a DROP IDENTITY IF EXISTS; -- noop -NOTICE: column "a" of relation "itest4" is not an identity column, skipping -INSERT INTO itest4 DEFAULT VALUES; -- fails because NOT NULL is not dropped -ERROR: null value in column "a" of relation "itest4" violates not-null constraint -DETAIL: Failing row contains (null, ). -ALTER TABLE itest4 ALTER COLUMN a DROP NOT NULL; -INSERT INTO itest4 DEFAULT VALUES; -SELECT * FROM itest4; - a | b ----+--- - 1 | - 2 | - | -(3 rows) - --- check that sequence is removed -SELECT sequence_name FROM itest4_a_seq; -ERROR: relation "itest4_a_seq" does not exist -LINE 1: SELECT sequence_name FROM itest4_a_seq; - ^ --- test views -CREATE TABLE itest10 (a int generated by default as identity, b text); -CREATE TABLE itest11 (a int generated always as identity, b text); -CREATE VIEW itestv10 AS SELECT * FROM itest10; -CREATE VIEW itestv11 AS SELECT * FROM itest11; -INSERT INTO itestv10 DEFAULT VALUES; -INSERT INTO itestv10 DEFAULT VALUES; -INSERT INTO itestv11 DEFAULT VALUES; -INSERT INTO itestv11 DEFAULT VALUES; -SELECT * FROM itestv10; - a | b ----+--- - 1 | - 2 | -(2 rows) - -SELECT * FROM itestv11; - a | b ----+--- - 1 | - 2 | -(2 rows) - -INSERT INTO itestv10 VALUES (10, 'xyz'); -INSERT INTO itestv10 OVERRIDING USER VALUE VALUES (11, 'xyz'); -SELECT * FROM itestv10; - a | b -----+----- - 1 | - 2 | - 10 | xyz - 3 | xyz -(4 rows) - -INSERT INTO itestv11 VALUES (10, 'xyz'); -ERROR: cannot insert a non-DEFAULT value into column "a" -DETAIL: Column "a" is an identity column defined as GENERATED ALWAYS. -HINT: Use OVERRIDING SYSTEM VALUE to override. -INSERT INTO itestv11 OVERRIDING SYSTEM VALUE VALUES (11, 'xyz'); -SELECT * FROM itestv11; - a | b -----+----- - 1 | - 2 | - 11 | xyz -(3 rows) - -DROP VIEW itestv10, itestv11; --- ADD COLUMN -CREATE TABLE itest13 (a int); --- add column to empty table -ALTER TABLE itest13 ADD COLUMN b int GENERATED BY DEFAULT AS IDENTITY; -INSERT INTO itest13 VALUES (1), (2), (3); --- add column to populated table -ALTER TABLE itest13 ADD COLUMN c int GENERATED BY DEFAULT AS IDENTITY; -SELECT * FROM itest13; - a | b | c ----+---+--- - 1 | 1 | 1 - 2 | 2 | 2 - 3 | 3 | 3 -(3 rows) - --- various ALTER COLUMN tests --- fail, not allowed for identity columns -ALTER TABLE itest1 ALTER COLUMN a SET DEFAULT 1; -ERROR: column "a" of relation "itest1" is an identity column --- fail, not allowed, already has a default -CREATE TABLE itest5 (a serial, b text); -ALTER TABLE itest5 ALTER COLUMN a ADD GENERATED ALWAYS AS IDENTITY; -ERROR: column "a" of relation "itest5" already has a default value -ALTER TABLE itest3 ALTER COLUMN a TYPE int; -SELECT seqtypid::regtype FROM pg_sequence WHERE seqrelid = 'itest3_a_seq'::regclass; - seqtypid ----------- - integer -(1 row) - -\d itest3 - Table "public.itest3" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+---------------------------------- - a | integer | | not null | generated by default as identity - b | text | | | - -ALTER TABLE itest3 ALTER COLUMN a TYPE text; -- error -ERROR: identity column type must be smallint, integer, or bigint --- check that unlogged propagates to sequence -CREATE UNLOGGED TABLE itest17 (a int NOT NULL, b text); -ALTER TABLE itest17 ALTER COLUMN a ADD GENERATED ALWAYS AS IDENTITY; -ALTER TABLE itest17 ADD COLUMN c int GENERATED ALWAYS AS IDENTITY; -\d itest17 - Unlogged table "public.itest17" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+------------------------------ - a | integer | | not null | generated always as identity - b | text | | | - c | integer | | not null | generated always as identity - -\d itest17_a_seq - Unlogged sequence "public.itest17_a_seq" - Type | Start | Minimum | Maximum | Increment | Cycles? | Cache ----------+-------+---------+------------+-----------+---------+------- - integer | 1 | 1 | 2147483647 | 1 | no | 1 -Sequence for identity column: public.itest17.a - -\d itest17_c_seq - Unlogged sequence "public.itest17_c_seq" - Type | Start | Minimum | Maximum | Increment | Cycles? | Cache ----------+-------+---------+------------+-----------+---------+------- - integer | 1 | 1 | 2147483647 | 1 | no | 1 -Sequence for identity column: public.itest17.c - -CREATE TABLE itest18 (a int NOT NULL, b text); -ALTER TABLE itest18 SET UNLOGGED, ALTER COLUMN a ADD GENERATED ALWAYS AS IDENTITY; -\d itest18 - Unlogged table "public.itest18" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+------------------------------ - a | integer | | not null | generated always as identity - b | text | | | - -\d itest18_a_seq - Unlogged sequence "public.itest18_a_seq" - Type | Start | Minimum | Maximum | Increment | Cycles? | Cache ----------+-------+---------+------------+-----------+---------+------- - integer | 1 | 1 | 2147483647 | 1 | no | 1 -Sequence for identity column: public.itest18.a - -ALTER TABLE itest18 SET LOGGED; -\d itest18 - Table "public.itest18" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+------------------------------ - a | integer | | not null | generated always as identity - b | text | | | - -\d itest18_a_seq - Sequence "public.itest18_a_seq" - Type | Start | Minimum | Maximum | Increment | Cycles? | Cache ----------+-------+---------+------------+-----------+---------+------- - integer | 1 | 1 | 2147483647 | 1 | no | 1 -Sequence for identity column: public.itest18.a - -ALTER TABLE itest18 SET UNLOGGED; -\d itest18 - Unlogged table "public.itest18" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+------------------------------ - a | integer | | not null | generated always as identity - b | text | | | - -\d itest18_a_seq - Unlogged sequence "public.itest18_a_seq" - Type | Start | Minimum | Maximum | Increment | Cycles? | Cache ----------+-------+---------+------------+-----------+---------+------- - integer | 1 | 1 | 2147483647 | 1 | no | 1 -Sequence for identity column: public.itest18.a - --- kinda silly to change property in the same command, but it should work -ALTER TABLE itest3 - ADD COLUMN c int GENERATED BY DEFAULT AS IDENTITY, - ALTER COLUMN c SET GENERATED ALWAYS; -\d itest3 - Table "public.itest3" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+---------------------------------- - a | integer | | not null | generated by default as identity - b | text | | | - c | integer | | not null | generated always as identity - --- ALTER COLUMN ... SET -CREATE TABLE itest6 (a int GENERATED ALWAYS AS IDENTITY, b text); -INSERT INTO itest6 DEFAULT VALUES; -ALTER TABLE itest6 ALTER COLUMN a SET GENERATED BY DEFAULT SET INCREMENT BY 2 SET START WITH 100 RESTART; -INSERT INTO itest6 DEFAULT VALUES; -INSERT INTO itest6 DEFAULT VALUES; -SELECT * FROM itest6; - a | b ------+--- - 1 | - 100 | - 102 | -(3 rows) - -SELECT table_name, column_name, is_identity, identity_generation FROM information_schema.columns WHERE table_name = 'itest6' ORDER BY 1, 2; - table_name | column_name | is_identity | identity_generation -------------+-------------+-------------+--------------------- - itest6 | a | YES | BY DEFAULT - itest6 | b | NO | -(2 rows) - -ALTER TABLE itest6 ALTER COLUMN b SET INCREMENT BY 2; -- fail, not identity -ERROR: column "b" of relation "itest6" is not an identity column --- prohibited direct modification of sequence -ALTER SEQUENCE itest6_a_seq OWNED BY NONE; -ERROR: cannot change ownership of identity sequence -DETAIL: Sequence "itest6_a_seq" is linked to table "itest6". --- inheritance -CREATE TABLE itest7 (a int GENERATED ALWAYS AS IDENTITY); -INSERT INTO itest7 DEFAULT VALUES; -SELECT * FROM itest7; - a ---- - 1 -(1 row) - --- identity property is not inherited -CREATE TABLE itest7a (b text) INHERITS (itest7); --- make column identity in child table -CREATE TABLE itest7b (a int); -CREATE TABLE itest7c (a int GENERATED ALWAYS AS IDENTITY) INHERITS (itest7b); -NOTICE: merging column "a" with inherited definition -INSERT INTO itest7c DEFAULT VALUES; -SELECT * FROM itest7c; - a ---- - 1 -(1 row) - -CREATE TABLE itest7d (a int not null); -CREATE TABLE itest7e () INHERITS (itest7d); -ALTER TABLE itest7d ALTER COLUMN a ADD GENERATED ALWAYS AS IDENTITY; -ALTER TABLE itest7d ADD COLUMN b int GENERATED ALWAYS AS IDENTITY; -- error -ERROR: cannot recursively add identity column to table that has child tables -SELECT table_name, column_name, is_nullable, is_identity, identity_generation FROM information_schema.columns WHERE table_name LIKE 'itest7%' ORDER BY 1, 2; - table_name | column_name | is_nullable | is_identity | identity_generation -------------+-------------+-------------+-------------+--------------------- - itest7 | a | NO | YES | ALWAYS - itest7a | a | NO | NO | - itest7a | b | YES | NO | - itest7b | a | YES | NO | - itest7c | a | NO | YES | ALWAYS - itest7d | a | NO | YES | ALWAYS - itest7e | a | NO | NO | -(7 rows) - --- These ALTER TABLE variants will not recurse. -ALTER TABLE itest7 ALTER COLUMN a SET GENERATED BY DEFAULT; -ALTER TABLE itest7 ALTER COLUMN a RESTART; -ALTER TABLE itest7 ALTER COLUMN a DROP IDENTITY; --- privileges -CREATE USER regress_identity_user1; -CREATE TABLE itest8 (a int GENERATED ALWAYS AS IDENTITY, b text); -GRANT SELECT, INSERT ON itest8 TO regress_identity_user1; -SET ROLE regress_identity_user1; -INSERT INTO itest8 DEFAULT VALUES; -SELECT * FROM itest8; - a | b ----+--- - 1 | -(1 row) - -RESET ROLE; -DROP TABLE itest8; -DROP USER regress_identity_user1; --- multiple steps in ALTER TABLE -CREATE TABLE itest8 (f1 int); -ALTER TABLE itest8 - ADD COLUMN f2 int NOT NULL, - ALTER COLUMN f2 ADD GENERATED ALWAYS AS IDENTITY; -ALTER TABLE itest8 - ADD COLUMN f3 int NOT NULL, - ALTER COLUMN f3 ADD GENERATED ALWAYS AS IDENTITY, - ALTER COLUMN f3 SET GENERATED BY DEFAULT SET INCREMENT 10; -ALTER TABLE itest8 - ADD COLUMN f4 int; -ALTER TABLE itest8 - ALTER COLUMN f4 SET NOT NULL, - ALTER COLUMN f4 ADD GENERATED ALWAYS AS IDENTITY, - ALTER COLUMN f4 SET DATA TYPE bigint; -ALTER TABLE itest8 - ADD COLUMN f5 int GENERATED ALWAYS AS IDENTITY; -ALTER TABLE itest8 - ALTER COLUMN f5 DROP IDENTITY, - ALTER COLUMN f5 DROP NOT NULL, - ALTER COLUMN f5 SET DATA TYPE bigint; -INSERT INTO itest8 VALUES(0), (1); --- This does not work when the table isn't empty. That's intentional, --- since ADD GENERATED should only affect later insertions: -ALTER TABLE itest8 - ADD COLUMN f22 int NOT NULL, - ALTER COLUMN f22 ADD GENERATED ALWAYS AS IDENTITY; -ERROR: column "f22" of relation "itest8" contains null values -TABLE itest8; - f1 | f2 | f3 | f4 | f5 -----+----+----+----+---- - 0 | 1 | 1 | 1 | - 1 | 2 | 11 | 2 | -(2 rows) - -\d+ itest8 - Table "public.itest8" - Column | Type | Collation | Nullable | Default | Storage | Stats target | Description ---------+---------+-----------+----------+----------------------------------+---------+--------------+------------- - f1 | integer | | | | plain | | - f2 | integer | | not null | generated always as identity | plain | | - f3 | integer | | not null | generated by default as identity | plain | | - f4 | bigint | | not null | generated always as identity | plain | | - f5 | bigint | | | | plain | | - -\d itest8_f2_seq - Sequence "public.itest8_f2_seq" - Type | Start | Minimum | Maximum | Increment | Cycles? | Cache ----------+-------+---------+------------+-----------+---------+------- - integer | 1 | 1 | 2147483647 | 1 | no | 1 -Sequence for identity column: public.itest8.f2 - -\d itest8_f3_seq - Sequence "public.itest8_f3_seq" - Type | Start | Minimum | Maximum | Increment | Cycles? | Cache ----------+-------+---------+------------+-----------+---------+------- - integer | 1 | 1 | 2147483647 | 10 | no | 1 -Sequence for identity column: public.itest8.f3 - -\d itest8_f4_seq - Sequence "public.itest8_f4_seq" - Type | Start | Minimum | Maximum | Increment | Cycles? | Cache ---------+-------+---------+---------------------+-----------+---------+------- - bigint | 1 | 1 | 9223372036854775807 | 1 | no | 1 -Sequence for identity column: public.itest8.f4 - -\d itest8_f5_seq -DROP TABLE itest8; --- typed tables (currently not supported) -CREATE TYPE itest_type AS (f1 integer, f2 text, f3 bigint); -CREATE TABLE itest12 OF itest_type (f1 WITH OPTIONS GENERATED ALWAYS AS IDENTITY); -- error -ERROR: identity columns are not supported on typed tables -DROP TYPE itest_type CASCADE; --- table partitions --- partitions inherit identity column and share sequence -CREATE TABLE pitest1 (f1 date NOT NULL, f2 text, f3 bigint generated always as identity) PARTITION BY RANGE (f1); --- new partition -CREATE TABLE pitest1_p1 PARTITION OF pitest1 FOR VALUES FROM ('2016-07-01') TO ('2016-08-01'); -INSERT into pitest1(f1, f2) VALUES ('2016-07-2', 'from pitest1'); -INSERT into pitest1_p1 (f1, f2) VALUES ('2016-07-3', 'from pitest1_p1'); --- attached partition -CREATE TABLE pitest1_p2 (f3 bigint, f2 text, f1 date NOT NULL); -INSERT INTO pitest1_p2 (f1, f2, f3) VALUES ('2016-08-2', 'before attaching', 100); -ALTER TABLE pitest1 ATTACH PARTITION pitest1_p2 FOR VALUES FROM ('2016-08-01') TO ('2016-09-01'); -- requires NOT NULL constraint -ERROR: column "f3" in child table must be marked NOT NULL -ALTER TABLE pitest1_p2 ALTER COLUMN f3 SET NOT NULL; -ALTER TABLE pitest1 ATTACH PARTITION pitest1_p2 FOR VALUES FROM ('2016-08-01') TO ('2016-09-01'); -INSERT INTO pitest1_p2 (f1, f2) VALUES ('2016-08-3', 'from pitest1_p2'); -INSERT INTO pitest1 (f1, f2) VALUES ('2016-08-4', 'from pitest1'); --- LIKE INCLUDING on partition -CREATE TABLE pitest1_p1_like (LIKE pitest1_p1 INCLUDING IDENTITY); -INSERT into pitest1_p1_like(f1, f2) VALUES ('2016-07-2', 'from pitest1_p1_like'); -SELECT tableoid::regclass, f1, f2, f3 FROM pitest1; - tableoid | f1 | f2 | f3 -------------+------------+------------------+----- - pitest1_p1 | 07-02-2016 | from pitest1 | 1 - pitest1_p1 | 07-03-2016 | from pitest1_p1 | 2 - pitest1_p2 | 08-02-2016 | before attaching | 100 - pitest1_p2 | 08-03-2016 | from pitest1_p2 | 3 - pitest1_p2 | 08-04-2016 | from pitest1 | 4 -(5 rows) - -SELECT tableoid::regclass, f1, f2, f3 FROM pitest1_p1_like; - tableoid | f1 | f2 | f3 ------------------+------------+----------------------+---- - pitest1_p1_like | 07-02-2016 | from pitest1_p1_like | 1 -(1 row) - -ALTER TABLE pitest1 ALTER COLUMN f3 SET DATA TYPE bigint; -SELECT tableoid::regclass, f1, f2, f3, pg_typeof(f3) FROM pitest1; - tableoid | f1 | f2 | f3 | pg_typeof -------------+------------+------------------+-----+----------- - pitest1_p1 | 07-02-2016 | from pitest1 | 1 | bigint - pitest1_p1 | 07-03-2016 | from pitest1_p1 | 2 | bigint - pitest1_p2 | 08-02-2016 | before attaching | 100 | bigint - pitest1_p2 | 08-03-2016 | from pitest1_p2 | 3 | bigint - pitest1_p2 | 08-04-2016 | from pitest1 | 4 | bigint -(5 rows) - -SELECT tableoid::regclass, f1, f2, f3, pg_typeof(f3) FROM pitest1_p2; - tableoid | f1 | f2 | f3 | pg_typeof -------------+------------+------------------+-----+----------- - pitest1_p2 | 08-02-2016 | before attaching | 100 | bigint - pitest1_p2 | 08-03-2016 | from pitest1_p2 | 3 | bigint - pitest1_p2 | 08-04-2016 | from pitest1 | 4 | bigint -(3 rows) - --- add identity column -CREATE TABLE pitest2 (f1 date NOT NULL, f2 text) PARTITION BY RANGE (f1); -CREATE TABLE pitest2_p1 PARTITION OF pitest2 FOR VALUES FROM ('2016-07-01') TO ('2016-08-01'); -CREATE TABLE pitest2_p2 PARTITION OF pitest2 FOR VALUES FROM ('2016-08-01') TO ('2016-09-01'); -INSERT into pitest2(f1, f2) VALUES ('2016-07-2', 'from pitest2'); -INSERT INTO pitest2 (f1, f2) VALUES ('2016-08-2', 'from pitest2'); -ALTER TABLE pitest2 ADD COLUMN f3 int GENERATED ALWAYS AS IDENTITY; -INSERT into pitest2_p1 (f1, f2) VALUES ('2016-07-3', 'from pitest2_p1'); -INSERT INTO pitest2_p2 (f1, f2) VALUES ('2016-08-3', 'from pitest2_p2'); -INSERT into pitest2(f1, f2) VALUES ('2016-07-4', 'from pitest2'); -INSERT INTO pitest2 (f1, f2) VALUES ('2016-08-4', 'from pitest2'); -SELECT tableoid::regclass, f1, f2, f3 FROM pitest2; - tableoid | f1 | f2 | f3 -------------+------------+-----------------+---- - pitest2_p1 | 07-02-2016 | from pitest2 | 1 - pitest2_p1 | 07-03-2016 | from pitest2_p1 | 3 - pitest2_p1 | 07-04-2016 | from pitest2 | 5 - pitest2_p2 | 08-02-2016 | from pitest2 | 2 - pitest2_p2 | 08-03-2016 | from pitest2_p2 | 4 - pitest2_p2 | 08-04-2016 | from pitest2 | 6 -(6 rows) - --- SET identity column -ALTER TABLE pitest2_p1 ALTER COLUMN f3 SET GENERATED BY DEFAULT; -- fails -ERROR: cannot change identity column of a partition -ALTER TABLE pitest2_p1 ALTER COLUMN f3 SET INCREMENT BY 2; -- fails -ERROR: cannot change identity column of a partition -ALTER TABLE ONLY pitest2 ALTER COLUMN f3 SET GENERATED BY DEFAULT SET INCREMENT BY 2 SET START WITH 1000 RESTART; -- fails -ERROR: cannot change identity column of only the partitioned table -HINT: Do not specify the ONLY keyword. -ALTER TABLE pitest2 ALTER COLUMN f3 SET GENERATED BY DEFAULT SET INCREMENT BY 2 SET START WITH 1000 RESTART; -INSERT into pitest2(f1, f2, f3) VALUES ('2016-07-5', 'from pitest2', 200); -INSERT INTO pitest2(f1, f2) VALUES ('2016-08-5', 'from pitest2'); -INSERT into pitest2_p1 (f1, f2) VALUES ('2016-07-6', 'from pitest2_p1'); -INSERT INTO pitest2_p2 (f1, f2, f3) VALUES ('2016-08-6', 'from pitest2_p2', 300); -SELECT tableoid::regclass, f1, f2, f3 FROM pitest2; - tableoid | f1 | f2 | f3 -------------+------------+-----------------+------ - pitest2_p1 | 07-02-2016 | from pitest2 | 1 - pitest2_p1 | 07-03-2016 | from pitest2_p1 | 3 - pitest2_p1 | 07-04-2016 | from pitest2 | 5 - pitest2_p1 | 07-05-2016 | from pitest2 | 200 - pitest2_p1 | 07-06-2016 | from pitest2_p1 | 1002 - pitest2_p2 | 08-02-2016 | from pitest2 | 2 - pitest2_p2 | 08-03-2016 | from pitest2_p2 | 4 - pitest2_p2 | 08-04-2016 | from pitest2 | 6 - pitest2_p2 | 08-05-2016 | from pitest2 | 1000 - pitest2_p2 | 08-06-2016 | from pitest2_p2 | 300 -(10 rows) - --- detaching a partition removes identity property -ALTER TABLE pitest2 DETACH PARTITION pitest2_p1; -INSERT into pitest2(f1, f2) VALUES ('2016-08-7', 'from pitest2'); -INSERT into pitest2_p1 (f1, f2) VALUES ('2016-07-7', 'from pitest2_p1'); -- error -ERROR: null value in column "f3" of relation "pitest2_p1" violates not-null constraint -DETAIL: Failing row contains (07-07-2016, from pitest2_p1, null). -INSERT into pitest2_p1 (f1, f2, f3) VALUES ('2016-07-7', 'from pitest2_p1', 2000); -SELECT tableoid::regclass, f1, f2, f3 FROM pitest2; - tableoid | f1 | f2 | f3 -------------+------------+-----------------+------ - pitest2_p2 | 08-02-2016 | from pitest2 | 2 - pitest2_p2 | 08-03-2016 | from pitest2_p2 | 4 - pitest2_p2 | 08-04-2016 | from pitest2 | 6 - pitest2_p2 | 08-05-2016 | from pitest2 | 1000 - pitest2_p2 | 08-06-2016 | from pitest2_p2 | 300 - pitest2_p2 | 08-07-2016 | from pitest2 | 1004 -(6 rows) - -SELECT tableoid::regclass, f1, f2, f3 FROM pitest2_p1; - tableoid | f1 | f2 | f3 -------------+------------+-----------------+------ - pitest2_p1 | 07-02-2016 | from pitest2 | 1 - pitest2_p1 | 07-03-2016 | from pitest2_p1 | 3 - pitest2_p1 | 07-04-2016 | from pitest2 | 5 - pitest2_p1 | 07-05-2016 | from pitest2 | 200 - pitest2_p1 | 07-06-2016 | from pitest2_p1 | 1002 - pitest2_p1 | 07-07-2016 | from pitest2_p1 | 2000 -(6 rows) - -DROP TABLE pitest2_p1; --- changing a regular column to identity column in a partitioned table -CREATE TABLE pitest3 (f1 date NOT NULL, f2 text, f3 int) PARTITION BY RANGE (f1); -CREATE TABLE pitest3_p1 PARTITION OF pitest3 FOR VALUES FROM ('2016-07-01') TO ('2016-08-01'); -INSERT into pitest3 VALUES ('2016-07-2', 'from pitest3', 1); -INSERT into pitest3_p1 VALUES ('2016-07-3', 'from pitest3_p1', 2); --- fails, changing only a partition not allowed -ALTER TABLE pitest3_p1 - ALTER COLUMN f3 SET NOT NULL, - ALTER COLUMN f3 ADD GENERATED ALWAYS AS IDENTITY (START WITH 3); -ERROR: cannot add identity to a column of a partition --- fails, changing only the partitioned table not allowed -BEGIN; -ALTER TABLE pitest3_p1 ALTER COLUMN f3 SET NOT NULL; -ALTER TABLE ONLY pitest3 - ALTER COLUMN f3 ADD GENERATED ALWAYS AS IDENTITY (START WITH 3); -ERROR: cannot add identity to a column of only the partitioned table -HINT: Do not specify the ONLY keyword. -ROLLBACK; -ALTER TABLE pitest3 - ALTER COLUMN f3 SET NOT NULL, - ALTER COLUMN f3 ADD GENERATED ALWAYS AS IDENTITY (START WITH 3); -INSERT into pitest3(f1, f2) VALUES ('2016-07-4', 'from pitest3'); -INSERT into pitest3_p1 (f1, f2) VALUES ('2016-07-5', 'from pitest3_p1'); -SELECT tableoid::regclass, f1, f2, f3 FROM pitest3; - tableoid | f1 | f2 | f3 -------------+------------+-----------------+---- - pitest3_p1 | 07-02-2016 | from pitest3 | 1 - pitest3_p1 | 07-03-2016 | from pitest3_p1 | 2 - pitest3_p1 | 07-04-2016 | from pitest3 | 3 - pitest3_p1 | 07-05-2016 | from pitest3_p1 | 4 -(4 rows) - --- changing an identity column to a non-identity column in a partitioned table -ALTER TABLE pitest3_p1 ALTER COLUMN f3 DROP IDENTITY; -- fails -ERROR: cannot drop identity from a column of a partition -ALTER TABLE ONLY pitest3 ALTER COLUMN f3 DROP IDENTITY; -- fails -ERROR: cannot drop identity from a column of only the partitioned table -HINT: Do not specify the ONLY keyword. -ALTER TABLE pitest3 ALTER COLUMN f3 DROP IDENTITY; -INSERT into pitest3(f1, f2) VALUES ('2016-07-4', 'from pitest3'); -- fails -ERROR: null value in column "f3" of relation "pitest3_p1" violates not-null constraint -DETAIL: Failing row contains (07-04-2016, from pitest3, null). -INSERT into pitest3_p1 (f1, f2) VALUES ('2016-07-5', 'from pitest3_p1'); -- fails -ERROR: null value in column "f3" of relation "pitest3_p1" violates not-null constraint -DETAIL: Failing row contains (07-05-2016, from pitest3_p1, null). -INSERT into pitest3(f1, f2, f3) VALUES ('2016-07-6', 'from pitest3', 5); -INSERT into pitest3_p1 (f1, f2, f3) VALUES ('2016-07-7', 'from pitest3_p1', 6); -SELECT tableoid::regclass, f1, f2, f3 FROM pitest3; - tableoid | f1 | f2 | f3 -------------+------------+-----------------+---- - pitest3_p1 | 07-02-2016 | from pitest3 | 1 - pitest3_p1 | 07-03-2016 | from pitest3_p1 | 2 - pitest3_p1 | 07-04-2016 | from pitest3 | 3 - pitest3_p1 | 07-05-2016 | from pitest3_p1 | 4 - pitest3_p1 | 07-06-2016 | from pitest3 | 5 - pitest3_p1 | 07-07-2016 | from pitest3_p1 | 6 -(6 rows) - --- Changing NOT NULL constraint of identity columns is not allowed -ALTER TABLE pitest1_p1 ALTER COLUMN f3 DROP NOT NULL; -ERROR: column "f3" of relation "pitest1_p1" is an identity column -ALTER TABLE pitest1 ALTER COLUMN f3 DROP NOT NULL; -ERROR: column "f3" of relation "pitest1" is an identity column --- Identity columns have their own default -ALTER TABLE pitest1_p2 ALTER COLUMN f3 SET DEFAULT 10000; -ERROR: column "f3" of relation "pitest1_p2" is an identity column -ALTER TABLE pitest1 ALTER COLUMN f3 SET DEFAULT 10000; -ERROR: column "f3" of relation "pitest1" is an identity column --- Adding identity to an identity column is not allowed -ALTER TABLE pitest1_p2 ALTER COLUMN f3 ADD GENERATED BY DEFAULT AS IDENTITY; -ERROR: cannot add identity to a column of a partition -ALTER TABLE pitest1 ALTER COLUMN f3 ADD GENERATED BY DEFAULT AS IDENTITY; -ERROR: column "f3" of relation "pitest1" is already an identity column --- partitions with their own identity columns are not allowed, even if the --- partitioned table does not have an identity column. -CREATE TABLE pitest1_pfail PARTITION OF pitest1 ( - f3 WITH OPTIONS GENERATED ALWAYS AS IDENTITY -) FOR VALUES FROM ('2016-11-01') TO ('2016-12-01'); -ERROR: identity columns are not supported on partitions -CREATE TABLE pitest_pfail PARTITION OF pitest3 ( - f3 WITH OPTIONS GENERATED ALWAYS AS IDENTITY -) FOR VALUES FROM ('2016-07-01') TO ('2016-08-01'); -ERROR: identity columns are not supported on partitions -CREATE TABLE pitest1_pfail (f1 date NOT NULL, f2 text, f3 bigint GENERATED ALWAYS AS IDENTITY); -ALTER TABLE pitest1 ATTACH PARTITION pitest1_pfail FOR VALUES FROM ('2016-11-01') TO ('2016-12-01'); -ERROR: table "pitest1_pfail" being attached contains an identity column "f3" -DETAIL: The new partition may not contain an identity column. -ALTER TABLE pitest3 ATTACH PARTITION pitest1_pfail FOR VALUES FROM ('2016-11-01') TO ('2016-12-01'); -ERROR: table "pitest1_pfail" being attached contains an identity column "f3" -DETAIL: The new partition may not contain an identity column. -DROP TABLE pitest1_pfail; -DROP TABLE pitest3; --- test that sequence of half-dropped serial column is properly ignored -CREATE TABLE itest14 (id serial); -ALTER TABLE itest14 ALTER id DROP DEFAULT; -ALTER TABLE itest14 ALTER id ADD GENERATED BY DEFAULT AS IDENTITY; -INSERT INTO itest14 (id) VALUES (DEFAULT); --- Identity columns must be NOT NULL (cf bug #16913) -CREATE TABLE itest15 (id integer GENERATED ALWAYS AS IDENTITY NULL); -- fail -ERROR: conflicting NULL/NOT NULL declarations for column "id" of table "itest15" -LINE 1: ...ABLE itest15 (id integer GENERATED ALWAYS AS IDENTITY NULL); - ^ -CREATE TABLE itest15 (id integer NULL GENERATED ALWAYS AS IDENTITY); -- fail -ERROR: conflicting NULL/NOT NULL declarations for column "id" of table "itest15" -LINE 1: CREATE TABLE itest15 (id integer NULL GENERATED ALWAYS AS ID... - ^ -CREATE TABLE itest15 (id integer GENERATED ALWAYS AS IDENTITY NOT NULL); -DROP TABLE itest15; -CREATE TABLE itest15 (id integer NOT NULL GENERATED ALWAYS AS IDENTITY); -DROP TABLE itest15; --- MERGE tests -CREATE TABLE itest15 (a int GENERATED ALWAYS AS IDENTITY, b text); -CREATE TABLE itest16 (a int GENERATED BY DEFAULT AS IDENTITY, b text); -MERGE INTO itest15 t -USING (SELECT 10 AS s_a, 'inserted by merge' AS s_b) s -ON t.a = s.s_a -WHEN NOT MATCHED THEN - INSERT (a, b) VALUES (s.s_a, s.s_b); -ERROR: cannot insert a non-DEFAULT value into column "a" -DETAIL: Column "a" is an identity column defined as GENERATED ALWAYS. -HINT: Use OVERRIDING SYSTEM VALUE to override. --- Used to fail, but now it works and ignores the user supplied value -MERGE INTO itest15 t -USING (SELECT 20 AS s_a, 'inserted by merge' AS s_b) s -ON t.a = s.s_a -WHEN NOT MATCHED THEN - INSERT (a, b) OVERRIDING USER VALUE VALUES (s.s_a, s.s_b); -MERGE INTO itest15 t -USING (SELECT 30 AS s_a, 'inserted by merge' AS s_b) s -ON t.a = s.s_a -WHEN NOT MATCHED THEN - INSERT (a, b) OVERRIDING SYSTEM VALUE VALUES (s.s_a, s.s_b); -MERGE INTO itest16 t -USING (SELECT 10 AS s_a, 'inserted by merge' AS s_b) s -ON t.a = s.s_a -WHEN NOT MATCHED THEN - INSERT (a, b) VALUES (s.s_a, s.s_b); -MERGE INTO itest16 t -USING (SELECT 20 AS s_a, 'inserted by merge' AS s_b) s -ON t.a = s.s_a -WHEN NOT MATCHED THEN - INSERT (a, b) OVERRIDING USER VALUE VALUES (s.s_a, s.s_b); -MERGE INTO itest16 t -USING (SELECT 30 AS s_a, 'inserted by merge' AS s_b) s -ON t.a = s.s_a -WHEN NOT MATCHED THEN - INSERT (a, b) OVERRIDING SYSTEM VALUE VALUES (s.s_a, s.s_b); -SELECT * FROM itest15; - a | b -----+------------------- - 1 | inserted by merge - 30 | inserted by merge -(2 rows) - -SELECT * FROM itest16; - a | b -----+------------------- - 10 | inserted by merge - 1 | inserted by merge - 30 | inserted by merge -(3 rows) - -DROP TABLE itest15; -DROP TABLE itest16; --- For testing of pg_dump and pg_upgrade, leave behind some identity --- sequences whose logged-ness doesn't match their owning table's. -CREATE TABLE identity_dump_logged (a INT GENERATED ALWAYS AS IDENTITY); -ALTER SEQUENCE identity_dump_logged_a_seq SET UNLOGGED; -CREATE UNLOGGED TABLE identity_dump_unlogged (a INT GENERATED ALWAYS AS IDENTITY); -ALTER SEQUENCE identity_dump_unlogged_a_seq SET LOGGED; -SELECT relname, relpersistence FROM pg_class - WHERE relname ~ '^identity_dump_' ORDER BY 1; - relname | relpersistence -------------------------------+---------------- - identity_dump_logged | p - identity_dump_logged_a_seq | u - identity_dump_unlogged | u - identity_dump_unlogged_a_seq | p -(4 rows) - +psql: error: connection to server on socket "/tmp/pg_regress-03Az7d/.s.PGSQL.40051" failed: FATAL: the database system is not yet accepting connections +DETAIL: Consistent recovery state has not been yet reached. diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/generated_stored.out /tmp/cirrus-ci-build/build/testrun/regress/regress/results/generated_stored.out --- /tmp/cirrus-ci-build/src/test/regress/expected/generated_stored.out 2024-09-20 21:41:45.686024521 +0000 +++ /tmp/cirrus-ci-build/build/testrun/regress/regress/results/generated_stored.out 2024-09-20 21:46:06.645623497 +0000 @@ -1,1356 +1,2 @@ --- sanity check of system catalog -SELECT attrelid, attname, attgenerated FROM pg_attribute WHERE attgenerated NOT IN ('', 's'); - attrelid | attname | attgenerated -----------+---------+-------------- -(0 rows) - -CREATE SCHEMA generated_stored_tests; -GRANT USAGE ON SCHEMA generated_stored_tests TO PUBLIC; -SET search_path = generated_stored_tests; -CREATE TABLE gtest0 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (55) STORED); -CREATE TABLE gtest1 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED); -SELECT table_name, column_name, column_default, is_nullable, is_generated, generation_expression FROM information_schema.columns WHERE table_schema = 'generated_stored_tests' ORDER BY 1, 2; - table_name | column_name | column_default | is_nullable | is_generated | generation_expression -------------+-------------+----------------+-------------+--------------+----------------------- - gtest0 | a | | NO | NEVER | - gtest0 | b | | YES | ALWAYS | 55 - gtest1 | a | | NO | NEVER | - gtest1 | b | | YES | ALWAYS | (a * 2) -(4 rows) - -SELECT table_name, column_name, dependent_column FROM information_schema.column_column_usage WHERE table_schema = 'generated_stored_tests' ORDER BY 1, 2, 3; - table_name | column_name | dependent_column -------------+-------------+------------------ - gtest1 | a | b -(1 row) - -\d gtest1 - Table "generated_stored_tests.gtest1" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+------------------------------------ - a | integer | | not null | - b | integer | | | generated always as (a * 2) stored -Indexes: - "gtest1_pkey" PRIMARY KEY, btree (a) - --- duplicate generated -CREATE TABLE gtest_err_1 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED GENERATED ALWAYS AS (a * 3) STORED); -ERROR: multiple generation clauses specified for column "b" of table "gtest_err_1" -LINE 1: ...ARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED GENERATED ... - ^ --- references to other generated columns, including self-references -CREATE TABLE gtest_err_2a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (b * 2) STORED); -ERROR: cannot use generated column "b" in column generation expression -LINE 1: ...2a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (b * 2) STO... - ^ -DETAIL: A generated column cannot reference another generated column. -CREATE TABLE gtest_err_2b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED, c int GENERATED ALWAYS AS (b * 3) STORED); -ERROR: cannot use generated column "b" in column generation expression -LINE 1: ...AYS AS (a * 2) STORED, c int GENERATED ALWAYS AS (b * 3) STO... - ^ -DETAIL: A generated column cannot reference another generated column. --- a whole-row var is a self-reference on steroids, so disallow that too -CREATE TABLE gtest_err_2c (a int PRIMARY KEY, - b int GENERATED ALWAYS AS (num_nulls(gtest_err_2c)) STORED); -ERROR: cannot use whole-row variable in column generation expression -LINE 2: b int GENERATED ALWAYS AS (num_nulls(gtest_err_2c)) STOR... - ^ -DETAIL: This would cause the generated column to depend on its own value. --- invalid reference -CREATE TABLE gtest_err_3 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (c * 2) STORED); -ERROR: column "c" does not exist -LINE 1: ..._3 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (c * 2) STO... - ^ --- generation expression must be immutable -CREATE TABLE gtest_err_4 (a int PRIMARY KEY, b double precision GENERATED ALWAYS AS (random()) STORED); -ERROR: generation expression is not immutable --- ... but be sure that the immutability test is accurate -CREATE TABLE gtest2 (a int, b text GENERATED ALWAYS AS (a || ' sec') STORED); -DROP TABLE gtest2; --- cannot have default/identity and generated -CREATE TABLE gtest_err_5a (a int PRIMARY KEY, b int DEFAULT 5 GENERATED ALWAYS AS (a * 2) STORED); -ERROR: both default and generation expression specified for column "b" of table "gtest_err_5a" -LINE 1: ... gtest_err_5a (a int PRIMARY KEY, b int DEFAULT 5 GENERATED ... - ^ -CREATE TABLE gtest_err_5b (a int PRIMARY KEY, b int GENERATED ALWAYS AS identity GENERATED ALWAYS AS (a * 2) STORED); -ERROR: both identity and generation expression specified for column "b" of table "gtest_err_5b" -LINE 1: ...t PRIMARY KEY, b int GENERATED ALWAYS AS identity GENERATED ... - ^ --- reference to system column not allowed in generated column --- (except tableoid, which we test below) -CREATE TABLE gtest_err_6a (a int PRIMARY KEY, b bool GENERATED ALWAYS AS (xmin <> 37) STORED); -ERROR: cannot use system column "xmin" in column generation expression -LINE 1: ...a (a int PRIMARY KEY, b bool GENERATED ALWAYS AS (xmin <> 37... - ^ --- various prohibited constructs -CREATE TABLE gtest_err_7a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (avg(a)) STORED); -ERROR: aggregate functions are not allowed in column generation expressions -LINE 1: ...7a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (avg(a)) ST... - ^ -CREATE TABLE gtest_err_7b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (row_number() OVER (ORDER BY a)) STORED); -ERROR: window functions are not allowed in column generation expressions -LINE 1: ...7b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (row_number... - ^ -CREATE TABLE gtest_err_7c (a int PRIMARY KEY, b int GENERATED ALWAYS AS ((SELECT a)) STORED); -ERROR: cannot use subquery in column generation expression -LINE 1: ...7c (a int PRIMARY KEY, b int GENERATED ALWAYS AS ((SELECT a)... - ^ -CREATE TABLE gtest_err_7d (a int PRIMARY KEY, b int GENERATED ALWAYS AS (generate_series(1, a)) STORED); -ERROR: set-returning functions are not allowed in column generation expressions -LINE 1: ...7d (a int PRIMARY KEY, b int GENERATED ALWAYS AS (generate_s... - ^ --- GENERATED BY DEFAULT not allowed -CREATE TABLE gtest_err_8 (a int PRIMARY KEY, b int GENERATED BY DEFAULT AS (a * 2) STORED); -ERROR: for a generated column, GENERATED ALWAYS must be specified -LINE 1: ...E gtest_err_8 (a int PRIMARY KEY, b int GENERATED BY DEFAULT... - ^ -INSERT INTO gtest1 VALUES (1); -INSERT INTO gtest1 VALUES (2, DEFAULT); -- ok -INSERT INTO gtest1 VALUES (3, 33); -- error -ERROR: cannot insert a non-DEFAULT value into column "b" -DETAIL: Column "b" is a generated column. -INSERT INTO gtest1 VALUES (3, 33), (4, 44); -- error -ERROR: cannot insert a non-DEFAULT value into column "b" -DETAIL: Column "b" is a generated column. -INSERT INTO gtest1 VALUES (3, DEFAULT), (4, 44); -- error -ERROR: cannot insert a non-DEFAULT value into column "b" -DETAIL: Column "b" is a generated column. -INSERT INTO gtest1 VALUES (3, 33), (4, DEFAULT); -- error -ERROR: cannot insert a non-DEFAULT value into column "b" -DETAIL: Column "b" is a generated column. -INSERT INTO gtest1 VALUES (3, DEFAULT), (4, DEFAULT); -- ok -SELECT * FROM gtest1 ORDER BY a; - a | b ----+--- - 1 | 2 - 2 | 4 - 3 | 6 - 4 | 8 -(4 rows) - -DELETE FROM gtest1 WHERE a >= 3; -UPDATE gtest1 SET b = DEFAULT WHERE a = 1; -UPDATE gtest1 SET b = 11 WHERE a = 1; -- error -ERROR: column "b" can only be updated to DEFAULT -DETAIL: Column "b" is a generated column. -SELECT * FROM gtest1 ORDER BY a; - a | b ----+--- - 1 | 2 - 2 | 4 -(2 rows) - -SELECT a, b, b * 2 AS b2 FROM gtest1 ORDER BY a; - a | b | b2 ----+---+---- - 1 | 2 | 4 - 2 | 4 | 8 -(2 rows) - -SELECT a, b FROM gtest1 WHERE b = 4 ORDER BY a; - a | b ----+--- - 2 | 4 -(1 row) - --- test that overflow error happens on write -INSERT INTO gtest1 VALUES (2000000000); -ERROR: integer out of range -SELECT * FROM gtest1; - a | b ----+--- - 2 | 4 - 1 | 2 -(2 rows) - -DELETE FROM gtest1 WHERE a = 2000000000; --- test with joins -CREATE TABLE gtestx (x int, y int); -INSERT INTO gtestx VALUES (11, 1), (22, 2), (33, 3); -SELECT * FROM gtestx, gtest1 WHERE gtestx.y = gtest1.a; - x | y | a | b -----+---+---+--- - 11 | 1 | 1 | 2 - 22 | 2 | 2 | 4 -(2 rows) - -DROP TABLE gtestx; --- test UPDATE/DELETE quals -SELECT * FROM gtest1 ORDER BY a; - a | b ----+--- - 1 | 2 - 2 | 4 -(2 rows) - -UPDATE gtest1 SET a = 3 WHERE b = 4; -SELECT * FROM gtest1 ORDER BY a; - a | b ----+--- - 1 | 2 - 3 | 6 -(2 rows) - -DELETE FROM gtest1 WHERE b = 2; -SELECT * FROM gtest1 ORDER BY a; - a | b ----+--- - 3 | 6 -(1 row) - --- test MERGE -CREATE TABLE gtestm ( - id int PRIMARY KEY, - f1 int, - f2 int, - f3 int GENERATED ALWAYS AS (f1 * 2) STORED, - f4 int GENERATED ALWAYS AS (f2 * 2) STORED -); -INSERT INTO gtestm VALUES (1, 5, 100); -MERGE INTO gtestm t USING (VALUES (1, 10), (2, 20)) v(id, f1) ON t.id = v.id - WHEN MATCHED THEN UPDATE SET f1 = v.f1 - WHEN NOT MATCHED THEN INSERT VALUES (v.id, v.f1, 200); -SELECT * FROM gtestm ORDER BY id; - id | f1 | f2 | f3 | f4 -----+----+-----+----+----- - 1 | 10 | 100 | 20 | 200 - 2 | 20 | 200 | 40 | 400 -(2 rows) - -DROP TABLE gtestm; --- views -CREATE VIEW gtest1v AS SELECT * FROM gtest1; -SELECT * FROM gtest1v; - a | b ----+--- - 3 | 6 -(1 row) - -INSERT INTO gtest1v VALUES (4, 8); -- error -ERROR: cannot insert a non-DEFAULT value into column "b" -DETAIL: Column "b" is a generated column. -INSERT INTO gtest1v VALUES (5, DEFAULT); -- ok -INSERT INTO gtest1v VALUES (6, 66), (7, 77); -- error -ERROR: cannot insert a non-DEFAULT value into column "b" -DETAIL: Column "b" is a generated column. -INSERT INTO gtest1v VALUES (6, DEFAULT), (7, 77); -- error -ERROR: cannot insert a non-DEFAULT value into column "b" -DETAIL: Column "b" is a generated column. -INSERT INTO gtest1v VALUES (6, 66), (7, DEFAULT); -- error -ERROR: cannot insert a non-DEFAULT value into column "b" -DETAIL: Column "b" is a generated column. -INSERT INTO gtest1v VALUES (6, DEFAULT), (7, DEFAULT); -- ok -ALTER VIEW gtest1v ALTER COLUMN b SET DEFAULT 100; -INSERT INTO gtest1v VALUES (8, DEFAULT); -- error -ERROR: cannot insert a non-DEFAULT value into column "b" -DETAIL: Column "b" is a generated column. -INSERT INTO gtest1v VALUES (8, DEFAULT), (9, DEFAULT); -- error -ERROR: cannot insert a non-DEFAULT value into column "b" -DETAIL: Column "b" is a generated column. -SELECT * FROM gtest1v; - a | b ----+---- - 3 | 6 - 5 | 10 - 6 | 12 - 7 | 14 -(4 rows) - -DELETE FROM gtest1v WHERE a >= 5; -DROP VIEW gtest1v; --- CTEs -WITH foo AS (SELECT * FROM gtest1) SELECT * FROM foo; - a | b ----+--- - 3 | 6 -(1 row) - --- inheritance -CREATE TABLE gtest1_1 () INHERITS (gtest1); -SELECT * FROM gtest1_1; - a | b ----+--- -(0 rows) - -\d gtest1_1 - Table "generated_stored_tests.gtest1_1" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+------------------------------------ - a | integer | | not null | - b | integer | | | generated always as (a * 2) stored -Inherits: gtest1 - -INSERT INTO gtest1_1 VALUES (4); -SELECT * FROM gtest1_1; - a | b ----+--- - 4 | 8 -(1 row) - -SELECT * FROM gtest1; - a | b ----+--- - 3 | 6 - 4 | 8 -(2 rows) - --- can't have generated column that is a child of normal column -CREATE TABLE gtest_normal (a int, b int); -CREATE TABLE gtest_normal_child (a int, b int GENERATED ALWAYS AS (a * 2) STORED) INHERITS (gtest_normal); -- error -NOTICE: merging column "a" with inherited definition -NOTICE: merging column "b" with inherited definition -ERROR: child column "b" specifies generation expression -HINT: A child table column cannot be generated unless its parent column is. -CREATE TABLE gtest_normal_child (a int, b int GENERATED ALWAYS AS (a * 2) STORED); -ALTER TABLE gtest_normal_child INHERIT gtest_normal; -- error -ERROR: column "b" in child table must not be a generated column -DROP TABLE gtest_normal, gtest_normal_child; --- test inheritance mismatches between parent and child -CREATE TABLE gtestx (x int, b int DEFAULT 10) INHERITS (gtest1); -- error -NOTICE: merging column "b" with inherited definition -ERROR: column "b" inherits from generated column but specifies default -CREATE TABLE gtestx (x int, b int GENERATED ALWAYS AS IDENTITY) INHERITS (gtest1); -- error -NOTICE: merging column "b" with inherited definition -ERROR: column "b" inherits from generated column but specifies identity -CREATE TABLE gtestx (x int, b int GENERATED ALWAYS AS (a * 22) STORED) INHERITS (gtest1); -- ok, overrides parent -NOTICE: merging column "b" with inherited definition -\d+ gtestx - Table "generated_stored_tests.gtestx" - Column | Type | Collation | Nullable | Default | Storage | Stats target | Description ---------+---------+-----------+----------+-------------------------------------+---------+--------------+------------- - a | integer | | not null | | plain | | - b | integer | | | generated always as (a * 22) stored | plain | | - x | integer | | | | plain | | -Inherits: gtest1 - -CREATE TABLE gtestxx_1 (a int NOT NULL, b int); -ALTER TABLE gtestxx_1 INHERIT gtest1; -- error -ERROR: column "b" in child table must be a generated column -CREATE TABLE gtestxx_3 (a int NOT NULL, b int GENERATED ALWAYS AS (a * 2) STORED); -ALTER TABLE gtestxx_3 INHERIT gtest1; -- ok -CREATE TABLE gtestxx_4 (b int GENERATED ALWAYS AS (a * 2) STORED, a int NOT NULL); -ALTER TABLE gtestxx_4 INHERIT gtest1; -- ok --- test multiple inheritance mismatches -CREATE TABLE gtesty (x int, b int DEFAULT 55); -CREATE TABLE gtest1_y () INHERITS (gtest0, gtesty); -- error -NOTICE: merging multiple inherited definitions of column "b" -ERROR: inherited column "b" has a generation conflict -DROP TABLE gtesty; -CREATE TABLE gtesty (x int, b int); -CREATE TABLE gtest1_y () INHERITS (gtest1, gtesty); -- error -NOTICE: merging multiple inherited definitions of column "b" -ERROR: inherited column "b" has a generation conflict -DROP TABLE gtesty; -CREATE TABLE gtesty (x int, b int GENERATED ALWAYS AS (x * 22) STORED); -CREATE TABLE gtest1_y () INHERITS (gtest1, gtesty); -- error -NOTICE: merging multiple inherited definitions of column "b" -ERROR: column "b" inherits conflicting generation expressions -HINT: To resolve the conflict, specify a generation expression explicitly. -CREATE TABLE gtest1_y (b int GENERATED ALWAYS AS (x + 1) STORED) INHERITS (gtest1, gtesty); -- ok -NOTICE: merging multiple inherited definitions of column "b" -NOTICE: moving and merging column "b" with inherited definition -DETAIL: User-specified column moved to the position of the inherited column. -\d gtest1_y - Table "generated_stored_tests.gtest1_y" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+------------------------------------ - a | integer | | not null | - b | integer | | | generated always as (x + 1) stored - x | integer | | | -Inherits: gtest1, - gtesty - --- test correct handling of GENERATED column that's only in child -CREATE TABLE gtestp (f1 int); -CREATE TABLE gtestc (f2 int GENERATED ALWAYS AS (f1+1) STORED) INHERITS(gtestp); -INSERT INTO gtestc values(42); -TABLE gtestc; - f1 | f2 -----+---- - 42 | 43 -(1 row) - -UPDATE gtestp SET f1 = f1 * 10; -TABLE gtestc; - f1 | f2 ------+----- - 420 | 421 -(1 row) - -DROP TABLE gtestp CASCADE; -NOTICE: drop cascades to table gtestc --- test stored update -CREATE TABLE gtest3 (a int, b int GENERATED ALWAYS AS (a * 3) STORED); -INSERT INTO gtest3 (a) VALUES (1), (2), (3), (NULL); -SELECT * FROM gtest3 ORDER BY a; - a | b ----+--- - 1 | 3 - 2 | 6 - 3 | 9 - | -(4 rows) - -UPDATE gtest3 SET a = 22 WHERE a = 2; -SELECT * FROM gtest3 ORDER BY a; - a | b -----+---- - 1 | 3 - 3 | 9 - 22 | 66 - | -(4 rows) - -CREATE TABLE gtest3a (a text, b text GENERATED ALWAYS AS (a || '+' || a) STORED); -INSERT INTO gtest3a (a) VALUES ('a'), ('b'), ('c'), (NULL); -SELECT * FROM gtest3a ORDER BY a; - a | b ----+----- - a | a+a - b | b+b - c | c+c - | -(4 rows) - -UPDATE gtest3a SET a = 'bb' WHERE a = 'b'; -SELECT * FROM gtest3a ORDER BY a; - a | b -----+------- - a | a+a - bb | bb+bb - c | c+c - | -(4 rows) - --- COPY -TRUNCATE gtest1; -INSERT INTO gtest1 (a) VALUES (1), (2); -COPY gtest1 TO stdout; -1 -2 -COPY gtest1 (a, b) TO stdout; -ERROR: column "b" is a generated column -DETAIL: Generated columns cannot be used in COPY. -COPY gtest1 FROM stdin; -COPY gtest1 (a, b) FROM stdin; -ERROR: column "b" is a generated column -DETAIL: Generated columns cannot be used in COPY. -SELECT * FROM gtest1 ORDER BY a; - a | b ----+--- - 1 | 2 - 2 | 4 - 3 | 6 - 4 | 8 -(4 rows) - -TRUNCATE gtest3; -INSERT INTO gtest3 (a) VALUES (1), (2); -COPY gtest3 TO stdout; -1 -2 -COPY gtest3 (a, b) TO stdout; -ERROR: column "b" is a generated column -DETAIL: Generated columns cannot be used in COPY. -COPY gtest3 FROM stdin; -COPY gtest3 (a, b) FROM stdin; -ERROR: column "b" is a generated column -DETAIL: Generated columns cannot be used in COPY. -SELECT * FROM gtest3 ORDER BY a; - a | b ----+---- - 1 | 3 - 2 | 6 - 3 | 9 - 4 | 12 -(4 rows) - --- null values -CREATE TABLE gtest2 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (NULL) STORED); -INSERT INTO gtest2 VALUES (1); -SELECT * FROM gtest2; - a | b ----+--- - 1 | -(1 row) - --- simple column reference for varlena types -CREATE TABLE gtest_varlena (a varchar, b varchar GENERATED ALWAYS AS (a) STORED); -INSERT INTO gtest_varlena (a) VALUES('01234567890123456789'); -INSERT INTO gtest_varlena (a) VALUES(NULL); -SELECT * FROM gtest_varlena ORDER BY a; - a | b -----------------------+---------------------- - 01234567890123456789 | 01234567890123456789 - | -(2 rows) - -DROP TABLE gtest_varlena; --- composite types -CREATE TYPE double_int as (a int, b int); -CREATE TABLE gtest4 ( - a int, - b double_int GENERATED ALWAYS AS ((a * 2, a * 3)) STORED -); -INSERT INTO gtest4 VALUES (1), (6); -SELECT * FROM gtest4; - a | b ----+--------- - 1 | (2,3) - 6 | (12,18) -(2 rows) - -DROP TABLE gtest4; -DROP TYPE double_int; --- using tableoid is allowed -CREATE TABLE gtest_tableoid ( - a int PRIMARY KEY, - b bool GENERATED ALWAYS AS (tableoid = 'gtest_tableoid'::regclass) STORED -); -INSERT INTO gtest_tableoid VALUES (1), (2); -ALTER TABLE gtest_tableoid ADD COLUMN - c regclass GENERATED ALWAYS AS (tableoid) STORED; -SELECT * FROM gtest_tableoid; - a | b | c ----+---+---------------- - 1 | t | gtest_tableoid - 2 | t | gtest_tableoid -(2 rows) - --- drop column behavior -CREATE TABLE gtest10 (a int PRIMARY KEY, b int, c int GENERATED ALWAYS AS (b * 2) STORED); -ALTER TABLE gtest10 DROP COLUMN b; -- fails -ERROR: cannot drop column b of table gtest10 because other objects depend on it -DETAIL: column c of table gtest10 depends on column b of table gtest10 -HINT: Use DROP ... CASCADE to drop the dependent objects too. -ALTER TABLE gtest10 DROP COLUMN b CASCADE; -- drops c too -NOTICE: drop cascades to column c of table gtest10 -\d gtest10 - Table "generated_stored_tests.gtest10" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - a | integer | | not null | -Indexes: - "gtest10_pkey" PRIMARY KEY, btree (a) - -CREATE TABLE gtest10a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED); -ALTER TABLE gtest10a DROP COLUMN b; -INSERT INTO gtest10a (a) VALUES (1); --- privileges -CREATE USER regress_user11; -CREATE TABLE gtest11s (a int PRIMARY KEY, b int, c int GENERATED ALWAYS AS (b * 2) STORED); -INSERT INTO gtest11s VALUES (1, 10), (2, 20); -GRANT SELECT (a, c) ON gtest11s TO regress_user11; -CREATE FUNCTION gf1(a int) RETURNS int AS $$ SELECT a * 3 $$ IMMUTABLE LANGUAGE SQL; -REVOKE ALL ON FUNCTION gf1(int) FROM PUBLIC; -CREATE TABLE gtest12s (a int PRIMARY KEY, b int, c int GENERATED ALWAYS AS (gf1(b)) STORED); -INSERT INTO gtest12s VALUES (1, 10), (2, 20); -GRANT SELECT (a, c) ON gtest12s TO regress_user11; -SET ROLE regress_user11; -SELECT a, b FROM gtest11s; -- not allowed -ERROR: permission denied for table gtest11s -SELECT a, c FROM gtest11s; -- allowed - a | c ----+---- - 1 | 20 - 2 | 40 -(2 rows) - -SELECT gf1(10); -- not allowed -ERROR: permission denied for function gf1 -SELECT a, c FROM gtest12s; -- allowed - a | c ----+---- - 1 | 30 - 2 | 60 -(2 rows) - -RESET ROLE; -DROP FUNCTION gf1(int); -- fail -ERROR: cannot drop function gf1(integer) because other objects depend on it -DETAIL: column c of table gtest12s depends on function gf1(integer) -HINT: Use DROP ... CASCADE to drop the dependent objects too. -DROP TABLE gtest11s, gtest12s; -DROP FUNCTION gf1(int); -DROP USER regress_user11; --- check constraints -CREATE TABLE gtest20 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED CHECK (b < 50)); -INSERT INTO gtest20 (a) VALUES (10); -- ok -INSERT INTO gtest20 (a) VALUES (30); -- violates constraint -ERROR: new row for relation "gtest20" violates check constraint "gtest20_b_check" -DETAIL: Failing row contains (30, 60). -ALTER TABLE gtest20 ALTER COLUMN b SET EXPRESSION AS (a * 100); -- violates constraint -ERROR: check constraint "gtest20_b_check" of relation "gtest20" is violated by some row -ALTER TABLE gtest20 ALTER COLUMN b SET EXPRESSION AS (a * 3); -- ok -CREATE TABLE gtest20a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED); -INSERT INTO gtest20a (a) VALUES (10); -INSERT INTO gtest20a (a) VALUES (30); -ALTER TABLE gtest20a ADD CHECK (b < 50); -- fails on existing row -ERROR: check constraint "gtest20a_b_check" of relation "gtest20a" is violated by some row -CREATE TABLE gtest20b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED); -INSERT INTO gtest20b (a) VALUES (10); -INSERT INTO gtest20b (a) VALUES (30); -ALTER TABLE gtest20b ADD CONSTRAINT chk CHECK (b < 50) NOT VALID; -ALTER TABLE gtest20b VALIDATE CONSTRAINT chk; -- fails on existing row -ERROR: check constraint "chk" of relation "gtest20b" is violated by some row --- not-null constraints -CREATE TABLE gtest21a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (nullif(a, 0)) STORED NOT NULL); -INSERT INTO gtest21a (a) VALUES (1); -- ok -INSERT INTO gtest21a (a) VALUES (0); -- violates constraint -ERROR: null value in column "b" of relation "gtest21a" violates not-null constraint -DETAIL: Failing row contains (0, null). -CREATE TABLE gtest21b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (nullif(a, 0)) STORED); -ALTER TABLE gtest21b ALTER COLUMN b SET NOT NULL; -INSERT INTO gtest21b (a) VALUES (1); -- ok -INSERT INTO gtest21b (a) VALUES (0); -- violates constraint -ERROR: null value in column "b" of relation "gtest21b" violates not-null constraint -DETAIL: Failing row contains (0, null). -ALTER TABLE gtest21b ALTER COLUMN b DROP NOT NULL; -INSERT INTO gtest21b (a) VALUES (0); -- ok now --- index constraints -CREATE TABLE gtest22a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a / 2) STORED UNIQUE); -INSERT INTO gtest22a VALUES (2); -INSERT INTO gtest22a VALUES (3); -ERROR: duplicate key value violates unique constraint "gtest22a_b_key" -DETAIL: Key (b)=(1) already exists. -INSERT INTO gtest22a VALUES (4); -CREATE TABLE gtest22b (a int, b int GENERATED ALWAYS AS (a / 2) STORED, PRIMARY KEY (a, b)); -INSERT INTO gtest22b VALUES (2); -INSERT INTO gtest22b VALUES (2); -ERROR: duplicate key value violates unique constraint "gtest22b_pkey" -DETAIL: Key (a, b)=(2, 1) already exists. --- indexes -CREATE TABLE gtest22c (a int, b int GENERATED ALWAYS AS (a * 2) STORED); -CREATE INDEX gtest22c_b_idx ON gtest22c (b); -CREATE INDEX gtest22c_expr_idx ON gtest22c ((b * 3)); -CREATE INDEX gtest22c_pred_idx ON gtest22c (a) WHERE b > 0; -\d gtest22c - Table "generated_stored_tests.gtest22c" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+------------------------------------ - a | integer | | | - b | integer | | | generated always as (a * 2) stored -Indexes: - "gtest22c_b_idx" btree (b) - "gtest22c_expr_idx" btree ((b * 3)) - "gtest22c_pred_idx" btree (a) WHERE b > 0 - -INSERT INTO gtest22c VALUES (1), (2), (3); -SET enable_seqscan TO off; -SET enable_bitmapscan TO off; -EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b = 4; - QUERY PLAN ---------------------------------------------- - Index Scan using gtest22c_b_idx on gtest22c - Index Cond: (b = 4) -(2 rows) - -SELECT * FROM gtest22c WHERE b = 4; - a | b ----+--- - 2 | 4 -(1 row) - -EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b * 3 = 6; - QUERY PLAN ------------------------------------------------- - Index Scan using gtest22c_expr_idx on gtest22c - Index Cond: ((b * 3) = 6) -(2 rows) - -SELECT * FROM gtest22c WHERE b * 3 = 6; - a | b ----+--- - 1 | 2 -(1 row) - -EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE a = 1 AND b > 0; - QUERY PLAN ------------------------------------------------- - Index Scan using gtest22c_pred_idx on gtest22c - Index Cond: (a = 1) -(2 rows) - -SELECT * FROM gtest22c WHERE a = 1 AND b > 0; - a | b ----+--- - 1 | 2 -(1 row) - -ALTER TABLE gtest22c ALTER COLUMN b SET EXPRESSION AS (a * 4); -ANALYZE gtest22c; -EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b = 8; - QUERY PLAN ---------------------------------------------- - Index Scan using gtest22c_b_idx on gtest22c - Index Cond: (b = 8) -(2 rows) - -SELECT * FROM gtest22c WHERE b = 8; - a | b ----+--- - 2 | 8 -(1 row) - -EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE b * 3 = 12; - QUERY PLAN ------------------------------------------------- - Index Scan using gtest22c_expr_idx on gtest22c - Index Cond: ((b * 3) = 12) -(2 rows) - -SELECT * FROM gtest22c WHERE b * 3 = 12; - a | b ----+--- - 1 | 4 -(1 row) - -EXPLAIN (COSTS OFF) SELECT * FROM gtest22c WHERE a = 1 AND b > 0; - QUERY PLAN ------------------------------------------------- - Index Scan using gtest22c_pred_idx on gtest22c - Index Cond: (a = 1) -(2 rows) - -SELECT * FROM gtest22c WHERE a = 1 AND b > 0; - a | b ----+--- - 1 | 4 -(1 row) - -RESET enable_seqscan; -RESET enable_bitmapscan; --- foreign keys -CREATE TABLE gtest23a (x int PRIMARY KEY, y int); -INSERT INTO gtest23a VALUES (1, 11), (2, 22), (3, 33); -CREATE TABLE gtest23x (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED REFERENCES gtest23a (x) ON UPDATE CASCADE); -- error -ERROR: invalid ON UPDATE action for foreign key constraint containing generated column -CREATE TABLE gtest23x (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED REFERENCES gtest23a (x) ON DELETE SET NULL); -- error -ERROR: invalid ON DELETE action for foreign key constraint containing generated column -CREATE TABLE gtest23b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED REFERENCES gtest23a (x)); -\d gtest23b - Table "generated_stored_tests.gtest23b" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+------------------------------------ - a | integer | | not null | - b | integer | | | generated always as (a * 2) stored -Indexes: - "gtest23b_pkey" PRIMARY KEY, btree (a) -Foreign-key constraints: - "gtest23b_b_fkey" FOREIGN KEY (b) REFERENCES gtest23a(x) - -INSERT INTO gtest23b VALUES (1); -- ok -INSERT INTO gtest23b VALUES (5); -- error -ERROR: insert or update on table "gtest23b" violates foreign key constraint "gtest23b_b_fkey" -DETAIL: Key (b)=(10) is not present in table "gtest23a". -ALTER TABLE gtest23b ALTER COLUMN b SET EXPRESSION AS (a * 5); -- error -ERROR: insert or update on table "gtest23b" violates foreign key constraint "gtest23b_b_fkey" -DETAIL: Key (b)=(5) is not present in table "gtest23a". -ALTER TABLE gtest23b ALTER COLUMN b SET EXPRESSION AS (a * 1); -- ok -DROP TABLE gtest23b; -DROP TABLE gtest23a; -CREATE TABLE gtest23p (x int, y int GENERATED ALWAYS AS (x * 2) STORED, PRIMARY KEY (y)); -INSERT INTO gtest23p VALUES (1), (2), (3); -CREATE TABLE gtest23q (a int PRIMARY KEY, b int REFERENCES gtest23p (y)); -INSERT INTO gtest23q VALUES (1, 2); -- ok -INSERT INTO gtest23q VALUES (2, 5); -- error -ERROR: insert or update on table "gtest23q" violates foreign key constraint "gtest23q_b_fkey" -DETAIL: Key (b)=(5) is not present in table "gtest23p". --- domains -CREATE DOMAIN gtestdomain1 AS int CHECK (VALUE < 10); -CREATE TABLE gtest24 (a int PRIMARY KEY, b gtestdomain1 GENERATED ALWAYS AS (a * 2) STORED); -INSERT INTO gtest24 (a) VALUES (4); -- ok -INSERT INTO gtest24 (a) VALUES (6); -- error -ERROR: value for domain gtestdomain1 violates check constraint "gtestdomain1_check" --- typed tables (currently not supported) -CREATE TYPE gtest_type AS (f1 integer, f2 text, f3 bigint); -CREATE TABLE gtest28 OF gtest_type (f1 WITH OPTIONS GENERATED ALWAYS AS (f2 *2) STORED); -ERROR: generated columns are not supported on typed tables -DROP TYPE gtest_type CASCADE; --- partitioning cases -CREATE TABLE gtest_parent (f1 date NOT NULL, f2 bigint, f3 bigint) PARTITION BY RANGE (f1); -CREATE TABLE gtest_child PARTITION OF gtest_parent ( - f3 WITH OPTIONS GENERATED ALWAYS AS (f2 * 2) STORED -) FOR VALUES FROM ('2016-07-01') TO ('2016-08-01'); -- error -ERROR: child column "f3" specifies generation expression -HINT: A child table column cannot be generated unless its parent column is. -CREATE TABLE gtest_child (f1 date NOT NULL, f2 bigint, f3 bigint GENERATED ALWAYS AS (f2 * 2) STORED); -ALTER TABLE gtest_parent ATTACH PARTITION gtest_child FOR VALUES FROM ('2016-07-01') TO ('2016-08-01'); -- error -ERROR: column "f3" in child table must not be a generated column -DROP TABLE gtest_parent, gtest_child; -CREATE TABLE gtest_parent (f1 date NOT NULL, f2 bigint, f3 bigint GENERATED ALWAYS AS (f2 * 2) STORED) PARTITION BY RANGE (f1); -CREATE TABLE gtest_child PARTITION OF gtest_parent - FOR VALUES FROM ('2016-07-01') TO ('2016-08-01'); -- inherits gen expr -CREATE TABLE gtest_child2 PARTITION OF gtest_parent ( - f3 WITH OPTIONS GENERATED ALWAYS AS (f2 * 22) STORED -- overrides gen expr -) FOR VALUES FROM ('2016-08-01') TO ('2016-09-01'); -CREATE TABLE gtest_child3 PARTITION OF gtest_parent ( - f3 DEFAULT 42 -- error -) FOR VALUES FROM ('2016-09-01') TO ('2016-10-01'); -ERROR: column "f3" inherits from generated column but specifies default -CREATE TABLE gtest_child3 PARTITION OF gtest_parent ( - f3 WITH OPTIONS GENERATED ALWAYS AS IDENTITY -- error -) FOR VALUES FROM ('2016-09-01') TO ('2016-10-01'); -ERROR: identity columns are not supported on partitions -CREATE TABLE gtest_child3 (f1 date NOT NULL, f2 bigint, f3 bigint); -ALTER TABLE gtest_parent ATTACH PARTITION gtest_child3 FOR VALUES FROM ('2016-09-01') TO ('2016-10-01'); -- error -ERROR: column "f3" in child table must be a generated column -DROP TABLE gtest_child3; -CREATE TABLE gtest_child3 (f1 date NOT NULL, f2 bigint, f3 bigint DEFAULT 42); -ALTER TABLE gtest_parent ATTACH PARTITION gtest_child3 FOR VALUES FROM ('2016-09-01') TO ('2016-10-01'); -- error -ERROR: column "f3" in child table must be a generated column -DROP TABLE gtest_child3; -CREATE TABLE gtest_child3 (f1 date NOT NULL, f2 bigint, f3 bigint GENERATED ALWAYS AS IDENTITY); -ALTER TABLE gtest_parent ATTACH PARTITION gtest_child3 FOR VALUES FROM ('2016-09-01') TO ('2016-10-01'); -- error -ERROR: table "gtest_child3" being attached contains an identity column "f3" -DETAIL: The new partition may not contain an identity column. -DROP TABLE gtest_child3; -CREATE TABLE gtest_child3 (f1 date NOT NULL, f2 bigint, f3 bigint GENERATED ALWAYS AS (f2 * 33) STORED); -ALTER TABLE gtest_parent ATTACH PARTITION gtest_child3 FOR VALUES FROM ('2016-09-01') TO ('2016-10-01'); -\d gtest_child - Table "generated_stored_tests.gtest_child" - Column | Type | Collation | Nullable | Default ---------+--------+-----------+----------+------------------------------------- - f1 | date | | not null | - f2 | bigint | | | - f3 | bigint | | | generated always as (f2 * 2) stored -Partition of: gtest_parent FOR VALUES FROM ('07-01-2016') TO ('08-01-2016') - -\d gtest_child2 - Table "generated_stored_tests.gtest_child2" - Column | Type | Collation | Nullable | Default ---------+--------+-----------+----------+-------------------------------------- - f1 | date | | not null | - f2 | bigint | | | - f3 | bigint | | | generated always as (f2 * 22) stored -Partition of: gtest_parent FOR VALUES FROM ('08-01-2016') TO ('09-01-2016') - -\d gtest_child3 - Table "generated_stored_tests.gtest_child3" - Column | Type | Collation | Nullable | Default ---------+--------+-----------+----------+-------------------------------------- - f1 | date | | not null | - f2 | bigint | | | - f3 | bigint | | | generated always as (f2 * 33) stored -Partition of: gtest_parent FOR VALUES FROM ('09-01-2016') TO ('10-01-2016') - -INSERT INTO gtest_parent (f1, f2) VALUES ('2016-07-15', 1); -INSERT INTO gtest_parent (f1, f2) VALUES ('2016-07-15', 2); -INSERT INTO gtest_parent (f1, f2) VALUES ('2016-08-15', 3); -SELECT tableoid::regclass, * FROM gtest_parent ORDER BY 1, 2, 3; - tableoid | f1 | f2 | f3 ---------------+------------+----+---- - gtest_child | 07-15-2016 | 1 | 2 - gtest_child | 07-15-2016 | 2 | 4 - gtest_child2 | 08-15-2016 | 3 | 66 -(3 rows) - -UPDATE gtest_parent SET f1 = f1 + 60 WHERE f2 = 1; -SELECT tableoid::regclass, * FROM gtest_parent ORDER BY 1, 2, 3; - tableoid | f1 | f2 | f3 ---------------+------------+----+---- - gtest_child | 07-15-2016 | 2 | 4 - gtest_child2 | 08-15-2016 | 3 | 66 - gtest_child3 | 09-13-2016 | 1 | 33 -(3 rows) - --- alter only parent's and one child's generation expression -ALTER TABLE ONLY gtest_parent ALTER COLUMN f3 SET EXPRESSION AS (f2 * 4); -ALTER TABLE gtest_child ALTER COLUMN f3 SET EXPRESSION AS (f2 * 10); -\d gtest_parent - Partitioned table "generated_stored_tests.gtest_parent" - Column | Type | Collation | Nullable | Default ---------+--------+-----------+----------+------------------------------------- - f1 | date | | not null | - f2 | bigint | | | - f3 | bigint | | | generated always as (f2 * 4) stored -Partition key: RANGE (f1) -Number of partitions: 3 (Use \d+ to list them.) - -\d gtest_child - Table "generated_stored_tests.gtest_child" - Column | Type | Collation | Nullable | Default ---------+--------+-----------+----------+-------------------------------------- - f1 | date | | not null | - f2 | bigint | | | - f3 | bigint | | | generated always as (f2 * 10) stored -Partition of: gtest_parent FOR VALUES FROM ('07-01-2016') TO ('08-01-2016') - -\d gtest_child2 - Table "generated_stored_tests.gtest_child2" - Column | Type | Collation | Nullable | Default ---------+--------+-----------+----------+-------------------------------------- - f1 | date | | not null | - f2 | bigint | | | - f3 | bigint | | | generated always as (f2 * 22) stored -Partition of: gtest_parent FOR VALUES FROM ('08-01-2016') TO ('09-01-2016') - -\d gtest_child3 - Table "generated_stored_tests.gtest_child3" - Column | Type | Collation | Nullable | Default ---------+--------+-----------+----------+-------------------------------------- - f1 | date | | not null | - f2 | bigint | | | - f3 | bigint | | | generated always as (f2 * 33) stored -Partition of: gtest_parent FOR VALUES FROM ('09-01-2016') TO ('10-01-2016') - -SELECT tableoid::regclass, * FROM gtest_parent ORDER BY 1, 2, 3; - tableoid | f1 | f2 | f3 ---------------+------------+----+---- - gtest_child | 07-15-2016 | 2 | 20 - gtest_child2 | 08-15-2016 | 3 | 66 - gtest_child3 | 09-13-2016 | 1 | 33 -(3 rows) - --- alter generation expression of parent and all its children altogether -ALTER TABLE gtest_parent ALTER COLUMN f3 SET EXPRESSION AS (f2 * 2); -\d gtest_parent - Partitioned table "generated_stored_tests.gtest_parent" - Column | Type | Collation | Nullable | Default ---------+--------+-----------+----------+------------------------------------- - f1 | date | | not null | - f2 | bigint | | | - f3 | bigint | | | generated always as (f2 * 2) stored -Partition key: RANGE (f1) -Number of partitions: 3 (Use \d+ to list them.) - -\d gtest_child - Table "generated_stored_tests.gtest_child" - Column | Type | Collation | Nullable | Default ---------+--------+-----------+----------+------------------------------------- - f1 | date | | not null | - f2 | bigint | | | - f3 | bigint | | | generated always as (f2 * 2) stored -Partition of: gtest_parent FOR VALUES FROM ('07-01-2016') TO ('08-01-2016') - -\d gtest_child2 - Table "generated_stored_tests.gtest_child2" - Column | Type | Collation | Nullable | Default ---------+--------+-----------+----------+------------------------------------- - f1 | date | | not null | - f2 | bigint | | | - f3 | bigint | | | generated always as (f2 * 2) stored -Partition of: gtest_parent FOR VALUES FROM ('08-01-2016') TO ('09-01-2016') - -\d gtest_child3 - Table "generated_stored_tests.gtest_child3" - Column | Type | Collation | Nullable | Default ---------+--------+-----------+----------+------------------------------------- - f1 | date | | not null | - f2 | bigint | | | - f3 | bigint | | | generated always as (f2 * 2) stored -Partition of: gtest_parent FOR VALUES FROM ('09-01-2016') TO ('10-01-2016') - -SELECT tableoid::regclass, * FROM gtest_parent ORDER BY 1, 2, 3; - tableoid | f1 | f2 | f3 ---------------+------------+----+---- - gtest_child | 07-15-2016 | 2 | 4 - gtest_child2 | 08-15-2016 | 3 | 6 - gtest_child3 | 09-13-2016 | 1 | 2 -(3 rows) - --- we leave these tables around for purposes of testing dump/reload/upgrade --- generated columns in partition key (not allowed) -CREATE TABLE gtest_part_key (f1 date NOT NULL, f2 bigint, f3 bigint GENERATED ALWAYS AS (f2 * 2) STORED) PARTITION BY RANGE (f3); -ERROR: cannot use generated column in partition key -LINE 1: ...ENERATED ALWAYS AS (f2 * 2) STORED) PARTITION BY RANGE (f3); - ^ -DETAIL: Column "f3" is a generated column. -CREATE TABLE gtest_part_key (f1 date NOT NULL, f2 bigint, f3 bigint GENERATED ALWAYS AS (f2 * 2) STORED) PARTITION BY RANGE ((f3 * 3)); -ERROR: cannot use generated column in partition key -LINE 1: ...ED ALWAYS AS (f2 * 2) STORED) PARTITION BY RANGE ((f3 * 3)); - ^ -DETAIL: Column "f3" is a generated column. --- ALTER TABLE ... ADD COLUMN -CREATE TABLE gtest25 (a int PRIMARY KEY); -INSERT INTO gtest25 VALUES (3), (4); -ALTER TABLE gtest25 ADD COLUMN b int GENERATED ALWAYS AS (a * 2) STORED, ALTER COLUMN b SET EXPRESSION AS (a * 3); -SELECT * FROM gtest25 ORDER BY a; - a | b ----+---- - 3 | 9 - 4 | 12 -(2 rows) - -ALTER TABLE gtest25 ADD COLUMN x int GENERATED ALWAYS AS (b * 4) STORED; -- error -ERROR: cannot use generated column "b" in column generation expression -DETAIL: A generated column cannot reference another generated column. -ALTER TABLE gtest25 ADD COLUMN x int GENERATED ALWAYS AS (z * 4) STORED; -- error -ERROR: column "z" does not exist -ALTER TABLE gtest25 ADD COLUMN c int DEFAULT 42, - ADD COLUMN x int GENERATED ALWAYS AS (c * 4) STORED; -ALTER TABLE gtest25 ADD COLUMN d int DEFAULT 101; -ALTER TABLE gtest25 ALTER COLUMN d SET DATA TYPE float8, - ADD COLUMN y float8 GENERATED ALWAYS AS (d * 4) STORED; -SELECT * FROM gtest25 ORDER BY a; - a | b | c | x | d | y ----+----+----+-----+-----+----- - 3 | 9 | 42 | 168 | 101 | 404 - 4 | 12 | 42 | 168 | 101 | 404 -(2 rows) - -\d gtest25 - Table "generated_stored_tests.gtest25" - Column | Type | Collation | Nullable | Default ---------+------------------+-----------+----------+------------------------------------------------------ - a | integer | | not null | - b | integer | | | generated always as (a * 3) stored - c | integer | | | 42 - x | integer | | | generated always as (c * 4) stored - d | double precision | | | 101 - y | double precision | | | generated always as (d * 4::double precision) stored -Indexes: - "gtest25_pkey" PRIMARY KEY, btree (a) - --- ALTER TABLE ... ALTER COLUMN -CREATE TABLE gtest27 ( - a int, - b int, - x int GENERATED ALWAYS AS ((a + b) * 2) STORED -); -INSERT INTO gtest27 (a, b) VALUES (3, 7), (4, 11); -ALTER TABLE gtest27 ALTER COLUMN a TYPE text; -- error -ERROR: cannot alter type of a column used by a generated column -DETAIL: Column "a" is used by generated column "x". -ALTER TABLE gtest27 ALTER COLUMN x TYPE numeric; -\d gtest27 - Table "generated_stored_tests.gtest27" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+-------------------------------------------- - a | integer | | | - b | integer | | | - x | numeric | | | generated always as (((a + b) * 2)) stored - -SELECT * FROM gtest27; - a | b | x ----+----+---- - 3 | 7 | 20 - 4 | 11 | 30 -(2 rows) - -ALTER TABLE gtest27 ALTER COLUMN x TYPE boolean USING x <> 0; -- error -ERROR: cannot specify USING when altering type of generated column -DETAIL: Column "x" is a generated column. -ALTER TABLE gtest27 ALTER COLUMN x DROP DEFAULT; -- error -ERROR: column "x" of relation "gtest27" is a generated column -HINT: Use ALTER TABLE ... ALTER COLUMN ... DROP EXPRESSION instead. --- It's possible to alter the column types this way: -ALTER TABLE gtest27 - DROP COLUMN x, - ALTER COLUMN a TYPE bigint, - ALTER COLUMN b TYPE bigint, - ADD COLUMN x bigint GENERATED ALWAYS AS ((a + b) * 2) STORED; -\d gtest27 - Table "generated_stored_tests.gtest27" - Column | Type | Collation | Nullable | Default ---------+--------+-----------+----------+------------------------------------------ - a | bigint | | | - b | bigint | | | - x | bigint | | | generated always as ((a + b) * 2) stored - --- Ideally you could just do this, but not today (and should x change type?): -ALTER TABLE gtest27 - ALTER COLUMN a TYPE float8, - ALTER COLUMN b TYPE float8; -- error -ERROR: cannot alter type of a column used by a generated column -DETAIL: Column "a" is used by generated column "x". -\d gtest27 - Table "generated_stored_tests.gtest27" - Column | Type | Collation | Nullable | Default ---------+--------+-----------+----------+------------------------------------------ - a | bigint | | | - b | bigint | | | - x | bigint | | | generated always as ((a + b) * 2) stored - -SELECT * FROM gtest27; - a | b | x ----+----+---- - 3 | 7 | 20 - 4 | 11 | 30 -(2 rows) - --- ALTER TABLE ... ALTER COLUMN ... DROP EXPRESSION -CREATE TABLE gtest29 ( - a int, - b int GENERATED ALWAYS AS (a * 2) STORED -); -INSERT INTO gtest29 (a) VALUES (3), (4); -SELECT * FROM gtest29; - a | b ----+--- - 3 | 6 - 4 | 8 -(2 rows) - -\d gtest29 - Table "generated_stored_tests.gtest29" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+------------------------------------ - a | integer | | | - b | integer | | | generated always as (a * 2) stored - -ALTER TABLE gtest29 ALTER COLUMN a SET EXPRESSION AS (a * 3); -- error -ERROR: column "a" of relation "gtest29" is not a generated column -ALTER TABLE gtest29 ALTER COLUMN a DROP EXPRESSION; -- error -ERROR: column "a" of relation "gtest29" is not a stored generated column -ALTER TABLE gtest29 ALTER COLUMN a DROP EXPRESSION IF EXISTS; -- notice -NOTICE: column "a" of relation "gtest29" is not a stored generated column, skipping --- Change the expression -ALTER TABLE gtest29 ALTER COLUMN b SET EXPRESSION AS (a * 3); -SELECT * FROM gtest29; - a | b ----+---- - 3 | 9 - 4 | 12 -(2 rows) - -\d gtest29 - Table "generated_stored_tests.gtest29" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+------------------------------------ - a | integer | | | - b | integer | | | generated always as (a * 3) stored - -ALTER TABLE gtest29 ALTER COLUMN b DROP EXPRESSION; -INSERT INTO gtest29 (a) VALUES (5); -INSERT INTO gtest29 (a, b) VALUES (6, 66); -SELECT * FROM gtest29; - a | b ----+---- - 3 | 9 - 4 | 12 - 5 | - 6 | 66 -(4 rows) - -\d gtest29 - Table "generated_stored_tests.gtest29" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - a | integer | | | - b | integer | | | - --- check that dependencies between columns have also been removed -ALTER TABLE gtest29 DROP COLUMN a; -- should not drop b -\d gtest29 - Table "generated_stored_tests.gtest29" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - b | integer | | | - --- with inheritance -CREATE TABLE gtest30 ( - a int, - b int GENERATED ALWAYS AS (a * 2) STORED -); -CREATE TABLE gtest30_1 () INHERITS (gtest30); -ALTER TABLE gtest30 ALTER COLUMN b DROP EXPRESSION; -\d gtest30 - Table "generated_stored_tests.gtest30" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - a | integer | | | - b | integer | | | -Number of child tables: 1 (Use \d+ to list them.) - -\d gtest30_1 - Table "generated_stored_tests.gtest30_1" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- - a | integer | | | - b | integer | | | -Inherits: gtest30 - -DROP TABLE gtest30 CASCADE; -NOTICE: drop cascades to table gtest30_1 -CREATE TABLE gtest30 ( - a int, - b int GENERATED ALWAYS AS (a * 2) STORED -); -CREATE TABLE gtest30_1 () INHERITS (gtest30); -ALTER TABLE ONLY gtest30 ALTER COLUMN b DROP EXPRESSION; -- error -ERROR: ALTER TABLE / DROP EXPRESSION must be applied to child tables too -\d gtest30 - Table "generated_stored_tests.gtest30" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+------------------------------------ - a | integer | | | - b | integer | | | generated always as (a * 2) stored -Number of child tables: 1 (Use \d+ to list them.) - -\d gtest30_1 - Table "generated_stored_tests.gtest30_1" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+------------------------------------ - a | integer | | | - b | integer | | | generated always as (a * 2) stored -Inherits: gtest30 - -ALTER TABLE gtest30_1 ALTER COLUMN b DROP EXPRESSION; -- error -ERROR: cannot drop generation expression from inherited column --- triggers -CREATE TABLE gtest26 ( - a int PRIMARY KEY, - b int GENERATED ALWAYS AS (a * 2) STORED -); -CREATE FUNCTION gtest_trigger_func() RETURNS trigger - LANGUAGE plpgsql -AS $$ -BEGIN - IF tg_op IN ('DELETE', 'UPDATE') THEN - RAISE INFO '%: %: old = %', TG_NAME, TG_WHEN, OLD; - END IF; - IF tg_op IN ('INSERT', 'UPDATE') THEN - RAISE INFO '%: %: new = %', TG_NAME, TG_WHEN, NEW; - END IF; - IF tg_op = 'DELETE' THEN - RETURN OLD; - ELSE - RETURN NEW; - END IF; -END -$$; -CREATE TRIGGER gtest1 BEFORE DELETE OR UPDATE ON gtest26 - FOR EACH ROW - WHEN (OLD.b < 0) -- ok - EXECUTE PROCEDURE gtest_trigger_func(); -CREATE TRIGGER gtest2a BEFORE INSERT OR UPDATE ON gtest26 - FOR EACH ROW - WHEN (NEW.b < 0) -- error - EXECUTE PROCEDURE gtest_trigger_func(); -ERROR: BEFORE trigger's WHEN condition cannot reference NEW generated columns -LINE 3: WHEN (NEW.b < 0) -- error - ^ -DETAIL: Column "b" is a generated column. -CREATE TRIGGER gtest2b BEFORE INSERT OR UPDATE ON gtest26 - FOR EACH ROW - WHEN (NEW.* IS NOT NULL) -- error - EXECUTE PROCEDURE gtest_trigger_func(); -ERROR: BEFORE trigger's WHEN condition cannot reference NEW generated columns -LINE 3: WHEN (NEW.* IS NOT NULL) -- error - ^ -DETAIL: A whole-row reference is used and the table contains generated columns. -CREATE TRIGGER gtest2 BEFORE INSERT ON gtest26 - FOR EACH ROW - WHEN (NEW.a < 0) - EXECUTE PROCEDURE gtest_trigger_func(); -CREATE TRIGGER gtest3 AFTER DELETE OR UPDATE ON gtest26 - FOR EACH ROW - WHEN (OLD.b < 0) -- ok - EXECUTE PROCEDURE gtest_trigger_func(); -CREATE TRIGGER gtest4 AFTER INSERT OR UPDATE ON gtest26 - FOR EACH ROW - WHEN (NEW.b < 0) -- ok - EXECUTE PROCEDURE gtest_trigger_func(); -INSERT INTO gtest26 (a) VALUES (-2), (0), (3); -INFO: gtest2: BEFORE: new = (-2,) -INFO: gtest4: AFTER: new = (-2,-4) -SELECT * FROM gtest26 ORDER BY a; - a | b -----+---- - -2 | -4 - 0 | 0 - 3 | 6 -(3 rows) - -UPDATE gtest26 SET a = a * -2; -INFO: gtest1: BEFORE: old = (-2,-4) -INFO: gtest1: BEFORE: new = (4,) -INFO: gtest3: AFTER: old = (-2,-4) -INFO: gtest3: AFTER: new = (4,8) -INFO: gtest4: AFTER: old = (3,6) -INFO: gtest4: AFTER: new = (-6,-12) -SELECT * FROM gtest26 ORDER BY a; - a | b -----+----- - -6 | -12 - 0 | 0 - 4 | 8 -(3 rows) - -DELETE FROM gtest26 WHERE a = -6; -INFO: gtest1: BEFORE: old = (-6,-12) -INFO: gtest3: AFTER: old = (-6,-12) -SELECT * FROM gtest26 ORDER BY a; - a | b ----+--- - 0 | 0 - 4 | 8 -(2 rows) - -DROP TRIGGER gtest1 ON gtest26; -DROP TRIGGER gtest2 ON gtest26; -DROP TRIGGER gtest3 ON gtest26; --- Check that an UPDATE of "a" fires the trigger for UPDATE OF b, per --- SQL standard. -CREATE FUNCTION gtest_trigger_func3() RETURNS trigger - LANGUAGE plpgsql -AS $$ -BEGIN - RAISE NOTICE 'OK'; - RETURN NEW; -END -$$; -CREATE TRIGGER gtest11 BEFORE UPDATE OF b ON gtest26 - FOR EACH ROW - EXECUTE PROCEDURE gtest_trigger_func3(); -UPDATE gtest26 SET a = 1 WHERE a = 0; -NOTICE: OK -DROP TRIGGER gtest11 ON gtest26; -TRUNCATE gtest26; --- check that modifications of stored generated columns in triggers do --- not get propagated -CREATE FUNCTION gtest_trigger_func4() RETURNS trigger - LANGUAGE plpgsql -AS $$ -BEGIN - NEW.a = 10; - NEW.b = 300; - RETURN NEW; -END; -$$; -CREATE TRIGGER gtest12_01 BEFORE UPDATE ON gtest26 - FOR EACH ROW - EXECUTE PROCEDURE gtest_trigger_func(); -CREATE TRIGGER gtest12_02 BEFORE UPDATE ON gtest26 - FOR EACH ROW - EXECUTE PROCEDURE gtest_trigger_func4(); -CREATE TRIGGER gtest12_03 BEFORE UPDATE ON gtest26 - FOR EACH ROW - EXECUTE PROCEDURE gtest_trigger_func(); -INSERT INTO gtest26 (a) VALUES (1); -UPDATE gtest26 SET a = 11 WHERE a = 1; -INFO: gtest12_01: BEFORE: old = (1,2) -INFO: gtest12_01: BEFORE: new = (11,) -INFO: gtest12_03: BEFORE: old = (1,2) -INFO: gtest12_03: BEFORE: new = (10,) -SELECT * FROM gtest26 ORDER BY a; - a | b -----+---- - 10 | 20 -(1 row) - --- LIKE INCLUDING GENERATED and dropped column handling -CREATE TABLE gtest28a ( - a int, - b int, - c int, - x int GENERATED ALWAYS AS (b * 2) STORED -); -ALTER TABLE gtest28a DROP COLUMN a; -CREATE TABLE gtest28b (LIKE gtest28a INCLUDING GENERATED); -\d gtest28* - Table "generated_stored_tests.gtest28a" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+------------------------------------ - b | integer | | | - c | integer | | | - x | integer | | | generated always as (b * 2) stored - - Table "generated_stored_tests.gtest28b" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+------------------------------------ - b | integer | | | - c | integer | | | - x | integer | | | generated always as (b * 2) stored - +psql: error: connection to server on socket "/tmp/pg_regress-03Az7d/.s.PGSQL.40051" failed: FATAL: the database system is not yet accepting connections +DETAIL: Consistent recovery state has not been yet reached. diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/join_hash.out /tmp/cirrus-ci-build/build/testrun/regress/regress/results/join_hash.out --- /tmp/cirrus-ci-build/src/test/regress/expected/join_hash.out 2024-09-20 21:41:45.706024522 +0000 +++ /tmp/cirrus-ci-build/build/testrun/regress/regress/results/join_hash.out 2024-09-20 21:46:06.641623503 +0000 @@ -1,1165 +1,2 @@ --- --- exercises for the hash join code --- -begin; -set local min_parallel_table_scan_size = 0; -set local parallel_setup_cost = 0; -set local enable_hashjoin = on; --- Extract bucket and batch counts from an explain analyze plan. In --- general we can't make assertions about how many batches (or --- buckets) will be required because it can vary, but we can in some --- special cases and we can check for growth. -create or replace function find_hash(node json) -returns json language plpgsql -as -$$ -declare - x json; - child json; -begin - if node->>'Node Type' = 'Hash' then - return node; - else - for child in select json_array_elements(node->'Plans') - loop - x := find_hash(child); - if x is not null then - return x; - end if; - end loop; - return null; - end if; -end; -$$; -create or replace function hash_join_batches(query text) -returns table (original int, final int) language plpgsql -as -$$ -declare - whole_plan json; - hash_node json; -begin - for whole_plan in - execute 'explain (analyze, format ''json'') ' || query - loop - hash_node := find_hash(json_extract_path(whole_plan, '0', 'Plan')); - original := hash_node->>'Original Hash Batches'; - final := hash_node->>'Hash Batches'; - return next; - end loop; -end; -$$; --- Make a simple relation with well distributed keys and correctly --- estimated size. -create table simple as - select generate_series(1, 20000) AS id, 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'; -alter table simple set (parallel_workers = 2); -analyze simple; --- Make a relation whose size we will under-estimate. We want stats --- to say 1000 rows, but actually there are 20,000 rows. -create table bigger_than_it_looks as - select generate_series(1, 20000) as id, 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'; -alter table bigger_than_it_looks set (autovacuum_enabled = 'false'); -alter table bigger_than_it_looks set (parallel_workers = 2); -analyze bigger_than_it_looks; -update pg_class set reltuples = 1000 where relname = 'bigger_than_it_looks'; --- Make a relation whose size we underestimate and that also has a --- kind of skew that breaks our batching scheme. We want stats to say --- 2 rows, but actually there are 20,000 rows with the same key. -create table extremely_skewed (id int, t text); -alter table extremely_skewed set (autovacuum_enabled = 'false'); -alter table extremely_skewed set (parallel_workers = 2); -analyze extremely_skewed; -insert into extremely_skewed - select 42 as id, 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' - from generate_series(1, 20000); -update pg_class - set reltuples = 2, relpages = pg_relation_size('extremely_skewed') / 8192 - where relname = 'extremely_skewed'; --- Make a relation with a couple of enormous tuples. -create table wide as select generate_series(1, 2) as id, rpad('', 320000, 'x') as t; -alter table wide set (parallel_workers = 2); --- The "optimal" case: the hash table fits in memory; we plan for 1 --- batch, we stick to that number, and peak memory usage stays within --- our work_mem budget --- non-parallel -savepoint settings; -set local max_parallel_workers_per_gather = 0; -set local work_mem = '4MB'; -set local hash_mem_multiplier = 1.0; -explain (costs off) - select count(*) from simple r join simple s using (id); - QUERY PLAN ----------------------------------------- - Aggregate - -> Hash Join - Hash Cond: (r.id = s.id) - -> Seq Scan on simple r - -> Hash - -> Seq Scan on simple s -(6 rows) - -select count(*) from simple r join simple s using (id); - count -------- - 20000 -(1 row) - -select original > 1 as initially_multibatch, final > original as increased_batches - from hash_join_batches( -$$ - select count(*) from simple r join simple s using (id); -$$); - initially_multibatch | increased_batches -----------------------+------------------- - f | f -(1 row) - -rollback to settings; --- parallel with parallel-oblivious hash join -savepoint settings; -set local max_parallel_workers_per_gather = 2; -set local work_mem = '4MB'; -set local hash_mem_multiplier = 1.0; -set local enable_parallel_hash = off; -explain (costs off) - select count(*) from simple r join simple s using (id); - QUERY PLAN -------------------------------------------------------- - Finalize Aggregate - -> Gather - Workers Planned: 2 - -> Partial Aggregate - -> Hash Join - Hash Cond: (r.id = s.id) - -> Parallel Seq Scan on simple r - -> Hash - -> Seq Scan on simple s -(9 rows) - -select count(*) from simple r join simple s using (id); - count -------- - 20000 -(1 row) - -select original > 1 as initially_multibatch, final > original as increased_batches - from hash_join_batches( -$$ - select count(*) from simple r join simple s using (id); -$$); - initially_multibatch | increased_batches -----------------------+------------------- - f | f -(1 row) - -rollback to settings; --- parallel with parallel-aware hash join -savepoint settings; -set local max_parallel_workers_per_gather = 2; -set local work_mem = '4MB'; -set local hash_mem_multiplier = 1.0; -set local enable_parallel_hash = on; -explain (costs off) - select count(*) from simple r join simple s using (id); - QUERY PLAN -------------------------------------------------------------- - Finalize Aggregate - -> Gather - Workers Planned: 2 - -> Partial Aggregate - -> Parallel Hash Join - Hash Cond: (r.id = s.id) - -> Parallel Seq Scan on simple r - -> Parallel Hash - -> Parallel Seq Scan on simple s -(9 rows) - -select count(*) from simple r join simple s using (id); - count -------- - 20000 -(1 row) - -select original > 1 as initially_multibatch, final > original as increased_batches - from hash_join_batches( -$$ - select count(*) from simple r join simple s using (id); -$$); - initially_multibatch | increased_batches -----------------------+------------------- - f | f -(1 row) - -rollback to settings; --- The "good" case: batches required, but we plan the right number; we --- plan for some number of batches, and we stick to that number, and --- peak memory usage says within our work_mem budget --- non-parallel -savepoint settings; -set local max_parallel_workers_per_gather = 0; -set local work_mem = '128kB'; -set local hash_mem_multiplier = 1.0; -explain (costs off) - select count(*) from simple r join simple s using (id); - QUERY PLAN ----------------------------------------- - Aggregate - -> Hash Join - Hash Cond: (r.id = s.id) - -> Seq Scan on simple r - -> Hash - -> Seq Scan on simple s -(6 rows) - -select count(*) from simple r join simple s using (id); - count -------- - 20000 -(1 row) - -select original > 1 as initially_multibatch, final > original as increased_batches - from hash_join_batches( -$$ - select count(*) from simple r join simple s using (id); -$$); - initially_multibatch | increased_batches -----------------------+------------------- - t | f -(1 row) - -rollback to settings; --- parallel with parallel-oblivious hash join -savepoint settings; -set local max_parallel_workers_per_gather = 2; -set local work_mem = '128kB'; -set local hash_mem_multiplier = 1.0; -set local enable_parallel_hash = off; -explain (costs off) - select count(*) from simple r join simple s using (id); - QUERY PLAN -------------------------------------------------------- - Finalize Aggregate - -> Gather - Workers Planned: 2 - -> Partial Aggregate - -> Hash Join - Hash Cond: (r.id = s.id) - -> Parallel Seq Scan on simple r - -> Hash - -> Seq Scan on simple s -(9 rows) - -select count(*) from simple r join simple s using (id); - count -------- - 20000 -(1 row) - -select original > 1 as initially_multibatch, final > original as increased_batches - from hash_join_batches( -$$ - select count(*) from simple r join simple s using (id); -$$); - initially_multibatch | increased_batches -----------------------+------------------- - t | f -(1 row) - -rollback to settings; --- parallel with parallel-aware hash join -savepoint settings; -set local max_parallel_workers_per_gather = 2; -set local work_mem = '192kB'; -set local hash_mem_multiplier = 1.0; -set local enable_parallel_hash = on; -explain (costs off) - select count(*) from simple r join simple s using (id); - QUERY PLAN -------------------------------------------------------------- - Finalize Aggregate - -> Gather - Workers Planned: 2 - -> Partial Aggregate - -> Parallel Hash Join - Hash Cond: (r.id = s.id) - -> Parallel Seq Scan on simple r - -> Parallel Hash - -> Parallel Seq Scan on simple s -(9 rows) - -select count(*) from simple r join simple s using (id); - count -------- - 20000 -(1 row) - -select original > 1 as initially_multibatch, final > original as increased_batches - from hash_join_batches( -$$ - select count(*) from simple r join simple s using (id); -$$); - initially_multibatch | increased_batches -----------------------+------------------- - t | f -(1 row) - --- parallel full multi-batch hash join -select count(*) from simple r full outer join simple s using (id); - count -------- - 20000 -(1 row) - -rollback to settings; --- The "bad" case: during execution we need to increase number of --- batches; in this case we plan for 1 batch, and increase at least a --- couple of times, and peak memory usage stays within our work_mem --- budget --- non-parallel -savepoint settings; -set local max_parallel_workers_per_gather = 0; -set local work_mem = '128kB'; -set local hash_mem_multiplier = 1.0; -explain (costs off) - select count(*) FROM simple r JOIN bigger_than_it_looks s USING (id); - QUERY PLAN ------------------------------------------------------- - Aggregate - -> Hash Join - Hash Cond: (r.id = s.id) - -> Seq Scan on simple r - -> Hash - -> Seq Scan on bigger_than_it_looks s -(6 rows) - -select count(*) FROM simple r JOIN bigger_than_it_looks s USING (id); - count -------- - 20000 -(1 row) - -select original > 1 as initially_multibatch, final > original as increased_batches - from hash_join_batches( -$$ - select count(*) FROM simple r JOIN bigger_than_it_looks s USING (id); -$$); - initially_multibatch | increased_batches -----------------------+------------------- - f | t -(1 row) - -rollback to settings; --- parallel with parallel-oblivious hash join -savepoint settings; -set local max_parallel_workers_per_gather = 2; -set local work_mem = '128kB'; -set local hash_mem_multiplier = 1.0; -set local enable_parallel_hash = off; -explain (costs off) - select count(*) from simple r join bigger_than_it_looks s using (id); - QUERY PLAN ------------------------------------------------------------------- - Finalize Aggregate - -> Gather - Workers Planned: 2 - -> Partial Aggregate - -> Hash Join - Hash Cond: (r.id = s.id) - -> Parallel Seq Scan on simple r - -> Hash - -> Seq Scan on bigger_than_it_looks s -(9 rows) - -select count(*) from simple r join bigger_than_it_looks s using (id); - count -------- - 20000 -(1 row) - -select original > 1 as initially_multibatch, final > original as increased_batches - from hash_join_batches( -$$ - select count(*) from simple r join bigger_than_it_looks s using (id); -$$); - initially_multibatch | increased_batches -----------------------+------------------- - f | t -(1 row) - -rollback to settings; --- parallel with parallel-aware hash join -savepoint settings; -set local max_parallel_workers_per_gather = 1; -set local work_mem = '192kB'; -set local hash_mem_multiplier = 1.0; -set local enable_parallel_hash = on; -explain (costs off) - select count(*) from simple r join bigger_than_it_looks s using (id); - QUERY PLAN ---------------------------------------------------------------------------- - Finalize Aggregate - -> Gather - Workers Planned: 1 - -> Partial Aggregate - -> Parallel Hash Join - Hash Cond: (r.id = s.id) - -> Parallel Seq Scan on simple r - -> Parallel Hash - -> Parallel Seq Scan on bigger_than_it_looks s -(9 rows) - -select count(*) from simple r join bigger_than_it_looks s using (id); - count -------- - 20000 -(1 row) - -select original > 1 as initially_multibatch, final > original as increased_batches - from hash_join_batches( -$$ - select count(*) from simple r join bigger_than_it_looks s using (id); -$$); - initially_multibatch | increased_batches -----------------------+------------------- - f | t -(1 row) - -rollback to settings; --- The "ugly" case: increasing the number of batches during execution --- doesn't help, so stop trying to fit in work_mem and hope for the --- best; in this case we plan for 1 batch, increases just once and --- then stop increasing because that didn't help at all, so we blow --- right through the work_mem budget and hope for the best... --- non-parallel -savepoint settings; -set local max_parallel_workers_per_gather = 0; -set local work_mem = '128kB'; -set local hash_mem_multiplier = 1.0; -explain (costs off) - select count(*) from simple r join extremely_skewed s using (id); - QUERY PLAN --------------------------------------------------- - Aggregate - -> Hash Join - Hash Cond: (r.id = s.id) - -> Seq Scan on simple r - -> Hash - -> Seq Scan on extremely_skewed s -(6 rows) - -select count(*) from simple r join extremely_skewed s using (id); - count -------- - 20000 -(1 row) - -select * from hash_join_batches( -$$ - select count(*) from simple r join extremely_skewed s using (id); -$$); - original | final -----------+------- - 1 | 2 -(1 row) - -rollback to settings; --- parallel with parallel-oblivious hash join -savepoint settings; -set local max_parallel_workers_per_gather = 2; -set local work_mem = '128kB'; -set local hash_mem_multiplier = 1.0; -set local enable_parallel_hash = off; -explain (costs off) - select count(*) from simple r join extremely_skewed s using (id); - QUERY PLAN --------------------------------------------------------- - Aggregate - -> Gather - Workers Planned: 2 - -> Hash Join - Hash Cond: (r.id = s.id) - -> Parallel Seq Scan on simple r - -> Hash - -> Seq Scan on extremely_skewed s -(8 rows) - -select count(*) from simple r join extremely_skewed s using (id); - count -------- - 20000 -(1 row) - -select * from hash_join_batches( -$$ - select count(*) from simple r join extremely_skewed s using (id); -$$); - original | final -----------+------- - 1 | 2 -(1 row) - -rollback to settings; --- parallel with parallel-aware hash join -savepoint settings; -set local max_parallel_workers_per_gather = 1; -set local work_mem = '128kB'; -set local hash_mem_multiplier = 1.0; -set local enable_parallel_hash = on; -explain (costs off) - select count(*) from simple r join extremely_skewed s using (id); - QUERY PLAN ------------------------------------------------------------------ - Aggregate - -> Gather - Workers Planned: 1 - -> Parallel Hash Join - Hash Cond: (r.id = s.id) - -> Parallel Seq Scan on simple r - -> Parallel Hash - -> Parallel Seq Scan on extremely_skewed s -(8 rows) - -select count(*) from simple r join extremely_skewed s using (id); - count -------- - 20000 -(1 row) - -select * from hash_join_batches( -$$ - select count(*) from simple r join extremely_skewed s using (id); -$$); - original | final -----------+------- - 1 | 4 -(1 row) - -rollback to settings; --- A couple of other hash join tests unrelated to work_mem management. --- Check that EXPLAIN ANALYZE has data even if the leader doesn't participate -savepoint settings; -set local max_parallel_workers_per_gather = 2; -set local work_mem = '4MB'; -set local hash_mem_multiplier = 1.0; -set local parallel_leader_participation = off; -select * from hash_join_batches( -$$ - select count(*) from simple r join simple s using (id); -$$); - original | final -----------+------- - 1 | 1 -(1 row) - -rollback to settings; --- Exercise rescans. We'll turn off parallel_leader_participation so --- that we can check that instrumentation comes back correctly. -create table join_foo as select generate_series(1, 3) as id, 'xxxxx'::text as t; -alter table join_foo set (parallel_workers = 0); -create table join_bar as select generate_series(1, 10000) as id, 'xxxxx'::text as t; -alter table join_bar set (parallel_workers = 2); --- multi-batch with rescan, parallel-oblivious -savepoint settings; -set enable_parallel_hash = off; -set parallel_leader_participation = off; -set min_parallel_table_scan_size = 0; -set parallel_setup_cost = 0; -set parallel_tuple_cost = 0; -set max_parallel_workers_per_gather = 2; -set enable_material = off; -set enable_mergejoin = off; -set work_mem = '64kB'; -set hash_mem_multiplier = 1.0; -explain (costs off) - select count(*) from join_foo - left join (select b1.id, b1.t from join_bar b1 join join_bar b2 using (id)) ss - on join_foo.id < ss.id + 1 and join_foo.id > ss.id - 1; - QUERY PLAN ------------------------------------------------------------------------------------- - Aggregate - -> Nested Loop Left Join - Join Filter: ((join_foo.id < (b1.id + 1)) AND (join_foo.id > (b1.id - 1))) - -> Seq Scan on join_foo - -> Gather - Workers Planned: 2 - -> Hash Join - Hash Cond: (b1.id = b2.id) - -> Parallel Seq Scan on join_bar b1 - -> Hash - -> Seq Scan on join_bar b2 -(11 rows) - -select count(*) from join_foo - left join (select b1.id, b1.t from join_bar b1 join join_bar b2 using (id)) ss - on join_foo.id < ss.id + 1 and join_foo.id > ss.id - 1; - count -------- - 3 -(1 row) - -select final > 1 as multibatch - from hash_join_batches( -$$ - select count(*) from join_foo - left join (select b1.id, b1.t from join_bar b1 join join_bar b2 using (id)) ss - on join_foo.id < ss.id + 1 and join_foo.id > ss.id - 1; -$$); - multibatch ------------- - t -(1 row) - -rollback to settings; --- single-batch with rescan, parallel-oblivious -savepoint settings; -set enable_parallel_hash = off; -set parallel_leader_participation = off; -set min_parallel_table_scan_size = 0; -set parallel_setup_cost = 0; -set parallel_tuple_cost = 0; -set max_parallel_workers_per_gather = 2; -set enable_material = off; -set enable_mergejoin = off; -set work_mem = '4MB'; -set hash_mem_multiplier = 1.0; -explain (costs off) - select count(*) from join_foo - left join (select b1.id, b1.t from join_bar b1 join join_bar b2 using (id)) ss - on join_foo.id < ss.id + 1 and join_foo.id > ss.id - 1; - QUERY PLAN ------------------------------------------------------------------------------------- - Aggregate - -> Nested Loop Left Join - Join Filter: ((join_foo.id < (b1.id + 1)) AND (join_foo.id > (b1.id - 1))) - -> Seq Scan on join_foo - -> Gather - Workers Planned: 2 - -> Hash Join - Hash Cond: (b1.id = b2.id) - -> Parallel Seq Scan on join_bar b1 - -> Hash - -> Seq Scan on join_bar b2 -(11 rows) - -select count(*) from join_foo - left join (select b1.id, b1.t from join_bar b1 join join_bar b2 using (id)) ss - on join_foo.id < ss.id + 1 and join_foo.id > ss.id - 1; - count -------- - 3 -(1 row) - -select final > 1 as multibatch - from hash_join_batches( -$$ - select count(*) from join_foo - left join (select b1.id, b1.t from join_bar b1 join join_bar b2 using (id)) ss - on join_foo.id < ss.id + 1 and join_foo.id > ss.id - 1; -$$); - multibatch ------------- - f -(1 row) - -rollback to settings; --- multi-batch with rescan, parallel-aware -savepoint settings; -set enable_parallel_hash = on; -set parallel_leader_participation = off; -set min_parallel_table_scan_size = 0; -set parallel_setup_cost = 0; -set parallel_tuple_cost = 0; -set max_parallel_workers_per_gather = 2; -set enable_material = off; -set enable_mergejoin = off; -set work_mem = '64kB'; -set hash_mem_multiplier = 1.0; -explain (costs off) - select count(*) from join_foo - left join (select b1.id, b1.t from join_bar b1 join join_bar b2 using (id)) ss - on join_foo.id < ss.id + 1 and join_foo.id > ss.id - 1; - QUERY PLAN ------------------------------------------------------------------------------------- - Aggregate - -> Nested Loop Left Join - Join Filter: ((join_foo.id < (b1.id + 1)) AND (join_foo.id > (b1.id - 1))) - -> Seq Scan on join_foo - -> Gather - Workers Planned: 2 - -> Parallel Hash Join - Hash Cond: (b1.id = b2.id) - -> Parallel Seq Scan on join_bar b1 - -> Parallel Hash - -> Parallel Seq Scan on join_bar b2 -(11 rows) - -select count(*) from join_foo - left join (select b1.id, b1.t from join_bar b1 join join_bar b2 using (id)) ss - on join_foo.id < ss.id + 1 and join_foo.id > ss.id - 1; - count -------- - 3 -(1 row) - -select final > 1 as multibatch - from hash_join_batches( -$$ - select count(*) from join_foo - left join (select b1.id, b1.t from join_bar b1 join join_bar b2 using (id)) ss - on join_foo.id < ss.id + 1 and join_foo.id > ss.id - 1; -$$); - multibatch ------------- - t -(1 row) - -rollback to settings; --- single-batch with rescan, parallel-aware -savepoint settings; -set enable_parallel_hash = on; -set parallel_leader_participation = off; -set min_parallel_table_scan_size = 0; -set parallel_setup_cost = 0; -set parallel_tuple_cost = 0; -set max_parallel_workers_per_gather = 2; -set enable_material = off; -set enable_mergejoin = off; -set work_mem = '4MB'; -set hash_mem_multiplier = 1.0; -explain (costs off) - select count(*) from join_foo - left join (select b1.id, b1.t from join_bar b1 join join_bar b2 using (id)) ss - on join_foo.id < ss.id + 1 and join_foo.id > ss.id - 1; - QUERY PLAN ------------------------------------------------------------------------------------- - Aggregate - -> Nested Loop Left Join - Join Filter: ((join_foo.id < (b1.id + 1)) AND (join_foo.id > (b1.id - 1))) - -> Seq Scan on join_foo - -> Gather - Workers Planned: 2 - -> Parallel Hash Join - Hash Cond: (b1.id = b2.id) - -> Parallel Seq Scan on join_bar b1 - -> Parallel Hash - -> Parallel Seq Scan on join_bar b2 -(11 rows) - -select count(*) from join_foo - left join (select b1.id, b1.t from join_bar b1 join join_bar b2 using (id)) ss - on join_foo.id < ss.id + 1 and join_foo.id > ss.id - 1; - count -------- - 3 -(1 row) - -select final > 1 as multibatch - from hash_join_batches( -$$ - select count(*) from join_foo - left join (select b1.id, b1.t from join_bar b1 join join_bar b2 using (id)) ss - on join_foo.id < ss.id + 1 and join_foo.id > ss.id - 1; -$$); - multibatch ------------- - f -(1 row) - -rollback to settings; --- A full outer join where every record is matched. --- non-parallel -savepoint settings; -set local max_parallel_workers_per_gather = 0; -explain (costs off) - select count(*) from simple r full outer join simple s using (id); - QUERY PLAN ----------------------------------------- - Aggregate - -> Hash Full Join - Hash Cond: (r.id = s.id) - -> Seq Scan on simple r - -> Hash - -> Seq Scan on simple s -(6 rows) - -select count(*) from simple r full outer join simple s using (id); - count -------- - 20000 -(1 row) - -rollback to settings; --- parallelism not possible with parallel-oblivious full hash join -savepoint settings; -set enable_parallel_hash = off; -set local max_parallel_workers_per_gather = 2; -explain (costs off) - select count(*) from simple r full outer join simple s using (id); - QUERY PLAN ----------------------------------------- - Aggregate - -> Hash Full Join - Hash Cond: (r.id = s.id) - -> Seq Scan on simple r - -> Hash - -> Seq Scan on simple s -(6 rows) - -select count(*) from simple r full outer join simple s using (id); - count -------- - 20000 -(1 row) - -rollback to settings; --- parallelism is possible with parallel-aware full hash join -savepoint settings; -set local max_parallel_workers_per_gather = 2; -explain (costs off) - select count(*) from simple r full outer join simple s using (id); - QUERY PLAN -------------------------------------------------------------- - Finalize Aggregate - -> Gather - Workers Planned: 2 - -> Partial Aggregate - -> Parallel Hash Full Join - Hash Cond: (r.id = s.id) - -> Parallel Seq Scan on simple r - -> Parallel Hash - -> Parallel Seq Scan on simple s -(9 rows) - -select count(*) from simple r full outer join simple s using (id); - count -------- - 20000 -(1 row) - -rollback to settings; --- A full outer join where every record is not matched. --- non-parallel -savepoint settings; -set local max_parallel_workers_per_gather = 0; -explain (costs off) - select count(*) from simple r full outer join simple s on (r.id = 0 - s.id); - QUERY PLAN ----------------------------------------- - Aggregate - -> Hash Full Join - Hash Cond: ((0 - s.id) = r.id) - -> Seq Scan on simple s - -> Hash - -> Seq Scan on simple r -(6 rows) - -select count(*) from simple r full outer join simple s on (r.id = 0 - s.id); - count -------- - 40000 -(1 row) - -rollback to settings; --- parallelism not possible with parallel-oblivious full hash join -savepoint settings; -set enable_parallel_hash = off; -set local max_parallel_workers_per_gather = 2; -explain (costs off) - select count(*) from simple r full outer join simple s on (r.id = 0 - s.id); - QUERY PLAN ----------------------------------------- - Aggregate - -> Hash Full Join - Hash Cond: ((0 - s.id) = r.id) - -> Seq Scan on simple s - -> Hash - -> Seq Scan on simple r -(6 rows) - -select count(*) from simple r full outer join simple s on (r.id = 0 - s.id); - count -------- - 40000 -(1 row) - -rollback to settings; --- parallelism is possible with parallel-aware full hash join -savepoint settings; -set local max_parallel_workers_per_gather = 2; -explain (costs off) - select count(*) from simple r full outer join simple s on (r.id = 0 - s.id); - QUERY PLAN -------------------------------------------------------------- - Finalize Aggregate - -> Gather - Workers Planned: 2 - -> Partial Aggregate - -> Parallel Hash Full Join - Hash Cond: ((0 - s.id) = r.id) - -> Parallel Seq Scan on simple s - -> Parallel Hash - -> Parallel Seq Scan on simple r -(9 rows) - -select count(*) from simple r full outer join simple s on (r.id = 0 - s.id); - count -------- - 40000 -(1 row) - -rollback to settings; --- exercise special code paths for huge tuples (note use of non-strict --- expression and left join required to get the detoasted tuple into --- the hash table) --- parallel with parallel-aware hash join (hits ExecParallelHashLoadTuple and --- sts_puttuple oversized tuple cases because it's multi-batch) -savepoint settings; -set max_parallel_workers_per_gather = 2; -set enable_parallel_hash = on; -set work_mem = '128kB'; -set hash_mem_multiplier = 1.0; -explain (costs off) - select length(max(s.t)) - from wide left join (select id, coalesce(t, '') || '' as t from wide) s using (id); - QUERY PLAN ----------------------------------------------------------------- - Finalize Aggregate - -> Gather - Workers Planned: 2 - -> Partial Aggregate - -> Parallel Hash Left Join - Hash Cond: (wide.id = wide_1.id) - -> Parallel Seq Scan on wide - -> Parallel Hash - -> Parallel Seq Scan on wide wide_1 -(9 rows) - -select length(max(s.t)) -from wide left join (select id, coalesce(t, '') || '' as t from wide) s using (id); - length --------- - 320000 -(1 row) - -select final > 1 as multibatch - from hash_join_batches( -$$ - select length(max(s.t)) - from wide left join (select id, coalesce(t, '') || '' as t from wide) s using (id); -$$); - multibatch ------------- - t -(1 row) - -rollback to settings; --- Hash join reuses the HOT status bit to indicate match status. This can only --- be guaranteed to produce correct results if all the hash join tuple match --- bits are reset before reuse. This is done upon loading them into the --- hashtable. -SAVEPOINT settings; -SET enable_parallel_hash = on; -SET min_parallel_table_scan_size = 0; -SET parallel_setup_cost = 0; -SET parallel_tuple_cost = 0; -CREATE TABLE hjtest_matchbits_t1(id int); -CREATE TABLE hjtest_matchbits_t2(id int); -INSERT INTO hjtest_matchbits_t1 VALUES (1); -INSERT INTO hjtest_matchbits_t2 VALUES (2); --- Update should create a HOT tuple. If this status bit isn't cleared, we won't --- correctly emit the NULL-extended unmatching tuple in full hash join. -UPDATE hjtest_matchbits_t2 set id = 2; -SELECT * FROM hjtest_matchbits_t1 t1 FULL JOIN hjtest_matchbits_t2 t2 ON t1.id = t2.id - ORDER BY t1.id; - id | id -----+---- - 1 | - | 2 -(2 rows) - --- Test serial full hash join. --- Resetting parallel_setup_cost should force a serial plan. --- Just to be safe, however, set enable_parallel_hash to off, as parallel full --- hash joins are only supported with shared hashtables. -RESET parallel_setup_cost; -SET enable_parallel_hash = off; -SELECT * FROM hjtest_matchbits_t1 t1 FULL JOIN hjtest_matchbits_t2 t2 ON t1.id = t2.id; - id | id -----+---- - 1 | - | 2 -(2 rows) - -ROLLBACK TO settings; -rollback; --- Verify that hash key expressions reference the correct --- nodes. Hashjoin's hashkeys need to reference its outer plan, Hash's --- need to reference Hash's outer plan (which is below HashJoin's --- inner plan). It's not trivial to verify that the references are --- correct (we don't display the hashkeys themselves), but if the --- hashkeys contain subplan references, those will be displayed. Force --- subplans to appear just about everywhere. --- --- Bug report: --- https://www.postgresql.org/message-id/CAPpHfdvGVegF_TKKRiBrSmatJL2dR9uwFCuR%2BteQ_8tEXU8mxg%40mail.gmail.com --- -BEGIN; -SET LOCAL enable_sort = OFF; -- avoid mergejoins -SET LOCAL from_collapse_limit = 1; -- allows easy changing of join order -CREATE TABLE hjtest_1 (a text, b int, id int, c bool); -CREATE TABLE hjtest_2 (a bool, id int, b text, c int); -INSERT INTO hjtest_1(a, b, id, c) VALUES ('text', 2, 1, false); -- matches -INSERT INTO hjtest_1(a, b, id, c) VALUES ('text', 1, 2, false); -- fails id join condition -INSERT INTO hjtest_1(a, b, id, c) VALUES ('text', 20, 1, false); -- fails < 50 -INSERT INTO hjtest_1(a, b, id, c) VALUES ('text', 1, 1, false); -- fails (SELECT hjtest_1.b * 5) = (SELECT hjtest_2.c*5) -INSERT INTO hjtest_2(a, id, b, c) VALUES (true, 1, 'another', 2); -- matches -INSERT INTO hjtest_2(a, id, b, c) VALUES (true, 3, 'another', 7); -- fails id join condition -INSERT INTO hjtest_2(a, id, b, c) VALUES (true, 1, 'another', 90); -- fails < 55 -INSERT INTO hjtest_2(a, id, b, c) VALUES (true, 1, 'another', 3); -- fails (SELECT hjtest_1.b * 5) = (SELECT hjtest_2.c*5) -INSERT INTO hjtest_2(a, id, b, c) VALUES (true, 1, 'text', 1); -- fails hjtest_1.a <> hjtest_2.b; -EXPLAIN (COSTS OFF, VERBOSE) -SELECT hjtest_1.a a1, hjtest_2.a a2,hjtest_1.tableoid::regclass t1, hjtest_2.tableoid::regclass t2 -FROM hjtest_1, hjtest_2 -WHERE - hjtest_1.id = (SELECT 1 WHERE hjtest_2.id = 1) - AND (SELECT hjtest_1.b * 5) = (SELECT hjtest_2.c*5) - AND (SELECT hjtest_1.b * 5) < 50 - AND (SELECT hjtest_2.c * 5) < 55 - AND hjtest_1.a <> hjtest_2.b; - QUERY PLAN ------------------------------------------------------------------------------------------------- - Hash Join - Output: hjtest_1.a, hjtest_2.a, (hjtest_1.tableoid)::regclass, (hjtest_2.tableoid)::regclass - Hash Cond: ((hjtest_1.id = (SubPlan 1)) AND ((SubPlan 2) = (SubPlan 3))) - Join Filter: (hjtest_1.a <> hjtest_2.b) - -> Seq Scan on public.hjtest_1 - Output: hjtest_1.a, hjtest_1.tableoid, hjtest_1.id, hjtest_1.b - Filter: ((SubPlan 4) < 50) - SubPlan 4 - -> Result - Output: (hjtest_1.b * 5) - -> Hash - Output: hjtest_2.a, hjtest_2.tableoid, hjtest_2.id, hjtest_2.c, hjtest_2.b - -> Seq Scan on public.hjtest_2 - Output: hjtest_2.a, hjtest_2.tableoid, hjtest_2.id, hjtest_2.c, hjtest_2.b - Filter: ((SubPlan 5) < 55) - SubPlan 5 - -> Result - Output: (hjtest_2.c * 5) - SubPlan 1 - -> Result - Output: 1 - One-Time Filter: (hjtest_2.id = 1) - SubPlan 3 - -> Result - Output: (hjtest_2.c * 5) - SubPlan 2 - -> Result - Output: (hjtest_1.b * 5) -(28 rows) - -SELECT hjtest_1.a a1, hjtest_2.a a2,hjtest_1.tableoid::regclass t1, hjtest_2.tableoid::regclass t2 -FROM hjtest_1, hjtest_2 -WHERE - hjtest_1.id = (SELECT 1 WHERE hjtest_2.id = 1) - AND (SELECT hjtest_1.b * 5) = (SELECT hjtest_2.c*5) - AND (SELECT hjtest_1.b * 5) < 50 - AND (SELECT hjtest_2.c * 5) < 55 - AND hjtest_1.a <> hjtest_2.b; - a1 | a2 | t1 | t2 -------+----+----------+---------- - text | t | hjtest_1 | hjtest_2 -(1 row) - -EXPLAIN (COSTS OFF, VERBOSE) -SELECT hjtest_1.a a1, hjtest_2.a a2,hjtest_1.tableoid::regclass t1, hjtest_2.tableoid::regclass t2 -FROM hjtest_2, hjtest_1 -WHERE - hjtest_1.id = (SELECT 1 WHERE hjtest_2.id = 1) - AND (SELECT hjtest_1.b * 5) = (SELECT hjtest_2.c*5) - AND (SELECT hjtest_1.b * 5) < 50 - AND (SELECT hjtest_2.c * 5) < 55 - AND hjtest_1.a <> hjtest_2.b; - QUERY PLAN ------------------------------------------------------------------------------------------------- - Hash Join - Output: hjtest_1.a, hjtest_2.a, (hjtest_1.tableoid)::regclass, (hjtest_2.tableoid)::regclass - Hash Cond: (((SubPlan 1) = hjtest_1.id) AND ((SubPlan 3) = (SubPlan 2))) - Join Filter: (hjtest_1.a <> hjtest_2.b) - -> Seq Scan on public.hjtest_2 - Output: hjtest_2.a, hjtest_2.tableoid, hjtest_2.id, hjtest_2.c, hjtest_2.b - Filter: ((SubPlan 5) < 55) - SubPlan 5 - -> Result - Output: (hjtest_2.c * 5) - -> Hash - Output: hjtest_1.a, hjtest_1.tableoid, hjtest_1.id, hjtest_1.b - -> Seq Scan on public.hjtest_1 - Output: hjtest_1.a, hjtest_1.tableoid, hjtest_1.id, hjtest_1.b - Filter: ((SubPlan 4) < 50) - SubPlan 4 - -> Result - Output: (hjtest_1.b * 5) - SubPlan 2 - -> Result - Output: (hjtest_1.b * 5) - SubPlan 1 - -> Result - Output: 1 - One-Time Filter: (hjtest_2.id = 1) - SubPlan 3 - -> Result - Output: (hjtest_2.c * 5) -(28 rows) - -SELECT hjtest_1.a a1, hjtest_2.a a2,hjtest_1.tableoid::regclass t1, hjtest_2.tableoid::regclass t2 -FROM hjtest_2, hjtest_1 -WHERE - hjtest_1.id = (SELECT 1 WHERE hjtest_2.id = 1) - AND (SELECT hjtest_1.b * 5) = (SELECT hjtest_2.c*5) - AND (SELECT hjtest_1.b * 5) < 50 - AND (SELECT hjtest_2.c * 5) < 55 - AND hjtest_1.a <> hjtest_2.b; - a1 | a2 | t1 | t2 -------+----+----------+---------- - text | t | hjtest_1 | hjtest_2 -(1 row) - -ROLLBACK; --- Verify that we behave sanely when the inner hash keys contain parameters --- (that is, outer or lateral references). This situation has to defeat --- re-use of the inner hash table across rescans. -begin; -set local enable_hashjoin = on; -explain (costs off) -select i8.q2, ss.* from -int8_tbl i8, -lateral (select t1.fivethous, i4.f1 from tenk1 t1 join int4_tbl i4 - on t1.fivethous = i4.f1+i8.q2 order by 1,2) ss; - QUERY PLAN ------------------------------------------------------------ - Nested Loop - -> Seq Scan on int8_tbl i8 - -> Sort - Sort Key: t1.fivethous, i4.f1 - -> Hash Join - Hash Cond: (t1.fivethous = (i4.f1 + i8.q2)) - -> Seq Scan on tenk1 t1 - -> Hash - -> Seq Scan on int4_tbl i4 -(9 rows) - -select i8.q2, ss.* from -int8_tbl i8, -lateral (select t1.fivethous, i4.f1 from tenk1 t1 join int4_tbl i4 - on t1.fivethous = i4.f1+i8.q2 order by 1,2) ss; - q2 | fivethous | f1 ------+-----------+---- - 456 | 456 | 0 - 456 | 456 | 0 - 123 | 123 | 0 - 123 | 123 | 0 -(4 rows) - -rollback; +psql: error: connection to server on socket "/tmp/pg_regress-03Az7d/.s.PGSQL.40051" failed: FATAL: the database system is not yet accepting connections +DETAIL: Consistent recovery state has not been yet reached. diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/amutils.out /tmp/cirrus-ci-build/build/testrun/regress/regress/results/amutils.out --- /tmp/cirrus-ci-build/src/test/regress/expected/amutils.out 2024-09-20 21:41:45.662024520 +0000 +++ /tmp/cirrus-ci-build/build/testrun/regress/regress/results/amutils.out 2024-09-20 21:46:09.969618983 +0000 @@ -93,20 +93,9 @@ 'bogus']::text[]) with ordinality as u(prop,ord) order by ord; - prop | btree | hash | gist | spgist_radix | spgist_quad | gin | brin ---------------------+-------+------+------+--------------+-------------+-----+------ - asc | t | f | f | f | f | f | f - desc | f | f | f | f | f | f | f - nulls_first | f | f | f | f | f | f | f - nulls_last | t | f | f | f | f | f | f - orderable | t | f | f | f | f | f | f - distance_orderable | f | f | t | f | t | f | f - returnable | t | f | f | t | t | f | f - search_array | t | f | f | f | f | f | f - search_nulls | t | f | t | t | t | f | t - bogus | | | | | | | -(10 rows) - +ERROR: relation "hash_i4_index" does not exist +LINE 3: pg_index_column_has_property('hash_i4_index'::regclas... + ^ select prop, pg_index_has_property('onek_hundred'::regclass, prop) as btree, pg_index_has_property('hash_i4_index'::regclass, prop) as hash, @@ -119,15 +108,9 @@ 'bogus']::text[]) with ordinality as u(prop,ord) order by ord; - prop | btree | hash | gist | spgist | gin | brin ----------------+-------+------+------+--------+-----+------ - clusterable | t | f | t | f | f | f - index_scan | t | t | t | t | f | f - bitmap_scan | t | t | t | t | t | t - backward_scan | t | t | f | f | f | f - bogus | | | | | | -(5 rows) - +ERROR: relation "hash_i4_index" does not exist +LINE 3: pg_index_has_property('hash_i4_index'::regclass, prop... + ^ select amname, prop, pg_indexam_has_property(a.oid, prop) as p from pg_am a, unnest(array['can_order', 'can_unique', 'can_multi_col',