From b518b083ccf16775e1674e7a7763fd5ebd1b270a Mon Sep 17 00:00:00 2001 From: Bernd Helmle Date: Fri, 11 Aug 2023 11:54:33 +0000 Subject: [PATCH] Squash commits of devel branch DEV_0_6_0 in preparation for release REL0_6_0. commit 0357237df15ffe16e4a73ca5835143d1973afa63 Author: Bernd Helmle Date: Sat Aug 5 14:10:03 2023 +0000 Follow up fix for issue https://github.com/credativ/informix_fdw/issues/31. Commit 285119f024a0137ba84a3fc7b825b9e2ccf107bf partially fixed our architecture thinko regarding query planning, but it turned out that we have the same issue in ifxGetForeignRelSize(). The former idea was to cache all informix objects created during the planning phase for execution later, since we rely on query statistics by using the query descriptors. We created them in ifxGetForeignRelSize() and reused these objects in ifxPlanForeignModify() later. But ifxGetForeignRelSize() isn't necessarily called, so ifxPlanForeignModify() needs to take care whether the objects are usable directly or still need to be created. I just copied the code from ifxGetForeignRelSize() without moving it into a dedicated function, which fixes the problem. It might be more pretty to have it in a dedicated function, but leave it as-is for now. Regression tests are extended to have trigger tests as described in issue #31. commit db73ab94c94d77d3d56590933c7a3dd1c66d1f5a Author: Bernd Helmle Date: Mon Jul 24 16:37:43 2023 +0000 Fix longstanding bug in issue #31. The issue describe here httpg://github.com/credativ/informix_fdw/issues/31 uncovered an unfortunate mistake in our approach on how we plan a modify action. Formerly, ifxPlanForeignModify() was also responsible to create and execute the necessary query objects on the remote Informix server (like describing the statement and thus creating the required SQLDA structure). In this issue above it turned out that we are far too optimistic in doing this preparation work at this early stage: ifxPlanForeignModify() might not be called once a prepared statement settles the execution plan and thus we miss important things like creating and describing the statement handles. To fix this, we need to delay creating of query objects on the remote Informix server to ifxBeginForeignModify(), which is now responsible to create and describe the statement. ifxPlanForeignModify() still creates all required identifiers for query objects (like cursor, statement handle, ...), but this leaves us after the plan settled with a static reference counter (refid) used in the identifiers. I think this isn't a problem here, since afaics the refid can't be reused somewhere else at this point. This might be wrong, but since we pass regression tests at this point i leave it for now. commit 3fdfab57056c454738c99279f1d9dbfad5e9c0e0 Author: Bernd Helmle Date: Thu Jul 20 16:41:31 2023 +0000 Update and correct README. commit b2798e3f4866c851dc20dc4d0109d66f58dda3dc Author: Bernd Helmle Date: Thu Jul 20 16:07:41 2023 +0000 Fix copy&paste error. commit 67eed75976efa40f17b5452b373fb732ed088c11 Author: Bernd Helmle Date: Thu Jul 20 15:56:46 2023 +0000 Follow up commit for backward compatibility. HASH_STRINGS is available as dynahash flag beginning with PG14, so handle it accordingly. commit ab2b38f5fd714acced49c9c6bac2a462a977d76d Author: Bernd Helmle Date: Thu Jul 20 10:21:08 2023 +0000 Fix compile issues caused by changes made for PG16 support. commit 1f71169f36d121260ec4b27c106d54c04ca4e24a Author: Bernd Helmle Date: Thu Jul 20 09:59:12 2023 +0000 Support PostgreSQL 16. Including commit 76a08e2e735584131e383c24e5bcdf3ff34da5e8 this introduces support for the upcoming new major release 16 of PostgreSQL. Commit a61b1f74823c9c4f79c95226a461f1e7a367764b reworked the relation permission checking infrastructure in PostgreSQL which forces us to cope with them properly. No new features yet and needs testing against all supported older major releases (as of this commit these are 10, 11, 12, 13, 14, 15). commit fc137b2c258ecc21cc1290da33417b04780abf5f Author: Bernd Helmle Date: Thu Jul 20 09:56:01 2023 +0000 Make HASH_STRINGS in connection cache explicit. Starting with PostgreSQL 16 dynahash's implementation forces explicit usage of HASH_STRINGS when creating the hash table we use for storing Informix connection handles. Since the lookup key is the connection identifier composed by strings up to IFX_CONNAME_LEN, we must set HASH_STRINGS accordingly, otherwise we run into an assertion failure. --- README | 53 ++++----- expected/informix_fdw_tx_1.out | 182 +++++++++++++++++++++++++++++ ifx_conncache.c | 10 +- ifx_fdw.c | 157 +++++++++++++++++++------ sql/informix_fdw_tx.sql | 201 +++++++++++++++++++++++++++++++++ 5 files changed, 539 insertions(+), 64 deletions(-) diff --git a/README b/README index 78ba5ac..408be25 100644 --- a/README +++ b/README @@ -121,44 +121,35 @@ your Client SDK installation (e.g. $INFORMIXDIR/etc/sqlhosts and the Informix FDW can be used as follows, assumed you have an Informix connection named 'centosifx_tcp': +```sql CREATE EXTENSION informix_fdw; -CREATE SERVER centosifx_tcp +CREATE SERVER test_server FOREIGN DATA WRAPPER informix_fdw -OPTIONS (informixserver 'centosifx_tcp'); +OPTIONS (informixserver 'informix', informixdir '/opt/informix/csdk'); CREATE USER MAPPING FOR CURRENT_USER -SERVER centosifx_tcp +SERVER test_server OPTIONS (username 'informix', password 'informix'); -CREATE FOREIGN TABLE foo ( - id integer, - value integer - ) -SERVER centosifx_tcp -OPTIONS ( query 'SELECT * FROM foo', - database 'test', - informixdir '/Applications/IBM/informix'); - -CREATE SERVER sles11_tcp -FOREIGN DATA WRAPPER informix_fdw -OPTIONS (informixserver 'ol_informix1170'); - -CREATE USER MAPPING FOR CURRENT_USER -SERVER sles11_tcp -OPTIONS (username 'informix', password 'informix'); - -CREATE FOREIGN TABLE foo ( - id integer, - value integer - ) -SERVER sles11_tcp -OPTIONS ( query 'SELECT * FROM foo', - database 'test', - db_locale 'en_us.819', - client_locale 'en_US.utf8', - informixserver 'ol_informix1170', - informixdir '/Applications/IBM/informix'); +CREATE FOREIGN TABLE inttest(f1 bigint not null, + f2 integer, + f3 smallint) +SERVER test_server +OPTIONS(table 'inttest', + client_locale 'en_US.utf8' + db_locale 'en_US.819', + database 'regression_dml'); +```sql + +The settings `informixdir` and `informixserver` are dependent on your +installation and configuration. `db_locale` and `client_locale` must be +specified to enable correct conversion between foreign Informix and local PostgreSQL server. + +`table` identifies the table on the remote Informix server you want to access. There +is also the `query` parameter in the example above, which can be used to access +the remote data in a view-like manner. For more detailed information about options +read the FDW Options section below. = Supported datatypes = diff --git a/expected/informix_fdw_tx_1.out b/expected/informix_fdw_tx_1.out index ddf99f9..72c5ac5 100644 --- a/expected/informix_fdw_tx_1.out +++ b/expected/informix_fdw_tx_1.out @@ -1219,6 +1219,188 @@ COMMIT; -- ALTER FOREIGN TABLE inttest OPTIONS(DROP disable_rowid); -------------------------------------------------------------------------------- +-- Tests for PREPARE +-- +-- See discussion in github issue +-- https://github.com/credativ/informix_fdw/issues/31 +-------------------------------------------------------------------------------- +-- +-- INSERT +-- +BEGIN; +PREPARE ins_inttest(bigint) AS INSERT INTO inttest VALUES($1); +EXECUTE ins_inttest (1); +EXECUTE ins_inttest (2); +EXECUTE ins_inttest (3); +EXECUTE ins_inttest (4); +EXECUTE ins_inttest (5); +EXECUTE ins_inttest (6); +EXECUTE ins_inttest (7); +COMMIT; +DEALLOCATE ins_inttest; +-- +-- UPDATE +-- +BEGIN; +PREPARE upd_inttest(bigint) AS UPDATE inttest SET f1 = f1 WHERE f1 = $1; +EXECUTE upd_inttest (1); +EXECUTE upd_inttest (2); +EXECUTE upd_inttest (3); +EXECUTE upd_inttest (4); +EXECUTE upd_inttest (5); +EXECUTE upd_inttest (6); +EXECUTE upd_inttest (7); +COMMIT; +DEALLOCATE upd_inttest; +-- +-- DELETE +-- +BEGIN; +PREPARE del_inttest(bigint) AS DELETE FROM inttest WHERE f1 = $1; +EXECUTE del_inttest (1); +EXECUTE del_inttest (2); +EXECUTE del_inttest (3); +EXECUTE del_inttest (4); +EXECUTE del_inttest (5); +EXECUTE del_inttest (6); +EXECUTE del_inttest (7); +COMMIT; +DEALLOCATE del_inttest; +-------------------------------------------------------------------------------- +-- Trigger Tests +-------------------------------------------------------------------------------- +BEGIN; +CREATE TABLE IF NOT EXISTS delete_fdw_trigger_test(id bigint primary key); +-- +-- A before trigger testing before actions on INSERT/UPDATE/DELETE +-- on a foreign table +-- +CREATE OR REPLACE FUNCTION f_tg_test() +RETURNS trigger +LANGUAGE plpgsql +AS +$$ +BEGIN + IF TG_OP = 'DELETE' THEN + DELETE FROM inttest WHERE f1 = OLD.id; + RETURN OLD; + ELSIF TG_OP = 'UPDATE' THEN + UPDATE inttest SET f1 = NEW.id WHERE f1 = OLD.id; + RETURN NEW; + ELSIF TG_OP = 'INSERT' THEN + INSERT INTO inttest VALUES(NEW.id); + RETURN NEW; + ELSE + RAISE EXCEPTION 'unhandled trigger action %', TG_OP; + END IF; +END; +$$; +-- +-- A broken trigger function referencing the wrong tuple identifiers +-- according to the trigger action (NEW vs. OLD) +-- +-- Basically the same as above. +-- +CREATE OR REPLACE FUNCTION f_tg_test_broken() +RETURNS trigger +LANGUAGE plpgsql +AS +$$ +BEGIN + IF TG_OP = 'DELETE' THEN + DELETE FROM inttest WHERE f1 = NEW.id; + RETURN OLD; + ELSIF TG_OP = 'UPDATE' THEN + UPDATE inttest SET f1 = NEW.id WHERE f1 = OLD.id; + RETURN NEW; + ELSIF TG_OP = 'INSERT' THEN + INSERT INTO inttest VALUES(OLD.id); + RETURN NEW; + ELSE + RAISE EXCEPTION 'unhandled trigger action %', TG_OP; + END IF; +END; +$$; +CREATE TRIGGER tg_inttest +BEFORE DELETE OR UPDATE OR INSERT ON delete_fdw_trigger_test +FOR EACH ROW EXECUTE PROCEDURE f_tg_test(); +TRUNCATE delete_fdw_trigger_test; +INSERT INTO delete_fdw_trigger_test VALUES(1), (2), (3); +SELECT * FROM inttest; + f1 | f2 | f3 +----+----+---- + 1 | | + 2 | | + 3 | | +(3 rows) + +DELETE FROM delete_fdw_trigger_test WHERE id = 2; +SELECT * FROM inttest; + f1 | f2 | f3 +----+----+---- + 1 | | + 3 | | +(2 rows) + +UPDATE delete_fdw_trigger_test SET id = 4 WHERE id = 3; +SELECT * FROM inttest; + f1 | f2 | f3 +----+----+---- + 1 | | + 4 | | +(2 rows) + +INSERT INTO delete_fdw_trigger_test VALUES(5); +SELECT * FROM inttest; + f1 | f2 | f3 +----+----+---- + 1 | | + 4 | | + 5 | | +(3 rows) + +DELETE FROM delete_fdw_trigger_test; +SELECT * FROM inttest; + f1 | f2 | f3 +----+----+---- +(0 rows) + +DROP TRIGGER tg_inttest ON delete_fdw_trigger_test; +CREATE TRIGGER tg_inttest +BEFORE DELETE OR UPDATE OR INSERT ON delete_fdw_trigger_test +FOR EACH ROW EXECUTE PROCEDURE f_tg_test_broken(); +-- should fail +SAVEPOINT broken; +INSERT INTO delete_fdw_trigger_test VALUES(1), (2), (3); +ROLLBACK TO broken; +SELECT * FROM inttest; + f1 | f2 | f3 +----+----+---- +(0 rows) + +-- should delete nothing +DELETE FROM delete_fdw_trigger_test WHERE id = 2; +SELECT * FROM inttest; + f1 | f2 | f3 +----+----+---- +(0 rows) + +-- should update nothing +UPDATE delete_fdw_trigger_test SET id = 4 WHERE id = 3; +SELECT * FROM inttest; + f1 | f2 | f3 +----+----+---- +(0 rows) + +DELETE FROM delete_fdw_trigger_test; +SELECT * FROM inttest; + f1 | f2 | f3 +----+----+---- +(0 rows) + +DROP TRIGGER tg_inttest ON delete_fdw_trigger_test; +COMMIT; +-------------------------------------------------------------------------------- -- Regression Tests End, Cleanup -------------------------------------------------------------------------------- DROP FOREIGN TABLE float_test; diff --git a/ifx_conncache.c b/ifx_conncache.c index 61e40d8..77c436d 100644 --- a/ifx_conncache.c +++ b/ifx_conncache.c @@ -25,6 +25,14 @@ */ #define IFX_CONNCACHE_HASHTABLE "IFX_CONN_CACHE" +/* + * Flags for dynahash hashtable, version dependent + */ +#if PG_VERSION_NUM >= 140000 +#define IFX_FDW_CONNCACHE_HASH_FLAGS HASH_ELEM | HASH_CONTEXT | HASH_STRINGS +#else +#define IFX_FDW_CONNCACHE_HASH_FLAGS HASH_ELEM | HASH_CONTEXT +#endif static void ifxConnCache_init(void); bool IfxCacheIsInitialized; @@ -64,7 +72,7 @@ static void ifxConnCache_init() ifxCache.connections = hash_create(IFX_CONNCACHE_HASHTABLE, IFX_CONNCACHE_SIZE, &hash_ctl, - HASH_ELEM | HASH_CONTEXT); + IFX_FDW_CONNCACHE_HASH_FLAGS); /* * Back to old context. diff --git a/ifx_fdw.c b/ifx_fdw.c index b549710..033db6f 100644 --- a/ifx_fdw.c +++ b/ifx_fdw.c @@ -32,6 +32,14 @@ #include "optimizer/appendinfo.h" #endif +/* + * For REL_16_STABLE, as of commit a61b1f74823 we need optimizer/inherit.h + * for get_rel_all_updated_cols(). + */ +#if PG_VERSION_NUM >= 160000 +#include "optimizer/inherit.h" +#endif + #include "access/xact.h" #include "utils/lsyscache.h" @@ -136,10 +144,21 @@ extern PGDLLIMPORT double cpu_tuple_cost; #define IFX_SYSTABLE_SCAN_SNAPSHOT NULL #endif -#if PG_VERSION_NUM >= 90500 -#define RTE_UPDATED_COLS(a) (a)->updatedCols +#if PG_VERSION_NUM >= 90500 && PG_VERSION_NUM < 160000 +#define RTE_UPDATED_COLS(planInfo, resultRel, set) \ + RangeTblEntry *rte = planner_rt_fetch((resultRel), (planInfo)); \ + (set) = bms_copy(rte->updatedCols); +#define BMS_LOOKUP_COL(set, attnum) bms_first_member((set)) +#elif PG_VERSION_NUM >= 160000 +#define RTE_UPDATED_COLS(planInfo, resultRel, set) \ + RelOptInfo *relInfo = find_base_rel((planInfo), (resultRel)); \ + (set) = get_rel_all_updated_cols((planInfo), (relInfo)); +#define BMS_LOOKUP_COL(set, attnum) bms_next_member((set), (attnum)) #else -#define RTE_UPDATED_COLS(a) (a)->modifiedCols +#define RTE_UPDATED_COLS(planInfo, resultRel, set) \ + RangeTblEntry *rte = planner_rt_fetch((resultRel), (planInfo)); \ + (set) = bms_copy(rte->modifiedCols); +#define BMS_LOOKUP_COL(set, attnum) bms_first_member((set)) #endif /* @@ -1434,12 +1453,82 @@ ifxPlanForeignModify(PlannerInfo *root, RelOptInfo *relInfo = root->simple_rel_array[resultRelation]; IfxCachedConnection *cached; IfxFdwExecutionState *scan_state; + IfxFdwPlanState *planState; /* - * Extract the state of the foreign scan. + * Check if there is a foreign scan state present. This would + * already have initialized all required objects on the foreign + * Informix server. + * + * In normal cases, ifxGetForeignRelSize() should already have + * initialized all required objects here, since we use the scan + * state to retrieve optimizer stats from Informix to get estimates + * based on the query back from the Informix server. + * + * This is not always true, so we need to check whether a foreign scan + * was already initialized or not (e.g. in the prepared statement + * case). Check if a private execution state was properly initialized, + * and if not, execute the required steps to initiate one ourselves. */ - scan_state = (IfxFdwExecutionState *) - ((IfxFdwPlanState *)relInfo->fdw_private)->state; + if (relInfo->fdw_private == NULL) { + + planState = palloc(sizeof(IfxFdwPlanState)); + + /* + * Establish remote informix connection or get + * a already cached connection from the informix connection + * cache. + */ + ifxSetupFdwScan(&coninfo, &scan_state, &plan_values, + rte->relid, IFX_PLAN_SCAN); + + /* + * Check for predicates that can be pushed down + * to the informix server, but skip it in case the user + * has set the disable_predicate_pushdown option... + */ + if (coninfo->predicate_pushdown) + { + /* + * Also save a list of excluded RestrictInfo structures not carrying any + * predicate found to be pushed down by ifxFilterQuals(). Those will + * passed later to ifxGetForeignPlan()... + */ + scan_state->stmt_info.predicate = ifxFilterQuals(root, relInfo, + &(planState->excl_restrictInfo), + rte->relid); + elog(DEBUG2, "predicate for pushdown: %s", scan_state->stmt_info.predicate); + } + else + { + elog(DEBUG2, "predicate pushdown disabled"); + scan_state->stmt_info.predicate = ""; + } + + /* + * Establish the remote query on the informix server. + * + * If we have an UPDATE or DELETE query, the foreign scan needs to + * employ an FOR UPDATE cursor, since we are going to reuse it + * during modify. + */ + if ((root->parse->commandType == CMD_UPDATE) + || (root->parse->commandType == CMD_DELETE)) + { + scan_state->stmt_info.cursorUsage = IFX_UPDATE_CURSOR; + } + + ifxPrepareScan(coninfo, scan_state); + + } else { + + /* + * Extract the state of the foreign scan. + */ + scan_state = (IfxFdwExecutionState *) + ((IfxFdwPlanState *)relInfo->fdw_private)->state; + + } /* * Don't reuse the connection info from the scan state, @@ -1490,8 +1579,23 @@ ifxPlanForeignModify(PlannerInfo *root, * cursor here feeded with the new values during ifxExecForeignInsert(). */ ifxSetupFdwScan(&coninfo, &state, &plan_values, rte->relid, IFX_PLAN_SCAN); + + /* + * ...don't forget the cursor name. + */ + state->stmt_info.cursor_name = ifxGenCursorName(state->stmt_info.refid); } + /* + * Check wether this foreign table has AFTER EACH ROW + * triggers attached. Currently this information is just + * for completeness, since we always include all columns + * in a foreign scan. + */ + ifxCheckForAfterRowTriggers(rte->relid, + state, + root->parse->commandType); + /* Sanity check, should not happen */ Assert((state != NULL) && (coninfo != NULL)); @@ -1519,9 +1623,10 @@ ifxPlanForeignModify(PlannerInfo *root, } /* - * Prepare and describe the statement. + * Generate a statement name for execution later. + * This is an unique statement identifier. */ - ifxPrepareModifyQuery(&state->stmt_info, coninfo, operation); + state->stmt_info.stmt_name = ifxGenStatementName(state->stmt_info.refid); /* * Serialize all required plan data for use in executor later. @@ -1542,11 +1647,6 @@ static void ifxPrepareModifyQuery(IfxStatementInfo *info, IfxConnectionInfo *coninfo, CmdType operation) { - /* - * Unique statement identifier. - */ - info->stmt_name = ifxGenStatementName(info->refid); - /* * Prepare the query. */ @@ -1560,11 +1660,6 @@ static void ifxPrepareModifyQuery(IfxStatementInfo *info, */ if (operation == CMD_INSERT) { - /* - * ...don't forget the cursor name. - */ - info->cursor_name = ifxGenCursorName(info->refid); - elog(DEBUG1, "declare cursor \"%s\" for statement \"%s\"", info->cursor_name, info->stmt_name); @@ -1656,6 +1751,11 @@ ifxBeginForeignModify(ModifyTableState *mstate, return; } + /* + * Prepare and describe the statement. + */ + ifxPrepareModifyQuery(&state->stmt_info, coninfo, mstate->operation); + /* * An INSERT action need to do much more preparing work * than UPDATE/DELETE: Since no foreign scan is involved, the @@ -1697,6 +1797,7 @@ ifxBeginForeignModify(ModifyTableState *mstate, else { /* CMD_UPDATE */ + state->stmt_info.descr_name = ifxGenDescrName(state->stmt_info.refid); ifxDescribeStmtInput(&state->stmt_info); } @@ -2043,13 +2144,15 @@ static void ifxPrepareParamsForModify(IfxFdwExecutionState *state, * * Shamelessly stolen from src/contrib/postgres_fdw. */ - RangeTblEntry *rte = planner_rt_fetch(resultRelation, planInfo); - Bitmapset *tmpset = bms_copy(RTE_UPDATED_COLS(rte)); + Bitmapset *tmpset = NULL; + int colnum = -1; AttrNumber col; - while ((col = bms_first_member(tmpset)) >= 0) + RTE_UPDATED_COLS(planInfo, resultRelation, tmpset); + + while ((colnum = BMS_LOOKUP_COL(tmpset, colnum)) >= 0) { - col += FirstLowInvalidHeapAttributeNumber; + col = colnum + FirstLowInvalidHeapAttributeNumber; if (col <= InvalidAttrNumber) /* shouldn't happen */ elog(ERROR, "system-column update is not supported"); state->affectedAttrNums = lappend_int(state->affectedAttrNums, @@ -3255,16 +3358,6 @@ static void ifxGetForeignRelSize(PlannerInfo *planInfo, ifxSetupFdwScan(&coninfo, &state, &plan_values, foreignTableId, IFX_PLAN_SCAN); - /* - * Check wether this foreign table has AFTER EACH ROW - * triggers attached. Currently this information is just - * for completeness, since we always include all columns - * in a foreign scan. - */ - ifxCheckForAfterRowTriggers(foreignTableId, - state, - planInfo->parse->commandType); - /* * Check for predicates that can be pushed down * to the informix server, but skip it in case the user diff --git a/sql/informix_fdw_tx.sql b/sql/informix_fdw_tx.sql index 15e5e47..0ca5b96 100644 --- a/sql/informix_fdw_tx.sql +++ b/sql/informix_fdw_tx.sql @@ -949,6 +949,207 @@ COMMIT; -- ALTER FOREIGN TABLE inttest OPTIONS(DROP disable_rowid); +-------------------------------------------------------------------------------- +-- Tests for PREPARE +-- +-- See discussion in github issue +-- https://github.com/credativ/informix_fdw/issues/31 +-------------------------------------------------------------------------------- + +-- +-- INSERT +-- + +BEGIN; + + +PREPARE ins_inttest(bigint) AS INSERT INTO inttest VALUES($1); + +EXECUTE ins_inttest (1); + +EXECUTE ins_inttest (2); + +EXECUTE ins_inttest (3); + +EXECUTE ins_inttest (4); + +EXECUTE ins_inttest (5); + +EXECUTE ins_inttest (6); + +EXECUTE ins_inttest (7); + +COMMIT; + +DEALLOCATE ins_inttest; + +-- +-- UPDATE +-- + +BEGIN; + +PREPARE upd_inttest(bigint) AS UPDATE inttest SET f1 = f1 WHERE f1 = $1; + +EXECUTE upd_inttest (1); + +EXECUTE upd_inttest (2); + +EXECUTE upd_inttest (3); + +EXECUTE upd_inttest (4); + +EXECUTE upd_inttest (5); + +EXECUTE upd_inttest (6); + +EXECUTE upd_inttest (7); + +COMMIT; + +DEALLOCATE upd_inttest; + +-- +-- DELETE +-- + +BEGIN; + +PREPARE del_inttest(bigint) AS DELETE FROM inttest WHERE f1 = $1; + +EXECUTE del_inttest (1); + +EXECUTE del_inttest (2); + +EXECUTE del_inttest (3); + +EXECUTE del_inttest (4); + +EXECUTE del_inttest (5); + +EXECUTE del_inttest (6); + +EXECUTE del_inttest (7); + +COMMIT; + +DEALLOCATE del_inttest; + +-------------------------------------------------------------------------------- +-- Trigger Tests +-------------------------------------------------------------------------------- +BEGIN; + +CREATE TABLE IF NOT EXISTS delete_fdw_trigger_test(id bigint primary key); + +-- +-- A before trigger testing before actions on INSERT/UPDATE/DELETE +-- on a foreign table +-- +CREATE OR REPLACE FUNCTION f_tg_test() +RETURNS trigger +LANGUAGE plpgsql +AS +$$ +BEGIN + IF TG_OP = 'DELETE' THEN + DELETE FROM inttest WHERE f1 = OLD.id; + RETURN OLD; + ELSIF TG_OP = 'UPDATE' THEN + UPDATE inttest SET f1 = NEW.id WHERE f1 = OLD.id; + RETURN NEW; + ELSIF TG_OP = 'INSERT' THEN + INSERT INTO inttest VALUES(NEW.id); + RETURN NEW; + ELSE + RAISE EXCEPTION 'unhandled trigger action %', TG_OP; + END IF; +END; +$$; + +-- +-- A broken trigger function referencing the wrong tuple identifiers +-- according to the trigger action (NEW vs. OLD) +-- +-- Basically the same as above. +-- +CREATE OR REPLACE FUNCTION f_tg_test_broken() +RETURNS trigger +LANGUAGE plpgsql +AS +$$ +BEGIN + IF TG_OP = 'DELETE' THEN + DELETE FROM inttest WHERE f1 = NEW.id; + RETURN OLD; + ELSIF TG_OP = 'UPDATE' THEN + UPDATE inttest SET f1 = NEW.id WHERE f1 = OLD.id; + RETURN NEW; + ELSIF TG_OP = 'INSERT' THEN + INSERT INTO inttest VALUES(OLD.id); + RETURN NEW; + ELSE + RAISE EXCEPTION 'unhandled trigger action %', TG_OP; + END IF; +END; +$$; + +CREATE TRIGGER tg_inttest +BEFORE DELETE OR UPDATE OR INSERT ON delete_fdw_trigger_test +FOR EACH ROW EXECUTE PROCEDURE f_tg_test(); + +TRUNCATE delete_fdw_trigger_test; +INSERT INTO delete_fdw_trigger_test VALUES(1), (2), (3); + +SELECT * FROM inttest; + +DELETE FROM delete_fdw_trigger_test WHERE id = 2; + +SELECT * FROM inttest; + +UPDATE delete_fdw_trigger_test SET id = 4 WHERE id = 3; + +SELECT * FROM inttest; + +INSERT INTO delete_fdw_trigger_test VALUES(5); + +SELECT * FROM inttest; + +DELETE FROM delete_fdw_trigger_test; + +SELECT * FROM inttest; + +DROP TRIGGER tg_inttest ON delete_fdw_trigger_test; + +CREATE TRIGGER tg_inttest +BEFORE DELETE OR UPDATE OR INSERT ON delete_fdw_trigger_test +FOR EACH ROW EXECUTE PROCEDURE f_tg_test_broken(); + +-- should fail +SAVEPOINT broken; +INSERT INTO delete_fdw_trigger_test VALUES(1), (2), (3); +ROLLBACK TO broken; + +SELECT * FROM inttest; + +-- should delete nothing +DELETE FROM delete_fdw_trigger_test WHERE id = 2; + +SELECT * FROM inttest; + +-- should update nothing +UPDATE delete_fdw_trigger_test SET id = 4 WHERE id = 3; + +SELECT * FROM inttest; + +DELETE FROM delete_fdw_trigger_test; + +SELECT * FROM inttest; + +DROP TRIGGER tg_inttest ON delete_fdw_trigger_test; + +COMMIT; + -------------------------------------------------------------------------------- -- Regression Tests End, Cleanup --------------------------------------------------------------------------------