@@ -7371,6 +7371,340 @@ NOTICE: drop cascades to foreign table bar2
73717371drop table loct1;
73727372drop table loct2;
73737373-- ===================================================================
7374+ -- test tuple routing for foreign-table partitions
7375+ -- ===================================================================
7376+ -- Test insert tuple routing
7377+ create table itrtest (a int, b text) partition by list (a);
7378+ create table loct1 (a int check (a in (1)), b text);
7379+ create foreign table remp1 (a int check (a in (1)), b text) server loopback options (table_name 'loct1');
7380+ create table loct2 (a int check (a in (2)), b text);
7381+ create foreign table remp2 (b text, a int check (a in (2))) server loopback options (table_name 'loct2');
7382+ alter table itrtest attach partition remp1 for values in (1);
7383+ alter table itrtest attach partition remp2 for values in (2);
7384+ insert into itrtest values (1, 'foo');
7385+ insert into itrtest values (1, 'bar') returning *;
7386+ a | b
7387+ ---+-----
7388+ 1 | bar
7389+ (1 row)
7390+
7391+ insert into itrtest values (2, 'baz');
7392+ insert into itrtest values (2, 'qux') returning *;
7393+ a | b
7394+ ---+-----
7395+ 2 | qux
7396+ (1 row)
7397+
7398+ insert into itrtest values (1, 'test1'), (2, 'test2') returning *;
7399+ a | b
7400+ ---+-------
7401+ 1 | test1
7402+ 2 | test2
7403+ (2 rows)
7404+
7405+ select tableoid::regclass, * FROM itrtest;
7406+ tableoid | a | b
7407+ ----------+---+-------
7408+ remp1 | 1 | foo
7409+ remp1 | 1 | bar
7410+ remp1 | 1 | test1
7411+ remp2 | 2 | baz
7412+ remp2 | 2 | qux
7413+ remp2 | 2 | test2
7414+ (6 rows)
7415+
7416+ select tableoid::regclass, * FROM remp1;
7417+ tableoid | a | b
7418+ ----------+---+-------
7419+ remp1 | 1 | foo
7420+ remp1 | 1 | bar
7421+ remp1 | 1 | test1
7422+ (3 rows)
7423+
7424+ select tableoid::regclass, * FROM remp2;
7425+ tableoid | b | a
7426+ ----------+-------+---
7427+ remp2 | baz | 2
7428+ remp2 | qux | 2
7429+ remp2 | test2 | 2
7430+ (3 rows)
7431+
7432+ delete from itrtest;
7433+ create unique index loct1_idx on loct1 (a);
7434+ -- DO NOTHING without an inference specification is supported
7435+ insert into itrtest values (1, 'foo') on conflict do nothing returning *;
7436+ a | b
7437+ ---+-----
7438+ 1 | foo
7439+ (1 row)
7440+
7441+ insert into itrtest values (1, 'foo') on conflict do nothing returning *;
7442+ a | b
7443+ ---+---
7444+ (0 rows)
7445+
7446+ -- But other cases are not supported
7447+ insert into itrtest values (1, 'bar') on conflict (a) do nothing;
7448+ ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification
7449+ insert into itrtest values (1, 'bar') on conflict (a) do update set b = excluded.b;
7450+ ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification
7451+ select tableoid::regclass, * FROM itrtest;
7452+ tableoid | a | b
7453+ ----------+---+-----
7454+ remp1 | 1 | foo
7455+ (1 row)
7456+
7457+ drop table itrtest;
7458+ drop table loct1;
7459+ drop table loct2;
7460+ -- Test update tuple routing
7461+ create table utrtest (a int, b text) partition by list (a);
7462+ create table loct (a int check (a in (1)), b text);
7463+ create foreign table remp (a int check (a in (1)), b text) server loopback options (table_name 'loct');
7464+ create table locp (a int check (a in (2)), b text);
7465+ alter table utrtest attach partition remp for values in (1);
7466+ alter table utrtest attach partition locp for values in (2);
7467+ insert into utrtest values (1, 'foo');
7468+ insert into utrtest values (2, 'qux');
7469+ select tableoid::regclass, * FROM utrtest;
7470+ tableoid | a | b
7471+ ----------+---+-----
7472+ remp | 1 | foo
7473+ locp | 2 | qux
7474+ (2 rows)
7475+
7476+ select tableoid::regclass, * FROM remp;
7477+ tableoid | a | b
7478+ ----------+---+-----
7479+ remp | 1 | foo
7480+ (1 row)
7481+
7482+ select tableoid::regclass, * FROM locp;
7483+ tableoid | a | b
7484+ ----------+---+-----
7485+ locp | 2 | qux
7486+ (1 row)
7487+
7488+ -- It's not allowed to move a row from a partition that is foreign to another
7489+ update utrtest set a = 2 where b = 'foo' returning *;
7490+ ERROR: new row for relation "loct" violates check constraint "loct_a_check"
7491+ DETAIL: Failing row contains (2, foo).
7492+ CONTEXT: remote SQL command: UPDATE public.loct SET a = 2 WHERE ((b = 'foo'::text)) RETURNING a, b
7493+ -- But the reverse is allowed
7494+ update utrtest set a = 1 where b = 'qux' returning *;
7495+ a | b
7496+ ---+-----
7497+ 1 | qux
7498+ (1 row)
7499+
7500+ select tableoid::regclass, * FROM utrtest;
7501+ tableoid | a | b
7502+ ----------+---+-----
7503+ remp | 1 | foo
7504+ remp | 1 | qux
7505+ (2 rows)
7506+
7507+ select tableoid::regclass, * FROM remp;
7508+ tableoid | a | b
7509+ ----------+---+-----
7510+ remp | 1 | foo
7511+ remp | 1 | qux
7512+ (2 rows)
7513+
7514+ select tableoid::regclass, * FROM locp;
7515+ tableoid | a | b
7516+ ----------+---+---
7517+ (0 rows)
7518+
7519+ -- The executor should not let unexercised FDWs shut down
7520+ update utrtest set a = 1 where b = 'foo';
7521+ drop table utrtest;
7522+ drop table loct;
7523+ -- Test copy tuple routing
7524+ create table ctrtest (a int, b text) partition by list (a);
7525+ create table loct1 (a int check (a in (1)), b text);
7526+ create foreign table remp1 (a int check (a in (1)), b text) server loopback options (table_name 'loct1');
7527+ create table loct2 (a int check (a in (2)), b text);
7528+ create foreign table remp2 (b text, a int check (a in (2))) server loopback options (table_name 'loct2');
7529+ alter table ctrtest attach partition remp1 for values in (1);
7530+ alter table ctrtest attach partition remp2 for values in (2);
7531+ copy ctrtest from stdin;
7532+ select tableoid::regclass, * FROM ctrtest;
7533+ tableoid | a | b
7534+ ----------+---+-----
7535+ remp1 | 1 | foo
7536+ remp2 | 2 | qux
7537+ (2 rows)
7538+
7539+ select tableoid::regclass, * FROM remp1;
7540+ tableoid | a | b
7541+ ----------+---+-----
7542+ remp1 | 1 | foo
7543+ (1 row)
7544+
7545+ select tableoid::regclass, * FROM remp2;
7546+ tableoid | b | a
7547+ ----------+-----+---
7548+ remp2 | qux | 2
7549+ (1 row)
7550+
7551+ -- Copying into foreign partitions directly should work as well
7552+ copy remp1 from stdin;
7553+ select tableoid::regclass, * FROM remp1;
7554+ tableoid | a | b
7555+ ----------+---+-----
7556+ remp1 | 1 | foo
7557+ remp1 | 1 | bar
7558+ (2 rows)
7559+
7560+ drop table ctrtest;
7561+ drop table loct1;
7562+ drop table loct2;
7563+ -- ===================================================================
7564+ -- test COPY FROM
7565+ -- ===================================================================
7566+ create table loc2 (f1 int, f2 text);
7567+ alter table loc2 set (autovacuum_enabled = 'false');
7568+ create foreign table rem2 (f1 int, f2 text) server loopback options(table_name 'loc2');
7569+ -- Test basic functionality
7570+ copy rem2 from stdin;
7571+ select * from rem2;
7572+ f1 | f2
7573+ ----+-----
7574+ 1 | foo
7575+ 2 | bar
7576+ (2 rows)
7577+
7578+ delete from rem2;
7579+ -- Test check constraints
7580+ alter table loc2 add constraint loc2_f1positive check (f1 >= 0);
7581+ alter foreign table rem2 add constraint rem2_f1positive check (f1 >= 0);
7582+ -- check constraint is enforced on the remote side, not locally
7583+ copy rem2 from stdin;
7584+ copy rem2 from stdin; -- ERROR
7585+ ERROR: new row for relation "loc2" violates check constraint "loc2_f1positive"
7586+ DETAIL: Failing row contains (-1, xyzzy).
7587+ CONTEXT: remote SQL command: INSERT INTO public.loc2(f1, f2) VALUES ($1, $2)
7588+ COPY rem2, line 1: "-1 xyzzy"
7589+ select * from rem2;
7590+ f1 | f2
7591+ ----+-----
7592+ 1 | foo
7593+ 2 | bar
7594+ (2 rows)
7595+
7596+ alter foreign table rem2 drop constraint rem2_f1positive;
7597+ alter table loc2 drop constraint loc2_f1positive;
7598+ delete from rem2;
7599+ -- Test local triggers
7600+ create trigger trig_stmt_before before insert on rem2
7601+ for each statement execute procedure trigger_func();
7602+ create trigger trig_stmt_after after insert on rem2
7603+ for each statement execute procedure trigger_func();
7604+ create trigger trig_row_before before insert on rem2
7605+ for each row execute procedure trigger_data(23,'skidoo');
7606+ create trigger trig_row_after after insert on rem2
7607+ for each row execute procedure trigger_data(23,'skidoo');
7608+ copy rem2 from stdin;
7609+ NOTICE: trigger_func(<NULL>) called: action = INSERT, when = BEFORE, level = STATEMENT
7610+ NOTICE: trig_row_before(23, skidoo) BEFORE ROW INSERT ON rem2
7611+ NOTICE: NEW: (1,foo)
7612+ NOTICE: trig_row_before(23, skidoo) BEFORE ROW INSERT ON rem2
7613+ NOTICE: NEW: (2,bar)
7614+ NOTICE: trig_row_after(23, skidoo) AFTER ROW INSERT ON rem2
7615+ NOTICE: NEW: (1,foo)
7616+ NOTICE: trig_row_after(23, skidoo) AFTER ROW INSERT ON rem2
7617+ NOTICE: NEW: (2,bar)
7618+ NOTICE: trigger_func(<NULL>) called: action = INSERT, when = AFTER, level = STATEMENT
7619+ select * from rem2;
7620+ f1 | f2
7621+ ----+-----
7622+ 1 | foo
7623+ 2 | bar
7624+ (2 rows)
7625+
7626+ drop trigger trig_row_before on rem2;
7627+ drop trigger trig_row_after on rem2;
7628+ drop trigger trig_stmt_before on rem2;
7629+ drop trigger trig_stmt_after on rem2;
7630+ delete from rem2;
7631+ create trigger trig_row_before_insert before insert on rem2
7632+ for each row execute procedure trig_row_before_insupdate();
7633+ -- The new values are concatenated with ' triggered !'
7634+ copy rem2 from stdin;
7635+ select * from rem2;
7636+ f1 | f2
7637+ ----+-----------------
7638+ 1 | foo triggered !
7639+ 2 | bar triggered !
7640+ (2 rows)
7641+
7642+ drop trigger trig_row_before_insert on rem2;
7643+ delete from rem2;
7644+ create trigger trig_null before insert on rem2
7645+ for each row execute procedure trig_null();
7646+ -- Nothing happens
7647+ copy rem2 from stdin;
7648+ select * from rem2;
7649+ f1 | f2
7650+ ----+----
7651+ (0 rows)
7652+
7653+ drop trigger trig_null on rem2;
7654+ delete from rem2;
7655+ -- Test remote triggers
7656+ create trigger trig_row_before_insert before insert on loc2
7657+ for each row execute procedure trig_row_before_insupdate();
7658+ -- The new values are concatenated with ' triggered !'
7659+ copy rem2 from stdin;
7660+ select * from rem2;
7661+ f1 | f2
7662+ ----+-----------------
7663+ 1 | foo triggered !
7664+ 2 | bar triggered !
7665+ (2 rows)
7666+
7667+ drop trigger trig_row_before_insert on loc2;
7668+ delete from rem2;
7669+ create trigger trig_null before insert on loc2
7670+ for each row execute procedure trig_null();
7671+ -- Nothing happens
7672+ copy rem2 from stdin;
7673+ select * from rem2;
7674+ f1 | f2
7675+ ----+----
7676+ (0 rows)
7677+
7678+ drop trigger trig_null on loc2;
7679+ delete from rem2;
7680+ -- Test a combination of local and remote triggers
7681+ create trigger rem2_trig_row_before before insert on rem2
7682+ for each row execute procedure trigger_data(23,'skidoo');
7683+ create trigger rem2_trig_row_after after insert on rem2
7684+ for each row execute procedure trigger_data(23,'skidoo');
7685+ create trigger loc2_trig_row_before_insert before insert on loc2
7686+ for each row execute procedure trig_row_before_insupdate();
7687+ copy rem2 from stdin;
7688+ NOTICE: rem2_trig_row_before(23, skidoo) BEFORE ROW INSERT ON rem2
7689+ NOTICE: NEW: (1,foo)
7690+ NOTICE: rem2_trig_row_before(23, skidoo) BEFORE ROW INSERT ON rem2
7691+ NOTICE: NEW: (2,bar)
7692+ NOTICE: rem2_trig_row_after(23, skidoo) AFTER ROW INSERT ON rem2
7693+ NOTICE: NEW: (1,"foo triggered !")
7694+ NOTICE: rem2_trig_row_after(23, skidoo) AFTER ROW INSERT ON rem2
7695+ NOTICE: NEW: (2,"bar triggered !")
7696+ select * from rem2;
7697+ f1 | f2
7698+ ----+-----------------
7699+ 1 | foo triggered !
7700+ 2 | bar triggered !
7701+ (2 rows)
7702+
7703+ drop trigger rem2_trig_row_before on rem2;
7704+ drop trigger rem2_trig_row_after on rem2;
7705+ drop trigger loc2_trig_row_before_insert on loc2;
7706+ delete from rem2;
7707+ -- ===================================================================
73747708-- test IMPORT FOREIGN SCHEMA
73757709-- ===================================================================
73767710CREATE SCHEMA import_source;
0 commit comments