1 -- ===================================================================
3 -- ===================================================================
5 CREATE EXTENSION postgres_fdw;
7 CREATE SERVER testserver1 FOREIGN DATA WRAPPER postgres_fdw;
10 EXECUTE $$CREATE SERVER loopback FOREIGN DATA WRAPPER postgres_fdw
11 OPTIONS (dbname '$$||current_database()||$$',
12 port '$$||current_setting('port')||$$'
17 CREATE USER MAPPING FOR public SERVER testserver1
18 OPTIONS (user 'value', password 'value');
19 CREATE USER MAPPING FOR CURRENT_USER SERVER loopback;
21 -- ===================================================================
22 -- create objects used through FDW loopback server
23 -- ===================================================================
24 CREATE TYPE user_enum AS ENUM ('foo', 'bar', 'buz');
26 CREATE TABLE "S 1"."T 1" (
35 CONSTRAINT t1_pkey PRIMARY KEY ("C 1")
37 CREATE TABLE "S 1"."T 2" (
40 CONSTRAINT t2_pkey PRIMARY KEY (c1)
43 INSERT INTO "S 1"."T 1"
46 to_char(id, 'FM00000'),
47 '1970-01-01'::timestamptz + ((id % 100) || ' days')::interval,
48 '1970-01-01'::timestamp + ((id % 100) || ' days')::interval,
52 FROM generate_series(1, 1000) id;
53 INSERT INTO "S 1"."T 2"
55 'AAA' || to_char(id, 'FM000')
56 FROM generate_series(1, 100) id;
61 -- ===================================================================
62 -- create foreign tables
63 -- ===================================================================
64 CREATE FOREIGN TABLE ft1 (
72 c7 char(10) default 'ft1',
75 ALTER FOREIGN TABLE ft1 DROP COLUMN c0;
77 CREATE FOREIGN TABLE ft2 (
85 c7 char(10) default 'ft2',
88 ALTER FOREIGN TABLE ft2 DROP COLUMN cx;
90 -- ===================================================================
91 -- tests for validator
92 -- ===================================================================
93 -- requiressl, krbsrvname and gsslib are omitted because they depend on
95 ALTER SERVER testserver1 OPTIONS (
96 use_remote_estimate 'false',
98 fdw_startup_cost '123.456',
99 fdw_tuple_cost '0.123',
101 connect_timeout 'value',
106 --client_encoding 'value',
107 application_name 'value',
108 --fallback_application_name 'value',
110 keepalives_idle 'value',
111 keepalives_interval 'value',
112 -- requiressl 'value',
113 sslcompression 'value',
119 --requirepeer 'value',
120 -- krbsrvname 'value',
122 --replication 'value'
124 ALTER USER MAPPING FOR public SERVER testserver1
125 OPTIONS (DROP user, DROP password);
126 ALTER FOREIGN TABLE ft1 OPTIONS (schema_name 'S 1', table_name 'T 1');
127 ALTER FOREIGN TABLE ft2 OPTIONS (schema_name 'S 1', table_name 'T 1');
128 ALTER FOREIGN TABLE ft1 ALTER COLUMN c1 OPTIONS (column_name 'C 1');
129 ALTER FOREIGN TABLE ft2 ALTER COLUMN c1 OPTIONS (column_name 'C 1');
132 -- Now we should be able to run ANALYZE.
133 -- To exercise multiple code paths, we use local stats on ft1
134 -- and remote-estimate mode on ft2.
136 ALTER FOREIGN TABLE ft2 OPTIONS (use_remote_estimate 'true');
138 -- ===================================================================
140 -- ===================================================================
141 -- single table, with/without alias
142 EXPLAIN (COSTS false) SELECT * FROM ft1 ORDER BY c3, c1 OFFSET 100 LIMIT 10;
143 SELECT * FROM ft1 ORDER BY c3, c1 OFFSET 100 LIMIT 10;
144 EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10;
145 SELECT * FROM ft1 t1 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10;
146 -- whole-row reference
147 EXPLAIN (VERBOSE, COSTS false) SELECT t1 FROM ft1 t1 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10;
148 SELECT t1 FROM ft1 t1 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10;
150 SELECT * FROM ft1 WHERE false;
152 EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = 101 AND t1.c6 = '1' AND t1.c7 >= '1';
153 SELECT * FROM ft1 t1 WHERE t1.c1 = 101 AND t1.c6 = '1' AND t1.c7 >= '1';
154 -- with FOR UPDATE/SHARE
155 EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c1 = 101 FOR UPDATE;
156 SELECT * FROM ft1 t1 WHERE c1 = 101 FOR UPDATE;
157 EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c1 = 102 FOR SHARE;
158 SELECT * FROM ft1 t1 WHERE c1 = 102 FOR SHARE;
160 SELECT COUNT(*) FROM ft1 t1;
162 SELECT t1.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10;
164 SELECT * FROM ft1 t1 WHERE t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 <= 10) ORDER BY c1;
166 SELECT * FROM ft1 t1 WHERE t1.c3 = (SELECT MAX(c3) FROM ft2 t2) ORDER BY c1;
168 WITH t1 AS (SELECT * FROM ft1 WHERE c1 <= 10) SELECT t2.c1, t2.c2, t2.c3, t2.c4 FROM t1, ft2 t2 WHERE t1.c1 = t2.c1 ORDER BY t1.c1;
170 SELECT 'fixed', NULL FROM ft1 t1 WHERE c1 = 1;
171 -- user-defined operator/function
172 CREATE FUNCTION postgres_fdw_abs(int) RETURNS int AS $$
176 $$ LANGUAGE plpgsql IMMUTABLE;
177 CREATE OPERATOR === (
184 EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = postgres_fdw_abs(t1.c2);
185 EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2;
186 EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = abs(t1.c2);
187 EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = t1.c2;
189 -- ===================================================================
190 -- WHERE with remotely-executable conditions
191 -- ===================================================================
192 EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = 1; -- Var, OpExpr(b), Const
193 EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = 100 AND t1.c2 = 0; -- BoolExpr
194 EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c1 IS NULL; -- NullTest
195 EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c1 IS NOT NULL; -- NullTest
196 EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE round(abs(c1), 0) = 1; -- FuncExpr
197 EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c1 = -c1; -- OpExpr(l)
198 EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE 1 = c1!; -- OpExpr(r)
199 EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE (c1 IS NOT NULL) IS DISTINCT FROM (c1 IS NOT NULL); -- DistinctExpr
200 EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c1 = ANY(ARRAY[c2, 1, c1 + 0]); -- ScalarArrayOpExpr
201 EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c1 = (ARRAY[c1,c2,3])[1]; -- ArrayRef
202 EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c6 = E'foo''s\\bar'; -- check special chars
203 EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE c8 = 'foo'; -- can't be sent to remote
204 -- parameterized remote path
205 EXPLAIN (VERBOSE, COSTS false)
206 SELECT * FROM ft2 a, ft2 b WHERE a.c1 = 47 AND b.c1 = a.c2;
207 SELECT * FROM ft2 a, ft2 b WHERE a.c1 = 47 AND b.c1 = a.c2;
208 -- check both safe and unsafe join conditions
209 EXPLAIN (VERBOSE, COSTS false)
210 SELECT * FROM ft2 a, ft2 b
211 WHERE a.c2 = 6 AND b.c1 = a.c1 AND a.c8 = 'foo' AND b.c7 = upper(a.c7);
212 SELECT * FROM ft2 a, ft2 b
213 WHERE a.c2 = 6 AND b.c1 = a.c1 AND a.c8 = 'foo' AND b.c7 = upper(a.c7);
214 -- bug before 9.3.5 due to sloppy handling of remote-estimate parameters
215 SELECT * FROM ft1 WHERE c1 = ANY (ARRAY(SELECT c1 FROM ft2 WHERE c1 < 5));
216 SELECT * FROM ft2 WHERE c1 = ANY (ARRAY(SELECT c1 FROM ft1 WHERE c1 < 5));
218 -- ===================================================================
219 -- parameterized queries
220 -- ===================================================================
222 PREPARE st1(int, int) AS SELECT t1.c3, t2.c3 FROM ft1 t1, ft2 t2 WHERE t1.c1 = $1 AND t2.c1 = $2;
223 EXPLAIN (VERBOSE, COSTS false) EXECUTE st1(1, 2);
225 EXECUTE st1(101, 101);
226 -- subquery using stable function (can't be sent to remote)
227 PREPARE st2(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND date(c4) = '1970-01-17'::date) ORDER BY c1;
228 EXPLAIN (VERBOSE, COSTS false) EXECUTE st2(10, 20);
230 EXECUTE st2(101, 121);
231 -- subquery using immutable function (can be sent to remote)
232 PREPARE st3(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND date(c5) = '1970-01-17'::date) ORDER BY c1;
233 EXPLAIN (VERBOSE, COSTS false) EXECUTE st3(10, 20);
236 -- custom plan should be chosen initially
237 PREPARE st4(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 = $1;
238 EXPLAIN (VERBOSE, COSTS false) EXECUTE st4(1);
239 EXPLAIN (VERBOSE, COSTS false) EXECUTE st4(1);
240 EXPLAIN (VERBOSE, COSTS false) EXECUTE st4(1);
241 EXPLAIN (VERBOSE, COSTS false) EXECUTE st4(1);
242 EXPLAIN (VERBOSE, COSTS false) EXECUTE st4(1);
243 -- once we try it enough times, should switch to generic plan
244 EXPLAIN (VERBOSE, COSTS false) EXECUTE st4(1);
245 -- value of $1 should not be sent to remote
246 PREPARE st5(user_enum,int) AS SELECT * FROM ft1 t1 WHERE c8 = $1 and c1 = $2;
247 EXPLAIN (VERBOSE, COSTS false) EXECUTE st5('foo', 1);
248 EXPLAIN (VERBOSE, COSTS false) EXECUTE st5('foo', 1);
249 EXPLAIN (VERBOSE, COSTS false) EXECUTE st5('foo', 1);
250 EXPLAIN (VERBOSE, COSTS false) EXECUTE st5('foo', 1);
251 EXPLAIN (VERBOSE, COSTS false) EXECUTE st5('foo', 1);
252 EXPLAIN (VERBOSE, COSTS false) EXECUTE st5('foo', 1);
253 EXECUTE st5('foo', 1);
262 -- System columns, except ctid, should not be sent to remote
263 EXPLAIN (VERBOSE, COSTS false)
264 SELECT * FROM ft1 t1 WHERE t1.tableoid = 'pg_class'::regclass LIMIT 1;
265 SELECT * FROM ft1 t1 WHERE t1.tableoid = 'ft1'::regclass LIMIT 1;
266 EXPLAIN (VERBOSE, COSTS false)
267 SELECT tableoid::regclass, * FROM ft1 t1 LIMIT 1;
268 SELECT tableoid::regclass, * FROM ft1 t1 LIMIT 1;
269 EXPLAIN (VERBOSE, COSTS false)
270 SELECT * FROM ft1 t1 WHERE t1.ctid = '(0,2)';
271 SELECT * FROM ft1 t1 WHERE t1.ctid = '(0,2)';
272 EXPLAIN (VERBOSE, COSTS false)
273 SELECT ctid, * FROM ft1 t1 LIMIT 1;
274 SELECT ctid, * FROM ft1 t1 LIMIT 1;
276 -- ===================================================================
277 -- used in pl/pgsql function
278 -- ===================================================================
279 CREATE OR REPLACE FUNCTION f_test(p_c1 int) RETURNS int AS $$
283 SELECT c1 INTO v_c1 FROM ft1 WHERE c1 = p_c1 LIMIT 1;
284 PERFORM c1 FROM ft1 WHERE c1 = p_c1 AND p_c1 = v_c1 LIMIT 1;
289 DROP FUNCTION f_test(int);
291 -- ===================================================================
293 -- ===================================================================
294 ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE int;
295 SELECT * FROM ft1 WHERE c1 = 1; -- ERROR
296 ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE user_enum;
298 -- ===================================================================
300 -- + local/remote error doesn't break cursor
301 -- ===================================================================
303 DECLARE c CURSOR FOR SELECT * FROM ft1 ORDER BY c1;
310 SELECT * FROM ft1 WHERE 1 / (c1 - 1) > 0; -- ERROR
313 SELECT * FROM ft1 ORDER BY c1 LIMIT 1;
316 -- ===================================================================
317 -- test handling of collations
318 -- ===================================================================
319 create table loct3 (f1 text collate "C" unique, f2 text, f3 varchar(10) unique);
320 create foreign table ft3 (f1 text collate "C", f2 text, f3 varchar(10))
321 server loopback options (table_name 'loct3', use_remote_estimate 'true');
323 -- can be sent to remote
324 explain (verbose, costs off) select * from ft3 where f1 = 'foo';
325 explain (verbose, costs off) select * from ft3 where f1 COLLATE "C" = 'foo';
326 explain (verbose, costs off) select * from ft3 where f2 = 'foo';
327 explain (verbose, costs off) select * from ft3 where f3 = 'foo';
328 explain (verbose, costs off) select * from ft3 f, loct3 l
329 where f.f3 = l.f3 and l.f1 = 'foo';
330 -- can't be sent to remote
331 explain (verbose, costs off) select * from ft3 where f1 COLLATE "POSIX" = 'foo';
332 explain (verbose, costs off) select * from ft3 where f1 = 'foo' COLLATE "C";
333 explain (verbose, costs off) select * from ft3 where f2 COLLATE "C" = 'foo';
334 explain (verbose, costs off) select * from ft3 where f2 = 'foo' COLLATE "C";
335 explain (verbose, costs off) select * from ft3 f, loct3 l
336 where f.f3 = l.f3 COLLATE "POSIX" and l.f1 = 'foo';
338 -- ===================================================================
339 -- test writable foreign table stuff
340 -- ===================================================================
341 EXPLAIN (verbose, costs off)
342 INSERT INTO ft2 (c1,c2,c3) SELECT c1+1000,c2+100, c3 || c3 FROM ft2 LIMIT 20;
343 INSERT INTO ft2 (c1,c2,c3) SELECT c1+1000,c2+100, c3 || c3 FROM ft2 LIMIT 20;
344 INSERT INTO ft2 (c1,c2,c3)
345 VALUES (1101,201,'aaa'), (1102,202,'bbb'), (1103,203,'ccc') RETURNING *;
346 INSERT INTO ft2 (c1,c2,c3) VALUES (1104,204,'ddd'), (1105,205,'eee');
347 UPDATE ft2 SET c2 = c2 + 300, c3 = c3 || '_update3' WHERE c1 % 10 = 3;
348 UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING *;
349 EXPLAIN (verbose, costs off)
350 UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT
351 FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9;
352 UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT
353 FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9;
354 EXPLAIN (verbose, costs off)
355 DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4;
356 DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4;
357 EXPLAIN (verbose, costs off)
358 DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2;
359 DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2;
360 SELECT c1,c2,c3,c4 FROM ft2 ORDER BY c1;
362 -- Test that trigger on remote table works as expected
363 CREATE OR REPLACE FUNCTION "S 1".F_BRTRIG() RETURNS trigger AS $$
365 NEW.c3 = NEW.c3 || '_trig_update';
369 CREATE TRIGGER t1_br_insert BEFORE INSERT OR UPDATE
370 ON "S 1"."T 1" FOR EACH ROW EXECUTE PROCEDURE "S 1".F_BRTRIG();
372 INSERT INTO ft2 (c1,c2,c3) VALUES (1208, 818, 'fff') RETURNING *;
373 INSERT INTO ft2 (c1,c2,c3,c6) VALUES (1218, 818, 'ggg', '(--;') RETURNING *;
374 UPDATE ft2 SET c2 = c2 + 600 WHERE c1 % 10 = 8 AND c1 < 1200 RETURNING *;
376 -- Test errors thrown on remote side during update
377 ALTER TABLE "S 1"."T 1" ADD CONSTRAINT c2positive CHECK (c2 >= 0);
379 INSERT INTO ft1(c1, c2) VALUES(11, 12); -- duplicate key
380 INSERT INTO ft1(c1, c2) VALUES(11, 12) ON CONFLICT DO NOTHING; -- works
381 INSERT INTO ft1(c1, c2) VALUES(11, 12) ON CONFLICT (c1, c2) DO NOTHING; -- unsupported
382 INSERT INTO ft1(c1, c2) VALUES(11, 12) ON CONFLICT (c1, c2) DO UPDATE SET c3 = 'ffg'; -- unsupported
383 INSERT INTO ft1(c1, c2) VALUES(1111, -2); -- c2positive
384 UPDATE ft1 SET c2 = -c2 WHERE c1 = 1; -- c2positive
386 -- Test savepoint/rollback behavior
387 select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
388 select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1;
390 update ft2 set c2 = 42 where c2 = 0;
391 select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
393 update ft2 set c2 = 44 where c2 = 4;
394 select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
395 release savepoint s1;
396 select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
398 update ft2 set c2 = 46 where c2 = 6;
399 select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
400 rollback to savepoint s2;
401 select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
402 release savepoint s2;
403 select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
405 update ft2 set c2 = -2 where c2 = 42 and c1 = 10; -- fail on remote side
406 rollback to savepoint s3;
407 select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
408 release savepoint s3;
409 select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
410 -- none of the above is committed yet remotely
411 select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1;
413 select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
414 select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1;
416 -- ===================================================================
417 -- test check constraints
418 -- ===================================================================
420 -- Consistent check constraints provide consistent results
421 ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c2positive CHECK (c2 >= 0);
422 EXPLAIN (VERBOSE, COSTS false) SELECT count(*) FROM ft1 WHERE c2 < 0;
423 SELECT count(*) FROM ft1 WHERE c2 < 0;
424 SET constraint_exclusion = 'on';
425 EXPLAIN (VERBOSE, COSTS false) SELECT count(*) FROM ft1 WHERE c2 < 0;
426 SELECT count(*) FROM ft1 WHERE c2 < 0;
427 RESET constraint_exclusion;
428 -- check constraint is enforced on the remote side, not locally
429 INSERT INTO ft1(c1, c2) VALUES(1111, -2); -- c2positive
430 UPDATE ft1 SET c2 = -c2 WHERE c1 = 1; -- c2positive
431 ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c2positive;
433 -- But inconsistent check constraints provide inconsistent results
434 ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c2negative CHECK (c2 < 0);
435 EXPLAIN (VERBOSE, COSTS false) SELECT count(*) FROM ft1 WHERE c2 >= 0;
436 SELECT count(*) FROM ft1 WHERE c2 >= 0;
437 SET constraint_exclusion = 'on';
438 EXPLAIN (VERBOSE, COSTS false) SELECT count(*) FROM ft1 WHERE c2 >= 0;
439 SELECT count(*) FROM ft1 WHERE c2 >= 0;
440 RESET constraint_exclusion;
441 -- local check constraint is not actually enforced
442 INSERT INTO ft1(c1, c2) VALUES(1111, 2);
443 UPDATE ft1 SET c2 = c2 + 1 WHERE c1 = 1;
444 ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c2negative;
446 -- ===================================================================
447 -- test serial columns (ie, sequence-based defaults)
448 -- ===================================================================
449 create table loc1 (f1 serial, f2 text);
450 create foreign table rem1 (f1 serial, f2 text)
451 server loopback options(table_name 'loc1');
452 select pg_catalog.setval('rem1_f1_seq', 10, false);
453 insert into loc1(f2) values('hi');
454 insert into rem1(f2) values('hi remote');
455 insert into loc1(f2) values('bye');
456 insert into rem1(f2) values('bye remote');
460 -- ===================================================================
461 -- test local triggers
462 -- ===================================================================
464 -- Trigger functions "borrowed" from triggers regress test.
465 CREATE FUNCTION trigger_func() RETURNS trigger LANGUAGE plpgsql AS $$
467 RAISE NOTICE 'trigger_func(%) called: action = %, when = %, level = %',
468 TG_ARGV[0], TG_OP, TG_WHEN, TG_LEVEL;
472 CREATE TRIGGER trig_stmt_before BEFORE DELETE OR INSERT OR UPDATE ON rem1
473 FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
474 CREATE TRIGGER trig_stmt_after AFTER DELETE OR INSERT OR UPDATE ON rem1
475 FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
477 CREATE OR REPLACE FUNCTION trigger_data() RETURNS trigger
478 LANGUAGE plpgsql AS $$
486 relid := TG_relid::regclass;
488 for i in 0 .. TG_nargs - 1 loop
490 argstr := argstr || ', ';
492 argstr := argstr || TG_argv[i];
495 RAISE NOTICE '%(%) % % % ON %',
496 tg_name, argstr, TG_when, TG_level, TG_OP, relid;
497 oldnew := '{}'::text[];
498 if TG_OP != 'INSERT' then
499 oldnew := array_append(oldnew, format('OLD: %s', OLD));
502 if TG_OP != 'DELETE' then
503 oldnew := array_append(oldnew, format('NEW: %s', NEW));
506 RAISE NOTICE '%', array_to_string(oldnew, ',');
508 if TG_OP = 'DELETE' then
516 -- Test basic functionality
517 CREATE TRIGGER trig_row_before
518 BEFORE INSERT OR UPDATE OR DELETE ON rem1
519 FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
521 CREATE TRIGGER trig_row_after
522 AFTER INSERT OR UPDATE OR DELETE ON rem1
523 FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
526 insert into rem1 values(1,'insert');
527 update rem1 set f2 = 'update' where f1 = 1;
528 update rem1 set f2 = f2 || f2;
532 DROP TRIGGER trig_row_before ON rem1;
533 DROP TRIGGER trig_row_after ON rem1;
534 DROP TRIGGER trig_stmt_before ON rem1;
535 DROP TRIGGER trig_stmt_after ON rem1;
540 -- Test WHEN conditions
542 CREATE TRIGGER trig_row_before_insupd
543 BEFORE INSERT OR UPDATE ON rem1
545 WHEN (NEW.f2 like '%update%')
546 EXECUTE PROCEDURE trigger_data(23,'skidoo');
548 CREATE TRIGGER trig_row_after_insupd
549 AFTER INSERT OR UPDATE ON rem1
551 WHEN (NEW.f2 like '%update%')
552 EXECUTE PROCEDURE trigger_data(23,'skidoo');
554 -- Insert or update not matching: nothing happens
555 INSERT INTO rem1 values(1, 'insert');
556 UPDATE rem1 set f2 = 'test';
558 -- Insert or update matching: triggers are fired
559 INSERT INTO rem1 values(2, 'update');
560 UPDATE rem1 set f2 = 'update update' where f1 = '2';
562 CREATE TRIGGER trig_row_before_delete
563 BEFORE DELETE ON rem1
565 WHEN (OLD.f2 like '%update%')
566 EXECUTE PROCEDURE trigger_data(23,'skidoo');
568 CREATE TRIGGER trig_row_after_delete
571 WHEN (OLD.f2 like '%update%')
572 EXECUTE PROCEDURE trigger_data(23,'skidoo');
574 -- Trigger is fired for f1=2, not for f1=1
578 DROP TRIGGER trig_row_before_insupd ON rem1;
579 DROP TRIGGER trig_row_after_insupd ON rem1;
580 DROP TRIGGER trig_row_before_delete ON rem1;
581 DROP TRIGGER trig_row_after_delete ON rem1;
584 -- Test various RETURN statements in BEFORE triggers.
586 CREATE FUNCTION trig_row_before_insupdate() RETURNS TRIGGER AS $$
588 NEW.f2 := NEW.f2 || ' triggered !';
593 CREATE TRIGGER trig_row_before_insupd
594 BEFORE INSERT OR UPDATE ON rem1
595 FOR EACH ROW EXECUTE PROCEDURE trig_row_before_insupdate();
597 -- The new values should have 'triggered' appended
598 INSERT INTO rem1 values(1, 'insert');
600 INSERT INTO rem1 values(2, 'insert') RETURNING f2;
602 UPDATE rem1 set f2 = '';
604 UPDATE rem1 set f2 = 'skidoo' RETURNING f2;
609 -- Add a second trigger, to check that the changes are propagated correctly
610 -- from trigger to trigger
611 CREATE TRIGGER trig_row_before_insupd2
612 BEFORE INSERT OR UPDATE ON rem1
613 FOR EACH ROW EXECUTE PROCEDURE trig_row_before_insupdate();
615 INSERT INTO rem1 values(1, 'insert');
617 INSERT INTO rem1 values(2, 'insert') RETURNING f2;
619 UPDATE rem1 set f2 = '';
621 UPDATE rem1 set f2 = 'skidoo' RETURNING f2;
624 DROP TRIGGER trig_row_before_insupd ON rem1;
625 DROP TRIGGER trig_row_before_insupd2 ON rem1;
629 INSERT INTO rem1 VALUES (1, 'test');
631 -- Test with a trigger returning NULL
632 CREATE FUNCTION trig_null() RETURNS TRIGGER AS $$
638 CREATE TRIGGER trig_null
639 BEFORE INSERT OR UPDATE OR DELETE ON rem1
640 FOR EACH ROW EXECUTE PROCEDURE trig_null();
642 -- Nothing should have changed.
643 INSERT INTO rem1 VALUES (2, 'test2');
647 UPDATE rem1 SET f2 = 'test2';
655 DROP TRIGGER trig_null ON rem1;
658 -- Test a combination of local and remote triggers
659 CREATE TRIGGER trig_row_before
660 BEFORE INSERT OR UPDATE OR DELETE ON rem1
661 FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
663 CREATE TRIGGER trig_row_after
664 AFTER INSERT OR UPDATE OR DELETE ON rem1
665 FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
667 CREATE TRIGGER trig_local_before BEFORE INSERT OR UPDATE ON loc1
668 FOR EACH ROW EXECUTE PROCEDURE trig_row_before_insupdate();
670 INSERT INTO rem1(f2) VALUES ('test');
671 UPDATE rem1 SET f2 = 'testo';
673 -- Test returning a system attribute
674 INSERT INTO rem1(f2) VALUES ('test') RETURNING ctid;
676 -- ===================================================================
677 -- test inheritance features
678 -- ===================================================================
680 CREATE TABLE a (aa TEXT);
681 CREATE TABLE loct (aa TEXT, bb TEXT);
682 CREATE FOREIGN TABLE b (bb TEXT) INHERITS (a)
683 SERVER loopback OPTIONS (table_name 'loct');
685 INSERT INTO a(aa) VALUES('aaa');
686 INSERT INTO a(aa) VALUES('aaaa');
687 INSERT INTO a(aa) VALUES('aaaaa');
689 INSERT INTO b(aa) VALUES('bbb');
690 INSERT INTO b(aa) VALUES('bbbb');
691 INSERT INTO b(aa) VALUES('bbbbb');
693 SELECT tableoid::regclass, * FROM a;
694 SELECT tableoid::regclass, * FROM b;
695 SELECT tableoid::regclass, * FROM ONLY a;
697 UPDATE a SET aa = 'zzzzzz' WHERE aa LIKE 'aaaa%';
699 SELECT tableoid::regclass, * FROM a;
700 SELECT tableoid::regclass, * FROM b;
701 SELECT tableoid::regclass, * FROM ONLY a;
703 UPDATE b SET aa = 'new';
705 SELECT tableoid::regclass, * FROM a;
706 SELECT tableoid::regclass, * FROM b;
707 SELECT tableoid::regclass, * FROM ONLY a;
709 UPDATE a SET aa = 'newtoo';
711 SELECT tableoid::regclass, * FROM a;
712 SELECT tableoid::regclass, * FROM b;
713 SELECT tableoid::regclass, * FROM ONLY a;
717 SELECT tableoid::regclass, * FROM a;
718 SELECT tableoid::regclass, * FROM b;
719 SELECT tableoid::regclass, * FROM ONLY a;
721 DROP TABLE a CASCADE;
724 -- Check SELECT FOR UPDATE/SHARE with an inherited source table
725 create table loct1 (f1 int, f2 int, f3 int);
726 create table loct2 (f1 int, f2 int, f3 int);
728 create table foo (f1 int, f2 int);
729 create foreign table foo2 (f3 int) inherits (foo)
730 server loopback options (table_name 'loct1');
731 create table bar (f1 int, f2 int);
732 create foreign table bar2 (f3 int) inherits (bar)
733 server loopback options (table_name 'loct2');
735 insert into foo values(1,1);
736 insert into foo values(3,3);
737 insert into foo2 values(2,2,2);
738 insert into foo2 values(4,4,4);
739 insert into bar values(1,11);
740 insert into bar values(2,22);
741 insert into bar values(6,66);
742 insert into bar2 values(3,33,33);
743 insert into bar2 values(4,44,44);
744 insert into bar2 values(7,77,77);
746 explain (verbose, costs off)
747 select * from bar where f1 in (select f1 from foo) for update;
748 select * from bar where f1 in (select f1 from foo) for update;
750 explain (verbose, costs off)
751 select * from bar where f1 in (select f1 from foo) for share;
752 select * from bar where f1 in (select f1 from foo) for share;
754 -- Check UPDATE with inherited target and an inherited source table
755 explain (verbose, costs off)
756 update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
757 update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
759 select tableoid::regclass, * from bar order by 1,2;
761 -- Check UPDATE with inherited target and an appendrel subquery
762 explain (verbose, costs off)
763 update bar set f2 = f2 + 100
765 ( select f1 from foo union all select f1+3 from foo ) ss
766 where bar.f1 = ss.f1;
767 update bar set f2 = f2 + 100
769 ( select f1 from foo union all select f1+3 from foo ) ss
770 where bar.f1 = ss.f1;
772 select tableoid::regclass, * from bar order by 1,2;
774 -- Test that WHERE CURRENT OF is not supported
776 declare c cursor for select * from bar where f1 = 7;
778 update bar set f2 = null where current of c;
781 drop table foo cascade;
782 drop table bar cascade;
786 -- ===================================================================
787 -- test IMPORT FOREIGN SCHEMA
788 -- ===================================================================
790 CREATE SCHEMA import_source;
791 CREATE TABLE import_source.t1 (c1 int, c2 varchar NOT NULL);
792 CREATE TABLE import_source.t2 (c1 int default 42, c2 varchar NULL, c3 text collate "POSIX");
793 CREATE TYPE typ1 AS (m1 int, m2 varchar);
794 CREATE TABLE import_source.t3 (c1 timestamptz default now(), c2 typ1);
795 CREATE TABLE import_source."x 4" (c1 float8, "C 2" text, c3 varchar(42));
796 CREATE TABLE import_source."x 5" (c1 float8);
797 ALTER TABLE import_source."x 5" DROP COLUMN c1;
799 CREATE SCHEMA import_dest1;
800 IMPORT FOREIGN SCHEMA import_source FROM SERVER loopback INTO import_dest1;
805 CREATE SCHEMA import_dest2;
806 IMPORT FOREIGN SCHEMA import_source FROM SERVER loopback INTO import_dest2
807 OPTIONS (import_default 'true');
810 CREATE SCHEMA import_dest3;
811 IMPORT FOREIGN SCHEMA import_source FROM SERVER loopback INTO import_dest3
812 OPTIONS (import_collate 'false', import_not_null 'false');
816 -- Check LIMIT TO and EXCEPT
817 CREATE SCHEMA import_dest4;
818 IMPORT FOREIGN SCHEMA import_source LIMIT TO (t1, nonesuch)
819 FROM SERVER loopback INTO import_dest4;
821 IMPORT FOREIGN SCHEMA import_source EXCEPT (t1, "x 4", nonesuch)
822 FROM SERVER loopback INTO import_dest4;
825 -- Assorted error cases
826 IMPORT FOREIGN SCHEMA import_source FROM SERVER loopback INTO import_dest4;
827 IMPORT FOREIGN SCHEMA nonesuch FROM SERVER loopback INTO import_dest4;
828 IMPORT FOREIGN SCHEMA nonesuch FROM SERVER loopback INTO notthere;
829 IMPORT FOREIGN SCHEMA nonesuch FROM SERVER nowhere INTO notthere;
831 -- Check case of a type present only on the remote server.
832 -- We can fake this by dropping the type locally in our transaction.
833 CREATE TYPE "Colors" AS ENUM ('red', 'green', 'blue');
834 CREATE TABLE import_source.t5 (c1 int, c2 text collate "C", "Col" "Colors");
836 CREATE SCHEMA import_dest5;
838 DROP TYPE "Colors" CASCADE;
839 IMPORT FOREIGN SCHEMA import_source LIMIT TO (t5)
840 FROM SERVER loopback INTO import_dest5; -- ERROR