From 4dd12998f9c1a5fcc5f8e690818641f506a4ec17 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Mon, 12 Jan 2026 09:27:11 +0100 Subject: [PATCH 001/120] #105 Introducing db2GetForeignUpperPaths --- Makefile | 1 + source/db2GetForeignUpperPaths.c | 41 ++++++++++++++++++++++++++++++++ source/db2_fdw.c | 2 ++ 3 files changed, 44 insertions(+) create mode 100644 source/db2GetForeignUpperPaths.c diff --git a/Makefile b/Makefile index 101d509..62c320c 100644 --- a/Makefile +++ b/Makefile @@ -4,6 +4,7 @@ MODULE_big = db2_fdw OBJS = source/db2_fdw.o\ source/db2GetForeignPlan.o\ source/db2GetForeignPaths.o\ + source/db2GetForeignUpperPaths.o\ source/db2GetForeignJoinPaths.o\ source/db2AnalyzeForeignTable.o\ source/db2ExplainForeignScan.o\ diff --git a/source/db2GetForeignUpperPaths.c b/source/db2GetForeignUpperPaths.c new file mode 100644 index 0000000..fab8297 --- /dev/null +++ b/source/db2GetForeignUpperPaths.c @@ -0,0 +1,41 @@ +#include +#include +#include +#include + +/** external prototypes */ +extern void db2Debug1 (const char* message, ...); + +/** local prototypes */ +void db2GetForeignUpperPaths(PlannerInfo *root, UpperRelationKind stage, RelOptInfo *input_rel, RelOptInfo *output_rel, void *extra); + +void db2GetForeignUpperPaths(PlannerInfo *root, UpperRelationKind stage, RelOptInfo *input_rel, RelOptInfo *output_rel, void *extra) { + db2Debug1("> %s::db2GetForeignUpperPaths",__FILE__); + if (stage == UPPERREL_GROUP_AGG) { + /* input_rel is the scan/join rel; output_rel is the aggregate rel */ + + /* 1) reject if grouping sets / DISTINCT aggregates / ordered-set etc, unless you support them */ +// if (!db2_groupagg_supported(root, output_rel)) +// return; + + /* 2) ensure all target expressions and having quals are shippable */ +// if (!db2_upper_targets_shippable(root, output_rel) || !db2_having_shippable(root, output_rel)) +// return; + + /* 3) create a ForeignPath that performs aggregation remotely */ +// Path* path = (Path*) create_foreign_upper_path( +// root, +// output_rel, +// /* fdw_private */ db2_build_upper_fdw_private(root, input_rel, output_rel), +// /* rows */ output_rel->rows, +// /* startup_cost */ 10000.0, +// /* total_cost */ 10000.0 + output_rel->rows * 10.0, +// /* pathkeys */ NIL, +// /* required_outer */ NULL, +// /* fdw_outerpath */ NULL +// ); + +// add_path(output_rel, path); + } + db2Debug1("< %s::db2GetForeignUpperPaths",__FILE__); +} diff --git a/source/db2_fdw.c b/source/db2_fdw.c index fa1ffcf..7e8f3d7 100644 --- a/source/db2_fdw.c +++ b/source/db2_fdw.c @@ -99,6 +99,7 @@ extern void db2GetForeignRelSize (PlannerInfo* root, RelOptInf */ extern ForeignScan* db2GetForeignPlan (PlannerInfo* root, RelOptInfo* foreignrel, Oid foreigntableid, ForeignPath* best_path, List* tlist, List* scan_clauses , Plan* outer_plan); extern void db2GetForeignPaths (PlannerInfo* root, RelOptInfo* baserel, Oid foreigntableid); +extern void db2GetForeignUpperPaths (PlannerInfo *root, UpperRelationKind stage, RelOptInfo *input_rel, RelOptInfo *output_rel, void *extra); extern void db2GetForeignJoinPaths (PlannerInfo* root, RelOptInfo* joinrel, RelOptInfo* outerrel, RelOptInfo* innerrel, JoinType jointype, JoinPathExtraData* extra); extern bool db2AnalyzeForeignTable (Relation relation, AcquireSampleRowsFunc* func, BlockNumber* totalpages); extern void db2ExplainForeignScan (ForeignScanState* node, ExplainState* es); @@ -140,6 +141,7 @@ PGDLLEXPORT Datum db2_fdw_handler (PG_FUNCTION_ARGS) { fdwroutine->GetForeignRelSize = db2GetForeignRelSize; fdwroutine->GetForeignPaths = db2GetForeignPaths; + fdwroutine->GetForeignUpperPaths = db2GetForeignUpperPaths; fdwroutine->GetForeignJoinPaths = db2GetForeignJoinPaths; fdwroutine->GetForeignPlan = db2GetForeignPlan; fdwroutine->AnalyzeForeignTable = db2AnalyzeForeignTable; From ade1cf454f9fdc09d34b183a9b4d8066bee45ed0 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Sat, 17 Jan 2026 14:23:58 +0100 Subject: [PATCH 002/120] add deep copy of fdwstate for upper path --- include/DB2FdwState.h | 3 +- source/db2GetForeignUpperPaths.c | 206 ++++++++++++++++++++++++++----- 2 files changed, 179 insertions(+), 30 deletions(-) diff --git a/include/DB2FdwState.h b/include/DB2FdwState.h index 2c831cc..b84cc4b 100644 --- a/include/DB2FdwState.h +++ b/include/DB2FdwState.h @@ -35,8 +35,7 @@ typedef struct db2FdwState { unsigned long prefetch; // number of rows to prefetch char* order_clause; // for sort-pushdown char* where_clause; // deparsed where clause - /* - * Restriction clauses, divided into safe and unsafe to pushdown subsets. + /** Restriction clauses, divided into safe and unsafe to pushdown subsets. * * For a base foreign relation this is a list of clauses along-with * RestrictInfo wrapper. Keeping RestrictInfo wrapper helps while dividing diff --git a/source/db2GetForeignUpperPaths.c b/source/db2GetForeignUpperPaths.c index fab8297..03bcb0a 100644 --- a/source/db2GetForeignUpperPaths.c +++ b/source/db2GetForeignUpperPaths.c @@ -1,41 +1,191 @@ #include #include -#include +#include #include +#include +#include +#include "db2_fdw.h" +#include "DB2FdwState.h" /** external prototypes */ extern void db2Debug1 (const char* message, ...); +extern void db2Debug2 (const char* message, ...); +extern void* db2alloc (const char* type, size_t size); +extern char* db2strdup (const char* source); /** local prototypes */ -void db2GetForeignUpperPaths(PlannerInfo *root, UpperRelationKind stage, RelOptInfo *input_rel, RelOptInfo *output_rel, void *extra); +void db2GetForeignUpperPaths (PlannerInfo *root, UpperRelationKind stage, RelOptInfo *input_rel, RelOptInfo *output_rel, void *extra); +DB2FdwState* db2CloneFdwStateUpper (PlannerInfo* root, const DB2FdwState* fdw_in, RelOptInfo* input_rel, RelOptInfo* output_rel); +DB2Table* db2CloneDb2TableForPlan (const DB2Table* src); +DB2Column* db2CloneDb2ColumnForPlan (const DB2Column* src); void db2GetForeignUpperPaths(PlannerInfo *root, UpperRelationKind stage, RelOptInfo *input_rel, RelOptInfo *output_rel, void *extra) { - db2Debug1("> %s::db2GetForeignUpperPaths",__FILE__); - if (stage == UPPERREL_GROUP_AGG) { - /* input_rel is the scan/join rel; output_rel is the aggregate rel */ - - /* 1) reject if grouping sets / DISTINCT aggregates / ordered-set etc, unless you support them */ -// if (!db2_groupagg_supported(root, output_rel)) -// return; - - /* 2) ensure all target expressions and having quals are shippable */ -// if (!db2_upper_targets_shippable(root, output_rel) || !db2_having_shippable(root, output_rel)) -// return; - - /* 3) create a ForeignPath that performs aggregation remotely */ -// Path* path = (Path*) create_foreign_upper_path( -// root, -// output_rel, -// /* fdw_private */ db2_build_upper_fdw_private(root, input_rel, output_rel), -// /* rows */ output_rel->rows, -// /* startup_cost */ 10000.0, -// /* total_cost */ 10000.0 + output_rel->rows * 10.0, -// /* pathkeys */ NIL, -// /* required_outer */ NULL, -// /* fdw_outerpath */ NULL -// ); - -// add_path(output_rel, path); + DB2FdwState* fdw_in = NULL; + + db2Debug1("> %s::db2GetForeignUpperPaths",__FILE__); + switch (stage) { + case UPPERREL_SETOP: // UNION/INTERSECT/EXCEPT + db2Debug2(" query contains UNION/INTERSECT/EXCEPT"); + break; + case UPPERREL_PARTIAL_GROUP_AGG: { // partial grouping/aggregation + db2Debug2(" query contains partial grouping/aggregation"); + fdw_in = (DB2FdwState*)input_rel->fdw_private; + } + break; + case UPPERREL_GROUP_AGG: { // grouping/aggregation + db2Debug2(" query contains grouping/aggregation"); + fdw_in = (DB2FdwState*)input_rel->fdw_private; + } + break; + case UPPERREL_WINDOW: // window functions + db2Debug2(" query contains window functions"); + break; + case UPPERREL_PARTIAL_DISTINCT: // partial "SELECT DISTINCT" + db2Debug2(" query contains partial distinct"); + break; + case UPPERREL_DISTINCT: // "SELECT DISTINCT" + db2Debug2(" query contains distinct"); + break; + case UPPERREL_ORDERED: // ORDER BY + db2Debug2(" query contains order by"); + break; + case UPPERREL_FINAL: // any remaining top-level actions + db2Debug2(" query contains other top-level actions"); + break; + default: // unknown stage type + db2Debug2(" unknown stage type"); + break; + } + if (fdw_in != NULL) { + // 1) Reject unsupported cases quickly + if (db2_groupagg_supported(root, output_rel)) { + // 2) Verify all GROUP keys, Aggrefs, HAVING are deparsable for DB2 + if (db2_groupagg_shippable(root, input_rel, output_rel, fdw_in)) { + // 3) Create a new state for the upper rel (copy + mark as aggregate) + DB2FdwState* state = db2CloneFdwStateUpper(root, fdw_in, input_rel, output_rel); +// state->is_upper = true; +// state->upper_kind = UPPERREL_GROUP_AGG; + // Estimate rows/cost (can be crude initially) + output_rel->rows = clamp_row_est(output_rel->rows); + + Path* path = (Path*) create_foreign_upper_path( root + , output_rel + , output_rel->reltarget /* pathtarget */ + , output_rel->rows + , 0 /* disabled nodes (PG18+) if needed */ + , state->startup_cost + , state->startup_cost + output_rel->rows * 10.0 + , NIL /* no pathkeys initially */ + , NULL /* required_outer */ + , NULL /* fdw_outerpath */ + , (void*)state + ); + + add_path(output_rel, path); + } + } } db2Debug1("< %s::db2GetForeignUpperPaths",__FILE__); } + +/** db2CloneFdwStateUpper + * Create a deep copy suitable for upper-relation planning. + * + * Rationale: planning can change mutable fields like DB2Column.used and also + * rewrite the params list (createQuery will NULL out entries). We must avoid + * those mutations affecting the original baserel/joinrel planning state. + */ +DB2FdwState* db2CloneFdwStateUpper(PlannerInfo* root, const DB2FdwState* fdw_in, RelOptInfo* input_rel, RelOptInfo* output_rel) { + DB2FdwState* copy = NULL; + + if (fdw_in != NULL) { + copy = (DB2FdwState*) db2alloc("fdw_state_upper", sizeof(DB2FdwState)); + + /* Connection/session fields */ + copy->dbserver = fdw_in->dbserver ? db2strdup(fdw_in->dbserver) : NULL; + copy->user = fdw_in->user ? db2strdup(fdw_in->user) : NULL; + copy->password = fdw_in->password ? db2strdup(fdw_in->password) : NULL; + copy->jwt_token = fdw_in->jwt_token ? db2strdup(fdw_in->jwt_token) : NULL; + copy->nls_lang = fdw_in->nls_lang ? db2strdup(fdw_in->nls_lang) : NULL; + /* Planner-time session handle can be shared (it is not serialized). */ + copy->session = fdw_in->session; + + /* Planning/execution fields */ + copy->query = fdw_in->query ? db2strdup(fdw_in->query) : NULL; + copy->prefetch = fdw_in->prefetch; + copy->startup_cost = fdw_in->startup_cost; + copy->total_cost = fdw_in->total_cost; + copy->rowcount = 0; + copy->columnindex = 0; + copy->temp_cxt = NULL; + + copy->order_clause = fdw_in->order_clause ? db2strdup(fdw_in->order_clause) : NULL; + copy->where_clause = fdw_in->where_clause ? db2strdup(fdw_in->where_clause) : NULL; + + /* Shallow-copy expression lists (Expr nodes are immutable at this stage), but + * ensure list cells are independent because createQuery mutates the list. + */ + copy->params = fdw_in->params ? list_copy(fdw_in->params) : NIL; + copy->remote_conds = fdw_in->remote_conds ? list_copy(fdw_in->remote_conds) : NIL; + copy->local_conds = fdw_in->local_conds ? list_copy(fdw_in->local_conds) : NIL; + + /* Deep-copy DB2 table/columns because DB2Column.used is re-derived for each + * planned query shape. + */ + copy->db2Table = db2CloneDb2TableForPlan(fdw_in->db2Table); + + /* Join info: keep as-is (typically NULL for baserels). */ + copy->outerrel = fdw_in->outerrel; + copy->innerrel = fdw_in->innerrel; + copy->jointype = fdw_in->jointype; + copy->joinclauses = fdw_in->joinclauses ? list_copy(fdw_in->joinclauses) : NIL; + + /* paramList is constructed at execution time from fdw_exprs. */ + copy->paramList = NULL; + } + return copy; +} + +DB2Table* db2CloneDb2TableForPlan(const DB2Table* src) { + DB2Table* dst = NULL; + int i; + + if (src != NULL) { + dst = (DB2Table*) db2alloc("db2_table_clone", sizeof(DB2Table)); + + dst->name = src->name ? db2strdup(src->name) : NULL; + dst->pgname = src->pgname ? db2strdup(src->pgname) : NULL; + dst->batchsz = src->batchsz; + dst->ncols = src->ncols; + dst->npgcols = src->npgcols; + + if (src->ncols > 0) { + dst->cols = (DB2Column**) db2alloc("db2_table_clone->cols", sizeof(DB2Column*) * src->ncols); + for (i = 0; i < src->ncols; ++i) { + dst->cols[i] = db2CloneDb2ColumnForPlan(src->cols[i]); + } + } else { + dst->cols = NULL; + } + } + return dst; +} + +DB2Column* db2CloneDb2ColumnForPlan(const DB2Column* src) { + DB2Column* dst = NULL; + + if (src != NULL) { + dst = (DB2Column*) db2alloc("db2_column_clone", sizeof(DB2Column)); + /* start with a struct copy, then fix up pointer members */ + *dst = *src; + + dst->colName = src->colName ? db2strdup(src->colName) : NULL; + dst->pgname = src->pgname ? db2strdup(src->pgname) : NULL; + + /* Never share row buffers between planning states. */ + dst->val = NULL; + dst->val_len = 0; + dst->val_null = 1; + } + return dst; +} From d6d59f20490d0c69eb22b8733739233318bd922e Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Sat, 17 Jan 2026 17:39:47 +0100 Subject: [PATCH 003/120] added deparseAggref to handle aggregate functions count, sum, min, max, avg --- source/db2_fdw_utils.c | 108 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/source/db2_fdw_utils.c b/source/db2_fdw_utils.c index 032b8d6..dba06d4 100644 --- a/source/db2_fdw_utils.c +++ b/source/db2_fdw_utils.c @@ -47,6 +47,7 @@ char* deparseBoolExpr (DB2Session* session, RelOptInfo* char* deparseCaseExpr (DB2Session* session, RelOptInfo* foreignrel, CaseExpr* expr, const DB2Table* db2Table, List** params); char* deparseCoalesceExpr (DB2Session* session, RelOptInfo* foreignrel, CoalesceExpr* expr, const DB2Table* db2Table, List** params); char* deparseFuncExpr (DB2Session* session, RelOptInfo* foreignrel, FuncExpr* expr, const DB2Table* db2Table, List** params); +char* deparseAggref (DB2Session* session, RelOptInfo* foreignrel, Aggref* expr, const DB2Table* db2Table, List** params); char* deparseCoerceViaIOExpr (CoerceViaIO* expr); #if PG_VERSION_NUM >= 100000 char* deparseSQLValueFuncExpr (SQLValueFunction* expr); @@ -188,6 +189,10 @@ char* deparseExpr (DB2Session* session, RelOptInfo* foreignrel, Expr* expr, cons } break; #endif + case T_Aggref: { + retValue = deparseAggref(session, foreignrel, (Aggref*)expr, db2Table, params); + } + break; default: { /* we cannot translate this to DB2 */ db2Debug2(" expression cannot be translated to DB2", __FILE__); @@ -1052,6 +1057,109 @@ char* deparseSQLValueFuncExpr (SQLValueFunction* expr) { } #endif +char* deparseAggref (DB2Session* session, RelOptInfo* foreignrel, Aggref* expr, const DB2Table* db2Table, List** params) { + char* value = NULL; + + db2Debug1("> %s::deparseAggref", __FILE__); + if (expr == NULL) { + db2Debug2(" expr is NULL"); + } else { + /* Resolve aggregate function name (OID -> pg_proc.proname). */ + HeapTuple tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(expr->aggfnoid)); + char* aggname = NULL; + char* nspname = NULL; + if (!HeapTupleIsValid(tuple)) { + elog(ERROR, "cache lookup failed for function %u", expr->aggfnoid); + } else { + Form_pg_proc procform = (Form_pg_proc) GETSTRUCT(tuple); + aggname = pstrdup(NameStr(procform->proname)); + /* Optional: capture schema for debugging/qualification decisions. */ + if (OidIsValid(procform->pronamespace)) { + HeapTuple ntup = SearchSysCache1(NAMESPACEOID, ObjectIdGetDatum(procform->pronamespace)); + if (HeapTupleIsValid(ntup)) { + Form_pg_namespace nspform = (Form_pg_namespace) GETSTRUCT(ntup); + nspname = pstrdup(NameStr(nspform->nspname)); + ReleaseSysCache(ntup); + } + } + ReleaseSysCache(tuple); + } + db2Debug2(" aggref->aggfnoid=%u name=%s%s%s", expr->aggfnoid, + nspname ? nspname : "", + nspname ? "." : "", + aggname ? aggname : ""); + /* We only support deparsing simple, standard aggregates for now. + * (This can be expanded to ordered-set / FILTER / WITHIN GROUP later.) + */ + if (expr->aggorder != NIL) { + db2Debug2(" aggregate ORDER BY not supported for pushdown"); + } else if (aggname != NULL) { + const char* db2func = NULL; + bool distinct = (expr->aggdistinct != NIL); + bool ok = true; + + if (strcmp(aggname, "count") == 0) db2func = "COUNT"; + else if (strcmp(aggname, "sum") == 0) db2func = "SUM"; + else if (strcmp(aggname, "avg") == 0) db2func = "AVG"; + else if (strcmp(aggname, "min") == 0) db2func = "MIN"; + else if (strcmp(aggname, "max") == 0) db2func = "MAX"; + else { + /* Unknown aggregate name: we can still report it (above), but don't emit SQL. */ + db2Debug2(" aggregate '%s' not supported for DB2 deparse", aggname); + } + if (db2func != NULL) { + StringInfoData result; + initStringInfo(&result); + appendStringInfo(&result, "%s(", db2func); + if (distinct) { + appendStringInfoString(&result, "DISTINCT "); + } + if (expr->aggstar) { + /* COUNT(*) */ + appendStringInfoString(&result, "*"); + } else { + ListCell* lc; + bool first_arg = true; + + foreach (lc, expr->args) { + Node* argnode = (Node*) lfirst(lc); + Expr* argexpr = NULL; + char* argsql; + + if (argnode == NULL) { + ok = false; + break; + } + if (argnode->type == T_TargetEntry) { + argexpr = ((TargetEntry*) argnode)->expr; + } else { + argexpr = (Expr*) argnode; + } + + argsql = deparseExpr(session, foreignrel, argexpr, db2Table, params); + if (argsql == NULL) { + ok = false; + break; + } + + appendStringInfo(&result, "%s%s", first_arg ? "" : ", ", argsql); + first_arg = false; + } + } + if (!ok) { + db2Debug2(" could not deparse aggregate args"); + } else { + appendStringInfoChar(&result, ')'); + value = result.data; + } + } + } + } + + db2Debug1("< %s::deparseAggref: %s", __FILE__, value); + return value; +} + /** datumToString * Convert a Datum to a string by calling the type output function. * Returns the result or NULL if it cannot be converted to DB2 SQL. From c367016d6165ba71eab646651f08428eb6136f95 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Sat, 17 Jan 2026 17:40:52 +0100 Subject: [PATCH 004/120] the code is able now to correctly parse the aggregate functions, but still issues with the result passed back to PG --- source/db2GetForeignUpperPaths.c | 231 +++++++++++++++++++++++++------ 1 file changed, 186 insertions(+), 45 deletions(-) diff --git a/source/db2GetForeignUpperPaths.c b/source/db2GetForeignUpperPaths.c index 03bcb0a..41e3165 100644 --- a/source/db2GetForeignUpperPaths.c +++ b/source/db2GetForeignUpperPaths.c @@ -4,88 +4,132 @@ #include #include #include +#include #include "db2_fdw.h" #include "DB2FdwState.h" /** external prototypes */ extern void db2Debug1 (const char* message, ...); extern void db2Debug2 (const char* message, ...); +extern void db2Debug3 (const char* message, ...); extern void* db2alloc (const char* type, size_t size); extern char* db2strdup (const char* source); +extern char* deparseExpr (DB2Session* session, RelOptInfo* foreignrel, Expr* expr, const DB2Table* db2Table, List** params); /** local prototypes */ void db2GetForeignUpperPaths (PlannerInfo *root, UpperRelationKind stage, RelOptInfo *input_rel, RelOptInfo *output_rel, void *extra); DB2FdwState* db2CloneFdwStateUpper (PlannerInfo* root, const DB2FdwState* fdw_in, RelOptInfo* input_rel, RelOptInfo* output_rel); DB2Table* db2CloneDb2TableForPlan (const DB2Table* src); DB2Column* db2CloneDb2ColumnForPlan (const DB2Column* src); +bool db2_is_shippable (PlannerInfo* root, UpperRelationKind stage, RelOptInfo* input_rel, RelOptInfo* output_rel, const DB2FdwState* fdw_in); +bool db2_is_shippable_expr (PlannerInfo* root, RelOptInfo* foreignrel, const DB2FdwState* fdw_in, Expr* expr, const char* label); void db2GetForeignUpperPaths(PlannerInfo *root, UpperRelationKind stage, RelOptInfo *input_rel, RelOptInfo *output_rel, void *extra) { - DB2FdwState* fdw_in = NULL; - db2Debug1("> %s::db2GetForeignUpperPaths",__FILE__); + if (root != NULL && root->parse != NULL) { + DB2FdwState* fdw_in = NULL; + Query* query = root->parse; + db2Debug3(" query->hasAggs : %s", query->hasAggs ? "true" : "false"); + db2Debug3(" query->hasWindowFuncs : %s", query->hasWindowFuncs ? "true" : "false"); + db2Debug3(" query->hasDistinctOn : %s", query->hasDistinctOn ? "true" : "false"); + db2Debug3(" query->hasTargetSRFs : %s", query->hasTargetSRFs ? "true" : "false"); + db2Debug3(" query->hasForUpdate : %s", query->hasForUpdate ? "true" : "false"); + db2Debug3(" query->hasGroupRTE : %s", query->hasGroupRTE ? "true" : "false"); + db2Debug3(" query->hasModifyingCTE: %s", query->hasModifyingCTE ? "true" : "false"); + db2Debug3(" query->hasRecursive : %s", query->hasRecursive ? "true" : "false"); + db2Debug3(" query->hasSubLinks : %s", query->hasSubLinks ? "true" : "false"); + db2Debug3(" query->hasRowSecurity : %s", query->hasRowSecurity ? "true" : "false"); switch (stage) { case UPPERREL_SETOP: // UNION/INTERSECT/EXCEPT - db2Debug2(" query contains UNION/INTERSECT/EXCEPT"); + db2Debug2(" stage: %d - UPPERREL_SETOP", stage); break; - case UPPERREL_PARTIAL_GROUP_AGG: { // partial grouping/aggregation - db2Debug2(" query contains partial grouping/aggregation"); - fdw_in = (DB2FdwState*)input_rel->fdw_private; - } + case UPPERREL_PARTIAL_GROUP_AGG: // partial grouping/aggregation + db2Debug2(" stage: %d - UPPERREL_PARTIAL_GROUP_AGG", stage); + db2Debug2(" query->hasAggs: %d", query->hasAggs); + db2Debug2(" query->groupClause: %x", query->groupClause); + if (query->hasAggs || query->groupClause != NIL) { + fdw_in = (DB2FdwState*)input_rel->fdw_private; + } break; case UPPERREL_GROUP_AGG: { // grouping/aggregation - db2Debug2(" query contains grouping/aggregation"); - fdw_in = (DB2FdwState*)input_rel->fdw_private; + db2Debug2(" stage: %d - UPPERREL_GROUP_AGG", stage); + db2Debug2(" query->hasAggs: %d", query->hasAggs); + db2Debug2(" query->groupClause: %x", query->groupClause); + if (query->hasAggs || query->groupClause != NIL) { + fdw_in = (DB2FdwState*)input_rel->fdw_private; + } } break; - case UPPERREL_WINDOW: // window functions - db2Debug2(" query contains window functions"); + case UPPERREL_WINDOW: { // window functions + db2Debug2(" stage: %d - UPPERREL_WINDOW", stage); + db2Debug2(" query->hasWindowFuncs: %d", query->hasWindowFuncs); + if (query->hasWindowFuncs) { + fdw_in = (DB2FdwState*)input_rel->fdw_private; + } + } break; - case UPPERREL_PARTIAL_DISTINCT: // partial "SELECT DISTINCT" - db2Debug2(" query contains partial distinct"); + case UPPERREL_PARTIAL_DISTINCT: { // partial "SELECT DISTINCT" + db2Debug2(" stage: %d - UPPERREL_PARTIAL_DISTINCT", stage); + db2Debug2(" query->hasDistinctOn: %d", query->hasDistinctOn); + if (query->hasDistinctOn) { + fdw_in = (DB2FdwState*)input_rel->fdw_private; + } + } break; - case UPPERREL_DISTINCT: // "SELECT DISTINCT" - db2Debug2(" query contains distinct"); + case UPPERREL_DISTINCT: { // "SELECT DISTINCT" + db2Debug2(" stage: %d - UPPERREL_DISTINCT", stage); + db2Debug2(" query->hasDistinctOn: %d", query->hasDistinctOn); + if (query->hasDistinctOn) { + fdw_in = (DB2FdwState*)input_rel->fdw_private; + } + } break; case UPPERREL_ORDERED: // ORDER BY - db2Debug2(" query contains order by"); + db2Debug2(" stage: %d - UPPERREL_ORDERED", stage); + db2Debug2(" query->setOperations: %x", query->setOperations); + if (query->setOperations != NULL) { + fdw_in = (DB2FdwState*)input_rel->fdw_private; + } break; case UPPERREL_FINAL: // any remaining top-level actions - db2Debug2(" query contains other top-level actions"); + db2Debug2(" stage: %d - UPPERREL_FINAL", stage); + fdw_in = (DB2FdwState*)input_rel->fdw_private; break; default: // unknown stage type - db2Debug2(" unknown stage type"); + db2Debug2(" stage: %d - unknown", stage); break; } if (fdw_in != NULL) { - // 1) Reject unsupported cases quickly - if (db2_groupagg_supported(root, output_rel)) { - // 2) Verify all GROUP keys, Aggrefs, HAVING are deparsable for DB2 - if (db2_groupagg_shippable(root, input_rel, output_rel, fdw_in)) { - // 3) Create a new state for the upper rel (copy + mark as aggregate) - DB2FdwState* state = db2CloneFdwStateUpper(root, fdw_in, input_rel, output_rel); -// state->is_upper = true; -// state->upper_kind = UPPERREL_GROUP_AGG; - // Estimate rows/cost (can be crude initially) - output_rel->rows = clamp_row_est(output_rel->rows); - - Path* path = (Path*) create_foreign_upper_path( root - , output_rel - , output_rel->reltarget /* pathtarget */ - , output_rel->rows - , 0 /* disabled nodes (PG18+) if needed */ - , state->startup_cost - , state->startup_cost + output_rel->rows * 10.0 - , NIL /* no pathkeys initially */ - , NULL /* required_outer */ - , NULL /* fdw_outerpath */ - , (void*)state - ); - - add_path(output_rel, path); - } + // verify all GROUP keys, Aggrefs, HAVING are deparsable for DB2 + if (db2_is_shippable(root, stage, input_rel, output_rel, fdw_in)) { + // create a new state for the upper rel (copy + mark as aggregate) + Path* path = NULL; + DB2FdwState* state = db2CloneFdwStateUpper(root, fdw_in, input_rel, output_rel); +// state->is_upper = true; +// state->upper_kind = stage; + // Estimate rows/cost (can be crude initially) + output_rel->rows = clamp_row_est(output_rel->rows); + path = (Path*) create_foreign_upper_path( root + , output_rel + , output_rel->reltarget /* pathtarget */ + , output_rel->rows + , 0 /* disabled nodes (PG18+) if needed */ + , state->startup_cost + , state->startup_cost + output_rel->rows * 10.0 + , NIL /* no pathkeys initially */ + , NULL /* required_outer */ + , NULL /* fdw_outerpath */ + , (void*)state + ); +// add_path(output_rel, path); } + } else { + db2Debug2(" not pushable"); } - db2Debug1("< %s::db2GetForeignUpperPaths",__FILE__); + } else { + db2Debug2(" no root or no query"); + } + db2Debug1("< %s::db2GetForeignUpperPaths",__FILE__); } /** db2CloneFdwStateUpper @@ -189,3 +233,100 @@ DB2Column* db2CloneDb2ColumnForPlan(const DB2Column* src) { } return dst; } + +bool db2_is_shippable(PlannerInfo* root, UpperRelationKind stage, RelOptInfo* input_rel, RelOptInfo* output_rel, const DB2FdwState* fdw_in) { + bool fResult = false; + db2Debug1("> %s::db2_is_shippable", __FILE__); + if (root == NULL || root->parse == NULL || input_rel == NULL || output_rel == NULL || fdw_in == NULL) { + db2Debug2(" missing context; not shippable"); + fResult = false; + } else { + Query* query = query = root->parse; + /* Conservatively reject query shapes we don't yet know how to translate. + * (This can be relaxed as we add DB2 SQL support.) + */ + if (query->hasSubLinks || query->hasWindowFuncs || query->hasDistinctOn || query->hasTargetSRFs || query->hasForUpdate || query->hasModifyingCTE || query->hasRecursive || query->hasRowSecurity) { + db2Debug2(" query has unsupported features; not shippable"); + fResult = false; + } else { + switch (stage) { + case UPPERREL_PARTIAL_GROUP_AGG: + case UPPERREL_GROUP_AGG: { + ListCell* lc = NULL; + + fResult = true; + /* 1) GROUP BY expressions must be deparsable. */ + foreach (lc, query->groupClause) { + SortGroupClause* grp = (SortGroupClause*) lfirst(lc); + TargetEntry* tle = get_sortgroupclause_tle(grp, query->targetList); + if (tle == NULL || tle->expr == NULL) { + db2Debug2(" missing GROUP BY target entry; not shippable"); + fResult = false; + break; + } + if (!db2_is_shippable_expr(root, input_rel, fdw_in, (Expr*) tle->expr, "GROUP BY")) { + fResult = false; + break; + } + } + + if (fResult) { + /* 2) HAVING clause must be deparsable (if present). */ + if (query->havingQual != NULL) { + if (!db2_is_shippable_expr(root, input_rel, fdw_in, (Expr*) query->havingQual, "HAVING")) { + fResult = false; + } + } + } + + if (fResult) { + /* 3) Output target expressions must be deparsable too. */ + foreach (lc, output_rel->reltarget->exprs) { + Expr* expr = (Expr*) lfirst(lc); + if (!db2_is_shippable_expr(root, input_rel, fdw_in, expr, "SELECT")) { + fResult = false; + break; + } + } + } + } + break; + default: { + db2Debug2(" stage %d not supported; not shippable", stage); + fResult = false; + } + break; + } + } + } + db2Debug1("< %s::db2_is_shippable : %s", __FILE__, fResult ? "true" : "false"); + return fResult; +} + +bool db2_is_shippable_expr(PlannerInfo* root, RelOptInfo* foreignrel, const DB2FdwState* fdw_in, Expr* expr, const char* label) { + bool fResult = false; + + db2Debug1("> %s::db2_is_shippable_expr", __FILE__); + if (expr == NULL) { + fResult = true; + } else if (fdw_in == NULL) { + fResult = false; + } else if (contain_agg_clause((Node*) expr)) { + List* params = NIL; + char* deparsed = NULL; + deparsed = deparseExpr(fdw_in->session, foreignrel, expr, fdw_in->db2Table, ¶ms); + db2Debug2(" deparsed: %s", deparsed); + fResult = (deparsed != NULL); + } else if (contain_window_function((Node*) expr)) { + db2Debug2(" %s contains window function; not shippable", label ? label : "expr"); + fResult = false; + } else { + List* params = NIL; + char* deparsed = NULL; + deparsed = deparseExpr(fdw_in->session, foreignrel, expr, fdw_in->db2Table, ¶ms); + db2Debug2(" deparsed: %s", deparsed); + fResult = (deparsed != NULL); + } + db2Debug1("> %s::db2_is_shippable_expr : %s", __FILE__, fResult ? "true" : "false"); + return fResult; +} From 08b462e40ecf9cfb6428d301b25f6cc946d03e1c Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Sat, 17 Jan 2026 18:04:27 +0100 Subject: [PATCH 005/120] enhanced traces --- source/db2GetForeignUpperPaths.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/source/db2GetForeignUpperPaths.c b/source/db2GetForeignUpperPaths.c index 41e3165..139e38a 100644 --- a/source/db2GetForeignUpperPaths.c +++ b/source/db2GetForeignUpperPaths.c @@ -142,6 +142,7 @@ void db2GetForeignUpperPaths(PlannerInfo *root, UpperRelationKind stage, RelOptI DB2FdwState* db2CloneFdwStateUpper(PlannerInfo* root, const DB2FdwState* fdw_in, RelOptInfo* input_rel, RelOptInfo* output_rel) { DB2FdwState* copy = NULL; + db2Debug1("> %s::db2CloneFdwStateUpper", __FILE__); if (fdw_in != NULL) { copy = (DB2FdwState*) db2alloc("fdw_state_upper", sizeof(DB2FdwState)); @@ -187,6 +188,7 @@ DB2FdwState* db2CloneFdwStateUpper(PlannerInfo* root, const DB2FdwState* fdw_in, /* paramList is constructed at execution time from fdw_exprs. */ copy->paramList = NULL; } + db2Debug1("< %s::db2CloneFdwStateUpper : %x", __FILE__, copy); return copy; } @@ -194,6 +196,7 @@ DB2Table* db2CloneDb2TableForPlan(const DB2Table* src) { DB2Table* dst = NULL; int i; + db2Debug1("> %s::db2CloneDb2TableForPlan", __FILE__); if (src != NULL) { dst = (DB2Table*) db2alloc("db2_table_clone", sizeof(DB2Table)); @@ -212,12 +215,14 @@ DB2Table* db2CloneDb2TableForPlan(const DB2Table* src) { dst->cols = NULL; } } + db2Debug1("< %s::db2CloneDb2TableForPlan : %x", __FILE__, dst); return dst; } DB2Column* db2CloneDb2ColumnForPlan(const DB2Column* src) { DB2Column* dst = NULL; + db2Debug1("> %s::db2CloneDb2ColumnForPlan", __FILE__); if (src != NULL) { dst = (DB2Column*) db2alloc("db2_column_clone", sizeof(DB2Column)); /* start with a struct copy, then fix up pointer members */ @@ -231,11 +236,13 @@ DB2Column* db2CloneDb2ColumnForPlan(const DB2Column* src) { dst->val_len = 0; dst->val_null = 1; } + db2Debug1("< %s::db2CloneDb2ColumnForPlan : %x", __FILE__, dst); return dst; } bool db2_is_shippable(PlannerInfo* root, UpperRelationKind stage, RelOptInfo* input_rel, RelOptInfo* output_rel, const DB2FdwState* fdw_in) { bool fResult = false; + db2Debug1("> %s::db2_is_shippable", __FILE__); if (root == NULL || root->parse == NULL || input_rel == NULL || output_rel == NULL || fdw_in == NULL) { db2Debug2(" missing context; not shippable"); From b46e854517fbc977b566a75309777a6f54d17e6f Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Sun, 18 Jan 2026 10:59:08 +0100 Subject: [PATCH 006/120] still not working properly. refer to pg_fdw for clarification. --- source/db2GetForeignUpperPaths.c | 52 ++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/source/db2GetForeignUpperPaths.c b/source/db2GetForeignUpperPaths.c index 139e38a..8ef044f 100644 --- a/source/db2GetForeignUpperPaths.c +++ b/source/db2GetForeignUpperPaths.c @@ -26,19 +26,19 @@ bool db2_is_shippable_expr (PlannerInfo* root, RelOptInfo* fo void db2GetForeignUpperPaths(PlannerInfo *root, UpperRelationKind stage, RelOptInfo *input_rel, RelOptInfo *output_rel, void *extra) { db2Debug1("> %s::db2GetForeignUpperPaths",__FILE__); - if (root != NULL && root->parse != NULL) { + if (root != NULL && root->parse != NULL && output_rel->fdw_private == NULL) { DB2FdwState* fdw_in = NULL; Query* query = root->parse; - db2Debug3(" query->hasAggs : %s", query->hasAggs ? "true" : "false"); - db2Debug3(" query->hasWindowFuncs : %s", query->hasWindowFuncs ? "true" : "false"); - db2Debug3(" query->hasDistinctOn : %s", query->hasDistinctOn ? "true" : "false"); - db2Debug3(" query->hasTargetSRFs : %s", query->hasTargetSRFs ? "true" : "false"); - db2Debug3(" query->hasForUpdate : %s", query->hasForUpdate ? "true" : "false"); - db2Debug3(" query->hasGroupRTE : %s", query->hasGroupRTE ? "true" : "false"); - db2Debug3(" query->hasModifyingCTE: %s", query->hasModifyingCTE ? "true" : "false"); - db2Debug3(" query->hasRecursive : %s", query->hasRecursive ? "true" : "false"); - db2Debug3(" query->hasSubLinks : %s", query->hasSubLinks ? "true" : "false"); - db2Debug3(" query->hasRowSecurity : %s", query->hasRowSecurity ? "true" : "false"); + db2Debug3(" query->hasAggs : %s", query->hasAggs ? "true" : "false"); + db2Debug3(" query->hasWindowFuncs : %s", query->hasWindowFuncs ? "true" : "false"); + db2Debug3(" query->hasDistinctOn : %s", query->hasDistinctOn ? "true" : "false"); + db2Debug3(" query->hasTargetSRFs : %s", query->hasTargetSRFs ? "true" : "false"); + db2Debug3(" query->hasForUpdate : %s", query->hasForUpdate ? "true" : "false"); + db2Debug3(" query->hasGroupRTE : %s", query->hasGroupRTE ? "true" : "false"); + db2Debug3(" query->hasModifyingCTE: %s", query->hasModifyingCTE ? "true" : "false"); + db2Debug3(" query->hasRecursive : %s", query->hasRecursive ? "true" : "false"); + db2Debug3(" query->hasSubLinks : %s", query->hasSubLinks ? "true" : "false"); + db2Debug3(" query->hasRowSecurity : %s", query->hasRowSecurity ? "true" : "false"); switch (stage) { case UPPERREL_SETOP: // UNION/INTERSECT/EXCEPT db2Debug2(" stage: %d - UPPERREL_SETOP", stage); @@ -93,7 +93,6 @@ void db2GetForeignUpperPaths(PlannerInfo *root, UpperRelationKind stage, RelOptI break; case UPPERREL_FINAL: // any remaining top-level actions db2Debug2(" stage: %d - UPPERREL_FINAL", stage); - fdw_in = (DB2FdwState*)input_rel->fdw_private; break; default: // unknown stage type db2Debug2(" stage: %d - unknown", stage); @@ -108,26 +107,39 @@ void db2GetForeignUpperPaths(PlannerInfo *root, UpperRelationKind stage, RelOptI // state->is_upper = true; // state->upper_kind = stage; // Estimate rows/cost (can be crude initially) - output_rel->rows = clamp_row_est(output_rel->rows); + output_rel->rows = clamp_row_est(output_rel->rows); + state->startup_cost = 10000.0; + state->total_cost = state->startup_cost + output_rel->rows * 10.0; path = (Path*) create_foreign_upper_path( root - , output_rel - , output_rel->reltarget /* pathtarget */ + , input_rel +#if PG_VERSION_NUM >= 90600 + , input_rel->reltarget /* pathtarget */ +#endif , output_rel->rows +#if PG_VERSION_NUM >= 180000 , 0 /* disabled nodes (PG18+) if needed */ +#endif , state->startup_cost - , state->startup_cost + output_rel->rows * 10.0 - , NIL /* no pathkeys initially */ + , state->total_cost + , NIL +#if PG_VERSION_NUM >= 90500 , NULL /* required_outer */ - , NULL /* fdw_outerpath */ +#endif +#if PG_VERSION_NUM >= 170000 + , NIL /* fdw_outerpath */ +#endif , (void*)state ); -// add_path(output_rel, path); +// add_path(input_rel, path); } } else { db2Debug2(" not pushable"); } } else { - db2Debug2(" no root or no query"); + db2Debug2(" skipping this call"); + db2Debug2(" root: %x", root); + db2Debug2(" root->parse: %x", root->parse); + db2Debug2(" output_rel->fdw_private: %x", output_rel->fdw_private); } db2Debug1("< %s::db2GetForeignUpperPaths",__FILE__); } From 7ae4113a97e55bc208575cdd6ca1e8d0234873c1 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Tue, 20 Jan 2026 16:32:06 +0100 Subject: [PATCH 007/120] reworks all the depare functions to match the pg_fdw implementation more closely --- include/db2_fdw.h | 14 - source/db2GetForeignJoinPaths.c | 10 +- source/db2GetForeignPaths.c | 10 +- source/db2GetForeignPlan.c | 130 +---- source/db2GetForeignRelSize.c | 45 +- source/db2GetForeignUpperPaths.c | 29 +- source/db2_fdw_utils.c | 813 +++++++++++++++++-------------- 7 files changed, 502 insertions(+), 549 deletions(-) diff --git a/include/db2_fdw.h b/include/db2_fdw.h index 8a4dcb6..97f27c9 100644 --- a/include/db2_fdw.h +++ b/include/db2_fdw.h @@ -67,13 +67,6 @@ #endif /* PG_VERSION_NUM */ #endif /* POSTGRES_H */ -/* this one is safe to include and gives us Oid */ -/* -#include -#include -#include -*/ - /* db2_fdw version */ #define DB2_FDW_VERSION "18.1.1" /* number of bytes to read per LOB chunk */ @@ -131,13 +124,6 @@ typedef enum { FDW_SERIALIZATION_FAILURE } db2error; -/* -#ifndef SQL_H_SQLCLI1 -#include "DB2FdwState.h" -#include "DB2FdwOption.h" -#endif -*/ - #define OPT_NLS_LANG "nls_lang" #define OPT_DBSERVER "dbserver" #define OPT_USER "user" diff --git a/source/db2GetForeignJoinPaths.c b/source/db2GetForeignJoinPaths.c index 7869702..7ce181b 100644 --- a/source/db2GetForeignJoinPaths.c +++ b/source/db2GetForeignJoinPaths.c @@ -15,13 +15,13 @@ /** external prototypes */ extern void db2Debug1 (const char* message, ...); -extern char* deparseExpr (DB2Session* session, RelOptInfo * foreignrel, Expr* expr, const DB2Table* db2Table, List** params); +extern char* deparseExpr (PlannerInfo* root, RelOptInfo* foreignrel, Expr* expr, List** params); extern char* db2strdup (const char* source); extern void* db2alloc (const char* type, size_t size); /** local prototypes */ void db2GetForeignJoinPaths(PlannerInfo* root, RelOptInfo* joinrel, RelOptInfo* outerrel, RelOptInfo* innerrel, JoinType jointype, JoinPathExtraData* extra); -bool foreign_join_ok (PlannerInfo* root, RelOptInfo* joinrel, JoinType jointype, RelOptInfo* outerrel, RelOptInfo* innerrel, JoinPathExtraData* extra); +static bool foreign_join_ok (PlannerInfo* root, RelOptInfo* joinrel, JoinType jointype, RelOptInfo* outerrel, RelOptInfo* innerrel, JoinPathExtraData* extra); /** db2GetForeignJoinPaths * Add possible ForeignPath to joinrel if the join is safe to push down. @@ -129,7 +129,7 @@ void db2GetForeignJoinPaths (PlannerInfo * root, RelOptInfo * joinrel, RelOptInf * to the foreign server. As a side effect, save information we obtain in this * function to DB2FdwState passed in. */ -bool foreign_join_ok (PlannerInfo * root, RelOptInfo * joinrel, JoinType jointype, RelOptInfo * outerrel, RelOptInfo * innerrel, JoinPathExtraData * extra) { +static bool foreign_join_ok (PlannerInfo * root, RelOptInfo * joinrel, JoinType jointype, RelOptInfo * outerrel, RelOptInfo * innerrel, JoinPathExtraData * extra) { DB2FdwState* fdwState = NULL; DB2FdwState* fdwState_o = NULL; DB2FdwState* fdwState_i = NULL; @@ -175,10 +175,10 @@ bool foreign_join_ok (PlannerInfo * root, RelOptInfo * joinrel, JoinType jointyp * Check which ones can be pushed down. */ foreach (lc, otherclauses) { - char *tmp = NULL; + char *tmp = NULL; Expr *expr = (Expr *) lfirst (lc); - tmp = deparseExpr (fdwState->session, joinrel, expr, fdwState->db2Table, &(fdwState->params)); + tmp = deparseExpr (root, joinrel, expr, &(fdwState->params)); if (tmp == NULL) fdwState->local_conds = lappend (fdwState->local_conds, expr); diff --git a/source/db2GetForeignPaths.c b/source/db2GetForeignPaths.c index b27f6ea..3b7bd36 100644 --- a/source/db2GetForeignPaths.c +++ b/source/db2GetForeignPaths.c @@ -15,11 +15,13 @@ /** external prototypes */ extern void db2Debug1 (const char* message, ...); -extern char* deparseExpr (DB2Session* session, RelOptInfo * foreignrel, Expr* expr, const DB2Table* db2Table, List** params); +extern char* deparseExpr (PlannerInfo* root, RelOptInfo* foreignrel, Expr* expr, List** params); /** local prototypes */ void db2GetForeignPaths (PlannerInfo* root, RelOptInfo* baserel, Oid foreigntableid); -Expr* find_em_expr_for_rel(EquivalenceClass * ec, RelOptInfo * rel); +#ifndef OLD_FDW_API +static Expr* find_em_expr_for_rel(EquivalenceClass * ec, RelOptInfo * rel); +#endif /** db2GetForeignPaths * Create a ForeignPath node and add it as only possible path. @@ -62,7 +64,7 @@ void db2GetForeignPaths(PlannerInfo* root, RelOptInfo* baserel, Oid foreigntable can_pushdown = false; } - if (can_pushdown && ((sort_clause = deparseExpr (fdwState->session, baserel, em_expr, fdwState->db2Table, &(fdwState->params))) != NULL)) { + if (can_pushdown && ((sort_clause = deparseExpr (root, baserel, em_expr, &(fdwState->params))) != NULL)) { /* keep usable_pathkeys for later use. */ usable_pathkeys = lappend (usable_pathkeys, pathkey); @@ -126,7 +128,7 @@ void db2GetForeignPaths(PlannerInfo* root, RelOptInfo* baserel, Oid foreigntable * Find an equivalence class member expression, all of whose Vars come from * the indicated relation. */ -Expr* find_em_expr_for_rel (EquivalenceClass* ec, RelOptInfo* rel) { +static Expr* find_em_expr_for_rel (EquivalenceClass* ec, RelOptInfo* rel) { ListCell* lc_em = NULL; Expr* result = NULL; db2Debug1("> find_em_expr_for_rel"); diff --git a/source/db2GetForeignPlan.c b/source/db2GetForeignPlan.c index 3259e32..28a37ba 100644 --- a/source/db2GetForeignPlan.c +++ b/source/db2GetForeignPlan.c @@ -21,7 +21,7 @@ /** external prototypes */ extern List* serializePlanData (DB2FdwState* fdwState); -extern char* deparseExpr (DB2Session* session, RelOptInfo * foreignrel, Expr* expr, const DB2Table* db2Table, List** params); +extern void deparseFromExprForRel (PlannerInfo* root, RelOptInfo* foreignrel, StringInfo buf, List** params_list); extern void checkDataType (short db2type, int scale, Oid pgtype, const char* tablename, const char* colname); extern void db2Debug1 (const char* message, ...); extern void db2Debug2 (const char* message, ...); @@ -30,13 +30,10 @@ extern void db2free (void* p); extern char* db2strdup (const char* p); /** local prototypes */ -const char* get_jointype_name (JoinType jointype); -List* build_tlist_to_deparse(RelOptInfo* foreignrel); -void getUsedColumns (Expr* expr, DB2Table* db2Table, int foreignrelid); -void appendConditions (List* exprs, StringInfo buf, RelOptInfo* joinrel, List** params_list); -char* createQuery (DB2FdwState* fdwState, RelOptInfo* foreignrel, bool modify, List* query_pathkeys); -void deparseFromExprForRel (DB2FdwState* fdwState, StringInfo buf, RelOptInfo* foreignrel, List** params_list); ForeignScan* db2GetForeignPlan (PlannerInfo* root, RelOptInfo* foreignrel, Oid foreigntableid, ForeignPath* best_path, List* tlist, List* scan_clauses , Plan* outer_plan); +static void createQuery (PlannerInfo* root, RelOptInfo* foreignrel, bool modify, List* query_pathkeys); +static void getUsedColumns (Expr* expr, DB2Table* db2Table, int foreignrelid); +static List* build_tlist_to_deparse(RelOptInfo* foreignrel); /** db2GetForeignPlan * Construct a ForeignScan node containing the serialized DB2FdwState, @@ -140,7 +137,7 @@ ForeignScan* db2GetForeignPlan (PlannerInfo* root, RelOptInfo* foreignrel, Oid f } } /* create remote query */ - fdwState->query = createQuery (fdwState, foreignrel, for_update, best_path->path.pathkeys); + createQuery (root, foreignrel, for_update, best_path->path.pathkeys); db2Debug2(" db2_fdw: remote query is: %s", fdwState->query); /* get PostgreSQL column data types, check that they match DB2's */ for (i = 0; i < fdwState->db2Table->ncols; ++i) { @@ -176,7 +173,8 @@ ForeignScan* db2GetForeignPlan (PlannerInfo* root, RelOptInfo* foreignrel, Oid f * which will be translated to ORDER BY clauses if possible. * As a side effect for base relations, we also mark the used columns in db2Table. */ -char* createQuery (DB2FdwState* fdwState, RelOptInfo* foreignrel, bool modify, List* query_pathkeys) { +static void createQuery (PlannerInfo* root, RelOptInfo* foreignrel, bool modify, List* query_pathkeys) { + DB2FdwState* fdwState = (DB2FdwState*) foreignrel->fdw_private; ListCell* cell; bool in_quote = false; int i, index; @@ -234,7 +232,7 @@ char* createQuery (DB2FdwState* fdwState, RelOptInfo* foreignrel, bool modify, L /* append FROM clause */ appendStringInfo (&query, " FROM "); - deparseFromExprForRel (fdwState, &query, foreignrel, &(fdwState->params)); + deparseFromExprForRel (root, foreignrel, &query, &(fdwState->params)); /* * For inner joins, all conditions that are pushed down get added @@ -294,89 +292,16 @@ if (!pg_md5_hash (query.data, strlen (query.data), md5)) { initStringInfo (&result); appendStringInfo (&result, "SELECT /*%s*/ %s", md5, query.data); db2free (query.data); - - db2Debug1("< createQuery returns: '%s'",result.data); - return result.data; -} - -/** deparseFromExprForRel - * Construct FROM clause for given relation. - * The function constructs ... JOIN ... ON ... for join relation. For a base - * relation it just returns the table name. - * All tables get an alias based on the range table index. - */ -void deparseFromExprForRel (DB2FdwState* fdwState, StringInfo buf, RelOptInfo* foreignrel, List** params_list) { - db2Debug1("> deparseFromExprForRel"); - db2Debug2(" buf: '%s",buf->data); - if (IS_SIMPLE_REL (foreignrel)) { - appendStringInfo (buf, "%s", fdwState->db2Table->name); - - appendStringInfo (buf, " %s%d", REL_ALIAS_PREFIX, foreignrel->relid); - } else { - /* join relation */ - RelOptInfo *rel_o = fdwState->outerrel; - RelOptInfo *rel_i = fdwState->innerrel; - StringInfoData join_sql_o; - StringInfoData join_sql_i; - DB2FdwState* fdwState_o = (DB2FdwState*) rel_o->fdw_private; - DB2FdwState* fdwState_i = (DB2FdwState*) rel_i->fdw_private; - - /* Deparse outer relation */ - initStringInfo (&join_sql_o); - deparseFromExprForRel (fdwState_o, &join_sql_o, rel_o, params_list); - - /* Deparse inner relation */ - initStringInfo (&join_sql_i); - deparseFromExprForRel (fdwState_i, &join_sql_i, rel_i, params_list); - - /* - * For a join relation FROM clause entry is deparsed as - * - * (outer relation) (inner relation) ON joinclauses - */ - appendStringInfo (buf, "(%s %s JOIN %s ON ", join_sql_o.data, get_jointype_name (fdwState->jointype), join_sql_i.data); - - /* we can only get here if the join is pushed down, so there are join clauses */ - Assert (fdwState->joinclauses); - appendConditions (fdwState->joinclauses, buf, foreignrel, params_list); - - /* End the FROM clause entry. */ - appendStringInfo (buf, ")"); - } - db2Debug2(" buf: '%s'",buf->data); - db2Debug1("< deparseFromExprForRel"); -} - -/** appendConditions - * Deparse conditions from the provided list and append them to buf. - * The conditions in the list are assumed to be ANDed. - * This function is used to deparse JOIN ... ON clauses. - */ -void appendConditions(List* exprs, StringInfo buf, RelOptInfo* joinrel, List** params_list) { - ListCell *lc = NULL; - bool is_first = true; - char *where = NULL; - - db2Debug1("> appendConditions( buf = '%s' )", buf->data); - foreach (lc, exprs) - { - Expr *expr = (Expr *)lfirst(lc); - /* connect expressions with AND */ - if (!is_first) - appendStringInfo(buf, " AND "); - /* deparse and append a join condition */ - where = deparseExpr(NULL, joinrel, expr, NULL, params_list); - appendStringInfo(buf, "%s", where); - is_first = false; - } - db2Debug2(" buf.data: '%s'", buf->data); - db2Debug1("< appendConditions"); + fdwState->query = (result.len > 0) ? db2strdup(result.data) : NULL; + db2free(result.data); + db2Debug2(" query: %s",fdwState->query); + db2Debug1("< createQuery"); } /** getUsedColumns * Set "used=true" in db2Table for all columns used in the expression. */ -void getUsedColumns (Expr* expr, DB2Table* db2Table, int foreignrelid) { +static void getUsedColumns (Expr* expr, DB2Table* db2Table, int foreignrelid) { ListCell* cell; Var* variable; int index; @@ -598,7 +523,7 @@ void getUsedColumns (Expr* expr, DB2Table* db2Table, int foreignrelid) { * The output targetlist contains the columns that need to be fetched from the * foreign server for the given relation. */ -List* build_tlist_to_deparse (RelOptInfo* foreignrel) { +static List* build_tlist_to_deparse (RelOptInfo* foreignrel) { List* tlist = NIL; DB2FdwState* fdwState = (DB2FdwState*) foreignrel->fdw_private; @@ -614,30 +539,3 @@ List* build_tlist_to_deparse (RelOptInfo* foreignrel) { return tlist; } -/** Output join name for given join type - */ -const char* get_jointype_name (JoinType jointype) { - char* type = NULL; - db2Debug1("> get_jointype_name"); - switch (jointype) { - case JOIN_INNER: - type = "INNER"; - break; - case JOIN_LEFT: - type = "LEFT"; - break; - case JOIN_RIGHT: - type = "RIGHT"; - break; - case JOIN_FULL: - type= "FULL"; - break; - default: - /* Shouldn't come here, but protect from buggy code. */ - elog (ERROR, "unsupported join type %d", jointype); - break; - } - db2Debug2(" type: '%s'",type); - db2Debug1("< get_jointype_name"); - return type; -} diff --git a/source/db2GetForeignRelSize.c b/source/db2GetForeignRelSize.c index 968ae35..00ccc29 100644 --- a/source/db2GetForeignRelSize.c +++ b/source/db2GetForeignRelSize.c @@ -14,12 +14,11 @@ /** external prototypes */ extern DB2FdwState* db2GetFdwState (Oid foreigntableid, double* sample_percent, bool describe); extern void db2Debug1 (const char* message, ...); -extern char* deparseExpr (DB2Session* session, RelOptInfo * foreignrel, Expr* expr, const DB2Table* db2Table, List** params); +extern char* deparseWhereConditions (PlannerInfo* root, RelOptInfo* baserel); extern void db2free (void* p); /** local prototypes */ void db2GetForeignRelSize (PlannerInfo* root, RelOptInfo* baserel, Oid foreigntableid); -char* deparseWhereConditions(DB2FdwState* fdwState, RelOptInfo* baserel, List** local_conds, List** remote_conds); /** db2GetForeignRelSize * Get an DB2FdwState for this foreign scan. @@ -34,6 +33,8 @@ void db2GetForeignRelSize (PlannerInfo* root, RelOptInfo* baserel, Oid foreignta db2Debug1("> db2GetForeignRelSize"); /* get connection options, connect and get the remote table description */ fdwState = db2GetFdwState(foreigntableid, NULL, true); + /* store the state so that the other planning functions can use it */ + baserel->fdw_private = (void *) fdwState; /** Store the table OID in each table column. * This is redundant for base relations, but join relations will * have columns from different tables, and we have to keep track of them. @@ -46,11 +47,7 @@ void db2GetForeignRelSize (PlannerInfo* root, RelOptInfo* baserel, Oid foreignta * Those conditions that can be pushed down will be collected into * an DB2 WHERE clause. */ - fdwState->where_clause = deparseWhereConditions ( fdwState - , baserel - , &(fdwState->local_conds) - , &(fdwState->remote_conds) - ); + fdwState->where_clause = deparseWhereConditions ( root, baserel ); /* release DB2 session (will be cached) */ db2free (fdwState->session); @@ -71,39 +68,5 @@ void db2GetForeignRelSize (PlannerInfo* root, RelOptInfo* baserel, Oid foreignta } /* estimate total cost as startup cost + 10 * (returned rows) */ fdwState->total_cost = fdwState->startup_cost + baserel->rows * 10.0; - /* store the state so that the other planning functions can use it */ - baserel->fdw_private = (void *) fdwState; db2Debug1("< db2GetForeignRelSize"); } - -/** deparseWhereConditions - * Classify conditions into remote_conds or local_conds. - * Those conditions that can be pushed down will be collected into - * an DB2 WHERE clause that is returned. - */ -char* deparseWhereConditions (DB2FdwState *fdwState, RelOptInfo * baserel, List ** local_conds, List ** remote_conds) { - List* conditions = baserel->baserestrictinfo; - ListCell* cell; - char* where; - char* keyword = "WHERE"; - StringInfoData where_clause; - - db2Debug1("> deparseWhereCondition"); - initStringInfo (&where_clause); - foreach (cell, conditions) { - /* check if the condition can be pushed down */ - where = deparseExpr (fdwState->session, baserel, ((RestrictInfo *) lfirst (cell))->clause, fdwState->db2Table, &(fdwState->params)); - if (where != NULL) { - *remote_conds = lappend (*remote_conds, ((RestrictInfo *) lfirst (cell))->clause); - - /* append new WHERE clause to query string */ - appendStringInfo (&where_clause, " %s %s", keyword, where); - keyword = "AND"; - db2free (where); - } else { - *local_conds = lappend (*local_conds, ((RestrictInfo *) lfirst (cell))->clause); - } - } - db2Debug1("< deparseWhereCondition - where_clause: '%s'",where_clause.data); - return where_clause.data; -} diff --git a/source/db2GetForeignUpperPaths.c b/source/db2GetForeignUpperPaths.c index 8ef044f..2a2c299 100644 --- a/source/db2GetForeignUpperPaths.c +++ b/source/db2GetForeignUpperPaths.c @@ -14,15 +14,16 @@ extern void db2Debug2 (const char* message, ...); extern void db2Debug3 (const char* message, ...); extern void* db2alloc (const char* type, size_t size); extern char* db2strdup (const char* source); -extern char* deparseExpr (DB2Session* session, RelOptInfo* foreignrel, Expr* expr, const DB2Table* db2Table, List** params); +extern void* db2free (void* p); +extern char* deparseExpr (PlannerInfo* root, RelOptInfo* foreignrel, Expr* expr, List** params); /** local prototypes */ void db2GetForeignUpperPaths (PlannerInfo *root, UpperRelationKind stage, RelOptInfo *input_rel, RelOptInfo *output_rel, void *extra); -DB2FdwState* db2CloneFdwStateUpper (PlannerInfo* root, const DB2FdwState* fdw_in, RelOptInfo* input_rel, RelOptInfo* output_rel); -DB2Table* db2CloneDb2TableForPlan (const DB2Table* src); -DB2Column* db2CloneDb2ColumnForPlan (const DB2Column* src); -bool db2_is_shippable (PlannerInfo* root, UpperRelationKind stage, RelOptInfo* input_rel, RelOptInfo* output_rel, const DB2FdwState* fdw_in); -bool db2_is_shippable_expr (PlannerInfo* root, RelOptInfo* foreignrel, const DB2FdwState* fdw_in, Expr* expr, const char* label); +static DB2FdwState* db2CloneFdwStateUpper (PlannerInfo* root, const DB2FdwState* fdw_in, RelOptInfo* input_rel, RelOptInfo* output_rel); +static DB2Table* db2CloneDb2TableForPlan (const DB2Table* src); +static DB2Column* db2CloneDb2ColumnForPlan (const DB2Column* src); +static bool db2_is_shippable (PlannerInfo* root, UpperRelationKind stage, RelOptInfo* input_rel, RelOptInfo* output_rel, const DB2FdwState* fdw_in); +static bool db2_is_shippable_expr (PlannerInfo* root, RelOptInfo* foreignrel, const DB2FdwState* fdw_in, Expr* expr, const char* label); void db2GetForeignUpperPaths(PlannerInfo *root, UpperRelationKind stage, RelOptInfo *input_rel, RelOptInfo *output_rel, void *extra) { db2Debug1("> %s::db2GetForeignUpperPaths",__FILE__); @@ -151,7 +152,7 @@ void db2GetForeignUpperPaths(PlannerInfo *root, UpperRelationKind stage, RelOptI * rewrite the params list (createQuery will NULL out entries). We must avoid * those mutations affecting the original baserel/joinrel planning state. */ -DB2FdwState* db2CloneFdwStateUpper(PlannerInfo* root, const DB2FdwState* fdw_in, RelOptInfo* input_rel, RelOptInfo* output_rel) { +static DB2FdwState* db2CloneFdwStateUpper(PlannerInfo* root, const DB2FdwState* fdw_in, RelOptInfo* input_rel, RelOptInfo* output_rel) { DB2FdwState* copy = NULL; db2Debug1("> %s::db2CloneFdwStateUpper", __FILE__); @@ -204,7 +205,7 @@ DB2FdwState* db2CloneFdwStateUpper(PlannerInfo* root, const DB2FdwState* fdw_in, return copy; } -DB2Table* db2CloneDb2TableForPlan(const DB2Table* src) { +static DB2Table* db2CloneDb2TableForPlan(const DB2Table* src) { DB2Table* dst = NULL; int i; @@ -231,7 +232,7 @@ DB2Table* db2CloneDb2TableForPlan(const DB2Table* src) { return dst; } -DB2Column* db2CloneDb2ColumnForPlan(const DB2Column* src) { +static DB2Column* db2CloneDb2ColumnForPlan(const DB2Column* src) { DB2Column* dst = NULL; db2Debug1("> %s::db2CloneDb2ColumnForPlan", __FILE__); @@ -252,7 +253,7 @@ DB2Column* db2CloneDb2ColumnForPlan(const DB2Column* src) { return dst; } -bool db2_is_shippable(PlannerInfo* root, UpperRelationKind stage, RelOptInfo* input_rel, RelOptInfo* output_rel, const DB2FdwState* fdw_in) { +static bool db2_is_shippable(PlannerInfo* root, UpperRelationKind stage, RelOptInfo* input_rel, RelOptInfo* output_rel, const DB2FdwState* fdw_in) { bool fResult = false; db2Debug1("> %s::db2_is_shippable", __FILE__); @@ -322,7 +323,7 @@ bool db2_is_shippable(PlannerInfo* root, UpperRelationKind stage, RelOptInfo* in return fResult; } -bool db2_is_shippable_expr(PlannerInfo* root, RelOptInfo* foreignrel, const DB2FdwState* fdw_in, Expr* expr, const char* label) { +static bool db2_is_shippable_expr(PlannerInfo* root, RelOptInfo* foreignrel, const DB2FdwState* fdw_in, Expr* expr, const char* label) { bool fResult = false; db2Debug1("> %s::db2_is_shippable_expr", __FILE__); @@ -333,18 +334,20 @@ bool db2_is_shippable_expr(PlannerInfo* root, RelOptInfo* foreignrel, const DB2F } else if (contain_agg_clause((Node*) expr)) { List* params = NIL; char* deparsed = NULL; - deparsed = deparseExpr(fdw_in->session, foreignrel, expr, fdw_in->db2Table, ¶ms); + deparsed = deparseExpr(root, foreignrel, expr, ¶ms); db2Debug2(" deparsed: %s", deparsed); fResult = (deparsed != NULL); + db2free(deparsed); } else if (contain_window_function((Node*) expr)) { db2Debug2(" %s contains window function; not shippable", label ? label : "expr"); fResult = false; } else { List* params = NIL; char* deparsed = NULL; - deparsed = deparseExpr(fdw_in->session, foreignrel, expr, fdw_in->db2Table, ¶ms); + deparsed = deparseExpr(root, foreignrel, expr, ¶ms); db2Debug2(" deparsed: %s", deparsed); fResult = (deparsed != NULL); + db2free(deparsed); } db2Debug1("> %s::db2_is_shippable_expr : %s", __FILE__, fResult ? "true" : "false"); return fResult; diff --git a/source/db2_fdw_utils.c b/source/db2_fdw_utils.c index dba06d4..8b31612 100644 --- a/source/db2_fdw_utils.c +++ b/source/db2_fdw_utils.c @@ -22,6 +22,16 @@ #include "db2_fdw.h" #include "DB2FdwState.h" + +/** Context for deparseExpr */ +typedef struct deparse_expr_cxt { + PlannerInfo* root; /* global planner state */ + RelOptInfo* foreignrel; /* the foreign relation we are planning for */ + RelOptInfo* scanrel; /* the underlying scan relation. Same as foreignrel, when that represents a join or a base relation. */ + StringInfo buf; /* output buffer to append to */ + List** params_list; /* exprs that will become remote Params */ +} deparse_expr_cxt; + /** external prototypes */ extern void db2GetLob (DB2Session* session, DB2Column* column, int cidx, char** value, long* value_len, unsigned long trunc); extern void db2Shutdown (void); @@ -35,22 +45,25 @@ extern void db2free (void* p); /** local prototypes */ void appendAsType (StringInfoData* dest, Oid type); -char* deparseExpr (DB2Session* session, RelOptInfo* foreignrel, Expr* expr, const DB2Table* db2Table, List** params); -char* deparseConstExpr (DB2Session* session, RelOptInfo* foreignrel, Const* expr, const DB2Table* db2Table, List** params); -char* deparseParamExpr (DB2Session* session, RelOptInfo* foreignrel, Param* expr, const DB2Table* db2Table, List** params); -char* deparseVarExpr (DB2Session* session, RelOptInfo* foreignrel, Var* expr, const DB2Table* db2Table, List** params); -char* deparseOpExpr (DB2Session* session, RelOptInfo* foreignrel, OpExpr* expr, const DB2Table* db2Table, List** params); -char* deparseScalarArrayOpExpr (DB2Session* session, RelOptInfo* foreignrel, ScalarArrayOpExpr* expr, const DB2Table* db2Table, List** params); -char* deparseDistinctExpr (DB2Session* session, RelOptInfo* foreignrel, DistinctExpr* expr, const DB2Table* db2Table, List** params); -char* deparseNullIfExpr (DB2Session* session, RelOptInfo* foreignrel, NullIfExpr* expr, const DB2Table* db2Table, List** params); -char* deparseBoolExpr (DB2Session* session, RelOptInfo* foreignrel, BoolExpr* expr, const DB2Table* db2Table, List** params); -char* deparseCaseExpr (DB2Session* session, RelOptInfo* foreignrel, CaseExpr* expr, const DB2Table* db2Table, List** params); -char* deparseCoalesceExpr (DB2Session* session, RelOptInfo* foreignrel, CoalesceExpr* expr, const DB2Table* db2Table, List** params); -char* deparseFuncExpr (DB2Session* session, RelOptInfo* foreignrel, FuncExpr* expr, const DB2Table* db2Table, List** params); -char* deparseAggref (DB2Session* session, RelOptInfo* foreignrel, Aggref* expr, const DB2Table* db2Table, List** params); -char* deparseCoerceViaIOExpr (CoerceViaIO* expr); +void deparseFromExprForRel (PlannerInfo* root, RelOptInfo* foreignrel, StringInfo buf, List** params_list); +char* deparseWhereConditions (PlannerInfo* root, RelOptInfo* rel); +char* deparseExpr (PlannerInfo* root, RelOptInfo* rel, Expr* expr, List** params); +static void deparseExprInt (Expr* expr, deparse_expr_cxt* ctx); +static void deparseConstExpr (Const* expr, deparse_expr_cxt* ctx); +static void deparseParamExpr (Param* expr, deparse_expr_cxt* ctx); +static void deparseVarExpr (Var* expr, deparse_expr_cxt* ctx); +static void deparseOpExpr (OpExpr* expr, deparse_expr_cxt* ctx); +static void deparseScalarArrayOpExpr (ScalarArrayOpExpr* expr, deparse_expr_cxt* ctx); +static void deparseDistinctExpr (DistinctExpr* expr, deparse_expr_cxt* ctx); +static void deparseNullIfExpr (NullIfExpr* expr, deparse_expr_cxt* ctx); +static void deparseBoolExpr (BoolExpr* expr, deparse_expr_cxt* ctx); +static void deparseCaseExpr (CaseExpr* expr, deparse_expr_cxt* ctx); +static void deparseCoalesceExpr (CoalesceExpr* expr, deparse_expr_cxt* ctx); +static void deparseFuncExpr (FuncExpr* expr, deparse_expr_cxt* ctx); +static void deparseAggref (Aggref* expr, deparse_expr_cxt* ctx); +static void deparseCoerceViaIOExpr (CoerceViaIO* expr, deparse_expr_cxt* ctx); #if PG_VERSION_NUM >= 100000 -char* deparseSQLValueFuncExpr (SQLValueFunction* expr); +static void deparseSQLValueFuncExpr (SQLValueFunction* expr, deparse_expr_cxt* ctx); #endif char* datumToString (Datum datum, Oid type); char* guessNlsLang (char* nls_lang); @@ -60,6 +73,7 @@ char* deparseInterval (Datum datum); void exitHook (int code, Datum arg); void convertTuple (DB2FdwState* fdw_state, Datum* values, bool* nulls, bool trunc_lob) ; void errorContextCallback (void* arg); +static const char* get_jointype_name (JoinType jointype); /** appendAsType * Append "s" to "dest", adding appropriate casts for datetime "type". @@ -101,96 +115,204 @@ void appendAsType (StringInfoData* dest, Oid type) { || (x) == NUMERICOID || (x) == DATEOID || (x) == TIMEOID || (x) == TIMESTAMPOID \ || (x) == TIMESTAMPTZOID || (x) == INTERVALOID) +/** deparseFromExprForRel + * Construct FROM clause for given relation. + * The function constructs ... JOIN ... ON ... for join relation. For a base + * relation it just returns the table name. + * All tables get an alias based on the range table index. + */ +void deparseFromExprForRel (PlannerInfo* root, RelOptInfo* foreignrel, StringInfo buf, List** params_list) { + DB2FdwState* fdwState = (DB2FdwState*) foreignrel->fdw_private; + + db2Debug1("> deparseFromExprForRel"); + db2Debug2(" buf: '%s",buf->data); + if (IS_SIMPLE_REL (foreignrel)) { + appendStringInfo (buf, "%s", fdwState->db2Table->name); + appendStringInfo (buf, " %s%d", REL_ALIAS_PREFIX, foreignrel->relid); + } else { + /* join relation */ + RelOptInfo* rel_o = fdwState->outerrel; + RelOptInfo* rel_i = fdwState->innerrel; + StringInfoData join_sql_o; + StringInfoData join_sql_i; + ListCell* lc = NULL; + bool is_first = true; + char* where = NULL; + + /* Deparse outer relation */ + initStringInfo (&join_sql_o); + deparseFromExprForRel (root, rel_o, &join_sql_o, params_list); + + /* Deparse inner relation */ + initStringInfo (&join_sql_i); + deparseFromExprForRel (root, rel_i, &join_sql_i, params_list); + + // For a join relation FROM clause entry is deparsed as (outer relation) (inner relation) ON joinclauses + appendStringInfo (buf, "(%s %s JOIN %s ON ", join_sql_o.data, get_jointype_name (fdwState->jointype), join_sql_i.data); + + /* we can only get here if the join is pushed down, so there are join clauses */ + Assert (fdwState->joinclauses); + + foreach (lc, fdwState->joinclauses) { + Expr *expr = (Expr *)lfirst(lc); + /* connect expressions with AND */ + if (!is_first) + appendStringInfo(buf, " AND "); + /* deparse and append a join condition */ + where = deparseExpr(root, foreignrel, expr, params_list); + appendStringInfo(buf, "%s", where); + is_first = false; + } + /* End the FROM clause entry. */ + appendStringInfo (buf, ")"); + } + db2Debug2(" buf: '%s'",buf->data); + db2Debug1("< deparseFromExprForRel"); +} + +/** deparseWhereConditions + * Classify conditions into remote_conds or local_conds. + * Those conditions that can be pushed down will be collected into + * an DB2 WHERE clause that is returned. + */ +char* deparseWhereConditions (PlannerInfo* root, RelOptInfo * rel) { + List* conditions = rel->baserestrictinfo; + DB2FdwState* fdwState = (DB2FdwState*) rel->fdw_private; + ListCell* cell; + char* where; + char* keyword = "WHERE"; + StringInfoData where_clause; + + db2Debug1("> deparseWhereCondition"); + initStringInfo (&where_clause); + foreach (cell, conditions) { + /* check if the condition can be pushed down */ + where = deparseExpr (root, rel, ((RestrictInfo*) lfirst (cell))->clause, &(fdwState->params)); + if (where != NULL) { + fdwState->remote_conds = lappend (fdwState->remote_conds, ((RestrictInfo*) lfirst (cell))->clause); + + /* append new WHERE clause to query string */ + appendStringInfo (&where_clause, " %s %s", keyword, where); + keyword = "AND"; + db2free (where); + } else { + fdwState->local_conds = lappend (fdwState->local_conds, ((RestrictInfo*) lfirst (cell))->clause); + } + } + db2Debug1("< deparseWhereCondition : %s",where_clause.data); + return where_clause.data; +} + /** deparseExpr * Create and return an DB2 SQL string from "expr". * Returns NULL if that is not possible, else an allocated string. * As a side effect, all Params incorporated in the WHERE clause * will be stored in "params". */ -char* deparseExpr (DB2Session* session, RelOptInfo* foreignrel, Expr* expr, const DB2Table* db2Table, List** params) { +char* deparseExpr (PlannerInfo* root, RelOptInfo* rel, Expr* expr, List** params) { char* retValue = NULL; db2Debug1("> %s::deparseExpr", __FILE__); + if (expr != NULL) { + deparse_expr_cxt* ctx = db2alloc("deparseExpr.context", sizeof(deparse_expr_cxt)); + StringInfoData buf; + + initStringInfo(&buf); + + ctx->root = root; + ctx->foreignrel = rel; + ctx->scanrel = rel; + ctx->buf = &buf; + ctx->params_list = params; + deparseExprInt(expr, ctx); + retValue = (buf.len > 0) ? db2strdup(buf.data) : NULL; + db2free(ctx->buf->data); + db2free(ctx); + } + db2Debug1("< %s::deparseExpr: %s", __FILE__, retValue); + return retValue; +} + +static void deparseExprInt (Expr* expr, deparse_expr_cxt* ctx) { + db2Debug1("> %s::deparseExprInt", __FILE__); db2Debug2(" expr: %x",expr); if (expr != NULL) { db2Debug2(" expr->type: %d",expr->type); switch (expr->type) { case T_Const: { - retValue = deparseConstExpr(session, foreignrel, (Const*)expr, db2Table, params); + deparseConstExpr((Const*)expr, ctx); } break; case T_Param: { - retValue = deparseParamExpr(session, foreignrel, (Param*) expr, db2Table, params); + deparseParamExpr((Param*) expr, ctx); } break; case T_Var: { - retValue = deparseVarExpr (session, foreignrel, (Var*)expr, db2Table, params); + deparseVarExpr ((Var*)expr, ctx); } break; case T_OpExpr: { - retValue = deparseOpExpr (session, foreignrel, (OpExpr*)expr, db2Table, params); + deparseOpExpr ((OpExpr*)expr, ctx); } break; case T_ScalarArrayOpExpr: { - retValue = deparseScalarArrayOpExpr (session, foreignrel, (ScalarArrayOpExpr*)expr, db2Table, params); + deparseScalarArrayOpExpr ((ScalarArrayOpExpr*)expr, ctx); } break; case T_DistinctExpr: { - retValue = deparseDistinctExpr(session, foreignrel, (DistinctExpr*)expr, db2Table, params); + deparseDistinctExpr ((DistinctExpr*)expr, ctx); } break; case T_NullIfExpr: { - retValue = deparseNullIfExpr(session, foreignrel, (NullIfExpr*)expr, db2Table, params); + deparseNullIfExpr ((NullIfExpr*)expr, ctx); } break; case T_BoolExpr: { - retValue = deparseBoolExpr(session, foreignrel, (BoolExpr*)expr, db2Table, params); + deparseBoolExpr ((BoolExpr*)expr, ctx); } break; case T_RelabelType: { - retValue = deparseExpr (session, foreignrel, ((RelabelType*)expr)->arg, db2Table, params); + deparseExprInt (((RelabelType*)expr)->arg, ctx); } break; case T_CoerceToDomain: { - retValue = deparseExpr (session, foreignrel, ((CoerceToDomain*)expr)->arg, db2Table, params); + deparseExprInt (((CoerceToDomain*)expr)->arg, ctx); } break; case T_CaseExpr: { - retValue = deparseCaseExpr(session, foreignrel, (CaseExpr*)expr, db2Table, params); + deparseCaseExpr ((CaseExpr*)expr, ctx); } break; case T_CoalesceExpr: { - retValue = deparseCoalesceExpr(session, foreignrel, (CoalesceExpr*)expr, db2Table, params); + deparseCoalesceExpr ((CoalesceExpr*)expr, ctx); } break; case T_NullTest: { - StringInfoData result; char* arg = NULL; db2Debug2(" T_NullTest"); - arg = deparseExpr(session, foreignrel, ((NullTest*) expr)->arg, db2Table, params); + arg = deparseExpr(ctx->root, ctx->foreignrel, ((NullTest*) expr)->arg, ctx->params_list); db2Debug2(" T_NullTest arg: %s", arg); if (arg != NULL) { - initStringInfo (&result); - appendStringInfo (&result, "(%s IS %sNULL)", arg, ((NullTest*)expr)->nulltesttype == IS_NOT_NULL ? "NOT " : ""); + appendStringInfo (ctx->buf, "(%s IS %sNULL)", arg, ((NullTest*)expr)->nulltesttype == IS_NOT_NULL ? "NOT " : ""); } - retValue = (arg == NULL) ? arg : result.data; + db2free(arg); } break; case T_FuncExpr: { - retValue = deparseFuncExpr(session, foreignrel, (FuncExpr*)expr, db2Table, params); + deparseFuncExpr((FuncExpr*)expr, ctx); } break; case T_CoerceViaIO: { - retValue = deparseCoerceViaIOExpr((CoerceViaIO*) expr); + deparseCoerceViaIOExpr((CoerceViaIO*) expr, ctx); } break; #if PG_VERSION_NUM >= 100000 case T_SQLValueFunction: { - retValue = deparseSQLValueFuncExpr((SQLValueFunction*)expr); + deparseSQLValueFuncExpr((SQLValueFunction*)expr, ctx); } break; #endif case T_Aggref: { - retValue = deparseAggref(session, foreignrel, (Aggref*)expr, db2Table, params); + deparseAggref((Aggref*)expr, ctx); } break; default: { @@ -200,38 +322,27 @@ char* deparseExpr (DB2Session* session, RelOptInfo* foreignrel, Expr* expr, cons break; } } - db2Debug1("< %s::deparseExpr: %s", __FILE__, retValue); - return retValue; + db2Debug1("< %s::deparseExpr : %s", __FILE__, ctx->buf->data); } -char* deparseConstExpr (DB2Session* session, RelOptInfo* foreignrel, Const* expr, const DB2Table* db2Table, List** params) { - char* value = NULL; - +static void deparseConstExpr (Const* expr, deparse_expr_cxt* ctx) { db2Debug1("> %s::deparseConstExpr", __FILE__); if (expr->constisnull) { /* only translate NULLs of a type DB2 can handle */ if (canHandleType (expr->consttype)) { - StringInfoData result; - initStringInfo (&result); - appendStringInfo (&result, "NULL"); - value = result.data; + appendStringInfo (ctx->buf, "NULL"); } } else { /* get a string representation of the value */ char* c = datumToString (expr->constvalue, expr->consttype); if (c != NULL) { - StringInfoData result; - initStringInfo (&result); - appendStringInfo (&result, "%s", c); - value = result.data; + appendStringInfo (ctx->buf, "%s", c); } } - db2Debug1("< %s::deparseConstExpr: %s", __FILE__, value); - return value; + db2Debug1("< %s::deparseConstExpr", __FILE__); } -char* deparseParamExpr (DB2Session* session, RelOptInfo* foreignrel, Param* expr, const DB2Table* db2Table, List** params) { - char* value = NULL; +static void deparseParamExpr (Param* expr, deparse_expr_cxt* ctx) { #ifdef OLD_FDW_API /* don't try to push down parameters with 9.1 */ db2Debug1("> %s::deparseParamExpr", __FILE__); @@ -245,44 +356,39 @@ char* deparseParamExpr (DB2Session* session, RelOptInfo* foreignrel, Par if (!canHandleType (expr->paramtype) || expr->paramtype == INTERVALOID) { db2Debug2(" !canHhandleType(expr->paramtype %d) || rxpr->paramtype == INTERVALOID)", expr->paramtype); } else { - StringInfoData result; /* find the index in the parameter list */ int index = 0; - foreach (cell, *params) { + foreach (cell, *(ctx->params_list)) { ++index; - if (equal (expr, (Node *) lfirst (cell))) + if (equal (expr, (Node*) lfirst (cell))) break; } if (cell == NULL) { /* add the parameter to the list */ ++index; - *params = lappend (*params, expr); + *(ctx->params_list) = lappend (*(ctx->params_list), expr); } /* parameters will be called :p1, :p2 etc. */ snprintf (parname, 10, ":p%d", index); - initStringInfo (&result); - appendAsType (&result, expr->paramtype); - value = result.data; + appendAsType (ctx->buf, expr->paramtype); } #endif /* OLD_FDW_API */ - db2Debug1("< %s::deparseParamExpr: %s", __FILE__, value); - return value; + db2Debug1("< %s::deparseParamExpr", __FILE__); } -char* deparseVarExpr (DB2Session* session, RelOptInfo* foreignrel, Var* expr, const DB2Table* db2Table, List** params) { - char* value = NULL; +static void deparseVarExpr (Var* expr, deparse_expr_cxt* ctx) { const DB2Table* var_table = NULL; /* db2Table that belongs to a Var */ db2Debug1("> %s::deparseVarExpr", __FILE__); /* check if the variable belongs to one of our foreign tables */ #ifdef JOIN_API - if (IS_SIMPLE_REL (foreignrel)) { + if (IS_SIMPLE_REL (ctx->foreignrel)) { #endif /* JOIN_API */ - if (expr->varno == foreignrel->relid && expr->varlevelsup == 0) - var_table = db2Table; + if (expr->varno == ctx->foreignrel->relid && expr->varlevelsup == 0) + var_table = ((DB2FdwState*)ctx->foreignrel->fdw_private)->db2Table; #ifdef JOIN_API } else { - DB2FdwState* joinstate = (DB2FdwState*) foreignrel->fdw_private; + DB2FdwState* joinstate = (DB2FdwState*) ctx->foreignrel->fdw_private; DB2FdwState* outerstate = (DB2FdwState*) joinstate->outerrel->fdw_private; DB2FdwState* innerstate = (DB2FdwState*) joinstate->innerrel->fdw_private; /* we can't get here if the foreign table has no columns, so this is safe */ @@ -310,10 +416,7 @@ char* deparseVarExpr (DB2Session* session, RelOptInfo* foreignrel, Var } /* if no DB2 column corresponds, translate as NULL */ if (index == -1) { - StringInfoData result; - initStringInfo (&result); - appendStringInfo (&result, "NULL"); - value = result.data; + appendStringInfo (ctx->buf, "NULL"); } else { /** Don't try to convert a column reference if the type is * converted from a non-string type in DB2 to a string type @@ -324,23 +427,16 @@ char* deparseVarExpr (DB2Session* session, RelOptInfo* foreignrel, Var if ((expr->vartype == TEXTOID || expr->vartype == BPCHAROID || expr->vartype == VARCHAROID) && db2type != DB2_VARCHAR && db2type != DB2_CHAR) { db2Debug2(" vartype: %d", expr->vartype); } else { - StringInfoData result; - StringInfoData alias; - - initStringInfo (&result); /* work around the lack of booleans in DB2 */ if (expr->vartype == BOOLOID) { - appendStringInfo (&result, "("); + appendStringInfo (ctx->buf, "("); } /* qualify with an alias based on the range table index */ - initStringInfo (&alias); - ADD_REL_QUALIFIER (&alias, var_table->cols[index]->varno); - appendStringInfo (&result, "%s%s", alias.data, var_table->cols[index]->colName); + appendStringInfo(ctx->buf, "%s%d.%s", "r", var_table->cols[index]->varno, var_table->cols[index]->colName); /* work around the lack of booleans in DB2 */ if (expr->vartype == BOOLOID) { - appendStringInfo (&result, " <> 0)"); + appendStringInfo (ctx->buf, " <> 0)"); } - value = result.data; } } } @@ -355,12 +451,11 @@ char* deparseVarExpr (DB2Session* session, RelOptInfo* foreignrel, Var if (!canHandleType (expr->vartype) || expr->vartype == INTERVALOID) { db2Debug2(" !canHandleType (vartype %d) || vartype == INTERVALOID", expr->vartype); } else { - StringInfoData result; ListCell* cell = NULL; int index = 0; /* find the index in the parameter list */ - foreach (cell, *params) { + foreach (cell, *(ctx->params_list)) { ++index; if (equal (expr, (Node*) lfirst (cell))) break; @@ -368,21 +463,17 @@ char* deparseVarExpr (DB2Session* session, RelOptInfo* foreignrel, Var if (cell == NULL) { /* add the parameter to the list */ ++index; - *params = lappend (*params, expr); + *(ctx->params_list) = lappend (*(ctx->params_list), expr); } /* parameters will be called :p1, :p2 etc. */ - initStringInfo (&result); - appendStringInfo (&result, ":p%d", index); - value = result.data; + appendStringInfo (ctx->buf, ":p%d", index); } #endif /* OLD_FDW_API */ } - db2Debug1("< %s::deparseVarExpr: %s", __FILE__, value); - return value; + db2Debug1("< %s::deparseVarExpr", __FILE__); } -char* deparseOpExpr (DB2Session* session, RelOptInfo* foreignrel, OpExpr* expr, const DB2Table* db2Table, List** params) { - char* value = NULL; +static void deparseOpExpr (OpExpr* expr, deparse_expr_cxt* ctx) { char* opername = NULL; char oprkind = 0x00; Oid rightargtype= 0; @@ -390,6 +481,7 @@ char* deparseOpExpr (DB2Session* session, RelOptInfo* foreignrel, OpE Oid schema = 0; HeapTuple tuple ; + db2Debug1("> %s::deparseOpExpr", __FILE__); /* get operator name, kind, argument type and schema */ tuple = SearchSysCache1 (OPEROID, ObjectIdGetDatum (expr->opno)); if (!HeapTupleIsValid (tuple)) { @@ -424,50 +516,50 @@ char* deparseOpExpr (DB2Session* session, RelOptInfo* foreignrel, OpE || strcmp (opername, "~~") == 0 || strcmp (opername, "!~~") == 0 || strcmp (opername, "~~*") == 0 || strcmp (opername, "!~~*") == 0 || strcmp (opername, "^") == 0 || strcmp (opername, "%") == 0 || strcmp (opername, "&") == 0 || strcmp (opername, "|/") == 0 || strcmp (opername, "@") == 0) { - char* left = deparseExpr (session, foreignrel, linitial (expr->args), db2Table, params); + char* left = NULL; + + left = deparseExpr (ctx->root, ctx->foreignrel, linitial(expr->args), ctx->params_list); db2Debug2(" left: %s", left); if (left != NULL) { if (oprkind == 'b') { /* binary operator */ - char* right = deparseExpr (session, foreignrel, lsecond (expr->args), db2Table, params); + char* right = NULL; + + right = deparseExpr (ctx->root, ctx->foreignrel, lsecond(expr->args), ctx->params_list); db2Debug2(" right: %s", right); if (right != NULL) { - StringInfoData result; - initStringInfo (&result); if (strcmp (opername, "~~") == 0) { - appendStringInfo (&result, "(%s LIKE %s ESCAPE '\\')", left, right); + appendStringInfo (ctx->buf, "(%s LIKE %s ESCAPE '\\')", left, right); } else if (strcmp (opername, "!~~") == 0) { - appendStringInfo (&result, "(%s NOT LIKE %s ESCAPE '\\')", left, right); + appendStringInfo (ctx->buf, "(%s NOT LIKE %s ESCAPE '\\')", left, right); } else if (strcmp (opername, "~~*") == 0) { - appendStringInfo (&result, "(UPPER(%s) LIKE UPPER(%s) ESCAPE '\\')", left, right); + appendStringInfo (ctx->buf, "(UPPER(%s) LIKE UPPER(%s) ESCAPE '\\')", left, right); } else if (strcmp (opername, "!~~*") == 0) { - appendStringInfo (&result, "(UPPER(%s) NOT LIKE UPPER(%s) ESCAPE '\\')", left, right); + appendStringInfo (ctx->buf, "(UPPER(%s) NOT LIKE UPPER(%s) ESCAPE '\\')", left, right); } else if (strcmp (opername, "^") == 0) { - appendStringInfo (&result, "POWER(%s, %s)", left, right); + appendStringInfo (ctx->buf, "POWER(%s, %s)", left, right); } else if (strcmp (opername, "%") == 0) { - appendStringInfo (&result, "MOD(%s, %s)", left, right); + appendStringInfo (ctx->buf, "MOD(%s, %s)", left, right); } else if (strcmp (opername, "&") == 0) { - appendStringInfo (&result, "BITAND(%s, %s)", left, right); + appendStringInfo (ctx->buf, "BITAND(%s, %s)", left, right); } else { /* the other operators have the same name in DB2 */ - appendStringInfo (&result, "(%s %s %s)", left, opername, right); + appendStringInfo (ctx->buf, "(%s %s %s)", left, opername, right); } - value = result.data; + db2free(right); } } else { - StringInfoData result; - initStringInfo (&result); /* unary operator */ if (strcmp (opername, "|/") == 0) { - appendStringInfo (&result, "SQRT(%s)", left); + appendStringInfo (ctx->buf, "SQRT(%s)", left); } else if (strcmp (opername, "@") == 0) { - appendStringInfo (&result, "ABS(%s)", left); + appendStringInfo (ctx->buf, "ABS(%s)", left); } else { /* unary + or - */ - appendStringInfo (&result, "(%s%s)", opername, left); + appendStringInfo (ctx->buf, "(%s%s)", opername, left); } - value = result.data; } + db2free(left); } } else { /* cannot translate this operator */ @@ -477,17 +569,16 @@ char* deparseOpExpr (DB2Session* session, RelOptInfo* foreignrel, OpE } } db2free (opername); - return value; + db2Debug1("< %s::deparseOpExpr", __FILE__); } -char* deparseScalarArrayOpExpr (DB2Session* session, RelOptInfo* foreignrel, ScalarArrayOpExpr* expr, const DB2Table* db2Table, List** params) { - char* value = NULL; +static void deparseScalarArrayOpExpr (ScalarArrayOpExpr* expr, deparse_expr_cxt* ctx) { char* opername; Oid leftargtype; Oid schema; HeapTuple tuple; - db2Debug1("> %s::deparseExpr", __FILE__); + db2Debug1("> %s::deparseScalarArrayOpExpr", __FILE__); tuple = SearchSysCache1 (OPEROID, ObjectIdGetDatum (expr->opno)); if (!HeapTupleIsValid (tuple)) { elog (ERROR, "cache lookup failed for operator %u", expr->opno); @@ -513,29 +604,32 @@ char* deparseScalarArrayOpExpr (DB2Session* session, RelOptInfo* foreignrel, Sca if (!canHandleType (leftargtype)) { db2Debug2(" cannot Handle Type leftargtype (%d)", leftargtype); } else { - char* left = deparseExpr (session, foreignrel, linitial (expr->args), db2Table, params); - db2Debug2(" left: %s", left); + char* left = NULL; + char* right = NULL; + + left = deparseExpr (ctx->root, ctx->foreignrel,linitial (expr->args), ctx->params_list); + // check if anything has been added beyond the initial "(" if (left != NULL) { - Expr* rightexpr = NULL; - bool bResult = true; - StringInfoData result; - /* begin to compose result */ - initStringInfo (&result); - appendStringInfo (&result, "(%s %s (", left, expr->useOr ? "IN" : "NOT IN"); + Expr* rightexpr = NULL; + bool bResult = true; + /* the second (=last) argument can be Const, ArrayExpr or ArrayCoerceExpr */ rightexpr = (Expr*)llast(expr->args); switch (rightexpr->type) { case T_Const: { + StringInfoData buf; /* the second (=last) argument is a Const of ArrayType */ Const* constant = (Const*) rightexpr; /* using NULL in place of an array or value list is valid in DB2 and PostgreSQL */ + initStringInfo(&buf); if (constant->constisnull) { - appendStringInfo (&result, "NULL"); + appendStringInfo(&buf, "NULL"); + right = db2strdup(buf.data); } else { - Datum datum; - bool isNull; - ArrayIterator iterator = array_create_iterator (DatumGetArrayTypeP (constant->constvalue), 0); - bool first_arg = true; + Datum datum; + bool isNull; + ArrayIterator iterator = array_create_iterator (DatumGetArrayTypeP (constant->constvalue), 0); + bool first_arg = true; /* loop through the array elements */ while (array_iterate (iterator, &datum, &isNull)) { @@ -552,7 +646,7 @@ char* deparseScalarArrayOpExpr (DB2Session* session, RelOptInfo* foreignrel, Sca } } /* append the argument */ - appendStringInfo (&result, "%s%s", first_arg ? "" : ", ", c); + appendStringInfo (&buf, "%s%s", first_arg ? "" : ", ", c); first_arg = false; } array_free_iterator (iterator); @@ -562,7 +656,11 @@ char* deparseScalarArrayOpExpr (DB2Session* session, RelOptInfo* foreignrel, Sca // since the semantics for NOT x = ANY() differ bResult = false; } + if (bResult) { + right = db2strdup(buf.data); + } } + db2free(buf.data); } break; case T_ArrayCoerceExpr: { @@ -588,29 +686,34 @@ char* deparseScalarArrayOpExpr (DB2Session* session, RelOptInfo* foreignrel, Sca /* fall through ! */ case T_ArrayExpr: { /* the second (=last) argument is an ArrayExpr */ - ArrayExpr* array = (ArrayExpr*) rightexpr; - ListCell* cell = NULL; - bool first_arg = true; + StringInfoData buf; + char* element = NULL; + ArrayExpr* array = (ArrayExpr*) rightexpr; + ListCell* cell = NULL; + bool first_arg = true; + + initStringInfo(&buf); /* loop the array arguments */ foreach (cell, array->elements) { - /* convert the argument to a string */ - char* element = deparseExpr (session, foreignrel, (Expr *) lfirst (cell), db2Table, params); - db2Debug2(" element: %s", element); + element = deparseExpr (ctx->root, ctx->foreignrel, (Expr*) lfirst (cell), ctx->params_list); if (element == NULL) { /* if any element cannot be converted, give up */ + db2free(buf.data); bResult = false; break; } - /* append the argument */ - appendStringInfo (&result, "%s%s", first_arg ? "" : ", ", element); + appendStringInfo(&buf,"%s%s",(first_arg) ? "": ", ",element); first_arg = false; } db2Debug2(" first_arg: %s", first_arg ? "true" : "false"); if (first_arg) { /* don't push down empty arrays, since the semantics for NOT x = ANY() differ */ + db2free(buf.data); bResult = false; break; } + right = (bResult) ? db2strdup(buf.data) : NULL; + db2free(buf.data); } break; default: { @@ -621,20 +724,18 @@ char* deparseScalarArrayOpExpr (DB2Session* session, RelOptInfo* foreignrel, Sca } // only when there is a usable result otherwise keep value to null if (bResult) { - /* two parentheses close the expression */ - appendStringInfo (&result, "))"); - value = result.data; + appendStringInfo (ctx->buf, "(%s %s IN (%s))",left, expr->useOr ? "" : "NOT", right); } + db2free(left); + db2free(right); } } } } - db2Debug1("< %s::deparseExpr: %s", __FILE__, value); - return value; + db2Debug1("< %s::deparseScalarArrayOpExpr", __FILE__); } -char* deparseDistinctExpr (DB2Session* session, RelOptInfo* foreignrel, DistinctExpr* expr, const DB2Table* db2Table, List** params) { - char* value = NULL; +static void deparseDistinctExpr (DistinctExpr* expr, deparse_expr_cxt* ctx) { Oid rightargtype = 0; HeapTuple tuple; @@ -648,25 +749,24 @@ char* deparseDistinctExpr (DB2Session* session, RelOptInfo* foreignrel, Dis if (!canHandleType (rightargtype)) { db2Debug2(" cannot Handle Type rightargtype (%d)",rightargtype); } else { - char* left = deparseExpr (session, foreignrel, linitial ((expr)->args), db2Table, params); - db2Debug2(" left: %s", left); + char* left = NULL; + + left = deparseExpr (ctx->root, ctx->foreignrel, linitial ((expr)->args), ctx->params_list); if (left != NULL) { - char* right = deparseExpr (session, foreignrel, lsecond ((expr)->args), db2Table, params); - db2Debug2(" right: %s", right); + char* right = NULL; + + right = deparseExpr (ctx->root, ctx->foreignrel, lsecond ((expr)->args), ctx->params_list); if (right != NULL) { - StringInfoData result; - initStringInfo (&result); - appendStringInfo (&result, "(%s IS DISTINCT FROM %s)", left, right); - value = result.data; + appendStringInfo (ctx->buf, "( %s IS DISTINCT FROM %s)", left, right); } + db2free(right); } + db2free(left); } - db2Debug1("1 %s::deparseDistinctExpr: %s", __FILE__, value); - return value; + db2Debug1("< %s::deparseDistinctExpr", __FILE__); } -char* deparseNullIfExpr (DB2Session* session, RelOptInfo* foreignrel, NullIfExpr* expr, const DB2Table* db2Table, List** params) { - char* value = NULL; +static void deparseNullIfExpr (NullIfExpr* expr, deparse_expr_cxt* ctx) { Oid rightargtype = 0; HeapTuple tuple; @@ -680,74 +780,72 @@ char* deparseNullIfExpr (DB2Session* session, RelOptInfo* foreignrel, Nul if (!canHandleType (rightargtype)) { db2Debug2(" cannot Handle Type rightargtype (%d)",rightargtype); } else { - char* left = deparseExpr (session, foreignrel, linitial((expr)->args), db2Table, params); - db2Debug2(" left: %s", left); + char* left = NULL; + left = deparseExpr (ctx->root, ctx->foreignrel, linitial((expr)->args), ctx->params_list); + if (left != NULL) { - char* right = deparseExpr (session, foreignrel, lsecond((expr)->args), db2Table, params); - db2Debug2(" right: %s", right); + char* right = NULL; + + right = deparseExpr (ctx->root, ctx->foreignrel, lsecond((expr)->args), ctx->params_list); if (right != NULL) { - StringInfoData result; - initStringInfo (&result); - appendStringInfo (&result, "NULLIF(%s, %s)", left, right); - value = result.data; + appendStringInfo (ctx->buf, "NULLIF(%s,%s)", left, right); } + db2free(right); } + db2free(left); } - db2Debug1("< %s::deparseNullIfExpr: %s", __FILE__, value); - return value; + db2Debug1("< %s::deparseNullIfExpr: %s", __FILE__, ctx->buf->data); } -char* deparseBoolExpr (DB2Session* session, RelOptInfo* foreignrel, BoolExpr* expr, const DB2Table* db2Table, List** params) { - ListCell* cell = NULL; - char* arg = NULL; +static void deparseBoolExpr (BoolExpr* expr, deparse_expr_cxt* ctx) { + ListCell* cell = NULL; + char* arg = NULL; + StringInfoData buf; + db2Debug1("> %s::deparseBoolExpr", __FILE__); - arg = deparseExpr (session, foreignrel, linitial(expr->args), db2Table, params); + initStringInfo(&buf); + arg = deparseExpr (ctx->root, ctx->foreignrel, linitial(expr->args), ctx->params_list); if (arg != NULL) { - StringInfoData result; - bool bBreak = false; - initStringInfo (&result); - appendStringInfo (&result, "(%s%s", expr->boolop == NOT_EXPR ? "NOT " : "", arg); + bool bBreak = false; + appendStringInfo (&buf, "(%s%s", expr->boolop == NOT_EXPR ? "NOT " : "", arg); do_each_cell(cell, expr->args, list_next(expr->args, list_head(expr->args))) { - arg = deparseExpr (session, foreignrel, (Expr*)lfirst(cell), db2Table, params); + db2free(arg); + arg = deparseExpr (ctx->root, ctx->foreignrel, (Expr*)lfirst(cell), ctx->params_list); if (arg != NULL) { - appendStringInfo (&result, " %s %s", expr->boolop == AND_EXPR ? "AND" : "OR", arg); + appendStringInfo (&buf, " %s %s", expr->boolop == AND_EXPR ? "AND":"OR", arg); } else { bBreak = true; break; } } if (!bBreak) { - appendStringInfo (&result, ")"); - arg = result.data; - } else { - db2free(result.data); - arg = NULL; + appendStringInfo (ctx->buf, "%s)", buf.data); } } - db2Debug1("< %s::deparseBoolExpr: %s", __FILE__, arg); - return arg; + db2free(buf.data); + db2free(arg); + db2Debug1("< %s::deparseBoolExpr: %s", __FILE__, ctx->buf->data); } -char* deparseCaseExpr (DB2Session* session, RelOptInfo* foreignrel, CaseExpr* expr, const DB2Table* db2Table, List** params) { - char* value = NULL; +static void deparseCaseExpr (CaseExpr* expr, deparse_expr_cxt* ctx) { db2Debug1("> %s::deparseCaseExpr", __FILE__); if (!canHandleType (expr->casetype)) { db2Debug2(" cannot Handle Type caseexpr->casetype (%d)", expr->casetype); } else { - StringInfoData result; + StringInfoData buf; bool bBreak = false; char* arg = NULL; ListCell* cell = NULL; - initStringInfo (&result); - appendStringInfo (&result, "CASE"); + initStringInfo (&buf); + appendStringInfo (&buf, "CASE"); if (expr->arg != NULL) { /* for the form "CASE arg WHEN ...", add first expression */ - arg = deparseExpr (session, foreignrel, expr->arg, db2Table, params); + arg = deparseExpr (ctx->root, ctx->foreignrel, expr->arg, ctx->params_list); db2Debug2(" CASE %s WHEN ...", arg); if (arg == NULL) { - appendStringInfo (&result, " %s", arg); + appendStringInfo (&buf, " %s", arg); } else { bBreak = true; } @@ -759,22 +857,22 @@ char* deparseCaseExpr (DB2Session* session, RelOptInfo* foreignrel, Cas /* WHEN */ if (expr->arg == NULL) { /* for CASE WHEN ..., use the whole expression */ - arg = deparseExpr (session, foreignrel, whenclause->expr, db2Table, params); + arg = deparseExpr (ctx->root, ctx->foreignrel, whenclause->expr, ctx->params_list); } else { /* for CASE arg WHEN ..., use only the right branch of the equality */ - arg = deparseExpr (session, foreignrel, lsecond (((OpExpr*) whenclause->expr)->args), db2Table, params); + arg = deparseExpr (ctx->root, ctx->foreignrel, lsecond (((OpExpr*) whenclause->expr)->args), ctx->params_list); } db2Debug2(" WHEN %s ", arg); if (arg != NULL) { - appendStringInfo (&result, " WHEN %s", arg); + appendStringInfo (&buf, " WHEN %s", arg); } else { bBreak = true; break; } /* THEN */ - arg = deparseExpr (session, foreignrel, whenclause->result, db2Table, params); + arg = deparseExpr (ctx->root, ctx->foreignrel, whenclause->result, ctx->params_list); db2Debug2(" THEN %s ", arg); if (arg != NULL) { - appendStringInfo (&result, " THEN %s", arg); + appendStringInfo (&buf, " THEN %s", arg); } else { bBreak = true; break; @@ -783,32 +881,27 @@ char* deparseCaseExpr (DB2Session* session, RelOptInfo* foreignrel, Cas if (!bBreak) { /* append ELSE clause if appropriate */ if (expr->defresult != NULL) { - arg = deparseExpr (session, foreignrel, expr->defresult, db2Table, params); + arg = deparseExpr (ctx->root, ctx->foreignrel, expr->defresult, ctx->params_list); db2Debug2(" ELSE %s", arg); if (arg != NULL) { - appendStringInfo (&result, " ELSE %s", arg); + appendStringInfo (&buf, " ELSE %s", arg); } else { bBreak = true; } } /* append END */ - appendStringInfo (&result, " END"); + appendStringInfo (&buf, " END"); } } if (!bBreak) { - value = result.data; - } else { - // in case somwhere within the construct we encountered an expression not supported - db2free(result.data); - value = NULL; + appendStringInfo(ctx->buf,"%s",buf.data); } + db2free(buf.data); } - db2Debug1("< %s::deparseCaseExpr: %s", __FILE__, value); - return value; + db2Debug1("< %s::deparseCaseExpr: %s", __FILE__, ctx->buf->data); } -char* deparseCoalesceExpr (DB2Session* session, RelOptInfo* foreignrel, CoalesceExpr* expr, const DB2Table* db2Table, List** params) { - char* value = NULL; +static void deparseCoalesceExpr (CoalesceExpr* expr, deparse_expr_cxt* ctx) { db2Debug1("> %s::deparseCoalesceExpr", __FILE__); if (!canHandleType (expr->coalescetype)) { db2Debug2(" cannot Handle Type coalesceexpr->coalescetype (%d)", expr->coalescetype); @@ -817,10 +910,11 @@ char* deparseCoalesceExpr (DB2Session* session, RelOptInfo* foreignrel, Coa char* arg = NULL; bool first_arg = true; ListCell* cell = NULL; + initStringInfo (&result); appendStringInfo (&result, "COALESCE("); foreach (cell, expr->args) { - arg = deparseExpr (session, foreignrel, (Expr*)lfirst(cell), db2Table, params); + arg = deparseExpr (ctx->root, ctx->foreignrel, (Expr*)lfirst(cell),ctx->params_list); db2Debug2(" arg: %s", arg); if (arg != NULL) { appendStringInfo(&result, ((first_arg) ? "%s" : ", %s"), arg); @@ -829,31 +923,27 @@ char* deparseCoalesceExpr (DB2Session* session, RelOptInfo* foreignrel, Coa break; } } - appendStringInfo (&result, ")"); - value = (arg == NULL) ? arg : result.data; + if (arg != NULL) { + appendStringInfo (ctx->buf, "%s)",result.data); + } + db2free(result.data); } - db2Debug1("< %s::deparseCoalesceExpr: %s", __FILE__, value); - return value; + db2Debug1("< %s::deparseCoalesceExpr: %s", __FILE__, ctx->buf->data); } -char* deparseFuncExpr (DB2Session* session, RelOptInfo* foreignrel, FuncExpr* expr, const DB2Table* db2Table, List** params) { - char* value = NULL; +static void deparseFuncExpr (FuncExpr* expr, deparse_expr_cxt* ctx) { db2Debug1("> %s::deparseFuncExpr", __FILE__); if (!canHandleType (expr->funcresulttype)) { db2Debug2(" cannot handle funct->funcresulttype: %d",expr->funcresulttype); } else if (expr->funcformat == COERCE_IMPLICIT_CAST) { /* do nothing for implicit casts */ db2Debug2(" COERCE_IMPLICIT_CAST == expr->funcformat(%d)",expr->funcformat); - value = deparseExpr (session, foreignrel, linitial (expr->args), db2Table, params); + deparseExprInt (linitial(expr->args), ctx); } else { - StringInfoData result; Oid schema; char* opername; - char* left; - char* right; - char* arg; - bool first_arg; HeapTuple tuple; + /* get function name and schema */ tuple = SearchSysCache1 (PROCOID, ObjectIdGetDatum (expr->funcid)); if (!HeapTupleIsValid (tuple)) { @@ -867,115 +957,104 @@ char* deparseFuncExpr (DB2Session* session, RelOptInfo* foreignrel, Fun /* ignore functions in other than the pg_catalog schema */ if (schema != PG_CATALOG_NAMESPACE) { db2Debug2(" T_FuncExpr: schema(%d) != PG_CATALOG_NAMESPACE", schema); - db2Debug1("< %s::deparseExpr: NULL", __FILE__); - return NULL; - } - /* the "normal" functions that we can translate */ - if (strcmp (opername, "abs") == 0 || strcmp (opername, "acos") == 0 || strcmp (opername, "asin") == 0 - || strcmp (opername, "atan") == 0 || strcmp (opername, "atan2") == 0 || strcmp (opername, "ceil") == 0 - || strcmp (opername, "ceiling") == 0 || strcmp (opername, "char_length") == 0 || strcmp (opername, "character_length") == 0 - || strcmp (opername, "concat") == 0 || strcmp (opername, "cos") == 0 || strcmp (opername, "exp") == 0 - || strcmp (opername, "initcap") == 0 || strcmp (opername, "length") == 0 || strcmp (opername, "lower") == 0 - || strcmp (opername, "lpad") == 0 || strcmp (opername, "ltrim") == 0 || strcmp (opername, "mod") == 0 - || strcmp (opername, "octet_length") == 0 || strcmp (opername, "position") == 0 || strcmp (opername, "pow") == 0 - || strcmp (opername, "power") == 0 || strcmp (opername, "replace") == 0 || strcmp (opername, "round") == 0 - || strcmp (opername, "rpad") == 0 || strcmp (opername, "rtrim") == 0 || strcmp (opername, "sign") == 0 - || strcmp (opername, "sin") == 0 || strcmp (opername, "sqrt") == 0 || strcmp (opername, "strpos") == 0 - || strcmp (opername, "substr") == 0 || strcmp (opername, "tan") == 0 || strcmp (opername, "to_char") == 0 - || strcmp (opername, "to_date") == 0 || strcmp (opername, "to_number") == 0 || strcmp (opername, "to_timestamp") == 0 - || strcmp (opername, "translate") == 0 || strcmp (opername, "trunc") == 0 || strcmp (opername, "upper") == 0 - || (strcmp (opername, "substring") == 0 && list_length (expr->args) == 3)) { - ListCell* cell; - initStringInfo (&result); - if (strcmp (opername, "ceiling") == 0) - appendStringInfo (&result, "CEIL("); - else if (strcmp (opername, "char_length") == 0 || strcmp (opername, "character_length") == 0) - appendStringInfo (&result, "LENGTH("); - else if (strcmp (opername, "pow") == 0) - appendStringInfo (&result, "POWER("); - else if (strcmp (opername, "octet_length") == 0) - appendStringInfo (&result, "LENGTHB("); - else if (strcmp (opername, "position") == 0 || strcmp (opername, "strpos") == 0) - appendStringInfo (&result, "INSTR("); - else if (strcmp (opername, "substring") == 0) - appendStringInfo (&result, "SUBSTR("); - else - appendStringInfo (&result, "%s(", opername); - first_arg = true; - foreach (cell, expr->args) { - arg = deparseExpr (session, foreignrel, lfirst (cell), db2Table, params); - if (arg == NULL) { - db2free (result.data); - db2Debug2(" T_FuncExpr: function %s that we cannot render for DB2", opername); - db2free (opername); - db2Debug1("< %s::deparseExpr: NULL", __FILE__); - return NULL; + } else { + /* the "normal" functions that we can translate */ + if (strcmp (opername, "abs") == 0 || strcmp (opername, "acos") == 0 || strcmp (opername, "asin") == 0 + || strcmp (opername, "atan") == 0 || strcmp (opername, "atan2") == 0 || strcmp (opername, "ceil") == 0 + || strcmp (opername, "ceiling") == 0 || strcmp (opername, "char_length") == 0 || strcmp (opername, "character_length") == 0 + || strcmp (opername, "concat") == 0 || strcmp (opername, "cos") == 0 || strcmp (opername, "exp") == 0 + || strcmp (opername, "initcap") == 0 || strcmp (opername, "length") == 0 || strcmp (opername, "lower") == 0 + || strcmp (opername, "lpad") == 0 || strcmp (opername, "ltrim") == 0 || strcmp (opername, "mod") == 0 + || strcmp (opername, "octet_length") == 0 || strcmp (opername, "position") == 0 || strcmp (opername, "pow") == 0 + || strcmp (opername, "power") == 0 || strcmp (opername, "replace") == 0 || strcmp (opername, "round") == 0 + || strcmp (opername, "rpad") == 0 || strcmp (opername, "rtrim") == 0 || strcmp (opername, "sign") == 0 + || strcmp (opername, "sin") == 0 || strcmp (opername, "sqrt") == 0 || strcmp (opername, "strpos") == 0 + || strcmp (opername, "substr") == 0 || strcmp (opername, "tan") == 0 || strcmp (opername, "to_char") == 0 + || strcmp (opername, "to_date") == 0 || strcmp (opername, "to_number") == 0 || strcmp (opername, "to_timestamp") == 0 + || strcmp (opername, "translate") == 0 || strcmp (opername, "trunc") == 0 || strcmp (opername, "upper") == 0 + || (strcmp (opername, "substring") == 0 && list_length (expr->args) == 3)) { + ListCell* cell; + char* arg = NULL; + bool ok = true; + bool first_arg = true; + StringInfoData buf; + + initStringInfo (&buf); + if (strcmp (opername, "ceiling") == 0) + appendStringInfo (&buf, "CEIL("); + else if (strcmp (opername, "char_length") == 0 || strcmp (opername, "character_length") == 0) + appendStringInfo (&buf, "LENGTH("); + else if (strcmp (opername, "pow") == 0) + appendStringInfo (&buf, "POWER("); + else if (strcmp (opername, "octet_length") == 0) + appendStringInfo (&buf, "LENGTHB("); + else if (strcmp (opername, "position") == 0 || strcmp (opername, "strpos") == 0) + appendStringInfo (&buf, "INSTR("); + else if (strcmp (opername, "substring") == 0) + appendStringInfo (&buf, "SUBSTR("); + else + appendStringInfo (&buf, "%s(", opername); + foreach (cell, expr->args) { + arg = deparseExpr (ctx->root, ctx->foreignrel, lfirst (cell), ctx->params_list); + if (arg != NULL) { + appendStringInfo (&buf, "%s%s", (first_arg) ? ", " : "",arg); + first_arg = false; + db2free(arg); + } else { + ok = false; + db2Debug2(" T_FuncExpr: function %s that we cannot render for DB2", opername); + break; + } } - if (first_arg) { - first_arg = false; - appendStringInfo (&result, "%s", arg); - } else { - appendStringInfo (&result, ", %s", arg); + appendStringInfo (&buf, ")"); + // copy to return value when successful + if (ok) { + appendStringInfo(ctx->buf,"%s",buf.data); } - db2free(arg); - } - appendStringInfo (&result, ")"); - } else if (strcmp (opername, "date_part") == 0) { - /* special case: EXTRACT */ - left = deparseExpr (session, foreignrel, linitial (expr->args), db2Table, params); - if (left == NULL) { - db2Debug2(" T_FuncExpr: function %s that we cannot render for DB2", opername); - db2free (opername); - db2Debug1("< %s::deparseExpr: NULL", __FILE__); - return NULL; - } - /* can only handle these fields in DB2 */ - if (strcmp (left, "'year'") == 0 || strcmp (left, "'month'") == 0 - || strcmp (left, "'day'") == 0 || strcmp (left, "'hour'") == 0 - || strcmp (left, "'minute'") == 0 || strcmp (left, "'second'") == 0 - || strcmp (left, "'timezone_hour'") == 0 || strcmp (left, "'timezone_minute'") == 0) { - /* remove final quote */ - left[strlen (left) - 1] = '\0'; - right = deparseExpr (session, foreignrel, lsecond (expr->args), db2Table, params); - if (right == NULL) { + db2free(buf.data); + } else if (strcmp (opername, "date_part") == 0) { + char* left = NULL; + + /* special case: EXTRACT */ + left = deparseExpr (ctx->root, ctx->foreignrel, linitial (expr->args), ctx->params_list); + if (left == NULL) { db2Debug2(" T_FuncExpr: function %s that we cannot render for DB2", opername); - db2free (opername); - db2free (left); - db2Debug1("< %s::deparseExpr: NULL", __FILE__); - return NULL; + } else { + /* can only handle these fields in DB2 */ + if (strcmp (left, "'year'") == 0 || strcmp (left, "'month'") == 0 + || strcmp (left, "'day'") == 0 || strcmp (left, "'hour'") == 0 + || strcmp (left, "'minute'") == 0 || strcmp (left, "'second'") == 0 + || strcmp (left, "'timezone_hour'") == 0 || strcmp (left, "'timezone_minute'") == 0) { + char* right = NULL; + + /* remove final quote */ + left[strlen (left) - 1] = '\0'; + right = deparseExpr (ctx->root, ctx->foreignrel, lsecond (expr->args), ctx->params_list); + if (right == NULL) { + db2Debug2(" T_FuncExpr: function %s that we cannot render for DB2", opername); + } else { + appendStringInfo (ctx->buf, "EXTRACT(%s FROM %s)", left + 1, right); + } + db2free(right); + } else { + db2Debug2(" T_FuncExpr: function %s that we cannot render for DB2", opername); + } } - initStringInfo (&result); - appendStringInfo (&result, "EXTRACT(%s FROM %s)", left + 1, right); + db2free (left); + } else if (strcmp (opername, "now") == 0 || strcmp (opername, "transaction_timestamp") == 0) { + /* special case: current timestamp */ + appendStringInfo (ctx->buf, "(CAST (?/*:now*/ AS TIMESTAMP))"); } else { + /* function that we cannot render for DB2 */ db2Debug2(" T_FuncExpr: function %s that we cannot render for DB2", opername); - db2free (opername); - db2free (left); - db2Debug1("< %s::deparseExpr: NULL", __FILE__); - return NULL; } - db2free (left); - db2free (right); - } else if (strcmp (opername, "now") == 0 || strcmp (opername, "transaction_timestamp") == 0) { - /* special case: current timestamp */ - initStringInfo (&result); - appendStringInfo (&result, "(CAST (?/*:now*/ AS TIMESTAMP))"); - } else { - /* function that we cannot render for DB2 */ - db2Debug2(" T_FuncExpr: function %s that we cannot render for DB2", opername); - db2free (opername); - db2Debug1("< %s::deparseExpr: NULL", __FILE__); - return NULL; } db2free (opername); - value = result.data; } - db2Debug1("< %s::deparseFuncExpr: %s", __FILE__, value); - return value; + db2Debug1("< %s::deparseFuncExpr: %s", __FILE__, ctx->buf->data); } -char* deparseCoerceViaIOExpr (CoerceViaIO* expr) { - char* value = NULL; - +static void deparseCoerceViaIOExpr (CoerceViaIO* expr, deparse_expr_cxt* ctx) { db2Debug1("> %s::deparseCoerceViaIOExpr", __FILE__); /* We will only handle casts of 'now'. */ /* only casts to these types are handled */ @@ -1004,62 +1083,56 @@ char* deparseCoerceViaIOExpr (CoerceViaIO* expr) { } else { switch (expr->resulttype) { case DATEOID: - value = "TRUNC(CAST (CAST(?/*:now*/ AS TIMESTAMP) AS DATE))"; + appendStringInfo(ctx->buf, "TRUNC(CAST (CAST(?/*:now*/ AS TIMESTAMP) AS DATE))"); break; case TIMESTAMPOID: - value = "(CAST (CAST (?/*:now*/ AS TIMESTAMP) AS TIMESTAMP))"; + appendStringInfo(ctx->buf, "(CAST (CAST (?/*:now*/ AS TIMESTAMP) AS TIMESTAMP))"); break; case TIMESTAMPTZOID: - value = "(CAST (?/*:now*/ AS TIMESTAMP))"; + appendStringInfo(ctx->buf, "(CAST (?/*:now*/ AS TIMESTAMP))"); break; case TIMEOID: - value = "(CAST (CAST (?/*:now*/ AS TIME) AS TIME))"; + appendStringInfo(ctx->buf, "(CAST (CAST (?/*:now*/ AS TIME) AS TIME))"); break; case TIMETZOID: - value = "(CAST (?/*:now*/ AS TIME))"; + appendStringInfo(ctx->buf, "(CAST (?/*:now*/ AS TIME))"); break; } } } } - db2Debug1("< %s::deparseCoerceViaIOExpr: %s", __FILE__, value); - return value; + db2Debug1("< %s::deparseCoerceViaIOExpr: %s", __FILE__, ctx->buf->data); } #if PG_VERSION_NUM >= 100000 -char* deparseSQLValueFuncExpr (SQLValueFunction* expr) { - char* value = NULL; +static void deparseSQLValueFuncExpr (SQLValueFunction* expr, deparse_expr_cxt* ctx) { db2Debug1("> %s::deparseSQLValueFuncExpr", __FILE__); switch (expr->op) { case SVFOP_CURRENT_DATE: - value = "TRUNC(CAST (CAST(?/*:now*/ AS TIMESTAMP) AS DATE))"; + appendStringInfo(ctx->buf, "TRUNC(CAST (CAST(?/*:now*/ AS TIMESTAMP) AS DATE))"); break; case SVFOP_CURRENT_TIMESTAMP: - value = "(CAST (?/*:now*/ AS TIMESTAMP))"; + appendStringInfo(ctx->buf, "(CAST (?/*:now*/ AS TIMESTAMP))"); break; case SVFOP_LOCALTIMESTAMP: - value = "(CAST (CAST (?/*:now*/ AS TIMESTAMP) AS TIMESTAMP))"; + appendStringInfo(ctx->buf, "(CAST (CAST (?/*:now*/ AS TIMESTAMP) AS TIMESTAMP))"); break; case SVFOP_CURRENT_TIME: - value = "(CAST (?/*:now*/ AS TIME))"; + appendStringInfo(ctx->buf, "(CAST (?/*:now*/ AS TIME))"); break; case SVFOP_LOCALTIME: - value = "(CAST (CAST (?/*:now*/ AS TIME) AS TIME))"; + appendStringInfo(ctx->buf, "(CAST (CAST (?/*:now*/ AS TIME) AS TIME))"); break; default: /* don't push down other functions */ db2Debug2(" op %d cannot be translated to DB2", expr->op); - value = NULL; break; } - db2Debug1("< %s::deparseSQLValueFuncExpr: %s", __FILE__, value); - return value; + db2Debug1("< %s::deparseSQLValueFuncExpr: %s", __FILE__, ctx->buf->data); } #endif -char* deparseAggref (DB2Session* session, RelOptInfo* foreignrel, Aggref* expr, const DB2Table* db2Table, List** params) { - char* value = NULL; - +static void deparseAggref (Aggref* expr, deparse_expr_cxt* ctx) { db2Debug1("> %s::deparseAggref", __FILE__); if (expr == NULL) { db2Debug2(" expr is NULL"); @@ -1084,10 +1157,12 @@ char* deparseAggref (DB2Session* session, RelOptInfo* foreignrel, Agg } ReleaseSysCache(tuple); } - db2Debug2(" aggref->aggfnoid=%u name=%s%s%s", expr->aggfnoid, - nspname ? nspname : "", - nspname ? "." : "", - aggname ? aggname : ""); + db2Debug2( " aggref->aggfnoid=%u name=%s%s%s" + , expr->aggfnoid + , nspname ? nspname : "" + , nspname ? "." : "" + , aggname ? aggname : "" + ); /* We only support deparsing simple, standard aggregates for now. * (This can be expanded to ordered-set / FILTER / WITHIN GROUP later.) */ @@ -1108,7 +1183,8 @@ char* deparseAggref (DB2Session* session, RelOptInfo* foreignrel, Agg db2Debug2(" aggregate '%s' not supported for DB2 deparse", aggname); } if (db2func != NULL) { - StringInfoData result; + StringInfoData result; + initStringInfo(&result); appendStringInfo(&result, "%s(", db2func); if (distinct) { @@ -1119,12 +1195,12 @@ char* deparseAggref (DB2Session* session, RelOptInfo* foreignrel, Agg appendStringInfoString(&result, "*"); } else { ListCell* lc; + char* arg = NULL; bool first_arg = true; foreach (lc, expr->args) { Node* argnode = (Node*) lfirst(lc); Expr* argexpr = NULL; - char* argsql; if (argnode == NULL) { ok = false; @@ -1135,29 +1211,26 @@ char* deparseAggref (DB2Session* session, RelOptInfo* foreignrel, Agg } else { argexpr = (Expr*) argnode; } - - argsql = deparseExpr(session, foreignrel, argexpr, db2Table, params); - if (argsql == NULL) { + arg = deparseExpr(ctx->root, ctx->foreignrel, argexpr, ctx->params_list); + if (arg == NULL) { ok = false; break; } - - appendStringInfo(&result, "%s%s", first_arg ? "" : ", ", argsql); + appendStringInfo(&result, "%s%s", arg, first_arg ? "" : ", "); first_arg = false; } } - if (!ok) { - db2Debug2(" could not deparse aggregate args"); + if (ok) { + appendStringInfo(ctx->buf, "%s)",result.data); } else { - appendStringInfoChar(&result, ')'); - value = result.data; + db2Debug2(" parsed aggref so far: %s", result.data); + db2Debug2(" could not deparse aggregate args"); } + db2free(result.data); } } } - - db2Debug1("< %s::deparseAggref: %s", __FILE__, value); - return value; + db2Debug1("< %s::deparseAggref: %s", __FILE__, ctx->buf->data); } /** datumToString @@ -1712,3 +1785,31 @@ void exitHook (int code, Datum arg) { db2Shutdown (); db2Debug1("< %s::exitHook",__FILE__); } + +/** Output join name for given join type + */ +static const char* get_jointype_name (JoinType jointype) { + char* type = NULL; + db2Debug1("> get_jointype_name"); + switch (jointype) { + case JOIN_INNER: + type = "INNER"; + break; + case JOIN_LEFT: + type = "LEFT"; + break; + case JOIN_RIGHT: + type = "RIGHT"; + break; + case JOIN_FULL: + type= "FULL"; + break; + default: + /* Shouldn't come here, but protect from buggy code. */ + elog (ERROR, "unsupported join type %d", jointype); + break; + } + db2Debug2(" type: '%s'",type); + db2Debug1("< get_jointype_name"); + return type; +} From b522b4a070c492b9b67de5a2cf352dfd588a9027 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Tue, 20 Jan 2026 17:06:54 +0100 Subject: [PATCH 008/120] removed all code no longer required by PG >= v13 --- include/db2_fdw.h | 14 -------------- source/db2AddForeignUpdateTargets.c | 6 ------ source/db2AnalyzeForeignTable.c | 6 ------ source/db2BeginForeignInsert.c | 6 ------ source/db2BeginForeignModify.c | 6 ------ source/db2BeginForeignModifyCommon.c | 6 ------ source/db2BeginForeignScan.c | 10 ---------- source/db2Debug.c | 6 ------ source/db2EndForeignModifyCommon.c | 6 ------ source/db2EndForeignScan.c | 6 ------ source/db2ExecForeignDelete.c | 6 ------ source/db2ExecForeignInsert.c | 6 ------ source/db2ExecForeignUpdate.c | 6 ------ source/db2ExplainForeignModify.c | 6 ------ source/db2ExplainForeignScan.c | 6 ------ source/db2GetFdwState.c | 6 ------ source/db2GetForeignJoinPaths.c | 10 ---------- source/db2GetForeignPaths.c | 10 ---------- source/db2GetForeignPlan.c | 25 ------------------------- source/db2GetForeignRelSize.c | 6 ------ source/db2GetForeignUpperPaths.c | 4 ---- source/db2GetOptions.c | 6 ------ source/db2GetShareFileName.c | 6 ------ source/db2ImportForeignSchema.c | 6 ------ source/db2IsForeignRelUpdatable.c | 6 ------ source/db2IterateForeignScan.c | 10 ---------- source/db2PlanForeignModify.c | 12 ------------ source/db2ReAllocFree.c | 6 ------ source/db2ReScanForeignScan.c | 6 ------ source/db2SetHandlers.c | 6 ------ source/db2_fdw.c | 10 ---------- source/db2_fdw_utils.c | 20 -------------------- 32 files changed, 257 deletions(-) diff --git a/include/db2_fdw.h b/include/db2_fdw.h index 97f27c9..dc6dc38 100644 --- a/include/db2_fdw.h +++ b/include/db2_fdw.h @@ -41,30 +41,16 @@ /* GetConfigOptionByName has a new signature from 9.6 on */ #define GetConfigOptionByName(name, varname) GetConfigOptionByName(name, varname, false) -#if PG_VERSION_NUM < 110000 -/* backport macro from V11 */ -#define TupleDescAttr(tupdesc, i) ((tupdesc)->attrs[(i)]) -#endif /* PG_VERSION_NUM */ /* list API has changed in v13 */ -#if PG_VERSION_NUM < 130000 -#define list_next(l, e) lnext((e)) -#define do_each_cell(cell, list, element) for_each_cell(cell, (element)) -#else #define list_next(l, e) lnext((l), (e)) #define do_each_cell(cell, list, element) for_each_cell(cell, (list), (element)) -#endif /* PG_VERSION_NUM */ /* older versions don't have JSONOID */ #ifndef JSONOID #define JSONOID InvalidOid #endif -/* "table_open" was "heap_open" before v12 */ -#if PG_VERSION_NUM < 120000 -#define table_open(x, y) heap_open(x, y) -#define table_close(x, y) heap_close(x, y) -#endif /* PG_VERSION_NUM */ #endif /* POSTGRES_H */ /* db2_fdw version */ diff --git a/source/db2AddForeignUpdateTargets.c b/source/db2AddForeignUpdateTargets.c index 4eb08f4..b2f21d2 100644 --- a/source/db2AddForeignUpdateTargets.c +++ b/source/db2AddForeignUpdateTargets.c @@ -4,15 +4,9 @@ #if PG_VERSION_NUM >= 140000 #include #endif /* PG_VERSION_NUM */ -#if PG_VERSION_NUM < 120000 -#include -#include -#include -#else #include #include #include -#endif #include "db2_fdw.h" /** external prototypes */ diff --git a/source/db2AnalyzeForeignTable.c b/source/db2AnalyzeForeignTable.c index 44dad38..7e4b25d 100644 --- a/source/db2AnalyzeForeignTable.c +++ b/source/db2AnalyzeForeignTable.c @@ -2,15 +2,9 @@ #include #include #include -#if PG_VERSION_NUM < 120000 -#include -#include -#include -#else #include #include #include -#endif #include "db2_fdw.h" #include "DB2FdwState.h" diff --git a/source/db2BeginForeignInsert.c b/source/db2BeginForeignInsert.c index 64a46e3..e20ebe2 100644 --- a/source/db2BeginForeignInsert.c +++ b/source/db2BeginForeignInsert.c @@ -3,15 +3,9 @@ #include #include #include -#if PG_VERSION_NUM < 120000 -#include -#include -#include -#else #include #include #include -#endif #include "db2_fdw.h" #include "DB2FdwState.h" diff --git a/source/db2BeginForeignModify.c b/source/db2BeginForeignModify.c index 22af5f5..5bced29 100644 --- a/source/db2BeginForeignModify.c +++ b/source/db2BeginForeignModify.c @@ -3,15 +3,9 @@ #include #include #include -#if PG_VERSION_NUM < 120000 -#include -#include -#include -#else #include #include #include -#endif #include "db2_fdw.h" #include "DB2FdwState.h" diff --git a/source/db2BeginForeignModifyCommon.c b/source/db2BeginForeignModifyCommon.c index 48a4a6c..f4f562d 100644 --- a/source/db2BeginForeignModifyCommon.c +++ b/source/db2BeginForeignModifyCommon.c @@ -3,16 +3,10 @@ #include #include #include -#if PG_VERSION_NUM < 120000 -#include -#include -#include -#else #include #include #include #include -#endif #include "db2_fdw.h" #include "DB2FdwState.h" diff --git a/source/db2BeginForeignScan.c b/source/db2BeginForeignScan.c index cd2fcb2..2df26e2 100644 --- a/source/db2BeginForeignScan.c +++ b/source/db2BeginForeignScan.c @@ -1,16 +1,10 @@ #include #include #include -#if PG_VERSION_NUM < 120000 -#include -#include -#include -#else #include #include #include #include -#endif #include "db2_fdw.h" #include "DB2FdwState.h" @@ -45,11 +39,7 @@ void db2BeginForeignScan(ForeignScanState* node, int eflags) { node->fdw_state = (void *) fdw_state; /* create an ExprState tree for the parameter expressions */ -#if PG_VERSION_NUM < 100000 - exec_exprs = (List *) ExecInitExpr ((Expr *) fsplan->fdw_exprs, (PlanState *) node); -#else exec_exprs = (List *) ExecInitExprList (fsplan->fdw_exprs, (PlanState *) node); -#endif /* PG_VERSION_NUM */ /* create the list of parameters */ index = 0; diff --git a/source/db2Debug.c b/source/db2Debug.c index 44e1c45..7e9e6e1 100644 --- a/source/db2Debug.c +++ b/source/db2Debug.c @@ -2,15 +2,9 @@ #include #include #include -#if PG_VERSION_NUM < 120000 -#include -#include -#include -#else #include #include #include -#endif #include "db2_fdw.h" /* get a PostgreSQL error code from an db2error */ diff --git a/source/db2EndForeignModifyCommon.c b/source/db2EndForeignModifyCommon.c index 7405af6..eeca861 100644 --- a/source/db2EndForeignModifyCommon.c +++ b/source/db2EndForeignModifyCommon.c @@ -1,15 +1,9 @@ #include #include #include -#if PG_VERSION_NUM < 120000 -#include -#include -#include -#else #include #include #include -#endif #include "db2_fdw.h" #include "DB2FdwState.h" /** external variables */ diff --git a/source/db2EndForeignScan.c b/source/db2EndForeignScan.c index e5b84bb..7e01279 100644 --- a/source/db2EndForeignScan.c +++ b/source/db2EndForeignScan.c @@ -1,14 +1,8 @@ #include #include -#if PG_VERSION_NUM < 120000 -#include -#include -#include -#else #include #include #include -#endif #include "db2_fdw.h" #include "DB2FdwState.h" diff --git a/source/db2ExecForeignDelete.c b/source/db2ExecForeignDelete.c index 96adbff..0abadf4 100644 --- a/source/db2ExecForeignDelete.c +++ b/source/db2ExecForeignDelete.c @@ -1,14 +1,8 @@ #include #include -#if PG_VERSION_NUM < 120000 -#include -#include -#include -#else #include #include #include -#endif #include "db2_fdw.h" #include "DB2FdwState.h" diff --git a/source/db2ExecForeignInsert.c b/source/db2ExecForeignInsert.c index 3dbf2a8..bd44a44 100644 --- a/source/db2ExecForeignInsert.c +++ b/source/db2ExecForeignInsert.c @@ -1,15 +1,9 @@ #include #include #include -#if PG_VERSION_NUM < 120000 -#include -#include -#include -#else #include #include #include -#endif #include "db2_fdw.h" #include "DB2FdwState.h" diff --git a/source/db2ExecForeignUpdate.c b/source/db2ExecForeignUpdate.c index aca8bc3..20ee9be 100644 --- a/source/db2ExecForeignUpdate.c +++ b/source/db2ExecForeignUpdate.c @@ -1,15 +1,9 @@ #include #include #include -#if PG_VERSION_NUM < 120000 -#include -#include -#include -#else #include #include #include -#endif #include "db2_fdw.h" #include "DB2FdwState.h" diff --git a/source/db2ExplainForeignModify.c b/source/db2ExplainForeignModify.c index 3ed17f4..51cf8bf 100644 --- a/source/db2ExplainForeignModify.c +++ b/source/db2ExplainForeignModify.c @@ -3,15 +3,9 @@ #if PG_VERSION_NUM >= 180000 #include #endif -#if PG_VERSION_NUM < 120000 -#include -#include -#include -#else #include #include #include -#endif #include "db2_fdw.h" #include "DB2FdwState.h" diff --git a/source/db2ExplainForeignScan.c b/source/db2ExplainForeignScan.c index 32fd035..680b42c 100644 --- a/source/db2ExplainForeignScan.c +++ b/source/db2ExplainForeignScan.c @@ -4,15 +4,9 @@ #include #include #endif -#if PG_VERSION_NUM < 120000 -#include -#include -#include -#else #include #include #include -#endif #include "db2_fdw.h" #include "DB2FdwState.h" diff --git a/source/db2GetFdwState.c b/source/db2GetFdwState.c index fa34770..73bc445 100644 --- a/source/db2GetFdwState.c +++ b/source/db2GetFdwState.c @@ -1,16 +1,10 @@ #include #include #include -#if PG_VERSION_NUM < 120000 -#include -#include -#include -#else #include #include #include #include -#endif #include "db2_fdw.h" #include "DB2FdwState.h" diff --git a/source/db2GetForeignJoinPaths.c b/source/db2GetForeignJoinPaths.c index 7ce181b..700cca1 100644 --- a/source/db2GetForeignJoinPaths.c +++ b/source/db2GetForeignJoinPaths.c @@ -1,15 +1,9 @@ #include #include #include -#if PG_VERSION_NUM < 120000 -#include -#include -#include -#else #include #include #include -#endif #include "db2_fdw.h" #include "DB2FdwState.h" @@ -98,11 +92,7 @@ void db2GetForeignJoinPaths (PlannerInfo * root, RelOptInfo * joinrel, RelOptInf fdwState->total_cost = total_cost; /* create a new join path */ -#if PG_VERSION_NUM < 120000 - joinpath = create_foreignscan_path ( root -#else joinpath = create_foreign_join_path( root -#endif /* PG_VERSION_NUM */ , joinrel , NULL /* default pathtarget */ , rows diff --git a/source/db2GetForeignPaths.c b/source/db2GetForeignPaths.c index 3b7bd36..3b53688 100644 --- a/source/db2GetForeignPaths.c +++ b/source/db2GetForeignPaths.c @@ -1,15 +1,9 @@ #include #include #include -#if PG_VERSION_NUM < 120000 -#include -#include -#include -#else #include #include #include -#endif #include "db2_fdw.h" #include "DB2FdwState.h" @@ -100,9 +94,7 @@ void db2GetForeignPaths(PlannerInfo* root, RelOptInfo* baserel, Oid foreigntable /* add the only path */ add_path (baserel, (Path *) create_foreignscan_path (root ,baserel - #if PG_VERSION_NUM >= 90600 ,NULL /* default pathtarget */ - #endif /* PG_VERSION_NUM */ ,baserel->rows #if PG_VERSION_NUM >= 180000 ,0 /* no disabled plan nodes */ @@ -111,9 +103,7 @@ void db2GetForeignPaths(PlannerInfo* root, RelOptInfo* baserel, Oid foreigntable ,fdwState->total_cost ,usable_pathkeys ,baserel->lateral_relids - #if PG_VERSION_NUM >= 90500 ,NULL /* no extra plan */ - #endif /* PG_VERSION_NUM */ #if PG_VERSION_NUM >= 170000 ,NIL /* no fdw_restrictinfo */ #endif /* PG_VERSION_NUM */ diff --git a/source/db2GetForeignPlan.c b/source/db2GetForeignPlan.c index 28a37ba..8cb3ad0 100644 --- a/source/db2GetForeignPlan.c +++ b/source/db2GetForeignPlan.c @@ -1,21 +1,11 @@ #include -#if PG_VERSION_NUM < 100000 -#include -#else #include -#endif /* PG_VERSION_NUM */ #include #include #include -#if PG_VERSION_NUM < 120000 -#include -#include -#include -#else #include #include #include -#endif #include "db2_fdw.h" #include "DB2FdwState.h" @@ -186,13 +176,7 @@ static void createQuery (PlannerInfo* root, RelOptInfo* foreignrel, bool modify, #endif db2Debug1("> createQuery"); - - #if PG_VERSION_NUM < 90600 - columnlist = foreignrel->reltargetlist; - #else columnlist = foreignrel->reltarget->exprs; - #endif - if (IS_SIMPLE_REL (foreignrel)) { db2Debug3(" IS_SIMPLE_REL"); /* find all the columns to include in the select list */ @@ -320,9 +304,7 @@ static void getUsedColumns (Expr* expr, DB2Table* db2Table, int foreignrelid) { case T_CaseTestExpr: case T_CoerceToDomainValue: case T_CurrentOfExpr: - #if PG_VERSION_NUM >= 100000 case T_NextValueExpr: - #endif break; case T_Var: variable = (Var*) expr; @@ -363,13 +345,8 @@ static void getUsedColumns (Expr* expr, DB2Table* db2Table, int foreignrelid) { getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); } break; - #if PG_VERSION_NUM < 120000 - case T_ArrayRef: { - ArrayRef* ref = (ArrayRef*)expr; - #else case T_SubscriptingRef: { SubscriptingRef* ref = (SubscriptingRef*) expr; - #endif foreach(cell, ref->refupperindexpr) { getUsedColumns((Expr*)lfirst(cell), db2Table, foreignrelid); } @@ -499,11 +476,9 @@ static void getUsedColumns (Expr* expr, DB2Table* db2Table, int foreignrelid) { case T_PlaceHolderVar: getUsedColumns (((PlaceHolderVar*) expr)->phexpr, db2Table, foreignrelid); break; - #if PG_VERSION_NUM >= 100000 case T_SQLValueFunction: //nop break; /* contains no column references */ - #endif default: /* * We must be able to handle all node types that can diff --git a/source/db2GetForeignRelSize.c b/source/db2GetForeignRelSize.c index 00ccc29..73625b1 100644 --- a/source/db2GetForeignRelSize.c +++ b/source/db2GetForeignRelSize.c @@ -1,13 +1,7 @@ #include -#if PG_VERSION_NUM < 120000 -#include -#include -#include -#else #include #include #include -#endif #include "db2_fdw.h" #include "DB2FdwState.h" diff --git a/source/db2GetForeignUpperPaths.c b/source/db2GetForeignUpperPaths.c index 2a2c299..fa7bc59 100644 --- a/source/db2GetForeignUpperPaths.c +++ b/source/db2GetForeignUpperPaths.c @@ -113,9 +113,7 @@ void db2GetForeignUpperPaths(PlannerInfo *root, UpperRelationKind stage, RelOptI state->total_cost = state->startup_cost + output_rel->rows * 10.0; path = (Path*) create_foreign_upper_path( root , input_rel -#if PG_VERSION_NUM >= 90600 , input_rel->reltarget /* pathtarget */ -#endif , output_rel->rows #if PG_VERSION_NUM >= 180000 , 0 /* disabled nodes (PG18+) if needed */ @@ -123,9 +121,7 @@ void db2GetForeignUpperPaths(PlannerInfo *root, UpperRelationKind stage, RelOptI , state->startup_cost , state->total_cost , NIL -#if PG_VERSION_NUM >= 90500 , NULL /* required_outer */ -#endif #if PG_VERSION_NUM >= 170000 , NIL /* fdw_outerpath */ #endif diff --git a/source/db2GetOptions.c b/source/db2GetOptions.c index 29513d1..e1c190f 100644 --- a/source/db2GetOptions.c +++ b/source/db2GetOptions.c @@ -1,15 +1,9 @@ #include #include #include -#if PG_VERSION_NUM < 120000 -#include -#include -#include -#else #include #include #include -#endif #include "db2_fdw.h" /** external prototypes */ diff --git a/source/db2GetShareFileName.c b/source/db2GetShareFileName.c index ae2e624..c3dbedf 100644 --- a/source/db2GetShareFileName.c +++ b/source/db2GetShareFileName.c @@ -1,14 +1,8 @@ #include #include -#if PG_VERSION_NUM < 120000 -#include -#include -#include -#else #include #include #include -#endif #include #include "db2_fdw.h" diff --git a/source/db2ImportForeignSchema.c b/source/db2ImportForeignSchema.c index 6a04c94..fc299e3 100644 --- a/source/db2ImportForeignSchema.c +++ b/source/db2ImportForeignSchema.c @@ -3,15 +3,9 @@ #include #include #include -#if PG_VERSION_NUM < 120000 -#include -#include -#include -#else #include #include #include -#endif #include "db2_fdw.h" /** external prototypes */ diff --git a/source/db2IsForeignRelUpdatable.c b/source/db2IsForeignRelUpdatable.c index 7324f6d..6058079 100644 --- a/source/db2IsForeignRelUpdatable.c +++ b/source/db2IsForeignRelUpdatable.c @@ -1,14 +1,8 @@ #include #include -#if PG_VERSION_NUM < 120000 -#include -#include -#include -#else #include #include #include -#endif #include "db2_fdw.h" /** external prototypes */ diff --git a/source/db2IterateForeignScan.c b/source/db2IterateForeignScan.c index ef24fe5..bc3c59d 100644 --- a/source/db2IterateForeignScan.c +++ b/source/db2IterateForeignScan.c @@ -2,16 +2,10 @@ #include #include #include -#if PG_VERSION_NUM < 120000 -#include -#include -#include -#else #include #include #include #include -#endif #include "db2_fdw.h" #include "DB2FdwState.h" @@ -116,11 +110,7 @@ char* setSelectParameters (ParamDesc* paramList, ExprContext* econtext) { /** Evaluate the expression. * This code path cannot be reached in 9.1 */ -#if PG_VERSION_NUM < 100000 - datum = ExecEvalExpr ((ExprState *) (param->node), econtext, &is_null, NULL); -#else datum = ExecEvalExpr ((ExprState *) (param->node), econtext, &is_null); -#endif /* PG_VERSION_NUM */ } if (is_null) { diff --git a/source/db2PlanForeignModify.c b/source/db2PlanForeignModify.c index 02dd39a..aba4bd7 100644 --- a/source/db2PlanForeignModify.c +++ b/source/db2PlanForeignModify.c @@ -3,15 +3,9 @@ #include #include #include -#if PG_VERSION_NUM < 120000 -#include -#include -#include -#else #include #include #include -#endif #include "db2_fdw.h" #include "DB2FdwState.h" @@ -68,20 +62,14 @@ List* db2PlanForeignModify (PlannerInfo* root, ModifyTable* plan, Index resultRe #if PG_VERSION_NUM >= 160000 RTEPermissionInfo *perminfo = getRTEPermissionInfo(root->parse->rteperminfos, rte); updated_cols = bms_copy(perminfo->updatedCols); -#else -#if PG_VERSION_NUM >= 90500 - updated_cols = bms_copy(rte->updatedCols); #else updated_cols = bms_copy(rte->modifiedCols); -#endif /* PG_VERSION_NUM >= 90500 */ #endif /* PG_VERSION_NUM >= 160000 */ db2Debug1("> db2PlanForeignModify"); -#if PG_VERSION_NUM >= 90500 /* we don't support INSERT ... ON CONFLICT */ if (plan->onConflictAction != ONCONFLICT_NONE) ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("INSERT with ON CONFLICT clause is not supported"))); -#endif /* PG_VERSION_NUM */ /* check if the foreign table is scanned and we already planned that scan */ if (resultRelation < root->simple_rel_array_size diff --git a/source/db2ReAllocFree.c b/source/db2ReAllocFree.c index c7cace8..e6108ff 100644 --- a/source/db2ReAllocFree.c +++ b/source/db2ReAllocFree.c @@ -1,14 +1,8 @@ #include //#include "db2_pg.h" -#if PG_VERSION_NUM < 120000 -#include -#include -#include -#else #include #include #include -#endif #include "db2_fdw.h" /*+ external prototypes */ diff --git a/source/db2ReScanForeignScan.c b/source/db2ReScanForeignScan.c index 2ddce25..898dc2a 100644 --- a/source/db2ReScanForeignScan.c +++ b/source/db2ReScanForeignScan.c @@ -1,14 +1,8 @@ #include #include -#if PG_VERSION_NUM < 120000 -#include -#include -#include -#else #include #include #include -#endif #include "db2_fdw.h" #include "DB2FdwState.h" diff --git a/source/db2SetHandlers.c b/source/db2SetHandlers.c index 09e9d9c..c24fff0 100644 --- a/source/db2SetHandlers.c +++ b/source/db2SetHandlers.c @@ -1,15 +1,9 @@ #include #include #include -#if PG_VERSION_NUM < 120000 -#include -#include -#include -#else #include #include #include -#endif #include "db2_fdw.h" /** external prototypes */ diff --git a/source/db2_fdw.c b/source/db2_fdw.c index 7e8f3d7..46b3034 100644 --- a/source/db2_fdw.c +++ b/source/db2_fdw.c @@ -22,15 +22,9 @@ #include #include #include -#if PG_VERSION_NUM < 120000 -#include -#include -#include -#else #include #include #include -#endif #include "db2_fdw.h" #include "DB2FdwOption.h" @@ -424,11 +418,7 @@ PGDLLEXPORT Datum db2_diag (PG_FUNCTION_ARGS) { ) ); } -#if PG_VERSION_NUM < 120000 - srvId = HeapTupleGetOid(tup); -#else srvId = ((Form_pg_foreign_server)GETSTRUCT(tup))->oid; -#endif table_close (rel, AccessShareLock); /* get the foreign server, the user mapping and the FDW */ server = GetForeignServer (srvId); diff --git a/source/db2_fdw_utils.c b/source/db2_fdw_utils.c index 8b31612..64223e4 100644 --- a/source/db2_fdw_utils.c +++ b/source/db2_fdw_utils.c @@ -10,15 +10,9 @@ #include #include #include -#if PG_VERSION_NUM < 120000 -#include -#include -#include -#else #include #include #include -#endif #include "db2_fdw.h" #include "DB2FdwState.h" @@ -62,9 +56,7 @@ static void deparseCoalesceExpr (CoalesceExpr* expr, deparse_ static void deparseFuncExpr (FuncExpr* expr, deparse_expr_cxt* ctx); static void deparseAggref (Aggref* expr, deparse_expr_cxt* ctx); static void deparseCoerceViaIOExpr (CoerceViaIO* expr, deparse_expr_cxt* ctx); -#if PG_VERSION_NUM >= 100000 static void deparseSQLValueFuncExpr (SQLValueFunction* expr, deparse_expr_cxt* ctx); -#endif char* datumToString (Datum datum, Oid type); char* guessNlsLang (char* nls_lang); char* deparseDate (Datum datum); @@ -305,12 +297,10 @@ static void deparseExprInt (Expr* expr, deparse_expr_cxt* deparseCoerceViaIOExpr((CoerceViaIO*) expr, ctx); } break; - #if PG_VERSION_NUM >= 100000 case T_SQLValueFunction: { deparseSQLValueFuncExpr((SQLValueFunction*)expr, ctx); } break; - #endif case T_Aggref: { deparseAggref((Aggref*)expr, ctx); } @@ -667,19 +657,11 @@ static void deparseScalarArrayOpExpr (ScalarArrayOpExpr* expr, deparse_expr_cxt* /* the second (=last) argument is an ArrayCoerceExpr */ ArrayCoerceExpr* arraycoerce = (ArrayCoerceExpr *) rightexpr; /* if the conversion requires more than binary coercion, don't push it down */ - #if PG_VERSION_NUM < 110000 - if (arraycoerce->elemfuncid != InvalidOid) { - db2Debug2(" arraycoerce->elemfuncid != InvalidOid"); - bResult = false; - break; - } - #else if (arraycoerce->elemexpr && arraycoerce->elemexpr->type != T_RelabelType) { db2Debug2(" arraycoerce->elemexpr && arraycoerce->elemexpr->type != T_RelabelType"); bResult = false; break; } - #endif /* the actual array is here */ rightexpr = arraycoerce->arg; } @@ -1104,7 +1086,6 @@ static void deparseCoerceViaIOExpr (CoerceViaIO* expr, deparse_expr_cxt* db2Debug1("< %s::deparseCoerceViaIOExpr: %s", __FILE__, ctx->buf->data); } -#if PG_VERSION_NUM >= 100000 static void deparseSQLValueFuncExpr (SQLValueFunction* expr, deparse_expr_cxt* ctx) { db2Debug1("> %s::deparseSQLValueFuncExpr", __FILE__); switch (expr->op) { @@ -1130,7 +1111,6 @@ static void deparseSQLValueFuncExpr (SQLValueFunction* expr, deparse_expr_cxt* } db2Debug1("< %s::deparseSQLValueFuncExpr: %s", __FILE__, ctx->buf->data); } -#endif static void deparseAggref (Aggref* expr, deparse_expr_cxt* ctx) { db2Debug1("> %s::deparseAggref", __FILE__); From f2f3c86c201ba0fa5592a85f1064d23705eb1b57 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Tue, 20 Jan 2026 17:15:44 +0100 Subject: [PATCH 009/120] added some later PG_VERSION_NUM sections for compatibility of the code vor PG 13-18 --- source/db2GetForeignUpperPaths.c | 4 ++++ source/db2PlanForeignModify.c | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/source/db2GetForeignUpperPaths.c b/source/db2GetForeignUpperPaths.c index fa7bc59..e081f0e 100644 --- a/source/db2GetForeignUpperPaths.c +++ b/source/db2GetForeignUpperPaths.c @@ -35,7 +35,9 @@ void db2GetForeignUpperPaths(PlannerInfo *root, UpperRelationKind stage, RelOptI db2Debug3(" query->hasDistinctOn : %s", query->hasDistinctOn ? "true" : "false"); db2Debug3(" query->hasTargetSRFs : %s", query->hasTargetSRFs ? "true" : "false"); db2Debug3(" query->hasForUpdate : %s", query->hasForUpdate ? "true" : "false"); + #if PG_VERSION_NUM >= 180000 db2Debug3(" query->hasGroupRTE : %s", query->hasGroupRTE ? "true" : "false"); + #endif db2Debug3(" query->hasModifyingCTE: %s", query->hasModifyingCTE ? "true" : "false"); db2Debug3(" query->hasRecursive : %s", query->hasRecursive ? "true" : "false"); db2Debug3(" query->hasSubLinks : %s", query->hasSubLinks ? "true" : "false"); @@ -69,6 +71,7 @@ void db2GetForeignUpperPaths(PlannerInfo *root, UpperRelationKind stage, RelOptI } } break; + #if PG_VERSION_NUM >= 150000 case UPPERREL_PARTIAL_DISTINCT: { // partial "SELECT DISTINCT" db2Debug2(" stage: %d - UPPERREL_PARTIAL_DISTINCT", stage); db2Debug2(" query->hasDistinctOn: %d", query->hasDistinctOn); @@ -77,6 +80,7 @@ void db2GetForeignUpperPaths(PlannerInfo *root, UpperRelationKind stage, RelOptI } } break; + #endif case UPPERREL_DISTINCT: { // "SELECT DISTINCT" db2Debug2(" stage: %d - UPPERREL_DISTINCT", stage); db2Debug2(" query->hasDistinctOn: %d", query->hasDistinctOn); diff --git a/source/db2PlanForeignModify.c b/source/db2PlanForeignModify.c index aba4bd7..640708d 100644 --- a/source/db2PlanForeignModify.c +++ b/source/db2PlanForeignModify.c @@ -63,7 +63,7 @@ List* db2PlanForeignModify (PlannerInfo* root, ModifyTable* plan, Index resultRe RTEPermissionInfo *perminfo = getRTEPermissionInfo(root->parse->rteperminfos, rte); updated_cols = bms_copy(perminfo->updatedCols); #else - updated_cols = bms_copy(rte->modifiedCols); + updated_cols = bms_copy(rte->updatedCols); #endif /* PG_VERSION_NUM >= 160000 */ db2Debug1("> db2PlanForeignModify"); From 52eba745c47954eb4339924caad0c32ec8b76675 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Wed, 21 Jan 2026 12:09:21 +0100 Subject: [PATCH 010/120] moved deparse functions from db2_fdw_utils to db2_deparse --- Makefile | 1 + source/db2_deparse.c | 1490 ++++++++++++++++++++++++++++++++++++++++ source/db2_fdw_utils.c | 1468 +-------------------------------------- 3 files changed, 1500 insertions(+), 1459 deletions(-) create mode 100644 source/db2_deparse.c diff --git a/Makefile b/Makefile index 62c320c..4269157 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,7 @@ EXTENSION = db2_fdw EXTVERSION = $(shell grep default_version $(EXTENSION).control | sed -e "s/default_version[[:space:]]*=[[:space:]]*'\([^']*\)'/\1/") MODULE_big = db2_fdw OBJS = source/db2_fdw.o\ + source/db2_deparse.o\ source/db2GetForeignPlan.o\ source/db2GetForeignPaths.o\ source/db2GetForeignUpperPaths.o\ diff --git a/source/db2_deparse.c b/source/db2_deparse.c new file mode 100644 index 0000000..a6d971c --- /dev/null +++ b/source/db2_deparse.c @@ -0,0 +1,1490 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "db2_fdw.h" +#include "DB2FdwState.h" + +/** Context for deparseExpr */ +typedef struct deparse_expr_cxt { + PlannerInfo* root; /* global planner state */ + RelOptInfo* foreignrel; /* the foreign relation we are planning for */ + RelOptInfo* scanrel; /* the underlying scan relation. Same as foreignrel, when that represents a join or a base relation. */ + List** params_list; /* exprs that will become remote Params */ + StringInfo buf; /* output buffer to append to */ +} deparse_expr_cxt; + +/** external prototypes */ +extern short c2dbType (short fcType); +extern void db2Debug1 (const char* message, ...); +extern void db2Debug2 (const char* message, ...); +extern void* db2alloc (const char* type, size_t size); +extern void* db2strdup (const char* source); +extern void db2free (void* p); + +/** local prototypes */ +void appendAsType (StringInfoData* dest, Oid type); +void deparseFromExprForRel (PlannerInfo* root, RelOptInfo* foreignrel, StringInfo buf, List** params_list); +char* deparseWhereConditions (PlannerInfo* root, RelOptInfo* rel); +char* deparseExpr (PlannerInfo* root, RelOptInfo* rel, Expr* expr, List** params); +static void deparseExprInt (Expr* expr, deparse_expr_cxt* ctx); +static void deparseConstExpr (Const* expr, deparse_expr_cxt* ctx); +static void deparseParamExpr (Param* expr, deparse_expr_cxt* ctx); +static void deparseVarExpr (Var* expr, deparse_expr_cxt* ctx); +static void deparseOpExpr (OpExpr* expr, deparse_expr_cxt* ctx); +static void deparseScalarArrayOpExpr (ScalarArrayOpExpr* expr, deparse_expr_cxt* ctx); +static void deparseDistinctExpr (DistinctExpr* expr, deparse_expr_cxt* ctx); +static void deparseNullTest (NullTest* expr, deparse_expr_cxt* ctx); +static void deparseNullIfExpr (NullIfExpr* expr, deparse_expr_cxt* ctx); +static void deparseBoolExpr (BoolExpr* expr, deparse_expr_cxt* ctx); +static void deparseCaseExpr (CaseExpr* expr, deparse_expr_cxt* ctx); +static void deparseCoalesceExpr (CoalesceExpr* expr, deparse_expr_cxt* ctx); +static void deparseFuncExpr (FuncExpr* expr, deparse_expr_cxt* ctx); +static void deparseAggref (Aggref* expr, deparse_expr_cxt* ctx); +static void deparseCoerceViaIOExpr (CoerceViaIO* expr, deparse_expr_cxt* ctx); +static void deparseSQLValueFuncExpr (SQLValueFunction* expr, deparse_expr_cxt* ctx); +static char* datumToString (Datum datum, Oid type); +char* deparseDate (Datum datum); +char* deparseTimestamp (Datum datum, bool hasTimezone); +static char* deparseInterval (Datum datum); +static const char* get_jointype_name (JoinType jointype); + +/** appendAsType + * Append "s" to "dest", adding appropriate casts for datetime "type". + */ +void appendAsType (StringInfoData* dest, Oid type) { + db2Debug1("> %s::appendAsType", __FILE__); + db2Debug2(" dest->data: '%s'",dest->data); + db2Debug2(" type: %d",type); + switch (type) { + case DATEOID: + appendStringInfo (dest, "CAST (? AS DATE)"); + break; + case TIMESTAMPOID: + appendStringInfo (dest, "CAST (? AS TIMESTAMP)"); + break; + case TIMESTAMPTZOID: + appendStringInfo (dest, "CAST (? AS TIMESTAMP)"); + break; + case TIMEOID: + appendStringInfo (dest, "(CAST (? AS TIME))"); + break; + case TIMETZOID: + appendStringInfo (dest, "(CAST (? AS TIME))"); + break; + default: + appendStringInfo (dest, "?"); + break; + } + db2Debug2(" dest->data: '%s'", dest->data); + db2Debug1("< %s::appendAsType", __FILE__); +} + +/** This macro is used by deparseExpr to identify PostgreSQL + * types that can be translated to DB2 SQL. + */ +#define canHandleType(x) ((x) == TEXTOID || (x) == CHAROID || (x) == BPCHAROID \ + || (x) == VARCHAROID || (x) == NAMEOID || (x) == INT8OID || (x) == INT2OID \ + || (x) == INT4OID || (x) == OIDOID || (x) == FLOAT4OID || (x) == FLOAT8OID \ + || (x) == NUMERICOID || (x) == DATEOID || (x) == TIMEOID || (x) == TIMESTAMPOID \ + || (x) == TIMESTAMPTZOID || (x) == INTERVALOID) + +/** deparseFromExprForRel + * Construct FROM clause for given relation. + * The function constructs ... JOIN ... ON ... for join relation. For a base + * relation it just returns the table name. + * All tables get an alias based on the range table index. + */ +void deparseFromExprForRel (PlannerInfo* root, RelOptInfo* foreignrel, StringInfo buf, List** params_list) { + DB2FdwState* fdwState = (DB2FdwState*) foreignrel->fdw_private; + + db2Debug1("> deparseFromExprForRel"); + db2Debug2(" buf: '%s",buf->data); + if (IS_SIMPLE_REL (foreignrel)) { + appendStringInfo (buf, "%s", fdwState->db2Table->name); + appendStringInfo (buf, " %s%d", REL_ALIAS_PREFIX, foreignrel->relid); + } else { + /* join relation */ + RelOptInfo* rel_o = fdwState->outerrel; + RelOptInfo* rel_i = fdwState->innerrel; + StringInfoData join_sql_o; + StringInfoData join_sql_i; + ListCell* lc = NULL; + bool is_first = true; + char* where = NULL; + + /* Deparse outer relation */ + initStringInfo (&join_sql_o); + deparseFromExprForRel (root, rel_o, &join_sql_o, params_list); + + /* Deparse inner relation */ + initStringInfo (&join_sql_i); + deparseFromExprForRel (root, rel_i, &join_sql_i, params_list); + + // For a join relation FROM clause entry is deparsed as (outer relation) (inner relation) ON joinclauses + appendStringInfo (buf, "(%s %s JOIN %s ON ", join_sql_o.data, get_jointype_name (fdwState->jointype), join_sql_i.data); + + /* we can only get here if the join is pushed down, so there are join clauses */ + Assert (fdwState->joinclauses); + + foreach (lc, fdwState->joinclauses) { + Expr *expr = (Expr *)lfirst(lc); + /* connect expressions with AND */ + if (!is_first) + appendStringInfo(buf, " AND "); + /* deparse and append a join condition */ + where = deparseExpr(root, foreignrel, expr, params_list); + appendStringInfo(buf, "%s", where); + is_first = false; + } + /* End the FROM clause entry. */ + appendStringInfo (buf, ")"); + } + db2Debug2(" buf: '%s'",buf->data); + db2Debug1("< deparseFromExprForRel"); +} + +/** deparseWhereConditions + * Classify conditions into remote_conds or local_conds. + * Those conditions that can be pushed down will be collected into + * an DB2 WHERE clause that is returned. + */ +char* deparseWhereConditions (PlannerInfo* root, RelOptInfo * rel) { + List* conditions = rel->baserestrictinfo; + DB2FdwState* fdwState = (DB2FdwState*) rel->fdw_private; + ListCell* cell; + char* where; + char* keyword = "WHERE"; + StringInfoData where_clause; + + db2Debug1("> deparseWhereCondition"); + initStringInfo (&where_clause); + foreach (cell, conditions) { + /* check if the condition can be pushed down */ + where = deparseExpr (root, rel, ((RestrictInfo*) lfirst (cell))->clause, &(fdwState->params)); + if (where != NULL) { + fdwState->remote_conds = lappend (fdwState->remote_conds, ((RestrictInfo*) lfirst (cell))->clause); + + /* append new WHERE clause to query string */ + appendStringInfo (&where_clause, " %s %s", keyword, where); + keyword = "AND"; + db2free (where); + } else { + fdwState->local_conds = lappend (fdwState->local_conds, ((RestrictInfo*) lfirst (cell))->clause); + } + } + db2Debug1("< deparseWhereCondition : %s",where_clause.data); + return where_clause.data; +} + +/** deparseExpr + * Create and return an DB2 SQL string from "expr". + * Returns NULL if that is not possible, else an allocated string. + * As a side effect, all Params incorporated in the WHERE clause + * will be stored in "params". + */ +char* deparseExpr (PlannerInfo* root, RelOptInfo* rel, Expr* expr, List** params) { + char* retValue = NULL; + db2Debug1("> %s::deparseExpr", __FILE__); + if (expr != NULL) { + deparse_expr_cxt* ctx = db2alloc("deparseExpr.context", sizeof(deparse_expr_cxt)); + StringInfoData buf; + + initStringInfo(&buf); + + ctx->root = root; + ctx->foreignrel = rel; + ctx->scanrel = rel; + ctx->buf = &buf; + ctx->params_list = params; + deparseExprInt(expr, ctx); + retValue = (buf.len > 0) ? db2strdup(buf.data) : NULL; + db2free(ctx->buf->data); + db2free(ctx); + } + db2Debug1("< %s::deparseExpr: %s", __FILE__, retValue); + return retValue; +} + +static void deparseExprInt (Expr* expr, deparse_expr_cxt* ctx) { + db2Debug1("> %s::deparseExprInt", __FILE__); + db2Debug2(" expr: %x",expr); + if (expr != NULL) { + db2Debug2(" expr->type: %d",expr->type); + switch (expr->type) { + case T_Const: { + deparseConstExpr((Const*)expr, ctx); + } + break; + case T_Param: { + deparseParamExpr((Param*) expr, ctx); + } + break; + case T_Var: { + deparseVarExpr ((Var*)expr, ctx); + } + break; + case T_OpExpr: { + deparseOpExpr ((OpExpr*)expr, ctx); + } + break; + case T_ScalarArrayOpExpr: { + deparseScalarArrayOpExpr ((ScalarArrayOpExpr*)expr, ctx); + } + break; + case T_DistinctExpr: { + deparseDistinctExpr ((DistinctExpr*)expr, ctx); + } + break; + case T_NullIfExpr: { + deparseNullIfExpr ((NullIfExpr*)expr, ctx); + } + break; + case T_BoolExpr: { + deparseBoolExpr ((BoolExpr*)expr, ctx); + } + break; + case T_RelabelType: { + deparseExprInt (((RelabelType*)expr)->arg, ctx); + } + break; + case T_CoerceToDomain: { + deparseExprInt (((CoerceToDomain*)expr)->arg, ctx); + } + break; + case T_CaseExpr: { + deparseCaseExpr ((CaseExpr*)expr, ctx); + } + break; + case T_CoalesceExpr: { + deparseCoalesceExpr ((CoalesceExpr*)expr, ctx); + } + break; + case T_NullTest: { + deparseNullTest((NullTest*) expr, ctx); + } + break; + case T_FuncExpr: { + deparseFuncExpr((FuncExpr*)expr, ctx); + } + break; + case T_CoerceViaIO: { + deparseCoerceViaIOExpr((CoerceViaIO*) expr, ctx); + } + break; + case T_SQLValueFunction: { + deparseSQLValueFuncExpr((SQLValueFunction*)expr, ctx); + } + break; + case T_Aggref: { + deparseAggref((Aggref*)expr, ctx); + } + break; + default: { + /* we cannot translate this to DB2 */ + db2Debug2(" expression cannot be translated to DB2", __FILE__); + } + break; + } + } + db2Debug1("< %s::deparseExpr : %s", __FILE__, ctx->buf->data); +} + +static void deparseConstExpr (Const* expr, deparse_expr_cxt* ctx) { + db2Debug1("> %s::deparseConstExpr", __FILE__); + if (expr->constisnull) { + /* only translate NULLs of a type DB2 can handle */ + if (canHandleType (expr->consttype)) { + appendStringInfo (ctx->buf, "NULL"); + } + } else { + /* get a string representation of the value */ + char* c = datumToString (expr->constvalue, expr->consttype); + if (c != NULL) { + appendStringInfo (ctx->buf, "%s", c); + } + } + db2Debug1("< %s::deparseConstExpr", __FILE__); +} + +static void deparseParamExpr (Param* expr, deparse_expr_cxt* ctx) { + #ifdef OLD_FDW_API + /* don't try to push down parameters with 9.1 */ + db2Debug1("> %s::deparseParamExpr", __FILE__); + db2Debug2(" don't try to push down parameters with 9.1"); + #else + ListCell* cell = NULL; + char parname[10]; + + db2Debug1("> %s::deparseParamExpr", __FILE__); + /* don't try to handle interval parameters */ + if (!canHandleType (expr->paramtype) || expr->paramtype == INTERVALOID) { + db2Debug2(" !canHhandleType(expr->paramtype %d) || rxpr->paramtype == INTERVALOID)", expr->paramtype); + } else { + /* find the index in the parameter list */ + int index = 0; + foreach (cell, *(ctx->params_list)) { + ++index; + if (equal (expr, (Node*) lfirst (cell))) + break; + } + if (cell == NULL) { + /* add the parameter to the list */ + ++index; + *(ctx->params_list) = lappend (*(ctx->params_list), expr); + } + /* parameters will be called :p1, :p2 etc. */ + snprintf (parname, 10, ":p%d", index); + appendAsType (ctx->buf, expr->paramtype); + } + #endif /* OLD_FDW_API */ + db2Debug1("< %s::deparseParamExpr", __FILE__); +} + +static void deparseVarExpr (Var* expr, deparse_expr_cxt* ctx) { + const DB2Table* var_table = NULL; /* db2Table that belongs to a Var */ + + db2Debug1("> %s::deparseVarExpr", __FILE__); + /* check if the variable belongs to one of our foreign tables */ + #ifdef JOIN_API + if (IS_SIMPLE_REL (ctx->foreignrel)) { + #endif /* JOIN_API */ + if (expr->varno == ctx->foreignrel->relid && expr->varlevelsup == 0) + var_table = ((DB2FdwState*)ctx->foreignrel->fdw_private)->db2Table; + #ifdef JOIN_API + } else { + DB2FdwState* joinstate = (DB2FdwState*) ctx->foreignrel->fdw_private; + DB2FdwState* outerstate = (DB2FdwState*) joinstate->outerrel->fdw_private; + DB2FdwState* innerstate = (DB2FdwState*) joinstate->innerrel->fdw_private; + /* we can't get here if the foreign table has no columns, so this is safe */ + if (expr->varno == outerstate->db2Table->cols[0]->varno && expr->varlevelsup == 0) + var_table = outerstate->db2Table; + if (expr->varno == innerstate->db2Table->cols[0]->varno && expr->varlevelsup == 0) + var_table = innerstate->db2Table; + } + #endif /* JOIN_API */ + if (var_table) { + /* the variable belongs to a foreign table, replace it with the name */ + /* we cannot handle system columns */ + db2Debug2(" varattno: %d",expr->varattno); + if (expr->varattno > 0) { + /** Allow boolean columns here. + * They will be rendered as ("COL" <> 0). + */ + if (!(canHandleType (expr->vartype) || expr->vartype == BOOLOID)) { + db2Debug2(" !(canHandleType (vartype %d) || vartype == BOOLOID",expr->vartype); + } else { + /* get var_table column index corresponding to this column (-1 if none) */ + int index = var_table->ncols - 1; + while (index >= 0 && var_table->cols[index]->pgattnum != expr->varattno) { + --index; + } + /* if no DB2 column corresponds, translate as NULL */ + if (index == -1) { + appendStringInfo (ctx->buf, "NULL"); + } else { + /** Don't try to convert a column reference if the type is + * converted from a non-string type in DB2 to a string type + * in PostgreSQL because functions and operators won't work the same. + */ + short db2type = c2dbType(var_table->cols[index]->colType); + db2Debug2(" db2type: %d", db2type); + if ((expr->vartype == TEXTOID || expr->vartype == BPCHAROID || expr->vartype == VARCHAROID) && db2type != DB2_VARCHAR && db2type != DB2_CHAR) { + db2Debug2(" vartype: %d", expr->vartype); + } else { + /* work around the lack of booleans in DB2 */ + if (expr->vartype == BOOLOID) { + appendStringInfo (ctx->buf, "("); + } + /* qualify with an alias based on the range table index */ + appendStringInfo(ctx->buf, "%s%d.%s", "r", var_table->cols[index]->varno, var_table->cols[index]->colName); + /* work around the lack of booleans in DB2 */ + if (expr->vartype == BOOLOID) { + appendStringInfo (ctx->buf, " <> 0)"); + } + } + } + } + } + } else { + #ifdef OLD_FDW_API + // treat it like a parameter + // don't try to push down parameters with 9.1 + db2Debug2(" don't try to push down parameters with 9.1"); + #else + // don't try to handle type interval + if (!canHandleType (expr->vartype) || expr->vartype == INTERVALOID) { + db2Debug2(" !canHandleType (vartype %d) || vartype == INTERVALOID", expr->vartype); + } else { + ListCell* cell = NULL; + int index = 0; + + /* find the index in the parameter list */ + foreach (cell, *(ctx->params_list)) { + ++index; + if (equal (expr, (Node*) lfirst (cell))) + break; + } + if (cell == NULL) { + /* add the parameter to the list */ + ++index; + *(ctx->params_list) = lappend (*(ctx->params_list), expr); + } + /* parameters will be called :p1, :p2 etc. */ + appendStringInfo (ctx->buf, ":p%d", index); + } + #endif /* OLD_FDW_API */ + } + db2Debug1("< %s::deparseVarExpr", __FILE__); +} + +static void deparseOpExpr (OpExpr* expr, deparse_expr_cxt* ctx) { + char* opername = NULL; + char oprkind = 0x00; + Oid rightargtype= 0; + Oid leftargtype = 0; + Oid schema = 0; + HeapTuple tuple ; + + db2Debug1("> %s::deparseOpExpr", __FILE__); + /* get operator name, kind, argument type and schema */ + tuple = SearchSysCache1 (OPEROID, ObjectIdGetDatum (expr->opno)); + if (!HeapTupleIsValid (tuple)) { + elog (ERROR, "cache lookup failed for operator %u", expr->opno); + } + opername = db2strdup (((Form_pg_operator) GETSTRUCT (tuple))->oprname.data); + oprkind = ((Form_pg_operator) GETSTRUCT (tuple))->oprkind; + leftargtype = ((Form_pg_operator) GETSTRUCT (tuple))->oprleft; + rightargtype = ((Form_pg_operator) GETSTRUCT (tuple))->oprright; + schema = ((Form_pg_operator) GETSTRUCT (tuple))->oprnamespace; + ReleaseSysCache (tuple); + /* ignore operators in other than the pg_catalog schema */ + if (schema != PG_CATALOG_NAMESPACE) { + db2Debug2(" schema != PG_CATALOG_NAMESPACE"); + } else { + if (!canHandleType (rightargtype)) { + db2Debug2(" !canHandleType rightargtype(%d)", rightargtype); + } else { + /** Don't translate operations on two intervals. + * INTERVAL YEAR TO MONTH and INTERVAL DAY TO SECOND don't mix well. + */ + if (leftargtype == INTERVALOID && rightargtype == INTERVALOID) { + db2Debug2(" leftargtype == INTERVALOID && rightargtype == INTERVALOID"); + } else { + /* the operators that we can translate */ + if ((strcmp (opername, ">") == 0 && rightargtype != TEXTOID && rightargtype != BPCHAROID && rightargtype != NAMEOID && rightargtype != CHAROID) + || (strcmp (opername, "<") == 0 && rightargtype != TEXTOID && rightargtype != BPCHAROID && rightargtype != NAMEOID && rightargtype != CHAROID) + || (strcmp (opername, ">=") == 0 && rightargtype != TEXTOID && rightargtype != BPCHAROID && rightargtype != NAMEOID && rightargtype != CHAROID) + || (strcmp (opername, "<=") == 0 && rightargtype != TEXTOID && rightargtype != BPCHAROID && rightargtype != NAMEOID && rightargtype != CHAROID) + || (strcmp (opername, "-") == 0 && rightargtype != DATEOID && rightargtype != TIMESTAMPOID && rightargtype != TIMESTAMPTZOID) + || strcmp (opername, "=") == 0 || strcmp (opername, "<>") == 0 || strcmp (opername, "+") == 0 || strcmp (opername, "*") == 0 + || strcmp (opername, "~~") == 0 || strcmp (opername, "!~~") == 0 || strcmp (opername, "~~*") == 0 || strcmp (opername, "!~~*") == 0 + || strcmp (opername, "^") == 0 || strcmp (opername, "%") == 0 || strcmp (opername, "&") == 0 || strcmp (opername, "|/") == 0 + || strcmp (opername, "@") == 0) { + char* left = NULL; + + left = deparseExpr (ctx->root, ctx->foreignrel, linitial(expr->args), ctx->params_list); + db2Debug2(" left: %s", left); + if (left != NULL) { + if (oprkind == 'b') { + /* binary operator */ + char* right = NULL; + + right = deparseExpr (ctx->root, ctx->foreignrel, lsecond(expr->args), ctx->params_list); + db2Debug2(" right: %s", right); + if (right != NULL) { + if (strcmp (opername, "~~") == 0) { + appendStringInfo (ctx->buf, "(%s LIKE %s ESCAPE '\\')", left, right); + } else if (strcmp (opername, "!~~") == 0) { + appendStringInfo (ctx->buf, "(%s NOT LIKE %s ESCAPE '\\')", left, right); + } else if (strcmp (opername, "~~*") == 0) { + appendStringInfo (ctx->buf, "(UPPER(%s) LIKE UPPER(%s) ESCAPE '\\')", left, right); + } else if (strcmp (opername, "!~~*") == 0) { + appendStringInfo (ctx->buf, "(UPPER(%s) NOT LIKE UPPER(%s) ESCAPE '\\')", left, right); + } else if (strcmp (opername, "^") == 0) { + appendStringInfo (ctx->buf, "POWER(%s, %s)", left, right); + } else if (strcmp (opername, "%") == 0) { + appendStringInfo (ctx->buf, "MOD(%s, %s)", left, right); + } else if (strcmp (opername, "&") == 0) { + appendStringInfo (ctx->buf, "BITAND(%s, %s)", left, right); + } else { + /* the other operators have the same name in DB2 */ + appendStringInfo (ctx->buf, "(%s %s %s)", left, opername, right); + } + db2free(right); + } + } else { + /* unary operator */ + if (strcmp (opername, "|/") == 0) { + appendStringInfo (ctx->buf, "SQRT(%s)", left); + } else if (strcmp (opername, "@") == 0) { + appendStringInfo (ctx->buf, "ABS(%s)", left); + } else { + /* unary + or - */ + appendStringInfo (ctx->buf, "(%s%s)", opername, left); + } + } + db2free(left); + } + } else { + /* cannot translate this operator */ + db2Debug2(" cannot translate this opername: %s", opername); + } + } + } + } + db2free (opername); + db2Debug1("< %s::deparseOpExpr", __FILE__); +} + +static void deparseScalarArrayOpExpr (ScalarArrayOpExpr* expr, deparse_expr_cxt* ctx) { + char* opername; + Oid leftargtype; + Oid schema; + HeapTuple tuple; + + db2Debug1("> %s::deparseScalarArrayOpExpr", __FILE__); + tuple = SearchSysCache1 (OPEROID, ObjectIdGetDatum (expr->opno)); + if (!HeapTupleIsValid (tuple)) { + elog (ERROR, "cache lookup failed for operator %u", expr->opno); + } + opername = db2strdup(((Form_pg_operator) GETSTRUCT (tuple))->oprname.data); + leftargtype = ((Form_pg_operator) GETSTRUCT (tuple))->oprleft; + schema = ((Form_pg_operator) GETSTRUCT (tuple))->oprnamespace; + ReleaseSysCache (tuple); + /* get the type's output function */ + tuple = SearchSysCache1 (TYPEOID, ObjectIdGetDatum (leftargtype)); + if (!HeapTupleIsValid (tuple)) { + elog (ERROR, "cache lookup failed for type %u", leftargtype); + } + ReleaseSysCache (tuple); + /* ignore operators in other than the pg_catalog schema */ + if (schema != PG_CATALOG_NAMESPACE) { + db2Debug2(" schema != PG_CATALOG_NAMESPACE"); + } else { + /* don't try to push down anything but IN and NOT IN expressions */ + if ((strcmp (opername, "=") != 0 || !expr->useOr) && (strcmp (opername, "<>") != 0 || expr->useOr)) { + db2Debug2(" don't try to push down anything but IN and NOT IN expressions"); + } else { + if (!canHandleType (leftargtype)) { + db2Debug2(" cannot Handle Type leftargtype (%d)", leftargtype); + } else { + char* left = NULL; + char* right = NULL; + + left = deparseExpr (ctx->root, ctx->foreignrel,linitial (expr->args), ctx->params_list); + // check if anything has been added beyond the initial "(" + if (left != NULL) { + Expr* rightexpr = NULL; + bool bResult = true; + + /* the second (=last) argument can be Const, ArrayExpr or ArrayCoerceExpr */ + rightexpr = (Expr*)llast(expr->args); + switch (rightexpr->type) { + case T_Const: { + StringInfoData buf; + /* the second (=last) argument is a Const of ArrayType */ + Const* constant = (Const*) rightexpr; + /* using NULL in place of an array or value list is valid in DB2 and PostgreSQL */ + initStringInfo(&buf); + if (constant->constisnull) { + appendStringInfo(&buf, "NULL"); + right = db2strdup(buf.data); + } else { + Datum datum; + bool isNull; + ArrayIterator iterator = array_create_iterator (DatumGetArrayTypeP (constant->constvalue), 0); + bool first_arg = true; + + /* loop through the array elements */ + while (array_iterate (iterator, &datum, &isNull)) { + char *c; + if (isNull) { + c = "NULL"; + } else { + c = datumToString (datum, leftargtype); + db2Debug2(" c: %s",c); + if (c == NULL) { + array_free_iterator (iterator); + bResult = false; + break; + } + } + /* append the argument */ + appendStringInfo (&buf, "%s%s", first_arg ? "" : ", ", c); + first_arg = false; + } + array_free_iterator (iterator); + db2Debug2(" first_arg: %s", first_arg ? "true":"false"); + if (first_arg) { + // don't push down empty arrays + // since the semantics for NOT x = ANY() differ + bResult = false; + } + if (bResult) { + right = db2strdup(buf.data); + } + } + db2free(buf.data); + } + break; + case T_ArrayCoerceExpr: { + /* the second (=last) argument is an ArrayCoerceExpr */ + ArrayCoerceExpr* arraycoerce = (ArrayCoerceExpr *) rightexpr; + /* if the conversion requires more than binary coercion, don't push it down */ + if (arraycoerce->elemexpr && arraycoerce->elemexpr->type != T_RelabelType) { + db2Debug2(" arraycoerce->elemexpr && arraycoerce->elemexpr->type != T_RelabelType"); + bResult = false; + break; + } + /* the actual array is here */ + rightexpr = arraycoerce->arg; + } + /* fall through ! */ + case T_ArrayExpr: { + /* the second (=last) argument is an ArrayExpr */ + StringInfoData buf; + char* element = NULL; + ArrayExpr* array = (ArrayExpr*) rightexpr; + ListCell* cell = NULL; + bool first_arg = true; + + initStringInfo(&buf); + /* loop the array arguments */ + foreach (cell, array->elements) { + element = deparseExpr (ctx->root, ctx->foreignrel, (Expr*) lfirst (cell), ctx->params_list); + if (element == NULL) { + /* if any element cannot be converted, give up */ + db2free(buf.data); + bResult = false; + break; + } + appendStringInfo(&buf,"%s%s",(first_arg) ? "": ", ",element); + first_arg = false; + } + db2Debug2(" first_arg: %s", first_arg ? "true" : "false"); + if (first_arg) { + /* don't push down empty arrays, since the semantics for NOT x = ANY() differ */ + db2free(buf.data); + bResult = false; + break; + } + right = (bResult) ? db2strdup(buf.data) : NULL; + db2free(buf.data); + } + break; + default: { + db2Debug2(" rightexpr->type(%d) default ",rightexpr->type); + bResult = false; + } + break; + } + // only when there is a usable result otherwise keep value to null + if (bResult) { + appendStringInfo (ctx->buf, "(%s %s IN (%s))",left, expr->useOr ? "" : "NOT", right); + } + db2free(left); + db2free(right); + } + } + } + } + db2Debug1("< %s::deparseScalarArrayOpExpr", __FILE__); +} + +static void deparseDistinctExpr (DistinctExpr* expr, deparse_expr_cxt* ctx) { + Oid rightargtype = 0; + HeapTuple tuple; + + db2Debug1("> %s::deparseDistinctExpr", __FILE__); + tuple = SearchSysCache1 (OPEROID, ObjectIdGetDatum ((expr)->opno)); + if (!HeapTupleIsValid (tuple)) { + elog (ERROR, "cache lookup failed for operator %u", (expr)->opno); + } + rightargtype = ((Form_pg_operator) GETSTRUCT (tuple))->oprright; + ReleaseSysCache (tuple); + if (!canHandleType (rightargtype)) { + db2Debug2(" cannot Handle Type rightargtype (%d)",rightargtype); + } else { + char* left = NULL; + + left = deparseExpr (ctx->root, ctx->foreignrel, linitial ((expr)->args), ctx->params_list); + if (left != NULL) { + char* right = NULL; + + right = deparseExpr (ctx->root, ctx->foreignrel, lsecond ((expr)->args), ctx->params_list); + if (right != NULL) { + appendStringInfo (ctx->buf, "( %s IS DISTINCT FROM %s)", left, right); + } + db2free(right); + } + db2free(left); + } + db2Debug1("< %s::deparseDistinctExpr", __FILE__); +} + +/** Deparse IS [NOT] NULL expression. + */ +static void deparseNullTest (NullTest* expr, deparse_expr_cxt* ctx) { + StringInfo buf = ctx->buf; + + db2Debug1("> %s::deparseNullTest", __FILE__); + appendStringInfoChar(buf, '('); + deparseExprInt (expr->arg, ctx); + + /** For scalar inputs, we prefer to print as IS [NOT] NULL, which is + * shorter and traditional. If it's a rowtype input but we're applying a + * scalar test, must print IS [NOT] DISTINCT FROM NULL to be semantically + * correct. + */ + if (expr->argisrow || !type_is_rowtype(exprType((Node *) expr->arg))) { + if (expr->nulltesttype == IS_NULL) + appendStringInfoString(buf, " IS NULL)"); + else + appendStringInfoString(buf, " IS NOT NULL)"); + } else { + if (expr->nulltesttype == IS_NULL) + appendStringInfoString(buf, " IS NOT DISTINCT FROM NULL)"); + else + appendStringInfoString(buf, " IS DISTINCT FROM NULL)"); + } + db2Debug1("< %s::deparseNullTest : %s", __FILE__, buf->data); +} + +static void deparseNullIfExpr (NullIfExpr* expr, deparse_expr_cxt* ctx) { + Oid rightargtype = 0; + HeapTuple tuple; + + db2Debug1("> %s::deparseNullIfExpr", __FILE__); + tuple = SearchSysCache1 (OPEROID, ObjectIdGetDatum ((expr)->opno)); + if (!HeapTupleIsValid (tuple)) { + elog (ERROR, "cache lookup failed for operator %u", (expr)->opno); + } + rightargtype = ((Form_pg_operator) GETSTRUCT (tuple))->oprright; + ReleaseSysCache (tuple); + if (!canHandleType (rightargtype)) { + db2Debug2(" cannot Handle Type rightargtype (%d)",rightargtype); + } else { + char* left = NULL; + left = deparseExpr (ctx->root, ctx->foreignrel, linitial((expr)->args), ctx->params_list); + + if (left != NULL) { + char* right = NULL; + + right = deparseExpr (ctx->root, ctx->foreignrel, lsecond((expr)->args), ctx->params_list); + if (right != NULL) { + appendStringInfo (ctx->buf, "NULLIF(%s,%s)", left, right); + } + db2free(right); + } + db2free(left); + } + db2Debug1("< %s::deparseNullIfExpr: %s", __FILE__, ctx->buf->data); +} + +static void deparseBoolExpr (BoolExpr* expr, deparse_expr_cxt* ctx) { + ListCell* cell = NULL; + char* arg = NULL; + StringInfoData buf; + + db2Debug1("> %s::deparseBoolExpr", __FILE__); + initStringInfo(&buf); + arg = deparseExpr (ctx->root, ctx->foreignrel, linitial(expr->args), ctx->params_list); + if (arg != NULL) { + bool bBreak = false; + appendStringInfo (&buf, "(%s%s", expr->boolop == NOT_EXPR ? "NOT " : "", arg); + do_each_cell(cell, expr->args, list_next(expr->args, list_head(expr->args))) { + db2free(arg); + arg = deparseExpr (ctx->root, ctx->foreignrel, (Expr*)lfirst(cell), ctx->params_list); + if (arg != NULL) { + appendStringInfo (&buf, " %s %s", expr->boolop == AND_EXPR ? "AND":"OR", arg); + } else { + bBreak = true; + break; + } + } + if (!bBreak) { + appendStringInfo (ctx->buf, "%s)", buf.data); + } + } + db2free(buf.data); + db2free(arg); + db2Debug1("< %s::deparseBoolExpr: %s", __FILE__, ctx->buf->data); +} + +static void deparseCaseExpr (CaseExpr* expr, deparse_expr_cxt* ctx) { + db2Debug1("> %s::deparseCaseExpr", __FILE__); + if (!canHandleType (expr->casetype)) { + db2Debug2(" cannot Handle Type caseexpr->casetype (%d)", expr->casetype); + } else { + StringInfoData buf; + bool bBreak = false; + char* arg = NULL; + ListCell* cell = NULL; + + initStringInfo (&buf); + appendStringInfo (&buf, "CASE"); + + if (expr->arg != NULL) { + /* for the form "CASE arg WHEN ...", add first expression */ + arg = deparseExpr (ctx->root, ctx->foreignrel, expr->arg, ctx->params_list); + db2Debug2(" CASE %s WHEN ...", arg); + if (arg == NULL) { + appendStringInfo (&buf, " %s", arg); + } else { + bBreak = true; + } + } + if (!bBreak) { + /* append WHEN ... THEN clauses */ + foreach (cell, expr->args) { + CaseWhen* whenclause = (CaseWhen*) lfirst (cell); + /* WHEN */ + if (expr->arg == NULL) { + /* for CASE WHEN ..., use the whole expression */ + arg = deparseExpr (ctx->root, ctx->foreignrel, whenclause->expr, ctx->params_list); + } else { + /* for CASE arg WHEN ..., use only the right branch of the equality */ + arg = deparseExpr (ctx->root, ctx->foreignrel, lsecond (((OpExpr*) whenclause->expr)->args), ctx->params_list); + } + db2Debug2(" WHEN %s ", arg); + if (arg != NULL) { + appendStringInfo (&buf, " WHEN %s", arg); + } else { + bBreak = true; + break; + } /* THEN */ + arg = deparseExpr (ctx->root, ctx->foreignrel, whenclause->result, ctx->params_list); + db2Debug2(" THEN %s ", arg); + if (arg != NULL) { + appendStringInfo (&buf, " THEN %s", arg); + } else { + bBreak = true; + break; + } + } + if (!bBreak) { + /* append ELSE clause if appropriate */ + if (expr->defresult != NULL) { + arg = deparseExpr (ctx->root, ctx->foreignrel, expr->defresult, ctx->params_list); + db2Debug2(" ELSE %s", arg); + if (arg != NULL) { + appendStringInfo (&buf, " ELSE %s", arg); + } else { + bBreak = true; + } + } + /* append END */ + appendStringInfo (&buf, " END"); + } + } + if (!bBreak) { + appendStringInfo(ctx->buf,"%s",buf.data); + } + db2free(buf.data); + } + db2Debug1("< %s::deparseCaseExpr: %s", __FILE__, ctx->buf->data); +} + +static void deparseCoalesceExpr (CoalesceExpr* expr, deparse_expr_cxt* ctx) { + db2Debug1("> %s::deparseCoalesceExpr", __FILE__); + if (!canHandleType (expr->coalescetype)) { + db2Debug2(" cannot Handle Type coalesceexpr->coalescetype (%d)", expr->coalescetype); + } else { + StringInfoData result; + char* arg = NULL; + bool first_arg = true; + ListCell* cell = NULL; + + initStringInfo (&result); + appendStringInfo (&result, "COALESCE("); + foreach (cell, expr->args) { + arg = deparseExpr (ctx->root, ctx->foreignrel, (Expr*)lfirst(cell),ctx->params_list); + db2Debug2(" arg: %s", arg); + if (arg != NULL) { + appendStringInfo(&result, ((first_arg) ? "%s" : ", %s"), arg); + first_arg = false; + } else { + break; + } + } + if (arg != NULL) { + appendStringInfo (ctx->buf, "%s)",result.data); + } + db2free(result.data); + } + db2Debug1("< %s::deparseCoalesceExpr: %s", __FILE__, ctx->buf->data); +} + +static void deparseFuncExpr (FuncExpr* expr, deparse_expr_cxt* ctx) { + db2Debug1("> %s::deparseFuncExpr", __FILE__); + if (!canHandleType (expr->funcresulttype)) { + db2Debug2(" cannot handle funct->funcresulttype: %d",expr->funcresulttype); + } else if (expr->funcformat == COERCE_IMPLICIT_CAST) { + /* do nothing for implicit casts */ + db2Debug2(" COERCE_IMPLICIT_CAST == expr->funcformat(%d)",expr->funcformat); + deparseExprInt (linitial(expr->args), ctx); + } else { + Oid schema; + char* opername; + HeapTuple tuple; + + /* get function name and schema */ + tuple = SearchSysCache1 (PROCOID, ObjectIdGetDatum (expr->funcid)); + if (!HeapTupleIsValid (tuple)) { + elog (ERROR, "cache lookup failed for function %u", expr->funcid); + } + opername = db2strdup (((Form_pg_proc) GETSTRUCT (tuple))->proname.data); + db2Debug2(" opername: %s",opername); + schema = ((Form_pg_proc) GETSTRUCT (tuple))->pronamespace; + db2Debug2(" schema: %d",schema); + ReleaseSysCache (tuple); + /* ignore functions in other than the pg_catalog schema */ + if (schema != PG_CATALOG_NAMESPACE) { + db2Debug2(" T_FuncExpr: schema(%d) != PG_CATALOG_NAMESPACE", schema); + } else { + /* the "normal" functions that we can translate */ + if (strcmp (opername, "abs") == 0 || strcmp (opername, "acos") == 0 || strcmp (opername, "asin") == 0 + || strcmp (opername, "atan") == 0 || strcmp (opername, "atan2") == 0 || strcmp (opername, "ceil") == 0 + || strcmp (opername, "ceiling") == 0 || strcmp (opername, "char_length") == 0 || strcmp (opername, "character_length") == 0 + || strcmp (opername, "concat") == 0 || strcmp (opername, "cos") == 0 || strcmp (opername, "exp") == 0 + || strcmp (opername, "initcap") == 0 || strcmp (opername, "length") == 0 || strcmp (opername, "lower") == 0 + || strcmp (opername, "lpad") == 0 || strcmp (opername, "ltrim") == 0 || strcmp (opername, "mod") == 0 + || strcmp (opername, "octet_length") == 0 || strcmp (opername, "position") == 0 || strcmp (opername, "pow") == 0 + || strcmp (opername, "power") == 0 || strcmp (opername, "replace") == 0 || strcmp (opername, "round") == 0 + || strcmp (opername, "rpad") == 0 || strcmp (opername, "rtrim") == 0 || strcmp (opername, "sign") == 0 + || strcmp (opername, "sin") == 0 || strcmp (opername, "sqrt") == 0 || strcmp (opername, "strpos") == 0 + || strcmp (opername, "substr") == 0 || strcmp (opername, "tan") == 0 || strcmp (opername, "to_char") == 0 + || strcmp (opername, "to_date") == 0 || strcmp (opername, "to_number") == 0 || strcmp (opername, "to_timestamp") == 0 + || strcmp (opername, "translate") == 0 || strcmp (opername, "trunc") == 0 || strcmp (opername, "upper") == 0 + || (strcmp (opername, "substring") == 0 && list_length (expr->args) == 3)) { + ListCell* cell; + char* arg = NULL; + bool ok = true; + bool first_arg = true; + StringInfoData buf; + + initStringInfo (&buf); + if (strcmp (opername, "ceiling") == 0) + appendStringInfo (&buf, "CEIL("); + else if (strcmp (opername, "char_length") == 0 || strcmp (opername, "character_length") == 0) + appendStringInfo (&buf, "LENGTH("); + else if (strcmp (opername, "pow") == 0) + appendStringInfo (&buf, "POWER("); + else if (strcmp (opername, "octet_length") == 0) + appendStringInfo (&buf, "LENGTHB("); + else if (strcmp (opername, "position") == 0 || strcmp (opername, "strpos") == 0) + appendStringInfo (&buf, "INSTR("); + else if (strcmp (opername, "substring") == 0) + appendStringInfo (&buf, "SUBSTR("); + else + appendStringInfo (&buf, "%s(", opername); + foreach (cell, expr->args) { + arg = deparseExpr (ctx->root, ctx->foreignrel, lfirst (cell), ctx->params_list); + if (arg != NULL) { + appendStringInfo (&buf, "%s%s", (first_arg) ? ", " : "",arg); + first_arg = false; + db2free(arg); + } else { + ok = false; + db2Debug2(" T_FuncExpr: function %s that we cannot render for DB2", opername); + break; + } + } + appendStringInfo (&buf, ")"); + // copy to return value when successful + if (ok) { + appendStringInfo(ctx->buf,"%s",buf.data); + } + db2free(buf.data); + } else if (strcmp (opername, "date_part") == 0) { + char* left = NULL; + + /* special case: EXTRACT */ + left = deparseExpr (ctx->root, ctx->foreignrel, linitial (expr->args), ctx->params_list); + if (left == NULL) { + db2Debug2(" T_FuncExpr: function %s that we cannot render for DB2", opername); + } else { + /* can only handle these fields in DB2 */ + if (strcmp (left, "'year'") == 0 || strcmp (left, "'month'") == 0 + || strcmp (left, "'day'") == 0 || strcmp (left, "'hour'") == 0 + || strcmp (left, "'minute'") == 0 || strcmp (left, "'second'") == 0 + || strcmp (left, "'timezone_hour'") == 0 || strcmp (left, "'timezone_minute'") == 0) { + char* right = NULL; + + /* remove final quote */ + left[strlen (left) - 1] = '\0'; + right = deparseExpr (ctx->root, ctx->foreignrel, lsecond (expr->args), ctx->params_list); + if (right == NULL) { + db2Debug2(" T_FuncExpr: function %s that we cannot render for DB2", opername); + } else { + appendStringInfo (ctx->buf, "EXTRACT(%s FROM %s)", left + 1, right); + } + db2free(right); + } else { + db2Debug2(" T_FuncExpr: function %s that we cannot render for DB2", opername); + } + } + db2free (left); + } else if (strcmp (opername, "now") == 0 || strcmp (opername, "transaction_timestamp") == 0) { + /* special case: current timestamp */ + appendStringInfo (ctx->buf, "(CAST (?/*:now*/ AS TIMESTAMP))"); + } else { + /* function that we cannot render for DB2 */ + db2Debug2(" T_FuncExpr: function %s that we cannot render for DB2", opername); + } + } + db2free (opername); + } + db2Debug1("< %s::deparseFuncExpr: %s", __FILE__, ctx->buf->data); +} + +static void deparseCoerceViaIOExpr (CoerceViaIO* expr, deparse_expr_cxt* ctx) { + db2Debug1("> %s::deparseCoerceViaIOExpr", __FILE__); + /* We will only handle casts of 'now'. */ + /* only casts to these types are handled */ + if (expr->resulttype != DATEOID && expr->resulttype != TIMESTAMPOID && expr->resulttype != TIMESTAMPTZOID) { + db2Debug2(" only casts to DATEOID, TIMESTAMPOID and TIMESTAMPTZOID are handled"); + } else if (expr->arg->type != T_Const) { + /* the argument must be a Const */ + db2Debug2(" T_CoerceViaIO: the argument must be a Const"); + } else { + Const* constant = (Const *) expr->arg; + if (constant->constisnull || (constant->consttype != CSTRINGOID && constant->consttype != TEXTOID)) { + /* the argument must be a not-NULL text constant */ + db2Debug2(" T_CoerceViaIO: the argument must be a not-NULL text constant"); + } else { + /* get the type's output function */ + HeapTuple tuple = SearchSysCache1 (TYPEOID, ObjectIdGetDatum (constant->consttype)); + regproc typoutput; + if (!HeapTupleIsValid (tuple)) { + elog (ERROR, "cache lookup failed for type %u", constant->consttype); + } + typoutput = ((Form_pg_type) GETSTRUCT (tuple))->typoutput; + ReleaseSysCache (tuple); + /* the value must be "now" */ + if (strcmp (DatumGetCString (OidFunctionCall1 (typoutput, constant->constvalue)), "now") != 0) { + db2Debug2(" value must be 'now'"); + } else { + switch (expr->resulttype) { + case DATEOID: + appendStringInfo(ctx->buf, "TRUNC(CAST (CAST(?/*:now*/ AS TIMESTAMP) AS DATE))"); + break; + case TIMESTAMPOID: + appendStringInfo(ctx->buf, "(CAST (CAST (?/*:now*/ AS TIMESTAMP) AS TIMESTAMP))"); + break; + case TIMESTAMPTZOID: + appendStringInfo(ctx->buf, "(CAST (?/*:now*/ AS TIMESTAMP))"); + break; + case TIMEOID: + appendStringInfo(ctx->buf, "(CAST (CAST (?/*:now*/ AS TIME) AS TIME))"); + break; + case TIMETZOID: + appendStringInfo(ctx->buf, "(CAST (?/*:now*/ AS TIME))"); + break; + } + } + } + } + db2Debug1("< %s::deparseCoerceViaIOExpr: %s", __FILE__, ctx->buf->data); +} + +static void deparseSQLValueFuncExpr (SQLValueFunction* expr, deparse_expr_cxt* ctx) { + db2Debug1("> %s::deparseSQLValueFuncExpr", __FILE__); + switch (expr->op) { + case SVFOP_CURRENT_DATE: + appendStringInfo(ctx->buf, "TRUNC(CAST (CAST(?/*:now*/ AS TIMESTAMP) AS DATE))"); + break; + case SVFOP_CURRENT_TIMESTAMP: + appendStringInfo(ctx->buf, "(CAST (?/*:now*/ AS TIMESTAMP))"); + break; + case SVFOP_LOCALTIMESTAMP: + appendStringInfo(ctx->buf, "(CAST (CAST (?/*:now*/ AS TIMESTAMP) AS TIMESTAMP))"); + break; + case SVFOP_CURRENT_TIME: + appendStringInfo(ctx->buf, "(CAST (?/*:now*/ AS TIME))"); + break; + case SVFOP_LOCALTIME: + appendStringInfo(ctx->buf, "(CAST (CAST (?/*:now*/ AS TIME) AS TIME))"); + break; + default: + /* don't push down other functions */ + db2Debug2(" op %d cannot be translated to DB2", expr->op); + break; + } + db2Debug1("< %s::deparseSQLValueFuncExpr: %s", __FILE__, ctx->buf->data); +} + +static void deparseAggref (Aggref* expr, deparse_expr_cxt* ctx) { + db2Debug1("> %s::deparseAggref", __FILE__); + if (expr == NULL) { + db2Debug2(" expr is NULL"); + } else { + /* Resolve aggregate function name (OID -> pg_proc.proname). */ + HeapTuple tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(expr->aggfnoid)); + char* aggname = NULL; + char* nspname = NULL; + if (!HeapTupleIsValid(tuple)) { + elog(ERROR, "cache lookup failed for function %u", expr->aggfnoid); + } else { + Form_pg_proc procform = (Form_pg_proc) GETSTRUCT(tuple); + aggname = pstrdup(NameStr(procform->proname)); + /* Optional: capture schema for debugging/qualification decisions. */ + if (OidIsValid(procform->pronamespace)) { + HeapTuple ntup = SearchSysCache1(NAMESPACEOID, ObjectIdGetDatum(procform->pronamespace)); + if (HeapTupleIsValid(ntup)) { + Form_pg_namespace nspform = (Form_pg_namespace) GETSTRUCT(ntup); + nspname = pstrdup(NameStr(nspform->nspname)); + ReleaseSysCache(ntup); + } + } + ReleaseSysCache(tuple); + } + db2Debug2( " aggref->aggfnoid=%u name=%s%s%s" + , expr->aggfnoid + , nspname ? nspname : "" + , nspname ? "." : "" + , aggname ? aggname : "" + ); + /* We only support deparsing simple, standard aggregates for now. + * (This can be expanded to ordered-set / FILTER / WITHIN GROUP later.) + */ + if (expr->aggorder != NIL) { + db2Debug2(" aggregate ORDER BY not supported for pushdown"); + } else if (aggname != NULL) { + const char* db2func = NULL; + bool distinct = (expr->aggdistinct != NIL); + bool ok = true; + + if (strcmp(aggname, "count") == 0) db2func = "COUNT"; + else if (strcmp(aggname, "sum") == 0) db2func = "SUM"; + else if (strcmp(aggname, "avg") == 0) db2func = "AVG"; + else if (strcmp(aggname, "min") == 0) db2func = "MIN"; + else if (strcmp(aggname, "max") == 0) db2func = "MAX"; + else { + /* Unknown aggregate name: we can still report it (above), but don't emit SQL. */ + db2Debug2(" aggregate '%s' not supported for DB2 deparse", aggname); + } + if (db2func != NULL) { + StringInfoData result; + + initStringInfo(&result); + appendStringInfo(&result, "%s(", db2func); + if (distinct) { + appendStringInfoString(&result, "DISTINCT "); + } + if (expr->aggstar) { + /* COUNT(*) */ + appendStringInfoString(&result, "*"); + } else { + ListCell* lc; + char* arg = NULL; + bool first_arg = true; + + foreach (lc, expr->args) { + Node* argnode = (Node*) lfirst(lc); + Expr* argexpr = NULL; + + if (argnode == NULL) { + ok = false; + break; + } + if (argnode->type == T_TargetEntry) { + argexpr = ((TargetEntry*) argnode)->expr; + } else { + argexpr = (Expr*) argnode; + } + arg = deparseExpr(ctx->root, ctx->foreignrel, argexpr, ctx->params_list); + if (arg == NULL) { + ok = false; + break; + } + appendStringInfo(&result, "%s%s", arg, first_arg ? "" : ", "); + first_arg = false; + } + } + if (ok) { + appendStringInfo(ctx->buf, "%s)",result.data); + } else { + db2Debug2(" parsed aggref so far: %s", result.data); + db2Debug2(" could not deparse aggregate args"); + } + db2free(result.data); + } + } + } + db2Debug1("< %s::deparseAggref: %s", __FILE__, ctx->buf->data); +} + +/** datumToString + * Convert a Datum to a string by calling the type output function. + * Returns the result or NULL if it cannot be converted to DB2 SQL. + */ +static char* datumToString (Datum datum, Oid type) { + StringInfoData result; + regproc typoutput; + HeapTuple tuple; + char* str; + char* p; + db2Debug1("> %s::datumToString", __FILE__); + /* get the type's output function */ + tuple = SearchSysCache1 (TYPEOID, ObjectIdGetDatum (type)); + if (!HeapTupleIsValid (tuple)) { + elog (ERROR, "cache lookup failed for type %u", type); + } + typoutput = ((Form_pg_type) GETSTRUCT (tuple))->typoutput; + ReleaseSysCache (tuple); + + /* render the constant in DB2 SQL */ + switch (type) { + case TEXTOID: + case CHAROID: + case BPCHAROID: + case VARCHAROID: + case NAMEOID: + str = DatumGetCString (OidFunctionCall1 (typoutput, datum)); + /* + * Don't try to convert empty strings to DB2. + * DB2 treats empty strings as NULL. + */ + if (str[0] == '\0') + return NULL; + + /* quote string */ + initStringInfo (&result); + appendStringInfo (&result, "'"); + for (p = str; *p; ++p) { + if (*p == '\'') + appendStringInfo (&result, "'"); + appendStringInfo (&result, "%c", *p); + } + appendStringInfo (&result, "'"); + break; + case INT8OID: + case INT2OID: + case INT4OID: + case OIDOID: + case FLOAT4OID: + case FLOAT8OID: + case NUMERICOID: + str = DatumGetCString (OidFunctionCall1 (typoutput, datum)); + initStringInfo (&result); + appendStringInfo (&result, "%s", str); + break; + case DATEOID: + str = deparseDate (datum); + initStringInfo (&result); + appendStringInfo (&result, "(CAST ('%s' AS DATE))", str); + break; + case TIMESTAMPOID: + str = deparseTimestamp (datum, false); + initStringInfo (&result); + appendStringInfo (&result, "(CAST ('%s' AS TIMESTAMP))", str); + break; + case TIMESTAMPTZOID: + str = deparseTimestamp (datum, false); + initStringInfo (&result); + appendStringInfo (&result, "(CAST ('%s' AS TIMESTAMP))", str); + break; + case TIMEOID: + str = deparseTimestamp (datum, false); + initStringInfo (&result); + appendStringInfo (&result, "(CAST ('%s' AS TIME))", str); + break; + case TIMETZOID: + str = deparseTimestamp (datum, false); + initStringInfo (&result); + appendStringInfo (&result, "(CAST ('%s' AS TIME))", str); + break; + case INTERVALOID: + str = deparseInterval (datum); + if (str == NULL) + return NULL; + initStringInfo (&result); + appendStringInfo (&result, "%s", str); + break; + default: + return NULL; + } + db2Debug1("< %s::datumToString - returns: '%s'", __FILE__, result.data); + return result.data; +} + +/** deparseDate + * Render a PostgreSQL date so that DB2 can parse it. + */ +char* deparseDate (Datum datum) { + struct pg_tm datetime_tm; + StringInfoData s; + db2Debug1("> %s::deparseDate", __FILE__); + if (DATE_NOT_FINITE (DatumGetDateADT (datum))) + ereport (ERROR, (errcode (ERRCODE_FDW_INVALID_ATTRIBUTE_VALUE), errmsg ("infinite date value cannot be stored in DB2"))); + + /* get the parts */ + (void) j2date (DatumGetDateADT (datum) + POSTGRES_EPOCH_JDATE, &(datetime_tm.tm_year), &(datetime_tm.tm_mon), &(datetime_tm.tm_mday)); + + if (datetime_tm.tm_year < 0) + ereport (ERROR, (errcode (ERRCODE_FDW_INVALID_ATTRIBUTE_VALUE), errmsg ("BC date value cannot be stored in DB2"))); + + initStringInfo (&s); + appendStringInfo (&s, "%04d-%02d-%02d 00:00:00", datetime_tm.tm_year > 0 ? datetime_tm.tm_year : -datetime_tm.tm_year + 1, datetime_tm.tm_mon, datetime_tm.tm_mday); + db2Debug1("< %s::deparseDate - returns: '%s'", __FILE__, s.data); + return s.data; +} + +/** deparseTimestamp + * Render a PostgreSQL timestamp so that DB2 can parse it. + */ +char* deparseTimestamp (Datum datum, bool hasTimezone) { + struct pg_tm datetime_tm; + int32 tzoffset; + fsec_t datetime_fsec; + StringInfoData s; + db2Debug1("> %s::deparseTimestamp",__FILE__); + /* this is sloppy, but DatumGetTimestampTz and DatumGetTimestamp are the same */ + if (TIMESTAMP_NOT_FINITE (DatumGetTimestampTz (datum))) + ereport (ERROR, (errcode (ERRCODE_FDW_INVALID_ATTRIBUTE_VALUE), errmsg ("infinite timestamp value cannot be stored in DB2"))); + + /* get the parts */ + tzoffset = 0; + (void) timestamp2tm (DatumGetTimestampTz (datum), hasTimezone ? &tzoffset : NULL, &datetime_tm, &datetime_fsec, NULL, NULL); + + if (datetime_tm.tm_year < 0) + ereport (ERROR, (errcode (ERRCODE_FDW_INVALID_ATTRIBUTE_VALUE), errmsg ("BC date value cannot be stored in DB2"))); + + initStringInfo (&s); + if (hasTimezone) + appendStringInfo (&s, "%04d-%02d-%02d %02d:%02d:%02d.%06d%+03d:%02d", + datetime_tm.tm_year > 0 ? datetime_tm.tm_year : -datetime_tm.tm_year + 1, + datetime_tm.tm_mon, datetime_tm.tm_mday, datetime_tm.tm_hour, + datetime_tm.tm_min, datetime_tm.tm_sec, (int32) datetime_fsec, + -tzoffset / 3600, ((tzoffset > 0) ? tzoffset % 3600 : -tzoffset % 3600) / 60); + else + appendStringInfo (&s, "%04d-%02d-%02d %02d:%02d:%02d.%06d", + datetime_tm.tm_year > 0 ? datetime_tm.tm_year : -datetime_tm.tm_year + 1, + datetime_tm.tm_mon, datetime_tm.tm_mday, datetime_tm.tm_hour, + datetime_tm.tm_min, datetime_tm.tm_sec, (int32) datetime_fsec); + db2Debug1("< %s::deparseTimestamp - returns: '%s'", __FILE__, s.data); + return s.data; +} + +/** deparsedeparseInterval + * Render a PostgreSQL timestamp so that DB2 can parse it. + */ +static char* deparseInterval (Datum datum) { + #if PG_VERSION_NUM >= 150000 + struct pg_itm tm; + #else + struct pg_tm tm; + #endif + fsec_t fsec=0; + StringInfoData s; + char* sign; + int idx = 0; + + db2Debug1("> %s::deparseInterval",__FILE__); + #if PG_VERSION_NUM >= 150000 + interval2itm (*DatumGetIntervalP (datum), &tm); + #else + if (interval2tm (*DatumGetIntervalP (datum), &tm, &fsec) != 0) { + elog (ERROR, "could not convert interval to tm"); + } + #endif + /* only translate intervals that can be translated to INTERVAL DAY TO SECOND */ +// if (tm.tm_year != 0 || tm.tm_mon != 0) +// return NULL; + + /* DB2 intervals have only one sign */ + if (tm.tm_mday < 0 || tm.tm_hour < 0 || tm.tm_min < 0 || tm.tm_sec < 0 || fsec < 0) { + sign = "-"; + /* all signs must match */ + if (tm.tm_mday > 0 || tm.tm_hour > 0 || tm.tm_min > 0 || tm.tm_sec > 0 || fsec > 0) + return NULL; + tm.tm_mday = -tm.tm_mday; + tm.tm_hour = -tm.tm_hour; + tm.tm_min = -tm.tm_min; + tm.tm_sec = -tm.tm_sec; + fsec = -fsec; + } else { + sign = "+"; + } + initStringInfo (&s); + if (tm.tm_year > 0) { + appendStringInfo(&s, ((tm.tm_year > 1) ? "%d YEARS" : "%d YEAR"),tm.tm_year); + } + idx += tm.tm_year; + if (tm.tm_mon > 0) { + appendStringInfo(&s," %s ",(idx > 0 ) ? sign : ""); + appendStringInfo(&s, ((tm.tm_mon > 1) ? "%d MONTHS" : "%d MONTH"),tm.tm_mon); + } + idx += tm.tm_mon; + if (tm.tm_mday > 0) { + appendStringInfo(&s," %s ",(idx > 0 ) ? sign : ""); + appendStringInfo(&s, ((tm.tm_mday > 1) ? "%d DAYS" : "%d DAY"),tm.tm_mday); + } + idx += tm.tm_mday; + if (tm.tm_hour > 0) { + appendStringInfo(&s," %s ",(idx > 0 ) ? sign : ""); + #if PG_VERSION_NUM >= 150000 + appendStringInfo(&s, ((tm.tm_hour > 1) ? "%ld HOURS" : "%ld HOUR"),tm.tm_hour); + #else + appendStringInfo(&s, ((tm.tm_hour > 1) ? "%d HOURS" : "%d HOUR"),tm.tm_hour); + #endif + } + idx += tm.tm_hour; + if (tm.tm_min > 0) { + appendStringInfo(&s," %s ",(idx > 0 ) ? sign : ""); + appendStringInfo(&s, ((tm.tm_min > 1) ? "%d MINUTES" : "%d MINUTE"),tm.tm_min); + } + idx += tm.tm_min; + if (tm.tm_sec > 0) { + appendStringInfo(&s," %s ",(idx > 0 ) ? sign : ""); + appendStringInfo(&s, ((tm.tm_sec > 1) ? "%d SECONDS" : "%d SECOND"),tm.tm_sec); + } + idx += tm.tm_sec; + +// #if PG_VERSION_NUM >= 150000 +// appendStringInfo (&s, "INTERVAL '%s%d %02ld:%02d:%02d.%06d' DAY TO SECOND", sign, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, fsec); +// #else +// appendStringInfo (&s, "INTERVAL '%s%d %02d:%02d:%02d.%06d' DAY(9) TO SECOND(6)", sign, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, fsec); +// #endif + db2Debug1("< %s::deparseInterval - returns: '%s'",__FILE__,s.data); + return s.data; +} + +/** Output join name for given join type + */ +static const char* get_jointype_name (JoinType jointype) { + char* type = NULL; + db2Debug1("> get_jointype_name"); + switch (jointype) { + case JOIN_INNER: + type = "INNER"; + break; + case JOIN_LEFT: + type = "LEFT"; + break; + case JOIN_RIGHT: + type = "RIGHT"; + break; + case JOIN_FULL: + type= "FULL"; + break; + default: + /* Shouldn't come here, but protect from buggy code. */ + elog (ERROR, "unsupported join type %d", jointype); + break; + } + db2Debug2(" type: '%s'",type); + db2Debug1("< get_jointype_name"); + return type; +} diff --git a/source/db2_fdw_utils.c b/source/db2_fdw_utils.c index 64223e4..3ddebe8 100644 --- a/source/db2_fdw_utils.c +++ b/source/db2_fdw_utils.c @@ -1,31 +1,14 @@ #include -#include -#include -#include -#include +#include #include #include -#include -#include -#include #include #include #include -#include -#include #include "db2_fdw.h" #include "DB2FdwState.h" -/** Context for deparseExpr */ -typedef struct deparse_expr_cxt { - PlannerInfo* root; /* global planner state */ - RelOptInfo* foreignrel; /* the foreign relation we are planning for */ - RelOptInfo* scanrel; /* the underlying scan relation. Same as foreignrel, when that represents a join or a base relation. */ - StringInfo buf; /* output buffer to append to */ - List** params_list; /* exprs that will become remote Params */ -} deparse_expr_cxt; - /** external prototypes */ extern void db2GetLob (DB2Session* session, DB2Column* column, int cidx, char** value, long* value_len, unsigned long trunc); extern void db2Shutdown (void); @@ -38,1274 +21,10 @@ extern void* db2strdup (const char* source); extern void db2free (void* p); /** local prototypes */ -void appendAsType (StringInfoData* dest, Oid type); -void deparseFromExprForRel (PlannerInfo* root, RelOptInfo* foreignrel, StringInfo buf, List** params_list); -char* deparseWhereConditions (PlannerInfo* root, RelOptInfo* rel); -char* deparseExpr (PlannerInfo* root, RelOptInfo* rel, Expr* expr, List** params); -static void deparseExprInt (Expr* expr, deparse_expr_cxt* ctx); -static void deparseConstExpr (Const* expr, deparse_expr_cxt* ctx); -static void deparseParamExpr (Param* expr, deparse_expr_cxt* ctx); -static void deparseVarExpr (Var* expr, deparse_expr_cxt* ctx); -static void deparseOpExpr (OpExpr* expr, deparse_expr_cxt* ctx); -static void deparseScalarArrayOpExpr (ScalarArrayOpExpr* expr, deparse_expr_cxt* ctx); -static void deparseDistinctExpr (DistinctExpr* expr, deparse_expr_cxt* ctx); -static void deparseNullIfExpr (NullIfExpr* expr, deparse_expr_cxt* ctx); -static void deparseBoolExpr (BoolExpr* expr, deparse_expr_cxt* ctx); -static void deparseCaseExpr (CaseExpr* expr, deparse_expr_cxt* ctx); -static void deparseCoalesceExpr (CoalesceExpr* expr, deparse_expr_cxt* ctx); -static void deparseFuncExpr (FuncExpr* expr, deparse_expr_cxt* ctx); -static void deparseAggref (Aggref* expr, deparse_expr_cxt* ctx); -static void deparseCoerceViaIOExpr (CoerceViaIO* expr, deparse_expr_cxt* ctx); -static void deparseSQLValueFuncExpr (SQLValueFunction* expr, deparse_expr_cxt* ctx); -char* datumToString (Datum datum, Oid type); char* guessNlsLang (char* nls_lang); -char* deparseDate (Datum datum); -char* deparseTimestamp (Datum datum, bool hasTimezone); -char* deparseInterval (Datum datum); void exitHook (int code, Datum arg); void convertTuple (DB2FdwState* fdw_state, Datum* values, bool* nulls, bool trunc_lob) ; -void errorContextCallback (void* arg); -static const char* get_jointype_name (JoinType jointype); - -/** appendAsType - * Append "s" to "dest", adding appropriate casts for datetime "type". - */ -void appendAsType (StringInfoData* dest, Oid type) { - db2Debug1("> %s::appendAsType", __FILE__); - db2Debug2(" dest->data: '%s'",dest->data); - db2Debug2(" type: %d",type); - switch (type) { - case DATEOID: - appendStringInfo (dest, "CAST (? AS DATE)"); - break; - case TIMESTAMPOID: - appendStringInfo (dest, "CAST (? AS TIMESTAMP)"); - break; - case TIMESTAMPTZOID: - appendStringInfo (dest, "CAST (? AS TIMESTAMP)"); - break; - case TIMEOID: - appendStringInfo (dest, "(CAST (? AS TIME))"); - break; - case TIMETZOID: - appendStringInfo (dest, "(CAST (? AS TIME))"); - break; - default: - appendStringInfo (dest, "?"); - break; - } - db2Debug2(" dest->data: '%s'", dest->data); - db2Debug1("< %s::appendAsType", __FILE__); -} - -/** This macro is used by deparseExpr to identify PostgreSQL - * types that can be translated to DB2 SQL. - */ -#define canHandleType(x) ((x) == TEXTOID || (x) == CHAROID || (x) == BPCHAROID \ - || (x) == VARCHAROID || (x) == NAMEOID || (x) == INT8OID || (x) == INT2OID \ - || (x) == INT4OID || (x) == OIDOID || (x) == FLOAT4OID || (x) == FLOAT8OID \ - || (x) == NUMERICOID || (x) == DATEOID || (x) == TIMEOID || (x) == TIMESTAMPOID \ - || (x) == TIMESTAMPTZOID || (x) == INTERVALOID) - -/** deparseFromExprForRel - * Construct FROM clause for given relation. - * The function constructs ... JOIN ... ON ... for join relation. For a base - * relation it just returns the table name. - * All tables get an alias based on the range table index. - */ -void deparseFromExprForRel (PlannerInfo* root, RelOptInfo* foreignrel, StringInfo buf, List** params_list) { - DB2FdwState* fdwState = (DB2FdwState*) foreignrel->fdw_private; - - db2Debug1("> deparseFromExprForRel"); - db2Debug2(" buf: '%s",buf->data); - if (IS_SIMPLE_REL (foreignrel)) { - appendStringInfo (buf, "%s", fdwState->db2Table->name); - appendStringInfo (buf, " %s%d", REL_ALIAS_PREFIX, foreignrel->relid); - } else { - /* join relation */ - RelOptInfo* rel_o = fdwState->outerrel; - RelOptInfo* rel_i = fdwState->innerrel; - StringInfoData join_sql_o; - StringInfoData join_sql_i; - ListCell* lc = NULL; - bool is_first = true; - char* where = NULL; - - /* Deparse outer relation */ - initStringInfo (&join_sql_o); - deparseFromExprForRel (root, rel_o, &join_sql_o, params_list); - - /* Deparse inner relation */ - initStringInfo (&join_sql_i); - deparseFromExprForRel (root, rel_i, &join_sql_i, params_list); - - // For a join relation FROM clause entry is deparsed as (outer relation) (inner relation) ON joinclauses - appendStringInfo (buf, "(%s %s JOIN %s ON ", join_sql_o.data, get_jointype_name (fdwState->jointype), join_sql_i.data); - - /* we can only get here if the join is pushed down, so there are join clauses */ - Assert (fdwState->joinclauses); - - foreach (lc, fdwState->joinclauses) { - Expr *expr = (Expr *)lfirst(lc); - /* connect expressions with AND */ - if (!is_first) - appendStringInfo(buf, " AND "); - /* deparse and append a join condition */ - where = deparseExpr(root, foreignrel, expr, params_list); - appendStringInfo(buf, "%s", where); - is_first = false; - } - /* End the FROM clause entry. */ - appendStringInfo (buf, ")"); - } - db2Debug2(" buf: '%s'",buf->data); - db2Debug1("< deparseFromExprForRel"); -} - -/** deparseWhereConditions - * Classify conditions into remote_conds or local_conds. - * Those conditions that can be pushed down will be collected into - * an DB2 WHERE clause that is returned. - */ -char* deparseWhereConditions (PlannerInfo* root, RelOptInfo * rel) { - List* conditions = rel->baserestrictinfo; - DB2FdwState* fdwState = (DB2FdwState*) rel->fdw_private; - ListCell* cell; - char* where; - char* keyword = "WHERE"; - StringInfoData where_clause; - - db2Debug1("> deparseWhereCondition"); - initStringInfo (&where_clause); - foreach (cell, conditions) { - /* check if the condition can be pushed down */ - where = deparseExpr (root, rel, ((RestrictInfo*) lfirst (cell))->clause, &(fdwState->params)); - if (where != NULL) { - fdwState->remote_conds = lappend (fdwState->remote_conds, ((RestrictInfo*) lfirst (cell))->clause); - - /* append new WHERE clause to query string */ - appendStringInfo (&where_clause, " %s %s", keyword, where); - keyword = "AND"; - db2free (where); - } else { - fdwState->local_conds = lappend (fdwState->local_conds, ((RestrictInfo*) lfirst (cell))->clause); - } - } - db2Debug1("< deparseWhereCondition : %s",where_clause.data); - return where_clause.data; -} - -/** deparseExpr - * Create and return an DB2 SQL string from "expr". - * Returns NULL if that is not possible, else an allocated string. - * As a side effect, all Params incorporated in the WHERE clause - * will be stored in "params". - */ -char* deparseExpr (PlannerInfo* root, RelOptInfo* rel, Expr* expr, List** params) { - char* retValue = NULL; - db2Debug1("> %s::deparseExpr", __FILE__); - if (expr != NULL) { - deparse_expr_cxt* ctx = db2alloc("deparseExpr.context", sizeof(deparse_expr_cxt)); - StringInfoData buf; - - initStringInfo(&buf); - - ctx->root = root; - ctx->foreignrel = rel; - ctx->scanrel = rel; - ctx->buf = &buf; - ctx->params_list = params; - deparseExprInt(expr, ctx); - retValue = (buf.len > 0) ? db2strdup(buf.data) : NULL; - db2free(ctx->buf->data); - db2free(ctx); - } - db2Debug1("< %s::deparseExpr: %s", __FILE__, retValue); - return retValue; -} - -static void deparseExprInt (Expr* expr, deparse_expr_cxt* ctx) { - db2Debug1("> %s::deparseExprInt", __FILE__); - db2Debug2(" expr: %x",expr); - if (expr != NULL) { - db2Debug2(" expr->type: %d",expr->type); - switch (expr->type) { - case T_Const: { - deparseConstExpr((Const*)expr, ctx); - } - break; - case T_Param: { - deparseParamExpr((Param*) expr, ctx); - } - break; - case T_Var: { - deparseVarExpr ((Var*)expr, ctx); - } - break; - case T_OpExpr: { - deparseOpExpr ((OpExpr*)expr, ctx); - } - break; - case T_ScalarArrayOpExpr: { - deparseScalarArrayOpExpr ((ScalarArrayOpExpr*)expr, ctx); - } - break; - case T_DistinctExpr: { - deparseDistinctExpr ((DistinctExpr*)expr, ctx); - } - break; - case T_NullIfExpr: { - deparseNullIfExpr ((NullIfExpr*)expr, ctx); - } - break; - case T_BoolExpr: { - deparseBoolExpr ((BoolExpr*)expr, ctx); - } - break; - case T_RelabelType: { - deparseExprInt (((RelabelType*)expr)->arg, ctx); - } - break; - case T_CoerceToDomain: { - deparseExprInt (((CoerceToDomain*)expr)->arg, ctx); - } - break; - case T_CaseExpr: { - deparseCaseExpr ((CaseExpr*)expr, ctx); - } - break; - case T_CoalesceExpr: { - deparseCoalesceExpr ((CoalesceExpr*)expr, ctx); - } - break; - case T_NullTest: { - char* arg = NULL; - db2Debug2(" T_NullTest"); - arg = deparseExpr(ctx->root, ctx->foreignrel, ((NullTest*) expr)->arg, ctx->params_list); - db2Debug2(" T_NullTest arg: %s", arg); - if (arg != NULL) { - appendStringInfo (ctx->buf, "(%s IS %sNULL)", arg, ((NullTest*)expr)->nulltesttype == IS_NOT_NULL ? "NOT " : ""); - } - db2free(arg); - } - break; - case T_FuncExpr: { - deparseFuncExpr((FuncExpr*)expr, ctx); - } - break; - case T_CoerceViaIO: { - deparseCoerceViaIOExpr((CoerceViaIO*) expr, ctx); - } - break; - case T_SQLValueFunction: { - deparseSQLValueFuncExpr((SQLValueFunction*)expr, ctx); - } - break; - case T_Aggref: { - deparseAggref((Aggref*)expr, ctx); - } - break; - default: { - /* we cannot translate this to DB2 */ - db2Debug2(" expression cannot be translated to DB2", __FILE__); - } - break; - } - } - db2Debug1("< %s::deparseExpr : %s", __FILE__, ctx->buf->data); -} - -static void deparseConstExpr (Const* expr, deparse_expr_cxt* ctx) { - db2Debug1("> %s::deparseConstExpr", __FILE__); - if (expr->constisnull) { - /* only translate NULLs of a type DB2 can handle */ - if (canHandleType (expr->consttype)) { - appendStringInfo (ctx->buf, "NULL"); - } - } else { - /* get a string representation of the value */ - char* c = datumToString (expr->constvalue, expr->consttype); - if (c != NULL) { - appendStringInfo (ctx->buf, "%s", c); - } - } - db2Debug1("< %s::deparseConstExpr", __FILE__); -} - -static void deparseParamExpr (Param* expr, deparse_expr_cxt* ctx) { - #ifdef OLD_FDW_API - /* don't try to push down parameters with 9.1 */ - db2Debug1("> %s::deparseParamExpr", __FILE__); - db2Debug2(" don't try to push down parameters with 9.1"); - #else - ListCell* cell = NULL; - char parname[10]; - - db2Debug1("> %s::deparseParamExpr", __FILE__); - /* don't try to handle interval parameters */ - if (!canHandleType (expr->paramtype) || expr->paramtype == INTERVALOID) { - db2Debug2(" !canHhandleType(expr->paramtype %d) || rxpr->paramtype == INTERVALOID)", expr->paramtype); - } else { - /* find the index in the parameter list */ - int index = 0; - foreach (cell, *(ctx->params_list)) { - ++index; - if (equal (expr, (Node*) lfirst (cell))) - break; - } - if (cell == NULL) { - /* add the parameter to the list */ - ++index; - *(ctx->params_list) = lappend (*(ctx->params_list), expr); - } - /* parameters will be called :p1, :p2 etc. */ - snprintf (parname, 10, ":p%d", index); - appendAsType (ctx->buf, expr->paramtype); - } - #endif /* OLD_FDW_API */ - db2Debug1("< %s::deparseParamExpr", __FILE__); -} - -static void deparseVarExpr (Var* expr, deparse_expr_cxt* ctx) { - const DB2Table* var_table = NULL; /* db2Table that belongs to a Var */ - - db2Debug1("> %s::deparseVarExpr", __FILE__); - /* check if the variable belongs to one of our foreign tables */ - #ifdef JOIN_API - if (IS_SIMPLE_REL (ctx->foreignrel)) { - #endif /* JOIN_API */ - if (expr->varno == ctx->foreignrel->relid && expr->varlevelsup == 0) - var_table = ((DB2FdwState*)ctx->foreignrel->fdw_private)->db2Table; - #ifdef JOIN_API - } else { - DB2FdwState* joinstate = (DB2FdwState*) ctx->foreignrel->fdw_private; - DB2FdwState* outerstate = (DB2FdwState*) joinstate->outerrel->fdw_private; - DB2FdwState* innerstate = (DB2FdwState*) joinstate->innerrel->fdw_private; - /* we can't get here if the foreign table has no columns, so this is safe */ - if (expr->varno == outerstate->db2Table->cols[0]->varno && expr->varlevelsup == 0) - var_table = outerstate->db2Table; - if (expr->varno == innerstate->db2Table->cols[0]->varno && expr->varlevelsup == 0) - var_table = innerstate->db2Table; - } - #endif /* JOIN_API */ - if (var_table) { - /* the variable belongs to a foreign table, replace it with the name */ - /* we cannot handle system columns */ - db2Debug2(" varattno: %d",expr->varattno); - if (expr->varattno > 0) { - /** Allow boolean columns here. - * They will be rendered as ("COL" <> 0). - */ - if (!(canHandleType (expr->vartype) || expr->vartype == BOOLOID)) { - db2Debug2(" !(canHandleType (vartype %d) || vartype == BOOLOID",expr->vartype); - } else { - /* get var_table column index corresponding to this column (-1 if none) */ - int index = var_table->ncols - 1; - while (index >= 0 && var_table->cols[index]->pgattnum != expr->varattno) { - --index; - } - /* if no DB2 column corresponds, translate as NULL */ - if (index == -1) { - appendStringInfo (ctx->buf, "NULL"); - } else { - /** Don't try to convert a column reference if the type is - * converted from a non-string type in DB2 to a string type - * in PostgreSQL because functions and operators won't work the same. - */ - short db2type = c2dbType(var_table->cols[index]->colType); - db2Debug2(" db2type: %d", db2type); - if ((expr->vartype == TEXTOID || expr->vartype == BPCHAROID || expr->vartype == VARCHAROID) && db2type != DB2_VARCHAR && db2type != DB2_CHAR) { - db2Debug2(" vartype: %d", expr->vartype); - } else { - /* work around the lack of booleans in DB2 */ - if (expr->vartype == BOOLOID) { - appendStringInfo (ctx->buf, "("); - } - /* qualify with an alias based on the range table index */ - appendStringInfo(ctx->buf, "%s%d.%s", "r", var_table->cols[index]->varno, var_table->cols[index]->colName); - /* work around the lack of booleans in DB2 */ - if (expr->vartype == BOOLOID) { - appendStringInfo (ctx->buf, " <> 0)"); - } - } - } - } - } - } else { - #ifdef OLD_FDW_API - // treat it like a parameter - // don't try to push down parameters with 9.1 - db2Debug2(" don't try to push down parameters with 9.1"); - #else - // don't try to handle type interval - if (!canHandleType (expr->vartype) || expr->vartype == INTERVALOID) { - db2Debug2(" !canHandleType (vartype %d) || vartype == INTERVALOID", expr->vartype); - } else { - ListCell* cell = NULL; - int index = 0; - - /* find the index in the parameter list */ - foreach (cell, *(ctx->params_list)) { - ++index; - if (equal (expr, (Node*) lfirst (cell))) - break; - } - if (cell == NULL) { - /* add the parameter to the list */ - ++index; - *(ctx->params_list) = lappend (*(ctx->params_list), expr); - } - /* parameters will be called :p1, :p2 etc. */ - appendStringInfo (ctx->buf, ":p%d", index); - } - #endif /* OLD_FDW_API */ - } - db2Debug1("< %s::deparseVarExpr", __FILE__); -} - -static void deparseOpExpr (OpExpr* expr, deparse_expr_cxt* ctx) { - char* opername = NULL; - char oprkind = 0x00; - Oid rightargtype= 0; - Oid leftargtype = 0; - Oid schema = 0; - HeapTuple tuple ; - - db2Debug1("> %s::deparseOpExpr", __FILE__); - /* get operator name, kind, argument type and schema */ - tuple = SearchSysCache1 (OPEROID, ObjectIdGetDatum (expr->opno)); - if (!HeapTupleIsValid (tuple)) { - elog (ERROR, "cache lookup failed for operator %u", expr->opno); - } - opername = db2strdup (((Form_pg_operator) GETSTRUCT (tuple))->oprname.data); - oprkind = ((Form_pg_operator) GETSTRUCT (tuple))->oprkind; - leftargtype = ((Form_pg_operator) GETSTRUCT (tuple))->oprleft; - rightargtype = ((Form_pg_operator) GETSTRUCT (tuple))->oprright; - schema = ((Form_pg_operator) GETSTRUCT (tuple))->oprnamespace; - ReleaseSysCache (tuple); - /* ignore operators in other than the pg_catalog schema */ - if (schema != PG_CATALOG_NAMESPACE) { - db2Debug2(" schema != PG_CATALOG_NAMESPACE"); - } else { - if (!canHandleType (rightargtype)) { - db2Debug2(" !canHandleType rightargtype(%d)", rightargtype); - } else { - /** Don't translate operations on two intervals. - * INTERVAL YEAR TO MONTH and INTERVAL DAY TO SECOND don't mix well. - */ - if (leftargtype == INTERVALOID && rightargtype == INTERVALOID) { - db2Debug2(" leftargtype == INTERVALOID && rightargtype == INTERVALOID"); - } else { - /* the operators that we can translate */ - if ((strcmp (opername, ">") == 0 && rightargtype != TEXTOID && rightargtype != BPCHAROID && rightargtype != NAMEOID && rightargtype != CHAROID) - || (strcmp (opername, "<") == 0 && rightargtype != TEXTOID && rightargtype != BPCHAROID && rightargtype != NAMEOID && rightargtype != CHAROID) - || (strcmp (opername, ">=") == 0 && rightargtype != TEXTOID && rightargtype != BPCHAROID && rightargtype != NAMEOID && rightargtype != CHAROID) - || (strcmp (opername, "<=") == 0 && rightargtype != TEXTOID && rightargtype != BPCHAROID && rightargtype != NAMEOID && rightargtype != CHAROID) - || (strcmp (opername, "-") == 0 && rightargtype != DATEOID && rightargtype != TIMESTAMPOID && rightargtype != TIMESTAMPTZOID) - || strcmp (opername, "=") == 0 || strcmp (opername, "<>") == 0 || strcmp (opername, "+") == 0 || strcmp (opername, "*") == 0 - || strcmp (opername, "~~") == 0 || strcmp (opername, "!~~") == 0 || strcmp (opername, "~~*") == 0 || strcmp (opername, "!~~*") == 0 - || strcmp (opername, "^") == 0 || strcmp (opername, "%") == 0 || strcmp (opername, "&") == 0 || strcmp (opername, "|/") == 0 - || strcmp (opername, "@") == 0) { - char* left = NULL; - - left = deparseExpr (ctx->root, ctx->foreignrel, linitial(expr->args), ctx->params_list); - db2Debug2(" left: %s", left); - if (left != NULL) { - if (oprkind == 'b') { - /* binary operator */ - char* right = NULL; - - right = deparseExpr (ctx->root, ctx->foreignrel, lsecond(expr->args), ctx->params_list); - db2Debug2(" right: %s", right); - if (right != NULL) { - if (strcmp (opername, "~~") == 0) { - appendStringInfo (ctx->buf, "(%s LIKE %s ESCAPE '\\')", left, right); - } else if (strcmp (opername, "!~~") == 0) { - appendStringInfo (ctx->buf, "(%s NOT LIKE %s ESCAPE '\\')", left, right); - } else if (strcmp (opername, "~~*") == 0) { - appendStringInfo (ctx->buf, "(UPPER(%s) LIKE UPPER(%s) ESCAPE '\\')", left, right); - } else if (strcmp (opername, "!~~*") == 0) { - appendStringInfo (ctx->buf, "(UPPER(%s) NOT LIKE UPPER(%s) ESCAPE '\\')", left, right); - } else if (strcmp (opername, "^") == 0) { - appendStringInfo (ctx->buf, "POWER(%s, %s)", left, right); - } else if (strcmp (opername, "%") == 0) { - appendStringInfo (ctx->buf, "MOD(%s, %s)", left, right); - } else if (strcmp (opername, "&") == 0) { - appendStringInfo (ctx->buf, "BITAND(%s, %s)", left, right); - } else { - /* the other operators have the same name in DB2 */ - appendStringInfo (ctx->buf, "(%s %s %s)", left, opername, right); - } - db2free(right); - } - } else { - /* unary operator */ - if (strcmp (opername, "|/") == 0) { - appendStringInfo (ctx->buf, "SQRT(%s)", left); - } else if (strcmp (opername, "@") == 0) { - appendStringInfo (ctx->buf, "ABS(%s)", left); - } else { - /* unary + or - */ - appendStringInfo (ctx->buf, "(%s%s)", opername, left); - } - } - db2free(left); - } - } else { - /* cannot translate this operator */ - db2Debug2(" cannot translate this opername: %s", opername); - } - } - } - } - db2free (opername); - db2Debug1("< %s::deparseOpExpr", __FILE__); -} - -static void deparseScalarArrayOpExpr (ScalarArrayOpExpr* expr, deparse_expr_cxt* ctx) { - char* opername; - Oid leftargtype; - Oid schema; - HeapTuple tuple; - - db2Debug1("> %s::deparseScalarArrayOpExpr", __FILE__); - tuple = SearchSysCache1 (OPEROID, ObjectIdGetDatum (expr->opno)); - if (!HeapTupleIsValid (tuple)) { - elog (ERROR, "cache lookup failed for operator %u", expr->opno); - } - opername = db2strdup(((Form_pg_operator) GETSTRUCT (tuple))->oprname.data); - leftargtype = ((Form_pg_operator) GETSTRUCT (tuple))->oprleft; - schema = ((Form_pg_operator) GETSTRUCT (tuple))->oprnamespace; - ReleaseSysCache (tuple); - /* get the type's output function */ - tuple = SearchSysCache1 (TYPEOID, ObjectIdGetDatum (leftargtype)); - if (!HeapTupleIsValid (tuple)) { - elog (ERROR, "cache lookup failed for type %u", leftargtype); - } - ReleaseSysCache (tuple); - /* ignore operators in other than the pg_catalog schema */ - if (schema != PG_CATALOG_NAMESPACE) { - db2Debug2(" schema != PG_CATALOG_NAMESPACE"); - } else { - /* don't try to push down anything but IN and NOT IN expressions */ - if ((strcmp (opername, "=") != 0 || !expr->useOr) && (strcmp (opername, "<>") != 0 || expr->useOr)) { - db2Debug2(" don't try to push down anything but IN and NOT IN expressions"); - } else { - if (!canHandleType (leftargtype)) { - db2Debug2(" cannot Handle Type leftargtype (%d)", leftargtype); - } else { - char* left = NULL; - char* right = NULL; - - left = deparseExpr (ctx->root, ctx->foreignrel,linitial (expr->args), ctx->params_list); - // check if anything has been added beyond the initial "(" - if (left != NULL) { - Expr* rightexpr = NULL; - bool bResult = true; - - /* the second (=last) argument can be Const, ArrayExpr or ArrayCoerceExpr */ - rightexpr = (Expr*)llast(expr->args); - switch (rightexpr->type) { - case T_Const: { - StringInfoData buf; - /* the second (=last) argument is a Const of ArrayType */ - Const* constant = (Const*) rightexpr; - /* using NULL in place of an array or value list is valid in DB2 and PostgreSQL */ - initStringInfo(&buf); - if (constant->constisnull) { - appendStringInfo(&buf, "NULL"); - right = db2strdup(buf.data); - } else { - Datum datum; - bool isNull; - ArrayIterator iterator = array_create_iterator (DatumGetArrayTypeP (constant->constvalue), 0); - bool first_arg = true; - - /* loop through the array elements */ - while (array_iterate (iterator, &datum, &isNull)) { - char *c; - if (isNull) { - c = "NULL"; - } else { - c = datumToString (datum, leftargtype); - db2Debug2(" c: %s",c); - if (c == NULL) { - array_free_iterator (iterator); - bResult = false; - break; - } - } - /* append the argument */ - appendStringInfo (&buf, "%s%s", first_arg ? "" : ", ", c); - first_arg = false; - } - array_free_iterator (iterator); - db2Debug2(" first_arg: %s", first_arg ? "true":"false"); - if (first_arg) { - // don't push down empty arrays - // since the semantics for NOT x = ANY() differ - bResult = false; - } - if (bResult) { - right = db2strdup(buf.data); - } - } - db2free(buf.data); - } - break; - case T_ArrayCoerceExpr: { - /* the second (=last) argument is an ArrayCoerceExpr */ - ArrayCoerceExpr* arraycoerce = (ArrayCoerceExpr *) rightexpr; - /* if the conversion requires more than binary coercion, don't push it down */ - if (arraycoerce->elemexpr && arraycoerce->elemexpr->type != T_RelabelType) { - db2Debug2(" arraycoerce->elemexpr && arraycoerce->elemexpr->type != T_RelabelType"); - bResult = false; - break; - } - /* the actual array is here */ - rightexpr = arraycoerce->arg; - } - /* fall through ! */ - case T_ArrayExpr: { - /* the second (=last) argument is an ArrayExpr */ - StringInfoData buf; - char* element = NULL; - ArrayExpr* array = (ArrayExpr*) rightexpr; - ListCell* cell = NULL; - bool first_arg = true; - - initStringInfo(&buf); - /* loop the array arguments */ - foreach (cell, array->elements) { - element = deparseExpr (ctx->root, ctx->foreignrel, (Expr*) lfirst (cell), ctx->params_list); - if (element == NULL) { - /* if any element cannot be converted, give up */ - db2free(buf.data); - bResult = false; - break; - } - appendStringInfo(&buf,"%s%s",(first_arg) ? "": ", ",element); - first_arg = false; - } - db2Debug2(" first_arg: %s", first_arg ? "true" : "false"); - if (first_arg) { - /* don't push down empty arrays, since the semantics for NOT x = ANY() differ */ - db2free(buf.data); - bResult = false; - break; - } - right = (bResult) ? db2strdup(buf.data) : NULL; - db2free(buf.data); - } - break; - default: { - db2Debug2(" rightexpr->type(%d) default ",rightexpr->type); - bResult = false; - } - break; - } - // only when there is a usable result otherwise keep value to null - if (bResult) { - appendStringInfo (ctx->buf, "(%s %s IN (%s))",left, expr->useOr ? "" : "NOT", right); - } - db2free(left); - db2free(right); - } - } - } - } - db2Debug1("< %s::deparseScalarArrayOpExpr", __FILE__); -} - -static void deparseDistinctExpr (DistinctExpr* expr, deparse_expr_cxt* ctx) { - Oid rightargtype = 0; - HeapTuple tuple; - - db2Debug1("> %s::deparseDistinctExpr", __FILE__); - tuple = SearchSysCache1 (OPEROID, ObjectIdGetDatum ((expr)->opno)); - if (!HeapTupleIsValid (tuple)) { - elog (ERROR, "cache lookup failed for operator %u", (expr)->opno); - } - rightargtype = ((Form_pg_operator) GETSTRUCT (tuple))->oprright; - ReleaseSysCache (tuple); - if (!canHandleType (rightargtype)) { - db2Debug2(" cannot Handle Type rightargtype (%d)",rightargtype); - } else { - char* left = NULL; - - left = deparseExpr (ctx->root, ctx->foreignrel, linitial ((expr)->args), ctx->params_list); - if (left != NULL) { - char* right = NULL; - - right = deparseExpr (ctx->root, ctx->foreignrel, lsecond ((expr)->args), ctx->params_list); - if (right != NULL) { - appendStringInfo (ctx->buf, "( %s IS DISTINCT FROM %s)", left, right); - } - db2free(right); - } - db2free(left); - } - db2Debug1("< %s::deparseDistinctExpr", __FILE__); -} - -static void deparseNullIfExpr (NullIfExpr* expr, deparse_expr_cxt* ctx) { - Oid rightargtype = 0; - HeapTuple tuple; - - db2Debug1("> %s::deparseNullIfExpr", __FILE__); - tuple = SearchSysCache1 (OPEROID, ObjectIdGetDatum ((expr)->opno)); - if (!HeapTupleIsValid (tuple)) { - elog (ERROR, "cache lookup failed for operator %u", (expr)->opno); - } - rightargtype = ((Form_pg_operator) GETSTRUCT (tuple))->oprright; - ReleaseSysCache (tuple); - if (!canHandleType (rightargtype)) { - db2Debug2(" cannot Handle Type rightargtype (%d)",rightargtype); - } else { - char* left = NULL; - left = deparseExpr (ctx->root, ctx->foreignrel, linitial((expr)->args), ctx->params_list); - - if (left != NULL) { - char* right = NULL; - - right = deparseExpr (ctx->root, ctx->foreignrel, lsecond((expr)->args), ctx->params_list); - if (right != NULL) { - appendStringInfo (ctx->buf, "NULLIF(%s,%s)", left, right); - } - db2free(right); - } - db2free(left); - } - db2Debug1("< %s::deparseNullIfExpr: %s", __FILE__, ctx->buf->data); -} - -static void deparseBoolExpr (BoolExpr* expr, deparse_expr_cxt* ctx) { - ListCell* cell = NULL; - char* arg = NULL; - StringInfoData buf; - - db2Debug1("> %s::deparseBoolExpr", __FILE__); - initStringInfo(&buf); - arg = deparseExpr (ctx->root, ctx->foreignrel, linitial(expr->args), ctx->params_list); - if (arg != NULL) { - bool bBreak = false; - appendStringInfo (&buf, "(%s%s", expr->boolop == NOT_EXPR ? "NOT " : "", arg); - do_each_cell(cell, expr->args, list_next(expr->args, list_head(expr->args))) { - db2free(arg); - arg = deparseExpr (ctx->root, ctx->foreignrel, (Expr*)lfirst(cell), ctx->params_list); - if (arg != NULL) { - appendStringInfo (&buf, " %s %s", expr->boolop == AND_EXPR ? "AND":"OR", arg); - } else { - bBreak = true; - break; - } - } - if (!bBreak) { - appendStringInfo (ctx->buf, "%s)", buf.data); - } - } - db2free(buf.data); - db2free(arg); - db2Debug1("< %s::deparseBoolExpr: %s", __FILE__, ctx->buf->data); -} - -static void deparseCaseExpr (CaseExpr* expr, deparse_expr_cxt* ctx) { - db2Debug1("> %s::deparseCaseExpr", __FILE__); - if (!canHandleType (expr->casetype)) { - db2Debug2(" cannot Handle Type caseexpr->casetype (%d)", expr->casetype); - } else { - StringInfoData buf; - bool bBreak = false; - char* arg = NULL; - ListCell* cell = NULL; - - initStringInfo (&buf); - appendStringInfo (&buf, "CASE"); - - if (expr->arg != NULL) { - /* for the form "CASE arg WHEN ...", add first expression */ - arg = deparseExpr (ctx->root, ctx->foreignrel, expr->arg, ctx->params_list); - db2Debug2(" CASE %s WHEN ...", arg); - if (arg == NULL) { - appendStringInfo (&buf, " %s", arg); - } else { - bBreak = true; - } - } - if (!bBreak) { - /* append WHEN ... THEN clauses */ - foreach (cell, expr->args) { - CaseWhen* whenclause = (CaseWhen*) lfirst (cell); - /* WHEN */ - if (expr->arg == NULL) { - /* for CASE WHEN ..., use the whole expression */ - arg = deparseExpr (ctx->root, ctx->foreignrel, whenclause->expr, ctx->params_list); - } else { - /* for CASE arg WHEN ..., use only the right branch of the equality */ - arg = deparseExpr (ctx->root, ctx->foreignrel, lsecond (((OpExpr*) whenclause->expr)->args), ctx->params_list); - } - db2Debug2(" WHEN %s ", arg); - if (arg != NULL) { - appendStringInfo (&buf, " WHEN %s", arg); - } else { - bBreak = true; - break; - } /* THEN */ - arg = deparseExpr (ctx->root, ctx->foreignrel, whenclause->result, ctx->params_list); - db2Debug2(" THEN %s ", arg); - if (arg != NULL) { - appendStringInfo (&buf, " THEN %s", arg); - } else { - bBreak = true; - break; - } - } - if (!bBreak) { - /* append ELSE clause if appropriate */ - if (expr->defresult != NULL) { - arg = deparseExpr (ctx->root, ctx->foreignrel, expr->defresult, ctx->params_list); - db2Debug2(" ELSE %s", arg); - if (arg != NULL) { - appendStringInfo (&buf, " ELSE %s", arg); - } else { - bBreak = true; - } - } - /* append END */ - appendStringInfo (&buf, " END"); - } - } - if (!bBreak) { - appendStringInfo(ctx->buf,"%s",buf.data); - } - db2free(buf.data); - } - db2Debug1("< %s::deparseCaseExpr: %s", __FILE__, ctx->buf->data); -} - -static void deparseCoalesceExpr (CoalesceExpr* expr, deparse_expr_cxt* ctx) { - db2Debug1("> %s::deparseCoalesceExpr", __FILE__); - if (!canHandleType (expr->coalescetype)) { - db2Debug2(" cannot Handle Type coalesceexpr->coalescetype (%d)", expr->coalescetype); - } else { - StringInfoData result; - char* arg = NULL; - bool first_arg = true; - ListCell* cell = NULL; - - initStringInfo (&result); - appendStringInfo (&result, "COALESCE("); - foreach (cell, expr->args) { - arg = deparseExpr (ctx->root, ctx->foreignrel, (Expr*)lfirst(cell),ctx->params_list); - db2Debug2(" arg: %s", arg); - if (arg != NULL) { - appendStringInfo(&result, ((first_arg) ? "%s" : ", %s"), arg); - first_arg = false; - } else { - break; - } - } - if (arg != NULL) { - appendStringInfo (ctx->buf, "%s)",result.data); - } - db2free(result.data); - } - db2Debug1("< %s::deparseCoalesceExpr: %s", __FILE__, ctx->buf->data); -} - -static void deparseFuncExpr (FuncExpr* expr, deparse_expr_cxt* ctx) { - db2Debug1("> %s::deparseFuncExpr", __FILE__); - if (!canHandleType (expr->funcresulttype)) { - db2Debug2(" cannot handle funct->funcresulttype: %d",expr->funcresulttype); - } else if (expr->funcformat == COERCE_IMPLICIT_CAST) { - /* do nothing for implicit casts */ - db2Debug2(" COERCE_IMPLICIT_CAST == expr->funcformat(%d)",expr->funcformat); - deparseExprInt (linitial(expr->args), ctx); - } else { - Oid schema; - char* opername; - HeapTuple tuple; - - /* get function name and schema */ - tuple = SearchSysCache1 (PROCOID, ObjectIdGetDatum (expr->funcid)); - if (!HeapTupleIsValid (tuple)) { - elog (ERROR, "cache lookup failed for function %u", expr->funcid); - } - opername = db2strdup (((Form_pg_proc) GETSTRUCT (tuple))->proname.data); - db2Debug2(" opername: %s",opername); - schema = ((Form_pg_proc) GETSTRUCT (tuple))->pronamespace; - db2Debug2(" schema: %d",schema); - ReleaseSysCache (tuple); - /* ignore functions in other than the pg_catalog schema */ - if (schema != PG_CATALOG_NAMESPACE) { - db2Debug2(" T_FuncExpr: schema(%d) != PG_CATALOG_NAMESPACE", schema); - } else { - /* the "normal" functions that we can translate */ - if (strcmp (opername, "abs") == 0 || strcmp (opername, "acos") == 0 || strcmp (opername, "asin") == 0 - || strcmp (opername, "atan") == 0 || strcmp (opername, "atan2") == 0 || strcmp (opername, "ceil") == 0 - || strcmp (opername, "ceiling") == 0 || strcmp (opername, "char_length") == 0 || strcmp (opername, "character_length") == 0 - || strcmp (opername, "concat") == 0 || strcmp (opername, "cos") == 0 || strcmp (opername, "exp") == 0 - || strcmp (opername, "initcap") == 0 || strcmp (opername, "length") == 0 || strcmp (opername, "lower") == 0 - || strcmp (opername, "lpad") == 0 || strcmp (opername, "ltrim") == 0 || strcmp (opername, "mod") == 0 - || strcmp (opername, "octet_length") == 0 || strcmp (opername, "position") == 0 || strcmp (opername, "pow") == 0 - || strcmp (opername, "power") == 0 || strcmp (opername, "replace") == 0 || strcmp (opername, "round") == 0 - || strcmp (opername, "rpad") == 0 || strcmp (opername, "rtrim") == 0 || strcmp (opername, "sign") == 0 - || strcmp (opername, "sin") == 0 || strcmp (opername, "sqrt") == 0 || strcmp (opername, "strpos") == 0 - || strcmp (opername, "substr") == 0 || strcmp (opername, "tan") == 0 || strcmp (opername, "to_char") == 0 - || strcmp (opername, "to_date") == 0 || strcmp (opername, "to_number") == 0 || strcmp (opername, "to_timestamp") == 0 - || strcmp (opername, "translate") == 0 || strcmp (opername, "trunc") == 0 || strcmp (opername, "upper") == 0 - || (strcmp (opername, "substring") == 0 && list_length (expr->args) == 3)) { - ListCell* cell; - char* arg = NULL; - bool ok = true; - bool first_arg = true; - StringInfoData buf; - - initStringInfo (&buf); - if (strcmp (opername, "ceiling") == 0) - appendStringInfo (&buf, "CEIL("); - else if (strcmp (opername, "char_length") == 0 || strcmp (opername, "character_length") == 0) - appendStringInfo (&buf, "LENGTH("); - else if (strcmp (opername, "pow") == 0) - appendStringInfo (&buf, "POWER("); - else if (strcmp (opername, "octet_length") == 0) - appendStringInfo (&buf, "LENGTHB("); - else if (strcmp (opername, "position") == 0 || strcmp (opername, "strpos") == 0) - appendStringInfo (&buf, "INSTR("); - else if (strcmp (opername, "substring") == 0) - appendStringInfo (&buf, "SUBSTR("); - else - appendStringInfo (&buf, "%s(", opername); - foreach (cell, expr->args) { - arg = deparseExpr (ctx->root, ctx->foreignrel, lfirst (cell), ctx->params_list); - if (arg != NULL) { - appendStringInfo (&buf, "%s%s", (first_arg) ? ", " : "",arg); - first_arg = false; - db2free(arg); - } else { - ok = false; - db2Debug2(" T_FuncExpr: function %s that we cannot render for DB2", opername); - break; - } - } - appendStringInfo (&buf, ")"); - // copy to return value when successful - if (ok) { - appendStringInfo(ctx->buf,"%s",buf.data); - } - db2free(buf.data); - } else if (strcmp (opername, "date_part") == 0) { - char* left = NULL; - - /* special case: EXTRACT */ - left = deparseExpr (ctx->root, ctx->foreignrel, linitial (expr->args), ctx->params_list); - if (left == NULL) { - db2Debug2(" T_FuncExpr: function %s that we cannot render for DB2", opername); - } else { - /* can only handle these fields in DB2 */ - if (strcmp (left, "'year'") == 0 || strcmp (left, "'month'") == 0 - || strcmp (left, "'day'") == 0 || strcmp (left, "'hour'") == 0 - || strcmp (left, "'minute'") == 0 || strcmp (left, "'second'") == 0 - || strcmp (left, "'timezone_hour'") == 0 || strcmp (left, "'timezone_minute'") == 0) { - char* right = NULL; - - /* remove final quote */ - left[strlen (left) - 1] = '\0'; - right = deparseExpr (ctx->root, ctx->foreignrel, lsecond (expr->args), ctx->params_list); - if (right == NULL) { - db2Debug2(" T_FuncExpr: function %s that we cannot render for DB2", opername); - } else { - appendStringInfo (ctx->buf, "EXTRACT(%s FROM %s)", left + 1, right); - } - db2free(right); - } else { - db2Debug2(" T_FuncExpr: function %s that we cannot render for DB2", opername); - } - } - db2free (left); - } else if (strcmp (opername, "now") == 0 || strcmp (opername, "transaction_timestamp") == 0) { - /* special case: current timestamp */ - appendStringInfo (ctx->buf, "(CAST (?/*:now*/ AS TIMESTAMP))"); - } else { - /* function that we cannot render for DB2 */ - db2Debug2(" T_FuncExpr: function %s that we cannot render for DB2", opername); - } - } - db2free (opername); - } - db2Debug1("< %s::deparseFuncExpr: %s", __FILE__, ctx->buf->data); -} - -static void deparseCoerceViaIOExpr (CoerceViaIO* expr, deparse_expr_cxt* ctx) { - db2Debug1("> %s::deparseCoerceViaIOExpr", __FILE__); - /* We will only handle casts of 'now'. */ - /* only casts to these types are handled */ - if (expr->resulttype != DATEOID && expr->resulttype != TIMESTAMPOID && expr->resulttype != TIMESTAMPTZOID) { - db2Debug2(" only casts to DATEOID, TIMESTAMPOID and TIMESTAMPTZOID are handled"); - } else if (expr->arg->type != T_Const) { - /* the argument must be a Const */ - db2Debug2(" T_CoerceViaIO: the argument must be a Const"); - } else { - Const* constant = (Const *) expr->arg; - if (constant->constisnull || (constant->consttype != CSTRINGOID && constant->consttype != TEXTOID)) { - /* the argument must be a not-NULL text constant */ - db2Debug2(" T_CoerceViaIO: the argument must be a not-NULL text constant"); - } else { - /* get the type's output function */ - HeapTuple tuple = SearchSysCache1 (TYPEOID, ObjectIdGetDatum (constant->consttype)); - regproc typoutput; - if (!HeapTupleIsValid (tuple)) { - elog (ERROR, "cache lookup failed for type %u", constant->consttype); - } - typoutput = ((Form_pg_type) GETSTRUCT (tuple))->typoutput; - ReleaseSysCache (tuple); - /* the value must be "now" */ - if (strcmp (DatumGetCString (OidFunctionCall1 (typoutput, constant->constvalue)), "now") != 0) { - db2Debug2(" value must be 'now'"); - } else { - switch (expr->resulttype) { - case DATEOID: - appendStringInfo(ctx->buf, "TRUNC(CAST (CAST(?/*:now*/ AS TIMESTAMP) AS DATE))"); - break; - case TIMESTAMPOID: - appendStringInfo(ctx->buf, "(CAST (CAST (?/*:now*/ AS TIMESTAMP) AS TIMESTAMP))"); - break; - case TIMESTAMPTZOID: - appendStringInfo(ctx->buf, "(CAST (?/*:now*/ AS TIMESTAMP))"); - break; - case TIMEOID: - appendStringInfo(ctx->buf, "(CAST (CAST (?/*:now*/ AS TIME) AS TIME))"); - break; - case TIMETZOID: - appendStringInfo(ctx->buf, "(CAST (?/*:now*/ AS TIME))"); - break; - } - } - } - } - db2Debug1("< %s::deparseCoerceViaIOExpr: %s", __FILE__, ctx->buf->data); -} - -static void deparseSQLValueFuncExpr (SQLValueFunction* expr, deparse_expr_cxt* ctx) { - db2Debug1("> %s::deparseSQLValueFuncExpr", __FILE__); - switch (expr->op) { - case SVFOP_CURRENT_DATE: - appendStringInfo(ctx->buf, "TRUNC(CAST (CAST(?/*:now*/ AS TIMESTAMP) AS DATE))"); - break; - case SVFOP_CURRENT_TIMESTAMP: - appendStringInfo(ctx->buf, "(CAST (?/*:now*/ AS TIMESTAMP))"); - break; - case SVFOP_LOCALTIMESTAMP: - appendStringInfo(ctx->buf, "(CAST (CAST (?/*:now*/ AS TIMESTAMP) AS TIMESTAMP))"); - break; - case SVFOP_CURRENT_TIME: - appendStringInfo(ctx->buf, "(CAST (?/*:now*/ AS TIME))"); - break; - case SVFOP_LOCALTIME: - appendStringInfo(ctx->buf, "(CAST (CAST (?/*:now*/ AS TIME) AS TIME))"); - break; - default: - /* don't push down other functions */ - db2Debug2(" op %d cannot be translated to DB2", expr->op); - break; - } - db2Debug1("< %s::deparseSQLValueFuncExpr: %s", __FILE__, ctx->buf->data); -} - -static void deparseAggref (Aggref* expr, deparse_expr_cxt* ctx) { - db2Debug1("> %s::deparseAggref", __FILE__); - if (expr == NULL) { - db2Debug2(" expr is NULL"); - } else { - /* Resolve aggregate function name (OID -> pg_proc.proname). */ - HeapTuple tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(expr->aggfnoid)); - char* aggname = NULL; - char* nspname = NULL; - if (!HeapTupleIsValid(tuple)) { - elog(ERROR, "cache lookup failed for function %u", expr->aggfnoid); - } else { - Form_pg_proc procform = (Form_pg_proc) GETSTRUCT(tuple); - aggname = pstrdup(NameStr(procform->proname)); - /* Optional: capture schema for debugging/qualification decisions. */ - if (OidIsValid(procform->pronamespace)) { - HeapTuple ntup = SearchSysCache1(NAMESPACEOID, ObjectIdGetDatum(procform->pronamespace)); - if (HeapTupleIsValid(ntup)) { - Form_pg_namespace nspform = (Form_pg_namespace) GETSTRUCT(ntup); - nspname = pstrdup(NameStr(nspform->nspname)); - ReleaseSysCache(ntup); - } - } - ReleaseSysCache(tuple); - } - db2Debug2( " aggref->aggfnoid=%u name=%s%s%s" - , expr->aggfnoid - , nspname ? nspname : "" - , nspname ? "." : "" - , aggname ? aggname : "" - ); - /* We only support deparsing simple, standard aggregates for now. - * (This can be expanded to ordered-set / FILTER / WITHIN GROUP later.) - */ - if (expr->aggorder != NIL) { - db2Debug2(" aggregate ORDER BY not supported for pushdown"); - } else if (aggname != NULL) { - const char* db2func = NULL; - bool distinct = (expr->aggdistinct != NIL); - bool ok = true; - - if (strcmp(aggname, "count") == 0) db2func = "COUNT"; - else if (strcmp(aggname, "sum") == 0) db2func = "SUM"; - else if (strcmp(aggname, "avg") == 0) db2func = "AVG"; - else if (strcmp(aggname, "min") == 0) db2func = "MIN"; - else if (strcmp(aggname, "max") == 0) db2func = "MAX"; - else { - /* Unknown aggregate name: we can still report it (above), but don't emit SQL. */ - db2Debug2(" aggregate '%s' not supported for DB2 deparse", aggname); - } - if (db2func != NULL) { - StringInfoData result; - - initStringInfo(&result); - appendStringInfo(&result, "%s(", db2func); - if (distinct) { - appendStringInfoString(&result, "DISTINCT "); - } - if (expr->aggstar) { - /* COUNT(*) */ - appendStringInfoString(&result, "*"); - } else { - ListCell* lc; - char* arg = NULL; - bool first_arg = true; - - foreach (lc, expr->args) { - Node* argnode = (Node*) lfirst(lc); - Expr* argexpr = NULL; - - if (argnode == NULL) { - ok = false; - break; - } - if (argnode->type == T_TargetEntry) { - argexpr = ((TargetEntry*) argnode)->expr; - } else { - argexpr = (Expr*) argnode; - } - arg = deparseExpr(ctx->root, ctx->foreignrel, argexpr, ctx->params_list); - if (arg == NULL) { - ok = false; - break; - } - appendStringInfo(&result, "%s%s", arg, first_arg ? "" : ", "); - first_arg = false; - } - } - if (ok) { - appendStringInfo(ctx->buf, "%s)",result.data); - } else { - db2Debug2(" parsed aggref so far: %s", result.data); - db2Debug2(" could not deparse aggregate args"); - } - db2free(result.data); - } - } - } - db2Debug1("< %s::deparseAggref: %s", __FILE__, ctx->buf->data); -} - -/** datumToString - * Convert a Datum to a string by calling the type output function. - * Returns the result or NULL if it cannot be converted to DB2 SQL. - */ -char* datumToString (Datum datum, Oid type) { - StringInfoData result; - regproc typoutput; - HeapTuple tuple; - char* str; - char* p; - db2Debug1("> %s::datumToString", __FILE__); - /* get the type's output function */ - tuple = SearchSysCache1 (TYPEOID, ObjectIdGetDatum (type)); - if (!HeapTupleIsValid (tuple)) { - elog (ERROR, "cache lookup failed for type %u", type); - } - typoutput = ((Form_pg_type) GETSTRUCT (tuple))->typoutput; - ReleaseSysCache (tuple); - - /* render the constant in DB2 SQL */ - switch (type) { - case TEXTOID: - case CHAROID: - case BPCHAROID: - case VARCHAROID: - case NAMEOID: - str = DatumGetCString (OidFunctionCall1 (typoutput, datum)); - /* - * Don't try to convert empty strings to DB2. - * DB2 treats empty strings as NULL. - */ - if (str[0] == '\0') - return NULL; - - /* quote string */ - initStringInfo (&result); - appendStringInfo (&result, "'"); - for (p = str; *p; ++p) { - if (*p == '\'') - appendStringInfo (&result, "'"); - appendStringInfo (&result, "%c", *p); - } - appendStringInfo (&result, "'"); - break; - case INT8OID: - case INT2OID: - case INT4OID: - case OIDOID: - case FLOAT4OID: - case FLOAT8OID: - case NUMERICOID: - str = DatumGetCString (OidFunctionCall1 (typoutput, datum)); - initStringInfo (&result); - appendStringInfo (&result, "%s", str); - break; - case DATEOID: - str = deparseDate (datum); - initStringInfo (&result); - appendStringInfo (&result, "(CAST ('%s' AS DATE))", str); - break; - case TIMESTAMPOID: - str = deparseTimestamp (datum, false); - initStringInfo (&result); - appendStringInfo (&result, "(CAST ('%s' AS TIMESTAMP))", str); - break; - case TIMESTAMPTZOID: - str = deparseTimestamp (datum, false); - initStringInfo (&result); - appendStringInfo (&result, "(CAST ('%s' AS TIMESTAMP))", str); - break; - case TIMEOID: - str = deparseTimestamp (datum, false); - initStringInfo (&result); - appendStringInfo (&result, "(CAST ('%s' AS TIME))", str); - break; - case TIMETZOID: - str = deparseTimestamp (datum, false); - initStringInfo (&result); - appendStringInfo (&result, "(CAST ('%s' AS TIME))", str); - break; - case INTERVALOID: - str = deparseInterval (datum); - if (str == NULL) - return NULL; - initStringInfo (&result); - appendStringInfo (&result, "%s", str); - break; - default: - return NULL; - } - db2Debug1("< %s::datumToString - returns: '%s'", __FILE__, result.data); - return result.data; -} +static void errorContextCallback (void* arg); /** guessNlsLang * If nls_lang is not NULL, return "NLS_LANG=". @@ -1421,146 +140,13 @@ char* guessNlsLang (char *nls_lang) { return buf.data; } -/** deparseDate - * Render a PostgreSQL date so that DB2 can parse it. - */ -char* deparseDate (Datum datum) { - struct pg_tm datetime_tm; - StringInfoData s; - db2Debug1("> %s::deparseDate", __FILE__); - if (DATE_NOT_FINITE (DatumGetDateADT (datum))) - ereport (ERROR, (errcode (ERRCODE_FDW_INVALID_ATTRIBUTE_VALUE), errmsg ("infinite date value cannot be stored in DB2"))); - - /* get the parts */ - (void) j2date (DatumGetDateADT (datum) + POSTGRES_EPOCH_JDATE, &(datetime_tm.tm_year), &(datetime_tm.tm_mon), &(datetime_tm.tm_mday)); - - if (datetime_tm.tm_year < 0) - ereport (ERROR, (errcode (ERRCODE_FDW_INVALID_ATTRIBUTE_VALUE), errmsg ("BC date value cannot be stored in DB2"))); - - initStringInfo (&s); - appendStringInfo (&s, "%04d-%02d-%02d 00:00:00", datetime_tm.tm_year > 0 ? datetime_tm.tm_year : -datetime_tm.tm_year + 1, datetime_tm.tm_mon, datetime_tm.tm_mday); - db2Debug1("< %s::deparseDate - returns: '%s'", __FILE__, s.data); - return s.data; -} - -/** deparseTimestamp - * Render a PostgreSQL timestamp so that DB2 can parse it. - */ -char* deparseTimestamp (Datum datum, bool hasTimezone) { - struct pg_tm datetime_tm; - int32 tzoffset; - fsec_t datetime_fsec; - StringInfoData s; - db2Debug1("> %s::deparseTimestamp",__FILE__); - /* this is sloppy, but DatumGetTimestampTz and DatumGetTimestamp are the same */ - if (TIMESTAMP_NOT_FINITE (DatumGetTimestampTz (datum))) - ereport (ERROR, (errcode (ERRCODE_FDW_INVALID_ATTRIBUTE_VALUE), errmsg ("infinite timestamp value cannot be stored in DB2"))); - - /* get the parts */ - tzoffset = 0; - (void) timestamp2tm (DatumGetTimestampTz (datum), hasTimezone ? &tzoffset : NULL, &datetime_tm, &datetime_fsec, NULL, NULL); - - if (datetime_tm.tm_year < 0) - ereport (ERROR, (errcode (ERRCODE_FDW_INVALID_ATTRIBUTE_VALUE), errmsg ("BC date value cannot be stored in DB2"))); - - initStringInfo (&s); - if (hasTimezone) - appendStringInfo (&s, "%04d-%02d-%02d %02d:%02d:%02d.%06d%+03d:%02d", - datetime_tm.tm_year > 0 ? datetime_tm.tm_year : -datetime_tm.tm_year + 1, - datetime_tm.tm_mon, datetime_tm.tm_mday, datetime_tm.tm_hour, - datetime_tm.tm_min, datetime_tm.tm_sec, (int32) datetime_fsec, - -tzoffset / 3600, ((tzoffset > 0) ? tzoffset % 3600 : -tzoffset % 3600) / 60); - else - appendStringInfo (&s, "%04d-%02d-%02d %02d:%02d:%02d.%06d", - datetime_tm.tm_year > 0 ? datetime_tm.tm_year : -datetime_tm.tm_year + 1, - datetime_tm.tm_mon, datetime_tm.tm_mday, datetime_tm.tm_hour, - datetime_tm.tm_min, datetime_tm.tm_sec, (int32) datetime_fsec); - db2Debug1("< %s::deparseTimestamp - returns: '%s'", __FILE__, s.data); - return s.data; -} - -/** deparsedeparseInterval - * Render a PostgreSQL timestamp so that DB2 can parse it. +/** exitHook + * Close all DB2 connections on process exit. */ -char* deparseInterval (Datum datum) { - #if PG_VERSION_NUM >= 150000 - struct pg_itm tm; - #else - struct pg_tm tm; - #endif - fsec_t fsec=0; - StringInfoData s; - char* sign; - int idx = 0; - - db2Debug1("> %s::deparseInterval",__FILE__); - #if PG_VERSION_NUM >= 150000 - interval2itm (*DatumGetIntervalP (datum), &tm); - #else - if (interval2tm (*DatumGetIntervalP (datum), &tm, &fsec) != 0) { - elog (ERROR, "could not convert interval to tm"); - } - #endif - /* only translate intervals that can be translated to INTERVAL DAY TO SECOND */ -// if (tm.tm_year != 0 || tm.tm_mon != 0) -// return NULL; - - /* DB2 intervals have only one sign */ - if (tm.tm_mday < 0 || tm.tm_hour < 0 || tm.tm_min < 0 || tm.tm_sec < 0 || fsec < 0) { - sign = "-"; - /* all signs must match */ - if (tm.tm_mday > 0 || tm.tm_hour > 0 || tm.tm_min > 0 || tm.tm_sec > 0 || fsec > 0) - return NULL; - tm.tm_mday = -tm.tm_mday; - tm.tm_hour = -tm.tm_hour; - tm.tm_min = -tm.tm_min; - tm.tm_sec = -tm.tm_sec; - fsec = -fsec; - } else { - sign = "+"; - } - initStringInfo (&s); - if (tm.tm_year > 0) { - appendStringInfo(&s, ((tm.tm_year > 1) ? "%d YEARS" : "%d YEAR"),tm.tm_year); - } - idx += tm.tm_year; - if (tm.tm_mon > 0) { - appendStringInfo(&s," %s ",(idx > 0 ) ? sign : ""); - appendStringInfo(&s, ((tm.tm_mon > 1) ? "%d MONTHS" : "%d MONTH"),tm.tm_mon); - } - idx += tm.tm_mon; - if (tm.tm_mday > 0) { - appendStringInfo(&s," %s ",(idx > 0 ) ? sign : ""); - appendStringInfo(&s, ((tm.tm_mday > 1) ? "%d DAYS" : "%d DAY"),tm.tm_mday); - } - idx += tm.tm_mday; - if (tm.tm_hour > 0) { - appendStringInfo(&s," %s ",(idx > 0 ) ? sign : ""); - #if PG_VERSION_NUM >= 150000 - appendStringInfo(&s, ((tm.tm_hour > 1) ? "%ld HOURS" : "%ld HOUR"),tm.tm_hour); - #else - appendStringInfo(&s, ((tm.tm_hour > 1) ? "%d HOURS" : "%d HOUR"),tm.tm_hour); - #endif - } - idx += tm.tm_hour; - if (tm.tm_min > 0) { - appendStringInfo(&s," %s ",(idx > 0 ) ? sign : ""); - appendStringInfo(&s, ((tm.tm_min > 1) ? "%d MINUTES" : "%d MINUTE"),tm.tm_min); - } - idx += tm.tm_min; - if (tm.tm_sec > 0) { - appendStringInfo(&s," %s ",(idx > 0 ) ? sign : ""); - appendStringInfo(&s, ((tm.tm_sec > 1) ? "%d SECONDS" : "%d SECOND"),tm.tm_sec); - } - idx += tm.tm_sec; - -// #if PG_VERSION_NUM >= 150000 -// appendStringInfo (&s, "INTERVAL '%s%d %02ld:%02d:%02d.%06d' DAY TO SECOND", sign, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, fsec); -// #else -// appendStringInfo (&s, "INTERVAL '%s%d %02d:%02d:%02d.%06d' DAY(9) TO SECOND(6)", sign, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, fsec); -// #endif - db2Debug1("< %s::deparseInterval - returns: '%s'",__FILE__,s.data); - return s.data; +void exitHook (int code, Datum arg) { + db2Debug1("> %s::exitHook",__FILE__); + db2Shutdown (); + db2Debug1("< %s::exitHook",__FILE__); } /** convertTuple @@ -1746,7 +332,7 @@ void convertTuple (DB2FdwState* fdw_state, Datum* values, bool* nulls, bool trun * Provides the context for an error message during a type input conversion. * The argument must be a pointer to a DB2FdwState. */ -void errorContextCallback (void* arg) { +static void errorContextCallback (void* arg) { DB2FdwState *fdw_state = (DB2FdwState*) arg; db2Debug1("> %s::errorContextCallback",__FILE__); errcontext ( "converting column \"%s\" for foreign table scan of \"%s\", row %lu" @@ -1757,39 +343,3 @@ void errorContextCallback (void* arg) { db2Debug1("< %s::errorContextCallback",__FILE__); } -/** exitHook - * Close all DB2 connections on process exit. - */ -void exitHook (int code, Datum arg) { - db2Debug1("> %s::exitHook",__FILE__); - db2Shutdown (); - db2Debug1("< %s::exitHook",__FILE__); -} - -/** Output join name for given join type - */ -static const char* get_jointype_name (JoinType jointype) { - char* type = NULL; - db2Debug1("> get_jointype_name"); - switch (jointype) { - case JOIN_INNER: - type = "INNER"; - break; - case JOIN_LEFT: - type = "LEFT"; - break; - case JOIN_RIGHT: - type = "RIGHT"; - break; - case JOIN_FULL: - type= "FULL"; - break; - default: - /* Shouldn't come here, but protect from buggy code. */ - elog (ERROR, "unsupported join type %d", jointype); - break; - } - db2Debug2(" type: '%s'",type); - db2Debug1("< get_jointype_name"); - return type; -} From 0a6efdf759134dacb03342fb07d3119b66ff7606 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Wed, 21 Jan 2026 17:52:31 +0100 Subject: [PATCH 011/120] adding more parsing functions --- include/DB2FdwPathExtraData.h | 21 + include/db2_fdw.h | 3 + source/db2GetForeignPaths.c | 11 +- source/db2GetForeignUpperPaths.c | 757 ++++++++++++++++++++++---- source/db2_deparse.c | 895 ++++++++++++++++++++++++++++++- 5 files changed, 1574 insertions(+), 113 deletions(-) create mode 100644 include/DB2FdwPathExtraData.h diff --git a/include/DB2FdwPathExtraData.h b/include/DB2FdwPathExtraData.h new file mode 100644 index 0000000..329ca1b --- /dev/null +++ b/include/DB2FdwPathExtraData.h @@ -0,0 +1,21 @@ +#ifndef DB2FDWPATHEXTRADATA_H +#define DB2FDWPATHEXTRADATA_H + +#include + +/** Struct for extra information passed to estimate_path_cost_size() + * + * @author Thomas Muenz + * @since 18.1.1 + */ + +typedef struct DB2FdwPathExtraData { + PathTarget* target; + bool has_final_sort; + bool has_limit; + double limit_tuples; + int64 count_est; + int64 offset_est; +} DB2FdwPathExtraData; + +#endif \ No newline at end of file diff --git a/include/db2_fdw.h b/include/db2_fdw.h index dc6dc38..0027b39 100644 --- a/include/db2_fdw.h +++ b/include/db2_fdw.h @@ -167,6 +167,9 @@ typedef enum { typedef enum { CASE_KEEP, CASE_LOWER, CASE_SMART } fold_t; #define REL_ALIAS_PREFIX "r" +#define SUBQUERY_REL_ALIAS_PREFIX "s" +#define SUBQUERY_COL_ALIAS_PREFIX "c" + /* Handy macro to add relation name qualification */ #define ADD_REL_QUALIFIER(buf, varno) appendStringInfo((buf), "%s%d.", REL_ALIAS_PREFIX, (varno)) #define serializeInt(x) makeConst(INT4OID, -1, InvalidOid, 4, Int32GetDatum((int32)(x)), false, true) diff --git a/source/db2GetForeignPaths.c b/source/db2GetForeignPaths.c index 3b53688..82b5bf5 100644 --- a/source/db2GetForeignPaths.c +++ b/source/db2GetForeignPaths.c @@ -74,12 +74,11 @@ void db2GetForeignPaths(PlannerInfo* root, RelOptInfo* baserel, Oid foreigntable #endif appendStringInfoString (&orderedquery, (pathkey->pk_nulls_first) ? " NULLS FIRST" : " NULLS LAST"); } else { - /* - * The planner and executor don't have any clever strategy for - * taking data sorted by a prefix of the query's pathkeys and - * getting it to be sorted by all of those pathekeys. We'll just - * end up resorting the entire data set. So, unless we can push - * down all of the query pathkeys, forget it. + /** The planner and executor don't have any clever strategy for + * taking data sorted by a prefix of the query's pathkeys and + * getting it to be sorted by all of those pathekeys. We'll just + * end up resorting the entire data set. So, unless we can push + * down all of the query pathkeys, forget it. */ list_free (usable_pathkeys); usable_pathkeys = NIL; diff --git a/source/db2GetForeignUpperPaths.c b/source/db2GetForeignUpperPaths.c index e081f0e..2239732 100644 --- a/source/db2GetForeignUpperPaths.c +++ b/source/db2GetForeignUpperPaths.c @@ -1,12 +1,17 @@ #include #include +#include +#include +#include #include #include #include #include #include +#include #include "db2_fdw.h" #include "DB2FdwState.h" +#include "DB2FdwPathExtraData.h" /** external prototypes */ extern void db2Debug1 (const char* message, ...); @@ -16,20 +21,27 @@ extern void* db2alloc (const char* type, size_t size); extern char* db2strdup (const char* source); extern void* db2free (void* p); extern char* deparseExpr (PlannerInfo* root, RelOptInfo* foreignrel, Expr* expr, List** params); +extern bool is_shippable (Oid objectId, Oid classId, DB2FdwState* fpinfo); +extern bool is_foreign_param (PlannerInfo *root, RelOptInfo *baserel, Expr *expr); +extern bool is_foreign_expr (PlannerInfo *root, RelOptInfo *baserel, Expr *expr); +extern bool is_foreign_pathkey (PlannerInfo *root, RelOptInfo *baserel, PathKey *pathkey); /** local prototypes */ void db2GetForeignUpperPaths (PlannerInfo *root, UpperRelationKind stage, RelOptInfo *input_rel, RelOptInfo *output_rel, void *extra); -static DB2FdwState* db2CloneFdwStateUpper (PlannerInfo* root, const DB2FdwState* fdw_in, RelOptInfo* input_rel, RelOptInfo* output_rel); +static void db2CloneFdwStateUpper (PlannerInfo* root, RelOptInfo* input_rel, RelOptInfo* output_rel); static DB2Table* db2CloneDb2TableForPlan (const DB2Table* src); static DB2Column* db2CloneDb2ColumnForPlan (const DB2Column* src); -static bool db2_is_shippable (PlannerInfo* root, UpperRelationKind stage, RelOptInfo* input_rel, RelOptInfo* output_rel, const DB2FdwState* fdw_in); -static bool db2_is_shippable_expr (PlannerInfo* root, RelOptInfo* foreignrel, const DB2FdwState* fdw_in, Expr* expr, const char* label); +static bool db2_is_shippable (PlannerInfo* root, UpperRelationKind stage, RelOptInfo* input_rel, RelOptInfo* output_rel); +static bool db2_is_shippable_expr (PlannerInfo* root, RelOptInfo* foreignrel, Expr* expr, const char* label); +static void add_foreign_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel, RelOptInfo *grouped_rel, GroupPathExtraData *extra); +static void add_foreign_ordered_paths (PlannerInfo *root, RelOptInfo *input_rel, RelOptInfo *ordered_rel); +static void add_foreign_final_paths (PlannerInfo *root, RelOptInfo *input_rel, RelOptInfo *final_rel, FinalPathExtraData *extra); +static bool foreign_grouping_ok (PlannerInfo *root, RelOptInfo *grouped_rel, Node *havingQual); void db2GetForeignUpperPaths(PlannerInfo *root, UpperRelationKind stage, RelOptInfo *input_rel, RelOptInfo *output_rel, void *extra) { db2Debug1("> %s::db2GetForeignUpperPaths",__FILE__); - if (root != NULL && root->parse != NULL && output_rel->fdw_private == NULL) { - DB2FdwState* fdw_in = NULL; - Query* query = root->parse; + if (root != NULL && root->parse != NULL && input_rel->fdw_private != NULL && output_rel->fdw_private == NULL) { + Query* query = root->parse; db2Debug3(" query->hasAggs : %s", query->hasAggs ? "true" : "false"); db2Debug3(" query->hasWindowFuncs : %s", query->hasWindowFuncs ? "true" : "false"); db2Debug3(" query->hasDistinctOn : %s", query->hasDistinctOn ? "true" : "false"); @@ -42,104 +54,80 @@ void db2GetForeignUpperPaths(PlannerInfo *root, UpperRelationKind stage, RelOptI db2Debug3(" query->hasRecursive : %s", query->hasRecursive ? "true" : "false"); db2Debug3(" query->hasSubLinks : %s", query->hasSubLinks ? "true" : "false"); db2Debug3(" query->hasRowSecurity : %s", query->hasRowSecurity ? "true" : "false"); - switch (stage) { - case UPPERREL_SETOP: // UNION/INTERSECT/EXCEPT - db2Debug2(" stage: %d - UPPERREL_SETOP", stage); - break; - case UPPERREL_PARTIAL_GROUP_AGG: // partial grouping/aggregation - db2Debug2(" stage: %d - UPPERREL_PARTIAL_GROUP_AGG", stage); - db2Debug2(" query->hasAggs: %d", query->hasAggs); - db2Debug2(" query->groupClause: %x", query->groupClause); - if (query->hasAggs || query->groupClause != NIL) { - fdw_in = (DB2FdwState*)input_rel->fdw_private; - } - break; - case UPPERREL_GROUP_AGG: { // grouping/aggregation - db2Debug2(" stage: %d - UPPERREL_GROUP_AGG", stage); - db2Debug2(" query->hasAggs: %d", query->hasAggs); - db2Debug2(" query->groupClause: %x", query->groupClause); - if (query->hasAggs || query->groupClause != NIL) { - fdw_in = (DB2FdwState*)input_rel->fdw_private; - } - } - break; - case UPPERREL_WINDOW: { // window functions - db2Debug2(" stage: %d - UPPERREL_WINDOW", stage); - db2Debug2(" query->hasWindowFuncs: %d", query->hasWindowFuncs); - if (query->hasWindowFuncs) { - fdw_in = (DB2FdwState*)input_rel->fdw_private; + + if (db2_is_shippable(root, stage, input_rel, output_rel)) { + db2CloneFdwStateUpper(root, input_rel, output_rel); + + switch (stage) { + case UPPERREL_SETOP: // UNION/INTERSECT/EXCEPT + db2Debug2(" stage: %d - UPPERREL_SETOP", stage); + break; + case UPPERREL_PARTIAL_GROUP_AGG: // partial grouping/aggregation + db2Debug2(" stage: %d - UPPERREL_PARTIAL_GROUP_AGG", stage); + db2Debug2(" query->hasAggs: %d", query->hasAggs); + db2Debug2(" query->groupClause: %x", query->groupClause); + if (query->hasAggs || query->groupClause != NIL) { + add_foreign_grouping_paths(root, input_rel, output_rel, (GroupPathExtraData*) extra); + } + break; + case UPPERREL_GROUP_AGG: { // grouping/aggregation + db2Debug2(" stage: %d - UPPERREL_GROUP_AGG", stage); + db2Debug2(" query->hasAggs: %d", query->hasAggs); + db2Debug2(" query->groupClause: %x", query->groupClause); + if (query->hasAggs || query->groupClause != NIL) { + add_foreign_grouping_paths(root, input_rel, output_rel, (GroupPathExtraData*) extra); + } } - } - break; - #if PG_VERSION_NUM >= 150000 - case UPPERREL_PARTIAL_DISTINCT: { // partial "SELECT DISTINCT" - db2Debug2(" stage: %d - UPPERREL_PARTIAL_DISTINCT", stage); - db2Debug2(" query->hasDistinctOn: %d", query->hasDistinctOn); - if (query->hasDistinctOn) { - fdw_in = (DB2FdwState*)input_rel->fdw_private; + break; + case UPPERREL_WINDOW: { // window functions + db2Debug2(" stage: %d - UPPERREL_WINDOW", stage); + db2Debug2(" query->hasWindowFuncs: %d", query->hasWindowFuncs); + if (query->hasWindowFuncs) { + db2Debug2(" window function push down not yet implemented"); + } } - } - break; - #endif - case UPPERREL_DISTINCT: { // "SELECT DISTINCT" - db2Debug2(" stage: %d - UPPERREL_DISTINCT", stage); - db2Debug2(" query->hasDistinctOn: %d", query->hasDistinctOn); - if (query->hasDistinctOn) { - fdw_in = (DB2FdwState*)input_rel->fdw_private; + break; + #if PG_VERSION_NUM >= 150000 + case UPPERREL_PARTIAL_DISTINCT: { // partial "SELECT DISTINCT" + db2Debug2(" stage: %d - UPPERREL_PARTIAL_DISTINCT", stage); + db2Debug2(" query->hasDistinctOn: %d", query->hasDistinctOn); + if (query->hasDistinctOn) { + db2Debug2(" distinct function push down not yet implemented"); + } } - } - break; - case UPPERREL_ORDERED: // ORDER BY - db2Debug2(" stage: %d - UPPERREL_ORDERED", stage); - db2Debug2(" query->setOperations: %x", query->setOperations); - if (query->setOperations != NULL) { - fdw_in = (DB2FdwState*)input_rel->fdw_private; + break; + #endif + case UPPERREL_DISTINCT: { // "SELECT DISTINCT" + db2Debug2(" stage: %d - UPPERREL_DISTINCT", stage); + db2Debug2(" query->hasDistinctOn: %d", query->hasDistinctOn); + if (query->hasDistinctOn) { + db2Debug2(" distinct function push down not yet implemented"); + } } - break; - case UPPERREL_FINAL: // any remaining top-level actions - db2Debug2(" stage: %d - UPPERREL_FINAL", stage); - break; - default: // unknown stage type - db2Debug2(" stage: %d - unknown", stage); - break; - } - if (fdw_in != NULL) { - // verify all GROUP keys, Aggrefs, HAVING are deparsable for DB2 - if (db2_is_shippable(root, stage, input_rel, output_rel, fdw_in)) { - // create a new state for the upper rel (copy + mark as aggregate) - Path* path = NULL; - DB2FdwState* state = db2CloneFdwStateUpper(root, fdw_in, input_rel, output_rel); -// state->is_upper = true; -// state->upper_kind = stage; - // Estimate rows/cost (can be crude initially) - output_rel->rows = clamp_row_est(output_rel->rows); - state->startup_cost = 10000.0; - state->total_cost = state->startup_cost + output_rel->rows * 10.0; - path = (Path*) create_foreign_upper_path( root - , input_rel - , input_rel->reltarget /* pathtarget */ - , output_rel->rows -#if PG_VERSION_NUM >= 180000 - , 0 /* disabled nodes (PG18+) if needed */ -#endif - , state->startup_cost - , state->total_cost - , NIL - , NULL /* required_outer */ -#if PG_VERSION_NUM >= 170000 - , NIL /* fdw_outerpath */ -#endif - , (void*)state - ); -// add_path(input_rel, path); + break; + case UPPERREL_ORDERED: // ORDER BY + db2Debug2(" stage: %d - UPPERREL_ORDERED", stage); + db2Debug2(" query->setOperations: %x", query->setOperations); + if (query->setOperations != NULL) { + add_foreign_ordered_paths(root, input_rel, output_rel); + } + break; + case UPPERREL_FINAL: // any remaining top-level actions + db2Debug2(" stage: %d - UPPERREL_FINAL", stage); + add_foreign_final_paths(root, input_rel, output_rel, (FinalPathExtraData*) extra); + break; + default: // unknown stage type + db2Debug2(" stage: %d - unknown", stage); + break; } } else { - db2Debug2(" not pushable"); + db2Debug2(" stage and or functions are not shippable to DB2"); } } else { db2Debug2(" skipping this call"); db2Debug2(" root: %x", root); db2Debug2(" root->parse: %x", root->parse); + db2Debug2(" input_rel->fdw_private: %x", input_rel->fdw_private); db2Debug2(" output_rel->fdw_private: %x", output_rel->fdw_private); } db2Debug1("< %s::db2GetForeignUpperPaths",__FILE__); @@ -152,8 +140,9 @@ void db2GetForeignUpperPaths(PlannerInfo *root, UpperRelationKind stage, RelOptI * rewrite the params list (createQuery will NULL out entries). We must avoid * those mutations affecting the original baserel/joinrel planning state. */ -static DB2FdwState* db2CloneFdwStateUpper(PlannerInfo* root, const DB2FdwState* fdw_in, RelOptInfo* input_rel, RelOptInfo* output_rel) { - DB2FdwState* copy = NULL; +static void db2CloneFdwStateUpper(PlannerInfo* root, RelOptInfo* input_rel, RelOptInfo* output_rel) { + DB2FdwState* fdw_in = (DB2FdwState*)input_rel->fdw_private; + DB2FdwState* copy = NULL; db2Debug1("> %s::db2CloneFdwStateUpper", __FILE__); if (fdw_in != NULL) { @@ -201,8 +190,8 @@ static DB2FdwState* db2CloneFdwStateUpper(PlannerInfo* root, const DB2FdwState* /* paramList is constructed at execution time from fdw_exprs. */ copy->paramList = NULL; } - db2Debug1("< %s::db2CloneFdwStateUpper : %x", __FILE__, copy); - return copy; + output_rel->fdw_private = copy; + db2Debug1("< %s::db2CloneFdwStateUpper", __FILE__); } static DB2Table* db2CloneDb2TableForPlan(const DB2Table* src) { @@ -253,8 +242,9 @@ static DB2Column* db2CloneDb2ColumnForPlan(const DB2Column* src) { return dst; } -static bool db2_is_shippable(PlannerInfo* root, UpperRelationKind stage, RelOptInfo* input_rel, RelOptInfo* output_rel, const DB2FdwState* fdw_in) { - bool fResult = false; +static bool db2_is_shippable(PlannerInfo* root, UpperRelationKind stage, RelOptInfo* input_rel, RelOptInfo* output_rel) { + bool fResult = false; + DB2FdwState* fdw_in = (DB2FdwState*)input_rel->fdw_private; db2Debug1("> %s::db2_is_shippable", __FILE__); if (root == NULL || root->parse == NULL || input_rel == NULL || output_rel == NULL || fdw_in == NULL) { @@ -284,7 +274,7 @@ static bool db2_is_shippable(PlannerInfo* root, UpperRelationKind stage, RelOptI fResult = false; break; } - if (!db2_is_shippable_expr(root, input_rel, fdw_in, (Expr*) tle->expr, "GROUP BY")) { + if (!db2_is_shippable_expr(root, input_rel, (Expr*) tle->expr, "GROUP BY")) { fResult = false; break; } @@ -293,7 +283,7 @@ static bool db2_is_shippable(PlannerInfo* root, UpperRelationKind stage, RelOptI if (fResult) { /* 2) HAVING clause must be deparsable (if present). */ if (query->havingQual != NULL) { - if (!db2_is_shippable_expr(root, input_rel, fdw_in, (Expr*) query->havingQual, "HAVING")) { + if (!db2_is_shippable_expr(root, input_rel, (Expr*) query->havingQual, "HAVING")) { fResult = false; } } @@ -303,7 +293,7 @@ static bool db2_is_shippable(PlannerInfo* root, UpperRelationKind stage, RelOptI /* 3) Output target expressions must be deparsable too. */ foreach (lc, output_rel->reltarget->exprs) { Expr* expr = (Expr*) lfirst(lc); - if (!db2_is_shippable_expr(root, input_rel, fdw_in, expr, "SELECT")) { + if (!db2_is_shippable_expr(root, input_rel, expr, "SELECT")) { fResult = false; break; } @@ -323,8 +313,9 @@ static bool db2_is_shippable(PlannerInfo* root, UpperRelationKind stage, RelOptI return fResult; } -static bool db2_is_shippable_expr(PlannerInfo* root, RelOptInfo* foreignrel, const DB2FdwState* fdw_in, Expr* expr, const char* label) { - bool fResult = false; +static bool db2_is_shippable_expr(PlannerInfo* root, RelOptInfo* foreignrel, Expr* expr, const char* label) { + bool fResult = false; + DB2FdwState* fdw_in = (DB2FdwState*)foreignrel->fdw_private; db2Debug1("> %s::db2_is_shippable_expr", __FILE__); if (expr == NULL) { @@ -352,3 +343,561 @@ static bool db2_is_shippable_expr(PlannerInfo* root, RelOptInfo* foreignrel, con db2Debug1("> %s::db2_is_shippable_expr : %s", __FILE__, fResult ? "true" : "false"); return fResult; } + +/** add_foreign_grouping_paths + * Add foreign path for grouping and/or aggregation. + * Given input_rel represents the underlying scan. The paths are added to the given grouped_rel. + */ +static void add_foreign_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel, RelOptInfo *grouped_rel, GroupPathExtraData *extra) { + Query* parse = root->parse; + DB2FdwState* ifpinfo = (DB2FdwState*)input_rel->fdw_private; + DB2FdwState* fpinfo = (DB2FdwState*)grouped_rel->fdw_private; + ForeignPath* grouppath; + double rows; + int width; + int disabled_nodes; + Cost startup_cost; + Cost total_cost; + /* Nothing to be done, if there is no grouping or aggregation required. */ + if (!parse->groupClause && !parse->groupingSets && !parse->hasAggs && !root->hasHavingQual) + return; + + Assert(extra->patype == PARTITIONWISE_AGGREGATE_NONE || extra->patype == PARTITIONWISE_AGGREGATE_FULL); + /* save the input_rel as outerrel in fpinfo */ + fpinfo->outerrel = input_rel; + +// All of this is redundant since fpinfo is 1:1 clone of ifpinfo + // Copy foreign table, foreign server, user mapping, FDW options etc. details from the input relation's fpinfo. +// fpinfo->db2Table = db2CloneDb2TableForPlan(ifpinfo->db2Table); +// fpinfo->dbserver = db2strdup(ifpinfo->dbserver); +// fpinfo->user = db2strdup(ifpinfo->user); +// merge_fdw_options(fpinfo, ifpinfo, NULL); + + /** Assess if it is safe to push down aggregation and grouping. + * + * Use HAVING qual from extra. In case of child partition, it will have translated Vars. + */ + if (!foreign_grouping_ok(root, grouped_rel, extra->havingQual)) + return; + /** Compute the selectivity and cost of the local_conds, so we don't have + * to do it over again for each path. (Currently we create just a single + * path here, but in future it would be possible that we build more paths + * such as pre-sorted paths as in postgresGetForeignPaths and + * postgresGetForeignJoinPaths.) The best we can do for these conditions + * is to estimate selectivity on the basis of local statistics. + */ + fpinfo->local_conds_sel = clauselist_selectivity(root, fpinfo->local_conds, 0, JOIN_INNER, NULL); + cost_qual_eval(&fpinfo->local_conds_cost, fpinfo->local_conds, root); + + /* Estimate the cost of push down */ + estimate_path_cost_size(root, grouped_rel, NIL, NIL, NULL, &rows, &width, &disabled_nodes, &startup_cost, &total_cost); + /* Now update this information in the fpinfo */ + fpinfo->rows = rows; + fpinfo->width = width; + fpinfo->disabled_nodes = disabled_nodes; + fpinfo->startup_cost = startup_cost; + fpinfo->total_cost = total_cost; + /* Create and add foreign path to the grouping relation. */ + grouppath = create_foreign_upper_path( root + , grouped_rel + , grouped_rel->reltarget + , rows + , disabled_nodes + , startup_cost + , total_cost + , NIL /* no pathkeys */ + , NULL + , NIL /* no fdw_restrictinfo list */ + , NIL); /* no fdw_private */ + /* Add generated path into grouped_rel by add_path(). */ + add_path(grouped_rel, (Path*) grouppath); +} + +/** add_foreign_ordered_paths + * Add foreign paths for performing the final sort remotely. + * Given input_rel contains the source-data Paths. The paths are added to the given ordered_rel. + */ +static void add_foreign_ordered_paths(PlannerInfo *root, RelOptInfo *input_rel, RelOptInfo *ordered_rel) { + Query* parse = root->parse; + DB2FdwState* ifpinfo = (DB2FdwState*)input_rel->fdw_private; + DB2FdwState* fpinfo = (DB2FdwState*)ordered_rel->fdw_private; + DB2FdwPathExtraData* fpextra; + double rows; + int width; + int disabled_nodes; + Cost startup_cost; + Cost total_cost; + List* fdw_private; + ForeignPath* ordered_path; + ListCell* lc; + + // Shouldn't get here unless the query has ORDER BY + Assert(parse->sortClause); + + // We don't support cases where there are any SRFs in the targetlist + if (parse->hasTargetSRFs) + return; + // Save the input_rel as outerrel in fpinfo + fpinfo->outerrel = input_rel; + + // Copy foreign table, foreign server, user mapping, FDW options etc. details from the input relation's fpinfo. + fpinfo->table = ifpinfo->table; + fpinfo->server = ifpinfo->server; + fpinfo->user = ifpinfo->user; + merge_fdw_options(fpinfo, ifpinfo, NULL); + + /** If the input_rel is a base or join relation, we would already have + * considered pushing down the final sort to the remote server when + * creating pre-sorted foreign paths for that relation, because the + * query_pathkeys is set to the root->sort_pathkeys in that case (see + * standard_qp_callback()). + */ + if (input_rel->reloptkind == RELOPT_BASEREL || input_rel->reloptkind == RELOPT_JOINREL) { + Assert(root->query_pathkeys == root->sort_pathkeys); + /* Safe to push down if the query_pathkeys is safe to push down */ + fpinfo->pushdown_safe = ifpinfo->qp_is_pushdown_safe; + return; + } + // The input_rel should be a grouping relation + Assert(input_rel->reloptkind == RELOPT_UPPER_REL && ifpinfo->stage == UPPERREL_GROUP_AGG); + /** We try to create a path below by extending a simple foreign path for + * the underlying grouping relation to perform the final sort remotely, + * which is stored into the fdw_private list of the resulting path. + */ + // Assess if it is safe to push down the final sort + foreach(lc, root->sort_pathkeys) { + PathKey *pathkey = (PathKey *) lfirst(lc); + EquivalenceClass *pathkey_ec = pathkey->pk_eclass; + /** is_foreign_expr would detect volatile expressions as well, but + * checking ec_has_volatile here saves some cycles. + */ + if (pathkey_ec->ec_has_volatile) + return; + /** Can't push down the sort if pathkey's opfamily is not shippable. + */ + if (!is_shippable(pathkey->pk_opfamily, OperatorFamilyRelationId, fpinfo)) + return; + /** The EC must contain a shippable EM that is computed in input_rel's + * reltarget, else we can't push down the sort. + */ + if (find_em_for_rel_target(root, pathkey_ec, input_rel) == NULL) + return; + } + /* Safe to push down */ + fpinfo->pushdown_safe = true; + /* Construct PgFdwPathExtraData */ + fpextra = palloc0_object(DB2FdwPathExtraData); + fpextra->target = root->upper_targets[UPPERREL_ORDERED]; + fpextra->has_final_sort = true; + /* Estimate the costs of performing the final sort remotely */ + estimate_path_cost_size(root, input_rel, NIL, root->sort_pathkeys, fpextra, &rows, &width, &disabled_nodes, &startup_cost, &total_cost); + /* + * Build the fdw_private list that will be used by postgresGetForeignPlan. + * Items in the list must match order in enum FdwPathPrivateIndex. + */ + fdw_private = list_make2(makeBoolean(true), makeBoolean(false)); + /* Create foreign ordering path */ + ordered_path = create_foreign_upper_path( root + , input_rel + , root->upper_targets[UPPERREL_ORDERED] + , rows + , disabled_nodes + , startup_cost + , total_cost + , root->sort_pathkeys + , NULL /* no extra plan */ + , NIL /* no fdw_restrictinfo list */ + , fdw_private); + /* and add it to the ordered_rel */ + add_path(ordered_rel, (Path *) ordered_path); +} + +/** add_foreign_final_paths + * Add foreign paths for performing the final processing remotely. + * Given input_rel contains the source-data Paths. The paths are added to the given final_rel. + */ +static void add_foreign_final_paths(PlannerInfo *root, RelOptInfo *input_rel, RelOptInfo *final_rel, FinalPathExtraData *extra) { + Query* parse = root->parse; + DB2FdwState* ifpinfo = (DB2FdwState *) input_rel->fdw_private; + DB2FdwState* fpinfo = (DB2FdwState *) final_rel->fdw_private; + bool has_final_sort = false; + List* pathkeys = NIL; + DB2FdwPathExtraData* fpextra = NULL; + bool save_use_remote_estimate = false; + double rows = 0; + int width = 0; + int disabled_nodes = 0; + Cost startup_cost ; + Cost total_cost ; + List* fdw_private = NIL; + ForeignPath* final_path = NULL; + + /** Currently, we only support this for SELECT commands */ + if (parse->commandType != CMD_SELECT) + return; + + // No work if there is no FOR UPDATE/SHARE clause and if there is no need to add a LIMIT node + if (!parse->rowMarks && !extra->limit_needed) + return; + + // We don't support cases where there are any SRFs in the targetlist + if (parse->hasTargetSRFs) + return; + + /* Save the input_rel as outerrel in fpinfo */ + fpinfo->outerrel = input_rel; + + // Copy foreign table, foreign server, user mapping, FDW options etc. details from the input relation's fpinfo. + fpinfo->table = ifpinfo->table; + fpinfo->server = ifpinfo->server; + fpinfo->user = ifpinfo->user; + merge_fdw_options(fpinfo, ifpinfo, NULL); + + /** If there is no need to add a LIMIT node, there might be a ForeignPath + * in the input_rel's pathlist that implements all behavior of the query. + * Note: we would already have accounted for the query's FOR UPDATE/SHARE + * (if any) before we get here. + */ + if (!extra->limit_needed) { + ListCell *lc; + + Assert(parse->rowMarks); + + /** Grouping and aggregation are not supported with FOR UPDATE/SHARE, + * so the input_rel should be a base, join, or ordered relation; and + * if it's an ordered relation, its input relation should be a base or + * join relation. + */ + Assert(input_rel->reloptkind == RELOPT_BASEREL || input_rel->reloptkind == RELOPT_JOINREL || (input_rel->reloptkind == RELOPT_UPPER_REL && ifpinfo->stage == UPPERREL_ORDERED && (ifpinfo->outerrel->reloptkind == RELOPT_BASEREL || ifpinfo->outerrel->reloptkind == RELOPT_JOINREL))); + + foreach(lc, input_rel->pathlist) { + Path* path = (Path*) lfirst(lc); + + /** apply_scanjoin_target_to_paths() uses create_projection_path() + * to adjust each of its input paths if needed, whereas + * create_ordered_paths() uses apply_projection_to_path() to do + * that. So the former might have put a ProjectionPath on top of + * the ForeignPath; look through ProjectionPath and see if the + * path underneath it is ForeignPath. + */ + if (IsA(path, ForeignPath) || (IsA(path, ProjectionPath) && IsA(((ProjectionPath *) path)->subpath, ForeignPath))) { + //Create foreign final path; this gets rid of a no-longer-needed outer plan (if any), which makes the EXPLAIN output look cleaner + final_path = create_foreign_upper_path( root + , path->parent + , path->pathtarget + , path->rows + , path->disabled_nodes + , path->startup_cost + , path->total_cost + , path->pathkeys + , NULL /* no extra plan */ + , NIL /* no fdw_restrictinfo list */ + , NIL); /* no fdw_private */ + + /* and add it to the final_rel */ + add_path(final_rel, (Path *) final_path); + + /* Safe to push down */ + fpinfo->pushdown_safe = true; + + return; + } + } + + /* + * If we get here it means no ForeignPaths; since we would already + * have considered pushing down all operations for the query to the + * remote server, give up on it. + */ + return; + } + + Assert(extra->limit_needed); + + // If the input_rel is an ordered relation, replace the input_rel with its input relation + if (input_rel->reloptkind == RELOPT_UPPER_REL && ifpinfo->stage == UPPERREL_ORDERED) { + input_rel = ifpinfo->outerrel; + ifpinfo = (DB2FdwState*) input_rel->fdw_private; + has_final_sort = true; + pathkeys = root->sort_pathkeys; + } + + /* The input_rel should be a base, join, or grouping relation */ + Assert(input_rel->reloptkind == RELOPT_BASEREL || input_rel->reloptkind == RELOPT_JOINREL || (input_rel->reloptkind == RELOPT_UPPER_REL && ifpinfo->stage == UPPERREL_GROUP_AGG)); + + /** We try to create a path below by extending a simple foreign path for + * the underlying base, join, or grouping relation to perform the final + * sort (if has_final_sort) and the LIMIT restriction remotely, which is + * stored into the fdw_private list of the resulting path. (We + * re-estimate the costs of sorting the underlying relation, if + * has_final_sort.) + */ + + /** Assess if it is safe to push down the LIMIT and OFFSET to the remote server + */ + + /** If the underlying relation has any local conditions, the LIMIT/OFFSET cannot be pushed down. + */ + if (ifpinfo->local_conds) + return; + + /** If the query has FETCH FIRST .. WITH TIES, 1) it must have ORDER BY as + * well, which is used to determine which additional rows tie for the last + * place in the result set, and 2) ORDER BY must already have been + * determined to be safe to push down before we get here. So in that case + * the FETCH clause is safe to push down with ORDER BY if the remote + * server is v13 or later, but if not, the remote query will fail entirely + * for lack of support for it. Since we do not currently have a way to do + * a remote-version check (without accessing the remote server), disable + * pushing the FETCH clause for now. + */ + if (parse->limitOption == LIMIT_OPTION_WITH_TIES) + return; + + /** Also, the LIMIT/OFFSET cannot be pushed down, if their expressions are + * not safe to remote. + */ + if (!is_foreign_expr(root, input_rel, (Expr *) parse->limitOffset) || !is_foreign_expr(root, input_rel, (Expr *) parse->limitCount)) + return; + + /* Safe to push down */ + fpinfo->pushdown_safe = true; + + /* Construct DB2FdwPathExtraData */ + fpextra = palloc0_object(DB2FdwPathExtraData); + fpextra->target = root->upper_targets[UPPERREL_FINAL]; + fpextra->has_final_sort = has_final_sort; + fpextra->has_limit = extra->limit_needed; + fpextra->limit_tuples = extra->limit_tuples; + fpextra->count_est = extra->count_est; + fpextra->offset_est = extra->offset_est; + + /** Estimate the costs of performing the final sort and the LIMIT + * restriction remotely. If has_final_sort is false, we wouldn't need to + * execute EXPLAIN anymore if use_remote_estimate, since the costs can be + * roughly estimated using the costs we already have for the underlying + * relation, in the same way as when use_remote_estimate is false. Since + * it's pretty expensive to execute EXPLAIN, force use_remote_estimate to + * false in that case. + */ + if (!fpextra->has_final_sort) { + save_use_remote_estimate = ifpinfo->use_remote_estimate; + ifpinfo->use_remote_estimate = false; + } + estimate_path_cost_size(root, input_rel, NIL, pathkeys, fpextra, &rows, &width, &disabled_nodes, &startup_cost, &total_cost); + if (!fpextra->has_final_sort) + ifpinfo->use_remote_estimate = save_use_remote_estimate; + + /** Build the fdw_private list that will be used by postgresGetForeignPlan. + * Items in the list must match order in enum FdwPathPrivateIndex. + */ + fdw_private = list_make2(makeBoolean(has_final_sort), makeBoolean(extra->limit_needed)); + + /** Create foreign final path; this gets rid of a no-longer-needed outer + * plan (if any), which makes the EXPLAIN output look cleaner + */ + final_path = create_foreign_upper_path( root + , input_rel + , root->upper_targets[UPPERREL_FINAL] + , rows + , disabled_nodes + , startup_cost + , total_cost + , pathkeys + , NULL /* no extra plan */ + , NIL /* no fdw_restrictinfo list */ + , fdw_private); + + /* and add it to the final_rel */ + add_path(final_rel, (Path*) final_path); +} + +/* + * Assess whether the aggregation, grouping and having operations can be pushed + * down to the foreign server. As a side effect, save information we obtain in + * this function to PgFdwRelationInfo of the input relation. + */ +static bool foreign_grouping_ok(PlannerInfo *root, RelOptInfo *grouped_rel, Node *havingQual) { + Query* query = root->parse; + DB2FdwState* fpinfo = (DB2FdwState*) grouped_rel->fdw_private; + PathTarget* grouping_target = grouped_rel->reltarget; + DB2FdwState* ofpinfo = NULL; + ListCell* lc = NULL; + int i = 0; + List* tlist = NIL; + + /* We currently don't support pushing Grouping Sets. */ + if (query->groupingSets) + return false; + + /* Get the fpinfo of the underlying scan relation. */ + ofpinfo = (DB2FdwState*) fpinfo->outerrel->fdw_private; + + /** If underlying scan relation has any local conditions, those conditions + * are required to be applied before performing aggregation. Hence the + * aggregate cannot be pushed down. + */ + if (ofpinfo->local_conds) + return false; + + /** Examine grouping expressions, as well as other expressions we'd need to + * compute, and check whether they are safe to push down to the foreign + * server. All GROUP BY expressions will be part of the grouping target + * and thus there is no need to search for them separately. Add grouping + * expressions into target list which will be passed to foreign server. + * + * A tricky fine point is that we must not put any expression into the + * target list that is just a foreign param (that is, something that + * deparse.c would conclude has to be sent to the foreign server). If we + * do, the expression will also appear in the fdw_exprs list of the plan + * node, and setrefs.c will get confused and decide that the fdw_exprs + * entry is actually a reference to the fdw_scan_tlist entry, resulting in + * a broken plan. Somewhat oddly, it's OK if the expression contains such + * a node, as long as it's not at top level; then no match is possible. + */ + i = 0; + foreach(lc, grouping_target->exprs) { + Expr* expr = (Expr *) lfirst(lc); + Index sgref = get_pathtarget_sortgroupref(grouping_target, i); + ListCell* l; + + /** Check whether this expression is part of GROUP BY clause. Note we + * check the whole GROUP BY clause not just processed_groupClause, + * because we will ship all of it, cf. appendGroupByClause. + */ + if (sgref && get_sortgroupref_clause_noerr(sgref, query->groupClause)) { + TargetEntry *tle; + + /** If any GROUP BY expression is not shippable, then we cannot + * push down aggregation to the foreign server. + */ + if (!is_foreign_expr(root, grouped_rel, expr)) + return false; + + /** If it would be a foreign param, we can't put it into the tlist, + * so we have to fail. + */ + if (is_foreign_param(root, grouped_rel, expr)) + return false; + + /** Pushable, so add to tlist. We need to create a TLE for this + * expression and apply the sortgroupref to it. We cannot use + * add_to_flat_tlist() here because that avoids making duplicate + * entries in the tlist. If there are duplicate entries with + * distinct sortgrouprefs, we have to duplicate that situation in + * the output tlist. + */ + tle = makeTargetEntry(expr, list_length(tlist) + 1, NULL, false); + tle->ressortgroupref = sgref; + tlist = lappend(tlist, tle); + } else { + /** Non-grouping expression we need to compute. Can we ship it + * as-is to the foreign server? + */ + if (is_foreign_expr(root, grouped_rel, expr) && !is_foreign_param(root, grouped_rel, expr)) { + /* Yes, so add to tlist as-is; OK to suppress duplicates */ + tlist = add_to_flat_tlist(tlist, list_make1(expr)); + } else { + /* Not pushable as a whole; extract its Vars and aggregates */ + List* aggvars = pull_var_clause((Node*) expr, PVC_INCLUDE_AGGREGATES); + + /** If any aggregate expression is not shippable, then we + * cannot push down aggregation to the foreign server. (We + * don't have to check is_foreign_param, since that certainly + * won't return true for any such expression.) + */ + if (!is_foreign_expr(root, grouped_rel, (Expr *) aggvars)) + return false; + + /** Add aggregates, if any, into the targetlist. Plain Vars + * outside an aggregate can be ignored, because they should be + * either same as some GROUP BY column or part of some GROUP + * BY expression. In either case, they are already part of + * the targetlist and thus no need to add them again. In fact + * including plain Vars in the tlist when they do not match a + * GROUP BY column would cause the foreign server to complain + * that the shipped query is invalid. + */ + foreach(l, aggvars) { + Expr* aggref = (Expr *) lfirst(l); + + if (IsA(aggref, Aggref)) + tlist = add_to_flat_tlist(tlist, list_make1(aggref)); + } + } + } + i++; + } + + /** Classify the pushable and non-pushable HAVING clauses and save them in + * remote_conds and local_conds of the grouped rel's fpinfo. + */ + if (havingQual) { + foreach(lc, (List *) havingQual) { + Expr *expr = (Expr *) lfirst(lc); + RestrictInfo *rinfo; + + /** Currently, the core code doesn't wrap havingQuals in + * RestrictInfos, so we must make our own. + */ + Assert(!IsA(expr, RestrictInfo)); + rinfo = make_restrictinfo(root, expr, true, false, false, false, root->qual_security_level, grouped_rel->relids, NULL, NULL); + if (is_foreign_expr(root, grouped_rel, expr)) + fpinfo->remote_conds = lappend(fpinfo->remote_conds, rinfo); + else + fpinfo->local_conds = lappend(fpinfo->local_conds, rinfo); + } + } + + /** If there are any local conditions, pull Vars and aggregates from it and + * check whether they are safe to pushdown or not. + */ + if (fpinfo->local_conds) { + List* aggvars = NIL; + + foreach(lc, fpinfo->local_conds) { + RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc); + + aggvars = list_concat(aggvars, pull_var_clause((Node*) rinfo->clause, PVC_INCLUDE_AGGREGATES)); + } + + foreach(lc, aggvars) { + Expr *expr = (Expr *) lfirst(lc); + + /** If aggregates within local conditions are not safe to push + * down, then we cannot push down the query. Vars are already + * part of GROUP BY clause which are checked above, so no need to + * access them again here. Again, we need not check + * is_foreign_param for a foreign aggregate. + */ + if (IsA(expr, Aggref)) { + if (!is_foreign_expr(root, grouped_rel, expr)) + return false; + + tlist = add_to_flat_tlist(tlist, list_make1(expr)); + } + } + } + + /* Store generated targetlist */ + fpinfo->grouped_tlist = tlist; + + /* Safe to pushdown */ + fpinfo->pushdown_safe = true; + + /** Set # of retrieved rows and cached relation costs to some negative + * value, so that we can detect when they are set to some sensible values, + * during one (usually the first) of the calls to estimate_path_cost_size. + */ + fpinfo->retrieved_rows = -1; + fpinfo->rel_startup_cost = -1; + fpinfo->rel_total_cost = -1; + + /** Set the string describing this grouped relation to be used in EXPLAIN + * output of corresponding ForeignScan. Note that the decoration we add + * to the base relation name mustn't include any digits, or it'll confuse + * postgresExplainForeignScan. + */ + fpinfo->relation_name = psprintf("Aggregate on (%s)", ofpinfo->relation_name); + return true; +} diff --git a/source/db2_deparse.c b/source/db2_deparse.c index a6d971c..f2fa175 100644 --- a/source/db2_deparse.c +++ b/source/db2_deparse.c @@ -1,19 +1,69 @@ #include + +#include +#include +#include + + +#include +#include +#include +#include #include #include +#include #include +#include +#include #include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + #include #include +#include #include #include #include -#include -#include -#include +#include +#include + #include "db2_fdw.h" #include "DB2FdwState.h" +/** Global context for foreign_expr_walker's search of an expression tree. + */ +typedef struct foreign_glob_cxt { + PlannerInfo* root; /* global planner state */ + RelOptInfo* foreignrel; /* the foreign relation we are planning for */ + Relids relids; /* relids of base relations in the underlying scan */ +} foreign_glob_cxt; + +/** Local (per-tree-level) context for foreign_expr_walker's search. + * This is concerned with identifying collations used in the expression. + */ +typedef enum { + FDW_COLLATE_NONE, /* expression is of a noncollatable type, or it has default collation that is not traceable to a foreign Var */ + FDW_COLLATE_SAFE, /* collation derives from a foreign Var */ + FDW_COLLATE_UNSAFE, /* collation is non-default and derives from something other than a foreign Var*/ +} FDWCollateState; + +typedef struct foreign_loc_cxt { + Oid collation; /* OID of current collation, if any */ + FDWCollateState state; /* state of current collation choice */ +} foreign_loc_cxt; + /** Context for deparseExpr */ typedef struct deparse_expr_cxt { PlannerInfo* root; /* global planner state */ @@ -30,8 +80,14 @@ extern void db2Debug2 (const char* message, ...); extern void* db2alloc (const char* type, size_t size); extern void* db2strdup (const char* source); extern void db2free (void* p); +extern bool is_shippable (Oid objectId, Oid classId, DB2FdwState* fpinfo); +extern EquivalenceMember* find_em_for_rel(PlannerInfo *root, EquivalenceClass *ec, RelOptInfo *rel); /** local prototypes */ +bool is_foreign_expr (PlannerInfo *root, RelOptInfo *baserel, Expr *expr); +static bool foreign_expr_walker (Node *node, foreign_glob_cxt *glob_cxt, foreign_loc_cxt *outer_cxt, foreign_loc_cxt *case_arg_cxt); +bool is_foreign_param (PlannerInfo *root, RelOptInfo *baserel, Expr *expr); +bool is_foreign_pathkey (PlannerInfo *root, RelOptInfo *baserel, PathKey *pathkey); void appendAsType (StringInfoData* dest, Oid type); void deparseFromExprForRel (PlannerInfo* root, RelOptInfo* foreignrel, StringInfo buf, List** params_list); char* deparseWhereConditions (PlannerInfo* root, RelOptInfo* rel); @@ -58,6 +114,839 @@ char* deparseTimestamp (Datum datum, bool hasTimezone); static char* deparseInterval (Datum datum); static const char* get_jointype_name (JoinType jointype); +/** Returns true if given expr is safe to evaluate on the foreign server. + */ +bool is_foreign_expr(PlannerInfo *root, RelOptInfo *baserel, Expr *expr) { + foreign_glob_cxt glob_cxt; + foreign_loc_cxt loc_cxt; + DB2FdwState* fpinfo = (DB2FdwState*) (baserel->fdw_private); + bool bResult = false; + + /** Check that the expression consists of nodes that are safe to execute + * remotely. + */ + glob_cxt.root = root; + glob_cxt.foreignrel = baserel; + + /* + * For an upper relation, use relids from its underneath scan relation, + * because the upperrel's own relids currently aren't set to anything + * meaningful by the core code. For other relation, use their own relids. + */ + glob_cxt.relids = (IS_UPPER_REL(baserel)) ? fpinfo->outerrel->relids : baserel->relids; + loc_cxt.collation = InvalidOid; + loc_cxt.state = FDW_COLLATE_NONE; + if (foreign_expr_walker((Node*) expr, &glob_cxt, &loc_cxt, NULL)) { + /** If the expression has a valid collation that does not arise from a + * foreign var, the expression can not be sent over. + */ + if (loc_cxt.state != FDW_COLLATE_UNSAFE) { + /** An expression which includes any mutable functions can't be sent over + * because its result is not stable. For example, sending now() remote + * side could cause confusion from clock offsets. Future versions might + * be able to make this choice with more granularity. (We check this last + * because it requires a lot of expensive catalog lookups.) + */ + if (!contain_mutable_functions((Node *) expr)) { + bResult = true; + } + } + } + /* OK to evaluate on the remote server */ + return bResult; +} + +/** Check if expression is safe to execute remotely, and return true if so. + * + * In addition, *outer_cxt is updated with collation information. + * + * case_arg_cxt is NULL if this subexpression is not inside a CASE-with-arg. + * Otherwise, it points to the collation info derived from the arg expression, + * which must be consulted by any CaseTestExpr. + * + * We must check that the expression contains only node types we can deparse, + * that all types/functions/operators are safe to send (they are "shippable"), + * and that all collations used in the expression derive from Vars of the + * foreign table. Because of the latter, the logic is pretty close to + * assign_collations_walker() in parse_collate.c, though we can assume here + * that the given expression is valid. Note function mutability is not + * currently considered here. + */ +static bool foreign_expr_walker(Node *node, foreign_glob_cxt *glob_cxt, foreign_loc_cxt *outer_cxt, foreign_loc_cxt *case_arg_cxt) { + bool check_type = true; + DB2FdwState* fpinfo = NULL; + foreign_loc_cxt inner_cxt; + Oid collation; + FDWCollateState state; + + /* Need do nothing for empty subexpressions */ + if (node == NULL) + return true; + + /* May need server info from baserel's fdw_private struct */ + fpinfo = (DB2FdwState*) glob_cxt->foreignrel->fdw_private; + + /* Set up inner_cxt for possible recursion to child nodes */ + inner_cxt.collation = InvalidOid; + inner_cxt.state = FDW_COLLATE_NONE; + + switch (nodeTag(node)) { + case T_Var: { + Var *var = (Var *) node; + /** If the Var is from the foreign table, we consider its + * collation (if any) safe to use. If it is from another + * table, we treat its collation the same way as we would a + * Param's collation, ie it's not safe for it to have a + * non-default collation. + */ + if (bms_is_member(var->varno, glob_cxt->relids) && var->varlevelsup == 0) { + /* Var belongs to foreign table */ + /** System columns other than ctid should not be sent to + * the remote, since we don't make any effort to ensure + * that local and remote values match (tableoid, in + * particular, almost certainly doesn't match). + */ + if (var->varattno < 0 && var->varattno != SelfItemPointerAttributeNumber) + return false; + + /* Else check the collation */ + collation = var->varcollid; + state = OidIsValid(collation) ? FDW_COLLATE_SAFE : FDW_COLLATE_NONE; + } else { + /* Var belongs to some other table */ + collation = var->varcollid; + if (collation == InvalidOid || collation == DEFAULT_COLLATION_OID) { + /** It's noncollatable, or it's safe to combine with a + * collatable foreign Var, so set state to NONE. + */ + state = FDW_COLLATE_NONE; + } else { + /** Do not fail right away, since the Var might appear + * in a collation-insensitive context. + */ + state = FDW_COLLATE_UNSAFE; + } + } + } + break; + case T_Const: { + Const* c = (Const *) node; + + /** Constants of regproc and related types can't be shipped + * unless the referenced object is shippable. But NULL's ok. + * (See also the related code in dependency.c.) + */ + if (!c->constisnull) { + switch (c->consttype) { + case REGPROCOID: + case REGPROCEDUREOID: + if (!is_shippable(DatumGetObjectId(c->constvalue), ProcedureRelationId, fpinfo)) + return false; + break; + case REGOPEROID: + case REGOPERATOROID: + if (!is_shippable(DatumGetObjectId(c->constvalue), OperatorRelationId, fpinfo)) + return false; + break; + case REGCLASSOID: + if (!is_shippable(DatumGetObjectId(c->constvalue), RelationRelationId, fpinfo)) + return false; + break; + case REGTYPEOID: + if (!is_shippable(DatumGetObjectId(c->constvalue), TypeRelationId, fpinfo)) + return false; + break; + case REGCOLLATIONOID: + if (!is_shippable(DatumGetObjectId(c->constvalue), CollationRelationId, fpinfo)) + return false; + break; + case REGCONFIGOID: + /** For text search objects only, we weaken the normal shippability criterion to + * allow all OIDs below FirstNormalObjectId. Without this, none of the initdb-installed + * TS configurations would be shippable, which would be quite annoying. + */ + if (DatumGetObjectId(c->constvalue) >= FirstNormalObjectId && !is_shippable(DatumGetObjectId(c->constvalue), TSConfigRelationId, fpinfo)) + return false; + break; + case REGDICTIONARYOID: + if (DatumGetObjectId(c->constvalue) >= FirstNormalObjectId && !is_shippable(DatumGetObjectId(c->constvalue), TSDictionaryRelationId, fpinfo)) + return false; + break; + case REGNAMESPACEOID: + if (!is_shippable(DatumGetObjectId(c->constvalue), NamespaceRelationId, fpinfo)) + return false; + break; + case REGROLEOID: + if (!is_shippable(DatumGetObjectId(c->constvalue), AuthIdRelationId, fpinfo)) + return false; + break; + #ifdef REGDATABASEOID + case REGDATABASEOID: + if (!is_shippable(DatumGetObjectId(c->constvalue), DatabaseRelationId, fpinfo)) + return false; + break; + #endif + } + } + + /** If the constant has nondefault collation, either it's of a + * non-builtin type, or it reflects folding of a CollateExpr. + * It's unsafe to send to the remote unless it's used in a + * non-collation-sensitive context. + */ + collation = c->constcollid; + state = (collation == InvalidOid || collation == DEFAULT_COLLATION_OID) ? FDW_COLLATE_NONE : FDW_COLLATE_UNSAFE; + } + break; + case T_Param: { + Param *p = (Param *) node; + + /** If it's a MULTIEXPR Param, punt. We can't tell from here whether the referenced sublink/subplan contains any remote + * Vars; if it does, handling that is too complicated to consider supporting at present. Fortunately, MULTIEXPR + * Params are not reduced to plain PARAM_EXEC until the end of planning, so we can easily detect this case. (Normal + * PARAM_EXEC Params are safe to ship because their values come from somewhere else in the plan tree; but a MULTIEXPR + * references a sub-select elsewhere in the same targetlist, so we'd be on the hook to evaluate it somehow if we wanted + * to handle such cases as direct foreign updates.) + */ + if (p->paramkind == PARAM_MULTIEXPR) + return false; + + /** Collation rule is same as for Consts and non-foreign Vars. */ + collation = p->paramcollid; + state = (collation == InvalidOid || collation == DEFAULT_COLLATION_OID) ? FDW_COLLATE_NONE : FDW_COLLATE_UNSAFE; + } + break; + case T_SubscriptingRef: + { + SubscriptingRef *sr = (SubscriptingRef *) node; + + /* Assignment should not be in restrictions. */ + if (sr->refassgnexpr != NULL) + return false; + + /* + * Recurse into the remaining subexpressions. The container + * subscripts will not affect collation of the SubscriptingRef + * result, so do those first and reset inner_cxt afterwards. + */ + if (!foreign_expr_walker((Node *) sr->refupperindexpr, + glob_cxt, &inner_cxt, case_arg_cxt)) + return false; + inner_cxt.collation = InvalidOid; + inner_cxt.state = FDW_COLLATE_NONE; + if (!foreign_expr_walker((Node *) sr->reflowerindexpr, + glob_cxt, &inner_cxt, case_arg_cxt)) + return false; + inner_cxt.collation = InvalidOid; + inner_cxt.state = FDW_COLLATE_NONE; + if (!foreign_expr_walker((Node *) sr->refexpr, + glob_cxt, &inner_cxt, case_arg_cxt)) + return false; + + /* + * Container subscripting typically yields same collation as + * refexpr's, but in case it doesn't, use same logic as for + * function nodes. + */ + collation = sr->refcollid; + if (collation == InvalidOid) + state = FDW_COLLATE_NONE; + else if (inner_cxt.state == FDW_COLLATE_SAFE && + collation == inner_cxt.collation) + state = FDW_COLLATE_SAFE; + else if (collation == DEFAULT_COLLATION_OID) + state = FDW_COLLATE_NONE; + else + state = FDW_COLLATE_UNSAFE; + } + break; + case T_FuncExpr: + { + FuncExpr *fe = (FuncExpr *) node; + + /* + * If function used by the expression is not shippable, it + * can't be sent to remote because it might have incompatible + * semantics on remote side. + */ + if (!is_shippable(fe->funcid, ProcedureRelationId, fpinfo)) + return false; + + /* + * Recurse to input subexpressions. + */ + if (!foreign_expr_walker((Node *) fe->args, + glob_cxt, &inner_cxt, case_arg_cxt)) + return false; + + /* + * If function's input collation is not derived from a foreign + * Var, it can't be sent to remote. + */ + if (fe->inputcollid == InvalidOid) + /* OK, inputs are all noncollatable */ ; + else if (inner_cxt.state != FDW_COLLATE_SAFE || + fe->inputcollid != inner_cxt.collation) + return false; + + /* + * Detect whether node is introducing a collation not derived + * from a foreign Var. (If so, we just mark it unsafe for now + * rather than immediately returning false, since the parent + * node might not care.) + */ + collation = fe->funccollid; + if (collation == InvalidOid) + state = FDW_COLLATE_NONE; + else if (inner_cxt.state == FDW_COLLATE_SAFE && + collation == inner_cxt.collation) + state = FDW_COLLATE_SAFE; + else if (collation == DEFAULT_COLLATION_OID) + state = FDW_COLLATE_NONE; + else + state = FDW_COLLATE_UNSAFE; + } + break; + case T_OpExpr: + case T_DistinctExpr: /* struct-equivalent to OpExpr */ + { + OpExpr *oe = (OpExpr *) node; + + /* + * Similarly, only shippable operators can be sent to remote. + * (If the operator is shippable, we assume its underlying + * function is too.) + */ + if (!is_shippable(oe->opno, OperatorRelationId, fpinfo)) + return false; + + /* + * Recurse to input subexpressions. + */ + if (!foreign_expr_walker((Node *) oe->args, + glob_cxt, &inner_cxt, case_arg_cxt)) + return false; + + /* + * If operator's input collation is not derived from a foreign + * Var, it can't be sent to remote. + */ + if (oe->inputcollid == InvalidOid) + /* OK, inputs are all noncollatable */ ; + else if (inner_cxt.state != FDW_COLLATE_SAFE || + oe->inputcollid != inner_cxt.collation) + return false; + + /* Result-collation handling is same as for functions */ + collation = oe->opcollid; + if (collation == InvalidOid) + state = FDW_COLLATE_NONE; + else if (inner_cxt.state == FDW_COLLATE_SAFE && + collation == inner_cxt.collation) + state = FDW_COLLATE_SAFE; + else if (collation == DEFAULT_COLLATION_OID) + state = FDW_COLLATE_NONE; + else + state = FDW_COLLATE_UNSAFE; + } + break; + case T_ScalarArrayOpExpr: + { + ScalarArrayOpExpr *oe = (ScalarArrayOpExpr *) node; + + /* + * Again, only shippable operators can be sent to remote. + */ + if (!is_shippable(oe->opno, OperatorRelationId, fpinfo)) + return false; + + /* + * Recurse to input subexpressions. + */ + if (!foreign_expr_walker((Node *) oe->args, + glob_cxt, &inner_cxt, case_arg_cxt)) + return false; + + /* + * If operator's input collation is not derived from a foreign + * Var, it can't be sent to remote. + */ + if (oe->inputcollid == InvalidOid) + /* OK, inputs are all noncollatable */ ; + else if (inner_cxt.state != FDW_COLLATE_SAFE || + oe->inputcollid != inner_cxt.collation) + return false; + + /* Output is always boolean and so noncollatable. */ + collation = InvalidOid; + state = FDW_COLLATE_NONE; + } + break; + case T_RelabelType: + { + RelabelType *r = (RelabelType *) node; + + /* + * Recurse to input subexpression. + */ + if (!foreign_expr_walker((Node *) r->arg, + glob_cxt, &inner_cxt, case_arg_cxt)) + return false; + + /* + * RelabelType must not introduce a collation not derived from + * an input foreign Var (same logic as for a real function). + */ + collation = r->resultcollid; + if (collation == InvalidOid) + state = FDW_COLLATE_NONE; + else if (inner_cxt.state == FDW_COLLATE_SAFE && + collation == inner_cxt.collation) + state = FDW_COLLATE_SAFE; + else if (collation == DEFAULT_COLLATION_OID) + state = FDW_COLLATE_NONE; + else + state = FDW_COLLATE_UNSAFE; + } + break; + case T_ArrayCoerceExpr: + { + ArrayCoerceExpr *e = (ArrayCoerceExpr *) node; + + /* + * Recurse to input subexpression. + */ + if (!foreign_expr_walker((Node *) e->arg, + glob_cxt, &inner_cxt, case_arg_cxt)) + return false; + + /* + * T_ArrayCoerceExpr must not introduce a collation not + * derived from an input foreign Var (same logic as for a + * function). + */ + collation = e->resultcollid; + if (collation == InvalidOid) + state = FDW_COLLATE_NONE; + else if (inner_cxt.state == FDW_COLLATE_SAFE && + collation == inner_cxt.collation) + state = FDW_COLLATE_SAFE; + else if (collation == DEFAULT_COLLATION_OID) + state = FDW_COLLATE_NONE; + else + state = FDW_COLLATE_UNSAFE; + } + break; + case T_BoolExpr: + { + BoolExpr *b = (BoolExpr *) node; + + /* + * Recurse to input subexpressions. + */ + if (!foreign_expr_walker((Node *) b->args, + glob_cxt, &inner_cxt, case_arg_cxt)) + return false; + + /* Output is always boolean and so noncollatable. */ + collation = InvalidOid; + state = FDW_COLLATE_NONE; + } + break; + case T_NullTest: + { + NullTest *nt = (NullTest *) node; + + /* + * Recurse to input subexpressions. + */ + if (!foreign_expr_walker((Node *) nt->arg, + glob_cxt, &inner_cxt, case_arg_cxt)) + return false; + + /* Output is always boolean and so noncollatable. */ + collation = InvalidOid; + state = FDW_COLLATE_NONE; + } + break; + case T_CaseExpr: + { + CaseExpr *ce = (CaseExpr *) node; + foreign_loc_cxt arg_cxt; + foreign_loc_cxt tmp_cxt; + ListCell *lc; + + /* + * Recurse to CASE's arg expression, if any. Its collation + * has to be saved aside for use while examining CaseTestExprs + * within the WHEN expressions. + */ + arg_cxt.collation = InvalidOid; + arg_cxt.state = FDW_COLLATE_NONE; + if (ce->arg) + { + if (!foreign_expr_walker((Node *) ce->arg, + glob_cxt, &arg_cxt, case_arg_cxt)) + return false; + } + + /* Examine the CaseWhen subexpressions. */ + foreach(lc, ce->args) + { + CaseWhen *cw = lfirst_node(CaseWhen, lc); + + if (ce->arg) + { + /* + * In a CASE-with-arg, the parser should have produced + * WHEN clauses of the form "CaseTestExpr = RHS", + * possibly with an implicit coercion inserted above + * the CaseTestExpr. However in an expression that's + * been through the optimizer, the WHEN clause could + * be almost anything (since the equality operator + * could have been expanded into an inline function). + * In such cases forbid pushdown, because + * deparseCaseExpr can't handle it. + */ + Node *whenExpr = (Node *) cw->expr; + List *opArgs; + + if (!IsA(whenExpr, OpExpr)) + return false; + + opArgs = ((OpExpr *) whenExpr)->args; + if (list_length(opArgs) != 2 || + !IsA(strip_implicit_coercions(linitial(opArgs)), + CaseTestExpr)) + return false; + } + + /* + * Recurse to WHEN expression, passing down the arg info. + * Its collation doesn't affect the result (really, it + * should be boolean and thus not have a collation). + */ + tmp_cxt.collation = InvalidOid; + tmp_cxt.state = FDW_COLLATE_NONE; + if (!foreign_expr_walker((Node *) cw->expr, + glob_cxt, &tmp_cxt, &arg_cxt)) + return false; + + /* Recurse to THEN expression. */ + if (!foreign_expr_walker((Node *) cw->result, + glob_cxt, &inner_cxt, case_arg_cxt)) + return false; + } + + /* Recurse to ELSE expression. */ + if (!foreign_expr_walker((Node *) ce->defresult, + glob_cxt, &inner_cxt, case_arg_cxt)) + return false; + + /* + * Detect whether node is introducing a collation not derived + * from a foreign Var. (If so, we just mark it unsafe for now + * rather than immediately returning false, since the parent + * node might not care.) This is the same as for function + * nodes, except that the input collation is derived from only + * the THEN and ELSE subexpressions. + */ + collation = ce->casecollid; + if (collation == InvalidOid) + state = FDW_COLLATE_NONE; + else if (inner_cxt.state == FDW_COLLATE_SAFE && + collation == inner_cxt.collation) + state = FDW_COLLATE_SAFE; + else if (collation == DEFAULT_COLLATION_OID) + state = FDW_COLLATE_NONE; + else + state = FDW_COLLATE_UNSAFE; + } + break; + case T_CaseTestExpr: + { + CaseTestExpr *c = (CaseTestExpr *) node; + + /* Punt if we seem not to be inside a CASE arg WHEN. */ + if (!case_arg_cxt) + return false; + + /* + * Otherwise, any nondefault collation attached to the + * CaseTestExpr node must be derived from foreign Var(s) in + * the CASE arg. + */ + collation = c->collation; + if (collation == InvalidOid) + state = FDW_COLLATE_NONE; + else if (case_arg_cxt->state == FDW_COLLATE_SAFE && + collation == case_arg_cxt->collation) + state = FDW_COLLATE_SAFE; + else if (collation == DEFAULT_COLLATION_OID) + state = FDW_COLLATE_NONE; + else + state = FDW_COLLATE_UNSAFE; + } + break; + case T_ArrayExpr: + { + ArrayExpr *a = (ArrayExpr *) node; + + /* + * Recurse to input subexpressions. + */ + if (!foreign_expr_walker((Node *) a->elements, + glob_cxt, &inner_cxt, case_arg_cxt)) + return false; + + /* + * ArrayExpr must not introduce a collation not derived from + * an input foreign Var (same logic as for a function). + */ + collation = a->array_collid; + if (collation == InvalidOid) + state = FDW_COLLATE_NONE; + else if (inner_cxt.state == FDW_COLLATE_SAFE && + collation == inner_cxt.collation) + state = FDW_COLLATE_SAFE; + else if (collation == DEFAULT_COLLATION_OID) + state = FDW_COLLATE_NONE; + else + state = FDW_COLLATE_UNSAFE; + } + break; + case T_List: + { + List *l = (List *) node; + ListCell *lc; + + /* + * Recurse to component subexpressions. + */ + foreach(lc, l) + { + if (!foreign_expr_walker((Node *) lfirst(lc), + glob_cxt, &inner_cxt, case_arg_cxt)) + return false; + } + + /* + * When processing a list, collation state just bubbles up + * from the list elements. + */ + collation = inner_cxt.collation; + state = inner_cxt.state; + + /* Don't apply exprType() to the list. */ + check_type = false; + } + break; + case T_Aggref: + { + Aggref *agg = (Aggref *) node; + ListCell *lc; + + /* Not safe to pushdown when not in grouping context */ + if (!IS_UPPER_REL(glob_cxt->foreignrel)) + return false; + + /* Only non-split aggregates are pushable. */ + if (agg->aggsplit != AGGSPLIT_SIMPLE) + return false; + + /* As usual, it must be shippable. */ + if (!is_shippable(agg->aggfnoid, ProcedureRelationId, fpinfo)) + return false; + + /* + * Recurse to input args. aggdirectargs, aggorder and + * aggdistinct are all present in args, so no need to check + * their shippability explicitly. + */ + foreach(lc, agg->args) + { + Node *n = (Node *) lfirst(lc); + + /* If TargetEntry, extract the expression from it */ + if (IsA(n, TargetEntry)) + { + TargetEntry *tle = (TargetEntry *) n; + + n = (Node *) tle->expr; + } + + if (!foreign_expr_walker(n, + glob_cxt, &inner_cxt, case_arg_cxt)) + return false; + } + + /* + * For aggorder elements, check whether the sort operator, if + * specified, is shippable or not. + */ + if (agg->aggorder) + { + foreach(lc, agg->aggorder) + { + SortGroupClause *srt = (SortGroupClause *) lfirst(lc); + Oid sortcoltype; + TypeCacheEntry *typentry; + TargetEntry *tle; + + tle = get_sortgroupref_tle(srt->tleSortGroupRef, + agg->args); + sortcoltype = exprType((Node *) tle->expr); + typentry = lookup_type_cache(sortcoltype, + TYPECACHE_LT_OPR | TYPECACHE_GT_OPR); + /* Check shippability of non-default sort operator. */ + if (srt->sortop != typentry->lt_opr && + srt->sortop != typentry->gt_opr && + !is_shippable(srt->sortop, OperatorRelationId, + fpinfo)) + return false; + } + } + + /* Check aggregate filter */ + if (!foreign_expr_walker((Node *) agg->aggfilter, + glob_cxt, &inner_cxt, case_arg_cxt)) + return false; + + /* + * If aggregate's input collation is not derived from a + * foreign Var, it can't be sent to remote. + */ + if (agg->inputcollid == InvalidOid) + /* OK, inputs are all noncollatable */ ; + else if (inner_cxt.state != FDW_COLLATE_SAFE || + agg->inputcollid != inner_cxt.collation) + return false; + + /* + * Detect whether node is introducing a collation not derived + * from a foreign Var. (If so, we just mark it unsafe for now + * rather than immediately returning false, since the parent + * node might not care.) + */ + collation = agg->aggcollid; + if (collation == InvalidOid) + state = FDW_COLLATE_NONE; + else if (inner_cxt.state == FDW_COLLATE_SAFE && + collation == inner_cxt.collation) + state = FDW_COLLATE_SAFE; + else if (collation == DEFAULT_COLLATION_OID) + state = FDW_COLLATE_NONE; + else + state = FDW_COLLATE_UNSAFE; + } + break; + default: + /** If it's anything else, assume it's unsafe. This list can be + * expanded later, but don't forget to add deparse support below. + */ + return false; + } + + /** If result type of given expression is not shippable, it can't be sent + * to remote because it might have incompatible semantics on remote side. + */ + if (check_type && !is_shippable(exprType(node), TypeRelationId, fpinfo)) + return false; + + /** Now, merge my collation information into my parent's state. */ + if (state > outer_cxt->state) { + /* Override previous parent state */ + outer_cxt->collation = collation; + outer_cxt->state = state; + } else if (state == outer_cxt->state) { + /* Merge, or detect error if there's a collation conflict */ + switch (state) { + case FDW_COLLATE_NONE: { + /* Nothing + nothing is still nothing */ + } + break; + case FDW_COLLATE_SAFE: { + if (collation != outer_cxt->collation) { + /** Non-default collation always beats default.*/ + if (outer_cxt->collation == DEFAULT_COLLATION_OID) { + /* Override previous parent state */ + outer_cxt->collation = collation; + } else if (collation != DEFAULT_COLLATION_OID) { + /** Conflict; show state as indeterminate. We don't + * want to "return false" right away, since parent + * node might not care about collation. + */ + outer_cxt->state = FDW_COLLATE_UNSAFE; + } + } + } + break; + case FDW_COLLATE_UNSAFE: { + /* We're still conflicted ... */ + } + break; + } + } + /* It looks OK */ + return true; +} + +/** Returns true if given expr is something we'd have to send the value of + * to the foreign server. + * + * This should return true when the expression is a shippable node that + * deparseExpr would add to context->params_list. Note that we don't care + * if the expression *contains* such a node, only whether one appears at top + * level. We need this to detect cases where setrefs.c would recognize a + * false match between an fdw_exprs item (which came from the params_list) + * and an entry in fdw_scan_tlist (which we're considering putting the given + * expression into). + */ +bool is_foreign_param(PlannerInfo *root, RelOptInfo *baserel, Expr *expr) { + bool bResult = false; + if (expr != NULL) { + switch (nodeTag(expr)) { + case T_Var: { + /* It would have to be sent unless it's a foreign Var */ + Var* var = (Var *) expr; + DB2FdwState* fpinfo = (DB2FdwState*) (baserel->fdw_private); + Relids relids; + + relids = (IS_UPPER_REL(baserel)) ? fpinfo->outerrel->relids : baserel->relids; + bResult = !(bms_is_member(var->varno, relids) && var->varlevelsup == 0); + } + break; + case T_Param: + /* Params always have to be sent to the foreign server */ + bResult = true; + default: + break; + } + } + return bResult; +} + +/** Returns true if it's safe to push down the sort expression described by + * 'pathkey' to the foreign server. + */ +bool is_foreign_pathkey(PlannerInfo *root, RelOptInfo *baserel, PathKey *pathkey) { + EquivalenceClass* pathkey_ec = pathkey->pk_eclass; + DB2FdwState* fpinfo = (DB2FdwState*) baserel->fdw_private; + bool bResult = false; + + /** is_foreign_expr would detect volatile expressions as well, but checking + * ec_has_volatile here saves some cycles. + */ + if (!pathkey_ec->ec_has_volatile) { + /* can't push down the sort if the pathkey's opfamily is not shippable */ + if (is_shippable(pathkey->pk_opfamily, OperatorFamilyRelationId, fpinfo)) { + /* can push if a suitable EC member exists */ + bResult = (find_em_for_rel(root, pathkey_ec, baserel) != NULL); + } + } + return bResult; +} + /** appendAsType * Append "s" to "dest", adding appropriate casts for datetime "type". */ From 0eedf458e8ccadb48c7cff4a56a0ae41d30710be Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Thu, 22 Jan 2026 16:17:38 +0100 Subject: [PATCH 012/120] add fetchsize to the fdw --- include/db2_fdw.h | 6 +++++- source/db2AnalyzeForeignTable.c | 4 ++-- source/db2BeginForeignModifyCommon.c | 4 ++-- source/db2GetFdwState.c | 19 ++++++++++++++----- source/db2IterateForeignScan.c | 4 ++-- source/db2PrepareQuery.c | 14 +++++++++++--- source/db2_fdw.c | 18 ++++++++++++++++-- 7 files changed, 52 insertions(+), 17 deletions(-) diff --git a/include/db2_fdw.h b/include/db2_fdw.h index 0027b39..eb533ce 100644 --- a/include/db2_fdw.h +++ b/include/db2_fdw.h @@ -61,11 +61,14 @@ #define SUBMESSAGE_LEN 200 #define EXPLAIN_LINE_SIZE 1000 #define DEFAULT_MAX_LONG 32767 -#define DEFAULT_PREFETCH 200 +#define DEFAULT_PREFETCH 0 +#define DEFAULT_FETCHSZ 1 #define DEFAULT_BATCHSZ 100 #define TABLE_NAME_LEN 129 #define COLUMN_NAME_LEN 129 #define SQLSTATE_LEN 6 +#define DB2_MAX_ATTR_PREFETCH_NROWS 1024 +#define DB2_MAX_ATTR_ROW_ARRAY_SIZE 32768 #ifdef SQL_H_SQLCLI1 #include "HdlEntry.h" @@ -124,6 +127,7 @@ typedef enum { #define OPT_PREFETCH "prefetch" #define OPT_NO_ENCODING_ERROR "no_encoding_error" #define OPT_BATCH_SIZE "batch_size" +#define OPT_FETCHSZ "fetchsize" /* types for the DB2 table description */ typedef enum { diff --git a/source/db2AnalyzeForeignTable.c b/source/db2AnalyzeForeignTable.c index 7e4b25d..d8b9831 100644 --- a/source/db2AnalyzeForeignTable.c +++ b/source/db2AnalyzeForeignTable.c @@ -11,7 +11,7 @@ /** external prototypes */ extern DB2FdwState* db2GetFdwState (Oid foreigntableid, double* sample_percent, bool describe); extern int db2IsStatementOpen (DB2Session* session); -extern void db2PrepareQuery (DB2Session* session, const char* query, DB2Table* db2Table, unsigned long prefetch); +extern void db2PrepareQuery (DB2Session* session, const char *query, DB2Table* db2Table, unsigned long prefetch, int fetchsize); extern int db2ExecuteQuery (DB2Session* session, const DB2Table* db2Table, ParamDesc* paramList); extern int db2FetchNext (DB2Session* session); extern void checkDataType (short db2type, int scale, Oid pgtype, const char* tablename, const char* colname); @@ -126,7 +126,7 @@ int acquireSampleRowsFunc (Relation relation, int elevel, HeapTuple * rows, int db2Debug3(" loop through query results"); /* loop through query results */ - while (db2IsStatementOpen (fdw_state->session) ? db2FetchNext (fdw_state->session) : (db2PrepareQuery (fdw_state->session, fdw_state->query, fdw_state->db2Table, fdw_state->prefetch), db2ExecuteQuery (fdw_state->session, fdw_state->db2Table, fdw_state->paramList))) { + while (db2IsStatementOpen (fdw_state->session) ? db2FetchNext (fdw_state->session) : (db2PrepareQuery (fdw_state->session, fdw_state->query, fdw_state->db2Table, fdw_state->prefetch, fdw_state->fetch_size), db2ExecuteQuery (fdw_state->session, fdw_state->db2Table, fdw_state->paramList))) { /* allow user to interrupt ANALYZE */ #if PG_VERSION_NUM >= 180000 vacuum_delay_point (true); diff --git a/source/db2BeginForeignModifyCommon.c b/source/db2BeginForeignModifyCommon.c index f4f562d..65fab24 100644 --- a/source/db2BeginForeignModifyCommon.c +++ b/source/db2BeginForeignModifyCommon.c @@ -15,7 +15,7 @@ extern regproc* output_funcs; /** external prototypes */ extern DB2Session* db2GetSession (const char* connectstring, char* user, char* password, char* jwt_token, const char* nls_lang, int curlevel); -extern void db2PrepareQuery (DB2Session* session, const char* query, DB2Table* db2Table, unsigned long prefetch); +extern void db2PrepareQuery (DB2Session* session, const char *query, DB2Table* db2Table, unsigned long prefetch, int fetchsize); extern void db2Debug1 (const char* message, ...); extern void* db2alloc (const char* type, size_t size); @@ -33,7 +33,7 @@ void db2BeginForeignModifyCommon(ModifyTableState* mtstate, ResultRelInfo* rinfo /* connect to DB2 database */ fdw_state->session = db2GetSession(fdw_state->dbserver, fdw_state->user, fdw_state->password, fdw_state->jwt_token, fdw_state->nls_lang, GetCurrentTransactionNestLevel()); - db2PrepareQuery(fdw_state->session, fdw_state->query, fdw_state->db2Table,0); + db2PrepareQuery(fdw_state->session, fdw_state->query, fdw_state->db2Table,fdw_state->prefetch,fdw_state->fetch_size); /* get the type output functions for the parameters */ output_funcs = (regproc*) db2alloc("output_funcs", fdw_state->db2Table->ncols * sizeof(regproc *)); diff --git a/source/db2GetFdwState.c b/source/db2GetFdwState.c index 73bc445..0086b33 100644 --- a/source/db2GetFdwState.c +++ b/source/db2GetFdwState.c @@ -42,7 +42,8 @@ DB2FdwState* db2GetFdwState (Oid foreigntableid, double *sample_percent, bool de char* table = NULL; char* maxlong = NULL; char* sample = NULL; - char* fetch = NULL; + char* prefetch = NULL; + char* fetchsz = NULL; char* noencerr = NULL; char* batchsz = NULL; long max_long; @@ -74,7 +75,9 @@ DB2FdwState* db2GetFdwState (Oid foreigntableid, double *sample_percent, bool de if (strcmp (def->defname, OPT_SAMPLE) == 0) sample = STRVAL(def->arg); if (strcmp (def->defname, OPT_PREFETCH) == 0) - fetch = STRVAL(def->arg); + prefetch = STRVAL(def->arg); + if (strcmp (def->defname, OPT_FETCHSZ) == 0) + fetchsz = STRVAL(def->arg); if (strcmp (def->defname, OPT_NO_ENCODING_ERROR) == 0) noencerr = STRVAL(def->arg); if (strcmp (def->defname, OPT_BATCH_SIZE) == 0) @@ -96,12 +99,18 @@ DB2FdwState* db2GetFdwState (Oid foreigntableid, double *sample_percent, bool de } /* convert "prefetch" to number (or use default) */ - if (fetch == NULL) + if (prefetch == NULL) fdwState->prefetch = DEFAULT_PREFETCH; else - fdwState->prefetch = (unsigned long) strtoul (fetch, NULL, 0); + fdwState->prefetch = (unsigned long) strtoul (prefetch, NULL, 0); - /* check if options are ok */ + /* convert "fetchsize" to number (or use default) */ + if (fetchsz == NULL) + fdwState->fetch_size = DEFAULT_FETCHSZ; + else + fdwState->fetch_size = (int) strtol (fetchsz, NULL, 0); + + /* check if options are ok */ if (table == NULL) ereport (ERROR, (errcode (ERRCODE_FDW_OPTION_NAME_NOT_FOUND), errmsg ("required option \"%s\" in foreign table \"%s\" missing", OPT_TABLE, pgtablename))); diff --git a/source/db2IterateForeignScan.c b/source/db2IterateForeignScan.c index bc3c59d..c9235eb 100644 --- a/source/db2IterateForeignScan.c +++ b/source/db2IterateForeignScan.c @@ -11,7 +11,7 @@ /** external prototypes */ extern int db2IsStatementOpen (DB2Session* session); -extern void db2PrepareQuery (DB2Session* session, const char* query, DB2Table* db2Table, unsigned long prefetch); +extern void db2PrepareQuery (DB2Session* session, const char *query, DB2Table* db2Table, unsigned long prefetch, int fetchsize); extern int db2ExecuteQuery (DB2Session* session, const DB2Table* db2Table, ParamDesc* paramList); extern int db2FetchNext (DB2Session* session); extern void db2CloseStatement (DB2Session* session); @@ -50,7 +50,7 @@ TupleTableSlot* db2IterateForeignScan (ForeignScanState* node) { char* paramInfo = setSelectParameters (fdw_state->paramList, econtext); /* execute the DB2 statement and fetch the first row */ db2Debug3(" execute query in foreign table scan '%s'", paramInfo); - db2PrepareQuery (fdw_state->session, fdw_state->query, fdw_state->db2Table, fdw_state->prefetch); + db2PrepareQuery (fdw_state->session, fdw_state->query, fdw_state->db2Table, fdw_state->prefetch, fdw_state->fetch_size); have_result = db2ExecuteQuery (fdw_state->session, fdw_state->db2Table, fdw_state->paramList); have_result = db2FetchNext (fdw_state->session); } diff --git a/source/db2PrepareQuery.c b/source/db2PrepareQuery.c index 6018912..e6e5269 100644 --- a/source/db2PrepareQuery.c +++ b/source/db2PrepareQuery.c @@ -21,7 +21,7 @@ extern SQLSMALLINT c2param (SQLSMALLINT fparamType); extern char* param2name (SQLSMALLINT fparamType); /** internal prototypes */ -void db2PrepareQuery (DB2Session* session, const char *query, DB2Table* db2Table, unsigned long prefetch); +void db2PrepareQuery (DB2Session* session, const char *query, DB2Table* db2Table, unsigned long prefetch, int fetchsize); /** db2PrepareQuery * Prepares an SQL statement for execution. @@ -31,7 +31,7 @@ void db2PrepareQuery (DB2Session* session, const char *query * - For DML statements, allocates LOB locators for the RETURNING clause in db2Table. * - Set the prefetch options. */ -void db2PrepareQuery (DB2Session* session, const char *query, DB2Table* db2Table, unsigned long prefetch) { +void db2PrepareQuery (DB2Session* session, const char *query, DB2Table* db2Table, unsigned long prefetch, int fetchsize) { int i = 0; int col_pos = 0; int is_select = 0; @@ -55,7 +55,8 @@ void db2PrepareQuery (DB2Session* session, const char *query, DB2Table* db2Table db2Debug2(" session->stmtp->hsql: %d",session->stmtp->hsql); /* set prefetch options */ if (is_select) { - unsigned long prefetch_rows = prefetch; + SQLULEN prefetch_rows = prefetch; + SQLULEN cur_fetchsize = fetchsize; db2Debug3(" IS_SELECT"); if (for_update) { db2Debug3(" FOR UPDATE"); @@ -81,6 +82,13 @@ void db2PrepareQuery (DB2Session* session, const char *query, DB2Table* db2Table } db2Debug3(" set cursor static"); } + // Fetch rows per network roundtrip + rc = SQLSetStmtAttr(session->stmtp->hsql, SQL_ATTR_ROW_ARRAY_SIZE, (SQLPOINTER)cur_fetchsize, 0); + rc = db2CheckErr(rc, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); + if (rc != SQL_SUCCESS) { + db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error executing query: SQLSetStmtAttr failed to set fetchsize in statement handle", db2Message); + } + db2Debug2(" set cursor fetchsize: %d",cur_fetchsize); // Prefetch rows per block for scrollable (non-dynamic) cursors rc = SQLSetStmtAttr(session->stmtp->hsql, SQL_ATTR_PREFETCH_NROWS, (SQLPOINTER)prefetch_rows, 0); rc = db2CheckErr(rc, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); diff --git a/source/db2_fdw.c b/source/db2_fdw.c index 46b3034..6d6e4f8 100644 --- a/source/db2_fdw.c +++ b/source/db2_fdw.c @@ -64,6 +64,7 @@ DB2FdwOption valid_options[] = { {OPT_READONLY , ForeignTableRelationId , false}, {OPT_SAMPLE , ForeignTableRelationId , false}, {OPT_PREFETCH , ForeignTableRelationId , false}, + {OPT_FETCHSZ , ForeignTableRelationId , false}, {OPT_KEY , AttributeRelationId , false}, #if PG_VERSION_NUM >= 140000 {OPT_BATCH_SIZE , ForeignServerRelationId , false}, @@ -269,11 +270,24 @@ PGDLLEXPORT Datum db2_fdw_validator (PG_FUNCTION_ARGS) { char *val = STRVAL(def->arg); char *endptr; unsigned long prefetch = strtol (val, &endptr, 0); - if (val[0] == '\0' || *endptr != '\0' || prefetch < 0 || prefetch > 10240) + if (val[0] == '\0' || *endptr != '\0' || prefetch < 0 || prefetch > DB2_MAX_ATTR_PREFETCH_NROWS) ereport ( ERROR , ( errcode (ERRCODE_FDW_INVALID_ATTRIBUTE_VALUE) , errmsg ("invalid value for option \"%s\"", def->defname) - , errhint ("Valid values in this context are integers between 0 and 10240.") + , errhint ("Valid values in this context are integers between 0 and %d.", DB2_MAX_ATTR_PREFETCH_NROWS) + ) + ); + } + /* check valid values for "fetchsize" */ + if (strcmp (def->defname, OPT_FETCHSZ) == 0) { + char *val = STRVAL(def->arg); + char *endptr; + unsigned long fetchsz = strtol (val, &endptr, 0); + if (val[0] == '\0' || *endptr != '\0' || fetchsz < 0 || fetchsz > DB2_MAX_ATTR_ROW_ARRAY_SIZE) + ereport ( ERROR + , ( errcode (ERRCODE_FDW_INVALID_ATTRIBUTE_VALUE) + , errmsg ("invalid value for option \"%s\"", def->defname) + , errhint ("Valid values in this context are integers between 1 and %d.", DB2_MAX_ATTR_ROW_ARRAY_SIZE) ) ); } From ffd991ad0f0f630f15c50b01c530b42fd43a3ea7 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Mon, 26 Jan 2026 14:31:00 +0100 Subject: [PATCH 013/120] fix warn on cast SQLULEN to SQLPOINTER --- source/db2PrepareQuery.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/source/db2PrepareQuery.c b/source/db2PrepareQuery.c index e6e5269..fda3ff6 100644 --- a/source/db2PrepareQuery.c +++ b/source/db2PrepareQuery.c @@ -4,6 +4,8 @@ #include "db2_fdw.h" #include "ParamDesc.h" +#define SQL_VALUE_PTR_ULEN(v) ((SQLPOINTER)(uintptr_t)(SQLULEN)(v)) + /** global variables */ /** external variables */ @@ -38,9 +40,15 @@ void db2PrepareQuery (DB2Session* session, const char *query, DB2Table* db2Table int for_update = 0; SQLRETURN rc = 0; + #ifdef FIXED_FETCH_SIZE + // Until the proper handling of multiple rows results on a single query are added the fetch size must be 1 + fetchsize = 1; + #endif + db2Debug1("> db2PrepareQuery"); - db2Debug2(" query : '%s'",query); - db2Debug2(" prefetch: %d ",prefetch); + db2Debug2(" query : '%s'",query); + db2Debug2(" prefetch : %d",prefetch); + db2Debug2(" fetchsize: %d",fetchsize); /* figure out if the query is FOR UPDATE */ is_select = (strncmp (query, "SELECT", 6) == 0); for_update = (strstr (query, "FOR UPDATE") != NULL); @@ -83,14 +91,14 @@ void db2PrepareQuery (DB2Session* session, const char *query, DB2Table* db2Table db2Debug3(" set cursor static"); } // Fetch rows per network roundtrip - rc = SQLSetStmtAttr(session->stmtp->hsql, SQL_ATTR_ROW_ARRAY_SIZE, (SQLPOINTER)cur_fetchsize, 0); + rc = SQLSetStmtAttr(session->stmtp->hsql, SQL_ATTR_ROW_ARRAY_SIZE, SQL_VALUE_PTR_ULEN(cur_fetchsize), 0); rc = db2CheckErr(rc, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); if (rc != SQL_SUCCESS) { db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error executing query: SQLSetStmtAttr failed to set fetchsize in statement handle", db2Message); } db2Debug2(" set cursor fetchsize: %d",cur_fetchsize); // Prefetch rows per block for scrollable (non-dynamic) cursors - rc = SQLSetStmtAttr(session->stmtp->hsql, SQL_ATTR_PREFETCH_NROWS, (SQLPOINTER)prefetch_rows, 0); + rc = SQLSetStmtAttr(session->stmtp->hsql, SQL_ATTR_PREFETCH_NROWS, SQL_VALUE_PTR_ULEN(prefetch_rows), 0); rc = db2CheckErr(rc, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); if (rc != SQL_SUCCESS) { db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error executing query: SQLSetStmtAttr failed to set number of prefetched rows in statement handle", db2Message); From e96a453e2354894ffb7297c8d1adba09c468ddd5 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Mon, 26 Jan 2026 14:31:30 +0100 Subject: [PATCH 014/120] adding fetchsize to fdwState (de)serializing --- source/db2PlanForeignModify.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/db2PlanForeignModify.c b/source/db2PlanForeignModify.c index 640708d..fb96ab0 100644 --- a/source/db2PlanForeignModify.c +++ b/source/db2PlanForeignModify.c @@ -477,6 +477,8 @@ List* serializePlanData (DB2FdwState* fdwState) { result = lappend (result, serializeString (fdwState->query)); /* DB2 prefetch count */ result = lappend (result, serializeLong (fdwState->prefetch)); + /* DB2 fetchsize count */ + result = lappend (result, serializeLong (fdwState->fetch_size)); /* DB2 table name */ result = lappend (result, serializeString (fdwState->db2Table->name)); /* PostgreSQL table name */ From 36a797a2dc6be2d962156004c6e4fedfaec766f9 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Mon, 26 Jan 2026 14:32:06 +0100 Subject: [PATCH 015/120] addinf fetch_size to fdwState (de)serializing --- source/db2BeginForeignModify.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/source/db2BeginForeignModify.c b/source/db2BeginForeignModify.c index 5bced29..b71d85e 100644 --- a/source/db2BeginForeignModify.c +++ b/source/db2BeginForeignModify.c @@ -93,6 +93,10 @@ DB2FdwState* deserializePlanData (List* list) { state->prefetch = (unsigned long) DatumGetInt32 (((Const *) lfirst (cell))->constvalue); cell = list_next (list,cell); + /* DB2 prefetch count */ + state->fetch_size = (unsigned long) DatumGetInt32 (((Const *) lfirst (cell))->constvalue); + cell = list_next (list,cell); + /* table data */ state->db2Table = (DB2Table*) db2alloc ("state->db2Table", sizeof (struct db2Table)); state->db2Table->name = deserializeString (lfirst (cell)); From b7a247975657b8f8c03c6d972af4e3766a570bfa Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Mon, 26 Jan 2026 14:32:37 +0100 Subject: [PATCH 016/120] more preparations for push down of aggregates --- include/DB2FdwState.h | 102 +- include/db2_fdw.h | 52 +- source/db2GetForeignPlan.c | 80 +- source/db2GetForeignUpperPaths.c | 526 ++++++- source/db2_deparse.c | 2398 ++++++++++++++++++++++-------- source/db2_fdw_utils.c | 185 +++ 6 files changed, 2621 insertions(+), 722 deletions(-) diff --git a/include/DB2FdwState.h b/include/DB2FdwState.h index b84cc4b..4c3ec83 100644 --- a/include/DB2FdwState.h +++ b/include/DB2FdwState.h @@ -1,9 +1,8 @@ #ifndef DB2FDWSTATE_H #define DB2FDWSTATE_H -#ifndef PARAMDESC_H +#include #include "ParamDesc.h" -#endif /** DB2FdwState * FDW-specific information for RelOptInfo.fdw_private and ForeignScanState.fdw_state. @@ -13,46 +12,77 @@ * For DML statements, the scan stage and the modify stage both hold an * DB2FdwState, and the latter is initialized by copying the former (see copyPlanData). * + * FDW-specific planner information kept in RelOptInfo.fdw_private for a postgres_fdw foreign table. + * For a baserel, this struct is created by postgresGetForeignRelSize, although some fields are not filled till later. + * postgresGetForeignJoinPaths creates it for a joinrel, and postgresGetForeignUpperPaths creates it for an upperrel. + * * @author Ing Wolfgang Brandl * @since 1.0 */ typedef struct db2FdwState { - char* dbserver; // DB2 connect string - char* user; // DB2 username - char* password; // DB2 password - char* jwt_token; // JWT token for authentication (alternative to user/password) - char* nls_lang; // DB2 locale information - DB2Session* session; // encapsulates the active DB2 session - char* query; // query we issue against DB2 - List* params; // list of parameters needed for the query - ParamDesc* paramList; // description of parameters needed for the query - DB2Table* db2Table; // description of the remote DB2 table - Cost startup_cost; // cost estimate, only needed for planning - Cost total_cost; // cost estimate, only needed for planning - unsigned long rowcount; // rows already read from DB2 - int columnindex; // currently processed column for error context - MemoryContext temp_cxt; // short-lived memory for data modification - unsigned long prefetch; // number of rows to prefetch - char* order_clause; // for sort-pushdown - char* where_clause; // deparsed where clause - /** Restriction clauses, divided into safe and unsafe to pushdown subsets. - * - * For a base foreign relation this is a list of clauses along-with - * RestrictInfo wrapper. Keeping RestrictInfo wrapper helps while dividing - * scan_clauses in db2GetForeignPlan into safe and unsafe subsets. - * Also it helps in estimating costs since RestrictInfo caches the - * selectivity and qual cost for the clause in it. - * - * For a join relation, however, they are part of otherclause list - * obtained from extract_actual_join_clauses, which strips RestrictInfo - * construct. So, for a join relation they are list of bare clauses. +// DB2 Section + char* dbserver; // DB2 connect string + char* user; // DB2 username + char* password; // DB2 password + char* jwt_token; // JWT token for authentication (alternative to user/password) + char* nls_lang; // DB2 locale information + DB2Session* session; // encapsulates the active DB2 session + char* query; // query we issue against DB2 + List* params; // list of parameters needed for the query + ParamDesc* paramList; // description of parameters needed for the query + DB2Table* db2Table; // description of the remote DB2 table + unsigned long rowcount; // rows already read from DB2 + int columnindex; // currently processed column for error context + MemoryContext temp_cxt; // short-lived memory for data modification + unsigned long prefetch; // number of rows to prefetch (SQL_ATTR_PREFETCH_NROWS 0-1024) + char* order_clause; // for sort-pushdown + char* where_clause; // deparsed where clause +// attributes taken over from PgFdwRelationInfo + bool pushdown_safe; // True: relation can be pushed down. Always true for simple foreign scan. +// All entries in these lists should have RestrictInfo wrappers; that improves efficiency of selectivity and cost estimation. + List* remote_conds; // Restriction clauses, safe to pushdown subsets. + List* local_conds; // Restriction clauses, unsafe to pushdown subsets. + List* final_remote_exprs; // Actual remote restriction clauses for scan (sans RestrictInfos) + Bitmapset* attrs_used; // Bitmap of attr numbers we need to fetch from the remote server. + bool qp_is_pushdown_safe; // True means that the query_pathkeys is safe to push down + QualCost local_conds_cost; // Cost of local_conds. + Selectivity local_conds_sel; // Selectivity of local_conds. + Selectivity joinclause_sel; // Selectivity of join conditions + double rows; // Estimated size and cost for a scan, join, or grouping/aggregation. + int width; // Estimated size and cost for a scan, join, or grouping/aggregation. + int disabled_nodes; // Estimated size and cost for a scan, join, or grouping/aggregation. + Cost startup_cost; // Estimated size and cost for a scan, join, or grouping/aggregation. + Cost total_cost; // Estimated size and cost for a scan, join, or grouping/aggregation. +// These are only used by estimate_path_cost_size(). + double retrieved_rows; // Estimated number of rows fetched from the foreign server + Cost rel_startup_cost; // Estimated costs excluding costs for transferring those rows from the foreign server. + Cost rel_total_cost; // Estimated costs excluding costs for transferring those rows from the foreign server. +// Options extracted from catalogs. + bool use_remote_estimate; + Cost fdw_startup_cost; + Cost fdw_tuple_cost; + List* shippable_extensions; // OIDs of shippable extensions + bool async_capable; + ForeignTable* ftable; // Cached catalog foreign table information + ForeignServer* fserver; // Cached catalog foreign server information + UserMapping* fuser; // only set in use_remote_estimate mode + int fetch_size; // fetch size for this remote table (SQL_ATTR_ROW_ARRAY_SIZE 1 - 32767) + /* Name of the relation, for use while EXPLAINing ForeignScan. It is used for join and upper relations but is set for all relations. + * For a base relation, this is really just the RT index as a string; we convert that while producing EXPLAIN output. + * For join and upper relations, the name indicates which base foreign tables are included and the join type or aggregation type used. */ - List* remote_conds; - List* local_conds; - /* Join information */ - RelOptInfo* outerrel; + char* relation_name; + RelOptInfo* outerrel; // Join information RelOptInfo* innerrel; JoinType jointype; - List* joinclauses; + List* joinclauses; // joinclauses contains only JOIN/ON conditions for an outer join + UpperRelationKind stage; // Upper relation information + List* grouped_tlist; // Grouping information +// Subquery information + bool make_outerrel_subquery; // do we deparse outerrel as a subquery? + bool make_innerrel_subquery; // do we deparse innerrel as a subquery? + Relids lower_subquery_rels; // all relids appearing in lower subqueries + Relids hidden_subquery_rels; // unreferrabl relids from upper relations, used internally for equivalence member search + int relation_index; // Index of the relation. It is used to create an alias to a subquery representing the relation. } DB2FdwState; #endif \ No newline at end of file diff --git a/include/db2_fdw.h b/include/db2_fdw.h index eb533ce..7e9680f 100644 --- a/include/db2_fdw.h +++ b/include/db2_fdw.h @@ -1,12 +1,11 @@ -/*------------------------------------------------------------------------- +/*------------------------------------------------------------------------------- * * db2_fdw.h - * This header file contains all definitions that are shared by db2_fdw.c - * and db2_utils.c. - * It is necessary to split db2xa_fdw into two source files because - * PostgreSQL and DB2 headers cannot be #included at the same time. + * This header file contains all definitions that are shared by all source files. + * It is necessary to split db2xa_fdw into two source files because + * PostgreSQL and DB2 headers cannot be #included at the same time. * - *------------------------------------------------------------------------- + *------------------------------------------------------------------------------- */ #ifndef _db2_fdw_h_ #define _db2_fdw_h_ @@ -31,13 +30,6 @@ #define array_create_iterator(arr, slice_ndim) array_create_iterator(arr, slice_ndim, NULL) #define JOIN_API -/* the useful macro IS_SIMPLE_REL is defined in v10, backport */ -#ifndef IS_SIMPLE_REL -#define IS_SIMPLE_REL(rel) \ - ((rel)->reloptkind == RELOPT_BASEREL || \ - (rel)->reloptkind == RELOPT_OTHER_MEMBER_REL) -#endif - /* GetConfigOptionByName has a new signature from 9.6 on */ #define GetConfigOptionByName(name, varname) GetConfigOptionByName(name, varname, false) @@ -54,22 +46,32 @@ #endif /* POSTGRES_H */ /* db2_fdw version */ -#define DB2_FDW_VERSION "18.1.1" +#define DB2_FDW_VERSION "18.1.1" /* number of bytes to read per LOB chunk */ -#define LOB_CHUNK_SIZE 8192 -#define ERRBUFSIZE 2000 -#define SUBMESSAGE_LEN 200 -#define EXPLAIN_LINE_SIZE 1000 -#define DEFAULT_MAX_LONG 32767 -#define DEFAULT_PREFETCH 0 -#define DEFAULT_FETCHSZ 1 -#define DEFAULT_BATCHSZ 100 -#define TABLE_NAME_LEN 129 -#define COLUMN_NAME_LEN 129 -#define SQLSTATE_LEN 6 +#define LOB_CHUNK_SIZE 8192 +#define ERRBUFSIZE 2000 +#define SUBMESSAGE_LEN 200 +#define EXPLAIN_LINE_SIZE 1000 +#define DEFAULT_MAX_LONG 32767 +#define DEFAULT_PREFETCH 100 +#define DEFAULT_FETCHSZ 1 +#define FIXED_FETCH_SIZE 1 +#define DEFAULT_BATCHSZ 100 +#define TABLE_NAME_LEN 129 +#define COLUMN_NAME_LEN 129 +#define SQLSTATE_LEN 6 #define DB2_MAX_ATTR_PREFETCH_NROWS 1024 #define DB2_MAX_ATTR_ROW_ARRAY_SIZE 32768 +/* Default CPU cost to start up a foreign query. */ +#define DEFAULT_FDW_STARTUP_COST 100.0 + +/* Default CPU cost to process 1 row (above and beyond cpu_tuple_cost). */ +#define DEFAULT_FDW_TUPLE_COST 0.2 + +/* If no remote estimates, assume a sort costs 20% extra */ +#define DEFAULT_FDW_SORT_MULTIPLIER 1.2 + #ifdef SQL_H_SQLCLI1 #include "HdlEntry.h" #include "DB2ConnEntry.h" diff --git a/source/db2GetForeignPlan.c b/source/db2GetForeignPlan.c index 8cb3ad0..9cec5de 100644 --- a/source/db2GetForeignPlan.c +++ b/source/db2GetForeignPlan.c @@ -11,19 +11,21 @@ /** external prototypes */ extern List* serializePlanData (DB2FdwState* fdwState); -extern void deparseFromExprForRel (PlannerInfo* root, RelOptInfo* foreignrel, StringInfo buf, List** params_list); extern void checkDataType (short db2type, int scale, Oid pgtype, const char* tablename, const char* colname); extern void db2Debug1 (const char* message, ...); extern void db2Debug2 (const char* message, ...); extern void db2Debug3 (const char* message, ...); extern void db2free (void* p); extern char* db2strdup (const char* p); +extern char* deparseExpr (PlannerInfo* root, RelOptInfo* rel, Expr* expr, List** params); +extern char* get_jointype_name (JoinType jointype); +extern List* build_tlist_to_deparse (RelOptInfo* foreignrel); /** local prototypes */ ForeignScan* db2GetForeignPlan (PlannerInfo* root, RelOptInfo* foreignrel, Oid foreigntableid, ForeignPath* best_path, List* tlist, List* scan_clauses , Plan* outer_plan); static void createQuery (PlannerInfo* root, RelOptInfo* foreignrel, bool modify, List* query_pathkeys); static void getUsedColumns (Expr* expr, DB2Table* db2Table, int foreignrelid); -static List* build_tlist_to_deparse(RelOptInfo* foreignrel); +static void deparseFromExprForRel (PlannerInfo* root, RelOptInfo* foreignrel, StringInfo buf, List** params_list); /** db2GetForeignPlan * Construct a ForeignScan node containing the serialized DB2FdwState, @@ -498,19 +500,69 @@ static void getUsedColumns (Expr* expr, DB2Table* db2Table, int foreignrelid) { * The output targetlist contains the columns that need to be fetched from the * foreign server for the given relation. */ -static List* build_tlist_to_deparse (RelOptInfo* foreignrel) { - List* tlist = NIL; +//static List* build_tlist_to_deparse (RelOptInfo* foreignrel) { +// List* tlist = NIL; +// DB2FdwState* fdwState = (DB2FdwState*) foreignrel->fdw_private; +// +// db2Debug1("> build_tlist_to_deparse"); +// /* We require columns specified in foreignrel->reltarget->exprs and those required for evaluating the local conditions. */ +// tlist = add_to_flat_tlist (tlist, pull_var_clause ((Node *) foreignrel->reltarget->exprs, PVC_RECURSE_PLACEHOLDERS)); +// tlist = add_to_flat_tlist(tlist, pull_var_clause((Node*) fdwState->local_conds, PVC_RECURSE_PLACEHOLDERS)); +// db2Debug1("< build_tlist_to_deparse"); +// return tlist; +//} + +/** deparseFromExprForRel + * Construct FROM clause for given relation. + * The function constructs ... JOIN ... ON ... for join relation. For a base + * relation it just returns the table name. + * All tables get an alias based on the range table index. + */ +static void deparseFromExprForRel (PlannerInfo* root, RelOptInfo* foreignrel, StringInfo buf, List** params_list) { DB2FdwState* fdwState = (DB2FdwState*) foreignrel->fdw_private; - db2Debug1("> build_tlist_to_deparse"); - /* - * We require columns specified in foreignrel->reltarget->exprs and those - * required for evaluating the local conditions. - */ - tlist = add_to_flat_tlist (tlist, pull_var_clause ((Node *) foreignrel->reltarget->exprs, PVC_RECURSE_PLACEHOLDERS)); - tlist = add_to_flat_tlist (tlist, pull_var_clause ((Node *) fdwState->local_conds, PVC_RECURSE_PLACEHOLDERS)); + db2Debug1("> deparseFromExprForRel"); + db2Debug2(" buf: '%s",buf->data); + if (IS_SIMPLE_REL (foreignrel)) { + appendStringInfo (buf, "%s", fdwState->db2Table->name); + appendStringInfo (buf, " %s%d", REL_ALIAS_PREFIX, foreignrel->relid); + } else { + /* join relation */ + RelOptInfo* rel_o = fdwState->outerrel; + RelOptInfo* rel_i = fdwState->innerrel; + StringInfoData join_sql_o; + StringInfoData join_sql_i; + ListCell* lc = NULL; + bool is_first = true; + char* where = NULL; - db2Debug1("< build_tlist_to_deparse"); - return tlist; -} + /* Deparse outer relation */ + initStringInfo (&join_sql_o); + deparseFromExprForRel (root, rel_o, &join_sql_o, params_list); + + /* Deparse inner relation */ + initStringInfo (&join_sql_i); + deparseFromExprForRel (root, rel_i, &join_sql_i, params_list); + + // For a join relation FROM clause entry is deparsed as (outer relation) (inner relation) ON joinclauses + appendStringInfo (buf, "(%s %s JOIN %s ON ", join_sql_o.data, get_jointype_name (fdwState->jointype), join_sql_i.data); + /* we can only get here if the join is pushed down, so there are join clauses */ + Assert (fdwState->joinclauses); + + foreach (lc, fdwState->joinclauses) { + Expr *expr = (Expr *)lfirst(lc); + /* connect expressions with AND */ + if (!is_first) + appendStringInfo(buf, " AND "); + /* deparse and append a join condition */ + where = deparseExpr(root, foreignrel, expr, params_list); + appendStringInfo(buf, "%s", where); + is_first = false; + } + /* End the FROM clause entry. */ + appendStringInfo (buf, ")"); + } + db2Debug2(" buf: '%s'",buf->data); + db2Debug1("< deparseFromExprForRel"); +} \ No newline at end of file diff --git a/source/db2GetForeignUpperPaths.c b/source/db2GetForeignUpperPaths.c index 2239732..b33af11 100644 --- a/source/db2GetForeignUpperPaths.c +++ b/source/db2GetForeignUpperPaths.c @@ -2,29 +2,39 @@ #include #include #include +#include + #include +#include #include #include #include #include +#include #include #include +#include + #include "db2_fdw.h" #include "DB2FdwState.h" #include "DB2FdwPathExtraData.h" /** external prototypes */ -extern void db2Debug1 (const char* message, ...); -extern void db2Debug2 (const char* message, ...); -extern void db2Debug3 (const char* message, ...); -extern void* db2alloc (const char* type, size_t size); -extern char* db2strdup (const char* source); -extern void* db2free (void* p); -extern char* deparseExpr (PlannerInfo* root, RelOptInfo* foreignrel, Expr* expr, List** params); -extern bool is_shippable (Oid objectId, Oid classId, DB2FdwState* fpinfo); -extern bool is_foreign_param (PlannerInfo *root, RelOptInfo *baserel, Expr *expr); -extern bool is_foreign_expr (PlannerInfo *root, RelOptInfo *baserel, Expr *expr); -extern bool is_foreign_pathkey (PlannerInfo *root, RelOptInfo *baserel, PathKey *pathkey); +extern void db2Debug1 (const char* message, ...); +extern void db2Debug2 (const char* message, ...); +extern void db2Debug3 (const char* message, ...); +extern void* db2alloc (const char* type, size_t size); +extern char* db2strdup (const char* source); +extern void* db2free (void* p); +extern List* build_tlist_to_deparse (RelOptInfo* foreignrel); +extern char* deparseExpr (PlannerInfo* root, RelOptInfo* foreignrel, Expr* expr, List** params); +extern bool is_shippable (Oid objectId, Oid classId, DB2FdwState* fpinfo); +extern bool is_foreign_param (PlannerInfo *root, RelOptInfo *baserel, Expr *expr); +extern bool is_foreign_expr (PlannerInfo *root, RelOptInfo *baserel, Expr *expr); +extern bool is_foreign_pathkey (PlannerInfo *root, RelOptInfo *baserel, PathKey *pathkey); +extern void deparseSelectStmtForRel (StringInfo buf, PlannerInfo* root, RelOptInfo* rel,List* tlist, List* remote_conds, List* pathkeys, bool has_final_sort, bool has_limit, bool is_subquery, List** retrieved_attrs, List** params_list); +extern void classifyConditions (PlannerInfo* root, RelOptInfo* baserel, List* input_conds, List** remote_conds, List** local_conds); +extern EquivalenceMember* find_em_for_rel_target (PlannerInfo* root, EquivalenceClass* ec, RelOptInfo* rel); /** local prototypes */ void db2GetForeignUpperPaths (PlannerInfo *root, UpperRelationKind stage, RelOptInfo *input_rel, RelOptInfo *output_rel, void *extra); @@ -33,10 +43,13 @@ static DB2Table* db2CloneDb2TableForPlan (const DB2Table* src); static DB2Column* db2CloneDb2ColumnForPlan (const DB2Column* src); static bool db2_is_shippable (PlannerInfo* root, UpperRelationKind stage, RelOptInfo* input_rel, RelOptInfo* output_rel); static bool db2_is_shippable_expr (PlannerInfo* root, RelOptInfo* foreignrel, Expr* expr, const char* label); -static void add_foreign_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel, RelOptInfo *grouped_rel, GroupPathExtraData *extra); -static void add_foreign_ordered_paths (PlannerInfo *root, RelOptInfo *input_rel, RelOptInfo *ordered_rel); -static void add_foreign_final_paths (PlannerInfo *root, RelOptInfo *input_rel, RelOptInfo *final_rel, FinalPathExtraData *extra); -static bool foreign_grouping_ok (PlannerInfo *root, RelOptInfo *grouped_rel, Node *havingQual); +static void add_foreign_grouping_paths(PlannerInfo* root, RelOptInfo* input_rel, RelOptInfo* grouped_rel, GroupPathExtraData *extra); +static void add_foreign_ordered_paths (PlannerInfo* root, RelOptInfo* input_rel, RelOptInfo* ordered_rel); +static void add_foreign_final_paths (PlannerInfo* root, RelOptInfo* input_rel, RelOptInfo* final_rel, FinalPathExtraData *extra); +static void adjust_foreign_grouping_path_cost(PlannerInfo* root, List* pathkeys, double retrieved_rows, double width, double limit_tuples, int* p_disabled_nodes, Cost* p_startup_cost, Cost* p_run_cost); +static bool foreign_grouping_ok (PlannerInfo* root, RelOptInfo* grouped_rel, Node* havingQual); +static void estimate_path_cost_size (PlannerInfo* root, RelOptInfo* foreignrel, List* param_join_conds, List* pathkeys, DB2FdwPathExtraData* fpextra, double* p_rows, int* p_width, int* p_disabled_nodes, Cost* p_startup_cost, Cost* p_total_cost); +static void merge_fdw_options (DB2FdwState* fpinfo, const DB2FdwState* fpinfo_o, const DB2FdwState* fpinfo_i); void db2GetForeignUpperPaths(PlannerInfo *root, UpperRelationKind stage, RelOptInfo *input_rel, RelOptInfo *output_rel, void *extra) { db2Debug1("> %s::db2GetForeignUpperPaths",__FILE__); @@ -55,7 +68,7 @@ void db2GetForeignUpperPaths(PlannerInfo *root, UpperRelationKind stage, RelOptI db2Debug3(" query->hasSubLinks : %s", query->hasSubLinks ? "true" : "false"); db2Debug3(" query->hasRowSecurity : %s", query->hasRowSecurity ? "true" : "false"); - if (db2_is_shippable(root, stage, input_rel, output_rel)) { +// if (db2_is_shippable(root, stage, input_rel, output_rel)) { db2CloneFdwStateUpper(root, input_rel, output_rel); switch (stage) { @@ -120,9 +133,9 @@ void db2GetForeignUpperPaths(PlannerInfo *root, UpperRelationKind stage, RelOptI db2Debug2(" stage: %d - unknown", stage); break; } - } else { - db2Debug2(" stage and or functions are not shippable to DB2"); - } +// } else { +// db2Debug2(" stage and or functions are not shippable to DB2"); +// } } else { db2Debug2(" skipping this call"); db2Debug2(" root: %x", root); @@ -367,11 +380,14 @@ static void add_foreign_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel, fpinfo->outerrel = input_rel; // All of this is redundant since fpinfo is 1:1 clone of ifpinfo - // Copy foreign table, foreign server, user mapping, FDW options etc. details from the input relation's fpinfo. +// Copy foreign table, foreign server, user mapping, FDW options etc. details from the input relation's fpinfo. + fpinfo->ftable = ifpinfo->ftable; + fpinfo->fserver = ifpinfo->fserver; + fpinfo->fuser = ifpinfo->fuser; // fpinfo->db2Table = db2CloneDb2TableForPlan(ifpinfo->db2Table); // fpinfo->dbserver = db2strdup(ifpinfo->dbserver); // fpinfo->user = db2strdup(ifpinfo->user); -// merge_fdw_options(fpinfo, ifpinfo, NULL); + merge_fdw_options(fpinfo, ifpinfo, NULL); /** Assess if it is safe to push down aggregation and grouping. * @@ -441,9 +457,9 @@ static void add_foreign_ordered_paths(PlannerInfo *root, RelOptInfo *input_rel, fpinfo->outerrel = input_rel; // Copy foreign table, foreign server, user mapping, FDW options etc. details from the input relation's fpinfo. - fpinfo->table = ifpinfo->table; - fpinfo->server = ifpinfo->server; - fpinfo->user = ifpinfo->user; + fpinfo->ftable = ifpinfo->ftable; + fpinfo->fserver = ifpinfo->fserver; + fpinfo->fuser = ifpinfo->fuser; merge_fdw_options(fpinfo, ifpinfo, NULL); /** If the input_rel is a base or join relation, we would already have @@ -548,9 +564,9 @@ static void add_foreign_final_paths(PlannerInfo *root, RelOptInfo *input_rel, Re fpinfo->outerrel = input_rel; // Copy foreign table, foreign server, user mapping, FDW options etc. details from the input relation's fpinfo. - fpinfo->table = ifpinfo->table; - fpinfo->server = ifpinfo->server; - fpinfo->user = ifpinfo->user; + fpinfo->ftable = ifpinfo->ftable; + fpinfo->fserver = ifpinfo->fserver; + fpinfo->fuser = ifpinfo->fuser; merge_fdw_options(fpinfo, ifpinfo, NULL); /** If there is no need to add a LIMIT node, there might be a ForeignPath @@ -712,12 +728,35 @@ static void add_foreign_final_paths(PlannerInfo *root, RelOptInfo *input_rel, Re add_path(final_rel, (Path*) final_path); } +/* Adjust the cost estimates of a foreign grouping path to include the cost of generating properly-sorted output. */ +static void adjust_foreign_grouping_path_cost(PlannerInfo* root, List* pathkeys, double retrieved_rows, double width, double limit_tuples, int* p_disabled_nodes, Cost* p_startup_cost, Cost* p_run_cost) { + /* If the GROUP BY clause isn't sort-able, the plan chosen by the remote side is unlikely to generate properly-sorted output, so it would need + * an explicit sort; adjust the given costs with cost_sort(). + * Likewise, if the GROUP BY clause is sort-able but isn't a superset of the given pathkeys, adjust the costs with that function. + * Otherwise, adjust the costs by applying the same heuristic as for the scan or join case. + */ + if (!grouping_is_sortable(root->processed_groupClause) || !pathkeys_contained_in(pathkeys, root->group_pathkeys)) { + Path sort_path; /* dummy for result of cost_sort */ + + cost_sort(&sort_path, root, pathkeys, 0, *p_startup_cost + *p_run_cost, retrieved_rows, width, 0.0, work_mem, limit_tuples); + + *p_startup_cost = sort_path.startup_cost; + *p_run_cost = sort_path.total_cost - sort_path.startup_cost; + } else { + /* The default extra cost seems too large for foreign-grouping cases; add 1/4th of that default. */ + double sort_multiplier = 1.0 + (DEFAULT_FDW_SORT_MULTIPLIER - 1.0) * 0.25; + + *p_startup_cost *= sort_multiplier; + *p_run_cost *= sort_multiplier; + } +} + /* * Assess whether the aggregation, grouping and having operations can be pushed * down to the foreign server. As a side effect, save information we obtain in * this function to PgFdwRelationInfo of the input relation. */ -static bool foreign_grouping_ok(PlannerInfo *root, RelOptInfo *grouped_rel, Node *havingQual) { +static bool foreign_grouping_ok(PlannerInfo* root, RelOptInfo* grouped_rel, Node* havingQual) { Query* query = root->parse; DB2FdwState* fpinfo = (DB2FdwState*) grouped_rel->fdw_private; PathTarget* grouping_target = grouped_rel->reltarget; @@ -901,3 +940,434 @@ static bool foreign_grouping_ok(PlannerInfo *root, RelOptInfo *grouped_rel, Node fpinfo->relation_name = psprintf("Aggregate on (%s)", ofpinfo->relation_name); return true; } + +/** estimate_path_cost_size + * Get cost and size estimates for a foreign scan on given foreign relation + * either a base relation or a join between foreign relations or an upper + * relation containing foreign relations. + * + * param_join_conds are the parameterization clauses with outer relations. + * pathkeys specify the expected sort order if any for given path being costed. + * fpextra specifies additional post-scan/join-processing steps such as the + * final sort and the LIMIT restriction. + * + * The function returns the cost and size estimates in p_rows, p_width, + * p_disabled_nodes, p_startup_cost and p_total_cost variables. + */ +static void estimate_path_cost_size(PlannerInfo* root, RelOptInfo* foreignrel, List* param_join_conds, List* pathkeys, DB2FdwPathExtraData* fpextra, double* p_rows, int* p_width, int* p_disabled_nodes, Cost* p_startup_cost, Cost* p_total_cost) { + DB2FdwState* fpinfo = (DB2FdwState*) foreignrel->fdw_private; + double rows = 0; + double retrieved_rows = 0; + int width = 0; + int disabled_nodes = 0; + Cost startup_cost; + Cost total_cost; + + /* Make sure the core code has set up the relation's reltarget */ + Assert(foreignrel->reltarget); + + /* If the table or the server is configured to use remote estimates, connect to the foreign server and execute EXPLAIN to estimate the + * number of rows selected by the restriction+join clauses. Otherwise, estimate rows using whatever statistics we have locally, in a way + * similar to ordinary tables. + */ + if (fpinfo->use_remote_estimate) { + List* remote_param_join_conds; + List* local_param_join_conds; + StringInfoData sql; +// PGconn* conn; + Selectivity local_sel; + QualCost local_cost; + List* fdw_scan_tlist = NIL; + List* remote_conds; + List* retrieved_attrs; /* Required only to be passed to deparseSelectStmtForRel */ + /* param_join_conds might contain both clauses that are safe to send across, and clauses that aren't. */ + classifyConditions(root, foreignrel, param_join_conds, &remote_param_join_conds, &local_param_join_conds); + + /* Build the list of columns to be fetched from the foreign server. */ + fdw_scan_tlist = (IS_JOIN_REL(foreignrel) || IS_UPPER_REL(foreignrel)) ? build_tlist_to_deparse(foreignrel) : NIL; + + /* The complete list of remote conditions includes everything from baserestrictinfo plus any extra join_conds relevant to this + * particular path. + */ + remote_conds = list_concat(remote_param_join_conds, fpinfo->remote_conds); + + /* Construct EXPLAIN query including the desired SELECT, FROM, and WHERE clauses. Params and other-relation Vars are replaced by dummy + * values, so don't request params_list. + */ + initStringInfo(&sql); + appendStringInfoString(&sql, "EXPLAIN "); + deparseSelectStmtForRel( &sql, root, foreignrel, fdw_scan_tlist, remote_conds, pathkeys + , fpextra ? fpextra->has_final_sort : false + , fpextra ? fpextra->has_limit : false + , false, &retrieved_attrs, NULL); + + /* Get the remote estimate */ +// conn = GetConnection(fpinfo->user, false, NULL); +// get_remote_estimate(sql.data, conn, &rows, &width, &startup_cost, &total_cost); +// ReleaseConnection(conn); + retrieved_rows = rows; + + /* Factor in the selectivity of the locally-checked quals */ + local_sel = clauselist_selectivity(root, local_param_join_conds, foreignrel->relid, JOIN_INNER, NULL); + local_sel *= fpinfo->local_conds_sel; + rows = clamp_row_est(rows* local_sel); + + /* Add in the eval cost of the locally-checked quals */ + startup_cost += fpinfo->local_conds_cost.startup; + total_cost += fpinfo->local_conds_cost.per_tuple * retrieved_rows; + cost_qual_eval(&local_cost, local_param_join_conds, root); + startup_cost += local_cost.startup; + total_cost += local_cost.per_tuple * retrieved_rows; + + /* Add in tlist eval cost for each output row. In case of an aggregate, some of the tlist expressions such as grouping + * expressions will be evaluated remotely, so adjust the costs. + */ + startup_cost += foreignrel->reltarget->cost.startup; + total_cost += foreignrel->reltarget->cost.startup; + total_cost += foreignrel->reltarget->cost.per_tuple * rows; + if (IS_UPPER_REL(foreignrel)) { + QualCost tlist_cost; + + cost_qual_eval(&tlist_cost, fdw_scan_tlist, root); + + startup_cost -= tlist_cost.startup; + total_cost -= tlist_cost.startup; + total_cost -= tlist_cost.per_tuple * rows; + } + } else { + Cost run_cost = 0; + + /* We don't support join conditions in this mode (hence, no parameterized paths can be made). */ + Assert(param_join_conds == NIL); + /* + * We will come here again and again with different set of pathkeys or + * additional post-scan/join-processing steps that caller wants to + * cost. We don't need to calculate the cost/size estimates for the + * underlying scan, join, or grouping each time. Instead, use those + * estimates if we have cached them already. + */ + if (fpinfo->rel_startup_cost >= 0 && fpinfo->rel_total_cost >= 0) { + Assert(fpinfo->retrieved_rows >= 0); + + rows = fpinfo->rows; + retrieved_rows = fpinfo->retrieved_rows; + width = fpinfo->width; + startup_cost = fpinfo->rel_startup_cost; + run_cost = fpinfo->rel_total_cost - fpinfo->rel_startup_cost; + + /* If we estimate the costs of a foreign scan or a foreign join with additional post-scan/join-processing steps, the scan or + * join costs obtained from the cache wouldn't yet contain the eval costs for the final scan/join target, which would've been + * updated by apply_scanjoin_target_to_paths(); add the eval costs now. + */ + if (fpextra && !IS_UPPER_REL(foreignrel)) { + /* Shouldn't get here unless we have LIMIT */ + Assert(fpextra->has_limit); + Assert(foreignrel->reloptkind == RELOPT_BASEREL || foreignrel->reloptkind == RELOPT_JOINREL); + startup_cost += foreignrel->reltarget->cost.startup; + run_cost += foreignrel->reltarget->cost.per_tuple * rows; + } + } else if (IS_JOIN_REL(foreignrel)) { + DB2FdwState* fpinfo_i; + DB2FdwState* fpinfo_o; + QualCost join_cost; + QualCost remote_conds_cost; + double nrows; + + /* Use rows/width estimates made by the core code. */ + rows = foreignrel->rows; + width = foreignrel->reltarget->width; + + /* For join we expect inner and outer relations set */ + Assert(fpinfo->innerrel && fpinfo->outerrel); + + fpinfo_i = (DB2FdwState*) fpinfo->innerrel->fdw_private; + fpinfo_o = (DB2FdwState*) fpinfo->outerrel->fdw_private; + + /* Estimate of number of rows in cross product */ + nrows = fpinfo_i->rows * fpinfo_o->rows; + + /* Back into an estimate of the number of retrieved rows. Just in case this is nuts, clamp to at most nrows. */ + retrieved_rows = clamp_row_est(rows / fpinfo->local_conds_sel); + retrieved_rows = Min(retrieved_rows, nrows); + + /* The cost of foreign join is estimated as cost of generating rows for the joining relations + cost for applying quals on the rows. */ + /* Calculate the cost of clauses pushed down to the foreign server */ + cost_qual_eval(&remote_conds_cost, fpinfo->remote_conds, root); + /* Calculate the cost of applying join clauses */ + cost_qual_eval(&join_cost, fpinfo->joinclauses, root); + + /* Startup cost includes startup cost of joining relations and the startup cost for join and other clauses. We do not include the + * startup cost specific to join strategy (e.g. setting up hash tables) since we do not know what strategy the foreign server + * is going to use. + */ + startup_cost = fpinfo_i->rel_startup_cost + fpinfo_o->rel_startup_cost; + startup_cost += join_cost.startup; + startup_cost += remote_conds_cost.startup; + startup_cost += fpinfo->local_conds_cost.startup; + + /* Run time cost includes: + * 1. Run time cost (total_cost - startup_cost) of relations being joined + * 2. Run time cost of applying join clauses on the cross product of the joining relations. + * 3. Run time cost of applying pushed down other clauses on the result of join + * 4. Run time cost of applying nonpushable other clauses locally on the result fetched from the foreign server. + */ + run_cost = fpinfo_i->rel_total_cost - fpinfo_i->rel_startup_cost; + run_cost += fpinfo_o->rel_total_cost - fpinfo_o->rel_startup_cost; + run_cost += nrows * join_cost.per_tuple; + nrows = clamp_row_est(nrows * fpinfo->joinclause_sel); + run_cost += nrows * remote_conds_cost.per_tuple; + run_cost += fpinfo->local_conds_cost.per_tuple * retrieved_rows; + + /* Add in tlist eval cost for each output row */ + startup_cost += foreignrel->reltarget->cost.startup; + run_cost += foreignrel->reltarget->cost.per_tuple * rows; + } else if (IS_UPPER_REL(foreignrel)) { + RelOptInfo* outerrel = fpinfo->outerrel; + DB2FdwState* ofpinfo; + AggClauseCosts aggcosts = {0}; + double input_rows; + int numGroupCols; + double numGroups = 1; + + /* The upper relation should have its outer relation set */ + Assert(outerrel); + /* and that outer relation should have its reltarget set */ + Assert(outerrel->reltarget); + /* This cost model is mixture of costing done for sorted and hashed aggregates in cost_agg(). We are not sure which + * strategy will be considered at remote side, thus for simplicity, we put all startup related costs in startup_cost + * and all finalization and run cost are added in total_cost. + */ + ofpinfo = (DB2FdwState*) outerrel->fdw_private; + /* Get rows from input rel */ + input_rows = ofpinfo->rows; + /* Collect statistics about aggregates for estimating costs. */ + if (root->parse->hasAggs) { + get_agg_clause_costs(root, AGGSPLIT_SIMPLE, &aggcosts); + } + /* Get number of grouping columns and possible number of groups */ + numGroupCols = list_length(root->processed_groupClause); + numGroups = estimate_num_groups( root + , get_sortgrouplist_exprs( root->processed_groupClause + , fpinfo->grouped_tlist + ) + , input_rows, NULL, NULL + ); + + /* Get the retrieved_rows and rows estimates. If there are HAVING quals, account for their selectivity. */ + if (root->hasHavingQual) { + /* Factor in the selectivity of the remotely-checked quals */ + retrieved_rows = clamp_row_est( numGroups * clauselist_selectivity( root + , fpinfo->remote_conds + , 0 + , JOIN_INNER + , NULL + ) + ); + /* Factor in the selectivity of the locally-checked quals */ + rows = clamp_row_est(retrieved_rows * fpinfo->local_conds_sel); + } else { + rows = retrieved_rows = numGroups; + } + + /* Use width estimate made by the core code. */ + width = foreignrel->reltarget->width; + + /*----- + * Startup cost includes: + * 1. Startup cost for underneath input relation, adjusted for + * tlist replacement by apply_scanjoin_target_to_paths() + * 2. Cost of performing aggregation, per cost_agg() + *----- + */ + startup_cost = ofpinfo->rel_startup_cost; + startup_cost += outerrel->reltarget->cost.startup; + startup_cost += aggcosts.transCost.startup; + startup_cost += aggcosts.transCost.per_tuple * input_rows; + startup_cost += aggcosts.finalCost.startup; + startup_cost += (cpu_operator_cost * numGroupCols) * input_rows; + + /*----- + * Run time cost includes: + * 1. Run time cost of underneath input relation, adjusted for + * tlist replacement by apply_scanjoin_target_to_paths() + * 2. Run time cost of performing aggregation, per cost_agg() + *----- + */ + run_cost = ofpinfo->rel_total_cost - ofpinfo->rel_startup_cost; + run_cost += outerrel->reltarget->cost.per_tuple * input_rows; + run_cost += aggcosts.finalCost.per_tuple * numGroups; + run_cost += cpu_tuple_cost * numGroups; + + /* Account for the eval cost of HAVING quals, if any */ + if (root->hasHavingQual) + { + QualCost remote_cost; + + /* Add in the eval cost of the remotely-checked quals */ + cost_qual_eval(&remote_cost, fpinfo->remote_conds, root); + startup_cost += remote_cost.startup; + run_cost += remote_cost.per_tuple * numGroups; + /* Add in the eval cost of the locally-checked quals */ + startup_cost += fpinfo->local_conds_cost.startup; + run_cost += fpinfo->local_conds_cost.per_tuple * retrieved_rows; + } + + /* Add in tlist eval cost for each output row */ + startup_cost += foreignrel->reltarget->cost.startup; + run_cost += foreignrel->reltarget->cost.per_tuple * rows; + } else { + Cost cpu_per_tuple; + + /* Use rows/width estimates made by set_baserel_size_estimates. */ + rows = foreignrel->rows; + width = foreignrel->reltarget->width; + + /* Back into an estimate of the number of retrieved rows. Just in case this is nuts, clamp to at most foreignrel->tuples. */ + retrieved_rows = clamp_row_est(rows / fpinfo->local_conds_sel); + retrieved_rows = Min(retrieved_rows, foreignrel->tuples); + + /* Cost as though this were a seqscan, which is pessimistic. We effectively imagine the local_conds are being evaluated + * remotely, too. + */ + startup_cost = 0; + run_cost = 0; + run_cost += seq_page_cost * foreignrel->pages; + + startup_cost += foreignrel->baserestrictcost.startup; + cpu_per_tuple = cpu_tuple_cost + foreignrel->baserestrictcost.per_tuple; + run_cost += cpu_per_tuple * foreignrel->tuples; + + /* Add in tlist eval cost for each output row */ + startup_cost += foreignrel->reltarget->cost.startup; + run_cost += foreignrel->reltarget->cost.per_tuple * rows; + } + + /* Without remote estimates, we have no real way to estimate the cost of generating sorted output. It could be free if the query plan + * the remote side would have chosen generates properly-sorted output anyway, but in most cases it will cost something. Estimate a value + * high enough that we won't pick the sorted path when the ordering isn't locally useful, but low enough that we'll err on the side of + * pushing down the ORDER BY clause when it's useful to do so. + */ + if (pathkeys != NIL) { + if (IS_UPPER_REL(foreignrel)) { + Assert(foreignrel->reloptkind == RELOPT_UPPER_REL && fpinfo->stage == UPPERREL_GROUP_AGG); + /* We can only get here when this function is called from add_foreign_ordered_paths() or add_foreign_final_paths(); + * in which cases, the passed-in fpextra should not be NULL. + */ + Assert(fpextra); + adjust_foreign_grouping_path_cost( root + , pathkeys + , retrieved_rows + , width + , fpextra->limit_tuples + , &disabled_nodes + , &startup_cost, &run_cost + ); + } else { + startup_cost *= DEFAULT_FDW_SORT_MULTIPLIER; + run_cost *= DEFAULT_FDW_SORT_MULTIPLIER; + } + } + total_cost = startup_cost + run_cost; + /* Adjust the cost estimates if we have LIMIT */ + if (fpextra && fpextra->has_limit) { + adjust_limit_rows_costs(&rows, &startup_cost, &total_cost, fpextra->offset_est, fpextra->count_est); + retrieved_rows = rows; + } + } + + /* If this includes the final sort step, the given target, which will be applied to the resulting path, might have different expressions from + * the foreignrel's reltarget (see make_sort_input_target()); adjust tlist eval costs. + */ + if (fpextra && fpextra->has_final_sort && fpextra->target != foreignrel->reltarget) { + QualCost oldcost = foreignrel->reltarget->cost; + QualCost newcost = fpextra->target->cost; + + startup_cost += newcost.startup - oldcost.startup; + total_cost += newcost.startup - oldcost.startup; + total_cost += (newcost.per_tuple - oldcost.per_tuple) * rows; + } + + /* Cache the retrieved rows and cost estimates for scans, joins, or groupings without any parameterization, pathkeys, or additional + * post-scan/join-processing steps, before adding the costs for transferring data from the foreign server. These estimates are useful + * for costing remote joins involving this relation or costing other remote operations on this relation such as remote sorts and remote + * LIMIT restrictions, when the costs can not be obtained from the foreign server. This function will be called at least once for every foreign + * relation without any parameterization, pathkeys, or additional post-scan/join-processing steps. + */ + if (pathkeys == NIL && param_join_conds == NIL && fpextra == NULL) { + fpinfo->retrieved_rows = retrieved_rows; + fpinfo->rel_startup_cost = startup_cost; + fpinfo->rel_total_cost = total_cost; + } + + /* Add some additional cost factors to account for connection overhead (fdw_startup_cost), transferring data across the network + * (fdw_tuple_cost per retrieved row), and local manipulation of the data (cpu_tuple_cost per retrieved row). + */ + startup_cost += fpinfo->fdw_startup_cost; + total_cost += fpinfo->fdw_startup_cost; + total_cost += fpinfo->fdw_tuple_cost * retrieved_rows; + total_cost += cpu_tuple_cost * retrieved_rows; + + /* If we have LIMIT, we should prefer performing the restriction remotely rather than locally, as the former avoids extra row fetches from the + * remote that the latter might cause. But since the core code doesn't account for such fetches when estimating the costs of the local + * restriction (see create_limit_path()), there would be no difference between the costs of the local restriction and the costs of the remote + * restriction estimated above if we don't use remote estimates (except for the case where the foreignrel is a grouping relation, the given + * pathkeys is not NIL, and the effects of a bounded sort for that rel is accounted for in costing the remote restriction). Tweak the costs of + * the remote restriction to ensure we'll prefer it if LIMIT is a useful one. + */ + if (!fpinfo->use_remote_estimate && fpextra && fpextra->has_limit && fpextra->limit_tuples > 0 && fpextra->limit_tuples < fpinfo->rows) { + Assert(fpinfo->rows > 0); + total_cost -= (total_cost - startup_cost) * 0.05 * (fpinfo->rows - fpextra->limit_tuples) / fpinfo->rows; + } + + /* Return results. */ + *p_rows = rows; + *p_width = width; + *p_disabled_nodes = disabled_nodes; + *p_startup_cost = startup_cost; + *p_total_cost = total_cost; +} + +/** Merge FDW options from input relations into a new set of options for a join or an upper rel. + * + * For a join relation, FDW-specific information about the inner and outer + * relations is provided using fpinfo_i and fpinfo_o. For an upper relation, + * fpinfo_o provides the information for the input relation; fpinfo_i is + * expected to NULL. + */ +static void merge_fdw_options(DB2FdwState* fpinfo, const DB2FdwState* fpinfo_o, const DB2FdwState* fpinfo_i) { + /* We must always have fpinfo_o. */ + Assert(fpinfo_o); + + /* fpinfo_i may be NULL, but if present the servers must both match. */ + Assert(!fpinfo_i || fpinfo_i->server->serverid == fpinfo_o->server->serverid); + + /* Copy the server specific FDW options. + * (For a join, both relations come from the same server, so the server options should have the same value for both relations.) + */ + fpinfo->fdw_startup_cost = fpinfo_o->fdw_startup_cost; + fpinfo->fdw_tuple_cost = fpinfo_o->fdw_tuple_cost; + fpinfo->shippable_extensions = fpinfo_o->shippable_extensions; + fpinfo->use_remote_estimate = fpinfo_o->use_remote_estimate; + fpinfo->fetch_size = fpinfo_o->fetch_size; + fpinfo->async_capable = fpinfo_o->async_capable; + + /* Merge the table level options from either side of the join. */ + if (fpinfo_i) { + /* We'll prefer to use remote estimates for this join if any table from either side of the join is using remote estimates. This is + * most likely going to be preferred since they're already willing to pay the price of a round trip to get the remote EXPLAIN. In any + * case it's not entirely clear how we might otherwise handle this best. + */ + fpinfo->use_remote_estimate = fpinfo_o->use_remote_estimate || fpinfo_i->use_remote_estimate; + + /* Set fetch size to maximum of the joining sides, since we are expecting the rows returned by the join to be proportional to the + * relation sizes. + */ + fpinfo->fetch_size = Max(fpinfo_o->fetch_size, fpinfo_i->fetch_size); + + /* We'll prefer to consider this join async-capable if any table from either side of the join is considered async-capable. This would be + * reasonable because in that case the foreign server would have its own resources to scan that table asynchronously, and the join could + * also be computed asynchronously using the resources. + */ + fpinfo->async_capable = fpinfo_o->async_capable || fpinfo_i->async_capable; + } +} diff --git a/source/db2_deparse.c b/source/db2_deparse.c index f2fa175..690a267 100644 --- a/source/db2_deparse.c +++ b/source/db2_deparse.c @@ -18,6 +18,8 @@ #include #include +#include + #include #include #include @@ -25,6 +27,7 @@ #include #include +#include #include #include @@ -42,8 +45,16 @@ #include "db2_fdw.h" #include "DB2FdwState.h" -/** Global context for foreign_expr_walker's search of an expression tree. +/** This macro is used by deparseExpr to identify PostgreSQL + * types that can be translated to DB2 SQL. */ +#define canHandleType(x) ((x) == TEXTOID || (x) == CHAROID || (x) == BPCHAROID \ + || (x) == VARCHAROID || (x) == NAMEOID || (x) == INT8OID || (x) == INT2OID \ + || (x) == INT4OID || (x) == OIDOID || (x) == FLOAT4OID || (x) == FLOAT8OID \ + || (x) == NUMERICOID || (x) == DATEOID || (x) == TIMEOID || (x) == TIMESTAMPOID \ + || (x) == TIMESTAMPTZOID || (x) == INTERVALOID) + +/* Global context for foreign_expr_walker's search of an expression tree. */ typedef struct foreign_glob_cxt { PlannerInfo* root; /* global planner state */ RelOptInfo* foreignrel; /* the foreign relation we are planning for */ @@ -74,24 +85,58 @@ typedef struct deparse_expr_cxt { } deparse_expr_cxt; /** external prototypes */ -extern short c2dbType (short fcType); -extern void db2Debug1 (const char* message, ...); -extern void db2Debug2 (const char* message, ...); -extern void* db2alloc (const char* type, size_t size); -extern void* db2strdup (const char* source); -extern void db2free (void* p); -extern bool is_shippable (Oid objectId, Oid classId, DB2FdwState* fpinfo); -extern EquivalenceMember* find_em_for_rel(PlannerInfo *root, EquivalenceClass *ec, RelOptInfo *rel); +extern short c2dbType (short fcType); +extern void db2Debug1 (const char* message, ...); +extern void db2Debug2 (const char* message, ...); +extern void* db2alloc (const char* type, size_t size); +extern void* db2strdup (const char* source); +extern void db2free (void* p); +extern bool is_shippable (Oid objectId, Oid classId, DB2FdwState* fpinfo); +extern EquivalenceMember* find_em_for_rel (PlannerInfo* root, EquivalenceClass* ec, RelOptInfo* rel); +extern EquivalenceMember* find_em_for_rel_target (PlannerInfo* root, EquivalenceClass* ec, RelOptInfo* rel); +extern void reset_transmission_modes (int nestlevel); +extern int set_transmission_modes (void); +extern bool is_builtin (Oid objectId); + /** local prototypes */ -bool is_foreign_expr (PlannerInfo *root, RelOptInfo *baserel, Expr *expr); -static bool foreign_expr_walker (Node *node, foreign_glob_cxt *glob_cxt, foreign_loc_cxt *outer_cxt, foreign_loc_cxt *case_arg_cxt); -bool is_foreign_param (PlannerInfo *root, RelOptInfo *baserel, Expr *expr); -bool is_foreign_pathkey (PlannerInfo *root, RelOptInfo *baserel, PathKey *pathkey); void appendAsType (StringInfoData* dest, Oid type); -void deparseFromExprForRel (PlannerInfo* root, RelOptInfo* foreignrel, StringInfo buf, List** params_list); +List* build_tlist_to_deparse (RelOptInfo* foreignrel); +void classifyConditions (PlannerInfo* root, RelOptInfo* baserel, List* input_conds, List** remote_conds, List** local_conds); +void deparseSelectStmtForRel (StringInfo buf, PlannerInfo* root, RelOptInfo* rel,List* tlist, List* remote_conds, List* pathkeys, bool has_final_sort, bool has_limit, bool is_subquery, List** retrieved_attrs, List** params_list); char* deparseWhereConditions (PlannerInfo* root, RelOptInfo* rel); +void deparseTruncateSql (StringInfo buf, List* rels, DropBehavior behavior, bool restart_seqs); char* deparseExpr (PlannerInfo* root, RelOptInfo* rel, Expr* expr, List** params); +void deparseStringLiteral (StringInfo buf, const char* val); +char* deparseDate (Datum datum); +char* deparseTimestamp (Datum datum, bool hasTimezone); +bool is_foreign_expr (PlannerInfo* root, RelOptInfo* baserel, Expr* expr); +bool is_foreign_param (PlannerInfo* root, RelOptInfo* baserel, Expr* expr); +bool is_foreign_pathkey (PlannerInfo* root, RelOptInfo* baserel, PathKey* pathkey); +char* get_jointype_name (JoinType jointype); +EquivalenceMember* find_em_for_rel (PlannerInfo* root, EquivalenceClass* ec, RelOptInfo* rel); +EquivalenceMember* find_em_for_rel_target (PlannerInfo* root, EquivalenceClass* ec, RelOptInfo* rel); + + +/** local helper (static) prototypes */ +static void appendGroupByClause (List* tlist, deparse_expr_cxt* context); +static void appendOrderByClause (List* pathkeys, bool has_final_sort, deparse_expr_cxt* context); +static void appendLimitClause (deparse_expr_cxt* context); +static void appendFunctionName (Oid funcid, deparse_expr_cxt* context); +static void appendOrderBySuffix (Oid sortop, Oid sortcoltype, bool nulls_first, deparse_expr_cxt* context); +static void appendConditions (List* exprs, deparse_expr_cxt* context); +static void appendWhereClause (List* exprs, List* additional_conds, deparse_expr_cxt* context); + +static char* datumToString (Datum datum, Oid type); +static char* deparse_type_name (Oid type_oid, int32 typemod); +static Node* deparseSortGroupClause (Index ref, List* tlist, bool force_colno, deparse_expr_cxt* context); +static void deparseRangeTblRef (StringInfo buf, PlannerInfo* root, RelOptInfo* foreignrel, bool make_subquery, Index ignore_rel, List** ignore_conds, List* *additional_conds, List** params_list); +static void deparseSelectSql (List* tlist, bool is_subquery, List** retrieved_attrs, deparse_expr_cxt* context); +static void deparseFromExpr (List* quals, deparse_expr_cxt* context); +static void deparseFromExprForRel (StringInfo buf, PlannerInfo* root, RelOptInfo* foreignrel, bool use_alias, Index ignore_rel, List** ignore_conds, List** additional_conds, List** params_list); +//void deparseFromExprForRel (PlannerInfo* root, RelOptInfo* foreignrel, StringInfo buf, List** params_list); +static void deparseColumnRef (StringInfo buf, int varno, int varattno, RangeTblEntry* rte, bool qualify_col); +static void deparseRelation (StringInfo buf, Relation rel); static void deparseExprInt (Expr* expr, deparse_expr_cxt* ctx); static void deparseConstExpr (Const* expr, deparse_expr_cxt* ctx); static void deparseParamExpr (Param* expr, deparse_expr_cxt* ctx); @@ -108,11 +153,43 @@ static void deparseFuncExpr (FuncExpr* expr, deparse_ static void deparseAggref (Aggref* expr, deparse_expr_cxt* ctx); static void deparseCoerceViaIOExpr (CoerceViaIO* expr, deparse_expr_cxt* ctx); static void deparseSQLValueFuncExpr (SQLValueFunction* expr, deparse_expr_cxt* ctx); -static char* datumToString (Datum datum, Oid type); -char* deparseDate (Datum datum); -char* deparseTimestamp (Datum datum, bool hasTimezone); +static void deparseVar (Var* node, deparse_expr_cxt* context); +static void deparseConst (Const* node, deparse_expr_cxt* context, int showtype); +static void deparseOperatorName (StringInfo buf, Form_pg_operator opform); +static void deparseLockingClause (deparse_expr_cxt* context); +static void deparseTargetList (StringInfo buf, RangeTblEntry* rte, Index rtindex, Relation rel, bool is_returning, Bitmapset* attrs_used, bool qualify_col, List** retrieved_attrs); +static void deparseExplicitTargetList (List* tlist, bool is_returning, List** retrieved_attrs, deparse_expr_cxt* context); +static void deparseSubqueryTargetList (deparse_expr_cxt* context); static char* deparseInterval (Datum datum); -static const char* get_jointype_name (JoinType jointype); + +static bool foreign_expr_walker (Node *node, foreign_glob_cxt* glob_cxt, foreign_loc_cxt* outer_cxt, foreign_loc_cxt* case_arg_cxt); + +static void get_relation_column_alias_ids(Var* node, RelOptInfo* foreignrel, int* relno, int* colno); + +static bool is_subquery_var (Var* node, RelOptInfo* foreignrel, int* relno, int* colno); + +static void printRemoteParam (int paramindex, Oid paramtype, int32 paramtypmod, deparse_expr_cxt* context); +static void printRemotePlaceholder (Oid paramtype, int32 paramtypmod, deparse_expr_cxt* context); + +/* Examine each qual clause in input_conds, and classify them into two groups, which are returned as two lists: + * - remote_conds contains expressions that can be evaluated remotely + * - local_conds contains expressions that can't be evaluated remotely + */ +void classifyConditions(PlannerInfo* root, RelOptInfo* baserel, List* input_conds, List** remote_conds, List** local_conds) { + ListCell* lc; + + *remote_conds = NIL; + *local_conds = NIL; + + foreach(lc, input_conds) { + RestrictInfo* ri = lfirst_node(RestrictInfo, lc); + + if (is_foreign_expr(root, baserel, ri->clause)) + *remote_conds = lappend(*remote_conds, ri); + else + *local_conds = lappend(*local_conds, ri); + } +} /** Returns true if given expr is safe to evaluate on the foreign server. */ @@ -122,32 +199,23 @@ bool is_foreign_expr(PlannerInfo *root, RelOptInfo *baserel, Expr *expr) { DB2FdwState* fpinfo = (DB2FdwState*) (baserel->fdw_private); bool bResult = false; - /** Check that the expression consists of nodes that are safe to execute - * remotely. - */ + // Check that the expression consists of nodes that are safe to execute remotely. glob_cxt.root = root; glob_cxt.foreignrel = baserel; - - /* - * For an upper relation, use relids from its underneath scan relation, - * because the upperrel's own relids currently aren't set to anything + /* For an upper relation, use relids from its underneath scan relation, because the upperrel's own relids currently aren't set to anything * meaningful by the core code. For other relation, use their own relids. */ glob_cxt.relids = (IS_UPPER_REL(baserel)) ? fpinfo->outerrel->relids : baserel->relids; loc_cxt.collation = InvalidOid; loc_cxt.state = FDW_COLLATE_NONE; if (foreign_expr_walker((Node*) expr, &glob_cxt, &loc_cxt, NULL)) { - /** If the expression has a valid collation that does not arise from a - * foreign var, the expression can not be sent over. - */ + // If the expression has a valid collation that does not arise from a foreign var, the expression can not be sent over. if (loc_cxt.state != FDW_COLLATE_UNSAFE) { - /** An expression which includes any mutable functions can't be sent over - * because its result is not stable. For example, sending now() remote - * side could cause confusion from clock offsets. Future versions might - * be able to make this choice with more granularity. (We check this last - * because it requires a lot of expensive catalog lookups.) + /* An expression which includes any mutable functions can't be sent over because its result is not stable. + * For example, sending now() remote side could cause confusion from clock offsets. + * Future versions might be able to make this choice with more granularity. (We check this last because it requires a lot of expensive catalog lookups.) */ - if (!contain_mutable_functions((Node *) expr)) { + if (!contain_mutable_functions((Node*) expr)) { bResult = true; } } @@ -172,24 +240,20 @@ bool is_foreign_expr(PlannerInfo *root, RelOptInfo *baserel, Expr *expr) { * that the given expression is valid. Note function mutability is not * currently considered here. */ -static bool foreign_expr_walker(Node *node, foreign_glob_cxt *glob_cxt, foreign_loc_cxt *outer_cxt, foreign_loc_cxt *case_arg_cxt) { +static bool foreign_expr_walker(Node* node, foreign_glob_cxt* glob_cxt, foreign_loc_cxt* outer_cxt, foreign_loc_cxt* case_arg_cxt) { bool check_type = true; DB2FdwState* fpinfo = NULL; foreign_loc_cxt inner_cxt; Oid collation; FDWCollateState state; - /* Need do nothing for empty subexpressions */ if (node == NULL) return true; - /* May need server info from baserel's fdw_private struct */ fpinfo = (DB2FdwState*) glob_cxt->foreignrel->fdw_private; - /* Set up inner_cxt for possible recursion to child nodes */ inner_cxt.collation = InvalidOid; - inner_cxt.state = FDW_COLLATE_NONE; - + inner_cxt.state = FDW_COLLATE_NONE; switch (nodeTag(node)) { case T_Var: { Var *var = (Var *) node; @@ -316,543 +380,356 @@ static bool foreign_expr_walker(Node *node, foreign_glob_cxt *glob_cxt, foreign_ state = (collation == InvalidOid || collation == DEFAULT_COLLATION_OID) ? FDW_COLLATE_NONE : FDW_COLLATE_UNSAFE; } break; - case T_SubscriptingRef: - { - SubscriptingRef *sr = (SubscriptingRef *) node; - - /* Assignment should not be in restrictions. */ - if (sr->refassgnexpr != NULL) - return false; - - /* - * Recurse into the remaining subexpressions. The container - * subscripts will not affect collation of the SubscriptingRef - * result, so do those first and reset inner_cxt afterwards. - */ - if (!foreign_expr_walker((Node *) sr->refupperindexpr, - glob_cxt, &inner_cxt, case_arg_cxt)) - return false; - inner_cxt.collation = InvalidOid; - inner_cxt.state = FDW_COLLATE_NONE; - if (!foreign_expr_walker((Node *) sr->reflowerindexpr, - glob_cxt, &inner_cxt, case_arg_cxt)) - return false; - inner_cxt.collation = InvalidOid; - inner_cxt.state = FDW_COLLATE_NONE; - if (!foreign_expr_walker((Node *) sr->refexpr, - glob_cxt, &inner_cxt, case_arg_cxt)) - return false; - - /* - * Container subscripting typically yields same collation as - * refexpr's, but in case it doesn't, use same logic as for - * function nodes. - */ - collation = sr->refcollid; - if (collation == InvalidOid) - state = FDW_COLLATE_NONE; - else if (inner_cxt.state == FDW_COLLATE_SAFE && - collation == inner_cxt.collation) - state = FDW_COLLATE_SAFE; - else if (collation == DEFAULT_COLLATION_OID) - state = FDW_COLLATE_NONE; - else - state = FDW_COLLATE_UNSAFE; - } - break; - case T_FuncExpr: - { - FuncExpr *fe = (FuncExpr *) node; - - /* - * If function used by the expression is not shippable, it - * can't be sent to remote because it might have incompatible - * semantics on remote side. - */ - if (!is_shippable(fe->funcid, ProcedureRelationId, fpinfo)) - return false; + case T_SubscriptingRef: { + SubscriptingRef *sr = (SubscriptingRef *) node; + // Assignment should not be in restrictions. + if (sr->refassgnexpr != NULL) + return false; + // Recurse into the remaining subexpressions. + // The container subscripts will not affect collation of the SubscriptingRef result, so do those first and reset inner_cxt afterwards. + if (!foreign_expr_walker((Node *) sr->refupperindexpr, glob_cxt, &inner_cxt, case_arg_cxt)) + return false; + inner_cxt.collation = InvalidOid; + inner_cxt.state = FDW_COLLATE_NONE; + if (!foreign_expr_walker((Node *) sr->reflowerindexpr, glob_cxt, &inner_cxt, case_arg_cxt)) + return false; + inner_cxt.collation = InvalidOid; + inner_cxt.state = FDW_COLLATE_NONE; + if (!foreign_expr_walker((Node *) sr->refexpr, glob_cxt, &inner_cxt, case_arg_cxt)) + return false; + // Container subscripting typically yields same collation as refexpr's, but in case it doesn't, use same logic as for function nodes. + collation = sr->refcollid; + if (collation == InvalidOid) + state = FDW_COLLATE_NONE; + else if (inner_cxt.state == FDW_COLLATE_SAFE && collation == inner_cxt.collation) + state = FDW_COLLATE_SAFE; + else if (collation == DEFAULT_COLLATION_OID) + state = FDW_COLLATE_NONE; + else + state = FDW_COLLATE_UNSAFE; + } + break; + case T_FuncExpr: { + FuncExpr* fe = (FuncExpr*) node; - /* - * Recurse to input subexpressions. - */ - if (!foreign_expr_walker((Node *) fe->args, - glob_cxt, &inner_cxt, case_arg_cxt)) - return false; + /* If function used by the expression is not shippable, it can't be sent to remote because it might have incompatible + * semantics on remote side. + */ + if (!is_shippable(fe->funcid, ProcedureRelationId, fpinfo)) + return false; - /* - * If function's input collation is not derived from a foreign - * Var, it can't be sent to remote. - */ - if (fe->inputcollid == InvalidOid) - /* OK, inputs are all noncollatable */ ; - else if (inner_cxt.state != FDW_COLLATE_SAFE || - fe->inputcollid != inner_cxt.collation) - return false; - - /* - * Detect whether node is introducing a collation not derived - * from a foreign Var. (If so, we just mark it unsafe for now - * rather than immediately returning false, since the parent - * node might not care.) - */ - collation = fe->funccollid; - if (collation == InvalidOid) - state = FDW_COLLATE_NONE; - else if (inner_cxt.state == FDW_COLLATE_SAFE && - collation == inner_cxt.collation) - state = FDW_COLLATE_SAFE; - else if (collation == DEFAULT_COLLATION_OID) - state = FDW_COLLATE_NONE; - else - state = FDW_COLLATE_UNSAFE; - } - break; - case T_OpExpr: - case T_DistinctExpr: /* struct-equivalent to OpExpr */ - { - OpExpr *oe = (OpExpr *) node; - - /* - * Similarly, only shippable operators can be sent to remote. - * (If the operator is shippable, we assume its underlying - * function is too.) - */ - if (!is_shippable(oe->opno, OperatorRelationId, fpinfo)) - return false; + // Recurse to input subexpressions. + if (!foreign_expr_walker((Node *) fe->args, glob_cxt, &inner_cxt, case_arg_cxt)) + return false; - /* - * Recurse to input subexpressions. - */ - if (!foreign_expr_walker((Node *) oe->args, - glob_cxt, &inner_cxt, case_arg_cxt)) - return false; + // If function's input collation is not derived from a foreign Var, it can't be sent to remote. + if (fe->inputcollid == InvalidOid) + /* OK, inputs are all noncollatable */ ; + else if (inner_cxt.state != FDW_COLLATE_SAFE || fe->inputcollid != inner_cxt.collation) + return false; - /* - * If operator's input collation is not derived from a foreign - * Var, it can't be sent to remote. - */ - if (oe->inputcollid == InvalidOid) - /* OK, inputs are all noncollatable */ ; - else if (inner_cxt.state != FDW_COLLATE_SAFE || - oe->inputcollid != inner_cxt.collation) - return false; - - /* Result-collation handling is same as for functions */ - collation = oe->opcollid; - if (collation == InvalidOid) - state = FDW_COLLATE_NONE; - else if (inner_cxt.state == FDW_COLLATE_SAFE && - collation == inner_cxt.collation) - state = FDW_COLLATE_SAFE; - else if (collation == DEFAULT_COLLATION_OID) - state = FDW_COLLATE_NONE; - else - state = FDW_COLLATE_UNSAFE; - } - break; - case T_ScalarArrayOpExpr: - { - ScalarArrayOpExpr *oe = (ScalarArrayOpExpr *) node; - - /* - * Again, only shippable operators can be sent to remote. - */ - if (!is_shippable(oe->opno, OperatorRelationId, fpinfo)) - return false; + /* Detect whether node is introducing a collation not derived from a foreign Var. (If so, we just mark it unsafe for now + * rather than immediately returning false, since the parent node might not care.) + */ + collation = fe->funccollid; + if (collation == InvalidOid) + state = FDW_COLLATE_NONE; + else if (inner_cxt.state == FDW_COLLATE_SAFE && collation == inner_cxt.collation) + state = FDW_COLLATE_SAFE; + else if (collation == DEFAULT_COLLATION_OID) + state = FDW_COLLATE_NONE; + else + state = FDW_COLLATE_UNSAFE; + } + break; + case T_OpExpr: + case T_DistinctExpr: { /* struct-equivalent to OpExpr */ + OpExpr* oe = (OpExpr*) node; - /* - * Recurse to input subexpressions. - */ - if (!foreign_expr_walker((Node *) oe->args, - glob_cxt, &inner_cxt, case_arg_cxt)) - return false; + // Similarly, only shippable operators can be sent to remote. + // (If the operator is shippable, we assume its underlying function is too.) + if (!is_shippable(oe->opno, OperatorRelationId, fpinfo)) + return false; - /* - * If operator's input collation is not derived from a foreign - * Var, it can't be sent to remote. - */ - if (oe->inputcollid == InvalidOid) - /* OK, inputs are all noncollatable */ ; - else if (inner_cxt.state != FDW_COLLATE_SAFE || - oe->inputcollid != inner_cxt.collation) - return false; - - /* Output is always boolean and so noncollatable. */ - collation = InvalidOid; - state = FDW_COLLATE_NONE; - } - break; - case T_RelabelType: - { - RelabelType *r = (RelabelType *) node; - - /* - * Recurse to input subexpression. - */ - if (!foreign_expr_walker((Node *) r->arg, - glob_cxt, &inner_cxt, case_arg_cxt)) - return false; + // Recurse to input subexpressions. + if (!foreign_expr_walker((Node *) oe->args, glob_cxt, &inner_cxt, case_arg_cxt)) + return false; - /* - * RelabelType must not introduce a collation not derived from - * an input foreign Var (same logic as for a real function). - */ - collation = r->resultcollid; - if (collation == InvalidOid) - state = FDW_COLLATE_NONE; - else if (inner_cxt.state == FDW_COLLATE_SAFE && - collation == inner_cxt.collation) - state = FDW_COLLATE_SAFE; - else if (collation == DEFAULT_COLLATION_OID) - state = FDW_COLLATE_NONE; - else - state = FDW_COLLATE_UNSAFE; - } - break; - case T_ArrayCoerceExpr: - { - ArrayCoerceExpr *e = (ArrayCoerceExpr *) node; - - /* - * Recurse to input subexpression. - */ - if (!foreign_expr_walker((Node *) e->arg, - glob_cxt, &inner_cxt, case_arg_cxt)) - return false; - - /* - * T_ArrayCoerceExpr must not introduce a collation not - * derived from an input foreign Var (same logic as for a - * function). - */ - collation = e->resultcollid; - if (collation == InvalidOid) - state = FDW_COLLATE_NONE; - else if (inner_cxt.state == FDW_COLLATE_SAFE && - collation == inner_cxt.collation) - state = FDW_COLLATE_SAFE; - else if (collation == DEFAULT_COLLATION_OID) - state = FDW_COLLATE_NONE; - else - state = FDW_COLLATE_UNSAFE; - } - break; - case T_BoolExpr: - { - BoolExpr *b = (BoolExpr *) node; - - /* - * Recurse to input subexpressions. - */ - if (!foreign_expr_walker((Node *) b->args, - glob_cxt, &inner_cxt, case_arg_cxt)) - return false; - - /* Output is always boolean and so noncollatable. */ - collation = InvalidOid; - state = FDW_COLLATE_NONE; - } - break; - case T_NullTest: - { - NullTest *nt = (NullTest *) node; - - /* - * Recurse to input subexpressions. - */ - if (!foreign_expr_walker((Node *) nt->arg, - glob_cxt, &inner_cxt, case_arg_cxt)) - return false; - - /* Output is always boolean and so noncollatable. */ - collation = InvalidOid; - state = FDW_COLLATE_NONE; - } - break; - case T_CaseExpr: - { - CaseExpr *ce = (CaseExpr *) node; - foreign_loc_cxt arg_cxt; - foreign_loc_cxt tmp_cxt; - ListCell *lc; - - /* - * Recurse to CASE's arg expression, if any. Its collation - * has to be saved aside for use while examining CaseTestExprs - * within the WHEN expressions. - */ - arg_cxt.collation = InvalidOid; - arg_cxt.state = FDW_COLLATE_NONE; - if (ce->arg) - { - if (!foreign_expr_walker((Node *) ce->arg, - glob_cxt, &arg_cxt, case_arg_cxt)) - return false; - } - - /* Examine the CaseWhen subexpressions. */ - foreach(lc, ce->args) - { - CaseWhen *cw = lfirst_node(CaseWhen, lc); - - if (ce->arg) - { - /* - * In a CASE-with-arg, the parser should have produced - * WHEN clauses of the form "CaseTestExpr = RHS", - * possibly with an implicit coercion inserted above - * the CaseTestExpr. However in an expression that's - * been through the optimizer, the WHEN clause could - * be almost anything (since the equality operator - * could have been expanded into an inline function). - * In such cases forbid pushdown, because - * deparseCaseExpr can't handle it. - */ - Node *whenExpr = (Node *) cw->expr; - List *opArgs; - - if (!IsA(whenExpr, OpExpr)) - return false; - - opArgs = ((OpExpr *) whenExpr)->args; - if (list_length(opArgs) != 2 || - !IsA(strip_implicit_coercions(linitial(opArgs)), - CaseTestExpr)) - return false; - } - - /* - * Recurse to WHEN expression, passing down the arg info. - * Its collation doesn't affect the result (really, it - * should be boolean and thus not have a collation). - */ - tmp_cxt.collation = InvalidOid; - tmp_cxt.state = FDW_COLLATE_NONE; - if (!foreign_expr_walker((Node *) cw->expr, - glob_cxt, &tmp_cxt, &arg_cxt)) - return false; - - /* Recurse to THEN expression. */ - if (!foreign_expr_walker((Node *) cw->result, - glob_cxt, &inner_cxt, case_arg_cxt)) - return false; - } - - /* Recurse to ELSE expression. */ - if (!foreign_expr_walker((Node *) ce->defresult, - glob_cxt, &inner_cxt, case_arg_cxt)) - return false; - - /* - * Detect whether node is introducing a collation not derived - * from a foreign Var. (If so, we just mark it unsafe for now - * rather than immediately returning false, since the parent - * node might not care.) This is the same as for function - * nodes, except that the input collation is derived from only - * the THEN and ELSE subexpressions. - */ - collation = ce->casecollid; - if (collation == InvalidOid) - state = FDW_COLLATE_NONE; - else if (inner_cxt.state == FDW_COLLATE_SAFE && - collation == inner_cxt.collation) - state = FDW_COLLATE_SAFE; - else if (collation == DEFAULT_COLLATION_OID) - state = FDW_COLLATE_NONE; - else - state = FDW_COLLATE_UNSAFE; - } - break; - case T_CaseTestExpr: - { - CaseTestExpr *c = (CaseTestExpr *) node; - - /* Punt if we seem not to be inside a CASE arg WHEN. */ - if (!case_arg_cxt) - return false; - - /* - * Otherwise, any nondefault collation attached to the - * CaseTestExpr node must be derived from foreign Var(s) in - * the CASE arg. - */ - collation = c->collation; - if (collation == InvalidOid) - state = FDW_COLLATE_NONE; - else if (case_arg_cxt->state == FDW_COLLATE_SAFE && - collation == case_arg_cxt->collation) - state = FDW_COLLATE_SAFE; - else if (collation == DEFAULT_COLLATION_OID) - state = FDW_COLLATE_NONE; - else - state = FDW_COLLATE_UNSAFE; - } - break; - case T_ArrayExpr: - { - ArrayExpr *a = (ArrayExpr *) node; - - /* - * Recurse to input subexpressions. - */ - if (!foreign_expr_walker((Node *) a->elements, - glob_cxt, &inner_cxt, case_arg_cxt)) - return false; + // If operator's input collation is not derived from a foreign Var, it can't be sent to remote. + if (oe->inputcollid == InvalidOid) + /* OK, inputs are all noncollatable */ ; + else if (inner_cxt.state != FDW_COLLATE_SAFE || oe->inputcollid != inner_cxt.collation) + return false; - /* - * ArrayExpr must not introduce a collation not derived from - * an input foreign Var (same logic as for a function). - */ - collation = a->array_collid; - if (collation == InvalidOid) - state = FDW_COLLATE_NONE; - else if (inner_cxt.state == FDW_COLLATE_SAFE && - collation == inner_cxt.collation) - state = FDW_COLLATE_SAFE; - else if (collation == DEFAULT_COLLATION_OID) - state = FDW_COLLATE_NONE; - else - state = FDW_COLLATE_UNSAFE; - } - break; - case T_List: - { - List *l = (List *) node; - ListCell *lc; - - /* - * Recurse to component subexpressions. - */ - foreach(lc, l) - { - if (!foreign_expr_walker((Node *) lfirst(lc), - glob_cxt, &inner_cxt, case_arg_cxt)) - return false; - } - - /* - * When processing a list, collation state just bubbles up - * from the list elements. - */ - collation = inner_cxt.collation; - state = inner_cxt.state; - - /* Don't apply exprType() to the list. */ - check_type = false; - } - break; - case T_Aggref: - { - Aggref *agg = (Aggref *) node; - ListCell *lc; - - /* Not safe to pushdown when not in grouping context */ - if (!IS_UPPER_REL(glob_cxt->foreignrel)) - return false; - - /* Only non-split aggregates are pushable. */ - if (agg->aggsplit != AGGSPLIT_SIMPLE) - return false; - - /* As usual, it must be shippable. */ - if (!is_shippable(agg->aggfnoid, ProcedureRelationId, fpinfo)) - return false; - - /* - * Recurse to input args. aggdirectargs, aggorder and - * aggdistinct are all present in args, so no need to check - * their shippability explicitly. - */ - foreach(lc, agg->args) - { - Node *n = (Node *) lfirst(lc); - - /* If TargetEntry, extract the expression from it */ - if (IsA(n, TargetEntry)) - { - TargetEntry *tle = (TargetEntry *) n; - - n = (Node *) tle->expr; - } - - if (!foreign_expr_walker(n, - glob_cxt, &inner_cxt, case_arg_cxt)) - return false; - } - - /* - * For aggorder elements, check whether the sort operator, if - * specified, is shippable or not. - */ - if (agg->aggorder) - { - foreach(lc, agg->aggorder) - { - SortGroupClause *srt = (SortGroupClause *) lfirst(lc); - Oid sortcoltype; - TypeCacheEntry *typentry; - TargetEntry *tle; - - tle = get_sortgroupref_tle(srt->tleSortGroupRef, - agg->args); - sortcoltype = exprType((Node *) tle->expr); - typentry = lookup_type_cache(sortcoltype, - TYPECACHE_LT_OPR | TYPECACHE_GT_OPR); - /* Check shippability of non-default sort operator. */ - if (srt->sortop != typentry->lt_opr && - srt->sortop != typentry->gt_opr && - !is_shippable(srt->sortop, OperatorRelationId, - fpinfo)) - return false; - } - } - - /* Check aggregate filter */ - if (!foreign_expr_walker((Node *) agg->aggfilter, - glob_cxt, &inner_cxt, case_arg_cxt)) - return false; - - /* - * If aggregate's input collation is not derived from a - * foreign Var, it can't be sent to remote. - */ - if (agg->inputcollid == InvalidOid) - /* OK, inputs are all noncollatable */ ; - else if (inner_cxt.state != FDW_COLLATE_SAFE || - agg->inputcollid != inner_cxt.collation) - return false; - - /* - * Detect whether node is introducing a collation not derived - * from a foreign Var. (If so, we just mark it unsafe for now - * rather than immediately returning false, since the parent - * node might not care.) - */ - collation = agg->aggcollid; - if (collation == InvalidOid) - state = FDW_COLLATE_NONE; - else if (inner_cxt.state == FDW_COLLATE_SAFE && - collation == inner_cxt.collation) - state = FDW_COLLATE_SAFE; - else if (collation == DEFAULT_COLLATION_OID) - state = FDW_COLLATE_NONE; - else - state = FDW_COLLATE_UNSAFE; - } - break; + // Result-collation handling is same as for functions + collation = oe->opcollid; + if (collation == InvalidOid) + state = FDW_COLLATE_NONE; + else if (inner_cxt.state == FDW_COLLATE_SAFE && collation == inner_cxt.collation) + state = FDW_COLLATE_SAFE; + else if (collation == DEFAULT_COLLATION_OID) + state = FDW_COLLATE_NONE; + else + state = FDW_COLLATE_UNSAFE; + } + break; + case T_ScalarArrayOpExpr: { + ScalarArrayOpExpr *oe = (ScalarArrayOpExpr *) node; + + // Again, only shippable operators can be sent to remote. + if (!is_shippable(oe->opno, OperatorRelationId, fpinfo)) + return false; + + // Recurse to input subexpressions. + if (!foreign_expr_walker((Node *) oe->args, glob_cxt, &inner_cxt, case_arg_cxt)) + return false; + + // If operator's input collation is not derived from a foreign Var, it can't be sent to remote. + if (oe->inputcollid == InvalidOid) + /* OK, inputs are all noncollatable */ ; + else if (inner_cxt.state != FDW_COLLATE_SAFE || oe->inputcollid != inner_cxt.collation) + return false; + + // Output is always boolean and so noncollatable. + collation = InvalidOid; + state = FDW_COLLATE_NONE; + } + break; + case T_RelabelType: { + RelabelType* r = (RelabelType*) node; + // Recurse to input subexpression. + if (!foreign_expr_walker((Node *) r->arg, glob_cxt, &inner_cxt, case_arg_cxt)) + return false; + // RelabelType must not introduce a collation not derived from an input foreign Var (same logic as for a real function). + collation = r->resultcollid; + if (collation == InvalidOid) + state = FDW_COLLATE_NONE; + else if (inner_cxt.state == FDW_COLLATE_SAFE && collation == inner_cxt.collation) + state = FDW_COLLATE_SAFE; + else if (collation == DEFAULT_COLLATION_OID) + state = FDW_COLLATE_NONE; + else + state = FDW_COLLATE_UNSAFE; + } + break; + case T_ArrayCoerceExpr: { + ArrayCoerceExpr *e = (ArrayCoerceExpr *) node; + // Recurse to input subexpression. + if (!foreign_expr_walker((Node *) e->arg, glob_cxt, &inner_cxt, case_arg_cxt)) + return false; + // T_ArrayCoerceExpr must not introduce a collation not derived from an input foreign Var (same logic as for a function). + collation = e->resultcollid; + if (collation == InvalidOid) + state = FDW_COLLATE_NONE; + else if (inner_cxt.state == FDW_COLLATE_SAFE && collation == inner_cxt.collation) + state = FDW_COLLATE_SAFE; + else if (collation == DEFAULT_COLLATION_OID) + state = FDW_COLLATE_NONE; + else + state = FDW_COLLATE_UNSAFE; + } + break; + case T_BoolExpr: { + BoolExpr* b = (BoolExpr*) node; + // Recurse to input subexpressions. + if (!foreign_expr_walker((Node*) b->args, glob_cxt, &inner_cxt, case_arg_cxt)) + return false; + // Output is always boolean and so noncollatable. + collation = InvalidOid; + state = FDW_COLLATE_NONE; + } + break; + case T_NullTest: { + NullTest* nt = (NullTest*) node; + // Recurse to input subexpressions. + if (!foreign_expr_walker((Node*) nt->arg, glob_cxt, &inner_cxt, case_arg_cxt)) + return false; + // Output is always boolean and so noncollatable. + collation = InvalidOid; + state = FDW_COLLATE_NONE; + } + break; + case T_CaseExpr: { + CaseExpr* ce = (CaseExpr*) node; + foreign_loc_cxt arg_cxt; + foreign_loc_cxt tmp_cxt; + ListCell* lc; + // Recurse to CASE's arg expression, if any. Its collation has to be saved aside for use while examining CaseTestExprs within the WHEN expressions. + arg_cxt.collation = InvalidOid; + arg_cxt.state = FDW_COLLATE_NONE; + if (ce->arg) { + if (!foreign_expr_walker((Node *) ce->arg, glob_cxt, &arg_cxt, case_arg_cxt)) + return false; + } + + // Examine the CaseWhen subexpressions. + foreach(lc, ce->args) { + CaseWhen* cw = lfirst_node(CaseWhen, lc); + if (ce->arg) { + /* In a CASE-with-arg, the parser should have produced WHEN clauses of the form "CaseTestExpr = RHS", + * possibly with an implicit coercion inserted above the CaseTestExpr. However in an expression that's + * been through the optimizer, the WHEN clause could be almost anything (since the equality operator + * could have been expanded into an inline function). + * In such cases forbid pushdown, because deparseCaseExpr can't handle it. + */ + Node* whenExpr = (Node*) cw->expr; + List* opArgs = NULL; + if (!IsA(whenExpr, OpExpr)) + return false; + opArgs = ((OpExpr *) whenExpr)->args; + if (list_length(opArgs) != 2 || !IsA(strip_implicit_coercions(linitial(opArgs)), CaseTestExpr)) + return false; + } + /* Recurse to WHEN expression, passing down the arg info. + * Its collation doesn't affect the result (really, it should be boolean and thus not have a collation). + */ + tmp_cxt.collation = InvalidOid; + tmp_cxt.state = FDW_COLLATE_NONE; + if (!foreign_expr_walker((Node *) cw->expr, glob_cxt, &tmp_cxt, &arg_cxt)) + return false; + /* Recurse to THEN expression. */ + if (!foreign_expr_walker((Node *) cw->result, glob_cxt, &inner_cxt, case_arg_cxt)) + return false; + } + // Recurse to ELSE expression. + if (!foreign_expr_walker((Node *) ce->defresult, glob_cxt, &inner_cxt, case_arg_cxt)) + return false; + /* Detect whether node is introducing a collation not derived from a foreign Var. (If so, we just mark it unsafe for now + * rather than immediately returning false, since the parent node might not care.) This is the same as for function + * nodes, except that the input collation is derived from only the THEN and ELSE subexpressions. + */ + collation = ce->casecollid; + if (collation == InvalidOid) + state = FDW_COLLATE_NONE; + else if (inner_cxt.state == FDW_COLLATE_SAFE && collation == inner_cxt.collation) + state = FDW_COLLATE_SAFE; + else if (collation == DEFAULT_COLLATION_OID) + state = FDW_COLLATE_NONE; + else + state = FDW_COLLATE_UNSAFE; + } + break; + case T_CaseTestExpr: { + CaseTestExpr* c = (CaseTestExpr*) node; + // Punt if we seem not to be inside a CASE arg WHEN. + if (!case_arg_cxt) + return false; + // Otherwise, any nondefault collation attached to the CaseTestExpr node must be derived from foreign Var(s) in the CASE arg. + collation = c->collation; + if (collation == InvalidOid) + state = FDW_COLLATE_NONE; + else if (case_arg_cxt->state == FDW_COLLATE_SAFE && collation == case_arg_cxt->collation) + state = FDW_COLLATE_SAFE; + else if (collation == DEFAULT_COLLATION_OID) + state = FDW_COLLATE_NONE; + else + state = FDW_COLLATE_UNSAFE; + } + break; + case T_ArrayExpr: { + ArrayExpr* a = (ArrayExpr *) node; + // Recurse to input subexpressions. + if (!foreign_expr_walker((Node *) a->elements, glob_cxt, &inner_cxt, case_arg_cxt)) + return false; + // ArrayExpr must not introduce a collation not derived from an input foreign Var (same logic as for a function). + collation = a->array_collid; + if (collation == InvalidOid) + state = FDW_COLLATE_NONE; + else if (inner_cxt.state == FDW_COLLATE_SAFE && collation == inner_cxt.collation) + state = FDW_COLLATE_SAFE; + else if (collation == DEFAULT_COLLATION_OID) + state = FDW_COLLATE_NONE; + else + state = FDW_COLLATE_UNSAFE; + } + break; + case T_List: { + List* l = (List*) node; + ListCell* lc = NULL; + // Recurse to component subexpressions. + foreach(lc, l) { + if (!foreign_expr_walker((Node *) lfirst(lc), glob_cxt, &inner_cxt, case_arg_cxt)) + return false; + } + // When processing a list, collation state just bubbles up from the list elements. + collation = inner_cxt.collation; + state = inner_cxt.state; + // Don't apply exprType() to the list. + check_type = false; + } + break; + case T_Aggref: { + Aggref* agg = (Aggref*) node; + ListCell* lc = NULL; + // Not safe to pushdown when not in grouping context + if (!IS_UPPER_REL(glob_cxt->foreignrel)) + return false; + // Only non-split aggregates are pushable. + if (agg->aggsplit != AGGSPLIT_SIMPLE) + return false; + // As usual, it must be shippable. + if (!is_shippable(agg->aggfnoid, ProcedureRelationId, fpinfo)) + return false; + // Recurse to input args. aggdirectargs, aggorder and aggdistinct are all present in args, so no need to check their shippability explicitly. + foreach(lc, agg->args) { + Node* n = (Node*) lfirst(lc); + // If TargetEntry, extract the expression from it + if (IsA(n, TargetEntry)) { + TargetEntry* tle = (TargetEntry*) n; + n = (Node*) tle->expr; + } + if (!foreign_expr_walker(n, glob_cxt, &inner_cxt, case_arg_cxt)) + return false; + } + // For aggorder elements, check whether the sort operator, if specified, is shippable or not. + if (agg->aggorder) { + foreach(lc, agg->aggorder) { + SortGroupClause* srt = (SortGroupClause*) lfirst(lc); + Oid sortcoltype; + TypeCacheEntry* typentry; + TargetEntry* tle; + + tle = get_sortgroupref_tle(srt->tleSortGroupRef, agg->args); + sortcoltype = exprType((Node *) tle->expr); + typentry = lookup_type_cache(sortcoltype, TYPECACHE_LT_OPR | TYPECACHE_GT_OPR); + // Check shippability of non-default sort operator. + if (srt->sortop != typentry->lt_opr && srt->sortop != typentry->gt_opr && !is_shippable(srt->sortop, OperatorRelationId, fpinfo)) + return false; + } + } + // Check aggregate filter + if (!foreign_expr_walker((Node *) agg->aggfilter, glob_cxt, &inner_cxt, case_arg_cxt)) + return false; + // If aggregate's input collation is not derived from a foreign Var, it can't be sent to remote. + if (agg->inputcollid == InvalidOid) + /* OK, inputs are all noncollatable */ ; + else if (inner_cxt.state != FDW_COLLATE_SAFE || agg->inputcollid != inner_cxt.collation) + return false; + /* Detect whether node is introducing a collation not derived from a foreign Var. (If so, we just mark it unsafe for now + * rather than immediately returning false, since the parent node might not care.) + */ + collation = agg->aggcollid; + if (collation == InvalidOid) + state = FDW_COLLATE_NONE; + else if (inner_cxt.state == FDW_COLLATE_SAFE && collation == inner_cxt.collation) + state = FDW_COLLATE_SAFE; + else if (collation == DEFAULT_COLLATION_OID) + state = FDW_COLLATE_NONE; + else + state = FDW_COLLATE_UNSAFE; + } + break; default: /** If it's anything else, assume it's unsafe. This list can be * expanded later, but don't forget to add deparse support below. */ return false; } - /** If result type of given expression is not shippable, it can't be sent * to remote because it might have incompatible semantics on remote side. */ if (check_type && !is_shippable(exprType(node), TypeRelationId, fpinfo)) return false; - /** Now, merge my collation information into my parent's state. */ if (state > outer_cxt->state) { /* Override previous parent state */ @@ -902,7 +779,7 @@ static bool foreign_expr_walker(Node *node, foreign_glob_cxt *glob_cxt, foreign_ * and an entry in fdw_scan_tlist (which we're considering putting the given * expression into). */ -bool is_foreign_param(PlannerInfo *root, RelOptInfo *baserel, Expr *expr) { +bool is_foreign_param(PlannerInfo* root, RelOptInfo* baserel, Expr* expr) { bool bResult = false; if (expr != NULL) { switch (nodeTag(expr)) { @@ -929,7 +806,7 @@ bool is_foreign_param(PlannerInfo *root, RelOptInfo *baserel, Expr *expr) { /** Returns true if it's safe to push down the sort expression described by * 'pathkey' to the foreign server. */ -bool is_foreign_pathkey(PlannerInfo *root, RelOptInfo *baserel, PathKey *pathkey) { +bool is_foreign_pathkey(PlannerInfo* root, RelOptInfo* baserel, PathKey* pathkey) { EquivalenceClass* pathkey_ec = pathkey->pk_eclass; DB2FdwState* fpinfo = (DB2FdwState*) baserel->fdw_private; bool bResult = false; @@ -947,6 +824,23 @@ bool is_foreign_pathkey(PlannerInfo *root, RelOptInfo *baserel, PathKey *pathkey return bResult; } +/* Convert type OID + typmod info into a type name we can ship to the remote server. + * Someplace else had better have verified that this type name is expected to be known on the remote end. + * + * This is almost just format_type_with_typemod(), except that if left to its own devices, that function will make + * schema-qualification decisions based on the local search_path, which is wrong. + * We must schema-qualify all type names that are not in pg_catalog. + * We assume here that built-in types are all in pg_catalog and need not be qualified; otherwise, qualify. + */ +static char* deparse_type_name(Oid type_oid, int32 typemod) { + bits16 flags = FORMAT_TYPE_TYPEMOD_GIVEN; + + if (!is_builtin(type_oid)) + flags |= FORMAT_TYPE_FORCE_QUALIFY; + + return format_type_extended(type_oid, typemod, flags); +} + /** appendAsType * Append "s" to "dest", adding appropriate casts for datetime "type". */ @@ -978,68 +872,752 @@ void appendAsType (StringInfoData* dest, Oid type) { db2Debug1("< %s::appendAsType", __FILE__); } -/** This macro is used by deparseExpr to identify PostgreSQL - * types that can be translated to DB2 SQL. +/** Deparse GROUP BY clause. */ -#define canHandleType(x) ((x) == TEXTOID || (x) == CHAROID || (x) == BPCHAROID \ - || (x) == VARCHAROID || (x) == NAMEOID || (x) == INT8OID || (x) == INT2OID \ - || (x) == INT4OID || (x) == OIDOID || (x) == FLOAT4OID || (x) == FLOAT8OID \ - || (x) == NUMERICOID || (x) == DATEOID || (x) == TIMEOID || (x) == TIMESTAMPOID \ - || (x) == TIMESTAMPTZOID || (x) == INTERVALOID) +static void appendGroupByClause(List* tlist, deparse_expr_cxt* context) { + StringInfo buf = context->buf; + Query* query = context->root->parse; + ListCell* lc = NULL; + bool first = true; + + /* Nothing to be done, if there's no GROUP BY clause in the query. */ + if (!query->groupClause) + return; + + appendStringInfoString(buf, " GROUP BY "); + + /** Queries with grouping sets are not pushed down, so we don't expect grouping sets here. + */ + Assert(!query->groupingSets); + + /* We intentionally print query->groupClause not processed_groupClause, leaving it to the remote planner to get rid of any redundant GROUP BY + * items again. This is necessary in case processed_groupClause reduced to empty, and in any case the redundancy situation on the remote might + * be different than what we think here. + */ + foreach(lc, query->groupClause) { + SortGroupClause *grp = (SortGroupClause *) lfirst(lc); + + if (!first) + appendStringInfoString(buf, ", "); + first = false; + deparseSortGroupClause(grp->tleSortGroupRef, tlist, true, context); + } +} + +/** Deparse ORDER BY clause defined by the given pathkeys. + * + * The clause should use Vars from context->scanrel if !has_final_sort, + * or from context->foreignrel's targetlist if has_final_sort. + * + * We find a suitable pathkey expression (some earlier step + * should have verified that there is one) and deparse it. + */ +static void appendOrderByClause(List* pathkeys, bool has_final_sort, deparse_expr_cxt* context) { + ListCell* lcell; + int nestlevel; + StringInfo buf = context->buf; + bool gotone = false; + + /* Make sure any constants in the exprs are printed portably */ + nestlevel = set_transmission_modes(); + + foreach(lcell, pathkeys) { + PathKey* pathkey = lfirst(lcell); + EquivalenceMember* em; + Expr* em_expr; + Oid oprid; + + if (has_final_sort) { + /* By construction, context->foreignrel is the input relation to the final sort. */ + em = find_em_for_rel_target(context->root, pathkey->pk_eclass, context->foreignrel); + } else { + em = find_em_for_rel(context->root, pathkey->pk_eclass, context->scanrel); + } + /* We don't expect any error here; it would mean that shippability wasn't verified earlier. + * For the same reason, we don't recheck shippability of the sort operator. + */ + if (em == NULL) + elog(ERROR, "could not find pathkey item to sort"); + + em_expr = em->em_expr; + + /* If the member is a Const expression then we needn't add it to the ORDER BY clause. This can happen in UNION ALL queries where the + * union child targetlist has a Const. Adding these would be wasteful, but also, for INT columns, an integer literal would be + * seen as an ordinal column position rather than a value to sort by. + * deparseConst() does have code to handle this, but it seems less effort on all accounts just to skip these for ORDER BY clauses. + */ + if (IsA(em_expr, Const)) + continue; + + if (!gotone) { + appendStringInfoString(buf, " ORDER BY "); + gotone = true; + } else { + appendStringInfoString(buf, ", "); + } + /* Lookup the operator corresponding to the compare type in the opclass. + * The datatype used by the opfamily is not necessarily the same as the expression type (for array types for example). + */ + oprid = get_opfamily_member_for_cmptype(pathkey->pk_opfamily, em->em_datatype, em->em_datatype, pathkey->pk_cmptype); + if (!OidIsValid(oprid)) + elog(ERROR, "missing operator %d(%u,%u) in opfamily %u", pathkey->pk_cmptype, em->em_datatype, em->em_datatype, pathkey->pk_opfamily); + deparseExprInt(em_expr, context); + + /* Here we need to use the expression's actual type to discover whether the desired operator will be the default or not. */ + appendOrderBySuffix(oprid, exprType((Node *) em_expr), pathkey->pk_nulls_first, context); + } + reset_transmission_modes(nestlevel); +} + +/** Deparse LIMIT/OFFSET clause. + */ +static void appendLimitClause(deparse_expr_cxt* context) { + PlannerInfo* root = context->root; + StringInfo buf = context->buf; + int nestlevel = 0; + + /* Make sure any constants in the exprs are printed portably */ + nestlevel = set_transmission_modes(); + + if (root->parse->limitCount) { + appendStringInfoString(buf, " LIMIT "); + deparseExprInt((Expr*) root->parse->limitCount, context); + } + if (root->parse->limitOffset) { + appendStringInfoString(buf, " OFFSET "); + deparseExprInt((Expr*) root->parse->limitOffset, context); + } + reset_transmission_modes(nestlevel); +} + +/** appendFunctionName + * Deparses function name from given function oid. + */ +static void appendFunctionName(Oid funcid, deparse_expr_cxt *context) { + StringInfo buf = context->buf; + HeapTuple proctup; + Form_pg_proc procform; + + proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid)); + if (!HeapTupleIsValid(proctup)) + elog(ERROR, "cache lookup failed for function %u", funcid); + procform = (Form_pg_proc) GETSTRUCT(proctup); + + /* Print schema name only if it's not pg_catalog */ + if (procform->pronamespace != PG_CATALOG_NAMESPACE) + appendStringInfo(buf, "%s.", quote_identifier(get_namespace_name(procform->pronamespace))); + + /* Always print the function name */ + appendStringInfoString(buf, quote_identifier(NameStr(procform->proname))); + ReleaseSysCache(proctup); +} + +/** Append the ASC, DESC, USING and NULLS FIRST / NULLS LAST parts of an ORDER BY clause. + */ +static void appendOrderBySuffix(Oid sortop, Oid sortcoltype, bool nulls_first, deparse_expr_cxt* context) { + StringInfo buf = context->buf; + TypeCacheEntry* typentry; + + /* See whether operator is default < or > for sort expr's datatype. */ + typentry = lookup_type_cache(sortcoltype, TYPECACHE_LT_OPR | TYPECACHE_GT_OPR); + + if (sortop == typentry->lt_opr) + appendStringInfoString(buf, " ASC"); + else if (sortop == typentry->gt_opr) + appendStringInfoString(buf, " DESC"); + else { + HeapTuple opertup; + Form_pg_operator operform; + + appendStringInfoString(buf, " USING "); + + /* Append operator name. */ + opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(sortop)); + if (!HeapTupleIsValid(opertup)) + elog(ERROR, "cache lookup failed for operator %u", sortop); + operform = (Form_pg_operator) GETSTRUCT(opertup); + deparseOperatorName(buf, operform); + ReleaseSysCache(opertup); + } + + if (nulls_first) + appendStringInfoString(buf, " NULLS FIRST"); + else + appendStringInfoString(buf, " NULLS LAST"); +} + +/* Print the representation of a parameter to be sent to the remote side. + * + * Note: we always label the Param's type explicitly rather than relying on transmitting a numeric type OID in PQsendQueryParams(). + * This allows us to avoid assuming that types have the same OIDs on the remote side as they do locally --- they need only have the same names. + */ +static void printRemoteParam(int paramindex, Oid paramtype, int32 paramtypmod, deparse_expr_cxt* context) { + StringInfo buf = context->buf; + char* ptypename = deparse_type_name(paramtype, paramtypmod); + + appendStringInfo(buf, "$%d::%s", paramindex, ptypename); +} + +/* Print the representation of a placeholder for a parameter that will be sent to the remote side at execution time. + * + * This is used when we're just trying to EXPLAIN the remote query. + * We don't have the actual value of the runtime parameter yet, and we don't want the remote planner to generate a + * plan that depends on such a value anyway. + * Thus, we can't do something simple like "$1::paramtype". + * Instead, we emit "((SELECT null::paramtype)::paramtype)". + * In all extant versions of Postgres, the planner will see that as an unknown constant value, which is what we want. + * This might need adjustment if we ever make the planner flatten scalar subqueries. + * Note: the reason for the apparently useless outer cast is to ensure that the representation as a whole will be + * parsed as an a_expr and not a select_with_parens; the latter would do the wrong thing in the context "x = ANY(...)". + */ +static void printRemotePlaceholder(Oid paramtype, int32 paramtypmod, deparse_expr_cxt* context) { + StringInfo buf = context->buf; + char* ptypename = deparse_type_name(paramtype, paramtypmod); + + appendStringInfo(buf, "((SELECT null::%s)::%s)", ptypename, ptypename); +} + +/** Deparse conditions from the provided list and append them to buf. + * + * The conditions in the list are assumed to be ANDed. This function is used to deparse WHERE clauses, JOIN .. ON clauses and HAVING clauses. + * + * Depending on the caller, the list elements might be either RestrictInfos or bare clauses. + */ +static void appendConditions(List *exprs, deparse_expr_cxt *context) { + int nestlevel = 0; + ListCell* lc = NULL; + bool is_first = true; + StringInfo buf = context->buf; + + /* Make sure any constants in the exprs are printed portably */ + nestlevel = set_transmission_modes(); + + foreach(lc, exprs) { + Expr* expr = (Expr*) lfirst(lc); + + /* Extract clause from RestrictInfo, if required */ + if (IsA(expr, RestrictInfo)) + expr = ((RestrictInfo*) expr)->clause; + + /* Connect expressions with "AND" and parenthesize each condition. */ + if (!is_first) + appendStringInfoString(buf, " AND "); + + appendStringInfoChar(buf, '('); + deparseExprInt(expr, context); + appendStringInfoChar(buf, ')'); + + is_first = false; + } + reset_transmission_modes(nestlevel); +} + +/* Append WHERE clause, containing conditions from exprs and additional_conds, to context->buf. + */ +static void appendWhereClause(List* exprs, List* additional_conds, deparse_expr_cxt* context) { + StringInfo buf = context->buf; + bool need_and = false; + ListCell* lc = NULL; + + if (exprs != NIL || additional_conds != NIL) + appendStringInfoString(buf, " WHERE "); + + /* If there are some filters, append them. */ + if (exprs != NIL) { + appendConditions(exprs, context); + need_and = true; + } + + /* If there are some EXISTS conditions, coming from SEMI-JOINS, append them. */ + foreach(lc, additional_conds) { + if (need_and) + appendStringInfoString(buf, " AND "); + appendStringInfoString(buf, (char *) lfirst(lc)); + need_and = true; + } +} + +/** Appends a sort or group clause. + * + * Like get_rule_sortgroupclause(), returns the expression tree, so caller + * need not find it again. + */ +static Node* deparseSortGroupClause(Index ref, List *tlist, bool force_colno, deparse_expr_cxt *context) { + StringInfo buf = context->buf; + TargetEntry* tle = NULL; + Expr* expr = NULL; + + tle = get_sortgroupref_tle(ref, tlist); + expr = tle->expr; + + if (force_colno) { + /* Use column-number form when requested by caller. */ + Assert(!tle->resjunk); + appendStringInfo(buf, "%d", tle->resno); + } else if (expr && IsA(expr, Const)) { + // Force a typecast here so that we don't emit something like "GROUP BY 2", which will be misconstrued as a column position rather than a constant. + deparseConst((Const*) expr, context, 1); + } else if (!expr || IsA(expr, Var)) { + deparseExprInt(expr, context); + } else { + /* Always parenthesize the expression. */ + appendStringInfoChar(buf, '('); + deparseExprInt(expr, context); + appendStringInfoChar(buf, ')'); + } + return (Node*) expr; +} + +/* Returns true if given Var is deparsed as a subquery output column, in which case, *relno and *colno are set to the IDs for the relation and + * column alias to the Var provided by the subquery. + */ +static bool is_subquery_var(Var* node, RelOptInfo* foreignrel, int* relno, int* colno) { + DB2FdwState* fpinfo = (DB2FdwState*) foreignrel->fdw_private; + RelOptInfo* outerrel = fpinfo->outerrel; + RelOptInfo* innerrel = fpinfo->innerrel; + + /* Should only be called in these cases. */ + Assert(IS_SIMPLE_REL(foreignrel) || IS_JOIN_REL(foreignrel)); + + /* If the given relation isn't a join relation, it doesn't have any lower subqueries, so the Var isn't a subquery output column. */ + if (!IS_JOIN_REL(foreignrel)) + return false; + + /* If the Var doesn't belong to any lower subqueries, it isn't a subquery output column. */ + if (!bms_is_member(node->varno, fpinfo->lower_subquery_rels)) + return false; + + if (bms_is_member(node->varno, outerrel->relids)) { + /* If outer relation is deparsed as a subquery, the Var is an output column of the subquery; get the IDs for the relation/column alias. */ + if (fpinfo->make_outerrel_subquery) { + get_relation_column_alias_ids(node, outerrel, relno, colno); + return true; + } + + /* Otherwise, recurse into the outer relation. */ + return is_subquery_var(node, outerrel, relno, colno); + } else { + Assert(bms_is_member(node->varno, innerrel->relids)); + + /* If inner relation is deparsed as a subquery, the Var is an output column of the subquery; get the IDs for the relation/column alias. */ + if (fpinfo->make_innerrel_subquery) { + get_relation_column_alias_ids(node, innerrel, relno, colno); + return true; + } + + /* Otherwise, recurse into the inner relation. */ + return is_subquery_var(node, innerrel, relno, colno); + } +} + +/* Get the IDs for the relation and column alias to given Var belonging to given relation, which are returned into *relno and *colno. + */ +static void get_relation_column_alias_ids(Var* node, RelOptInfo* foreignrel, int* relno, int* colno) { + DB2FdwState* fpinfo = (DB2FdwState*) foreignrel->fdw_private; + int i; + ListCell* lc; + + /* Get the relation alias ID */ + *relno = fpinfo->relation_index; + + /* Get the column alias ID */ + i = 1; + foreach(lc, foreignrel->reltarget->exprs) { + Var* tlvar = (Var*) lfirst(lc); + + /* Match reltarget entries only on varno/varattno. Ideally there would be some cross-check on varnullingrels, but it's unclear what + * to do exactly; we don't have enough context to know what that value should be. + */ + if (IsA(tlvar, Var) && tlvar->varno == node->varno && tlvar->varattno == node->varattno) { + *colno = i; + return; + } + i++; + } -/** deparseFromExprForRel - * Construct FROM clause for given relation. - * The function constructs ... JOIN ... ON ... for join relation. For a base - * relation it just returns the table name. - * All tables get an alias based on the range table index. + /* Shouldn't get here */ + elog(ERROR, "unexpected expression in subquery output"); +} + +/* Build the targetlist for given relation to be deparsed as SELECT clause. + * + * The output targetlist contains the columns that need to be fetched from the foreign server for the given relation. + * If foreignrel is an upper relation, then the output targetlist can also contain expressions to be evaluated on + * foreign server. + */ +List* build_tlist_to_deparse(RelOptInfo* foreignrel) { + List* tlist = NIL; + DB2FdwState* fpinfo = (DB2FdwState*) foreignrel->fdw_private; + ListCell* lc; + + /* For an upper relation, we have already built the target list while checking shippability, so just return that. */ + if (IS_UPPER_REL(foreignrel)) + return fpinfo->grouped_tlist; + + /* We require columns specified in foreignrel->reltarget->exprs and those required for evaluating the local conditions. */ + tlist = add_to_flat_tlist(tlist, pull_var_clause((Node*) foreignrel->reltarget->exprs, PVC_RECURSE_PLACEHOLDERS)); + foreach(lc, fpinfo->local_conds) { + RestrictInfo* rinfo = lfirst_node(RestrictInfo, lc); + tlist = add_to_flat_tlist(tlist, pull_var_clause((Node *) rinfo->clause, PVC_RECURSE_PLACEHOLDERS)); + } + return tlist; +} + +/** Deparse SELECT statement for given relation into buf. + * + * tlist contains the list of desired columns to be fetched from foreign server. + * For a base relation fpinfo->attrs_used is used to construct SELECT clause, + * hence the tlist is ignored for a base relation. + * + * remote_conds is the list of conditions to be deparsed into the WHERE clause + * (or, in the case of upper relations, into the HAVING clause). + * + * If params_list is not NULL, it receives a list of Params and other-relation + * Vars used in the clauses; these values must be transmitted to the remote + * server as parameter values. + * + * If params_list is NULL, we're generating the query for EXPLAIN purposes, + * so Params and other-relation Vars should be replaced by dummy values. + * + * pathkeys is the list of pathkeys to order the result by. + * + * is_subquery is the flag to indicate whether to deparse the specified + * relation as a subquery. + * + * List of columns selected is returned in retrieved_attrs. + */ +void deparseSelectStmtForRel(StringInfo buf, PlannerInfo* root, RelOptInfo* rel,List* tlist, List* remote_conds, List* pathkeys, bool has_final_sort, bool has_limit, bool is_subquery, List** retrieved_attrs, List** params_list) { + deparse_expr_cxt context; + DB2FdwState* fpinfo = (DB2FdwState*)rel->fdw_private; + List* quals = NIL; + + //We handle relations for foreign tables, joins between those and upper relations. + Assert(IS_JOIN_REL(rel) || IS_SIMPLE_REL(rel) || IS_UPPER_REL(rel)); + + // Fill portions of context common to upper, join and base relation + context.buf = buf; + context.root = root; + context.foreignrel = rel; + context.scanrel = IS_UPPER_REL(rel) ? fpinfo->outerrel : rel; + context.params_list = params_list; + + // Construct SELECT clause + deparseSelectSql(tlist, is_subquery, retrieved_attrs, &context); + + /* For upper relations, the WHERE clause is built from the remote conditions of the underlying scan relation; otherwise, we can use the + * supplied list of remote conditions directly. + */ + if (IS_UPPER_REL(rel)) { + DB2FdwState* ofpinfo = (DB2FdwState*) fpinfo->outerrel->fdw_private; + quals = ofpinfo->remote_conds; + } else { + quals = remote_conds; + } + + // Construct FROM and WHERE clauses + deparseFromExpr(quals, &context); + + if (IS_UPPER_REL(rel)) { + // Append GROUP BY clause + appendGroupByClause(tlist, &context); + + // Append HAVING clause + if (remote_conds) { + appendStringInfoString(buf, " HAVING "); + appendConditions(remote_conds, &context); + } + } + + // Add ORDER BY clause if we found any useful pathkeys + if (pathkeys) + appendOrderByClause(pathkeys, has_final_sort, &context); + + // Add LIMIT clause if necessary + if (has_limit) + appendLimitClause(&context); + + // Add any necessary FOR UPDATE/SHARE. + deparseLockingClause(&context); +} + +/* + * Construct a simple SELECT statement that retrieves desired columns + * of the specified foreign table, and append it to "buf". The output + * contains just "SELECT ... ". + * + * We also create an integer List of the columns being retrieved, which is + * returned to *retrieved_attrs, unless we deparse the specified relation + * as a subquery. + * + * tlist is the list of desired columns. is_subquery is the flag to + * indicate whether to deparse the specified relation as a subquery. + * Read prologue of deparseSelectStmtForRel() for details. + */ +static void deparseSelectSql(List *tlist, bool is_subquery, List **retrieved_attrs, deparse_expr_cxt *context) { + StringInfo buf = context->buf; + RelOptInfo* foreignrel = context->foreignrel; + PlannerInfo* root = context->root; + DB2FdwState* fpinfo = (DB2FdwState*) foreignrel->fdw_private; + + // Construct SELECT list + appendStringInfoString(buf, "SELECT "); + + if (is_subquery) { + // For a relation that is deparsed as a subquery, emit expressions specified in the relation's reltarget. + // Note that since this is for the subquery, no need to care about *retrieved_attrs. + deparseSubqueryTargetList(context); + } + else if (IS_JOIN_REL(foreignrel) || IS_UPPER_REL(foreignrel)) { + // For a join or upper relation the input tlist gives the list of columns required to be fetched from the foreign server. + deparseExplicitTargetList(tlist, false, retrieved_attrs, context); + } else { + // For a base relation fpinfo->attrs_used gives the list of columns* required to be fetched from the foreign server. + RangeTblEntry *rte = planner_rt_fetch(foreignrel->relid, root); + + // Core code already has some lock on each rel being planned, so we can use NoLock here. + Relation rel = table_open(rte->relid, NoLock); + + deparseTargetList(buf, rte, foreignrel->relid, rel, false, fpinfo->attrs_used, false, retrieved_attrs); + table_close(rel, NoLock); + } +} + +/* + * Construct a FROM clause and, if needed, a WHERE clause, and append those to + * "buf". + * + * quals is the list of clauses to be included in the WHERE clause. + * (These may or may not include RestrictInfo decoration.) + */ +static void deparseFromExpr(List *quals, deparse_expr_cxt *context) { + StringInfo buf = context->buf; + RelOptInfo* scanrel = context->scanrel; + List* additional_conds = NIL; + + /* For upper relations, scanrel must be either a joinrel or a baserel */ + Assert(!IS_UPPER_REL(context->foreignrel) || IS_JOIN_REL(scanrel) || IS_SIMPLE_REL(scanrel)); + + /* Construct FROM clause */ + appendStringInfoString(buf, " FROM "); + deparseFromExprForRel(buf, context->root, scanrel, (bms_membership(scanrel->relids) == BMS_MULTIPLE), (Index) 0, NULL, &additional_conds, context->params_list); + appendWhereClause(quals, additional_conds, context); + if (additional_conds != NIL) + list_free_deep(additional_conds); +} + +/* + * Construct FROM clause for given relation + * + * The function constructs ... JOIN ... ON ... for join relation. For a base + * relation it just returns schema-qualified tablename, with the appropriate + * alias if so requested. + * + * 'ignore_rel' is either zero or the RT index of a target relation. In the + * latter case the function constructs FROM clause of UPDATE or USING clause + * of DELETE; it deparses the join relation as if the relation never contained + * the target relation, and creates a List of conditions to be deparsed into + * the top-level WHERE clause, which is returned to *ignore_conds. + * + * 'additional_conds' is a pointer to a list of strings to be appended to + * the WHERE clause, coming from lower-level SEMI-JOINs. + */ +static void deparseFromExprForRel(StringInfo buf, PlannerInfo* root, RelOptInfo* foreignrel, bool use_alias, Index ignore_rel, List** ignore_conds, List** additional_conds, List** params_list) { + DB2FdwState* fpinfo = (DB2FdwState*) foreignrel->fdw_private; + + if (IS_JOIN_REL(foreignrel)) { + StringInfoData join_sql_o; + StringInfoData join_sql_i; + RelOptInfo* outerrel = fpinfo->outerrel; + RelOptInfo* innerrel = fpinfo->innerrel; + bool outerrel_is_target = false; + bool innerrel_is_target = false; + List* additional_conds_i = NIL; + List* additional_conds_o = NIL; + + if (ignore_rel > 0 && bms_is_member(ignore_rel, foreignrel->relids)) { + /* If this is an inner join, add joinclauses to *ignore_conds and set it to empty so that those can be deparsed into the WHERE + * clause. Note that since the target relation can never be within the nullable side of an outer join, those could safely + * be pulled up into the WHERE clause (see foreign_join_ok()). + * Note also that since the target relation is only inner-joined to any other relation in the query, all conditions in the join + * tree mentioning the target relation could be deparsed into the WHERE clause by doing this recursively. + */ + if (fpinfo->jointype == JOIN_INNER) { + *ignore_conds = list_concat(*ignore_conds, fpinfo->joinclauses); + fpinfo->joinclauses = NIL; + } + /* Check if either of the input relations is the target relation. */ + if (outerrel->relid == ignore_rel) + outerrel_is_target = true; + else if (innerrel->relid == ignore_rel) + innerrel_is_target = true; + } + /* Deparse outer relation if not the target relation. */ + if (!outerrel_is_target) { + initStringInfo(&join_sql_o); + deparseRangeTblRef(&join_sql_o, root, outerrel, fpinfo->make_outerrel_subquery, ignore_rel, ignore_conds, &additional_conds_o, params_list); + /* If inner relation is the target relation, skip deparsing it. + * Note that since the join of the target relation with any other relation in the query is an inner join and can never be within + * the nullable side of an outer join, the join could be interchanged with higher-level joins (cf. identity 1 on outer + * join reordering shown in src/backend/optimizer/README), which means it's safe to skip the target-relation deparsing here. + */ + if (innerrel_is_target) { + Assert(fpinfo->jointype == JOIN_INNER); + Assert(fpinfo->joinclauses == NIL); + appendBinaryStringInfo(buf, join_sql_o.data, join_sql_o.len); + /* Pass EXISTS conditions to upper level */ + if (additional_conds_o != NIL) { + Assert(*additional_conds == NIL); + *additional_conds = additional_conds_o; + } + return; + } + } + /* Deparse inner relation if not the target relation. */ + if (!innerrel_is_target) { + initStringInfo(&join_sql_i); + deparseRangeTblRef(&join_sql_i, root, innerrel, fpinfo->make_innerrel_subquery, ignore_rel, ignore_conds, &additional_conds_i, params_list); + /* SEMI-JOIN is deparsed as the EXISTS subquery. + * It references outer and inner relations, so it should be evaluated as the condition in the upper-level WHERE clause. + * We deparse the condition and pass it to upper level callers as an additional_conds list. + * Upper level callers are responsible for inserting conditions from the list where appropriate. + */ + if (fpinfo->jointype == JOIN_SEMI) { + deparse_expr_cxt context; + StringInfoData str; + + /* Construct deparsed condition from this SEMI-JOIN */ + initStringInfo(&str); + appendStringInfo(&str, "EXISTS (SELECT NULL FROM %s", join_sql_i.data); + context.buf = &str; + context.foreignrel = foreignrel; + context.scanrel = foreignrel; + context.root = root; + context.params_list = params_list; + /* Append SEMI-JOIN clauses and EXISTS conditions from lower levels to the current EXISTS subquery */ + appendWhereClause(fpinfo->joinclauses, additional_conds_i, &context); + /* EXISTS conditions, coming from lower join levels, have just been processed. */ + if (additional_conds_i != NIL) { + list_free_deep(additional_conds_i); + additional_conds_i = NIL; + } + /* Close parentheses for EXISTS subquery */ + appendStringInfoChar(&str, ')'); + *additional_conds = lappend(*additional_conds, str.data); + } + /* If outer relation is the target relation, skip deparsing it. + * See the above note about safety. + */ + if (outerrel_is_target) { + Assert(fpinfo->jointype == JOIN_INNER); + Assert(fpinfo->joinclauses == NIL); + appendBinaryStringInfo(buf, join_sql_i.data, join_sql_i.len); + /* Pass EXISTS conditions to the upper call */ + if (additional_conds_i != NIL) { + Assert(*additional_conds == NIL); + *additional_conds = additional_conds_i; + } + return; + } + } + /* Neither of the relations is the target relation. */ + Assert(!outerrel_is_target && !innerrel_is_target); + /* + * For semijoin FROM clause is deparsed as an outer relation. An inner + * relation and join clauses are converted to EXISTS condition and + * passed to the upper level. + */ + if (fpinfo->jointype == JOIN_SEMI) { + appendBinaryStringInfo(buf, join_sql_o.data, join_sql_o.len); + } else { + /* For a join relation FROM clause, entry is deparsed as ((outer relation) (inner relation) ON (joinclauses)) */ + appendStringInfo(buf, "(%s %s JOIN %s ON ", join_sql_o.data, get_jointype_name(fpinfo->jointype), join_sql_i.data); + /* Append join clause; (TRUE) if no join clause */ + if (fpinfo->joinclauses) { + deparse_expr_cxt context; + + context.buf = buf; + context.foreignrel = foreignrel; + context.scanrel = foreignrel; + context.root = root; + context.params_list = params_list; + appendStringInfoChar(buf, '('); + appendConditions(fpinfo->joinclauses, &context); + appendStringInfoChar(buf, ')'); + } else { + appendStringInfoString(buf, "(TRUE)"); + } + /* End the FROM clause entry. */ + appendStringInfoChar(buf, ')'); + } + /* Construct additional_conds to be passed to the upper caller from current level additional_conds and additional_conds, coming from inner and outer rels. */ + if (additional_conds_o != NIL) { + *additional_conds = list_concat(*additional_conds, additional_conds_o); + list_free(additional_conds_o); + } + if (additional_conds_i != NIL) { + *additional_conds = list_concat(*additional_conds, additional_conds_i); + list_free(additional_conds_i); + } + } else { + RangeTblEntry *rte = planner_rt_fetch(foreignrel->relid, root); + /* Core code already has some lock on each rel being planned, so we can use NoLock here. */ + Relation rel = table_open(rte->relid, NoLock); + deparseRelation(buf, rel); + /* Add a unique alias to avoid any conflict in relation names due to pulled up subqueries in the query being built for a pushed down join. */ + if (use_alias) + appendStringInfo(buf, " %s%d", REL_ALIAS_PREFIX, foreignrel->relid); + table_close(rel, NoLock); + } +} + +/* Append FROM clause entry for the given relation into buf. + * Conditions from lower-level SEMI-JOINs are appended to additional_conds and should be added to upper level WHERE clause. */ -void deparseFromExprForRel (PlannerInfo* root, RelOptInfo* foreignrel, StringInfo buf, List** params_list) { - DB2FdwState* fdwState = (DB2FdwState*) foreignrel->fdw_private; - - db2Debug1("> deparseFromExprForRel"); - db2Debug2(" buf: '%s",buf->data); - if (IS_SIMPLE_REL (foreignrel)) { - appendStringInfo (buf, "%s", fdwState->db2Table->name); - appendStringInfo (buf, " %s%d", REL_ALIAS_PREFIX, foreignrel->relid); +static void deparseRangeTblRef(StringInfo buf, PlannerInfo *root, RelOptInfo *foreignrel, bool make_subquery, Index ignore_rel, List **ignore_conds, List **additional_conds, List **params_list) { + DB2FdwState* fpinfo = (DB2FdwState*) foreignrel->fdw_private; + + /* Should only be called in these cases. */ + Assert(IS_SIMPLE_REL(foreignrel) || IS_JOIN_REL(foreignrel)); + + Assert(fpinfo->local_conds == NIL); + + /* If make_subquery is true, deparse the relation as a subquery. */ + if (make_subquery) { + List* retrieved_attrs; + int ncols; + + /* The given relation shouldn't contain the target relation, because this should only happen for input relations for a full join, and + * such relations can never contain an UPDATE/DELETE target. + */ + Assert(ignore_rel == 0 || !bms_is_member(ignore_rel, foreignrel->relids)); + + /* Deparse the subquery representing the relation. */ + appendStringInfoChar(buf, '('); + deparseSelectStmtForRel(buf, root, foreignrel, NIL, fpinfo->remote_conds, NIL, false, false, true, &retrieved_attrs, params_list); + appendStringInfoChar(buf, ')'); + + /* Append the relation alias. */ + appendStringInfo(buf, " %s%d", SUBQUERY_REL_ALIAS_PREFIX, fpinfo->relation_index); + + /* Append the column aliases if needed. Note that the subquery emits expressions specified in the relation's reltarget + * (see deparseSubqueryTargetList). + */ + ncols = list_length(foreignrel->reltarget->exprs); + if (ncols > 0) { + int i; + + appendStringInfoChar(buf, '('); + for (i = 1; i <= ncols; i++) { + if (i > 1) + appendStringInfoString(buf, ", "); + + appendStringInfo(buf, "%s%d", SUBQUERY_COL_ALIAS_PREFIX, i); + } + appendStringInfoChar(buf, ')'); + } } else { - /* join relation */ - RelOptInfo* rel_o = fdwState->outerrel; - RelOptInfo* rel_i = fdwState->innerrel; - StringInfoData join_sql_o; - StringInfoData join_sql_i; - ListCell* lc = NULL; - bool is_first = true; - char* where = NULL; - - /* Deparse outer relation */ - initStringInfo (&join_sql_o); - deparseFromExprForRel (root, rel_o, &join_sql_o, params_list); - - /* Deparse inner relation */ - initStringInfo (&join_sql_i); - deparseFromExprForRel (root, rel_i, &join_sql_i, params_list); - - // For a join relation FROM clause entry is deparsed as (outer relation) (inner relation) ON joinclauses - appendStringInfo (buf, "(%s %s JOIN %s ON ", join_sql_o.data, get_jointype_name (fdwState->jointype), join_sql_i.data); - - /* we can only get here if the join is pushed down, so there are join clauses */ - Assert (fdwState->joinclauses); - - foreach (lc, fdwState->joinclauses) { - Expr *expr = (Expr *)lfirst(lc); - /* connect expressions with AND */ - if (!is_first) - appendStringInfo(buf, " AND "); - /* deparse and append a join condition */ - where = deparseExpr(root, foreignrel, expr, params_list); - appendStringInfo(buf, "%s", where); - is_first = false; - } - /* End the FROM clause entry. */ - appendStringInfo (buf, ")"); - } - db2Debug2(" buf: '%s'",buf->data); - db2Debug1("< deparseFromExprForRel"); + deparseFromExprForRel(buf, root, foreignrel, true, ignore_rel, ignore_conds, additional_conds, params_list); + } } /** deparseWhereConditions @@ -1075,6 +1653,173 @@ char* deparseWhereConditions (PlannerInfo* root, RelOptInfo * rel) { return where_clause.data; } +/* Construct a simple "TRUNCATE rel" statement */ +void deparseTruncateSql(StringInfo buf, List* rels, DropBehavior behavior, bool restart_seqs) { + ListCell* cell; + + appendStringInfoString(buf, "TRUNCATE "); + foreach(cell, rels) { + Relation rel = lfirst(cell); + + if (cell != list_head(rels)) + appendStringInfoString(buf, ", "); + deparseRelation(buf, rel); + } + appendStringInfo(buf, " %s IDENTITY", restart_seqs ? "RESTART" : "CONTINUE"); + if (behavior == DROP_RESTRICT) + appendStringInfoString(buf, " RESTRICT"); + else if (behavior == DROP_CASCADE) + appendStringInfoString(buf, " CASCADE"); +} + +/* Construct name to use for given column, and emit it into buf. + * If it has a column_name FDW option, use that instead of attribute name. + * + * If qualify_col is true, qualify column name with the alias of relation. + */ +static void deparseColumnRef(StringInfo buf, int varno, int varattno, RangeTblEntry *rte, bool qualify_col) { + /* We support fetching the remote side's CTID and OID. */ + if (varattno == SelfItemPointerAttributeNumber) { + if (qualify_col) + ADD_REL_QUALIFIER(buf, varno); + appendStringInfoString(buf, "ctid"); + } else if (varattno < 0) { + /* All other system attributes are fetched as 0, except for table OID, which is fetched as the local table OID. However, we must be + * careful; the table could be beneath an outer join, in which case it must go to NULL whenever the rest of the row does. + */ + Oid fetchval = 0; + + if (varattno == TableOidAttributeNumber) + fetchval = rte->relid; + + if (qualify_col) { + appendStringInfoString(buf, "CASE WHEN ("); + ADD_REL_QUALIFIER(buf, varno); + appendStringInfo(buf, "*)::text IS NOT NULL THEN %u END", fetchval); + } else { + appendStringInfo(buf, "%u", fetchval); + } + } else if (varattno == 0) { + /* Whole row reference */ + Relation rel; + Bitmapset* attrs_used; + /* Required only to be passed down to deparseTargetList(). */ + List* retrieved_attrs; + + /* The lock on the relation will be held by upper callers, so it's fine to open it with no lock here. */ + rel = table_open(rte->relid, NoLock); + + /* The local name of the foreign table can not be recognized by the foreign server and the table it references on foreign server might + * have different column ordering or different columns than those declared locally. Hence we have to deparse whole-row reference as + * ROW(columns referenced locally). Construct this by deparsing a "whole row" attribute. + */ + attrs_used = bms_add_member(NULL, 0 - FirstLowInvalidHeapAttributeNumber); + + /* In case the whole-row reference is under an outer join then it has to go NULL whenever the rest of the row goes NULL. Deparsing a join + * query would always involve multiple relations, thus qualify_col would be true. + */ + if (qualify_col) { + appendStringInfoString(buf, "CASE WHEN ("); + ADD_REL_QUALIFIER(buf, varno); + appendStringInfoString(buf, "*)::text IS NOT NULL THEN "); + } + + appendStringInfoString(buf, "ROW("); + deparseTargetList(buf, rte, varno, rel, false, attrs_used, qualify_col, &retrieved_attrs); + appendStringInfoChar(buf, ')'); + + /* Complete the CASE WHEN statement started above. */ + if (qualify_col) + appendStringInfoString(buf, " END"); + + table_close(rel, NoLock); + bms_free(attrs_used); + } else { + char* colname = NULL; + List* options = NIL; + ListCell* lc = NULL; + + /* varno must not be any of OUTER_VAR, INNER_VAR and INDEX_VAR. */ + Assert(!IS_SPECIAL_VARNO(varno)); + + /* If it's a column of a foreign table, and it has the column_name FDW option, use that value. */ + options = GetForeignColumnOptions(rte->relid, varattno); + foreach(lc, options) { + DefElem* def = (DefElem*) lfirst(lc); + if (strcmp(def->defname, "column_name") == 0) { + colname = defGetString(def); + break; + } + } + + /* If it's a column of a regular table or it doesn't have column_name FDW option, use attribute name. */ + if (colname == NULL) + colname = get_attname(rte->relid, varattno, false); + + if (qualify_col) + ADD_REL_QUALIFIER(buf, varno); + + appendStringInfoString(buf, quote_identifier(colname)); + } +} + +/* Append remote name of specified foreign table to buf. + * Use value of table_name FDW option (if any) instead of relation's name. + * Similarly, schema_name FDW option overrides schema name. + */ +static void deparseRelation(StringInfo buf, Relation rel) { + ForeignTable* table; + const char* nspname = NULL; + const char* relname = NULL; + ListCell* lc; + + /* obtain additional catalog information. */ + table = GetForeignTable(RelationGetRelid(rel)); + + /* + * Use value of FDW options if any, instead of the name of object itself. + */ + foreach(lc, table->options) { + DefElem* def = (DefElem*) lfirst(lc); + + if (strcmp(def->defname, "schema_name") == 0) + nspname = defGetString(def); + else if (strcmp(def->defname, "table_name") == 0) + relname = defGetString(def); + } + + /* Note: we could skip printing the schema name if it's pg_catalog, but that doesn't seem worth the trouble. */ + if (nspname == NULL) + nspname = get_namespace_name(RelationGetNamespace(rel)); + if (relname == NULL) + relname = RelationGetRelationName(rel); + + appendStringInfo(buf, "%s.%s", quote_identifier(nspname), quote_identifier(relname)); +} + +/* Append a SQL string literal representing "val" to buf. */ +void deparseStringLiteral(StringInfo buf, const char* val) { + const char* valptr; + + /* Rather than making assumptions about the remote server's value of standard_conforming_strings, always use E'foo' syntax if there are any + * backslashes. + * This will fail on remote servers before 8.1, but those are long out of support. + */ + if (strchr(val, '\\') != NULL) { + appendStringInfoChar(buf, ESCAPE_STRING_SYNTAX); + } + appendStringInfoChar(buf, '\''); + for (valptr = val; *valptr; valptr++) { + char ch = *valptr; + + if (SQL_STR_DOUBLE(ch, true)) { + appendStringInfoChar(buf, ch); + } + appendStringInfoChar(buf, ch); + } + appendStringInfoChar(buf, '\''); +} + /** deparseExpr * Create and return an DB2 SQL string from "expr". * Returns NULL if that is not possible, else an allocated string. @@ -2266,6 +3011,172 @@ char* deparseTimestamp (Datum datum, bool hasTimezone) { return s.data; } +/** Deparse given Var node into context->buf. + * + * If the Var belongs to the foreign relation, just print its remote name. + * Otherwise, it's effectively a Param (and will in fact be a Param at run time). + * Handle it the same way we handle plain Params --- see deparseParam for comments. + */ +static void deparseVar(Var *node, deparse_expr_cxt *context) { + Relids relids = context->scanrel->relids; + int relno; + int colno; + /* Qualify columns when multiple relations are involved. */ + bool qualify_col = (bms_membership(relids) == BMS_MULTIPLE); + + /* If the Var belongs to the foreign relation that is deparsed as a subquery, use the relation and column alias to the Var provided by the + * subquery, instead of the remote name. + */ + if (is_subquery_var(node, context->scanrel, &relno, &colno)) { + appendStringInfo(context->buf, "%s%d.%s%d", SUBQUERY_REL_ALIAS_PREFIX, relno, SUBQUERY_COL_ALIAS_PREFIX, colno); + return; + } + if (bms_is_member(node->varno, relids) && node->varlevelsup == 0) { + deparseColumnRef(context->buf, node->varno, node->varattno, planner_rt_fetch(node->varno, context->root), qualify_col); + } else { + /* Treat like a Param */ + if (context->params_list) { + int pindex = 0; + ListCell* lc; + + /* find its index in params_list */ + foreach(lc, *context->params_list) { + pindex++; + if (equal(node, (Node *) lfirst(lc))) + break; + } + if (lc == NULL) { + /* not in list, so add it */ + pindex++; + *context->params_list = lappend(*context->params_list, node); + } + printRemoteParam(pindex, node->vartype, node->vartypmod, context); + } else { + printRemotePlaceholder(node->vartype, node->vartypmod, context); + } + } +} + +/* + * Deparse given constant value into context->buf. + * + * This function has to be kept in sync with ruleutils.c's get_const_expr. + * + * As in that function, showtype can be -1 to never show "::typename" + * decoration, +1 to always show it, or 0 to show it only if the constant + * wouldn't be assumed to be the right type by default. + * + * In addition, this code allows showtype to be -2 to indicate that we should + * not show "::typename" decoration if the constant is printed as an untyped + * literal or NULL (while in other cases, behaving as for showtype == 0). + */ +static void deparseConst(Const *node, deparse_expr_cxt *context, int showtype) { + StringInfo buf = context->buf; + Oid typoutput; + bool typIsVarlena; + char* extval; + bool isfloat = false; + bool isstring = false; + bool needlabel; + + if (node->constisnull) { + appendStringInfoString(buf, "NULL"); + if (showtype >= 0) + appendStringInfo(buf, "::%s", deparse_type_name(node->consttype, node->consttypmod)); + return; + } + + getTypeOutputInfo(node->consttype, &typoutput, &typIsVarlena); + extval = OidOutputFunctionCall(typoutput, node->constvalue); + + switch (node->consttype) { + case INT2OID: + case INT4OID: + case INT8OID: + case OIDOID: + case FLOAT4OID: + case FLOAT8OID: + case NUMERICOID: { + /* No need to quote unless it's a special value such as 'NaN'. + * See comments in get_const_expr(). + */ + if (strspn(extval, "0123456789+-eE.") == strlen(extval)) { + if (extval[0] == '+' || extval[0] == '-') + appendStringInfo(buf, "(%s)", extval); + else + appendStringInfoString(buf, extval); + if (strcspn(extval, "eE.") != strlen(extval)) + isfloat = true; /* it looks like a float */ + } else { + appendStringInfo(buf, "'%s'", extval); + } + } + break; + case BITOID: + case VARBITOID: + appendStringInfo(buf, "B'%s'", extval); + break; + case BOOLOID: + if (strcmp(extval, "t") == 0) + appendStringInfoString(buf, "true"); + else + appendStringInfoString(buf, "false"); + break; + default: + deparseStringLiteral(buf, extval); + isstring = true; + break; + } + pfree(extval); + if (showtype == -1) + return; /* never print type label */ + + /* For showtype == 0, append ::typename unless the constant will be implicitly typed as the right type when it is read in. + * XXX this code has to be kept in sync with the behavior of the parser, especially make_const. + */ + switch (node->consttype) { + case BOOLOID: + case INT4OID: + case UNKNOWNOID: + needlabel = false; + break; + case NUMERICOID: + needlabel = !isfloat || (node->consttypmod >= 0); + break; + default: + if (showtype == -2) { + /* label unless we printed it as an untyped string */ + needlabel = !isstring; + } else { + needlabel = true; + } + break; + } + if (needlabel || showtype > 0) + appendStringInfo(buf, "::%s", deparse_type_name(node->consttype, node->consttypmod)); +} + +/** Print the name of an operator. + */ +static void deparseOperatorName(StringInfo buf, Form_pg_operator opform) { + char* opname = NULL; + + /* opname is not a SQL identifier, so we should not quote it. */ + opname = NameStr(opform->oprname); + + /* Print schema name only if it's not pg_catalog */ + if (opform->oprnamespace != PG_CATALOG_NAMESPACE) { + const char *opnspname; + + opnspname = get_namespace_name(opform->oprnamespace); + /* Print fully qualified operator name. */ + appendStringInfo(buf, "OPERATOR(%s.%s)", quote_identifier(opnspname), opname); + } else { + /* Just print operator name. */ + appendStringInfoString(buf, opname); + } +} + /** deparsedeparseInterval * Render a PostgreSQL timestamp so that DB2 can parse it. */ @@ -2350,9 +3261,69 @@ static char* deparseInterval (Datum datum) { return s.data; } +/** Deparse the appropriate locking clause (FOR UPDATE or FOR SHARE) for a given relation (context->scanrel). + */ +static void deparseLockingClause(deparse_expr_cxt *context) { + StringInfo buf = context->buf; + PlannerInfo* root = context->root; + RelOptInfo* rel = context->scanrel; + DB2FdwState* fpinfo = (DB2FdwState*) rel->fdw_private; + int relid = -1; + + while ((relid = bms_next_member(rel->relids, relid)) >= 0) { + /* Ignore relation if it appears in a lower subquery. Locking clause for such a relation is included in the subquery if necessary. */ + if (bms_is_member(relid, fpinfo->lower_subquery_rels)) + continue; + + /* Add FOR UPDATE/SHARE if appropriate. + * We apply locking during the initial row fetch, rather than later on as is done for local tables. + * The extra roundtrips involved in trying to duplicate the local semantics exactly don't seem worthwhile + * (see also comments for RowMarkType). + * + * Note: because we actually run the query as a cursor, this assumes that DECLARE CURSOR ... FOR UPDATE is supported, which it isn't before 8.3. + */ + if (bms_is_member(relid, root->all_result_relids) && (root->parse->commandType == CMD_UPDATE || root->parse->commandType == CMD_DELETE)) { + /* Relation is UPDATE/DELETE target, so use FOR UPDATE */ + appendStringInfoString(buf, " FOR UPDATE"); + + /* Add the relation alias if we are here for a join relation */ + if (IS_JOIN_REL(rel)) + appendStringInfo(buf, " OF %s%d", REL_ALIAS_PREFIX, relid); + } else { + PlanRowMark *rc = get_plan_rowmark(root->rowMarks, relid); + if (rc) { + /* Relation is specified as a FOR UPDATE/SHARE target, so handle that. + * (But we could also see LCS_NONE, meaning this isn't a target relation after all.) + * + * For now, just ignore any [NO] KEY specification, + * since (a) it's not clear what that means for a remote table that we don't have complete information about, + * and (b) it wouldn't work anyway on older remote servers. + * Likewise, we don't worry about NOWAIT. + */ + switch (rc->strength) { + case LCS_NONE: + /* No locking needed */ + break; + case LCS_FORKEYSHARE: + case LCS_FORSHARE: + appendStringInfoString(buf, " FOR SHARE"); + break; + case LCS_FORNOKEYUPDATE: + case LCS_FORUPDATE: + appendStringInfoString(buf, " FOR UPDATE"); + break; + } + /* Add the relation alias if we are here for a join relation */ + if (bms_membership(rel->relids) == BMS_MULTIPLE && rc->strength != LCS_NONE) + appendStringInfo(buf, " OF %s%d", REL_ALIAS_PREFIX, relid); + } + } + } +} + /** Output join name for given join type */ -static const char* get_jointype_name (JoinType jointype) { +char* get_jointype_name (JoinType jointype) { char* type = NULL; db2Debug1("> get_jointype_name"); switch (jointype) { @@ -2377,3 +3348,192 @@ static const char* get_jointype_name (JoinType jointype) { db2Debug1("< get_jointype_name"); return type; } + +/** Emit a target list that retrieves the columns specified in attrs_used. + * This is used for both SELECT and RETURNING targetlists; the is_returning parameter is true only for a RETURNING targetlist. + * The tlist text is appended to buf, and we also create an integer List of the columns being retrieved, which is returned to *retrieved_attrs. + * If qualify_col is true, add relation alias before the column name. + */ +static void deparseTargetList(StringInfo buf, RangeTblEntry *rte, Index rtindex, Relation rel, bool is_returning, Bitmapset *attrs_used, bool qualify_col, List **retrieved_attrs) { + TupleDesc tupdesc = RelationGetDescr(rel); + bool have_wholerow; + bool first; + int i; + + *retrieved_attrs = NIL; + + /* If there's a whole-row reference, we'll need all the columns. */ + have_wholerow = bms_is_member(0 - FirstLowInvalidHeapAttributeNumber, attrs_used); + + first = true; + for (i = 1; i <= tupdesc->natts; i++) { + /* Ignore dropped attributes. */ + if (TupleDescCompactAttr(tupdesc, i - 1)->attisdropped) + continue; + + if (have_wholerow || bms_is_member(i - FirstLowInvalidHeapAttributeNumber, attrs_used)) { + if (!first) + appendStringInfoString(buf, ", "); + else if (is_returning) + appendStringInfoString(buf, " RETURNING "); + first = false; + deparseColumnRef(buf, rtindex, i, rte, qualify_col); + *retrieved_attrs = lappend_int(*retrieved_attrs, i); + } + } + + /* Add ctid if needed. We currently don't support retrieving any other system columns. */ + if (bms_is_member(SelfItemPointerAttributeNumber - FirstLowInvalidHeapAttributeNumber, attrs_used)) { + if (!first) + appendStringInfoString(buf, ", "); + else if (is_returning) + appendStringInfoString(buf, " RETURNING "); + first = false; + if (qualify_col) { + ADD_REL_QUALIFIER(buf, rtindex); + } + appendStringInfoString(buf, "ctid"); + *retrieved_attrs = lappend_int(*retrieved_attrs, SelfItemPointerAttributeNumber); + } + /* Don't generate bad syntax if no undropped columns */ + if (first && !is_returning) + appendStringInfoString(buf, "NULL"); +} + +/** Deparse given targetlist and append it to context->buf. + * + * tlist is list of TargetEntry's which in turn contain Var nodes. + * + * retrieved_attrs is the list of continuously increasing integers starting from 1. It has same number of entries as tlist. + * + * This is used for both SELECT and RETURNING targetlists; the is_returning parameter is true only for a RETURNING targetlist. + */ +static void deparseExplicitTargetList(List* tlist, bool is_returning, List** retrieved_attrs, deparse_expr_cxt* context) { + ListCell* lc = NULL; + StringInfo buf = context->buf; + int i = 0; + + *retrieved_attrs = NIL; + + foreach(lc, tlist) { + TargetEntry *tle = lfirst_node(TargetEntry, lc); + + if (i > 0) + appendStringInfoString(buf, ", "); + else if (is_returning) + appendStringInfoString(buf, " RETURNING "); + + deparseExprInt((Expr*) tle->expr, context); + + *retrieved_attrs = lappend_int(*retrieved_attrs, i + 1); + i++; + } + + if (i == 0 && !is_returning) + appendStringInfoString(buf, "NULL"); +} + +/** Emit expressions specified in the given relation's reltarget. + * + * This is used for deparsing the given relation as a subquery. + */ +static void deparseSubqueryTargetList(deparse_expr_cxt* context) { + bool first = true; + ListCell* lc; + + /* Should only be called in these cases. */ + Assert(IS_SIMPLE_REL(context->foreignrel) || IS_JOIN_REL(context->foreignrel)); + foreach(lc, context->foreignrel->reltarget->exprs) { + if (!first) + appendStringInfoString(context->buf, ", "); + first = false; + deparseExprInt((Expr*)lfirst(lc), context); + } + + /* Don't generate bad syntax if no expressions */ + if (first) + appendStringInfoString(context->buf, "NULL"); +} + +/** Given an EquivalenceClass and a foreign relation, find an EC member that can be used to sort the relation remotely according to a pathkey + * using this EC. + * + * If there is more than one suitable candidate, return an arbitrary one of them. If there is none, return NULL. + * + * This checks that the EC member expression uses only Vars from the given rel and is shippable. Caller must separately verify that the pathkey's + * ordering operator is shippable. + */ +EquivalenceMember* find_em_for_rel(PlannerInfo* root, EquivalenceClass* ec, RelOptInfo* rel) { + DB2FdwState* fpinfo = (DB2FdwState*) rel->fdw_private; + EquivalenceMemberIterator it; + EquivalenceMember* em = NULL; + + setup_eclass_member_iterator(&it, ec, rel->relids); + while ((em = eclass_member_iterator_next(&it)) != NULL) { + /* Note we require !bms_is_empty, else we'd accept constant expressions which are not suitable for the purpose. */ + if (bms_is_subset(em->em_relids, rel->relids) + && !bms_is_empty(em->em_relids) + && bms_is_empty(bms_intersect(em->em_relids, fpinfo->hidden_subquery_rels)) + && is_foreign_expr(root, rel, em->em_expr)) + return em; + } + return NULL; +} + +/** Find an EquivalenceClass member that is to be computed as a sort column in the given rel's reltarget, and is shippable. + * + * If there is more than one suitable candidate, return an arbitrary one of them. If there is none, return NULL. + * + * This checks that the EC member expression uses only Vars from the given rel and is shippable. Caller must separately verify that the pathkey's + * ordering operator is shippable. + */ +EquivalenceMember* find_em_for_rel_target(PlannerInfo* root, EquivalenceClass* ec, RelOptInfo* rel) { + PathTarget* target = rel->reltarget; + ListCell* lc1; + int i = 0; + + foreach(lc1, target->exprs) { + Expr* expr = (Expr *) lfirst(lc1); + Index sgref = get_pathtarget_sortgroupref(target, i); + ListCell* lc2 = NULL; + + /* Ignore non-sort expressions */ + if (sgref == 0 || get_sortgroupref_clause_noerr(sgref, root->parse->sortClause) == NULL) { + i++; + continue; + } + + /* We ignore binary-compatible relabeling on both ends */ + while (expr && IsA(expr, RelabelType)) + expr = ((RelabelType*) expr)->arg; + + /* Locate an EquivalenceClass member matching this expr, if any. + * Ignore child members. + */ + foreach(lc2, ec->ec_members) { + EquivalenceMember* em = (EquivalenceMember*) lfirst(lc2); + Expr* em_expr = NULL; + + /* Don't match constants */ + if (em->em_is_const) + continue; + + /* Child members should not exist in ec_members */ + Assert(!em->em_is_child); + + /* Match if same expression (after stripping relabel) */ + em_expr = em->em_expr; + while (em_expr && IsA(em_expr, RelabelType)) + em_expr = ((RelabelType *) em_expr)->arg; + + if (!equal(em_expr, expr)) + continue; + + /* Check that expression (including relabels!) is shippable */ + if (is_foreign_expr(root, rel, em->em_expr)) + return em; + } + i++; + } + return NULL; +} diff --git a/source/db2_fdw_utils.c b/source/db2_fdw_utils.c index 3ddebe8..afe8930 100644 --- a/source/db2_fdw_utils.c +++ b/source/db2_fdw_utils.c @@ -1,13 +1,46 @@ #include + +#include #include + #include + +#include + +#include +#include + #include +#include #include +#include +#include #include + #include + #include "db2_fdw.h" #include "DB2FdwState.h" +/* Hash table for caching the results of shippability lookups */ +static HTAB* ShippableCacheHash = NULL; + +/* Hash key for shippability lookups. + * We include the FDW server OID because decisions may differ per-server. + * Otherwise, objects are identified by their (local!) OID and catalog OID. + */ +typedef struct { + /* XXX we assume this struct contains no padding bytes */ + Oid objid; /* function/operator/type OID */ + Oid classid; /* OID of its catalog (pg_proc, etc) */ + Oid serverid; /* FDW server we are concerned with */ +} ShippableCacheKey; + +typedef struct { + ShippableCacheKey key; /* hash key - must be first */ + bool shippable; +} ShippableCacheEntry; + /** external prototypes */ extern void db2GetLob (DB2Session* session, DB2Column* column, int cidx, char** value, long* value_len, unsigned long trunc); @@ -24,7 +57,14 @@ extern void db2free (void* p); char* guessNlsLang (char* nls_lang); void exitHook (int code, Datum arg); void convertTuple (DB2FdwState* fdw_state, Datum* values, bool* nulls, bool trunc_lob) ; +void reset_transmission_modes (int nestlevel); +int set_transmission_modes (void); +bool is_builtin (Oid objectId); +bool is_shippable (Oid objectId, Oid classId, DB2FdwState* fpinfo); static void errorContextCallback (void* arg); +static void InvalidateShippableCacheCallback(Datum arg, int cacheid, uint32 hashvalue); +static void InitializeShippableCache (void); +static bool lookup_shippable (Oid objectId, Oid classId, DB2FdwState* fpinfo); /** guessNlsLang * If nls_lang is not NULL, return "NLS_LANG=". @@ -343,3 +383,148 @@ static void errorContextCallback (void* arg) { db2Debug1("< %s::errorContextCallback",__FILE__); } +/** Undo the effects of set_transmission_modes(). + */ +void reset_transmission_modes(int nestlevel) { + AtEOXact_GUC(true, nestlevel); +} + +/** Force assorted GUC parameters to settings that ensure that we'll output data values in a form that is unambiguous to the remote server. + * + * This is rather expensive and annoying to do once per row, but there's little choice if we want to be sure values are transmitted accurately; + * we can't leave the settings in place between rows for fear of affecting user-visible computations. + * + * We use the equivalent of a function SET option to allow the settings to persist only until the caller calls reset_transmission_modes(). If an + * error is thrown in between, guc.c will take care of undoing the settings. + * + * The return value is the nestlevel that must be passed to reset_transmission_modes() to undo things. + */ +int set_transmission_modes(void) { + int nestlevel = NewGUCNestLevel(); + + /* The values set here should match what pg_dump does. See also configure_remote_session in connection.c. */ + if (DateStyle != USE_ISO_DATES) + (void) set_config_option("datestyle", "ISO", PGC_USERSET, PGC_S_SESSION, GUC_ACTION_SAVE, true, 0, false); + if (IntervalStyle != INTSTYLE_POSTGRES) + (void) set_config_option("intervalstyle", "postgres", PGC_USERSET, PGC_S_SESSION, GUC_ACTION_SAVE, true, 0, false); + if (extra_float_digits < 3) + (void) set_config_option("extra_float_digits", "3", PGC_USERSET, PGC_S_SESSION, GUC_ACTION_SAVE, true, 0, false); + + /* + * In addition force restrictive search_path, in case there are any + * regproc or similar constants to be printed. + */ + (void) set_config_option("search_path", "pg_catalog", PGC_USERSET, PGC_S_SESSION, GUC_ACTION_SAVE, true, 0, false); + + return nestlevel; +} + +/* Return true if given object is one of PostgreSQL's built-in objects. + * + * We use FirstGenbkiObjectId as the cutoff, so that we only consider objects with hand-assigned OIDs to be "built in", not for instance any + * function or type defined in the information_schema. + * + * Our constraints for dealing with types are tighter than they are for functions or operators: we want to accept only types that are in pg_catalog, + * else deparse_type_name might incorrectly fail to schema-qualify their names. + * Thus we must exclude information_schema types. + * + * XXX there is a problem with this, which is that the set of built-in objects expands over time. + * Something that is built-in to us might not + * be known to the remote server, if it's of an older version. + * But keeping track of that would be a huge exercise. + */ +bool is_builtin (Oid objectId) { + return (objectId < FirstGenbkiObjectId); +} + +/* is_shippable + * Is this object (function/operator/type) shippable to foreign server? + */ +bool is_shippable (Oid objectId, Oid classId, DB2FdwState* fpinfo) { + ShippableCacheKey key; + ShippableCacheEntry* entry; + + /* Built-in objects are presumed shippable. */ + if (is_builtin(objectId)) + return true; + + /* Otherwise, give up if user hasn't specified any shippable extensions. */ + if (fpinfo->shippable_extensions == NIL) + return false; + + /* Initialize cache if first time through. */ + if (!ShippableCacheHash) + InitializeShippableCache(); + + /* Set up cache hash key */ + key.objid = objectId; + key.classid = classId; + key.serverid = fpinfo->fserver->serverid; + + /* See if we already cached the result. */ + entry = (ShippableCacheEntry*) hash_search(ShippableCacheHash, &key, HASH_FIND, NULL); + + if (!entry) { + /* Not found in cache, so perform shippability lookup. */ + bool shippable = lookup_shippable(objectId, classId, fpinfo); + + /* Don't create a new hash entry until *after* we have the shippable result in hand, as the underlying catalog lookups might trigger a + * cache invalidation. + */ + entry = (ShippableCacheEntry*) hash_search(ShippableCacheHash, &key, HASH_ENTER, NULL); + entry->shippable = shippable; + } + return entry->shippable; +} + +/* Flush cache entries when pg_foreign_server is updated. + * + * We do this because of the possibility of ALTER SERVER being used to change a server's extensions option. + * We do not currently bother to check whether objects' extension membership changes once a shippability decision has been + * made for them, however. + */ +static void InvalidateShippableCacheCallback(Datum arg, int cacheid, uint32 hashvalue) { + HASH_SEQ_STATUS status; + ShippableCacheEntry* entry; + + /* In principle we could flush only cache entries relating to the pg_foreign_server entry being outdated; but that would be more + * complicated, and it's probably not worth the trouble. + * So for now, just flush all entries. + */ + hash_seq_init(&status, ShippableCacheHash); + while ((entry = (ShippableCacheEntry *) hash_seq_search(&status)) != NULL) { + if (hash_search(ShippableCacheHash, &entry->key, HASH_REMOVE, NULL) == NULL) + elog(ERROR, "hash table corrupted"); + } +} + +/* Initialize the backend-lifespan cache of shippability decisions. */ +static void InitializeShippableCache(void) { + HASHCTL ctl; + + /* Create the hash table. */ + ctl.keysize = sizeof(ShippableCacheKey); + ctl.entrysize = sizeof(ShippableCacheEntry); + ShippableCacheHash = hash_create("Shippability cache", 256, &ctl, HASH_ELEM | HASH_BLOBS); + + /* Set up invalidation callback on pg_foreign_server. */ + CacheRegisterSyscacheCallback(FOREIGNSERVEROID, InvalidateShippableCacheCallback, (Datum) 0); +} + +/* Returns true if given object (operator/function/type) is shippable according to the server options. + * + * Right now "shippability" is exclusively a function of whether the object belongs to an extension declared by the user. + * In the future we could additionally have a list of functions/operators declared one at a time. + */ +static bool lookup_shippable(Oid objectId, Oid classId, DB2FdwState* fpinfo) { + Oid extensionOid; + + /* Is object a member of some extension? (Note: this is a fairly expensive lookup, which is why we try to cache the results.) */ + extensionOid = getExtensionOfObject(classId, objectId); + + /* If so, is that extension in fpinfo->shippable_extensions? */ + if (OidIsValid(extensionOid) && list_member_oid(fpinfo->shippable_extensions, extensionOid)) + return true; + return false; +} + From 23772122c7179caeceeeae3e9a42590c21ccf21c Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Mon, 26 Jan 2026 18:22:43 +0100 Subject: [PATCH 017/120] just define FIXED_FETCH_SIZE --- include/db2_fdw.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/db2_fdw.h b/include/db2_fdw.h index 7e9680f..4a953ed 100644 --- a/include/db2_fdw.h +++ b/include/db2_fdw.h @@ -55,7 +55,7 @@ #define DEFAULT_MAX_LONG 32767 #define DEFAULT_PREFETCH 100 #define DEFAULT_FETCHSZ 1 -#define FIXED_FETCH_SIZE 1 +#define FIXED_FETCH_SIZE #define DEFAULT_BATCHSZ 100 #define TABLE_NAME_LEN 129 #define COLUMN_NAME_LEN 129 From 71b2f6ea5abba3d0f0374af0554b67457fb49056 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Mon, 26 Jan 2026 18:22:55 +0100 Subject: [PATCH 018/120] added debug tracing --- source/db2_deparse.c | 1291 ++++++++++++++++++++++-------------------- 1 file changed, 671 insertions(+), 620 deletions(-) diff --git a/source/db2_deparse.c b/source/db2_deparse.c index 690a267..b0d22a6 100644 --- a/source/db2_deparse.c +++ b/source/db2_deparse.c @@ -88,6 +88,9 @@ typedef struct deparse_expr_cxt { extern short c2dbType (short fcType); extern void db2Debug1 (const char* message, ...); extern void db2Debug2 (const char* message, ...); +extern void db2Debug3 (const char* message, ...); +extern void db2Debug4 (const char* message, ...); +extern void db2Debug5 (const char* message, ...); extern void* db2alloc (const char* type, size_t size); extern void* db2strdup (const char* source); extern void db2free (void* p); @@ -176,8 +179,9 @@ static void printRemotePlaceholder (Oid paramtype, int32 paramtypmod, * - local_conds contains expressions that can't be evaluated remotely */ void classifyConditions(PlannerInfo* root, RelOptInfo* baserel, List* input_conds, List** remote_conds, List** local_conds) { - ListCell* lc; + ListCell* lc = NULL; + db2Debug1("> %s::classifyConditions",__FILE__); *remote_conds = NIL; *local_conds = NIL; @@ -189,6 +193,7 @@ void classifyConditions(PlannerInfo* root, RelOptInfo* baserel, List* input_cond else *local_conds = lappend(*local_conds, ri); } + db2Debug1("< %s::classifyConditions",__FILE__); } /** Returns true if given expr is safe to evaluate on the foreign server. @@ -197,8 +202,9 @@ bool is_foreign_expr(PlannerInfo *root, RelOptInfo *baserel, Expr *expr) { foreign_glob_cxt glob_cxt; foreign_loc_cxt loc_cxt; DB2FdwState* fpinfo = (DB2FdwState*) (baserel->fdw_private); - bool bResult = false; + bool fResult = false; + db2Debug1("> %s::is_foreign_expr",__FILE__); // Check that the expression consists of nodes that are safe to execute remotely. glob_cxt.root = root; glob_cxt.foreignrel = baserel; @@ -216,12 +222,13 @@ bool is_foreign_expr(PlannerInfo *root, RelOptInfo *baserel, Expr *expr) { * Future versions might be able to make this choice with more granularity. (We check this last because it requires a lot of expensive catalog lookups.) */ if (!contain_mutable_functions((Node*) expr)) { - bResult = true; + fResult = true; } } } + db2Debug1("< %s::is_foreign_expr : %s",__FILE__, (fResult) ? "true": "false"); /* OK to evaluate on the remote server */ - return bResult; + return fResult; } /** Check if expression is safe to execute remotely, and return true if so. @@ -241,566 +248,554 @@ bool is_foreign_expr(PlannerInfo *root, RelOptInfo *baserel, Expr *expr) { * currently considered here. */ static bool foreign_expr_walker(Node* node, foreign_glob_cxt* glob_cxt, foreign_loc_cxt* outer_cxt, foreign_loc_cxt* case_arg_cxt) { - bool check_type = true; - DB2FdwState* fpinfo = NULL; - foreign_loc_cxt inner_cxt; - Oid collation; - FDWCollateState state; + bool fResult = true; + + db2Debug4("> %s::foreign_expr_walker",__FILE__); /* Need do nothing for empty subexpressions */ - if (node == NULL) - return true; - /* May need server info from baserel's fdw_private struct */ - fpinfo = (DB2FdwState*) glob_cxt->foreignrel->fdw_private; - /* Set up inner_cxt for possible recursion to child nodes */ - inner_cxt.collation = InvalidOid; - inner_cxt.state = FDW_COLLATE_NONE; - switch (nodeTag(node)) { - case T_Var: { - Var *var = (Var *) node; - /** If the Var is from the foreign table, we consider its - * collation (if any) safe to use. If it is from another - * table, we treat its collation the same way as we would a - * Param's collation, ie it's not safe for it to have a - * non-default collation. - */ - if (bms_is_member(var->varno, glob_cxt->relids) && var->varlevelsup == 0) { - /* Var belongs to foreign table */ - /** System columns other than ctid should not be sent to - * the remote, since we don't make any effort to ensure - * that local and remote values match (tableoid, in - * particular, almost certainly doesn't match). + if (node != NULL) { + bool check_type = true; + DB2FdwState* fpinfo = (DB2FdwState*) glob_cxt->foreignrel->fdw_private; + foreign_loc_cxt inner_cxt; + Oid collation; + FDWCollateState state; + + /* Set up inner_cxt for possible recursion to child nodes */ + inner_cxt.collation = InvalidOid; + inner_cxt.state = FDW_COLLATE_NONE; + switch (nodeTag(node)) { + case T_Var: { + Var* var = (Var*) node; + /* If the Var is from the foreign table, we consider its collation (if any) safe to use. + * If it is from another table, we treat its collation the same way as we would a Param's collation, + * ie it's not safe for it to have a non-default collation. */ - if (var->varattno < 0 && var->varattno != SelfItemPointerAttributeNumber) - return false; - - /* Else check the collation */ - collation = var->varcollid; - state = OidIsValid(collation) ? FDW_COLLATE_SAFE : FDW_COLLATE_NONE; - } else { - /* Var belongs to some other table */ - collation = var->varcollid; - if (collation == InvalidOid || collation == DEFAULT_COLLATION_OID) { - /** It's noncollatable, or it's safe to combine with a - * collatable foreign Var, so set state to NONE. + if (bms_is_member(var->varno, glob_cxt->relids) && var->varlevelsup == 0) { + /* Var belongs to foreign table + * System columns other than ctid should not be sent to the remote, since we don't make any effort to ensure + * that local and remote values match (tableoid, in particular, almost certainly doesn't match). */ - state = FDW_COLLATE_NONE; + if (var->varattno < 0 && var->varattno != SelfItemPointerAttributeNumber) + return false; + + /* Else check the collation */ + collation = var->varcollid; + state = OidIsValid(collation) ? FDW_COLLATE_SAFE : FDW_COLLATE_NONE; } else { - /** Do not fail right away, since the Var might appear - * in a collation-insensitive context. - */ - state = FDW_COLLATE_UNSAFE; + /* Var belongs to some other table */ + collation = var->varcollid; + if (collation == InvalidOid || collation == DEFAULT_COLLATION_OID) { + /* It's noncollatable, or it's safe to combine with a collatable foreign Var, so set state to NONE. */ + state = FDW_COLLATE_NONE; + } else { + /* Do not fail right away, since the Var might appear in a collation-insensitive context. */ + state = FDW_COLLATE_UNSAFE; + } } } - } - break; - case T_Const: { - Const* c = (Const *) node; - - /** Constants of regproc and related types can't be shipped - * unless the referenced object is shippable. But NULL's ok. - * (See also the related code in dependency.c.) - */ - if (!c->constisnull) { - switch (c->consttype) { - case REGPROCOID: - case REGPROCEDUREOID: - if (!is_shippable(DatumGetObjectId(c->constvalue), ProcedureRelationId, fpinfo)) - return false; - break; - case REGOPEROID: - case REGOPERATOROID: - if (!is_shippable(DatumGetObjectId(c->constvalue), OperatorRelationId, fpinfo)) - return false; - break; - case REGCLASSOID: - if (!is_shippable(DatumGetObjectId(c->constvalue), RelationRelationId, fpinfo)) - return false; - break; - case REGTYPEOID: - if (!is_shippable(DatumGetObjectId(c->constvalue), TypeRelationId, fpinfo)) - return false; - break; - case REGCOLLATIONOID: - if (!is_shippable(DatumGetObjectId(c->constvalue), CollationRelationId, fpinfo)) - return false; - break; - case REGCONFIGOID: - /** For text search objects only, we weaken the normal shippability criterion to - * allow all OIDs below FirstNormalObjectId. Without this, none of the initdb-installed - * TS configurations would be shippable, which would be quite annoying. - */ - if (DatumGetObjectId(c->constvalue) >= FirstNormalObjectId && !is_shippable(DatumGetObjectId(c->constvalue), TSConfigRelationId, fpinfo)) - return false; - break; - case REGDICTIONARYOID: - if (DatumGetObjectId(c->constvalue) >= FirstNormalObjectId && !is_shippable(DatumGetObjectId(c->constvalue), TSDictionaryRelationId, fpinfo)) - return false; - break; - case REGNAMESPACEOID: - if (!is_shippable(DatumGetObjectId(c->constvalue), NamespaceRelationId, fpinfo)) - return false; - break; - case REGROLEOID: - if (!is_shippable(DatumGetObjectId(c->constvalue), AuthIdRelationId, fpinfo)) - return false; - break; - #ifdef REGDATABASEOID - case REGDATABASEOID: - if (!is_shippable(DatumGetObjectId(c->constvalue), DatabaseRelationId, fpinfo)) - return false; - break; - #endif + break; + case T_Const: { + Const* c = (Const*) node; + + /** Constants of regproc and related types can't be shipped + * unless the referenced object is shippable. But NULL's ok. + * (See also the related code in dependency.c.) + */ + if (!c->constisnull) { + switch (c->consttype) { + case REGPROCOID: + case REGPROCEDUREOID: + if (!is_shippable(DatumGetObjectId(c->constvalue), ProcedureRelationId, fpinfo)) + return false; + break; + case REGOPEROID: + case REGOPERATOROID: + if (!is_shippable(DatumGetObjectId(c->constvalue), OperatorRelationId, fpinfo)) + return false; + break; + case REGCLASSOID: + if (!is_shippable(DatumGetObjectId(c->constvalue), RelationRelationId, fpinfo)) + return false; + break; + case REGTYPEOID: + if (!is_shippable(DatumGetObjectId(c->constvalue), TypeRelationId, fpinfo)) + return false; + break; + case REGCOLLATIONOID: + if (!is_shippable(DatumGetObjectId(c->constvalue), CollationRelationId, fpinfo)) + return false; + break; + case REGCONFIGOID: + /* For text search objects only, we weaken the normal shippability criterion to allow all OIDs below FirstNormalObjectId. + * Without this, none of the initdb-installed TS configurations would be shippable, which would be quite annoying. + */ + if (DatumGetObjectId(c->constvalue) >= FirstNormalObjectId && !is_shippable(DatumGetObjectId(c->constvalue), TSConfigRelationId, fpinfo)) + return false; + break; + case REGDICTIONARYOID: + if (DatumGetObjectId(c->constvalue) >= FirstNormalObjectId && !is_shippable(DatumGetObjectId(c->constvalue), TSDictionaryRelationId, fpinfo)) + return false; + break; + case REGNAMESPACEOID: + if (!is_shippable(DatumGetObjectId(c->constvalue), NamespaceRelationId, fpinfo)) + return false; + break; + case REGROLEOID: + if (!is_shippable(DatumGetObjectId(c->constvalue), AuthIdRelationId, fpinfo)) + return false; + break; + #ifdef REGDATABASEOID + case REGDATABASEOID: + if (!is_shippable(DatumGetObjectId(c->constvalue), DatabaseRelationId, fpinfo)) + return false; + break; + #endif + } } + /* If the constant has nondefault collation, either it's of a non-builtin type, or it reflects folding of a CollateExpr. + * It's unsafe to send to the remote unless it's used in a non-collation-sensitive context. + */ + collation = c->constcollid; + state = (collation == InvalidOid || collation == DEFAULT_COLLATION_OID) ? FDW_COLLATE_NONE : FDW_COLLATE_UNSAFE; } + break; + case T_Param: { + Param *p = (Param *) node; + + /** If it's a MULTIEXPR Param, punt. We can't tell from here whether the referenced sublink/subplan contains any remote + * Vars; if it does, handling that is too complicated to consider supporting at present. Fortunately, MULTIEXPR + * Params are not reduced to plain PARAM_EXEC until the end of planning, so we can easily detect this case. (Normal + * PARAM_EXEC Params are safe to ship because their values come from somewhere else in the plan tree; but a MULTIEXPR + * references a sub-select elsewhere in the same targetlist, so we'd be on the hook to evaluate it somehow if we wanted + * to handle such cases as direct foreign updates.) + */ + if (p->paramkind == PARAM_MULTIEXPR) + return false; + + /** Collation rule is same as for Consts and non-foreign Vars. */ + collation = p->paramcollid; + state = (collation == InvalidOid || collation == DEFAULT_COLLATION_OID) ? FDW_COLLATE_NONE : FDW_COLLATE_UNSAFE; + } + break; + case T_SubscriptingRef: { + SubscriptingRef *sr = (SubscriptingRef *) node; + // Assignment should not be in restrictions. + if (sr->refassgnexpr != NULL) + return false; + // Recurse into the remaining subexpressions. + // The container subscripts will not affect collation of the SubscriptingRef result, so do those first and reset inner_cxt afterwards. + if (!foreign_expr_walker((Node *) sr->refupperindexpr, glob_cxt, &inner_cxt, case_arg_cxt)) + return false; + inner_cxt.collation = InvalidOid; + inner_cxt.state = FDW_COLLATE_NONE; + if (!foreign_expr_walker((Node *) sr->reflowerindexpr, glob_cxt, &inner_cxt, case_arg_cxt)) + return false; + inner_cxt.collation = InvalidOid; + inner_cxt.state = FDW_COLLATE_NONE; + if (!foreign_expr_walker((Node *) sr->refexpr, glob_cxt, &inner_cxt, case_arg_cxt)) + return false; + // Container subscripting typically yields same collation as refexpr's, but in case it doesn't, use same logic as for function nodes. + collation = sr->refcollid; + if (collation == InvalidOid) + state = FDW_COLLATE_NONE; + else if (inner_cxt.state == FDW_COLLATE_SAFE && collation == inner_cxt.collation) + state = FDW_COLLATE_SAFE; + else if (collation == DEFAULT_COLLATION_OID) + state = FDW_COLLATE_NONE; + else + state = FDW_COLLATE_UNSAFE; + } + break; + case T_FuncExpr: { + FuncExpr* fe = (FuncExpr*) node; + + /* If function used by the expression is not shippable, it can't be sent to remote because it might have incompatible + * semantics on remote side. + */ + if (!is_shippable(fe->funcid, ProcedureRelationId, fpinfo)) + return false; + + // Recurse to input subexpressions. + if (!foreign_expr_walker((Node *) fe->args, glob_cxt, &inner_cxt, case_arg_cxt)) + return false; + + // If function's input collation is not derived from a foreign Var, it can't be sent to remote. + if (fe->inputcollid == InvalidOid) + /* OK, inputs are all noncollatable */ ; + else if (inner_cxt.state != FDW_COLLATE_SAFE || fe->inputcollid != inner_cxt.collation) + return false; + + /* Detect whether node is introducing a collation not derived from a foreign Var. (If so, we just mark it unsafe for now + * rather than immediately returning false, since the parent node might not care.) + */ + collation = fe->funccollid; + if (collation == InvalidOid) + state = FDW_COLLATE_NONE; + else if (inner_cxt.state == FDW_COLLATE_SAFE && collation == inner_cxt.collation) + state = FDW_COLLATE_SAFE; + else if (collation == DEFAULT_COLLATION_OID) + state = FDW_COLLATE_NONE; + else + state = FDW_COLLATE_UNSAFE; + } + break; + case T_OpExpr: + case T_DistinctExpr: { /* struct-equivalent to OpExpr */ + OpExpr* oe = (OpExpr*) node; + + // Similarly, only shippable operators can be sent to remote. + // (If the operator is shippable, we assume its underlying function is too.) + if (!is_shippable(oe->opno, OperatorRelationId, fpinfo)) + return false; - /** If the constant has nondefault collation, either it's of a - * non-builtin type, or it reflects folding of a CollateExpr. - * It's unsafe to send to the remote unless it's used in a - * non-collation-sensitive context. - */ - collation = c->constcollid; - state = (collation == InvalidOid || collation == DEFAULT_COLLATION_OID) ? FDW_COLLATE_NONE : FDW_COLLATE_UNSAFE; - } - break; - case T_Param: { - Param *p = (Param *) node; - - /** If it's a MULTIEXPR Param, punt. We can't tell from here whether the referenced sublink/subplan contains any remote - * Vars; if it does, handling that is too complicated to consider supporting at present. Fortunately, MULTIEXPR - * Params are not reduced to plain PARAM_EXEC until the end of planning, so we can easily detect this case. (Normal - * PARAM_EXEC Params are safe to ship because their values come from somewhere else in the plan tree; but a MULTIEXPR - * references a sub-select elsewhere in the same targetlist, so we'd be on the hook to evaluate it somehow if we wanted - * to handle such cases as direct foreign updates.) - */ - if (p->paramkind == PARAM_MULTIEXPR) - return false; - - /** Collation rule is same as for Consts and non-foreign Vars. */ - collation = p->paramcollid; - state = (collation == InvalidOid || collation == DEFAULT_COLLATION_OID) ? FDW_COLLATE_NONE : FDW_COLLATE_UNSAFE; - } - break; - case T_SubscriptingRef: { - SubscriptingRef *sr = (SubscriptingRef *) node; - // Assignment should not be in restrictions. - if (sr->refassgnexpr != NULL) - return false; - // Recurse into the remaining subexpressions. - // The container subscripts will not affect collation of the SubscriptingRef result, so do those first and reset inner_cxt afterwards. - if (!foreign_expr_walker((Node *) sr->refupperindexpr, glob_cxt, &inner_cxt, case_arg_cxt)) - return false; - inner_cxt.collation = InvalidOid; - inner_cxt.state = FDW_COLLATE_NONE; - if (!foreign_expr_walker((Node *) sr->reflowerindexpr, glob_cxt, &inner_cxt, case_arg_cxt)) - return false; - inner_cxt.collation = InvalidOid; - inner_cxt.state = FDW_COLLATE_NONE; - if (!foreign_expr_walker((Node *) sr->refexpr, glob_cxt, &inner_cxt, case_arg_cxt)) - return false; - // Container subscripting typically yields same collation as refexpr's, but in case it doesn't, use same logic as for function nodes. - collation = sr->refcollid; - if (collation == InvalidOid) - state = FDW_COLLATE_NONE; - else if (inner_cxt.state == FDW_COLLATE_SAFE && collation == inner_cxt.collation) - state = FDW_COLLATE_SAFE; - else if (collation == DEFAULT_COLLATION_OID) - state = FDW_COLLATE_NONE; - else - state = FDW_COLLATE_UNSAFE; - } - break; - case T_FuncExpr: { - FuncExpr* fe = (FuncExpr*) node; - - /* If function used by the expression is not shippable, it can't be sent to remote because it might have incompatible - * semantics on remote side. - */ - if (!is_shippable(fe->funcid, ProcedureRelationId, fpinfo)) - return false; - - // Recurse to input subexpressions. - if (!foreign_expr_walker((Node *) fe->args, glob_cxt, &inner_cxt, case_arg_cxt)) - return false; - - // If function's input collation is not derived from a foreign Var, it can't be sent to remote. - if (fe->inputcollid == InvalidOid) - /* OK, inputs are all noncollatable */ ; - else if (inner_cxt.state != FDW_COLLATE_SAFE || fe->inputcollid != inner_cxt.collation) - return false; - - /* Detect whether node is introducing a collation not derived from a foreign Var. (If so, we just mark it unsafe for now - * rather than immediately returning false, since the parent node might not care.) - */ - collation = fe->funccollid; - if (collation == InvalidOid) - state = FDW_COLLATE_NONE; - else if (inner_cxt.state == FDW_COLLATE_SAFE && collation == inner_cxt.collation) - state = FDW_COLLATE_SAFE; - else if (collation == DEFAULT_COLLATION_OID) - state = FDW_COLLATE_NONE; - else - state = FDW_COLLATE_UNSAFE; - } - break; - case T_OpExpr: - case T_DistinctExpr: { /* struct-equivalent to OpExpr */ - OpExpr* oe = (OpExpr*) node; - - // Similarly, only shippable operators can be sent to remote. - // (If the operator is shippable, we assume its underlying function is too.) - if (!is_shippable(oe->opno, OperatorRelationId, fpinfo)) - return false; - - // Recurse to input subexpressions. - if (!foreign_expr_walker((Node *) oe->args, glob_cxt, &inner_cxt, case_arg_cxt)) - return false; - - // If operator's input collation is not derived from a foreign Var, it can't be sent to remote. - if (oe->inputcollid == InvalidOid) - /* OK, inputs are all noncollatable */ ; - else if (inner_cxt.state != FDW_COLLATE_SAFE || oe->inputcollid != inner_cxt.collation) - return false; - - // Result-collation handling is same as for functions - collation = oe->opcollid; - if (collation == InvalidOid) - state = FDW_COLLATE_NONE; - else if (inner_cxt.state == FDW_COLLATE_SAFE && collation == inner_cxt.collation) - state = FDW_COLLATE_SAFE; - else if (collation == DEFAULT_COLLATION_OID) - state = FDW_COLLATE_NONE; - else - state = FDW_COLLATE_UNSAFE; - } - break; - case T_ScalarArrayOpExpr: { - ScalarArrayOpExpr *oe = (ScalarArrayOpExpr *) node; - - // Again, only shippable operators can be sent to remote. - if (!is_shippable(oe->opno, OperatorRelationId, fpinfo)) - return false; - - // Recurse to input subexpressions. - if (!foreign_expr_walker((Node *) oe->args, glob_cxt, &inner_cxt, case_arg_cxt)) - return false; + // Recurse to input subexpressions. + if (!foreign_expr_walker((Node *) oe->args, glob_cxt, &inner_cxt, case_arg_cxt)) + return false; - // If operator's input collation is not derived from a foreign Var, it can't be sent to remote. - if (oe->inputcollid == InvalidOid) - /* OK, inputs are all noncollatable */ ; - else if (inner_cxt.state != FDW_COLLATE_SAFE || oe->inputcollid != inner_cxt.collation) - return false; + // If operator's input collation is not derived from a foreign Var, it can't be sent to remote. + if (oe->inputcollid == InvalidOid) + /* OK, inputs are all noncollatable */ ; + else if (inner_cxt.state != FDW_COLLATE_SAFE || oe->inputcollid != inner_cxt.collation) + return false; - // Output is always boolean and so noncollatable. - collation = InvalidOid; - state = FDW_COLLATE_NONE; - } - break; - case T_RelabelType: { - RelabelType* r = (RelabelType*) node; - // Recurse to input subexpression. - if (!foreign_expr_walker((Node *) r->arg, glob_cxt, &inner_cxt, case_arg_cxt)) - return false; - // RelabelType must not introduce a collation not derived from an input foreign Var (same logic as for a real function). - collation = r->resultcollid; - if (collation == InvalidOid) - state = FDW_COLLATE_NONE; - else if (inner_cxt.state == FDW_COLLATE_SAFE && collation == inner_cxt.collation) - state = FDW_COLLATE_SAFE; - else if (collation == DEFAULT_COLLATION_OID) - state = FDW_COLLATE_NONE; - else - state = FDW_COLLATE_UNSAFE; - } - break; - case T_ArrayCoerceExpr: { - ArrayCoerceExpr *e = (ArrayCoerceExpr *) node; - // Recurse to input subexpression. - if (!foreign_expr_walker((Node *) e->arg, glob_cxt, &inner_cxt, case_arg_cxt)) - return false; - // T_ArrayCoerceExpr must not introduce a collation not derived from an input foreign Var (same logic as for a function). - collation = e->resultcollid; - if (collation == InvalidOid) + // Result-collation handling is same as for functions + collation = oe->opcollid; + if (collation == InvalidOid) + state = FDW_COLLATE_NONE; + else if (inner_cxt.state == FDW_COLLATE_SAFE && collation == inner_cxt.collation) + state = FDW_COLLATE_SAFE; + else if (collation == DEFAULT_COLLATION_OID) + state = FDW_COLLATE_NONE; + else + state = FDW_COLLATE_UNSAFE; + } + break; + case T_ScalarArrayOpExpr: { + ScalarArrayOpExpr *oe = (ScalarArrayOpExpr *) node; + + // Again, only shippable operators can be sent to remote. + if (!is_shippable(oe->opno, OperatorRelationId, fpinfo)) + return false; + + // Recurse to input subexpressions. + if (!foreign_expr_walker((Node *) oe->args, glob_cxt, &inner_cxt, case_arg_cxt)) + return false; + + // If operator's input collation is not derived from a foreign Var, it can't be sent to remote. + if (oe->inputcollid == InvalidOid) + /* OK, inputs are all noncollatable */ ; + else if (inner_cxt.state != FDW_COLLATE_SAFE || oe->inputcollid != inner_cxt.collation) + return false; + + // Output is always boolean and so noncollatable. + collation = InvalidOid; state = FDW_COLLATE_NONE; - else if (inner_cxt.state == FDW_COLLATE_SAFE && collation == inner_cxt.collation) - state = FDW_COLLATE_SAFE; - else if (collation == DEFAULT_COLLATION_OID) + } + break; + case T_RelabelType: { + RelabelType* r = (RelabelType*) node; + // Recurse to input subexpression. + if (!foreign_expr_walker((Node *) r->arg, glob_cxt, &inner_cxt, case_arg_cxt)) + return false; + // RelabelType must not introduce a collation not derived from an input foreign Var (same logic as for a real function). + collation = r->resultcollid; + if (collation == InvalidOid) + state = FDW_COLLATE_NONE; + else if (inner_cxt.state == FDW_COLLATE_SAFE && collation == inner_cxt.collation) + state = FDW_COLLATE_SAFE; + else if (collation == DEFAULT_COLLATION_OID) + state = FDW_COLLATE_NONE; + else + state = FDW_COLLATE_UNSAFE; + } + break; + case T_ArrayCoerceExpr: { + ArrayCoerceExpr *e = (ArrayCoerceExpr *) node; + // Recurse to input subexpression. + if (!foreign_expr_walker((Node *) e->arg, glob_cxt, &inner_cxt, case_arg_cxt)) + return false; + // T_ArrayCoerceExpr must not introduce a collation not derived from an input foreign Var (same logic as for a function). + collation = e->resultcollid; + if (collation == InvalidOid) + state = FDW_COLLATE_NONE; + else if (inner_cxt.state == FDW_COLLATE_SAFE && collation == inner_cxt.collation) + state = FDW_COLLATE_SAFE; + else if (collation == DEFAULT_COLLATION_OID) + state = FDW_COLLATE_NONE; + else + state = FDW_COLLATE_UNSAFE; + } + break; + case T_BoolExpr: { + BoolExpr* b = (BoolExpr*) node; + // Recurse to input subexpressions. + if (!foreign_expr_walker((Node*) b->args, glob_cxt, &inner_cxt, case_arg_cxt)) + return false; + // Output is always boolean and so noncollatable. + collation = InvalidOid; state = FDW_COLLATE_NONE; - else - state = FDW_COLLATE_UNSAFE; - } - break; - case T_BoolExpr: { - BoolExpr* b = (BoolExpr*) node; - // Recurse to input subexpressions. - if (!foreign_expr_walker((Node*) b->args, glob_cxt, &inner_cxt, case_arg_cxt)) - return false; - // Output is always boolean and so noncollatable. - collation = InvalidOid; - state = FDW_COLLATE_NONE; - } - break; - case T_NullTest: { - NullTest* nt = (NullTest*) node; - // Recurse to input subexpressions. - if (!foreign_expr_walker((Node*) nt->arg, glob_cxt, &inner_cxt, case_arg_cxt)) - return false; - // Output is always boolean and so noncollatable. - collation = InvalidOid; - state = FDW_COLLATE_NONE; - } - break; - case T_CaseExpr: { - CaseExpr* ce = (CaseExpr*) node; - foreign_loc_cxt arg_cxt; - foreign_loc_cxt tmp_cxt; - ListCell* lc; - // Recurse to CASE's arg expression, if any. Its collation has to be saved aside for use while examining CaseTestExprs within the WHEN expressions. - arg_cxt.collation = InvalidOid; - arg_cxt.state = FDW_COLLATE_NONE; - if (ce->arg) { - if (!foreign_expr_walker((Node *) ce->arg, glob_cxt, &arg_cxt, case_arg_cxt)) + } + break; + case T_NullTest: { + NullTest* nt = (NullTest*) node; + // Recurse to input subexpressions. + if (!foreign_expr_walker((Node*) nt->arg, glob_cxt, &inner_cxt, case_arg_cxt)) return false; + // Output is always boolean and so noncollatable. + collation = InvalidOid; + state = FDW_COLLATE_NONE; } - - // Examine the CaseWhen subexpressions. - foreach(lc, ce->args) { - CaseWhen* cw = lfirst_node(CaseWhen, lc); + break; + case T_CaseExpr: { + CaseExpr* ce = (CaseExpr*) node; + foreign_loc_cxt arg_cxt; + foreign_loc_cxt tmp_cxt; + ListCell* lc; + // Recurse to CASE's arg expression, if any. Its collation has to be saved aside for use while examining CaseTestExprs within the WHEN expressions. + arg_cxt.collation = InvalidOid; + arg_cxt.state = FDW_COLLATE_NONE; if (ce->arg) { - /* In a CASE-with-arg, the parser should have produced WHEN clauses of the form "CaseTestExpr = RHS", - * possibly with an implicit coercion inserted above the CaseTestExpr. However in an expression that's - * been through the optimizer, the WHEN clause could be almost anything (since the equality operator - * could have been expanded into an inline function). - * In such cases forbid pushdown, because deparseCaseExpr can't handle it. + if (!foreign_expr_walker((Node *) ce->arg, glob_cxt, &arg_cxt, case_arg_cxt)) + return false; + } + + // Examine the CaseWhen subexpressions. + foreach(lc, ce->args) { + CaseWhen* cw = lfirst_node(CaseWhen, lc); + if (ce->arg) { + /* In a CASE-with-arg, the parser should have produced WHEN clauses of the form "CaseTestExpr = RHS", + * possibly with an implicit coercion inserted above the CaseTestExpr. However in an expression that's + * been through the optimizer, the WHEN clause could be almost anything (since the equality operator + * could have been expanded into an inline function). + * In such cases forbid pushdown, because deparseCaseExpr can't handle it. + */ + Node* whenExpr = (Node*) cw->expr; + List* opArgs = NULL; + if (!IsA(whenExpr, OpExpr)) + return false; + opArgs = ((OpExpr *) whenExpr)->args; + if (list_length(opArgs) != 2 || !IsA(strip_implicit_coercions(linitial(opArgs)), CaseTestExpr)) + return false; + } + /* Recurse to WHEN expression, passing down the arg info. + * Its collation doesn't affect the result (really, it should be boolean and thus not have a collation). */ - Node* whenExpr = (Node*) cw->expr; - List* opArgs = NULL; - if (!IsA(whenExpr, OpExpr)) + tmp_cxt.collation = InvalidOid; + tmp_cxt.state = FDW_COLLATE_NONE; + if (!foreign_expr_walker((Node *) cw->expr, glob_cxt, &tmp_cxt, &arg_cxt)) return false; - opArgs = ((OpExpr *) whenExpr)->args; - if (list_length(opArgs) != 2 || !IsA(strip_implicit_coercions(linitial(opArgs)), CaseTestExpr)) + /* Recurse to THEN expression. */ + if (!foreign_expr_walker((Node *) cw->result, glob_cxt, &inner_cxt, case_arg_cxt)) return false; } - /* Recurse to WHEN expression, passing down the arg info. - * Its collation doesn't affect the result (really, it should be boolean and thus not have a collation). - */ - tmp_cxt.collation = InvalidOid; - tmp_cxt.state = FDW_COLLATE_NONE; - if (!foreign_expr_walker((Node *) cw->expr, glob_cxt, &tmp_cxt, &arg_cxt)) + // Recurse to ELSE expression. + if (!foreign_expr_walker((Node *) ce->defresult, glob_cxt, &inner_cxt, case_arg_cxt)) return false; - /* Recurse to THEN expression. */ - if (!foreign_expr_walker((Node *) cw->result, glob_cxt, &inner_cxt, case_arg_cxt)) + /* Detect whether node is introducing a collation not derived from a foreign Var. (If so, we just mark it unsafe for now + * rather than immediately returning false, since the parent node might not care.) This is the same as for function + * nodes, except that the input collation is derived from only the THEN and ELSE subexpressions. + */ + collation = ce->casecollid; + if (collation == InvalidOid) + state = FDW_COLLATE_NONE; + else if (inner_cxt.state == FDW_COLLATE_SAFE && collation == inner_cxt.collation) + state = FDW_COLLATE_SAFE; + else if (collation == DEFAULT_COLLATION_OID) + state = FDW_COLLATE_NONE; + else + state = FDW_COLLATE_UNSAFE; + } + break; + case T_CaseTestExpr: { + CaseTestExpr* c = (CaseTestExpr*) node; + // Punt if we seem not to be inside a CASE arg WHEN. + if (!case_arg_cxt) return false; + // Otherwise, any nondefault collation attached to the CaseTestExpr node must be derived from foreign Var(s) in the CASE arg. + collation = c->collation; + if (collation == InvalidOid) + state = FDW_COLLATE_NONE; + else if (case_arg_cxt->state == FDW_COLLATE_SAFE && collation == case_arg_cxt->collation) + state = FDW_COLLATE_SAFE; + else if (collation == DEFAULT_COLLATION_OID) + state = FDW_COLLATE_NONE; + else + state = FDW_COLLATE_UNSAFE; } - // Recurse to ELSE expression. - if (!foreign_expr_walker((Node *) ce->defresult, glob_cxt, &inner_cxt, case_arg_cxt)) - return false; - /* Detect whether node is introducing a collation not derived from a foreign Var. (If so, we just mark it unsafe for now - * rather than immediately returning false, since the parent node might not care.) This is the same as for function - * nodes, except that the input collation is derived from only the THEN and ELSE subexpressions. - */ - collation = ce->casecollid; - if (collation == InvalidOid) - state = FDW_COLLATE_NONE; - else if (inner_cxt.state == FDW_COLLATE_SAFE && collation == inner_cxt.collation) - state = FDW_COLLATE_SAFE; - else if (collation == DEFAULT_COLLATION_OID) - state = FDW_COLLATE_NONE; - else - state = FDW_COLLATE_UNSAFE; - } - break; - case T_CaseTestExpr: { - CaseTestExpr* c = (CaseTestExpr*) node; - // Punt if we seem not to be inside a CASE arg WHEN. - if (!case_arg_cxt) - return false; - // Otherwise, any nondefault collation attached to the CaseTestExpr node must be derived from foreign Var(s) in the CASE arg. - collation = c->collation; - if (collation == InvalidOid) - state = FDW_COLLATE_NONE; - else if (case_arg_cxt->state == FDW_COLLATE_SAFE && collation == case_arg_cxt->collation) - state = FDW_COLLATE_SAFE; - else if (collation == DEFAULT_COLLATION_OID) - state = FDW_COLLATE_NONE; - else - state = FDW_COLLATE_UNSAFE; - } - break; - case T_ArrayExpr: { - ArrayExpr* a = (ArrayExpr *) node; - // Recurse to input subexpressions. - if (!foreign_expr_walker((Node *) a->elements, glob_cxt, &inner_cxt, case_arg_cxt)) - return false; - // ArrayExpr must not introduce a collation not derived from an input foreign Var (same logic as for a function). - collation = a->array_collid; - if (collation == InvalidOid) - state = FDW_COLLATE_NONE; - else if (inner_cxt.state == FDW_COLLATE_SAFE && collation == inner_cxt.collation) - state = FDW_COLLATE_SAFE; - else if (collation == DEFAULT_COLLATION_OID) - state = FDW_COLLATE_NONE; - else - state = FDW_COLLATE_UNSAFE; - } - break; - case T_List: { - List* l = (List*) node; - ListCell* lc = NULL; - // Recurse to component subexpressions. - foreach(lc, l) { - if (!foreign_expr_walker((Node *) lfirst(lc), glob_cxt, &inner_cxt, case_arg_cxt)) + break; + case T_ArrayExpr: { + ArrayExpr* a = (ArrayExpr *) node; + // Recurse to input subexpressions. + if (!foreign_expr_walker((Node *) a->elements, glob_cxt, &inner_cxt, case_arg_cxt)) return false; + // ArrayExpr must not introduce a collation not derived from an input foreign Var (same logic as for a function). + collation = a->array_collid; + if (collation == InvalidOid) + state = FDW_COLLATE_NONE; + else if (inner_cxt.state == FDW_COLLATE_SAFE && collation == inner_cxt.collation) + state = FDW_COLLATE_SAFE; + else if (collation == DEFAULT_COLLATION_OID) + state = FDW_COLLATE_NONE; + else + state = FDW_COLLATE_UNSAFE; } - // When processing a list, collation state just bubbles up from the list elements. - collation = inner_cxt.collation; - state = inner_cxt.state; - // Don't apply exprType() to the list. - check_type = false; - } - break; - case T_Aggref: { - Aggref* agg = (Aggref*) node; - ListCell* lc = NULL; - // Not safe to pushdown when not in grouping context - if (!IS_UPPER_REL(glob_cxt->foreignrel)) - return false; - // Only non-split aggregates are pushable. - if (agg->aggsplit != AGGSPLIT_SIMPLE) - return false; - // As usual, it must be shippable. - if (!is_shippable(agg->aggfnoid, ProcedureRelationId, fpinfo)) - return false; - // Recurse to input args. aggdirectargs, aggorder and aggdistinct are all present in args, so no need to check their shippability explicitly. - foreach(lc, agg->args) { - Node* n = (Node*) lfirst(lc); - // If TargetEntry, extract the expression from it - if (IsA(n, TargetEntry)) { - TargetEntry* tle = (TargetEntry*) n; - n = (Node*) tle->expr; + break; + case T_List: { + List* l = (List*) node; + ListCell* lc = NULL; + // Recurse to component subexpressions. + foreach(lc, l) { + if (!foreign_expr_walker((Node *) lfirst(lc), glob_cxt, &inner_cxt, case_arg_cxt)) + return false; } - if (!foreign_expr_walker(n, glob_cxt, &inner_cxt, case_arg_cxt)) - return false; + // When processing a list, collation state just bubbles up from the list elements. + collation = inner_cxt.collation; + state = inner_cxt.state; + // Don't apply exprType() to the list. + check_type = false; } - // For aggorder elements, check whether the sort operator, if specified, is shippable or not. - if (agg->aggorder) { - foreach(lc, agg->aggorder) { - SortGroupClause* srt = (SortGroupClause*) lfirst(lc); - Oid sortcoltype; - TypeCacheEntry* typentry; - TargetEntry* tle; - - tle = get_sortgroupref_tle(srt->tleSortGroupRef, agg->args); - sortcoltype = exprType((Node *) tle->expr); - typentry = lookup_type_cache(sortcoltype, TYPECACHE_LT_OPR | TYPECACHE_GT_OPR); - // Check shippability of non-default sort operator. - if (srt->sortop != typentry->lt_opr && srt->sortop != typentry->gt_opr && !is_shippable(srt->sortop, OperatorRelationId, fpinfo)) + break; + case T_Aggref: { + Aggref* agg = (Aggref*) node; + ListCell* lc = NULL; + // Not safe to pushdown when not in grouping context + if (!IS_UPPER_REL(glob_cxt->foreignrel)) + return false; + // Only non-split aggregates are pushable. + if (agg->aggsplit != AGGSPLIT_SIMPLE) + return false; + // As usual, it must be shippable. + if (!is_shippable(agg->aggfnoid, ProcedureRelationId, fpinfo)) + return false; + // Recurse to input args. aggdirectargs, aggorder and aggdistinct are all present in args, so no need to check their shippability explicitly. + foreach(lc, agg->args) { + Node* n = (Node*) lfirst(lc); + // If TargetEntry, extract the expression from it + if (IsA(n, TargetEntry)) { + TargetEntry* tle = (TargetEntry*) n; + n = (Node*) tle->expr; + } + if (!foreign_expr_walker(n, glob_cxt, &inner_cxt, case_arg_cxt)) return false; } + // For aggorder elements, check whether the sort operator, if specified, is shippable or not. + if (agg->aggorder) { + foreach(lc, agg->aggorder) { + SortGroupClause* srt = (SortGroupClause*) lfirst(lc); + Oid sortcoltype; + TypeCacheEntry* typentry; + TargetEntry* tle; + + tle = get_sortgroupref_tle(srt->tleSortGroupRef, agg->args); + sortcoltype = exprType((Node *) tle->expr); + typentry = lookup_type_cache(sortcoltype, TYPECACHE_LT_OPR | TYPECACHE_GT_OPR); + // Check shippability of non-default sort operator. + if (srt->sortop != typentry->lt_opr && srt->sortop != typentry->gt_opr && !is_shippable(srt->sortop, OperatorRelationId, fpinfo)) + return false; + } + } + // Check aggregate filter + if (!foreign_expr_walker((Node *) agg->aggfilter, glob_cxt, &inner_cxt, case_arg_cxt)) + return false; + // If aggregate's input collation is not derived from a foreign Var, it can't be sent to remote. + if (agg->inputcollid == InvalidOid) + /* OK, inputs are all noncollatable */ ; + else if (inner_cxt.state != FDW_COLLATE_SAFE || agg->inputcollid != inner_cxt.collation) + return false; + /* Detect whether node is introducing a collation not derived from a foreign Var. (If so, we just mark it unsafe for now + * rather than immediately returning false, since the parent node might not care.) + */ + collation = agg->aggcollid; + if (collation == InvalidOid) + state = FDW_COLLATE_NONE; + else if (inner_cxt.state == FDW_COLLATE_SAFE && collation == inner_cxt.collation) + state = FDW_COLLATE_SAFE; + else if (collation == DEFAULT_COLLATION_OID) + state = FDW_COLLATE_NONE; + else + state = FDW_COLLATE_UNSAFE; } - // Check aggregate filter - if (!foreign_expr_walker((Node *) agg->aggfilter, glob_cxt, &inner_cxt, case_arg_cxt)) - return false; - // If aggregate's input collation is not derived from a foreign Var, it can't be sent to remote. - if (agg->inputcollid == InvalidOid) - /* OK, inputs are all noncollatable */ ; - else if (inner_cxt.state != FDW_COLLATE_SAFE || agg->inputcollid != inner_cxt.collation) + break; + default: + /* If it's anything else, assume it's unsafe. + * This list can be expanded later, but don't forget to add deparse support below. + */ return false; - /* Detect whether node is introducing a collation not derived from a foreign Var. (If so, we just mark it unsafe for now - * rather than immediately returning false, since the parent node might not care.) - */ - collation = agg->aggcollid; - if (collation == InvalidOid) - state = FDW_COLLATE_NONE; - else if (inner_cxt.state == FDW_COLLATE_SAFE && collation == inner_cxt.collation) - state = FDW_COLLATE_SAFE; - else if (collation == DEFAULT_COLLATION_OID) - state = FDW_COLLATE_NONE; - else - state = FDW_COLLATE_UNSAFE; } - break; - default: - /** If it's anything else, assume it's unsafe. This list can be - * expanded later, but don't forget to add deparse support below. - */ + /* If result type of given expression is not shippable, it can't be sent to remote because it might have incompatible semantics on remote side. */ + if (check_type && !is_shippable(exprType(node), TypeRelationId, fpinfo)) return false; - } - /** If result type of given expression is not shippable, it can't be sent - * to remote because it might have incompatible semantics on remote side. - */ - if (check_type && !is_shippable(exprType(node), TypeRelationId, fpinfo)) - return false; - /** Now, merge my collation information into my parent's state. */ - if (state > outer_cxt->state) { - /* Override previous parent state */ - outer_cxt->collation = collation; - outer_cxt->state = state; - } else if (state == outer_cxt->state) { - /* Merge, or detect error if there's a collation conflict */ - switch (state) { - case FDW_COLLATE_NONE: { - /* Nothing + nothing is still nothing */ - } - break; - case FDW_COLLATE_SAFE: { - if (collation != outer_cxt->collation) { - /** Non-default collation always beats default.*/ - if (outer_cxt->collation == DEFAULT_COLLATION_OID) { - /* Override previous parent state */ - outer_cxt->collation = collation; - } else if (collation != DEFAULT_COLLATION_OID) { - /** Conflict; show state as indeterminate. We don't - * want to "return false" right away, since parent - * node might not care about collation. - */ - outer_cxt->state = FDW_COLLATE_UNSAFE; + /* Now, merge my collation information into my parent's state. */ + if (state > outer_cxt->state) { + /* Override previous parent state */ + outer_cxt->collation = collation; + outer_cxt->state = state; + } else if (state == outer_cxt->state) { + /* Merge, or detect error if there's a collation conflict */ + switch (state) { + case FDW_COLLATE_NONE: { + /* Nothing + nothing is still nothing */ + } + break; + case FDW_COLLATE_SAFE: { + if (collation != outer_cxt->collation) { + /** Non-default collation always beats default.*/ + if (outer_cxt->collation == DEFAULT_COLLATION_OID) { + /* Override previous parent state */ + outer_cxt->collation = collation; + } else if (collation != DEFAULT_COLLATION_OID) { + /* Conflict; show state as indeterminate. + * We don't want to "return false" right away, since parent node might not care about collation. + */ + outer_cxt->state = FDW_COLLATE_UNSAFE; + } } } + break; + case FDW_COLLATE_UNSAFE: { + /* We're still conflicted ... */ + } + break; } - break; - case FDW_COLLATE_UNSAFE: { - /* We're still conflicted ... */ - } - break; } } /* It looks OK */ - return true; + db2Debug4("< %s::foreign_expr_walker : %s",__FILE__, (fResult) ? "true" : "false"); + return fResult; } -/** Returns true if given expr is something we'd have to send the value of - * to the foreign server. +/** Returns true if given expr is something we'd have to send the value of to the foreign server. * - * This should return true when the expression is a shippable node that - * deparseExpr would add to context->params_list. Note that we don't care - * if the expression *contains* such a node, only whether one appears at top - * level. We need this to detect cases where setrefs.c would recognize a - * false match between an fdw_exprs item (which came from the params_list) - * and an entry in fdw_scan_tlist (which we're considering putting the given - * expression into). + * This should return true when the expression is a shippable node that deparseExpr would add to context->params_list. + * Note that we don't care if the expression *contains* such a node, only whether one appears at top level. + * We need this to detect cases where setrefs.c would recognize a false match between an fdw_exprs item (which came from the params_list) + * and an entry in fdw_scan_tlist (which we're considering putting the given expression into). */ bool is_foreign_param(PlannerInfo* root, RelOptInfo* baserel, Expr* expr) { - bool bResult = false; + bool fResult = false; + db2Debug1("> %s::is_foreign_param", __FILE__); + db2Debug2(" expr: %x", expr); if (expr != NULL) { + db2Debug5(" ((Node*)expr)->type: %d", nodeTag(expr)); switch (nodeTag(expr)) { case T_Var: { /* It would have to be sent unless it's a foreign Var */ - Var* var = (Var *) expr; + Var* var = (Var*) expr; DB2FdwState* fpinfo = (DB2FdwState*) (baserel->fdw_private); Relids relids; relids = (IS_UPPER_REL(baserel)) ? fpinfo->outerrel->relids : baserel->relids; - bResult = !(bms_is_member(var->varno, relids) && var->varlevelsup == 0); + fResult = !(bms_is_member(var->varno, relids) && var->varlevelsup == 0); } break; case T_Param: /* Params always have to be sent to the foreign server */ - bResult = true; + fResult = true; default: break; } } - return bResult; + db2Debug1("< %s::is_foreign_param : %s",__FILE__, (fResult) ? "true" : "false"); + return fResult; } /** Returns true if it's safe to push down the sort expression described by @@ -809,19 +804,19 @@ bool is_foreign_param(PlannerInfo* root, RelOptInfo* baserel, Expr* expr) { bool is_foreign_pathkey(PlannerInfo* root, RelOptInfo* baserel, PathKey* pathkey) { EquivalenceClass* pathkey_ec = pathkey->pk_eclass; DB2FdwState* fpinfo = (DB2FdwState*) baserel->fdw_private; - bool bResult = false; + bool fResult = false; - /** is_foreign_expr would detect volatile expressions as well, but checking - * ec_has_volatile here saves some cycles. - */ + db2Debug4("> %s::is_foreign_pathkey",__FILE__); + /* is_foreign_expr would detect volatile expressions as well, but checking ec_has_volatile here saves some cycles. */ if (!pathkey_ec->ec_has_volatile) { /* can't push down the sort if the pathkey's opfamily is not shippable */ if (is_shippable(pathkey->pk_opfamily, OperatorFamilyRelationId, fpinfo)) { /* can push if a suitable EC member exists */ - bResult = (find_em_for_rel(root, pathkey_ec, baserel) != NULL); + fResult = (find_em_for_rel(root, pathkey_ec, baserel) != NULL); } } - return bResult; + db2Debug4("< %s::is_foreign_pathkey : %s",__FILE__, (fResult) ? "true" : "false"); + return fResult; } /* Convert type OID + typmod info into a type name we can ship to the remote server. @@ -833,12 +828,16 @@ bool is_foreign_pathkey(PlannerInfo* root, RelOptInfo* baserel, PathKey* pathkey * We assume here that built-in types are all in pg_catalog and need not be qualified; otherwise, qualify. */ static char* deparse_type_name(Oid type_oid, int32 typemod) { - bits16 flags = FORMAT_TYPE_TYPEMOD_GIVEN; + bits16 flags = FORMAT_TYPE_TYPEMOD_GIVEN; + char* result = NULL; + db2Debug4("> %s::deparse_type_name",__FILE__); if (!is_builtin(type_oid)) flags |= FORMAT_TYPE_FORCE_QUALIFY; - return format_type_extended(type_oid, typemod, flags); + result = format_type_extended(type_oid, typemod, flags); + db2Debug4("< %s::deparse_type_name : %s",__FILE__, result); + return result; } /** appendAsType @@ -875,33 +874,33 @@ void appendAsType (StringInfoData* dest, Oid type) { /** Deparse GROUP BY clause. */ static void appendGroupByClause(List* tlist, deparse_expr_cxt* context) { - StringInfo buf = context->buf; Query* query = context->root->parse; - ListCell* lc = NULL; - bool first = true; + db2Debug4("> %s::appendGroupByClause",__FILE__); /* Nothing to be done, if there's no GROUP BY clause in the query. */ - if (!query->groupClause) - return; - - appendStringInfoString(buf, " GROUP BY "); - - /** Queries with grouping sets are not pushed down, so we don't expect grouping sets here. - */ - Assert(!query->groupingSets); - - /* We intentionally print query->groupClause not processed_groupClause, leaving it to the remote planner to get rid of any redundant GROUP BY - * items again. This is necessary in case processed_groupClause reduced to empty, and in any case the redundancy situation on the remote might - * be different than what we think here. - */ - foreach(lc, query->groupClause) { - SortGroupClause *grp = (SortGroupClause *) lfirst(lc); - - if (!first) - appendStringInfoString(buf, ", "); - first = false; - deparseSortGroupClause(grp->tleSortGroupRef, tlist, true, context); + if (query->groupClause) { + StringInfo buf = context->buf; + ListCell* lc = NULL; + bool first = true; + + appendStringInfoString(buf, " GROUP BY "); + /* Queries with grouping sets are not pushed down, so we don't expect grouping sets here. */ + Assert(!query->groupingSets); + + /* We intentionally print query->groupClause not processed_groupClause, leaving it to the remote planner to get rid of any redundant GROUP BY + * items again. This is necessary in case processed_groupClause reduced to empty, and in any case the redundancy situation on the remote might + * be different than what we think here. + */ + foreach(lc, query->groupClause) { + SortGroupClause *grp = (SortGroupClause*) lfirst(lc); + if (!first) + appendStringInfoString(buf, ", "); + first = false; + deparseSortGroupClause(grp->tleSortGroupRef, tlist, true, context); + } + db2Debug5(" clause: %s", buf->data); } + db2Debug4("< %s::appendGroupByClause",__FILE__); } /** Deparse ORDER BY clause defined by the given pathkeys. @@ -913,11 +912,12 @@ static void appendGroupByClause(List* tlist, deparse_expr_cxt* context) { * should have verified that there is one) and deparse it. */ static void appendOrderByClause(List* pathkeys, bool has_final_sort, deparse_expr_cxt* context) { - ListCell* lcell; - int nestlevel; + ListCell* lcell = NULL; + int nestlevel = 0; StringInfo buf = context->buf; bool gotone = false; + db2Debug4("> %s::appendOrderByClause",__FILE__); /* Make sure any constants in the exprs are printed portably */ nestlevel = set_transmission_modes(); @@ -941,11 +941,12 @@ static void appendOrderByClause(List* pathkeys, bool has_final_sort, deparse_exp em_expr = em->em_expr; - /* If the member is a Const expression then we needn't add it to the ORDER BY clause. This can happen in UNION ALL queries where the - * union child targetlist has a Const. Adding these would be wasteful, but also, for INT columns, an integer literal would be - * seen as an ordinal column position rather than a value to sort by. - * deparseConst() does have code to handle this, but it seems less effort on all accounts just to skip these for ORDER BY clauses. - */ + /* If the member is a Const expression then we needn't add it to the ORDER BY clause. + * This can happen in UNION ALL queries where the union child targetlist has a Const. + * Adding these would be wasteful, but also, for INT columns, an integer literal would be seen as an ordinal column position rather + * than a value to sort by. + * deparseConst() does have code to handle this, but it seems less effort on all accounts just to skip these for ORDER BY clauses. + */ if (IsA(em_expr, Const)) continue; @@ -967,6 +968,8 @@ static void appendOrderByClause(List* pathkeys, bool has_final_sort, deparse_exp appendOrderBySuffix(oprid, exprType((Node *) em_expr), pathkey->pk_nulls_first, context); } reset_transmission_modes(nestlevel); + db2Debug5(" clause: %s", context->buf->data); + db2Debug4("< %s::appendOrderByClause",__FILE__); } /** Deparse LIMIT/OFFSET clause. @@ -976,6 +979,7 @@ static void appendLimitClause(deparse_expr_cxt* context) { StringInfo buf = context->buf; int nestlevel = 0; + db2Debug4("> %s::appendLimitClause",__FILE__); /* Make sure any constants in the exprs are printed portably */ nestlevel = set_transmission_modes(); @@ -988,6 +992,8 @@ static void appendLimitClause(deparse_expr_cxt* context) { deparseExprInt((Expr*) root->parse->limitOffset, context); } reset_transmission_modes(nestlevel); + db2Debug5(" clause: %s", context->buf->data); + db2Debug4("< %s::appendLimitClause",__FILE__); } /** appendFunctionName @@ -998,6 +1004,7 @@ static void appendFunctionName(Oid funcid, deparse_expr_cxt *context) { HeapTuple proctup; Form_pg_proc procform; + db2Debug4("> %s::appendFunctionName",__FILE__); proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid)); if (!HeapTupleIsValid(proctup)) elog(ERROR, "cache lookup failed for function %u", funcid); @@ -1010,6 +1017,8 @@ static void appendFunctionName(Oid funcid, deparse_expr_cxt *context) { /* Always print the function name */ appendStringInfoString(buf, quote_identifier(NameStr(procform->proname))); ReleaseSysCache(proctup); + db2Debug5(" function name: %s", context->buf->data); + db2Debug4("< %s::appendFunctionName",__FILE__); } /** Append the ASC, DESC, USING and NULLS FIRST / NULLS LAST parts of an ORDER BY clause. @@ -1018,6 +1027,7 @@ static void appendOrderBySuffix(Oid sortop, Oid sortcoltype, bool nulls_first, d StringInfo buf = context->buf; TypeCacheEntry* typentry; + db2Debug4("< %s::appendOrderBySuffix",__FILE__); /* See whether operator is default < or > for sort expr's datatype. */ typentry = lookup_type_cache(sortcoltype, TYPECACHE_LT_OPR | TYPECACHE_GT_OPR); @@ -1026,11 +1036,10 @@ static void appendOrderBySuffix(Oid sortop, Oid sortcoltype, bool nulls_first, d else if (sortop == typentry->gt_opr) appendStringInfoString(buf, " DESC"); else { - HeapTuple opertup; - Form_pg_operator operform; + HeapTuple opertup; + Form_pg_operator operform; appendStringInfoString(buf, " USING "); - /* Append operator name. */ opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(sortop)); if (!HeapTupleIsValid(opertup)) @@ -1039,11 +1048,10 @@ static void appendOrderBySuffix(Oid sortop, Oid sortcoltype, bool nulls_first, d deparseOperatorName(buf, operform); ReleaseSysCache(opertup); } + appendStringInfo(buf, " NULLS %s", (nulls_first) ? "FIRST" : "LAST"); - if (nulls_first) - appendStringInfoString(buf, " NULLS FIRST"); - else - appendStringInfoString(buf, " NULLS LAST"); + db2Debug5(" order by suffix: %s", buf->data); + db2Debug4("< %s::appendOrderBySuffix",__FILE__); } /* Print the representation of a parameter to be sent to the remote side. @@ -1055,7 +1063,10 @@ static void printRemoteParam(int paramindex, Oid paramtype, int32 paramtypmod, d StringInfo buf = context->buf; char* ptypename = deparse_type_name(paramtype, paramtypmod); + db2Debug4("> printRemoteParam",__FILE__); appendStringInfo(buf, "$%d::%s", paramindex, ptypename); + db2Debug5(" remoteParam: %s", buf->data); + db2Debug4("< printRemoteParam",__FILE__); } /* Print the representation of a placeholder for a parameter that will be sent to the remote side at execution time. @@ -1074,7 +1085,10 @@ static void printRemotePlaceholder(Oid paramtype, int32 paramtypmod, deparse_exp StringInfo buf = context->buf; char* ptypename = deparse_type_name(paramtype, paramtypmod); + db2Debug4("> printRemotePlaceholder",__FILE__); appendStringInfo(buf, "((SELECT null::%s)::%s)", ptypename, ptypename); + db2Debug5(" remotePlaceholder: %s", buf->data); + db2Debug4("< printRemotePlaceholder",__FILE__); } /** Deparse conditions from the provided list and append them to buf. @@ -1083,12 +1097,13 @@ static void printRemotePlaceholder(Oid paramtype, int32 paramtypmod, deparse_exp * * Depending on the caller, the list elements might be either RestrictInfos or bare clauses. */ -static void appendConditions(List *exprs, deparse_expr_cxt *context) { +static void appendConditions(List* exprs, deparse_expr_cxt* context) { int nestlevel = 0; ListCell* lc = NULL; bool is_first = true; StringInfo buf = context->buf; + db2Debug4("> appendConditions",__FILE__); /* Make sure any constants in the exprs are printed portably */ nestlevel = set_transmission_modes(); @@ -1110,6 +1125,8 @@ static void appendConditions(List *exprs, deparse_expr_cxt *context) { is_first = false; } reset_transmission_modes(nestlevel); + db2Debug5(" conditions: %s", buf->data); + db2Debug4("< appendConditions",__FILE__); } /* Append WHERE clause, containing conditions from exprs and additional_conds, to context->buf. @@ -1119,6 +1136,7 @@ static void appendWhereClause(List* exprs, List* additional_conds, deparse_expr_ bool need_and = false; ListCell* lc = NULL; + db2Debug4("> appendWhereClause",__FILE__); if (exprs != NIL || additional_conds != NIL) appendStringInfoString(buf, " WHERE "); @@ -1132,9 +1150,11 @@ static void appendWhereClause(List* exprs, List* additional_conds, deparse_expr_ foreach(lc, additional_conds) { if (need_and) appendStringInfoString(buf, " AND "); - appendStringInfoString(buf, (char *) lfirst(lc)); + appendStringInfoString(buf, (char*) lfirst(lc)); need_and = true; } + db2Debug5(" where clause: %s", buf->data); + db2Debug4("< appendWhereClause",__FILE__); } /** Appends a sort or group clause. @@ -1142,20 +1162,18 @@ static void appendWhereClause(List* exprs, List* additional_conds, deparse_expr_ * Like get_rule_sortgroupclause(), returns the expression tree, so caller * need not find it again. */ -static Node* deparseSortGroupClause(Index ref, List *tlist, bool force_colno, deparse_expr_cxt *context) { +static Node* deparseSortGroupClause(Index ref, List* tlist, bool force_colno, deparse_expr_cxt* context) { StringInfo buf = context->buf; - TargetEntry* tle = NULL; - Expr* expr = NULL; - - tle = get_sortgroupref_tle(ref, tlist); - expr = tle->expr; + TargetEntry* tle = get_sortgroupref_tle(ref, tlist); + Expr* expr = tle->expr; + db2Debug4("> %s::deparseSortGroupClause",__FILE__); if (force_colno) { /* Use column-number form when requested by caller. */ Assert(!tle->resjunk); appendStringInfo(buf, "%d", tle->resno); } else if (expr && IsA(expr, Const)) { - // Force a typecast here so that we don't emit something like "GROUP BY 2", which will be misconstrued as a column position rather than a constant. + /* Force a typecast here so that we don't emit something like "GROUP BY 2", which will be misconstrued as a column position rather than a constant. */ deparseConst((Const*) expr, context, 1); } else if (!expr || IsA(expr, Var)) { deparseExprInt(expr, context); @@ -1165,63 +1183,66 @@ static Node* deparseSortGroupClause(Index ref, List *tlist, bool force_colno, de deparseExprInt(expr, context); appendStringInfoChar(buf, ')'); } - return (Node*) expr; + db2Debug5(" clause: %s", buf->data); + db2Debug4("< %s::deparseSortGroupClause : %x",__FILE__, expr); + return (Node*)expr; } /* Returns true if given Var is deparsed as a subquery output column, in which case, *relno and *colno are set to the IDs for the relation and * column alias to the Var provided by the subquery. */ static bool is_subquery_var(Var* node, RelOptInfo* foreignrel, int* relno, int* colno) { - DB2FdwState* fpinfo = (DB2FdwState*) foreignrel->fdw_private; - RelOptInfo* outerrel = fpinfo->outerrel; - RelOptInfo* innerrel = fpinfo->innerrel; + bool fResult = false; + db2Debug5("> %s::is_subquery_var",__FILE__); /* Should only be called in these cases. */ Assert(IS_SIMPLE_REL(foreignrel) || IS_JOIN_REL(foreignrel)); - /* If the given relation isn't a join relation, it doesn't have any lower subqueries, so the Var isn't a subquery output column. */ - if (!IS_JOIN_REL(foreignrel)) - return false; - - /* If the Var doesn't belong to any lower subqueries, it isn't a subquery output column. */ - if (!bms_is_member(node->varno, fpinfo->lower_subquery_rels)) - return false; - - if (bms_is_member(node->varno, outerrel->relids)) { - /* If outer relation is deparsed as a subquery, the Var is an output column of the subquery; get the IDs for the relation/column alias. */ - if (fpinfo->make_outerrel_subquery) { - get_relation_column_alias_ids(node, outerrel, relno, colno); - return true; - } - - /* Otherwise, recurse into the outer relation. */ - return is_subquery_var(node, outerrel, relno, colno); - } else { - Assert(bms_is_member(node->varno, innerrel->relids)); - - /* If inner relation is deparsed as a subquery, the Var is an output column of the subquery; get the IDs for the relation/column alias. */ - if (fpinfo->make_innerrel_subquery) { - get_relation_column_alias_ids(node, innerrel, relno, colno); - return true; + if (IS_JOIN_REL(foreignrel)) { + DB2FdwState* fpinfo = (DB2FdwState*) foreignrel->fdw_private; + + /* If the Var doesn't belong to any lower subqueries, it isn't a subquery output column. */ + if (bms_is_member(node->varno, fpinfo->lower_subquery_rels)) { + if (bms_is_member(node->varno, fpinfo->outerrel->relids)) { + /* If outer relation is deparsed as a subquery, the Var is an output column of the subquery; get the IDs for the relation/column alias. */ + if (fpinfo->make_outerrel_subquery) { + get_relation_column_alias_ids(node, fpinfo->outerrel, relno, colno); + fResult = true; + } else { + /* Otherwise, recurse into the outer relation. */ + fResult = is_subquery_var(node, fpinfo->outerrel, relno, colno); + } + } else { + Assert(bms_is_member(node->varno, fpinfo->innerrel->relids)); + /* If inner relation is deparsed as a subquery, the Var is an output column of the subquery; get the IDs for the relation/column alias. */ + if (fpinfo->make_innerrel_subquery) { + get_relation_column_alias_ids(node, fpinfo->innerrel, relno, colno); + fResult = true; + } else { + /* Otherwise, recurse into the inner relation. */ + fResult = is_subquery_var(node, fpinfo->innerrel, relno, colno); + } + } } - - /* Otherwise, recurse into the inner relation. */ - return is_subquery_var(node, innerrel, relno, colno); } + db2Debug5("< %s::is_subquery_var : %s",__FILE__, (fResult) ? "true": "false"); + return fResult; } /* Get the IDs for the relation and column alias to given Var belonging to given relation, which are returned into *relno and *colno. */ static void get_relation_column_alias_ids(Var* node, RelOptInfo* foreignrel, int* relno, int* colno) { - DB2FdwState* fpinfo = (DB2FdwState*) foreignrel->fdw_private; - int i; - ListCell* lc; + DB2FdwState* fpinfo = (DB2FdwState*) foreignrel->fdw_private; + int i = 1; + ListCell* lc = NULL; + bool fFound = false; + db2Debug4("> %s::get_relation_column_alias_ids",__FILE__); /* Get the relation alias ID */ *relno = fpinfo->relation_index; + db2Debug5(" relno: %d", *relno); /* Get the column alias ID */ - i = 1; foreach(lc, foreignrel->reltarget->exprs) { Var* tlvar = (Var*) lfirst(lc); @@ -1230,13 +1251,18 @@ static void get_relation_column_alias_ids(Var* node, RelOptInfo* foreignrel, int */ if (IsA(tlvar, Var) && tlvar->varno == node->varno && tlvar->varattno == node->varattno) { *colno = i; - return; + db2Debug5(" colno: %d", *colno); + fFound = true; + break; } i++; } - /* Shouldn't get here */ - elog(ERROR, "unexpected expression in subquery output"); + if (!fFound) { + /* Shouldn't get here */ + elog(ERROR, "unexpected expression in subquery output"); + } + db2Debug4("< %s::get_relation_column_alias_ids",__FILE__); } /* Build the targetlist for given relation to be deparsed as SELECT clause. @@ -1248,18 +1274,22 @@ static void get_relation_column_alias_ids(Var* node, RelOptInfo* foreignrel, int List* build_tlist_to_deparse(RelOptInfo* foreignrel) { List* tlist = NIL; DB2FdwState* fpinfo = (DB2FdwState*) foreignrel->fdw_private; - ListCell* lc; + db2Debug1("> %s::build_tlist_to_deparse",__FILE__); /* For an upper relation, we have already built the target list while checking shippability, so just return that. */ - if (IS_UPPER_REL(foreignrel)) - return fpinfo->grouped_tlist; + if (IS_UPPER_REL(foreignrel)) { + tlist = fpinfo->grouped_tlist; + } else { + ListCell* lc = NULL; - /* We require columns specified in foreignrel->reltarget->exprs and those required for evaluating the local conditions. */ - tlist = add_to_flat_tlist(tlist, pull_var_clause((Node*) foreignrel->reltarget->exprs, PVC_RECURSE_PLACEHOLDERS)); - foreach(lc, fpinfo->local_conds) { - RestrictInfo* rinfo = lfirst_node(RestrictInfo, lc); - tlist = add_to_flat_tlist(tlist, pull_var_clause((Node *) rinfo->clause, PVC_RECURSE_PLACEHOLDERS)); + /* We require columns specified in foreignrel->reltarget->exprs and those required for evaluating the local conditions. */ + tlist = add_to_flat_tlist(tlist, pull_var_clause((Node*) foreignrel->reltarget->exprs, PVC_RECURSE_PLACEHOLDERS)); + foreach(lc, fpinfo->local_conds) { + RestrictInfo* rinfo = lfirst_node(RestrictInfo, lc); + tlist = add_to_flat_tlist(tlist, pull_var_clause((Node*) rinfo->clause, PVC_RECURSE_PLACEHOLDERS)); + } } + db2Debug1("< %s::build_tlist_to_deparse : %x",__FILE__, tlist); return tlist; } @@ -1291,6 +1321,7 @@ void deparseSelectStmtForRel(StringInfo buf, PlannerInfo* root, RelOptInfo* rel, DB2FdwState* fpinfo = (DB2FdwState*)rel->fdw_private; List* quals = NIL; + db2Debug1("> %s::deparseSelectStmtForRel",__FILE__); //We handle relations for foreign tables, joins between those and upper relations. Assert(IS_JOIN_REL(rel) || IS_SIMPLE_REL(rel) || IS_UPPER_REL(rel)); @@ -1338,6 +1369,8 @@ void deparseSelectStmtForRel(StringInfo buf, PlannerInfo* root, RelOptInfo* rel, // Add any necessary FOR UPDATE/SHARE. deparseLockingClause(&context); + db2Debug2(" select stmt: %s", buf->data); + db2Debug1("< %s::deparseSelectStmtForRel",__FILE__); } /* @@ -1359,6 +1392,7 @@ static void deparseSelectSql(List *tlist, bool is_subquery, List **retrieved_att PlannerInfo* root = context->root; DB2FdwState* fpinfo = (DB2FdwState*) foreignrel->fdw_private; + db2Debug4("> %s::deparseSelectSql",__FILE__); // Construct SELECT list appendStringInfoString(buf, "SELECT "); @@ -1380,6 +1414,8 @@ static void deparseSelectSql(List *tlist, bool is_subquery, List **retrieved_att deparseTargetList(buf, rte, foreignrel->relid, rel, false, fpinfo->attrs_used, false, retrieved_attrs); table_close(rel, NoLock); } + db2Debug4(" select: %s", buf->data); + db2Debug4("< %s::deparseSelectSql",__FILE__); } /* @@ -1394,6 +1430,7 @@ static void deparseFromExpr(List *quals, deparse_expr_cxt *context) { RelOptInfo* scanrel = context->scanrel; List* additional_conds = NIL; + db2Debug4("> %s::deparseFromExpr",__FILE__); /* For upper relations, scanrel must be either a joinrel or a baserel */ Assert(!IS_UPPER_REL(context->foreignrel) || IS_JOIN_REL(scanrel) || IS_SIMPLE_REL(scanrel)); @@ -1403,6 +1440,8 @@ static void deparseFromExpr(List *quals, deparse_expr_cxt *context) { appendWhereClause(quals, additional_conds, context); if (additional_conds != NIL) list_free_deep(additional_conds); + db2Debug5(" from: %s",buf->data); + db2Debug4("< %s::deparseFromExpr",__FILE__); } /* @@ -1424,6 +1463,7 @@ static void deparseFromExpr(List *quals, deparse_expr_cxt *context) { static void deparseFromExprForRel(StringInfo buf, PlannerInfo* root, RelOptInfo* foreignrel, bool use_alias, Index ignore_rel, List** ignore_conds, List** additional_conds, List** params_list) { DB2FdwState* fpinfo = (DB2FdwState*) foreignrel->fdw_private; + db2Debug4("> %s::deparseFromExprForRel",__FILE__); if (IS_JOIN_REL(foreignrel)) { StringInfoData join_sql_o; StringInfoData join_sql_i; @@ -1469,6 +1509,7 @@ static void deparseFromExprForRel(StringInfo buf, PlannerInfo* root, RelOptInfo* Assert(*additional_conds == NIL); *additional_conds = additional_conds_o; } + db2Debug4("< %s::deparseFromExprForRel",__FILE__); return; } } @@ -1504,9 +1545,7 @@ static void deparseFromExprForRel(StringInfo buf, PlannerInfo* root, RelOptInfo* appendStringInfoChar(&str, ')'); *additional_conds = lappend(*additional_conds, str.data); } - /* If outer relation is the target relation, skip deparsing it. - * See the above note about safety. - */ + /* If outer relation is the target relation, skip deparsing it. See the above note about safety. */ if (outerrel_is_target) { Assert(fpinfo->jointype == JOIN_INNER); Assert(fpinfo->joinclauses == NIL); @@ -1516,6 +1555,7 @@ static void deparseFromExprForRel(StringInfo buf, PlannerInfo* root, RelOptInfo* Assert(*additional_conds == NIL); *additional_conds = additional_conds_i; } + db2Debug4("< %s::deparseFromExprForRel",__FILE__); return; } } @@ -1568,6 +1608,7 @@ static void deparseFromExprForRel(StringInfo buf, PlannerInfo* root, RelOptInfo* appendStringInfo(buf, " %s%d", REL_ALIAS_PREFIX, foreignrel->relid); table_close(rel, NoLock); } + db2Debug4("< %s::deparseFromExprForRel",__FILE__); } /* Append FROM clause entry for the given relation into buf. @@ -1576,11 +1617,10 @@ static void deparseFromExprForRel(StringInfo buf, PlannerInfo* root, RelOptInfo* static void deparseRangeTblRef(StringInfo buf, PlannerInfo *root, RelOptInfo *foreignrel, bool make_subquery, Index ignore_rel, List **ignore_conds, List **additional_conds, List **params_list) { DB2FdwState* fpinfo = (DB2FdwState*) foreignrel->fdw_private; + db2Debug4("> %s::deparseRangeTblRef",__FILE__); /* Should only be called in these cases. */ Assert(IS_SIMPLE_REL(foreignrel) || IS_JOIN_REL(foreignrel)); - Assert(fpinfo->local_conds == NIL); - /* If make_subquery is true, deparse the relation as a subquery. */ if (make_subquery) { List* retrieved_attrs; @@ -1604,13 +1644,13 @@ static void deparseRangeTblRef(StringInfo buf, PlannerInfo *root, RelOptInfo *fo */ ncols = list_length(foreignrel->reltarget->exprs); if (ncols > 0) { - int i; + int i; appendStringInfoChar(buf, '('); for (i = 1; i <= ncols; i++) { - if (i > 1) + if (i > 1) { appendStringInfoString(buf, ", "); - + } appendStringInfo(buf, "%s%d", SUBQUERY_COL_ALIAS_PREFIX, i); } appendStringInfoChar(buf, ')'); @@ -1618,6 +1658,8 @@ static void deparseRangeTblRef(StringInfo buf, PlannerInfo *root, RelOptInfo *fo } else { deparseFromExprForRel(buf, root, foreignrel, true, ignore_rel, ignore_conds, additional_conds, params_list); } + db2Debug4(" rangeTblRef: %s", buf->data); + db2Debug4("< %s::deparseRangeTblRef",__FILE__); } /** deparseWhereConditions @@ -1655,14 +1697,15 @@ char* deparseWhereConditions (PlannerInfo* root, RelOptInfo * rel) { /* Construct a simple "TRUNCATE rel" statement */ void deparseTruncateSql(StringInfo buf, List* rels, DropBehavior behavior, bool restart_seqs) { - ListCell* cell; + ListCell* cell = NULL; + db2Debug1("> %s::deparseTruncateSql",__FILE__); appendStringInfoString(buf, "TRUNCATE "); foreach(cell, rels) { Relation rel = lfirst(cell); if (cell != list_head(rels)) - appendStringInfoString(buf, ", "); + appendStringInfoString(buf, ", "); deparseRelation(buf, rel); } appendStringInfo(buf, " %s IDENTITY", restart_seqs ? "RESTART" : "CONTINUE"); @@ -1670,6 +1713,8 @@ void deparseTruncateSql(StringInfo buf, List* rels, DropBehavior behavior, bool appendStringInfoString(buf, " RESTRICT"); else if (behavior == DROP_CASCADE) appendStringInfoString(buf, " CASCADE"); + db2Debug2(" truncateSql : %s",buf->data); + db2Debug1("> %s::deparseTruncateSql",__FILE__); } /* Construct name to use for given column, and emit it into buf. @@ -1684,8 +1729,8 @@ static void deparseColumnRef(StringInfo buf, int varno, int varattno, RangeTblEn ADD_REL_QUALIFIER(buf, varno); appendStringInfoString(buf, "ctid"); } else if (varattno < 0) { - /* All other system attributes are fetched as 0, except for table OID, which is fetched as the local table OID. However, we must be - * careful; the table could be beneath an outer join, in which case it must go to NULL whenever the rest of the row does. + /* All other system attributes are fetched as 0, except for table OID, which is fetched as the local table OID. + * However, we must be careful; the table could be beneath an outer join, in which case it must go to NULL whenever the rest of the row does. */ Oid fetchval = 0; @@ -1717,7 +1762,7 @@ static void deparseColumnRef(StringInfo buf, int varno, int varattno, RangeTblEn /* In case the whole-row reference is under an outer join then it has to go NULL whenever the rest of the row goes NULL. Deparsing a join * query would always involve multiple relations, thus qualify_col would be true. - */ + */ if (qualify_col) { appendStringInfoString(buf, "CASE WHEN ("); ADD_REL_QUALIFIER(buf, varno); @@ -1768,11 +1813,12 @@ static void deparseColumnRef(StringInfo buf, int varno, int varattno, RangeTblEn * Similarly, schema_name FDW option overrides schema name. */ static void deparseRelation(StringInfo buf, Relation rel) { - ForeignTable* table; + ForeignTable* table = NULL; const char* nspname = NULL; const char* relname = NULL; - ListCell* lc; + ListCell* lc = NULL; + db2Debug4("> %s::deparseRelation",__FILE__); /* obtain additional catalog information. */ table = GetForeignTable(RelationGetRelid(rel)); @@ -1795,12 +1841,15 @@ static void deparseRelation(StringInfo buf, Relation rel) { relname = RelationGetRelationName(rel); appendStringInfo(buf, "%s.%s", quote_identifier(nspname), quote_identifier(relname)); + db2Debug5(" relation: %s",buf->data); + db2Debug4("< %s::deparseRelation",__FILE__); } /* Append a SQL string literal representing "val" to buf. */ void deparseStringLiteral(StringInfo buf, const char* val) { - const char* valptr; + const char* valptr = NULL; + db2Debug4("> %s::deparseStringLiteral",__FILE__); /* Rather than making assumptions about the remote server's value of standard_conforming_strings, always use E'foo' syntax if there are any * backslashes. * This will fail on remote servers before 8.1, but those are long out of support. @@ -1818,6 +1867,8 @@ void deparseStringLiteral(StringInfo buf, const char* val) { appendStringInfoChar(buf, ch); } appendStringInfoChar(buf, '\''); + db2Debug5(" literal: %s",buf->data); + db2Debug4("< %s::deparseStringLiteral",__FILE__); } /** deparseExpr From cc0c0aebb2d2f5c59f78bdedcb4636339726a8fe Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Tue, 27 Jan 2026 17:55:27 +0100 Subject: [PATCH 019/120] save the old file just in case we need to revert quickly --- source/db2GetForeignPlanOld.c | 582 ++++++++++++++++++++++++++++++++++ 1 file changed, 582 insertions(+) create mode 100644 source/db2GetForeignPlanOld.c diff --git a/source/db2GetForeignPlanOld.c b/source/db2GetForeignPlanOld.c new file mode 100644 index 0000000..9a90252 --- /dev/null +++ b/source/db2GetForeignPlanOld.c @@ -0,0 +1,582 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "db2_fdw.h" +#include "DB2FdwState.h" + +/** external prototypes */ +extern List* serializePlanData (DB2FdwState* fdwState); +extern void checkDataType (short db2type, int scale, Oid pgtype, const char* tablename, const char* colname); +extern void db2Debug1 (const char* message, ...); +extern void db2Debug2 (const char* message, ...); +extern void db2Debug3 (const char* message, ...); +extern void db2free (void* p); +extern char* db2strdup (const char* p); +extern char* deparseExpr (PlannerInfo* root, RelOptInfo* rel, Expr* expr, List** params); +extern char* get_jointype_name (JoinType jointype); +extern List* build_tlist_to_deparse (RelOptInfo* foreignrel); + +/** local prototypes */ +ForeignScan* db2GetForeignPlan (PlannerInfo* root, RelOptInfo* foreignrel, Oid foreigntableid, ForeignPath* best_path, List* tlist, List* scan_clauses , Plan* outer_plan); +static void createQuery (PlannerInfo* root, RelOptInfo* foreignrel, bool modify, List* query_pathkeys); +static void getUsedColumns (Expr* expr, DB2Table* db2Table, int foreignrelid); +static void deparseFromExprForRel (PlannerInfo* root, RelOptInfo* foreignrel, StringInfo buf, List** params_list); + +/** db2GetForeignPlan + * Construct a ForeignScan node containing the serialized DB2FdwState, + * the RestrictInfo clauses not handled entirely by DB2 and the list + * of parameters we need for execution. + */ +ForeignScan* db2GetForeignPlan (PlannerInfo* root, RelOptInfo* foreignrel, Oid foreigntableid, ForeignPath* best_path, List* tlist, List* scan_clauses , Plan* outer_plan) { + DB2FdwState* fdwState = (DB2FdwState*) foreignrel->fdw_private; + List* fdw_private = NIL; + int i; + bool need_keys = false, + for_update = false, + has_trigger; + Relation rel; + Index scan_relid; /* will be 0 for join relations */ + List* local_exprs = fdwState->local_conds; + List* fdw_scan_tlist = NIL; + ForeignScan* result = NULL; + + db2Debug1("> %s::db2GetForeignPlan",__FILE__); + /* treat base relations and join relations differently */ + if (IS_SIMPLE_REL (foreignrel)) { + /* for base relations, set scan_relid as the relid of the relation */ + scan_relid = foreignrel->relid; + /* check if the foreign scan is for an UPDATE or DELETE */ +#if PG_VERSION_NUM < 140000 + if (foreignrel->relid == root->parse->resultRelation && (root->parse->commandType == CMD_UPDATE || root->parse->commandType == CMD_DELETE)) { +#else + if (bms_is_member(foreignrel->relid, root->all_result_relids) && (root->parse->commandType == CMD_UPDATE || root->parse->commandType == CMD_DELETE)) { +#endif /* PG_VERSION_NUM */ + /* we need the table's primary key columns */ + need_keys = true; + } + /* check if FOR [KEY] SHARE/UPDATE was specified */ + if (need_keys || get_parse_rowmark (root->parse, foreignrel->relid)) { + /* we should add FOR UPDATE */ + for_update = true; + } + if (need_keys) { + /* we need to fetch all primary key columns */ + for (i = 0; i < fdwState->db2Table->ncols; ++i) { + if (fdwState->db2Table->cols[i]->colPrimKeyPart) { + fdwState->db2Table->cols[i]->used = 1; + } + } + } + /* + * Core code already has some lock on each rel being planned, so we can + * use NoLock here. + */ + rel = table_open (foreigntableid, NoLock); + /* is there an AFTER trigger FOR EACH ROW? */ + has_trigger = (foreignrel->relid == root->parse->resultRelation) + && rel->trigdesc + && ((root->parse->commandType == CMD_UPDATE && rel->trigdesc->trig_update_after_row) || (root->parse->commandType == CMD_DELETE && rel->trigdesc->trig_delete_after_row)); + table_close (rel, NoLock); + if (has_trigger) { + /* we need to fetch and return all columns */ + for (i = 0; i < fdwState->db2Table->ncols; ++i) { + if (fdwState->db2Table->cols[i]->pgname) { + fdwState->db2Table->cols[i]->used = 1; + } + } + } + } else { + /* we have a join relation, so set scan_relid to 0 */ + scan_relid = 0; + /* + * create_scan_plan() and create_foreignscan_plan() pass + * rel->baserestrictinfo + parameterization clauses through + * scan_clauses. For a join rel->baserestrictinfo is NIL and we are + * not considering parameterization right now, so there should be no + * scan_clauses for a joinrel. + */ + Assert (!scan_clauses); + /* Build the list of columns to be fetched from the foreign server. */ + fdw_scan_tlist = build_tlist_to_deparse (foreignrel); + /* + * Ensure that the outer plan produces a tuple whose descriptor + * matches our scan tuple slot. This is safe because all scans and + * joins support projection, so we never need to insert a Result node. + * Also, remove the local conditions from outer plan's quals, lest + * they will be evaluated twice, once by the local plan and once by + * the scan. + */ + if (outer_plan) { + ListCell* lc; + outer_plan->targetlist = fdw_scan_tlist; + foreach (lc, local_exprs) { + Join* join_plan = (Join*) outer_plan; + Node* qual = lfirst (lc); + outer_plan->qual = list_delete (outer_plan->qual, qual); + /* + * For an inner join the local conditions of foreign scan plan + * can be part of the joinquals as well. + */ + if (join_plan->jointype == JOIN_INNER) { + join_plan->joinqual = list_delete (join_plan->joinqual, qual); + } + } + } + } + /* create remote query */ + createQuery (root, foreignrel, for_update, best_path->path.pathkeys); + db2Debug2(" db2_fdw: remote query is: %s", fdwState->query); + /* get PostgreSQL column data types, check that they match DB2's */ + for (i = 0; i < fdwState->db2Table->ncols; ++i) { + if (fdwState->db2Table->cols[i]->used) { + checkDataType (fdwState->db2Table->cols[i]->colType + ,fdwState->db2Table->cols[i]->colScale + ,fdwState->db2Table->cols[i]->pgtype + ,fdwState->db2Table->pgname + ,fdwState->db2Table->cols[i]->pgname + ); + } + } + fdw_private = serializePlanData (fdwState); + /* + * Create the ForeignScan node for the given relation. + * + * Note that the remote parameter expressions are stored in the fdw_exprs + * field of the finished plan node; we can't keep them in private state + * because then they wouldn't be subject to later planner processing. + */ + + result = make_foreignscan (tlist, local_exprs, scan_relid, fdwState->params, fdw_private, fdw_scan_tlist, NIL, outer_plan); + db2Debug1("< %s::db2GetForeignPlan",__FILE__); + return result; +} + +/** createQuery + * Construct a query string for DB2 that + * a) contains only the necessary columns in the SELECT list + * b) has all the WHERE and ORDER BY clauses that can safely be translated to DB2. + * Untranslatable clauses are omitted and left for PostgreSQL to check. + * "query_pathkeys" contains the desired sort order of the scan results + * which will be translated to ORDER BY clauses if possible. + * As a side effect for base relations, we also mark the used columns in db2Table. + */ +static void createQuery (PlannerInfo* root, RelOptInfo* foreignrel, bool modify, List* query_pathkeys) { + DB2FdwState* fdwState = (DB2FdwState*) foreignrel->fdw_private; + ListCell* cell; + bool in_quote = false; + int i, index; + char* wherecopy, *p, md5[33], parname[10], *separator = ""; + StringInfoData query, result; + List* columnlist, *conditions = foreignrel->baserestrictinfo; + #if PG_VERSION_NUM >= 150000 + const char* errstr = NULL; + #endif + + db2Debug1("> %s::createQuery",__FILE__); + columnlist = foreignrel->reltarget->exprs; + if (IS_SIMPLE_REL (foreignrel)) { + db2Debug3(" IS_SIMPLE_REL"); + /* find all the columns to include in the select list */ + /* examine each SELECT list entry for Var nodes */ + db2Debug3(" size of columnlist: %d", list_length(columnlist)); + foreach (cell, columnlist) { + db2Debug3(" examine column"); + getUsedColumns ((Expr*) lfirst (cell), fdwState->db2Table, foreignrel->relid); + } + /* examine each condition for Var nodes */ + db2Debug3(" size of conditions: %d", list_length(conditions)); + foreach (cell, conditions) { + db2Debug3(" examine condition"); + getUsedColumns ((Expr*) lfirst (cell), fdwState->db2Table, foreignrel->relid); + } + } + + /* construct SELECT list */ + initStringInfo (&query); + for (i = 0; i < fdwState->db2Table->ncols; ++i) { + db2Debug2(" %s.%s.->used: %d",fdwState->db2Table->name,fdwState->db2Table->cols[i]->colName,fdwState->db2Table->cols[i]->used); + if (fdwState->db2Table->cols[i]->used) { + StringInfoData alias; + initStringInfo (&alias); + /* table alias is created from range table index */ + ADD_REL_QUALIFIER (&alias, fdwState->db2Table->cols[i]->varno); + + /* add qualified column name */ + appendStringInfo (&query, "%s%s%s", separator, alias.data, fdwState->db2Table->cols[i]->colName); + separator = ", "; + } + } + + /* dummy column if there is no result column we need from DB2 */ + if (separator[0] == '\0') + appendStringInfo (&query, "'1'"); + + /* append FROM clause */ + appendStringInfo (&query, " FROM "); + deparseFromExprForRel (root, foreignrel, &query, &(fdwState->params)); + db2Debug2(" append from clause: %s", query.data); + /* + * For inner joins, all conditions that are pushed down get added + * to fdwState->joinclauses and have already been added above, + * so there is no extra WHERE clause. + */ + if (IS_SIMPLE_REL (foreignrel)) { + /* append WHERE clauses */ + if (fdwState->where_clause) + appendStringInfo (&query, "%s", fdwState->where_clause); + } + db2Debug2(" append where clause: %s", query.data); + + /* append ORDER BY clause if all its expressions can be pushed down */ + if (fdwState->order_clause) + appendStringInfo (&query, " ORDER BY%s", fdwState->order_clause); + db2Debug2(" append order by clause: %s", query.data); + + /* append FOR UPDATE if if the scan is for a modification */ + if (modify) + appendStringInfo (&query, " FOR UPDATE"); + db2Debug2(" append modify clause: %s", query.data); + + /* get a copy of the where clause without single quoted string literals */ + wherecopy = db2strdup (query.data); + for (p = wherecopy; *p != '\0'; ++p) { + if (*p == '\'') + in_quote = !in_quote; + if (in_quote) + *p = ' '; + } + + /* remove all parameters that do not actually occur in the query */ + index = 0; + foreach (cell, fdwState->params) { + ++index; + snprintf (parname, 10, ":p%d", index); + if (strstr (wherecopy, parname) == NULL) { + /* set the element to NULL to indicate it's gone */ + lfirst (cell) = NULL; + } + } + + db2free (wherecopy); + + /* + * Calculate MD5 hash of the query string so far. + * This is needed to find the query in DB2's library cache for EXPLAIN. + */ +#if PG_VERSION_NUM >= 150000 + if (!pg_md5_hash (query.data, strlen (query.data), md5,&errstr)) { + ereport (ERROR, (errcode (ERRCODE_OUT_OF_MEMORY), errmsg ("out of memory"))); + } +#else +if (!pg_md5_hash (query.data, strlen (query.data), md5)) { + ereport (ERROR, (errcode (ERRCODE_OUT_OF_MEMORY), errmsg ("out of memory"))); + } +#endif + /* add comment with MD5 hash to query */ + initStringInfo (&result); + appendStringInfo (&result, "SELECT /*%s*/ %s", md5, query.data); + db2free (query.data); + fdwState->query = (result.len > 0) ? db2strdup(result.data) : NULL; + db2free(result.data); + db2Debug2(" query: %s",fdwState->query); + db2Debug1("< createQuery"); +} + +/** getUsedColumns + * Set "used=true" in db2Table for all columns used in the expression. + */ +static void getUsedColumns (Expr* expr, DB2Table* db2Table, int foreignrelid) { + ListCell* cell; + Var* variable; + int index; + + db2Debug1("> getUsedColumns"); + if (expr != NULL) { + switch (expr->type) { + case T_RestrictInfo: + getUsedColumns (((RestrictInfo*) expr)->clause, db2Table, foreignrelid); + break; + case T_TargetEntry: + getUsedColumns (((TargetEntry*) expr)->expr, db2Table, foreignrelid); + break; + case T_Const: + case T_Param: + case T_CaseTestExpr: + case T_CoerceToDomainValue: + case T_CurrentOfExpr: + case T_NextValueExpr: + break; + case T_Var: + variable = (Var*) expr; + /* ignore system columns */ + if (variable->varattno < 0) + break; + /* if this is a wholerow reference, we need all columns */ + if (variable->varattno == 0) { + for (index = 0; index < db2Table->ncols; ++index) + if (db2Table->cols[index]->pgname) + db2Table->cols[index]->used = 1; + break; + } + /* get db2Table column index corresponding to this column (-1 if none) */ + index = db2Table->ncols - 1; + while (index >= 0 && db2Table->cols[index]->pgattnum != variable->varattno) { + --index; + } + if (index == -1) { + ereport (WARNING, (errcode (ERRCODE_WARNING),errmsg ("column number %d of foreign table \"%s\" does not exist in foreign DB2 table, will be replaced by NULL", variable->varattno, db2Table->pgname))); + } else { + db2Table->cols[index]->used = 1; + } + break; + case T_Aggref: + foreach (cell, ((Aggref*) expr)->args) { + getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + } + foreach (cell, ((Aggref*) expr)->aggorder) { + getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + } + foreach (cell, ((Aggref*) expr)->aggdistinct) { + getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + } + break; + case T_WindowFunc: + foreach (cell, ((WindowFunc*) expr)->args) { + getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + } + break; + case T_SubscriptingRef: { + SubscriptingRef* ref = (SubscriptingRef*) expr; + foreach(cell, ref->refupperindexpr) { + getUsedColumns((Expr*)lfirst(cell), db2Table, foreignrelid); + } + foreach(cell, ref->reflowerindexpr) { + getUsedColumns((Expr*)lfirst(cell), db2Table, foreignrelid); + } + getUsedColumns(ref->refexpr, db2Table, foreignrelid); + getUsedColumns(ref->refassgnexpr, db2Table, foreignrelid); + } + break; + case T_FuncExpr: + foreach (cell, ((FuncExpr*) expr)->args) { + getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + } + break; + case T_OpExpr: + foreach (cell, ((OpExpr*) expr)->args) { + getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + } + break; + case T_DistinctExpr: + foreach (cell, ((DistinctExpr*) expr)->args) { + getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + } + break; + case T_NullIfExpr: + foreach (cell, ((NullIfExpr*) expr)->args) { + getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + } + break; + case T_ScalarArrayOpExpr: + foreach (cell, ((ScalarArrayOpExpr*) expr)->args) { + getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + } + break; + case T_BoolExpr: + foreach (cell, ((BoolExpr*) expr)->args) { + getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + } + break; + case T_SubPlan: + foreach (cell, ((SubPlan*) expr)->args) { + getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + } + break; + case T_AlternativeSubPlan: + /* examine only first alternative */ + getUsedColumns ((Expr*) linitial (((AlternativeSubPlan*) expr)->subplans), db2Table, foreignrelid); + break; + case T_NamedArgExpr: + getUsedColumns (((NamedArgExpr*) expr)->arg, db2Table, foreignrelid); + break; + case T_FieldSelect: + getUsedColumns (((FieldSelect*) expr)->arg, db2Table, foreignrelid); + break; + case T_RelabelType: + getUsedColumns (((RelabelType*) expr)->arg, db2Table, foreignrelid); + break; + case T_CoerceViaIO: + getUsedColumns (((CoerceViaIO*) expr)->arg, db2Table, foreignrelid); + break; + case T_ArrayCoerceExpr: + getUsedColumns (((ArrayCoerceExpr*) expr)->arg, db2Table, foreignrelid); + break; + case T_ConvertRowtypeExpr: + getUsedColumns (((ConvertRowtypeExpr*) expr)->arg, db2Table, foreignrelid); + break; + case T_CollateExpr: + getUsedColumns (((CollateExpr*) expr)->arg, db2Table, foreignrelid); + break; + case T_CaseExpr: + foreach (cell, ((CaseExpr*) expr)->args) { + getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + } + getUsedColumns (((CaseExpr*) expr)->arg, db2Table, foreignrelid); + getUsedColumns (((CaseExpr*) expr)->defresult, db2Table, foreignrelid); + break; + case T_CaseWhen: + getUsedColumns (((CaseWhen*) expr)->expr, db2Table, foreignrelid); + getUsedColumns (((CaseWhen*) expr)->result, db2Table, foreignrelid); + break; + case T_ArrayExpr: + foreach (cell, ((ArrayExpr*) expr)->elements) { + getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + } + break; + case T_RowExpr: + foreach (cell, ((RowExpr*) expr)->args) { + getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + } + break; + case T_RowCompareExpr: + foreach (cell, ((RowCompareExpr*) expr)->largs) { + getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + } + foreach (cell, ((RowCompareExpr*) expr)->rargs) { + getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + } + break; + case T_CoalesceExpr: + foreach (cell, ((CoalesceExpr*) expr)->args) { + getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + } + break; + case T_MinMaxExpr: + foreach (cell, ((MinMaxExpr*) expr)->args) { + getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + } + break; + case T_XmlExpr: + foreach (cell, ((XmlExpr*) expr)->named_args) { + getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + } + foreach (cell, ((XmlExpr*) expr)->args) { + getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + } + break; + case T_NullTest: + getUsedColumns (((NullTest*) expr)->arg, db2Table, foreignrelid); + break; + case T_BooleanTest: + getUsedColumns (((BooleanTest*) expr)->arg, db2Table, foreignrelid); + break; + case T_CoerceToDomain: + getUsedColumns (((CoerceToDomain*) expr)->arg, db2Table, foreignrelid); + break; + case T_PlaceHolderVar: + getUsedColumns (((PlaceHolderVar*) expr)->phexpr, db2Table, foreignrelid); + break; + case T_SQLValueFunction: + //nop + break; /* contains no column references */ + default: + /* + * We must be able to handle all node types that can + * appear because we cannot omit a column from the remote + * query that will be needed. + * Throw an error if we encounter an unexpected node type. + */ + ereport (ERROR, (errcode (ERRCODE_FDW_UNABLE_TO_CREATE_REPLY), errmsg ("Internal db2_fdw error: encountered unknown node type %d.", expr->type))); + break; + } + } + db2Debug1("< getUsedColumns"); +} + +/** Build the targetlist for given relation to be deparsed as SELECT clause. + * + * The output targetlist contains the columns that need to be fetched from the + * foreign server for the given relation. + */ +//static List* build_tlist_to_deparse (RelOptInfo* foreignrel) { +// List* tlist = NIL; +// DB2FdwState* fdwState = (DB2FdwState*) foreignrel->fdw_private; +// +// db2Debug1("> build_tlist_to_deparse"); +// /* We require columns specified in foreignrel->reltarget->exprs and those required for evaluating the local conditions. */ +// tlist = add_to_flat_tlist (tlist, pull_var_clause ((Node *) foreignrel->reltarget->exprs, PVC_RECURSE_PLACEHOLDERS)); +// tlist = add_to_flat_tlist(tlist, pull_var_clause((Node*) fdwState->local_conds, PVC_RECURSE_PLACEHOLDERS)); +// db2Debug1("< build_tlist_to_deparse"); +// return tlist; +//} + +/** deparseFromExprForRel + * Construct FROM clause for given relation. + * The function constructs ... JOIN ... ON ... for join relation. For a base + * relation it just returns the table name. + * All tables get an alias based on the range table index. + */ +static void deparseFromExprForRel (PlannerInfo* root, RelOptInfo* foreignrel, StringInfo buf, List** params_list) { + DB2FdwState* fdwState = (DB2FdwState*) foreignrel->fdw_private; + + db2Debug1("> %s::deparseFromExprForRel",__FILE__); + db2Debug2(" buf: '%s'",buf->data); + if (IS_SIMPLE_REL (foreignrel)) { + db2Debug2(" IS_SIMPLE_REL(%d)",foreignrel->relid); + appendStringInfo (buf, "%s", fdwState->db2Table->name); + db2Debug2(" buf: '%s'",buf->data); + appendStringInfo (buf, " %s%d", REL_ALIAS_PREFIX, foreignrel->relid); + db2Debug2(" buf: '%s'",buf->data); + } else { + if (IS_JOIN_REL(foreignrel)) { + db2Debug2(" IS_JOIN_REL(%d)",foreignrel->relid); + /* join relation */ + RelOptInfo* rel_o = fdwState->outerrel; + RelOptInfo* rel_i = fdwState->innerrel; + StringInfoData join_sql_o; + StringInfoData join_sql_i; + ListCell* lc = NULL; + bool is_first = true; + char* where = NULL; + + /* Deparse outer relation */ + initStringInfo (&join_sql_o); + deparseFromExprForRel (root, rel_o, &join_sql_o, params_list); + db2Debug2(" outer join: %s",join_sql_o.data); + + /* Deparse inner relation */ + initStringInfo (&join_sql_i); + deparseFromExprForRel (root, rel_i, &join_sql_i, params_list); + db2Debug2(" inner join: %s",join_sql_i.data); + + // For a join relation FROM clause entry is deparsed as (outer relation) (inner relation) ON joinclauses + appendStringInfo (buf, "(%s %s JOIN %s ON ", join_sql_o.data, get_jointype_name (fdwState->jointype), join_sql_i.data); + + /* we can only get here if the join is pushed down, so there are join clauses */ + db2Debug2(" joinclauses: %x",fdwState->joinclauses); + Assert (fdwState->joinclauses); + + foreach (lc, fdwState->joinclauses) { + Expr *expr = (Expr *)lfirst(lc); + /* connect expressions with AND */ + if (!is_first) + appendStringInfo(buf, " AND "); + /* deparse and append a join condition */ + where = deparseExpr(root, foreignrel, expr, params_list); + appendStringInfo(buf, "%s", where); + is_first = false; + } + /* End the FROM clause entry. */ + appendStringInfo (buf, ")"); + } else { + // this part is only reached when we process a ForeignUpperPath + } + } + db2Debug2(" buf: '%s'",buf->data); + db2Debug1("< %s::deparseFromExprForRel",__FILE__); +} \ No newline at end of file From e3803ab37c0f9346b2f030954075f2690c408f45 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Tue, 27 Jan 2026 17:55:47 +0100 Subject: [PATCH 020/120] added retrieved_attr list --- include/DB2FdwState.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/DB2FdwState.h b/include/DB2FdwState.h index 4c3ec83..673c22a 100644 --- a/include/DB2FdwState.h +++ b/include/DB2FdwState.h @@ -40,6 +40,7 @@ typedef struct db2FdwState { // attributes taken over from PgFdwRelationInfo bool pushdown_safe; // True: relation can be pushed down. Always true for simple foreign scan. // All entries in these lists should have RestrictInfo wrappers; that improves efficiency of selectivity and cost estimation. + List* retrieved_attr; // Retrieved Attributes List. List* remote_conds; // Restriction clauses, safe to pushdown subsets. List* local_conds; // Restriction clauses, unsafe to pushdown subsets. List* final_remote_exprs; // Actual remote restriction clauses for scan (sans RestrictInfos) From fe5ac4ff336c20421d45c53e1fee2ab3c3d7aa24 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Tue, 27 Jan 2026 17:56:20 +0100 Subject: [PATCH 021/120] factor out de/serializatin of plan to a new file --- Makefile | 1 + source/db2_de_serialize.c | 290 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 291 insertions(+) create mode 100644 source/db2_de_serialize.c diff --git a/Makefile b/Makefile index 4269157..99c9c17 100644 --- a/Makefile +++ b/Makefile @@ -3,6 +3,7 @@ EXTVERSION = $(shell grep default_version $(EXTENSION).control | sed -e "s/def MODULE_big = db2_fdw OBJS = source/db2_fdw.o\ source/db2_deparse.o\ + source/db2_de_serialize.o\ source/db2GetForeignPlan.o\ source/db2GetForeignPaths.o\ source/db2GetForeignUpperPaths.o\ diff --git a/source/db2_de_serialize.c b/source/db2_de_serialize.c new file mode 100644 index 0000000..2ee983a --- /dev/null +++ b/source/db2_de_serialize.c @@ -0,0 +1,290 @@ +#include +#include +#include +#include + +//#include +//#include +//#include +//#include +//#include +//#include + + +#include "db2_fdw.h" +#include "DB2FdwState.h" + +/** external prototypes */ +extern void db2Debug1 (const char* message, ...); +extern void db2Debug2 (const char* message, ...); +extern void db2Debug4 (const char* message, ...); +extern void* db2alloc (const char* type, size_t size); +extern char* c2name (short fcType); + +/** local prototypes */ + DB2FdwState* deserializePlanData (List* list); + List* serializePlanData (DB2FdwState* fdwState); + +static char* deserializeString (Const* constant); +static long deserializeLong (Const* constant); +static Const* serializeString (const char* s); +static Const* serializeLong (long i); + +/** deserializePlanData + * Extract the data structures from a List created by serializePlanData. + */ +DB2FdwState* deserializePlanData (List* list) { + DB2FdwState* state = db2alloc ("DB2FdwState", sizeof (DB2FdwState)); + int idx = 0; + int i = 0; + int len = 0; + ParamDesc* param = NULL; + + db2Debug1("> deserializePlanData"); + /* session will be set upon connect */ + state->session = NULL; + /* these fields are not needed during execution */ + state->startup_cost = 0; + state->total_cost = 0; + /* these are not serialized */ + state->rowcount = 0; + state->columnindex = 0; + state->params = NULL; + state->temp_cxt = NULL; + state->order_clause = NULL; + + state->retrieved_attr = (List *) list_nth(list, idx++); + /* dbserver */ + state->dbserver = deserializeString(list_nth(list, idx++)); + /* user */ + state->user = deserializeString(list_nth(list, idx++)); + /* password */ + state->password = deserializeString(list_nth(list, idx++)); + /* nls_lang */ + state->nls_lang = deserializeString(list_nth(list, idx++)); + /* query */ + state->query = deserializeString(list_nth(list, idx++)); + /* DB2 prefetch count */ + state->prefetch = (unsigned long) DatumGetInt32 (((Const*)list_nth(list, idx++))->constvalue); + /* DB2 fetch_size */ + state->fetch_size = (unsigned long) DatumGetInt32 (((Const*)list_nth(list, idx++))->constvalue); + /* relation_name */ + state->relation_name = deserializeString(list_nth(list, idx++)); + db2Debug2(" state->relation_name: '%s'",state->relation_name); + /* table data */ + state->db2Table = (DB2Table*) db2alloc ("state->db2Table", sizeof (struct db2Table)); + state->db2Table->name = deserializeString(list_nth(list, idx++)); + db2Debug2(" state->db2Table->name: '%s'",state->db2Table->name); + state->db2Table->pgname = deserializeString(list_nth(list, idx++)); + db2Debug2(" state->db2Table->pgname: '%s'",state->db2Table->pgname); + state->db2Table->batchsz = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); + db2Debug2(" state->db2Table->batchsz: %d",state->db2Table->batchsz); + state->db2Table->ncols = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); + db2Debug2(" state->db2Table->ncols: %d",state->db2Table->ncols); + state->db2Table->npgcols = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); + db2Debug2(" state->db2Table->npgcols: %d",state->db2Table->npgcols); + state->db2Table->cols = (DB2Column**) db2alloc ("state->db2Table->cols", sizeof (DB2Column*) * state->db2Table->ncols); + + /* loop columns */ + for (i = 0; i < state->db2Table->ncols; ++i) { + state->db2Table->cols[i] = (DB2Column *) db2alloc ("state->db2Table->cols[i]", sizeof (DB2Column)); + state->db2Table->cols[i]->colName = deserializeString(list_nth(list, idx++)); + db2Debug2(" state->db2Table->cols[%d]->colName: '%s'",i,state->db2Table->cols[i]->colName); + state->db2Table->cols[i]->colType = (short) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); + db2Debug2(" state->db2Table->cols[%d]->colType: %d (%s)",i,state->db2Table->cols[i]->colType,c2name(state->db2Table->cols[i]->colType)); + state->db2Table->cols[i]->colSize = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); + db2Debug2(" state->db2Table->cols[%d]->colSize: %lld",i,state->db2Table->cols[i]->colSize); + state->db2Table->cols[i]->colScale = (short) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); + db2Debug2(" state->db2Table->cols[%d]->colScale: %d",i,state->db2Table->cols[i]->colScale); + state->db2Table->cols[i]->colNulls = (short) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); + db2Debug2(" state->db2Table->cols[%d]->colNulls: %d",i,state->db2Table->cols[i]->colNulls); + state->db2Table->cols[i]->colChars = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); + db2Debug2(" state->db2Table->cols[%lld]->colChars: %lld",i,state->db2Table->cols[i]->colChars); + state->db2Table->cols[i]->colBytes = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); + db2Debug2(" state->db2Table->cols[%lld]->colBytes: %lld",i,state->db2Table->cols[i]->colBytes); + state->db2Table->cols[i]->colPrimKeyPart = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); + db2Debug2(" state->db2Table->cols[%lld]->colPrimKeyPart: %lld",i,state->db2Table->cols[i]->colPrimKeyPart); + state->db2Table->cols[i]->colCodepage = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); + db2Debug2(" state->db2Table->cols[%lld]->colCodepaget: %lld",i,state->db2Table->cols[i]->colCodepage); + state->db2Table->cols[i]->pgname = deserializeString(list_nth(list, idx++)); + db2Debug2(" state->db2Table->cols[%d]->pgname: '%s'",i,state->db2Table->cols[i]->pgname); + state->db2Table->cols[i]->pgattnum = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); + db2Debug2(" state->db2Table->cols[%d]->pgattnum: %d",i,state->db2Table->cols[i]->pgattnum); + state->db2Table->cols[i]->pgtype = DatumGetObjectId(((Const*)list_nth(list, idx++))->constvalue); + db2Debug2(" state->db2Table->cols[%d]->pgtype: %d",i,state->db2Table->cols[i]->pgtype); + state->db2Table->cols[i]->pgtypmod = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); + db2Debug2(" state->db2Table->cols[%d]->pgtypmod: %d",i,state->db2Table->cols[i]->pgtypmod); + state->db2Table->cols[i]->used = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); + db2Debug2(" state->db2Table->cols[%d]->used: %d",i,state->db2Table->cols[i]->used); + state->db2Table->cols[i]->pkey = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); + db2Debug2(" state->db2Table->cols[%d]->pkey: %d",i,state->db2Table->cols[i]->pkey); + state->db2Table->cols[i]->val_size = deserializeLong(list_nth(list, idx++)); + db2Debug2(" state->db2Table->cols[%d]->val_size: %ld",i,state->db2Table->cols[i]->val_size); + state->db2Table->cols[i]->noencerr = deserializeLong(list_nth(list, idx++)); + db2Debug2(" state->db2Table->cols[%d]->noencerr: %d",i,state->db2Table->cols[i]->noencerr); + /* allocate memory for the result value only when the column is used in query */ + state->db2Table->cols[i]->val = (state->db2Table->cols[i]->used == 1) ? (char*) db2alloc ("state->db2Table->cols[i]->val", state->db2Table->cols[i]->val_size + 1) : NULL; + db2Debug2(" state->db2Table->cols[%d]->val: %x",i,state->db2Table->cols[i]->val); + state->db2Table->cols[i]->val_len = 0; + db2Debug2(" state->db2Table->cols[%d]->val_len: %d",i,state->db2Table->cols[i]->val_len); + state->db2Table->cols[i]->val_null = 1; + db2Debug2(" state->db2Table->cols[%d]->val_null: %d",i,state->db2Table->cols[i]->val_null); + } + + /* length of parameter list */ + len = (int) DatumGetInt32 (((Const*)list_nth(list, idx++))->constvalue); + + /* parameter table entries */ + state->paramList = NULL; + for (i = 0; i < len; ++i) { + param = (ParamDesc*) db2alloc ("state->parmList->next", sizeof (ParamDesc)); + param->type = DatumGetObjectId(((Const*)list_nth(list, idx++))->constvalue); + param->bindType = (db2BindType) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); + if (param->bindType == BIND_OUTPUT) + param->value = (void *) 42; /* something != NULL */ + else + param->value = NULL; + param->node = NULL; + param->colnum = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); + param->txts = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); + param->next = state->paramList; + state->paramList = param; + } + db2Debug1("< deserializePlanData - returns: %x", state); + return state; +} + +/** deserializeString + * Extracts a string from a Const, returns a deep copy. + */ +static char* deserializeString (Const* constant) { + char* result = NULL; + db2Debug4("> deserializeString"); + if (!constant->constisnull) + result = text_to_cstring (DatumGetTextP (constant->constvalue)); + db2Debug4("< deserializeString: '%s'", result); + return result; +} + +/** deserializeLong + * Extracts a long integer from a Const. + */ +static long deserializeLong (Const* constant) { + long result = 0L; + db2Debug4("> deserializeLong"); + result = (sizeof (long) <= 4) ? (long) DatumGetInt32 (constant->constvalue) + : (long) DatumGetInt64 (constant->constvalue); + db2Debug4("< deserializeLong - returns: %ld", result); + return result; +} + +/** serializePlanData + * Create a List representation of plan data that copyObject can copy. + * This List can be parsed by deserializePlanData. + */ +List* serializePlanData (DB2FdwState* fdwState) { + List* result = NIL; + int idxCol = 0; + int lenParam = 0; + ParamDesc* param = NULL; + + db2Debug1("> serializePlanData"); + result = list_make1(fdwState->retrieved_attr); + /* dbserver */ + result = lappend (result, serializeString (fdwState->dbserver)); + /* user name */ + result = lappend (result, serializeString (fdwState->user)); + /* password */ + result = lappend (result, serializeString (fdwState->password)); + /* nls_lang */ + result = lappend (result, serializeString (fdwState->nls_lang)); + /* query */ + result = lappend (result, serializeString (fdwState->query)); + /* DB2 prefetch count */ + result = lappend (result, serializeLong (fdwState->prefetch)); + /* DB2 fetchsize count */ + result = lappend (result, serializeLong (fdwState->fetch_size)); + /* relation_name */ + result = lappend (result, serializeString (fdwState->relation_name)); + /* DB2 table name */ + result = lappend (result, serializeString (fdwState->db2Table->name)); + /* PostgreSQL table name */ + result = lappend (result, serializeString (fdwState->db2Table->pgname)); + /* batch size in DB2 table */ + result = lappend (result, serializeInt (fdwState->db2Table->batchsz)); + /* number of columns in DB2 table */ + result = lappend (result, serializeInt (fdwState->db2Table->ncols)); + /* number of columns in PostgreSQL table */ + result = lappend (result, serializeInt (fdwState->db2Table->npgcols)); + /* column data */ + for (idxCol = 0; idxCol < fdwState->db2Table->ncols; ++idxCol) { + result = lappend (result, serializeString (fdwState->db2Table->cols[idxCol]->colName)); + result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->colType)); + result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->colSize)); + result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->colScale)); + result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->colNulls)); + result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->colChars)); + result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->colBytes)); + result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->colPrimKeyPart)); + result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->colCodepage)); + result = lappend (result, serializeString (fdwState->db2Table->cols[idxCol]->pgname)); + result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->pgattnum)); + result = lappend (result, serializeOid (fdwState->db2Table->cols[idxCol]->pgtype)); + result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->pgtypmod)); + result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->used)); + result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->pkey)); + result = lappend (result, serializeLong (fdwState->db2Table->cols[idxCol]->val_size)); + result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->noencerr)); + /* don't serialize val, val_len, val_null and varno */ + } + + /* find length of parameter list */ + for (param = fdwState->paramList; param; param = param->next) { + ++lenParam; + } + /* serialize length */ + result = lappend (result, serializeInt (lenParam)); + /* parameter list entries */ + for (param = fdwState->paramList; param; param = param->next) { + result = lappend (result, serializeOid (param->type)); + result = lappend (result, serializeInt ((int) param->bindType)); + result = lappend (result, serializeInt ((int) param->colnum)); + result = lappend (result, serializeInt ((int) param->txts)); + /* don't serialize value and node */ + } + /* don't serialize params, startup_cost, total_cost, rowcount, columnindex, temp_cxt, order_clause and where_clause */ + db2Debug1("< serializePlanData - returns: %x",result); + return result; +} + +/** serializeString + * Create a Const that contains the string. + */ +static Const* serializeString (const char* s) { + Const* result = NULL; + db2Debug1("> serializeString"); + result = (s == NULL) ? makeNullConst (TEXTOID, -1, InvalidOid) + : makeConst (TEXTOID, -1, InvalidOid, -1, PointerGetDatum (cstring_to_text (s)), false, false); + db2Debug1("< serializeString - returns: %x",result); + return result; +} + +/** serializeLong + * Create a Const that contains the long integer. + */ +static Const* serializeLong (long i) { + Const* result = NULL; + db2Debug1("> serializeLong"); + if (sizeof (long) <= 4) + result = makeConst (INT4OID, -1, InvalidOid, 4, Int32GetDatum ((int32) i), false, true); + else + result = makeConst (INT4OID, -1, InvalidOid, 8, Int64GetDatum ((int64) i), false, +#ifdef USE_FLOAT8_BYVAL + true +#else + false +#endif /* USE_FLOAT8_BYVAL */ + ); + db2Debug1("< serializeLong - returns: %x",result); + return result; +} From 433f278ffa148d6d657af072ea2d8a477b7bfbce Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Tue, 27 Jan 2026 17:57:45 +0100 Subject: [PATCH 022/120] factor our de / serialization of plan data --- source/db2BeginForeignModify.c | 204 +-------------------------------- source/db2PlanForeignModify.c | 116 +------------------ 2 files changed, 5 insertions(+), 315 deletions(-) diff --git a/source/db2BeginForeignModify.c b/source/db2BeginForeignModify.c index b71d85e..716342e 100644 --- a/source/db2BeginForeignModify.c +++ b/source/db2BeginForeignModify.c @@ -1,29 +1,18 @@ #include #include -#include -#include -#include #include -#include -#include #include "db2_fdw.h" #include "DB2FdwState.h" /** external variables */ /** external prototypes */ -extern void db2PrepareQuery (DB2Session* session, const char* query, DB2Table* db2Table, unsigned long prefetch); extern void db2Debug1 (const char* message, ...); -extern void db2Debug2 (const char* message, ...); -extern void* db2alloc (const char* type, size_t size); -extern char* c2name (short fcType); extern void db2BeginForeignModifyCommon(ModifyTableState* mtstate, ResultRelInfo* rinfo, DB2FdwState* fdw_state, Plan* subplan); +extern DB2FdwState* deserializePlanData (List* list); /** local prototypes */ void db2BeginForeignModify(ModifyTableState* mtstate, ResultRelInfo* rinfo, List* fdw_private, int subplan_index, int eflags); -DB2FdwState* deserializePlanData (List* list); -char* deserializeString (Const* constant); -long deserializeLong (Const* constant); /** db2BeginForeignModify * Prepare everything for the DML query: @@ -36,7 +25,6 @@ void db2BeginForeignModify (ModifyTableState * mtstate, ResultRelInfo * rinfo, L Plan *subplan = NULL; db2Debug1("> db2BeginForeignModify"); - db2Debug2(" relid: %d", RelationGetRelid (rinfo->ri_RelationDesc)); #if PG_VERSION_NUM < 140000 subplan = mtstate->mt_plans[subplan_index]->plan; #else @@ -44,192 +32,4 @@ void db2BeginForeignModify (ModifyTableState * mtstate, ResultRelInfo * rinfo, L #endif db2BeginForeignModifyCommon(mtstate, rinfo, fdw_state, subplan); -} - -/** deserializePlanData - * Extract the data structures from a List created by serializePlanData. - */ -DB2FdwState* deserializePlanData (List* list) { - DB2FdwState* state = db2alloc ("DB2FdwState", sizeof (DB2FdwState)); - ListCell* cell = list_head (list); - int i, - len; - ParamDesc* param; - - db2Debug1("> deserializePlanData"); - /* session will be set upon connect */ - state->session = NULL; - /* these fields are not needed during execution */ - state->startup_cost = 0; - state->total_cost = 0; - /* these are not serialized */ - state->rowcount = 0; - state->columnindex = 0; - state->params = NULL; - state->temp_cxt = NULL; - state->order_clause = NULL; - - /* dbserver */ - state->dbserver = deserializeString (lfirst (cell)); - cell = list_next (list,cell); - - /* user */ - state->user = deserializeString (lfirst (cell)); - cell = list_next (list,cell); - - /* password */ - state->password = deserializeString (lfirst (cell)); - cell = list_next (list,cell); - - /* nls_lang */ - state->nls_lang = deserializeString (lfirst (cell)); - cell = list_next (list,cell); - - /* query */ - state->query = deserializeString (lfirst (cell)); - cell = list_next (list,cell); - - /* DB2 prefetch count */ - state->prefetch = (unsigned long) DatumGetInt32 (((Const *) lfirst (cell))->constvalue); - cell = list_next (list,cell); - - /* DB2 prefetch count */ - state->fetch_size = (unsigned long) DatumGetInt32 (((Const *) lfirst (cell))->constvalue); - cell = list_next (list,cell); - - /* table data */ - state->db2Table = (DB2Table*) db2alloc ("state->db2Table", sizeof (struct db2Table)); - state->db2Table->name = deserializeString (lfirst (cell)); - db2Debug2(" state->db2Table->name: '%s'",state->db2Table->name); - cell = list_next (list,cell); - state->db2Table->pgname = deserializeString (lfirst (cell)); - db2Debug2(" state->db2Table->pgname: '%s'",state->db2Table->pgname); - cell = list_next (list,cell); - state->db2Table->batchsz = (int) DatumGetInt32 (((Const*) lfirst (cell))->constvalue); - db2Debug2(" state->db2Table->batchsz: %d",state->db2Table->batchsz); - cell = list_next (list,cell); - state->db2Table->ncols = (int) DatumGetInt32 (((Const*) lfirst (cell))->constvalue); - db2Debug2(" state->db2Table->ncols: %d",state->db2Table->ncols); - cell = list_next (list,cell); - state->db2Table->npgcols = (int) DatumGetInt32 (((Const*) lfirst (cell))->constvalue); - db2Debug2(" state->db2Table->npgcols: %d",state->db2Table->npgcols); - cell = list_next (list,cell); - state->db2Table->cols = (DB2Column**) db2alloc ("state->db2Table->cols", sizeof (DB2Column*) * state->db2Table->ncols); - - /* loop columns */ - for (i = 0; i < state->db2Table->ncols; ++i) { - state->db2Table->cols[i] = (DB2Column *) db2alloc ("state->db2Table->cols[i]", sizeof (DB2Column)); - state->db2Table->cols[i]->colName = deserializeString (lfirst (cell)); - db2Debug2(" state->db2Table->cols[%d]->colName: '%s'",i,state->db2Table->cols[i]->colName); - cell = list_next (list,cell); - state->db2Table->cols[i]->colType = (short) DatumGetInt32 (((Const*) lfirst (cell))->constvalue); - db2Debug2(" state->db2Table->cols[%d]->colType: %d (%s)",i,state->db2Table->cols[i]->colType,c2name(state->db2Table->cols[i]->colType)); - cell = list_next (list,cell); - state->db2Table->cols[i]->colSize = (size_t) DatumGetInt32 (((Const*) lfirst (cell))->constvalue); - db2Debug2(" state->db2Table->cols[%d]->colSize: %lld",i,state->db2Table->cols[i]->colSize); - cell = list_next (list,cell); - state->db2Table->cols[i]->colScale = (short) DatumGetInt32 (((Const*) lfirst (cell))->constvalue); - db2Debug2(" state->db2Table->cols[%d]->colScale: %d",i,state->db2Table->cols[i]->colScale); - cell = list_next (list,cell); - state->db2Table->cols[i]->colNulls = (short) DatumGetInt32 (((Const*) lfirst (cell))->constvalue); - db2Debug2(" state->db2Table->cols[%d]->colNulls: %d",i,state->db2Table->cols[i]->colNulls); - cell = list_next (list,cell); - state->db2Table->cols[i]->colChars = (size_t) DatumGetInt32 (((Const*) lfirst (cell))->constvalue); - db2Debug2(" state->db2Table->cols[%lld]->colChars: %lld",i,state->db2Table->cols[i]->colChars); - cell = list_next (list,cell); - state->db2Table->cols[i]->colBytes = (size_t) DatumGetInt32 (((Const*) lfirst (cell))->constvalue); - db2Debug2(" state->db2Table->cols[%lld]->colBytes: %lld",i,state->db2Table->cols[i]->colBytes); - cell = list_next (list,cell); - state->db2Table->cols[i]->colPrimKeyPart = (size_t) DatumGetInt32 (((Const*) lfirst (cell))->constvalue); - db2Debug2(" state->db2Table->cols[%lld]->colPrimKeyPart: %lld",i,state->db2Table->cols[i]->colPrimKeyPart); - cell = list_next (list,cell); - state->db2Table->cols[i]->colCodepage = (size_t) DatumGetInt32 (((Const*) lfirst (cell))->constvalue); - db2Debug2(" state->db2Table->cols[%lld]->colCodepaget: %lld",i,state->db2Table->cols[i]->colCodepage); - cell = list_next (list,cell); - state->db2Table->cols[i]->pgname = deserializeString (lfirst (cell)); - db2Debug2(" state->db2Table->cols[%d]->pgname: '%s'",i,state->db2Table->cols[i]->pgname); - cell = list_next (list,cell); - state->db2Table->cols[i]->pgattnum = (int) DatumGetInt32 (((Const*) lfirst (cell))->constvalue); - db2Debug2(" state->db2Table->cols[%d]->pgattnum: %d",i,state->db2Table->cols[i]->pgattnum); - cell = list_next (list,cell); - state->db2Table->cols[i]->pgtype = DatumGetObjectId (((Const*) lfirst (cell))->constvalue); - db2Debug2(" state->db2Table->cols[%d]->pgtype: %d",i,state->db2Table->cols[i]->pgtype); - cell = list_next (list,cell); - state->db2Table->cols[i]->pgtypmod = (int) DatumGetInt32 (((Const*) lfirst (cell))->constvalue); - db2Debug2(" state->db2Table->cols[%d]->pgtypmod: %d",i,state->db2Table->cols[i]->pgtypmod); - cell = list_next (list,cell); - state->db2Table->cols[i]->used = (int) DatumGetInt32 (((Const*) lfirst (cell))->constvalue); - db2Debug2(" state->db2Table->cols[%d]->used: %d",i,state->db2Table->cols[i]->used); - cell = list_next (list,cell); - state->db2Table->cols[i]->pkey = (int) DatumGetInt32 (((Const*) lfirst (cell))->constvalue); - db2Debug2(" state->db2Table->cols[%d]->pkey: %d",i,state->db2Table->cols[i]->pkey); - cell = list_next (list,cell); - state->db2Table->cols[i]->val_size = deserializeLong (lfirst (cell)); - db2Debug2(" state->db2Table->cols[%d]->val_size: %ld",i,state->db2Table->cols[i]->val_size); - cell = list_next (list,cell); - state->db2Table->cols[i]->noencerr = deserializeLong (lfirst (cell)); - db2Debug2(" state->db2Table->cols[%d]->noencerr: %d",i,state->db2Table->cols[i]->noencerr); - cell = list_next (list,cell); - /* allocate memory for the result value only when the column is used in query */ - state->db2Table->cols[i]->val = (state->db2Table->cols[i]->used == 1) ? (char*) db2alloc ("state->db2Table->cols[i]->val", state->db2Table->cols[i]->val_size + 1) : NULL; - db2Debug2(" state->db2Table->cols[%d]->val: %x",i,state->db2Table->cols[i]->val); - state->db2Table->cols[i]->val_len = 0; - db2Debug2(" state->db2Table->cols[%d]->val_len: %d",i,state->db2Table->cols[i]->val_len); - state->db2Table->cols[i]->val_null = 1; - db2Debug2(" state->db2Table->cols[%d]->val_null: %d",i,state->db2Table->cols[i]->val_null); - } - - /* length of parameter list */ - len = (int) DatumGetInt32 (((Const*) lfirst (cell))->constvalue); - cell = list_next (list,cell); - - /* parameter table entries */ - state->paramList = NULL; - for (i = 0; i < len; ++i) { - param = (ParamDesc*) db2alloc ("state->parmList->next", sizeof (ParamDesc)); - param->type = DatumGetObjectId (((Const *) lfirst (cell))->constvalue); - cell = list_next (list,cell); - param->bindType = (db2BindType) DatumGetInt32 (((Const *) lfirst (cell))->constvalue); - cell = list_next (list,cell); - if (param->bindType == BIND_OUTPUT) - param->value = (void *) 42; /* something != NULL */ - else - param->value = NULL; - param->node = NULL; - param->colnum = (int) DatumGetInt32 (((Const *) lfirst (cell))->constvalue); - cell = list_next (list,cell); - param->txts = (int) DatumGetInt32 (((Const *) lfirst (cell))->constvalue); - cell = list_next (list,cell); - param->next = state->paramList; - state->paramList = param; - } - - db2Debug1("< deserializePlanData - returns: %x", state); - return state; -} - -/** deserializeString - * Extracts a string from a Const, returns a deep copy. - */ -char* deserializeString (Const* constant) { - char* result = NULL; - db2Debug1("> deserializeString"); - if (constant->constisnull) - result = NULL; - else - result = text_to_cstring (DatumGetTextP (constant->constvalue)); - db2Debug1("< deserializeString: '%s'", result); - return result; -} - -/** deserializeLong - * Extracts a long integer from a Const. - */ -long deserializeLong (Const* constant) { - long result = 0L; - db2Debug1("> deserializeLong"); - result = (sizeof (long) <= 4) ? (long) DatumGetInt32 (constant->constvalue) - : (long) DatumGetInt64 (constant->constvalue); - db2Debug1("< deserializeLong - returns: %ld", result); - return result; -} +} \ No newline at end of file diff --git a/source/db2PlanForeignModify.c b/source/db2PlanForeignModify.c index fb96ab0..f6e8d22 100644 --- a/source/db2PlanForeignModify.c +++ b/source/db2PlanForeignModify.c @@ -2,7 +2,6 @@ #include #include #include -#include #include #include #include @@ -19,17 +18,15 @@ extern void db2Debug4 (const char* message, ...); extern void db2Debug5 (const char* message, ...); extern short c2dbType (short fcType); extern void appendAsType (StringInfoData* dest, Oid type); +extern List* serializePlanData (DB2FdwState* fdwState); /** local prototypes */ List* db2PlanForeignModify(PlannerInfo* root, ModifyTable* plan, Index resultRelation, int subplan_index); #ifdef WRITE_API -DB2FdwState* copyPlanData (DB2FdwState* orig); +static DB2FdwState* copyPlanData (DB2FdwState* orig); void addParam (ParamDesc** paramList, Oid pgtype, short colType, int colnum, int txts); #endif void checkDataType (short db2type, int scale, Oid pgtype, const char* tablename, const char* colname); -List* serializePlanData (DB2FdwState* fdwState); -Const* serializeString (const char* s); -Const* serializeLong (long i); /** db2PlanForeignModify * Construct an DB2FdwState or copy it from the foreign scan plan. @@ -322,7 +319,7 @@ List* db2PlanForeignModify (PlannerInfo* root, ModifyTable* plan, Index resultRe /** copyPlanData * Create a deep copy of the argument, copy only those fields needed for planning. */ -DB2FdwState* copyPlanData (DB2FdwState* orig) { +static DB2FdwState* copyPlanData (DB2FdwState* orig) { int i = 0; DB2FdwState* copy = NULL; @@ -454,110 +451,3 @@ void checkDataType (short sqltype, int scale, Oid pgtype, const char *tablename, db2Debug4("< checkDataType"); } -/** serializePlanData - * Create a List representation of plan data that copyObject can copy. - * This List can be parsed by deserializePlanData. - */ -List* serializePlanData (DB2FdwState* fdwState) { - List* result = NIL; - int idxCol = 0; - int lenParam = 0; - ParamDesc* param = NULL; - - db2Debug1("> serializePlanData"); - /* dbserver */ - result = lappend (result, serializeString (fdwState->dbserver)); - /* user name */ - result = lappend (result, serializeString (fdwState->user)); - /* password */ - result = lappend (result, serializeString (fdwState->password)); - /* nls_lang */ - result = lappend (result, serializeString (fdwState->nls_lang)); - /* query */ - result = lappend (result, serializeString (fdwState->query)); - /* DB2 prefetch count */ - result = lappend (result, serializeLong (fdwState->prefetch)); - /* DB2 fetchsize count */ - result = lappend (result, serializeLong (fdwState->fetch_size)); - /* DB2 table name */ - result = lappend (result, serializeString (fdwState->db2Table->name)); - /* PostgreSQL table name */ - result = lappend (result, serializeString (fdwState->db2Table->pgname)); - /* batch size in DB2 table */ - result = lappend (result, serializeInt (fdwState->db2Table->batchsz)); - /* number of columns in DB2 table */ - result = lappend (result, serializeInt (fdwState->db2Table->ncols)); - /* number of columns in PostgreSQL table */ - result = lappend (result, serializeInt (fdwState->db2Table->npgcols)); - /* column data */ - for (idxCol = 0; idxCol < fdwState->db2Table->ncols; ++idxCol) { - result = lappend (result, serializeString (fdwState->db2Table->cols[idxCol]->colName)); - result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->colType)); - result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->colSize)); - result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->colScale)); - result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->colNulls)); - result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->colChars)); - result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->colBytes)); - result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->colPrimKeyPart)); - result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->colCodepage)); - result = lappend (result, serializeString (fdwState->db2Table->cols[idxCol]->pgname)); - result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->pgattnum)); - result = lappend (result, serializeOid (fdwState->db2Table->cols[idxCol]->pgtype)); - result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->pgtypmod)); - result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->used)); - result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->pkey)); - result = lappend (result, serializeLong (fdwState->db2Table->cols[idxCol]->val_size)); - result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->noencerr)); - /* don't serialize val, val_len, val_null and varno */ - } - - /* find length of parameter list */ - for (param = fdwState->paramList; param; param = param->next) { - ++lenParam; - } - /* serialize length */ - result = lappend (result, serializeInt (lenParam)); - /* parameter list entries */ - for (param = fdwState->paramList; param; param = param->next) { - result = lappend (result, serializeOid (param->type)); - result = lappend (result, serializeInt ((int) param->bindType)); - result = lappend (result, serializeInt ((int) param->colnum)); - result = lappend (result, serializeInt ((int) param->txts)); - /* don't serialize value and node */ - } - /* don't serialize params, startup_cost, total_cost, rowcount, columnindex, temp_cxt, order_clause and where_clause */ - db2Debug1("< serializePlanData - returns: %x",result); - return result; -} - -/** serializeString - * Create a Const that contains the string. - */ -Const* serializeString (const char* s) { - Const* result = NULL; - db2Debug1("> serializeString"); - result = (s == NULL) ? makeNullConst (TEXTOID, -1, InvalidOid) - : makeConst (TEXTOID, -1, InvalidOid, -1, PointerGetDatum (cstring_to_text (s)), false, false); - db2Debug1("< serializeString - returns: %x",result); - return result; -} - -/** serializeLong - * Create a Const that contains the long integer. - */ -Const* serializeLong (long i) { - Const* result = NULL; - db2Debug1("> serializeLong"); - if (sizeof (long) <= 4) - result = makeConst (INT4OID, -1, InvalidOid, 4, Int32GetDatum ((int32) i), false, true); - else - result = makeConst (INT4OID, -1, InvalidOid, 8, Int64GetDatum ((int64) i), false, -#ifdef USE_FLOAT8_BYVAL - true -#else - false -#endif /* USE_FLOAT8_BYVAL */ - ); - db2Debug1("< serializeLong - returns: %x",result); - return result; -} From fe324b83b032bba04ccfaa1dde6a147f55a5f69c Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Tue, 27 Jan 2026 17:58:54 +0100 Subject: [PATCH 023/120] rework of building the foreign plan --- source/db2GetForeignPlan.c | 682 ++++++++----------------------------- 1 file changed, 144 insertions(+), 538 deletions(-) diff --git a/source/db2GetForeignPlan.c b/source/db2GetForeignPlan.c index 9cec5de..4c6390c 100644 --- a/source/db2GetForeignPlan.c +++ b/source/db2GetForeignPlan.c @@ -1,568 +1,174 @@ #include -#include #include -#include -#include -#include -#include -#include +#include #include "db2_fdw.h" #include "DB2FdwState.h" +/* This enum describes what's kept in the fdw_private list for a ForeignPath. + * We store: + * + * 1) Boolean flag showing if the remote query has the final sort + * 2) Boolean flag showing if the remote query has the LIMIT clause + */ +enum FdwPathPrivateIndex { + FdwPathPrivateHasFinalSort, /* has-final-sort flag (as a Boolean node) */ + FdwPathPrivateHasLimit, /* has-limit flag (as a Boolean node) */ +}; + /** external prototypes */ -extern List* serializePlanData (DB2FdwState* fdwState); -extern void checkDataType (short db2type, int scale, Oid pgtype, const char* tablename, const char* colname); -extern void db2Debug1 (const char* message, ...); -extern void db2Debug2 (const char* message, ...); -extern void db2Debug3 (const char* message, ...); -extern void db2free (void* p); -extern char* db2strdup (const char* p); -extern char* deparseExpr (PlannerInfo* root, RelOptInfo* rel, Expr* expr, List** params); -extern char* get_jointype_name (JoinType jointype); -extern List* build_tlist_to_deparse (RelOptInfo* foreignrel); +extern void db2Debug1 (const char* message, ...); +extern void db2Debug2 (const char* message, ...); +extern void db2Debug3 (const char* message, ...); +extern bool is_foreign_expr (PlannerInfo* root, RelOptInfo* baserel, Expr* expr); +extern void deparseSelectStmtForRel (StringInfo buf, PlannerInfo* root, RelOptInfo* rel, List* tlist, List* remote_conds, List* pathkeys, bool has_final_sort, bool has_limit, bool is_subquery, List** retrieved_attrs, List** params_list); +extern List* build_tlist_to_deparse (RelOptInfo* foreignrel); +extern List* serializePlanData (DB2FdwState* fdw_state); /** local prototypes */ -ForeignScan* db2GetForeignPlan (PlannerInfo* root, RelOptInfo* foreignrel, Oid foreigntableid, ForeignPath* best_path, List* tlist, List* scan_clauses , Plan* outer_plan); -static void createQuery (PlannerInfo* root, RelOptInfo* foreignrel, bool modify, List* query_pathkeys); -static void getUsedColumns (Expr* expr, DB2Table* db2Table, int foreignrelid); -static void deparseFromExprForRel (PlannerInfo* root, RelOptInfo* foreignrel, StringInfo buf, List** params_list); +ForeignScan* db2GetForeignPlan (PlannerInfo* root, RelOptInfo* foreignrel, Oid foreigntableid, ForeignPath* best_path, List* tlist, List* scan_clauses , Plan* outer_plan); -/** db2GetForeignPlan - * Construct a ForeignScan node containing the serialized DB2FdwState, - * the RestrictInfo clauses not handled entirely by DB2 and the list - * of parameters we need for execution. +/* postgresGetForeignPlan + * Create ForeignScan plan node which implements selected best path */ -ForeignScan* db2GetForeignPlan (PlannerInfo* root, RelOptInfo* foreignrel, Oid foreigntableid, ForeignPath* best_path, List* tlist, List* scan_clauses , Plan* outer_plan) { - DB2FdwState* fdwState = (DB2FdwState*) foreignrel->fdw_private; - List* fdw_private = NIL; - int i; - bool need_keys = false, - for_update = false, - has_trigger; - Relation rel; - Index scan_relid; /* will be 0 for join relations */ - List* local_exprs = fdwState->local_conds; - List* fdw_scan_tlist = NIL; - ForeignScan* result = NULL; +ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid foreigntableid, ForeignPath* best_path, List* tlist, List* scan_clauses, Plan* outer_plan) { + DB2FdwState* fpinfo = (DB2FdwState*) foreignrel->fdw_private; + ForeignScan* fscan = NULL; + List* fdw_private = NIL; + List* remote_exprs = NIL; + List* local_exprs = NIL; + List* params_list = NIL; + List* fdw_scan_tlist = NIL; + List* fdw_recheck_quals = NIL; + List* retrieved_attrs = NIL; + ListCell* lc = NULL; + bool has_final_sort = false; + bool has_limit = false; + Index scan_relid; + StringInfoData sql; + + db2Debug1("> %s::db2GetForeignPlan",__FILE__); + /* Get FDW private data created by db2GetForeignUpperPaths(), if any. */ + if (best_path->fdw_private) { + has_final_sort = boolVal(list_nth(best_path->fdw_private, FdwPathPrivateHasFinalSort)); + has_limit = boolVal(list_nth(best_path->fdw_private, FdwPathPrivateHasLimit)); + } - db2Debug1("> db2GetForeignPlan"); - /* treat base relations and join relations differently */ - if (IS_SIMPLE_REL (foreignrel)) { - /* for base relations, set scan_relid as the relid of the relation */ + if (IS_SIMPLE_REL(foreignrel)) { + /* For base relations, set scan_relid as the relid of the relation. */ scan_relid = foreignrel->relid; - /* check if the foreign scan is for an UPDATE or DELETE */ -#if PG_VERSION_NUM < 140000 - if (foreignrel->relid == root->parse->resultRelation && (root->parse->commandType == CMD_UPDATE || root->parse->commandType == CMD_DELETE)) { -#else - if (bms_is_member(foreignrel->relid, root->all_result_relids) && (root->parse->commandType == CMD_UPDATE || root->parse->commandType == CMD_DELETE)) { -#endif /* PG_VERSION_NUM */ - /* we need the table's primary key columns */ - need_keys = true; - } - /* check if FOR [KEY] SHARE/UPDATE was specified */ - if (need_keys || get_parse_rowmark (root->parse, foreignrel->relid)) { - /* we should add FOR UPDATE */ - for_update = true; - } - if (need_keys) { - /* we need to fetch all primary key columns */ - for (i = 0; i < fdwState->db2Table->ncols; ++i) { - if (fdwState->db2Table->cols[i]->colPrimKeyPart) { - fdwState->db2Table->cols[i]->used = 1; - } - } - } - /* - * Core code already has some lock on each rel being planned, so we can - * use NoLock here. + + /* In a base-relation scan, we must apply the given scan_clauses. + * + * Separate the scan_clauses into those that can be executed remotely and those that can't. + * baserestrictinfo clauses that were previously determined to be safe or unsafe by classifyConditions + * are found in fpinfo->remote_conds and fpinfo->local_conds. + * Anything else in the scan_clauses list will be a join clause, which we have to check for remote-safety. + * + * Note: the join clauses we see here should be the exact same ones previously examined by postgresGetForeignPaths. + * Possibly it'd be worth passing forward the classification work done then, rather than repeating it here. + * + * This code must match "extract_actual_clauses(scan_clauses, false)" except for the additional decision about remote versus local execution. */ - rel = table_open (foreigntableid, NoLock); - /* is there an AFTER trigger FOR EACH ROW? */ - has_trigger = (foreignrel->relid == root->parse->resultRelation) - && rel->trigdesc - && ((root->parse->commandType == CMD_UPDATE && rel->trigdesc->trig_update_after_row) || (root->parse->commandType == CMD_DELETE && rel->trigdesc->trig_delete_after_row)); - table_close (rel, NoLock); - if (has_trigger) { - /* we need to fetch and return all columns */ - for (i = 0; i < fdwState->db2Table->ncols; ++i) { - if (fdwState->db2Table->cols[i]->pgname) { - fdwState->db2Table->cols[i]->used = 1; - } - } + foreach(lc, scan_clauses) { + RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc); + + /* Ignore any pseudoconstants, they're dealt with elsewhere */ + if (rinfo->pseudoconstant) + continue; + + if (list_member_ptr(fpinfo->remote_conds, rinfo)) + remote_exprs = lappend(remote_exprs, rinfo->clause); + else if (list_member_ptr(fpinfo->local_conds, rinfo)) + local_exprs = lappend(local_exprs, rinfo->clause); + else if (is_foreign_expr(root, foreignrel, rinfo->clause)) + remote_exprs = lappend(remote_exprs, rinfo->clause); + else + local_exprs = lappend(local_exprs, rinfo->clause); } + + /* For a base-relation scan, we have to support EPQ recheck, which should recheck all the remote quals. */ + fdw_recheck_quals = remote_exprs; } else { - /* we have a join relation, so set scan_relid to 0 */ + /* Join relation or upper relation - set scan_relid to 0. */ scan_relid = 0; - /* - * create_scan_plan() and create_foreignscan_plan() pass - * rel->baserestrictinfo + parameterization clauses through - * scan_clauses. For a join rel->baserestrictinfo is NIL and we are - * not considering parameterization right now, so there should be no - * scan_clauses for a joinrel. + + /* For a join rel, baserestrictinfo is NIL and we are not considering parameterization right now, + * so there should be no scan_clauses for a joinrel or an upper rel either. + */ + Assert(!scan_clauses); + + /* Instead we get the conditions to apply from the fdw_private structure. */ + remote_exprs = extract_actual_clauses(fpinfo->remote_conds, false); + local_exprs = extract_actual_clauses(fpinfo->local_conds, false); + + /* We leave fdw_recheck_quals empty in this case, since we never need to apply EPQ recheck clauses. In the case of a joinrel, EPQ + * recheck is handled elsewhere --- see postgresGetForeignJoinPaths(). + * If we're planning an upperrel (ie, remote grouping or aggregation) then there's no EPQ to do because SELECT FOR UPDATE wouldn't be + * allowed, and indeed we *can't* put the remote clauses into fdw_recheck_quals because the unaggregated Vars won't be available + * locally. + * + * Build the list of columns to be fetched from the foreign server. */ - Assert (!scan_clauses); - /* Build the list of columns to be fetched from the foreign server. */ - fdw_scan_tlist = build_tlist_to_deparse (foreignrel); - /* - * Ensure that the outer plan produces a tuple whose descriptor - * matches our scan tuple slot. This is safe because all scans and - * joins support projection, so we never need to insert a Result node. - * Also, remove the local conditions from outer plan's quals, lest - * they will be evaluated twice, once by the local plan and once by - * the scan. + fdw_scan_tlist = build_tlist_to_deparse(foreignrel); + + /* Ensure that the outer plan produces a tuple whose descriptor matches our scan tuple slot. Also, remove the local conditions + * from outer plan's quals, lest they be evaluated twice, once by the local plan and once by the scan. */ if (outer_plan) { - ListCell* lc; - outer_plan->targetlist = fdw_scan_tlist; - foreach (lc, local_exprs) { - Join* join_plan = (Join*) outer_plan; - Node* qual = lfirst (lc); - outer_plan->qual = list_delete (outer_plan->qual, qual); - /* - * For an inner join the local conditions of foreign scan plan - * can be part of the joinquals as well. + /* Right now, we only consider grouping and aggregation beyond joins. + * Queries involving aggregates or grouping do not require EPQ mechanism, hence should not have an outer plan here. + */ + Assert(!IS_UPPER_REL(foreignrel)); + /* First, update the plan's qual list if possible. + * In some cases the quals might be enforced below the topmost plan level, in which case we'll fail to remove them; it's not worth working + * harder than this. + */ + foreach(lc, local_exprs) { + Node* qual = lfirst(lc); + + outer_plan->qual = list_delete(outer_plan->qual, qual); + /* For an inner join the local conditions of foreign scan plan can be part of the joinquals as well. + * (They might also be in the mergequals or hashquals, but we can't touch those without breaking the plan.) */ - if (join_plan->jointype == JOIN_INNER) { - join_plan->joinqual = list_delete (join_plan->joinqual, qual); + if (IsA(outer_plan, NestLoop) || IsA(outer_plan, MergeJoin) || IsA(outer_plan, HashJoin)) { + Join* join_plan = (Join*) outer_plan; + + if (join_plan->jointype == JOIN_INNER) + join_plan->joinqual = list_delete(join_plan->joinqual, qual); } } + /* Now fix the subplan's tlist --- this might result in inserting a Result node atop the plan tree. */ + outer_plan = change_plan_targetlist(outer_plan, fdw_scan_tlist, best_path->path.parallel_safe); } } - /* create remote query */ - createQuery (root, foreignrel, for_update, best_path->path.pathkeys); - db2Debug2(" db2_fdw: remote query is: %s", fdwState->query); - /* get PostgreSQL column data types, check that they match DB2's */ - for (i = 0; i < fdwState->db2Table->ncols; ++i) { - if (fdwState->db2Table->cols[i]->used) { - checkDataType (fdwState->db2Table->cols[i]->colType - ,fdwState->db2Table->cols[i]->colScale - ,fdwState->db2Table->cols[i]->pgtype - ,fdwState->db2Table->pgname - ,fdwState->db2Table->cols[i]->pgname - ); - } - } - fdw_private = serializePlanData (fdwState); - /* - * Create the ForeignScan node for the given relation. + + /* Build the query string to be sent for execution, and identify expressions to be sent as parameters. */ + initStringInfo(&sql); + deparseSelectStmtForRel(&sql, root, foreignrel, fdw_scan_tlist, remote_exprs, best_path->path.pathkeys, has_final_sort, has_limit, false, &retrieved_attrs, ¶ms_list); + db2Debug2(" deparsed foreign query: %s", sql.data); + /* Remember remote_exprs for possible use by postgresPlanDirectModify */ + fpinfo->final_remote_exprs = remote_exprs; + + // TODO serialize the whole Db2FdwState as used to including these parameters and ensure the deserialization restores them correctly + + /* Build the fdw_private list that will be available to the executor. + * Items in the list must match order in enum FdwScanPrivateIndex. + */ + fpinfo->query = sql.data; + fpinfo->retrieved_attr = retrieved_attrs; + fdw_private = serializePlanData(fpinfo); + +// fdw_private = list_make3(makeString(sql.data), retrieved_attrs, makeInteger(fpinfo->fetch_size)); +// if (IS_JOIN_REL(foreignrel) || IS_UPPER_REL(foreignrel)) +// fdw_private = lappend(fdw_private, makeString(fpinfo->relation_name)); + + /* Create the ForeignScan node for the given relation. * * Note that the remote parameter expressions are stored in the fdw_exprs * field of the finished plan node; we can't keep them in private state * because then they wouldn't be subject to later planner processing. */ - - result = make_foreignscan (tlist, local_exprs, scan_relid, fdwState->params, fdw_private, fdw_scan_tlist, NIL, outer_plan); - db2Debug1("< db2GetForeignPlan"); - return result; + fscan = make_foreignscan(tlist, local_exprs, scan_relid, params_list, fdw_private, fdw_scan_tlist, fdw_recheck_quals, outer_plan); + db2Debug1("< %s::db2GetForeignPlan : %x",__FILE__,fscan); + return fscan; } - -/** createQuery - * Construct a query string for DB2 that - * a) contains only the necessary columns in the SELECT list - * b) has all the WHERE and ORDER BY clauses that can safely be translated to DB2. - * Untranslatable clauses are omitted and left for PostgreSQL to check. - * "query_pathkeys" contains the desired sort order of the scan results - * which will be translated to ORDER BY clauses if possible. - * As a side effect for base relations, we also mark the used columns in db2Table. - */ -static void createQuery (PlannerInfo* root, RelOptInfo* foreignrel, bool modify, List* query_pathkeys) { - DB2FdwState* fdwState = (DB2FdwState*) foreignrel->fdw_private; - ListCell* cell; - bool in_quote = false; - int i, index; - char* wherecopy, *p, md5[33], parname[10], *separator = ""; - StringInfoData query, result; - List* columnlist, *conditions = foreignrel->baserestrictinfo; - #if PG_VERSION_NUM >= 150000 - const char* errstr = NULL; - #endif - - db2Debug1("> createQuery"); - columnlist = foreignrel->reltarget->exprs; - if (IS_SIMPLE_REL (foreignrel)) { - db2Debug3(" IS_SIMPLE_REL"); - /* find all the columns to include in the select list */ - /* examine each SELECT list entry for Var nodes */ - db2Debug3(" size of columnlist: %d", list_length(columnlist)); - foreach (cell, columnlist) { - db2Debug3(" examine column"); - getUsedColumns ((Expr*) lfirst (cell), fdwState->db2Table, foreignrel->relid); - } - /* examine each condition for Var nodes */ - db2Debug3(" size of conditions: %d", list_length(conditions)); - foreach (cell, conditions) { - db2Debug3(" examine condition"); - getUsedColumns ((Expr *) lfirst (cell), fdwState->db2Table, foreignrel->relid); - } - } - - /* construct SELECT list */ - initStringInfo (&query); - for (i = 0; i < fdwState->db2Table->ncols; ++i) { - db2Debug2(" %s.%s.->used: %d",fdwState->db2Table->name,fdwState->db2Table->cols[i]->colName,fdwState->db2Table->cols[i]->used); - if (fdwState->db2Table->cols[i]->used) { - StringInfoData alias; - initStringInfo (&alias); - /* table alias is created from range table index */ - ADD_REL_QUALIFIER (&alias, fdwState->db2Table->cols[i]->varno); - - /* add qualified column name */ - appendStringInfo (&query, "%s%s%s", separator, alias.data, fdwState->db2Table->cols[i]->colName); - separator = ", "; - } - } - - /* dummy column if there is no result column we need from DB2 */ - if (separator[0] == '\0') - appendStringInfo (&query, "'1'"); - - /* append FROM clause */ - appendStringInfo (&query, " FROM "); - deparseFromExprForRel (root, foreignrel, &query, &(fdwState->params)); - - /* - * For inner joins, all conditions that are pushed down get added - * to fdwState->joinclauses and have already been added above, - * so there is no extra WHERE clause. - */ - if (IS_SIMPLE_REL (foreignrel)) { - /* append WHERE clauses */ - if (fdwState->where_clause) - appendStringInfo (&query, "%s", fdwState->where_clause); - } - - /* append ORDER BY clause if all its expressions can be pushed down */ - if (fdwState->order_clause) - appendStringInfo (&query, " ORDER BY%s", fdwState->order_clause); - - /* append FOR UPDATE if if the scan is for a modification */ - if (modify) - appendStringInfo (&query, " FOR UPDATE"); - - /* get a copy of the where clause without single quoted string literals */ - wherecopy = db2strdup (query.data); - for (p = wherecopy; *p != '\0'; ++p) { - if (*p == '\'') - in_quote = !in_quote; - if (in_quote) - *p = ' '; - } - - /* remove all parameters that do not actually occur in the query */ - index = 0; - foreach (cell, fdwState->params) { - ++index; - snprintf (parname, 10, ":p%d", index); - if (strstr (wherecopy, parname) == NULL) { - /* set the element to NULL to indicate it's gone */ - lfirst (cell) = NULL; - } - } - - db2free (wherecopy); - - /* - * Calculate MD5 hash of the query string so far. - * This is needed to find the query in DB2's library cache for EXPLAIN. - */ -#if PG_VERSION_NUM >= 150000 - if (!pg_md5_hash (query.data, strlen (query.data), md5,&errstr)) { - ereport (ERROR, (errcode (ERRCODE_OUT_OF_MEMORY), errmsg ("out of memory"))); - } -#else -if (!pg_md5_hash (query.data, strlen (query.data), md5)) { - ereport (ERROR, (errcode (ERRCODE_OUT_OF_MEMORY), errmsg ("out of memory"))); - } -#endif - /* add comment with MD5 hash to query */ - initStringInfo (&result); - appendStringInfo (&result, "SELECT /*%s*/ %s", md5, query.data); - db2free (query.data); - fdwState->query = (result.len > 0) ? db2strdup(result.data) : NULL; - db2free(result.data); - db2Debug2(" query: %s",fdwState->query); - db2Debug1("< createQuery"); -} - -/** getUsedColumns - * Set "used=true" in db2Table for all columns used in the expression. - */ -static void getUsedColumns (Expr* expr, DB2Table* db2Table, int foreignrelid) { - ListCell* cell; - Var* variable; - int index; - - db2Debug1("> getUsedColumns"); - if (expr != NULL) { - switch (expr->type) { - case T_RestrictInfo: - getUsedColumns (((RestrictInfo*) expr)->clause, db2Table, foreignrelid); - break; - case T_TargetEntry: - getUsedColumns (((TargetEntry*) expr)->expr, db2Table, foreignrelid); - break; - case T_Const: - case T_Param: - case T_CaseTestExpr: - case T_CoerceToDomainValue: - case T_CurrentOfExpr: - case T_NextValueExpr: - break; - case T_Var: - variable = (Var*) expr; - /* ignore system columns */ - if (variable->varattno < 0) - break; - /* if this is a wholerow reference, we need all columns */ - if (variable->varattno == 0) { - for (index = 0; index < db2Table->ncols; ++index) - if (db2Table->cols[index]->pgname) - db2Table->cols[index]->used = 1; - break; - } - /* get db2Table column index corresponding to this column (-1 if none) */ - index = db2Table->ncols - 1; - while (index >= 0 && db2Table->cols[index]->pgattnum != variable->varattno) { - --index; - } - if (index == -1) { - ereport (WARNING, (errcode (ERRCODE_WARNING),errmsg ("column number %d of foreign table \"%s\" does not exist in foreign DB2 table, will be replaced by NULL", variable->varattno, db2Table->pgname))); - } else { - db2Table->cols[index]->used = 1; - } - break; - case T_Aggref: - foreach (cell, ((Aggref*) expr)->args) { - getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); - } - foreach (cell, ((Aggref*) expr)->aggorder) { - getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); - } - foreach (cell, ((Aggref*) expr)->aggdistinct) { - getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); - } - break; - case T_WindowFunc: - foreach (cell, ((WindowFunc*) expr)->args) { - getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); - } - break; - case T_SubscriptingRef: { - SubscriptingRef* ref = (SubscriptingRef*) expr; - foreach(cell, ref->refupperindexpr) { - getUsedColumns((Expr*)lfirst(cell), db2Table, foreignrelid); - } - foreach(cell, ref->reflowerindexpr) { - getUsedColumns((Expr*)lfirst(cell), db2Table, foreignrelid); - } - getUsedColumns(ref->refexpr, db2Table, foreignrelid); - getUsedColumns(ref->refassgnexpr, db2Table, foreignrelid); - } - break; - case T_FuncExpr: - foreach (cell, ((FuncExpr*) expr)->args) { - getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); - } - break; - case T_OpExpr: - foreach (cell, ((OpExpr*) expr)->args) { - getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); - } - break; - case T_DistinctExpr: - foreach (cell, ((DistinctExpr*) expr)->args) { - getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); - } - break; - case T_NullIfExpr: - foreach (cell, ((NullIfExpr*) expr)->args) { - getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); - } - break; - case T_ScalarArrayOpExpr: - foreach (cell, ((ScalarArrayOpExpr*) expr)->args) { - getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); - } - break; - case T_BoolExpr: - foreach (cell, ((BoolExpr*) expr)->args) { - getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); - } - break; - case T_SubPlan: - foreach (cell, ((SubPlan*) expr)->args) { - getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); - } - break; - case T_AlternativeSubPlan: - /* examine only first alternative */ - getUsedColumns ((Expr*) linitial (((AlternativeSubPlan*) expr)->subplans), db2Table, foreignrelid); - break; - case T_NamedArgExpr: - getUsedColumns (((NamedArgExpr*) expr)->arg, db2Table, foreignrelid); - break; - case T_FieldSelect: - getUsedColumns (((FieldSelect*) expr)->arg, db2Table, foreignrelid); - break; - case T_RelabelType: - getUsedColumns (((RelabelType*) expr)->arg, db2Table, foreignrelid); - break; - case T_CoerceViaIO: - getUsedColumns (((CoerceViaIO*) expr)->arg, db2Table, foreignrelid); - break; - case T_ArrayCoerceExpr: - getUsedColumns (((ArrayCoerceExpr*) expr)->arg, db2Table, foreignrelid); - break; - case T_ConvertRowtypeExpr: - getUsedColumns (((ConvertRowtypeExpr*) expr)->arg, db2Table, foreignrelid); - break; - case T_CollateExpr: - getUsedColumns (((CollateExpr*) expr)->arg, db2Table, foreignrelid); - break; - case T_CaseExpr: - foreach (cell, ((CaseExpr*) expr)->args) { - getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); - } - getUsedColumns (((CaseExpr*) expr)->arg, db2Table, foreignrelid); - getUsedColumns (((CaseExpr*) expr)->defresult, db2Table, foreignrelid); - break; - case T_CaseWhen: - getUsedColumns (((CaseWhen*) expr)->expr, db2Table, foreignrelid); - getUsedColumns (((CaseWhen*) expr)->result, db2Table, foreignrelid); - break; - case T_ArrayExpr: - foreach (cell, ((ArrayExpr*) expr)->elements) { - getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); - } - break; - case T_RowExpr: - foreach (cell, ((RowExpr*) expr)->args) { - getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); - } - break; - case T_RowCompareExpr: - foreach (cell, ((RowCompareExpr*) expr)->largs) { - getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); - } - foreach (cell, ((RowCompareExpr*) expr)->rargs) { - getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); - } - break; - case T_CoalesceExpr: - foreach (cell, ((CoalesceExpr*) expr)->args) { - getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); - } - break; - case T_MinMaxExpr: - foreach (cell, ((MinMaxExpr*) expr)->args) { - getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); - } - break; - case T_XmlExpr: - foreach (cell, ((XmlExpr*) expr)->named_args) { - getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); - } - foreach (cell, ((XmlExpr*) expr)->args) { - getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); - } - break; - case T_NullTest: - getUsedColumns (((NullTest*) expr)->arg, db2Table, foreignrelid); - break; - case T_BooleanTest: - getUsedColumns (((BooleanTest*) expr)->arg, db2Table, foreignrelid); - break; - case T_CoerceToDomain: - getUsedColumns (((CoerceToDomain*) expr)->arg, db2Table, foreignrelid); - break; - case T_PlaceHolderVar: - getUsedColumns (((PlaceHolderVar*) expr)->phexpr, db2Table, foreignrelid); - break; - case T_SQLValueFunction: - //nop - break; /* contains no column references */ - default: - /* - * We must be able to handle all node types that can - * appear because we cannot omit a column from the remote - * query that will be needed. - * Throw an error if we encounter an unexpected node type. - */ - ereport (ERROR, (errcode (ERRCODE_FDW_UNABLE_TO_CREATE_REPLY), errmsg ("Internal db2_fdw error: encountered unknown node type %d.", expr->type))); - break; - } - } - db2Debug1("< getUsedColumns"); -} - -/** Build the targetlist for given relation to be deparsed as SELECT clause. - * - * The output targetlist contains the columns that need to be fetched from the - * foreign server for the given relation. - */ -//static List* build_tlist_to_deparse (RelOptInfo* foreignrel) { -// List* tlist = NIL; -// DB2FdwState* fdwState = (DB2FdwState*) foreignrel->fdw_private; -// -// db2Debug1("> build_tlist_to_deparse"); -// /* We require columns specified in foreignrel->reltarget->exprs and those required for evaluating the local conditions. */ -// tlist = add_to_flat_tlist (tlist, pull_var_clause ((Node *) foreignrel->reltarget->exprs, PVC_RECURSE_PLACEHOLDERS)); -// tlist = add_to_flat_tlist(tlist, pull_var_clause((Node*) fdwState->local_conds, PVC_RECURSE_PLACEHOLDERS)); -// db2Debug1("< build_tlist_to_deparse"); -// return tlist; -//} - -/** deparseFromExprForRel - * Construct FROM clause for given relation. - * The function constructs ... JOIN ... ON ... for join relation. For a base - * relation it just returns the table name. - * All tables get an alias based on the range table index. - */ -static void deparseFromExprForRel (PlannerInfo* root, RelOptInfo* foreignrel, StringInfo buf, List** params_list) { - DB2FdwState* fdwState = (DB2FdwState*) foreignrel->fdw_private; - - db2Debug1("> deparseFromExprForRel"); - db2Debug2(" buf: '%s",buf->data); - if (IS_SIMPLE_REL (foreignrel)) { - appendStringInfo (buf, "%s", fdwState->db2Table->name); - appendStringInfo (buf, " %s%d", REL_ALIAS_PREFIX, foreignrel->relid); - } else { - /* join relation */ - RelOptInfo* rel_o = fdwState->outerrel; - RelOptInfo* rel_i = fdwState->innerrel; - StringInfoData join_sql_o; - StringInfoData join_sql_i; - ListCell* lc = NULL; - bool is_first = true; - char* where = NULL; - - /* Deparse outer relation */ - initStringInfo (&join_sql_o); - deparseFromExprForRel (root, rel_o, &join_sql_o, params_list); - - /* Deparse inner relation */ - initStringInfo (&join_sql_i); - deparseFromExprForRel (root, rel_i, &join_sql_i, params_list); - - // For a join relation FROM clause entry is deparsed as (outer relation) (inner relation) ON joinclauses - appendStringInfo (buf, "(%s %s JOIN %s ON ", join_sql_o.data, get_jointype_name (fdwState->jointype), join_sql_i.data); - - /* we can only get here if the join is pushed down, so there are join clauses */ - Assert (fdwState->joinclauses); - - foreach (lc, fdwState->joinclauses) { - Expr *expr = (Expr *)lfirst(lc); - /* connect expressions with AND */ - if (!is_first) - appendStringInfo(buf, " AND "); - /* deparse and append a join condition */ - where = deparseExpr(root, foreignrel, expr, params_list); - appendStringInfo(buf, "%s", where); - is_first = false; - } - /* End the FROM clause entry. */ - appendStringInfo (buf, ")"); - } - db2Debug2(" buf: '%s'",buf->data); - db2Debug1("< deparseFromExprForRel"); -} \ No newline at end of file From a85867870814726c653439c43d23ce3b470dd49b Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Tue, 27 Jan 2026 17:59:17 +0100 Subject: [PATCH 024/120] more deparsing rework --- source/db2_deparse.c | 163 ++++++++++++++++++++++++------------------- 1 file changed, 91 insertions(+), 72 deletions(-) diff --git a/source/db2_deparse.c b/source/db2_deparse.c index b0d22a6..f712761 100644 --- a/source/db2_deparse.c +++ b/source/db2_deparse.c @@ -1,3 +1,5 @@ +#include +#include #include #include @@ -36,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -137,13 +140,13 @@ static void deparseRangeTblRef (StringInfo buf, PlannerInfo* root static void deparseSelectSql (List* tlist, bool is_subquery, List** retrieved_attrs, deparse_expr_cxt* context); static void deparseFromExpr (List* quals, deparse_expr_cxt* context); static void deparseFromExprForRel (StringInfo buf, PlannerInfo* root, RelOptInfo* foreignrel, bool use_alias, Index ignore_rel, List** ignore_conds, List** additional_conds, List** params_list); -//void deparseFromExprForRel (PlannerInfo* root, RelOptInfo* foreignrel, StringInfo buf, List** params_list); static void deparseColumnRef (StringInfo buf, int varno, int varattno, RangeTblEntry* rte, bool qualify_col); static void deparseRelation (StringInfo buf, Relation rel); static void deparseExprInt (Expr* expr, deparse_expr_cxt* ctx); static void deparseConstExpr (Const* expr, deparse_expr_cxt* ctx); static void deparseParamExpr (Param* expr, deparse_expr_cxt* ctx); static void deparseVarExpr (Var* expr, deparse_expr_cxt* ctx); +static void deparseVar (Var* expr, deparse_expr_cxt* ctx); static void deparseOpExpr (OpExpr* expr, deparse_expr_cxt* ctx); static void deparseScalarArrayOpExpr (ScalarArrayOpExpr* expr, deparse_expr_cxt* ctx); static void deparseDistinctExpr (DistinctExpr* expr, deparse_expr_cxt* ctx); @@ -156,7 +159,6 @@ static void deparseFuncExpr (FuncExpr* expr, deparse_ static void deparseAggref (Aggref* expr, deparse_expr_cxt* ctx); static void deparseCoerceViaIOExpr (CoerceViaIO* expr, deparse_expr_cxt* ctx); static void deparseSQLValueFuncExpr (SQLValueFunction* expr, deparse_expr_cxt* ctx); -static void deparseVar (Var* node, deparse_expr_cxt* context); static void deparseConst (Const* node, deparse_expr_cxt* context, int showtype); static void deparseOperatorName (StringInfo buf, Form_pg_operator opform); static void deparseLockingClause (deparse_expr_cxt* context); @@ -1061,12 +1063,12 @@ static void appendOrderBySuffix(Oid sortop, Oid sortcoltype, bool nulls_first, d */ static void printRemoteParam(int paramindex, Oid paramtype, int32 paramtypmod, deparse_expr_cxt* context) { StringInfo buf = context->buf; - char* ptypename = deparse_type_name(paramtype, paramtypmod); +//char* ptypename = deparse_type_name(paramtype, paramtypmod); - db2Debug4("> printRemoteParam",__FILE__); - appendStringInfo(buf, "$%d::%s", paramindex, ptypename); - db2Debug5(" remoteParam: %s", buf->data); - db2Debug4("< printRemoteParam",__FILE__); + db2Debug4("> %s::printRemoteParam",__FILE__); +// appendStringInfo(buf, "$%d::%s", paramindex, ptypename); + appendStringInfo(buf, ":p%d", paramindex); + db2Debug4("< %s::printRemoteParam : %s",__FILE__, buf->data); } /* Print the representation of a placeholder for a parameter that will be sent to the remote side at execution time. @@ -1386,7 +1388,7 @@ void deparseSelectStmtForRel(StringInfo buf, PlannerInfo* root, RelOptInfo* rel, * indicate whether to deparse the specified relation as a subquery. * Read prologue of deparseSelectStmtForRel() for details. */ -static void deparseSelectSql(List *tlist, bool is_subquery, List **retrieved_attrs, deparse_expr_cxt *context) { +static void deparseSelectSql(List *tlist, bool is_subquery, List **retrieved_attrs, deparse_expr_cxt* context) { StringInfo buf = context->buf; RelOptInfo* foreignrel = context->foreignrel; PlannerInfo* root = context->root; @@ -1723,6 +1725,7 @@ void deparseTruncateSql(StringInfo buf, List* rels, DropBehavior behavior, bool * If qualify_col is true, qualify column name with the alias of relation. */ static void deparseColumnRef(StringInfo buf, int varno, int varattno, RangeTblEntry *rte, bool qualify_col) { + db2Debug1("> %s::deparseColumnRef",__FILE__); /* We support fetching the remote side's CTID and OID. */ if (varattno == SelfItemPointerAttributeNumber) { if (qualify_col) @@ -1804,8 +1807,9 @@ static void deparseColumnRef(StringInfo buf, int varno, int varattno, RangeTblEn if (qualify_col) ADD_REL_QUALIFIER(buf, varno); - appendStringInfoString(buf, quote_identifier(colname)); + appendStringInfoString(buf, quote_identifier(str_toupper (colname, strlen (colname), DEFAULT_COLLATION_OID))); } + db2Debug1("< %s::deparseColumnRef : %s",__FILE__, buf->data); } /* Append remote name of specified foreign table to buf. @@ -1828,9 +1832,9 @@ static void deparseRelation(StringInfo buf, Relation rel) { foreach(lc, table->options) { DefElem* def = (DefElem*) lfirst(lc); - if (strcmp(def->defname, "schema_name") == 0) + if (strcmp(def->defname, "schema") == 0) nspname = defGetString(def); - else if (strcmp(def->defname, "table_name") == 0) + else if (strcmp(def->defname, "table") == 0) relname = defGetString(def); } @@ -1915,7 +1919,8 @@ static void deparseExprInt (Expr* expr, deparse_expr_cxt* } break; case T_Var: { - deparseVarExpr ((Var*)expr, ctx); +// deparseVarExpr ((Var*)expr, ctx); + deparseVar ((Var*)expr, ctx); } break; case T_OpExpr: { @@ -2132,6 +2137,56 @@ static void deparseVarExpr (Var* expr, deparse_expr_cxt* db2Debug1("< %s::deparseVarExpr", __FILE__); } +/* Deparse given Var node into context->buf. + * + * If the Var belongs to the foreign relation, just print its remote name. + * Otherwise, it's effectively a Param (and will in fact be a Param at run time). + * Handle it the same way we handle plain Params --- see deparseParam for comments. + */ +static void deparseVar(Var* expr, deparse_expr_cxt* ctx) { + Relids relids = ctx->scanrel->relids; + int relno = 0; + int colno = 0; + /* Qualify columns when multiple relations are involved. */ + bool qualify_col = (bms_membership(relids) == BMS_MULTIPLE); + + db2Debug1("> %s::deparseVar", __FILE__); + /* If the Var belongs to the foreign relation that is deparsed as a subquery, use the relation and column alias to the Var provided by the + * subquery, instead of the remote name. + */ + if (is_subquery_var(expr, ctx->scanrel, &relno, &colno)) { + appendStringInfo(ctx->buf, "%s%d.%s%d", SUBQUERY_REL_ALIAS_PREFIX, relno, SUBQUERY_COL_ALIAS_PREFIX, colno); + return; + } + db2Debug2(" bms_is_member(%d,%d): %s",expr->varno, relids,bms_is_member(expr->varno, relids) ? "true":"false"); + db2Debug2(" expr->varlevelsup: %d",expr->varlevelsup); + if (bms_is_member(expr->varno, relids) && expr->varlevelsup == 0) { + deparseColumnRef(ctx->buf, expr->varno, expr->varattno, planner_rt_fetch(expr->varno, ctx->root), qualify_col); + } else { + /* Treat like a Param */ + if (ctx->params_list) { + int pindex = 0; + ListCell* lc = NULL; + + /* find its index in params_list */ + foreach(lc, *ctx->params_list) { + pindex++; + if (equal(expr, (Node*) lfirst(lc))) + break; + } + if (lc == NULL) { + /* not in list, so add it */ + pindex++; + *ctx->params_list = lappend(*ctx->params_list, expr); + } + printRemoteParam(pindex, expr->vartype, expr->vartypmod, ctx); + } else { + printRemotePlaceholder(expr->vartype, expr->vartypmod, ctx); + } + } + db2Debug1("< %s::deparseVar : %s", __FILE__,ctx->buf->data); +} + static void deparseOpExpr (OpExpr* expr, deparse_expr_cxt* ctx) { char* opername = NULL; char oprkind = 0x00; @@ -2834,12 +2889,7 @@ static void deparseAggref (Aggref* expr, deparse_expr_cxt* } ReleaseSysCache(tuple); } - db2Debug2( " aggref->aggfnoid=%u name=%s%s%s" - , expr->aggfnoid - , nspname ? nspname : "" - , nspname ? "." : "" - , aggname ? aggname : "" - ); + db2Debug2( " aggref->aggfnoid=%u name=%s%s%s", expr->aggfnoid, nspname ? nspname : "", nspname ? "." : "", aggname ? aggname : ""); /* We only support deparsing simple, standard aggregates for now. * (This can be expanded to ordered-set / FILTER / WITHIN GROUP later.) */ @@ -2871,13 +2921,21 @@ static void deparseAggref (Aggref* expr, deparse_expr_cxt* /* COUNT(*) */ appendStringInfoString(&result, "*"); } else { - ListCell* lc; - char* arg = NULL; - bool first_arg = true; + ListCell* lc; + bool first_arg = true; foreach (lc, expr->args) { - Node* argnode = (Node*) lfirst(lc); - Expr* argexpr = NULL; + Node* argnode = (Node*) lfirst(lc); + Expr* argexpr = NULL; + StringInfoData cbuf; + deparse_expr_cxt context; + + initStringInfo(&cbuf); + context.root = ctx->root; + context.buf = &cbuf; + context.foreignrel = ctx->foreignrel; + context.scanrel = ctx->scanrel; + context.params_list = ctx->params_list; if (argnode == NULL) { ok = false; @@ -2888,12 +2946,13 @@ static void deparseAggref (Aggref* expr, deparse_expr_cxt* } else { argexpr = (Expr*) argnode; } - arg = deparseExpr(ctx->root, ctx->foreignrel, argexpr, ctx->params_list); - if (arg == NULL) { + deparseExprInt(argexpr, &context); + if (cbuf.len <= 0) { ok = false; break; } - appendStringInfo(&result, "%s%s", arg, first_arg ? "" : ", "); + appendStringInfo(&result, "%s%s", cbuf.data, first_arg ? "" : ", "); + db2free(cbuf.data); first_arg = false; } } @@ -3062,52 +3121,6 @@ char* deparseTimestamp (Datum datum, bool hasTimezone) { return s.data; } -/** Deparse given Var node into context->buf. - * - * If the Var belongs to the foreign relation, just print its remote name. - * Otherwise, it's effectively a Param (and will in fact be a Param at run time). - * Handle it the same way we handle plain Params --- see deparseParam for comments. - */ -static void deparseVar(Var *node, deparse_expr_cxt *context) { - Relids relids = context->scanrel->relids; - int relno; - int colno; - /* Qualify columns when multiple relations are involved. */ - bool qualify_col = (bms_membership(relids) == BMS_MULTIPLE); - - /* If the Var belongs to the foreign relation that is deparsed as a subquery, use the relation and column alias to the Var provided by the - * subquery, instead of the remote name. - */ - if (is_subquery_var(node, context->scanrel, &relno, &colno)) { - appendStringInfo(context->buf, "%s%d.%s%d", SUBQUERY_REL_ALIAS_PREFIX, relno, SUBQUERY_COL_ALIAS_PREFIX, colno); - return; - } - if (bms_is_member(node->varno, relids) && node->varlevelsup == 0) { - deparseColumnRef(context->buf, node->varno, node->varattno, planner_rt_fetch(node->varno, context->root), qualify_col); - } else { - /* Treat like a Param */ - if (context->params_list) { - int pindex = 0; - ListCell* lc; - - /* find its index in params_list */ - foreach(lc, *context->params_list) { - pindex++; - if (equal(node, (Node *) lfirst(lc))) - break; - } - if (lc == NULL) { - /* not in list, so add it */ - pindex++; - *context->params_list = lappend(*context->params_list, node); - } - printRemoteParam(pindex, node->vartype, node->vartypmod, context); - } else { - printRemotePlaceholder(node->vartype, node->vartypmod, context); - } - } -} - /* * Deparse given constant value into context->buf. * @@ -3411,6 +3424,7 @@ static void deparseTargetList(StringInfo buf, RangeTblEntry *rte, Index rtindex, bool first; int i; + db2Debug1("> %s::deparseTargetList",__FILE__); *retrieved_attrs = NIL; /* If there's a whole-row reference, we'll need all the columns. */ @@ -3449,6 +3463,7 @@ static void deparseTargetList(StringInfo buf, RangeTblEntry *rte, Index rtindex, /* Don't generate bad syntax if no undropped columns */ if (first && !is_returning) appendStringInfoString(buf, "NULL"); +db2Debug1("> %s::deparseTargetList : %s",__FILE__, buf->data); } /** Deparse given targetlist and append it to context->buf. @@ -3464,6 +3479,7 @@ static void deparseExplicitTargetList(List* tlist, bool is_returning, List** ret StringInfo buf = context->buf; int i = 0; + db2Debug1("> %s::deparseExplicitTargetList",__FILE__); *retrieved_attrs = NIL; foreach(lc, tlist) { @@ -3482,6 +3498,7 @@ static void deparseExplicitTargetList(List* tlist, bool is_returning, List** ret if (i == 0 && !is_returning) appendStringInfoString(buf, "NULL"); + db2Debug1("< %s::deparseExplicitTargetList : %s",__FILE__, buf->data); } /** Emit expressions specified in the given relation's reltarget. @@ -3492,6 +3509,7 @@ static void deparseSubqueryTargetList(deparse_expr_cxt* context) { bool first = true; ListCell* lc; + db2Debug1("> %s::deparseSubqueryTargetList",__FILE__); /* Should only be called in these cases. */ Assert(IS_SIMPLE_REL(context->foreignrel) || IS_JOIN_REL(context->foreignrel)); foreach(lc, context->foreignrel->reltarget->exprs) { @@ -3504,6 +3522,7 @@ static void deparseSubqueryTargetList(deparse_expr_cxt* context) { /* Don't generate bad syntax if no expressions */ if (first) appendStringInfoString(context->buf, "NULL"); + db2Debug1("< %s::deparseSubqueryTargetList : %s",__FILE__, context->buf->data); } /** Given an EquivalenceClass and a foreign relation, find an EC member that can be used to sort the relation remotely according to a pathkey From 633492fe6367fd192e9fadd8984898115c93eaee Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Wed, 28 Jan 2026 10:56:04 +0100 Subject: [PATCH 025/120] adding JWT Token to de / serialization of fdw_state --- source/db2_de_serialize.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/source/db2_de_serialize.c b/source/db2_de_serialize.c index 2ee983a..12405d8 100644 --- a/source/db2_de_serialize.c +++ b/source/db2_de_serialize.c @@ -60,6 +60,8 @@ DB2FdwState* deserializePlanData (List* list) { state->user = deserializeString(list_nth(list, idx++)); /* password */ state->password = deserializeString(list_nth(list, idx++)); + /* jwt-token */ + state->jwt_token = deserializeString(list_nth(list, idx++)); /* nls_lang */ state->nls_lang = deserializeString(list_nth(list, idx++)); /* query */ @@ -196,6 +198,8 @@ List* serializePlanData (DB2FdwState* fdwState) { result = lappend (result, serializeString (fdwState->user)); /* password */ result = lappend (result, serializeString (fdwState->password)); + /* jwt_token */ + result = lappend (result, serializeString (fdwState->jwt_token)); /* nls_lang */ result = lappend (result, serializeString (fdwState->nls_lang)); /* query */ From ef9add12059a2258b6ad57946e97e74a587ec920 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Wed, 28 Jan 2026 11:28:46 +0100 Subject: [PATCH 026/120] follow up #107 change in this branch --- source/db2AllocConnHdl.c | 25 +++++++++++++++++++------ source/db2GetSession.c | 4 ++-- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/source/db2AllocConnHdl.c b/source/db2AllocConnHdl.c index 15daf78..90847d4 100644 --- a/source/db2AllocConnHdl.c +++ b/source/db2AllocConnHdl.c @@ -19,7 +19,7 @@ extern char* db2strdup (const char* p); /** local prototypes */ DB2ConnEntry* db2AllocConnHdl (DB2EnvEntry* envp,const char* srvname, char* user, char* password, char* jwt_token, const char* nls_lang); -DB2ConnEntry* findconnEntry (DB2ConnEntry* start, const char* srvname, const char* user); +DB2ConnEntry* findconnEntry (DB2ConnEntry* start, const char* srvname, const char* user, const char* jwttok); DB2ConnEntry* insertconnEntry (DB2ConnEntry* start, const char* srvname, const char* uid, const char* pwd, const char* jwt_token, SQLHDBC hdbc); /** db2AllocConnHdl @@ -43,7 +43,7 @@ DB2ConnEntry* db2AllocConnHdl(DB2EnvEntry* envp,const char* srvname, char* user, // envp->connlist = connp = insertconnEntry (envp->connlist, srvname, user, password, hdbc); } else { /* search user session for this server in cache */ - connp = findconnEntry(envp->connlist, srvname, user); + connp = findconnEntry(envp->connlist, srvname, user, jwt_token); if (connp == NULL) { /* Declare all variables at beginning for C90 compatibility */ char connStr[4096]; @@ -127,14 +127,27 @@ DB2ConnEntry* db2AllocConnHdl(DB2EnvEntry* envp,const char* srvname, char* user, /** findconnEntry * */ -DB2ConnEntry* findconnEntry(DB2ConnEntry* start, const char* srvname, const char* user) { +DB2ConnEntry* findconnEntry(DB2ConnEntry* start, const char* srvname, const char* user, const char* jwttok) { DB2ConnEntry* step = NULL; db2Debug2(" > findconnEntry"); for (step = start; step != NULL; step = step->right){ /* NULL-safe comparison for JWT auth where user may be NULL */ - int srv_match = (step->srvname && srvname) ? strcmp(step->srvname, srvname) == 0 : step->srvname == srvname; - int uid_match = (step->uid && user) ? strcmp(step->uid, user) == 0 : step->uid == user; - if (srv_match && uid_match) { + int jwt_null_or_empty = (!step->jwt_token || step->jwt_token[0] == '\0'); + int jwttok_null_or_empty = (!jwttok || jwttok[0] == '\0'); + int jwt_match = (jwt_null_or_empty && jwttok_null_or_empty) + || (!jwt_null_or_empty && !jwttok_null_or_empty && strcmp(step->jwt_token, jwttok) == 0); + + int srv_null_or_empty = (!step->srvname || step->srvname[0] == '\0'); + int srvname_null_or_empty = (!srvname || srvname[0] == '\0'); + int srv_match = (srv_null_or_empty && srvname_null_or_empty) + || (!srv_null_or_empty && !srvname_null_or_empty && strcmp(step->srvname, srvname) == 0); + + int uid_null_or_empty = (!step->uid || step->uid[0] == '\0'); + int user_null_or_empty = (!user || user[0] == '\0'); + int uid_match = (uid_null_or_empty && user_null_or_empty) + || (!uid_null_or_empty && !user_null_or_empty && strcmp(step->uid, user) == 0); + + if (srv_match && uid_match && jwt_match) { break; } } diff --git a/source/db2GetSession.c b/source/db2GetSession.c index 035ae5b..395ea4f 100644 --- a/source/db2GetSession.c +++ b/source/db2GetSession.c @@ -14,7 +14,7 @@ extern void db2Debug2 (const char* message, ...); extern DB2ConnEntry* db2AllocConnHdl (DB2EnvEntry* envp,const char* srvname, char* user, char* password, char* jwt_token, const char* nls_lang); extern DB2EnvEntry* db2AllocEnvHdl (const char* nls_lang); extern DB2EnvEntry* findenvEntry (DB2EnvEntry* start, const char* nlslang); -extern DB2ConnEntry* findconnEntry (DB2ConnEntry* start, const char* srvname, const char* user); +extern DB2ConnEntry* findconnEntry (DB2ConnEntry* start, const char* srvname, const char* user, const char* jwttok); extern void db2SetSavepoint (DB2Session* session, int nest_level); /** local prototypes */ @@ -44,7 +44,7 @@ DB2Session* db2GetSession (const char* srvname, char* user, char* password, char if (envp == NULL) { envp = db2AllocEnvHdl(nls_lang); } - connp = findconnEntry(envp->connlist, srvname, user); + connp = findconnEntry(envp->connlist, srvname, user, jwt_token); if (connp == NULL){ connp = db2AllocConnHdl(envp, srvname, user, password, jwt_token, NULL); } From fe3c77ef724c85378008a3e6b9236963d6402e99 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Fri, 6 Feb 2026 16:09:49 +0100 Subject: [PATCH 027/120] more preparations for push down of aggregate functions --- include/DB2Table.h | 1 + source/db2BeginForeignScan.c | 164 +++++++++++++++----- source/db2Describe.c | 2 +- source/db2GetForeignPaths.c | 18 +-- source/db2GetForeignPlan.c | 249 +++++++++++++++++++++++++++++-- source/db2GetForeignRelSize.c | 214 +++++++++++++++++++++++++- source/db2GetForeignUpperPaths.c | 68 +++------ source/db2PrepareQuery.c | 40 +++-- source/db2_de_serialize.c | 4 + 9 files changed, 641 insertions(+), 119 deletions(-) diff --git a/include/DB2Table.h b/include/DB2Table.h index 24ea2c8..85340f8 100644 --- a/include/DB2Table.h +++ b/include/DB2Table.h @@ -15,5 +15,6 @@ typedef struct db2Table { int ncols; // number of columns in DB2 table int npgcols; // number of columns (including dropped) in the PostgreSQL foreign table DB2Column** cols; // pointer to an array of DB2Column descriptors, as many as ncols tells + int rncols; // number of result columns to be fetched from DB2 } DB2Table; #endif diff --git a/source/db2BeginForeignScan.c b/source/db2BeginForeignScan.c index 2df26e2..d668c06 100644 --- a/source/db2BeginForeignScan.c +++ b/source/db2BeginForeignScan.c @@ -16,7 +16,9 @@ extern void db2Debug1 (const char* message, ...); extern void db2Debug2 (const char* message, ...); /** local prototypes */ -void db2BeginForeignScan(ForeignScanState* node, int eflags); + void db2BeginForeignScan (ForeignScanState* node, int eflags); +static int addExprParams (ForeignScanState* node); +static int addTleExprParams (ForeignScanState* node, int paramCount); /** db2BeginForeignScan * Recover ("deserialize") connection information, remote query, @@ -26,23 +28,62 @@ void db2BeginForeignScan(ForeignScanState* node, int eflags); */ void db2BeginForeignScan(ForeignScanState* node, int eflags) { ForeignScan* fsplan = (ForeignScan*) node->ss.ps.plan; - List* fdw_private = fsplan->fdw_private; - List* exec_exprs = NULL; - ListCell* cell = NULL; - int index = 0; - ParamDesc* paramDesc = NULL; DB2FdwState* fdw_state = NULL; + int paramCount = 0; db2Debug1("> db2BeginForeignScan"); /* deserialize private plan data */ - fdw_state = deserializePlanData(fdw_private); + fdw_state = deserializePlanData(fsplan->fdw_private); node->fdw_state = (void *) fdw_state; - /* create an ExprState tree for the parameter expressions */ - exec_exprs = (List *) ExecInitExprList (fsplan->fdw_exprs, (PlanState *) node); + paramCount = addExprParams(node); + addTleExprParams(node,paramCount); + + /* add a fake parameter "if that string appears in the query */ + if (strstr (fdw_state->query, "?/*:now*/") != NULL) { + ParamDesc* paramDesc = (ParamDesc*) db2alloc ("fdw_state->paramList->next", sizeof (ParamDesc)); + paramDesc->type = TIMESTAMPTZOID; + paramDesc->bindType = BIND_STRING; + paramDesc->value = NULL; + paramDesc->node = NULL; + paramDesc->colnum = -1; + paramDesc->txts = 1; + paramDesc->next = fdw_state->paramList; + fdw_state->paramList = paramDesc; + } + + if (node->ss.ss_currentRelation) + elog (DEBUG3, " begin foreign table scan on relid: %d", RelationGetRelid (node->ss.ss_currentRelation)); + else + elog (DEBUG3, " begin foreign join"); + /* connect to DB2 database */ + fdw_state->session = db2GetSession (fdw_state->dbserver + ,fdw_state->user + ,fdw_state->password + ,fdw_state->jwt_token + ,fdw_state->nls_lang + ,GetCurrentTransactionNestLevel() + ); + + /* initialize row count to zero */ + fdw_state->rowcount = 0; + db2Debug1("< db2BeginForeignScan"); +} + +static int addExprParams(ForeignScanState* node){ + DB2FdwState* fdw_state = node->fdw_state; + ForeignScan* fsplan = (ForeignScan*) node->ss.ps.plan; + List* exec_exprs = NIL; + ParamDesc* paramDesc = NULL; + ListCell* cell = NULL; + int index = 0; + + db2Debug1("> addExprParams"); + /* create an ExprState tree for the parameter expressions */ + exec_exprs = (List*) ExecInitExprList (fsplan->fdw_exprs, (PlanState*) node); + db2Debug2(" exec_expr: %x[%d]",exec_exprs, list_length(exec_exprs)); /* create the list of parameters */ - index = 0; foreach (cell, exec_exprs) { ExprState* expr = (ExprState*) lfirst (cell); @@ -76,35 +117,88 @@ void db2BeginForeignScan(ForeignScanState* node, int eflags) { db2Debug2(" paramDesc->colnum: %d ",paramDesc->colnum); fdw_state->paramList = paramDesc; } + db2Debug1("< addExprParams : %d", index); + return index; +} + +static int addTleExprParams(ForeignScanState* node, int paramCount){ + DB2FdwState* fdw_state = node->fdw_state; + ForeignScan* fsplan = (ForeignScan*) node->ss.ps.plan; + List* tle_exprs = fsplan->fdw_scan_tlist; + ParamDesc* paramDesc = NULL; + ListCell* cell = NULL; + int index = 0; + + db2Debug1("> addTleExprParams"); + db2Debug2(" tle_expr: %x[%d]",tle_exprs, list_length(tle_exprs)); + + // create the list of parameters + foreach (cell, tle_exprs) { + Expr* expr = NULL; + TargetEntry* tle = (TargetEntry*) lfirst(cell); + + if (tle == NULL) + continue; + + // count, but skip deleted entries + db2Debug2(" result index: %d",++index); + db2Debug2(" tle->resno: %d", tle->resno); + expr = (Expr*) tle->expr; + if (expr != NULL ) { + db2Debug2(" tle->expr->type: %d", expr->type); +/* + switch(tle->expr->type) { + case T_Aggref: { + Aggref* agg = (Aggref*) expr; + db2Debug2(" agg->aggno : %d", agg->aggno); + db2Debug2(" agg->aggstar : %s", agg->aggstar ? "true" : "false"); + db2Debug2(" agg->aggtype : %d", agg->aggtype); + paramDesc = (ParamDesc*) db2alloc("fdw_state->paramList->next", sizeof (ParamDesc)); + paramDesc->type = agg->aggtype; + paramDesc->bindType = (agg->aggtype == NUMERICOID) ? BIND_NUMBER : BIND_STRING; + paramDesc->value = NULL; + paramDesc->node = agg; + paramDesc->colnum = -1; + paramDesc->txts = 0; + paramDesc->next = fdw_state->paramList; + db2Debug2(" paramDesc->colnum: %d ",paramDesc->colnum); + fdw_state->paramList = paramDesc; + } + break; + default: + continue; // skip non-constant expressions + break; + } +*/ + } +/* + // create a new entry in the parameter list + paramDesc = (ParamDesc*) db2alloc("fdw_state->paramList->next", sizeof (ParamDesc)); + paramDesc->type = exprType ((Node*)(expr->expr)); + + if (paramDesc->type == TEXTOID + || paramDesc->type == VARCHAROID + || paramDesc->type == BPCHAROID + || paramDesc->type == CHAROID + || paramDesc->type == DATEOID + || paramDesc->type == TIMESTAMPOID + || paramDesc->type == TIMESTAMPTZOID + || paramDesc->type == TIMEOID + || paramDesc->type == TIMETZOID) + paramDesc->bindType = BIND_STRING; + else + paramDesc->bindType = BIND_NUMBER; - /* add a fake parameter "if that string appears in the query */ - if (strstr (fdw_state->query, "?/*:now*/") != NULL) { - paramDesc = (ParamDesc*) db2alloc ("fdw_state->paramList->next", sizeof (ParamDesc)); - paramDesc->type = TIMESTAMPTZOID; - paramDesc->bindType = BIND_STRING; paramDesc->value = NULL; - paramDesc->node = NULL; + paramDesc->node = expr; paramDesc->colnum = -1; - paramDesc->txts = 1; + paramDesc->txts = 0; paramDesc->next = fdw_state->paramList; + db2Debug2(" paramDesc->colnum: %d ",paramDesc->colnum); fdw_state->paramList = paramDesc; +*/ } - if (node->ss.ss_currentRelation) - elog (DEBUG3, " begin foreign table scan on relid: %d", RelationGetRelid (node->ss.ss_currentRelation)); - else - elog (DEBUG3, " begin foreign join"); - - /* connect to DB2 database */ - fdw_state->session = db2GetSession (fdw_state->dbserver - ,fdw_state->user - ,fdw_state->password - ,fdw_state->jwt_token - ,fdw_state->nls_lang - ,GetCurrentTransactionNestLevel() - ); - - /* initialize row count to zero */ - fdw_state->rowcount = 0; - db2Debug1("< db2BeginForeignScan"); -} + db2Debug1("< addTleExprParams : %d", index); + return index; +} \ No newline at end of file diff --git a/source/db2Describe.c b/source/db2Describe.c index 558a8f4..c48c032 100644 --- a/source/db2Describe.c +++ b/source/db2Describe.c @@ -121,7 +121,7 @@ DB2Table* db2Describe (DB2Session* session, char* schema, char* table, char* pgn } reply->ncols = ncols; - reply->cols = (DB2Column **) db2alloc ("reply->cols", sizeof (DB2Column *) * reply->ncols); + reply->cols = (DB2Column**) db2alloc ("reply->cols", sizeof (DB2Column*) *reply->ncols); db2Debug2(" reply->ncols : %d", reply->ncols); /* loop through the column list */ diff --git a/source/db2GetForeignPaths.c b/source/db2GetForeignPaths.c index 82b5bf5..66035a6 100644 --- a/source/db2GetForeignPaths.c +++ b/source/db2GetForeignPaths.c @@ -23,10 +23,7 @@ static Expr* find_em_expr_for_rel(EquivalenceClass * ec, RelOptInfo * rel); void db2GetForeignPaths(PlannerInfo* root, RelOptInfo* baserel, Oid foreigntableid) { DB2FdwState* fdwState = (DB2FdwState*) baserel->fdw_private; - /* - * Determine whether we can potentially push query pathkeys to the remote - * side, avoiding a local sort. - */ + /* Determine whether we can potentially push query pathkeys to the remote side, avoiding a local sort. */ StringInfoData orderedquery; List* usable_pathkeys = NIL; ListCell* cell; @@ -43,9 +40,7 @@ void db2GetForeignPaths(PlannerInfo* root, RelOptInfo* baserel, Oid foreigntable Oid em_type; bool can_pushdown; - /** deparseExpr would detect volatile expressions as well, but - * ec_has_volatile saves some cycles. - */ + /* deparseExpr would detect volatile expressions as well, but ec_has_volatile saves some cycles. */ can_pushdown = !pathkey_ec->ec_has_volatile && ((em_expr = find_em_expr_for_rel (pathkey_ec, baserel)) != NULL); if (can_pushdown) { @@ -74,11 +69,10 @@ void db2GetForeignPaths(PlannerInfo* root, RelOptInfo* baserel, Oid foreigntable #endif appendStringInfoString (&orderedquery, (pathkey->pk_nulls_first) ? " NULLS FIRST" : " NULLS LAST"); } else { - /** The planner and executor don't have any clever strategy for - * taking data sorted by a prefix of the query's pathkeys and - * getting it to be sorted by all of those pathekeys. We'll just - * end up resorting the entire data set. So, unless we can push - * down all of the query pathkeys, forget it. + /* The planner and executor don't have any clever strategy for taking data sorted by a prefix of the query's pathkeys and + * getting it to be sorted by all of those pathekeys. + * We'll just end up resorting the entire data set. + * So, unless we can push down all of the query pathkeys, forget it. */ list_free (usable_pathkeys); usable_pathkeys = NIL; diff --git a/source/db2GetForeignPlan.c b/source/db2GetForeignPlan.c index 4c6390c..6d4328e 100644 --- a/source/db2GetForeignPlan.c +++ b/source/db2GetForeignPlan.c @@ -25,7 +25,8 @@ extern List* build_tlist_to_deparse (RelOptInfo* foreignrel); extern List* serializePlanData (DB2FdwState* fdw_state); /** local prototypes */ -ForeignScan* db2GetForeignPlan (PlannerInfo* root, RelOptInfo* foreignrel, Oid foreigntableid, ForeignPath* best_path, List* tlist, List* scan_clauses , Plan* outer_plan); + ForeignScan* db2GetForeignPlan (PlannerInfo* root, RelOptInfo* foreignrel, Oid foreigntableid, ForeignPath* best_path, List* tlist, List* scan_clauses , Plan* outer_plan); +static void getUsedColumns (Expr* expr, DB2Table* db2Table, int foreignrelid); /* postgresGetForeignPlan * Create ForeignScan plan node which implements selected best path @@ -54,9 +55,26 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo } if (IS_SIMPLE_REL(foreignrel)) { + ListCell* cell = NULL; + /* For base relations, set scan_relid as the relid of the relation. */ scan_relid = foreignrel->relid; + /* find all the columns to include in the select list */ + /* examine each SELECT list entry for Var nodes */ + db2Debug3(" size of columnlist: %d", list_length(foreignrel->reltarget->exprs)); + foreach (cell, foreignrel->reltarget->exprs) { + db2Debug3(" examine column"); + getUsedColumns ((Expr*) lfirst (cell), fpinfo->db2Table, foreignrel->relid); + } + + /* examine each condition for Var nodes */ + db2Debug3(" size of conditions: %d", list_length(foreignrel->baserestrictinfo)); + foreach (cell, foreignrel->baserestrictinfo) { + db2Debug3(" examine condition"); + getUsedColumns ((Expr*) lfirst (cell), fpinfo->db2Table, foreignrel->relid); + } + /* In a base-relation scan, we must apply the given scan_clauses. * * Separate the scan_clauses into those that can be executed remotely and those that can't. @@ -75,7 +93,6 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo /* Ignore any pseudoconstants, they're dealt with elsewhere */ if (rinfo->pseudoconstant) continue; - if (list_member_ptr(fpinfo->remote_conds, rinfo)) remote_exprs = lappend(remote_exprs, rinfo->clause); else if (list_member_ptr(fpinfo->local_conds, rinfo)) @@ -149,19 +166,14 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo /* Remember remote_exprs for possible use by postgresPlanDirectModify */ fpinfo->final_remote_exprs = remote_exprs; - // TODO serialize the whole Db2FdwState as used to including these parameters and ensure the deserialization restores them correctly - /* Build the fdw_private list that will be available to the executor. * Items in the list must match order in enum FdwScanPrivateIndex. */ - fpinfo->query = sql.data; - fpinfo->retrieved_attr = retrieved_attrs; - fdw_private = serializePlanData(fpinfo); - -// fdw_private = list_make3(makeString(sql.data), retrieved_attrs, makeInteger(fpinfo->fetch_size)); -// if (IS_JOIN_REL(foreignrel) || IS_UPPER_REL(foreignrel)) -// fdw_private = lappend(fdw_private, makeString(fpinfo->relation_name)); - + fpinfo->db2Table->rncols = list_length(fdw_scan_tlist); + fpinfo->query = sql.data; + fpinfo->retrieved_attr = retrieved_attrs; + fdw_private = serializePlanData(fpinfo); + /* Create the ForeignScan node for the given relation. * * Note that the remote parameter expressions are stored in the fdw_exprs @@ -172,3 +184,216 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo db2Debug1("< %s::db2GetForeignPlan : %x",__FILE__,fscan); return fscan; } + +/** getUsedColumns + * Set "used=true" in db2Table for all columns used in the expression. + */ +static void getUsedColumns (Expr* expr, DB2Table* db2Table, int foreignrelid) { + ListCell* cell; + Var* variable; + int index; + + db2Debug1("> getUsedColumns"); + if (expr != NULL) { + switch (expr->type) { + case T_RestrictInfo: + getUsedColumns (((RestrictInfo*) expr)->clause, db2Table, foreignrelid); + break; + case T_TargetEntry: + getUsedColumns (((TargetEntry*) expr)->expr, db2Table, foreignrelid); + break; + case T_Const: + case T_Param: + case T_CaseTestExpr: + case T_CoerceToDomainValue: + case T_CurrentOfExpr: + case T_NextValueExpr: + break; + case T_Var: + variable = (Var*) expr; + /* ignore system columns */ + if (variable->varattno < 0) + break; + /* if this is a wholerow reference, we need all columns */ + if (variable->varattno == 0) { + for (index = 0; index < db2Table->ncols; ++index) { + if (db2Table->cols[index]->pgname) { + db2Table->cols[index]->used = 1; + } + } + break; + } + /* get db2Table column index corresponding to this column (-1 if none) */ + index = db2Table->ncols - 1; + while (index >= 0 && db2Table->cols[index]->pgattnum != variable->varattno) { + --index; + } + if (index == -1) { + ereport (WARNING, (errcode (ERRCODE_WARNING),errmsg ("column number %d of foreign table \"%s\" does not exist in foreign DB2 table, will be replaced by NULL", variable->varattno, db2Table->pgname))); + } else { + db2Table->cols[index]->used = 1; + } + break; + case T_Aggref: + foreach (cell, ((Aggref*) expr)->args) { + getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + } + foreach (cell, ((Aggref*) expr)->aggorder) { + getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + } + foreach (cell, ((Aggref*) expr)->aggdistinct) { + getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + } + break; + case T_WindowFunc: + foreach (cell, ((WindowFunc*) expr)->args) { + getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + } + break; + case T_SubscriptingRef: { + SubscriptingRef* ref = (SubscriptingRef*) expr; + foreach(cell, ref->refupperindexpr) { + getUsedColumns((Expr*)lfirst(cell), db2Table, foreignrelid); + } + foreach(cell, ref->reflowerindexpr) { + getUsedColumns((Expr*)lfirst(cell), db2Table, foreignrelid); + } + getUsedColumns(ref->refexpr, db2Table, foreignrelid); + getUsedColumns(ref->refassgnexpr, db2Table, foreignrelid); + } + break; + case T_FuncExpr: + foreach (cell, ((FuncExpr*) expr)->args) { + getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + } + break; + case T_OpExpr: + foreach (cell, ((OpExpr*) expr)->args) { + getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + } + break; + case T_DistinctExpr: + foreach (cell, ((DistinctExpr*) expr)->args) { + getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + } + break; + case T_NullIfExpr: + foreach (cell, ((NullIfExpr*) expr)->args) { + getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + } + break; + case T_ScalarArrayOpExpr: + foreach (cell, ((ScalarArrayOpExpr*) expr)->args) { + getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + } + break; + case T_BoolExpr: + foreach (cell, ((BoolExpr*) expr)->args) { + getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + } + break; + case T_SubPlan: + foreach (cell, ((SubPlan*) expr)->args) { + getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + } + break; + case T_AlternativeSubPlan: + /* examine only first alternative */ + getUsedColumns ((Expr*) linitial (((AlternativeSubPlan*) expr)->subplans), db2Table, foreignrelid); + break; + case T_NamedArgExpr: + getUsedColumns (((NamedArgExpr*) expr)->arg, db2Table, foreignrelid); + break; + case T_FieldSelect: + getUsedColumns (((FieldSelect*) expr)->arg, db2Table, foreignrelid); + break; + case T_RelabelType: + getUsedColumns (((RelabelType*) expr)->arg, db2Table, foreignrelid); + break; + case T_CoerceViaIO: + getUsedColumns (((CoerceViaIO*) expr)->arg, db2Table, foreignrelid); + break; + case T_ArrayCoerceExpr: + getUsedColumns (((ArrayCoerceExpr*) expr)->arg, db2Table, foreignrelid); + break; + case T_ConvertRowtypeExpr: + getUsedColumns (((ConvertRowtypeExpr*) expr)->arg, db2Table, foreignrelid); + break; + case T_CollateExpr: + getUsedColumns (((CollateExpr*) expr)->arg, db2Table, foreignrelid); + break; + case T_CaseExpr: + foreach (cell, ((CaseExpr*) expr)->args) { + getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + } + getUsedColumns (((CaseExpr*) expr)->arg, db2Table, foreignrelid); + getUsedColumns (((CaseExpr*) expr)->defresult, db2Table, foreignrelid); + break; + case T_CaseWhen: + getUsedColumns (((CaseWhen*) expr)->expr, db2Table, foreignrelid); + getUsedColumns (((CaseWhen*) expr)->result, db2Table, foreignrelid); + break; + case T_ArrayExpr: + foreach (cell, ((ArrayExpr*) expr)->elements) { + getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + } + break; + case T_RowExpr: + foreach (cell, ((RowExpr*) expr)->args) { + getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + } + break; + case T_RowCompareExpr: + foreach (cell, ((RowCompareExpr*) expr)->largs) { + getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + } + foreach (cell, ((RowCompareExpr*) expr)->rargs) { + getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + } + break; + case T_CoalesceExpr: + foreach (cell, ((CoalesceExpr*) expr)->args) { + getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + } + break; + case T_MinMaxExpr: + foreach (cell, ((MinMaxExpr*) expr)->args) { + getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + } + break; + case T_XmlExpr: + foreach (cell, ((XmlExpr*) expr)->named_args) { + getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + } + foreach (cell, ((XmlExpr*) expr)->args) { + getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + } + break; + case T_NullTest: + getUsedColumns (((NullTest*) expr)->arg, db2Table, foreignrelid); + break; + case T_BooleanTest: + getUsedColumns (((BooleanTest*) expr)->arg, db2Table, foreignrelid); + break; + case T_CoerceToDomain: + getUsedColumns (((CoerceToDomain*) expr)->arg, db2Table, foreignrelid); + break; + case T_PlaceHolderVar: + getUsedColumns (((PlaceHolderVar*) expr)->phexpr, db2Table, foreignrelid); + break; + case T_SQLValueFunction: + //nop + break; /* contains no column references */ + default: + /* + * We must be able to handle all node types that can + * appear because we cannot omit a column from the remote + * query that will be needed. + * Throw an error if we encounter an unexpected node type. + */ + ereport (ERROR, (errcode (ERRCODE_FDW_UNABLE_TO_CREATE_REPLY), errmsg ("Internal db2_fdw error: encountered unknown node type %d.", expr->type))); + break; + } + } + db2Debug1("< getUsedColumns"); +} diff --git a/source/db2GetForeignRelSize.c b/source/db2GetForeignRelSize.c index 73625b1..83352d1 100644 --- a/source/db2GetForeignRelSize.c +++ b/source/db2GetForeignRelSize.c @@ -1,18 +1,33 @@ #include +#include +#include +#include +#include #include +#include #include -#include +#include +#include #include "db2_fdw.h" #include "DB2FdwState.h" +#include "DB2FdwPathExtraData.h" /** external prototypes */ extern DB2FdwState* db2GetFdwState (Oid foreigntableid, double* sample_percent, bool describe); extern void db2Debug1 (const char* message, ...); extern char* deparseWhereConditions (PlannerInfo* root, RelOptInfo* baserel); extern void db2free (void* p); +extern void classifyConditions (PlannerInfo* root, RelOptInfo* baserel, List* input_conds, List** remote_conds, List** local_conds); +extern void estimate_path_cost_size (PlannerInfo* root, RelOptInfo* foreignrel, List* param_join_conds, List* pathkeys, DB2FdwPathExtraData* fpextra, double* p_rows, int* p_width, int* p_disabled_nodes, Cost* p_startup_cost, Cost* p_total_cost); /** local prototypes */ -void db2GetForeignRelSize (PlannerInfo* root, RelOptInfo* baserel, Oid foreigntableid); + void db2GetForeignRelSize (PlannerInfo* root, RelOptInfo* baserel, Oid foreigntableid); +static void db2PopulateFdwStateOld(PlannerInfo* root, RelOptInfo* baserel, Oid foreigntableid); +static void db2PopulateFdwStateNew(PlannerInfo* root, RelOptInfo* baserel, Oid foreigntableid); +static void apply_server_options (DB2FdwState* fpinfo); +static void apply_table_options (DB2FdwState* fpinfo); +static List* ExtractExtensionList (const char* extensionsString, bool warnOnMissing); + /** db2GetForeignRelSize * Get an DB2FdwState for this foreign scan. @@ -21,14 +36,22 @@ void db2GetForeignRelSize (PlannerInfo* root, RelOptInfo* baserel, Oid foreign */ void db2GetForeignRelSize (PlannerInfo* root, RelOptInfo* baserel, Oid foreigntableid) { DB2FdwState* fdwState = NULL; - int i = 0; - double ntuples = -1; db2Debug1("> db2GetForeignRelSize"); /* get connection options, connect and get the remote table description */ fdwState = db2GetFdwState(foreigntableid, NULL, true); /* store the state so that the other planning functions can use it */ - baserel->fdw_private = (void *) fdwState; + baserel->fdw_private = (void*) fdwState; + db2PopulateFdwStateOld(root, baserel,foreigntableid); + db2PopulateFdwStateNew(root, baserel,foreigntableid); + db2Debug1("< db2GetForeignRelSize"); +} + +static void db2PopulateFdwStateOld(PlannerInfo* root, RelOptInfo* baserel, Oid foreigntableid){ + DB2FdwState* fdwState = (DB2FdwState*)baserel->fdw_private; + int i = 0; + double ntuples = -1; + /** Store the table OID in each table column. * This is redundant for base relations, but join relations will * have columns from different tables, and we have to keep track of them. @@ -62,5 +85,184 @@ void db2GetForeignRelSize (PlannerInfo* root, RelOptInfo* baserel, Oid foreignta } /* estimate total cost as startup cost + 10 * (returned rows) */ fdwState->total_cost = fdwState->startup_cost + baserel->rows * 10.0; - db2Debug1("< db2GetForeignRelSize"); + +} + +static void db2PopulateFdwStateNew(PlannerInfo* root, RelOptInfo* baserel, Oid foreigntableid){ + DB2FdwState* fpinfo = (DB2FdwState*)baserel->fdw_private; + ListCell* lc = NULL; + + /* Base foreign tables need to be pushed down always. */ + fpinfo->pushdown_safe = true; + + /* Look up foreign-table catalog info. */ + fpinfo->ftable = GetForeignTable(foreigntableid); + fpinfo->fserver = GetForeignServer(fpinfo->ftable->serverid); + + /* Extract user-settable option values. Note that per-table settings of use_remote_estimate, fetch_size and async_capable override per-server + * settings of them, respectively. + */ + fpinfo->use_remote_estimate = false; + fpinfo->fdw_startup_cost = DEFAULT_FDW_STARTUP_COST; + fpinfo->fdw_tuple_cost = DEFAULT_FDW_TUPLE_COST; + fpinfo->shippable_extensions = NIL; + fpinfo->async_capable = false; + + apply_server_options(fpinfo); + apply_table_options(fpinfo); + + /* If the table or the server is configured to use remote estimates, identify which user to do remote access as during planning. + * This should match what ExecCheckPermissions() does. If we fail due to lack of permissions, the query would have failed at runtime anyway. + */ + if (fpinfo->use_remote_estimate) { + Oid userid; + + userid = OidIsValid(baserel->userid) ? baserel->userid : GetUserId(); + fpinfo->fuser = GetUserMapping(userid, fpinfo->fserver->serverid); + } else { + fpinfo->fuser = NULL; + } + + /* Identify which baserestrictinfo clauses can be sent to the remote server and which can't. */ + classifyConditions(root, baserel, baserel->baserestrictinfo, &fpinfo->remote_conds, &fpinfo->local_conds); + + /* Identify which attributes will need to be retrieved from the remote server. + * These include all attrs needed for joins or final output, plus all attrs used in the local_conds. + * (Note: if we end up using a parameterized scan, it's possible that some of the join clauses will be sent to the remote + * and thus we wouldn't really need to retrieve the columns used in them. Doesn't seem worth detecting that case though.) + */ + fpinfo->attrs_used = NULL; + pull_varattnos((Node *) baserel->reltarget->exprs, baserel->relid, &fpinfo->attrs_used); + foreach(lc, fpinfo->local_conds) { + RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc); + + pull_varattnos((Node *) rinfo->clause, baserel->relid, &fpinfo->attrs_used); + } + + /* Compute the selectivity and cost of the local_conds, so we don't have to do it over again for each path. + * The best we can do for these conditions is to estimate selectivity on the basis of local statistics. + */ + fpinfo->local_conds_sel = clauselist_selectivity(root, fpinfo->local_conds, baserel->relid, JOIN_INNER, NULL); + cost_qual_eval(&fpinfo->local_conds_cost, fpinfo->local_conds, root); + + /* Set # of retrieved rows and cached relation costs to some negative value, so that we can detect when they are set to some sensible values, + * during one (usually the first) of the calls to estimate_path_cost_size. + */ + fpinfo->retrieved_rows = -1; + fpinfo->rel_startup_cost = -1; + fpinfo->rel_total_cost = -1; + + /* If the table or the server is configured to use remote estimates, connect to the foreign server and execute EXPLAIN to estimate the + * number of rows selected by the restriction clauses, as well as the average row width. Otherwise, estimate using whatever statistics we + * have locally, in a way similar to ordinary tables. + */ + if (fpinfo->use_remote_estimate) { + /* Get cost/size estimates with help of remote server. + * Save the values in fpinfo so we don't need to do it again to generate the basic foreign path. + */ + estimate_path_cost_size(root, baserel, NIL, NIL, NULL, &fpinfo->rows, &fpinfo->width, &fpinfo->disabled_nodes, &fpinfo->startup_cost, &fpinfo->total_cost); + + /* Report estimated baserel size to planner. */ + baserel->rows = fpinfo->rows; + baserel->reltarget->width = fpinfo->width; + } else { + /* If the foreign table has never been ANALYZEd, it will have reltuples < 0, meaning "unknown". + * We can't do much if we're not allowed to consult the remote server, but we can use a hack similar + * to plancat.c's treatment of empty relations: use a minimum size estimate of 10 pages, and divide by + * the column-datatype-based width estimate to get the corresponding number of tuples. + */ + if (baserel->tuples < 0) { + baserel->pages = 10; + baserel->tuples = (10 * BLCKSZ) / (baserel->reltarget->width + MAXALIGN(SizeofHeapTupleHeader)); + } + + /* Estimate baserel size as best we can with local statistics. */ + set_baserel_size_estimates(root, baserel); + + /* Fill in basically-bogus cost estimates for use later. */ + estimate_path_cost_size(root, baserel, NIL, NIL, NULL, &fpinfo->rows, &fpinfo->width, &fpinfo->disabled_nodes, &fpinfo->startup_cost, &fpinfo->total_cost); + } + + /* fpinfo->relation_name gets the numeric rangetable index of the foreign table RTE. + * (If this query gets EXPLAIN'd, we'll convert that to a human-readable string at that time.) + */ + fpinfo->relation_name = psprintf("%u", baserel->relid); + + /* No outer and inner relations. */ + fpinfo->make_outerrel_subquery = false; + fpinfo->make_innerrel_subquery = false; + fpinfo->lower_subquery_rels = NULL; + fpinfo->hidden_subquery_rels = NULL; + /* Set the relation index. */ + fpinfo->relation_index = baserel->relid; +} + +/* Parse options from foreign server and apply them to fpinfo. + * New options might also require tweaking merge_fdw_options(). + */ +static void apply_server_options(DB2FdwState* fpinfo) { + ListCell* lc; + + foreach(lc, fpinfo->fserver->options) { + DefElem* def = (DefElem*) lfirst(lc); + + if (strcmp(def->defname, "use_remote_estimate") == 0) + fpinfo->use_remote_estimate = defGetBoolean(def); + else if (strcmp(def->defname, "fdw_startup_cost") == 0) + (void) parse_real(defGetString(def), &fpinfo->fdw_startup_cost, 0, NULL); + else if (strcmp(def->defname, "fdw_tuple_cost") == 0) + (void) parse_real(defGetString(def), &fpinfo->fdw_tuple_cost, 0, NULL); + else if (strcmp(def->defname, "extensions") == 0) + fpinfo->shippable_extensions = ExtractExtensionList(defGetString(def), false); + else if (strcmp(def->defname, "fetch_size") == 0) + (void) parse_int(defGetString(def), &fpinfo->fetch_size, 0, NULL); + else if (strcmp(def->defname, "async_capable") == 0) + fpinfo->async_capable = defGetBoolean(def); + } +} + +/* Parse options from foreign table and apply them to fpinfo. + * New options might also require tweaking merge_fdw_options(). + */ +static void apply_table_options(DB2FdwState* fpinfo) { + ListCell* lc; + + foreach(lc, fpinfo->ftable->options) { + DefElem* def = (DefElem*) lfirst(lc); + + if (strcmp(def->defname, "use_remote_estimate") == 0) + fpinfo->use_remote_estimate = defGetBoolean(def); + else if (strcmp(def->defname, "fetch_size") == 0) + (void) parse_int(defGetString(def), &fpinfo->fetch_size, 0, NULL); + else if (strcmp(def->defname, "async_capable") == 0) + fpinfo->async_capable = defGetBoolean(def); + } +} + +/* Parse a comma-separated string and return a List of the OIDs of the extensions named in the string. If any names in the list cannot be + * found, report a warning if warnOnMissing is true, else just silently ignore them. + */ +static List * ExtractExtensionList(const char *extensionsString, bool warnOnMissing) { + List* extensionOids = NIL; + List* extlist = NIL; + ListCell* lc = NULL; + + /* SplitIdentifierString scribbles on its input, so pstrdup first */ + if (!SplitIdentifierString(pstrdup(extensionsString), ',', &extlist)) { + /* syntax error in name list */ + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("parameter \"%s\" must be a list of extension names", "extensions"))); + } + + foreach(lc, extlist) { + const char *extension_name = (const char *) lfirst(lc); + Oid extension_oid = get_extension_oid(extension_name, true); + + if (OidIsValid(extension_oid)) { + extensionOids = lappend_oid(extensionOids, extension_oid); + } else if (warnOnMissing) { + ereport(WARNING, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("extension \"%s\" is not installed", extension_name))); + } + } + list_free(extlist); + return extensionOids; } diff --git a/source/db2GetForeignUpperPaths.c b/source/db2GetForeignUpperPaths.c index b33af11..fb89dcf 100644 --- a/source/db2GetForeignUpperPaths.c +++ b/source/db2GetForeignUpperPaths.c @@ -48,7 +48,7 @@ static void add_foreign_ordered_paths (PlannerInfo* root, RelOptInfo* in static void add_foreign_final_paths (PlannerInfo* root, RelOptInfo* input_rel, RelOptInfo* final_rel, FinalPathExtraData *extra); static void adjust_foreign_grouping_path_cost(PlannerInfo* root, List* pathkeys, double retrieved_rows, double width, double limit_tuples, int* p_disabled_nodes, Cost* p_startup_cost, Cost* p_run_cost); static bool foreign_grouping_ok (PlannerInfo* root, RelOptInfo* grouped_rel, Node* havingQual); -static void estimate_path_cost_size (PlannerInfo* root, RelOptInfo* foreignrel, List* param_join_conds, List* pathkeys, DB2FdwPathExtraData* fpextra, double* p_rows, int* p_width, int* p_disabled_nodes, Cost* p_startup_cost, Cost* p_total_cost); + void estimate_path_cost_size (PlannerInfo* root, RelOptInfo* foreignrel, List* param_join_conds, List* pathkeys, DB2FdwPathExtraData* fpextra, double* p_rows, int* p_width, int* p_disabled_nodes, Cost* p_startup_cost, Cost* p_total_cost); static void merge_fdw_options (DB2FdwState* fpinfo, const DB2FdwState* fpinfo_o, const DB2FdwState* fpinfo_i); void db2GetForeignUpperPaths(PlannerInfo *root, UpperRelationKind stage, RelOptInfo *input_rel, RelOptInfo *output_rel, void *extra) { @@ -620,13 +620,11 @@ static void add_foreign_final_paths(PlannerInfo *root, RelOptInfo *input_rel, Re } } - /* - * If we get here it means no ForeignPaths; since we would already - * have considered pushing down all operations for the query to the - * remote server, give up on it. - */ - return; - } + /* If we get here it means no ForeignPaths; since we would already have considered pushing down all operations for the query to the + * remote server, give up on it. + */ + return; + } Assert(extra->limit_needed); @@ -641,38 +639,27 @@ static void add_foreign_final_paths(PlannerInfo *root, RelOptInfo *input_rel, Re /* The input_rel should be a base, join, or grouping relation */ Assert(input_rel->reloptkind == RELOPT_BASEREL || input_rel->reloptkind == RELOPT_JOINREL || (input_rel->reloptkind == RELOPT_UPPER_REL && ifpinfo->stage == UPPERREL_GROUP_AGG)); - /** We try to create a path below by extending a simple foreign path for - * the underlying base, join, or grouping relation to perform the final - * sort (if has_final_sort) and the LIMIT restriction remotely, which is - * stored into the fdw_private list of the resulting path. (We - * re-estimate the costs of sorting the underlying relation, if - * has_final_sort.) + /* We try to create a path below by extending a simple foreign path for the underlying base, join, or grouping relation to perform the final + * sort (if has_final_sort) and the LIMIT restriction remotely, which is stored into the fdw_private list of the resulting path. + * (We re-estimate the costs of sorting the underlying relation, if has_final_sort.) */ - /** Assess if it is safe to push down the LIMIT and OFFSET to the remote server - */ + /* Assess if it is safe to push down the LIMIT and OFFSET to the remote server */ - /** If the underlying relation has any local conditions, the LIMIT/OFFSET cannot be pushed down. - */ + /* If the underlying relation has any local conditions, the LIMIT/OFFSET cannot be pushed down. */ if (ifpinfo->local_conds) return; - /** If the query has FETCH FIRST .. WITH TIES, 1) it must have ORDER BY as - * well, which is used to determine which additional rows tie for the last - * place in the result set, and 2) ORDER BY must already have been - * determined to be safe to push down before we get here. So in that case - * the FETCH clause is safe to push down with ORDER BY if the remote - * server is v13 or later, but if not, the remote query will fail entirely - * for lack of support for it. Since we do not currently have a way to do - * a remote-version check (without accessing the remote server), disable - * pushing the FETCH clause for now. + /* If the query has FETCH FIRST .. WITH TIES, 1) it must have ORDER BY as well, which is used to determine which additional rows tie for the last + * place in the result set, and 2) ORDER BY must already have been determined to be safe to push down before we get here. + * So in that case the FETCH clause is safe to push down with ORDER BY if the remote server is v13 or later, but if not, the remote query will fail + * entirely for lack of support for it. + * Since we do not currently have a way to do a remote-version check (without accessing the remote server), disable pushing the FETCH clause for now. */ if (parse->limitOption == LIMIT_OPTION_WITH_TIES) return; - /** Also, the LIMIT/OFFSET cannot be pushed down, if their expressions are - * not safe to remote. - */ + /* Also, the LIMIT/OFFSET cannot be pushed down, if their expressions are not safe to remote. */ if (!is_foreign_expr(root, input_rel, (Expr *) parse->limitOffset) || !is_foreign_expr(root, input_rel, (Expr *) parse->limitCount)) return; @@ -688,13 +675,10 @@ static void add_foreign_final_paths(PlannerInfo *root, RelOptInfo *input_rel, Re fpextra->count_est = extra->count_est; fpextra->offset_est = extra->offset_est; - /** Estimate the costs of performing the final sort and the LIMIT - * restriction remotely. If has_final_sort is false, we wouldn't need to - * execute EXPLAIN anymore if use_remote_estimate, since the costs can be - * roughly estimated using the costs we already have for the underlying - * relation, in the same way as when use_remote_estimate is false. Since - * it's pretty expensive to execute EXPLAIN, force use_remote_estimate to - * false in that case. + /* Estimate the costs of performing the final sort and the LIMIT restriction remotely. + * If has_final_sort is false, we wouldn't need to execute EXPLAIN anymore if use_remote_estimate, since the costs can be + * roughly estimated using the costs we already have for the underlying relation, in the same way as when use_remote_estimate is false. + * Since it's pretty expensive to execute EXPLAIN, force use_remote_estimate to false in that case. */ if (!fpextra->has_final_sort) { save_use_remote_estimate = ifpinfo->use_remote_estimate; @@ -704,14 +688,10 @@ static void add_foreign_final_paths(PlannerInfo *root, RelOptInfo *input_rel, Re if (!fpextra->has_final_sort) ifpinfo->use_remote_estimate = save_use_remote_estimate; - /** Build the fdw_private list that will be used by postgresGetForeignPlan. - * Items in the list must match order in enum FdwPathPrivateIndex. - */ + /* Build the fdw_private list that will be used by postgresGetForeignPlan. Items in the list must match order in enum FdwPathPrivateIndex. */ fdw_private = list_make2(makeBoolean(has_final_sort), makeBoolean(extra->limit_needed)); - /** Create foreign final path; this gets rid of a no-longer-needed outer - * plan (if any), which makes the EXPLAIN output look cleaner - */ + /* Create foreign final path; this gets rid of a no-longer-needed outer plan (if any), which makes the EXPLAIN output look cleaner */ final_path = create_foreign_upper_path( root , input_rel , root->upper_targets[UPPERREL_FINAL] @@ -954,7 +934,7 @@ static bool foreign_grouping_ok(PlannerInfo* root, RelOptInfo* grouped_rel, Node * The function returns the cost and size estimates in p_rows, p_width, * p_disabled_nodes, p_startup_cost and p_total_cost variables. */ -static void estimate_path_cost_size(PlannerInfo* root, RelOptInfo* foreignrel, List* param_join_conds, List* pathkeys, DB2FdwPathExtraData* fpextra, double* p_rows, int* p_width, int* p_disabled_nodes, Cost* p_startup_cost, Cost* p_total_cost) { +void estimate_path_cost_size(PlannerInfo* root, RelOptInfo* foreignrel, List* param_join_conds, List* pathkeys, DB2FdwPathExtraData* fpextra, double* p_rows, int* p_width, int* p_disabled_nodes, Cost* p_startup_cost, Cost* p_total_cost) { DB2FdwState* fpinfo = (DB2FdwState*) foreignrel->fdw_private; double rows = 0; double retrieved_rows = 0; diff --git a/source/db2PrepareQuery.c b/source/db2PrepareQuery.c index fda3ff6..6a8fe0a 100644 --- a/source/db2PrepareQuery.c +++ b/source/db2PrepareQuery.c @@ -21,6 +21,7 @@ extern void db2Error_d (db2error sqlstate, const char* message extern HdlEntry* db2AllocStmtHdl (SQLSMALLINT type, DB2ConnEntry* connp, db2error error, const char* errmsg); extern SQLSMALLINT c2param (SQLSMALLINT fparamType); extern char* param2name (SQLSMALLINT fparamType); +extern void* db2alloc (const char* type, size_t size); /** internal prototypes */ void db2PrepareQuery (DB2Session* session, const char *query, DB2Table* db2Table, unsigned long prefetch, int fetchsize); @@ -154,16 +155,37 @@ void db2PrepareQuery (DB2Session* session, const char *query, DB2Table* db2Table } } } + db2Debug2(" is_select: %s",is_select ? "true" : "false"); + db2Debug2(" col_pos: %d",col_pos); if (is_select && col_pos == 0) { - /* - * No columns selected (i.e., SELECT '1' FROM or COUNT(*)). - * Use persistent buffers from statement handle to avoid stack deallocation issues. - * This fixes the segfault when using aggregate functions without WHERE clause. - */ - rc = SQLBindCol(session->stmtp->hsql, 1, SQL_C_CHAR, session->stmtp->dummy_buffer, sizeof(session->stmtp->dummy_buffer), &session->stmtp->dummy_null); - rc = db2CheckErr(rc, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); - if (rc != SQL_SUCCESS) { - db2Error_d ( FDW_UNABLE_TO_CREATE_EXECUTION, "error executing query: SQLBindCol failed to define result value", db2Message); + if (db2Table->rncols > 0) { + int idx = 0; + // count the number of comma between SELECT and FROM to understand how many result columns we need. + for (idx=0; idx < db2Table->rncols - col_pos; idx++) { + SQLCHAR* dummy_buffer = (SQLCHAR*)db2alloc("dummy_buffer",256); + SQLLEN* dummy_null = (SQLLEN*)db2alloc("dummy_null",sizeof(SQLLEN)); + rc = SQLBindCol(session->stmtp->hsql + , idx+1 + , SQL_C_CHAR + , dummy_buffer + , sizeof(dummy_buffer) + , dummy_null + ); + rc = db2CheckErr(rc, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); + if (rc != SQL_SUCCESS) { + db2Error_d ( FDW_UNABLE_TO_CREATE_EXECUTION, "error executing query: SQLBindCol failed to define result value", db2Message); + } + } + } else { + /* No columns selected (i.e., SELECT '1' FROM or COUNT(*)). + * Use persistent buffers from statement handle to avoid stack deallocation issues. + * This fixes the segfault when using aggregate functions without WHERE clause. + */ + rc = SQLBindCol(session->stmtp->hsql, 1, SQL_C_CHAR, session->stmtp->dummy_buffer, sizeof(session->stmtp->dummy_buffer), &session->stmtp->dummy_null); + rc = db2CheckErr(rc, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); + if (rc != SQL_SUCCESS) { + db2Error_d ( FDW_UNABLE_TO_CREATE_EXECUTION, "error executing query: SQLBindCol failed to define result value", db2Message); + } } } diff --git a/source/db2_de_serialize.c b/source/db2_de_serialize.c index 12405d8..e9270a0 100644 --- a/source/db2_de_serialize.c +++ b/source/db2_de_serialize.c @@ -83,6 +83,8 @@ DB2FdwState* deserializePlanData (List* list) { db2Debug2(" state->db2Table->batchsz: %d",state->db2Table->batchsz); state->db2Table->ncols = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); db2Debug2(" state->db2Table->ncols: %d",state->db2Table->ncols); + state->db2Table->rncols = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); + db2Debug2(" state->db2Table->rncols: %d",state->db2Table->rncols); state->db2Table->npgcols = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); db2Debug2(" state->db2Table->npgcols: %d",state->db2Table->npgcols); state->db2Table->cols = (DB2Column**) db2alloc ("state->db2Table->cols", sizeof (DB2Column*) * state->db2Table->ncols); @@ -218,6 +220,8 @@ List* serializePlanData (DB2FdwState* fdwState) { result = lappend (result, serializeInt (fdwState->db2Table->batchsz)); /* number of columns in DB2 table */ result = lappend (result, serializeInt (fdwState->db2Table->ncols)); + /* number of result columns in DB2 table */ + result = lappend (result, serializeInt (fdwState->db2Table->rncols)); /* number of columns in PostgreSQL table */ result = lappend (result, serializeInt (fdwState->db2Table->npgcols)); /* column data */ From c705ff60c7757db3ee59ca27f3b1214037ae5a79 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Fri, 6 Feb 2026 16:22:55 +0100 Subject: [PATCH 028/120] the repo has moved to pg-fdw --- META.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/META.json b/META.json index c9907ea..b86d7e5 100644 --- a/META.json +++ b/META.json @@ -4,7 +4,7 @@ "description": "With the Data Wrapper you can acces DB2 Tabels. Not supported for all Data Types (BLOB over 2 GByte)", "version": "18.1.1", "maintainer": [ - "Thomas Muenz " + "Thomas Muenz " ], "license": "postgresql", "provides": { @@ -17,8 +17,8 @@ }, "resources": { "repository": { - "url": "https://github.com/Living-Mainframe/db2_fdw.git", - "web": "https://github.com/Living-Mainframe/db2_fdw", + "url": "https://github.com/pg-fdw/db2_fdw.git", + "web": "https://github.com/pg-fdw/db2_fdw", "type": "git" } }, From 02f9a3407324ba3bb1e2d353ad78f24b38d80b72 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Fri, 6 Feb 2026 16:53:10 +0100 Subject: [PATCH 029/120] Update documentation to reflect correct GitHub URL and author email --- db2_fdw.spec | 2 +- doc/db2_fdw.md | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/db2_fdw.spec b/db2_fdw.spec index dca62e6..ac558b3 100644 --- a/db2_fdw.spec +++ b/db2_fdw.spec @@ -5,7 +5,7 @@ Version: %{fdw_version} Release: 1%{?dist} License: PostgreSQL License Group: Applications/Databases -Url: https://github.com/Living-Mainframe/db2_fdw +Url: https://github.com/pg-fdw/db2_fdw %undefine _disable_source_fetch BuildArch: x86_64 diff --git a/doc/db2_fdw.md b/doc/db2_fdw.md index c44979a..3144f17 100644 --- a/doc/db2_fdw.md +++ b/doc/db2_fdw.md @@ -21,16 +21,16 @@ Usage Support ------- - [GitHub Link to db2_fdw](https://github.com/Living-Mainframe/db2_fdw) + [GitHub Link to db2_fdw](https://github.com/pg-fdw/db2_fdw) - [email](thomas.muenz@living-mainframe.de) + [email](thomas.muenz@pg-fdw.de) Author ------ -[Thomas Muenz](thomas.muenz@living-mainframe.de) +[Thomas Muenz](thomas.muenz@pg-fdw.de) Copyright and License --------------------- -Copyright (c) 2025 Thomas Muenz, Living-Mainframe GmbH +Copyright (c) 2025 Thomas Muenz, pg-fdw.de From 16ddb47bceab1d94847699fcdd5ea24f53dbadfe Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Sat, 7 Feb 2026 23:44:24 +0100 Subject: [PATCH 030/120] first working version of aggregate push down --- include/DB2FdwState.h | 2 + include/DB2ResultColumn.h | 34 +++++ source/db2AnalyzeForeignTable.c | 6 +- source/db2ExecForeignDelete.c | 4 +- source/db2ExecForeignInsert.c | 4 +- source/db2ExecForeignUpdate.c | 4 +- source/db2GetForeignPlan.c | 175 ++++++++++++++--------- source/db2IterateForeignScan.c | 19 +-- source/db2PlanForeignModify.c | 14 +- source/db2PrepareQuery.c | 162 +++++++++++++--------- source/db2_de_serialize.c | 236 ++++++++++++++++++++++++-------- source/db2_fdw_utils.c | 95 +++++++------ 12 files changed, 506 insertions(+), 249 deletions(-) create mode 100644 include/DB2ResultColumn.h diff --git a/include/DB2FdwState.h b/include/DB2FdwState.h index 673c22a..402b30d 100644 --- a/include/DB2FdwState.h +++ b/include/DB2FdwState.h @@ -3,6 +3,7 @@ #include #include "ParamDesc.h" +#include "DB2ResultColumn.h" /** DB2FdwState * FDW-specific information for RelOptInfo.fdw_private and ForeignScanState.fdw_state. @@ -30,6 +31,7 @@ typedef struct db2FdwState { char* query; // query we issue against DB2 List* params; // list of parameters needed for the query ParamDesc* paramList; // description of parameters needed for the query + DB2ResultColumn* resultList; // list of result columns for the query DB2Table* db2Table; // description of the remote DB2 table unsigned long rowcount; // rows already read from DB2 int columnindex; // currently processed column for error context diff --git a/include/DB2ResultColumn.h b/include/DB2ResultColumn.h new file mode 100644 index 0000000..0891997 --- /dev/null +++ b/include/DB2ResultColumn.h @@ -0,0 +1,34 @@ +#ifndef DB2RESULTCOLUMN_H +#define DB2RESULTCOLUMN_H +/** DB2ResultColumn + * A full descriptor of a DB2 table column and its corresponding PG column. + * + * @author Thomas Muenz + * @since 18.2.0 + */ +typedef struct db2ResultColumn { + char* colName; // column name in DB2 + short colType; // column data type in DB2 + size_t colSize; // column size + short colScale; // column scale of size describing digits right of decimal point + short colNulls; // column is nullable + size_t colChars; // numer of characters fit in column size, it is less if UTF8, 16 or DBCS + size_t colBytes; // number of bytes representing colSize + int colPrimKeyPart;// 1 if column is part of the primary key - only relevant for UPDATE or DELETE + int colCodepage; // codepage set for this column (only set on char columns), if 0 the content is binary + char* pgname; // PG column name + int pgattnum; // PG attribute number + Oid pgtype; // PG data type + int pgtypmod; // PG type modifier + int pkey; // nonzero for primary keys, later set to the resjunk attribute number + int resnum; // position of result in cursor 1 based + char* val; // buffer for DB2 to return results in (LOB locator for LOBs) + size_t val_size; // allocated size in val + size_t val_len; // actual length of val + int val_null; // indicator for NULL value + int varno; // range table index of this column's relation + db2NoEncErrType noencerr; // no encoding error produced + struct db2ResultColumn* next; +} DB2ResultColumn; + +#endif \ No newline at end of file diff --git a/source/db2AnalyzeForeignTable.c b/source/db2AnalyzeForeignTable.c index d8b9831..7bde7ee 100644 --- a/source/db2AnalyzeForeignTable.c +++ b/source/db2AnalyzeForeignTable.c @@ -16,7 +16,7 @@ extern int db2ExecuteQuery (DB2Session* session, const DB2Tab extern int db2FetchNext (DB2Session* session); extern void checkDataType (short db2type, int scale, Oid pgtype, const char* tablename, const char* colname); extern short c2dbType (short fcType); -extern void convertTuple (DB2FdwState* fdw_state, Datum* values, bool* nulls, bool trunc_lob) ; +extern void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls, bool trunc_lob) ; extern void db2Debug1 (const char* message, ...); extern void db2Debug2 (const char* message, ...); extern void db2Debug3 (const char* message, ...); @@ -140,7 +140,7 @@ int acquireSampleRowsFunc (Relation relation, int elevel, HeapTuple * rows, int /* the first "targrows" rows are added as samples */ /* use a temporary memory context during convertTuple */ old_cxt = MemoryContextSwitchTo (tmp_cxt); - convertTuple (fdw_state, values, nulls, true); + convertTuple (fdw_state, tupDesc->natts, values, nulls, true); MemoryContextSwitchTo (old_cxt); rows[collected_rows++] = heap_form_tuple (tupDesc, values, nulls); MemoryContextReset (tmp_cxt); @@ -157,7 +157,7 @@ int acquireSampleRowsFunc (Relation relation, int elevel, HeapTuple * rows, int heap_freetuple (rows[k]); /* use a temporary memory context during convertTuple */ old_cxt = MemoryContextSwitchTo (tmp_cxt); - convertTuple (fdw_state, values, nulls, true); + convertTuple (fdw_state, tupDesc->natts, values, nulls, true); MemoryContextSwitchTo (old_cxt); rows[k] = heap_form_tuple (tupDesc, values, nulls); MemoryContextReset (tmp_cxt); diff --git a/source/db2ExecForeignDelete.c b/source/db2ExecForeignDelete.c index 0abadf4..9936d4f 100644 --- a/source/db2ExecForeignDelete.c +++ b/source/db2ExecForeignDelete.c @@ -14,7 +14,7 @@ extern regproc* output_funcs; extern int db2ExecuteQuery (DB2Session* session, const DB2Table* db2Table, ParamDesc* paramList); extern void db2Debug1 (const char* message, ...); extern void db2Debug2 (const char* message, ...); -extern void convertTuple (DB2FdwState* fdw_state, Datum* values, bool* nulls, bool trunc_lob) ; +extern void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls, bool trunc_lob) ; extern char* deparseDate (Datum datum); extern char* deparseTimestamp (Datum datum, bool hasTimezone); extern void* db2alloc (const char* type, size_t size); @@ -63,7 +63,7 @@ TupleTableSlot* db2ExecForeignDelete (EState* estate, ResultRelInfo* rinfo, Tupl ExecClearTuple (slot); /* convert result for RETURNING to arrays of values and null indicators */ - convertTuple (fdw_state, slot->tts_values, slot->tts_isnull, false); + convertTuple (fdw_state, slot->tts_tupleDescriptor->natts, slot->tts_values, slot->tts_isnull, false); /* store the virtual tuple */ ExecStoreVirtualTuple (slot); diff --git a/source/db2ExecForeignInsert.c b/source/db2ExecForeignInsert.c index bd44a44..c92f672 100644 --- a/source/db2ExecForeignInsert.c +++ b/source/db2ExecForeignInsert.c @@ -16,7 +16,7 @@ extern void db2Debug1 (const char* message, ...); #ifdef WRITE_API extern void setModifyParameters (ParamDesc* paramList, TupleTableSlot* newslot, TupleTableSlot* oldslot, DB2Table* db2Table, DB2Session* session); #endif -extern void convertTuple (DB2FdwState* fdw_state, Datum* values, bool* nulls, bool trunc_lob) ; +extern void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls, bool trunc_lob) ; /** local prototypes */ TupleTableSlot* db2ExecForeignInsert(EState* estate, ResultRelInfo* rinfo, TupleTableSlot* slot, TupleTableSlot* planSlot); @@ -54,7 +54,7 @@ TupleTableSlot* db2ExecForeignInsert (EState* estate, ResultRelInfo* rinfo, Tupl ExecClearTuple (slot); /* convert result for RETURNING to arrays of values and null indicators */ - convertTuple (fdw_state, slot->tts_values, slot->tts_isnull, false); + convertTuple (fdw_state, slot->tts_tupleDescriptor->natts, slot->tts_values, slot->tts_isnull, false); /* store the virtual tuple */ ExecStoreVirtualTuple (slot); diff --git a/source/db2ExecForeignUpdate.c b/source/db2ExecForeignUpdate.c index 20ee9be..3509444 100644 --- a/source/db2ExecForeignUpdate.c +++ b/source/db2ExecForeignUpdate.c @@ -17,7 +17,7 @@ extern void db2Debug2 (const char* message, ...); #ifdef WRITE_API extern void setModifyParameters (ParamDesc* paramList, TupleTableSlot* newslot, TupleTableSlot* oldslot, DB2Table* db2Table, DB2Session* session); #endif -extern void convertTuple (DB2FdwState* fdw_state, Datum* values, bool* nulls, bool trunc_lob) ; +extern void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls, bool trunc_lob) ; /** local prototypes */ TupleTableSlot* db2ExecForeignUpdate (EState* estate, ResultRelInfo* rinfo, TupleTableSlot* slot, TupleTableSlot* planSlot); @@ -60,7 +60,7 @@ TupleTableSlot* db2ExecForeignUpdate (EState* estate, ResultRelInfo* rinfo, Tupl ExecClearTuple (slot); /* convert result for RETURNING to arrays of values and null indicators */ - convertTuple (fdw_state, slot->tts_values, slot->tts_isnull, false); + convertTuple (fdw_state, slot->tts_tupleDescriptor->natts, slot->tts_values, slot->tts_isnull, false); /* store the virtual tuple */ ExecStoreVirtualTuple (slot); diff --git a/source/db2GetForeignPlan.c b/source/db2GetForeignPlan.c index 6d4328e..2398256 100644 --- a/source/db2GetForeignPlan.c +++ b/source/db2GetForeignPlan.c @@ -1,6 +1,7 @@ #include #include #include +#include #include "db2_fdw.h" #include "DB2FdwState.h" @@ -19,6 +20,10 @@ enum FdwPathPrivateIndex { extern void db2Debug1 (const char* message, ...); extern void db2Debug2 (const char* message, ...); extern void db2Debug3 (const char* message, ...); +extern void* db2alloc (const char* type, size_t size); +extern char* db2strdup (const char* source); + + extern bool is_foreign_expr (PlannerInfo* root, RelOptInfo* baserel, Expr* expr); extern void deparseSelectStmtForRel (StringInfo buf, PlannerInfo* root, RelOptInfo* rel, List* tlist, List* remote_conds, List* pathkeys, bool has_final_sort, bool has_limit, bool is_subquery, List** retrieved_attrs, List** params_list); extern List* build_tlist_to_deparse (RelOptInfo* foreignrel); @@ -26,7 +31,8 @@ extern List* serializePlanData (DB2FdwState* fdw_state); /** local prototypes */ ForeignScan* db2GetForeignPlan (PlannerInfo* root, RelOptInfo* foreignrel, Oid foreigntableid, ForeignPath* best_path, List* tlist, List* scan_clauses , Plan* outer_plan); -static void getUsedColumns (Expr* expr, DB2Table* db2Table, int foreignrelid); +static void getUsedColumns (Expr* expr, RelOptInfo* foreignrel); +static void addResult (DB2ResultColumn** resultList, DB2Column* db2Column); /* postgresGetForeignPlan * Create ForeignScan plan node which implements selected best path @@ -65,14 +71,14 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo db2Debug3(" size of columnlist: %d", list_length(foreignrel->reltarget->exprs)); foreach (cell, foreignrel->reltarget->exprs) { db2Debug3(" examine column"); - getUsedColumns ((Expr*) lfirst (cell), fpinfo->db2Table, foreignrel->relid); + getUsedColumns ((Expr*) lfirst (cell), foreignrel); } /* examine each condition for Var nodes */ db2Debug3(" size of conditions: %d", list_length(foreignrel->baserestrictinfo)); foreach (cell, foreignrel->baserestrictinfo) { db2Debug3(" examine condition"); - getUsedColumns ((Expr*) lfirst (cell), fpinfo->db2Table, foreignrel->relid); + getUsedColumns ((Expr*) lfirst (cell), foreignrel); } /* In a base-relation scan, we must apply the given scan_clauses. @@ -127,6 +133,16 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo * Build the list of columns to be fetched from the foreign server. */ fdw_scan_tlist = build_tlist_to_deparse(foreignrel); + if (list_length(fdw_scan_tlist) > 0) { + ListCell* cell = NULL; + + /* examine each condition for Tlist nodes */ + db2Debug3(" size of tlist: %d", list_length(fdw_scan_tlist)); + foreach (cell, fdw_scan_tlist) { + db2Debug3(" examine tlist"); + getUsedColumns ((Expr*) lfirst (cell), foreignrel); + } + } /* Ensure that the outer plan produces a tuple whose descriptor matches our scan tuple slot. Also, remove the local conditions * from outer plan's quals, lest they be evaluated twice, once by the local plan and once by the scan. @@ -188,19 +204,17 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo /** getUsedColumns * Set "used=true" in db2Table for all columns used in the expression. */ -static void getUsedColumns (Expr* expr, DB2Table* db2Table, int foreignrelid) { - ListCell* cell; - Var* variable; - int index; +static void getUsedColumns (Expr* expr, RelOptInfo* foreignrel) { + ListCell* cell = NULL; db2Debug1("> getUsedColumns"); if (expr != NULL) { switch (expr->type) { case T_RestrictInfo: - getUsedColumns (((RestrictInfo*) expr)->clause, db2Table, foreignrelid); + getUsedColumns (((RestrictInfo*) expr)->clause, foreignrel); break; case T_TargetEntry: - getUsedColumns (((TargetEntry*) expr)->expr, db2Table, foreignrelid); + getUsedColumns (((TargetEntry*) expr)->expr, foreignrel); break; case T_Const: case T_Param: @@ -209,177 +223,187 @@ static void getUsedColumns (Expr* expr, DB2Table* db2Table, int foreignrelid) { case T_CurrentOfExpr: case T_NextValueExpr: break; - case T_Var: + case T_Var: { + DB2FdwState* fpinfo = (DB2FdwState*) foreignrel->fdw_private; + Var* variable = NULL; + int index = 0; + variable = (Var*) expr; /* ignore system columns */ if (variable->varattno < 0) break; /* if this is a wholerow reference, we need all columns */ if (variable->varattno == 0) { - for (index = 0; index < db2Table->ncols; ++index) { - if (db2Table->cols[index]->pgname) { - db2Table->cols[index]->used = 1; + for (index = 0; index < fpinfo->db2Table->ncols; ++index) { + if (fpinfo->db2Table->cols[index]->pgname) { + addResult(&fpinfo->resultList,fpinfo->db2Table->cols[index]); +// fpinfo->db2Table->cols[index]->used = 1; } } break; } /* get db2Table column index corresponding to this column (-1 if none) */ - index = db2Table->ncols - 1; - while (index >= 0 && db2Table->cols[index]->pgattnum != variable->varattno) { + index = fpinfo->db2Table->ncols - 1; + db2Debug2(" variable->varattno: %d", variable->varattno); + while (index >= 0 && fpinfo->db2Table->cols[index]->pgattnum != variable->varattno) { --index; } if (index == -1) { - ereport (WARNING, (errcode (ERRCODE_WARNING),errmsg ("column number %d of foreign table \"%s\" does not exist in foreign DB2 table, will be replaced by NULL", variable->varattno, db2Table->pgname))); + ereport (WARNING, (errcode (ERRCODE_WARNING),errmsg ("column number %d of foreign table \"%s\" does not exist in foreign DB2 table, will be replaced by NULL", variable->varattno, fpinfo->db2Table->pgname))); } else { - db2Table->cols[index]->used = 1; + addResult(&fpinfo->resultList,fpinfo->db2Table->cols[index]); +// fpinfo->db2Table->cols[index]->used = 1; } + } break; - case T_Aggref: - foreach (cell, ((Aggref*) expr)->args) { - getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + case T_Aggref: { + Aggref* aggref = (Aggref*) expr; + foreach (cell, aggref->args) { + getUsedColumns ((Expr*) lfirst (cell), foreignrel); } - foreach (cell, ((Aggref*) expr)->aggorder) { - getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + foreach (cell, aggref->aggorder) { + getUsedColumns ((Expr*) lfirst (cell), foreignrel); } - foreach (cell, ((Aggref*) expr)->aggdistinct) { - getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + foreach (cell, aggref->aggdistinct) { + getUsedColumns ((Expr*) lfirst (cell), foreignrel); } + } break; case T_WindowFunc: foreach (cell, ((WindowFunc*) expr)->args) { - getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + getUsedColumns ((Expr*) lfirst (cell), foreignrel); } break; case T_SubscriptingRef: { SubscriptingRef* ref = (SubscriptingRef*) expr; foreach(cell, ref->refupperindexpr) { - getUsedColumns((Expr*)lfirst(cell), db2Table, foreignrelid); + getUsedColumns((Expr*)lfirst(cell), foreignrel); } foreach(cell, ref->reflowerindexpr) { - getUsedColumns((Expr*)lfirst(cell), db2Table, foreignrelid); + getUsedColumns((Expr*)lfirst(cell), foreignrel); } - getUsedColumns(ref->refexpr, db2Table, foreignrelid); - getUsedColumns(ref->refassgnexpr, db2Table, foreignrelid); + getUsedColumns(ref->refexpr, foreignrel); + getUsedColumns(ref->refassgnexpr, foreignrel); } break; case T_FuncExpr: foreach (cell, ((FuncExpr*) expr)->args) { - getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + getUsedColumns ((Expr*) lfirst (cell), foreignrel); } break; case T_OpExpr: foreach (cell, ((OpExpr*) expr)->args) { - getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + getUsedColumns ((Expr*) lfirst (cell), foreignrel); } break; case T_DistinctExpr: foreach (cell, ((DistinctExpr*) expr)->args) { - getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + getUsedColumns ((Expr*) lfirst (cell), foreignrel); } break; case T_NullIfExpr: foreach (cell, ((NullIfExpr*) expr)->args) { - getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + getUsedColumns ((Expr*) lfirst (cell), foreignrel); } break; case T_ScalarArrayOpExpr: foreach (cell, ((ScalarArrayOpExpr*) expr)->args) { - getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + getUsedColumns ((Expr*) lfirst (cell), foreignrel); } break; case T_BoolExpr: foreach (cell, ((BoolExpr*) expr)->args) { - getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + getUsedColumns ((Expr*) lfirst (cell), foreignrel); } break; case T_SubPlan: foreach (cell, ((SubPlan*) expr)->args) { - getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + getUsedColumns ((Expr*) lfirst (cell), foreignrel); } break; case T_AlternativeSubPlan: /* examine only first alternative */ - getUsedColumns ((Expr*) linitial (((AlternativeSubPlan*) expr)->subplans), db2Table, foreignrelid); + getUsedColumns ((Expr*) linitial (((AlternativeSubPlan*) expr)->subplans), foreignrel); break; case T_NamedArgExpr: - getUsedColumns (((NamedArgExpr*) expr)->arg, db2Table, foreignrelid); + getUsedColumns (((NamedArgExpr*) expr)->arg, foreignrel); break; case T_FieldSelect: - getUsedColumns (((FieldSelect*) expr)->arg, db2Table, foreignrelid); + getUsedColumns (((FieldSelect*) expr)->arg, foreignrel); break; case T_RelabelType: - getUsedColumns (((RelabelType*) expr)->arg, db2Table, foreignrelid); + getUsedColumns (((RelabelType*) expr)->arg, foreignrel); break; case T_CoerceViaIO: - getUsedColumns (((CoerceViaIO*) expr)->arg, db2Table, foreignrelid); + getUsedColumns (((CoerceViaIO*) expr)->arg, foreignrel); break; case T_ArrayCoerceExpr: - getUsedColumns (((ArrayCoerceExpr*) expr)->arg, db2Table, foreignrelid); + getUsedColumns (((ArrayCoerceExpr*) expr)->arg, foreignrel); break; case T_ConvertRowtypeExpr: - getUsedColumns (((ConvertRowtypeExpr*) expr)->arg, db2Table, foreignrelid); + getUsedColumns (((ConvertRowtypeExpr*) expr)->arg, foreignrel); break; case T_CollateExpr: - getUsedColumns (((CollateExpr*) expr)->arg, db2Table, foreignrelid); + getUsedColumns (((CollateExpr*) expr)->arg, foreignrel); break; case T_CaseExpr: foreach (cell, ((CaseExpr*) expr)->args) { - getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + getUsedColumns ((Expr*) lfirst (cell), foreignrel); } - getUsedColumns (((CaseExpr*) expr)->arg, db2Table, foreignrelid); - getUsedColumns (((CaseExpr*) expr)->defresult, db2Table, foreignrelid); + getUsedColumns (((CaseExpr*) expr)->arg, foreignrel); + getUsedColumns (((CaseExpr*) expr)->defresult, foreignrel); break; case T_CaseWhen: - getUsedColumns (((CaseWhen*) expr)->expr, db2Table, foreignrelid); - getUsedColumns (((CaseWhen*) expr)->result, db2Table, foreignrelid); + getUsedColumns (((CaseWhen*) expr)->expr, foreignrel); + getUsedColumns (((CaseWhen*) expr)->result, foreignrel); break; case T_ArrayExpr: foreach (cell, ((ArrayExpr*) expr)->elements) { - getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + getUsedColumns ((Expr*) lfirst (cell), foreignrel); } break; case T_RowExpr: foreach (cell, ((RowExpr*) expr)->args) { - getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + getUsedColumns ((Expr*) lfirst (cell), foreignrel); } break; case T_RowCompareExpr: foreach (cell, ((RowCompareExpr*) expr)->largs) { - getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + getUsedColumns ((Expr*) lfirst (cell), foreignrel); } foreach (cell, ((RowCompareExpr*) expr)->rargs) { - getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + getUsedColumns ((Expr*) lfirst (cell), foreignrel); } break; case T_CoalesceExpr: foreach (cell, ((CoalesceExpr*) expr)->args) { - getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + getUsedColumns ((Expr*) lfirst (cell), foreignrel); } break; case T_MinMaxExpr: foreach (cell, ((MinMaxExpr*) expr)->args) { - getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + getUsedColumns ((Expr*) lfirst (cell), foreignrel); } break; case T_XmlExpr: foreach (cell, ((XmlExpr*) expr)->named_args) { - getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + getUsedColumns ((Expr*) lfirst (cell), foreignrel); } foreach (cell, ((XmlExpr*) expr)->args) { - getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + getUsedColumns ((Expr*) lfirst (cell), foreignrel); } break; case T_NullTest: - getUsedColumns (((NullTest*) expr)->arg, db2Table, foreignrelid); + getUsedColumns (((NullTest*) expr)->arg, foreignrel); break; case T_BooleanTest: - getUsedColumns (((BooleanTest*) expr)->arg, db2Table, foreignrelid); + getUsedColumns (((BooleanTest*) expr)->arg, foreignrel); break; case T_CoerceToDomain: - getUsedColumns (((CoerceToDomain*) expr)->arg, db2Table, foreignrelid); + getUsedColumns (((CoerceToDomain*) expr)->arg, foreignrel); break; case T_PlaceHolderVar: - getUsedColumns (((PlaceHolderVar*) expr)->phexpr, db2Table, foreignrelid); + getUsedColumns (((PlaceHolderVar*) expr)->phexpr, foreignrel); break; case T_SQLValueFunction: //nop @@ -397,3 +421,32 @@ static void getUsedColumns (Expr* expr, DB2Table* db2Table, int foreignrelid) { } db2Debug1("< getUsedColumns"); } + +static void addResult(DB2ResultColumn** resultList, DB2Column* column) { + DB2ResultColumn* resCol = NULL; + db2Debug1("> %s::addResult",__FILE__); + resCol = db2alloc("resultColumn",sizeof(DB2ResultColumn)); + resCol->next = *resultList; + resCol->colName = db2strdup(column->colName); + resCol->colType = column->colType; + resCol->colSize = column->colSize; + resCol->colScale = column->colScale; + resCol->colNulls = column->colNulls; + resCol->colChars = column->colChars; + resCol->colBytes = column->colBytes; + resCol->colPrimKeyPart = column->colPrimKeyPart; + resCol->colCodepage = column->colCodepage; + resCol->pgname = db2strdup(column->pgname); + resCol->pgattnum = column->pgattnum; + resCol->pgtype = column->pgtype; + resCol->pgtypmod = column->pgtypmod; + resCol->pkey = column->pkey; + resCol->val = db2strdup(column->val); + resCol->val_size = column->val_size; + resCol->val_len = column->val_len; + resCol->val_null = column->val_null; + resCol->varno = column->varno; + resCol->noencerr = column->noencerr; + *resultList = resCol; + db2Debug1("< %s::addResult",__FILE__); +} \ No newline at end of file diff --git a/source/db2IterateForeignScan.c b/source/db2IterateForeignScan.c index c9235eb..a7b63be 100644 --- a/source/db2IterateForeignScan.c +++ b/source/db2IterateForeignScan.c @@ -11,14 +11,14 @@ /** external prototypes */ extern int db2IsStatementOpen (DB2Session* session); -extern void db2PrepareQuery (DB2Session* session, const char *query, DB2Table* db2Table, unsigned long prefetch, int fetchsize); +extern void db2PrepareQuery (DB2Session* session, const char *query, DB2ResultColumn* resultList, unsigned long prefetch, int fetchsize); extern int db2ExecuteQuery (DB2Session* session, const DB2Table* db2Table, ParamDesc* paramList); extern int db2FetchNext (DB2Session* session); extern void db2CloseStatement (DB2Session* session); extern void db2Debug1 (const char* message, ...); extern void db2Debug2 (const char* message, ...); extern void db2Debug3 (const char* message, ...); -extern void convertTuple (DB2FdwState* fdw_state, Datum* values, bool* nulls, bool trunc_lob) ; +extern void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls, bool trunc_lob) ; extern char* deparseDate (Datum datum); extern char* deparseTimestamp (Datum datum, bool hasTimezone); @@ -50,7 +50,7 @@ TupleTableSlot* db2IterateForeignScan (ForeignScanState* node) { char* paramInfo = setSelectParameters (fdw_state->paramList, econtext); /* execute the DB2 statement and fetch the first row */ db2Debug3(" execute query in foreign table scan '%s'", paramInfo); - db2PrepareQuery (fdw_state->session, fdw_state->query, fdw_state->db2Table, fdw_state->prefetch, fdw_state->fetch_size); + db2PrepareQuery (fdw_state->session, fdw_state->query, fdw_state->resultList, fdw_state->prefetch, fdw_state->fetch_size); have_result = db2ExecuteQuery (fdw_state->session, fdw_state->db2Table, fdw_state->paramList); have_result = db2FetchNext (fdw_state->session); } @@ -60,7 +60,8 @@ TupleTableSlot* db2IterateForeignScan (ForeignScanState* node) { /* increase row count */ ++fdw_state->rowcount; /* convert result to arrays of values and null indicators */ - convertTuple (fdw_state, slot->tts_values, slot->tts_isnull, false); + db2Debug2(" slot->tts_tupleDescriptor->natts: %d",slot->tts_tupleDescriptor->natts); + convertTuple (fdw_state, slot->tts_tupleDescriptor->natts, slot->tts_values, slot->tts_isnull, false); /* store the virtual tuple */ ExecStoreVirtualTuple (slot); } else { @@ -107,10 +108,12 @@ char* setSelectParameters (ParamDesc* paramList, ExprContext* econtext) { datum = TimestampGetDatum (tstamp); is_null = false; } else { - /** Evaluate the expression. - * This code path cannot be reached in 9.1 - */ - datum = ExecEvalExpr ((ExprState *) (param->node), econtext, &is_null); + if (param->node) { + /* Evaluate the expression. This code path cannot be reached in 9.1 */ + datum = ExecEvalExpr ((ExprState *) (param->node), econtext, &is_null); + } else { + is_null = true; + } } if (is_null) { diff --git a/source/db2PlanForeignModify.c b/source/db2PlanForeignModify.c index f6e8d22..a27f69d 100644 --- a/source/db2PlanForeignModify.c +++ b/source/db2PlanForeignModify.c @@ -380,7 +380,11 @@ static DB2FdwState* copyPlanData (DB2FdwState* orig) { void addParam (ParamDesc **paramList, Oid pgtype, short colType, int colnum, int txts) { ParamDesc *param; - db2Debug1("> addParam"); + db2Debug1("> addParam"); + db2Debug2(" pgtype: %d",pgtype); + db2Debug2(" colType: %d",colType); + db2Debug2(" colnum: %d",colnum); + db2Debug2(" txts: %d",txts); param = db2alloc("paramList->next",sizeof (ParamDesc)); param->type = pgtype; switch (c2dbType(colType)) { @@ -402,14 +406,18 @@ void addParam (ParamDesc **paramList, Oid pgtype, short colType, int colnum, int default: param->bindType = BIND_STRING; } + db2Debug2(" param->bindType: '%d'",param->bindType); param->value = NULL; + db2Debug2(" param->value: %x",param->value); param->node = NULL; + db2Debug2(" param->node: %x",param->node); param->colnum = colnum; + db2Debug2(" param->colnum: %d",param->colnum); param->txts = txts; - db2Debug2(" param->colnum: '%d'",param->colnum); + db2Debug2(" param->txts: %d",param->txts); param->next = *paramList; *paramList = param; - db2Debug1("> addParam"); + db2Debug1("< addParam"); } #endif /* WRITE_API */ diff --git a/source/db2PrepareQuery.c b/source/db2PrepareQuery.c index 6a8fe0a..980bdbb 100644 --- a/source/db2PrepareQuery.c +++ b/source/db2PrepareQuery.c @@ -3,6 +3,7 @@ #include #include "db2_fdw.h" #include "ParamDesc.h" +#include "DB2ResultColumn.h" #define SQL_VALUE_PTR_ULEN(v) ((SQLPOINTER)(uintptr_t)(SQLULEN)(v)) @@ -24,7 +25,7 @@ extern char* param2name (SQLSMALLINT fparamType); extern void* db2alloc (const char* type, size_t size); /** internal prototypes */ -void db2PrepareQuery (DB2Session* session, const char *query, DB2Table* db2Table, unsigned long prefetch, int fetchsize); +void db2PrepareQuery (DB2Session* session, const char *query, DB2ResultColumn* resultList, unsigned long prefetch, int fetchsize); /** db2PrepareQuery * Prepares an SQL statement for execution. @@ -34,12 +35,13 @@ void db2PrepareQuery (DB2Session* session, const char *query * - For DML statements, allocates LOB locators for the RETURNING clause in db2Table. * - Set the prefetch options. */ -void db2PrepareQuery (DB2Session* session, const char *query, DB2Table* db2Table, unsigned long prefetch, int fetchsize) { - int i = 0; - int col_pos = 0; - int is_select = 0; - int for_update = 0; - SQLRETURN rc = 0; +void db2PrepareQuery (DB2Session* session, const char *query, DB2ResultColumn* resultList, unsigned long prefetch, int fetchsize) { + int i = 0; + int col_pos = 0; + int is_select = 0; + int for_update = 0; + SQLRETURN rc = 0; + DB2ResultColumn* res = NULL; #ifdef FIXED_FETCH_SIZE // Until the proper handling of multiple rows results on a single query are added the fetch size must be 1 @@ -115,68 +117,98 @@ void db2PrepareQuery (DB2Session* session, const char *query, DB2Table* db2Table db2Error_d(FDW_UNABLE_TO_CREATE_EXECUTION, "error executing query: SQLPrepare failed to prepare remote query", db2Message); } - /* loop through table columns */ - col_pos = 0; - for (i = 0; i < db2Table->ncols; ++i) { - if (db2Table->cols[i]->used) { - SQLSMALLINT fparamType = c2param((SQLSMALLINT)db2Table->cols[i]->colType); - /* - * Unfortunately DB2 handles DML statements with a RETURNING clause - * quite different from SELECT statements. In the latter, the result - * columns are "defined", i.e. bound to some storage space. - * This definition is only necessary once, even if the query is executed - * multiple times, so we do this here. - * RETURNING clause are handled in db2ExecuteQuery, here we only - * allocate locators for LOB columns in RETURNING clauses. - */ - /* figure out in which format we want the results */ - if (db2Table->cols[i]->pgtype == UUIDOID) { - fparamType = SQL_C_CHAR; - } - db2Debug2(" db2Table->cols[%d]->colName : '%s' ",i,db2Table->cols[i]->colName); - db2Debug2(" db2Table->cols[%d]->colSize : '%ld'",i,db2Table->cols[i]->colSize); - db2Debug2(" db2Table->cols[%d]->colScale : '%d' ",i,db2Table->cols[i]->colScale); - db2Debug2(" db2Table->cols[%d]->colNulls : '%d' ",i,db2Table->cols[i]->colNulls); - db2Debug2(" db2Table->cols[%d]->colChars : '%ld'",i,db2Table->cols[i]->colChars); - db2Debug2(" db2Table->cols[%d]->colBytes : '%ld'",i,db2Table->cols[i]->colBytes); - db2Debug2(" db2Table->cols[%d]->colPrimKeyPart: '%d' ",i,db2Table->cols[i]->colPrimKeyPart); - db2Debug2(" db2Table->cols[%d]->colCodepage : '%d' ",i,db2Table->cols[i]->colCodepage); - db2Debug2(" db2Table->cols[%d]->val : '%x'" ,i,db2Table->cols[i]->val); - db2Debug2(" db2Table->cols[%d]->val_size : '%ld'",i,db2Table->cols[i]->val_size); - db2Debug2(" db2Table->cols[%d]->val_len : '%d' ",i,db2Table->cols[i]->val_len); - db2Debug2(" db2Table->cols[%d]->val_null : '%d' ",i,db2Table->cols[i]->val_null); - db2Debug2(" fparamType: %d (%s)",fparamType,param2name(fparamType)); - ++col_pos; - db2Debug2(" SQLBindCol(%d,%d,%d(%s),%x,%ld,%x)",session->stmtp->hsql,col_pos, fparamType, param2name(fparamType), db2Table->cols[i]->val, db2Table->cols[i]->val_size, &db2Table->cols[i]->val_null); - rc = SQLBindCol (session->stmtp->hsql,col_pos, fparamType, db2Table->cols[i]->val, db2Table->cols[i]->val_size, &db2Table->cols[i]->val_null); - rc = db2CheckErr(rc, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); - if (rc != SQL_SUCCESS) { - db2Error_d(FDW_UNABLE_TO_CREATE_EXECUTION, "error executing query: SQLBindCol failed to define result value", db2Message); - } + /* loop through expected result columns */ + for (res = resultList; res; res = res->next){ + SQLSMALLINT fparamType = c2param((SQLSMALLINT)res->colType); + /* Unfortunately DB2 handles DML statements with a RETURNING clause quite different from SELECT statements. + * In the latter, the result columns are "defined", i.e. bound to some storage space. + * This definition is only necessary once, even if the query is executed multiple times, so we do this here. + * RETURNING clause are handled in db2ExecuteQuery, here we only allocate locators for LOB columns in RETURNING clauses. + */ + /* figure out in which format we want the results */ + if (res->pgtype == UUIDOID) { + fparamType = SQL_C_CHAR; + } + db2Debug2(" res->colName : %s" ,res->colName); + db2Debug2(" res->colSize : %ld",res->colSize); + db2Debug2(" res->colScale : %d" ,res->colScale); + db2Debug2(" res->colNulls : %d" ,res->colNulls); + db2Debug2(" res->colChars : %ld",res->colChars); + db2Debug2(" res->colBytes : %ld",res->colBytes); + db2Debug2(" res->colPrimKeyPart: %d" ,res->colPrimKeyPart); + db2Debug2(" res->colCodepage : %d" ,res->colCodepage); + db2Debug2(" res->val : %x" ,res->val); + db2Debug2(" res->val_size : %ld",res->val_size); + db2Debug2(" res->val_len : %d" ,res->val_len); + db2Debug2(" res->val_null : %d" ,res->val_null); + db2Debug2(" res->resnum. : %d" ,res->resnum); + db2Debug2(" fparamType: %d (%s)",fparamType,param2name(fparamType)); + db2Debug2(" SQLBindCol(%d,%d,%d(%s),%x,%ld,%x)",session->stmtp->hsql,res->resnum, fparamType, param2name(fparamType), res->val, res->val_size, &res->val_null); + rc = SQLBindCol (session->stmtp->hsql,res->resnum, fparamType, res->val, res->val_size, &res->val_null); + rc = db2CheckErr(rc, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); + if (rc != SQL_SUCCESS) { + db2Error_d(FDW_UNABLE_TO_CREATE_EXECUTION, "error executing query: SQLBindCol failed to define result value", db2Message); } + col_pos++; } + +// col_pos = 0; +// for (i = 0; i < db2Table->ncols; ++i) { +// if (db2Table->cols[i]->used) { +// SQLSMALLINT fparamType = c2param((SQLSMALLINT)db2Table->cols[i]->colType); +// /* Unfortunately DB2 handles DML statements with a RETURNING clause quite different from SELECT statements. +// * In the latter, the result columns are "defined", i.e. bound to some storage space. +// * This definition is only necessary once, even if the query is executed multiple times, so we do this here. +// * RETURNING clause are handled in db2ExecuteQuery, here we only allocate locators for LOB columns in RETURNING clauses. +// */ +// /* figure out in which format we want the results */ +// if (db2Table->cols[i]->pgtype == UUIDOID) { +// fparamType = SQL_C_CHAR; +// } +// db2Debug2(" db2Table->cols[%d]->colName : '%s' ",i,db2Table->cols[i]->colName); +// db2Debug2(" db2Table->cols[%d]->colSize : '%ld'",i,db2Table->cols[i]->colSize); +// db2Debug2(" db2Table->cols[%d]->colScale : '%d' ",i,db2Table->cols[i]->colScale); +// db2Debug2(" db2Table->cols[%d]->colNulls : '%d' ",i,db2Table->cols[i]->colNulls); +// db2Debug2(" db2Table->cols[%d]->colChars : '%ld'",i,db2Table->cols[i]->colChars); +// db2Debug2(" db2Table->cols[%d]->colBytes : '%ld'",i,db2Table->cols[i]->colBytes); +// db2Debug2(" db2Table->cols[%d]->colPrimKeyPart: '%d' ",i,db2Table->cols[i]->colPrimKeyPart); +// db2Debug2(" db2Table->cols[%d]->colCodepage : '%d' ",i,db2Table->cols[i]->colCodepage); +// db2Debug2(" db2Table->cols[%d]->val : '%x'" ,i,db2Table->cols[i]->val); +// db2Debug2(" db2Table->cols[%d]->val_size : '%ld'",i,db2Table->cols[i]->val_size); +// db2Debug2(" db2Table->cols[%d]->val_len : '%d' ",i,db2Table->cols[i]->val_len); +// db2Debug2(" db2Table->cols[%d]->val_null : '%d' ",i,db2Table->cols[i]->val_null); +// db2Debug2(" fparamType: %d (%s)",fparamType,param2name(fparamType)); +// ++col_pos; +// db2Debug2(" SQLBindCol(%d,%d,%d(%s),%x,%ld,%x)",session->stmtp->hsql,col_pos, fparamType, param2name(fparamType), db2Table->cols[i]->val, db2Table->cols[i]->val_size, &db2Table->cols[i]->val_null); +// rc = SQLBindCol (session->stmtp->hsql,col_pos, fparamType, db2Table->cols[i]->val, db2Table->cols[i]->val_size, &db2Table->cols[i]->val_null); +// rc = db2CheckErr(rc, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); +// if (rc != SQL_SUCCESS) { +// db2Error_d(FDW_UNABLE_TO_CREATE_EXECUTION, "error executing query: SQLBindCol failed to define result value", db2Message); +// } +// } +// } db2Debug2(" is_select: %s",is_select ? "true" : "false"); db2Debug2(" col_pos: %d",col_pos); if (is_select && col_pos == 0) { - if (db2Table->rncols > 0) { - int idx = 0; - // count the number of comma between SELECT and FROM to understand how many result columns we need. - for (idx=0; idx < db2Table->rncols - col_pos; idx++) { - SQLCHAR* dummy_buffer = (SQLCHAR*)db2alloc("dummy_buffer",256); - SQLLEN* dummy_null = (SQLLEN*)db2alloc("dummy_null",sizeof(SQLLEN)); - rc = SQLBindCol(session->stmtp->hsql - , idx+1 - , SQL_C_CHAR - , dummy_buffer - , sizeof(dummy_buffer) - , dummy_null - ); - rc = db2CheckErr(rc, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); - if (rc != SQL_SUCCESS) { - db2Error_d ( FDW_UNABLE_TO_CREATE_EXECUTION, "error executing query: SQLBindCol failed to define result value", db2Message); - } - } - } else { +// if (db2Table->rncols > 0) { +// int idx = 0; +// // count the number of comma between SELECT and FROM to understand how many result columns we need. +// for (idx=0; idx < db2Table->rncols - col_pos; idx++) { +// SQLCHAR* dummy_buffer = (SQLCHAR*)db2alloc("dummy_buffer",256); +// SQLLEN* dummy_null = (SQLLEN*)db2alloc("dummy_null",sizeof(SQLLEN)); +// rc = SQLBindCol(session->stmtp->hsql +// , idx+1 +// , SQL_C_CHAR +// , dummy_buffer +// , sizeof(dummy_buffer) +// , dummy_null +// ); +// rc = db2CheckErr(rc, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); +// if (rc != SQL_SUCCESS) { +// db2Error_d ( FDW_UNABLE_TO_CREATE_EXECUTION, "error executing query: SQLBindCol failed to define result value", db2Message); +// } +// } +// } else { /* No columns selected (i.e., SELECT '1' FROM or COUNT(*)). * Use persistent buffers from statement handle to avoid stack deallocation issues. * This fixes the segfault when using aggregate functions without WHERE clause. @@ -185,7 +217,7 @@ void db2PrepareQuery (DB2Session* session, const char *query, DB2Table* db2Table rc = db2CheckErr(rc, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); if (rc != SQL_SUCCESS) { db2Error_d ( FDW_UNABLE_TO_CREATE_EXECUTION, "error executing query: SQLBindCol failed to define result value", db2Message); - } +// } } } diff --git a/source/db2_de_serialize.c b/source/db2_de_serialize.c index e9270a0..256fe68 100644 --- a/source/db2_de_serialize.c +++ b/source/db2_de_serialize.c @@ -18,6 +18,7 @@ extern void db2Debug1 (const char* message, ...); extern void db2Debug2 (const char* message, ...); extern void db2Debug4 (const char* message, ...); +extern void db2Debug5 (const char* message, ...); extern void* db2alloc (const char* type, size_t size); extern char* c2name (short fcType); @@ -42,97 +43,87 @@ DB2FdwState* deserializePlanData (List* list) { db2Debug1("> deserializePlanData"); /* session will be set upon connect */ - state->session = NULL; + state->session = NULL; /* these fields are not needed during execution */ - state->startup_cost = 0; - state->total_cost = 0; + state->startup_cost = 0; + state->total_cost = 0; /* these are not serialized */ - state->rowcount = 0; - state->columnindex = 0; - state->params = NULL; - state->temp_cxt = NULL; - state->order_clause = NULL; + state->rowcount = 0; + state->columnindex = 0; + state->params = NULL; + state->temp_cxt = NULL; + state->order_clause = NULL; - state->retrieved_attr = (List *) list_nth(list, idx++); + state->retrieved_attr = (List *) list_nth(list, idx++); /* dbserver */ - state->dbserver = deserializeString(list_nth(list, idx++)); + state->dbserver = deserializeString(list_nth(list, idx++)); /* user */ - state->user = deserializeString(list_nth(list, idx++)); + state->user = deserializeString(list_nth(list, idx++)); /* password */ - state->password = deserializeString(list_nth(list, idx++)); + state->password = deserializeString(list_nth(list, idx++)); /* jwt-token */ - state->jwt_token = deserializeString(list_nth(list, idx++)); + state->jwt_token = deserializeString(list_nth(list, idx++)); /* nls_lang */ - state->nls_lang = deserializeString(list_nth(list, idx++)); + state->nls_lang = deserializeString(list_nth(list, idx++)); /* query */ - state->query = deserializeString(list_nth(list, idx++)); + state->query = deserializeString(list_nth(list, idx++)); /* DB2 prefetch count */ - state->prefetch = (unsigned long) DatumGetInt32 (((Const*)list_nth(list, idx++))->constvalue); + state->prefetch = (unsigned long) DatumGetInt32 (((Const*)list_nth(list, idx++))->constvalue); /* DB2 fetch_size */ - state->fetch_size = (unsigned long) DatumGetInt32 (((Const*)list_nth(list, idx++))->constvalue); + state->fetch_size = (unsigned long) DatumGetInt32 (((Const*)list_nth(list, idx++))->constvalue); /* relation_name */ - state->relation_name = deserializeString(list_nth(list, idx++)); - db2Debug2(" state->relation_name: '%s'",state->relation_name); + state->relation_name = deserializeString(list_nth(list, idx++)); /* table data */ - state->db2Table = (DB2Table*) db2alloc ("state->db2Table", sizeof (struct db2Table)); - state->db2Table->name = deserializeString(list_nth(list, idx++)); - db2Debug2(" state->db2Table->name: '%s'",state->db2Table->name); - state->db2Table->pgname = deserializeString(list_nth(list, idx++)); - db2Debug2(" state->db2Table->pgname: '%s'",state->db2Table->pgname); + state->db2Table = (DB2Table*) db2alloc ("state->db2Table", sizeof (struct db2Table)); + state->db2Table->name = deserializeString(list_nth(list, idx++)); + state->db2Table->pgname = deserializeString(list_nth(list, idx++)); state->db2Table->batchsz = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug2(" state->db2Table->batchsz: %d",state->db2Table->batchsz); - state->db2Table->ncols = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug2(" state->db2Table->ncols: %d",state->db2Table->ncols); - state->db2Table->rncols = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug2(" state->db2Table->rncols: %d",state->db2Table->rncols); + state->db2Table->ncols = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); + state->db2Table->rncols = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); state->db2Table->npgcols = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug2(" state->db2Table->npgcols: %d",state->db2Table->npgcols); - state->db2Table->cols = (DB2Column**) db2alloc ("state->db2Table->cols", sizeof (DB2Column*) * state->db2Table->ncols); + state->db2Table->cols = (DB2Column**) db2alloc ("state->db2Table->cols", sizeof (DB2Column*) * state->db2Table->ncols); /* loop columns */ for (i = 0; i < state->db2Table->ncols; ++i) { state->db2Table->cols[i] = (DB2Column *) db2alloc ("state->db2Table->cols[i]", sizeof (DB2Column)); state->db2Table->cols[i]->colName = deserializeString(list_nth(list, idx++)); - db2Debug2(" state->db2Table->cols[%d]->colName: '%s'",i,state->db2Table->cols[i]->colName); + db2Debug4(" deserialize col[%d].colName: %s" ,i, state->db2Table->cols[i]->colName); state->db2Table->cols[i]->colType = (short) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug2(" state->db2Table->cols[%d]->colType: %d (%s)",i,state->db2Table->cols[i]->colType,c2name(state->db2Table->cols[i]->colType)); + db2Debug4(" deserialize col[%d].colType: %d" ,i, state->db2Table->cols[i]->colType); state->db2Table->cols[i]->colSize = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug2(" state->db2Table->cols[%d]->colSize: %lld",i,state->db2Table->cols[i]->colSize); + db2Debug4(" deserialize col[%d].colSize: %d" ,i, state->db2Table->cols[i]->colSize); state->db2Table->cols[i]->colScale = (short) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug2(" state->db2Table->cols[%d]->colScale: %d",i,state->db2Table->cols[i]->colScale); + db2Debug4(" deserialize col[%d].colScale: %d" ,i, state->db2Table->cols[i]->colScale); state->db2Table->cols[i]->colNulls = (short) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug2(" state->db2Table->cols[%d]->colNulls: %d",i,state->db2Table->cols[i]->colNulls); + db2Debug4(" deserialize col[%d].colNulls: %d" ,i, state->db2Table->cols[i]->colNulls); state->db2Table->cols[i]->colChars = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug2(" state->db2Table->cols[%lld]->colChars: %lld",i,state->db2Table->cols[i]->colChars); + db2Debug4(" deserialize col[%d].colChars: %d" ,i, state->db2Table->cols[i]->colChars); state->db2Table->cols[i]->colBytes = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug2(" state->db2Table->cols[%lld]->colBytes: %lld",i,state->db2Table->cols[i]->colBytes); + db2Debug4(" deserialize col[%d].colBytes: %d" ,i, state->db2Table->cols[i]->colBytes); state->db2Table->cols[i]->colPrimKeyPart = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug2(" state->db2Table->cols[%lld]->colPrimKeyPart: %lld",i,state->db2Table->cols[i]->colPrimKeyPart); + db2Debug4(" deserialize col[%d].colPrimKeyPart: %d" ,i, state->db2Table->cols[i]->colPrimKeyPart); state->db2Table->cols[i]->colCodepage = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug2(" state->db2Table->cols[%lld]->colCodepaget: %lld",i,state->db2Table->cols[i]->colCodepage); + db2Debug4(" deserialize col[%d].colCodepage: %d" ,i, state->db2Table->cols[i]->colCodepage); state->db2Table->cols[i]->pgname = deserializeString(list_nth(list, idx++)); - db2Debug2(" state->db2Table->cols[%d]->pgname: '%s'",i,state->db2Table->cols[i]->pgname); + db2Debug4(" deserialize col[%d].pgname: %s" ,i, state->db2Table->cols[i]->pgname); state->db2Table->cols[i]->pgattnum = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug2(" state->db2Table->cols[%d]->pgattnum: %d",i,state->db2Table->cols[i]->pgattnum); + db2Debug4(" deserialize col[%d].pgattnum: %d" ,i, state->db2Table->cols[i]->pgattnum); state->db2Table->cols[i]->pgtype = DatumGetObjectId(((Const*)list_nth(list, idx++))->constvalue); - db2Debug2(" state->db2Table->cols[%d]->pgtype: %d",i,state->db2Table->cols[i]->pgtype); + db2Debug4(" deserialize col[%d].pgtype: %d" ,i, state->db2Table->cols[i]->pgtype); state->db2Table->cols[i]->pgtypmod = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug2(" state->db2Table->cols[%d]->pgtypmod: %d",i,state->db2Table->cols[i]->pgtypmod); + db2Debug4(" deserialize col[%d].pgtypmod: %d" ,i, state->db2Table->cols[i]->pgtypmod); state->db2Table->cols[i]->used = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug2(" state->db2Table->cols[%d]->used: %d",i,state->db2Table->cols[i]->used); + db2Debug4(" deserialize col[%d].used: %d" ,i, state->db2Table->cols[i]->used); state->db2Table->cols[i]->pkey = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug2(" state->db2Table->cols[%d]->pkey: %d",i,state->db2Table->cols[i]->pkey); + db2Debug4(" deserialize col[%d].pkey: %d" ,i, state->db2Table->cols[i]->pkey); state->db2Table->cols[i]->val_size = deserializeLong(list_nth(list, idx++)); - db2Debug2(" state->db2Table->cols[%d]->val_size: %ld",i,state->db2Table->cols[i]->val_size); + db2Debug4(" deserialize col[%d].val_size: %ld" ,i, state->db2Table->cols[i]->val_size); state->db2Table->cols[i]->noencerr = deserializeLong(list_nth(list, idx++)); - db2Debug2(" state->db2Table->cols[%d]->noencerr: %d",i,state->db2Table->cols[i]->noencerr); + db2Debug4(" deserialize col[%d].noencerr: %ld" ,i, state->db2Table->cols[i]->noencerr); /* allocate memory for the result value only when the column is used in query */ state->db2Table->cols[i]->val = (state->db2Table->cols[i]->used == 1) ? (char*) db2alloc ("state->db2Table->cols[i]->val", state->db2Table->cols[i]->val_size + 1) : NULL; - db2Debug2(" state->db2Table->cols[%d]->val: %x",i,state->db2Table->cols[i]->val); state->db2Table->cols[i]->val_len = 0; - db2Debug2(" state->db2Table->cols[%d]->val_len: %d",i,state->db2Table->cols[i]->val_len); state->db2Table->cols[i]->val_null = 1; - db2Debug2(" state->db2Table->cols[%d]->val_null: %d",i,state->db2Table->cols[i]->val_null); } /* length of parameter list */ @@ -143,17 +134,72 @@ DB2FdwState* deserializePlanData (List* list) { for (i = 0; i < len; ++i) { param = (ParamDesc*) db2alloc ("state->parmList->next", sizeof (ParamDesc)); param->type = DatumGetObjectId(((Const*)list_nth(list, idx++))->constvalue); + db2Debug4(" deserialize param[%d].type: %d" ,i, param->type); param->bindType = (db2BindType) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); + db2Debug4(" deserialize param[%d].bindType: %d" ,i, param->bindType); if (param->bindType == BIND_OUTPUT) param->value = (void *) 42; /* something != NULL */ else param->value = NULL; + db2Debug4(" deserialize param[%d].value: %x" ,i, param->value); param->node = NULL; + db2Debug4(" deserialize param[%d].node: %x" ,i, param->node); param->colnum = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); + db2Debug4(" deserialize param[%d].colnum: %d" ,i, param->colnum); param->txts = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); + db2Debug4(" deserialize param[%d].txts: %d" ,i, param->txts); param->next = state->paramList; state->paramList = param; } + + /* length of parameter list */ + len = (int) DatumGetInt32 (((Const*)list_nth(list, idx++))->constvalue); + /* parameter table entries */ + state->resultList = NULL; + for (i = 0; i < len; ++i) { + DB2ResultColumn* res = (DB2ResultColumn *) db2alloc ("state->resultList->next", sizeof (DB2ResultColumn)); + res->colName = deserializeString(list_nth(list, idx++)); + db2Debug4(" deserialize res[%d].colName: %s" ,i, res->colName); + res->colType = (short) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); + db2Debug4(" deserialize res[%d].colType: %d" ,i, res->colType); + res->colSize = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); + db2Debug4(" deserialize res[%d].colSize: %d" ,i, res->colSize); + res->colScale = (short) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); + db2Debug4(" deserialize res[%d].colScale: %d" ,i, res->colScale); + res->colNulls = (short) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); + db2Debug4(" deserialize res[%d].colNulls: %d" ,i, res->colNulls); + res->colChars = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); + db2Debug4(" deserialize res[%d].colChars: %d" ,i, res->colChars); + res->colBytes = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); + db2Debug4(" deserialize res[%d].colBytes: %d" ,i, res->colBytes); + res->colPrimKeyPart = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); + db2Debug4(" deserialize res[%d].colPrimKeyPart: %d" ,i, res->colPrimKeyPart); + res->colCodepage = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); + db2Debug4(" deserialize res[%d].colCodepage: %d" ,i, res->colCodepage); + res->pgname = deserializeString(list_nth(list, idx++)); + db2Debug4(" deserialize res[%d].pgname: %s" ,i, res->pgname); + res->pgattnum = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); + db2Debug4(" deserialize res[%d].pgattnum: %d" ,i, res->pgattnum); + res->pgtype = DatumGetObjectId(((Const*)list_nth(list, idx++))->constvalue); + db2Debug4(" deserialize res[%d].pgtype: %d" ,i, res->pgtype); + res->pgtypmod = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); + db2Debug4(" deserialize res[%d].pgtypmod: %d" ,i, res->pgtypmod); + res->pkey = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); + db2Debug4(" deserialize res[%d].pkey: %d" ,i, res->pkey); + res->val_size = deserializeLong(list_nth(list, idx++)); + db2Debug4(" deserialize res[%d].val_size: %ld" ,i, res->val_size); + res->noencerr = deserializeLong(list_nth(list, idx++)); + db2Debug4(" deserialize res[%d].noencerr: %ld" ,i, res->noencerr); + res->resnum = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); + db2Debug4(" deserialize res[%d].resnum: %d" ,i, res->resnum); + res->val = (char*) db2alloc ("res->val", res->val_size + 1); + res->val_len = 0; + res->val_null = 1; + res->next = state->resultList; + state->resultList = res; + } + + db2Debug1("< deserializePlanData - returns: %x", state); return state; } @@ -187,10 +233,11 @@ static long deserializeLong (Const* constant) { * This List can be parsed by deserializePlanData. */ List* serializePlanData (DB2FdwState* fdwState) { - List* result = NIL; - int idxCol = 0; - int lenParam = 0; - ParamDesc* param = NULL; + List* result = NIL; + int idxCol = 0; + int lenParam = 0; + ParamDesc* param = NULL; + DB2ResultColumn* rcol = NULL; db2Debug1("> serializePlanData"); result = list_make1(fdwState->retrieved_attr); @@ -227,22 +274,39 @@ List* serializePlanData (DB2FdwState* fdwState) { /* column data */ for (idxCol = 0; idxCol < fdwState->db2Table->ncols; ++idxCol) { result = lappend (result, serializeString (fdwState->db2Table->cols[idxCol]->colName)); + db2Debug4(" serialize col[%d].colName: %s" ,idxCol, fdwState->db2Table->cols[idxCol]->colName); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->colType)); + db2Debug4(" serialize col[%d].colType: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colType); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->colSize)); + db2Debug4(" serialize col[%d].colSize: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colSize); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->colScale)); + db2Debug4(" serialize col[%d].colScale: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colScale); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->colNulls)); + db2Debug4(" serialize col[%d].colNulls: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colNulls); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->colChars)); + db2Debug4(" serialize col[%d].colChars: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colChars); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->colBytes)); + db2Debug4(" serialize col[%d].colBytes: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colBytes); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->colPrimKeyPart)); + db2Debug4(" serialize col[%d].colPrimKeyPart: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colPrimKeyPart); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->colCodepage)); + db2Debug4(" serialize col[%d].colCodepage: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colCodepage); result = lappend (result, serializeString (fdwState->db2Table->cols[idxCol]->pgname)); + db2Debug4(" serialize col[%d].pgname: %s" ,idxCol, fdwState->db2Table->cols[idxCol]->pgname); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->pgattnum)); + db2Debug4(" serialize col[%d].pgattnum: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->pgattnum); result = lappend (result, serializeOid (fdwState->db2Table->cols[idxCol]->pgtype)); + db2Debug4(" serialize col[%d].pgtype: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->pgtype); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->pgtypmod)); + db2Debug4(" serialize col[%d].pgtypmod: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->pgtypmod); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->used)); + db2Debug4(" serialize col[%d].used: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->used); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->pkey)); + db2Debug4(" serialize col[%d].pkey: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->pkey); result = lappend (result, serializeLong (fdwState->db2Table->cols[idxCol]->val_size)); + db2Debug4(" serialize col[%d].val_size: %ld" ,idxCol, fdwState->db2Table->cols[idxCol]->val_size); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->noencerr)); + db2Debug4(" serialize col[%d].val_size: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->noencerr); /* don't serialize val, val_len, val_null and varno */ } @@ -252,14 +316,66 @@ List* serializePlanData (DB2FdwState* fdwState) { } /* serialize length */ result = lappend (result, serializeInt (lenParam)); + db2Debug4(" serialize paramList.length: %d", lenParam); /* parameter list entries */ for (param = fdwState->paramList; param; param = param->next) { result = lappend (result, serializeOid (param->type)); + db2Debug4(" serialize param.type: %d" , param->type); result = lappend (result, serializeInt ((int) param->bindType)); + db2Debug4(" serialize param.bindType: %d" , param->bindType); result = lappend (result, serializeInt ((int) param->colnum)); + db2Debug4(" serialize param.colnum: %d" , param->colnum); result = lappend (result, serializeInt ((int) param->txts)); - /* don't serialize value and node */ + db2Debug4(" serialize param.txta: %d" , param->txts); } + + /* find length of result list */ + lenParam = 0; + for (rcol = fdwState->resultList; rcol; rcol = rcol->next) { + ++lenParam; + } + /* serialize length */ + result = lappend (result, serializeInt (lenParam)); + db2Debug4(" serialize resultList.length: %d", lenParam); + /* parameter list entries */ + for (rcol = fdwState->resultList; rcol; rcol = rcol->next) { + result = lappend (result, serializeString (rcol->colName)); + db2Debug4(" serialize res.colName: %s" , rcol->colName); + result = lappend (result, serializeInt (rcol->colType)); + db2Debug4(" serialize res.colType: %d" , rcol->colType); + result = lappend (result, serializeInt (rcol->colSize)); + db2Debug4(" serialize res.colSize: %d" , rcol->colSize); + result = lappend (result, serializeInt (rcol->colScale)); + db2Debug4(" serialize res.colScale: %d" , rcol->colScale); + result = lappend (result, serializeInt (rcol->colNulls)); + db2Debug4(" serialize res.colNulls: %d", rcol->colNulls); + result = lappend (result, serializeInt (rcol->colChars)); + db2Debug4(" serialize res.colChars: %d" , rcol->colChars); + result = lappend (result, serializeInt (rcol->colBytes)); + db2Debug4(" serialize res.colBytes: %d" , rcol->colBytes); + result = lappend (result, serializeInt (rcol->colPrimKeyPart)); + db2Debug4(" serialize res.colPrimKeyPart: %d", rcol->colPrimKeyPart); + result = lappend (result, serializeInt (rcol->colCodepage)); + db2Debug4(" serialize res.codepage: %d" , rcol->colCodepage); + result = lappend (result, serializeString (rcol->pgname)); + db2Debug4(" serialize res.pgname: %s" , rcol->pgname); + result = lappend (result, serializeInt (rcol->pgattnum)); + db2Debug4(" serialize res.pgattnum: %d" , rcol->pgattnum); + result = lappend (result, serializeOid (rcol->pgtype)); + db2Debug4(" serialize res.pgtype: %d" , rcol->pgtype); + result = lappend (result, serializeInt (rcol->pgtypmod)); + db2Debug4(" serialize res.pgtypmod: %d" , rcol->pgtypmod); + result = lappend (result, serializeInt (rcol->pkey)); + db2Debug4(" serialize res.pkey: %d" , rcol->pkey); + result = lappend (result, serializeLong (rcol->val_size)); + db2Debug4(" serialize res.val_size: %ld", rcol->val_size); + result = lappend (result, serializeInt (rcol->noencerr)); + db2Debug4(" serialize res.noencerr: %d" , rcol->noencerr); + result = lappend (result, serializeInt (lenParam)); // the last result is the first in the list + db2Debug4(" serialize res.resnum: %d" , lenParam); + lenParam--; + } + /* don't serialize params, startup_cost, total_cost, rowcount, columnindex, temp_cxt, order_clause and where_clause */ db2Debug1("< serializePlanData - returns: %x",result); return result; @@ -270,10 +386,10 @@ List* serializePlanData (DB2FdwState* fdwState) { */ static Const* serializeString (const char* s) { Const* result = NULL; - db2Debug1("> serializeString"); + db2Debug5("> serializeString"); result = (s == NULL) ? makeNullConst (TEXTOID, -1, InvalidOid) : makeConst (TEXTOID, -1, InvalidOid, -1, PointerGetDatum (cstring_to_text (s)), false, false); - db2Debug1("< serializeString - returns: %x",result); + db2Debug5("< serializeString - returns: %x",result); return result; } @@ -282,7 +398,7 @@ static Const* serializeString (const char* s) { */ static Const* serializeLong (long i) { Const* result = NULL; - db2Debug1("> serializeLong"); + db2Debug5("> serializeLong"); if (sizeof (long) <= 4) result = makeConst (INT4OID, -1, InvalidOid, 4, Int32GetDatum ((int32) i), false, true); else @@ -293,6 +409,6 @@ static Const* serializeLong (long i) { false #endif /* USE_FLOAT8_BYVAL */ ); - db2Debug1("< serializeLong - returns: %x",result); + db2Debug5("< serializeLong - returns: %x",result); return result; } diff --git a/source/db2_fdw_utils.c b/source/db2_fdw_utils.c index afe8930..2cc674a 100644 --- a/source/db2_fdw_utils.c +++ b/source/db2_fdw_utils.c @@ -56,7 +56,7 @@ extern void db2free (void* p); /** local prototypes */ char* guessNlsLang (char* nls_lang); void exitHook (int code, Datum arg); -void convertTuple (DB2FdwState* fdw_state, Datum* values, bool* nulls, bool trunc_lob) ; +void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls, bool trunc_lob) ; void reset_transmission_modes (int nestlevel); int set_transmission_modes (void); bool is_builtin (Oid objectId); @@ -194,14 +194,17 @@ void exitHook (int code, Datum arg) { * into arrays of values and null indicators. * If trunc_lob it true, truncate LOBs to WIDTH_THRESHOLD+1 bytes. */ -void convertTuple (DB2FdwState* fdw_state, Datum* values, bool* nulls, bool trunc_lob) { - char* tmp_value = NULL; - char* value = NULL; - long value_len = 0; - int j, - index = -1; +void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls, bool trunc_lob) { + char* tmp_value = NULL; + char* value = NULL; + long value_len = 0; + int j = 0; + int index = -1; // ErrorContextCallback errcb; Oid pgtype; + DB2Table* db2Table = fdw_state->db2Table; + DB2ResultColumn* res = NULL; + bool isSimpleSelect = false; db2Debug1("> %s::convertTuple",__FILE__); /* initialize error context callback, install it only during conversions */ @@ -209,37 +212,46 @@ void convertTuple (DB2FdwState* fdw_state, Datum* values, bool* nulls, bool trun // errcb.arg = (void *) fdw_state; /* assign result values */ - for (j = 0; j < fdw_state->db2Table->npgcols; ++j) { + isSimpleSelect = (natts == db2Table->npgcols); + db2Debug2(" isSimpleSelect: %s", isSimpleSelect ? "true": "false"); + for (j = 0; j < db2Table->npgcols; ++j) { short db2Type; - db2Debug2(" start processing column %d of %d",j + 1, fdw_state->db2Table->npgcols); + db2Debug2(" start processing column %d of %d",j + 1, db2Table->npgcols); db2Debug2(" index: %d",index); /* for dropped columns, insert a NULL */ - if ((index + 1 < fdw_state->db2Table->ncols) && (fdw_state->db2Table->cols[index + 1]->pgattnum > j + 1)) { - nulls[j] = true; + if ((index + 1 < db2Table->ncols) && (db2Table->cols[index+1]->pgattnum > j + 1)) { + nulls[j] = true; values[j] = PointerGetDatum (NULL); continue; } else { ++index; } db2Debug2(" index: %d",index); - /* - * Columns exceeding the length of the DB2 table will be NULL, - * as well as columns that are not used in the query. - * Geometry columns are NULL if the value is NULL, - * for all other types use the NULL indicator. + /* get the result for the column of the current index */ + for (res = fdw_state->resultList; res; res = res->next) { + if (isSimpleSelect) { + if (res->pgattnum == j+1) + break; + } else { + if (res->resnum == j+1) + break; + } + } + /* Columns exceeding the length of the DB2 table will be NULL, as well as columns that are not used in the query. + * Geometry columns are NULL if the value is NULL, for all other types use the NULL indicator. */ - if (index >= fdw_state->db2Table->ncols || fdw_state->db2Table->cols[index]->used == 0 || fdw_state->db2Table->cols[index]->val_null == -1) { - nulls[j] = true; + if (index >= db2Table->ncols || res == NULL || res->val_null == -1) { + nulls[j] = true; values[j] = PointerGetDatum (NULL); continue; } /* from here on, we can assume columns to be NOT NULL */ nulls[j] = false; - pgtype = fdw_state->db2Table->cols[index]->pgtype; + pgtype = res->pgtype; /* get the data and its length */ - switch(c2dbType(fdw_state->db2Table->cols[index]->colType)) { + switch(c2dbType(res->colType)) { case DB2_BLOB: case DB2_CLOB: { db2Debug3(" DB2_BLOB or DB2CLOB"); @@ -251,9 +263,9 @@ void convertTuple (DB2FdwState* fdw_state, Datum* values, bool* nulls, bool trun case DB2_LONGVARBINARY: { db2Debug3(" DB2_LONGBINARY datatypes"); /* for LONG and LONG RAW, the first 4 bytes contain the length */ - value_len = *((int32 *) fdw_state->db2Table->cols[index]->val); + value_len = *((int32*) res->val); /* the rest is the actual data */ - value = fdw_state->db2Table->cols[index]->val; + value = res->val; /* terminating zero byte (needed for LONGs) */ value[value_len] = '\0'; } @@ -266,8 +278,8 @@ void convertTuple (DB2FdwState* fdw_state, Datum* values, bool* nulls, bool trun case DB2_DECFLOAT: case DB2_DOUBLE: { db2Debug3(" DB2_FLOAT, DECIMAL, SMALLINT, INTEGER, REAL, DECFLOAT, DOUBLE"); - value = fdw_state->db2Table->cols[index]->val; - value_len = fdw_state->db2Table->cols[index]->val_len; + value = res->val; + value_len = res->val_len; value_len = (value_len == 0) ? strlen(value) : value_len; tmp_value = value; if((tmp_value = strchr(value,','))!=NULL) { @@ -278,23 +290,20 @@ void convertTuple (DB2FdwState* fdw_state, Datum* values, bool* nulls, bool trun default: { db2Debug3(" shoud be string based values"); /* for other data types, db2Table contains the results */ - value = fdw_state->db2Table->cols[index]->val; - value_len = fdw_state->db2Table->cols[index]->val_len; + value = res->val; + value_len = res->val_len; value_len = (value_len == 0) ? strlen(value) : value_len; } break; } - db2Debug2(" value : '%x'", value); - if (value != NULL) { - db2Debug2(" value : '%s'", value); - } - db2Debug2(" value_len: %ld" , value_len); - db2Debug2(" fdw_state->db2Table->cols[%d]->val_null : %d",index,fdw_state->db2Table->cols[index]->val_len ); - db2Debug2(" fdw_state->db2Table->cols[%d]->val_null : %d",index,fdw_state->db2Table->cols[index]->val_null); - db2Debug2(" fdw_state->db2Table->cols[%d]->pgname : %s",index,fdw_state->db2Table->cols[index]->pgname ); - db2Debug2(" fdw_state->db2Table->cols[%d]->pgattnum : %d",index,fdw_state->db2Table->cols[index]->pgattnum); - db2Debug2(" fdw_state->db2Table->cols[%d]->pgtype : %d",index,fdw_state->db2Table->cols[index]->pgtype ); - db2Debug2(" fdw_state->db2Table->cols[%d]->pgtypemod: %d",index,fdw_state->db2Table->cols[index]->pgtypmod); + db2Debug2(" value : %s" , value); + db2Debug2(" value_len : %ld" , value_len); + db2Debug2(" res->val_null : %d" ,res->val_len ); + db2Debug2(" res->val_null : %d" ,res->val_null); + db2Debug2(" res->pgname : %s" ,res->pgname ); + db2Debug2(" res->pgattnum : %d" ,res->pgattnum); + db2Debug2(" res->pgtype : %d" ,res->pgtype ); + db2Debug2(" res->pgtypemod: %d" ,res->pgtypmod); /* fill the TupleSlot with the data (after conversion if necessary) */ if (pgtype == BYTEAOID) { /* binary columns are not converted */ @@ -315,8 +324,8 @@ void convertTuple (DB2FdwState* fdw_state, Datum* values, bool* nulls, bool trun } typinput = ((Form_pg_type) GETSTRUCT (tuple))->typinput; ReleaseSysCache (tuple); - db2Debug3(" CStringGetDatum"); dat = CStringGetDatum (value); + db2Debug3(" CStringGetDatum(%s): %d",value, dat); /* install error context callback */ // db2Debug3(" error_context_stack"); // db2Debug2(" errcb.previous: %x",errcb.previous); @@ -330,7 +339,7 @@ void convertTuple (DB2FdwState* fdw_state, Datum* values, bool* nulls, bool trun /* for string types, check that the data are in the database encoding */ if (pgtype == BPCHAROID || pgtype == VARCHAROID || pgtype == TEXTOID) { db2Debug3(" pg_verify_mbstr"); - (void) pg_verify_mbstr (GetDatabaseEncoding (), value, value_len, fdw_state->db2Table->cols[index]->noencerr == NO_ENC_ERR_TRUE); + (void) pg_verify_mbstr (GetDatabaseEncoding (), value, value_len, res->noencerr == NO_ENC_ERR_TRUE); } /* call the type input function */ switch (pgtype) { @@ -342,21 +351,21 @@ void convertTuple (DB2FdwState* fdw_state, Datum* values, bool* nulls, bool trun case TIMETZOID: case INTERVALOID: case NUMERICOID: - db2Debug3(" Calling OidFunctionCall3"); /* these functions require the type modifier */ - values[j] = OidFunctionCall3 (typinput, dat, ObjectIdGetDatum (InvalidOid), Int32GetDatum (fdw_state->db2Table->cols[index]->pgtypmod)); + values[j] = OidFunctionCall3 (typinput, dat, ObjectIdGetDatum (InvalidOid), Int32GetDatum (res->pgtypmod)); + db2Debug3(" OidFunctionCall3 : values[%d]: %d", j, values[j]); break; default: - db2Debug3(" Calling OidFunctionCall1"); /* the others don't */ values[j] = OidFunctionCall1 (typinput, dat); + db2Debug3(" OidFunctionCall1 : values[%d]: %d", j, values[j]); } /* uninstall error context callback */ // error_context_stack = errcb.previous; } /* release the data buffer for LOBs */ - db2Type = c2dbType(fdw_state->db2Table->cols[index]->colType); + db2Type = c2dbType(res->colType); if (db2Type == DB2_BLOB || db2Type == DB2_CLOB) { if (value != NULL) { db2free (value); From 958c18753b885327eb057e4965c86d20217f53df Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Sun, 8 Feb 2026 15:16:25 +0100 Subject: [PATCH 031/120] refactor convertTuple to simply iterate over the expected results and in that change db2GetLob to use DB2ResultColumn instead of DB2Column --- source/db2GetLob.c | 9 ++-- source/db2_fdw_utils.c | 98 +++++++++++------------------------------- 2 files changed, 31 insertions(+), 76 deletions(-) diff --git a/source/db2GetLob.c b/source/db2GetLob.c index af32667..ce98d18 100644 --- a/source/db2GetLob.c +++ b/source/db2GetLob.c @@ -2,6 +2,7 @@ #include #include #include "db2_fdw.h" +#include "DB2ResultColumn.h" /** global variables */ @@ -18,27 +19,27 @@ extern SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQ extern void db2Error_d (db2error sqlstate, const char* message, const char* detail, ...); /** internal prototypes */ -void db2GetLob (DB2Session* session, DB2Column* column, int cidx, char** value, long* value_len, unsigned long trunc); +void db2GetLob (DB2Session* session, DB2ResultColumn* column, char** value, long* value_len, unsigned long trunc); /** db2GetLob * Get the LOB contents and store them in *value and *value_len. * If "trunc" is nonzero, it contains the number of bytes or characters to get. */ -void db2GetLob (DB2Session* session, DB2Column* column, int cidx, char** value, long* value_len, unsigned long trunc) { +void db2GetLob (DB2Session* session, DB2ResultColumn* column, char** value, long* value_len, unsigned long trunc) { SQLRETURN rc = SQL_SUCCESS; SQLLEN ind = 0; SQLCHAR buf[LOB_CHUNK_SIZE+1]; int extend = 0; db2Debug1("> db2GetLob"); db2Debug2(" column->colName: '%s'",column->colName); - db2Debug2(" cidx : %d ",cidx); + db2Debug2(" column->resnum : %d ",column->resnum); /* initialize result buffer length */ *value_len = 0; /* read the LOB in chunks */ do { db2Debug2(" value_len: %ld",*value_len); db2Debug2(" reading %d byte chunck of data",sizeof(buf)); - rc = SQLGetData(session->stmtp->hsql, cidx, SQL_C_CHAR, buf, sizeof(buf), &ind); + rc = SQLGetData(session->stmtp->hsql, column->resnum, SQL_C_CHAR, buf, sizeof(buf), &ind); rc = db2CheckErr(rc,session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); if (rc == SQL_ERROR) { db2Error_d ( FDW_UNABLE_TO_CREATE_EXECUTION, "error fetching result: SQLGetData failed to read LOB chunk", db2Message); diff --git a/source/db2_fdw_utils.c b/source/db2_fdw_utils.c index 2cc674a..a05e0f5 100644 --- a/source/db2_fdw_utils.c +++ b/source/db2_fdw_utils.c @@ -43,7 +43,7 @@ typedef struct { /** external prototypes */ -extern void db2GetLob (DB2Session* session, DB2Column* column, int cidx, char** value, long* value_len, unsigned long trunc); +extern void db2GetLob (DB2Session* session, DB2ResultColumn* column, char** value, long* value_len, unsigned long trunc); extern void db2Shutdown (void); extern short c2dbType (short fcType); extern void db2Debug1 (const char* message, ...); @@ -61,7 +61,6 @@ void reset_transmission_modes (int nestlevel); int set_transmission_modes (void); bool is_builtin (Oid objectId); bool is_shippable (Oid objectId, Oid classId, DB2FdwState* fpinfo); -static void errorContextCallback (void* arg); static void InvalidateShippableCacheCallback(Datum arg, int cacheid, uint32 hashvalue); static void InitializeShippableCache (void); static bool lookup_shippable (Oid objectId, Oid classId, DB2FdwState* fpinfo); @@ -195,56 +194,40 @@ void exitHook (int code, Datum arg) { * If trunc_lob it true, truncate LOBs to WIDTH_THRESHOLD+1 bytes. */ void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls, bool trunc_lob) { - char* tmp_value = NULL; char* value = NULL; long value_len = 0; int j = 0; int index = -1; -// ErrorContextCallback errcb; Oid pgtype; DB2Table* db2Table = fdw_state->db2Table; DB2ResultColumn* res = NULL; bool isSimpleSelect = false; db2Debug1("> %s::convertTuple",__FILE__); - /* initialize error context callback, install it only during conversions */ -// errcb.callback = errorContextCallback; -// errcb.arg = (void *) fdw_state; + db2Debug2(" natts: %d", natts); + db2Debug2(" truncate lob: %s", trunc_lob ? "true": "false"); /* assign result values */ isSimpleSelect = (natts == db2Table->npgcols); db2Debug2(" isSimpleSelect: %s", isSimpleSelect ? "true": "false"); - for (j = 0; j < db2Table->npgcols; ++j) { - short db2Type; - db2Debug2(" start processing column %d of %d",j + 1, db2Table->npgcols); - db2Debug2(" index: %d",index); - /* for dropped columns, insert a NULL */ - if ((index + 1 < db2Table->ncols) && (db2Table->cols[index+1]->pgattnum > j + 1)) { - nulls[j] = true; - values[j] = PointerGetDatum (NULL); - continue; - } else { - ++index; - } - db2Debug2(" index: %d",index); - /* get the result for the column of the current index */ - for (res = fdw_state->resultList; res; res = res->next) { - if (isSimpleSelect) { - if (res->pgattnum == j+1) - break; - } else { - if (res->resnum == j+1) - break; - } - } - /* Columns exceeding the length of the DB2 table will be NULL, as well as columns that are not used in the query. - * Geometry columns are NULL if the value is NULL, for all other types use the NULL indicator. - */ - if (index >= db2Table->ncols || res == NULL || res->val_null == -1) { - nulls[j] = true; - values[j] = PointerGetDatum (NULL); - continue; - } + + // initialize all columns to NULL + for (j = 0; j < natts; j++) { + nulls[j] = true; + values[j] = PointerGetDatum (NULL); + } + + for (res = fdw_state->resultList; res; res = res->next) { + short db2Type = 0; + j = ((isSimpleSelect) ? res->pgattnum : res->resnum) - 1; + db2Debug2(" start processing column %d of %d: values index = %d", res->resnum, natts, j); + db2Debug2(" res->pgname : %s" ,res->pgname ); + db2Debug2(" res->pgattnum : %d" ,res->pgattnum); + db2Debug2(" res->pgtype : %d" ,res->pgtype ); + db2Debug2(" res->pgtypemod: %d" ,res->pgtypmod); + db2Debug2(" res->val : %s" ,res->val ); + db2Debug2(" res->val_len : %d" ,res->val_len ); + db2Debug2(" res->val_null : %d" ,res->val_null); /* from here on, we can assume columns to be NOT NULL */ nulls[j] = false; @@ -257,7 +240,7 @@ void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls db2Debug3(" DB2_BLOB or DB2CLOB"); /* for LOBs, get the actual LOB contents (allocated), truncated if desired */ /* the column index is 1 based, whereas index id 0 based, so always add 1 to index when calling db2GetLob, since it does a column based access*/ - db2GetLob (fdw_state->session, fdw_state->db2Table->cols[index], index+1, &value, &value_len, trunc_lob ? (WIDTH_THRESHOLD + 1) : 0); + db2GetLob (fdw_state->session, res, &value, &value_len, trunc_lob ? (WIDTH_THRESHOLD + 1) : 0); } break; case DB2_LONGVARBINARY: { @@ -277,12 +260,14 @@ void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls case DB2_REAL: case DB2_DECFLOAT: case DB2_DOUBLE: { + char* tmp_value = NULL; + db2Debug3(" DB2_FLOAT, DECIMAL, SMALLINT, INTEGER, REAL, DECFLOAT, DOUBLE"); value = res->val; value_len = res->val_len; value_len = (value_len == 0) ? strlen(value) : value_len; tmp_value = value; - if((tmp_value = strchr(value,','))!=NULL) { + if((tmp_value = strchr(value,',')) != NULL) { *tmp_value = '.'; } } @@ -298,12 +283,6 @@ void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls } db2Debug2(" value : %s" , value); db2Debug2(" value_len : %ld" , value_len); - db2Debug2(" res->val_null : %d" ,res->val_len ); - db2Debug2(" res->val_null : %d" ,res->val_null); - db2Debug2(" res->pgname : %s" ,res->pgname ); - db2Debug2(" res->pgattnum : %d" ,res->pgattnum); - db2Debug2(" res->pgtype : %d" ,res->pgtype ); - db2Debug2(" res->pgtypemod: %d" ,res->pgtypmod); /* fill the TupleSlot with the data (after conversion if necessary) */ if (pgtype == BYTEAOID) { /* binary columns are not converted */ @@ -326,15 +305,6 @@ void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls ReleaseSysCache (tuple); dat = CStringGetDatum (value); db2Debug3(" CStringGetDatum(%s): %d",value, dat); - /* install error context callback */ -// db2Debug3(" error_context_stack"); -// db2Debug2(" errcb.previous: %x",errcb.previous); -// errcb.previous = error_context_stack; -// db2Debug2(" errcb.previous: %x",errcb.previous); -// db2Debug2(" &errcb: %x", &errcb); -// error_context_stack = &errcb; - db2Debug2(" index: %d", index); - fdw_state->columnindex = index; /* for string types, check that the data are in the database encoding */ if (pgtype == BPCHAROID || pgtype == VARCHAROID || pgtype == TEXTOID) { @@ -360,8 +330,6 @@ void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls values[j] = OidFunctionCall1 (typinput, dat); db2Debug3(" OidFunctionCall1 : values[%d]: %d", j, values[j]); } - /* uninstall error context callback */ -// error_context_stack = errcb.previous; } /* release the data buffer for LOBs */ @@ -374,22 +342,8 @@ void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls } } } - db2Debug1("< %s::convertTuple",__FILE__); -} -/** errorContextCallback - * Provides the context for an error message during a type input conversion. - * The argument must be a pointer to a DB2FdwState. - */ -static void errorContextCallback (void* arg) { - DB2FdwState *fdw_state = (DB2FdwState*) arg; - db2Debug1("> %s::errorContextCallback",__FILE__); - errcontext ( "converting column \"%s\" for foreign table scan of \"%s\", row %lu" - , quote_identifier (fdw_state->db2Table->cols[fdw_state->columnindex]->pgname) - , quote_identifier (fdw_state->db2Table->pgname) - , fdw_state->rowcount - ); - db2Debug1("< %s::errorContextCallback",__FILE__); + db2Debug1("< %s::convertTuple",__FILE__); } /** Undo the effects of set_transmission_modes(). From 3a95713b86d471f71a7570de9556f0e153f7b5c2 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Sun, 8 Feb 2026 15:17:06 +0100 Subject: [PATCH 032/120] remove columnindex from DB2FdwState since it is no longer used --- include/DB2FdwState.h | 1 - source/db2GetForeignUpperPaths.c | 1 - source/db2PlanForeignModify.c | 1 - source/db2_de_serialize.c | 3 +-- 4 files changed, 1 insertion(+), 5 deletions(-) diff --git a/include/DB2FdwState.h b/include/DB2FdwState.h index 402b30d..6b8b06d 100644 --- a/include/DB2FdwState.h +++ b/include/DB2FdwState.h @@ -34,7 +34,6 @@ typedef struct db2FdwState { DB2ResultColumn* resultList; // list of result columns for the query DB2Table* db2Table; // description of the remote DB2 table unsigned long rowcount; // rows already read from DB2 - int columnindex; // currently processed column for error context MemoryContext temp_cxt; // short-lived memory for data modification unsigned long prefetch; // number of rows to prefetch (SQL_ATTR_PREFETCH_NROWS 0-1024) char* order_clause; // for sort-pushdown diff --git a/source/db2GetForeignUpperPaths.c b/source/db2GetForeignUpperPaths.c index fb89dcf..ba6c97a 100644 --- a/source/db2GetForeignUpperPaths.c +++ b/source/db2GetForeignUpperPaths.c @@ -176,7 +176,6 @@ static void db2CloneFdwStateUpper(PlannerInfo* root, RelOptInfo* input_rel, RelO copy->startup_cost = fdw_in->startup_cost; copy->total_cost = fdw_in->total_cost; copy->rowcount = 0; - copy->columnindex = 0; copy->temp_cxt = NULL; copy->order_clause = fdw_in->order_clause ? db2strdup(fdw_in->order_clause) : NULL; diff --git a/source/db2PlanForeignModify.c b/source/db2PlanForeignModify.c index a27f69d..3727f17 100644 --- a/source/db2PlanForeignModify.c +++ b/source/db2PlanForeignModify.c @@ -366,7 +366,6 @@ static DB2FdwState* copyPlanData (DB2FdwState* orig) { copy->startup_cost = 0.0; copy->total_cost = 0.0; copy->rowcount = 0; - copy->columnindex = 0; copy->temp_cxt = NULL; copy->order_clause = NULL; db2Debug1("< copyPlanData"); diff --git a/source/db2_de_serialize.c b/source/db2_de_serialize.c index 256fe68..11d8a33 100644 --- a/source/db2_de_serialize.c +++ b/source/db2_de_serialize.c @@ -49,7 +49,6 @@ DB2FdwState* deserializePlanData (List* list) { state->total_cost = 0; /* these are not serialized */ state->rowcount = 0; - state->columnindex = 0; state->params = NULL; state->temp_cxt = NULL; state->order_clause = NULL; @@ -376,7 +375,7 @@ List* serializePlanData (DB2FdwState* fdwState) { lenParam--; } - /* don't serialize params, startup_cost, total_cost, rowcount, columnindex, temp_cxt, order_clause and where_clause */ + /* don't serialize params, startup_cost, total_cost, rowcount, temp_cxt, order_clause and where_clause */ db2Debug1("< serializePlanData - returns: %x",result); return result; } From 3616fc8b6d372b370c441b2d74ec01e25efc1923 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Sun, 8 Feb 2026 15:25:34 +0100 Subject: [PATCH 033/120] if ever, this new will be version 18.2.0 --- META.json | 4 ++-- db2_fdw.control | 2 +- include/db2_fdw.h | 2 +- sql/{db2_fdw--18.1.1.sql => db2_fdw--18.2.0.sql} | 0 ...tall_db2_fdw--18.1.1.sql => uninstall_db2_fdw--18.2.0.sql} | 0 5 files changed, 4 insertions(+), 4 deletions(-) rename sql/{db2_fdw--18.1.1.sql => db2_fdw--18.2.0.sql} (100%) rename sql/{uninstall_db2_fdw--18.1.1.sql => uninstall_db2_fdw--18.2.0.sql} (100%) diff --git a/META.json b/META.json index b86d7e5..ecc6d7d 100644 --- a/META.json +++ b/META.json @@ -2,7 +2,7 @@ "name": "db2_fdw", "abstract": "PostgreSQL Data Wrappper to DB2 databases", "description": "With the Data Wrapper you can acces DB2 Tabels. Not supported for all Data Types (BLOB over 2 GByte)", - "version": "18.1.1", + "version": "18.2.0", "maintainer": [ "Thomas Muenz " ], @@ -12,7 +12,7 @@ "abstract": "PostgreSQL Data Wrappper to DB2 databases", "file": "sql/db2_fdw.sql", "docfile": "doc/db2_fdw.md", - "version": "18.1.1" + "version": "18.2.0" } }, "resources": { diff --git a/db2_fdw.control b/db2_fdw.control index 577c083..cf5184b 100644 --- a/db2_fdw.control +++ b/db2_fdw.control @@ -1,5 +1,5 @@ # db2_fdw extension comment = 'foreign data wrapper for DB2 access' -default_version = '18.1.1' +default_version = '18.2.0' module_pathname = '$libdir/db2_fdw' relocatable = true diff --git a/include/db2_fdw.h b/include/db2_fdw.h index 4a953ed..54c5c57 100644 --- a/include/db2_fdw.h +++ b/include/db2_fdw.h @@ -46,7 +46,7 @@ #endif /* POSTGRES_H */ /* db2_fdw version */ -#define DB2_FDW_VERSION "18.1.1" +#define DB2_FDW_VERSION "18.2.0" /* number of bytes to read per LOB chunk */ #define LOB_CHUNK_SIZE 8192 #define ERRBUFSIZE 2000 diff --git a/sql/db2_fdw--18.1.1.sql b/sql/db2_fdw--18.2.0.sql similarity index 100% rename from sql/db2_fdw--18.1.1.sql rename to sql/db2_fdw--18.2.0.sql diff --git a/sql/uninstall_db2_fdw--18.1.1.sql b/sql/uninstall_db2_fdw--18.2.0.sql similarity index 100% rename from sql/uninstall_db2_fdw--18.1.1.sql rename to sql/uninstall_db2_fdw--18.2.0.sql From a6a428d0b7b5462943da43f70a78ae1d46e6ad6e Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Tue, 10 Feb 2026 15:16:05 +0100 Subject: [PATCH 034/120] enhanced debug output --- source/db2PrepareQuery.c | 1 + 1 file changed, 1 insertion(+) diff --git a/source/db2PrepareQuery.c b/source/db2PrepareQuery.c index 980bdbb..043397d 100644 --- a/source/db2PrepareQuery.c +++ b/source/db2PrepareQuery.c @@ -131,6 +131,7 @@ void db2PrepareQuery (DB2Session* session, const char *query, DB2ResultColumn* r } db2Debug2(" res->colName : %s" ,res->colName); db2Debug2(" res->colSize : %ld",res->colSize); + db2Debug2(" res->colType : %d" ,res->colType); db2Debug2(" res->colScale : %d" ,res->colScale); db2Debug2(" res->colNulls : %d" ,res->colNulls); db2Debug2(" res->colChars : %ld",res->colChars); From 8ea2d54bd8f0e981f6f726d736d8c2fab1812e95 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Tue, 10 Feb 2026 15:17:30 +0100 Subject: [PATCH 035/120] solved the problem of multiple columns in an aggregate function as well as ensured the result for count is always a big integer --- source/db2GetForeignPlan.c | 226 ++++++++++++++++++++++++------------- 1 file changed, 145 insertions(+), 81 deletions(-) diff --git a/source/db2GetForeignPlan.c b/source/db2GetForeignPlan.c index 2398256..4caf83a 100644 --- a/source/db2GetForeignPlan.c +++ b/source/db2GetForeignPlan.c @@ -1,7 +1,12 @@ #include +#include +#include +#include +#include #include #include #include +#include #include "db2_fdw.h" #include "DB2FdwState.h" @@ -31,8 +36,8 @@ extern List* serializePlanData (DB2FdwState* fdw_state); /** local prototypes */ ForeignScan* db2GetForeignPlan (PlannerInfo* root, RelOptInfo* foreignrel, Oid foreigntableid, ForeignPath* best_path, List* tlist, List* scan_clauses , Plan* outer_plan); -static void getUsedColumns (Expr* expr, RelOptInfo* foreignrel); -static void addResult (DB2ResultColumn** resultList, DB2Column* db2Column); +static void getUsedColumns (Expr* expr, RelOptInfo* foreignrel, DB2ResultColumn* resCol); +static void addResult (DB2ResultColumn* resCol, DB2Column* db2Column); /* postgresGetForeignPlan * Create ForeignScan plan node which implements selected best path @@ -71,14 +76,20 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo db2Debug3(" size of columnlist: %d", list_length(foreignrel->reltarget->exprs)); foreach (cell, foreignrel->reltarget->exprs) { db2Debug3(" examine column"); - getUsedColumns ((Expr*) lfirst (cell), foreignrel); + DB2ResultColumn* resCol = (DB2ResultColumn*)db2alloc("resultColumn",sizeof(DB2ResultColumn)); + resCol->next = fpinfo->resultList; + fpinfo->resultList = resCol; + getUsedColumns ((Expr*) lfirst (cell), foreignrel, resCol); } /* examine each condition for Var nodes */ db2Debug3(" size of conditions: %d", list_length(foreignrel->baserestrictinfo)); foreach (cell, foreignrel->baserestrictinfo) { db2Debug3(" examine condition"); - getUsedColumns ((Expr*) lfirst (cell), foreignrel); + DB2ResultColumn* resCol = (DB2ResultColumn*)db2alloc("resultColumn",sizeof(DB2ResultColumn)); + resCol->next = fpinfo->resultList; + fpinfo->resultList = resCol; + getUsedColumns ((Expr*) lfirst (cell), foreignrel, resCol); } /* In a base-relation scan, we must apply the given scan_clauses. @@ -140,7 +151,10 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo db2Debug3(" size of tlist: %d", list_length(fdw_scan_tlist)); foreach (cell, fdw_scan_tlist) { db2Debug3(" examine tlist"); - getUsedColumns ((Expr*) lfirst (cell), foreignrel); + DB2ResultColumn* resCol = (DB2ResultColumn*)db2alloc("resultColumn",sizeof(DB2ResultColumn)); + resCol->next = fpinfo->resultList; + fpinfo->resultList = resCol; + getUsedColumns ((Expr*) lfirst (cell), foreignrel, resCol); } } @@ -204,17 +218,18 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo /** getUsedColumns * Set "used=true" in db2Table for all columns used in the expression. */ -static void getUsedColumns (Expr* expr, RelOptInfo* foreignrel) { +static void getUsedColumns (Expr* expr, RelOptInfo* foreignrel, DB2ResultColumn* resCol) { ListCell* cell = NULL; db2Debug1("> getUsedColumns"); if (expr != NULL) { + db2Debug2(" examine node of type: %d", expr->type); switch (expr->type) { case T_RestrictInfo: - getUsedColumns (((RestrictInfo*) expr)->clause, foreignrel); + getUsedColumns (((RestrictInfo*) expr)->clause, foreignrel, resCol); break; case T_TargetEntry: - getUsedColumns (((TargetEntry*) expr)->expr, foreignrel); + getUsedColumns (((TargetEntry*) expr)->expr, foreignrel, resCol); break; case T_Const: case T_Param: @@ -236,8 +251,10 @@ static void getUsedColumns (Expr* expr, RelOptInfo* foreignrel) { if (variable->varattno == 0) { for (index = 0; index < fpinfo->db2Table->ncols; ++index) { if (fpinfo->db2Table->cols[index]->pgname) { - addResult(&fpinfo->resultList,fpinfo->db2Table->cols[index]); -// fpinfo->db2Table->cols[index]->used = 1; + addResult(resCol,fpinfo->db2Table->cols[index]); + resCol = (DB2ResultColumn*)db2alloc("resultColumn",sizeof(DB2ResultColumn)); + resCol->next = fpinfo->resultList; + fpinfo->resultList = resCol; } } break; @@ -251,159 +268,206 @@ static void getUsedColumns (Expr* expr, RelOptInfo* foreignrel) { if (index == -1) { ereport (WARNING, (errcode (ERRCODE_WARNING),errmsg ("column number %d of foreign table \"%s\" does not exist in foreign DB2 table, will be replaced by NULL", variable->varattno, fpinfo->db2Table->pgname))); } else { - addResult(&fpinfo->resultList,fpinfo->db2Table->cols[index]); -// fpinfo->db2Table->cols[index]->used = 1; + addResult(resCol,fpinfo->db2Table->cols[index]); } } break; case T_Aggref: { Aggref* aggref = (Aggref*) expr; - foreach (cell, aggref->args) { - getUsedColumns ((Expr*) lfirst (cell), foreignrel); - } - foreach (cell, aggref->aggorder) { - getUsedColumns ((Expr*) lfirst (cell), foreignrel); + /* Resolve aggregate function name (OID -> pg_proc.proname). */ + HeapTuple tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(aggref->aggfnoid)); + char* aggname = NULL; + char* nspname = NULL; + if (HeapTupleIsValid(tuple)) { + Form_pg_proc procform = (Form_pg_proc) GETSTRUCT(tuple); + aggname = pstrdup(NameStr(procform->proname)); + /* Optional: capture schema for debugging/qualification decisions. */ + if (OidIsValid(procform->pronamespace)) { + HeapTuple ntup = SearchSysCache1(NAMESPACEOID, ObjectIdGetDatum(procform->pronamespace)); + if (HeapTupleIsValid(ntup)) { + Form_pg_namespace nspform = (Form_pg_namespace) GETSTRUCT(ntup); + nspname = pstrdup(NameStr(nspform->nspname)); + ReleaseSysCache(ntup); + } + } + ReleaseSysCache(tuple); } - foreach (cell, aggref->aggdistinct) { - getUsedColumns ((Expr*) lfirst (cell), foreignrel); + db2Debug2( " aggref->aggfnoid=%u name=%s%s%s", aggref->aggfnoid, nspname ? nspname : "", nspname ? "." : "", aggname ? aggname : ""); + if (aggname && strcmp(aggname, "count") == 0) { + db2Debug2(" found COUNT aggregate"); + DB2FdwState* fpinfo = (DB2FdwState*) foreignrel->fdw_private; + /* if it's a COUNT(*) then we need an additional result */ + DB2Column* col = db2alloc("DB2Column for count(*)",sizeof(DB2Column)); + col->colName = "count"; + col->colType = -5; // SQL_BIGINT type in DB2, which can hold the result of COUNT(*) + col->colSize = 8; + col->colScale = 0; + col->colNulls = 1; + col->colChars = 23; // max number of characters needed to represent a 8-byte integer, including sign + col->colBytes = 8; + col->colPrimKeyPart = 0; + col->colCodepage = 0; + col->pgname = "count"; + col->pgattnum = 0; + col->pgtype = INT8OID; + col->pgtypmod = -1; + col->used = 1; + col->pkey = 0; + col->val = NULL; + col->val_size = 24; + col->val_len = 0; + col->val_null = 0; + col->noencerr = fpinfo->db2Table->cols[0]->noencerr; // use same noencerr as first column + addResult(resCol,col); + } else { + db2Debug2(" count aggref->args: %d",list_length(aggref->args)); + foreach (cell, aggref->args) { + getUsedColumns ((Expr*) lfirst (cell), foreignrel, resCol); + } + foreach (cell, aggref->aggorder) { + getUsedColumns ((Expr*) lfirst (cell), foreignrel, resCol); + } + foreach (cell, aggref->aggdistinct) { + getUsedColumns ((Expr*) lfirst (cell), foreignrel, resCol); + } } } break; case T_WindowFunc: foreach (cell, ((WindowFunc*) expr)->args) { - getUsedColumns ((Expr*) lfirst (cell), foreignrel); + getUsedColumns ((Expr*) lfirst (cell), foreignrel, resCol); } break; case T_SubscriptingRef: { SubscriptingRef* ref = (SubscriptingRef*) expr; foreach(cell, ref->refupperindexpr) { - getUsedColumns((Expr*)lfirst(cell), foreignrel); + getUsedColumns((Expr*)lfirst(cell), foreignrel, resCol); } foreach(cell, ref->reflowerindexpr) { - getUsedColumns((Expr*)lfirst(cell), foreignrel); + getUsedColumns((Expr*)lfirst(cell), foreignrel, resCol); } - getUsedColumns(ref->refexpr, foreignrel); - getUsedColumns(ref->refassgnexpr, foreignrel); + getUsedColumns(ref->refexpr, foreignrel, resCol); + getUsedColumns(ref->refassgnexpr, foreignrel, resCol); } break; case T_FuncExpr: foreach (cell, ((FuncExpr*) expr)->args) { - getUsedColumns ((Expr*) lfirst (cell), foreignrel); + getUsedColumns ((Expr*) lfirst (cell), foreignrel, resCol); } break; case T_OpExpr: foreach (cell, ((OpExpr*) expr)->args) { - getUsedColumns ((Expr*) lfirst (cell), foreignrel); + getUsedColumns ((Expr*) lfirst (cell), foreignrel, resCol); } break; case T_DistinctExpr: foreach (cell, ((DistinctExpr*) expr)->args) { - getUsedColumns ((Expr*) lfirst (cell), foreignrel); + getUsedColumns ((Expr*) lfirst (cell), foreignrel, resCol); } break; case T_NullIfExpr: foreach (cell, ((NullIfExpr*) expr)->args) { - getUsedColumns ((Expr*) lfirst (cell), foreignrel); + getUsedColumns ((Expr*) lfirst (cell), foreignrel, resCol); } break; case T_ScalarArrayOpExpr: foreach (cell, ((ScalarArrayOpExpr*) expr)->args) { - getUsedColumns ((Expr*) lfirst (cell), foreignrel); + getUsedColumns ((Expr*) lfirst (cell), foreignrel, resCol); } break; case T_BoolExpr: foreach (cell, ((BoolExpr*) expr)->args) { - getUsedColumns ((Expr*) lfirst (cell), foreignrel); + getUsedColumns ((Expr*) lfirst (cell), foreignrel, resCol); } break; case T_SubPlan: foreach (cell, ((SubPlan*) expr)->args) { - getUsedColumns ((Expr*) lfirst (cell), foreignrel); + getUsedColumns ((Expr*) lfirst (cell), foreignrel, resCol); } break; case T_AlternativeSubPlan: /* examine only first alternative */ - getUsedColumns ((Expr*) linitial (((AlternativeSubPlan*) expr)->subplans), foreignrel); + getUsedColumns ((Expr*) linitial (((AlternativeSubPlan*) expr)->subplans), foreignrel, resCol); break; case T_NamedArgExpr: - getUsedColumns (((NamedArgExpr*) expr)->arg, foreignrel); + getUsedColumns (((NamedArgExpr*) expr)->arg, foreignrel, resCol); break; case T_FieldSelect: - getUsedColumns (((FieldSelect*) expr)->arg, foreignrel); + getUsedColumns (((FieldSelect*) expr)->arg, foreignrel, resCol); break; case T_RelabelType: - getUsedColumns (((RelabelType*) expr)->arg, foreignrel); + getUsedColumns (((RelabelType*) expr)->arg, foreignrel, resCol); break; case T_CoerceViaIO: - getUsedColumns (((CoerceViaIO*) expr)->arg, foreignrel); + getUsedColumns (((CoerceViaIO*) expr)->arg, foreignrel, resCol); break; case T_ArrayCoerceExpr: - getUsedColumns (((ArrayCoerceExpr*) expr)->arg, foreignrel); + getUsedColumns (((ArrayCoerceExpr*) expr)->arg, foreignrel, resCol); break; case T_ConvertRowtypeExpr: - getUsedColumns (((ConvertRowtypeExpr*) expr)->arg, foreignrel); + getUsedColumns (((ConvertRowtypeExpr*) expr)->arg, foreignrel, resCol); break; case T_CollateExpr: - getUsedColumns (((CollateExpr*) expr)->arg, foreignrel); + getUsedColumns (((CollateExpr*) expr)->arg, foreignrel, resCol); break; case T_CaseExpr: foreach (cell, ((CaseExpr*) expr)->args) { - getUsedColumns ((Expr*) lfirst (cell), foreignrel); + getUsedColumns ((Expr*) lfirst (cell), foreignrel, resCol); } - getUsedColumns (((CaseExpr*) expr)->arg, foreignrel); - getUsedColumns (((CaseExpr*) expr)->defresult, foreignrel); + getUsedColumns (((CaseExpr*) expr)->arg, foreignrel, resCol); + getUsedColumns (((CaseExpr*) expr)->defresult, foreignrel, resCol); break; case T_CaseWhen: - getUsedColumns (((CaseWhen*) expr)->expr, foreignrel); - getUsedColumns (((CaseWhen*) expr)->result, foreignrel); + getUsedColumns (((CaseWhen*) expr)->expr, foreignrel, resCol); + getUsedColumns (((CaseWhen*) expr)->result, foreignrel, resCol); break; case T_ArrayExpr: foreach (cell, ((ArrayExpr*) expr)->elements) { - getUsedColumns ((Expr*) lfirst (cell), foreignrel); + getUsedColumns ((Expr*) lfirst (cell), foreignrel, resCol); } break; case T_RowExpr: foreach (cell, ((RowExpr*) expr)->args) { - getUsedColumns ((Expr*) lfirst (cell), foreignrel); + getUsedColumns ((Expr*) lfirst (cell), foreignrel, resCol); } break; case T_RowCompareExpr: foreach (cell, ((RowCompareExpr*) expr)->largs) { - getUsedColumns ((Expr*) lfirst (cell), foreignrel); + getUsedColumns ((Expr*) lfirst (cell), foreignrel, resCol); } foreach (cell, ((RowCompareExpr*) expr)->rargs) { - getUsedColumns ((Expr*) lfirst (cell), foreignrel); + getUsedColumns ((Expr*) lfirst (cell), foreignrel, resCol); } break; case T_CoalesceExpr: foreach (cell, ((CoalesceExpr*) expr)->args) { - getUsedColumns ((Expr*) lfirst (cell), foreignrel); + getUsedColumns ((Expr*) lfirst (cell), foreignrel, resCol); } break; case T_MinMaxExpr: foreach (cell, ((MinMaxExpr*) expr)->args) { - getUsedColumns ((Expr*) lfirst (cell), foreignrel); + getUsedColumns ((Expr*) lfirst (cell), foreignrel, resCol); } break; case T_XmlExpr: foreach (cell, ((XmlExpr*) expr)->named_args) { - getUsedColumns ((Expr*) lfirst (cell), foreignrel); + getUsedColumns ((Expr*) lfirst (cell), foreignrel, resCol); } foreach (cell, ((XmlExpr*) expr)->args) { - getUsedColumns ((Expr*) lfirst (cell), foreignrel); + getUsedColumns ((Expr*) lfirst (cell), foreignrel, resCol); } break; case T_NullTest: - getUsedColumns (((NullTest*) expr)->arg, foreignrel); + getUsedColumns (((NullTest*) expr)->arg, foreignrel, resCol); break; case T_BooleanTest: - getUsedColumns (((BooleanTest*) expr)->arg, foreignrel); + getUsedColumns (((BooleanTest*) expr)->arg, foreignrel, resCol); break; case T_CoerceToDomain: - getUsedColumns (((CoerceToDomain*) expr)->arg, foreignrel); + getUsedColumns (((CoerceToDomain*) expr)->arg, foreignrel, resCol); break; case T_PlaceHolderVar: - getUsedColumns (((PlaceHolderVar*) expr)->phexpr, foreignrel); + getUsedColumns (((PlaceHolderVar*) expr)->phexpr, foreignrel, resCol); break; case T_SQLValueFunction: //nop @@ -422,31 +486,31 @@ static void getUsedColumns (Expr* expr, RelOptInfo* foreignrel) { db2Debug1("< getUsedColumns"); } -static void addResult(DB2ResultColumn** resultList, DB2Column* column) { - DB2ResultColumn* resCol = NULL; +static void addResult(DB2ResultColumn* resCol, DB2Column* column) { db2Debug1("> %s::addResult",__FILE__); - resCol = db2alloc("resultColumn",sizeof(DB2ResultColumn)); - resCol->next = *resultList; - resCol->colName = db2strdup(column->colName); - resCol->colType = column->colType; - resCol->colSize = column->colSize; - resCol->colScale = column->colScale; - resCol->colNulls = column->colNulls; - resCol->colChars = column->colChars; - resCol->colBytes = column->colBytes; - resCol->colPrimKeyPart = column->colPrimKeyPart; - resCol->colCodepage = column->colCodepage; - resCol->pgname = db2strdup(column->pgname); - resCol->pgattnum = column->pgattnum; - resCol->pgtype = column->pgtype; - resCol->pgtypmod = column->pgtypmod; - resCol->pkey = column->pkey; - resCol->val = db2strdup(column->val); - resCol->val_size = column->val_size; - resCol->val_len = column->val_len; - resCol->val_null = column->val_null; - resCol->varno = column->varno; - resCol->noencerr = column->noencerr; - *resultList = resCol; + if (resCol->colName == NULL) { + resCol->colName = db2strdup(column->colName); + resCol->colType = column->colType; + resCol->colSize = column->colSize; + resCol->colScale = column->colScale; + resCol->colNulls = column->colNulls; + resCol->colChars = column->colChars; + resCol->colBytes = column->colBytes; + resCol->colPrimKeyPart = column->colPrimKeyPart; + resCol->colCodepage = column->colCodepage; + resCol->pgname = db2strdup(column->pgname); + resCol->pgattnum = column->pgattnum; + resCol->pgtype = column->pgtype; + resCol->pgtypmod = column->pgtypmod; + resCol->pkey = column->pkey; + resCol->val = db2strdup(column->val); + resCol->val_size = column->val_size; + resCol->val_len = column->val_len; + resCol->val_null = column->val_null; + resCol->varno = column->varno; + resCol->noencerr = column->noencerr; + } else { + db2Debug2(" column %s already in result list", column->colName); + } db2Debug1("< %s::addResult",__FILE__); } \ No newline at end of file From f714d2e8a3d48b8902c81353d133db109937003d Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Tue, 10 Feb 2026 15:17:47 +0100 Subject: [PATCH 036/120] enhanced debugging output --- source/db2_fdw_utils.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/db2_fdw_utils.c b/source/db2_fdw_utils.c index a05e0f5..23967dc 100644 --- a/source/db2_fdw_utils.c +++ b/source/db2_fdw_utils.c @@ -224,7 +224,7 @@ void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls db2Debug2(" res->pgname : %s" ,res->pgname ); db2Debug2(" res->pgattnum : %d" ,res->pgattnum); db2Debug2(" res->pgtype : %d" ,res->pgtype ); - db2Debug2(" res->pgtypemod: %d" ,res->pgtypmod); + db2Debug2(" res->pgtypmod : %d" ,res->pgtypmod); db2Debug2(" res->val : %s" ,res->val ); db2Debug2(" res->val_len : %d" ,res->val_len ); db2Debug2(" res->val_null : %d" ,res->val_null); From 10599e5e94cf954127220ab9b68e65fca4f060b7 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Tue, 10 Feb 2026 15:18:20 +0100 Subject: [PATCH 037/120] fixed the test for an expression is a column of any of the relations used in the query --- source/db2_deparse.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/source/db2_deparse.c b/source/db2_deparse.c index f712761..8798b03 100644 --- a/source/db2_deparse.c +++ b/source/db2_deparse.c @@ -2149,6 +2149,7 @@ static void deparseVar(Var* expr, deparse_expr_cxt* ctx) { int colno = 0; /* Qualify columns when multiple relations are involved. */ bool qualify_col = (bms_membership(relids) == BMS_MULTIPLE); + bool is_query_var = false; db2Debug1("> %s::deparseVar", __FILE__); /* If the Var belongs to the foreign relation that is deparsed as a subquery, use the relation and column alias to the Var provided by the @@ -2158,9 +2159,10 @@ static void deparseVar(Var* expr, deparse_expr_cxt* ctx) { appendStringInfo(ctx->buf, "%s%d.%s%d", SUBQUERY_REL_ALIAS_PREFIX, relno, SUBQUERY_COL_ALIAS_PREFIX, colno); return; } - db2Debug2(" bms_is_member(%d,%d): %s",expr->varno, relids,bms_is_member(expr->varno, relids) ? "true":"false"); + is_query_var = bms_is_member(expr->varno, ctx->root->all_query_rels); + db2Debug2(" bms_is_member(%d,%d): %s",expr->varno, ctx->root->all_query_rels, is_query_var ? "true":"false"); db2Debug2(" expr->varlevelsup: %d",expr->varlevelsup); - if (bms_is_member(expr->varno, relids) && expr->varlevelsup == 0) { + if (is_query_var && expr->varlevelsup == 0) { deparseColumnRef(ctx->buf, expr->varno, expr->varattno, planner_rt_fetch(expr->varno, ctx->root), qualify_col); } else { /* Treat like a Param */ From eb9e25d6c232fa00edfce6c76882bbdb188ac0d6 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Tue, 10 Feb 2026 15:41:41 +0100 Subject: [PATCH 038/120] handle NULLs correctly --- source/db2_fdw_utils.c | 46 +++++++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/source/db2_fdw_utils.c b/source/db2_fdw_utils.c index 23967dc..d30dbc0 100644 --- a/source/db2_fdw_utils.c +++ b/source/db2_fdw_utils.c @@ -218,8 +218,6 @@ void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls } for (res = fdw_state->resultList; res; res = res->next) { - short db2Type = 0; - j = ((isSimpleSelect) ? res->pgattnum : res->resnum) - 1; db2Debug2(" start processing column %d of %d: values index = %d", res->resnum, natts, j); db2Debug2(" res->pgname : %s" ,res->pgname ); db2Debug2(" res->pgattnum : %d" ,res->pgattnum); @@ -229,12 +227,15 @@ void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls db2Debug2(" res->val_len : %d" ,res->val_len ); db2Debug2(" res->val_null : %d" ,res->val_null); - /* from here on, we can assume columns to be NOT NULL */ - nulls[j] = false; - pgtype = res->pgtype; - - /* get the data and its length */ - switch(c2dbType(res->colType)) { + if (res->val_null >= 0) { + short db2Type = 0; + j = ((isSimpleSelect) ? res->pgattnum : res->resnum) - 1; + /* from here on, we can assume columns to be NOT NULL */ + nulls[j] = false; + pgtype = res->pgtype; + + /* get the data and its length */ + switch(c2dbType(res->colType)) { case DB2_BLOB: case DB2_CLOB: { db2Debug3(" DB2_BLOB or DB2CLOB"); @@ -281,17 +282,17 @@ void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls } break; } - db2Debug2(" value : %s" , value); - db2Debug2(" value_len : %ld" , value_len); - /* fill the TupleSlot with the data (after conversion if necessary) */ - if (pgtype == BYTEAOID) { + db2Debug2(" value : %s" , value); + db2Debug2(" value_len : %ld" , value_len); + /* fill the TupleSlot with the data (after conversion if necessary) */ + if (pgtype == BYTEAOID) { /* binary columns are not converted */ bytea* result = (bytea*) db2alloc ("bytea", value_len + VARHDRSZ); memcpy (VARDATA (result), value, value_len); SET_VARSIZE (result, value_len + VARHDRSZ); values[j] = PointerGetDatum (result); - } else { + } else { regproc typinput; HeapTuple tuple; Datum dat; @@ -331,15 +332,18 @@ void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls db2Debug3(" OidFunctionCall1 : values[%d]: %d", j, values[j]); } } - - /* release the data buffer for LOBs */ - db2Type = c2dbType(res->colType); - if (db2Type == DB2_BLOB || db2Type == DB2_CLOB) { - if (value != NULL) { - db2free (value); - } else { - db2Debug2(" not freeing value, since it is null"); + + /* release the data buffer for LOBs */ + db2Type = c2dbType(res->colType); + if (db2Type == DB2_BLOB || db2Type == DB2_CLOB) { + if (value != NULL) { + db2free (value); + } else { + db2Debug2(" not freeing value, since it is null"); + } } + } else { + db2Debug2(" column %d is NULL", res->resnum); } } From e821a20631d1a7249c02146456198aaa08b308cf Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Tue, 10 Feb 2026 17:17:56 +0100 Subject: [PATCH 039/120] remove the reverting of the resulnumber. The aim now is to ensure the resnumber is correctly set to the position of the result in the query. --- source/db2_de_serialize.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/db2_de_serialize.c b/source/db2_de_serialize.c index 11d8a33..42a3cdc 100644 --- a/source/db2_de_serialize.c +++ b/source/db2_de_serialize.c @@ -370,8 +370,8 @@ List* serializePlanData (DB2FdwState* fdwState) { db2Debug4(" serialize res.val_size: %ld", rcol->val_size); result = lappend (result, serializeInt (rcol->noencerr)); db2Debug4(" serialize res.noencerr: %d" , rcol->noencerr); - result = lappend (result, serializeInt (lenParam)); // the last result is the first in the list - db2Debug4(" serialize res.resnum: %d" , lenParam); + result = lappend (result, serializeInt (rcol->resnum)); // the last result is the first in the list + db2Debug4(" serialize res.resnum: %d" , rcol->resnum); lenParam--; } From 39d91204d6ff3a3a330413008f1da3e2f0623f23 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Tue, 10 Feb 2026 17:18:42 +0100 Subject: [PATCH 040/120] removed pgtype and directly used res->pgtype --- source/db2_fdw_utils.c | 183 ++++++++++++++++++++--------------------- 1 file changed, 90 insertions(+), 93 deletions(-) diff --git a/source/db2_fdw_utils.c b/source/db2_fdw_utils.c index d30dbc0..b851336 100644 --- a/source/db2_fdw_utils.c +++ b/source/db2_fdw_utils.c @@ -198,7 +198,6 @@ void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls long value_len = 0; int j = 0; int index = -1; - Oid pgtype; DB2Table* db2Table = fdw_state->db2Table; DB2ResultColumn* res = NULL; bool isSimpleSelect = false; @@ -218,6 +217,7 @@ void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls } for (res = fdw_state->resultList; res; res = res->next) { + j = ((isSimpleSelect) ? res->pgattnum : res->resnum) - 1; db2Debug2(" start processing column %d of %d: values index = %d", res->resnum, natts, j); db2Debug2(" res->pgname : %s" ,res->pgname ); db2Debug2(" res->pgattnum : %d" ,res->pgattnum); @@ -229,110 +229,107 @@ void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls if (res->val_null >= 0) { short db2Type = 0; - j = ((isSimpleSelect) ? res->pgattnum : res->resnum) - 1; /* from here on, we can assume columns to be NOT NULL */ nulls[j] = false; - pgtype = res->pgtype; /* get the data and its length */ switch(c2dbType(res->colType)) { - case DB2_BLOB: - case DB2_CLOB: { - db2Debug3(" DB2_BLOB or DB2CLOB"); - /* for LOBs, get the actual LOB contents (allocated), truncated if desired */ - /* the column index is 1 based, whereas index id 0 based, so always add 1 to index when calling db2GetLob, since it does a column based access*/ - db2GetLob (fdw_state->session, res, &value, &value_len, trunc_lob ? (WIDTH_THRESHOLD + 1) : 0); - } - break; - case DB2_LONGVARBINARY: { - db2Debug3(" DB2_LONGBINARY datatypes"); - /* for LONG and LONG RAW, the first 4 bytes contain the length */ - value_len = *((int32*) res->val); - /* the rest is the actual data */ - value = res->val; - /* terminating zero byte (needed for LONGs) */ - value[value_len] = '\0'; - } - break; - case DB2_FLOAT: - case DB2_DECIMAL: - case DB2_SMALLINT: - case DB2_INTEGER: - case DB2_REAL: - case DB2_DECFLOAT: - case DB2_DOUBLE: { - char* tmp_value = NULL; - - db2Debug3(" DB2_FLOAT, DECIMAL, SMALLINT, INTEGER, REAL, DECFLOAT, DOUBLE"); - value = res->val; - value_len = res->val_len; - value_len = (value_len == 0) ? strlen(value) : value_len; - tmp_value = value; - if((tmp_value = strchr(value,',')) != NULL) { - *tmp_value = '.'; + case DB2_BLOB: + case DB2_CLOB: { + db2Debug3(" DB2_BLOB or DB2CLOB"); + /* for LOBs, get the actual LOB contents (allocated), truncated if desired */ + /* the column index is 1 based, whereas index id 0 based, so always add 1 to index when calling db2GetLob, since it does a column based access*/ + db2GetLob (fdw_state->session, res, &value, &value_len, trunc_lob ? (WIDTH_THRESHOLD + 1) : 0); } + break; + case DB2_LONGVARBINARY: { + db2Debug3(" DB2_LONGBINARY datatypes"); + /* for LONG and LONG RAW, the first 4 bytes contain the length */ + value_len = *((int32*) res->val); + /* the rest is the actual data */ + value = res->val; + /* terminating zero byte (needed for LONGs) */ + value[value_len] = '\0'; + } + break; + case DB2_FLOAT: + case DB2_DECIMAL: + case DB2_SMALLINT: + case DB2_INTEGER: + case DB2_REAL: + case DB2_DECFLOAT: + case DB2_DOUBLE: { + char* tmp_value = NULL; + + db2Debug3(" DB2_FLOAT, DECIMAL, SMALLINT, INTEGER, REAL, DECFLOAT, DOUBLE"); + value = res->val; + value_len = res->val_len; + value_len = (value_len == 0) ? strlen(value) : value_len; + tmp_value = value; + if((tmp_value = strchr(value,',')) != NULL) { + *tmp_value = '.'; + } + } + break; + default: { + db2Debug3(" shoud be string based values"); + /* for other data types, db2Table contains the results */ + value = res->val; + value_len = res->val_len; + value_len = (value_len == 0) ? strlen(value) : value_len; + } + break; } - break; - default: { - db2Debug3(" shoud be string based values"); - /* for other data types, db2Table contains the results */ - value = res->val; - value_len = res->val_len; - value_len = (value_len == 0) ? strlen(value) : value_len; - } - break; - } db2Debug2(" value : %s" , value); db2Debug2(" value_len : %ld" , value_len); /* fill the TupleSlot with the data (after conversion if necessary) */ - if (pgtype == BYTEAOID) { - /* binary columns are not converted */ - bytea* result = (bytea*) db2alloc ("bytea", value_len + VARHDRSZ); - memcpy (VARDATA (result), value, value_len); - SET_VARSIZE (result, value_len + VARHDRSZ); - - values[j] = PointerGetDatum (result); + if (res->pgtype == BYTEAOID) { + /* binary columns are not converted */ + bytea* result = (bytea*) db2alloc ("bytea", value_len + VARHDRSZ); + memcpy (VARDATA (result), value, value_len); + SET_VARSIZE (result, value_len + VARHDRSZ); + + values[j] = PointerGetDatum (result); } else { - regproc typinput; - HeapTuple tuple; - Datum dat; - db2Debug2(" pgtype: %d",pgtype); - /* find the appropriate conversion function */ - tuple = SearchSysCache1 (TYPEOID, ObjectIdGetDatum (pgtype)); - if (!HeapTupleIsValid (tuple)) { - elog (ERROR, "cache lookup failed for type %u", pgtype); - } - typinput = ((Form_pg_type) GETSTRUCT (tuple))->typinput; - ReleaseSysCache (tuple); - dat = CStringGetDatum (value); - db2Debug3(" CStringGetDatum(%s): %d",value, dat); - - /* for string types, check that the data are in the database encoding */ - if (pgtype == BPCHAROID || pgtype == VARCHAROID || pgtype == TEXTOID) { - db2Debug3(" pg_verify_mbstr"); - (void) pg_verify_mbstr (GetDatabaseEncoding (), value, value_len, res->noencerr == NO_ENC_ERR_TRUE); - } - /* call the type input function */ - switch (pgtype) { - case BPCHAROID: - case VARCHAROID: - case TIMESTAMPOID: - case TIMESTAMPTZOID: - case TIMEOID: - case TIMETZOID: - case INTERVALOID: - case NUMERICOID: - /* these functions require the type modifier */ - values[j] = OidFunctionCall3 (typinput, dat, ObjectIdGetDatum (InvalidOid), Int32GetDatum (res->pgtypmod)); - db2Debug3(" OidFunctionCall3 : values[%d]: %d", j, values[j]); - break; - default: - /* the others don't */ - values[j] = OidFunctionCall1 (typinput, dat); - db2Debug3(" OidFunctionCall1 : values[%d]: %d", j, values[j]); - } - } + regproc typinput; + HeapTuple tuple; + Datum dat; + db2Debug2(" pgtype: %d",res->pgtype); + /* find the appropriate conversion function */ + tuple = SearchSysCache1 (TYPEOID, ObjectIdGetDatum (res->pgtype)); + if (!HeapTupleIsValid (tuple)) { + elog (ERROR, "cache lookup failed for type %u", res->pgtype); + } + typinput = ((Form_pg_type) GETSTRUCT (tuple))->typinput; + ReleaseSysCache (tuple); + dat = CStringGetDatum (value); + db2Debug3(" CStringGetDatum(%s): %d",value, dat); + /* for string types, check that the data are in the database encoding */ + if (res->pgtype == BPCHAROID || res->pgtype == VARCHAROID || res->pgtype == TEXTOID) { + db2Debug3(" pg_verify_mbstr"); + (void) pg_verify_mbstr (GetDatabaseEncoding(), value, value_len, res->noencerr == NO_ENC_ERR_TRUE); + } + /* call the type input function */ + switch (res->pgtype) { + case BPCHAROID: + case VARCHAROID: + case TIMESTAMPOID: + case TIMESTAMPTZOID: + case TIMEOID: + case TIMETZOID: + case INTERVALOID: + case NUMERICOID: + /* these functions require the type modifier */ + values[j] = OidFunctionCall3 (typinput, dat, ObjectIdGetDatum (InvalidOid), Int32GetDatum (res->pgtypmod)); + db2Debug3(" OidFunctionCall3 : values[%d]: %d", j, values[j]); + break; + default: + /* the others don't */ + values[j] = OidFunctionCall1 (typinput, dat); + db2Debug3(" OidFunctionCall1 : values[%d]: %d", j, values[j]); + } + } /* release the data buffer for LOBs */ db2Type = c2dbType(res->colType); if (db2Type == DB2_BLOB || db2Type == DB2_CLOB) { From 31dd92e1e221f937d9c073d33e9cded75422539a Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Tue, 10 Feb 2026 17:19:55 +0100 Subject: [PATCH 041/120] started to set resnum correctly, but on foreignjoinpaths the way the foreignrel->reltarget-expr is populated in reverse to the query sequence --- source/db2GetForeignPlan.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/source/db2GetForeignPlan.c b/source/db2GetForeignPlan.c index 4caf83a..a338885 100644 --- a/source/db2GetForeignPlan.c +++ b/source/db2GetForeignPlan.c @@ -67,6 +67,7 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo if (IS_SIMPLE_REL(foreignrel)) { ListCell* cell = NULL; + int resnum = 0; /* For base relations, set scan_relid as the relid of the relation. */ scan_relid = foreignrel->relid; @@ -79,17 +80,16 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo DB2ResultColumn* resCol = (DB2ResultColumn*)db2alloc("resultColumn",sizeof(DB2ResultColumn)); resCol->next = fpinfo->resultList; fpinfo->resultList = resCol; + resCol->resnum = ++resnum; getUsedColumns ((Expr*) lfirst (cell), foreignrel, resCol); + db2Debug3(" column %s added to result list", resCol->colName); } /* examine each condition for Var nodes */ db2Debug3(" size of conditions: %d", list_length(foreignrel->baserestrictinfo)); foreach (cell, foreignrel->baserestrictinfo) { db2Debug3(" examine condition"); - DB2ResultColumn* resCol = (DB2ResultColumn*)db2alloc("resultColumn",sizeof(DB2ResultColumn)); - resCol->next = fpinfo->resultList; - fpinfo->resultList = resCol; - getUsedColumns ((Expr*) lfirst (cell), foreignrel, resCol); + getUsedColumns ((Expr*) lfirst (cell), foreignrel, NULL); } /* In a base-relation scan, we must apply the given scan_clauses. @@ -146,6 +146,7 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo fdw_scan_tlist = build_tlist_to_deparse(foreignrel); if (list_length(fdw_scan_tlist) > 0) { ListCell* cell = NULL; + int resnum = 0; /* examine each condition for Tlist nodes */ db2Debug3(" size of tlist: %d", list_length(fdw_scan_tlist)); @@ -154,6 +155,7 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo DB2ResultColumn* resCol = (DB2ResultColumn*)db2alloc("resultColumn",sizeof(DB2ResultColumn)); resCol->next = fpinfo->resultList; fpinfo->resultList = resCol; + resCol->resnum = ++resnum; getUsedColumns ((Expr*) lfirst (cell), foreignrel, resCol); } } @@ -488,7 +490,7 @@ static void getUsedColumns (Expr* expr, RelOptInfo* foreignrel, DB2ResultColumn* static void addResult(DB2ResultColumn* resCol, DB2Column* column) { db2Debug1("> %s::addResult",__FILE__); - if (resCol->colName == NULL) { + if (resCol && resCol->colName == NULL) { resCol->colName = db2strdup(column->colName); resCol->colType = column->colType; resCol->colSize = column->colSize; @@ -509,8 +511,6 @@ static void addResult(DB2ResultColumn* resCol, DB2Column* column) { resCol->val_null = column->val_null; resCol->varno = column->varno; resCol->noencerr = column->noencerr; - } else { - db2Debug2(" column %s already in result list", column->colName); } db2Debug1("< %s::addResult",__FILE__); } \ No newline at end of file From d1cf6c7bf9e70c5c4fbc1279e06fe6c796a984b0 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Wed, 11 Feb 2026 14:54:20 +0100 Subject: [PATCH 042/120] enhanced Debug Traces --- source/db2GetForeignUpperPaths.c | 53 +++++++++++++++++++++++++++----- source/db2_deparse.c | 2 +- 2 files changed, 46 insertions(+), 9 deletions(-) diff --git a/source/db2GetForeignUpperPaths.c b/source/db2GetForeignUpperPaths.c index ba6c97a..868f2ec 100644 --- a/source/db2GetForeignUpperPaths.c +++ b/source/db2GetForeignUpperPaths.c @@ -370,6 +370,9 @@ static void add_foreign_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel, int disabled_nodes; Cost startup_cost; Cost total_cost; + + db2Debug1("> %s::add_foreign_grouping_paths", __FILE__); + /* Nothing to be done, if there is no grouping or aggregation required. */ if (!parse->groupClause && !parse->groupingSets && !parse->hasAggs && !root->hasHavingQual) return; @@ -426,6 +429,7 @@ static void add_foreign_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel, , NIL); /* no fdw_private */ /* Add generated path into grouped_rel by add_path(). */ add_path(grouped_rel, (Path*) grouppath); + db2Debug1("< %s::add_foreign_grouping_paths", __FILE__); } /** add_foreign_ordered_paths @@ -446,6 +450,8 @@ static void add_foreign_ordered_paths(PlannerInfo *root, RelOptInfo *input_rel, ForeignPath* ordered_path; ListCell* lc; + db2Debug1("> %s::add_foreign_ordered_paths", __FILE__); + // Shouldn't get here unless the query has ORDER BY Assert(parse->sortClause); @@ -525,6 +531,7 @@ static void add_foreign_ordered_paths(PlannerInfo *root, RelOptInfo *input_rel, , fdw_private); /* and add it to the ordered_rel */ add_path(ordered_rel, (Path *) ordered_path); + db2Debug1("< %s::add_foreign_ordered_paths", __FILE__); } /** add_foreign_final_paths @@ -547,6 +554,8 @@ static void add_foreign_final_paths(PlannerInfo *root, RelOptInfo *input_rel, Re List* fdw_private = NIL; ForeignPath* final_path = NULL; + db2Debug1("> %s::add_foreign_ordered_paths", __FILE__); + /** Currently, we only support this for SELECT commands */ if (parse->commandType != CMD_SELECT) return; @@ -615,6 +624,7 @@ static void add_foreign_final_paths(PlannerInfo *root, RelOptInfo *input_rel, Re /* Safe to push down */ fpinfo->pushdown_safe = true; + db2Debug1("< %s::add_foreign_ordered_paths", __FILE__); return; } } @@ -622,6 +632,7 @@ static void add_foreign_final_paths(PlannerInfo *root, RelOptInfo *input_rel, Re /* If we get here it means no ForeignPaths; since we would already have considered pushing down all operations for the query to the * remote server, give up on it. */ + db2Debug1("< %s::add_foreign_ordered_paths", __FILE__); return; } @@ -646,8 +657,10 @@ static void add_foreign_final_paths(PlannerInfo *root, RelOptInfo *input_rel, Re /* Assess if it is safe to push down the LIMIT and OFFSET to the remote server */ /* If the underlying relation has any local conditions, the LIMIT/OFFSET cannot be pushed down. */ - if (ifpinfo->local_conds) + if (ifpinfo->local_conds) { + db2Debug1("< %s::add_foreign_ordered_paths", __FILE__); return; + } /* If the query has FETCH FIRST .. WITH TIES, 1) it must have ORDER BY as well, which is used to determine which additional rows tie for the last * place in the result set, and 2) ORDER BY must already have been determined to be safe to push down before we get here. @@ -655,12 +668,15 @@ static void add_foreign_final_paths(PlannerInfo *root, RelOptInfo *input_rel, Re * entirely for lack of support for it. * Since we do not currently have a way to do a remote-version check (without accessing the remote server), disable pushing the FETCH clause for now. */ - if (parse->limitOption == LIMIT_OPTION_WITH_TIES) + if (parse->limitOption == LIMIT_OPTION_WITH_TIES) { + db2Debug1("< %s::add_foreign_ordered_paths", __FILE__); return; - + } /* Also, the LIMIT/OFFSET cannot be pushed down, if their expressions are not safe to remote. */ - if (!is_foreign_expr(root, input_rel, (Expr *) parse->limitOffset) || !is_foreign_expr(root, input_rel, (Expr *) parse->limitCount)) + if (!is_foreign_expr(root, input_rel, (Expr *) parse->limitOffset) || !is_foreign_expr(root, input_rel, (Expr *) parse->limitCount)) { + db2Debug1("< %s::add_foreign_ordered_paths", __FILE__); return; + } /* Safe to push down */ fpinfo->pushdown_safe = true; @@ -705,10 +721,12 @@ static void add_foreign_final_paths(PlannerInfo *root, RelOptInfo *input_rel, Re /* and add it to the final_rel */ add_path(final_rel, (Path*) final_path); + db2Debug1(" %s::add_foreign_ordered_paths", __FILE__); } /* Adjust the cost estimates of a foreign grouping path to include the cost of generating properly-sorted output. */ static void adjust_foreign_grouping_path_cost(PlannerInfo* root, List* pathkeys, double retrieved_rows, double width, double limit_tuples, int* p_disabled_nodes, Cost* p_startup_cost, Cost* p_run_cost) { + db2Debug1("> %s::adjust_foreign_grouping_path_cost", __FILE__); /* If the GROUP BY clause isn't sort-able, the plan chosen by the remote side is unlikely to generate properly-sorted output, so it would need * an explicit sort; adjust the given costs with cost_sort(). * Likewise, if the GROUP BY clause is sort-able but isn't a superset of the given pathkeys, adjust the costs with that function. @@ -728,6 +746,7 @@ static void adjust_foreign_grouping_path_cost(PlannerInfo* root, List* pathkeys, *p_startup_cost *= sort_multiplier; *p_run_cost *= sort_multiplier; } + db2Debug1("< %s::adjust_foreign_grouping_path_cost", __FILE__); } /* @@ -744,6 +763,7 @@ static bool foreign_grouping_ok(PlannerInfo* root, RelOptInfo* grouped_rel, Node int i = 0; List* tlist = NIL; + db2Debug1("> %s::foreign_grouping_ok", __FILE__); /* We currently don't support pushing Grouping Sets. */ if (query->groupingSets) return false; @@ -755,8 +775,11 @@ static bool foreign_grouping_ok(PlannerInfo* root, RelOptInfo* grouped_rel, Node * are required to be applied before performing aggregation. Hence the * aggregate cannot be pushed down. */ - if (ofpinfo->local_conds) + if (ofpinfo->local_conds) { + db2Debug2(" foreign_grouping_ok: local_conds found"); + db2Debug1("< %s::foreign_grouping_ok", __FILE__); return false; + } /** Examine grouping expressions, as well as other expressions we'd need to * compute, and check whether they are safe to push down to the foreign @@ -789,14 +812,20 @@ static bool foreign_grouping_ok(PlannerInfo* root, RelOptInfo* grouped_rel, Node /** If any GROUP BY expression is not shippable, then we cannot * push down aggregation to the foreign server. */ - if (!is_foreign_expr(root, grouped_rel, expr)) + if (!is_foreign_expr(root, grouped_rel, expr)) { + db2Debug2(" foreign_grouping_ok: non-foreign expr found"); + db2Debug1("< %s::foreign_grouping_ok", __FILE__); return false; + } /** If it would be a foreign param, we can't put it into the tlist, * so we have to fail. */ - if (is_foreign_param(root, grouped_rel, expr)) + if (is_foreign_param(root, grouped_rel, expr)) { + db2Debug2(" foreign_grouping_ok: foreign param found"); + db2Debug1("< %s::foreign_grouping_ok", __FILE__); return false; + } /** Pushable, so add to tlist. We need to create a TLE for this * expression and apply the sortgroupref to it. We cannot use @@ -824,8 +853,11 @@ static bool foreign_grouping_ok(PlannerInfo* root, RelOptInfo* grouped_rel, Node * don't have to check is_foreign_param, since that certainly * won't return true for any such expression.) */ - if (!is_foreign_expr(root, grouped_rel, (Expr *) aggvars)) + if (!is_foreign_expr(root, grouped_rel, (Expr *) aggvars)) { + db2Debug2(" foreign_grouping_ok: non-foreign aggvar found"); + db2Debug1("< %s::foreign_grouping_ok", __FILE__); return false; + } /** Add aggregates, if any, into the targetlist. Plain Vars * outside an aggregate can be ignored, because they should be @@ -917,6 +949,7 @@ static bool foreign_grouping_ok(PlannerInfo* root, RelOptInfo* grouped_rel, Node * postgresExplainForeignScan. */ fpinfo->relation_name = psprintf("Aggregate on (%s)", ofpinfo->relation_name); + db2Debug1("< %s::foreign_grouping_ok", __FILE__); return true; } @@ -942,6 +975,7 @@ void estimate_path_cost_size(PlannerInfo* root, RelOptInfo* foreignrel, List* pa Cost startup_cost; Cost total_cost; + db2Debug1("> %s::estimate_path_cost_size", __FILE__); /* Make sure the core code has set up the relation's reltarget */ Assert(foreignrel->reltarget); @@ -1304,6 +1338,7 @@ void estimate_path_cost_size(PlannerInfo* root, RelOptInfo* foreignrel, List* pa *p_disabled_nodes = disabled_nodes; *p_startup_cost = startup_cost; *p_total_cost = total_cost; + db2Debug1("< %s::estimate_path_cost_size", __FILE__); } /** Merge FDW options from input relations into a new set of options for a join or an upper rel. @@ -1314,6 +1349,7 @@ void estimate_path_cost_size(PlannerInfo* root, RelOptInfo* foreignrel, List* pa * expected to NULL. */ static void merge_fdw_options(DB2FdwState* fpinfo, const DB2FdwState* fpinfo_o, const DB2FdwState* fpinfo_i) { + db2Debug1("> %s::merge_fdw_options", __FILE__); /* We must always have fpinfo_o. */ Assert(fpinfo_o); @@ -1349,4 +1385,5 @@ static void merge_fdw_options(DB2FdwState* fpinfo, const DB2FdwState* fpinfo_o, */ fpinfo->async_capable = fpinfo_o->async_capable || fpinfo_i->async_capable; } + db2Debug1("< %s::merge_fdw_options", __FILE__); } diff --git a/source/db2_deparse.c b/source/db2_deparse.c index 8798b03..26ae713 100644 --- a/source/db2_deparse.c +++ b/source/db2_deparse.c @@ -3465,7 +3465,7 @@ static void deparseTargetList(StringInfo buf, RangeTblEntry *rte, Index rtindex, /* Don't generate bad syntax if no undropped columns */ if (first && !is_returning) appendStringInfoString(buf, "NULL"); -db2Debug1("> %s::deparseTargetList : %s",__FILE__, buf->data); + db2Debug1("> %s::deparseTargetList : %s",__FILE__, buf->data); } /** Deparse given targetlist and append it to context->buf. From a95d209ba463bf1549388127be7652bf7bc50a95 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Wed, 11 Feb 2026 14:55:23 +0100 Subject: [PATCH 043/120] ensure the result columns are correctly sorted as mentioned in the query that is sent to the foreign server --- source/db2GetForeignPlan.c | 44 ++++++++++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/source/db2GetForeignPlan.c b/source/db2GetForeignPlan.c index a338885..a9bdccd 100644 --- a/source/db2GetForeignPlan.c +++ b/source/db2GetForeignPlan.c @@ -38,6 +38,7 @@ extern List* serializePlanData (DB2FdwState* fdw_state); ForeignScan* db2GetForeignPlan (PlannerInfo* root, RelOptInfo* foreignrel, Oid foreigntableid, ForeignPath* best_path, List* tlist, List* scan_clauses , Plan* outer_plan); static void getUsedColumns (Expr* expr, RelOptInfo* foreignrel, DB2ResultColumn* resCol); static void addResult (DB2ResultColumn* resCol, DB2Column* db2Column); +static int compareResultColumns (const void* a, const void* b); /* postgresGetForeignPlan * Create ForeignScan plan node which implements selected best path @@ -75,16 +76,25 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo /* find all the columns to include in the select list */ /* examine each SELECT list entry for Var nodes */ db2Debug3(" size of columnlist: %d", list_length(foreignrel->reltarget->exprs)); + DB2ResultColumn** cols = (DB2ResultColumn**)db2alloc("resultColumns", list_length(foreignrel->reltarget->exprs) * sizeof(DB2ResultColumn*)); foreach (cell, foreignrel->reltarget->exprs) { - db2Debug3(" examine column"); - DB2ResultColumn* resCol = (DB2ResultColumn*)db2alloc("resultColumn",sizeof(DB2ResultColumn)); - resCol->next = fpinfo->resultList; - fpinfo->resultList = resCol; - resCol->resnum = ++resnum; - getUsedColumns ((Expr*) lfirst (cell), foreignrel, resCol); - db2Debug3(" column %s added to result list", resCol->colName); + db2Debug3(" examine column %d", resnum); + cols[resnum] = (DB2ResultColumn*)db2alloc("resultColumn",sizeof(DB2ResultColumn)); + getUsedColumns ((Expr*) lfirst (cell), foreignrel, cols[resnum]); + db2Debug3(" cols[%d]->colName %s added to result list", resnum, cols[resnum]->colName); + resnum++; + } + // sort the array in ascending order of the pgattnum, so that we can compare it with the order of columns in the foreign table + db2Debug3(" sorting result columns by pgattnum"); + qsort(cols, list_length(foreignrel->reltarget->exprs), sizeof(DB2ResultColumn*), compareResultColumns); + // generate the sorted array into the resultList + for (int idx = 0; idx < list_length(foreignrel->reltarget->exprs); idx++) { + db2Debug3(" result column %d: %s", idx, cols[idx]->colName); + cols[idx]->next = fpinfo->resultList; + cols[idx]->resnum = idx+1; + db2Debug3(" column %s added to result list with resnum %d", cols[idx]->colName, cols[idx]->resnum); + fpinfo->resultList = cols[idx]; } - /* examine each condition for Var nodes */ db2Debug3(" size of conditions: %d", list_length(foreignrel->baserestrictinfo)); foreach (cell, foreignrel->baserestrictinfo) { @@ -146,17 +156,18 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo fdw_scan_tlist = build_tlist_to_deparse(foreignrel); if (list_length(fdw_scan_tlist) > 0) { ListCell* cell = NULL; - int resnum = 0; + int resnum = 1; - /* examine each condition for Tlist nodes */ + /* examine each condition for Tlist nodes; they come in the correct sequence as in the query and do not need to be sorted */ db2Debug3(" size of tlist: %d", list_length(fdw_scan_tlist)); foreach (cell, fdw_scan_tlist) { db2Debug3(" examine tlist"); DB2ResultColumn* resCol = (DB2ResultColumn*)db2alloc("resultColumn",sizeof(DB2ResultColumn)); resCol->next = fpinfo->resultList; fpinfo->resultList = resCol; - resCol->resnum = ++resnum; + resCol->resnum = resnum; getUsedColumns ((Expr*) lfirst (cell), foreignrel, resCol); + resnum++; } } @@ -513,4 +524,15 @@ static void addResult(DB2ResultColumn* resCol, DB2Column* column) { resCol->noencerr = column->noencerr; } db2Debug1("< %s::addResult",__FILE__); +} + +static int compareResultColumns(const void* a, const void* b) { + int result = 0; + db2Debug1("> %s::compareResultColumns",__FILE__); + DB2ResultColumn* colA = *(DB2ResultColumn**) a; + DB2ResultColumn* colB = *(DB2ResultColumn**) b; + result = (colA->pgattnum - colB->pgattnum); + db2Debug2(" comparing %s -> pgattnum %d and %s -> pgattnum %d, result = %d", colA->colName, colA->pgattnum, colB->colName, colB->pgattnum, result); + db2Debug1("< %s::compareResultColumns: %d",__FILE__, result); + return result; } \ No newline at end of file From 2751024c04a42e59ddd2b4e952be0f8fede9141a Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Wed, 11 Feb 2026 17:10:01 +0100 Subject: [PATCH 044/120] use the correct db2PrepareQuery Prototype --- source/db2AnalyzeForeignTable.c | 9 +++++---- source/db2BeginForeignModify.c | 2 +- source/db2BeginForeignModifyCommon.c | 4 ++-- source/db2ExecForeignTruncate.c | 1 - 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/source/db2AnalyzeForeignTable.c b/source/db2AnalyzeForeignTable.c index 7bde7ee..02d6429 100644 --- a/source/db2AnalyzeForeignTable.c +++ b/source/db2AnalyzeForeignTable.c @@ -11,7 +11,7 @@ /** external prototypes */ extern DB2FdwState* db2GetFdwState (Oid foreigntableid, double* sample_percent, bool describe); extern int db2IsStatementOpen (DB2Session* session); -extern void db2PrepareQuery (DB2Session* session, const char *query, DB2Table* db2Table, unsigned long prefetch, int fetchsize); +extern void db2PrepareQuery (DB2Session* session, const char *query, DB2ResultColumn* resultList, unsigned long prefetch, int fetchsize); extern int db2ExecuteQuery (DB2Session* session, const DB2Table* db2Table, ParamDesc* paramList); extern int db2FetchNext (DB2Session* session); extern void checkDataType (short db2type, int scale, Oid pgtype, const char* tablename, const char* colname); @@ -43,7 +43,7 @@ bool db2AnalyzeForeignTable (Relation relation, AcquireSampleRowsFunc* func, Blo * All LOB values are truncated to WIDTH_THRESHOLD+1 because anything * exceeding this is not used by compute_scalar_stats(). */ -int acquireSampleRowsFunc (Relation relation, int elevel, HeapTuple * rows, int targrows, double *totalrows, double *totaldeadrows) { +int acquireSampleRowsFunc (Relation relation, int elevel, HeapTuple* rows, int targrows, double* totalrows, double* totaldeadrows) { int collected_rows = 0, i; DB2FdwState* fdw_state; bool first_column = true; @@ -126,7 +126,8 @@ int acquireSampleRowsFunc (Relation relation, int elevel, HeapTuple * rows, int db2Debug3(" loop through query results"); /* loop through query results */ - while (db2IsStatementOpen (fdw_state->session) ? db2FetchNext (fdw_state->session) : (db2PrepareQuery (fdw_state->session, fdw_state->query, fdw_state->db2Table, fdw_state->prefetch, fdw_state->fetch_size), db2ExecuteQuery (fdw_state->session, fdw_state->db2Table, fdw_state->paramList))) { + fdw_state->rowcount = -1; + while (db2IsStatementOpen (fdw_state->session) ? db2FetchNext (fdw_state->session) : (db2PrepareQuery (fdw_state->session, fdw_state->query, fdw_state->resultList, fdw_state->prefetch, fdw_state->fetch_size), db2ExecuteQuery (fdw_state->session, fdw_state->db2Table, fdw_state->paramList))) { /* allow user to interrupt ANALYZE */ #if PG_VERSION_NUM >= 180000 vacuum_delay_point (true); @@ -171,7 +172,7 @@ int acquireSampleRowsFunc (Relation relation, int elevel, HeapTuple * rows, int *totaldeadrows = 0; /* report report */ - ereport (elevel, (errmsg ("\"%s\": table contains %lu rows; %d rows in sample", RelationGetRelationName (relation), fdw_state->rowcount, collected_rows))); + ereport (elevel, (errmsg ("\"%s\": table contains %lu rows; %d rows in sample", RelationGetRelationName (relation), fdw_state->rowcount, collected_rows-1))); db2Debug1("< acquireSampleRowsFunc"); return collected_rows; diff --git a/source/db2BeginForeignModify.c b/source/db2BeginForeignModify.c index 716342e..95b0268 100644 --- a/source/db2BeginForeignModify.c +++ b/source/db2BeginForeignModify.c @@ -20,7 +20,7 @@ void db2BeginForeignModify(ModifyTableState* mtstate, ResultRelInfo* rin * the parameters are fetched, and the column numbers of the * resjunk attributes are stored in the "pkey" field. */ -void db2BeginForeignModify (ModifyTableState * mtstate, ResultRelInfo * rinfo, List * fdw_private, int subplan_index, int eflags) { +void db2BeginForeignModify (ModifyTableState* mtstate, ResultRelInfo* rinfo, List* fdw_private, int subplan_index, int eflags) { DB2FdwState* fdw_state = deserializePlanData (fdw_private); Plan *subplan = NULL; diff --git a/source/db2BeginForeignModifyCommon.c b/source/db2BeginForeignModifyCommon.c index 65fab24..1d84e3a 100644 --- a/source/db2BeginForeignModifyCommon.c +++ b/source/db2BeginForeignModifyCommon.c @@ -15,7 +15,7 @@ extern regproc* output_funcs; /** external prototypes */ extern DB2Session* db2GetSession (const char* connectstring, char* user, char* password, char* jwt_token, const char* nls_lang, int curlevel); -extern void db2PrepareQuery (DB2Session* session, const char *query, DB2Table* db2Table, unsigned long prefetch, int fetchsize); +extern void db2PrepareQuery (DB2Session* session, const char *query, DB2ResultColumn* db2ResultList, unsigned long prefetch, int fetchsize); extern void db2Debug1 (const char* message, ...); extern void* db2alloc (const char* type, size_t size); @@ -33,7 +33,7 @@ void db2BeginForeignModifyCommon(ModifyTableState* mtstate, ResultRelInfo* rinfo /* connect to DB2 database */ fdw_state->session = db2GetSession(fdw_state->dbserver, fdw_state->user, fdw_state->password, fdw_state->jwt_token, fdw_state->nls_lang, GetCurrentTransactionNestLevel()); - db2PrepareQuery(fdw_state->session, fdw_state->query, fdw_state->db2Table,fdw_state->prefetch,fdw_state->fetch_size); + db2PrepareQuery(fdw_state->session, fdw_state->query, fdw_state->resultList,fdw_state->prefetch,fdw_state->fetch_size); /* get the type output functions for the parameters */ output_funcs = (regproc*) db2alloc("output_funcs", fdw_state->db2Table->ncols * sizeof(regproc *)); diff --git a/source/db2ExecForeignTruncate.c b/source/db2ExecForeignTruncate.c index 8a5f835..df147b4 100644 --- a/source/db2ExecForeignTruncate.c +++ b/source/db2ExecForeignTruncate.c @@ -10,7 +10,6 @@ /** external prototypes */ extern DB2FdwState* db2GetFdwState (Oid foreigntableid, double* sample_percent, bool drescribe); extern DB2Session* db2GetSession (const char* connectstring, char* user, char* password, char* jwt_token, const char* nls_lang, int curlevel); -extern void db2PrepareQuery (DB2Session* session, const char* query, DB2Table* db2Table, unsigned long prefetch); extern void db2Debug1 (const char* message, ...); extern void db2Debug2 (const char* message, ...); extern void db2Debug3 (const char* message, ...); From 9e35a040a0072e0c96d17263485eb1f151883334 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Thu, 12 Feb 2026 13:11:54 +0100 Subject: [PATCH 045/120] cleanup the code formatting --- source/db2BeginForeignInsert.c | 109 +++++++++++++++------------------ 1 file changed, 49 insertions(+), 60 deletions(-) diff --git a/source/db2BeginForeignInsert.c b/source/db2BeginForeignInsert.c index e20ebe2..7842cef 100644 --- a/source/db2BeginForeignInsert.c +++ b/source/db2BeginForeignInsert.c @@ -25,70 +25,59 @@ void db2BeginForeignInsert (ModifyTableState* mtstate, Resul DB2FdwState* db2BuildInsertFdwState (Relation rel); void db2BeginForeignInsert(ModifyTableState* mtstate, ResultRelInfo* rinfo) { - Relation rel = rinfo->ri_RelationDesc; - DB2FdwState *fdw_state = NULL; - db2Debug1("> db2BeginForeignInsert"); - fdw_state = db2BuildInsertFdwState(rel); - /* subplan is irrelevant for pure INSERT/COPY */ - db2BeginForeignModifyCommon(mtstate, rinfo, fdw_state, NULL); - db2Debug1("< db2BeginForeignInsert"); + Relation rel = rinfo->ri_RelationDesc; + DB2FdwState* fdw_state = NULL; + + db2Debug1("> db2BeginForeignInsert"); + fdw_state = db2BuildInsertFdwState(rel); + /* subplan is irrelevant for pure INSERT/COPY */ + db2BeginForeignModifyCommon(mtstate, rinfo, fdw_state, NULL); + db2Debug1("< db2BeginForeignInsert"); } DB2FdwState* db2BuildInsertFdwState(Relation rel) { - DB2FdwState *fdwState; - StringInfoData sql; - int i; - bool firstcol; - db2Debug1("> db2BuildInsertFdwState"); - - /* Same logic as CMD_INSERT branch of db2PlanForeignModify: */ - fdwState = db2GetFdwState(RelationGetRelid(rel), NULL, true); - initStringInfo(&sql); - - appendStringInfo(&sql, "INSERT INTO %s (", fdwState->db2Table->name); - firstcol = true; - for (i = 0; i < fdwState->db2Table->ncols; ++i) - { - if (fdwState->db2Table->cols[i]->pgname == NULL) - continue; - - if (!firstcol) - appendStringInfo(&sql, ", "); - else - firstcol = false; - - appendStringInfo(&sql, "%s", fdwState->db2Table->cols[i]->colName); - } - appendStringInfo(&sql, ") VALUES ("); - - firstcol = true; - for (i = 0; i < fdwState->db2Table->ncols; ++i) - { - if (fdwState->db2Table->cols[i]->pgname == NULL) - continue; - - checkDataType(fdwState->db2Table->cols[i]->colType, - fdwState->db2Table->cols[i]->colScale, - fdwState->db2Table->cols[i]->pgtype, - fdwState->db2Table->pgname, - fdwState->db2Table->cols[i]->pgname); - - addParam(&fdwState->paramList, - fdwState->db2Table->cols[i]->pgtype, - fdwState->db2Table->cols[i]->colType, - i, - 0); - - if (!firstcol) - appendStringInfo(&sql, ", "); - else - firstcol = false; + DB2FdwState* fdwState; + StringInfoData sql; + int i; + bool firstcol; - appendAsType(&sql, fdwState->db2Table->cols[i]->pgtype); + db2Debug1("> db2BuildInsertFdwState"); + /* Same logic as CMD_INSERT branch of db2PlanForeignModify: */ + fdwState = db2GetFdwState(RelationGetRelid(rel), NULL, true); + initStringInfo(&sql); + appendStringInfo(&sql, "INSERT INTO %s (", fdwState->db2Table->name); + firstcol = true; + for (i = 0; i < fdwState->db2Table->ncols; ++i) { + if (fdwState->db2Table->cols[i]->pgname == NULL) { + continue; } - appendStringInfo(&sql, ")"); - fdwState->query = sql.data; - db2Debug2(" fdwState->query: '%s'",sql.data); - db2Debug1("< db2BuildInsertFdwState - returns fdwState: %x",fdwState); + appendStringInfo(&sql, "%s%s", (firstcol) ? "" : ", ", fdwState->db2Table->cols[i]->colName); + firstcol = false; + } + appendStringInfo(&sql, ") VALUES ("); + firstcol = true; + for (i = 0; i < fdwState->db2Table->ncols; ++i) { + if (fdwState->db2Table->cols[i]->pgname == NULL) + continue; + checkDataType(fdwState->db2Table->cols[i]->colType, + fdwState->db2Table->cols[i]->colScale, + fdwState->db2Table->cols[i]->pgtype, + fdwState->db2Table->pgname, + fdwState->db2Table->cols[i]->pgname); + addParam(&fdwState->paramList, + fdwState->db2Table->cols[i]->pgtype, + fdwState->db2Table->cols[i]->colType, + i, + 0); + if (!firstcol) + appendStringInfo(&sql, ", "); + else + firstcol = false; + appendAsType(&sql, fdwState->db2Table->cols[i]->pgtype); + } + appendStringInfo(&sql, ")"); + fdwState->query = sql.data; + db2Debug2(" fdwState->query: '%s'",sql.data); + db2Debug1("< db2BuildInsertFdwState - returns fdwState: %x",fdwState); return fdwState; } \ No newline at end of file From 106982106f8f6abb7e06bf3610349aaaec7c75e1 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Thu, 12 Feb 2026 16:07:29 +0100 Subject: [PATCH 046/120] cleanup code --- include/db2_fdw.h | 1 - source/db2BeginForeignInsert.c | 2 - source/db2BeginForeignScan.c | 83 ------------------------------ source/db2ExecForeignBatchInsert.c | 16 ++---- source/db2ExecForeignDelete.c | 4 -- source/db2ExecForeignInsert.c | 2 - source/db2ExecForeignUpdate.c | 2 - source/db2PlanForeignModify.c | 4 -- source/db2PrepareQuery.c | 74 ++++---------------------- source/db2ReAllocFree.c | 1 - source/db2_fdw_utils.c | 3 +- 11 files changed, 14 insertions(+), 178 deletions(-) diff --git a/include/db2_fdw.h b/include/db2_fdw.h index 54c5c57..8a60c91 100644 --- a/include/db2_fdw.h +++ b/include/db2_fdw.h @@ -23,7 +23,6 @@ #endif /* WIDTH_THRESHOLD */ #undef OLD_FDW_API -#define WRITE_API #define IMPORT_API /* array_create_iterator has a new signature from 9.5 on */ diff --git a/source/db2BeginForeignInsert.c b/source/db2BeginForeignInsert.c index 7842cef..759c965 100644 --- a/source/db2BeginForeignInsert.c +++ b/source/db2BeginForeignInsert.c @@ -11,9 +11,7 @@ /** external prototypes */ extern DB2FdwState* db2GetFdwState (Oid foreigntableid, double* sample_percent, bool describe); -#ifdef WRITE_API extern void addParam (ParamDesc** paramList, Oid pgtype, short colType, int colnum, int txts); -#endif extern void checkDataType (short db2type, int scale, Oid pgtype, const char* tablename, const char* colname); extern void db2Debug1 (const char* message, ...); extern void db2Debug2 (const char* message, ...); diff --git a/source/db2BeginForeignScan.c b/source/db2BeginForeignScan.c index d668c06..46e69fb 100644 --- a/source/db2BeginForeignScan.c +++ b/source/db2BeginForeignScan.c @@ -37,7 +37,6 @@ void db2BeginForeignScan(ForeignScanState* node, int eflags) { node->fdw_state = (void *) fdw_state; paramCount = addExprParams(node); - addTleExprParams(node,paramCount); /* add a fake parameter "if that string appears in the query */ if (strstr (fdw_state->query, "?/*:now*/") != NULL) { @@ -120,85 +119,3 @@ static int addExprParams(ForeignScanState* node){ db2Debug1("< addExprParams : %d", index); return index; } - -static int addTleExprParams(ForeignScanState* node, int paramCount){ - DB2FdwState* fdw_state = node->fdw_state; - ForeignScan* fsplan = (ForeignScan*) node->ss.ps.plan; - List* tle_exprs = fsplan->fdw_scan_tlist; - ParamDesc* paramDesc = NULL; - ListCell* cell = NULL; - int index = 0; - - db2Debug1("> addTleExprParams"); - db2Debug2(" tle_expr: %x[%d]",tle_exprs, list_length(tle_exprs)); - - // create the list of parameters - foreach (cell, tle_exprs) { - Expr* expr = NULL; - TargetEntry* tle = (TargetEntry*) lfirst(cell); - - if (tle == NULL) - continue; - - // count, but skip deleted entries - db2Debug2(" result index: %d",++index); - db2Debug2(" tle->resno: %d", tle->resno); - expr = (Expr*) tle->expr; - if (expr != NULL ) { - db2Debug2(" tle->expr->type: %d", expr->type); -/* - switch(tle->expr->type) { - case T_Aggref: { - Aggref* agg = (Aggref*) expr; - db2Debug2(" agg->aggno : %d", agg->aggno); - db2Debug2(" agg->aggstar : %s", agg->aggstar ? "true" : "false"); - db2Debug2(" agg->aggtype : %d", agg->aggtype); - paramDesc = (ParamDesc*) db2alloc("fdw_state->paramList->next", sizeof (ParamDesc)); - paramDesc->type = agg->aggtype; - paramDesc->bindType = (agg->aggtype == NUMERICOID) ? BIND_NUMBER : BIND_STRING; - paramDesc->value = NULL; - paramDesc->node = agg; - paramDesc->colnum = -1; - paramDesc->txts = 0; - paramDesc->next = fdw_state->paramList; - db2Debug2(" paramDesc->colnum: %d ",paramDesc->colnum); - fdw_state->paramList = paramDesc; - } - break; - default: - continue; // skip non-constant expressions - break; - } -*/ - } -/* - // create a new entry in the parameter list - paramDesc = (ParamDesc*) db2alloc("fdw_state->paramList->next", sizeof (ParamDesc)); - paramDesc->type = exprType ((Node*)(expr->expr)); - - if (paramDesc->type == TEXTOID - || paramDesc->type == VARCHAROID - || paramDesc->type == BPCHAROID - || paramDesc->type == CHAROID - || paramDesc->type == DATEOID - || paramDesc->type == TIMESTAMPOID - || paramDesc->type == TIMESTAMPTZOID - || paramDesc->type == TIMEOID - || paramDesc->type == TIMETZOID) - paramDesc->bindType = BIND_STRING; - else - paramDesc->bindType = BIND_NUMBER; - - paramDesc->value = NULL; - paramDesc->node = expr; - paramDesc->colnum = -1; - paramDesc->txts = 0; - paramDesc->next = fdw_state->paramList; - db2Debug2(" paramDesc->colnum: %d ",paramDesc->colnum); - fdw_state->paramList = paramDesc; -*/ - } - - db2Debug1("< addTleExprParams : %d", index); - return index; -} \ No newline at end of file diff --git a/source/db2ExecForeignBatchInsert.c b/source/db2ExecForeignBatchInsert.c index 0da1e3c..90d7a8f 100644 --- a/source/db2ExecForeignBatchInsert.c +++ b/source/db2ExecForeignBatchInsert.c @@ -12,25 +12,17 @@ extern TupleTableSlot* db2ExecForeignInsert (EState* estate, ResultRelInfo* /** local prototypes */ TupleTableSlot** db2ExecForeignBatchInsert (EState *estate, ResultRelInfo *rinfo, TupleTableSlot **slots, TupleTableSlot **planSlots, int *numSlots); -/* - * db2ExecForeignBatchInsert - * +/* db2ExecForeignBatchInsert * Called when the executor wants to insert multiple rows in one go. * For now we just loop and reuse db2ExecForeignInsert for each slot. * - * The executor expects the returned array to point to slots containing - * the inserted rows (or RETURNING results). We simply reuse the input - * slots array. + * The executor expects the returned array to point to slots containing the inserted rows (or RETURNING results). We simply reuse the input slots array. */ TupleTableSlot ** db2ExecForeignBatchInsert(EState *estate, ResultRelInfo *rinfo, TupleTableSlot **slots, TupleTableSlot **planSlots, int *numSlots) { int i; db2Debug1("> db2ExecForeignBatchInsert"); - /* - * According to the FDW API, this is *not* used when there is - * a RETURNING clause, so normally these inserts don't need to - * produce a result tuple. However, to be safe and to match the - * ExecForeignInsert semantics, we just call db2ExecForeignInsert - * and keep its results in the same slots. + /* According to the FDW API, this is *not* used when there is a RETURNING clause, so normally these inserts don't need to produce a result tuple. + * However, to be safe and to match the ExecForeignInsert semantics, we just call db2ExecForeignInsert and keep its results in the same slots. */ for (i = 0; i < *numSlots; i++) { if (slots[i] == NULL) diff --git a/source/db2ExecForeignDelete.c b/source/db2ExecForeignDelete.c index 9936d4f..80ba1e7 100644 --- a/source/db2ExecForeignDelete.c +++ b/source/db2ExecForeignDelete.c @@ -21,9 +21,7 @@ extern void* db2alloc (const char* type, size_t size) /** local prototypes */ TupleTableSlot* db2ExecForeignDelete (EState* estate, ResultRelInfo* rinfo, TupleTableSlot* slot, TupleTableSlot* planSlot); -#ifdef WRITE_API void setModifyParameters (ParamDesc* paramList, TupleTableSlot* newslot, TupleTableSlot* oldslot, DB2Table* db2Table, DB2Session* session); -#endif /** db2ExecForeignDelete * Set the parameter values from the slots and execute the DELETE statement. @@ -72,7 +70,6 @@ TupleTableSlot* db2ExecForeignDelete (EState* estate, ResultRelInfo* rinfo, Tupl return slot; } -#ifdef WRITE_API /** setModifyParameters * Set the parameter values from the values in the slots. * "newslot" contains the new values, "oldslot" the old ones. @@ -169,4 +166,3 @@ void setModifyParameters (ParamDesc *paramList, TupleTableSlot * newslot, TupleT } db2Debug1("< setModifyParameters"); } -#endif /* WRITE_API */ diff --git a/source/db2ExecForeignInsert.c b/source/db2ExecForeignInsert.c index c92f672..b084fe6 100644 --- a/source/db2ExecForeignInsert.c +++ b/source/db2ExecForeignInsert.c @@ -13,9 +13,7 @@ extern bool dml_in_transaction; /** external prototypes */ extern int db2ExecuteInsert (DB2Session* session, const DB2Table* db2Table, ParamDesc* paramList); extern void db2Debug1 (const char* message, ...); -#ifdef WRITE_API extern void setModifyParameters (ParamDesc* paramList, TupleTableSlot* newslot, TupleTableSlot* oldslot, DB2Table* db2Table, DB2Session* session); -#endif extern void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls, bool trunc_lob) ; /** local prototypes */ diff --git a/source/db2ExecForeignUpdate.c b/source/db2ExecForeignUpdate.c index 3509444..cd0570d 100644 --- a/source/db2ExecForeignUpdate.c +++ b/source/db2ExecForeignUpdate.c @@ -14,9 +14,7 @@ extern bool dml_in_transaction; extern int db2ExecuteQuery (DB2Session* session, const DB2Table* db2Table, ParamDesc* paramList); extern void db2Debug1 (const char* message, ...); extern void db2Debug2 (const char* message, ...); -#ifdef WRITE_API extern void setModifyParameters (ParamDesc* paramList, TupleTableSlot* newslot, TupleTableSlot* oldslot, DB2Table* db2Table, DB2Session* session); -#endif extern void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls, bool trunc_lob) ; /** local prototypes */ diff --git a/source/db2PlanForeignModify.c b/source/db2PlanForeignModify.c index 3727f17..a2fcc16 100644 --- a/source/db2PlanForeignModify.c +++ b/source/db2PlanForeignModify.c @@ -22,10 +22,8 @@ extern List* serializePlanData (DB2FdwState* fdwState); /** local prototypes */ List* db2PlanForeignModify(PlannerInfo* root, ModifyTable* plan, Index resultRelation, int subplan_index); -#ifdef WRITE_API static DB2FdwState* copyPlanData (DB2FdwState* orig); void addParam (ParamDesc** paramList, Oid pgtype, short colType, int colnum, int txts); -#endif void checkDataType (short db2type, int scale, Oid pgtype, const char* tablename, const char* colname); /** db2PlanForeignModify @@ -315,7 +313,6 @@ List* db2PlanForeignModify (PlannerInfo* root, ModifyTable* plan, Index resultRe return result; } -#ifdef WRITE_API /** copyPlanData * Create a deep copy of the argument, copy only those fields needed for planning. */ @@ -418,7 +415,6 @@ void addParam (ParamDesc **paramList, Oid pgtype, short colType, int colnum, int *paramList = param; db2Debug1("< addParam"); } -#endif /* WRITE_API */ /** checkDataType * Check that the DB2 data type of a column can be diff --git a/source/db2PrepareQuery.c b/source/db2PrepareQuery.c index 043397d..982cf83 100644 --- a/source/db2PrepareQuery.c +++ b/source/db2PrepareQuery.c @@ -142,7 +142,7 @@ void db2PrepareQuery (DB2Session* session, const char *query, DB2ResultColumn* r db2Debug2(" res->val_size : %ld",res->val_size); db2Debug2(" res->val_len : %d" ,res->val_len); db2Debug2(" res->val_null : %d" ,res->val_null); - db2Debug2(" res->resnum. : %d" ,res->resnum); + db2Debug2(" res->resnum : %d" ,res->resnum); db2Debug2(" fparamType: %d (%s)",fparamType,param2name(fparamType)); db2Debug2(" SQLBindCol(%d,%d,%d(%s),%x,%ld,%x)",session->stmtp->hsql,res->resnum, fparamType, param2name(fparamType), res->val, res->val_size, &res->val_null); rc = SQLBindCol (session->stmtp->hsql,res->resnum, fparamType, res->val, res->val_size, &res->val_null); @@ -153,74 +153,18 @@ void db2PrepareQuery (DB2Session* session, const char *query, DB2ResultColumn* r col_pos++; } -// col_pos = 0; -// for (i = 0; i < db2Table->ncols; ++i) { -// if (db2Table->cols[i]->used) { -// SQLSMALLINT fparamType = c2param((SQLSMALLINT)db2Table->cols[i]->colType); -// /* Unfortunately DB2 handles DML statements with a RETURNING clause quite different from SELECT statements. -// * In the latter, the result columns are "defined", i.e. bound to some storage space. -// * This definition is only necessary once, even if the query is executed multiple times, so we do this here. -// * RETURNING clause are handled in db2ExecuteQuery, here we only allocate locators for LOB columns in RETURNING clauses. -// */ -// /* figure out in which format we want the results */ -// if (db2Table->cols[i]->pgtype == UUIDOID) { -// fparamType = SQL_C_CHAR; -// } -// db2Debug2(" db2Table->cols[%d]->colName : '%s' ",i,db2Table->cols[i]->colName); -// db2Debug2(" db2Table->cols[%d]->colSize : '%ld'",i,db2Table->cols[i]->colSize); -// db2Debug2(" db2Table->cols[%d]->colScale : '%d' ",i,db2Table->cols[i]->colScale); -// db2Debug2(" db2Table->cols[%d]->colNulls : '%d' ",i,db2Table->cols[i]->colNulls); -// db2Debug2(" db2Table->cols[%d]->colChars : '%ld'",i,db2Table->cols[i]->colChars); -// db2Debug2(" db2Table->cols[%d]->colBytes : '%ld'",i,db2Table->cols[i]->colBytes); -// db2Debug2(" db2Table->cols[%d]->colPrimKeyPart: '%d' ",i,db2Table->cols[i]->colPrimKeyPart); -// db2Debug2(" db2Table->cols[%d]->colCodepage : '%d' ",i,db2Table->cols[i]->colCodepage); -// db2Debug2(" db2Table->cols[%d]->val : '%x'" ,i,db2Table->cols[i]->val); -// db2Debug2(" db2Table->cols[%d]->val_size : '%ld'",i,db2Table->cols[i]->val_size); -// db2Debug2(" db2Table->cols[%d]->val_len : '%d' ",i,db2Table->cols[i]->val_len); -// db2Debug2(" db2Table->cols[%d]->val_null : '%d' ",i,db2Table->cols[i]->val_null); -// db2Debug2(" fparamType: %d (%s)",fparamType,param2name(fparamType)); -// ++col_pos; -// db2Debug2(" SQLBindCol(%d,%d,%d(%s),%x,%ld,%x)",session->stmtp->hsql,col_pos, fparamType, param2name(fparamType), db2Table->cols[i]->val, db2Table->cols[i]->val_size, &db2Table->cols[i]->val_null); -// rc = SQLBindCol (session->stmtp->hsql,col_pos, fparamType, db2Table->cols[i]->val, db2Table->cols[i]->val_size, &db2Table->cols[i]->val_null); -// rc = db2CheckErr(rc, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); -// if (rc != SQL_SUCCESS) { -// db2Error_d(FDW_UNABLE_TO_CREATE_EXECUTION, "error executing query: SQLBindCol failed to define result value", db2Message); -// } -// } -// } db2Debug2(" is_select: %s",is_select ? "true" : "false"); db2Debug2(" col_pos: %d",col_pos); if (is_select && col_pos == 0) { -// if (db2Table->rncols > 0) { -// int idx = 0; -// // count the number of comma between SELECT and FROM to understand how many result columns we need. -// for (idx=0; idx < db2Table->rncols - col_pos; idx++) { -// SQLCHAR* dummy_buffer = (SQLCHAR*)db2alloc("dummy_buffer",256); -// SQLLEN* dummy_null = (SQLLEN*)db2alloc("dummy_null",sizeof(SQLLEN)); -// rc = SQLBindCol(session->stmtp->hsql -// , idx+1 -// , SQL_C_CHAR -// , dummy_buffer -// , sizeof(dummy_buffer) -// , dummy_null -// ); -// rc = db2CheckErr(rc, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); -// if (rc != SQL_SUCCESS) { -// db2Error_d ( FDW_UNABLE_TO_CREATE_EXECUTION, "error executing query: SQLBindCol failed to define result value", db2Message); -// } -// } -// } else { - /* No columns selected (i.e., SELECT '1' FROM or COUNT(*)). - * Use persistent buffers from statement handle to avoid stack deallocation issues. - * This fixes the segfault when using aggregate functions without WHERE clause. - */ - rc = SQLBindCol(session->stmtp->hsql, 1, SQL_C_CHAR, session->stmtp->dummy_buffer, sizeof(session->stmtp->dummy_buffer), &session->stmtp->dummy_null); - rc = db2CheckErr(rc, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); - if (rc != SQL_SUCCESS) { - db2Error_d ( FDW_UNABLE_TO_CREATE_EXECUTION, "error executing query: SQLBindCol failed to define result value", db2Message); -// } + /* No columns selected (i.e., SELECT '1' FROM or COUNT(*)). + * Use persistent buffers from statement handle to avoid stack deallocation issues. + * This fixes the segfault when using aggregate functions without WHERE clause. + */ + rc = SQLBindCol(session->stmtp->hsql, 1, SQL_C_CHAR, session->stmtp->dummy_buffer, sizeof(session->stmtp->dummy_buffer), &session->stmtp->dummy_null); + rc = db2CheckErr(rc, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); + if (rc != SQL_SUCCESS) { + db2Error_d ( FDW_UNABLE_TO_CREATE_EXECUTION, "error executing query: SQLBindCol failed to define result value", db2Message); } } - db2Debug1("< db2PrepareQuery"); } diff --git a/source/db2ReAllocFree.c b/source/db2ReAllocFree.c index e6108ff..ec6f787 100644 --- a/source/db2ReAllocFree.c +++ b/source/db2ReAllocFree.c @@ -1,5 +1,4 @@ #include -//#include "db2_pg.h" #include #include #include diff --git a/source/db2_fdw_utils.c b/source/db2_fdw_utils.c index b851336..23cecae 100644 --- a/source/db2_fdw_utils.c +++ b/source/db2_fdw_utils.c @@ -490,5 +490,4 @@ static bool lookup_shippable(Oid objectId, Oid classId, DB2FdwState* fpinfo) { if (OidIsValid(extensionOid) && list_member_oid(fpinfo->shippable_extensions, extensionOid)) return true; return false; -} - +} \ No newline at end of file From 7aacff1ea4902296a105c968ed8e9aa387197ef4 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Thu, 12 Feb 2026 17:05:04 +0100 Subject: [PATCH 047/120] adding relevant binding parameters to ParamDesc so it can be bound without the reference to DB2Column --- include/ParamDesc.h | 4 +++ source/db2AnalyzeForeignTable.c | 4 +-- source/db2BeginForeignInsert.c | 9 +++---- source/db2BindParameter.c | 37 +++++++++++++--------------- source/db2ExecForeignDelete.c | 4 +-- source/db2ExecForeignInsert.c | 4 +-- source/db2ExecForeignUpdate.c | 4 +-- source/db2ExecuteInsert.c | 10 ++++---- source/db2ExecuteQuery.c | 9 +++---- source/db2IterateForeignScan.c | 4 +-- source/db2PlanForeignModify.c | 43 ++++++++++++++++++++------------- source/db2_de_serialize.c | 24 +++++++++++++++--- 12 files changed, 89 insertions(+), 67 deletions(-) diff --git a/include/ParamDesc.h b/include/ParamDesc.h index 02e3a74..ff0533e 100644 --- a/include/ParamDesc.h +++ b/include/ParamDesc.h @@ -8,9 +8,13 @@ * @since 1.0 */ typedef struct paramDesc { + char* colName; // column name in DB2 + short colType; // column data type in DB2 + size_t colSize; // column size Oid type; // PG data type db2BindType bindType; // which type to use for binding to DB2 statement char* value; // value rendered for DB2 + size_t val_size; // size to allocate val with in bytes void* node; // the executable expression int colnum; // corresponding column in DB2Table (-1 in SELECT queries unless output column) int txts; // transaction timestamp diff --git a/source/db2AnalyzeForeignTable.c b/source/db2AnalyzeForeignTable.c index 02d6429..51ab905 100644 --- a/source/db2AnalyzeForeignTable.c +++ b/source/db2AnalyzeForeignTable.c @@ -12,7 +12,7 @@ extern DB2FdwState* db2GetFdwState (Oid foreigntableid, double* sample_percent, bool describe); extern int db2IsStatementOpen (DB2Session* session); extern void db2PrepareQuery (DB2Session* session, const char *query, DB2ResultColumn* resultList, unsigned long prefetch, int fetchsize); -extern int db2ExecuteQuery (DB2Session* session, const DB2Table* db2Table, ParamDesc* paramList); +extern int db2ExecuteQuery (DB2Session* session, ParamDesc* paramList); extern int db2FetchNext (DB2Session* session); extern void checkDataType (short db2type, int scale, Oid pgtype, const char* tablename, const char* colname); extern short c2dbType (short fcType); @@ -127,7 +127,7 @@ int acquireSampleRowsFunc (Relation relation, int elevel, HeapTuple* rows, int t db2Debug3(" loop through query results"); /* loop through query results */ fdw_state->rowcount = -1; - while (db2IsStatementOpen (fdw_state->session) ? db2FetchNext (fdw_state->session) : (db2PrepareQuery (fdw_state->session, fdw_state->query, fdw_state->resultList, fdw_state->prefetch, fdw_state->fetch_size), db2ExecuteQuery (fdw_state->session, fdw_state->db2Table, fdw_state->paramList))) { + while (db2IsStatementOpen (fdw_state->session) ? db2FetchNext (fdw_state->session) : (db2PrepareQuery (fdw_state->session, fdw_state->query, fdw_state->resultList, fdw_state->prefetch, fdw_state->fetch_size), db2ExecuteQuery (fdw_state->session, fdw_state->paramList))) { /* allow user to interrupt ANALYZE */ #if PG_VERSION_NUM >= 180000 vacuum_delay_point (true); diff --git a/source/db2BeginForeignInsert.c b/source/db2BeginForeignInsert.c index 759c965..942e87c 100644 --- a/source/db2BeginForeignInsert.c +++ b/source/db2BeginForeignInsert.c @@ -8,10 +8,11 @@ #include #include "db2_fdw.h" #include "DB2FdwState.h" +#include "DB2Column.h" /** external prototypes */ extern DB2FdwState* db2GetFdwState (Oid foreigntableid, double* sample_percent, bool describe); -extern void addParam (ParamDesc** paramList, Oid pgtype, short colType, int colnum, int txts); +extern void addParam (ParamDesc** paramList, DB2Column* db2col, int colnum, int txts); extern void checkDataType (short db2type, int scale, Oid pgtype, const char* tablename, const char* colname); extern void db2Debug1 (const char* message, ...); extern void db2Debug2 (const char* message, ...); @@ -62,11 +63,7 @@ DB2FdwState* db2BuildInsertFdwState(Relation rel) { fdwState->db2Table->cols[i]->pgtype, fdwState->db2Table->pgname, fdwState->db2Table->cols[i]->pgname); - addParam(&fdwState->paramList, - fdwState->db2Table->cols[i]->pgtype, - fdwState->db2Table->cols[i]->colType, - i, - 0); + addParam(&fdwState->paramList, fdwState->db2Table->cols[i], i, 0); if (!firstcol) appendStringInfo(&sql, ", "); else diff --git a/source/db2BindParameter.c b/source/db2BindParameter.c index 08dc0f0..bd326ec 100644 --- a/source/db2BindParameter.c +++ b/source/db2BindParameter.c @@ -22,9 +22,9 @@ extern void parse2num_struct (const char* s, SQL_NUMERIC_STRUCT* ns) extern char* c2name (short fcType); /** internal prototypes */ -void db2BindParameter (DB2Session* session, const DB2Table* db2Table, ParamDesc* param, SQLLEN* indicator, int param_count, int col_num); +void db2BindParameter (DB2Session* session, ParamDesc* param, SQLLEN* indicator, int param_count, int col_num); -void db2BindParameter (DB2Session* session, const DB2Table* db2Table, ParamDesc* param, SQLLEN* indicator, int param_count, int col_num) { +void db2BindParameter (DB2Session* session, ParamDesc* param, SQLLEN* indicator, int param_count, int col_num) { SQLRETURN rc = 0; db2Debug1("> db2BindParameter"); db2Debug2(" param_count : %d",param_count); @@ -33,15 +33,15 @@ void db2BindParameter (DB2Session* session, const DB2Table* db2Table, ParamDesc* db2Debug2(" param->colnum : %d",param->colnum); db2Debug2(" param->bindType : %d",param->bindType); if (param->colnum >= 0) { - db2Debug2(" colName : %s",db2Table->cols[param->colnum]->colName); + db2Debug2(" colName : %s",param->colName); } switch (param->bindType) { case BIND_NUMBER: { db2Debug3(" param->bindType: BIND_NUMBER"); *indicator = (SQLLEN) ((param->value == NULL) ? SQL_NULL_DATA : 0); db2Debug2(" param_ind : %d",*indicator); - db2Debug2(" colType : %d - %s",db2Table->cols[param->colnum]->colType,c2name(db2Table->cols[param->colnum]->colType)); - switch (db2Table->cols[param->colnum]->colType) { + db2Debug2(" colType : %d - %s",param->colType,c2name(param->colType)); + switch (param->colType) { case SQL_BIGINT:{ char* end = NULL; SQLBIGINT* sqlbint = NULL; @@ -54,7 +54,7 @@ void db2BindParameter (DB2Session* session, const DB2Table* db2Table, ParamDesc* , col_num , SQL_PARAM_INPUT , SQL_C_SBIGINT - , db2Table->cols[param->colnum]->colType + , param->colType , 0 , 0 , sqlbint @@ -75,7 +75,7 @@ void db2BindParameter (DB2Session* session, const DB2Table* db2Table, ParamDesc* , col_num , SQL_PARAM_INPUT , SQL_C_SSHORT - , db2Table->cols[param->colnum]->colType + , param->colType , 0 , 0 , sqlsint @@ -96,7 +96,7 @@ void db2BindParameter (DB2Session* session, const DB2Table* db2Table, ParamDesc* , col_num , SQL_PARAM_INPUT , SQL_C_SLONG - , db2Table->cols[param->colnum]->colType + , param->colType , 0 , 0 , sqlint @@ -121,7 +121,7 @@ void db2BindParameter (DB2Session* session, const DB2Table* db2Table, ParamDesc* , col_num , SQL_PARAM_INPUT , SQL_C_NUMERIC - , db2Table->cols[param->colnum]->colType + , param->colType , num->precision , num->scale , num @@ -131,10 +131,7 @@ void db2BindParameter (DB2Session* session, const DB2Table* db2Table, ParamDesc* } break; default: { - snprintf(db2Message,ERRBUFSIZE,"unsupported sql number type: %d - %s" - ,db2Table->cols[param->colnum]->colType - ,c2name(db2Table->cols[param->colnum]->colType) - ); + snprintf(db2Message, ERRBUFSIZE, "unsupported sql number type: %d - %s" , param->colType, c2name(param->colType)); db2Error_d(FDW_UNABLE_TO_CREATE_EXECUTION, "error executing isrt query: unable to bind parameter", db2Message); } break; @@ -150,7 +147,7 @@ void db2BindParameter (DB2Session* session, const DB2Table* db2Table, ParamDesc* , SQL_PARAM_INPUT , SQL_C_CHAR , SQL_VARCHAR - , db2Table->cols[param->colnum]->colSize + , param->colSize , 0 , (SQLPOINTER) param->value , 0 @@ -167,7 +164,7 @@ void db2BindParameter (DB2Session* session, const DB2Table* db2Table, ParamDesc* , SQL_PARAM_INPUT , SQL_C_BINARY , SQL_LONGVARBINARY - , db2Table->cols[param->colnum]->colSize + , param->colSize , 0 , (SQLPOINTER) param->value , 0 @@ -185,7 +182,7 @@ void db2BindParameter (DB2Session* session, const DB2Table* db2Table, ParamDesc* , SQL_PARAM_INPUT , SQL_C_CHAR , SQL_LONGVARCHAR - , db2Table->cols[param->colnum]->colSize + , param->colSize , 0 , (SQLPOINTER) param->value , 0 @@ -199,11 +196,11 @@ void db2BindParameter (DB2Session* session, const DB2Table* db2Table, ParamDesc* db2Debug2(" param->bindType: BIND_OUTPUT"); *indicator = (SQLLEN) ((param->value == NULL) ? SQL_NULL_DATA : 0); db2Debug2(" param_ind : %d",*indicator); - if (db2Table->cols[param->colnum]->pgtype == UUIDOID) { + if (param->type == UUIDOID) { /* the type input function will interpret the string value correctly */ fcType = SQL_CHAR; } else { - fcType = db2Table->cols[param->colnum]->colType; + fcType = param->colType; } fParamType = param2c(fcType); rc = SQLBindParameter( session->stmtp->hsql @@ -211,10 +208,10 @@ void db2BindParameter (DB2Session* session, const DB2Table* db2Table, ParamDesc* , SQL_PARAM_OUTPUT , fParamType , fcType - , db2Table->cols[param->colnum]->colSize + , param->colSize , 0 , (SQLPOINTER) param->value - , db2Table->cols[param->colnum]->val_size + , param->val_size , indicator ); } diff --git a/source/db2ExecForeignDelete.c b/source/db2ExecForeignDelete.c index 80ba1e7..4131d6e 100644 --- a/source/db2ExecForeignDelete.c +++ b/source/db2ExecForeignDelete.c @@ -11,7 +11,7 @@ extern bool dml_in_transaction; extern regproc* output_funcs; /** external prototypes */ -extern int db2ExecuteQuery (DB2Session* session, const DB2Table* db2Table, ParamDesc* paramList); +extern int db2ExecuteQuery (DB2Session* session, ParamDesc* paramList); extern void db2Debug1 (const char* message, ...); extern void db2Debug2 (const char* message, ...); extern void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls, bool trunc_lob) ; @@ -45,7 +45,7 @@ TupleTableSlot* db2ExecForeignDelete (EState* estate, ResultRelInfo* rinfo, Tupl setModifyParameters (fdw_state->paramList, slot, planSlot, fdw_state->db2Table, fdw_state->session); /* execute the DELETE statement and store RETURNING values in db2Table's columns */ - rows = db2ExecuteQuery (fdw_state->session, fdw_state->db2Table, fdw_state->paramList); + rows = db2ExecuteQuery (fdw_state->session, fdw_state->paramList); if (rows != 1) ereport ( ERROR diff --git a/source/db2ExecForeignInsert.c b/source/db2ExecForeignInsert.c index b084fe6..e60463d 100644 --- a/source/db2ExecForeignInsert.c +++ b/source/db2ExecForeignInsert.c @@ -11,7 +11,7 @@ extern bool dml_in_transaction; /** external prototypes */ -extern int db2ExecuteInsert (DB2Session* session, const DB2Table* db2Table, ParamDesc* paramList); +extern int db2ExecuteInsert (DB2Session* session, ParamDesc* paramList); extern void db2Debug1 (const char* message, ...); extern void setModifyParameters (ParamDesc* paramList, TupleTableSlot* newslot, TupleTableSlot* oldslot, DB2Table* db2Table, DB2Session* session); extern void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls, bool trunc_lob) ; @@ -41,7 +41,7 @@ TupleTableSlot* db2ExecForeignInsert (EState* estate, ResultRelInfo* rinfo, Tupl setModifyParameters (fdw_state->paramList, slot, planSlot, fdw_state->db2Table, fdw_state->session); /* execute the INSERT statement and store RETURNING values in db2Table's columns */ - rows = db2ExecuteInsert (fdw_state->session, fdw_state->db2Table, fdw_state->paramList); + rows = db2ExecuteInsert (fdw_state->session, fdw_state->paramList); if (rows != 1) ereport (ERROR, (errcode (ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg ("INSERT on DB2 table added %d rows instead of one in iteration %lu", rows, fdw_state->rowcount))); diff --git a/source/db2ExecForeignUpdate.c b/source/db2ExecForeignUpdate.c index cd0570d..0f8bf61 100644 --- a/source/db2ExecForeignUpdate.c +++ b/source/db2ExecForeignUpdate.c @@ -11,7 +11,7 @@ extern bool dml_in_transaction; /** external prototypes */ -extern int db2ExecuteQuery (DB2Session* session, const DB2Table* db2Table, ParamDesc* paramList); +extern int db2ExecuteQuery (DB2Session* session, ParamDesc* paramList); extern void db2Debug1 (const char* message, ...); extern void db2Debug2 (const char* message, ...); extern void setModifyParameters (ParamDesc* paramList, TupleTableSlot* newslot, TupleTableSlot* oldslot, DB2Table* db2Table, DB2Session* session); @@ -42,7 +42,7 @@ TupleTableSlot* db2ExecForeignUpdate (EState* estate, ResultRelInfo* rinfo, Tupl setModifyParameters (fdw_state->paramList, slot, planSlot, fdw_state->db2Table, fdw_state->session); /* execute the UPDATE statement and store RETURNING values in db2Table's columns */ - rows = db2ExecuteQuery (fdw_state->session, fdw_state->db2Table, fdw_state->paramList); + rows = db2ExecuteQuery (fdw_state->session, fdw_state->paramList); if (rows != 1) ereport ( ERROR diff --git a/source/db2ExecuteInsert.c b/source/db2ExecuteInsert.c index afeae68..a957584 100644 --- a/source/db2ExecuteInsert.c +++ b/source/db2ExecuteInsert.c @@ -22,10 +22,10 @@ extern void db2Error_d (db2error sqlstate, const char* message extern SQLSMALLINT param2c (SQLSMALLINT fcType); extern void parse2num_struct (const char* s, SQL_NUMERIC_STRUCT* ns); extern char* c2name (short fcType); -extern void db2BindParameter (DB2Session* session, const DB2Table* db2Table, ParamDesc* param, SQLLEN* indicators, int param_count, int col_num); +extern void db2BindParameter (DB2Session* session, ParamDesc* param, SQLLEN* indicators, int param_count, int col_num); /** internal prototypes */ -int db2ExecuteInsert (DB2Session* session, const DB2Table* db2Table, ParamDesc* paramList); +int db2ExecuteInsert (DB2Session* session, ParamDesc* paramList); /** db2ExecuteInsert * Execute a prepared statement and fetches the first result row. @@ -33,7 +33,7 @@ int db2ExecuteInsert (DB2Session* session, const DB2Table* d * Returns the count of processed rows. * This can be called several times for a prepared SQL statement. */ -int db2ExecuteInsert (DB2Session* session, const DB2Table* db2Table, ParamDesc* paramList) { +int db2ExecuteInsert (DB2Session* session, ParamDesc* paramList) { SQLLEN* indicators = NULL; ParamDesc* param = NULL; SQLRETURN rc = 0; @@ -55,8 +55,8 @@ int db2ExecuteInsert (DB2Session* session, const DB2Table* db2Table, ParamDesc* param_count = 0; for (param = paramList; param; param = param->next) { ++param_count; - /** colnum in param and param_count are 0 based, but in isrt statements need to be 1 based */ - db2BindParameter(session, db2Table, param, &indicators[param_count], param_count, param->colnum+1); + /* colnum must map to the column position in the table, as a parameter that must be 1 based */ + db2BindParameter(session, param, &indicators[param_count], param_count, param->colnum+1); } /* execute the query and get the first result row */ diff --git a/source/db2ExecuteQuery.c b/source/db2ExecuteQuery.c index 1f35a75..a695c7f 100644 --- a/source/db2ExecuteQuery.c +++ b/source/db2ExecuteQuery.c @@ -23,10 +23,10 @@ extern void db2Error_d (db2error sqlstate, const char* message extern SQLSMALLINT param2c (SQLSMALLINT fcType); extern void parse2num_struct (const char* s, SQL_NUMERIC_STRUCT* ns); extern char* c2name (short fcType); -extern void db2BindParameter (DB2Session* session, const DB2Table* db2Table, ParamDesc* param, SQLLEN* indicators, int param_count, int col_num); +extern void db2BindParameter (DB2Session* session, ParamDesc* param, SQLLEN* indicators, int param_count, int col_num); /** internal prototypes */ -int db2ExecuteQuery (DB2Session* session, const DB2Table* db2Table, ParamDesc* paramList); +int db2ExecuteQuery (DB2Session* session, ParamDesc* paramList); /** db2ExecuteQuery * Execute a prepared statement and fetches the first result row. @@ -34,7 +34,7 @@ int db2ExecuteQuery (DB2Session* session, const DB2Table* d * Returns the count of processed rows. * This can be called several times for a prepared SQL statement. */ -int db2ExecuteQuery (DB2Session* session, const DB2Table* db2Table, ParamDesc* paramList) { +int db2ExecuteQuery (DB2Session* session, ParamDesc* paramList) { SQLLEN* indicators = NULL; ParamDesc* param = NULL; SQLRETURN rc = 0; @@ -56,8 +56,7 @@ int db2ExecuteQuery (DB2Session* session, const DB2Table* db2Table, ParamDesc* p param_count = 0; for (param = paramList; param; param = param->next) { ++param_count; - /** colnum in param and param_count are 0 based, and select/update/delete statements need to be 0 based */ - db2BindParameter(session, db2Table, param, &indicators[param_count], param_count, param_count); + db2BindParameter(session, param, &indicators[param_count], param_count, param_count); } /* execute the query and get the first result row */ db2Debug2(" session->stmtp->hsql: %d",session->stmtp->hsql); diff --git a/source/db2IterateForeignScan.c b/source/db2IterateForeignScan.c index a7b63be..7b448ed 100644 --- a/source/db2IterateForeignScan.c +++ b/source/db2IterateForeignScan.c @@ -12,7 +12,7 @@ /** external prototypes */ extern int db2IsStatementOpen (DB2Session* session); extern void db2PrepareQuery (DB2Session* session, const char *query, DB2ResultColumn* resultList, unsigned long prefetch, int fetchsize); -extern int db2ExecuteQuery (DB2Session* session, const DB2Table* db2Table, ParamDesc* paramList); +extern int db2ExecuteQuery (DB2Session* session, ParamDesc* paramList); extern int db2FetchNext (DB2Session* session); extern void db2CloseStatement (DB2Session* session); extern void db2Debug1 (const char* message, ...); @@ -51,7 +51,7 @@ TupleTableSlot* db2IterateForeignScan (ForeignScanState* node) { /* execute the DB2 statement and fetch the first row */ db2Debug3(" execute query in foreign table scan '%s'", paramInfo); db2PrepareQuery (fdw_state->session, fdw_state->query, fdw_state->resultList, fdw_state->prefetch, fdw_state->fetch_size); - have_result = db2ExecuteQuery (fdw_state->session, fdw_state->db2Table, fdw_state->paramList); + have_result = db2ExecuteQuery (fdw_state->session, fdw_state->paramList); have_result = db2FetchNext (fdw_state->session); } /* initialize virtual tuple */ diff --git a/source/db2PlanForeignModify.c b/source/db2PlanForeignModify.c index a2fcc16..cab8679 100644 --- a/source/db2PlanForeignModify.c +++ b/source/db2PlanForeignModify.c @@ -7,6 +7,7 @@ #include #include "db2_fdw.h" #include "DB2FdwState.h" +#include "DB2Column.h" /** external prototypes */ extern char* db2strdup (const char* source); @@ -21,10 +22,10 @@ extern void appendAsType (StringInfoData* dest, Oid type); extern List* serializePlanData (DB2FdwState* fdwState); /** local prototypes */ -List* db2PlanForeignModify(PlannerInfo* root, ModifyTable* plan, Index resultRelation, int subplan_index); + List* db2PlanForeignModify(PlannerInfo* root, ModifyTable* plan, Index resultRelation, int subplan_index); static DB2FdwState* copyPlanData (DB2FdwState* orig); -void addParam (ParamDesc** paramList, Oid pgtype, short colType, int colnum, int txts); -void checkDataType (short db2type, int scale, Oid pgtype, const char* tablename, const char* colname); + void addParam (ParamDesc** paramList, DB2Column* db2col, int colnum, int txts); + void checkDataType (short db2type, int scale, Oid pgtype, const char* tablename, const char* colname); /** db2PlanForeignModify * Construct an DB2FdwState or copy it from the foreign scan plan. @@ -193,7 +194,7 @@ List* db2PlanForeignModify (PlannerInfo* root, ModifyTable* plan, Index resultRe /* check that the data types can be converted */ checkDataType (fdwState->db2Table->cols[i]->colType, fdwState->db2Table->cols[i]->colScale, fdwState->db2Table->cols[i]->pgtype, fdwState->db2Table->pgname, fdwState->db2Table->cols[i]->pgname); /* add a parameter description for the column */ - addParam (&fdwState->paramList, fdwState->db2Table->cols[i]->pgtype, fdwState->db2Table->cols[i]->colType, i, 0); + addParam (&fdwState->paramList, fdwState->db2Table->cols[i], i, 0); /* add parameter name */ if (firstcol) firstcol = false; @@ -219,7 +220,7 @@ List* db2PlanForeignModify (PlannerInfo* root, ModifyTable* plan, Index resultRe /* check that the data types can be converted */ checkDataType (fdwState->db2Table->cols[i]->colType, fdwState->db2Table->cols[i]->colScale, fdwState->db2Table->cols[i]->pgtype, fdwState->db2Table->pgname, fdwState->db2Table->cols[i]->pgname); /* add a parameter description for the column */ - addParam (&fdwState->paramList, fdwState->db2Table->cols[i]->pgtype, fdwState->db2Table->cols[i]->colType, i, 0); + addParam (&fdwState->paramList, fdwState->db2Table->cols[i], i, 0); /* add the parameter name to the query */ if (firstcol) firstcol = false; @@ -253,7 +254,7 @@ List* db2PlanForeignModify (PlannerInfo* root, ModifyTable* plan, Index resultRe /* only set the flag here because we only retrieve the old key values in update or delete cases, later in setModifyParms*/ fdwState->db2Table->cols[i]->colPrimKeyPart = 1; /* add a parameter description */ - addParam (&fdwState->paramList, fdwState->db2Table->cols[i]->pgtype, fdwState->db2Table->cols[i]->colType, i, 0); + addParam (&fdwState->paramList, fdwState->db2Table->cols[i], i, 0); /* add column and parameter name to query */ if (firstcol) { appendStringInfo (&sql, " WHERE"); @@ -373,17 +374,23 @@ static DB2FdwState* copyPlanData (DB2FdwState* orig) { * Creates a new ParamDesc with the given values and adds it to the list. * A deep copy of the parameter is created. */ -void addParam (ParamDesc **paramList, Oid pgtype, short colType, int colnum, int txts) { +void addParam (ParamDesc **paramList, DB2Column* db2col, int colnum, int txts) { ParamDesc *param; db2Debug1("> addParam"); - db2Debug2(" pgtype: %d",pgtype); - db2Debug2(" colType: %d",colType); + db2Debug2(" pgtype: %d",db2col->pgtype); + db2Debug2(" colType: %d",db2col->colType); db2Debug2(" colnum: %d",colnum); db2Debug2(" txts: %d",txts); param = db2alloc("paramList->next",sizeof (ParamDesc)); - param->type = pgtype; - switch (c2dbType(colType)) { + param->colName = db2strdup(db2col->colName); + db2Debug2(" param->colName: '%s'",param->colName); + param->colType = db2col->colType; + db2Debug2(" param->colType: '%d'",param->colType); + param->colSize = db2col->colSize; + db2Debug2(" param->colSize: '%d'",param->colSize); + param->type = db2col->pgtype; + switch (c2dbType(db2col->colType)) { case DB2_INTEGER: case DB2_NUMERIC: case DB2_BIGINT: @@ -403,16 +410,18 @@ void addParam (ParamDesc **paramList, Oid pgtype, short colType, int colnum, int param->bindType = BIND_STRING; } db2Debug2(" param->bindType: '%d'",param->bindType); - param->value = NULL; + param->value = NULL; db2Debug2(" param->value: %x",param->value); - param->node = NULL; + param->val_size = db2col->val_size; + db2Debug2(" param->val_size: %d",param->val_size); + param->node = NULL; db2Debug2(" param->node: %x",param->node); - param->colnum = colnum; + param->colnum = colnum; db2Debug2(" param->colnum: %d",param->colnum); - param->txts = txts; + param->txts = txts; db2Debug2(" param->txts: %d",param->txts); - param->next = *paramList; - *paramList = param; + param->next = *paramList; + *paramList = param; db2Debug1("< addParam"); } diff --git a/source/db2_de_serialize.c b/source/db2_de_serialize.c index 42a3cdc..2a4a242 100644 --- a/source/db2_de_serialize.c +++ b/source/db2_de_serialize.c @@ -120,9 +120,9 @@ DB2FdwState* deserializePlanData (List* list) { state->db2Table->cols[i]->noencerr = deserializeLong(list_nth(list, idx++)); db2Debug4(" deserialize col[%d].noencerr: %ld" ,i, state->db2Table->cols[i]->noencerr); /* allocate memory for the result value only when the column is used in query */ - state->db2Table->cols[i]->val = (state->db2Table->cols[i]->used == 1) ? (char*) db2alloc ("state->db2Table->cols[i]->val", state->db2Table->cols[i]->val_size + 1) : NULL; - state->db2Table->cols[i]->val_len = 0; - state->db2Table->cols[i]->val_null = 1; +// state->db2Table->cols[i]->val = (state->db2Table->cols[i]->used == 1) ? (char*) db2alloc ("state->db2Table->cols[i]->val", state->db2Table->cols[i]->val_size + 1) : NULL; +// state->db2Table->cols[i]->val_len = 0; +// state->db2Table->cols[i]->val_null = 1; } /* length of parameter list */ @@ -132,6 +132,12 @@ DB2FdwState* deserializePlanData (List* list) { state->paramList = NULL; for (i = 0; i < len; ++i) { param = (ParamDesc*) db2alloc ("state->parmList->next", sizeof (ParamDesc)); + param->colName = deserializeString(list_nth(list, idx++)); + db2Debug4(" deserialize param[%d].colName: %s" ,i, param->colName); + param->colType = (short) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); + db2Debug4(" deserialize param[%d].colType: %d" ,i, param->colType); + param->colSize = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); + db2Debug4(" deserialize param[%d].colSize: %d" ,i, param->colSize); param->type = DatumGetObjectId(((Const*)list_nth(list, idx++))->constvalue); db2Debug4(" deserialize param[%d].type: %d" ,i, param->type); param->bindType = (db2BindType) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); @@ -141,6 +147,8 @@ DB2FdwState* deserializePlanData (List* list) { else param->value = NULL; db2Debug4(" deserialize param[%d].value: %x" ,i, param->value); + param->val_size = deserializeLong(list_nth(list, idx++)); + db2Debug4(" deserialize param[%d].val_size: %ld" ,i, param->val_size); param->node = NULL; db2Debug4(" deserialize param[%d].node: %x" ,i, param->node); param->colnum = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); @@ -305,7 +313,7 @@ List* serializePlanData (DB2FdwState* fdwState) { result = lappend (result, serializeLong (fdwState->db2Table->cols[idxCol]->val_size)); db2Debug4(" serialize col[%d].val_size: %ld" ,idxCol, fdwState->db2Table->cols[idxCol]->val_size); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->noencerr)); - db2Debug4(" serialize col[%d].val_size: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->noencerr); + db2Debug4(" serialize col[%d].noencerr: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->noencerr); /* don't serialize val, val_len, val_null and varno */ } @@ -318,10 +326,18 @@ List* serializePlanData (DB2FdwState* fdwState) { db2Debug4(" serialize paramList.length: %d", lenParam); /* parameter list entries */ for (param = fdwState->paramList; param; param = param->next) { + result = lappend (result, serializeString (param->colName)); + db2Debug4(" serialize param.colName: %s" , param->colName); + result = lappend (result, serializeInt (param->colType)); + db2Debug4(" serialize param.colType: %d" , param->colType); + result = lappend (result, serializeInt (param->colSize)); + db2Debug4(" serialize param.colSize: %d" , param->colSize); result = lappend (result, serializeOid (param->type)); db2Debug4(" serialize param.type: %d" , param->type); result = lappend (result, serializeInt ((int) param->bindType)); db2Debug4(" serialize param.bindType: %d" , param->bindType); + result = lappend (result, serializeLong (param->val_size)); + db2Debug4(" serialize param.val_size: %ld" , param->val_size); result = lappend (result, serializeInt ((int) param->colnum)); db2Debug4(" serialize param.colnum: %d" , param->colnum); result = lappend (result, serializeInt ((int) param->txts)); From f10435381561481d0dc6ac3d3a4a0bc6ddeb88e7 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Thu, 12 Feb 2026 17:49:10 +0100 Subject: [PATCH 048/120] more dead code cleanup --- Makefile | 2 +- include/DB2Column.h | 3 - source/db2AnalyzeForeignTable.c | 10 -- source/db2BeginForeignScan.c | 13 +- source/db2Describe.c | 9 -- source/db2GetForeignPlan.c | 21 ++- source/db2GetForeignUpperPaths.c | 212 ++++++++++++++------------- source/db2PlanForeignModify.c | 3 - source/db2PrepareQuery.c | 1 - source/db2_deparse.c | 238 +++++++++++++++---------------- source/db2_fdw_utils.c | 3 +- 11 files changed, 237 insertions(+), 278 deletions(-) diff --git a/Makefile b/Makefile index 99c9c17..cf4eb93 100644 --- a/Makefile +++ b/Makefile @@ -79,7 +79,7 @@ REGRESS_OPTS = --inputdir=test # to your extention. # #MODULES = $(patsubst %.c,%,$(wildcard src/*.c)) -PG_CPPFLAGS = -g -fPIC -I$(DB2_HOME)/include -I./include +PG_CPPFLAGS = -fPIC -I$(DB2_HOME)/include -I./include SHLIB_LINK = -fPIC -L$(DB2_HOME)/lib64 -L$(DB2_HOME)/bin -ldb2 PG_CONFIG ?= pg_config PGXS := $(shell $(PG_CONFIG) --pgxs) diff --git a/include/DB2Column.h b/include/DB2Column.h index f71d74e..648326f 100644 --- a/include/DB2Column.h +++ b/include/DB2Column.h @@ -22,10 +22,7 @@ typedef struct db2Column { int pgtypmod; // PG type modifier int used; // is the column used in the query? int pkey; // nonzero for primary keys, later set to the resjunk attribute number - char* val; // buffer for DB2 to return results in (LOB locator for LOBs) size_t val_size; // allocated size in val - size_t val_len; // actual length of val - int val_null; // indicator for NULL value int varno; // range table index of this column's relation db2NoEncErrType noencerr; // no encoding error produced } DB2Column; diff --git a/source/db2AnalyzeForeignTable.c b/source/db2AnalyzeForeignTable.c index 51ab905..e5d9348 100644 --- a/source/db2AnalyzeForeignTable.c +++ b/source/db2AnalyzeForeignTable.c @@ -85,16 +85,6 @@ int acquireSampleRowsFunc (Relation relation, int elevel, HeapTuple* rows, int t /* all columns are used */ fdw_state->db2Table->cols[i]->used = 1; db2Debug2(" fdw_state->db2Table->cols[%d]->used: %d",i,fdw_state->db2Table->cols[i]->used); - - /* allocate memory for return value */ - db2Debug2(" fdw_state->db2Table->cols[%d]->val_size: %x",i,fdw_state->db2Table->cols[i]->val_size); - fdw_state->db2Table->cols[i]->val = (char *) db2alloc ("fdw_state->db2Table->cols[i]->val", fdw_state->db2Table->cols[i]->val_size + 1); - db2Debug2(" fdw_state->db2Table->cols[%d]->val: %x",i,fdw_state->db2Table->cols[i]->val); - fdw_state->db2Table->cols[i]->val_len = 0; - db2Debug2(" fdw_state->db2Table->cols[%d]->val_len: %x",i,fdw_state->db2Table->cols[i]->val); - fdw_state->db2Table->cols[i]->val_null = 1; - db2Debug2(" fdw_state->db2Table->cols[%d]->val_null: %x",i,fdw_state->db2Table->cols[i]->val_null); - if (first_column) first_column = false; else diff --git a/source/db2BeginForeignScan.c b/source/db2BeginForeignScan.c index 46e69fb..15ade7c 100644 --- a/source/db2BeginForeignScan.c +++ b/source/db2BeginForeignScan.c @@ -17,8 +17,7 @@ extern void db2Debug2 (const char* message, ...); /** local prototypes */ void db2BeginForeignScan (ForeignScanState* node, int eflags); -static int addExprParams (ForeignScanState* node); -static int addTleExprParams (ForeignScanState* node, int paramCount); +static void addExprParams (ForeignScanState* node); /** db2BeginForeignScan * Recover ("deserialize") connection information, remote query, @@ -29,14 +28,13 @@ static int addTleExprParams (ForeignScanState* node, int paramCount); void db2BeginForeignScan(ForeignScanState* node, int eflags) { ForeignScan* fsplan = (ForeignScan*) node->ss.ps.plan; DB2FdwState* fdw_state = NULL; - int paramCount = 0; db2Debug1("> db2BeginForeignScan"); /* deserialize private plan data */ fdw_state = deserializePlanData(fsplan->fdw_private); node->fdw_state = (void *) fdw_state; - paramCount = addExprParams(node); + addExprParams(node); /* add a fake parameter "if that string appears in the query */ if (strstr (fdw_state->query, "?/*:now*/") != NULL) { @@ -70,13 +68,12 @@ void db2BeginForeignScan(ForeignScanState* node, int eflags) { db2Debug1("< db2BeginForeignScan"); } -static int addExprParams(ForeignScanState* node){ +static void addExprParams(ForeignScanState* node){ DB2FdwState* fdw_state = node->fdw_state; ForeignScan* fsplan = (ForeignScan*) node->ss.ps.plan; List* exec_exprs = NIL; ParamDesc* paramDesc = NULL; ListCell* cell = NULL; - int index = 0; db2Debug1("> addExprParams"); /* create an ExprState tree for the parameter expressions */ @@ -87,7 +84,6 @@ static int addExprParams(ForeignScanState* node){ ExprState* expr = (ExprState*) lfirst (cell); /* count, but skip deleted entries */ - ++index; if (expr == NULL) continue; @@ -116,6 +112,5 @@ static int addExprParams(ForeignScanState* node){ db2Debug2(" paramDesc->colnum: %d ",paramDesc->colnum); fdw_state->paramList = paramDesc; } - db2Debug1("< addExprParams : %d", index); - return index; + db2Debug1("< addExprParams"); } diff --git a/source/db2Describe.c b/source/db2Describe.c index c48c032..990aa78 100644 --- a/source/db2Describe.c +++ b/source/db2Describe.c @@ -136,9 +136,6 @@ DB2Table* db2Describe (DB2Session* session, char* schema, char* table, char* pgn reply->cols[i - 1]->pgtypmod = 0; reply->cols[i - 1]->used = 0; reply->cols[i - 1]->pkey = 0; - reply->cols[i - 1]->val = NULL; - reply->cols[i - 1]->val_len = 0; - reply->cols[i - 1]->val_null = 1; reply->cols[i - 1]->noencerr = NO_ENC_ERR_NULL; if (noencerr != NULL) { @@ -266,15 +263,9 @@ DB2Table* db2Describe (DB2Session* session, char* schema, char* table, char* pgn reply->cols[i - 1]->val_size = bin_size; break; default: -// reply->cols[i - 1]->val_size = bin_size * 4 + 1; -// reply->cols[i - 1]->db2type = SQL_TYPE_OTHER; -// reply->cols[i - 1]->val_size = 0; break; } - db2Debug2(" reply->cols[%d]->val : %x", (i-1), reply->cols[i - 1]->val); db2Debug2(" reply->cols[%d]->val_size : %d", (i-1), reply->cols[i - 1]->val_size); - db2Debug2(" reply->cols[%d]->val_len : %d", (i-1), reply->cols[i - 1]->val_len); - db2Debug2(" reply->cols[%d]->val_null : %d", (i-1), reply->cols[i - 1]->val_null); } /* release statement handle, this takes care of the parameter handles */ db2FreeStmtHdl(stmthp, session->connp); diff --git a/source/db2GetForeignPlan.c b/source/db2GetForeignPlan.c index a9bdccd..bd7add5 100644 --- a/source/db2GetForeignPlan.c +++ b/source/db2GetForeignPlan.c @@ -67,8 +67,9 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo } if (IS_SIMPLE_REL(foreignrel)) { - ListCell* cell = NULL; - int resnum = 0; + DB2ResultColumn** cols = NULL; + ListCell* cell = NULL; + int resnum = 0; /* For base relations, set scan_relid as the relid of the relation. */ scan_relid = foreignrel->relid; @@ -76,7 +77,7 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo /* find all the columns to include in the select list */ /* examine each SELECT list entry for Var nodes */ db2Debug3(" size of columnlist: %d", list_length(foreignrel->reltarget->exprs)); - DB2ResultColumn** cols = (DB2ResultColumn**)db2alloc("resultColumns", list_length(foreignrel->reltarget->exprs) * sizeof(DB2ResultColumn*)); + cols = (DB2ResultColumn**)db2alloc("resultColumns", list_length(foreignrel->reltarget->exprs) * sizeof(DB2ResultColumn*)); foreach (cell, foreignrel->reltarget->exprs) { db2Debug3(" examine column %d", resnum); cols[resnum] = (DB2ResultColumn*)db2alloc("resultColumn",sizeof(DB2ResultColumn)); @@ -161,8 +162,8 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo /* examine each condition for Tlist nodes; they come in the correct sequence as in the query and do not need to be sorted */ db2Debug3(" size of tlist: %d", list_length(fdw_scan_tlist)); foreach (cell, fdw_scan_tlist) { - db2Debug3(" examine tlist"); DB2ResultColumn* resCol = (DB2ResultColumn*)db2alloc("resultColumn",sizeof(DB2ResultColumn)); + db2Debug3(" examine tlist"); resCol->next = fpinfo->resultList; fpinfo->resultList = resCol; resCol->resnum = resnum; @@ -307,10 +308,10 @@ static void getUsedColumns (Expr* expr, RelOptInfo* foreignrel, DB2ResultColumn* } db2Debug2( " aggref->aggfnoid=%u name=%s%s%s", aggref->aggfnoid, nspname ? nspname : "", nspname ? "." : "", aggname ? aggname : ""); if (aggname && strcmp(aggname, "count") == 0) { - db2Debug2(" found COUNT aggregate"); DB2FdwState* fpinfo = (DB2FdwState*) foreignrel->fdw_private; /* if it's a COUNT(*) then we need an additional result */ DB2Column* col = db2alloc("DB2Column for count(*)",sizeof(DB2Column)); + db2Debug2(" found COUNT aggregate"); col->colName = "count"; col->colType = -5; // SQL_BIGINT type in DB2, which can hold the result of COUNT(*) col->colSize = 8; @@ -326,10 +327,7 @@ static void getUsedColumns (Expr* expr, RelOptInfo* foreignrel, DB2ResultColumn* col->pgtypmod = -1; col->used = 1; col->pkey = 0; - col->val = NULL; col->val_size = 24; - col->val_len = 0; - col->val_null = 0; col->noencerr = fpinfo->db2Table->cols[0]->noencerr; // use same noencerr as first column addResult(resCol,col); } else { @@ -516,10 +514,7 @@ static void addResult(DB2ResultColumn* resCol, DB2Column* column) { resCol->pgtype = column->pgtype; resCol->pgtypmod = column->pgtypmod; resCol->pkey = column->pkey; - resCol->val = db2strdup(column->val); resCol->val_size = column->val_size; - resCol->val_len = column->val_len; - resCol->val_null = column->val_null; resCol->varno = column->varno; resCol->noencerr = column->noencerr; } @@ -527,10 +522,10 @@ static void addResult(DB2ResultColumn* resCol, DB2Column* column) { } static int compareResultColumns(const void* a, const void* b) { - int result = 0; - db2Debug1("> %s::compareResultColumns",__FILE__); DB2ResultColumn* colA = *(DB2ResultColumn**) a; DB2ResultColumn* colB = *(DB2ResultColumn**) b; + int result = 0; + db2Debug1("> %s::compareResultColumns",__FILE__); result = (colA->pgattnum - colB->pgattnum); db2Debug2(" comparing %s -> pgattnum %d and %s -> pgattnum %d, result = %d", colA->colName, colA->pgattnum, colB->colName, colB->pgattnum, result); db2Debug1("< %s::compareResultColumns: %d",__FILE__, result); diff --git a/source/db2GetForeignUpperPaths.c b/source/db2GetForeignUpperPaths.c index 868f2ec..b3a36ed 100644 --- a/source/db2GetForeignUpperPaths.c +++ b/source/db2GetForeignUpperPaths.c @@ -41,8 +41,8 @@ void db2GetForeignUpperPaths (PlannerInfo *root, UpperRelationK static void db2CloneFdwStateUpper (PlannerInfo* root, RelOptInfo* input_rel, RelOptInfo* output_rel); static DB2Table* db2CloneDb2TableForPlan (const DB2Table* src); static DB2Column* db2CloneDb2ColumnForPlan (const DB2Column* src); -static bool db2_is_shippable (PlannerInfo* root, UpperRelationKind stage, RelOptInfo* input_rel, RelOptInfo* output_rel); -static bool db2_is_shippable_expr (PlannerInfo* root, RelOptInfo* foreignrel, Expr* expr, const char* label); +//static bool db2_is_shippable (PlannerInfo* root, UpperRelationKind stage, RelOptInfo* input_rel, RelOptInfo* output_rel); +//static bool db2_is_shippable_expr (PlannerInfo* root, RelOptInfo* foreignrel, Expr* expr, const char* label); static void add_foreign_grouping_paths(PlannerInfo* root, RelOptInfo* input_rel, RelOptInfo* grouped_rel, GroupPathExtraData *extra); static void add_foreign_ordered_paths (PlannerInfo* root, RelOptInfo* input_rel, RelOptInfo* ordered_rel); static void add_foreign_final_paths (PlannerInfo* root, RelOptInfo* input_rel, RelOptInfo* final_rel, FinalPathExtraData *extra); @@ -244,117 +244,113 @@ static DB2Column* db2CloneDb2ColumnForPlan(const DB2Column* src) { dst->colName = src->colName ? db2strdup(src->colName) : NULL; dst->pgname = src->pgname ? db2strdup(src->pgname) : NULL; - - /* Never share row buffers between planning states. */ - dst->val = NULL; - dst->val_len = 0; - dst->val_null = 1; } db2Debug1("< %s::db2CloneDb2ColumnForPlan : %x", __FILE__, dst); return dst; } -static bool db2_is_shippable(PlannerInfo* root, UpperRelationKind stage, RelOptInfo* input_rel, RelOptInfo* output_rel) { - bool fResult = false; - DB2FdwState* fdw_in = (DB2FdwState*)input_rel->fdw_private; - - db2Debug1("> %s::db2_is_shippable", __FILE__); - if (root == NULL || root->parse == NULL || input_rel == NULL || output_rel == NULL || fdw_in == NULL) { - db2Debug2(" missing context; not shippable"); - fResult = false; - } else { - Query* query = query = root->parse; - /* Conservatively reject query shapes we don't yet know how to translate. - * (This can be relaxed as we add DB2 SQL support.) - */ - if (query->hasSubLinks || query->hasWindowFuncs || query->hasDistinctOn || query->hasTargetSRFs || query->hasForUpdate || query->hasModifyingCTE || query->hasRecursive || query->hasRowSecurity) { - db2Debug2(" query has unsupported features; not shippable"); - fResult = false; - } else { - switch (stage) { - case UPPERREL_PARTIAL_GROUP_AGG: - case UPPERREL_GROUP_AGG: { - ListCell* lc = NULL; - - fResult = true; - /* 1) GROUP BY expressions must be deparsable. */ - foreach (lc, query->groupClause) { - SortGroupClause* grp = (SortGroupClause*) lfirst(lc); - TargetEntry* tle = get_sortgroupclause_tle(grp, query->targetList); - if (tle == NULL || tle->expr == NULL) { - db2Debug2(" missing GROUP BY target entry; not shippable"); - fResult = false; - break; - } - if (!db2_is_shippable_expr(root, input_rel, (Expr*) tle->expr, "GROUP BY")) { - fResult = false; - break; - } - } - - if (fResult) { - /* 2) HAVING clause must be deparsable (if present). */ - if (query->havingQual != NULL) { - if (!db2_is_shippable_expr(root, input_rel, (Expr*) query->havingQual, "HAVING")) { - fResult = false; - } - } - } - - if (fResult) { - /* 3) Output target expressions must be deparsable too. */ - foreach (lc, output_rel->reltarget->exprs) { - Expr* expr = (Expr*) lfirst(lc); - if (!db2_is_shippable_expr(root, input_rel, expr, "SELECT")) { - fResult = false; - break; - } - } - } - } - break; - default: { - db2Debug2(" stage %d not supported; not shippable", stage); - fResult = false; - } - break; - } - } - } - db2Debug1("< %s::db2_is_shippable : %s", __FILE__, fResult ? "true" : "false"); - return fResult; -} - -static bool db2_is_shippable_expr(PlannerInfo* root, RelOptInfo* foreignrel, Expr* expr, const char* label) { - bool fResult = false; - DB2FdwState* fdw_in = (DB2FdwState*)foreignrel->fdw_private; - - db2Debug1("> %s::db2_is_shippable_expr", __FILE__); - if (expr == NULL) { - fResult = true; - } else if (fdw_in == NULL) { - fResult = false; - } else if (contain_agg_clause((Node*) expr)) { - List* params = NIL; - char* deparsed = NULL; - deparsed = deparseExpr(root, foreignrel, expr, ¶ms); - db2Debug2(" deparsed: %s", deparsed); - fResult = (deparsed != NULL); - db2free(deparsed); - } else if (contain_window_function((Node*) expr)) { - db2Debug2(" %s contains window function; not shippable", label ? label : "expr"); - fResult = false; - } else { - List* params = NIL; - char* deparsed = NULL; - deparsed = deparseExpr(root, foreignrel, expr, ¶ms); - db2Debug2(" deparsed: %s", deparsed); - fResult = (deparsed != NULL); - db2free(deparsed); - } - db2Debug1("> %s::db2_is_shippable_expr : %s", __FILE__, fResult ? "true" : "false"); - return fResult; -} +//static bool db2_is_shippable(PlannerInfo* root, UpperRelationKind stage, RelOptInfo* input_rel, RelOptInfo* output_rel) { +// bool fResult = false; +// DB2FdwState* fdw_in = (DB2FdwState*)input_rel->fdw_private; +// +// db2Debug1("> %s::db2_is_shippable", __FILE__); +// if (root == NULL || root->parse == NULL || input_rel == NULL || output_rel == NULL || fdw_in == NULL) { +// db2Debug2(" missing context; not shippable"); +// fResult = false; +// } else { +// Query* query = query = root->parse; +// /* Conservatively reject query shapes we don't yet know how to translate. +// * (This can be relaxed as we add DB2 SQL support.) +// */ +// if (query->hasSubLinks || query->hasWindowFuncs || query->hasDistinctOn || query->hasTargetSRFs || query->hasForUpdate || query->hasModifyingCTE || query->hasRecursive || query->hasRowSecurity) { +// db2Debug2(" query has unsupported features; not shippable"); +// fResult = false; +// } else { +// switch (stage) { +// case UPPERREL_PARTIAL_GROUP_AGG: +// case UPPERREL_GROUP_AGG: { +// ListCell* lc = NULL; +// +// fResult = true; +// /* 1) GROUP BY expressions must be deparsable. */ +// foreach (lc, query->groupClause) { +// SortGroupClause* grp = (SortGroupClause*) lfirst(lc); +// TargetEntry* tle = get_sortgroupclause_tle(grp, query->targetList); +// if (tle == NULL || tle->expr == NULL) { +// db2Debug2(" missing GROUP BY target entry; not shippable"); +// fResult = false; +// break; +// } +// if (!db2_is_shippable_expr(root, input_rel, (Expr*) tle->expr, "GROUP BY")) { +// fResult = false; +// break; +// } +// } +// +// if (fResult) { +// /* 2) HAVING clause must be deparsable (if present). */ +// if (query->havingQual != NULL) { +// if (!db2_is_shippable_expr(root, input_rel, (Expr*) query->havingQual, "HAVING")) { +// fResult = false; +// } +// } +// } +// +// if (fResult) { +// /* 3) Output target expressions must be deparsable too. */ +// foreach (lc, output_rel->reltarget->exprs) { +// Expr* expr = (Expr*) lfirst(lc); +// if (!db2_is_shippable_expr(root, input_rel, expr, "SELECT")) { +// fResult = false; +// break; +// } +// } +// } +// } +// break; +// default: { +// db2Debug2(" stage %d not supported; not shippable", stage); +// fResult = false; +// } +// break; +// } +// } +// } +// db2Debug1("< %s::db2_is_shippable : %s", __FILE__, fResult ? "true" : "false"); +// return fResult; +//} + + +//static bool db2_is_shippable_expr(PlannerInfo* root, RelOptInfo* foreignrel, Expr* expr, const char* label) { +// bool fResult = false; +// DB2FdwState* fdw_in = (DB2FdwState*)foreignrel->fdw_private; +// +// db2Debug1("> %s::db2_is_shippable_expr", __FILE__); +// if (expr == NULL) { +// fResult = true; +// } else if (fdw_in == NULL) { +// fResult = false; +// } else if (contain_agg_clause((Node*) expr)) { +// List* params = NIL; +// char* deparsed = NULL; +// deparsed = deparseExpr(root, foreignrel, expr, ¶ms); +// db2Debug2(" deparsed: %s", deparsed); +// fResult = (deparsed != NULL); +// db2free(deparsed); +// } else if (contain_window_function((Node*) expr)) { +// db2Debug2(" %s contains window function; not shippable", label ? label : "expr"); +// fResult = false; +// } else { +// List* params = NIL; +// char* deparsed = NULL; +// deparsed = deparseExpr(root, foreignrel, expr, ¶ms); +// db2Debug2(" deparsed: %s", deparsed); +// fResult = (deparsed != NULL); +// db2free(deparsed); +// } +// db2Debug1("> %s::db2_is_shippable_expr : %s", __FILE__, fResult ? "true" : "false"); +// return fResult; +//} /** add_foreign_grouping_paths * Add foreign path for grouping and/or aggregation. diff --git a/source/db2PlanForeignModify.c b/source/db2PlanForeignModify.c index cab8679..1da4ed6 100644 --- a/source/db2PlanForeignModify.c +++ b/source/db2PlanForeignModify.c @@ -356,10 +356,7 @@ static DB2FdwState* copyPlanData (DB2FdwState* orig) { copy->db2Table->cols[i]->pgtypmod = orig->db2Table->cols[i]->pgtypmod; copy->db2Table->cols[i]->used = 0; copy->db2Table->cols[i]->pkey = orig->db2Table->cols[i]->pkey; - copy->db2Table->cols[i]->val = NULL; copy->db2Table->cols[i]->val_size = orig->db2Table->cols[i]->val_size; - copy->db2Table->cols[i]->val_len = 0; - copy->db2Table->cols[i]->val_null = 0; } copy->startup_cost = 0.0; copy->total_cost = 0.0; diff --git a/source/db2PrepareQuery.c b/source/db2PrepareQuery.c index 982cf83..c47d329 100644 --- a/source/db2PrepareQuery.c +++ b/source/db2PrepareQuery.c @@ -36,7 +36,6 @@ void db2PrepareQuery (DB2Session* session, const char *query * - Set the prefetch options. */ void db2PrepareQuery (DB2Session* session, const char *query, DB2ResultColumn* resultList, unsigned long prefetch, int fetchsize) { - int i = 0; int col_pos = 0; int is_select = 0; int for_update = 0; diff --git a/source/db2_deparse.c b/source/db2_deparse.c index 26ae713..a504b85 100644 --- a/source/db2_deparse.c +++ b/source/db2_deparse.c @@ -128,7 +128,7 @@ EquivalenceMember* find_em_for_rel_target (PlannerInfo* root, EquivalenceCla static void appendGroupByClause (List* tlist, deparse_expr_cxt* context); static void appendOrderByClause (List* pathkeys, bool has_final_sort, deparse_expr_cxt* context); static void appendLimitClause (deparse_expr_cxt* context); -static void appendFunctionName (Oid funcid, deparse_expr_cxt* context); +//static void appendFunctionName (Oid funcid, deparse_expr_cxt* context); static void appendOrderBySuffix (Oid sortop, Oid sortcoltype, bool nulls_first, deparse_expr_cxt* context); static void appendConditions (List* exprs, deparse_expr_cxt* context); static void appendWhereClause (List* exprs, List* additional_conds, deparse_expr_cxt* context); @@ -145,7 +145,7 @@ static void deparseRelation (StringInfo buf, Relation rel); static void deparseExprInt (Expr* expr, deparse_expr_cxt* ctx); static void deparseConstExpr (Const* expr, deparse_expr_cxt* ctx); static void deparseParamExpr (Param* expr, deparse_expr_cxt* ctx); -static void deparseVarExpr (Var* expr, deparse_expr_cxt* ctx); +//static void deparseVarExpr (Var* expr, deparse_expr_cxt* ctx); static void deparseVar (Var* expr, deparse_expr_cxt* ctx); static void deparseOpExpr (OpExpr* expr, deparse_expr_cxt* ctx); static void deparseScalarArrayOpExpr (ScalarArrayOpExpr* expr, deparse_expr_cxt* ctx); @@ -1001,27 +1001,27 @@ static void appendLimitClause(deparse_expr_cxt* context) { /** appendFunctionName * Deparses function name from given function oid. */ -static void appendFunctionName(Oid funcid, deparse_expr_cxt *context) { - StringInfo buf = context->buf; - HeapTuple proctup; - Form_pg_proc procform; - - db2Debug4("> %s::appendFunctionName",__FILE__); - proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid)); - if (!HeapTupleIsValid(proctup)) - elog(ERROR, "cache lookup failed for function %u", funcid); - procform = (Form_pg_proc) GETSTRUCT(proctup); - - /* Print schema name only if it's not pg_catalog */ - if (procform->pronamespace != PG_CATALOG_NAMESPACE) - appendStringInfo(buf, "%s.", quote_identifier(get_namespace_name(procform->pronamespace))); - - /* Always print the function name */ - appendStringInfoString(buf, quote_identifier(NameStr(procform->proname))); - ReleaseSysCache(proctup); - db2Debug5(" function name: %s", context->buf->data); - db2Debug4("< %s::appendFunctionName",__FILE__); -} +//static void appendFunctionName(Oid funcid, deparse_expr_cxt *context) { +// StringInfo buf = context->buf; +// HeapTuple proctup; +// Form_pg_proc procform; +// +// db2Debug4("> %s::appendFunctionName",__FILE__); +// proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid)); +// if (!HeapTupleIsValid(proctup)) +// elog(ERROR, "cache lookup failed for function %u", funcid); +// procform = (Form_pg_proc) GETSTRUCT(proctup); +// +// /* Print schema name only if it's not pg_catalog */ +// if (procform->pronamespace != PG_CATALOG_NAMESPACE) +// appendStringInfo(buf, "%s.", quote_identifier(get_namespace_name(procform->pronamespace))); +// +// /* Always print the function name */ +// appendStringInfoString(buf, quote_identifier(NameStr(procform->proname))); +// ReleaseSysCache(proctup); +// db2Debug5(" function name: %s", context->buf->data); +// db2Debug4("< %s::appendFunctionName",__FILE__); +//} /** Append the ASC, DESC, USING and NULLS FIRST / NULLS LAST parts of an ORDER BY clause. */ @@ -2040,102 +2040,102 @@ static void deparseParamExpr (Param* expr, deparse_expr_cxt* db2Debug1("< %s::deparseParamExpr", __FILE__); } -static void deparseVarExpr (Var* expr, deparse_expr_cxt* ctx) { - const DB2Table* var_table = NULL; /* db2Table that belongs to a Var */ - - db2Debug1("> %s::deparseVarExpr", __FILE__); - /* check if the variable belongs to one of our foreign tables */ - #ifdef JOIN_API - if (IS_SIMPLE_REL (ctx->foreignrel)) { - #endif /* JOIN_API */ - if (expr->varno == ctx->foreignrel->relid && expr->varlevelsup == 0) - var_table = ((DB2FdwState*)ctx->foreignrel->fdw_private)->db2Table; - #ifdef JOIN_API - } else { - DB2FdwState* joinstate = (DB2FdwState*) ctx->foreignrel->fdw_private; - DB2FdwState* outerstate = (DB2FdwState*) joinstate->outerrel->fdw_private; - DB2FdwState* innerstate = (DB2FdwState*) joinstate->innerrel->fdw_private; - /* we can't get here if the foreign table has no columns, so this is safe */ - if (expr->varno == outerstate->db2Table->cols[0]->varno && expr->varlevelsup == 0) - var_table = outerstate->db2Table; - if (expr->varno == innerstate->db2Table->cols[0]->varno && expr->varlevelsup == 0) - var_table = innerstate->db2Table; - } - #endif /* JOIN_API */ - if (var_table) { - /* the variable belongs to a foreign table, replace it with the name */ - /* we cannot handle system columns */ - db2Debug2(" varattno: %d",expr->varattno); - if (expr->varattno > 0) { - /** Allow boolean columns here. - * They will be rendered as ("COL" <> 0). - */ - if (!(canHandleType (expr->vartype) || expr->vartype == BOOLOID)) { - db2Debug2(" !(canHandleType (vartype %d) || vartype == BOOLOID",expr->vartype); - } else { - /* get var_table column index corresponding to this column (-1 if none) */ - int index = var_table->ncols - 1; - while (index >= 0 && var_table->cols[index]->pgattnum != expr->varattno) { - --index; - } - /* if no DB2 column corresponds, translate as NULL */ - if (index == -1) { - appendStringInfo (ctx->buf, "NULL"); - } else { - /** Don't try to convert a column reference if the type is - * converted from a non-string type in DB2 to a string type - * in PostgreSQL because functions and operators won't work the same. - */ - short db2type = c2dbType(var_table->cols[index]->colType); - db2Debug2(" db2type: %d", db2type); - if ((expr->vartype == TEXTOID || expr->vartype == BPCHAROID || expr->vartype == VARCHAROID) && db2type != DB2_VARCHAR && db2type != DB2_CHAR) { - db2Debug2(" vartype: %d", expr->vartype); - } else { - /* work around the lack of booleans in DB2 */ - if (expr->vartype == BOOLOID) { - appendStringInfo (ctx->buf, "("); - } - /* qualify with an alias based on the range table index */ - appendStringInfo(ctx->buf, "%s%d.%s", "r", var_table->cols[index]->varno, var_table->cols[index]->colName); - /* work around the lack of booleans in DB2 */ - if (expr->vartype == BOOLOID) { - appendStringInfo (ctx->buf, " <> 0)"); - } - } - } - } - } - } else { - #ifdef OLD_FDW_API - // treat it like a parameter - // don't try to push down parameters with 9.1 - db2Debug2(" don't try to push down parameters with 9.1"); - #else - // don't try to handle type interval - if (!canHandleType (expr->vartype) || expr->vartype == INTERVALOID) { - db2Debug2(" !canHandleType (vartype %d) || vartype == INTERVALOID", expr->vartype); - } else { - ListCell* cell = NULL; - int index = 0; - - /* find the index in the parameter list */ - foreach (cell, *(ctx->params_list)) { - ++index; - if (equal (expr, (Node*) lfirst (cell))) - break; - } - if (cell == NULL) { - /* add the parameter to the list */ - ++index; - *(ctx->params_list) = lappend (*(ctx->params_list), expr); - } - /* parameters will be called :p1, :p2 etc. */ - appendStringInfo (ctx->buf, ":p%d", index); - } - #endif /* OLD_FDW_API */ - } - db2Debug1("< %s::deparseVarExpr", __FILE__); -} +//static void deparseVarExpr (Var* expr, deparse_expr_cxt* ctx) { +// const DB2Table* var_table = NULL; /* db2Table that belongs to a Var */ +// +// db2Debug1("> %s::deparseVarExpr", __FILE__); +// /* check if the variable belongs to one of our foreign tables */ +// #ifdef JOIN_API +// if (IS_SIMPLE_REL (ctx->foreignrel)) { +// #endif /* JOIN_API */ +// if (expr->varno == ctx->foreignrel->relid && expr->varlevelsup == 0) +// var_table = ((DB2FdwState*)ctx->foreignrel->fdw_private)->db2Table; +// #ifdef JOIN_API +// } else { +// DB2FdwState* joinstate = (DB2FdwState*) ctx->foreignrel->fdw_private; +// DB2FdwState* outerstate = (DB2FdwState*) joinstate->outerrel->fdw_private; +// DB2FdwState* innerstate = (DB2FdwState*) joinstate->innerrel->fdw_private; +// /* we can't get here if the foreign table has no columns, so this is safe */ +// if (expr->varno == outerstate->db2Table->cols[0]->varno && expr->varlevelsup == 0) +// var_table = outerstate->db2Table; +// if (expr->varno == innerstate->db2Table->cols[0]->varno && expr->varlevelsup == 0) +// var_table = innerstate->db2Table; +// } +// #endif /* JOIN_API */ +// if (var_table) { +// /* the variable belongs to a foreign table, replace it with the name */ +// /* we cannot handle system columns */ +// db2Debug2(" varattno: %d",expr->varattno); +// if (expr->varattno > 0) { +// /** Allow boolean columns here. +// * They will be rendered as ("COL" <> 0). +// */ +// if (!(canHandleType (expr->vartype) || expr->vartype == BOOLOID)) { +// db2Debug2(" !(canHandleType (vartype %d) || vartype == BOOLOID",expr->vartype); +// } else { +// /* get var_table column index corresponding to this column (-1 if none) */ +// int index = var_table->ncols - 1; +// while (index >= 0 && var_table->cols[index]->pgattnum != expr->varattno) { +// --index; +// } +// /* if no DB2 column corresponds, translate as NULL */ +// if (index == -1) { +// appendStringInfo (ctx->buf, "NULL"); +// } else { +// /** Don't try to convert a column reference if the type is +// * converted from a non-string type in DB2 to a string type +// * in PostgreSQL because functions and operators won't work the same. +// */ +// short db2type = c2dbType(var_table->cols[index]->colType); +// db2Debug2(" db2type: %d", db2type); +// if ((expr->vartype == TEXTOID || expr->vartype == BPCHAROID || expr->vartype == VARCHAROID) && db2type != DB2_VARCHAR && db2type != DB2_CHAR) { +// db2Debug2(" vartype: %d", expr->vartype); +// } else { +// /* work around the lack of booleans in DB2 */ +// if (expr->vartype == BOOLOID) { +// appendStringInfo (ctx->buf, "("); +// } +// /* qualify with an alias based on the range table index */ +// appendStringInfo(ctx->buf, "%s%d.%s", "r", var_table->cols[index]->varno, var_table->cols[index]->colName); +// /* work around the lack of booleans in DB2 */ +// if (expr->vartype == BOOLOID) { +// appendStringInfo (ctx->buf, " <> 0)"); +// } +// } +// } +// } +// } +// } else { +// #ifdef OLD_FDW_API +// // treat it like a parameter +// // don't try to push down parameters with 9.1 +// db2Debug2(" don't try to push down parameters with 9.1"); +// #else +// // don't try to handle type interval +// if (!canHandleType (expr->vartype) || expr->vartype == INTERVALOID) { +// db2Debug2(" !canHandleType (vartype %d) || vartype == INTERVALOID", expr->vartype); +// } else { +// ListCell* cell = NULL; +// int index = 0; +// +// /* find the index in the parameter list */ +// foreach (cell, *(ctx->params_list)) { +// ++index; +// if (equal (expr, (Node*) lfirst (cell))) +// break; +// } +// if (cell == NULL) { +// /* add the parameter to the list */ +// ++index; +// *(ctx->params_list) = lappend (*(ctx->params_list), expr); +// } +// /* parameters will be called :p1, :p2 etc. */ +// appendStringInfo (ctx->buf, ":p%d", index); +// } +// #endif /* OLD_FDW_API */ +// } +// db2Debug1("< %s::deparseVarExpr", __FILE__); +//} /* Deparse given Var node into context->buf. * diff --git a/source/db2_fdw_utils.c b/source/db2_fdw_utils.c index 23cecae..c04e0cb 100644 --- a/source/db2_fdw_utils.c +++ b/source/db2_fdw_utils.c @@ -196,8 +196,7 @@ void exitHook (int code, Datum arg) { void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls, bool trunc_lob) { char* value = NULL; long value_len = 0; - int j = 0; - int index = -1; + int j = 0; DB2Table* db2Table = fdw_state->db2Table; DB2ResultColumn* res = NULL; bool isSimpleSelect = false; From 378b203aafab59858b42761fab1b580361dbed22 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Fri, 13 Feb 2026 10:33:28 +0100 Subject: [PATCH 049/120] add pathnodes.h to ensure elements if DB2FdWState are resovable wherever used. --- include/DB2FdwState.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/DB2FdwState.h b/include/DB2FdwState.h index 6b8b06d..d679eb7 100644 --- a/include/DB2FdwState.h +++ b/include/DB2FdwState.h @@ -2,6 +2,7 @@ #define DB2FDWSTATE_H #include +#include #include "ParamDesc.h" #include "DB2ResultColumn.h" From ee10ad2f0c550f392ceb8fc9671a4b5574f69488 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Fri, 13 Feb 2026 10:40:15 +0100 Subject: [PATCH 050/120] rearrange the sequence of options sources to reflect the order of precedence --- source/db2GetOptions.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/db2GetOptions.c b/source/db2GetOptions.c index e1c190f..dd2e453 100644 --- a/source/db2GetOptions.c +++ b/source/db2GetOptions.c @@ -19,10 +19,10 @@ void db2GetOptions(Oid foreigntableid, List** options); * in that order. Column options are ignored. */ void db2GetOptions (Oid foreigntableid, List** options) { - ForeignTable* table = NULL; + ForeignDataWrapper* wrapper = NULL; ForeignServer* server = NULL; UserMapping* mapping = NULL; - ForeignDataWrapper* wrapper = NULL; + ForeignTable* table = NULL; db2Debug1("> db2GetOptions"); /** Gather all data for the foreign table. */ From b2f572425b56713306e2938464e26f0a14245b13 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Fri, 13 Feb 2026 10:40:46 +0100 Subject: [PATCH 051/120] define PQ_QUERY_PARAM_MAX_LIMIT if not defined yet --- include/db2_fdw.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/db2_fdw.h b/include/db2_fdw.h index 8a60c91..f1d76d8 100644 --- a/include/db2_fdw.h +++ b/include/db2_fdw.h @@ -22,6 +22,10 @@ #define WIDTH_THRESHOLD 1024 #endif /* WIDTH_THRESHOLD */ +#ifndef PQ_QUERY_PARAM_MAX_LIMIT +#define PQ_QUERY_PARAM_MAX_LIMIT 65535 +#endif /* PQ_QUERY_PARAM_MAX_LIMIT */ + #undef OLD_FDW_API #define IMPORT_API From fc65ff398214e2283417a75c1517665a5b8525a7 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Fri, 13 Feb 2026 10:41:22 +0100 Subject: [PATCH 052/120] aligned the code more with how postgres_fdw does it --- source/db2GetForeignModifyBatchSize.c | 101 +++++++++++++++++++------- 1 file changed, 75 insertions(+), 26 deletions(-) diff --git a/source/db2GetForeignModifyBatchSize.c b/source/db2GetForeignModifyBatchSize.c index cc0cb5d..f952192 100644 --- a/source/db2GetForeignModifyBatchSize.c +++ b/source/db2GetForeignModifyBatchSize.c @@ -1,11 +1,11 @@ #include #if PG_VERSION_NUM >= 140000 -#include #include #include #include #include "db2_fdw.h" +#include "DB2FdwState.h" /** external variables */ @@ -13,44 +13,93 @@ extern void db2Debug1 (const char* message, ...); /** local prototypes */ -int db2_get_batch_size_option (Relation rel); int db2GetForeignModifyBatchSize(ResultRelInfo *rinfo); +static int db2_get_batch_size_option (Relation rel); -/* - * db2GetForeignModifyBatchSize +/* db2GetForeignModifyBatchSize + * Determine the maximum number of tuples that can be inserted in bulk * - * Returns the batch size to use for INSERTs into this foreign table. - * - * Returning 1 tells the executor to not batch (i.e., call ExecForeignInsert - * per-row). Any value > 1 enables batching, subject to what the executor - * actually decides to send. + * Returns the batch size specified for server or table. + * When batching is not allowed (e.g. for tables with BEFORE/AFTER ROW triggers or with RETURNING clause), returns 1. */ int db2GetForeignModifyBatchSize(ResultRelInfo *rinfo) { - Relation rel = rinfo->ri_RelationDesc; - int batch_size = 0; - - db2Debug1("> db2GetForeignModifyBatchSize"); - /* - * If the table has BEFORE/AFTER ROW triggers or a RETURNING clause - * is involved, it’s safer to disable batching and just do per-row - * inserts. That's what postgres_fdw does as well.:contentReference[oaicite:4]{index=4} + int batch_size = 1; + DB2FdwState* fmstate = (DB2FdwState*) rinfo->ri_FdwState; + + /* should be called only once */ + Assert(rinfo->ri_BatchSize == 0); + + /* Should never get called when the insert is being performed on a table that is also among the target relations of an UPDATE operation, because + * postgresBeginForeignInsert() currently rejects such insert attempts. + */ + Assert(fmstate == NULL || fmstate->aux_fmstate == NULL); + + /* In EXPLAIN without ANALYZE, ri_FdwState is NULL, so we have to lookup the option directly in server/table options. + * Otherwise just use the value we determined earlier. + */ + batch_size = (fmstate) ? fmstate->db2Table->batchsz : db2_get_batch_size_option(rinfo->ri_RelationDesc); + + /* Disable batching when we have to use RETURNING, there are any BEFORE/AFTER ROW INSERT triggers on the foreign table, or there are any + * WITH CHECK OPTION constraints from parent views. + * + * When there are any BEFORE ROW INSERT triggers on the table, we can't support it, because such triggers might query the table we're inserting + * into and act differently if the tuples that have already been processed and prepared for insertion are not there. */ - if (rel->trigdesc && (rel->trigdesc->trig_insert_before_row || rel->trigdesc->trig_insert_after_row)) { + if (rinfo->ri_projectReturning != NULL + || rinfo->ri_WithCheckOptions != NIL + || (rinfo->ri_TrigDesc && (rinfo->ri_TrigDesc->trig_insert_before_row || rinfo->ri_TrigDesc->trig_insert_after_row))) { batch_size = 1; } else { - /* - * We don't have easy access to "has RETURNING" here like postgres_fdw - * does (it stores that in its modify state), but PostgreSQL core - * currently skips ExecForeignBatchInsert if there is a RETURNING - * clause anyway.:contentReference[oaicite:5]{index=5} + int resultLength = (fmstate) ? (sizeof(fmstate->resultList)/sizeof(DB2ResultColumn*)) : 0; + /* If the foreign table has no columns, disable batching as the INSERT syntax doesn't allow batching multiple empty rows into a zero-column + * table in a single statement. + * This is needed for COPY FROM, in which case fmstate must be non-NULL. */ - batch_size = db2_get_batch_size_option(rel); + if (resultLength == 0) { + batch_size = 1; + } else { + int paramLength = (fmstate) ? (sizeof(fmstate->paramList)/sizeof(ParamDesc*)) : 0; + /* Otherwise use the batch size specified for server/table. + * The number of parameters in a batch is limited to 65535 (uint16), + * so make sure we don't exceed this limit by using the maximum batch_size possible. + */ + if (paramLength > 0) { + batch_size = Min(batch_size, (PQ_QUERY_PARAM_MAX_LIMIT / paramLength)); + } + } } - db2Debug1("< db2GetForeignModifyBatchSize - batch_size: %d", batch_size); return batch_size; } -int db2_get_batch_size_option(Relation rel) { + +/* db2GetForeignModifyBatchSize + * + * Returns the batch size to use for INSERTs into this foreign table. + * + * Returning 1 tells the executor to not batch (i.e., call ExecForeignInsert per-row). + * Any value > 1 enables batching, subject to what the executor actually decides to send. + */ +//int db2GetForeignModifyBatchSize(ResultRelInfo *rinfo) { +// Relation rel = rinfo->ri_RelationDesc; +// int batch_size = 0; +// +// db2Debug1("> db2GetForeignModifyBatchSize"); +// /* If the table has BEFORE/AFTER ROW triggers or a RETURNING clause is involved, it’s safer to disable batching and just do per-row inserts. +// * That's what postgres_fdw does as well.:contentReference[oaicite:4]{index=4} +// */ +// if (rel->trigdesc && (rel->trigdesc->trig_insert_before_row || rel->trigdesc->trig_insert_after_row)) { +// batch_size = 1; +// } else { +// /* We don't have easy access to "has RETURNING" here like postgres_fdw does (it stores that in its modify state), but PostgreSQL core +// * currently skips ExecForeignBatchInsert if there is a RETURNING clause anyway.:contentReference[oaicite:5]{index=5} +// */ +// batch_size = db2_get_batch_size_option(rel); +// } +// db2Debug1("< db2GetForeignModifyBatchSize - batch_size: %d", batch_size); +// return batch_size; +//} + +static int db2_get_batch_size_option(Relation rel) { Oid relid = RelationGetRelid(rel); ForeignTable* table; ForeignServer* server; From 14ec5b8c8227e518c131674d0ada14afe6d91345 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Fri, 13 Feb 2026 16:24:42 +0100 Subject: [PATCH 053/120] removed foreign.h and pathnodes.h since they are included in DB2FdwState.h now --- source/db2GetFdwState.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/source/db2GetFdwState.c b/source/db2GetFdwState.c index 0086b33..8f576d0 100644 --- a/source/db2GetFdwState.c +++ b/source/db2GetFdwState.c @@ -1,7 +1,5 @@ #include -#include #include -#include #include #include #include From af0a84c87b68b19846a89b479618624146872c4b Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Fri, 13 Feb 2026 16:24:56 +0100 Subject: [PATCH 054/120] enhance debug tracing --- source/db2_deparse.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/db2_deparse.c b/source/db2_deparse.c index a504b85..089f26a 100644 --- a/source/db2_deparse.c +++ b/source/db2_deparse.c @@ -1281,10 +1281,12 @@ List* build_tlist_to_deparse(RelOptInfo* foreignrel) { /* For an upper relation, we have already built the target list while checking shippability, so just return that. */ if (IS_UPPER_REL(foreignrel)) { tlist = fpinfo->grouped_tlist; + db2Debug2(" using fpinfo->grouped_tlist"); } else { ListCell* lc = NULL; /* We require columns specified in foreignrel->reltarget->exprs and those required for evaluating the local conditions. */ + db2Debug2(" using foreignrel->reltarget->exprs"); tlist = add_to_flat_tlist(tlist, pull_var_clause((Node*) foreignrel->reltarget->exprs, PVC_RECURSE_PLACEHOLDERS)); foreach(lc, fpinfo->local_conds) { RestrictInfo* rinfo = lfirst_node(RestrictInfo, lc); From d2f069d80cb7b699b0dfafbbdadfd61d085d22d7 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Fri, 13 Feb 2026 16:25:08 +0100 Subject: [PATCH 055/120] enhance debug tracing --- source/db2AddForeignUpdateTargets.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/source/db2AddForeignUpdateTargets.c b/source/db2AddForeignUpdateTargets.c index b2f21d2..cf5e26d 100644 --- a/source/db2AddForeignUpdateTargets.c +++ b/source/db2AddForeignUpdateTargets.c @@ -7,6 +7,7 @@ #include #include #include +#include #include "db2_fdw.h" /** external prototypes */ @@ -14,9 +15,8 @@ extern bool optionIsTrue (const char* value); #endif extern void db2Debug1 (const char* message, ...); -#if PG_VERSION_NUM < 140000 +extern void db2Debug2 (const char* message, ...); extern char* db2strdup (const char* source); -#endif /** local prototypes */ #if PG_VERSION_NUM < 140000 @@ -38,7 +38,7 @@ void db2AddForeignUpdateTargets (PlannerInfo* root, Index rtindex,RangeTblEntry* int i = 0; bool has_key = false; db2Debug1("> db2AddForeignUpdateTargets"); - db2Debug1(" add target columns for update on %d", relid); + db2Debug2(" add target columns for update on %d - %s", relid, get_rel_name(relid)); /* loop through all columns of the foreign table */ for (i = 0; i < tupdesc->natts; ++i) { Form_pg_attribute att = TupleDescAttr (tupdesc, i); @@ -79,6 +79,7 @@ void db2AddForeignUpdateTargets (PlannerInfo* root, Index rtindex,RangeTblEntry* , att->attcollation , 0 ); + db2Debug2(" add resjunk for column %d - %s at index %d", attrno, NameStr(att->attname),rtindex); add_row_identity_var(root, var, rtindex, NameStr(att->attname)); #endif /* PG_VERSION_NUM */ has_key = true; From a7b40a0df832aa8d5503ce7dc8908214fe059ba7 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Fri, 13 Feb 2026 16:26:42 +0100 Subject: [PATCH 056/120] when one column signals inclusion of all colums the logic was not coping with that - it is now. --- source/db2GetForeignPlan.c | 123 +++++++++++++++++++++++-------------- 1 file changed, 78 insertions(+), 45 deletions(-) diff --git a/source/db2GetForeignPlan.c b/source/db2GetForeignPlan.c index bd7add5..fec79c2 100644 --- a/source/db2GetForeignPlan.c +++ b/source/db2GetForeignPlan.c @@ -50,9 +50,10 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo List* remote_exprs = NIL; List* local_exprs = NIL; List* params_list = NIL; - List* fdw_scan_tlist = NIL; List* fdw_recheck_quals = NIL; List* retrieved_attrs = NIL; + List* ptlist = NIL; + int ptlist_len = 0; ListCell* lc = NULL; bool has_final_sort = false; bool has_limit = false; @@ -66,43 +67,64 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo has_limit = boolVal(list_nth(best_path->fdw_private, FdwPathPrivateHasLimit)); } + db2Debug2(" length of tlist: %d", list_length(tlist)); + + ptlist = build_tlist_to_deparse(foreignrel); + ptlist_len = list_length(ptlist); + if (IS_SIMPLE_REL(foreignrel)) { - DB2ResultColumn** cols = NULL; - ListCell* cell = NULL; - int resnum = 0; + DB2ResultColumn** cols = NULL; + ListCell* cell = NULL; + int iResCol = 0; /* For base relations, set scan_relid as the relid of the relation. */ scan_relid = foreignrel->relid; - /* find all the columns to include in the select list */ - /* examine each SELECT list entry for Var nodes */ - db2Debug3(" size of columnlist: %d", list_length(foreignrel->reltarget->exprs)); - cols = (DB2ResultColumn**)db2alloc("resultColumns", list_length(foreignrel->reltarget->exprs) * sizeof(DB2ResultColumn*)); - foreach (cell, foreignrel->reltarget->exprs) { - db2Debug3(" examine column %d", resnum); - cols[resnum] = (DB2ResultColumn*)db2alloc("resultColumn",sizeof(DB2ResultColumn)); - getUsedColumns ((Expr*) lfirst (cell), foreignrel, cols[resnum]); - db2Debug3(" cols[%d]->colName %s added to result list", resnum, cols[resnum]->colName); - resnum++; - } - // sort the array in ascending order of the pgattnum, so that we can compare it with the order of columns in the foreign table - db2Debug3(" sorting result columns by pgattnum"); - qsort(cols, list_length(foreignrel->reltarget->exprs), sizeof(DB2ResultColumn*), compareResultColumns); - // generate the sorted array into the resultList - for (int idx = 0; idx < list_length(foreignrel->reltarget->exprs); idx++) { - db2Debug3(" result column %d: %s", idx, cols[idx]->colName); - cols[idx]->next = fpinfo->resultList; - cols[idx]->resnum = idx+1; - db2Debug3(" column %s added to result list with resnum %d", cols[idx]->colName, cols[idx]->resnum); - fpinfo->resultList = cols[idx]; - } - /* examine each condition for Var nodes */ - db2Debug3(" size of conditions: %d", list_length(foreignrel->baserestrictinfo)); - foreach (cell, foreignrel->baserestrictinfo) { - db2Debug3(" examine condition"); - getUsedColumns ((Expr*) lfirst (cell), foreignrel, NULL); + if (ptlist_len >0) { + DB2ResultColumn* resCol = NULL; + /* find all the columns to include in the select list */ + /* examine each SELECT list entry for Var nodes */ + db2Debug3(" size of tlist: %d", ptlist_len); + foreach (cell, ptlist) { + resCol = (DB2ResultColumn*)db2alloc("resultColumn",sizeof(DB2ResultColumn)); + getUsedColumns ((Expr*) lfirst (cell), foreignrel, resCol); + resCol->next = fpinfo->resultList; + db2Debug3(" resCol->next: %x", resCol->next); + fpinfo->resultList = resCol; + db2Debug3(" fpinfo->resultList: %x", fpinfo->resultList); + } + for (resCol = fpinfo->resultList; resCol; resCol = resCol->next) { + iResCol++; + } + cols = (DB2ResultColumn**)db2alloc("resultColumns", iResCol+1 * sizeof(DB2ResultColumn*)); + iResCol = 0; + for (resCol = fpinfo->resultList; resCol; resCol = resCol->next) { + db2Debug2(" resCol: %x", resCol); + cols[iResCol] = resCol; + db2Debug2(" cols[%d]: %x", iResCol, cols[iResCol]); + db2Debug2(" cols[%d]->resnum: %d", iResCol, cols[iResCol]->resnum); + iResCol++; + db2Debug2(" resCol->next: %x", resCol->next); + } + // sort the array in ascending order of the pgattnum, so that we can compare it with the order of columns in the foreign table + db2Debug3(" sorting result cols %d columns by pgattnum", iResCol); + qsort(cols, iResCol, sizeof(DB2ResultColumn*), compareResultColumns); + // generate the sorted array into the resultList + fpinfo->resultList = NULL; + for (int idx = 0; idx < iResCol; idx++) { + db2Debug3(" result column %d: %s", idx, cols[idx]->colName); + cols[idx]->next = fpinfo->resultList; + cols[idx]->resnum = idx+1; + db2Debug3(" column %s added to result list with resnum %d", cols[idx]->colName, cols[idx]->resnum); + fpinfo->resultList = cols[idx]; + } + /* examine each condition for Var nodes */ + db2Debug3(" size of conditions: %d", list_length(foreignrel->baserestrictinfo)); + foreach (cell, foreignrel->baserestrictinfo) { + db2Debug3(" examine condition"); + getUsedColumns ((Expr*) lfirst (cell), foreignrel, NULL); + } } - /* In a base-relation scan, we must apply the given scan_clauses. * * Separate the scan_clauses into those that can be executed remotely and those that can't. @@ -154,14 +176,13 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo * * Build the list of columns to be fetched from the foreign server. */ - fdw_scan_tlist = build_tlist_to_deparse(foreignrel); - if (list_length(fdw_scan_tlist) > 0) { + if (ptlist_len > 0) { ListCell* cell = NULL; int resnum = 1; /* examine each condition for Tlist nodes; they come in the correct sequence as in the query and do not need to be sorted */ - db2Debug3(" size of tlist: %d", list_length(fdw_scan_tlist)); - foreach (cell, fdw_scan_tlist) { + db2Debug3(" size of tlist: %d", ptlist_len); + foreach (cell, ptlist) { DB2ResultColumn* resCol = (DB2ResultColumn*)db2alloc("resultColumn",sizeof(DB2ResultColumn)); db2Debug3(" examine tlist"); resCol->next = fpinfo->resultList; @@ -199,13 +220,13 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo } } /* Now fix the subplan's tlist --- this might result in inserting a Result node atop the plan tree. */ - outer_plan = change_plan_targetlist(outer_plan, fdw_scan_tlist, best_path->path.parallel_safe); + outer_plan = change_plan_targetlist(outer_plan, tlist, best_path->path.parallel_safe); } } /* Build the query string to be sent for execution, and identify expressions to be sent as parameters. */ initStringInfo(&sql); - deparseSelectStmtForRel(&sql, root, foreignrel, fdw_scan_tlist, remote_exprs, best_path->path.pathkeys, has_final_sort, has_limit, false, &retrieved_attrs, ¶ms_list); + deparseSelectStmtForRel(&sql, root, foreignrel, ptlist, remote_exprs, best_path->path.pathkeys, has_final_sort, has_limit, false, &retrieved_attrs, ¶ms_list); db2Debug2(" deparsed foreign query: %s", sql.data); /* Remember remote_exprs for possible use by postgresPlanDirectModify */ fpinfo->final_remote_exprs = remote_exprs; @@ -213,7 +234,7 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo /* Build the fdw_private list that will be available to the executor. * Items in the list must match order in enum FdwScanPrivateIndex. */ - fpinfo->db2Table->rncols = list_length(fdw_scan_tlist); + fpinfo->db2Table->rncols = ptlist_len; fpinfo->query = sql.data; fpinfo->retrieved_attr = retrieved_attrs; fdw_private = serializePlanData(fpinfo); @@ -224,7 +245,7 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo * field of the finished plan node; we can't keep them in private state * because then they wouldn't be subject to later planner processing. */ - fscan = make_foreignscan(tlist, local_exprs, scan_relid, params_list, fdw_private, fdw_scan_tlist, fdw_recheck_quals, outer_plan); + fscan = make_foreignscan(tlist, local_exprs, scan_relid, params_list, fdw_private, ptlist, fdw_recheck_quals, outer_plan); db2Debug1("< %s::db2GetForeignPlan : %x",__FILE__,fscan); return fscan; } @@ -263,14 +284,26 @@ static void getUsedColumns (Expr* expr, RelOptInfo* foreignrel, DB2ResultColumn* break; /* if this is a wholerow reference, we need all columns */ if (variable->varattno == 0) { - for (index = 0; index < fpinfo->db2Table->ncols; ++index) { + DB2ResultColumn* tmpCol = NULL; + db2Debug2(" found whole-row reference, need to add all columns"); + db2Debug2(" fpinfo->resultList: %x", fpinfo->resultList); + // add all columns but the last one here + for (index = 0; index < (fpinfo->db2Table->ncols - 1); index++) { if (fpinfo->db2Table->cols[index]->pgname) { - addResult(resCol,fpinfo->db2Table->cols[index]); - resCol = (DB2ResultColumn*)db2alloc("resultColumn",sizeof(DB2ResultColumn)); - resCol->next = fpinfo->resultList; - fpinfo->resultList = resCol; + tmpCol = (DB2ResultColumn*)db2alloc("resultColumn",sizeof(DB2ResultColumn)); + tmpCol->resnum = index+1; + addResult(tmpCol,fpinfo->db2Table->cols[index]); + db2Debug2(" db2Table[%d]->colName %s added to result list", index, fpinfo->db2Table->cols[index]->colName); + tmpCol->next = fpinfo->resultList; + db2Debug2(" tmpCol-next: %x", tmpCol->next); + fpinfo->resultList = tmpCol; + db2Debug2(" fpinfo->resultList: %x", fpinfo->resultList); } } + // now add the last colum using the resCol passed in, so that the column name in the result list is correct for whole row reference + addResult(resCol,fpinfo->db2Table->cols[index]); + resCol->resnum = index+1; + db2Debug3(" db2Table[%d]->colName %s added to result list", index, fpinfo->db2Table->cols[index]->colName); break; } /* get db2Table column index corresponding to this column (-1 if none) */ From 0e87370779c35d9820a5d8eec907b797161729c2 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Fri, 13 Feb 2026 16:47:11 +0100 Subject: [PATCH 057/120] enhanced debug tracing --- source/db2Callbacks.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/db2Callbacks.c b/source/db2Callbacks.c index 31844e5..bf518a3 100644 --- a/source/db2Callbacks.c +++ b/source/db2Callbacks.c @@ -40,7 +40,7 @@ void db2UnregisterCallback (void *arg) { * Commit or rollback DB2 transactions when appropriate. */ void transactionCallback (XactEvent event, void *arg) { - db2Debug1("> transactionCallback"); + db2Debug1("> transactionCallback(Event %d, Arg %x)", event, arg); switch (event) { case XACT_EVENT_PRE_COMMIT: case XACT_EVENT_PARALLEL_PRE_COMMIT: From 493221db5c7d33f3d76b54c4fe2e48d7bcad954e Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Sat, 14 Feb 2026 16:31:45 +0100 Subject: [PATCH 058/120] renaming Db2Column.varno to pgrelid which is the index of the relation in the query (e.g. in joins 1., 2., .. table) --- include/DB2Column.h | 36 ++++++++++++++++----------------- include/DB2ResultColumn.h | 2 +- source/db2GetForeignJoinPaths.c | 24 +++++++++++----------- source/db2GetForeignPlan.c | 2 +- source/db2GetForeignRelSize.c | 2 +- 5 files changed, 33 insertions(+), 33 deletions(-) diff --git a/include/DB2Column.h b/include/DB2Column.h index 648326f..cf70d05 100644 --- a/include/DB2Column.h +++ b/include/DB2Column.h @@ -7,24 +7,24 @@ * @since 1.0 */ typedef struct db2Column { - char* colName; // column name in DB2 - short colType; // column data type in DB2 - size_t colSize; // column size - short colScale; // column scale of size describing digits right of decimal point - short colNulls; // column is nullable - size_t colChars; // numer of characters fit in column size, it is less if UTF8, 16 or DBCS - size_t colBytes; // number of bytes representing colSize - int colPrimKeyPart;// 1 if column is part of the primary key - only relevant for UPDATE or DELETE - int colCodepage; // codepage set for this column (only set on char columns), if 0 the content is binary - char* pgname; // PG column name - int pgattnum; // PG attribute number - Oid pgtype; // PG data type - int pgtypmod; // PG type modifier - int used; // is the column used in the query? - int pkey; // nonzero for primary keys, later set to the resjunk attribute number - size_t val_size; // allocated size in val - int varno; // range table index of this column's relation - db2NoEncErrType noencerr; // no encoding error produced + char* colName; // column name in DB2 + short colType; // column data type in DB2 + size_t colSize; // column size + short colScale; // column scale of size describing digits right of decimal point + short colNulls; // column is nullable + size_t colChars; // numer of characters fit in column size, it is less if UTF8, 16 or DBCS + size_t colBytes; // number of bytes representing colSize + int colPrimKeyPart; // 1 if column is part of the primary key - only relevant for UPDATE or DELETE + int colCodepage; // codepage set for this column (only set on char columns), if 0 the content is binary + int pgrelid; // range table index of this column's relation + char* pgname; // PG column name + int pgattnum; // PG attribute number + Oid pgtype; // PG data type + int pgtypmod; // PG type modifier + int used; // is the column used in the query? + int pkey; // nonzero for primary keys, later set to the resjunk attribute number + size_t val_size; // allocated size in val + db2NoEncErrType noencerr; // no encoding error produced } DB2Column; #endif \ No newline at end of file diff --git a/include/DB2ResultColumn.h b/include/DB2ResultColumn.h index 0891997..a438775 100644 --- a/include/DB2ResultColumn.h +++ b/include/DB2ResultColumn.h @@ -16,6 +16,7 @@ typedef struct db2ResultColumn { size_t colBytes; // number of bytes representing colSize int colPrimKeyPart;// 1 if column is part of the primary key - only relevant for UPDATE or DELETE int colCodepage; // codepage set for this column (only set on char columns), if 0 the content is binary + int pgbaserelid; // range table index of this column's relation char* pgname; // PG column name int pgattnum; // PG attribute number Oid pgtype; // PG data type @@ -26,7 +27,6 @@ typedef struct db2ResultColumn { size_t val_size; // allocated size in val size_t val_len; // actual length of val int val_null; // indicator for NULL value - int varno; // range table index of this column's relation db2NoEncErrType noencerr; // no encoding error produced struct db2ResultColumn* next; } DB2ResultColumn; diff --git a/source/db2GetForeignJoinPaths.c b/source/db2GetForeignJoinPaths.c index 700cca1..ffb7d23 100644 --- a/source/db2GetForeignJoinPaths.c +++ b/source/db2GetForeignJoinPaths.c @@ -261,7 +261,7 @@ static bool foreign_join_ok (PlannerInfo * root, RelOptInfo * joinrel, JoinType for (i = 0; i < db2Table_o->ncols; ++i) { struct db2Column *tmp = db2Table_o->cols[i]; - if (tmp->varno == var->varno) { + if (tmp->pgrelid == var->varno) { tabname = db2Table_o->pgname; if (tmp->pgattnum == var->varattno) { @@ -274,7 +274,7 @@ static bool foreign_join_ok (PlannerInfo * root, RelOptInfo * joinrel, JoinType for (i = 0; i < db2Table_i->ncols; ++i) { struct db2Column *tmp = db2Table_i->cols[i]; - if (tmp->varno == var->varno) { + if (tmp->pgrelid == var->varno) { tabname = db2Table_i->pgname; if (tmp->pgattnum == var->varattno) { @@ -290,16 +290,16 @@ static bool foreign_join_ok (PlannerInfo * root, RelOptInfo * joinrel, JoinType memcpy (newcol, col, sizeof (struct db2Column)); used_flag = 1; } else { - /* non-existing column, print a warning */ - ereport (WARNING - ,(errcode(ERRCODE_WARNING) - ,errmsg ("column number %d of foreign table \"%s\" does not exist in foreign DB2 table, will be replaced by NULL" - ,var->varattno - ,tabname - ) - ) - ); - } + /* non-existing column, print a warning */ + ereport (WARNING + ,(errcode(ERRCODE_WARNING) + ,errmsg ("column number %d of foreign table \"%s\" does not exist in foreign DB2 table, will be replaced by NULL" + ,var->varattno + ,tabname + ) + ) + ); + } newcol->used = used_flag; /* pgattnum should be the index in SELECT clause of join query. */ newcol->pgattnum = fdwState->db2Table->ncols + 1; diff --git a/source/db2GetForeignPlan.c b/source/db2GetForeignPlan.c index fec79c2..658174e 100644 --- a/source/db2GetForeignPlan.c +++ b/source/db2GetForeignPlan.c @@ -542,13 +542,13 @@ static void addResult(DB2ResultColumn* resCol, DB2Column* column) { resCol->colBytes = column->colBytes; resCol->colPrimKeyPart = column->colPrimKeyPart; resCol->colCodepage = column->colCodepage; + resCol->pgbaserelid = column->pgrelid; resCol->pgname = db2strdup(column->pgname); resCol->pgattnum = column->pgattnum; resCol->pgtype = column->pgtype; resCol->pgtypmod = column->pgtypmod; resCol->pkey = column->pkey; resCol->val_size = column->val_size; - resCol->varno = column->varno; resCol->noencerr = column->noencerr; } db2Debug1("< %s::addResult",__FILE__); diff --git a/source/db2GetForeignRelSize.c b/source/db2GetForeignRelSize.c index 83352d1..3b765a3 100644 --- a/source/db2GetForeignRelSize.c +++ b/source/db2GetForeignRelSize.c @@ -57,7 +57,7 @@ static void db2PopulateFdwStateOld(PlannerInfo* root, RelOptInfo* baserel, Oid f * have columns from different tables, and we have to keep track of them. */ for (i = 0; i < fdwState->db2Table->ncols; ++i) { - fdwState->db2Table->cols[i]->varno = baserel->relid; + fdwState->db2Table->cols[i]->pgrelid = baserel->relid; } /** Classify conditions into remote_conds or local_conds. * These parameters are used in foreign_join_ok and db2GetForeignPlan. From 27be15f04b4912883bd1cc6dd1299a7644348d8f Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Sat, 14 Feb 2026 16:32:04 +0100 Subject: [PATCH 059/120] proper formatting of pointers --- source/db2GetFdwState.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/db2GetFdwState.c b/source/db2GetFdwState.c index 8f576d0..9438068 100644 --- a/source/db2GetFdwState.c +++ b/source/db2GetFdwState.c @@ -31,7 +31,7 @@ bool optionIsTrue (const char* value); * "sample_percent" is set from the foreign table options. * "sample_percent" can be NULL, in that case it is not set. */ -DB2FdwState* db2GetFdwState (Oid foreigntableid, double *sample_percent, bool describe) { +DB2FdwState* db2GetFdwState (Oid foreigntableid, double* sample_percent, bool describe) { DB2FdwState* fdwState = db2alloc("fdw_state", sizeof (DB2FdwState)); char* pgtablename = get_rel_name (foreigntableid); List* options; From 7ae9dcfbc017e7acd2efb075a74f7ee0c95d016b Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Sat, 14 Feb 2026 16:33:11 +0100 Subject: [PATCH 060/120] actually for analyze we seem not to be needing any results. The code generates a dummy result capture which is sufficient. --- source/db2AnalyzeForeignTable.c | 70 +++++++++++++-------------------- 1 file changed, 27 insertions(+), 43 deletions(-) diff --git a/source/db2AnalyzeForeignTable.c b/source/db2AnalyzeForeignTable.c index e5d9348..d4fba8d 100644 --- a/source/db2AnalyzeForeignTable.c +++ b/source/db2AnalyzeForeignTable.c @@ -26,7 +26,7 @@ extern void* db2alloc (const char* type, size_t size); bool db2AnalyzeForeignTable(Relation relation, AcquireSampleRowsFunc* func, BlockNumber* totalpages); int acquireSampleRowsFunc (Relation relation, int elevel, HeapTuple* rows, int targrows, double* totalrows, double* totaldeadrows); -/** db2AnalyzeForeignTable +/* db2AnalyzeForeignTable * */ bool db2AnalyzeForeignTable (Relation relation, AcquireSampleRowsFunc* func, BlockNumber* totalpages) { @@ -38,21 +38,23 @@ bool db2AnalyzeForeignTable (Relation relation, AcquireSampleRowsFunc* func, Blo return true; } -/** acquireSampleRowsFunc - * Perform a sequential scan on the DB2 table and return a sampe of rows. - * All LOB values are truncated to WIDTH_THRESHOLD+1 because anything - * exceeding this is not used by compute_scalar_stats(). +/* acquireSampleRowsFunc + * Perform a sequential scan on the DB2 table and return a sample of rows. + * All LOB values are truncated to WIDTH_THRESHOLD+1 because anything exceeding this is not used by compute_scalar_stats(). */ int acquireSampleRowsFunc (Relation relation, int elevel, HeapTuple* rows, int targrows, double* totalrows, double* totaldeadrows) { - int collected_rows = 0, i; - DB2FdwState* fdw_state; - bool first_column = true; - StringInfoData query; - TupleDesc tupDesc = RelationGetDescr (relation); - Datum* values = (Datum*) db2alloc("values", tupDesc->natts* sizeof (Datum)); - bool* nulls = (bool*) db2alloc("null" , tupDesc->natts* sizeof (bool)); - double rstate, rowstoskip = -1, sample_percent; - MemoryContext old_cxt, tmp_cxt; + int collected_rows = 0; + DB2FdwState* fdw_state = NULL; + bool first_column = true; + StringInfoData query; + TupleDesc tupDesc = RelationGetDescr (relation); + Datum* values = (Datum*) db2alloc("values", tupDesc->natts* sizeof (Datum)); + bool* nulls = (bool*) db2alloc("null" , tupDesc->natts* sizeof (bool)); + double rstate = 0; + double rowstoskip = -1; + double sample_percent = 0; + MemoryContext old_cxt; + MemoryContext tmp_cxt; db2Debug1("> acquireSampleRowsFunc"); elog (DEBUG1, "db2_fdw: analyze foreign table %d", RelationGetRelid (relation)); @@ -66,32 +68,20 @@ int acquireSampleRowsFunc (Relation relation, int elevel, HeapTuple* rows, int t rstate = anl_init_selection_state (targrows); /* get connection options, connect and get the remote table description */ - fdw_state = db2GetFdwState (RelationGetRelid (relation), &sample_percent, true); - fdw_state->paramList = NULL; - fdw_state->rowcount = 0; + fdw_state = db2GetFdwState (RelationGetRelid (relation), &sample_percent, true); + fdw_state->paramList = NULL; + fdw_state->rowcount = 0; /* construct query */ initStringInfo (&query); appendStringInfo (&query, "SELECT "); /* loop columns */ - for (i = 0; i < fdw_state->db2Table->ncols; ++i) { - /* don't get LONG, LONG RAW and untranslatable values */ - short dbType = c2dbType(fdw_state->db2Table->cols[i]->colType); - if (dbType == DB2_BIGINT || dbType == DB2_UNKNOWN_TYPE) { - fdw_state->db2Table->cols[i]->used = 0; - } else { - db2Debug2(" fdw_state->db2Table->cols[%d]->name: %s",i,fdw_state->db2Table->cols[i]->colName); - /* all columns are used */ - fdw_state->db2Table->cols[i]->used = 1; - db2Debug2(" fdw_state->db2Table->cols[%d]->used: %d",i,fdw_state->db2Table->cols[i]->used); - if (first_column) - first_column = false; - else - appendStringInfo (&query, ", "); - - /* append column name */ - appendStringInfo (&query, "%s", fdw_state->db2Table->cols[i]->colName); + for (int i = 0; i < fdw_state->db2Table->ncols; ++i) { + if (DB2_UNKNOWN_TYPE != c2dbType(fdw_state->db2Table->cols[i]->colType)) { + checkDataType(fdw_state->db2Table->cols[i]->colType,fdw_state->db2Table->cols[i]->colScale,fdw_state->db2Table->cols[i]->pgtype,fdw_state->db2Table->pgname,fdw_state->db2Table->cols[i]->pgname); + appendStringInfo (&query, "%s%s", ((first_column) ? "" : ", "), fdw_state->db2Table->cols[i]->colName); + first_column = false; } } @@ -109,11 +99,6 @@ int acquireSampleRowsFunc (Relation relation, int elevel, HeapTuple* rows, int t fdw_state->query = query.data; elog (DEBUG2, " fdw_state->query: '%s'", fdw_state->query); - /* get PostgreSQL column data types, check that they match DB2's */ - for (i = 0; i < fdw_state->db2Table->ncols; ++i) - if (fdw_state->db2Table->cols[i]->used) - checkDataType (fdw_state->db2Table->cols[i]->colType, fdw_state->db2Table->cols[i]->colScale, fdw_state->db2Table->cols[i]->pgtype, fdw_state->db2Table->pgname, fdw_state->db2Table->cols[i]->pgname); - db2Debug3(" loop through query results"); /* loop through query results */ fdw_state->rowcount = -1; @@ -136,8 +121,7 @@ int acquireSampleRowsFunc (Relation relation, int elevel, HeapTuple* rows, int t rows[collected_rows++] = heap_form_tuple (tupDesc, values, nulls); MemoryContextReset (tmp_cxt); } else { - /* - * Skip a number of rows before replacing a random sample row. + /* Skip a number of rows before replacing a random sample row. * A more detailed description of the algorithm can be found in analyze.c */ if (rowstoskip < 0) { @@ -158,8 +142,8 @@ int acquireSampleRowsFunc (Relation relation, int elevel, HeapTuple* rows, int t MemoryContextDelete (tmp_cxt); - *totalrows = (double) fdw_state->rowcount / sample_percent * 100.0; - *totaldeadrows = 0; + *totalrows = (double) fdw_state->rowcount / sample_percent * 100.0; + *totaldeadrows = 0; /* report report */ ereport (elevel, (errmsg ("\"%s\": table contains %lu rows; %d rows in sample", RelationGetRelationName (relation), fdw_state->rowcount, collected_rows-1))); From 1c3b3cb5085b9f83a9a1343a71daa1d241fb9696 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Sat, 14 Feb 2026 16:53:13 +0100 Subject: [PATCH 061/120] fixed a typo in debug output --- source/db2ImportForeignSchema.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/db2ImportForeignSchema.c b/source/db2ImportForeignSchema.c index fc299e3..c3eed6d 100644 --- a/source/db2ImportForeignSchema.c +++ b/source/db2ImportForeignSchema.c @@ -126,7 +126,7 @@ List* db2ImportForeignSchema (ImportForeignSchemaStmt* stmt, Oid serverOid) { db2Debug2(" stmt->local_schema : '%s'",stmt->local_schema); db2Debug2(" stmt->remote_schema: '%s'",stmt->remote_schema); db2Debug2(" stmt->server_name : '%s'",stmt->server_name); - db2Debug2(" stmt->tabel_list : '%x'",stmt->table_list); + db2Debug2(" stmt->table_list : '%x'",stmt->table_list); db2Debug2(" stmt->type : %d ",stmt->type); initStringInfo (&tblist); From 4644f2b9f58c5a9cfa70be93ab949b10c7e7c030 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Sun, 15 Feb 2026 14:39:41 +0100 Subject: [PATCH 062/120] add all the db2 column attributes to the fdw column option. with that we ought to be able to no longer need to db2Describe the table(s) in use at the begining of a query and safe those roundtrips to the DB2 catalog. --- include/db2_fdw.h | 8 +++++++- source/db2ImportForeignSchema.c | 23 +++++++++++++---------- source/db2_fdw.c | 17 ++++++++++++++--- 3 files changed, 34 insertions(+), 14 deletions(-) diff --git a/include/db2_fdw.h b/include/db2_fdw.h index f1d76d8..1f32229 100644 --- a/include/db2_fdw.h +++ b/include/db2_fdw.h @@ -27,7 +27,6 @@ #endif /* PQ_QUERY_PARAM_MAX_LIMIT */ #undef OLD_FDW_API -#define IMPORT_API /* array_create_iterator has a new signature from 9.5 on */ #define array_create_iterator(arr, slice_ndim) array_create_iterator(arr, slice_ndim, NULL) @@ -128,6 +127,13 @@ typedef enum { #define OPT_MAX_LONG "max_long" #define OPT_READONLY "readonly" #define OPT_KEY "key" + +#define OPT_DB2TYPE "db2type" +#define OPT_DB2SIZE "db2size" +#define OPT_DB2SCALE "db2scale" +#define OPT_DB2NULL "db2null" +#define OPT_DB2CCSID "db2ccsid" + #define OPT_SAMPLE "sample_percent" #define OPT_PREFETCH "prefetch" #define OPT_NO_ENCODING_ERROR "no_encoding_error" diff --git a/source/db2ImportForeignSchema.c b/source/db2ImportForeignSchema.c index c3eed6d..6fbc9d1 100644 --- a/source/db2ImportForeignSchema.c +++ b/source/db2ImportForeignSchema.c @@ -20,9 +20,7 @@ extern char* db2strdup (const char* source); /** local prototypes */ List* db2ImportForeignSchema(ImportForeignSchemaStmt* stmt, Oid serverOid); -#ifdef IMPORT_API -char* fold_case (char* name, fold_t foldcase); -#endif +static char* fold_case (char* name, fold_t foldcase); /** db2ImportForeignSchema * Returns a List of CREATE FOREIGN TABLE statements. @@ -269,8 +267,15 @@ List* db2ImportForeignSchema (ImportForeignSchemaStmt* stmt, Oid serverOid) { break; } /* part of the primary key */ - if (key) - appendStringInfo (&buf, " OPTIONS (key 'true')"); + appendStringInfo (&buf + , " OPTIONS (%s '%d', %s '%ld', %s '%d', %s '%d', %s '%d'%s)" + ,OPT_DB2TYPE , colType + ,OPT_DB2SIZE , colSize + ,OPT_DB2SCALE, colScale + ,OPT_DB2NULL , colNulls + ,OPT_DB2CCSID, cp + , (key) ? ", key 'true'" : "" + ); /* not nullable */ if (!colNulls) appendStringInfo (&buf, " NOT NULL"); @@ -280,11 +285,10 @@ List* db2ImportForeignSchema (ImportForeignSchemaStmt* stmt, Oid serverOid) { return result; } -#ifdef IMPORT_API -/** fold_case - * Returns a dup'ed string that is the case-folded first argument. +/* fold_case + * Returns a dup'ed string that is the case-folded first argument. */ -char* fold_case (char *name, fold_t foldcase) { +static char* fold_case (char *name, fold_t foldcase) { char* result = NULL; db2Debug1("> fold_case"); if (foldcase == CASE_KEEP) { @@ -309,4 +313,3 @@ char* fold_case (char *name, fold_t foldcase) { db2Debug1("< fold_case - returns: '%s'", result); return result; } -#endif /* IMPORT_API */ diff --git a/source/db2_fdw.c b/source/db2_fdw.c index 6d6e4f8..e9e9a48 100644 --- a/source/db2_fdw.c +++ b/source/db2_fdw.c @@ -66,6 +66,11 @@ DB2FdwOption valid_options[] = { {OPT_PREFETCH , ForeignTableRelationId , false}, {OPT_FETCHSZ , ForeignTableRelationId , false}, {OPT_KEY , AttributeRelationId , false}, + {OPT_DB2TYPE , AttributeRelationId , false}, + {OPT_DB2SIZE , AttributeRelationId , false}, + {OPT_DB2SCALE , AttributeRelationId , false}, + {OPT_DB2NULL , AttributeRelationId , false}, + {OPT_DB2CCSID , AttributeRelationId , false}, #if PG_VERSION_NUM >= 140000 {OPT_BATCH_SIZE , ForeignServerRelationId , false}, {OPT_BATCH_SIZE , ForeignTableRelationId , false}, @@ -237,12 +242,18 @@ PGDLLEXPORT Datum db2_fdw_validator (PG_FUNCTION_ARGS) { ) ); } + /* check valid values for column options */ /* check valid values for max_long */ - if (strcmp (def->defname, OPT_MAX_LONG) == 0) { + if (strcmp (def->defname, OPT_DB2TYPE ) == 0 + || strcmp (def->defname, OPT_DB2NULL ) == 0 + || strcmp (def->defname, OPT_DB2SIZE ) == 0 + || strcmp (def->defname, OPT_DB2SCALE) == 0 + || strcmp (def->defname, OPT_DB2CCSID) == 0 + || strcmp (def->defname, OPT_MAX_LONG) == 0) { char *val = STRVAL(def->arg); char *endptr; - unsigned long max_long = strtoul (val, &endptr, 0); - if (val[0] == '\0' || *endptr != '\0' || max_long < 1 || max_long > 1073741823ul) + long lvalue = strtol (val, &endptr, 0); + if (val[0] == '\0' || *endptr != '\0' || lvalue < LONG_MIN || lvalue > LONG_MAX) ereport (ERROR , ( errcode(ERRCODE_FDW_INVALID_ATTRIBUTE_VALUE) , errmsg ("invalid value for option \"%s\"", def->defname) From 9ffaa232b4bf1dda21c2c07a2563b77c39421d41 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Sun, 15 Feb 2026 15:20:27 +0100 Subject: [PATCH 063/120] remove OLD_FDW_API precompile exclusions from the code --- source/db2AddForeignUpdateTargets.c | 2 -- source/db2GetFdwState.c | 17 +++++---------- source/db2GetForeignPaths.c | 11 +++------- source/db2IsForeignRelUpdatable.c | 2 -- source/db2IterateForeignScan.c | 6 ----- source/db2_deparse.c | 34 ----------------------------- 6 files changed, 9 insertions(+), 63 deletions(-) diff --git a/source/db2AddForeignUpdateTargets.c b/source/db2AddForeignUpdateTargets.c index cf5e26d..76037c3 100644 --- a/source/db2AddForeignUpdateTargets.c +++ b/source/db2AddForeignUpdateTargets.c @@ -11,9 +11,7 @@ #include "db2_fdw.h" /** external prototypes */ -#ifndef OLD_FDW_API extern bool optionIsTrue (const char* value); -#endif extern void db2Debug1 (const char* message, ...); extern void db2Debug2 (const char* message, ...); extern char* db2strdup (const char* source); diff --git a/source/db2GetFdwState.c b/source/db2GetFdwState.c index 9438068..606b39a 100644 --- a/source/db2GetFdwState.c +++ b/source/db2GetFdwState.c @@ -18,11 +18,9 @@ extern void* db2alloc (const char* type, size_t size); extern char* db2strdup (const char* source); /** local prototypes */ -DB2FdwState* db2GetFdwState(Oid foreigntableid, double* sample_percent, bool describe); -void getColumnData (DB2Table* db2Table, Oid foreigntableid); -#ifndef OLD_FDW_API -bool optionIsTrue (const char* value); -#endif + DB2FdwState* db2GetFdwState(Oid foreigntableid, double* sample_percent, bool describe); +static void getColumnData (DB2Table* db2Table, Oid foreigntableid); + bool optionIsTrue (const char* value); /** db2GetFdwState * Construct an DB2FdwState from the options of the foreign table. @@ -135,7 +133,7 @@ DB2FdwState* db2GetFdwState (Oid foreigntableid, double* sample_percent, bool de * Set db2Table->npgcols. * For PostgreSQL 9.2 and better, find the primary key columns and mark them in db2Table. */ -void getColumnData (DB2Table* db2Table, Oid foreigntableid) { +static void getColumnData (DB2Table* db2Table, Oid foreigntableid) { Relation rel; TupleDesc tupdesc; int i, index; @@ -187,9 +185,8 @@ void getColumnData (DB2Table* db2Table, Oid foreigntableid) { db2Debug2(" < getColumnData"); } -#ifndef OLD_FDW_API -/** optionIsTrue - * Returns true if the string is "true", "on" or "yes". +/* optionIsTrue + * Returns true if the string is "true", "on" or "yes". */ bool optionIsTrue (const char *value) { bool result = false; @@ -198,5 +195,3 @@ bool optionIsTrue (const char *value) { db2Debug3(" < optionIsTrue - returns: '%s'",((result) ? "true" : "false")); return result; } -#endif /* OLD_FDW_API */ - diff --git a/source/db2GetForeignPaths.c b/source/db2GetForeignPaths.c index 66035a6..dd6e436 100644 --- a/source/db2GetForeignPaths.c +++ b/source/db2GetForeignPaths.c @@ -12,10 +12,8 @@ extern void db2Debug1 (const char* message, ...); extern char* deparseExpr (PlannerInfo* root, RelOptInfo* foreignrel, Expr* expr, List** params); /** local prototypes */ -void db2GetForeignPaths (PlannerInfo* root, RelOptInfo* baserel, Oid foreigntableid); -#ifndef OLD_FDW_API + void db2GetForeignPaths (PlannerInfo* root, RelOptInfo* baserel, Oid foreigntableid); static Expr* find_em_expr_for_rel(EquivalenceClass * ec, RelOptInfo * rel); -#endif /** db2GetForeignPaths * Create a ForeignPath node and add it as only possible path. @@ -106,10 +104,8 @@ void db2GetForeignPaths(PlannerInfo* root, RelOptInfo* baserel, Oid foreigntable db2Debug1("< db2GetForeignPaths"); } -#ifndef OLD_FDW_API -/** find_em_expr_for_rel - * Find an equivalence class member expression, all of whose Vars come from - * the indicated relation. +/* find_em_expr_for_rel + * Find an equivalence class member expression, all of whose Vars come from the indicated relation. */ static Expr* find_em_expr_for_rel (EquivalenceClass* ec, RelOptInfo* rel) { ListCell* lc_em = NULL; @@ -130,4 +126,3 @@ static Expr* find_em_expr_for_rel (EquivalenceClass* ec, RelOptInfo* rel) { db2Debug1("< find_em_expr_for_rel - returns: %x", result); return result; } -#endif /* OLD_FDW_API */ diff --git a/source/db2IsForeignRelUpdatable.c b/source/db2IsForeignRelUpdatable.c index 6058079..e263d14 100644 --- a/source/db2IsForeignRelUpdatable.c +++ b/source/db2IsForeignRelUpdatable.c @@ -6,9 +6,7 @@ #include "db2_fdw.h" /** external prototypes */ -#ifndef OLD_FDW_API extern bool optionIsTrue (const char* value); -#endif extern void db2Debug1 (const char* message, ...); extern void db2Debug2 (const char* message, ...); diff --git a/source/db2IterateForeignScan.c b/source/db2IterateForeignScan.c index 7b448ed..d732d53 100644 --- a/source/db2IterateForeignScan.c +++ b/source/db2IterateForeignScan.c @@ -83,9 +83,7 @@ char* setSelectParameters (ParamDesc* paramList, ExprContext* econtext) { TimestampTz tstamp; bool is_null; bool first_param = true; -#ifndef OLD_FDW_API MemoryContext oldcontext; -#endif /* OLD_FDW_API */ StringInfoData info; /* list of parameters for DEBUG message */ db2Debug1("> setSelectParameters"); @@ -94,10 +92,8 @@ char* setSelectParameters (ParamDesc* paramList, ExprContext* econtext) { initStringInfo (&info); -#ifndef OLD_FDW_API /* switch to short lived memory context */ oldcontext = MemoryContextSwitchTo (econtext->ecxt_per_tuple_memory); -#endif /* OLD_FDW_API */ /* iterate parameter list and fill values */ for (param = paramList; param; param = param->next) { @@ -150,10 +146,8 @@ char* setSelectParameters (ParamDesc* paramList, ExprContext* econtext) { } } -#ifndef OLD_FDW_API /* reset memory context */ MemoryContextSwitchTo (oldcontext); -#endif /* OLD_FDW_API */ db2Debug1("< setSelectParameters - returns: '%s'",info.data); return info.data; diff --git a/source/db2_deparse.c b/source/db2_deparse.c index 089f26a..721a2d9 100644 --- a/source/db2_deparse.c +++ b/source/db2_deparse.c @@ -2009,11 +2009,6 @@ static void deparseConstExpr (Const* expr, deparse_expr_cxt* } static void deparseParamExpr (Param* expr, deparse_expr_cxt* ctx) { - #ifdef OLD_FDW_API - /* don't try to push down parameters with 9.1 */ - db2Debug1("> %s::deparseParamExpr", __FILE__); - db2Debug2(" don't try to push down parameters with 9.1"); - #else ListCell* cell = NULL; char parname[10]; @@ -2038,7 +2033,6 @@ static void deparseParamExpr (Param* expr, deparse_expr_cxt* snprintf (parname, 10, ":p%d", index); appendAsType (ctx->buf, expr->paramtype); } - #endif /* OLD_FDW_API */ db2Debug1("< %s::deparseParamExpr", __FILE__); } @@ -2107,34 +2101,6 @@ static void deparseParamExpr (Param* expr, deparse_expr_cxt* // } // } // } -// } else { -// #ifdef OLD_FDW_API -// // treat it like a parameter -// // don't try to push down parameters with 9.1 -// db2Debug2(" don't try to push down parameters with 9.1"); -// #else -// // don't try to handle type interval -// if (!canHandleType (expr->vartype) || expr->vartype == INTERVALOID) { -// db2Debug2(" !canHandleType (vartype %d) || vartype == INTERVALOID", expr->vartype); -// } else { -// ListCell* cell = NULL; -// int index = 0; -// -// /* find the index in the parameter list */ -// foreach (cell, *(ctx->params_list)) { -// ++index; -// if (equal (expr, (Node*) lfirst (cell))) -// break; -// } -// if (cell == NULL) { -// /* add the parameter to the list */ -// ++index; -// *(ctx->params_list) = lappend (*(ctx->params_list), expr); -// } -// /* parameters will be called :p1, :p2 etc. */ -// appendStringInfo (ctx->buf, ":p%d", index); -// } -// #endif /* OLD_FDW_API */ // } // db2Debug1("< %s::deparseVarExpr", __FILE__); //} From 0c9e27d9d92f2278d294abeb8dafb93e55525b70 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Mon, 16 Feb 2026 19:33:37 +0100 Subject: [PATCH 064/120] adding db2bytes and db2chars as fdw column options --- include/db2_fdw.h | 2 ++ source/db2_fdw.c | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/include/db2_fdw.h b/include/db2_fdw.h index 1f32229..c759587 100644 --- a/include/db2_fdw.h +++ b/include/db2_fdw.h @@ -130,6 +130,8 @@ typedef enum { #define OPT_DB2TYPE "db2type" #define OPT_DB2SIZE "db2size" +#define OPT_DB2BYTES "db2bytes" +#define OPT_DB2CHARS "db2chars" #define OPT_DB2SCALE "db2scale" #define OPT_DB2NULL "db2null" #define OPT_DB2CCSID "db2ccsid" diff --git a/source/db2_fdw.c b/source/db2_fdw.c index e9e9a48..1000008 100644 --- a/source/db2_fdw.c +++ b/source/db2_fdw.c @@ -68,6 +68,8 @@ DB2FdwOption valid_options[] = { {OPT_KEY , AttributeRelationId , false}, {OPT_DB2TYPE , AttributeRelationId , false}, {OPT_DB2SIZE , AttributeRelationId , false}, + {OPT_DB2BYTES , AttributeRelationId , false}, + {OPT_DB2CHARS , AttributeRelationId , false}, {OPT_DB2SCALE , AttributeRelationId , false}, {OPT_DB2NULL , AttributeRelationId , false}, {OPT_DB2CCSID , AttributeRelationId , false}, @@ -247,6 +249,8 @@ PGDLLEXPORT Datum db2_fdw_validator (PG_FUNCTION_ARGS) { if (strcmp (def->defname, OPT_DB2TYPE ) == 0 || strcmp (def->defname, OPT_DB2NULL ) == 0 || strcmp (def->defname, OPT_DB2SIZE ) == 0 + || strcmp (def->defname, OPT_DB2BYTES) == 0 + || strcmp (def->defname, OPT_DB2CHARS) == 0 || strcmp (def->defname, OPT_DB2SCALE) == 0 || strcmp (def->defname, OPT_DB2CCSID) == 0 || strcmp (def->defname, OPT_MAX_LONG) == 0) { From 83f0afef91decb181671efb770e73b52d2febd2c Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Mon, 16 Feb 2026 19:41:02 +0100 Subject: [PATCH 065/120] reworked ImportForeignSchema that adds all necessary db2 column info into FDW options --- Makefile | 2 +- source/db2GetImportColumn.c | 320 ---------------- source/db2ImportForeignSchema.c | 338 +++++++++-------- source/db2ImportForeignSchemaData.c | 544 ++++++++++++++++++++++++++++ 4 files changed, 710 insertions(+), 494 deletions(-) delete mode 100644 source/db2GetImportColumn.c create mode 100644 source/db2ImportForeignSchemaData.c diff --git a/Makefile b/Makefile index cf4eb93..f18046b 100644 --- a/Makefile +++ b/Makefile @@ -29,6 +29,7 @@ OBJS = source/db2_fdw.o\ source/db2ExplainForeignModify.o\ source/db2IsForeignRelUpdatable.o\ source/db2ImportForeignSchema.o\ + source/db2ImportForeignSchemaData.o\ source/db2GetFdwState.o\ source/db2GetForeignRelSize.o\ source/db2ReAllocFree.o\ @@ -47,7 +48,6 @@ OBJS = source/db2_fdw.o\ source/db2FreeStmtHdl.o\ source/db2GetSession.o\ source/db2Describe.o\ - source/db2GetImportColumn.o\ source/db2PrepareQuery.o\ source/db2BindParameter.o\ source/db2ExecuteQuery.o\ diff --git a/source/db2GetImportColumn.c b/source/db2GetImportColumn.c deleted file mode 100644 index 177a594..0000000 --- a/source/db2GetImportColumn.c +++ /dev/null @@ -1,320 +0,0 @@ -#include -#include -#include -#include -#include "db2_fdw.h" - -/** global variables */ - -/** external variables */ -extern char db2Message[ERRBUFSIZE];/* contains DB2 error messages, set by db2CheckErr() */ - -/** external prototypes */ -extern void* db2alloc (const char* type, size_t size); -extern void db2free (void* p); -extern void db2Debug1 (const char* message, ...); -extern void db2Debug2 (const char* message, ...); -extern void db2Debug3 (const char* message, ...); -extern SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleType, int line, char* file); -extern void db2Error_d (db2error sqlstate, const char* message, const char* detail, ...); -extern char* c2name (short fcType); -extern HdlEntry* db2AllocStmtHdl (SQLSMALLINT type, DB2ConnEntry* connp, db2error error, const char* errmsg); -extern void db2FreeStmtHdl (HdlEntry* handlep, DB2ConnEntry* connp); - -/** internal prototypes */ -int db2GetImportColumn (DB2Session* session, char* schema, char* table_list, int list_type, char* tabname, char* colName, short* colType, size_t* colLen, short* colScale, short* colNulls, int* key, int* cp); - -/** db2GetImportColumn - * Get the next element in the ordered list of tables and their columns for "schema". - * Returns 0 if there are no more columns, -1 if the remote schema does not exist, else 1. - */ -int db2GetImportColumn(DB2Session* session, char* schema, char* table_list, int list_type, char* tabname, char* colName, short* colType, size_t* colLen, short* colScale, short* colNulls, int* key, int* cp) { - /* the static variables will contain data returned to the caller */ - SQLCHAR tab_buf [TABLE_NAME_LEN]; - SQLLEN ind_tab; - SQLCHAR col_buf [COLUMN_NAME_LEN]; - SQLLEN ind_col; - SQLCHAR typ_buf [19]; - SQLLEN ind_typ; - SQLINTEGER len_val; - SQLLEN ind_len; - SQLSMALLINT scale_val; - SQLLEN ind_scale; - SQLCHAR nulls_val[2]; - SQLLEN ind_nulls; - SQLSMALLINT keyseq_val; - SQLLEN ind_key; - SQLSMALLINT cp_val; - SQLLEN ind_cp; - SQLRETURN result = 0; - - db2Debug1("> db2GetImportCol"); - db2Debug2(" session: %x", session); - db2Debug2(" session->connp: %x", session->connp); - db2Debug2(" session->emvp : %x", session->envp); - db2Debug2(" session->stmtp: %x", session->stmtp); - /* return a pointer to the static variables */ - - /* when first called, check if the schema does exist */ - if (session->stmtp == NULL) { - SQLBIGINT count = 0; - SQLLEN ind = SQL_NTS; - SQLLEN ind_c = 0; - char* schema_query = "SELECT COUNT(*) AS COUNTER FROM SYSCAT.SCHEMATA WHERE SCHEMANAME = ?"; - db2Debug2(" count : %lld", (long long)count); - db2Debug2(" schema query : '%s'", schema_query); - - /* create statement handle */ - session->stmtp = db2AllocStmtHdl(SQL_HANDLE_STMT, session->connp, FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: failed to allocate statement handle"); - db2Debug2(" session->stmp->hsql : %d",session->stmtp->hsql); - db2Debug2(" session->stmp->type : %d",session->stmtp->type); - /* prepare the query */ - result = SQLPrepare(session->stmtp->hsql, (SQLCHAR*)schema_query, SQL_NTS); - db2Debug2(" SQLPrepare rc : %d",result); - result = db2CheckErr(result, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); - if (result != SQL_SUCCESS) { - db2Error_d ( FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLPrepare failed to prepare schema query", db2Message); - } - - /* bind the parameter */ - result = SQLBindParameter(session->stmtp->hsql, 1, SQL_PARAM_INPUT,SQL_C_CHAR, SQL_VARCHAR, 128, 0, schema, sizeof(schema), &ind); - db2Debug2(" SQLBindParameter1 NAME = '%s', ind = %d, rc : %d",schema, ind, result); - result = db2CheckErr(result, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); - if (result != SQL_SUCCESS) { - db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLBindParameter failed to bind parameter", db2Message); - } - - /* define the result value */ - result = SQLBindCol (session->stmtp->hsql, 1, SQL_C_SBIGINT, &count, 0, &ind_c); - db2Debug2(" SQLBindCol rc : %d",result); - result = db2CheckErr(result, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); - if (result != SQL_SUCCESS) { - db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLBindCol failed to define result", db2Message); - } - - /* execute the query and get the first result row */ - result = SQLExecute(session->stmtp->hsql); - db2Debug2(" SQLExecute rc : %d",result); - result = db2CheckErr(result, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); - if (result != SQL_SUCCESS) { - db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLExecute failed to execute schema query", db2Message); - } else { - result = SQLFetch(session->stmtp->hsql); - db2Debug2(" SQLFetch rc : %d, count = %lld, ind_c = %d",result, (long long)count, ind_c); - result = db2CheckErr(result, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); - if (result != SQL_SUCCESS) { - db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLFetch failed to execute schema query", db2Message); - } - } - db2Debug2(" count(*) = %lld, ind_c = %d", (long long)count, ind_c); - /* release the statement handle */ - db2FreeStmtHdl(session->stmtp, session->connp); - db2Debug2(" session->connp: %x", session->connp); - db2Debug2(" session->emvp : %x", session->envp); - db2Debug2(" session->stmtp: %x", session->stmtp); - db2Debug2(" try to set session->stmtp to NULL"); - session->stmtp = NULL; - /* return -1 if the remote schema does not exist */ - if (count == 0) { - db2Debug1("< db2GetImportCol - returns: -1"); - return -1; - } - } - - /* when first calles, prepare the query to obtain the schema data */ - if (session->stmtp == NULL) { - SQLLEN ind_s = SQL_NTS; - char* column_query = NULL; - - switch(list_type){ - case 0: { /* FDW_IMPORT_SCHEMA_ALL */ - char* query_str = "SELECT T.TABNAME, C.COLNAME, C.TYPENAME, C.LENGTH, C.SCALE, C.NULLS, COALESCE(C.KEYSEQ, 0) AS KEY, C.CODEPAGE" - " FROM SYSCAT.TABLES T JOIN SYSCAT.COLUMNS C ON T.TABSCHEMA = C.TABSCHEMA AND T.TABNAME = C.TABNAME" - " WHERE T.TABSCHEMA = ? AND T.TYPE IN ('T','V') ORDER BY T.TABNAME, C.COLNO"; - int s_len = strlen(query_str)+1; - column_query = db2alloc("column_query",s_len); - strncpy(column_query,query_str,s_len); - } - break; - case 1: { /* FDW_IMPORT_SCHEMA_LIMIT_TO */ - char* query_str = "SELECT T.TABNAME, C.COLNAME, C.TYPENAME, C.LENGTH, C.SCALE, C.NULLS, COALESCE(C.KEYSEQ, 0) AS KEY, C.CODEPAGE" - " FROM SYSCAT.TABLES T JOIN SYSCAT.COLUMNS C ON T.TABSCHEMA = C.TABSCHEMA AND T.TABNAME = C.TABNAME" - " WHERE T.TABSCHEMA = ? AND T.TYPE IN ('T','V') AND T.TABNAME IN (%s) ORDER BY T.TABNAME, C.COLNO"; - int s_len = strlen(query_str) + strlen(table_list) + 1; - column_query = db2alloc("column_query",s_len); - snprintf(column_query,s_len,query_str,table_list); - } - break; - case 2: { /* FDW_IMPORT_SCHEMA_EXCEPT */ - char* query_str = "SELECT T.TABNAME, C.COLNAME, C.TYPENAME, C.LENGTH, C.SCALE, C.NULLS, COALESCE(C.KEYSEQ, 0) AS KEY, C.CODEPAGE" - " FROM SYSCAT.TABLES T JOIN SYSCAT.COLUMNS C ON T.TABSCHEMA = C.TABSCHEMA AND T.TABNAME = C.TABNAME" - " WHERE T.TABSCHEMA = ? AND T.TYPE IN ('T','V') AND T.TABNAME NOT IN (%s) ORDER BY T.TABNAME, C.COLNO"; - int s_len = strlen(query_str) + strlen(table_list) + 1; - column_query = db2alloc("column_query",s_len); - snprintf(column_query,s_len,query_str,table_list); - } - break; - default: - db2Debug2(" schema import type: %d", list_type); - db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "invalid schema import type", db2Message); - break; - } - db2Debug2(" column query : '%s'", column_query); - /* create statement handle */ - session->stmtp = db2AllocStmtHdl(SQL_HANDLE_STMT, session->connp, FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: failed to allocate statement handle"); - - /* prepare the query */ - result = SQLPrepare(session->stmtp->hsql, (SQLCHAR*)column_query, SQL_NTS); - db2Debug2(" SQLPrepare rc : %d",result); - result = db2CheckErr(result, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); - if (result != SQL_SUCCESS) { - db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLPrepare failed to prepare remote query", db2Message); - } - - /* bind the parameter */ - result = SQLBindParameter(session->stmtp->hsql, SQL_PARAM_INPUT, 1, SQL_C_CHAR, SQL_VARCHAR, 128, 0, schema, sizeof(schema), &ind_s); - db2Debug2(" SQLBindParameter table_schema = '%s' rc : %d",schema, result); - result = db2CheckErr(result, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); - if (result != SQL_SUCCESS) { - db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLBindParameter failed to bind parameter", db2Message); - } - - result = SQLBindCol(session->stmtp->hsql, 1, SQL_C_CHAR, tab_buf, sizeof(tab_buf), &ind_tab); - db2Debug2(" SQLBindCol1 rc : %d",result); - result = db2CheckErr(result, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); - if (result != SQL_SUCCESS) { - db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLBindCol failed to define result for table name", db2Message); - } - - result = SQLBindCol(session->stmtp->hsql, 2, SQL_C_CHAR, col_buf, sizeof(col_buf), &ind_col); - db2Debug2(" SQLBindCol2 rc : %d",result); - result = db2CheckErr(result, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); - if (result != SQL_SUCCESS) { - db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLBindCol failed to define result for column name", db2Message); - } - - result = SQLBindCol(session->stmtp->hsql, 3, SQL_C_CHAR, typ_buf, sizeof(typ_buf), &ind_typ); - db2Debug2(" SQLBindCol3 rc : %d",result); - result = db2CheckErr(result, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); - if (result != SQL_SUCCESS) { - db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLBindCol failed to define result for type name", db2Message); - } - - result = SQLBindCol(session->stmtp->hsql, 4, SQL_C_LONG, &len_val, 0, &ind_len); - db2Debug2(" SQLBindCol4 rc : %d",result); - result = db2CheckErr(result, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); - if (result != SQL_SUCCESS) { - db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLBindCol failed to define result for character length", db2Message); - } - - result = SQLBindCol(session->stmtp->hsql, 5, SQL_C_SHORT, &scale_val, 0, &ind_scale); - db2Debug2(" SQLBindCol5 rc : %d",result); - result = db2CheckErr(result, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); - if (result != SQL_SUCCESS) { - db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLBindCol failed to define result for type scale", db2Message); - } - - result = SQLBindCol(session->stmtp->hsql, 6, SQL_C_CHAR, nulls_val, sizeof(nulls_val), &ind_nulls); - db2Debug2(" SQLBindCol6 rc : %d",result); - result = db2CheckErr(result, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); - if (result != SQL_SUCCESS) { - db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLBindCol failed to define result for nullability", db2Message); - } - - result = SQLBindCol(session->stmtp->hsql, 7, SQL_C_SHORT, &keyseq_val, 0, &ind_key); - db2Debug2(" SQLBindCol7 rc : %d",result); - result = db2CheckErr(result, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); - if (result != SQL_SUCCESS) { - db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLBindCol failed to define result for primary key", db2Message); - } - - result = SQLBindCol(session->stmtp->hsql, 8, SQL_C_SHORT, &cp_val, 0, &ind_cp); - db2Debug2(" SQLBindCol8 rc : %d",result); - result = db2CheckErr(result, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); - if (result != SQL_SUCCESS) { - db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLBindCol failed to define result for type scale", db2Message); - } - - /* execute the query and get the first result row */ - result = SQLExecute (session->stmtp->hsql); - db2Debug2(" SQLExecute rc : %d",result); - result = db2CheckErr(result, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); - if (result != SQL_SUCCESS && result != SQL_NO_DATA) { - db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLExecute failed to execute column query", db2Message); - } - db2free(column_query); - } - - /* for any subsequent call, just fetch the next row from that cursor */ - if (session->stmtp != NULL) { - /* fetch the next result row */ - result = SQLFetch(session->stmtp->hsql); - result = db2CheckErr(result, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); - if (result != SQL_SUCCESS && result != SQL_NO_DATA) { - db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLFetchScroll failed to fetch next result row", db2Message); - } - } - - if (result == SQL_NO_DATA) { - db2Debug3(" End of Data reached"); - /* release the statement handle */ - db2FreeStmtHdl(session->stmtp, session->connp); - session->stmtp = NULL; - db2Debug1("< db2GetImportCol - returns: 0"); - return 0; - } else { - char* typename = (char*)typ_buf; - db2Debug2(" tabname : '%s', ind: %d", tab_buf , ind_tab ); - db2Debug2(" colname : '%s', ind: %d", col_buf , ind_col ); - db2Debug2(" typename: '%s', ind: %d", typ_buf , ind_typ ); - db2Debug2(" length : %d , ind: %d", len_val , ind_len ); - db2Debug2(" scale : %d , ind: %d", scale_val , ind_scale); - db2Debug2(" isnull : '%s', ind: %d", nulls_val , ind_nulls); - db2Debug2(" key : %d , ind: %d", keyseq_val, ind_key ); - db2Debug2(" codepage: %d , ind: %d", cp_val , ind_cp ); - if (ind_tab == SQL_NULL_DATA) - tabname[0] = '\0'; - else - strncpy(tabname, (char*)tab_buf, TABLE_NAME_LEN); - if (ind_col == SQL_NULL_DATA) - colName[0] = '\0'; - else - strncpy(colName, (char*)col_buf, COLUMN_NAME_LEN); - *colLen = (ind_len == SQL_NULL_DATA) ? 0 : (size_t) len_val; - *colScale = (ind_scale == SQL_NULL_DATA) ? 0 : (short) scale_val; - *colNulls = (ind_nulls == SQL_NULL_DATA) ? 0 : (nulls_val[0] == 'Y'); - *key = (ind_key == SQL_NULL_DATA) ? 0 : (int) keyseq_val; - *cp = (ind_cp == SQL_NULL_DATA) ? 0 : (int) cp_val; - /* figure out correct data type */ - if (strcmp (typename, "VARCHAR" ) == 0) *colType = SQL_VARCHAR; - else if (strcmp (typename, "LONG VARCHAR" ) == 0 && *cp != 0) *colType = SQL_LONGVARCHAR; - else if (strcmp (typename, "LONG VARCHAR" ) == 0 && *cp == 0) *colType = SQL_LONGVARBINARY; - else if (strcmp (typename, "CHARACTER" ) == 0) *colType = SQL_CHAR; - else if (strcmp (typename, "BINARY" ) == 0) *colType = SQL_BINARY; - else if (strcmp (typename, "VARBINARY" ) == 0) *colType = SQL_VARBINARY; - else if (strcmp (typename, "SMALLINT" ) == 0) *colType = SQL_SMALLINT; - else if (strcmp (typename, "INTEGER" ) == 0) *colType = SQL_INTEGER; - else if (strcmp (typename, "BIGINT" ) == 0) *colType = SQL_BIGINT; - else if (strcmp (typename, "DATE" ) == 0) *colType = SQL_TYPE_DATE; - else if (strcmp (typename, "TIMESTAMP" ) == 0) *colType = SQL_TYPE_TIMESTAMP; - else if (strcmp (typename, "TIME" ) == 0) *colType = SQL_TYPE_TIME; - else if (strcmp (typename, "XML" ) == 0) *colType = SQL_XML; - else if (strcmp (typename, "BLOB" ) == 0) *colType = SQL_BLOB; - else if (strcmp (typename, "CLOB" ) == 0) *colType = SQL_CLOB; - else if (strcmp (typename, "DECIMAL" ) == 0) *colType = SQL_DECIMAL; - else if (strcmp (typename, "GRAPHIC" ) == 0) *colType = SQL_GRAPHIC; - else if (strcmp (typename, "VARGRAPHIC" ) == 0) *colType = SQL_VARGRAPHIC; - else if (strcmp (typename, "DECFLOAT" ) == 0) *colType = SQL_DECFLOAT; - else if (strcmp (typename, "DOUBLE" ) == 0) *colType = SQL_DOUBLE; - else if (strcmp (typename, "REAL" ) == 0) *colType = SQL_REAL; - else if (strcmp (typename, "FLOAT" ) == 0) *colType = SQL_FLOAT; - else if (strcmp (typename, "BOOLEAN" ) == 0) *colType = SQL_BOOLEAN; - else { - db2Debug1(" unknown typename: '%s'",typename); - *colType = SQL_UNKNOWN_TYPE; - } - db2Debug2(" colType : %s (%d)", c2name(*colType), *colType); - } - db2Debug1("< db2GetImportCol - returns: 1"); - return 1; -} diff --git a/source/db2ImportForeignSchema.c b/source/db2ImportForeignSchema.c index 6fbc9d1..009fad5 100644 --- a/source/db2ImportForeignSchema.c +++ b/source/db2ImportForeignSchema.c @@ -10,17 +10,20 @@ /** external prototypes */ extern DB2Session* db2GetSession (const char* connectstring, char* user, char* password, char* jwt_token, const char* nls_lang, int curlevel); -extern int db2GetImportColumn (DB2Session* session, char* stmt, char* table_list, int list_type, char* tabname, char* colname, short* colType, size_t* colLen, short* typescale, short* nullable, int* key, int* cp); extern char* guessNlsLang (char* nls_lang); extern void db2Debug1 (const char* message, ...); extern void db2Debug2 (const char* message, ...); extern short c2dbType (short fcType); extern void db2free (void* p); extern char* db2strdup (const char* source); +extern bool isForeignSchema (DB2Session* session, char* schema); +extern char** getForeignTableList (DB2Session* session, char* schema, int list_type, char* table_list); +extern DB2Table* describeForeignTable (DB2Session* session, char* schema, char* tabname); /** local prototypes */ List* db2ImportForeignSchema(ImportForeignSchemaStmt* stmt, Oid serverOid); static char* fold_case (char* name, fold_t foldcase); +static void generateForeignTableCreate(StringInfo buf, char* servername, char* local_schema, char* remote_schema, DB2Table* db2Table, fold_t foldcase, bool readonly); /** db2ImportForeignSchema * Returns a List of CREATE FOREIGN TABLE statements. @@ -29,32 +32,20 @@ List* db2ImportForeignSchema (ImportForeignSchemaStmt* stmt, Oid serverOid) { ForeignServer* server; UserMapping* mapping; ForeignDataWrapper* wrapper; - char tabname [TABLE_NAME_LEN] = { '\0' }; - char colname [COLUMN_NAME_LEN] = { '\0' }; - char oldtabname[TABLE_NAME_LEN] = { '\0' }; - char* foldedname; char* nls_lang = NULL; char* user = NULL; char* password = NULL; char* jwt_token = NULL; char* dbserver = NULL; - short colType; - size_t colSize; - short colScale; - short colNulls; - int key; - int cp; - int rc; List* options; List* result = NIL; ListCell* cell; DB2Session* session; fold_t foldcase = CASE_SMART; StringInfoData buf; - StringInfoData tblist; bool readonly = false; - bool firstcol = true; - db2Debug1("> db2ImportForeignSchema"); + + db2Debug1("> %s::db2ImportForeignSchema",__FILE__); /* get the foreign server, the user mapping and the FDW */ server = GetForeignServer (serverOid); @@ -119,169 +110,47 @@ List* db2ImportForeignSchema (ImportForeignSchemaStmt* stmt, Oid serverOid) { /* connect to DB2 database */ session = db2GetSession (dbserver, user, password, jwt_token, nls_lang, 1); - initStringInfo (&buf); - db2Debug2(" stmt->list_type : %d ",stmt->list_type); - db2Debug2(" stmt->local_schema : '%s'",stmt->local_schema); - db2Debug2(" stmt->remote_schema: '%s'",stmt->remote_schema); - db2Debug2(" stmt->server_name : '%s'",stmt->server_name); - db2Debug2(" stmt->table_list : '%x'",stmt->table_list); - db2Debug2(" stmt->type : %d ",stmt->type); - - initStringInfo (&tblist); - if (stmt->list_type != FDW_IMPORT_SCHEMA_ALL) { - foreach (cell, stmt->table_list) { - RangeVar* rVar = lfirst(cell); - db2Debug2(" rVar : %x ", rVar); - if (rVar != NULL) { - db2Debug2(" rVar->type : %d ", rVar->type); - db2Debug2(" rVar->catalogname: '%s'", rVar->catalogname); - db2Debug2(" rVar->schemaname : '%s'", rVar->schemaname); - db2Debug2(" rVar->relname : '%s'", rVar->relname); - if (tblist.len != 0) { - appendStringInfo(&tblist,",'%s'",rVar->relname); - } else { - appendStringInfo(&tblist,"'%s'",rVar->relname); - } - } - } - } - db2Debug2(" import table_list: '%s'",tblist.data); - do { - /* get the next column definition */ - rc = db2GetImportColumn (session, stmt->remote_schema, tblist.data, stmt->list_type, tabname, colname, &colType, &colSize, &colScale, &colNulls, &key, &cp); + db2Debug2(" stmt->list_type : %d", stmt->list_type); + db2Debug2(" stmt->local_schema : %s", stmt->local_schema); + db2Debug2(" stmt->remote_schema: %s", stmt->remote_schema); + db2Debug2(" stmt->server_name : %s", stmt->server_name); + db2Debug2(" stmt->table_list : %s", stmt->table_list); + db2Debug2(" stmt->type : %d", stmt->type); - if (rc == -1) { - /* remote schema does not exist, issue a warning */ - ereport (ERROR,(errcode(ERRCODE_FDW_SCHEMA_NOT_FOUND) - ,errmsg ("remote schema \"%s\" does not exist", stmt->remote_schema) - ,errhint ("Enclose the schema name in double quotes to prevent case folding.") - ) - ); - return NIL; - } + if (isForeignSchema (session, stmt->remote_schema)) { + StringInfoData tblist; + char** tablist = NULL; + initStringInfo(&buf); + initStringInfo(&tblist); - if ((rc == 0 && oldtabname[0] != '\0') || (rc == 1 && oldtabname[0] != '\0' && strcmp (tabname, oldtabname))) { - /* finish previous CREATE FOREIGN TABLE statement */ - appendStringInfo (&buf, ") SERVER \"%s\" OPTIONS (schema '%s', table '%s'", server->servername, stmt->remote_schema, oldtabname); - if (readonly) { - appendStringInfo (&buf, ", readonly 'true'"); + if (stmt->list_type != FDW_IMPORT_SCHEMA_ALL) { + foreach (cell, stmt->table_list) { + RangeVar* rVar = lfirst(cell); + db2Debug2(" rVar : %x ", rVar); + if (rVar != NULL) { + db2Debug2(" rVar->type : %d ", rVar->type); + db2Debug2(" rVar->catalogname: '%s'", rVar->catalogname); + db2Debug2(" rVar->schemaname : '%s'", rVar->schemaname); + db2Debug2(" rVar->relname : '%s'", rVar->relname); + appendStringInfo(&tblist,"%s'%s'",((tblist.len == 0) ? "" : ","),rVar->relname); + } } - appendStringInfo (&buf, ")"); - db2Debug2 (" pg fdw table ddl: '%s'",buf.data); - result = lappend (result, db2strdup (buf.data)); - } - - if (rc == 1 && (oldtabname[0] == '\0' || strcmp (tabname, oldtabname))) { - /* start a new CREATE FOREIGN TABLE statement */ - resetStringInfo (&buf); - foldedname = fold_case (tabname, foldcase); - appendStringInfo (&buf, "CREATE FOREIGN TABLE \"%s\".\"%s\" (", stmt->local_schema, foldedname); - db2free (foldedname); - - firstcol = true; - strncpy (oldtabname, tabname, sizeof(oldtabname)); + db2Debug2(" import table_list: %s",tblist.data); } - - if (rc == 1) { - /** Add a column definition. */ - if (firstcol) - firstcol = false; - else - appendStringInfo (&buf, ", "); - - /* column name */ - foldedname = fold_case (colname, foldcase); - appendStringInfo (&buf, "\"%s\" ", foldedname); - db2free (foldedname); - - // check charlen is not 0; set it to 1 in that case - colSize = colSize == 0 ? 1 : colSize; - /* data type */ - switch (c2dbType(colType)) { - case DB2_CHAR: - appendStringInfo (&buf, "character(%ld)", colSize); - break; - case DB2_VARCHAR: - appendStringInfo (&buf, "character varying(%ld)", colSize); - break; - case DB2_LONGVARCHAR: - case DB2_CLOB: - case DB2_VARGRAPHIC: - case DB2_GRAPHIC: - case DB2_DBCLOB: - appendStringInfo (&buf, "text"); - break; - case DB2_SMALLINT: - appendStringInfo (&buf, "smallint"); - break; - case DB2_INTEGER: - appendStringInfo (&buf, "integer"); - break; - case DB2_BIGINT: - appendStringInfo (&buf, "bigint"); - break; - case DB2_BOOLEAN: - appendStringInfo (&buf, "boolean"); - break; - case DB2_NUMERIC: - appendStringInfo (&buf, "numeric(%ld,%d)", colSize, colScale); - break; - case DB2_DECIMAL: - appendStringInfo (&buf, "decimal(%ld,%d)", colSize, colScale); - break; - case DB2_DOUBLE: - appendStringInfo (&buf, "double precision"); - break; - case DB2_DECFLOAT: - case DB2_FLOAT: - colSize = (colSize > 8) ? 8 : colSize; - appendStringInfo (&buf, "float(%ld)", colSize); - break; - case DB2_REAL: - appendStringInfo (&buf, "real"); - break; - case DB2_XML: - appendStringInfo (&buf, "xml"); - break; - case DB2_BINARY: - case DB2_VARBINARY: - case DB2_LONGVARBINARY: - case DB2_BLOB: - appendStringInfo (&buf, "bytea"); - break; - case DB2_TYPE_DATE: - appendStringInfo (&buf, "date"); - break; - case DB2_TYPE_TIMESTAMP: - appendStringInfo (&buf, "timestamp(%d)", (colScale > 6) ? 6 : colScale); - break; - case DB2_TYPE_TIMESTAMP_WITH_TIMEZONE: - appendStringInfo (&buf, "timestamp(%d) with time zone", (colScale > 6) ? 6 : colScale); - break; - case DB2_TYPE_TIME: - appendStringInfo (&buf, "time(%d)", (colScale > 6) ? 6 : colScale); - break; - default: - elog (DEBUG2, "column \"%s\" of table \"%s\" has an untranslatable data type", colname, tabname); - appendStringInfo (&buf, "text"); - break; + tablist = getForeignTableList(session, stmt->remote_schema, stmt->list_type, tblist.data); + db2free (tblist.data); + for (int i = 0; tablist[i] != NULL; i++) { + DB2Table* db2Table = describeForeignTable(session, stmt->remote_schema, tablist[i]); + if (db2Table != NULL) { + generateForeignTableCreate(&buf, server->servername, stmt->local_schema, stmt->remote_schema, db2Table, foldcase, readonly); + db2Debug2 (" pg fdw table ddl: '%s'",buf.data); + result = lappend (result, db2strdup (buf.data)); + resetStringInfo (&buf); } - /* part of the primary key */ - appendStringInfo (&buf - , " OPTIONS (%s '%d', %s '%ld', %s '%d', %s '%d', %s '%d'%s)" - ,OPT_DB2TYPE , colType - ,OPT_DB2SIZE , colSize - ,OPT_DB2SCALE, colScale - ,OPT_DB2NULL , colNulls - ,OPT_DB2CCSID, cp - , (key) ? ", key 'true'" : "" - ); - /* not nullable */ - if (!colNulls) - appendStringInfo (&buf, " NOT NULL"); } - } while (rc == 1); - db2Debug1("< db2ImportForeignSchema"); + db2free (tablist); + } + db2Debug1("< %s::db2ImportForeignSchema : %d",__FILE__, list_length(result)); return result; } @@ -290,7 +159,7 @@ List* db2ImportForeignSchema (ImportForeignSchemaStmt* stmt, Oid serverOid) { */ static char* fold_case (char *name, fold_t foldcase) { char* result = NULL; - db2Debug1("> fold_case"); + db2Debug1("> fold_case(name: '%s', foldcase: %d)", name, foldcase); if (foldcase == CASE_KEEP) { result = db2strdup (name); } else { @@ -313,3 +182,126 @@ static char* fold_case (char *name, fold_t foldcase) { db2Debug1("< fold_case - returns: '%s'", result); return result; } + +static void generateForeignTableCreate(StringInfo buf, char* servername, char* local_schema, char* remote_schema, DB2Table* db2Table, fold_t foldcase, bool readonly) { + StringInfoData coldef; + char* foldedname; + bool firstcol = true; + + initStringInfo(&coldef); + foldedname = fold_case (db2Table->name, foldcase); + appendStringInfo( buf + , "CREATE FOREIGN TABLE \"%s\".\"%s\" (" + , local_schema + , foldedname + ); + db2free (foldedname); + for (int i = 0; i < db2Table->ncols; i++) { + appendStringInfo(buf, (firstcol) ? "" : ", "); + + /* column name */ + foldedname = fold_case (db2Table->cols[i]->colName, foldcase); + appendStringInfo (buf, "\"%s\" ", foldedname); + db2free (foldedname); + + // check charlen is not 0; set it to 1 in that case + db2Table->cols[i]->colSize = db2Table->cols[i]->colSize == 0 ? 1 : db2Table->cols[i]->colSize; + /* data type */ + switch (c2dbType(db2Table->cols[i]->colType)) { + case DB2_CHAR: + appendStringInfo (buf, "character(%ld)", db2Table->cols[i]->colSize); + break; + case DB2_VARCHAR: + appendStringInfo (buf, "character varying(%ld)", db2Table->cols[i]->colSize); + break; + case DB2_LONGVARCHAR: + case DB2_CLOB: + case DB2_VARGRAPHIC: + case DB2_GRAPHIC: + case DB2_DBCLOB: + appendStringInfo (buf, "text"); + break; + case DB2_SMALLINT: + appendStringInfo (buf, "smallint"); + break; + case DB2_INTEGER: + appendStringInfo (buf, "integer"); + break; + case DB2_BIGINT: + appendStringInfo (buf, "bigint"); + break; + case DB2_BOOLEAN: + appendStringInfo (buf, "boolean"); + break; + case DB2_NUMERIC: + appendStringInfo (buf, "numeric(%ld,%d)", db2Table->cols[i]->colSize, db2Table->cols[i]->colScale); + break; + case DB2_DECIMAL: + appendStringInfo (buf, "decimal(%ld,%d)", db2Table->cols[i]->colSize, db2Table->cols[i]->colScale); + break; + case DB2_DOUBLE: + appendStringInfo (buf, "double precision"); + break; + case DB2_DECFLOAT: + case DB2_FLOAT: + db2Table->cols[i]->colSize = (db2Table->cols[i]->colSize > 8) ? 8 : db2Table->cols[i]->colSize; + appendStringInfo (buf, "float(%ld)", db2Table->cols[i]->colSize); + break; + case DB2_REAL: + appendStringInfo (buf, "real"); + break; + case DB2_XML: + appendStringInfo (buf, "xml"); + break; + case DB2_BINARY: + case DB2_VARBINARY: + case DB2_LONGVARBINARY: + case DB2_BLOB: + appendStringInfo (buf, "bytea"); + break; + case DB2_TYPE_DATE: + appendStringInfo (buf, "date"); + break; + case DB2_TYPE_TIMESTAMP: + appendStringInfo (buf, "timestamp(%d)", (db2Table->cols[i]->colScale > 6) ? 6 : db2Table->cols[i]->colScale); + break; + case DB2_TYPE_TIMESTAMP_WITH_TIMEZONE: + appendStringInfo (buf, "timestamp(%d) with time zone", (db2Table->cols[i]->colScale > 6) ? 6 : db2Table->cols[i]->colScale); + break; + case DB2_TYPE_TIME: + appendStringInfo (buf, "time(%d)", (db2Table->cols[i]->colScale > 6) ? 6 : db2Table->cols[i]->colScale); + break; + default: + elog (DEBUG2, "column \"%s\" of table \"%s\" has an untranslatable data type", db2Table->cols[i]->colName, db2Table->name); + appendStringInfo (buf, "text"); + break; + } + appendStringInfo (buf, " OPTIONS ("); + appendStringInfo (buf, "%s '%d'" , OPT_DB2TYPE , db2Table->cols[i]->colType); + appendStringInfo (buf, ", %s '%ld'", OPT_DB2SIZE , db2Table->cols[i]->colSize); + appendStringInfo (buf, ", %s '%ld'", OPT_DB2BYTES, db2Table->cols[i]->colBytes); + appendStringInfo (buf, ", %s '%ld'", OPT_DB2CHARS, db2Table->cols[i]->colChars); + appendStringInfo (buf, ", %s '%d'" , OPT_DB2SCALE, db2Table->cols[i]->colScale); + appendStringInfo (buf, ", %s '%d'" , OPT_DB2NULL , db2Table->cols[i]->colNulls); + appendStringInfo (buf, ", %s '%d'" , OPT_DB2CCSID, db2Table->cols[i]->colCodepage); + /* part of the primary key */ + if (db2Table->cols[i]->colPrimKeyPart) + appendStringInfo (buf, ", %s 'true'", OPT_KEY); + appendStringInfo (buf, ")"); + + /* not nullable */ + if (!db2Table->cols[i]->colNulls) + appendStringInfo (buf, " NOT NULL"); + firstcol = false; + } + appendStringInfo( buf + , ") SERVER \"%s\" OPTIONS (schema '%s', table '%s'" + , servername + , remote_schema + , db2Table->name + ); + if (readonly) { + appendStringInfo (buf, ", readonly 'true'"); + } + appendStringInfo (buf, ")"); +} \ No newline at end of file diff --git a/source/db2ImportForeignSchemaData.c b/source/db2ImportForeignSchemaData.c new file mode 100644 index 0000000..c9d539a --- /dev/null +++ b/source/db2ImportForeignSchemaData.c @@ -0,0 +1,544 @@ +#include +#include +#include +#include +#include +#include "db2_fdw.h" + +/** global variables */ + +/** external variables */ +extern char db2Message[ERRBUFSIZE];/* contains DB2 error messages, set by db2CheckErr() */ +extern int err_code; /* error code, set by db2CheckErr() */ + +/** external prototypes */ +extern void* db2alloc (const char* type, size_t size); +extern void* db2realloc (void* p, size_t size); +extern void db2free (void* p); +extern char* db2strdup (const char* source); +extern char* db2CopyText (const char* string, int size, int quote); +extern void db2Debug1 (const char* message, ...); +extern void db2Debug2 (const char* message, ...); +extern void db2Debug3 (const char* message, ...); +extern SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleType, int line, char* file); +extern void db2Error_d (db2error sqlstate, const char* message, const char* detail, ...); +extern char* c2name (short fcType); +extern HdlEntry* db2AllocStmtHdl (SQLSMALLINT type, DB2ConnEntry* connp, db2error error, const char* errmsg); +extern void db2FreeStmtHdl (HdlEntry* handlep, DB2ConnEntry* connp); + +/** internal prototypes */ + bool isForeignSchema (DB2Session* session, char* schema); + char** getForeignTableList (DB2Session* session, char* schema, int list_type, char* table_list); + DB2Table* describeForeignTable (DB2Session* session, char* schema, char* tabname); +static void describeForeignColumns(DB2Session* session, char* schema, char* tabname, DB2Table* db2Table); + +/* isForeignSchema + * Check if the given schema exists in the remote DB2 database. + * Returns true if the schema exists, false if it does not exist. + */ +bool isForeignSchema(DB2Session* session, char* schema) { + bool fResult = false; + HdlEntry* stmtp = NULL; + SQLBIGINT count = 0; + SQLLEN ind = SQL_NTS; + SQLLEN ind_c = 0; + SQLRETURN result = 0; + char* schema_query = "SELECT COUNT(*) AS COUNTER FROM SYSCAT.SCHEMATA WHERE SCHEMANAME = ?"; + + db2Debug1("> %s::isForeignSchema(schema: '%s')", __FILE__, schema); + db2Debug2(" count : %lld", (long long)count); + db2Debug2(" schema query : '%s'", schema_query); + /* create statement handle */ + stmtp = db2AllocStmtHdl(SQL_HANDLE_STMT, session->connp, FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: failed to allocate statement handle"); + db2Debug2(" stmp->hsql : %d",stmtp->hsql); + db2Debug2(" stmp->type : %d",stmtp->type); + /* prepare the query */ + result = SQLPrepare(stmtp->hsql, (SQLCHAR*)schema_query, SQL_NTS); + db2Debug2(" SQLPrepare rc : %d",result); + result = db2CheckErr(result, stmtp->hsql, stmtp->type, __LINE__, __FILE__); + if (result != SQL_SUCCESS) { + db2Error_d ( FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLPrepare failed to prepare schema query", db2Message); + } + /* bind the parameter */ + result = SQLBindParameter(stmtp->hsql, 1, SQL_PARAM_INPUT,SQL_C_CHAR, SQL_VARCHAR, 128, 0, schema, sizeof(schema), &ind); + db2Debug2(" SQLBindParameter1 NAME = '%s', ind = %d, rc : %d",schema, ind, result); + result = db2CheckErr(result, stmtp->hsql, stmtp->type, __LINE__, __FILE__); + if (result != SQL_SUCCESS) { + db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLBindParameter failed to bind parameter", db2Message); + } + /* define the result value */ + result = SQLBindCol (stmtp->hsql, 1, SQL_C_SBIGINT, &count, 0, &ind_c); + db2Debug2(" SQLBindCol rc : %d",result); + result = db2CheckErr(result, stmtp->hsql, stmtp->type, __LINE__, __FILE__); + if (result != SQL_SUCCESS) { + db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLBindCol failed to define result", db2Message); + } + /* execute the query and get the first result row */ + result = SQLExecute(stmtp->hsql); + db2Debug2(" SQLExecute rc : %d",result); + result = db2CheckErr(result, stmtp->hsql, stmtp->type, __LINE__, __FILE__); + if (result != SQL_SUCCESS) { + db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLExecute failed to execute schema query", db2Message); + } else { + result = SQLFetch(stmtp->hsql); + db2Debug2(" SQLFetch rc : %d, count = %lld, ind_c = %d",result, (long long)count, ind_c); + result = db2CheckErr(result, stmtp->hsql, stmtp->type, __LINE__, __FILE__); + if (result != SQL_SUCCESS) { + db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLFetch failed to execute schema query", db2Message); + } + } + db2Debug2(" count(*) = %lld, ind_c = %d", (long long)count, ind_c); + /* release the statement handle */ + db2FreeStmtHdl(stmtp, session->connp); + stmtp = NULL; + /* return false if the remote schema does not exist */ + fResult = (count > 0); + db2Debug1("< %s::isForeignSchema : %s, result = %s", __FILE__, schema, fResult ? "true" : "false"); + return fResult; +} + +/* getForeignTableList + * Get the list of tables in the given schema on the remote DB2 database. + * Returns an allocated array of table names, terminated by a NULL entry. + */ +char** getForeignTableList(DB2Session* session, char* schema, int list_type, char* table_list){ + SQLRETURN rc = 0; + HdlEntry* stmtp = NULL; + SQLLEN ind_s = SQL_NTS; + char* column_query = NULL; + SQLCHAR tab_buf [TABLE_NAME_LEN]; + SQLLEN ind_tab; + int tabidx = 0; + char** tabnames = NULL; + db2Debug1("> %s::getForeignTableList(schema: '%s', list_type: %d, table_list: '%s')", __FILE__, schema, list_type, table_list); + switch(list_type){ + case 0: { /* FDW_IMPORT_SCHEMA_ALL */ + char* query_str = "SELECT T.TABNAME FROM SYSCAT.TABLES T WHERE T.TABSCHEMA = ? AND T.TYPE IN ('T','V') ORDER BY T.TABNAME"; + int s_len = strlen(query_str)+1; + column_query = db2alloc("column_query",s_len); + strncpy(column_query,query_str,s_len); + } + break; + case 1: { /* FDW_IMPORT_SCHEMA_LIMIT_TO */ + char* query_str = "SELECT T.TABNAME FROM SYSCAT.TABLES T WHERE T.TABSCHEMA = ? AND T.TYPE IN ('T','V') AND T.TABNAME IN (%s) ORDER BY T.TABNAME"; + int s_len = strlen(query_str) + strlen(table_list) + 1; + column_query = db2alloc("column_query",s_len); + snprintf(column_query,s_len,query_str,table_list); + } + break; + case 2: { /* FDW_IMPORT_SCHEMA_EXCEPT */ + char* query_str = "SELECT T.TABNAME FROM SYSCAT.TABLES T WHERE T.TABSCHEMA = ? AND T.TYPE IN ('T','V') AND T.TABNAME NOT IN (%s) ORDER BY T.TABNAME"; + int s_len = strlen(query_str) + strlen(table_list) + 1; + column_query = db2alloc("column_query",s_len); + snprintf(column_query,s_len,query_str,table_list); + } + break; + default: + db2Debug2(" schema import type: %d", list_type); + db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "invalid schema import type", db2Message); + break; + } + db2Debug2(" column query : '%s'", column_query); + /* create statement handle */ + stmtp = db2AllocStmtHdl(SQL_HANDLE_STMT, session->connp, FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: failed to allocate statement handle"); + + /* prepare the query */ + rc = SQLPrepare(stmtp->hsql, (SQLCHAR*)column_query, SQL_NTS); + db2Debug2(" SQLPrepare rc : %d",rc); + rc = db2CheckErr(rc, stmtp->hsql, stmtp->type, __LINE__, __FILE__); + if (rc != SQL_SUCCESS) { + db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLPrepare failed to prepare remote query", db2Message); + } + /* bind the parameter */ + rc = SQLBindParameter(stmtp->hsql, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_VARCHAR, 128, 0, schema, sizeof(schema), &ind_s); + db2Debug2(" SQLBindParameter table_schema = '%s' rc : %d",schema, rc); + rc = db2CheckErr(rc, stmtp->hsql, stmtp->type, __LINE__, __FILE__); + if (rc != SQL_SUCCESS) { + db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLBindParameter failed to bind parameter", db2Message); + } + rc = SQLBindCol(stmtp->hsql, 1, SQL_C_CHAR, tab_buf, sizeof(tab_buf), &ind_tab); + db2Debug2(" SQLBindCol1 rc : %d",rc); + rc = db2CheckErr(rc, stmtp->hsql, stmtp->type, __LINE__, __FILE__); + if (rc != SQL_SUCCESS) { + db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLBindCol failed to define result for table name", db2Message); + } + + /* execute the query and get the first result row */ + rc = SQLExecute (stmtp->hsql); + db2Debug2(" SQLExecute rc : %d",rc); + rc = db2CheckErr(rc, stmtp->hsql, stmtp->type, __LINE__, __FILE__); + if (rc != SQL_SUCCESS && rc != SQL_NO_DATA) { + db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLExecute failed to execute column query", db2Message); + } + tabidx = 0; + rc = SQLFetch(stmtp->hsql); + rc = db2CheckErr(rc, stmtp->hsql, stmtp->type, __LINE__, __FILE__); + if (rc != SQL_SUCCESS && rc != SQL_NO_DATA) { + db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLFetch failed to execute column query", db2Message); + } + tabnames = (char**) db2alloc("tabnames", (tabidx + 1) * sizeof(char*)); + while(rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO) { + tabnames[tabidx] = NULL; + db2Debug2(" tabname[%d] : '%s', ind: %d", tabidx, tab_buf, ind_tab); + if (ind_tab != SQL_NULL_DATA) { + char* tabname = (char*) db2alloc("tabname", strlen((char*)tab_buf)+1); + strncpy(tabname, (char*)tab_buf, strlen((char*)tab_buf)+1); + tabnames[tabidx] = tabname; + } + rc = SQLFetch(stmtp->hsql); + rc = db2CheckErr(rc, stmtp->hsql, stmtp->type, __LINE__, __FILE__); + if (rc != SQL_SUCCESS && rc != SQL_NO_DATA) { + db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLFetch failed to execute column query", db2Message); + } + tabidx++; + tabnames = (char**) db2realloc(tabnames, (tabidx + 1) * sizeof(char*)); + } + tabnames[tabidx] = NULL; + /* release the statement handle */ + db2FreeStmtHdl(stmtp, session->connp); + stmtp = NULL; + db2free(column_query); + db2Debug1("< %s::getForeignTableList : [%d]", __FILE__, tabidx-1); + return tabnames; +} + +/* describeForeignTable + * Find the remote DB2 table and describes it. + * Returns an allocated data structure with the results. + */ +DB2Table* describeForeignTable (DB2Session* session, char* schema, char* tabname) { + DB2Table* reply; + HdlEntry* stmthp; + char* qtable = NULL; + char* qschema = NULL; + char* tablename = NULL; + SQLCHAR* query = NULL; + int i; + int length; + SQLSMALLINT ncols; + SQLCHAR colName[128]; + SQLSMALLINT nameLen; + SQLSMALLINT dataType; + SQLULEN colSize; + SQLLEN charlen; + SQLLEN bin_size; + SQLSMALLINT scale; + SQLSMALLINT nullable; + SQLINTEGER codepage = 0; + SQLRETURN rc = 0; + + db2Debug1("> %s::db2DescribeForeignTable(schema: %s, tablename: %s)", __FILE__, schema, tabname); + /* get a complete quoted table name */ + qtable = db2CopyText (tabname, strlen (tabname), 1); + length = strlen (qtable); + if (schema != NULL) { + qschema = db2CopyText (schema, strlen (schema), 1); + length += strlen (qschema) + 1; + } + tablename = db2alloc ("reply->name", length + 1); + tablename[0] = '\0'; /* empty */ + if (schema != NULL) { + strncat (tablename, qschema,length); + strncat (tablename, ".",length); + } + strncat (tablename, qtable,length); + db2free (qtable); + if (schema != NULL) + db2free (qschema); + + /* construct a "SELECT * FROM ..." query to describe columns */ + length += 40; + query = db2alloc ("query", length + 1); + snprintf ((char*)query, length+1, (char*)"SELECT * FROM %s FETCH FIRST 1 ROW ONLY", tablename); + + /* create statement handle */ + stmthp = db2AllocStmtHdl(SQL_HANDLE_STMT, session->connp, FDW_UNABLE_TO_CREATE_REPLY, "error describing remote table: failed to allocate statement handle"); + + /* prepare the query */ + rc = SQLPrepare(stmthp->hsql, query, SQL_NTS); + rc = db2CheckErr(rc, stmthp->hsql,stmthp->type, __LINE__, __FILE__); + if (rc != SQL_SUCCESS) { + db2Error_d (FDW_UNABLE_TO_CREATE_REPLY, "error describing remote table: SQLPrepare failed to prepare query", db2Message); + } + /* execute the query */ + rc = SQLExecute(stmthp->hsql); + rc= db2CheckErr(rc, stmthp->hsql, stmthp->type, __LINE__, __FILE__); + if (rc != SQL_SUCCESS) { + if (err_code == 942) + db2Error_d (FDW_TABLE_NOT_FOUND, "table not found", + "DB2 table %s does not exist or does not allow read access;%s", tablename, + db2Message, "DB2 table names are case sensitive (normally all uppercase)."); + else + db2Error_d (FDW_UNABLE_TO_CREATE_REPLY, "error describing remote table: SQLExecute failed to describe table", db2Message); + } + db2free(query); + + /* allocate an db2Table struct for the results */ + reply = db2alloc ("reply", sizeof (DB2Table)); + reply->name = tabname; + db2Debug2(" table description"); + db2Debug2(" reply->name : '%s'", reply->name); + reply->batchsz = DEFAULT_BATCHSZ; + + /* get the number of columns */ + rc = SQLNumResultCols(stmthp->hsql, &ncols); + rc = db2CheckErr(rc, stmthp->hsql, stmthp->type, __LINE__, __FILE__); + if (rc != SQL_SUCCESS) { + db2Error_d (FDW_UNABLE_TO_CREATE_REPLY, "error describing remote table: SQLNumResultCols failed to get number of columns", db2Message); + } + + reply->ncols = ncols; + reply->cols = (DB2Column**) db2alloc ("reply->cols", sizeof (DB2Column*) *reply->ncols); + db2Debug2(" reply->ncols : %d", reply->ncols); + + /* loop through the column list */ + for (i = 1; i <= reply->ncols; ++i) { + /* allocate an db2Column struct for the column */ + reply->cols[i - 1] = (DB2Column *) db2alloc (" reply->cols[i - 1]", sizeof (DB2Column)); + reply->cols[i - 1]->colPrimKeyPart = 0; + reply->cols[i - 1]->colCodepage = 0; + reply->cols[i - 1]->pgname = NULL; + reply->cols[i - 1]->pgattnum = 0; + reply->cols[i - 1]->pgtype = 0; + reply->cols[i - 1]->pgtypmod = 0; + reply->cols[i - 1]->used = 0; + reply->cols[i - 1]->pkey = 0; + reply->cols[i - 1]->noencerr = NO_ENC_ERR_NULL; + + /* get the parameter descriptor for the column */ + rc = SQLDescribeCol(stmthp->hsql + , i // index of column in table + , (SQLCHAR*)&colName // column name + , sizeof(colName) // buffer length + , &nameLen // column name length + , &dataType // column data type + , &colSize // column data type size + , &scale // column data type precision + , &nullable // column nullable + ); + rc = db2CheckErr(rc, stmthp->hsql, stmthp->type, __LINE__, __FILE__); + if (rc != SQL_SUCCESS) { + db2Error_d (FDW_UNABLE_TO_CREATE_REPLY, "error describing remote table: SQLDescribeCol failed to get column data", db2Message); + } + reply->cols[i - 1]->colName = db2strdup((char*)colName); + db2Debug2(" reply->cols[%d]->colName : '%s'", (i-1), reply->cols[i - 1]->colName); + db2Debug2(" dataType: %d", dataType); + reply->cols[i - 1]->colType = (short) dataType; + if (dataType == -7){ + // datatype -7 does not exist it seems to be used for SQL_BOOLEAN wrongly + reply->cols[i - 1]->colType = SQL_BOOLEAN; + } + db2Debug2(" reply->cols[%d]->colType : %d (%s)", (i-1), reply->cols[i - 1]->colType,c2name(reply->cols[i - 1]->colType)); + reply->cols[i - 1]->colSize = (size_t) colSize; + db2Debug2(" reply->cols[%d]->colSize : %ld", (i-1), reply->cols[i - 1]->colSize); + reply->cols[i - 1]->colScale = (short) scale; + db2Debug2(" reply->cols[%d]->colScale : %d", (i-1), reply->cols[i - 1]->colScale); + reply->cols[i - 1]->colNulls = (short) nullable; + db2Debug2(" reply->cols[%d]->colNulls : %d", (i-1), reply->cols[i - 1]->colNulls); + + /* get the number of characters for string fields */ + rc = SQLColAttribute (stmthp->hsql, i, SQL_DESC_PRECISION, NULL, 0, NULL, &charlen); + rc = db2CheckErr(rc, stmthp->hsql, stmthp->type, __LINE__, __FILE__); + if (rc != SQL_SUCCESS) { + db2Error_d (FDW_UNABLE_TO_CREATE_REPLY, "error describing remote table: SQLColAttribute failed to get column length", db2Message); + } + reply->cols[i - 1]->colChars = (size_t) charlen; + db2Debug2(" reply->cols[%d]->colChars : %ld", (i-1), reply->cols[i - 1]->colChars); + + /* get the binary length for RAW fields */ + rc = SQLColAttribute (stmthp->hsql, i, SQL_DESC_OCTET_LENGTH, NULL, 0, NULL, &bin_size); + rc = db2CheckErr(rc, stmthp->hsql, stmthp->type, __LINE__, __FILE__); + if (rc != SQL_SUCCESS) { + db2Error_d (FDW_UNABLE_TO_CREATE_REPLY, "error describing remote table: SQLColAttribute failed to get column size", db2Message); + } + reply->cols[i - 1]->colBytes = (size_t) bin_size; + db2Debug2(" reply->cols[%d]->colBytes : %ld", (i-1), reply->cols[i - 1]->colBytes); + + /* get the columns codepage */ + rc = SQLColAttribute(stmthp->hsql, i, SQL_DESC_CODEPAGE, NULL, 0, NULL, (SQLPOINTER)&codepage); + rc = db2CheckErr(rc, stmthp->hsql, stmthp->type, __LINE__, __FILE__); + if (rc != SQL_SUCCESS) { + db2Error_d (FDW_UNABLE_TO_CREATE_REPLY, "error describing remote table: SQLColAttribute failed to get column codepage", db2Message); + } + reply->cols[i - 1]->colCodepage = (int) codepage; + db2Debug2(" reply->cols[%d]->colCodepage : %d", (i-1), reply->cols[i - 1]->colCodepage); + + /* Unfortunately a LONG VARBINARY is of type LONG VARCHAR but the codepage is set to 0 */ + if (reply->cols[i-1]->colType == SQL_LONGVARCHAR && reply->cols[i-1]->colCodepage == 0){ + reply->cols[i-1]->colType = SQL_LONGVARBINARY; + } + + /* determine db2Type and length to allocate */ + switch (reply->cols[i - 1]->colType) { + case SQL_CHAR: + case SQL_VARCHAR: + case SQL_LONGVARCHAR: + reply->cols[i - 1]->val_size = bin_size + 1; + break; + case SQL_BLOB: + case SQL_CLOB: + reply->cols[i - 1]->val_size = bin_size + 1; + break; + case SQL_GRAPHIC: + case SQL_VARGRAPHIC: + case SQL_LONGVARGRAPHIC: + case SQL_WCHAR: + case SQL_WVARCHAR: + case SQL_WLONGVARCHAR: + case SQL_DBCLOB: + reply->cols[i - 1]->val_size = bin_size + 1; + break; + case SQL_BOOLEAN: + reply->cols[i - 1]->val_size = bin_size + 1; + break; + case SQL_INTEGER: + case SQL_SMALLINT: + reply->cols[i - 1]->val_size = charlen + 2; + break; + case SQL_NUMERIC: + case SQL_DECIMAL: + if (scale == 0) + reply->cols[i - 1]->val_size = bin_size; + else + reply->cols[i - 1]->val_size = (scale > colSize ? scale : colSize) + 5; + break; + case SQL_REAL: + case SQL_DOUBLE: + case SQL_FLOAT: + case SQL_DECFLOAT: + reply->cols[i - 1]->val_size = 24 + 1; + break; + case SQL_TYPE_DATE: + case SQL_TYPE_TIME: + case SQL_TYPE_TIMESTAMP: + case SQL_TYPE_TIMESTAMP_WITH_TIMEZONE: + reply->cols[i - 1]->val_size = colSize + 1; + break; + case SQL_BIGINT: + reply->cols[i - 1]->val_size = 24; + break; + case SQL_XML: + reply->cols[i - 1]->val_size = LOB_CHUNK_SIZE + 1; + break; + case SQL_BINARY: + case SQL_VARBINARY: + case SQL_LONGVARBINARY: + reply->cols[i - 1]->val_size = bin_size; + break; + default: + break; + } + db2Debug2(" reply->cols[%d]->val_size : %d", (i-1), reply->cols[i - 1]->val_size); + } + /* release statement handle, this takes care of the parameter handles */ + db2FreeStmtHdl(stmthp, session->connp); + if (reply != NULL) { + /* get the primary key information for the table and mark the columns in the reply */ + describeForeignColumns(session, schema, tabname, reply); + } + db2Debug1("< %s::db2DescribeForeignTable - returns: %x", __FILE__, reply); + return reply; +} + +/* describeForeignColumns + * Get the primary key information for the given table and mark the columns in the reply. + */ +static void describeForeignColumns(DB2Session* session, char* schema, char* tabname, DB2Table* db2Table) { + int colidx = 0; + HdlEntry* stmtp = NULL; + SQLRETURN rc = 0; + SQLSMALLINT keyseq_val; + SQLLEN ind_key; + SQLSMALLINT cp_val; + SQLLEN ind_cp; + SQLLEN ind_s = SQL_NTS; + SQLLEN ind_t = SQL_NTS; + char* query = "SELECT COALESCE(C.KEYSEQ, 0) AS KEY, C.CODEPAGE FROM SYSCAT.COLUMNS C WHERE C.TABSCHEMA = ? AND C.TABNAME = ? AND COALESCE(C.HIDDEN,'') = '' ORDER BY C.COLNO"; + + db2Debug1("> %s::describeForeignColumn(schema: %s, tabname: %s)", __FILE__, schema, tabname); + db2Debug2(" query : '%s'", query); + /* create statement handle */ + stmtp = db2AllocStmtHdl(SQL_HANDLE_STMT, session->connp, FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: failed to allocate statement handle"); + + /* prepare the query */ + rc = SQLPrepare(stmtp->hsql, (SQLCHAR*)query, SQL_NTS); + db2Debug2(" SQLPrepare rc : %d",rc); + rc = db2CheckErr(rc, stmtp->hsql, stmtp->type, __LINE__, __FILE__); + if (rc != SQL_SUCCESS) { + db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLPrepare failed to prepare remote query", db2Message); + } + + /* bind the parameter 1 - schema */ + rc = SQLBindParameter(stmtp->hsql, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, 128, 0, schema, 0, &ind_s); + db2Debug2(" SQLBindParameter table_schema = '%s' rc : %d",schema, rc); + rc = db2CheckErr(rc, stmtp->hsql, stmtp->type, __LINE__, __FILE__); + if (rc != SQL_SUCCESS) { + db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLBindParameter failed to bind parameter", db2Message); + } + /* bind the parameter 2 - tablename */ + rc = SQLBindParameter(stmtp->hsql, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, 128, 0, tabname, 0, &ind_t); + db2Debug2(" SQLBindParameter table_name = '%s' rc : %d",tabname, rc); + rc = db2CheckErr(rc, stmtp->hsql, stmtp->type, __LINE__, __FILE__); + if (rc != SQL_SUCCESS) { + db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLBindParameter failed to bind parameter", db2Message); + } + /* bind result column 1 - KEYSEQ */ + rc = SQLBindCol(stmtp->hsql, 1, SQL_C_SHORT, &keyseq_val, 0, &ind_key); + db2Debug2(" SQLBindCol1 rc : %d",rc); + rc = db2CheckErr(rc, stmtp->hsql, stmtp->type, __LINE__, __FILE__); + if (rc != SQL_SUCCESS) { + db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLBindCol failed to define result for primary key", db2Message); + } + /* bind result column 2 - CODEPAGE */ + rc = SQLBindCol(stmtp->hsql, 2, SQL_C_SHORT, &cp_val, 0, &ind_cp); + db2Debug2(" SQLBindCol2 rc : %d",rc); + rc = db2CheckErr(rc, stmtp->hsql, stmtp->type, __LINE__, __FILE__); + if (rc != SQL_SUCCESS) { + db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLBindCol failed to define result for codepage", db2Message); + } + /* execute the query and get the first result row */ + rc = SQLExecute (stmtp->hsql); + db2Debug2(" SQLExecute rc : %d",rc); + rc = db2CheckErr(rc, stmtp->hsql, stmtp->type, __LINE__, __FILE__); + if (rc != SQL_SUCCESS && rc != SQL_NO_DATA) { + db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLExecute failed to execute column query", db2Message); + } + + /* fetch the first result row */ + colidx = 0; + rc = SQLFetch(stmtp->hsql); + rc = db2CheckErr(rc, stmtp->hsql, stmtp->type, __LINE__, __FILE__); + if (rc != SQL_SUCCESS && rc != SQL_NO_DATA) { + db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLFetch failed to fetch result row", db2Message); + } + while(rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO) { + + db2Debug2(" keyseq_val: %d, ind: %d", keyseq_val, ind_key); + db2Table->cols[colidx]->colPrimKeyPart = (ind_key == SQL_NULL_DATA) ? 0 : (int) keyseq_val; + db2Debug2(" cp_val : %d, ind: %d", cp_val, ind_cp); + db2Table->cols[colidx]->colCodepage = (ind_cp == SQL_NULL_DATA) ? 0 : (int) cp_val; + + db2Debug2(" db2Table->cols[%d]->colName : %s " , colidx, db2Table->cols[colidx]->colName ); + db2Debug2(" db2Table->cols[%d]->colType : %d - %s,", colidx, db2Table->cols[colidx]->colType, c2name(db2Table->cols[colidx]->colType)); + db2Debug2(" db2Table->cols[%d]->colSize : %d" , colidx, db2Table->cols[colidx]->colSize ); + db2Debug2(" db2Table->cols[%d]->colBytes : %d" , colidx, db2Table->cols[colidx]->colBytes ); + db2Debug2(" db2Table->cols[%d]->colChars : %d" , colidx, db2Table->cols[colidx]->colChars ); + db2Debug2(" db2Table->cols[%d]->colScale : %d" , colidx, db2Table->cols[colidx]->colScale); + db2Debug2(" db2Table->cols[%d]->colNulls : %d" , colidx, db2Table->cols[colidx]->colNulls); + db2Debug2(" db2Table->cols[%d]->colPrimKeyPart: %d" , colidx, db2Table->cols[colidx]->colPrimKeyPart); + db2Debug2(" db2Table->cols[%d]->colCodepage : %d" , colidx, db2Table->cols[colidx]->colCodepage); + db2Debug2(" db2Table->cols[%d]->val_size : %d" , colidx, db2Table->cols[colidx]->val_size); + + /* fetch the next result row */ + rc = SQLFetch(stmtp->hsql); + rc = db2CheckErr(rc, stmtp->hsql, stmtp->type, __LINE__, __FILE__); + if (rc != SQL_SUCCESS && rc != SQL_NO_DATA) { + db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLFetch failed to fetch result row", db2Message); + } + colidx++; + } + db2Debug3(" End of Data reached"); + /* release the statement handle */ + db2FreeStmtHdl(stmtp, session->connp); + db2Debug1("< %s::describeForeignColumn", __FILE__); +} \ No newline at end of file From cea7b383020f95e91d1a74a6ec5f050eecd9a344 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Mon, 16 Feb 2026 20:05:30 +0100 Subject: [PATCH 066/120] code cleanup --- source/db2ImportForeignSchema.c | 7 +- source/db2_de_serialize.c | 197 +++++++++++++++----------------- 2 files changed, 95 insertions(+), 109 deletions(-) diff --git a/source/db2ImportForeignSchema.c b/source/db2ImportForeignSchema.c index 009fad5..2622930 100644 --- a/source/db2ImportForeignSchema.c +++ b/source/db2ImportForeignSchema.c @@ -19,6 +19,7 @@ extern char* db2strdup (const char* source); extern bool isForeignSchema (DB2Session* session, char* schema); extern char** getForeignTableList (DB2Session* session, char* schema, int list_type, char* table_list); extern DB2Table* describeForeignTable (DB2Session* session, char* schema, char* tabname); +extern bool optionIsTrue (const char* value); /** local prototypes */ List* db2ImportForeignSchema(ImportForeignSchemaStmt* stmt, Oid serverOid); @@ -93,10 +94,8 @@ List* db2ImportForeignSchema (ImportForeignSchemaStmt* stmt, Oid serverOid) { continue; } else if (strcmp (def->defname, "readonly") == 0) { char *s = STRVAL(def->arg); - if (pg_strcasecmp (s, "on") != 0 || pg_strcasecmp (s, "yes") != 0 || pg_strcasecmp (s, "true") != 0) - readonly = true; - else if (pg_strcasecmp (s, "off") != 0 || pg_strcasecmp (s, "no") != 0 || pg_strcasecmp (s, "false") != 0) - readonly = false; + if (pg_strcasecmp (s, "on") != 0 || pg_strcasecmp (s, "yes") != 0 || pg_strcasecmp (s, "true") != 0 || pg_strcasecmp (s, "off") != 0 || pg_strcasecmp (s, "no") != 0 || pg_strcasecmp (s, "false") != 0) + readonly = optionIsTrue(s); else ereport (ERROR, (errcode (ERRCODE_FDW_INVALID_ATTRIBUTE_VALUE), errmsg ("invalid value for option \"%s\"", def->defname))); continue; diff --git a/source/db2_de_serialize.c b/source/db2_de_serialize.c index 2a4a242..e2700a4 100644 --- a/source/db2_de_serialize.c +++ b/source/db2_de_serialize.c @@ -2,15 +2,6 @@ #include #include #include - -//#include -//#include -//#include -//#include -//#include -//#include - - #include "db2_fdw.h" #include "DB2FdwState.h" @@ -86,43 +77,39 @@ DB2FdwState* deserializePlanData (List* list) { for (i = 0; i < state->db2Table->ncols; ++i) { state->db2Table->cols[i] = (DB2Column *) db2alloc ("state->db2Table->cols[i]", sizeof (DB2Column)); state->db2Table->cols[i]->colName = deserializeString(list_nth(list, idx++)); - db2Debug4(" deserialize col[%d].colName: %s" ,i, state->db2Table->cols[i]->colName); + db2Debug5(" deserialize col[%d].colName: %s" ,i, state->db2Table->cols[i]->colName); state->db2Table->cols[i]->colType = (short) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug4(" deserialize col[%d].colType: %d" ,i, state->db2Table->cols[i]->colType); + db2Debug5(" deserialize col[%d].colType: %d" ,i, state->db2Table->cols[i]->colType); state->db2Table->cols[i]->colSize = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug4(" deserialize col[%d].colSize: %d" ,i, state->db2Table->cols[i]->colSize); + db2Debug5(" deserialize col[%d].colSize: %d" ,i, state->db2Table->cols[i]->colSize); state->db2Table->cols[i]->colScale = (short) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug4(" deserialize col[%d].colScale: %d" ,i, state->db2Table->cols[i]->colScale); + db2Debug5(" deserialize col[%d].colScale: %d" ,i, state->db2Table->cols[i]->colScale); state->db2Table->cols[i]->colNulls = (short) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug4(" deserialize col[%d].colNulls: %d" ,i, state->db2Table->cols[i]->colNulls); + db2Debug5(" deserialize col[%d].colNulls: %d" ,i, state->db2Table->cols[i]->colNulls); state->db2Table->cols[i]->colChars = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug4(" deserialize col[%d].colChars: %d" ,i, state->db2Table->cols[i]->colChars); + db2Debug5(" deserialize col[%d].colChars: %d" ,i, state->db2Table->cols[i]->colChars); state->db2Table->cols[i]->colBytes = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug4(" deserialize col[%d].colBytes: %d" ,i, state->db2Table->cols[i]->colBytes); + db2Debug5(" deserialize col[%d].colBytes: %d" ,i, state->db2Table->cols[i]->colBytes); state->db2Table->cols[i]->colPrimKeyPart = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug4(" deserialize col[%d].colPrimKeyPart: %d" ,i, state->db2Table->cols[i]->colPrimKeyPart); + db2Debug5(" deserialize col[%d].colPrimKeyPart: %d" ,i, state->db2Table->cols[i]->colPrimKeyPart); state->db2Table->cols[i]->colCodepage = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug4(" deserialize col[%d].colCodepage: %d" ,i, state->db2Table->cols[i]->colCodepage); + db2Debug5(" deserialize col[%d].colCodepage: %d" ,i, state->db2Table->cols[i]->colCodepage); state->db2Table->cols[i]->pgname = deserializeString(list_nth(list, idx++)); - db2Debug4(" deserialize col[%d].pgname: %s" ,i, state->db2Table->cols[i]->pgname); + db2Debug5(" deserialize col[%d].pgname: %s" ,i, state->db2Table->cols[i]->pgname); state->db2Table->cols[i]->pgattnum = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug4(" deserialize col[%d].pgattnum: %d" ,i, state->db2Table->cols[i]->pgattnum); + db2Debug5(" deserialize col[%d].pgattnum: %d" ,i, state->db2Table->cols[i]->pgattnum); state->db2Table->cols[i]->pgtype = DatumGetObjectId(((Const*)list_nth(list, idx++))->constvalue); - db2Debug4(" deserialize col[%d].pgtype: %d" ,i, state->db2Table->cols[i]->pgtype); + db2Debug5(" deserialize col[%d].pgtype: %d" ,i, state->db2Table->cols[i]->pgtype); state->db2Table->cols[i]->pgtypmod = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug4(" deserialize col[%d].pgtypmod: %d" ,i, state->db2Table->cols[i]->pgtypmod); + db2Debug5(" deserialize col[%d].pgtypmod: %d" ,i, state->db2Table->cols[i]->pgtypmod); state->db2Table->cols[i]->used = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug4(" deserialize col[%d].used: %d" ,i, state->db2Table->cols[i]->used); + db2Debug5(" deserialize col[%d].used: %d" ,i, state->db2Table->cols[i]->used); state->db2Table->cols[i]->pkey = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug4(" deserialize col[%d].pkey: %d" ,i, state->db2Table->cols[i]->pkey); + db2Debug5(" deserialize col[%d].pkey: %d" ,i, state->db2Table->cols[i]->pkey); state->db2Table->cols[i]->val_size = deserializeLong(list_nth(list, idx++)); - db2Debug4(" deserialize col[%d].val_size: %ld" ,i, state->db2Table->cols[i]->val_size); + db2Debug5(" deserialize col[%d].val_size: %ld" ,i, state->db2Table->cols[i]->val_size); state->db2Table->cols[i]->noencerr = deserializeLong(list_nth(list, idx++)); - db2Debug4(" deserialize col[%d].noencerr: %ld" ,i, state->db2Table->cols[i]->noencerr); - /* allocate memory for the result value only when the column is used in query */ -// state->db2Table->cols[i]->val = (state->db2Table->cols[i]->used == 1) ? (char*) db2alloc ("state->db2Table->cols[i]->val", state->db2Table->cols[i]->val_size + 1) : NULL; -// state->db2Table->cols[i]->val_len = 0; -// state->db2Table->cols[i]->val_null = 1; + db2Debug5(" deserialize col[%d].noencerr: %ld" ,i, state->db2Table->cols[i]->noencerr); } /* length of parameter list */ @@ -133,28 +120,28 @@ DB2FdwState* deserializePlanData (List* list) { for (i = 0; i < len; ++i) { param = (ParamDesc*) db2alloc ("state->parmList->next", sizeof (ParamDesc)); param->colName = deserializeString(list_nth(list, idx++)); - db2Debug4(" deserialize param[%d].colName: %s" ,i, param->colName); + db2Debug5(" deserialize param[%d].colName: %s" ,i, param->colName); param->colType = (short) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug4(" deserialize param[%d].colType: %d" ,i, param->colType); + db2Debug5(" deserialize param[%d].colType: %d" ,i, param->colType); param->colSize = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug4(" deserialize param[%d].colSize: %d" ,i, param->colSize); + db2Debug5(" deserialize param[%d].colSize: %d" ,i, param->colSize); param->type = DatumGetObjectId(((Const*)list_nth(list, idx++))->constvalue); - db2Debug4(" deserialize param[%d].type: %d" ,i, param->type); + db2Debug5(" deserialize param[%d].type: %d" ,i, param->type); param->bindType = (db2BindType) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug4(" deserialize param[%d].bindType: %d" ,i, param->bindType); + db2Debug5(" deserialize param[%d].bindType: %d" ,i, param->bindType); if (param->bindType == BIND_OUTPUT) param->value = (void *) 42; /* something != NULL */ else param->value = NULL; - db2Debug4(" deserialize param[%d].value: %x" ,i, param->value); + db2Debug5(" deserialize param[%d].value: %x" ,i, param->value); param->val_size = deserializeLong(list_nth(list, idx++)); - db2Debug4(" deserialize param[%d].val_size: %ld" ,i, param->val_size); + db2Debug5(" deserialize param[%d].val_size: %ld" ,i, param->val_size); param->node = NULL; - db2Debug4(" deserialize param[%d].node: %x" ,i, param->node); + db2Debug5(" deserialize param[%d].node: %x" ,i, param->node); param->colnum = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug4(" deserialize param[%d].colnum: %d" ,i, param->colnum); + db2Debug5(" deserialize param[%d].colnum: %d" ,i, param->colnum); param->txts = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug4(" deserialize param[%d].txts: %d" ,i, param->txts); + db2Debug5(" deserialize param[%d].txts: %d" ,i, param->txts); param->next = state->paramList; state->paramList = param; } @@ -166,39 +153,39 @@ DB2FdwState* deserializePlanData (List* list) { for (i = 0; i < len; ++i) { DB2ResultColumn* res = (DB2ResultColumn *) db2alloc ("state->resultList->next", sizeof (DB2ResultColumn)); res->colName = deserializeString(list_nth(list, idx++)); - db2Debug4(" deserialize res[%d].colName: %s" ,i, res->colName); + db2Debug5(" deserialize res[%d].colName: %s" ,i, res->colName); res->colType = (short) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug4(" deserialize res[%d].colType: %d" ,i, res->colType); + db2Debug5(" deserialize res[%d].colType: %d" ,i, res->colType); res->colSize = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug4(" deserialize res[%d].colSize: %d" ,i, res->colSize); + db2Debug5(" deserialize res[%d].colSize: %d" ,i, res->colSize); res->colScale = (short) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug4(" deserialize res[%d].colScale: %d" ,i, res->colScale); + db2Debug5(" deserialize res[%d].colScale: %d" ,i, res->colScale); res->colNulls = (short) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug4(" deserialize res[%d].colNulls: %d" ,i, res->colNulls); + db2Debug5(" deserialize res[%d].colNulls: %d" ,i, res->colNulls); res->colChars = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug4(" deserialize res[%d].colChars: %d" ,i, res->colChars); + db2Debug5(" deserialize res[%d].colChars: %d" ,i, res->colChars); res->colBytes = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug4(" deserialize res[%d].colBytes: %d" ,i, res->colBytes); + db2Debug5(" deserialize res[%d].colBytes: %d" ,i, res->colBytes); res->colPrimKeyPart = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug4(" deserialize res[%d].colPrimKeyPart: %d" ,i, res->colPrimKeyPart); + db2Debug5(" deserialize res[%d].colPrimKeyPart: %d" ,i, res->colPrimKeyPart); res->colCodepage = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug4(" deserialize res[%d].colCodepage: %d" ,i, res->colCodepage); + db2Debug5(" deserialize res[%d].colCodepage: %d" ,i, res->colCodepage); res->pgname = deserializeString(list_nth(list, idx++)); - db2Debug4(" deserialize res[%d].pgname: %s" ,i, res->pgname); + db2Debug5(" deserialize res[%d].pgname: %s" ,i, res->pgname); res->pgattnum = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug4(" deserialize res[%d].pgattnum: %d" ,i, res->pgattnum); + db2Debug5(" deserialize res[%d].pgattnum: %d" ,i, res->pgattnum); res->pgtype = DatumGetObjectId(((Const*)list_nth(list, idx++))->constvalue); - db2Debug4(" deserialize res[%d].pgtype: %d" ,i, res->pgtype); + db2Debug5(" deserialize res[%d].pgtype: %d" ,i, res->pgtype); res->pgtypmod = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug4(" deserialize res[%d].pgtypmod: %d" ,i, res->pgtypmod); + db2Debug5(" deserialize res[%d].pgtypmod: %d" ,i, res->pgtypmod); res->pkey = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug4(" deserialize res[%d].pkey: %d" ,i, res->pkey); + db2Debug5(" deserialize res[%d].pkey: %d" ,i, res->pkey); res->val_size = deserializeLong(list_nth(list, idx++)); - db2Debug4(" deserialize res[%d].val_size: %ld" ,i, res->val_size); + db2Debug5(" deserialize res[%d].val_size: %ld" ,i, res->val_size); res->noencerr = deserializeLong(list_nth(list, idx++)); - db2Debug4(" deserialize res[%d].noencerr: %ld" ,i, res->noencerr); + db2Debug5(" deserialize res[%d].noencerr: %ld" ,i, res->noencerr); res->resnum = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug4(" deserialize res[%d].resnum: %d" ,i, res->resnum); + db2Debug5(" deserialize res[%d].resnum: %d" ,i, res->resnum); res->val = (char*) db2alloc ("res->val", res->val_size + 1); res->val_len = 0; res->val_null = 1; @@ -281,39 +268,39 @@ List* serializePlanData (DB2FdwState* fdwState) { /* column data */ for (idxCol = 0; idxCol < fdwState->db2Table->ncols; ++idxCol) { result = lappend (result, serializeString (fdwState->db2Table->cols[idxCol]->colName)); - db2Debug4(" serialize col[%d].colName: %s" ,idxCol, fdwState->db2Table->cols[idxCol]->colName); + db2Debug5(" serialize col[%d].colName: %s" ,idxCol, fdwState->db2Table->cols[idxCol]->colName); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->colType)); - db2Debug4(" serialize col[%d].colType: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colType); + db2Debug5(" serialize col[%d].colType: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colType); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->colSize)); - db2Debug4(" serialize col[%d].colSize: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colSize); + db2Debug5(" serialize col[%d].colSize: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colSize); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->colScale)); - db2Debug4(" serialize col[%d].colScale: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colScale); + db2Debug5(" serialize col[%d].colScale: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colScale); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->colNulls)); - db2Debug4(" serialize col[%d].colNulls: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colNulls); + db2Debug5(" serialize col[%d].colNulls: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colNulls); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->colChars)); - db2Debug4(" serialize col[%d].colChars: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colChars); + db2Debug5(" serialize col[%d].colChars: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colChars); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->colBytes)); - db2Debug4(" serialize col[%d].colBytes: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colBytes); + db2Debug5(" serialize col[%d].colBytes: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colBytes); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->colPrimKeyPart)); - db2Debug4(" serialize col[%d].colPrimKeyPart: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colPrimKeyPart); + db2Debug5(" serialize col[%d].colPrimKeyPart: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colPrimKeyPart); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->colCodepage)); - db2Debug4(" serialize col[%d].colCodepage: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colCodepage); + db2Debug5(" serialize col[%d].colCodepage: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colCodepage); result = lappend (result, serializeString (fdwState->db2Table->cols[idxCol]->pgname)); - db2Debug4(" serialize col[%d].pgname: %s" ,idxCol, fdwState->db2Table->cols[idxCol]->pgname); + db2Debug5(" serialize col[%d].pgname: %s" ,idxCol, fdwState->db2Table->cols[idxCol]->pgname); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->pgattnum)); - db2Debug4(" serialize col[%d].pgattnum: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->pgattnum); + db2Debug5(" serialize col[%d].pgattnum: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->pgattnum); result = lappend (result, serializeOid (fdwState->db2Table->cols[idxCol]->pgtype)); - db2Debug4(" serialize col[%d].pgtype: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->pgtype); + db2Debug5(" serialize col[%d].pgtype: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->pgtype); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->pgtypmod)); - db2Debug4(" serialize col[%d].pgtypmod: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->pgtypmod); + db2Debug5(" serialize col[%d].pgtypmod: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->pgtypmod); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->used)); - db2Debug4(" serialize col[%d].used: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->used); + db2Debug5(" serialize col[%d].used: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->used); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->pkey)); - db2Debug4(" serialize col[%d].pkey: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->pkey); + db2Debug5(" serialize col[%d].pkey: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->pkey); result = lappend (result, serializeLong (fdwState->db2Table->cols[idxCol]->val_size)); - db2Debug4(" serialize col[%d].val_size: %ld" ,idxCol, fdwState->db2Table->cols[idxCol]->val_size); + db2Debug5(" serialize col[%d].val_size: %ld" ,idxCol, fdwState->db2Table->cols[idxCol]->val_size); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->noencerr)); - db2Debug4(" serialize col[%d].noencerr: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->noencerr); + db2Debug5(" serialize col[%d].noencerr: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->noencerr); /* don't serialize val, val_len, val_null and varno */ } @@ -323,25 +310,25 @@ List* serializePlanData (DB2FdwState* fdwState) { } /* serialize length */ result = lappend (result, serializeInt (lenParam)); - db2Debug4(" serialize paramList.length: %d", lenParam); + db2Debug5(" serialize paramList.length: %d", lenParam); /* parameter list entries */ for (param = fdwState->paramList; param; param = param->next) { result = lappend (result, serializeString (param->colName)); - db2Debug4(" serialize param.colName: %s" , param->colName); + db2Debug5(" serialize param.colName: %s" , param->colName); result = lappend (result, serializeInt (param->colType)); - db2Debug4(" serialize param.colType: %d" , param->colType); + db2Debug5(" serialize param.colType: %d" , param->colType); result = lappend (result, serializeInt (param->colSize)); - db2Debug4(" serialize param.colSize: %d" , param->colSize); + db2Debug5(" serialize param.colSize: %d" , param->colSize); result = lappend (result, serializeOid (param->type)); - db2Debug4(" serialize param.type: %d" , param->type); + db2Debug5(" serialize param.type: %d" , param->type); result = lappend (result, serializeInt ((int) param->bindType)); - db2Debug4(" serialize param.bindType: %d" , param->bindType); + db2Debug5(" serialize param.bindType: %d" , param->bindType); result = lappend (result, serializeLong (param->val_size)); - db2Debug4(" serialize param.val_size: %ld" , param->val_size); + db2Debug5(" serialize param.val_size: %ld" , param->val_size); result = lappend (result, serializeInt ((int) param->colnum)); - db2Debug4(" serialize param.colnum: %d" , param->colnum); + db2Debug5(" serialize param.colnum: %d" , param->colnum); result = lappend (result, serializeInt ((int) param->txts)); - db2Debug4(" serialize param.txta: %d" , param->txts); + db2Debug5(" serialize param.txts: %d" , param->txts); } /* find length of result list */ @@ -351,43 +338,43 @@ List* serializePlanData (DB2FdwState* fdwState) { } /* serialize length */ result = lappend (result, serializeInt (lenParam)); - db2Debug4(" serialize resultList.length: %d", lenParam); + db2Debug5(" serialize resultList.length: %d", lenParam); /* parameter list entries */ for (rcol = fdwState->resultList; rcol; rcol = rcol->next) { result = lappend (result, serializeString (rcol->colName)); - db2Debug4(" serialize res.colName: %s" , rcol->colName); + db2Debug5(" serialize res.colName: %s" , rcol->colName); result = lappend (result, serializeInt (rcol->colType)); - db2Debug4(" serialize res.colType: %d" , rcol->colType); + db2Debug5(" serialize res.colType: %d" , rcol->colType); result = lappend (result, serializeInt (rcol->colSize)); - db2Debug4(" serialize res.colSize: %d" , rcol->colSize); + db2Debug5(" serialize res.colSize: %d" , rcol->colSize); result = lappend (result, serializeInt (rcol->colScale)); - db2Debug4(" serialize res.colScale: %d" , rcol->colScale); + db2Debug5(" serialize res.colScale: %d" , rcol->colScale); result = lappend (result, serializeInt (rcol->colNulls)); - db2Debug4(" serialize res.colNulls: %d", rcol->colNulls); + db2Debug5(" serialize res.colNulls: %d", rcol->colNulls); result = lappend (result, serializeInt (rcol->colChars)); - db2Debug4(" serialize res.colChars: %d" , rcol->colChars); + db2Debug5(" serialize res.colChars: %d" , rcol->colChars); result = lappend (result, serializeInt (rcol->colBytes)); - db2Debug4(" serialize res.colBytes: %d" , rcol->colBytes); + db2Debug5(" serialize res.colBytes: %d" , rcol->colBytes); result = lappend (result, serializeInt (rcol->colPrimKeyPart)); - db2Debug4(" serialize res.colPrimKeyPart: %d", rcol->colPrimKeyPart); + db2Debug5(" serialize res.colPrimKeyPart: %d", rcol->colPrimKeyPart); result = lappend (result, serializeInt (rcol->colCodepage)); - db2Debug4(" serialize res.codepage: %d" , rcol->colCodepage); + db2Debug5(" serialize res.codepage: %d" , rcol->colCodepage); result = lappend (result, serializeString (rcol->pgname)); - db2Debug4(" serialize res.pgname: %s" , rcol->pgname); + db2Debug5(" serialize res.pgname: %s" , rcol->pgname); result = lappend (result, serializeInt (rcol->pgattnum)); - db2Debug4(" serialize res.pgattnum: %d" , rcol->pgattnum); + db2Debug5(" serialize res.pgattnum: %d" , rcol->pgattnum); result = lappend (result, serializeOid (rcol->pgtype)); - db2Debug4(" serialize res.pgtype: %d" , rcol->pgtype); + db2Debug5(" serialize res.pgtype: %d" , rcol->pgtype); result = lappend (result, serializeInt (rcol->pgtypmod)); - db2Debug4(" serialize res.pgtypmod: %d" , rcol->pgtypmod); + db2Debug5(" serialize res.pgtypmod: %d" , rcol->pgtypmod); result = lappend (result, serializeInt (rcol->pkey)); - db2Debug4(" serialize res.pkey: %d" , rcol->pkey); + db2Debug5(" serialize res.pkey: %d" , rcol->pkey); result = lappend (result, serializeLong (rcol->val_size)); - db2Debug4(" serialize res.val_size: %ld", rcol->val_size); + db2Debug5(" serialize res.val_size: %ld", rcol->val_size); result = lappend (result, serializeInt (rcol->noencerr)); - db2Debug4(" serialize res.noencerr: %d" , rcol->noencerr); + db2Debug5(" serialize res.noencerr: %d" , rcol->noencerr); result = lappend (result, serializeInt (rcol->resnum)); // the last result is the first in the list - db2Debug4(" serialize res.resnum: %d" , rcol->resnum); + db2Debug5(" serialize res.resnum: %d" , rcol->resnum); lenParam--; } @@ -401,10 +388,10 @@ List* serializePlanData (DB2FdwState* fdwState) { */ static Const* serializeString (const char* s) { Const* result = NULL; - db2Debug5("> serializeString"); + db2Debug4("> serializeString"); result = (s == NULL) ? makeNullConst (TEXTOID, -1, InvalidOid) : makeConst (TEXTOID, -1, InvalidOid, -1, PointerGetDatum (cstring_to_text (s)), false, false); - db2Debug5("< serializeString - returns: %x",result); + db2Debug4("< serializeString - returns: %x",result); return result; } @@ -413,7 +400,7 @@ static Const* serializeString (const char* s) { */ static Const* serializeLong (long i) { Const* result = NULL; - db2Debug5("> serializeLong"); + db2Debug4("> serializeLong"); if (sizeof (long) <= 4) result = makeConst (INT4OID, -1, InvalidOid, 4, Int32GetDatum ((int32) i), false, true); else @@ -424,6 +411,6 @@ static Const* serializeLong (long i) { false #endif /* USE_FLOAT8_BYVAL */ ); - db2Debug5("< serializeLong - returns: %x",result); + db2Debug4("< serializeLong - returns: %x",result); return result; } From cd5c2306e8f9976dde9d7dc517849a82a4fd58e2 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Tue, 17 Feb 2026 10:01:26 +0100 Subject: [PATCH 067/120] added a compiler error when using PG < 13 --- include/db2_fdw.h | 7 ++++--- source/db2_deparse.c | 4 ---- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/include/db2_fdw.h b/include/db2_fdw.h index c759587..f8c32bc 100644 --- a/include/db2_fdw.h +++ b/include/db2_fdw.h @@ -17,6 +17,10 @@ #define STRVAL(arg) ((Value*)(arg))->val.str #endif +#if PG_VERSION_NUM < 130000 +#error "This extension requires PostgreSQL version 13.0 or higher." +#endif + /* defined in backend/commands/analyze.c */ #ifndef WIDTH_THRESHOLD #define WIDTH_THRESHOLD 1024 @@ -26,11 +30,8 @@ #define PQ_QUERY_PARAM_MAX_LIMIT 65535 #endif /* PQ_QUERY_PARAM_MAX_LIMIT */ -#undef OLD_FDW_API - /* array_create_iterator has a new signature from 9.5 on */ #define array_create_iterator(arr, slice_ndim) array_create_iterator(arr, slice_ndim, NULL) -#define JOIN_API /* GetConfigOptionByName has a new signature from 9.6 on */ #define GetConfigOptionByName(name, varname) GetConfigOptionByName(name, varname, false) diff --git a/source/db2_deparse.c b/source/db2_deparse.c index 721a2d9..e03d5bf 100644 --- a/source/db2_deparse.c +++ b/source/db2_deparse.c @@ -2041,12 +2041,9 @@ static void deparseParamExpr (Param* expr, deparse_expr_cxt* // // db2Debug1("> %s::deparseVarExpr", __FILE__); // /* check if the variable belongs to one of our foreign tables */ -// #ifdef JOIN_API // if (IS_SIMPLE_REL (ctx->foreignrel)) { -// #endif /* JOIN_API */ // if (expr->varno == ctx->foreignrel->relid && expr->varlevelsup == 0) // var_table = ((DB2FdwState*)ctx->foreignrel->fdw_private)->db2Table; -// #ifdef JOIN_API // } else { // DB2FdwState* joinstate = (DB2FdwState*) ctx->foreignrel->fdw_private; // DB2FdwState* outerstate = (DB2FdwState*) joinstate->outerrel->fdw_private; @@ -2057,7 +2054,6 @@ static void deparseParamExpr (Param* expr, deparse_expr_cxt* // if (expr->varno == innerstate->db2Table->cols[0]->varno && expr->varlevelsup == 0) // var_table = innerstate->db2Table; // } -// #endif /* JOIN_API */ // if (var_table) { // /* the variable belongs to a foreign table, replace it with the name */ // /* we cannot handle system columns */ From 254ab304a692924d9cec7ba2d87dc6a86e22aebb Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Tue, 17 Feb 2026 15:24:16 +0100 Subject: [PATCH 068/120] rncols no longer required --- include/DB2Table.h | 1 - source/db2GetForeignPlan.c | 1 - source/db2_de_serialize.c | 3 --- 3 files changed, 5 deletions(-) diff --git a/include/DB2Table.h b/include/DB2Table.h index 85340f8..24ea2c8 100644 --- a/include/DB2Table.h +++ b/include/DB2Table.h @@ -15,6 +15,5 @@ typedef struct db2Table { int ncols; // number of columns in DB2 table int npgcols; // number of columns (including dropped) in the PostgreSQL foreign table DB2Column** cols; // pointer to an array of DB2Column descriptors, as many as ncols tells - int rncols; // number of result columns to be fetched from DB2 } DB2Table; #endif diff --git a/source/db2GetForeignPlan.c b/source/db2GetForeignPlan.c index 658174e..e0cdbce 100644 --- a/source/db2GetForeignPlan.c +++ b/source/db2GetForeignPlan.c @@ -234,7 +234,6 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo /* Build the fdw_private list that will be available to the executor. * Items in the list must match order in enum FdwScanPrivateIndex. */ - fpinfo->db2Table->rncols = ptlist_len; fpinfo->query = sql.data; fpinfo->retrieved_attr = retrieved_attrs; fdw_private = serializePlanData(fpinfo); diff --git a/source/db2_de_serialize.c b/source/db2_de_serialize.c index e2700a4..63e9d81 100644 --- a/source/db2_de_serialize.c +++ b/source/db2_de_serialize.c @@ -69,7 +69,6 @@ DB2FdwState* deserializePlanData (List* list) { state->db2Table->pgname = deserializeString(list_nth(list, idx++)); state->db2Table->batchsz = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); state->db2Table->ncols = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - state->db2Table->rncols = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); state->db2Table->npgcols = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); state->db2Table->cols = (DB2Column**) db2alloc ("state->db2Table->cols", sizeof (DB2Column*) * state->db2Table->ncols); @@ -261,8 +260,6 @@ List* serializePlanData (DB2FdwState* fdwState) { result = lappend (result, serializeInt (fdwState->db2Table->batchsz)); /* number of columns in DB2 table */ result = lappend (result, serializeInt (fdwState->db2Table->ncols)); - /* number of result columns in DB2 table */ - result = lappend (result, serializeInt (fdwState->db2Table->rncols)); /* number of columns in PostgreSQL table */ result = lappend (result, serializeInt (fdwState->db2Table->npgcols)); /* column data */ From 02fd943e81a06a7e2435190ebc3439d8b2fad5bd Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Tue, 17 Feb 2026 15:26:26 +0100 Subject: [PATCH 069/120] cleanup code --- source/db2CopyText.c | 6 +++--- source/db2GetOptions.c | 15 ++++++++++++++- source/db2ImportForeignSchema.c | 6 +++--- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/source/db2CopyText.c b/source/db2CopyText.c index 8d13ab6..47fe2e6 100644 --- a/source/db2CopyText.c +++ b/source/db2CopyText.c @@ -9,7 +9,7 @@ /** external prototypes */ extern void* db2alloc (const char* type, size_t size); -extern void db2Debug1 (const char* message, ...); +extern void db2Debug4 (const char* message, ...); /** local prototypes */ char* db2CopyText (const char* string, int size, int quote); @@ -25,7 +25,7 @@ char* db2CopyText (const char* string, int size, int quote) { register int j = -1; char* result; - db2Debug1("> db2CopyText(string: '%s', size: %d, quote: %d)",string,size,quote); + db2Debug4("> db2CopyText(string: '%s', size: %d, quote: %d)",string,size,quote); /* if "string" is parenthized, return a copy */ if (string[0] == '(' && string[size - 1] == ')') { result = db2alloc ("copyText", size + 1); @@ -53,6 +53,6 @@ char* db2CopyText (const char* string, int size, int quote) { result[++j] = '"'; result[j + 1] = '\0'; - db2Debug1("< db2CopyText - result: %s",result); + db2Debug4("< db2CopyText - result: %s",result); return result; } diff --git a/source/db2GetOptions.c b/source/db2GetOptions.c index dd2e453..7be738f 100644 --- a/source/db2GetOptions.c +++ b/source/db2GetOptions.c @@ -8,9 +8,11 @@ /** external prototypes */ extern void db2Debug1 (const char* message, ...); +extern void db2Debug4 (const char* message, ...); /** local prototypes */ void db2GetOptions(Oid foreigntableid, List** options); +bool optionIsTrue (const char* value); /** db2GetOptions * Fetch the options for an db2_fdw foreign table. @@ -54,4 +56,15 @@ void db2GetOptions (Oid foreigntableid, List** options) { db2Debug1(" unable to GetForeignTable: %d",foreigntableid); } db2Debug1("< db2GetOptions"); -} \ No newline at end of file +} + +/* optionIsTrue + * Returns true if the string is "true", "on" or "yes". + */ +bool optionIsTrue (const char *value) { + bool result = false; + db2Debug4("> optionIsTrue(value: '%s')",value); + result = (pg_strcasecmp (value, "on") == 0 || pg_strcasecmp (value, "yes") == 0 || pg_strcasecmp (value, "true") == 0); + db2Debug4("< optionIsTrue - returns: '%s'",((result) ? "true" : "false")); + return result; +} diff --git a/source/db2ImportForeignSchema.c b/source/db2ImportForeignSchema.c index 2622930..68bdad2 100644 --- a/source/db2ImportForeignSchema.c +++ b/source/db2ImportForeignSchema.c @@ -22,9 +22,9 @@ extern DB2Table* describeForeignTable (DB2Session* session, char* schema extern bool optionIsTrue (const char* value); /** local prototypes */ -List* db2ImportForeignSchema(ImportForeignSchemaStmt* stmt, Oid serverOid); -static char* fold_case (char* name, fold_t foldcase); -static void generateForeignTableCreate(StringInfo buf, char* servername, char* local_schema, char* remote_schema, DB2Table* db2Table, fold_t foldcase, bool readonly); + List* db2ImportForeignSchema (ImportForeignSchemaStmt* stmt, Oid serverOid); +static char* fold_case (char* name, fold_t foldcase); +static void generateForeignTableCreate(StringInfo buf, char* servername, char* local_schema, char* remote_schema, DB2Table* db2Table, fold_t foldcase, bool readonly); /** db2ImportForeignSchema * Returns a List of CREATE FOREIGN TABLE statements. From 6014f8e6e0755e1b03cdbcd5f0771de4a3b4bcf4 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Tue, 17 Feb 2026 15:27:21 +0100 Subject: [PATCH 070/120] first try the new way of describing the fdw table if that fails use the old way --- source/db2GetFdwState.c | 336 ++++++++++++++++++++++++++++++++-------- 1 file changed, 272 insertions(+), 64 deletions(-) diff --git a/source/db2GetFdwState.c b/source/db2GetFdwState.c index 606b39a..bcb04c9 100644 --- a/source/db2GetFdwState.c +++ b/source/db2GetFdwState.c @@ -1,26 +1,32 @@ #include -#include -#include +#include #include #include +#include +#include +#include +#include #include "db2_fdw.h" #include "DB2FdwState.h" /** external prototypes */ -extern char* guessNlsLang (char* nls_lang); -extern void db2GetOptions (Oid foreigntableid, List** options); -extern DB2Session* db2GetSession (const char* connectstring, char* user, char* password, char* jwt_token, const char* nls_lang, int curlevel); -extern DB2Table* db2Describe (DB2Session* session, char* schema, char* table, char* pgname, long max_long, char* noencerr, char* batchsz); -extern void db2Debug1 (const char* message, ...); -extern void db2Debug2 (const char* message, ...); -extern void db2Debug3 (const char* message, ...); -extern void* db2alloc (const char* type, size_t size); -extern char* db2strdup (const char* source); +extern char* guessNlsLang (char* nls_lang); +extern void db2GetOptions (Oid foreigntableid, List** options); +extern bool optionIsTrue (const char* value); +extern DB2Session* db2GetSession (const char* connectstring, char* user, char* password, char* jwt_token, const char* nls_lang, int curlevel); +extern DB2Table* db2Describe (DB2Session* session, char* schema, char* table, char* pgname, long max_long, char* noencerr, char* batchsz); +extern char* db2CopyText (const char* string, int size, int quote); +extern char* c2name (short fcType); +extern void db2Debug1 (const char* message, ...); +extern void db2Debug2 (const char* message, ...); +extern void* db2alloc (const char* type, size_t size); +extern void db2free (void* p); +extern char* db2strdup (const char* source); /** local prototypes */ DB2FdwState* db2GetFdwState(Oid foreigntableid, double* sample_percent, bool describe); +static DB2Table* describeForeignTable (Oid foreigntableid, char* schema, char* table, char* pgname, long max_long, char* noencerr, char* batchsz); static void getColumnData (DB2Table* db2Table, Oid foreigntableid); - bool optionIsTrue (const char* value); /** db2GetFdwState * Construct an DB2FdwState from the options of the foreign table. @@ -32,23 +38,20 @@ static void getColumnData (DB2Table* db2Table, Oid foreigntableid); DB2FdwState* db2GetFdwState (Oid foreigntableid, double* sample_percent, bool describe) { DB2FdwState* fdwState = db2alloc("fdw_state", sizeof (DB2FdwState)); char* pgtablename = get_rel_name (foreigntableid); - List* options; - ListCell* cell; - char* schema = NULL; - char* table = NULL; - char* maxlong = NULL; - char* sample = NULL; - char* prefetch = NULL; - char* fetchsz = NULL; - char* noencerr = NULL; - char* batchsz = NULL; - long max_long; + List* options = NIL; + ListCell* cell = NULL; + char* schema = NULL; + char* table = NULL; + char* maxlong = NULL; + char* sample = NULL; + char* prefetch = NULL; + char* fetchsz = NULL; + char* noencerr = NULL; + char* batchsz = NULL; + long max_long = 0; db2Debug1("> db2GetFdwState"); - /* - * Get all relevant options from the foreign table, the user mapping, - * the foreign server and the foreign data wrapper. - */ + /* Get all relevant options from the foreign table, the user mapping, the foreign server and the foreign data wrapper. */ db2GetOptions (foreigntableid, &options); foreach (cell, options) { DefElem *def = (DefElem *) lfirst (cell); @@ -81,10 +84,7 @@ DB2FdwState* db2GetFdwState (Oid foreigntableid, double* sample_percent, bool de } /* convert "max_long" option to number or use default */ - if (maxlong == NULL) - max_long = DEFAULT_MAX_LONG; - else - max_long = strtol (maxlong, NULL, 0); + max_long = (maxlong == NULL) ? DEFAULT_MAX_LONG : strtol (maxlong, NULL, 0); /* convert "sample_percent" to double */ if (sample_percent != NULL) { @@ -93,45 +93,264 @@ DB2FdwState* db2GetFdwState (Oid foreigntableid, double* sample_percent, bool de else *sample_percent = strtod (sample, NULL); } - /* convert "prefetch" to number (or use default) */ - if (prefetch == NULL) - fdwState->prefetch = DEFAULT_PREFETCH; - else - fdwState->prefetch = (unsigned long) strtoul (prefetch, NULL, 0); + fdwState->prefetch = (prefetch == NULL) ? DEFAULT_PREFETCH : (unsigned long) strtoul (prefetch, NULL, 0); /* convert "fetchsize" to number (or use default) */ - if (fetchsz == NULL) - fdwState->fetch_size = DEFAULT_FETCHSZ; - else - fdwState->fetch_size = (int) strtol (fetchsz, NULL, 0); + fdwState->fetch_size = (fetchsz == NULL) ? DEFAULT_FETCHSZ : (int) strtol (fetchsz, NULL, 0); /* check if options are ok */ - if (table == NULL) + if (table == NULL) { ereport (ERROR, (errcode (ERRCODE_FDW_OPTION_NAME_NOT_FOUND), errmsg ("required option \"%s\" in foreign table \"%s\" missing", OPT_TABLE, pgtablename))); + } /* guess a good NLS_LANG environment setting */ fdwState->nls_lang = guessNlsLang (fdwState->nls_lang); - /* connect to DB2 database */ - fdwState->session = db2GetSession (fdwState->dbserver, fdwState->user, fdwState->password, fdwState->jwt_token, fdwState->nls_lang, GetCurrentTransactionNestLevel () ); - if (describe) { - /* get remote table description */ - fdwState->db2Table = db2Describe (fdwState->session, schema, table, pgtablename, max_long, noencerr, batchsz); - - /* add PostgreSQL data to table description */ - getColumnData (fdwState->db2Table, foreigntableid); + fdwState->db2Table = describeForeignTable(foreigntableid, schema, table, pgtablename, max_long, noencerr, batchsz); + if (fdwState->db2Table == NULL) { + /* connect to DB2 database */ + fdwState->session = db2GetSession (fdwState->dbserver, fdwState->user, fdwState->password, fdwState->jwt_token, fdwState->nls_lang, GetCurrentTransactionNestLevel () ); + /* get remote table description */ + fdwState->db2Table = db2Describe (fdwState->session, schema, table, pgtablename, max_long, noencerr, batchsz); + /* add PostgreSQL data to table description */ + getColumnData (fdwState->db2Table, foreigntableid); + } } db2Debug1("< db2GetFdwState"); return fdwState; } -/** getColumnData - * Get PostgreSQL column name and number, data type and data type modifier. - * Set db2Table->npgcols. - * For PostgreSQL 9.2 and better, find the primary key columns and mark them in db2Table. +static DB2Table* describeForeignTable (Oid foreigntableid, char* schema, char* table, char* pgname, long max_long, char* noencerr, char* batchsz) { + DB2Table* db2Table = NULL; + char* qtable = NULL; + char* qschema = NULL; + char* tablename = NULL; + Relation rel; + TupleDesc tupdesc; + int length = 0; + + db2Debug1("> %s::describeForeignTable",__FILE__); + + db2Table = (DB2Table*)db2alloc("db2_table", sizeof (DB2Table)); + /* get a complete quoted table name */ + qtable = db2CopyText (table, strlen (table), 1); + length = strlen (qtable); + if (schema != NULL) { + qschema = db2CopyText (schema, strlen (schema), 1); + length += strlen (qschema) + 1; + } + tablename = db2alloc ("db2Table->name", length + 1); + tablename[0] = '\0'; /* empty */ + if (schema != NULL) { + strncat (tablename, qschema, length); + strncat (tablename, ".", length); + } + strncat (tablename, qtable,length); + db2free (qtable); + if (schema != NULL) + db2free (qschema); + + db2Table->name = tablename; + db2Debug2(" table description"); + db2Debug2(" db2Table->name : '%s'", db2Table->name); + db2Table->pgname = pgname; + db2Debug2(" db2Table->pgname : '%s'", db2Table->pgname); + + db2Table->batchsz = DEFAULT_BATCHSZ; + if (batchsz != NULL) { + char* end; + db2Table->batchsz = strtol(batchsz,&end,10); + db2Debug2(" db2Table->batchsz : %d", db2Table->batchsz); + } + + rel = table_open (foreigntableid, NoLock); + tupdesc = rel->rd_att; + + db2Table->npgcols = tupdesc->natts; + db2Debug2(" db2Table->npgcols : %d", db2Table->npgcols); + db2Table->ncols = tupdesc->natts; + db2Debug2(" db2Table->ncols : %d", db2Table->ncols); + db2Table->cols = (DB2Column**) db2alloc ("db2Table->cols", sizeof (DB2Column*) * db2Table->ncols); + db2Debug2(" db2Table->cols : %x", db2Table->cols); + + /* loop through foreign table columns */ + for (int i = 0, cidx = 0; i < tupdesc->natts; ++i) { + Form_pg_attribute att_tuple = TupleDescAttr (tupdesc, i); + List* options = NIL; + ListCell* option = NULL; + + /* ignore dropped columns */ + if (att_tuple->attisdropped) { + continue; + } + /* get PostgreSQL column number and type */ + if (cidx <= db2Table->ncols) { + int bin_size = 0; + int charlen = 0; + unsigned int colSize = 0; + short scale = 0; + bool db2type_set = false; + bool db2size_set = false; + bool db2bytes_set = false; + bool db2chars_set = false; + bool db2scale_set = false; + bool db2nulls_set = false; + bool db2codepage_set = false; + + db2Table->cols[cidx] = (DB2Column*) db2alloc ("db2Table->cols[cidx]", sizeof (DB2Column)); + db2Table->cols[cidx]->used = 0; + db2Table->cols[cidx]->pgattnum = att_tuple->attnum; + db2Table->cols[cidx]->pgtype = att_tuple->atttypid; + db2Table->cols[cidx]->pgtypmod = att_tuple->atttypmod; + db2Table->cols[cidx]->pgname = db2strdup (NameStr(att_tuple->attname)); + db2Table->cols[cidx]->colName = db2CopyText ( str_toupper(db2Table->cols[cidx]->pgname, strlen(db2Table->cols[cidx]->pgname), DEFAULT_COLLATION_OID) + , strlen(db2Table->cols[cidx]->pgname) + , 1 + ); + /* loop through column options */ + options = GetForeignColumnOptions (foreigntableid, att_tuple->attnum); + if (noencerr != NULL) { + db2Table->cols[cidx]->noencerr = optionIsTrue(noencerr) ? NO_ENC_ERR_TRUE : NO_ENC_ERR_FALSE; + } else { + db2Table->cols[cidx]->noencerr = NO_ENC_ERR_NULL; + } + foreach (option, options) { + DefElem* def = (DefElem*) lfirst (option); + if (strcmp (def->defname, OPT_KEY) == 0) { + db2Table->cols[cidx]->pkey = optionIsTrue ((STRVAL(def->arg))) ? 1 : 0; /* is it the "key" option and is it set to "true" ? */ + db2Table->cols[cidx]->colPrimKeyPart = db2Table->cols[cidx]->pkey; + } else if (strcmp (def->defname, OPT_NO_ENCODING_ERROR) == 0) { + db2Table->cols[cidx]->noencerr = optionIsTrue((STRVAL(def->arg))) ? NO_ENC_ERR_TRUE : NO_ENC_ERR_FALSE; /* is it the "no_encoding_error" option set */ + } else if (strcmp (def->defname, OPT_DB2TYPE) == 0) { + db2type_set = true; + db2Table->cols[cidx]->colType = (short)strtol(STRVAL(def->arg), NULL, 10); + } else if (strcmp (def->defname, OPT_DB2SIZE) == 0) { + db2size_set = true; + db2Table->cols[cidx]->colSize = (size_t)strtol(STRVAL(def->arg), NULL, 10); + } else if (strcmp (def->defname, OPT_DB2BYTES) == 0) { + db2bytes_set = true; + db2Table->cols[cidx]->colBytes = (size_t)strtol(STRVAL(def->arg), NULL, 10); + } else if (strcmp (def->defname, OPT_DB2CHARS) == 0) { + db2chars_set = true; + db2Table->cols[cidx]->colChars = (size_t)strtol(STRVAL(def->arg), NULL, 10); + } else if (strcmp (def->defname, OPT_DB2SCALE) == 0) { + db2scale_set = true; + db2Table->cols[cidx]->colScale = (short)strtol(STRVAL(def->arg), NULL, 10); + } else if (strcmp (def->defname, OPT_DB2NULL) == 0) { + db2nulls_set = true; + db2Table->cols[cidx]->colNulls = (short)strtol(STRVAL(def->arg), NULL, 10); + } else if (strcmp (def->defname, OPT_DB2CCSID) == 0) { + db2codepage_set = true; + db2Table->cols[cidx]->colCodepage = (int)strtol(STRVAL(def->arg), NULL, 10); + } + } + if (!db2type_set || !db2size_set || !db2bytes_set || !db2chars_set || !db2scale_set || !db2nulls_set || !db2codepage_set) { + db2Debug1(" column %d - %s without required options, discarding db2Table", cidx, db2Table->cols[cidx]->pgname); + db2free (db2Table); + db2Table = NULL; + break; + } + bin_size = db2Table->cols[cidx]->colBytes; + charlen = db2Table->cols[cidx]->colChars; + colSize = db2Table->cols[cidx]->colSize; + scale = db2Table->cols[cidx]->colScale; + // val_size berechnen + /* determine db2Type and length to allocate */ + switch (db2Table->cols[cidx]->colType) { + case SQL_CHAR: + case SQL_VARCHAR: + case SQL_LONGVARCHAR: + db2Table->cols[cidx]->val_size = bin_size + 1; + break; + case SQL_BLOB: + case SQL_CLOB: + db2Table->cols[cidx]->val_size = bin_size + 1; + break; + case SQL_GRAPHIC: + case SQL_VARGRAPHIC: + case SQL_LONGVARGRAPHIC: + case SQL_WCHAR: + case SQL_WVARCHAR: + case SQL_WLONGVARCHAR: + case SQL_DBCLOB: + db2Table->cols[cidx]->val_size = bin_size + 1; + break; + case SQL_BOOLEAN: + db2Table->cols[cidx]->val_size = bin_size + 1; + break; + case SQL_INTEGER: + case SQL_SMALLINT: + db2Table->cols[cidx]->val_size = charlen + 2; + break; + case SQL_NUMERIC: + case SQL_DECIMAL: + if (db2Table->cols[cidx]->colScale == 0) + db2Table->cols[cidx]->val_size = bin_size; + else + db2Table->cols[cidx]->val_size = (scale > colSize ? scale : colSize) + 5; + break; + case SQL_REAL: + case SQL_DOUBLE: + case SQL_FLOAT: + case SQL_DECFLOAT: + db2Table->cols[cidx]->val_size = 24 + 1; + break; + case SQL_TYPE_DATE: + case SQL_TYPE_TIME: + case SQL_TYPE_TIMESTAMP: + case SQL_TYPE_TIMESTAMP_WITH_TIMEZONE: + db2Table->cols[cidx]->val_size = colSize + 1; + break; + case SQL_BIGINT: + db2Table->cols[cidx]->val_size = 24; + break; + case SQL_XML: + db2Table->cols[cidx]->val_size = LOB_CHUNK_SIZE + 1; + break; + case SQL_BINARY: + case SQL_VARBINARY: + case SQL_LONGVARBINARY: + db2Table->cols[cidx]->val_size = bin_size; + break; + default: + break; + } + db2Debug2(" db2Table->cols >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); + db2Debug2(" db2Table->cols[%d] : %x" , cidx, db2Table->cols[cidx]); + db2Debug2(" db2Table->cols[%d]->colName : %s" , cidx, db2Table->cols[cidx]->colName); + db2Debug2(" db2Table->cols[%d]->colType : %d - (%s)" , cidx, db2Table->cols[cidx]->colType,c2name(db2Table->cols[cidx]->colType)); + db2Debug2(" db2Table->cols[%d]->colSize : %ld", cidx, db2Table->cols[cidx]->colSize); + db2Debug2(" db2Table->cols[%d]->colScale : %d" , cidx, db2Table->cols[cidx]->colScale); + db2Debug2(" db2Table->cols[%d]->colNulls : %d" , cidx, db2Table->cols[cidx]->colNulls); + db2Debug2(" db2Table->cols[%d]->colChars : %ld", cidx, db2Table->cols[cidx]->colChars); + db2Debug2(" db2Table->cols[%d]->colBytes : %ld", cidx, db2Table->cols[cidx]->colBytes); + db2Debug2(" db2Table->cols[%d]->colPrimKeyPart : %d" , cidx, db2Table->cols[cidx]->colPrimKeyPart); + db2Debug2(" db2Table->cols[%d]->colCodepage : %d" , cidx, db2Table->cols[cidx]->colCodepage); + db2Debug2(" db2Table->cols[%d]->pgrelid : %d" , cidx, db2Table->cols[cidx]->pgrelid); + db2Debug2(" db2Table->cols[%d]->pgname : %s" , cidx, db2Table->cols[cidx]->pgname); + db2Debug2(" db2Table->cols[%d]->pgattnum : %d" , cidx, db2Table->cols[cidx]->pgattnum); + db2Debug2(" db2Table->cols[%d]->pgtype : %d" , cidx, db2Table->cols[cidx]->pgtype); + db2Debug2(" db2Table->cols[%d]->pgtypmod : %d" , cidx, db2Table->cols[cidx]->pgtypmod); + db2Debug2(" db2Table->cols[%d]->used : %d" , cidx, db2Table->cols[cidx]->used); + db2Debug2(" db2Table->cols[%d]->pkey : %d" , cidx, db2Table->cols[cidx]->pkey); + db2Debug2(" db2Table->cols[%d]->val_size : %ld", cidx, db2Table->cols[cidx]->val_size); + db2Debug2(" db2Table->cols[%d]->noencerr : %d" , cidx, db2Table->cols[cidx]->noencerr); + } + ++cidx; + } + + table_close (rel, NoLock); + db2Debug1("< %s::describeForeignTable : %x",__FILE__, db2Table); + return db2Table; +} + +/* getColumnData + * Get PostgreSQL column name and number, data type and data type modifier. + * Set db2Table->npgcols. + * For PostgreSQL 9.2 and better, find the primary key columns and mark them in db2Table. */ static void getColumnData (DB2Table* db2Table, Oid foreigntableid) { Relation rel; @@ -184,14 +403,3 @@ static void getColumnData (DB2Table* db2Table, Oid foreigntableid) { table_close (rel, NoLock); db2Debug2(" < getColumnData"); } - -/* optionIsTrue - * Returns true if the string is "true", "on" or "yes". - */ -bool optionIsTrue (const char *value) { - bool result = false; - db2Debug3(" > optionIsTrue(value: '%s')",value); - result = (pg_strcasecmp (value, "on") == 0 || pg_strcasecmp (value, "yes") == 0 || pg_strcasecmp (value, "true") == 0); - db2Debug3(" < optionIsTrue - returns: '%s'",((result) ? "true" : "false")); - return result; -} From 9d86f6c09511a6f0f486556c7b35ada94385ad60 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Tue, 17 Feb 2026 17:33:53 +0100 Subject: [PATCH 071/120] the function has been added to db2GetFdwState, as it is only used there --- source/db2GetOptions.c | 70 ------------------------------------------ 1 file changed, 70 deletions(-) delete mode 100644 source/db2GetOptions.c diff --git a/source/db2GetOptions.c b/source/db2GetOptions.c deleted file mode 100644 index 7be738f..0000000 --- a/source/db2GetOptions.c +++ /dev/null @@ -1,70 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include "db2_fdw.h" - -/** external prototypes */ -extern void db2Debug1 (const char* message, ...); -extern void db2Debug4 (const char* message, ...); - -/** local prototypes */ -void db2GetOptions(Oid foreigntableid, List** options); -bool optionIsTrue (const char* value); - -/** db2GetOptions - * Fetch the options for an db2_fdw foreign table. - * Returns a union of the options of the foreign data wrapper, - * the foreign server, the user mapping and the foreign table, - * in that order. Column options are ignored. - */ -void db2GetOptions (Oid foreigntableid, List** options) { - ForeignDataWrapper* wrapper = NULL; - ForeignServer* server = NULL; - UserMapping* mapping = NULL; - ForeignTable* table = NULL; - - db2Debug1("> db2GetOptions"); - /** Gather all data for the foreign table. */ - table = GetForeignTable(foreigntableid); - if (table != NULL) { - server = GetForeignServer(table->serverid); - mapping = GetUserMapping(GetUserId(), table->serverid); - if (server != NULL) { - wrapper = GetForeignDataWrapper(server->fdwid); - } else { - db2Debug1(" unable to GetForeignServer: %d", table->serverid); - } - /* later options override earlier ones */ - *options = NIL; - if (wrapper != NULL) - *options = list_concat(*options, wrapper->options); - else - db2Debug1(" unable to get wrapper options"); - if (server != NULL) - *options = list_concat(*options, server->options); - else - db2Debug1(" unable to get server options"); - if (mapping != NULL) - *options = list_concat(*options, mapping->options); - else - db2Debug1(" unable to get mapping options"); - *options = list_concat(*options, table->options); - } else { - db2Debug1(" unable to GetForeignTable: %d",foreigntableid); - } - db2Debug1("< db2GetOptions"); -} - -/* optionIsTrue - * Returns true if the string is "true", "on" or "yes". - */ -bool optionIsTrue (const char *value) { - bool result = false; - db2Debug4("> optionIsTrue(value: '%s')",value); - result = (pg_strcasecmp (value, "on") == 0 || pg_strcasecmp (value, "yes") == 0 || pg_strcasecmp (value, "true") == 0); - db2Debug4("< optionIsTrue - returns: '%s'",((result) ? "true" : "false")); - return result; -} From 484f8cd840b93fdba9c1716cd9446e50b9ed2331 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Tue, 17 Feb 2026 17:34:13 +0100 Subject: [PATCH 072/120] db2Getoptions.c has become obsolete --- Makefile | 1 - 1 file changed, 1 deletion(-) diff --git a/Makefile b/Makefile index f18046b..56d67af 100644 --- a/Makefile +++ b/Makefile @@ -35,7 +35,6 @@ OBJS = source/db2_fdw.o\ source/db2ReAllocFree.o\ source/db2SetHandlers.o\ source/db2Callbacks.o\ - source/db2GetOptions.o\ source/db2Debug.o\ source/db2ServerVersion.o\ source/db2ClientVersion.o\ From e9f4f2406379f5a901665a25f86395710b81b897 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Tue, 17 Feb 2026 17:35:05 +0100 Subject: [PATCH 073/120] code and debug beautifying --- source/db2Describe.c | 2 +- source/db2GetFdwState.c | 166 +++++++++++++++++++------------- source/db2ImportForeignSchema.c | 91 +++++++++-------- source/db2_fdw.c | 1 + source/db2_fdw_utils.c | 141 +++++++++++++-------------- 5 files changed, 220 insertions(+), 181 deletions(-) diff --git a/source/db2Describe.c b/source/db2Describe.c index 990aa78..7d305ce 100644 --- a/source/db2Describe.c +++ b/source/db2Describe.c @@ -129,7 +129,7 @@ DB2Table* db2Describe (DB2Session* session, char* schema, char* table, char* pgn /* allocate an db2Column struct for the column */ reply->cols[i - 1] = (DB2Column *) db2alloc (" reply->cols[i - 1]", sizeof (DB2Column)); reply->cols[i - 1]->colPrimKeyPart = 0; - reply->cols[i -1 ]->colCodepage = 0; + reply->cols[i - 1]->colCodepage = 0; reply->cols[i - 1]->pgname = NULL; reply->cols[i - 1]->pgattnum = 0; reply->cols[i - 1]->pgtype = 0; diff --git a/source/db2GetFdwState.c b/source/db2GetFdwState.c index bcb04c9..9c77af6 100644 --- a/source/db2GetFdwState.c +++ b/source/db2GetFdwState.c @@ -1,9 +1,8 @@ #include #include #include -#include #include -#include +#include #include #include #include "db2_fdw.h" @@ -11,7 +10,6 @@ /** external prototypes */ extern char* guessNlsLang (char* nls_lang); -extern void db2GetOptions (Oid foreigntableid, List** options); extern bool optionIsTrue (const char* value); extern DB2Session* db2GetSession (const char* connectstring, char* user, char* password, char* jwt_token, const char* nls_lang, int curlevel); extern DB2Table* db2Describe (DB2Session* session, char* schema, char* table, char* pgname, long max_long, char* noencerr, char* batchsz); @@ -19,6 +17,9 @@ extern char* db2CopyText (const char* string, int size, int quote); extern char* c2name (short fcType); extern void db2Debug1 (const char* message, ...); extern void db2Debug2 (const char* message, ...); +extern void db2Debug3 (const char* message, ...); +extern void db2Debug4 (const char* message, ...); +extern void db2Debug5 (const char* message, ...); extern void* db2alloc (const char* type, size_t size); extern void db2free (void* p); extern char* db2strdup (const char* source); @@ -26,7 +27,9 @@ extern char* db2strdup (const char* source); /** local prototypes */ DB2FdwState* db2GetFdwState(Oid foreigntableid, double* sample_percent, bool describe); static DB2Table* describeForeignTable (Oid foreigntableid, char* schema, char* table, char* pgname, long max_long, char* noencerr, char* batchsz); -static void getColumnData (DB2Table* db2Table, Oid foreigntableid); +static void getColumnData (DB2Table* db2Table, Oid foreigntableid); +static void getOptions (Oid foreigntableid, List** options); + /** db2GetFdwState * Construct an DB2FdwState from the options of the foreign table. @@ -50,37 +53,25 @@ DB2FdwState* db2GetFdwState (Oid foreigntableid, double* sample_percent, bool de char* batchsz = NULL; long max_long = 0; - db2Debug1("> db2GetFdwState"); + db2Debug1("> %s::db2GetFdwState", __FILE__); /* Get all relevant options from the foreign table, the user mapping, the foreign server and the foreign data wrapper. */ - db2GetOptions (foreigntableid, &options); + getOptions (foreigntableid, &options); + foreach (cell, options) { DefElem *def = (DefElem *) lfirst (cell); - if (strcmp (def->defname, OPT_NLS_LANG) == 0) - fdwState->nls_lang = STRVAL(def->arg); - if (strcmp (def->defname, OPT_DBSERVER) == 0) - fdwState->dbserver = STRVAL(def->arg); - if (strcmp (def->defname, OPT_USER) == 0) - fdwState->user = STRVAL(def->arg); - if (strcmp (def->defname, OPT_PASSWORD) == 0) - fdwState->password = STRVAL(def->arg); - if (strcmp (def->defname, OPT_JWT_TOKEN) == 0) - fdwState->jwt_token = STRVAL(def->arg); - if (strcmp (def->defname, OPT_SCHEMA) == 0) - schema = STRVAL(def->arg); - if (strcmp (def->defname, OPT_TABLE) == 0) - table = STRVAL(def->arg); - if (strcmp (def->defname, OPT_MAX_LONG) == 0) - maxlong = STRVAL(def->arg); - if (strcmp (def->defname, OPT_SAMPLE) == 0) - sample = STRVAL(def->arg); - if (strcmp (def->defname, OPT_PREFETCH) == 0) - prefetch = STRVAL(def->arg); - if (strcmp (def->defname, OPT_FETCHSZ) == 0) - fetchsz = STRVAL(def->arg); - if (strcmp (def->defname, OPT_NO_ENCODING_ERROR) == 0) - noencerr = STRVAL(def->arg); - if (strcmp (def->defname, OPT_BATCH_SIZE) == 0) - batchsz = STRVAL(def->arg); + fdwState->nls_lang = (strcmp (def->defname, OPT_NLS_LANG) == 0) ? STRVAL(def->arg) : fdwState->nls_lang; + fdwState->dbserver = (strcmp (def->defname, OPT_DBSERVER) == 0) ? STRVAL(def->arg) : fdwState->dbserver; + fdwState->user = (strcmp (def->defname, OPT_USER) == 0) ? STRVAL(def->arg) : fdwState->user; + fdwState->password = (strcmp (def->defname, OPT_PASSWORD) == 0) ? STRVAL(def->arg) : fdwState->password; + fdwState->jwt_token = (strcmp (def->defname, OPT_JWT_TOKEN) == 0) ? STRVAL(def->arg) : fdwState->jwt_token; + schema = (strcmp (def->defname, OPT_SCHEMA) == 0) ? STRVAL(def->arg) : schema; + table = (strcmp (def->defname, OPT_TABLE) == 0) ? STRVAL(def->arg) : table; + maxlong = (strcmp (def->defname, OPT_MAX_LONG) == 0) ? STRVAL(def->arg) : maxlong; + sample = (strcmp (def->defname, OPT_SAMPLE) == 0) ? STRVAL(def->arg) : sample; + prefetch = (strcmp (def->defname, OPT_PREFETCH) == 0) ? STRVAL(def->arg) : prefetch; + fetchsz = (strcmp (def->defname, OPT_FETCHSZ) == 0) ? STRVAL(def->arg) : fetchsz; + noencerr = (strcmp (def->defname, OPT_NO_ENCODING_ERROR) == 0) ? STRVAL(def->arg) : noencerr; + batchsz = (strcmp (def->defname, OPT_BATCH_SIZE) == 0) ? STRVAL(def->arg) : batchsz; } /* convert "max_long" option to number or use default */ @@ -94,7 +85,7 @@ DB2FdwState* db2GetFdwState (Oid foreigntableid, double* sample_percent, bool de *sample_percent = strtod (sample, NULL); } /* convert "prefetch" to number (or use default) */ - fdwState->prefetch = (prefetch == NULL) ? DEFAULT_PREFETCH : (unsigned long) strtoul (prefetch, NULL, 0); + fdwState->prefetch = (prefetch == NULL) ? DEFAULT_PREFETCH : (unsigned long) strtoul (prefetch, NULL, 0); /* convert "fetchsize" to number (or use default) */ fdwState->fetch_size = (fetchsz == NULL) ? DEFAULT_FETCHSZ : (int) strtol (fetchsz, NULL, 0); @@ -119,7 +110,7 @@ DB2FdwState* db2GetFdwState (Oid foreigntableid, double* sample_percent, bool de } } - db2Debug1("< db2GetFdwState"); + db2Debug1("< %s::db2GetFdwState", __FILE__); return fdwState; } @@ -132,7 +123,7 @@ static DB2Table* describeForeignTable (Oid foreigntableid, char* schema, char* t TupleDesc tupdesc; int length = 0; - db2Debug1("> %s::describeForeignTable",__FILE__); + db2Debug2(" > %s::describeForeignTable",__FILE__); db2Table = (DB2Table*)db2alloc("db2_table", sizeof (DB2Table)); /* get a complete quoted table name */ @@ -154,27 +145,27 @@ static DB2Table* describeForeignTable (Oid foreigntableid, char* schema, char* t db2free (qschema); db2Table->name = tablename; - db2Debug2(" table description"); - db2Debug2(" db2Table->name : '%s'", db2Table->name); + db2Debug3(" table description"); + db2Debug3(" db2Table->name : '%s'", db2Table->name); db2Table->pgname = pgname; - db2Debug2(" db2Table->pgname : '%s'", db2Table->pgname); + db2Debug3(" db2Table->pgname : '%s'", db2Table->pgname); db2Table->batchsz = DEFAULT_BATCHSZ; if (batchsz != NULL) { char* end; db2Table->batchsz = strtol(batchsz,&end,10); - db2Debug2(" db2Table->batchsz : %d", db2Table->batchsz); + db2Debug3(" db2Table->batchsz : %d", db2Table->batchsz); } rel = table_open (foreigntableid, NoLock); tupdesc = rel->rd_att; db2Table->npgcols = tupdesc->natts; - db2Debug2(" db2Table->npgcols : %d", db2Table->npgcols); + db2Debug3(" db2Table->npgcols : %d", db2Table->npgcols); db2Table->ncols = tupdesc->natts; - db2Debug2(" db2Table->ncols : %d", db2Table->ncols); + db2Debug3(" db2Table->ncols : %d", db2Table->ncols); db2Table->cols = (DB2Column**) db2alloc ("db2Table->cols", sizeof (DB2Column*) * db2Table->ncols); - db2Debug2(" db2Table->cols : %x", db2Table->cols); + db2Debug3(" db2Table->cols : %x", db2Table->cols); /* loop through foreign table columns */ for (int i = 0, cidx = 0; i < tupdesc->natts; ++i) { @@ -248,7 +239,7 @@ static DB2Table* describeForeignTable (Oid foreigntableid, char* schema, char* t } } if (!db2type_set || !db2size_set || !db2bytes_set || !db2chars_set || !db2scale_set || !db2nulls_set || !db2codepage_set) { - db2Debug1(" column %d - %s without required options, discarding db2Table", cidx, db2Table->cols[cidx]->pgname); + db2Debug1(" INFO: column %d - %s without required options, discarding db2Table", cidx, db2Table->cols[cidx]->pgname); db2free (db2Table); db2Table = NULL; break; @@ -318,32 +309,32 @@ static DB2Table* describeForeignTable (Oid foreigntableid, char* schema, char* t default: break; } - db2Debug2(" db2Table->cols >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); - db2Debug2(" db2Table->cols[%d] : %x" , cidx, db2Table->cols[cidx]); - db2Debug2(" db2Table->cols[%d]->colName : %s" , cidx, db2Table->cols[cidx]->colName); - db2Debug2(" db2Table->cols[%d]->colType : %d - (%s)" , cidx, db2Table->cols[cidx]->colType,c2name(db2Table->cols[cidx]->colType)); - db2Debug2(" db2Table->cols[%d]->colSize : %ld", cidx, db2Table->cols[cidx]->colSize); - db2Debug2(" db2Table->cols[%d]->colScale : %d" , cidx, db2Table->cols[cidx]->colScale); - db2Debug2(" db2Table->cols[%d]->colNulls : %d" , cidx, db2Table->cols[cidx]->colNulls); - db2Debug2(" db2Table->cols[%d]->colChars : %ld", cidx, db2Table->cols[cidx]->colChars); - db2Debug2(" db2Table->cols[%d]->colBytes : %ld", cidx, db2Table->cols[cidx]->colBytes); - db2Debug2(" db2Table->cols[%d]->colPrimKeyPart : %d" , cidx, db2Table->cols[cidx]->colPrimKeyPart); - db2Debug2(" db2Table->cols[%d]->colCodepage : %d" , cidx, db2Table->cols[cidx]->colCodepage); - db2Debug2(" db2Table->cols[%d]->pgrelid : %d" , cidx, db2Table->cols[cidx]->pgrelid); - db2Debug2(" db2Table->cols[%d]->pgname : %s" , cidx, db2Table->cols[cidx]->pgname); - db2Debug2(" db2Table->cols[%d]->pgattnum : %d" , cidx, db2Table->cols[cidx]->pgattnum); - db2Debug2(" db2Table->cols[%d]->pgtype : %d" , cidx, db2Table->cols[cidx]->pgtype); - db2Debug2(" db2Table->cols[%d]->pgtypmod : %d" , cidx, db2Table->cols[cidx]->pgtypmod); - db2Debug2(" db2Table->cols[%d]->used : %d" , cidx, db2Table->cols[cidx]->used); - db2Debug2(" db2Table->cols[%d]->pkey : %d" , cidx, db2Table->cols[cidx]->pkey); - db2Debug2(" db2Table->cols[%d]->val_size : %ld", cidx, db2Table->cols[cidx]->val_size); - db2Debug2(" db2Table->cols[%d]->noencerr : %d" , cidx, db2Table->cols[cidx]->noencerr); + db2Debug3(" db2Table->cols >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); + db2Debug3(" db2Table->cols[%d] : %x" , cidx, db2Table->cols[cidx]); + db2Debug3(" db2Table->cols[%d]->colName : %s" , cidx, db2Table->cols[cidx]->colName); + db2Debug3(" db2Table->cols[%d]->colType : %d - (%s)" , cidx, db2Table->cols[cidx]->colType,c2name(db2Table->cols[cidx]->colType)); + db2Debug3(" db2Table->cols[%d]->colSize : %ld", cidx, db2Table->cols[cidx]->colSize); + db2Debug3(" db2Table->cols[%d]->colScale : %d" , cidx, db2Table->cols[cidx]->colScale); + db2Debug3(" db2Table->cols[%d]->colNulls : %d" , cidx, db2Table->cols[cidx]->colNulls); + db2Debug3(" db2Table->cols[%d]->colChars : %ld", cidx, db2Table->cols[cidx]->colChars); + db2Debug3(" db2Table->cols[%d]->colBytes : %ld", cidx, db2Table->cols[cidx]->colBytes); + db2Debug3(" db2Table->cols[%d]->colPrimKeyPart : %d" , cidx, db2Table->cols[cidx]->colPrimKeyPart); + db2Debug3(" db2Table->cols[%d]->colCodepage : %d" , cidx, db2Table->cols[cidx]->colCodepage); + db2Debug3(" db2Table->cols[%d]->pgrelid : %d" , cidx, db2Table->cols[cidx]->pgrelid); + db2Debug3(" db2Table->cols[%d]->pgname : %s" , cidx, db2Table->cols[cidx]->pgname); + db2Debug3(" db2Table->cols[%d]->pgattnum : %d" , cidx, db2Table->cols[cidx]->pgattnum); + db2Debug3(" db2Table->cols[%d]->pgtype : %d" , cidx, db2Table->cols[cidx]->pgtype); + db2Debug3(" db2Table->cols[%d]->pgtypmod : %d" , cidx, db2Table->cols[cidx]->pgtypmod); + db2Debug3(" db2Table->cols[%d]->used : %d" , cidx, db2Table->cols[cidx]->used); + db2Debug3(" db2Table->cols[%d]->pkey : %d" , cidx, db2Table->cols[cidx]->pkey); + db2Debug3(" db2Table->cols[%d]->val_size : %ld", cidx, db2Table->cols[cidx]->val_size); + db2Debug3(" db2Table->cols[%d]->noencerr : %d" , cidx, db2Table->cols[cidx]->noencerr); } ++cidx; } table_close (rel, NoLock); - db2Debug1("< %s::describeForeignTable : %x",__FILE__, db2Table); + db2Debug2(" < %s::describeForeignTable : %x",__FILE__, db2Table); return db2Table; } @@ -357,7 +348,7 @@ static void getColumnData (DB2Table* db2Table, Oid foreigntableid) { TupleDesc tupdesc; int i, index; - db2Debug2(" > getColumnData"); + db2Debug4(" > %s::getColumnData", __FILE__); rel = table_open (foreigntableid, NoLock); tupdesc = rel->rd_att; @@ -401,5 +392,48 @@ static void getColumnData (DB2Table* db2Table, Oid foreigntableid) { } table_close (rel, NoLock); - db2Debug2(" < getColumnData"); + db2Debug4(" < %s::getColumnData", __FILE__); } + +/* getOptions + * Fetch the options for an db2_fdw foreign table. + * Returns a union of the options of the foreign data wrapper, the foreign server, the user mapping and the foreign table, in that order. + * Column options are ignored. + */ +static void getOptions (Oid foreigntableid, List** options) { + ForeignDataWrapper* wrapper = NULL; + ForeignServer* server = NULL; + UserMapping* mapping = NULL; + ForeignTable* table = NULL; + + db2Debug4(" > %s::getOptions", __FILE__); + /** Gather all data for the foreign table. */ + table = GetForeignTable(foreigntableid); + if (table != NULL) { + server = GetForeignServer(table->serverid); + mapping = GetUserMapping(GetUserId(), table->serverid); + if (server != NULL) { + wrapper = GetForeignDataWrapper(server->fdwid); + } else { + db2Debug5(" unable to GetForeignServer: %d", table->serverid); + } + /* later options override earlier ones */ + *options = NIL; + if (wrapper != NULL) + *options = list_concat(*options, wrapper->options); + else + db2Debug5(" unable to get wrapper options"); + if (server != NULL) + *options = list_concat(*options, server->options); + else + db2Debug5(" unable to get server options"); + if (mapping != NULL) + *options = list_concat(*options, mapping->options); + else + db2Debug5(" unable to get mapping options"); + *options = list_concat(*options, table->options); + } else { + db2Debug5(" unable to GetForeignTable: %d",foreigntableid); + } + db2Debug4(" < %s::getOptions", __FILE__); +} \ No newline at end of file diff --git a/source/db2ImportForeignSchema.c b/source/db2ImportForeignSchema.c index 68bdad2..65683a9 100644 --- a/source/db2ImportForeignSchema.c +++ b/source/db2ImportForeignSchema.c @@ -13,6 +13,7 @@ extern DB2Session* db2GetSession (const char* connectstring, char* extern char* guessNlsLang (char* nls_lang); extern void db2Debug1 (const char* message, ...); extern void db2Debug2 (const char* message, ...); +extern void db2Debug4 (const char* message, ...); extern short c2dbType (short fcType); extern void db2free (void* p); extern char* db2strdup (const char* source); @@ -22,54 +23,40 @@ extern DB2Table* describeForeignTable (DB2Session* session, char* schema extern bool optionIsTrue (const char* value); /** local prototypes */ - List* db2ImportForeignSchema (ImportForeignSchemaStmt* stmt, Oid serverOid); -static char* fold_case (char* name, fold_t foldcase); -static void generateForeignTableCreate(StringInfo buf, char* servername, char* local_schema, char* remote_schema, DB2Table* db2Table, fold_t foldcase, bool readonly); + List* db2ImportForeignSchema (ImportForeignSchemaStmt* stmt, Oid serverOid); +static char* fold_case (char* name, fold_t foldcase); +static void generateForeignTableCreate(StringInfo buf, char* servername, char* local_schema, char* remote_schema, DB2Table* db2Table, fold_t foldcase, bool readonly); +static ForeignServer* getOptions (Oid serverOid, List** options); -/** db2ImportForeignSchema - * Returns a List of CREATE FOREIGN TABLE statements. +/* db2ImportForeignSchema + * Returns a List of CREATE FOREIGN TABLE statements. */ List* db2ImportForeignSchema (ImportForeignSchemaStmt* stmt, Oid serverOid) { - ForeignServer* server; - UserMapping* mapping; - ForeignDataWrapper* wrapper; char* nls_lang = NULL; char* user = NULL; char* password = NULL; char* jwt_token = NULL; char* dbserver = NULL; - List* options; - List* result = NIL; - ListCell* cell; - DB2Session* session; + List* options = NULL; + ListCell* cell = NULL; + DB2Session* session = NULL; fold_t foldcase = CASE_SMART; StringInfoData buf; bool readonly = false; + List* result = NIL; + ForeignServer* server = NULL; db2Debug1("> %s::db2ImportForeignSchema",__FILE__); - - /* get the foreign server, the user mapping and the FDW */ - server = GetForeignServer (serverOid); - mapping = GetUserMapping (GetUserId (), serverOid); - wrapper = GetForeignDataWrapper (server->fdwid); - - /* get all options for these objects */ - options = wrapper->options; - options = list_concat (options, server->options); - options = list_concat (options, mapping->options); - + /* process the server options */ + server = getOptions (serverOid, &options); foreach (cell, options) { DefElem *def = (DefElem *) lfirst (cell); - if (strcmp (def->defname, OPT_NLS_LANG) == 0) - nls_lang = STRVAL(def->arg); - if (strcmp (def->defname, OPT_DBSERVER) == 0) - dbserver = STRVAL(def->arg); - if (strcmp (def->defname, OPT_USER) == 0) - user = STRVAL(def->arg); - if (strcmp (def->defname, OPT_PASSWORD) == 0) - password = STRVAL(def->arg); - if (strcmp (def->defname, OPT_JWT_TOKEN) == 0) - jwt_token = STRVAL(def->arg); + db2Debug2(" option: '%s'", def->defname); + nls_lang = (strcmp (def->defname, OPT_NLS_LANG) == 0) ? STRVAL(def->arg) : nls_lang; + dbserver = (strcmp (def->defname, OPT_DBSERVER) == 0) ? STRVAL(def->arg) : dbserver; + user = (strcmp (def->defname, OPT_USER) == 0) ? STRVAL(def->arg) : user; + password = (strcmp (def->defname, OPT_PASSWORD) == 0) ? STRVAL(def->arg) : password; + jwt_token = (strcmp (def->defname, OPT_JWT_TOKEN) == 0) ? STRVAL(def->arg) : jwt_token; } /* process the options of the IMPORT FOREIGN SCHEMA command */ @@ -85,19 +72,14 @@ List* db2ImportForeignSchema (ImportForeignSchemaStmt* stmt, Oid serverOid) { else if (strcmp (s, "smart") == 0) foldcase = CASE_SMART; else - ereport (ERROR - , ( errcode(ERRCODE_FDW_INVALID_ATTRIBUTE_VALUE) - , errmsg("invalid value for option \"%s\"", def->defname) - , errhint("Valid values in this context are: %s", "keep, lower, smart") - ) - ); + ereport (ERROR, ( errcode(ERRCODE_FDW_INVALID_ATTRIBUTE_VALUE), errmsg("invalid value for option \"%s\"", def->defname), errhint("Valid values in this context are: %s", "keep, lower, smart"))); continue; } else if (strcmp (def->defname, "readonly") == 0) { char *s = STRVAL(def->arg); if (pg_strcasecmp (s, "on") != 0 || pg_strcasecmp (s, "yes") != 0 || pg_strcasecmp (s, "true") != 0 || pg_strcasecmp (s, "off") != 0 || pg_strcasecmp (s, "no") != 0 || pg_strcasecmp (s, "false") != 0) readonly = optionIsTrue(s); else - ereport (ERROR, (errcode (ERRCODE_FDW_INVALID_ATTRIBUTE_VALUE), errmsg ("invalid value for option \"%s\"", def->defname))); + ereport (ERROR, (errcode (ERRCODE_FDW_INVALID_ATTRIBUTE_VALUE), errmsg ("invalid value for option \"%s\"", def->defname),errhint ("Valid values in this context are: %s", "on, yes, true, off, no, false"))); continue; } ereport (ERROR, (errcode (ERRCODE_FDW_INVALID_OPTION_NAME), errmsg ("invalid option \"%s\"", def->defname), errhint ("Valid options in this context are: %s", "case, readonly"))); @@ -303,4 +285,33 @@ static void generateForeignTableCreate(StringInfo buf, char* servername, char* l appendStringInfo (buf, ", readonly 'true'"); } appendStringInfo (buf, ")"); +} + +/* getOptions + * Fetch the options for an db2_fdw foreign table. + * Returns a union of the options of the foreign data wrapper, the foreign server, the user mapping and the foreign table, in that order. + * Column options are ignored. + */ +static ForeignServer* getOptions (Oid serverOid, List** options) { + ForeignDataWrapper* wrapper = NULL; + ForeignServer* server = NULL; + UserMapping* mapping = NULL; + + db2Debug4(" > %s::getOptions", __FILE__); + /* get the foreign server, the user mapping and the FDW */ + server = GetForeignServer (serverOid); + mapping = GetUserMapping (GetUserId (), serverOid); + if (server != NULL) + wrapper = GetForeignDataWrapper (server->fdwid); + + /* get all options for these objects */ + *options = NIL; + if (wrapper != NULL) + *options = list_concat (*options, wrapper->options) ; + if (server != NULL) + *options = list_concat (*options, server->options); + if (mapping != NULL) + *options = list_concat (*options, mapping->options); + db2Debug4(" < %s::getOptions : %x", __FILE__, server); + return server; } \ No newline at end of file diff --git a/source/db2_fdw.c b/source/db2_fdw.c index 1000008..1107166 100644 --- a/source/db2_fdw.c +++ b/source/db2_fdw.c @@ -449,6 +449,7 @@ PGDLLEXPORT Datum db2_diag (PG_FUNCTION_ARGS) { } srvId = ((Form_pg_foreign_server)GETSTRUCT(tup))->oid; table_close (rel, AccessShareLock); + /* get the foreign server, the user mapping and the FDW */ server = GetForeignServer (srvId); mapping = GetUserMapping (GetUserId (), srvId); diff --git a/source/db2_fdw_utils.c b/source/db2_fdw_utils.c index c04e0cb..75c7458 100644 --- a/source/db2_fdw_utils.c +++ b/source/db2_fdw_utils.c @@ -1,24 +1,17 @@ #include - #include #include - #include - #include - +#include #include #include - #include #include #include #include #include #include - -#include - #include "db2_fdw.h" #include "DB2FdwState.h" @@ -43,36 +36,39 @@ typedef struct { /** external prototypes */ -extern void db2GetLob (DB2Session* session, DB2ResultColumn* column, char** value, long* value_len, unsigned long trunc); -extern void db2Shutdown (void); -extern short c2dbType (short fcType); -extern void db2Debug1 (const char* message, ...); -extern void db2Debug2 (const char* message, ...); -extern void db2Debug3 (const char* message, ...); -extern void* db2alloc (const char* type, size_t size); -extern void* db2strdup (const char* source); -extern void db2free (void* p); +extern void db2GetLob (DB2Session* session, DB2ResultColumn* column, char** value, long* value_len, unsigned long trunc); +extern void db2Shutdown (void); +extern short c2dbType (short fcType); +extern void db2Debug4 (const char* message, ...); +extern void db2Debug5 (const char* message, ...); +extern void* db2alloc (const char* type, size_t size); +extern void* db2strdup (const char* source); +extern void db2free (void* p); /** local prototypes */ -char* guessNlsLang (char* nls_lang); -void exitHook (int code, Datum arg); -void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls, bool trunc_lob) ; -void reset_transmission_modes (int nestlevel); -int set_transmission_modes (void); -bool is_builtin (Oid objectId); -bool is_shippable (Oid objectId, Oid classId, DB2FdwState* fpinfo); -static void InvalidateShippableCacheCallback(Datum arg, int cacheid, uint32 hashvalue); -static void InitializeShippableCache (void); -static bool lookup_shippable (Oid objectId, Oid classId, DB2FdwState* fpinfo); - -/** guessNlsLang - * If nls_lang is not NULL, return "NLS_LANG=". - * Otherwise, return a good guess for DB2's NLS_LANG. +char* guessNlsLang (char* nls_lang); +void exitHook (int code, Datum arg); +void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls, bool trunc_lob) ; +void reset_transmission_modes (int nestlevel); +int set_transmission_modes (void); +bool is_builtin (Oid objectId); +bool is_shippable (Oid objectId, Oid classId, DB2FdwState* fpinfo); +static void InvalidateShippableCacheCbk(Datum arg, int cacheid, uint32 hashvalue); +static void InitializeShippableCache (void); +static bool lookup_shippable (Oid objectId, Oid classId, DB2FdwState* fpinfo); + +/* guessNlsLang + * If nls_lang is not NULL, return "NLS_LANG=". + * Otherwise, return a good guess for DB2's NLS_LANG. */ char* guessNlsLang (char *nls_lang) { - char *server_encoding, *lc_messages, *language = "AMERICAN_AMERICA", *charset = NULL; + char* server_encoding = NULL; + char* lc_messages = NULL; + char* language = "AMERICAN_AMERICA"; + char* charset = NULL; StringInfoData buf; - db2Debug1("> %s::guessNlsLang(nls_lang: %s)", __FILE__, nls_lang); + + db2Debug4("> %s::guessNlsLang(nls_lang: %s)", __FILE__, nls_lang); initStringInfo (&buf); if (nls_lang == NULL) { server_encoding = db2strdup (GetConfigOption ("server_encoding", false, true)); @@ -175,23 +171,22 @@ char* guessNlsLang (char *nls_lang) { } else { appendStringInfo (&buf, "NLS_LANG=%s", nls_lang); } - db2Debug1("< %s::guessNlsLang - returns: '%s'", __FILE__, buf.data); + db2Debug4("< %s::guessNlsLang : %s", __FILE__, buf.data); return buf.data; } -/** exitHook - * Close all DB2 connections on process exit. +/* exitHook + * Close all DB2 connections on process exit. */ void exitHook (int code, Datum arg) { - db2Debug1("> %s::exitHook",__FILE__); + db2Debug4("> %s::exitHook",__FILE__); db2Shutdown (); - db2Debug1("< %s::exitHook",__FILE__); + db2Debug4("< %s::exitHook",__FILE__); } -/** convertTuple - * Convert a result row from DB2 stored in db2Table - * into arrays of values and null indicators. - * If trunc_lob it true, truncate LOBs to WIDTH_THRESHOLD+1 bytes. +/* convertTuple + * Convert a result row from DB2 stored in db2Table into arrays of values and null indicators. + * If trunc_lob it true, truncate LOBs to WIDTH_THRESHOLD+1 bytes. */ void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls, bool trunc_lob) { char* value = NULL; @@ -201,13 +196,13 @@ void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls DB2ResultColumn* res = NULL; bool isSimpleSelect = false; - db2Debug1("> %s::convertTuple",__FILE__); - db2Debug2(" natts: %d", natts); - db2Debug2(" truncate lob: %s", trunc_lob ? "true": "false"); + db2Debug4("> %s::convertTuple",__FILE__); + db2Debug5(" natts: %d", natts); + db2Debug5(" truncate lob: %s", trunc_lob ? "true": "false"); /* assign result values */ isSimpleSelect = (natts == db2Table->npgcols); - db2Debug2(" isSimpleSelect: %s", isSimpleSelect ? "true": "false"); + db2Debug5(" isSimpleSelect: %s", isSimpleSelect ? "true": "false"); // initialize all columns to NULL for (j = 0; j < natts; j++) { @@ -217,14 +212,14 @@ void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls for (res = fdw_state->resultList; res; res = res->next) { j = ((isSimpleSelect) ? res->pgattnum : res->resnum) - 1; - db2Debug2(" start processing column %d of %d: values index = %d", res->resnum, natts, j); - db2Debug2(" res->pgname : %s" ,res->pgname ); - db2Debug2(" res->pgattnum : %d" ,res->pgattnum); - db2Debug2(" res->pgtype : %d" ,res->pgtype ); - db2Debug2(" res->pgtypmod : %d" ,res->pgtypmod); - db2Debug2(" res->val : %s" ,res->val ); - db2Debug2(" res->val_len : %d" ,res->val_len ); - db2Debug2(" res->val_null : %d" ,res->val_null); + db2Debug5(" start processing column %d of %d: values index = %d", res->resnum, natts, j); + db2Debug5(" res->pgname : %s" ,res->pgname ); + db2Debug5(" res->pgattnum : %d" ,res->pgattnum); + db2Debug5(" res->pgtype : %d" ,res->pgtype ); + db2Debug5(" res->pgtypmod : %d" ,res->pgtypmod); + db2Debug5(" res->val : %s" ,res->val ); + db2Debug5(" res->val_len : %d" ,res->val_len ); + db2Debug5(" res->val_null : %d" ,res->val_null); if (res->val_null >= 0) { short db2Type = 0; @@ -235,14 +230,14 @@ void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls switch(c2dbType(res->colType)) { case DB2_BLOB: case DB2_CLOB: { - db2Debug3(" DB2_BLOB or DB2CLOB"); + db2Debug5(" DB2_BLOB or DB2CLOB"); /* for LOBs, get the actual LOB contents (allocated), truncated if desired */ /* the column index is 1 based, whereas index id 0 based, so always add 1 to index when calling db2GetLob, since it does a column based access*/ db2GetLob (fdw_state->session, res, &value, &value_len, trunc_lob ? (WIDTH_THRESHOLD + 1) : 0); } break; case DB2_LONGVARBINARY: { - db2Debug3(" DB2_LONGBINARY datatypes"); + db2Debug5(" DB2_LONGBINARY datatypes"); /* for LONG and LONG RAW, the first 4 bytes contain the length */ value_len = *((int32*) res->val); /* the rest is the actual data */ @@ -260,7 +255,7 @@ void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls case DB2_DOUBLE: { char* tmp_value = NULL; - db2Debug3(" DB2_FLOAT, DECIMAL, SMALLINT, INTEGER, REAL, DECFLOAT, DOUBLE"); + db2Debug5(" DB2_FLOAT, DECIMAL, SMALLINT, INTEGER, REAL, DECFLOAT, DOUBLE"); value = res->val; value_len = res->val_len; value_len = (value_len == 0) ? strlen(value) : value_len; @@ -271,7 +266,7 @@ void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls } break; default: { - db2Debug3(" shoud be string based values"); + db2Debug5(" shoud be string based values"); /* for other data types, db2Table contains the results */ value = res->val; value_len = res->val_len; @@ -279,8 +274,8 @@ void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls } break; } - db2Debug2(" value : %s" , value); - db2Debug2(" value_len : %ld" , value_len); + db2Debug5(" value : %s" , value); + db2Debug5(" value_len : %ld" , value_len); /* fill the TupleSlot with the data (after conversion if necessary) */ if (res->pgtype == BYTEAOID) { /* binary columns are not converted */ @@ -293,7 +288,7 @@ void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls regproc typinput; HeapTuple tuple; Datum dat; - db2Debug2(" pgtype: %d",res->pgtype); + db2Debug5(" pgtype: %d",res->pgtype); /* find the appropriate conversion function */ tuple = SearchSysCache1 (TYPEOID, ObjectIdGetDatum (res->pgtype)); if (!HeapTupleIsValid (tuple)) { @@ -302,11 +297,11 @@ void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls typinput = ((Form_pg_type) GETSTRUCT (tuple))->typinput; ReleaseSysCache (tuple); dat = CStringGetDatum (value); - db2Debug3(" CStringGetDatum(%s): %d",value, dat); + db2Debug5(" CStringGetDatum(%s): %d",value, dat); /* for string types, check that the data are in the database encoding */ if (res->pgtype == BPCHAROID || res->pgtype == VARCHAROID || res->pgtype == TEXTOID) { - db2Debug3(" pg_verify_mbstr"); + db2Debug5(" pg_verify_mbstr"); (void) pg_verify_mbstr (GetDatabaseEncoding(), value, value_len, res->noencerr == NO_ENC_ERR_TRUE); } /* call the type input function */ @@ -321,12 +316,12 @@ void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls case NUMERICOID: /* these functions require the type modifier */ values[j] = OidFunctionCall3 (typinput, dat, ObjectIdGetDatum (InvalidOid), Int32GetDatum (res->pgtypmod)); - db2Debug3(" OidFunctionCall3 : values[%d]: %d", j, values[j]); + db2Debug5(" OidFunctionCall3 : values[%d]: %d", j, values[j]); break; default: /* the others don't */ values[j] = OidFunctionCall1 (typinput, dat); - db2Debug3(" OidFunctionCall1 : values[%d]: %d", j, values[j]); + db2Debug5(" OidFunctionCall1 : values[%d]: %d", j, values[j]); } } /* release the data buffer for LOBs */ @@ -335,24 +330,22 @@ void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls if (value != NULL) { db2free (value); } else { - db2Debug2(" not freeing value, since it is null"); + db2Debug5(" not freeing value, since it is null"); } } } else { - db2Debug2(" column %d is NULL", res->resnum); + db2Debug5(" column %d is NULL", res->resnum); } } - - db2Debug1("< %s::convertTuple",__FILE__); + db2Debug4("< %s::convertTuple",__FILE__); } -/** Undo the effects of set_transmission_modes(). - */ +/* Undo the effects of set_transmission_modes(). */ void reset_transmission_modes(int nestlevel) { AtEOXact_GUC(true, nestlevel); } -/** Force assorted GUC parameters to settings that ensure that we'll output data values in a form that is unambiguous to the remote server. +/* Force assorted GUC parameters to settings that ensure that we'll output data values in a form that is unambiguous to the remote server. * * This is rather expensive and annoying to do once per row, but there's little choice if we want to be sure values are transmitted accurately; * we can't leave the settings in place between rows for fear of affecting user-visible computations. @@ -446,7 +439,7 @@ bool is_shippable (Oid objectId, Oid classId, DB2FdwState* fpinfo) { * We do not currently bother to check whether objects' extension membership changes once a shippability decision has been * made for them, however. */ -static void InvalidateShippableCacheCallback(Datum arg, int cacheid, uint32 hashvalue) { +static void InvalidateShippableCacheCbk(Datum arg, int cacheid, uint32 hashvalue) { HASH_SEQ_STATUS status; ShippableCacheEntry* entry; @@ -471,7 +464,7 @@ static void InitializeShippableCache(void) { ShippableCacheHash = hash_create("Shippability cache", 256, &ctl, HASH_ELEM | HASH_BLOBS); /* Set up invalidation callback on pg_foreign_server. */ - CacheRegisterSyscacheCallback(FOREIGNSERVEROID, InvalidateShippableCacheCallback, (Datum) 0); + CacheRegisterSyscacheCallback(FOREIGNSERVEROID, InvalidateShippableCacheCbk, (Datum) 0); } /* Returns true if given object (operator/function/type) is shippable according to the server options. From 14c9103149c327c7aa7700aa00b62b87be3926e4 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Tue, 17 Feb 2026 17:40:04 +0100 Subject: [PATCH 074/120] oops forgot to move optionIsTrue - now in db2_fdw_utils.c --- source/db2_fdw_utils.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/source/db2_fdw_utils.c b/source/db2_fdw_utils.c index 75c7458..c688760 100644 --- a/source/db2_fdw_utils.c +++ b/source/db2_fdw_utils.c @@ -46,6 +46,7 @@ extern void* db2strdup (const char* source); extern void db2free (void* p); /** local prototypes */ +bool optionIsTrue (const char *value); char* guessNlsLang (char* nls_lang); void exitHook (int code, Datum arg); void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls, bool trunc_lob) ; @@ -57,6 +58,17 @@ static void InvalidateShippableCacheCbk(Datum arg, int cacheid, uint32 h static void InitializeShippableCache (void); static bool lookup_shippable (Oid objectId, Oid classId, DB2FdwState* fpinfo); +/* optionIsTrue + * Returns true if the string is "true", "on" or "yes". + */ +bool optionIsTrue (const char *value) { + bool result = false; + db2Debug4("> optionIsTrue(value: '%s')",value); + result = (pg_strcasecmp (value, "on") == 0 || pg_strcasecmp (value, "yes") == 0 || pg_strcasecmp (value, "true") == 0); + db2Debug4("< optionIsTrue - returns: '%s'",((result) ? "true" : "false")); + return result; +} + /* guessNlsLang * If nls_lang is not NULL, return "NLS_LANG=". * Otherwise, return a good guess for DB2's NLS_LANG. From 056e6b087686488160189b309745e0fb6a4c2a3a Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Sun, 22 Feb 2026 19:26:33 +0100 Subject: [PATCH 075/120] prepare DirectModify Function for UPDATE and DELETE --- Makefile | 4 + include/DB2FdwDirectModifyState.h | 66 +++++++++ include/DB2FdwState.h | 6 +- include/db2_fdw.h | 2 + source/db2BeginDirectModify.c | 223 ++++++++++++++++++++++++++++++ source/db2EndDirectModify.c | 54 ++++++++ source/db2GetFdwState.c | 87 +++++++++++- source/db2IterateDirectModify.c | 24 ++++ source/db2PlanDirectModify.c | 196 ++++++++++++++++++++++++++ source/db2_deparse.c | 190 +++++++++++++++++++++++++ source/db2_fdw.c | 11 ++ 11 files changed, 856 insertions(+), 7 deletions(-) create mode 100644 include/DB2FdwDirectModifyState.h create mode 100644 source/db2BeginDirectModify.c create mode 100644 source/db2EndDirectModify.c create mode 100644 source/db2IterateDirectModify.c create mode 100644 source/db2PlanDirectModify.c diff --git a/Makefile b/Makefile index 56d67af..413ac3c 100644 --- a/Makefile +++ b/Makefile @@ -66,6 +66,10 @@ OBJS = source/db2_fdw.o\ source/db2Shutdown.o\ source/db2CopyText.o\ source/db2IsStatementOpen.o\ + source/db2PlanDirectModify.o\ + source/db2BeginDirectModify.o\ + source/db2IterateDirectModify.o\ + source/db2EndDirectModify.o\ source/db2_utils.o RELEASE = $(shell grep default_version $(EXTENSION).control | sed -e "s/default_version[[:space:]]*=[[:space:]]*'\([^']*\)'/\1/") DATA = $(wildcard sql/*--*.sql) diff --git a/include/DB2FdwDirectModifyState.h b/include/DB2FdwDirectModifyState.h new file mode 100644 index 0000000..6acecc4 --- /dev/null +++ b/include/DB2FdwDirectModifyState.h @@ -0,0 +1,66 @@ +#ifndef DB2FDWDIRECTMODIFYSTATE_H +#define DB2FDWDIRECTMODIFYSTATE_H + +#include +#include +#include +#include "ParamDesc.h" + +// Execution state of a foreign scan that modifies a foreign table directly. +typedef struct db2FdwDirectModifyState { + // DB2 Section + char* dbserver; // DB2 connect string + char* user; // DB2 username + char* password; // DB2 password + char* jwt_token; // JWT token for authentication (alternative to user/password) + char* nls_lang; // DB2 locale information + DB2Session* session; // encapsulates the active DB2 session + ParamDesc* paramList; // description of parameters needed for the query + DB2Table* db2Table; // description of the remote DB2 table + unsigned long prefetch; // number of rows to prefetch (SQL_ATTR_PREFETCH_NROWS 0-1024) + int fetch_size; // fetch size for this remote table (SQL_ATTR_ROW_ARRAY_SIZE 1 - 32767) + Relation rel; // relcache entry for the foreign table + AttInMetadata* attinmeta; // attribute datatype conversion metadata + // extracted fdw_private data + char* query; // text of UPDATE/DELETE command + bool has_returning; // is there a RETURNING clause? + List* retrieved_attrs; // attr numbers retrieved by RETURNING + bool set_processed; // do we set the command es_processed? + // for remote query execution + int numParams; // number of parameters passed to query + FmgrInfo* param_flinfo; // output conversion functions for them + List* param_exprs; // executable expressions for param values + const char** param_values; // textual values of query parameters + // for storing result tuples + // PGresult* result; // result for query + int num_tuples; // # of result tuples + int next_tuple; // index of next one to return + Relation resultRel; // relcache entry for the target relation + AttrNumber* attnoMap; // array of attnums of input user columns + AttrNumber ctidAttno; // attnum of input ctid column + AttrNumber oidAttno; // attnum of input oid column + bool hasSystemCols; // are there system columns of resultRel? + // working memory context + MemoryContext temp_cxt; // context for per-tuple temporary data +} DB2FdwDirectModifyState; + +/* Similarly, this enum describes what's kept in the fdw_private list for + * a ForeignScan node that modifies a foreign table directly. We store: + * + * 1) UPDATE/DELETE statement text to be sent to the remote server + * 2) Boolean flag showing if the remote query has a RETURNING clause + * 3) Integer list of attribute numbers retrieved by RETURNING, if any + * 4) Boolean flag showing if we set the command es_processed + */ +enum FdwDirectModifyPrivateIndex { + // SQL statement to execute remotely (as a String node) + FdwDirectModifyPrivateUpdateSql, + // has-returning flag (as a Boolean node) + FdwDirectModifyPrivateHasReturning, + // Integer list of attribute numbers retrieved by RETURNING + FdwDirectModifyPrivateRetrievedAttrs, + // set-processed flag (as a Boolean node) + FdwDirectModifyPrivateSetProcessed, +}; + +#endif \ No newline at end of file diff --git a/include/DB2FdwState.h b/include/DB2FdwState.h index d679eb7..7a38e8e 100644 --- a/include/DB2FdwState.h +++ b/include/DB2FdwState.h @@ -22,7 +22,7 @@ * @since 1.0 */ typedef struct db2FdwState { -// DB2 Section + // DB2 Section char* dbserver; // DB2 connect string char* user; // DB2 username char* password; // DB2 password @@ -39,9 +39,9 @@ typedef struct db2FdwState { unsigned long prefetch; // number of rows to prefetch (SQL_ATTR_PREFETCH_NROWS 0-1024) char* order_clause; // for sort-pushdown char* where_clause; // deparsed where clause -// attributes taken over from PgFdwRelationInfo + // attributes taken over from PgFdwRelationInfo bool pushdown_safe; // True: relation can be pushed down. Always true for simple foreign scan. -// All entries in these lists should have RestrictInfo wrappers; that improves efficiency of selectivity and cost estimation. + // All entries in these lists should have RestrictInfo wrappers; that improves efficiency of selectivity and cost estimation. List* retrieved_attr; // Retrieved Attributes List. List* remote_conds; // Restriction clauses, safe to pushdown subsets. List* local_conds; // Restriction clauses, unsafe to pushdown subsets. diff --git a/include/db2_fdw.h b/include/db2_fdw.h index f8c32bc..8c5c640 100644 --- a/include/db2_fdw.h +++ b/include/db2_fdw.h @@ -11,6 +11,8 @@ #define _db2_fdw_h_ #ifdef POSTGRES_H +#include +#include #if PG_VERSION_NUM >= 150000 #define STRVAL(arg) ((String *)(arg))->sval #else diff --git a/source/db2BeginDirectModify.c b/source/db2BeginDirectModify.c new file mode 100644 index 0000000..5557590 --- /dev/null +++ b/source/db2BeginDirectModify.c @@ -0,0 +1,223 @@ +#include +#include +#include +#include +#include +#include + +#include "db2_fdw.h" +#include "DB2FdwDirectModifyState.h" + +extern void* db2alloc (const char* type, size_t size); +extern DB2Session* db2GetSession (const char* connectstring, char* user, char* password, char* jwt_token, const char* nls_lang, int curlevel); +extern DB2FdwDirectModifyState* db2GetFdwDirectModifyState (Oid foreigntableid, double* sample_percent, bool describe); + + void db2BeginDirectModify (ForeignScanState* node, int eflags); +static TupleDesc get_tupdesc_for_join_scan_tuples(ForeignScanState* node); +static void init_returning_filter (DB2FdwDirectModifyState* dmstate, List* fdw_scan_tlist, Index rtindex); +static void prepare_query_params (PlanState* node, List* fdw_exprs, int numParams, FmgrInfo** param_flinfo, List** param_exprs, const char ***param_values); + +/* postgresBeginDirectModify + * Prepare a direct foreign table modification + */ +void db2BeginDirectModify(ForeignScanState* node, int eflags) { + ForeignScan* fsplan = (ForeignScan*) node->ss.ps.plan; + EState* estate = node->ss.ps.state; + DB2FdwDirectModifyState* dmstate = NULL; + Index rtindex; + Relation foreigntable; + + /* Do nothing in EXPLAIN (no ANALYZE) case. node->fdw_state stays NULL. */ + if (!(eflags & EXEC_FLAG_EXPLAIN_ONLY)) { + /* Get info about foreign table. */ + rtindex = node->resultRelInfo->ri_RangeTableIndex; + foreigntable = (fsplan->scan.scanrelid == 0) ? ExecOpenScanRelation(estate, rtindex, eflags) : node->ss.ss_currentRelation; + /* We'll save private state in node->fdw_state. */ + dmstate = db2GetFdwDirectModifyState(foreigntable->rd_id, NULL, false); + dmstate->rel = foreigntable; + node->fdw_state = dmstate; + + /* Update the foreign-join-related fields. */ + if (fsplan->scan.scanrelid == 0) { + /* Save info about foreign table. */ + dmstate->resultRel = dmstate->rel; + + /* Set dmstate->rel to NULL to teach get_returning_data() and make_tuple_from_result_row() + * that columns fetched from the remote server are described by fdw_scan_tlist of the + * foreign-scan plan node, not the tuple descriptor for the target relation. + */ + dmstate->rel = NULL; + } + + /* Initialize state variable */ + dmstate->num_tuples = -1; /* -1 means not set yet */ + + /* Get private info created by planner functions. */ + dmstate->query = strVal(list_nth (fsplan->fdw_private, FdwDirectModifyPrivateUpdateSql)); + dmstate->has_returning = boolVal(list_nth(fsplan->fdw_private, FdwDirectModifyPrivateHasReturning)); + dmstate->retrieved_attrs = (List*) list_nth(fsplan->fdw_private, FdwDirectModifyPrivateRetrievedAttrs); + dmstate->set_processed = boolVal(list_nth(fsplan->fdw_private, FdwDirectModifyPrivateSetProcessed)); + + /* Create context for per-tuple temp workspace. */ + dmstate->temp_cxt = AllocSetContextCreate(estate->es_query_cxt, "db2_fdw temporary data", ALLOCSET_SMALL_SIZES); + + /* Prepare for input conversion of RETURNING results. */ + if (dmstate->has_returning) { + TupleDesc tupdesc; + + if (fsplan->scan.scanrelid == 0) + tupdesc = get_tupdesc_for_join_scan_tuples(node); + else + tupdesc = RelationGetDescr(dmstate->rel); + + dmstate->attinmeta = TupleDescGetAttInMetadata(tupdesc); + + /* When performing an UPDATE/DELETE .. RETURNING on a join directly, initialize a filter to + * extract an updated/deleted tuple from a scan tuple. + */ + if (fsplan->scan.scanrelid == 0) + init_returning_filter(dmstate, fsplan->fdw_scan_tlist, rtindex); + } + + /* Prepare for processing of parameters used in remote query, if any. */ + dmstate->numParams = list_length(fsplan->fdw_exprs); + if (dmstate->numParams > 0) { + prepare_query_params( (PlanState*) node + , fsplan->fdw_exprs + , dmstate->numParams + , &dmstate->param_flinfo + , &dmstate->param_exprs + , &dmstate->param_values + ); + } + } +} + +/* + * Construct a tuple descriptor for the scan tuples handled by a foreign join. + */ +static TupleDesc get_tupdesc_for_join_scan_tuples(ForeignScanState *node) { + ForeignScan *fsplan = (ForeignScan *) node->ss.ps.plan; + EState *estate = node->ss.ps.state; + TupleDesc tupdesc; + + /* + * The core code has already set up a scan tuple slot based on + * fsplan->fdw_scan_tlist, and this slot's tupdesc is mostly good enough, + * but there's one case where it isn't. If we have any whole-row row + * identifier Vars, they may have vartype RECORD, and we need to replace + * that with the associated table's actual composite type. This ensures + * that when we read those ROW() expression values from the remote server, + * we can convert them to a composite type the local server knows. + */ + tupdesc = CreateTupleDescCopy(node->ss.ss_ScanTupleSlot->tts_tupleDescriptor); + for (int i = 0; i < tupdesc->natts; i++) + { + Form_pg_attribute att = TupleDescAttr(tupdesc, i); + Var *var; + RangeTblEntry *rte; + Oid reltype; + + /* Nothing to do if it's not a generic RECORD attribute */ + if (att->atttypid != RECORDOID || att->atttypmod >= 0) + continue; + + /* + * If we can't identify the referenced table, do nothing. This'll + * likely lead to failure later, but perhaps we can muddle through. + */ + var = (Var *) list_nth_node(TargetEntry, fsplan->fdw_scan_tlist, + i)->expr; + if (!IsA(var, Var) || var->varattno != 0) + continue; + rte = list_nth(estate->es_range_table, var->varno - 1); + if (rte->rtekind != RTE_RELATION) + continue; + reltype = get_rel_type_id(rte->relid); + if (!OidIsValid(reltype)) + continue; + att->atttypid = reltype; + /* shouldn't need to change anything else */ + } + return tupdesc; +} + +/* Initialize a filter to extract an updated/deleted tuple from a scan tuple. */ +static void init_returning_filter(DB2FdwDirectModifyState* dmstate, List* fdw_scan_tlist, Index rtindex) { + TupleDesc resultTupType = RelationGetDescr(dmstate->resultRel); + ListCell* lc = NULL; + int i = 0; + + /* Calculate the mapping between the fdw_scan_tlist's entries and the result tuple's attributes. + * + * The "map" is an array of indexes of the result tuple's attributes in fdw_scan_tlist, i.e., one entry for every attribute + * of the result tuple. + * We store zero for any attributes that don't have the corresponding entries in that list, marking that a NULL is needed in + * the result tuple. + * + * Also get the indexes of the entries for ctid and oid if any. + */ + dmstate->attnoMap = (AttrNumber*) db2alloc("init_returning_filter::attnoMap",resultTupType->natts * sizeof(AttrNumber)); + dmstate->ctidAttno = dmstate->oidAttno = 0; + + i = 1; + dmstate->hasSystemCols = false; + foreach(lc, fdw_scan_tlist) { + TargetEntry* tle = (TargetEntry*) lfirst(lc); + Var* var = (Var*) tle->expr; + + Assert(IsA(var, Var)); + + /* If the Var is a column of the target relation to be retrieved from the foreign server, get the index of the entry. */ + if (var->varno == rtindex && list_member_int(dmstate->retrieved_attrs, i)) { + int attrno = var->varattno; + if (attrno < 0) { + /* We don't retrieve system columns other than ctid and oid. */ + if (attrno == SelfItemPointerAttributeNumber) + dmstate->ctidAttno = i; + else + Assert(false); + dmstate->hasSystemCols = true; + } else { + /* We don't retrieve whole-row references to the target relation either. */ + Assert(attrno > 0); + dmstate->attnoMap[attrno - 1] = i; + } + } + i++; + } +} + +/* Prepare for processing of parameters used in remote query. */ +static void prepare_query_params(PlanState* node, List* fdw_exprs, int numParams, FmgrInfo** param_flinfo, List** param_exprs, const char ***param_values) { + int i = 0; + ListCell* lc = NULL; + + Assert(numParams > 0); + + /* Prepare for output conversion of parameters used in remote query. */ + *param_flinfo = palloc0_array(FmgrInfo, numParams); + + i = 0; + foreach(lc, fdw_exprs) { + Node* param_expr = (Node *) lfirst(lc); + Oid typefnoid; + bool isvarlena; + + getTypeOutputInfo(exprType(param_expr), &typefnoid, &isvarlena); + fmgr_info(typefnoid, &(*param_flinfo)[i]); + i++; + } + + /* Prepare remote-parameter expressions for evaluation. + * (Note: in practice, we expect that all these expressions will be just Params, so we could possibly do something + * more efficient than using the full expression-eval machinery for this. + * But probably there would be little benefit, and it'd require postgres_fdw to know more than is desirable + * about Param evaluation.) + */ + *param_exprs = ExecInitExprList(fdw_exprs, node); + + /* Allocate buffer for text form of query parameters. */ + *param_values = (const char **) db2alloc("prepare_query_params::param_values",numParams * sizeof(char *)); +} + diff --git a/source/db2EndDirectModify.c b/source/db2EndDirectModify.c new file mode 100644 index 0000000..b67f667 --- /dev/null +++ b/source/db2EndDirectModify.c @@ -0,0 +1,54 @@ +#include +#include "db2_fdw.h" +#include "DB2FdwDirectModifyState.h" + +/** external variables */ +extern regproc* output_funcs; + +/** external prototypes */ +extern void db2CloseStatement (DB2Session* session); +extern void db2free (void* p); +extern void db2Debug1 (const char* message, ...); +extern void db2Debug2 (const char* message, ...); + +/** local prototypes */ +void db2EndDirectModify(ForeignScanState* node); + +/* postgresEndDirectModify + * Finish a direct foreign table modification + */ +void db2EndDirectModify(ForeignScanState* node) { + DB2FdwDirectModifyState* fdw_state = (DB2FdwDirectModifyState*) node->fdw_state; + + db2Debug1("> %s::db2EndDirectModify",__FILE__); + /* MemoryContext will be deleted automatically. */ + if (fdw_state == NULL) { + db2Debug2(" no fdw_state, nothing to do"); + return; + } + +// /* If you’re batching for COPY, flush any remaining rows here */ +// if (fdw_state->session && fdw_state->db2Table) { +// /* e.g. db2FlushBatch(fdw_state->session, fdw_state->db2Table); */ +// } + + /* Finish statement / cursor, if you keep a handle there */ + if (fdw_state->session) { + db2CloseStatement (fdw_state->session); + db2free(fdw_state->session); + fdw_state->session = NULL; + } + + if (fdw_state->temp_cxt) { + MemoryContextDelete (fdw_state->temp_cxt); + fdw_state->temp_cxt = NULL; + } + + if (output_funcs){ + db2free(output_funcs); + } + + db2free(fdw_state); + node->fdw_state = NULL; + db2Debug1("< %s::db2EndDirectModify",__FILE__); +} \ No newline at end of file diff --git a/source/db2GetFdwState.c b/source/db2GetFdwState.c index 9c77af6..717c318 100644 --- a/source/db2GetFdwState.c +++ b/source/db2GetFdwState.c @@ -7,6 +7,7 @@ #include #include "db2_fdw.h" #include "DB2FdwState.h" +#include "DB2FdwDirectModifyState.h" /** external prototypes */ extern char* guessNlsLang (char* nls_lang); @@ -25,10 +26,11 @@ extern void db2free (void* p); extern char* db2strdup (const char* source); /** local prototypes */ - DB2FdwState* db2GetFdwState(Oid foreigntableid, double* sample_percent, bool describe); -static DB2Table* describeForeignTable (Oid foreigntableid, char* schema, char* table, char* pgname, long max_long, char* noencerr, char* batchsz); -static void getColumnData (DB2Table* db2Table, Oid foreigntableid); -static void getOptions (Oid foreigntableid, List** options); + DB2FdwState* db2GetFdwState (Oid foreigntableid, double* sample_percent, bool describe); + DB2FdwDirectModifyState* db2GetFdwDirectModifyState(Oid foreigntableid, double* sample_percent, bool describe); +static DB2Table* describeForeignTable (Oid foreigntableid, char* schema, char* table, char* pgname, long max_long, char* noencerr, char* batchsz); +static void getColumnData (DB2Table* db2Table, Oid foreigntableid); +static void getOptions (Oid foreigntableid, List** options); /** db2GetFdwState @@ -114,6 +116,83 @@ DB2FdwState* db2GetFdwState (Oid foreigntableid, double* sample_percent, bool de return fdwState; } +/** db2GetFdwState + * Construct an DB2FdwState from the options of the foreign table. + * Establish an DB2 connection and get a description of the + * remote table. + * "sample_percent" is set from the foreign table options. + * "sample_percent" can be NULL, in that case it is not set. + */ +DB2FdwDirectModifyState* db2GetFdwDirectModifyState (Oid foreigntableid, double* sample_percent, bool describe) { + DB2FdwDirectModifyState* fdwState = db2alloc("fdw_state", sizeof (DB2FdwDirectModifyState)); + char* pgtablename = get_rel_name (foreigntableid); + List* options = NIL; + ListCell* cell = NULL; + char* schema = NULL; + char* table = NULL; + char* maxlong = NULL; + char* sample = NULL; + char* prefetch = NULL; + char* fetchsz = NULL; + char* noencerr = NULL; + char* batchsz = NULL; + long max_long = 0; + + db2Debug1("> %s::db2GetFdwDirectModifyState", __FILE__); + /* Get all relevant options from the foreign table, the user mapping, the foreign server and the foreign data wrapper. */ + getOptions (foreigntableid, &options); + + foreach (cell, options) { + DefElem *def = (DefElem *) lfirst (cell); + fdwState->nls_lang = (strcmp (def->defname, OPT_NLS_LANG) == 0) ? STRVAL(def->arg) : fdwState->nls_lang; + fdwState->dbserver = (strcmp (def->defname, OPT_DBSERVER) == 0) ? STRVAL(def->arg) : fdwState->dbserver; + fdwState->user = (strcmp (def->defname, OPT_USER) == 0) ? STRVAL(def->arg) : fdwState->user; + fdwState->password = (strcmp (def->defname, OPT_PASSWORD) == 0) ? STRVAL(def->arg) : fdwState->password; + fdwState->jwt_token = (strcmp (def->defname, OPT_JWT_TOKEN) == 0) ? STRVAL(def->arg) : fdwState->jwt_token; + schema = (strcmp (def->defname, OPT_SCHEMA) == 0) ? STRVAL(def->arg) : schema; + table = (strcmp (def->defname, OPT_TABLE) == 0) ? STRVAL(def->arg) : table; + maxlong = (strcmp (def->defname, OPT_MAX_LONG) == 0) ? STRVAL(def->arg) : maxlong; + sample = (strcmp (def->defname, OPT_SAMPLE) == 0) ? STRVAL(def->arg) : sample; + prefetch = (strcmp (def->defname, OPT_PREFETCH) == 0) ? STRVAL(def->arg) : prefetch; + fetchsz = (strcmp (def->defname, OPT_FETCHSZ) == 0) ? STRVAL(def->arg) : fetchsz; + noencerr = (strcmp (def->defname, OPT_NO_ENCODING_ERROR) == 0) ? STRVAL(def->arg) : noencerr; + batchsz = (strcmp (def->defname, OPT_BATCH_SIZE) == 0) ? STRVAL(def->arg) : batchsz; + } + + /* convert "max_long" option to number or use default */ + max_long = (maxlong == NULL) ? DEFAULT_MAX_LONG : strtol (maxlong, NULL, 0); + + /* convert "sample_percent" to double */ + if (sample_percent != NULL) { + if (sample == NULL) + *sample_percent = 100.0; + else + *sample_percent = strtod (sample, NULL); + } + /* convert "prefetch" to number (or use default) */ + fdwState->prefetch = (prefetch == NULL) ? DEFAULT_PREFETCH : (unsigned long) strtoul (prefetch, NULL, 0); + + /* convert "fetchsize" to number (or use default) */ + fdwState->fetch_size = (fetchsz == NULL) ? DEFAULT_FETCHSZ : (int) strtol (fetchsz, NULL, 0); + + /* check if options are ok */ + if (table == NULL) { + ereport (ERROR, (errcode (ERRCODE_FDW_OPTION_NAME_NOT_FOUND), errmsg ("required option \"%s\" in foreign table \"%s\" missing", OPT_TABLE, pgtablename))); + } + + /* guess a good NLS_LANG environment setting */ + fdwState->nls_lang = guessNlsLang (fdwState->nls_lang); + + if (describe) { + fdwState->db2Table = describeForeignTable(foreigntableid, schema, table, pgtablename, max_long, noencerr, batchsz); + } + fdwState->session = db2GetSession (fdwState->dbserver, fdwState->user, fdwState->password, fdwState->jwt_token, fdwState->nls_lang, GetCurrentTransactionNestLevel () ); + + db2Debug1("< %s::db2GetFdwDirectModifyState", __FILE__); + return fdwState; +} + + static DB2Table* describeForeignTable (Oid foreigntableid, char* schema, char* table, char* pgname, long max_long, char* noencerr, char* batchsz) { DB2Table* db2Table = NULL; char* qtable = NULL; diff --git a/source/db2IterateDirectModify.c b/source/db2IterateDirectModify.c new file mode 100644 index 0000000..440fb81 --- /dev/null +++ b/source/db2IterateDirectModify.c @@ -0,0 +1,24 @@ +#include +#include "db2_fdw.h" +#include "DB2FdwDirectModifyState.h" + +TupleTableSlot* db2IterateDirectModify(ForeignScanState *node); + +/* postgresIterateDirectModify + * Execute a direct foreign table modification + */ +TupleTableSlot* db2IterateDirectModify(ForeignScanState *node) { + DB2FdwDirectModifyState* dmstate = (DB2FdwDirectModifyState*) node->fdw_state; + EState* estate = node->ss.ps.state; + ResultRelInfo* rtinfo = node->resultRelInfo; + TupleTableSlot* slot = NULL; + + // nachfolgende Werte in ParamDesc Liste zusammenfassen + int numParams = dmstate->numParams; + const char** values = dmstate->param_values; + FmgrInfo* param_flinfo = dmstate->param_flinfo; + List* param_exps = dmstate->param_exprs; + + // call db2ExecForeignDirectUpdate() similar to db2ExecForeignInsert() + return slot; +} diff --git a/source/db2PlanDirectModify.c b/source/db2PlanDirectModify.c new file mode 100644 index 0000000..33f3da2 --- /dev/null +++ b/source/db2PlanDirectModify.c @@ -0,0 +1,196 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "db2_fdw.h" +#include "DB2FdwState.h" + +/** external prototypes */ +extern void db2Debug1 (const char* message, ...); +extern void db2Debug2 (const char* message, ...); +extern bool is_foreign_expr (PlannerInfo *root, RelOptInfo *baserel, Expr *expr); +extern void deparseDirectUpdateSql (StringInfo buf, PlannerInfo *root, Index rtindex, Relation rel, RelOptInfo *foreignrel, List *targetlist, List *targetAttrs, List *remote_conds, List **params_list, List *returningList, List **retrieved_attrs); +extern void deparseDirectDeleteSql (StringInfo buf, PlannerInfo *root, Index rtindex, Relation rel, RelOptInfo *foreignrel, List *remote_conds, List **params_list, List *returningList, List **retrieved_attrs); + +/** local prototypes */ + bool db2PlanDirectModify (PlannerInfo* root, ModifyTable* plan, Index rtindex, int subplan_index); +static ForeignScan* find_modifytable_subplan (PlannerInfo* root, ModifyTable* plan, Index rtindex, int subplan_index); + +/* postgresPlanDirectModify + * Decide whether it is safe to modify a foreign table directly, and if so, rewrite subplan accordingly. + */ +bool db2PlanDirectModify(PlannerInfo* root, ModifyTable* plan, Index rtindex, int subplan_index) { + bool fResult = true; + + db2Debug1("> %s::db2PlanDirectModify", __FILE__); + /* Decide whether it is safe to modify a foreign table directly. */ + /* The table modification must be an UPDATE or DELETE. */ + if ((plan->operation == CMD_UPDATE || plan->operation == CMD_DELETE) && plan->returningLists == NIL) { + /* Try to locate the ForeignScan subplan that's scanning rtindex. */ + ForeignScan* fscan = find_modifytable_subplan(root, plan, rtindex, subplan_index); + if (fscan) { + /* It's unsafe to modify a foreign table directly if there are any quals that should be evaluated locally. */ + if (fscan->scan.plan.qual == NIL) { + RelOptInfo* foreignrel = NULL; + RangeTblEntry* rte = NULL; + DB2FdwState* fpinfo = NULL; + List* processed_tlist = NIL; + List* targetAttrs = NIL; + + /* Safe to fetch data about the target foreign rel */ + if (fscan->scan.scanrelid == 0) { + foreignrel = find_join_rel(root, fscan->fs_relids); + /* We should have a rel for this foreign join. */ + Assert(foreignrel); + } else { + foreignrel = root->simple_rel_array[rtindex]; + } + rte = root->simple_rte_array[rtindex]; + fpinfo = (DB2FdwState*) foreignrel->fdw_private; + + /* It's unsafe to update a foreign table directly, + * if any expressions to assign to the target columns are unsafe to evaluate remotely. + */ + if (plan->operation == CMD_UPDATE) { + ListCell* lc = NULL; + ListCell* lc2 = NULL; + + /* The expressions of concern are the first N columns of the processed targetlist, + * where N is the length of the rel's update_colnos. + */ + get_translated_update_targetlist(root, rtindex, &processed_tlist, &targetAttrs); + forboth(lc, processed_tlist, lc2, targetAttrs) { + TargetEntry* tle = lfirst_node(TargetEntry, lc); + AttrNumber attno = lfirst_int(lc2); + + /* update's new-value expressions shouldn't be resjunk */ + Assert(!tle->resjunk); + + if (attno <= InvalidAttrNumber) /* shouldn't happen */ + elog(ERROR, "system-column update is not supported"); + + if (!is_foreign_expr(root, foreignrel, (Expr *) tle->expr)) { + fResult = false; + break; + } + } + } + + if (fResult) { + StringInfoData sql; + Relation rel; + List* remote_exprs = NIL; + List* params_list = NIL; + List* returningList = NIL; + List* retrieved_attrs = NIL; + + /* Ok, rewrite subplan so as to modify the foreign table directly. */ + initStringInfo(&sql); + + /* Core code already has some lock on each rel being planned, so we can use NoLock here. + */ + rel = table_open(rte->relid, NoLock); + + /* Recall the qual clauses that must be evaluated remotely. + * (These are bare clauses not RestrictInfos, but deparse.c's appendConditions() doesn't care.) + */ + remote_exprs = fpinfo->final_remote_exprs; + + /* DB2 does not support RETURNIN in UPDATE and DELETE queries */ + + /* Construct the SQL command string. */ + switch (plan->operation) { + case CMD_UPDATE: + deparseDirectUpdateSql(&sql, root, rtindex, rel, + foreignrel, + processed_tlist, + targetAttrs, + remote_exprs, ¶ms_list, + returningList, &retrieved_attrs); + break; + case CMD_DELETE: + deparseDirectDeleteSql(&sql, root, rtindex, rel, + foreignrel, + remote_exprs, ¶ms_list, + returningList, &retrieved_attrs); + break; + default: + elog(ERROR, "unexpected operation: %d", (int) plan->operation); + break; + } + + /* Update the operation and target relation info. */ + fscan->operation = plan->operation; + fscan->resultRelation = rtindex; + + /* Update the fdw_exprs list that will be available to the executor. */ + fscan->fdw_exprs = params_list; + + /* Update the fdw_private list that will be available to the executor. + * Items in the list must match enum FdwDirectModifyPrivateIndex, above. + */ + fscan->fdw_private = list_make4(makeString(sql.data), makeBoolean((retrieved_attrs != NIL)), retrieved_attrs, makeBoolean(plan->canSetTag)); + + /* Update the foreign-join-related fields. */ + if (fscan->scan.scanrelid == 0) { + /* No need for the outer subplan. */ + fscan->scan.plan.lefttree = NULL; + } + + /* Finally, unset the async-capable flag if it is set, as we currently don't support asynchronous execution of direct modifications. */ + if (fscan->scan.plan.async_capable) + fscan->scan.plan.async_capable = false; + + table_close(rel, NoLock); + } + } + } + } + db2Debug1("< %s::db2PlanDirectModify : %s", __FILE__, (fResult) ? "true" : "false"); + return fResult; +} + +/* find_modifytable_subplan + * Helper routine for postgresPlanDirectModify to find the ModifyTable subplan node that scans the specified RTI. + * + * Returns NULL if the subplan couldn't be identified. + * That's not a fatal error condition, we just abandon trying to do the update directly. + */ +static ForeignScan* find_modifytable_subplan(PlannerInfo* root, ModifyTable* plan, Index rtindex, int subplan_index) { + ForeignScan* fscan = NULL; + Plan* subplan = outerPlan(plan); + + /* The cases we support are (1) the desired ForeignScan is the immediate child of ModifyTable, or (2) it is the subplan_index'th child of an + * Append node that is the immediate child of ModifyTable. + * There is no point in looking further down, as that would mean that local joins are involved, so we can't do the update directly. + * There could be a Result atop the Append too, acting to compute the UPDATE targetlist values. + * We ignore that here; the tlist will be checked by our caller. + * In principle we could examine all the children of the Append, but it's currently unlikely that the core planner would generate such a plan + * with the children out-of-order. + * Moreover, such a search risks costing O(N^2) time when there are a lot of children. + */ + if (IsA(subplan, Append)) { + Append* appendplan = (Append*) subplan; + + if (subplan_index < list_length(appendplan->appendplans)) + subplan = (Plan *) list_nth(appendplan->appendplans, subplan_index); + } + else if (IsA(subplan, Result) && outerPlan(subplan) != NULL && IsA(outerPlan(subplan), Append)) { + Append* appendplan = (Append*) outerPlan(subplan); + + if (subplan_index < list_length(appendplan->appendplans)) + subplan = (Plan *) list_nth(appendplan->appendplans, subplan_index); + } + + /* Now, have we got a ForeignScan on the desired rel? */ + if (IsA(subplan, ForeignScan) && (bms_is_member(rtindex, ((ForeignScan*) subplan)->fs_base_relids))) { + fscan = (ForeignScan*) subplan; + } + + return fscan; +} + diff --git a/source/db2_deparse.c b/source/db2_deparse.c index e03d5bf..6b4dc49 100644 --- a/source/db2_deparse.c +++ b/source/db2_deparse.c @@ -176,6 +176,13 @@ static bool is_subquery_var (Var* node, RelOptInfo* foreignrel static void printRemoteParam (int paramindex, Oid paramtype, int32 paramtypmod, deparse_expr_cxt* context); static void printRemotePlaceholder (Oid paramtype, int32 paramtypmod, deparse_expr_cxt* context); + void deparseDirectUpdateSql (StringInfo buf, PlannerInfo *root, Index rtindex, Relation rel, RelOptInfo *foreignrel, List *targetlist, List *targetAttrs, List *remote_conds, List **params_list, List *returningList, List **retrieved_attrs); +static void deparseReturningList (StringInfo buf, RangeTblEntry *rte, Index rtindex, Relation rel, bool trig_after_row, List *withCheckOptionList, List *returningList, List **retrieved_attrs); + void deparseDeleteSql (StringInfo buf, RangeTblEntry *rte, Index rtindex, Relation rel, List *returningList, List **retrieved_attrs); + void deparseDirectDeleteSql (StringInfo buf, PlannerInfo *root, Index rtindex, Relation rel, RelOptInfo *foreignrel, List *remote_conds, List **params_list, List *returningList, List **retrieved_attrs); + + + /* Examine each qual clause in input_conds, and classify them into two groups, which are returned as two lists: * - remote_conds contains expressions that can be evaluated remotely * - local_conds contains expressions that can't be evaluated remotely @@ -3573,3 +3580,186 @@ EquivalenceMember* find_em_for_rel_target(PlannerInfo* root, EquivalenceClass* e } return NULL; } + +/* deparse remote UPDATE statement + * + * 'buf' is the output buffer to append the statement to 'rtindex' is the RT index of the associated target relation + * 'rel' is the relation descriptor for the target relation + * 'foreignrel' is the RelOptInfo for the target relation or the join relation containing all base relations in the query + * 'targetlist' is the tlist of the underlying foreign-scan plan node (note that this only contains new-value expressions and junk attrs) + * 'targetAttrs' is the target columns of the UPDATE + * 'remote_conds' is the qual clauses that must be evaluated remotely + * '*params_list' is an output list of exprs that will become remote Params + * 'returningList' is the RETURNING targetlist + * '*retrieved_attrs' is an output list of integers of columns being retrieved by RETURNING (if any) + */ +void deparseDirectUpdateSql(StringInfo buf, PlannerInfo *root, Index rtindex, Relation rel, RelOptInfo *foreignrel, List *targetlist, List *targetAttrs, List *remote_conds, List **params_list, List *returningList, List **retrieved_attrs) { + deparse_expr_cxt context; + int nestlevel; + bool first; + RangeTblEntry *rte = planner_rt_fetch(rtindex, root); + ListCell *lc, + *lc2; + List *additional_conds = NIL; + + /* Set up context struct for recursion */ + context.root = root; + context.foreignrel = foreignrel; + context.scanrel = foreignrel; + context.buf = buf; + context.params_list = params_list; + + appendStringInfoString(buf, "UPDATE "); + deparseRelation(buf, rel); + if (foreignrel->reloptkind == RELOPT_JOINREL) + appendStringInfo(buf, " %s%d", REL_ALIAS_PREFIX, rtindex); + appendStringInfoString(buf, " SET "); + + /* Make sure any constants in the exprs are printed portably */ + nestlevel = set_transmission_modes(); + + first = true; + forboth(lc, targetlist, lc2, targetAttrs) + { + TargetEntry *tle = lfirst_node(TargetEntry, lc); + int attnum = lfirst_int(lc2); + + /* update's new-value expressions shouldn't be resjunk */ + Assert(!tle->resjunk); + + if (!first) + appendStringInfoString(buf, ", "); + first = false; + + deparseColumnRef(buf, rtindex, attnum, rte, false); + appendStringInfoString(buf, " = "); + deparseExprInt((Expr*) tle->expr, &context); + } + + reset_transmission_modes(nestlevel); + + if (foreignrel->reloptkind == RELOPT_JOINREL) + { + List *ignore_conds = NIL; + + + appendStringInfoString(buf, " FROM "); + deparseFromExprForRel(buf, root, foreignrel, true, rtindex, + &ignore_conds, &additional_conds, params_list); + remote_conds = list_concat(remote_conds, ignore_conds); + } + + appendWhereClause(remote_conds, additional_conds, &context); + + if (additional_conds != NIL) + list_free_deep(additional_conds); + + if (foreignrel->reloptkind == RELOPT_JOINREL) + deparseExplicitTargetList(returningList, true, retrieved_attrs, + &context); + else + deparseReturningList(buf, rte, rtindex, rel, false, + NIL, returningList, retrieved_attrs); +} + +/* + * Add a RETURNING clause, if needed, to an INSERT/UPDATE/DELETE. + */ +static void deparseReturningList(StringInfo buf, RangeTblEntry *rte, Index rtindex, Relation rel, bool trig_after_row, List *withCheckOptionList, List *returningList, List **retrieved_attrs) { + Bitmapset *attrs_used = NULL; + + if (trig_after_row) { + /* whole-row reference acquires all non-system columns */ + attrs_used = + bms_make_singleton(0 - FirstLowInvalidHeapAttributeNumber); + } + + if (withCheckOptionList != NIL) { + /* We need the attrs, non-system and system, mentioned in the local query's WITH CHECK OPTION list. + * + * Note: we do this to ensure that WCO constraints will be evaluated on the data actually inserted/updated on the remote side, which + * might differ from the data supplied by the core code, for example as a result of remote triggers. + */ + pull_varattnos((Node *) withCheckOptionList, rtindex, + &attrs_used); + } + + if (returningList != NIL) { + /* + * We need the attrs, non-system and system, mentioned in the local + * query's RETURNING list. + */ + pull_varattnos((Node *) returningList, rtindex, + &attrs_used); + } + + if (attrs_used != NULL) + deparseTargetList(buf, rte, rtindex, rel, true, attrs_used, false, + retrieved_attrs); + else + *retrieved_attrs = NIL; +} + +/* deparse remote DELETE statement + * The statement text is appended to buf, and we also create an integer List of the columns being retrieved by RETURNING (if any), which is returned to *retrieved_attrs. + */ +void deparseDeleteSql(StringInfo buf, RangeTblEntry *rte, Index rtindex, Relation rel, List *returningList, List **retrieved_attrs) +{ + appendStringInfoString(buf, "DELETE FROM "); + deparseRelation(buf, rel); + appendStringInfoString(buf, " WHERE ctid = $1"); + + deparseReturningList(buf, rte, rtindex, rel, + rel->trigdesc && rel->trigdesc->trig_delete_after_row, + NIL, returningList, retrieved_attrs); +} + +/* deparse remote DELETE statement + * + * 'buf' is the output buffer to append the statement to 'rtindex' is the RT index of the associated target relation + * 'rel' is the relation descriptor for the target relation + * 'foreignrel' is the RelOptInfo for the target relation or the join relation containing all base relations in the query + * 'remote_conds' is the qual clauses that must be evaluated remotely + * '*params_list' is an output list of exprs that will become remote Params + * 'returningList' is the RETURNING targetlist + * '*retrieved_attrs' is an output list of integers of columns being retrieved by RETURNING (if any) + */ +void deparseDirectDeleteSql(StringInfo buf, PlannerInfo *root, Index rtindex, Relation rel, RelOptInfo *foreignrel, List *remote_conds, List **params_list, List *returningList, List **retrieved_attrs) { + deparse_expr_cxt context; + List *additional_conds = NIL; + + /* Set up context struct for recursion */ + context.root = root; + context.foreignrel = foreignrel; + context.scanrel = foreignrel; + context.buf = buf; + context.params_list = params_list; + + appendStringInfoString(buf, "DELETE FROM "); + deparseRelation(buf, rel); + if (foreignrel->reloptkind == RELOPT_JOINREL) + appendStringInfo(buf, " %s%d", REL_ALIAS_PREFIX, rtindex); + + if (foreignrel->reloptkind == RELOPT_JOINREL) + { + List *ignore_conds = NIL; + + appendStringInfoString(buf, " USING "); + deparseFromExprForRel(buf, root, foreignrel, true, rtindex, + &ignore_conds, &additional_conds, params_list); + remote_conds = list_concat(remote_conds, ignore_conds); + } + + appendWhereClause(remote_conds, additional_conds, &context); + + if (additional_conds != NIL) + list_free_deep(additional_conds); + + if (foreignrel->reloptkind == RELOPT_JOINREL) + deparseExplicitTargetList(returningList, true, retrieved_attrs, + &context); + else + deparseReturningList(buf, planner_rt_fetch(rtindex, root), + rtindex, rel, false, + NIL, returningList, retrieved_attrs); +} diff --git a/source/db2_fdw.c b/source/db2_fdw.c index 1107166..9c0e69e 100644 --- a/source/db2_fdw.c +++ b/source/db2_fdw.c @@ -125,6 +125,11 @@ extern void db2EndForeignInsert (EState* estate, ResultRelIn extern void db2ExplainForeignModify (ModifyTableState* mtstate, ResultRelInfo* rinfo, List* fdw_private, int subplan_index, ExplainState* es); extern int db2IsForeignRelUpdatable (Relation rel); extern List* db2ImportForeignSchema (ImportForeignSchemaStmt* stmt, Oid serverOid); +extern bool db2PlanDirectModify (PlannerInfo* root, ModifyTable* plan, Index resultRelation, int subplan_index); +extern void db2BeginDirectModify (ForeignScanState* node, int eflags); +extern TupleTableSlot* db2IterateDirectModify (ForeignScanState* node); +extern void db2EndDirectModify (ForeignScanState* node); + #if PG_VERSION_NUM >= 140000 extern void db2ExecForeignTruncate (List *rels, DropBehavior behavior, bool restart_seqs); extern TupleTableSlot** db2ExecForeignBatchInsert (EState *estate, ResultRelInfo *rinfo, TupleTableSlot **slots, TupleTableSlot **planSlots, int *numSlots); @@ -164,6 +169,12 @@ PGDLLEXPORT Datum db2_fdw_handler (PG_FUNCTION_ARGS) { fdwroutine->ImportForeignSchema = db2ImportForeignSchema; fdwroutine->BeginForeignInsert = db2BeginForeignInsert; fdwroutine->EndForeignInsert = db2EndForeignInsert; + +// fdwroutine->PlanDirectModify = db2PlanDirectModify; +// fdwroutine->BeginDirectModify = db2BeginDirectModify; +// fdwroutine->IterateDirectModify = db2IterateDirectModify; +// fdwroutine->EndDirectModify = db2EndDirectModify; + #if PG_VERSION_NUM >= 140000 fdwroutine->ExecForeignTruncate = db2ExecForeignTruncate; fdwroutine->ExecForeignBatchInsert = db2ExecForeignBatchInsert; From 39e072ea3c41b6f5962ba93c0abf3eb2e00fbb70 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Sun, 22 Feb 2026 19:27:10 +0100 Subject: [PATCH 076/120] ensure the value of serialized and deserialized FDWState is printed at debug level 3 --- source/db2_de_serialize.c | 181 +++++++++++++++++++------------------- 1 file changed, 89 insertions(+), 92 deletions(-) diff --git a/source/db2_de_serialize.c b/source/db2_de_serialize.c index 63e9d81..10c068a 100644 --- a/source/db2_de_serialize.c +++ b/source/db2_de_serialize.c @@ -7,9 +7,8 @@ /** external prototypes */ extern void db2Debug1 (const char* message, ...); -extern void db2Debug2 (const char* message, ...); +extern void db2Debug3 (const char* message, ...); extern void db2Debug4 (const char* message, ...); -extern void db2Debug5 (const char* message, ...); extern void* db2alloc (const char* type, size_t size); extern char* c2name (short fcType); @@ -76,39 +75,39 @@ DB2FdwState* deserializePlanData (List* list) { for (i = 0; i < state->db2Table->ncols; ++i) { state->db2Table->cols[i] = (DB2Column *) db2alloc ("state->db2Table->cols[i]", sizeof (DB2Column)); state->db2Table->cols[i]->colName = deserializeString(list_nth(list, idx++)); - db2Debug5(" deserialize col[%d].colName: %s" ,i, state->db2Table->cols[i]->colName); + db2Debug3(" deserialize col[%d].colName: %s" ,i, state->db2Table->cols[i]->colName); state->db2Table->cols[i]->colType = (short) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug5(" deserialize col[%d].colType: %d" ,i, state->db2Table->cols[i]->colType); + db2Debug3(" deserialize col[%d].colType: %d" ,i, state->db2Table->cols[i]->colType); state->db2Table->cols[i]->colSize = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug5(" deserialize col[%d].colSize: %d" ,i, state->db2Table->cols[i]->colSize); + db2Debug3(" deserialize col[%d].colSize: %d" ,i, state->db2Table->cols[i]->colSize); state->db2Table->cols[i]->colScale = (short) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug5(" deserialize col[%d].colScale: %d" ,i, state->db2Table->cols[i]->colScale); + db2Debug3(" deserialize col[%d].colScale: %d" ,i, state->db2Table->cols[i]->colScale); state->db2Table->cols[i]->colNulls = (short) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug5(" deserialize col[%d].colNulls: %d" ,i, state->db2Table->cols[i]->colNulls); + db2Debug3(" deserialize col[%d].colNulls: %d" ,i, state->db2Table->cols[i]->colNulls); state->db2Table->cols[i]->colChars = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug5(" deserialize col[%d].colChars: %d" ,i, state->db2Table->cols[i]->colChars); + db2Debug3(" deserialize col[%d].colChars: %d" ,i, state->db2Table->cols[i]->colChars); state->db2Table->cols[i]->colBytes = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug5(" deserialize col[%d].colBytes: %d" ,i, state->db2Table->cols[i]->colBytes); + db2Debug3(" deserialize col[%d].colBytes: %d" ,i, state->db2Table->cols[i]->colBytes); state->db2Table->cols[i]->colPrimKeyPart = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug5(" deserialize col[%d].colPrimKeyPart: %d" ,i, state->db2Table->cols[i]->colPrimKeyPart); + db2Debug3(" deserialize col[%d].colPrimKeyPart: %d" ,i, state->db2Table->cols[i]->colPrimKeyPart); state->db2Table->cols[i]->colCodepage = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug5(" deserialize col[%d].colCodepage: %d" ,i, state->db2Table->cols[i]->colCodepage); + db2Debug3(" deserialize col[%d].colCodepage: %d" ,i, state->db2Table->cols[i]->colCodepage); state->db2Table->cols[i]->pgname = deserializeString(list_nth(list, idx++)); - db2Debug5(" deserialize col[%d].pgname: %s" ,i, state->db2Table->cols[i]->pgname); + db2Debug3(" deserialize col[%d].pgname: %s" ,i, state->db2Table->cols[i]->pgname); state->db2Table->cols[i]->pgattnum = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug5(" deserialize col[%d].pgattnum: %d" ,i, state->db2Table->cols[i]->pgattnum); + db2Debug3(" deserialize col[%d].pgattnum: %d" ,i, state->db2Table->cols[i]->pgattnum); state->db2Table->cols[i]->pgtype = DatumGetObjectId(((Const*)list_nth(list, idx++))->constvalue); - db2Debug5(" deserialize col[%d].pgtype: %d" ,i, state->db2Table->cols[i]->pgtype); + db2Debug3(" deserialize col[%d].pgtype: %d" ,i, state->db2Table->cols[i]->pgtype); state->db2Table->cols[i]->pgtypmod = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug5(" deserialize col[%d].pgtypmod: %d" ,i, state->db2Table->cols[i]->pgtypmod); + db2Debug3(" deserialize col[%d].pgtypmod: %d" ,i, state->db2Table->cols[i]->pgtypmod); state->db2Table->cols[i]->used = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug5(" deserialize col[%d].used: %d" ,i, state->db2Table->cols[i]->used); + db2Debug3(" deserialize col[%d].used: %d" ,i, state->db2Table->cols[i]->used); state->db2Table->cols[i]->pkey = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug5(" deserialize col[%d].pkey: %d" ,i, state->db2Table->cols[i]->pkey); + db2Debug3(" deserialize col[%d].pkey: %d" ,i, state->db2Table->cols[i]->pkey); state->db2Table->cols[i]->val_size = deserializeLong(list_nth(list, idx++)); - db2Debug5(" deserialize col[%d].val_size: %ld" ,i, state->db2Table->cols[i]->val_size); + db2Debug3(" deserialize col[%d].val_size: %ld" ,i, state->db2Table->cols[i]->val_size); state->db2Table->cols[i]->noencerr = deserializeLong(list_nth(list, idx++)); - db2Debug5(" deserialize col[%d].noencerr: %ld" ,i, state->db2Table->cols[i]->noencerr); + db2Debug3(" deserialize col[%d].noencerr: %ld" ,i, state->db2Table->cols[i]->noencerr); } /* length of parameter list */ @@ -119,28 +118,28 @@ DB2FdwState* deserializePlanData (List* list) { for (i = 0; i < len; ++i) { param = (ParamDesc*) db2alloc ("state->parmList->next", sizeof (ParamDesc)); param->colName = deserializeString(list_nth(list, idx++)); - db2Debug5(" deserialize param[%d].colName: %s" ,i, param->colName); + db2Debug3(" deserialize param[%d].colName: %s" ,i, param->colName); param->colType = (short) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug5(" deserialize param[%d].colType: %d" ,i, param->colType); + db2Debug3(" deserialize param[%d].colType: %d" ,i, param->colType); param->colSize = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug5(" deserialize param[%d].colSize: %d" ,i, param->colSize); + db2Debug3(" deserialize param[%d].colSize: %d" ,i, param->colSize); param->type = DatumGetObjectId(((Const*)list_nth(list, idx++))->constvalue); - db2Debug5(" deserialize param[%d].type: %d" ,i, param->type); + db2Debug3(" deserialize param[%d].type: %d" ,i, param->type); param->bindType = (db2BindType) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug5(" deserialize param[%d].bindType: %d" ,i, param->bindType); + db2Debug3(" deserialize param[%d].bindType: %d" ,i, param->bindType); if (param->bindType == BIND_OUTPUT) param->value = (void *) 42; /* something != NULL */ else param->value = NULL; - db2Debug5(" deserialize param[%d].value: %x" ,i, param->value); + db2Debug3(" deserialize param[%d].value: %x" ,i, param->value); param->val_size = deserializeLong(list_nth(list, idx++)); - db2Debug5(" deserialize param[%d].val_size: %ld" ,i, param->val_size); + db2Debug3(" deserialize param[%d].val_size: %ld" ,i, param->val_size); param->node = NULL; - db2Debug5(" deserialize param[%d].node: %x" ,i, param->node); + db2Debug3(" deserialize param[%d].node: %x" ,i, param->node); param->colnum = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug5(" deserialize param[%d].colnum: %d" ,i, param->colnum); + db2Debug3(" deserialize param[%d].colnum: %d" ,i, param->colnum); param->txts = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug5(" deserialize param[%d].txts: %d" ,i, param->txts); + db2Debug3(" deserialize param[%d].txts: %d" ,i, param->txts); param->next = state->paramList; state->paramList = param; } @@ -152,47 +151,45 @@ DB2FdwState* deserializePlanData (List* list) { for (i = 0; i < len; ++i) { DB2ResultColumn* res = (DB2ResultColumn *) db2alloc ("state->resultList->next", sizeof (DB2ResultColumn)); res->colName = deserializeString(list_nth(list, idx++)); - db2Debug5(" deserialize res[%d].colName: %s" ,i, res->colName); + db2Debug3(" deserialize res[%d].colName: %s" ,i, res->colName); res->colType = (short) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug5(" deserialize res[%d].colType: %d" ,i, res->colType); + db2Debug3(" deserialize res[%d].colType: %d" ,i, res->colType); res->colSize = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug5(" deserialize res[%d].colSize: %d" ,i, res->colSize); + db2Debug3(" deserialize res[%d].colSize: %d" ,i, res->colSize); res->colScale = (short) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug5(" deserialize res[%d].colScale: %d" ,i, res->colScale); + db2Debug3(" deserialize res[%d].colScale: %d" ,i, res->colScale); res->colNulls = (short) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug5(" deserialize res[%d].colNulls: %d" ,i, res->colNulls); + db2Debug3(" deserialize res[%d].colNulls: %d" ,i, res->colNulls); res->colChars = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug5(" deserialize res[%d].colChars: %d" ,i, res->colChars); + db2Debug3(" deserialize res[%d].colChars: %d" ,i, res->colChars); res->colBytes = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug5(" deserialize res[%d].colBytes: %d" ,i, res->colBytes); + db2Debug3(" deserialize res[%d].colBytes: %d" ,i, res->colBytes); res->colPrimKeyPart = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug5(" deserialize res[%d].colPrimKeyPart: %d" ,i, res->colPrimKeyPart); + db2Debug3(" deserialize res[%d].colPrimKeyPart: %d" ,i, res->colPrimKeyPart); res->colCodepage = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug5(" deserialize res[%d].colCodepage: %d" ,i, res->colCodepage); + db2Debug3(" deserialize res[%d].colCodepage: %d" ,i, res->colCodepage); res->pgname = deserializeString(list_nth(list, idx++)); - db2Debug5(" deserialize res[%d].pgname: %s" ,i, res->pgname); + db2Debug3(" deserialize res[%d].pgname: %s" ,i, res->pgname); res->pgattnum = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug5(" deserialize res[%d].pgattnum: %d" ,i, res->pgattnum); + db2Debug3(" deserialize res[%d].pgattnum: %d" ,i, res->pgattnum); res->pgtype = DatumGetObjectId(((Const*)list_nth(list, idx++))->constvalue); - db2Debug5(" deserialize res[%d].pgtype: %d" ,i, res->pgtype); + db2Debug3(" deserialize res[%d].pgtype: %d" ,i, res->pgtype); res->pgtypmod = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug5(" deserialize res[%d].pgtypmod: %d" ,i, res->pgtypmod); + db2Debug3(" deserialize res[%d].pgtypmod: %d" ,i, res->pgtypmod); res->pkey = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug5(" deserialize res[%d].pkey: %d" ,i, res->pkey); + db2Debug3(" deserialize res[%d].pkey: %d" ,i, res->pkey); res->val_size = deserializeLong(list_nth(list, idx++)); - db2Debug5(" deserialize res[%d].val_size: %ld" ,i, res->val_size); + db2Debug3(" deserialize res[%d].val_size: %ld" ,i, res->val_size); res->noencerr = deserializeLong(list_nth(list, idx++)); - db2Debug5(" deserialize res[%d].noencerr: %ld" ,i, res->noencerr); + db2Debug3(" deserialize res[%d].noencerr: %ld" ,i, res->noencerr); res->resnum = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug5(" deserialize res[%d].resnum: %d" ,i, res->resnum); + db2Debug3(" deserialize res[%d].resnum: %d" ,i, res->resnum); res->val = (char*) db2alloc ("res->val", res->val_size + 1); res->val_len = 0; res->val_null = 1; res->next = state->resultList; state->resultList = res; } - - db2Debug1("< deserializePlanData - returns: %x", state); return state; } @@ -265,39 +262,39 @@ List* serializePlanData (DB2FdwState* fdwState) { /* column data */ for (idxCol = 0; idxCol < fdwState->db2Table->ncols; ++idxCol) { result = lappend (result, serializeString (fdwState->db2Table->cols[idxCol]->colName)); - db2Debug5(" serialize col[%d].colName: %s" ,idxCol, fdwState->db2Table->cols[idxCol]->colName); + db2Debug3(" serialize col[%d].colName: %s" ,idxCol, fdwState->db2Table->cols[idxCol]->colName); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->colType)); - db2Debug5(" serialize col[%d].colType: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colType); + db2Debug3(" serialize col[%d].colType: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colType); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->colSize)); - db2Debug5(" serialize col[%d].colSize: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colSize); + db2Debug3(" serialize col[%d].colSize: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colSize); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->colScale)); - db2Debug5(" serialize col[%d].colScale: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colScale); + db2Debug3(" serialize col[%d].colScale: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colScale); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->colNulls)); - db2Debug5(" serialize col[%d].colNulls: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colNulls); + db2Debug3(" serialize col[%d].colNulls: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colNulls); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->colChars)); - db2Debug5(" serialize col[%d].colChars: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colChars); + db2Debug3(" serialize col[%d].colChars: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colChars); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->colBytes)); - db2Debug5(" serialize col[%d].colBytes: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colBytes); + db2Debug3(" serialize col[%d].colBytes: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colBytes); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->colPrimKeyPart)); - db2Debug5(" serialize col[%d].colPrimKeyPart: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colPrimKeyPart); + db2Debug3(" serialize col[%d].colPrimKeyPart: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colPrimKeyPart); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->colCodepage)); - db2Debug5(" serialize col[%d].colCodepage: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colCodepage); + db2Debug3(" serialize col[%d].colCodepage: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colCodepage); result = lappend (result, serializeString (fdwState->db2Table->cols[idxCol]->pgname)); - db2Debug5(" serialize col[%d].pgname: %s" ,idxCol, fdwState->db2Table->cols[idxCol]->pgname); + db2Debug3(" serialize col[%d].pgname: %s" ,idxCol, fdwState->db2Table->cols[idxCol]->pgname); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->pgattnum)); - db2Debug5(" serialize col[%d].pgattnum: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->pgattnum); + db2Debug3(" serialize col[%d].pgattnum: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->pgattnum); result = lappend (result, serializeOid (fdwState->db2Table->cols[idxCol]->pgtype)); - db2Debug5(" serialize col[%d].pgtype: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->pgtype); + db2Debug3(" serialize col[%d].pgtype: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->pgtype); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->pgtypmod)); - db2Debug5(" serialize col[%d].pgtypmod: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->pgtypmod); + db2Debug3(" serialize col[%d].pgtypmod: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->pgtypmod); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->used)); - db2Debug5(" serialize col[%d].used: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->used); + db2Debug3(" serialize col[%d].used: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->used); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->pkey)); - db2Debug5(" serialize col[%d].pkey: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->pkey); + db2Debug3(" serialize col[%d].pkey: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->pkey); result = lappend (result, serializeLong (fdwState->db2Table->cols[idxCol]->val_size)); - db2Debug5(" serialize col[%d].val_size: %ld" ,idxCol, fdwState->db2Table->cols[idxCol]->val_size); + db2Debug3(" serialize col[%d].val_size: %ld" ,idxCol, fdwState->db2Table->cols[idxCol]->val_size); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->noencerr)); - db2Debug5(" serialize col[%d].noencerr: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->noencerr); + db2Debug3(" serialize col[%d].noencerr: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->noencerr); /* don't serialize val, val_len, val_null and varno */ } @@ -307,25 +304,25 @@ List* serializePlanData (DB2FdwState* fdwState) { } /* serialize length */ result = lappend (result, serializeInt (lenParam)); - db2Debug5(" serialize paramList.length: %d", lenParam); + db2Debug3(" serialize paramList.length: %d", lenParam); /* parameter list entries */ for (param = fdwState->paramList; param; param = param->next) { result = lappend (result, serializeString (param->colName)); - db2Debug5(" serialize param.colName: %s" , param->colName); + db2Debug3(" serialize param.colName: %s" , param->colName); result = lappend (result, serializeInt (param->colType)); - db2Debug5(" serialize param.colType: %d" , param->colType); + db2Debug3(" serialize param.colType: %d" , param->colType); result = lappend (result, serializeInt (param->colSize)); - db2Debug5(" serialize param.colSize: %d" , param->colSize); + db2Debug3(" serialize param.colSize: %d" , param->colSize); result = lappend (result, serializeOid (param->type)); - db2Debug5(" serialize param.type: %d" , param->type); + db2Debug3(" serialize param.type: %d" , param->type); result = lappend (result, serializeInt ((int) param->bindType)); - db2Debug5(" serialize param.bindType: %d" , param->bindType); + db2Debug3(" serialize param.bindType: %d" , param->bindType); result = lappend (result, serializeLong (param->val_size)); - db2Debug5(" serialize param.val_size: %ld" , param->val_size); + db2Debug3(" serialize param.val_size: %ld" , param->val_size); result = lappend (result, serializeInt ((int) param->colnum)); - db2Debug5(" serialize param.colnum: %d" , param->colnum); + db2Debug3(" serialize param.colnum: %d" , param->colnum); result = lappend (result, serializeInt ((int) param->txts)); - db2Debug5(" serialize param.txts: %d" , param->txts); + db2Debug3(" serialize param.txts: %d" , param->txts); } /* find length of result list */ @@ -335,43 +332,43 @@ List* serializePlanData (DB2FdwState* fdwState) { } /* serialize length */ result = lappend (result, serializeInt (lenParam)); - db2Debug5(" serialize resultList.length: %d", lenParam); + db2Debug3(" serialize resultList.length: %d", lenParam); /* parameter list entries */ for (rcol = fdwState->resultList; rcol; rcol = rcol->next) { result = lappend (result, serializeString (rcol->colName)); - db2Debug5(" serialize res.colName: %s" , rcol->colName); + db2Debug3(" serialize res.colName: %s" , rcol->colName); result = lappend (result, serializeInt (rcol->colType)); - db2Debug5(" serialize res.colType: %d" , rcol->colType); + db2Debug3(" serialize res.colType: %d" , rcol->colType); result = lappend (result, serializeInt (rcol->colSize)); - db2Debug5(" serialize res.colSize: %d" , rcol->colSize); + db2Debug3(" serialize res.colSize: %d" , rcol->colSize); result = lappend (result, serializeInt (rcol->colScale)); - db2Debug5(" serialize res.colScale: %d" , rcol->colScale); + db2Debug3(" serialize res.colScale: %d" , rcol->colScale); result = lappend (result, serializeInt (rcol->colNulls)); - db2Debug5(" serialize res.colNulls: %d", rcol->colNulls); + db2Debug3(" serialize res.colNulls: %d", rcol->colNulls); result = lappend (result, serializeInt (rcol->colChars)); - db2Debug5(" serialize res.colChars: %d" , rcol->colChars); + db2Debug3(" serialize res.colChars: %d" , rcol->colChars); result = lappend (result, serializeInt (rcol->colBytes)); - db2Debug5(" serialize res.colBytes: %d" , rcol->colBytes); + db2Debug3(" serialize res.colBytes: %d" , rcol->colBytes); result = lappend (result, serializeInt (rcol->colPrimKeyPart)); - db2Debug5(" serialize res.colPrimKeyPart: %d", rcol->colPrimKeyPart); + db2Debug3(" serialize res.colPrimKeyPart: %d", rcol->colPrimKeyPart); result = lappend (result, serializeInt (rcol->colCodepage)); - db2Debug5(" serialize res.codepage: %d" , rcol->colCodepage); + db2Debug3(" serialize res.codepage: %d" , rcol->colCodepage); result = lappend (result, serializeString (rcol->pgname)); - db2Debug5(" serialize res.pgname: %s" , rcol->pgname); + db2Debug3(" serialize res.pgname: %s" , rcol->pgname); result = lappend (result, serializeInt (rcol->pgattnum)); - db2Debug5(" serialize res.pgattnum: %d" , rcol->pgattnum); + db2Debug3(" serialize res.pgattnum: %d" , rcol->pgattnum); result = lappend (result, serializeOid (rcol->pgtype)); - db2Debug5(" serialize res.pgtype: %d" , rcol->pgtype); + db2Debug3(" serialize res.pgtype: %d" , rcol->pgtype); result = lappend (result, serializeInt (rcol->pgtypmod)); - db2Debug5(" serialize res.pgtypmod: %d" , rcol->pgtypmod); + db2Debug3(" serialize res.pgtypmod: %d" , rcol->pgtypmod); result = lappend (result, serializeInt (rcol->pkey)); - db2Debug5(" serialize res.pkey: %d" , rcol->pkey); + db2Debug3(" serialize res.pkey: %d" , rcol->pkey); result = lappend (result, serializeLong (rcol->val_size)); - db2Debug5(" serialize res.val_size: %ld", rcol->val_size); + db2Debug3(" serialize res.val_size: %ld", rcol->val_size); result = lappend (result, serializeInt (rcol->noencerr)); - db2Debug5(" serialize res.noencerr: %d" , rcol->noencerr); + db2Debug3(" serialize res.noencerr: %d" , rcol->noencerr); result = lappend (result, serializeInt (rcol->resnum)); // the last result is the first in the list - db2Debug5(" serialize res.resnum: %d" , rcol->resnum); + db2Debug3(" serialize res.resnum: %d" , rcol->resnum); lenParam--; } From 356a2050dd5980d1a9ba2d410798e2f248f11b63 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Sun, 22 Feb 2026 19:27:49 +0100 Subject: [PATCH 077/120] still trying to get the Update to work but the junkres does not bear the correct value yet --- source/db2AddForeignUpdateTargets.c | 29 +++-- source/db2ExecForeignDelete.c | 179 ++++++++++++++++++---------- source/db2GetForeignPlan.c | 96 +++++++++------ source/db2GetForeignUpperPaths.c | 34 +++--- 4 files changed, 209 insertions(+), 129 deletions(-) diff --git a/source/db2AddForeignUpdateTargets.c b/source/db2AddForeignUpdateTargets.c index 76037c3..11c7c95 100644 --- a/source/db2AddForeignUpdateTargets.c +++ b/source/db2AddForeignUpdateTargets.c @@ -43,6 +43,10 @@ void db2AddForeignUpdateTargets (PlannerInfo* root, Index rtindex,RangeTblEntry* AttrNumber attrno = att->attnum; List* options = NIL; ListCell* option = NULL; + + if (att->attisdropped) + continue; + /* look for the "key" option on this column */ options = GetForeignColumnOptions (relid, attrno); foreach (option, options) { @@ -50,7 +54,9 @@ void db2AddForeignUpdateTargets (PlannerInfo* root, Index rtindex,RangeTblEntry* /* if "key" is set, add a resjunk for this column */ if (strcmp (def->defname, OPT_KEY) == 0) { if (optionIsTrue (STRVAL(def->arg))) { - Var* var; + Var* var = NULL; + char* key_col_name = db2strdup(psprintf("__db2fdw_rowid_%s", NameStr(att->attname))); + #if PG_VERSION_NUM < 140000 TargetEntry *tle; /* Make a Var representing the desired value */ @@ -62,14 +68,15 @@ void db2AddForeignUpdateTargets (PlannerInfo* root, Index rtindex,RangeTblEntry* att->attcollation, 0); /* Wrap it in a resjunk TLE with the right name ... */ - tle = makeTargetEntry((Expr *)var, - list_length(parsetree->targetList) + 1, - db2strdup(NameStr(att->attname)), - true); + tle = makeTargetEntry( (Expr*)var + , list_length(parsetree->targetList) + 1 + , key_col_name + , true + ); /* ... and add it to the query's targetlist */ parsetree->targetList = lappend(parsetree->targetList, tle); #else - /* Make a Var representing the desired value */ + /* Build a Var referencing the PK column of the target RTE */ var = makeVar( rtindex , attrno , att->atttypid @@ -77,13 +84,15 @@ void db2AddForeignUpdateTargets (PlannerInfo* root, Index rtindex,RangeTblEntry* , att->attcollation , 0 ); - db2Debug2(" add resjunk for column %d - %s at index %d", attrno, NameStr(att->attname),rtindex); - add_row_identity_var(root, var, rtindex, NameStr(att->attname)); + db2Debug2(" create var rtindex: %d, attrno: %d, typid: %d, typmod: %d, collation: %d",rtindex,attrno,att->atttypid,att->atttypmod,att->attcollation); + /* Register it as a required row-identity column. + * The name becomes the resjunk column name in the plan. + */ + add_row_identity_var(root, var, rtindex, key_col_name); + db2Debug2(" add resjunk column %s: %d", key_col_name, rtindex); #endif /* PG_VERSION_NUM */ has_key = true; } - } else { - elog (ERROR, "impossible column option \"%s\"", def->defname); } } } diff --git a/source/db2ExecForeignDelete.c b/source/db2ExecForeignDelete.c index 4131d6e..c8dd800 100644 --- a/source/db2ExecForeignDelete.c +++ b/source/db2ExecForeignDelete.c @@ -75,94 +75,143 @@ TupleTableSlot* db2ExecForeignDelete (EState* estate, ResultRelInfo* rinfo, Tupl * "newslot" contains the new values, "oldslot" the old ones. */ void setModifyParameters (ParamDesc *paramList, TupleTableSlot * newslot, TupleTableSlot * oldslot, DB2Table *db2Table, DB2Session * session) { - ParamDesc *param; - Datum datum; - bool isnull; - int32 value_len; - char *p, *q; - Oid pgtype; - db2Debug1("> setModifyParameters"); + ParamDesc* param = NULL; + Datum datum = 0; + bool isnull = true; + + db2Debug1("> %s::setModifyParameters", __FILE__); for (param = paramList; param != NULL; param = param->next) { db2Debug2(" db2Table->cols[%d]->colName: %s ",param->colnum,db2Table->cols[param->colnum]->colName); - db2Debug2(" param->bindType: %d ",param->bindType); - db2Debug2(" param->colnum : %d ",param->colnum); - db2Debug2(" param->txts : %d ",param->txts); - db2Debug2(" param->type : %d ",param->type); - db2Debug2(" param->value : '%s'",param->value); + db2Debug2(" param->bindType: %d",param->bindType); + db2Debug2(" param->colnum : %d",param->colnum); + db2Debug2(" param->txts : %d",param->txts); + db2Debug2(" param->type : %d",param->type); + db2Debug2(" param->value : %s - initial",param->value); /* don't do anything for output parameters */ - if (param->bindType == BIND_OUTPUT) + if (param->bindType == BIND_OUTPUT) { + db2Debug2(" param->bindType: %d - BIND_OUTPUT - skipped",param->bindType); continue; - - if (db2Table->cols[param->colnum]->colPrimKeyPart != 0) { - /* for primary key parameters extract the resjunk entry */ - datum = ExecGetJunkAttribute (oldslot, db2Table->cols[param->colnum]->pkey, &isnull); } - else { + if (db2Table->cols[param->colnum]->colPrimKeyPart != 0) { + AttrNumber attrno = -1; + TupleDesc tupdesc = oldslot->tts_tupleDescriptor; + for (int i = 0; i < tupdesc->natts; i++) { + Form_pg_attribute att = TupleDescAttr(tupdesc,i); + if (att->attisdropped) + continue; + if (strcmp(NameStr(att->attname), psprintf("__db2fdw_rowid_%s", db2Table->cols[param->colnum]->pgname)) == 0) { + attrno = i + 1; + db2Debug2(" key %s attrno : %d",NameStr(att->attname),attrno); + db2Debug2(" atttypid : %d",att->atttypid); + db2Debug2(" atttypmod: %d",att->atttypmod); + break; + } + } + if (attrno != -1) { + /* for primary key parameters extract the resjunk entry */ + datum = ExecGetJunkAttribute (oldslot, attrno, &isnull); + db2Debug2(" primaryKey value from resjunk entry: %ld",datum); + } + } else { /* for other parameters extract the datum from newslot */ datum = slot_getattr (newslot, db2Table->cols[param->colnum]->pgattnum, &isnull); + db2Debug2(" parameter value from newslot: %ld",datum); } switch (param->bindType) { case BIND_STRING: - case BIND_NUMBER: + case BIND_NUMBER: { if (isnull) { param->value = NULL; - break; - } - pgtype = db2Table->cols[param->colnum]->pgtype; - db2Debug2(" db2Table->cols[%d]->pgtype: %d",param->colnum,db2Table->cols[param->colnum]->pgtype); - /* special treatment for date, timestamps and intervals */ - if (pgtype == DATEOID) { - param->value = deparseDate (datum); - break; /* from switch (param->bindType) */ - } else if (pgtype == TIMESTAMPOID || pgtype == TIMESTAMPTZOID) { - param->value = deparseTimestamp (datum, false/*(pgtype == TIMESTAMPTZOID)*/); - break; /* from switch (param->bindType) */ - } else if (pgtype == TIMEOID || pgtype == TIMETZOID) { - param->value = deparseTimestamp (datum, false/*(pgtype == TIMETZOID)*/); - break; /* from switch (param->bindType) */ - } - /* convert the parameter value into a string */ - param->value = DatumGetCString (OidFunctionCall1 (output_funcs[param->colnum], datum)); - db2Debug2(" param->value: %s",param->value); - /* some data types need additional processing */ - switch (db2Table->cols[param->colnum]->pgtype) { - case UUIDOID: - /* remove the minus signs for UUIDs */ - for (p = q = param->value; *p != '\0'; ++p, ++q) { - if (*p == '-') - ++p; - *q = *p; + db2Debug2(" param->value: %s - (NULL since isnull is set)",param->value); + } else { + db2Debug2(" db2Table->cols[%d]->pgtype: %d",param->colnum,db2Table->cols[param->colnum]->pgtype); + /* special treatment for date, timestamps and intervals */ + switch (db2Table->cols[param->colnum]->pgtype) { + case DATEOID: { + param->value = deparseDate (datum); + db2Debug2(" param->value: %s - (ought to be a date)",param->value); } - *q = '\0'; - break; - case BOOLOID: - /* convert booleans to numbers */ - if (param->value[0] == 't') - param->value[0] = '1'; - else - param->value[0] = '0'; - param->value[1] = '\0'; - break; + break; + case TIMESTAMPOID: + case TIMESTAMPTZOID: { + param->value = deparseTimestamp (datum, false/*(pgtype == TIMESTAMPTZOID)*/); + db2Debug2(" param->value: %s - (ought to be a timestamp)",param->value); + } + break; + case TIMEOID: + case TIMETZOID:{ + param->value = deparseTimestamp (datum, false/*(pgtype == TIMETZOID)*/); + db2Debug2(" param->value: %s (ought to be a time)",param->value); + } + break; + case BPCHAROID: + case VARCHAROID: + case INTERVALOID: + case NUMERICOID: { + /* these functions require the type modifier */ + param->value = DatumGetCString( OidFunctionCall3 (output_funcs[param->colnum], datum, ObjectIdGetDatum (InvalidOid), Int32GetDatum (db2Table->cols[param->colnum]->pgtypmod))); + db2Debug2(" param->value: %s (ought to be a BPCHAR, VARCHAR,INTERVAL or NUMERIC)",param->value); + } + break; + case UUIDOID: { + char* p = NULL; + char* q = NULL; + + param->value = DatumGetCString (OidFunctionCall1 (output_funcs[param->colnum], datum)); + db2Debug2(" param->value: %s (ought to be a UUID)",param->value); + + /* remove the minus signs for UUIDs */ + for (p = q = param->value; *p != '\0'; ++p, ++q) { + if (*p == '-') + ++p; + *q = *p; + } + *q = '\0'; + } + break; + case BOOLOID: { + /* convert booleans to numbers */ + param->value = DatumGetCString (OidFunctionCall1 (output_funcs[param->colnum], datum)); + db2Debug2(" param->value: %s (ought to be a boolean)",param->value); + param->value[0] = (param->value[0] == 't') ? '1' : '0'; + param->value[1] = '\0'; + } + break; + default: { + /* the others don't */ + /* convert the parameter value into a string */ + param->value = DatumGetCString (OidFunctionCall1 (output_funcs[param->colnum], datum)); + db2Debug2(" param->value: %s (ought to be a string)",param->value); + } + break; + } } + } break; case BIND_LONG: - case BIND_LONGRAW: + case BIND_LONGRAW: { if (isnull) { param->value = NULL; - break; + db2Debug2(" param->value: %s - (NULL since isnull is set)",param->value); + } else { + int32 value_len = 0; + + /* detoast it if necessary */ + datum = (Datum) PG_DETOAST_DATUM (datum); + /* the first 4 bytes contain the length */ + value_len = VARSIZE (datum) - VARHDRSZ; + param->value = db2alloc("param->value", value_len); + memcpy (param->value, VARDATA(datum), value_len); + db2Debug2(" param->value: %s (ought to be a LONG or LONGRAW)",param->value); } - /* detoast it if necessary */ - datum = (Datum) PG_DETOAST_DATUM (datum); - /* the first 4 bytes contain the length */ - value_len = VARSIZE (datum) - VARHDRSZ; - param->value = db2alloc("param->value", value_len); - memcpy (param->value, VARDATA(datum), value_len); + } break; - case BIND_OUTPUT: + default: + db2Debug2(" unknown BIND_TYPE: %d", param->bindType); break; } - db2Debug2(" param->value : '%s'",param->value); + db2Debug2(" param->value : %s - finally",param->value); } db2Debug1("< setModifyParameters"); } diff --git a/source/db2GetForeignPlan.c b/source/db2GetForeignPlan.c index e0cdbce..604dee3 100644 --- a/source/db2GetForeignPlan.c +++ b/source/db2GetForeignPlan.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include "db2_fdw.h" @@ -26,6 +27,7 @@ extern void db2Debug1 (const char* message, ...); extern void db2Debug2 (const char* message, ...); extern void db2Debug3 (const char* message, ...); extern void* db2alloc (const char* type, size_t size); +extern void* db2free (void* pvoid); extern char* db2strdup (const char* source); @@ -37,7 +39,7 @@ extern List* serializePlanData (DB2FdwState* fdw_state); /** local prototypes */ ForeignScan* db2GetForeignPlan (PlannerInfo* root, RelOptInfo* foreignrel, Oid foreigntableid, ForeignPath* best_path, List* tlist, List* scan_clauses , Plan* outer_plan); static void getUsedColumns (Expr* expr, RelOptInfo* foreignrel, DB2ResultColumn* resCol); -static void addResult (DB2ResultColumn* resCol, DB2Column* db2Column); +static void copyCol2Result (DB2ResultColumn* resCol, DB2Column* db2Column); static int compareResultColumns (const void* a, const void* b); /* postgresGetForeignPlan @@ -69,7 +71,9 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo db2Debug2(" length of tlist: %d", list_length(tlist)); - ptlist = build_tlist_to_deparse(foreignrel); +// ptlist = build_tlist_to_deparse(foreignrel); + ptlist = make_tlist_from_pathtarget(foreignrel->reltarget); + apply_pathtarget_labeling_to_tlist(ptlist, foreignrel->reltarget); ptlist_len = list_length(ptlist); if (IS_SIMPLE_REL(foreignrel)) { @@ -77,6 +81,7 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo ListCell* cell = NULL; int iResCol = 0; + db2Debug3(" base relation scan: set scan_relid to %d", foreignrel->relid); /* For base relations, set scan_relid as the relid of the relation. */ scan_relid = foreignrel->relid; @@ -88,10 +93,16 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo foreach (cell, ptlist) { resCol = (DB2ResultColumn*)db2alloc("resultColumn",sizeof(DB2ResultColumn)); getUsedColumns ((Expr*) lfirst (cell), foreignrel, resCol); - resCol->next = fpinfo->resultList; - db2Debug3(" resCol->next: %x", resCol->next); - fpinfo->resultList = resCol; - db2Debug3(" fpinfo->resultList: %x", fpinfo->resultList); + db2Debug3(" resCol->colName: %s", resCol->colName); + db2Debug3(" resCol->pgattnum: %d", resCol->pgattnum); + if (resCol->colName != NULL && resCol->pgattnum <= fpinfo->db2Table->npgcols) { + resCol->next = fpinfo->resultList; + db2Debug3(" resCol->next: %x", resCol->next); + fpinfo->resultList = resCol; + db2Debug3(" fpinfo->resultList: %x", fpinfo->resultList); + } else { + db2free(resCol); + } } for (resCol = fpinfo->resultList; resCol; resCol = resCol->next) { iResCol++; @@ -101,8 +112,9 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo for (resCol = fpinfo->resultList; resCol; resCol = resCol->next) { db2Debug2(" resCol: %x", resCol); cols[iResCol] = resCol; - db2Debug2(" cols[%d]: %x", iResCol, cols[iResCol]); - db2Debug2(" cols[%d]->resnum: %d", iResCol, cols[iResCol]->resnum); + db2Debug2(" cols[%d] : %x", iResCol, cols[iResCol]); + db2Debug2(" cols[%d]->colName: %s", iResCol, cols[iResCol]->colName); + db2Debug2(" cols[%d]->resnum : %d", iResCol, cols[iResCol]->resnum); iResCol++; db2Debug2(" resCol->next: %x", resCol->next); } @@ -111,10 +123,16 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo qsort(cols, iResCol, sizeof(DB2ResultColumn*), compareResultColumns); // generate the sorted array into the resultList fpinfo->resultList = NULL; - for (int idx = 0; idx < iResCol; idx++) { + for (int idx = 0, cidx = 0; idx < iResCol; idx++) { db2Debug3(" result column %d: %s", idx, cols[idx]->colName); + if (idx > 0) { + // check if this column is the same as the one before and if so, skip it + if (cols[idx]->pgattnum == cols[idx-1]->pgattnum) { + continue; + } + } cols[idx]->next = fpinfo->resultList; - cols[idx]->resnum = idx+1; + cols[idx]->resnum = ++cidx; // result number must be 1 - based db2Debug3(" column %s added to result list with resnum %d", cols[idx]->colName, cols[idx]->resnum); fpinfo->resultList = cols[idx]; } @@ -156,6 +174,7 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo /* For a base-relation scan, we have to support EPQ recheck, which should recheck all the remote quals. */ fdw_recheck_quals = remote_exprs; } else { + db2Debug3(" join relation scan: set scan_relid to 0"); /* Join relation or upper relation - set scan_relid to 0. */ scan_relid = 0; @@ -189,6 +208,7 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo fpinfo->resultList = resCol; resCol->resnum = resnum; getUsedColumns ((Expr*) lfirst (cell), foreignrel, resCol); + db2Debug3(" result column %d: %s", resCol->resnum, resCol->colName); resnum++; } } @@ -197,6 +217,7 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo * from outer plan's quals, lest they be evaluated twice, once by the local plan and once by the scan. */ if (outer_plan) { + db2Debug3(" adjusting outer plan's targetlist and quals to match scan's needs"); /* Right now, we only consider grouping and aggregation beyond joins. * Queries involving aggregates or grouping do not require EPQ mechanism, hence should not have an outer plan here. */ @@ -249,8 +270,8 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo return fscan; } -/** getUsedColumns - * Set "used=true" in db2Table for all columns used in the expression. +/* getUsedColumns + * Set "used=true" in db2Table for all columns used in the expression. */ static void getUsedColumns (Expr* expr, RelOptInfo* foreignrel, DB2ResultColumn* resCol) { ListCell* cell = NULL; @@ -273,16 +294,17 @@ static void getUsedColumns (Expr* expr, RelOptInfo* foreignrel, DB2ResultColumn* case T_NextValueExpr: break; case T_Var: { - DB2FdwState* fpinfo = (DB2FdwState*) foreignrel->fdw_private; - Var* variable = NULL; - int index = 0; + DB2FdwState* fpinfo = (DB2FdwState*) foreignrel->fdw_private; + Var* var = NULL; + int index = 0; - variable = (Var*) expr; + var = (Var*) expr; + db2Debug2(" var->varattno: %d", var->varattno); /* ignore system columns */ - if (variable->varattno < 0) + if (var->varattno < 0) break; /* if this is a wholerow reference, we need all columns */ - if (variable->varattno == 0) { + if (var->varattno == 0) { DB2ResultColumn* tmpCol = NULL; db2Debug2(" found whole-row reference, need to add all columns"); db2Debug2(" fpinfo->resultList: %x", fpinfo->resultList); @@ -291,7 +313,7 @@ static void getUsedColumns (Expr* expr, RelOptInfo* foreignrel, DB2ResultColumn* if (fpinfo->db2Table->cols[index]->pgname) { tmpCol = (DB2ResultColumn*)db2alloc("resultColumn",sizeof(DB2ResultColumn)); tmpCol->resnum = index+1; - addResult(tmpCol,fpinfo->db2Table->cols[index]); + copyCol2Result(tmpCol,fpinfo->db2Table->cols[index]); db2Debug2(" db2Table[%d]->colName %s added to result list", index, fpinfo->db2Table->cols[index]->colName); tmpCol->next = fpinfo->resultList; db2Debug2(" tmpCol-next: %x", tmpCol->next); @@ -300,21 +322,21 @@ static void getUsedColumns (Expr* expr, RelOptInfo* foreignrel, DB2ResultColumn* } } // now add the last colum using the resCol passed in, so that the column name in the result list is correct for whole row reference - addResult(resCol,fpinfo->db2Table->cols[index]); + copyCol2Result(resCol,fpinfo->db2Table->cols[index]); resCol->resnum = index+1; db2Debug3(" db2Table[%d]->colName %s added to result list", index, fpinfo->db2Table->cols[index]->colName); break; - } - /* get db2Table column index corresponding to this column (-1 if none) */ - index = fpinfo->db2Table->ncols - 1; - db2Debug2(" variable->varattno: %d", variable->varattno); - while (index >= 0 && fpinfo->db2Table->cols[index]->pgattnum != variable->varattno) { - --index; - } - if (index == -1) { - ereport (WARNING, (errcode (ERRCODE_WARNING),errmsg ("column number %d of foreign table \"%s\" does not exist in foreign DB2 table, will be replaced by NULL", variable->varattno, fpinfo->db2Table->pgname))); } else { - addResult(resCol,fpinfo->db2Table->cols[index]); + /* get db2Table column index corresponding to this column (-1 if none) */ + index = fpinfo->db2Table->ncols - 1; + while (index >= 0 && fpinfo->db2Table->cols[index]->pgattnum != var->varattno) { + --index; + } + if (index == -1) { + ereport (WARNING, (errcode (ERRCODE_WARNING),errmsg ("column number %d of foreign table \"%s\" does not exist in foreign DB2 table, will be replaced by NULL", var->varattno, fpinfo->db2Table->pgname))); + } else { + copyCol2Result(resCol,fpinfo->db2Table->cols[index]); + } } } break; @@ -361,7 +383,7 @@ static void getUsedColumns (Expr* expr, RelOptInfo* foreignrel, DB2ResultColumn* col->pkey = 0; col->val_size = 24; col->noencerr = fpinfo->db2Table->cols[0]->noencerr; // use same noencerr as first column - addResult(resCol,col); + copyCol2Result(resCol,col); } else { db2Debug2(" count aggref->args: %d",list_length(aggref->args)); foreach (cell, aggref->args) { @@ -529,8 +551,11 @@ static void getUsedColumns (Expr* expr, RelOptInfo* foreignrel, DB2ResultColumn* db2Debug1("< getUsedColumns"); } -static void addResult(DB2ResultColumn* resCol, DB2Column* column) { - db2Debug1("> %s::addResult",__FILE__); +/* copyCol2Result + * Copy the column information from the db2Table column to the result column. + */ +static void copyCol2Result(DB2ResultColumn* resCol, DB2Column* column) { + db2Debug1("> %s::copyCol2Result",__FILE__); if (resCol && resCol->colName == NULL) { resCol->colName = db2strdup(column->colName); resCol->colType = column->colType; @@ -550,9 +575,12 @@ static void addResult(DB2ResultColumn* resCol, DB2Column* column) { resCol->val_size = column->val_size; resCol->noencerr = column->noencerr; } - db2Debug1("< %s::addResult",__FILE__); + db2Debug1("< %s::copyCol2Result",__FILE__); } +/* compareResultColumns + * Compare two DB2ResultColumn pointers by their pgattnum. + */ static int compareResultColumns(const void* a, const void* b) { DB2ResultColumn* colA = *(DB2ResultColumn**) a; DB2ResultColumn* colB = *(DB2ResultColumn**) b; diff --git a/source/db2GetForeignUpperPaths.c b/source/db2GetForeignUpperPaths.c index b3a36ed..47b6b1c 100644 --- a/source/db2GetForeignUpperPaths.c +++ b/source/db2GetForeignUpperPaths.c @@ -1,9 +1,7 @@ #include -#include #include #include #include - #include #include #include @@ -162,13 +160,13 @@ static void db2CloneFdwStateUpper(PlannerInfo* root, RelOptInfo* input_rel, RelO copy = (DB2FdwState*) db2alloc("fdw_state_upper", sizeof(DB2FdwState)); /* Connection/session fields */ - copy->dbserver = fdw_in->dbserver ? db2strdup(fdw_in->dbserver) : NULL; - copy->user = fdw_in->user ? db2strdup(fdw_in->user) : NULL; - copy->password = fdw_in->password ? db2strdup(fdw_in->password) : NULL; - copy->jwt_token = fdw_in->jwt_token ? db2strdup(fdw_in->jwt_token) : NULL; - copy->nls_lang = fdw_in->nls_lang ? db2strdup(fdw_in->nls_lang) : NULL; + copy->dbserver = fdw_in->dbserver ? db2strdup(fdw_in->dbserver) : NULL; + copy->user = fdw_in->user ? db2strdup(fdw_in->user) : NULL; + copy->password = fdw_in->password ? db2strdup(fdw_in->password) : NULL; + copy->jwt_token = fdw_in->jwt_token ? db2strdup(fdw_in->jwt_token) : NULL; + copy->nls_lang = fdw_in->nls_lang ? db2strdup(fdw_in->nls_lang) : NULL; /* Planner-time session handle can be shared (it is not serialized). */ - copy->session = fdw_in->session; + copy->session = fdw_in->session; /* Planning/execution fields */ copy->query = fdw_in->query ? db2strdup(fdw_in->query) : NULL; @@ -181,26 +179,22 @@ static void db2CloneFdwStateUpper(PlannerInfo* root, RelOptInfo* input_rel, RelO copy->order_clause = fdw_in->order_clause ? db2strdup(fdw_in->order_clause) : NULL; copy->where_clause = fdw_in->where_clause ? db2strdup(fdw_in->where_clause) : NULL; - /* Shallow-copy expression lists (Expr nodes are immutable at this stage), but - * ensure list cells are independent because createQuery mutates the list. - */ + /* Shallow-copy expression lists (Expr nodes are immutable at this stage), but ensure list cells are independent because createQuery mutates the list. */ copy->params = fdw_in->params ? list_copy(fdw_in->params) : NIL; copy->remote_conds = fdw_in->remote_conds ? list_copy(fdw_in->remote_conds) : NIL; copy->local_conds = fdw_in->local_conds ? list_copy(fdw_in->local_conds) : NIL; - /* Deep-copy DB2 table/columns because DB2Column.used is re-derived for each - * planned query shape. - */ - copy->db2Table = db2CloneDb2TableForPlan(fdw_in->db2Table); + /* Deep-copy DB2 table/columns because DB2Column used is re-derived for each planned query shape. */ + copy->db2Table = db2CloneDb2TableForPlan(fdw_in->db2Table); /* Join info: keep as-is (typically NULL for baserels). */ - copy->outerrel = fdw_in->outerrel; - copy->innerrel = fdw_in->innerrel; - copy->jointype = fdw_in->jointype; - copy->joinclauses = fdw_in->joinclauses ? list_copy(fdw_in->joinclauses) : NIL; + copy->outerrel = fdw_in->outerrel; + copy->innerrel = fdw_in->innerrel; + copy->jointype = fdw_in->jointype; + copy->joinclauses = fdw_in->joinclauses ? list_copy(fdw_in->joinclauses) : NIL; /* paramList is constructed at execution time from fdw_exprs. */ - copy->paramList = NULL; + copy->paramList = NULL; } output_rel->fdw_private = copy; db2Debug1("< %s::db2CloneFdwStateUpper", __FILE__); From 9026f72cf3da231a7e0d098454389930c741e6ad Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Tue, 24 Feb 2026 13:22:23 +0100 Subject: [PATCH 078/120] complete rework of the debug tracing functions --- include/db2_fdw.h | 1 + source/db2AddForeignUpdateTargets.c | 25 +- source/db2AllocConnHdl.c | 52 +- source/db2AllocEnvHdl.c | 76 ++- source/db2AllocStmtHdl.c | 103 ++-- source/db2AnalyzeForeignTable.c | 36 +- source/db2BeginDirectModify.c | 105 ++-- source/db2BeginForeignInsert.c | 16 +- source/db2BeginForeignModify.c | 19 +- source/db2BeginForeignModifyCommon.c | 8 +- source/db2BeginForeignScan.c | 30 +- source/db2BindParameter.c | 54 +- source/db2Callbacks.c | 35 +- source/db2Cancel.c | 13 +- source/db2CheckErr.c | 45 +- source/db2ClientVersion.c | 15 +- source/db2CloseConnections.c | 71 ++- source/db2CloseStatement.c | 17 +- source/db2CopyText.c | 16 +- source/db2Debug.c | 142 +++-- source/db2Describe.c | 45 +- source/db2EndDirectModify.c | 11 +- source/db2EndForeignInsert.c | 7 +- source/db2EndForeignModify.c | 11 +- source/db2EndForeignModifyCommon.c | 14 +- source/db2EndForeignScan.c | 16 +- source/db2EndSubtransaction.c | 22 +- source/db2EndTransaction.c | 79 ++- source/db2ExecForeignBatchInsert.c | 7 +- source/db2ExecForeignDelete.c | 78 +-- source/db2ExecForeignInsert.c | 23 +- source/db2ExecForeignTruncate.c | 37 +- source/db2ExecForeignUpdate.c | 24 +- source/db2ExecuteInsert.c | 49 +- source/db2ExecuteQuery.c | 39 +- source/db2ExecuteTruncate.c | 14 +- source/db2ExplainForeignModify.c | 18 +- source/db2ExplainForeignScan.c | 32 +- source/db2FetchNext.c | 11 +- source/db2FreeEnvHdl.c | 108 ++-- source/db2FreeStmtHdl.c | 36 +- source/db2GetFdwState.c | 116 ++-- source/db2GetForeignJoinPaths.c | 194 +++--- source/db2GetForeignModifyBatchSize.c | 51 +- source/db2GetForeignPaths.c | 62 +- source/db2GetForeignPlan.c | 98 ++- source/db2GetForeignPlanOld.c | 1 - source/db2GetForeignRelSize.c | 20 +- source/db2GetForeignUpperPaths.c | 858 +++++++++++--------------- source/db2GetLob.c | 52 +- source/db2GetSession.c | 15 +- source/db2GetShareFileName.c | 12 +- source/db2ImportForeignSchema.c | 78 +-- source/db2ImportForeignSchemaData.c | 122 ++-- source/db2IsForeignRelUpdatable.c | 16 +- source/db2IsStatementOpen.c | 13 +- source/db2IterateDirectModify.c | 32 +- source/db2IterateForeignScan.c | 47 +- source/db2PlanDirectModify.c | 86 +-- source/db2PlanForeignModify.c | 121 ++-- source/db2PrepareQuery.c | 83 ++- source/db2ReAllocFree.c | 22 +- source/db2ReScanForeignScan.c | 16 +- source/db2ServerVersion.c | 15 +- source/db2SetHandlers.c | 27 +- source/db2SetSavepoint.c | 22 +- source/db2Shutdown.c | 13 +- source/db2_de_serialize.c | 207 ++++--- source/db2_deparse.c | 718 +++++++++++---------- source/db2_fdw.c | 11 +- source/db2_fdw_utils.c | 159 ++--- source/db2_utils.c | 19 +- 72 files changed, 2366 insertions(+), 2500 deletions(-) diff --git a/include/db2_fdw.h b/include/db2_fdw.h index 8c5c640..c6d8113 100644 --- a/include/db2_fdw.h +++ b/include/db2_fdw.h @@ -13,6 +13,7 @@ #ifdef POSTGRES_H #include #include +#include #if PG_VERSION_NUM >= 150000 #define STRVAL(arg) ((String *)(arg))->sval #else diff --git a/source/db2AddForeignUpdateTargets.c b/source/db2AddForeignUpdateTargets.c index 11c7c95..db5bf83 100644 --- a/source/db2AddForeignUpdateTargets.c +++ b/source/db2AddForeignUpdateTargets.c @@ -1,20 +1,19 @@ #include -#include #include #if PG_VERSION_NUM >= 140000 #include #endif /* PG_VERSION_NUM */ -#include #include #include #include #include "db2_fdw.h" /** external prototypes */ -extern bool optionIsTrue (const char* value); -extern void db2Debug1 (const char* message, ...); -extern void db2Debug2 (const char* message, ...); -extern char* db2strdup (const char* source); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); +extern void db2Debug (int level, const char* message, ...); +extern char* db2strdup (const char* source); +extern bool optionIsTrue (const char* value); /** local prototypes */ #if PG_VERSION_NUM < 140000 @@ -35,8 +34,8 @@ void db2AddForeignUpdateTargets (PlannerInfo* root, Index rtindex,RangeTblEntry* TupleDesc tupdesc = target_relation->rd_att; int i = 0; bool has_key = false; - db2Debug1("> db2AddForeignUpdateTargets"); - db2Debug2(" add target columns for update on %d - %s", relid, get_rel_name(relid)); + db2Entry(1,"> db2AddForeignUpdateTargets.c::db2AddForeignUpdateTargets"); + db2Debug(2,"add target columns for update on %d - %s", relid, get_rel_name(relid)); /* loop through all columns of the foreign table */ for (i = 0; i < tupdesc->natts; ++i) { Form_pg_attribute att = TupleDescAttr (tupdesc, i); @@ -84,12 +83,10 @@ void db2AddForeignUpdateTargets (PlannerInfo* root, Index rtindex,RangeTblEntry* , att->attcollation , 0 ); - db2Debug2(" create var rtindex: %d, attrno: %d, typid: %d, typmod: %d, collation: %d",rtindex,attrno,att->atttypid,att->atttypmod,att->attcollation); - /* Register it as a required row-identity column. - * The name becomes the resjunk column name in the plan. - */ + db2Debug(2,"create var rtindex: %d, attrno: %d, typid: %d, typmod: %d, collation: %d",rtindex,attrno,att->atttypid,att->atttypmod,att->attcollation); + /* Register it as a required row-identity column. The name becomes the resjunk column name in the plan. */ add_row_identity_var(root, var, rtindex, key_col_name); - db2Debug2(" add resjunk column %s: %d", key_col_name, rtindex); + db2Debug(2,"add resjunk column %s: %d", key_col_name, rtindex); #endif /* PG_VERSION_NUM */ has_key = true; } @@ -105,5 +102,5 @@ void db2AddForeignUpdateTargets (PlannerInfo* root, Index rtindex,RangeTblEntry* ) ); } - db2Debug1("< db2AddForeignUpdateTargets"); + db2Exit(1,"< db2AddForeignUpdateTargets.c::db2AddForeignUpdateTargets"); } diff --git a/source/db2AllocConnHdl.c b/source/db2AllocConnHdl.c index 90847d4..b392578 100644 --- a/source/db2AllocConnHdl.c +++ b/source/db2AllocConnHdl.c @@ -8,9 +8,9 @@ extern char db2Message[ERRBUFSIZE];/* contains DB2 error messages, set by db2CheckErr() */ /** external prototypes */ -extern void db2Debug1 (const char* message, ...); -extern void db2Debug2 (const char* message, ...); -extern void db2Debug3 (const char* message, ...); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); +extern void db2Debug (int level, const char* message, ...); extern void db2Error_d (db2error sqlstate, const char* message, const char* detail, ...); extern void db2RegisterCallback (void* arg); extern SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleType, int line, char* file); @@ -18,22 +18,20 @@ extern void db2FreeEnvHdl (DB2EnvEntry* envp, const char* nls_lang); extern char* db2strdup (const char* p); /** local prototypes */ -DB2ConnEntry* db2AllocConnHdl (DB2EnvEntry* envp,const char* srvname, char* user, char* password, char* jwt_token, const char* nls_lang); -DB2ConnEntry* findconnEntry (DB2ConnEntry* start, const char* srvname, const char* user, const char* jwttok); -DB2ConnEntry* insertconnEntry (DB2ConnEntry* start, const char* srvname, const char* uid, const char* pwd, const char* jwt_token, SQLHDBC hdbc); + DB2ConnEntry* db2AllocConnHdl (DB2EnvEntry* envp,const char* srvname, char* user, char* password, char* jwt_token, const char* nls_lang); + DB2ConnEntry* findconnEntry (DB2ConnEntry* start, const char* srvname, const char* user, const char* jwttok); +static DB2ConnEntry* insertconnEntry (DB2ConnEntry* start, const char* srvname, const char* uid, const char* pwd, const char* jwt_token, SQLHDBC hdbc); -/** db2AllocConnHdl - * - */ +/* db2AllocConnHdl */ DB2ConnEntry* db2AllocConnHdl(DB2EnvEntry* envp,const char* srvname, char* user, char* password, char* jwt_token, const char* nls_lang) { DB2ConnEntry* connp = NULL; SQLRETURN rc = 0; SQLHDBC hdbc = SQL_NULL_HDBC; - db2Debug1("> db2AllocConnHdl(envp: %x, srvname: %s, user: %s, password: %s, jwt_token: %s, nls_lang: %s)", envp, srvname,user, password, jwt_token ? "***" : "NULL", nls_lang); + db2Entry(1,"> db2AllocConnHdl.c::db2AllocConnHdl(envp: %x, srvname: %s, user: %s, password: %s, jwt_token: %s, nls_lang: %s)", envp, srvname,user, password, jwt_token ? "***" : "NULL", nls_lang); if (nls_lang != NULL) { rc = SQLAllocHandle(SQL_HANDLE_DBC, envp->henv, &hdbc); - db2Debug3(" alloc dbc handle - rc: %d, henv: %d, hdbc: %d",rc, envp->henv, hdbc); + db2Debug(3,"alloc dbc handle - rc: %d, henv: %d, hdbc: %d",rc, envp->henv, hdbc); rc = db2CheckErr(rc, hdbc, SQL_HANDLE_DBC, __LINE__, __FILE__); if (rc != SQL_SUCCESS) { db2Error_d (FDW_UNABLE_TO_ESTABLISH_CONNECTION, "error connecting to DB2: SQLAllocHandle failed to allocate hdbc handle", db2Message); @@ -53,7 +51,7 @@ DB2ConnEntry* db2AllocConnHdl(DB2EnvEntry* envp,const char* srvname, char* user, /* create connection handle */ rc = SQLAllocHandle(SQL_HANDLE_DBC, envp->henv, &hdbc); - db2Debug3(" alloc dbc handle - rc: %d, henv: %d, hdbc: %d",rc, envp->henv, hdbc); + db2Debug(3,"alloc dbc handle - rc: %d, henv: %d, hdbc: %d",rc, envp->henv, hdbc); rc = db2CheckErr(rc, envp->henv, SQL_HANDLE_ENV, __LINE__, __FILE__); if (rc != SQL_SUCCESS) { db2Error_d (FDW_UNABLE_TO_ESTABLISH_CONNECTION, "error connecting to DB2: SQLAllochHandle failed to allocate hdbc handle", db2Message); @@ -62,7 +60,7 @@ DB2ConnEntry* db2AllocConnHdl(DB2EnvEntry* envp,const char* srvname, char* user, /* Check if JWT token authentication is used */ if (jwt_token != NULL && jwt_token[0] != '\0') { /* JWT token authentication */ - db2Debug1(" using JWT token authentication"); + db2Debug(2,"using JWT token authentication"); /* For DB2 11.5.4+ with JWT, use SQLDriverConnect with AUTHENTICATION=TOKEN */ /* Requires: DB2 client 11.5.4+, server configured with db2token.cfg */ @@ -78,14 +76,14 @@ DB2ConnEntry* db2AllocConnHdl(DB2EnvEntry* envp,const char* srvname, char* user, db2Error_d (FDW_UNABLE_TO_ESTABLISH_CONNECTION, "connection string too long", " connection to foreign DB2 server"); } - db2Debug1(" connecting with connection string (token hidden)"); + db2Debug(2,"connecting with connection string (token hidden)"); /* Use SQLDriverConnect instead of SQLConnect */ rc = SQLDriverConnect(hdbc, NULL, (SQLCHAR*)connStr, SQL_NTS, outConnStr, sizeof(outConnStr), &outConnStrLen, SQL_DRIVER_NOPROMPT); - db2Debug1(" connect to database(%s) with JWT token - rc: %d, hdbc: %d", srvname, rc, hdbc); + db2Debug(2,"connect to database(%s) with JWT token - rc: %d, hdbc: %d", srvname, rc, hdbc); rc = db2CheckErr(rc, hdbc, SQL_HANDLE_DBC, __LINE__, __FILE__); if (rc != SQL_SUCCESS) { db2Error_d (FDW_UNABLE_TO_ESTABLISH_CONNECTION, "cannot authenticate with JWT token", " connection connectstring: %s ,%s", srvname, db2Message); @@ -93,9 +91,9 @@ DB2ConnEntry* db2AllocConnHdl(DB2EnvEntry* envp,const char* srvname, char* user, } } else { /* Traditional user/password authentication */ - db2Debug1(" using user/password authentication"); + db2Debug(2,"using user/password authentication"); rc = SQLConnect(hdbc, (SQLCHAR*)srvname, SQL_NTS, (SQLCHAR*)user, SQL_NTS, (SQLCHAR*)password, SQL_NTS); - db2Debug1(" connect to database(%s) - rc: %d, hdbc: %d",srvname, rc, hdbc); + db2Debug(2,"connect to database(%s) - rc: %d, hdbc: %d",srvname, rc, hdbc); rc = db2CheckErr(rc, hdbc, SQL_HANDLE_DBC, __LINE__, __FILE__); if (rc != SQL_SUCCESS) { db2Error_d (FDW_UNABLE_TO_ESTABLISH_CONNECTION, "cannot authenticate"," connection User: %s ,%s" , user , db2Message); @@ -120,16 +118,14 @@ DB2ConnEntry* db2AllocConnHdl(DB2EnvEntry* envp,const char* srvname, char* user, } } } - db2Debug1("< db2AllocConnHdl - returns: %x",connp); + db2Exit(1,"< db2AllocConnHdl.c::db2AllocConnHdl : %x",connp); return connp; } -/** findconnEntry - * - */ +/* findconnEntry */ DB2ConnEntry* findconnEntry(DB2ConnEntry* start, const char* srvname, const char* user, const char* jwttok) { DB2ConnEntry* step = NULL; - db2Debug2(" > findconnEntry"); + db2Entry(2,"> db2AllocConnHdl.c::findconnEntry"); for (step = start; step != NULL; step = step->right){ /* NULL-safe comparison for JWT auth where user may be NULL */ int jwt_null_or_empty = (!step->jwt_token || step->jwt_token[0] == '\0'); @@ -151,18 +147,16 @@ DB2ConnEntry* findconnEntry(DB2ConnEntry* start, const char* srvname, const char break; } } - db2Debug2(" < findconnEntry - returns: %x", step); + db2Exit(2,"< db2AllocConnHdl.c::findconnEntry : %x", step); return step; } -/** insertconnEntry - * - */ -DB2ConnEntry* insertconnEntry(DB2ConnEntry* start, const char* srvname, const char* uid, const char* pwd, const char* jwt_token, SQLHDBC hdbc) { +/* insertconnEntry */ +static DB2ConnEntry* insertconnEntry(DB2ConnEntry* start, const char* srvname, const char* uid, const char* pwd, const char* jwt_token, SQLHDBC hdbc) { DB2ConnEntry* step = NULL; DB2ConnEntry* new = NULL; - db2Debug2(" > insertconnEntry"); + db2Entry(2,"> db2AllocConnHdl.c::insertconnEntry"); new = malloc(sizeof(DB2ConnEntry)); if (start == NULL){ /* first entry in list */ new->right = new->left = NULL; @@ -180,6 +174,6 @@ DB2ConnEntry* insertconnEntry(DB2ConnEntry* start, const char* srvname, const ch new->handlelist = NULL; new->hdbc = hdbc; new->xact_level = 0; - db2Debug2(" < insertconnEntry - returns: %x",new); + db2Exit(2,"< db2AllocConnHdl.c::insertconnEntry : %x",new); return new; } diff --git a/source/db2AllocEnvHdl.c b/source/db2AllocEnvHdl.c index 7d74118..7195a8d 100644 --- a/source/db2AllocEnvHdl.c +++ b/source/db2AllocEnvHdl.c @@ -11,30 +11,28 @@ int sql_initialized = 0; /* set to "1" as soon as SQLAllocHand extern char db2Message[ERRBUFSIZE];/* contains DB2 error messages, set by db2CheckErr() */ /** external prototypes */ +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); +extern void db2Debug (int level, const char* message, ...); extern void db2SetHandlers (void); -extern void db2Debug1 (const char* message, ...); -extern void db2Debug2 (const char* message, ...); -extern void db2Debug3 (const char* message, ...); extern void db2Error_d (db2error sqlstate, const char* message, const char* detail, ...); extern SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleType, int line, char* file); extern char* db2strdup (const char* p); extern void db2free (void* p); /** local prototypes */ -DB2EnvEntry* db2AllocEnvHdl (const char* nls_lang); -void setDB2Environment (char* nls_lang); -DB2EnvEntry* insertenvEntry (DB2EnvEntry* start, const char* nlslang, SQLHENV henv); + DB2EnvEntry* db2AllocEnvHdl (const char* nls_lang); +static void setDB2Environment (char* nls_lang); +static DB2EnvEntry* insertenvEntry (DB2EnvEntry* start, const char* nlslang, SQLHENV henv); -/** db2AllocEnvHdl - * - */ +/* db2AllocEnvHdl */ DB2EnvEntry* db2AllocEnvHdl(const char* nls_lang){ char* nlscopy = NULL; DB2EnvEntry* envp = NULL; SQLHENV henv = SQL_NULL_HENV; SQLRETURN rc = 0; - db2Debug1("> db2AllocEnvHdl"); + db2Entry(1,"> db2AllocEnvHdl.c::db2AllocEnvHdl"); /* create persistent copy of "nls_lang" */ if ((nlscopy = db2strdup (nls_lang)) == NULL) db2Error_d (FDW_OUT_OF_MEMORY, "error connecting to DB2:"," failed to allocate %d bytes of memory", strlen (nls_lang) + 1); @@ -44,7 +42,7 @@ DB2EnvEntry* db2AllocEnvHdl(const char* nls_lang){ /* create environment handle */ rc = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv); - db2Debug3(" allocate env handle - rc: %d, henv: %d",rc, henv); + db2Debug(3,"allocate env handle - rc: %d, henv: %d",rc, henv); rc = db2CheckErr(rc, henv, SQL_HANDLE_ENV, __LINE__, __FILE__); if (rc != SQL_SUCCESS) { db2free (nlscopy); @@ -53,20 +51,18 @@ DB2EnvEntry* db2AllocEnvHdl(const char* nls_lang){ /* we can call db2Shutdown now */ sql_initialized = 1; - db2Debug3(" sql_initialized: %d",sql_initialized); + db2Debug(3,"sql_initialized: %d",sql_initialized); rc = SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3, 0); - db2Debug3(" set env attributes odbcv3 - rc: %d, henv: %d",rc, henv); + db2Debug(3,"set env attributes odbcv3 - rc: %d, henv: %d",rc, henv); rc = db2CheckErr(rc, henv, SQL_HANDLE_ENV, __LINE__, __FILE__); if (rc != SQL_SUCCESS) { db2free (nlscopy); db2Error_d (FDW_UNABLE_TO_ESTABLISH_CONNECTION, "error connecting to DB2: SQLSetEnvAttr failed to set ODBC v3.0", db2Message); } - /* - * DB2 overwrites PostgreSQL's signal handlers, so we have to restore them. - * DB2's SIGINT handler is ok (it cancels the query), but we must do something - * reasonable for SIGTERM. + /* DB2 overwrites PostgreSQL's signal handlers, so we have to restore them. + * DB2's SIGINT handler is ok (it cancels the query), but we must do something reasonable for SIGTERM. */ db2SetHandlers (); /* add handles to cache */ @@ -75,27 +71,27 @@ DB2EnvEntry* db2AllocEnvHdl(const char* nls_lang){ rootenvEntry = envp; } - db2Debug1("< db2AllocEnvHdl - returns: %x",envp); + db2Exit(1,"< db2AllocEnvHdl.c::db2AllocEnvHdl - returns: %x",envp); return envp; } -/** setDB2Environment - * Set environment variables do that DB2 works as we want. +/* setDB2Environment + * Set environment variables do that DB2 works as we want. * - * NLS_LANG sets the language and client encoding - * NLS_NCHAR is unset so that N* data types are converted to the - * character set specified in NLS_LANG. + * NLS_LANG sets the language and client encoding + * NLS_NCHAR is unset so that N* data types are converted to the + * character set specified in NLS_LANG. * - * The following variables are set to values that make DB2 convert - * numeric and date/time values to strings PostgreSQL can parse: - * NLS_DATE_FORMAT - * NLS_TIMESTAMP_FORMAT - * NLS_TIMESTAMP_TZ_FORMAT - * NLS_NUMERIC_CHARACTERS - * NLS_CALENDAR + * The following variables are set to values that make DB2 convert + * numeric and date/time values to strings PostgreSQL can parse: + * NLS_DATE_FORMAT + * NLS_TIMESTAMP_FORMAT + * NLS_TIMESTAMP_TZ_FORMAT + * NLS_NUMERIC_CHARACTERS + * NLS_CALENDAR */ -void setDB2Environment (char* nls_lang) { - db2Debug2(" > setDB2Environment"); +static void setDB2Environment (char* nls_lang) { + db2Entry(4,"> db2AllocEnvHdl.c::setDB2Environment"); if (putenv (nls_lang) != 0) { db2free (nls_lang); db2Error_d (FDW_UNABLE_TO_ESTABLISH_CONNECTION, "error connecting to DB2", "Environment variable NLS_LANG cannot be set."); @@ -137,17 +133,15 @@ void setDB2Environment (char* nls_lang) { db2free (nls_lang); db2Error_d (FDW_UNABLE_TO_ESTABLISH_CONNECTION, "error connecting to DB2", "Environment variable NLS_NCHAR cannot be set."); } - db2Debug2(" < setDB2Environment"); + db2Exit(4,"< db2AllocEnvHdl.c::setDB2Environment"); } -/** insertenvEntry - * - */ -DB2EnvEntry* insertenvEntry(DB2EnvEntry* start, const char* nlslang, SQLHENV henv) { +/* insertenvEntry */ +static DB2EnvEntry* insertenvEntry(DB2EnvEntry* start, const char* nlslang, SQLHENV henv) { DB2EnvEntry* step = NULL; DB2EnvEntry* new = NULL; - db2Debug2(" > insertenvEntry(start: %x, nlslang: '%s', henv: %d)",start, nlslang, henv); + db2Entry(2,"> db2AllocEnvHdl.c::insertenvEntry(start: %x, nlslang: '%s', henv: %d)",start, nlslang, henv); /* allocate a new DB2EnvEntry and initialize it*/ new = malloc(sizeof(DB2EnvEntry)); if (new == NULL) { @@ -167,7 +161,7 @@ DB2EnvEntry* insertenvEntry(DB2EnvEntry* start, const char* nlslang, SQLHENV hen new->left = step; new->right = NULL; } - db2Debug3(" new: %x ->henv: %d, ->connlist: %x, ->left: %x, ->right: %x, ->nls_lang: '%s'",new,new->henv,new->connlist,new->left,new->right,new->nls_lang); - db2Debug2(" < insertenvEntry - returns: %x", new); + db2Debug(3,"new: %x ->henv: %d, ->connlist: %x, ->left: %x, ->right: %x, ->nls_lang: '%s'",new,new->henv,new->connlist,new->left,new->right,new->nls_lang); + db2Exit(2,"< db2AllocEnvHdl.c::insertenvEntry : %x", new); return new; -} +} diff --git a/source/db2AllocStmtHdl.c b/source/db2AllocStmtHdl.c index 737b63e..0d83ab1 100644 --- a/source/db2AllocStmtHdl.c +++ b/source/db2AllocStmtHdl.c @@ -15,82 +15,77 @@ extern DB2EnvEntry* rootenvEntry; /* Linked list of handles for cached DB2 connections. */ /** external prototypes */ -extern void db2Debug1 (const char* message, ...); -extern void db2Debug2 (const char* message, ...); -extern void db2Debug3 (const char* message, ...); -extern void db2Debug5 (const char* message, ...); +extern int isLogLevel (int level); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); +extern void db2Debug (int level, const char* message, ...); extern void db2Error (db2error sqlstate, const char* message); extern void db2Error_d (db2error sqlstate, const char* message, const char* detail, ...); /** inetrnal prototypes */ -HdlEntry* db2AllocStmtHdl (SQLSMALLINT type, DB2ConnEntry* connp, db2error error, const char* errmsg); -void printstruct (void); +HdlEntry* db2AllocStmtHdl (SQLSMALLINT type, DB2ConnEntry* connp, db2error error, const char* errmsg); -/** db2AllocStmtHdl - * Allocate an DB2 statement handle, keep it in the cached list. +/* db2AllocStmtHdl + * Allocate an DB2 statement handle, keep it in the cached list. */ HdlEntry* db2AllocStmtHdl (SQLSMALLINT type, DB2ConnEntry* connp, db2error error, const char* errmsg) { - HdlEntry* entry = NULL; - SQLRETURN rc = 0; + HdlEntry* entry = NULL; + SQLRETURN rc = 0; - db2Debug1("> db2AllocStmtHdl"); - printstruct(); + db2Entry(1,"> db2AllocStmtHdl.c::db2AllocStmtHdl"); + if (isLogLevel(5)) { + DB2EnvEntry* envstep = NULL; + DB2ConnEntry* constep = NULL; + HdlEntry* hdlstep = NULL; + + db2Debug(5,"struct before calling pthread_create getpid: %d getpthread_self: %d", getpid(), (int)pthread_self()); + for (envstep = rootenvEntry; envstep != NULL; envstep = envstep->right){ + db2Debug(5,"EnvEntry : %x",envstep); + db2Debug(5," nls_lang : %s",envstep->nls_lang); + db2Debug(5," step->henv : %x",envstep->henv); + db2Debug(5," step->*left : %x",envstep->left); + db2Debug(5," step->*right : %x",envstep->right); + db2Debug(5," step->*connlist : %x",envstep->connlist); + for (constep = envstep->connlist; constep != NULL; constep = constep->right){ + db2Debug(5," ConnEntr : %x",constep); + db2Debug(5," dbAlias : %s",constep->srvname); + db2Debug(5," user : %s",constep->uid); + db2Debug(5," password : %s",constep->pwd); + db2Debug(5," xact_level : %d",constep->xact_level); + db2Debug(5," conattr : %d",constep->conAttr); + db2Debug(5," *handlelist : %x",constep->handlelist); + db2Debug(5," DB2ConnEntry *left : %x",constep->left); + db2Debug(5," Db2ConnEntry *right : %x",constep->right); + for (hdlstep = constep->handlelist; hdlstep != NULL; hdlstep = hdlstep->next){ + db2Debug(5," HandleEntry : %x",hdlstep); + db2Debug(5," hsql : %d",hdlstep->hsql); + db2Debug(5," type : %d",hdlstep->type); + } + } + } + } /* create entry for linked list */ if ((entry = malloc (sizeof (HdlEntry))) == NULL) { db2Error_d (FDW_OUT_OF_MEMORY, "error allocating handle:"," failed to allocate %d bytes of memory", sizeof (HdlEntry)); } - db2Debug1(" HdlEntry allocated: %x",entry); + db2Debug(2,"HdlEntry allocated: %x",entry); rc = SQLAllocHandle(type, connp->hdbc, &(entry->hsql)); if (rc != SQL_SUCCESS) { - db2Debug3(" SQLAllocHandle not SQL_SUCCESS: %d",rc); - db2Debug1(" HdlEntry freeed: %x",entry); + db2Debug(3,"SQLAllocHandle not SQL_SUCCESS: %d",rc); + db2Debug(2,"HdlEntry freeed: %x",entry); free (entry); entry = NULL; db2Error (error, errmsg); } else { /* add handle to linked list */ - db2Debug3(" entry->hsql: %d",entry->hsql); + db2Debug(3,"entry->hsql: %d",entry->hsql); entry->type = type; - db2Debug3(" entry->type: %d",entry->type); + db2Debug(3,"entry->type: %d",entry->type); entry->next = connp->handlelist; - db2Debug3(" adding connp->handlelist: %x to entry->next: %x",connp->handlelist, entry->next); + db2Debug(3,"adding connp->handlelist: %x to entry->next: %x",connp->handlelist, entry->next); connp->handlelist = entry; - db2Debug3(" set entry %x to start connp->handlelist: %x",entry,connp->handlelist); + db2Debug(3,"set entry %x to start connp->handlelist: %x",entry,connp->handlelist); } - db2Debug1("< db2AllocStmtHdl - returns: %x",entry); + db2Exit(1,"< db2AllocStmtHdl.c::db2AllocStmtHdl : %x",entry); return entry; } - -/** printstruct - * - */ -void printstruct(void) { - DB2EnvEntry* envstep; - DB2ConnEntry* constep; - HdlEntry* hdlstep; - db2Debug5(" printstruct before calling pthread_create getpid: %d getpthread_self: %d", getpid(), (int)pthread_self()); - for (envstep = rootenvEntry; envstep != NULL; envstep = envstep->right){ - db2Debug5(" EnvEntry : %x",envstep); - db2Debug5(" nls_lang : %s",envstep->nls_lang); - db2Debug5(" step->henv : %x",envstep->henv); - db2Debug5(" step->*left : %x",envstep->left); - db2Debug5(" step->*right : %x",envstep->right); - db2Debug5(" step->*connlist : %x",envstep->connlist); - for (constep = envstep->connlist; constep != NULL; constep = constep->right){ - db2Debug5(" ConnEntr : %x",constep); - db2Debug5(" dbAlias : %s",constep->srvname); - db2Debug5(" user : %s",constep->uid); - db2Debug5(" password : %s",constep->pwd); - db2Debug5(" xact_level : %d",constep->xact_level); - db2Debug5(" conattr : %d",constep->conAttr); - db2Debug5(" *handlelist : %x",constep->handlelist); - db2Debug5(" DB2ConnEntry *left : %x",constep->left); - db2Debug5(" Db2ConnEntry *right : %x",constep->right); - for (hdlstep = constep->handlelist; hdlstep != NULL; hdlstep = hdlstep->next){ - db2Debug5(" HandleEntry : %x",hdlstep); - db2Debug5(" hsql : %d",hdlstep->hsql); - db2Debug5(" type : %d",hdlstep->type); - } - } - } -} diff --git a/source/db2AnalyzeForeignTable.c b/source/db2AnalyzeForeignTable.c index d4fba8d..6701db8 100644 --- a/source/db2AnalyzeForeignTable.c +++ b/source/db2AnalyzeForeignTable.c @@ -1,14 +1,13 @@ #include #include -#include #include -#include #include #include #include "db2_fdw.h" #include "DB2FdwState.h" /** external prototypes */ +extern DB2Session* db2GetSession (const char* connectstring, char* user, char* password, char* jwt_token, const char* nls_lang, int curlevel); extern DB2FdwState* db2GetFdwState (Oid foreigntableid, double* sample_percent, bool describe); extern int db2IsStatementOpen (DB2Session* session); extern void db2PrepareQuery (DB2Session* session, const char *query, DB2ResultColumn* resultList, unsigned long prefetch, int fetchsize); @@ -17,24 +16,22 @@ extern int db2FetchNext (DB2Session* session); extern void checkDataType (short db2type, int scale, Oid pgtype, const char* tablename, const char* colname); extern short c2dbType (short fcType); extern void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls, bool trunc_lob) ; -extern void db2Debug1 (const char* message, ...); -extern void db2Debug2 (const char* message, ...); -extern void db2Debug3 (const char* message, ...); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); +extern void db2Debug (int level, const char* message, ...); extern void* db2alloc (const char* type, size_t size); /** local prototypes */ -bool db2AnalyzeForeignTable(Relation relation, AcquireSampleRowsFunc* func, BlockNumber* totalpages); -int acquireSampleRowsFunc (Relation relation, int elevel, HeapTuple* rows, int targrows, double* totalrows, double* totaldeadrows); + bool db2AnalyzeForeignTable(Relation relation, AcquireSampleRowsFunc* func, BlockNumber* totalpages); +static int acquireSampleRowsFunc (Relation relation, int elevel, HeapTuple* rows, int targrows, double* totalrows, double* totaldeadrows); -/* db2AnalyzeForeignTable - * - */ +/* db2AnalyzeForeignTable */ bool db2AnalyzeForeignTable (Relation relation, AcquireSampleRowsFunc* func, BlockNumber* totalpages) { - db2Debug1("> db2AnalyzeForeignTable"); + db2Entry(1,"> db2AnalyzeForeignTable.c::db2AnalyzeForeignTable"); *func = acquireSampleRowsFunc; /* use positive page count as a sign that the table has been ANALYZEd */ *totalpages = 42; - db2Debug1("< db2AnalyzeForeignTable"); + db2Exit(1,"< db2AnalyzeForeignTable.c::db2AnalyzeForeignTable : true"); return true; } @@ -42,7 +39,7 @@ bool db2AnalyzeForeignTable (Relation relation, AcquireSampleRowsFunc* func, Blo * Perform a sequential scan on the DB2 table and return a sample of rows. * All LOB values are truncated to WIDTH_THRESHOLD+1 because anything exceeding this is not used by compute_scalar_stats(). */ -int acquireSampleRowsFunc (Relation relation, int elevel, HeapTuple* rows, int targrows, double* totalrows, double* totaldeadrows) { +static int acquireSampleRowsFunc (Relation relation, int elevel, HeapTuple* rows, int targrows, double* totalrows, double* totaldeadrows) { int collected_rows = 0; DB2FdwState* fdw_state = NULL; bool first_column = true; @@ -56,8 +53,8 @@ int acquireSampleRowsFunc (Relation relation, int elevel, HeapTuple* rows, int t MemoryContext old_cxt; MemoryContext tmp_cxt; - db2Debug1("> acquireSampleRowsFunc"); - elog (DEBUG1, "db2_fdw: analyze foreign table %d", RelationGetRelid (relation)); + db2Entry(1,"> db2AnalyzeForeignTable.c::acquireSampleRowsFunc"); + db2Debug(2,"db2_fdw: analyze foreign table %d", RelationGetRelid (relation)); *totalrows = 0; @@ -69,6 +66,9 @@ int acquireSampleRowsFunc (Relation relation, int elevel, HeapTuple* rows, int t /* get connection options, connect and get the remote table description */ fdw_state = db2GetFdwState (RelationGetRelid (relation), &sample_percent, true); + if (!fdw_state->session) { + fdw_state->session = db2GetSession (fdw_state->dbserver, fdw_state->user, fdw_state->password, fdw_state->jwt_token, fdw_state->nls_lang, GetCurrentTransactionNestLevel () ); + } fdw_state->paramList = NULL; fdw_state->rowcount = 0; @@ -97,9 +97,9 @@ int acquireSampleRowsFunc (Relation relation, int elevel, HeapTuple* rows, int t appendStringInfo (&query, " SAMPLE BLOCK (%f)", sample_percent); fdw_state->query = query.data; - elog (DEBUG2, " fdw_state->query: '%s'", fdw_state->query); + db2Debug(2,"fdw_state->query: '%s'", fdw_state->query); - db2Debug3(" loop through query results"); + db2Debug(3,"loop through query results"); /* loop through query results */ fdw_state->rowcount = -1; while (db2IsStatementOpen (fdw_state->session) ? db2FetchNext (fdw_state->session) : (db2PrepareQuery (fdw_state->session, fdw_state->query, fdw_state->resultList, fdw_state->prefetch, fdw_state->fetch_size), db2ExecuteQuery (fdw_state->session, fdw_state->paramList))) { @@ -148,7 +148,7 @@ int acquireSampleRowsFunc (Relation relation, int elevel, HeapTuple* rows, int t /* report report */ ereport (elevel, (errmsg ("\"%s\": table contains %lu rows; %d rows in sample", RelationGetRelationName (relation), fdw_state->rowcount, collected_rows-1))); - db2Debug1("< acquireSampleRowsFunc"); + db2Exit(1,"< db2AnalyzeForeignTable.c::acquireSampleRowsFunc"); return collected_rows; } diff --git a/source/db2BeginDirectModify.c b/source/db2BeginDirectModify.c index 5557590..99e1792 100644 --- a/source/db2BeginDirectModify.c +++ b/source/db2BeginDirectModify.c @@ -8,11 +8,14 @@ #include "db2_fdw.h" #include "DB2FdwDirectModifyState.h" -extern void* db2alloc (const char* type, size_t size); -extern DB2Session* db2GetSession (const char* connectstring, char* user, char* password, char* jwt_token, const char* nls_lang, int curlevel); -extern DB2FdwDirectModifyState* db2GetFdwDirectModifyState (Oid foreigntableid, double* sample_percent, bool describe); - - void db2BeginDirectModify (ForeignScanState* node, int eflags); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); +extern void db2Debug (int level, const char* message, ...); +extern void* db2alloc (const char* type, size_t size); +extern DB2Session* db2GetSession (const char* connectstring, char* user, char* password, char* jwt_token, const char* nls_lang, int curlevel); +extern DB2FdwDirectModifyState* db2GetFdwDirectModifyState(Oid foreigntableid, double* sample_percent, bool describe); + + void db2BeginDirectModify (ForeignScanState* node, int eflags); static TupleDesc get_tupdesc_for_join_scan_tuples(ForeignScanState* node); static void init_returning_filter (DB2FdwDirectModifyState* dmstate, List* fdw_scan_tlist, Index rtindex); static void prepare_query_params (PlanState* node, List* fdw_exprs, int numParams, FmgrInfo** param_flinfo, List** param_exprs, const char ***param_values); @@ -21,12 +24,13 @@ static void prepare_query_params (PlanState* node, List* fdw_ex * Prepare a direct foreign table modification */ void db2BeginDirectModify(ForeignScanState* node, int eflags) { - ForeignScan* fsplan = (ForeignScan*) node->ss.ps.plan; - EState* estate = node->ss.ps.state; - DB2FdwDirectModifyState* dmstate = NULL; - Index rtindex; - Relation foreigntable; + ForeignScan* fsplan = (ForeignScan*) node->ss.ps.plan; + EState* estate = node->ss.ps.state; + DB2FdwDirectModifyState* dmstate = NULL; + Index rtindex; + Relation foreigntable; + db2Entry(1,"> db2BeginDirectModify.c::db2BeginDirectModify"); /* Do nothing in EXPLAIN (no ANALYZE) case. node->fdw_state stays NULL. */ if (!(eflags & EXEC_FLAG_EXPLAIN_ONLY)) { /* Get info about foreign table. */ @@ -42,7 +46,7 @@ void db2BeginDirectModify(ForeignScanState* node, int eflags) { /* Save info about foreign table. */ dmstate->resultRel = dmstate->rel; - /* Set dmstate->rel to NULL to teach get_returning_data() and make_tuple_from_result_row() + /* Set dmstate->rel to NULL to teach get_returning_data() and make_tuple_from_result_row() * that columns fetched from the remote server are described by fdw_scan_tlist of the * foreign-scan plan node, not the tuple descriptor for the target relation. */ @@ -91,6 +95,7 @@ void db2BeginDirectModify(ForeignScanState* node, int eflags) { ); } } + db2Exit(1,"< db2BeginDirectModify.c::db2BeginDirectModify"); } /* @@ -101,33 +106,24 @@ static TupleDesc get_tupdesc_for_join_scan_tuples(ForeignScanState *node) { EState *estate = node->ss.ps.state; TupleDesc tupdesc; - /* - * The core code has already set up a scan tuple slot based on - * fsplan->fdw_scan_tlist, and this slot's tupdesc is mostly good enough, - * but there's one case where it isn't. If we have any whole-row row - * identifier Vars, they may have vartype RECORD, and we need to replace - * that with the associated table's actual composite type. This ensures - * that when we read those ROW() expression values from the remote server, - * we can convert them to a composite type the local server knows. + db2Entry(4,"> db2BeginDirectModify.c::get_tupdesc_for_join_scan_tuples"); + /* The core code has already set up a scan tuple slot based on fsplan->fdw_scan_tlist, and this slot's tupdesc is mostly good enough, but there's one case where it isn't. + * If we have any whole-row row identifier Vars, they may have vartype RECORD, and we need to replace that with the associated table's actual composite type. + * This ensures that when we read those ROW() expression values from the remote server, we can convert them to a composite type the local server knows. */ tupdesc = CreateTupleDescCopy(node->ss.ss_ScanTupleSlot->tts_tupleDescriptor); - for (int i = 0; i < tupdesc->natts; i++) - { + for (int i = 0; i < tupdesc->natts; i++) { Form_pg_attribute att = TupleDescAttr(tupdesc, i); - Var *var; - RangeTblEntry *rte; - Oid reltype; + Var* var; + RangeTblEntry* rte; + Oid reltype; /* Nothing to do if it's not a generic RECORD attribute */ if (att->atttypid != RECORDOID || att->atttypmod >= 0) continue; - /* - * If we can't identify the referenced table, do nothing. This'll - * likely lead to failure later, but perhaps we can muddle through. - */ - var = (Var *) list_nth_node(TargetEntry, fsplan->fdw_scan_tlist, - i)->expr; + /* If we can't identify the referenced table, do nothing. This'll likely lead to failure later, but perhaps we can muddle through. */ + var = (Var *) list_nth_node(TargetEntry, fsplan->fdw_scan_tlist, i)->expr; if (!IsA(var, Var) || var->varattno != 0) continue; rte = list_nth(estate->es_range_table, var->varno - 1); @@ -139,15 +135,17 @@ static TupleDesc get_tupdesc_for_join_scan_tuples(ForeignScanState *node) { att->atttypid = reltype; /* shouldn't need to change anything else */ } + db2Exit(4,"< db2BeginDirectModify.c::get_tupdesc_for_join_scan_tuples"); return tupdesc; } /* Initialize a filter to extract an updated/deleted tuple from a scan tuple. */ static void init_returning_filter(DB2FdwDirectModifyState* dmstate, List* fdw_scan_tlist, Index rtindex) { - TupleDesc resultTupType = RelationGetDescr(dmstate->resultRel); + TupleDesc resultTupType = RelationGetDescr(dmstate->resultRel); ListCell* lc = NULL; - int i = 0; + int i = 0; + db2Entry(4,"< db2BeginDirectModify.c::init_returning_filter"); /* Calculate the mapping between the fdw_scan_tlist's entries and the result tuple's attributes. * * The "map" is an array of indexes of the result tuple's attributes in fdw_scan_tlist, i.e., one entry for every attribute @@ -186,38 +184,41 @@ static void init_returning_filter(DB2FdwDirectModifyState* dmstate, List* fdw_sc } i++; } + db2Exit(4,"< db2BeginDirectModify.c::init_returning_filter"); } /* Prepare for processing of parameters used in remote query. */ static void prepare_query_params(PlanState* node, List* fdw_exprs, int numParams, FmgrInfo** param_flinfo, List** param_exprs, const char ***param_values) { - int i = 0; - ListCell* lc = NULL; + int i = 0; + ListCell* lc = NULL; - Assert(numParams > 0); + db2Entry(4,"> db2BeginDirectModify.c::prepare_query_params"); + Assert(numParams > 0); - /* Prepare for output conversion of parameters used in remote query. */ - *param_flinfo = palloc0_array(FmgrInfo, numParams); + /* Prepare for output conversion of parameters used in remote query. */ + *param_flinfo = palloc0_array(FmgrInfo, numParams); - i = 0; - foreach(lc, fdw_exprs) { - Node* param_expr = (Node *) lfirst(lc); - Oid typefnoid; - bool isvarlena; + i = 0; + foreach(lc, fdw_exprs) { + Node* param_expr = (Node *) lfirst(lc); + Oid typefnoid; + bool isvarlena; - getTypeOutputInfo(exprType(param_expr), &typefnoid, &isvarlena); - fmgr_info(typefnoid, &(*param_flinfo)[i]); - i++; - } + getTypeOutputInfo(exprType(param_expr), &typefnoid, &isvarlena); + fmgr_info(typefnoid, &(*param_flinfo)[i]); + i++; + } - /* Prepare remote-parameter expressions for evaluation. + /* Prepare remote-parameter expressions for evaluation. * (Note: in practice, we expect that all these expressions will be just Params, so we could possibly do something * more efficient than using the full expression-eval machinery for this. * But probably there would be little benefit, and it'd require postgres_fdw to know more than is desirable - * about Param evaluation.) - */ - *param_exprs = ExecInitExprList(fdw_exprs, node); + * about Param evaluation.) + */ + *param_exprs = ExecInitExprList(fdw_exprs, node); - /* Allocate buffer for text form of query parameters. */ - *param_values = (const char **) db2alloc("prepare_query_params::param_values",numParams * sizeof(char *)); + /* Allocate buffer for text form of query parameters. */ + *param_values = (const char **) db2alloc("prepare_query_params::param_values",numParams * sizeof(char *)); + db2Exit(4,"< db2BeginDirectModify.c::prepare_query_params"); } diff --git a/source/db2BeginForeignInsert.c b/source/db2BeginForeignInsert.c index 942e87c..888c5aa 100644 --- a/source/db2BeginForeignInsert.c +++ b/source/db2BeginForeignInsert.c @@ -3,7 +3,6 @@ #include #include #include -#include #include #include #include "db2_fdw.h" @@ -14,8 +13,9 @@ extern DB2FdwState* db2GetFdwState (Oid foreigntableid, double* sample_percent, bool describe); extern void addParam (ParamDesc** paramList, DB2Column* db2col, int colnum, int txts); extern void checkDataType (short db2type, int scale, Oid pgtype, const char* tablename, const char* colname); -extern void db2Debug1 (const char* message, ...); -extern void db2Debug2 (const char* message, ...); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); +extern void db2Debug (int level, const char* message, ...); extern void appendAsType (StringInfoData* dest, Oid type); extern void db2BeginForeignModifyCommon(ModifyTableState* mtstate, ResultRelInfo* rinfo, DB2FdwState* fdw_state, Plan* subplan); @@ -27,11 +27,11 @@ void db2BeginForeignInsert(ModifyTableState* mtstate, ResultRelInfo* rinfo) { Relation rel = rinfo->ri_RelationDesc; DB2FdwState* fdw_state = NULL; - db2Debug1("> db2BeginForeignInsert"); + db2Entry(1," db2BeginForeignInsert.c::db2BeginForeignInsert"); fdw_state = db2BuildInsertFdwState(rel); /* subplan is irrelevant for pure INSERT/COPY */ db2BeginForeignModifyCommon(mtstate, rinfo, fdw_state, NULL); - db2Debug1("< db2BeginForeignInsert"); + db2Exit(1,"< db2BeginForeignInsert.c::db2BeginForeignInsert"); } DB2FdwState* db2BuildInsertFdwState(Relation rel) { @@ -40,7 +40,7 @@ DB2FdwState* db2BuildInsertFdwState(Relation rel) { int i; bool firstcol; - db2Debug1("> db2BuildInsertFdwState"); + db2Entry(1," db2BeginForeignInsert.c::db2BuildInsertFdwState"); /* Same logic as CMD_INSERT branch of db2PlanForeignModify: */ fdwState = db2GetFdwState(RelationGetRelid(rel), NULL, true); initStringInfo(&sql); @@ -72,7 +72,7 @@ DB2FdwState* db2BuildInsertFdwState(Relation rel) { } appendStringInfo(&sql, ")"); fdwState->query = sql.data; - db2Debug2(" fdwState->query: '%s'",sql.data); - db2Debug1("< db2BuildInsertFdwState - returns fdwState: %x",fdwState); + db2Debug(2,"fdwState->query: '%s'",sql.data); + db2Exit(1,"< db2BeginForeignInsert.c::db2BuildInsertFdwState - returns fdwState: %x",fdwState); return fdwState; } \ No newline at end of file diff --git a/source/db2BeginForeignModify.c b/source/db2BeginForeignModify.c index 95b0268..009892d 100644 --- a/source/db2BeginForeignModify.c +++ b/source/db2BeginForeignModify.c @@ -1,30 +1,26 @@ #include #include -#include #include "db2_fdw.h" #include "DB2FdwState.h" -/** external variables */ - /** external prototypes */ -extern void db2Debug1 (const char* message, ...); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); extern void db2BeginForeignModifyCommon(ModifyTableState* mtstate, ResultRelInfo* rinfo, DB2FdwState* fdw_state, Plan* subplan); extern DB2FdwState* deserializePlanData (List* list); /** local prototypes */ -void db2BeginForeignModify(ModifyTableState* mtstate, ResultRelInfo* rinfo, List* fdw_private, int subplan_index, int eflags); +void db2BeginForeignModify (ModifyTableState* mtstate, ResultRelInfo* rinfo, List* fdw_private, int subplan_index, int eflags); -/** db2BeginForeignModify - * Prepare everything for the DML query: - * The SQL statement is prepared, the type output functions for - * the parameters are fetched, and the column numbers of the - * resjunk attributes are stored in the "pkey" field. +/* db2BeginForeignModify + * Prepare everything for the DML query: + * The SQL statement is prepared, the type output functions for the parameters are fetched, and the column numbers of the resjunk attributes are stored in the "pkey" field. */ void db2BeginForeignModify (ModifyTableState* mtstate, ResultRelInfo* rinfo, List* fdw_private, int subplan_index, int eflags) { DB2FdwState* fdw_state = deserializePlanData (fdw_private); Plan *subplan = NULL; - db2Debug1("> db2BeginForeignModify"); + db2Entry(1,"> db2BeginForeignModify.c::db2BeginForeignModify"); #if PG_VERSION_NUM < 140000 subplan = mtstate->mt_plans[subplan_index]->plan; #else @@ -32,4 +28,5 @@ void db2BeginForeignModify (ModifyTableState* mtstate, ResultRelInfo* rinfo, Lis #endif db2BeginForeignModifyCommon(mtstate, rinfo, fdw_state, subplan); + db2Exit(1,"< db2BeginForeignModify.c::db2BeginForeignModify"); } \ No newline at end of file diff --git a/source/db2BeginForeignModifyCommon.c b/source/db2BeginForeignModifyCommon.c index 1d84e3a..ab0483a 100644 --- a/source/db2BeginForeignModifyCommon.c +++ b/source/db2BeginForeignModifyCommon.c @@ -3,7 +3,6 @@ #include #include #include -#include #include #include #include @@ -16,7 +15,8 @@ extern regproc* output_funcs; /** external prototypes */ extern DB2Session* db2GetSession (const char* connectstring, char* user, char* password, char* jwt_token, const char* nls_lang, int curlevel); extern void db2PrepareQuery (DB2Session* session, const char *query, DB2ResultColumn* db2ResultList, unsigned long prefetch, int fetchsize); -extern void db2Debug1 (const char* message, ...); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); extern void* db2alloc (const char* type, size_t size); /** local prototypes */ @@ -28,7 +28,7 @@ void db2BeginForeignModifyCommon(ModifyTableState* mtstate, ResultRelInfo* rinfo HeapTuple tuple; int i; - db2Debug1("> db2BeginForeignModifyCommon"); + db2Entry(1,"> db2BeginForeignModifyCommon.c::db2BeginForeignModifyCommon"); rinfo->ri_FdwState = fdw_state; /* connect to DB2 database */ @@ -60,5 +60,5 @@ void db2BeginForeignModifyCommon(ModifyTableState* mtstate, ResultRelInfo* rinfo /* create a memory context for short-lived memory */ fdw_state->temp_cxt = AllocSetContextCreate(estate->es_query_cxt, "db2_fdw temporary data", ALLOCSET_SMALL_MINSIZE, ALLOCSET_SMALL_INITSIZE, ALLOCSET_SMALL_MAXSIZE); - db2Debug1("< db2BeginForeignModifyCommon"); + db2Exit(1,"< db2BeginForeignModifyCommon.c::db2BeginForeignModifyCommon"); } diff --git a/source/db2BeginForeignScan.c b/source/db2BeginForeignScan.c index 15ade7c..a634f37 100644 --- a/source/db2BeginForeignScan.c +++ b/source/db2BeginForeignScan.c @@ -1,7 +1,6 @@ #include #include #include -#include #include #include #include @@ -12,24 +11,23 @@ extern DB2Session* db2GetSession (const char* connectstring, char* user, char* password, char* jwt_token, const char* nls_lang, int curlevel); extern void* db2alloc (const char* type, size_t size); extern DB2FdwState* deserializePlanData (List* list); -extern void db2Debug1 (const char* message, ...); -extern void db2Debug2 (const char* message, ...); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); +extern void db2Debug (int level, const char* message, ...); /** local prototypes */ void db2BeginForeignScan (ForeignScanState* node, int eflags); static void addExprParams (ForeignScanState* node); -/** db2BeginForeignScan - * Recover ("deserialize") connection information, remote query, - * DB2 table description and parameter list from the plan's - * "fdw_private" field. - * Reestablish a connection to DB2. +/* db2BeginForeignScan + * Recover ("deserialize") connection information, remote query, DB2 table description and parameter list from the plan's "fdw_private" field. + * Reestablish a connection to DB2. */ void db2BeginForeignScan(ForeignScanState* node, int eflags) { ForeignScan* fsplan = (ForeignScan*) node->ss.ps.plan; DB2FdwState* fdw_state = NULL; - db2Debug1("> db2BeginForeignScan"); + db2Entry(1,"> db2BeginForeignScan.c::db2BeginForeignScan"); /* deserialize private plan data */ fdw_state = deserializePlanData(fsplan->fdw_private); node->fdw_state = (void *) fdw_state; @@ -50,9 +48,9 @@ void db2BeginForeignScan(ForeignScanState* node, int eflags) { } if (node->ss.ss_currentRelation) - elog (DEBUG3, " begin foreign table scan on relid: %d", RelationGetRelid (node->ss.ss_currentRelation)); + db2Debug(3,"begin foreign table scan on relid: %d", RelationGetRelid (node->ss.ss_currentRelation)); else - elog (DEBUG3, " begin foreign join"); + db2Debug(3,"begin foreign join"); /* connect to DB2 database */ fdw_state->session = db2GetSession (fdw_state->dbserver @@ -65,7 +63,7 @@ void db2BeginForeignScan(ForeignScanState* node, int eflags) { /* initialize row count to zero */ fdw_state->rowcount = 0; - db2Debug1("< db2BeginForeignScan"); + db2Exit(1,"< db2BeginForeignScan.c::db2BeginForeignScan"); } static void addExprParams(ForeignScanState* node){ @@ -75,10 +73,10 @@ static void addExprParams(ForeignScanState* node){ ParamDesc* paramDesc = NULL; ListCell* cell = NULL; - db2Debug1("> addExprParams"); + db2Entry(1,"> db2BeginForeignScan.c::addExprParams"); /* create an ExprState tree for the parameter expressions */ exec_exprs = (List*) ExecInitExprList (fsplan->fdw_exprs, (PlanState*) node); - db2Debug2(" exec_expr: %x[%d]",exec_exprs, list_length(exec_exprs)); + db2Debug(2,"exec_expr: %x[%d]",exec_exprs, list_length(exec_exprs)); /* create the list of parameters */ foreach (cell, exec_exprs) { ExprState* expr = (ExprState*) lfirst (cell); @@ -109,8 +107,8 @@ static void addExprParams(ForeignScanState* node){ paramDesc->colnum = -1; paramDesc->txts = 0; paramDesc->next = fdw_state->paramList; - db2Debug2(" paramDesc->colnum: %d ",paramDesc->colnum); + db2Debug(2,"paramDesc->colnum: %d ",paramDesc->colnum); fdw_state->paramList = paramDesc; } - db2Debug1("< addExprParams"); + db2Exit(1,"< db2BeginForeignScan.c::addExprParams"); } diff --git a/source/db2BindParameter.c b/source/db2BindParameter.c index bd326ec..31420ab 100644 --- a/source/db2BindParameter.c +++ b/source/db2BindParameter.c @@ -12,9 +12,9 @@ extern char db2Message[ERRBUFSIZE];/* contains DB2 error messages, set b /** external prototypes */ extern void* db2alloc (const char* type, size_t size); -extern void db2Debug1 (const char* message, ...); -extern void db2Debug2 (const char* message, ...); -extern void db2Debug3 (const char* message, ...); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); +extern void db2Debug (int level, const char* message, ...); extern SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleType, int line, char* file); extern void db2Error_d (db2error sqlstate, const char* message, const char* detail, ...); extern SQLSMALLINT param2c (SQLSMALLINT fcType); @@ -26,21 +26,21 @@ void db2BindParameter (DB2Session* session, ParamDesc* param, SQLLEN* indicator, void db2BindParameter (DB2Session* session, ParamDesc* param, SQLLEN* indicator, int param_count, int col_num) { SQLRETURN rc = 0; - db2Debug1("> db2BindParameter"); - db2Debug2(" param_count : %d",param_count); - db2Debug2(" col_num : %d",col_num); - db2Debug2(" param->value : %s",param->value); - db2Debug2(" param->colnum : %d",param->colnum); - db2Debug2(" param->bindType : %d",param->bindType); + db2Entry(1,"> db2BindParameter.c::db2BindParameter"); + db2Debug(2,"param_count : %d",param_count); + db2Debug(2,"col_num : %d",col_num); + db2Debug(2,"param->value : %s",param->value); + db2Debug(2,"param->colnum : %d",param->colnum); + db2Debug(2,"param->bindType : %d",param->bindType); if (param->colnum >= 0) { - db2Debug2(" colName : %s",param->colName); + db2Debug(2,"colName : %s",param->colName); } switch (param->bindType) { case BIND_NUMBER: { - db2Debug3(" param->bindType: BIND_NUMBER"); + db2Debug(3,"param->bindType: BIND_NUMBER"); *indicator = (SQLLEN) ((param->value == NULL) ? SQL_NULL_DATA : 0); - db2Debug2(" param_ind : %d",*indicator); - db2Debug2(" colType : %d - %s",param->colType,c2name(param->colType)); + db2Debug(2,"param_ind : %d",*indicator); + db2Debug(2,"colType : %d - %s",param->colType,c2name(param->colType)); switch (param->colType) { case SQL_BIGINT:{ char* end = NULL; @@ -48,7 +48,7 @@ void db2BindParameter (DB2Session* session, ParamDesc* param, SQLLEN* indicator, if (param->value != NULL) { sqlbint = db2alloc("SQLBIGINT",sizeof(SQLBIGINT)); *sqlbint = strtoll(param->value,&end,10); - db2Debug2(" sqlbint: %d",*sqlbint); + db2Debug(2,"sqlbint: %d",*sqlbint); } rc = SQLBindParameter( session->stmtp->hsql , col_num @@ -69,7 +69,7 @@ void db2BindParameter (DB2Session* session, ParamDesc* param, SQLLEN* indicator, if (param->value != NULL) { sqlsint = db2alloc("SQLSMALLINT",sizeof(SQLSMALLINT)); *sqlsint = strtol(param->value,&end,10); - db2Debug2(" sqlsint: %d",*sqlsint); + db2Debug(2,"sqlsint: %d",*sqlsint); } rc = SQLBindParameter( session->stmtp->hsql , col_num @@ -90,7 +90,7 @@ void db2BindParameter (DB2Session* session, ParamDesc* param, SQLLEN* indicator, if (param->value != NULL) { sqlint = db2alloc("SQLINTEGER",sizeof(SQLINTEGER)); *sqlint = strtol(param->value,&end,10); - db2Debug2(" sqlint: %d",*sqlint); + db2Debug(2,"sqlint: %d",*sqlint); } rc = SQLBindParameter( session->stmtp->hsql , col_num @@ -115,7 +115,7 @@ void db2BindParameter (DB2Session* session, ParamDesc* param, SQLLEN* indicator, if (param->value != NULL) { num = db2alloc("SQL_NUMERIC_STRUCT",sizeof(SQL_NUMERIC_STRUCT)); parse2num_struct(param->value, num); - db2Debug2(" num: '%s'",*num); + db2Debug(2,"num: '%s'",*num); } rc = SQLBindParameter( session->stmtp->hsql , col_num @@ -139,9 +139,9 @@ void db2BindParameter (DB2Session* session, ParamDesc* param, SQLLEN* indicator, } break; case BIND_STRING: { - db2Debug3(" param->bindType: BIND_STRING"); + db2Debug(3,"param->bindType: BIND_STRING"); *indicator = (SQLLEN) ((param->value == NULL) ? SQL_NULL_DATA : SQL_NTS); - db2Debug2(" param_ind : %d",*indicator); + db2Debug(2,"param_ind : %d",*indicator); rc = SQLBindParameter( session->stmtp->hsql , col_num , SQL_PARAM_INPUT @@ -156,9 +156,9 @@ void db2BindParameter (DB2Session* session, ParamDesc* param, SQLLEN* indicator, } break; case BIND_LONGRAW: { - db2Debug3(" param->bindType: BIND_LONGRAW"); + db2Debug(3,"param->bindType: BIND_LONGRAW"); *indicator = (SQLLEN) ((param->value == NULL) ? SQL_NULL_DATA : SQL_NTS); - db2Debug2(" param_ind : %d",*indicator); + db2Debug(2,"param_ind : %d",*indicator); rc = SQLBindParameter( session->stmtp->hsql , col_num , SQL_PARAM_INPUT @@ -173,10 +173,10 @@ void db2BindParameter (DB2Session* session, ParamDesc* param, SQLLEN* indicator, } break; case BIND_LONG: { - db2Debug3(" param->bindType: BIND_LONG"); + db2Debug(3,"param->bindType: BIND_LONG"); *indicator = (SQLLEN) ((param->value == NULL) ? SQL_NULL_DATA : SQL_NTS); - db2Debug2(" param_ind : %d",*indicator); - db2Debug2(" param->value : '%s'",param->value); + db2Debug(2,"param_ind : %d",*indicator); + db2Debug(2,"param->value : '%s'",param->value); rc = SQLBindParameter( session->stmtp->hsql , col_num , SQL_PARAM_INPUT @@ -193,9 +193,9 @@ void db2BindParameter (DB2Session* session, ParamDesc* param, SQLLEN* indicator, case BIND_OUTPUT: { SQLSMALLINT fcType; SQLSMALLINT fParamType; - db2Debug2(" param->bindType: BIND_OUTPUT"); + db2Debug(2,"param->bindType: BIND_OUTPUT"); *indicator = (SQLLEN) ((param->value == NULL) ? SQL_NULL_DATA : 0); - db2Debug2(" param_ind : %d",*indicator); + db2Debug(2,"param_ind : %d",*indicator); if (param->type == UUIDOID) { /* the type input function will interpret the string value correctly */ fcType = SQL_CHAR; @@ -222,5 +222,5 @@ void db2BindParameter (DB2Session* session, ParamDesc* param, SQLLEN* indicator, if (rc != SQL_SUCCESS) { db2Error_d(FDW_UNABLE_TO_CREATE_EXECUTION, "error executing query: SQLBindParameter failed to bind parameter", db2Message); } - db2Debug1("< db2BindParameter"); + db2Exit(1,"< db2BindParameter.c::db2BindParameter"); } \ No newline at end of file diff --git a/source/db2Callbacks.c b/source/db2Callbacks.c index bf518a3..be6b5d7 100644 --- a/source/db2Callbacks.c +++ b/source/db2Callbacks.c @@ -8,7 +8,8 @@ extern bool dml_in_transaction; /** external prototypes */ extern void db2EndTransaction (void* arg, int is_commit, int noerror); extern void db2EndSubtransaction (void* arg, int nest_level, int is_commit); -extern void db2Debug1 (const char* message, ...); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); /** local prototypes */ void db2RegisterCallback (void* arg); @@ -16,31 +17,31 @@ void db2UnregisterCallback (void* arg); void transactionCallback (XactEvent event, void *arg); void subtransactionCallback(SubXactEvent event, SubTransactionId mySubid, SubTransactionId parentSubid, void* arg); -/** db2RegisterCallback - * Register a callback for PostgreSQL transaction events. +/* db2RegisterCallback + * Register a callback for PostgreSQL transaction events. */ void db2RegisterCallback (void *arg) { - db2Debug1("> db2RegisterCallback(%x)",arg); + db2Entry(1,"> db2Callbacks.c::db2RegisterCallback(%x)",arg); RegisterXactCallback (transactionCallback, arg); RegisterSubXactCallback (subtransactionCallback, arg); - db2Debug1("< db2RegisterCallback"); + db2Exit(1,"< db2Callbacks.c::db2RegisterCallback"); } -/** db2UnregisterCallback - * Unregister a callback for PostgreSQL transaction events. +/* db2UnregisterCallback + * Unregister a callback for PostgreSQL transaction events. */ void db2UnregisterCallback (void *arg) { - db2Debug1("> db2UnregisterCallback(%x)",arg); + db2Entry(1,"> db2Callbacks.c::db2UnregisterCallback(%x)",arg); UnregisterXactCallback (transactionCallback, arg); UnregisterSubXactCallback (subtransactionCallback, arg); - db2Debug1("< db2UnregisterCallback"); + db2Exit(1,"< db2Callbacks.c::db2UnregisterCallback"); } -/** transactionCallback - * Commit or rollback DB2 transactions when appropriate. +/* transactionCallback + * Commit or rollback DB2 transactions when appropriate. */ void transactionCallback (XactEvent event, void *arg) { - db2Debug1("> transactionCallback(Event %d, Arg %x)", event, arg); + db2Entry(1,"> db2Callbacks.c::transactionCallback(Event %d, Arg %x)", event, arg); switch (event) { case XACT_EVENT_PRE_COMMIT: case XACT_EVENT_PARALLEL_PRE_COMMIT: @@ -67,16 +68,16 @@ void transactionCallback (XactEvent event, void *arg) { break; } dml_in_transaction = false; - db2Debug1("< transactionCallback"); + db2Exit(1,"< db2Callbacks.c::transactionCallback"); } -/** subtransactionCallback - * Set or rollback to DB2 savepoints when appropriate. +/* subtransactionCallback + * Set or rollback to DB2 savepoints when appropriate. */ void subtransactionCallback (SubXactEvent event, SubTransactionId mySubid, SubTransactionId parentSubid, void *arg) { - db2Debug1("> subtransactionCallback"); + db2Entry(1,"> db2Callbacks.c::subtransactionCallback"); /* rollback to the appropriate savepoint on subtransaction abort */ if (event == SUBXACT_EVENT_ABORT_SUB || event == SUBXACT_EVENT_PRE_COMMIT_SUB) db2EndSubtransaction (arg, GetCurrentTransactionNestLevel (), event == SUBXACT_EVENT_PRE_COMMIT_SUB); - db2Debug1("< subtransactionCallback"); + db2Exit(1,"< db2Callbacks.c::subtransactionCallback"); } diff --git a/source/db2Cancel.c b/source/db2Cancel.c index 432d7c9..80b5570 100644 --- a/source/db2Cancel.c +++ b/source/db2Cancel.c @@ -8,7 +8,8 @@ extern DB2EnvEntry* rootenvEntry; /* contains DB2 error messages, set by db2CheckErr() */ /** external prototypes */ -extern void db2Debug1 (const char* message, ...); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); /** local prototypes */ void db2Cancel (void); @@ -17,11 +18,11 @@ void db2Cancel (void); * Cancel all running DB2 queries. */ void db2Cancel (void) { - DB2EnvEntry* envp ; - DB2ConnEntry* connp ; - HdlEntry* entryp; + DB2EnvEntry* envp = NULL; + DB2ConnEntry* connp = NULL; + HdlEntry* entryp = NULL; - db2Debug1("> db2Cancel"); + db2Entry(1,"> db2Cancel.c::db2Cancel"); /* send a cancel request for all servers ignoring errors */ for (envp = rootenvEntry; envp != NULL; envp = envp->right) { for (connp = envp->connlist; connp != NULL; connp = connp->right) { @@ -32,5 +33,5 @@ void db2Cancel (void) { } } } - db2Debug1("< db2Cancel"); + db2Exit(1,"< db2Cancel.c::db2Cancel"); } diff --git a/source/db2CheckErr.c b/source/db2CheckErr.c index 5c076eb..b5d3343 100644 --- a/source/db2CheckErr.c +++ b/source/db2CheckErr.c @@ -11,30 +11,31 @@ char db2Message[ERRBUFSIZE];/* contains DB2 error messages, set b /** external variables */ /** external prototypes */ -extern void db2Debug4 (const char* message, ...); -extern void db2Debug5 (const char* message, ...); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); +extern void db2Debug (int level, const char* message, ...); /** local prototypes */ SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleType, int line, char* file); -/** db2CheckErr - * Call SQLGetDiagRec to get sqlcode, sqlstate and db2 error message. - * It sets the global err_code with a value, so subsequent code can evaluate. - * It populates the db2Message with SQLCODE, SQLSTATE and the DB2 message text. - * It modifys the result to SQL_SUCCESS in case the status was SQL_SUCCESS_WITH_INFO. - * It sets err_code to 100 upon SQL_NO_DATA. +/* db2CheckErr + * Call SQLGetDiagRec to get sqlcode, sqlstate and db2 error message. + * It sets the global err_code with a value, so subsequent code can evaluate. + * It populates the db2Message with SQLCODE, SQLSTATE and the DB2 message text. + * It modifys the result to SQL_SUCCESS in case the status was SQL_SUCCESS_WITH_INFO. + * It sets err_code to 100 upon SQL_NO_DATA. * - * @param status the returncode from a previous executed SQL API call - * @param handle the handle used in that previous SQL API call - * @param handleType the type of handle used (HENV, HDBC, HSTMT, etc) - * @param line the source-code-line db2CheckErr was invoked from - * @param file the name of the sourcefile db2CheckErr was invoked from + * @param status the returncode from a previous executed SQL API call + * @param handle the handle used in that previous SQL API call + * @param handleType the type of handle used (HENV, HDBC, HSTMT, etc) + * @param line the source-code-line db2CheckErr was invoked from + * @param file the name of the sourcefile db2CheckErr was invoked from * - * @return SQLRETURN passing back the status, which in cases is modified - * @since 1.0.0 + * @return SQLRETURN passing back the status, which in cases is modified + * @since 1.0.0 */ SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleType, int line, char* file) { - db2Debug4("> db2CheckErr"); + db2Entry(4,"> db2CheckErr.c::db2CheckErr"); memset (db2Message,0x00,sizeof(db2Message)); switch (status) { case SQL_INVALID_HANDLE: { @@ -54,9 +55,9 @@ SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleTyp memset(message ,0x00,SQL_MAX_MESSAGE_LENGTH); while (SQL_SUCCEEDED(SQLGetDiagRec(handleType,handle,i,sqlstate,&sqlcode,message,SQL_MAX_MESSAGE_LENGTH,&msgLen))) { - db2Debug5(" SQLCODE : %d ",sqlcode); - db2Debug5(" SQLSTATE: %d ",sqlstate); - db2Debug5(" MESSAGE : '%s'",message); + db2Debug(5,"SQLCODE : %d ",sqlcode); + db2Debug(5,"SQLSTATE: %d ",sqlstate); + db2Debug(5,"MESSAGE : '%s'",message); snprintf((char*)submessage, SUBMESSAGE_LEN, "SQLSTATE = %s SQLCODE = %d\nline=%d\nfile=%s\n", sqlstate,sqlcode,line,file); if ((sizeof(db2Message) - strlen((char*)db2Message)) > strlen((char*)submessage) + 1) { strncat ((char*)db2Message,(char*)submessage, SUBMESSAGE_LEN); @@ -85,8 +86,8 @@ SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleTyp } break; } - db2Debug5(" db2Message: '%s'",db2Message); - db2Debug5(" err_code : %d ",err_code); - db2Debug4("< db2CheckErr - returns: %d",status); + db2Debug(5,"db2Message: '%s'",db2Message); + db2Debug(5,"err_code : %d ",err_code); + db2Exit(4,"< db2CheckErr.c::db2CheckErr - returns: %d",status); return status; } diff --git a/source/db2ClientVersion.c b/source/db2ClientVersion.c index 95732b8..587c4cc 100644 --- a/source/db2ClientVersion.c +++ b/source/db2ClientVersion.c @@ -9,24 +9,25 @@ /** external variables */ /** external prototypes */ -extern void db2Debug1 (const char* message, ...); -extern void db2Debug2 (const char* message, ...); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); +extern void db2Debug (int level, const char* message, ...); extern SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleType, int line, char* file); /** local prototypes */ void db2ClientVersion (DB2Session* session, char* version); -/** db2ClientVersion - * Returns the five components of the client version. +/* db2ClientVersion + * Returns the five components of the client version. */ void db2ClientVersion (DB2Session* session, char* version) { SQLSMALLINT len = 0; size_t ver_len = sizeof(version); SQLRETURN rc = 0; - db2Debug1("> db2ClientVersion"); + db2Entry(1,"> db2ClientVersion.c::db2ClientVersion"); memset(version,0x00,ver_len); rc = SQLGetInfo(session->connp->hdbc, SQL_DRIVER_VER, version, sizeof(version), &len); - db2Debug2(" rc = %d, version = '%s', ind = %d", rc, version, len); + db2Debug(2,"rc = %d, version = '%s', ind = %d", rc, version, len); rc = db2CheckErr(rc,session->connp->hdbc,SQL_HANDLE_DBC,__LINE__,__FILE__); - db2Debug1("< db2ClientVersion - version: '%s'", version); + db2Exit(1,"< db2ClientVersion.c::db2ClientVersion - version: '%s'", version); } diff --git a/source/db2CloseConnections.c b/source/db2CloseConnections.c index 62d28cb..66406b1 100644 --- a/source/db2CloseConnections.c +++ b/source/db2CloseConnections.c @@ -11,9 +11,10 @@ extern DB2EnvEntry* rootenvEntry; /* Linked list of handles for cached extern char db2Message[ERRBUFSIZE];/* contains DB2 error messages, set by db2CheckErr() */ /** external prototypes */ -extern void db2Debug1 (const char* message, ...); -extern void db2Debug2 (const char* message, ...); -extern void db2Debug3 (const char* message, ...); +extern int isLogLevel (int level); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); +extern void db2Debug (int level, const char* message, ...); extern void db2Error (db2error sqlstate, const char* message); extern void db2Error_d (db2error sqlstate, const char* message, const char* detail, ...); extern SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleType, int line, char* file); @@ -22,53 +23,51 @@ extern void db2FreeEnvHdl (DB2EnvEntry* envp, const char* nls_lang); extern void db2free (void* p); /** local prototypes */ -void db2CloseConnections (void); -void db2FreeConnHdl (DB2EnvEntry* envp, DB2ConnEntry* connp); -int deleteconnEntry (DB2ConnEntry* start, DB2ConnEntry* node); + void db2CloseConnections (void); +static void db2FreeConnHdl (DB2EnvEntry* envp, DB2ConnEntry* connp); +static int deleteconnEntry (DB2ConnEntry* start, DB2ConnEntry* node); -/** db2CloseConnections - * Close everything in the cache. +/* db2CloseConnections + * Close everything in the cache. */ void db2CloseConnections (void) { - db2Debug1("> db2CloseConnections"); + db2Entry(1,"> db2CloseConnections.c::db2CloseConnections"); while (rootenvEntry != NULL) { while (rootenvEntry->connlist != NULL) { db2FreeConnHdl(rootenvEntry, rootenvEntry->connlist); - db2Debug3(" rootenvEntry: %x, rootenvEntry->connlist: %x",rootenvEntry, rootenvEntry->connlist); + db2Debug(3,"rootenvEntry: %x, rootenvEntry->connlist: %x",rootenvEntry, rootenvEntry->connlist); } db2FreeEnvHdl(rootenvEntry, NULL); } - db2Debug1("< db2CloseConnections"); + db2Exit(1,"< db2CloseConnections.c::db2CloseConnections"); } -/** db2FreeConnHdl - * - */ -void db2FreeConnHdl(DB2EnvEntry* envp, DB2ConnEntry* connp){ +/* db2FreeConnHdl */ +static void db2FreeConnHdl(DB2EnvEntry* envp, DB2ConnEntry* connp){ SQLRETURN rc = 0; int result = 0; - db2Debug1("> db2FreeConnHdl"); - db2Debug2(" envp : %x, ->henv: %d, ->connlist: %x",envp,envp->henv,envp->connlist); - db2Debug2(" connp: %x, ->hdbc: %d, ->handlelist: %x",connp,connp->hdbc,connp->handlelist); + db2Entry(2,"> db2CloseConnections.c::db2FreeConnHdl"); + db2Debug(3,"envp : %x, ->henv: %d, ->connlist: %x",envp,envp->henv,envp->connlist); + db2Debug(3,"connp: %x, ->hdbc: %d, ->handlelist: %x",connp,connp->hdbc,connp->handlelist); if (connp == NULL) { if (silent) return; else db2Error (FDW_ERROR, "closeSession internal error: connp is null"); } /* terminate the session */ - db2Debug2(" connp->hdbc: %x",connp->hdbc); + db2Debug(3,"connp->hdbc: %x",connp->hdbc); rc = SQLDisconnect(connp->hdbc); - db2Debug3(" SQLDisconnect.rc: %d",rc); + db2Debug(4,"SQLDisconnect.rc: %d",rc); rc = db2CheckErr(rc, connp->hdbc,SQL_HANDLE_DBC,__LINE__,__FILE__); if (rc != SQL_SUCCESS && !silent) { db2Error_d (FDW_UNABLE_TO_CREATE_REPLY, "error closing session: SQLDisconnect failed to terminate session", db2Message); } /* release the session handle */ - db2Debug2(" connp->hdbc: %x",connp->hdbc); + db2Debug(3,"connp->hdbc: %x",connp->hdbc); rc = SQLFreeHandle(SQL_HANDLE_DBC, connp->hdbc); - db2Debug3(" SQLFreeHandle.rc: %d",rc); + db2Debug(4,"SQLFreeHandle.rc: %d",rc); if (rc != SQL_SUCCESS && !silent) { db2Error_d (FDW_UNABLE_TO_CREATE_REPLY, "error freeing session handle: SQLFreeHandle failed", db2Message); } @@ -79,30 +78,28 @@ void db2FreeConnHdl(DB2EnvEntry* envp, DB2ConnEntry* connp){ result = deleteconnEntry(envp->connlist, connp); if (result && envp->connlist == connp) { envp->connlist = NULL; - db2Debug3(" envp->connlist: %x",envp->connlist); + db2Debug(4,"envp->connlist: %x",envp->connlist); } - db2Debug1("< db2FreeConnHdl"); + db2Exit(2,"< db2CloseConnections.c::db2FreeConnHdl"); } -/** deleteconnEntry - * - */ +/* deleteconnEntry */ int deleteconnEntry(DB2ConnEntry* start, DB2ConnEntry* node) { int result = 0; DB2ConnEntry* step = NULL; - db2Debug1("> deleteconnEntry(start:%x,node:%x)",start,node); + db2Entry(2,"> db2CloseConnections.c::deleteconnEntry(start:%x,node:%x)",start,node); for (step = start; step != NULL; step = step->right) { if (step == node) { - db2Debug3(" step == node: start: %x, step: %x, node %x", start, step, node); + db2Debug(4,"step == node: start: %x, step: %x, node %x", start, step, node); if (step->left == NULL && step->right == NULL){ - db2Debug3(" step left and right is null: start: %x, step: %x",start,step); + db2Debug(4,"step left and right is null: start: %x, step: %x",start,step); } else if (step->left == NULL) { - db2Debug3(" step left null"); + db2Debug(4,"step left null"); step->right->left = NULL; } else if (step->right == NULL) { - db2Debug3(" step right null"); + db2Debug(4,"step right null"); step->left->right = NULL; } else { step->left->right = step->right; @@ -113,16 +110,18 @@ int deleteconnEntry(DB2ConnEntry* start, DB2ConnEntry* node) { if (step->pwd) free (step->pwd); if (step->jwt_token) free (step->jwt_token); if (step) { - db2Debug1(" DB2ConnEntry freed: %x", step); + db2Debug(4,"DB2ConnEntry freed: %x", step); free (step); } result = 1; break; } } - for (step = start; step != NULL; step = step->right) { - db2Debug3(" start:%x, step:%x, step->left: %x, step->right:%x",start,step,step->left,step->right); + if (isLogLevel(3)) { + for (step = start; step != NULL; step = step->right) { + db2Debug(3,"start:%x, step:%x, step->left: %x, step->right:%x",start,step,step->left,step->right); + } } - db2Debug1("< deleteconnEntry - returns: %d", result); + db2Exit(2,"< db2CloseConnections.c::deleteconnEntry - returns: %d", result); return result; } diff --git a/source/db2CloseStatement.c b/source/db2CloseStatement.c index 665b836..7437449 100644 --- a/source/db2CloseStatement.c +++ b/source/db2CloseStatement.c @@ -7,25 +7,26 @@ /** external variables */ /** external prototypes */ -extern void db2Debug1 (const char* message, ...); -extern void db2Debug3 (const char* message, ...); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); +extern void db2Debug (int level, const char* message, ...); extern void db2FreeStmtHdl (HdlEntry* handlep, DB2ConnEntry* connp); /** local prototypes */ -void db2CloseStatement (DB2Session* session); +void db2CloseStatement (DB2Session* session); -/** db2CloseStatement - * Close any open statement associated with the session. +/* db2CloseStatement + * Close any open statement associated with the session. */ void db2CloseStatement (DB2Session* session) { - db2Debug1("> db2CloseStatement"); + db2Entry(1,"> db2CloseStatement.c::db2CloseStatement"); /* release statement handle, if it exists */ if (session->stmtp != NULL) { /* release the statement handle */ db2FreeStmtHdl(session->stmtp, session->connp); session->stmtp = NULL; } else { - db2Debug3( " no handle to close"); + db2Debug(3,"no handle to close"); } - db2Debug1("< db2CloseStatement"); + db2Exit(1,"< db2CloseStatement.c::db2CloseStatement"); } diff --git a/source/db2CopyText.c b/source/db2CopyText.c index 47fe2e6..01c3169 100644 --- a/source/db2CopyText.c +++ b/source/db2CopyText.c @@ -9,15 +9,15 @@ /** external prototypes */ extern void* db2alloc (const char* type, size_t size); -extern void db2Debug4 (const char* message, ...); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); /** local prototypes */ -char* db2CopyText (const char* string, int size, int quote); +char* db2CopyText (const char* string, int size, int quote); -/** db2CopyText - * Returns an allocated string containing a (possibly quoted) copy of "string". - * If the string starts with "(" and ends with ")", no quoting will take place - * even if "quote" is true. +/* db2CopyText + * Returns an allocated string containing a (possibly quoted) copy of "string". + * If the string starts with "(" and ends with ")", no quoting will take place even if "quote" is true. */ char* db2CopyText (const char* string, int size, int quote) { int resultsize = (quote ? size + 2 : size); @@ -25,7 +25,7 @@ char* db2CopyText (const char* string, int size, int quote) { register int j = -1; char* result; - db2Debug4("> db2CopyText(string: '%s', size: %d, quote: %d)",string,size,quote); + db2Entry(4,"> db2CopyText.c::db2CopyText(string: '%s', size: %d, quote: %d)",string,size,quote); /* if "string" is parenthized, return a copy */ if (string[0] == '(' && string[size - 1] == ')') { result = db2alloc ("copyText", size + 1); @@ -53,6 +53,6 @@ char* db2CopyText (const char* string, int size, int quote) { result[++j] = '"'; result[j + 1] = '\0'; - db2Debug4("< db2CopyText - result: %s",result); + db2Exit(4,"< db2CopyText.c::db2CopyText - result: %s",result); return result; } diff --git a/source/db2Debug.c b/source/db2Debug.c index 7e9e6e1..71fd489 100644 --- a/source/db2Debug.c +++ b/source/db2Debug.c @@ -2,11 +2,13 @@ #include #include #include -#include #include #include +#include #include "db2_fdw.h" +_Thread_local static int debug_depth = 0; + /* get a PostgreSQL error code from an db2error */ #define to_sqlstate(x) \ (x==FDW_UNABLE_TO_ESTABLISH_CONNECTION ? ERRCODE_FDW_UNABLE_TO_ESTABLISH_CONNECTION : \ @@ -17,13 +19,12 @@ (x==FDW_SERIALIZATION_FAILURE ? ERRCODE_T_R_SERIALIZATION_FAILURE : ERRCODE_FDW_ERROR)))))) /** local prototype */ +int isLogLevel(int level); void db2Error (db2error sqlstate, const char* message); void db2Error_d(db2error sqlstate, const char* message, const char* detail, ...) __attribute__ ((format (gnu_printf, 2, 0))); -void db2Debug1 (const char* message, ...)__attribute__ ((format (gnu_printf, 1, 0))); -void db2Debug2 (const char* message, ...)__attribute__ ((format (gnu_printf, 1, 0))); -void db2Debug3 (const char* message, ...)__attribute__ ((format (gnu_printf, 1, 0))); -void db2Debug4 (const char* message, ...)__attribute__ ((format (gnu_printf, 1, 0))); -void db2Debug5 (const char* message, ...)__attribute__ ((format (gnu_printf, 1, 0))); +void db2Entry (int level, const char* message, ...)__attribute__ ((format (gnu_printf, 2, 0))); +void db2Exit (int level, const char* message, ...)__attribute__ ((format (gnu_printf, 2, 0))); +void db2Debug (int level, const char* message, ...)__attribute__ ((format (gnu_printf, 2, 0))); /** db2Error_d * Report a PostgreSQL error with a detail message. @@ -51,58 +52,85 @@ void db2Error (db2error sqlstate, const char *message) { } } -/** db2Debug1 - * Rendering a single DEBUG1 output line to the pg log file. - */ -void db2Debug1(const char* message, ...) { - char cBuffer [4000]; - va_list arg_marker; - va_start (arg_marker, message); - vsnprintf (cBuffer, sizeof(cBuffer), message, arg_marker); - elog (DEBUG1, "%s", cBuffer); - va_end (arg_marker); -} -/** db2Debug2 - * Rendering a single DEBUG2 output line to the pg log file. - */ -void db2Debug2(const char* message, ...) { - char cBuffer [4000]; - va_list arg_marker; - va_start (arg_marker, message); - vsnprintf (cBuffer, sizeof(cBuffer), message, arg_marker); - elog (DEBUG2, "%s", cBuffer); - va_end (arg_marker); +int isLogLevel(int level) { + int dLevel = 0; + switch(level){ + case 1: + dLevel = DEBUG1; + break; + case 2: + dLevel = DEBUG2; + break; + case 3: + dLevel = DEBUG3; + break; + case 4: + dLevel = DEBUG4; + break; + case 5: + default: + dLevel = DEBUG5; + break; + } + dLevel = (dLevel >= log_min_messages); + return dLevel; } -/** db2Debug3 - * Rendering a single DEBUG3 output line to the pg log file. - */ -void db2Debug3(const char* message, ...) { - char cBuffer [4000]; - va_list arg_marker; - va_start (arg_marker, message); - vsnprintf (cBuffer, sizeof(cBuffer), message, arg_marker); - elog (DEBUG3, "%s", cBuffer); - va_end (arg_marker); + +void db2Entry(int level, const char* message, ...) { + if (isLogLevel(level)) { + char cBuffer [4000]; + va_list arg_marker; + va_start (arg_marker, message); + + vsnprintf (cBuffer, sizeof(cBuffer), message, arg_marker); + db2Debug(level, cBuffer); + ++debug_depth; + } } -/** db2Debug4 - * Rendering a single DEBUG4 output line to the pg log file. - */ -void db2Debug4(const char* message, ...) { - char cBuffer [4000]; - va_list arg_marker; - va_start (arg_marker, message); - vsnprintf (cBuffer, sizeof(cBuffer), message, arg_marker); - elog (DEBUG4, "%s", cBuffer); - va_end (arg_marker); + +void db2Exit(int level, const char* message, ...) { + if (isLogLevel(level)) { + char cBuffer [4000]; + va_list arg_marker; + va_start (arg_marker, message); + vsnprintf (cBuffer, sizeof(cBuffer), message, arg_marker); + + --debug_depth; + db2Debug(level, cBuffer); + } } -/** db2Debug5 - * Rendering a single DEBUG5 output line to the pg log file. - */ -void db2Debug5(const char* message, ...) { - char cBuffer [4000]; - va_list arg_marker; - va_start (arg_marker, message); - vsnprintf (cBuffer, sizeof(cBuffer), message, arg_marker); - elog (DEBUG5, "%s", cBuffer); - va_end (arg_marker); + +void db2Debug(int level, const char* message, ...) { + if (isLogLevel(level)) { + char cBuffer [4000]; + char* sIndent = (char*)palloc0(2*debug_depth+1); + int dLevel = DEBUG5; + va_list arg_marker; + + memset(sIndent, ' ', 2 * debug_depth); + + va_start (arg_marker, message); + vsnprintf (cBuffer, sizeof(cBuffer), message, arg_marker); + switch(level){ + case 1: + dLevel = DEBUG1; + break; + case 2: + dLevel = DEBUG2; + break; + case 3: + dLevel = DEBUG3; + break; + case 4: + dLevel = DEBUG4; + break; + case 5: + default: + dLevel = DEBUG5; + break; + } + elog (dLevel, "%s%s", sIndent, cBuffer); + va_end (arg_marker); + pfree(sIndent); + } } diff --git a/source/db2Describe.c b/source/db2Describe.c index 7d305ce..7d567e2 100644 --- a/source/db2Describe.c +++ b/source/db2Describe.c @@ -15,8 +15,9 @@ extern int err_code; /* error code, set by db2CheckErr() extern bool optionIsTrue (const char* value); extern void* db2alloc (const char* type, size_t size); extern void db2free (void* p); -extern void db2Debug1 (const char* message, ...); -extern void db2Debug2 (const char* message, ...); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); +extern void db2Debug (int level, const char* message, ...); extern SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleType, int line, char* file); extern void db2Error_d (db2error sqlstate, const char* message, const char* detail, ...); extern char* db2CopyText (const char* string, int size, int quote); @@ -27,9 +28,9 @@ extern void db2FreeStmtHdl (HdlEntry* handlep, DB2ConnEntry* connp /** internal prototypes */ DB2Table* db2Describe (DB2Session* session, char* schema, char* table, char* pgname, long max_long, char* noencerr, char* batchsz); -/** db2Describe - * Find the remote DB2 table and describe it. - * Returns an allocated data structure with the results. +/* db2Describe + * Find the remote DB2 table and describe it. + * Returns an allocated data structure with the results. */ DB2Table* db2Describe (DB2Session* session, char* schema, char* table, char* pgname, long max_long, char* noencerr, char* batchsz) { DB2Table* reply; @@ -52,7 +53,7 @@ DB2Table* db2Describe (DB2Session* session, char* schema, char* table, char* pgn SQLINTEGER codepage = 0; SQLRETURN rc = 0; - db2Debug1("> db2Describe"); + db2Entry(1,"> db2Describe.c::db2Describe"); /* get a complete quoted table name */ qtable = db2CopyText (table, strlen (table), 1); length = strlen (qtable); @@ -87,7 +88,7 @@ DB2Table* db2Describe (DB2Session* session, char* schema, char* table, char* pgn } /* execute the query */ rc = SQLExecute(stmthp->hsql); - rc= db2CheckErr(rc, stmthp->hsql, stmthp->type, __LINE__, __FILE__); + rc = db2CheckErr(rc, stmthp->hsql, stmthp->type, __LINE__, __FILE__); if (rc != SQL_SUCCESS) { if (err_code == 942) db2Error_d (FDW_TABLE_NOT_FOUND, "table not found", @@ -101,10 +102,10 @@ DB2Table* db2Describe (DB2Session* session, char* schema, char* table, char* pgn /* allocate an db2Table struct for the results */ reply = db2alloc ("reply", sizeof (DB2Table)); reply->name = tablename; - db2Debug2(" table description"); - db2Debug2(" reply->name : '%s'", reply->name); + db2Debug(2,"table description"); + db2Debug(2,"reply->name : '%s'", reply->name); reply->pgname = pgname; - db2Debug2(" reply->pgname : '%s'", reply->pgname); + db2Debug(2,"reply->pgname : '%s'", reply->pgname); reply->npgcols = 0; reply->batchsz = DEFAULT_BATCHSZ; @@ -122,7 +123,7 @@ DB2Table* db2Describe (DB2Session* session, char* schema, char* table, char* pgn reply->ncols = ncols; reply->cols = (DB2Column**) db2alloc ("reply->cols", sizeof (DB2Column*) *reply->ncols); - db2Debug2(" reply->ncols : %d", reply->ncols); + db2Debug(2,"reply->ncols : %d", reply->ncols); /* loop through the column list */ for (i = 1; i <= reply->ncols; ++i) { @@ -158,20 +159,20 @@ DB2Table* db2Describe (DB2Session* session, char* schema, char* table, char* pgn db2Error_d (FDW_UNABLE_TO_CREATE_REPLY, "error describing remote table: SQLDescribeCol failed to get column data", db2Message); } reply->cols[i - 1]->colName = db2CopyText ((char*) colName, (int) nameLen, 1); - db2Debug2(" reply->cols[%d]->colName : '%s'", (i-1), reply->cols[i - 1]->colName); - db2Debug2(" dataType: %d", dataType); + db2Debug(2,"reply->cols[%d]->colName : '%s'", (i-1), reply->cols[i - 1]->colName); + db2Debug(2,"dataType: %d", dataType); reply->cols[i - 1]->colType = (short) dataType; if (dataType == -7){ // datatype -7 does not exist it seems to be used for SQL_BOOLEAN wrongly reply->cols[i - 1]->colType = SQL_BOOLEAN; } - db2Debug2(" reply->cols[%d]->colType : %d (%s)", (i-1), reply->cols[i - 1]->colType,c2name(reply->cols[i - 1]->colType)); + db2Debug(2,"reply->cols[%d]->colType : %d (%s)", (i-1), reply->cols[i - 1]->colType,c2name(reply->cols[i - 1]->colType)); reply->cols[i - 1]->colSize = (size_t) colSize; - db2Debug2(" reply->cols[%d]->colSize : %ld", (i-1), reply->cols[i - 1]->colSize); + db2Debug(2,"reply->cols[%d]->colSize : %ld", (i-1), reply->cols[i - 1]->colSize); reply->cols[i - 1]->colScale = (short) scale; - db2Debug2(" reply->cols[%d]->colScale : %d", (i-1), reply->cols[i - 1]->colScale); + db2Debug(2,"reply->cols[%d]->colScale : %d", (i-1), reply->cols[i - 1]->colScale); reply->cols[i - 1]->colNulls = (short) nullable; - db2Debug2(" reply->cols[%d]->colNulls : %d", (i-1), reply->cols[i - 1]->colNulls); + db2Debug(2,"reply->cols[%d]->colNulls : %d", (i-1), reply->cols[i - 1]->colNulls); /* get the number of characters for string fields */ rc = SQLColAttribute (stmthp->hsql, i, SQL_DESC_PRECISION, NULL, 0, NULL, &charlen); @@ -180,7 +181,7 @@ DB2Table* db2Describe (DB2Session* session, char* schema, char* table, char* pgn db2Error_d (FDW_UNABLE_TO_CREATE_REPLY, "error describing remote table: SQLColAttribute failed to get column length", db2Message); } reply->cols[i - 1]->colChars = (size_t) charlen; - db2Debug2(" reply->cols[%d]->colChars : %ld", (i-1), reply->cols[i - 1]->colChars); + db2Debug(2,"reply->cols[%d]->colChars : %ld", (i-1), reply->cols[i - 1]->colChars); /* get the binary length for RAW fields */ rc = SQLColAttribute (stmthp->hsql, i, SQL_DESC_OCTET_LENGTH, NULL, 0, NULL, &bin_size); @@ -189,7 +190,7 @@ DB2Table* db2Describe (DB2Session* session, char* schema, char* table, char* pgn db2Error_d (FDW_UNABLE_TO_CREATE_REPLY, "error describing remote table: SQLColAttribute failed to get column size", db2Message); } reply->cols[i - 1]->colBytes = (size_t) bin_size; - db2Debug2(" reply->cols[%d]->colBytes : %ld", (i-1), reply->cols[i - 1]->colBytes); + db2Debug(2,"reply->cols[%d]->colBytes : %ld", (i-1), reply->cols[i - 1]->colBytes); /* get the columns codepage */ rc = SQLColAttribute(stmthp->hsql, i, SQL_DESC_CODEPAGE, NULL, 0, NULL, (SQLPOINTER)&codepage); @@ -198,7 +199,7 @@ DB2Table* db2Describe (DB2Session* session, char* schema, char* table, char* pgn db2Error_d (FDW_UNABLE_TO_CREATE_REPLY, "error describing remote table: SQLColAttribute failed to get column codepage", db2Message); } reply->cols[i - 1]->colCodepage = (int) codepage; - db2Debug2(" reply->cols[%d]->colCodepage : %d", (i-1), reply->cols[i - 1]->colCodepage); + db2Debug(2,"reply->cols[%d]->colCodepage : %d", (i-1), reply->cols[i - 1]->colCodepage); /* Unfortunately a LONG VARBINARY is of type LONG VARCHAR but the codepage is set to 0 */ if (reply->cols[i-1]->colType == SQL_LONGVARCHAR && reply->cols[i-1]->colCodepage == 0){ @@ -265,10 +266,10 @@ DB2Table* db2Describe (DB2Session* session, char* schema, char* table, char* pgn default: break; } - db2Debug2(" reply->cols[%d]->val_size : %d", (i-1), reply->cols[i - 1]->val_size); + db2Debug(2,"reply->cols[%d]->val_size : %d", (i-1), reply->cols[i - 1]->val_size); } /* release statement handle, this takes care of the parameter handles */ db2FreeStmtHdl(stmthp, session->connp); - db2Debug1("< db2Describe - returns: %x", reply); + db2Exit(1,"< db2Describe.c::db2Describe - returns: %x", reply); return reply; } diff --git a/source/db2EndDirectModify.c b/source/db2EndDirectModify.c index b67f667..71346db 100644 --- a/source/db2EndDirectModify.c +++ b/source/db2EndDirectModify.c @@ -8,8 +8,9 @@ extern regproc* output_funcs; /** external prototypes */ extern void db2CloseStatement (DB2Session* session); extern void db2free (void* p); -extern void db2Debug1 (const char* message, ...); -extern void db2Debug2 (const char* message, ...); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); +extern void db2Debug (int level, const char* message, ...); /** local prototypes */ void db2EndDirectModify(ForeignScanState* node); @@ -20,10 +21,10 @@ void db2EndDirectModify(ForeignScanState* node); void db2EndDirectModify(ForeignScanState* node) { DB2FdwDirectModifyState* fdw_state = (DB2FdwDirectModifyState*) node->fdw_state; - db2Debug1("> %s::db2EndDirectModify",__FILE__); + db2Entry(1,"> db2EndDirectModify.c::db2EndDirectModify"); /* MemoryContext will be deleted automatically. */ if (fdw_state == NULL) { - db2Debug2(" no fdw_state, nothing to do"); + db2Debug(2,"no fdw_state, nothing to do"); return; } @@ -50,5 +51,5 @@ void db2EndDirectModify(ForeignScanState* node) { db2free(fdw_state); node->fdw_state = NULL; - db2Debug1("< %s::db2EndDirectModify",__FILE__); + db2Exit(1,"< db2EndDirectModify.c::db2EndDirectModify"); } \ No newline at end of file diff --git a/source/db2EndForeignInsert.c b/source/db2EndForeignInsert.c index 070c4b6..dd8dcbf 100644 --- a/source/db2EndForeignInsert.c +++ b/source/db2EndForeignInsert.c @@ -4,7 +4,8 @@ /** external variables */ /** external prototypes */ -extern void db2Debug1 (const char* message, ...); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); extern void db2EndForeignModifyCommon(EState *estate, ResultRelInfo *rinfo); /** local prototypes */ @@ -14,8 +15,8 @@ void db2EndForeignInsert (EState* estate, ResultRelInfo* rinfo); * Close the currently active DB2 statement. */ void db2EndForeignInsert (EState* estate, ResultRelInfo* rinfo) { - db2Debug1("> db2EndForeignInsert"); + db2Entry(1,"> db2EndForeignInsert.c::db2EndForeignInsert"); db2EndForeignModifyCommon(estate, rinfo); - db2Debug1("< db2EndForeignInsert"); + db2Exit(1,"< db2EndForeignInsert.c::db2EndForeignInsert"); } diff --git a/source/db2EndForeignModify.c b/source/db2EndForeignModify.c index 48335b1..1f7eafe 100644 --- a/source/db2EndForeignModify.c +++ b/source/db2EndForeignModify.c @@ -5,17 +5,18 @@ /** external prototypes */ extern void db2EndForeignModifyCommon(EState *estate, ResultRelInfo *rinfo); -extern void db2Debug1 (const char* message, ...); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); /** local prototypes */ void db2EndForeignModify (EState* estate, ResultRelInfo* rinfo); -/** db2EndForeignModify - * Close the currently active DB2 statement. +/* db2EndForeignModify + * Close the currently active DB2 statement. */ void db2EndForeignModify (EState* estate, ResultRelInfo* rinfo) { - db2Debug1("> db2EndForeignModify"); + db2Entry(1,"> db2EndForeignModify.c::db2EndForeignModify"); db2EndForeignModifyCommon(estate, rinfo); - db2Debug1("< db2EndForeignModify"); + db2Exit(1,"< db2EndForeignModify.c::db2EndForeignModify"); } diff --git a/source/db2EndForeignModifyCommon.c b/source/db2EndForeignModifyCommon.c index eeca861..bb9298b 100644 --- a/source/db2EndForeignModifyCommon.c +++ b/source/db2EndForeignModifyCommon.c @@ -1,7 +1,6 @@ #include #include #include -#include #include #include #include "db2_fdw.h" @@ -12,8 +11,9 @@ extern regproc* output_funcs; /** external prototypes */ extern void db2CloseStatement (DB2Session* session); extern void db2free (void* p); -extern void db2Debug1 (const char* message, ...); -extern void db2Debug2 (const char* message, ...); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); +extern void db2Debug (int level, const char* message, ...); /** local prototypes */ void db2EndForeignModifyCommon(EState *estate, ResultRelInfo *rinfo); @@ -21,12 +21,12 @@ void db2EndForeignModifyCommon(EState *estate, ResultRelInfo *rin void db2EndForeignModifyCommon(EState *estate, ResultRelInfo *rinfo) { DB2FdwState *fdw_state = NULL; - db2Debug1("> db2EndForeignModifyCommon"); - db2Debug2(" relid: %d", RelationGetRelid (rinfo->ri_RelationDesc)); + db2Entry(1,"> db2EndForeignModifyCommon.c::db2EndForeignModifyCommon"); + db2Debug(2,"relid: %d", RelationGetRelid (rinfo->ri_RelationDesc)); fdw_state = (DB2FdwState*) rinfo->ri_FdwState; if (fdw_state == NULL) { - db2Debug2(" no fdw_state, nothing to do"); + db2Debug(2,"no fdw_state, nothing to do"); return; } @@ -53,5 +53,5 @@ void db2EndForeignModifyCommon(EState *estate, ResultRelInfo *rinfo) { rinfo->ri_FdwState = NULL; db2free(fdw_state); - db2Debug1("< db2EndForeignModifyCommon"); + db2Exit(1,"< db2EndForeignModifyCommon.c::db2EndForeignModifyCommon"); } diff --git a/source/db2EndForeignScan.c b/source/db2EndForeignScan.c index 7e01279..393ea72 100644 --- a/source/db2EndForeignScan.c +++ b/source/db2EndForeignScan.c @@ -1,26 +1,26 @@ #include #include -#include #include #include #include "db2_fdw.h" #include "DB2FdwState.h" /** external prototypes */ -extern void db2CloseStatement (DB2Session* session); -extern void db2free (void* p); -extern void db2Debug1 (const char* message, ...); +extern void db2CloseStatement (DB2Session* session); +extern void db2free (void* p); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); /** local prototypes */ void db2EndForeignScan(ForeignScanState* node); -/** db2EndForeignScan - * Close the currently active DB2 statement. +/* db2EndForeignScan + * Close the currently active DB2 statement. */ void db2EndForeignScan (ForeignScanState* node) { DB2FdwState* fdw_state = (DB2FdwState*) node->fdw_state; - db2Debug1("> db2EndForeignScan"); + db2Entry(1,"> db2EndForeignScan.c::db2EndForeignScan"); /* release the DB2 session */ db2CloseStatement(fdw_state->session); // check fdw_state->session for dangling references that need to be freed @@ -28,5 +28,5 @@ void db2EndForeignScan (ForeignScanState* node) { fdw_state->session = NULL; // check fdw_state for dangling references that need to be freed db2free(fdw_state); - db2Debug1("< db2EndForeignScan"); + db2Exit(1,"< db2EndForeignScan.c::db2EndForeignScan"); } diff --git a/source/db2EndSubtransaction.c b/source/db2EndSubtransaction.c index 00212e3..63e641d 100644 --- a/source/db2EndSubtransaction.c +++ b/source/db2EndSubtransaction.c @@ -8,8 +8,9 @@ extern char db2Message[ERRBUFSIZE];/* contains DB2 error messages, set b extern DB2EnvEntry* rootenvEntry; /* Linked list of handles for cached DB2 connections. */ /** external prototypes */ -extern void db2Debug1 (const char* message, ...); -extern void db2Debug2 (const char* message, ...); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); +extern void db2Debug (int level, const char* message, ...); extern void db2Error (db2error sqlstate, const char* message); extern void db2Error_d (db2error sqlstate, const char* message, const char* detail, ...); extern SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleType, int line, char* file); @@ -19,10 +20,10 @@ extern void db2FreeStmtHdl (HdlEntry* handlep, DB2ConnEntry* connp); /** local prototypes */ void db2EndSubtransaction (void* arg, int nest_level, int is_commit); -/** db2EndSubtransaction - * Commit or rollback all subtransaction up to savepoint "nest_nevel". - * The first argument must be a connEntry. - * If "is_commit" is not true, rollback. +/* db2EndSubtransaction + * Commit or rollback all subtransaction up to savepoint "nest_nevel". + * The first argument must be a connEntry. + * If "is_commit" is not true, rollback. */ void db2EndSubtransaction (void* arg, int nest_level, int is_commit) { SQLCHAR query[50]; @@ -33,7 +34,7 @@ void db2EndSubtransaction (void* arg, int nest_level, int is_commit) { SQLRETURN rc = 0; HdlEntry* hstmtp = NULL; - db2Debug1("> db2EndSubtransaction"); + db2Entry(1,"> db2EndSubtransaction.c::db2EndSubtransaction"); /* do nothing if the transaction level is lower than nest_level */ if (con->xact_level < nest_level) return; @@ -41,8 +42,7 @@ void db2EndSubtransaction (void* arg, int nest_level, int is_commit) { con->xact_level = nest_level - 1; if (is_commit) { - /* - * There is nothing to do as savepoints don't get released in DB2: + /* There is nothing to do as savepoints don't get released in DB2: * Setting the same savepoint again just overwrites the previous one. */ return; @@ -63,7 +63,7 @@ void db2EndSubtransaction (void* arg, int nest_level, int is_commit) { db2Error (FDW_ERROR, "db2RollbackSavepoint internal error: handle not found in cache"); } - db2Debug2(" rollback to savepoint s%d", nest_level); + db2Debug(2,"rollback to savepoint s%d", nest_level); snprintf ((char*)query, 49, "ROLLBACK TO SAVEPOINT s%d", nest_level); /* create statement handle */ @@ -83,5 +83,5 @@ void db2EndSubtransaction (void* arg, int nest_level, int is_commit) { db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error setting savepoint: SQLExecute failed to set savepoint", db2Message); } db2FreeStmtHdl(hstmtp, connp); - db2Debug1("< db2EndSubtransaction"); + db2Exit(1,"< db2EndSubtransaction.c::db2EndSubtransaction"); } diff --git a/source/db2EndTransaction.c b/source/db2EndTransaction.c index d5b7131..e4f8803 100644 --- a/source/db2EndTransaction.c +++ b/source/db2EndTransaction.c @@ -7,8 +7,9 @@ extern char db2Message[ERRBUFSIZE];/* contains DB2 error messages, set b extern DB2EnvEntry* rootenvEntry; /* Linked list of handles for cached DB2 connections. */ /** external prototypes */ -extern void db2Debug1 (const char* message, ...); -extern void db2Debug2 (const char* message, ...); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); +extern void db2Debug (int level, const char* message, ...); extern void db2Error (db2error sqlstate, const char* message); extern void db2Error_d (db2error sqlstate, const char* message, const char* detail, ...); extern SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleType, int line, char* file); @@ -28,50 +29,48 @@ void db2EndTransaction (void* arg, int is_commit, int noerror) { int found = 0; SQLRETURN rc = 0; - db2Debug1("> db2EndTransaction(arg:%x, is_commit:%d, noerror:%d)",arg,is_commit,noerror); + db2Entry(1,"> db2EndTransaction.c::db2EndTransaction(arg:%x, is_commit:%d, noerror:%d)",arg,is_commit,noerror); /* do nothing if there is no transaction */ if (((DB2ConnEntry*) arg)->xact_level == 0) { - db2Debug2(" there is no transaction - return"); - db2Debug2(" ((DB2ConnEntry*) arg)->xact_level: %d",((DB2ConnEntry*) arg)->xact_level); - db2Debug1("< db2EndTransaction"); - return; - } - - /* find the cached handles for the argument */ - envp = rootenvEntry; - for (envp = rootenvEntry; envp != NULL; envp = envp->right) { - for (connp = envp->connlist; connp != NULL; connp = connp->right ){ - if (connp == (DB2ConnEntry *) arg) { - found = 1; - break; + db2Debug(2,"there is no transaction"); + db2Debug(2,"((DB2ConnEntry*) arg)->xact_level: %d",((DB2ConnEntry*) arg)->xact_level); + } else { + /* find the cached handles for the argument */ + envp = rootenvEntry; + for (envp = rootenvEntry; envp != NULL; envp = envp->right) { + for (connp = envp->connlist; connp != NULL; connp = connp->right ){ + if (connp == (DB2ConnEntry *) arg) { + found = 1; + break; + } } } - } - if (!found) - /* print this trace hint, since the code will abend due to connp = NULL*/ - db2Error (FDW_ERROR, "db2EndTransaction internal error: handle not found in cache"); + if (!found) + /* print this trace hint, since the code will abend due to connp = NULL*/ + db2Error (FDW_ERROR, "db2EndTransaction internal error: handle not found in cache"); - /* release all handles of this connection, if any*/ - while (connp->handlelist != NULL) - db2FreeStmtHdl(connp->handlelist, connp); + /* release all handles of this connection, if any*/ + while (connp->handlelist != NULL) + db2FreeStmtHdl(connp->handlelist, connp); - /* commit or rollback */ - if (is_commit) { - db2Debug2(" db2_fdw::db2EndTransaction: commit remote transaction"); - rc = SQLEndTran(SQL_HANDLE_DBC, connp->hdbc, SQL_COMMIT); - rc = db2CheckErr(rc, connp->hdbc, SQL_HANDLE_DBC, __LINE__, __FILE__); - if (rc != SQL_SUCCESS && !noerror) { - db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error committing transaction: SQLEndTran failed", db2Message); - } - } else { - db2Debug2(" db2_fdw::db2EndTransaction: roll back remote transaction"); - rc = SQLEndTran(SQL_HANDLE_DBC, connp->hdbc, SQL_ROLLBACK); - rc = db2CheckErr(rc, connp->hdbc, SQL_HANDLE_DBC, __LINE__, __FILE__); - if (rc != SQL_SUCCESS && !noerror) { - db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error rolling back transaction: SQLEndTran failed", db2Message); + /* commit or rollback */ + if (is_commit) { + db2Debug(2,"db2_fdw::db2EndTransaction: commit remote transaction"); + rc = SQLEndTran(SQL_HANDLE_DBC, connp->hdbc, SQL_COMMIT); + rc = db2CheckErr(rc, connp->hdbc, SQL_HANDLE_DBC, __LINE__, __FILE__); + if (rc != SQL_SUCCESS && !noerror) { + db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error committing transaction: SQLEndTran failed", db2Message); + } + } else { + db2Debug(2,"db2_fdw::db2EndTransaction: roll back remote transaction"); + rc = SQLEndTran(SQL_HANDLE_DBC, connp->hdbc, SQL_ROLLBACK); + rc = db2CheckErr(rc, connp->hdbc, SQL_HANDLE_DBC, __LINE__, __FILE__); + if (rc != SQL_SUCCESS && !noerror) { + db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error rolling back transaction: SQLEndTran failed", db2Message); + } } + connp->xact_level = 0; + db2Debug(2,"connp->xact_level: %d",connp->xact_level); } - connp->xact_level = 0; - db2Debug2(" connp->xact_level: %d",connp->xact_level); - db2Debug1("< db2EndTransaction"); + db2Exit(1,"< db2EndTransaction.c::db2EndTransaction"); } diff --git a/source/db2ExecForeignBatchInsert.c b/source/db2ExecForeignBatchInsert.c index 90d7a8f..26902fb 100644 --- a/source/db2ExecForeignBatchInsert.c +++ b/source/db2ExecForeignBatchInsert.c @@ -6,7 +6,8 @@ /** external variables */ /** external prototypes */ -extern void db2Debug1 (const char* message, ...); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); extern TupleTableSlot* db2ExecForeignInsert (EState* estate, ResultRelInfo* rinfo, TupleTableSlot* slot, TupleTableSlot* planSlot); /** local prototypes */ @@ -20,7 +21,7 @@ TupleTableSlot** db2ExecForeignBatchInsert (EState *estate, ResultRelInfo */ TupleTableSlot ** db2ExecForeignBatchInsert(EState *estate, ResultRelInfo *rinfo, TupleTableSlot **slots, TupleTableSlot **planSlots, int *numSlots) { int i; - db2Debug1("> db2ExecForeignBatchInsert"); + db2Entry(1,"> db2ExecForeignBatchInsert.c::db2ExecForeignBatchInsert"); /* According to the FDW API, this is *not* used when there is a RETURNING clause, so normally these inserts don't need to produce a result tuple. * However, to be safe and to match the ExecForeignInsert semantics, we just call db2ExecForeignInsert and keep its results in the same slots. */ @@ -30,7 +31,7 @@ TupleTableSlot ** db2ExecForeignBatchInsert(EState *estate, ResultRelInfo *rinfo db2ExecForeignInsert(estate, rinfo, slots[i], planSlots ? planSlots[i] : NULL); } /* All results are in slots[0..*numSlots - 1]. */ - db2Debug1("< db2ExecForeignBatchInsert slots: %x", slots); + db2Exit(1,"< db2ExecForeignBatchInsert.c::db2ExecForeignBatchInsert slots: %x", slots); return slots; } #endif \ No newline at end of file diff --git a/source/db2ExecForeignDelete.c b/source/db2ExecForeignDelete.c index c8dd800..5bf9bf1 100644 --- a/source/db2ExecForeignDelete.c +++ b/source/db2ExecForeignDelete.c @@ -1,6 +1,5 @@ #include #include -#include #include #include #include "db2_fdw.h" @@ -12,8 +11,9 @@ extern regproc* output_funcs; /** external prototypes */ extern int db2ExecuteQuery (DB2Session* session, ParamDesc* paramList); -extern void db2Debug1 (const char* message, ...); -extern void db2Debug2 (const char* message, ...); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); +extern void db2Debug (int level, const char* message, ...); extern void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls, bool trunc_lob) ; extern char* deparseDate (Datum datum); extern char* deparseTimestamp (Datum datum, bool hasTimezone); @@ -23,17 +23,17 @@ extern void* db2alloc (const char* type, size_t size) TupleTableSlot* db2ExecForeignDelete (EState* estate, ResultRelInfo* rinfo, TupleTableSlot* slot, TupleTableSlot* planSlot); void setModifyParameters (ParamDesc* paramList, TupleTableSlot* newslot, TupleTableSlot* oldslot, DB2Table* db2Table, DB2Session* session); -/** db2ExecForeignDelete - * Set the parameter values from the slots and execute the DELETE statement. - * Returns a slot with the results from the RETRUNING clause. +/* db2ExecForeignDelete + * Set the parameter values from the slots and execute the DELETE statement. + * Returns a slot with the results from the RETRUNING clause. */ TupleTableSlot* db2ExecForeignDelete (EState* estate, ResultRelInfo* rinfo, TupleTableSlot* slot, TupleTableSlot* planSlot) { DB2FdwState* fdw_state = (DB2FdwState*) rinfo->ri_FdwState; int rows; MemoryContext oldcontext; - db2Debug1("> db2ExecForeignDelete"); - db2Debug2(" relid: %d", RelationGetRelid (rinfo->ri_RelationDesc)); + db2Entry(1,"> db2ExecForeignDelete.c::db2ExecForeignDelete"); + db2Debug(2,"relid: %d", RelationGetRelid (rinfo->ri_RelationDesc)); ++fdw_state->rowcount; dml_in_transaction = true; @@ -66,30 +66,30 @@ TupleTableSlot* db2ExecForeignDelete (EState* estate, ResultRelInfo* rinfo, Tupl /* store the virtual tuple */ ExecStoreVirtualTuple (slot); - db2Debug1("< db2ExecForeignDelete"); + db2Exit(1,"< db2ExecForeignDelete.c::db2ExecForeignDelete"); return slot; } -/** setModifyParameters - * Set the parameter values from the values in the slots. - * "newslot" contains the new values, "oldslot" the old ones. +/* setModifyParameters + * Set the parameter values from the values in the slots. + * "newslot" contains the new values, "oldslot" the old ones. */ void setModifyParameters (ParamDesc *paramList, TupleTableSlot * newslot, TupleTableSlot * oldslot, DB2Table *db2Table, DB2Session * session) { ParamDesc* param = NULL; Datum datum = 0; bool isnull = true; - db2Debug1("> %s::setModifyParameters", __FILE__); + db2Entry(1,"> db2ExecForeignDelete.c::setModifyParameters", __FILE__); for (param = paramList; param != NULL; param = param->next) { - db2Debug2(" db2Table->cols[%d]->colName: %s ",param->colnum,db2Table->cols[param->colnum]->colName); - db2Debug2(" param->bindType: %d",param->bindType); - db2Debug2(" param->colnum : %d",param->colnum); - db2Debug2(" param->txts : %d",param->txts); - db2Debug2(" param->type : %d",param->type); - db2Debug2(" param->value : %s - initial",param->value); + db2Debug(2,"db2Table->cols[%d]->colName: %s ",param->colnum,db2Table->cols[param->colnum]->colName); + db2Debug(2,"param->bindType: %d",param->bindType); + db2Debug(2,"param->colnum : %d",param->colnum); + db2Debug(2,"param->txts : %d",param->txts); + db2Debug(2,"param->type : %d",param->type); + db2Debug(2,"param->value : %s - initial",param->value); /* don't do anything for output parameters */ if (param->bindType == BIND_OUTPUT) { - db2Debug2(" param->bindType: %d - BIND_OUTPUT - skipped",param->bindType); + db2Debug(2,"param->bindType: %d - BIND_OUTPUT - skipped",param->bindType); continue; } if (db2Table->cols[param->colnum]->colPrimKeyPart != 0) { @@ -101,21 +101,21 @@ void setModifyParameters (ParamDesc *paramList, TupleTableSlot * newslot, TupleT continue; if (strcmp(NameStr(att->attname), psprintf("__db2fdw_rowid_%s", db2Table->cols[param->colnum]->pgname)) == 0) { attrno = i + 1; - db2Debug2(" key %s attrno : %d",NameStr(att->attname),attrno); - db2Debug2(" atttypid : %d",att->atttypid); - db2Debug2(" atttypmod: %d",att->atttypmod); + db2Debug(2,"key %s attrno : %d",NameStr(att->attname),attrno); + db2Debug(2," atttypid : %d",att->atttypid); + db2Debug(2," atttypmod: %d",att->atttypmod); break; } } if (attrno != -1) { /* for primary key parameters extract the resjunk entry */ datum = ExecGetJunkAttribute (oldslot, attrno, &isnull); - db2Debug2(" primaryKey value from resjunk entry: %ld",datum); + db2Debug(2,"primaryKey value from resjunk entry: %ld",datum); } } else { /* for other parameters extract the datum from newslot */ datum = slot_getattr (newslot, db2Table->cols[param->colnum]->pgattnum, &isnull); - db2Debug2(" parameter value from newslot: %ld",datum); + db2Debug(2,"parameter value from newslot: %ld",datum); } switch (param->bindType) { @@ -123,26 +123,26 @@ void setModifyParameters (ParamDesc *paramList, TupleTableSlot * newslot, TupleT case BIND_NUMBER: { if (isnull) { param->value = NULL; - db2Debug2(" param->value: %s - (NULL since isnull is set)",param->value); + db2Debug(2,"param->value: %s - (NULL since isnull is set)",param->value); } else { - db2Debug2(" db2Table->cols[%d]->pgtype: %d",param->colnum,db2Table->cols[param->colnum]->pgtype); + db2Debug(2,"db2Table->cols[%d]->pgtype: %d",param->colnum,db2Table->cols[param->colnum]->pgtype); /* special treatment for date, timestamps and intervals */ switch (db2Table->cols[param->colnum]->pgtype) { case DATEOID: { param->value = deparseDate (datum); - db2Debug2(" param->value: %s - (ought to be a date)",param->value); + db2Debug(2,"param->value: %s - (ought to be a date)",param->value); } break; case TIMESTAMPOID: case TIMESTAMPTZOID: { param->value = deparseTimestamp (datum, false/*(pgtype == TIMESTAMPTZOID)*/); - db2Debug2(" param->value: %s - (ought to be a timestamp)",param->value); + db2Debug(2,"param->value: %s - (ought to be a timestamp)",param->value); } break; case TIMEOID: case TIMETZOID:{ param->value = deparseTimestamp (datum, false/*(pgtype == TIMETZOID)*/); - db2Debug2(" param->value: %s (ought to be a time)",param->value); + db2Debug(2,"param->value: %s (ought to be a time)",param->value); } break; case BPCHAROID: @@ -151,7 +151,7 @@ void setModifyParameters (ParamDesc *paramList, TupleTableSlot * newslot, TupleT case NUMERICOID: { /* these functions require the type modifier */ param->value = DatumGetCString( OidFunctionCall3 (output_funcs[param->colnum], datum, ObjectIdGetDatum (InvalidOid), Int32GetDatum (db2Table->cols[param->colnum]->pgtypmod))); - db2Debug2(" param->value: %s (ought to be a BPCHAR, VARCHAR,INTERVAL or NUMERIC)",param->value); + db2Debug(2,"param->value: %s (ought to be a BPCHAR, VARCHAR,INTERVAL or NUMERIC)",param->value); } break; case UUIDOID: { @@ -159,7 +159,7 @@ void setModifyParameters (ParamDesc *paramList, TupleTableSlot * newslot, TupleT char* q = NULL; param->value = DatumGetCString (OidFunctionCall1 (output_funcs[param->colnum], datum)); - db2Debug2(" param->value: %s (ought to be a UUID)",param->value); + db2Debug(2,"param->value: %s (ought to be a UUID)",param->value); /* remove the minus signs for UUIDs */ for (p = q = param->value; *p != '\0'; ++p, ++q) { @@ -173,7 +173,7 @@ void setModifyParameters (ParamDesc *paramList, TupleTableSlot * newslot, TupleT case BOOLOID: { /* convert booleans to numbers */ param->value = DatumGetCString (OidFunctionCall1 (output_funcs[param->colnum], datum)); - db2Debug2(" param->value: %s (ought to be a boolean)",param->value); + db2Debug(2,"param->value: %s (ought to be a boolean)",param->value); param->value[0] = (param->value[0] == 't') ? '1' : '0'; param->value[1] = '\0'; } @@ -182,7 +182,7 @@ void setModifyParameters (ParamDesc *paramList, TupleTableSlot * newslot, TupleT /* the others don't */ /* convert the parameter value into a string */ param->value = DatumGetCString (OidFunctionCall1 (output_funcs[param->colnum], datum)); - db2Debug2(" param->value: %s (ought to be a string)",param->value); + db2Debug(2,"param->value: %s (ought to be a string)",param->value); } break; } @@ -193,7 +193,7 @@ void setModifyParameters (ParamDesc *paramList, TupleTableSlot * newslot, TupleT case BIND_LONGRAW: { if (isnull) { param->value = NULL; - db2Debug2(" param->value: %s - (NULL since isnull is set)",param->value); + db2Debug(2,"param->value: %s - (NULL since isnull is set)",param->value); } else { int32 value_len = 0; @@ -203,15 +203,15 @@ void setModifyParameters (ParamDesc *paramList, TupleTableSlot * newslot, TupleT value_len = VARSIZE (datum) - VARHDRSZ; param->value = db2alloc("param->value", value_len); memcpy (param->value, VARDATA(datum), value_len); - db2Debug2(" param->value: %s (ought to be a LONG or LONGRAW)",param->value); + db2Debug(2,"param->value: %s (ought to be a LONG or LONGRAW)",param->value); } } break; default: - db2Debug2(" unknown BIND_TYPE: %d", param->bindType); + db2Debug(2,"unknown BIND_TYPE: %d", param->bindType); break; } - db2Debug2(" param->value : %s - finally",param->value); + db2Debug(2,"param->value : %s - finally",param->value); } - db2Debug1("< setModifyParameters"); + db2Exit(1,"< db2ExecForeignDelete.c::setModifyParameters"); } diff --git a/source/db2ExecForeignInsert.c b/source/db2ExecForeignInsert.c index e60463d..691aac7 100644 --- a/source/db2ExecForeignInsert.c +++ b/source/db2ExecForeignInsert.c @@ -1,7 +1,6 @@ #include #include #include -#include #include #include #include "db2_fdw.h" @@ -11,25 +10,27 @@ extern bool dml_in_transaction; /** external prototypes */ -extern int db2ExecuteInsert (DB2Session* session, ParamDesc* paramList); -extern void db2Debug1 (const char* message, ...); -extern void setModifyParameters (ParamDesc* paramList, TupleTableSlot* newslot, TupleTableSlot* oldslot, DB2Table* db2Table, DB2Session* session); -extern void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls, bool trunc_lob) ; +extern int db2ExecuteInsert (DB2Session* session, ParamDesc* paramList); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); +extern void db2Debug (int level, const char* message, ...); +extern void setModifyParameters (ParamDesc* paramList, TupleTableSlot* newslot, TupleTableSlot* oldslot, DB2Table* db2Table, DB2Session* session); +extern void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls, bool trunc_lob) ; /** local prototypes */ TupleTableSlot* db2ExecForeignInsert(EState* estate, ResultRelInfo* rinfo, TupleTableSlot* slot, TupleTableSlot* planSlot); -/** db2ExecForeignInsert - * Set the parameter values from the slots and execute the INSERT statement. - * Returns a slot with the results from the RETRUNING clause. +/* db2ExecForeignInsert + * Set the parameter values from the slots and execute the INSERT statement. + * Returns a slot with the results from the RETRUNING clause. */ TupleTableSlot* db2ExecForeignInsert (EState* estate, ResultRelInfo* rinfo, TupleTableSlot* slot, TupleTableSlot* planSlot) { DB2FdwState* fdw_state = (DB2FdwState*) rinfo->ri_FdwState; int rows; MemoryContext oldcontext; - db2Debug1("> db2ExecForeignInsert"); - elog (DEBUG2, " relid: %d", RelationGetRelid (rinfo->ri_RelationDesc)); + db2Entry(1,"> db2ExecForeignInsert.c::db2ExecForeignInsert"); + db2Debug(2,"relid: %d", RelationGetRelid (rinfo->ri_RelationDesc)); ++fdw_state->rowcount; dml_in_transaction = true; @@ -57,6 +58,6 @@ TupleTableSlot* db2ExecForeignInsert (EState* estate, ResultRelInfo* rinfo, Tupl /* store the virtual tuple */ ExecStoreVirtualTuple (slot); - db2Debug1("< db2ExecForeignInsert"); + db2Exit(1,"< db2ExecForeignInsert.c::db2ExecForeignInsert"); return slot; } diff --git a/source/db2ExecForeignTruncate.c b/source/db2ExecForeignTruncate.c index df147b4..4634e65 100644 --- a/source/db2ExecForeignTruncate.c +++ b/source/db2ExecForeignTruncate.c @@ -1,27 +1,25 @@ #include #if PG_VERSION_NUM >= 140000 #include -#include #include - #include "db2_fdw.h" #include "DB2FdwState.h" /** external prototypes */ extern DB2FdwState* db2GetFdwState (Oid foreigntableid, double* sample_percent, bool drescribe); extern DB2Session* db2GetSession (const char* connectstring, char* user, char* password, char* jwt_token, const char* nls_lang, int curlevel); -extern void db2Debug1 (const char* message, ...); -extern void db2Debug2 (const char* message, ...); -extern void db2Debug3 (const char* message, ...); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); +extern void db2Debug (int level, const char* message, ...); extern int db2ExecuteTruncate (DB2Session* session, const char* query); extern void db2CloseStatement (DB2Session* session); extern void db2free (void* p); /** local prototypes */ -DB2FdwState* db2BuildTruncateFdwState(Relation rel, bool restart_seqs); -void db2ExecForeignTruncate (List *rels, DropBehavior behavior, bool restart_seqs); + void db2ExecForeignTruncate (List *rels, DropBehavior behavior, bool restart_seqs); +static DB2FdwState* db2BuildTruncateFdwState(Relation rel, bool restart_seqs); -/** ExecForeignTruncate +/* ExecForeignTruncate * * Called once per foreign server. All relations in "rels" must belong * to that server. @@ -31,11 +29,10 @@ void db2ExecForeignTruncate(List *rels, DropBehavior behavior, bool restart_seqs DB2FdwState* fdw_state = NULL; ListCell* lc; - db2Debug1("> db2ExecForeignTruncate"); + db2Entry(1,"> db2ExecForeignTruncate.c::db2ExecForeignTruncate"); if (rels != NIL) { - /** Optionally, you could inspect "behavior" (DROP_CASCADE / DROP_RESTRICT) - * and try to be clever. In practice, Db2 won't cascade TRUNCATE through - * RI anyway, so we just ignore it and let Db2 raise an error if there + /* Optionally, you could inspect "behavior" (DROP_CASCADE / DROP_RESTRICT) and try to be clever. + * In practice, Db2 won't cascade TRUNCATE through RI anyway, so we just ignore it and let DB2 raise an error if there * are incompatible constraints. */ foreach(lc, rels) { @@ -51,19 +48,17 @@ void db2ExecForeignTruncate(List *rels, DropBehavior behavior, bool restart_seqs fdw_state->session = NULL; } } - db2Debug1("< db2ExecForeignTruncate"); + db2Exit(1,"< db2ExecForeignTruncate.c::db2ExecForeignTruncate"); } -/** db2BuildTruncateFdwState - * - */ -DB2FdwState* db2BuildTruncateFdwState(Relation rel, bool restart_seqs) { +/* db2BuildTruncateFdwState */ +static DB2FdwState* db2BuildTruncateFdwState(Relation rel, bool restart_seqs) { DB2FdwState* fdwState; StringInfoData sql; char* identity_clause; char* storage_clause = "DROP STORAGE"; /* or REUSE STORAGE */ char* trigger_clause = "IGNORE DELETE TRIGGERS"; - db2Debug2("> db2BuildTruncateFdwState"); + db2Entry(1,"> db2ExecForeignTruncate.c::db2BuildTruncateFdwState"); /** Map Postgres' RESTART/CONTINUE IDENTITY to Db2's TRUNCATE options. */ if (restart_seqs) @@ -75,7 +70,7 @@ DB2FdwState* db2BuildTruncateFdwState(Relation rel, bool restart_seqs) { fdwState = db2GetFdwState(RelationGetRelid(rel), NULL, true); initStringInfo(&sql); - /** Build the TRUNCATE TABLE statement. + /* Build the TRUNCATE TABLE statement. * * Example: * TRUNCATE TABLE "SCHEMA"."TAB" @@ -88,8 +83,8 @@ DB2FdwState* db2BuildTruncateFdwState(Relation rel, bool restart_seqs) { */ appendStringInfo(&sql, "TRUNCATE TABLE %s %s %s %s IMMEDIATE", fdwState->db2Table->name, storage_clause, trigger_clause, identity_clause); fdwState->query = sql.data; - db2Debug3(" fdwState->query: '%s'",sql.data); - db2Debug2("< db2BuildTruncateFdwState - returns fdwState: %x",fdwState); + db2Debug(2,"fdwState->query: '%s'",sql.data); + db2Exit(1,"< db2ExecForeignTruncate.c::db2BuildTruncateFdwState : %x",fdwState); return fdwState; } #endif \ No newline at end of file diff --git a/source/db2ExecForeignUpdate.c b/source/db2ExecForeignUpdate.c index 0f8bf61..6b7dce8 100644 --- a/source/db2ExecForeignUpdate.c +++ b/source/db2ExecForeignUpdate.c @@ -1,7 +1,6 @@ #include #include #include -#include #include #include #include "db2_fdw.h" @@ -11,26 +10,27 @@ extern bool dml_in_transaction; /** external prototypes */ -extern int db2ExecuteQuery (DB2Session* session, ParamDesc* paramList); -extern void db2Debug1 (const char* message, ...); -extern void db2Debug2 (const char* message, ...); -extern void setModifyParameters (ParamDesc* paramList, TupleTableSlot* newslot, TupleTableSlot* oldslot, DB2Table* db2Table, DB2Session* session); -extern void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls, bool trunc_lob) ; +extern int db2ExecuteQuery (DB2Session* session, ParamDesc* paramList); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); +extern void db2Debug (int level, const char* message, ...); +extern void setModifyParameters (ParamDesc* paramList, TupleTableSlot* newslot, TupleTableSlot* oldslot, DB2Table* db2Table, DB2Session* session); +extern void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls, bool trunc_lob) ; /** local prototypes */ TupleTableSlot* db2ExecForeignUpdate (EState* estate, ResultRelInfo* rinfo, TupleTableSlot* slot, TupleTableSlot* planSlot); -/** db2ExecForeignUpdate - * Set the parameter values from the slots and execute the UPDATE statement. - * Returns a slot with the results from the RETRUNING clause. +/* db2ExecForeignUpdate + * Set the parameter values from the slots and execute the UPDATE statement. + * Returns a slot with the results from the RETRUNING clause. */ TupleTableSlot* db2ExecForeignUpdate (EState* estate, ResultRelInfo* rinfo, TupleTableSlot* slot, TupleTableSlot* planSlot) { DB2FdwState* fdw_state = (DB2FdwState*) rinfo->ri_FdwState; int rows = 0; MemoryContext oldcontext; - db2Debug1("> db2ExecForeignUpdate"); - db2Debug2(" relid: %d", RelationGetRelid (rinfo->ri_RelationDesc)); + db2Entry(1,"> db2ExecForeignUpdate.c::db2ExecForeignUpdate"); + db2Debug(2,"relid: %d", RelationGetRelid (rinfo->ri_RelationDesc)); ++fdw_state->rowcount; dml_in_transaction = true; @@ -63,7 +63,7 @@ TupleTableSlot* db2ExecForeignUpdate (EState* estate, ResultRelInfo* rinfo, Tupl /* store the virtual tuple */ ExecStoreVirtualTuple (slot); - db2Debug1("< db2ExecForeignUpdate"); + db2Exit(1,"< db2ExecForeignUpdate.c::db2ExecForeignUpdate"); return slot; } diff --git a/source/db2ExecuteInsert.c b/source/db2ExecuteInsert.c index a957584..0222282 100644 --- a/source/db2ExecuteInsert.c +++ b/source/db2ExecuteInsert.c @@ -14,9 +14,9 @@ extern int err_code; /* error code, set by db2CheckErr() /** external prototypes */ extern void* db2alloc (const char* type, size_t size); extern void db2free (void* p); -extern void db2Debug1 (const char* message, ...); -extern void db2Debug2 (const char* message, ...); -extern void db2Debug3 (const char* message, ...); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); +extern void db2Debug (int level, const char* message, ...); extern SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleType, int line, char* file); extern void db2Error_d (db2error sqlstate, const char* message, const char* detail, ...); extern SQLSMALLINT param2c (SQLSMALLINT fcType); @@ -27,27 +27,26 @@ extern void db2BindParameter (DB2Session* session, ParamDesc* param, /** internal prototypes */ int db2ExecuteInsert (DB2Session* session, ParamDesc* paramList); -/** db2ExecuteInsert - * Execute a prepared statement and fetches the first result row. - * The parameters ("bind variables") are filled from paramList. - * Returns the count of processed rows. - * This can be called several times for a prepared SQL statement. +/* db2ExecuteInsert + * Execute a prepared statement and fetches the first result row. + * The parameters ("bind variables") are filled from paramList. + * Returns the count of processed rows. + * This can be called several times for a prepared SQL statement. */ int db2ExecuteInsert (DB2Session* session, ParamDesc* paramList) { SQLLEN* indicators = NULL; ParamDesc* param = NULL; SQLRETURN rc = 0; - SQLINTEGER rowcount_val = 0; SQLSMALLINT outlen = 0; SQLCHAR cname[256] = {0}; /* 256 is usually plenty; see note below */ int rowcount = 0; int param_count = 0; - db2Debug1("> db2ExecuteInsert"); + db2Entry(1,"> db2ExecuteInsert.c::db2ExecuteInsert"); for (param = paramList; param != NULL; param = param->next) { ++param_count; } - db2Debug2(" paramcount: %d",param_count); + db2Debug(2,"paramcount: %d",param_count); /* allocate a temporary array of indicators */ indicators = db2alloc ("indicators", param_count * sizeof (SQLLEN)); @@ -60,13 +59,13 @@ int db2ExecuteInsert (DB2Session* session, ParamDesc* paramList) { } /* execute the query and get the first result row */ - db2Debug2(" session->stmtp->hsql: %d",session->stmtp->hsql); + db2Debug(2,"session->stmtp->hsql: %d",session->stmtp->hsql); rc = SQLGetCursorName(session->stmtp->hsql, cname, (SQLSMALLINT)sizeof(cname), &outlen); rc = db2CheckErr(rc, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); if (rc != SQL_SUCCESS) { db2Error_d(FDW_UNABLE_TO_CREATE_EXECUTION, "error executing query: SQLGetCusorName failed to obtain cursor name", db2Message); } - db2Debug2(" cursor name: '%s'", cname); + db2Debug(2,"cursor name: '%s'", cname); rc = SQLExecute (session->stmtp->hsql); rc = db2CheckErr(rc, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); if (rc != SQL_SUCCESS && rc != SQL_NO_DATA) { @@ -77,19 +76,19 @@ int db2ExecuteInsert (DB2Session* session, ParamDesc* paramList) { /* db2free all indicators */ db2free (indicators); if (rc == SQL_NO_DATA) { - db2Debug3(" SQL_NO_DATA"); - db2Debug1("< db2ExecuteInsert - returns: 0"); - return 0; - } + db2Debug(3,"SQL_NO_DATA"); + } else { + SQLINTEGER rowcount_val = 0; - /* get the number of processed rows (important for DML) */ - rc = SQLRowCount(session->stmtp->hsql, &rowcount_val); - rc = db2CheckErr(rc, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); - if (rc != SQL_SUCCESS) { - db2Error_d ( FDW_UNABLE_TO_CREATE_EXECUTION, "error executing query: SQLRowCount failed to get number of affected rows", db2Message); + /* get the number of processed rows (important for DML) */ + rc = SQLRowCount(session->stmtp->hsql, &rowcount_val); + rc = db2CheckErr(rc, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); + if (rc != SQL_SUCCESS) { + db2Error_d ( FDW_UNABLE_TO_CREATE_EXECUTION, "error executing query: SQLRowCount failed to get number of affected rows", db2Message); + } + db2Debug(2,"rowcount_val: %lld", rowcount_val); + rowcount = (int) rowcount_val; } - db2Debug2(" rowcount_val: %lld", rowcount_val); - rowcount = (int) rowcount_val; - db2Debug1("< db2ExecuteInsert - returns: %d",rowcount); + db2Exit(1,"< db2ExecuteInsert.c::db2ExecuteInsert - returns: %d",rowcount); return rowcount; } diff --git a/source/db2ExecuteQuery.c b/source/db2ExecuteQuery.c index a695c7f..76aedae 100644 --- a/source/db2ExecuteQuery.c +++ b/source/db2ExecuteQuery.c @@ -15,9 +15,9 @@ extern int err_code; /* error code, set by db2CheckErr() /** external prototypes */ extern void* db2alloc (const char* type, size_t size); extern void db2free (void* p); -extern void db2Debug1 (const char* message, ...); -extern void db2Debug2 (const char* message, ...); -extern void db2Debug3 (const char* message, ...); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); +extern void db2Debug (int level, const char* message, ...); extern SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleType, int line, char* file); extern void db2Error_d (db2error sqlstate, const char* message, const char* detail, ...); extern SQLSMALLINT param2c (SQLSMALLINT fcType); @@ -38,17 +38,16 @@ int db2ExecuteQuery (DB2Session* session, ParamDesc* paramList) { SQLLEN* indicators = NULL; ParamDesc* param = NULL; SQLRETURN rc = 0; - SQLINTEGER rowcount_val = 0; SQLSMALLINT outlen = 0; SQLCHAR cname[256] = {0}; /* 256 is usually plenty; see note below */ int rowcount = 0; int param_count = 0; - db2Debug1("> db2ExecureQuery"); + db2Entry(1,"> db2ExecuteQuery.c::db2ExecureQuery"); for (param = paramList; param != NULL; param = param->next) { ++param_count; } - db2Debug2(" paramcount: %d",param_count); + db2Debug(2,"paramcount: %d",param_count); /* allocate a temporary array of indicators */ indicators = db2alloc ("indicators", param_count * sizeof (SQLLEN)); @@ -59,13 +58,13 @@ int db2ExecuteQuery (DB2Session* session, ParamDesc* paramList) { db2BindParameter(session, param, &indicators[param_count], param_count, param_count); } /* execute the query and get the first result row */ - db2Debug2(" session->stmtp->hsql: %d",session->stmtp->hsql); + db2Debug(2,"session->stmtp->hsql: %d",session->stmtp->hsql); rc = SQLGetCursorName(session->stmtp->hsql, cname, (SQLSMALLINT)sizeof(cname), &outlen); rc = db2CheckErr(rc, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); if (rc != SQL_SUCCESS) { db2Error_d(FDW_UNABLE_TO_CREATE_EXECUTION, "error executing query: SQLGetCusorName failed to obtain cursor name", db2Message); } - db2Debug2(" cursor name: '%s'", cname); + db2Debug(2,"cursor name: '%s'", cname); rc = SQLExecute (session->stmtp->hsql); rc = db2CheckErr(rc, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); if (rc != SQL_SUCCESS && rc != SQL_NO_DATA) { @@ -74,19 +73,19 @@ int db2ExecuteQuery (DB2Session* session, ParamDesc* paramList) { } db2free(indicators); if (rc == SQL_NO_DATA) { - db2Debug3(" SQL_NO_DATA"); - db2Debug1("< db2ExecureQuery - returns: 0"); - return 0; - } + db2Debug(3,"SQL_NO_DATA"); + } else { + SQLINTEGER rowcount_val = 0; - /* get the number of processed rows (important for DML) */ - rc = SQLRowCount(session->stmtp->hsql, &rowcount_val); - rc = db2CheckErr(rc, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); - if (rc != SQL_SUCCESS) { - db2Error_d ( FDW_UNABLE_TO_CREATE_EXECUTION, "error executing query: SQLRowCount failed to get number of affected rows", db2Message); + /* get the number of processed rows (important for DML) */ + rc = SQLRowCount(session->stmtp->hsql, &rowcount_val); + rc = db2CheckErr(rc, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); + if (rc != SQL_SUCCESS) { + db2Error_d ( FDW_UNABLE_TO_CREATE_EXECUTION, "error executing query: SQLRowCount failed to get number of affected rows", db2Message); + } + db2Debug(2,"rowcount_val: %lld", rowcount_val); + rowcount = (int) rowcount_val; } - db2Debug2(" rowcount_val: %lld", rowcount_val); - rowcount = (int) rowcount_val; - db2Debug1("< db2ExecureQuery - returns: %d",rowcount); + db2Exit(1,"< db2ExecuteQuery.c::db2ExecureQuery : %d",rowcount); return rowcount; } diff --git a/source/db2ExecuteTruncate.c b/source/db2ExecuteTruncate.c index f07bbb9..aa8baa1 100644 --- a/source/db2ExecuteTruncate.c +++ b/source/db2ExecuteTruncate.c @@ -11,8 +11,9 @@ extern char db2Message[ERRBUFSIZE];/* contains DB2 error messages, set b extern int err_code; /* error code, set by db2CheckErr() */ /** external prototypes */ -extern void db2Debug1 (const char* message, ...); -extern void db2Debug2 (const char* message, ...); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); +extern void db2Debug (int level, const char* message, ...); extern SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleType, int line, char* file); extern void db2Error_d (db2error sqlstate, const char* message, const char* detail, ...); extern HdlEntry* db2AllocStmtHdl (SQLSMALLINT type, DB2ConnEntry* connp, db2error error, const char* errmsg); @@ -20,14 +21,13 @@ extern HdlEntry* db2AllocStmtHdl (SQLSMALLINT type, DB2ConnEntry* connp, /** internal prototypes */ int db2ExecuteTruncate (DB2Session* session, const char* query); -/** db2ExecuteTruncate - */ +/* db2ExecuteTruncate */ int db2ExecuteTruncate (DB2Session* session, const char* query) { SQLRETURN rc = 0; SQLINTEGER rowcount_val = 0; int rowcount = 0; - db2Debug1("> db2ExecuteTruncate(DB2Session: %x, query: %s)",session,query); + db2Entry(1,"> db2ExecuteTruncate.c::db2ExecuteTruncate(DB2Session: %x, query: %s)",session,query); rc = SQLEndTran(SQL_HANDLE_DBC, session->connp->hdbc, SQL_COMMIT); rc = db2CheckErr(rc, session->connp->hdbc, SQL_HANDLE_DBC, __LINE__, __FILE__); @@ -42,8 +42,8 @@ int db2ExecuteTruncate (DB2Session* session, const char* query) { db2Error_d(err_code == 8177 ? FDW_SERIALIZATION_FAILURE : FDW_UNABLE_TO_CREATE_EXECUTION, "error executing query: SQLExecute failed to execute remote query", db2Message); } - db2Debug2(" rowcount_val: %lld", rowcount_val); + db2Debug(2,"rowcount_val: %lld", rowcount_val); rowcount = (int) rowcount_val; - db2Debug1("< db2ExecuteTruncate - returns: %d",rowcount); + db2Exit(1,"< db2ExecuteTruncate.c::db2ExecuteTruncate - returns: %d",rowcount); return rowcount; } diff --git a/source/db2ExplainForeignModify.c b/source/db2ExplainForeignModify.c index 51cf8bf..a704f8a 100644 --- a/source/db2ExplainForeignModify.c +++ b/source/db2ExplainForeignModify.c @@ -3,29 +3,29 @@ #if PG_VERSION_NUM >= 180000 #include #endif -#include #include #include #include "db2_fdw.h" #include "DB2FdwState.h" /** external prototypes */ -extern void db2Debug1 (const char* message, ...); -extern void db2Debug2 (const char* message, ...); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); +extern void db2Debug (int level, const char* message, ...); /** local prototypes */ void db2ExplainForeignModify (ModifyTableState* mtstate, ResultRelInfo* rinfo, List* fdw_private, int subplan_index, struct ExplainState* es); -/** db2ExplainForeignModify - * Show the DB2 DML statement. - * Nothing special is done for VERBOSE because the query plan is likely trivial. +/* db2ExplainForeignModify + * Show the DB2 DML statement. + * Nothing special is done for VERBOSE because the query plan is likely trivial. */ void db2ExplainForeignModify (ModifyTableState* mtstate, ResultRelInfo* rinfo, List* fdw_private, int subplan_index, struct ExplainState* es) { DB2FdwState* fdw_state = (DB2FdwState*) rinfo->ri_FdwState; - db2Debug1("> db2ExplainForeignModify"); - db2Debug2(" relid: %d", RelationGetRelid (rinfo->ri_RelationDesc)); + db2Entry(1,"> db2ExplainForeignModify.c::db2ExplainForeignModify"); + db2Debug(2,"relid: %d", RelationGetRelid (rinfo->ri_RelationDesc)); /* show query */ ExplainPropertyText ("DB2 statement", fdw_state->query, es); - db2Debug1("< db2ExplainForeignModify"); + db2Exit(1,"< db2ExplainForeignModify.c::db2ExplainForeignModify"); } diff --git a/source/db2ExplainForeignScan.c b/source/db2ExplainForeignScan.c index 680b42c..93dc9c1 100644 --- a/source/db2ExplainForeignScan.c +++ b/source/db2ExplainForeignScan.c @@ -4,7 +4,6 @@ #include #include #endif -#include #include #include #include "db2_fdw.h" @@ -13,30 +12,29 @@ /** external prototypes */ extern void* db2alloc (const char* type, size_t size); extern void db2free (void* p); -extern void db2Debug1 (const char* message, ...); -extern void db2Debug2 (const char* message, ...); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); +extern void db2Debug (int level, const char* message, ...); /** local prototypes */ -void db2ExplainForeignScan(ForeignScanState* node, ExplainState* es); -void db2Explain (void* fdw, ExplainState* es); + void db2ExplainForeignScan(ForeignScanState* node, ExplainState* es); +static void db2Explain (void* fdw, ExplainState* es); -/** db2ExplainForeignScan - * Produce extra output for EXPLAIN: - * the DB2 query and, if VERBOSE was given, the execution plan. +/* db2ExplainForeignScan + * Produce extra output for EXPLAIN: + * the DB2 query and, if VERBOSE was given, the execution plan. */ void db2ExplainForeignScan (ForeignScanState* node, ExplainState* es) { DB2FdwState* fdw_state = (DB2FdwState*) node->fdw_state; - db2Debug1("> db2ExplainForeignScan"); + db2Entry(1,"> db2ExplainForeignScan.c::db2ExplainForeignScan"); elog (DEBUG1, "db2_fdw: explain foreign table scan"); ExplainPropertyText ("DB2 query", fdw_state->query, es); db2Explain (fdw_state, es); - db2Debug1("< db2ExplainForeignScan"); + db2Exit(1,"< db2ExplainForeignScan.c::db2ExplainForeignScan"); } -/** db2Explain - * - */ -void db2Explain (void* fdw, ExplainState* es) { +/* db2Explain */ +static void db2Explain (void* fdw, ExplainState* es) { FILE* fp; char path[1035]; char execution_cmd[300]; @@ -46,8 +44,8 @@ void db2Explain (void* fdw, ExplainState* es) { char* tempQuery = NULL; char* src = fdw_state->query; char* dest = NULL; - db2Debug1("> db2Explain"); + db2Entry(1,"> db2ExplainForeignScan.c::db2Explain"); for (const char* p = src; *p; p++) { if (*p == '"') count++; } @@ -77,7 +75,7 @@ void db2Explain (void* fdw, ExplainState* es) { snprintf(execution_cmd,sizeof(execution_cmd),"db2expln -t -d %s -q \"%s\" |grep -E \"Estimated Cost|Estimated Cardinality\" ",fdw_state->dbserver,tempQuery); } } - db2Debug2(" execution_cmd: '%s'",execution_cmd); + db2Debug(2,"execution_cmd: '%s'",execution_cmd); /* Open the command for reading. */ fp = popen(execution_cmd, "r"); if (fp == NULL) { @@ -93,5 +91,5 @@ void db2Explain (void* fdw, ExplainState* es) { /* close */ pclose(fp); db2free(tempQuery); - db2Debug1("< db2Explain"); + db2Exit(1,"< db2ExplainForeignScan.c::db2Explain"); } diff --git a/source/db2FetchNext.c b/source/db2FetchNext.c index e98d02d..c83b075 100644 --- a/source/db2FetchNext.c +++ b/source/db2FetchNext.c @@ -9,7 +9,8 @@ extern char db2Message[ERRBUFSIZE];/* contains DB2 error messages, set b extern int err_code; /* error code, set by db2CheckErr() */ /** external prototypes */ -extern void db2Debug1 (const char* message, ...); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); extern void db2Error (db2error sqlstate, const char* message); extern void db2Error_d (db2error sqlstate, const char* message, const char* detail, ...); extern SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleType, int line, char* file); @@ -17,12 +18,12 @@ extern SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSM /** local prototypes */ int db2FetchNext (DB2Session* session); -/** db2FetchNext - * Fetch the next result row, return 1 if there is one, else 0. +/* db2FetchNext + * Fetch the next result row, return 1 if there is one, else 0. */ int db2FetchNext (DB2Session* session) { SQLRETURN rc = 0; - db2Debug1("> db2FetchNext"); + db2Entry(1,"> db2FetchNext.c::db2FetchNext"); /* make sure there is a statement handle stored in "session" */ if (session->stmtp == NULL) { db2Error (FDW_ERROR, "db2FetchNext internal error: statement handle is NULL"); @@ -33,6 +34,6 @@ int db2FetchNext (DB2Session* session) { if (rc != SQL_SUCCESS && rc != SQL_NO_DATA) { db2Error_d (err_code == 8177 ? FDW_SERIALIZATION_FAILURE : FDW_UNABLE_TO_CREATE_EXECUTION, "error fetching result: SQLFetchScroll failed to fetch next result row", db2Message); } - db2Debug1("< db2FetchNext - returns: %d",(rc == SQL_SUCCESS)); + db2Exit(1,"< db2FetchNext.c::db2FetchNext : %d",(rc == SQL_SUCCESS)); return (rc == SQL_SUCCESS); } diff --git a/source/db2FreeEnvHdl.c b/source/db2FreeEnvHdl.c index 5684dc1..bb5b81e 100644 --- a/source/db2FreeEnvHdl.c +++ b/source/db2FreeEnvHdl.c @@ -12,40 +12,38 @@ extern char db2Message[ERRBUFSIZE];/* contains DB2 error messages, set b extern DB2EnvEntry* rootenvEntry; /* Linked list of handles for cached DB2 connections. */ /** external prototypes */ -extern void db2Debug1 (const char* message, ...); -extern void db2Debug2 (const char* message, ...); -extern void db2Debug3 (const char* message, ...); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); +extern void db2Debug (int level, const char* message, ...); extern void db2Error (db2error sqlstate, const char* message); extern void db2Error_d (db2error sqlstate, const char* message, const char* detail, ...); extern SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleType, int line, char* file); extern void db2free (void* p); /** local prototypes */ -void db2FreeEnvHdl (DB2EnvEntry* envp, const char* nls_lang); -int deleteenvEntry (DB2EnvEntry* start, DB2EnvEntry* node); -int deleteenvEntryLang (DB2EnvEntry* start, const char* nlslang); -DB2EnvEntry* findenvEntryHandle (DB2EnvEntry* start, SQLHENV henv); -DB2EnvEntry* findenvEntry (DB2EnvEntry* start, const char* nlslang); + void db2FreeEnvHdl (DB2EnvEntry* envp, const char* nls_lang); +static int deleteenvEntry (DB2EnvEntry* start, DB2EnvEntry* node); +static int deleteenvEntryLang (DB2EnvEntry* start, const char* nlslang); +static DB2EnvEntry* findenvEntryHandle (DB2EnvEntry* start, SQLHENV henv); + DB2EnvEntry* findenvEntry (DB2EnvEntry* start, const char* nlslang); -/** db2FreeEnvHdl - * - */ +/* db2FreeEnvHdl */ void db2FreeEnvHdl(DB2EnvEntry* envp, const char* nls_lang){ SQLRETURN rc = 0; - db2Debug1("> db2FreeEnvHdl"); + db2Entry(1,"> db2FreeEnvHdl.c::db2FreeEnvHdl"); /* search environment handle in cache */ envp = findenvEntryHandle (rootenvEntry, envp->henv); if (envp == NULL) { - db2Debug3(" removeEnvironment internal error: environment handle not found in cache"); + db2Debug(3,"removeEnvironment internal error: environment handle not found in cache"); if (!silent) { db2Error (FDW_ERROR, "removeEnvironment internal error: environment handle not found in cache"); } } else { /* release environment handle */ rc = SQLFreeHandle(SQL_HANDLE_ENV, envp->henv); - db2Debug3(" release env handle - rc: %d, henv: %d", rc, envp->henv); + db2Debug(3,"release env handle - rc: %d, henv: %d", rc, envp->henv); rc = db2CheckErr(rc, envp->henv, SQL_HANDLE_ENV,__LINE__, __FILE__); if (rc != SQL_SUCCESS) { db2Error_d (FDW_UNABLE_TO_ESTABLISH_CONNECTION, "cannot release environment handle","%s", db2Message); @@ -55,45 +53,43 @@ void db2FreeEnvHdl(DB2EnvEntry* envp, const char* nls_lang){ } deleteenvEntry(rootenvEntry,envp); sql_initialized = 0; - db2Debug3(" sql_initialized: %d",sql_initialized); + db2Debug(3,"sql_initialized: %d",sql_initialized); } - db2Debug1("< db2FreeEnvHdl"); + db2Exit(1,"< db2FreeEnvHdl.c::db2FreeEnvHdl"); } -/** deleteenvEntry - * - */ -int deleteenvEntry(DB2EnvEntry* start, DB2EnvEntry* node) { +/* deleteenvEntry */ +static int deleteenvEntry(DB2EnvEntry* start, DB2EnvEntry* node) { int result = 1; DB2EnvEntry* step = NULL; - db2Debug2(" > deleteenvEntry(start: %x, node: %x)", start, node); + db2Entry(1,"> db2FreeEnvHdl.c::deleteenvEntry(start: %x, node: %x)", start, node); for (step = start; step != NULL; step = step->right){ if (step == node) { free (step->nls_lang); step->nls_lang = NULL; if (step->left == NULL && step->right == NULL){ rootenvEntry = NULL; - db2Debug3(" rootenvEntry : %x", rootenvEntry); - db2Debug3(" DB2Enventry freed: %x", step); + db2Debug(3,"rootenvEntry : %x", rootenvEntry); + db2Debug(3,"DB2Enventry freed: %x", step); free (step); step = NULL; } else if (step->left == NULL) { step->right->left = NULL; - db2Debug3(" rootenvEntry : %x", rootenvEntry); - db2Debug3(" DB2Enventry freed: %x", step); + db2Debug(3,"rootenvEntry : %x", rootenvEntry); + db2Debug(3,"DB2Enventry freed: %x", step); free (step); step = NULL; } else if (step->right == NULL) { step->left->right = NULL; - db2Debug3(" rootenvEntry : %x", rootenvEntry); - db2Debug3(" DB2Enventry freed: %x", step); + db2Debug(3,"rootenvEntry : %x", rootenvEntry); + db2Debug(3,"DB2Enventry freed: %x", step); free (step); step = NULL; } else { step->left->right = step->right; step->right->left = step->left; - db2Debug3(" rootenvEntry : %x", rootenvEntry); - db2Debug3(" DB2Enventry freed: %x", step); + db2Debug(3,"rootenvEntry : %x", rootenvEntry); + db2Debug(3,"DB2Enventry freed: %x", step); free (step); step = NULL; } @@ -101,43 +97,41 @@ int deleteenvEntry(DB2EnvEntry* start, DB2EnvEntry* node) { break; } } - db2Debug2(" < deleteenvEntry - returns: %d",result); + db2Exit(1,"< db2FreeEnvHdl.c::deleteenvEntry - returns: %d",result); return result; } -/** deleteenvEntryLang - * - */ -int deleteenvEntryLang(DB2EnvEntry* start, const char* nlslang) { +/* deleteenvEntryLang */ +static int deleteenvEntryLang(DB2EnvEntry* start, const char* nlslang) { int result = 1; DB2EnvEntry *step = NULL; - db2Debug2(" > deleteenvEntryLang(start: %x, nlslang: %s)", start, nlslang); + db2Entry(1,"> db2FreeEnvHdl.c::deleteenvEntryLang(start: %x, nlslang: %s)", start, nlslang); for (step = start; step != NULL; step = step->right){ if (strcmp (step->nls_lang, nlslang) == 0) { free (step->nls_lang); if (step->left == NULL && step->right == NULL){ rootenvEntry = NULL; - db2Debug3(" rootenvEntry : %x", rootenvEntry); - db2Debug3(" DB2Enventry freed: %x", step); + db2Debug(3,"rootenvEntry : %x", rootenvEntry); + db2Debug(3,"DB2Enventry freed: %x", step); free (step); step = NULL; } else if (step->left == NULL) { step->right->left = NULL; - db2Debug3(" rootenvEntry : %x", rootenvEntry); - db2Debug3(" DB2Enventry freed: %x", step); + db2Debug(3,"rootenvEntry : %x", rootenvEntry); + db2Debug(3,"DB2Enventry freed: %x", step); free (step); step = NULL; } else if (step->right == NULL) { step->left->right = NULL; - db2Debug3(" rootenvEntry : %x", rootenvEntry); - db2Debug3(" DB2Enventry freed: %x", step); + db2Debug(3,"rootenvEntry : %x", rootenvEntry); + db2Debug(3,"DB2Enventry freed: %x", step); free (step); step = NULL; } else { step->left->right = step->right; step->right->left = step->left; - db2Debug3(" rootenvEntry : %x", rootenvEntry); - db2Debug3(" DB2Enventry freed: %x", step); + db2Debug(3,"rootenvEntry : %x", rootenvEntry); + db2Debug(3,"DB2Enventry freed: %x", step); free (step); step = NULL; } @@ -145,42 +139,38 @@ int deleteenvEntryLang(DB2EnvEntry* start, const char* nlslang) { break; } } - db2Debug2(" < deleteenvEntryLang - returns: %d",result); + db2Exit(1,"< db2FreeEnvHdl.c::deleteenvEntryLang : %d",result); return result; } -/** findenvEntryHandle - * - */ -DB2EnvEntry* findenvEntryHandle (DB2EnvEntry* start, SQLHENV henv) { +/* findenvEntryHandle */ +static DB2EnvEntry* findenvEntryHandle (DB2EnvEntry* start, SQLHENV henv) { DB2EnvEntry* step = NULL; - db2Debug2(" > findenvEntryHandle(start: %x, SQLHENV: %d)",start, henv); + db2Entry(1,"> db2FreeEnvHdl.c::findenvEntryHandle(start: %x, SQLHENV: %d)",start, henv); for (step = start; step != NULL; step = step->right){ if (step->henv == henv) { break; } } - db2Debug2(" < findenvEntryHandle - returns: %x",step); + db2Exit(1,"< db2FreeEnvHdl.c::findenvEntryHandle : %x",step); return step; } -/** findenvEntry - * - */ +/* findenvEntry */ DB2EnvEntry* findenvEntry(DB2EnvEntry* start, const char* nlslang) { DB2EnvEntry* step = NULL; - db2Debug2(" > findenvEntry(start: %x, nlslang: '%s')", start, nlslang); + db2Entry(1,"> db2FreeEnvHdl.c::findenvEntry(start: %x, nlslang: '%s')", start, nlslang); for (step = start; step != NULL; step = step->right) { - db2Debug3(" step: %x ->nls_lang: '%s'", step, step->nls_lang); - db2Debug3(" nls_lang : '%s'", nlslang); - db2Debug3(" strcmp(step->nls_lang, nlslang): %d",strcmp (step->nls_lang, nlslang)); + db2Debug(3,"step: %x ->nls_lang: '%s'", step, step->nls_lang); + db2Debug(3,"nls_lang : '%s'", nlslang); + db2Debug(3,"strcmp(step->nls_lang, nlslang): %d",strcmp (step->nls_lang, nlslang)); if (strcmp (step->nls_lang, nlslang) == 0) { break; } } if (step != NULL) { - db2Debug3(" step: %x, ->henv: %d, ->nls_lang: '%s', ->connlist: %x", step, step->henv, step->nls_lang,step->connlist); + db2Debug(3,"step: %x, ->henv: %d, ->nls_lang: '%s', ->connlist: %x", step, step->henv, step->nls_lang,step->connlist); } - db2Debug2(" < findenvEntry - returns: %x", step); + db2Exit(1,"< db2FreeEnvHdl.c::findenvEntry - returns: %x", step); return step; } diff --git a/source/db2FreeStmtHdl.c b/source/db2FreeStmtHdl.c index e017211..3f53dd2 100644 --- a/source/db2FreeStmtHdl.c +++ b/source/db2FreeStmtHdl.c @@ -5,15 +5,15 @@ /** external variables */ /** external prototypes */ -extern void db2Debug1 (const char* message, ...); -extern void db2Debug2 (const char* message, ...); -extern void db2Debug3 (const char* message, ...); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); +extern void db2Debug (int level, const char* message, ...); extern void db2Error (db2error sqlstate, const char* message); extern SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleType, int line, char* file); /** local prototypes */ -void db2FreeStmtHdl (HdlEntry* handlep, DB2ConnEntry* connp); -HdlEntry* findhdlEntry (HdlEntry* start, SQLHANDLE hsql); + void db2FreeStmtHdl (HdlEntry* handlep, DB2ConnEntry* connp); +static HdlEntry* findhdlEntry (HdlEntry* start, SQLHANDLE hsql); /** db2FreeStmtHdl * release a DB2 statement handle, remove it from the cached list. @@ -23,14 +23,14 @@ void db2FreeStmtHdl (HdlEntry* handlep, DB2ConnEntry* connp) { HdlEntry* prev_entryp = NULL; SQLRETURN rc = 0; - db2Debug1("> db2FreeStmtHdl(handlep,connp)"); - db2Debug1(" handlep: %x ->hsql: %d ->type: %d ->next: %x", handlep, handlep->hsql, handlep->type, handlep->next); - db2Debug1(" connp : %x ->handlelist: %x", connp, connp->handlelist); + db2Entry(1,"> db2FreeStmtHdl.c::db2FreeStmtHdl(handlep,connp)"); + db2Debug(2,"handlep: %x ->hsql: %d ->type: %d ->next: %x", handlep, handlep->hsql, handlep->type, handlep->next); + db2Debug(2,"connp : %x ->handlelist: %x", connp, connp->handlelist); /* find the predecessor of handlep in the list of handles starting from connp->handlelist*/ prev_entryp = findhdlEntry(connp->handlelist, handlep->hsql); /* remember prev_entryp might be actually the root element at conp->handlelist*/ - db2Debug3(" prev_entryp: %x ->hsql : %d ->type : %d->next : %x", prev_entryp, prev_entryp->hsql, prev_entryp->type, prev_entryp->next); + db2Debug(3,"prev_entryp: %x ->hsql : %d ->type : %d->next : %x", prev_entryp, prev_entryp->hsql, prev_entryp->type, prev_entryp->next); /* release the handle */ rc = SQLFreeHandle(handlep->type, handlep->hsql); @@ -41,31 +41,29 @@ void db2FreeStmtHdl (HdlEntry* handlep, DB2ConnEntry* connp) { /* we closed the one and only element of connp->handlelist */ /* entryp->next must be NULL, so it is safe to assign it to connp->handlelist*/ connp->handlelist = entryp->next; - db2Debug3(" connp->handlelist: '%x'", connp->handlelist); + db2Debug(3,"connp->handlelist: '%x'", connp->handlelist); } else { /* we closed one element of connp->handlelist */ /* here we need to set handlep->next to prev_entryp->next isolating entryp for subsequent release*/ prev_entryp->next = handlep->next; - db2Debug3(" prev_entryp->next: '%x'", prev_entryp->next); + db2Debug(3,"prev_entryp->next: '%x'", prev_entryp->next); } - db2Debug1(" HdlEntry freeed: %x",entryp); + db2Debug(2,"HdlEntry freeed: %x",entryp); free (entryp); - db2Debug1("< db2FreeStmtHdl"); + db2Exit(1,"< db2FreeStmtHdl.c::db2FreeStmtHdl"); } -/** findhdlEntry - * - */ -HdlEntry* findhdlEntry (HdlEntry* start, SQLHANDLE hsql) { +/* findhdlEntry */ +static HdlEntry* findhdlEntry (HdlEntry* start, SQLHANDLE hsql) { HdlEntry* step = NULL; HdlEntry* prev = start; - db2Debug2(" > findhdlEntry"); + db2Entry(4,"> db2FreeStmtHdl.c::findhdlEntry"); for (step = start; step != NULL; step = step->next){ if (step->hsql == hsql) { break; } prev = step; } - db2Debug2(" < findhdlEntry - returns: %x", prev); + db2Exit(4,"< db2FreeStmtHdl.c::findhdlEntry : %x", prev); return prev; } diff --git a/source/db2GetFdwState.c b/source/db2GetFdwState.c index 717c318..af85c11 100644 --- a/source/db2GetFdwState.c +++ b/source/db2GetFdwState.c @@ -16,11 +16,9 @@ extern DB2Session* db2GetSession (const char* connectstring, char* user, char* extern DB2Table* db2Describe (DB2Session* session, char* schema, char* table, char* pgname, long max_long, char* noencerr, char* batchsz); extern char* db2CopyText (const char* string, int size, int quote); extern char* c2name (short fcType); -extern void db2Debug1 (const char* message, ...); -extern void db2Debug2 (const char* message, ...); -extern void db2Debug3 (const char* message, ...); -extern void db2Debug4 (const char* message, ...); -extern void db2Debug5 (const char* message, ...); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); +extern void db2Debug (int level, const char* message, ...); extern void* db2alloc (const char* type, size_t size); extern void db2free (void* p); extern char* db2strdup (const char* source); @@ -33,12 +31,11 @@ static void getColumnData (DB2Table* db2Table, O static void getOptions (Oid foreigntableid, List** options); -/** db2GetFdwState - * Construct an DB2FdwState from the options of the foreign table. - * Establish an DB2 connection and get a description of the - * remote table. - * "sample_percent" is set from the foreign table options. - * "sample_percent" can be NULL, in that case it is not set. +/* db2GetFdwState + * Construct an DB2FdwState from the options of the foreign table. + * Establish an DB2 connection and get a description of the remote table. + * "sample_percent" is set from the foreign table options. + * "sample_percent" can be NULL, in that case it is not set. */ DB2FdwState* db2GetFdwState (Oid foreigntableid, double* sample_percent, bool describe) { DB2FdwState* fdwState = db2alloc("fdw_state", sizeof (DB2FdwState)); @@ -55,7 +52,7 @@ DB2FdwState* db2GetFdwState (Oid foreigntableid, double* sample_percent, bool de char* batchsz = NULL; long max_long = 0; - db2Debug1("> %s::db2GetFdwState", __FILE__); + db2Entry(1,"> db2GetFdwState.c::db2GetFdwState"); /* Get all relevant options from the foreign table, the user mapping, the foreign server and the foreign data wrapper. */ getOptions (foreigntableid, &options); @@ -112,16 +109,15 @@ DB2FdwState* db2GetFdwState (Oid foreigntableid, double* sample_percent, bool de } } - db2Debug1("< %s::db2GetFdwState", __FILE__); + db2Exit(1,"< db2GetFdwState.c::db2GetFdwState"); return fdwState; } -/** db2GetFdwState - * Construct an DB2FdwState from the options of the foreign table. - * Establish an DB2 connection and get a description of the - * remote table. - * "sample_percent" is set from the foreign table options. - * "sample_percent" can be NULL, in that case it is not set. +/* db2GetFdwDirectModifyState + * Construct an DB2FdwState from the options of the foreign table. + * Establish an DB2 connection and get a description of the remote table. + * "sample_percent" is set from the foreign table options. + * "sample_percent" can be NULL, in that case it is not set. */ DB2FdwDirectModifyState* db2GetFdwDirectModifyState (Oid foreigntableid, double* sample_percent, bool describe) { DB2FdwDirectModifyState* fdwState = db2alloc("fdw_state", sizeof (DB2FdwDirectModifyState)); @@ -138,7 +134,7 @@ DB2FdwDirectModifyState* db2GetFdwDirectModifyState (Oid foreigntableid, double* char* batchsz = NULL; long max_long = 0; - db2Debug1("> %s::db2GetFdwDirectModifyState", __FILE__); + db2Entry(1,"> db2GetFdwState.c::db2GetFdwDirectModifyState"); /* Get all relevant options from the foreign table, the user mapping, the foreign server and the foreign data wrapper. */ getOptions (foreigntableid, &options); @@ -188,7 +184,7 @@ DB2FdwDirectModifyState* db2GetFdwDirectModifyState (Oid foreigntableid, double* } fdwState->session = db2GetSession (fdwState->dbserver, fdwState->user, fdwState->password, fdwState->jwt_token, fdwState->nls_lang, GetCurrentTransactionNestLevel () ); - db2Debug1("< %s::db2GetFdwDirectModifyState", __FILE__); + db2Exit(1,"< db2GetFdwState.c::db2GetFdwDirectModifyState"); return fdwState; } @@ -202,7 +198,7 @@ static DB2Table* describeForeignTable (Oid foreigntableid, char* schema, char* t TupleDesc tupdesc; int length = 0; - db2Debug2(" > %s::describeForeignTable",__FILE__); + db2Entry(2,"> db2GetFdwState.c::describeForeignTable"); db2Table = (DB2Table*)db2alloc("db2_table", sizeof (DB2Table)); /* get a complete quoted table name */ @@ -224,27 +220,27 @@ static DB2Table* describeForeignTable (Oid foreigntableid, char* schema, char* t db2free (qschema); db2Table->name = tablename; - db2Debug3(" table description"); - db2Debug3(" db2Table->name : '%s'", db2Table->name); + db2Debug(3,"table description"); + db2Debug(3,"db2Table->name : '%s'", db2Table->name); db2Table->pgname = pgname; - db2Debug3(" db2Table->pgname : '%s'", db2Table->pgname); + db2Debug(3,"db2Table->pgname : '%s'", db2Table->pgname); db2Table->batchsz = DEFAULT_BATCHSZ; if (batchsz != NULL) { char* end; db2Table->batchsz = strtol(batchsz,&end,10); - db2Debug3(" db2Table->batchsz : %d", db2Table->batchsz); + db2Debug(3,"db2Table->batchsz : %d", db2Table->batchsz); } rel = table_open (foreigntableid, NoLock); tupdesc = rel->rd_att; db2Table->npgcols = tupdesc->natts; - db2Debug3(" db2Table->npgcols : %d", db2Table->npgcols); + db2Debug(3,"db2Table->npgcols : %d", db2Table->npgcols); db2Table->ncols = tupdesc->natts; - db2Debug3(" db2Table->ncols : %d", db2Table->ncols); + db2Debug(3,"db2Table->ncols : %d", db2Table->ncols); db2Table->cols = (DB2Column**) db2alloc ("db2Table->cols", sizeof (DB2Column*) * db2Table->ncols); - db2Debug3(" db2Table->cols : %x", db2Table->cols); + db2Debug(3,"db2Table->cols : %x", db2Table->cols); /* loop through foreign table columns */ for (int i = 0, cidx = 0; i < tupdesc->natts; ++i) { @@ -318,7 +314,7 @@ static DB2Table* describeForeignTable (Oid foreigntableid, char* schema, char* t } } if (!db2type_set || !db2size_set || !db2bytes_set || !db2chars_set || !db2scale_set || !db2nulls_set || !db2codepage_set) { - db2Debug1(" INFO: column %d - %s without required options, discarding db2Table", cidx, db2Table->cols[cidx]->pgname); + db2Debug(2,"INFO: column %d - %s without required options, discarding db2Table", cidx, db2Table->cols[cidx]->pgname); db2free (db2Table); db2Table = NULL; break; @@ -388,32 +384,32 @@ static DB2Table* describeForeignTable (Oid foreigntableid, char* schema, char* t default: break; } - db2Debug3(" db2Table->cols >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); - db2Debug3(" db2Table->cols[%d] : %x" , cidx, db2Table->cols[cidx]); - db2Debug3(" db2Table->cols[%d]->colName : %s" , cidx, db2Table->cols[cidx]->colName); - db2Debug3(" db2Table->cols[%d]->colType : %d - (%s)" , cidx, db2Table->cols[cidx]->colType,c2name(db2Table->cols[cidx]->colType)); - db2Debug3(" db2Table->cols[%d]->colSize : %ld", cidx, db2Table->cols[cidx]->colSize); - db2Debug3(" db2Table->cols[%d]->colScale : %d" , cidx, db2Table->cols[cidx]->colScale); - db2Debug3(" db2Table->cols[%d]->colNulls : %d" , cidx, db2Table->cols[cidx]->colNulls); - db2Debug3(" db2Table->cols[%d]->colChars : %ld", cidx, db2Table->cols[cidx]->colChars); - db2Debug3(" db2Table->cols[%d]->colBytes : %ld", cidx, db2Table->cols[cidx]->colBytes); - db2Debug3(" db2Table->cols[%d]->colPrimKeyPart : %d" , cidx, db2Table->cols[cidx]->colPrimKeyPart); - db2Debug3(" db2Table->cols[%d]->colCodepage : %d" , cidx, db2Table->cols[cidx]->colCodepage); - db2Debug3(" db2Table->cols[%d]->pgrelid : %d" , cidx, db2Table->cols[cidx]->pgrelid); - db2Debug3(" db2Table->cols[%d]->pgname : %s" , cidx, db2Table->cols[cidx]->pgname); - db2Debug3(" db2Table->cols[%d]->pgattnum : %d" , cidx, db2Table->cols[cidx]->pgattnum); - db2Debug3(" db2Table->cols[%d]->pgtype : %d" , cidx, db2Table->cols[cidx]->pgtype); - db2Debug3(" db2Table->cols[%d]->pgtypmod : %d" , cidx, db2Table->cols[cidx]->pgtypmod); - db2Debug3(" db2Table->cols[%d]->used : %d" , cidx, db2Table->cols[cidx]->used); - db2Debug3(" db2Table->cols[%d]->pkey : %d" , cidx, db2Table->cols[cidx]->pkey); - db2Debug3(" db2Table->cols[%d]->val_size : %ld", cidx, db2Table->cols[cidx]->val_size); - db2Debug3(" db2Table->cols[%d]->noencerr : %d" , cidx, db2Table->cols[cidx]->noencerr); + db2Debug(3,"db2Table->cols >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); + db2Debug(3,"db2Table->cols[%d] : %x" , cidx, db2Table->cols[cidx]); + db2Debug(3,"db2Table->cols[%d]->colName : %s" , cidx, db2Table->cols[cidx]->colName); + db2Debug(3,"db2Table->cols[%d]->colType : %d - (%s)" , cidx, db2Table->cols[cidx]->colType,c2name(db2Table->cols[cidx]->colType)); + db2Debug(3,"db2Table->cols[%d]->colSize : %ld", cidx, db2Table->cols[cidx]->colSize); + db2Debug(3,"db2Table->cols[%d]->colScale : %d" , cidx, db2Table->cols[cidx]->colScale); + db2Debug(3,"db2Table->cols[%d]->colNulls : %d" , cidx, db2Table->cols[cidx]->colNulls); + db2Debug(3,"db2Table->cols[%d]->colChars : %ld", cidx, db2Table->cols[cidx]->colChars); + db2Debug(3,"db2Table->cols[%d]->colBytes : %ld", cidx, db2Table->cols[cidx]->colBytes); + db2Debug(3,"db2Table->cols[%d]->colPrimKeyPart : %d" , cidx, db2Table->cols[cidx]->colPrimKeyPart); + db2Debug(3,"db2Table->cols[%d]->colCodepage : %d" , cidx, db2Table->cols[cidx]->colCodepage); + db2Debug(3,"db2Table->cols[%d]->pgrelid : %d" , cidx, db2Table->cols[cidx]->pgrelid); + db2Debug(3,"db2Table->cols[%d]->pgname : %s" , cidx, db2Table->cols[cidx]->pgname); + db2Debug(3,"db2Table->cols[%d]->pgattnum : %d" , cidx, db2Table->cols[cidx]->pgattnum); + db2Debug(3,"db2Table->cols[%d]->pgtype : %d" , cidx, db2Table->cols[cidx]->pgtype); + db2Debug(3,"db2Table->cols[%d]->pgtypmod : %d" , cidx, db2Table->cols[cidx]->pgtypmod); + db2Debug(3,"db2Table->cols[%d]->used : %d" , cidx, db2Table->cols[cidx]->used); + db2Debug(3,"db2Table->cols[%d]->pkey : %d" , cidx, db2Table->cols[cidx]->pkey); + db2Debug(3,"db2Table->cols[%d]->val_size : %ld", cidx, db2Table->cols[cidx]->val_size); + db2Debug(3,"db2Table->cols[%d]->noencerr : %d" , cidx, db2Table->cols[cidx]->noencerr); } ++cidx; } table_close (rel, NoLock); - db2Debug2(" < %s::describeForeignTable : %x",__FILE__, db2Table); + db2Exit(2,"< db2GetFdwState.c::describeForeignTable : %x", db2Table); return db2Table; } @@ -427,7 +423,7 @@ static void getColumnData (DB2Table* db2Table, Oid foreigntableid) { TupleDesc tupdesc; int i, index; - db2Debug4(" > %s::getColumnData", __FILE__); + db2Entry(4,"> db2GetFdwState.c::getColumnData"); rel = table_open (foreigntableid, NoLock); tupdesc = rel->rd_att; @@ -471,7 +467,7 @@ static void getColumnData (DB2Table* db2Table, Oid foreigntableid) { } table_close (rel, NoLock); - db2Debug4(" < %s::getColumnData", __FILE__); + db2Exit(4,"< db2GetFdwState.c::getColumnData"); } /* getOptions @@ -485,7 +481,7 @@ static void getOptions (Oid foreigntableid, List** options) { UserMapping* mapping = NULL; ForeignTable* table = NULL; - db2Debug4(" > %s::getOptions", __FILE__); + db2Entry(4,"> db2GetFdwState.c::getOptions"); /** Gather all data for the foreign table. */ table = GetForeignTable(foreigntableid); if (table != NULL) { @@ -494,25 +490,25 @@ static void getOptions (Oid foreigntableid, List** options) { if (server != NULL) { wrapper = GetForeignDataWrapper(server->fdwid); } else { - db2Debug5(" unable to GetForeignServer: %d", table->serverid); + db2Debug(5,"unable to GetForeignServer: %d", table->serverid); } /* later options override earlier ones */ *options = NIL; if (wrapper != NULL) *options = list_concat(*options, wrapper->options); else - db2Debug5(" unable to get wrapper options"); + db2Debug(5,"unable to get wrapper options"); if (server != NULL) *options = list_concat(*options, server->options); else - db2Debug5(" unable to get server options"); + db2Debug(5,"unable to get server options"); if (mapping != NULL) *options = list_concat(*options, mapping->options); else - db2Debug5(" unable to get mapping options"); + db2Debug(5,"unable to get mapping options"); *options = list_concat(*options, table->options); } else { - db2Debug5(" unable to GetForeignTable: %d",foreigntableid); + db2Debug(5,"unable to GetForeignTable: %d",foreigntableid); } - db2Debug4(" < %s::getOptions", __FILE__); + db2Exit(4,"< db2GetFdwState.c::getOptions"); } \ No newline at end of file diff --git a/source/db2GetForeignJoinPaths.c b/source/db2GetForeignJoinPaths.c index ffb7d23..b4be157 100644 --- a/source/db2GetForeignJoinPaths.c +++ b/source/db2GetForeignJoinPaths.c @@ -1,14 +1,15 @@ #include #include #include -#include #include #include #include "db2_fdw.h" #include "DB2FdwState.h" /** external prototypes */ -extern void db2Debug1 (const char* message, ...); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); +extern void db2Debug (int level, const char* message, ...); extern char* deparseExpr (PlannerInfo* root, RelOptInfo* foreignrel, Expr* expr, List** params); extern char* db2strdup (const char* source); extern void* db2alloc (const char* type, size_t size); @@ -17,9 +18,9 @@ extern void* db2alloc (const char* type, size_t size); void db2GetForeignJoinPaths(PlannerInfo* root, RelOptInfo* joinrel, RelOptInfo* outerrel, RelOptInfo* innerrel, JoinType jointype, JoinPathExtraData* extra); static bool foreign_join_ok (PlannerInfo* root, RelOptInfo* joinrel, JoinType jointype, RelOptInfo* outerrel, RelOptInfo* innerrel, JoinPathExtraData* extra); -/** db2GetForeignJoinPaths - * Add possible ForeignPath to joinrel if the join is safe to push down. - * For now, we can only push down 2-way inner join for SELECT. +/* db2GetForeignJoinPaths + * Add possible ForeignPath to joinrel if the join is safe to push down. + * For now, we can only push down 2-way inner join for SELECT. */ void db2GetForeignJoinPaths (PlannerInfo * root, RelOptInfo * joinrel, RelOptInfo * outerrel, RelOptInfo * innerrel, JoinType jointype, JoinPathExtraData * extra) { DB2FdwState* fdwState = NULL; @@ -29,95 +30,91 @@ void db2GetForeignJoinPaths (PlannerInfo * root, RelOptInfo * joinrel, RelOptInf Cost startup_cost; Cost total_cost; - db2Debug1("> db2GetForeignJoinPaths"); - /* - * Currently we don't push-down joins in query for UPDATE/DELETE. + db2Entry(1,"> db2GetForeignJoinPaths.c::db2GetForeignJoinPaths"); + /* Currently we don't push-down joins in query for UPDATE/DELETE. * This would require a path for EvalPlanQual. * This restriction might be relaxed in a later release. */ if (root->parse->commandType != CMD_SELECT) { - elog (DEBUG2, "db2_fdw: don't push down join because it is no SELECT"); - return; - } - - /* - * N-way join is not supported, due to the column definition infrastracture. - * If we can track relid mapping of join relations, we can support N-way join. - */ - if (!IS_SIMPLE_REL (outerrel) || !IS_SIMPLE_REL (innerrel)) - return; - - /* skip if this join combination has been considered already */ - if (joinrel->fdw_private) - return; - - /* - * Create unfinished DB2FdwState which is used to indicate - * that the join relation has already been considered, so that we won't waste - * time considering it again and don't add the same path a second time. - * Once we know that this join can be pushed down, we fill the data structure. - */ - fdwState = (DB2FdwState *) db2alloc("joinrel->fdw_private", sizeof (DB2FdwState)); - - joinrel->fdw_private = fdwState; - - /* this performs further checks and completes joinrel->fdw_private */ - if (!foreign_join_ok (root, joinrel, jointype, outerrel, innerrel, extra)) - return; - - /* estimate the number of result rows for the join */ -#if PG_VERSION_NUM < 140000 - if (outerrel->pages > 0 && innerrel->pages > 0) -#else - if (outerrel->tuples >= 0 && innerrel->tuples >= 0) -#endif /* PG_VERSION_NUM */ - { - /* both relations have been ANALYZEd, so there should be useful statistics */ - joinclauses_selectivity = clauselist_selectivity(root, fdwState->joinclauses, 0, JOIN_INNER, extra->sjinfo); - rows = clamp_row_est (innerrel->tuples * outerrel->tuples * joinclauses_selectivity); + db2Debug(2,"db2_fdw: don't push down join because it is no SELECT"); } else { - /* at least one table lacks statistics, so use a fixed estimate */ - rows = 1000.0; - } + /* N-way join is not supported, due to the column definition infrastracture. + * If we can track relid mapping of join relations, we can support N-way join. + */ + if (!IS_SIMPLE_REL (outerrel) || !IS_SIMPLE_REL (innerrel)) { + db2Debug(2,"either outerrel or ínnerel is not a simple relation"); + } else { + /* skip if this join combination has been considered already */ + if (joinrel->fdw_private) { + db2Debug(2,"this join combination has been considered already"); + } else { + /* Create unfinished DB2FdwState which is used to indicate + * that the join relation has already been considered, so that we won't waste + * time considering it again and don't add the same path a second time. + * Once we know that this join can be pushed down, we fill the data structure. + */ + fdwState = (DB2FdwState *) db2alloc("joinrel->fdw_private", sizeof (DB2FdwState)); + + joinrel->fdw_private = fdwState; + + /* this performs further checks and completes joinrel->fdw_private */ + if (foreign_join_ok (root, joinrel, jointype, outerrel, innerrel, extra)) { + /* estimate the number of result rows for the join */ + #if PG_VERSION_NUM < 140000 + if (outerrel->pages > 0 && innerrel->pages > 0) + #else + if (outerrel->tuples >= 0 && innerrel->tuples >= 0) + #endif /* PG_VERSION_NUM */ + { + /* both relations have been ANALYZEd, so there should be useful statistics */ + joinclauses_selectivity = clauselist_selectivity(root, fdwState->joinclauses, 0, JOIN_INNER, extra->sjinfo); + rows = clamp_row_est (innerrel->tuples * outerrel->tuples * joinclauses_selectivity); + } else { + /* at least one table lacks statistics, so use a fixed estimate */ + rows = 1000.0; + } - /* use a random "high" value for startup cost */ - startup_cost = 10000.0; - - /* estimate total cost as startup cost + (returned rows) * 10.0 */ - total_cost = startup_cost + rows * 10.0; - - /* store cost estimation results */ - joinrel->rows = rows; - fdwState->startup_cost = startup_cost; - fdwState->total_cost = total_cost; - - /* create a new join path */ - joinpath = create_foreign_join_path( root - , joinrel - , NULL /* default pathtarget */ - , rows -#if PG_VERSION_NUM >= 180000 - , 0 /* no disabled plan nodes */ -#endif /* PG_VERSION_NUM */ - , startup_cost - , total_cost - , NIL /* no pathkeys */ - , joinrel->lateral_relids - , NULL /* no epq_path */ -#if PG_VERSION_NUM >= 170000 - , NIL /* no fdw_restrictinfo */ -#endif /* PG_VERSION_NUM */ - , NIL /* no fdw_private */ - ); - /* add generated path to joinrel */ - add_path(joinrel, (Path *) joinpath); - db2Debug1("< db2GetForeignJoinPaths"); + /* use a random "high" value for startup cost */ + startup_cost = 10000.0; + + /* estimate total cost as startup cost + (returned rows) * 10.0 */ + total_cost = startup_cost + rows * 10.0; + + /* store cost estimation results */ + joinrel->rows = rows; + fdwState->startup_cost = startup_cost; + fdwState->total_cost = total_cost; + + /* create a new join path */ + joinpath = create_foreign_join_path( root + , joinrel + , NULL /* default pathtarget */ + , rows + #if PG_VERSION_NUM >= 180000 + , 0 /* no disabled plan nodes */ + #endif /* PG_VERSION_NUM */ + , startup_cost + , total_cost + , NIL /* no pathkeys */ + , joinrel->lateral_relids + , NULL /* no epq_path */ + #if PG_VERSION_NUM >= 170000 + , NIL /* no fdw_restrictinfo */ + #endif /* PG_VERSION_NUM */ + , NIL /* no fdw_private */ + ); + /* add generated path to joinrel */ + add_path(joinrel, (Path *) joinpath); + } + } + } + } + db2Exit(1,"< db2GetForeignJoinPaths.c::db2GetForeignJoinPaths"); } -/** foreign_join_ok - * Assess whether the join between inner and outer relations can be pushed down - * to the foreign server. As a side effect, save information we obtain in this - * function to DB2FdwState passed in. +/* foreign_join_ok + * Assess whether the join between inner and outer relations can be pushed down to the foreign server. As a side effect, save information we obtain in this + * function to DB2FdwState passed in. */ static bool foreign_join_ok (PlannerInfo * root, RelOptInfo * joinrel, JoinType jointype, RelOptInfo * outerrel, RelOptInfo * innerrel, JoinPathExtraData * extra) { DB2FdwState* fdwState = NULL; @@ -129,7 +126,7 @@ static bool foreign_join_ok (PlannerInfo * root, RelOptInfo * joinrel, JoinType List* otherclauses = NULL; char* tabname = NULL;/* for warning messages */ - db2Debug1("> foreign_join_ok"); + db2Entry(1,"> db2GetForeignJoinPaths.c::foreign_join_ok"); /* we only support pushing down INNER joins */ if (jointype != JOIN_INNER) return false; @@ -143,8 +140,7 @@ static bool foreign_join_ok (PlannerInfo * root, RelOptInfo * joinrel, JoinType fdwState->innerrel = innerrel; fdwState->jointype = jointype; - /* - * If joining relations have local conditions, those conditions are + /* If joining relations have local conditions, those conditions are * required to be applied before joining the relations. Hence the join can * not be pushed down. */ @@ -153,15 +149,13 @@ static bool foreign_join_ok (PlannerInfo * root, RelOptInfo * joinrel, JoinType /* Separate restrict list into join quals and quals on join relation */ - /* - * Unlike an outer join, for inner join, the join result contains only + /* Unlike an outer join, for inner join, the join result contains only * the rows which satisfy join clauses, similar to the other clause. * Hence all clauses can be treated the same. */ otherclauses = extract_actual_clauses (extra->restrictlist, false); - /* - * For inner joins, "otherclauses" contains now the join conditions. + /* For inner joins, "otherclauses" contains now the join conditions. * Check which ones can be pushed down. */ foreach (lc, otherclauses) { @@ -176,8 +170,7 @@ static bool foreign_join_ok (PlannerInfo * root, RelOptInfo * joinrel, JoinType fdwState->remote_conds = lappend (fdwState->remote_conds, expr); } - /* - * Only push down joins for which all join conditions can be pushed down. + /* Only push down joins for which all join conditions can be pushed down. * * For an inner join it would be ok to only push own some of the join * conditions and evaluate the others locally, but we cannot be certain @@ -197,8 +190,7 @@ static bool foreign_join_ok (PlannerInfo * root, RelOptInfo * joinrel, JoinType if (fdwState->remote_conds == NIL) return false; - /* - * Pull the other remote conditions from the joining relations into join + /* Pull the other remote conditions from the joining relations into join * clauses or other remote clauses (remote_conds) of this relation * wherever possible. This avoids building subqueries at every join step, * which is not currently supported by the deparser logic. @@ -212,8 +204,7 @@ static bool foreign_join_ok (PlannerInfo * root, RelOptInfo * joinrel, JoinType fdwState->remote_conds = list_concat (fdwState->remote_conds, list_copy (fdwState_i->remote_conds)); fdwState->remote_conds = list_concat (fdwState->remote_conds, list_copy (fdwState_o->remote_conds)); - /* - * For an inner join, all restrictions can be treated alike. Treating the + /* For an inner join, all restrictions can be treated alike. Treating the * pushed down conditions as join conditions allows a top level full outer * join to be deparsed without requiring subqueries. */ @@ -243,8 +234,7 @@ static bool foreign_join_ok (PlannerInfo * root, RelOptInfo * joinrel, JoinType fdwState->db2Table->npgcols = 0; fdwState->db2Table->cols = (DB2Column **) db2alloc("fdw_state->db2Table->cols[]", (sizeof (DB2Column*) * (db2Table_o->ncols + db2Table_i->ncols))); - /* - * Search db2Column from children's db2Table. + /* Search db2Column from children's db2Table. * Here we assume that children are foreign table, not foreign join. * We need capability to track relid chain through join tree to support N-way join. */ @@ -309,7 +299,7 @@ static bool foreign_join_ok (PlannerInfo * root, RelOptInfo * joinrel, JoinType fdwState->db2Table->npgcols = fdwState->db2Table->ncols; - db2Debug1("< foreign_join_ok"); + db2Exit(1,"< db2GetForeignJoinPaths.c::foreign_join_ok"); return true; } diff --git a/source/db2GetForeignModifyBatchSize.c b/source/db2GetForeignModifyBatchSize.c index f952192..9936621 100644 --- a/source/db2GetForeignModifyBatchSize.c +++ b/source/db2GetForeignModifyBatchSize.c @@ -10,11 +10,13 @@ /** external variables */ /** external prototypes */ -extern void db2Debug1 (const char* message, ...); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); +extern void db2Debug (int level, const char* message, ...); /** local prototypes */ -int db2GetForeignModifyBatchSize(ResultRelInfo *rinfo); -static int db2_get_batch_size_option (Relation rel); + int db2GetForeignModifyBatchSize(ResultRelInfo *rinfo); +static int db2_get_batch_size_option (Relation rel); /* db2GetForeignModifyBatchSize * Determine the maximum number of tuples that can be inserted in bulk @@ -26,6 +28,7 @@ int db2GetForeignModifyBatchSize(ResultRelInfo *rinfo) { int batch_size = 1; DB2FdwState* fmstate = (DB2FdwState*) rinfo->ri_FdwState; + db2Entry(1,"> db2GetForeignModifyBatchSize.c::db2GetForeignModifyBatchSize"); /* should be called only once */ Assert(rinfo->ri_BatchSize == 0); @@ -68,45 +71,18 @@ int db2GetForeignModifyBatchSize(ResultRelInfo *rinfo) { } } } + db2Exit(1,"< db2GetForeignModifyBatchSize.c::db2GetForeignModifyBatchSize : %d", batch_size); return batch_size; } - -/* db2GetForeignModifyBatchSize - * - * Returns the batch size to use for INSERTs into this foreign table. - * - * Returning 1 tells the executor to not batch (i.e., call ExecForeignInsert per-row). - * Any value > 1 enables batching, subject to what the executor actually decides to send. - */ -//int db2GetForeignModifyBatchSize(ResultRelInfo *rinfo) { -// Relation rel = rinfo->ri_RelationDesc; -// int batch_size = 0; -// -// db2Debug1("> db2GetForeignModifyBatchSize"); -// /* If the table has BEFORE/AFTER ROW triggers or a RETURNING clause is involved, it’s safer to disable batching and just do per-row inserts. -// * That's what postgres_fdw does as well.:contentReference[oaicite:4]{index=4} -// */ -// if (rel->trigdesc && (rel->trigdesc->trig_insert_before_row || rel->trigdesc->trig_insert_after_row)) { -// batch_size = 1; -// } else { -// /* We don't have easy access to "has RETURNING" here like postgres_fdw does (it stores that in its modify state), but PostgreSQL core -// * currently skips ExecForeignBatchInsert if there is a RETURNING clause anyway.:contentReference[oaicite:5]{index=5} -// */ -// batch_size = db2_get_batch_size_option(rel); -// } -// db2Debug1("< db2GetForeignModifyBatchSize - batch_size: %d", batch_size); -// return batch_size; -//} - static int db2_get_batch_size_option(Relation rel) { - Oid relid = RelationGetRelid(rel); - ForeignTable* table; - ForeignServer* server; - ListCell* lc; + Oid relid = RelationGetRelid(rel); + ForeignTable* table = NULL; + ForeignServer* server = NULL; + ListCell* lc = NULL; int batch_size = 0; - db2Debug1("> db2_get_batch_size_option"); + db2Entry(1,"> db2GetForeignModifyBatchSize.c::db2_get_batch_size_option"); table = GetForeignTable(relid); server = GetForeignServer(table->serverid); @@ -147,10 +123,9 @@ static int db2_get_batch_size_option(Relation rel) { } } } - /* Default: no batching */ batch_size = (batch_size < 1) ? DEFAULT_BATCHSZ : batch_size; - db2Debug1("< db2_get_batch_size_option- batch_size: %d", batch_size); + db2Exit(1,"< db2GetForeignModifyBatchSize.c::db2_get_batch_size_option : %d", batch_size); return batch_size; } #endif \ No newline at end of file diff --git a/source/db2GetForeignPaths.c b/source/db2GetForeignPaths.c index dd6e436..89614d0 100644 --- a/source/db2GetForeignPaths.c +++ b/source/db2GetForeignPaths.c @@ -1,22 +1,23 @@ #include #include #include -#include #include #include #include "db2_fdw.h" #include "DB2FdwState.h" /** external prototypes */ -extern void db2Debug1 (const char* message, ...); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); +extern void db2Debug (int level, const char* message, ...); extern char* deparseExpr (PlannerInfo* root, RelOptInfo* foreignrel, Expr* expr, List** params); /** local prototypes */ void db2GetForeignPaths (PlannerInfo* root, RelOptInfo* baserel, Oid foreigntableid); static Expr* find_em_expr_for_rel(EquivalenceClass * ec, RelOptInfo * rel); -/** db2GetForeignPaths - * Create a ForeignPath node and add it as only possible path. +/* db2GetForeignPaths + * Create a ForeignPath node and add it as only possible path. */ void db2GetForeignPaths(PlannerInfo* root, RelOptInfo* baserel, Oid foreigntableid) { DB2FdwState* fdwState = (DB2FdwState*) baserel->fdw_private; @@ -27,16 +28,16 @@ void db2GetForeignPaths(PlannerInfo* root, RelOptInfo* baserel, Oid foreigntable ListCell* cell; char* delim = " "; - db2Debug1("> db2GetForeignPaths"); + db2Entry(1,"> db2GetForeignPaths.c::db2GetForeignPaths"); initStringInfo (&orderedquery); foreach (cell, root->query_pathkeys) { - PathKey* pathkey = (PathKey *) lfirst (cell); - EquivalenceClass* pathkey_ec = pathkey->pk_eclass; - Expr* em_expr = NULL; - char* sort_clause; - Oid em_type; - bool can_pushdown; + PathKey* pathkey = (PathKey*) lfirst (cell); + EquivalenceClass* pathkey_ec = pathkey->pk_eclass; + Expr* em_expr = NULL; + char* sort_clause = NULL; + Oid em_type = 0; + bool can_pushdown = false; /* deparseExpr would detect volatile expressions as well, but ec_has_volatile saves some cycles. */ can_pushdown = !pathkey_ec->ec_has_volatile && ((em_expr = find_em_expr_for_rel (pathkey_ec, baserel)) != NULL); @@ -45,10 +46,26 @@ void db2GetForeignPaths(PlannerInfo* root, RelOptInfo* baserel, Oid foreigntable em_type = exprType ((Node *) em_expr); /* expressions of a type different from this are not safe to push down into ORDER BY clauses */ - if (em_type != INT8OID && em_type != INT2OID && em_type != INT4OID && em_type != OIDOID && em_type != FLOAT4OID - && em_type != FLOAT8OID && em_type != NUMERICOID && em_type != DATEOID && em_type != TIMESTAMPOID && em_type != TIMESTAMPTZOID - && em_type != TIMEOID && em_type != TIMETZOID && em_type != INTERVALOID) - can_pushdown = false; + switch(em_type){ + case INT8OID: + case INT2OID: + case INT4OID: + case OIDOID: + case FLOAT4OID: + case FLOAT8OID: + case NUMERICOID: + case DATEOID: + case TIMESTAMPOID: + case TIMESTAMPTZOID: + case TIMEOID: + case TIMETZOID: + case INTERVALOID: + can_pushdown = true; + break; + default: + can_pushdown = false; + break; + } } if (can_pushdown && ((sort_clause = deparseExpr (root, baserel, em_expr, &(fdwState->params))) != NULL)) { @@ -101,28 +118,25 @@ void db2GetForeignPaths(PlannerInfo* root, RelOptInfo* baserel, Oid foreigntable ,NIL ) ); - db2Debug1("< db2GetForeignPaths"); + db2Exit(1,"< db2GetForeignPaths.c::db2GetForeignPaths"); } /* find_em_expr_for_rel * Find an equivalence class member expression, all of whose Vars come from the indicated relation. */ static Expr* find_em_expr_for_rel (EquivalenceClass* ec, RelOptInfo* rel) { - ListCell* lc_em = NULL; + ListCell* lc_em = NULL; Expr* result = NULL; - db2Debug1("> find_em_expr_for_rel"); + + db2Entry(4,"> db2GetForeignPaths.c::find_em_expr_for_rel"); foreach (lc_em, ec->ec_members) { EquivalenceMember* em = lfirst (lc_em); if (bms_equal (em->em_relids, rel->relids)) { - /* - * If there is more than one equivalence member whose Vars are - * taken entirely from this relation, we'll be content to choose - * any one of those. - */ + /* If there is more than one equivalence member whose Vars are taken entirely from this relation, we'll be content to choose any one of those. */ result = em->em_expr; break; } } - db2Debug1("< find_em_expr_for_rel - returns: %x", result); + db2Exit(4,"< db2GetForeignPaths.c::find_em_expr_for_rel : %x", result); return result; } diff --git a/source/db2GetForeignPlan.c b/source/db2GetForeignPlan.c index 604dee3..81cf4f5 100644 --- a/source/db2GetForeignPlan.c +++ b/source/db2GetForeignPlan.c @@ -23,14 +23,13 @@ enum FdwPathPrivateIndex { }; /** external prototypes */ -extern void db2Debug1 (const char* message, ...); -extern void db2Debug2 (const char* message, ...); -extern void db2Debug3 (const char* message, ...); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); +extern void db2Debug (int level, const char* message, ...); extern void* db2alloc (const char* type, size_t size); extern void* db2free (void* pvoid); extern char* db2strdup (const char* source); - extern bool is_foreign_expr (PlannerInfo* root, RelOptInfo* baserel, Expr* expr); extern void deparseSelectStmtForRel (StringInfo buf, PlannerInfo* root, RelOptInfo* rel, List* tlist, List* remote_conds, List* pathkeys, bool has_final_sort, bool has_limit, bool is_subquery, List** retrieved_attrs, List** params_list); extern List* build_tlist_to_deparse (RelOptInfo* foreignrel); @@ -62,14 +61,14 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo Index scan_relid; StringInfoData sql; - db2Debug1("> %s::db2GetForeignPlan",__FILE__); + db2Entry(1,"> db2GetForeignPlan.c::db2GetForeignPlan"); /* Get FDW private data created by db2GetForeignUpperPaths(), if any. */ if (best_path->fdw_private) { has_final_sort = boolVal(list_nth(best_path->fdw_private, FdwPathPrivateHasFinalSort)); has_limit = boolVal(list_nth(best_path->fdw_private, FdwPathPrivateHasLimit)); } - db2Debug2(" length of tlist: %d", list_length(tlist)); + db2Debug(2,"length of tlist: %d", list_length(tlist)); // ptlist = build_tlist_to_deparse(foreignrel); ptlist = make_tlist_from_pathtarget(foreignrel->reltarget); @@ -81,7 +80,7 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo ListCell* cell = NULL; int iResCol = 0; - db2Debug3(" base relation scan: set scan_relid to %d", foreignrel->relid); + db2Debug(3,"base relation scan: set scan_relid to %d", foreignrel->relid); /* For base relations, set scan_relid as the relid of the relation. */ scan_relid = foreignrel->relid; @@ -89,17 +88,17 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo DB2ResultColumn* resCol = NULL; /* find all the columns to include in the select list */ /* examine each SELECT list entry for Var nodes */ - db2Debug3(" size of tlist: %d", ptlist_len); + db2Debug(3,"size of tlist: %d", ptlist_len); foreach (cell, ptlist) { resCol = (DB2ResultColumn*)db2alloc("resultColumn",sizeof(DB2ResultColumn)); getUsedColumns ((Expr*) lfirst (cell), foreignrel, resCol); - db2Debug3(" resCol->colName: %s", resCol->colName); - db2Debug3(" resCol->pgattnum: %d", resCol->pgattnum); + db2Debug(3,"resCol->colName: %s", resCol->colName); + db2Debug(3,"resCol->pgattnum: %d", resCol->pgattnum); if (resCol->colName != NULL && resCol->pgattnum <= fpinfo->db2Table->npgcols) { resCol->next = fpinfo->resultList; - db2Debug3(" resCol->next: %x", resCol->next); + db2Debug(3,"resCol->next: %x", resCol->next); fpinfo->resultList = resCol; - db2Debug3(" fpinfo->resultList: %x", fpinfo->resultList); + db2Debug(3,"fpinfo->resultList: %x", fpinfo->resultList); } else { db2free(resCol); } @@ -110,21 +109,21 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo cols = (DB2ResultColumn**)db2alloc("resultColumns", iResCol+1 * sizeof(DB2ResultColumn*)); iResCol = 0; for (resCol = fpinfo->resultList; resCol; resCol = resCol->next) { - db2Debug2(" resCol: %x", resCol); + db2Debug(2,"resCol: %x", resCol); cols[iResCol] = resCol; - db2Debug2(" cols[%d] : %x", iResCol, cols[iResCol]); - db2Debug2(" cols[%d]->colName: %s", iResCol, cols[iResCol]->colName); - db2Debug2(" cols[%d]->resnum : %d", iResCol, cols[iResCol]->resnum); + db2Debug(2,"cols[%d] : %x", iResCol, cols[iResCol]); + db2Debug(2,"cols[%d]->colName: %s", iResCol, cols[iResCol]->colName); + db2Debug(2,"cols[%d]->resnum : %d", iResCol, cols[iResCol]->resnum); iResCol++; - db2Debug2(" resCol->next: %x", resCol->next); + db2Debug(2,"resCol->next: %x", resCol->next); } // sort the array in ascending order of the pgattnum, so that we can compare it with the order of columns in the foreign table - db2Debug3(" sorting result cols %d columns by pgattnum", iResCol); + db2Debug(3,"sorting result cols %d columns by pgattnum", iResCol); qsort(cols, iResCol, sizeof(DB2ResultColumn*), compareResultColumns); // generate the sorted array into the resultList fpinfo->resultList = NULL; for (int idx = 0, cidx = 0; idx < iResCol; idx++) { - db2Debug3(" result column %d: %s", idx, cols[idx]->colName); + db2Debug(3,"result column %d: %s", idx, cols[idx]->colName); if (idx > 0) { // check if this column is the same as the one before and if so, skip it if (cols[idx]->pgattnum == cols[idx-1]->pgattnum) { @@ -133,13 +132,13 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo } cols[idx]->next = fpinfo->resultList; cols[idx]->resnum = ++cidx; // result number must be 1 - based - db2Debug3(" column %s added to result list with resnum %d", cols[idx]->colName, cols[idx]->resnum); + db2Debug(3,"column %s added to result list with resnum %d", cols[idx]->colName, cols[idx]->resnum); fpinfo->resultList = cols[idx]; } /* examine each condition for Var nodes */ - db2Debug3(" size of conditions: %d", list_length(foreignrel->baserestrictinfo)); + db2Debug(3,"size of conditions: %d", list_length(foreignrel->baserestrictinfo)); foreach (cell, foreignrel->baserestrictinfo) { - db2Debug3(" examine condition"); + db2Debug(3,"examine condition"); getUsedColumns ((Expr*) lfirst (cell), foreignrel, NULL); } } @@ -174,7 +173,7 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo /* For a base-relation scan, we have to support EPQ recheck, which should recheck all the remote quals. */ fdw_recheck_quals = remote_exprs; } else { - db2Debug3(" join relation scan: set scan_relid to 0"); + db2Debug(3,"join relation scan: set scan_relid to 0"); /* Join relation or upper relation - set scan_relid to 0. */ scan_relid = 0; @@ -200,15 +199,15 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo int resnum = 1; /* examine each condition for Tlist nodes; they come in the correct sequence as in the query and do not need to be sorted */ - db2Debug3(" size of tlist: %d", ptlist_len); + db2Debug(3,"size of tlist: %d", ptlist_len); foreach (cell, ptlist) { DB2ResultColumn* resCol = (DB2ResultColumn*)db2alloc("resultColumn",sizeof(DB2ResultColumn)); - db2Debug3(" examine tlist"); + db2Debug(3,"examine tlist"); resCol->next = fpinfo->resultList; fpinfo->resultList = resCol; resCol->resnum = resnum; getUsedColumns ((Expr*) lfirst (cell), foreignrel, resCol); - db2Debug3(" result column %d: %s", resCol->resnum, resCol->colName); + db2Debug(3,"result column %d: %s", resCol->resnum, resCol->colName); resnum++; } } @@ -217,7 +216,7 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo * from outer plan's quals, lest they be evaluated twice, once by the local plan and once by the scan. */ if (outer_plan) { - db2Debug3(" adjusting outer plan's targetlist and quals to match scan's needs"); + db2Debug(3,"adjusting outer plan's targetlist and quals to match scan's needs"); /* Right now, we only consider grouping and aggregation beyond joins. * Queries involving aggregates or grouping do not require EPQ mechanism, hence should not have an outer plan here. */ @@ -248,7 +247,7 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo /* Build the query string to be sent for execution, and identify expressions to be sent as parameters. */ initStringInfo(&sql); deparseSelectStmtForRel(&sql, root, foreignrel, ptlist, remote_exprs, best_path->path.pathkeys, has_final_sort, has_limit, false, &retrieved_attrs, ¶ms_list); - db2Debug2(" deparsed foreign query: %s", sql.data); + db2Debug(2,"deparsed foreign query: %s", sql.data); /* Remember remote_exprs for possible use by postgresPlanDirectModify */ fpinfo->final_remote_exprs = remote_exprs; @@ -266,7 +265,7 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo * because then they wouldn't be subject to later planner processing. */ fscan = make_foreignscan(tlist, local_exprs, scan_relid, params_list, fdw_private, ptlist, fdw_recheck_quals, outer_plan); - db2Debug1("< %s::db2GetForeignPlan : %x",__FILE__,fscan); + db2Exit(1,"< db2GetForeignPlan.c::db2GetForeignPlan : %x",fscan); return fscan; } @@ -276,9 +275,9 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo static void getUsedColumns (Expr* expr, RelOptInfo* foreignrel, DB2ResultColumn* resCol) { ListCell* cell = NULL; - db2Debug1("> getUsedColumns"); + db2Entry(3,"> db2GetForeignPlan.c::getUsedColumns"); if (expr != NULL) { - db2Debug2(" examine node of type: %d", expr->type); + db2Debug(4,"examine node of type: %d", expr->type); switch (expr->type) { case T_RestrictInfo: getUsedColumns (((RestrictInfo*) expr)->clause, foreignrel, resCol); @@ -299,32 +298,32 @@ static void getUsedColumns (Expr* expr, RelOptInfo* foreignrel, DB2ResultColumn* int index = 0; var = (Var*) expr; - db2Debug2(" var->varattno: %d", var->varattno); + db2Debug(4,"var->varattno: %d", var->varattno); /* ignore system columns */ if (var->varattno < 0) break; /* if this is a wholerow reference, we need all columns */ if (var->varattno == 0) { DB2ResultColumn* tmpCol = NULL; - db2Debug2(" found whole-row reference, need to add all columns"); - db2Debug2(" fpinfo->resultList: %x", fpinfo->resultList); + db2Debug(4,"found whole-row reference, need to add all columns"); + db2Debug(4,"fpinfo->resultList: %x", fpinfo->resultList); // add all columns but the last one here for (index = 0; index < (fpinfo->db2Table->ncols - 1); index++) { if (fpinfo->db2Table->cols[index]->pgname) { tmpCol = (DB2ResultColumn*)db2alloc("resultColumn",sizeof(DB2ResultColumn)); tmpCol->resnum = index+1; copyCol2Result(tmpCol,fpinfo->db2Table->cols[index]); - db2Debug2(" db2Table[%d]->colName %s added to result list", index, fpinfo->db2Table->cols[index]->colName); + db2Debug(4,"db2Table[%d]->colName %s added to result list", index, fpinfo->db2Table->cols[index]->colName); tmpCol->next = fpinfo->resultList; - db2Debug2(" tmpCol-next: %x", tmpCol->next); + db2Debug(4,"tmpCol-next: %x", tmpCol->next); fpinfo->resultList = tmpCol; - db2Debug2(" fpinfo->resultList: %x", fpinfo->resultList); + db2Debug(4,"fpinfo->resultList: %x", fpinfo->resultList); } } // now add the last colum using the resCol passed in, so that the column name in the result list is correct for whole row reference copyCol2Result(resCol,fpinfo->db2Table->cols[index]); resCol->resnum = index+1; - db2Debug3(" db2Table[%d]->colName %s added to result list", index, fpinfo->db2Table->cols[index]->colName); + db2Debug(4,"db2Table[%d]->colName %s added to result list", index, fpinfo->db2Table->cols[index]->colName); break; } else { /* get db2Table column index corresponding to this column (-1 if none) */ @@ -360,12 +359,12 @@ static void getUsedColumns (Expr* expr, RelOptInfo* foreignrel, DB2ResultColumn* } ReleaseSysCache(tuple); } - db2Debug2( " aggref->aggfnoid=%u name=%s%s%s", aggref->aggfnoid, nspname ? nspname : "", nspname ? "." : "", aggname ? aggname : ""); + db2Debug(4,"aggref->aggfnoid=%u name=%s%s%s", aggref->aggfnoid, nspname ? nspname : "", nspname ? "." : "", aggname ? aggname : ""); if (aggname && strcmp(aggname, "count") == 0) { DB2FdwState* fpinfo = (DB2FdwState*) foreignrel->fdw_private; /* if it's a COUNT(*) then we need an additional result */ DB2Column* col = db2alloc("DB2Column for count(*)",sizeof(DB2Column)); - db2Debug2(" found COUNT aggregate"); + db2Debug(4,"found COUNT aggregate"); col->colName = "count"; col->colType = -5; // SQL_BIGINT type in DB2, which can hold the result of COUNT(*) col->colSize = 8; @@ -385,7 +384,7 @@ static void getUsedColumns (Expr* expr, RelOptInfo* foreignrel, DB2ResultColumn* col->noencerr = fpinfo->db2Table->cols[0]->noencerr; // use same noencerr as first column copyCol2Result(resCol,col); } else { - db2Debug2(" count aggref->args: %d",list_length(aggref->args)); + db2Debug(4,"count aggref->args: %d",list_length(aggref->args)); foreach (cell, aggref->args) { getUsedColumns ((Expr*) lfirst (cell), foreignrel, resCol); } @@ -538,24 +537,21 @@ static void getUsedColumns (Expr* expr, RelOptInfo* foreignrel, DB2ResultColumn* //nop break; /* contains no column references */ default: - /* - * We must be able to handle all node types that can - * appear because we cannot omit a column from the remote - * query that will be needed. + /* We must be able to handle all node types that can appear because we cannot omit a column from the remote query that will be needed. * Throw an error if we encounter an unexpected node type. */ ereport (ERROR, (errcode (ERRCODE_FDW_UNABLE_TO_CREATE_REPLY), errmsg ("Internal db2_fdw error: encountered unknown node type %d.", expr->type))); break; } } - db2Debug1("< getUsedColumns"); + db2Exit(3,"< db2GetForeignPlan.c::getUsedColumns"); } /* copyCol2Result * Copy the column information from the db2Table column to the result column. */ static void copyCol2Result(DB2ResultColumn* resCol, DB2Column* column) { - db2Debug1("> %s::copyCol2Result",__FILE__); + db2Entry(4,"> db2GetForeignPlan.c::copyCol2Result"); if (resCol && resCol->colName == NULL) { resCol->colName = db2strdup(column->colName); resCol->colType = column->colType; @@ -575,7 +571,7 @@ static void copyCol2Result(DB2ResultColumn* resCol, DB2Column* column) { resCol->val_size = column->val_size; resCol->noencerr = column->noencerr; } - db2Debug1("< %s::copyCol2Result",__FILE__); + db2Exit(4,"< db2GetForeignPlan.c::copyCol2Result"); } /* compareResultColumns @@ -585,9 +581,9 @@ static int compareResultColumns(const void* a, const void* b) { DB2ResultColumn* colA = *(DB2ResultColumn**) a; DB2ResultColumn* colB = *(DB2ResultColumn**) b; int result = 0; - db2Debug1("> %s::compareResultColumns",__FILE__); + db2Entry(4,"> db2GetForeignPlan.c::compareResultColumns"); result = (colA->pgattnum - colB->pgattnum); - db2Debug2(" comparing %s -> pgattnum %d and %s -> pgattnum %d, result = %d", colA->colName, colA->pgattnum, colB->colName, colB->pgattnum, result); - db2Debug1("< %s::compareResultColumns: %d",__FILE__, result); + db2Debug(5,"comparing %s -> pgattnum %d and %s -> pgattnum %d, result = %d", colA->colName, colA->pgattnum, colB->colName, colB->pgattnum, result); + db2Exit(4,"< db2GetForeignPlan.c::compareResultColumns : %d", result); return result; } \ No newline at end of file diff --git a/source/db2GetForeignPlanOld.c b/source/db2GetForeignPlanOld.c index 9a90252..65cba99 100644 --- a/source/db2GetForeignPlanOld.c +++ b/source/db2GetForeignPlanOld.c @@ -3,7 +3,6 @@ #include #include #include -#include #include #include #include "db2_fdw.h" diff --git a/source/db2GetForeignRelSize.c b/source/db2GetForeignRelSize.c index 3b765a3..876123c 100644 --- a/source/db2GetForeignRelSize.c +++ b/source/db2GetForeignRelSize.c @@ -3,7 +3,6 @@ #include #include #include -#include #include #include #include @@ -14,7 +13,9 @@ /** external prototypes */ extern DB2FdwState* db2GetFdwState (Oid foreigntableid, double* sample_percent, bool describe); -extern void db2Debug1 (const char* message, ...); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); +extern void db2Debug (int level, const char* message, ...); extern char* deparseWhereConditions (PlannerInfo* root, RelOptInfo* baserel); extern void db2free (void* p); extern void classifyConditions (PlannerInfo* root, RelOptInfo* baserel, List* input_conds, List** remote_conds, List** local_conds); @@ -37,14 +38,14 @@ static List* ExtractExtensionList (const char* extensionsString, bool warnOnMi void db2GetForeignRelSize (PlannerInfo* root, RelOptInfo* baserel, Oid foreigntableid) { DB2FdwState* fdwState = NULL; - db2Debug1("> db2GetForeignRelSize"); + db2Entry(1,"> db2GetForeignRelSize.c::db2GetForeignRelSize"); /* get connection options, connect and get the remote table description */ fdwState = db2GetFdwState(foreigntableid, NULL, true); /* store the state so that the other planning functions can use it */ baserel->fdw_private = (void*) fdwState; db2PopulateFdwStateOld(root, baserel,foreigntableid); db2PopulateFdwStateNew(root, baserel,foreigntableid); - db2Debug1("< db2GetForeignRelSize"); + db2Exit(1,"< db2GetForeignRelSize.c::db2GetForeignRelSize"); } static void db2PopulateFdwStateOld(PlannerInfo* root, RelOptInfo* baserel, Oid foreigntableid){ @@ -52,6 +53,7 @@ static void db2PopulateFdwStateOld(PlannerInfo* root, RelOptInfo* baserel, Oid f int i = 0; double ntuples = -1; + db2Entry(1,"> db2GetForeignRelSize.c::db2PopulateFdwStateOld"); /** Store the table OID in each table column. * This is redundant for base relations, but join relations will * have columns from different tables, and we have to keep track of them. @@ -85,13 +87,14 @@ static void db2PopulateFdwStateOld(PlannerInfo* root, RelOptInfo* baserel, Oid f } /* estimate total cost as startup cost + 10 * (returned rows) */ fdwState->total_cost = fdwState->startup_cost + baserel->rows * 10.0; - + db2Exit(1,"< db2GetForeignRelSize.c::db2PopulateFdwStateOld"); } static void db2PopulateFdwStateNew(PlannerInfo* root, RelOptInfo* baserel, Oid foreigntableid){ DB2FdwState* fpinfo = (DB2FdwState*)baserel->fdw_private; ListCell* lc = NULL; + db2Entry(1,"> db2GetForeignRelSize.c::db2PopulateFdwStateNew"); /* Base foreign tables need to be pushed down always. */ fpinfo->pushdown_safe = true; @@ -195,6 +198,7 @@ static void db2PopulateFdwStateNew(PlannerInfo* root, RelOptInfo* baserel, Oid f fpinfo->hidden_subquery_rels = NULL; /* Set the relation index. */ fpinfo->relation_index = baserel->relid; + db2Exit(1,"< db2GetForeignRelSize.c::db2PopulateFdwStateNew"); } /* Parse options from foreign server and apply them to fpinfo. @@ -203,6 +207,7 @@ static void db2PopulateFdwStateNew(PlannerInfo* root, RelOptInfo* baserel, Oid f static void apply_server_options(DB2FdwState* fpinfo) { ListCell* lc; + db2Entry(4,"> db2GetForeignRelSize.c::apply_server_options"); foreach(lc, fpinfo->fserver->options) { DefElem* def = (DefElem*) lfirst(lc); @@ -219,6 +224,7 @@ static void apply_server_options(DB2FdwState* fpinfo) { else if (strcmp(def->defname, "async_capable") == 0) fpinfo->async_capable = defGetBoolean(def); } + db2Exit(4,"< db2GetForeignRelSize.c::apply_server_options"); } /* Parse options from foreign table and apply them to fpinfo. @@ -227,6 +233,7 @@ static void apply_server_options(DB2FdwState* fpinfo) { static void apply_table_options(DB2FdwState* fpinfo) { ListCell* lc; + db2Entry(4,"> db2GetForeignRelSize.c::apply_table_options"); foreach(lc, fpinfo->ftable->options) { DefElem* def = (DefElem*) lfirst(lc); @@ -237,6 +244,7 @@ static void apply_table_options(DB2FdwState* fpinfo) { else if (strcmp(def->defname, "async_capable") == 0) fpinfo->async_capable = defGetBoolean(def); } + db2Exit(4,"< db2GetForeignRelSize.c::apply_table_options"); } /* Parse a comma-separated string and return a List of the OIDs of the extensions named in the string. If any names in the list cannot be @@ -247,6 +255,7 @@ static List * ExtractExtensionList(const char *extensionsString, bool warnOnMiss List* extlist = NIL; ListCell* lc = NULL; + db2Entry(4,"> db2GetForeignRelSize.c::ExtractExtensionList"); /* SplitIdentifierString scribbles on its input, so pstrdup first */ if (!SplitIdentifierString(pstrdup(extensionsString), ',', &extlist)) { /* syntax error in name list */ @@ -264,5 +273,6 @@ static List * ExtractExtensionList(const char *extensionsString, bool warnOnMiss } } list_free(extlist); + db2Exit(4,"< db2GetForeignRelSize.c::ExtractExtensionList : %x", extensionOids); return extensionOids; } diff --git a/source/db2GetForeignUpperPaths.c b/source/db2GetForeignUpperPaths.c index 47b6b1c..8a0d3a1 100644 --- a/source/db2GetForeignUpperPaths.c +++ b/source/db2GetForeignUpperPaths.c @@ -18,9 +18,9 @@ #include "DB2FdwPathExtraData.h" /** external prototypes */ -extern void db2Debug1 (const char* message, ...); -extern void db2Debug2 (const char* message, ...); -extern void db2Debug3 (const char* message, ...); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); +extern void db2Debug (int level, const char* message, ...); extern void* db2alloc (const char* type, size_t size); extern char* db2strdup (const char* source); extern void* db2free (void* p); @@ -50,98 +50,98 @@ static bool foreign_grouping_ok (PlannerInfo* root, RelOptInfo* gr static void merge_fdw_options (DB2FdwState* fpinfo, const DB2FdwState* fpinfo_o, const DB2FdwState* fpinfo_i); void db2GetForeignUpperPaths(PlannerInfo *root, UpperRelationKind stage, RelOptInfo *input_rel, RelOptInfo *output_rel, void *extra) { - db2Debug1("> %s::db2GetForeignUpperPaths",__FILE__); + db2Entry(1,"> db2GetForeignUpperPaths.c::db2GetForeignUpperPaths"); if (root != NULL && root->parse != NULL && input_rel->fdw_private != NULL && output_rel->fdw_private == NULL) { Query* query = root->parse; - db2Debug3(" query->hasAggs : %s", query->hasAggs ? "true" : "false"); - db2Debug3(" query->hasWindowFuncs : %s", query->hasWindowFuncs ? "true" : "false"); - db2Debug3(" query->hasDistinctOn : %s", query->hasDistinctOn ? "true" : "false"); - db2Debug3(" query->hasTargetSRFs : %s", query->hasTargetSRFs ? "true" : "false"); - db2Debug3(" query->hasForUpdate : %s", query->hasForUpdate ? "true" : "false"); + db2Debug(3,"query->hasAggs : %s", query->hasAggs ? "true" : "false"); + db2Debug(3,"query->hasWindowFuncs : %s", query->hasWindowFuncs ? "true" : "false"); + db2Debug(3,"query->hasDistinctOn : %s", query->hasDistinctOn ? "true" : "false"); + db2Debug(3,"query->hasTargetSRFs : %s", query->hasTargetSRFs ? "true" : "false"); + db2Debug(3,"query->hasForUpdate : %s", query->hasForUpdate ? "true" : "false"); #if PG_VERSION_NUM >= 180000 - db2Debug3(" query->hasGroupRTE : %s", query->hasGroupRTE ? "true" : "false"); + db2Debug(3,"query->hasGroupRTE : %s", query->hasGroupRTE ? "true" : "false"); #endif - db2Debug3(" query->hasModifyingCTE: %s", query->hasModifyingCTE ? "true" : "false"); - db2Debug3(" query->hasRecursive : %s", query->hasRecursive ? "true" : "false"); - db2Debug3(" query->hasSubLinks : %s", query->hasSubLinks ? "true" : "false"); - db2Debug3(" query->hasRowSecurity : %s", query->hasRowSecurity ? "true" : "false"); + db2Debug(3,"query->hasModifyingCTE: %s", query->hasModifyingCTE ? "true" : "false"); + db2Debug(3,"query->hasRecursive : %s", query->hasRecursive ? "true" : "false"); + db2Debug(3,"query->hasSubLinks : %s", query->hasSubLinks ? "true" : "false"); + db2Debug(3,"query->hasRowSecurity : %s", query->hasRowSecurity ? "true" : "false"); // if (db2_is_shippable(root, stage, input_rel, output_rel)) { db2CloneFdwStateUpper(root, input_rel, output_rel); switch (stage) { case UPPERREL_SETOP: // UNION/INTERSECT/EXCEPT - db2Debug2(" stage: %d - UPPERREL_SETOP", stage); + db2Debug(2,"stage: %d - UPPERREL_SETOP", stage); break; case UPPERREL_PARTIAL_GROUP_AGG: // partial grouping/aggregation - db2Debug2(" stage: %d - UPPERREL_PARTIAL_GROUP_AGG", stage); - db2Debug2(" query->hasAggs: %d", query->hasAggs); - db2Debug2(" query->groupClause: %x", query->groupClause); + db2Debug(2,"stage: %d - UPPERREL_PARTIAL_GROUP_AGG", stage); + db2Debug(2,"query->hasAggs: %d", query->hasAggs); + db2Debug(2,"query->groupClause: %x", query->groupClause); if (query->hasAggs || query->groupClause != NIL) { add_foreign_grouping_paths(root, input_rel, output_rel, (GroupPathExtraData*) extra); } break; case UPPERREL_GROUP_AGG: { // grouping/aggregation - db2Debug2(" stage: %d - UPPERREL_GROUP_AGG", stage); - db2Debug2(" query->hasAggs: %d", query->hasAggs); - db2Debug2(" query->groupClause: %x", query->groupClause); + db2Debug(2,"stage: %d - UPPERREL_GROUP_AGG", stage); + db2Debug(2,"query->hasAggs: %d", query->hasAggs); + db2Debug(2,"query->groupClause: %x", query->groupClause); if (query->hasAggs || query->groupClause != NIL) { add_foreign_grouping_paths(root, input_rel, output_rel, (GroupPathExtraData*) extra); } } break; case UPPERREL_WINDOW: { // window functions - db2Debug2(" stage: %d - UPPERREL_WINDOW", stage); - db2Debug2(" query->hasWindowFuncs: %d", query->hasWindowFuncs); + db2Debug(2,"stage: %d - UPPERREL_WINDOW", stage); + db2Debug(2,"query->hasWindowFuncs: %d", query->hasWindowFuncs); if (query->hasWindowFuncs) { - db2Debug2(" window function push down not yet implemented"); + db2Debug(2,"window function push down not yet implemented"); } } break; #if PG_VERSION_NUM >= 150000 case UPPERREL_PARTIAL_DISTINCT: { // partial "SELECT DISTINCT" - db2Debug2(" stage: %d - UPPERREL_PARTIAL_DISTINCT", stage); - db2Debug2(" query->hasDistinctOn: %d", query->hasDistinctOn); + db2Debug(2,"stage: %d - UPPERREL_PARTIAL_DISTINCT", stage); + db2Debug(2,"query->hasDistinctOn: %d", query->hasDistinctOn); if (query->hasDistinctOn) { - db2Debug2(" distinct function push down not yet implemented"); + db2Debug(2,"distinct function push down not yet implemented"); } } break; #endif case UPPERREL_DISTINCT: { // "SELECT DISTINCT" - db2Debug2(" stage: %d - UPPERREL_DISTINCT", stage); - db2Debug2(" query->hasDistinctOn: %d", query->hasDistinctOn); + db2Debug(2,"stage: %d - UPPERREL_DISTINCT", stage); + db2Debug(2,"query->hasDistinctOn: %d", query->hasDistinctOn); if (query->hasDistinctOn) { - db2Debug2(" distinct function push down not yet implemented"); + db2Debug(2,"distinct function push down not yet implemented"); } } break; case UPPERREL_ORDERED: // ORDER BY - db2Debug2(" stage: %d - UPPERREL_ORDERED", stage); - db2Debug2(" query->setOperations: %x", query->setOperations); + db2Debug(2,"stage: %d - UPPERREL_ORDERED", stage); + db2Debug(2,"query->setOperations: %x", query->setOperations); if (query->setOperations != NULL) { add_foreign_ordered_paths(root, input_rel, output_rel); } break; case UPPERREL_FINAL: // any remaining top-level actions - db2Debug2(" stage: %d - UPPERREL_FINAL", stage); + db2Debug(2,"stage: %d - UPPERREL_FINAL", stage); add_foreign_final_paths(root, input_rel, output_rel, (FinalPathExtraData*) extra); break; default: // unknown stage type - db2Debug2(" stage: %d - unknown", stage); + db2Debug(2,"stage: %d - unknown", stage); break; } // } else { -// db2Debug2(" stage and or functions are not shippable to DB2"); +// db2Debug(2,"stage and or functions are not shippable to DB2"); // } } else { - db2Debug2(" skipping this call"); - db2Debug2(" root: %x", root); - db2Debug2(" root->parse: %x", root->parse); - db2Debug2(" input_rel->fdw_private: %x", input_rel->fdw_private); - db2Debug2(" output_rel->fdw_private: %x", output_rel->fdw_private); + db2Debug(2,"skipping this call"); + db2Debug(2,"root: %x", root); + db2Debug(2,"root->parse: %x", root->parse); + db2Debug(2,"input_rel->fdw_private: %x", input_rel->fdw_private); + db2Debug(2,"output_rel->fdw_private: %x", output_rel->fdw_private); } - db2Debug1("< %s::db2GetForeignUpperPaths",__FILE__); + db2Exit(1,"< db2GetForeignUpperPaths.c::db2GetForeignUpperPaths"); } /** db2CloneFdwStateUpper @@ -155,7 +155,7 @@ static void db2CloneFdwStateUpper(PlannerInfo* root, RelOptInfo* input_rel, RelO DB2FdwState* fdw_in = (DB2FdwState*)input_rel->fdw_private; DB2FdwState* copy = NULL; - db2Debug1("> %s::db2CloneFdwStateUpper", __FILE__); + db2Entry(4,"> db2GetForeignUpperPaths.c::db2CloneFdwStateUpper"); if (fdw_in != NULL) { copy = (DB2FdwState*) db2alloc("fdw_state_upper", sizeof(DB2FdwState)); @@ -197,14 +197,14 @@ static void db2CloneFdwStateUpper(PlannerInfo* root, RelOptInfo* input_rel, RelO copy->paramList = NULL; } output_rel->fdw_private = copy; - db2Debug1("< %s::db2CloneFdwStateUpper", __FILE__); + db2Exit(4,"< db2GetForeignUpperPaths.c::db2CloneFdwStateUpper"); } static DB2Table* db2CloneDb2TableForPlan(const DB2Table* src) { DB2Table* dst = NULL; int i; - db2Debug1("> %s::db2CloneDb2TableForPlan", __FILE__); + db2Entry(4,"> db2GetForeignUpperPaths.c::db2CloneDb2TableForPlan"); if (src != NULL) { dst = (DB2Table*) db2alloc("db2_table_clone", sizeof(DB2Table)); @@ -223,14 +223,14 @@ static DB2Table* db2CloneDb2TableForPlan(const DB2Table* src) { dst->cols = NULL; } } - db2Debug1("< %s::db2CloneDb2TableForPlan : %x", __FILE__, dst); + db2Exit(4,"< db2GetForeignUpperPaths.c::db2CloneDb2TableForPlan : %x", dst); return dst; } static DB2Column* db2CloneDb2ColumnForPlan(const DB2Column* src) { DB2Column* dst = NULL; - db2Debug1("> %s::db2CloneDb2ColumnForPlan", __FILE__); + db2Entry(4,"> db2GetForeignUpperPaths.c::db2CloneDb2ColumnForPlan"); if (src != NULL) { dst = (DB2Column*) db2alloc("db2_column_clone", sizeof(DB2Column)); /* start with a struct copy, then fix up pointer members */ @@ -239,116 +239,13 @@ static DB2Column* db2CloneDb2ColumnForPlan(const DB2Column* src) { dst->colName = src->colName ? db2strdup(src->colName) : NULL; dst->pgname = src->pgname ? db2strdup(src->pgname) : NULL; } - db2Debug1("< %s::db2CloneDb2ColumnForPlan : %x", __FILE__, dst); + db2Exit(4,"< db2GetForeignUpperPaths.c::db2CloneDb2ColumnForPlan : %x", dst); return dst; } -//static bool db2_is_shippable(PlannerInfo* root, UpperRelationKind stage, RelOptInfo* input_rel, RelOptInfo* output_rel) { -// bool fResult = false; -// DB2FdwState* fdw_in = (DB2FdwState*)input_rel->fdw_private; -// -// db2Debug1("> %s::db2_is_shippable", __FILE__); -// if (root == NULL || root->parse == NULL || input_rel == NULL || output_rel == NULL || fdw_in == NULL) { -// db2Debug2(" missing context; not shippable"); -// fResult = false; -// } else { -// Query* query = query = root->parse; -// /* Conservatively reject query shapes we don't yet know how to translate. -// * (This can be relaxed as we add DB2 SQL support.) -// */ -// if (query->hasSubLinks || query->hasWindowFuncs || query->hasDistinctOn || query->hasTargetSRFs || query->hasForUpdate || query->hasModifyingCTE || query->hasRecursive || query->hasRowSecurity) { -// db2Debug2(" query has unsupported features; not shippable"); -// fResult = false; -// } else { -// switch (stage) { -// case UPPERREL_PARTIAL_GROUP_AGG: -// case UPPERREL_GROUP_AGG: { -// ListCell* lc = NULL; -// -// fResult = true; -// /* 1) GROUP BY expressions must be deparsable. */ -// foreach (lc, query->groupClause) { -// SortGroupClause* grp = (SortGroupClause*) lfirst(lc); -// TargetEntry* tle = get_sortgroupclause_tle(grp, query->targetList); -// if (tle == NULL || tle->expr == NULL) { -// db2Debug2(" missing GROUP BY target entry; not shippable"); -// fResult = false; -// break; -// } -// if (!db2_is_shippable_expr(root, input_rel, (Expr*) tle->expr, "GROUP BY")) { -// fResult = false; -// break; -// } -// } -// -// if (fResult) { -// /* 2) HAVING clause must be deparsable (if present). */ -// if (query->havingQual != NULL) { -// if (!db2_is_shippable_expr(root, input_rel, (Expr*) query->havingQual, "HAVING")) { -// fResult = false; -// } -// } -// } -// -// if (fResult) { -// /* 3) Output target expressions must be deparsable too. */ -// foreach (lc, output_rel->reltarget->exprs) { -// Expr* expr = (Expr*) lfirst(lc); -// if (!db2_is_shippable_expr(root, input_rel, expr, "SELECT")) { -// fResult = false; -// break; -// } -// } -// } -// } -// break; -// default: { -// db2Debug2(" stage %d not supported; not shippable", stage); -// fResult = false; -// } -// break; -// } -// } -// } -// db2Debug1("< %s::db2_is_shippable : %s", __FILE__, fResult ? "true" : "false"); -// return fResult; -//} - - -//static bool db2_is_shippable_expr(PlannerInfo* root, RelOptInfo* foreignrel, Expr* expr, const char* label) { -// bool fResult = false; -// DB2FdwState* fdw_in = (DB2FdwState*)foreignrel->fdw_private; -// -// db2Debug1("> %s::db2_is_shippable_expr", __FILE__); -// if (expr == NULL) { -// fResult = true; -// } else if (fdw_in == NULL) { -// fResult = false; -// } else if (contain_agg_clause((Node*) expr)) { -// List* params = NIL; -// char* deparsed = NULL; -// deparsed = deparseExpr(root, foreignrel, expr, ¶ms); -// db2Debug2(" deparsed: %s", deparsed); -// fResult = (deparsed != NULL); -// db2free(deparsed); -// } else if (contain_window_function((Node*) expr)) { -// db2Debug2(" %s contains window function; not shippable", label ? label : "expr"); -// fResult = false; -// } else { -// List* params = NIL; -// char* deparsed = NULL; -// deparsed = deparseExpr(root, foreignrel, expr, ¶ms); -// db2Debug2(" deparsed: %s", deparsed); -// fResult = (deparsed != NULL); -// db2free(deparsed); -// } -// db2Debug1("> %s::db2_is_shippable_expr : %s", __FILE__, fResult ? "true" : "false"); -// return fResult; -//} - -/** add_foreign_grouping_paths - * Add foreign path for grouping and/or aggregation. - * Given input_rel represents the underlying scan. The paths are added to the given grouped_rel. +/* add_foreign_grouping_paths + * Add foreign path for grouping and/or aggregation. + * Given input_rel represents the underlying scan. The paths are added to the given grouped_rel. */ static void add_foreign_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel, RelOptInfo *grouped_rel, GroupPathExtraData *extra) { Query* parse = root->parse; @@ -361,70 +258,70 @@ static void add_foreign_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel, Cost startup_cost; Cost total_cost; - db2Debug1("> %s::add_foreign_grouping_paths", __FILE__); - + db2Entry(4,"> db2GetForeignUpperPaths.c:::add_foreign_grouping_paths"); /* Nothing to be done, if there is no grouping or aggregation required. */ - if (!parse->groupClause && !parse->groupingSets && !parse->hasAggs && !root->hasHavingQual) - return; - - Assert(extra->patype == PARTITIONWISE_AGGREGATE_NONE || extra->patype == PARTITIONWISE_AGGREGATE_FULL); - /* save the input_rel as outerrel in fpinfo */ - fpinfo->outerrel = input_rel; - -// All of this is redundant since fpinfo is 1:1 clone of ifpinfo -// Copy foreign table, foreign server, user mapping, FDW options etc. details from the input relation's fpinfo. - fpinfo->ftable = ifpinfo->ftable; - fpinfo->fserver = ifpinfo->fserver; - fpinfo->fuser = ifpinfo->fuser; -// fpinfo->db2Table = db2CloneDb2TableForPlan(ifpinfo->db2Table); -// fpinfo->dbserver = db2strdup(ifpinfo->dbserver); -// fpinfo->user = db2strdup(ifpinfo->user); - merge_fdw_options(fpinfo, ifpinfo, NULL); - - /** Assess if it is safe to push down aggregation and grouping. - * - * Use HAVING qual from extra. In case of child partition, it will have translated Vars. - */ - if (!foreign_grouping_ok(root, grouped_rel, extra->havingQual)) - return; - /** Compute the selectivity and cost of the local_conds, so we don't have - * to do it over again for each path. (Currently we create just a single - * path here, but in future it would be possible that we build more paths - * such as pre-sorted paths as in postgresGetForeignPaths and - * postgresGetForeignJoinPaths.) The best we can do for these conditions - * is to estimate selectivity on the basis of local statistics. - */ - fpinfo->local_conds_sel = clauselist_selectivity(root, fpinfo->local_conds, 0, JOIN_INNER, NULL); - cost_qual_eval(&fpinfo->local_conds_cost, fpinfo->local_conds, root); - - /* Estimate the cost of push down */ - estimate_path_cost_size(root, grouped_rel, NIL, NIL, NULL, &rows, &width, &disabled_nodes, &startup_cost, &total_cost); - /* Now update this information in the fpinfo */ - fpinfo->rows = rows; - fpinfo->width = width; - fpinfo->disabled_nodes = disabled_nodes; - fpinfo->startup_cost = startup_cost; - fpinfo->total_cost = total_cost; - /* Create and add foreign path to the grouping relation. */ - grouppath = create_foreign_upper_path( root - , grouped_rel - , grouped_rel->reltarget - , rows - , disabled_nodes - , startup_cost - , total_cost - , NIL /* no pathkeys */ - , NULL - , NIL /* no fdw_restrictinfo list */ - , NIL); /* no fdw_private */ - /* Add generated path into grouped_rel by add_path(). */ - add_path(grouped_rel, (Path*) grouppath); - db2Debug1("< %s::add_foreign_grouping_paths", __FILE__); + if (!parse->groupClause && !parse->groupingSets && !parse->hasAggs && !root->hasHavingQual) { + db2Debug(5,"Nothing to be done, if there is no grouping or aggregation required"); + } else { + Assert(extra->patype == PARTITIONWISE_AGGREGATE_NONE || extra->patype == PARTITIONWISE_AGGREGATE_FULL); + /* save the input_rel as outerrel in fpinfo */ + fpinfo->outerrel = input_rel; + + // All of this is redundant since fpinfo is 1:1 clone of ifpinfo + // Copy foreign table, foreign server, user mapping, FDW options etc. details from the input relation's fpinfo. + fpinfo->ftable = ifpinfo->ftable; + fpinfo->fserver = ifpinfo->fserver; + fpinfo->fuser = ifpinfo->fuser; + //fpinfo->db2Table = db2CloneDb2TableForPlan(ifpinfo->db2Table); + //fpinfo->dbserver = db2strdup(ifpinfo->dbserver); + //fpinfo->user = db2strdup(ifpinfo->user); + merge_fdw_options(fpinfo, ifpinfo, NULL); + + /* Assess if it is safe to push down aggregation and grouping. + * Use HAVING qual from extra. In case of child partition, it will have translated Vars. + */ + if (!foreign_grouping_ok(root, grouped_rel, extra->havingQual)) { + db2Exit(5,"foreigm grouping is not ok"); + } else { + /* Compute the selectivity and cost of the local_conds, so we don't have to do it over again for each path. + * (Currently we create just a single path here, but in future it would be possible that we build more paths + * such as pre-sorted paths as in postgresGetForeignPaths and postgresGetForeignJoinPaths.) + * The best we can do for these conditions is to estimate selectivity on the basis of local statistics. + */ + fpinfo->local_conds_sel = clauselist_selectivity(root, fpinfo->local_conds, 0, JOIN_INNER, NULL); + cost_qual_eval(&fpinfo->local_conds_cost, fpinfo->local_conds, root); + + /* Estimate the cost of push down */ + estimate_path_cost_size(root, grouped_rel, NIL, NIL, NULL, &rows, &width, &disabled_nodes, &startup_cost, &total_cost); + /* Now update this information in the fpinfo */ + fpinfo->rows = rows; + fpinfo->width = width; + fpinfo->disabled_nodes = disabled_nodes; + fpinfo->startup_cost = startup_cost; + fpinfo->total_cost = total_cost; + /* Create and add foreign path to the grouping relation. */ + grouppath = create_foreign_upper_path( root + , grouped_rel + , grouped_rel->reltarget + , rows + , disabled_nodes + , startup_cost + , total_cost + , NIL /* no pathkeys */ + , NULL + , NIL /* no fdw_restrictinfo list */ + , NIL); /* no fdw_private */ + /* Add generated path into grouped_rel by add_path(). */ + add_path(grouped_rel, (Path*) grouppath); + } + } + db2Exit(4,"< db2GetForeignUpperPaths.c::add_foreign_grouping_paths"); } -/** add_foreign_ordered_paths - * Add foreign paths for performing the final sort remotely. - * Given input_rel contains the source-data Paths. The paths are added to the given ordered_rel. +/* add_foreign_ordered_paths + * Add foreign paths for performing the final sort remotely. + * Given input_rel contains the source-data Paths. + * The paths are added to the given ordered_rel. */ static void add_foreign_ordered_paths(PlannerInfo *root, RelOptInfo *input_rel, RelOptInfo *ordered_rel) { Query* parse = root->parse; @@ -440,93 +337,101 @@ static void add_foreign_ordered_paths(PlannerInfo *root, RelOptInfo *input_rel, ForeignPath* ordered_path; ListCell* lc; - db2Debug1("> %s::add_foreign_ordered_paths", __FILE__); + db2Entry(4,"> db2GetForeignUpperPaths.c::add_foreign_ordered_paths"); // Shouldn't get here unless the query has ORDER BY Assert(parse->sortClause); // We don't support cases where there are any SRFs in the targetlist - if (parse->hasTargetSRFs) - return; - // Save the input_rel as outerrel in fpinfo - fpinfo->outerrel = input_rel; + if (parse->hasTargetSRFs) { + db2Debug(5,"no support where there are any SRFs in the targetlist"); + } else { + bool isSafe = true; - // Copy foreign table, foreign server, user mapping, FDW options etc. details from the input relation's fpinfo. - fpinfo->ftable = ifpinfo->ftable; - fpinfo->fserver = ifpinfo->fserver; - fpinfo->fuser = ifpinfo->fuser; - merge_fdw_options(fpinfo, ifpinfo, NULL); + // Save the input_rel as outerrel in fpinfo + fpinfo->outerrel = input_rel; - /** If the input_rel is a base or join relation, we would already have - * considered pushing down the final sort to the remote server when - * creating pre-sorted foreign paths for that relation, because the - * query_pathkeys is set to the root->sort_pathkeys in that case (see - * standard_qp_callback()). - */ - if (input_rel->reloptkind == RELOPT_BASEREL || input_rel->reloptkind == RELOPT_JOINREL) { - Assert(root->query_pathkeys == root->sort_pathkeys); - /* Safe to push down if the query_pathkeys is safe to push down */ - fpinfo->pushdown_safe = ifpinfo->qp_is_pushdown_safe; - return; - } - // The input_rel should be a grouping relation - Assert(input_rel->reloptkind == RELOPT_UPPER_REL && ifpinfo->stage == UPPERREL_GROUP_AGG); - /** We try to create a path below by extending a simple foreign path for - * the underlying grouping relation to perform the final sort remotely, - * which is stored into the fdw_private list of the resulting path. - */ - // Assess if it is safe to push down the final sort - foreach(lc, root->sort_pathkeys) { - PathKey *pathkey = (PathKey *) lfirst(lc); - EquivalenceClass *pathkey_ec = pathkey->pk_eclass; - /** is_foreign_expr would detect volatile expressions as well, but - * checking ec_has_volatile here saves some cycles. - */ - if (pathkey_ec->ec_has_volatile) - return; - /** Can't push down the sort if pathkey's opfamily is not shippable. - */ - if (!is_shippable(pathkey->pk_opfamily, OperatorFamilyRelationId, fpinfo)) - return; - /** The EC must contain a shippable EM that is computed in input_rel's - * reltarget, else we can't push down the sort. + // Copy foreign table, foreign server, user mapping, FDW options etc. details from the input relation's fpinfo. + fpinfo->ftable = ifpinfo->ftable; + fpinfo->fserver = ifpinfo->fserver; + fpinfo->fuser = ifpinfo->fuser; + merge_fdw_options(fpinfo, ifpinfo, NULL); + + /* If the input_rel is a base or join relation, we would already have considered pushing down the final sort to the remote server when + * creating pre-sorted foreign paths for that relation, because the query_pathkeys is set to the root->sort_pathkeys in that case (see + * standard_qp_callback()). */ - if (find_em_for_rel_target(root, pathkey_ec, input_rel) == NULL) - return; + if (input_rel->reloptkind == RELOPT_BASEREL || input_rel->reloptkind == RELOPT_JOINREL) { + Assert(root->query_pathkeys == root->sort_pathkeys); + /* Safe to push down if the query_pathkeys is safe to push down */ + fpinfo->pushdown_safe = ifpinfo->qp_is_pushdown_safe; + db2Debug(5,"query_pathkeys is safe to push down"); + } else { + // The input_rel should be a grouping relation + Assert(input_rel->reloptkind == RELOPT_UPPER_REL && ifpinfo->stage == UPPERREL_GROUP_AGG); + /* We try to create a path below by extending a simple foreign path for the underlying grouping relation to perform the final sort remotely, + * which is stored into the fdw_private list of the resulting path. + */ + foreach(lc, root->sort_pathkeys) { + PathKey* pathkey = (PathKey*) lfirst(lc); + EquivalenceClass* pathkey_ec = pathkey->pk_eclass; + /* is_foreign_expr would detect volatile expressions as well, but checking ec_has_volatile here saves some cycles. */ + if (pathkey_ec->ec_has_volatile) { + db2Debug(5,"ec_has_volatile is true"); + isSafe = false; + break; + } + /* Can't push down the sort if pathkey's opfamily is not shippable. */ + if (!is_shippable(pathkey->pk_opfamily, OperatorFamilyRelationId, fpinfo)) { + db2Debug(5,"pathkey's opfamily is not shippable"); + isSafe = false; + break; + } + /* The EC must contain a shippable EM that is computed in input_rel's reltarget, else we can't push down the sort. */ + if (find_em_for_rel_target(root, pathkey_ec, input_rel) == NULL) { + db2Debug(5,"non shippable EM that is computed in input_rel's reltarget"); + isSafe = false; + break; + } + } + if (isSafe) { + /* Safe to push down */ + fpinfo->pushdown_safe = true; + /* Construct PgFdwPathExtraData */ + fpextra = palloc0_object(DB2FdwPathExtraData); + fpextra->target = root->upper_targets[UPPERREL_ORDERED]; + fpextra->has_final_sort = true; + /* Estimate the costs of performing the final sort remotely */ + estimate_path_cost_size(root, input_rel, NIL, root->sort_pathkeys, fpextra, &rows, &width, &disabled_nodes, &startup_cost, &total_cost); + /* + * Build the fdw_private list that will be used by postgresGetForeignPlan. + * Items in the list must match order in enum FdwPathPrivateIndex. + */ + fdw_private = list_make2(makeBoolean(true), makeBoolean(false)); + /* Create foreign ordering path */ + ordered_path = create_foreign_upper_path( root + , input_rel + , root->upper_targets[UPPERREL_ORDERED] + , rows + , disabled_nodes + , startup_cost + , total_cost + , root->sort_pathkeys + , NULL /* no extra plan */ + , NIL /* no fdw_restrictinfo list */ + , fdw_private); + /* and add it to the ordered_rel */ + add_path(ordered_rel, (Path *) ordered_path); + } + } } - /* Safe to push down */ - fpinfo->pushdown_safe = true; - /* Construct PgFdwPathExtraData */ - fpextra = palloc0_object(DB2FdwPathExtraData); - fpextra->target = root->upper_targets[UPPERREL_ORDERED]; - fpextra->has_final_sort = true; - /* Estimate the costs of performing the final sort remotely */ - estimate_path_cost_size(root, input_rel, NIL, root->sort_pathkeys, fpextra, &rows, &width, &disabled_nodes, &startup_cost, &total_cost); - /* - * Build the fdw_private list that will be used by postgresGetForeignPlan. - * Items in the list must match order in enum FdwPathPrivateIndex. - */ - fdw_private = list_make2(makeBoolean(true), makeBoolean(false)); - /* Create foreign ordering path */ - ordered_path = create_foreign_upper_path( root - , input_rel - , root->upper_targets[UPPERREL_ORDERED] - , rows - , disabled_nodes - , startup_cost - , total_cost - , root->sort_pathkeys - , NULL /* no extra plan */ - , NIL /* no fdw_restrictinfo list */ - , fdw_private); - /* and add it to the ordered_rel */ - add_path(ordered_rel, (Path *) ordered_path); - db2Debug1("< %s::add_foreign_ordered_paths", __FILE__); + db2Exit(4,"< db2GetForeignUpperPaths.c::add_foreign_ordered_paths"); } -/** add_foreign_final_paths - * Add foreign paths for performing the final processing remotely. - * Given input_rel contains the source-data Paths. The paths are added to the given final_rel. +/* add_foreign_final_paths + * Add foreign paths for performing the final processing remotely. + * Given input_rel contains the source-data Paths. + * The paths are added to the given final_rel. */ static void add_foreign_final_paths(PlannerInfo *root, RelOptInfo *input_rel, RelOptInfo *final_rel, FinalPathExtraData *extra) { Query* parse = root->parse; @@ -544,19 +449,28 @@ static void add_foreign_final_paths(PlannerInfo *root, RelOptInfo *input_rel, Re List* fdw_private = NIL; ForeignPath* final_path = NULL; - db2Debug1("> %s::add_foreign_ordered_paths", __FILE__); + db2Entry(4,"> db2GetForeignUpperPaths.c::add_foreign_ordered_paths"); /** Currently, we only support this for SELECT commands */ - if (parse->commandType != CMD_SELECT) + if (parse->commandType != CMD_SELECT) { + db2Debug(5,"only support SELECT command"); + db2Exit(4,"< db2GetForeignUpperPaths.c::add_foreign_ordered_paths"); return; + } // No work if there is no FOR UPDATE/SHARE clause and if there is no need to add a LIMIT node - if (!parse->rowMarks && !extra->limit_needed) + if (!parse->rowMarks && !extra->limit_needed) { + db2Debug(5,"no FOR UPDATE/SHARE clause and no need to add a LIMIT node"); + db2Exit(4,"< db2GetForeignUpperPaths.c::add_foreign_ordered_paths"); return; + } // We don't support cases where there are any SRFs in the targetlist - if (parse->hasTargetSRFs) + if (parse->hasTargetSRFs) { + db2Debug(5,"no support for any SRFs in the targetlist"); + db2Exit(4,"< db2GetForeignUpperPaths.c::add_foreign_ordered_paths"); return; + } /* Save the input_rel as outerrel in fpinfo */ fpinfo->outerrel = input_rel; @@ -567,31 +481,25 @@ static void add_foreign_final_paths(PlannerInfo *root, RelOptInfo *input_rel, Re fpinfo->fuser = ifpinfo->fuser; merge_fdw_options(fpinfo, ifpinfo, NULL); - /** If there is no need to add a LIMIT node, there might be a ForeignPath - * in the input_rel's pathlist that implements all behavior of the query. - * Note: we would already have accounted for the query's FOR UPDATE/SHARE - * (if any) before we get here. + /* If there is no need to add a LIMIT node, there might be a ForeignPath in the input_rel's pathlist that implements all behavior of the query. + * Note: we would already have accounted for the query's FOR UPDATE/SHARE (if any) before we get here. */ if (!extra->limit_needed) { ListCell *lc; Assert(parse->rowMarks); - /** Grouping and aggregation are not supported with FOR UPDATE/SHARE, - * so the input_rel should be a base, join, or ordered relation; and - * if it's an ordered relation, its input relation should be a base or - * join relation. + /* Grouping and aggregation are not supported with FOR UPDATE/SHARE, so the input_rel should be a base, join, or ordered relation; and + * if it's an ordered relation, its input relation should be a base or join relation. */ Assert(input_rel->reloptkind == RELOPT_BASEREL || input_rel->reloptkind == RELOPT_JOINREL || (input_rel->reloptkind == RELOPT_UPPER_REL && ifpinfo->stage == UPPERREL_ORDERED && (ifpinfo->outerrel->reloptkind == RELOPT_BASEREL || ifpinfo->outerrel->reloptkind == RELOPT_JOINREL))); foreach(lc, input_rel->pathlist) { Path* path = (Path*) lfirst(lc); - /** apply_scanjoin_target_to_paths() uses create_projection_path() - * to adjust each of its input paths if needed, whereas - * create_ordered_paths() uses apply_projection_to_path() to do - * that. So the former might have put a ProjectionPath on top of - * the ForeignPath; look through ProjectionPath and see if the + /* apply_scanjoin_target_to_paths() uses create_projection_path() to adjust each of its input paths if needed, whereas + * create_ordered_paths() uses apply_projection_to_path() to do that. + * So the former might have put a ProjectionPath on top of the ForeignPath; look through ProjectionPath and see if the * path underneath it is ForeignPath. */ if (IsA(path, ForeignPath) || (IsA(path, ProjectionPath) && IsA(((ProjectionPath *) path)->subpath, ForeignPath))) { @@ -613,8 +521,8 @@ static void add_foreign_final_paths(PlannerInfo *root, RelOptInfo *input_rel, Re /* Safe to push down */ fpinfo->pushdown_safe = true; - - db2Debug1("< %s::add_foreign_ordered_paths", __FILE__); + db2Debug(5,"created foreign final path; this gets rid of a no-longer-needed outer plan (if any), which makes the EXPLAIN output look cleaner"); + db2Exit(4,"< db2GetForeignUpperPaths.c::add_foreign_ordered_paths"); return; } } @@ -622,14 +530,15 @@ static void add_foreign_final_paths(PlannerInfo *root, RelOptInfo *input_rel, Re /* If we get here it means no ForeignPaths; since we would already have considered pushing down all operations for the query to the * remote server, give up on it. */ - db2Debug1("< %s::add_foreign_ordered_paths", __FILE__); + db2Debug(5,"no ForeignPaths; since we would already have considered pushing down all operations for the query to the remote server, give up on it"); + db2Exit(4,"< db2GetForeignUpperPaths.c::add_foreign_ordered_paths"); return; } Assert(extra->limit_needed); // If the input_rel is an ordered relation, replace the input_rel with its input relation - if (input_rel->reloptkind == RELOPT_UPPER_REL && ifpinfo->stage == UPPERREL_ORDERED) { + if (input_rel->reloptkind == RELOPT_UPPER_REL && ifpinfo->stage == UPPERREL_ORDERED) { input_rel = ifpinfo->outerrel; ifpinfo = (DB2FdwState*) input_rel->fdw_private; has_final_sort = true; @@ -648,23 +557,27 @@ static void add_foreign_final_paths(PlannerInfo *root, RelOptInfo *input_rel, Re /* If the underlying relation has any local conditions, the LIMIT/OFFSET cannot be pushed down. */ if (ifpinfo->local_conds) { - db2Debug1("< %s::add_foreign_ordered_paths", __FILE__); + db2Debug(5,"the underlying relation has any local conditions, the LIMIT/OFFSET cannot be pushed down"); + db2Exit(4,"< db2GetForeignUpperPaths.c::add_foreign_ordered_paths"); return; } - /* If the query has FETCH FIRST .. WITH TIES, 1) it must have ORDER BY as well, which is used to determine which additional rows tie for the last - * place in the result set, and 2) ORDER BY must already have been determined to be safe to push down before we get here. + /* If the query has FETCH FIRST .. WITH TIES, + * 1) it must have ORDER BY as well, which is used to determine which additional rows tie for the last place in the result set, and + * 2) ORDER BY must already have been determined to be safe to push down before we get here. * So in that case the FETCH clause is safe to push down with ORDER BY if the remote server is v13 or later, but if not, the remote query will fail * entirely for lack of support for it. * Since we do not currently have a way to do a remote-version check (without accessing the remote server), disable pushing the FETCH clause for now. */ if (parse->limitOption == LIMIT_OPTION_WITH_TIES) { - db2Debug1("< %s::add_foreign_ordered_paths", __FILE__); + db2Debug(5,"the query has FETCH FIRST .. WITH TIES without ORDER BY"); + db2Exit(4,"< db2GetForeignUpperPaths.c::add_foreign_ordered_paths"); return; } /* Also, the LIMIT/OFFSET cannot be pushed down, if their expressions are not safe to remote. */ if (!is_foreign_expr(root, input_rel, (Expr *) parse->limitOffset) || !is_foreign_expr(root, input_rel, (Expr *) parse->limitCount)) { - db2Debug1("< %s::add_foreign_ordered_paths", __FILE__); + db2Debug(5,"the LIMIT/OFFSET cannot be pushed down, if their expressions are not safe to remote"); + db2Exit(4,"< db2GetForeignUpperPaths.c::add_foreign_ordered_paths"); return; } @@ -711,12 +624,12 @@ static void add_foreign_final_paths(PlannerInfo *root, RelOptInfo *input_rel, Re /* and add it to the final_rel */ add_path(final_rel, (Path*) final_path); - db2Debug1(" %s::add_foreign_ordered_paths", __FILE__); + db2Exit(4,"< db2GetForeignUpperPaths.c::add_foreign_ordered_paths"); } /* Adjust the cost estimates of a foreign grouping path to include the cost of generating properly-sorted output. */ static void adjust_foreign_grouping_path_cost(PlannerInfo* root, List* pathkeys, double retrieved_rows, double width, double limit_tuples, int* p_disabled_nodes, Cost* p_startup_cost, Cost* p_run_cost) { - db2Debug1("> %s::adjust_foreign_grouping_path_cost", __FILE__); + db2Entry(4,"> db2GetForeignUpperPaths.c::adjust_foreign_grouping_path_cost"); /* If the GROUP BY clause isn't sort-able, the plan chosen by the remote side is unlikely to generate properly-sorted output, so it would need * an explicit sort; adjust the given costs with cost_sort(). * Likewise, if the GROUP BY clause is sort-able but isn't a superset of the given pathkeys, adjust the costs with that function. @@ -736,13 +649,11 @@ static void adjust_foreign_grouping_path_cost(PlannerInfo* root, List* pathkeys, *p_startup_cost *= sort_multiplier; *p_run_cost *= sort_multiplier; } - db2Debug1("< %s::adjust_foreign_grouping_path_cost", __FILE__); + db2Exit(4,"< db2GetForeignUpperPaths.c::adjust_foreign_grouping_path_cost"); } -/* - * Assess whether the aggregation, grouping and having operations can be pushed - * down to the foreign server. As a side effect, save information we obtain in - * this function to PgFdwRelationInfo of the input relation. +/* Assess whether the aggregation, grouping and having operations can be pushed down to the foreign server. + * As a side effect, save information we obtain in this function to PgFdwRelationInfo of the input relation. */ static bool foreign_grouping_ok(PlannerInfo* root, RelOptInfo* grouped_rel, Node* havingQual) { Query* query = root->parse; @@ -753,38 +664,35 @@ static bool foreign_grouping_ok(PlannerInfo* root, RelOptInfo* grouped_rel, Node int i = 0; List* tlist = NIL; - db2Debug1("> %s::foreign_grouping_ok", __FILE__); + db2Entry(4,"> db2GetForeignUpperPaths.c::foreign_grouping_ok"); /* We currently don't support pushing Grouping Sets. */ - if (query->groupingSets) + if (query->groupingSets) { + db2Debug(5,"no support pushing Grouping Sets"); + db2Exit(4,"< db2GetForeignUpperPaths.c::foreign_grouping_ok"); return false; + } /* Get the fpinfo of the underlying scan relation. */ ofpinfo = (DB2FdwState*) fpinfo->outerrel->fdw_private; - /** If underlying scan relation has any local conditions, those conditions - * are required to be applied before performing aggregation. Hence the - * aggregate cannot be pushed down. + /* If underlying scan relation has any local conditions, those conditions are required to be applied before performing aggregation. + * Hence the aggregate cannot be pushed down. */ if (ofpinfo->local_conds) { - db2Debug2(" foreign_grouping_ok: local_conds found"); - db2Debug1("< %s::foreign_grouping_ok", __FILE__); + db2Debug(5,"foreign_grouping_ok: local_conds found"); + db2Exit(4,"< db2GetForeignUpperPaths.c::foreign_grouping_ok : false"); return false; } - /** Examine grouping expressions, as well as other expressions we'd need to - * compute, and check whether they are safe to push down to the foreign - * server. All GROUP BY expressions will be part of the grouping target - * and thus there is no need to search for them separately. Add grouping - * expressions into target list which will be passed to foreign server. + /* Examine grouping expressions, as well as other expressions we'd need to compute, and check whether they are safe to push down to the foreign server. + * All GROUP BY expressions will be part of the grouping target and thus there is no need to search for them separately. + * Add grouping expressions into target list which will be passed to foreign server. * - * A tricky fine point is that we must not put any expression into the - * target list that is just a foreign param (that is, something that - * deparse.c would conclude has to be sent to the foreign server). If we - * do, the expression will also appear in the fdw_exprs list of the plan - * node, and setrefs.c will get confused and decide that the fdw_exprs - * entry is actually a reference to the fdw_scan_tlist entry, resulting in - * a broken plan. Somewhat oddly, it's OK if the expression contains such - * a node, as long as it's not at top level; then no match is possible. + * A tricky fine point is that we must not put any expression into the target list that is just a foreign param + * (that is, something that deparse.c would conclude has to be sent to the foreign server). + * If we do, the expression will also appear in the fdw_exprs list of the plan node, and setrefs.c will get confused and decide that the fdw_exprs + * entry is actually a reference to the fdw_scan_tlist entry, resulting in a broken plan. + * Somewhat oddly, it's OK if the expression contains such a node, as long as it's not at top level; then no match is possible. */ i = 0; foreach(lc, grouping_target->exprs) { @@ -792,44 +700,37 @@ static bool foreign_grouping_ok(PlannerInfo* root, RelOptInfo* grouped_rel, Node Index sgref = get_pathtarget_sortgroupref(grouping_target, i); ListCell* l; - /** Check whether this expression is part of GROUP BY clause. Note we - * check the whole GROUP BY clause not just processed_groupClause, - * because we will ship all of it, cf. appendGroupByClause. + /* Check whether this expression is part of GROUP BY clause. + * Note we check the whole GROUP BY clause not just processed_groupClause, because we will ship all of it, cf. appendGroupByClause. */ if (sgref && get_sortgroupref_clause_noerr(sgref, query->groupClause)) { TargetEntry *tle; - /** If any GROUP BY expression is not shippable, then we cannot - * push down aggregation to the foreign server. - */ + /* If any GROUP BY expression is not shippable, then we cannot push down aggregation to the foreign server. */ if (!is_foreign_expr(root, grouped_rel, expr)) { - db2Debug2(" foreign_grouping_ok: non-foreign expr found"); - db2Debug1("< %s::foreign_grouping_ok", __FILE__); + db2Debug(5,"foreign_grouping_ok: non-foreign expr found"); + db2Exit(4,"< db2GetForeignUpperPaths.c::foreign_grouping_ok : false"); return false; } - /** If it would be a foreign param, we can't put it into the tlist, - * so we have to fail. - */ + /* If it would be a foreign param, we can't put it into the tlist, so we have to fail. */ if (is_foreign_param(root, grouped_rel, expr)) { - db2Debug2(" foreign_grouping_ok: foreign param found"); - db2Debug1("< %s::foreign_grouping_ok", __FILE__); + db2Debug(5,"foreign_grouping_ok: foreign param found"); + db2Exit(4,"< db2GetForeignUpperPaths.c::foreign_grouping_ok : false"); return false; } - /** Pushable, so add to tlist. We need to create a TLE for this - * expression and apply the sortgroupref to it. We cannot use - * add_to_flat_tlist() here because that avoids making duplicate - * entries in the tlist. If there are duplicate entries with - * distinct sortgrouprefs, we have to duplicate that situation in - * the output tlist. + /* Pushable, so add to tlist. + * We need to create a TLE for this expression and apply the sortgroupref to it. + * We cannot use add_to_flat_tlist() here because that avoids making duplicate entries in the tlist. + * If there are duplicate entries with distinct sortgrouprefs, we have to duplicate that situation in the output tlist. */ tle = makeTargetEntry(expr, list_length(tlist) + 1, NULL, false); tle->ressortgroupref = sgref; tlist = lappend(tlist, tle); } else { - /** Non-grouping expression we need to compute. Can we ship it - * as-is to the foreign server? + /* Non-grouping expression we need to compute. + * Can we ship it as-is to the foreign server? */ if (is_foreign_expr(root, grouped_rel, expr) && !is_foreign_param(root, grouped_rel, expr)) { /* Yes, so add to tlist as-is; OK to suppress duplicates */ @@ -838,25 +739,19 @@ static bool foreign_grouping_ok(PlannerInfo* root, RelOptInfo* grouped_rel, Node /* Not pushable as a whole; extract its Vars and aggregates */ List* aggvars = pull_var_clause((Node*) expr, PVC_INCLUDE_AGGREGATES); - /** If any aggregate expression is not shippable, then we - * cannot push down aggregation to the foreign server. (We - * don't have to check is_foreign_param, since that certainly - * won't return true for any such expression.) + /* If any aggregate expression is not shippable, then we cannot push down aggregation to the foreign server. + * (We don't have to check is_foreign_param, since that certainly won't return true for any such expression.) */ if (!is_foreign_expr(root, grouped_rel, (Expr *) aggvars)) { - db2Debug2(" foreign_grouping_ok: non-foreign aggvar found"); - db2Debug1("< %s::foreign_grouping_ok", __FILE__); + db2Debug(5,"foreign_grouping_ok: non-foreign aggvar found"); + db2Exit(4,"< db2GetForeignUpperPaths.c::foreign_grouping_ok : false"); return false; } - /** Add aggregates, if any, into the targetlist. Plain Vars - * outside an aggregate can be ignored, because they should be - * either same as some GROUP BY column or part of some GROUP - * BY expression. In either case, they are already part of - * the targetlist and thus no need to add them again. In fact - * including plain Vars in the tlist when they do not match a - * GROUP BY column would cause the foreign server to complain - * that the shipped query is invalid. + /* Add aggregates, if any, into the targetlist. + * Plain Vars outside an aggregate can be ignored, because they should be either same as some GROUP BY column or part of some GROUP BY expression. + * In either case, they are already part of the targetlist and thus no need to add them again. + * In fact including plain Vars in the tlist when they do not match a GROUP BY column would cause the foreign server to complain that the shipped query is invalid. */ foreach(l, aggvars) { Expr* aggref = (Expr *) lfirst(l); @@ -869,17 +764,13 @@ static bool foreign_grouping_ok(PlannerInfo* root, RelOptInfo* grouped_rel, Node i++; } - /** Classify the pushable and non-pushable HAVING clauses and save them in - * remote_conds and local_conds of the grouped rel's fpinfo. - */ + /* Classify the pushable and non-pushable HAVING clauses and save them in remote_conds and local_conds of the grouped rel's fpinfo. */ if (havingQual) { foreach(lc, (List *) havingQual) { Expr *expr = (Expr *) lfirst(lc); RestrictInfo *rinfo; - /** Currently, the core code doesn't wrap havingQuals in - * RestrictInfos, so we must make our own. - */ + /* Currently, the core code doesn't wrap havingQuals in RestrictInfos, so we must make our own. */ Assert(!IsA(expr, RestrictInfo)); rinfo = make_restrictinfo(root, expr, true, false, false, false, root->qual_security_level, grouped_rel->relids, NULL, NULL); if (is_foreign_expr(root, grouped_rel, expr)) @@ -889,9 +780,7 @@ static bool foreign_grouping_ok(PlannerInfo* root, RelOptInfo* grouped_rel, Node } } - /** If there are any local conditions, pull Vars and aggregates from it and - * check whether they are safe to pushdown or not. - */ + /* If there are any local conditions, pull Vars and aggregates from it and check whether they are safe to pushdown or not. */ if (fpinfo->local_conds) { List* aggvars = NIL; @@ -904,11 +793,9 @@ static bool foreign_grouping_ok(PlannerInfo* root, RelOptInfo* grouped_rel, Node foreach(lc, aggvars) { Expr *expr = (Expr *) lfirst(lc); - /** If aggregates within local conditions are not safe to push - * down, then we cannot push down the query. Vars are already - * part of GROUP BY clause which are checked above, so no need to - * access them again here. Again, we need not check - * is_foreign_param for a foreign aggregate. + /* If aggregates within local conditions are not safe to push down, then we cannot push down the query. + * Vars are already part of GROUP BY clause which are checked above, so no need to access them again here. + * Again, we need not check is_foreign_param for a foreign aggregate. */ if (IsA(expr, Aggref)) { if (!is_foreign_expr(root, grouped_rel, expr)) @@ -925,36 +812,30 @@ static bool foreign_grouping_ok(PlannerInfo* root, RelOptInfo* grouped_rel, Node /* Safe to pushdown */ fpinfo->pushdown_safe = true; - /** Set # of retrieved rows and cached relation costs to some negative - * value, so that we can detect when they are set to some sensible values, + /* Set # of retrieved rows and cached relation costs to some negative value, so that we can detect when they are set to some sensible values, * during one (usually the first) of the calls to estimate_path_cost_size. */ fpinfo->retrieved_rows = -1; fpinfo->rel_startup_cost = -1; fpinfo->rel_total_cost = -1; - /** Set the string describing this grouped relation to be used in EXPLAIN - * output of corresponding ForeignScan. Note that the decoration we add - * to the base relation name mustn't include any digits, or it'll confuse - * postgresExplainForeignScan. + /* Set the string describing this grouped relation to be used in EXPLAIN output of corresponding ForeignScan. + * Note that the decoration we add to the base relation name mustn't include any digits, or it'll confuse postgresExplainForeignScan. */ fpinfo->relation_name = psprintf("Aggregate on (%s)", ofpinfo->relation_name); - db2Debug1("< %s::foreign_grouping_ok", __FILE__); + db2Exit(4,"< db2GetForeignUpperPaths.c::foreign_grouping_ok : true"); return true; } -/** estimate_path_cost_size - * Get cost and size estimates for a foreign scan on given foreign relation - * either a base relation or a join between foreign relations or an upper - * relation containing foreign relations. +/* estimate_path_cost_size + * Get cost and size estimates for a foreign scan on given foreign relation either a base relation or a join between foreign relations or an upper + * relation containing foreign relations. * - * param_join_conds are the parameterization clauses with outer relations. - * pathkeys specify the expected sort order if any for given path being costed. - * fpextra specifies additional post-scan/join-processing steps such as the - * final sort and the LIMIT restriction. + * param_join_conds are the parameterization clauses with outer relations. + * pathkeys specify the expected sort order if any for given path being costed. + * fpextra specifies additional post-scan/join-processing steps such as the final sort and the LIMIT restriction. * - * The function returns the cost and size estimates in p_rows, p_width, - * p_disabled_nodes, p_startup_cost and p_total_cost variables. + * The function returns the cost and size estimates in p_rows, p_width, p_disabled_nodes, p_startup_cost and p_total_cost variables. */ void estimate_path_cost_size(PlannerInfo* root, RelOptInfo* foreignrel, List* param_join_conds, List* pathkeys, DB2FdwPathExtraData* fpextra, double* p_rows, int* p_width, int* p_disabled_nodes, Cost* p_startup_cost, Cost* p_total_cost) { DB2FdwState* fpinfo = (DB2FdwState*) foreignrel->fdw_private; @@ -965,13 +846,13 @@ void estimate_path_cost_size(PlannerInfo* root, RelOptInfo* foreignrel, List* pa Cost startup_cost; Cost total_cost; - db2Debug1("> %s::estimate_path_cost_size", __FILE__); + db2Entry(4,"> db2GetForeignUpperPaths.c::estimate_path_cost_size"); /* Make sure the core code has set up the relation's reltarget */ Assert(foreignrel->reltarget); /* If the table or the server is configured to use remote estimates, connect to the foreign server and execute EXPLAIN to estimate the - * number of rows selected by the restriction+join clauses. Otherwise, estimate rows using whatever statistics we have locally, in a way - * similar to ordinary tables. + * number of rows selected by the restriction+join clauses. + * Otherwise, estimate rows using whatever statistics we have locally, in a way similar to ordinary tables. */ if (fpinfo->use_remote_estimate) { List* remote_param_join_conds; @@ -1042,12 +923,9 @@ void estimate_path_cost_size(PlannerInfo* root, RelOptInfo* foreignrel, List* pa /* We don't support join conditions in this mode (hence, no parameterized paths can be made). */ Assert(param_join_conds == NIL); - /* - * We will come here again and again with different set of pathkeys or - * additional post-scan/join-processing steps that caller wants to - * cost. We don't need to calculate the cost/size estimates for the - * underlying scan, join, or grouping each time. Instead, use those - * estimates if we have cached them already. + /* We will come here again and again with different set of pathkeys or additional post-scan/join-processing steps that caller wants to + * cost. We don't need to calculate the cost/size estimates for the underlying scan, join, or grouping each time. + * Instead, use those estimates if we have cached them already. */ if (fpinfo->rel_startup_cost >= 0 && fpinfo->rel_total_cost >= 0) { Assert(fpinfo->retrieved_rows >= 0); @@ -1172,52 +1050,46 @@ void estimate_path_cost_size(PlannerInfo* root, RelOptInfo* foreignrel, List* pa rows = retrieved_rows = numGroups; } - /* Use width estimate made by the core code. */ - width = foreignrel->reltarget->width; - - /*----- - * Startup cost includes: - * 1. Startup cost for underneath input relation, adjusted for - * tlist replacement by apply_scanjoin_target_to_paths() - * 2. Cost of performing aggregation, per cost_agg() - *----- - */ - startup_cost = ofpinfo->rel_startup_cost; - startup_cost += outerrel->reltarget->cost.startup; - startup_cost += aggcosts.transCost.startup; - startup_cost += aggcosts.transCost.per_tuple * input_rows; - startup_cost += aggcosts.finalCost.startup; - startup_cost += (cpu_operator_cost * numGroupCols) * input_rows; - - /*----- - * Run time cost includes: - * 1. Run time cost of underneath input relation, adjusted for - * tlist replacement by apply_scanjoin_target_to_paths() - * 2. Run time cost of performing aggregation, per cost_agg() - *----- - */ - run_cost = ofpinfo->rel_total_cost - ofpinfo->rel_startup_cost; - run_cost += outerrel->reltarget->cost.per_tuple * input_rows; - run_cost += aggcosts.finalCost.per_tuple * numGroups; - run_cost += cpu_tuple_cost * numGroups; - - /* Account for the eval cost of HAVING quals, if any */ - if (root->hasHavingQual) - { - QualCost remote_cost; - - /* Add in the eval cost of the remotely-checked quals */ - cost_qual_eval(&remote_cost, fpinfo->remote_conds, root); - startup_cost += remote_cost.startup; - run_cost += remote_cost.per_tuple * numGroups; - /* Add in the eval cost of the locally-checked quals */ - startup_cost += fpinfo->local_conds_cost.startup; - run_cost += fpinfo->local_conds_cost.per_tuple * retrieved_rows; - } - - /* Add in tlist eval cost for each output row */ - startup_cost += foreignrel->reltarget->cost.startup; - run_cost += foreignrel->reltarget->cost.per_tuple * rows; + /* Use width estimate made by the core code. */ + width = foreignrel->reltarget->width; + + /* Startup cost includes: + * 1. Startup cost for underneath input relation, adjusted for tlist replacement by apply_scanjoin_target_to_paths() + * 2. Cost of performing aggregation, per cost_agg() + */ + startup_cost = ofpinfo->rel_startup_cost; + startup_cost += outerrel->reltarget->cost.startup; + startup_cost += aggcosts.transCost.startup; + startup_cost += aggcosts.transCost.per_tuple * input_rows; + startup_cost += aggcosts.finalCost.startup; + startup_cost += (cpu_operator_cost * numGroupCols) * input_rows; + + /* Run time cost includes: + * 1. Run time cost of underneath input relation, adjusted for tlist replacement by apply_scanjoin_target_to_paths() + * 2. Run time cost of performing aggregation, per cost_agg() + */ + run_cost = ofpinfo->rel_total_cost - ofpinfo->rel_startup_cost; + run_cost += outerrel->reltarget->cost.per_tuple * input_rows; + run_cost += aggcosts.finalCost.per_tuple * numGroups; + run_cost += cpu_tuple_cost * numGroups; + + /* Account for the eval cost of HAVING quals, if any */ + if (root->hasHavingQual) + { + QualCost remote_cost; + + /* Add in the eval cost of the remotely-checked quals */ + cost_qual_eval(&remote_cost, fpinfo->remote_conds, root); + startup_cost += remote_cost.startup; + run_cost += remote_cost.per_tuple * numGroups; + /* Add in the eval cost of the locally-checked quals */ + startup_cost += fpinfo->local_conds_cost.startup; + run_cost += fpinfo->local_conds_cost.per_tuple * retrieved_rows; + } + + /* Add in tlist eval cost for each output row */ + startup_cost += foreignrel->reltarget->cost.startup; + run_cost += foreignrel->reltarget->cost.per_tuple * rows; } else { Cost cpu_per_tuple; @@ -1229,9 +1101,7 @@ void estimate_path_cost_size(PlannerInfo* root, RelOptInfo* foreignrel, List* pa retrieved_rows = clamp_row_est(rows / fpinfo->local_conds_sel); retrieved_rows = Min(retrieved_rows, foreignrel->tuples); - /* Cost as though this were a seqscan, which is pessimistic. We effectively imagine the local_conds are being evaluated - * remotely, too. - */ + /* Cost as though this were a seqscan, which is pessimistic. We effectively imagine the local_conds are being evaluated remotely, too. */ startup_cost = 0; run_cost = 0; run_cost += seq_page_cost * foreignrel->pages; @@ -1245,10 +1115,11 @@ void estimate_path_cost_size(PlannerInfo* root, RelOptInfo* foreignrel, List* pa run_cost += foreignrel->reltarget->cost.per_tuple * rows; } - /* Without remote estimates, we have no real way to estimate the cost of generating sorted output. It could be free if the query plan - * the remote side would have chosen generates properly-sorted output anyway, but in most cases it will cost something. Estimate a value - * high enough that we won't pick the sorted path when the ordering isn't locally useful, but low enough that we'll err on the side of - * pushing down the ORDER BY clause when it's useful to do so. + /* Without remote estimates, we have no real way to estimate the cost of generating sorted output. + * It could be free if the query plan the remote side would have chosen generates properly-sorted output anyway, but in most cases it + * will cost something. + * Estimate a value high enough that we won't pick the sorted path when the ordering isn't locally useful, but low enough that we'll + * err on the side of pushing down the ORDER BY clause when it's useful to do so. */ if (pathkeys != NIL) { if (IS_UPPER_REL(foreignrel)) { @@ -1291,15 +1162,16 @@ void estimate_path_cost_size(PlannerInfo* root, RelOptInfo* foreignrel, List* pa } /* Cache the retrieved rows and cost estimates for scans, joins, or groupings without any parameterization, pathkeys, or additional - * post-scan/join-processing steps, before adding the costs for transferring data from the foreign server. These estimates are useful - * for costing remote joins involving this relation or costing other remote operations on this relation such as remote sorts and remote - * LIMIT restrictions, when the costs can not be obtained from the foreign server. This function will be called at least once for every foreign - * relation without any parameterization, pathkeys, or additional post-scan/join-processing steps. + * post-scan/join-processing steps, before adding the costs for transferring data from the foreign server. + * These estimates are useful for costing remote joins involving this relation or costing other remote operations on this relation such as remote + * sorts and remote LIMIT restrictions, when the costs can not be obtained from the foreign server. + * This function will be called at least once for every foreign relation without any parameterization, pathkeys, or additional + * post-scan/join-processing steps. */ if (pathkeys == NIL && param_join_conds == NIL && fpextra == NULL) { - fpinfo->retrieved_rows = retrieved_rows; - fpinfo->rel_startup_cost = startup_cost; - fpinfo->rel_total_cost = total_cost; + fpinfo->retrieved_rows = retrieved_rows; + fpinfo->rel_startup_cost = startup_cost; + fpinfo->rel_total_cost = total_cost; } /* Add some additional cost factors to account for connection overhead (fdw_startup_cost), transferring data across the network @@ -1328,18 +1200,16 @@ void estimate_path_cost_size(PlannerInfo* root, RelOptInfo* foreignrel, List* pa *p_disabled_nodes = disabled_nodes; *p_startup_cost = startup_cost; *p_total_cost = total_cost; - db2Debug1("< %s::estimate_path_cost_size", __FILE__); + db2Exit(4,"< db2GetForeignUpperPaths.c::estimate_path_cost_size"); } -/** Merge FDW options from input relations into a new set of options for a join or an upper rel. +/* Merge FDW options from input relations into a new set of options for a join or an upper rel. * - * For a join relation, FDW-specific information about the inner and outer - * relations is provided using fpinfo_i and fpinfo_o. For an upper relation, - * fpinfo_o provides the information for the input relation; fpinfo_i is - * expected to NULL. + * For a join relation, FDW-specific information about the inner and outer relations is provided using fpinfo_i and fpinfo_o. + * For an upper relation, fpinfo_o provides the information for the input relation; fpinfo_i is expected to NULL. */ static void merge_fdw_options(DB2FdwState* fpinfo, const DB2FdwState* fpinfo_o, const DB2FdwState* fpinfo_i) { - db2Debug1("> %s::merge_fdw_options", __FILE__); + db2Entry(4,"> db2GetForeignUpperPaths.c::merge_fdw_options"); /* We must always have fpinfo_o. */ Assert(fpinfo_o); @@ -1358,22 +1228,20 @@ static void merge_fdw_options(DB2FdwState* fpinfo, const DB2FdwState* fpinfo_o, /* Merge the table level options from either side of the join. */ if (fpinfo_i) { - /* We'll prefer to use remote estimates for this join if any table from either side of the join is using remote estimates. This is - * most likely going to be preferred since they're already willing to pay the price of a round trip to get the remote EXPLAIN. In any - * case it's not entirely clear how we might otherwise handle this best. + /* We'll prefer to use remote estimates for this join if any table from either side of the join is using remote estimates. + * This is most likely going to be preferred since they're already willing to pay the price of a round trip to get the remote EXPLAIN. + * In any case it's not entirely clear how we might otherwise handle this best. */ fpinfo->use_remote_estimate = fpinfo_o->use_remote_estimate || fpinfo_i->use_remote_estimate; - /* Set fetch size to maximum of the joining sides, since we are expecting the rows returned by the join to be proportional to the - * relation sizes. - */ + /* Set fetch size to maximum of the joining sides, since we are expecting the rows returned by the join to be proportional to the relation sizes. */ fpinfo->fetch_size = Max(fpinfo_o->fetch_size, fpinfo_i->fetch_size); - /* We'll prefer to consider this join async-capable if any table from either side of the join is considered async-capable. This would be - * reasonable because in that case the foreign server would have its own resources to scan that table asynchronously, and the join could - * also be computed asynchronously using the resources. + /* We'll prefer to consider this join async-capable if any table from either side of the join is considered async-capable. + * This would be reasonable because in that case the foreign server would have its own resources to scan that table asynchronously, + * and the join could also be computed asynchronously using the resources. */ fpinfo->async_capable = fpinfo_o->async_capable || fpinfo_i->async_capable; } - db2Debug1("< %s::merge_fdw_options", __FILE__); + db2Exit(4,"< db2GetForeignUpperPaths.c::merge_fdw_options"); } diff --git a/source/db2GetLob.c b/source/db2GetLob.c index ce98d18..7167c1c 100644 --- a/source/db2GetLob.c +++ b/source/db2GetLob.c @@ -12,33 +12,33 @@ extern char db2Message[ERRBUFSIZE];/* contains DB2 error messages, set b /** external prototypes */ extern void* db2alloc (const char* type, size_t size); extern void* db2realloc (void* p, size_t size); -extern void db2Debug1 (const char* message, ...); -extern void db2Debug2 (const char* message, ...); -extern void db2Debug3 (const char* message, ...); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); +extern void db2Debug (int level, const char* message, ...); extern SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleType, int line, char* file); extern void db2Error_d (db2error sqlstate, const char* message, const char* detail, ...); /** internal prototypes */ void db2GetLob (DB2Session* session, DB2ResultColumn* column, char** value, long* value_len, unsigned long trunc); -/** db2GetLob - * Get the LOB contents and store them in *value and *value_len. - * If "trunc" is nonzero, it contains the number of bytes or characters to get. +/* db2GetLob + * Get the LOB contents and store them in *value and *value_len. + * If "trunc" is nonzero, it contains the number of bytes or characters to get. */ void db2GetLob (DB2Session* session, DB2ResultColumn* column, char** value, long* value_len, unsigned long trunc) { SQLRETURN rc = SQL_SUCCESS; SQLLEN ind = 0; SQLCHAR buf[LOB_CHUNK_SIZE+1]; int extend = 0; - db2Debug1("> db2GetLob"); - db2Debug2(" column->colName: '%s'",column->colName); - db2Debug2(" column->resnum : %d ",column->resnum); + db2Entry(1,"> db2GetLob.c::db2GetLob"); + db2Debug(2,"column->colName: '%s'",column->colName); + db2Debug(2,"column->resnum : %d ",column->resnum); /* initialize result buffer length */ *value_len = 0; /* read the LOB in chunks */ do { - db2Debug2(" value_len: %ld",*value_len); - db2Debug2(" reading %d byte chunck of data",sizeof(buf)); + db2Debug(2,"value_len: %ld",*value_len); + db2Debug(2,"reading %d byte chunck of data",sizeof(buf)); rc = SQLGetData(session->stmtp->hsql, column->resnum, SQL_C_CHAR, buf, sizeof(buf), &ind); rc = db2CheckErr(rc,session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); if (rc == SQL_ERROR) { @@ -47,54 +47,54 @@ void db2GetLob (DB2Session* session, DB2ResultColumn* column, char** value, long if (rc != 100) { switch(ind) { case SQL_NULL_DATA: - db2Debug3(" data length is null (SQL_NULL_DATA)"); + db2Debug(3,"data length is null (SQL_NULL_DATA)"); extend = 0; break; case SQL_NO_TOTAL: - db2Debug3(" undefined data length (SQL_NO_TOTAL)"); + db2Debug(3,"undefined data length (SQL_NO_TOTAL)"); extend = LOB_CHUNK_SIZE; break; default: - db2Debug3(" bytes still remaining: %d", ind); + db2Debug(3,"bytes still remaining: %d", ind); extend = (ind < LOB_CHUNK_SIZE) ? ind : LOB_CHUNK_SIZE; break; } /* extend result buffer by ind */ - db2Debug2(" value_len: %ld", *value_len); - db2Debug2(" extend : %d", extend); + db2Debug(2,"value_len: %ld", *value_len); + db2Debug(2,"extend : %d", extend); if (*value_len == 0) { if (extend > 0) { *value = db2alloc ("lob_value", *value_len + extend + 1); } else { *value = NULL; - db2Debug3(" not allocating space since the LOB value is apparently NULL"); + db2Debug(3,"not allocating space since the LOB value is apparently NULL"); } } else { // do not add another 0 termination byte, since we already have one *value = db2realloc (*value, *value_len + extend); } // append the buffer read to the value excluding 0 termination byte - db2Debug2(" *value : %x", *value); - db2Debug2(" *value_len: %x", *value_len); + db2Debug(2,"*value : %x", *value); + db2Debug(2,"*value_len: %x", *value_len); if (*value != NULL) { - db2Debug3(" memcpy(%x,%x,%d)",*value+*value_len,buf,extend); + db2Debug(3,"memcpy(%x,%x,%d)",*value+*value_len,buf,extend); memcpy(*value + *value_len, buf, extend); /* update LOB length */ *value_len += extend; } else { - db2Debug3(" skipping value copy, since value is NULL"); + db2Debug(3,"skipping value copy, since value is NULL"); } } } while (rc == SQL_SUCCESS_WITH_INFO); /* string end for CLOBs */ - db2Debug2(" *value : %x" , *value); - db2Debug2(" value_len: %ld", *value_len); + db2Debug(2,"*value : %x" , *value); + db2Debug(2,"value_len: %ld", *value_len); if (*value != NULL) { (*value)[*value_len] = '\0'; - db2Debug2(" strlen of lob: %ld", strlen(*value)); + db2Debug(2,"strlen of lob: %ld", strlen(*value)); } else { - db2Debug2(" strlen of lob: 0 since *value is NULL"); + db2Debug(2,"strlen of lob: 0 since *value is NULL"); } - db2Debug1("< db2GetLob"); + db2Exit(1,"< db2GetLob.c::db2GetLob"); } diff --git a/source/db2GetSession.c b/source/db2GetSession.c index 395ea4f..f7a1eba 100644 --- a/source/db2GetSession.c +++ b/source/db2GetSession.c @@ -9,8 +9,9 @@ extern DB2EnvEntry* rootenvEntry; /* contains DB2 error messages, set /** external prototypes */ extern void* db2alloc (const char* type, size_t size); -extern void db2Debug1 (const char* message, ...); -extern void db2Debug2 (const char* message, ...); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); +extern void db2Debug (int level, const char* message, ...); extern DB2ConnEntry* db2AllocConnHdl (DB2EnvEntry* envp,const char* srvname, char* user, char* password, char* jwt_token, const char* nls_lang); extern DB2EnvEntry* db2AllocEnvHdl (const char* nls_lang); extern DB2EnvEntry* findenvEntry (DB2EnvEntry* start, const char* nlslang); @@ -20,7 +21,7 @@ extern void db2SetSavepoint (DB2Session* session, int nest_level); /** local prototypes */ DB2Session* db2GetSession (const char* srvname, char* user, char* password, char* jwt_token, const char* nls_lang, int curlevel); -/** db2GetSession +/* db2GetSession * Look up an DB2 connection in the cache, create a new one if there is none. * The result is an allocated data structure containing the connection. * "curlevel" is the current PostgreSQL transaction level. @@ -30,7 +31,7 @@ DB2Session* db2GetSession (const char* srvname, char* user, char* password, char DB2EnvEntry* envp = NULL; DB2ConnEntry* connp = NULL; - db2Debug1("> db2GetSession"); + db2Entry(1,"> db2GetSession.c::db2GetSession"); /* it's easier to deal with empty strings */ if (!srvname) srvname = ""; if (!user) user = ""; @@ -39,7 +40,7 @@ DB2Session* db2GetSession (const char* srvname, char* user, char* password, char if (!nls_lang) nls_lang = ""; /* search environment and server handle in cache */ - db2Debug1( " rootenvEntry: %x", rootenvEntry); + db2Debug(2,"rootenvEntry: %x", rootenvEntry); envp = findenvEntry (rootenvEntry, nls_lang); if (envp == NULL) { envp = db2AllocEnvHdl(nls_lang); @@ -49,7 +50,7 @@ DB2Session* db2GetSession (const char* srvname, char* user, char* password, char connp = db2AllocConnHdl(envp, srvname, user, password, jwt_token, NULL); } if (connp->xact_level <= 0) { - db2Debug2(" db2_fdw::db2GetSession: begin serializable remote transaction"); + db2Debug(2,"db2_fdw::db2GetSession: begin serializable remote transaction"); connp->xact_level = 1; } @@ -62,6 +63,6 @@ DB2Session* db2GetSession (const char* srvname, char* user, char* password, char /* set savepoints up to the current level */ db2SetSavepoint (session, curlevel); - db2Debug1("< db2GetSession"); + db2Exit(1,"< db2GetSession.c::db2GetSession"); return session; } diff --git a/source/db2GetShareFileName.c b/source/db2GetShareFileName.c index c3dbedf..cd526e2 100644 --- a/source/db2GetShareFileName.c +++ b/source/db2GetShareFileName.c @@ -1,27 +1,27 @@ #include #include -#include #include #include #include #include "db2_fdw.h" /** external prototypes */ -extern void db2Debug1 (const char* message, ...); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); extern void* db2alloc (const char* type, size_t size); /** local prototypes */ char* db2GetShareFileName(const char *relativename); -/** db2GetShareFileName - * Returns the allocated absolute path of a file in the "share" directory. +/* db2GetShareFileName + * Returns the allocated absolute path of a file in the "share" directory. */ char* db2GetShareFileName (const char *relativename) { char share_path[MAXPGPATH], *result; - db2Debug1("> db2GetShareFileName"); + db2Entry(1,"> db2GetShareFileName.c::db2GetShareFileName"); get_share_path(my_exec_path, share_path); result = db2alloc("sharedFileName", MAXPGPATH); snprintf(result, MAXPGPATH, "%s/%s", share_path, relativename); - db2Debug1("< db2GetShareFileName - returns: '%s'",result); + db2Exit(1,"< db2GetShareFileName.c::db2GetShareFileName : '%s'",result); return result; } diff --git a/source/db2ImportForeignSchema.c b/source/db2ImportForeignSchema.c index 65683a9..7bb2982 100644 --- a/source/db2ImportForeignSchema.c +++ b/source/db2ImportForeignSchema.c @@ -1,32 +1,30 @@ #include #include -#include #include #include -#include #include #include #include "db2_fdw.h" /** external prototypes */ -extern DB2Session* db2GetSession (const char* connectstring, char* user, char* password, char* jwt_token, const char* nls_lang, int curlevel); -extern char* guessNlsLang (char* nls_lang); -extern void db2Debug1 (const char* message, ...); -extern void db2Debug2 (const char* message, ...); -extern void db2Debug4 (const char* message, ...); -extern short c2dbType (short fcType); -extern void db2free (void* p); -extern char* db2strdup (const char* source); -extern bool isForeignSchema (DB2Session* session, char* schema); -extern char** getForeignTableList (DB2Session* session, char* schema, int list_type, char* table_list); -extern DB2Table* describeForeignTable (DB2Session* session, char* schema, char* tabname); -extern bool optionIsTrue (const char* value); +extern DB2Session* db2GetSession (const char* connectstring, char* user, char* password, char* jwt_token, const char* nls_lang, int curlevel); +extern char* guessNlsLang (char* nls_lang); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); +extern void db2Debug (int level, const char* message, ...); +extern short c2dbType (short fcType); +extern void db2free (void* p); +extern char* db2strdup (const char* source); +extern bool isForeignSchema (DB2Session* session, char* schema); +extern char** getForeignTableList (DB2Session* session, char* schema, int list_type, char* table_list); +extern DB2Table* describeForeignTable (DB2Session* session, char* schema, char* tabname); +extern bool optionIsTrue (const char* value); /** local prototypes */ - List* db2ImportForeignSchema (ImportForeignSchemaStmt* stmt, Oid serverOid); -static char* fold_case (char* name, fold_t foldcase); -static void generateForeignTableCreate(StringInfo buf, char* servername, char* local_schema, char* remote_schema, DB2Table* db2Table, fold_t foldcase, bool readonly); -static ForeignServer* getOptions (Oid serverOid, List** options); + List* db2ImportForeignSchema (ImportForeignSchemaStmt* stmt, Oid serverOid); +static char* fold_case (char* name, fold_t foldcase); +static void generateForeignTableCreate(StringInfo buf, char* servername, char* local_schema, char* remote_schema, DB2Table* db2Table, fold_t foldcase, bool readonly); +static ForeignServer* getOptions (Oid serverOid, List** options); /* db2ImportForeignSchema * Returns a List of CREATE FOREIGN TABLE statements. @@ -46,12 +44,12 @@ List* db2ImportForeignSchema (ImportForeignSchemaStmt* stmt, Oid serverOid) { List* result = NIL; ForeignServer* server = NULL; - db2Debug1("> %s::db2ImportForeignSchema",__FILE__); + db2Entry(1,"> db2ImportForeignSchema.c::db2ImportForeignSchema"); /* process the server options */ server = getOptions (serverOid, &options); foreach (cell, options) { DefElem *def = (DefElem *) lfirst (cell); - db2Debug2(" option: '%s'", def->defname); + db2Debug(2,"option: '%s'", def->defname); nls_lang = (strcmp (def->defname, OPT_NLS_LANG) == 0) ? STRVAL(def->arg) : nls_lang; dbserver = (strcmp (def->defname, OPT_DBSERVER) == 0) ? STRVAL(def->arg) : dbserver; user = (strcmp (def->defname, OPT_USER) == 0) ? STRVAL(def->arg) : user; @@ -62,7 +60,7 @@ List* db2ImportForeignSchema (ImportForeignSchemaStmt* stmt, Oid serverOid) { /* process the options of the IMPORT FOREIGN SCHEMA command */ foreach (cell, stmt->options) { DefElem *def = (DefElem *) lfirst (cell); - db2Debug2(" option: '%s'", def->defname); + db2Debug(2,"option: '%s'", def->defname); if (strcmp (def->defname, "case") == 0) { char *s = STRVAL(def->arg); if (strcmp (s, "keep") == 0) @@ -91,12 +89,12 @@ List* db2ImportForeignSchema (ImportForeignSchemaStmt* stmt, Oid serverOid) { /* connect to DB2 database */ session = db2GetSession (dbserver, user, password, jwt_token, nls_lang, 1); - db2Debug2(" stmt->list_type : %d", stmt->list_type); - db2Debug2(" stmt->local_schema : %s", stmt->local_schema); - db2Debug2(" stmt->remote_schema: %s", stmt->remote_schema); - db2Debug2(" stmt->server_name : %s", stmt->server_name); - db2Debug2(" stmt->table_list : %s", stmt->table_list); - db2Debug2(" stmt->type : %d", stmt->type); + db2Debug(2,"stmt->list_type : %d", stmt->list_type); + db2Debug(2,"stmt->local_schema : %s", stmt->local_schema); + db2Debug(2,"stmt->remote_schema: %s", stmt->remote_schema); + db2Debug(2,"stmt->server_name : %s", stmt->server_name); + db2Debug(2,"stmt->table_list : %s", stmt->table_list); + db2Debug(2,"stmt->type : %d", stmt->type); if (isForeignSchema (session, stmt->remote_schema)) { StringInfoData tblist; @@ -107,16 +105,16 @@ List* db2ImportForeignSchema (ImportForeignSchemaStmt* stmt, Oid serverOid) { if (stmt->list_type != FDW_IMPORT_SCHEMA_ALL) { foreach (cell, stmt->table_list) { RangeVar* rVar = lfirst(cell); - db2Debug2(" rVar : %x ", rVar); + db2Debug(2,"rVar : %x ", rVar); if (rVar != NULL) { - db2Debug2(" rVar->type : %d ", rVar->type); - db2Debug2(" rVar->catalogname: '%s'", rVar->catalogname); - db2Debug2(" rVar->schemaname : '%s'", rVar->schemaname); - db2Debug2(" rVar->relname : '%s'", rVar->relname); + db2Debug(2,"rVar->type : %d ", rVar->type); + db2Debug(2,"rVar->catalogname: '%s'", rVar->catalogname); + db2Debug(2,"rVar->schemaname : '%s'", rVar->schemaname); + db2Debug(2,"rVar->relname : '%s'", rVar->relname); appendStringInfo(&tblist,"%s'%s'",((tblist.len == 0) ? "" : ","),rVar->relname); } } - db2Debug2(" import table_list: %s",tblist.data); + db2Debug(2,"import table_list: %s",tblist.data); } tablist = getForeignTableList(session, stmt->remote_schema, stmt->list_type, tblist.data); db2free (tblist.data); @@ -124,14 +122,14 @@ List* db2ImportForeignSchema (ImportForeignSchemaStmt* stmt, Oid serverOid) { DB2Table* db2Table = describeForeignTable(session, stmt->remote_schema, tablist[i]); if (db2Table != NULL) { generateForeignTableCreate(&buf, server->servername, stmt->local_schema, stmt->remote_schema, db2Table, foldcase, readonly); - db2Debug2 (" pg fdw table ddl: '%s'",buf.data); + db2Debug(2,"pg fdw table ddl: '%s'",buf.data); result = lappend (result, db2strdup (buf.data)); resetStringInfo (&buf); } } db2free (tablist); } - db2Debug1("< %s::db2ImportForeignSchema : %d",__FILE__, list_length(result)); + db2Exit(1,"< db2ImportForeignSchema.c::db2ImportForeignSchema : %d", list_length(result)); return result; } @@ -140,7 +138,7 @@ List* db2ImportForeignSchema (ImportForeignSchemaStmt* stmt, Oid serverOid) { */ static char* fold_case (char *name, fold_t foldcase) { char* result = NULL; - db2Debug1("> fold_case(name: '%s', foldcase: %d)", name, foldcase); + db2Entry(4,"> db2ImportForeignSchema.c::fold_case(name: '%s', foldcase: %d)", name, foldcase); if (foldcase == CASE_KEEP) { result = db2strdup (name); } else { @@ -160,7 +158,7 @@ static char* fold_case (char *name, fold_t foldcase) { if (result == NULL) { elog (ERROR, "impossible case folding type %d", foldcase); } - db2Debug1("< fold_case - returns: '%s'", result); + db2Exit(4,"< db2ImportForeignSchema.c::fold_case - returns: '%s'", result); return result; } @@ -169,6 +167,7 @@ static void generateForeignTableCreate(StringInfo buf, char* servername, char* l char* foldedname; bool firstcol = true; + db2Entry(4,"> db2ImportForeignSchema.c::generateForeignTableCreate"); initStringInfo(&coldef); foldedname = fold_case (db2Table->name, foldcase); appendStringInfo( buf @@ -285,6 +284,7 @@ static void generateForeignTableCreate(StringInfo buf, char* servername, char* l appendStringInfo (buf, ", readonly 'true'"); } appendStringInfo (buf, ")"); + db2Exit(4,"< db2ImportForeignSchema.c::generateForeignTableCreate : %s", buf->data); } /* getOptions @@ -297,7 +297,7 @@ static ForeignServer* getOptions (Oid serverOid, List** options) { ForeignServer* server = NULL; UserMapping* mapping = NULL; - db2Debug4(" > %s::getOptions", __FILE__); + db2Entry(4,"> db2ImportForeignSchema.c::getOptions"); /* get the foreign server, the user mapping and the FDW */ server = GetForeignServer (serverOid); mapping = GetUserMapping (GetUserId (), serverOid); @@ -312,6 +312,6 @@ static ForeignServer* getOptions (Oid serverOid, List** options) { *options = list_concat (*options, server->options); if (mapping != NULL) *options = list_concat (*options, mapping->options); - db2Debug4(" < %s::getOptions : %x", __FILE__, server); + db2Exit(4,"< db2ImportForeignSchema.c::getOptions : %x", server); return server; } \ No newline at end of file diff --git a/source/db2ImportForeignSchemaData.c b/source/db2ImportForeignSchemaData.c index c9d539a..a151c0f 100644 --- a/source/db2ImportForeignSchemaData.c +++ b/source/db2ImportForeignSchemaData.c @@ -17,9 +17,9 @@ extern void* db2realloc (void* p, size_t size); extern void db2free (void* p); extern char* db2strdup (const char* source); extern char* db2CopyText (const char* string, int size, int quote); -extern void db2Debug1 (const char* message, ...); -extern void db2Debug2 (const char* message, ...); -extern void db2Debug3 (const char* message, ...); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); +extern void db2Debug (int level, const char* message, ...); extern SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleType, int line, char* file); extern void db2Error_d (db2error sqlstate, const char* message, const char* detail, ...); extern char* c2name (short fcType); @@ -45,55 +45,55 @@ bool isForeignSchema(DB2Session* session, char* schema) { SQLRETURN result = 0; char* schema_query = "SELECT COUNT(*) AS COUNTER FROM SYSCAT.SCHEMATA WHERE SCHEMANAME = ?"; - db2Debug1("> %s::isForeignSchema(schema: '%s')", __FILE__, schema); - db2Debug2(" count : %lld", (long long)count); - db2Debug2(" schema query : '%s'", schema_query); + db2Entry(1,"> db2ImportForeignSchemaData.c::isForeignSchema(schema: '%s')", schema); + db2Debug(2,"count : %lld", (long long)count); + db2Debug(2,"schema query : '%s'", schema_query); /* create statement handle */ stmtp = db2AllocStmtHdl(SQL_HANDLE_STMT, session->connp, FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: failed to allocate statement handle"); - db2Debug2(" stmp->hsql : %d",stmtp->hsql); - db2Debug2(" stmp->type : %d",stmtp->type); + db2Debug(2,"stmp->hsql : %d",stmtp->hsql); + db2Debug(2,"stmp->type : %d",stmtp->type); /* prepare the query */ result = SQLPrepare(stmtp->hsql, (SQLCHAR*)schema_query, SQL_NTS); - db2Debug2(" SQLPrepare rc : %d",result); + db2Debug(2,"SQLPrepare rc : %d",result); result = db2CheckErr(result, stmtp->hsql, stmtp->type, __LINE__, __FILE__); if (result != SQL_SUCCESS) { db2Error_d ( FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLPrepare failed to prepare schema query", db2Message); } /* bind the parameter */ result = SQLBindParameter(stmtp->hsql, 1, SQL_PARAM_INPUT,SQL_C_CHAR, SQL_VARCHAR, 128, 0, schema, sizeof(schema), &ind); - db2Debug2(" SQLBindParameter1 NAME = '%s', ind = %d, rc : %d",schema, ind, result); + db2Debug(2,"SQLBindParameter1 NAME = '%s', ind = %d, rc : %d",schema, ind, result); result = db2CheckErr(result, stmtp->hsql, stmtp->type, __LINE__, __FILE__); if (result != SQL_SUCCESS) { db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLBindParameter failed to bind parameter", db2Message); } /* define the result value */ result = SQLBindCol (stmtp->hsql, 1, SQL_C_SBIGINT, &count, 0, &ind_c); - db2Debug2(" SQLBindCol rc : %d",result); + db2Debug(2,"SQLBindCol rc : %d",result); result = db2CheckErr(result, stmtp->hsql, stmtp->type, __LINE__, __FILE__); if (result != SQL_SUCCESS) { db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLBindCol failed to define result", db2Message); } /* execute the query and get the first result row */ result = SQLExecute(stmtp->hsql); - db2Debug2(" SQLExecute rc : %d",result); + db2Debug(2,"SQLExecute rc : %d",result); result = db2CheckErr(result, stmtp->hsql, stmtp->type, __LINE__, __FILE__); if (result != SQL_SUCCESS) { db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLExecute failed to execute schema query", db2Message); } else { result = SQLFetch(stmtp->hsql); - db2Debug2(" SQLFetch rc : %d, count = %lld, ind_c = %d",result, (long long)count, ind_c); + db2Debug(2,"SQLFetch rc : %d, count = %lld, ind_c = %d",result, (long long)count, ind_c); result = db2CheckErr(result, stmtp->hsql, stmtp->type, __LINE__, __FILE__); if (result != SQL_SUCCESS) { db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLFetch failed to execute schema query", db2Message); } } - db2Debug2(" count(*) = %lld, ind_c = %d", (long long)count, ind_c); + db2Debug(2,"count(*) = %lld, ind_c = %d", (long long)count, ind_c); /* release the statement handle */ db2FreeStmtHdl(stmtp, session->connp); stmtp = NULL; /* return false if the remote schema does not exist */ fResult = (count > 0); - db2Debug1("< %s::isForeignSchema : %s, result = %s", __FILE__, schema, fResult ? "true" : "false"); + db2Exit(1,"< db2ImportForeignSchemaData.c::isForeignSchema : %s, result = %s", schema, fResult ? "true" : "false"); return fResult; } @@ -110,7 +110,7 @@ char** getForeignTableList(DB2Session* session, char* schema, int list_type, cha SQLLEN ind_tab; int tabidx = 0; char** tabnames = NULL; - db2Debug1("> %s::getForeignTableList(schema: '%s', list_type: %d, table_list: '%s')", __FILE__, schema, list_type, table_list); + db2Entry(1,"> db2ImportForeignSchemaData.c::getForeignTableList(schema: '%s', list_type: %d, table_list: '%s')", schema, list_type, table_list); switch(list_type){ case 0: { /* FDW_IMPORT_SCHEMA_ALL */ char* query_str = "SELECT T.TABNAME FROM SYSCAT.TABLES T WHERE T.TABSCHEMA = ? AND T.TYPE IN ('T','V') ORDER BY T.TABNAME"; @@ -134,30 +134,30 @@ char** getForeignTableList(DB2Session* session, char* schema, int list_type, cha } break; default: - db2Debug2(" schema import type: %d", list_type); + db2Debug(2,"schema import type: %d", list_type); db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "invalid schema import type", db2Message); break; } - db2Debug2(" column query : '%s'", column_query); + db2Debug(2,"column query : '%s'", column_query); /* create statement handle */ stmtp = db2AllocStmtHdl(SQL_HANDLE_STMT, session->connp, FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: failed to allocate statement handle"); /* prepare the query */ rc = SQLPrepare(stmtp->hsql, (SQLCHAR*)column_query, SQL_NTS); - db2Debug2(" SQLPrepare rc : %d",rc); + db2Debug(2,"SQLPrepare rc : %d",rc); rc = db2CheckErr(rc, stmtp->hsql, stmtp->type, __LINE__, __FILE__); if (rc != SQL_SUCCESS) { db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLPrepare failed to prepare remote query", db2Message); } /* bind the parameter */ rc = SQLBindParameter(stmtp->hsql, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_VARCHAR, 128, 0, schema, sizeof(schema), &ind_s); - db2Debug2(" SQLBindParameter table_schema = '%s' rc : %d",schema, rc); + db2Debug(2,"SQLBindParameter table_schema = '%s' rc : %d",schema, rc); rc = db2CheckErr(rc, stmtp->hsql, stmtp->type, __LINE__, __FILE__); if (rc != SQL_SUCCESS) { db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLBindParameter failed to bind parameter", db2Message); } rc = SQLBindCol(stmtp->hsql, 1, SQL_C_CHAR, tab_buf, sizeof(tab_buf), &ind_tab); - db2Debug2(" SQLBindCol1 rc : %d",rc); + db2Debug(2,"SQLBindCol1 rc : %d",rc); rc = db2CheckErr(rc, stmtp->hsql, stmtp->type, __LINE__, __FILE__); if (rc != SQL_SUCCESS) { db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLBindCol failed to define result for table name", db2Message); @@ -165,7 +165,7 @@ char** getForeignTableList(DB2Session* session, char* schema, int list_type, cha /* execute the query and get the first result row */ rc = SQLExecute (stmtp->hsql); - db2Debug2(" SQLExecute rc : %d",rc); + db2Debug(2,"SQLExecute rc : %d",rc); rc = db2CheckErr(rc, stmtp->hsql, stmtp->type, __LINE__, __FILE__); if (rc != SQL_SUCCESS && rc != SQL_NO_DATA) { db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLExecute failed to execute column query", db2Message); @@ -179,7 +179,7 @@ char** getForeignTableList(DB2Session* session, char* schema, int list_type, cha tabnames = (char**) db2alloc("tabnames", (tabidx + 1) * sizeof(char*)); while(rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO) { tabnames[tabidx] = NULL; - db2Debug2(" tabname[%d] : '%s', ind: %d", tabidx, tab_buf, ind_tab); + db2Debug(2,"tabname[%d] : '%s', ind: %d", tabidx, tab_buf, ind_tab); if (ind_tab != SQL_NULL_DATA) { char* tabname = (char*) db2alloc("tabname", strlen((char*)tab_buf)+1); strncpy(tabname, (char*)tab_buf, strlen((char*)tab_buf)+1); @@ -198,7 +198,7 @@ char** getForeignTableList(DB2Session* session, char* schema, int list_type, cha db2FreeStmtHdl(stmtp, session->connp); stmtp = NULL; db2free(column_query); - db2Debug1("< %s::getForeignTableList : [%d]", __FILE__, tabidx-1); + db2Exit(1,"< db2ImportForeignSchemaData.c::getForeignTableList : [%d]", tabidx-1); return tabnames; } @@ -227,7 +227,7 @@ DB2Table* describeForeignTable (DB2Session* session, char* schema, char* tabname SQLINTEGER codepage = 0; SQLRETURN rc = 0; - db2Debug1("> %s::db2DescribeForeignTable(schema: %s, tablename: %s)", __FILE__, schema, tabname); + db2Entry(1,"> db2ImportForeignSchemaData.c::db2DescribeForeignTable(schema: %s, tablename: %s)", schema, tabname); /* get a complete quoted table name */ qtable = db2CopyText (tabname, strlen (tabname), 1); length = strlen (qtable); @@ -276,8 +276,8 @@ DB2Table* describeForeignTable (DB2Session* session, char* schema, char* tabname /* allocate an db2Table struct for the results */ reply = db2alloc ("reply", sizeof (DB2Table)); reply->name = tabname; - db2Debug2(" table description"); - db2Debug2(" reply->name : '%s'", reply->name); + db2Debug(2,"table description"); + db2Debug(2,"reply->name : '%s'", reply->name); reply->batchsz = DEFAULT_BATCHSZ; /* get the number of columns */ @@ -289,7 +289,7 @@ DB2Table* describeForeignTable (DB2Session* session, char* schema, char* tabname reply->ncols = ncols; reply->cols = (DB2Column**) db2alloc ("reply->cols", sizeof (DB2Column*) *reply->ncols); - db2Debug2(" reply->ncols : %d", reply->ncols); + db2Debug(2,"reply->ncols : %d", reply->ncols); /* loop through the column list */ for (i = 1; i <= reply->ncols; ++i) { @@ -321,20 +321,20 @@ DB2Table* describeForeignTable (DB2Session* session, char* schema, char* tabname db2Error_d (FDW_UNABLE_TO_CREATE_REPLY, "error describing remote table: SQLDescribeCol failed to get column data", db2Message); } reply->cols[i - 1]->colName = db2strdup((char*)colName); - db2Debug2(" reply->cols[%d]->colName : '%s'", (i-1), reply->cols[i - 1]->colName); - db2Debug2(" dataType: %d", dataType); + db2Debug(2,"reply->cols[%d]->colName : '%s'", (i-1), reply->cols[i - 1]->colName); + db2Debug(2,"dataType: %d", dataType); reply->cols[i - 1]->colType = (short) dataType; if (dataType == -7){ // datatype -7 does not exist it seems to be used for SQL_BOOLEAN wrongly reply->cols[i - 1]->colType = SQL_BOOLEAN; } - db2Debug2(" reply->cols[%d]->colType : %d (%s)", (i-1), reply->cols[i - 1]->colType,c2name(reply->cols[i - 1]->colType)); + db2Debug(2,"reply->cols[%d]->colType : %d (%s)", (i-1), reply->cols[i - 1]->colType,c2name(reply->cols[i - 1]->colType)); reply->cols[i - 1]->colSize = (size_t) colSize; - db2Debug2(" reply->cols[%d]->colSize : %ld", (i-1), reply->cols[i - 1]->colSize); + db2Debug(2,"reply->cols[%d]->colSize : %ld", (i-1), reply->cols[i - 1]->colSize); reply->cols[i - 1]->colScale = (short) scale; - db2Debug2(" reply->cols[%d]->colScale : %d", (i-1), reply->cols[i - 1]->colScale); + db2Debug(2,"reply->cols[%d]->colScale : %d", (i-1), reply->cols[i - 1]->colScale); reply->cols[i - 1]->colNulls = (short) nullable; - db2Debug2(" reply->cols[%d]->colNulls : %d", (i-1), reply->cols[i - 1]->colNulls); + db2Debug(2,"reply->cols[%d]->colNulls : %d", (i-1), reply->cols[i - 1]->colNulls); /* get the number of characters for string fields */ rc = SQLColAttribute (stmthp->hsql, i, SQL_DESC_PRECISION, NULL, 0, NULL, &charlen); @@ -343,7 +343,7 @@ DB2Table* describeForeignTable (DB2Session* session, char* schema, char* tabname db2Error_d (FDW_UNABLE_TO_CREATE_REPLY, "error describing remote table: SQLColAttribute failed to get column length", db2Message); } reply->cols[i - 1]->colChars = (size_t) charlen; - db2Debug2(" reply->cols[%d]->colChars : %ld", (i-1), reply->cols[i - 1]->colChars); + db2Debug(2,"reply->cols[%d]->colChars : %ld", (i-1), reply->cols[i - 1]->colChars); /* get the binary length for RAW fields */ rc = SQLColAttribute (stmthp->hsql, i, SQL_DESC_OCTET_LENGTH, NULL, 0, NULL, &bin_size); @@ -352,7 +352,7 @@ DB2Table* describeForeignTable (DB2Session* session, char* schema, char* tabname db2Error_d (FDW_UNABLE_TO_CREATE_REPLY, "error describing remote table: SQLColAttribute failed to get column size", db2Message); } reply->cols[i - 1]->colBytes = (size_t) bin_size; - db2Debug2(" reply->cols[%d]->colBytes : %ld", (i-1), reply->cols[i - 1]->colBytes); + db2Debug(2,"reply->cols[%d]->colBytes : %ld", (i-1), reply->cols[i - 1]->colBytes); /* get the columns codepage */ rc = SQLColAttribute(stmthp->hsql, i, SQL_DESC_CODEPAGE, NULL, 0, NULL, (SQLPOINTER)&codepage); @@ -361,7 +361,7 @@ DB2Table* describeForeignTable (DB2Session* session, char* schema, char* tabname db2Error_d (FDW_UNABLE_TO_CREATE_REPLY, "error describing remote table: SQLColAttribute failed to get column codepage", db2Message); } reply->cols[i - 1]->colCodepage = (int) codepage; - db2Debug2(" reply->cols[%d]->colCodepage : %d", (i-1), reply->cols[i - 1]->colCodepage); + db2Debug(2,"reply->cols[%d]->colCodepage : %d", (i-1), reply->cols[i - 1]->colCodepage); /* Unfortunately a LONG VARBINARY is of type LONG VARCHAR but the codepage is set to 0 */ if (reply->cols[i-1]->colType == SQL_LONGVARCHAR && reply->cols[i-1]->colCodepage == 0){ @@ -428,7 +428,7 @@ DB2Table* describeForeignTable (DB2Session* session, char* schema, char* tabname default: break; } - db2Debug2(" reply->cols[%d]->val_size : %d", (i-1), reply->cols[i - 1]->val_size); + db2Debug(2,"reply->cols[%d]->val_size : %d", (i-1), reply->cols[i - 1]->val_size); } /* release statement handle, this takes care of the parameter handles */ db2FreeStmtHdl(stmthp, session->connp); @@ -436,7 +436,7 @@ DB2Table* describeForeignTable (DB2Session* session, char* schema, char* tabname /* get the primary key information for the table and mark the columns in the reply */ describeForeignColumns(session, schema, tabname, reply); } - db2Debug1("< %s::db2DescribeForeignTable - returns: %x", __FILE__, reply); + db2Exit(1,"< db2ImportForeignSchemaData.c::db2DescribeForeignTable : %x", reply); return reply; } @@ -455,14 +455,14 @@ static void describeForeignColumns(DB2Session* session, char* schema, char* tabn SQLLEN ind_t = SQL_NTS; char* query = "SELECT COALESCE(C.KEYSEQ, 0) AS KEY, C.CODEPAGE FROM SYSCAT.COLUMNS C WHERE C.TABSCHEMA = ? AND C.TABNAME = ? AND COALESCE(C.HIDDEN,'') = '' ORDER BY C.COLNO"; - db2Debug1("> %s::describeForeignColumn(schema: %s, tabname: %s)", __FILE__, schema, tabname); - db2Debug2(" query : '%s'", query); + db2Entry(1,"> db2ImportForeignSchemaData.c::describeForeignColumn(schema: %s, tabname: %s)", schema, tabname); + db2Debug(2,"query : '%s'", query); /* create statement handle */ stmtp = db2AllocStmtHdl(SQL_HANDLE_STMT, session->connp, FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: failed to allocate statement handle"); /* prepare the query */ rc = SQLPrepare(stmtp->hsql, (SQLCHAR*)query, SQL_NTS); - db2Debug2(" SQLPrepare rc : %d",rc); + db2Debug(2,"SQLPrepare rc : %d",rc); rc = db2CheckErr(rc, stmtp->hsql, stmtp->type, __LINE__, __FILE__); if (rc != SQL_SUCCESS) { db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLPrepare failed to prepare remote query", db2Message); @@ -470,35 +470,35 @@ static void describeForeignColumns(DB2Session* session, char* schema, char* tabn /* bind the parameter 1 - schema */ rc = SQLBindParameter(stmtp->hsql, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, 128, 0, schema, 0, &ind_s); - db2Debug2(" SQLBindParameter table_schema = '%s' rc : %d",schema, rc); + db2Debug(2,"SQLBindParameter table_schema = '%s' rc : %d",schema, rc); rc = db2CheckErr(rc, stmtp->hsql, stmtp->type, __LINE__, __FILE__); if (rc != SQL_SUCCESS) { db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLBindParameter failed to bind parameter", db2Message); } /* bind the parameter 2 - tablename */ rc = SQLBindParameter(stmtp->hsql, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, 128, 0, tabname, 0, &ind_t); - db2Debug2(" SQLBindParameter table_name = '%s' rc : %d",tabname, rc); + db2Debug(2,"SQLBindParameter table_name = '%s' rc : %d",tabname, rc); rc = db2CheckErr(rc, stmtp->hsql, stmtp->type, __LINE__, __FILE__); if (rc != SQL_SUCCESS) { db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLBindParameter failed to bind parameter", db2Message); } /* bind result column 1 - KEYSEQ */ rc = SQLBindCol(stmtp->hsql, 1, SQL_C_SHORT, &keyseq_val, 0, &ind_key); - db2Debug2(" SQLBindCol1 rc : %d",rc); + db2Debug(2,"SQLBindCol1 rc : %d",rc); rc = db2CheckErr(rc, stmtp->hsql, stmtp->type, __LINE__, __FILE__); if (rc != SQL_SUCCESS) { db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLBindCol failed to define result for primary key", db2Message); } /* bind result column 2 - CODEPAGE */ rc = SQLBindCol(stmtp->hsql, 2, SQL_C_SHORT, &cp_val, 0, &ind_cp); - db2Debug2(" SQLBindCol2 rc : %d",rc); + db2Debug(2,"SQLBindCol2 rc : %d",rc); rc = db2CheckErr(rc, stmtp->hsql, stmtp->type, __LINE__, __FILE__); if (rc != SQL_SUCCESS) { db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLBindCol failed to define result for codepage", db2Message); } /* execute the query and get the first result row */ rc = SQLExecute (stmtp->hsql); - db2Debug2(" SQLExecute rc : %d",rc); + db2Debug(2,"SQLExecute rc : %d",rc); rc = db2CheckErr(rc, stmtp->hsql, stmtp->type, __LINE__, __FILE__); if (rc != SQL_SUCCESS && rc != SQL_NO_DATA) { db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLExecute failed to execute column query", db2Message); @@ -513,21 +513,21 @@ static void describeForeignColumns(DB2Session* session, char* schema, char* tabn } while(rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO) { - db2Debug2(" keyseq_val: %d, ind: %d", keyseq_val, ind_key); + db2Debug(2,"keyseq_val: %d, ind: %d", keyseq_val, ind_key); db2Table->cols[colidx]->colPrimKeyPart = (ind_key == SQL_NULL_DATA) ? 0 : (int) keyseq_val; - db2Debug2(" cp_val : %d, ind: %d", cp_val, ind_cp); + db2Debug(2,"cp_val : %d, ind: %d", cp_val, ind_cp); db2Table->cols[colidx]->colCodepage = (ind_cp == SQL_NULL_DATA) ? 0 : (int) cp_val; - db2Debug2(" db2Table->cols[%d]->colName : %s " , colidx, db2Table->cols[colidx]->colName ); - db2Debug2(" db2Table->cols[%d]->colType : %d - %s,", colidx, db2Table->cols[colidx]->colType, c2name(db2Table->cols[colidx]->colType)); - db2Debug2(" db2Table->cols[%d]->colSize : %d" , colidx, db2Table->cols[colidx]->colSize ); - db2Debug2(" db2Table->cols[%d]->colBytes : %d" , colidx, db2Table->cols[colidx]->colBytes ); - db2Debug2(" db2Table->cols[%d]->colChars : %d" , colidx, db2Table->cols[colidx]->colChars ); - db2Debug2(" db2Table->cols[%d]->colScale : %d" , colidx, db2Table->cols[colidx]->colScale); - db2Debug2(" db2Table->cols[%d]->colNulls : %d" , colidx, db2Table->cols[colidx]->colNulls); - db2Debug2(" db2Table->cols[%d]->colPrimKeyPart: %d" , colidx, db2Table->cols[colidx]->colPrimKeyPart); - db2Debug2(" db2Table->cols[%d]->colCodepage : %d" , colidx, db2Table->cols[colidx]->colCodepage); - db2Debug2(" db2Table->cols[%d]->val_size : %d" , colidx, db2Table->cols[colidx]->val_size); + db2Debug(2,"db2Table->cols[%d]->colName : %s " , colidx, db2Table->cols[colidx]->colName ); + db2Debug(2,"db2Table->cols[%d]->colType : %d - %s,", colidx, db2Table->cols[colidx]->colType, c2name(db2Table->cols[colidx]->colType)); + db2Debug(2,"db2Table->cols[%d]->colSize : %d" , colidx, db2Table->cols[colidx]->colSize ); + db2Debug(2,"db2Table->cols[%d]->colBytes : %d" , colidx, db2Table->cols[colidx]->colBytes ); + db2Debug(2,"db2Table->cols[%d]->colChars : %d" , colidx, db2Table->cols[colidx]->colChars ); + db2Debug(2,"db2Table->cols[%d]->colScale : %d" , colidx, db2Table->cols[colidx]->colScale); + db2Debug(2,"db2Table->cols[%d]->colNulls : %d" , colidx, db2Table->cols[colidx]->colNulls); + db2Debug(2,"db2Table->cols[%d]->colPrimKeyPart: %d" , colidx, db2Table->cols[colidx]->colPrimKeyPart); + db2Debug(2,"db2Table->cols[%d]->colCodepage : %d" , colidx, db2Table->cols[colidx]->colCodepage); + db2Debug(2,"db2Table->cols[%d]->val_size : %d" , colidx, db2Table->cols[colidx]->val_size); /* fetch the next result row */ rc = SQLFetch(stmtp->hsql); @@ -537,8 +537,8 @@ static void describeForeignColumns(DB2Session* session, char* schema, char* tabn } colidx++; } - db2Debug3(" End of Data reached"); + db2Debug(3,"End of Data reached"); /* release the statement handle */ db2FreeStmtHdl(stmtp, session->connp); - db2Debug1("< %s::describeForeignColumn", __FILE__); + db2Exit(1,"< db2ImportForeignSchemaData.c::describeForeignColumn"); } \ No newline at end of file diff --git a/source/db2IsForeignRelUpdatable.c b/source/db2IsForeignRelUpdatable.c index e263d14..89a5aa7 100644 --- a/source/db2IsForeignRelUpdatable.c +++ b/source/db2IsForeignRelUpdatable.c @@ -1,25 +1,23 @@ #include -#include -#include #include #include #include "db2_fdw.h" /** external prototypes */ -extern bool optionIsTrue (const char* value); -extern void db2Debug1 (const char* message, ...); -extern void db2Debug2 (const char* message, ...); +extern bool optionIsTrue (const char* value); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); /** local prototypes */ int db2IsForeignRelUpdatable(Relation rel); -/** db2IsForeignRelUpdatable - * Returns 0 if "readonly" is set, a value indicating that all DML is allowed. +/* db2IsForeignRelUpdatable + * Returns 0 if "readonly" is set, a value indicating that all DML is allowed. */ int db2IsForeignRelUpdatable(Relation rel) { ListCell* cell; int result = 0; - db2Debug1("> db2IsForeignRelUpdatable"); + db2Entry(1,"> db2IsForeignRelUpdatable.c::db2IsForeignRelUpdatable"); /* loop foreign table options */ foreach (cell, GetForeignTable (RelationGetRelid (rel))->options) { DefElem *def = (DefElem *) lfirst (cell); @@ -28,7 +26,7 @@ int db2IsForeignRelUpdatable(Relation rel) { return 0; } result = (1 << CMD_UPDATE) | (1 << CMD_INSERT) | (1 << CMD_DELETE); - db2Debug1("< db2IsForeignRelUpdatable - returns: %d", result); + db2Exit(1,"< db2IsForeignRelUpdatable.c::db2IsForeignRelUpdatable - returns: %d", result); return result; } diff --git a/source/db2IsStatementOpen.c b/source/db2IsStatementOpen.c index dcab4e5..2bc9adf 100644 --- a/source/db2IsStatementOpen.c +++ b/source/db2IsStatementOpen.c @@ -7,18 +7,19 @@ /** external variables */ /** external prototypes */ -extern void db2Debug1 (const char* message, ...); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); /** local prototypes */ -int db2IsStatementOpen (DB2Session* session); +int db2IsStatementOpen (DB2Session* session); -/** db2IsStatementOpen - * Return 1 if there is a statement handle, else 0. +/* db2IsStatementOpen + * Return 1 if there is a statement handle, else 0. */ int db2IsStatementOpen (DB2Session* session) { int result = 0; - db2Debug1("> db2IsStatementOpen"); + db2Entry(1,"> db2IsStatementOpen.c::db2IsStatementOpen"); result = (session->stmtp != NULL && session->stmtp->hsql != SQL_NULL_HSTMT); - db2Debug1("< db2IsStatementOpen - result: %d",result); + db2Exit(1,"< db2IsStatementOpen.c::db2IsStatementOpen : %d",result); return result; } diff --git a/source/db2IterateDirectModify.c b/source/db2IterateDirectModify.c index 440fb81..c38c5ad 100644 --- a/source/db2IterateDirectModify.c +++ b/source/db2IterateDirectModify.c @@ -2,23 +2,31 @@ #include "db2_fdw.h" #include "DB2FdwDirectModifyState.h" +/** external prototypes */ +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); +extern void db2Debug (int level, const char* message, ...); + +/** local prototypes */ TupleTableSlot* db2IterateDirectModify(ForeignScanState *node); /* postgresIterateDirectModify * Execute a direct foreign table modification */ TupleTableSlot* db2IterateDirectModify(ForeignScanState *node) { - DB2FdwDirectModifyState* dmstate = (DB2FdwDirectModifyState*) node->fdw_state; - EState* estate = node->ss.ps.state; - ResultRelInfo* rtinfo = node->resultRelInfo; - TupleTableSlot* slot = NULL; + DB2FdwDirectModifyState* dmstate = (DB2FdwDirectModifyState*) node->fdw_state; + EState* estate = node->ss.ps.state; + ResultRelInfo* rtinfo = node->resultRelInfo; + TupleTableSlot* slot = NULL; - // nachfolgende Werte in ParamDesc Liste zusammenfassen - int numParams = dmstate->numParams; - const char** values = dmstate->param_values; - FmgrInfo* param_flinfo = dmstate->param_flinfo; - List* param_exps = dmstate->param_exprs; - - // call db2ExecForeignDirectUpdate() similar to db2ExecForeignInsert() - return slot; + // nachfolgende Werte in ParamDesc Liste zusammenfassen + int numParams = dmstate->numParams; + const char** values = dmstate->param_values; + FmgrInfo* param_flinfo = dmstate->param_flinfo; + List* param_exps = dmstate->param_exprs; + + // call db2ExecForeignDirectUpdate() similar to db2ExecForeignInsert() + db2Entry(1,"> db2IterateDirectModify.c::db2IterateDirectModify"); + db2Exit(1,"> db2IterateDirectModify.c::db2IterateDirectModify : %x", slot); + return slot; } diff --git a/source/db2IterateForeignScan.c b/source/db2IterateForeignScan.c index d732d53..ecadf7b 100644 --- a/source/db2IterateForeignScan.c +++ b/source/db2IterateForeignScan.c @@ -2,7 +2,6 @@ #include #include #include -#include #include #include #include @@ -15,41 +14,39 @@ extern void db2PrepareQuery (DB2Session* session, const char * extern int db2ExecuteQuery (DB2Session* session, ParamDesc* paramList); extern int db2FetchNext (DB2Session* session); extern void db2CloseStatement (DB2Session* session); -extern void db2Debug1 (const char* message, ...); -extern void db2Debug2 (const char* message, ...); -extern void db2Debug3 (const char* message, ...); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); +extern void db2Debug (int level, const char* message, ...); extern void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls, bool trunc_lob) ; extern char* deparseDate (Datum datum); extern char* deparseTimestamp (Datum datum, bool hasTimezone); /** local prototypes */ -TupleTableSlot* db2IterateForeignScan(ForeignScanState* node); -char* setSelectParameters (ParamDesc *paramList, ExprContext * econtext); - -/** db2IterateForeignScan - * On first invocation (if there is no DB2 statement yet), - * get the actual parameter values and run the remote query against - * the DB2 database, retrieving the first result row. - * Subsequent invocations will fetch more result rows until there - * are no more. - * The result is stored as a virtual tuple in the ScanState's - * TupleSlot and returned. + TupleTableSlot* db2IterateForeignScan(ForeignScanState* node); +static char* setSelectParameters (ParamDesc *paramList, ExprContext * econtext); + +/* db2IterateForeignScan + * On first invocation (if there is no DB2 statement yet), get the actual parameter values and run the remote query against + * the DB2 database, retrieving the first result row. + * Subsequent invocations will fetch more result rows until there are no more. + * The result is stored as a virtual tuple in the ScanState's TupleSlot and returned. */ TupleTableSlot* db2IterateForeignScan (ForeignScanState* node) { TupleTableSlot* slot = node->ss.ss_ScanTupleSlot; ExprContext* econtext = node->ss.ps.ps_ExprContext; int have_result; DB2FdwState* fdw_state = (DB2FdwState*) node->fdw_state; - db2Debug1("> db2IterateForeignScan"); + + db2Entry(1,"> db2IterateForeignScan.c::db2IterateForeignScan"); if (db2IsStatementOpen (fdw_state->session)) { - db2Debug3(" get next row in foreign table scan"); + db2Debug(2,"get next row in foreign table scan"); /* fetch the next result row */ have_result = db2FetchNext (fdw_state->session); } else { /* fill the parameter list with the actual values */ char* paramInfo = setSelectParameters (fdw_state->paramList, econtext); /* execute the DB2 statement and fetch the first row */ - db2Debug3(" execute query in foreign table scan '%s'", paramInfo); + db2Debug(2,"execute query in foreign table scan '%s'", paramInfo); db2PrepareQuery (fdw_state->session, fdw_state->query, fdw_state->resultList, fdw_state->prefetch, fdw_state->fetch_size); have_result = db2ExecuteQuery (fdw_state->session, fdw_state->paramList); have_result = db2FetchNext (fdw_state->session); @@ -60,7 +57,7 @@ TupleTableSlot* db2IterateForeignScan (ForeignScanState* node) { /* increase row count */ ++fdw_state->rowcount; /* convert result to arrays of values and null indicators */ - db2Debug2(" slot->tts_tupleDescriptor->natts: %d",slot->tts_tupleDescriptor->natts); + db2Debug(2,"slot->tts_tupleDescriptor->natts: %d",slot->tts_tupleDescriptor->natts); convertTuple (fdw_state, slot->tts_tupleDescriptor->natts, slot->tts_values, slot->tts_isnull, false); /* store the virtual tuple */ ExecStoreVirtualTuple (slot); @@ -68,7 +65,7 @@ TupleTableSlot* db2IterateForeignScan (ForeignScanState* node) { /* close the statement */ db2CloseStatement (fdw_state->session); } - db2Debug1("< db2IterateForeignScan"); + db2Exit(1,"< db2IterateForeignScan.c::db2IterateForeignScan"); return slot; } @@ -76,7 +73,7 @@ TupleTableSlot* db2IterateForeignScan (ForeignScanState* node) { * Set the current values of the parameters into paramList. * Return a string containing the parameters set for a DEBUG message. */ -char* setSelectParameters (ParamDesc* paramList, ExprContext* econtext) { +static char* setSelectParameters (ParamDesc* paramList, ExprContext* econtext) { ParamDesc* param; Datum datum; HeapTuple tuple; @@ -86,9 +83,9 @@ char* setSelectParameters (ParamDesc* paramList, ExprContext* econtext) { MemoryContext oldcontext; StringInfoData info; /* list of parameters for DEBUG message */ - db2Debug1("> setSelectParameters"); - db2Debug2(" paramList: %x",paramList); - db2Debug2(" econtext : %x",econtext); + db2Entry(4,"> db2IterateForeignScan.c::setSelectParameters"); + db2Debug(5,"paramList: %x",paramList); + db2Debug(5,"econtext : %x",econtext); initStringInfo (&info); @@ -149,7 +146,7 @@ char* setSelectParameters (ParamDesc* paramList, ExprContext* econtext) { /* reset memory context */ MemoryContextSwitchTo (oldcontext); - db2Debug1("< setSelectParameters - returns: '%s'",info.data); + db2Exit(4,"< db2IterateForeignScan.c::setSelectParameters : %s", info.data); return info.data; } diff --git a/source/db2PlanDirectModify.c b/source/db2PlanDirectModify.c index 33f3da2..9176127 100644 --- a/source/db2PlanDirectModify.c +++ b/source/db2PlanDirectModify.c @@ -10,8 +10,9 @@ #include "DB2FdwState.h" /** external prototypes */ -extern void db2Debug1 (const char* message, ...); -extern void db2Debug2 (const char* message, ...); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); +extern void db2Debug (int level, const char* message, ...); extern bool is_foreign_expr (PlannerInfo *root, RelOptInfo *baserel, Expr *expr); extern void deparseDirectUpdateSql (StringInfo buf, PlannerInfo *root, Index rtindex, Relation rel, RelOptInfo *foreignrel, List *targetlist, List *targetAttrs, List *remote_conds, List **params_list, List *returningList, List **retrieved_attrs); extern void deparseDirectDeleteSql (StringInfo buf, PlannerInfo *root, Index rtindex, Relation rel, RelOptInfo *foreignrel, List *remote_conds, List **params_list, List *returningList, List **retrieved_attrs); @@ -24,16 +25,19 @@ static ForeignScan* find_modifytable_subplan (PlannerInfo* root, ModifyTable* p * Decide whether it is safe to modify a foreign table directly, and if so, rewrite subplan accordingly. */ bool db2PlanDirectModify(PlannerInfo* root, ModifyTable* plan, Index rtindex, int subplan_index) { - bool fResult = true; + bool fResult = true; - db2Debug1("> %s::db2PlanDirectModify", __FILE__); - /* Decide whether it is safe to modify a foreign table directly. */ - /* The table modification must be an UPDATE or DELETE. */ + db2Entry(1,"> db2PlanDirectModify.c::db2PlanDirectModify"); + db2Debug(2,"plan->operation: %d", plan->operation); + db2Debug(2,"plan->returningLists: %x - %d", plan->returningLists, list_length(plan->returningLists)); + /* The table modification must be an UPDATE or DELETE and must not use RETURNING */ if ((plan->operation == CMD_UPDATE || plan->operation == CMD_DELETE) && plan->returningLists == NIL) { /* Try to locate the ForeignScan subplan that's scanning rtindex. */ ForeignScan* fscan = find_modifytable_subplan(root, plan, rtindex, subplan_index); + db2Debug(2,"fscan: %x",fscan); if (fscan) { /* It's unsafe to modify a foreign table directly if there are any quals that should be evaluated locally. */ + db2Debug(2,"fscan->scan.plan.qual: %x",fscan->scan.plan.qual); if (fscan->scan.plan.qual == NIL) { RelOptInfo* foreignrel = NULL; RangeTblEntry* rte = NULL; @@ -50,6 +54,7 @@ bool db2PlanDirectModify(PlannerInfo* root, ModifyTable* plan, Index rtindex, in foreignrel = root->simple_rel_array[rtindex]; } rte = root->simple_rte_array[rtindex]; + /* skip deserialization of plan data*/ fpinfo = (DB2FdwState*) foreignrel->fdw_private; /* It's unsafe to update a foreign table directly, @@ -66,20 +71,17 @@ bool db2PlanDirectModify(PlannerInfo* root, ModifyTable* plan, Index rtindex, in forboth(lc, processed_tlist, lc2, targetAttrs) { TargetEntry* tle = lfirst_node(TargetEntry, lc); AttrNumber attno = lfirst_int(lc2); - /* update's new-value expressions shouldn't be resjunk */ Assert(!tle->resjunk); - if (attno <= InvalidAttrNumber) /* shouldn't happen */ elog(ERROR, "system-column update is not supported"); - if (!is_foreign_expr(root, foreignrel, (Expr *) tle->expr)) { fResult = false; break; } } } - + db2Debug(2,"fResult: %s",(fResult) ? "true" : "false"); if (fResult) { StringInfoData sql; Relation rel; @@ -150,7 +152,7 @@ bool db2PlanDirectModify(PlannerInfo* root, ModifyTable* plan, Index rtindex, in } } } - db2Debug1("< %s::db2PlanDirectModify : %s", __FILE__, (fResult) ? "true" : "false"); + db2Exit(1,"< db2PlanDirectModify.c::db2PlanDirectModify : %s", (fResult) ? "true" : "false"); return fResult; } @@ -162,35 +164,43 @@ bool db2PlanDirectModify(PlannerInfo* root, ModifyTable* plan, Index rtindex, in */ static ForeignScan* find_modifytable_subplan(PlannerInfo* root, ModifyTable* plan, Index rtindex, int subplan_index) { ForeignScan* fscan = NULL; - Plan* subplan = outerPlan(plan); + Plan* subplan = outerPlan(plan); - /* The cases we support are (1) the desired ForeignScan is the immediate child of ModifyTable, or (2) it is the subplan_index'th child of an - * Append node that is the immediate child of ModifyTable. + db2Entry(1,"> db2PlanDirectModify.c::find_modifytable_subplan"); + /* The cases we support are (1) the desired ForeignScan is the immediate child of ModifyTable, or (2) it is the subplan_index'th child of an + * Append node that is the immediate child of ModifyTable. * There is no point in looking further down, as that would mean that local joins are involved, so we can't do the update directly. - * There could be a Result atop the Append too, acting to compute the UPDATE targetlist values. + * There could be a Result atop the Append too, acting to compute the UPDATE targetlist values. * We ignore that here; the tlist will be checked by our caller. - * In principle we could examine all the children of the Append, but it's currently unlikely that the core planner would generate such a plan - * with the children out-of-order. + * In principle we could examine all the children of the Append, but it's currently unlikely that the core planner would generate such a plan + * with the children out-of-order. * Moreover, such a search risks costing O(N^2) time when there are a lot of children. - */ - if (IsA(subplan, Append)) { - Append* appendplan = (Append*) subplan; - - if (subplan_index < list_length(appendplan->appendplans)) - subplan = (Plan *) list_nth(appendplan->appendplans, subplan_index); - } - else if (IsA(subplan, Result) && outerPlan(subplan) != NULL && IsA(outerPlan(subplan), Append)) { - Append* appendplan = (Append*) outerPlan(subplan); - - if (subplan_index < list_length(appendplan->appendplans)) - subplan = (Plan *) list_nth(appendplan->appendplans, subplan_index); - } - - /* Now, have we got a ForeignScan on the desired rel? */ - if (IsA(subplan, ForeignScan) && (bms_is_member(rtindex, ((ForeignScan*) subplan)->fs_base_relids))) { - fscan = (ForeignScan*) subplan; - } - - return fscan; -} + */ + db2Debug(3,"subplan from outerplan: %x",subplan); + if (IsA(subplan, Append)) { + Append* append = (Append*) subplan; + + db2Debug(4,"subplan is Append"); + if (subplan_index < list_length(append->appendplans)) { + subplan = (Plan*) list_nth(append->appendplans, subplan_index); + db2Debug(3,"subplan from appendplan: %x",subplan); + } + } else if (IsA(subplan, Result) && outerPlan(subplan) != NULL && IsA(outerPlan(subplan), Append)) { + Append* append = (Append*) outerPlan(subplan); + db2Debug(4,"subplan is Result"); + if (subplan_index < list_length(append->appendplans)) { + subplan = (Plan*) list_nth(append->appendplans, subplan_index); + db2Debug(3,"subplan from resultplan: %x",subplan); + } + } + + /* Now, have we got a ForeignScan on the desired rel? */ + db2Debug(3,"subplan: %x",subplan); + if (IsA(subplan, ForeignScan) && (bms_is_member(rtindex, ((ForeignScan*) subplan)->fs_base_relids))) { + db2Debug(4,"subplan is ForeignScan"); + fscan = (ForeignScan*) subplan; + } + db2Exit(1,"< db2PlanDirectModify.c::find_modifytable_subplan : %x", fscan); + return fscan; +} diff --git a/source/db2PlanForeignModify.c b/source/db2PlanForeignModify.c index 1da4ed6..8d322f5 100644 --- a/source/db2PlanForeignModify.c +++ b/source/db2PlanForeignModify.c @@ -2,7 +2,6 @@ #include #include #include -#include #include #include #include "db2_fdw.h" @@ -13,10 +12,9 @@ extern char* db2strdup (const char* source); extern void* db2alloc (const char* type, size_t size); extern DB2FdwState* db2GetFdwState (Oid foreigntableid, double* sample_percent, bool describe); -extern void db2Debug1 (const char* message, ...); -extern void db2Debug2 (const char* message, ...); -extern void db2Debug4 (const char* message, ...); -extern void db2Debug5 (const char* message, ...); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); +extern void db2Debug (int level, const char* message, ...); extern short c2dbType (short fcType); extern void appendAsType (StringInfoData* dest, Oid type); extern List* serializePlanData (DB2FdwState* fdwState); @@ -27,10 +25,10 @@ static DB2FdwState* copyPlanData (DB2FdwState* orig); void addParam (ParamDesc** paramList, DB2Column* db2col, int colnum, int txts); void checkDataType (short db2type, int scale, Oid pgtype, const char* tablename, const char* colname); -/** db2PlanForeignModify - * Construct an DB2FdwState or copy it from the foreign scan plan. - * Construct the DB2 DML statement and a list of necessary parameters. - * Return the serialized DB2FdwState. +/* db2PlanForeignModify + * Construct an DB2FdwState or copy it from the foreign scan plan. + * Construct the DB2 DML statement and a list of necessary parameters. + * Return the serialized DB2FdwState. */ List* db2PlanForeignModify (PlannerInfo* root, ModifyTable* plan, Index resultRelation, int subplan_index) { CmdType operation = plan->operation; @@ -50,20 +48,22 @@ List* db2PlanForeignModify (PlannerInfo* root, ModifyTable* plan, Index resultRe AttrNumber col; int col_idx = -1; List* result = NIL; - /* - * Get the updated columns and the user for permission checks. - * We put that here at the beginning, since the way to do that changed - * considerably over the different PostgreSQL versions. + #if PG_VERSION_NUM >= 160000 + RTEPermissionInfo* perminfo = NULL; + #endif /* PG_VERSION_NUM >= 160000 */ + + db2Entry(1,"> db2PlanForeignModify.c::db2PlanForeignModify"); + /* Get the updated columns and the user for permission checks. + * We put that here at the beginning, since the way to do that changed considerably over the different PostgreSQL versions. */ -#if PG_VERSION_NUM >= 160000 - RTEPermissionInfo *perminfo = getRTEPermissionInfo(root->parse->rteperminfos, rte); + #if PG_VERSION_NUM >= 160000 + perminfo = getRTEPermissionInfo(root->parse->rteperminfos, rte); updated_cols = bms_copy(perminfo->updatedCols); -#else + #else updated_cols = bms_copy(rte->updatedCols); -#endif /* PG_VERSION_NUM >= 160000 */ - db2Debug1("> db2PlanForeignModify"); + #endif /* PG_VERSION_NUM >= 160000 */ -/* we don't support INSERT ... ON CONFLICT */ + /* we don't support INSERT ... ON CONFLICT */ if (plan->onConflictAction != ONCONFLICT_NONE) ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("INSERT with ON CONFLICT clause is not supported"))); @@ -74,19 +74,14 @@ List* db2PlanForeignModify (PlannerInfo* root, ModifyTable* plan, Index resultRe /* if yes, copy the foreign table information from the associated RelOptInfo */ fdwState = copyPlanData((DB2FdwState*)(root->simple_rel_array[resultRelation]->fdw_private)); } else { - /* - * If no, we have to construct the foreign table data ourselves. - * To match what ExecCheckRTEPerms does, pass the user whose user mapping - * should be used (if invalid, the current user is used). + /* If no, we have to construct the foreign table data ourselves. + * To match what ExecCheckRTEPerms does, pass the user whose user mapping should be used (if invalid, the current user is used). */ fdwState = db2GetFdwState(rte->relid, NULL, true); } initStringInfo(&sql); - /* - * Core code already has some lock on each rel being planned, so we can - * use NoLock here. - */ + /* Core code already has some lock on each rel being planned, so we can use NoLock here. */ rel = table_open(rte->relid, NoLock); /* figure out which attributes are affected and if there is a trigger */ @@ -229,7 +224,7 @@ List* db2PlanForeignModify (PlannerInfo* root, ModifyTable* plan, Index resultRe appendStringInfo (&sql, "%s = ", fdwState->db2Table->cols[i]->colName); appendAsType (&sql, fdwState->db2Table->cols[i]->pgtype); } - db2Debug2(" sql: '%s'",sql.data); + db2Debug(2,"sql: '%s'",sql.data); /* throw a meaningful error if nothing is updated */ if (firstcol) ereport (ERROR @@ -307,10 +302,10 @@ List* db2PlanForeignModify (PlannerInfo* root, ModifyTable* plan, Index resultRe } } fdwState->query = sql.data; - db2Debug2(" fdwState->query: '%s'", fdwState->query); + db2Debug(2,"fdwState->query: '%s'", fdwState->query); /* return a serialized form of the plan state */ result = serializePlanData (fdwState); - db2Debug1("< db2PlanForeignModify"); + db2Exit(1,"< db2PlanForeignModify.c::db2PlanForeignModify"); return result; } @@ -321,7 +316,7 @@ static DB2FdwState* copyPlanData (DB2FdwState* orig) { int i = 0; DB2FdwState* copy = NULL; - db2Debug1("> copyPlanData"); + db2Entry(4,"> db2PlanForeignModify.c::copyPlanData"); copy = db2alloc("copy_fdw_state", sizeof (DB2FdwState)); copy->dbserver = db2strdup(orig->dbserver); copy->user = db2strdup(orig->user); @@ -363,29 +358,29 @@ static DB2FdwState* copyPlanData (DB2FdwState* orig) { copy->rowcount = 0; copy->temp_cxt = NULL; copy->order_clause = NULL; - db2Debug1("< copyPlanData"); + db2Exit(4,"< db2PlanForeignModify.c::copyPlanData"); return copy; } -/** addParam - * Creates a new ParamDesc with the given values and adds it to the list. - * A deep copy of the parameter is created. +/* addParam + * Creates a new ParamDesc with the given values and adds it to the list. + * A deep copy of the parameter is created. */ void addParam (ParamDesc **paramList, DB2Column* db2col, int colnum, int txts) { ParamDesc *param; - db2Debug1("> addParam"); - db2Debug2(" pgtype: %d",db2col->pgtype); - db2Debug2(" colType: %d",db2col->colType); - db2Debug2(" colnum: %d",colnum); - db2Debug2(" txts: %d",txts); + db2Entry(1,"> db2PlanForeignModify.c::addParam"); + db2Debug(2,"pgtype: %d",db2col->pgtype); + db2Debug(2,"colType: %d",db2col->colType); + db2Debug(2,"colnum: %d",colnum); + db2Debug(2,"txts: %d",txts); param = db2alloc("paramList->next",sizeof (ParamDesc)); param->colName = db2strdup(db2col->colName); - db2Debug2(" param->colName: '%s'",param->colName); + db2Debug(2,"param->colName: '%s'",param->colName); param->colType = db2col->colType; - db2Debug2(" param->colType: '%d'",param->colType); + db2Debug(2,"param->colType: '%d'",param->colType); param->colSize = db2col->colSize; - db2Debug2(" param->colSize: '%d'",param->colSize); + db2Debug(2,"param->colSize: '%d'",param->colSize); param->type = db2col->pgtype; switch (c2dbType(db2col->colType)) { case DB2_INTEGER: @@ -406,45 +401,44 @@ void addParam (ParamDesc **paramList, DB2Column* db2col, int colnum, int txts) { default: param->bindType = BIND_STRING; } - db2Debug2(" param->bindType: '%d'",param->bindType); + db2Debug(2,"param->bindType: '%d'",param->bindType); param->value = NULL; - db2Debug2(" param->value: %x",param->value); + db2Debug(2,"param->value: %x",param->value); param->val_size = db2col->val_size; - db2Debug2(" param->val_size: %d",param->val_size); + db2Debug(2,"param->val_size: %d",param->val_size); param->node = NULL; - db2Debug2(" param->node: %x",param->node); + db2Debug(2,"param->node: %x",param->node); param->colnum = colnum; - db2Debug2(" param->colnum: %d",param->colnum); + db2Debug(2,"param->colnum: %d",param->colnum); param->txts = txts; - db2Debug2(" param->txts: %d",param->txts); + db2Debug(2,"param->txts: %d",param->txts); param->next = *paramList; *paramList = param; - db2Debug1("< addParam"); + db2Exit(1,"< db2PlanForeignModify.c::addParam"); } -/** checkDataType - * Check that the DB2 data type of a column can be - * converted to the PostgreSQL data type, raise an error if not. +/* checkDataType + * Check that the DB2 data type of a column can be converted to the PostgreSQL data type, raise an error if not. */ void checkDataType (short sqltype, int scale, Oid pgtype, const char *tablename, const char *colname) { short db2type = c2dbType(sqltype); - db2Debug4("> checkDataType"); - db2Debug4(" checkDataType: %s.%s of sqltype: %d, db2type: %d, pgtype: %d",tablename,colname,sqltype, db2type, pgtype); + db2Entry(4,"> db2PlanForeignModify.c::checkDataType"); + db2Debug(4,"checkDataType: %s.%s of sqltype: %d, db2type: %d, pgtype: %d",tablename,colname,sqltype, db2type, pgtype); /* the binary DB2 types can be converted to bytea */ if (db2type == DB2_BLOB && pgtype == BYTEAOID) { - db2Debug5(" DB2_BLOB can be converted into BYTEAOID"); + db2Debug(5,"DB2_BLOB can be converted into BYTEAOID"); } else if (db2type == DB2_XML && pgtype == XMLOID) { - db2Debug5(" DB2_XML can be converted into XMLOID"); + db2Debug(5,"DB2_XML can be converted into XMLOID"); } else if (db2type != DB2_UNKNOWN_TYPE && db2type != DB2_BLOB && (pgtype == TEXTOID || pgtype == VARCHAROID || pgtype == BPCHAROID)) { - db2Debug5(" DB2_UNKNONW && not DB2_BLOB can be converted into TEXTOID, VARCHAROID, BPCHAROID"); + db2Debug(5,"DB2_UNKNONW && not DB2_BLOB can be converted into TEXTOID, VARCHAROID, BPCHAROID"); } else if ((db2type == DB2_INTEGER || db2type == DB2_SMALLINT || db2type == DB2_BIGINT || db2type == DB2_FLOAT || db2type == DB2_DOUBLE || db2type == DB2_REAL || db2type == DB2_DECIMAL || db2type == DB2_DECFLOAT) && (pgtype == NUMERICOID || pgtype == FLOAT4OID || pgtype == FLOAT8OID)) { - db2Debug5(" DB2_INTEGER,SMALLINT,BIGINT,FLOAT,DOUBLE,REAL,DECIMAL,DECFLOAT can be converted into NUMERICOID,FLOAT4OID,FLOAT8OID"); + db2Debug(5,"DB2_INTEGER,SMALLINT,BIGINT,FLOAT,DOUBLE,REAL,DECIMAL,DECFLOAT can be converted into NUMERICOID,FLOAT4OID,FLOAT8OID"); } else if ((db2type == DB2_INTEGER || db2type == DB2_SMALLINT || db2type == DB2_BIGINT || db2type == DB2_BOOLEAN) && scale <= 0 && (pgtype == INT2OID || pgtype == INT4OID || pgtype == INT8OID || pgtype == BOOLOID)) { - db2Debug5(" DB2_INTEGER,SMALLINT,BIGINT,BOOLEAN can be converted into INT2OID, INT42OID, INT8OID,BOOLOID"); + db2Debug(5,"DB2_INTEGER,SMALLINT,BIGINT,BOOLEAN can be converted into INT2OID, INT42OID, INT8OID,BOOLOID"); } else if ((db2type == DB2_TYPE_DATE || db2type == DB2_TYPE_TIME || db2type == DB2_TYPE_TIMESTAMP || db2type == DB2_TYPE_TIMESTAMP_WITH_TIMEZONE) && (pgtype == DATEOID || pgtype == TIMESTAMPOID || pgtype == TIMESTAMPTZOID || pgtype == TIMEOID || pgtype == TIMETZOID)) { - db2Debug5(" DB2_TYPE_DATE,TIME,TIMESTAMP,TIMESTAMP_WITH_TIMEZONE can be converted into DATEOID,TIMESTAMPOID,TIMESTAMPTZOID,TIMEOID,TIMETZOID"); + db2Debug(5,"DB2_TYPE_DATE,TIME,TIMESTAMP,TIMESTAMP_WITH_TIMEZONE can be converted into DATEOID,TIMESTAMPOID,TIMESTAMPTZOID,TIMEOID,TIMETZOID"); } else if ((db2type == DB2_VARCHAR || db2type == DB2_CLOB) && pgtype == JSONOID) { - db2Debug5(" DB2_VARCHAR or DB2_CLOB can be converted into JSONOID"); + db2Debug(5,"DB2_VARCHAR or DB2_CLOB can be converted into JSONOID"); } else { /* nok - report an error */ ereport ( ERROR @@ -457,6 +451,5 @@ void checkDataType (short sqltype, int scale, Oid pgtype, const char *tablename, ) ); } - db2Debug4("< checkDataType"); + db2Exit(4,"< db2PlanForeignModify.c::checkDataType"); } - diff --git a/source/db2PrepareQuery.c b/source/db2PrepareQuery.c index c47d329..8993a03 100644 --- a/source/db2PrepareQuery.c +++ b/source/db2PrepareQuery.c @@ -13,9 +13,9 @@ extern char db2Message[ERRBUFSIZE];/* contains DB2 error messages, set by db2CheckErr() */ /** external prototypes */ -extern void db2Debug1 (const char* message, ...); -extern void db2Debug2 (const char* message, ...); -extern void db2Debug3 (const char* message, ...); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); +extern void db2Debug (int level, const char* message, ...); extern SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleType, int line, char* file); extern void db2Error (db2error sqlstate, const char* message); extern void db2Error_d (db2error sqlstate, const char* message, const char* detail, ...); @@ -27,13 +27,12 @@ extern void* db2alloc (const char* type, size_t size); /** internal prototypes */ void db2PrepareQuery (DB2Session* session, const char *query, DB2ResultColumn* resultList, unsigned long prefetch, int fetchsize); -/** db2PrepareQuery - * Prepares an SQL statement for execution. - * This function should handle everything that has to be done only once - * even if the statement is executed multiple times, that is: - * - For SELECT statements, defines the result values to be stored in db2Table. - * - For DML statements, allocates LOB locators for the RETURNING clause in db2Table. - * - Set the prefetch options. +/* db2PrepareQuery + * Prepares an SQL statement for execution. + * This function should handle everything that has to be done only once even if the statement is executed multiple times, that is: + * - For SELECT statements, defines the result values to be stored in db2Table. + * - For DML statements, allocates LOB locators for the RETURNING clause in db2Table. + * - Set the prefetch options. */ void db2PrepareQuery (DB2Session* session, const char *query, DB2ResultColumn* resultList, unsigned long prefetch, int fetchsize) { int col_pos = 0; @@ -47,10 +46,10 @@ void db2PrepareQuery (DB2Session* session, const char *query, DB2ResultColumn* r fetchsize = 1; #endif - db2Debug1("> db2PrepareQuery"); - db2Debug2(" query : '%s'",query); - db2Debug2(" prefetch : %d",prefetch); - db2Debug2(" fetchsize: %d",fetchsize); + db2Entry(1,"> db2PrepareQuery.c::db2PrepareQuery"); + db2Debug(2,"query : '%s'",query); + db2Debug(2,"prefetch : %d",prefetch); + db2Debug(2,"fetchsize: %d",fetchsize); /* figure out if the query is FOR UPDATE */ is_select = (strncmp (query, "SELECT", 6) == 0); for_update = (strstr (query, "FOR UPDATE") != NULL); @@ -62,27 +61,27 @@ void db2PrepareQuery (DB2Session* session, const char *query, DB2ResultColumn* r /* create statement handle */ session->stmtp = db2AllocStmtHdl(SQL_HANDLE_STMT, session->connp, FDW_UNABLE_TO_CREATE_EXECUTION, "error executing query: failed to allocate statement handle"); - db2Debug2(" session->stmtp->hsql: %d",session->stmtp->hsql); + db2Debug(2,"session->stmtp->hsql: %d",session->stmtp->hsql); /* set prefetch options */ if (is_select) { SQLULEN prefetch_rows = prefetch; SQLULEN cur_fetchsize = fetchsize; - db2Debug3(" IS_SELECT"); + db2Debug(3,"IS_SELECT"); if (for_update) { - db2Debug3(" FOR UPDATE"); + db2Debug(3,"FOR UPDATE"); // Make the cursor sensitive scrollable (e.g., static) so PREFETCH_NROWS applies rc = SQLSetStmtAttr(session->stmtp->hsql, SQL_ATTR_CURSOR_TYPE, (SQLPOINTER)SQL_CURSOR_DYNAMIC, 0); rc = db2CheckErr(rc, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); if (rc != SQL_SUCCESS) { db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error executing query: SQLSetStmtAttr failed to make cursor dynamic", db2Message); } - db2Debug3(" set cursor dynamic"); + db2Debug(3,"set cursor dynamic"); rc = SQLSetStmtAttr(session->stmtp->hsql, SQL_ATTR_CONCURRENCY, (SQLPOINTER)SQL_CONCUR_LOCK, 0); rc = db2CheckErr(rc, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); if (rc != SQL_SUCCESS) { db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error executing query: SQLSetStmtAttr failed to make cursor pessemistic", db2Message); } - db2Debug3(" set cursor pessemistic"); + db2Debug(3,"set cursor pessemistic"); } else { // Make the cursor insensitive scrollable (e.g., static) so PREFETCH_NROWS applies rc = SQLSetStmtAttr(session->stmtp->hsql, SQL_ATTR_CURSOR_TYPE, (SQLPOINTER)SQL_CURSOR_STATIC, 0); @@ -90,7 +89,7 @@ void db2PrepareQuery (DB2Session* session, const char *query, DB2ResultColumn* r if (rc != SQL_SUCCESS) { db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error executing query: SQLSetStmtAttr failed to make cursor scrollable", db2Message); } - db2Debug3(" set cursor static"); + db2Debug(3,"set cursor static"); } // Fetch rows per network roundtrip rc = SQLSetStmtAttr(session->stmtp->hsql, SQL_ATTR_ROW_ARRAY_SIZE, SQL_VALUE_PTR_ULEN(cur_fetchsize), 0); @@ -98,18 +97,18 @@ void db2PrepareQuery (DB2Session* session, const char *query, DB2ResultColumn* r if (rc != SQL_SUCCESS) { db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error executing query: SQLSetStmtAttr failed to set fetchsize in statement handle", db2Message); } - db2Debug2(" set cursor fetchsize: %d",cur_fetchsize); + db2Debug(2,"set cursor fetchsize: %d",cur_fetchsize); // Prefetch rows per block for scrollable (non-dynamic) cursors rc = SQLSetStmtAttr(session->stmtp->hsql, SQL_ATTR_PREFETCH_NROWS, SQL_VALUE_PTR_ULEN(prefetch_rows), 0); rc = db2CheckErr(rc, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); if (rc != SQL_SUCCESS) { db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error executing query: SQLSetStmtAttr failed to set number of prefetched rows in statement handle", db2Message); } - db2Debug2(" set cursor prefetch: %d",prefetch_rows); + db2Debug(2,"set cursor prefetch: %d",prefetch_rows); } /* prepare the statement */ - db2Debug2(" query to prepare: '%s'",query); + db2Debug(2,"query to prepare: '%s'",query); rc = SQLPrepare(session->stmtp->hsql, (SQLCHAR*)query, SQL_NTS); rc = db2CheckErr(rc, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); if (rc != SQL_SUCCESS) { @@ -128,22 +127,22 @@ void db2PrepareQuery (DB2Session* session, const char *query, DB2ResultColumn* r if (res->pgtype == UUIDOID) { fparamType = SQL_C_CHAR; } - db2Debug2(" res->colName : %s" ,res->colName); - db2Debug2(" res->colSize : %ld",res->colSize); - db2Debug2(" res->colType : %d" ,res->colType); - db2Debug2(" res->colScale : %d" ,res->colScale); - db2Debug2(" res->colNulls : %d" ,res->colNulls); - db2Debug2(" res->colChars : %ld",res->colChars); - db2Debug2(" res->colBytes : %ld",res->colBytes); - db2Debug2(" res->colPrimKeyPart: %d" ,res->colPrimKeyPart); - db2Debug2(" res->colCodepage : %d" ,res->colCodepage); - db2Debug2(" res->val : %x" ,res->val); - db2Debug2(" res->val_size : %ld",res->val_size); - db2Debug2(" res->val_len : %d" ,res->val_len); - db2Debug2(" res->val_null : %d" ,res->val_null); - db2Debug2(" res->resnum : %d" ,res->resnum); - db2Debug2(" fparamType: %d (%s)",fparamType,param2name(fparamType)); - db2Debug2(" SQLBindCol(%d,%d,%d(%s),%x,%ld,%x)",session->stmtp->hsql,res->resnum, fparamType, param2name(fparamType), res->val, res->val_size, &res->val_null); + db2Debug(2,"res->colName : %s" ,res->colName); + db2Debug(2,"res->colSize : %ld",res->colSize); + db2Debug(2,"res->colType : %d" ,res->colType); + db2Debug(2,"res->colScale : %d" ,res->colScale); + db2Debug(2,"res->colNulls : %d" ,res->colNulls); + db2Debug(2,"res->colChars : %ld",res->colChars); + db2Debug(2,"res->colBytes : %ld",res->colBytes); + db2Debug(2,"res->colPrimKeyPart: %d" ,res->colPrimKeyPart); + db2Debug(2,"res->colCodepage : %d" ,res->colCodepage); + db2Debug(2,"res->val : %x" ,res->val); + db2Debug(2,"res->val_size : %ld",res->val_size); + db2Debug(2,"res->val_len : %d" ,res->val_len); + db2Debug(2,"res->val_null : %d" ,res->val_null); + db2Debug(2,"res->resnum : %d" ,res->resnum); + db2Debug(2,"fparamType: %d (%s)",fparamType,param2name(fparamType)); + db2Debug(2,"SQLBindCol(%d,%d,%d(%s),%x,%ld,%x)",session->stmtp->hsql,res->resnum, fparamType, param2name(fparamType), res->val, res->val_size, &res->val_null); rc = SQLBindCol (session->stmtp->hsql,res->resnum, fparamType, res->val, res->val_size, &res->val_null); rc = db2CheckErr(rc, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); if (rc != SQL_SUCCESS) { @@ -152,8 +151,8 @@ void db2PrepareQuery (DB2Session* session, const char *query, DB2ResultColumn* r col_pos++; } - db2Debug2(" is_select: %s",is_select ? "true" : "false"); - db2Debug2(" col_pos: %d",col_pos); + db2Debug(2,"is_select: %s",is_select ? "true" : "false"); + db2Debug(2,"col_pos: %d",col_pos); if (is_select && col_pos == 0) { /* No columns selected (i.e., SELECT '1' FROM or COUNT(*)). * Use persistent buffers from statement handle to avoid stack deallocation issues. @@ -165,5 +164,5 @@ void db2PrepareQuery (DB2Session* session, const char *query, DB2ResultColumn* r db2Error_d ( FDW_UNABLE_TO_CREATE_EXECUTION, "error executing query: SQLBindCol failed to define result value", db2Message); } } - db2Debug1("< db2PrepareQuery"); + db2Exit(1,"< db2PrepareQuery.c::db2PrepareQuery"); } diff --git a/source/db2ReAllocFree.c b/source/db2ReAllocFree.c index ec6f787..64ca2fb 100644 --- a/source/db2ReAllocFree.c +++ b/source/db2ReAllocFree.c @@ -1,11 +1,12 @@ #include -#include #include #include #include "db2_fdw.h" /*+ external prototypes */ -extern void db2Debug5 (const char* message, ...); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); +extern void db2Debug (int level, const char* message, ...); /** local prototypes */ void* db2alloc (const char* type, size_t size); @@ -18,7 +19,9 @@ char* db2strdup (const char* source); */ void* db2alloc (const char* type, size_t size) { void* memory = palloc0(size); - db2Debug5(" ++ %x: %d bytes - %s", memory, size, type); + db2Entry(5,"db2ReAllocFree.c::db2alloc"); + db2Debug(4,"++ %x: %d bytes - %s", memory, size, type); + db2Exit (5,"db2ReAllocFree.c::db2alloc"); return memory; } @@ -27,24 +30,31 @@ void* db2alloc (const char* type, size_t size) { */ void* db2realloc (void* p, size_t size) { void* memory = repalloc(p, size); - db2Debug5(" ++ %x: %d bytes", memory, size); + db2Entry(5,"db2ReAllocFree.c::db2realloc"); + db2Debug(4,"++ %x: %d bytes", memory, size); + db2Exit(5,"db2ReAllocFree.c::db2realloc"); return memory; } + /** db2free * Expose pfree() to DB2 functions. */ void db2free (void* p) { + db2Entry(5,"db2ReAllocFree.c::db2free"); if (p != NULL) { - db2Debug5(" -- %x", p); + db2Debug(4,"-- %x", p); pfree (p); } + db2Exit(5,"db2ReAllocFree.c::db2free"); } char* db2strdup(const char* source) { char* target = NULL; + db2Entry(5,"db2ReAllocFree.c::db2strdup"); if (source != NULL && source[0] != '\0') { target = pstrdup(source); } - db2Debug5(" ++ %x: dup'ed string from %x content '%s'",target, source, source); + db2Debug(4,"++ %x: dup'ed string from %x content '%s'",target, source, source); + db2Exit(5,"db2ReAllocFree.c::db2strdup"); return target; } \ No newline at end of file diff --git a/source/db2ReScanForeignScan.c b/source/db2ReScanForeignScan.c index 898dc2a..71594ea 100644 --- a/source/db2ReScanForeignScan.c +++ b/source/db2ReScanForeignScan.c @@ -1,29 +1,29 @@ #include #include -#include #include #include #include "db2_fdw.h" #include "DB2FdwState.h" /** external prototypes */ -extern void db2CloseStatement (DB2Session* session); -extern void db2Debug1 (const char* message, ...); +extern void db2CloseStatement (DB2Session* session); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); /** local prototypes */ void db2ReScanForeignScan(ForeignScanState* node); -/** db2ReScanForeignScan - * Close the DB2 statement if there is any. - * That causes the next db2IterateForeignScan call to restart the scan. +/* db2ReScanForeignScan + * Close the DB2 statement if there is any. + * That causes the next db2IterateForeignScan call to restart the scan. */ void db2ReScanForeignScan (ForeignScanState* node) { DB2FdwState* fdw_state = (DB2FdwState*) node->fdw_state; - db2Debug1("> db2ReScanForeignScan"); + db2Entry(1,"> db2ReScanForeignScan.c::db2ReScanForeignScan"); /* close open DB2 statement if there is one */ db2CloseStatement(fdw_state->session); /* reset row count to zero */ fdw_state->rowcount = 0; - db2Debug1("< db2ReScanForeignScan"); + db2Exit(1,"< db2ReScanForeignScan.c::db2ReScanForeignScan"); } diff --git a/source/db2ServerVersion.c b/source/db2ServerVersion.c index ac31e11..5ca53ba 100644 --- a/source/db2ServerVersion.c +++ b/source/db2ServerVersion.c @@ -8,24 +8,25 @@ /** external variables */ /** external prototypes */ -extern void db2Debug1 (const char* message, ...); -extern void db2Debug2 (const char* message, ...); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); +extern void db2Debug (int level, const char* message, ...); extern SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleType, int line, char* file); /** local prototypes */ void db2ServerVersion (DB2Session* session, char* version); -/** db2ServerVersion - * Returns the five components of the server version. +/* db2ServerVersion + * Returns the five components of the server version. */ void db2ServerVersion (DB2Session* session, char* version) { SQLSMALLINT len = 0; size_t ver_len = sizeof(version); SQLRETURN rc = 0; - db2Debug1("> db2ServerVersion"); + db2Entry(1,"> db2ServerVersion.c::db2ServerVersion"); memset(version,0x00,ver_len); rc = SQLGetInfo(session->connp->hdbc, SQL_DBMS_VER, version, sizeof(version), &len); - db2Debug2(" rc = %d, version = '%s', ind = %d", rc, version, len); + db2Debug(2,"rc = %d, version = '%s', ind = %d", rc, version, len); rc = db2CheckErr(rc,session->connp->hdbc,SQL_HANDLE_DBC,__LINE__,__FILE__); - db2Debug1("< db2ServerVersion - version: '%s'", version); + db2Exit(1,"< db2ServerVersion.c::db2ServerVersion - version: '%s'", version); } diff --git a/source/db2SetHandlers.c b/source/db2SetHandlers.c index c24fff0..17b8c83 100644 --- a/source/db2SetHandlers.c +++ b/source/db2SetHandlers.c @@ -1,42 +1,43 @@ #include #include #include -#include #include #include #include "db2_fdw.h" /** external prototypes */ extern void db2Cancel (void); -extern void db2Debug1 (const char* message, ...); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); /** local prototypes */ void db2SetHandlers(void); void db2Die (SIGNAL_ARGS); -/** db2SetHandlers - * Set signal handler for SIGTERM. +/* db2SetHandlers + * Set signal handler for SIGTERM. */ void db2SetHandlers (void) { + db2Entry(5,"> db2SetHandlers.c::db2SetHandlers"); pqsignal (SIGTERM, db2Die); + db2Exit(5,"< db2SetHandlers.c::db2SetHandlers"); } -/** db2Die - * Terminate the current query and prepare backend shutdown. - * This is a signal handler function. +/* db2Die + * Terminate the current query and prepare backend shutdown. + * This is a signal handler function. */ void db2Die (SIGNAL_ARGS) { - db2Debug1("> db2Die"); - /** Terminate any running queries. + db2Entry(1,"> db2SetHandlers.c::db2Die"); + /* Terminate any running queries. * The DB2 sessions will be terminated by exitHook(). */ db2Cancel(); - /** Call the original backend shutdown function. + /* Call the original backend shutdown function. * If a query was canceled above, an error from DB2 would result. - * To have the backend report the correct FATAL error instead, - * we have to call CHECK_FOR_INTERRUPTS() before we report that error; + * To have the backend report the correct FATAL error instead, we have to call CHECK_FOR_INTERRUPTS() before we report that error; * this is done in db2Error_d. */ die (postgres_signal_arg); - db2Debug1("< db2Die"); + db2Exit(1,"< db2SetHandlers.c::db2Die"); } \ No newline at end of file diff --git a/source/db2SetSavepoint.c b/source/db2SetSavepoint.c index 2066e89..ba97641 100644 --- a/source/db2SetSavepoint.c +++ b/source/db2SetSavepoint.c @@ -9,8 +9,9 @@ extern char db2Message[ERRBUFSIZE];/* contains DB2 error messages, set by db2CheckErr() */ /** external prototypes */ -extern void db2Debug1 (const char* message, ...); -extern void db2Debug2 (const char* message, ...); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); +extern void db2Debug (int level, const char* message, ...); extern void db2Error_d (db2error sqlstate, const char* message, const char* detail, ...); extern SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleType, int line, char* file); extern HdlEntry* db2AllocStmtHdl (SQLSMALLINT type, DB2ConnEntry* connp, db2error error, const char* errmsg); @@ -19,20 +20,21 @@ extern void db2FreeStmtHdl (HdlEntry* handlep, DB2ConnEntry* conn /** local prototypes */ void db2SetSavepoint (DB2Session* session, int nest_level); -/** db2SetSavepoint - * Set savepoints up to level "nest_level". +/* db2SetSavepoint + * Set savepoints up to level "nest_level". */ void db2SetSavepoint (DB2Session* session, int nest_level) { SQLRETURN rc = 0; HdlEntry* hstmt = NULL; - db2Debug1("> db2SetSavepoint(session, nest_level %d)",nest_level); - db2Debug2(" xact_level: %d",session->connp->xact_level); + + db2Entry(1,"> db2SetSavepoint.c::db2SetSavepoint(session, nest_level %d)",nest_level); + db2Debug(2,"xact_level: %d",session->connp->xact_level); while (session->connp->xact_level < nest_level) { SQLCHAR query[80]; - db2Debug2(" db2_fdw::db2SetSavepoint: set savepoint s%d", session->connp->xact_level + 1); + db2Debug(2,"db2_fdw::db2SetSavepoint: set savepoint s%d", session->connp->xact_level + 1); snprintf((char*)query, 79, "SAVEPOINT s%d ON ROLLBACK RETAIN CURSORS", session->connp->xact_level + 1); - db2Debug2(" query: '%s'",query); + db2Debug(2,"query: '%s'",query); /* create statement handle */ hstmt = db2AllocStmtHdl(SQL_HANDLE_STMT, session->connp, FDW_UNABLE_TO_CREATE_EXECUTION, "error setting savepoint: failed to allocate statement handle"); @@ -55,6 +57,6 @@ void db2SetSavepoint (DB2Session* session, int nest_level) { db2FreeStmtHdl(hstmt, session->connp); ++session->connp->xact_level; } - db2Debug2(" xact_level: %d",session->connp->xact_level); - db2Debug1("< db2SetSavepoint"); + db2Debug(2,"xact_level: %d",session->connp->xact_level); + db2Exit(1,"< db2SetSavepoint.c::db2SetSavepoint"); } diff --git a/source/db2Shutdown.c b/source/db2Shutdown.c index db9ae5b..80c1141 100644 --- a/source/db2Shutdown.c +++ b/source/db2Shutdown.c @@ -10,24 +10,25 @@ extern int sql_initialized; /* set to "1" as soon as SQLAllocHand extern DB2EnvEntry* rootenvEntry; /* Linked list of handles for cached DB2 connections. */ /** external prototypes */ -extern void db2Debug1 (const char* message, ...); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); extern void db2FreeEnvHdl (DB2EnvEntry* envp, const char* nls_lang); extern void db2CloseConnections (void); /** local prototypes */ void db2Shutdown(void); -/** db2Shutdown - * Close all open connections, release handles, terminate DB2. - * This will be called at the end of the PostgreSQL session. +/* db2Shutdown + * Close all open connections, release handles, terminate DB2. + * This will be called at the end of the PostgreSQL session. */ void db2Shutdown (void) { - db2Debug1("> db2Shutdown"); + db2Entry(1,"> db2Shutdown.c::db2Shutdown"); /* don't report error messages */ silent = 1; db2CloseConnections(); /* done with DB2 */ if (sql_initialized) db2FreeEnvHdl(rootenvEntry, NULL); - db2Debug1("< db2Shutdown"); + db2Exit(1,"< db2Shutdown.c::db2Shutdown"); } diff --git a/source/db2_de_serialize.c b/source/db2_de_serialize.c index 10c068a..2c45cd4 100644 --- a/source/db2_de_serialize.c +++ b/source/db2_de_serialize.c @@ -1,14 +1,13 @@ #include #include #include -#include #include "db2_fdw.h" #include "DB2FdwState.h" /** external prototypes */ -extern void db2Debug1 (const char* message, ...); -extern void db2Debug3 (const char* message, ...); -extern void db2Debug4 (const char* message, ...); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); +extern void db2Debug (int level, const char* message, ...); extern void* db2alloc (const char* type, size_t size); extern char* c2name (short fcType); @@ -31,7 +30,7 @@ DB2FdwState* deserializePlanData (List* list) { int len = 0; ParamDesc* param = NULL; - db2Debug1("> deserializePlanData"); + db2Entry(1,"> db2_de_serialize.c::deserializePlanData"); /* session will be set upon connect */ state->session = NULL; /* these fields are not needed during execution */ @@ -75,39 +74,39 @@ DB2FdwState* deserializePlanData (List* list) { for (i = 0; i < state->db2Table->ncols; ++i) { state->db2Table->cols[i] = (DB2Column *) db2alloc ("state->db2Table->cols[i]", sizeof (DB2Column)); state->db2Table->cols[i]->colName = deserializeString(list_nth(list, idx++)); - db2Debug3(" deserialize col[%d].colName: %s" ,i, state->db2Table->cols[i]->colName); + db2Debug(3,"deserialize col[%d].colName: %s" ,i, state->db2Table->cols[i]->colName); state->db2Table->cols[i]->colType = (short) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug3(" deserialize col[%d].colType: %d" ,i, state->db2Table->cols[i]->colType); + db2Debug(3,"deserialize col[%d].colType: %d" ,i, state->db2Table->cols[i]->colType); state->db2Table->cols[i]->colSize = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug3(" deserialize col[%d].colSize: %d" ,i, state->db2Table->cols[i]->colSize); + db2Debug(3,"deserialize col[%d].colSize: %d" ,i, state->db2Table->cols[i]->colSize); state->db2Table->cols[i]->colScale = (short) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug3(" deserialize col[%d].colScale: %d" ,i, state->db2Table->cols[i]->colScale); + db2Debug(3,"deserialize col[%d].colScale: %d" ,i, state->db2Table->cols[i]->colScale); state->db2Table->cols[i]->colNulls = (short) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug3(" deserialize col[%d].colNulls: %d" ,i, state->db2Table->cols[i]->colNulls); + db2Debug(3,"deserialize col[%d].colNulls: %d" ,i, state->db2Table->cols[i]->colNulls); state->db2Table->cols[i]->colChars = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug3(" deserialize col[%d].colChars: %d" ,i, state->db2Table->cols[i]->colChars); + db2Debug(3,"deserialize col[%d].colChars: %d" ,i, state->db2Table->cols[i]->colChars); state->db2Table->cols[i]->colBytes = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug3(" deserialize col[%d].colBytes: %d" ,i, state->db2Table->cols[i]->colBytes); + db2Debug(3,"deserialize col[%d].colBytes: %d" ,i, state->db2Table->cols[i]->colBytes); state->db2Table->cols[i]->colPrimKeyPart = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug3(" deserialize col[%d].colPrimKeyPart: %d" ,i, state->db2Table->cols[i]->colPrimKeyPart); + db2Debug(3,"deserialize col[%d].colPrimKeyPart: %d" ,i, state->db2Table->cols[i]->colPrimKeyPart); state->db2Table->cols[i]->colCodepage = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug3(" deserialize col[%d].colCodepage: %d" ,i, state->db2Table->cols[i]->colCodepage); + db2Debug(3,"deserialize col[%d].colCodepage: %d" ,i, state->db2Table->cols[i]->colCodepage); state->db2Table->cols[i]->pgname = deserializeString(list_nth(list, idx++)); - db2Debug3(" deserialize col[%d].pgname: %s" ,i, state->db2Table->cols[i]->pgname); + db2Debug(3,"deserialize col[%d].pgname: %s" ,i, state->db2Table->cols[i]->pgname); state->db2Table->cols[i]->pgattnum = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug3(" deserialize col[%d].pgattnum: %d" ,i, state->db2Table->cols[i]->pgattnum); + db2Debug(3,"deserialize col[%d].pgattnum: %d" ,i, state->db2Table->cols[i]->pgattnum); state->db2Table->cols[i]->pgtype = DatumGetObjectId(((Const*)list_nth(list, idx++))->constvalue); - db2Debug3(" deserialize col[%d].pgtype: %d" ,i, state->db2Table->cols[i]->pgtype); + db2Debug(3,"deserialize col[%d].pgtype: %d" ,i, state->db2Table->cols[i]->pgtype); state->db2Table->cols[i]->pgtypmod = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug3(" deserialize col[%d].pgtypmod: %d" ,i, state->db2Table->cols[i]->pgtypmod); + db2Debug(3,"deserialize col[%d].pgtypmod: %d" ,i, state->db2Table->cols[i]->pgtypmod); state->db2Table->cols[i]->used = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug3(" deserialize col[%d].used: %d" ,i, state->db2Table->cols[i]->used); + db2Debug(3,"deserialize col[%d].used: %d" ,i, state->db2Table->cols[i]->used); state->db2Table->cols[i]->pkey = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug3(" deserialize col[%d].pkey: %d" ,i, state->db2Table->cols[i]->pkey); + db2Debug(3,"deserialize col[%d].pkey: %d" ,i, state->db2Table->cols[i]->pkey); state->db2Table->cols[i]->val_size = deserializeLong(list_nth(list, idx++)); - db2Debug3(" deserialize col[%d].val_size: %ld" ,i, state->db2Table->cols[i]->val_size); + db2Debug(3,"deserialize col[%d].val_size: %ld" ,i, state->db2Table->cols[i]->val_size); state->db2Table->cols[i]->noencerr = deserializeLong(list_nth(list, idx++)); - db2Debug3(" deserialize col[%d].noencerr: %ld" ,i, state->db2Table->cols[i]->noencerr); + db2Debug(3,"deserialize col[%d].noencerr: %ld" ,i, state->db2Table->cols[i]->noencerr); } /* length of parameter list */ @@ -118,28 +117,28 @@ DB2FdwState* deserializePlanData (List* list) { for (i = 0; i < len; ++i) { param = (ParamDesc*) db2alloc ("state->parmList->next", sizeof (ParamDesc)); param->colName = deserializeString(list_nth(list, idx++)); - db2Debug3(" deserialize param[%d].colName: %s" ,i, param->colName); + db2Debug(3,"deserialize param[%d].colName: %s" ,i, param->colName); param->colType = (short) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug3(" deserialize param[%d].colType: %d" ,i, param->colType); + db2Debug(3,"deserialize param[%d].colType: %d" ,i, param->colType); param->colSize = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug3(" deserialize param[%d].colSize: %d" ,i, param->colSize); + db2Debug(3,"deserialize param[%d].colSize: %d" ,i, param->colSize); param->type = DatumGetObjectId(((Const*)list_nth(list, idx++))->constvalue); - db2Debug3(" deserialize param[%d].type: %d" ,i, param->type); + db2Debug(3,"deserialize param[%d].type: %d" ,i, param->type); param->bindType = (db2BindType) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug3(" deserialize param[%d].bindType: %d" ,i, param->bindType); + db2Debug(3,"deserialize param[%d].bindType: %d" ,i, param->bindType); if (param->bindType == BIND_OUTPUT) param->value = (void *) 42; /* something != NULL */ else param->value = NULL; - db2Debug3(" deserialize param[%d].value: %x" ,i, param->value); + db2Debug(3,"deserialize param[%d].value: %x" ,i, param->value); param->val_size = deserializeLong(list_nth(list, idx++)); - db2Debug3(" deserialize param[%d].val_size: %ld" ,i, param->val_size); + db2Debug(3,"deserialize param[%d].val_size: %ld" ,i, param->val_size); param->node = NULL; - db2Debug3(" deserialize param[%d].node: %x" ,i, param->node); + db2Debug(3,"deserialize param[%d].node: %x" ,i, param->node); param->colnum = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug3(" deserialize param[%d].colnum: %d" ,i, param->colnum); + db2Debug(3,"deserialize param[%d].colnum: %d" ,i, param->colnum); param->txts = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug3(" deserialize param[%d].txts: %d" ,i, param->txts); + db2Debug(3,"deserialize param[%d].txts: %d" ,i, param->txts); param->next = state->paramList; state->paramList = param; } @@ -151,46 +150,46 @@ DB2FdwState* deserializePlanData (List* list) { for (i = 0; i < len; ++i) { DB2ResultColumn* res = (DB2ResultColumn *) db2alloc ("state->resultList->next", sizeof (DB2ResultColumn)); res->colName = deserializeString(list_nth(list, idx++)); - db2Debug3(" deserialize res[%d].colName: %s" ,i, res->colName); + db2Debug(3,"deserialize res[%d].colName: %s" ,i, res->colName); res->colType = (short) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug3(" deserialize res[%d].colType: %d" ,i, res->colType); + db2Debug(3,"deserialize res[%d].colType: %d" ,i, res->colType); res->colSize = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug3(" deserialize res[%d].colSize: %d" ,i, res->colSize); + db2Debug(3,"deserialize res[%d].colSize: %d" ,i, res->colSize); res->colScale = (short) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug3(" deserialize res[%d].colScale: %d" ,i, res->colScale); + db2Debug(3,"deserialize res[%d].colScale: %d" ,i, res->colScale); res->colNulls = (short) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug3(" deserialize res[%d].colNulls: %d" ,i, res->colNulls); + db2Debug(3,"deserialize res[%d].colNulls: %d" ,i, res->colNulls); res->colChars = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug3(" deserialize res[%d].colChars: %d" ,i, res->colChars); + db2Debug(3,"deserialize res[%d].colChars: %d" ,i, res->colChars); res->colBytes = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug3(" deserialize res[%d].colBytes: %d" ,i, res->colBytes); + db2Debug(3,"deserialize res[%d].colBytes: %d" ,i, res->colBytes); res->colPrimKeyPart = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug3(" deserialize res[%d].colPrimKeyPart: %d" ,i, res->colPrimKeyPart); + db2Debug(3,"deserialize res[%d].colPrimKeyPart: %d" ,i, res->colPrimKeyPart); res->colCodepage = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug3(" deserialize res[%d].colCodepage: %d" ,i, res->colCodepage); + db2Debug(3,"deserialize res[%d].colCodepage: %d" ,i, res->colCodepage); res->pgname = deserializeString(list_nth(list, idx++)); - db2Debug3(" deserialize res[%d].pgname: %s" ,i, res->pgname); + db2Debug(3,"deserialize res[%d].pgname: %s" ,i, res->pgname); res->pgattnum = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug3(" deserialize res[%d].pgattnum: %d" ,i, res->pgattnum); + db2Debug(3,"deserialize res[%d].pgattnum: %d" ,i, res->pgattnum); res->pgtype = DatumGetObjectId(((Const*)list_nth(list, idx++))->constvalue); - db2Debug3(" deserialize res[%d].pgtype: %d" ,i, res->pgtype); + db2Debug(3,"deserialize res[%d].pgtype: %d" ,i, res->pgtype); res->pgtypmod = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug3(" deserialize res[%d].pgtypmod: %d" ,i, res->pgtypmod); + db2Debug(3,"deserialize res[%d].pgtypmod: %d" ,i, res->pgtypmod); res->pkey = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug3(" deserialize res[%d].pkey: %d" ,i, res->pkey); + db2Debug(3,"deserialize res[%d].pkey: %d" ,i, res->pkey); res->val_size = deserializeLong(list_nth(list, idx++)); - db2Debug3(" deserialize res[%d].val_size: %ld" ,i, res->val_size); + db2Debug(3,"deserialize res[%d].val_size: %ld" ,i, res->val_size); res->noencerr = deserializeLong(list_nth(list, idx++)); - db2Debug3(" deserialize res[%d].noencerr: %ld" ,i, res->noencerr); + db2Debug(3,"deserialize res[%d].noencerr: %ld" ,i, res->noencerr); res->resnum = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug3(" deserialize res[%d].resnum: %d" ,i, res->resnum); + db2Debug(3,"deserialize res[%d].resnum: %d" ,i, res->resnum); res->val = (char*) db2alloc ("res->val", res->val_size + 1); res->val_len = 0; res->val_null = 1; res->next = state->resultList; state->resultList = res; } - db2Debug1("< deserializePlanData - returns: %x", state); + db2Exit(1,"< db2_de_serialize.c::deserializePlanData : %x", state); return state; } @@ -199,10 +198,10 @@ DB2FdwState* deserializePlanData (List* list) { */ static char* deserializeString (Const* constant) { char* result = NULL; - db2Debug4("> deserializeString"); + db2Entry(5,"> db2_de_serialize.c::deserializeString"); if (!constant->constisnull) result = text_to_cstring (DatumGetTextP (constant->constvalue)); - db2Debug4("< deserializeString: '%s'", result); + db2Exit(5,"< db2_de_serialize.c::deserializeString : '%s'", result); return result; } @@ -211,10 +210,10 @@ static char* deserializeString (Const* constant) { */ static long deserializeLong (Const* constant) { long result = 0L; - db2Debug4("> deserializeLong"); + db2Entry(5,"> db2_de_serialize.c::deserializeLong"); result = (sizeof (long) <= 4) ? (long) DatumGetInt32 (constant->constvalue) : (long) DatumGetInt64 (constant->constvalue); - db2Debug4("< deserializeLong - returns: %ld", result); + db2Exit(5,"< db2_de_serialize.c::deserializeLong : %ld", result); return result; } @@ -229,7 +228,7 @@ List* serializePlanData (DB2FdwState* fdwState) { ParamDesc* param = NULL; DB2ResultColumn* rcol = NULL; - db2Debug1("> serializePlanData"); + db2Entry(1,"> db2_de_serialize.c::serializePlanData"); result = list_make1(fdwState->retrieved_attr); /* dbserver */ result = lappend (result, serializeString (fdwState->dbserver)); @@ -262,39 +261,39 @@ List* serializePlanData (DB2FdwState* fdwState) { /* column data */ for (idxCol = 0; idxCol < fdwState->db2Table->ncols; ++idxCol) { result = lappend (result, serializeString (fdwState->db2Table->cols[idxCol]->colName)); - db2Debug3(" serialize col[%d].colName: %s" ,idxCol, fdwState->db2Table->cols[idxCol]->colName); + db2Debug(3,"serialize col[%d].colName: %s" ,idxCol, fdwState->db2Table->cols[idxCol]->colName); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->colType)); - db2Debug3(" serialize col[%d].colType: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colType); + db2Debug(3,"serialize col[%d].colType: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colType); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->colSize)); - db2Debug3(" serialize col[%d].colSize: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colSize); + db2Debug(3,"serialize col[%d].colSize: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colSize); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->colScale)); - db2Debug3(" serialize col[%d].colScale: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colScale); + db2Debug(3,"serialize col[%d].colScale: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colScale); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->colNulls)); - db2Debug3(" serialize col[%d].colNulls: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colNulls); + db2Debug(3,"serialize col[%d].colNulls: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colNulls); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->colChars)); - db2Debug3(" serialize col[%d].colChars: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colChars); + db2Debug(3,"serialize col[%d].colChars: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colChars); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->colBytes)); - db2Debug3(" serialize col[%d].colBytes: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colBytes); + db2Debug(3,"serialize col[%d].colBytes: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colBytes); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->colPrimKeyPart)); - db2Debug3(" serialize col[%d].colPrimKeyPart: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colPrimKeyPart); + db2Debug(3,"serialize col[%d].colPrimKeyPart: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colPrimKeyPart); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->colCodepage)); - db2Debug3(" serialize col[%d].colCodepage: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colCodepage); + db2Debug(3,"serialize col[%d].colCodepage: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colCodepage); result = lappend (result, serializeString (fdwState->db2Table->cols[idxCol]->pgname)); - db2Debug3(" serialize col[%d].pgname: %s" ,idxCol, fdwState->db2Table->cols[idxCol]->pgname); + db2Debug(3,"serialize col[%d].pgname: %s" ,idxCol, fdwState->db2Table->cols[idxCol]->pgname); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->pgattnum)); - db2Debug3(" serialize col[%d].pgattnum: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->pgattnum); + db2Debug(3,"serialize col[%d].pgattnum: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->pgattnum); result = lappend (result, serializeOid (fdwState->db2Table->cols[idxCol]->pgtype)); - db2Debug3(" serialize col[%d].pgtype: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->pgtype); + db2Debug(3,"serialize col[%d].pgtype: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->pgtype); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->pgtypmod)); - db2Debug3(" serialize col[%d].pgtypmod: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->pgtypmod); + db2Debug(3,"serialize col[%d].pgtypmod: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->pgtypmod); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->used)); - db2Debug3(" serialize col[%d].used: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->used); + db2Debug(3,"serialize col[%d].used: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->used); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->pkey)); - db2Debug3(" serialize col[%d].pkey: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->pkey); + db2Debug(3,"serialize col[%d].pkey: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->pkey); result = lappend (result, serializeLong (fdwState->db2Table->cols[idxCol]->val_size)); - db2Debug3(" serialize col[%d].val_size: %ld" ,idxCol, fdwState->db2Table->cols[idxCol]->val_size); + db2Debug(3,"serialize col[%d].val_size: %ld" ,idxCol, fdwState->db2Table->cols[idxCol]->val_size); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->noencerr)); - db2Debug3(" serialize col[%d].noencerr: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->noencerr); + db2Debug(3,"serialize col[%d].noencerr: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->noencerr); /* don't serialize val, val_len, val_null and varno */ } @@ -304,25 +303,25 @@ List* serializePlanData (DB2FdwState* fdwState) { } /* serialize length */ result = lappend (result, serializeInt (lenParam)); - db2Debug3(" serialize paramList.length: %d", lenParam); + db2Debug(3,"serialize paramList.length: %d", lenParam); /* parameter list entries */ for (param = fdwState->paramList; param; param = param->next) { result = lappend (result, serializeString (param->colName)); - db2Debug3(" serialize param.colName: %s" , param->colName); + db2Debug(3,"serialize param.colName: %s" , param->colName); result = lappend (result, serializeInt (param->colType)); - db2Debug3(" serialize param.colType: %d" , param->colType); + db2Debug(3,"serialize param.colType: %d" , param->colType); result = lappend (result, serializeInt (param->colSize)); - db2Debug3(" serialize param.colSize: %d" , param->colSize); + db2Debug(3,"serialize param.colSize: %d" , param->colSize); result = lappend (result, serializeOid (param->type)); - db2Debug3(" serialize param.type: %d" , param->type); + db2Debug(3,"serialize param.type: %d" , param->type); result = lappend (result, serializeInt ((int) param->bindType)); - db2Debug3(" serialize param.bindType: %d" , param->bindType); + db2Debug(3,"serialize param.bindType: %d" , param->bindType); result = lappend (result, serializeLong (param->val_size)); - db2Debug3(" serialize param.val_size: %ld" , param->val_size); + db2Debug(3,"serialize param.val_size: %ld" , param->val_size); result = lappend (result, serializeInt ((int) param->colnum)); - db2Debug3(" serialize param.colnum: %d" , param->colnum); + db2Debug(3,"serialize param.colnum: %d" , param->colnum); result = lappend (result, serializeInt ((int) param->txts)); - db2Debug3(" serialize param.txts: %d" , param->txts); + db2Debug(3,"serialize param.txts: %d" , param->txts); } /* find length of result list */ @@ -332,48 +331,48 @@ List* serializePlanData (DB2FdwState* fdwState) { } /* serialize length */ result = lappend (result, serializeInt (lenParam)); - db2Debug3(" serialize resultList.length: %d", lenParam); + db2Debug(3,"serialize resultList.length: %d", lenParam); /* parameter list entries */ for (rcol = fdwState->resultList; rcol; rcol = rcol->next) { result = lappend (result, serializeString (rcol->colName)); - db2Debug3(" serialize res.colName: %s" , rcol->colName); + db2Debug(3,"serialize res.colName: %s" , rcol->colName); result = lappend (result, serializeInt (rcol->colType)); - db2Debug3(" serialize res.colType: %d" , rcol->colType); + db2Debug(3,"serialize res.colType: %d" , rcol->colType); result = lappend (result, serializeInt (rcol->colSize)); - db2Debug3(" serialize res.colSize: %d" , rcol->colSize); + db2Debug(3,"serialize res.colSize: %d" , rcol->colSize); result = lappend (result, serializeInt (rcol->colScale)); - db2Debug3(" serialize res.colScale: %d" , rcol->colScale); + db2Debug(3,"serialize res.colScale: %d" , rcol->colScale); result = lappend (result, serializeInt (rcol->colNulls)); - db2Debug3(" serialize res.colNulls: %d", rcol->colNulls); + db2Debug(3,"serialize res.colNulls: %d", rcol->colNulls); result = lappend (result, serializeInt (rcol->colChars)); - db2Debug3(" serialize res.colChars: %d" , rcol->colChars); + db2Debug(3,"serialize res.colChars: %d" , rcol->colChars); result = lappend (result, serializeInt (rcol->colBytes)); - db2Debug3(" serialize res.colBytes: %d" , rcol->colBytes); + db2Debug(3,"serialize res.colBytes: %d" , rcol->colBytes); result = lappend (result, serializeInt (rcol->colPrimKeyPart)); - db2Debug3(" serialize res.colPrimKeyPart: %d", rcol->colPrimKeyPart); + db2Debug(3,"serialize res.colPrimKeyPart: %d", rcol->colPrimKeyPart); result = lappend (result, serializeInt (rcol->colCodepage)); - db2Debug3(" serialize res.codepage: %d" , rcol->colCodepage); + db2Debug(3,"serialize res.codepage: %d" , rcol->colCodepage); result = lappend (result, serializeString (rcol->pgname)); - db2Debug3(" serialize res.pgname: %s" , rcol->pgname); + db2Debug(3,"serialize res.pgname: %s" , rcol->pgname); result = lappend (result, serializeInt (rcol->pgattnum)); - db2Debug3(" serialize res.pgattnum: %d" , rcol->pgattnum); + db2Debug(3,"serialize res.pgattnum: %d" , rcol->pgattnum); result = lappend (result, serializeOid (rcol->pgtype)); - db2Debug3(" serialize res.pgtype: %d" , rcol->pgtype); + db2Debug(3,"serialize res.pgtype: %d" , rcol->pgtype); result = lappend (result, serializeInt (rcol->pgtypmod)); - db2Debug3(" serialize res.pgtypmod: %d" , rcol->pgtypmod); + db2Debug(3,"serialize res.pgtypmod: %d" , rcol->pgtypmod); result = lappend (result, serializeInt (rcol->pkey)); - db2Debug3(" serialize res.pkey: %d" , rcol->pkey); + db2Debug(3,"serialize res.pkey: %d" , rcol->pkey); result = lappend (result, serializeLong (rcol->val_size)); - db2Debug3(" serialize res.val_size: %ld", rcol->val_size); + db2Debug(3,"serialize res.val_size: %ld", rcol->val_size); result = lappend (result, serializeInt (rcol->noencerr)); - db2Debug3(" serialize res.noencerr: %d" , rcol->noencerr); + db2Debug(3,"serialize res.noencerr: %d" , rcol->noencerr); result = lappend (result, serializeInt (rcol->resnum)); // the last result is the first in the list - db2Debug3(" serialize res.resnum: %d" , rcol->resnum); + db2Debug(3,"serialize res.resnum: %d" , rcol->resnum); lenParam--; } /* don't serialize params, startup_cost, total_cost, rowcount, temp_cxt, order_clause and where_clause */ - db2Debug1("< serializePlanData - returns: %x",result); + db2Exit(1,"< db2_de_serialize.c::serializePlanData : %x",result); return result; } @@ -382,10 +381,10 @@ List* serializePlanData (DB2FdwState* fdwState) { */ static Const* serializeString (const char* s) { Const* result = NULL; - db2Debug4("> serializeString"); + db2Entry(5,"> db2_de_serialize.c::serializeString"); result = (s == NULL) ? makeNullConst (TEXTOID, -1, InvalidOid) : makeConst (TEXTOID, -1, InvalidOid, -1, PointerGetDatum (cstring_to_text (s)), false, false); - db2Debug4("< serializeString - returns: %x",result); + db2Exit(5,"< db2_de_serialize.c::serializeString : %x",result); return result; } @@ -394,7 +393,7 @@ static Const* serializeString (const char* s) { */ static Const* serializeLong (long i) { Const* result = NULL; - db2Debug4("> serializeLong"); + db2Entry(5,"> db2_de_serialize.c::serializeLong"); if (sizeof (long) <= 4) result = makeConst (INT4OID, -1, InvalidOid, 4, Int32GetDatum ((int32) i), false, true); else @@ -405,6 +404,6 @@ static Const* serializeLong (long i) { false #endif /* USE_FLOAT8_BYVAL */ ); - db2Debug4("< serializeLong - returns: %x",result); + db2Exit(5,"< db2_de_serialize.c::serializeLong : %x",result); return result; } diff --git a/source/db2_deparse.c b/source/db2_deparse.c index 6b4dc49..bebebcd 100644 --- a/source/db2_deparse.c +++ b/source/db2_deparse.c @@ -23,7 +23,6 @@ #include #include -#include #include #include #include @@ -89,11 +88,9 @@ typedef struct deparse_expr_cxt { /** external prototypes */ extern short c2dbType (short fcType); -extern void db2Debug1 (const char* message, ...); -extern void db2Debug2 (const char* message, ...); -extern void db2Debug3 (const char* message, ...); -extern void db2Debug4 (const char* message, ...); -extern void db2Debug5 (const char* message, ...); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); +extern void db2Debug (int level, const char* message, ...); extern void* db2alloc (const char* type, size_t size); extern void* db2strdup (const char* source); extern void db2free (void* p); @@ -190,7 +187,7 @@ static void deparseReturningList (StringInfo buf, RangeTblEntry *rt void classifyConditions(PlannerInfo* root, RelOptInfo* baserel, List* input_conds, List** remote_conds, List** local_conds) { ListCell* lc = NULL; - db2Debug1("> %s::classifyConditions",__FILE__); + db2Entry(1,"> db2_deparse.c::classifyConditions"); *remote_conds = NIL; *local_conds = NIL; @@ -202,7 +199,7 @@ void classifyConditions(PlannerInfo* root, RelOptInfo* baserel, List* input_cond else *local_conds = lappend(*local_conds, ri); } - db2Debug1("< %s::classifyConditions",__FILE__); + db2Exit(1,"< db2_deparse.c::classifyConditions"); } /** Returns true if given expr is safe to evaluate on the foreign server. @@ -213,7 +210,7 @@ bool is_foreign_expr(PlannerInfo *root, RelOptInfo *baserel, Expr *expr) { DB2FdwState* fpinfo = (DB2FdwState*) (baserel->fdw_private); bool fResult = false; - db2Debug1("> %s::is_foreign_expr",__FILE__); + db2Entry(1,"> db2_deparse.c::is_foreign_expr"); // Check that the expression consists of nodes that are safe to execute remotely. glob_cxt.root = root; glob_cxt.foreignrel = baserel; @@ -235,7 +232,7 @@ bool is_foreign_expr(PlannerInfo *root, RelOptInfo *baserel, Expr *expr) { } } } - db2Debug1("< %s::is_foreign_expr : %s",__FILE__, (fResult) ? "true": "false"); + db2Exit(1,"< db2_deparse.c::is_foreign_expr : %s", (fResult) ? "true": "false"); /* OK to evaluate on the remote server */ return fResult; } @@ -259,7 +256,7 @@ bool is_foreign_expr(PlannerInfo *root, RelOptInfo *baserel, Expr *expr) { static bool foreign_expr_walker(Node* node, foreign_glob_cxt* glob_cxt, foreign_loc_cxt* outer_cxt, foreign_loc_cxt* case_arg_cxt) { bool fResult = true; - db2Debug4("> %s::foreign_expr_walker",__FILE__); + db2Entry(1,"> db2_deparse.c::foreign_expr_walker"); /* Need do nothing for empty subexpressions */ if (node != NULL) { bool check_type = true; @@ -768,7 +765,7 @@ static bool foreign_expr_walker(Node* node, foreign_glob_cxt* glob_cxt, foreign_ } } /* It looks OK */ - db2Debug4("< %s::foreign_expr_walker : %s",__FILE__, (fResult) ? "true" : "false"); + db2Exit(1,"< db2_deparse.c::foreign_expr_walker : %s", (fResult) ? "true" : "false"); return fResult; } @@ -781,10 +778,10 @@ static bool foreign_expr_walker(Node* node, foreign_glob_cxt* glob_cxt, foreign_ */ bool is_foreign_param(PlannerInfo* root, RelOptInfo* baserel, Expr* expr) { bool fResult = false; - db2Debug1("> %s::is_foreign_param", __FILE__); - db2Debug2(" expr: %x", expr); + db2Entry(1,"> db2_deparse.c::is_foreign_param"); + db2Debug(2,"expr: %x", expr); if (expr != NULL) { - db2Debug5(" ((Node*)expr)->type: %d", nodeTag(expr)); + db2Debug(5,"((Node*)expr)->type: %d", nodeTag(expr)); switch (nodeTag(expr)) { case T_Var: { /* It would have to be sent unless it's a foreign Var */ @@ -803,7 +800,7 @@ bool is_foreign_param(PlannerInfo* root, RelOptInfo* baserel, Expr* expr) { break; } } - db2Debug1("< %s::is_foreign_param : %s",__FILE__, (fResult) ? "true" : "false"); + db2Exit(1,"< db2_deparse.c::is_foreign_param : %s", (fResult) ? "true" : "false"); return fResult; } @@ -815,7 +812,7 @@ bool is_foreign_pathkey(PlannerInfo* root, RelOptInfo* baserel, PathKey* pathkey DB2FdwState* fpinfo = (DB2FdwState*) baserel->fdw_private; bool fResult = false; - db2Debug4("> %s::is_foreign_pathkey",__FILE__); + db2Entry(1,"> db2_deparse.c::is_foreign_pathkey"); /* is_foreign_expr would detect volatile expressions as well, but checking ec_has_volatile here saves some cycles. */ if (!pathkey_ec->ec_has_volatile) { /* can't push down the sort if the pathkey's opfamily is not shippable */ @@ -824,7 +821,7 @@ bool is_foreign_pathkey(PlannerInfo* root, RelOptInfo* baserel, PathKey* pathkey fResult = (find_em_for_rel(root, pathkey_ec, baserel) != NULL); } } - db2Debug4("< %s::is_foreign_pathkey : %s",__FILE__, (fResult) ? "true" : "false"); + db2Exit(1,"< db2_deparse.c::is_foreign_pathkey : %s", (fResult) ? "true" : "false"); return fResult; } @@ -840,12 +837,12 @@ static char* deparse_type_name(Oid type_oid, int32 typemod) { bits16 flags = FORMAT_TYPE_TYPEMOD_GIVEN; char* result = NULL; - db2Debug4("> %s::deparse_type_name",__FILE__); + db2Entry(1,"> db2_deparse.c::deparse_type_name"); if (!is_builtin(type_oid)) flags |= FORMAT_TYPE_FORCE_QUALIFY; result = format_type_extended(type_oid, typemod, flags); - db2Debug4("< %s::deparse_type_name : %s",__FILE__, result); + db2Exit(1,"< db2_deparse.c::deparse_type_name : %s", result); return result; } @@ -853,9 +850,9 @@ static char* deparse_type_name(Oid type_oid, int32 typemod) { * Append "s" to "dest", adding appropriate casts for datetime "type". */ void appendAsType (StringInfoData* dest, Oid type) { - db2Debug1("> %s::appendAsType", __FILE__); - db2Debug2(" dest->data: '%s'",dest->data); - db2Debug2(" type: %d",type); + db2Entry(1,"> db2_deparse.c::appendAsType"); + db2Debug(2,"dest->data: '%s'",dest->data); + db2Debug(2,"type: %d",type); switch (type) { case DATEOID: appendStringInfo (dest, "CAST (? AS DATE)"); @@ -876,8 +873,8 @@ void appendAsType (StringInfoData* dest, Oid type) { appendStringInfo (dest, "?"); break; } - db2Debug2(" dest->data: '%s'", dest->data); - db2Debug1("< %s::appendAsType", __FILE__); + db2Debug(2,"dest->data: '%s'", dest->data); + db2Exit(1,"< db2_deparse.c::appendAsType"); } /** Deparse GROUP BY clause. @@ -885,7 +882,7 @@ void appendAsType (StringInfoData* dest, Oid type) { static void appendGroupByClause(List* tlist, deparse_expr_cxt* context) { Query* query = context->root->parse; - db2Debug4("> %s::appendGroupByClause",__FILE__); + db2Entry(1,"> db2_deparse.c::appendGroupByClause"); /* Nothing to be done, if there's no GROUP BY clause in the query. */ if (query->groupClause) { StringInfo buf = context->buf; @@ -907,9 +904,9 @@ static void appendGroupByClause(List* tlist, deparse_expr_cxt* context) { first = false; deparseSortGroupClause(grp->tleSortGroupRef, tlist, true, context); } - db2Debug5(" clause: %s", buf->data); + db2Debug(5,"clause: %s", buf->data); } - db2Debug4("< %s::appendGroupByClause",__FILE__); + db2Exit(1,"< db2_deparse.c::appendGroupByClause"); } /** Deparse ORDER BY clause defined by the given pathkeys. @@ -926,7 +923,7 @@ static void appendOrderByClause(List* pathkeys, bool has_final_sort, deparse_exp StringInfo buf = context->buf; bool gotone = false; - db2Debug4("> %s::appendOrderByClause",__FILE__); + db2Entry(1,"> db2_deparse.c::appendOrderByClause"); /* Make sure any constants in the exprs are printed portably */ nestlevel = set_transmission_modes(); @@ -977,8 +974,8 @@ static void appendOrderByClause(List* pathkeys, bool has_final_sort, deparse_exp appendOrderBySuffix(oprid, exprType((Node *) em_expr), pathkey->pk_nulls_first, context); } reset_transmission_modes(nestlevel); - db2Debug5(" clause: %s", context->buf->data); - db2Debug4("< %s::appendOrderByClause",__FILE__); + db2Debug(5,"clause: %s", context->buf->data); + db2Exit(1,"< db2_deparse.c::appendOrderByClause"); } /** Deparse LIMIT/OFFSET clause. @@ -988,7 +985,7 @@ static void appendLimitClause(deparse_expr_cxt* context) { StringInfo buf = context->buf; int nestlevel = 0; - db2Debug4("> %s::appendLimitClause",__FILE__); + db2Entry(1,"> db2_deparse.c::appendLimitClause"); /* Make sure any constants in the exprs are printed portably */ nestlevel = set_transmission_modes(); @@ -1001,8 +998,8 @@ static void appendLimitClause(deparse_expr_cxt* context) { deparseExprInt((Expr*) root->parse->limitOffset, context); } reset_transmission_modes(nestlevel); - db2Debug5(" clause: %s", context->buf->data); - db2Debug4("< %s::appendLimitClause",__FILE__); + db2Debug(5," clause: %s", context->buf->data); + db2Exit(1,"< db2_deparse.c::appendLimitClause"); } /** appendFunctionName @@ -1013,7 +1010,7 @@ static void appendLimitClause(deparse_expr_cxt* context) { // HeapTuple proctup; // Form_pg_proc procform; // -// db2Debug4("> %s::appendFunctionName",__FILE__); +// db2Entry(1,"> db2_deparse.c::appendFunctionName"); // proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid)); // if (!HeapTupleIsValid(proctup)) // elog(ERROR, "cache lookup failed for function %u", funcid); @@ -1026,8 +1023,7 @@ static void appendLimitClause(deparse_expr_cxt* context) { // /* Always print the function name */ // appendStringInfoString(buf, quote_identifier(NameStr(procform->proname))); // ReleaseSysCache(proctup); -// db2Debug5(" function name: %s", context->buf->data); -// db2Debug4("< %s::appendFunctionName",__FILE__); +// db2Exit(1,"< db2_deparse.c::appendFunctionName : %s", context->buf->data); //} /** Append the ASC, DESC, USING and NULLS FIRST / NULLS LAST parts of an ORDER BY clause. @@ -1036,7 +1032,7 @@ static void appendOrderBySuffix(Oid sortop, Oid sortcoltype, bool nulls_first, d StringInfo buf = context->buf; TypeCacheEntry* typentry; - db2Debug4("< %s::appendOrderBySuffix",__FILE__); + db2Entry(1,"< db2_deparse.c::appendOrderBySuffix"); /* See whether operator is default < or > for sort expr's datatype. */ typentry = lookup_type_cache(sortcoltype, TYPECACHE_LT_OPR | TYPECACHE_GT_OPR); @@ -1059,8 +1055,7 @@ static void appendOrderBySuffix(Oid sortop, Oid sortcoltype, bool nulls_first, d } appendStringInfo(buf, " NULLS %s", (nulls_first) ? "FIRST" : "LAST"); - db2Debug5(" order by suffix: %s", buf->data); - db2Debug4("< %s::appendOrderBySuffix",__FILE__); + db2Exit(1,"< db2_deparse.c::appendOrderBySuffix : %s", buf->data); } /* Print the representation of a parameter to be sent to the remote side. @@ -1072,10 +1067,10 @@ static void printRemoteParam(int paramindex, Oid paramtype, int32 paramtypmod, d StringInfo buf = context->buf; //char* ptypename = deparse_type_name(paramtype, paramtypmod); - db2Debug4("> %s::printRemoteParam",__FILE__); + db2Entry(1,"> db2_deparse.c::printRemoteParam"); // appendStringInfo(buf, "$%d::%s", paramindex, ptypename); appendStringInfo(buf, ":p%d", paramindex); - db2Debug4("< %s::printRemoteParam : %s",__FILE__, buf->data); + db2Exit(1,"< db2_deparse.c::printRemoteParam : %s", buf->data); } /* Print the representation of a placeholder for a parameter that will be sent to the remote side at execution time. @@ -1094,10 +1089,9 @@ static void printRemotePlaceholder(Oid paramtype, int32 paramtypmod, deparse_exp StringInfo buf = context->buf; char* ptypename = deparse_type_name(paramtype, paramtypmod); - db2Debug4("> printRemotePlaceholder",__FILE__); + db2Entry(1,"> printRemotePlaceholder"); appendStringInfo(buf, "((SELECT null::%s)::%s)", ptypename, ptypename); - db2Debug5(" remotePlaceholder: %s", buf->data); - db2Debug4("< printRemotePlaceholder",__FILE__); + db2Exit(1,"< printRemotePlaceholder : %s", buf->data); } /** Deparse conditions from the provided list and append them to buf. @@ -1112,7 +1106,7 @@ static void appendConditions(List* exprs, deparse_expr_cxt* context) { bool is_first = true; StringInfo buf = context->buf; - db2Debug4("> appendConditions",__FILE__); + db2Entry(1,"> appendConditions"); /* Make sure any constants in the exprs are printed portably */ nestlevel = set_transmission_modes(); @@ -1134,8 +1128,7 @@ static void appendConditions(List* exprs, deparse_expr_cxt* context) { is_first = false; } reset_transmission_modes(nestlevel); - db2Debug5(" conditions: %s", buf->data); - db2Debug4("< appendConditions",__FILE__); + db2Exit(1,"< appendConditions : %s", buf->data); } /* Append WHERE clause, containing conditions from exprs and additional_conds, to context->buf. @@ -1145,7 +1138,7 @@ static void appendWhereClause(List* exprs, List* additional_conds, deparse_expr_ bool need_and = false; ListCell* lc = NULL; - db2Debug4("> appendWhereClause",__FILE__); + db2Entry(1,"> appendWhereClause"); if (exprs != NIL || additional_conds != NIL) appendStringInfoString(buf, " WHERE "); @@ -1162,8 +1155,7 @@ static void appendWhereClause(List* exprs, List* additional_conds, deparse_expr_ appendStringInfoString(buf, (char*) lfirst(lc)); need_and = true; } - db2Debug5(" where clause: %s", buf->data); - db2Debug4("< appendWhereClause",__FILE__); + db2Exit(1,"< appendWhereClause : %s", buf->data); } /** Appends a sort or group clause. @@ -1176,7 +1168,7 @@ static Node* deparseSortGroupClause(Index ref, List* tlist, bool force_colno, de TargetEntry* tle = get_sortgroupref_tle(ref, tlist); Expr* expr = tle->expr; - db2Debug4("> %s::deparseSortGroupClause",__FILE__); + db2Entry(1,"> db2_deparse.c::deparseSortGroupClause"); if (force_colno) { /* Use column-number form when requested by caller. */ Assert(!tle->resjunk); @@ -1192,8 +1184,8 @@ static Node* deparseSortGroupClause(Index ref, List* tlist, bool force_colno, de deparseExprInt(expr, context); appendStringInfoChar(buf, ')'); } - db2Debug5(" clause: %s", buf->data); - db2Debug4("< %s::deparseSortGroupClause : %x",__FILE__, expr); + db2Debug(5,"clause: %s", buf->data); + db2Exit(1,"< db2_deparse.c::deparseSortGroupClause : %x", expr); return (Node*)expr; } @@ -1203,7 +1195,7 @@ static Node* deparseSortGroupClause(Index ref, List* tlist, bool force_colno, de static bool is_subquery_var(Var* node, RelOptInfo* foreignrel, int* relno, int* colno) { bool fResult = false; - db2Debug5("> %s::is_subquery_var",__FILE__); + db2Entry(1,"> db2_deparse.c::is_subquery_var"); /* Should only be called in these cases. */ Assert(IS_SIMPLE_REL(foreignrel) || IS_JOIN_REL(foreignrel)); /* If the given relation isn't a join relation, it doesn't have any lower subqueries, so the Var isn't a subquery output column. */ @@ -1234,7 +1226,7 @@ static bool is_subquery_var(Var* node, RelOptInfo* foreignrel, int* relno, int* } } } - db2Debug5("< %s::is_subquery_var : %s",__FILE__, (fResult) ? "true": "false"); + db2Exit(1,"< db2_deparse.c::is_subquery_var : %s", (fResult) ? "true": "false"); return fResult; } @@ -1246,10 +1238,10 @@ static void get_relation_column_alias_ids(Var* node, RelOptInfo* foreignrel, int ListCell* lc = NULL; bool fFound = false; - db2Debug4("> %s::get_relation_column_alias_ids",__FILE__); + db2Entry(1,"> db2_deparse.c::get_relation_column_alias_ids"); /* Get the relation alias ID */ *relno = fpinfo->relation_index; - db2Debug5(" relno: %d", *relno); + db2Debug(5,"relno: %d", *relno); /* Get the column alias ID */ foreach(lc, foreignrel->reltarget->exprs) { @@ -1260,7 +1252,7 @@ static void get_relation_column_alias_ids(Var* node, RelOptInfo* foreignrel, int */ if (IsA(tlvar, Var) && tlvar->varno == node->varno && tlvar->varattno == node->varattno) { *colno = i; - db2Debug5(" colno: %d", *colno); + db2Debug(5,"colno: %d", *colno); fFound = true; break; } @@ -1271,7 +1263,7 @@ static void get_relation_column_alias_ids(Var* node, RelOptInfo* foreignrel, int /* Shouldn't get here */ elog(ERROR, "unexpected expression in subquery output"); } - db2Debug4("< %s::get_relation_column_alias_ids",__FILE__); + db2Exit(1,"< db2_deparse.c::get_relation_column_alias_ids"); } /* Build the targetlist for given relation to be deparsed as SELECT clause. @@ -1284,23 +1276,23 @@ List* build_tlist_to_deparse(RelOptInfo* foreignrel) { List* tlist = NIL; DB2FdwState* fpinfo = (DB2FdwState*) foreignrel->fdw_private; - db2Debug1("> %s::build_tlist_to_deparse",__FILE__); + db2Entry(1,"> db2_deparse.c::build_tlist_to_deparse"); /* For an upper relation, we have already built the target list while checking shippability, so just return that. */ if (IS_UPPER_REL(foreignrel)) { tlist = fpinfo->grouped_tlist; - db2Debug2(" using fpinfo->grouped_tlist"); + db2Debug(2,"using fpinfo->grouped_tlist"); } else { ListCell* lc = NULL; /* We require columns specified in foreignrel->reltarget->exprs and those required for evaluating the local conditions. */ - db2Debug2(" using foreignrel->reltarget->exprs"); + db2Debug(2,"using foreignrel->reltarget->exprs"); tlist = add_to_flat_tlist(tlist, pull_var_clause((Node*) foreignrel->reltarget->exprs, PVC_RECURSE_PLACEHOLDERS)); foreach(lc, fpinfo->local_conds) { RestrictInfo* rinfo = lfirst_node(RestrictInfo, lc); tlist = add_to_flat_tlist(tlist, pull_var_clause((Node*) rinfo->clause, PVC_RECURSE_PLACEHOLDERS)); } } - db2Debug1("< %s::build_tlist_to_deparse : %x",__FILE__, tlist); + db2Exit(1,"< db2_deparse.c::build_tlist_to_deparse : %x", tlist); return tlist; } @@ -1332,7 +1324,7 @@ void deparseSelectStmtForRel(StringInfo buf, PlannerInfo* root, RelOptInfo* rel, DB2FdwState* fpinfo = (DB2FdwState*)rel->fdw_private; List* quals = NIL; - db2Debug1("> %s::deparseSelectStmtForRel",__FILE__); + db2Entry(1,"> db2_deparse.c::deparseSelectStmtForRel"); //We handle relations for foreign tables, joins between those and upper relations. Assert(IS_JOIN_REL(rel) || IS_SIMPLE_REL(rel) || IS_UPPER_REL(rel)); @@ -1380,8 +1372,7 @@ void deparseSelectStmtForRel(StringInfo buf, PlannerInfo* root, RelOptInfo* rel, // Add any necessary FOR UPDATE/SHARE. deparseLockingClause(&context); - db2Debug2(" select stmt: %s", buf->data); - db2Debug1("< %s::deparseSelectStmtForRel",__FILE__); + db2Exit(1,"< db2_deparse.c::deparseSelectStmtForRel: %s", buf->data); } /* @@ -1403,7 +1394,7 @@ static void deparseSelectSql(List *tlist, bool is_subquery, List **retrieved_att PlannerInfo* root = context->root; DB2FdwState* fpinfo = (DB2FdwState*) foreignrel->fdw_private; - db2Debug4("> %s::deparseSelectSql",__FILE__); + db2Entry(1,"> db2_deparse.c::deparseSelectSql"); // Construct SELECT list appendStringInfoString(buf, "SELECT "); @@ -1425,8 +1416,7 @@ static void deparseSelectSql(List *tlist, bool is_subquery, List **retrieved_att deparseTargetList(buf, rte, foreignrel->relid, rel, false, fpinfo->attrs_used, false, retrieved_attrs); table_close(rel, NoLock); } - db2Debug4(" select: %s", buf->data); - db2Debug4("< %s::deparseSelectSql",__FILE__); + db2Exit(1,"< db2_deparse.c::deparseSelectSql : %s", buf->data); } /* @@ -1441,7 +1431,7 @@ static void deparseFromExpr(List *quals, deparse_expr_cxt *context) { RelOptInfo* scanrel = context->scanrel; List* additional_conds = NIL; - db2Debug4("> %s::deparseFromExpr",__FILE__); + db2Entry(1,"> db2_deparse.c::deparseFromExpr"); /* For upper relations, scanrel must be either a joinrel or a baserel */ Assert(!IS_UPPER_REL(context->foreignrel) || IS_JOIN_REL(scanrel) || IS_SIMPLE_REL(scanrel)); @@ -1451,8 +1441,7 @@ static void deparseFromExpr(List *quals, deparse_expr_cxt *context) { appendWhereClause(quals, additional_conds, context); if (additional_conds != NIL) list_free_deep(additional_conds); - db2Debug5(" from: %s",buf->data); - db2Debug4("< %s::deparseFromExpr",__FILE__); + db2Exit(1,"< db2_deparse.c::deparseFromExpr : %s",buf->data); } /* @@ -1474,7 +1463,7 @@ static void deparseFromExpr(List *quals, deparse_expr_cxt *context) { static void deparseFromExprForRel(StringInfo buf, PlannerInfo* root, RelOptInfo* foreignrel, bool use_alias, Index ignore_rel, List** ignore_conds, List** additional_conds, List** params_list) { DB2FdwState* fpinfo = (DB2FdwState*) foreignrel->fdw_private; - db2Debug4("> %s::deparseFromExprForRel",__FILE__); + db2Entry(1,"> db2_deparse.c::deparseFromExprForRel"); if (IS_JOIN_REL(foreignrel)) { StringInfoData join_sql_o; StringInfoData join_sql_i; @@ -1520,7 +1509,7 @@ static void deparseFromExprForRel(StringInfo buf, PlannerInfo* root, RelOptInfo* Assert(*additional_conds == NIL); *additional_conds = additional_conds_o; } - db2Debug4("< %s::deparseFromExprForRel",__FILE__); + db2Exit(1,"< db2_deparse.c::deparseFromExprForRel"); return; } } @@ -1566,17 +1555,17 @@ static void deparseFromExprForRel(StringInfo buf, PlannerInfo* root, RelOptInfo* Assert(*additional_conds == NIL); *additional_conds = additional_conds_i; } - db2Debug4("< %s::deparseFromExprForRel",__FILE__); + db2Exit(1,"< db2_deparse.c::deparseFromExprForRel"); return; } } /* Neither of the relations is the target relation. */ Assert(!outerrel_is_target && !innerrel_is_target); /* - * For semijoin FROM clause is deparsed as an outer relation. An inner - * relation and join clauses are converted to EXISTS condition and - * passed to the upper level. - */ + * For semijoin FROM clause is deparsed as an outer relation. An inner + * relation and join clauses are converted to EXISTS condition and + * passed to the upper level. + */ if (fpinfo->jointype == JOIN_SEMI) { appendBinaryStringInfo(buf, join_sql_o.data, join_sql_o.len); } else { @@ -1619,7 +1608,7 @@ static void deparseFromExprForRel(StringInfo buf, PlannerInfo* root, RelOptInfo* appendStringInfo(buf, " %s%d", REL_ALIAS_PREFIX, foreignrel->relid); table_close(rel, NoLock); } - db2Debug4("< %s::deparseFromExprForRel",__FILE__); + db2Exit(1,"< db2_deparse.c::deparseFromExprForRel"); } /* Append FROM clause entry for the given relation into buf. @@ -1628,7 +1617,7 @@ static void deparseFromExprForRel(StringInfo buf, PlannerInfo* root, RelOptInfo* static void deparseRangeTblRef(StringInfo buf, PlannerInfo *root, RelOptInfo *foreignrel, bool make_subquery, Index ignore_rel, List **ignore_conds, List **additional_conds, List **params_list) { DB2FdwState* fpinfo = (DB2FdwState*) foreignrel->fdw_private; - db2Debug4("> %s::deparseRangeTblRef",__FILE__); + db2Entry(1,"> db2_deparse.c::deparseRangeTblRef"); /* Should only be called in these cases. */ Assert(IS_SIMPLE_REL(foreignrel) || IS_JOIN_REL(foreignrel)); Assert(fpinfo->local_conds == NIL); @@ -1669,8 +1658,7 @@ static void deparseRangeTblRef(StringInfo buf, PlannerInfo *root, RelOptInfo *fo } else { deparseFromExprForRel(buf, root, foreignrel, true, ignore_rel, ignore_conds, additional_conds, params_list); } - db2Debug4(" rangeTblRef: %s", buf->data); - db2Debug4("< %s::deparseRangeTblRef",__FILE__); + db2Exit(1,"< db2_deparse.c::deparseRangeTblRef : %s", buf->data); } /** deparseWhereConditions @@ -1686,7 +1674,7 @@ char* deparseWhereConditions (PlannerInfo* root, RelOptInfo * rel) { char* keyword = "WHERE"; StringInfoData where_clause; - db2Debug1("> deparseWhereCondition"); + db2Entry(1,"> deparseWhereCondition"); initStringInfo (&where_clause); foreach (cell, conditions) { /* check if the condition can be pushed down */ @@ -1702,7 +1690,7 @@ char* deparseWhereConditions (PlannerInfo* root, RelOptInfo * rel) { fdwState->local_conds = lappend (fdwState->local_conds, ((RestrictInfo*) lfirst (cell))->clause); } } - db2Debug1("< deparseWhereCondition : %s",where_clause.data); + db2Exit(1,"< deparseWhereCondition : %s",where_clause.data); return where_clause.data; } @@ -1710,7 +1698,7 @@ char* deparseWhereConditions (PlannerInfo* root, RelOptInfo * rel) { void deparseTruncateSql(StringInfo buf, List* rels, DropBehavior behavior, bool restart_seqs) { ListCell* cell = NULL; - db2Debug1("> %s::deparseTruncateSql",__FILE__); + db2Entry(1,"> db2_deparse.c::deparseTruncateSql"); appendStringInfoString(buf, "TRUNCATE "); foreach(cell, rels) { Relation rel = lfirst(cell); @@ -1724,8 +1712,7 @@ void deparseTruncateSql(StringInfo buf, List* rels, DropBehavior behavior, bool appendStringInfoString(buf, " RESTRICT"); else if (behavior == DROP_CASCADE) appendStringInfoString(buf, " CASCADE"); - db2Debug2(" truncateSql : %s",buf->data); - db2Debug1("> %s::deparseTruncateSql",__FILE__); + db2Entry(1,"> db2_deparse.c::deparseTruncateSql : %s",buf->data); } /* Construct name to use for given column, and emit it into buf. @@ -1734,7 +1721,7 @@ void deparseTruncateSql(StringInfo buf, List* rels, DropBehavior behavior, bool * If qualify_col is true, qualify column name with the alias of relation. */ static void deparseColumnRef(StringInfo buf, int varno, int varattno, RangeTblEntry *rte, bool qualify_col) { - db2Debug1("> %s::deparseColumnRef",__FILE__); + db2Entry(1,"> db2_deparse.c::deparseColumnRef"); /* We support fetching the remote side's CTID and OID. */ if (varattno == SelfItemPointerAttributeNumber) { if (qualify_col) @@ -1818,7 +1805,7 @@ static void deparseColumnRef(StringInfo buf, int varno, int varattno, RangeTblEn appendStringInfoString(buf, quote_identifier(str_toupper (colname, strlen (colname), DEFAULT_COLLATION_OID))); } - db2Debug1("< %s::deparseColumnRef : %s",__FILE__, buf->data); + db2Exit(1,"< db2_deparse.c::deparseColumnRef : %s", buf->data); } /* Append remote name of specified foreign table to buf. @@ -1831,7 +1818,7 @@ static void deparseRelation(StringInfo buf, Relation rel) { const char* relname = NULL; ListCell* lc = NULL; - db2Debug4("> %s::deparseRelation",__FILE__); + db2Entry(1,"> db2_deparse.c::deparseRelation"); /* obtain additional catalog information. */ table = GetForeignTable(RelationGetRelid(rel)); @@ -1854,15 +1841,15 @@ static void deparseRelation(StringInfo buf, Relation rel) { relname = RelationGetRelationName(rel); appendStringInfo(buf, "%s.%s", quote_identifier(nspname), quote_identifier(relname)); - db2Debug5(" relation: %s",buf->data); - db2Debug4("< %s::deparseRelation",__FILE__); + db2Debug(5,"relation: %s",buf->data); + db2Exit(1,"< db2_deparse.c::deparseRelation"); } /* Append a SQL string literal representing "val" to buf. */ void deparseStringLiteral(StringInfo buf, const char* val) { const char* valptr = NULL; - db2Debug4("> %s::deparseStringLiteral",__FILE__); + db2Entry(1,"> db2_deparse.c::deparseStringLiteral"); /* Rather than making assumptions about the remote server's value of standard_conforming_strings, always use E'foo' syntax if there are any * backslashes. * This will fail on remote servers before 8.1, but those are long out of support. @@ -1880,8 +1867,8 @@ void deparseStringLiteral(StringInfo buf, const char* val) { appendStringInfoChar(buf, ch); } appendStringInfoChar(buf, '\''); - db2Debug5(" literal: %s",buf->data); - db2Debug4("< %s::deparseStringLiteral",__FILE__); + db2Debug(5,"literal: %s",buf->data); + db2Exit(1,"< db2_deparse.c::deparseStringLiteral"); } /** deparseExpr @@ -1892,7 +1879,7 @@ void deparseStringLiteral(StringInfo buf, const char* val) { */ char* deparseExpr (PlannerInfo* root, RelOptInfo* rel, Expr* expr, List** params) { char* retValue = NULL; - db2Debug1("> %s::deparseExpr", __FILE__); + db2Entry(1,"> db2_deparse.c::deparseExpr"); if (expr != NULL) { deparse_expr_cxt* ctx = db2alloc("deparseExpr.context", sizeof(deparse_expr_cxt)); StringInfoData buf; @@ -1909,15 +1896,15 @@ char* deparseExpr (PlannerInfo* root, RelOptInfo* rel, Expr* expr, List** params db2free(ctx->buf->data); db2free(ctx); } - db2Debug1("< %s::deparseExpr: %s", __FILE__, retValue); + db2Exit(1,"< db2_deparse.c::deparseExpr: %s", retValue); return retValue; } static void deparseExprInt (Expr* expr, deparse_expr_cxt* ctx) { - db2Debug1("> %s::deparseExprInt", __FILE__); - db2Debug2(" expr: %x",expr); + db2Entry(1,"> db2_deparse.c::deparseExprInt"); + db2Debug(2,"expr: %x",expr); if (expr != NULL) { - db2Debug2(" expr->type: %d",expr->type); + db2Debug(2,"expr->type: %d",expr->type); switch (expr->type) { case T_Const: { deparseConstExpr((Const*)expr, ctx); @@ -1990,16 +1977,16 @@ static void deparseExprInt (Expr* expr, deparse_expr_cxt* break; default: { /* we cannot translate this to DB2 */ - db2Debug2(" expression cannot be translated to DB2", __FILE__); + db2Debug(2,"expression cannot be translated to DB2"); } break; } } - db2Debug1("< %s::deparseExpr : %s", __FILE__, ctx->buf->data); + db2Exit(1,"< db2_deparse.c::deparseExpr : %s", ctx->buf->data); } static void deparseConstExpr (Const* expr, deparse_expr_cxt* ctx) { - db2Debug1("> %s::deparseConstExpr", __FILE__); + db2Entry(1,"> db2_deparse.c::deparseConstExpr"); if (expr->constisnull) { /* only translate NULLs of a type DB2 can handle */ if (canHandleType (expr->consttype)) { @@ -2012,17 +1999,17 @@ static void deparseConstExpr (Const* expr, deparse_expr_cxt* appendStringInfo (ctx->buf, "%s", c); } } - db2Debug1("< %s::deparseConstExpr", __FILE__); + db2Exit(1,"< db2_deparse.c::deparseConstExpr"); } static void deparseParamExpr (Param* expr, deparse_expr_cxt* ctx) { ListCell* cell = NULL; char parname[10]; - db2Debug1("> %s::deparseParamExpr", __FILE__); + db2Entry(1,"> db2_deparse.c::deparseParamExpr"); /* don't try to handle interval parameters */ if (!canHandleType (expr->paramtype) || expr->paramtype == INTERVALOID) { - db2Debug2(" !canHhandleType(expr->paramtype %d) || rxpr->paramtype == INTERVALOID)", expr->paramtype); + db2Debug(2,"!canHhandleType(expr->paramtype %d) || rxpr->paramtype == INTERVALOID)", expr->paramtype); } else { /* find the index in the parameter list */ int index = 0; @@ -2040,13 +2027,13 @@ static void deparseParamExpr (Param* expr, deparse_expr_cxt* snprintf (parname, 10, ":p%d", index); appendAsType (ctx->buf, expr->paramtype); } - db2Debug1("< %s::deparseParamExpr", __FILE__); + db2Exit(1,"< db2_deparse.c::deparseParamExpr"); } //static void deparseVarExpr (Var* expr, deparse_expr_cxt* ctx) { // const DB2Table* var_table = NULL; /* db2Table that belongs to a Var */ // -// db2Debug1("> %s::deparseVarExpr", __FILE__); +// db2Entry(1,"> db2_deparse.c::deparseVarExpr"); // /* check if the variable belongs to one of our foreign tables */ // if (IS_SIMPLE_REL (ctx->foreignrel)) { // if (expr->varno == ctx->foreignrel->relid && expr->varlevelsup == 0) @@ -2064,13 +2051,13 @@ static void deparseParamExpr (Param* expr, deparse_expr_cxt* // if (var_table) { // /* the variable belongs to a foreign table, replace it with the name */ // /* we cannot handle system columns */ -// db2Debug2(" varattno: %d",expr->varattno); +// db2Debug(2,"varattno: %d",expr->varattno); // if (expr->varattno > 0) { // /** Allow boolean columns here. // * They will be rendered as ("COL" <> 0). // */ // if (!(canHandleType (expr->vartype) || expr->vartype == BOOLOID)) { -// db2Debug2(" !(canHandleType (vartype %d) || vartype == BOOLOID",expr->vartype); +// db2Debug(2,"!(canHandleType (vartype %d) || vartype == BOOLOID",expr->vartype); // } else { // /* get var_table column index corresponding to this column (-1 if none) */ // int index = var_table->ncols - 1; @@ -2086,9 +2073,9 @@ static void deparseParamExpr (Param* expr, deparse_expr_cxt* // * in PostgreSQL because functions and operators won't work the same. // */ // short db2type = c2dbType(var_table->cols[index]->colType); -// db2Debug2(" db2type: %d", db2type); +// db2Debug(2,"db2type: %d", db2type); // if ((expr->vartype == TEXTOID || expr->vartype == BPCHAROID || expr->vartype == VARCHAROID) && db2type != DB2_VARCHAR && db2type != DB2_CHAR) { -// db2Debug2(" vartype: %d", expr->vartype); +// db2Debug(2,"vartype: %d", expr->vartype); // } else { // /* work around the lack of booleans in DB2 */ // if (expr->vartype == BOOLOID) { @@ -2105,7 +2092,7 @@ static void deparseParamExpr (Param* expr, deparse_expr_cxt* // } // } // } -// db2Debug1("< %s::deparseVarExpr", __FILE__); +// db2Exit(1,"< db2_deparse.c::deparseVarExpr"); //} /* Deparse given Var node into context->buf. @@ -2122,7 +2109,7 @@ static void deparseVar(Var* expr, deparse_expr_cxt* ctx) { bool qualify_col = (bms_membership(relids) == BMS_MULTIPLE); bool is_query_var = false; - db2Debug1("> %s::deparseVar", __FILE__); + db2Entry(1,"> db2_deparse.c::deparseVar"); /* If the Var belongs to the foreign relation that is deparsed as a subquery, use the relation and column alias to the Var provided by the * subquery, instead of the remote name. */ @@ -2131,8 +2118,8 @@ static void deparseVar(Var* expr, deparse_expr_cxt* ctx) { return; } is_query_var = bms_is_member(expr->varno, ctx->root->all_query_rels); - db2Debug2(" bms_is_member(%d,%d): %s",expr->varno, ctx->root->all_query_rels, is_query_var ? "true":"false"); - db2Debug2(" expr->varlevelsup: %d",expr->varlevelsup); + db2Debug(2,"bms_is_member(%d,%d): %s",expr->varno, ctx->root->all_query_rels, is_query_var ? "true":"false"); + db2Debug(2,"expr->varlevelsup: %d",expr->varlevelsup); if (is_query_var && expr->varlevelsup == 0) { deparseColumnRef(ctx->buf, expr->varno, expr->varattno, planner_rt_fetch(expr->varno, ctx->root), qualify_col); } else { @@ -2157,7 +2144,7 @@ static void deparseVar(Var* expr, deparse_expr_cxt* ctx) { printRemotePlaceholder(expr->vartype, expr->vartypmod, ctx); } } - db2Debug1("< %s::deparseVar : %s", __FILE__,ctx->buf->data); + db2Exit(1,"< db2_deparse.c::deparseVar : %s",ctx->buf->data); } static void deparseOpExpr (OpExpr* expr, deparse_expr_cxt* ctx) { @@ -2168,7 +2155,7 @@ static void deparseOpExpr (OpExpr* expr, deparse_expr_cxt* Oid schema = 0; HeapTuple tuple ; - db2Debug1("> %s::deparseOpExpr", __FILE__); + db2Entry(1,"> db2_deparse.c::deparseOpExpr"); /* get operator name, kind, argument type and schema */ tuple = SearchSysCache1 (OPEROID, ObjectIdGetDatum (expr->opno)); if (!HeapTupleIsValid (tuple)) { @@ -2182,16 +2169,16 @@ static void deparseOpExpr (OpExpr* expr, deparse_expr_cxt* ReleaseSysCache (tuple); /* ignore operators in other than the pg_catalog schema */ if (schema != PG_CATALOG_NAMESPACE) { - db2Debug2(" schema != PG_CATALOG_NAMESPACE"); + db2Debug(2,"schema != PG_CATALOG_NAMESPACE"); } else { if (!canHandleType (rightargtype)) { - db2Debug2(" !canHandleType rightargtype(%d)", rightargtype); + db2Debug(2,"!canHandleType rightargtype(%d)", rightargtype); } else { /** Don't translate operations on two intervals. * INTERVAL YEAR TO MONTH and INTERVAL DAY TO SECOND don't mix well. */ if (leftargtype == INTERVALOID && rightargtype == INTERVALOID) { - db2Debug2(" leftargtype == INTERVALOID && rightargtype == INTERVALOID"); + db2Debug(2,"leftargtype == INTERVALOID && rightargtype == INTERVALOID"); } else { /* the operators that we can translate */ if ((strcmp (opername, ">") == 0 && rightargtype != TEXTOID && rightargtype != BPCHAROID && rightargtype != NAMEOID && rightargtype != CHAROID) @@ -2206,14 +2193,14 @@ static void deparseOpExpr (OpExpr* expr, deparse_expr_cxt* char* left = NULL; left = deparseExpr (ctx->root, ctx->foreignrel, linitial(expr->args), ctx->params_list); - db2Debug2(" left: %s", left); + db2Debug(2,"left: %s", left); if (left != NULL) { if (oprkind == 'b') { /* binary operator */ char* right = NULL; right = deparseExpr (ctx->root, ctx->foreignrel, lsecond(expr->args), ctx->params_list); - db2Debug2(" right: %s", right); + db2Debug(2,"right: %s", right); if (right != NULL) { if (strcmp (opername, "~~") == 0) { appendStringInfo (ctx->buf, "(%s LIKE %s ESCAPE '\\')", left, right); @@ -2250,13 +2237,13 @@ static void deparseOpExpr (OpExpr* expr, deparse_expr_cxt* } } else { /* cannot translate this operator */ - db2Debug2(" cannot translate this opername: %s", opername); + db2Debug(2,"cannot translate this opername: %s", opername); } } } } db2free (opername); - db2Debug1("< %s::deparseOpExpr", __FILE__); + db2Exit(1,"< db2_deparse.c::deparseOpExpr"); } static void deparseScalarArrayOpExpr (ScalarArrayOpExpr* expr, deparse_expr_cxt* ctx) { @@ -2265,7 +2252,7 @@ static void deparseScalarArrayOpExpr (ScalarArrayOpExpr* expr, deparse_expr_cxt* Oid schema; HeapTuple tuple; - db2Debug1("> %s::deparseScalarArrayOpExpr", __FILE__); + db2Entry(1,"> db2_deparse.c::deparseScalarArrayOpExpr"); tuple = SearchSysCache1 (OPEROID, ObjectIdGetDatum (expr->opno)); if (!HeapTupleIsValid (tuple)) { elog (ERROR, "cache lookup failed for operator %u", expr->opno); @@ -2282,14 +2269,14 @@ static void deparseScalarArrayOpExpr (ScalarArrayOpExpr* expr, deparse_expr_cxt* ReleaseSysCache (tuple); /* ignore operators in other than the pg_catalog schema */ if (schema != PG_CATALOG_NAMESPACE) { - db2Debug2(" schema != PG_CATALOG_NAMESPACE"); + db2Debug(2,"schema != PG_CATALOG_NAMESPACE"); } else { /* don't try to push down anything but IN and NOT IN expressions */ if ((strcmp (opername, "=") != 0 || !expr->useOr) && (strcmp (opername, "<>") != 0 || expr->useOr)) { - db2Debug2(" don't try to push down anything but IN and NOT IN expressions"); + db2Debug(2,"don't try to push down anything but IN and NOT IN expressions"); } else { if (!canHandleType (leftargtype)) { - db2Debug2(" cannot Handle Type leftargtype (%d)", leftargtype); + db2Debug(2,"cannot Handle Type leftargtype (%d)", leftargtype); } else { char* left = NULL; char* right = NULL; @@ -2325,7 +2312,7 @@ static void deparseScalarArrayOpExpr (ScalarArrayOpExpr* expr, deparse_expr_cxt* c = "NULL"; } else { c = datumToString (datum, leftargtype); - db2Debug2(" c: %s",c); + db2Debug(2,"c: %s",c); if (c == NULL) { array_free_iterator (iterator); bResult = false; @@ -2337,7 +2324,7 @@ static void deparseScalarArrayOpExpr (ScalarArrayOpExpr* expr, deparse_expr_cxt* first_arg = false; } array_free_iterator (iterator); - db2Debug2(" first_arg: %s", first_arg ? "true":"false"); + db2Debug(2,"first_arg: %s", first_arg ? "true":"false"); if (first_arg) { // don't push down empty arrays // since the semantics for NOT x = ANY() differ @@ -2355,7 +2342,7 @@ static void deparseScalarArrayOpExpr (ScalarArrayOpExpr* expr, deparse_expr_cxt* ArrayCoerceExpr* arraycoerce = (ArrayCoerceExpr *) rightexpr; /* if the conversion requires more than binary coercion, don't push it down */ if (arraycoerce->elemexpr && arraycoerce->elemexpr->type != T_RelabelType) { - db2Debug2(" arraycoerce->elemexpr && arraycoerce->elemexpr->type != T_RelabelType"); + db2Debug(2,"arraycoerce->elemexpr && arraycoerce->elemexpr->type != T_RelabelType"); bResult = false; break; } @@ -2384,7 +2371,7 @@ static void deparseScalarArrayOpExpr (ScalarArrayOpExpr* expr, deparse_expr_cxt* appendStringInfo(&buf,"%s%s",(first_arg) ? "": ", ",element); first_arg = false; } - db2Debug2(" first_arg: %s", first_arg ? "true" : "false"); + db2Debug(2,"first_arg: %s", first_arg ? "true" : "false"); if (first_arg) { /* don't push down empty arrays, since the semantics for NOT x = ANY() differ */ db2free(buf.data); @@ -2396,7 +2383,7 @@ static void deparseScalarArrayOpExpr (ScalarArrayOpExpr* expr, deparse_expr_cxt* } break; default: { - db2Debug2(" rightexpr->type(%d) default ",rightexpr->type); + db2Debug(2,"rightexpr->type(%d) default ",rightexpr->type); bResult = false; } break; @@ -2411,14 +2398,14 @@ static void deparseScalarArrayOpExpr (ScalarArrayOpExpr* expr, deparse_expr_cxt* } } } - db2Debug1("< %s::deparseScalarArrayOpExpr", __FILE__); + db2Exit(1,"< db2_deparse.c::deparseScalarArrayOpExpr"); } static void deparseDistinctExpr (DistinctExpr* expr, deparse_expr_cxt* ctx) { Oid rightargtype = 0; HeapTuple tuple; - db2Debug1("> %s::deparseDistinctExpr", __FILE__); + db2Entry(1,"> db2_deparse.c::deparseDistinctExpr"); tuple = SearchSysCache1 (OPEROID, ObjectIdGetDatum ((expr)->opno)); if (!HeapTupleIsValid (tuple)) { elog (ERROR, "cache lookup failed for operator %u", (expr)->opno); @@ -2426,7 +2413,7 @@ static void deparseDistinctExpr (DistinctExpr* expr, deparse_expr_cxt* rightargtype = ((Form_pg_operator) GETSTRUCT (tuple))->oprright; ReleaseSysCache (tuple); if (!canHandleType (rightargtype)) { - db2Debug2(" cannot Handle Type rightargtype (%d)",rightargtype); + db2Debug(2,"cannot Handle Type rightargtype (%d)",rightargtype); } else { char* left = NULL; @@ -2442,7 +2429,7 @@ static void deparseDistinctExpr (DistinctExpr* expr, deparse_expr_cxt* } db2free(left); } - db2Debug1("< %s::deparseDistinctExpr", __FILE__); + db2Exit(1,"< db2_deparse.c::deparseDistinctExpr"); } /** Deparse IS [NOT] NULL expression. @@ -2450,7 +2437,7 @@ static void deparseDistinctExpr (DistinctExpr* expr, deparse_expr_cxt* static void deparseNullTest (NullTest* expr, deparse_expr_cxt* ctx) { StringInfo buf = ctx->buf; - db2Debug1("> %s::deparseNullTest", __FILE__); + db2Entry(1,"> db2_deparse.c::deparseNullTest"); appendStringInfoChar(buf, '('); deparseExprInt (expr->arg, ctx); @@ -2470,14 +2457,14 @@ static void deparseNullTest (NullTest* expr, deparse_expr_cxt* else appendStringInfoString(buf, " IS DISTINCT FROM NULL)"); } - db2Debug1("< %s::deparseNullTest : %s", __FILE__, buf->data); + db2Exit(1,"< db2_deparse.c::deparseNullTest : %s", buf->data); } static void deparseNullIfExpr (NullIfExpr* expr, deparse_expr_cxt* ctx) { Oid rightargtype = 0; HeapTuple tuple; - db2Debug1("> %s::deparseNullIfExpr", __FILE__); + db2Entry(1,"> db2_deparse.c::deparseNullIfExpr"); tuple = SearchSysCache1 (OPEROID, ObjectIdGetDatum ((expr)->opno)); if (!HeapTupleIsValid (tuple)) { elog (ERROR, "cache lookup failed for operator %u", (expr)->opno); @@ -2485,7 +2472,7 @@ static void deparseNullIfExpr (NullIfExpr* expr, deparse_expr_cxt* rightargtype = ((Form_pg_operator) GETSTRUCT (tuple))->oprright; ReleaseSysCache (tuple); if (!canHandleType (rightargtype)) { - db2Debug2(" cannot Handle Type rightargtype (%d)",rightargtype); + db2Debug(2,"cannot Handle Type rightargtype (%d)",rightargtype); } else { char* left = NULL; left = deparseExpr (ctx->root, ctx->foreignrel, linitial((expr)->args), ctx->params_list); @@ -2501,7 +2488,7 @@ static void deparseNullIfExpr (NullIfExpr* expr, deparse_expr_cxt* } db2free(left); } - db2Debug1("< %s::deparseNullIfExpr: %s", __FILE__, ctx->buf->data); + db2Exit(1,"< db2_deparse.c::deparseNullIfExpr: %s", ctx->buf->data); } static void deparseBoolExpr (BoolExpr* expr, deparse_expr_cxt* ctx) { @@ -2509,7 +2496,7 @@ static void deparseBoolExpr (BoolExpr* expr, deparse_expr_cxt* char* arg = NULL; StringInfoData buf; - db2Debug1("> %s::deparseBoolExpr", __FILE__); + db2Entry(1,"> db2_deparse.c::deparseBoolExpr"); initStringInfo(&buf); arg = deparseExpr (ctx->root, ctx->foreignrel, linitial(expr->args), ctx->params_list); if (arg != NULL) { @@ -2531,13 +2518,13 @@ static void deparseBoolExpr (BoolExpr* expr, deparse_expr_cxt* } db2free(buf.data); db2free(arg); - db2Debug1("< %s::deparseBoolExpr: %s", __FILE__, ctx->buf->data); + db2Exit(1,"< db2_deparse.c::deparseBoolExpr: %s", ctx->buf->data); } static void deparseCaseExpr (CaseExpr* expr, deparse_expr_cxt* ctx) { - db2Debug1("> %s::deparseCaseExpr", __FILE__); + db2Entry(1,"> db2_deparse.c::deparseCaseExpr"); if (!canHandleType (expr->casetype)) { - db2Debug2(" cannot Handle Type caseexpr->casetype (%d)", expr->casetype); + db2Debug(2,"cannot Handle Type caseexpr->casetype (%d)", expr->casetype); } else { StringInfoData buf; bool bBreak = false; @@ -2550,7 +2537,7 @@ static void deparseCaseExpr (CaseExpr* expr, deparse_expr_cxt* if (expr->arg != NULL) { /* for the form "CASE arg WHEN ...", add first expression */ arg = deparseExpr (ctx->root, ctx->foreignrel, expr->arg, ctx->params_list); - db2Debug2(" CASE %s WHEN ...", arg); + db2Debug(2,"CASE %s WHEN ...", arg); if (arg == NULL) { appendStringInfo (&buf, " %s", arg); } else { @@ -2569,7 +2556,7 @@ static void deparseCaseExpr (CaseExpr* expr, deparse_expr_cxt* /* for CASE arg WHEN ..., use only the right branch of the equality */ arg = deparseExpr (ctx->root, ctx->foreignrel, lsecond (((OpExpr*) whenclause->expr)->args), ctx->params_list); } - db2Debug2(" WHEN %s ", arg); + db2Debug(2,"WHEN %s ", arg); if (arg != NULL) { appendStringInfo (&buf, " WHEN %s", arg); } else { @@ -2577,7 +2564,7 @@ static void deparseCaseExpr (CaseExpr* expr, deparse_expr_cxt* break; } /* THEN */ arg = deparseExpr (ctx->root, ctx->foreignrel, whenclause->result, ctx->params_list); - db2Debug2(" THEN %s ", arg); + db2Debug(2," THEN %s ", arg); if (arg != NULL) { appendStringInfo (&buf, " THEN %s", arg); } else { @@ -2589,7 +2576,7 @@ static void deparseCaseExpr (CaseExpr* expr, deparse_expr_cxt* /* append ELSE clause if appropriate */ if (expr->defresult != NULL) { arg = deparseExpr (ctx->root, ctx->foreignrel, expr->defresult, ctx->params_list); - db2Debug2(" ELSE %s", arg); + db2Debug(2," ELSE %s", arg); if (arg != NULL) { appendStringInfo (&buf, " ELSE %s", arg); } else { @@ -2605,13 +2592,13 @@ static void deparseCaseExpr (CaseExpr* expr, deparse_expr_cxt* } db2free(buf.data); } - db2Debug1("< %s::deparseCaseExpr: %s", __FILE__, ctx->buf->data); + db2Exit(1,"< db2_deparse.c::deparseCaseExpr: %s", ctx->buf->data); } static void deparseCoalesceExpr (CoalesceExpr* expr, deparse_expr_cxt* ctx) { - db2Debug1("> %s::deparseCoalesceExpr", __FILE__); + db2Entry(1,"> db2_deparse.c::deparseCoalesceExpr"); if (!canHandleType (expr->coalescetype)) { - db2Debug2(" cannot Handle Type coalesceexpr->coalescetype (%d)", expr->coalescetype); + db2Debug(2,"cannot Handle Type coalesceexpr->coalescetype (%d)", expr->coalescetype); } else { StringInfoData result; char* arg = NULL; @@ -2622,7 +2609,7 @@ static void deparseCoalesceExpr (CoalesceExpr* expr, deparse_expr_cxt* appendStringInfo (&result, "COALESCE("); foreach (cell, expr->args) { arg = deparseExpr (ctx->root, ctx->foreignrel, (Expr*)lfirst(cell),ctx->params_list); - db2Debug2(" arg: %s", arg); + db2Debug(2,"arg: %s", arg); if (arg != NULL) { appendStringInfo(&result, ((first_arg) ? "%s" : ", %s"), arg); first_arg = false; @@ -2635,16 +2622,16 @@ static void deparseCoalesceExpr (CoalesceExpr* expr, deparse_expr_cxt* } db2free(result.data); } - db2Debug1("< %s::deparseCoalesceExpr: %s", __FILE__, ctx->buf->data); + db2Exit(1,"< db2_deparse.c::deparseCoalesceExpr: %s", ctx->buf->data); } static void deparseFuncExpr (FuncExpr* expr, deparse_expr_cxt* ctx) { - db2Debug1("> %s::deparseFuncExpr", __FILE__); + db2Entry(1,"> db2_deparse.c::deparseFuncExpr"); if (!canHandleType (expr->funcresulttype)) { - db2Debug2(" cannot handle funct->funcresulttype: %d",expr->funcresulttype); + db2Debug(2,"cannot handle funct->funcresulttype: %d",expr->funcresulttype); } else if (expr->funcformat == COERCE_IMPLICIT_CAST) { /* do nothing for implicit casts */ - db2Debug2(" COERCE_IMPLICIT_CAST == expr->funcformat(%d)",expr->funcformat); + db2Debug(2,"COERCE_IMPLICIT_CAST == expr->funcformat(%d)",expr->funcformat); deparseExprInt (linitial(expr->args), ctx); } else { Oid schema; @@ -2657,13 +2644,13 @@ static void deparseFuncExpr (FuncExpr* expr, deparse_expr_cxt* elog (ERROR, "cache lookup failed for function %u", expr->funcid); } opername = db2strdup (((Form_pg_proc) GETSTRUCT (tuple))->proname.data); - db2Debug2(" opername: %s",opername); + db2Debug(2,"opername: %s",opername); schema = ((Form_pg_proc) GETSTRUCT (tuple))->pronamespace; - db2Debug2(" schema: %d",schema); + db2Debug(2,"schema: %d",schema); ReleaseSysCache (tuple); /* ignore functions in other than the pg_catalog schema */ if (schema != PG_CATALOG_NAMESPACE) { - db2Debug2(" T_FuncExpr: schema(%d) != PG_CATALOG_NAMESPACE", schema); + db2Debug(2,"T_FuncExpr: schema(%d) != PG_CATALOG_NAMESPACE", schema); } else { /* the "normal" functions that we can translate */ if (strcmp (opername, "abs") == 0 || strcmp (opername, "acos") == 0 || strcmp (opername, "asin") == 0 @@ -2709,7 +2696,7 @@ static void deparseFuncExpr (FuncExpr* expr, deparse_expr_cxt* db2free(arg); } else { ok = false; - db2Debug2(" T_FuncExpr: function %s that we cannot render for DB2", opername); + db2Debug(2,"T_FuncExpr: function %s that we cannot render for DB2", opername); break; } } @@ -2725,7 +2712,7 @@ static void deparseFuncExpr (FuncExpr* expr, deparse_expr_cxt* /* special case: EXTRACT */ left = deparseExpr (ctx->root, ctx->foreignrel, linitial (expr->args), ctx->params_list); if (left == NULL) { - db2Debug2(" T_FuncExpr: function %s that we cannot render for DB2", opername); + db2Debug(2,"T_FuncExpr: function %s that we cannot render for DB2", opername); } else { /* can only handle these fields in DB2 */ if (strcmp (left, "'year'") == 0 || strcmp (left, "'month'") == 0 @@ -2738,13 +2725,13 @@ static void deparseFuncExpr (FuncExpr* expr, deparse_expr_cxt* left[strlen (left) - 1] = '\0'; right = deparseExpr (ctx->root, ctx->foreignrel, lsecond (expr->args), ctx->params_list); if (right == NULL) { - db2Debug2(" T_FuncExpr: function %s that we cannot render for DB2", opername); + db2Debug(2,"T_FuncExpr: function %s that we cannot render for DB2", opername); } else { appendStringInfo (ctx->buf, "EXTRACT(%s FROM %s)", left + 1, right); } db2free(right); } else { - db2Debug2(" T_FuncExpr: function %s that we cannot render for DB2", opername); + db2Debug(2,"T_FuncExpr: function %s that we cannot render for DB2", opername); } } db2free (left); @@ -2753,28 +2740,28 @@ static void deparseFuncExpr (FuncExpr* expr, deparse_expr_cxt* appendStringInfo (ctx->buf, "(CAST (?/*:now*/ AS TIMESTAMP))"); } else { /* function that we cannot render for DB2 */ - db2Debug2(" T_FuncExpr: function %s that we cannot render for DB2", opername); + db2Debug(2,"T_FuncExpr: function %s that we cannot render for DB2", opername); } } db2free (opername); } - db2Debug1("< %s::deparseFuncExpr: %s", __FILE__, ctx->buf->data); + db2Exit(1,"< db2_deparse.c::deparseFuncExpr: %s", ctx->buf->data); } static void deparseCoerceViaIOExpr (CoerceViaIO* expr, deparse_expr_cxt* ctx) { - db2Debug1("> %s::deparseCoerceViaIOExpr", __FILE__); + db2Entry(1,"> db2_deparse.c::deparseCoerceViaIOExpr"); /* We will only handle casts of 'now'. */ /* only casts to these types are handled */ if (expr->resulttype != DATEOID && expr->resulttype != TIMESTAMPOID && expr->resulttype != TIMESTAMPTZOID) { - db2Debug2(" only casts to DATEOID, TIMESTAMPOID and TIMESTAMPTZOID are handled"); + db2Debug(2,"only casts to DATEOID, TIMESTAMPOID and TIMESTAMPTZOID are handled"); } else if (expr->arg->type != T_Const) { /* the argument must be a Const */ - db2Debug2(" T_CoerceViaIO: the argument must be a Const"); + db2Debug(2,"T_CoerceViaIO: the argument must be a Const"); } else { Const* constant = (Const *) expr->arg; if (constant->constisnull || (constant->consttype != CSTRINGOID && constant->consttype != TEXTOID)) { /* the argument must be a not-NULL text constant */ - db2Debug2(" T_CoerceViaIO: the argument must be a not-NULL text constant"); + db2Debug(2,"T_CoerceViaIO: the argument must be a not-NULL text constant"); } else { /* get the type's output function */ HeapTuple tuple = SearchSysCache1 (TYPEOID, ObjectIdGetDatum (constant->consttype)); @@ -2786,7 +2773,7 @@ static void deparseCoerceViaIOExpr (CoerceViaIO* expr, deparse_expr_cxt* ReleaseSysCache (tuple); /* the value must be "now" */ if (strcmp (DatumGetCString (OidFunctionCall1 (typoutput, constant->constvalue)), "now") != 0) { - db2Debug2(" value must be 'now'"); + db2Debug(2,"value must be 'now'"); } else { switch (expr->resulttype) { case DATEOID: @@ -2808,11 +2795,11 @@ static void deparseCoerceViaIOExpr (CoerceViaIO* expr, deparse_expr_cxt* } } } - db2Debug1("< %s::deparseCoerceViaIOExpr: %s", __FILE__, ctx->buf->data); + db2Exit(1,"< db2_deparse.c::deparseCoerceViaIOExpr: %s", ctx->buf->data); } static void deparseSQLValueFuncExpr (SQLValueFunction* expr, deparse_expr_cxt* ctx) { - db2Debug1("> %s::deparseSQLValueFuncExpr", __FILE__); + db2Entry(1,"> db2_deparse.c::deparseSQLValueFuncExpr"); switch (expr->op) { case SVFOP_CURRENT_DATE: appendStringInfo(ctx->buf, "TRUNC(CAST (CAST(?/*:now*/ AS TIMESTAMP) AS DATE))"); @@ -2831,16 +2818,16 @@ static void deparseSQLValueFuncExpr (SQLValueFunction* expr, deparse_expr_cxt* break; default: /* don't push down other functions */ - db2Debug2(" op %d cannot be translated to DB2", expr->op); + db2Debug(2,"op %d cannot be translated to DB2", expr->op); break; } - db2Debug1("< %s::deparseSQLValueFuncExpr: %s", __FILE__, ctx->buf->data); + db2Exit(1,"< db2_deparse.c::deparseSQLValueFuncExpr: %s", ctx->buf->data); } static void deparseAggref (Aggref* expr, deparse_expr_cxt* ctx) { - db2Debug1("> %s::deparseAggref", __FILE__); + db2Entry(1,"> db2_deparse.c::deparseAggref"); if (expr == NULL) { - db2Debug2(" expr is NULL"); + db2Debug(2,"expr is NULL"); } else { /* Resolve aggregate function name (OID -> pg_proc.proname). */ HeapTuple tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(expr->aggfnoid)); @@ -2862,12 +2849,12 @@ static void deparseAggref (Aggref* expr, deparse_expr_cxt* } ReleaseSysCache(tuple); } - db2Debug2( " aggref->aggfnoid=%u name=%s%s%s", expr->aggfnoid, nspname ? nspname : "", nspname ? "." : "", aggname ? aggname : ""); + db2Debug(2,"aggref->aggfnoid=%u name=%s%s%s", expr->aggfnoid, nspname ? nspname : "", nspname ? "." : "", aggname ? aggname : ""); /* We only support deparsing simple, standard aggregates for now. * (This can be expanded to ordered-set / FILTER / WITHIN GROUP later.) */ if (expr->aggorder != NIL) { - db2Debug2(" aggregate ORDER BY not supported for pushdown"); + db2Debug(2,"aggregate ORDER BY not supported for pushdown"); } else if (aggname != NULL) { const char* db2func = NULL; bool distinct = (expr->aggdistinct != NIL); @@ -2880,7 +2867,7 @@ static void deparseAggref (Aggref* expr, deparse_expr_cxt* else if (strcmp(aggname, "max") == 0) db2func = "MAX"; else { /* Unknown aggregate name: we can still report it (above), but don't emit SQL. */ - db2Debug2(" aggregate '%s' not supported for DB2 deparse", aggname); + db2Debug(2,"aggregate '%s' not supported for DB2 deparse", aggname); } if (db2func != NULL) { StringInfoData result; @@ -2932,14 +2919,14 @@ static void deparseAggref (Aggref* expr, deparse_expr_cxt* if (ok) { appendStringInfo(ctx->buf, "%s)",result.data); } else { - db2Debug2(" parsed aggref so far: %s", result.data); - db2Debug2(" could not deparse aggregate args"); + db2Debug(2,"parsed aggref so far: %s", result.data); + db2Debug(2,"could not deparse aggregate args"); } db2free(result.data); } } } - db2Debug1("< %s::deparseAggref: %s", __FILE__, ctx->buf->data); + db2Exit(1,"< db2_deparse.c::deparseAggref: %s", ctx->buf->data); } /** datumToString @@ -2952,7 +2939,7 @@ static char* datumToString (Datum datum, Oid type) { HeapTuple tuple; char* str; char* p; - db2Debug1("> %s::datumToString", __FILE__); + db2Entry(1,"> db2_deparse.c::datumToString"); /* get the type's output function */ tuple = SearchSysCache1 (TYPEOID, ObjectIdGetDatum (type)); if (!HeapTupleIsValid (tuple)) { @@ -3032,7 +3019,7 @@ static char* datumToString (Datum datum, Oid type) { default: return NULL; } - db2Debug1("< %s::datumToString - returns: '%s'", __FILE__, result.data); + db2Exit(1,"< db2_deparse.c::datumToString - returns: '%s'", result.data); return result.data; } @@ -3042,7 +3029,7 @@ static char* datumToString (Datum datum, Oid type) { char* deparseDate (Datum datum) { struct pg_tm datetime_tm; StringInfoData s; - db2Debug1("> %s::deparseDate", __FILE__); + db2Entry(1,"> db2_deparse.c::deparseDate"); if (DATE_NOT_FINITE (DatumGetDateADT (datum))) ereport (ERROR, (errcode (ERRCODE_FDW_INVALID_ATTRIBUTE_VALUE), errmsg ("infinite date value cannot be stored in DB2"))); @@ -3054,7 +3041,7 @@ char* deparseDate (Datum datum) { initStringInfo (&s); appendStringInfo (&s, "%04d-%02d-%02d 00:00:00", datetime_tm.tm_year > 0 ? datetime_tm.tm_year : -datetime_tm.tm_year + 1, datetime_tm.tm_mon, datetime_tm.tm_mday); - db2Debug1("< %s::deparseDate - returns: '%s'", __FILE__, s.data); + db2Exit(1,"< db2_deparse.c::deparseDate - returns: '%s'", s.data); return s.data; } @@ -3066,7 +3053,7 @@ char* deparseTimestamp (Datum datum, bool hasTimezone) { int32 tzoffset; fsec_t datetime_fsec; StringInfoData s; - db2Debug1("> %s::deparseTimestamp",__FILE__); + db2Entry(1,"> db2_deparse.c::deparseTimestamp"); /* this is sloppy, but DatumGetTimestampTz and DatumGetTimestamp are the same */ if (TIMESTAMP_NOT_FINITE (DatumGetTimestampTz (datum))) ereport (ERROR, (errcode (ERRCODE_FDW_INVALID_ATTRIBUTE_VALUE), errmsg ("infinite timestamp value cannot be stored in DB2"))); @@ -3090,22 +3077,18 @@ char* deparseTimestamp (Datum datum, bool hasTimezone) { datetime_tm.tm_year > 0 ? datetime_tm.tm_year : -datetime_tm.tm_year + 1, datetime_tm.tm_mon, datetime_tm.tm_mday, datetime_tm.tm_hour, datetime_tm.tm_min, datetime_tm.tm_sec, (int32) datetime_fsec); - db2Debug1("< %s::deparseTimestamp - returns: '%s'", __FILE__, s.data); + db2Exit(1,"< db2_deparse.c::deparseTimestamp - returns: '%s'", s.data); return s.data; } -/* - * Deparse given constant value into context->buf. - * +/* Deparse given constant value into context->buf. * This function has to be kept in sync with ruleutils.c's get_const_expr. * - * As in that function, showtype can be -1 to never show "::typename" - * decoration, +1 to always show it, or 0 to show it only if the constant + * As in that function, showtype can be -1 to never show "::typename" decoration, +1 to always show it, or 0 to show it only if the constant * wouldn't be assumed to be the right type by default. * - * In addition, this code allows showtype to be -2 to indicate that we should - * not show "::typename" decoration if the constant is printed as an untyped - * literal or NULL (while in other cases, behaving as for showtype == 0). + * In addition, this code allows showtype to be -2 to indicate that we should not show "::typename" decoration if the constant is printed as + * an untyped literal or NULL (while in other cases, behaving as for showtype == 0). */ static void deparseConst(Const *node, deparse_expr_cxt *context, int showtype) { StringInfo buf = context->buf; @@ -3116,6 +3099,7 @@ static void deparseConst(Const *node, deparse_expr_cxt *context, int showtype) { bool isstring = false; bool needlabel; + db2Entry(1,"> db2_deparse.c::deparseConst"); if (node->constisnull) { appendStringInfoString(buf, "NULL"); if (showtype >= 0) @@ -3165,9 +3149,10 @@ static void deparseConst(Const *node, deparse_expr_cxt *context, int showtype) { break; } pfree(extval); - if (showtype == -1) + if (showtype == -1) { + db2Exit(1,"< db2_deparse.c::deparseConst"); return; /* never print type label */ - + } /* For showtype == 0, append ::typename unless the constant will be implicitly typed as the right type when it is read in. * XXX this code has to be kept in sync with the behavior of the parser, especially make_const. */ @@ -3185,12 +3170,13 @@ static void deparseConst(Const *node, deparse_expr_cxt *context, int showtype) { /* label unless we printed it as an untyped string */ needlabel = !isstring; } else { - needlabel = true; + needlabel = true; } break; } if (needlabel || showtype > 0) appendStringInfo(buf, "::%s", deparse_type_name(node->consttype, node->consttypmod)); + db2Exit(1,"< db2_deparse.c::deparseConst"); } /** Print the name of an operator. @@ -3198,6 +3184,7 @@ static void deparseConst(Const *node, deparse_expr_cxt *context, int showtype) { static void deparseOperatorName(StringInfo buf, Form_pg_operator opform) { char* opname = NULL; + db2Entry(1,"> db2_deparse.c::deparseOperatorName"); /* opname is not a SQL identifier, so we should not quote it. */ opname = NameStr(opform->oprname); @@ -3212,6 +3199,7 @@ static void deparseOperatorName(StringInfo buf, Form_pg_operator opform) { /* Just print operator name. */ appendStringInfoString(buf, opname); } + db2Exit(1,"< db2_deparse.c::deparseOperatorName"); } /** deparsedeparseInterval @@ -3228,7 +3216,7 @@ static char* deparseInterval (Datum datum) { char* sign; int idx = 0; - db2Debug1("> %s::deparseInterval",__FILE__); + db2Entry(1,"> db2_deparse.c::deparseInterval"); #if PG_VERSION_NUM >= 150000 interval2itm (*DatumGetIntervalP (datum), &tm); #else @@ -3294,7 +3282,7 @@ static char* deparseInterval (Datum datum) { // #else // appendStringInfo (&s, "INTERVAL '%s%d %02d:%02d:%02d.%06d' DAY(9) TO SECOND(6)", sign, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, fsec); // #endif - db2Debug1("< %s::deparseInterval - returns: '%s'",__FILE__,s.data); + db2Exit(1,"< db2_deparse.c::deparseInterval - returns: '%s'",s.data); return s.data; } @@ -3307,6 +3295,7 @@ static void deparseLockingClause(deparse_expr_cxt *context) { DB2FdwState* fpinfo = (DB2FdwState*) rel->fdw_private; int relid = -1; + db2Entry(1,"> db2_deparse.c::deparseLockingClause"); while ((relid = bms_next_member(rel->relids, relid)) >= 0) { /* Ignore relation if it appears in a lower subquery. Locking clause for such a relation is included in the subquery if necessary. */ if (bms_is_member(relid, fpinfo->lower_subquery_rels)) @@ -3336,7 +3325,7 @@ static void deparseLockingClause(deparse_expr_cxt *context) { * since (a) it's not clear what that means for a remote table that we don't have complete information about, * and (b) it wouldn't work anyway on older remote servers. * Likewise, we don't worry about NOWAIT. - */ + */ switch (rc->strength) { case LCS_NONE: /* No locking needed */ @@ -3356,13 +3345,14 @@ static void deparseLockingClause(deparse_expr_cxt *context) { } } } + db2Exit(1,"< db2_deparse.c::deparseLockingClause"); } /** Output join name for given join type */ char* get_jointype_name (JoinType jointype) { char* type = NULL; - db2Debug1("> get_jointype_name"); + db2Entry(1,"> get_jointype_name"); switch (jointype) { case JOIN_INNER: type = "INNER"; @@ -3381,8 +3371,8 @@ char* get_jointype_name (JoinType jointype) { elog (ERROR, "unsupported join type %d", jointype); break; } - db2Debug2(" type: '%s'",type); - db2Debug1("< get_jointype_name"); + db2Debug(2,"type: '%s'",type); + db2Exit(1,"< get_jointype_name"); return type; } @@ -3397,7 +3387,7 @@ static void deparseTargetList(StringInfo buf, RangeTblEntry *rte, Index rtindex, bool first; int i; - db2Debug1("> %s::deparseTargetList",__FILE__); + db2Entry(1,"> db2_deparse.c::deparseTargetList"); *retrieved_attrs = NIL; /* If there's a whole-row reference, we'll need all the columns. */ @@ -3436,7 +3426,7 @@ static void deparseTargetList(StringInfo buf, RangeTblEntry *rte, Index rtindex, /* Don't generate bad syntax if no undropped columns */ if (first && !is_returning) appendStringInfoString(buf, "NULL"); - db2Debug1("> %s::deparseTargetList : %s",__FILE__, buf->data); + db2Exit(1,"< db2_deparse.c::deparseTargetList : %s", buf->data); } /** Deparse given targetlist and append it to context->buf. @@ -3452,7 +3442,7 @@ static void deparseExplicitTargetList(List* tlist, bool is_returning, List** ret StringInfo buf = context->buf; int i = 0; - db2Debug1("> %s::deparseExplicitTargetList",__FILE__); + db2Entry(1,"> db2_deparse.c::deparseExplicitTargetList"); *retrieved_attrs = NIL; foreach(lc, tlist) { @@ -3471,10 +3461,10 @@ static void deparseExplicitTargetList(List* tlist, bool is_returning, List** ret if (i == 0 && !is_returning) appendStringInfoString(buf, "NULL"); - db2Debug1("< %s::deparseExplicitTargetList : %s",__FILE__, buf->data); + db2Exit(1,"< db2_deparse.c::deparseExplicitTargetList : %s", buf->data); } -/** Emit expressions specified in the given relation's reltarget. +/* Emit expressions specified in the given relation's reltarget. * * This is used for deparsing the given relation as a subquery. */ @@ -3482,7 +3472,7 @@ static void deparseSubqueryTargetList(deparse_expr_cxt* context) { bool first = true; ListCell* lc; - db2Debug1("> %s::deparseSubqueryTargetList",__FILE__); + db2Entry(1,"> db2_deparse.c::deparseSubqueryTargetList"); /* Should only be called in these cases. */ Assert(IS_SIMPLE_REL(context->foreignrel) || IS_JOIN_REL(context->foreignrel)); foreach(lc, context->foreignrel->reltarget->exprs) { @@ -3495,11 +3485,10 @@ static void deparseSubqueryTargetList(deparse_expr_cxt* context) { /* Don't generate bad syntax if no expressions */ if (first) appendStringInfoString(context->buf, "NULL"); - db2Debug1("< %s::deparseSubqueryTargetList : %s",__FILE__, context->buf->data); + db2Exit(1,"< db2_deparse.c::deparseSubqueryTargetList : %s", context->buf->data); } -/** Given an EquivalenceClass and a foreign relation, find an EC member that can be used to sort the relation remotely according to a pathkey - * using this EC. +/* Given an EquivalenceClass and a foreign relation, find an EC member that can be used to sort the relation remotely according to a pathkey using this EC. * * If there is more than one suitable candidate, return an arbitrary one of them. If there is none, return NULL. * @@ -3511,6 +3500,7 @@ EquivalenceMember* find_em_for_rel(PlannerInfo* root, EquivalenceClass* ec, RelO EquivalenceMemberIterator it; EquivalenceMember* em = NULL; + db2Entry(1,"> db2_deparse.c::find_em_for_rel"); setup_eclass_member_iterator(&it, ec, rel->relids); while ((em = eclass_member_iterator_next(&it)) != NULL) { /* Note we require !bms_is_empty, else we'd accept constant expressions which are not suitable for the purpose. */ @@ -3518,12 +3508,13 @@ EquivalenceMember* find_em_for_rel(PlannerInfo* root, EquivalenceClass* ec, RelO && !bms_is_empty(em->em_relids) && bms_is_empty(bms_intersect(em->em_relids, fpinfo->hidden_subquery_rels)) && is_foreign_expr(root, rel, em->em_expr)) - return em; + break; } - return NULL; + db2Exit(1,"< db2_deparse.c::find_em_for_rel : %x",em); + return em; } -/** Find an EquivalenceClass member that is to be computed as a sort column in the given rel's reltarget, and is shippable. +/* Find an EquivalenceClass member that is to be computed as a sort column in the given rel's reltarget, and is shippable. * * If there is more than one suitable candidate, return an arbitrary one of them. If there is none, return NULL. * @@ -3535,6 +3526,7 @@ EquivalenceMember* find_em_for_rel_target(PlannerInfo* root, EquivalenceClass* e ListCell* lc1; int i = 0; + db2Entry(1,"> db2_deparse.c::find_em_for_rel_target"); foreach(lc1, target->exprs) { Expr* expr = (Expr *) lfirst(lc1); Index sgref = get_pathtarget_sortgroupref(target, i); @@ -3573,11 +3565,14 @@ EquivalenceMember* find_em_for_rel_target(PlannerInfo* root, EquivalenceClass* e continue; /* Check that expression (including relabels!) is shippable */ - if (is_foreign_expr(root, rel, em->em_expr)) + if (is_foreign_expr(root, rel, em->em_expr)) { + db2Exit(1,"< db2_deparse.c::find_em_for_rel_target : %x", em); return em; + } } i++; } + db2Exit(1,"< db2_deparse.c::find_em_for_rel_target : %x", NULL); return NULL; } @@ -3594,124 +3589,115 @@ EquivalenceMember* find_em_for_rel_target(PlannerInfo* root, EquivalenceClass* e * '*retrieved_attrs' is an output list of integers of columns being retrieved by RETURNING (if any) */ void deparseDirectUpdateSql(StringInfo buf, PlannerInfo *root, Index rtindex, Relation rel, RelOptInfo *foreignrel, List *targetlist, List *targetAttrs, List *remote_conds, List **params_list, List *returningList, List **retrieved_attrs) { - deparse_expr_cxt context; - int nestlevel; - bool first; - RangeTblEntry *rte = planner_rt_fetch(rtindex, root); - ListCell *lc, - *lc2; - List *additional_conds = NIL; - - /* Set up context struct for recursion */ - context.root = root; - context.foreignrel = foreignrel; - context.scanrel = foreignrel; - context.buf = buf; - context.params_list = params_list; - - appendStringInfoString(buf, "UPDATE "); - deparseRelation(buf, rel); - if (foreignrel->reloptkind == RELOPT_JOINREL) - appendStringInfo(buf, " %s%d", REL_ALIAS_PREFIX, rtindex); - appendStringInfoString(buf, " SET "); - - /* Make sure any constants in the exprs are printed portably */ - nestlevel = set_transmission_modes(); - - first = true; - forboth(lc, targetlist, lc2, targetAttrs) - { - TargetEntry *tle = lfirst_node(TargetEntry, lc); - int attnum = lfirst_int(lc2); - - /* update's new-value expressions shouldn't be resjunk */ - Assert(!tle->resjunk); - - if (!first) - appendStringInfoString(buf, ", "); - first = false; - - deparseColumnRef(buf, rtindex, attnum, rte, false); - appendStringInfoString(buf, " = "); - deparseExprInt((Expr*) tle->expr, &context); - } - - reset_transmission_modes(nestlevel); - - if (foreignrel->reloptkind == RELOPT_JOINREL) - { - List *ignore_conds = NIL; - - - appendStringInfoString(buf, " FROM "); - deparseFromExprForRel(buf, root, foreignrel, true, rtindex, - &ignore_conds, &additional_conds, params_list); - remote_conds = list_concat(remote_conds, ignore_conds); - } - - appendWhereClause(remote_conds, additional_conds, &context); - - if (additional_conds != NIL) - list_free_deep(additional_conds); - - if (foreignrel->reloptkind == RELOPT_JOINREL) - deparseExplicitTargetList(returningList, true, retrieved_attrs, - &context); - else - deparseReturningList(buf, rte, rtindex, rel, false, - NIL, returningList, retrieved_attrs); + deparse_expr_cxt context; + int nestlevel = 0; + bool first = true; + RangeTblEntry* rte = planner_rt_fetch(rtindex, root); + ListCell* lc = NULL; + ListCell* lc2 = NULL; + List* additional_conds = NIL; + + db2Entry(1,"> db2_deparse.c::deparseDirectUpdateSql"); + /* Set up context struct for recursion */ + context.root = root; + context.foreignrel = foreignrel; + context.scanrel = foreignrel; + context.buf = buf; + context.params_list = params_list; + + appendStringInfoString(buf, "UPDATE "); + deparseRelation(buf, rel); + if (foreignrel->reloptkind == RELOPT_JOINREL) + appendStringInfo(buf, " %s%d", REL_ALIAS_PREFIX, rtindex); + appendStringInfoString(buf, " SET "); + + /* Make sure any constants in the exprs are printed portably */ + nestlevel = set_transmission_modes(); + + first = true; + forboth(lc, targetlist, lc2, targetAttrs) { + TargetEntry* tle = lfirst_node(TargetEntry, lc); + int attnum = lfirst_int(lc2); + + /* update's new-value expressions shouldn't be resjunk */ + Assert(!tle->resjunk); + + if (!first) + appendStringInfoString(buf, ", "); + first = false; + + deparseColumnRef(buf, rtindex, attnum, rte, false); + appendStringInfoString(buf, " = "); + deparseExprInt((Expr*) tle->expr, &context); + } + + reset_transmission_modes(nestlevel); + + if (foreignrel->reloptkind == RELOPT_JOINREL) { + List* ignore_conds = NIL; + + appendStringInfoString(buf, " FROM "); + deparseFromExprForRel(buf, root, foreignrel, true, rtindex, &ignore_conds, &additional_conds, params_list); + remote_conds = list_concat(remote_conds, ignore_conds); + } + + appendWhereClause(remote_conds, additional_conds, &context); + + if (additional_conds != NIL) + list_free_deep(additional_conds); + + if (foreignrel->reloptkind == RELOPT_JOINREL) + deparseExplicitTargetList(returningList, true, retrieved_attrs, &context); + else + deparseReturningList(buf, rte, rtindex, rel, false, NIL, returningList, retrieved_attrs); + db2Exit(1,"< db2_deparse.c::deparseDirectUpdateSql : %s",buf->data); } /* * Add a RETURNING clause, if needed, to an INSERT/UPDATE/DELETE. */ static void deparseReturningList(StringInfo buf, RangeTblEntry *rte, Index rtindex, Relation rel, bool trig_after_row, List *withCheckOptionList, List *returningList, List **retrieved_attrs) { - Bitmapset *attrs_used = NULL; - - if (trig_after_row) { - /* whole-row reference acquires all non-system columns */ - attrs_used = - bms_make_singleton(0 - FirstLowInvalidHeapAttributeNumber); - } - - if (withCheckOptionList != NIL) { - /* We need the attrs, non-system and system, mentioned in the local query's WITH CHECK OPTION list. - * - * Note: we do this to ensure that WCO constraints will be evaluated on the data actually inserted/updated on the remote side, which - * might differ from the data supplied by the core code, for example as a result of remote triggers. - */ - pull_varattnos((Node *) withCheckOptionList, rtindex, - &attrs_used); - } - - if (returningList != NIL) { - /* - * We need the attrs, non-system and system, mentioned in the local - * query's RETURNING list. - */ - pull_varattnos((Node *) returningList, rtindex, - &attrs_used); - } - - if (attrs_used != NULL) - deparseTargetList(buf, rte, rtindex, rel, true, attrs_used, false, - retrieved_attrs); - else - *retrieved_attrs = NIL; + Bitmapset *attrs_used = NULL; + + db2Entry(1,"> db2_deparse.c::deparseReturningList"); + if (trig_after_row) { + /* whole-row reference acquires all non-system columns */ + attrs_used = bms_make_singleton(0 - FirstLowInvalidHeapAttributeNumber); + } + + if (withCheckOptionList != NIL) { + /* We need the attrs, non-system and system, mentioned in the local query's WITH CHECK OPTION list. + * + * Note: we do this to ensure that WCO constraints will be evaluated on the data actually inserted/updated on the remote side, which + * might differ from the data supplied by the core code, for example as a result of remote triggers. + */ + pull_varattnos((Node *) withCheckOptionList, rtindex, &attrs_used); + } + + if (returningList != NIL) { + /* We need the attrs, non-system and system, mentioned in the local query's RETURNING list. */ + pull_varattnos((Node *) returningList, rtindex, &attrs_used); + } + + if (attrs_used != NULL) + deparseTargetList(buf, rte, rtindex, rel, true, attrs_used, false, retrieved_attrs); + else + *retrieved_attrs = NIL; + db2Exit(1,"< db2_deparse.c::deparseReturningList : %s", buf->data); } /* deparse remote DELETE statement * The statement text is appended to buf, and we also create an integer List of the columns being retrieved by RETURNING (if any), which is returned to *retrieved_attrs. */ -void deparseDeleteSql(StringInfo buf, RangeTblEntry *rte, Index rtindex, Relation rel, List *returningList, List **retrieved_attrs) -{ - appendStringInfoString(buf, "DELETE FROM "); - deparseRelation(buf, rel); - appendStringInfoString(buf, " WHERE ctid = $1"); - - deparseReturningList(buf, rte, rtindex, rel, - rel->trigdesc && rel->trigdesc->trig_delete_after_row, - NIL, returningList, retrieved_attrs); +void deparseDeleteSql(StringInfo buf, RangeTblEntry *rte, Index rtindex, Relation rel, List *returningList, List **retrieved_attrs) { + db2Entry(1,"> db2_deparse.c::deparseDeleteSql"); + + appendStringInfoString(buf, "DELETE FROM "); + deparseRelation(buf, rel); + appendStringInfoString(buf, " WHERE ctid = $1"); + deparseReturningList(buf, rte, rtindex, rel, rel->trigdesc && rel->trigdesc->trig_delete_after_row, NIL, returningList, retrieved_attrs); + + db2Exit(1,"> db2_deparse.c::deparseDeleteSql : %s", buf->data); } /* deparse remote DELETE statement @@ -3725,41 +3711,39 @@ void deparseDeleteSql(StringInfo buf, RangeTblEntry *rte, Index rtindex, Relatio * '*retrieved_attrs' is an output list of integers of columns being retrieved by RETURNING (if any) */ void deparseDirectDeleteSql(StringInfo buf, PlannerInfo *root, Index rtindex, Relation rel, RelOptInfo *foreignrel, List *remote_conds, List **params_list, List *returningList, List **retrieved_attrs) { - deparse_expr_cxt context; - List *additional_conds = NIL; - - /* Set up context struct for recursion */ - context.root = root; - context.foreignrel = foreignrel; - context.scanrel = foreignrel; - context.buf = buf; - context.params_list = params_list; - - appendStringInfoString(buf, "DELETE FROM "); - deparseRelation(buf, rel); - if (foreignrel->reloptkind == RELOPT_JOINREL) - appendStringInfo(buf, " %s%d", REL_ALIAS_PREFIX, rtindex); - - if (foreignrel->reloptkind == RELOPT_JOINREL) - { - List *ignore_conds = NIL; - - appendStringInfoString(buf, " USING "); - deparseFromExprForRel(buf, root, foreignrel, true, rtindex, - &ignore_conds, &additional_conds, params_list); - remote_conds = list_concat(remote_conds, ignore_conds); - } - - appendWhereClause(remote_conds, additional_conds, &context); - - if (additional_conds != NIL) - list_free_deep(additional_conds); - - if (foreignrel->reloptkind == RELOPT_JOINREL) - deparseExplicitTargetList(returningList, true, retrieved_attrs, - &context); - else - deparseReturningList(buf, planner_rt_fetch(rtindex, root), - rtindex, rel, false, - NIL, returningList, retrieved_attrs); + deparse_expr_cxt context; + List *additional_conds = NIL; + + db2Entry(1,"> db2_deparse.c::deparseDirectDeleteSql"); + /* Set up context struct for recursion */ + context.root = root; + context.foreignrel = foreignrel; + context.scanrel = foreignrel; + context.buf = buf; + context.params_list = params_list; + + appendStringInfoString(buf, "DELETE FROM "); + deparseRelation(buf, rel); + if (foreignrel->reloptkind == RELOPT_JOINREL) + appendStringInfo(buf, " %s%d", REL_ALIAS_PREFIX, rtindex); + + if (foreignrel->reloptkind == RELOPT_JOINREL) + { + List *ignore_conds = NIL; + + appendStringInfoString(buf, " USING "); + deparseFromExprForRel(buf, root, foreignrel, true, rtindex, &ignore_conds, &additional_conds, params_list); + remote_conds = list_concat(remote_conds, ignore_conds); + } + + appendWhereClause(remote_conds, additional_conds, &context); + + if (additional_conds != NIL) + list_free_deep(additional_conds); + + if (foreignrel->reloptkind == RELOPT_JOINREL) + deparseExplicitTargetList(returningList, true, retrieved_attrs, &context); + else + deparseReturningList(buf, planner_rt_fetch(rtindex, root), rtindex, rel, false, NIL, returningList, retrieved_attrs); + db2Exit(1,"< db2_deparse.c::deparseDirectDeleteSql : %s", buf->data); } diff --git a/source/db2_fdw.c b/source/db2_fdw.c index 9c0e69e..46fff32 100644 --- a/source/db2_fdw.c +++ b/source/db2_fdw.c @@ -14,15 +14,12 @@ #include #include #endif -#include -#include #include #include #include #include #include #include -#include #include #include #include "db2_fdw.h" @@ -170,10 +167,10 @@ PGDLLEXPORT Datum db2_fdw_handler (PG_FUNCTION_ARGS) { fdwroutine->BeginForeignInsert = db2BeginForeignInsert; fdwroutine->EndForeignInsert = db2EndForeignInsert; -// fdwroutine->PlanDirectModify = db2PlanDirectModify; -// fdwroutine->BeginDirectModify = db2BeginDirectModify; -// fdwroutine->IterateDirectModify = db2IterateDirectModify; -// fdwroutine->EndDirectModify = db2EndDirectModify; + fdwroutine->PlanDirectModify = db2PlanDirectModify; + fdwroutine->BeginDirectModify = db2BeginDirectModify; + fdwroutine->IterateDirectModify = db2IterateDirectModify; + fdwroutine->EndDirectModify = db2EndDirectModify; #if PG_VERSION_NUM >= 140000 fdwroutine->ExecForeignTruncate = db2ExecForeignTruncate; diff --git a/source/db2_fdw_utils.c b/source/db2_fdw_utils.c index c688760..9fed162 100644 --- a/source/db2_fdw_utils.c +++ b/source/db2_fdw_utils.c @@ -3,7 +3,6 @@ #include #include #include -#include #include #include #include @@ -36,11 +35,12 @@ typedef struct { /** external prototypes */ +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); +extern void db2Debug (int level, const char* message, ...); extern void db2GetLob (DB2Session* session, DB2ResultColumn* column, char** value, long* value_len, unsigned long trunc); extern void db2Shutdown (void); extern short c2dbType (short fcType); -extern void db2Debug4 (const char* message, ...); -extern void db2Debug5 (const char* message, ...); extern void* db2alloc (const char* type, size_t size); extern void* db2strdup (const char* source); extern void db2free (void* p); @@ -63,9 +63,9 @@ static bool lookup_shippable (Oid objectId, Oid classId, DB2Fd */ bool optionIsTrue (const char *value) { bool result = false; - db2Debug4("> optionIsTrue(value: '%s')",value); + db2Entry(4,"> db2_fdw_utils.c::optionIsTrue(value: '%s')",value); result = (pg_strcasecmp (value, "on") == 0 || pg_strcasecmp (value, "yes") == 0 || pg_strcasecmp (value, "true") == 0); - db2Debug4("< optionIsTrue - returns: '%s'",((result) ? "true" : "false")); + db2Exit(4,"< db2_fdw_utils.c::optionIsTrue : '%s'",((result) ? "true" : "false")); return result; } @@ -80,7 +80,7 @@ char* guessNlsLang (char *nls_lang) { char* charset = NULL; StringInfoData buf; - db2Debug4("> %s::guessNlsLang(nls_lang: %s)", __FILE__, nls_lang); + db2Entry(4,"> db2_fdw_utils.c::guessNlsLang(nls_lang: %s)", nls_lang); initStringInfo (&buf); if (nls_lang == NULL) { server_encoding = db2strdup (GetConfigOption ("server_encoding", false, true)); @@ -183,7 +183,7 @@ char* guessNlsLang (char *nls_lang) { } else { appendStringInfo (&buf, "NLS_LANG=%s", nls_lang); } - db2Debug4("< %s::guessNlsLang : %s", __FILE__, buf.data); + db2Exit(4,"< db2_fdw_utils.c::guessNlsLang : %s", buf.data); return buf.data; } @@ -191,9 +191,9 @@ char* guessNlsLang (char *nls_lang) { * Close all DB2 connections on process exit. */ void exitHook (int code, Datum arg) { - db2Debug4("> %s::exitHook",__FILE__); + db2Entry(4,"> db2_fdw_utils.c::exitHook"); db2Shutdown (); - db2Debug4("< %s::exitHook",__FILE__); + db2Exit(4,"< db2_fdw_utils.c::exitHook"); } /* convertTuple @@ -208,13 +208,13 @@ void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls DB2ResultColumn* res = NULL; bool isSimpleSelect = false; - db2Debug4("> %s::convertTuple",__FILE__); - db2Debug5(" natts: %d", natts); - db2Debug5(" truncate lob: %s", trunc_lob ? "true": "false"); + db2Entry(4,"> db2_fdw_utils.c::convertTuple"); + db2Debug(5,"natts: %d", natts); + db2Debug(5,"truncate lob: %s", trunc_lob ? "true": "false"); /* assign result values */ isSimpleSelect = (natts == db2Table->npgcols); - db2Debug5(" isSimpleSelect: %s", isSimpleSelect ? "true": "false"); + db2Debug(5,"isSimpleSelect: %s", isSimpleSelect ? "true": "false"); // initialize all columns to NULL for (j = 0; j < natts; j++) { @@ -224,14 +224,14 @@ void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls for (res = fdw_state->resultList; res; res = res->next) { j = ((isSimpleSelect) ? res->pgattnum : res->resnum) - 1; - db2Debug5(" start processing column %d of %d: values index = %d", res->resnum, natts, j); - db2Debug5(" res->pgname : %s" ,res->pgname ); - db2Debug5(" res->pgattnum : %d" ,res->pgattnum); - db2Debug5(" res->pgtype : %d" ,res->pgtype ); - db2Debug5(" res->pgtypmod : %d" ,res->pgtypmod); - db2Debug5(" res->val : %s" ,res->val ); - db2Debug5(" res->val_len : %d" ,res->val_len ); - db2Debug5(" res->val_null : %d" ,res->val_null); + db2Debug(5,"start processing column %d of %d: values index = %d", res->resnum, natts, j); + db2Debug(5,"res->pgname : %s" ,res->pgname ); + db2Debug(5,"res->pgattnum : %d" ,res->pgattnum); + db2Debug(5,"res->pgtype : %d" ,res->pgtype ); + db2Debug(5,"res->pgtypmod : %d" ,res->pgtypmod); + db2Debug(5,"res->val : %s" ,res->val ); + db2Debug(5,"res->val_len : %d" ,res->val_len ); + db2Debug(5,"res->val_null : %d" ,res->val_null); if (res->val_null >= 0) { short db2Type = 0; @@ -242,14 +242,14 @@ void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls switch(c2dbType(res->colType)) { case DB2_BLOB: case DB2_CLOB: { - db2Debug5(" DB2_BLOB or DB2CLOB"); + db2Debug(5,"DB2_BLOB or DB2CLOB"); /* for LOBs, get the actual LOB contents (allocated), truncated if desired */ /* the column index is 1 based, whereas index id 0 based, so always add 1 to index when calling db2GetLob, since it does a column based access*/ db2GetLob (fdw_state->session, res, &value, &value_len, trunc_lob ? (WIDTH_THRESHOLD + 1) : 0); } break; case DB2_LONGVARBINARY: { - db2Debug5(" DB2_LONGBINARY datatypes"); + db2Debug(5,"DB2_LONGBINARY datatypes"); /* for LONG and LONG RAW, the first 4 bytes contain the length */ value_len = *((int32*) res->val); /* the rest is the actual data */ @@ -267,7 +267,7 @@ void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls case DB2_DOUBLE: { char* tmp_value = NULL; - db2Debug5(" DB2_FLOAT, DECIMAL, SMALLINT, INTEGER, REAL, DECFLOAT, DOUBLE"); + db2Debug(5,"DB2_FLOAT, DECIMAL, SMALLINT, INTEGER, REAL, DECFLOAT, DOUBLE"); value = res->val; value_len = res->val_len; value_len = (value_len == 0) ? strlen(value) : value_len; @@ -278,7 +278,7 @@ void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls } break; default: { - db2Debug5(" shoud be string based values"); + db2Debug(5,"shoud be string based values"); /* for other data types, db2Table contains the results */ value = res->val; value_len = res->val_len; @@ -286,8 +286,8 @@ void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls } break; } - db2Debug5(" value : %s" , value); - db2Debug5(" value_len : %ld" , value_len); + db2Debug(5,"value : %s" , value); + db2Debug(5,"value_len : %ld" , value_len); /* fill the TupleSlot with the data (after conversion if necessary) */ if (res->pgtype == BYTEAOID) { /* binary columns are not converted */ @@ -300,7 +300,7 @@ void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls regproc typinput; HeapTuple tuple; Datum dat; - db2Debug5(" pgtype: %d",res->pgtype); + db2Debug(5,"pgtype: %d",res->pgtype); /* find the appropriate conversion function */ tuple = SearchSysCache1 (TYPEOID, ObjectIdGetDatum (res->pgtype)); if (!HeapTupleIsValid (tuple)) { @@ -309,11 +309,11 @@ void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls typinput = ((Form_pg_type) GETSTRUCT (tuple))->typinput; ReleaseSysCache (tuple); dat = CStringGetDatum (value); - db2Debug5(" CStringGetDatum(%s): %d",value, dat); + db2Debug(5,"CStringGetDatum(%s): %d",value, dat); /* for string types, check that the data are in the database encoding */ if (res->pgtype == BPCHAROID || res->pgtype == VARCHAROID || res->pgtype == TEXTOID) { - db2Debug5(" pg_verify_mbstr"); + db2Debug(5,"pg_verify_mbstr"); (void) pg_verify_mbstr (GetDatabaseEncoding(), value, value_len, res->noencerr == NO_ENC_ERR_TRUE); } /* call the type input function */ @@ -328,12 +328,12 @@ void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls case NUMERICOID: /* these functions require the type modifier */ values[j] = OidFunctionCall3 (typinput, dat, ObjectIdGetDatum (InvalidOid), Int32GetDatum (res->pgtypmod)); - db2Debug5(" OidFunctionCall3 : values[%d]: %d", j, values[j]); + db2Debug(5,"OidFunctionCall3 : values[%d]: %d", j, values[j]); break; default: /* the others don't */ values[j] = OidFunctionCall1 (typinput, dat); - db2Debug5(" OidFunctionCall1 : values[%d]: %d", j, values[j]); + db2Debug(5,"OidFunctionCall1 : values[%d]: %d", j, values[j]); } } /* release the data buffer for LOBs */ @@ -342,19 +342,21 @@ void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls if (value != NULL) { db2free (value); } else { - db2Debug5(" not freeing value, since it is null"); + db2Debug(5,"not freeing value, since it is null"); } } } else { - db2Debug5(" column %d is NULL", res->resnum); + db2Debug(5,"column %d is NULL", res->resnum); } } - db2Debug4("< %s::convertTuple",__FILE__); + db2Exit(4,"< db2_fdw_utils.c::convertTuple"); } /* Undo the effects of set_transmission_modes(). */ void reset_transmission_modes(int nestlevel) { + db2Entry(4,"> db2_fdw_utils.c::reset_transmission_modes"); AtEOXact_GUC(true, nestlevel); + db2Exit(4,"< db2_fdw_utils.c::reset_transmission_modes"); } /* Force assorted GUC parameters to settings that ensure that we'll output data values in a form that is unambiguous to the remote server. @@ -370,6 +372,7 @@ void reset_transmission_modes(int nestlevel) { int set_transmission_modes(void) { int nestlevel = NewGUCNestLevel(); + db2Entry(4,"> db2_fdw_utils.c::set_transmission_modes"); /* The values set here should match what pg_dump does. See also configure_remote_session in connection.c. */ if (DateStyle != USE_ISO_DATES) (void) set_config_option("datestyle", "ISO", PGC_USERSET, PGC_S_SESSION, GUC_ACTION_SAVE, true, 0, false); @@ -384,6 +387,7 @@ int set_transmission_modes(void) { */ (void) set_config_option("search_path", "pg_catalog", PGC_USERSET, PGC_S_SESSION, GUC_ACTION_SAVE, true, 0, false); + db2Exit(4,"< db2_fdw_utils.c::set_transmission_modes : %d", nestlevel); return nestlevel; } @@ -402,7 +406,10 @@ int set_transmission_modes(void) { * But keeping track of that would be a huge exercise. */ bool is_builtin (Oid objectId) { - return (objectId < FirstGenbkiObjectId); + bool isBuiltin = (objectId < FirstGenbkiObjectId); + db2Entry(4,"> db2_fdw_utils.c::is_builtin"); + db2Exit (4,"> db2_fdw_utils.c::is_builtin : %s", (isBuiltin) ? "true": "false"); + return isBuiltin; } /* is_shippable @@ -410,39 +417,47 @@ bool is_builtin (Oid objectId) { */ bool is_shippable (Oid objectId, Oid classId, DB2FdwState* fpinfo) { ShippableCacheKey key; - ShippableCacheEntry* entry; + ShippableCacheEntry* entry = NULL; + bool shippable = false; + db2Entry(4,"> db2_fdw_utils.c::is_shippable"); /* Built-in objects are presumed shippable. */ - if (is_builtin(objectId)) - return true; - - /* Otherwise, give up if user hasn't specified any shippable extensions. */ - if (fpinfo->shippable_extensions == NIL) - return false; - - /* Initialize cache if first time through. */ - if (!ShippableCacheHash) - InitializeShippableCache(); - - /* Set up cache hash key */ - key.objid = objectId; - key.classid = classId; - key.serverid = fpinfo->fserver->serverid; - - /* See if we already cached the result. */ - entry = (ShippableCacheEntry*) hash_search(ShippableCacheHash, &key, HASH_FIND, NULL); - - if (!entry) { - /* Not found in cache, so perform shippability lookup. */ - bool shippable = lookup_shippable(objectId, classId, fpinfo); - - /* Don't create a new hash entry until *after* we have the shippable result in hand, as the underlying catalog lookups might trigger a - * cache invalidation. - */ - entry = (ShippableCacheEntry*) hash_search(ShippableCacheHash, &key, HASH_ENTER, NULL); - entry->shippable = shippable; + if (is_builtin(objectId)) { + shippable = true; + } else { + /* Otherwise, give up if user hasn't specified any shippable extensions. */ + if (fpinfo->shippable_extensions == NIL) { + shippable = false; + } else { + /* Initialize cache if first time through. */ + if (!ShippableCacheHash) { + InitializeShippableCache(); + } + + /* Set up cache hash key */ + key.objid = objectId; + key.classid = classId; + key.serverid = fpinfo->fserver->serverid; + + /* See if we already cached the result. */ + entry = (ShippableCacheEntry*) hash_search(ShippableCacheHash, &key, HASH_FIND, NULL); + if (!entry) { + /* Not found in cache, so perform shippability lookup. */ + shippable = lookup_shippable(objectId, classId, fpinfo); + + /* Don't create a new hash entry until *after* we have the shippable result in hand, as the underlying catalog lookups might trigger a + * cache invalidation. + */ + entry = (ShippableCacheEntry*) hash_search(ShippableCacheHash, &key, HASH_ENTER, NULL); + entry->shippable = shippable; + } else { + db2Debug(4, "no shippable cache entry (%x) found shippable is set to false", entry); + shippable = false; + } + } } - return entry->shippable; + db2Exit(4,"< db2_fdw_utils.c::is_shippable : %s", shippable ? "true" : "false"); + return shippable; } /* Flush cache entries when pg_foreign_server is updated. @@ -455,6 +470,7 @@ static void InvalidateShippableCacheCbk(Datum arg, int cacheid, uint32 hashvalue HASH_SEQ_STATUS status; ShippableCacheEntry* entry; + db2Entry(5,"> db2_fdw_utils.c::InvalidateShippableCacheCbk"); /* In principle we could flush only cache entries relating to the pg_foreign_server entry being outdated; but that would be more * complicated, and it's probably not worth the trouble. * So for now, just flush all entries. @@ -464,12 +480,14 @@ static void InvalidateShippableCacheCbk(Datum arg, int cacheid, uint32 hashvalue if (hash_search(ShippableCacheHash, &entry->key, HASH_REMOVE, NULL) == NULL) elog(ERROR, "hash table corrupted"); } + db2Exit(5,"< db2_fdw_utils.c::InvalidateShippableCacheCbk"); } /* Initialize the backend-lifespan cache of shippability decisions. */ static void InitializeShippableCache(void) { HASHCTL ctl; + db2Entry(5,"> db2_fdw_utils.c::InitializeShippableCache"); /* Create the hash table. */ ctl.keysize = sizeof(ShippableCacheKey); ctl.entrysize = sizeof(ShippableCacheEntry); @@ -477,6 +495,7 @@ static void InitializeShippableCache(void) { /* Set up invalidation callback on pg_foreign_server. */ CacheRegisterSyscacheCallback(FOREIGNSERVEROID, InvalidateShippableCacheCbk, (Datum) 0); + db2Exit(5,"< db2_fdw_utils.c::InitializeShippableCache"); } /* Returns true if given object (operator/function/type) is shippable according to the server options. @@ -485,13 +504,15 @@ static void InitializeShippableCache(void) { * In the future we could additionally have a list of functions/operators declared one at a time. */ static bool lookup_shippable(Oid objectId, Oid classId, DB2FdwState* fpinfo) { - Oid extensionOid; + Oid extensionOid = 0; + bool isValid = false; + db2Entry(5,"> db2_fdw_utils.c::lookup_shippable"); /* Is object a member of some extension? (Note: this is a fairly expensive lookup, which is why we try to cache the results.) */ extensionOid = getExtensionOfObject(classId, objectId); /* If so, is that extension in fpinfo->shippable_extensions? */ - if (OidIsValid(extensionOid) && list_member_oid(fpinfo->shippable_extensions, extensionOid)) - return true; + isValid = (OidIsValid(extensionOid) && list_member_oid(fpinfo->shippable_extensions, extensionOid)); + db2Exit(5,"< db2_fdw_utils.c::lookup_shippable : %s", (isValid) ? "true" : "false"); return false; } \ No newline at end of file diff --git a/source/db2_utils.c b/source/db2_utils.c index 30efef2..69403b7 100644 --- a/source/db2_utils.c +++ b/source/db2_utils.c @@ -5,8 +5,9 @@ #include "db2_fdw.h" /** external variables */ -extern void db2Debug4 (const char* message, ...); -extern void db2Debug5 (const char* message, ...); +extern void db2Entry (int level, const char* message, ...); +extern void db2Exit (int level, const char* message, ...); +extern void db2Debug (int level, const char* message, ...); /** local prototypes */ SQLSMALLINT c2param (SQLSMALLINT fparamType); @@ -80,23 +81,23 @@ char* param2name(SQLSMALLINT fparamType){ */ SQLSMALLINT c2param (SQLSMALLINT fcType) { SQLSMALLINT fparamType = SQL_C_CHAR; - db2Debug4("> c2param(fcType: %d)",fcType); + db2Entry(4,"> db2_utils.c::c2param(fcType: %d)",fcType); switch (fcType) { case SQL_BLOB: fparamType = SQL_C_BLOB_LOCATOR; - db2Debug5(" SQL_BLOB => SQL_C_LOCATOR"); + db2Debug(5,"SQL_BLOB => SQL_C_LOCATOR"); break; case SQL_CLOB: fparamType = SQL_C_CLOB_LOCATOR; - db2Debug5(" SQL_COB => SQL_C_CLOB_LOCATOR"); + db2Debug(5,"SQL_COB => SQL_C_CLOB_LOCATOR"); break; default: /* all other columns are converted to strings */ fparamType = SQL_C_CHAR; - db2Debug5(" %s => SQL_C_CHAR",c2name(fcType)); + db2Debug(5,"%s => SQL_C_CHAR",c2name(fcType)); break; } - db2Debug4("< c2param - fparamType: %d)",fparamType); + db2Exit(4,"< db2_utils.c::c2param : %d)",fparamType); return fparamType; } @@ -112,7 +113,7 @@ void parse2num_struct(const char* s, SQL_NUMERIC_STRUCT* ns) { long int intPart = 0; int negative = 0; int fracLen = 0; - db2Debug4("> parse2num_struct( '%s')",s); + db2Entry(4,"> db2_utils.c::parse2num_struct( '%s')",s); // Simple, minimal parser: handles optional leading '-' and '.'; no thousands sep. memset(ns, 0, sizeof(*ns)); ns->precision = 18; // set to your target @@ -162,7 +163,7 @@ void parse2num_struct(const char* s, SQL_NUMERIC_STRUCT* ns) { ns->val[i] = (SQLCHAR)(mag & 0xFF); mag >>= 8; } - db2Debug4("< parse2num_struct"); + db2Exit(4,"< db2_utils.c::parse2num_struct"); } /** c2dbType From 83ce160c4607292fc4884f289032494e38d4d90f Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Tue, 24 Feb 2026 18:20:27 +0100 Subject: [PATCH 079/120] migration of debug tracing to macros defined in db2_fdw.h --- include/db2_fdw.h | 36 +++ source/db2AddForeignUpdateTargets.c | 17 +- source/db2AllocConnHdl.c | 29 +- source/db2AllocEnvHdl.c | 23 +- source/db2AllocStmtHdl.c | 59 ++-- source/db2AnalyzeForeignTable.c | 17 +- source/db2BeginDirectModify.c | 19 +- source/db2BeginForeignInsert.c | 11 +- source/db2BeginForeignModify.c | 6 +- source/db2BeginForeignModifyCommon.c | 6 +- source/db2BeginForeignScan.c | 19 +- source/db2BindParameter.c | 51 ++-- source/db2Callbacks.c | 19 +- source/db2Cancel.c | 6 +- source/db2CheckErr.c | 17 +- source/db2ClientVersion.c | 9 +- source/db2CloseConnections.c | 43 ++- source/db2CloseStatement.c | 9 +- source/db2CopyText.c | 6 +- source/db2Debug.c | 37 +-- source/db2Describe.c | 35 +-- source/db2EndDirectModify.c | 9 +- source/db2EndForeignInsert.c | 11 +- source/db2EndForeignModify.c | 7 +- source/db2EndForeignModifyCommon.c | 12 +- source/db2EndForeignScan.c | 6 +- source/db2EndSubtransaction.c | 9 +- source/db2EndTransaction.c | 25 +- source/db2ExecForeignBatchInsert.c | 8 +- source/db2ExecForeignDelete.c | 62 ++-- source/db2ExecForeignInsert.c | 9 +- source/db2ExecForeignTruncate.c | 13 +- source/db2ExecForeignUpdate.c | 9 +- source/db2ExecuteInsert.c | 17 +- source/db2ExecuteQuery.c | 27 +- source/db2ExecuteTruncate.c | 9 +- source/db2ExplainForeignModify.c | 9 +- source/db2ExplainForeignScan.c | 15 +- source/db2FetchNext.c | 6 +- source/db2FreeEnvHdl.c | 69 +++-- source/db2FreeStmtHdl.c | 23 +- source/db2GetFdwState.c | 89 +++--- source/db2GetForeignJoinPaths.c | 17 +- source/db2GetForeignModifyBatchSize.c | 11 +- source/db2GetForeignPaths.c | 11 +- source/db2GetForeignPlan.c | 89 +++--- source/db2GetForeignPlanOld.c | 3 - source/db2GetForeignRelSize.c | 39 ++- source/db2GetForeignUpperPaths.c | 199 ++++++------- source/db2GetLob.c | 43 ++- source/db2GetSession.c | 11 +- source/db2GetShareFileName.c | 6 +- source/db2ImportForeignSchema.c | 49 ++- source/db2ImportForeignSchemaData.c | 119 ++++---- source/db2IsForeignRelUpdatable.c | 6 +- source/db2IsStatementOpen.c | 6 +- source/db2IterateDirectModify.c | 7 +- source/db2IterateForeignScan.c | 21 +- source/db2PlanDirectModify.c | 35 +-- source/db2PlanForeignModify.c | 65 ++-- source/db2PrepareQuery.c | 67 ++--- source/db2ReAllocFree.c | 19 +- source/db2ReScanForeignScan.c | 6 +- source/db2ServerVersion.c | 9 +- source/db2SetHandlers.c | 10 +- source/db2SetSavepoint.c | 15 +- source/db2Shutdown.c | 6 +- source/db2_de_serialize.c | 203 +++++++------ source/db2_deparse.c | 410 +++++++++++++------------- source/db2_fdw_utils.c | 99 +++---- source/db2_utils.c | 19 +- 71 files changed, 1174 insertions(+), 1344 deletions(-) diff --git a/include/db2_fdw.h b/include/db2_fdw.h index c6d8113..1fd738c 100644 --- a/include/db2_fdw.h +++ b/include/db2_fdw.h @@ -196,4 +196,40 @@ typedef enum { CASE_KEEP, CASE_LOWER, CASE_SMART } fold_t; #define serializeInt(x) makeConst(INT4OID, -1, InvalidOid, 4, Int32GetDatum((int32)(x)), false, true) #define serializeOid(x) makeConst(OIDOID, -1, InvalidOid, 4, ObjectIdGetDatum(x), false, true) +extern void db2EntryExit(int level, int entry, const char* message, ...); +extern void db2Debug (int level, const char* message, ...); + +#define db2Entry1(fmt, ...) \ + db2EntryExit(1, 1, "> %s:%d:%s" fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) +#define db2Entry2(fmt, ...) \ + db2EntryExit(2, 1, "> %s:%d:%s" fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) +#define db2Entry3(fmt, ...) \ + db2EntryExit(3, 1, "> %s:%d:%s" fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) +#define db2Entry4(fmt, ...) \ + db2EntryExit(4, 1, "> %s:%d:%s" fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) +#define db2Entry5(fmt, ...) \ + db2EntryExit(5, 1, "> %s:%d:%s" fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) + +#define db2Exit1(fmt, ...) \ + db2EntryExit(1, 0, "< %s:%d:%s" fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) +#define db2Exit2(fmt, ...) \ + db2EntryExit(2, 0, "< %s:%d:%s" fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) +#define db2Exit3(fmt, ...) \ + db2EntryExit(3, 0, "< %s:%d:%s" fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) +#define db2Exit4(fmt, ...) \ + db2EntryExit(4, 0, "< %s:%d:%s" fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) +#define db2Exit5(fmt, ...) \ + db2EntryExit(5, 0, "< %s:%d:%s" fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) + +#define db2Debug1(fmt, ...) \ + db2Debug(1, "1 %s:%d:%s " fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) +#define db2Debug2(fmt, ...) \ + db2Debug(2, "2 %s:%d:%s " fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) +#define db2Debug3(fmt, ...) \ + db2Debug(3, "3 %s:%d:%s " fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) +#define db2Debug4(fmt, ...) \ + db2Debug(4, "4 %s:%d:%s " fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) +#define db2Debug5(fmt, ...) \ + db2Debug(5, "5 %s:%d:%s " fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) + #endif \ No newline at end of file diff --git a/source/db2AddForeignUpdateTargets.c b/source/db2AddForeignUpdateTargets.c index db5bf83..9b8f669 100644 --- a/source/db2AddForeignUpdateTargets.c +++ b/source/db2AddForeignUpdateTargets.c @@ -9,9 +9,6 @@ #include "db2_fdw.h" /** external prototypes */ -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); -extern void db2Debug (int level, const char* message, ...); extern char* db2strdup (const char* source); extern bool optionIsTrue (const char* value); @@ -22,8 +19,8 @@ void db2AddForeignUpdateTargets(Query* parsetree, RangeTblEntry* target_rte, Rel void db2AddForeignUpdateTargets(PlannerInfo* root, Index rtindex, RangeTblEntry* target_rte, Relation target_relation); #endif -/** db2AddForeignUpdateTargets - * Add the primary key columns as resjunk entries. +/* db2AddForeignUpdateTargets + * Add the primary key columns as resjunk entries. */ #if PG_VERSION_NUM < 140000 void db2AddForeignUpdateTargets (Query* parsetree,RangeTblEntry* target_rte, Relation target_relation){ @@ -34,8 +31,8 @@ void db2AddForeignUpdateTargets (PlannerInfo* root, Index rtindex,RangeTblEntry* TupleDesc tupdesc = target_relation->rd_att; int i = 0; bool has_key = false; - db2Entry(1,"> db2AddForeignUpdateTargets.c::db2AddForeignUpdateTargets"); - db2Debug(2,"add target columns for update on %d - %s", relid, get_rel_name(relid)); + db2Entry1(); + db2Debug2("add target columns for update on %d - %s", relid, get_rel_name(relid)); /* loop through all columns of the foreign table */ for (i = 0; i < tupdesc->natts; ++i) { Form_pg_attribute att = TupleDescAttr (tupdesc, i); @@ -83,10 +80,10 @@ void db2AddForeignUpdateTargets (PlannerInfo* root, Index rtindex,RangeTblEntry* , att->attcollation , 0 ); - db2Debug(2,"create var rtindex: %d, attrno: %d, typid: %d, typmod: %d, collation: %d",rtindex,attrno,att->atttypid,att->atttypmod,att->attcollation); + db2Debug2("create var rtindex: %d, attrno: %d, typid: %d, typmod: %d, collation: %d",rtindex,attrno,att->atttypid,att->atttypmod,att->attcollation); /* Register it as a required row-identity column. The name becomes the resjunk column name in the plan. */ add_row_identity_var(root, var, rtindex, key_col_name); - db2Debug(2,"add resjunk column %s: %d", key_col_name, rtindex); + db2Debug2("add resjunk column %s: %d", key_col_name, rtindex); #endif /* PG_VERSION_NUM */ has_key = true; } @@ -102,5 +99,5 @@ void db2AddForeignUpdateTargets (PlannerInfo* root, Index rtindex,RangeTblEntry* ) ); } - db2Exit(1,"< db2AddForeignUpdateTargets.c::db2AddForeignUpdateTargets"); + db2Exit1(); } diff --git a/source/db2AllocConnHdl.c b/source/db2AllocConnHdl.c index b392578..af22c02 100644 --- a/source/db2AllocConnHdl.c +++ b/source/db2AllocConnHdl.c @@ -8,9 +8,6 @@ extern char db2Message[ERRBUFSIZE];/* contains DB2 error messages, set by db2CheckErr() */ /** external prototypes */ -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); -extern void db2Debug (int level, const char* message, ...); extern void db2Error_d (db2error sqlstate, const char* message, const char* detail, ...); extern void db2RegisterCallback (void* arg); extern SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleType, int line, char* file); @@ -28,10 +25,10 @@ DB2ConnEntry* db2AllocConnHdl(DB2EnvEntry* envp,const char* srvname, char* user, SQLRETURN rc = 0; SQLHDBC hdbc = SQL_NULL_HDBC; - db2Entry(1,"> db2AllocConnHdl.c::db2AllocConnHdl(envp: %x, srvname: %s, user: %s, password: %s, jwt_token: %s, nls_lang: %s)", envp, srvname,user, password, jwt_token ? "***" : "NULL", nls_lang); + db2Entry1("(envp: %x, srvname: %s, user: %s, password: %s, jwt_token: %s, nls_lang: %s)", envp, srvname,user, password, jwt_token ? "***" : "NULL", nls_lang); if (nls_lang != NULL) { rc = SQLAllocHandle(SQL_HANDLE_DBC, envp->henv, &hdbc); - db2Debug(3,"alloc dbc handle - rc: %d, henv: %d, hdbc: %d",rc, envp->henv, hdbc); + db2Debug3("alloc dbc handle - rc: %d, henv: %d, hdbc: %d",rc, envp->henv, hdbc); rc = db2CheckErr(rc, hdbc, SQL_HANDLE_DBC, __LINE__, __FILE__); if (rc != SQL_SUCCESS) { db2Error_d (FDW_UNABLE_TO_ESTABLISH_CONNECTION, "error connecting to DB2: SQLAllocHandle failed to allocate hdbc handle", db2Message); @@ -51,7 +48,7 @@ DB2ConnEntry* db2AllocConnHdl(DB2EnvEntry* envp,const char* srvname, char* user, /* create connection handle */ rc = SQLAllocHandle(SQL_HANDLE_DBC, envp->henv, &hdbc); - db2Debug(3,"alloc dbc handle - rc: %d, henv: %d, hdbc: %d",rc, envp->henv, hdbc); + db2Debug3("alloc dbc handle - rc: %d, henv: %d, hdbc: %d",rc, envp->henv, hdbc); rc = db2CheckErr(rc, envp->henv, SQL_HANDLE_ENV, __LINE__, __FILE__); if (rc != SQL_SUCCESS) { db2Error_d (FDW_UNABLE_TO_ESTABLISH_CONNECTION, "error connecting to DB2: SQLAllochHandle failed to allocate hdbc handle", db2Message); @@ -60,7 +57,7 @@ DB2ConnEntry* db2AllocConnHdl(DB2EnvEntry* envp,const char* srvname, char* user, /* Check if JWT token authentication is used */ if (jwt_token != NULL && jwt_token[0] != '\0') { /* JWT token authentication */ - db2Debug(2,"using JWT token authentication"); + db2Debug2("using JWT token authentication"); /* For DB2 11.5.4+ with JWT, use SQLDriverConnect with AUTHENTICATION=TOKEN */ /* Requires: DB2 client 11.5.4+, server configured with db2token.cfg */ @@ -76,14 +73,14 @@ DB2ConnEntry* db2AllocConnHdl(DB2EnvEntry* envp,const char* srvname, char* user, db2Error_d (FDW_UNABLE_TO_ESTABLISH_CONNECTION, "connection string too long", " connection to foreign DB2 server"); } - db2Debug(2,"connecting with connection string (token hidden)"); + db2Debug2("connecting with connection string (token hidden)"); /* Use SQLDriverConnect instead of SQLConnect */ rc = SQLDriverConnect(hdbc, NULL, (SQLCHAR*)connStr, SQL_NTS, outConnStr, sizeof(outConnStr), &outConnStrLen, SQL_DRIVER_NOPROMPT); - db2Debug(2,"connect to database(%s) with JWT token - rc: %d, hdbc: %d", srvname, rc, hdbc); + db2Debug2("connect to database(%s) with JWT token - rc: %d, hdbc: %d", srvname, rc, hdbc); rc = db2CheckErr(rc, hdbc, SQL_HANDLE_DBC, __LINE__, __FILE__); if (rc != SQL_SUCCESS) { db2Error_d (FDW_UNABLE_TO_ESTABLISH_CONNECTION, "cannot authenticate with JWT token", " connection connectstring: %s ,%s", srvname, db2Message); @@ -91,9 +88,9 @@ DB2ConnEntry* db2AllocConnHdl(DB2EnvEntry* envp,const char* srvname, char* user, } } else { /* Traditional user/password authentication */ - db2Debug(2,"using user/password authentication"); + db2Debug2("using user/password authentication"); rc = SQLConnect(hdbc, (SQLCHAR*)srvname, SQL_NTS, (SQLCHAR*)user, SQL_NTS, (SQLCHAR*)password, SQL_NTS); - db2Debug(2,"connect to database(%s) - rc: %d, hdbc: %d",srvname, rc, hdbc); + db2Debug2("connect to database(%s) - rc: %d, hdbc: %d",srvname, rc, hdbc); rc = db2CheckErr(rc, hdbc, SQL_HANDLE_DBC, __LINE__, __FILE__); if (rc != SQL_SUCCESS) { db2Error_d (FDW_UNABLE_TO_ESTABLISH_CONNECTION, "cannot authenticate"," connection User: %s ,%s" , user , db2Message); @@ -118,14 +115,14 @@ DB2ConnEntry* db2AllocConnHdl(DB2EnvEntry* envp,const char* srvname, char* user, } } } - db2Exit(1,"< db2AllocConnHdl.c::db2AllocConnHdl : %x",connp); + db2Exit1(": %x",connp); return connp; } /* findconnEntry */ DB2ConnEntry* findconnEntry(DB2ConnEntry* start, const char* srvname, const char* user, const char* jwttok) { DB2ConnEntry* step = NULL; - db2Entry(2,"> db2AllocConnHdl.c::findconnEntry"); + db2Entry2(); for (step = start; step != NULL; step = step->right){ /* NULL-safe comparison for JWT auth where user may be NULL */ int jwt_null_or_empty = (!step->jwt_token || step->jwt_token[0] == '\0'); @@ -147,7 +144,7 @@ DB2ConnEntry* findconnEntry(DB2ConnEntry* start, const char* srvname, const char break; } } - db2Exit(2,"< db2AllocConnHdl.c::findconnEntry : %x", step); + db2Exit2(": %x", step); return step; } @@ -156,7 +153,7 @@ static DB2ConnEntry* insertconnEntry(DB2ConnEntry* start, const char* srvname, c DB2ConnEntry* step = NULL; DB2ConnEntry* new = NULL; - db2Entry(2,"> db2AllocConnHdl.c::insertconnEntry"); + db2Entry2(); new = malloc(sizeof(DB2ConnEntry)); if (start == NULL){ /* first entry in list */ new->right = new->left = NULL; @@ -174,6 +171,6 @@ static DB2ConnEntry* insertconnEntry(DB2ConnEntry* start, const char* srvname, c new->handlelist = NULL; new->hdbc = hdbc; new->xact_level = 0; - db2Exit(2,"< db2AllocConnHdl.c::insertconnEntry : %x",new); + db2Exit2(": %x",new); return new; } diff --git a/source/db2AllocEnvHdl.c b/source/db2AllocEnvHdl.c index 7195a8d..d70a306 100644 --- a/source/db2AllocEnvHdl.c +++ b/source/db2AllocEnvHdl.c @@ -11,9 +11,6 @@ int sql_initialized = 0; /* set to "1" as soon as SQLAllocHand extern char db2Message[ERRBUFSIZE];/* contains DB2 error messages, set by db2CheckErr() */ /** external prototypes */ -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); -extern void db2Debug (int level, const char* message, ...); extern void db2SetHandlers (void); extern void db2Error_d (db2error sqlstate, const char* message, const char* detail, ...); extern SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleType, int line, char* file); @@ -32,7 +29,7 @@ DB2EnvEntry* db2AllocEnvHdl(const char* nls_lang){ SQLHENV henv = SQL_NULL_HENV; SQLRETURN rc = 0; - db2Entry(1,"> db2AllocEnvHdl.c::db2AllocEnvHdl"); + db2Entry1(); /* create persistent copy of "nls_lang" */ if ((nlscopy = db2strdup (nls_lang)) == NULL) db2Error_d (FDW_OUT_OF_MEMORY, "error connecting to DB2:"," failed to allocate %d bytes of memory", strlen (nls_lang) + 1); @@ -42,7 +39,7 @@ DB2EnvEntry* db2AllocEnvHdl(const char* nls_lang){ /* create environment handle */ rc = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv); - db2Debug(3,"allocate env handle - rc: %d, henv: %d",rc, henv); + db2Debug3("allocate env handle - rc: %d, henv: %d",rc, henv); rc = db2CheckErr(rc, henv, SQL_HANDLE_ENV, __LINE__, __FILE__); if (rc != SQL_SUCCESS) { db2free (nlscopy); @@ -51,10 +48,10 @@ DB2EnvEntry* db2AllocEnvHdl(const char* nls_lang){ /* we can call db2Shutdown now */ sql_initialized = 1; - db2Debug(3,"sql_initialized: %d",sql_initialized); + db2Debug3("sql_initialized: %d",sql_initialized); rc = SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3, 0); - db2Debug(3,"set env attributes odbcv3 - rc: %d, henv: %d",rc, henv); + db2Debug3("set env attributes odbcv3 - rc: %d, henv: %d",rc, henv); rc = db2CheckErr(rc, henv, SQL_HANDLE_ENV, __LINE__, __FILE__); if (rc != SQL_SUCCESS) { db2free (nlscopy); @@ -71,7 +68,7 @@ DB2EnvEntry* db2AllocEnvHdl(const char* nls_lang){ rootenvEntry = envp; } - db2Exit(1,"< db2AllocEnvHdl.c::db2AllocEnvHdl - returns: %x",envp); + db2Exit1(": %x",envp); return envp; } @@ -91,7 +88,7 @@ DB2EnvEntry* db2AllocEnvHdl(const char* nls_lang){ * NLS_CALENDAR */ static void setDB2Environment (char* nls_lang) { - db2Entry(4,"> db2AllocEnvHdl.c::setDB2Environment"); + db2Entry4(); if (putenv (nls_lang) != 0) { db2free (nls_lang); db2Error_d (FDW_UNABLE_TO_ESTABLISH_CONNECTION, "error connecting to DB2", "Environment variable NLS_LANG cannot be set."); @@ -133,7 +130,7 @@ static void setDB2Environment (char* nls_lang) { db2free (nls_lang); db2Error_d (FDW_UNABLE_TO_ESTABLISH_CONNECTION, "error connecting to DB2", "Environment variable NLS_NCHAR cannot be set."); } - db2Exit(4,"< db2AllocEnvHdl.c::setDB2Environment"); + db2Exit4(); } /* insertenvEntry */ @@ -141,7 +138,7 @@ static DB2EnvEntry* insertenvEntry(DB2EnvEntry* start, const char* nlslang, SQLH DB2EnvEntry* step = NULL; DB2EnvEntry* new = NULL; - db2Entry(2,"> db2AllocEnvHdl.c::insertenvEntry(start: %x, nlslang: '%s', henv: %d)",start, nlslang, henv); + db2Entry2("(start: %x, nlslang: '%s', henv: %d)",start, nlslang, henv); /* allocate a new DB2EnvEntry and initialize it*/ new = malloc(sizeof(DB2EnvEntry)); if (new == NULL) { @@ -161,7 +158,7 @@ static DB2EnvEntry* insertenvEntry(DB2EnvEntry* start, const char* nlslang, SQLH new->left = step; new->right = NULL; } - db2Debug(3,"new: %x ->henv: %d, ->connlist: %x, ->left: %x, ->right: %x, ->nls_lang: '%s'",new,new->henv,new->connlist,new->left,new->right,new->nls_lang); - db2Exit(2,"< db2AllocEnvHdl.c::insertenvEntry : %x", new); + db2Debug3("new: %x ->henv: %d, ->connlist: %x, ->left: %x, ->right: %x, ->nls_lang: '%s'",new,new->henv,new->connlist,new->left,new->right,new->nls_lang); + db2Exit2(": %x", new); return new; } diff --git a/source/db2AllocStmtHdl.c b/source/db2AllocStmtHdl.c index 0d83ab1..400e00b 100644 --- a/source/db2AllocStmtHdl.c +++ b/source/db2AllocStmtHdl.c @@ -16,9 +16,6 @@ extern DB2EnvEntry* rootenvEntry; /* Linked list of handles for cached /** external prototypes */ extern int isLogLevel (int level); -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); -extern void db2Debug (int level, const char* message, ...); extern void db2Error (db2error sqlstate, const char* message); extern void db2Error_d (db2error sqlstate, const char* message, const char* detail, ...); @@ -32,34 +29,34 @@ HdlEntry* db2AllocStmtHdl (SQLSMALLINT type, DB2ConnEntry* connp, db2error error HdlEntry* entry = NULL; SQLRETURN rc = 0; - db2Entry(1,"> db2AllocStmtHdl.c::db2AllocStmtHdl"); + db2Entry1(); if (isLogLevel(5)) { DB2EnvEntry* envstep = NULL; DB2ConnEntry* constep = NULL; HdlEntry* hdlstep = NULL; - db2Debug(5,"struct before calling pthread_create getpid: %d getpthread_self: %d", getpid(), (int)pthread_self()); + db2Debug5("struct before calling pthread_create getpid: %d getpthread_self: %d", getpid(), (int)pthread_self()); for (envstep = rootenvEntry; envstep != NULL; envstep = envstep->right){ - db2Debug(5,"EnvEntry : %x",envstep); - db2Debug(5," nls_lang : %s",envstep->nls_lang); - db2Debug(5," step->henv : %x",envstep->henv); - db2Debug(5," step->*left : %x",envstep->left); - db2Debug(5," step->*right : %x",envstep->right); - db2Debug(5," step->*connlist : %x",envstep->connlist); + db2Debug5("EnvEntry : %x",envstep); + db2Debug5(" nls_lang : %s",envstep->nls_lang); + db2Debug5(" step->henv : %x",envstep->henv); + db2Debug5(" step->*left : %x",envstep->left); + db2Debug5(" step->*right : %x",envstep->right); + db2Debug5(" step->*connlist : %x",envstep->connlist); for (constep = envstep->connlist; constep != NULL; constep = constep->right){ - db2Debug(5," ConnEntr : %x",constep); - db2Debug(5," dbAlias : %s",constep->srvname); - db2Debug(5," user : %s",constep->uid); - db2Debug(5," password : %s",constep->pwd); - db2Debug(5," xact_level : %d",constep->xact_level); - db2Debug(5," conattr : %d",constep->conAttr); - db2Debug(5," *handlelist : %x",constep->handlelist); - db2Debug(5," DB2ConnEntry *left : %x",constep->left); - db2Debug(5," Db2ConnEntry *right : %x",constep->right); + db2Debug5(" ConnEntr : %x",constep); + db2Debug5(" dbAlias : %s",constep->srvname); + db2Debug5(" user : %s",constep->uid); + db2Debug5(" password : %s",constep->pwd); + db2Debug5(" xact_level : %d",constep->xact_level); + db2Debug5(" conattr : %d",constep->conAttr); + db2Debug5(" *handlelist : %x",constep->handlelist); + db2Debug5(" DB2ConnEntry *left : %x",constep->left); + db2Debug5(" Db2ConnEntry *right : %x",constep->right); for (hdlstep = constep->handlelist; hdlstep != NULL; hdlstep = hdlstep->next){ - db2Debug(5," HandleEntry : %x",hdlstep); - db2Debug(5," hsql : %d",hdlstep->hsql); - db2Debug(5," type : %d",hdlstep->type); + db2Debug5(" HandleEntry : %x",hdlstep); + db2Debug5(" hsql : %d",hdlstep->hsql); + db2Debug5(" type : %d",hdlstep->type); } } } @@ -68,24 +65,24 @@ HdlEntry* db2AllocStmtHdl (SQLSMALLINT type, DB2ConnEntry* connp, db2error error if ((entry = malloc (sizeof (HdlEntry))) == NULL) { db2Error_d (FDW_OUT_OF_MEMORY, "error allocating handle:"," failed to allocate %d bytes of memory", sizeof (HdlEntry)); } - db2Debug(2,"HdlEntry allocated: %x",entry); + db2Debug2("HdlEntry allocated: %x",entry); rc = SQLAllocHandle(type, connp->hdbc, &(entry->hsql)); if (rc != SQL_SUCCESS) { - db2Debug(3,"SQLAllocHandle not SQL_SUCCESS: %d",rc); - db2Debug(2,"HdlEntry freeed: %x",entry); + db2Debug3("SQLAllocHandle not SQL_SUCCESS: %d",rc); + db2Debug2("HdlEntry freeed: %x",entry); free (entry); entry = NULL; db2Error (error, errmsg); } else { /* add handle to linked list */ - db2Debug(3,"entry->hsql: %d",entry->hsql); + db2Debug3("entry->hsql: %d",entry->hsql); entry->type = type; - db2Debug(3,"entry->type: %d",entry->type); + db2Debug3("entry->type: %d",entry->type); entry->next = connp->handlelist; - db2Debug(3,"adding connp->handlelist: %x to entry->next: %x",connp->handlelist, entry->next); + db2Debug3("adding connp->handlelist: %x to entry->next: %x",connp->handlelist, entry->next); connp->handlelist = entry; - db2Debug(3,"set entry %x to start connp->handlelist: %x",entry,connp->handlelist); + db2Debug3("set entry %x to start connp->handlelist: %x",entry,connp->handlelist); } - db2Exit(1,"< db2AllocStmtHdl.c::db2AllocStmtHdl : %x",entry); + db2Exit1(": %x",entry); return entry; } diff --git a/source/db2AnalyzeForeignTable.c b/source/db2AnalyzeForeignTable.c index 6701db8..74873c3 100644 --- a/source/db2AnalyzeForeignTable.c +++ b/source/db2AnalyzeForeignTable.c @@ -16,9 +16,6 @@ extern int db2FetchNext (DB2Session* session); extern void checkDataType (short db2type, int scale, Oid pgtype, const char* tablename, const char* colname); extern short c2dbType (short fcType); extern void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls, bool trunc_lob) ; -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); -extern void db2Debug (int level, const char* message, ...); extern void* db2alloc (const char* type, size_t size); /** local prototypes */ @@ -27,11 +24,11 @@ static int acquireSampleRowsFunc (Relation relation, int elevel, HeapTuple* row /* db2AnalyzeForeignTable */ bool db2AnalyzeForeignTable (Relation relation, AcquireSampleRowsFunc* func, BlockNumber* totalpages) { - db2Entry(1,"> db2AnalyzeForeignTable.c::db2AnalyzeForeignTable"); + db2Entry1(); *func = acquireSampleRowsFunc; /* use positive page count as a sign that the table has been ANALYZEd */ *totalpages = 42; - db2Exit(1,"< db2AnalyzeForeignTable.c::db2AnalyzeForeignTable : true"); + db2Exit1(": true"); return true; } @@ -53,8 +50,8 @@ static int acquireSampleRowsFunc (Relation relation, int elevel, HeapTuple* rows MemoryContext old_cxt; MemoryContext tmp_cxt; - db2Entry(1,"> db2AnalyzeForeignTable.c::acquireSampleRowsFunc"); - db2Debug(2,"db2_fdw: analyze foreign table %d", RelationGetRelid (relation)); + db2Entry1(); + db2Debug2("db2_fdw: analyze foreign table %d", RelationGetRelid (relation)); *totalrows = 0; @@ -97,9 +94,9 @@ static int acquireSampleRowsFunc (Relation relation, int elevel, HeapTuple* rows appendStringInfo (&query, " SAMPLE BLOCK (%f)", sample_percent); fdw_state->query = query.data; - db2Debug(2,"fdw_state->query: '%s'", fdw_state->query); + db2Debug2("fdw_state->query: '%s'", fdw_state->query); - db2Debug(3,"loop through query results"); + db2Debug3("loop through query results"); /* loop through query results */ fdw_state->rowcount = -1; while (db2IsStatementOpen (fdw_state->session) ? db2FetchNext (fdw_state->session) : (db2PrepareQuery (fdw_state->session, fdw_state->query, fdw_state->resultList, fdw_state->prefetch, fdw_state->fetch_size), db2ExecuteQuery (fdw_state->session, fdw_state->paramList))) { @@ -148,7 +145,7 @@ static int acquireSampleRowsFunc (Relation relation, int elevel, HeapTuple* rows /* report report */ ereport (elevel, (errmsg ("\"%s\": table contains %lu rows; %d rows in sample", RelationGetRelationName (relation), fdw_state->rowcount, collected_rows-1))); - db2Exit(1,"< db2AnalyzeForeignTable.c::acquireSampleRowsFunc"); + db2Exit1(); return collected_rows; } diff --git a/source/db2BeginDirectModify.c b/source/db2BeginDirectModify.c index 99e1792..e926f01 100644 --- a/source/db2BeginDirectModify.c +++ b/source/db2BeginDirectModify.c @@ -8,9 +8,6 @@ #include "db2_fdw.h" #include "DB2FdwDirectModifyState.h" -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); -extern void db2Debug (int level, const char* message, ...); extern void* db2alloc (const char* type, size_t size); extern DB2Session* db2GetSession (const char* connectstring, char* user, char* password, char* jwt_token, const char* nls_lang, int curlevel); extern DB2FdwDirectModifyState* db2GetFdwDirectModifyState(Oid foreigntableid, double* sample_percent, bool describe); @@ -30,7 +27,7 @@ void db2BeginDirectModify(ForeignScanState* node, int eflags) { Index rtindex; Relation foreigntable; - db2Entry(1,"> db2BeginDirectModify.c::db2BeginDirectModify"); + db2Entry1(); /* Do nothing in EXPLAIN (no ANALYZE) case. node->fdw_state stays NULL. */ if (!(eflags & EXEC_FLAG_EXPLAIN_ONLY)) { /* Get info about foreign table. */ @@ -95,7 +92,7 @@ void db2BeginDirectModify(ForeignScanState* node, int eflags) { ); } } - db2Exit(1,"< db2BeginDirectModify.c::db2BeginDirectModify"); + db2Exit1(); } /* @@ -106,7 +103,7 @@ static TupleDesc get_tupdesc_for_join_scan_tuples(ForeignScanState *node) { EState *estate = node->ss.ps.state; TupleDesc tupdesc; - db2Entry(4,"> db2BeginDirectModify.c::get_tupdesc_for_join_scan_tuples"); + db2Entry4(); /* The core code has already set up a scan tuple slot based on fsplan->fdw_scan_tlist, and this slot's tupdesc is mostly good enough, but there's one case where it isn't. * If we have any whole-row row identifier Vars, they may have vartype RECORD, and we need to replace that with the associated table's actual composite type. * This ensures that when we read those ROW() expression values from the remote server, we can convert them to a composite type the local server knows. @@ -135,7 +132,7 @@ static TupleDesc get_tupdesc_for_join_scan_tuples(ForeignScanState *node) { att->atttypid = reltype; /* shouldn't need to change anything else */ } - db2Exit(4,"< db2BeginDirectModify.c::get_tupdesc_for_join_scan_tuples"); + db2Exit4(); return tupdesc; } @@ -145,7 +142,7 @@ static void init_returning_filter(DB2FdwDirectModifyState* dmstate, List* fdw_sc ListCell* lc = NULL; int i = 0; - db2Entry(4,"< db2BeginDirectModify.c::init_returning_filter"); + db2Entry4(); /* Calculate the mapping between the fdw_scan_tlist's entries and the result tuple's attributes. * * The "map" is an array of indexes of the result tuple's attributes in fdw_scan_tlist, i.e., one entry for every attribute @@ -184,7 +181,7 @@ static void init_returning_filter(DB2FdwDirectModifyState* dmstate, List* fdw_sc } i++; } - db2Exit(4,"< db2BeginDirectModify.c::init_returning_filter"); + db2Exit4(); } /* Prepare for processing of parameters used in remote query. */ @@ -192,7 +189,7 @@ static void prepare_query_params(PlanState* node, List* fdw_exprs, int numParams int i = 0; ListCell* lc = NULL; - db2Entry(4,"> db2BeginDirectModify.c::prepare_query_params"); + db2Entry4(); Assert(numParams > 0); /* Prepare for output conversion of parameters used in remote query. */ @@ -219,6 +216,6 @@ static void prepare_query_params(PlanState* node, List* fdw_exprs, int numParams /* Allocate buffer for text form of query parameters. */ *param_values = (const char **) db2alloc("prepare_query_params::param_values",numParams * sizeof(char *)); - db2Exit(4,"< db2BeginDirectModify.c::prepare_query_params"); + db2Exit4(); } diff --git a/source/db2BeginForeignInsert.c b/source/db2BeginForeignInsert.c index 888c5aa..7f13d98 100644 --- a/source/db2BeginForeignInsert.c +++ b/source/db2BeginForeignInsert.c @@ -13,9 +13,6 @@ extern DB2FdwState* db2GetFdwState (Oid foreigntableid, double* sample_percent, bool describe); extern void addParam (ParamDesc** paramList, DB2Column* db2col, int colnum, int txts); extern void checkDataType (short db2type, int scale, Oid pgtype, const char* tablename, const char* colname); -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); -extern void db2Debug (int level, const char* message, ...); extern void appendAsType (StringInfoData* dest, Oid type); extern void db2BeginForeignModifyCommon(ModifyTableState* mtstate, ResultRelInfo* rinfo, DB2FdwState* fdw_state, Plan* subplan); @@ -27,11 +24,11 @@ void db2BeginForeignInsert(ModifyTableState* mtstate, ResultRelInfo* rinfo) { Relation rel = rinfo->ri_RelationDesc; DB2FdwState* fdw_state = NULL; - db2Entry(1," db2BeginForeignInsert.c::db2BeginForeignInsert"); + db2Entry1(); fdw_state = db2BuildInsertFdwState(rel); /* subplan is irrelevant for pure INSERT/COPY */ db2BeginForeignModifyCommon(mtstate, rinfo, fdw_state, NULL); - db2Exit(1,"< db2BeginForeignInsert.c::db2BeginForeignInsert"); + db2Exit1(); } DB2FdwState* db2BuildInsertFdwState(Relation rel) { @@ -40,7 +37,7 @@ DB2FdwState* db2BuildInsertFdwState(Relation rel) { int i; bool firstcol; - db2Entry(1," db2BeginForeignInsert.c::db2BuildInsertFdwState"); + db2Entry1(); /* Same logic as CMD_INSERT branch of db2PlanForeignModify: */ fdwState = db2GetFdwState(RelationGetRelid(rel), NULL, true); initStringInfo(&sql); @@ -73,6 +70,6 @@ DB2FdwState* db2BuildInsertFdwState(Relation rel) { appendStringInfo(&sql, ")"); fdwState->query = sql.data; db2Debug(2,"fdwState->query: '%s'",sql.data); - db2Exit(1,"< db2BeginForeignInsert.c::db2BuildInsertFdwState - returns fdwState: %x",fdwState); + db2Exit1(": %x",fdwState); return fdwState; } \ No newline at end of file diff --git a/source/db2BeginForeignModify.c b/source/db2BeginForeignModify.c index 009892d..e14a3e7 100644 --- a/source/db2BeginForeignModify.c +++ b/source/db2BeginForeignModify.c @@ -4,8 +4,6 @@ #include "DB2FdwState.h" /** external prototypes */ -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); extern void db2BeginForeignModifyCommon(ModifyTableState* mtstate, ResultRelInfo* rinfo, DB2FdwState* fdw_state, Plan* subplan); extern DB2FdwState* deserializePlanData (List* list); @@ -20,7 +18,7 @@ void db2BeginForeignModify (ModifyTableState* mtstate, ResultRelInfo* rinfo, Lis DB2FdwState* fdw_state = deserializePlanData (fdw_private); Plan *subplan = NULL; - db2Entry(1,"> db2BeginForeignModify.c::db2BeginForeignModify"); + db2Entry1(); #if PG_VERSION_NUM < 140000 subplan = mtstate->mt_plans[subplan_index]->plan; #else @@ -28,5 +26,5 @@ void db2BeginForeignModify (ModifyTableState* mtstate, ResultRelInfo* rinfo, Lis #endif db2BeginForeignModifyCommon(mtstate, rinfo, fdw_state, subplan); - db2Exit(1,"< db2BeginForeignModify.c::db2BeginForeignModify"); + db2Exit1(); } \ No newline at end of file diff --git a/source/db2BeginForeignModifyCommon.c b/source/db2BeginForeignModifyCommon.c index ab0483a..296d615 100644 --- a/source/db2BeginForeignModifyCommon.c +++ b/source/db2BeginForeignModifyCommon.c @@ -15,8 +15,6 @@ extern regproc* output_funcs; /** external prototypes */ extern DB2Session* db2GetSession (const char* connectstring, char* user, char* password, char* jwt_token, const char* nls_lang, int curlevel); extern void db2PrepareQuery (DB2Session* session, const char *query, DB2ResultColumn* db2ResultList, unsigned long prefetch, int fetchsize); -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); extern void* db2alloc (const char* type, size_t size); /** local prototypes */ @@ -28,7 +26,7 @@ void db2BeginForeignModifyCommon(ModifyTableState* mtstate, ResultRelInfo* rinfo HeapTuple tuple; int i; - db2Entry(1,"> db2BeginForeignModifyCommon.c::db2BeginForeignModifyCommon"); + db2Entry1(); rinfo->ri_FdwState = fdw_state; /* connect to DB2 database */ @@ -60,5 +58,5 @@ void db2BeginForeignModifyCommon(ModifyTableState* mtstate, ResultRelInfo* rinfo /* create a memory context for short-lived memory */ fdw_state->temp_cxt = AllocSetContextCreate(estate->es_query_cxt, "db2_fdw temporary data", ALLOCSET_SMALL_MINSIZE, ALLOCSET_SMALL_INITSIZE, ALLOCSET_SMALL_MAXSIZE); - db2Exit(1,"< db2BeginForeignModifyCommon.c::db2BeginForeignModifyCommon"); + db2Exit1(); } diff --git a/source/db2BeginForeignScan.c b/source/db2BeginForeignScan.c index a634f37..0b95c85 100644 --- a/source/db2BeginForeignScan.c +++ b/source/db2BeginForeignScan.c @@ -11,9 +11,6 @@ extern DB2Session* db2GetSession (const char* connectstring, char* user, char* password, char* jwt_token, const char* nls_lang, int curlevel); extern void* db2alloc (const char* type, size_t size); extern DB2FdwState* deserializePlanData (List* list); -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); -extern void db2Debug (int level, const char* message, ...); /** local prototypes */ void db2BeginForeignScan (ForeignScanState* node, int eflags); @@ -27,7 +24,7 @@ void db2BeginForeignScan(ForeignScanState* node, int eflags) { ForeignScan* fsplan = (ForeignScan*) node->ss.ps.plan; DB2FdwState* fdw_state = NULL; - db2Entry(1,"> db2BeginForeignScan.c::db2BeginForeignScan"); + db2Entry1(); /* deserialize private plan data */ fdw_state = deserializePlanData(fsplan->fdw_private); node->fdw_state = (void *) fdw_state; @@ -48,9 +45,9 @@ void db2BeginForeignScan(ForeignScanState* node, int eflags) { } if (node->ss.ss_currentRelation) - db2Debug(3,"begin foreign table scan on relid: %d", RelationGetRelid (node->ss.ss_currentRelation)); + db2Debug3("begin foreign table scan on relid: %d", RelationGetRelid (node->ss.ss_currentRelation)); else - db2Debug(3,"begin foreign join"); + db2Debug3("begin foreign join"); /* connect to DB2 database */ fdw_state->session = db2GetSession (fdw_state->dbserver @@ -63,7 +60,7 @@ void db2BeginForeignScan(ForeignScanState* node, int eflags) { /* initialize row count to zero */ fdw_state->rowcount = 0; - db2Exit(1,"< db2BeginForeignScan.c::db2BeginForeignScan"); + db2Exit1(); } static void addExprParams(ForeignScanState* node){ @@ -73,10 +70,10 @@ static void addExprParams(ForeignScanState* node){ ParamDesc* paramDesc = NULL; ListCell* cell = NULL; - db2Entry(1,"> db2BeginForeignScan.c::addExprParams"); + db2Entry1(); /* create an ExprState tree for the parameter expressions */ exec_exprs = (List*) ExecInitExprList (fsplan->fdw_exprs, (PlanState*) node); - db2Debug(2,"exec_expr: %x[%d]",exec_exprs, list_length(exec_exprs)); + db2Debug2("exec_expr: %x[%d]",exec_exprs, list_length(exec_exprs)); /* create the list of parameters */ foreach (cell, exec_exprs) { ExprState* expr = (ExprState*) lfirst (cell); @@ -107,8 +104,8 @@ static void addExprParams(ForeignScanState* node){ paramDesc->colnum = -1; paramDesc->txts = 0; paramDesc->next = fdw_state->paramList; - db2Debug(2,"paramDesc->colnum: %d ",paramDesc->colnum); + db2Debug2("paramDesc->colnum: %d ",paramDesc->colnum); fdw_state->paramList = paramDesc; } - db2Exit(1,"< db2BeginForeignScan.c::addExprParams"); + db2Exit1(); } diff --git a/source/db2BindParameter.c b/source/db2BindParameter.c index 31420ab..75be289 100644 --- a/source/db2BindParameter.c +++ b/source/db2BindParameter.c @@ -12,9 +12,6 @@ extern char db2Message[ERRBUFSIZE];/* contains DB2 error messages, set b /** external prototypes */ extern void* db2alloc (const char* type, size_t size); -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); -extern void db2Debug (int level, const char* message, ...); extern SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleType, int line, char* file); extern void db2Error_d (db2error sqlstate, const char* message, const char* detail, ...); extern SQLSMALLINT param2c (SQLSMALLINT fcType); @@ -26,21 +23,21 @@ void db2BindParameter (DB2Session* session, ParamDesc* param, SQLLEN* indicator, void db2BindParameter (DB2Session* session, ParamDesc* param, SQLLEN* indicator, int param_count, int col_num) { SQLRETURN rc = 0; - db2Entry(1,"> db2BindParameter.c::db2BindParameter"); - db2Debug(2,"param_count : %d",param_count); - db2Debug(2,"col_num : %d",col_num); - db2Debug(2,"param->value : %s",param->value); - db2Debug(2,"param->colnum : %d",param->colnum); - db2Debug(2,"param->bindType : %d",param->bindType); + db2Entry1(); + db2Debug2("param_count : %d",param_count); + db2Debug2("col_num : %d",col_num); + db2Debug2("param->value : %s",param->value); + db2Debug2("param->colnum : %d",param->colnum); + db2Debug2("param->bindType : %d",param->bindType); if (param->colnum >= 0) { - db2Debug(2,"colName : %s",param->colName); + db2Debug2("colName : %s",param->colName); } switch (param->bindType) { case BIND_NUMBER: { - db2Debug(3,"param->bindType: BIND_NUMBER"); + db2Debug3("param->bindType: BIND_NUMBER"); *indicator = (SQLLEN) ((param->value == NULL) ? SQL_NULL_DATA : 0); - db2Debug(2,"param_ind : %d",*indicator); - db2Debug(2,"colType : %d - %s",param->colType,c2name(param->colType)); + db2Debug2("param_ind : %d",*indicator); + db2Debug2("colType : %d - %s",param->colType,c2name(param->colType)); switch (param->colType) { case SQL_BIGINT:{ char* end = NULL; @@ -48,7 +45,7 @@ void db2BindParameter (DB2Session* session, ParamDesc* param, SQLLEN* indicator, if (param->value != NULL) { sqlbint = db2alloc("SQLBIGINT",sizeof(SQLBIGINT)); *sqlbint = strtoll(param->value,&end,10); - db2Debug(2,"sqlbint: %d",*sqlbint); + db2Debug2("sqlbint: %d",*sqlbint); } rc = SQLBindParameter( session->stmtp->hsql , col_num @@ -69,7 +66,7 @@ void db2BindParameter (DB2Session* session, ParamDesc* param, SQLLEN* indicator, if (param->value != NULL) { sqlsint = db2alloc("SQLSMALLINT",sizeof(SQLSMALLINT)); *sqlsint = strtol(param->value,&end,10); - db2Debug(2,"sqlsint: %d",*sqlsint); + db2Debug2("sqlsint: %d",*sqlsint); } rc = SQLBindParameter( session->stmtp->hsql , col_num @@ -90,7 +87,7 @@ void db2BindParameter (DB2Session* session, ParamDesc* param, SQLLEN* indicator, if (param->value != NULL) { sqlint = db2alloc("SQLINTEGER",sizeof(SQLINTEGER)); *sqlint = strtol(param->value,&end,10); - db2Debug(2,"sqlint: %d",*sqlint); + db2Debug2("sqlint: %d",*sqlint); } rc = SQLBindParameter( session->stmtp->hsql , col_num @@ -115,7 +112,7 @@ void db2BindParameter (DB2Session* session, ParamDesc* param, SQLLEN* indicator, if (param->value != NULL) { num = db2alloc("SQL_NUMERIC_STRUCT",sizeof(SQL_NUMERIC_STRUCT)); parse2num_struct(param->value, num); - db2Debug(2,"num: '%s'",*num); + db2Debug2("num: '%s'",*num); } rc = SQLBindParameter( session->stmtp->hsql , col_num @@ -139,9 +136,9 @@ void db2BindParameter (DB2Session* session, ParamDesc* param, SQLLEN* indicator, } break; case BIND_STRING: { - db2Debug(3,"param->bindType: BIND_STRING"); + db2Debug3("param->bindType: BIND_STRING"); *indicator = (SQLLEN) ((param->value == NULL) ? SQL_NULL_DATA : SQL_NTS); - db2Debug(2,"param_ind : %d",*indicator); + db2Debug2("param_ind : %d",*indicator); rc = SQLBindParameter( session->stmtp->hsql , col_num , SQL_PARAM_INPUT @@ -156,9 +153,9 @@ void db2BindParameter (DB2Session* session, ParamDesc* param, SQLLEN* indicator, } break; case BIND_LONGRAW: { - db2Debug(3,"param->bindType: BIND_LONGRAW"); + db2Debug3("param->bindType: BIND_LONGRAW"); *indicator = (SQLLEN) ((param->value == NULL) ? SQL_NULL_DATA : SQL_NTS); - db2Debug(2,"param_ind : %d",*indicator); + db2Debug2("param_ind : %d",*indicator); rc = SQLBindParameter( session->stmtp->hsql , col_num , SQL_PARAM_INPUT @@ -173,10 +170,10 @@ void db2BindParameter (DB2Session* session, ParamDesc* param, SQLLEN* indicator, } break; case BIND_LONG: { - db2Debug(3,"param->bindType: BIND_LONG"); + db2Debug3("param->bindType: BIND_LONG"); *indicator = (SQLLEN) ((param->value == NULL) ? SQL_NULL_DATA : SQL_NTS); - db2Debug(2,"param_ind : %d",*indicator); - db2Debug(2,"param->value : '%s'",param->value); + db2Debug2("param_ind : %d",*indicator); + db2Debug2("param->value : '%s'",param->value); rc = SQLBindParameter( session->stmtp->hsql , col_num , SQL_PARAM_INPUT @@ -193,9 +190,9 @@ void db2BindParameter (DB2Session* session, ParamDesc* param, SQLLEN* indicator, case BIND_OUTPUT: { SQLSMALLINT fcType; SQLSMALLINT fParamType; - db2Debug(2,"param->bindType: BIND_OUTPUT"); + db2Debug2("param->bindType: BIND_OUTPUT"); *indicator = (SQLLEN) ((param->value == NULL) ? SQL_NULL_DATA : 0); - db2Debug(2,"param_ind : %d",*indicator); + db2Debug2("param_ind : %d",*indicator); if (param->type == UUIDOID) { /* the type input function will interpret the string value correctly */ fcType = SQL_CHAR; @@ -222,5 +219,5 @@ void db2BindParameter (DB2Session* session, ParamDesc* param, SQLLEN* indicator, if (rc != SQL_SUCCESS) { db2Error_d(FDW_UNABLE_TO_CREATE_EXECUTION, "error executing query: SQLBindParameter failed to bind parameter", db2Message); } - db2Exit(1,"< db2BindParameter.c::db2BindParameter"); + db2Exit1(); } \ No newline at end of file diff --git a/source/db2Callbacks.c b/source/db2Callbacks.c index be6b5d7..3ac40a7 100644 --- a/source/db2Callbacks.c +++ b/source/db2Callbacks.c @@ -1,6 +1,7 @@ #include #include #include +#include "db2_fdw.h" /** eternal variables */ extern bool dml_in_transaction; @@ -8,8 +9,6 @@ extern bool dml_in_transaction; /** external prototypes */ extern void db2EndTransaction (void* arg, int is_commit, int noerror); extern void db2EndSubtransaction (void* arg, int nest_level, int is_commit); -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); /** local prototypes */ void db2RegisterCallback (void* arg); @@ -21,27 +20,27 @@ void subtransactionCallback(SubXactEvent event, SubTransactionId mySubid, SubTra * Register a callback for PostgreSQL transaction events. */ void db2RegisterCallback (void *arg) { - db2Entry(1,"> db2Callbacks.c::db2RegisterCallback(%x)",arg); + db2Entry1("(arg: %x)",arg); RegisterXactCallback (transactionCallback, arg); RegisterSubXactCallback (subtransactionCallback, arg); - db2Exit(1,"< db2Callbacks.c::db2RegisterCallback"); + db2Exit1(); } /* db2UnregisterCallback * Unregister a callback for PostgreSQL transaction events. */ void db2UnregisterCallback (void *arg) { - db2Entry(1,"> db2Callbacks.c::db2UnregisterCallback(%x)",arg); + db2Entry1("(arg: %x)",arg); UnregisterXactCallback (transactionCallback, arg); UnregisterSubXactCallback (subtransactionCallback, arg); - db2Exit(1,"< db2Callbacks.c::db2UnregisterCallback"); + db2Exit1(); } /* transactionCallback * Commit or rollback DB2 transactions when appropriate. */ void transactionCallback (XactEvent event, void *arg) { - db2Entry(1,"> db2Callbacks.c::transactionCallback(Event %d, Arg %x)", event, arg); + db2Entry1("(event: %d, arg: %x)", event, arg); switch (event) { case XACT_EVENT_PRE_COMMIT: case XACT_EVENT_PARALLEL_PRE_COMMIT: @@ -68,16 +67,16 @@ void transactionCallback (XactEvent event, void *arg) { break; } dml_in_transaction = false; - db2Exit(1,"< db2Callbacks.c::transactionCallback"); + db2Exit1(); } /* subtransactionCallback * Set or rollback to DB2 savepoints when appropriate. */ void subtransactionCallback (SubXactEvent event, SubTransactionId mySubid, SubTransactionId parentSubid, void *arg) { - db2Entry(1,"> db2Callbacks.c::subtransactionCallback"); + db2Entry1(); /* rollback to the appropriate savepoint on subtransaction abort */ if (event == SUBXACT_EVENT_ABORT_SUB || event == SUBXACT_EVENT_PRE_COMMIT_SUB) db2EndSubtransaction (arg, GetCurrentTransactionNestLevel (), event == SUBXACT_EVENT_PRE_COMMIT_SUB); - db2Exit(1,"< db2Callbacks.c::subtransactionCallback"); + db2Exit1(); } diff --git a/source/db2Cancel.c b/source/db2Cancel.c index 80b5570..c72115a 100644 --- a/source/db2Cancel.c +++ b/source/db2Cancel.c @@ -8,8 +8,6 @@ extern DB2EnvEntry* rootenvEntry; /* contains DB2 error messages, set by db2CheckErr() */ /** external prototypes */ -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); /** local prototypes */ void db2Cancel (void); @@ -22,7 +20,7 @@ void db2Cancel (void) { DB2ConnEntry* connp = NULL; HdlEntry* entryp = NULL; - db2Entry(1,"> db2Cancel.c::db2Cancel"); + db2Entry1(); /* send a cancel request for all servers ignoring errors */ for (envp = rootenvEntry; envp != NULL; envp = envp->right) { for (connp = envp->connlist; connp != NULL; connp = connp->right) { @@ -33,5 +31,5 @@ void db2Cancel (void) { } } } - db2Exit(1,"< db2Cancel.c::db2Cancel"); + db2Exit1(); } diff --git a/source/db2CheckErr.c b/source/db2CheckErr.c index b5d3343..fbb2509 100644 --- a/source/db2CheckErr.c +++ b/source/db2CheckErr.c @@ -11,9 +11,6 @@ char db2Message[ERRBUFSIZE];/* contains DB2 error messages, set b /** external variables */ /** external prototypes */ -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); -extern void db2Debug (int level, const char* message, ...); /** local prototypes */ SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleType, int line, char* file); @@ -35,7 +32,7 @@ SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleTyp * @since 1.0.0 */ SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleType, int line, char* file) { - db2Entry(4,"> db2CheckErr.c::db2CheckErr"); + db2Entry4(); memset (db2Message,0x00,sizeof(db2Message)); switch (status) { case SQL_INVALID_HANDLE: { @@ -55,9 +52,9 @@ SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleTyp memset(message ,0x00,SQL_MAX_MESSAGE_LENGTH); while (SQL_SUCCEEDED(SQLGetDiagRec(handleType,handle,i,sqlstate,&sqlcode,message,SQL_MAX_MESSAGE_LENGTH,&msgLen))) { - db2Debug(5,"SQLCODE : %d ",sqlcode); - db2Debug(5,"SQLSTATE: %d ",sqlstate); - db2Debug(5,"MESSAGE : '%s'",message); + db2Debug5("SQLCODE : %d ",sqlcode); + db2Debug5("SQLSTATE: %d ",sqlstate); + db2Debug5("MESSAGE : '%s'",message); snprintf((char*)submessage, SUBMESSAGE_LEN, "SQLSTATE = %s SQLCODE = %d\nline=%d\nfile=%s\n", sqlstate,sqlcode,line,file); if ((sizeof(db2Message) - strlen((char*)db2Message)) > strlen((char*)submessage) + 1) { strncat ((char*)db2Message,(char*)submessage, SUBMESSAGE_LEN); @@ -86,8 +83,8 @@ SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleTyp } break; } - db2Debug(5,"db2Message: '%s'",db2Message); - db2Debug(5,"err_code : %d ",err_code); - db2Exit(4,"< db2CheckErr.c::db2CheckErr - returns: %d",status); + db2Debug5("db2Message: '%s'",db2Message); + db2Debug5("err_code : %d ",err_code); + db2Exit4(": %d",status); return status; } diff --git a/source/db2ClientVersion.c b/source/db2ClientVersion.c index 587c4cc..a17272e 100644 --- a/source/db2ClientVersion.c +++ b/source/db2ClientVersion.c @@ -9,9 +9,6 @@ /** external variables */ /** external prototypes */ -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); -extern void db2Debug (int level, const char* message, ...); extern SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleType, int line, char* file); /** local prototypes */ @@ -24,10 +21,10 @@ void db2ClientVersion (DB2Session* session, char* version) { SQLSMALLINT len = 0; size_t ver_len = sizeof(version); SQLRETURN rc = 0; - db2Entry(1,"> db2ClientVersion.c::db2ClientVersion"); + db2Entry1(); memset(version,0x00,ver_len); rc = SQLGetInfo(session->connp->hdbc, SQL_DRIVER_VER, version, sizeof(version), &len); - db2Debug(2,"rc = %d, version = '%s', ind = %d", rc, version, len); + db2Debug2("rc = %d, version = '%s', ind = %d", rc, version, len); rc = db2CheckErr(rc,session->connp->hdbc,SQL_HANDLE_DBC,__LINE__,__FILE__); - db2Exit(1,"< db2ClientVersion.c::db2ClientVersion - version: '%s'", version); + db2Exit1(": '%s'", version); } diff --git a/source/db2CloseConnections.c b/source/db2CloseConnections.c index 66406b1..3b59112 100644 --- a/source/db2CloseConnections.c +++ b/source/db2CloseConnections.c @@ -12,9 +12,6 @@ extern char db2Message[ERRBUFSIZE];/* contains DB2 error messages, set b /** external prototypes */ extern int isLogLevel (int level); -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); -extern void db2Debug (int level, const char* message, ...); extern void db2Error (db2error sqlstate, const char* message); extern void db2Error_d (db2error sqlstate, const char* message, const char* detail, ...); extern SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleType, int line, char* file); @@ -31,15 +28,15 @@ static int deleteconnEntry (DB2ConnEntry* start, DB2ConnEntry* node); * Close everything in the cache. */ void db2CloseConnections (void) { - db2Entry(1,"> db2CloseConnections.c::db2CloseConnections"); + db2Entry1(); while (rootenvEntry != NULL) { while (rootenvEntry->connlist != NULL) { db2FreeConnHdl(rootenvEntry, rootenvEntry->connlist); - db2Debug(3,"rootenvEntry: %x, rootenvEntry->connlist: %x",rootenvEntry, rootenvEntry->connlist); + db2Debug3("rootenvEntry: %x, rootenvEntry->connlist: %x",rootenvEntry, rootenvEntry->connlist); } db2FreeEnvHdl(rootenvEntry, NULL); } - db2Exit(1,"< db2CloseConnections.c::db2CloseConnections"); + db2Exit1(); } /* db2FreeConnHdl */ @@ -47,27 +44,27 @@ static void db2FreeConnHdl(DB2EnvEntry* envp, DB2ConnEntry* connp){ SQLRETURN rc = 0; int result = 0; - db2Entry(2,"> db2CloseConnections.c::db2FreeConnHdl"); - db2Debug(3,"envp : %x, ->henv: %d, ->connlist: %x",envp,envp->henv,envp->connlist); - db2Debug(3,"connp: %x, ->hdbc: %d, ->handlelist: %x",connp,connp->hdbc,connp->handlelist); + db2Entry2(); + db2Debug3("envp : %x, ->henv: %d, ->connlist: %x",envp,envp->henv,envp->connlist); + db2Debug3("connp: %x, ->hdbc: %d, ->handlelist: %x",connp,connp->hdbc,connp->handlelist); if (connp == NULL) { if (silent) return; else db2Error (FDW_ERROR, "closeSession internal error: connp is null"); } /* terminate the session */ - db2Debug(3,"connp->hdbc: %x",connp->hdbc); + db2Debug3("connp->hdbc: %x",connp->hdbc); rc = SQLDisconnect(connp->hdbc); - db2Debug(4,"SQLDisconnect.rc: %d",rc); + db2Debug4("SQLDisconnect.rc: %d",rc); rc = db2CheckErr(rc, connp->hdbc,SQL_HANDLE_DBC,__LINE__,__FILE__); if (rc != SQL_SUCCESS && !silent) { db2Error_d (FDW_UNABLE_TO_CREATE_REPLY, "error closing session: SQLDisconnect failed to terminate session", db2Message); } /* release the session handle */ - db2Debug(3,"connp->hdbc: %x",connp->hdbc); + db2Debug3("connp->hdbc: %x",connp->hdbc); rc = SQLFreeHandle(SQL_HANDLE_DBC, connp->hdbc); - db2Debug(4,"SQLFreeHandle.rc: %d",rc); + db2Debug4("SQLFreeHandle.rc: %d",rc); if (rc != SQL_SUCCESS && !silent) { db2Error_d (FDW_UNABLE_TO_CREATE_REPLY, "error freeing session handle: SQLFreeHandle failed", db2Message); } @@ -78,28 +75,28 @@ static void db2FreeConnHdl(DB2EnvEntry* envp, DB2ConnEntry* connp){ result = deleteconnEntry(envp->connlist, connp); if (result && envp->connlist == connp) { envp->connlist = NULL; - db2Debug(4,"envp->connlist: %x",envp->connlist); + db2Debug4("envp->connlist: %x",envp->connlist); } - db2Exit(2,"< db2CloseConnections.c::db2FreeConnHdl"); + db2Exit2(); } /* deleteconnEntry */ int deleteconnEntry(DB2ConnEntry* start, DB2ConnEntry* node) { int result = 0; DB2ConnEntry* step = NULL; - db2Entry(2,"> db2CloseConnections.c::deleteconnEntry(start:%x,node:%x)",start,node); + db2Entry2("(start:%x,node:%x)",start,node); for (step = start; step != NULL; step = step->right) { if (step == node) { - db2Debug(4,"step == node: start: %x, step: %x, node %x", start, step, node); + db2Debug4("step == node: start: %x, step: %x, node %x", start, step, node); if (step->left == NULL && step->right == NULL){ - db2Debug(4,"step left and right is null: start: %x, step: %x",start,step); + db2Debug4("step left and right is null: start: %x, step: %x",start,step); } else if (step->left == NULL) { - db2Debug(4,"step left null"); + db2Debug4("step left null"); step->right->left = NULL; } else if (step->right == NULL) { - db2Debug(4,"step right null"); + db2Debug4("step right null"); step->left->right = NULL; } else { step->left->right = step->right; @@ -110,7 +107,7 @@ int deleteconnEntry(DB2ConnEntry* start, DB2ConnEntry* node) { if (step->pwd) free (step->pwd); if (step->jwt_token) free (step->jwt_token); if (step) { - db2Debug(4,"DB2ConnEntry freed: %x", step); + db2Debug4("DB2ConnEntry freed: %x", step); free (step); } result = 1; @@ -119,9 +116,9 @@ int deleteconnEntry(DB2ConnEntry* start, DB2ConnEntry* node) { } if (isLogLevel(3)) { for (step = start; step != NULL; step = step->right) { - db2Debug(3,"start:%x, step:%x, step->left: %x, step->right:%x",start,step,step->left,step->right); + db2Debug3("start:%x, step:%x, step->left: %x, step->right:%x",start,step,step->left,step->right); } } - db2Exit(2,"< db2CloseConnections.c::deleteconnEntry - returns: %d", result); + db2Exit2(": %d", result); return result; } diff --git a/source/db2CloseStatement.c b/source/db2CloseStatement.c index 7437449..6e52a98 100644 --- a/source/db2CloseStatement.c +++ b/source/db2CloseStatement.c @@ -7,9 +7,6 @@ /** external variables */ /** external prototypes */ -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); -extern void db2Debug (int level, const char* message, ...); extern void db2FreeStmtHdl (HdlEntry* handlep, DB2ConnEntry* connp); /** local prototypes */ @@ -19,14 +16,14 @@ void db2CloseStatement (DB2Session* session); * Close any open statement associated with the session. */ void db2CloseStatement (DB2Session* session) { - db2Entry(1,"> db2CloseStatement.c::db2CloseStatement"); + db2Entry1(); /* release statement handle, if it exists */ if (session->stmtp != NULL) { /* release the statement handle */ db2FreeStmtHdl(session->stmtp, session->connp); session->stmtp = NULL; } else { - db2Debug(3,"no handle to close"); + db2Debug3("no handle to close"); } - db2Exit(1,"< db2CloseStatement.c::db2CloseStatement"); + db2Exit1(); } diff --git a/source/db2CopyText.c b/source/db2CopyText.c index 01c3169..38d368d 100644 --- a/source/db2CopyText.c +++ b/source/db2CopyText.c @@ -9,8 +9,6 @@ /** external prototypes */ extern void* db2alloc (const char* type, size_t size); -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); /** local prototypes */ char* db2CopyText (const char* string, int size, int quote); @@ -25,7 +23,7 @@ char* db2CopyText (const char* string, int size, int quote) { register int j = -1; char* result; - db2Entry(4,"> db2CopyText.c::db2CopyText(string: '%s', size: %d, quote: %d)",string,size,quote); + db2Entry4("(string: '%s', size: %d, quote: %d)",string,size,quote); /* if "string" is parenthized, return a copy */ if (string[0] == '(' && string[size - 1] == ')') { result = db2alloc ("copyText", size + 1); @@ -53,6 +51,6 @@ char* db2CopyText (const char* string, int size, int quote) { result[++j] = '"'; result[j + 1] = '\0'; - db2Exit(4,"< db2CopyText.c::db2CopyText - result: %s",result); + db2Exit4(": %s",result); return result; } diff --git a/source/db2Debug.c b/source/db2Debug.c index 71fd489..b79a5b4 100644 --- a/source/db2Debug.c +++ b/source/db2Debug.c @@ -19,12 +19,11 @@ _Thread_local static int debug_depth = 0; (x==FDW_SERIALIZATION_FAILURE ? ERRCODE_T_R_SERIALIZATION_FAILURE : ERRCODE_FDW_ERROR)))))) /** local prototype */ -int isLogLevel(int level); -void db2Error (db2error sqlstate, const char* message); -void db2Error_d(db2error sqlstate, const char* message, const char* detail, ...) __attribute__ ((format (gnu_printf, 2, 0))); -void db2Entry (int level, const char* message, ...)__attribute__ ((format (gnu_printf, 2, 0))); -void db2Exit (int level, const char* message, ...)__attribute__ ((format (gnu_printf, 2, 0))); -void db2Debug (int level, const char* message, ...)__attribute__ ((format (gnu_printf, 2, 0))); +int isLogLevel (int level); +void db2Error (db2error sqlstate, const char* message); +void db2Error_d (db2error sqlstate, const char* message, const char* detail, ...) __attribute__ ((format (gnu_printf, 2, 0))); +void db2EntryExit(int level, int entry, const char* message, ...)__attribute__ ((format (gnu_printf, 3, 0))); +void db2Debug (int level, const char* message, ...)__attribute__ ((format (gnu_printf, 2, 0))); /** db2Error_d * Report a PostgreSQL error with a detail message. @@ -76,34 +75,28 @@ int isLogLevel(int level) { return dLevel; } -void db2Entry(int level, const char* message, ...) { +void db2EntryExit(int level, int entry, const char* message, ...) { if (isLogLevel(level)) { char cBuffer [4000]; va_list arg_marker; - va_start (arg_marker, message); - + va_start(arg_marker, message); vsnprintf (cBuffer, sizeof(cBuffer), message, arg_marker); - db2Debug(level, cBuffer); - ++debug_depth; - } -} -void db2Exit(int level, const char* message, ...) { - if (isLogLevel(level)) { - char cBuffer [4000]; - va_list arg_marker; - va_start (arg_marker, message); - vsnprintf (cBuffer, sizeof(cBuffer), message, arg_marker); + if (entry == 1) { + db2Debug(level, cBuffer); + ++debug_depth; - --debug_depth; - db2Debug(level, cBuffer); + } else { + --debug_depth; + db2Debug(level, cBuffer); + } } } void db2Debug(int level, const char* message, ...) { if (isLogLevel(level)) { char cBuffer [4000]; - char* sIndent = (char*)palloc0(2*debug_depth+1); + char* sIndent = (char*)palloc0(2 * debug_depth + 1); int dLevel = DEBUG5; va_list arg_marker; diff --git a/source/db2Describe.c b/source/db2Describe.c index 7d567e2..3f7445f 100644 --- a/source/db2Describe.c +++ b/source/db2Describe.c @@ -15,9 +15,6 @@ extern int err_code; /* error code, set by db2CheckErr() extern bool optionIsTrue (const char* value); extern void* db2alloc (const char* type, size_t size); extern void db2free (void* p); -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); -extern void db2Debug (int level, const char* message, ...); extern SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleType, int line, char* file); extern void db2Error_d (db2error sqlstate, const char* message, const char* detail, ...); extern char* db2CopyText (const char* string, int size, int quote); @@ -53,7 +50,7 @@ DB2Table* db2Describe (DB2Session* session, char* schema, char* table, char* pgn SQLINTEGER codepage = 0; SQLRETURN rc = 0; - db2Entry(1,"> db2Describe.c::db2Describe"); + db2Entry1(); /* get a complete quoted table name */ qtable = db2CopyText (table, strlen (table), 1); length = strlen (qtable); @@ -102,10 +99,10 @@ DB2Table* db2Describe (DB2Session* session, char* schema, char* table, char* pgn /* allocate an db2Table struct for the results */ reply = db2alloc ("reply", sizeof (DB2Table)); reply->name = tablename; - db2Debug(2,"table description"); - db2Debug(2,"reply->name : '%s'", reply->name); + db2Debug2("table description"); + db2Debug2("reply->name : '%s'", reply->name); reply->pgname = pgname; - db2Debug(2,"reply->pgname : '%s'", reply->pgname); + db2Debug2("reply->pgname : '%s'", reply->pgname); reply->npgcols = 0; reply->batchsz = DEFAULT_BATCHSZ; @@ -123,7 +120,7 @@ DB2Table* db2Describe (DB2Session* session, char* schema, char* table, char* pgn reply->ncols = ncols; reply->cols = (DB2Column**) db2alloc ("reply->cols", sizeof (DB2Column*) *reply->ncols); - db2Debug(2,"reply->ncols : %d", reply->ncols); + db2Debug2("reply->ncols : %d", reply->ncols); /* loop through the column list */ for (i = 1; i <= reply->ncols; ++i) { @@ -159,20 +156,20 @@ DB2Table* db2Describe (DB2Session* session, char* schema, char* table, char* pgn db2Error_d (FDW_UNABLE_TO_CREATE_REPLY, "error describing remote table: SQLDescribeCol failed to get column data", db2Message); } reply->cols[i - 1]->colName = db2CopyText ((char*) colName, (int) nameLen, 1); - db2Debug(2,"reply->cols[%d]->colName : '%s'", (i-1), reply->cols[i - 1]->colName); - db2Debug(2,"dataType: %d", dataType); + db2Debug2("reply->cols[%d]->colName : '%s'", (i-1), reply->cols[i - 1]->colName); + db2Debug2("dataType: %d", dataType); reply->cols[i - 1]->colType = (short) dataType; if (dataType == -7){ // datatype -7 does not exist it seems to be used for SQL_BOOLEAN wrongly reply->cols[i - 1]->colType = SQL_BOOLEAN; } - db2Debug(2,"reply->cols[%d]->colType : %d (%s)", (i-1), reply->cols[i - 1]->colType,c2name(reply->cols[i - 1]->colType)); + db2Debug2("reply->cols[%d]->colType : %d (%s)", (i-1), reply->cols[i - 1]->colType,c2name(reply->cols[i - 1]->colType)); reply->cols[i - 1]->colSize = (size_t) colSize; - db2Debug(2,"reply->cols[%d]->colSize : %ld", (i-1), reply->cols[i - 1]->colSize); + db2Debug2("reply->cols[%d]->colSize : %ld", (i-1), reply->cols[i - 1]->colSize); reply->cols[i - 1]->colScale = (short) scale; - db2Debug(2,"reply->cols[%d]->colScale : %d", (i-1), reply->cols[i - 1]->colScale); + db2Debug2("reply->cols[%d]->colScale : %d", (i-1), reply->cols[i - 1]->colScale); reply->cols[i - 1]->colNulls = (short) nullable; - db2Debug(2,"reply->cols[%d]->colNulls : %d", (i-1), reply->cols[i - 1]->colNulls); + db2Debug2("reply->cols[%d]->colNulls : %d", (i-1), reply->cols[i - 1]->colNulls); /* get the number of characters for string fields */ rc = SQLColAttribute (stmthp->hsql, i, SQL_DESC_PRECISION, NULL, 0, NULL, &charlen); @@ -181,7 +178,7 @@ DB2Table* db2Describe (DB2Session* session, char* schema, char* table, char* pgn db2Error_d (FDW_UNABLE_TO_CREATE_REPLY, "error describing remote table: SQLColAttribute failed to get column length", db2Message); } reply->cols[i - 1]->colChars = (size_t) charlen; - db2Debug(2,"reply->cols[%d]->colChars : %ld", (i-1), reply->cols[i - 1]->colChars); + db2Debug2("reply->cols[%d]->colChars : %ld", (i-1), reply->cols[i - 1]->colChars); /* get the binary length for RAW fields */ rc = SQLColAttribute (stmthp->hsql, i, SQL_DESC_OCTET_LENGTH, NULL, 0, NULL, &bin_size); @@ -190,7 +187,7 @@ DB2Table* db2Describe (DB2Session* session, char* schema, char* table, char* pgn db2Error_d (FDW_UNABLE_TO_CREATE_REPLY, "error describing remote table: SQLColAttribute failed to get column size", db2Message); } reply->cols[i - 1]->colBytes = (size_t) bin_size; - db2Debug(2,"reply->cols[%d]->colBytes : %ld", (i-1), reply->cols[i - 1]->colBytes); + db2Debug2("reply->cols[%d]->colBytes : %ld", (i-1), reply->cols[i - 1]->colBytes); /* get the columns codepage */ rc = SQLColAttribute(stmthp->hsql, i, SQL_DESC_CODEPAGE, NULL, 0, NULL, (SQLPOINTER)&codepage); @@ -199,7 +196,7 @@ DB2Table* db2Describe (DB2Session* session, char* schema, char* table, char* pgn db2Error_d (FDW_UNABLE_TO_CREATE_REPLY, "error describing remote table: SQLColAttribute failed to get column codepage", db2Message); } reply->cols[i - 1]->colCodepage = (int) codepage; - db2Debug(2,"reply->cols[%d]->colCodepage : %d", (i-1), reply->cols[i - 1]->colCodepage); + db2Debug2("reply->cols[%d]->colCodepage : %d", (i-1), reply->cols[i - 1]->colCodepage); /* Unfortunately a LONG VARBINARY is of type LONG VARCHAR but the codepage is set to 0 */ if (reply->cols[i-1]->colType == SQL_LONGVARCHAR && reply->cols[i-1]->colCodepage == 0){ @@ -266,10 +263,10 @@ DB2Table* db2Describe (DB2Session* session, char* schema, char* table, char* pgn default: break; } - db2Debug(2,"reply->cols[%d]->val_size : %d", (i-1), reply->cols[i - 1]->val_size); + db2Debug2("reply->cols[%d]->val_size : %d", (i-1), reply->cols[i - 1]->val_size); } /* release statement handle, this takes care of the parameter handles */ db2FreeStmtHdl(stmthp, session->connp); - db2Exit(1,"< db2Describe.c::db2Describe - returns: %x", reply); + db2Exit1(": %x", reply); return reply; } diff --git a/source/db2EndDirectModify.c b/source/db2EndDirectModify.c index 71346db..a5defe4 100644 --- a/source/db2EndDirectModify.c +++ b/source/db2EndDirectModify.c @@ -8,9 +8,6 @@ extern regproc* output_funcs; /** external prototypes */ extern void db2CloseStatement (DB2Session* session); extern void db2free (void* p); -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); -extern void db2Debug (int level, const char* message, ...); /** local prototypes */ void db2EndDirectModify(ForeignScanState* node); @@ -21,10 +18,10 @@ void db2EndDirectModify(ForeignScanState* node); void db2EndDirectModify(ForeignScanState* node) { DB2FdwDirectModifyState* fdw_state = (DB2FdwDirectModifyState*) node->fdw_state; - db2Entry(1,"> db2EndDirectModify.c::db2EndDirectModify"); + db2Entry1(); /* MemoryContext will be deleted automatically. */ if (fdw_state == NULL) { - db2Debug(2,"no fdw_state, nothing to do"); + db2Debug2("no fdw_state, nothing to do"); return; } @@ -51,5 +48,5 @@ void db2EndDirectModify(ForeignScanState* node) { db2free(fdw_state); node->fdw_state = NULL; - db2Exit(1,"< db2EndDirectModify.c::db2EndDirectModify"); + db2Exit1(); } \ No newline at end of file diff --git a/source/db2EndForeignInsert.c b/source/db2EndForeignInsert.c index dd8dcbf..439b8bb 100644 --- a/source/db2EndForeignInsert.c +++ b/source/db2EndForeignInsert.c @@ -1,22 +1,21 @@ #include #include +#include "db2_fdw.h" /** external variables */ /** external prototypes */ -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); extern void db2EndForeignModifyCommon(EState *estate, ResultRelInfo *rinfo); /** local prototypes */ void db2EndForeignInsert (EState* estate, ResultRelInfo* rinfo); -/** db2EndForeignInsert - * Close the currently active DB2 statement. +/* db2EndForeignInsert + * Close the currently active DB2 statement. */ void db2EndForeignInsert (EState* estate, ResultRelInfo* rinfo) { - db2Entry(1,"> db2EndForeignInsert.c::db2EndForeignInsert"); + db2Entry1(); db2EndForeignModifyCommon(estate, rinfo); - db2Exit(1,"< db2EndForeignInsert.c::db2EndForeignInsert"); + db2Exit1(); } diff --git a/source/db2EndForeignModify.c b/source/db2EndForeignModify.c index 1f7eafe..8925062 100644 --- a/source/db2EndForeignModify.c +++ b/source/db2EndForeignModify.c @@ -1,12 +1,11 @@ #include #include +#include "db2_fdw.h" /** external variables */ /** external prototypes */ extern void db2EndForeignModifyCommon(EState *estate, ResultRelInfo *rinfo); -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); /** local prototypes */ void db2EndForeignModify (EState* estate, ResultRelInfo* rinfo); @@ -15,8 +14,8 @@ void db2EndForeignModify (EState* estate, ResultRelInfo* rin * Close the currently active DB2 statement. */ void db2EndForeignModify (EState* estate, ResultRelInfo* rinfo) { - db2Entry(1,"> db2EndForeignModify.c::db2EndForeignModify"); + db2Entry1(); db2EndForeignModifyCommon(estate, rinfo); - db2Exit(1,"< db2EndForeignModify.c::db2EndForeignModify"); + db2Exit1(); } diff --git a/source/db2EndForeignModifyCommon.c b/source/db2EndForeignModifyCommon.c index bb9298b..757aa09 100644 --- a/source/db2EndForeignModifyCommon.c +++ b/source/db2EndForeignModifyCommon.c @@ -5,15 +5,13 @@ #include #include "db2_fdw.h" #include "DB2FdwState.h" + /** external variables */ extern regproc* output_funcs; /** external prototypes */ extern void db2CloseStatement (DB2Session* session); extern void db2free (void* p); -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); -extern void db2Debug (int level, const char* message, ...); /** local prototypes */ void db2EndForeignModifyCommon(EState *estate, ResultRelInfo *rinfo); @@ -21,12 +19,12 @@ void db2EndForeignModifyCommon(EState *estate, ResultRelInfo *rin void db2EndForeignModifyCommon(EState *estate, ResultRelInfo *rinfo) { DB2FdwState *fdw_state = NULL; - db2Entry(1,"> db2EndForeignModifyCommon.c::db2EndForeignModifyCommon"); - db2Debug(2,"relid: %d", RelationGetRelid (rinfo->ri_RelationDesc)); + db2Entry1(); + db2Debug2("relid: %d", RelationGetRelid (rinfo->ri_RelationDesc)); fdw_state = (DB2FdwState*) rinfo->ri_FdwState; if (fdw_state == NULL) { - db2Debug(2,"no fdw_state, nothing to do"); + db2Debug2("no fdw_state, nothing to do"); return; } @@ -53,5 +51,5 @@ void db2EndForeignModifyCommon(EState *estate, ResultRelInfo *rinfo) { rinfo->ri_FdwState = NULL; db2free(fdw_state); - db2Exit(1,"< db2EndForeignModifyCommon.c::db2EndForeignModifyCommon"); + db2Exit1(); } diff --git a/source/db2EndForeignScan.c b/source/db2EndForeignScan.c index 393ea72..4b59000 100644 --- a/source/db2EndForeignScan.c +++ b/source/db2EndForeignScan.c @@ -8,8 +8,6 @@ /** external prototypes */ extern void db2CloseStatement (DB2Session* session); extern void db2free (void* p); -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); /** local prototypes */ void db2EndForeignScan(ForeignScanState* node); @@ -20,7 +18,7 @@ void db2EndForeignScan(ForeignScanState* node); void db2EndForeignScan (ForeignScanState* node) { DB2FdwState* fdw_state = (DB2FdwState*) node->fdw_state; - db2Entry(1,"> db2EndForeignScan.c::db2EndForeignScan"); + db2Entry1(); /* release the DB2 session */ db2CloseStatement(fdw_state->session); // check fdw_state->session for dangling references that need to be freed @@ -28,5 +26,5 @@ void db2EndForeignScan (ForeignScanState* node) { fdw_state->session = NULL; // check fdw_state for dangling references that need to be freed db2free(fdw_state); - db2Exit(1,"< db2EndForeignScan.c::db2EndForeignScan"); + db2Exit1(); } diff --git a/source/db2EndSubtransaction.c b/source/db2EndSubtransaction.c index 63e641d..b252183 100644 --- a/source/db2EndSubtransaction.c +++ b/source/db2EndSubtransaction.c @@ -8,9 +8,6 @@ extern char db2Message[ERRBUFSIZE];/* contains DB2 error messages, set b extern DB2EnvEntry* rootenvEntry; /* Linked list of handles for cached DB2 connections. */ /** external prototypes */ -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); -extern void db2Debug (int level, const char* message, ...); extern void db2Error (db2error sqlstate, const char* message); extern void db2Error_d (db2error sqlstate, const char* message, const char* detail, ...); extern SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleType, int line, char* file); @@ -34,7 +31,7 @@ void db2EndSubtransaction (void* arg, int nest_level, int is_commit) { SQLRETURN rc = 0; HdlEntry* hstmtp = NULL; - db2Entry(1,"> db2EndSubtransaction.c::db2EndSubtransaction"); + db2Entry1(); /* do nothing if the transaction level is lower than nest_level */ if (con->xact_level < nest_level) return; @@ -63,7 +60,7 @@ void db2EndSubtransaction (void* arg, int nest_level, int is_commit) { db2Error (FDW_ERROR, "db2RollbackSavepoint internal error: handle not found in cache"); } - db2Debug(2,"rollback to savepoint s%d", nest_level); + db2Debug2("rollback to savepoint s%d", nest_level); snprintf ((char*)query, 49, "ROLLBACK TO SAVEPOINT s%d", nest_level); /* create statement handle */ @@ -83,5 +80,5 @@ void db2EndSubtransaction (void* arg, int nest_level, int is_commit) { db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error setting savepoint: SQLExecute failed to set savepoint", db2Message); } db2FreeStmtHdl(hstmtp, connp); - db2Exit(1,"< db2EndSubtransaction.c::db2EndSubtransaction"); + db2Exit1(); } diff --git a/source/db2EndTransaction.c b/source/db2EndTransaction.c index e4f8803..8d5e9f5 100644 --- a/source/db2EndTransaction.c +++ b/source/db2EndTransaction.c @@ -7,9 +7,6 @@ extern char db2Message[ERRBUFSIZE];/* contains DB2 error messages, set b extern DB2EnvEntry* rootenvEntry; /* Linked list of handles for cached DB2 connections. */ /** external prototypes */ -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); -extern void db2Debug (int level, const char* message, ...); extern void db2Error (db2error sqlstate, const char* message); extern void db2Error_d (db2error sqlstate, const char* message, const char* detail, ...); extern SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleType, int line, char* file); @@ -18,10 +15,10 @@ extern void db2FreeStmtHdl (HdlEntry* handlep, DB2ConnEntry* connp); /** local prototypes */ void db2EndTransaction (void* arg, int is_commit, int noerror); -/** db2EndTransaction - * Commit or rollback the transaction. - * The first argument must be a connEntry. - * If "noerror" is true, don't throw errors. +/* db2EndTransaction + * Commit or rollback the transaction. + * The first argument must be a connEntry. + * If "noerror" is true, don't throw errors. */ void db2EndTransaction (void* arg, int is_commit, int noerror) { DB2ConnEntry* connp = NULL; @@ -29,11 +26,11 @@ void db2EndTransaction (void* arg, int is_commit, int noerror) { int found = 0; SQLRETURN rc = 0; - db2Entry(1,"> db2EndTransaction.c::db2EndTransaction(arg:%x, is_commit:%d, noerror:%d)",arg,is_commit,noerror); + db2Entry1("(arg:%x, is_commit:%d, noerror:%d)",arg,is_commit,noerror); /* do nothing if there is no transaction */ if (((DB2ConnEntry*) arg)->xact_level == 0) { - db2Debug(2,"there is no transaction"); - db2Debug(2,"((DB2ConnEntry*) arg)->xact_level: %d",((DB2ConnEntry*) arg)->xact_level); + db2Debug2("there is no transaction"); + db2Debug2("((DB2ConnEntry*) arg)->xact_level: %d",((DB2ConnEntry*) arg)->xact_level); } else { /* find the cached handles for the argument */ envp = rootenvEntry; @@ -55,14 +52,14 @@ void db2EndTransaction (void* arg, int is_commit, int noerror) { /* commit or rollback */ if (is_commit) { - db2Debug(2,"db2_fdw::db2EndTransaction: commit remote transaction"); + db2Debug2("db2_fdw::db2EndTransaction: commit remote transaction"); rc = SQLEndTran(SQL_HANDLE_DBC, connp->hdbc, SQL_COMMIT); rc = db2CheckErr(rc, connp->hdbc, SQL_HANDLE_DBC, __LINE__, __FILE__); if (rc != SQL_SUCCESS && !noerror) { db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error committing transaction: SQLEndTran failed", db2Message); } } else { - db2Debug(2,"db2_fdw::db2EndTransaction: roll back remote transaction"); + db2Debug2("db2_fdw::db2EndTransaction: roll back remote transaction"); rc = SQLEndTran(SQL_HANDLE_DBC, connp->hdbc, SQL_ROLLBACK); rc = db2CheckErr(rc, connp->hdbc, SQL_HANDLE_DBC, __LINE__, __FILE__); if (rc != SQL_SUCCESS && !noerror) { @@ -70,7 +67,7 @@ void db2EndTransaction (void* arg, int is_commit, int noerror) { } } connp->xact_level = 0; - db2Debug(2,"connp->xact_level: %d",connp->xact_level); + db2Debug2("connp->xact_level: %d",connp->xact_level); } - db2Exit(1,"< db2EndTransaction.c::db2EndTransaction"); + db2Exit1(); } diff --git a/source/db2ExecForeignBatchInsert.c b/source/db2ExecForeignBatchInsert.c index 26902fb..e1f7f8e 100644 --- a/source/db2ExecForeignBatchInsert.c +++ b/source/db2ExecForeignBatchInsert.c @@ -1,13 +1,11 @@ #include - #if PG_VERSION_NUM >= 140000 #include +#include "db2_fdw.h" /** external variables */ /** external prototypes */ -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); extern TupleTableSlot* db2ExecForeignInsert (EState* estate, ResultRelInfo* rinfo, TupleTableSlot* slot, TupleTableSlot* planSlot); /** local prototypes */ @@ -21,7 +19,7 @@ TupleTableSlot** db2ExecForeignBatchInsert (EState *estate, ResultRelInfo */ TupleTableSlot ** db2ExecForeignBatchInsert(EState *estate, ResultRelInfo *rinfo, TupleTableSlot **slots, TupleTableSlot **planSlots, int *numSlots) { int i; - db2Entry(1,"> db2ExecForeignBatchInsert.c::db2ExecForeignBatchInsert"); + db2Entry1(); /* According to the FDW API, this is *not* used when there is a RETURNING clause, so normally these inserts don't need to produce a result tuple. * However, to be safe and to match the ExecForeignInsert semantics, we just call db2ExecForeignInsert and keep its results in the same slots. */ @@ -31,7 +29,7 @@ TupleTableSlot ** db2ExecForeignBatchInsert(EState *estate, ResultRelInfo *rinfo db2ExecForeignInsert(estate, rinfo, slots[i], planSlots ? planSlots[i] : NULL); } /* All results are in slots[0..*numSlots - 1]. */ - db2Exit(1,"< db2ExecForeignBatchInsert.c::db2ExecForeignBatchInsert slots: %x", slots); + db2Exit1(": %x", slots); return slots; } #endif \ No newline at end of file diff --git a/source/db2ExecForeignDelete.c b/source/db2ExecForeignDelete.c index 5bf9bf1..626d2f2 100644 --- a/source/db2ExecForeignDelete.c +++ b/source/db2ExecForeignDelete.c @@ -11,8 +11,6 @@ extern regproc* output_funcs; /** external prototypes */ extern int db2ExecuteQuery (DB2Session* session, ParamDesc* paramList); -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); extern void db2Debug (int level, const char* message, ...); extern void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls, bool trunc_lob) ; extern char* deparseDate (Datum datum); @@ -32,8 +30,8 @@ TupleTableSlot* db2ExecForeignDelete (EState* estate, ResultRelInfo* rinfo, Tupl int rows; MemoryContext oldcontext; - db2Entry(1,"> db2ExecForeignDelete.c::db2ExecForeignDelete"); - db2Debug(2,"relid: %d", RelationGetRelid (rinfo->ri_RelationDesc)); + db2Entry1(); + db2Debug2("relid: %d", RelationGetRelid (rinfo->ri_RelationDesc)); ++fdw_state->rowcount; dml_in_transaction = true; @@ -66,7 +64,7 @@ TupleTableSlot* db2ExecForeignDelete (EState* estate, ResultRelInfo* rinfo, Tupl /* store the virtual tuple */ ExecStoreVirtualTuple (slot); - db2Exit(1,"< db2ExecForeignDelete.c::db2ExecForeignDelete"); + db2Exit1(); return slot; } @@ -79,17 +77,17 @@ void setModifyParameters (ParamDesc *paramList, TupleTableSlot * newslot, TupleT Datum datum = 0; bool isnull = true; - db2Entry(1,"> db2ExecForeignDelete.c::setModifyParameters", __FILE__); + db2Entry1(); for (param = paramList; param != NULL; param = param->next) { - db2Debug(2,"db2Table->cols[%d]->colName: %s ",param->colnum,db2Table->cols[param->colnum]->colName); - db2Debug(2,"param->bindType: %d",param->bindType); - db2Debug(2,"param->colnum : %d",param->colnum); - db2Debug(2,"param->txts : %d",param->txts); - db2Debug(2,"param->type : %d",param->type); - db2Debug(2,"param->value : %s - initial",param->value); + db2Debug2("db2Table->cols[%d]->colName: %s ",param->colnum,db2Table->cols[param->colnum]->colName); + db2Debug2("param->bindType: %d",param->bindType); + db2Debug2("param->colnum : %d",param->colnum); + db2Debug2("param->txts : %d",param->txts); + db2Debug2("param->type : %d",param->type); + db2Debug2("param->value : %s - initial",param->value); /* don't do anything for output parameters */ if (param->bindType == BIND_OUTPUT) { - db2Debug(2,"param->bindType: %d - BIND_OUTPUT - skipped",param->bindType); + db2Debug2("param->bindType: %d - BIND_OUTPUT - skipped",param->bindType); continue; } if (db2Table->cols[param->colnum]->colPrimKeyPart != 0) { @@ -101,21 +99,21 @@ void setModifyParameters (ParamDesc *paramList, TupleTableSlot * newslot, TupleT continue; if (strcmp(NameStr(att->attname), psprintf("__db2fdw_rowid_%s", db2Table->cols[param->colnum]->pgname)) == 0) { attrno = i + 1; - db2Debug(2,"key %s attrno : %d",NameStr(att->attname),attrno); - db2Debug(2," atttypid : %d",att->atttypid); - db2Debug(2," atttypmod: %d",att->atttypmod); + db2Debug2("key %s attrno : %d",NameStr(att->attname),attrno); + db2Debug2(" atttypid : %d",att->atttypid); + db2Debug2(" atttypmod: %d",att->atttypmod); break; } } if (attrno != -1) { /* for primary key parameters extract the resjunk entry */ datum = ExecGetJunkAttribute (oldslot, attrno, &isnull); - db2Debug(2,"primaryKey value from resjunk entry: %ld",datum); + db2Debug2("primaryKey value from resjunk entry: %ld",datum); } } else { /* for other parameters extract the datum from newslot */ datum = slot_getattr (newslot, db2Table->cols[param->colnum]->pgattnum, &isnull); - db2Debug(2,"parameter value from newslot: %ld",datum); + db2Debug2("parameter value from newslot: %ld",datum); } switch (param->bindType) { @@ -123,26 +121,26 @@ void setModifyParameters (ParamDesc *paramList, TupleTableSlot * newslot, TupleT case BIND_NUMBER: { if (isnull) { param->value = NULL; - db2Debug(2,"param->value: %s - (NULL since isnull is set)",param->value); + db2Debug2("param->value: %s - (NULL since isnull is set)",param->value); } else { - db2Debug(2,"db2Table->cols[%d]->pgtype: %d",param->colnum,db2Table->cols[param->colnum]->pgtype); + db2Debug2("db2Table->cols[%d]->pgtype: %d",param->colnum,db2Table->cols[param->colnum]->pgtype); /* special treatment for date, timestamps and intervals */ switch (db2Table->cols[param->colnum]->pgtype) { case DATEOID: { param->value = deparseDate (datum); - db2Debug(2,"param->value: %s - (ought to be a date)",param->value); + db2Debug2("param->value: %s - (ought to be a date)",param->value); } break; case TIMESTAMPOID: case TIMESTAMPTZOID: { param->value = deparseTimestamp (datum, false/*(pgtype == TIMESTAMPTZOID)*/); - db2Debug(2,"param->value: %s - (ought to be a timestamp)",param->value); + db2Debug2("param->value: %s - (ought to be a timestamp)",param->value); } break; case TIMEOID: case TIMETZOID:{ param->value = deparseTimestamp (datum, false/*(pgtype == TIMETZOID)*/); - db2Debug(2,"param->value: %s (ought to be a time)",param->value); + db2Debug2("param->value: %s (ought to be a time)",param->value); } break; case BPCHAROID: @@ -151,7 +149,7 @@ void setModifyParameters (ParamDesc *paramList, TupleTableSlot * newslot, TupleT case NUMERICOID: { /* these functions require the type modifier */ param->value = DatumGetCString( OidFunctionCall3 (output_funcs[param->colnum], datum, ObjectIdGetDatum (InvalidOid), Int32GetDatum (db2Table->cols[param->colnum]->pgtypmod))); - db2Debug(2,"param->value: %s (ought to be a BPCHAR, VARCHAR,INTERVAL or NUMERIC)",param->value); + db2Debug2("param->value: %s (ought to be a BPCHAR, VARCHAR,INTERVAL or NUMERIC)",param->value); } break; case UUIDOID: { @@ -159,7 +157,7 @@ void setModifyParameters (ParamDesc *paramList, TupleTableSlot * newslot, TupleT char* q = NULL; param->value = DatumGetCString (OidFunctionCall1 (output_funcs[param->colnum], datum)); - db2Debug(2,"param->value: %s (ought to be a UUID)",param->value); + db2Debug2("param->value: %s (ought to be a UUID)",param->value); /* remove the minus signs for UUIDs */ for (p = q = param->value; *p != '\0'; ++p, ++q) { @@ -173,7 +171,7 @@ void setModifyParameters (ParamDesc *paramList, TupleTableSlot * newslot, TupleT case BOOLOID: { /* convert booleans to numbers */ param->value = DatumGetCString (OidFunctionCall1 (output_funcs[param->colnum], datum)); - db2Debug(2,"param->value: %s (ought to be a boolean)",param->value); + db2Debug2("param->value: %s (ought to be a boolean)",param->value); param->value[0] = (param->value[0] == 't') ? '1' : '0'; param->value[1] = '\0'; } @@ -182,7 +180,7 @@ void setModifyParameters (ParamDesc *paramList, TupleTableSlot * newslot, TupleT /* the others don't */ /* convert the parameter value into a string */ param->value = DatumGetCString (OidFunctionCall1 (output_funcs[param->colnum], datum)); - db2Debug(2,"param->value: %s (ought to be a string)",param->value); + db2Debug2("param->value: %s (ought to be a string)",param->value); } break; } @@ -193,7 +191,7 @@ void setModifyParameters (ParamDesc *paramList, TupleTableSlot * newslot, TupleT case BIND_LONGRAW: { if (isnull) { param->value = NULL; - db2Debug(2,"param->value: %s - (NULL since isnull is set)",param->value); + db2Debug2("param->value: %s - (NULL since isnull is set)",param->value); } else { int32 value_len = 0; @@ -203,15 +201,15 @@ void setModifyParameters (ParamDesc *paramList, TupleTableSlot * newslot, TupleT value_len = VARSIZE (datum) - VARHDRSZ; param->value = db2alloc("param->value", value_len); memcpy (param->value, VARDATA(datum), value_len); - db2Debug(2,"param->value: %s (ought to be a LONG or LONGRAW)",param->value); + db2Debug2("param->value: %s (ought to be a LONG or LONGRAW)",param->value); } } break; default: - db2Debug(2,"unknown BIND_TYPE: %d", param->bindType); + db2Debug2("unknown BIND_TYPE: %d", param->bindType); break; } - db2Debug(2,"param->value : %s - finally",param->value); + db2Debug2("param->value : %s - finally",param->value); } - db2Exit(1,"< db2ExecForeignDelete.c::setModifyParameters"); + db2Exit1(); } diff --git a/source/db2ExecForeignInsert.c b/source/db2ExecForeignInsert.c index 691aac7..de8cc1b 100644 --- a/source/db2ExecForeignInsert.c +++ b/source/db2ExecForeignInsert.c @@ -11,9 +11,6 @@ extern bool dml_in_transaction; /** external prototypes */ extern int db2ExecuteInsert (DB2Session* session, ParamDesc* paramList); -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); -extern void db2Debug (int level, const char* message, ...); extern void setModifyParameters (ParamDesc* paramList, TupleTableSlot* newslot, TupleTableSlot* oldslot, DB2Table* db2Table, DB2Session* session); extern void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls, bool trunc_lob) ; @@ -29,8 +26,8 @@ TupleTableSlot* db2ExecForeignInsert (EState* estate, ResultRelInfo* rinfo, Tupl int rows; MemoryContext oldcontext; - db2Entry(1,"> db2ExecForeignInsert.c::db2ExecForeignInsert"); - db2Debug(2,"relid: %d", RelationGetRelid (rinfo->ri_RelationDesc)); + db2Entry1(); + db2Debug2("relid: %d", RelationGetRelid (rinfo->ri_RelationDesc)); ++fdw_state->rowcount; dml_in_transaction = true; @@ -58,6 +55,6 @@ TupleTableSlot* db2ExecForeignInsert (EState* estate, ResultRelInfo* rinfo, Tupl /* store the virtual tuple */ ExecStoreVirtualTuple (slot); - db2Exit(1,"< db2ExecForeignInsert.c::db2ExecForeignInsert"); + db2Exit1(); return slot; } diff --git a/source/db2ExecForeignTruncate.c b/source/db2ExecForeignTruncate.c index 4634e65..1bb5268 100644 --- a/source/db2ExecForeignTruncate.c +++ b/source/db2ExecForeignTruncate.c @@ -8,9 +8,6 @@ /** external prototypes */ extern DB2FdwState* db2GetFdwState (Oid foreigntableid, double* sample_percent, bool drescribe); extern DB2Session* db2GetSession (const char* connectstring, char* user, char* password, char* jwt_token, const char* nls_lang, int curlevel); -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); -extern void db2Debug (int level, const char* message, ...); extern int db2ExecuteTruncate (DB2Session* session, const char* query); extern void db2CloseStatement (DB2Session* session); extern void db2free (void* p); @@ -29,7 +26,7 @@ void db2ExecForeignTruncate(List *rels, DropBehavior behavior, bool restart_seqs DB2FdwState* fdw_state = NULL; ListCell* lc; - db2Entry(1,"> db2ExecForeignTruncate.c::db2ExecForeignTruncate"); + db2Entry1(); if (rels != NIL) { /* Optionally, you could inspect "behavior" (DROP_CASCADE / DROP_RESTRICT) and try to be clever. * In practice, Db2 won't cascade TRUNCATE through RI anyway, so we just ignore it and let DB2 raise an error if there @@ -48,7 +45,7 @@ void db2ExecForeignTruncate(List *rels, DropBehavior behavior, bool restart_seqs fdw_state->session = NULL; } } - db2Exit(1,"< db2ExecForeignTruncate.c::db2ExecForeignTruncate"); + db2Exit1(); } /* db2BuildTruncateFdwState */ @@ -58,7 +55,7 @@ static DB2FdwState* db2BuildTruncateFdwState(Relation rel, bool restart_seqs) { char* identity_clause; char* storage_clause = "DROP STORAGE"; /* or REUSE STORAGE */ char* trigger_clause = "IGNORE DELETE TRIGGERS"; - db2Entry(1,"> db2ExecForeignTruncate.c::db2BuildTruncateFdwState"); + db2Entry1(); /** Map Postgres' RESTART/CONTINUE IDENTITY to Db2's TRUNCATE options. */ if (restart_seqs) @@ -83,8 +80,8 @@ static DB2FdwState* db2BuildTruncateFdwState(Relation rel, bool restart_seqs) { */ appendStringInfo(&sql, "TRUNCATE TABLE %s %s %s %s IMMEDIATE", fdwState->db2Table->name, storage_clause, trigger_clause, identity_clause); fdwState->query = sql.data; - db2Debug(2,"fdwState->query: '%s'",sql.data); - db2Exit(1,"< db2ExecForeignTruncate.c::db2BuildTruncateFdwState : %x",fdwState); + db2Debug2("fdwState->query: '%s'",sql.data); + db2Exit1(": %x",fdwState); return fdwState; } #endif \ No newline at end of file diff --git a/source/db2ExecForeignUpdate.c b/source/db2ExecForeignUpdate.c index 6b7dce8..486d2c6 100644 --- a/source/db2ExecForeignUpdate.c +++ b/source/db2ExecForeignUpdate.c @@ -11,9 +11,6 @@ extern bool dml_in_transaction; /** external prototypes */ extern int db2ExecuteQuery (DB2Session* session, ParamDesc* paramList); -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); -extern void db2Debug (int level, const char* message, ...); extern void setModifyParameters (ParamDesc* paramList, TupleTableSlot* newslot, TupleTableSlot* oldslot, DB2Table* db2Table, DB2Session* session); extern void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls, bool trunc_lob) ; @@ -29,8 +26,8 @@ TupleTableSlot* db2ExecForeignUpdate (EState* estate, ResultRelInfo* rinfo, Tupl int rows = 0; MemoryContext oldcontext; - db2Entry(1,"> db2ExecForeignUpdate.c::db2ExecForeignUpdate"); - db2Debug(2,"relid: %d", RelationGetRelid (rinfo->ri_RelationDesc)); + db2Entry1(); + db2Debug2("relid: %d", RelationGetRelid (rinfo->ri_RelationDesc)); ++fdw_state->rowcount; dml_in_transaction = true; @@ -63,7 +60,7 @@ TupleTableSlot* db2ExecForeignUpdate (EState* estate, ResultRelInfo* rinfo, Tupl /* store the virtual tuple */ ExecStoreVirtualTuple (slot); - db2Exit(1,"< db2ExecForeignUpdate.c::db2ExecForeignUpdate"); + db2Exit1(); return slot; } diff --git a/source/db2ExecuteInsert.c b/source/db2ExecuteInsert.c index 0222282..df50546 100644 --- a/source/db2ExecuteInsert.c +++ b/source/db2ExecuteInsert.c @@ -14,9 +14,6 @@ extern int err_code; /* error code, set by db2CheckErr() /** external prototypes */ extern void* db2alloc (const char* type, size_t size); extern void db2free (void* p); -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); -extern void db2Debug (int level, const char* message, ...); extern SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleType, int line, char* file); extern void db2Error_d (db2error sqlstate, const char* message, const char* detail, ...); extern SQLSMALLINT param2c (SQLSMALLINT fcType); @@ -42,11 +39,11 @@ int db2ExecuteInsert (DB2Session* session, ParamDesc* paramList) { int rowcount = 0; int param_count = 0; - db2Entry(1,"> db2ExecuteInsert.c::db2ExecuteInsert"); + db2Entry1(); for (param = paramList; param != NULL; param = param->next) { ++param_count; } - db2Debug(2,"paramcount: %d",param_count); + db2Debug2("paramcount: %d",param_count); /* allocate a temporary array of indicators */ indicators = db2alloc ("indicators", param_count * sizeof (SQLLEN)); @@ -59,13 +56,13 @@ int db2ExecuteInsert (DB2Session* session, ParamDesc* paramList) { } /* execute the query and get the first result row */ - db2Debug(2,"session->stmtp->hsql: %d",session->stmtp->hsql); + db2Debug2("session->stmtp->hsql: %d",session->stmtp->hsql); rc = SQLGetCursorName(session->stmtp->hsql, cname, (SQLSMALLINT)sizeof(cname), &outlen); rc = db2CheckErr(rc, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); if (rc != SQL_SUCCESS) { db2Error_d(FDW_UNABLE_TO_CREATE_EXECUTION, "error executing query: SQLGetCusorName failed to obtain cursor name", db2Message); } - db2Debug(2,"cursor name: '%s'", cname); + db2Debug2("cursor name: '%s'", cname); rc = SQLExecute (session->stmtp->hsql); rc = db2CheckErr(rc, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); if (rc != SQL_SUCCESS && rc != SQL_NO_DATA) { @@ -76,7 +73,7 @@ int db2ExecuteInsert (DB2Session* session, ParamDesc* paramList) { /* db2free all indicators */ db2free (indicators); if (rc == SQL_NO_DATA) { - db2Debug(3,"SQL_NO_DATA"); + db2Debug3("SQL_NO_DATA"); } else { SQLINTEGER rowcount_val = 0; @@ -86,9 +83,9 @@ int db2ExecuteInsert (DB2Session* session, ParamDesc* paramList) { if (rc != SQL_SUCCESS) { db2Error_d ( FDW_UNABLE_TO_CREATE_EXECUTION, "error executing query: SQLRowCount failed to get number of affected rows", db2Message); } - db2Debug(2,"rowcount_val: %lld", rowcount_val); + db2Debug2("rowcount_val: %lld", rowcount_val); rowcount = (int) rowcount_val; } - db2Exit(1,"< db2ExecuteInsert.c::db2ExecuteInsert - returns: %d",rowcount); + db2Exit1(": %d",rowcount); return rowcount; } diff --git a/source/db2ExecuteQuery.c b/source/db2ExecuteQuery.c index 76aedae..4a89ccf 100644 --- a/source/db2ExecuteQuery.c +++ b/source/db2ExecuteQuery.c @@ -15,9 +15,6 @@ extern int err_code; /* error code, set by db2CheckErr() /** external prototypes */ extern void* db2alloc (const char* type, size_t size); extern void db2free (void* p); -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); -extern void db2Debug (int level, const char* message, ...); extern SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleType, int line, char* file); extern void db2Error_d (db2error sqlstate, const char* message, const char* detail, ...); extern SQLSMALLINT param2c (SQLSMALLINT fcType); @@ -28,11 +25,11 @@ extern void db2BindParameter (DB2Session* session, ParamDesc* param, /** internal prototypes */ int db2ExecuteQuery (DB2Session* session, ParamDesc* paramList); -/** db2ExecuteQuery - * Execute a prepared statement and fetches the first result row. - * The parameters ("bind variables") are filled from paramList. - * Returns the count of processed rows. - * This can be called several times for a prepared SQL statement. +/* db2ExecuteQuery + * Execute a prepared statement and fetches the first result row. + * The parameters ("bind variables") are filled from paramList. + * Returns the count of processed rows. + * This can be called several times for a prepared SQL statement. */ int db2ExecuteQuery (DB2Session* session, ParamDesc* paramList) { SQLLEN* indicators = NULL; @@ -43,11 +40,11 @@ int db2ExecuteQuery (DB2Session* session, ParamDesc* paramList) { int rowcount = 0; int param_count = 0; - db2Entry(1,"> db2ExecuteQuery.c::db2ExecureQuery"); + db2Entry1(); for (param = paramList; param != NULL; param = param->next) { ++param_count; } - db2Debug(2,"paramcount: %d",param_count); + db2Debug2("paramcount: %d",param_count); /* allocate a temporary array of indicators */ indicators = db2alloc ("indicators", param_count * sizeof (SQLLEN)); @@ -58,13 +55,13 @@ int db2ExecuteQuery (DB2Session* session, ParamDesc* paramList) { db2BindParameter(session, param, &indicators[param_count], param_count, param_count); } /* execute the query and get the first result row */ - db2Debug(2,"session->stmtp->hsql: %d",session->stmtp->hsql); + db2Debug2("session->stmtp->hsql: %d",session->stmtp->hsql); rc = SQLGetCursorName(session->stmtp->hsql, cname, (SQLSMALLINT)sizeof(cname), &outlen); rc = db2CheckErr(rc, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); if (rc != SQL_SUCCESS) { db2Error_d(FDW_UNABLE_TO_CREATE_EXECUTION, "error executing query: SQLGetCusorName failed to obtain cursor name", db2Message); } - db2Debug(2,"cursor name: '%s'", cname); + db2Debug2("cursor name: '%s'", cname); rc = SQLExecute (session->stmtp->hsql); rc = db2CheckErr(rc, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); if (rc != SQL_SUCCESS && rc != SQL_NO_DATA) { @@ -73,7 +70,7 @@ int db2ExecuteQuery (DB2Session* session, ParamDesc* paramList) { } db2free(indicators); if (rc == SQL_NO_DATA) { - db2Debug(3,"SQL_NO_DATA"); + db2Debug3("SQL_NO_DATA"); } else { SQLINTEGER rowcount_val = 0; @@ -83,9 +80,9 @@ int db2ExecuteQuery (DB2Session* session, ParamDesc* paramList) { if (rc != SQL_SUCCESS) { db2Error_d ( FDW_UNABLE_TO_CREATE_EXECUTION, "error executing query: SQLRowCount failed to get number of affected rows", db2Message); } - db2Debug(2,"rowcount_val: %lld", rowcount_val); + db2Debug2("rowcount_val: %lld", rowcount_val); rowcount = (int) rowcount_val; } - db2Exit(1,"< db2ExecuteQuery.c::db2ExecureQuery : %d",rowcount); + db2Exit1(": %d",rowcount); return rowcount; } diff --git a/source/db2ExecuteTruncate.c b/source/db2ExecuteTruncate.c index aa8baa1..8a1fd34 100644 --- a/source/db2ExecuteTruncate.c +++ b/source/db2ExecuteTruncate.c @@ -11,9 +11,6 @@ extern char db2Message[ERRBUFSIZE];/* contains DB2 error messages, set b extern int err_code; /* error code, set by db2CheckErr() */ /** external prototypes */ -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); -extern void db2Debug (int level, const char* message, ...); extern SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleType, int line, char* file); extern void db2Error_d (db2error sqlstate, const char* message, const char* detail, ...); extern HdlEntry* db2AllocStmtHdl (SQLSMALLINT type, DB2ConnEntry* connp, db2error error, const char* errmsg); @@ -27,7 +24,7 @@ int db2ExecuteTruncate (DB2Session* session, const char* query) { SQLINTEGER rowcount_val = 0; int rowcount = 0; - db2Entry(1,"> db2ExecuteTruncate.c::db2ExecuteTruncate(DB2Session: %x, query: %s)",session,query); + db2Entry1("(DB2Session: %x, query: %s)",session,query); rc = SQLEndTran(SQL_HANDLE_DBC, session->connp->hdbc, SQL_COMMIT); rc = db2CheckErr(rc, session->connp->hdbc, SQL_HANDLE_DBC, __LINE__, __FILE__); @@ -42,8 +39,8 @@ int db2ExecuteTruncate (DB2Session* session, const char* query) { db2Error_d(err_code == 8177 ? FDW_SERIALIZATION_FAILURE : FDW_UNABLE_TO_CREATE_EXECUTION, "error executing query: SQLExecute failed to execute remote query", db2Message); } - db2Debug(2,"rowcount_val: %lld", rowcount_val); + db2Debug2("rowcount_val: %lld", rowcount_val); rowcount = (int) rowcount_val; - db2Exit(1,"< db2ExecuteTruncate.c::db2ExecuteTruncate - returns: %d",rowcount); + db2Exit1(": %d",rowcount); return rowcount; } diff --git a/source/db2ExplainForeignModify.c b/source/db2ExplainForeignModify.c index a704f8a..39b4229 100644 --- a/source/db2ExplainForeignModify.c +++ b/source/db2ExplainForeignModify.c @@ -9,9 +9,6 @@ #include "DB2FdwState.h" /** external prototypes */ -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); -extern void db2Debug (int level, const char* message, ...); /** local prototypes */ void db2ExplainForeignModify (ModifyTableState* mtstate, ResultRelInfo* rinfo, List* fdw_private, int subplan_index, struct ExplainState* es); @@ -22,10 +19,10 @@ void db2ExplainForeignModify (ModifyTableState* mtstate, ResultRelInfo* rinfo, L */ void db2ExplainForeignModify (ModifyTableState* mtstate, ResultRelInfo* rinfo, List* fdw_private, int subplan_index, struct ExplainState* es) { DB2FdwState* fdw_state = (DB2FdwState*) rinfo->ri_FdwState; - db2Entry(1,"> db2ExplainForeignModify.c::db2ExplainForeignModify"); - db2Debug(2,"relid: %d", RelationGetRelid (rinfo->ri_RelationDesc)); + db2Entry1(); + db2Debug2("relid: %d", RelationGetRelid (rinfo->ri_RelationDesc)); /* show query */ ExplainPropertyText ("DB2 statement", fdw_state->query, es); - db2Exit(1,"< db2ExplainForeignModify.c::db2ExplainForeignModify"); + db2Exit1(); } diff --git a/source/db2ExplainForeignScan.c b/source/db2ExplainForeignScan.c index 93dc9c1..8a4ba0d 100644 --- a/source/db2ExplainForeignScan.c +++ b/source/db2ExplainForeignScan.c @@ -12,9 +12,6 @@ /** external prototypes */ extern void* db2alloc (const char* type, size_t size); extern void db2free (void* p); -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); -extern void db2Debug (int level, const char* message, ...); /** local prototypes */ void db2ExplainForeignScan(ForeignScanState* node, ExplainState* es); @@ -26,11 +23,11 @@ static void db2Explain (void* fdw, ExplainState* es); */ void db2ExplainForeignScan (ForeignScanState* node, ExplainState* es) { DB2FdwState* fdw_state = (DB2FdwState*) node->fdw_state; - db2Entry(1,"> db2ExplainForeignScan.c::db2ExplainForeignScan"); - elog (DEBUG1, "db2_fdw: explain foreign table scan"); + db2Entry1(); + db2Debug2("db2_fdw: explain foreign table scan"); ExplainPropertyText ("DB2 query", fdw_state->query, es); db2Explain (fdw_state, es); - db2Exit(1,"< db2ExplainForeignScan.c::db2ExplainForeignScan"); + db2Exit1(); } /* db2Explain */ @@ -45,7 +42,7 @@ static void db2Explain (void* fdw, ExplainState* es) { char* src = fdw_state->query; char* dest = NULL; - db2Entry(1,"> db2ExplainForeignScan.c::db2Explain"); + db2Entry1(); for (const char* p = src; *p; p++) { if (*p == '"') count++; } @@ -75,7 +72,7 @@ static void db2Explain (void* fdw, ExplainState* es) { snprintf(execution_cmd,sizeof(execution_cmd),"db2expln -t -d %s -q \"%s\" |grep -E \"Estimated Cost|Estimated Cardinality\" ",fdw_state->dbserver,tempQuery); } } - db2Debug(2,"execution_cmd: '%s'",execution_cmd); + db2Debug2("execution_cmd: '%s'",execution_cmd); /* Open the command for reading. */ fp = popen(execution_cmd, "r"); if (fp == NULL) { @@ -91,5 +88,5 @@ static void db2Explain (void* fdw, ExplainState* es) { /* close */ pclose(fp); db2free(tempQuery); - db2Exit(1,"< db2ExplainForeignScan.c::db2Explain"); + db2Exit1(); } diff --git a/source/db2FetchNext.c b/source/db2FetchNext.c index c83b075..1cb84ef 100644 --- a/source/db2FetchNext.c +++ b/source/db2FetchNext.c @@ -9,8 +9,6 @@ extern char db2Message[ERRBUFSIZE];/* contains DB2 error messages, set b extern int err_code; /* error code, set by db2CheckErr() */ /** external prototypes */ -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); extern void db2Error (db2error sqlstate, const char* message); extern void db2Error_d (db2error sqlstate, const char* message, const char* detail, ...); extern SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleType, int line, char* file); @@ -23,7 +21,7 @@ int db2FetchNext (DB2Session* session); */ int db2FetchNext (DB2Session* session) { SQLRETURN rc = 0; - db2Entry(1,"> db2FetchNext.c::db2FetchNext"); + db2Entry1(); /* make sure there is a statement handle stored in "session" */ if (session->stmtp == NULL) { db2Error (FDW_ERROR, "db2FetchNext internal error: statement handle is NULL"); @@ -34,6 +32,6 @@ int db2FetchNext (DB2Session* session) { if (rc != SQL_SUCCESS && rc != SQL_NO_DATA) { db2Error_d (err_code == 8177 ? FDW_SERIALIZATION_FAILURE : FDW_UNABLE_TO_CREATE_EXECUTION, "error fetching result: SQLFetchScroll failed to fetch next result row", db2Message); } - db2Exit(1,"< db2FetchNext.c::db2FetchNext : %d",(rc == SQL_SUCCESS)); + db2Exit1(": %d",(rc == SQL_SUCCESS)); return (rc == SQL_SUCCESS); } diff --git a/source/db2FreeEnvHdl.c b/source/db2FreeEnvHdl.c index bb5b81e..bd17e1b 100644 --- a/source/db2FreeEnvHdl.c +++ b/source/db2FreeEnvHdl.c @@ -12,9 +12,6 @@ extern char db2Message[ERRBUFSIZE];/* contains DB2 error messages, set b extern DB2EnvEntry* rootenvEntry; /* Linked list of handles for cached DB2 connections. */ /** external prototypes */ -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); -extern void db2Debug (int level, const char* message, ...); extern void db2Error (db2error sqlstate, const char* message); extern void db2Error_d (db2error sqlstate, const char* message, const char* detail, ...); extern SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleType, int line, char* file); @@ -31,19 +28,19 @@ static DB2EnvEntry* findenvEntryHandle (DB2EnvEntry* start, SQLHENV henv); void db2FreeEnvHdl(DB2EnvEntry* envp, const char* nls_lang){ SQLRETURN rc = 0; - db2Entry(1,"> db2FreeEnvHdl.c::db2FreeEnvHdl"); + db2Entry1(); /* search environment handle in cache */ envp = findenvEntryHandle (rootenvEntry, envp->henv); if (envp == NULL) { - db2Debug(3,"removeEnvironment internal error: environment handle not found in cache"); + db2Debug3("removeEnvironment internal error: environment handle not found in cache"); if (!silent) { db2Error (FDW_ERROR, "removeEnvironment internal error: environment handle not found in cache"); } } else { /* release environment handle */ rc = SQLFreeHandle(SQL_HANDLE_ENV, envp->henv); - db2Debug(3,"release env handle - rc: %d, henv: %d", rc, envp->henv); + db2Debug3("release env handle - rc: %d, henv: %d", rc, envp->henv); rc = db2CheckErr(rc, envp->henv, SQL_HANDLE_ENV,__LINE__, __FILE__); if (rc != SQL_SUCCESS) { db2Error_d (FDW_UNABLE_TO_ESTABLISH_CONNECTION, "cannot release environment handle","%s", db2Message); @@ -53,43 +50,43 @@ void db2FreeEnvHdl(DB2EnvEntry* envp, const char* nls_lang){ } deleteenvEntry(rootenvEntry,envp); sql_initialized = 0; - db2Debug(3,"sql_initialized: %d",sql_initialized); + db2Debug3("sql_initialized: %d",sql_initialized); } - db2Exit(1,"< db2FreeEnvHdl.c::db2FreeEnvHdl"); + db2Exit1(); } /* deleteenvEntry */ static int deleteenvEntry(DB2EnvEntry* start, DB2EnvEntry* node) { int result = 1; DB2EnvEntry* step = NULL; - db2Entry(1,"> db2FreeEnvHdl.c::deleteenvEntry(start: %x, node: %x)", start, node); + db2Entry1("(start: %x, node: %x)", start, node); for (step = start; step != NULL; step = step->right){ if (step == node) { free (step->nls_lang); step->nls_lang = NULL; if (step->left == NULL && step->right == NULL){ rootenvEntry = NULL; - db2Debug(3,"rootenvEntry : %x", rootenvEntry); - db2Debug(3,"DB2Enventry freed: %x", step); + db2Debug3("rootenvEntry : %x", rootenvEntry); + db2Debug3("DB2Enventry freed: %x", step); free (step); step = NULL; } else if (step->left == NULL) { step->right->left = NULL; - db2Debug(3,"rootenvEntry : %x", rootenvEntry); - db2Debug(3,"DB2Enventry freed: %x", step); + db2Debug3("rootenvEntry : %x", rootenvEntry); + db2Debug3("DB2Enventry freed: %x", step); free (step); step = NULL; } else if (step->right == NULL) { step->left->right = NULL; - db2Debug(3,"rootenvEntry : %x", rootenvEntry); - db2Debug(3,"DB2Enventry freed: %x", step); + db2Debug3("rootenvEntry : %x", rootenvEntry); + db2Debug3("DB2Enventry freed: %x", step); free (step); step = NULL; } else { step->left->right = step->right; step->right->left = step->left; - db2Debug(3,"rootenvEntry : %x", rootenvEntry); - db2Debug(3,"DB2Enventry freed: %x", step); + db2Debug3("rootenvEntry : %x", rootenvEntry); + db2Debug3("DB2Enventry freed: %x", step); free (step); step = NULL; } @@ -97,7 +94,7 @@ static int deleteenvEntry(DB2EnvEntry* start, DB2EnvEntry* node) { break; } } - db2Exit(1,"< db2FreeEnvHdl.c::deleteenvEntry - returns: %d",result); + db2Exit1(": %d",result); return result; } @@ -105,33 +102,33 @@ static int deleteenvEntry(DB2EnvEntry* start, DB2EnvEntry* node) { static int deleteenvEntryLang(DB2EnvEntry* start, const char* nlslang) { int result = 1; DB2EnvEntry *step = NULL; - db2Entry(1,"> db2FreeEnvHdl.c::deleteenvEntryLang(start: %x, nlslang: %s)", start, nlslang); + db2Entry1("(start: %x, nlslang: %s)", start, nlslang); for (step = start; step != NULL; step = step->right){ if (strcmp (step->nls_lang, nlslang) == 0) { free (step->nls_lang); if (step->left == NULL && step->right == NULL){ rootenvEntry = NULL; - db2Debug(3,"rootenvEntry : %x", rootenvEntry); - db2Debug(3,"DB2Enventry freed: %x", step); + db2Debug3("rootenvEntry : %x", rootenvEntry); + db2Debug3("DB2Enventry freed: %x", step); free (step); step = NULL; } else if (step->left == NULL) { step->right->left = NULL; - db2Debug(3,"rootenvEntry : %x", rootenvEntry); - db2Debug(3,"DB2Enventry freed: %x", step); + db2Debug3("rootenvEntry : %x", rootenvEntry); + db2Debug3("DB2Enventry freed: %x", step); free (step); step = NULL; } else if (step->right == NULL) { step->left->right = NULL; - db2Debug(3,"rootenvEntry : %x", rootenvEntry); - db2Debug(3,"DB2Enventry freed: %x", step); + db2Debug3("rootenvEntry : %x", rootenvEntry); + db2Debug3("DB2Enventry freed: %x", step); free (step); step = NULL; } else { step->left->right = step->right; step->right->left = step->left; - db2Debug(3,"rootenvEntry : %x", rootenvEntry); - db2Debug(3,"DB2Enventry freed: %x", step); + db2Debug3("rootenvEntry : %x", rootenvEntry); + db2Debug3("DB2Enventry freed: %x", step); free (step); step = NULL; } @@ -139,38 +136,38 @@ static int deleteenvEntryLang(DB2EnvEntry* start, const char* nlslang) { break; } } - db2Exit(1,"< db2FreeEnvHdl.c::deleteenvEntryLang : %d",result); + db2Exit1(": %d",result); return result; } /* findenvEntryHandle */ static DB2EnvEntry* findenvEntryHandle (DB2EnvEntry* start, SQLHENV henv) { DB2EnvEntry* step = NULL; - db2Entry(1,"> db2FreeEnvHdl.c::findenvEntryHandle(start: %x, SQLHENV: %d)",start, henv); + db2Entry1("(start: %x, SQLHENV: %d)",start, henv); for (step = start; step != NULL; step = step->right){ if (step->henv == henv) { break; } } - db2Exit(1,"< db2FreeEnvHdl.c::findenvEntryHandle : %x",step); + db2Exit1(": %x",step); return step; } /* findenvEntry */ DB2EnvEntry* findenvEntry(DB2EnvEntry* start, const char* nlslang) { DB2EnvEntry* step = NULL; - db2Entry(1,"> db2FreeEnvHdl.c::findenvEntry(start: %x, nlslang: '%s')", start, nlslang); + db2Entry1("(start: %x, nlslang: '%s')", start, nlslang); for (step = start; step != NULL; step = step->right) { - db2Debug(3,"step: %x ->nls_lang: '%s'", step, step->nls_lang); - db2Debug(3,"nls_lang : '%s'", nlslang); - db2Debug(3,"strcmp(step->nls_lang, nlslang): %d",strcmp (step->nls_lang, nlslang)); + db2Debug3("step: %x ->nls_lang: '%s'", step, step->nls_lang); + db2Debug3("nls_lang : '%s'", nlslang); + db2Debug3("strcmp(step->nls_lang, nlslang): %d",strcmp (step->nls_lang, nlslang)); if (strcmp (step->nls_lang, nlslang) == 0) { break; } } if (step != NULL) { - db2Debug(3,"step: %x, ->henv: %d, ->nls_lang: '%s', ->connlist: %x", step, step->henv, step->nls_lang,step->connlist); + db2Debug3("step: %x, ->henv: %d, ->nls_lang: '%s', ->connlist: %x", step, step->henv, step->nls_lang,step->connlist); } - db2Exit(1,"< db2FreeEnvHdl.c::findenvEntry - returns: %x", step); + db2Exit1(": %x", step); return step; } diff --git a/source/db2FreeStmtHdl.c b/source/db2FreeStmtHdl.c index 3f53dd2..db6eb1e 100644 --- a/source/db2FreeStmtHdl.c +++ b/source/db2FreeStmtHdl.c @@ -5,9 +5,6 @@ /** external variables */ /** external prototypes */ -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); -extern void db2Debug (int level, const char* message, ...); extern void db2Error (db2error sqlstate, const char* message); extern SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleType, int line, char* file); @@ -23,14 +20,14 @@ void db2FreeStmtHdl (HdlEntry* handlep, DB2ConnEntry* connp) { HdlEntry* prev_entryp = NULL; SQLRETURN rc = 0; - db2Entry(1,"> db2FreeStmtHdl.c::db2FreeStmtHdl(handlep,connp)"); - db2Debug(2,"handlep: %x ->hsql: %d ->type: %d ->next: %x", handlep, handlep->hsql, handlep->type, handlep->next); - db2Debug(2,"connp : %x ->handlelist: %x", connp, connp->handlelist); + db2Entry1("(handlep: %x, connp: %x)",handlep, connp); + db2Debug2("handlep: %x ->hsql: %d ->type: %d ->next: %x", handlep, handlep->hsql, handlep->type, handlep->next); + db2Debug2("connp : %x ->handlelist: %x", connp, connp->handlelist); /* find the predecessor of handlep in the list of handles starting from connp->handlelist*/ prev_entryp = findhdlEntry(connp->handlelist, handlep->hsql); /* remember prev_entryp might be actually the root element at conp->handlelist*/ - db2Debug(3,"prev_entryp: %x ->hsql : %d ->type : %d->next : %x", prev_entryp, prev_entryp->hsql, prev_entryp->type, prev_entryp->next); + db2Debug3("prev_entryp: %x ->hsql : %d ->type : %d->next : %x", prev_entryp, prev_entryp->hsql, prev_entryp->type, prev_entryp->next); /* release the handle */ rc = SQLFreeHandle(handlep->type, handlep->hsql); @@ -41,29 +38,29 @@ void db2FreeStmtHdl (HdlEntry* handlep, DB2ConnEntry* connp) { /* we closed the one and only element of connp->handlelist */ /* entryp->next must be NULL, so it is safe to assign it to connp->handlelist*/ connp->handlelist = entryp->next; - db2Debug(3,"connp->handlelist: '%x'", connp->handlelist); + db2Debug3("connp->handlelist: '%x'", connp->handlelist); } else { /* we closed one element of connp->handlelist */ /* here we need to set handlep->next to prev_entryp->next isolating entryp for subsequent release*/ prev_entryp->next = handlep->next; - db2Debug(3,"prev_entryp->next: '%x'", prev_entryp->next); + db2Debug3("prev_entryp->next: '%x'", prev_entryp->next); } - db2Debug(2,"HdlEntry freeed: %x",entryp); + db2Debug2("HdlEntry freeed: %x",entryp); free (entryp); - db2Exit(1,"< db2FreeStmtHdl.c::db2FreeStmtHdl"); + db2Exit1(); } /* findhdlEntry */ static HdlEntry* findhdlEntry (HdlEntry* start, SQLHANDLE hsql) { HdlEntry* step = NULL; HdlEntry* prev = start; - db2Entry(4,"> db2FreeStmtHdl.c::findhdlEntry"); + db2Entry4(); for (step = start; step != NULL; step = step->next){ if (step->hsql == hsql) { break; } prev = step; } - db2Exit(4,"< db2FreeStmtHdl.c::findhdlEntry : %x", prev); + db2Exit4(": %x", prev); return prev; } diff --git a/source/db2GetFdwState.c b/source/db2GetFdwState.c index af85c11..e43f0bd 100644 --- a/source/db2GetFdwState.c +++ b/source/db2GetFdwState.c @@ -16,9 +16,6 @@ extern DB2Session* db2GetSession (const char* connectstring, char* user, char* extern DB2Table* db2Describe (DB2Session* session, char* schema, char* table, char* pgname, long max_long, char* noencerr, char* batchsz); extern char* db2CopyText (const char* string, int size, int quote); extern char* c2name (short fcType); -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); -extern void db2Debug (int level, const char* message, ...); extern void* db2alloc (const char* type, size_t size); extern void db2free (void* p); extern char* db2strdup (const char* source); @@ -52,7 +49,7 @@ DB2FdwState* db2GetFdwState (Oid foreigntableid, double* sample_percent, bool de char* batchsz = NULL; long max_long = 0; - db2Entry(1,"> db2GetFdwState.c::db2GetFdwState"); + db2Entry1(); /* Get all relevant options from the foreign table, the user mapping, the foreign server and the foreign data wrapper. */ getOptions (foreigntableid, &options); @@ -109,7 +106,7 @@ DB2FdwState* db2GetFdwState (Oid foreigntableid, double* sample_percent, bool de } } - db2Exit(1,"< db2GetFdwState.c::db2GetFdwState"); + db2Exit1(": %x", fdwState); return fdwState; } @@ -134,7 +131,7 @@ DB2FdwDirectModifyState* db2GetFdwDirectModifyState (Oid foreigntableid, double* char* batchsz = NULL; long max_long = 0; - db2Entry(1,"> db2GetFdwState.c::db2GetFdwDirectModifyState"); + db2Entry1(); /* Get all relevant options from the foreign table, the user mapping, the foreign server and the foreign data wrapper. */ getOptions (foreigntableid, &options); @@ -184,7 +181,7 @@ DB2FdwDirectModifyState* db2GetFdwDirectModifyState (Oid foreigntableid, double* } fdwState->session = db2GetSession (fdwState->dbserver, fdwState->user, fdwState->password, fdwState->jwt_token, fdwState->nls_lang, GetCurrentTransactionNestLevel () ); - db2Exit(1,"< db2GetFdwState.c::db2GetFdwDirectModifyState"); + db2Exit1(); return fdwState; } @@ -198,7 +195,7 @@ static DB2Table* describeForeignTable (Oid foreigntableid, char* schema, char* t TupleDesc tupdesc; int length = 0; - db2Entry(2,"> db2GetFdwState.c::describeForeignTable"); + db2Entry2(); db2Table = (DB2Table*)db2alloc("db2_table", sizeof (DB2Table)); /* get a complete quoted table name */ @@ -220,27 +217,27 @@ static DB2Table* describeForeignTable (Oid foreigntableid, char* schema, char* t db2free (qschema); db2Table->name = tablename; - db2Debug(3,"table description"); - db2Debug(3,"db2Table->name : '%s'", db2Table->name); + db2Debug3("table description"); + db2Debug3("db2Table->name : '%s'", db2Table->name); db2Table->pgname = pgname; - db2Debug(3,"db2Table->pgname : '%s'", db2Table->pgname); + db2Debug3("db2Table->pgname : '%s'", db2Table->pgname); db2Table->batchsz = DEFAULT_BATCHSZ; if (batchsz != NULL) { char* end; db2Table->batchsz = strtol(batchsz,&end,10); - db2Debug(3,"db2Table->batchsz : %d", db2Table->batchsz); + db2Debug3("db2Table->batchsz : %d", db2Table->batchsz); } rel = table_open (foreigntableid, NoLock); tupdesc = rel->rd_att; db2Table->npgcols = tupdesc->natts; - db2Debug(3,"db2Table->npgcols : %d", db2Table->npgcols); + db2Debug3("db2Table->npgcols : %d", db2Table->npgcols); db2Table->ncols = tupdesc->natts; - db2Debug(3,"db2Table->ncols : %d", db2Table->ncols); + db2Debug3("db2Table->ncols : %d", db2Table->ncols); db2Table->cols = (DB2Column**) db2alloc ("db2Table->cols", sizeof (DB2Column*) * db2Table->ncols); - db2Debug(3,"db2Table->cols : %x", db2Table->cols); + db2Debug3("db2Table->cols : %x", db2Table->cols); /* loop through foreign table columns */ for (int i = 0, cidx = 0; i < tupdesc->natts; ++i) { @@ -314,7 +311,7 @@ static DB2Table* describeForeignTable (Oid foreigntableid, char* schema, char* t } } if (!db2type_set || !db2size_set || !db2bytes_set || !db2chars_set || !db2scale_set || !db2nulls_set || !db2codepage_set) { - db2Debug(2,"INFO: column %d - %s without required options, discarding db2Table", cidx, db2Table->cols[cidx]->pgname); + db2Debug2("INFO: column %d - %s without required options, discarding db2Table", cidx, db2Table->cols[cidx]->pgname); db2free (db2Table); db2Table = NULL; break; @@ -384,32 +381,32 @@ static DB2Table* describeForeignTable (Oid foreigntableid, char* schema, char* t default: break; } - db2Debug(3,"db2Table->cols >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); - db2Debug(3,"db2Table->cols[%d] : %x" , cidx, db2Table->cols[cidx]); - db2Debug(3,"db2Table->cols[%d]->colName : %s" , cidx, db2Table->cols[cidx]->colName); - db2Debug(3,"db2Table->cols[%d]->colType : %d - (%s)" , cidx, db2Table->cols[cidx]->colType,c2name(db2Table->cols[cidx]->colType)); - db2Debug(3,"db2Table->cols[%d]->colSize : %ld", cidx, db2Table->cols[cidx]->colSize); - db2Debug(3,"db2Table->cols[%d]->colScale : %d" , cidx, db2Table->cols[cidx]->colScale); - db2Debug(3,"db2Table->cols[%d]->colNulls : %d" , cidx, db2Table->cols[cidx]->colNulls); - db2Debug(3,"db2Table->cols[%d]->colChars : %ld", cidx, db2Table->cols[cidx]->colChars); - db2Debug(3,"db2Table->cols[%d]->colBytes : %ld", cidx, db2Table->cols[cidx]->colBytes); - db2Debug(3,"db2Table->cols[%d]->colPrimKeyPart : %d" , cidx, db2Table->cols[cidx]->colPrimKeyPart); - db2Debug(3,"db2Table->cols[%d]->colCodepage : %d" , cidx, db2Table->cols[cidx]->colCodepage); - db2Debug(3,"db2Table->cols[%d]->pgrelid : %d" , cidx, db2Table->cols[cidx]->pgrelid); - db2Debug(3,"db2Table->cols[%d]->pgname : %s" , cidx, db2Table->cols[cidx]->pgname); - db2Debug(3,"db2Table->cols[%d]->pgattnum : %d" , cidx, db2Table->cols[cidx]->pgattnum); - db2Debug(3,"db2Table->cols[%d]->pgtype : %d" , cidx, db2Table->cols[cidx]->pgtype); - db2Debug(3,"db2Table->cols[%d]->pgtypmod : %d" , cidx, db2Table->cols[cidx]->pgtypmod); - db2Debug(3,"db2Table->cols[%d]->used : %d" , cidx, db2Table->cols[cidx]->used); - db2Debug(3,"db2Table->cols[%d]->pkey : %d" , cidx, db2Table->cols[cidx]->pkey); - db2Debug(3,"db2Table->cols[%d]->val_size : %ld", cidx, db2Table->cols[cidx]->val_size); - db2Debug(3,"db2Table->cols[%d]->noencerr : %d" , cidx, db2Table->cols[cidx]->noencerr); + db2Debug3("db2Table->cols >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); + db2Debug3("db2Table->cols[%d] : %x" , cidx, db2Table->cols[cidx]); + db2Debug3("db2Table->cols[%d]->colName : %s" , cidx, db2Table->cols[cidx]->colName); + db2Debug3("db2Table->cols[%d]->colType : %d - (%s)" , cidx, db2Table->cols[cidx]->colType,c2name(db2Table->cols[cidx]->colType)); + db2Debug3("db2Table->cols[%d]->colSize : %ld", cidx, db2Table->cols[cidx]->colSize); + db2Debug3("db2Table->cols[%d]->colScale : %d" , cidx, db2Table->cols[cidx]->colScale); + db2Debug3("db2Table->cols[%d]->colNulls : %d" , cidx, db2Table->cols[cidx]->colNulls); + db2Debug3("db2Table->cols[%d]->colChars : %ld", cidx, db2Table->cols[cidx]->colChars); + db2Debug3("db2Table->cols[%d]->colBytes : %ld", cidx, db2Table->cols[cidx]->colBytes); + db2Debug3("db2Table->cols[%d]->colPrimKeyPart : %d" , cidx, db2Table->cols[cidx]->colPrimKeyPart); + db2Debug3("db2Table->cols[%d]->colCodepage : %d" , cidx, db2Table->cols[cidx]->colCodepage); + db2Debug3("db2Table->cols[%d]->pgrelid : %d" , cidx, db2Table->cols[cidx]->pgrelid); + db2Debug3("db2Table->cols[%d]->pgname : %s" , cidx, db2Table->cols[cidx]->pgname); + db2Debug3("db2Table->cols[%d]->pgattnum : %d" , cidx, db2Table->cols[cidx]->pgattnum); + db2Debug3("db2Table->cols[%d]->pgtype : %d" , cidx, db2Table->cols[cidx]->pgtype); + db2Debug3("db2Table->cols[%d]->pgtypmod : %d" , cidx, db2Table->cols[cidx]->pgtypmod); + db2Debug3("db2Table->cols[%d]->used : %d" , cidx, db2Table->cols[cidx]->used); + db2Debug3("db2Table->cols[%d]->pkey : %d" , cidx, db2Table->cols[cidx]->pkey); + db2Debug3("db2Table->cols[%d]->val_size : %ld", cidx, db2Table->cols[cidx]->val_size); + db2Debug3("db2Table->cols[%d]->noencerr : %d" , cidx, db2Table->cols[cidx]->noencerr); } ++cidx; } table_close (rel, NoLock); - db2Exit(2,"< db2GetFdwState.c::describeForeignTable : %x", db2Table); + db2Exit2(": %x", db2Table); return db2Table; } @@ -423,7 +420,7 @@ static void getColumnData (DB2Table* db2Table, Oid foreigntableid) { TupleDesc tupdesc; int i, index; - db2Entry(4,"> db2GetFdwState.c::getColumnData"); + db2Entry4(); rel = table_open (foreigntableid, NoLock); tupdesc = rel->rd_att; @@ -467,7 +464,7 @@ static void getColumnData (DB2Table* db2Table, Oid foreigntableid) { } table_close (rel, NoLock); - db2Exit(4,"< db2GetFdwState.c::getColumnData"); + db2Exit4(); } /* getOptions @@ -481,7 +478,7 @@ static void getOptions (Oid foreigntableid, List** options) { UserMapping* mapping = NULL; ForeignTable* table = NULL; - db2Entry(4,"> db2GetFdwState.c::getOptions"); + db2Entry4(); /** Gather all data for the foreign table. */ table = GetForeignTable(foreigntableid); if (table != NULL) { @@ -490,25 +487,25 @@ static void getOptions (Oid foreigntableid, List** options) { if (server != NULL) { wrapper = GetForeignDataWrapper(server->fdwid); } else { - db2Debug(5,"unable to GetForeignServer: %d", table->serverid); + db2Debug5("unable to GetForeignServer: %d", table->serverid); } /* later options override earlier ones */ *options = NIL; if (wrapper != NULL) *options = list_concat(*options, wrapper->options); else - db2Debug(5,"unable to get wrapper options"); + db2Debug5("unable to get wrapper options"); if (server != NULL) *options = list_concat(*options, server->options); else - db2Debug(5,"unable to get server options"); + db2Debug5("unable to get server options"); if (mapping != NULL) *options = list_concat(*options, mapping->options); else - db2Debug(5,"unable to get mapping options"); + db2Debug5("unable to get mapping options"); *options = list_concat(*options, table->options); } else { - db2Debug(5,"unable to GetForeignTable: %d",foreigntableid); + db2Debug5("unable to GetForeignTable: %d",foreigntableid); } - db2Exit(4,"< db2GetFdwState.c::getOptions"); + db2Exit4(); } \ No newline at end of file diff --git a/source/db2GetForeignJoinPaths.c b/source/db2GetForeignJoinPaths.c index b4be157..8ffa747 100644 --- a/source/db2GetForeignJoinPaths.c +++ b/source/db2GetForeignJoinPaths.c @@ -7,9 +7,6 @@ #include "DB2FdwState.h" /** external prototypes */ -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); -extern void db2Debug (int level, const char* message, ...); extern char* deparseExpr (PlannerInfo* root, RelOptInfo* foreignrel, Expr* expr, List** params); extern char* db2strdup (const char* source); extern void* db2alloc (const char* type, size_t size); @@ -30,23 +27,23 @@ void db2GetForeignJoinPaths (PlannerInfo * root, RelOptInfo * joinrel, RelOptInf Cost startup_cost; Cost total_cost; - db2Entry(1,"> db2GetForeignJoinPaths.c::db2GetForeignJoinPaths"); + db2Entry1(); /* Currently we don't push-down joins in query for UPDATE/DELETE. * This would require a path for EvalPlanQual. * This restriction might be relaxed in a later release. */ if (root->parse->commandType != CMD_SELECT) { - db2Debug(2,"db2_fdw: don't push down join because it is no SELECT"); + db2Debug2("db2_fdw: don't push down join because it is no SELECT"); } else { /* N-way join is not supported, due to the column definition infrastracture. * If we can track relid mapping of join relations, we can support N-way join. */ if (!IS_SIMPLE_REL (outerrel) || !IS_SIMPLE_REL (innerrel)) { - db2Debug(2,"either outerrel or ínnerel is not a simple relation"); + db2Debug2("either outerrel or ínnerel is not a simple relation"); } else { /* skip if this join combination has been considered already */ if (joinrel->fdw_private) { - db2Debug(2,"this join combination has been considered already"); + db2Debug2("this join combination has been considered already"); } else { /* Create unfinished DB2FdwState which is used to indicate * that the join relation has already been considered, so that we won't waste @@ -109,7 +106,7 @@ void db2GetForeignJoinPaths (PlannerInfo * root, RelOptInfo * joinrel, RelOptInf } } } - db2Exit(1,"< db2GetForeignJoinPaths.c::db2GetForeignJoinPaths"); + db2Exit1(); } /* foreign_join_ok @@ -126,7 +123,7 @@ static bool foreign_join_ok (PlannerInfo * root, RelOptInfo * joinrel, JoinType List* otherclauses = NULL; char* tabname = NULL;/* for warning messages */ - db2Entry(1,"> db2GetForeignJoinPaths.c::foreign_join_ok"); + db2Entry1(); /* we only support pushing down INNER joins */ if (jointype != JOIN_INNER) return false; @@ -299,7 +296,7 @@ static bool foreign_join_ok (PlannerInfo * root, RelOptInfo * joinrel, JoinType fdwState->db2Table->npgcols = fdwState->db2Table->ncols; - db2Exit(1,"< db2GetForeignJoinPaths.c::foreign_join_ok"); + db2Exit1(); return true; } diff --git a/source/db2GetForeignModifyBatchSize.c b/source/db2GetForeignModifyBatchSize.c index 9936621..965ef71 100644 --- a/source/db2GetForeignModifyBatchSize.c +++ b/source/db2GetForeignModifyBatchSize.c @@ -10,9 +10,6 @@ /** external variables */ /** external prototypes */ -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); -extern void db2Debug (int level, const char* message, ...); /** local prototypes */ int db2GetForeignModifyBatchSize(ResultRelInfo *rinfo); @@ -28,7 +25,7 @@ int db2GetForeignModifyBatchSize(ResultRelInfo *rinfo) { int batch_size = 1; DB2FdwState* fmstate = (DB2FdwState*) rinfo->ri_FdwState; - db2Entry(1,"> db2GetForeignModifyBatchSize.c::db2GetForeignModifyBatchSize"); + db2Entry1(); /* should be called only once */ Assert(rinfo->ri_BatchSize == 0); @@ -71,7 +68,7 @@ int db2GetForeignModifyBatchSize(ResultRelInfo *rinfo) { } } } - db2Exit(1,"< db2GetForeignModifyBatchSize.c::db2GetForeignModifyBatchSize : %d", batch_size); + db2Exit1(": %d", batch_size); return batch_size; } @@ -82,7 +79,7 @@ static int db2_get_batch_size_option(Relation rel) { ListCell* lc = NULL; int batch_size = 0; - db2Entry(1,"> db2GetForeignModifyBatchSize.c::db2_get_batch_size_option"); + db2Entry1(); table = GetForeignTable(relid); server = GetForeignServer(table->serverid); @@ -125,7 +122,7 @@ static int db2_get_batch_size_option(Relation rel) { } /* Default: no batching */ batch_size = (batch_size < 1) ? DEFAULT_BATCHSZ : batch_size; - db2Exit(1,"< db2GetForeignModifyBatchSize.c::db2_get_batch_size_option : %d", batch_size); + db2Exit1(": %d", batch_size); return batch_size; } #endif \ No newline at end of file diff --git a/source/db2GetForeignPaths.c b/source/db2GetForeignPaths.c index 89614d0..e403071 100644 --- a/source/db2GetForeignPaths.c +++ b/source/db2GetForeignPaths.c @@ -7,9 +7,6 @@ #include "DB2FdwState.h" /** external prototypes */ -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); -extern void db2Debug (int level, const char* message, ...); extern char* deparseExpr (PlannerInfo* root, RelOptInfo* foreignrel, Expr* expr, List** params); /** local prototypes */ @@ -28,7 +25,7 @@ void db2GetForeignPaths(PlannerInfo* root, RelOptInfo* baserel, Oid foreigntable ListCell* cell; char* delim = " "; - db2Entry(1,"> db2GetForeignPaths.c::db2GetForeignPaths"); + db2Entry1(); initStringInfo (&orderedquery); foreach (cell, root->query_pathkeys) { @@ -118,7 +115,7 @@ void db2GetForeignPaths(PlannerInfo* root, RelOptInfo* baserel, Oid foreigntable ,NIL ) ); - db2Exit(1,"< db2GetForeignPaths.c::db2GetForeignPaths"); + db2Exit1(); } /* find_em_expr_for_rel @@ -128,7 +125,7 @@ static Expr* find_em_expr_for_rel (EquivalenceClass* ec, RelOptInfo* rel) { ListCell* lc_em = NULL; Expr* result = NULL; - db2Entry(4,"> db2GetForeignPaths.c::find_em_expr_for_rel"); + db2Entry4(); foreach (lc_em, ec->ec_members) { EquivalenceMember* em = lfirst (lc_em); if (bms_equal (em->em_relids, rel->relids)) { @@ -137,6 +134,6 @@ static Expr* find_em_expr_for_rel (EquivalenceClass* ec, RelOptInfo* rel) { break; } } - db2Exit(4,"< db2GetForeignPaths.c::find_em_expr_for_rel : %x", result); + db2Exit4(": %x", result); return result; } diff --git a/source/db2GetForeignPlan.c b/source/db2GetForeignPlan.c index 81cf4f5..41c94ae 100644 --- a/source/db2GetForeignPlan.c +++ b/source/db2GetForeignPlan.c @@ -23,9 +23,6 @@ enum FdwPathPrivateIndex { }; /** external prototypes */ -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); -extern void db2Debug (int level, const char* message, ...); extern void* db2alloc (const char* type, size_t size); extern void* db2free (void* pvoid); extern char* db2strdup (const char* source); @@ -61,14 +58,14 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo Index scan_relid; StringInfoData sql; - db2Entry(1,"> db2GetForeignPlan.c::db2GetForeignPlan"); + db2Entry1(); /* Get FDW private data created by db2GetForeignUpperPaths(), if any. */ if (best_path->fdw_private) { has_final_sort = boolVal(list_nth(best_path->fdw_private, FdwPathPrivateHasFinalSort)); has_limit = boolVal(list_nth(best_path->fdw_private, FdwPathPrivateHasLimit)); } - db2Debug(2,"length of tlist: %d", list_length(tlist)); + db2Debug2("length of tlist: %d", list_length(tlist)); // ptlist = build_tlist_to_deparse(foreignrel); ptlist = make_tlist_from_pathtarget(foreignrel->reltarget); @@ -80,7 +77,7 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo ListCell* cell = NULL; int iResCol = 0; - db2Debug(3,"base relation scan: set scan_relid to %d", foreignrel->relid); + db2Debug3("base relation scan: set scan_relid to %d", foreignrel->relid); /* For base relations, set scan_relid as the relid of the relation. */ scan_relid = foreignrel->relid; @@ -88,17 +85,17 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo DB2ResultColumn* resCol = NULL; /* find all the columns to include in the select list */ /* examine each SELECT list entry for Var nodes */ - db2Debug(3,"size of tlist: %d", ptlist_len); + db2Debug3("size of tlist: %d", ptlist_len); foreach (cell, ptlist) { resCol = (DB2ResultColumn*)db2alloc("resultColumn",sizeof(DB2ResultColumn)); getUsedColumns ((Expr*) lfirst (cell), foreignrel, resCol); - db2Debug(3,"resCol->colName: %s", resCol->colName); - db2Debug(3,"resCol->pgattnum: %d", resCol->pgattnum); + db2Debug3("resCol->colName: %s", resCol->colName); + db2Debug3("resCol->pgattnum: %d", resCol->pgattnum); if (resCol->colName != NULL && resCol->pgattnum <= fpinfo->db2Table->npgcols) { resCol->next = fpinfo->resultList; - db2Debug(3,"resCol->next: %x", resCol->next); + db2Debug3("resCol->next: %x", resCol->next); fpinfo->resultList = resCol; - db2Debug(3,"fpinfo->resultList: %x", fpinfo->resultList); + db2Debug3("fpinfo->resultList: %x", fpinfo->resultList); } else { db2free(resCol); } @@ -109,21 +106,21 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo cols = (DB2ResultColumn**)db2alloc("resultColumns", iResCol+1 * sizeof(DB2ResultColumn*)); iResCol = 0; for (resCol = fpinfo->resultList; resCol; resCol = resCol->next) { - db2Debug(2,"resCol: %x", resCol); + db2Debug2("resCol: %x", resCol); cols[iResCol] = resCol; - db2Debug(2,"cols[%d] : %x", iResCol, cols[iResCol]); - db2Debug(2,"cols[%d]->colName: %s", iResCol, cols[iResCol]->colName); - db2Debug(2,"cols[%d]->resnum : %d", iResCol, cols[iResCol]->resnum); + db2Debug2("cols[%d] : %x", iResCol, cols[iResCol]); + db2Debug2("cols[%d]->colName: %s", iResCol, cols[iResCol]->colName); + db2Debug2("cols[%d]->resnum : %d", iResCol, cols[iResCol]->resnum); iResCol++; - db2Debug(2,"resCol->next: %x", resCol->next); + db2Debug2("resCol->next: %x", resCol->next); } // sort the array in ascending order of the pgattnum, so that we can compare it with the order of columns in the foreign table - db2Debug(3,"sorting result cols %d columns by pgattnum", iResCol); + db2Debug3("sorting result cols %d columns by pgattnum", iResCol); qsort(cols, iResCol, sizeof(DB2ResultColumn*), compareResultColumns); // generate the sorted array into the resultList fpinfo->resultList = NULL; for (int idx = 0, cidx = 0; idx < iResCol; idx++) { - db2Debug(3,"result column %d: %s", idx, cols[idx]->colName); + db2Debug3("result column %d: %s", idx, cols[idx]->colName); if (idx > 0) { // check if this column is the same as the one before and if so, skip it if (cols[idx]->pgattnum == cols[idx-1]->pgattnum) { @@ -132,13 +129,13 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo } cols[idx]->next = fpinfo->resultList; cols[idx]->resnum = ++cidx; // result number must be 1 - based - db2Debug(3,"column %s added to result list with resnum %d", cols[idx]->colName, cols[idx]->resnum); + db2Debug3("column %s added to result list with resnum %d", cols[idx]->colName, cols[idx]->resnum); fpinfo->resultList = cols[idx]; } /* examine each condition for Var nodes */ - db2Debug(3,"size of conditions: %d", list_length(foreignrel->baserestrictinfo)); + db2Debug3("size of conditions: %d", list_length(foreignrel->baserestrictinfo)); foreach (cell, foreignrel->baserestrictinfo) { - db2Debug(3,"examine condition"); + db2Debug3("examine condition"); getUsedColumns ((Expr*) lfirst (cell), foreignrel, NULL); } } @@ -173,7 +170,7 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo /* For a base-relation scan, we have to support EPQ recheck, which should recheck all the remote quals. */ fdw_recheck_quals = remote_exprs; } else { - db2Debug(3,"join relation scan: set scan_relid to 0"); + db2Debug3("join relation scan: set scan_relid to 0"); /* Join relation or upper relation - set scan_relid to 0. */ scan_relid = 0; @@ -199,15 +196,15 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo int resnum = 1; /* examine each condition for Tlist nodes; they come in the correct sequence as in the query and do not need to be sorted */ - db2Debug(3,"size of tlist: %d", ptlist_len); + db2Debug3("size of tlist: %d", ptlist_len); foreach (cell, ptlist) { DB2ResultColumn* resCol = (DB2ResultColumn*)db2alloc("resultColumn",sizeof(DB2ResultColumn)); - db2Debug(3,"examine tlist"); + db2Debug3("examine tlist"); resCol->next = fpinfo->resultList; fpinfo->resultList = resCol; resCol->resnum = resnum; getUsedColumns ((Expr*) lfirst (cell), foreignrel, resCol); - db2Debug(3,"result column %d: %s", resCol->resnum, resCol->colName); + db2Debug3("result column %d: %s", resCol->resnum, resCol->colName); resnum++; } } @@ -216,7 +213,7 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo * from outer plan's quals, lest they be evaluated twice, once by the local plan and once by the scan. */ if (outer_plan) { - db2Debug(3,"adjusting outer plan's targetlist and quals to match scan's needs"); + db2Debug3("adjusting outer plan's targetlist and quals to match scan's needs"); /* Right now, we only consider grouping and aggregation beyond joins. * Queries involving aggregates or grouping do not require EPQ mechanism, hence should not have an outer plan here. */ @@ -247,7 +244,7 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo /* Build the query string to be sent for execution, and identify expressions to be sent as parameters. */ initStringInfo(&sql); deparseSelectStmtForRel(&sql, root, foreignrel, ptlist, remote_exprs, best_path->path.pathkeys, has_final_sort, has_limit, false, &retrieved_attrs, ¶ms_list); - db2Debug(2,"deparsed foreign query: %s", sql.data); + db2Debug2("deparsed foreign query: %s", sql.data); /* Remember remote_exprs for possible use by postgresPlanDirectModify */ fpinfo->final_remote_exprs = remote_exprs; @@ -265,7 +262,7 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo * because then they wouldn't be subject to later planner processing. */ fscan = make_foreignscan(tlist, local_exprs, scan_relid, params_list, fdw_private, ptlist, fdw_recheck_quals, outer_plan); - db2Exit(1,"< db2GetForeignPlan.c::db2GetForeignPlan : %x",fscan); + db2Exit1(": %x",fscan); return fscan; } @@ -275,9 +272,9 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo static void getUsedColumns (Expr* expr, RelOptInfo* foreignrel, DB2ResultColumn* resCol) { ListCell* cell = NULL; - db2Entry(3,"> db2GetForeignPlan.c::getUsedColumns"); + db2Entry3(); if (expr != NULL) { - db2Debug(4,"examine node of type: %d", expr->type); + db2Debug4("examine node of type: %d", expr->type); switch (expr->type) { case T_RestrictInfo: getUsedColumns (((RestrictInfo*) expr)->clause, foreignrel, resCol); @@ -298,32 +295,32 @@ static void getUsedColumns (Expr* expr, RelOptInfo* foreignrel, DB2ResultColumn* int index = 0; var = (Var*) expr; - db2Debug(4,"var->varattno: %d", var->varattno); + db2Debug4("var->varattno: %d", var->varattno); /* ignore system columns */ if (var->varattno < 0) break; /* if this is a wholerow reference, we need all columns */ if (var->varattno == 0) { DB2ResultColumn* tmpCol = NULL; - db2Debug(4,"found whole-row reference, need to add all columns"); - db2Debug(4,"fpinfo->resultList: %x", fpinfo->resultList); + db2Debug4("found whole-row reference, need to add all columns"); + db2Debug4("fpinfo->resultList: %x", fpinfo->resultList); // add all columns but the last one here for (index = 0; index < (fpinfo->db2Table->ncols - 1); index++) { if (fpinfo->db2Table->cols[index]->pgname) { tmpCol = (DB2ResultColumn*)db2alloc("resultColumn",sizeof(DB2ResultColumn)); tmpCol->resnum = index+1; copyCol2Result(tmpCol,fpinfo->db2Table->cols[index]); - db2Debug(4,"db2Table[%d]->colName %s added to result list", index, fpinfo->db2Table->cols[index]->colName); + db2Debug4("db2Table[%d]->colName %s added to result list", index, fpinfo->db2Table->cols[index]->colName); tmpCol->next = fpinfo->resultList; - db2Debug(4,"tmpCol-next: %x", tmpCol->next); + db2Debug4("tmpCol-next: %x", tmpCol->next); fpinfo->resultList = tmpCol; - db2Debug(4,"fpinfo->resultList: %x", fpinfo->resultList); + db2Debug4("fpinfo->resultList: %x", fpinfo->resultList); } } // now add the last colum using the resCol passed in, so that the column name in the result list is correct for whole row reference copyCol2Result(resCol,fpinfo->db2Table->cols[index]); resCol->resnum = index+1; - db2Debug(4,"db2Table[%d]->colName %s added to result list", index, fpinfo->db2Table->cols[index]->colName); + db2Debug4("db2Table[%d]->colName %s added to result list", index, fpinfo->db2Table->cols[index]->colName); break; } else { /* get db2Table column index corresponding to this column (-1 if none) */ @@ -359,12 +356,12 @@ static void getUsedColumns (Expr* expr, RelOptInfo* foreignrel, DB2ResultColumn* } ReleaseSysCache(tuple); } - db2Debug(4,"aggref->aggfnoid=%u name=%s%s%s", aggref->aggfnoid, nspname ? nspname : "", nspname ? "." : "", aggname ? aggname : ""); + db2Debug4("aggref->aggfnoid=%u name=%s%s%s", aggref->aggfnoid, nspname ? nspname : "", nspname ? "." : "", aggname ? aggname : ""); if (aggname && strcmp(aggname, "count") == 0) { DB2FdwState* fpinfo = (DB2FdwState*) foreignrel->fdw_private; /* if it's a COUNT(*) then we need an additional result */ DB2Column* col = db2alloc("DB2Column for count(*)",sizeof(DB2Column)); - db2Debug(4,"found COUNT aggregate"); + db2Debug4("found COUNT aggregate"); col->colName = "count"; col->colType = -5; // SQL_BIGINT type in DB2, which can hold the result of COUNT(*) col->colSize = 8; @@ -384,7 +381,7 @@ static void getUsedColumns (Expr* expr, RelOptInfo* foreignrel, DB2ResultColumn* col->noencerr = fpinfo->db2Table->cols[0]->noencerr; // use same noencerr as first column copyCol2Result(resCol,col); } else { - db2Debug(4,"count aggref->args: %d",list_length(aggref->args)); + db2Debug4("count aggref->args: %d",list_length(aggref->args)); foreach (cell, aggref->args) { getUsedColumns ((Expr*) lfirst (cell), foreignrel, resCol); } @@ -544,14 +541,14 @@ static void getUsedColumns (Expr* expr, RelOptInfo* foreignrel, DB2ResultColumn* break; } } - db2Exit(3,"< db2GetForeignPlan.c::getUsedColumns"); + db2Exit3(); } /* copyCol2Result * Copy the column information from the db2Table column to the result column. */ static void copyCol2Result(DB2ResultColumn* resCol, DB2Column* column) { - db2Entry(4,"> db2GetForeignPlan.c::copyCol2Result"); + db2Entry4(); if (resCol && resCol->colName == NULL) { resCol->colName = db2strdup(column->colName); resCol->colType = column->colType; @@ -571,7 +568,7 @@ static void copyCol2Result(DB2ResultColumn* resCol, DB2Column* column) { resCol->val_size = column->val_size; resCol->noencerr = column->noencerr; } - db2Exit(4,"< db2GetForeignPlan.c::copyCol2Result"); + db2Exit4(); } /* compareResultColumns @@ -581,9 +578,9 @@ static int compareResultColumns(const void* a, const void* b) { DB2ResultColumn* colA = *(DB2ResultColumn**) a; DB2ResultColumn* colB = *(DB2ResultColumn**) b; int result = 0; - db2Entry(4,"> db2GetForeignPlan.c::compareResultColumns"); + db2Entry4(); result = (colA->pgattnum - colB->pgattnum); - db2Debug(5,"comparing %s -> pgattnum %d and %s -> pgattnum %d, result = %d", colA->colName, colA->pgattnum, colB->colName, colB->pgattnum, result); - db2Exit(4,"< db2GetForeignPlan.c::compareResultColumns : %d", result); + db2Debug5("comparing %s -> pgattnum %d and %s -> pgattnum %d, result = %d", colA->colName, colA->pgattnum, colB->colName, colB->pgattnum, result); + db2Exit4(": %d", result); return result; } \ No newline at end of file diff --git a/source/db2GetForeignPlanOld.c b/source/db2GetForeignPlanOld.c index 65cba99..6e54088 100644 --- a/source/db2GetForeignPlanOld.c +++ b/source/db2GetForeignPlanOld.c @@ -11,9 +11,6 @@ /** external prototypes */ extern List* serializePlanData (DB2FdwState* fdwState); extern void checkDataType (short db2type, int scale, Oid pgtype, const char* tablename, const char* colname); -extern void db2Debug1 (const char* message, ...); -extern void db2Debug2 (const char* message, ...); -extern void db2Debug3 (const char* message, ...); extern void db2free (void* p); extern char* db2strdup (const char* p); extern char* deparseExpr (PlannerInfo* root, RelOptInfo* rel, Expr* expr, List** params); diff --git a/source/db2GetForeignRelSize.c b/source/db2GetForeignRelSize.c index 876123c..0788a3c 100644 --- a/source/db2GetForeignRelSize.c +++ b/source/db2GetForeignRelSize.c @@ -13,9 +13,6 @@ /** external prototypes */ extern DB2FdwState* db2GetFdwState (Oid foreigntableid, double* sample_percent, bool describe); -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); -extern void db2Debug (int level, const char* message, ...); extern char* deparseWhereConditions (PlannerInfo* root, RelOptInfo* baserel); extern void db2free (void* p); extern void classifyConditions (PlannerInfo* root, RelOptInfo* baserel, List* input_conds, List** remote_conds, List** local_conds); @@ -38,14 +35,14 @@ static List* ExtractExtensionList (const char* extensionsString, bool warnOnMi void db2GetForeignRelSize (PlannerInfo* root, RelOptInfo* baserel, Oid foreigntableid) { DB2FdwState* fdwState = NULL; - db2Entry(1,"> db2GetForeignRelSize.c::db2GetForeignRelSize"); + db2Entry1(); /* get connection options, connect and get the remote table description */ fdwState = db2GetFdwState(foreigntableid, NULL, true); /* store the state so that the other planning functions can use it */ baserel->fdw_private = (void*) fdwState; db2PopulateFdwStateOld(root, baserel,foreigntableid); db2PopulateFdwStateNew(root, baserel,foreigntableid); - db2Exit(1,"< db2GetForeignRelSize.c::db2GetForeignRelSize"); + db2Exit1(); } static void db2PopulateFdwStateOld(PlannerInfo* root, RelOptInfo* baserel, Oid foreigntableid){ @@ -53,18 +50,16 @@ static void db2PopulateFdwStateOld(PlannerInfo* root, RelOptInfo* baserel, Oid f int i = 0; double ntuples = -1; - db2Entry(1,"> db2GetForeignRelSize.c::db2PopulateFdwStateOld"); - /** Store the table OID in each table column. - * This is redundant for base relations, but join relations will - * have columns from different tables, and we have to keep track of them. + db2Entry1(); + /* Store the table OID in each table column. + * This is redundant for base relations, but join relations will have columns from different tables, and we have to keep track of them. */ for (i = 0; i < fdwState->db2Table->ncols; ++i) { fdwState->db2Table->cols[i]->pgrelid = baserel->relid; } - /** Classify conditions into remote_conds or local_conds. + /* Classify conditions into remote_conds or local_conds. * These parameters are used in foreign_join_ok and db2GetForeignPlan. - * Those conditions that can be pushed down will be collected into - * an DB2 WHERE clause. + * Those conditions that can be pushed down will be collected into an DB2 WHERE clause. */ fdwState->where_clause = deparseWhereConditions ( root, baserel ); @@ -87,14 +82,14 @@ static void db2PopulateFdwStateOld(PlannerInfo* root, RelOptInfo* baserel, Oid f } /* estimate total cost as startup cost + 10 * (returned rows) */ fdwState->total_cost = fdwState->startup_cost + baserel->rows * 10.0; - db2Exit(1,"< db2GetForeignRelSize.c::db2PopulateFdwStateOld"); + db2Exit1(); } static void db2PopulateFdwStateNew(PlannerInfo* root, RelOptInfo* baserel, Oid foreigntableid){ DB2FdwState* fpinfo = (DB2FdwState*)baserel->fdw_private; ListCell* lc = NULL; - db2Entry(1,"> db2GetForeignRelSize.c::db2PopulateFdwStateNew"); + db2Entry1(); /* Base foreign tables need to be pushed down always. */ fpinfo->pushdown_safe = true; @@ -198,7 +193,7 @@ static void db2PopulateFdwStateNew(PlannerInfo* root, RelOptInfo* baserel, Oid f fpinfo->hidden_subquery_rels = NULL; /* Set the relation index. */ fpinfo->relation_index = baserel->relid; - db2Exit(1,"< db2GetForeignRelSize.c::db2PopulateFdwStateNew"); + db2Exit1(); } /* Parse options from foreign server and apply them to fpinfo. @@ -207,7 +202,7 @@ static void db2PopulateFdwStateNew(PlannerInfo* root, RelOptInfo* baserel, Oid f static void apply_server_options(DB2FdwState* fpinfo) { ListCell* lc; - db2Entry(4,"> db2GetForeignRelSize.c::apply_server_options"); + db2Entry4(); foreach(lc, fpinfo->fserver->options) { DefElem* def = (DefElem*) lfirst(lc); @@ -224,7 +219,7 @@ static void apply_server_options(DB2FdwState* fpinfo) { else if (strcmp(def->defname, "async_capable") == 0) fpinfo->async_capable = defGetBoolean(def); } - db2Exit(4,"< db2GetForeignRelSize.c::apply_server_options"); + db2Exit4(); } /* Parse options from foreign table and apply them to fpinfo. @@ -233,7 +228,7 @@ static void apply_server_options(DB2FdwState* fpinfo) { static void apply_table_options(DB2FdwState* fpinfo) { ListCell* lc; - db2Entry(4,"> db2GetForeignRelSize.c::apply_table_options"); + db2Entry4(); foreach(lc, fpinfo->ftable->options) { DefElem* def = (DefElem*) lfirst(lc); @@ -244,7 +239,7 @@ static void apply_table_options(DB2FdwState* fpinfo) { else if (strcmp(def->defname, "async_capable") == 0) fpinfo->async_capable = defGetBoolean(def); } - db2Exit(4,"< db2GetForeignRelSize.c::apply_table_options"); + db2Exit4(); } /* Parse a comma-separated string and return a List of the OIDs of the extensions named in the string. If any names in the list cannot be @@ -255,7 +250,7 @@ static List * ExtractExtensionList(const char *extensionsString, bool warnOnMiss List* extlist = NIL; ListCell* lc = NULL; - db2Entry(4,"> db2GetForeignRelSize.c::ExtractExtensionList"); + db2Entry4("extensionString: %s, warnOnMissing: %d",extensionsString, warnOnMissing); /* SplitIdentifierString scribbles on its input, so pstrdup first */ if (!SplitIdentifierString(pstrdup(extensionsString), ',', &extlist)) { /* syntax error in name list */ @@ -264,7 +259,7 @@ static List * ExtractExtensionList(const char *extensionsString, bool warnOnMiss foreach(lc, extlist) { const char *extension_name = (const char *) lfirst(lc); - Oid extension_oid = get_extension_oid(extension_name, true); + Oid extension_oid = get_extension_oid(extension_name, true); if (OidIsValid(extension_oid)) { extensionOids = lappend_oid(extensionOids, extension_oid); @@ -273,6 +268,6 @@ static List * ExtractExtensionList(const char *extensionsString, bool warnOnMiss } } list_free(extlist); - db2Exit(4,"< db2GetForeignRelSize.c::ExtractExtensionList : %x", extensionOids); + db2Exit4(": %x", extensionOids); return extensionOids; } diff --git a/source/db2GetForeignUpperPaths.c b/source/db2GetForeignUpperPaths.c index 8a0d3a1..ef2904b 100644 --- a/source/db2GetForeignUpperPaths.c +++ b/source/db2GetForeignUpperPaths.c @@ -18,9 +18,6 @@ #include "DB2FdwPathExtraData.h" /** external prototypes */ -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); -extern void db2Debug (int level, const char* message, ...); extern void* db2alloc (const char* type, size_t size); extern char* db2strdup (const char* source); extern void* db2free (void* p); @@ -50,112 +47,111 @@ static bool foreign_grouping_ok (PlannerInfo* root, RelOptInfo* gr static void merge_fdw_options (DB2FdwState* fpinfo, const DB2FdwState* fpinfo_o, const DB2FdwState* fpinfo_i); void db2GetForeignUpperPaths(PlannerInfo *root, UpperRelationKind stage, RelOptInfo *input_rel, RelOptInfo *output_rel, void *extra) { - db2Entry(1,"> db2GetForeignUpperPaths.c::db2GetForeignUpperPaths"); + db2Entry1(); if (root != NULL && root->parse != NULL && input_rel->fdw_private != NULL && output_rel->fdw_private == NULL) { Query* query = root->parse; - db2Debug(3,"query->hasAggs : %s", query->hasAggs ? "true" : "false"); - db2Debug(3,"query->hasWindowFuncs : %s", query->hasWindowFuncs ? "true" : "false"); - db2Debug(3,"query->hasDistinctOn : %s", query->hasDistinctOn ? "true" : "false"); - db2Debug(3,"query->hasTargetSRFs : %s", query->hasTargetSRFs ? "true" : "false"); - db2Debug(3,"query->hasForUpdate : %s", query->hasForUpdate ? "true" : "false"); + db2Debug3("query->hasAggs : %s", query->hasAggs ? "true" : "false"); + db2Debug3("query->hasWindowFuncs : %s", query->hasWindowFuncs ? "true" : "false"); + db2Debug3("query->hasDistinctOn : %s", query->hasDistinctOn ? "true" : "false"); + db2Debug3("query->hasTargetSRFs : %s", query->hasTargetSRFs ? "true" : "false"); + db2Debug3("query->hasForUpdate : %s", query->hasForUpdate ? "true" : "false"); #if PG_VERSION_NUM >= 180000 - db2Debug(3,"query->hasGroupRTE : %s", query->hasGroupRTE ? "true" : "false"); + db2Debug3("query->hasGroupRTE : %s", query->hasGroupRTE ? "true" : "false"); #endif - db2Debug(3,"query->hasModifyingCTE: %s", query->hasModifyingCTE ? "true" : "false"); - db2Debug(3,"query->hasRecursive : %s", query->hasRecursive ? "true" : "false"); - db2Debug(3,"query->hasSubLinks : %s", query->hasSubLinks ? "true" : "false"); - db2Debug(3,"query->hasRowSecurity : %s", query->hasRowSecurity ? "true" : "false"); + db2Debug3("query->hasModifyingCTE: %s", query->hasModifyingCTE ? "true" : "false"); + db2Debug3("query->hasRecursive : %s", query->hasRecursive ? "true" : "false"); + db2Debug3("query->hasSubLinks : %s", query->hasSubLinks ? "true" : "false"); + db2Debug3("query->hasRowSecurity : %s", query->hasRowSecurity ? "true" : "false"); // if (db2_is_shippable(root, stage, input_rel, output_rel)) { db2CloneFdwStateUpper(root, input_rel, output_rel); switch (stage) { case UPPERREL_SETOP: // UNION/INTERSECT/EXCEPT - db2Debug(2,"stage: %d - UPPERREL_SETOP", stage); + db2Debug2("stage: %d - UPPERREL_SETOP", stage); break; case UPPERREL_PARTIAL_GROUP_AGG: // partial grouping/aggregation - db2Debug(2,"stage: %d - UPPERREL_PARTIAL_GROUP_AGG", stage); - db2Debug(2,"query->hasAggs: %d", query->hasAggs); - db2Debug(2,"query->groupClause: %x", query->groupClause); + db2Debug2("stage: %d - UPPERREL_PARTIAL_GROUP_AGG", stage); + db2Debug2("query->hasAggs: %d", query->hasAggs); + db2Debug2("query->groupClause: %x", query->groupClause); if (query->hasAggs || query->groupClause != NIL) { add_foreign_grouping_paths(root, input_rel, output_rel, (GroupPathExtraData*) extra); } break; case UPPERREL_GROUP_AGG: { // grouping/aggregation - db2Debug(2,"stage: %d - UPPERREL_GROUP_AGG", stage); - db2Debug(2,"query->hasAggs: %d", query->hasAggs); - db2Debug(2,"query->groupClause: %x", query->groupClause); + db2Debug2("stage: %d - UPPERREL_GROUP_AGG", stage); + db2Debug2("query->hasAggs: %d", query->hasAggs); + db2Debug2("query->groupClause: %x", query->groupClause); if (query->hasAggs || query->groupClause != NIL) { add_foreign_grouping_paths(root, input_rel, output_rel, (GroupPathExtraData*) extra); } } break; case UPPERREL_WINDOW: { // window functions - db2Debug(2,"stage: %d - UPPERREL_WINDOW", stage); - db2Debug(2,"query->hasWindowFuncs: %d", query->hasWindowFuncs); + db2Debug2("stage: %d - UPPERREL_WINDOW", stage); + db2Debug2("query->hasWindowFuncs: %d", query->hasWindowFuncs); if (query->hasWindowFuncs) { - db2Debug(2,"window function push down not yet implemented"); + db2Debug2("window function push down not yet implemented"); } } break; #if PG_VERSION_NUM >= 150000 case UPPERREL_PARTIAL_DISTINCT: { // partial "SELECT DISTINCT" - db2Debug(2,"stage: %d - UPPERREL_PARTIAL_DISTINCT", stage); - db2Debug(2,"query->hasDistinctOn: %d", query->hasDistinctOn); + db2Debug2("stage: %d - UPPERREL_PARTIAL_DISTINCT", stage); + db2Debug2("query->hasDistinctOn: %d", query->hasDistinctOn); if (query->hasDistinctOn) { - db2Debug(2,"distinct function push down not yet implemented"); + db2Debug2("distinct function push down not yet implemented"); } } break; #endif case UPPERREL_DISTINCT: { // "SELECT DISTINCT" - db2Debug(2,"stage: %d - UPPERREL_DISTINCT", stage); - db2Debug(2,"query->hasDistinctOn: %d", query->hasDistinctOn); + db2Debug2("stage: %d - UPPERREL_DISTINCT", stage); + db2Debug2("query->hasDistinctOn: %d", query->hasDistinctOn); if (query->hasDistinctOn) { - db2Debug(2,"distinct function push down not yet implemented"); + db2Debug2("distinct function push down not yet implemented"); } } break; case UPPERREL_ORDERED: // ORDER BY - db2Debug(2,"stage: %d - UPPERREL_ORDERED", stage); - db2Debug(2,"query->setOperations: %x", query->setOperations); + db2Debug2("stage: %d - UPPERREL_ORDERED", stage); + db2Debug2("query->setOperations: %x", query->setOperations); if (query->setOperations != NULL) { add_foreign_ordered_paths(root, input_rel, output_rel); } break; case UPPERREL_FINAL: // any remaining top-level actions - db2Debug(2,"stage: %d - UPPERREL_FINAL", stage); + db2Debug2("stage: %d - UPPERREL_FINAL", stage); add_foreign_final_paths(root, input_rel, output_rel, (FinalPathExtraData*) extra); break; default: // unknown stage type - db2Debug(2,"stage: %d - unknown", stage); + db2Debug2("stage: %d - unknown", stage); break; } // } else { -// db2Debug(2,"stage and or functions are not shippable to DB2"); +// db2Debug2("stage and or functions are not shippable to DB2"); // } } else { - db2Debug(2,"skipping this call"); - db2Debug(2,"root: %x", root); - db2Debug(2,"root->parse: %x", root->parse); - db2Debug(2,"input_rel->fdw_private: %x", input_rel->fdw_private); - db2Debug(2,"output_rel->fdw_private: %x", output_rel->fdw_private); + db2Debug2("skipping this call"); + db2Debug2("root: %x", root); + db2Debug2("root->parse: %x", root->parse); + db2Debug2("input_rel->fdw_private: %x", input_rel->fdw_private); + db2Debug2("output_rel->fdw_private: %x", output_rel->fdw_private); } - db2Exit(1,"< db2GetForeignUpperPaths.c::db2GetForeignUpperPaths"); + db2Exit1(); } -/** db2CloneFdwStateUpper - * Create a deep copy suitable for upper-relation planning. +/* db2CloneFdwStateUpper + * Create a deep copy suitable for upper-relation planning. * - * Rationale: planning can change mutable fields like DB2Column.used and also - * rewrite the params list (createQuery will NULL out entries). We must avoid - * those mutations affecting the original baserel/joinrel planning state. + * Rationale: planning can change mutable fields like DB2Column.used and also rewrite the params list (createQuery will NULL out entries). + * We must avoid those mutations affecting the original baserel/joinrel planning state. */ static void db2CloneFdwStateUpper(PlannerInfo* root, RelOptInfo* input_rel, RelOptInfo* output_rel) { DB2FdwState* fdw_in = (DB2FdwState*)input_rel->fdw_private; DB2FdwState* copy = NULL; - db2Entry(4,"> db2GetForeignUpperPaths.c::db2CloneFdwStateUpper"); + db2Entry4(); if (fdw_in != NULL) { copy = (DB2FdwState*) db2alloc("fdw_state_upper", sizeof(DB2FdwState)); @@ -197,14 +193,14 @@ static void db2CloneFdwStateUpper(PlannerInfo* root, RelOptInfo* input_rel, RelO copy->paramList = NULL; } output_rel->fdw_private = copy; - db2Exit(4,"< db2GetForeignUpperPaths.c::db2CloneFdwStateUpper"); + db2Exit4(); } static DB2Table* db2CloneDb2TableForPlan(const DB2Table* src) { DB2Table* dst = NULL; int i; - db2Entry(4,"> db2GetForeignUpperPaths.c::db2CloneDb2TableForPlan"); + db2Entry4(); if (src != NULL) { dst = (DB2Table*) db2alloc("db2_table_clone", sizeof(DB2Table)); @@ -223,14 +219,14 @@ static DB2Table* db2CloneDb2TableForPlan(const DB2Table* src) { dst->cols = NULL; } } - db2Exit(4,"< db2GetForeignUpperPaths.c::db2CloneDb2TableForPlan : %x", dst); + db2Exit4(": %x", dst); return dst; } static DB2Column* db2CloneDb2ColumnForPlan(const DB2Column* src) { DB2Column* dst = NULL; - db2Entry(4,"> db2GetForeignUpperPaths.c::db2CloneDb2ColumnForPlan"); + db2Entry4(); if (src != NULL) { dst = (DB2Column*) db2alloc("db2_column_clone", sizeof(DB2Column)); /* start with a struct copy, then fix up pointer members */ @@ -239,7 +235,7 @@ static DB2Column* db2CloneDb2ColumnForPlan(const DB2Column* src) { dst->colName = src->colName ? db2strdup(src->colName) : NULL; dst->pgname = src->pgname ? db2strdup(src->pgname) : NULL; } - db2Exit(4,"< db2GetForeignUpperPaths.c::db2CloneDb2ColumnForPlan : %x", dst); + db2Exit4(": %x", dst); return dst; } @@ -258,10 +254,10 @@ static void add_foreign_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel, Cost startup_cost; Cost total_cost; - db2Entry(4,"> db2GetForeignUpperPaths.c:::add_foreign_grouping_paths"); + db2Entry4(); /* Nothing to be done, if there is no grouping or aggregation required. */ if (!parse->groupClause && !parse->groupingSets && !parse->hasAggs && !root->hasHavingQual) { - db2Debug(5,"Nothing to be done, if there is no grouping or aggregation required"); + db2Debug5("Nothing to be done, if there is no grouping or aggregation required"); } else { Assert(extra->patype == PARTITIONWISE_AGGREGATE_NONE || extra->patype == PARTITIONWISE_AGGREGATE_FULL); /* save the input_rel as outerrel in fpinfo */ @@ -281,7 +277,7 @@ static void add_foreign_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel, * Use HAVING qual from extra. In case of child partition, it will have translated Vars. */ if (!foreign_grouping_ok(root, grouped_rel, extra->havingQual)) { - db2Exit(5,"foreigm grouping is not ok"); + db2Debug5("foreigm grouping is not ok"); } else { /* Compute the selectivity and cost of the local_conds, so we don't have to do it over again for each path. * (Currently we create just a single path here, but in future it would be possible that we build more paths @@ -315,7 +311,7 @@ static void add_foreign_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel, add_path(grouped_rel, (Path*) grouppath); } } - db2Exit(4,"< db2GetForeignUpperPaths.c::add_foreign_grouping_paths"); + db2Exit4(); } /* add_foreign_ordered_paths @@ -337,14 +333,14 @@ static void add_foreign_ordered_paths(PlannerInfo *root, RelOptInfo *input_rel, ForeignPath* ordered_path; ListCell* lc; - db2Entry(4,"> db2GetForeignUpperPaths.c::add_foreign_ordered_paths"); + db2Entry4(); // Shouldn't get here unless the query has ORDER BY Assert(parse->sortClause); // We don't support cases where there are any SRFs in the targetlist if (parse->hasTargetSRFs) { - db2Debug(5,"no support where there are any SRFs in the targetlist"); + db2Debug5("no support where there are any SRFs in the targetlist"); } else { bool isSafe = true; @@ -365,7 +361,7 @@ static void add_foreign_ordered_paths(PlannerInfo *root, RelOptInfo *input_rel, Assert(root->query_pathkeys == root->sort_pathkeys); /* Safe to push down if the query_pathkeys is safe to push down */ fpinfo->pushdown_safe = ifpinfo->qp_is_pushdown_safe; - db2Debug(5,"query_pathkeys is safe to push down"); + db2Debug5("query_pathkeys is safe to push down"); } else { // The input_rel should be a grouping relation Assert(input_rel->reloptkind == RELOPT_UPPER_REL && ifpinfo->stage == UPPERREL_GROUP_AGG); @@ -377,19 +373,19 @@ static void add_foreign_ordered_paths(PlannerInfo *root, RelOptInfo *input_rel, EquivalenceClass* pathkey_ec = pathkey->pk_eclass; /* is_foreign_expr would detect volatile expressions as well, but checking ec_has_volatile here saves some cycles. */ if (pathkey_ec->ec_has_volatile) { - db2Debug(5,"ec_has_volatile is true"); + db2Debug5("ec_has_volatile is true"); isSafe = false; break; } /* Can't push down the sort if pathkey's opfamily is not shippable. */ if (!is_shippable(pathkey->pk_opfamily, OperatorFamilyRelationId, fpinfo)) { - db2Debug(5,"pathkey's opfamily is not shippable"); + db2Debug5("pathkey's opfamily is not shippable"); isSafe = false; break; } /* The EC must contain a shippable EM that is computed in input_rel's reltarget, else we can't push down the sort. */ if (find_em_for_rel_target(root, pathkey_ec, input_rel) == NULL) { - db2Debug(5,"non shippable EM that is computed in input_rel's reltarget"); + db2Debug5("non shippable EM that is computed in input_rel's reltarget"); isSafe = false; break; } @@ -425,7 +421,7 @@ static void add_foreign_ordered_paths(PlannerInfo *root, RelOptInfo *input_rel, } } } - db2Exit(4,"< db2GetForeignUpperPaths.c::add_foreign_ordered_paths"); + db2Exit4(); } /* add_foreign_final_paths @@ -449,26 +445,26 @@ static void add_foreign_final_paths(PlannerInfo *root, RelOptInfo *input_rel, Re List* fdw_private = NIL; ForeignPath* final_path = NULL; - db2Entry(4,"> db2GetForeignUpperPaths.c::add_foreign_ordered_paths"); + db2Entry4(); /** Currently, we only support this for SELECT commands */ if (parse->commandType != CMD_SELECT) { - db2Debug(5,"only support SELECT command"); - db2Exit(4,"< db2GetForeignUpperPaths.c::add_foreign_ordered_paths"); + db2Debug5("only support SELECT command"); + db2Exit4(); return; } // No work if there is no FOR UPDATE/SHARE clause and if there is no need to add a LIMIT node if (!parse->rowMarks && !extra->limit_needed) { - db2Debug(5,"no FOR UPDATE/SHARE clause and no need to add a LIMIT node"); - db2Exit(4,"< db2GetForeignUpperPaths.c::add_foreign_ordered_paths"); + db2Debug5("no FOR UPDATE/SHARE clause and no need to add a LIMIT node"); + db2Exit4(); return; } // We don't support cases where there are any SRFs in the targetlist if (parse->hasTargetSRFs) { - db2Debug(5,"no support for any SRFs in the targetlist"); - db2Exit(4,"< db2GetForeignUpperPaths.c::add_foreign_ordered_paths"); + db2Debug5("no support for any SRFs in the targetlist"); + db2Exit4(); return; } @@ -521,8 +517,8 @@ static void add_foreign_final_paths(PlannerInfo *root, RelOptInfo *input_rel, Re /* Safe to push down */ fpinfo->pushdown_safe = true; - db2Debug(5,"created foreign final path; this gets rid of a no-longer-needed outer plan (if any), which makes the EXPLAIN output look cleaner"); - db2Exit(4,"< db2GetForeignUpperPaths.c::add_foreign_ordered_paths"); + db2Debug5("created foreign final path; this gets rid of a no-longer-needed outer plan (if any), which makes the EXPLAIN output look cleaner"); + db2Exit4(); return; } } @@ -530,8 +526,8 @@ static void add_foreign_final_paths(PlannerInfo *root, RelOptInfo *input_rel, Re /* If we get here it means no ForeignPaths; since we would already have considered pushing down all operations for the query to the * remote server, give up on it. */ - db2Debug(5,"no ForeignPaths; since we would already have considered pushing down all operations for the query to the remote server, give up on it"); - db2Exit(4,"< db2GetForeignUpperPaths.c::add_foreign_ordered_paths"); + db2Debug5("no ForeignPaths; since we would already have considered pushing down all operations for the query to the remote server, give up on it"); + db2Exit4(); return; } @@ -557,8 +553,8 @@ static void add_foreign_final_paths(PlannerInfo *root, RelOptInfo *input_rel, Re /* If the underlying relation has any local conditions, the LIMIT/OFFSET cannot be pushed down. */ if (ifpinfo->local_conds) { - db2Debug(5,"the underlying relation has any local conditions, the LIMIT/OFFSET cannot be pushed down"); - db2Exit(4,"< db2GetForeignUpperPaths.c::add_foreign_ordered_paths"); + db2Debug5("the underlying relation has any local conditions, the LIMIT/OFFSET cannot be pushed down"); + db2Exit4(); return; } @@ -570,14 +566,14 @@ static void add_foreign_final_paths(PlannerInfo *root, RelOptInfo *input_rel, Re * Since we do not currently have a way to do a remote-version check (without accessing the remote server), disable pushing the FETCH clause for now. */ if (parse->limitOption == LIMIT_OPTION_WITH_TIES) { - db2Debug(5,"the query has FETCH FIRST .. WITH TIES without ORDER BY"); - db2Exit(4,"< db2GetForeignUpperPaths.c::add_foreign_ordered_paths"); + db2Debug5("the query has FETCH FIRST .. WITH TIES without ORDER BY"); + db2Exit4(); return; } /* Also, the LIMIT/OFFSET cannot be pushed down, if their expressions are not safe to remote. */ if (!is_foreign_expr(root, input_rel, (Expr *) parse->limitOffset) || !is_foreign_expr(root, input_rel, (Expr *) parse->limitCount)) { - db2Debug(5,"the LIMIT/OFFSET cannot be pushed down, if their expressions are not safe to remote"); - db2Exit(4,"< db2GetForeignUpperPaths.c::add_foreign_ordered_paths"); + db2Debug5("the LIMIT/OFFSET cannot be pushed down, if their expressions are not safe to remote"); + db2Exit4(); return; } @@ -624,12 +620,12 @@ static void add_foreign_final_paths(PlannerInfo *root, RelOptInfo *input_rel, Re /* and add it to the final_rel */ add_path(final_rel, (Path*) final_path); - db2Exit(4,"< db2GetForeignUpperPaths.c::add_foreign_ordered_paths"); + db2Exit4(); } /* Adjust the cost estimates of a foreign grouping path to include the cost of generating properly-sorted output. */ static void adjust_foreign_grouping_path_cost(PlannerInfo* root, List* pathkeys, double retrieved_rows, double width, double limit_tuples, int* p_disabled_nodes, Cost* p_startup_cost, Cost* p_run_cost) { - db2Entry(4,"> db2GetForeignUpperPaths.c::adjust_foreign_grouping_path_cost"); + db2Entry4(); /* If the GROUP BY clause isn't sort-able, the plan chosen by the remote side is unlikely to generate properly-sorted output, so it would need * an explicit sort; adjust the given costs with cost_sort(). * Likewise, if the GROUP BY clause is sort-able but isn't a superset of the given pathkeys, adjust the costs with that function. @@ -649,7 +645,7 @@ static void adjust_foreign_grouping_path_cost(PlannerInfo* root, List* pathkeys, *p_startup_cost *= sort_multiplier; *p_run_cost *= sort_multiplier; } - db2Exit(4,"< db2GetForeignUpperPaths.c::adjust_foreign_grouping_path_cost"); + db2Exit4(); } /* Assess whether the aggregation, grouping and having operations can be pushed down to the foreign server. @@ -664,11 +660,11 @@ static bool foreign_grouping_ok(PlannerInfo* root, RelOptInfo* grouped_rel, Node int i = 0; List* tlist = NIL; - db2Entry(4,"> db2GetForeignUpperPaths.c::foreign_grouping_ok"); + db2Entry4(); /* We currently don't support pushing Grouping Sets. */ if (query->groupingSets) { - db2Debug(5,"no support pushing Grouping Sets"); - db2Exit(4,"< db2GetForeignUpperPaths.c::foreign_grouping_ok"); + db2Debug5("no support pushing Grouping Sets"); + db2Exit4(); return false; } @@ -679,8 +675,8 @@ static bool foreign_grouping_ok(PlannerInfo* root, RelOptInfo* grouped_rel, Node * Hence the aggregate cannot be pushed down. */ if (ofpinfo->local_conds) { - db2Debug(5,"foreign_grouping_ok: local_conds found"); - db2Exit(4,"< db2GetForeignUpperPaths.c::foreign_grouping_ok : false"); + db2Debug5("foreign_grouping_ok: local_conds found"); + db2Exit4(": false"); return false; } @@ -708,15 +704,15 @@ static bool foreign_grouping_ok(PlannerInfo* root, RelOptInfo* grouped_rel, Node /* If any GROUP BY expression is not shippable, then we cannot push down aggregation to the foreign server. */ if (!is_foreign_expr(root, grouped_rel, expr)) { - db2Debug(5,"foreign_grouping_ok: non-foreign expr found"); - db2Exit(4,"< db2GetForeignUpperPaths.c::foreign_grouping_ok : false"); + db2Debug5("foreign_grouping_ok: non-foreign expr found"); + db2Exit4(": false"); return false; } /* If it would be a foreign param, we can't put it into the tlist, so we have to fail. */ if (is_foreign_param(root, grouped_rel, expr)) { - db2Debug(5,"foreign_grouping_ok: foreign param found"); - db2Exit(4,"< db2GetForeignUpperPaths.c::foreign_grouping_ok : false"); + db2Debug5("foreign_grouping_ok: foreign param found"); + db2Exit4(": false"); return false; } @@ -743,8 +739,8 @@ static bool foreign_grouping_ok(PlannerInfo* root, RelOptInfo* grouped_rel, Node * (We don't have to check is_foreign_param, since that certainly won't return true for any such expression.) */ if (!is_foreign_expr(root, grouped_rel, (Expr *) aggvars)) { - db2Debug(5,"foreign_grouping_ok: non-foreign aggvar found"); - db2Exit(4,"< db2GetForeignUpperPaths.c::foreign_grouping_ok : false"); + db2Debug5("foreign_grouping_ok: non-foreign aggvar found"); + db2Exit4(": false"); return false; } @@ -798,9 +794,10 @@ static bool foreign_grouping_ok(PlannerInfo* root, RelOptInfo* grouped_rel, Node * Again, we need not check is_foreign_param for a foreign aggregate. */ if (IsA(expr, Aggref)) { - if (!is_foreign_expr(root, grouped_rel, expr)) + if (!is_foreign_expr(root, grouped_rel, expr)) { + db2Exit4(": false"); return false; - + } tlist = add_to_flat_tlist(tlist, list_make1(expr)); } } @@ -823,7 +820,7 @@ static bool foreign_grouping_ok(PlannerInfo* root, RelOptInfo* grouped_rel, Node * Note that the decoration we add to the base relation name mustn't include any digits, or it'll confuse postgresExplainForeignScan. */ fpinfo->relation_name = psprintf("Aggregate on (%s)", ofpinfo->relation_name); - db2Exit(4,"< db2GetForeignUpperPaths.c::foreign_grouping_ok : true"); + db2Exit4(": true"); return true; } @@ -846,7 +843,7 @@ void estimate_path_cost_size(PlannerInfo* root, RelOptInfo* foreignrel, List* pa Cost startup_cost; Cost total_cost; - db2Entry(4,"> db2GetForeignUpperPaths.c::estimate_path_cost_size"); + db2Entry4(); /* Make sure the core code has set up the relation's reltarget */ Assert(foreignrel->reltarget); @@ -1200,7 +1197,7 @@ void estimate_path_cost_size(PlannerInfo* root, RelOptInfo* foreignrel, List* pa *p_disabled_nodes = disabled_nodes; *p_startup_cost = startup_cost; *p_total_cost = total_cost; - db2Exit(4,"< db2GetForeignUpperPaths.c::estimate_path_cost_size"); + db2Exit4(); } /* Merge FDW options from input relations into a new set of options for a join or an upper rel. @@ -1209,7 +1206,7 @@ void estimate_path_cost_size(PlannerInfo* root, RelOptInfo* foreignrel, List* pa * For an upper relation, fpinfo_o provides the information for the input relation; fpinfo_i is expected to NULL. */ static void merge_fdw_options(DB2FdwState* fpinfo, const DB2FdwState* fpinfo_o, const DB2FdwState* fpinfo_i) { - db2Entry(4,"> db2GetForeignUpperPaths.c::merge_fdw_options"); + db2Entry4(); /* We must always have fpinfo_o. */ Assert(fpinfo_o); @@ -1243,5 +1240,5 @@ static void merge_fdw_options(DB2FdwState* fpinfo, const DB2FdwState* fpinfo_o, */ fpinfo->async_capable = fpinfo_o->async_capable || fpinfo_i->async_capable; } - db2Exit(4,"< db2GetForeignUpperPaths.c::merge_fdw_options"); + db2Exit4(); } diff --git a/source/db2GetLob.c b/source/db2GetLob.c index 7167c1c..c55b42e 100644 --- a/source/db2GetLob.c +++ b/source/db2GetLob.c @@ -12,9 +12,6 @@ extern char db2Message[ERRBUFSIZE];/* contains DB2 error messages, set b /** external prototypes */ extern void* db2alloc (const char* type, size_t size); extern void* db2realloc (void* p, size_t size); -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); -extern void db2Debug (int level, const char* message, ...); extern SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleType, int line, char* file); extern void db2Error_d (db2error sqlstate, const char* message, const char* detail, ...); @@ -30,15 +27,15 @@ void db2GetLob (DB2Session* session, DB2ResultColumn* column, char** value, long SQLLEN ind = 0; SQLCHAR buf[LOB_CHUNK_SIZE+1]; int extend = 0; - db2Entry(1,"> db2GetLob.c::db2GetLob"); - db2Debug(2,"column->colName: '%s'",column->colName); - db2Debug(2,"column->resnum : %d ",column->resnum); + db2Entry1(); + db2Debug2("column->colName: '%s'",column->colName); + db2Debug2("column->resnum : %d ",column->resnum); /* initialize result buffer length */ *value_len = 0; /* read the LOB in chunks */ do { - db2Debug(2,"value_len: %ld",*value_len); - db2Debug(2,"reading %d byte chunck of data",sizeof(buf)); + db2Debug2("value_len: %ld",*value_len); + db2Debug2("reading %d byte chunck of data",sizeof(buf)); rc = SQLGetData(session->stmtp->hsql, column->resnum, SQL_C_CHAR, buf, sizeof(buf), &ind); rc = db2CheckErr(rc,session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); if (rc == SQL_ERROR) { @@ -47,54 +44,54 @@ void db2GetLob (DB2Session* session, DB2ResultColumn* column, char** value, long if (rc != 100) { switch(ind) { case SQL_NULL_DATA: - db2Debug(3,"data length is null (SQL_NULL_DATA)"); + db2Debug3("data length is null (SQL_NULL_DATA)"); extend = 0; break; case SQL_NO_TOTAL: - db2Debug(3,"undefined data length (SQL_NO_TOTAL)"); + db2Debug3("undefined data length (SQL_NO_TOTAL)"); extend = LOB_CHUNK_SIZE; break; default: - db2Debug(3,"bytes still remaining: %d", ind); + db2Debug3("bytes still remaining: %d", ind); extend = (ind < LOB_CHUNK_SIZE) ? ind : LOB_CHUNK_SIZE; break; } /* extend result buffer by ind */ - db2Debug(2,"value_len: %ld", *value_len); - db2Debug(2,"extend : %d", extend); + db2Debug2("value_len: %ld", *value_len); + db2Debug2("extend : %d", extend); if (*value_len == 0) { if (extend > 0) { *value = db2alloc ("lob_value", *value_len + extend + 1); } else { *value = NULL; - db2Debug(3,"not allocating space since the LOB value is apparently NULL"); + db2Debug3("not allocating space since the LOB value is apparently NULL"); } } else { // do not add another 0 termination byte, since we already have one *value = db2realloc (*value, *value_len + extend); } // append the buffer read to the value excluding 0 termination byte - db2Debug(2,"*value : %x", *value); - db2Debug(2,"*value_len: %x", *value_len); + db2Debug2("*value : %x", *value); + db2Debug2("*value_len: %x", *value_len); if (*value != NULL) { - db2Debug(3,"memcpy(%x,%x,%d)",*value+*value_len,buf,extend); + db2Debug3("memcpy(%x,%x,%d)",*value+*value_len,buf,extend); memcpy(*value + *value_len, buf, extend); /* update LOB length */ *value_len += extend; } else { - db2Debug(3,"skipping value copy, since value is NULL"); + db2Debug3("skipping value copy, since value is NULL"); } } } while (rc == SQL_SUCCESS_WITH_INFO); /* string end for CLOBs */ - db2Debug(2,"*value : %x" , *value); - db2Debug(2,"value_len: %ld", *value_len); + db2Debug2("*value : %x" , *value); + db2Debug2("value_len: %ld", *value_len); if (*value != NULL) { (*value)[*value_len] = '\0'; - db2Debug(2,"strlen of lob: %ld", strlen(*value)); + db2Debug2("strlen of lob: %ld", strlen(*value)); } else { - db2Debug(2,"strlen of lob: 0 since *value is NULL"); + db2Debug2("strlen of lob: 0 since *value is NULL"); } - db2Exit(1,"< db2GetLob.c::db2GetLob"); + db2Exit1(); } diff --git a/source/db2GetSession.c b/source/db2GetSession.c index f7a1eba..c2453a1 100644 --- a/source/db2GetSession.c +++ b/source/db2GetSession.c @@ -9,9 +9,6 @@ extern DB2EnvEntry* rootenvEntry; /* contains DB2 error messages, set /** external prototypes */ extern void* db2alloc (const char* type, size_t size); -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); -extern void db2Debug (int level, const char* message, ...); extern DB2ConnEntry* db2AllocConnHdl (DB2EnvEntry* envp,const char* srvname, char* user, char* password, char* jwt_token, const char* nls_lang); extern DB2EnvEntry* db2AllocEnvHdl (const char* nls_lang); extern DB2EnvEntry* findenvEntry (DB2EnvEntry* start, const char* nlslang); @@ -31,7 +28,7 @@ DB2Session* db2GetSession (const char* srvname, char* user, char* password, char DB2EnvEntry* envp = NULL; DB2ConnEntry* connp = NULL; - db2Entry(1,"> db2GetSession.c::db2GetSession"); + db2Entry1(); /* it's easier to deal with empty strings */ if (!srvname) srvname = ""; if (!user) user = ""; @@ -40,7 +37,7 @@ DB2Session* db2GetSession (const char* srvname, char* user, char* password, char if (!nls_lang) nls_lang = ""; /* search environment and server handle in cache */ - db2Debug(2,"rootenvEntry: %x", rootenvEntry); + db2Debug2("rootenvEntry: %x", rootenvEntry); envp = findenvEntry (rootenvEntry, nls_lang); if (envp == NULL) { envp = db2AllocEnvHdl(nls_lang); @@ -50,7 +47,7 @@ DB2Session* db2GetSession (const char* srvname, char* user, char* password, char connp = db2AllocConnHdl(envp, srvname, user, password, jwt_token, NULL); } if (connp->xact_level <= 0) { - db2Debug(2,"db2_fdw::db2GetSession: begin serializable remote transaction"); + db2Debug2("db2_fdw::db2GetSession: begin serializable remote transaction"); connp->xact_level = 1; } @@ -63,6 +60,6 @@ DB2Session* db2GetSession (const char* srvname, char* user, char* password, char /* set savepoints up to the current level */ db2SetSavepoint (session, curlevel); - db2Exit(1,"< db2GetSession.c::db2GetSession"); + db2Exit1(); return session; } diff --git a/source/db2GetShareFileName.c b/source/db2GetShareFileName.c index cd526e2..3b9185b 100644 --- a/source/db2GetShareFileName.c +++ b/source/db2GetShareFileName.c @@ -6,8 +6,6 @@ #include "db2_fdw.h" /** external prototypes */ -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); extern void* db2alloc (const char* type, size_t size); /** local prototypes */ @@ -18,10 +16,10 @@ char* db2GetShareFileName(const char *relativename); */ char* db2GetShareFileName (const char *relativename) { char share_path[MAXPGPATH], *result; - db2Entry(1,"> db2GetShareFileName.c::db2GetShareFileName"); + db2Entry1(); get_share_path(my_exec_path, share_path); result = db2alloc("sharedFileName", MAXPGPATH); snprintf(result, MAXPGPATH, "%s/%s", share_path, relativename); - db2Exit(1,"< db2GetShareFileName.c::db2GetShareFileName : '%s'",result); + db2Exit1(": %s",result); return result; } diff --git a/source/db2ImportForeignSchema.c b/source/db2ImportForeignSchema.c index 7bb2982..ec77bde 100644 --- a/source/db2ImportForeignSchema.c +++ b/source/db2ImportForeignSchema.c @@ -9,9 +9,6 @@ /** external prototypes */ extern DB2Session* db2GetSession (const char* connectstring, char* user, char* password, char* jwt_token, const char* nls_lang, int curlevel); extern char* guessNlsLang (char* nls_lang); -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); -extern void db2Debug (int level, const char* message, ...); extern short c2dbType (short fcType); extern void db2free (void* p); extern char* db2strdup (const char* source); @@ -44,12 +41,12 @@ List* db2ImportForeignSchema (ImportForeignSchemaStmt* stmt, Oid serverOid) { List* result = NIL; ForeignServer* server = NULL; - db2Entry(1,"> db2ImportForeignSchema.c::db2ImportForeignSchema"); + db2Entry1(); /* process the server options */ server = getOptions (serverOid, &options); foreach (cell, options) { DefElem *def = (DefElem *) lfirst (cell); - db2Debug(2,"option: '%s'", def->defname); + db2Debug2("option: '%s'", def->defname); nls_lang = (strcmp (def->defname, OPT_NLS_LANG) == 0) ? STRVAL(def->arg) : nls_lang; dbserver = (strcmp (def->defname, OPT_DBSERVER) == 0) ? STRVAL(def->arg) : dbserver; user = (strcmp (def->defname, OPT_USER) == 0) ? STRVAL(def->arg) : user; @@ -60,7 +57,7 @@ List* db2ImportForeignSchema (ImportForeignSchemaStmt* stmt, Oid serverOid) { /* process the options of the IMPORT FOREIGN SCHEMA command */ foreach (cell, stmt->options) { DefElem *def = (DefElem *) lfirst (cell); - db2Debug(2,"option: '%s'", def->defname); + db2Debug2("option: '%s'", def->defname); if (strcmp (def->defname, "case") == 0) { char *s = STRVAL(def->arg); if (strcmp (s, "keep") == 0) @@ -89,12 +86,12 @@ List* db2ImportForeignSchema (ImportForeignSchemaStmt* stmt, Oid serverOid) { /* connect to DB2 database */ session = db2GetSession (dbserver, user, password, jwt_token, nls_lang, 1); - db2Debug(2,"stmt->list_type : %d", stmt->list_type); - db2Debug(2,"stmt->local_schema : %s", stmt->local_schema); - db2Debug(2,"stmt->remote_schema: %s", stmt->remote_schema); - db2Debug(2,"stmt->server_name : %s", stmt->server_name); - db2Debug(2,"stmt->table_list : %s", stmt->table_list); - db2Debug(2,"stmt->type : %d", stmt->type); + db2Debug2("stmt->list_type : %d", stmt->list_type); + db2Debug2("stmt->local_schema : %s", stmt->local_schema); + db2Debug2("stmt->remote_schema: %s", stmt->remote_schema); + db2Debug2("stmt->server_name : %s", stmt->server_name); + db2Debug2("stmt->table_list : %s", stmt->table_list); + db2Debug2("stmt->type : %d", stmt->type); if (isForeignSchema (session, stmt->remote_schema)) { StringInfoData tblist; @@ -105,16 +102,16 @@ List* db2ImportForeignSchema (ImportForeignSchemaStmt* stmt, Oid serverOid) { if (stmt->list_type != FDW_IMPORT_SCHEMA_ALL) { foreach (cell, stmt->table_list) { RangeVar* rVar = lfirst(cell); - db2Debug(2,"rVar : %x ", rVar); + db2Debug2("rVar : %x ", rVar); if (rVar != NULL) { - db2Debug(2,"rVar->type : %d ", rVar->type); - db2Debug(2,"rVar->catalogname: '%s'", rVar->catalogname); - db2Debug(2,"rVar->schemaname : '%s'", rVar->schemaname); - db2Debug(2,"rVar->relname : '%s'", rVar->relname); + db2Debug2("rVar->type : %d ", rVar->type); + db2Debug2("rVar->catalogname: '%s'", rVar->catalogname); + db2Debug2("rVar->schemaname : '%s'", rVar->schemaname); + db2Debug2("rVar->relname : '%s'", rVar->relname); appendStringInfo(&tblist,"%s'%s'",((tblist.len == 0) ? "" : ","),rVar->relname); } } - db2Debug(2,"import table_list: %s",tblist.data); + db2Debug2("import table_list: %s",tblist.data); } tablist = getForeignTableList(session, stmt->remote_schema, stmt->list_type, tblist.data); db2free (tblist.data); @@ -122,14 +119,14 @@ List* db2ImportForeignSchema (ImportForeignSchemaStmt* stmt, Oid serverOid) { DB2Table* db2Table = describeForeignTable(session, stmt->remote_schema, tablist[i]); if (db2Table != NULL) { generateForeignTableCreate(&buf, server->servername, stmt->local_schema, stmt->remote_schema, db2Table, foldcase, readonly); - db2Debug(2,"pg fdw table ddl: '%s'",buf.data); + db2Debug2("pg fdw table ddl: '%s'",buf.data); result = lappend (result, db2strdup (buf.data)); resetStringInfo (&buf); } } db2free (tablist); } - db2Exit(1,"< db2ImportForeignSchema.c::db2ImportForeignSchema : %d", list_length(result)); + db2Exit1(": %d", list_length(result)); return result; } @@ -138,7 +135,7 @@ List* db2ImportForeignSchema (ImportForeignSchemaStmt* stmt, Oid serverOid) { */ static char* fold_case (char *name, fold_t foldcase) { char* result = NULL; - db2Entry(4,"> db2ImportForeignSchema.c::fold_case(name: '%s', foldcase: %d)", name, foldcase); + db2Entry4("(name: '%s', foldcase: %d)", name, foldcase); if (foldcase == CASE_KEEP) { result = db2strdup (name); } else { @@ -158,7 +155,7 @@ static char* fold_case (char *name, fold_t foldcase) { if (result == NULL) { elog (ERROR, "impossible case folding type %d", foldcase); } - db2Exit(4,"< db2ImportForeignSchema.c::fold_case - returns: '%s'", result); + db2Exit4(": '%s'", result); return result; } @@ -167,7 +164,7 @@ static void generateForeignTableCreate(StringInfo buf, char* servername, char* l char* foldedname; bool firstcol = true; - db2Entry(4,"> db2ImportForeignSchema.c::generateForeignTableCreate"); + db2Entry4(); initStringInfo(&coldef); foldedname = fold_case (db2Table->name, foldcase); appendStringInfo( buf @@ -284,7 +281,7 @@ static void generateForeignTableCreate(StringInfo buf, char* servername, char* l appendStringInfo (buf, ", readonly 'true'"); } appendStringInfo (buf, ")"); - db2Exit(4,"< db2ImportForeignSchema.c::generateForeignTableCreate : %s", buf->data); + db2Exit4(": %s", buf->data); } /* getOptions @@ -297,7 +294,7 @@ static ForeignServer* getOptions (Oid serverOid, List** options) { ForeignServer* server = NULL; UserMapping* mapping = NULL; - db2Entry(4,"> db2ImportForeignSchema.c::getOptions"); + db2Entry4(); /* get the foreign server, the user mapping and the FDW */ server = GetForeignServer (serverOid); mapping = GetUserMapping (GetUserId (), serverOid); @@ -312,6 +309,6 @@ static ForeignServer* getOptions (Oid serverOid, List** options) { *options = list_concat (*options, server->options); if (mapping != NULL) *options = list_concat (*options, mapping->options); - db2Exit(4,"< db2ImportForeignSchema.c::getOptions : %x", server); + db2Exit4(": %x", server); return server; } \ No newline at end of file diff --git a/source/db2ImportForeignSchemaData.c b/source/db2ImportForeignSchemaData.c index a151c0f..6fcfe3e 100644 --- a/source/db2ImportForeignSchemaData.c +++ b/source/db2ImportForeignSchemaData.c @@ -17,9 +17,6 @@ extern void* db2realloc (void* p, size_t size); extern void db2free (void* p); extern char* db2strdup (const char* source); extern char* db2CopyText (const char* string, int size, int quote); -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); -extern void db2Debug (int level, const char* message, ...); extern SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleType, int line, char* file); extern void db2Error_d (db2error sqlstate, const char* message, const char* detail, ...); extern char* c2name (short fcType); @@ -45,55 +42,55 @@ bool isForeignSchema(DB2Session* session, char* schema) { SQLRETURN result = 0; char* schema_query = "SELECT COUNT(*) AS COUNTER FROM SYSCAT.SCHEMATA WHERE SCHEMANAME = ?"; - db2Entry(1,"> db2ImportForeignSchemaData.c::isForeignSchema(schema: '%s')", schema); - db2Debug(2,"count : %lld", (long long)count); - db2Debug(2,"schema query : '%s'", schema_query); + db2Entry1("(schema: '%s')", schema); + db2Debug2("count : %lld", (long long)count); + db2Debug2("schema query : '%s'", schema_query); /* create statement handle */ stmtp = db2AllocStmtHdl(SQL_HANDLE_STMT, session->connp, FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: failed to allocate statement handle"); - db2Debug(2,"stmp->hsql : %d",stmtp->hsql); - db2Debug(2,"stmp->type : %d",stmtp->type); + db2Debug2("stmp->hsql : %d",stmtp->hsql); + db2Debug2("stmp->type : %d",stmtp->type); /* prepare the query */ result = SQLPrepare(stmtp->hsql, (SQLCHAR*)schema_query, SQL_NTS); - db2Debug(2,"SQLPrepare rc : %d",result); + db2Debug2("SQLPrepare rc : %d",result); result = db2CheckErr(result, stmtp->hsql, stmtp->type, __LINE__, __FILE__); if (result != SQL_SUCCESS) { db2Error_d ( FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLPrepare failed to prepare schema query", db2Message); } /* bind the parameter */ result = SQLBindParameter(stmtp->hsql, 1, SQL_PARAM_INPUT,SQL_C_CHAR, SQL_VARCHAR, 128, 0, schema, sizeof(schema), &ind); - db2Debug(2,"SQLBindParameter1 NAME = '%s', ind = %d, rc : %d",schema, ind, result); + db2Debug2("SQLBindParameter1 NAME = '%s', ind = %d, rc : %d",schema, ind, result); result = db2CheckErr(result, stmtp->hsql, stmtp->type, __LINE__, __FILE__); if (result != SQL_SUCCESS) { db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLBindParameter failed to bind parameter", db2Message); } /* define the result value */ result = SQLBindCol (stmtp->hsql, 1, SQL_C_SBIGINT, &count, 0, &ind_c); - db2Debug(2,"SQLBindCol rc : %d",result); + db2Debug2("SQLBindCol rc : %d",result); result = db2CheckErr(result, stmtp->hsql, stmtp->type, __LINE__, __FILE__); if (result != SQL_SUCCESS) { db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLBindCol failed to define result", db2Message); } /* execute the query and get the first result row */ result = SQLExecute(stmtp->hsql); - db2Debug(2,"SQLExecute rc : %d",result); + db2Debug2("SQLExecute rc : %d",result); result = db2CheckErr(result, stmtp->hsql, stmtp->type, __LINE__, __FILE__); if (result != SQL_SUCCESS) { db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLExecute failed to execute schema query", db2Message); } else { result = SQLFetch(stmtp->hsql); - db2Debug(2,"SQLFetch rc : %d, count = %lld, ind_c = %d",result, (long long)count, ind_c); + db2Debug2("SQLFetch rc : %d, count = %lld, ind_c = %d",result, (long long)count, ind_c); result = db2CheckErr(result, stmtp->hsql, stmtp->type, __LINE__, __FILE__); if (result != SQL_SUCCESS) { db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLFetch failed to execute schema query", db2Message); } } - db2Debug(2,"count(*) = %lld, ind_c = %d", (long long)count, ind_c); + db2Debug2("count(*) = %lld, ind_c = %d", (long long)count, ind_c); /* release the statement handle */ db2FreeStmtHdl(stmtp, session->connp); stmtp = NULL; /* return false if the remote schema does not exist */ fResult = (count > 0); - db2Exit(1,"< db2ImportForeignSchemaData.c::isForeignSchema : %s, result = %s", schema, fResult ? "true" : "false"); + db2Exit1(": %s, result = %s", schema, fResult ? "true" : "false"); return fResult; } @@ -110,7 +107,7 @@ char** getForeignTableList(DB2Session* session, char* schema, int list_type, cha SQLLEN ind_tab; int tabidx = 0; char** tabnames = NULL; - db2Entry(1,"> db2ImportForeignSchemaData.c::getForeignTableList(schema: '%s', list_type: %d, table_list: '%s')", schema, list_type, table_list); + db2Entry1("(schema: '%s', list_type: %d, table_list: '%s')", schema, list_type, table_list); switch(list_type){ case 0: { /* FDW_IMPORT_SCHEMA_ALL */ char* query_str = "SELECT T.TABNAME FROM SYSCAT.TABLES T WHERE T.TABSCHEMA = ? AND T.TYPE IN ('T','V') ORDER BY T.TABNAME"; @@ -134,30 +131,30 @@ char** getForeignTableList(DB2Session* session, char* schema, int list_type, cha } break; default: - db2Debug(2,"schema import type: %d", list_type); + db2Debug2("schema import type: %d", list_type); db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "invalid schema import type", db2Message); break; } - db2Debug(2,"column query : '%s'", column_query); + db2Debug2("column query : '%s'", column_query); /* create statement handle */ stmtp = db2AllocStmtHdl(SQL_HANDLE_STMT, session->connp, FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: failed to allocate statement handle"); /* prepare the query */ rc = SQLPrepare(stmtp->hsql, (SQLCHAR*)column_query, SQL_NTS); - db2Debug(2,"SQLPrepare rc : %d",rc); + db2Debug2("SQLPrepare rc : %d",rc); rc = db2CheckErr(rc, stmtp->hsql, stmtp->type, __LINE__, __FILE__); if (rc != SQL_SUCCESS) { db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLPrepare failed to prepare remote query", db2Message); } /* bind the parameter */ rc = SQLBindParameter(stmtp->hsql, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_VARCHAR, 128, 0, schema, sizeof(schema), &ind_s); - db2Debug(2,"SQLBindParameter table_schema = '%s' rc : %d",schema, rc); + db2Debug2("SQLBindParameter table_schema = '%s' rc : %d",schema, rc); rc = db2CheckErr(rc, stmtp->hsql, stmtp->type, __LINE__, __FILE__); if (rc != SQL_SUCCESS) { db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLBindParameter failed to bind parameter", db2Message); } rc = SQLBindCol(stmtp->hsql, 1, SQL_C_CHAR, tab_buf, sizeof(tab_buf), &ind_tab); - db2Debug(2,"SQLBindCol1 rc : %d",rc); + db2Debug2("SQLBindCol1 rc : %d",rc); rc = db2CheckErr(rc, stmtp->hsql, stmtp->type, __LINE__, __FILE__); if (rc != SQL_SUCCESS) { db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLBindCol failed to define result for table name", db2Message); @@ -165,7 +162,7 @@ char** getForeignTableList(DB2Session* session, char* schema, int list_type, cha /* execute the query and get the first result row */ rc = SQLExecute (stmtp->hsql); - db2Debug(2,"SQLExecute rc : %d",rc); + db2Debug2("SQLExecute rc : %d",rc); rc = db2CheckErr(rc, stmtp->hsql, stmtp->type, __LINE__, __FILE__); if (rc != SQL_SUCCESS && rc != SQL_NO_DATA) { db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLExecute failed to execute column query", db2Message); @@ -179,7 +176,7 @@ char** getForeignTableList(DB2Session* session, char* schema, int list_type, cha tabnames = (char**) db2alloc("tabnames", (tabidx + 1) * sizeof(char*)); while(rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO) { tabnames[tabidx] = NULL; - db2Debug(2,"tabname[%d] : '%s', ind: %d", tabidx, tab_buf, ind_tab); + db2Debug2("tabname[%d] : '%s', ind: %d", tabidx, tab_buf, ind_tab); if (ind_tab != SQL_NULL_DATA) { char* tabname = (char*) db2alloc("tabname", strlen((char*)tab_buf)+1); strncpy(tabname, (char*)tab_buf, strlen((char*)tab_buf)+1); @@ -198,7 +195,7 @@ char** getForeignTableList(DB2Session* session, char* schema, int list_type, cha db2FreeStmtHdl(stmtp, session->connp); stmtp = NULL; db2free(column_query); - db2Exit(1,"< db2ImportForeignSchemaData.c::getForeignTableList : [%d]", tabidx-1); + db2Exit1(": [%d]", tabidx-1); return tabnames; } @@ -227,7 +224,7 @@ DB2Table* describeForeignTable (DB2Session* session, char* schema, char* tabname SQLINTEGER codepage = 0; SQLRETURN rc = 0; - db2Entry(1,"> db2ImportForeignSchemaData.c::db2DescribeForeignTable(schema: %s, tablename: %s)", schema, tabname); + db2Entry1("(schema: %s, tablename: %s)", schema, tabname); /* get a complete quoted table name */ qtable = db2CopyText (tabname, strlen (tabname), 1); length = strlen (qtable); @@ -276,8 +273,8 @@ DB2Table* describeForeignTable (DB2Session* session, char* schema, char* tabname /* allocate an db2Table struct for the results */ reply = db2alloc ("reply", sizeof (DB2Table)); reply->name = tabname; - db2Debug(2,"table description"); - db2Debug(2,"reply->name : '%s'", reply->name); + db2Debug2("table description"); + db2Debug2("reply->name : '%s'", reply->name); reply->batchsz = DEFAULT_BATCHSZ; /* get the number of columns */ @@ -289,7 +286,7 @@ DB2Table* describeForeignTable (DB2Session* session, char* schema, char* tabname reply->ncols = ncols; reply->cols = (DB2Column**) db2alloc ("reply->cols", sizeof (DB2Column*) *reply->ncols); - db2Debug(2,"reply->ncols : %d", reply->ncols); + db2Debug2("reply->ncols : %d", reply->ncols); /* loop through the column list */ for (i = 1; i <= reply->ncols; ++i) { @@ -321,20 +318,20 @@ DB2Table* describeForeignTable (DB2Session* session, char* schema, char* tabname db2Error_d (FDW_UNABLE_TO_CREATE_REPLY, "error describing remote table: SQLDescribeCol failed to get column data", db2Message); } reply->cols[i - 1]->colName = db2strdup((char*)colName); - db2Debug(2,"reply->cols[%d]->colName : '%s'", (i-1), reply->cols[i - 1]->colName); - db2Debug(2,"dataType: %d", dataType); + db2Debug2("reply->cols[%d]->colName : '%s'", (i-1), reply->cols[i - 1]->colName); + db2Debug2("dataType: %d", dataType); reply->cols[i - 1]->colType = (short) dataType; if (dataType == -7){ // datatype -7 does not exist it seems to be used for SQL_BOOLEAN wrongly reply->cols[i - 1]->colType = SQL_BOOLEAN; } - db2Debug(2,"reply->cols[%d]->colType : %d (%s)", (i-1), reply->cols[i - 1]->colType,c2name(reply->cols[i - 1]->colType)); + db2Debug2("reply->cols[%d]->colType : %d (%s)", (i-1), reply->cols[i - 1]->colType,c2name(reply->cols[i - 1]->colType)); reply->cols[i - 1]->colSize = (size_t) colSize; - db2Debug(2,"reply->cols[%d]->colSize : %ld", (i-1), reply->cols[i - 1]->colSize); + db2Debug2("reply->cols[%d]->colSize : %ld", (i-1), reply->cols[i - 1]->colSize); reply->cols[i - 1]->colScale = (short) scale; - db2Debug(2,"reply->cols[%d]->colScale : %d", (i-1), reply->cols[i - 1]->colScale); + db2Debug2("reply->cols[%d]->colScale : %d", (i-1), reply->cols[i - 1]->colScale); reply->cols[i - 1]->colNulls = (short) nullable; - db2Debug(2,"reply->cols[%d]->colNulls : %d", (i-1), reply->cols[i - 1]->colNulls); + db2Debug2("reply->cols[%d]->colNulls : %d", (i-1), reply->cols[i - 1]->colNulls); /* get the number of characters for string fields */ rc = SQLColAttribute (stmthp->hsql, i, SQL_DESC_PRECISION, NULL, 0, NULL, &charlen); @@ -343,7 +340,7 @@ DB2Table* describeForeignTable (DB2Session* session, char* schema, char* tabname db2Error_d (FDW_UNABLE_TO_CREATE_REPLY, "error describing remote table: SQLColAttribute failed to get column length", db2Message); } reply->cols[i - 1]->colChars = (size_t) charlen; - db2Debug(2,"reply->cols[%d]->colChars : %ld", (i-1), reply->cols[i - 1]->colChars); + db2Debug2("reply->cols[%d]->colChars : %ld", (i-1), reply->cols[i - 1]->colChars); /* get the binary length for RAW fields */ rc = SQLColAttribute (stmthp->hsql, i, SQL_DESC_OCTET_LENGTH, NULL, 0, NULL, &bin_size); @@ -352,7 +349,7 @@ DB2Table* describeForeignTable (DB2Session* session, char* schema, char* tabname db2Error_d (FDW_UNABLE_TO_CREATE_REPLY, "error describing remote table: SQLColAttribute failed to get column size", db2Message); } reply->cols[i - 1]->colBytes = (size_t) bin_size; - db2Debug(2,"reply->cols[%d]->colBytes : %ld", (i-1), reply->cols[i - 1]->colBytes); + db2Debug2("reply->cols[%d]->colBytes : %ld", (i-1), reply->cols[i - 1]->colBytes); /* get the columns codepage */ rc = SQLColAttribute(stmthp->hsql, i, SQL_DESC_CODEPAGE, NULL, 0, NULL, (SQLPOINTER)&codepage); @@ -361,7 +358,7 @@ DB2Table* describeForeignTable (DB2Session* session, char* schema, char* tabname db2Error_d (FDW_UNABLE_TO_CREATE_REPLY, "error describing remote table: SQLColAttribute failed to get column codepage", db2Message); } reply->cols[i - 1]->colCodepage = (int) codepage; - db2Debug(2,"reply->cols[%d]->colCodepage : %d", (i-1), reply->cols[i - 1]->colCodepage); + db2Debug2("reply->cols[%d]->colCodepage : %d", (i-1), reply->cols[i - 1]->colCodepage); /* Unfortunately a LONG VARBINARY is of type LONG VARCHAR but the codepage is set to 0 */ if (reply->cols[i-1]->colType == SQL_LONGVARCHAR && reply->cols[i-1]->colCodepage == 0){ @@ -428,7 +425,7 @@ DB2Table* describeForeignTable (DB2Session* session, char* schema, char* tabname default: break; } - db2Debug(2,"reply->cols[%d]->val_size : %d", (i-1), reply->cols[i - 1]->val_size); + db2Debug2("reply->cols[%d]->val_size : %d", (i-1), reply->cols[i - 1]->val_size); } /* release statement handle, this takes care of the parameter handles */ db2FreeStmtHdl(stmthp, session->connp); @@ -436,7 +433,7 @@ DB2Table* describeForeignTable (DB2Session* session, char* schema, char* tabname /* get the primary key information for the table and mark the columns in the reply */ describeForeignColumns(session, schema, tabname, reply); } - db2Exit(1,"< db2ImportForeignSchemaData.c::db2DescribeForeignTable : %x", reply); + db2Exit1(": %x", reply); return reply; } @@ -455,14 +452,14 @@ static void describeForeignColumns(DB2Session* session, char* schema, char* tabn SQLLEN ind_t = SQL_NTS; char* query = "SELECT COALESCE(C.KEYSEQ, 0) AS KEY, C.CODEPAGE FROM SYSCAT.COLUMNS C WHERE C.TABSCHEMA = ? AND C.TABNAME = ? AND COALESCE(C.HIDDEN,'') = '' ORDER BY C.COLNO"; - db2Entry(1,"> db2ImportForeignSchemaData.c::describeForeignColumn(schema: %s, tabname: %s)", schema, tabname); - db2Debug(2,"query : '%s'", query); + db2Entry1("(schema: %s, tabname: %s)", schema, tabname); + db2Debug2("query : '%s'", query); /* create statement handle */ stmtp = db2AllocStmtHdl(SQL_HANDLE_STMT, session->connp, FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: failed to allocate statement handle"); /* prepare the query */ rc = SQLPrepare(stmtp->hsql, (SQLCHAR*)query, SQL_NTS); - db2Debug(2,"SQLPrepare rc : %d",rc); + db2Debug2("SQLPrepare rc : %d",rc); rc = db2CheckErr(rc, stmtp->hsql, stmtp->type, __LINE__, __FILE__); if (rc != SQL_SUCCESS) { db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLPrepare failed to prepare remote query", db2Message); @@ -470,35 +467,35 @@ static void describeForeignColumns(DB2Session* session, char* schema, char* tabn /* bind the parameter 1 - schema */ rc = SQLBindParameter(stmtp->hsql, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, 128, 0, schema, 0, &ind_s); - db2Debug(2,"SQLBindParameter table_schema = '%s' rc : %d",schema, rc); + db2Debug2("SQLBindParameter table_schema = '%s' rc : %d",schema, rc); rc = db2CheckErr(rc, stmtp->hsql, stmtp->type, __LINE__, __FILE__); if (rc != SQL_SUCCESS) { db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLBindParameter failed to bind parameter", db2Message); } /* bind the parameter 2 - tablename */ rc = SQLBindParameter(stmtp->hsql, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, 128, 0, tabname, 0, &ind_t); - db2Debug(2,"SQLBindParameter table_name = '%s' rc : %d",tabname, rc); + db2Debug2("SQLBindParameter table_name = '%s' rc : %d",tabname, rc); rc = db2CheckErr(rc, stmtp->hsql, stmtp->type, __LINE__, __FILE__); if (rc != SQL_SUCCESS) { db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLBindParameter failed to bind parameter", db2Message); } /* bind result column 1 - KEYSEQ */ rc = SQLBindCol(stmtp->hsql, 1, SQL_C_SHORT, &keyseq_val, 0, &ind_key); - db2Debug(2,"SQLBindCol1 rc : %d",rc); + db2Debug2("SQLBindCol1 rc : %d",rc); rc = db2CheckErr(rc, stmtp->hsql, stmtp->type, __LINE__, __FILE__); if (rc != SQL_SUCCESS) { db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLBindCol failed to define result for primary key", db2Message); } /* bind result column 2 - CODEPAGE */ rc = SQLBindCol(stmtp->hsql, 2, SQL_C_SHORT, &cp_val, 0, &ind_cp); - db2Debug(2,"SQLBindCol2 rc : %d",rc); + db2Debug2("SQLBindCol2 rc : %d",rc); rc = db2CheckErr(rc, stmtp->hsql, stmtp->type, __LINE__, __FILE__); if (rc != SQL_SUCCESS) { db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLBindCol failed to define result for codepage", db2Message); } /* execute the query and get the first result row */ rc = SQLExecute (stmtp->hsql); - db2Debug(2,"SQLExecute rc : %d",rc); + db2Debug2("SQLExecute rc : %d",rc); rc = db2CheckErr(rc, stmtp->hsql, stmtp->type, __LINE__, __FILE__); if (rc != SQL_SUCCESS && rc != SQL_NO_DATA) { db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLExecute failed to execute column query", db2Message); @@ -513,21 +510,21 @@ static void describeForeignColumns(DB2Session* session, char* schema, char* tabn } while(rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO) { - db2Debug(2,"keyseq_val: %d, ind: %d", keyseq_val, ind_key); + db2Debug2("keyseq_val: %d, ind: %d", keyseq_val, ind_key); db2Table->cols[colidx]->colPrimKeyPart = (ind_key == SQL_NULL_DATA) ? 0 : (int) keyseq_val; - db2Debug(2,"cp_val : %d, ind: %d", cp_val, ind_cp); + db2Debug2("cp_val : %d, ind: %d", cp_val, ind_cp); db2Table->cols[colidx]->colCodepage = (ind_cp == SQL_NULL_DATA) ? 0 : (int) cp_val; - db2Debug(2,"db2Table->cols[%d]->colName : %s " , colidx, db2Table->cols[colidx]->colName ); - db2Debug(2,"db2Table->cols[%d]->colType : %d - %s,", colidx, db2Table->cols[colidx]->colType, c2name(db2Table->cols[colidx]->colType)); - db2Debug(2,"db2Table->cols[%d]->colSize : %d" , colidx, db2Table->cols[colidx]->colSize ); - db2Debug(2,"db2Table->cols[%d]->colBytes : %d" , colidx, db2Table->cols[colidx]->colBytes ); - db2Debug(2,"db2Table->cols[%d]->colChars : %d" , colidx, db2Table->cols[colidx]->colChars ); - db2Debug(2,"db2Table->cols[%d]->colScale : %d" , colidx, db2Table->cols[colidx]->colScale); - db2Debug(2,"db2Table->cols[%d]->colNulls : %d" , colidx, db2Table->cols[colidx]->colNulls); - db2Debug(2,"db2Table->cols[%d]->colPrimKeyPart: %d" , colidx, db2Table->cols[colidx]->colPrimKeyPart); - db2Debug(2,"db2Table->cols[%d]->colCodepage : %d" , colidx, db2Table->cols[colidx]->colCodepage); - db2Debug(2,"db2Table->cols[%d]->val_size : %d" , colidx, db2Table->cols[colidx]->val_size); + db2Debug2("db2Table->cols[%d]->colName : %s " , colidx, db2Table->cols[colidx]->colName ); + db2Debug2("db2Table->cols[%d]->colType : %d - %s,", colidx, db2Table->cols[colidx]->colType, c2name(db2Table->cols[colidx]->colType)); + db2Debug2("db2Table->cols[%d]->colSize : %d" , colidx, db2Table->cols[colidx]->colSize ); + db2Debug2("db2Table->cols[%d]->colBytes : %d" , colidx, db2Table->cols[colidx]->colBytes ); + db2Debug2("db2Table->cols[%d]->colChars : %d" , colidx, db2Table->cols[colidx]->colChars ); + db2Debug2("db2Table->cols[%d]->colScale : %d" , colidx, db2Table->cols[colidx]->colScale); + db2Debug2("db2Table->cols[%d]->colNulls : %d" , colidx, db2Table->cols[colidx]->colNulls); + db2Debug2("db2Table->cols[%d]->colPrimKeyPart: %d" , colidx, db2Table->cols[colidx]->colPrimKeyPart); + db2Debug2("db2Table->cols[%d]->colCodepage : %d" , colidx, db2Table->cols[colidx]->colCodepage); + db2Debug2("db2Table->cols[%d]->val_size : %d" , colidx, db2Table->cols[colidx]->val_size); /* fetch the next result row */ rc = SQLFetch(stmtp->hsql); @@ -537,8 +534,8 @@ static void describeForeignColumns(DB2Session* session, char* schema, char* tabn } colidx++; } - db2Debug(3,"End of Data reached"); + db2Debug3("End of Data reached"); /* release the statement handle */ db2FreeStmtHdl(stmtp, session->connp); - db2Exit(1,"< db2ImportForeignSchemaData.c::describeForeignColumn"); + db2Exit1(); } \ No newline at end of file diff --git a/source/db2IsForeignRelUpdatable.c b/source/db2IsForeignRelUpdatable.c index 89a5aa7..8674a19 100644 --- a/source/db2IsForeignRelUpdatable.c +++ b/source/db2IsForeignRelUpdatable.c @@ -5,8 +5,6 @@ /** external prototypes */ extern bool optionIsTrue (const char* value); -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); /** local prototypes */ int db2IsForeignRelUpdatable(Relation rel); @@ -17,7 +15,7 @@ int db2IsForeignRelUpdatable(Relation rel); int db2IsForeignRelUpdatable(Relation rel) { ListCell* cell; int result = 0; - db2Entry(1,"> db2IsForeignRelUpdatable.c::db2IsForeignRelUpdatable"); + db2Entry1(); /* loop foreign table options */ foreach (cell, GetForeignTable (RelationGetRelid (rel))->options) { DefElem *def = (DefElem *) lfirst (cell); @@ -26,7 +24,7 @@ int db2IsForeignRelUpdatable(Relation rel) { return 0; } result = (1 << CMD_UPDATE) | (1 << CMD_INSERT) | (1 << CMD_DELETE); - db2Exit(1,"< db2IsForeignRelUpdatable.c::db2IsForeignRelUpdatable - returns: %d", result); + db2Exit1(": %d", result); return result; } diff --git a/source/db2IsStatementOpen.c b/source/db2IsStatementOpen.c index 2bc9adf..842e0a6 100644 --- a/source/db2IsStatementOpen.c +++ b/source/db2IsStatementOpen.c @@ -7,8 +7,6 @@ /** external variables */ /** external prototypes */ -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); /** local prototypes */ int db2IsStatementOpen (DB2Session* session); @@ -18,8 +16,8 @@ int db2IsStatementOpen (DB2Session* session); */ int db2IsStatementOpen (DB2Session* session) { int result = 0; - db2Entry(1,"> db2IsStatementOpen.c::db2IsStatementOpen"); + db2Entry1(); result = (session->stmtp != NULL && session->stmtp->hsql != SQL_NULL_HSTMT); - db2Exit(1,"< db2IsStatementOpen.c::db2IsStatementOpen : %d",result); + db2Exit1(": %d",result); return result; } diff --git a/source/db2IterateDirectModify.c b/source/db2IterateDirectModify.c index c38c5ad..7616bf0 100644 --- a/source/db2IterateDirectModify.c +++ b/source/db2IterateDirectModify.c @@ -3,9 +3,6 @@ #include "DB2FdwDirectModifyState.h" /** external prototypes */ -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); -extern void db2Debug (int level, const char* message, ...); /** local prototypes */ TupleTableSlot* db2IterateDirectModify(ForeignScanState *node); @@ -26,7 +23,7 @@ TupleTableSlot* db2IterateDirectModify(ForeignScanState *node) { List* param_exps = dmstate->param_exprs; // call db2ExecForeignDirectUpdate() similar to db2ExecForeignInsert() - db2Entry(1,"> db2IterateDirectModify.c::db2IterateDirectModify"); - db2Exit(1,"> db2IterateDirectModify.c::db2IterateDirectModify : %x", slot); + db2Entry1(); + db2Exit1(": %x", slot); return slot; } diff --git a/source/db2IterateForeignScan.c b/source/db2IterateForeignScan.c index ecadf7b..615381b 100644 --- a/source/db2IterateForeignScan.c +++ b/source/db2IterateForeignScan.c @@ -14,9 +14,6 @@ extern void db2PrepareQuery (DB2Session* session, const char * extern int db2ExecuteQuery (DB2Session* session, ParamDesc* paramList); extern int db2FetchNext (DB2Session* session); extern void db2CloseStatement (DB2Session* session); -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); -extern void db2Debug (int level, const char* message, ...); extern void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls, bool trunc_lob) ; extern char* deparseDate (Datum datum); extern char* deparseTimestamp (Datum datum, bool hasTimezone); @@ -37,16 +34,16 @@ TupleTableSlot* db2IterateForeignScan (ForeignScanState* node) { int have_result; DB2FdwState* fdw_state = (DB2FdwState*) node->fdw_state; - db2Entry(1,"> db2IterateForeignScan.c::db2IterateForeignScan"); + db2Entry1(); if (db2IsStatementOpen (fdw_state->session)) { - db2Debug(2,"get next row in foreign table scan"); + db2Debug2("get next row in foreign table scan"); /* fetch the next result row */ have_result = db2FetchNext (fdw_state->session); } else { /* fill the parameter list with the actual values */ char* paramInfo = setSelectParameters (fdw_state->paramList, econtext); /* execute the DB2 statement and fetch the first row */ - db2Debug(2,"execute query in foreign table scan '%s'", paramInfo); + db2Debug2("execute query in foreign table scan '%s'", paramInfo); db2PrepareQuery (fdw_state->session, fdw_state->query, fdw_state->resultList, fdw_state->prefetch, fdw_state->fetch_size); have_result = db2ExecuteQuery (fdw_state->session, fdw_state->paramList); have_result = db2FetchNext (fdw_state->session); @@ -57,7 +54,7 @@ TupleTableSlot* db2IterateForeignScan (ForeignScanState* node) { /* increase row count */ ++fdw_state->rowcount; /* convert result to arrays of values and null indicators */ - db2Debug(2,"slot->tts_tupleDescriptor->natts: %d",slot->tts_tupleDescriptor->natts); + db2Debug2("slot->tts_tupleDescriptor->natts: %d",slot->tts_tupleDescriptor->natts); convertTuple (fdw_state, slot->tts_tupleDescriptor->natts, slot->tts_values, slot->tts_isnull, false); /* store the virtual tuple */ ExecStoreVirtualTuple (slot); @@ -65,7 +62,7 @@ TupleTableSlot* db2IterateForeignScan (ForeignScanState* node) { /* close the statement */ db2CloseStatement (fdw_state->session); } - db2Exit(1,"< db2IterateForeignScan.c::db2IterateForeignScan"); + db2Exit1(); return slot; } @@ -83,9 +80,9 @@ static char* setSelectParameters (ParamDesc* paramList, ExprContext* econtext) { MemoryContext oldcontext; StringInfoData info; /* list of parameters for DEBUG message */ - db2Entry(4,"> db2IterateForeignScan.c::setSelectParameters"); - db2Debug(5,"paramList: %x",paramList); - db2Debug(5,"econtext : %x",econtext); + db2Entry4(); + db2Debug5("paramList: %x",paramList); + db2Debug5("econtext : %x",econtext); initStringInfo (&info); @@ -146,7 +143,7 @@ static char* setSelectParameters (ParamDesc* paramList, ExprContext* econtext) { /* reset memory context */ MemoryContextSwitchTo (oldcontext); - db2Exit(4,"< db2IterateForeignScan.c::setSelectParameters : %s", info.data); + db2Exit4(": %s", info.data); return info.data; } diff --git a/source/db2PlanDirectModify.c b/source/db2PlanDirectModify.c index 9176127..efc7ff5 100644 --- a/source/db2PlanDirectModify.c +++ b/source/db2PlanDirectModify.c @@ -10,9 +10,6 @@ #include "DB2FdwState.h" /** external prototypes */ -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); -extern void db2Debug (int level, const char* message, ...); extern bool is_foreign_expr (PlannerInfo *root, RelOptInfo *baserel, Expr *expr); extern void deparseDirectUpdateSql (StringInfo buf, PlannerInfo *root, Index rtindex, Relation rel, RelOptInfo *foreignrel, List *targetlist, List *targetAttrs, List *remote_conds, List **params_list, List *returningList, List **retrieved_attrs); extern void deparseDirectDeleteSql (StringInfo buf, PlannerInfo *root, Index rtindex, Relation rel, RelOptInfo *foreignrel, List *remote_conds, List **params_list, List *returningList, List **retrieved_attrs); @@ -27,17 +24,17 @@ static ForeignScan* find_modifytable_subplan (PlannerInfo* root, ModifyTable* p bool db2PlanDirectModify(PlannerInfo* root, ModifyTable* plan, Index rtindex, int subplan_index) { bool fResult = true; - db2Entry(1,"> db2PlanDirectModify.c::db2PlanDirectModify"); - db2Debug(2,"plan->operation: %d", plan->operation); - db2Debug(2,"plan->returningLists: %x - %d", plan->returningLists, list_length(plan->returningLists)); + db2Entry1(); + db2Debug2("plan->operation: %d", plan->operation); + db2Debug2("plan->returningLists: %x - %d", plan->returningLists, list_length(plan->returningLists)); /* The table modification must be an UPDATE or DELETE and must not use RETURNING */ if ((plan->operation == CMD_UPDATE || plan->operation == CMD_DELETE) && plan->returningLists == NIL) { /* Try to locate the ForeignScan subplan that's scanning rtindex. */ ForeignScan* fscan = find_modifytable_subplan(root, plan, rtindex, subplan_index); - db2Debug(2,"fscan: %x",fscan); + db2Debug2("fscan: %x",fscan); if (fscan) { /* It's unsafe to modify a foreign table directly if there are any quals that should be evaluated locally. */ - db2Debug(2,"fscan->scan.plan.qual: %x",fscan->scan.plan.qual); + db2Debug2("fscan->scan.plan.qual: %x",fscan->scan.plan.qual); if (fscan->scan.plan.qual == NIL) { RelOptInfo* foreignrel = NULL; RangeTblEntry* rte = NULL; @@ -81,7 +78,7 @@ bool db2PlanDirectModify(PlannerInfo* root, ModifyTable* plan, Index rtindex, in } } } - db2Debug(2,"fResult: %s",(fResult) ? "true" : "false"); + db2Debug2("fResult: %s",(fResult) ? "true" : "false"); if (fResult) { StringInfoData sql; Relation rel; @@ -152,7 +149,7 @@ bool db2PlanDirectModify(PlannerInfo* root, ModifyTable* plan, Index rtindex, in } } } - db2Exit(1,"< db2PlanDirectModify.c::db2PlanDirectModify : %s", (fResult) ? "true" : "false"); + db2Exit1(": %s", (fResult) ? "true" : "false"); return fResult; } @@ -166,7 +163,7 @@ static ForeignScan* find_modifytable_subplan(PlannerInfo* root, ModifyTable* pla ForeignScan* fscan = NULL; Plan* subplan = outerPlan(plan); - db2Entry(1,"> db2PlanDirectModify.c::find_modifytable_subplan"); + db2Entry1(); /* The cases we support are (1) the desired ForeignScan is the immediate child of ModifyTable, or (2) it is the subplan_index'th child of an * Append node that is the immediate child of ModifyTable. * There is no point in looking further down, as that would mean that local joins are involved, so we can't do the update directly. @@ -176,31 +173,31 @@ static ForeignScan* find_modifytable_subplan(PlannerInfo* root, ModifyTable* pla * with the children out-of-order. * Moreover, such a search risks costing O(N^2) time when there are a lot of children. */ - db2Debug(3,"subplan from outerplan: %x",subplan); + db2Debug3("subplan from outerplan: %x",subplan); if (IsA(subplan, Append)) { Append* append = (Append*) subplan; - db2Debug(4,"subplan is Append"); + db2Debug4("subplan is Append"); if (subplan_index < list_length(append->appendplans)) { subplan = (Plan*) list_nth(append->appendplans, subplan_index); - db2Debug(3,"subplan from appendplan: %x",subplan); + db2Debug3("subplan from appendplan: %x",subplan); } } else if (IsA(subplan, Result) && outerPlan(subplan) != NULL && IsA(outerPlan(subplan), Append)) { Append* append = (Append*) outerPlan(subplan); - db2Debug(4,"subplan is Result"); + db2Debug4("subplan is Result"); if (subplan_index < list_length(append->appendplans)) { subplan = (Plan*) list_nth(append->appendplans, subplan_index); - db2Debug(3,"subplan from resultplan: %x",subplan); + db2Debug3("subplan from resultplan: %x",subplan); } } /* Now, have we got a ForeignScan on the desired rel? */ - db2Debug(3,"subplan: %x",subplan); + db2Debug3("subplan: %x",subplan); if (IsA(subplan, ForeignScan) && (bms_is_member(rtindex, ((ForeignScan*) subplan)->fs_base_relids))) { - db2Debug(4,"subplan is ForeignScan"); + db2Debug4("subplan is ForeignScan"); fscan = (ForeignScan*) subplan; } - db2Exit(1,"< db2PlanDirectModify.c::find_modifytable_subplan : %x", fscan); + db2Exit1(": %x", fscan); return fscan; } diff --git a/source/db2PlanForeignModify.c b/source/db2PlanForeignModify.c index 8d322f5..0a352d5 100644 --- a/source/db2PlanForeignModify.c +++ b/source/db2PlanForeignModify.c @@ -12,9 +12,6 @@ extern char* db2strdup (const char* source); extern void* db2alloc (const char* type, size_t size); extern DB2FdwState* db2GetFdwState (Oid foreigntableid, double* sample_percent, bool describe); -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); -extern void db2Debug (int level, const char* message, ...); extern short c2dbType (short fcType); extern void appendAsType (StringInfoData* dest, Oid type); extern List* serializePlanData (DB2FdwState* fdwState); @@ -52,7 +49,7 @@ List* db2PlanForeignModify (PlannerInfo* root, ModifyTable* plan, Index resultRe RTEPermissionInfo* perminfo = NULL; #endif /* PG_VERSION_NUM >= 160000 */ - db2Entry(1,"> db2PlanForeignModify.c::db2PlanForeignModify"); + db2Entry1(); /* Get the updated columns and the user for permission checks. * We put that here at the beginning, since the way to do that changed considerably over the different PostgreSQL versions. */ @@ -224,7 +221,7 @@ List* db2PlanForeignModify (PlannerInfo* root, ModifyTable* plan, Index resultRe appendStringInfo (&sql, "%s = ", fdwState->db2Table->cols[i]->colName); appendAsType (&sql, fdwState->db2Table->cols[i]->pgtype); } - db2Debug(2,"sql: '%s'",sql.data); + db2Debug2("sql: '%s'",sql.data); /* throw a meaningful error if nothing is updated */ if (firstcol) ereport (ERROR @@ -302,10 +299,10 @@ List* db2PlanForeignModify (PlannerInfo* root, ModifyTable* plan, Index resultRe } } fdwState->query = sql.data; - db2Debug(2,"fdwState->query: '%s'", fdwState->query); + db2Debug2("fdwState->query: '%s'", fdwState->query); /* return a serialized form of the plan state */ result = serializePlanData (fdwState); - db2Exit(1,"< db2PlanForeignModify.c::db2PlanForeignModify"); + db2Exit1(": %x", result); return result; } @@ -316,7 +313,7 @@ static DB2FdwState* copyPlanData (DB2FdwState* orig) { int i = 0; DB2FdwState* copy = NULL; - db2Entry(4,"> db2PlanForeignModify.c::copyPlanData"); + db2Entry4(); copy = db2alloc("copy_fdw_state", sizeof (DB2FdwState)); copy->dbserver = db2strdup(orig->dbserver); copy->user = db2strdup(orig->user); @@ -358,7 +355,7 @@ static DB2FdwState* copyPlanData (DB2FdwState* orig) { copy->rowcount = 0; copy->temp_cxt = NULL; copy->order_clause = NULL; - db2Exit(4,"< db2PlanForeignModify.c::copyPlanData"); + db2Exit4(": %x", copy); return copy; } @@ -369,18 +366,18 @@ static DB2FdwState* copyPlanData (DB2FdwState* orig) { void addParam (ParamDesc **paramList, DB2Column* db2col, int colnum, int txts) { ParamDesc *param; - db2Entry(1,"> db2PlanForeignModify.c::addParam"); - db2Debug(2,"pgtype: %d",db2col->pgtype); - db2Debug(2,"colType: %d",db2col->colType); - db2Debug(2,"colnum: %d",colnum); - db2Debug(2,"txts: %d",txts); + db2Entry1(); + db2Debug2("pgtype: %d",db2col->pgtype); + db2Debug2("colType: %d",db2col->colType); + db2Debug2("colnum: %d",colnum); + db2Debug2("txts: %d",txts); param = db2alloc("paramList->next",sizeof (ParamDesc)); param->colName = db2strdup(db2col->colName); - db2Debug(2,"param->colName: '%s'",param->colName); + db2Debug2("param->colName: '%s'",param->colName); param->colType = db2col->colType; - db2Debug(2,"param->colType: '%d'",param->colType); + db2Debug2("param->colType: '%d'",param->colType); param->colSize = db2col->colSize; - db2Debug(2,"param->colSize: '%d'",param->colSize); + db2Debug2("param->colSize: '%d'",param->colSize); param->type = db2col->pgtype; switch (c2dbType(db2col->colType)) { case DB2_INTEGER: @@ -401,20 +398,20 @@ void addParam (ParamDesc **paramList, DB2Column* db2col, int colnum, int txts) { default: param->bindType = BIND_STRING; } - db2Debug(2,"param->bindType: '%d'",param->bindType); + db2Debug2("param->bindType: '%d'",param->bindType); param->value = NULL; - db2Debug(2,"param->value: %x",param->value); + db2Debug2("param->value: %x",param->value); param->val_size = db2col->val_size; - db2Debug(2,"param->val_size: %d",param->val_size); + db2Debug2("param->val_size: %d",param->val_size); param->node = NULL; - db2Debug(2,"param->node: %x",param->node); + db2Debug2("param->node: %x",param->node); param->colnum = colnum; - db2Debug(2,"param->colnum: %d",param->colnum); + db2Debug2("param->colnum: %d",param->colnum); param->txts = txts; - db2Debug(2,"param->txts: %d",param->txts); + db2Debug2("param->txts: %d",param->txts); param->next = *paramList; *paramList = param; - db2Exit(1,"< db2PlanForeignModify.c::addParam"); + db2Exit1(); } /* checkDataType @@ -422,23 +419,23 @@ void addParam (ParamDesc **paramList, DB2Column* db2col, int colnum, int txts) { */ void checkDataType (short sqltype, int scale, Oid pgtype, const char *tablename, const char *colname) { short db2type = c2dbType(sqltype); - db2Entry(4,"> db2PlanForeignModify.c::checkDataType"); - db2Debug(4,"checkDataType: %s.%s of sqltype: %d, db2type: %d, pgtype: %d",tablename,colname,sqltype, db2type, pgtype); + db2Entry4(); + db2Debug4("checkDataType: %s.%s of sqltype: %d, db2type: %d, pgtype: %d",tablename,colname,sqltype, db2type, pgtype); /* the binary DB2 types can be converted to bytea */ if (db2type == DB2_BLOB && pgtype == BYTEAOID) { - db2Debug(5,"DB2_BLOB can be converted into BYTEAOID"); + db2Debug5("DB2_BLOB can be converted into BYTEAOID"); } else if (db2type == DB2_XML && pgtype == XMLOID) { - db2Debug(5,"DB2_XML can be converted into XMLOID"); + db2Debug5("DB2_XML can be converted into XMLOID"); } else if (db2type != DB2_UNKNOWN_TYPE && db2type != DB2_BLOB && (pgtype == TEXTOID || pgtype == VARCHAROID || pgtype == BPCHAROID)) { - db2Debug(5,"DB2_UNKNONW && not DB2_BLOB can be converted into TEXTOID, VARCHAROID, BPCHAROID"); + db2Debug5("DB2_UNKNONW && not DB2_BLOB can be converted into TEXTOID, VARCHAROID, BPCHAROID"); } else if ((db2type == DB2_INTEGER || db2type == DB2_SMALLINT || db2type == DB2_BIGINT || db2type == DB2_FLOAT || db2type == DB2_DOUBLE || db2type == DB2_REAL || db2type == DB2_DECIMAL || db2type == DB2_DECFLOAT) && (pgtype == NUMERICOID || pgtype == FLOAT4OID || pgtype == FLOAT8OID)) { - db2Debug(5,"DB2_INTEGER,SMALLINT,BIGINT,FLOAT,DOUBLE,REAL,DECIMAL,DECFLOAT can be converted into NUMERICOID,FLOAT4OID,FLOAT8OID"); + db2Debug5("DB2_INTEGER,SMALLINT,BIGINT,FLOAT,DOUBLE,REAL,DECIMAL,DECFLOAT can be converted into NUMERICOID,FLOAT4OID,FLOAT8OID"); } else if ((db2type == DB2_INTEGER || db2type == DB2_SMALLINT || db2type == DB2_BIGINT || db2type == DB2_BOOLEAN) && scale <= 0 && (pgtype == INT2OID || pgtype == INT4OID || pgtype == INT8OID || pgtype == BOOLOID)) { - db2Debug(5,"DB2_INTEGER,SMALLINT,BIGINT,BOOLEAN can be converted into INT2OID, INT42OID, INT8OID,BOOLOID"); + db2Debug5("DB2_INTEGER,SMALLINT,BIGINT,BOOLEAN can be converted into INT2OID, INT42OID, INT8OID,BOOLOID"); } else if ((db2type == DB2_TYPE_DATE || db2type == DB2_TYPE_TIME || db2type == DB2_TYPE_TIMESTAMP || db2type == DB2_TYPE_TIMESTAMP_WITH_TIMEZONE) && (pgtype == DATEOID || pgtype == TIMESTAMPOID || pgtype == TIMESTAMPTZOID || pgtype == TIMEOID || pgtype == TIMETZOID)) { - db2Debug(5,"DB2_TYPE_DATE,TIME,TIMESTAMP,TIMESTAMP_WITH_TIMEZONE can be converted into DATEOID,TIMESTAMPOID,TIMESTAMPTZOID,TIMEOID,TIMETZOID"); + db2Debug5("DB2_TYPE_DATE,TIME,TIMESTAMP,TIMESTAMP_WITH_TIMEZONE can be converted into DATEOID,TIMESTAMPOID,TIMESTAMPTZOID,TIMEOID,TIMETZOID"); } else if ((db2type == DB2_VARCHAR || db2type == DB2_CLOB) && pgtype == JSONOID) { - db2Debug(5,"DB2_VARCHAR or DB2_CLOB can be converted into JSONOID"); + db2Debug5("DB2_VARCHAR or DB2_CLOB can be converted into JSONOID"); } else { /* nok - report an error */ ereport ( ERROR @@ -451,5 +448,5 @@ void checkDataType (short sqltype, int scale, Oid pgtype, const char *tablename, ) ); } - db2Exit(4,"< db2PlanForeignModify.c::checkDataType"); + db2Exit4(); } diff --git a/source/db2PrepareQuery.c b/source/db2PrepareQuery.c index 8993a03..a3ead8e 100644 --- a/source/db2PrepareQuery.c +++ b/source/db2PrepareQuery.c @@ -13,9 +13,6 @@ extern char db2Message[ERRBUFSIZE];/* contains DB2 error messages, set by db2CheckErr() */ /** external prototypes */ -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); -extern void db2Debug (int level, const char* message, ...); extern SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleType, int line, char* file); extern void db2Error (db2error sqlstate, const char* message); extern void db2Error_d (db2error sqlstate, const char* message, const char* detail, ...); @@ -46,10 +43,10 @@ void db2PrepareQuery (DB2Session* session, const char *query, DB2ResultColumn* r fetchsize = 1; #endif - db2Entry(1,"> db2PrepareQuery.c::db2PrepareQuery"); - db2Debug(2,"query : '%s'",query); - db2Debug(2,"prefetch : %d",prefetch); - db2Debug(2,"fetchsize: %d",fetchsize); + db2Entry1(); + db2Debug2("query : '%s'",query); + db2Debug2("prefetch : %d",prefetch); + db2Debug2("fetchsize: %d",fetchsize); /* figure out if the query is FOR UPDATE */ is_select = (strncmp (query, "SELECT", 6) == 0); for_update = (strstr (query, "FOR UPDATE") != NULL); @@ -61,27 +58,27 @@ void db2PrepareQuery (DB2Session* session, const char *query, DB2ResultColumn* r /* create statement handle */ session->stmtp = db2AllocStmtHdl(SQL_HANDLE_STMT, session->connp, FDW_UNABLE_TO_CREATE_EXECUTION, "error executing query: failed to allocate statement handle"); - db2Debug(2,"session->stmtp->hsql: %d",session->stmtp->hsql); + db2Debug2("session->stmtp->hsql: %d",session->stmtp->hsql); /* set prefetch options */ if (is_select) { SQLULEN prefetch_rows = prefetch; SQLULEN cur_fetchsize = fetchsize; - db2Debug(3,"IS_SELECT"); + db2Debug3("IS_SELECT"); if (for_update) { - db2Debug(3,"FOR UPDATE"); + db2Debug3("FOR UPDATE"); // Make the cursor sensitive scrollable (e.g., static) so PREFETCH_NROWS applies rc = SQLSetStmtAttr(session->stmtp->hsql, SQL_ATTR_CURSOR_TYPE, (SQLPOINTER)SQL_CURSOR_DYNAMIC, 0); rc = db2CheckErr(rc, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); if (rc != SQL_SUCCESS) { db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error executing query: SQLSetStmtAttr failed to make cursor dynamic", db2Message); } - db2Debug(3,"set cursor dynamic"); + db2Debug3("set cursor dynamic"); rc = SQLSetStmtAttr(session->stmtp->hsql, SQL_ATTR_CONCURRENCY, (SQLPOINTER)SQL_CONCUR_LOCK, 0); rc = db2CheckErr(rc, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); if (rc != SQL_SUCCESS) { db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error executing query: SQLSetStmtAttr failed to make cursor pessemistic", db2Message); } - db2Debug(3,"set cursor pessemistic"); + db2Debug3("set cursor pessemistic"); } else { // Make the cursor insensitive scrollable (e.g., static) so PREFETCH_NROWS applies rc = SQLSetStmtAttr(session->stmtp->hsql, SQL_ATTR_CURSOR_TYPE, (SQLPOINTER)SQL_CURSOR_STATIC, 0); @@ -89,7 +86,7 @@ void db2PrepareQuery (DB2Session* session, const char *query, DB2ResultColumn* r if (rc != SQL_SUCCESS) { db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error executing query: SQLSetStmtAttr failed to make cursor scrollable", db2Message); } - db2Debug(3,"set cursor static"); + db2Debug3("set cursor static"); } // Fetch rows per network roundtrip rc = SQLSetStmtAttr(session->stmtp->hsql, SQL_ATTR_ROW_ARRAY_SIZE, SQL_VALUE_PTR_ULEN(cur_fetchsize), 0); @@ -97,18 +94,18 @@ void db2PrepareQuery (DB2Session* session, const char *query, DB2ResultColumn* r if (rc != SQL_SUCCESS) { db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error executing query: SQLSetStmtAttr failed to set fetchsize in statement handle", db2Message); } - db2Debug(2,"set cursor fetchsize: %d",cur_fetchsize); + db2Debug2("set cursor fetchsize: %d",cur_fetchsize); // Prefetch rows per block for scrollable (non-dynamic) cursors rc = SQLSetStmtAttr(session->stmtp->hsql, SQL_ATTR_PREFETCH_NROWS, SQL_VALUE_PTR_ULEN(prefetch_rows), 0); rc = db2CheckErr(rc, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); if (rc != SQL_SUCCESS) { db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error executing query: SQLSetStmtAttr failed to set number of prefetched rows in statement handle", db2Message); } - db2Debug(2,"set cursor prefetch: %d",prefetch_rows); + db2Debug2("set cursor prefetch: %d",prefetch_rows); } /* prepare the statement */ - db2Debug(2,"query to prepare: '%s'",query); + db2Debug2("query to prepare: '%s'",query); rc = SQLPrepare(session->stmtp->hsql, (SQLCHAR*)query, SQL_NTS); rc = db2CheckErr(rc, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); if (rc != SQL_SUCCESS) { @@ -127,22 +124,22 @@ void db2PrepareQuery (DB2Session* session, const char *query, DB2ResultColumn* r if (res->pgtype == UUIDOID) { fparamType = SQL_C_CHAR; } - db2Debug(2,"res->colName : %s" ,res->colName); - db2Debug(2,"res->colSize : %ld",res->colSize); - db2Debug(2,"res->colType : %d" ,res->colType); - db2Debug(2,"res->colScale : %d" ,res->colScale); - db2Debug(2,"res->colNulls : %d" ,res->colNulls); - db2Debug(2,"res->colChars : %ld",res->colChars); - db2Debug(2,"res->colBytes : %ld",res->colBytes); - db2Debug(2,"res->colPrimKeyPart: %d" ,res->colPrimKeyPart); - db2Debug(2,"res->colCodepage : %d" ,res->colCodepage); - db2Debug(2,"res->val : %x" ,res->val); - db2Debug(2,"res->val_size : %ld",res->val_size); - db2Debug(2,"res->val_len : %d" ,res->val_len); - db2Debug(2,"res->val_null : %d" ,res->val_null); - db2Debug(2,"res->resnum : %d" ,res->resnum); - db2Debug(2,"fparamType: %d (%s)",fparamType,param2name(fparamType)); - db2Debug(2,"SQLBindCol(%d,%d,%d(%s),%x,%ld,%x)",session->stmtp->hsql,res->resnum, fparamType, param2name(fparamType), res->val, res->val_size, &res->val_null); + db2Debug2("res->colName : %s" ,res->colName); + db2Debug2("res->colSize : %ld",res->colSize); + db2Debug2("res->colType : %d" ,res->colType); + db2Debug2("res->colScale : %d" ,res->colScale); + db2Debug2("res->colNulls : %d" ,res->colNulls); + db2Debug2("res->colChars : %ld",res->colChars); + db2Debug2("res->colBytes : %ld",res->colBytes); + db2Debug2("res->colPrimKeyPart: %d" ,res->colPrimKeyPart); + db2Debug2("res->colCodepage : %d" ,res->colCodepage); + db2Debug2("res->val : %x" ,res->val); + db2Debug2("res->val_size : %ld",res->val_size); + db2Debug2("res->val_len : %d" ,res->val_len); + db2Debug2("res->val_null : %d" ,res->val_null); + db2Debug2("res->resnum : %d" ,res->resnum); + db2Debug2("fparamType: %d (%s)",fparamType,param2name(fparamType)); + db2Debug2("SQLBindCol(%d,%d,%d(%s),%x,%ld,%x)",session->stmtp->hsql,res->resnum, fparamType, param2name(fparamType), res->val, res->val_size, &res->val_null); rc = SQLBindCol (session->stmtp->hsql,res->resnum, fparamType, res->val, res->val_size, &res->val_null); rc = db2CheckErr(rc, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); if (rc != SQL_SUCCESS) { @@ -151,8 +148,8 @@ void db2PrepareQuery (DB2Session* session, const char *query, DB2ResultColumn* r col_pos++; } - db2Debug(2,"is_select: %s",is_select ? "true" : "false"); - db2Debug(2,"col_pos: %d",col_pos); + db2Debug2("is_select: %s",is_select ? "true" : "false"); + db2Debug2("col_pos: %d",col_pos); if (is_select && col_pos == 0) { /* No columns selected (i.e., SELECT '1' FROM or COUNT(*)). * Use persistent buffers from statement handle to avoid stack deallocation issues. @@ -164,5 +161,5 @@ void db2PrepareQuery (DB2Session* session, const char *query, DB2ResultColumn* r db2Error_d ( FDW_UNABLE_TO_CREATE_EXECUTION, "error executing query: SQLBindCol failed to define result value", db2Message); } } - db2Exit(1,"< db2PrepareQuery.c::db2PrepareQuery"); + db2Exit1(); } diff --git a/source/db2ReAllocFree.c b/source/db2ReAllocFree.c index 64ca2fb..4a1c56b 100644 --- a/source/db2ReAllocFree.c +++ b/source/db2ReAllocFree.c @@ -4,9 +4,6 @@ #include "db2_fdw.h" /*+ external prototypes */ -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); -extern void db2Debug (int level, const char* message, ...); /** local prototypes */ void* db2alloc (const char* type, size_t size); @@ -19,9 +16,7 @@ char* db2strdup (const char* source); */ void* db2alloc (const char* type, size_t size) { void* memory = palloc0(size); - db2Entry(5,"db2ReAllocFree.c::db2alloc"); - db2Debug(4,"++ %x: %d bytes - %s", memory, size, type); - db2Exit (5,"db2ReAllocFree.c::db2alloc"); + db2Debug5("++ %x: %d bytes - %s", memory, size, type); return memory; } @@ -30,9 +25,7 @@ void* db2alloc (const char* type, size_t size) { */ void* db2realloc (void* p, size_t size) { void* memory = repalloc(p, size); - db2Entry(5,"db2ReAllocFree.c::db2realloc"); - db2Debug(4,"++ %x: %d bytes", memory, size); - db2Exit(5,"db2ReAllocFree.c::db2realloc"); + db2Debug5("++ %x: %d bytes", memory, size); return memory; } @@ -40,21 +33,17 @@ void* db2realloc (void* p, size_t size) { * Expose pfree() to DB2 functions. */ void db2free (void* p) { - db2Entry(5,"db2ReAllocFree.c::db2free"); if (p != NULL) { - db2Debug(4,"-- %x", p); + db2Debug5("-- %x", p); pfree (p); } - db2Exit(5,"db2ReAllocFree.c::db2free"); } char* db2strdup(const char* source) { char* target = NULL; - db2Entry(5,"db2ReAllocFree.c::db2strdup"); if (source != NULL && source[0] != '\0') { target = pstrdup(source); } - db2Debug(4,"++ %x: dup'ed string from %x content '%s'",target, source, source); - db2Exit(5,"db2ReAllocFree.c::db2strdup"); + db2Debug5("++ %x: dup'ed string from %x content '%s'",target, source, source); return target; } \ No newline at end of file diff --git a/source/db2ReScanForeignScan.c b/source/db2ReScanForeignScan.c index 71594ea..d6e7e12 100644 --- a/source/db2ReScanForeignScan.c +++ b/source/db2ReScanForeignScan.c @@ -7,8 +7,6 @@ /** external prototypes */ extern void db2CloseStatement (DB2Session* session); -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); /** local prototypes */ void db2ReScanForeignScan(ForeignScanState* node); @@ -20,10 +18,10 @@ void db2ReScanForeignScan(ForeignScanState* node); void db2ReScanForeignScan (ForeignScanState* node) { DB2FdwState* fdw_state = (DB2FdwState*) node->fdw_state; - db2Entry(1,"> db2ReScanForeignScan.c::db2ReScanForeignScan"); + db2Entry1(); /* close open DB2 statement if there is one */ db2CloseStatement(fdw_state->session); /* reset row count to zero */ fdw_state->rowcount = 0; - db2Exit(1,"< db2ReScanForeignScan.c::db2ReScanForeignScan"); + db2Exit1(); } diff --git a/source/db2ServerVersion.c b/source/db2ServerVersion.c index 5ca53ba..a599885 100644 --- a/source/db2ServerVersion.c +++ b/source/db2ServerVersion.c @@ -8,9 +8,6 @@ /** external variables */ /** external prototypes */ -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); -extern void db2Debug (int level, const char* message, ...); extern SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleType, int line, char* file); /** local prototypes */ @@ -23,10 +20,10 @@ void db2ServerVersion (DB2Session* session, char* version) { SQLSMALLINT len = 0; size_t ver_len = sizeof(version); SQLRETURN rc = 0; - db2Entry(1,"> db2ServerVersion.c::db2ServerVersion"); + db2Entry1(); memset(version,0x00,ver_len); rc = SQLGetInfo(session->connp->hdbc, SQL_DBMS_VER, version, sizeof(version), &len); - db2Debug(2,"rc = %d, version = '%s', ind = %d", rc, version, len); + db2Debug2("rc = %d, version = '%s', ind = %d", rc, version, len); rc = db2CheckErr(rc,session->connp->hdbc,SQL_HANDLE_DBC,__LINE__,__FILE__); - db2Exit(1,"< db2ServerVersion.c::db2ServerVersion - version: '%s'", version); + db2Exit1(": '%s'", version); } diff --git a/source/db2SetHandlers.c b/source/db2SetHandlers.c index 17b8c83..cfbb0d0 100644 --- a/source/db2SetHandlers.c +++ b/source/db2SetHandlers.c @@ -7,8 +7,6 @@ /** external prototypes */ extern void db2Cancel (void); -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); /** local prototypes */ void db2SetHandlers(void); @@ -18,9 +16,9 @@ void db2Die (SIGNAL_ARGS); * Set signal handler for SIGTERM. */ void db2SetHandlers (void) { - db2Entry(5,"> db2SetHandlers.c::db2SetHandlers"); + db2Entry5(); pqsignal (SIGTERM, db2Die); - db2Exit(5,"< db2SetHandlers.c::db2SetHandlers"); + db2Exit5(); } /* db2Die @@ -28,7 +26,7 @@ void db2SetHandlers (void) { * This is a signal handler function. */ void db2Die (SIGNAL_ARGS) { - db2Entry(1,"> db2SetHandlers.c::db2Die"); + db2Entry1(); /* Terminate any running queries. * The DB2 sessions will be terminated by exitHook(). */ @@ -39,5 +37,5 @@ void db2Die (SIGNAL_ARGS) { * this is done in db2Error_d. */ die (postgres_signal_arg); - db2Exit(1,"< db2SetHandlers.c::db2Die"); + db2Exit1(); } \ No newline at end of file diff --git a/source/db2SetSavepoint.c b/source/db2SetSavepoint.c index ba97641..bf17cc2 100644 --- a/source/db2SetSavepoint.c +++ b/source/db2SetSavepoint.c @@ -9,9 +9,6 @@ extern char db2Message[ERRBUFSIZE];/* contains DB2 error messages, set by db2CheckErr() */ /** external prototypes */ -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); -extern void db2Debug (int level, const char* message, ...); extern void db2Error_d (db2error sqlstate, const char* message, const char* detail, ...); extern SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleType, int line, char* file); extern HdlEntry* db2AllocStmtHdl (SQLSMALLINT type, DB2ConnEntry* connp, db2error error, const char* errmsg); @@ -27,14 +24,14 @@ void db2SetSavepoint (DB2Session* session, int nest_level) { SQLRETURN rc = 0; HdlEntry* hstmt = NULL; - db2Entry(1,"> db2SetSavepoint.c::db2SetSavepoint(session, nest_level %d)",nest_level); - db2Debug(2,"xact_level: %d",session->connp->xact_level); + db2Entry1("(session, nest_level %d)",nest_level); + db2Debug2("xact_level: %d",session->connp->xact_level); while (session->connp->xact_level < nest_level) { SQLCHAR query[80]; - db2Debug(2,"db2_fdw::db2SetSavepoint: set savepoint s%d", session->connp->xact_level + 1); + db2Debug2("db2_fdw::db2SetSavepoint: set savepoint s%d", session->connp->xact_level + 1); snprintf((char*)query, 79, "SAVEPOINT s%d ON ROLLBACK RETAIN CURSORS", session->connp->xact_level + 1); - db2Debug(2,"query: '%s'",query); + db2Debug2("query: '%s'",query); /* create statement handle */ hstmt = db2AllocStmtHdl(SQL_HANDLE_STMT, session->connp, FDW_UNABLE_TO_CREATE_EXECUTION, "error setting savepoint: failed to allocate statement handle"); @@ -57,6 +54,6 @@ void db2SetSavepoint (DB2Session* session, int nest_level) { db2FreeStmtHdl(hstmt, session->connp); ++session->connp->xact_level; } - db2Debug(2,"xact_level: %d",session->connp->xact_level); - db2Exit(1,"< db2SetSavepoint.c::db2SetSavepoint"); + db2Debug2("xact_level: %d",session->connp->xact_level); + db2Exit1(); } diff --git a/source/db2Shutdown.c b/source/db2Shutdown.c index 80c1141..d590246 100644 --- a/source/db2Shutdown.c +++ b/source/db2Shutdown.c @@ -10,8 +10,6 @@ extern int sql_initialized; /* set to "1" as soon as SQLAllocHand extern DB2EnvEntry* rootenvEntry; /* Linked list of handles for cached DB2 connections. */ /** external prototypes */ -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); extern void db2FreeEnvHdl (DB2EnvEntry* envp, const char* nls_lang); extern void db2CloseConnections (void); @@ -23,12 +21,12 @@ void db2Shutdown(void); * This will be called at the end of the PostgreSQL session. */ void db2Shutdown (void) { - db2Entry(1,"> db2Shutdown.c::db2Shutdown"); + db2Entry1(); /* don't report error messages */ silent = 1; db2CloseConnections(); /* done with DB2 */ if (sql_initialized) db2FreeEnvHdl(rootenvEntry, NULL); - db2Exit(1,"< db2Shutdown.c::db2Shutdown"); + db2Exit1(); } diff --git a/source/db2_de_serialize.c b/source/db2_de_serialize.c index 2c45cd4..a186a83 100644 --- a/source/db2_de_serialize.c +++ b/source/db2_de_serialize.c @@ -5,9 +5,6 @@ #include "DB2FdwState.h" /** external prototypes */ -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); -extern void db2Debug (int level, const char* message, ...); extern void* db2alloc (const char* type, size_t size); extern char* c2name (short fcType); @@ -30,7 +27,7 @@ DB2FdwState* deserializePlanData (List* list) { int len = 0; ParamDesc* param = NULL; - db2Entry(1,"> db2_de_serialize.c::deserializePlanData"); + db2Entry1(); /* session will be set upon connect */ state->session = NULL; /* these fields are not needed during execution */ @@ -74,39 +71,39 @@ DB2FdwState* deserializePlanData (List* list) { for (i = 0; i < state->db2Table->ncols; ++i) { state->db2Table->cols[i] = (DB2Column *) db2alloc ("state->db2Table->cols[i]", sizeof (DB2Column)); state->db2Table->cols[i]->colName = deserializeString(list_nth(list, idx++)); - db2Debug(3,"deserialize col[%d].colName: %s" ,i, state->db2Table->cols[i]->colName); + db2Debug3("deserialize col[%d].colName: %s" ,i, state->db2Table->cols[i]->colName); state->db2Table->cols[i]->colType = (short) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug(3,"deserialize col[%d].colType: %d" ,i, state->db2Table->cols[i]->colType); + db2Debug3("deserialize col[%d].colType: %d" ,i, state->db2Table->cols[i]->colType); state->db2Table->cols[i]->colSize = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug(3,"deserialize col[%d].colSize: %d" ,i, state->db2Table->cols[i]->colSize); + db2Debug3("deserialize col[%d].colSize: %d" ,i, state->db2Table->cols[i]->colSize); state->db2Table->cols[i]->colScale = (short) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug(3,"deserialize col[%d].colScale: %d" ,i, state->db2Table->cols[i]->colScale); + db2Debug3("deserialize col[%d].colScale: %d" ,i, state->db2Table->cols[i]->colScale); state->db2Table->cols[i]->colNulls = (short) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug(3,"deserialize col[%d].colNulls: %d" ,i, state->db2Table->cols[i]->colNulls); + db2Debug3("deserialize col[%d].colNulls: %d" ,i, state->db2Table->cols[i]->colNulls); state->db2Table->cols[i]->colChars = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug(3,"deserialize col[%d].colChars: %d" ,i, state->db2Table->cols[i]->colChars); + db2Debug3("deserialize col[%d].colChars: %d" ,i, state->db2Table->cols[i]->colChars); state->db2Table->cols[i]->colBytes = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug(3,"deserialize col[%d].colBytes: %d" ,i, state->db2Table->cols[i]->colBytes); + db2Debug3("deserialize col[%d].colBytes: %d" ,i, state->db2Table->cols[i]->colBytes); state->db2Table->cols[i]->colPrimKeyPart = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug(3,"deserialize col[%d].colPrimKeyPart: %d" ,i, state->db2Table->cols[i]->colPrimKeyPart); + db2Debug3("deserialize col[%d].colPrimKeyPart: %d" ,i, state->db2Table->cols[i]->colPrimKeyPart); state->db2Table->cols[i]->colCodepage = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug(3,"deserialize col[%d].colCodepage: %d" ,i, state->db2Table->cols[i]->colCodepage); + db2Debug3("deserialize col[%d].colCodepage: %d" ,i, state->db2Table->cols[i]->colCodepage); state->db2Table->cols[i]->pgname = deserializeString(list_nth(list, idx++)); - db2Debug(3,"deserialize col[%d].pgname: %s" ,i, state->db2Table->cols[i]->pgname); + db2Debug3("deserialize col[%d].pgname: %s" ,i, state->db2Table->cols[i]->pgname); state->db2Table->cols[i]->pgattnum = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug(3,"deserialize col[%d].pgattnum: %d" ,i, state->db2Table->cols[i]->pgattnum); + db2Debug3("deserialize col[%d].pgattnum: %d" ,i, state->db2Table->cols[i]->pgattnum); state->db2Table->cols[i]->pgtype = DatumGetObjectId(((Const*)list_nth(list, idx++))->constvalue); - db2Debug(3,"deserialize col[%d].pgtype: %d" ,i, state->db2Table->cols[i]->pgtype); + db2Debug3("deserialize col[%d].pgtype: %d" ,i, state->db2Table->cols[i]->pgtype); state->db2Table->cols[i]->pgtypmod = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug(3,"deserialize col[%d].pgtypmod: %d" ,i, state->db2Table->cols[i]->pgtypmod); + db2Debug3("deserialize col[%d].pgtypmod: %d" ,i, state->db2Table->cols[i]->pgtypmod); state->db2Table->cols[i]->used = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug(3,"deserialize col[%d].used: %d" ,i, state->db2Table->cols[i]->used); + db2Debug3("deserialize col[%d].used: %d" ,i, state->db2Table->cols[i]->used); state->db2Table->cols[i]->pkey = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug(3,"deserialize col[%d].pkey: %d" ,i, state->db2Table->cols[i]->pkey); + db2Debug3("deserialize col[%d].pkey: %d" ,i, state->db2Table->cols[i]->pkey); state->db2Table->cols[i]->val_size = deserializeLong(list_nth(list, idx++)); - db2Debug(3,"deserialize col[%d].val_size: %ld" ,i, state->db2Table->cols[i]->val_size); + db2Debug3("deserialize col[%d].val_size: %ld" ,i, state->db2Table->cols[i]->val_size); state->db2Table->cols[i]->noencerr = deserializeLong(list_nth(list, idx++)); - db2Debug(3,"deserialize col[%d].noencerr: %ld" ,i, state->db2Table->cols[i]->noencerr); + db2Debug3("deserialize col[%d].noencerr: %ld" ,i, state->db2Table->cols[i]->noencerr); } /* length of parameter list */ @@ -117,28 +114,28 @@ DB2FdwState* deserializePlanData (List* list) { for (i = 0; i < len; ++i) { param = (ParamDesc*) db2alloc ("state->parmList->next", sizeof (ParamDesc)); param->colName = deserializeString(list_nth(list, idx++)); - db2Debug(3,"deserialize param[%d].colName: %s" ,i, param->colName); + db2Debug3("deserialize param[%d].colName: %s" ,i, param->colName); param->colType = (short) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug(3,"deserialize param[%d].colType: %d" ,i, param->colType); + db2Debug3("deserialize param[%d].colType: %d" ,i, param->colType); param->colSize = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug(3,"deserialize param[%d].colSize: %d" ,i, param->colSize); + db2Debug3("deserialize param[%d].colSize: %d" ,i, param->colSize); param->type = DatumGetObjectId(((Const*)list_nth(list, idx++))->constvalue); - db2Debug(3,"deserialize param[%d].type: %d" ,i, param->type); + db2Debug3("deserialize param[%d].type: %d" ,i, param->type); param->bindType = (db2BindType) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug(3,"deserialize param[%d].bindType: %d" ,i, param->bindType); + db2Debug3("deserialize param[%d].bindType: %d" ,i, param->bindType); if (param->bindType == BIND_OUTPUT) param->value = (void *) 42; /* something != NULL */ else param->value = NULL; - db2Debug(3,"deserialize param[%d].value: %x" ,i, param->value); + db2Debug3("deserialize param[%d].value: %x" ,i, param->value); param->val_size = deserializeLong(list_nth(list, idx++)); - db2Debug(3,"deserialize param[%d].val_size: %ld" ,i, param->val_size); + db2Debug3("deserialize param[%d].val_size: %ld" ,i, param->val_size); param->node = NULL; - db2Debug(3,"deserialize param[%d].node: %x" ,i, param->node); + db2Debug3("deserialize param[%d].node: %x" ,i, param->node); param->colnum = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug(3,"deserialize param[%d].colnum: %d" ,i, param->colnum); + db2Debug3("deserialize param[%d].colnum: %d" ,i, param->colnum); param->txts = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug(3,"deserialize param[%d].txts: %d" ,i, param->txts); + db2Debug3("deserialize param[%d].txts: %d" ,i, param->txts); param->next = state->paramList; state->paramList = param; } @@ -150,46 +147,46 @@ DB2FdwState* deserializePlanData (List* list) { for (i = 0; i < len; ++i) { DB2ResultColumn* res = (DB2ResultColumn *) db2alloc ("state->resultList->next", sizeof (DB2ResultColumn)); res->colName = deserializeString(list_nth(list, idx++)); - db2Debug(3,"deserialize res[%d].colName: %s" ,i, res->colName); + db2Debug3("deserialize res[%d].colName: %s" ,i, res->colName); res->colType = (short) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug(3,"deserialize res[%d].colType: %d" ,i, res->colType); + db2Debug3("deserialize res[%d].colType: %d" ,i, res->colType); res->colSize = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug(3,"deserialize res[%d].colSize: %d" ,i, res->colSize); + db2Debug3("deserialize res[%d].colSize: %d" ,i, res->colSize); res->colScale = (short) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug(3,"deserialize res[%d].colScale: %d" ,i, res->colScale); + db2Debug3("deserialize res[%d].colScale: %d" ,i, res->colScale); res->colNulls = (short) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug(3,"deserialize res[%d].colNulls: %d" ,i, res->colNulls); + db2Debug3("deserialize res[%d].colNulls: %d" ,i, res->colNulls); res->colChars = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug(3,"deserialize res[%d].colChars: %d" ,i, res->colChars); + db2Debug3("deserialize res[%d].colChars: %d" ,i, res->colChars); res->colBytes = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug(3,"deserialize res[%d].colBytes: %d" ,i, res->colBytes); + db2Debug3("deserialize res[%d].colBytes: %d" ,i, res->colBytes); res->colPrimKeyPart = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug(3,"deserialize res[%d].colPrimKeyPart: %d" ,i, res->colPrimKeyPart); + db2Debug3("deserialize res[%d].colPrimKeyPart: %d" ,i, res->colPrimKeyPart); res->colCodepage = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug(3,"deserialize res[%d].colCodepage: %d" ,i, res->colCodepage); + db2Debug3("deserialize res[%d].colCodepage: %d" ,i, res->colCodepage); res->pgname = deserializeString(list_nth(list, idx++)); - db2Debug(3,"deserialize res[%d].pgname: %s" ,i, res->pgname); + db2Debug3("deserialize res[%d].pgname: %s" ,i, res->pgname); res->pgattnum = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug(3,"deserialize res[%d].pgattnum: %d" ,i, res->pgattnum); + db2Debug3("deserialize res[%d].pgattnum: %d" ,i, res->pgattnum); res->pgtype = DatumGetObjectId(((Const*)list_nth(list, idx++))->constvalue); - db2Debug(3,"deserialize res[%d].pgtype: %d" ,i, res->pgtype); + db2Debug3("deserialize res[%d].pgtype: %d" ,i, res->pgtype); res->pgtypmod = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug(3,"deserialize res[%d].pgtypmod: %d" ,i, res->pgtypmod); + db2Debug3("deserialize res[%d].pgtypmod: %d" ,i, res->pgtypmod); res->pkey = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug(3,"deserialize res[%d].pkey: %d" ,i, res->pkey); + db2Debug3("deserialize res[%d].pkey: %d" ,i, res->pkey); res->val_size = deserializeLong(list_nth(list, idx++)); - db2Debug(3,"deserialize res[%d].val_size: %ld" ,i, res->val_size); + db2Debug3("deserialize res[%d].val_size: %ld" ,i, res->val_size); res->noencerr = deserializeLong(list_nth(list, idx++)); - db2Debug(3,"deserialize res[%d].noencerr: %ld" ,i, res->noencerr); + db2Debug3("deserialize res[%d].noencerr: %ld" ,i, res->noencerr); res->resnum = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - db2Debug(3,"deserialize res[%d].resnum: %d" ,i, res->resnum); + db2Debug3("deserialize res[%d].resnum: %d" ,i, res->resnum); res->val = (char*) db2alloc ("res->val", res->val_size + 1); res->val_len = 0; res->val_null = 1; res->next = state->resultList; state->resultList = res; } - db2Exit(1,"< db2_de_serialize.c::deserializePlanData : %x", state); + db2Exit1(": %x", state); return state; } @@ -198,10 +195,10 @@ DB2FdwState* deserializePlanData (List* list) { */ static char* deserializeString (Const* constant) { char* result = NULL; - db2Entry(5,"> db2_de_serialize.c::deserializeString"); + db2Entry5(); if (!constant->constisnull) result = text_to_cstring (DatumGetTextP (constant->constvalue)); - db2Exit(5,"< db2_de_serialize.c::deserializeString : '%s'", result); + db2Exit5(": '%s'", result); return result; } @@ -210,10 +207,10 @@ static char* deserializeString (Const* constant) { */ static long deserializeLong (Const* constant) { long result = 0L; - db2Entry(5,"> db2_de_serialize.c::deserializeLong"); + db2Entry5(); result = (sizeof (long) <= 4) ? (long) DatumGetInt32 (constant->constvalue) : (long) DatumGetInt64 (constant->constvalue); - db2Exit(5,"< db2_de_serialize.c::deserializeLong : %ld", result); + db2Exit5(": %ld", result); return result; } @@ -228,7 +225,7 @@ List* serializePlanData (DB2FdwState* fdwState) { ParamDesc* param = NULL; DB2ResultColumn* rcol = NULL; - db2Entry(1,"> db2_de_serialize.c::serializePlanData"); + db2Entry1(); result = list_make1(fdwState->retrieved_attr); /* dbserver */ result = lappend (result, serializeString (fdwState->dbserver)); @@ -261,39 +258,39 @@ List* serializePlanData (DB2FdwState* fdwState) { /* column data */ for (idxCol = 0; idxCol < fdwState->db2Table->ncols; ++idxCol) { result = lappend (result, serializeString (fdwState->db2Table->cols[idxCol]->colName)); - db2Debug(3,"serialize col[%d].colName: %s" ,idxCol, fdwState->db2Table->cols[idxCol]->colName); + db2Debug3("serialize col[%d].colName: %s" ,idxCol, fdwState->db2Table->cols[idxCol]->colName); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->colType)); - db2Debug(3,"serialize col[%d].colType: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colType); + db2Debug3("serialize col[%d].colType: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colType); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->colSize)); - db2Debug(3,"serialize col[%d].colSize: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colSize); + db2Debug3("serialize col[%d].colSize: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colSize); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->colScale)); - db2Debug(3,"serialize col[%d].colScale: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colScale); + db2Debug3("serialize col[%d].colScale: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colScale); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->colNulls)); - db2Debug(3,"serialize col[%d].colNulls: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colNulls); + db2Debug3("serialize col[%d].colNulls: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colNulls); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->colChars)); - db2Debug(3,"serialize col[%d].colChars: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colChars); + db2Debug3("serialize col[%d].colChars: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colChars); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->colBytes)); - db2Debug(3,"serialize col[%d].colBytes: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colBytes); + db2Debug3("serialize col[%d].colBytes: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colBytes); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->colPrimKeyPart)); - db2Debug(3,"serialize col[%d].colPrimKeyPart: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colPrimKeyPart); + db2Debug3("serialize col[%d].colPrimKeyPart: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colPrimKeyPart); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->colCodepage)); - db2Debug(3,"serialize col[%d].colCodepage: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colCodepage); + db2Debug3("serialize col[%d].colCodepage: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->colCodepage); result = lappend (result, serializeString (fdwState->db2Table->cols[idxCol]->pgname)); - db2Debug(3,"serialize col[%d].pgname: %s" ,idxCol, fdwState->db2Table->cols[idxCol]->pgname); + db2Debug3("serialize col[%d].pgname: %s" ,idxCol, fdwState->db2Table->cols[idxCol]->pgname); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->pgattnum)); - db2Debug(3,"serialize col[%d].pgattnum: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->pgattnum); + db2Debug3("serialize col[%d].pgattnum: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->pgattnum); result = lappend (result, serializeOid (fdwState->db2Table->cols[idxCol]->pgtype)); - db2Debug(3,"serialize col[%d].pgtype: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->pgtype); + db2Debug3("serialize col[%d].pgtype: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->pgtype); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->pgtypmod)); - db2Debug(3,"serialize col[%d].pgtypmod: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->pgtypmod); + db2Debug3("serialize col[%d].pgtypmod: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->pgtypmod); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->used)); - db2Debug(3,"serialize col[%d].used: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->used); + db2Debug3("serialize col[%d].used: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->used); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->pkey)); - db2Debug(3,"serialize col[%d].pkey: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->pkey); + db2Debug3("serialize col[%d].pkey: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->pkey); result = lappend (result, serializeLong (fdwState->db2Table->cols[idxCol]->val_size)); - db2Debug(3,"serialize col[%d].val_size: %ld" ,idxCol, fdwState->db2Table->cols[idxCol]->val_size); + db2Debug3("serialize col[%d].val_size: %ld" ,idxCol, fdwState->db2Table->cols[idxCol]->val_size); result = lappend (result, serializeInt (fdwState->db2Table->cols[idxCol]->noencerr)); - db2Debug(3,"serialize col[%d].noencerr: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->noencerr); + db2Debug3("serialize col[%d].noencerr: %d" ,idxCol, fdwState->db2Table->cols[idxCol]->noencerr); /* don't serialize val, val_len, val_null and varno */ } @@ -303,25 +300,25 @@ List* serializePlanData (DB2FdwState* fdwState) { } /* serialize length */ result = lappend (result, serializeInt (lenParam)); - db2Debug(3,"serialize paramList.length: %d", lenParam); + db2Debug3("serialize paramList.length: %d", lenParam); /* parameter list entries */ for (param = fdwState->paramList; param; param = param->next) { result = lappend (result, serializeString (param->colName)); - db2Debug(3,"serialize param.colName: %s" , param->colName); + db2Debug3("serialize param.colName: %s" , param->colName); result = lappend (result, serializeInt (param->colType)); - db2Debug(3,"serialize param.colType: %d" , param->colType); + db2Debug3("serialize param.colType: %d" , param->colType); result = lappend (result, serializeInt (param->colSize)); - db2Debug(3,"serialize param.colSize: %d" , param->colSize); + db2Debug3("serialize param.colSize: %d" , param->colSize); result = lappend (result, serializeOid (param->type)); - db2Debug(3,"serialize param.type: %d" , param->type); + db2Debug3("serialize param.type: %d" , param->type); result = lappend (result, serializeInt ((int) param->bindType)); - db2Debug(3,"serialize param.bindType: %d" , param->bindType); + db2Debug3("serialize param.bindType: %d" , param->bindType); result = lappend (result, serializeLong (param->val_size)); - db2Debug(3,"serialize param.val_size: %ld" , param->val_size); + db2Debug3("serialize param.val_size: %ld" , param->val_size); result = lappend (result, serializeInt ((int) param->colnum)); - db2Debug(3,"serialize param.colnum: %d" , param->colnum); + db2Debug3("serialize param.colnum: %d" , param->colnum); result = lappend (result, serializeInt ((int) param->txts)); - db2Debug(3,"serialize param.txts: %d" , param->txts); + db2Debug3("serialize param.txts: %d" , param->txts); } /* find length of result list */ @@ -331,48 +328,48 @@ List* serializePlanData (DB2FdwState* fdwState) { } /* serialize length */ result = lappend (result, serializeInt (lenParam)); - db2Debug(3,"serialize resultList.length: %d", lenParam); + db2Debug3("serialize resultList.length: %d", lenParam); /* parameter list entries */ for (rcol = fdwState->resultList; rcol; rcol = rcol->next) { result = lappend (result, serializeString (rcol->colName)); - db2Debug(3,"serialize res.colName: %s" , rcol->colName); + db2Debug3("serialize res.colName: %s" , rcol->colName); result = lappend (result, serializeInt (rcol->colType)); - db2Debug(3,"serialize res.colType: %d" , rcol->colType); + db2Debug3("serialize res.colType: %d" , rcol->colType); result = lappend (result, serializeInt (rcol->colSize)); - db2Debug(3,"serialize res.colSize: %d" , rcol->colSize); + db2Debug3("serialize res.colSize: %d" , rcol->colSize); result = lappend (result, serializeInt (rcol->colScale)); - db2Debug(3,"serialize res.colScale: %d" , rcol->colScale); + db2Debug3("serialize res.colScale: %d" , rcol->colScale); result = lappend (result, serializeInt (rcol->colNulls)); - db2Debug(3,"serialize res.colNulls: %d", rcol->colNulls); + db2Debug3("serialize res.colNulls: %d", rcol->colNulls); result = lappend (result, serializeInt (rcol->colChars)); - db2Debug(3,"serialize res.colChars: %d" , rcol->colChars); + db2Debug3("serialize res.colChars: %d" , rcol->colChars); result = lappend (result, serializeInt (rcol->colBytes)); - db2Debug(3,"serialize res.colBytes: %d" , rcol->colBytes); + db2Debug3("serialize res.colBytes: %d" , rcol->colBytes); result = lappend (result, serializeInt (rcol->colPrimKeyPart)); - db2Debug(3,"serialize res.colPrimKeyPart: %d", rcol->colPrimKeyPart); + db2Debug3("serialize res.colPrimKeyPart: %d", rcol->colPrimKeyPart); result = lappend (result, serializeInt (rcol->colCodepage)); - db2Debug(3,"serialize res.codepage: %d" , rcol->colCodepage); + db2Debug3("serialize res.codepage: %d" , rcol->colCodepage); result = lappend (result, serializeString (rcol->pgname)); - db2Debug(3,"serialize res.pgname: %s" , rcol->pgname); + db2Debug3("serialize res.pgname: %s" , rcol->pgname); result = lappend (result, serializeInt (rcol->pgattnum)); - db2Debug(3,"serialize res.pgattnum: %d" , rcol->pgattnum); + db2Debug3("serialize res.pgattnum: %d" , rcol->pgattnum); result = lappend (result, serializeOid (rcol->pgtype)); - db2Debug(3,"serialize res.pgtype: %d" , rcol->pgtype); + db2Debug3("serialize res.pgtype: %d" , rcol->pgtype); result = lappend (result, serializeInt (rcol->pgtypmod)); - db2Debug(3,"serialize res.pgtypmod: %d" , rcol->pgtypmod); + db2Debug3("serialize res.pgtypmod: %d" , rcol->pgtypmod); result = lappend (result, serializeInt (rcol->pkey)); - db2Debug(3,"serialize res.pkey: %d" , rcol->pkey); + db2Debug3("serialize res.pkey: %d" , rcol->pkey); result = lappend (result, serializeLong (rcol->val_size)); - db2Debug(3,"serialize res.val_size: %ld", rcol->val_size); + db2Debug3("serialize res.val_size: %ld", rcol->val_size); result = lappend (result, serializeInt (rcol->noencerr)); - db2Debug(3,"serialize res.noencerr: %d" , rcol->noencerr); + db2Debug3("serialize res.noencerr: %d" , rcol->noencerr); result = lappend (result, serializeInt (rcol->resnum)); // the last result is the first in the list - db2Debug(3,"serialize res.resnum: %d" , rcol->resnum); + db2Debug3("serialize res.resnum: %d" , rcol->resnum); lenParam--; } /* don't serialize params, startup_cost, total_cost, rowcount, temp_cxt, order_clause and where_clause */ - db2Exit(1,"< db2_de_serialize.c::serializePlanData : %x",result); + db2Exit1(": %x",result); return result; } @@ -381,10 +378,10 @@ List* serializePlanData (DB2FdwState* fdwState) { */ static Const* serializeString (const char* s) { Const* result = NULL; - db2Entry(5,"> db2_de_serialize.c::serializeString"); + db2Entry5(); result = (s == NULL) ? makeNullConst (TEXTOID, -1, InvalidOid) : makeConst (TEXTOID, -1, InvalidOid, -1, PointerGetDatum (cstring_to_text (s)), false, false); - db2Exit(5,"< db2_de_serialize.c::serializeString : %x",result); + db2Exit5(": %x",result); return result; } @@ -393,7 +390,7 @@ static Const* serializeString (const char* s) { */ static Const* serializeLong (long i) { Const* result = NULL; - db2Entry(5,"> db2_de_serialize.c::serializeLong"); + db2Entry5(); if (sizeof (long) <= 4) result = makeConst (INT4OID, -1, InvalidOid, 4, Int32GetDatum ((int32) i), false, true); else @@ -404,6 +401,6 @@ static Const* serializeLong (long i) { false #endif /* USE_FLOAT8_BYVAL */ ); - db2Exit(5,"< db2_de_serialize.c::serializeLong : %x",result); + db2Exit5(": %x",result); return result; } diff --git a/source/db2_deparse.c b/source/db2_deparse.c index bebebcd..1070c24 100644 --- a/source/db2_deparse.c +++ b/source/db2_deparse.c @@ -88,9 +88,6 @@ typedef struct deparse_expr_cxt { /** external prototypes */ extern short c2dbType (short fcType); -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); -extern void db2Debug (int level, const char* message, ...); extern void* db2alloc (const char* type, size_t size); extern void* db2strdup (const char* source); extern void db2free (void* p); @@ -187,7 +184,7 @@ static void deparseReturningList (StringInfo buf, RangeTblEntry *rt void classifyConditions(PlannerInfo* root, RelOptInfo* baserel, List* input_conds, List** remote_conds, List** local_conds) { ListCell* lc = NULL; - db2Entry(1,"> db2_deparse.c::classifyConditions"); + db2Entry1(); *remote_conds = NIL; *local_conds = NIL; @@ -199,7 +196,7 @@ void classifyConditions(PlannerInfo* root, RelOptInfo* baserel, List* input_cond else *local_conds = lappend(*local_conds, ri); } - db2Exit(1,"< db2_deparse.c::classifyConditions"); + db2Exit1(); } /** Returns true if given expr is safe to evaluate on the foreign server. @@ -210,7 +207,7 @@ bool is_foreign_expr(PlannerInfo *root, RelOptInfo *baserel, Expr *expr) { DB2FdwState* fpinfo = (DB2FdwState*) (baserel->fdw_private); bool fResult = false; - db2Entry(1,"> db2_deparse.c::is_foreign_expr"); + db2Entry1(); // Check that the expression consists of nodes that are safe to execute remotely. glob_cxt.root = root; glob_cxt.foreignrel = baserel; @@ -232,7 +229,7 @@ bool is_foreign_expr(PlannerInfo *root, RelOptInfo *baserel, Expr *expr) { } } } - db2Exit(1,"< db2_deparse.c::is_foreign_expr : %s", (fResult) ? "true": "false"); + db2Exit1(": %s", (fResult) ? "true": "false"); /* OK to evaluate on the remote server */ return fResult; } @@ -256,7 +253,7 @@ bool is_foreign_expr(PlannerInfo *root, RelOptInfo *baserel, Expr *expr) { static bool foreign_expr_walker(Node* node, foreign_glob_cxt* glob_cxt, foreign_loc_cxt* outer_cxt, foreign_loc_cxt* case_arg_cxt) { bool fResult = true; - db2Entry(1,"> db2_deparse.c::foreign_expr_walker"); + db2Entry1(); /* Need do nothing for empty subexpressions */ if (node != NULL) { bool check_type = true; @@ -765,7 +762,7 @@ static bool foreign_expr_walker(Node* node, foreign_glob_cxt* glob_cxt, foreign_ } } /* It looks OK */ - db2Exit(1,"< db2_deparse.c::foreign_expr_walker : %s", (fResult) ? "true" : "false"); + db2Exit1(": %s", (fResult) ? "true" : "false"); return fResult; } @@ -778,10 +775,10 @@ static bool foreign_expr_walker(Node* node, foreign_glob_cxt* glob_cxt, foreign_ */ bool is_foreign_param(PlannerInfo* root, RelOptInfo* baserel, Expr* expr) { bool fResult = false; - db2Entry(1,"> db2_deparse.c::is_foreign_param"); - db2Debug(2,"expr: %x", expr); + db2Entry1(); + db2Debug2("expr: %x", expr); if (expr != NULL) { - db2Debug(5,"((Node*)expr)->type: %d", nodeTag(expr)); + db2Debug5("((Node*)expr)->type: %d", nodeTag(expr)); switch (nodeTag(expr)) { case T_Var: { /* It would have to be sent unless it's a foreign Var */ @@ -800,7 +797,7 @@ bool is_foreign_param(PlannerInfo* root, RelOptInfo* baserel, Expr* expr) { break; } } - db2Exit(1,"< db2_deparse.c::is_foreign_param : %s", (fResult) ? "true" : "false"); + db2Exit1(": %s", (fResult) ? "true" : "false"); return fResult; } @@ -812,7 +809,7 @@ bool is_foreign_pathkey(PlannerInfo* root, RelOptInfo* baserel, PathKey* pathkey DB2FdwState* fpinfo = (DB2FdwState*) baserel->fdw_private; bool fResult = false; - db2Entry(1,"> db2_deparse.c::is_foreign_pathkey"); + db2Entry1(); /* is_foreign_expr would detect volatile expressions as well, but checking ec_has_volatile here saves some cycles. */ if (!pathkey_ec->ec_has_volatile) { /* can't push down the sort if the pathkey's opfamily is not shippable */ @@ -821,7 +818,7 @@ bool is_foreign_pathkey(PlannerInfo* root, RelOptInfo* baserel, PathKey* pathkey fResult = (find_em_for_rel(root, pathkey_ec, baserel) != NULL); } } - db2Exit(1,"< db2_deparse.c::is_foreign_pathkey : %s", (fResult) ? "true" : "false"); + db2Exit1(": %s", (fResult) ? "true" : "false"); return fResult; } @@ -837,12 +834,12 @@ static char* deparse_type_name(Oid type_oid, int32 typemod) { bits16 flags = FORMAT_TYPE_TYPEMOD_GIVEN; char* result = NULL; - db2Entry(1,"> db2_deparse.c::deparse_type_name"); + db2Entry1(); if (!is_builtin(type_oid)) flags |= FORMAT_TYPE_FORCE_QUALIFY; result = format_type_extended(type_oid, typemod, flags); - db2Exit(1,"< db2_deparse.c::deparse_type_name : %s", result); + db2Exit1(": %s", result); return result; } @@ -850,9 +847,9 @@ static char* deparse_type_name(Oid type_oid, int32 typemod) { * Append "s" to "dest", adding appropriate casts for datetime "type". */ void appendAsType (StringInfoData* dest, Oid type) { - db2Entry(1,"> db2_deparse.c::appendAsType"); - db2Debug(2,"dest->data: '%s'",dest->data); - db2Debug(2,"type: %d",type); + db2Entry1(); + db2Debug2("dest->data: '%s'",dest->data); + db2Debug2("type: %d",type); switch (type) { case DATEOID: appendStringInfo (dest, "CAST (? AS DATE)"); @@ -873,8 +870,8 @@ void appendAsType (StringInfoData* dest, Oid type) { appendStringInfo (dest, "?"); break; } - db2Debug(2,"dest->data: '%s'", dest->data); - db2Exit(1,"< db2_deparse.c::appendAsType"); + db2Debug2("dest->data: '%s'", dest->data); + db2Exit1(); } /** Deparse GROUP BY clause. @@ -882,7 +879,7 @@ void appendAsType (StringInfoData* dest, Oid type) { static void appendGroupByClause(List* tlist, deparse_expr_cxt* context) { Query* query = context->root->parse; - db2Entry(1,"> db2_deparse.c::appendGroupByClause"); + db2Entry1(); /* Nothing to be done, if there's no GROUP BY clause in the query. */ if (query->groupClause) { StringInfo buf = context->buf; @@ -904,9 +901,9 @@ static void appendGroupByClause(List* tlist, deparse_expr_cxt* context) { first = false; deparseSortGroupClause(grp->tleSortGroupRef, tlist, true, context); } - db2Debug(5,"clause: %s", buf->data); + db2Debug5("clause: %s", buf->data); } - db2Exit(1,"< db2_deparse.c::appendGroupByClause"); + db2Exit1(); } /** Deparse ORDER BY clause defined by the given pathkeys. @@ -923,7 +920,7 @@ static void appendOrderByClause(List* pathkeys, bool has_final_sort, deparse_exp StringInfo buf = context->buf; bool gotone = false; - db2Entry(1,"> db2_deparse.c::appendOrderByClause"); + db2Entry1(); /* Make sure any constants in the exprs are printed portably */ nestlevel = set_transmission_modes(); @@ -974,8 +971,8 @@ static void appendOrderByClause(List* pathkeys, bool has_final_sort, deparse_exp appendOrderBySuffix(oprid, exprType((Node *) em_expr), pathkey->pk_nulls_first, context); } reset_transmission_modes(nestlevel); - db2Debug(5,"clause: %s", context->buf->data); - db2Exit(1,"< db2_deparse.c::appendOrderByClause"); + db2Debug5("clause: %s", context->buf->data); + db2Exit1(); } /** Deparse LIMIT/OFFSET clause. @@ -985,7 +982,7 @@ static void appendLimitClause(deparse_expr_cxt* context) { StringInfo buf = context->buf; int nestlevel = 0; - db2Entry(1,"> db2_deparse.c::appendLimitClause"); + db2Entry1(); /* Make sure any constants in the exprs are printed portably */ nestlevel = set_transmission_modes(); @@ -998,8 +995,8 @@ static void appendLimitClause(deparse_expr_cxt* context) { deparseExprInt((Expr*) root->parse->limitOffset, context); } reset_transmission_modes(nestlevel); - db2Debug(5," clause: %s", context->buf->data); - db2Exit(1,"< db2_deparse.c::appendLimitClause"); + db2Debug5(" clause: %s", context->buf->data); + db2Exit1(); } /** appendFunctionName @@ -1010,7 +1007,7 @@ static void appendLimitClause(deparse_expr_cxt* context) { // HeapTuple proctup; // Form_pg_proc procform; // -// db2Entry(1,"> db2_deparse.c::appendFunctionName"); +// db2Entry1(); // proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid)); // if (!HeapTupleIsValid(proctup)) // elog(ERROR, "cache lookup failed for function %u", funcid); @@ -1023,7 +1020,7 @@ static void appendLimitClause(deparse_expr_cxt* context) { // /* Always print the function name */ // appendStringInfoString(buf, quote_identifier(NameStr(procform->proname))); // ReleaseSysCache(proctup); -// db2Exit(1,"< db2_deparse.c::appendFunctionName : %s", context->buf->data); +// db2Exit1(": %s", context->buf->data); //} /** Append the ASC, DESC, USING and NULLS FIRST / NULLS LAST parts of an ORDER BY clause. @@ -1032,7 +1029,7 @@ static void appendOrderBySuffix(Oid sortop, Oid sortcoltype, bool nulls_first, d StringInfo buf = context->buf; TypeCacheEntry* typentry; - db2Entry(1,"< db2_deparse.c::appendOrderBySuffix"); + db2Entry1(); /* See whether operator is default < or > for sort expr's datatype. */ typentry = lookup_type_cache(sortcoltype, TYPECACHE_LT_OPR | TYPECACHE_GT_OPR); @@ -1055,7 +1052,7 @@ static void appendOrderBySuffix(Oid sortop, Oid sortcoltype, bool nulls_first, d } appendStringInfo(buf, " NULLS %s", (nulls_first) ? "FIRST" : "LAST"); - db2Exit(1,"< db2_deparse.c::appendOrderBySuffix : %s", buf->data); + db2Exit1(": %s", buf->data); } /* Print the representation of a parameter to be sent to the remote side. @@ -1067,10 +1064,10 @@ static void printRemoteParam(int paramindex, Oid paramtype, int32 paramtypmod, d StringInfo buf = context->buf; //char* ptypename = deparse_type_name(paramtype, paramtypmod); - db2Entry(1,"> db2_deparse.c::printRemoteParam"); + db2Entry1(); // appendStringInfo(buf, "$%d::%s", paramindex, ptypename); appendStringInfo(buf, ":p%d", paramindex); - db2Exit(1,"< db2_deparse.c::printRemoteParam : %s", buf->data); + db2Exit1(": %s", buf->data); } /* Print the representation of a placeholder for a parameter that will be sent to the remote side at execution time. @@ -1089,9 +1086,9 @@ static void printRemotePlaceholder(Oid paramtype, int32 paramtypmod, deparse_exp StringInfo buf = context->buf; char* ptypename = deparse_type_name(paramtype, paramtypmod); - db2Entry(1,"> printRemotePlaceholder"); + db2Entry1(); appendStringInfo(buf, "((SELECT null::%s)::%s)", ptypename, ptypename); - db2Exit(1,"< printRemotePlaceholder : %s", buf->data); + db2Exit1(": %s", buf->data); } /** Deparse conditions from the provided list and append them to buf. @@ -1106,7 +1103,7 @@ static void appendConditions(List* exprs, deparse_expr_cxt* context) { bool is_first = true; StringInfo buf = context->buf; - db2Entry(1,"> appendConditions"); + db2Entry1(); /* Make sure any constants in the exprs are printed portably */ nestlevel = set_transmission_modes(); @@ -1128,7 +1125,7 @@ static void appendConditions(List* exprs, deparse_expr_cxt* context) { is_first = false; } reset_transmission_modes(nestlevel); - db2Exit(1,"< appendConditions : %s", buf->data); + db2Exit1(": %s", buf->data); } /* Append WHERE clause, containing conditions from exprs and additional_conds, to context->buf. @@ -1138,7 +1135,7 @@ static void appendWhereClause(List* exprs, List* additional_conds, deparse_expr_ bool need_and = false; ListCell* lc = NULL; - db2Entry(1,"> appendWhereClause"); + db2Entry1(); if (exprs != NIL || additional_conds != NIL) appendStringInfoString(buf, " WHERE "); @@ -1155,7 +1152,7 @@ static void appendWhereClause(List* exprs, List* additional_conds, deparse_expr_ appendStringInfoString(buf, (char*) lfirst(lc)); need_and = true; } - db2Exit(1,"< appendWhereClause : %s", buf->data); + db2Exit1(": %s", buf->data); } /** Appends a sort or group clause. @@ -1168,7 +1165,7 @@ static Node* deparseSortGroupClause(Index ref, List* tlist, bool force_colno, de TargetEntry* tle = get_sortgroupref_tle(ref, tlist); Expr* expr = tle->expr; - db2Entry(1,"> db2_deparse.c::deparseSortGroupClause"); + db2Entry1(); if (force_colno) { /* Use column-number form when requested by caller. */ Assert(!tle->resjunk); @@ -1184,8 +1181,8 @@ static Node* deparseSortGroupClause(Index ref, List* tlist, bool force_colno, de deparseExprInt(expr, context); appendStringInfoChar(buf, ')'); } - db2Debug(5,"clause: %s", buf->data); - db2Exit(1,"< db2_deparse.c::deparseSortGroupClause : %x", expr); + db2Debug5("clause: %s", buf->data); + db2Exit1(": %x", expr); return (Node*)expr; } @@ -1195,7 +1192,7 @@ static Node* deparseSortGroupClause(Index ref, List* tlist, bool force_colno, de static bool is_subquery_var(Var* node, RelOptInfo* foreignrel, int* relno, int* colno) { bool fResult = false; - db2Entry(1,"> db2_deparse.c::is_subquery_var"); + db2Entry1(); /* Should only be called in these cases. */ Assert(IS_SIMPLE_REL(foreignrel) || IS_JOIN_REL(foreignrel)); /* If the given relation isn't a join relation, it doesn't have any lower subqueries, so the Var isn't a subquery output column. */ @@ -1226,7 +1223,7 @@ static bool is_subquery_var(Var* node, RelOptInfo* foreignrel, int* relno, int* } } } - db2Exit(1,"< db2_deparse.c::is_subquery_var : %s", (fResult) ? "true": "false"); + db2Exit1(": %s", (fResult) ? "true": "false"); return fResult; } @@ -1238,10 +1235,10 @@ static void get_relation_column_alias_ids(Var* node, RelOptInfo* foreignrel, int ListCell* lc = NULL; bool fFound = false; - db2Entry(1,"> db2_deparse.c::get_relation_column_alias_ids"); + db2Entry1(); /* Get the relation alias ID */ *relno = fpinfo->relation_index; - db2Debug(5,"relno: %d", *relno); + db2Debug5("relno: %d", *relno); /* Get the column alias ID */ foreach(lc, foreignrel->reltarget->exprs) { @@ -1252,7 +1249,7 @@ static void get_relation_column_alias_ids(Var* node, RelOptInfo* foreignrel, int */ if (IsA(tlvar, Var) && tlvar->varno == node->varno && tlvar->varattno == node->varattno) { *colno = i; - db2Debug(5,"colno: %d", *colno); + db2Debug5("colno: %d", *colno); fFound = true; break; } @@ -1263,7 +1260,7 @@ static void get_relation_column_alias_ids(Var* node, RelOptInfo* foreignrel, int /* Shouldn't get here */ elog(ERROR, "unexpected expression in subquery output"); } - db2Exit(1,"< db2_deparse.c::get_relation_column_alias_ids"); + db2Exit1(); } /* Build the targetlist for given relation to be deparsed as SELECT clause. @@ -1276,23 +1273,23 @@ List* build_tlist_to_deparse(RelOptInfo* foreignrel) { List* tlist = NIL; DB2FdwState* fpinfo = (DB2FdwState*) foreignrel->fdw_private; - db2Entry(1,"> db2_deparse.c::build_tlist_to_deparse"); + db2Entry1(); /* For an upper relation, we have already built the target list while checking shippability, so just return that. */ if (IS_UPPER_REL(foreignrel)) { tlist = fpinfo->grouped_tlist; - db2Debug(2,"using fpinfo->grouped_tlist"); + db2Debug2("using fpinfo->grouped_tlist"); } else { ListCell* lc = NULL; /* We require columns specified in foreignrel->reltarget->exprs and those required for evaluating the local conditions. */ - db2Debug(2,"using foreignrel->reltarget->exprs"); + db2Debug2("using foreignrel->reltarget->exprs"); tlist = add_to_flat_tlist(tlist, pull_var_clause((Node*) foreignrel->reltarget->exprs, PVC_RECURSE_PLACEHOLDERS)); foreach(lc, fpinfo->local_conds) { RestrictInfo* rinfo = lfirst_node(RestrictInfo, lc); tlist = add_to_flat_tlist(tlist, pull_var_clause((Node*) rinfo->clause, PVC_RECURSE_PLACEHOLDERS)); } } - db2Exit(1,"< db2_deparse.c::build_tlist_to_deparse : %x", tlist); + db2Exit1(": %x", tlist); return tlist; } @@ -1324,7 +1321,7 @@ void deparseSelectStmtForRel(StringInfo buf, PlannerInfo* root, RelOptInfo* rel, DB2FdwState* fpinfo = (DB2FdwState*)rel->fdw_private; List* quals = NIL; - db2Entry(1,"> db2_deparse.c::deparseSelectStmtForRel"); + db2Entry1(); //We handle relations for foreign tables, joins between those and upper relations. Assert(IS_JOIN_REL(rel) || IS_SIMPLE_REL(rel) || IS_UPPER_REL(rel)); @@ -1372,7 +1369,7 @@ void deparseSelectStmtForRel(StringInfo buf, PlannerInfo* root, RelOptInfo* rel, // Add any necessary FOR UPDATE/SHARE. deparseLockingClause(&context); - db2Exit(1,"< db2_deparse.c::deparseSelectStmtForRel: %s", buf->data); + db2Exit1(": %s", buf->data); } /* @@ -1394,7 +1391,7 @@ static void deparseSelectSql(List *tlist, bool is_subquery, List **retrieved_att PlannerInfo* root = context->root; DB2FdwState* fpinfo = (DB2FdwState*) foreignrel->fdw_private; - db2Entry(1,"> db2_deparse.c::deparseSelectSql"); + db2Entry1(); // Construct SELECT list appendStringInfoString(buf, "SELECT "); @@ -1416,7 +1413,7 @@ static void deparseSelectSql(List *tlist, bool is_subquery, List **retrieved_att deparseTargetList(buf, rte, foreignrel->relid, rel, false, fpinfo->attrs_used, false, retrieved_attrs); table_close(rel, NoLock); } - db2Exit(1,"< db2_deparse.c::deparseSelectSql : %s", buf->data); + db2Exit1(": %s", buf->data); } /* @@ -1431,7 +1428,7 @@ static void deparseFromExpr(List *quals, deparse_expr_cxt *context) { RelOptInfo* scanrel = context->scanrel; List* additional_conds = NIL; - db2Entry(1,"> db2_deparse.c::deparseFromExpr"); + db2Entry1(); /* For upper relations, scanrel must be either a joinrel or a baserel */ Assert(!IS_UPPER_REL(context->foreignrel) || IS_JOIN_REL(scanrel) || IS_SIMPLE_REL(scanrel)); @@ -1441,7 +1438,7 @@ static void deparseFromExpr(List *quals, deparse_expr_cxt *context) { appendWhereClause(quals, additional_conds, context); if (additional_conds != NIL) list_free_deep(additional_conds); - db2Exit(1,"< db2_deparse.c::deparseFromExpr : %s",buf->data); + db2Exit1(": %s",buf->data); } /* @@ -1463,7 +1460,7 @@ static void deparseFromExpr(List *quals, deparse_expr_cxt *context) { static void deparseFromExprForRel(StringInfo buf, PlannerInfo* root, RelOptInfo* foreignrel, bool use_alias, Index ignore_rel, List** ignore_conds, List** additional_conds, List** params_list) { DB2FdwState* fpinfo = (DB2FdwState*) foreignrel->fdw_private; - db2Entry(1,"> db2_deparse.c::deparseFromExprForRel"); + db2Entry1(); if (IS_JOIN_REL(foreignrel)) { StringInfoData join_sql_o; StringInfoData join_sql_i; @@ -1509,7 +1506,7 @@ static void deparseFromExprForRel(StringInfo buf, PlannerInfo* root, RelOptInfo* Assert(*additional_conds == NIL); *additional_conds = additional_conds_o; } - db2Exit(1,"< db2_deparse.c::deparseFromExprForRel"); + db2Exit1(); return; } } @@ -1555,7 +1552,7 @@ static void deparseFromExprForRel(StringInfo buf, PlannerInfo* root, RelOptInfo* Assert(*additional_conds == NIL); *additional_conds = additional_conds_i; } - db2Exit(1,"< db2_deparse.c::deparseFromExprForRel"); + db2Exit1(); return; } } @@ -1608,7 +1605,7 @@ static void deparseFromExprForRel(StringInfo buf, PlannerInfo* root, RelOptInfo* appendStringInfo(buf, " %s%d", REL_ALIAS_PREFIX, foreignrel->relid); table_close(rel, NoLock); } - db2Exit(1,"< db2_deparse.c::deparseFromExprForRel"); + db2Exit1(); } /* Append FROM clause entry for the given relation into buf. @@ -1617,7 +1614,7 @@ static void deparseFromExprForRel(StringInfo buf, PlannerInfo* root, RelOptInfo* static void deparseRangeTblRef(StringInfo buf, PlannerInfo *root, RelOptInfo *foreignrel, bool make_subquery, Index ignore_rel, List **ignore_conds, List **additional_conds, List **params_list) { DB2FdwState* fpinfo = (DB2FdwState*) foreignrel->fdw_private; - db2Entry(1,"> db2_deparse.c::deparseRangeTblRef"); + db2Entry1(); /* Should only be called in these cases. */ Assert(IS_SIMPLE_REL(foreignrel) || IS_JOIN_REL(foreignrel)); Assert(fpinfo->local_conds == NIL); @@ -1658,7 +1655,7 @@ static void deparseRangeTblRef(StringInfo buf, PlannerInfo *root, RelOptInfo *fo } else { deparseFromExprForRel(buf, root, foreignrel, true, ignore_rel, ignore_conds, additional_conds, params_list); } - db2Exit(1,"< db2_deparse.c::deparseRangeTblRef : %s", buf->data); + db2Exit1(": %s", buf->data); } /** deparseWhereConditions @@ -1674,7 +1671,7 @@ char* deparseWhereConditions (PlannerInfo* root, RelOptInfo * rel) { char* keyword = "WHERE"; StringInfoData where_clause; - db2Entry(1,"> deparseWhereCondition"); + db2Entry1(); initStringInfo (&where_clause); foreach (cell, conditions) { /* check if the condition can be pushed down */ @@ -1690,7 +1687,7 @@ char* deparseWhereConditions (PlannerInfo* root, RelOptInfo * rel) { fdwState->local_conds = lappend (fdwState->local_conds, ((RestrictInfo*) lfirst (cell))->clause); } } - db2Exit(1,"< deparseWhereCondition : %s",where_clause.data); + db2Exit1(": %s",where_clause.data); return where_clause.data; } @@ -1698,7 +1695,7 @@ char* deparseWhereConditions (PlannerInfo* root, RelOptInfo * rel) { void deparseTruncateSql(StringInfo buf, List* rels, DropBehavior behavior, bool restart_seqs) { ListCell* cell = NULL; - db2Entry(1,"> db2_deparse.c::deparseTruncateSql"); + db2Entry1(); appendStringInfoString(buf, "TRUNCATE "); foreach(cell, rels) { Relation rel = lfirst(cell); @@ -1712,7 +1709,7 @@ void deparseTruncateSql(StringInfo buf, List* rels, DropBehavior behavior, bool appendStringInfoString(buf, " RESTRICT"); else if (behavior == DROP_CASCADE) appendStringInfoString(buf, " CASCADE"); - db2Entry(1,"> db2_deparse.c::deparseTruncateSql : %s",buf->data); + db2Exit1(": %s",buf->data); } /* Construct name to use for given column, and emit it into buf. @@ -1721,7 +1718,7 @@ void deparseTruncateSql(StringInfo buf, List* rels, DropBehavior behavior, bool * If qualify_col is true, qualify column name with the alias of relation. */ static void deparseColumnRef(StringInfo buf, int varno, int varattno, RangeTblEntry *rte, bool qualify_col) { - db2Entry(1,"> db2_deparse.c::deparseColumnRef"); + db2Entry1(); /* We support fetching the remote side's CTID and OID. */ if (varattno == SelfItemPointerAttributeNumber) { if (qualify_col) @@ -1805,7 +1802,7 @@ static void deparseColumnRef(StringInfo buf, int varno, int varattno, RangeTblEn appendStringInfoString(buf, quote_identifier(str_toupper (colname, strlen (colname), DEFAULT_COLLATION_OID))); } - db2Exit(1,"< db2_deparse.c::deparseColumnRef : %s", buf->data); + db2Exit1(": %s", buf->data); } /* Append remote name of specified foreign table to buf. @@ -1818,7 +1815,7 @@ static void deparseRelation(StringInfo buf, Relation rel) { const char* relname = NULL; ListCell* lc = NULL; - db2Entry(1,"> db2_deparse.c::deparseRelation"); + db2Entry1(); /* obtain additional catalog information. */ table = GetForeignTable(RelationGetRelid(rel)); @@ -1841,15 +1838,15 @@ static void deparseRelation(StringInfo buf, Relation rel) { relname = RelationGetRelationName(rel); appendStringInfo(buf, "%s.%s", quote_identifier(nspname), quote_identifier(relname)); - db2Debug(5,"relation: %s",buf->data); - db2Exit(1,"< db2_deparse.c::deparseRelation"); + db2Debug5("relation: %s",buf->data); + db2Exit1(); } /* Append a SQL string literal representing "val" to buf. */ void deparseStringLiteral(StringInfo buf, const char* val) { const char* valptr = NULL; - db2Entry(1,"> db2_deparse.c::deparseStringLiteral"); + db2Entry1(); /* Rather than making assumptions about the remote server's value of standard_conforming_strings, always use E'foo' syntax if there are any * backslashes. * This will fail on remote servers before 8.1, but those are long out of support. @@ -1867,8 +1864,8 @@ void deparseStringLiteral(StringInfo buf, const char* val) { appendStringInfoChar(buf, ch); } appendStringInfoChar(buf, '\''); - db2Debug(5,"literal: %s",buf->data); - db2Exit(1,"< db2_deparse.c::deparseStringLiteral"); + db2Debug5("literal: %s",buf->data); + db2Exit1(); } /** deparseExpr @@ -1879,7 +1876,7 @@ void deparseStringLiteral(StringInfo buf, const char* val) { */ char* deparseExpr (PlannerInfo* root, RelOptInfo* rel, Expr* expr, List** params) { char* retValue = NULL; - db2Entry(1,"> db2_deparse.c::deparseExpr"); + db2Entry1(); if (expr != NULL) { deparse_expr_cxt* ctx = db2alloc("deparseExpr.context", sizeof(deparse_expr_cxt)); StringInfoData buf; @@ -1896,15 +1893,15 @@ char* deparseExpr (PlannerInfo* root, RelOptInfo* rel, Expr* expr, List** params db2free(ctx->buf->data); db2free(ctx); } - db2Exit(1,"< db2_deparse.c::deparseExpr: %s", retValue); + db2Exit1(": %s", retValue); return retValue; } static void deparseExprInt (Expr* expr, deparse_expr_cxt* ctx) { - db2Entry(1,"> db2_deparse.c::deparseExprInt"); - db2Debug(2,"expr: %x",expr); + db2Entry1(); + db2Debug2("expr: %x",expr); if (expr != NULL) { - db2Debug(2,"expr->type: %d",expr->type); + db2Debug2("expr->type: %d",expr->type); switch (expr->type) { case T_Const: { deparseConstExpr((Const*)expr, ctx); @@ -1977,16 +1974,16 @@ static void deparseExprInt (Expr* expr, deparse_expr_cxt* break; default: { /* we cannot translate this to DB2 */ - db2Debug(2,"expression cannot be translated to DB2"); + db2Debug2("expression cannot be translated to DB2"); } break; } } - db2Exit(1,"< db2_deparse.c::deparseExpr : %s", ctx->buf->data); + db2Exit1(": %s", ctx->buf->data); } static void deparseConstExpr (Const* expr, deparse_expr_cxt* ctx) { - db2Entry(1,"> db2_deparse.c::deparseConstExpr"); + db2Entry1(); if (expr->constisnull) { /* only translate NULLs of a type DB2 can handle */ if (canHandleType (expr->consttype)) { @@ -1999,17 +1996,17 @@ static void deparseConstExpr (Const* expr, deparse_expr_cxt* appendStringInfo (ctx->buf, "%s", c); } } - db2Exit(1,"< db2_deparse.c::deparseConstExpr"); + db2Exit1(); } static void deparseParamExpr (Param* expr, deparse_expr_cxt* ctx) { ListCell* cell = NULL; char parname[10]; - db2Entry(1,"> db2_deparse.c::deparseParamExpr"); + db2Entry1(); /* don't try to handle interval parameters */ if (!canHandleType (expr->paramtype) || expr->paramtype == INTERVALOID) { - db2Debug(2,"!canHhandleType(expr->paramtype %d) || rxpr->paramtype == INTERVALOID)", expr->paramtype); + db2Debug2("!canHhandleType(expr->paramtype %d) || rxpr->paramtype == INTERVALOID)", expr->paramtype); } else { /* find the index in the parameter list */ int index = 0; @@ -2027,13 +2024,13 @@ static void deparseParamExpr (Param* expr, deparse_expr_cxt* snprintf (parname, 10, ":p%d", index); appendAsType (ctx->buf, expr->paramtype); } - db2Exit(1,"< db2_deparse.c::deparseParamExpr"); + db2Exit1(); } //static void deparseVarExpr (Var* expr, deparse_expr_cxt* ctx) { // const DB2Table* var_table = NULL; /* db2Table that belongs to a Var */ // -// db2Entry(1,"> db2_deparse.c::deparseVarExpr"); +// db2Entry1(); // /* check if the variable belongs to one of our foreign tables */ // if (IS_SIMPLE_REL (ctx->foreignrel)) { // if (expr->varno == ctx->foreignrel->relid && expr->varlevelsup == 0) @@ -2051,13 +2048,13 @@ static void deparseParamExpr (Param* expr, deparse_expr_cxt* // if (var_table) { // /* the variable belongs to a foreign table, replace it with the name */ // /* we cannot handle system columns */ -// db2Debug(2,"varattno: %d",expr->varattno); +// db2Debug2("varattno: %d",expr->varattno); // if (expr->varattno > 0) { // /** Allow boolean columns here. // * They will be rendered as ("COL" <> 0). // */ // if (!(canHandleType (expr->vartype) || expr->vartype == BOOLOID)) { -// db2Debug(2,"!(canHandleType (vartype %d) || vartype == BOOLOID",expr->vartype); +// db2Debug2("!(canHandleType (vartype %d) || vartype == BOOLOID",expr->vartype); // } else { // /* get var_table column index corresponding to this column (-1 if none) */ // int index = var_table->ncols - 1; @@ -2073,9 +2070,9 @@ static void deparseParamExpr (Param* expr, deparse_expr_cxt* // * in PostgreSQL because functions and operators won't work the same. // */ // short db2type = c2dbType(var_table->cols[index]->colType); -// db2Debug(2,"db2type: %d", db2type); +// db2Debug2("db2type: %d", db2type); // if ((expr->vartype == TEXTOID || expr->vartype == BPCHAROID || expr->vartype == VARCHAROID) && db2type != DB2_VARCHAR && db2type != DB2_CHAR) { -// db2Debug(2,"vartype: %d", expr->vartype); +// db2Debug2("vartype: %d", expr->vartype); // } else { // /* work around the lack of booleans in DB2 */ // if (expr->vartype == BOOLOID) { @@ -2092,7 +2089,7 @@ static void deparseParamExpr (Param* expr, deparse_expr_cxt* // } // } // } -// db2Exit(1,"< db2_deparse.c::deparseVarExpr"); +// db2Exit1(); //} /* Deparse given Var node into context->buf. @@ -2109,7 +2106,7 @@ static void deparseVar(Var* expr, deparse_expr_cxt* ctx) { bool qualify_col = (bms_membership(relids) == BMS_MULTIPLE); bool is_query_var = false; - db2Entry(1,"> db2_deparse.c::deparseVar"); + db2Entry1(); /* If the Var belongs to the foreign relation that is deparsed as a subquery, use the relation and column alias to the Var provided by the * subquery, instead of the remote name. */ @@ -2118,8 +2115,8 @@ static void deparseVar(Var* expr, deparse_expr_cxt* ctx) { return; } is_query_var = bms_is_member(expr->varno, ctx->root->all_query_rels); - db2Debug(2,"bms_is_member(%d,%d): %s",expr->varno, ctx->root->all_query_rels, is_query_var ? "true":"false"); - db2Debug(2,"expr->varlevelsup: %d",expr->varlevelsup); + db2Debug2("bms_is_member(%d,%d): %s",expr->varno, ctx->root->all_query_rels, is_query_var ? "true":"false"); + db2Debug2("expr->varlevelsup: %d",expr->varlevelsup); if (is_query_var && expr->varlevelsup == 0) { deparseColumnRef(ctx->buf, expr->varno, expr->varattno, planner_rt_fetch(expr->varno, ctx->root), qualify_col); } else { @@ -2144,7 +2141,7 @@ static void deparseVar(Var* expr, deparse_expr_cxt* ctx) { printRemotePlaceholder(expr->vartype, expr->vartypmod, ctx); } } - db2Exit(1,"< db2_deparse.c::deparseVar : %s",ctx->buf->data); + db2Exit1(": %s",ctx->buf->data); } static void deparseOpExpr (OpExpr* expr, deparse_expr_cxt* ctx) { @@ -2155,7 +2152,7 @@ static void deparseOpExpr (OpExpr* expr, deparse_expr_cxt* Oid schema = 0; HeapTuple tuple ; - db2Entry(1,"> db2_deparse.c::deparseOpExpr"); + db2Entry1(); /* get operator name, kind, argument type and schema */ tuple = SearchSysCache1 (OPEROID, ObjectIdGetDatum (expr->opno)); if (!HeapTupleIsValid (tuple)) { @@ -2169,16 +2166,16 @@ static void deparseOpExpr (OpExpr* expr, deparse_expr_cxt* ReleaseSysCache (tuple); /* ignore operators in other than the pg_catalog schema */ if (schema != PG_CATALOG_NAMESPACE) { - db2Debug(2,"schema != PG_CATALOG_NAMESPACE"); + db2Debug2("schema != PG_CATALOG_NAMESPACE"); } else { if (!canHandleType (rightargtype)) { - db2Debug(2,"!canHandleType rightargtype(%d)", rightargtype); + db2Debug2("!canHandleType rightargtype(%d)", rightargtype); } else { /** Don't translate operations on two intervals. * INTERVAL YEAR TO MONTH and INTERVAL DAY TO SECOND don't mix well. */ if (leftargtype == INTERVALOID && rightargtype == INTERVALOID) { - db2Debug(2,"leftargtype == INTERVALOID && rightargtype == INTERVALOID"); + db2Debug2("leftargtype == INTERVALOID && rightargtype == INTERVALOID"); } else { /* the operators that we can translate */ if ((strcmp (opername, ">") == 0 && rightargtype != TEXTOID && rightargtype != BPCHAROID && rightargtype != NAMEOID && rightargtype != CHAROID) @@ -2193,14 +2190,14 @@ static void deparseOpExpr (OpExpr* expr, deparse_expr_cxt* char* left = NULL; left = deparseExpr (ctx->root, ctx->foreignrel, linitial(expr->args), ctx->params_list); - db2Debug(2,"left: %s", left); + db2Debug2("left: %s", left); if (left != NULL) { if (oprkind == 'b') { /* binary operator */ char* right = NULL; right = deparseExpr (ctx->root, ctx->foreignrel, lsecond(expr->args), ctx->params_list); - db2Debug(2,"right: %s", right); + db2Debug2("right: %s", right); if (right != NULL) { if (strcmp (opername, "~~") == 0) { appendStringInfo (ctx->buf, "(%s LIKE %s ESCAPE '\\')", left, right); @@ -2237,13 +2234,13 @@ static void deparseOpExpr (OpExpr* expr, deparse_expr_cxt* } } else { /* cannot translate this operator */ - db2Debug(2,"cannot translate this opername: %s", opername); + db2Debug2("cannot translate this opername: %s", opername); } } } } db2free (opername); - db2Exit(1,"< db2_deparse.c::deparseOpExpr"); + db2Exit1(); } static void deparseScalarArrayOpExpr (ScalarArrayOpExpr* expr, deparse_expr_cxt* ctx) { @@ -2252,7 +2249,7 @@ static void deparseScalarArrayOpExpr (ScalarArrayOpExpr* expr, deparse_expr_cxt* Oid schema; HeapTuple tuple; - db2Entry(1,"> db2_deparse.c::deparseScalarArrayOpExpr"); + db2Entry1(); tuple = SearchSysCache1 (OPEROID, ObjectIdGetDatum (expr->opno)); if (!HeapTupleIsValid (tuple)) { elog (ERROR, "cache lookup failed for operator %u", expr->opno); @@ -2269,14 +2266,14 @@ static void deparseScalarArrayOpExpr (ScalarArrayOpExpr* expr, deparse_expr_cxt* ReleaseSysCache (tuple); /* ignore operators in other than the pg_catalog schema */ if (schema != PG_CATALOG_NAMESPACE) { - db2Debug(2,"schema != PG_CATALOG_NAMESPACE"); + db2Debug2("schema != PG_CATALOG_NAMESPACE"); } else { /* don't try to push down anything but IN and NOT IN expressions */ if ((strcmp (opername, "=") != 0 || !expr->useOr) && (strcmp (opername, "<>") != 0 || expr->useOr)) { - db2Debug(2,"don't try to push down anything but IN and NOT IN expressions"); + db2Debug2("don't try to push down anything but IN and NOT IN expressions"); } else { if (!canHandleType (leftargtype)) { - db2Debug(2,"cannot Handle Type leftargtype (%d)", leftargtype); + db2Debug2("cannot Handle Type leftargtype (%d)", leftargtype); } else { char* left = NULL; char* right = NULL; @@ -2312,7 +2309,7 @@ static void deparseScalarArrayOpExpr (ScalarArrayOpExpr* expr, deparse_expr_cxt* c = "NULL"; } else { c = datumToString (datum, leftargtype); - db2Debug(2,"c: %s",c); + db2Debug2("c: %s",c); if (c == NULL) { array_free_iterator (iterator); bResult = false; @@ -2324,7 +2321,7 @@ static void deparseScalarArrayOpExpr (ScalarArrayOpExpr* expr, deparse_expr_cxt* first_arg = false; } array_free_iterator (iterator); - db2Debug(2,"first_arg: %s", first_arg ? "true":"false"); + db2Debug2("first_arg: %s", first_arg ? "true":"false"); if (first_arg) { // don't push down empty arrays // since the semantics for NOT x = ANY() differ @@ -2342,7 +2339,7 @@ static void deparseScalarArrayOpExpr (ScalarArrayOpExpr* expr, deparse_expr_cxt* ArrayCoerceExpr* arraycoerce = (ArrayCoerceExpr *) rightexpr; /* if the conversion requires more than binary coercion, don't push it down */ if (arraycoerce->elemexpr && arraycoerce->elemexpr->type != T_RelabelType) { - db2Debug(2,"arraycoerce->elemexpr && arraycoerce->elemexpr->type != T_RelabelType"); + db2Debug2("arraycoerce->elemexpr && arraycoerce->elemexpr->type != T_RelabelType"); bResult = false; break; } @@ -2371,7 +2368,7 @@ static void deparseScalarArrayOpExpr (ScalarArrayOpExpr* expr, deparse_expr_cxt* appendStringInfo(&buf,"%s%s",(first_arg) ? "": ", ",element); first_arg = false; } - db2Debug(2,"first_arg: %s", first_arg ? "true" : "false"); + db2Debug2("first_arg: %s", first_arg ? "true" : "false"); if (first_arg) { /* don't push down empty arrays, since the semantics for NOT x = ANY() differ */ db2free(buf.data); @@ -2383,7 +2380,7 @@ static void deparseScalarArrayOpExpr (ScalarArrayOpExpr* expr, deparse_expr_cxt* } break; default: { - db2Debug(2,"rightexpr->type(%d) default ",rightexpr->type); + db2Debug2("rightexpr->type(%d) default ",rightexpr->type); bResult = false; } break; @@ -2398,14 +2395,14 @@ static void deparseScalarArrayOpExpr (ScalarArrayOpExpr* expr, deparse_expr_cxt* } } } - db2Exit(1,"< db2_deparse.c::deparseScalarArrayOpExpr"); + db2Exit1(); } static void deparseDistinctExpr (DistinctExpr* expr, deparse_expr_cxt* ctx) { Oid rightargtype = 0; HeapTuple tuple; - db2Entry(1,"> db2_deparse.c::deparseDistinctExpr"); + db2Entry1(); tuple = SearchSysCache1 (OPEROID, ObjectIdGetDatum ((expr)->opno)); if (!HeapTupleIsValid (tuple)) { elog (ERROR, "cache lookup failed for operator %u", (expr)->opno); @@ -2413,7 +2410,7 @@ static void deparseDistinctExpr (DistinctExpr* expr, deparse_expr_cxt* rightargtype = ((Form_pg_operator) GETSTRUCT (tuple))->oprright; ReleaseSysCache (tuple); if (!canHandleType (rightargtype)) { - db2Debug(2,"cannot Handle Type rightargtype (%d)",rightargtype); + db2Debug2("cannot Handle Type rightargtype (%d)",rightargtype); } else { char* left = NULL; @@ -2429,7 +2426,7 @@ static void deparseDistinctExpr (DistinctExpr* expr, deparse_expr_cxt* } db2free(left); } - db2Exit(1,"< db2_deparse.c::deparseDistinctExpr"); + db2Exit1(); } /** Deparse IS [NOT] NULL expression. @@ -2437,7 +2434,7 @@ static void deparseDistinctExpr (DistinctExpr* expr, deparse_expr_cxt* static void deparseNullTest (NullTest* expr, deparse_expr_cxt* ctx) { StringInfo buf = ctx->buf; - db2Entry(1,"> db2_deparse.c::deparseNullTest"); + db2Entry1(); appendStringInfoChar(buf, '('); deparseExprInt (expr->arg, ctx); @@ -2457,14 +2454,14 @@ static void deparseNullTest (NullTest* expr, deparse_expr_cxt* else appendStringInfoString(buf, " IS DISTINCT FROM NULL)"); } - db2Exit(1,"< db2_deparse.c::deparseNullTest : %s", buf->data); + db2Exit1(": %s", buf->data); } static void deparseNullIfExpr (NullIfExpr* expr, deparse_expr_cxt* ctx) { Oid rightargtype = 0; HeapTuple tuple; - db2Entry(1,"> db2_deparse.c::deparseNullIfExpr"); + db2Entry1(); tuple = SearchSysCache1 (OPEROID, ObjectIdGetDatum ((expr)->opno)); if (!HeapTupleIsValid (tuple)) { elog (ERROR, "cache lookup failed for operator %u", (expr)->opno); @@ -2472,7 +2469,7 @@ static void deparseNullIfExpr (NullIfExpr* expr, deparse_expr_cxt* rightargtype = ((Form_pg_operator) GETSTRUCT (tuple))->oprright; ReleaseSysCache (tuple); if (!canHandleType (rightargtype)) { - db2Debug(2,"cannot Handle Type rightargtype (%d)",rightargtype); + db2Debug2("cannot Handle Type rightargtype (%d)",rightargtype); } else { char* left = NULL; left = deparseExpr (ctx->root, ctx->foreignrel, linitial((expr)->args), ctx->params_list); @@ -2488,7 +2485,7 @@ static void deparseNullIfExpr (NullIfExpr* expr, deparse_expr_cxt* } db2free(left); } - db2Exit(1,"< db2_deparse.c::deparseNullIfExpr: %s", ctx->buf->data); + db2Exit1(": %s", ctx->buf->data); } static void deparseBoolExpr (BoolExpr* expr, deparse_expr_cxt* ctx) { @@ -2496,7 +2493,7 @@ static void deparseBoolExpr (BoolExpr* expr, deparse_expr_cxt* char* arg = NULL; StringInfoData buf; - db2Entry(1,"> db2_deparse.c::deparseBoolExpr"); + db2Entry1(); initStringInfo(&buf); arg = deparseExpr (ctx->root, ctx->foreignrel, linitial(expr->args), ctx->params_list); if (arg != NULL) { @@ -2518,13 +2515,13 @@ static void deparseBoolExpr (BoolExpr* expr, deparse_expr_cxt* } db2free(buf.data); db2free(arg); - db2Exit(1,"< db2_deparse.c::deparseBoolExpr: %s", ctx->buf->data); + db2Exit1(": %s", ctx->buf->data); } static void deparseCaseExpr (CaseExpr* expr, deparse_expr_cxt* ctx) { - db2Entry(1,"> db2_deparse.c::deparseCaseExpr"); + db2Entry1(); if (!canHandleType (expr->casetype)) { - db2Debug(2,"cannot Handle Type caseexpr->casetype (%d)", expr->casetype); + db2Debug2("cannot Handle Type caseexpr->casetype (%d)", expr->casetype); } else { StringInfoData buf; bool bBreak = false; @@ -2537,7 +2534,7 @@ static void deparseCaseExpr (CaseExpr* expr, deparse_expr_cxt* if (expr->arg != NULL) { /* for the form "CASE arg WHEN ...", add first expression */ arg = deparseExpr (ctx->root, ctx->foreignrel, expr->arg, ctx->params_list); - db2Debug(2,"CASE %s WHEN ...", arg); + db2Debug2("CASE %s WHEN ...", arg); if (arg == NULL) { appendStringInfo (&buf, " %s", arg); } else { @@ -2556,7 +2553,7 @@ static void deparseCaseExpr (CaseExpr* expr, deparse_expr_cxt* /* for CASE arg WHEN ..., use only the right branch of the equality */ arg = deparseExpr (ctx->root, ctx->foreignrel, lsecond (((OpExpr*) whenclause->expr)->args), ctx->params_list); } - db2Debug(2,"WHEN %s ", arg); + db2Debug2("WHEN %s ", arg); if (arg != NULL) { appendStringInfo (&buf, " WHEN %s", arg); } else { @@ -2564,7 +2561,7 @@ static void deparseCaseExpr (CaseExpr* expr, deparse_expr_cxt* break; } /* THEN */ arg = deparseExpr (ctx->root, ctx->foreignrel, whenclause->result, ctx->params_list); - db2Debug(2," THEN %s ", arg); + db2Debug2(" THEN %s ", arg); if (arg != NULL) { appendStringInfo (&buf, " THEN %s", arg); } else { @@ -2576,7 +2573,7 @@ static void deparseCaseExpr (CaseExpr* expr, deparse_expr_cxt* /* append ELSE clause if appropriate */ if (expr->defresult != NULL) { arg = deparseExpr (ctx->root, ctx->foreignrel, expr->defresult, ctx->params_list); - db2Debug(2," ELSE %s", arg); + db2Debug2(" ELSE %s", arg); if (arg != NULL) { appendStringInfo (&buf, " ELSE %s", arg); } else { @@ -2592,13 +2589,13 @@ static void deparseCaseExpr (CaseExpr* expr, deparse_expr_cxt* } db2free(buf.data); } - db2Exit(1,"< db2_deparse.c::deparseCaseExpr: %s", ctx->buf->data); + db2Exit1(": %s", ctx->buf->data); } static void deparseCoalesceExpr (CoalesceExpr* expr, deparse_expr_cxt* ctx) { - db2Entry(1,"> db2_deparse.c::deparseCoalesceExpr"); + db2Entry1(); if (!canHandleType (expr->coalescetype)) { - db2Debug(2,"cannot Handle Type coalesceexpr->coalescetype (%d)", expr->coalescetype); + db2Debug2("cannot Handle Type coalesceexpr->coalescetype (%d)", expr->coalescetype); } else { StringInfoData result; char* arg = NULL; @@ -2609,7 +2606,7 @@ static void deparseCoalesceExpr (CoalesceExpr* expr, deparse_expr_cxt* appendStringInfo (&result, "COALESCE("); foreach (cell, expr->args) { arg = deparseExpr (ctx->root, ctx->foreignrel, (Expr*)lfirst(cell),ctx->params_list); - db2Debug(2,"arg: %s", arg); + db2Debug2("arg: %s", arg); if (arg != NULL) { appendStringInfo(&result, ((first_arg) ? "%s" : ", %s"), arg); first_arg = false; @@ -2622,16 +2619,16 @@ static void deparseCoalesceExpr (CoalesceExpr* expr, deparse_expr_cxt* } db2free(result.data); } - db2Exit(1,"< db2_deparse.c::deparseCoalesceExpr: %s", ctx->buf->data); + db2Exit1(": %s", ctx->buf->data); } static void deparseFuncExpr (FuncExpr* expr, deparse_expr_cxt* ctx) { - db2Entry(1,"> db2_deparse.c::deparseFuncExpr"); + db2Entry1(); if (!canHandleType (expr->funcresulttype)) { - db2Debug(2,"cannot handle funct->funcresulttype: %d",expr->funcresulttype); + db2Debug2("cannot handle funct->funcresulttype: %d",expr->funcresulttype); } else if (expr->funcformat == COERCE_IMPLICIT_CAST) { /* do nothing for implicit casts */ - db2Debug(2,"COERCE_IMPLICIT_CAST == expr->funcformat(%d)",expr->funcformat); + db2Debug2("COERCE_IMPLICIT_CAST == expr->funcformat(%d)",expr->funcformat); deparseExprInt (linitial(expr->args), ctx); } else { Oid schema; @@ -2644,13 +2641,13 @@ static void deparseFuncExpr (FuncExpr* expr, deparse_expr_cxt* elog (ERROR, "cache lookup failed for function %u", expr->funcid); } opername = db2strdup (((Form_pg_proc) GETSTRUCT (tuple))->proname.data); - db2Debug(2,"opername: %s",opername); + db2Debug2("opername: %s",opername); schema = ((Form_pg_proc) GETSTRUCT (tuple))->pronamespace; - db2Debug(2,"schema: %d",schema); + db2Debug2("schema: %d",schema); ReleaseSysCache (tuple); /* ignore functions in other than the pg_catalog schema */ if (schema != PG_CATALOG_NAMESPACE) { - db2Debug(2,"T_FuncExpr: schema(%d) != PG_CATALOG_NAMESPACE", schema); + db2Debug2("T_FuncExpr: schema(%d) != PG_CATALOG_NAMESPACE", schema); } else { /* the "normal" functions that we can translate */ if (strcmp (opername, "abs") == 0 || strcmp (opername, "acos") == 0 || strcmp (opername, "asin") == 0 @@ -2696,7 +2693,7 @@ static void deparseFuncExpr (FuncExpr* expr, deparse_expr_cxt* db2free(arg); } else { ok = false; - db2Debug(2,"T_FuncExpr: function %s that we cannot render for DB2", opername); + db2Debug2("T_FuncExpr: function %s that we cannot render for DB2", opername); break; } } @@ -2712,7 +2709,7 @@ static void deparseFuncExpr (FuncExpr* expr, deparse_expr_cxt* /* special case: EXTRACT */ left = deparseExpr (ctx->root, ctx->foreignrel, linitial (expr->args), ctx->params_list); if (left == NULL) { - db2Debug(2,"T_FuncExpr: function %s that we cannot render for DB2", opername); + db2Debug2("T_FuncExpr: function %s that we cannot render for DB2", opername); } else { /* can only handle these fields in DB2 */ if (strcmp (left, "'year'") == 0 || strcmp (left, "'month'") == 0 @@ -2725,13 +2722,13 @@ static void deparseFuncExpr (FuncExpr* expr, deparse_expr_cxt* left[strlen (left) - 1] = '\0'; right = deparseExpr (ctx->root, ctx->foreignrel, lsecond (expr->args), ctx->params_list); if (right == NULL) { - db2Debug(2,"T_FuncExpr: function %s that we cannot render for DB2", opername); + db2Debug2("T_FuncExpr: function %s that we cannot render for DB2", opername); } else { appendStringInfo (ctx->buf, "EXTRACT(%s FROM %s)", left + 1, right); } db2free(right); } else { - db2Debug(2,"T_FuncExpr: function %s that we cannot render for DB2", opername); + db2Debug2("T_FuncExpr: function %s that we cannot render for DB2", opername); } } db2free (left); @@ -2740,28 +2737,28 @@ static void deparseFuncExpr (FuncExpr* expr, deparse_expr_cxt* appendStringInfo (ctx->buf, "(CAST (?/*:now*/ AS TIMESTAMP))"); } else { /* function that we cannot render for DB2 */ - db2Debug(2,"T_FuncExpr: function %s that we cannot render for DB2", opername); + db2Debug2("T_FuncExpr: function %s that we cannot render for DB2", opername); } } db2free (opername); } - db2Exit(1,"< db2_deparse.c::deparseFuncExpr: %s", ctx->buf->data); + db2Exit1(": %s", ctx->buf->data); } static void deparseCoerceViaIOExpr (CoerceViaIO* expr, deparse_expr_cxt* ctx) { - db2Entry(1,"> db2_deparse.c::deparseCoerceViaIOExpr"); + db2Entry1(); /* We will only handle casts of 'now'. */ /* only casts to these types are handled */ if (expr->resulttype != DATEOID && expr->resulttype != TIMESTAMPOID && expr->resulttype != TIMESTAMPTZOID) { - db2Debug(2,"only casts to DATEOID, TIMESTAMPOID and TIMESTAMPTZOID are handled"); + db2Debug2("only casts to DATEOID, TIMESTAMPOID and TIMESTAMPTZOID are handled"); } else if (expr->arg->type != T_Const) { /* the argument must be a Const */ - db2Debug(2,"T_CoerceViaIO: the argument must be a Const"); + db2Debug2("T_CoerceViaIO: the argument must be a Const"); } else { Const* constant = (Const *) expr->arg; if (constant->constisnull || (constant->consttype != CSTRINGOID && constant->consttype != TEXTOID)) { /* the argument must be a not-NULL text constant */ - db2Debug(2,"T_CoerceViaIO: the argument must be a not-NULL text constant"); + db2Debug2("T_CoerceViaIO: the argument must be a not-NULL text constant"); } else { /* get the type's output function */ HeapTuple tuple = SearchSysCache1 (TYPEOID, ObjectIdGetDatum (constant->consttype)); @@ -2773,7 +2770,7 @@ static void deparseCoerceViaIOExpr (CoerceViaIO* expr, deparse_expr_cxt* ReleaseSysCache (tuple); /* the value must be "now" */ if (strcmp (DatumGetCString (OidFunctionCall1 (typoutput, constant->constvalue)), "now") != 0) { - db2Debug(2,"value must be 'now'"); + db2Debug2("value must be 'now'"); } else { switch (expr->resulttype) { case DATEOID: @@ -2795,11 +2792,11 @@ static void deparseCoerceViaIOExpr (CoerceViaIO* expr, deparse_expr_cxt* } } } - db2Exit(1,"< db2_deparse.c::deparseCoerceViaIOExpr: %s", ctx->buf->data); + db2Exit1(": %s", ctx->buf->data); } static void deparseSQLValueFuncExpr (SQLValueFunction* expr, deparse_expr_cxt* ctx) { - db2Entry(1,"> db2_deparse.c::deparseSQLValueFuncExpr"); + db2Entry1(); switch (expr->op) { case SVFOP_CURRENT_DATE: appendStringInfo(ctx->buf, "TRUNC(CAST (CAST(?/*:now*/ AS TIMESTAMP) AS DATE))"); @@ -2818,16 +2815,16 @@ static void deparseSQLValueFuncExpr (SQLValueFunction* expr, deparse_expr_cxt* break; default: /* don't push down other functions */ - db2Debug(2,"op %d cannot be translated to DB2", expr->op); + db2Debug2("op %d cannot be translated to DB2", expr->op); break; } - db2Exit(1,"< db2_deparse.c::deparseSQLValueFuncExpr: %s", ctx->buf->data); + db2Exit1(": %s", ctx->buf->data); } static void deparseAggref (Aggref* expr, deparse_expr_cxt* ctx) { - db2Entry(1,"> db2_deparse.c::deparseAggref"); + db2Entry1(); if (expr == NULL) { - db2Debug(2,"expr is NULL"); + db2Debug2("expr is NULL"); } else { /* Resolve aggregate function name (OID -> pg_proc.proname). */ HeapTuple tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(expr->aggfnoid)); @@ -2849,12 +2846,12 @@ static void deparseAggref (Aggref* expr, deparse_expr_cxt* } ReleaseSysCache(tuple); } - db2Debug(2,"aggref->aggfnoid=%u name=%s%s%s", expr->aggfnoid, nspname ? nspname : "", nspname ? "." : "", aggname ? aggname : ""); + db2Debug2("aggref->aggfnoid=%u name=%s%s%s", expr->aggfnoid, nspname ? nspname : "", nspname ? "." : "", aggname ? aggname : ""); /* We only support deparsing simple, standard aggregates for now. * (This can be expanded to ordered-set / FILTER / WITHIN GROUP later.) */ if (expr->aggorder != NIL) { - db2Debug(2,"aggregate ORDER BY not supported for pushdown"); + db2Debug2("aggregate ORDER BY not supported for pushdown"); } else if (aggname != NULL) { const char* db2func = NULL; bool distinct = (expr->aggdistinct != NIL); @@ -2867,7 +2864,7 @@ static void deparseAggref (Aggref* expr, deparse_expr_cxt* else if (strcmp(aggname, "max") == 0) db2func = "MAX"; else { /* Unknown aggregate name: we can still report it (above), but don't emit SQL. */ - db2Debug(2,"aggregate '%s' not supported for DB2 deparse", aggname); + db2Debug2("aggregate '%s' not supported for DB2 deparse", aggname); } if (db2func != NULL) { StringInfoData result; @@ -2919,14 +2916,14 @@ static void deparseAggref (Aggref* expr, deparse_expr_cxt* if (ok) { appendStringInfo(ctx->buf, "%s)",result.data); } else { - db2Debug(2,"parsed aggref so far: %s", result.data); - db2Debug(2,"could not deparse aggregate args"); + db2Debug2("parsed aggref so far: %s", result.data); + db2Debug2("could not deparse aggregate args"); } db2free(result.data); } } } - db2Exit(1,"< db2_deparse.c::deparseAggref: %s", ctx->buf->data); + db2Exit1(": %s", ctx->buf->data); } /** datumToString @@ -2939,7 +2936,7 @@ static char* datumToString (Datum datum, Oid type) { HeapTuple tuple; char* str; char* p; - db2Entry(1,"> db2_deparse.c::datumToString"); + db2Entry1(); /* get the type's output function */ tuple = SearchSysCache1 (TYPEOID, ObjectIdGetDatum (type)); if (!HeapTupleIsValid (tuple)) { @@ -3019,7 +3016,7 @@ static char* datumToString (Datum datum, Oid type) { default: return NULL; } - db2Exit(1,"< db2_deparse.c::datumToString - returns: '%s'", result.data); + db2Exit1(": %s", result.data); return result.data; } @@ -3029,7 +3026,7 @@ static char* datumToString (Datum datum, Oid type) { char* deparseDate (Datum datum) { struct pg_tm datetime_tm; StringInfoData s; - db2Entry(1,"> db2_deparse.c::deparseDate"); + db2Entry1(); if (DATE_NOT_FINITE (DatumGetDateADT (datum))) ereport (ERROR, (errcode (ERRCODE_FDW_INVALID_ATTRIBUTE_VALUE), errmsg ("infinite date value cannot be stored in DB2"))); @@ -3041,7 +3038,7 @@ char* deparseDate (Datum datum) { initStringInfo (&s); appendStringInfo (&s, "%04d-%02d-%02d 00:00:00", datetime_tm.tm_year > 0 ? datetime_tm.tm_year : -datetime_tm.tm_year + 1, datetime_tm.tm_mon, datetime_tm.tm_mday); - db2Exit(1,"< db2_deparse.c::deparseDate - returns: '%s'", s.data); + db2Exit1(": %s", s.data); return s.data; } @@ -3053,7 +3050,7 @@ char* deparseTimestamp (Datum datum, bool hasTimezone) { int32 tzoffset; fsec_t datetime_fsec; StringInfoData s; - db2Entry(1,"> db2_deparse.c::deparseTimestamp"); + db2Entry1(); /* this is sloppy, but DatumGetTimestampTz and DatumGetTimestamp are the same */ if (TIMESTAMP_NOT_FINITE (DatumGetTimestampTz (datum))) ereport (ERROR, (errcode (ERRCODE_FDW_INVALID_ATTRIBUTE_VALUE), errmsg ("infinite timestamp value cannot be stored in DB2"))); @@ -3077,7 +3074,7 @@ char* deparseTimestamp (Datum datum, bool hasTimezone) { datetime_tm.tm_year > 0 ? datetime_tm.tm_year : -datetime_tm.tm_year + 1, datetime_tm.tm_mon, datetime_tm.tm_mday, datetime_tm.tm_hour, datetime_tm.tm_min, datetime_tm.tm_sec, (int32) datetime_fsec); - db2Exit(1,"< db2_deparse.c::deparseTimestamp - returns: '%s'", s.data); + db2Exit1(": '%s'", s.data); return s.data; } @@ -3099,7 +3096,7 @@ static void deparseConst(Const *node, deparse_expr_cxt *context, int showtype) { bool isstring = false; bool needlabel; - db2Entry(1,"> db2_deparse.c::deparseConst"); + db2Entry1(); if (node->constisnull) { appendStringInfoString(buf, "NULL"); if (showtype >= 0) @@ -3150,7 +3147,7 @@ static void deparseConst(Const *node, deparse_expr_cxt *context, int showtype) { } pfree(extval); if (showtype == -1) { - db2Exit(1,"< db2_deparse.c::deparseConst"); + db2Exit1(); return; /* never print type label */ } /* For showtype == 0, append ::typename unless the constant will be implicitly typed as the right type when it is read in. @@ -3176,7 +3173,7 @@ static void deparseConst(Const *node, deparse_expr_cxt *context, int showtype) { } if (needlabel || showtype > 0) appendStringInfo(buf, "::%s", deparse_type_name(node->consttype, node->consttypmod)); - db2Exit(1,"< db2_deparse.c::deparseConst"); + db2Exit1(); } /** Print the name of an operator. @@ -3184,7 +3181,7 @@ static void deparseConst(Const *node, deparse_expr_cxt *context, int showtype) { static void deparseOperatorName(StringInfo buf, Form_pg_operator opform) { char* opname = NULL; - db2Entry(1,"> db2_deparse.c::deparseOperatorName"); + db2Entry1(); /* opname is not a SQL identifier, so we should not quote it. */ opname = NameStr(opform->oprname); @@ -3199,7 +3196,7 @@ static void deparseOperatorName(StringInfo buf, Form_pg_operator opform) { /* Just print operator name. */ appendStringInfoString(buf, opname); } - db2Exit(1,"< db2_deparse.c::deparseOperatorName"); + db2Exit1(); } /** deparsedeparseInterval @@ -3216,7 +3213,7 @@ static char* deparseInterval (Datum datum) { char* sign; int idx = 0; - db2Entry(1,"> db2_deparse.c::deparseInterval"); + db2Entry1(); #if PG_VERSION_NUM >= 150000 interval2itm (*DatumGetIntervalP (datum), &tm); #else @@ -3282,7 +3279,7 @@ static char* deparseInterval (Datum datum) { // #else // appendStringInfo (&s, "INTERVAL '%s%d %02d:%02d:%02d.%06d' DAY(9) TO SECOND(6)", sign, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, fsec); // #endif - db2Exit(1,"< db2_deparse.c::deparseInterval - returns: '%s'",s.data); + db2Exit1(": '%s'",s.data); return s.data; } @@ -3295,7 +3292,7 @@ static void deparseLockingClause(deparse_expr_cxt *context) { DB2FdwState* fpinfo = (DB2FdwState*) rel->fdw_private; int relid = -1; - db2Entry(1,"> db2_deparse.c::deparseLockingClause"); + db2Entry1(); while ((relid = bms_next_member(rel->relids, relid)) >= 0) { /* Ignore relation if it appears in a lower subquery. Locking clause for such a relation is included in the subquery if necessary. */ if (bms_is_member(relid, fpinfo->lower_subquery_rels)) @@ -3345,14 +3342,14 @@ static void deparseLockingClause(deparse_expr_cxt *context) { } } } - db2Exit(1,"< db2_deparse.c::deparseLockingClause"); + db2Exit1(); } /** Output join name for given join type */ char* get_jointype_name (JoinType jointype) { char* type = NULL; - db2Entry(1,"> get_jointype_name"); + db2Entry1(); switch (jointype) { case JOIN_INNER: type = "INNER"; @@ -3371,8 +3368,7 @@ char* get_jointype_name (JoinType jointype) { elog (ERROR, "unsupported join type %d", jointype); break; } - db2Debug(2,"type: '%s'",type); - db2Exit(1,"< get_jointype_name"); + db2Exit1(": %s", type); return type; } @@ -3387,7 +3383,7 @@ static void deparseTargetList(StringInfo buf, RangeTblEntry *rte, Index rtindex, bool first; int i; - db2Entry(1,"> db2_deparse.c::deparseTargetList"); + db2Entry1(); *retrieved_attrs = NIL; /* If there's a whole-row reference, we'll need all the columns. */ @@ -3426,7 +3422,7 @@ static void deparseTargetList(StringInfo buf, RangeTblEntry *rte, Index rtindex, /* Don't generate bad syntax if no undropped columns */ if (first && !is_returning) appendStringInfoString(buf, "NULL"); - db2Exit(1,"< db2_deparse.c::deparseTargetList : %s", buf->data); + db2Exit1(": %s", buf->data); } /** Deparse given targetlist and append it to context->buf. @@ -3442,7 +3438,7 @@ static void deparseExplicitTargetList(List* tlist, bool is_returning, List** ret StringInfo buf = context->buf; int i = 0; - db2Entry(1,"> db2_deparse.c::deparseExplicitTargetList"); + db2Entry1(); *retrieved_attrs = NIL; foreach(lc, tlist) { @@ -3461,7 +3457,7 @@ static void deparseExplicitTargetList(List* tlist, bool is_returning, List** ret if (i == 0 && !is_returning) appendStringInfoString(buf, "NULL"); - db2Exit(1,"< db2_deparse.c::deparseExplicitTargetList : %s", buf->data); + db2Exit1(": %s", buf->data); } /* Emit expressions specified in the given relation's reltarget. @@ -3472,7 +3468,7 @@ static void deparseSubqueryTargetList(deparse_expr_cxt* context) { bool first = true; ListCell* lc; - db2Entry(1,"> db2_deparse.c::deparseSubqueryTargetList"); + db2Entry1(); /* Should only be called in these cases. */ Assert(IS_SIMPLE_REL(context->foreignrel) || IS_JOIN_REL(context->foreignrel)); foreach(lc, context->foreignrel->reltarget->exprs) { @@ -3485,7 +3481,7 @@ static void deparseSubqueryTargetList(deparse_expr_cxt* context) { /* Don't generate bad syntax if no expressions */ if (first) appendStringInfoString(context->buf, "NULL"); - db2Exit(1,"< db2_deparse.c::deparseSubqueryTargetList : %s", context->buf->data); + db2Exit1(": %s", context->buf->data); } /* Given an EquivalenceClass and a foreign relation, find an EC member that can be used to sort the relation remotely according to a pathkey using this EC. @@ -3500,7 +3496,7 @@ EquivalenceMember* find_em_for_rel(PlannerInfo* root, EquivalenceClass* ec, RelO EquivalenceMemberIterator it; EquivalenceMember* em = NULL; - db2Entry(1,"> db2_deparse.c::find_em_for_rel"); + db2Entry1(); setup_eclass_member_iterator(&it, ec, rel->relids); while ((em = eclass_member_iterator_next(&it)) != NULL) { /* Note we require !bms_is_empty, else we'd accept constant expressions which are not suitable for the purpose. */ @@ -3510,7 +3506,7 @@ EquivalenceMember* find_em_for_rel(PlannerInfo* root, EquivalenceClass* ec, RelO && is_foreign_expr(root, rel, em->em_expr)) break; } - db2Exit(1,"< db2_deparse.c::find_em_for_rel : %x",em); + db2Exit1(": %x",em); return em; } @@ -3526,7 +3522,7 @@ EquivalenceMember* find_em_for_rel_target(PlannerInfo* root, EquivalenceClass* e ListCell* lc1; int i = 0; - db2Entry(1,"> db2_deparse.c::find_em_for_rel_target"); + db2Entry1(); foreach(lc1, target->exprs) { Expr* expr = (Expr *) lfirst(lc1); Index sgref = get_pathtarget_sortgroupref(target, i); @@ -3566,13 +3562,13 @@ EquivalenceMember* find_em_for_rel_target(PlannerInfo* root, EquivalenceClass* e /* Check that expression (including relabels!) is shippable */ if (is_foreign_expr(root, rel, em->em_expr)) { - db2Exit(1,"< db2_deparse.c::find_em_for_rel_target : %x", em); + db2Exit1(": %x", em); return em; } } i++; } - db2Exit(1,"< db2_deparse.c::find_em_for_rel_target : %x", NULL); + db2Exit1(": %x", NULL); return NULL; } @@ -3597,7 +3593,7 @@ void deparseDirectUpdateSql(StringInfo buf, PlannerInfo *root, Index rtindex, Re ListCell* lc2 = NULL; List* additional_conds = NIL; - db2Entry(1,"> db2_deparse.c::deparseDirectUpdateSql"); + db2Entry1(); /* Set up context struct for recursion */ context.root = root; context.foreignrel = foreignrel; @@ -3650,7 +3646,7 @@ void deparseDirectUpdateSql(StringInfo buf, PlannerInfo *root, Index rtindex, Re deparseExplicitTargetList(returningList, true, retrieved_attrs, &context); else deparseReturningList(buf, rte, rtindex, rel, false, NIL, returningList, retrieved_attrs); - db2Exit(1,"< db2_deparse.c::deparseDirectUpdateSql : %s",buf->data); + db2Exit1(": %s",buf->data); } /* @@ -3659,7 +3655,7 @@ void deparseDirectUpdateSql(StringInfo buf, PlannerInfo *root, Index rtindex, Re static void deparseReturningList(StringInfo buf, RangeTblEntry *rte, Index rtindex, Relation rel, bool trig_after_row, List *withCheckOptionList, List *returningList, List **retrieved_attrs) { Bitmapset *attrs_used = NULL; - db2Entry(1,"> db2_deparse.c::deparseReturningList"); + db2Entry1(); if (trig_after_row) { /* whole-row reference acquires all non-system columns */ attrs_used = bms_make_singleton(0 - FirstLowInvalidHeapAttributeNumber); @@ -3683,21 +3679,21 @@ static void deparseReturningList(StringInfo buf, RangeTblEntry *rte, Index rtind deparseTargetList(buf, rte, rtindex, rel, true, attrs_used, false, retrieved_attrs); else *retrieved_attrs = NIL; - db2Exit(1,"< db2_deparse.c::deparseReturningList : %s", buf->data); + db2Exit1(": %s", buf->data); } /* deparse remote DELETE statement * The statement text is appended to buf, and we also create an integer List of the columns being retrieved by RETURNING (if any), which is returned to *retrieved_attrs. */ void deparseDeleteSql(StringInfo buf, RangeTblEntry *rte, Index rtindex, Relation rel, List *returningList, List **retrieved_attrs) { - db2Entry(1,"> db2_deparse.c::deparseDeleteSql"); + db2Entry1(); appendStringInfoString(buf, "DELETE FROM "); deparseRelation(buf, rel); appendStringInfoString(buf, " WHERE ctid = $1"); deparseReturningList(buf, rte, rtindex, rel, rel->trigdesc && rel->trigdesc->trig_delete_after_row, NIL, returningList, retrieved_attrs); - db2Exit(1,"> db2_deparse.c::deparseDeleteSql : %s", buf->data); + db2Exit1(": %s", buf->data); } /* deparse remote DELETE statement @@ -3714,7 +3710,7 @@ void deparseDirectDeleteSql(StringInfo buf, PlannerInfo *root, Index rtindex, Re deparse_expr_cxt context; List *additional_conds = NIL; - db2Entry(1,"> db2_deparse.c::deparseDirectDeleteSql"); + db2Entry1(); /* Set up context struct for recursion */ context.root = root; context.foreignrel = foreignrel; @@ -3745,5 +3741,5 @@ void deparseDirectDeleteSql(StringInfo buf, PlannerInfo *root, Index rtindex, Re deparseExplicitTargetList(returningList, true, retrieved_attrs, &context); else deparseReturningList(buf, planner_rt_fetch(rtindex, root), rtindex, rel, false, NIL, returningList, retrieved_attrs); - db2Exit(1,"< db2_deparse.c::deparseDirectDeleteSql : %s", buf->data); + db2Exit1(": %s", buf->data); } diff --git a/source/db2_fdw_utils.c b/source/db2_fdw_utils.c index 9fed162..3a8a544 100644 --- a/source/db2_fdw_utils.c +++ b/source/db2_fdw_utils.c @@ -35,9 +35,6 @@ typedef struct { /** external prototypes */ -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); -extern void db2Debug (int level, const char* message, ...); extern void db2GetLob (DB2Session* session, DB2ResultColumn* column, char** value, long* value_len, unsigned long trunc); extern void db2Shutdown (void); extern short c2dbType (short fcType); @@ -61,11 +58,11 @@ static bool lookup_shippable (Oid objectId, Oid classId, DB2Fd /* optionIsTrue * Returns true if the string is "true", "on" or "yes". */ -bool optionIsTrue (const char *value) { +bool optionIsTrue (const char* value) { bool result = false; - db2Entry(4,"> db2_fdw_utils.c::optionIsTrue(value: '%s')",value); + db2Entry4("(value: '%s')", value); result = (pg_strcasecmp (value, "on") == 0 || pg_strcasecmp (value, "yes") == 0 || pg_strcasecmp (value, "true") == 0); - db2Exit(4,"< db2_fdw_utils.c::optionIsTrue : '%s'",((result) ? "true" : "false")); + db2Exit4(": '%s'",((result) ? "true" : "false")); return result; } @@ -80,7 +77,7 @@ char* guessNlsLang (char *nls_lang) { char* charset = NULL; StringInfoData buf; - db2Entry(4,"> db2_fdw_utils.c::guessNlsLang(nls_lang: %s)", nls_lang); + db2Entry4("(nls_lang: %s)", nls_lang); initStringInfo (&buf); if (nls_lang == NULL) { server_encoding = db2strdup (GetConfigOption ("server_encoding", false, true)); @@ -183,7 +180,7 @@ char* guessNlsLang (char *nls_lang) { } else { appendStringInfo (&buf, "NLS_LANG=%s", nls_lang); } - db2Exit(4,"< db2_fdw_utils.c::guessNlsLang : %s", buf.data); + db2Exit4(": %s", buf.data); return buf.data; } @@ -191,9 +188,9 @@ char* guessNlsLang (char *nls_lang) { * Close all DB2 connections on process exit. */ void exitHook (int code, Datum arg) { - db2Entry(4,"> db2_fdw_utils.c::exitHook"); + db2Entry4(); db2Shutdown (); - db2Exit(4,"< db2_fdw_utils.c::exitHook"); + db2Exit4(); } /* convertTuple @@ -208,13 +205,13 @@ void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls DB2ResultColumn* res = NULL; bool isSimpleSelect = false; - db2Entry(4,"> db2_fdw_utils.c::convertTuple"); - db2Debug(5,"natts: %d", natts); - db2Debug(5,"truncate lob: %s", trunc_lob ? "true": "false"); + db2Entry4(); + db2Debug5("natts: %d", natts); + db2Debug5("truncate lob: %s", trunc_lob ? "true": "false"); /* assign result values */ isSimpleSelect = (natts == db2Table->npgcols); - db2Debug(5,"isSimpleSelect: %s", isSimpleSelect ? "true": "false"); + db2Debug5("isSimpleSelect: %s", isSimpleSelect ? "true": "false"); // initialize all columns to NULL for (j = 0; j < natts; j++) { @@ -224,14 +221,14 @@ void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls for (res = fdw_state->resultList; res; res = res->next) { j = ((isSimpleSelect) ? res->pgattnum : res->resnum) - 1; - db2Debug(5,"start processing column %d of %d: values index = %d", res->resnum, natts, j); - db2Debug(5,"res->pgname : %s" ,res->pgname ); - db2Debug(5,"res->pgattnum : %d" ,res->pgattnum); - db2Debug(5,"res->pgtype : %d" ,res->pgtype ); - db2Debug(5,"res->pgtypmod : %d" ,res->pgtypmod); - db2Debug(5,"res->val : %s" ,res->val ); - db2Debug(5,"res->val_len : %d" ,res->val_len ); - db2Debug(5,"res->val_null : %d" ,res->val_null); + db2Debug5("start processing column %d of %d: values index = %d", res->resnum, natts, j); + db2Debug5("res->pgname : %s" ,res->pgname ); + db2Debug5("res->pgattnum : %d" ,res->pgattnum); + db2Debug5("res->pgtype : %d" ,res->pgtype ); + db2Debug5("res->pgtypmod : %d" ,res->pgtypmod); + db2Debug5("res->val : %s" ,res->val ); + db2Debug5("res->val_len : %d" ,res->val_len ); + db2Debug5("res->val_null : %d" ,res->val_null); if (res->val_null >= 0) { short db2Type = 0; @@ -242,14 +239,14 @@ void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls switch(c2dbType(res->colType)) { case DB2_BLOB: case DB2_CLOB: { - db2Debug(5,"DB2_BLOB or DB2CLOB"); + db2Debug5("DB2_BLOB or DB2CLOB"); /* for LOBs, get the actual LOB contents (allocated), truncated if desired */ /* the column index is 1 based, whereas index id 0 based, so always add 1 to index when calling db2GetLob, since it does a column based access*/ db2GetLob (fdw_state->session, res, &value, &value_len, trunc_lob ? (WIDTH_THRESHOLD + 1) : 0); } break; case DB2_LONGVARBINARY: { - db2Debug(5,"DB2_LONGBINARY datatypes"); + db2Debug5("DB2_LONGBINARY datatypes"); /* for LONG and LONG RAW, the first 4 bytes contain the length */ value_len = *((int32*) res->val); /* the rest is the actual data */ @@ -267,7 +264,7 @@ void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls case DB2_DOUBLE: { char* tmp_value = NULL; - db2Debug(5,"DB2_FLOAT, DECIMAL, SMALLINT, INTEGER, REAL, DECFLOAT, DOUBLE"); + db2Debug5("DB2_FLOAT, DECIMAL, SMALLINT, INTEGER, REAL, DECFLOAT, DOUBLE"); value = res->val; value_len = res->val_len; value_len = (value_len == 0) ? strlen(value) : value_len; @@ -278,7 +275,7 @@ void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls } break; default: { - db2Debug(5,"shoud be string based values"); + db2Debug5("shoud be string based values"); /* for other data types, db2Table contains the results */ value = res->val; value_len = res->val_len; @@ -286,8 +283,8 @@ void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls } break; } - db2Debug(5,"value : %s" , value); - db2Debug(5,"value_len : %ld" , value_len); + db2Debug5("value : %s" , value); + db2Debug5("value_len : %ld" , value_len); /* fill the TupleSlot with the data (after conversion if necessary) */ if (res->pgtype == BYTEAOID) { /* binary columns are not converted */ @@ -300,7 +297,7 @@ void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls regproc typinput; HeapTuple tuple; Datum dat; - db2Debug(5,"pgtype: %d",res->pgtype); + db2Debug5("pgtype: %d",res->pgtype); /* find the appropriate conversion function */ tuple = SearchSysCache1 (TYPEOID, ObjectIdGetDatum (res->pgtype)); if (!HeapTupleIsValid (tuple)) { @@ -309,11 +306,11 @@ void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls typinput = ((Form_pg_type) GETSTRUCT (tuple))->typinput; ReleaseSysCache (tuple); dat = CStringGetDatum (value); - db2Debug(5,"CStringGetDatum(%s): %d",value, dat); + db2Debug5("CStringGetDatum(%s): %d",value, dat); /* for string types, check that the data are in the database encoding */ if (res->pgtype == BPCHAROID || res->pgtype == VARCHAROID || res->pgtype == TEXTOID) { - db2Debug(5,"pg_verify_mbstr"); + db2Debug5("pg_verify_mbstr"); (void) pg_verify_mbstr (GetDatabaseEncoding(), value, value_len, res->noencerr == NO_ENC_ERR_TRUE); } /* call the type input function */ @@ -328,12 +325,12 @@ void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls case NUMERICOID: /* these functions require the type modifier */ values[j] = OidFunctionCall3 (typinput, dat, ObjectIdGetDatum (InvalidOid), Int32GetDatum (res->pgtypmod)); - db2Debug(5,"OidFunctionCall3 : values[%d]: %d", j, values[j]); + db2Debug5("OidFunctionCall3 : values[%d]: %d", j, values[j]); break; default: /* the others don't */ values[j] = OidFunctionCall1 (typinput, dat); - db2Debug(5,"OidFunctionCall1 : values[%d]: %d", j, values[j]); + db2Debug5("OidFunctionCall1 : values[%d]: %d", j, values[j]); } } /* release the data buffer for LOBs */ @@ -342,21 +339,21 @@ void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls if (value != NULL) { db2free (value); } else { - db2Debug(5,"not freeing value, since it is null"); + db2Debug5("not freeing value, since it is null"); } } } else { - db2Debug(5,"column %d is NULL", res->resnum); + db2Debug5("column %d is NULL", res->resnum); } } - db2Exit(4,"< db2_fdw_utils.c::convertTuple"); + db2Exit4(); } /* Undo the effects of set_transmission_modes(). */ void reset_transmission_modes(int nestlevel) { - db2Entry(4,"> db2_fdw_utils.c::reset_transmission_modes"); + db2Entry4(); AtEOXact_GUC(true, nestlevel); - db2Exit(4,"< db2_fdw_utils.c::reset_transmission_modes"); + db2Exit4(); } /* Force assorted GUC parameters to settings that ensure that we'll output data values in a form that is unambiguous to the remote server. @@ -372,7 +369,7 @@ void reset_transmission_modes(int nestlevel) { int set_transmission_modes(void) { int nestlevel = NewGUCNestLevel(); - db2Entry(4,"> db2_fdw_utils.c::set_transmission_modes"); + db2Entry4(); /* The values set here should match what pg_dump does. See also configure_remote_session in connection.c. */ if (DateStyle != USE_ISO_DATES) (void) set_config_option("datestyle", "ISO", PGC_USERSET, PGC_S_SESSION, GUC_ACTION_SAVE, true, 0, false); @@ -387,7 +384,7 @@ int set_transmission_modes(void) { */ (void) set_config_option("search_path", "pg_catalog", PGC_USERSET, PGC_S_SESSION, GUC_ACTION_SAVE, true, 0, false); - db2Exit(4,"< db2_fdw_utils.c::set_transmission_modes : %d", nestlevel); + db2Exit4(": %d", nestlevel); return nestlevel; } @@ -407,8 +404,8 @@ int set_transmission_modes(void) { */ bool is_builtin (Oid objectId) { bool isBuiltin = (objectId < FirstGenbkiObjectId); - db2Entry(4,"> db2_fdw_utils.c::is_builtin"); - db2Exit (4,"> db2_fdw_utils.c::is_builtin : %s", (isBuiltin) ? "true": "false"); + db2Entry4(); + db2Exit4(": %s", (isBuiltin) ? "true": "false"); return isBuiltin; } @@ -420,7 +417,7 @@ bool is_shippable (Oid objectId, Oid classId, DB2FdwState* fpinfo) { ShippableCacheEntry* entry = NULL; bool shippable = false; - db2Entry(4,"> db2_fdw_utils.c::is_shippable"); + db2Entry4(); /* Built-in objects are presumed shippable. */ if (is_builtin(objectId)) { shippable = true; @@ -451,12 +448,12 @@ bool is_shippable (Oid objectId, Oid classId, DB2FdwState* fpinfo) { entry = (ShippableCacheEntry*) hash_search(ShippableCacheHash, &key, HASH_ENTER, NULL); entry->shippable = shippable; } else { - db2Debug(4, "no shippable cache entry (%x) found shippable is set to false", entry); + db2Debug4("no shippable cache entry (%x) found shippable is set to false", entry); shippable = false; } } } - db2Exit(4,"< db2_fdw_utils.c::is_shippable : %s", shippable ? "true" : "false"); + db2Exit4(": %s", shippable ? "true" : "false"); return shippable; } @@ -470,7 +467,7 @@ static void InvalidateShippableCacheCbk(Datum arg, int cacheid, uint32 hashvalue HASH_SEQ_STATUS status; ShippableCacheEntry* entry; - db2Entry(5,"> db2_fdw_utils.c::InvalidateShippableCacheCbk"); + db2Entry5(); /* In principle we could flush only cache entries relating to the pg_foreign_server entry being outdated; but that would be more * complicated, and it's probably not worth the trouble. * So for now, just flush all entries. @@ -480,14 +477,14 @@ static void InvalidateShippableCacheCbk(Datum arg, int cacheid, uint32 hashvalue if (hash_search(ShippableCacheHash, &entry->key, HASH_REMOVE, NULL) == NULL) elog(ERROR, "hash table corrupted"); } - db2Exit(5,"< db2_fdw_utils.c::InvalidateShippableCacheCbk"); + db2Exit5(); } /* Initialize the backend-lifespan cache of shippability decisions. */ static void InitializeShippableCache(void) { HASHCTL ctl; - db2Entry(5,"> db2_fdw_utils.c::InitializeShippableCache"); + db2Entry5(); /* Create the hash table. */ ctl.keysize = sizeof(ShippableCacheKey); ctl.entrysize = sizeof(ShippableCacheEntry); @@ -495,7 +492,7 @@ static void InitializeShippableCache(void) { /* Set up invalidation callback on pg_foreign_server. */ CacheRegisterSyscacheCallback(FOREIGNSERVEROID, InvalidateShippableCacheCbk, (Datum) 0); - db2Exit(5,"< db2_fdw_utils.c::InitializeShippableCache"); + db2Exit5(); } /* Returns true if given object (operator/function/type) is shippable according to the server options. @@ -507,12 +504,12 @@ static bool lookup_shippable(Oid objectId, Oid classId, DB2FdwState* fpinfo) { Oid extensionOid = 0; bool isValid = false; - db2Entry(5,"> db2_fdw_utils.c::lookup_shippable"); + db2Entry5(); /* Is object a member of some extension? (Note: this is a fairly expensive lookup, which is why we try to cache the results.) */ extensionOid = getExtensionOfObject(classId, objectId); /* If so, is that extension in fpinfo->shippable_extensions? */ isValid = (OidIsValid(extensionOid) && list_member_oid(fpinfo->shippable_extensions, extensionOid)); - db2Exit(5,"< db2_fdw_utils.c::lookup_shippable : %s", (isValid) ? "true" : "false"); + db2Exit5(": %s", (isValid) ? "true" : "false"); return false; } \ No newline at end of file diff --git a/source/db2_utils.c b/source/db2_utils.c index 69403b7..1f0a467 100644 --- a/source/db2_utils.c +++ b/source/db2_utils.c @@ -4,11 +4,6 @@ #include #include "db2_fdw.h" -/** external variables */ -extern void db2Entry (int level, const char* message, ...); -extern void db2Exit (int level, const char* message, ...); -extern void db2Debug (int level, const char* message, ...); - /** local prototypes */ SQLSMALLINT c2param (SQLSMALLINT fparamType); char* param2name (SQLSMALLINT fparamType); @@ -81,23 +76,23 @@ char* param2name(SQLSMALLINT fparamType){ */ SQLSMALLINT c2param (SQLSMALLINT fcType) { SQLSMALLINT fparamType = SQL_C_CHAR; - db2Entry(4,"> db2_utils.c::c2param(fcType: %d)",fcType); + db2Entry4("(fcType: %d)",fcType); switch (fcType) { case SQL_BLOB: fparamType = SQL_C_BLOB_LOCATOR; - db2Debug(5,"SQL_BLOB => SQL_C_LOCATOR"); + db2Debug5("SQL_BLOB => SQL_C_LOCATOR"); break; case SQL_CLOB: fparamType = SQL_C_CLOB_LOCATOR; - db2Debug(5,"SQL_COB => SQL_C_CLOB_LOCATOR"); + db2Debug5("SQL_COB => SQL_C_CLOB_LOCATOR"); break; default: /* all other columns are converted to strings */ fparamType = SQL_C_CHAR; - db2Debug(5,"%s => SQL_C_CHAR",c2name(fcType)); + db2Debug5("%s => SQL_C_CHAR",c2name(fcType)); break; } - db2Exit(4,"< db2_utils.c::c2param : %d)",fparamType); + db2Exit4(": %d",fparamType); return fparamType; } @@ -113,7 +108,7 @@ void parse2num_struct(const char* s, SQL_NUMERIC_STRUCT* ns) { long int intPart = 0; int negative = 0; int fracLen = 0; - db2Entry(4,"> db2_utils.c::parse2num_struct( '%s')",s); + db2Entry4("(s: %s)",s); // Simple, minimal parser: handles optional leading '-' and '.'; no thousands sep. memset(ns, 0, sizeof(*ns)); ns->precision = 18; // set to your target @@ -163,7 +158,7 @@ void parse2num_struct(const char* s, SQL_NUMERIC_STRUCT* ns) { ns->val[i] = (SQLCHAR)(mag & 0xFF); mag >>= 8; } - db2Exit(4,"< db2_utils.c::parse2num_struct"); + db2Exit4(); } /** c2dbType From a60ed922fd27d6bfe59bdc8ba291d21562164ca4 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Fri, 27 Feb 2026 14:22:06 +0100 Subject: [PATCH 080/120] direct update and direct delete now work, but the code is likely not final yet --- include/DB2FdwDirectModifyState.h | 3 ++ source/db2BeginDirectModify.c | 4 +++ source/db2IterateDirectModify.c | 48 +++++++++++++++++++++++++------ source/db2PlanDirectModify.c | 6 ++++ 4 files changed, 52 insertions(+), 9 deletions(-) diff --git a/include/DB2FdwDirectModifyState.h b/include/DB2FdwDirectModifyState.h index 6acecc4..c4b8d35 100644 --- a/include/DB2FdwDirectModifyState.h +++ b/include/DB2FdwDirectModifyState.h @@ -5,6 +5,8 @@ #include #include #include "ParamDesc.h" +#include "DB2ResultColumn.h" + // Execution state of a foreign scan that modifies a foreign table directly. typedef struct db2FdwDirectModifyState { @@ -16,6 +18,7 @@ typedef struct db2FdwDirectModifyState { char* nls_lang; // DB2 locale information DB2Session* session; // encapsulates the active DB2 session ParamDesc* paramList; // description of parameters needed for the query + DB2ResultColumn* resultList; // list of result columns for the query DB2Table* db2Table; // description of the remote DB2 table unsigned long prefetch; // number of rows to prefetch (SQL_ATTR_PREFETCH_NROWS 0-1024) int fetch_size; // fetch size for this remote table (SQL_ATTR_ROW_ARRAY_SIZE 1 - 32767) diff --git a/source/db2BeginDirectModify.c b/source/db2BeginDirectModify.c index e926f01..78660be 100644 --- a/source/db2BeginDirectModify.c +++ b/source/db2BeginDirectModify.c @@ -55,9 +55,13 @@ void db2BeginDirectModify(ForeignScanState* node, int eflags) { /* Get private info created by planner functions. */ dmstate->query = strVal(list_nth (fsplan->fdw_private, FdwDirectModifyPrivateUpdateSql)); + db2Debug2("dmstate->query: %s",dmstate->query); dmstate->has_returning = boolVal(list_nth(fsplan->fdw_private, FdwDirectModifyPrivateHasReturning)); + db2Debug2("dmstate->has_returning: %s",dmstate->has_returning? "true" : "false"); dmstate->retrieved_attrs = (List*) list_nth(fsplan->fdw_private, FdwDirectModifyPrivateRetrievedAttrs); + db2Debug2("dmstate->retrieved_attrs: %x - %d", dmstate->retrieved_attrs, list_length(dmstate->retrieved_attrs)); dmstate->set_processed = boolVal(list_nth(fsplan->fdw_private, FdwDirectModifyPrivateSetProcessed)); + db2Debug2("dmstate->set_processed: %s", dmstate->set_processed ? "true" : "false"); /* Create context for per-tuple temp workspace. */ dmstate->temp_cxt = AllocSetContextCreate(estate->es_query_cxt, "db2_fdw temporary data", ALLOCSET_SMALL_SIZES); diff --git a/source/db2IterateDirectModify.c b/source/db2IterateDirectModify.c index 7616bf0..af06a0f 100644 --- a/source/db2IterateDirectModify.c +++ b/source/db2IterateDirectModify.c @@ -3,6 +3,11 @@ #include "DB2FdwDirectModifyState.h" /** external prototypes */ +extern int db2IsStatementOpen (DB2Session* session); +extern void db2PrepareQuery (DB2Session* session, const char *query, DB2ResultColumn* resultList, unsigned long prefetch, int fetchsize); +extern int db2ExecuteQuery (DB2Session* session, ParamDesc* paramList); +extern int db2FetchNext (DB2Session* session); +extern void db2CloseStatement (DB2Session* session); /** local prototypes */ TupleTableSlot* db2IterateDirectModify(ForeignScanState *node); @@ -11,19 +16,44 @@ TupleTableSlot* db2IterateDirectModify(ForeignScanState *node); * Execute a direct foreign table modification */ TupleTableSlot* db2IterateDirectModify(ForeignScanState *node) { - DB2FdwDirectModifyState* dmstate = (DB2FdwDirectModifyState*) node->fdw_state; - EState* estate = node->ss.ps.state; - ResultRelInfo* rtinfo = node->resultRelInfo; - TupleTableSlot* slot = NULL; + DB2FdwDirectModifyState* dmstate = (DB2FdwDirectModifyState*) node->fdw_state; + EState* estate = node->ss.ps.state; + ResultRelInfo* rtinfo = node->resultRelInfo; + int have_result = 0; + TupleTableSlot* slot = NULL; // nachfolgende Werte in ParamDesc Liste zusammenfassen - int numParams = dmstate->numParams; - const char** values = dmstate->param_values; - FmgrInfo* param_flinfo = dmstate->param_flinfo; - List* param_exps = dmstate->param_exprs; + //int numParams = dmstate->numParams; + //const char** values = dmstate->param_values; + //FmgrInfo* param_flinfo = dmstate->param_flinfo; + //List* param_exps = dmstate->param_exprs; - // call db2ExecForeignDirectUpdate() similar to db2ExecForeignInsert() db2Entry1(); + // We should have a valid session. + // It was created during db2GetFdwDirectModifyState during db2BeginDirectModify + + if (db2IsStatementOpen(dmstate->session)) { + + } else { + db2PrepareQuery(dmstate->session, dmstate->query, NULL, dmstate->prefetch, dmstate->fetch_size); + have_result = db2ExecuteQuery (dmstate->session, dmstate->paramList); + db2Debug2(" %d rows affected by this query", have_result); + } + + /* initialize virtual tuple */ +// ExecClearTuple (slot); +// if (have_result) { + /* increase row count */ +// ++dmstate->rowcount; + /* convert result to arrays of values and null indicators */ +// db2Debug2("slot->tts_tupleDescriptor->natts: %d",slot->tts_tupleDescriptor->natts); +// convertTuple (dmstate, slot->tts_tupleDescriptor->natts, slot->tts_values, slot->tts_isnull, false); + /* store the virtual tuple */ +// ExecStoreVirtualTuple (slot); +// } else { + /* close the statement */ + db2CloseStatement (dmstate->session); +// } db2Exit1(": %x", slot); return slot; } diff --git a/source/db2PlanDirectModify.c b/source/db2PlanDirectModify.c index efc7ff5..ffdd0ec 100644 --- a/source/db2PlanDirectModify.c +++ b/source/db2PlanDirectModify.c @@ -146,8 +146,14 @@ bool db2PlanDirectModify(PlannerInfo* root, ModifyTable* plan, Index rtindex, in table_close(rel, NoLock); } + } else { + fResult = false; } + } else { + fResult = false; } + } else { + fResult = false; } db2Exit1(": %s", (fResult) ? "true" : "false"); return fResult; From bfb80053b60f57ab66d99f3ae293ceee3c694a5e Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Sun, 1 Mar 2026 11:34:22 +0100 Subject: [PATCH 081/120] instead of allocating indentation, just move the output in the outputbuffer --- source/db2Debug.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/source/db2Debug.c b/source/db2Debug.c index b79a5b4..7301099 100644 --- a/source/db2Debug.c +++ b/source/db2Debug.c @@ -96,14 +96,15 @@ void db2EntryExit(int level, int entry, const char* message, ...) { void db2Debug(int level, const char* message, ...) { if (isLogLevel(level)) { char cBuffer [4000]; - char* sIndent = (char*)palloc0(2 * debug_depth + 1); int dLevel = DEBUG5; + int offset = (2*debug_depth); va_list arg_marker; - memset(sIndent, ' ', 2 * debug_depth); + memset(cBuffer, ' ', offset); + cBuffer[offset] = '\0'; va_start (arg_marker, message); - vsnprintf (cBuffer, sizeof(cBuffer), message, arg_marker); + vsnprintf (cBuffer+offset, sizeof(cBuffer)-offset, message, arg_marker); switch(level){ case 1: dLevel = DEBUG1; @@ -122,8 +123,7 @@ void db2Debug(int level, const char* message, ...) { dLevel = DEBUG5; break; } - elog (dLevel, "%s%s", sIndent, cBuffer); + elog (dLevel, "%s", cBuffer); va_end (arg_marker); - pfree(sIndent); } } From cf1f3ee07a3359a34f8bcdf097a0b39abcf46d13 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Sun, 1 Mar 2026 11:35:03 +0100 Subject: [PATCH 082/120] adding comments to functions --- source/db2ReAllocFree.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/source/db2ReAllocFree.c b/source/db2ReAllocFree.c index 4a1c56b..94e987e 100644 --- a/source/db2ReAllocFree.c +++ b/source/db2ReAllocFree.c @@ -11,8 +11,8 @@ void* db2realloc (void* p, size_t size); void db2free (void* p); char* db2strdup (const char* source); -/** db2alloc - * Expose palloc() to DB2 functions. +/* db2alloc + * Expose palloc0() to DB2 functions. */ void* db2alloc (const char* type, size_t size) { void* memory = palloc0(size); @@ -20,8 +20,8 @@ void* db2alloc (const char* type, size_t size) { return memory; } -/** db2realloc - * Expose repalloc() to DB2 functions. +/* db2realloc + * Expose repalloc() to DB2 functions. */ void* db2realloc (void* p, size_t size) { void* memory = repalloc(p, size); @@ -29,8 +29,8 @@ void* db2realloc (void* p, size_t size) { return memory; } -/** db2free - * Expose pfree() to DB2 functions. +/* db2free + * Expose pfree() to DB2 functions. */ void db2free (void* p) { if (p != NULL) { @@ -39,6 +39,9 @@ void db2free (void* p) { } } +/* db2strdup + * Expose pstrdup() to DB2 functions. + */ char* db2strdup(const char* source) { char* target = NULL; if (source != NULL && source[0] != '\0') { From fcbb1df370229aa22e0d502cabf8c430bc101a96 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Sun, 1 Mar 2026 11:35:43 +0100 Subject: [PATCH 083/120] estate and rtinfo are currently not used in db2IterateDirectModify, but probably later, so just commented out for the time being --- source/db2IterateDirectModify.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/db2IterateDirectModify.c b/source/db2IterateDirectModify.c index af06a0f..954619f 100644 --- a/source/db2IterateDirectModify.c +++ b/source/db2IterateDirectModify.c @@ -17,8 +17,8 @@ TupleTableSlot* db2IterateDirectModify(ForeignScanState *node); */ TupleTableSlot* db2IterateDirectModify(ForeignScanState *node) { DB2FdwDirectModifyState* dmstate = (DB2FdwDirectModifyState*) node->fdw_state; - EState* estate = node->ss.ps.state; - ResultRelInfo* rtinfo = node->resultRelInfo; +// EState* estate = node->ss.ps.state; +// ResultRelInfo* rtinfo = node->resultRelInfo; int have_result = 0; TupleTableSlot* slot = NULL; From 1310c8b1c33257c08813f9c23698571c83ee697f Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Sun, 1 Mar 2026 11:36:18 +0100 Subject: [PATCH 084/120] using db2alloc instead of palloc --- source/db2BeginDirectModify.c | 2 +- source/db2GetForeignUpperPaths.c | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/source/db2BeginDirectModify.c b/source/db2BeginDirectModify.c index 78660be..63eae9c 100644 --- a/source/db2BeginDirectModify.c +++ b/source/db2BeginDirectModify.c @@ -197,7 +197,7 @@ static void prepare_query_params(PlanState* node, List* fdw_exprs, int numParams Assert(numParams > 0); /* Prepare for output conversion of parameters used in remote query. */ - *param_flinfo = palloc0_array(FmgrInfo, numParams); + *param_flinfo = db2alloc("prepare_query_params:param_flinfo",sizeof(FmgrInfo) * numParams); i = 0; foreach(lc, fdw_exprs) { diff --git a/source/db2GetForeignUpperPaths.c b/source/db2GetForeignUpperPaths.c index ef2904b..d040ecd 100644 --- a/source/db2GetForeignUpperPaths.c +++ b/source/db2GetForeignUpperPaths.c @@ -394,8 +394,8 @@ static void add_foreign_ordered_paths(PlannerInfo *root, RelOptInfo *input_rel, /* Safe to push down */ fpinfo->pushdown_safe = true; /* Construct PgFdwPathExtraData */ - fpextra = palloc0_object(DB2FdwPathExtraData); - fpextra->target = root->upper_targets[UPPERREL_ORDERED]; + fpextra = db2alloc("add_foreign_ordered_path:fpextra",sizeof(DB2FdwPathExtraData)); + fpextra->target = root->upper_targets[UPPERREL_ORDERED]; fpextra->has_final_sort = true; /* Estimate the costs of performing the final sort remotely */ estimate_path_cost_size(root, input_rel, NIL, root->sort_pathkeys, fpextra, &rows, &width, &disabled_nodes, &startup_cost, &total_cost); @@ -581,7 +581,7 @@ static void add_foreign_final_paths(PlannerInfo *root, RelOptInfo *input_rel, Re fpinfo->pushdown_safe = true; /* Construct DB2FdwPathExtraData */ - fpextra = palloc0_object(DB2FdwPathExtraData); + fpextra = db2alloc("add_foreign_final_path:fpextra",sizeof(DB2FdwPathExtraData)); fpextra->target = root->upper_targets[UPPERREL_FINAL]; fpextra->has_final_sort = has_final_sort; fpextra->has_limit = extra->limit_needed; From 155786cd2b99b567220691caa9490db0e7109e94 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Sun, 1 Mar 2026 11:36:47 +0100 Subject: [PATCH 085/120] adding a debug trace before freeing resCol --- source/db2GetForeignPlan.c | 1 + 1 file changed, 1 insertion(+) diff --git a/source/db2GetForeignPlan.c b/source/db2GetForeignPlan.c index 41c94ae..cf78192 100644 --- a/source/db2GetForeignPlan.c +++ b/source/db2GetForeignPlan.c @@ -97,6 +97,7 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo fpinfo->resultList = resCol; db2Debug3("fpinfo->resultList: %x", fpinfo->resultList); } else { + db2Debug3("about to free resCol: %x, colName: %s, pgattnum: %d", resCol, resCol->colName, resCol->pgattnum); db2free(resCol); } } From cd552c1d82088c154c6e968978eebc54ddaa3cb9 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Sun, 1 Mar 2026 11:37:47 +0100 Subject: [PATCH 086/120] on insert we lost the input value of the key, so adding to look for it in the slot attribute if it was not set in resjunk --- source/db2ExecForeignDelete.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/source/db2ExecForeignDelete.c b/source/db2ExecForeignDelete.c index 626d2f2..f0a011b 100644 --- a/source/db2ExecForeignDelete.c +++ b/source/db2ExecForeignDelete.c @@ -110,6 +110,13 @@ void setModifyParameters (ParamDesc *paramList, TupleTableSlot * newslot, TupleT datum = ExecGetJunkAttribute (oldslot, attrno, &isnull); db2Debug2("primaryKey value from resjunk entry: %ld",datum); } + // If null go and check the normal parameter in the slot + if (isnull) { + /* for other parameters extract the datum from newslot */ + datum = slot_getattr (newslot, db2Table->cols[param->colnum]->pgattnum, &isnull); + db2Debug2("parameter value from newslot: %ld",datum); + isnull = (datum == 0); + } } else { /* for other parameters extract the datum from newslot */ datum = slot_getattr (newslot, db2Table->cols[param->colnum]->pgattnum, &isnull); From a81797560a7ea2910cf74a97bf548bec1a13a1fe Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Sun, 1 Mar 2026 18:41:25 +0100 Subject: [PATCH 087/120] fix a debug message --- source/db2_fdw_utils.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/db2_fdw_utils.c b/source/db2_fdw_utils.c index 3a8a544..f44692e 100644 --- a/source/db2_fdw_utils.c +++ b/source/db2_fdw_utils.c @@ -275,7 +275,7 @@ void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls } break; default: { - db2Debug5("shoud be string based values"); + db2Debug5("should be string based values"); /* for other data types, db2Table contains the results */ value = res->val; value_len = res->val_len; From 02c82670e2e70766f037f36462dcb12952235450 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Sun, 1 Mar 2026 18:41:56 +0100 Subject: [PATCH 088/120] simply use the Key Column name for the junkresname as well, it seems to have no impact --- source/db2AddForeignUpdateTargets.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/source/db2AddForeignUpdateTargets.c b/source/db2AddForeignUpdateTargets.c index 9b8f669..4676a93 100644 --- a/source/db2AddForeignUpdateTargets.c +++ b/source/db2AddForeignUpdateTargets.c @@ -51,7 +51,6 @@ void db2AddForeignUpdateTargets (PlannerInfo* root, Index rtindex,RangeTblEntry* if (strcmp (def->defname, OPT_KEY) == 0) { if (optionIsTrue (STRVAL(def->arg))) { Var* var = NULL; - char* key_col_name = db2strdup(psprintf("__db2fdw_rowid_%s", NameStr(att->attname))); #if PG_VERSION_NUM < 140000 TargetEntry *tle; @@ -82,8 +81,8 @@ void db2AddForeignUpdateTargets (PlannerInfo* root, Index rtindex,RangeTblEntry* ); db2Debug2("create var rtindex: %d, attrno: %d, typid: %d, typmod: %d, collation: %d",rtindex,attrno,att->atttypid,att->atttypmod,att->attcollation); /* Register it as a required row-identity column. The name becomes the resjunk column name in the plan. */ - add_row_identity_var(root, var, rtindex, key_col_name); - db2Debug2("add resjunk column %s: %d", key_col_name, rtindex); + add_row_identity_var(root, var, rtindex, NameStr(att->attname)); + db2Debug2("add resjunk column %s: %d", NameStr(att->attname), rtindex); #endif /* PG_VERSION_NUM */ has_key = true; } From 72db2974a7526233dc73f2d608a963dec5313352 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Sun, 1 Mar 2026 18:43:19 +0100 Subject: [PATCH 089/120] ensure junk attrs are only obtained on UPDATE and DELETE. Obtain the attrno by Calling by column name, since we used that on AddUpdateTargets. --- source/db2BeginForeignModifyCommon.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/source/db2BeginForeignModifyCommon.c b/source/db2BeginForeignModifyCommon.c index 296d615..12df090 100644 --- a/source/db2BeginForeignModifyCommon.c +++ b/source/db2BeginForeignModifyCommon.c @@ -48,11 +48,14 @@ void db2BeginForeignModifyCommon(ModifyTableState* mtstate, ResultRelInfo* rinfo } /* primary-key junk attrs are only needed for UPDATE/DELETE */ - if (subplan != NULL) { + if (subplan != NULL && (mtstate->operation == CMD_DELETE || mtstate->operation == CMD_UPDATE)) { for (i = 0; i < fdw_state->db2Table->ncols; ++i) { if (!fdw_state->db2Table->cols[i]->colPrimKeyPart) continue; fdw_state->db2Table->cols[i]->pkey = ExecFindJunkAttributeInTlist(subplan->targetlist, fdw_state->db2Table->cols[i]->pgname); + db2Debug2("fdw_state->db2Table->cols[%d]->pkey: %d", i, fdw_state->db2Table->cols[i]->pkey); + if (!AttributeNumberIsValid(fdw_state->db2Table->cols[i]->pkey)) + elog(ERROR, "could not find junk %s column",fdw_state->db2Table->cols[i]->pgname); } } From b502bc6bf22699b41cc16d31260e555dce83759b Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Sun, 1 Mar 2026 18:43:45 +0100 Subject: [PATCH 090/120] obtaining junk attr values is now easy by simple using pkey value. --- source/db2ExecForeignDelete.c | 27 ++++++++------------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/source/db2ExecForeignDelete.c b/source/db2ExecForeignDelete.c index f0a011b..53fee6d 100644 --- a/source/db2ExecForeignDelete.c +++ b/source/db2ExecForeignDelete.c @@ -90,26 +90,15 @@ void setModifyParameters (ParamDesc *paramList, TupleTableSlot * newslot, TupleT db2Debug2("param->bindType: %d - BIND_OUTPUT - skipped",param->bindType); continue; } + db2Debug3("db2Table->cols[%d]->colPrimKeyPart: %d ",param->colnum,db2Table->cols[param->colnum]->colPrimKeyPart); if (db2Table->cols[param->colnum]->colPrimKeyPart != 0) { - AttrNumber attrno = -1; - TupleDesc tupdesc = oldslot->tts_tupleDescriptor; - for (int i = 0; i < tupdesc->natts; i++) { - Form_pg_attribute att = TupleDescAttr(tupdesc,i); - if (att->attisdropped) - continue; - if (strcmp(NameStr(att->attname), psprintf("__db2fdw_rowid_%s", db2Table->cols[param->colnum]->pgname)) == 0) { - attrno = i + 1; - db2Debug2("key %s attrno : %d",NameStr(att->attname),attrno); - db2Debug2(" atttypid : %d",att->atttypid); - db2Debug2(" atttypmod: %d",att->atttypmod); - break; - } - } - if (attrno != -1) { - /* for primary key parameters extract the resjunk entry */ - datum = ExecGetJunkAttribute (oldslot, attrno, &isnull); - db2Debug2("primaryKey value from resjunk entry: %ld",datum); - } + if (AttributeNumberIsValid(db2Table->cols[param->colnum]->pkey)) { + db2Debug2("db2Table->cols[%d]->pkey: %d",param->colnum,db2Table->cols[param->colnum]->pkey); + datum = ExecGetJunkAttribute (oldslot, db2Table->cols[param->colnum]->pkey, &isnull); + db2Debug2("primaryKey value from oldslot resjunk entry: %ld",datum); + } else { + elog(ERROR, "no JunkAttribute Value found for key column: %s",db2Table->cols[param->colnum]->colName); + } // If null go and check the normal parameter in the slot if (isnull) { /* for other parameters extract the datum from newslot */ From 368935102b85b1211b0a8a12d494310a0cd43403 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Mon, 2 Mar 2026 11:34:05 +0100 Subject: [PATCH 091/120] refactored convertTuple to call it from functions using both DB2FdwState and DB2FdwDirectModifyState --- source/db2AnalyzeForeignTable.c | 6 +++--- source/db2ExecForeignDelete.c | 8 ++++---- source/db2ExecForeignInsert.c | 4 ++-- source/db2ExecForeignUpdate.c | 4 ++-- source/db2IterateForeignScan.c | 4 ++-- source/db2_fdw_utils.c | 11 +++++------ 6 files changed, 18 insertions(+), 19 deletions(-) diff --git a/source/db2AnalyzeForeignTable.c b/source/db2AnalyzeForeignTable.c index 74873c3..f2abf93 100644 --- a/source/db2AnalyzeForeignTable.c +++ b/source/db2AnalyzeForeignTable.c @@ -15,7 +15,7 @@ extern int db2ExecuteQuery (DB2Session* session, ParamDesc* p extern int db2FetchNext (DB2Session* session); extern void checkDataType (short db2type, int scale, Oid pgtype, const char* tablename, const char* colname); extern short c2dbType (short fcType); -extern void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls, bool trunc_lob) ; +extern void convertTuple (DB2Session* session, DB2Table* db2Table, DB2ResultColumn* reslist, int natts, Datum* values, bool* nulls, bool trunc_lob); extern void* db2alloc (const char* type, size_t size); /** local prototypes */ @@ -113,7 +113,7 @@ static int acquireSampleRowsFunc (Relation relation, int elevel, HeapTuple* rows /* the first "targrows" rows are added as samples */ /* use a temporary memory context during convertTuple */ old_cxt = MemoryContextSwitchTo (tmp_cxt); - convertTuple (fdw_state, tupDesc->natts, values, nulls, true); + convertTuple (fdw_state->session,fdw_state->db2Table,fdw_state->resultList, tupDesc->natts, values, nulls, true); MemoryContextSwitchTo (old_cxt); rows[collected_rows++] = heap_form_tuple (tupDesc, values, nulls); MemoryContextReset (tmp_cxt); @@ -129,7 +129,7 @@ static int acquireSampleRowsFunc (Relation relation, int elevel, HeapTuple* rows heap_freetuple (rows[k]); /* use a temporary memory context during convertTuple */ old_cxt = MemoryContextSwitchTo (tmp_cxt); - convertTuple (fdw_state, tupDesc->natts, values, nulls, true); + convertTuple (fdw_state->session,fdw_state->db2Table,fdw_state->resultList, tupDesc->natts, values, nulls, true); MemoryContextSwitchTo (old_cxt); rows[k] = heap_form_tuple (tupDesc, values, nulls); MemoryContextReset (tmp_cxt); diff --git a/source/db2ExecForeignDelete.c b/source/db2ExecForeignDelete.c index 53fee6d..f62ec17 100644 --- a/source/db2ExecForeignDelete.c +++ b/source/db2ExecForeignDelete.c @@ -12,7 +12,7 @@ extern regproc* output_funcs; /** external prototypes */ extern int db2ExecuteQuery (DB2Session* session, ParamDesc* paramList); extern void db2Debug (int level, const char* message, ...); -extern void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls, bool trunc_lob) ; +extern void convertTuple (DB2Session* session, DB2Table* db2Table, DB2ResultColumn* reslist, int natts, Datum* values, bool* nulls, bool trunc_lob); extern char* deparseDate (Datum datum); extern char* deparseTimestamp (Datum datum, bool hasTimezone); extern void* db2alloc (const char* type, size_t size); @@ -59,7 +59,7 @@ TupleTableSlot* db2ExecForeignDelete (EState* estate, ResultRelInfo* rinfo, Tupl ExecClearTuple (slot); /* convert result for RETURNING to arrays of values and null indicators */ - convertTuple (fdw_state, slot->tts_tupleDescriptor->natts, slot->tts_values, slot->tts_isnull, false); + convertTuple (fdw_state->session,fdw_state->db2Table,fdw_state->resultList, slot->tts_tupleDescriptor->natts, slot->tts_values, slot->tts_isnull, false); /* store the virtual tuple */ ExecStoreVirtualTuple (slot); @@ -97,8 +97,8 @@ void setModifyParameters (ParamDesc *paramList, TupleTableSlot * newslot, TupleT datum = ExecGetJunkAttribute (oldslot, db2Table->cols[param->colnum]->pkey, &isnull); db2Debug2("primaryKey value from oldslot resjunk entry: %ld",datum); } else { - elog(ERROR, "no JunkAttribute Value found for key column: %s",db2Table->cols[param->colnum]->colName); - } + elog(ERROR, "no JunkAttribute Value found for key column: %s",db2Table->cols[param->colnum]->colName); + } // If null go and check the normal parameter in the slot if (isnull) { /* for other parameters extract the datum from newslot */ diff --git a/source/db2ExecForeignInsert.c b/source/db2ExecForeignInsert.c index de8cc1b..5abe4f7 100644 --- a/source/db2ExecForeignInsert.c +++ b/source/db2ExecForeignInsert.c @@ -12,7 +12,7 @@ extern bool dml_in_transaction; /** external prototypes */ extern int db2ExecuteInsert (DB2Session* session, ParamDesc* paramList); extern void setModifyParameters (ParamDesc* paramList, TupleTableSlot* newslot, TupleTableSlot* oldslot, DB2Table* db2Table, DB2Session* session); -extern void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls, bool trunc_lob) ; +extern void convertTuple (DB2Session* session, DB2Table* db2Table, DB2ResultColumn* reslist, int natts, Datum* values, bool* nulls, bool trunc_lob); /** local prototypes */ TupleTableSlot* db2ExecForeignInsert(EState* estate, ResultRelInfo* rinfo, TupleTableSlot* slot, TupleTableSlot* planSlot); @@ -50,7 +50,7 @@ TupleTableSlot* db2ExecForeignInsert (EState* estate, ResultRelInfo* rinfo, Tupl ExecClearTuple (slot); /* convert result for RETURNING to arrays of values and null indicators */ - convertTuple (fdw_state, slot->tts_tupleDescriptor->natts, slot->tts_values, slot->tts_isnull, false); + convertTuple (fdw_state->session,fdw_state->db2Table,fdw_state->resultList, slot->tts_tupleDescriptor->natts, slot->tts_values, slot->tts_isnull, false); /* store the virtual tuple */ ExecStoreVirtualTuple (slot); diff --git a/source/db2ExecForeignUpdate.c b/source/db2ExecForeignUpdate.c index 486d2c6..51ce935 100644 --- a/source/db2ExecForeignUpdate.c +++ b/source/db2ExecForeignUpdate.c @@ -12,7 +12,7 @@ extern bool dml_in_transaction; /** external prototypes */ extern int db2ExecuteQuery (DB2Session* session, ParamDesc* paramList); extern void setModifyParameters (ParamDesc* paramList, TupleTableSlot* newslot, TupleTableSlot* oldslot, DB2Table* db2Table, DB2Session* session); -extern void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls, bool trunc_lob) ; +extern void convertTuple (DB2Session* session, DB2Table* db2Table, DB2ResultColumn* reslist, int natts, Datum* values, bool* nulls, bool trunc_lob); /** local prototypes */ TupleTableSlot* db2ExecForeignUpdate (EState* estate, ResultRelInfo* rinfo, TupleTableSlot* slot, TupleTableSlot* planSlot); @@ -55,7 +55,7 @@ TupleTableSlot* db2ExecForeignUpdate (EState* estate, ResultRelInfo* rinfo, Tupl ExecClearTuple (slot); /* convert result for RETURNING to arrays of values and null indicators */ - convertTuple (fdw_state, slot->tts_tupleDescriptor->natts, slot->tts_values, slot->tts_isnull, false); + convertTuple (fdw_state->session,fdw_state->db2Table,fdw_state->resultList, slot->tts_tupleDescriptor->natts, slot->tts_values, slot->tts_isnull, false); /* store the virtual tuple */ ExecStoreVirtualTuple (slot); diff --git a/source/db2IterateForeignScan.c b/source/db2IterateForeignScan.c index 615381b..22c04c0 100644 --- a/source/db2IterateForeignScan.c +++ b/source/db2IterateForeignScan.c @@ -14,7 +14,7 @@ extern void db2PrepareQuery (DB2Session* session, const char * extern int db2ExecuteQuery (DB2Session* session, ParamDesc* paramList); extern int db2FetchNext (DB2Session* session); extern void db2CloseStatement (DB2Session* session); -extern void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls, bool trunc_lob) ; +extern void convertTuple (DB2Session* session, DB2Table* db2Table, DB2ResultColumn* reslist, int natts, Datum* values, bool* nulls, bool trunc_lob); extern char* deparseDate (Datum datum); extern char* deparseTimestamp (Datum datum, bool hasTimezone); @@ -55,7 +55,7 @@ TupleTableSlot* db2IterateForeignScan (ForeignScanState* node) { ++fdw_state->rowcount; /* convert result to arrays of values and null indicators */ db2Debug2("slot->tts_tupleDescriptor->natts: %d",slot->tts_tupleDescriptor->natts); - convertTuple (fdw_state, slot->tts_tupleDescriptor->natts, slot->tts_values, slot->tts_isnull, false); + convertTuple (fdw_state->session,fdw_state->db2Table,fdw_state->resultList, slot->tts_tupleDescriptor->natts, slot->tts_values, slot->tts_isnull, false); /* store the virtual tuple */ ExecStoreVirtualTuple (slot); } else { diff --git a/source/db2_fdw_utils.c b/source/db2_fdw_utils.c index f44692e..4cd65bf 100644 --- a/source/db2_fdw_utils.c +++ b/source/db2_fdw_utils.c @@ -46,7 +46,7 @@ extern void db2free (void* p); bool optionIsTrue (const char *value); char* guessNlsLang (char* nls_lang); void exitHook (int code, Datum arg); -void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls, bool trunc_lob) ; +void convertTuple (DB2Session* session, DB2Table* db2Table, DB2ResultColumn* reslist, int natts, Datum* values, bool* nulls, bool trunc_lob); void reset_transmission_modes (int nestlevel); int set_transmission_modes (void); bool is_builtin (Oid objectId); @@ -197,11 +197,10 @@ void exitHook (int code, Datum arg) { * Convert a result row from DB2 stored in db2Table into arrays of values and null indicators. * If trunc_lob it true, truncate LOBs to WIDTH_THRESHOLD+1 bytes. */ -void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls, bool trunc_lob) { +void convertTuple (DB2Session* session, DB2Table* db2Table, DB2ResultColumn* reslist, int natts, Datum* values, bool* nulls, bool trunc_lob) { char* value = NULL; long value_len = 0; int j = 0; - DB2Table* db2Table = fdw_state->db2Table; DB2ResultColumn* res = NULL; bool isSimpleSelect = false; @@ -210,7 +209,7 @@ void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls db2Debug5("truncate lob: %s", trunc_lob ? "true": "false"); /* assign result values */ - isSimpleSelect = (natts == db2Table->npgcols); + isSimpleSelect = (db2Table && natts == db2Table->npgcols); db2Debug5("isSimpleSelect: %s", isSimpleSelect ? "true": "false"); // initialize all columns to NULL @@ -219,7 +218,7 @@ void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls values[j] = PointerGetDatum (NULL); } - for (res = fdw_state->resultList; res; res = res->next) { + for (res = reslist; res; res = res->next) { j = ((isSimpleSelect) ? res->pgattnum : res->resnum) - 1; db2Debug5("start processing column %d of %d: values index = %d", res->resnum, natts, j); db2Debug5("res->pgname : %s" ,res->pgname ); @@ -242,7 +241,7 @@ void convertTuple (DB2FdwState* fdw_state, int natts, Datum* values, bool* nulls db2Debug5("DB2_BLOB or DB2CLOB"); /* for LOBs, get the actual LOB contents (allocated), truncated if desired */ /* the column index is 1 based, whereas index id 0 based, so always add 1 to index when calling db2GetLob, since it does a column based access*/ - db2GetLob (fdw_state->session, res, &value, &value_len, trunc_lob ? (WIDTH_THRESHOLD + 1) : 0); + db2GetLob (session, res, &value, &value_len, trunc_lob ? (WIDTH_THRESHOLD + 1) : 0); } break; case DB2_LONGVARBINARY: { From 6cc7c0567555c51a231473614ae120d25376409f Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Mon, 2 Mar 2026 11:34:24 +0100 Subject: [PATCH 092/120] added proper return of affected rows --- source/db2IterateDirectModify.c | 57 ++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/source/db2IterateDirectModify.c b/source/db2IterateDirectModify.c index 954619f..51cc428 100644 --- a/source/db2IterateDirectModify.c +++ b/source/db2IterateDirectModify.c @@ -1,4 +1,6 @@ #include +#include +#include #include "db2_fdw.h" #include "DB2FdwDirectModifyState.h" @@ -10,50 +12,53 @@ extern int db2FetchNext (DB2Session* session); extern void db2CloseStatement (DB2Session* session); /** local prototypes */ -TupleTableSlot* db2IterateDirectModify(ForeignScanState *node); + TupleTableSlot* db2IterateDirectModify(ForeignScanState *node); -/* postgresIterateDirectModify + /* postgresIterateDirectModify * Execute a direct foreign table modification */ TupleTableSlot* db2IterateDirectModify(ForeignScanState *node) { DB2FdwDirectModifyState* dmstate = (DB2FdwDirectModifyState*) node->fdw_state; -// EState* estate = node->ss.ps.state; + EState* estate = node->ss.ps.state; + TupleTableSlot* slot = node->ss.ss_ScanTupleSlot; // ResultRelInfo* rtinfo = node->resultRelInfo; int have_result = 0; - TupleTableSlot* slot = NULL; + MemoryContext oldcontext; // nachfolgende Werte in ParamDesc Liste zusammenfassen - //int numParams = dmstate->numParams; - //const char** values = dmstate->param_values; - //FmgrInfo* param_flinfo = dmstate->param_flinfo; - //List* param_exps = dmstate->param_exprs; +// int numParams = dmstate->numParams; +// const char** values = dmstate->param_values; +// FmgrInfo* param_flinfo = dmstate->param_flinfo; +// List* param_exps = dmstate->param_exprs; db2Entry1(); // We should have a valid session. // It was created during db2GetFdwDirectModifyState during db2BeginDirectModify + MemoryContextReset (dmstate->temp_cxt); + oldcontext = MemoryContextSwitchTo (dmstate->temp_cxt); + db2PrepareQuery(dmstate->session, dmstate->query, NULL, dmstate->prefetch, dmstate->fetch_size); + dmstate->num_tuples = db2ExecuteQuery (dmstate->session, dmstate->paramList); + db2Debug2(" %d rows affected by this query", have_result); + MemoryContextSwitchTo (oldcontext); - if (db2IsStatementOpen(dmstate->session)) { + slot = ExecClearTuple (slot); + if (dmstate->num_tuples) { + Instrumentation* instr = node->ss.ps.instrument; - } else { - db2PrepareQuery(dmstate->session, dmstate->query, NULL, dmstate->prefetch, dmstate->fetch_size); - have_result = db2ExecuteQuery (dmstate->session, dmstate->paramList); - db2Debug2(" %d rows affected by this query", have_result); - } + /* Increment the command es_processed count if necessary. */ + if (dmstate->set_processed) + estate->es_processed += dmstate->num_tuples; + + /* Increment the tuple count for EXPLAIN ANALYZE if necessary. */ + if (instr) + instr->tuplecount += dmstate->num_tuples; - /* initialize virtual tuple */ -// ExecClearTuple (slot); -// if (have_result) { - /* increase row count */ -// ++dmstate->rowcount; - /* convert result to arrays of values and null indicators */ -// db2Debug2("slot->tts_tupleDescriptor->natts: %d",slot->tts_tupleDescriptor->natts); -// convertTuple (dmstate, slot->tts_tupleDescriptor->natts, slot->tts_values, slot->tts_isnull, false); - /* store the virtual tuple */ -// ExecStoreVirtualTuple (slot); -// } else { + /* initialize virtual tuple */ + } else { /* close the statement */ db2CloseStatement (dmstate->session); -// } + } + db2Exit1(": %x", slot); return slot; } From 0bc7c2147ca391adbcbe7ced2e5fae0c0f1a23af Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Thu, 5 Mar 2026 18:33:25 +0100 Subject: [PATCH 093/120] fail on error when deployed to an too old pg version (<13) --- source/db2_fdw.c | 66 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 44 insertions(+), 22 deletions(-) diff --git a/source/db2_fdw.c b/source/db2_fdw.c index 46fff32..1450ee6 100644 --- a/source/db2_fdw.c +++ b/source/db2_fdw.c @@ -137,11 +137,10 @@ extern char* guessNlsLang (char* nls_lang); extern void exitHook (int code, Datum arg); -/** Foreign-data wrapper handler function: return a struct with pointers - * to callback routines. +/* Foreign-data wrapper handler function: return a struct with pointers to callback routines. */ PGDLLEXPORT Datum db2_fdw_handler (PG_FUNCTION_ARGS) { - FdwRoutine *fdwroutine = makeNode (FdwRoutine); + FdwRoutine* fdwroutine = makeNode (FdwRoutine); fdwroutine->GetForeignRelSize = db2GetForeignRelSize; fdwroutine->GetForeignPaths = db2GetForeignPaths; @@ -181,12 +180,9 @@ PGDLLEXPORT Datum db2_fdw_handler (PG_FUNCTION_ARGS) { PG_RETURN_POINTER (fdwroutine); } -/** db2_fdw_validator - * Validate the generic options given to a FOREIGN DATA WRAPPER, SERVER, - * USER MAPPING or FOREIGN TABLE that uses db2_fdw. - * - * Raise an ERROR if the option or its value are considered invalid - * or a required option is missing. +/* db2_fdw_validator + * Validate the generic options given to a FOREIGN DATA WRAPPER, SERVER, USER MAPPING or FOREIGN TABLE that uses db2_fdw. + * Raise an ERROR if the option or its value are considered invalid or a required option is missing. */ PGDLLEXPORT Datum db2_fdw_validator (PG_FUNCTION_ARGS) { List* options_list = untransformRelOptions (PG_GETARG_DATUM (0)); @@ -382,8 +378,8 @@ PGDLLEXPORT Datum db2_fdw_validator (PG_FUNCTION_ARGS) { PG_RETURN_VOID (); } -/** db2_close_connections - * Close all open DB2 connections. +/* db2_close_connections + * Close all open DB2 connections. */ PGDLLEXPORT Datum db2_close_connections (PG_FUNCTION_ARGS) { if (dml_in_transaction) @@ -398,19 +394,19 @@ PGDLLEXPORT Datum db2_close_connections (PG_FUNCTION_ARGS) { PG_RETURN_VOID (); } -/** db2_diag - * Get the DB2 client version. - * If a non-NULL argument is supplied, it must be a foreign server name. - * In this case, the remote server version is returned as well. +/* db2_diag + * Get the DB2 client version. + * If a non-NULL argument is supplied, it must be a foreign server name. + * In this case, the remote server version is returned as well. */ PGDLLEXPORT Datum db2_diag (PG_FUNCTION_ARGS) { Oid srvId = InvalidOid; char* pgversion = NULL; StringInfoData version; - /** Get the PostgreSQL server version. + /* Get the PostgreSQL server version. * We cannot use PG_VERSION because that would give the version against which - * db2xa_fdw was compiled, not the version it is running with. + * db2_fdw was compiled, not the version it is running with. */ pgversion = GetConfigOptionByName ("server_version", NULL); @@ -491,11 +487,37 @@ PGDLLEXPORT Datum db2_diag (PG_FUNCTION_ARGS) { PG_RETURN_TEXT_P (cstring_to_text (version.data)); } -/** _PG_init - * Library load-time initalization. - * Sets exitHook() callback for backend shutdown. +/* _PG_init + * Library load-time initalization. + * Sets exitHook() callback for backend shutdown. */ void _PG_init (void) { - /* register an exit hook */ - on_proc_exit (&exitHook, PointerGetDatum (NULL)); + char* pgversion = NULL; + int min_pgversion = PG_SUPPORTED_MIN_VERSION / 10000; + int i_pgversion = min_pgversion; + + /* Get the PostgreSQL server version. + * We cannot use PG_VERSION because that would give the version against which + * db2_fdw was compiled, not the version it is running with. + */ + pgversion = GetConfigOptionByName ("server_version", NULL); + if (pgversion != NULL) { + char majorversion[3]; + majorversion[0] = pgversion[0]; + majorversion[1] = pgversion[1]; + majorversion[2] = '\0'; + i_pgversion = atoi(majorversion); + } + + if (i_pgversion >= min_pgversion) { + /* register an exit hook */ + on_proc_exit (&exitHook, PointerGetDatum (NULL)); + } else { + ereport (ERROR + , ( errcode(ERRCODE_INSUFFICIENT_RESOURCES) + , errmsg ("You are running a PG version %s which is not supported.", pgversion) + , errhint("You need at least PG version %d or higher.", min_pgversion) + ) + ); + } } From f1d2f65f67810353c2741e06cb5a4ec4fe5f23b3 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Thu, 5 Mar 2026 18:34:19 +0100 Subject: [PATCH 094/120] introduced PG_SUPPORTED_MIN_VERSION --- include/db2_fdw.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/db2_fdw.h b/include/db2_fdw.h index 1fd738c..4e4d0a5 100644 --- a/include/db2_fdw.h +++ b/include/db2_fdw.h @@ -14,13 +14,14 @@ #include #include #include +#define PG_SUPPORTED_MIN_VERSION 130000 #if PG_VERSION_NUM >= 150000 #define STRVAL(arg) ((String *)(arg))->sval #else #define STRVAL(arg) ((Value*)(arg))->val.str #endif -#if PG_VERSION_NUM < 130000 +#if PG_VERSION_NUM < PG_SUPPORTED_MIN_VERSION #error "This extension requires PostgreSQL version 13.0 or higher." #endif From 180fa63de6fb136a3fe25644432ddf6e768562dc Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Thu, 5 Mar 2026 18:34:48 +0100 Subject: [PATCH 095/120] ensure the code compiles for PG 13 thru 18 --- source/db2AddForeignUpdateTargets.c | 2 +- source/db2AnalyzeForeignTable.c | 7 ++- source/db2BeginDirectModify.c | 12 ++++ source/db2GetFdwState.c | 3 + source/db2GetForeignPlan.c | 5 ++ source/db2GetForeignUpperPaths.c | 81 ++++++++++++++++++++++++++- source/db2PlanDirectModify.c | 86 +++++++++++++++++++++++++++-- source/db2PrepareQuery.c | 2 +- source/db2_deparse.c | 43 +++++++++++++-- 9 files changed, 225 insertions(+), 16 deletions(-) diff --git a/source/db2AddForeignUpdateTargets.c b/source/db2AddForeignUpdateTargets.c index 4676a93..b400cac 100644 --- a/source/db2AddForeignUpdateTargets.c +++ b/source/db2AddForeignUpdateTargets.c @@ -65,7 +65,7 @@ void db2AddForeignUpdateTargets (PlannerInfo* root, Index rtindex,RangeTblEntry* /* Wrap it in a resjunk TLE with the right name ... */ tle = makeTargetEntry( (Expr*)var , list_length(parsetree->targetList) + 1 - , key_col_name + , NameStr(att->attname) , true ); /* ... and add it to the query's targetlist */ diff --git a/source/db2AnalyzeForeignTable.c b/source/db2AnalyzeForeignTable.c index f2abf93..2930cb3 100644 --- a/source/db2AnalyzeForeignTable.c +++ b/source/db2AnalyzeForeignTable.c @@ -1,8 +1,11 @@ #include +#include +#if PG_VERSION_NUM < 140000 +#include +#endif #include -#include #include -#include +#include #include "db2_fdw.h" #include "DB2FdwState.h" diff --git a/source/db2BeginDirectModify.c b/source/db2BeginDirectModify.c index 63eae9c..4a744d7 100644 --- a/source/db2BeginDirectModify.c +++ b/source/db2BeginDirectModify.c @@ -31,7 +31,11 @@ void db2BeginDirectModify(ForeignScanState* node, int eflags) { /* Do nothing in EXPLAIN (no ANALYZE) case. node->fdw_state stays NULL. */ if (!(eflags & EXEC_FLAG_EXPLAIN_ONLY)) { /* Get info about foreign table. */ + #if PG_VERSION_NUM < 140000 + rtindex = estate->es_result_relation_info->ri_RangeTableIndex; + #else rtindex = node->resultRelInfo->ri_RangeTableIndex; + #endif foreigntable = (fsplan->scan.scanrelid == 0) ? ExecOpenScanRelation(estate, rtindex, eflags) : node->ss.ss_currentRelation; /* We'll save private state in node->fdw_state. */ dmstate = db2GetFdwDirectModifyState(foreigntable->rd_id, NULL, false); @@ -56,11 +60,19 @@ void db2BeginDirectModify(ForeignScanState* node, int eflags) { /* Get private info created by planner functions. */ dmstate->query = strVal(list_nth (fsplan->fdw_private, FdwDirectModifyPrivateUpdateSql)); db2Debug2("dmstate->query: %s",dmstate->query); + #if PG_VERSION_NUM < 150000 + dmstate->has_returning = intVal(list_nth(fsplan->fdw_private, FdwDirectModifyPrivateHasReturning)); + #else dmstate->has_returning = boolVal(list_nth(fsplan->fdw_private, FdwDirectModifyPrivateHasReturning)); + #endif db2Debug2("dmstate->has_returning: %s",dmstate->has_returning? "true" : "false"); dmstate->retrieved_attrs = (List*) list_nth(fsplan->fdw_private, FdwDirectModifyPrivateRetrievedAttrs); db2Debug2("dmstate->retrieved_attrs: %x - %d", dmstate->retrieved_attrs, list_length(dmstate->retrieved_attrs)); + #if PG_VERSION_NUM < 150000 + dmstate->set_processed = intVal(list_nth(fsplan->fdw_private, FdwDirectModifyPrivateSetProcessed)); + #else dmstate->set_processed = boolVal(list_nth(fsplan->fdw_private, FdwDirectModifyPrivateSetProcessed)); + #endif db2Debug2("dmstate->set_processed: %s", dmstate->set_processed ? "true" : "false"); /* Create context for per-tuple temp workspace. */ diff --git a/source/db2GetFdwState.c b/source/db2GetFdwState.c index e43f0bd..2ed0404 100644 --- a/source/db2GetFdwState.c +++ b/source/db2GetFdwState.c @@ -1,6 +1,9 @@ #include #include #include +#if PG_VERSION_NUM < 140000 +#include +#endif #include #include #include diff --git a/source/db2GetForeignPlan.c b/source/db2GetForeignPlan.c index cf78192..7349bd7 100644 --- a/source/db2GetForeignPlan.c +++ b/source/db2GetForeignPlan.c @@ -61,8 +61,13 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo db2Entry1(); /* Get FDW private data created by db2GetForeignUpperPaths(), if any. */ if (best_path->fdw_private) { + #if PG_VERSION_NUM < 150000 + has_final_sort = intVal(list_nth(best_path->fdw_private, FdwPathPrivateHasFinalSort)); + has_limit = intVal(list_nth(best_path->fdw_private, FdwPathPrivateHasLimit)); + #else has_final_sort = boolVal(list_nth(best_path->fdw_private, FdwPathPrivateHasFinalSort)); has_limit = boolVal(list_nth(best_path->fdw_private, FdwPathPrivateHasLimit)); + #endif } db2Debug2("length of tlist: %d", list_length(tlist)); diff --git a/source/db2GetForeignUpperPaths.c b/source/db2GetForeignUpperPaths.c index d040ecd..16e958b 100644 --- a/source/db2GetForeignUpperPaths.c +++ b/source/db2GetForeignUpperPaths.c @@ -17,6 +17,11 @@ #include "DB2FdwState.h" #include "DB2FdwPathExtraData.h" +#if PG_VERSION_NUM < 140000 +/* source-code-compatibility hacks for pull_varnos() API change */ +#define make_restrictinfo(a,b,c,d,e,f,g,h,i) make_restrictinfo_new(a,b,c,d,e,f,g,h,i) +#endif + /** external prototypes */ extern void* db2alloc (const char* type, size_t size); extern char* db2strdup (const char* source); @@ -300,12 +305,16 @@ static void add_foreign_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel, , grouped_rel , grouped_rel->reltarget , rows + #if PG_VERSION_NUM >= 180000 , disabled_nodes + #endif , startup_cost , total_cost , NIL /* no pathkeys */ , NULL + #if PG_VERSION_NUM >= 170000 , NIL /* no fdw_restrictinfo list */ + #endif , NIL); /* no fdw_private */ /* Add generated path into grouped_rel by add_path(). */ add_path(grouped_rel, (Path*) grouppath); @@ -403,18 +412,26 @@ static void add_foreign_ordered_paths(PlannerInfo *root, RelOptInfo *input_rel, * Build the fdw_private list that will be used by postgresGetForeignPlan. * Items in the list must match order in enum FdwPathPrivateIndex. */ + #if PG_VERSION_NUM < 150000 + fdw_private = list_make2(makeInteger(true), makeInteger(false)); + #else fdw_private = list_make2(makeBoolean(true), makeBoolean(false)); + #endif /* Create foreign ordering path */ ordered_path = create_foreign_upper_path( root , input_rel , root->upper_targets[UPPERREL_ORDERED] , rows + #if PG_VERSION_NUM >= 180000 , disabled_nodes + #endif , startup_cost , total_cost , root->sort_pathkeys , NULL /* no extra plan */ + #if PG_VERSION_NUM >= 170000 , NIL /* no fdw_restrictinfo list */ + #endif , fdw_private); /* and add it to the ordered_rel */ add_path(ordered_rel, (Path *) ordered_path); @@ -504,12 +521,16 @@ static void add_foreign_final_paths(PlannerInfo *root, RelOptInfo *input_rel, Re , path->parent , path->pathtarget , path->rows + #if PG_VERSION_NUM >= 180000 , path->disabled_nodes + #endif , path->startup_cost , path->total_cost , path->pathkeys , NULL /* no extra plan */ + #if PG_VERSION_NUM >= 170000 , NIL /* no fdw_restrictinfo list */ + #endif , NIL); /* no fdw_private */ /* and add it to the final_rel */ @@ -603,19 +624,27 @@ static void add_foreign_final_paths(PlannerInfo *root, RelOptInfo *input_rel, Re ifpinfo->use_remote_estimate = save_use_remote_estimate; /* Build the fdw_private list that will be used by postgresGetForeignPlan. Items in the list must match order in enum FdwPathPrivateIndex. */ + #if PG_VERSION_NUM < 150000 + fdw_private = list_make2(makeInteger(has_final_sort), makeInteger(extra->limit_needed)); + #else fdw_private = list_make2(makeBoolean(has_final_sort), makeBoolean(extra->limit_needed)); + #endif /* Create foreign final path; this gets rid of a no-longer-needed outer plan (if any), which makes the EXPLAIN output look cleaner */ final_path = create_foreign_upper_path( root , input_rel , root->upper_targets[UPPERREL_FINAL] , rows + #if PG_VERSION_NUM >= 180000 , disabled_nodes + #endif , startup_cost , total_cost , pathkeys , NULL /* no extra plan */ + #if PG_VERSION_NUM >= 170000 , NIL /* no fdw_restrictinfo list */ + #endif , fdw_private); /* and add it to the final_rel */ @@ -631,10 +660,26 @@ static void adjust_foreign_grouping_path_cost(PlannerInfo* root, List* pathkeys, * Likewise, if the GROUP BY clause is sort-able but isn't a superset of the given pathkeys, adjust the costs with that function. * Otherwise, adjust the costs by applying the same heuristic as for the scan or join case. */ + #if PG_VERSION_NUM < 160000 + if (!grouping_is_sortable(root->parse->groupClause) || !pathkeys_contained_in(pathkeys, root->group_pathkeys)) { + #else if (!grouping_is_sortable(root->processed_groupClause) || !pathkeys_contained_in(pathkeys, root->group_pathkeys)) { + #endif Path sort_path; /* dummy for result of cost_sort */ - cost_sort(&sort_path, root, pathkeys, 0, *p_startup_cost + *p_run_cost, retrieved_rows, width, 0.0, work_mem, limit_tuples); + cost_sort( &sort_path + , root + , pathkeys + #if PG_VERSION_NUM >= 180000 + , 0 + #endif + , *p_startup_cost + *p_run_cost + , retrieved_rows + , width + , 0.0 + , work_mem + , limit_tuples + ); *p_startup_cost = sort_path.startup_cost; *p_run_cost = sort_path.total_cost - sort_path.startup_cost; @@ -768,7 +813,11 @@ static bool foreign_grouping_ok(PlannerInfo* root, RelOptInfo* grouped_rel, Node /* Currently, the core code doesn't wrap havingQuals in RestrictInfos, so we must make our own. */ Assert(!IsA(expr, RestrictInfo)); + #if PG_VERSION_NUM < 160000 + rinfo = make_restrictinfo(root, expr, true, false, false, root->qual_security_level, grouped_rel->relids, NULL, NULL); + #else rinfo = make_restrictinfo(root, expr, true, false, false, false, root->qual_security_level, grouped_rel->relids, NULL, NULL); + #endif if (is_foreign_expr(root, grouped_rel, expr)) fpinfo->remote_conds = lappend(fpinfo->remote_conds, rinfo); else @@ -1019,10 +1068,39 @@ void estimate_path_cost_size(PlannerInfo* root, RelOptInfo* foreignrel, List* pa /* Get rows from input rel */ input_rows = ofpinfo->rows; /* Collect statistics about aggregates for estimating costs. */ + #if PG_VERSION_NUM < 140000 + MemSet(&aggcosts, 0, sizeof(AggClauseCosts)); + if (root->parse->hasAggs) { + get_agg_clause_costs(root, (Node *) fpinfo->grouped_tlist, AGGSPLIT_SIMPLE, &aggcosts); + /* The cost of aggregates in the HAVING qual will be the same for each child as it is for the parent, so there's no need + * to use a translated version of havingQual. + */ + get_agg_clause_costs(root, (Node *) root->parse->havingQual, AGGSPLIT_SIMPLE, &aggcosts); + } + #else if (root->parse->hasAggs) { get_agg_clause_costs(root, AGGSPLIT_SIMPLE, &aggcosts); } + #endif /* Get number of grouping columns and possible number of groups */ + #if PG_VERSION_NUM < 160000 + numGroupCols = list_length(root->parse->groupClause); + #if PG_VERSION_NUM < 140000 + numGroups = estimate_num_groups( root + , get_sortgrouplist_exprs( root->parse->groupClause + , fpinfo->grouped_tlist + ) + , input_rows, NULL + ); + #else + numGroups = estimate_num_groups( root + , get_sortgrouplist_exprs( root->parse->groupClause + , fpinfo->grouped_tlist + ) + , input_rows, NULL, NULL + ); + #endif + #else numGroupCols = list_length(root->processed_groupClause); numGroups = estimate_num_groups( root , get_sortgrouplist_exprs( root->processed_groupClause @@ -1030,6 +1108,7 @@ void estimate_path_cost_size(PlannerInfo* root, RelOptInfo* foreignrel, List* pa ) , input_rows, NULL, NULL ); + #endif /* Get the retrieved_rows and rows estimates. If there are HAVING quals, account for their selectivity. */ if (root->hasHavingQual) { diff --git a/source/db2PlanDirectModify.c b/source/db2PlanDirectModify.c index ffdd0ec..0a64032 100644 --- a/source/db2PlanDirectModify.c +++ b/source/db2PlanDirectModify.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include "db2_fdw.h" #include "DB2FdwState.h" @@ -16,8 +17,11 @@ extern void deparseDirectDeleteSql (StringInfo buf, PlannerInfo *root /** local prototypes */ bool db2PlanDirectModify (PlannerInfo* root, ModifyTable* plan, Index rtindex, int subplan_index); +#if PG_VERSION_NUM >= 140000 static ForeignScan* find_modifytable_subplan (PlannerInfo* root, ModifyTable* plan, Index rtindex, int subplan_index); - +#else +static void rebuild_fdw_scan_tlist (ForeignScan *fscan, List *tlist); +#endif /* postgresPlanDirectModify * Decide whether it is safe to modify a foreign table directly, and if so, rewrite subplan accordingly. */ @@ -29,10 +33,16 @@ bool db2PlanDirectModify(PlannerInfo* root, ModifyTable* plan, Index rtindex, in db2Debug2("plan->returningLists: %x - %d", plan->returningLists, list_length(plan->returningLists)); /* The table modification must be an UPDATE or DELETE and must not use RETURNING */ if ((plan->operation == CMD_UPDATE || plan->operation == CMD_DELETE) && plan->returningLists == NIL) { + #if PG_VERSION_NUM < 140000 + Plan* subplan = (Plan *) list_nth(plan->plans, subplan_index); + if (IsA(subplan, ForeignScan)) { + ForeignScan*fscan = (ForeignScan *) subplan; + #else /* Try to locate the ForeignScan subplan that's scanning rtindex. */ ForeignScan* fscan = find_modifytable_subplan(root, plan, rtindex, subplan_index); db2Debug2("fscan: %x",fscan); if (fscan) { + #endif /* It's unsafe to modify a foreign table directly if there are any quals that should be evaluated locally. */ db2Debug2("fscan->scan.plan.qual: %x",fscan->scan.plan.qual); if (fscan->scan.plan.qual == NIL) { @@ -58,6 +68,31 @@ bool db2PlanDirectModify(PlannerInfo* root, ModifyTable* plan, Index rtindex, in * if any expressions to assign to the target columns are unsafe to evaluate remotely. */ if (plan->operation == CMD_UPDATE) { + #if PG_VERSION_NUM < 140000 + int col; + + /* We transmit only columns that were explicitly targets of the UPDATE, so as to avoid unnecessary data transmission. */ + col = -1; + while ((col = bms_next_member(rte->updatedCols, col)) >= 0) { + /* bit numbers are offset by FirstLowInvalidHeapAttributeNumber */ + AttrNumber attno = col + FirstLowInvalidHeapAttributeNumber; + TargetEntry *tle; + + if (attno <= InvalidAttrNumber) /* shouldn't happen */ + elog(ERROR, "system-column update is not supported"); + + tle = get_tle_by_resno(subplan->targetlist, attno); + + if (!tle) + elog(ERROR, "attribute number %d not found in subplan targetlist", attno); + + if (!is_foreign_expr(root, foreignrel, (Expr *) tle->expr)) { + fResult = false; + break; + } + targetAttrs = lappend_int(targetAttrs, attno); + } + #else ListCell* lc = NULL; ListCell* lc2 = NULL; @@ -77,6 +112,7 @@ bool db2PlanDirectModify(PlannerInfo* root, ModifyTable* plan, Index rtindex, in break; } } + #endif } db2Debug2("fResult: %s",(fResult) ? "true" : "false"); if (fResult) { @@ -124,26 +160,36 @@ bool db2PlanDirectModify(PlannerInfo* root, ModifyTable* plan, Index rtindex, in /* Update the operation and target relation info. */ fscan->operation = plan->operation; + #if PG_VERSION_NUM >= 140000 fscan->resultRelation = rtindex; - + #endif /* Update the fdw_exprs list that will be available to the executor. */ fscan->fdw_exprs = params_list; /* Update the fdw_private list that will be available to the executor. * Items in the list must match enum FdwDirectModifyPrivateIndex, above. */ + #if PG_VERSION_NUM < 150000 + fscan->fdw_private = list_make4(makeString(sql.data), makeInteger((retrieved_attrs != NIL)), retrieved_attrs, makeInteger(plan->canSetTag)); + #else fscan->fdw_private = list_make4(makeString(sql.data), makeBoolean((retrieved_attrs != NIL)), retrieved_attrs, makeBoolean(plan->canSetTag)); + #endif /* Update the foreign-join-related fields. */ if (fscan->scan.scanrelid == 0) { /* No need for the outer subplan. */ fscan->scan.plan.lefttree = NULL; + #if PG_VERSION_NUM < 140000 + /* Build new fdw_scan_tlist if UPDATE/DELETE .. RETURNING. */ + if (returningList) + rebuild_fdw_scan_tlist(fscan, returningList); } - - /* Finally, unset the async-capable flag if it is set, as we currently don't support asynchronous execution of direct modifications. */ + #else + } + /* Finally, unset the async-capable flag if it is set, as we currently don't support asynchronous execution of direct modifications. */ if (fscan->scan.plan.async_capable) fscan->scan.plan.async_capable = false; - + #endif table_close(rel, NoLock); } } else { @@ -159,6 +205,7 @@ bool db2PlanDirectModify(PlannerInfo* root, ModifyTable* plan, Index rtindex, in return fResult; } +#if PG_VERSION_NUM >= 140000 /* find_modifytable_subplan * Helper routine for postgresPlanDirectModify to find the ModifyTable subplan node that scans the specified RTI. * @@ -200,10 +247,39 @@ static ForeignScan* find_modifytable_subplan(PlannerInfo* root, ModifyTable* pla /* Now, have we got a ForeignScan on the desired rel? */ db2Debug3("subplan: %x",subplan); + #if PG_VERSION_NUM < 160000 + if (IsA(subplan, ForeignScan) && (bms_is_member(rtindex, ((ForeignScan*) subplan)->fs_relids))) { + #else if (IsA(subplan, ForeignScan) && (bms_is_member(rtindex, ((ForeignScan*) subplan)->fs_base_relids))) { + #endif db2Debug4("subplan is ForeignScan"); fscan = (ForeignScan*) subplan; } db2Exit1(": %x", fscan); return fscan; } +#else +/* rebuild_fdw_scan_tlist + * Build new fdw_scan_tlist of given foreign-scan plan node from given tlist + * + * There might be columns that the fdw_scan_tlist of the given foreign-scan plan node contains that the given tlist doesn't. + * The fdw_scan_tlist would have contained resjunk columns such as 'ctid' of the target relation and 'wholerow' of non-target relations, + * but the tlist might not contain them, for example. + * So, adjust the tlist so it contains all the columns specified in the fdw_scan_tlist; else setrefs.c will get confused. + */ +static void rebuild_fdw_scan_tlist(ForeignScan *fscan, List *tlist) { + List* new_tlist = tlist; + List* old_tlist = fscan->fdw_scan_tlist; + ListCell* lc; + + foreach(lc, old_tlist) { + TargetEntry *tle = (TargetEntry *) lfirst(lc); + + if (tlist_member(tle->expr, new_tlist)) + continue; /* already got it */ + + new_tlist = lappend(new_tlist, makeTargetEntry(tle->expr, list_length(new_tlist) + 1, NULL, false)); + } + fscan->fdw_scan_tlist = new_tlist; +} +#endif \ No newline at end of file diff --git a/source/db2PrepareQuery.c b/source/db2PrepareQuery.c index a3ead8e..fef5b39 100644 --- a/source/db2PrepareQuery.c +++ b/source/db2PrepareQuery.c @@ -5,7 +5,7 @@ #include "ParamDesc.h" #include "DB2ResultColumn.h" -#define SQL_VALUE_PTR_ULEN(v) ((SQLPOINTER)(uintptr_t)(SQLULEN)(v)) +#define SQL_VALUE_PTR_ULEN(v) ((SQLPOINTER)(u_int64_t)(SQLULEN)(v)) /** global variables */ diff --git a/source/db2_deparse.c b/source/db2_deparse.c index 1070c24..5fb7937 100644 --- a/source/db2_deparse.c +++ b/source/db2_deparse.c @@ -6,7 +6,6 @@ #include #include - #include #include #include @@ -962,9 +961,15 @@ static void appendOrderByClause(List* pathkeys, bool has_final_sort, deparse_exp /* Lookup the operator corresponding to the compare type in the opclass. * The datatype used by the opfamily is not necessarily the same as the expression type (for array types for example). */ + #if PG_VERSION_NUM < 180000 + oprid = get_opfamily_member(pathkey->pk_opfamily, em->em_datatype, em->em_datatype, pathkey->pk_strategy); + if (!OidIsValid(oprid)) + elog(ERROR, "missing operator %d(%u,%u) in opfamily %u", pathkey->pk_strategy, em->em_datatype, em->em_datatype, pathkey->pk_opfamily); + #else oprid = get_opfamily_member_for_cmptype(pathkey->pk_opfamily, em->em_datatype, em->em_datatype, pathkey->pk_cmptype); if (!OidIsValid(oprid)) elog(ERROR, "missing operator %d(%u,%u) in opfamily %u", pathkey->pk_cmptype, em->em_datatype, em->em_datatype, pathkey->pk_opfamily); + #endif deparseExprInt(em_expr, context); /* Here we need to use the expression's actual type to discover whether the desired operator will be the default or not. */ @@ -2099,11 +2104,10 @@ static void deparseParamExpr (Param* expr, deparse_expr_cxt* * Handle it the same way we handle plain Params --- see deparseParam for comments. */ static void deparseVar(Var* expr, deparse_expr_cxt* ctx) { - Relids relids = ctx->scanrel->relids; int relno = 0; int colno = 0; /* Qualify columns when multiple relations are involved. */ - bool qualify_col = (bms_membership(relids) == BMS_MULTIPLE); + bool qualify_col = (bms_membership( ctx->scanrel->relids) == BMS_MULTIPLE); bool is_query_var = false; db2Entry1(); @@ -2114,8 +2118,13 @@ static void deparseVar(Var* expr, deparse_expr_cxt* ctx) { appendStringInfo(ctx->buf, "%s%d.%s%d", SUBQUERY_REL_ALIAS_PREFIX, relno, SUBQUERY_COL_ALIAS_PREFIX, colno); return; } - is_query_var = bms_is_member(expr->varno, ctx->root->all_query_rels); + #if PG_VERSION_NUM < 160000 + is_query_var = bms_is_member(expr->varno, ctx->scanrel->relids); + db2Debug2("bms_is_member(%d,%d): %s",expr->varno, ctx->scanrel->relids, is_query_var ? "true":"false"); + #else + is_query_var = bms_is_member(expr->varno, ctx->root->all_query_rels); db2Debug2("bms_is_member(%d,%d): %s",expr->varno, ctx->root->all_query_rels, is_query_var ? "true":"false"); + #endif db2Debug2("expr->varlevelsup: %d",expr->varlevelsup); if (is_query_var && expr->varlevelsup == 0) { deparseColumnRef(ctx->buf, expr->varno, expr->varattno, planner_rt_fetch(expr->varno, ctx->root), qualify_col); @@ -3305,7 +3314,11 @@ static void deparseLockingClause(deparse_expr_cxt *context) { * * Note: because we actually run the query as a cursor, this assumes that DECLARE CURSOR ... FOR UPDATE is supported, which it isn't before 8.3. */ + #if PG_VERSION_NUM < 140000 + if (relid == root->parse->resultRelation && (root->parse->commandType == CMD_UPDATE || root->parse->commandType == CMD_DELETE)) { + #else if (bms_is_member(relid, root->all_result_relids) && (root->parse->commandType == CMD_UPDATE || root->parse->commandType == CMD_DELETE)) { + #endif /* Relation is UPDATE/DELETE target, so use FOR UPDATE */ appendStringInfoString(buf, " FOR UPDATE"); @@ -3391,9 +3404,17 @@ static void deparseTargetList(StringInfo buf, RangeTblEntry *rte, Index rtindex, first = true; for (i = 1; i <= tupdesc->natts; i++) { + #if PG_VERISON_NUM < 180000 + Form_pg_attribute attr = TupleDescAttr(tupdesc, i - 1); + + /* Ignore dropped attributes. */ + if (attr->attisdropped) + continue; + #else /* Ignore dropped attributes. */ if (TupleDescCompactAttr(tupdesc, i - 1)->attisdropped) continue; + #endif if (have_wholerow || bms_is_member(i - FirstLowInvalidHeapAttributeNumber, attrs_used)) { if (!first) @@ -3492,13 +3513,23 @@ static void deparseSubqueryTargetList(deparse_expr_cxt* context) { * ordering operator is shippable. */ EquivalenceMember* find_em_for_rel(PlannerInfo* root, EquivalenceClass* ec, RelOptInfo* rel) { - DB2FdwState* fpinfo = (DB2FdwState*) rel->fdw_private; + DB2FdwState* fpinfo = (DB2FdwState*) rel->fdw_private; + EquivalenceMember* em = NULL; + #if PG_VERSION_NUM < 180000 + ListCell* lc = NULL; + #else EquivalenceMemberIterator it; - EquivalenceMember* em = NULL; + #endif db2Entry1(); + + #if PG_VERSION_NUM < 180000 + foreach(lc, ec->ec_members) { + em = (EquivalenceMember *) lfirst(lc); + #else setup_eclass_member_iterator(&it, ec, rel->relids); while ((em = eclass_member_iterator_next(&it)) != NULL) { + #endif /* Note we require !bms_is_empty, else we'd accept constant expressions which are not suitable for the purpose. */ if (bms_is_subset(em->em_relids, rel->relids) && !bms_is_empty(em->em_relids) From a488d4412052dcf00434b85d0ba19b6aff40a9cc Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Sat, 7 Mar 2026 10:22:05 +0100 Subject: [PATCH 096/120] moving the includes for pure sqlcli functions to db2_fdw.h --- include/DB2FdwDirectModifyState.h | 1 - include/db2_fdw.h | 7 ++++++- source/db2AllocConnHdl.c | 2 -- source/db2AllocEnvHdl.c | 2 -- source/db2AllocStmtHdl.c | 2 -- source/db2BindParameter.c | 2 -- source/db2Cancel.c | 2 -- source/db2CheckErr.c | 2 -- source/db2ClientVersion.c | 2 -- source/db2CloseConnections.c | 2 -- source/db2CloseStatement.c | 2 -- source/db2CopyText.c | 2 -- source/db2Describe.c | 2 -- source/db2EndSubtransaction.c | 2 -- source/db2EndTransaction.c | 2 -- source/db2ExecuteInsert.c | 2 -- source/db2ExecuteQuery.c | 2 -- source/db2ExecuteTruncate.c | 2 -- source/db2FetchNext.c | 2 -- source/db2FreeEnvHdl.c | 2 -- source/db2FreeStmtHdl.c | 2 -- source/db2GetLob.c | 2 -- source/db2GetSession.c | 2 -- source/db2ImportForeignSchemaData.c | 2 -- source/db2IsStatementOpen.c | 2 -- source/db2PrepareQuery.c | 2 -- source/db2ServerVersion.c | 2 -- source/db2SetSavepoint.c | 2 -- source/db2Shutdown.c | 2 -- source/db2_utils.c | 2 -- 30 files changed, 6 insertions(+), 58 deletions(-) diff --git a/include/DB2FdwDirectModifyState.h b/include/DB2FdwDirectModifyState.h index c4b8d35..33e7798 100644 --- a/include/DB2FdwDirectModifyState.h +++ b/include/DB2FdwDirectModifyState.h @@ -7,7 +7,6 @@ #include "ParamDesc.h" #include "DB2ResultColumn.h" - // Execution state of a foreign scan that modifies a foreign table directly. typedef struct db2FdwDirectModifyState { // DB2 Section diff --git a/include/db2_fdw.h b/include/db2_fdw.h index 4e4d0a5..9856f5a 100644 --- a/include/db2_fdw.h +++ b/include/db2_fdw.h @@ -49,9 +49,14 @@ #ifndef JSONOID #define JSONOID InvalidOid #endif - +#else +#ifndef SQL_H_SQLCLI1 +#include +#include +#endif /* SQL_H_SQLCLI1 */ #endif /* POSTGRES_H */ + /* db2_fdw version */ #define DB2_FDW_VERSION "18.2.0" /* number of bytes to read per LOB chunk */ diff --git a/source/db2AllocConnHdl.c b/source/db2AllocConnHdl.c index af22c02..055a050 100644 --- a/source/db2AllocConnHdl.c +++ b/source/db2AllocConnHdl.c @@ -1,7 +1,5 @@ #include #include -#include -#include #include "db2_fdw.h" /** external variables */ diff --git a/source/db2AllocEnvHdl.c b/source/db2AllocEnvHdl.c index d70a306..bd1a006 100644 --- a/source/db2AllocEnvHdl.c +++ b/source/db2AllocEnvHdl.c @@ -1,6 +1,4 @@ #include -#include -#include #include "db2_fdw.h" /** global variables */ diff --git a/source/db2AllocStmtHdl.c b/source/db2AllocStmtHdl.c index 400e00b..036ce79 100644 --- a/source/db2AllocStmtHdl.c +++ b/source/db2AllocStmtHdl.c @@ -7,8 +7,6 @@ /* Windows doesn't have snprintf */ #define snprintf _snprintf #endif -#include -#include #include "db2_fdw.h" /** external variables */ diff --git a/source/db2BindParameter.c b/source/db2BindParameter.c index 75be289..8ccaa36 100644 --- a/source/db2BindParameter.c +++ b/source/db2BindParameter.c @@ -1,7 +1,5 @@ #include #include -#include -#include #include "db2_fdw.h" #include "ParamDesc.h" diff --git a/source/db2Cancel.c b/source/db2Cancel.c index c72115a..d63ec11 100644 --- a/source/db2Cancel.c +++ b/source/db2Cancel.c @@ -1,5 +1,3 @@ -#include -#include #include "db2_fdw.h" /** global variables */ diff --git a/source/db2CheckErr.c b/source/db2CheckErr.c index fbb2509..52a6936 100644 --- a/source/db2CheckErr.c +++ b/source/db2CheckErr.c @@ -1,7 +1,5 @@ #include #include -#include -#include #include "db2_fdw.h" /** global variables */ diff --git a/source/db2ClientVersion.c b/source/db2ClientVersion.c index a17272e..d5ea9ca 100644 --- a/source/db2ClientVersion.c +++ b/source/db2ClientVersion.c @@ -1,7 +1,5 @@ #include #include -#include -#include #include "db2_fdw.h" /** global variables */ diff --git a/source/db2CloseConnections.c b/source/db2CloseConnections.c index 3b59112..8106add 100644 --- a/source/db2CloseConnections.c +++ b/source/db2CloseConnections.c @@ -1,5 +1,3 @@ -#include -#include #include "db2_fdw.h" /** global variables */ diff --git a/source/db2CloseStatement.c b/source/db2CloseStatement.c index 6e52a98..edfe398 100644 --- a/source/db2CloseStatement.c +++ b/source/db2CloseStatement.c @@ -1,5 +1,3 @@ -#include -#include #include "db2_fdw.h" /** global variables */ diff --git a/source/db2CopyText.c b/source/db2CopyText.c index 38d368d..ca552aa 100644 --- a/source/db2CopyText.c +++ b/source/db2CopyText.c @@ -1,6 +1,4 @@ #include -#include -#include #include "db2_fdw.h" /** global variables */ diff --git a/source/db2Describe.c b/source/db2Describe.c index 3f7445f..6483dbf 100644 --- a/source/db2Describe.c +++ b/source/db2Describe.c @@ -1,7 +1,5 @@ #include #include -#include -#include #include #include "db2_fdw.h" diff --git a/source/db2EndSubtransaction.c b/source/db2EndSubtransaction.c index b252183..bd7ce4e 100644 --- a/source/db2EndSubtransaction.c +++ b/source/db2EndSubtransaction.c @@ -1,6 +1,4 @@ #include -#include -#include #include "db2_fdw.h" /** external variables */ diff --git a/source/db2EndTransaction.c b/source/db2EndTransaction.c index 8d5e9f5..fdc232c 100644 --- a/source/db2EndTransaction.c +++ b/source/db2EndTransaction.c @@ -1,5 +1,3 @@ -#include -#include #include "db2_fdw.h" /** external variables */ diff --git a/source/db2ExecuteInsert.c b/source/db2ExecuteInsert.c index df50546..f12b8c1 100644 --- a/source/db2ExecuteInsert.c +++ b/source/db2ExecuteInsert.c @@ -1,7 +1,5 @@ #include #include -#include -#include #include "db2_fdw.h" #include "ParamDesc.h" diff --git a/source/db2ExecuteQuery.c b/source/db2ExecuteQuery.c index 4a89ccf..7f2e1ef 100644 --- a/source/db2ExecuteQuery.c +++ b/source/db2ExecuteQuery.c @@ -1,8 +1,6 @@ #include #include #include -#include -#include #include "db2_fdw.h" #include "ParamDesc.h" diff --git a/source/db2ExecuteTruncate.c b/source/db2ExecuteTruncate.c index 8a1fd34..1d26879 100644 --- a/source/db2ExecuteTruncate.c +++ b/source/db2ExecuteTruncate.c @@ -1,6 +1,4 @@ #include -#include -#include #include "db2_fdw.h" #include "ParamDesc.h" diff --git a/source/db2FetchNext.c b/source/db2FetchNext.c index 1cb84ef..b8fd556 100644 --- a/source/db2FetchNext.c +++ b/source/db2FetchNext.c @@ -1,5 +1,3 @@ -#include -#include #include "db2_fdw.h" /** global variables */ diff --git a/source/db2FreeEnvHdl.c b/source/db2FreeEnvHdl.c index bd17e1b..7f69879 100644 --- a/source/db2FreeEnvHdl.c +++ b/source/db2FreeEnvHdl.c @@ -1,6 +1,4 @@ #include -#include -#include #include "db2_fdw.h" /** global variables */ diff --git a/source/db2FreeStmtHdl.c b/source/db2FreeStmtHdl.c index db6eb1e..14fa38f 100644 --- a/source/db2FreeStmtHdl.c +++ b/source/db2FreeStmtHdl.c @@ -1,5 +1,3 @@ -#include -#include #include "db2_fdw.h" /** external variables */ diff --git a/source/db2GetLob.c b/source/db2GetLob.c index c55b42e..c7aad96 100644 --- a/source/db2GetLob.c +++ b/source/db2GetLob.c @@ -1,6 +1,4 @@ #include -#include -#include #include "db2_fdw.h" #include "DB2ResultColumn.h" diff --git a/source/db2GetSession.c b/source/db2GetSession.c index c2453a1..fdc48a3 100644 --- a/source/db2GetSession.c +++ b/source/db2GetSession.c @@ -1,5 +1,3 @@ -#include -#include #include "db2_fdw.h" /** global variables */ diff --git a/source/db2ImportForeignSchemaData.c b/source/db2ImportForeignSchemaData.c index 6fcfe3e..24d7274 100644 --- a/source/db2ImportForeignSchemaData.c +++ b/source/db2ImportForeignSchemaData.c @@ -1,8 +1,6 @@ #include #include #include -#include -#include #include "db2_fdw.h" /** global variables */ diff --git a/source/db2IsStatementOpen.c b/source/db2IsStatementOpen.c index 842e0a6..5e68969 100644 --- a/source/db2IsStatementOpen.c +++ b/source/db2IsStatementOpen.c @@ -1,5 +1,3 @@ -#include -#include #include "db2_fdw.h" /** global variables */ diff --git a/source/db2PrepareQuery.c b/source/db2PrepareQuery.c index fef5b39..e168edf 100644 --- a/source/db2PrepareQuery.c +++ b/source/db2PrepareQuery.c @@ -1,6 +1,4 @@ #include -#include -#include #include "db2_fdw.h" #include "ParamDesc.h" #include "DB2ResultColumn.h" diff --git a/source/db2ServerVersion.c b/source/db2ServerVersion.c index a599885..de2d4a6 100644 --- a/source/db2ServerVersion.c +++ b/source/db2ServerVersion.c @@ -1,6 +1,4 @@ #include -#include -#include #include "db2_fdw.h" /** global variables */ diff --git a/source/db2SetSavepoint.c b/source/db2SetSavepoint.c index bf17cc2..26f2347 100644 --- a/source/db2SetSavepoint.c +++ b/source/db2SetSavepoint.c @@ -1,6 +1,4 @@ #include -#include -#include #include "db2_fdw.h" /** global variables */ diff --git a/source/db2Shutdown.c b/source/db2Shutdown.c index d590246..978c1cb 100644 --- a/source/db2Shutdown.c +++ b/source/db2Shutdown.c @@ -1,5 +1,3 @@ -#include -#include #include "db2_fdw.h" /** global variables */ diff --git a/source/db2_utils.c b/source/db2_utils.c index 1f0a467..bc4a61b 100644 --- a/source/db2_utils.c +++ b/source/db2_utils.c @@ -1,7 +1,5 @@ #include #include -#include -#include #include "db2_fdw.h" /** local prototypes */ From 08c9727bb5d9b5c1bbe46d5463e46b48fc229cbf Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Sat, 7 Mar 2026 11:07:54 +0100 Subject: [PATCH 097/120] adding db2Log, db2LogInfo, db2LogNotice, db2LogWarning and db2LogError functions to enhance debug tracing --- include/db2_fdw.h | 63 +++++++++++++++++++++++++++--------- source/db2AllocStmtHdl.c | 3 +- source/db2CloseConnections.c | 3 +- source/db2Debug.c | 37 ++++----------------- 4 files changed, 57 insertions(+), 49 deletions(-) diff --git a/include/db2_fdw.h b/include/db2_fdw.h index 9856f5a..7ad728a 100644 --- a/include/db2_fdw.h +++ b/include/db2_fdw.h @@ -202,40 +202,73 @@ typedef enum { CASE_KEEP, CASE_LOWER, CASE_SMART } fold_t; #define serializeInt(x) makeConst(INT4OID, -1, InvalidOid, 4, Int32GetDatum((int32)(x)), false, true) #define serializeOid(x) makeConst(OIDOID, -1, InvalidOid, 4, ObjectIdGetDatum(x), false, true) +// keep the values in sync with the LogLevels defined in elog.h of PostgreSQL +// output, that only appears in log +#define DB2DEBUG5 10 +#define DB2DEBUG4 11 +#define DB2DEBUG3 12 +#define DB2DEBUG2 13 +#define DB2DEBUG1 14 +#define DB2LOG 15 +// output, that only appears not in log +#define DB2LINFO 17 +#define DB2LNOTICE 18 +#define DB2LWARNING 20 +#define DB2LERROR 21 + +extern int isLogLevel (int level); extern void db2EntryExit(int level, int entry, const char* message, ...); extern void db2Debug (int level, const char* message, ...); +#define db2IsLogEnabled(level) \ + isLogLevel(level) + #define db2Entry1(fmt, ...) \ - db2EntryExit(1, 1, "> %s:%d:%s" fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) + db2EntryExit(DB2DEBUG1, 1, "> %s:%d:%s" fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) #define db2Entry2(fmt, ...) \ - db2EntryExit(2, 1, "> %s:%d:%s" fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) + db2EntryExit(DB2DEBUG2, 1, "> %s:%d:%s" fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) #define db2Entry3(fmt, ...) \ - db2EntryExit(3, 1, "> %s:%d:%s" fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) + db2EntryExit(DB2DEBUG3, 1, "> %s:%d:%s" fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) #define db2Entry4(fmt, ...) \ - db2EntryExit(4, 1, "> %s:%d:%s" fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) + db2EntryExit(DB2DEBUG4, 1, "> %s:%d:%s" fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) #define db2Entry5(fmt, ...) \ - db2EntryExit(5, 1, "> %s:%d:%s" fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) + db2EntryExit(DB2DEBUG5, 1, "> %s:%d:%s" fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) #define db2Exit1(fmt, ...) \ - db2EntryExit(1, 0, "< %s:%d:%s" fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) + db2EntryExit(DB2DEBUG1, 0, "< %s:%d:%s" fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) #define db2Exit2(fmt, ...) \ - db2EntryExit(2, 0, "< %s:%d:%s" fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) + db2EntryExit(DB2DEBUG2, 0, "< %s:%d:%s" fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) #define db2Exit3(fmt, ...) \ - db2EntryExit(3, 0, "< %s:%d:%s" fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) + db2EntryExit(DB2DEBUG3, 0, "< %s:%d:%s" fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) #define db2Exit4(fmt, ...) \ - db2EntryExit(4, 0, "< %s:%d:%s" fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) + db2EntryExit(DB2DEBUG4, 0, "< %s:%d:%s" fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) #define db2Exit5(fmt, ...) \ - db2EntryExit(5, 0, "< %s:%d:%s" fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) + db2EntryExit(DB2DEBUG5, 0, "< %s:%d:%s" fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) #define db2Debug1(fmt, ...) \ - db2Debug(1, "1 %s:%d:%s " fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) + db2Debug(DB2DEBUG1, "1 %s:%d:%s " fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) #define db2Debug2(fmt, ...) \ - db2Debug(2, "2 %s:%d:%s " fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) + db2Debug(DB2DEBUG2, "2 %s:%d:%s " fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) #define db2Debug3(fmt, ...) \ - db2Debug(3, "3 %s:%d:%s " fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) + db2Debug(DB2DEBUG3, "3 %s:%d:%s " fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) #define db2Debug4(fmt, ...) \ - db2Debug(4, "4 %s:%d:%s " fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) + db2Debug(DB2DEBUG4, "4 %s:%d:%s " fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) #define db2Debug5(fmt, ...) \ - db2Debug(5, "5 %s:%d:%s " fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) + db2Debug(DB2DEBUG5, "5 %s:%d:%s " fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) + +#define db2Log(fmt, ...) \ + db2Debug(DB2LOG, "l %s:%d:%s " fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) + +#define db2LogInfo(fmt, ...) \ + db2Debug(DB2LINFO, "i %s:%d:%s " fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) + +#define db2LogNotice(fmt, ...) \ + db2Debug(DB2LNOTICE, "n %s:%d:%s " fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) + +#define db2LogWarn(fmt, ...) \ + db2Debug(DB2LWARNING, "w %s:%d:%s " fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) + +#define db2LogError(fmt, ...) \ + db2Debug(DB2LERROR, "e %s:%d:%s " fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) #endif \ No newline at end of file diff --git a/source/db2AllocStmtHdl.c b/source/db2AllocStmtHdl.c index 036ce79..759deeb 100644 --- a/source/db2AllocStmtHdl.c +++ b/source/db2AllocStmtHdl.c @@ -13,7 +13,6 @@ extern DB2EnvEntry* rootenvEntry; /* Linked list of handles for cached DB2 connections. */ /** external prototypes */ -extern int isLogLevel (int level); extern void db2Error (db2error sqlstate, const char* message); extern void db2Error_d (db2error sqlstate, const char* message, const char* detail, ...); @@ -28,7 +27,7 @@ HdlEntry* db2AllocStmtHdl (SQLSMALLINT type, DB2ConnEntry* connp, db2error error SQLRETURN rc = 0; db2Entry1(); - if (isLogLevel(5)) { + if (db2IsLogEnabled(DB2DEBUG5)) { DB2EnvEntry* envstep = NULL; DB2ConnEntry* constep = NULL; HdlEntry* hdlstep = NULL; diff --git a/source/db2CloseConnections.c b/source/db2CloseConnections.c index 8106add..b5da7f7 100644 --- a/source/db2CloseConnections.c +++ b/source/db2CloseConnections.c @@ -9,7 +9,6 @@ extern DB2EnvEntry* rootenvEntry; /* Linked list of handles for cached extern char db2Message[ERRBUFSIZE];/* contains DB2 error messages, set by db2CheckErr() */ /** external prototypes */ -extern int isLogLevel (int level); extern void db2Error (db2error sqlstate, const char* message); extern void db2Error_d (db2error sqlstate, const char* message, const char* detail, ...); extern SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleType, int line, char* file); @@ -112,7 +111,7 @@ int deleteconnEntry(DB2ConnEntry* start, DB2ConnEntry* node) { break; } } - if (isLogLevel(3)) { + if (db2IsLogEnabled(DB2DEBUG3)) { for (step = start; step != NULL; step = step->right) { db2Debug3("start:%x, step:%x, step->left: %x, step->right:%x",start,step,step->left,step->right); } diff --git a/source/db2Debug.c b/source/db2Debug.c index 7301099..fb00a65 100644 --- a/source/db2Debug.c +++ b/source/db2Debug.c @@ -19,14 +19,11 @@ _Thread_local static int debug_depth = 0; (x==FDW_SERIALIZATION_FAILURE ? ERRCODE_T_R_SERIALIZATION_FAILURE : ERRCODE_FDW_ERROR)))))) /** local prototype */ -int isLogLevel (int level); void db2Error (db2error sqlstate, const char* message); void db2Error_d (db2error sqlstate, const char* message, const char* detail, ...) __attribute__ ((format (gnu_printf, 2, 0))); -void db2EntryExit(int level, int entry, const char* message, ...)__attribute__ ((format (gnu_printf, 3, 0))); -void db2Debug (int level, const char* message, ...)__attribute__ ((format (gnu_printf, 2, 0))); -/** db2Error_d - * Report a PostgreSQL error with a detail message. +/* db2Error_d + * Report a PostgreSQL error with a detail message. */ void db2Error_d (db2error sqlstate, const char *message, const char *detail, ...) { char cBuffer [4000]; @@ -39,8 +36,8 @@ void db2Error_d (db2error sqlstate, const char *message, const char *detail, ... va_end (arg_marker); } -/** db2error - * Report a PostgreSQL error without detail message. +/* db2error + * Report a PostgreSQL error without detail message. */ void db2Error (db2error sqlstate, const char *message) { /* use errcode_for_file_access() if the message contains %m */ @@ -52,31 +49,11 @@ void db2Error (db2error sqlstate, const char *message) { } int isLogLevel(int level) { - int dLevel = 0; - switch(level){ - case 1: - dLevel = DEBUG1; - break; - case 2: - dLevel = DEBUG2; - break; - case 3: - dLevel = DEBUG3; - break; - case 4: - dLevel = DEBUG4; - break; - case 5: - default: - dLevel = DEBUG5; - break; - } - dLevel = (dLevel >= log_min_messages); - return dLevel; + return (level >= log_min_messages); } void db2EntryExit(int level, int entry, const char* message, ...) { - if (isLogLevel(level)) { + if (db2IsLogEnabled(level)) { char cBuffer [4000]; va_list arg_marker; va_start(arg_marker, message); @@ -94,7 +71,7 @@ void db2EntryExit(int level, int entry, const char* message, ...) { } void db2Debug(int level, const char* message, ...) { - if (isLogLevel(level)) { + if (db2IsLogEnabled(level)) { char cBuffer [4000]; int dLevel = DEBUG5; int offset = (2*debug_depth); From 365c5c47f41616cb58f8bf5be1b1b0627a19cf73 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Sat, 7 Mar 2026 11:12:26 +0100 Subject: [PATCH 098/120] completed prototype for the debugging functions. --- include/db2_fdw.h | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/include/db2_fdw.h b/include/db2_fdw.h index 7ad728a..5d721f2 100644 --- a/include/db2_fdw.h +++ b/include/db2_fdw.h @@ -50,10 +50,8 @@ #define JSONOID InvalidOid #endif #else -#ifndef SQL_H_SQLCLI1 #include #include -#endif /* SQL_H_SQLCLI1 */ #endif /* POSTGRES_H */ @@ -189,11 +187,10 @@ typedef enum { DB2_LONGVARBINARY } nonSQLType; -/** Options for case folding for names in IMPORT FOREIGN TABLE. - */ +/* Options for case folding for names in IMPORT FOREIGN TABLE. */ typedef enum { CASE_KEEP, CASE_LOWER, CASE_SMART } fold_t; -#define REL_ALIAS_PREFIX "r" +#define REL_ALIAS_PREFIX "r" #define SUBQUERY_REL_ALIAS_PREFIX "s" #define SUBQUERY_COL_ALIAS_PREFIX "c" @@ -217,8 +214,8 @@ typedef enum { CASE_KEEP, CASE_LOWER, CASE_SMART } fold_t; #define DB2LERROR 21 extern int isLogLevel (int level); -extern void db2EntryExit(int level, int entry, const char* message, ...); -extern void db2Debug (int level, const char* message, ...); +extern void db2EntryExit(int level, int entry, const char* message, ...) __attribute__ ((format (gnu_printf, 3, 0))); +extern void db2Debug (int level, const char* message, ...) __attribute__ ((format (gnu_printf, 2, 0))); #define db2IsLogEnabled(level) \ isLogLevel(level) From 2bb9d38a27d01c063b03aaf0fc6e483070534694 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Sat, 7 Mar 2026 11:23:52 +0100 Subject: [PATCH 099/120] removed more pre PG V13 defines --- include/db2_fdw.h | 20 +++++--------------- source/db2_deparse.c | 4 ++-- source/db2_fdw.c | 4 ++-- 3 files changed, 9 insertions(+), 19 deletions(-) diff --git a/include/db2_fdw.h b/include/db2_fdw.h index 5d721f2..a04c3a8 100644 --- a/include/db2_fdw.h +++ b/include/db2_fdw.h @@ -11,10 +11,13 @@ #define _db2_fdw_h_ #ifdef POSTGRES_H + #include #include #include + #define PG_SUPPORTED_MIN_VERSION 130000 + #if PG_VERSION_NUM >= 150000 #define STRVAL(arg) ((String *)(arg))->sval #else @@ -34,24 +37,11 @@ #define PQ_QUERY_PARAM_MAX_LIMIT 65535 #endif /* PQ_QUERY_PARAM_MAX_LIMIT */ -/* array_create_iterator has a new signature from 9.5 on */ -#define array_create_iterator(arr, slice_ndim) array_create_iterator(arr, slice_ndim, NULL) - -/* GetConfigOptionByName has a new signature from 9.6 on */ -#define GetConfigOptionByName(name, varname) GetConfigOptionByName(name, varname, false) +#else /* POSTGRES_H */ - -/* list API has changed in v13 */ -#define list_next(l, e) lnext((l), (e)) -#define do_each_cell(cell, list, element) for_each_cell(cell, (list), (element)) - -/* older versions don't have JSONOID */ -#ifndef JSONOID -#define JSONOID InvalidOid -#endif -#else #include #include + #endif /* POSTGRES_H */ diff --git a/source/db2_deparse.c b/source/db2_deparse.c index 5fb7937..79610b5 100644 --- a/source/db2_deparse.c +++ b/source/db2_deparse.c @@ -2308,7 +2308,7 @@ static void deparseScalarArrayOpExpr (ScalarArrayOpExpr* expr, deparse_expr_cxt* } else { Datum datum; bool isNull; - ArrayIterator iterator = array_create_iterator (DatumGetArrayTypeP (constant->constvalue), 0); + ArrayIterator iterator = array_create_iterator (DatumGetArrayTypeP (constant->constvalue), 0, NULL); bool first_arg = true; /* loop through the array elements */ @@ -2508,7 +2508,7 @@ static void deparseBoolExpr (BoolExpr* expr, deparse_expr_cxt* if (arg != NULL) { bool bBreak = false; appendStringInfo (&buf, "(%s%s", expr->boolop == NOT_EXPR ? "NOT " : "", arg); - do_each_cell(cell, expr->args, list_next(expr->args, list_head(expr->args))) { + for_each_cell(cell, expr->args, lnext(expr->args, list_head(expr->args))) { db2free(arg); arg = deparseExpr (ctx->root, ctx->foreignrel, (Expr*)lfirst(cell), ctx->params_list); if (arg != NULL) { diff --git a/source/db2_fdw.c b/source/db2_fdw.c index 1450ee6..dc602f2 100644 --- a/source/db2_fdw.c +++ b/source/db2_fdw.c @@ -408,7 +408,7 @@ PGDLLEXPORT Datum db2_diag (PG_FUNCTION_ARGS) { * We cannot use PG_VERSION because that would give the version against which * db2_fdw was compiled, not the version it is running with. */ - pgversion = GetConfigOptionByName ("server_version", NULL); + pgversion = GetConfigOptionByName ("server_version", NULL, false); initStringInfo (&version); appendStringInfo (&version, "db2_fdw %s, PostgreSQL %s", DB2_FDW_VERSION, pgversion); @@ -500,7 +500,7 @@ void _PG_init (void) { * We cannot use PG_VERSION because that would give the version against which * db2_fdw was compiled, not the version it is running with. */ - pgversion = GetConfigOptionByName ("server_version", NULL); + pgversion = GetConfigOptionByName ("server_version", NULL, false); if (pgversion != NULL) { char majorversion[3]; majorversion[0] = pgversion[0]; From ecedc8dae60a9aafb669cb00d557420fd7ff9e68 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Sat, 7 Mar 2026 17:17:28 +0100 Subject: [PATCH 100/120] changed memory management functions in code to use macros for better debug tracing --- include/db2_fdw.h | 19 +++++++ source/db2AddForeignUpdateTargets.c | 1 - source/db2AllocConnHdl.c | 1 - source/db2AllocEnvHdl.c | 28 +++++------ source/db2AnalyzeForeignTable.c | 5 +- source/db2BeginDirectModify.c | 7 ++- source/db2BeginForeignModifyCommon.c | 3 +- source/db2BeginForeignScan.c | 5 +- source/db2BindParameter.c | 9 ++-- source/db2CloseConnections.c | 1 - source/db2CopyText.c | 5 +- source/db2Debug.c | 5 +- source/db2Describe.c | 18 +++---- source/db2EndDirectModify.c | 7 ++- source/db2EndForeignModifyCommon.c | 7 ++- source/db2EndForeignScan.c | 5 +- source/db2ExecForeignDelete.c | 3 +- source/db2ExecForeignTruncate.c | 3 +- source/db2ExecuteInsert.c | 6 +-- source/db2ExecuteQuery.c | 6 +-- source/db2ExplainForeignScan.c | 6 +-- source/db2FreeEnvHdl.c | 1 - source/db2GetFdwState.c | 25 ++++------ source/db2GetForeignJoinPaths.c | 14 +++--- source/db2GetForeignPlan.c | 20 +++----- source/db2GetForeignPlanOld.c | 18 +++---- source/db2GetForeignRelSize.c | 3 +- source/db2GetForeignUpperPaths.c | 39 +++++++-------- source/db2GetLob.c | 6 +-- source/db2GetSession.c | 3 +- source/db2GetShareFileName.c | 7 +-- source/db2ImportForeignSchema.c | 16 +++--- source/db2ImportForeignSchemaData.c | 36 ++++++------- source/db2PlanForeignModify.c | 35 ++++++------- source/db2PrepareQuery.c | 1 - source/db2ReAllocFree.c | 66 ++++++++++++++++-------- source/db2_de_serialize.c | 15 +++--- source/db2_deparse.c | 75 +++++++++++++--------------- source/db2_fdw_utils.c | 15 +++--- 39 files changed, 262 insertions(+), 283 deletions(-) diff --git a/include/db2_fdw.h b/include/db2_fdw.h index a04c3a8..a00b10b 100644 --- a/include/db2_fdw.h +++ b/include/db2_fdw.h @@ -189,6 +189,25 @@ typedef enum { CASE_KEEP, CASE_LOWER, CASE_SMART } fold_t; #define serializeInt(x) makeConst(INT4OID, -1, InvalidOid, 4, Int32GetDatum((int32)(x)), false, true) #define serializeOid(x) makeConst(OIDOID, -1, InvalidOid, 4, ObjectIdGetDatum(x), false, true) + +extern void* db2Alloc (size_t size, const char* message, ...) __attribute__ ((format (gnu_printf, 2, 0))); +extern void db2Free (void* p, const char* message, ...) __attribute__ ((format (gnu_printf, 2, 0))); +extern void* db2ReAlloc(size_t size, void* p, const char* message, ...) __attribute__ ((format (gnu_printf, 3, 0))); +extern char* db2StrDup (const char* source, const char* message, ...) __attribute__ ((format (gnu_printf, 2, 0))); + +#define db2alloc(size, msg, ...) \ + db2Alloc( size, "%d %s:%s %s", __LINE__, __FILE__, __func__, msg, ##__VA_ARGS__) + +#define db2free(pvoid, msg, ...) \ + db2Free( pvoid, "%d %s:%s %s", __LINE__, __FILE__, __func__, msg, ##__VA_ARGS__) + +#define db2realloc(size, pvoid, msg, ...) \ + db2ReAlloc( size, pvoid, "%d %s:%s %s", __LINE__, __FILE__, __func__, msg, ##__VA_ARGS__) + +#define db2strdup(src, msg, ...) \ + db2StrDup( src, "%d %s:%s %s", __LINE__, __FILE__, __func__, msg, ##__VA_ARGS__) + + // keep the values in sync with the LogLevels defined in elog.h of PostgreSQL // output, that only appears in log #define DB2DEBUG5 10 diff --git a/source/db2AddForeignUpdateTargets.c b/source/db2AddForeignUpdateTargets.c index b400cac..b3437ba 100644 --- a/source/db2AddForeignUpdateTargets.c +++ b/source/db2AddForeignUpdateTargets.c @@ -9,7 +9,6 @@ #include "db2_fdw.h" /** external prototypes */ -extern char* db2strdup (const char* source); extern bool optionIsTrue (const char* value); /** local prototypes */ diff --git a/source/db2AllocConnHdl.c b/source/db2AllocConnHdl.c index 055a050..07161fb 100644 --- a/source/db2AllocConnHdl.c +++ b/source/db2AllocConnHdl.c @@ -10,7 +10,6 @@ extern void db2Error_d (db2error sqlstate, const char* message, c extern void db2RegisterCallback (void* arg); extern SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleType, int line, char* file); extern void db2FreeEnvHdl (DB2EnvEntry* envp, const char* nls_lang); -extern char* db2strdup (const char* p); /** local prototypes */ DB2ConnEntry* db2AllocConnHdl (DB2EnvEntry* envp,const char* srvname, char* user, char* password, char* jwt_token, const char* nls_lang); diff --git a/source/db2AllocEnvHdl.c b/source/db2AllocEnvHdl.c index bd1a006..04f58bc 100644 --- a/source/db2AllocEnvHdl.c +++ b/source/db2AllocEnvHdl.c @@ -12,8 +12,6 @@ extern char db2Message[ERRBUFSIZE];/* contains DB2 error messages, set b extern void db2SetHandlers (void); extern void db2Error_d (db2error sqlstate, const char* message, const char* detail, ...); extern SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleType, int line, char* file); -extern char* db2strdup (const char* p); -extern void db2free (void* p); /** local prototypes */ DB2EnvEntry* db2AllocEnvHdl (const char* nls_lang); @@ -29,7 +27,7 @@ DB2EnvEntry* db2AllocEnvHdl(const char* nls_lang){ db2Entry1(); /* create persistent copy of "nls_lang" */ - if ((nlscopy = db2strdup (nls_lang)) == NULL) + if ((nlscopy = db2strdup (nls_lang,"nls_lang")) == NULL) db2Error_d (FDW_OUT_OF_MEMORY, "error connecting to DB2:"," failed to allocate %d bytes of memory", strlen (nls_lang) + 1); /* set DB2 environment */ @@ -40,7 +38,7 @@ DB2EnvEntry* db2AllocEnvHdl(const char* nls_lang){ db2Debug3("allocate env handle - rc: %d, henv: %d",rc, henv); rc = db2CheckErr(rc, henv, SQL_HANDLE_ENV, __LINE__, __FILE__); if (rc != SQL_SUCCESS) { - db2free (nlscopy); + db2free (nlscopy,"nlscopy"); db2Error_d (FDW_UNABLE_TO_ESTABLISH_CONNECTION, "error connecting to DB2: SQLAllocHandle failed to create environment handle", db2Message); } @@ -52,7 +50,7 @@ DB2EnvEntry* db2AllocEnvHdl(const char* nls_lang){ db2Debug3("set env attributes odbcv3 - rc: %d, henv: %d",rc, henv); rc = db2CheckErr(rc, henv, SQL_HANDLE_ENV, __LINE__, __FILE__); if (rc != SQL_SUCCESS) { - db2free (nlscopy); + db2free (nlscopy,"nlscopy"); db2Error_d (FDW_UNABLE_TO_ESTABLISH_CONNECTION, "error connecting to DB2: SQLSetEnvAttr failed to set ODBC v3.0", db2Message); } @@ -88,44 +86,44 @@ DB2EnvEntry* db2AllocEnvHdl(const char* nls_lang){ static void setDB2Environment (char* nls_lang) { db2Entry4(); if (putenv (nls_lang) != 0) { - db2free (nls_lang); + db2free (nls_lang,"nls_lang"); db2Error_d (FDW_UNABLE_TO_ESTABLISH_CONNECTION, "error connecting to DB2", "Environment variable NLS_LANG cannot be set."); } /* other environment variables that control DB2 formats */ if (putenv ("NLS_DATE_LANGUAGE=AMERICAN") != 0) { - db2free (nls_lang); + db2free (nls_lang,"nls_lang"); db2Error_d (FDW_UNABLE_TO_ESTABLISH_CONNECTION, "error connecting to DB2", "Environment variable NLS_DATE_LANGUAGE cannot be set."); } if (putenv ("NLS_DATE_FORMAT=YYYY-MM-DD HH24:MI:SS BC") != 0) { - db2free (nls_lang); + db2free (nls_lang,"nls_lang"); db2Error_d (FDW_UNABLE_TO_ESTABLISH_CONNECTION, "error connecting to DB2", "Environment variable NLS_DATE_FORMAT cannot be set."); } if (putenv ("NLS_TIMESTAMP_FORMAT=YYYY-MM-DD HH24:MI:SS.FF9 BC") != 0) { - db2free (nls_lang); + db2free (nls_lang,"nls_lang"); db2Error_d (FDW_UNABLE_TO_ESTABLISH_CONNECTION, "error connecting to DB2", "Environment variable NLS_TIMESTAMP_FORMAT cannot be set."); } if (putenv ("NLS_TIMESTAMP_TZ_FORMAT=YYYY-MM-DD HH24:MI:SS.FF9TZH:TZM BC") != 0) { - db2free (nls_lang); + db2free (nls_lang,"nls_lang"); db2Error_d (FDW_UNABLE_TO_ESTABLISH_CONNECTION, "error connecting to DB2", "Environment variable NLS_TIMESTAMP_TZ_FORMAT cannot be set."); } if (putenv ("NLS_TIME_FORMAT=HH24:MI:SS.FF9 BC") != 0) { - db2free (nls_lang); + db2free (nls_lang,"nls_lang"); db2Error_d (FDW_UNABLE_TO_ESTABLISH_CONNECTION, "error connecting to DB2", "Environment variable NLS_TIME_FORMAT cannot be set."); } if (putenv ("NLS_TIME_TZ_FORMAT= HH24:MI:SS.FF9TZH:TZM BC") != 0) { - db2free (nls_lang); + db2free (nls_lang,"nls_lang"); db2Error_d (FDW_UNABLE_TO_ESTABLISH_CONNECTION, "error connecting to DB2", "Environment variable NLS_TIME_TZ_FORMAT cannot be set."); } if (putenv ("NLS_NUMERIC_CHARACTERS=.,") != 0) { - db2free (nls_lang); + db2free (nls_lang,"nls_lang"); db2Error_d (FDW_UNABLE_TO_ESTABLISH_CONNECTION, "error connecting to DB2", "Environment variable NLS_NUMERIC_CHARACTERS cannot be set."); } if (putenv ("NLS_CALENDAR=") != 0) { - db2free (nls_lang); + db2free (nls_lang,"nls_lang"); db2Error_d (FDW_UNABLE_TO_ESTABLISH_CONNECTION, "error connecting to DB2", "Environment variable NLS_CALENDAR cannot be set."); } if (putenv ("NLS_NCHAR=") != 0) { - db2free (nls_lang); + db2free (nls_lang,"nls_lang"); db2Error_d (FDW_UNABLE_TO_ESTABLISH_CONNECTION, "error connecting to DB2", "Environment variable NLS_NCHAR cannot be set."); } db2Exit4(); diff --git a/source/db2AnalyzeForeignTable.c b/source/db2AnalyzeForeignTable.c index 2930cb3..3c06212 100644 --- a/source/db2AnalyzeForeignTable.c +++ b/source/db2AnalyzeForeignTable.c @@ -19,7 +19,6 @@ extern int db2FetchNext (DB2Session* session); extern void checkDataType (short db2type, int scale, Oid pgtype, const char* tablename, const char* colname); extern short c2dbType (short fcType); extern void convertTuple (DB2Session* session, DB2Table* db2Table, DB2ResultColumn* reslist, int natts, Datum* values, bool* nulls, bool trunc_lob); -extern void* db2alloc (const char* type, size_t size); /** local prototypes */ bool db2AnalyzeForeignTable(Relation relation, AcquireSampleRowsFunc* func, BlockNumber* totalpages); @@ -45,8 +44,8 @@ static int acquireSampleRowsFunc (Relation relation, int elevel, HeapTuple* rows bool first_column = true; StringInfoData query; TupleDesc tupDesc = RelationGetDescr (relation); - Datum* values = (Datum*) db2alloc("values", tupDesc->natts* sizeof (Datum)); - bool* nulls = (bool*) db2alloc("null" , tupDesc->natts* sizeof (bool)); + Datum* values = (Datum*) db2alloc(tupDesc->natts* sizeof (Datum), "values"); + bool* nulls = (bool*) db2alloc(tupDesc->natts* sizeof (bool) , "null"); double rstate = 0; double rowstoskip = -1; double sample_percent = 0; diff --git a/source/db2BeginDirectModify.c b/source/db2BeginDirectModify.c index 4a744d7..488ff9f 100644 --- a/source/db2BeginDirectModify.c +++ b/source/db2BeginDirectModify.c @@ -8,7 +8,6 @@ #include "db2_fdw.h" #include "DB2FdwDirectModifyState.h" -extern void* db2alloc (const char* type, size_t size); extern DB2Session* db2GetSession (const char* connectstring, char* user, char* password, char* jwt_token, const char* nls_lang, int curlevel); extern DB2FdwDirectModifyState* db2GetFdwDirectModifyState(Oid foreigntableid, double* sample_percent, bool describe); @@ -168,7 +167,7 @@ static void init_returning_filter(DB2FdwDirectModifyState* dmstate, List* fdw_sc * * Also get the indexes of the entries for ctid and oid if any. */ - dmstate->attnoMap = (AttrNumber*) db2alloc("init_returning_filter::attnoMap",resultTupType->natts * sizeof(AttrNumber)); + dmstate->attnoMap = (AttrNumber*) db2alloc(resultTupType->natts * sizeof(AttrNumber), "attnoMap"); dmstate->ctidAttno = dmstate->oidAttno = 0; i = 1; @@ -209,7 +208,7 @@ static void prepare_query_params(PlanState* node, List* fdw_exprs, int numParams Assert(numParams > 0); /* Prepare for output conversion of parameters used in remote query. */ - *param_flinfo = db2alloc("prepare_query_params:param_flinfo",sizeof(FmgrInfo) * numParams); + *param_flinfo = db2alloc(sizeof(FmgrInfo) * numParams,"param_flinfo"); i = 0; foreach(lc, fdw_exprs) { @@ -231,7 +230,7 @@ static void prepare_query_params(PlanState* node, List* fdw_exprs, int numParams *param_exprs = ExecInitExprList(fdw_exprs, node); /* Allocate buffer for text form of query parameters. */ - *param_values = (const char **) db2alloc("prepare_query_params::param_values",numParams * sizeof(char *)); + *param_values = (const char **) db2alloc(numParams * sizeof(char *),"param_values"); db2Exit4(); } diff --git a/source/db2BeginForeignModifyCommon.c b/source/db2BeginForeignModifyCommon.c index 12df090..eb4f270 100644 --- a/source/db2BeginForeignModifyCommon.c +++ b/source/db2BeginForeignModifyCommon.c @@ -15,7 +15,6 @@ extern regproc* output_funcs; /** external prototypes */ extern DB2Session* db2GetSession (const char* connectstring, char* user, char* password, char* jwt_token, const char* nls_lang, int curlevel); extern void db2PrepareQuery (DB2Session* session, const char *query, DB2ResultColumn* db2ResultList, unsigned long prefetch, int fetchsize); -extern void* db2alloc (const char* type, size_t size); /** local prototypes */ void db2BeginForeignModifyCommon(ModifyTableState* mtstate, ResultRelInfo* rinfo, DB2FdwState* fdw_state, Plan* subplan); @@ -34,7 +33,7 @@ void db2BeginForeignModifyCommon(ModifyTableState* mtstate, ResultRelInfo* rinfo db2PrepareQuery(fdw_state->session, fdw_state->query, fdw_state->resultList,fdw_state->prefetch,fdw_state->fetch_size); /* get the type output functions for the parameters */ - output_funcs = (regproc*) db2alloc("output_funcs", fdw_state->db2Table->ncols * sizeof(regproc *)); + output_funcs = (regproc*) db2alloc(fdw_state->db2Table->ncols * sizeof(regproc *), "output_funcs"); for (param = fdw_state->paramList; param != NULL; param = param->next) { /* ignore output parameters */ if (param->bindType == BIND_OUTPUT) diff --git a/source/db2BeginForeignScan.c b/source/db2BeginForeignScan.c index 0b95c85..5c658bb 100644 --- a/source/db2BeginForeignScan.c +++ b/source/db2BeginForeignScan.c @@ -9,7 +9,6 @@ /** external prototypes */ extern DB2Session* db2GetSession (const char* connectstring, char* user, char* password, char* jwt_token, const char* nls_lang, int curlevel); -extern void* db2alloc (const char* type, size_t size); extern DB2FdwState* deserializePlanData (List* list); /** local prototypes */ @@ -33,7 +32,7 @@ void db2BeginForeignScan(ForeignScanState* node, int eflags) { /* add a fake parameter "if that string appears in the query */ if (strstr (fdw_state->query, "?/*:now*/") != NULL) { - ParamDesc* paramDesc = (ParamDesc*) db2alloc ("fdw_state->paramList->next", sizeof (ParamDesc)); + ParamDesc* paramDesc = (ParamDesc*) db2alloc (sizeof (ParamDesc), "fdw_state->paramList->next"); paramDesc->type = TIMESTAMPTZOID; paramDesc->bindType = BIND_STRING; paramDesc->value = NULL; @@ -83,7 +82,7 @@ static void addExprParams(ForeignScanState* node){ continue; /* create a new entry in the parameter list */ - paramDesc = (ParamDesc*) db2alloc("fdw_state->paramList->next", sizeof (ParamDesc)); + paramDesc = (ParamDesc*) db2alloc(sizeof (ParamDesc), "fdw_state->paramList->next"); paramDesc->type = exprType ((Node*) (expr->expr)); if (paramDesc->type == TEXTOID diff --git a/source/db2BindParameter.c b/source/db2BindParameter.c index 8ccaa36..4185484 100644 --- a/source/db2BindParameter.c +++ b/source/db2BindParameter.c @@ -9,7 +9,6 @@ extern char db2Message[ERRBUFSIZE];/* contains DB2 error messages, set by db2CheckErr() */ /** external prototypes */ -extern void* db2alloc (const char* type, size_t size); extern SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleType, int line, char* file); extern void db2Error_d (db2error sqlstate, const char* message, const char* detail, ...); extern SQLSMALLINT param2c (SQLSMALLINT fcType); @@ -41,7 +40,7 @@ void db2BindParameter (DB2Session* session, ParamDesc* param, SQLLEN* indicator, char* end = NULL; SQLBIGINT* sqlbint = NULL; if (param->value != NULL) { - sqlbint = db2alloc("SQLBIGINT",sizeof(SQLBIGINT)); + sqlbint = db2alloc(sizeof(SQLBIGINT), "SQLBIGINT sqlbigint"); *sqlbint = strtoll(param->value,&end,10); db2Debug2("sqlbint: %d",*sqlbint); } @@ -62,7 +61,7 @@ void db2BindParameter (DB2Session* session, ParamDesc* param, SQLLEN* indicator, char* end = NULL; SQLSMALLINT* sqlsint = NULL; if (param->value != NULL) { - sqlsint = db2alloc("SQLSMALLINT",sizeof(SQLSMALLINT)); + sqlsint = db2alloc(sizeof(SQLSMALLINT), "SQLSMALLINT sqlsint"); *sqlsint = strtol(param->value,&end,10); db2Debug2("sqlsint: %d",*sqlsint); } @@ -83,7 +82,7 @@ void db2BindParameter (DB2Session* session, ParamDesc* param, SQLLEN* indicator, char* end = NULL; SQLINTEGER* sqlint = NULL; if (param->value != NULL) { - sqlint = db2alloc("SQLINTEGER",sizeof(SQLINTEGER)); + sqlint = db2alloc(sizeof(SQLINTEGER),"SQLINTEGER sqlint"); *sqlint = strtol(param->value,&end,10); db2Debug2("sqlint: %d",*sqlint); } @@ -108,7 +107,7 @@ void db2BindParameter (DB2Session* session, ParamDesc* param, SQLLEN* indicator, case SQL_DECFLOAT: { SQL_NUMERIC_STRUCT* num = NULL; if (param->value != NULL) { - num = db2alloc("SQL_NUMERIC_STRUCT",sizeof(SQL_NUMERIC_STRUCT)); + num = db2alloc(sizeof(SQL_NUMERIC_STRUCT), "SQL_NUMERIC_STRUCT num "); parse2num_struct(param->value, num); db2Debug2("num: '%s'",*num); } diff --git a/source/db2CloseConnections.c b/source/db2CloseConnections.c index b5da7f7..1d5109b 100644 --- a/source/db2CloseConnections.c +++ b/source/db2CloseConnections.c @@ -14,7 +14,6 @@ extern void db2Error_d (db2error sqlstate, const char* message, c extern SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleType, int line, char* file); extern void db2UnregisterCallback(void* arg); extern void db2FreeEnvHdl (DB2EnvEntry* envp, const char* nls_lang); -extern void db2free (void* p); /** local prototypes */ void db2CloseConnections (void); diff --git a/source/db2CopyText.c b/source/db2CopyText.c index ca552aa..3b214e2 100644 --- a/source/db2CopyText.c +++ b/source/db2CopyText.c @@ -6,7 +6,6 @@ /** external variables */ /** external prototypes */ -extern void* db2alloc (const char* type, size_t size); /** local prototypes */ char* db2CopyText (const char* string, int size, int quote); @@ -24,7 +23,7 @@ char* db2CopyText (const char* string, int size, int quote) { db2Entry4("(string: '%s', size: %d, quote: %d)",string,size,quote); /* if "string" is parenthized, return a copy */ if (string[0] == '(' && string[size - 1] == ')') { - result = db2alloc ("copyText", size + 1); + result = db2alloc (size + 1, "result"); memcpy (result, string, size); result[size] = '\0'; return result; @@ -37,7 +36,7 @@ char* db2CopyText (const char* string, int size, int quote) { } } - result = db2alloc ("copyText", resultsize + 1); + result = db2alloc (resultsize + 1, "result"); if (quote) result[++j] = '"'; for (i = 0; i < size; ++i) { diff --git a/source/db2Debug.c b/source/db2Debug.c index fb00a65..7ea3ec4 100644 --- a/source/db2Debug.c +++ b/source/db2Debug.c @@ -1,9 +1,6 @@ +#include #include #include -#include -#include -#include -#include #include #include "db2_fdw.h" diff --git a/source/db2Describe.c b/source/db2Describe.c index 6483dbf..dae58b6 100644 --- a/source/db2Describe.c +++ b/source/db2Describe.c @@ -11,8 +11,6 @@ extern int err_code; /* error code, set by db2CheckErr() /** external prototypes */ extern bool optionIsTrue (const char* value); -extern void* db2alloc (const char* type, size_t size); -extern void db2free (void* p); extern SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleType, int line, char* file); extern void db2Error_d (db2error sqlstate, const char* message, const char* detail, ...); extern char* db2CopyText (const char* string, int size, int quote); @@ -56,20 +54,20 @@ DB2Table* db2Describe (DB2Session* session, char* schema, char* table, char* pgn qschema = db2CopyText (schema, strlen (schema), 1); length += strlen (qschema) + 1; } - tablename = db2alloc ("reply->name", length + 1); + tablename = db2alloc (length + 1, "tablename"); tablename[0] = '\0'; /* empty */ if (schema != NULL) { strncat (tablename, qschema,length); strncat (tablename, ".",length); } strncat (tablename, qtable,length); - db2free (qtable); + db2free (qtable, "qtable"); if (schema != NULL) - db2free (qschema); + db2free (qschema, "qschema"); /* construct a "SELECT * FROM ..." query to describe columns */ length += 40; - query = db2alloc ("query", length + 1); + query = db2alloc (length + 1, "query"); snprintf ((char*)query, length+1, (char*)"SELECT * FROM %s FETCH FIRST 1 ROW ONLY", tablename); /* create statement handle */ @@ -92,10 +90,10 @@ DB2Table* db2Describe (DB2Session* session, char* schema, char* table, char* pgn else db2Error_d (FDW_UNABLE_TO_CREATE_REPLY, "error describing remote table: SQLExecute failed to describe table", db2Message); } - db2free(query); + db2free(query, "query"); /* allocate an db2Table struct for the results */ - reply = db2alloc ("reply", sizeof (DB2Table)); + reply = db2alloc (sizeof (DB2Table), "DB2Table reply"); reply->name = tablename; db2Debug2("table description"); db2Debug2("reply->name : '%s'", reply->name); @@ -117,13 +115,13 @@ DB2Table* db2Describe (DB2Session* session, char* schema, char* table, char* pgn } reply->ncols = ncols; - reply->cols = (DB2Column**) db2alloc ("reply->cols", sizeof (DB2Column*) *reply->ncols); + reply->cols = (DB2Column**) db2alloc ((sizeof (DB2Column*) * reply->ncols), "DB2Columns* reply->cols(%d)",reply->ncols); db2Debug2("reply->ncols : %d", reply->ncols); /* loop through the column list */ for (i = 1; i <= reply->ncols; ++i) { /* allocate an db2Column struct for the column */ - reply->cols[i - 1] = (DB2Column *) db2alloc (" reply->cols[i - 1]", sizeof (DB2Column)); + reply->cols[i - 1] = (DB2Column *) db2alloc (sizeof (DB2Column)," DB2Column reply->cols[%s - 1]", i); reply->cols[i - 1]->colPrimKeyPart = 0; reply->cols[i - 1]->colCodepage = 0; reply->cols[i - 1]->pgname = NULL; diff --git a/source/db2EndDirectModify.c b/source/db2EndDirectModify.c index a5defe4..796ac3d 100644 --- a/source/db2EndDirectModify.c +++ b/source/db2EndDirectModify.c @@ -7,7 +7,6 @@ extern regproc* output_funcs; /** external prototypes */ extern void db2CloseStatement (DB2Session* session); -extern void db2free (void* p); /** local prototypes */ void db2EndDirectModify(ForeignScanState* node); @@ -33,7 +32,7 @@ void db2EndDirectModify(ForeignScanState* node) { /* Finish statement / cursor, if you keep a handle there */ if (fdw_state->session) { db2CloseStatement (fdw_state->session); - db2free(fdw_state->session); + db2free(fdw_state->session,"fdw_state->session"); fdw_state->session = NULL; } @@ -43,10 +42,10 @@ void db2EndDirectModify(ForeignScanState* node) { } if (output_funcs){ - db2free(output_funcs); + db2free(output_funcs,"output_funcs"); } - db2free(fdw_state); + db2free(fdw_state,"fdw_state"); node->fdw_state = NULL; db2Exit1(); } \ No newline at end of file diff --git a/source/db2EndForeignModifyCommon.c b/source/db2EndForeignModifyCommon.c index 757aa09..9506c13 100644 --- a/source/db2EndForeignModifyCommon.c +++ b/source/db2EndForeignModifyCommon.c @@ -11,7 +11,6 @@ extern regproc* output_funcs; /** external prototypes */ extern void db2CloseStatement (DB2Session* session); -extern void db2free (void* p); /** local prototypes */ void db2EndForeignModifyCommon(EState *estate, ResultRelInfo *rinfo); @@ -36,7 +35,7 @@ void db2EndForeignModifyCommon(EState *estate, ResultRelInfo *rinfo) { /* Finish statement / cursor, if you keep a handle there */ if (fdw_state->session) { db2CloseStatement (fdw_state->session); - db2free(fdw_state->session); + db2free(fdw_state->session,"fdw_state->session"); fdw_state->session = NULL; } @@ -46,10 +45,10 @@ void db2EndForeignModifyCommon(EState *estate, ResultRelInfo *rinfo) { } if (output_funcs){ - db2free(output_funcs); + db2free(output_funcs,"output_funcs"); } rinfo->ri_FdwState = NULL; - db2free(fdw_state); + db2free(fdw_state,"fdw_state"); db2Exit1(); } diff --git a/source/db2EndForeignScan.c b/source/db2EndForeignScan.c index 4b59000..7f58305 100644 --- a/source/db2EndForeignScan.c +++ b/source/db2EndForeignScan.c @@ -7,7 +7,6 @@ /** external prototypes */ extern void db2CloseStatement (DB2Session* session); -extern void db2free (void* p); /** local prototypes */ void db2EndForeignScan(ForeignScanState* node); @@ -22,9 +21,9 @@ void db2EndForeignScan (ForeignScanState* node) { /* release the DB2 session */ db2CloseStatement(fdw_state->session); // check fdw_state->session for dangling references that need to be freed - db2free(fdw_state->session); + db2free(fdw_state->session,"fdw_state->session"); fdw_state->session = NULL; // check fdw_state for dangling references that need to be freed - db2free(fdw_state); + db2free(fdw_state,"fdw_state"); db2Exit1(); } diff --git a/source/db2ExecForeignDelete.c b/source/db2ExecForeignDelete.c index f62ec17..e07f176 100644 --- a/source/db2ExecForeignDelete.c +++ b/source/db2ExecForeignDelete.c @@ -15,7 +15,6 @@ extern void db2Debug (int level, const char* message extern void convertTuple (DB2Session* session, DB2Table* db2Table, DB2ResultColumn* reslist, int natts, Datum* values, bool* nulls, bool trunc_lob); extern char* deparseDate (Datum datum); extern char* deparseTimestamp (Datum datum, bool hasTimezone); -extern void* db2alloc (const char* type, size_t size); /** local prototypes */ TupleTableSlot* db2ExecForeignDelete (EState* estate, ResultRelInfo* rinfo, TupleTableSlot* slot, TupleTableSlot* planSlot); @@ -195,7 +194,7 @@ void setModifyParameters (ParamDesc *paramList, TupleTableSlot * newslot, TupleT datum = (Datum) PG_DETOAST_DATUM (datum); /* the first 4 bytes contain the length */ value_len = VARSIZE (datum) - VARHDRSZ; - param->value = db2alloc("param->value", value_len); + param->value = db2alloc(value_len,"param->value"); memcpy (param->value, VARDATA(datum), value_len); db2Debug2("param->value: %s (ought to be a LONG or LONGRAW)",param->value); } diff --git a/source/db2ExecForeignTruncate.c b/source/db2ExecForeignTruncate.c index 1bb5268..18f011e 100644 --- a/source/db2ExecForeignTruncate.c +++ b/source/db2ExecForeignTruncate.c @@ -10,7 +10,6 @@ extern DB2FdwState* db2GetFdwState (Oid foreigntableid, double* sample_per extern DB2Session* db2GetSession (const char* connectstring, char* user, char* password, char* jwt_token, const char* nls_lang, int curlevel); extern int db2ExecuteTruncate (DB2Session* session, const char* query); extern void db2CloseStatement (DB2Session* session); -extern void db2free (void* p); /** local prototypes */ void db2ExecForeignTruncate (List *rels, DropBehavior behavior, bool restart_seqs); @@ -41,7 +40,7 @@ void db2ExecForeignTruncate(List *rels, DropBehavior behavior, bool restart_seqs db2ExecuteTruncate(fdw_state->session,fdw_state->query); db2CloseStatement (fdw_state->session); - db2free(fdw_state->session); + db2free(fdw_state->session,"fdw_state->session"); fdw_state->session = NULL; } } diff --git a/source/db2ExecuteInsert.c b/source/db2ExecuteInsert.c index f12b8c1..62514dd 100644 --- a/source/db2ExecuteInsert.c +++ b/source/db2ExecuteInsert.c @@ -10,8 +10,6 @@ extern char db2Message[ERRBUFSIZE];/* contains DB2 error messages, set b extern int err_code; /* error code, set by db2CheckErr() */ /** external prototypes */ -extern void* db2alloc (const char* type, size_t size); -extern void db2free (void* p); extern SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleType, int line, char* file); extern void db2Error_d (db2error sqlstate, const char* message, const char* detail, ...); extern SQLSMALLINT param2c (SQLSMALLINT fcType); @@ -43,7 +41,7 @@ int db2ExecuteInsert (DB2Session* session, ParamDesc* paramList) { } db2Debug2("paramcount: %d",param_count); /* allocate a temporary array of indicators */ - indicators = db2alloc ("indicators", param_count * sizeof (SQLLEN)); + indicators = db2alloc ((param_count * sizeof (SQLLEN)), "indicators[%d]", param_count); /* bind the parameters */ param_count = 0; @@ -69,7 +67,7 @@ int db2ExecuteInsert (DB2Session* session, ParamDesc* paramList) { } /* db2free all indicators */ - db2free (indicators); + db2free (indicators, "indicators[%d]", param_count); if (rc == SQL_NO_DATA) { db2Debug3("SQL_NO_DATA"); } else { diff --git a/source/db2ExecuteQuery.c b/source/db2ExecuteQuery.c index 7f2e1ef..09a9f91 100644 --- a/source/db2ExecuteQuery.c +++ b/source/db2ExecuteQuery.c @@ -11,8 +11,6 @@ extern char db2Message[ERRBUFSIZE];/* contains DB2 error messages, set b extern int err_code; /* error code, set by db2CheckErr() */ /** external prototypes */ -extern void* db2alloc (const char* type, size_t size); -extern void db2free (void* p); extern SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleType, int line, char* file); extern void db2Error_d (db2error sqlstate, const char* message, const char* detail, ...); extern SQLSMALLINT param2c (SQLSMALLINT fcType); @@ -44,7 +42,7 @@ int db2ExecuteQuery (DB2Session* session, ParamDesc* paramList) { } db2Debug2("paramcount: %d",param_count); /* allocate a temporary array of indicators */ - indicators = db2alloc ("indicators", param_count * sizeof (SQLLEN)); + indicators = db2alloc (param_count * sizeof (SQLLEN),"indicators[%d]",param_count); /* bind the parameters */ param_count = 0; @@ -66,7 +64,7 @@ int db2ExecuteQuery (DB2Session* session, ParamDesc* paramList) { /* use the correct SQLSTATE for serialization failures */ db2Error_d(err_code == 8177 ? FDW_SERIALIZATION_FAILURE : FDW_UNABLE_TO_CREATE_EXECUTION, "error executing query: SQLExecute failed to execute remote query", db2Message); } - db2free(indicators); + db2free(indicators,"indicators[%d]",param_count); if (rc == SQL_NO_DATA) { db2Debug3("SQL_NO_DATA"); } else { diff --git a/source/db2ExplainForeignScan.c b/source/db2ExplainForeignScan.c index 8a4ba0d..debc338 100644 --- a/source/db2ExplainForeignScan.c +++ b/source/db2ExplainForeignScan.c @@ -10,8 +10,6 @@ #include "DB2FdwState.h" /** external prototypes */ -extern void* db2alloc (const char* type, size_t size); -extern void db2free (void* p); /** local prototypes */ void db2ExplainForeignScan(ForeignScanState* node, ExplainState* es); @@ -46,7 +44,7 @@ static void db2Explain (void* fdw, ExplainState* es) { for (const char* p = src; *p; p++) { if (*p == '"') count++; } - tempQuery = db2alloc("tempQuery", qlength+count+1); + tempQuery = db2alloc(qlength+count+1,"tempQuery"); dest = tempQuery; src = fdw_state->query; while(*src){ @@ -87,6 +85,6 @@ static void db2Explain (void* fdw, ExplainState* es) { } /* close */ pclose(fp); - db2free(tempQuery); + db2free(tempQuery,"tempQuery"); db2Exit1(); } diff --git a/source/db2FreeEnvHdl.c b/source/db2FreeEnvHdl.c index 7f69879..728a1a5 100644 --- a/source/db2FreeEnvHdl.c +++ b/source/db2FreeEnvHdl.c @@ -13,7 +13,6 @@ extern DB2EnvEntry* rootenvEntry; /* Linked list of handles for cached extern void db2Error (db2error sqlstate, const char* message); extern void db2Error_d (db2error sqlstate, const char* message, const char* detail, ...); extern SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleType, int line, char* file); -extern void db2free (void* p); /** local prototypes */ void db2FreeEnvHdl (DB2EnvEntry* envp, const char* nls_lang); diff --git a/source/db2GetFdwState.c b/source/db2GetFdwState.c index 2ed0404..2d088c7 100644 --- a/source/db2GetFdwState.c +++ b/source/db2GetFdwState.c @@ -19,9 +19,6 @@ extern DB2Session* db2GetSession (const char* connectstring, char* user, char* extern DB2Table* db2Describe (DB2Session* session, char* schema, char* table, char* pgname, long max_long, char* noencerr, char* batchsz); extern char* db2CopyText (const char* string, int size, int quote); extern char* c2name (short fcType); -extern void* db2alloc (const char* type, size_t size); -extern void db2free (void* p); -extern char* db2strdup (const char* source); /** local prototypes */ DB2FdwState* db2GetFdwState (Oid foreigntableid, double* sample_percent, bool describe); @@ -38,7 +35,7 @@ static void getOptions (Oid foreigntableid, L * "sample_percent" can be NULL, in that case it is not set. */ DB2FdwState* db2GetFdwState (Oid foreigntableid, double* sample_percent, bool describe) { - DB2FdwState* fdwState = db2alloc("fdw_state", sizeof (DB2FdwState)); + DB2FdwState* fdwState = db2alloc(sizeof (DB2FdwState),"DB2FdwState* fdwState"); char* pgtablename = get_rel_name (foreigntableid); List* options = NIL; ListCell* cell = NULL; @@ -120,7 +117,7 @@ DB2FdwState* db2GetFdwState (Oid foreigntableid, double* sample_percent, bool de * "sample_percent" can be NULL, in that case it is not set. */ DB2FdwDirectModifyState* db2GetFdwDirectModifyState (Oid foreigntableid, double* sample_percent, bool describe) { - DB2FdwDirectModifyState* fdwState = db2alloc("fdw_state", sizeof (DB2FdwDirectModifyState)); + DB2FdwDirectModifyState* fdwState = db2alloc(sizeof (DB2FdwDirectModifyState),"DB2FdwDirectModifyState* fdwState"); char* pgtablename = get_rel_name (foreigntableid); List* options = NIL; ListCell* cell = NULL; @@ -200,7 +197,7 @@ static DB2Table* describeForeignTable (Oid foreigntableid, char* schema, char* t db2Entry2(); - db2Table = (DB2Table*)db2alloc("db2_table", sizeof (DB2Table)); + db2Table = (DB2Table*)db2alloc(sizeof (DB2Table),"DB2Table* db2_table"); /* get a complete quoted table name */ qtable = db2CopyText (table, strlen (table), 1); length = strlen (qtable); @@ -208,16 +205,16 @@ static DB2Table* describeForeignTable (Oid foreigntableid, char* schema, char* t qschema = db2CopyText (schema, strlen (schema), 1); length += strlen (qschema) + 1; } - tablename = db2alloc ("db2Table->name", length + 1); + tablename = db2alloc (length + 1,"db2Table->name"); tablename[0] = '\0'; /* empty */ if (schema != NULL) { strncat (tablename, qschema, length); strncat (tablename, ".", length); } strncat (tablename, qtable,length); - db2free (qtable); + db2free (qtable,"qtable"); if (schema != NULL) - db2free (qschema); + db2free (qschema,"qschema"); db2Table->name = tablename; db2Debug3("table description"); @@ -239,7 +236,7 @@ static DB2Table* describeForeignTable (Oid foreigntableid, char* schema, char* t db2Debug3("db2Table->npgcols : %d", db2Table->npgcols); db2Table->ncols = tupdesc->natts; db2Debug3("db2Table->ncols : %d", db2Table->ncols); - db2Table->cols = (DB2Column**) db2alloc ("db2Table->cols", sizeof (DB2Column*) * db2Table->ncols); + db2Table->cols = (DB2Column**) db2alloc(sizeof (DB2Column*) * db2Table->ncols,"DB2Columns* db2Table->cols(%d)",db2Table->ncols); db2Debug3("db2Table->cols : %x", db2Table->cols); /* loop through foreign table columns */ @@ -266,12 +263,12 @@ static DB2Table* describeForeignTable (Oid foreigntableid, char* schema, char* t bool db2nulls_set = false; bool db2codepage_set = false; - db2Table->cols[cidx] = (DB2Column*) db2alloc ("db2Table->cols[cidx]", sizeof (DB2Column)); + db2Table->cols[cidx] = (DB2Column*) db2alloc (sizeof (DB2Column),"DB2Column db2Table->cols[%d]",cidx); db2Table->cols[cidx]->used = 0; db2Table->cols[cidx]->pgattnum = att_tuple->attnum; db2Table->cols[cidx]->pgtype = att_tuple->atttypid; db2Table->cols[cidx]->pgtypmod = att_tuple->atttypmod; - db2Table->cols[cidx]->pgname = db2strdup (NameStr(att_tuple->attname)); + db2Table->cols[cidx]->pgname = db2strdup (NameStr(att_tuple->attname),"db2Table->cols[%d]->pgname",cidx); db2Table->cols[cidx]->colName = db2CopyText ( str_toupper(db2Table->cols[cidx]->pgname, strlen(db2Table->cols[cidx]->pgname), DEFAULT_COLLATION_OID) , strlen(db2Table->cols[cidx]->pgname) , 1 @@ -315,7 +312,7 @@ static DB2Table* describeForeignTable (Oid foreigntableid, char* schema, char* t } if (!db2type_set || !db2size_set || !db2bytes_set || !db2chars_set || !db2scale_set || !db2nulls_set || !db2codepage_set) { db2Debug2("INFO: column %d - %s without required options, discarding db2Table", cidx, db2Table->cols[cidx]->pgname); - db2free (db2Table); + db2free (db2Table,"db2Table"); db2Table = NULL; break; } @@ -447,7 +444,7 @@ static void getColumnData (DB2Table* db2Table, Oid foreigntableid) { db2Table->cols[index - 1]->pgattnum = att_tuple->attnum; db2Table->cols[index - 1]->pgtype = att_tuple->atttypid; db2Table->cols[index - 1]->pgtypmod = att_tuple->atttypmod; - db2Table->cols[index - 1]->pgname = db2strdup (NameStr(att_tuple->attname)); + db2Table->cols[index - 1]->pgname = db2strdup (NameStr(att_tuple->attname),"db2Table->cols[%d - 1]->pgname", index); } /* loop through column options */ diff --git a/source/db2GetForeignJoinPaths.c b/source/db2GetForeignJoinPaths.c index 8ffa747..96a44ea 100644 --- a/source/db2GetForeignJoinPaths.c +++ b/source/db2GetForeignJoinPaths.c @@ -8,8 +8,6 @@ /** external prototypes */ extern char* deparseExpr (PlannerInfo* root, RelOptInfo* foreignrel, Expr* expr, List** params); -extern char* db2strdup (const char* source); -extern void* db2alloc (const char* type, size_t size); /** local prototypes */ void db2GetForeignJoinPaths(PlannerInfo* root, RelOptInfo* joinrel, RelOptInfo* outerrel, RelOptInfo* innerrel, JoinType jointype, JoinPathExtraData* extra); @@ -50,7 +48,7 @@ void db2GetForeignJoinPaths (PlannerInfo * root, RelOptInfo * joinrel, RelOptInf * time considering it again and don't add the same path a second time. * Once we know that this join can be pushed down, we fill the data structure. */ - fdwState = (DB2FdwState *) db2alloc("joinrel->fdw_private", sizeof (DB2FdwState)); + fdwState = (DB2FdwState *) db2alloc(sizeof (DB2FdwState),"DB2FdwState* fdwState"); joinrel->fdw_private = fdwState; @@ -224,12 +222,12 @@ static bool foreign_join_ok (PlannerInfo * root, RelOptInfo * joinrel, JoinType db2Table_o = fdwState_o->db2Table; db2Table_i = fdwState_i->db2Table; - fdwState->db2Table = (DB2Table*) db2alloc("fdw_state->db2Table", sizeof (DB2Table)); - fdwState->db2Table->name = db2strdup (""); - fdwState->db2Table->pgname = db2strdup (""); + fdwState->db2Table = (DB2Table*) db2alloc(sizeof (DB2Table), "fdw_state->db2Table"); + fdwState->db2Table->name = db2strdup ("", "fdwState->db2Table->name"); + fdwState->db2Table->pgname = db2strdup ("", "fdwState->db2Table->pgname"); fdwState->db2Table->ncols = 0; fdwState->db2Table->npgcols = 0; - fdwState->db2Table->cols = (DB2Column **) db2alloc("fdw_state->db2Table->cols[]", (sizeof (DB2Column*) * (db2Table_o->ncols + db2Table_i->ncols))); + fdwState->db2Table->cols = (DB2Column **) db2alloc((sizeof (DB2Column*) * (db2Table_o->ncols + db2Table_i->ncols)), "fdw_state->db2Table->cols[%d]",(db2Table_o->ncols + db2Table_i->ncols)); /* Search db2Column from children's db2Table. * Here we assume that children are foreign table, not foreign join. @@ -272,7 +270,7 @@ static bool foreign_join_ok (PlannerInfo * root, RelOptInfo * joinrel, JoinType } } - newcol = (DB2Column*) db2alloc("fdw_state->db2Table->cols[idx]", sizeof (DB2Column)); + newcol = (DB2Column*) db2alloc(sizeof (DB2Column), "newcol"); if (col) { memcpy (newcol, col, sizeof (struct db2Column)); used_flag = 1; diff --git a/source/db2GetForeignPlan.c b/source/db2GetForeignPlan.c index 7349bd7..c1e081f 100644 --- a/source/db2GetForeignPlan.c +++ b/source/db2GetForeignPlan.c @@ -23,10 +23,6 @@ enum FdwPathPrivateIndex { }; /** external prototypes */ -extern void* db2alloc (const char* type, size_t size); -extern void* db2free (void* pvoid); -extern char* db2strdup (const char* source); - extern bool is_foreign_expr (PlannerInfo* root, RelOptInfo* baserel, Expr* expr); extern void deparseSelectStmtForRel (StringInfo buf, PlannerInfo* root, RelOptInfo* rel, List* tlist, List* remote_conds, List* pathkeys, bool has_final_sort, bool has_limit, bool is_subquery, List** retrieved_attrs, List** params_list); extern List* build_tlist_to_deparse (RelOptInfo* foreignrel); @@ -92,7 +88,7 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo /* examine each SELECT list entry for Var nodes */ db2Debug3("size of tlist: %d", ptlist_len); foreach (cell, ptlist) { - resCol = (DB2ResultColumn*)db2alloc("resultColumn",sizeof(DB2ResultColumn)); + resCol = (DB2ResultColumn*)db2alloc(sizeof(DB2ResultColumn), "resCol"); getUsedColumns ((Expr*) lfirst (cell), foreignrel, resCol); db2Debug3("resCol->colName: %s", resCol->colName); db2Debug3("resCol->pgattnum: %d", resCol->pgattnum); @@ -103,13 +99,13 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo db2Debug3("fpinfo->resultList: %x", fpinfo->resultList); } else { db2Debug3("about to free resCol: %x, colName: %s, pgattnum: %d", resCol, resCol->colName, resCol->pgattnum); - db2free(resCol); + db2free(resCol,"resCol"); } } for (resCol = fpinfo->resultList; resCol; resCol = resCol->next) { iResCol++; } - cols = (DB2ResultColumn**)db2alloc("resultColumns", iResCol+1 * sizeof(DB2ResultColumn*)); + cols = (DB2ResultColumn**)db2alloc(iResCol+1 * sizeof(DB2ResultColumn*),"cols(%d)",iResCol+1); iResCol = 0; for (resCol = fpinfo->resultList; resCol; resCol = resCol->next) { db2Debug2("resCol: %x", resCol); @@ -204,7 +200,7 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo /* examine each condition for Tlist nodes; they come in the correct sequence as in the query and do not need to be sorted */ db2Debug3("size of tlist: %d", ptlist_len); foreach (cell, ptlist) { - DB2ResultColumn* resCol = (DB2ResultColumn*)db2alloc("resultColumn",sizeof(DB2ResultColumn)); + DB2ResultColumn* resCol = (DB2ResultColumn*)db2alloc(sizeof(DB2ResultColumn),"DB2ResultColumn* resCol"); db2Debug3("examine tlist"); resCol->next = fpinfo->resultList; fpinfo->resultList = resCol; @@ -313,7 +309,7 @@ static void getUsedColumns (Expr* expr, RelOptInfo* foreignrel, DB2ResultColumn* // add all columns but the last one here for (index = 0; index < (fpinfo->db2Table->ncols - 1); index++) { if (fpinfo->db2Table->cols[index]->pgname) { - tmpCol = (DB2ResultColumn*)db2alloc("resultColumn",sizeof(DB2ResultColumn)); + tmpCol = (DB2ResultColumn*)db2alloc(sizeof(DB2ResultColumn),"tmpCol"); tmpCol->resnum = index+1; copyCol2Result(tmpCol,fpinfo->db2Table->cols[index]); db2Debug4("db2Table[%d]->colName %s added to result list", index, fpinfo->db2Table->cols[index]->colName); @@ -366,7 +362,7 @@ static void getUsedColumns (Expr* expr, RelOptInfo* foreignrel, DB2ResultColumn* if (aggname && strcmp(aggname, "count") == 0) { DB2FdwState* fpinfo = (DB2FdwState*) foreignrel->fdw_private; /* if it's a COUNT(*) then we need an additional result */ - DB2Column* col = db2alloc("DB2Column for count(*)",sizeof(DB2Column)); + DB2Column* col = db2alloc(sizeof(DB2Column),"DB2Column* col"); db2Debug4("found COUNT aggregate"); col->colName = "count"; col->colType = -5; // SQL_BIGINT type in DB2, which can hold the result of COUNT(*) @@ -556,7 +552,7 @@ static void getUsedColumns (Expr* expr, RelOptInfo* foreignrel, DB2ResultColumn* static void copyCol2Result(DB2ResultColumn* resCol, DB2Column* column) { db2Entry4(); if (resCol && resCol->colName == NULL) { - resCol->colName = db2strdup(column->colName); + resCol->colName = db2strdup(column->colName,"resCol->colName"); resCol->colType = column->colType; resCol->colSize = column->colSize; resCol->colScale = column->colScale; @@ -566,7 +562,7 @@ static void copyCol2Result(DB2ResultColumn* resCol, DB2Column* column) { resCol->colPrimKeyPart = column->colPrimKeyPart; resCol->colCodepage = column->colCodepage; resCol->pgbaserelid = column->pgrelid; - resCol->pgname = db2strdup(column->pgname); + resCol->pgname = db2strdup(column->pgname,"resCol->pgname"); resCol->pgattnum = column->pgattnum; resCol->pgtype = column->pgtype; resCol->pgtypmod = column->pgtypmod; diff --git a/source/db2GetForeignPlanOld.c b/source/db2GetForeignPlanOld.c index 6e54088..e08083d 100644 --- a/source/db2GetForeignPlanOld.c +++ b/source/db2GetForeignPlanOld.c @@ -11,8 +11,6 @@ /** external prototypes */ extern List* serializePlanData (DB2FdwState* fdwState); extern void checkDataType (short db2type, int scale, Oid pgtype, const char* tablename, const char* colname); -extern void db2free (void* p); -extern char* db2strdup (const char* p); extern char* deparseExpr (PlannerInfo* root, RelOptInfo* rel, Expr* expr, List** params); extern char* get_jointype_name (JoinType jointype); extern List* build_tlist_to_deparse (RelOptInfo* foreignrel); @@ -47,11 +45,11 @@ ForeignScan* db2GetForeignPlan (PlannerInfo* root, RelOptInfo* foreignrel, Oid f /* for base relations, set scan_relid as the relid of the relation */ scan_relid = foreignrel->relid; /* check if the foreign scan is for an UPDATE or DELETE */ -#if PG_VERSION_NUM < 140000 + #if PG_VERSION_NUM < 140000 if (foreignrel->relid == root->parse->resultRelation && (root->parse->commandType == CMD_UPDATE || root->parse->commandType == CMD_DELETE)) { -#else + #else if (bms_is_member(foreignrel->relid, root->all_result_relids) && (root->parse->commandType == CMD_UPDATE || root->parse->commandType == CMD_DELETE)) { -#endif /* PG_VERSION_NUM */ + #endif /* PG_VERSION_NUM */ /* we need the table's primary key columns */ need_keys = true; } @@ -239,7 +237,7 @@ static void createQuery (PlannerInfo* root, RelOptInfo* foreignrel, bool modify, db2Debug2(" append modify clause: %s", query.data); /* get a copy of the where clause without single quoted string literals */ - wherecopy = db2strdup (query.data); + wherecopy = db2strdup (query.data,"wherecopy"); for (p = wherecopy; *p != '\0'; ++p) { if (*p == '\'') in_quote = !in_quote; @@ -258,7 +256,7 @@ static void createQuery (PlannerInfo* root, RelOptInfo* foreignrel, bool modify, } } - db2free (wherecopy); + db2free (wherecopy,"wherecopy"); /* * Calculate MD5 hash of the query string so far. @@ -276,9 +274,9 @@ if (!pg_md5_hash (query.data, strlen (query.data), md5)) { /* add comment with MD5 hash to query */ initStringInfo (&result); appendStringInfo (&result, "SELECT /*%s*/ %s", md5, query.data); - db2free (query.data); - fdwState->query = (result.len > 0) ? db2strdup(result.data) : NULL; - db2free(result.data); + db2free (query.data,"query.data"); + fdwState->query = (result.len > 0) ? db2strdup(result.data,"result.data") : NULL; + db2free(result.data,"result.data"); db2Debug2(" query: %s",fdwState->query); db2Debug1("< createQuery"); } diff --git a/source/db2GetForeignRelSize.c b/source/db2GetForeignRelSize.c index 0788a3c..796d0d6 100644 --- a/source/db2GetForeignRelSize.c +++ b/source/db2GetForeignRelSize.c @@ -14,7 +14,6 @@ /** external prototypes */ extern DB2FdwState* db2GetFdwState (Oid foreigntableid, double* sample_percent, bool describe); extern char* deparseWhereConditions (PlannerInfo* root, RelOptInfo* baserel); -extern void db2free (void* p); extern void classifyConditions (PlannerInfo* root, RelOptInfo* baserel, List* input_conds, List** remote_conds, List** local_conds); extern void estimate_path_cost_size (PlannerInfo* root, RelOptInfo* foreignrel, List* param_join_conds, List* pathkeys, DB2FdwPathExtraData* fpextra, double* p_rows, int* p_width, int* p_disabled_nodes, Cost* p_startup_cost, Cost* p_total_cost); @@ -64,7 +63,7 @@ static void db2PopulateFdwStateOld(PlannerInfo* root, RelOptInfo* baserel, Oid f fdwState->where_clause = deparseWhereConditions ( root, baserel ); /* release DB2 session (will be cached) */ - db2free (fdwState->session); + db2free (fdwState->session,"fdwState->session"); fdwState->session = NULL; /* use a random "high" value for cost */ fdwState->startup_cost = 10000.0; diff --git a/source/db2GetForeignUpperPaths.c b/source/db2GetForeignUpperPaths.c index 16e958b..528ca62 100644 --- a/source/db2GetForeignUpperPaths.c +++ b/source/db2GetForeignUpperPaths.c @@ -23,9 +23,6 @@ #endif /** external prototypes */ -extern void* db2alloc (const char* type, size_t size); -extern char* db2strdup (const char* source); -extern void* db2free (void* p); extern List* build_tlist_to_deparse (RelOptInfo* foreignrel); extern char* deparseExpr (PlannerInfo* root, RelOptInfo* foreignrel, Expr* expr, List** params); extern bool is_shippable (Oid objectId, Oid classId, DB2FdwState* fpinfo); @@ -158,27 +155,27 @@ static void db2CloneFdwStateUpper(PlannerInfo* root, RelOptInfo* input_rel, RelO db2Entry4(); if (fdw_in != NULL) { - copy = (DB2FdwState*) db2alloc("fdw_state_upper", sizeof(DB2FdwState)); + copy = (DB2FdwState*) db2alloc(sizeof(DB2FdwState), "copy"); /* Connection/session fields */ - copy->dbserver = fdw_in->dbserver ? db2strdup(fdw_in->dbserver) : NULL; - copy->user = fdw_in->user ? db2strdup(fdw_in->user) : NULL; - copy->password = fdw_in->password ? db2strdup(fdw_in->password) : NULL; - copy->jwt_token = fdw_in->jwt_token ? db2strdup(fdw_in->jwt_token) : NULL; - copy->nls_lang = fdw_in->nls_lang ? db2strdup(fdw_in->nls_lang) : NULL; + copy->dbserver = fdw_in->dbserver ? db2strdup(fdw_in->dbserver, "copy->dbserver") : NULL; + copy->user = fdw_in->user ? db2strdup(fdw_in->user, "copy->user") : NULL; + copy->password = fdw_in->password ? db2strdup(fdw_in->password, "copy->password") : NULL; + copy->jwt_token = fdw_in->jwt_token ? db2strdup(fdw_in->jwt_token,"copy->jwt_token ") : NULL; + copy->nls_lang = fdw_in->nls_lang ? db2strdup(fdw_in->nls_lang, "copy->nls_lang") : NULL; /* Planner-time session handle can be shared (it is not serialized). */ copy->session = fdw_in->session; /* Planning/execution fields */ - copy->query = fdw_in->query ? db2strdup(fdw_in->query) : NULL; + copy->query = fdw_in->query ? db2strdup(fdw_in->query,copy->query) : NULL; copy->prefetch = fdw_in->prefetch; copy->startup_cost = fdw_in->startup_cost; copy->total_cost = fdw_in->total_cost; copy->rowcount = 0; copy->temp_cxt = NULL; - copy->order_clause = fdw_in->order_clause ? db2strdup(fdw_in->order_clause) : NULL; - copy->where_clause = fdw_in->where_clause ? db2strdup(fdw_in->where_clause) : NULL; + copy->order_clause = fdw_in->order_clause ? db2strdup(fdw_in->order_clause,"copy->order_clause") : NULL; + copy->where_clause = fdw_in->where_clause ? db2strdup(fdw_in->where_clause,"copy->where_clause") : NULL; /* Shallow-copy expression lists (Expr nodes are immutable at this stage), but ensure list cells are independent because createQuery mutates the list. */ copy->params = fdw_in->params ? list_copy(fdw_in->params) : NIL; @@ -207,16 +204,16 @@ static DB2Table* db2CloneDb2TableForPlan(const DB2Table* src) { db2Entry4(); if (src != NULL) { - dst = (DB2Table*) db2alloc("db2_table_clone", sizeof(DB2Table)); + dst = (DB2Table*) db2alloc(sizeof(DB2Table),"DB2Table* dst"); - dst->name = src->name ? db2strdup(src->name) : NULL; - dst->pgname = src->pgname ? db2strdup(src->pgname) : NULL; + dst->name = src->name ? db2strdup(src->name,"dst->name") : NULL; + dst->pgname = src->pgname ? db2strdup(src->pgname,"dst->pgname") : NULL; dst->batchsz = src->batchsz; dst->ncols = src->ncols; dst->npgcols = src->npgcols; if (src->ncols > 0) { - dst->cols = (DB2Column**) db2alloc("db2_table_clone->cols", sizeof(DB2Column*) * src->ncols); + dst->cols = (DB2Column**) db2alloc(sizeof(DB2Column*) * src->ncols,"dst->cols(%d)",src->ncols); for (i = 0; i < src->ncols; ++i) { dst->cols[i] = db2CloneDb2ColumnForPlan(src->cols[i]); } @@ -233,12 +230,12 @@ static DB2Column* db2CloneDb2ColumnForPlan(const DB2Column* src) { db2Entry4(); if (src != NULL) { - dst = (DB2Column*) db2alloc("db2_column_clone", sizeof(DB2Column)); + dst = (DB2Column*) db2alloc( sizeof(DB2Column),"DB2Column* dst"); /* start with a struct copy, then fix up pointer members */ *dst = *src; - dst->colName = src->colName ? db2strdup(src->colName) : NULL; - dst->pgname = src->pgname ? db2strdup(src->pgname) : NULL; + dst->colName = src->colName ? db2strdup(src->colName,"dst->colName") : NULL; + dst->pgname = src->pgname ? db2strdup(src->pgname,"dst->pgname") : NULL; } db2Exit4(": %x", dst); return dst; @@ -403,7 +400,7 @@ static void add_foreign_ordered_paths(PlannerInfo *root, RelOptInfo *input_rel, /* Safe to push down */ fpinfo->pushdown_safe = true; /* Construct PgFdwPathExtraData */ - fpextra = db2alloc("add_foreign_ordered_path:fpextra",sizeof(DB2FdwPathExtraData)); + fpextra = db2alloc(sizeof(DB2FdwPathExtraData),"fpextra"); fpextra->target = root->upper_targets[UPPERREL_ORDERED]; fpextra->has_final_sort = true; /* Estimate the costs of performing the final sort remotely */ @@ -602,7 +599,7 @@ static void add_foreign_final_paths(PlannerInfo *root, RelOptInfo *input_rel, Re fpinfo->pushdown_safe = true; /* Construct DB2FdwPathExtraData */ - fpextra = db2alloc("add_foreign_final_path:fpextra",sizeof(DB2FdwPathExtraData)); + fpextra = db2alloc(sizeof(DB2FdwPathExtraData),"fpextra"); fpextra->target = root->upper_targets[UPPERREL_FINAL]; fpextra->has_final_sort = has_final_sort; fpextra->has_limit = extra->limit_needed; diff --git a/source/db2GetLob.c b/source/db2GetLob.c index c7aad96..3d61504 100644 --- a/source/db2GetLob.c +++ b/source/db2GetLob.c @@ -8,8 +8,6 @@ extern char db2Message[ERRBUFSIZE];/* contains DB2 error messages, set by db2CheckErr() */ /** external prototypes */ -extern void* db2alloc (const char* type, size_t size); -extern void* db2realloc (void* p, size_t size); extern SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleType, int line, char* file); extern void db2Error_d (db2error sqlstate, const char* message, const char* detail, ...); @@ -59,14 +57,14 @@ void db2GetLob (DB2Session* session, DB2ResultColumn* column, char** value, long db2Debug2("extend : %d", extend); if (*value_len == 0) { if (extend > 0) { - *value = db2alloc ("lob_value", *value_len + extend + 1); + *value = db2alloc (*value_len + extend + 1,"*value"); } else { *value = NULL; db2Debug3("not allocating space since the LOB value is apparently NULL"); } } else { // do not add another 0 termination byte, since we already have one - *value = db2realloc (*value, *value_len + extend); + *value = db2realloc (*value_len + extend, *value, "*value"); } // append the buffer read to the value excluding 0 termination byte db2Debug2("*value : %x", *value); diff --git a/source/db2GetSession.c b/source/db2GetSession.c index fdc48a3..4941a99 100644 --- a/source/db2GetSession.c +++ b/source/db2GetSession.c @@ -6,7 +6,6 @@ extern DB2EnvEntry* rootenvEntry; /* contains DB2 error messages, set by db2CheckErr() */ /** external prototypes */ -extern void* db2alloc (const char* type, size_t size); extern DB2ConnEntry* db2AllocConnHdl (DB2EnvEntry* envp,const char* srvname, char* user, char* password, char* jwt_token, const char* nls_lang); extern DB2EnvEntry* db2AllocEnvHdl (const char* nls_lang); extern DB2EnvEntry* findenvEntry (DB2EnvEntry* start, const char* nlslang); @@ -50,7 +49,7 @@ DB2Session* db2GetSession (const char* srvname, char* user, char* password, char } /* allocate a data structure pointing to the cached entries */ - session = db2alloc("session", sizeof (DB2Session)); + session = db2alloc(sizeof (DB2Session),"DB2Session* session"); session->envp = envp; session->connp = connp; session->stmtp = NULL; diff --git a/source/db2GetShareFileName.c b/source/db2GetShareFileName.c index 3b9185b..3e0f0a6 100644 --- a/source/db2GetShareFileName.c +++ b/source/db2GetShareFileName.c @@ -6,7 +6,6 @@ #include "db2_fdw.h" /** external prototypes */ -extern void* db2alloc (const char* type, size_t size); /** local prototypes */ char* db2GetShareFileName(const char *relativename); @@ -15,10 +14,12 @@ char* db2GetShareFileName(const char *relativename); * Returns the allocated absolute path of a file in the "share" directory. */ char* db2GetShareFileName (const char *relativename) { - char share_path[MAXPGPATH], *result; + char share_path[MAXPGPATH]; + char* result = NULL; + db2Entry1(); get_share_path(my_exec_path, share_path); - result = db2alloc("sharedFileName", MAXPGPATH); + result = db2alloc(MAXPGPATH,"result[%d]",MAXPGPATH); snprintf(result, MAXPGPATH, "%s/%s", share_path, relativename); db2Exit1(": %s",result); return result; diff --git a/source/db2ImportForeignSchema.c b/source/db2ImportForeignSchema.c index ec77bde..15928ed 100644 --- a/source/db2ImportForeignSchema.c +++ b/source/db2ImportForeignSchema.c @@ -10,8 +10,6 @@ extern DB2Session* db2GetSession (const char* connectstring, char* user, char* password, char* jwt_token, const char* nls_lang, int curlevel); extern char* guessNlsLang (char* nls_lang); extern short c2dbType (short fcType); -extern void db2free (void* p); -extern char* db2strdup (const char* source); extern bool isForeignSchema (DB2Session* session, char* schema); extern char** getForeignTableList (DB2Session* session, char* schema, int list_type, char* table_list); extern DB2Table* describeForeignTable (DB2Session* session, char* schema, char* tabname); @@ -114,17 +112,17 @@ List* db2ImportForeignSchema (ImportForeignSchemaStmt* stmt, Oid serverOid) { db2Debug2("import table_list: %s",tblist.data); } tablist = getForeignTableList(session, stmt->remote_schema, stmt->list_type, tblist.data); - db2free (tblist.data); + db2free (tblist.data,"tblist.data"); for (int i = 0; tablist[i] != NULL; i++) { DB2Table* db2Table = describeForeignTable(session, stmt->remote_schema, tablist[i]); if (db2Table != NULL) { generateForeignTableCreate(&buf, server->servername, stmt->local_schema, stmt->remote_schema, db2Table, foldcase, readonly); db2Debug2("pg fdw table ddl: '%s'",buf.data); - result = lappend (result, db2strdup (buf.data)); + result = lappend (result, db2strdup (buf.data,"buf.data")); resetStringInfo (&buf); } } - db2free (tablist); + db2free (tablist,"tablist"); } db2Exit1(": %d", list_length(result)); return result; @@ -137,7 +135,7 @@ static char* fold_case (char *name, fold_t foldcase) { char* result = NULL; db2Entry4("(name: '%s', foldcase: %d)", name, foldcase); if (foldcase == CASE_KEEP) { - result = db2strdup (name); + result = db2strdup (name,"result"); } else { if (foldcase == CASE_LOWER) { result = str_tolower (name, strlen (name), DEFAULT_COLLATION_OID); @@ -148,7 +146,7 @@ static char* fold_case (char *name, fold_t foldcase) { if (strcmp (upstr, name) == 0) result = str_tolower (name, strlen (name), DEFAULT_COLLATION_OID); else - result = db2strdup (name); + result = db2strdup (name,"result"); } } } @@ -172,14 +170,14 @@ static void generateForeignTableCreate(StringInfo buf, char* servername, char* l , local_schema , foldedname ); - db2free (foldedname); + db2free (foldedname,"foldedname"); for (int i = 0; i < db2Table->ncols; i++) { appendStringInfo(buf, (firstcol) ? "" : ", "); /* column name */ foldedname = fold_case (db2Table->cols[i]->colName, foldcase); appendStringInfo (buf, "\"%s\" ", foldedname); - db2free (foldedname); + db2free (foldedname,"foldedname"); // check charlen is not 0; set it to 1 in that case db2Table->cols[i]->colSize = db2Table->cols[i]->colSize == 0 ? 1 : db2Table->cols[i]->colSize; diff --git a/source/db2ImportForeignSchemaData.c b/source/db2ImportForeignSchemaData.c index 24d7274..a1185d0 100644 --- a/source/db2ImportForeignSchemaData.c +++ b/source/db2ImportForeignSchemaData.c @@ -10,10 +10,6 @@ extern char db2Message[ERRBUFSIZE];/* contains DB2 error messages, set b extern int err_code; /* error code, set by db2CheckErr() */ /** external prototypes */ -extern void* db2alloc (const char* type, size_t size); -extern void* db2realloc (void* p, size_t size); -extern void db2free (void* p); -extern char* db2strdup (const char* source); extern char* db2CopyText (const char* string, int size, int quote); extern SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleType, int line, char* file); extern void db2Error_d (db2error sqlstate, const char* message, const char* detail, ...); @@ -110,21 +106,21 @@ char** getForeignTableList(DB2Session* session, char* schema, int list_type, cha case 0: { /* FDW_IMPORT_SCHEMA_ALL */ char* query_str = "SELECT T.TABNAME FROM SYSCAT.TABLES T WHERE T.TABSCHEMA = ? AND T.TYPE IN ('T','V') ORDER BY T.TABNAME"; int s_len = strlen(query_str)+1; - column_query = db2alloc("column_query",s_len); + column_query = db2alloc(s_len, "column_query"); strncpy(column_query,query_str,s_len); } break; case 1: { /* FDW_IMPORT_SCHEMA_LIMIT_TO */ char* query_str = "SELECT T.TABNAME FROM SYSCAT.TABLES T WHERE T.TABSCHEMA = ? AND T.TYPE IN ('T','V') AND T.TABNAME IN (%s) ORDER BY T.TABNAME"; int s_len = strlen(query_str) + strlen(table_list) + 1; - column_query = db2alloc("column_query",s_len); + column_query = db2alloc(s_len, "column_query"); snprintf(column_query,s_len,query_str,table_list); } break; case 2: { /* FDW_IMPORT_SCHEMA_EXCEPT */ char* query_str = "SELECT T.TABNAME FROM SYSCAT.TABLES T WHERE T.TABSCHEMA = ? AND T.TYPE IN ('T','V') AND T.TABNAME NOT IN (%s) ORDER BY T.TABNAME"; int s_len = strlen(query_str) + strlen(table_list) + 1; - column_query = db2alloc("column_query",s_len); + column_query = db2alloc(s_len, "column_query"); snprintf(column_query,s_len,query_str,table_list); } break; @@ -171,12 +167,12 @@ char** getForeignTableList(DB2Session* session, char* schema, int list_type, cha if (rc != SQL_SUCCESS && rc != SQL_NO_DATA) { db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLFetch failed to execute column query", db2Message); } - tabnames = (char**) db2alloc("tabnames", (tabidx + 1) * sizeof(char*)); + tabnames = (char**) db2alloc( (tabidx + 1) * sizeof(char*), "tabnames"); while(rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO) { tabnames[tabidx] = NULL; db2Debug2("tabname[%d] : '%s', ind: %d", tabidx, tab_buf, ind_tab); if (ind_tab != SQL_NULL_DATA) { - char* tabname = (char*) db2alloc("tabname", strlen((char*)tab_buf)+1); + char* tabname = (char*) db2alloc(strlen((char*)tab_buf)+1, "tabname"); strncpy(tabname, (char*)tab_buf, strlen((char*)tab_buf)+1); tabnames[tabidx] = tabname; } @@ -186,13 +182,13 @@ char** getForeignTableList(DB2Session* session, char* schema, int list_type, cha db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error importing foreign schema: SQLFetch failed to execute column query", db2Message); } tabidx++; - tabnames = (char**) db2realloc(tabnames, (tabidx + 1) * sizeof(char*)); + tabnames = (char**) db2realloc((tabidx + 1) * sizeof(char*), tabnames, "tabnames"); } tabnames[tabidx] = NULL; /* release the statement handle */ db2FreeStmtHdl(stmtp, session->connp); stmtp = NULL; - db2free(column_query); + db2free(column_query,"column_query"); db2Exit1(": [%d]", tabidx-1); return tabnames; } @@ -230,20 +226,20 @@ DB2Table* describeForeignTable (DB2Session* session, char* schema, char* tabname qschema = db2CopyText (schema, strlen (schema), 1); length += strlen (qschema) + 1; } - tablename = db2alloc ("reply->name", length + 1); + tablename = db2alloc (length + 1,"tablename"); tablename[0] = '\0'; /* empty */ if (schema != NULL) { strncat (tablename, qschema,length); strncat (tablename, ".",length); } strncat (tablename, qtable,length); - db2free (qtable); + db2free (qtable,"qtable"); if (schema != NULL) - db2free (qschema); + db2free (qschema,"qschema"); /* construct a "SELECT * FROM ..." query to describe columns */ length += 40; - query = db2alloc ("query", length + 1); + query = db2alloc (length + 1, "query"); snprintf ((char*)query, length+1, (char*)"SELECT * FROM %s FETCH FIRST 1 ROW ONLY", tablename); /* create statement handle */ @@ -266,10 +262,10 @@ DB2Table* describeForeignTable (DB2Session* session, char* schema, char* tabname else db2Error_d (FDW_UNABLE_TO_CREATE_REPLY, "error describing remote table: SQLExecute failed to describe table", db2Message); } - db2free(query); + db2free(query,"query"); /* allocate an db2Table struct for the results */ - reply = db2alloc ("reply", sizeof (DB2Table)); + reply = db2alloc (sizeof (DB2Table),"DB2Table* reply"); reply->name = tabname; db2Debug2("table description"); db2Debug2("reply->name : '%s'", reply->name); @@ -283,13 +279,13 @@ DB2Table* describeForeignTable (DB2Session* session, char* schema, char* tabname } reply->ncols = ncols; - reply->cols = (DB2Column**) db2alloc ("reply->cols", sizeof (DB2Column*) *reply->ncols); + reply->cols = (DB2Column**) db2alloc (sizeof (DB2Column*) *reply->ncols,"reply->cols(%d)",reply->ncols); db2Debug2("reply->ncols : %d", reply->ncols); /* loop through the column list */ for (i = 1; i <= reply->ncols; ++i) { /* allocate an db2Column struct for the column */ - reply->cols[i - 1] = (DB2Column *) db2alloc (" reply->cols[i - 1]", sizeof (DB2Column)); + reply->cols[i - 1] = (DB2Column *) db2alloc (sizeof (DB2Column), "reply->cols[%d - 1]", i); reply->cols[i - 1]->colPrimKeyPart = 0; reply->cols[i - 1]->colCodepage = 0; reply->cols[i - 1]->pgname = NULL; @@ -315,7 +311,7 @@ DB2Table* describeForeignTable (DB2Session* session, char* schema, char* tabname if (rc != SQL_SUCCESS) { db2Error_d (FDW_UNABLE_TO_CREATE_REPLY, "error describing remote table: SQLDescribeCol failed to get column data", db2Message); } - reply->cols[i - 1]->colName = db2strdup((char*)colName); + reply->cols[i - 1]->colName = db2strdup((char*)colName,"reply->cols[%d - 1]->colName",i); db2Debug2("reply->cols[%d]->colName : '%s'", (i-1), reply->cols[i - 1]->colName); db2Debug2("dataType: %d", dataType); reply->cols[i - 1]->colType = (short) dataType; diff --git a/source/db2PlanForeignModify.c b/source/db2PlanForeignModify.c index 0a352d5..3a1dbcd 100644 --- a/source/db2PlanForeignModify.c +++ b/source/db2PlanForeignModify.c @@ -9,8 +9,6 @@ #include "DB2Column.h" /** external prototypes */ -extern char* db2strdup (const char* source); -extern void* db2alloc (const char* type, size_t size); extern DB2FdwState* db2GetFdwState (Oid foreigntableid, double* sample_percent, bool describe); extern short c2dbType (short fcType); extern void appendAsType (StringInfoData* dest, Oid type); @@ -280,7 +278,7 @@ List* db2PlanForeignModify (PlannerInfo* root, ModifyTable* plan, Index resultRe checkDataType (fdwState->db2Table->cols[i]->colType, fdwState->db2Table->cols[i]->colScale, fdwState->db2Table->cols[i]->pgtype, fdwState->db2Table->pgname, fdwState->db2Table->cols[i]->pgname); /* create a new entry in the parameter list */ - param = (ParamDesc *) db2alloc("fdwState->paramList->next", sizeof (ParamDesc)); + param = (ParamDesc *) db2alloc(sizeof (ParamDesc),"param"); param->type = fdwState->db2Table->cols[i]->pgtype; param->bindType = BIND_OUTPUT; param->value = NULL; @@ -314,23 +312,23 @@ static DB2FdwState* copyPlanData (DB2FdwState* orig) { DB2FdwState* copy = NULL; db2Entry4(); - copy = db2alloc("copy_fdw_state", sizeof (DB2FdwState)); - copy->dbserver = db2strdup(orig->dbserver); - copy->user = db2strdup(orig->user); - copy->password = db2strdup(orig->password); - copy->nls_lang = db2strdup(orig->nls_lang); + copy = db2alloc(sizeof (DB2FdwState), "DB2FdwState* copy"); + copy->dbserver = db2strdup(orig->dbserver, "copy->dbserver"); + copy->user = db2strdup(orig->user, "copy->user"); + copy->password = db2strdup(orig->password, "copy->password"); + copy->nls_lang = db2strdup(orig->nls_lang, "copy->nls_lang"); copy->session = NULL; copy->query = NULL; copy->paramList = NULL; - copy->db2Table = (DB2Table*) db2alloc("copy_fdw_state->db2Table", sizeof (DB2Table)); - copy->db2Table->name = db2strdup(orig->db2Table->name); - copy->db2Table->pgname = db2strdup(orig->db2Table->pgname); + copy->db2Table = (DB2Table*) db2alloc( sizeof (DB2Table),"copy->db2Table"); + copy->db2Table->name = db2strdup(orig->db2Table->name,"copy->db2Table->name"); + copy->db2Table->pgname = db2strdup(orig->db2Table->pgname,"copy->db2Table->pgname"); copy->db2Table->ncols = orig->db2Table->ncols; copy->db2Table->npgcols = orig->db2Table->npgcols; - copy->db2Table->cols = (DB2Column**) db2alloc("copy_fdw_state->db2Table->cols",sizeof (DB2Column*) * orig->db2Table->ncols); + copy->db2Table->cols = (DB2Column**) db2alloc(sizeof (DB2Column*) * orig->db2Table->ncols,"copy->db2Table->cols(%d)",orig->db2Table->ncols); for (i = 0; i < orig->db2Table->ncols; ++i) { - copy->db2Table->cols[i] = (DB2Column*) db2alloc("copy_fdw_state->db2Table->cols[i]", sizeof (DB2Column)); - copy->db2Table->cols[i]->colName = db2strdup(orig->db2Table->cols[i]->colName); + copy->db2Table->cols[i] = (DB2Column*) db2alloc( sizeof (DB2Column),"copy->db2Table->cols[%d]",i); + copy->db2Table->cols[i]->colName = db2strdup(orig->db2Table->cols[i]->colName,"copy->db2Table->cols[%d]->colName",i); copy->db2Table->cols[i]->colType = orig->db2Table->cols[i]->colType; copy->db2Table->cols[i]->colSize = orig->db2Table->cols[i]->colSize; copy->db2Table->cols[i]->colScale = orig->db2Table->cols[i]->colScale; @@ -339,10 +337,7 @@ static DB2FdwState* copyPlanData (DB2FdwState* orig) { copy->db2Table->cols[i]->colBytes = orig->db2Table->cols[i]->colBytes; copy->db2Table->cols[i]->colPrimKeyPart = orig->db2Table->cols[i]->colPrimKeyPart; copy->db2Table->cols[i]->colCodepage = orig->db2Table->cols[i]->colCodepage; - if (orig->db2Table->cols[i]->pgname == NULL) - copy->db2Table->cols[i]->pgname = NULL; - else - copy->db2Table->cols[i]->pgname = db2strdup(orig->db2Table->cols[i]->pgname); + copy->db2Table->cols[i]->pgname = db2strdup(orig->db2Table->cols[i]->pgname,"copy->db2Table->cols[%d]->pgname",i); copy->db2Table->cols[i]->pgattnum = orig->db2Table->cols[i]->pgattnum; copy->db2Table->cols[i]->pgtype = orig->db2Table->cols[i]->pgtype; copy->db2Table->cols[i]->pgtypmod = orig->db2Table->cols[i]->pgtypmod; @@ -371,8 +366,8 @@ void addParam (ParamDesc **paramList, DB2Column* db2col, int colnum, int txts) { db2Debug2("colType: %d",db2col->colType); db2Debug2("colnum: %d",colnum); db2Debug2("txts: %d",txts); - param = db2alloc("paramList->next",sizeof (ParamDesc)); - param->colName = db2strdup(db2col->colName); + param = db2alloc(sizeof (ParamDesc), "param"); + param->colName = db2strdup(db2col->colName,"param->colName"); db2Debug2("param->colName: '%s'",param->colName); param->colType = db2col->colType; db2Debug2("param->colType: '%d'",param->colType); diff --git a/source/db2PrepareQuery.c b/source/db2PrepareQuery.c index e168edf..debf79b 100644 --- a/source/db2PrepareQuery.c +++ b/source/db2PrepareQuery.c @@ -17,7 +17,6 @@ extern void db2Error_d (db2error sqlstate, const char* message extern HdlEntry* db2AllocStmtHdl (SQLSMALLINT type, DB2ConnEntry* connp, db2error error, const char* errmsg); extern SQLSMALLINT c2param (SQLSMALLINT fparamType); extern char* param2name (SQLSMALLINT fparamType); -extern void* db2alloc (const char* type, size_t size); /** internal prototypes */ void db2PrepareQuery (DB2Session* session, const char *query, DB2ResultColumn* resultList, unsigned long prefetch, int fetchsize); diff --git a/source/db2ReAllocFree.c b/source/db2ReAllocFree.c index 94e987e..fd42513 100644 --- a/source/db2ReAllocFree.c +++ b/source/db2ReAllocFree.c @@ -1,52 +1,76 @@ #include -#include -#include #include "db2_fdw.h" /*+ external prototypes */ /** local prototypes */ -void* db2alloc (const char* type, size_t size); -void* db2realloc (void* p, size_t size); -void db2free (void* p); -char* db2strdup (const char* source); -/* db2alloc +/* db2Alloc * Expose palloc0() to DB2 functions. */ -void* db2alloc (const char* type, size_t size) { - void* memory = palloc0(size); - db2Debug5("++ %x: %d bytes - %s", memory, size, type); +void* db2Alloc (size_t size,const char* message, ...) { + void* memory = palloc0(size); + + if (db2IsLogEnabled(DB2DEBUG5)) { + char cBuffer[4000]; + va_list arg_marker; + va_start(arg_marker, message); + vsnprintf(cBuffer, sizeof(cBuffer), message, arg_marker); + db2Debug5("++ %s: %x: %d bytes - %s", cBuffer, memory, size, memory); + va_end (arg_marker); + } return memory; } -/* db2realloc +/* db2ReAlloc * Expose repalloc() to DB2 functions. */ -void* db2realloc (void* p, size_t size) { - void* memory = repalloc(p, size); - db2Debug5("++ %x: %d bytes", memory, size); +void* db2ReAlloc (size_t size, void* p, const char* message, ...) { + void* memory = repalloc(p, size); + + if (db2IsLogEnabled(DB2DEBUG5)) { + char cBuffer[4000]; + va_list arg_marker; + va_start(arg_marker, message); + vsnprintf(cBuffer, sizeof(cBuffer), message, arg_marker); + db2Debug5("++ %s: %x: %d bytes - %x", cBuffer, memory, size, p); + va_end (arg_marker); + } return memory; } -/* db2free +/* db2Free * Expose pfree() to DB2 functions. */ -void db2free (void* p) { +void db2Free (void* p, const char* message, ...) { + if (db2IsLogEnabled(DB2DEBUG5) && p != NULL) { + char cBuffer[4000]; + va_list arg_marker; + va_start(arg_marker, message); + vsnprintf(cBuffer, sizeof(cBuffer), message, arg_marker); + db2Debug5("-- %s: %x", cBuffer, p); + va_end (arg_marker); + } if (p != NULL) { - db2Debug5("-- %x", p); pfree (p); } } -/* db2strdup +/* db2StrDup * Expose pstrdup() to DB2 functions. */ -char* db2strdup(const char* source) { - char* target = NULL; +char* db2StrDup(const char* source, const char* message, ...) { + char* target = NULL; if (source != NULL && source[0] != '\0') { target = pstrdup(source); } - db2Debug5("++ %x: dup'ed string from %x content '%s'",target, source, source); + if (db2IsLogEnabled(DB2DEBUG5) && source != NULL && source[0] != '\0') { + char cBuffer[4000]; + va_list arg_marker; + va_start(arg_marker, message); + vsnprintf(cBuffer, sizeof(cBuffer), message, arg_marker); + db2Debug5("++ %s: %x: dup'ed string from %x content source: '%s' target: '%s'",cBuffer, target, source, source, target); + va_end (arg_marker); + } return target; } \ No newline at end of file diff --git a/source/db2_de_serialize.c b/source/db2_de_serialize.c index a186a83..b7ac76f 100644 --- a/source/db2_de_serialize.c +++ b/source/db2_de_serialize.c @@ -5,7 +5,6 @@ #include "DB2FdwState.h" /** external prototypes */ -extern void* db2alloc (const char* type, size_t size); extern char* c2name (short fcType); /** local prototypes */ @@ -21,7 +20,7 @@ static Const* serializeLong (long i); * Extract the data structures from a List created by serializePlanData. */ DB2FdwState* deserializePlanData (List* list) { - DB2FdwState* state = db2alloc ("DB2FdwState", sizeof (DB2FdwState)); + DB2FdwState* state = db2alloc (sizeof(DB2FdwState),"DB2FdwState"); int idx = 0; int i = 0; int len = 0; @@ -59,17 +58,17 @@ DB2FdwState* deserializePlanData (List* list) { /* relation_name */ state->relation_name = deserializeString(list_nth(list, idx++)); /* table data */ - state->db2Table = (DB2Table*) db2alloc ("state->db2Table", sizeof (struct db2Table)); + state->db2Table = (DB2Table*) db2alloc (sizeof (struct db2Table),"state->db2Table"); state->db2Table->name = deserializeString(list_nth(list, idx++)); state->db2Table->pgname = deserializeString(list_nth(list, idx++)); state->db2Table->batchsz = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); state->db2Table->ncols = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); state->db2Table->npgcols = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); - state->db2Table->cols = (DB2Column**) db2alloc ("state->db2Table->cols", sizeof (DB2Column*) * state->db2Table->ncols); + state->db2Table->cols = (DB2Column**) db2alloc (sizeof (DB2Column*) * state->db2Table->ncols,"state->db2Table->cols"); /* loop columns */ for (i = 0; i < state->db2Table->ncols; ++i) { - state->db2Table->cols[i] = (DB2Column *) db2alloc ("state->db2Table->cols[i]", sizeof (DB2Column)); + state->db2Table->cols[i] = (DB2Column *) db2alloc (sizeof (DB2Column), "state->db2Table->cols[i]"); state->db2Table->cols[i]->colName = deserializeString(list_nth(list, idx++)); db2Debug3("deserialize col[%d].colName: %s" ,i, state->db2Table->cols[i]->colName); state->db2Table->cols[i]->colType = (short) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); @@ -112,7 +111,7 @@ DB2FdwState* deserializePlanData (List* list) { /* parameter table entries */ state->paramList = NULL; for (i = 0; i < len; ++i) { - param = (ParamDesc*) db2alloc ("state->parmList->next", sizeof (ParamDesc)); + param = (ParamDesc*) db2alloc (sizeof (ParamDesc),"state->parmList->next"); param->colName = deserializeString(list_nth(list, idx++)); db2Debug3("deserialize param[%d].colName: %s" ,i, param->colName); param->colType = (short) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); @@ -145,7 +144,7 @@ DB2FdwState* deserializePlanData (List* list) { /* parameter table entries */ state->resultList = NULL; for (i = 0; i < len; ++i) { - DB2ResultColumn* res = (DB2ResultColumn *) db2alloc ("state->resultList->next", sizeof (DB2ResultColumn)); + DB2ResultColumn* res = (DB2ResultColumn *) db2alloc (sizeof (DB2ResultColumn),"state->resultList->next"); res->colName = deserializeString(list_nth(list, idx++)); db2Debug3("deserialize res[%d].colName: %s" ,i, res->colName); res->colType = (short) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); @@ -180,7 +179,7 @@ DB2FdwState* deserializePlanData (List* list) { db2Debug3("deserialize res[%d].noencerr: %ld" ,i, res->noencerr); res->resnum = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); db2Debug3("deserialize res[%d].resnum: %d" ,i, res->resnum); - res->val = (char*) db2alloc ("res->val", res->val_size + 1); + res->val = (char*) db2alloc (res->val_size + 1, "res->val"); res->val_len = 0; res->val_null = 1; res->next = state->resultList; diff --git a/source/db2_deparse.c b/source/db2_deparse.c index 79610b5..60784b3 100644 --- a/source/db2_deparse.c +++ b/source/db2_deparse.c @@ -87,9 +87,6 @@ typedef struct deparse_expr_cxt { /** external prototypes */ extern short c2dbType (short fcType); -extern void* db2alloc (const char* type, size_t size); -extern void* db2strdup (const char* source); -extern void db2free (void* p); extern bool is_shippable (Oid objectId, Oid classId, DB2FdwState* fpinfo); extern EquivalenceMember* find_em_for_rel (PlannerInfo* root, EquivalenceClass* ec, RelOptInfo* rel); extern EquivalenceMember* find_em_for_rel_target (PlannerInfo* root, EquivalenceClass* ec, RelOptInfo* rel); @@ -1687,7 +1684,7 @@ char* deparseWhereConditions (PlannerInfo* root, RelOptInfo * rel) { /* append new WHERE clause to query string */ appendStringInfo (&where_clause, " %s %s", keyword, where); keyword = "AND"; - db2free (where); + db2free (where, "where"); } else { fdwState->local_conds = lappend (fdwState->local_conds, ((RestrictInfo*) lfirst (cell))->clause); } @@ -1883,7 +1880,7 @@ char* deparseExpr (PlannerInfo* root, RelOptInfo* rel, Expr* expr, List** params char* retValue = NULL; db2Entry1(); if (expr != NULL) { - deparse_expr_cxt* ctx = db2alloc("deparseExpr.context", sizeof(deparse_expr_cxt)); + deparse_expr_cxt* ctx = db2alloc(sizeof(deparse_expr_cxt),"deparseExpr.context"); StringInfoData buf; initStringInfo(&buf); @@ -1894,9 +1891,9 @@ char* deparseExpr (PlannerInfo* root, RelOptInfo* rel, Expr* expr, List** params ctx->buf = &buf; ctx->params_list = params; deparseExprInt(expr, ctx); - retValue = (buf.len > 0) ? db2strdup(buf.data) : NULL; - db2free(ctx->buf->data); - db2free(ctx); + retValue = (buf.len > 0) ? db2strdup(buf.data,"buf.data") : NULL; + db2free(ctx->buf->data,"ctx->buf->data"); + db2free(ctx, "ctx"); } db2Exit1(": %s", retValue); return retValue; @@ -2167,7 +2164,7 @@ static void deparseOpExpr (OpExpr* expr, deparse_expr_cxt* if (!HeapTupleIsValid (tuple)) { elog (ERROR, "cache lookup failed for operator %u", expr->opno); } - opername = db2strdup (((Form_pg_operator) GETSTRUCT (tuple))->oprname.data); + opername = db2strdup (((Form_pg_operator) GETSTRUCT (tuple))->oprname.data,"opername"); oprkind = ((Form_pg_operator) GETSTRUCT (tuple))->oprkind; leftargtype = ((Form_pg_operator) GETSTRUCT (tuple))->oprleft; rightargtype = ((Form_pg_operator) GETSTRUCT (tuple))->oprright; @@ -2226,7 +2223,7 @@ static void deparseOpExpr (OpExpr* expr, deparse_expr_cxt* /* the other operators have the same name in DB2 */ appendStringInfo (ctx->buf, "(%s %s %s)", left, opername, right); } - db2free(right); + db2free(right,"right"); } } else { /* unary operator */ @@ -2239,7 +2236,7 @@ static void deparseOpExpr (OpExpr* expr, deparse_expr_cxt* appendStringInfo (ctx->buf, "(%s%s)", opername, left); } } - db2free(left); + db2free(left,"left"); } } else { /* cannot translate this operator */ @@ -2248,7 +2245,7 @@ static void deparseOpExpr (OpExpr* expr, deparse_expr_cxt* } } } - db2free (opername); + db2free (opername,"opername"); db2Exit1(); } @@ -2263,7 +2260,7 @@ static void deparseScalarArrayOpExpr (ScalarArrayOpExpr* expr, deparse_expr_cxt* if (!HeapTupleIsValid (tuple)) { elog (ERROR, "cache lookup failed for operator %u", expr->opno); } - opername = db2strdup(((Form_pg_operator) GETSTRUCT (tuple))->oprname.data); + opername = db2strdup(((Form_pg_operator) GETSTRUCT (tuple))->oprname.data,"opername"); leftargtype = ((Form_pg_operator) GETSTRUCT (tuple))->oprleft; schema = ((Form_pg_operator) GETSTRUCT (tuple))->oprnamespace; ReleaseSysCache (tuple); @@ -2304,7 +2301,7 @@ static void deparseScalarArrayOpExpr (ScalarArrayOpExpr* expr, deparse_expr_cxt* initStringInfo(&buf); if (constant->constisnull) { appendStringInfo(&buf, "NULL"); - right = db2strdup(buf.data); + right = db2strdup(buf.data,"buf.data"); } else { Datum datum; bool isNull; @@ -2337,10 +2334,10 @@ static void deparseScalarArrayOpExpr (ScalarArrayOpExpr* expr, deparse_expr_cxt* bResult = false; } if (bResult) { - right = db2strdup(buf.data); + right = db2strdup(buf.data,"buf.data"); } } - db2free(buf.data); + db2free(buf.data,"buf.data"); } break; case T_ArrayCoerceExpr: { @@ -2370,7 +2367,7 @@ static void deparseScalarArrayOpExpr (ScalarArrayOpExpr* expr, deparse_expr_cxt* element = deparseExpr (ctx->root, ctx->foreignrel, (Expr*) lfirst (cell), ctx->params_list); if (element == NULL) { /* if any element cannot be converted, give up */ - db2free(buf.data); + db2free(buf.data,"buf.data"); bResult = false; break; } @@ -2380,12 +2377,12 @@ static void deparseScalarArrayOpExpr (ScalarArrayOpExpr* expr, deparse_expr_cxt* db2Debug2("first_arg: %s", first_arg ? "true" : "false"); if (first_arg) { /* don't push down empty arrays, since the semantics for NOT x = ANY() differ */ - db2free(buf.data); + db2free(buf.data,"buf.data"); bResult = false; break; } - right = (bResult) ? db2strdup(buf.data) : NULL; - db2free(buf.data); + right = (bResult) ? db2strdup(buf.data,"buf.data") : NULL; + db2free(buf.data,"buf.data"); } break; default: { @@ -2398,8 +2395,8 @@ static void deparseScalarArrayOpExpr (ScalarArrayOpExpr* expr, deparse_expr_cxt* if (bResult) { appendStringInfo (ctx->buf, "(%s %s IN (%s))",left, expr->useOr ? "" : "NOT", right); } - db2free(left); - db2free(right); + db2free(left,"left"); + db2free(right,"right"); } } } @@ -2431,9 +2428,9 @@ static void deparseDistinctExpr (DistinctExpr* expr, deparse_expr_cxt* if (right != NULL) { appendStringInfo (ctx->buf, "( %s IS DISTINCT FROM %s)", left, right); } - db2free(right); + db2free(right,"right"); } - db2free(left); + db2free(left,"left"); } db2Exit1(); } @@ -2490,9 +2487,9 @@ static void deparseNullIfExpr (NullIfExpr* expr, deparse_expr_cxt* if (right != NULL) { appendStringInfo (ctx->buf, "NULLIF(%s,%s)", left, right); } - db2free(right); + db2free(right,"right"); } - db2free(left); + db2free(left,"left"); } db2Exit1(": %s", ctx->buf->data); } @@ -2509,7 +2506,7 @@ static void deparseBoolExpr (BoolExpr* expr, deparse_expr_cxt* bool bBreak = false; appendStringInfo (&buf, "(%s%s", expr->boolop == NOT_EXPR ? "NOT " : "", arg); for_each_cell(cell, expr->args, lnext(expr->args, list_head(expr->args))) { - db2free(arg); + db2free(arg,"arg"); arg = deparseExpr (ctx->root, ctx->foreignrel, (Expr*)lfirst(cell), ctx->params_list); if (arg != NULL) { appendStringInfo (&buf, " %s %s", expr->boolop == AND_EXPR ? "AND":"OR", arg); @@ -2522,8 +2519,8 @@ static void deparseBoolExpr (BoolExpr* expr, deparse_expr_cxt* appendStringInfo (ctx->buf, "%s)", buf.data); } } - db2free(buf.data); - db2free(arg); + db2free(buf.data,"buf.data"); + db2free(arg,"arg"); db2Exit1(": %s", ctx->buf->data); } @@ -2596,7 +2593,7 @@ static void deparseCaseExpr (CaseExpr* expr, deparse_expr_cxt* if (!bBreak) { appendStringInfo(ctx->buf,"%s",buf.data); } - db2free(buf.data); + db2free(buf.data,"buf.data"); } db2Exit1(": %s", ctx->buf->data); } @@ -2626,7 +2623,7 @@ static void deparseCoalesceExpr (CoalesceExpr* expr, deparse_expr_cxt* if (arg != NULL) { appendStringInfo (ctx->buf, "%s)",result.data); } - db2free(result.data); + db2free(result.data,"result.data"); } db2Exit1(": %s", ctx->buf->data); } @@ -2649,7 +2646,7 @@ static void deparseFuncExpr (FuncExpr* expr, deparse_expr_cxt* if (!HeapTupleIsValid (tuple)) { elog (ERROR, "cache lookup failed for function %u", expr->funcid); } - opername = db2strdup (((Form_pg_proc) GETSTRUCT (tuple))->proname.data); + opername = db2strdup (((Form_pg_proc) GETSTRUCT (tuple))->proname.data,"opername"); db2Debug2("opername: %s",opername); schema = ((Form_pg_proc) GETSTRUCT (tuple))->pronamespace; db2Debug2("schema: %d",schema); @@ -2699,7 +2696,7 @@ static void deparseFuncExpr (FuncExpr* expr, deparse_expr_cxt* if (arg != NULL) { appendStringInfo (&buf, "%s%s", (first_arg) ? ", " : "",arg); first_arg = false; - db2free(arg); + db2free(arg,"arg"); } else { ok = false; db2Debug2("T_FuncExpr: function %s that we cannot render for DB2", opername); @@ -2711,7 +2708,7 @@ static void deparseFuncExpr (FuncExpr* expr, deparse_expr_cxt* if (ok) { appendStringInfo(ctx->buf,"%s",buf.data); } - db2free(buf.data); + db2free(buf.data,"buf.data"); } else if (strcmp (opername, "date_part") == 0) { char* left = NULL; @@ -2735,12 +2732,12 @@ static void deparseFuncExpr (FuncExpr* expr, deparse_expr_cxt* } else { appendStringInfo (ctx->buf, "EXTRACT(%s FROM %s)", left + 1, right); } - db2free(right); + db2free(right,"right"); } else { db2Debug2("T_FuncExpr: function %s that we cannot render for DB2", opername); } } - db2free (left); + db2free (left,"left"); } else if (strcmp (opername, "now") == 0 || strcmp (opername, "transaction_timestamp") == 0) { /* special case: current timestamp */ appendStringInfo (ctx->buf, "(CAST (?/*:now*/ AS TIMESTAMP))"); @@ -2749,7 +2746,7 @@ static void deparseFuncExpr (FuncExpr* expr, deparse_expr_cxt* db2Debug2("T_FuncExpr: function %s that we cannot render for DB2", opername); } } - db2free (opername); + db2free (opername,"opername"); } db2Exit1(": %s", ctx->buf->data); } @@ -2918,7 +2915,7 @@ static void deparseAggref (Aggref* expr, deparse_expr_cxt* break; } appendStringInfo(&result, "%s%s", cbuf.data, first_arg ? "" : ", "); - db2free(cbuf.data); + db2free(cbuf.data,"cbuf.data"); first_arg = false; } } @@ -2928,7 +2925,7 @@ static void deparseAggref (Aggref* expr, deparse_expr_cxt* db2Debug2("parsed aggref so far: %s", result.data); db2Debug2("could not deparse aggregate args"); } - db2free(result.data); + db2free(result.data,"result.data"); } } } diff --git a/source/db2_fdw_utils.c b/source/db2_fdw_utils.c index 4cd65bf..59eeb83 100644 --- a/source/db2_fdw_utils.c +++ b/source/db2_fdw_utils.c @@ -38,9 +38,6 @@ typedef struct { extern void db2GetLob (DB2Session* session, DB2ResultColumn* column, char** value, long* value_len, unsigned long trunc); extern void db2Shutdown (void); extern short c2dbType (short fcType); -extern void* db2alloc (const char* type, size_t size); -extern void* db2strdup (const char* source); -extern void db2free (void* p); /** local prototypes */ bool optionIsTrue (const char *value); @@ -80,7 +77,7 @@ char* guessNlsLang (char *nls_lang) { db2Entry4("(nls_lang: %s)", nls_lang); initStringInfo (&buf); if (nls_lang == NULL) { - server_encoding = db2strdup (GetConfigOption ("server_encoding", false, true)); + server_encoding = db2strdup (GetConfigOption ("server_encoding", false, true),"server_encoding"); /* find an DB2 client character set that matches the database encoding */ if (strcmp (server_encoding, "UTF8") == 0) charset = "AL32UTF8"; @@ -150,8 +147,8 @@ char* guessNlsLang (char *nls_lang) { ) ); } - db2free(server_encoding); - lc_messages = db2strdup (GetConfigOption ("lc_messages", false, true)); + db2free(server_encoding,"server_encoding"); + lc_messages = db2strdup (GetConfigOption ("lc_messages", false, true),"lc_messages"); /* try to guess those for which there is a backend translation */ if (strncmp (lc_messages, "de_", 3) == 0 || pg_strncasecmp (lc_messages, "german", 6) == 0) language = "GERMAN_GERMANY"; @@ -176,7 +173,7 @@ char* guessNlsLang (char *nls_lang) { if (strncmp (lc_messages, "zh_TW", 5) == 0 || pg_strncasecmp (lc_messages, "chinese-traditional", 19) == 0) language = "TRADITIONAL CHINESE_TAIWAN"; appendStringInfo (&buf, "NLS_LANG=%s.%s", language, charset); - db2free(lc_messages); + db2free(lc_messages,"lc_messages"); } else { appendStringInfo (&buf, "NLS_LANG=%s", nls_lang); } @@ -287,7 +284,7 @@ void convertTuple (DB2Session* session, DB2Table* db2Table, DB2ResultColumn* res /* fill the TupleSlot with the data (after conversion if necessary) */ if (res->pgtype == BYTEAOID) { /* binary columns are not converted */ - bytea* result = (bytea*) db2alloc ("bytea", value_len + VARHDRSZ); + bytea* result = (bytea*) db2alloc (value_len + VARHDRSZ,"result"); memcpy (VARDATA (result), value, value_len); SET_VARSIZE (result, value_len + VARHDRSZ); @@ -336,7 +333,7 @@ void convertTuple (DB2Session* session, DB2Table* db2Table, DB2ResultColumn* res db2Type = c2dbType(res->colType); if (db2Type == DB2_BLOB || db2Type == DB2_CLOB) { if (value != NULL) { - db2free (value); + db2free (value,"value"); } else { db2Debug5("not freeing value, since it is null"); } From ac4736b003f9079f884c69e1c7b2de49d38dd9a6 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Sun, 19 Apr 2026 15:16:14 +0200 Subject: [PATCH 101/120] fixing query like "select empno, firstnme,lastname, salary + bonus + comm from sample.employee where salary > 8000 and lastname like 'L%';" --- source/db2GetForeignPlan.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/source/db2GetForeignPlan.c b/source/db2GetForeignPlan.c index c1e081f..4cfb638 100644 --- a/source/db2GetForeignPlan.c +++ b/source/db2GetForeignPlan.c @@ -68,9 +68,15 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo db2Debug2("length of tlist: %d", list_length(tlist)); -// ptlist = build_tlist_to_deparse(foreignrel); - ptlist = make_tlist_from_pathtarget(foreignrel->reltarget); - apply_pathtarget_labeling_to_tlist(ptlist, foreignrel->reltarget); + /* + * fdw_scan_tlist must contain all base Vars required to evaluate local + * quals and to compute any non-pushed-down target expressions. + * + * Using a non-flattened PathTarget tlist here can leave out Vars that are + * only referenced inside expressions (e.g. salary + bonus + comm), which can + * lead to setrefs.c errors like "variable not found in subplan target list". + */ + ptlist = build_tlist_to_deparse(foreignrel); ptlist_len = list_length(ptlist); if (IS_SIMPLE_REL(foreignrel)) { From e4c56726d0a130f185729bc0ce9a70e2b2d1d18a Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Sun, 3 May 2026 14:18:10 +0200 Subject: [PATCH 102/120] porting fixes for #110, #111, #112 and #113 into this branch --- include/db2_fdw.h | 9 ++++---- source/db2AnalyzeForeignTable.c | 6 ++--- source/db2ExecForeignDelete.c | 4 ++-- source/db2ExecForeignInsert.c | 4 ++-- source/db2ExecForeignUpdate.c | 4 ++-- source/db2GetLob.c | 8 +++---- source/db2ImportForeignSchema.c | 35 +++++++++++++++++++++++------ source/db2ImportForeignSchemaData.c | 8 +++---- source/db2IterateForeignScan.c | 4 ++-- source/db2_de_serialize.c | 2 +- source/db2_fdw_utils.c | 11 ++++----- 11 files changed, 56 insertions(+), 39 deletions(-) diff --git a/include/db2_fdw.h b/include/db2_fdw.h index a00b10b..1cccc20 100644 --- a/include/db2_fdw.h +++ b/include/db2_fdw.h @@ -28,11 +28,6 @@ #error "This extension requires PostgreSQL version 13.0 or higher." #endif -/* defined in backend/commands/analyze.c */ -#ifndef WIDTH_THRESHOLD -#define WIDTH_THRESHOLD 1024 -#endif /* WIDTH_THRESHOLD */ - #ifndef PQ_QUERY_PARAM_MAX_LIMIT #define PQ_QUERY_PARAM_MAX_LIMIT 65535 #endif /* PQ_QUERY_PARAM_MAX_LIMIT */ @@ -277,4 +272,8 @@ extern void db2Debug (int level, const char* message, ...) __attribute__ ((fo #define db2LogError(fmt, ...) \ db2Debug(DB2LERROR, "e %s:%d:%s " fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) +#ifndef MIN +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#endif + #endif \ No newline at end of file diff --git a/source/db2AnalyzeForeignTable.c b/source/db2AnalyzeForeignTable.c index 3c06212..94f54c6 100644 --- a/source/db2AnalyzeForeignTable.c +++ b/source/db2AnalyzeForeignTable.c @@ -18,7 +18,7 @@ extern int db2ExecuteQuery (DB2Session* session, ParamDesc* p extern int db2FetchNext (DB2Session* session); extern void checkDataType (short db2type, int scale, Oid pgtype, const char* tablename, const char* colname); extern short c2dbType (short fcType); -extern void convertTuple (DB2Session* session, DB2Table* db2Table, DB2ResultColumn* reslist, int natts, Datum* values, bool* nulls, bool trunc_lob); +extern void convertTuple (DB2Session* session, DB2Table* db2Table, DB2ResultColumn* reslist, int natts, Datum* values, bool* nulls); /** local prototypes */ bool db2AnalyzeForeignTable(Relation relation, AcquireSampleRowsFunc* func, BlockNumber* totalpages); @@ -115,7 +115,7 @@ static int acquireSampleRowsFunc (Relation relation, int elevel, HeapTuple* rows /* the first "targrows" rows are added as samples */ /* use a temporary memory context during convertTuple */ old_cxt = MemoryContextSwitchTo (tmp_cxt); - convertTuple (fdw_state->session,fdw_state->db2Table,fdw_state->resultList, tupDesc->natts, values, nulls, true); + convertTuple (fdw_state->session,fdw_state->db2Table,fdw_state->resultList, tupDesc->natts, values, nulls); MemoryContextSwitchTo (old_cxt); rows[collected_rows++] = heap_form_tuple (tupDesc, values, nulls); MemoryContextReset (tmp_cxt); @@ -131,7 +131,7 @@ static int acquireSampleRowsFunc (Relation relation, int elevel, HeapTuple* rows heap_freetuple (rows[k]); /* use a temporary memory context during convertTuple */ old_cxt = MemoryContextSwitchTo (tmp_cxt); - convertTuple (fdw_state->session,fdw_state->db2Table,fdw_state->resultList, tupDesc->natts, values, nulls, true); + convertTuple (fdw_state->session,fdw_state->db2Table,fdw_state->resultList, tupDesc->natts, values, nulls); MemoryContextSwitchTo (old_cxt); rows[k] = heap_form_tuple (tupDesc, values, nulls); MemoryContextReset (tmp_cxt); diff --git a/source/db2ExecForeignDelete.c b/source/db2ExecForeignDelete.c index e07f176..ec8c594 100644 --- a/source/db2ExecForeignDelete.c +++ b/source/db2ExecForeignDelete.c @@ -12,7 +12,7 @@ extern regproc* output_funcs; /** external prototypes */ extern int db2ExecuteQuery (DB2Session* session, ParamDesc* paramList); extern void db2Debug (int level, const char* message, ...); -extern void convertTuple (DB2Session* session, DB2Table* db2Table, DB2ResultColumn* reslist, int natts, Datum* values, bool* nulls, bool trunc_lob); +extern void convertTuple (DB2Session* session, DB2Table* db2Table, DB2ResultColumn* reslist, int natts, Datum* values, bool* nulls); extern char* deparseDate (Datum datum); extern char* deparseTimestamp (Datum datum, bool hasTimezone); @@ -58,7 +58,7 @@ TupleTableSlot* db2ExecForeignDelete (EState* estate, ResultRelInfo* rinfo, Tupl ExecClearTuple (slot); /* convert result for RETURNING to arrays of values and null indicators */ - convertTuple (fdw_state->session,fdw_state->db2Table,fdw_state->resultList, slot->tts_tupleDescriptor->natts, slot->tts_values, slot->tts_isnull, false); + convertTuple (fdw_state->session,fdw_state->db2Table,fdw_state->resultList, slot->tts_tupleDescriptor->natts, slot->tts_values, slot->tts_isnull); /* store the virtual tuple */ ExecStoreVirtualTuple (slot); diff --git a/source/db2ExecForeignInsert.c b/source/db2ExecForeignInsert.c index 5abe4f7..0d9b0e1 100644 --- a/source/db2ExecForeignInsert.c +++ b/source/db2ExecForeignInsert.c @@ -12,7 +12,7 @@ extern bool dml_in_transaction; /** external prototypes */ extern int db2ExecuteInsert (DB2Session* session, ParamDesc* paramList); extern void setModifyParameters (ParamDesc* paramList, TupleTableSlot* newslot, TupleTableSlot* oldslot, DB2Table* db2Table, DB2Session* session); -extern void convertTuple (DB2Session* session, DB2Table* db2Table, DB2ResultColumn* reslist, int natts, Datum* values, bool* nulls, bool trunc_lob); +extern void convertTuple (DB2Session* session, DB2Table* db2Table, DB2ResultColumn* reslist, int natts, Datum* values, bool* nulls); /** local prototypes */ TupleTableSlot* db2ExecForeignInsert(EState* estate, ResultRelInfo* rinfo, TupleTableSlot* slot, TupleTableSlot* planSlot); @@ -50,7 +50,7 @@ TupleTableSlot* db2ExecForeignInsert (EState* estate, ResultRelInfo* rinfo, Tupl ExecClearTuple (slot); /* convert result for RETURNING to arrays of values and null indicators */ - convertTuple (fdw_state->session,fdw_state->db2Table,fdw_state->resultList, slot->tts_tupleDescriptor->natts, slot->tts_values, slot->tts_isnull, false); + convertTuple (fdw_state->session,fdw_state->db2Table,fdw_state->resultList, slot->tts_tupleDescriptor->natts, slot->tts_values, slot->tts_isnull); /* store the virtual tuple */ ExecStoreVirtualTuple (slot); diff --git a/source/db2ExecForeignUpdate.c b/source/db2ExecForeignUpdate.c index 51ce935..e95333e 100644 --- a/source/db2ExecForeignUpdate.c +++ b/source/db2ExecForeignUpdate.c @@ -12,7 +12,7 @@ extern bool dml_in_transaction; /** external prototypes */ extern int db2ExecuteQuery (DB2Session* session, ParamDesc* paramList); extern void setModifyParameters (ParamDesc* paramList, TupleTableSlot* newslot, TupleTableSlot* oldslot, DB2Table* db2Table, DB2Session* session); -extern void convertTuple (DB2Session* session, DB2Table* db2Table, DB2ResultColumn* reslist, int natts, Datum* values, bool* nulls, bool trunc_lob); +extern void convertTuple (DB2Session* session, DB2Table* db2Table, DB2ResultColumn* reslist, int natts, Datum* values, bool* nulls); /** local prototypes */ TupleTableSlot* db2ExecForeignUpdate (EState* estate, ResultRelInfo* rinfo, TupleTableSlot* slot, TupleTableSlot* planSlot); @@ -55,7 +55,7 @@ TupleTableSlot* db2ExecForeignUpdate (EState* estate, ResultRelInfo* rinfo, Tupl ExecClearTuple (slot); /* convert result for RETURNING to arrays of values and null indicators */ - convertTuple (fdw_state->session,fdw_state->db2Table,fdw_state->resultList, slot->tts_tupleDescriptor->natts, slot->tts_values, slot->tts_isnull, false); + convertTuple (fdw_state->session,fdw_state->db2Table,fdw_state->resultList, slot->tts_tupleDescriptor->natts, slot->tts_values, slot->tts_isnull); /* store the virtual tuple */ ExecStoreVirtualTuple (slot); diff --git a/source/db2GetLob.c b/source/db2GetLob.c index 3d61504..336e260 100644 --- a/source/db2GetLob.c +++ b/source/db2GetLob.c @@ -12,16 +12,16 @@ extern SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQ extern void db2Error_d (db2error sqlstate, const char* message, const char* detail, ...); /** internal prototypes */ -void db2GetLob (DB2Session* session, DB2ResultColumn* column, char** value, long* value_len, unsigned long trunc); +void db2GetLob (DB2Session* session, DB2ResultColumn* column, char** value, long* value_len); /* db2GetLob * Get the LOB contents and store them in *value and *value_len. - * If "trunc" is nonzero, it contains the number of bytes or characters to get. */ -void db2GetLob (DB2Session* session, DB2ResultColumn* column, char** value, long* value_len, unsigned long trunc) { +void db2GetLob (DB2Session* session, DB2ResultColumn* column, char** value, long* value_len) { SQLRETURN rc = SQL_SUCCESS; SQLLEN ind = 0; SQLCHAR buf[LOB_CHUNK_SIZE+1]; + SQLSMALLINT fcType = (column->colType == DB2_CLOB) ? SQL_C_CHAR : SQL_C_BINARY; int extend = 0; db2Entry1(); db2Debug2("column->colName: '%s'",column->colName); @@ -32,7 +32,7 @@ void db2GetLob (DB2Session* session, DB2ResultColumn* column, char** value, long do { db2Debug2("value_len: %ld",*value_len); db2Debug2("reading %d byte chunck of data",sizeof(buf)); - rc = SQLGetData(session->stmtp->hsql, column->resnum, SQL_C_CHAR, buf, sizeof(buf), &ind); + rc = SQLGetData(session->stmtp->hsql, column->resnum, fcType, buf, sizeof(buf), &ind); rc = db2CheckErr(rc,session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); if (rc == SQL_ERROR) { db2Error_d ( FDW_UNABLE_TO_CREATE_EXECUTION, "error fetching result: SQLGetData failed to read LOB chunk", db2Message); diff --git a/source/db2ImportForeignSchema.c b/source/db2ImportForeignSchema.c index 15928ed..521718d 100644 --- a/source/db2ImportForeignSchema.c +++ b/source/db2ImportForeignSchema.c @@ -99,15 +99,36 @@ List* db2ImportForeignSchema (ImportForeignSchemaStmt* stmt, Oid serverOid) { if (stmt->list_type != FDW_IMPORT_SCHEMA_ALL) { foreach (cell, stmt->table_list) { - RangeVar* rVar = lfirst(cell); + RangeVar* rVar = lfirst(cell); + char* uppername = NULL; + char* folded = NULL; db2Debug2("rVar : %x ", rVar); - if (rVar != NULL) { - db2Debug2("rVar->type : %d ", rVar->type); - db2Debug2("rVar->catalogname: '%s'", rVar->catalogname); - db2Debug2("rVar->schemaname : '%s'", rVar->schemaname); - db2Debug2("rVar->relname : '%s'", rVar->relname); - appendStringInfo(&tblist,"%s'%s'",((tblist.len == 0) ? "" : ","),rVar->relname); + if (rVar == NULL || rVar->relname == NULL) + continue; + + /* + * IMPORTANT: the table list in LIMIT TO / EXCEPT is compared against the + * names that will be created in PostgreSQL after case folding. + * + * So we normalize rVar->relname in-place using fold_case() so that + * PostgreSQL's LIMIT/EXCEPT filtering and our generated CREATE FOREIGN + * TABLE statements agree. + * + * For the DB2-side query filter we use an uppercased version of the name + * and the DB2 query itself compares with UPPER(T.TABNAME), making the + * matching case-insensitive. + */ + uppername = str_toupper (rVar->relname, strlen (rVar->relname), DEFAULT_COLLATION_OID); + if (tblist.len != 0) { + appendStringInfo(&tblist,",'%s'", uppername); + } else { + appendStringInfo(&tblist,"'%s'", uppername); } + db2free (uppername,"uppername"); + + folded = fold_case (rVar->relname, foldcase); + rVar->relname = folded; + } db2Debug2("import table_list: %s",tblist.data); } diff --git a/source/db2ImportForeignSchemaData.c b/source/db2ImportForeignSchemaData.c index a1185d0..1cd3117 100644 --- a/source/db2ImportForeignSchemaData.c +++ b/source/db2ImportForeignSchemaData.c @@ -104,21 +104,21 @@ char** getForeignTableList(DB2Session* session, char* schema, int list_type, cha db2Entry1("(schema: '%s', list_type: %d, table_list: '%s')", schema, list_type, table_list); switch(list_type){ case 0: { /* FDW_IMPORT_SCHEMA_ALL */ - char* query_str = "SELECT T.TABNAME FROM SYSCAT.TABLES T WHERE T.TABSCHEMA = ? AND T.TYPE IN ('T','V') ORDER BY T.TABNAME"; + char* query_str = "SELECT T.TABNAME FROM SYSCAT.TABLES T WHERE UPPER(T.TABSCHEMA) = UPPER(?) AND T.TYPE IN ('T','V') ORDER BY T.TABNAME"; int s_len = strlen(query_str)+1; column_query = db2alloc(s_len, "column_query"); strncpy(column_query,query_str,s_len); } break; case 1: { /* FDW_IMPORT_SCHEMA_LIMIT_TO */ - char* query_str = "SELECT T.TABNAME FROM SYSCAT.TABLES T WHERE T.TABSCHEMA = ? AND T.TYPE IN ('T','V') AND T.TABNAME IN (%s) ORDER BY T.TABNAME"; + char* query_str = "SELECT T.TABNAME FROM SYSCAT.TABLES T WHERE UPPER(T.TABSCHEMA) = UPPER(?) AND T.TYPE IN ('T','V') AND UPPER(T.TABNAME) IN (%s) ORDER BY T.TABNAME"; int s_len = strlen(query_str) + strlen(table_list) + 1; column_query = db2alloc(s_len, "column_query"); snprintf(column_query,s_len,query_str,table_list); } break; case 2: { /* FDW_IMPORT_SCHEMA_EXCEPT */ - char* query_str = "SELECT T.TABNAME FROM SYSCAT.TABLES T WHERE T.TABSCHEMA = ? AND T.TYPE IN ('T','V') AND T.TABNAME NOT IN (%s) ORDER BY T.TABNAME"; + char* query_str = "SELECT T.TABNAME FROM SYSCAT.TABLES T WHERE UPPER(T.TABSCHEMA) = UPPER(?) AND T.TYPE IN ('T','V') AND UPPER(T.TABNAME) NOT IN (%s) ORDER BY T.TABNAME"; int s_len = strlen(query_str) + strlen(table_list) + 1; column_query = db2alloc(s_len, "column_query"); snprintf(column_query,s_len,query_str,table_list); @@ -444,7 +444,7 @@ static void describeForeignColumns(DB2Session* session, char* schema, char* tabn SQLLEN ind_cp; SQLLEN ind_s = SQL_NTS; SQLLEN ind_t = SQL_NTS; - char* query = "SELECT COALESCE(C.KEYSEQ, 0) AS KEY, C.CODEPAGE FROM SYSCAT.COLUMNS C WHERE C.TABSCHEMA = ? AND C.TABNAME = ? AND COALESCE(C.HIDDEN,'') = '' ORDER BY C.COLNO"; + char* query = "SELECT COALESCE(C.KEYSEQ, 0) AS KEY, C.CODEPAGE FROM SYSCAT.COLUMNS C WHERE UPPER(C.TABSCHEMA) = UPPER(?) AND UPPER(C.TABNAME) = UPPER(?) AND COALESCE(C.HIDDEN,'') = '' ORDER BY C.COLNO"; db2Entry1("(schema: %s, tabname: %s)", schema, tabname); db2Debug2("query : '%s'", query); diff --git a/source/db2IterateForeignScan.c b/source/db2IterateForeignScan.c index 22c04c0..a7954f1 100644 --- a/source/db2IterateForeignScan.c +++ b/source/db2IterateForeignScan.c @@ -14,7 +14,7 @@ extern void db2PrepareQuery (DB2Session* session, const char * extern int db2ExecuteQuery (DB2Session* session, ParamDesc* paramList); extern int db2FetchNext (DB2Session* session); extern void db2CloseStatement (DB2Session* session); -extern void convertTuple (DB2Session* session, DB2Table* db2Table, DB2ResultColumn* reslist, int natts, Datum* values, bool* nulls, bool trunc_lob); +extern void convertTuple (DB2Session* session, DB2Table* db2Table, DB2ResultColumn* reslist, int natts, Datum* values, bool* nulls); extern char* deparseDate (Datum datum); extern char* deparseTimestamp (Datum datum, bool hasTimezone); @@ -55,7 +55,7 @@ TupleTableSlot* db2IterateForeignScan (ForeignScanState* node) { ++fdw_state->rowcount; /* convert result to arrays of values and null indicators */ db2Debug2("slot->tts_tupleDescriptor->natts: %d",slot->tts_tupleDescriptor->natts); - convertTuple (fdw_state->session,fdw_state->db2Table,fdw_state->resultList, slot->tts_tupleDescriptor->natts, slot->tts_values, slot->tts_isnull, false); + convertTuple (fdw_state->session,fdw_state->db2Table,fdw_state->resultList, slot->tts_tupleDescriptor->natts, slot->tts_values, slot->tts_isnull); /* store the virtual tuple */ ExecStoreVirtualTuple (slot); } else { diff --git a/source/db2_de_serialize.c b/source/db2_de_serialize.c index b7ac76f..8c1c12f 100644 --- a/source/db2_de_serialize.c +++ b/source/db2_de_serialize.c @@ -179,7 +179,7 @@ DB2FdwState* deserializePlanData (List* list) { db2Debug3("deserialize res[%d].noencerr: %ld" ,i, res->noencerr); res->resnum = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); db2Debug3("deserialize res[%d].resnum: %d" ,i, res->resnum); - res->val = (char*) db2alloc (res->val_size + 1, "res->val"); + res->val = (char*) db2alloc (MIN(res->val_size + 1, 1073741823), "res->val"); res->val_len = 0; res->val_null = 1; res->next = state->resultList; diff --git a/source/db2_fdw_utils.c b/source/db2_fdw_utils.c index 59eeb83..315e81e 100644 --- a/source/db2_fdw_utils.c +++ b/source/db2_fdw_utils.c @@ -35,7 +35,7 @@ typedef struct { /** external prototypes */ -extern void db2GetLob (DB2Session* session, DB2ResultColumn* column, char** value, long* value_len, unsigned long trunc); +extern void db2GetLob (DB2Session* session, DB2ResultColumn* column, char** value, long* value_len); extern void db2Shutdown (void); extern short c2dbType (short fcType); @@ -43,7 +43,7 @@ extern short c2dbType (short fcType); bool optionIsTrue (const char *value); char* guessNlsLang (char* nls_lang); void exitHook (int code, Datum arg); -void convertTuple (DB2Session* session, DB2Table* db2Table, DB2ResultColumn* reslist, int natts, Datum* values, bool* nulls, bool trunc_lob); +void convertTuple (DB2Session* session, DB2Table* db2Table, DB2ResultColumn* reslist, int natts, Datum* values, bool* nulls); void reset_transmission_modes (int nestlevel); int set_transmission_modes (void); bool is_builtin (Oid objectId); @@ -192,9 +192,8 @@ void exitHook (int code, Datum arg) { /* convertTuple * Convert a result row from DB2 stored in db2Table into arrays of values and null indicators. - * If trunc_lob it true, truncate LOBs to WIDTH_THRESHOLD+1 bytes. */ -void convertTuple (DB2Session* session, DB2Table* db2Table, DB2ResultColumn* reslist, int natts, Datum* values, bool* nulls, bool trunc_lob) { +void convertTuple (DB2Session* session, DB2Table* db2Table, DB2ResultColumn* reslist, int natts, Datum* values, bool* nulls) { char* value = NULL; long value_len = 0; int j = 0; @@ -203,7 +202,6 @@ void convertTuple (DB2Session* session, DB2Table* db2Table, DB2ResultColumn* res db2Entry4(); db2Debug5("natts: %d", natts); - db2Debug5("truncate lob: %s", trunc_lob ? "true": "false"); /* assign result values */ isSimpleSelect = (db2Table && natts == db2Table->npgcols); @@ -237,8 +235,7 @@ void convertTuple (DB2Session* session, DB2Table* db2Table, DB2ResultColumn* res case DB2_CLOB: { db2Debug5("DB2_BLOB or DB2CLOB"); /* for LOBs, get the actual LOB contents (allocated), truncated if desired */ - /* the column index is 1 based, whereas index id 0 based, so always add 1 to index when calling db2GetLob, since it does a column based access*/ - db2GetLob (session, res, &value, &value_len, trunc_lob ? (WIDTH_THRESHOLD + 1) : 0); + db2GetLob (session, res, &value, &value_len); } break; case DB2_LONGVARBINARY: { From acb8f8b2d3fb5bf69d521aa6d54dc4ae260e03b7 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Wed, 6 May 2026 10:24:18 +0200 Subject: [PATCH 103/120] modularize the testing --- test/sql/base.sql | 49 ++++++++++++++++------------------------------- 1 file changed, 16 insertions(+), 33 deletions(-) diff --git a/test/sql/base.sql b/test/sql/base.sql index eb65a22..78c00a4 100644 --- a/test/sql/base.sql +++ b/test/sql/base.sql @@ -15,38 +15,21 @@ CREATE SCHEMA IF NOT EXISTS sample; IMPORT FOREIGN SCHEMA "DB2INST1" FROM SERVER sample INTO sample; -- list imported tables \detr+ sample.* --- drop an imported table -DROP FOREIGN TABLE IF EXISTS sample.org; --- recreate it manually -CREATE FOREIGN TABLE sample.org ( - DEPTNUMB SMALLINT OPTIONS (key 'yes') NOT NULL , - DEPTNAME VARCHAR(14) , - MANAGER SMALLINT , - DIVISION VARCHAR(10) , - LOCATION VARCHAR(13) - ) - SERVER sample OPTIONS (schema 'DB2INST1',table 'ORG'); --- remove its content -delete from sample.org; --- repopulate the content -insert into sample.org (DEPTNUMB,DEPTNAME,MANAGER,DIVISION,LOCATION) values(10,'Head Office',160,'Corporate','New York'); -insert into sample.org (DEPTNUMB,DEPTNAME,MANAGER,DIVISION,LOCATION) values(15,'New England',50,'Eastern','Boston'); -insert into sample.org (DEPTNUMB,DEPTNAME,MANAGER,DIVISION,LOCATION) values(20,'Mid Atlantic',10,'Eastern','Washington'); -insert into sample.org (DEPTNUMB,DEPTNAME,MANAGER,DIVISION,LOCATION) values(38,'South Atlantic',30,'Eastern','Atlanta'); -insert into sample.org (DEPTNUMB,DEPTNAME,MANAGER,DIVISION,LOCATION) values(42,'Great Lakes',100,'Midwest','Chicago'); -insert into sample.org (DEPTNUMB,DEPTNAME,MANAGER,DIVISION,LOCATION) values(51,'Plains',140,'Midwest','Dallas'); -insert into sample.org (DEPTNUMB,DEPTNAME,MANAGER,DIVISION,LOCATION) values(66,'Pacific',270,'Western','San Francisco'); -insert into sample.org (DEPTNUMB,DEPTNAME,MANAGER,DIVISION,LOCATION) values(84,'Mountain',290,'Western','Denver'); --- inquire the content -select * from sample.org; -select * from sample.sales; --- test a simple join -select * from sample.employee a, sample.sales b where a.lastname = b.sales_person; --- create a local table importing its structure and content from an fdw table -create table sample.orgcopy as select * from sample.org; -\d+ sample.org* -drop table sample.orgcopy; --- cleanup +-- +select db2_diag(); +-- starting testcases +-- running tc001.sql +\i tc001.sql +-- running tc002.sql +\i tc002.sql +-- running tc003.sql +\i tc003.sql +-- running tc004.sql +\i tc004.sql +-- running tc005.sql +\i tc005.sql +-- testcases ended +-- starting cleanup \c postgres DROP DATABASE regtest; --- \ No newline at end of file +-- test finished \ No newline at end of file From 17d58faa6817d6faa4b25677d9d73207adc12ad4 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Wed, 6 May 2026 10:24:34 +0200 Subject: [PATCH 104/120] TC001: Dropping and re-creating a foreign table "manually" --- test/sql/tc001.sql | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 test/sql/tc001.sql diff --git a/test/sql/tc001.sql b/test/sql/tc001.sql new file mode 100644 index 0000000..5717b95 --- /dev/null +++ b/test/sql/tc001.sql @@ -0,0 +1,37 @@ +-- +-- TC001: Dropping and re-creating a foreign table "manually" +-- +-- drop an imported table +\d+ sample.org; +DROP FOREIGN TABLE IF EXISTS sample.org; +-- recreate it manually +\d+ sample.org; +CREATE FOREIGN TABLE sample.org ( + DEPTNUMB SMALLINT OPTIONS (key 'yes') NOT NULL , + DEPTNAME VARCHAR(14) , + MANAGER SMALLINT , + DIVISION VARCHAR(10) , + LOCATION VARCHAR(13) + ) + SERVER sample OPTIONS (schema 'DB2INST1',table 'ORG'); +\d+ sample.org; +-- +-- TC001a: on a freshly created foreign table remove the content and manually re-create it again. +-- +-- remove its content +delete from sample.org; +SELECT * FROM sample.org; +-- repopulate the content +insert into sample.org (DEPTNUMB,DEPTNAME,MANAGER,DIVISION,LOCATION) values(10,'Head Office',160,'Corporate','New York'); +insert into sample.org (DEPTNUMB,DEPTNAME,MANAGER,DIVISION,LOCATION) values(15,'New England',50,'Eastern','Boston'); +insert into sample.org (DEPTNUMB,DEPTNAME,MANAGER,DIVISION,LOCATION) values(20,'Mid Atlantic',10,'Eastern','Washington'); +insert into sample.org (DEPTNUMB,DEPTNAME,MANAGER,DIVISION,LOCATION) values(38,'South Atlantic',30,'Eastern','Atlanta'); +insert into sample.org (DEPTNUMB,DEPTNAME,MANAGER,DIVISION,LOCATION) values(42,'Great Lakes',100,'Midwest','Chicago'); +insert into sample.org (DEPTNUMB,DEPTNAME,MANAGER,DIVISION,LOCATION) values(51,'Plains',140,'Midwest','Dallas'); +insert into sample.org (DEPTNUMB,DEPTNAME,MANAGER,DIVISION,LOCATION) values(66,'Pacific',270,'Western','San Francisco'); +insert into sample.org (DEPTNUMB,DEPTNAME,MANAGER,DIVISION,LOCATION) values(84,'Mountain',290,'Western','Denver'); +-- inquire the content +SELECT * FROM sample.org; +-- +-- END of TC001 +-- \ No newline at end of file From 2835f04afa0cf6305fbe5d6f150c2a08ca7ceb39 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Wed, 6 May 2026 10:24:47 +0200 Subject: [PATCH 105/120] TC002: Importing a single foreign table from a remote schema --- test/sql/tc002.sql | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 test/sql/tc002.sql diff --git a/test/sql/tc002.sql b/test/sql/tc002.sql new file mode 100644 index 0000000..64e1e53 --- /dev/null +++ b/test/sql/tc002.sql @@ -0,0 +1,15 @@ +-- +-- TC002: Importing a single foreign table from a remote schema +-- +\d+ sample.act; +SELECT * FROM sample.act; +-- drop foreign table act +DROP FOREIGN TABLE IF EXISTS sample.act; +-- import it using limit to +IMPORT FOREIGN SCHEMA "DB2INST1" LIMIT TO ("ACT") FROM SERVER sample INTO sample; +-- test its working again +\d+ sample.act; +SELECT * FROM sample.act; +-- +-- END of TC002 +-- \ No newline at end of file From 5c082e81786c1a572610c4fd6d6f6c1ff274ec11 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Wed, 6 May 2026 10:24:56 +0200 Subject: [PATCH 106/120] TC003: Run a simple join --- test/sql/tc003.sql | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 test/sql/tc003.sql diff --git a/test/sql/tc003.sql b/test/sql/tc003.sql new file mode 100644 index 0000000..3df150e --- /dev/null +++ b/test/sql/tc003.sql @@ -0,0 +1,10 @@ +-- +-- TC003: Run a simple join +-- +\d+ sample.employee; +\d+ sample.sales; +-- test a simple join +select * from sample.employee a, sample.sales b where a.lastname = b.sales_person; +-- +-- End of TC003 +-- \ No newline at end of file From 5ff62cbacc01a84c5133e728ffc1e8d290556470 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Wed, 6 May 2026 10:25:05 +0200 Subject: [PATCH 107/120] TC004: clone a foreign table into a local table including content --- test/sql/tc004.sql | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 test/sql/tc004.sql diff --git a/test/sql/tc004.sql b/test/sql/tc004.sql new file mode 100644 index 0000000..5eee9c9 --- /dev/null +++ b/test/sql/tc004.sql @@ -0,0 +1,9 @@ +-- +-- TC004: clone a foreign table into a local table including content +-- +create table sample.orgcopy as select * from sample.org; +\d+ sample.org* +drop table sample.orgcopy; +-- +-- END of TC004 +-- \ No newline at end of file From eca8eb150bd3761ff06e47a43792e1706225bb1c Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Wed, 6 May 2026 10:25:16 +0200 Subject: [PATCH 108/120] TC005: run a pushdown query for aggregate functions --- test/sql/tc005.sql | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 test/sql/tc005.sql diff --git a/test/sql/tc005.sql b/test/sql/tc005.sql new file mode 100644 index 0000000..9d2281e --- /dev/null +++ b/test/sql/tc005.sql @@ -0,0 +1,12 @@ +-- +-- TC005: run a pushdown query for aggregate functions +-- +\d+ sample.employee; +explain (analyze,verbose) select min(salary),max(salary),avg(salary),sum(salary +comm + bonus),count(*) from sample.employee; +select min(salary),max(salary),avg(salary),sum(salary +comm + bonus),count(*) from sample.employee; +-- +explain (analyze,verbose) select empno, firstnme,lastname, salary + bonus + comm from sample.employee where salary > 8000 and lastname like 'L%'; +select empno, firstnme,lastname, salary + bonus + comm from sample.employee where salary > 8000 and lastname like 'L%'; +-- +-- END of TC004 +-- \ No newline at end of file From 83feee0cd8c8019c962948fa31f9c96485a77884 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Wed, 6 May 2026 10:25:39 +0200 Subject: [PATCH 109/120] test results --- test/expected/base.out | 699 +++++++++++++++++++++++++++++++---------- 1 file changed, 529 insertions(+), 170 deletions(-) diff --git a/test/expected/base.out b/test/expected/base.out index fca9ef9..c3bae79 100644 --- a/test/expected/base.out +++ b/test/expected/base.out @@ -3,7 +3,7 @@ CREATE DATABASE GRANT ALL PRIVILEGES ON DATABASE regtest to postgres; GRANT \c regtest -Sie sind jetzt verbunden mit der Datenbank »regtest« als Benutzer »postgres«. +You are now connected to database "regtest" as user "postgres". -- Install extension CREATE EXTENSION IF NOT EXISTS db2_fdw; CREATE EXTENSION @@ -22,61 +22,95 @@ IMPORT FOREIGN SCHEMA "DB2INST1" FROM SERVER sample INTO sample; IMPORT FOREIGN SCHEMA -- list imported tables \detr+ sample.* - Liste der Fremdtabellen - Schema | Tabelle | Server | FDW-Optionen | Beschreibung ---------+-----------------+--------+------------------------------------------------+-------------- - sample | act | sample | (schema 'DB2INST1', "table" 'ACT') | - sample | bintypes | sample | (schema 'DB2INST1', "table" 'BINTYPES') | - sample | catalog | sample | (schema 'DB2INST1', "table" 'CATALOG') | - sample | cl_sched | sample | (schema 'DB2INST1', "table" 'CL_SCHED') | - sample | customer | sample | (schema 'DB2INST1', "table" 'CUSTOMER') | - sample | department | sample | (schema 'DB2INST1', "table" 'DEPARTMENT') | - sample | emp_photo | sample | (schema 'DB2INST1', "table" 'EMP_PHOTO') | - sample | emp_resume | sample | (schema 'DB2INST1', "table" 'EMP_RESUME') | - sample | employee | sample | (schema 'DB2INST1', "table" 'EMPLOYEE') | - sample | empmdc | sample | (schema 'DB2INST1', "table" 'EMPMDC') | - sample | empprojact | sample | (schema 'DB2INST1', "table" 'EMPPROJACT') | - sample | in_tray | sample | (schema 'DB2INST1', "table" 'IN_TRAY') | - sample | inventory | sample | (schema 'DB2INST1', "table" 'INVENTORY') | - sample | numerictypes | sample | (schema 'DB2INST1', "table" 'NUMERICTYPES') | - sample | org | sample | (schema 'DB2INST1', "table" 'ORG') | - sample | product | sample | (schema 'DB2INST1', "table" 'PRODUCT') | - sample | productsupplier | sample | (schema 'DB2INST1', "table" 'PRODUCTSUPPLIER') | - sample | projact | sample | (schema 'DB2INST1', "table" 'PROJACT') | - sample | project | sample | (schema 'DB2INST1', "table" 'PROJECT') | - sample | purchaseorder | sample | (schema 'DB2INST1', "table" 'PURCHASEORDER') | - sample | sales | sample | (schema 'DB2INST1', "table" 'SALES') | - sample | staff | sample | (schema 'DB2INST1', "table" 'STAFF') | - sample | staffg | sample | (schema 'DB2INST1', "table" 'STAFFG') | - sample | suppliers | sample | (schema 'DB2INST1', "table" 'SUPPLIERS') | - sample | timetypes | sample | (schema 'DB2INST1', "table" 'TIMETYPES') | - sample | tm2acct | sample | (schema 'DB2INST1', "table" 'TM2ACCT') | - sample | vact | sample | (schema 'DB2INST1', "table" 'VACT') | - sample | vastrde1 | sample | (schema 'DB2INST1', "table" 'VASTRDE1') | - sample | vastrde2 | sample | (schema 'DB2INST1', "table" 'VASTRDE2') | - sample | vdepmg1 | sample | (schema 'DB2INST1', "table" 'VDEPMG1') | - sample | vdept | sample | (schema 'DB2INST1', "table" 'VDEPT') | - sample | vemp | sample | (schema 'DB2INST1', "table" 'VEMP') | - sample | vempdpt1 | sample | (schema 'DB2INST1', "table" 'VEMPDPT1') | - sample | vemplp | sample | (schema 'DB2INST1', "table" 'VEMPLP') | - sample | vempprojact | sample | (schema 'DB2INST1', "table" 'VEMPPROJACT') | - sample | vforpla | sample | (schema 'DB2INST1', "table" 'VFORPLA') | - sample | vhdept | sample | (schema 'DB2INST1', "table" 'VHDEPT') | - sample | vm2acct | sample | (schema 'DB2INST1', "table" 'VM2ACCT') | - sample | vphone | sample | (schema 'DB2INST1', "table" 'VPHONE') | - sample | vproj | sample | (schema 'DB2INST1', "table" 'VPROJ') | - sample | vprojact | sample | (schema 'DB2INST1', "table" 'VPROJACT') | - sample | vprojre1 | sample | (schema 'DB2INST1', "table" 'VPROJRE1') | - sample | vpstrde1 | sample | (schema 'DB2INST1', "table" 'VPSTRDE1') | - sample | vpstrde2 | sample | (schema 'DB2INST1', "table" 'VPSTRDE2') | - sample | vstafac1 | sample | (schema 'DB2INST1', "table" 'VSTAFAC1') | - sample | vstafac2 | sample | (schema 'DB2INST1', "table" 'VSTAFAC2') | -(46 Zeilen) + List of foreign tables + Schema | Table | Server | FDW options | Description +--------+-------------------+--------+--------------------------------------------------+------------- + sample | act | sample | (schema 'DB2INST1', "table" 'ACT') | + sample | bintypes | sample | (schema 'DB2INST1', "table" 'BINTYPES') | + sample | catalog | sample | (schema 'DB2INST1', "table" 'CATALOG') | + sample | cl_sched | sample | (schema 'DB2INST1', "table" 'CL_SCHED') | + sample | customer | sample | (schema 'DB2INST1', "table" 'CUSTOMER') | + sample | department | sample | (schema 'DB2INST1', "table" 'DEPARTMENT') | + sample | emp_photo | sample | (schema 'DB2INST1', "table" 'EMP_PHOTO') | + sample | emp_resume | sample | (schema 'DB2INST1', "table" 'EMP_RESUME') | + sample | employee | sample | (schema 'DB2INST1', "table" 'EMPLOYEE') | + sample | empmdc | sample | (schema 'DB2INST1', "table" 'EMPMDC') | + sample | empprojact | sample | (schema 'DB2INST1', "table" 'EMPPROJACT') | + sample | in_tray | sample | (schema 'DB2INST1', "table" 'IN_TRAY') | + sample | inventory | sample | (schema 'DB2INST1', "table" 'INVENTORY') | + sample | iot_gre_space | sample | (schema 'DB2INST1', "table" 'IOT_GRE_SPACE') | + sample | numerictypes | sample | (schema 'DB2INST1', "table" 'NUMERICTYPES') | + sample | org | sample | (schema 'DB2INST1', "table" 'ORG') | + sample | product | sample | (schema 'DB2INST1', "table" 'PRODUCT') | + sample | productsupplier | sample | (schema 'DB2INST1', "table" 'PRODUCTSUPPLIER') | + sample | projact | sample | (schema 'DB2INST1', "table" 'PROJACT') | + sample | project | sample | (schema 'DB2INST1', "table" 'PROJECT') | + sample | purchaseorder | sample | (schema 'DB2INST1', "table" 'PURCHASEORDER') | + sample | remotetable | sample | (schema 'DB2INST1', "table" 'REMOTETABLE') | + sample | sales | sample | (schema 'DB2INST1', "table" 'SALES') | + sample | staff | sample | (schema 'DB2INST1', "table" 'STAFF') | + sample | staffg | sample | (schema 'DB2INST1', "table" 'STAFFG') | + sample | suppliers | sample | (schema 'DB2INST1', "table" 'SUPPLIERS') | + sample | test_hidden | sample | (schema 'DB2INST1', "table" 'TEST_HIDDEN') | + sample | testbigint | sample | (schema 'DB2INST1', "table" 'TESTBIGINT') | + sample | timetypes | sample | (schema 'DB2INST1', "table" 'TIMETYPES') | + sample | tm2acct | sample | (schema 'DB2INST1', "table" 'TM2ACCT') | + sample | uaci_rtdeployment | sample | (schema 'DB2INST1', "table" 'UACI_RTDEPLOYMENT') | + sample | vact | sample | (schema 'DB2INST1', "table" 'VACT') | + sample | vastrde1 | sample | (schema 'DB2INST1', "table" 'VASTRDE1') | + sample | vastrde2 | sample | (schema 'DB2INST1', "table" 'VASTRDE2') | + sample | vdepmg1 | sample | (schema 'DB2INST1', "table" 'VDEPMG1') | + sample | vdept | sample | (schema 'DB2INST1', "table" 'VDEPT') | + sample | vemp | sample | (schema 'DB2INST1', "table" 'VEMP') | + sample | vempdpt1 | sample | (schema 'DB2INST1', "table" 'VEMPDPT1') | + sample | vemplp | sample | (schema 'DB2INST1', "table" 'VEMPLP') | + sample | vempprojact | sample | (schema 'DB2INST1', "table" 'VEMPPROJACT') | + sample | vforpla | sample | (schema 'DB2INST1', "table" 'VFORPLA') | + sample | vhdept | sample | (schema 'DB2INST1', "table" 'VHDEPT') | + sample | vm2acct | sample | (schema 'DB2INST1', "table" 'VM2ACCT') | + sample | vphone | sample | (schema 'DB2INST1', "table" 'VPHONE') | + sample | vproj | sample | (schema 'DB2INST1', "table" 'VPROJ') | + sample | vprojact | sample | (schema 'DB2INST1', "table" 'VPROJACT') | + sample | vprojre1 | sample | (schema 'DB2INST1', "table" 'VPROJRE1') | + sample | vpstrde1 | sample | (schema 'DB2INST1', "table" 'VPSTRDE1') | + sample | vpstrde2 | sample | (schema 'DB2INST1', "table" 'VPSTRDE2') | + sample | vstafac1 | sample | (schema 'DB2INST1', "table" 'VSTAFAC1') | + sample | vstafac2 | sample | (schema 'DB2INST1', "table" 'VSTAFAC2') | +(51 rows) +-- +select db2_diag(); + db2_diag +------------------------------------------------------------------------------------------------------------------------- + db2_fdw 18.2.0, PostgreSQL 18.1, DB2INSTANCE=postgres, DB2_HOME=/var/lib/pgsql/sqllib, DB2LIB=/var/lib/pgsql/sqllib/lib +(1 row) + +-- starting testcases +-- running tc001.sql +\i tc001.sql +-- +-- TC001: Dropping and re-creating a foreign table "manually" +-- -- drop an imported table +\d+ sample.org; + Foreign table "sample.org" + Column | Type | Collation | Nullable | Default | FDW options | Storage | Stats target | Description +----------+-----------------------+-----------+----------+---------+-------------------------------------------------------------------------------------------------------+----------+--------------+------------- + deptnumb | smallint | | not null | | (db2type '5', db2size '5', db2bytes '2', db2chars '5', db2scale '0', db2null '0', db2ccsid '0') | plain | | + deptname | character varying(14) | | | | (db2type '12', db2size '14', db2bytes '14', db2chars '0', db2scale '0', db2null '1', db2ccsid '1208') | extended | | + manager | smallint | | | | (db2type '5', db2size '5', db2bytes '2', db2chars '5', db2scale '0', db2null '1', db2ccsid '0') | plain | | + division | character varying(10) | | | | (db2type '12', db2size '10', db2bytes '10', db2chars '0', db2scale '0', db2null '1', db2ccsid '1208') | extended | | + location | character varying(13) | | | | (db2type '12', db2size '13', db2bytes '13', db2chars '0', db2scale '0', db2null '1', db2ccsid '1208') | extended | | +Not-null constraints: + "org_deptnumb_not_null" NOT NULL "deptnumb" +Server: sample +FDW options: (schema 'DB2INST1', "table" 'ORG') + DROP FOREIGN TABLE IF EXISTS sample.org; DROP FOREIGN TABLE -- recreate it manually +\d+ sample.org; +psql:tc001.sql:8: error: Did not find any relation named "sample.org". CREATE FOREIGN TABLE sample.org ( DEPTNUMB SMALLINT OPTIONS (key 'yes') NOT NULL , DEPTNAME VARCHAR(14) , @@ -86,9 +120,31 @@ CREATE FOREIGN TABLE sample.org ( ) SERVER sample OPTIONS (schema 'DB2INST1',table 'ORG'); CREATE FOREIGN TABLE +\d+ sample.org; + Foreign table "sample.org" + Column | Type | Collation | Nullable | Default | FDW options | Storage | Stats target | Description +----------+-----------------------+-----------+----------+---------+-------------+----------+--------------+------------- + deptnumb | smallint | | not null | | (key 'yes') | plain | | + deptname | character varying(14) | | | | | extended | | + manager | smallint | | | | | plain | | + division | character varying(10) | | | | | extended | | + location | character varying(13) | | | | | extended | | +Not-null constraints: + "org_deptnumb_not_null" NOT NULL "deptnumb" +Server: sample +FDW options: (schema 'DB2INST1', "table" 'ORG') + +-- +-- TC001a: on a freshly created foreign table remove the content and manually re-create it again. +-- -- remove its content delete from sample.org; DELETE 8 +SELECT * FROM sample.org; + deptnumb | deptname | manager | division | location +----------+----------+---------+----------+---------- +(0 rows) + -- repopulate the content insert into sample.org (DEPTNUMB,DEPTNAME,MANAGER,DIVISION,LOCATION) values(10,'Head Office',160,'Corporate','New York'); INSERT 0 1 @@ -107,144 +163,447 @@ INSERT 0 1 insert into sample.org (DEPTNUMB,DEPTNAME,MANAGER,DIVISION,LOCATION) values(84,'Mountain',290,'Western','Denver'); INSERT 0 1 -- inquire the content -select * from sample.org; +SELECT * FROM sample.org; deptnumb | deptname | manager | division | location ----------+----------------+---------+-----------+--------------- - 160 | Head Office | 160 | Corporate | New York - 50 | New England | 50 | Eastern | Boston - 10 | Mid Atlantic | 10 | Eastern | Washington - 30 | South Atlantic | 30 | Eastern | Atlanta - 100 | Great Lakes | 100 | Midwest | Chicago - 140 | Plains | 140 | Midwest | Dallas - 270 | Pacific | 270 | Western | San Francisco - 290 | Mountain | 290 | Western | Denver -(8 Zeilen) - -select * from sample.sales; - sales_date | sales_person | region | sales -------------+--------------+---------------+------- - 2005-12-31 | LUCCHESSI | Ontario-South | 1 - 2005-12-31 | LEE | Ontario-South | 3 - 2005-12-31 | LEE | Quebec | 1 - 2005-12-31 | LEE | Manitoba | 2 - 2005-12-31 | GOUNOT | Quebec | 1 - 2006-03-29 | LUCCHESSI | Ontario-South | 3 - 2006-03-29 | LUCCHESSI | Quebec | 1 - 2006-03-29 | LEE | Ontario-South | 2 - 1996-03-29 | LEE | Ontario-North | 2 - 2006-03-29 | LEE | Quebec | 3 - 2006-03-29 | LEE | Manitoba | 5 - 2006-03-29 | GOUNOT | Ontario-South | 3 - 2006-03-29 | GOUNOT | Quebec | 1 - 2006-03-29 | GOUNOT | Manitoba | 7 - 2006-03-30 | LUCCHESSI | Ontario-South | 1 - 2006-03-30 | LUCCHESSI | Quebec | 2 - 2006-03-30 | LUCCHESSI | Manitoba | 1 - 2006-03-30 | LEE | Ontario-South | 7 - 2006-03-30 | LEE | Ontario-North | 3 - 2006-03-30 | LEE | Quebec | 7 - 2006-03-30 | LEE | Manitoba | 4 - 2006-03-30 | GOUNOT | Ontario-South | 2 - 2006-03-30 | GOUNOT | Quebec | 18 - 2006-03-31 | GOUNOT | Manitoba | 1 - 2006-03-31 | LUCCHESSI | Manitoba | 1 - 2006-03-31 | LEE | Ontario-South | 14 - 2006-03-31 | LEE | Ontario-North | 3 - 2006-03-31 | LEE | Quebec | 7 - 2006-03-31 | LEE | Manitoba | 3 - 2006-03-31 | GOUNOT | Ontario-South | 2 - 2006-03-31 | GOUNOT | Quebec | 1 - 2006-04-01 | LUCCHESSI | Ontario-South | 3 - 2006-04-01 | LUCCHESSI | Manitoba | 1 - 2006-04-01 | LEE | Ontario-South | 8 - 2006-04-01 | LEE | Ontario-North | - 2006-04-01 | LEE | Quebec | 8 - 2006-04-01 | LEE | Manitoba | 9 - 2006-04-01 | GOUNOT | Ontario-South | 3 - 2006-04-01 | GOUNOT | Ontario-North | 1 - 2006-04-01 | GOUNOT | Quebec | 3 - 2006-04-01 | GOUNOT | Manitoba | 7 -(41 Zeilen) + 10 | Head Office | 160 | Corporate | New York + 15 | New England | 50 | Eastern | Boston + 20 | Mid Atlantic | 10 | Eastern | Washington + 38 | South Atlantic | 30 | Eastern | Atlanta + 42 | Great Lakes | 100 | Midwest | Chicago + 51 | Plains | 140 | Midwest | Dallas + 66 | Pacific | 270 | Western | San Francisco + 84 | Mountain | 290 | Western | Denver +(8 rows) + +-- +-- END of TC001 +-- +-- running tc002.sql +\i tc002.sql +-- +-- TC002: Importing a single foreign table from a remote schema +-- +\d+ sample.act; + Foreign table "sample.act" + Column | Type | Collation | Nullable | Default | FDW options | Storage | Stats target | Description +---------+-----------------------+-----------+----------+---------+-------------------------------------------------------------------------------------------------------------+----------+--------------+------------- + actno | smallint | | not null | | (db2type '5', db2size '5', db2bytes '2', db2chars '5', db2scale '0', db2null '0', db2ccsid '0', key 'true') | plain | | + actkwd | character(6) | | not null | | (db2type '1', db2size '6', db2bytes '6', db2chars '0', db2scale '0', db2null '0', db2ccsid '1208') | extended | | + actdesc | character varying(20) | | not null | | (db2type '12', db2size '20', db2bytes '20', db2chars '0', db2scale '0', db2null '0', db2ccsid '1208') | extended | | +Not-null constraints: + "act_actno_not_null" NOT NULL "actno" + "act_actkwd_not_null" NOT NULL "actkwd" + "act_actdesc_not_null" NOT NULL "actdesc" +Server: sample +FDW options: (schema 'DB2INST1', "table" 'ACT') + +SELECT * FROM sample.act; + actno | actkwd | actdesc +-------+--------+--------------------- + 10 | MANAGE | MANAGE/ADVISE + 20 | ECOST | ESTIMATE COST + 30 | DEFINE | DEFINE SPECS + 40 | LEADPR | LEAD PROGRAM/DESIGN + 50 | SPECS | WRITE SPECS + 60 | LOGIC | DESCRIBE LOGIC + 70 | CODE | CODE PROGRAMS + 80 | TEST | TEST PROGRAMS + 90 | ADMQS | ADM QUERY SYSTEM + 100 | TEACH | TEACH CLASSES + 110 | COURSE | DEVELOP COURSES + 120 | STAFF | PERS AND STAFFING + 130 | OPERAT | OPER COMPUTER SYS + 140 | MAINT | MAINT SOFTWARE SYS + 150 | ADMSYS | ADM OPERATING SYS + 160 | ADMDB | ADM DATA BASES + 170 | ADMDC | ADM DATA COMM + 180 | DOC | DOCUMENT + 190 | XML | XML Document + 999 | ABCDE | uhbviurw +(20 rows) + +-- drop foreign table act +DROP FOREIGN TABLE IF EXISTS sample.act; +DROP FOREIGN TABLE +-- import it using limit to +IMPORT FOREIGN SCHEMA "DB2INST1" LIMIT TO ("ACT") FROM SERVER sample INTO sample; +IMPORT FOREIGN SCHEMA +-- test its working again +\d+ sample.act; + Foreign table "sample.act" + Column | Type | Collation | Nullable | Default | FDW options | Storage | Stats target | Description +---------+-----------------------+-----------+----------+---------+-------------------------------------------------------------------------------------------------------------+----------+--------------+------------- + actno | smallint | | not null | | (db2type '5', db2size '5', db2bytes '2', db2chars '5', db2scale '0', db2null '0', db2ccsid '0', key 'true') | plain | | + actkwd | character(6) | | not null | | (db2type '1', db2size '6', db2bytes '6', db2chars '0', db2scale '0', db2null '0', db2ccsid '1208') | extended | | + actdesc | character varying(20) | | not null | | (db2type '12', db2size '20', db2bytes '20', db2chars '0', db2scale '0', db2null '0', db2ccsid '1208') | extended | | +Not-null constraints: + "act_actno_not_null" NOT NULL "actno" + "act_actkwd_not_null" NOT NULL "actkwd" + "act_actdesc_not_null" NOT NULL "actdesc" +Server: sample +FDW options: (schema 'DB2INST1', "table" 'ACT') + +SELECT * FROM sample.act; + actno | actkwd | actdesc +-------+--------+--------------------- + 10 | MANAGE | MANAGE/ADVISE + 20 | ECOST | ESTIMATE COST + 30 | DEFINE | DEFINE SPECS + 40 | LEADPR | LEAD PROGRAM/DESIGN + 50 | SPECS | WRITE SPECS + 60 | LOGIC | DESCRIBE LOGIC + 70 | CODE | CODE PROGRAMS + 80 | TEST | TEST PROGRAMS + 90 | ADMQS | ADM QUERY SYSTEM + 100 | TEACH | TEACH CLASSES + 110 | COURSE | DEVELOP COURSES + 120 | STAFF | PERS AND STAFFING + 130 | OPERAT | OPER COMPUTER SYS + 140 | MAINT | MAINT SOFTWARE SYS + 150 | ADMSYS | ADM OPERATING SYS + 160 | ADMDB | ADM DATA BASES + 170 | ADMDC | ADM DATA COMM + 180 | DOC | DOCUMENT + 190 | XML | XML Document + 999 | ABCDE | uhbviurw +(20 rows) + +-- +-- END of TC002 +-- +-- running tc003.sql +\i tc003.sql +-- +-- TC003: Run a simple join +-- +\d+ sample.employee; + Foreign table "sample.employee" + Column | Type | Collation | Nullable | Default | FDW options | Storage | Stats target | Description +-----------+-----------------------+-----------+----------+---------+----------------------------------------------------------------------------------------------------------------+----------+--------------+------------- + empno | character(6) | | not null | | (db2type '1', db2size '6', db2bytes '6', db2chars '0', db2scale '0', db2null '0', db2ccsid '1208', key 'true') | extended | | + firstnme | character varying(12) | | not null | | (db2type '12', db2size '12', db2bytes '12', db2chars '0', db2scale '0', db2null '0', db2ccsid '1208') | extended | | + midinit | character(1) | | | | (db2type '1', db2size '1', db2bytes '1', db2chars '0', db2scale '0', db2null '1', db2ccsid '1208') | extended | | + lastname | character varying(15) | | not null | | (db2type '12', db2size '15', db2bytes '15', db2chars '0', db2scale '0', db2null '0', db2ccsid '1208') | extended | | + workdept | character(3) | | | | (db2type '1', db2size '3', db2bytes '3', db2chars '0', db2scale '0', db2null '1', db2ccsid '1208') | extended | | + phoneno | character(4) | | | | (db2type '1', db2size '4', db2bytes '4', db2chars '0', db2scale '0', db2null '1', db2ccsid '1208') | extended | | + hiredate | date | | | | (db2type '91', db2size '10', db2bytes '6', db2chars '0', db2scale '0', db2null '1', db2ccsid '0') | plain | | + job | character(8) | | | | (db2type '1', db2size '8', db2bytes '8', db2chars '0', db2scale '0', db2null '1', db2ccsid '1208') | extended | | + edlevel | smallint | | not null | | (db2type '5', db2size '5', db2bytes '2', db2chars '5', db2scale '0', db2null '0', db2ccsid '0') | plain | | + sex | character(1) | | | | (db2type '1', db2size '1', db2bytes '1', db2chars '0', db2scale '0', db2null '1', db2ccsid '1208') | extended | | + birthdate | date | | | | (db2type '91', db2size '10', db2bytes '6', db2chars '0', db2scale '0', db2null '1', db2ccsid '0') | plain | | + salary | numeric(9,2) | | | | (db2type '3', db2size '9', db2bytes '11', db2chars '9', db2scale '2', db2null '1', db2ccsid '0') | main | | + bonus | numeric(9,2) | | | | (db2type '3', db2size '9', db2bytes '11', db2chars '9', db2scale '2', db2null '1', db2ccsid '0') | main | | + comm | numeric(9,2) | | | | (db2type '3', db2size '9', db2bytes '11', db2chars '9', db2scale '2', db2null '1', db2ccsid '0') | main | | +Not-null constraints: + "employee_empno_not_null" NOT NULL "empno" + "employee_firstnme_not_null" NOT NULL "firstnme" + "employee_lastname_not_null" NOT NULL "lastname" + "employee_edlevel_not_null" NOT NULL "edlevel" +Server: sample +FDW options: (schema 'DB2INST1', "table" 'EMPLOYEE') + +\d+ sample.sales; + Foreign table "sample.sales" + Column | Type | Collation | Nullable | Default | FDW options | Storage | Stats target | Description +--------------+-----------------------+-----------+----------+---------+-------------------------------------------------------------------------------------------------------+----------+--------------+------------- + sales_date | date | | | | (db2type '91', db2size '10', db2bytes '6', db2chars '0', db2scale '0', db2null '1', db2ccsid '0') | plain | | + sales_person | character varying(15) | | | | (db2type '12', db2size '15', db2bytes '15', db2chars '0', db2scale '0', db2null '1', db2ccsid '1208') | extended | | + region | character varying(15) | | | | (db2type '12', db2size '15', db2bytes '15', db2chars '0', db2scale '0', db2null '1', db2ccsid '1208') | extended | | + sales | integer | | | | (db2type '4', db2size '10', db2bytes '4', db2chars '10', db2scale '0', db2null '1', db2ccsid '0') | plain | | +Server: sample +FDW options: (schema 'DB2INST1', "table" 'SALES') -- test a simple join select * from sample.employee a, sample.sales b where a.lastname = b.sales_person; empno | firstnme | midinit | lastname | workdept | phoneno | hiredate | job | edlevel | sex | birthdate | salary | bonus | comm | sales_date | sales_person | region | sales --------+----------+---------+-----------+----------+---------+------------+----------+---------+-----+------------+----------+--------+---------+------------+--------------+---------------+------- - 000110 | VINCENZO | G | LUCCHESSI | A00 | 3490 | 1988-05-16 | SALESREP | 19 | M | 1959-11-05 | 66500.00 | 900.00 | 3720.00 | 2006-04-01 | LUCCHESSI | Manitoba | 1 - 000110 | VINCENZO | G | LUCCHESSI | A00 | 3490 | 1988-05-16 | SALESREP | 19 | M | 1959-11-05 | 66500.00 | 900.00 | 3720.00 | 2006-04-01 | LUCCHESSI | Ontario-South | 3 - 000110 | VINCENZO | G | LUCCHESSI | A00 | 3490 | 1988-05-16 | SALESREP | 19 | M | 1959-11-05 | 66500.00 | 900.00 | 3720.00 | 2006-03-31 | LUCCHESSI | Manitoba | 1 - 000110 | VINCENZO | G | LUCCHESSI | A00 | 3490 | 1988-05-16 | SALESREP | 19 | M | 1959-11-05 | 66500.00 | 900.00 | 3720.00 | 2006-03-30 | LUCCHESSI | Manitoba | 1 - 000110 | VINCENZO | G | LUCCHESSI | A00 | 3490 | 1988-05-16 | SALESREP | 19 | M | 1959-11-05 | 66500.00 | 900.00 | 3720.00 | 2006-03-30 | LUCCHESSI | Quebec | 2 - 000110 | VINCENZO | G | LUCCHESSI | A00 | 3490 | 1988-05-16 | SALESREP | 19 | M | 1959-11-05 | 66500.00 | 900.00 | 3720.00 | 2006-03-30 | LUCCHESSI | Ontario-South | 1 - 000110 | VINCENZO | G | LUCCHESSI | A00 | 3490 | 1988-05-16 | SALESREP | 19 | M | 1959-11-05 | 66500.00 | 900.00 | 3720.00 | 2006-03-29 | LUCCHESSI | Quebec | 1 - 000110 | VINCENZO | G | LUCCHESSI | A00 | 3490 | 1988-05-16 | SALESREP | 19 | M | 1959-11-05 | 66500.00 | 900.00 | 3720.00 | 2006-03-29 | LUCCHESSI | Ontario-South | 3 000110 | VINCENZO | G | LUCCHESSI | A00 | 3490 | 1988-05-16 | SALESREP | 19 | M | 1959-11-05 | 66500.00 | 900.00 | 3720.00 | 2005-12-31 | LUCCHESSI | Ontario-South | 1 - 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-04-01 | LEE | Manitoba | 9 - 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-04-01 | LEE | Quebec | 8 - 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-04-01 | LEE | Ontario-North | - 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-04-01 | LEE | Ontario-South | 8 - 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-03-31 | LEE | Manitoba | 3 - 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-03-31 | LEE | Quebec | 7 - 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-03-31 | LEE | Ontario-North | 3 - 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-03-31 | LEE | Ontario-South | 14 - 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-03-30 | LEE | Manitoba | 4 - 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-03-30 | LEE | Quebec | 7 - 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-03-30 | LEE | Ontario-North | 3 - 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-03-30 | LEE | Ontario-South | 7 - 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-03-29 | LEE | Manitoba | 5 - 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-03-29 | LEE | Quebec | 3 - 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 1996-03-29 | LEE | Ontario-North | 2 - 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-03-29 | LEE | Ontario-South | 2 - 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2005-12-31 | LEE | Manitoba | 2 - 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2005-12-31 | LEE | Quebec | 1 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2005-12-31 | LEE | Ontario-South | 3 - 000340 | JASON | R | GOUNOT | E21 | 5698 | 1977-05-05 | FIELDREP | 16 | M | 1956-05-17 | 43840.00 | 500.00 | 1907.00 | 2006-04-01 | GOUNOT | Manitoba | 7 - 000340 | JASON | R | GOUNOT | E21 | 5698 | 1977-05-05 | FIELDREP | 16 | M | 1956-05-17 | 43840.00 | 500.00 | 1907.00 | 2006-04-01 | GOUNOT | Quebec | 3 - 000340 | JASON | R | GOUNOT | E21 | 5698 | 1977-05-05 | FIELDREP | 16 | M | 1956-05-17 | 43840.00 | 500.00 | 1907.00 | 2006-04-01 | GOUNOT | Ontario-North | 1 - 000340 | JASON | R | GOUNOT | E21 | 5698 | 1977-05-05 | FIELDREP | 16 | M | 1956-05-17 | 43840.00 | 500.00 | 1907.00 | 2006-04-01 | GOUNOT | Ontario-South | 3 - 000340 | JASON | R | GOUNOT | E21 | 5698 | 1977-05-05 | FIELDREP | 16 | M | 1956-05-17 | 43840.00 | 500.00 | 1907.00 | 2006-03-31 | GOUNOT | Quebec | 1 - 000340 | JASON | R | GOUNOT | E21 | 5698 | 1977-05-05 | FIELDREP | 16 | M | 1956-05-17 | 43840.00 | 500.00 | 1907.00 | 2006-03-31 | GOUNOT | Ontario-South | 2 - 000340 | JASON | R | GOUNOT | E21 | 5698 | 1977-05-05 | FIELDREP | 16 | M | 1956-05-17 | 43840.00 | 500.00 | 1907.00 | 2006-03-31 | GOUNOT | Manitoba | 1 - 000340 | JASON | R | GOUNOT | E21 | 5698 | 1977-05-05 | FIELDREP | 16 | M | 1956-05-17 | 43840.00 | 500.00 | 1907.00 | 2006-03-30 | GOUNOT | Quebec | 18 - 000340 | JASON | R | GOUNOT | E21 | 5698 | 1977-05-05 | FIELDREP | 16 | M | 1956-05-17 | 43840.00 | 500.00 | 1907.00 | 2006-03-30 | GOUNOT | Ontario-South | 2 - 000340 | JASON | R | GOUNOT | E21 | 5698 | 1977-05-05 | FIELDREP | 16 | M | 1956-05-17 | 43840.00 | 500.00 | 1907.00 | 2006-03-29 | GOUNOT | Manitoba | 7 - 000340 | JASON | R | GOUNOT | E21 | 5698 | 1977-05-05 | FIELDREP | 16 | M | 1956-05-17 | 43840.00 | 500.00 | 1907.00 | 2006-03-29 | GOUNOT | Quebec | 1 - 000340 | JASON | R | GOUNOT | E21 | 5698 | 1977-05-05 | FIELDREP | 16 | M | 1956-05-17 | 43840.00 | 500.00 | 1907.00 | 2006-03-29 | GOUNOT | Ontario-South | 3 + 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2005-12-31 | LEE | Quebec | 1 + 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2005-12-31 | LEE | Manitoba | 2 000340 | JASON | R | GOUNOT | E21 | 5698 | 1977-05-05 | FIELDREP | 16 | M | 1956-05-17 | 43840.00 | 500.00 | 1907.00 | 2005-12-31 | GOUNOT | Quebec | 1 -(41 Zeilen) + 000110 | VINCENZO | G | LUCCHESSI | A00 | 3490 | 1988-05-16 | SALESREP | 19 | M | 1959-11-05 | 66500.00 | 900.00 | 3720.00 | 2006-03-29 | LUCCHESSI | Ontario-South | 3 + 000110 | VINCENZO | G | LUCCHESSI | A00 | 3490 | 1988-05-16 | SALESREP | 19 | M | 1959-11-05 | 66500.00 | 900.00 | 3720.00 | 2006-03-29 | LUCCHESSI | Quebec | 1 + 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-03-29 | LEE | Ontario-South | 2 + 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 1996-03-29 | LEE | Ontario-North | 2 + 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-03-29 | LEE | Quebec | 3 + 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-03-29 | LEE | Manitoba | 5 + 000340 | JASON | R | GOUNOT | E21 | 5698 | 1977-05-05 | FIELDREP | 16 | M | 1956-05-17 | 43840.00 | 500.00 | 1907.00 | 2006-03-29 | GOUNOT | Ontario-South | 3 + 000340 | JASON | R | GOUNOT | E21 | 5698 | 1977-05-05 | FIELDREP | 16 | M | 1956-05-17 | 43840.00 | 500.00 | 1907.00 | 2006-03-29 | GOUNOT | Quebec | 1 + 000340 | JASON | R | GOUNOT | E21 | 5698 | 1977-05-05 | FIELDREP | 16 | M | 1956-05-17 | 43840.00 | 500.00 | 1907.00 | 2006-03-29 | GOUNOT | Manitoba | 7 + 000110 | VINCENZO | G | LUCCHESSI | A00 | 3490 | 1988-05-16 | SALESREP | 19 | M | 1959-11-05 | 66500.00 | 900.00 | 3720.00 | 2006-03-30 | LUCCHESSI | Ontario-South | 1 + 000110 | VINCENZO | G | LUCCHESSI | A00 | 3490 | 1988-05-16 | SALESREP | 19 | M | 1959-11-05 | 66500.00 | 900.00 | 3720.00 | 2006-03-30 | LUCCHESSI | Quebec | 2 + 000110 | VINCENZO | G | LUCCHESSI | A00 | 3490 | 1988-05-16 | SALESREP | 19 | M | 1959-11-05 | 66500.00 | 900.00 | 3720.00 | 2006-03-30 | LUCCHESSI | Manitoba | 1 + 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-03-30 | LEE | Ontario-South | 7 + 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-03-30 | LEE | Ontario-North | 3 + 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-03-30 | LEE | Quebec | 7 + 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-03-30 | LEE | Manitoba | 4 + 000340 | JASON | R | GOUNOT | E21 | 5698 | 1977-05-05 | FIELDREP | 16 | M | 1956-05-17 | 43840.00 | 500.00 | 1907.00 | 2006-03-30 | GOUNOT | Ontario-South | 2 + 000340 | JASON | R | GOUNOT | E21 | 5698 | 1977-05-05 | FIELDREP | 16 | M | 1956-05-17 | 43840.00 | 500.00 | 1907.00 | 2006-03-30 | GOUNOT | Quebec | 18 + 000340 | JASON | R | GOUNOT | E21 | 5698 | 1977-05-05 | FIELDREP | 16 | M | 1956-05-17 | 43840.00 | 500.00 | 1907.00 | 2006-03-31 | GOUNOT | Manitoba | 1 + 000110 | VINCENZO | G | LUCCHESSI | A00 | 3490 | 1988-05-16 | SALESREP | 19 | M | 1959-11-05 | 66500.00 | 900.00 | 3720.00 | 2006-03-31 | LUCCHESSI | Manitoba | 1 + 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-03-31 | LEE | Ontario-South | 14 + 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-03-31 | LEE | Ontario-North | 3 + 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-03-31 | LEE | Quebec | 7 + 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-03-31 | LEE | Manitoba | 3 + 000340 | JASON | R | GOUNOT | E21 | 5698 | 1977-05-05 | FIELDREP | 16 | M | 1956-05-17 | 43840.00 | 500.00 | 1907.00 | 2006-03-31 | GOUNOT | Ontario-South | 2 + 000340 | JASON | R | GOUNOT | E21 | 5698 | 1977-05-05 | FIELDREP | 16 | M | 1956-05-17 | 43840.00 | 500.00 | 1907.00 | 2006-03-31 | GOUNOT | Quebec | 1 + 000110 | VINCENZO | G | LUCCHESSI | A00 | 3490 | 1988-05-16 | SALESREP | 19 | M | 1959-11-05 | 66500.00 | 900.00 | 3720.00 | 2006-04-01 | LUCCHESSI | Ontario-South | 3 + 000110 | VINCENZO | G | LUCCHESSI | A00 | 3490 | 1988-05-16 | SALESREP | 19 | M | 1959-11-05 | 66500.00 | 900.00 | 3720.00 | 2006-04-01 | LUCCHESSI | Manitoba | 1 + 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-04-01 | LEE | Ontario-South | 8 + 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-04-01 | LEE | Ontario-North | + 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-04-01 | LEE | Quebec | 8 + 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-04-01 | LEE | Manitoba | 9 + 000340 | JASON | R | GOUNOT | E21 | 5698 | 1977-05-05 | FIELDREP | 16 | M | 1956-05-17 | 43840.00 | 500.00 | 1907.00 | 2006-04-01 | GOUNOT | Ontario-South | 3 + 000340 | JASON | R | GOUNOT | E21 | 5698 | 1977-05-05 | FIELDREP | 16 | M | 1956-05-17 | 43840.00 | 500.00 | 1907.00 | 2006-04-01 | GOUNOT | Ontario-North | 1 + 000340 | JASON | R | GOUNOT | E21 | 5698 | 1977-05-05 | FIELDREP | 16 | M | 1956-05-17 | 43840.00 | 500.00 | 1907.00 | 2006-04-01 | GOUNOT | Quebec | 3 + 000340 | JASON | R | GOUNOT | E21 | 5698 | 1977-05-05 | FIELDREP | 16 | M | 1956-05-17 | 43840.00 | 500.00 | 1907.00 | 2006-04-01 | GOUNOT | Manitoba | 7 +(41 rows) --- create a local table importing its structure and content from an fdw table +-- +-- End of TC003 +-- +-- running tc004.sql +\i tc004.sql +-- +-- TC004: clone a foreign table into a local table including content +-- create table sample.orgcopy as select * from sample.org; SELECT 8 \d+ sample.org* - Fremdtabelle »sample.org« - Spalte | Typ | Sortierfolge | NULL erlaubt? | Vorgabewert | FDW-Optionen | Speicherung | Statistikziel | Beschreibung -----------+-----------------------+--------------+---------------+-------------+--------------+-------------+---------------+-------------- - deptnumb | smallint | | not null | | (key 'yes') | plain | | - deptname | character varying(14) | | | | | extended | | - manager | smallint | | | | | plain | | - division | character varying(10) | | | | | extended | | - location | character varying(13) | | | | | extended | | -Not-Null-Constraints: + Foreign table "sample.org" + Column | Type | Collation | Nullable | Default | FDW options | Storage | Stats target | Description +----------+-----------------------+-----------+----------+---------+-------------+----------+--------------+------------- + deptnumb | smallint | | not null | | (key 'yes') | plain | | + deptname | character varying(14) | | | | | extended | | + manager | smallint | | | | | plain | | + division | character varying(10) | | | | | extended | | + location | character varying(13) | | | | | extended | | +Not-null constraints: "org_deptnumb_not_null" NOT NULL "deptnumb" Server: sample -FDW-Optionen: (schema 'DB2INST1', "table" 'ORG') - - Tabelle »sample.orgcopy« - Spalte | Typ | Sortierfolge | NULL erlaubt? | Vorgabewert | Speicherung | Kompression | Statistikziel | Beschreibung -----------+-----------------------+--------------+---------------+-------------+-------------+-------------+---------------+-------------- - deptnumb | smallint | | | | plain | | | - deptname | character varying(14) | | | | extended | | | - manager | smallint | | | | plain | | | - division | character varying(10) | | | | extended | | | - location | character varying(13) | | | | extended | | | -Zugriffsmethode: heap +FDW options: (schema 'DB2INST1', "table" 'ORG') + + Table "sample.orgcopy" + Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description +----------+-----------------------+-----------+----------+---------+----------+-------------+--------------+------------- + deptnumb | smallint | | | | plain | | | + deptname | character varying(14) | | | | extended | | | + manager | smallint | | | | plain | | | + division | character varying(10) | | | | extended | | | + location | character varying(13) | | | | extended | | | +Access method: heap drop table sample.orgcopy; DROP TABLE --- cleanup +-- +-- END of TC004 +-- +-- running tc005.sql +\i tc005.sql +-- +-- TC005: run a pushdown query for aggregate functions +-- +\d+ sample.employee; + Foreign table "sample.employee" + Column | Type | Collation | Nullable | Default | FDW options | Storage | Stats target | Description +-----------+-----------------------+-----------+----------+---------+----------------------------------------------------------------------------------------------------------------+----------+--------------+------------- + empno | character(6) | | not null | | (db2type '1', db2size '6', db2bytes '6', db2chars '0', db2scale '0', db2null '0', db2ccsid '1208', key 'true') | extended | | + firstnme | character varying(12) | | not null | | (db2type '12', db2size '12', db2bytes '12', db2chars '0', db2scale '0', db2null '0', db2ccsid '1208') | extended | | + midinit | character(1) | | | | (db2type '1', db2size '1', db2bytes '1', db2chars '0', db2scale '0', db2null '1', db2ccsid '1208') | extended | | + lastname | character varying(15) | | not null | | (db2type '12', db2size '15', db2bytes '15', db2chars '0', db2scale '0', db2null '0', db2ccsid '1208') | extended | | + workdept | character(3) | | | | (db2type '1', db2size '3', db2bytes '3', db2chars '0', db2scale '0', db2null '1', db2ccsid '1208') | extended | | + phoneno | character(4) | | | | (db2type '1', db2size '4', db2bytes '4', db2chars '0', db2scale '0', db2null '1', db2ccsid '1208') | extended | | + hiredate | date | | | | (db2type '91', db2size '10', db2bytes '6', db2chars '0', db2scale '0', db2null '1', db2ccsid '0') | plain | | + job | character(8) | | | | (db2type '1', db2size '8', db2bytes '8', db2chars '0', db2scale '0', db2null '1', db2ccsid '1208') | extended | | + edlevel | smallint | | not null | | (db2type '5', db2size '5', db2bytes '2', db2chars '5', db2scale '0', db2null '0', db2ccsid '0') | plain | | + sex | character(1) | | | | (db2type '1', db2size '1', db2bytes '1', db2chars '0', db2scale '0', db2null '1', db2ccsid '1208') | extended | | + birthdate | date | | | | (db2type '91', db2size '10', db2bytes '6', db2chars '0', db2scale '0', db2null '1', db2ccsid '0') | plain | | + salary | numeric(9,2) | | | | (db2type '3', db2size '9', db2bytes '11', db2chars '9', db2scale '2', db2null '1', db2ccsid '0') | main | | + bonus | numeric(9,2) | | | | (db2type '3', db2size '9', db2bytes '11', db2chars '9', db2scale '2', db2null '1', db2ccsid '0') | main | | + comm | numeric(9,2) | | | | (db2type '3', db2size '9', db2bytes '11', db2chars '9', db2scale '2', db2null '1', db2ccsid '0') | main | | +Not-null constraints: + "employee_empno_not_null" NOT NULL "empno" + "employee_firstnme_not_null" NOT NULL "firstnme" + "employee_lastname_not_null" NOT NULL "lastname" + "employee_edlevel_not_null" NOT NULL "edlevel" +Server: sample +FDW options: (schema 'DB2INST1', "table" 'EMPLOYEE') + +explain (analyze,verbose) select min(salary),max(salary),avg(salary),sum(salary +comm + bonus),count(*) from sample.employee; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan (cost=121.72..144.35 rows=1 width=136) (actual time=3.002..3.137 rows=1.00 loops=1) + Output: (min(salary)), (max(salary)), (avg(salary)), (sum(((salary + comm) + bonus))), (count(*)) + DB2 query: SELECT MIN("SALARY"), MAX("SALARY"), AVG("SALARY"), SUM((("SALARY" + "COMM") + "BONUS")), COUNT(*) FROM "DB2INST1"."EMPLOYEE" + DB2 plan: + DB2 plan: DB2 Universal Database Version 11.5, 5622-044 (c) Copyright IBM Corp. 1991, 2019 + DB2 plan: Licensed Material - Program Property of IBM + DB2 plan: IBM DB2 Universal Database SQL and XQUERY Explain Tool + DB2 plan: + DB2 plan: DB2 Universal Database Version 12.1, 5622-044 (c) Copyright IBM Corp. 1991, 2022 + DB2 plan: Licensed Material - Program Property of IBM + DB2 plan: IBM DB2 Universal Database SQL and XQUERY Explain Tool + DB2 plan: + DB2 plan: ******************** DYNAMIC *************************************** + DB2 plan: + DB2 plan: ==================== STATEMENT ========================================== + DB2 plan: + DB2 plan: Isolation Level = Cursor Stability + DB2 plan: Blocking = Block Unambiguous Cursors + DB2 plan: Query Optimization Class = 5 + DB2 plan: + DB2 plan: Partition Parallel = No + DB2 plan: Intra-Partition Parallel = No + DB2 plan: + DB2 plan: SQL Path = "SYSIBM", "SYSFUN", "SYSPROC", "SYSIBMADM", + DB2 plan: "SYSHADOOP", "DB2INST1" + DB2 plan: + DB2 plan: + DB2 plan: Statement: + DB2 plan: + DB2 plan: SELECT MIN("SALARY" ), MAX("SALARY" ), AVG("SALARY" ), SUM((( + DB2 plan: "SALARY" + "COMM" )+ "BONUS" )), COUNT(*) + DB2 plan: FROM "DB2INST1" ."EMPLOYEE" + DB2 plan: + DB2 plan: + DB2 plan: compiledInTenantID = 0 + DB2 plan: + DB2 plan: Section Code Page = 1208 + DB2 plan: + DB2 plan: Estimated Cost = 6,817206 + DB2 plan: Estimated Cardinality = 1,000000 + DB2 plan: + DB2 plan: Access Table Name = DB2INST1.EMPLOYEE ID = 2,6 + DB2 plan: | tenantID = 0 + DB2 plan: | #Columns = 3 + DB2 plan: | Skip Inserted Rows + DB2 plan: | Avoid Locking Committed Data + DB2 plan: | Currently Committed for Cursor Stability + DB2 plan: | May participate in Scan Sharing structures + DB2 plan: | Scan may start anywhere and wrap, for completion + DB2 plan: | Fast scan, for purposes of scan sharing management + DB2 plan: | Scan can be throttled in scan sharing management + DB2 plan: | Relation Scan + DB2 plan: | | Prefetch: Eligible + DB2 plan: | Lock Intents + DB2 plan: | | Table: Intent Share + DB2 plan: | | Row : Next Key Share + DB2 plan: | Sargable Predicate(s) + DB2 plan: | | Predicate Aggregation + DB2 plan: | | | Column Function(s) + DB2 plan: Aggregation Completion + DB2 plan: | Column Function(s) + DB2 plan: Return Data to Application + DB2 plan: | #Columns = 5 + DB2 plan: + DB2 plan: End of section + DB2 plan: + DB2 plan: + Planning: + Buffers: shared hit=20 + Planning Time: 0.132 ms + Execution Time: 3.157 ms +(71 rows) + +select min(salary),max(salary),avg(salary),sum(salary +comm + bonus),count(*) from sample.employee; + min | max | avg | sum | count +---------+-----------+----------+------------+------- + 8000.00 | 152750.00 | 56988.95 | 2567223.00 | 43 +(1 row) + +-- +explain (analyze,verbose) select empno, firstnme,lastname, salary + bonus + comm from sample.employee where salary > 8000 and lastname like 'L%'; + QUERY PLAN +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on sample.employee (cost=100.00..116.89 rows=1 width=150) (actual time=2.818..2.988 rows=3.00 loops=1) + Output: empno, firstnme, lastname, ((salary + bonus) + comm) + DB2 query: SELECT "EMPNO", "FIRSTNME", "LASTNAME", "SALARY", "BONUS", "COMM" FROM "DB2INST1"."EMPLOYEE" WHERE (("SALARY" > 8000)) AND (("LASTNAME" LIKE 'L%' ESCAPE '\')) + DB2 plan: + DB2 plan: DB2 Universal Database Version 11.5, 5622-044 (c) Copyright IBM Corp. 1991, 2019 + DB2 plan: Licensed Material - Program Property of IBM + DB2 plan: IBM DB2 Universal Database SQL and XQUERY Explain Tool + DB2 plan: + DB2 plan: DB2 Universal Database Version 12.1, 5622-044 (c) Copyright IBM Corp. 1991, 2022 + DB2 plan: Licensed Material - Program Property of IBM + DB2 plan: IBM DB2 Universal Database SQL and XQUERY Explain Tool + DB2 plan: + DB2 plan: ******************** DYNAMIC *************************************** + DB2 plan: + DB2 plan: ==================== STATEMENT ========================================== + DB2 plan: + DB2 plan: Isolation Level = Cursor Stability + DB2 plan: Blocking = Block Unambiguous Cursors + DB2 plan: Query Optimization Class = 5 + DB2 plan: + DB2 plan: Partition Parallel = No + DB2 plan: Intra-Partition Parallel = No + DB2 plan: + DB2 plan: SQL Path = "SYSIBM", "SYSFUN", "SYSPROC", "SYSIBMADM", + DB2 plan: "SYSHADOOP", "DB2INST1" + DB2 plan: + DB2 plan: + DB2 plan: Statement: + DB2 plan: + DB2 plan: SELECT "EMPNO" , "FIRSTNME" , "LASTNAME" , "SALARY" , "BONUS" , + DB2 plan: "COMM" + DB2 plan: FROM "DB2INST1" ."EMPLOYEE" + DB2 plan: WHERE (("SALARY" > 8000))AND (("LASTNAME" LIKE 'L%' ESCAPE '\' )) + DB2 plan: + DB2 plan: + DB2 plan: compiledInTenantID = 0 + DB2 plan: + DB2 plan: Section Code Page = 1208 + DB2 plan: + DB2 plan: Estimated Cost = 6,816737 + DB2 plan: Estimated Cardinality = 3,714774 + DB2 plan: + DB2 plan: Access Table Name = DB2INST1.EMPLOYEE ID = 2,6 + DB2 plan: | tenantID = 0 + DB2 plan: | #Columns = 6 + DB2 plan: | Skip Inserted Rows + DB2 plan: | Avoid Locking Committed Data + DB2 plan: | Currently Committed for Cursor Stability + DB2 plan: | May participate in Scan Sharing structures + DB2 plan: | Scan may start anywhere and wrap, for completion + DB2 plan: | Fast scan, for purposes of scan sharing management + DB2 plan: | Scan can be throttled in scan sharing management + DB2 plan: | Relation Scan + DB2 plan: | | Prefetch: Eligible + DB2 plan: | Lock Intents + DB2 plan: | | Table: Intent Share + DB2 plan: | | Row : Next Key Share + DB2 plan: | Sargable Predicate(s) + DB2 plan: | | #Predicates = 2 + DB2 plan: | | Return Data to Application + DB2 plan: | | | #Columns = 6 + DB2 plan: Return Data Completion + DB2 plan: + DB2 plan: End of section + DB2 plan: + DB2 plan: + Planning: + Buffers: shared hit=3 + Planning Time: 0.140 ms + Execution Time: 3.010 ms +(70 rows) + +select empno, firstnme,lastname, salary + bonus + comm from sample.employee where salary > 8000 and lastname like 'L%'; + empno | firstnme | lastname | ?column? +--------+----------+-----------+---------- + 000110 | VINCENZO | LUCCHESSI | 71120.00 + 000220 | JENNIFER | LUTZ | 52827.00 + 000330 | WING | LEE | 47900.00 +(3 rows) + +-- +-- END of TC004 +-- +-- testcases ended +-- starting cleanup \c postgres -Sie sind jetzt verbunden mit der Datenbank »postgres« als Benutzer »postgres«. +You are now connected to database "postgres" as user "postgres". DROP DATABASE regtest; DROP DATABASE --- +-- test finished From 2158e01d49ae85579092566556c53cf61a7e529d Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Wed, 6 May 2026 10:27:41 +0200 Subject: [PATCH 110/120] update license and remove LivingMainframe from it --- LICENSE | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/LICENSE b/LICENSE index 8f2003a..aa1a3cd 100644 --- a/LICENSE +++ b/LICENSE @@ -1,9 +1,9 @@ The PostgreSQL License Copyright (c) 2023, Ing Wolfgang Brandl. -Copyright (c) 2025, Thomas Muenz, Living-Mainframe GmbH. +Copyright (c) 2025, Thomas Muenz, PG_FDW Project. -Wolfgang Brandl, Thomas Muenz and Living-Mainframe Gmbh, furthermore called "the developers". +Wolfgang Brandl, Thomas Muenz, furthermore called "the developers". Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement is hereby granted, provided that the above copyright notice and this paragraph and the following two paragraphs appear in all copies. From d819f272c5b1b09bfe3fb71d4d20243cd53d382f Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Sat, 9 May 2026 15:04:06 +0200 Subject: [PATCH 111/120] fixing TC007 --- source/db2ExecuteInsert.c | 13 ++++++++++--- source/db2ExecuteQuery.c | 14 +++++++++++--- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/source/db2ExecuteInsert.c b/source/db2ExecuteInsert.c index 62514dd..f505613 100644 --- a/source/db2ExecuteInsert.c +++ b/source/db2ExecuteInsert.c @@ -34,14 +34,21 @@ int db2ExecuteInsert (DB2Session* session, ParamDesc* paramList) { SQLCHAR cname[256] = {0}; /* 256 is usually plenty; see note below */ int rowcount = 0; int param_count = 0; + int indicator_count = 0; db2Entry1(); for (param = paramList; param != NULL; param = param->next) { ++param_count; } db2Debug2("paramcount: %d",param_count); - /* allocate a temporary array of indicators */ - indicators = db2alloc ((param_count * sizeof (SQLLEN)), "indicators[%d]", param_count); + /* + * Allocate a temporary array of indicators. + * + * We use 1-based indexing below (indicator[1..param_count]) to match the + * parameter numbering passed to SQLBindParameter. + */ + indicator_count = param_count + 1; + indicators = db2alloc(indicator_count * sizeof(SQLLEN), "indicators[%d]", indicator_count); /* bind the parameters */ param_count = 0; @@ -67,7 +74,7 @@ int db2ExecuteInsert (DB2Session* session, ParamDesc* paramList) { } /* db2free all indicators */ - db2free (indicators, "indicators[%d]", param_count); + db2free (indicators, "indicators[%d]", indicator_count); if (rc == SQL_NO_DATA) { db2Debug3("SQL_NO_DATA"); } else { diff --git a/source/db2ExecuteQuery.c b/source/db2ExecuteQuery.c index 09a9f91..d9decaf 100644 --- a/source/db2ExecuteQuery.c +++ b/source/db2ExecuteQuery.c @@ -35,14 +35,22 @@ int db2ExecuteQuery (DB2Session* session, ParamDesc* paramList) { SQLCHAR cname[256] = {0}; /* 256 is usually plenty; see note below */ int rowcount = 0; int param_count = 0; + int indicator_count = 0; db2Entry1(); for (param = paramList; param != NULL; param = param->next) { ++param_count; } db2Debug2("paramcount: %d",param_count); - /* allocate a temporary array of indicators */ - indicators = db2alloc (param_count * sizeof (SQLLEN),"indicators[%d]",param_count); + /* + * Allocate a temporary array of indicators. + * + * Note: we intentionally use 1-based indexing below (indicator[1..param_count]) + * to match the parameter numbering passed to SQLBindParameter. Therefore we + * must allocate param_count + 1 entries. + */ + indicator_count = param_count + 1; + indicators = db2alloc(indicator_count * sizeof(SQLLEN), "indicators[%d]", indicator_count); /* bind the parameters */ param_count = 0; @@ -64,7 +72,7 @@ int db2ExecuteQuery (DB2Session* session, ParamDesc* paramList) { /* use the correct SQLSTATE for serialization failures */ db2Error_d(err_code == 8177 ? FDW_SERIALIZATION_FAILURE : FDW_UNABLE_TO_CREATE_EXECUTION, "error executing query: SQLExecute failed to execute remote query", db2Message); } - db2free(indicators,"indicators[%d]",param_count); + db2free(indicators, "indicators[%d]", indicator_count); if (rc == SQL_NO_DATA) { db2Debug3("SQL_NO_DATA"); } else { From f832ae5357eb19cb7763a446fdaea8c571a00f1c Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Sat, 9 May 2026 15:04:19 +0200 Subject: [PATCH 112/120] new testcases added --- test/expected/base.out | 383 ++++++++++++++++++++++++++++++++++++++--- test/sql/base.sql | 33 +++- test/sql/tc006.sql | 28 +++ test/sql/tc007.sql | 16 ++ test/sql/tc008.sql | 24 +++ test/sql/tc009.sql | 10 ++ test/sql/tc010.sql | 17 ++ test/sql/tc011.sql | 10 ++ test/sql/tc012.sql | 14 ++ 9 files changed, 509 insertions(+), 26 deletions(-) create mode 100644 test/sql/tc006.sql create mode 100644 test/sql/tc007.sql create mode 100644 test/sql/tc008.sql create mode 100644 test/sql/tc009.sql create mode 100644 test/sql/tc010.sql create mode 100644 test/sql/tc011.sql create mode 100644 test/sql/tc012.sql diff --git a/test/expected/base.out b/test/expected/base.out index c3bae79..1b5ca7f 100644 --- a/test/expected/base.out +++ b/test/expected/base.out @@ -20,6 +20,12 @@ CREATE SCHEMA -- Import the complete sample db into the local schema IMPORT FOREIGN SCHEMA "DB2INST1" FROM SERVER sample INTO sample; IMPORT FOREIGN SCHEMA +-- For UPDATE/DELETE, db2_fdw requires at least one primary key column marked +-- with the column option "key". +-- The DB2 SAMPLE.ORG table uses DEPTNUMB as its primary key. +ALTER FOREIGN TABLE sample.org + ALTER COLUMN deptnumb OPTIONS (ADD key 'true'); +ALTER FOREIGN TABLE -- list imported tables \detr+ sample.* List of foreign tables @@ -85,22 +91,24 @@ select db2_diag(); db2_fdw 18.2.0, PostgreSQL 18.1, DB2INSTANCE=postgres, DB2_HOME=/var/lib/pgsql/sqllib, DB2LIB=/var/lib/pgsql/sqllib/lib (1 row) +set log_min_messages=debug5; +SET -- starting testcases -- running tc001.sql -\i tc001.sql +\i ./test/sql/tc001.sql -- -- TC001: Dropping and re-creating a foreign table "manually" -- -- drop an imported table \d+ sample.org; - Foreign table "sample.org" - Column | Type | Collation | Nullable | Default | FDW options | Storage | Stats target | Description -----------+-----------------------+-----------+----------+---------+-------------------------------------------------------------------------------------------------------+----------+--------------+------------- - deptnumb | smallint | | not null | | (db2type '5', db2size '5', db2bytes '2', db2chars '5', db2scale '0', db2null '0', db2ccsid '0') | plain | | - deptname | character varying(14) | | | | (db2type '12', db2size '14', db2bytes '14', db2chars '0', db2scale '0', db2null '1', db2ccsid '1208') | extended | | - manager | smallint | | | | (db2type '5', db2size '5', db2bytes '2', db2chars '5', db2scale '0', db2null '1', db2ccsid '0') | plain | | - division | character varying(10) | | | | (db2type '12', db2size '10', db2bytes '10', db2chars '0', db2scale '0', db2null '1', db2ccsid '1208') | extended | | - location | character varying(13) | | | | (db2type '12', db2size '13', db2bytes '13', db2chars '0', db2scale '0', db2null '1', db2ccsid '1208') | extended | | + Foreign table "sample.org" + Column | Type | Collation | Nullable | Default | FDW options | Storage | Stats target | Description +----------+-----------------------+-----------+----------+---------+-------------------------------------------------------------------------------------------------------------+----------+--------------+------------- + deptnumb | smallint | | not null | | (db2type '5', db2size '5', db2bytes '2', db2chars '5', db2scale '0', db2null '0', db2ccsid '0', key 'true') | plain | | + deptname | character varying(14) | | | | (db2type '12', db2size '14', db2bytes '14', db2chars '0', db2scale '0', db2null '1', db2ccsid '1208') | extended | | + manager | smallint | | | | (db2type '5', db2size '5', db2bytes '2', db2chars '5', db2scale '0', db2null '1', db2ccsid '0') | plain | | + division | character varying(10) | | | | (db2type '12', db2size '10', db2bytes '10', db2chars '0', db2scale '0', db2null '1', db2ccsid '1208') | extended | | + location | character varying(13) | | | | (db2type '12', db2size '13', db2bytes '13', db2chars '0', db2scale '0', db2null '1', db2ccsid '1208') | extended | | Not-null constraints: "org_deptnumb_not_null" NOT NULL "deptnumb" Server: sample @@ -110,7 +118,7 @@ DROP FOREIGN TABLE IF EXISTS sample.org; DROP FOREIGN TABLE -- recreate it manually \d+ sample.org; -psql:tc001.sql:8: error: Did not find any relation named "sample.org". +psql:test/sql/tc001.sql:8: error: Did not find any relation named "sample.org". CREATE FOREIGN TABLE sample.org ( DEPTNUMB SMALLINT OPTIONS (key 'yes') NOT NULL , DEPTNAME VARCHAR(14) , @@ -139,7 +147,7 @@ FDW options: (schema 'DB2INST1', "table" 'ORG') -- -- remove its content delete from sample.org; -DELETE 8 +DELETE 9 SELECT * FROM sample.org; deptnumb | deptname | manager | division | location ----------+----------+---------+----------+---------- @@ -180,7 +188,7 @@ SELECT * FROM sample.org; -- END of TC001 -- -- running tc002.sql -\i tc002.sql +\i ./test/sql/tc002.sql -- -- TC002: Importing a single foreign table from a remote schema -- @@ -273,7 +281,7 @@ SELECT * FROM sample.act; -- END of TC002 -- -- running tc003.sql -\i tc003.sql +\i ./test/sql/tc003.sql -- -- TC003: Run a simple join -- @@ -365,7 +373,7 @@ select * from sample.employee a, sample.sales b where a.lastname = b.sales_perso -- End of TC003 -- -- running tc004.sql -\i tc004.sql +\i ./test/sql/tc004.sql -- -- TC004: clone a foreign table into a local table including content -- @@ -401,7 +409,7 @@ DROP TABLE -- END of TC004 -- -- running tc005.sql -\i tc005.sql +\i ./test/sql/tc005.sql -- -- TC005: run a pushdown query for aggregate functions -- @@ -434,7 +442,7 @@ FDW options: (schema 'DB2INST1', "table" 'EMPLOYEE') explain (analyze,verbose) select min(salary),max(salary),avg(salary),sum(salary +comm + bonus),count(*) from sample.employee; QUERY PLAN -------------------------------------------------------------------------------------------------------------------------------------------- - Foreign Scan (cost=121.72..144.35 rows=1 width=136) (actual time=3.002..3.137 rows=1.00 loops=1) + Foreign Scan (cost=121.72..144.35 rows=1 width=136) (actual time=3.864..4.106 rows=1.00 loops=1) Output: (min(salary)), (max(salary)), (avg(salary)), (sum(((salary + comm) + bonus))), (count(*)) DB2 query: SELECT MIN("SALARY"), MAX("SALARY"), AVG("SALARY"), SUM((("SALARY" + "COMM") + "BONUS")), COUNT(*) FROM "DB2INST1"."EMPLOYEE" DB2 plan: @@ -503,8 +511,8 @@ explain (analyze,verbose) select min(salary),max(salary),avg(salary),sum(salary DB2 plan: Planning: Buffers: shared hit=20 - Planning Time: 0.132 ms - Execution Time: 3.157 ms + Planning Time: 3.501 ms + Execution Time: 5.306 ms (71 rows) select min(salary),max(salary),avg(salary),sum(salary +comm + bonus),count(*) from sample.employee; @@ -517,7 +525,7 @@ select min(salary),max(salary),avg(salary),sum(salary +comm + bonus),count(*) fr explain (analyze,verbose) select empno, firstnme,lastname, salary + bonus + comm from sample.employee where salary > 8000 and lastname like 'L%'; QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - Foreign Scan on sample.employee (cost=100.00..116.89 rows=1 width=150) (actual time=2.818..2.988 rows=3.00 loops=1) + Foreign Scan on sample.employee (cost=100.00..116.89 rows=1 width=150) (actual time=3.881..4.902 rows=3.00 loops=1) Output: empno, firstnme, lastname, ((salary + bonus) + comm) DB2 query: SELECT "EMPNO", "FIRSTNME", "LASTNAME", "SALARY", "BONUS", "COMM" FROM "DB2INST1"."EMPLOYEE" WHERE (("SALARY" > 8000)) AND (("LASTNAME" LIKE 'L%' ESCAPE '\')) DB2 plan: @@ -585,8 +593,8 @@ explain (analyze,verbose) select empno, firstnme,lastname, salary + bonus + comm DB2 plan: Planning: Buffers: shared hit=3 - Planning Time: 0.140 ms - Execution Time: 3.010 ms + Planning Time: 3.898 ms + Execution Time: 6.151 ms (70 rows) select empno, firstnme,lastname, salary + bonus + comm from sample.employee where salary > 8000 and lastname like 'L%'; @@ -600,6 +608,339 @@ select empno, firstnme,lastname, salary + bonus + comm from sample.employee wher -- -- END of TC004 -- +-- running tc006.sql +\i ./test/sql/tc006.sql +-- +-- TC006: UPDATE/DELETE against a key column and revert to baseline +-- +\d+ sample.org; + Foreign table "sample.org" + Column | Type | Collation | Nullable | Default | FDW options | Storage | Stats target | Description +----------+-----------------------+-----------+----------+---------+-------------+----------+--------------+------------- + deptnumb | smallint | | not null | | (key 'yes') | plain | | + deptname | character varying(14) | | | | | extended | | + manager | smallint | | | | | plain | | + division | character varying(10) | | | | | extended | | + location | character varying(13) | | | | | extended | | +Not-null constraints: + "org_deptnumb_not_null" NOT NULL "deptnumb" +Server: sample +FDW options: (schema 'DB2INST1', "table" 'ORG') + +-- Baseline row +SELECT deptnumb, deptname, location FROM sample.org WHERE deptnumb = 10; + deptnumb | deptname | location +----------+-------------+---------- + 10 | Head Office | New York +(1 row) + +-- UPDATE + verify +UPDATE sample.org SET location = 'New York City' WHERE deptnumb = 10; +psql:test/sql/tc006.sql:10: FEHLER: pfree called with invalid pointer 0x425e530 (header 0x0000000000000002) +SELECT deptnumb, deptname, location FROM sample.org WHERE deptnumb = 10; + deptnumb | deptname | location +----------+-------------+---------- + 10 | Head Office | New York +(1 row) + +-- Revert + verify +UPDATE sample.org SET location = 'New York' WHERE deptnumb = 10; +psql:test/sql/tc006.sql:14: FEHLER: pfree called with invalid pointer 0x425e530 (header 0x0000000000000002) +SELECT deptnumb, deptname, location FROM sample.org WHERE deptnumb = 10; + deptnumb | deptname | location +----------+-------------+---------- + 10 | Head Office | New York +(1 row) + +-- DELETE + INSERT to test DELETE/INSERT pair (choose a row we can reconstruct deterministically) +SELECT * FROM sample.org WHERE deptnumb = 84; + deptnumb | deptname | manager | division | location +----------+----------+---------+----------+---------- + 84 | Mountain | 290 | Western | Denver +(1 row) + +DELETE FROM sample.org WHERE deptnumb = 84; +psql:test/sql/tc006.sql:19: FEHLER: pfree called with invalid pointer 0x425e530 (header 0x000000000425e4f8) +SELECT * FROM sample.org WHERE deptnumb = 84; + deptnumb | deptname | manager | division | location +----------+----------+---------+----------+---------- + 84 | Mountain | 290 | Western | Denver +(1 row) + +INSERT INTO sample.org (deptnumb, deptname, manager, division, location) +VALUES (84, 'Mountain', 290, 'Western', 'Denver'); +INSERT 0 1 +SELECT * FROM sample.org WHERE deptnumb = 84; + deptnumb | deptname | manager | division | location +----------+----------+---------+----------+---------- + 84 | Mountain | 290 | Western | Denver + 84 | Mountain | 290 | Western | Denver +(2 rows) + +-- +-- END of TC006 +-- +-- running tc007.sql +\i ./test/sql/tc007.sql +-- +-- TC007: Transaction / rollback semantics for foreign table modifications +-- +SELECT deptnumb, location FROM sample.org WHERE deptnumb = 15; + deptnumb | location +----------+---------- + 15 | Boston +(1 row) + +BEGIN; +BEGIN + UPDATE sample.org SET location = 'Boston (temp)' WHERE deptnumb = 15; +psql:test/sql/tc007.sql:8: FEHLER: pfree called with invalid pointer 0x425e530 (header 0x0000000000000002) + SELECT deptnumb, location FROM sample.org WHERE deptnumb = 15; +psql:test/sql/tc007.sql:9: FEHLER: aktuelle Transaktion wurde abgebrochen, Befehle werden bis zum Ende der Transaktion ignoriert +ROLLBACK; +ROLLBACK +-- Should be back to original +SELECT deptnumb, location FROM sample.org WHERE deptnumb = 15; + deptnumb | location +----------+---------- + 15 | Boston +(1 row) + +-- +-- END of TC007 +-- +-- running tc008.sql +\i ./test/sql/tc008.sql +-- +-- TC008: Query-based foreign table +-- +DROP FOREIGN TABLE IF EXISTS sample.emp_l; +psql:test/sql/tc008.sql:4: HINWEIS: Fremdtabelle »emp_l« existiert nicht, wird übersprungen +DROP FOREIGN TABLE +CREATE FOREIGN TABLE sample.emp_l ( + empno char(6), + firstnme varchar(12), + lastname varchar(15) +) +SERVER sample +OPTIONS (schema 'DB2INST1',table 'EMPLOYEE', readonly 'true'); +CREATE FOREIGN TABLE +\d+ sample.emp_l; + Foreign table "sample.emp_l" + Column | Type | Collation | Nullable | Default | FDW options | Storage | Stats target | Description +----------+-----------------------+-----------+----------+---------+-------------+----------+--------------+------------- + empno | character(6) | | | | | extended | | + firstnme | character varying(12) | | | | | extended | | + lastname | character varying(15) | | | | | extended | | +Server: sample +FDW options: (schema 'DB2INST1', "table" 'EMPLOYEE', readonly 'true') + +SELECT * FROM sample.emp_l ORDER BY empno; + empno | firstnme | lastname +--------+-----------+---------- + 000010 | CHRISTINE | H + 000020 | MICHAEL | T + 000030 | SALLY | K + 000050 | JOHN | G + 000060 | IRVING | S + 000070 | EVA | P + 000090 | EILEEN | H + 000100 | THEODORE | S + 000110 | VINCENZO | L + 000120 | SEAN | O + 000130 | DELORES | Q + 000140 | HEATHER | N + 000150 | BRUCE | A + 000160 | ELIZABETH | P + 000170 | MASATOSHI | Y + 000180 | MARILYN | S + 000190 | JAMES | W + 000200 | DAVID | B + 000210 | WILLIAM | J + 000220 | JENNIFER | L + 000230 | JAMES | J + 000240 | SALVATORE | M + 000250 | DANIEL | S + 000260 | SYBIL | J + 000270 | MARIA | P + 000280 | ETHEL | S + 000290 | JOHN | P + 000300 | PHILIP | S + 000310 | MAUDE | S + 000320 | RAMLAL | M + 000330 | WING | L + 000340 | JASON | G + 200010 | DIAN | H + 200120 | GREG | O + 200140 | KIM | N + 200170 | KIYOSHI | Y + 200220 | REBA | J + 200240 | ROBERT | M + 200280 | EILEEN | S + 200310 | MICHELLE | S + 200330 | HELENA | W + 200340 | ROY | A + 300001 | Thomas | M +(43 rows) + +-- Optional: show it is read-only by intent (uncomment if you want a hard failure case) +INSERT INTO sample.emp_l(empno, firstnme, lastname) VALUES ('999999', 'X', 'Y'); +psql:test/sql/tc008.sql:18: FEHLER: Fremdtabelle »emp_l« erlaubt kein Einfügen +DROP FOREIGN TABLE sample.emp_l; +DROP FOREIGN TABLE +-- +-- END of TC008 +-- +-- running tc009.sql +\i ./test/sql/tc009.sql +-- +-- TC009: Close cached DB2 connections and verify reconnect works +-- +SELECT count(*) FROM sample.employee; + count +------- + 43 +(1 row) + +SELECT db2_close_connections(); + db2_close_connections +----------------------- + +(1 row) + +SELECT count(*) FROM sample.employee; + count +------- + 43 +(1 row) + +-- +-- END of TC009 +-- +-- running tc010.sql +\i ./test/sql/tc010.sql +-- +-- TC010: Pushdown predicates (IN/BETWEEN/OR/IS NULL) +-- +EXPLAIN (analyze,verbose) SELECT empno, lastname, salary FROM sample.employee WHERE workdept IN ('A00','B01','E21') AND salary BETWEEN 40000 AND 70000 AND (lastname LIKE 'L%' OR lastname LIKE 'G%'); +psql:test/sql/tc010.sql:4: FEHLER: variable not found in subplan target list +SELECT empno, lastname, salary FROM sample.employee WHERE workdept IN ('A00','B01','E21') AND salary BETWEEN 40000 AND 70000 AND (lastname LIKE 'L%' OR lastname LIKE 'G%') ORDER BY empno; +psql:test/sql/tc010.sql:6: FEHLER: variable not found in subplan target list +-- NULL handling sanity check +SELECT + count(*) AS total, + count(*) FILTER (WHERE comm IS NULL) AS comm_is_null, + count(*) FILTER (WHERE comm IS NOT NULL) AS comm_is_not_null +FROM sample.employee; + total | comm_is_null | comm_is_not_null +-------+--------------+------------------ + 43 | 43 | 43 +(1 row) + +-- +-- END of TC010 +-- +-- running tc011.sql +--\i ./test/sql/tc011.sql +-- running tc012.sql +\i ./test/sql/tc012.sql +-- +-- TC012: PREPARE/EXECUTE parameter binding +-- +PREPARE act_by_no(int) AS + SELECT actno, actkwd, actdesc FROM sample.act WHERE actno = $1; +PREPARE +EXPLAIN (analyze,verbose) EXECUTE act_by_no(10); + QUERY PLAN +---------------------------------------------------------------------------------------------------------------- + Foreign Scan on sample.act (cost=100.00..119.98 rows=4 width=88) (actual time=5.048..5.343 rows=1.00 loops=1) + Output: actno, actkwd, actdesc + DB2 query: SELECT "ACTNO", "ACTKWD", "ACTDESC" FROM "DB2INST1"."ACT" WHERE (("ACTNO" = 10)) + DB2 plan: + DB2 plan: DB2 Universal Database Version 11.5, 5622-044 (c) Copyright IBM Corp. 1991, 2019 + DB2 plan: Licensed Material - Program Property of IBM + DB2 plan: IBM DB2 Universal Database SQL and XQUERY Explain Tool + DB2 plan: + DB2 plan: DB2 Universal Database Version 12.1, 5622-044 (c) Copyright IBM Corp. 1991, 2022 + DB2 plan: Licensed Material - Program Property of IBM + DB2 plan: IBM DB2 Universal Database SQL and XQUERY Explain Tool + DB2 plan: + DB2 plan: ******************** DYNAMIC *************************************** + DB2 plan: + DB2 plan: ==================== STATEMENT ========================================== + DB2 plan: + DB2 plan: Isolation Level = Cursor Stability + DB2 plan: Blocking = Block Unambiguous Cursors + DB2 plan: Query Optimization Class = 5 + DB2 plan: + DB2 plan: Partition Parallel = No + DB2 plan: Intra-Partition Parallel = No + DB2 plan: + DB2 plan: SQL Path = "SYSIBM", "SYSFUN", "SYSPROC", "SYSIBMADM", + DB2 plan: "SYSHADOOP", "DB2INST1" + DB2 plan: + DB2 plan: + DB2 plan: Statement: + DB2 plan: + DB2 plan: SELECT "ACTNO" , "ACTKWD" , "ACTDESC" + DB2 plan: FROM "DB2INST1" ."ACT" + DB2 plan: WHERE (("ACTNO" =10)) + DB2 plan: + DB2 plan: + DB2 plan: compiledInTenantID = 0 + DB2 plan: + DB2 plan: Section Code Page = 1208 + DB2 plan: + DB2 plan: Estimated Cost = 6,809176 + DB2 plan: Estimated Cardinality = 1,000000 + DB2 plan: + DB2 plan: Access Table Name = DB2INST1.ACT ID = 2,12 + DB2 plan: | tenantID = 0 + DB2 plan: | Index Scan: Name = DB2INST1.XACT2 ID = 2 + DB2 plan: | | Regular Index (Not Clustered) + DB2 plan: | | Index Columns: + DB2 plan: | | | 1: ACTNO (Ascending) + DB2 plan: | | | 2: ACTKWD (Ascending) + DB2 plan: | #Columns = 3 + DB2 plan: | Single Record + DB2 plan: | Fully Qualified Unique Key + DB2 plan: | Skip Inserted Rows + DB2 plan: | Avoid Locking Committed Data + DB2 plan: | Currently Committed for Cursor Stability + DB2 plan: | Evaluate Predicates Before Locking for Key + DB2 plan: | #Key Columns = 1 + DB2 plan: | | Start Key: Inclusive Value + DB2 plan: | | | 1: 10 + DB2 plan: | | Stop Key: Inclusive Value + DB2 plan: | | | 1: 10 + DB2 plan: | Data Prefetch: None + DB2 plan: | Index Prefetch: None + DB2 plan: | Lock Intents + DB2 plan: | | Table: Intent Share + DB2 plan: | | Row : Next Key Share + DB2 plan: | Sargable Predicate(s) + DB2 plan: | | Return Data to Application + DB2 plan: | | | #Columns = 3 + DB2 plan: Return Data Completion + DB2 plan: + DB2 plan: End of section + DB2 plan: + DB2 plan: + Planning Time: 1.417 ms + Execution Time: 5.821 ms +(75 rows) + +EXECUTE act_by_no(10); + actno | actkwd | actdesc +-------+--------+--------------- + 10 | MANAGE | MANAGE/ADVISE +(1 row) + +DEALLOCATE act_by_no; +DEALLOCATE +-- +-- END of TC012 +-- -- testcases ended -- starting cleanup \c postgres diff --git a/test/sql/base.sql b/test/sql/base.sql index 78c00a4..fc0af05 100644 --- a/test/sql/base.sql +++ b/test/sql/base.sql @@ -13,21 +13,44 @@ CREATE USER MAPPING FOR PUBLIC SERVER sample OPTIONS (user 'db2inst1', password CREATE SCHEMA IF NOT EXISTS sample; -- Import the complete sample db into the local schema IMPORT FOREIGN SCHEMA "DB2INST1" FROM SERVER sample INTO sample; + +-- For UPDATE/DELETE, db2_fdw requires at least one primary key column marked +-- with the column option "key". +-- The DB2 SAMPLE.ORG table uses DEPTNUMB as its primary key. +ALTER FOREIGN TABLE sample.org + ALTER COLUMN deptnumb OPTIONS (ADD key 'true'); + -- list imported tables \detr+ sample.* -- select db2_diag(); +set log_min_messages=debug5; + -- starting testcases -- running tc001.sql -\i tc001.sql +\i ./test/sql/tc001.sql -- running tc002.sql -\i tc002.sql +\i ./test/sql/tc002.sql -- running tc003.sql -\i tc003.sql +\i ./test/sql/tc003.sql -- running tc004.sql -\i tc004.sql +\i ./test/sql/tc004.sql -- running tc005.sql -\i tc005.sql +\i ./test/sql/tc005.sql +-- running tc006.sql +\i ./test/sql/tc006.sql +-- running tc007.sql +\i ./test/sql/tc007.sql +-- running tc008.sql +\i ./test/sql/tc008.sql +-- running tc009.sql +\i ./test/sql/tc009.sql +-- running tc010.sql +\i ./test/sql/tc010.sql +-- running tc011.sql +--\i ./test/sql/tc011.sql +-- running tc012.sql +\i ./test/sql/tc012.sql -- testcases ended -- starting cleanup \c postgres diff --git a/test/sql/tc006.sql b/test/sql/tc006.sql new file mode 100644 index 0000000..2839738 --- /dev/null +++ b/test/sql/tc006.sql @@ -0,0 +1,28 @@ +-- +-- TC006: UPDATE/DELETE against a key column and revert to baseline +-- +\d+ sample.org; + +-- Baseline row +SELECT deptnumb, deptname, location FROM sample.org WHERE deptnumb = 10; + +-- UPDATE + verify +UPDATE sample.org SET location = 'New York City' WHERE deptnumb = 10; +SELECT deptnumb, deptname, location FROM sample.org WHERE deptnumb = 10; + +-- Revert + verify +UPDATE sample.org SET location = 'New York' WHERE deptnumb = 10; +SELECT deptnumb, deptname, location FROM sample.org WHERE deptnumb = 10; + +-- DELETE + INSERT to test DELETE/INSERT pair (choose a row we can reconstruct deterministically) +SELECT * FROM sample.org WHERE deptnumb = 84; +DELETE FROM sample.org WHERE deptnumb = 84; +SELECT * FROM sample.org WHERE deptnumb = 84; + +INSERT INTO sample.org (deptnumb, deptname, manager, division, location) +VALUES (84, 'Mountain', 290, 'Western', 'Denver'); +SELECT * FROM sample.org WHERE deptnumb = 84; + +-- +-- END of TC006 +-- diff --git a/test/sql/tc007.sql b/test/sql/tc007.sql new file mode 100644 index 0000000..45cc548 --- /dev/null +++ b/test/sql/tc007.sql @@ -0,0 +1,16 @@ +-- +-- TC007: Transaction / rollback semantics for foreign table modifications +-- + +SELECT deptnumb, location FROM sample.org WHERE deptnumb = 15; + +BEGIN; + UPDATE sample.org SET location = 'Boston (temp)' WHERE deptnumb = 15; + SELECT deptnumb, location FROM sample.org WHERE deptnumb = 15; +ROLLBACK; + +-- Should be back to original +SELECT deptnumb, location FROM sample.org WHERE deptnumb = 15; +-- +-- END of TC007 +-- diff --git a/test/sql/tc008.sql b/test/sql/tc008.sql new file mode 100644 index 0000000..e006d36 --- /dev/null +++ b/test/sql/tc008.sql @@ -0,0 +1,24 @@ +-- +-- TC008: Query-based foreign table +-- +DROP FOREIGN TABLE IF EXISTS sample.emp_l; + +CREATE FOREIGN TABLE sample.emp_l ( + empno char(6), + firstnme varchar(12), + lastname varchar(15) +) +SERVER sample +OPTIONS (schema 'DB2INST1',table 'EMPLOYEE', readonly 'true'); + +\d+ sample.emp_l; +SELECT * FROM sample.emp_l ORDER BY empno; + +-- Optional: show it is read-only by intent (uncomment if you want a hard failure case) +INSERT INTO sample.emp_l(empno, firstnme, lastname) VALUES ('999999', 'X', 'Y'); + +DROP FOREIGN TABLE sample.emp_l; + +-- +-- END of TC008 +-- diff --git a/test/sql/tc009.sql b/test/sql/tc009.sql new file mode 100644 index 0000000..64950d6 --- /dev/null +++ b/test/sql/tc009.sql @@ -0,0 +1,10 @@ +-- +-- TC009: Close cached DB2 connections and verify reconnect works +-- +SELECT count(*) FROM sample.employee; +SELECT db2_close_connections(); +SELECT count(*) FROM sample.employee; + +-- +-- END of TC009 +-- diff --git a/test/sql/tc010.sql b/test/sql/tc010.sql new file mode 100644 index 0000000..94da887 --- /dev/null +++ b/test/sql/tc010.sql @@ -0,0 +1,17 @@ +-- +-- TC010: Pushdown predicates (IN/BETWEEN/OR/IS NULL) +-- +EXPLAIN (analyze,verbose) SELECT empno, lastname, salary FROM sample.employee WHERE workdept IN ('A00','B01','E21') AND salary BETWEEN 40000 AND 70000 AND (lastname LIKE 'L%' OR lastname LIKE 'G%'); + +SELECT empno, lastname, salary FROM sample.employee WHERE workdept IN ('A00','B01','E21') AND salary BETWEEN 40000 AND 70000 AND (lastname LIKE 'L%' OR lastname LIKE 'G%') ORDER BY empno; + +-- NULL handling sanity check +SELECT + count(*) AS total, + count(*) FILTER (WHERE comm IS NULL) AS comm_is_null, + count(*) FILTER (WHERE comm IS NOT NULL) AS comm_is_not_null +FROM sample.employee; + +-- +-- END of TC010 +-- diff --git a/test/sql/tc011.sql b/test/sql/tc011.sql new file mode 100644 index 0000000..de23fac --- /dev/null +++ b/test/sql/tc011.sql @@ -0,0 +1,10 @@ +-- +-- TC011: ORDER BY with LIMIT/OFFSET +-- +EXPLAIN (analyze,verbose) SELECT empno, lastname, salary FROM sample.employee ORDER BY salary DESC, empno LIMIT 5 OFFSET 2; + +SELECT empno, lastname, salary FROM sample.employee ORDER BY salary DESC, empno LIMIT 5 OFFSET 2; + +-- +-- END of TC011 +-- diff --git a/test/sql/tc012.sql b/test/sql/tc012.sql new file mode 100644 index 0000000..ed4a2ee --- /dev/null +++ b/test/sql/tc012.sql @@ -0,0 +1,14 @@ +-- +-- TC012: PREPARE/EXECUTE parameter binding +-- +PREPARE act_by_no(int) AS + SELECT actno, actkwd, actdesc FROM sample.act WHERE actno = $1; + +EXPLAIN (analyze,verbose) EXECUTE act_by_no(10); +EXECUTE act_by_no(10); + +DEALLOCATE act_by_no; + +-- +-- END of TC012 +-- From e8c44f6c47068703ca77f164350028fdc42fa7ff Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Sat, 9 May 2026 16:08:46 +0200 Subject: [PATCH 113/120] fixing push down LIMIT n OFFEST m; --- source/db2ExplainForeignScan.c | 46 +++++-- source/db2GetForeignUpperPaths.c | 207 ++++++++++++++++--------------- source/db2_deparse.c | 41 +++++- test/expected/base.out | 139 ++++++++++++++++++--- test/sql/base.sql | 4 +- 5 files changed, 305 insertions(+), 132 deletions(-) diff --git a/source/db2ExplainForeignScan.c b/source/db2ExplainForeignScan.c index debc338..5f8d2e0 100644 --- a/source/db2ExplainForeignScan.c +++ b/source/db2ExplainForeignScan.c @@ -32,7 +32,7 @@ void db2ExplainForeignScan (ForeignScanState* node, ExplainState* es) { static void db2Explain (void* fdw, ExplainState* es) { FILE* fp; char path[1035]; - char execution_cmd[300]; + StringInfoData execution_cmd; DB2FdwState* fdw_state = (DB2FdwState*) fdw; int count = 0; int qlength = strlen(fdw_state->query); @@ -56,26 +56,47 @@ static void db2Explain (void* fdw, ExplainState* es) { } *dest = '\0'; - memset(execution_cmd,0x00,sizeof(execution_cmd)); + initStringInfo(&execution_cmd); if (es->verbose) { - if (strlen(fdw_state->user)){ - snprintf(execution_cmd,sizeof(execution_cmd),"db2expln -t -d %s -u %s %s -q \"%s\" ",fdw_state->dbserver,fdw_state->user,fdw_state->password,tempQuery); + if (strlen(fdw_state->user)) { + appendStringInfo(&execution_cmd, + "db2expln -t -d %s -u %s %s -q \"%s\" ", + fdw_state->dbserver, + fdw_state->user, + fdw_state->password, + tempQuery); } else { - snprintf(execution_cmd,sizeof(execution_cmd),"db2expln -t -d %s -q \"%s\" ",fdw_state->dbserver,tempQuery); + appendStringInfo(&execution_cmd, + "db2expln -t -d %s -q \"%s\" ", + fdw_state->dbserver, + tempQuery); } } else { - if (strlen(fdw_state->user)){ - snprintf(execution_cmd,sizeof(execution_cmd),"db2expln -t -d %s -u %s %s -q \"%s\" |grep -E \"Estimated Cost|Estimated Cardinality\" ",fdw_state->dbserver,fdw_state->user,fdw_state->password,tempQuery); + if (strlen(fdw_state->user)) { + appendStringInfo(&execution_cmd, + "db2expln -t -d %s -u %s %s -q \"%s\" |grep -E \"Estimated Cost|Estimated Cardinality\" ", + fdw_state->dbserver, + fdw_state->user, + fdw_state->password, + tempQuery); } else { - snprintf(execution_cmd,sizeof(execution_cmd),"db2expln -t -d %s -q \"%s\" |grep -E \"Estimated Cost|Estimated Cardinality\" ",fdw_state->dbserver,tempQuery); + appendStringInfo(&execution_cmd, + "db2expln -t -d %s -q \"%s\" |grep -E \"Estimated Cost|Estimated Cardinality\" ", + fdw_state->dbserver, + tempQuery); } } - db2Debug2("execution_cmd: '%s'",execution_cmd); + db2Debug2("execution_cmd: '%s'", execution_cmd.data); /* Open the command for reading. */ - fp = popen(execution_cmd, "r"); + fp = popen(execution_cmd.data, "r"); if (fp == NULL) { - elog (ERROR, "db2_fdw: Failed to run command"); - exit(1); + int save_errno = errno; + db2free(tempQuery,"tempQuery"); + pfree(execution_cmd.data); + ereport(ERROR, + (errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION), + errmsg("db2_fdw: failed to run db2expln"), + errdetail("popen() failed: %s", strerror(save_errno)))); } /* Read the output a line at a time - output it. */ @@ -86,5 +107,6 @@ static void db2Explain (void* fdw, ExplainState* es) { /* close */ pclose(fp); db2free(tempQuery,"tempQuery"); + pfree(execution_cmd.data); db2Exit1(); } diff --git a/source/db2GetForeignUpperPaths.c b/source/db2GetForeignUpperPaths.c index 528ca62..f397aa5 100644 --- a/source/db2GetForeignUpperPaths.c +++ b/source/db2GetForeignUpperPaths.c @@ -51,7 +51,9 @@ static void merge_fdw_options (DB2FdwState* fpinfo, const DB2Fdw void db2GetForeignUpperPaths(PlannerInfo *root, UpperRelationKind stage, RelOptInfo *input_rel, RelOptInfo *output_rel, void *extra) { db2Entry1(); if (root != NULL && root->parse != NULL && input_rel->fdw_private != NULL && output_rel->fdw_private == NULL) { - Query* query = root->parse; + Query* query = root->parse; + DB2FdwState* fpinfo = NULL; + db2Debug3("query->hasAggs : %s", query->hasAggs ? "true" : "false"); db2Debug3("query->hasWindowFuncs : %s", query->hasWindowFuncs ? "true" : "false"); db2Debug3("query->hasDistinctOn : %s", query->hasDistinctOn ? "true" : "false"); @@ -65,74 +67,84 @@ void db2GetForeignUpperPaths(PlannerInfo *root, UpperRelationKind stage, RelOptI db2Debug3("query->hasSubLinks : %s", query->hasSubLinks ? "true" : "false"); db2Debug3("query->hasRowSecurity : %s", query->hasRowSecurity ? "true" : "false"); -// if (db2_is_shippable(root, stage, input_rel, output_rel)) { - db2CloneFdwStateUpper(root, input_rel, output_rel); - - switch (stage) { - case UPPERREL_SETOP: // UNION/INTERSECT/EXCEPT - db2Debug2("stage: %d - UPPERREL_SETOP", stage); - break; - case UPPERREL_PARTIAL_GROUP_AGG: // partial grouping/aggregation - db2Debug2("stage: %d - UPPERREL_PARTIAL_GROUP_AGG", stage); - db2Debug2("query->hasAggs: %d", query->hasAggs); - db2Debug2("query->groupClause: %x", query->groupClause); - if (query->hasAggs || query->groupClause != NIL) { - add_foreign_grouping_paths(root, input_rel, output_rel, (GroupPathExtraData*) extra); - } - break; - case UPPERREL_GROUP_AGG: { // grouping/aggregation - db2Debug2("stage: %d - UPPERREL_GROUP_AGG", stage); - db2Debug2("query->hasAggs: %d", query->hasAggs); - db2Debug2("query->groupClause: %x", query->groupClause); - if (query->hasAggs || query->groupClause != NIL) { - add_foreign_grouping_paths(root, input_rel, output_rel, (GroupPathExtraData*) extra); - } + db2CloneFdwStateUpper(root, input_rel, output_rel); + + /* + * Ensure upperrel FDW state is fully initialized. + * - stage is relied upon by later upper-path stages (e.g. FINAL handling of ORDERED input). + * - outerrel must point at the immediate underlying relation for upperrels. + */ + fpinfo = (DB2FdwState*) output_rel->fdw_private; + if (fpinfo != NULL) { + fpinfo->stage = stage; + fpinfo->outerrel = input_rel; + } + switch (stage) { + case UPPERREL_SETOP: // UNION/INTERSECT/EXCEPT + db2Debug2("stage: %d - UPPERREL_SETOP", stage); + break; + case UPPERREL_PARTIAL_GROUP_AGG: // partial grouping/aggregation + db2Debug2("stage: %d - UPPERREL_PARTIAL_GROUP_AGG", stage); + db2Debug2("query->hasAggs: %d", query->hasAggs); + db2Debug2("query->groupClause: %x", query->groupClause); + if (query->hasAggs || query->groupClause != NIL) { + add_foreign_grouping_paths(root, input_rel, output_rel, (GroupPathExtraData*) extra); } - break; - case UPPERREL_WINDOW: { // window functions - db2Debug2("stage: %d - UPPERREL_WINDOW", stage); - db2Debug2("query->hasWindowFuncs: %d", query->hasWindowFuncs); - if (query->hasWindowFuncs) { - db2Debug2("window function push down not yet implemented"); - } + break; + case UPPERREL_GROUP_AGG: { // grouping/aggregation + db2Debug2("stage: %d - UPPERREL_GROUP_AGG", stage); + db2Debug2("query->hasAggs: %d", query->hasAggs); + db2Debug2("query->groupClause: %x", query->groupClause); + if (query->hasAggs || query->groupClause != NIL) { + add_foreign_grouping_paths(root, input_rel, output_rel, (GroupPathExtraData*) extra); } - break; - #if PG_VERSION_NUM >= 150000 - case UPPERREL_PARTIAL_DISTINCT: { // partial "SELECT DISTINCT" - db2Debug2("stage: %d - UPPERREL_PARTIAL_DISTINCT", stage); - db2Debug2("query->hasDistinctOn: %d", query->hasDistinctOn); - if (query->hasDistinctOn) { - db2Debug2("distinct function push down not yet implemented"); - } + } + break; + case UPPERREL_WINDOW: { // window functions + db2Debug2("stage: %d - UPPERREL_WINDOW", stage); + db2Debug2("query->hasWindowFuncs: %d", query->hasWindowFuncs); + if (query->hasWindowFuncs) { + db2Debug2("window function push down not yet implemented"); } - break; - #endif - case UPPERREL_DISTINCT: { // "SELECT DISTINCT" - db2Debug2("stage: %d - UPPERREL_DISTINCT", stage); - db2Debug2("query->hasDistinctOn: %d", query->hasDistinctOn); - if (query->hasDistinctOn) { - db2Debug2("distinct function push down not yet implemented"); - } + } + break; + #if PG_VERSION_NUM >= 150000 + case UPPERREL_PARTIAL_DISTINCT: { // partial "SELECT DISTINCT" + db2Debug2("stage: %d - UPPERREL_PARTIAL_DISTINCT", stage); + db2Debug2("query->hasDistinctOn: %d", query->hasDistinctOn); + if (query->hasDistinctOn) { + db2Debug2("distinct function push down not yet implemented"); + } + } + break; + #endif + case UPPERREL_DISTINCT: { // "SELECT DISTINCT" + db2Debug2("stage: %d - UPPERREL_DISTINCT", stage); + db2Debug2("query->hasDistinctOn: %d", query->hasDistinctOn); + if (query->hasDistinctOn) { + db2Debug2("distinct function push down not yet implemented"); } - break; - case UPPERREL_ORDERED: // ORDER BY - db2Debug2("stage: %d - UPPERREL_ORDERED", stage); - db2Debug2("query->setOperations: %x", query->setOperations); - if (query->setOperations != NULL) { - add_foreign_ordered_paths(root, input_rel, output_rel); - } - break; - case UPPERREL_FINAL: // any remaining top-level actions - db2Debug2("stage: %d - UPPERREL_FINAL", stage); - add_foreign_final_paths(root, input_rel, output_rel, (FinalPathExtraData*) extra); - break; - default: // unknown stage type - db2Debug2("stage: %d - unknown", stage); - break; } -// } else { -// db2Debug2("stage and or functions are not shippable to DB2"); -// } + break; + case UPPERREL_ORDERED: // ORDER BY + db2Debug2("stage: %d - UPPERREL_ORDERED", stage); + db2Debug2("query->setOperations: %x", query->setOperations); + /* + * ORDER BY handling: attempt pushdown when this query has a sort clause. + * (The ordered upperrel is part of the normal path even when there are no set operations.) + */ + if (query->sortClause != NIL) { + add_foreign_ordered_paths(root, input_rel, output_rel); + } + break; + case UPPERREL_FINAL: // any remaining top-level actions + db2Debug2("stage: %d - UPPERREL_FINAL", stage); + add_foreign_final_paths(root, input_rel, output_rel, (FinalPathExtraData*) extra); + break; + default: // unknown stage type + db2Debug2("stage: %d - unknown", stage); + break; + } } else { db2Debug2("skipping this call"); db2Debug2("root: %x", root); @@ -157,42 +169,41 @@ static void db2CloneFdwStateUpper(PlannerInfo* root, RelOptInfo* input_rel, RelO if (fdw_in != NULL) { copy = (DB2FdwState*) db2alloc(sizeof(DB2FdwState), "copy"); - /* Connection/session fields */ - copy->dbserver = fdw_in->dbserver ? db2strdup(fdw_in->dbserver, "copy->dbserver") : NULL; - copy->user = fdw_in->user ? db2strdup(fdw_in->user, "copy->user") : NULL; - copy->password = fdw_in->password ? db2strdup(fdw_in->password, "copy->password") : NULL; - copy->jwt_token = fdw_in->jwt_token ? db2strdup(fdw_in->jwt_token,"copy->jwt_token ") : NULL; - copy->nls_lang = fdw_in->nls_lang ? db2strdup(fdw_in->nls_lang, "copy->nls_lang") : NULL; - /* Planner-time session handle can be shared (it is not serialized). */ - copy->session = fdw_in->session; - - /* Planning/execution fields */ - copy->query = fdw_in->query ? db2strdup(fdw_in->query,copy->query) : NULL; - copy->prefetch = fdw_in->prefetch; - copy->startup_cost = fdw_in->startup_cost; - copy->total_cost = fdw_in->total_cost; - copy->rowcount = 0; - copy->temp_cxt = NULL; - - copy->order_clause = fdw_in->order_clause ? db2strdup(fdw_in->order_clause,"copy->order_clause") : NULL; - copy->where_clause = fdw_in->where_clause ? db2strdup(fdw_in->where_clause,"copy->where_clause") : NULL; + /* Start from a full struct copy so we don't leave fields uninitialized. */ + *copy = *fdw_in; - /* Shallow-copy expression lists (Expr nodes are immutable at this stage), but ensure list cells are independent because createQuery mutates the list. */ - copy->params = fdw_in->params ? list_copy(fdw_in->params) : NIL; - copy->remote_conds = fdw_in->remote_conds ? list_copy(fdw_in->remote_conds) : NIL; - copy->local_conds = fdw_in->local_conds ? list_copy(fdw_in->local_conds) : NIL; - - /* Deep-copy DB2 table/columns because DB2Column used is re-derived for each planned query shape. */ - copy->db2Table = db2CloneDb2TableForPlan(fdw_in->db2Table); + /* Deep-copy mutable/owned pointer fields */ + copy->dbserver = fdw_in->dbserver ? db2strdup(fdw_in->dbserver, "copy->dbserver") : NULL; + copy->user = fdw_in->user ? db2strdup(fdw_in->user, "copy->user") : NULL; + copy->password = fdw_in->password ? db2strdup(fdw_in->password, "copy->password") : NULL; + copy->jwt_token = fdw_in->jwt_token ? db2strdup(fdw_in->jwt_token, "copy->jwt_token") : NULL; + copy->nls_lang = fdw_in->nls_lang ? db2strdup(fdw_in->nls_lang, "copy->nls_lang") : NULL; + copy->query = fdw_in->query ? db2strdup(fdw_in->query, "copy->query") : NULL; + copy->order_clause = fdw_in->order_clause ? db2strdup(fdw_in->order_clause, "copy->order_clause") : NULL; + copy->where_clause = fdw_in->where_clause ? db2strdup(fdw_in->where_clause, "copy->where_clause") : NULL; + copy->relation_name = fdw_in->relation_name ? db2strdup(fdw_in->relation_name, "copy->relation_name") : NULL; - /* Join info: keep as-is (typically NULL for baserels). */ - copy->outerrel = fdw_in->outerrel; - copy->innerrel = fdw_in->innerrel; - copy->jointype = fdw_in->jointype; - copy->joinclauses = fdw_in->joinclauses ? list_copy(fdw_in->joinclauses) : NIL; - - /* paramList is constructed at execution time from fdw_exprs. */ - copy->paramList = NULL; + /* Shallow-copy expression lists (Expr nodes are immutable at this stage), but ensure list cells are independent because createQuery mutates the list. */ + copy->params = fdw_in->params ? list_copy(fdw_in->params) : NIL; + copy->retrieved_attr = fdw_in->retrieved_attr ? list_copy(fdw_in->retrieved_attr) : NIL; + copy->remote_conds = fdw_in->remote_conds ? list_copy(fdw_in->remote_conds) : NIL; + copy->local_conds = fdw_in->local_conds ? list_copy(fdw_in->local_conds) : NIL; + copy->final_remote_exprs = fdw_in->final_remote_exprs ? list_copy(fdw_in->final_remote_exprs) : NIL; + copy->shippable_extensions= fdw_in->shippable_extensions? list_copy(fdw_in->shippable_extensions): NIL; + copy->joinclauses = fdw_in->joinclauses ? list_copy(fdw_in->joinclauses) : NIL; + copy->grouped_tlist = fdw_in->grouped_tlist ? list_copy(fdw_in->grouped_tlist) : NIL; + + copy->attrs_used = fdw_in->attrs_used ? bms_copy(fdw_in->attrs_used) : NULL; + copy->lower_subquery_rels = fdw_in->lower_subquery_rels ? bms_copy(fdw_in->lower_subquery_rels) : NULL; + copy->hidden_subquery_rels= fdw_in->hidden_subquery_rels? bms_copy(fdw_in->hidden_subquery_rels) : NULL; + + /* Deep-copy DB2 table/columns because DB2Column.used is re-derived for each planned query shape. */ + copy->db2Table = db2CloneDb2TableForPlan(fdw_in->db2Table); + + /* Runtime-only / per-plan rebuilt fields */ + copy->rowcount = 0; + copy->temp_cxt = NULL; + copy->paramList = NULL; } output_rel->fdw_private = copy; db2Exit4(); diff --git a/source/db2_deparse.c b/source/db2_deparse.c index 60784b3..6f844a6 100644 --- a/source/db2_deparse.c +++ b/source/db2_deparse.c @@ -204,13 +204,29 @@ bool is_foreign_expr(PlannerInfo *root, RelOptInfo *baserel, Expr *expr) { bool fResult = false; db2Entry1(); + /* + * baserel->fdw_private is expected to be initialized by the FDW planning + * callbacks. If it is missing, we must not dereference it (planner-time + * crashes have been observed here for upper relations). + */ + if (fpinfo == NULL) { + db2Exit1(": %s", "false"); + return false; + } // Check that the expression consists of nodes that are safe to execute remotely. glob_cxt.root = root; glob_cxt.foreignrel = baserel; /* For an upper relation, use relids from its underneath scan relation, because the upperrel's own relids currently aren't set to anything * meaningful by the core code. For other relation, use their own relids. */ - glob_cxt.relids = (IS_UPPER_REL(baserel)) ? fpinfo->outerrel->relids : baserel->relids; + if (IS_UPPER_REL(baserel)) { + if (fpinfo->outerrel != NULL && fpinfo->outerrel->relids != NULL) + glob_cxt.relids = fpinfo->outerrel->relids; + else + glob_cxt.relids = baserel->relids; + } else { + glob_cxt.relids = baserel->relids; + } loc_cxt.collation = InvalidOid; loc_cxt.state = FDW_COLLATE_NONE; if (foreign_expr_walker((Node*) expr, &glob_cxt, &loc_cxt, NULL)) { @@ -988,13 +1004,28 @@ static void appendLimitClause(deparse_expr_cxt* context) { /* Make sure any constants in the exprs are printed portably */ nestlevel = set_transmission_modes(); - if (root->parse->limitCount) { - appendStringInfoString(buf, " LIMIT "); - deparseExprInt((Expr*) root->parse->limitCount, context); - } + /* + * DB2 does not support the Postgres syntax "LIMIT OFFSET ". + * Use DB2 pagination syntax instead: + * - "FETCH FIRST ROWS ONLY" (limit) + * - "OFFSET ROWS" (offset) + * - "OFFSET ROWS FETCH NEXT ROWS ONLY" (limit+offset) + */ + if (root->parse->limitOffset) { appendStringInfoString(buf, " OFFSET "); deparseExprInt((Expr*) root->parse->limitOffset, context); + appendStringInfoString(buf, " ROWS"); + } + + if (root->parse->limitCount) { + if (root->parse->limitOffset) + appendStringInfoString(buf, " FETCH NEXT "); + else + appendStringInfoString(buf, " FETCH FIRST "); + + deparseExprInt((Expr*) root->parse->limitCount, context); + appendStringInfoString(buf, " ROWS ONLY"); } reset_transmission_modes(nestlevel); db2Debug5(" clause: %s", context->buf->data); diff --git a/test/expected/base.out b/test/expected/base.out index 1b5ca7f..00cc782 100644 --- a/test/expected/base.out +++ b/test/expected/base.out @@ -442,7 +442,7 @@ FDW options: (schema 'DB2INST1', "table" 'EMPLOYEE') explain (analyze,verbose) select min(salary),max(salary),avg(salary),sum(salary +comm + bonus),count(*) from sample.employee; QUERY PLAN -------------------------------------------------------------------------------------------------------------------------------------------- - Foreign Scan (cost=121.72..144.35 rows=1 width=136) (actual time=3.864..4.106 rows=1.00 loops=1) + Foreign Scan (cost=121.72..144.35 rows=1 width=136) (actual time=3.630..3.900 rows=1.00 loops=1) Output: (min(salary)), (max(salary)), (avg(salary)), (sum(((salary + comm) + bonus))), (count(*)) DB2 query: SELECT MIN("SALARY"), MAX("SALARY"), AVG("SALARY"), SUM((("SALARY" + "COMM") + "BONUS")), COUNT(*) FROM "DB2INST1"."EMPLOYEE" DB2 plan: @@ -511,8 +511,8 @@ explain (analyze,verbose) select min(salary),max(salary),avg(salary),sum(salary DB2 plan: Planning: Buffers: shared hit=20 - Planning Time: 3.501 ms - Execution Time: 5.306 ms + Planning Time: 3.398 ms + Execution Time: 5.036 ms (71 rows) select min(salary),max(salary),avg(salary),sum(salary +comm + bonus),count(*) from sample.employee; @@ -525,7 +525,7 @@ select min(salary),max(salary),avg(salary),sum(salary +comm + bonus),count(*) fr explain (analyze,verbose) select empno, firstnme,lastname, salary + bonus + comm from sample.employee where salary > 8000 and lastname like 'L%'; QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - Foreign Scan on sample.employee (cost=100.00..116.89 rows=1 width=150) (actual time=3.881..4.902 rows=3.00 loops=1) + Foreign Scan on sample.employee (cost=100.00..116.89 rows=1 width=150) (actual time=3.987..4.655 rows=3.00 loops=1) Output: empno, firstnme, lastname, ((salary + bonus) + comm) DB2 query: SELECT "EMPNO", "FIRSTNME", "LASTNAME", "SALARY", "BONUS", "COMM" FROM "DB2INST1"."EMPLOYEE" WHERE (("SALARY" > 8000)) AND (("LASTNAME" LIKE 'L%' ESCAPE '\')) DB2 plan: @@ -593,8 +593,8 @@ explain (analyze,verbose) select empno, firstnme,lastname, salary + bonus + comm DB2 plan: Planning: Buffers: shared hit=3 - Planning Time: 3.898 ms - Execution Time: 6.151 ms + Planning Time: 4.045 ms + Execution Time: 5.857 ms (70 rows) select empno, firstnme,lastname, salary + bonus + comm from sample.employee where salary > 8000 and lastname like 'L%'; @@ -636,7 +636,7 @@ SELECT deptnumb, deptname, location FROM sample.org WHERE deptnumb = 10; -- UPDATE + verify UPDATE sample.org SET location = 'New York City' WHERE deptnumb = 10; -psql:test/sql/tc006.sql:10: FEHLER: pfree called with invalid pointer 0x425e530 (header 0x0000000000000002) +psql:test/sql/tc006.sql:10: FEHLER: pfree called with invalid pointer 0xc542eb0 (header 0x0000000000000002) SELECT deptnumb, deptname, location FROM sample.org WHERE deptnumb = 10; deptnumb | deptname | location ----------+-------------+---------- @@ -645,7 +645,7 @@ SELECT deptnumb, deptname, location FROM sample.org WHERE deptnumb = 10; -- Revert + verify UPDATE sample.org SET location = 'New York' WHERE deptnumb = 10; -psql:test/sql/tc006.sql:14: FEHLER: pfree called with invalid pointer 0x425e530 (header 0x0000000000000002) +psql:test/sql/tc006.sql:14: FEHLER: pfree called with invalid pointer 0xc542eb0 (header 0x0000000000000002) SELECT deptnumb, deptname, location FROM sample.org WHERE deptnumb = 10; deptnumb | deptname | location ----------+-------------+---------- @@ -660,7 +660,7 @@ SELECT * FROM sample.org WHERE deptnumb = 84; (1 row) DELETE FROM sample.org WHERE deptnumb = 84; -psql:test/sql/tc006.sql:19: FEHLER: pfree called with invalid pointer 0x425e530 (header 0x000000000425e4f8) +psql:test/sql/tc006.sql:19: FEHLER: pfree called with invalid pointer 0xc542eb0 (header 0x000000000c542e78) SELECT * FROM sample.org WHERE deptnumb = 84; deptnumb | deptname | manager | division | location ----------+----------+---------+----------+---------- @@ -694,7 +694,7 @@ SELECT deptnumb, location FROM sample.org WHERE deptnumb = 15; BEGIN; BEGIN UPDATE sample.org SET location = 'Boston (temp)' WHERE deptnumb = 15; -psql:test/sql/tc007.sql:8: FEHLER: pfree called with invalid pointer 0x425e530 (header 0x0000000000000002) +psql:test/sql/tc007.sql:8: FEHLER: pfree called with invalid pointer 0xc542eb0 (header 0x0000000000000002) SELECT deptnumb, location FROM sample.org WHERE deptnumb = 15; psql:test/sql/tc007.sql:9: FEHLER: aktuelle Transaktion wurde abgebrochen, Befehle werden bis zum Ende der Transaktion ignoriert ROLLBACK; @@ -840,8 +840,117 @@ FROM sample.employee; -- -- END of TC010 -- --- running tc011.sql ---\i ./test/sql/tc011.sql +--running tc011.sql +\i ./test/sql/tc011.sql +-- +-- TC011: ORDER BY with LIMIT/OFFSET +-- +EXPLAIN (analyze,verbose) SELECT empno, lastname, salary FROM sample.employee ORDER BY salary DESC, empno LIMIT 5 OFFSET 2; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + Foreign Scan on sample.employee (cost=100.06..101.19 rows=5 width=90) (actual time=4.394..5.143 rows=5.00 loops=1) + Output: empno, lastname, salary + DB2 query: SELECT "EMPNO", "LASTNAME", "SALARY" FROM "DB2INST1"."EMPLOYEE" ORDER BY "SALARY" DESC NULLS FIRST, "EMPNO" ASC NULLS LAST OFFSET 2 ROWS FETCH NEXT 5 ROWS ONLY + DB2 plan: + DB2 plan: DB2 Universal Database Version 11.5, 5622-044 (c) Copyright IBM Corp. 1991, 2019 + DB2 plan: Licensed Material - Program Property of IBM + DB2 plan: IBM DB2 Universal Database SQL and XQUERY Explain Tool + DB2 plan: + DB2 plan: DB2 Universal Database Version 12.1, 5622-044 (c) Copyright IBM Corp. 1991, 2022 + DB2 plan: Licensed Material - Program Property of IBM + DB2 plan: IBM DB2 Universal Database SQL and XQUERY Explain Tool + DB2 plan: + DB2 plan: ******************** DYNAMIC *************************************** + DB2 plan: + DB2 plan: ==================== STATEMENT ========================================== + DB2 plan: + DB2 plan: Isolation Level = Cursor Stability + DB2 plan: Blocking = Block Unambiguous Cursors + DB2 plan: Query Optimization Class = 5 + DB2 plan: + DB2 plan: Partition Parallel = No + DB2 plan: Intra-Partition Parallel = No + DB2 plan: + DB2 plan: SQL Path = "SYSIBM", "SYSFUN", "SYSPROC", "SYSIBMADM", + DB2 plan: "SYSHADOOP", "DB2INST1" + DB2 plan: + DB2 plan: + DB2 plan: Statement: + DB2 plan: + DB2 plan: SELECT "EMPNO" , "LASTNAME" , "SALARY" + DB2 plan: FROM "DB2INST1" ."EMPLOYEE" + DB2 plan: ORDER BY "SALARY" DESC NULLS FIRST, "EMPNO" ASC NULLS LAST OFFSET 2 + DB2 plan: ROWS + DB2 plan: FETCH NEXT 5 ROWS ONLY + DB2 plan: + DB2 plan: + DB2 plan: compiledInTenantID = 0 + DB2 plan: + DB2 plan: Section Code Page = 1208 + DB2 plan: + DB2 plan: Estimated Cost = 6,816353 + DB2 plan: Estimated Cardinality = 2,333333 + DB2 plan: + DB2 plan: Access Table Name = DB2INST1.EMPLOYEE ID = 2,6 + DB2 plan: | tenantID = 0 + DB2 plan: | #Columns = 3 + DB2 plan: | Skip Inserted Rows + DB2 plan: | Avoid Locking Committed Data + DB2 plan: | Currently Committed for Cursor Stability + DB2 plan: | May participate in Scan Sharing structures + DB2 plan: | Scan may start anywhere and wrap, for completion + DB2 plan: | Fast scan, for purposes of scan sharing management + DB2 plan: | Scan can be throttled in scan sharing management + DB2 plan: | Relation Scan + DB2 plan: | | Prefetch: Eligible + DB2 plan: | Lock Intents + DB2 plan: | | Table: Intent Share + DB2 plan: | | Row : Next Key Share + DB2 plan: | Sargable Predicate(s) + DB2 plan: | | Insert Into Sorted Temp Table ID = t1 + DB2 plan: | | | tenantID = 0 + DB2 plan: | | | #Columns = 3 + DB2 plan: | | | #Sort Key Columns = 2 + DB2 plan: | | | | Key 1: SALARY (Descending) + DB2 plan: | | | | Key 2: EMPNO (Ascending) + DB2 plan: | | | Sortheap Allocation Parameters: + DB2 plan: | | | | #Rows = 7,000000 + DB2 plan: | | | | Row Width = 28 + DB2 plan: | | | | Sort Limited To Estimated Row Count + DB2 plan: | | | Piped + DB2 plan: Sorted Temp Table Completion ID = t1 + DB2 plan: Access Temp Table ID = t1 + DB2 plan: | tenantID = 0 + DB2 plan: | #Columns = 3 + DB2 plan: | Relation Scan + DB2 plan: | | Prefetch: Eligible + DB2 plan: Residual Predicate(s) + DB2 plan: | #Predicates = 1 + DB2 plan: Return Data to Application + DB2 plan: | #Columns = 3 + DB2 plan: + DB2 plan: End of section + DB2 plan: + DB2 plan: + Planning: + Buffers: shared hit=12 + Planning Time: 4.867 ms + Execution Time: 6.184 ms +(88 rows) + +SELECT empno, lastname, salary FROM sample.employee ORDER BY salary DESC, empno LIMIT 5 OFFSET 2; + empno | lastname | salary +--------+-----------+---------- + 000070 | PULASKI | 96170.00 + 000020 | THOMPSON | 94250.00 + 000090 | HENDERSON | 89750.00 + 000100 | SPENSER | 86150.00 + 000050 | GEYER | 80175.00 +(5 rows) + +-- +-- END of TC011 +-- -- running tc012.sql \i ./test/sql/tc012.sql -- @@ -853,7 +962,7 @@ PREPARE EXPLAIN (analyze,verbose) EXECUTE act_by_no(10); QUERY PLAN ---------------------------------------------------------------------------------------------------------------- - Foreign Scan on sample.act (cost=100.00..119.98 rows=4 width=88) (actual time=5.048..5.343 rows=1.00 loops=1) + Foreign Scan on sample.act (cost=100.00..119.98 rows=4 width=88) (actual time=4.685..4.991 rows=1.00 loops=1) Output: actno, actkwd, actdesc DB2 query: SELECT "ACTNO", "ACTKWD", "ACTDESC" FROM "DB2INST1"."ACT" WHERE (("ACTNO" = 10)) DB2 plan: @@ -926,8 +1035,8 @@ EXPLAIN (analyze,verbose) EXECUTE act_by_no(10); DB2 plan: End of section DB2 plan: DB2 plan: - Planning Time: 1.417 ms - Execution Time: 5.821 ms + Planning Time: 1.453 ms + Execution Time: 5.446 ms (75 rows) EXECUTE act_by_no(10); diff --git a/test/sql/base.sql b/test/sql/base.sql index fc0af05..3998248 100644 --- a/test/sql/base.sql +++ b/test/sql/base.sql @@ -47,8 +47,8 @@ set log_min_messages=debug5; \i ./test/sql/tc009.sql -- running tc010.sql \i ./test/sql/tc010.sql --- running tc011.sql ---\i ./test/sql/tc011.sql +--running tc011.sql +\i ./test/sql/tc011.sql -- running tc012.sql \i ./test/sql/tc012.sql -- testcases ended From d02c0f5a0d1fcaf3ff8c2e9bc2b369b604e2e07a Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Sat, 9 May 2026 16:16:10 +0200 Subject: [PATCH 114/120] extending TC011 --- test/expected/base.out | 192 +++++++++++++++++++++++++++++++++++++---- test/sql/tc011.sql | 8 +- 2 files changed, 182 insertions(+), 18 deletions(-) diff --git a/test/expected/base.out b/test/expected/base.out index 00cc782..485d23e 100644 --- a/test/expected/base.out +++ b/test/expected/base.out @@ -442,7 +442,7 @@ FDW options: (schema 'DB2INST1', "table" 'EMPLOYEE') explain (analyze,verbose) select min(salary),max(salary),avg(salary),sum(salary +comm + bonus),count(*) from sample.employee; QUERY PLAN -------------------------------------------------------------------------------------------------------------------------------------------- - Foreign Scan (cost=121.72..144.35 rows=1 width=136) (actual time=3.630..3.900 rows=1.00 loops=1) + Foreign Scan (cost=121.72..144.35 rows=1 width=136) (actual time=4.902..5.289 rows=1.00 loops=1) Output: (min(salary)), (max(salary)), (avg(salary)), (sum(((salary + comm) + bonus))), (count(*)) DB2 query: SELECT MIN("SALARY"), MAX("SALARY"), AVG("SALARY"), SUM((("SALARY" + "COMM") + "BONUS")), COUNT(*) FROM "DB2INST1"."EMPLOYEE" DB2 plan: @@ -511,8 +511,8 @@ explain (analyze,verbose) select min(salary),max(salary),avg(salary),sum(salary DB2 plan: Planning: Buffers: shared hit=20 - Planning Time: 3.398 ms - Execution Time: 5.036 ms + Planning Time: 10.731 ms + Execution Time: 8.132 ms (71 rows) select min(salary),max(salary),avg(salary),sum(salary +comm + bonus),count(*) from sample.employee; @@ -525,7 +525,7 @@ select min(salary),max(salary),avg(salary),sum(salary +comm + bonus),count(*) fr explain (analyze,verbose) select empno, firstnme,lastname, salary + bonus + comm from sample.employee where salary > 8000 and lastname like 'L%'; QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - Foreign Scan on sample.employee (cost=100.00..116.89 rows=1 width=150) (actual time=3.987..4.655 rows=3.00 loops=1) + Foreign Scan on sample.employee (cost=100.00..116.89 rows=1 width=150) (actual time=3.637..4.420 rows=3.00 loops=1) Output: empno, firstnme, lastname, ((salary + bonus) + comm) DB2 query: SELECT "EMPNO", "FIRSTNME", "LASTNAME", "SALARY", "BONUS", "COMM" FROM "DB2INST1"."EMPLOYEE" WHERE (("SALARY" > 8000)) AND (("LASTNAME" LIKE 'L%' ESCAPE '\')) DB2 plan: @@ -593,8 +593,8 @@ explain (analyze,verbose) select empno, firstnme,lastname, salary + bonus + comm DB2 plan: Planning: Buffers: shared hit=3 - Planning Time: 4.045 ms - Execution Time: 5.857 ms + Planning Time: 3.369 ms + Execution Time: 5.638 ms (70 rows) select empno, firstnme,lastname, salary + bonus + comm from sample.employee where salary > 8000 and lastname like 'L%'; @@ -636,7 +636,7 @@ SELECT deptnumb, deptname, location FROM sample.org WHERE deptnumb = 10; -- UPDATE + verify UPDATE sample.org SET location = 'New York City' WHERE deptnumb = 10; -psql:test/sql/tc006.sql:10: FEHLER: pfree called with invalid pointer 0xc542eb0 (header 0x0000000000000002) +psql:test/sql/tc006.sql:10: FEHLER: pfree called with invalid pointer 0xc546050 (header 0x0000000000000002) SELECT deptnumb, deptname, location FROM sample.org WHERE deptnumb = 10; deptnumb | deptname | location ----------+-------------+---------- @@ -645,7 +645,7 @@ SELECT deptnumb, deptname, location FROM sample.org WHERE deptnumb = 10; -- Revert + verify UPDATE sample.org SET location = 'New York' WHERE deptnumb = 10; -psql:test/sql/tc006.sql:14: FEHLER: pfree called with invalid pointer 0xc542eb0 (header 0x0000000000000002) +psql:test/sql/tc006.sql:14: FEHLER: pfree called with invalid pointer 0xc546050 (header 0x0000000000000002) SELECT deptnumb, deptname, location FROM sample.org WHERE deptnumb = 10; deptnumb | deptname | location ----------+-------------+---------- @@ -660,7 +660,7 @@ SELECT * FROM sample.org WHERE deptnumb = 84; (1 row) DELETE FROM sample.org WHERE deptnumb = 84; -psql:test/sql/tc006.sql:19: FEHLER: pfree called with invalid pointer 0xc542eb0 (header 0x000000000c542e78) +psql:test/sql/tc006.sql:19: FEHLER: pfree called with invalid pointer 0xc546050 (header 0x000000000c546018) SELECT * FROM sample.org WHERE deptnumb = 84; deptnumb | deptname | manager | division | location ----------+----------+---------+----------+---------- @@ -694,7 +694,7 @@ SELECT deptnumb, location FROM sample.org WHERE deptnumb = 15; BEGIN; BEGIN UPDATE sample.org SET location = 'Boston (temp)' WHERE deptnumb = 15; -psql:test/sql/tc007.sql:8: FEHLER: pfree called with invalid pointer 0xc542eb0 (header 0x0000000000000002) +psql:test/sql/tc007.sql:8: FEHLER: pfree called with invalid pointer 0xc546050 (header 0x0000000000000002) SELECT deptnumb, location FROM sample.org WHERE deptnumb = 15; psql:test/sql/tc007.sql:9: FEHLER: aktuelle Transaktion wurde abgebrochen, Befehle werden bis zum Ende der Transaktion ignoriert ROLLBACK; @@ -845,10 +845,170 @@ FROM sample.employee; -- -- TC011: ORDER BY with LIMIT/OFFSET -- +EXPLAIN (analyze,verbose) SELECT empno, lastname, salary FROM sample.employee LIMIT 5; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------- + Foreign Scan on sample.employee (cost=100.00..101.11 rows=5 width=90) (actual time=3.069..3.794 rows=5.00 loops=1) + Output: empno, lastname, salary + DB2 query: SELECT "EMPNO", "LASTNAME", "SALARY" FROM "DB2INST1"."EMPLOYEE" FETCH FIRST 5 ROWS ONLY + DB2 plan: + DB2 plan: DB2 Universal Database Version 11.5, 5622-044 (c) Copyright IBM Corp. 1991, 2019 + DB2 plan: Licensed Material - Program Property of IBM + DB2 plan: IBM DB2 Universal Database SQL and XQUERY Explain Tool + DB2 plan: + DB2 plan: DB2 Universal Database Version 12.1, 5622-044 (c) Copyright IBM Corp. 1991, 2022 + DB2 plan: Licensed Material - Program Property of IBM + DB2 plan: IBM DB2 Universal Database SQL and XQUERY Explain Tool + DB2 plan: + DB2 plan: ******************** DYNAMIC *************************************** + DB2 plan: + DB2 plan: ==================== STATEMENT ========================================== + DB2 plan: + DB2 plan: Isolation Level = Cursor Stability + DB2 plan: Blocking = Block Unambiguous Cursors + DB2 plan: Query Optimization Class = 5 + DB2 plan: + DB2 plan: Partition Parallel = No + DB2 plan: Intra-Partition Parallel = No + DB2 plan: + DB2 plan: SQL Path = "SYSIBM", "SYSFUN", "SYSPROC", "SYSIBMADM", + DB2 plan: "SYSHADOOP", "DB2INST1" + DB2 plan: + DB2 plan: + DB2 plan: Statement: + DB2 plan: + DB2 plan: SELECT "EMPNO" , "LASTNAME" , "SALARY" + DB2 plan: FROM "DB2INST1" ."EMPLOYEE" + DB2 plan: FETCH FIRST 5 ROWS ONLY + DB2 plan: + DB2 plan: + DB2 plan: compiledInTenantID = 0 + DB2 plan: + DB2 plan: Section Code Page = 1208 + DB2 plan: + DB2 plan: Estimated Cost = 6,809195 + DB2 plan: Estimated Cardinality = 5,000000 + DB2 plan: + DB2 plan: Access Table Name = DB2INST1.EMPLOYEE ID = 2,6 + DB2 plan: | tenantID = 0 + DB2 plan: | #Columns = 3 + DB2 plan: | Skip Inserted Rows + DB2 plan: | Avoid Locking Committed Data + DB2 plan: | Currently Committed for Cursor Stability + DB2 plan: | May participate in Scan Sharing structures + DB2 plan: | Scan may start anywhere and wrap, for completion + DB2 plan: | Fast scan, for purposes of scan sharing management + DB2 plan: | Scan can be throttled in scan sharing management + DB2 plan: | Relation Scan + DB2 plan: | | Prefetch: Eligible + DB2 plan: | Lock Intents + DB2 plan: | | Table: Intent Share + DB2 plan: | | Row : Next Key Share + DB2 plan: Return Data to Application + DB2 plan: | #Columns = 3 + DB2 plan: + DB2 plan: End of section + DB2 plan: + DB2 plan: + Planning Time: 2.437 ms + Execution Time: 4.851 ms +(64 rows) + +SELECT empno, lastname, salary FROM sample.employee LIMIT 5; + empno | lastname | salary +--------+----------+----------- + 000010 | HAAS | 152750.00 + 000020 | THOMPSON | 94250.00 + 000030 | KWAN | 98250.00 + 000050 | GEYER | 80175.00 + 000060 | STERN | 72250.00 +(5 rows) + +EXPLAIN (analyze,verbose) SELECT empno, lastname, salary FROM sample.employee LIMIT 5 OFFSET 2; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------- + Foreign Scan on sample.employee (cost=100.05..101.16 rows=5 width=90) (actual time=3.593..4.494 rows=5.00 loops=1) + Output: empno, lastname, salary + DB2 query: SELECT "EMPNO", "LASTNAME", "SALARY" FROM "DB2INST1"."EMPLOYEE" OFFSET 2 ROWS FETCH NEXT 5 ROWS ONLY + DB2 plan: + DB2 plan: DB2 Universal Database Version 11.5, 5622-044 (c) Copyright IBM Corp. 1991, 2019 + DB2 plan: Licensed Material - Program Property of IBM + DB2 plan: IBM DB2 Universal Database SQL and XQUERY Explain Tool + DB2 plan: + DB2 plan: DB2 Universal Database Version 12.1, 5622-044 (c) Copyright IBM Corp. 1991, 2022 + DB2 plan: Licensed Material - Program Property of IBM + DB2 plan: IBM DB2 Universal Database SQL and XQUERY Explain Tool + DB2 plan: + DB2 plan: ******************** DYNAMIC *************************************** + DB2 plan: + DB2 plan: ==================== STATEMENT ========================================== + DB2 plan: + DB2 plan: Isolation Level = Cursor Stability + DB2 plan: Blocking = Block Unambiguous Cursors + DB2 plan: Query Optimization Class = 5 + DB2 plan: + DB2 plan: Partition Parallel = No + DB2 plan: Intra-Partition Parallel = No + DB2 plan: + DB2 plan: SQL Path = "SYSIBM", "SYSFUN", "SYSPROC", "SYSIBMADM", + DB2 plan: "SYSHADOOP", "DB2INST1" + DB2 plan: + DB2 plan: + DB2 plan: Statement: + DB2 plan: + DB2 plan: SELECT "EMPNO" , "LASTNAME" , "SALARY" + DB2 plan: FROM "DB2INST1" ."EMPLOYEE" OFFSET 2 ROWS + DB2 plan: FETCH NEXT 5 ROWS ONLY + DB2 plan: + DB2 plan: + DB2 plan: compiledInTenantID = 0 + DB2 plan: + DB2 plan: Section Code Page = 1208 + DB2 plan: + DB2 plan: Estimated Cost = 6,810677 + DB2 plan: Estimated Cardinality = 2,333333 + DB2 plan: + DB2 plan: Access Table Name = DB2INST1.EMPLOYEE ID = 2,6 + DB2 plan: | tenantID = 0 + DB2 plan: | #Columns = 3 + DB2 plan: | Skip Inserted Rows + DB2 plan: | Avoid Locking Committed Data + DB2 plan: | Currently Committed for Cursor Stability + DB2 plan: | May participate in Scan Sharing structures + DB2 plan: | Scan may start anywhere and wrap, for completion + DB2 plan: | Fast scan, for purposes of scan sharing management + DB2 plan: | Scan can be throttled in scan sharing management + DB2 plan: | Relation Scan + DB2 plan: | | Prefetch: Eligible + DB2 plan: | Lock Intents + DB2 plan: | | Table: Intent Share + DB2 plan: | | Row : Next Key Share + DB2 plan: Residual Predicate(s) + DB2 plan: | #Predicates = 1 + DB2 plan: Return Data to Application + DB2 plan: | #Columns = 3 + DB2 plan: + DB2 plan: End of section + DB2 plan: + DB2 plan: + Planning Time: 2.557 ms + Execution Time: 5.589 ms +(66 rows) + +SELECT empno, lastname, salary FROM sample.employee LIMIT 5 OFFSET 2; + empno | lastname | salary +--------+-----------+---------- + 000030 | KWAN | 98250.00 + 000050 | GEYER | 80175.00 + 000060 | STERN | 72250.00 + 000070 | PULASKI | 96170.00 + 000090 | HENDERSON | 89750.00 +(5 rows) + EXPLAIN (analyze,verbose) SELECT empno, lastname, salary FROM sample.employee ORDER BY salary DESC, empno LIMIT 5 OFFSET 2; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - Foreign Scan on sample.employee (cost=100.06..101.19 rows=5 width=90) (actual time=4.394..5.143 rows=5.00 loops=1) + Foreign Scan on sample.employee (cost=100.06..101.19 rows=5 width=90) (actual time=5.042..6.655 rows=5.00 loops=1) Output: empno, lastname, salary DB2 query: SELECT "EMPNO", "LASTNAME", "SALARY" FROM "DB2INST1"."EMPLOYEE" ORDER BY "SALARY" DESC NULLS FIRST, "EMPNO" ASC NULLS LAST OFFSET 2 ROWS FETCH NEXT 5 ROWS ONLY DB2 plan: @@ -934,8 +1094,8 @@ EXPLAIN (analyze,verbose) SELECT empno, lastname, salary FROM sample.employee OR DB2 plan: Planning: Buffers: shared hit=12 - Planning Time: 4.867 ms - Execution Time: 6.184 ms + Planning Time: 4.396 ms + Execution Time: 7.734 ms (88 rows) SELECT empno, lastname, salary FROM sample.employee ORDER BY salary DESC, empno LIMIT 5 OFFSET 2; @@ -962,7 +1122,7 @@ PREPARE EXPLAIN (analyze,verbose) EXECUTE act_by_no(10); QUERY PLAN ---------------------------------------------------------------------------------------------------------------- - Foreign Scan on sample.act (cost=100.00..119.98 rows=4 width=88) (actual time=4.685..4.991 rows=1.00 loops=1) + Foreign Scan on sample.act (cost=100.00..119.98 rows=4 width=88) (actual time=5.000..5.322 rows=1.00 loops=1) Output: actno, actkwd, actdesc DB2 query: SELECT "ACTNO", "ACTKWD", "ACTDESC" FROM "DB2INST1"."ACT" WHERE (("ACTNO" = 10)) DB2 plan: @@ -1035,8 +1195,8 @@ EXPLAIN (analyze,verbose) EXECUTE act_by_no(10); DB2 plan: End of section DB2 plan: DB2 plan: - Planning Time: 1.453 ms - Execution Time: 5.446 ms + Planning Time: 2.139 ms + Execution Time: 5.820 ms (75 rows) EXECUTE act_by_no(10); diff --git a/test/sql/tc011.sql b/test/sql/tc011.sql index de23fac..f660d76 100644 --- a/test/sql/tc011.sql +++ b/test/sql/tc011.sql @@ -1,10 +1,14 @@ -- -- TC011: ORDER BY with LIMIT/OFFSET -- -EXPLAIN (analyze,verbose) SELECT empno, lastname, salary FROM sample.employee ORDER BY salary DESC, empno LIMIT 5 OFFSET 2; +EXPLAIN (analyze,verbose) SELECT empno, lastname, salary FROM sample.employee LIMIT 5; +SELECT empno, lastname, salary FROM sample.employee LIMIT 5; -SELECT empno, lastname, salary FROM sample.employee ORDER BY salary DESC, empno LIMIT 5 OFFSET 2; +EXPLAIN (analyze,verbose) SELECT empno, lastname, salary FROM sample.employee LIMIT 5 OFFSET 2; +SELECT empno, lastname, salary FROM sample.employee LIMIT 5 OFFSET 2; +EXPLAIN (analyze,verbose) SELECT empno, lastname, salary FROM sample.employee ORDER BY salary DESC, empno LIMIT 5 OFFSET 2; +SELECT empno, lastname, salary FROM sample.employee ORDER BY salary DESC, empno LIMIT 5 OFFSET 2; -- -- END of TC011 -- From 0940ff9551cf0e48c6469e9c50890dd311c1d4b0 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Sun, 10 May 2026 14:34:33 +0200 Subject: [PATCH 115/120] enhanced testcases --- test/sql/base.sql | 32 +++++++------------------------- test/sql/tc003.sql | 1 + test/sql/tc004.sql | 1 + test/sql/tc005.sql | 4 ++-- test/sql/tc006.sql | 6 ++++++ test/sql/tc013.sql | 9 +++++++++ test/sql/tccdb.sql | 8 ++++++++ test/sql/tcend.sql | 2 ++ test/sql/tcfdw.sql | 8 ++++++++ test/sql/tcstart.sql | 7 +++++++ 10 files changed, 51 insertions(+), 27 deletions(-) create mode 100644 test/sql/tc013.sql create mode 100644 test/sql/tccdb.sql create mode 100644 test/sql/tcend.sql create mode 100644 test/sql/tcfdw.sql create mode 100644 test/sql/tcstart.sql diff --git a/test/sql/base.sql b/test/sql/base.sql index 3998248..3b1ab7b 100644 --- a/test/sql/base.sql +++ b/test/sql/base.sql @@ -1,29 +1,10 @@ \set ECHO all -CREATE DATABASE regtest; -GRANT ALL PRIVILEGES ON DATABASE regtest to postgres; -\c regtest --- Install extension -CREATE EXTENSION IF NOT EXISTS db2_fdw; --- Install FDW Server -CREATE SERVER IF NOT EXISTS sample FOREIGN DATA WRAPPER db2_fdw OPTIONS (dbserver 'SAMPLE'); --- Map a user -CREATE USER MAPPING FOR PUBLIC SERVER sample OPTIONS (user 'db2inst1', password 'db2inst1'); --- CREATE USER MAPPING FOR PUBLIC SERVER sample OPTIONS (user '', password ''); --- Prepare a local schema -CREATE SCHEMA IF NOT EXISTS sample; --- Import the complete sample db into the local schema -IMPORT FOREIGN SCHEMA "DB2INST1" FROM SERVER sample INTO sample; +\pset null '[NULL]' --- For UPDATE/DELETE, db2_fdw requires at least one primary key column marked --- with the column option "key". --- The DB2 SAMPLE.ORG table uses DEPTNUMB as its primary key. -ALTER FOREIGN TABLE sample.org - ALTER COLUMN deptnumb OPTIONS (ADD key 'true'); +\i ./test/sql/tccdb.sql +\i ./test/sql/tcfdw.sql +\i ./test/sql/tcstart.sql --- list imported tables -\detr+ sample.* --- -select db2_diag(); set log_min_messages=debug5; -- starting testcases @@ -51,8 +32,9 @@ set log_min_messages=debug5; \i ./test/sql/tc011.sql -- running tc012.sql \i ./test/sql/tc012.sql +-- running tc013.sql +\i ./test/sql/tc013.sql -- testcases ended -- starting cleanup -\c postgres -DROP DATABASE regtest; +\i ./test/sql/tcend.sql -- test finished \ No newline at end of file diff --git a/test/sql/tc003.sql b/test/sql/tc003.sql index 3df150e..cb2594a 100644 --- a/test/sql/tc003.sql +++ b/test/sql/tc003.sql @@ -4,6 +4,7 @@ \d+ sample.employee; \d+ sample.sales; -- test a simple join +explain (analyze,verbose) select * from sample.employee a, sample.sales b where a.lastname = b.sales_person; select * from sample.employee a, sample.sales b where a.lastname = b.sales_person; -- -- End of TC003 diff --git a/test/sql/tc004.sql b/test/sql/tc004.sql index 5eee9c9..96597f6 100644 --- a/test/sql/tc004.sql +++ b/test/sql/tc004.sql @@ -3,6 +3,7 @@ -- create table sample.orgcopy as select * from sample.org; \d+ sample.org* +select * from sample.orgcopy; drop table sample.orgcopy; -- -- END of TC004 diff --git a/test/sql/tc005.sql b/test/sql/tc005.sql index 9d2281e..3e9d9fc 100644 --- a/test/sql/tc005.sql +++ b/test/sql/tc005.sql @@ -5,8 +5,8 @@ explain (analyze,verbose) select min(salary),max(salary),avg(salary),sum(salary +comm + bonus),count(*) from sample.employee; select min(salary),max(salary),avg(salary),sum(salary +comm + bonus),count(*) from sample.employee; -- -explain (analyze,verbose) select empno, firstnme,lastname, salary + bonus + comm from sample.employee where salary > 8000 and lastname like 'L%'; -select empno, firstnme,lastname, salary + bonus + comm from sample.employee where salary > 8000 and lastname like 'L%'; +explain (analyze,verbose) select empno, firstnme,lastname, salary + bonus + comm from sample.employee where salary > 43840.01 and lastname like 'L%'; +select empno, firstnme,lastname, salary + bonus + comm from sample.employee where salary > 43840.01 and lastname like 'L%'; -- -- END of TC004 -- \ No newline at end of file diff --git a/test/sql/tc006.sql b/test/sql/tc006.sql index 2839738..da24597 100644 --- a/test/sql/tc006.sql +++ b/test/sql/tc006.sql @@ -1,6 +1,12 @@ -- -- TC006: UPDATE/DELETE against a key column and revert to baseline -- +-- For UPDATE/DELETE, db2_fdw requires at least one primary key column marked +-- with the column option "key". +-- The DB2 SAMPLE.ORG table uses DEPTNUMB as its primary key. +ALTER FOREIGN TABLE sample.org + ALTER COLUMN deptnumb OPTIONS (ADD key 'true'); + \d+ sample.org; -- Baseline row diff --git a/test/sql/tc013.sql b/test/sql/tc013.sql new file mode 100644 index 0000000..2a912c8 --- /dev/null +++ b/test/sql/tc013.sql @@ -0,0 +1,9 @@ +-- +-- TC013: Obtain LOB and NULLs +-- +select deploymentdata from sample.uaci_rtdeployment; +select rtdeploymentid,icid,deploymentdata from sample.uaci_rtdeployment; +select * from sample.uaci_rtdeployment; +-- +-- END of TC013 +-- diff --git a/test/sql/tccdb.sql b/test/sql/tccdb.sql new file mode 100644 index 0000000..13a8f8a --- /dev/null +++ b/test/sql/tccdb.sql @@ -0,0 +1,8 @@ +SELECT 'CREATE DATABASE regtest' +WHERE NOT EXISTS ( + SELECT FROM pg_database + WHERE datname = 'regtest' +)\gexec + +GRANT ALL PRIVILEGES ON DATABASE regtest to postgres; +\c regtest diff --git a/test/sql/tcend.sql b/test/sql/tcend.sql new file mode 100644 index 0000000..8edc003 --- /dev/null +++ b/test/sql/tcend.sql @@ -0,0 +1,2 @@ +\c postgres +DROP DATABASE regtest; diff --git a/test/sql/tcfdw.sql b/test/sql/tcfdw.sql new file mode 100644 index 0000000..de232c7 --- /dev/null +++ b/test/sql/tcfdw.sql @@ -0,0 +1,8 @@ +-- Install extension +CREATE EXTENSION IF NOT EXISTS db2_fdw; +select db2_diag(); + +-- Install FDW Server +CREATE SERVER IF NOT EXISTS sample FOREIGN DATA WRAPPER db2_fdw OPTIONS (dbserver 'SAMPLE'); +-- Map a user +CREATE USER MAPPING FOR PUBLIC SERVER sample OPTIONS (user 'db2inst1', password 'db2inst1'); diff --git a/test/sql/tcstart.sql b/test/sql/tcstart.sql new file mode 100644 index 0000000..45dc381 --- /dev/null +++ b/test/sql/tcstart.sql @@ -0,0 +1,7 @@ +-- Prepare a local schema +CREATE SCHEMA IF NOT EXISTS sample; +-- Import the complete sample db into the local schema +IMPORT FOREIGN SCHEMA "DB2INST1" FROM SERVER sample INTO sample; + +-- list imported tables +\detr+ sample.* From bc1421817bdf821caa516fad626be6e6e0f2ab18 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Sun, 10 May 2026 14:35:11 +0200 Subject: [PATCH 116/120] enhanced push down order by limit offset --- include/DB2ResultColumn.h | 15 +- source/db2AnalyzeForeignTable.c | 4 +- source/db2BindParameter.c | 25 +- source/db2CheckErr.c | 39 +- source/db2EndDirectModify.c | 1 + source/db2EndForeignModifyCommon.c | 1 + source/db2ExecuteInsert.c | 1 - source/db2ExecuteQuery.c | 1 - source/db2FetchNext.c | 189 +++++++++- source/db2GetForeignJoinPaths.c | 163 ++++++--- source/db2GetForeignPlan.c | 204 +++++++---- source/db2IterateDirectModify.c | 2 +- source/db2IterateForeignScan.c | 6 +- source/db2PlanForeignModify.c | 27 ++ source/db2PrepareQuery.c | 143 +++++++- source/db2_deparse.c | 22 +- source/db2_fdw_utils.c | 4 +- source/db2_utils.c | 66 ---- test/expected/base.out | 553 ++++++++++++++++++++--------- 19 files changed, 1067 insertions(+), 399 deletions(-) diff --git a/include/DB2ResultColumn.h b/include/DB2ResultColumn.h index a438775..c16be75 100644 --- a/include/DB2ResultColumn.h +++ b/include/DB2ResultColumn.h @@ -1,5 +1,18 @@ #ifndef DB2RESULTCOLUMN_H #define DB2RESULTCOLUMN_H + +/* + * ODBC/CLI uses SQLLEN* as the indicator pointer for SQLBindCol. + * + * This header is included from PostgreSQL-facing code paths where we avoid + * including DB2 CLI headers (sqlcli.h/sqlcli1.h) due to header conflicts. + * Also, DB2's sqlcli.h defines SQLLEN as a macro, so attempting to typedef it + * here is fragile. + * + * We therefore store the indicator in a toolchain-agnostic pointer-sized + * integer, and cast to SQLLEN* only in the DB2-CLI compilation units. + */ +#include /** DB2ResultColumn * A full descriptor of a DB2 table column and its corresponding PG column. * @@ -26,7 +39,7 @@ typedef struct db2ResultColumn { char* val; // buffer for DB2 to return results in (LOB locator for LOBs) size_t val_size; // allocated size in val size_t val_len; // actual length of val - int val_null; // indicator for NULL value + intptr_t val_null; // NULL indicator (cast to SQLLEN* at SQLBindCol) db2NoEncErrType noencerr; // no encoding error produced struct db2ResultColumn* next; } DB2ResultColumn; diff --git a/source/db2AnalyzeForeignTable.c b/source/db2AnalyzeForeignTable.c index 94f54c6..c1eacd5 100644 --- a/source/db2AnalyzeForeignTable.c +++ b/source/db2AnalyzeForeignTable.c @@ -15,7 +15,7 @@ extern DB2FdwState* db2GetFdwState (Oid foreigntableid, double* sampl extern int db2IsStatementOpen (DB2Session* session); extern void db2PrepareQuery (DB2Session* session, const char *query, DB2ResultColumn* resultList, unsigned long prefetch, int fetchsize); extern int db2ExecuteQuery (DB2Session* session, ParamDesc* paramList); -extern int db2FetchNext (DB2Session* session); +extern int db2FetchNext (DB2Session* session, DB2ResultColumn* resultList); extern void checkDataType (short db2type, int scale, Oid pgtype, const char* tablename, const char* colname); extern short c2dbType (short fcType); extern void convertTuple (DB2Session* session, DB2Table* db2Table, DB2ResultColumn* reslist, int natts, Datum* values, bool* nulls); @@ -101,7 +101,7 @@ static int acquireSampleRowsFunc (Relation relation, int elevel, HeapTuple* rows db2Debug3("loop through query results"); /* loop through query results */ fdw_state->rowcount = -1; - while (db2IsStatementOpen (fdw_state->session) ? db2FetchNext (fdw_state->session) : (db2PrepareQuery (fdw_state->session, fdw_state->query, fdw_state->resultList, fdw_state->prefetch, fdw_state->fetch_size), db2ExecuteQuery (fdw_state->session, fdw_state->paramList))) { + while (db2IsStatementOpen (fdw_state->session) ? db2FetchNext (fdw_state->session, fdw_state->resultList) : (db2PrepareQuery (fdw_state->session, fdw_state->query, fdw_state->resultList, fdw_state->prefetch, fdw_state->fetch_size), db2ExecuteQuery (fdw_state->session, fdw_state->paramList))) { /* allow user to interrupt ANALYZE */ #if PG_VERSION_NUM >= 180000 vacuum_delay_point (true); diff --git a/source/db2BindParameter.c b/source/db2BindParameter.c index 4185484..f185af1 100644 --- a/source/db2BindParameter.c +++ b/source/db2BindParameter.c @@ -12,7 +12,6 @@ extern char db2Message[ERRBUFSIZE];/* contains DB2 error messages, set b extern SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleType, int line, char* file); extern void db2Error_d (db2error sqlstate, const char* message, const char* detail, ...); extern SQLSMALLINT param2c (SQLSMALLINT fcType); -extern void parse2num_struct (const char* s, SQL_NUMERIC_STRUCT* ns); extern char* c2name (short fcType); /** internal prototypes */ @@ -105,21 +104,23 @@ void db2BindParameter (DB2Session* session, ParamDesc* param, SQLLEN* indicator, case SQL_REAL: case SQL_DOUBLE: case SQL_DECFLOAT: { - SQL_NUMERIC_STRUCT* num = NULL; - if (param->value != NULL) { - num = db2alloc(sizeof(SQL_NUMERIC_STRUCT), "SQL_NUMERIC_STRUCT num "); - parse2num_struct(param->value, num); - db2Debug2("num: '%s'",*num); - } + /* + * Bind numeric values as strings. + * The previous SQL_C_NUMERIC + SQL_NUMERIC_STRUCT path relied on parse2num_struct(), which hard-coded precision/scale and could + * trigger DB2 CLI0111E / SQLSTATE 22003 for values that don't match the target column's precision/scale. + */ + *indicator = (SQLLEN) ((param->value == NULL) ? SQL_NULL_DATA : SQL_NTS); + db2Debug2("param_ind : %d",*indicator); + rc = SQLBindParameter( session->stmtp->hsql , col_num , SQL_PARAM_INPUT - , SQL_C_NUMERIC + , SQL_C_CHAR , param->colType - , num->precision - , num->scale - , num - , sizeof(*num) + , param->colSize + , 0 + , (SQLPOINTER) param->value + , 0 , indicator ); } diff --git a/source/db2CheckErr.c b/source/db2CheckErr.c index 52a6936..989282e 100644 --- a/source/db2CheckErr.c +++ b/source/db2CheckErr.c @@ -43,23 +43,54 @@ SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleTyp SQLCHAR submessage [SUBMESSAGE_LEN]; SQLCHAR sqlstate [SQLSTATE_LEN]; SQLINTEGER sqlcode; + SQLINTEGER diag_column_number = 0; + SQLLEN diag_row_number = 0; + char diag_info [128]; SQLSMALLINT msgLen; int i = 1; memset(submessage,0x00,SUBMESSAGE_LEN); memset(message ,0x00,SQL_MAX_MESSAGE_LENGTH); + memset(diag_info,0x00,sizeof(diag_info)); while (SQL_SUCCEEDED(SQLGetDiagRec(handleType,handle,i,sqlstate,&sqlcode,message,SQL_MAX_MESSAGE_LENGTH,&msgLen))) { db2Debug5("SQLCODE : %d ",sqlcode); - db2Debug5("SQLSTATE: %d ",sqlstate); + db2Debug5("SQLSTATE: %s ",sqlstate); db2Debug5("MESSAGE : '%s'",message); + if (i == 1) { + SQLRETURN diag_rc; + diag_rc = SQLGetDiagField(handleType, handle, i, SQL_DIAG_COLUMN_NUMBER, + &diag_column_number, (SQLSMALLINT) sizeof(diag_column_number), NULL); + if (SQL_SUCCEEDED(diag_rc)) { + db2Debug5("DIAG_COLUMN_NUMBER: %d", diag_column_number); + } + diag_rc = SQLGetDiagField(handleType, handle, i, SQL_DIAG_ROW_NUMBER, + &diag_row_number, (SQLSMALLINT) sizeof(diag_row_number), NULL); + if (SQL_SUCCEEDED(diag_rc)) { + db2Debug5("DIAG_ROW_NUMBER : %ld", (long) diag_row_number); + } + if (diag_column_number != 0 || diag_row_number != 0) { + snprintf(diag_info, sizeof(diag_info), + "DIAG_COLUMN_NUMBER=%d\nDIAG_ROW_NUMBER=%ld\n", + diag_column_number, (long) diag_row_number); + } + } snprintf((char*)submessage, SUBMESSAGE_LEN, "SQLSTATE = %s SQLCODE = %d\nline=%d\nfile=%s\n", sqlstate,sqlcode,line,file); + if (diag_info[0] != '\0') { + if ((sizeof(submessage) - strlen((char*)submessage)) > strlen(diag_info) + 1) { + size_t avail = sizeof(submessage) - strlen((char*)submessage) - 1; + strncat((char*)submessage, diag_info, avail); + } + } if ((sizeof(db2Message) - strlen((char*)db2Message)) > strlen((char*)submessage) + 1) { - strncat ((char*)db2Message,(char*)submessage, SUBMESSAGE_LEN); + size_t avail = sizeof(db2Message) - strlen((char*)db2Message) - 1; + strncat((char*)db2Message, (char*)submessage, avail); } if ((sizeof(db2Message) - strlen((char*)db2Message)) > strlen((char*)message) + 2) { - strncat ((char*)db2Message,(char*)message,SQL_MAX_MESSAGE_LENGTH); - strncat ((char*)db2Message,"\n",strlen("\n")); + size_t avail = sizeof(db2Message) - strlen((char*)db2Message) - 1; + strncat((char*)db2Message, (char*)message, avail); + avail = sizeof(db2Message) - strlen((char*)db2Message) - 1; + strncat((char*)db2Message, "\n", avail); } if (i == 1) { err_code = ((sqlcode == -911 || sqlcode == -913) && strcmp((char*)sqlstate,"40001") == 0) ? 8177 : abs(sqlcode); diff --git a/source/db2EndDirectModify.c b/source/db2EndDirectModify.c index 796ac3d..efe4343 100644 --- a/source/db2EndDirectModify.c +++ b/source/db2EndDirectModify.c @@ -43,6 +43,7 @@ void db2EndDirectModify(ForeignScanState* node) { if (output_funcs){ db2free(output_funcs,"output_funcs"); + output_funcs = NULL; } db2free(fdw_state,"fdw_state"); diff --git a/source/db2EndForeignModifyCommon.c b/source/db2EndForeignModifyCommon.c index 9506c13..138f126 100644 --- a/source/db2EndForeignModifyCommon.c +++ b/source/db2EndForeignModifyCommon.c @@ -46,6 +46,7 @@ void db2EndForeignModifyCommon(EState *estate, ResultRelInfo *rinfo) { if (output_funcs){ db2free(output_funcs,"output_funcs"); + output_funcs = NULL; } rinfo->ri_FdwState = NULL; diff --git a/source/db2ExecuteInsert.c b/source/db2ExecuteInsert.c index f505613..051f6f8 100644 --- a/source/db2ExecuteInsert.c +++ b/source/db2ExecuteInsert.c @@ -13,7 +13,6 @@ extern int err_code; /* error code, set by db2CheckErr() extern SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleType, int line, char* file); extern void db2Error_d (db2error sqlstate, const char* message, const char* detail, ...); extern SQLSMALLINT param2c (SQLSMALLINT fcType); -extern void parse2num_struct (const char* s, SQL_NUMERIC_STRUCT* ns); extern char* c2name (short fcType); extern void db2BindParameter (DB2Session* session, ParamDesc* param, SQLLEN* indicators, int param_count, int col_num); diff --git a/source/db2ExecuteQuery.c b/source/db2ExecuteQuery.c index d9decaf..45ec7ce 100644 --- a/source/db2ExecuteQuery.c +++ b/source/db2ExecuteQuery.c @@ -14,7 +14,6 @@ extern int err_code; /* error code, set by db2CheckErr() extern SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleType, int line, char* file); extern void db2Error_d (db2error sqlstate, const char* message, const char* detail, ...); extern SQLSMALLINT param2c (SQLSMALLINT fcType); -extern void parse2num_struct (const char* s, SQL_NUMERIC_STRUCT* ns); extern char* c2name (short fcType); extern void db2BindParameter (DB2Session* session, ParamDesc* param, SQLLEN* indicators, int param_count, int col_num); diff --git a/source/db2FetchNext.c b/source/db2FetchNext.c index b8fd556..71fd5e2 100644 --- a/source/db2FetchNext.c +++ b/source/db2FetchNext.c @@ -1,4 +1,6 @@ +#include #include "db2_fdw.h" +#include "DB2ResultColumn.h" /** global variables */ @@ -12,23 +14,202 @@ extern void db2Error_d (db2error sqlstate, const char* message, c extern SQLRETURN db2CheckErr (SQLRETURN status, SQLHANDLE handle, SQLSMALLINT handleType, int line, char* file); /** local prototypes */ -int db2FetchNext (DB2Session* session); +int db2FetchNext (DB2Session* session, DB2ResultColumn* resultList); /* db2FetchNext * Fetch the next result row, return 1 if there is one, else 0. */ -int db2FetchNext (DB2Session* session) { +int db2FetchNext (DB2Session* session, DB2ResultColumn* resultList) { SQLRETURN rc = 0; + DB2ResultColumn* res = NULL; + DB2ResultColumn* scan = NULL; + int max_resnum = 0; + int i = 0; + SQLULEN retrieve_data = SQL_RD_ON; + SQLRETURN attr_rc = SQL_SUCCESS; + int was_rd_off = 0; db2Entry1(); /* make sure there is a statement handle stored in "session" */ if (session->stmtp == NULL) { db2Error (FDW_ERROR, "db2FetchNext internal error: statement handle is NULL"); } /* fetch the next result row */ - rc = SQLFetchScroll (session->stmtp->hsql, SQL_FETCH_NEXT, 1); + rc = SQLFetch (session->stmtp->hsql); rc = db2CheckErr(rc, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); if (rc != SQL_SUCCESS && rc != SQL_NO_DATA) { - db2Error_d (err_code == 8177 ? FDW_SERIALIZATION_FAILURE : FDW_UNABLE_TO_CREATE_EXECUTION, "error fetching result: SQLFetchScroll failed to fetch next result row", db2Message); + db2Error_d (err_code == 8177 ? FDW_SERIALIZATION_FAILURE : FDW_UNABLE_TO_CREATE_EXECUTION, "error fetching result: SQLFetch failed to fetch next result row", db2Message); + } + + /* Determine whether SQLFetch retrieved data into bound columns. */ + attr_rc = SQLGetStmtAttr(session->stmtp->hsql, SQL_ATTR_RETRIEVE_DATA, &retrieve_data, 0, NULL); + if (!SQL_SUCCEEDED(attr_rc)) + retrieve_data = SQL_RD_ON; + + was_rd_off = (retrieve_data == SQL_RD_OFF); + + /* + * DB2 CLI note: + * Some driver setups behave as if SQL_RD_OFF disables *all* retrieval for the + * current row (including SQLGetData). To avoid SQLFetch-time conversion while + * still allowing SQLGetData, keep SQL_RD_OFF during SQLFetch, then temporarily + * switch to SQL_RD_ON before calling SQLGetData. + */ + if (rc == SQL_SUCCESS && retrieve_data == SQL_RD_OFF) { + SQLRETURN set_rc; + set_rc = SQLSetStmtAttr(session->stmtp->hsql, SQL_ATTR_RETRIEVE_DATA, (SQLPOINTER) SQL_RD_ON, 0); + if (!SQL_SUCCEEDED(set_rc)) + retrieve_data = SQL_RD_ON; + } + + /* + * If SQL_ATTR_RETRIEVE_DATA is SQL_RD_OFF, SQLFetch did not retrieve into any + * bound columns. Fetch values for all (non-LOB) result columns via SQLGetData. + * + * Otherwise, only fetch DECIMAL/NUMERIC/DECFLOAT via SQLGetData. + */ + if (rc == SQL_SUCCESS && resultList) { + /* + * Some DB2 CLI / ODBC driver setups require SQLGetData calls to be made in + * strict ascending column order. Our internal result column list is not + * guaranteed to be ordered by resnum, so enforce ordering here. + */ + for (scan = resultList; scan; scan = scan->next) { + if (scan->resnum > max_resnum) + max_resnum = scan->resnum; + } + + for (i = 1; i <= max_resnum; i++) { + SQLLEN ind = 0; + SQLRETURN get_rc_raw; + SQLRETURN get_rc; + int want_getdata = 0; + + res = NULL; + for (scan = resultList; scan; scan = scan->next) { + if (scan->resnum == i) { + res = scan; + break; + } + } + if (res == NULL) { + if (retrieve_data == SQL_RD_OFF) { + db2Error (FDW_ERROR, "db2FetchNext internal error: missing result column for resnum"); + } + continue; + } + + if (retrieve_data == SQL_RD_OFF) { + /* Skip LOBs here; convertTuple/db2GetLob will fetch them separately. */ + if (res->colType == SQL_BLOB || res->colType == SQL_CLOB) + continue; + want_getdata = 1; + } else { + want_getdata = (res->colType == SQL_DECIMAL || res->colType == SQL_NUMERIC || res->colType == SQL_DECFLOAT); + } + + if (!want_getdata) + continue; + + if (res->val == NULL || res->val_size == 0) { + db2Error (FDW_ERROR, "db2FetchNext internal error: result column buffer is NULL"); + } + + /* + * Some DB2 CLI/ODBC drivers return fixed-width character data without + * writing a NUL terminator when using SQLGetData(SQL_C_CHAR). Ensure the + * buffer is pre-zeroed so the string is always terminated even if the + * driver only overwrites the payload bytes. + */ + memset(res->val, 0, res->val_size); + + get_rc_raw = SQLGetData(session->stmtp->hsql, (SQLUSMALLINT) res->resnum, + SQL_C_CHAR, res->val, (SQLLEN) res->val_size, &ind); + get_rc = db2CheckErr(get_rc_raw, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); + if (get_rc != SQL_SUCCESS && get_rc != SQL_NO_DATA) { + db2Error_d (err_code == 8177 ? FDW_SERIALIZATION_FAILURE : FDW_UNABLE_TO_CREATE_EXECUTION, "error fetching result: SQLGetData failed to fetch column", db2Message); + } + + if (ind == SQL_NULL_DATA) { + res->val_null = (intptr_t) SQL_NULL_DATA; + res->val_len = 0; + continue; + } + if (ind == SQL_NO_TOTAL) { + /* Best-effort: treat as a C string. */ + res->val[res->val_size - 1] = '\0'; + res->val_len = strlen(res->val); + res->val_null = (intptr_t) res->val_len; + continue; + } + + /* If we got truncation info, grow buffer once and retry. */ + if (get_rc_raw == SQL_SUCCESS_WITH_INFO && ind >= (SQLLEN) res->val_size) { + size_t needed = (size_t) ind + 1; + res->val = (char*) db2realloc(needed, res->val, "res->val"); + res->val_size = needed; + ind = 0; + + memset(res->val, 0, res->val_size); + get_rc_raw = SQLGetData(session->stmtp->hsql, (SQLUSMALLINT) res->resnum, + SQL_C_CHAR, res->val, (SQLLEN) res->val_size, &ind); + get_rc = db2CheckErr(get_rc_raw, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); + if (get_rc != SQL_SUCCESS && get_rc != SQL_NO_DATA) { + db2Error_d (err_code == 8177 ? FDW_SERIALIZATION_FAILURE : FDW_UNABLE_TO_CREATE_EXECUTION, "error fetching result: SQLGetData failed to fetch column", db2Message); + } + if (ind == SQL_NULL_DATA) { + res->val_null = (intptr_t) SQL_NULL_DATA; + res->val_len = 0; + continue; + } + if (ind == SQL_NO_TOTAL) { + res->val[res->val_size - 1] = '\0'; + res->val_len = strlen(res->val); + res->val_null = (intptr_t) res->val_len; + continue; + } + } + + res->val_null = (intptr_t) ind; + res->val_len = (size_t) ind; + if (res->val_len >= res->val_size) { + res->val_len = res->val_size - 1; + } + res->val[res->val_len] = '\0'; + } + } + + /* + * Defensive normalization for NULL indicators in SQL_RD_ON mode. + * + * We store the indicator in an intptr_t (DB2ResultColumn.val_null) and cast it + * to SQLLEN* when binding. Some driver/toolchain combinations appear to write + * a 32-bit SQL_NULL_DATA (-1) into the lower 4 bytes only, leaving stale high + * bits. That can turn a NULL into a large positive value, which later code + * interprets as NOT NULL and may try to parse an empty buffer (e.g. timestamp + * "" -> invalid input syntax). + */ + if (rc == SQL_SUCCESS && resultList && retrieve_data != SQL_RD_OFF) { + for (res = resultList; res; res = res->next) { + if ((int32_t) res->val_null == (int32_t) SQL_NULL_DATA) { + res->val_null = (intptr_t) SQL_NULL_DATA; + res->val_len = 0; + } + } + } + + /* + * If this fetch started in SQL_RD_OFF mode, we temporarily switched to + * SQL_RD_ON to allow SQLGetData. Restore SQL_RD_OFF so subsequent SQLFetch + * calls continue to avoid fetch-time conversion and we keep refreshing all + * columns per-row via SQLGetData. + */ + if (rc == SQL_SUCCESS && was_rd_off) { + SQLRETURN set_back_rc; + set_back_rc = SQLSetStmtAttr(session->stmtp->hsql, SQL_ATTR_RETRIEVE_DATA, (SQLPOINTER) SQL_RD_OFF, 0); + set_back_rc = db2CheckErr(set_back_rc, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); + if (set_back_rc != SQL_SUCCESS) { + db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error fetching result: failed to restore SQL_ATTR_RETRIEVE_DATA=SQL_RD_OFF", db2Message); + } } db2Exit1(": %d",(rc == SQL_SUCCESS)); return (rc == SQL_SUCCESS); diff --git a/source/db2GetForeignJoinPaths.c b/source/db2GetForeignJoinPaths.c index 96a44ea..753ed84 100644 --- a/source/db2GetForeignJoinPaths.c +++ b/source/db2GetForeignJoinPaths.c @@ -12,6 +12,22 @@ extern char* deparseExpr (PlannerInfo* root, RelOptInfo* fo /** local prototypes */ void db2GetForeignJoinPaths(PlannerInfo* root, RelOptInfo* joinrel, RelOptInfo* outerrel, RelOptInfo* innerrel, JoinType jointype, JoinPathExtraData* extra); static bool foreign_join_ok (PlannerInfo* root, RelOptInfo* joinrel, JoinType jointype, RelOptInfo* outerrel, RelOptInfo* innerrel, JoinPathExtraData* extra); +static DB2Column* db2FindColumnByRelidAttnum(DB2Table* db2Table, int pgrelid, int pgattnum); + +/* db2FindColumnByRelidAttnum + * Find the DB2Column descriptor for a base foreign relation column. + */ +static DB2Column* db2FindColumnByRelidAttnum(DB2Table* db2Table, int pgrelid, int pgattnum) { + int i; + if (!db2Table || !db2Table->cols) + return NULL; + for (i = 0; i < db2Table->ncols; ++i) { + DB2Column* tmp = db2Table->cols[i]; + if (tmp && tmp->pgrelid == pgrelid && tmp->pgattnum == pgattnum) + return tmp; + } + return NULL; +} /* db2GetForeignJoinPaths * Add possible ForeignPath to joinrel if the join is safe to push down. @@ -19,6 +35,8 @@ static bool foreign_join_ok (PlannerInfo* root, RelOptInfo* joinrel, JoinT */ void db2GetForeignJoinPaths (PlannerInfo * root, RelOptInfo * joinrel, RelOptInfo * outerrel, RelOptInfo * innerrel, JoinType jointype, JoinPathExtraData * extra) { DB2FdwState* fdwState = NULL; + DB2FdwState* fdwState_o = NULL; + DB2FdwState* fdwState_i = NULL; ForeignPath* joinpath = NULL; double joinclauses_selectivity = 0; double rows = 0; /* estimated number of returned rows */ @@ -54,6 +72,9 @@ void db2GetForeignJoinPaths (PlannerInfo * root, RelOptInfo * joinrel, RelOptInf /* this performs further checks and completes joinrel->fdw_private */ if (foreign_join_ok (root, joinrel, jointype, outerrel, innerrel, extra)) { + fdwState_o = (DB2FdwState*) outerrel->fdw_private; + fdwState_i = (DB2FdwState*) innerrel->fdw_private; + /* estimate the number of result rows for the join */ #if PG_VERSION_NUM < 140000 if (outerrel->pages > 0 && innerrel->pages > 0) @@ -69,11 +90,17 @@ void db2GetForeignJoinPaths (PlannerInfo * root, RelOptInfo * joinrel, RelOptInf rows = 1000.0; } - /* use a random "high" value for startup cost */ - startup_cost = 10000.0; - - /* estimate total cost as startup cost + (returned rows) * 10.0 */ - total_cost = startup_cost + rows * 10.0; + /* + * Minimal cost tweak: + * The previous hard-coded startup_cost=10000 made the foreign-join path + * effectively impossible to win against a local join, so join pushdown + * never happened even when it was otherwise safe. + * + * Use the already-estimated costs of the two input foreign relations as + * the baseline cost for the pushed-down join. + */ + startup_cost = (fdwState_o ? fdwState_o->startup_cost : 0) + (fdwState_i ? fdwState_i->startup_cost : 0); + total_cost = (fdwState_o ? fdwState_o->total_cost : 0) + (fdwState_i ? fdwState_i->total_cost : 0); /* store cost estimation results */ joinrel->rows = rows; @@ -100,6 +127,14 @@ void db2GetForeignJoinPaths (PlannerInfo * root, RelOptInfo * joinrel, RelOptInf ); /* add generated path to joinrel */ add_path(joinrel, (Path *) joinpath); + } else { + /* + * foreign_join_ok can reject pushdown for reasons that might depend on + * planner state (or because we could not map join target columns). In + * that case, don't leave a half-initialized marker state behind that + * prevents reconsideration. + */ + joinrel->fdw_private = NULL; } } } @@ -119,7 +154,6 @@ static bool foreign_join_ok (PlannerInfo * root, RelOptInfo * joinrel, JoinType DB2Table* db2Table_i = NULL; ListCell* lc = NULL; List* otherclauses = NULL; - char* tabname = NULL;/* for warning messages */ db2Entry1(); /* we only support pushing down INNER joins */ @@ -223,8 +257,9 @@ static bool foreign_join_ok (PlannerInfo * root, RelOptInfo * joinrel, JoinType db2Table_i = fdwState_i->db2Table; fdwState->db2Table = (DB2Table*) db2alloc(sizeof (DB2Table), "fdw_state->db2Table"); - fdwState->db2Table->name = db2strdup ("", "fdwState->db2Table->name"); - fdwState->db2Table->pgname = db2strdup ("", "fdwState->db2Table->pgname"); + /* Give the joinrel a non-empty name so warnings/debug output are readable. */ + fdwState->db2Table->name = db2strdup ("joinrel", "fdwState->db2Table->name"); + fdwState->db2Table->pgname = db2strdup ("joinrel", "fdwState->db2Table->pgname"); fdwState->db2Table->ncols = 0; fdwState->db2Table->npgcols = 0; fdwState->db2Table->cols = (DB2Column **) db2alloc((sizeof (DB2Column*) * (db2Table_o->ncols + db2Table_i->ncols)), "fdw_state->db2Table->cols[%d]",(db2Table_o->ncols + db2Table_i->ncols)); @@ -233,66 +268,84 @@ static bool foreign_join_ok (PlannerInfo * root, RelOptInfo * joinrel, JoinType * Here we assume that children are foreign table, not foreign join. * We need capability to track relid chain through join tree to support N-way join. */ - tabname = "?"; foreach (lc, joinrel->reltarget->exprs) { - int i; - Var *var = (Var *) lfirst (lc); - struct db2Column *col = NULL; - struct db2Column *newcol; - int used_flag = 0; - - Assert (IsA (var, Var)); - /* Find appropriate entry from children's db2Table. */ - for (i = 0; i < db2Table_o->ncols; ++i) { - struct db2Column *tmp = db2Table_o->cols[i]; - - if (tmp->pgrelid == var->varno) { - tabname = db2Table_o->pgname; - - if (tmp->pgattnum == var->varattno) { - col = tmp; - break; - } - } - } + Var* var = (Var *) lfirst(lc); + DB2Column* col = NULL; + DB2Column* newcol = NULL; + int used_flag = 0; + int src_varno; + int src_attno; + int src_relid; + + if (!IsA(var, Var)) + return false; + + /* + * joinrel->reltarget->exprs can contain Vars referencing join inputs via + * OUTER_VAR/INNER_VAR (and sometimes direct baserel RT indexes). Resolve + * those to the appropriate child's relid so we can look up the base column + * descriptor and copy its DB2/PG type metadata. + */ + src_varno = var->varno; + src_attno = var->varattno; + src_relid = src_varno; + + if (src_varno == OUTER_VAR) + src_relid = outerrel->relid; + else if (src_varno == INNER_VAR) + src_relid = innerrel->relid; + + col = db2FindColumnByRelidAttnum(db2Table_o, src_relid, src_attno); if (!col) { - for (i = 0; i < db2Table_i->ncols; ++i) { - struct db2Column *tmp = db2Table_i->cols[i]; - - if (tmp->pgrelid == var->varno) { - tabname = db2Table_i->pgname; - - if (tmp->pgattnum == var->varattno) { - col = tmp; - break; - } - } - } + col = db2FindColumnByRelidAttnum(db2Table_i, src_relid, src_attno); } - newcol = (DB2Column*) db2alloc(sizeof (DB2Column), "newcol"); + newcol = (DB2Column*) db2alloc(sizeof(DB2Column), "newcol"); + memset(newcol, 0, sizeof(DB2Column)); + if (col) { - memcpy (newcol, col, sizeof (struct db2Column)); + memcpy(newcol, col, sizeof(DB2Column)); used_flag = 1; } else { - /* non-existing column, print a warning */ - ereport (WARNING - ,(errcode(ERRCODE_WARNING) - ,errmsg ("column number %d of foreign table \"%s\" does not exist in foreign DB2 table, will be replaced by NULL" - ,var->varattno - ,tabname - ) - ) - ); + /* + * If we can't map an output column back to an underlying foreign table + * column, join pushdown isn't safe (we'd otherwise create a column with + * undefined pgtype/colType and crash at execution time). + */ + ereport(DEBUG2, + (errmsg("db2_fdw: cannot map join output column (varno=%d attno=%d); disabling join pushdown for this join", + var->varno, var->varattno))); + return false; } + newcol->used = used_flag; - /* pgattnum should be the index in SELECT clause of join query. */ - newcol->pgattnum = fdwState->db2Table->ncols + 1; + /* + * IMPORTANT: + * db2GetForeignPlan() later maps Vars by Var.varattno to db2Table->cols[*]->pgattnum. + * For joinrels these Var.varattno values are already the join's attribute numbers, + * so we must preserve them here (not renumber sequentially), otherwise planning + * will fail to find columns and leave result bindings uninitialized. + */ + newcol->pgattnum = var->varattno; fdwState->db2Table->cols[fdwState->db2Table->ncols++] = newcol; } - fdwState->db2Table->npgcols = fdwState->db2Table->ncols; + /* + * IMPORTANT: + * For base foreign tables, db2Table->npgcols matches the number of PG columns + * and convertTuple() can map output columns by pgattnum. + * + * For join pushdown, however, the executor slot natts equals the number of + * projected join output columns, while the pgattnum values we carry in the + * DB2ResultColumn descriptors refer to *base-table* attnums (and can be + * sparse / non-1..N). If npgcols == natts, convertTuple() would treat the + * join as a "simple select" and use pgattnum as the destination index, + * causing out-of-bounds writes and corrupted tuples. + * + * Force convertTuple() to use resnum-based mapping for joinrels. + */ + fdwState->db2Table->npgcols = 0; db2Exit1(); return true; diff --git a/source/db2GetForeignPlan.c b/source/db2GetForeignPlan.c index 4cfb638..9080ab1 100644 --- a/source/db2GetForeignPlan.c +++ b/source/db2GetForeignPlan.c @@ -3,6 +3,8 @@ #include #include #include +#include +#include #include #include #include @@ -32,7 +34,6 @@ extern List* serializePlanData (DB2FdwState* fdw_state); ForeignScan* db2GetForeignPlan (PlannerInfo* root, RelOptInfo* foreignrel, Oid foreigntableid, ForeignPath* best_path, List* tlist, List* scan_clauses , Plan* outer_plan); static void getUsedColumns (Expr* expr, RelOptInfo* foreignrel, DB2ResultColumn* resCol); static void copyCol2Result (DB2ResultColumn* resCol, DB2Column* db2Column); -static int compareResultColumns (const void* a, const void* b); /* postgresGetForeignPlan * Create ForeignScan plan node which implements selected best path @@ -80,71 +81,122 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo ptlist_len = list_length(ptlist); if (IS_SIMPLE_REL(foreignrel)) { - DB2ResultColumn** cols = NULL; +// DB2ResultColumn** cols = NULL; ListCell* cell = NULL; - int iResCol = 0; +// int iResCol = 0; db2Debug3("base relation scan: set scan_relid to %d", foreignrel->relid); /* For base relations, set scan_relid as the relid of the relation. */ scan_relid = foreignrel->relid; - if (ptlist_len >0) { - DB2ResultColumn* resCol = NULL; - /* find all the columns to include in the select list */ - /* examine each SELECT list entry for Var nodes */ - db2Debug3("size of tlist: %d", ptlist_len); - foreach (cell, ptlist) { - resCol = (DB2ResultColumn*)db2alloc(sizeof(DB2ResultColumn), "resCol"); - getUsedColumns ((Expr*) lfirst (cell), foreignrel, resCol); - db2Debug3("resCol->colName: %s", resCol->colName); - db2Debug3("resCol->pgattnum: %d", resCol->pgattnum); - if (resCol->colName != NULL && resCol->pgattnum <= fpinfo->db2Table->npgcols) { - resCol->next = fpinfo->resultList; - db2Debug3("resCol->next: %x", resCol->next); - fpinfo->resultList = resCol; - db2Debug3("fpinfo->resultList: %x", fpinfo->resultList); - } else { - db2Debug3("about to free resCol: %x, colName: %s, pgattnum: %d", resCol, resCol->colName, resCol->pgattnum); - db2free(resCol,"resCol"); - } + /* + * If we have any locally-evaluated quals, they might reference Vars that are + * *not* part of the query's output targetlist. Those Vars must still be + * available in the ForeignScan's tuple slot, otherwise setrefs.c can error + * out with "variable not found in subplan target list". + * + * IMPORTANT: + * We must include Vars referenced by quals that can be evaluated locally. + * That includes: + * - fpinfo->local_conds (not shippable), and + * - fpinfo->remote_conds, because they are stored in fdw_recheck_quals and + * can be evaluated locally during EPQ recheck. + * + * The remote SELECT list must also include these Vars; deparseSelectSql() + * is responsible for honoring the fdw_scan_tlist (ptlist) for base rels. + */ + { + List* qual_vars = NIL; + ListCell* c = NULL; + + foreach (c, fpinfo->local_conds) { + RestrictInfo* rinfo = lfirst_node(RestrictInfo, c); + qual_vars = list_concat(qual_vars, pull_var_clause((Node*) rinfo->clause, PVC_RECURSE_PLACEHOLDERS)); } - for (resCol = fpinfo->resultList; resCol; resCol = resCol->next) { - iResCol++; + + foreach (c, fpinfo->remote_conds) { + RestrictInfo* rinfo = lfirst_node(RestrictInfo, c); + qual_vars = list_concat(qual_vars, pull_var_clause((Node*) rinfo->clause, PVC_RECURSE_PLACEHOLDERS)); } - cols = (DB2ResultColumn**)db2alloc(iResCol+1 * sizeof(DB2ResultColumn*),"cols(%d)",iResCol+1); - iResCol = 0; - for (resCol = fpinfo->resultList; resCol; resCol = resCol->next) { - db2Debug2("resCol: %x", resCol); - cols[iResCol] = resCol; - db2Debug2("cols[%d] : %x", iResCol, cols[iResCol]); - db2Debug2("cols[%d]->colName: %s", iResCol, cols[iResCol]->colName); - db2Debug2("cols[%d]->resnum : %d", iResCol, cols[iResCol]->resnum); - iResCol++; - db2Debug2("resCol->next: %x", resCol->next); + + if (qual_vars != NIL) { + ListCell* v = NULL; + + ptlist = add_to_flat_tlist(ptlist, qual_vars); + ptlist_len = list_length(ptlist); + + foreach (v, qual_vars) { + Var* var = lfirst_node(Var, v); + + /* Ignore system columns and whole-row refs. */ + if (var->varattno <= 0) + continue; + + if (tlist_member((Expr*) var, tlist) == NULL) { + char* attname = get_attname(foreigntableid, var->varattno, false); + TargetEntry* tle = makeTargetEntry((Expr*) copyObject(var), list_length(tlist) + 1, attname, true); + + /* makeTargetEntry(..., resjunk=true) already sets resjunk */ + tlist = lappend(tlist, tle); + } + } } - // sort the array in ascending order of the pgattnum, so that we can compare it with the order of columns in the foreign table - db2Debug3("sorting result cols %d columns by pgattnum", iResCol); - qsort(cols, iResCol, sizeof(DB2ResultColumn*), compareResultColumns); - // generate the sorted array into the resultList + } + + if (ptlist_len > 0) { + DB2ResultColumn* resCol = NULL; + DB2ResultColumn* tail = NULL; + int resnum = 0; + + /* + * Build fpinfo->resultList in the same order as ptlist (which is also the + * order used to deparse the remote SELECT-list). Do NOT sort by pgattnum: + * that can reorder resjunk columns and desynchronize resnum vs DB2 cursor + * column positions, leading to mis-fetched values (e.g. WORKDEPT receiving + * SALARY bytes). + */ fpinfo->resultList = NULL; - for (int idx = 0, cidx = 0; idx < iResCol; idx++) { - db2Debug3("result column %d: %s", idx, cols[idx]->colName); - if (idx > 0) { - // check if this column is the same as the one before and if so, skip it - if (cols[idx]->pgattnum == cols[idx-1]->pgattnum) { - continue; + db2Debug3("size of tlist: %d", ptlist_len); + foreach (cell, ptlist) { + DB2ResultColumn* scan = NULL; + bool dup = false; + + resCol = (DB2ResultColumn*) db2alloc(sizeof(DB2ResultColumn), "resCol"); + getUsedColumns((Expr*) lfirst(cell), foreignrel, resCol); + + if (resCol->colName == NULL || resCol->pgattnum > fpinfo->db2Table->npgcols) { + db2free(resCol, "resCol"); + continue; + } + + /* De-duplicate by pgattnum (same base relation). */ + for (scan = fpinfo->resultList; scan; scan = scan->next) { + if (scan->pgattnum == resCol->pgattnum) { + dup = true; + break; } } - cols[idx]->next = fpinfo->resultList; - cols[idx]->resnum = ++cidx; // result number must be 1 - based - db2Debug3("column %s added to result list with resnum %d", cols[idx]->colName, cols[idx]->resnum); - fpinfo->resultList = cols[idx]; + if (dup) { + db2free(resCol, "resCol"); + continue; + } + + resCol->resnum = ++resnum; + resCol->next = NULL; + if (fpinfo->resultList == NULL) { + fpinfo->resultList = resCol; + tail = resCol; + } else { + tail->next = resCol; + tail = resCol; + } } + /* examine each condition for Var nodes */ db2Debug3("size of conditions: %d", list_length(foreignrel->baserestrictinfo)); foreach (cell, foreignrel->baserestrictinfo) { db2Debug3("examine condition"); - getUsedColumns ((Expr*) lfirst (cell), foreignrel, NULL); + getUsedColumns((Expr*) lfirst(cell), foreignrel, NULL); } } /* In a base-relation scan, we must apply the given scan_clauses. @@ -215,6 +267,25 @@ ForeignScan* db2GetForeignPlan(PlannerInfo* root, RelOptInfo* foreignrel, Oid fo db2Debug3("result column %d: %s", resCol->resnum, resCol->colName); resnum++; } + + /* + * The loop above prepends each entry, so fpinfo->resultList is built in + * reverse order. For joinrels/upperrels we rely on the SELECT-list order + * to match result bindings (resnum / DB2 column positions). + */ + { + DB2ResultColumn* prev = NULL; + DB2ResultColumn* cur = fpinfo->resultList; + + while (cur) { + DB2ResultColumn* next = cur->next; + cur->next = prev; + prev = cur; + cur = next; + } + + fpinfo->resultList = prev; + } } /* Ensure that the outer plan produces a tuple whose descriptor matches our scan tuple slot. Also, remove the local conditions @@ -301,9 +372,27 @@ static void getUsedColumns (Expr* expr, RelOptInfo* foreignrel, DB2ResultColumn* DB2FdwState* fpinfo = (DB2FdwState*) foreignrel->fdw_private; Var* var = NULL; int index = 0; + int relid = 0; var = (Var*) expr; db2Debug4("var->varattno: %d", var->varattno); + + /* + * For joinrels, Vars can refer to join inputs via OUTER_VAR/INNER_VAR. + * Attribute numbers can overlap between the two inputs, so matching only + * on varattno can bind the wrong column (and later mis-convert values). + * + * Resolve OUTER_VAR/INNER_VAR to the underlying child's relid so we can + * match on (pgrelid, pgattnum). + */ + relid = var->varno; + if (!IS_SIMPLE_REL(foreignrel) && fpinfo) { + if (relid == OUTER_VAR && fpinfo->outerrel) + relid = fpinfo->outerrel->relid; + else if (relid == INNER_VAR && fpinfo->innerrel) + relid = fpinfo->innerrel->relid; + } + /* ignore system columns */ if (var->varattno < 0) break; @@ -333,7 +422,8 @@ static void getUsedColumns (Expr* expr, RelOptInfo* foreignrel, DB2ResultColumn* } else { /* get db2Table column index corresponding to this column (-1 if none) */ index = fpinfo->db2Table->ncols - 1; - while (index >= 0 && fpinfo->db2Table->cols[index]->pgattnum != var->varattno) { + while (index >= 0 && (fpinfo->db2Table->cols[index]->pgattnum != var->varattno || + (relid != 0 && fpinfo->db2Table->cols[index]->pgrelid != relid))) { --index; } if (index == -1) { @@ -578,17 +668,3 @@ static void copyCol2Result(DB2ResultColumn* resCol, DB2Column* column) { } db2Exit4(); } - -/* compareResultColumns - * Compare two DB2ResultColumn pointers by their pgattnum. - */ -static int compareResultColumns(const void* a, const void* b) { - DB2ResultColumn* colA = *(DB2ResultColumn**) a; - DB2ResultColumn* colB = *(DB2ResultColumn**) b; - int result = 0; - db2Entry4(); - result = (colA->pgattnum - colB->pgattnum); - db2Debug5("comparing %s -> pgattnum %d and %s -> pgattnum %d, result = %d", colA->colName, colA->pgattnum, colB->colName, colB->pgattnum, result); - db2Exit4(": %d", result); - return result; -} \ No newline at end of file diff --git a/source/db2IterateDirectModify.c b/source/db2IterateDirectModify.c index 51cc428..e8b8e3b 100644 --- a/source/db2IterateDirectModify.c +++ b/source/db2IterateDirectModify.c @@ -8,7 +8,7 @@ extern int db2IsStatementOpen (DB2Session* session); extern void db2PrepareQuery (DB2Session* session, const char *query, DB2ResultColumn* resultList, unsigned long prefetch, int fetchsize); extern int db2ExecuteQuery (DB2Session* session, ParamDesc* paramList); -extern int db2FetchNext (DB2Session* session); +extern int db2FetchNext (DB2Session* session, DB2ResultColumn* resultList); extern void db2CloseStatement (DB2Session* session); /** local prototypes */ diff --git a/source/db2IterateForeignScan.c b/source/db2IterateForeignScan.c index a7954f1..a784050 100644 --- a/source/db2IterateForeignScan.c +++ b/source/db2IterateForeignScan.c @@ -12,7 +12,7 @@ extern int db2IsStatementOpen (DB2Session* session); extern void db2PrepareQuery (DB2Session* session, const char *query, DB2ResultColumn* resultList, unsigned long prefetch, int fetchsize); extern int db2ExecuteQuery (DB2Session* session, ParamDesc* paramList); -extern int db2FetchNext (DB2Session* session); +extern int db2FetchNext (DB2Session* session, DB2ResultColumn* resultList); extern void db2CloseStatement (DB2Session* session); extern void convertTuple (DB2Session* session, DB2Table* db2Table, DB2ResultColumn* reslist, int natts, Datum* values, bool* nulls); extern char* deparseDate (Datum datum); @@ -38,7 +38,7 @@ TupleTableSlot* db2IterateForeignScan (ForeignScanState* node) { if (db2IsStatementOpen (fdw_state->session)) { db2Debug2("get next row in foreign table scan"); /* fetch the next result row */ - have_result = db2FetchNext (fdw_state->session); + have_result = db2FetchNext (fdw_state->session, fdw_state->resultList); } else { /* fill the parameter list with the actual values */ char* paramInfo = setSelectParameters (fdw_state->paramList, econtext); @@ -46,7 +46,7 @@ TupleTableSlot* db2IterateForeignScan (ForeignScanState* node) { db2Debug2("execute query in foreign table scan '%s'", paramInfo); db2PrepareQuery (fdw_state->session, fdw_state->query, fdw_state->resultList, fdw_state->prefetch, fdw_state->fetch_size); have_result = db2ExecuteQuery (fdw_state->session, fdw_state->paramList); - have_result = db2FetchNext (fdw_state->session); + have_result = db2FetchNext (fdw_state->session, fdw_state->resultList); } /* initialize virtual tuple */ ExecClearTuple (slot); diff --git a/source/db2PlanForeignModify.c b/source/db2PlanForeignModify.c index 3a1dbcd..10255f1 100644 --- a/source/db2PlanForeignModify.c +++ b/source/db2PlanForeignModify.c @@ -17,6 +17,7 @@ extern List* serializePlanData (DB2FdwState* fdwState); /** local prototypes */ List* db2PlanForeignModify(PlannerInfo* root, ModifyTable* plan, Index resultRelation, int subplan_index); static DB2FdwState* copyPlanData (DB2FdwState* orig); +static ParamDesc* reverseParamList (ParamDesc* head); void addParam (ParamDesc** paramList, DB2Column* db2col, int colnum, int txts); void checkDataType (short db2type, int scale, Oid pgtype, const char* tablename, const char* colname); @@ -296,6 +297,18 @@ List* db2PlanForeignModify (PlannerInfo* root, ModifyTable* plan, Index resultRe appendStringInfo (&sql, "?"); } } + + /* + * addParam() and the output-param builder above currently prepend to + * fdwState->paramList. + * + * db2ExecuteQuery() binds parameters in list traversal order (1..N). If we + * leave the list in prepended order, the bound parameter values won't match + * the order of '?' placeholders in the generated SQL, leading to wrong DML + * execution and hard-to-debug runtime failures. + */ + fdwState->paramList = reverseParamList(fdwState->paramList); + fdwState->query = sql.data; db2Debug2("fdwState->query: '%s'", fdwState->query); /* return a serialized form of the plan state */ @@ -304,6 +317,20 @@ List* db2PlanForeignModify (PlannerInfo* root, ModifyTable* plan, Index resultRe return result; } +static ParamDesc* reverseParamList(ParamDesc* head) { + ParamDesc* prev = NULL; + ParamDesc* cur = head; + + while (cur) { + ParamDesc* next = cur->next; + cur->next = prev; + prev = cur; + cur = next; + } + + return prev; +} + /** copyPlanData * Create a deep copy of the argument, copy only those fields needed for planning. */ diff --git a/source/db2PrepareQuery.c b/source/db2PrepareQuery.c index debf79b..008aca9 100644 --- a/source/db2PrepareQuery.c +++ b/source/db2PrepareQuery.c @@ -1,9 +1,10 @@ #include +#include #include "db2_fdw.h" #include "ParamDesc.h" #include "DB2ResultColumn.h" -#define SQL_VALUE_PTR_ULEN(v) ((SQLPOINTER)(u_int64_t)(SQLULEN)(v)) +#define SQL_VALUE_PTR_ULEN(v) ((SQLPOINTER)(uintptr_t)(SQLULEN)(v)) /** global variables */ @@ -34,12 +35,29 @@ void db2PrepareQuery (DB2Session* session, const char *query, DB2ResultColumn* r int for_update = 0; SQLRETURN rc = 0; DB2ResultColumn* res = NULL; + int need_getdata = 0; #ifdef FIXED_FETCH_SIZE // Until the proper handling of multiple rows results on a single query are added the fetch size must be 1 fetchsize = 1; #endif + /* + * If we need SQLGetData for any result column, force row array size to 1. + * + * SQLGetData is only well-defined for single-row fetches; with rowsets enabled + * some DB2 CLI setups can fail already at SQLFetch time. + */ + for (res = resultList; res; res = res->next) { + if (res->colType == SQL_DECIMAL || res->colType == SQL_NUMERIC || res->colType == SQL_DECFLOAT) { + need_getdata = 1; + break; + } + } + if (need_getdata) { + fetchsize = 1; + } + db2Entry1(); db2Debug2("query : '%s'",query); db2Debug2("prefetch : %d",prefetch); @@ -77,13 +95,19 @@ void db2PrepareQuery (DB2Session* session, const char *query, DB2ResultColumn* r } db2Debug3("set cursor pessemistic"); } else { - // Make the cursor insensitive scrollable (e.g., static) so PREFETCH_NROWS applies - rc = SQLSetStmtAttr(session->stmtp->hsql, SQL_ATTR_CURSOR_TYPE, (SQLPOINTER)SQL_CURSOR_STATIC, 0); + /* + * Use a forward-only cursor for plain SELECTs. + * + * Several DB2 CLI setups report CLI0111E / SQLSTATE 22003 during SQLFetch + * when using scrollable/static cursors + prefetch attributes. + * For correctness, prefer forward-only here. + */ + rc = SQLSetStmtAttr(session->stmtp->hsql, SQL_ATTR_CURSOR_TYPE, (SQLPOINTER)SQL_CURSOR_FORWARD_ONLY, 0); rc = db2CheckErr(rc, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); if (rc != SQL_SUCCESS) { - db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error executing query: SQLSetStmtAttr failed to make cursor scrollable", db2Message); + db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error executing query: SQLSetStmtAttr failed to make cursor forward-only", db2Message); } - db2Debug3("set cursor static"); + db2Debug3("set cursor forward-only"); } // Fetch rows per network roundtrip rc = SQLSetStmtAttr(session->stmtp->hsql, SQL_ATTR_ROW_ARRAY_SIZE, SQL_VALUE_PTR_ULEN(cur_fetchsize), 0); @@ -92,13 +116,32 @@ void db2PrepareQuery (DB2Session* session, const char *query, DB2ResultColumn* r db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error executing query: SQLSetStmtAttr failed to set fetchsize in statement handle", db2Message); } db2Debug2("set cursor fetchsize: %d",cur_fetchsize); - // Prefetch rows per block for scrollable (non-dynamic) cursors - rc = SQLSetStmtAttr(session->stmtp->hsql, SQL_ATTR_PREFETCH_NROWS, SQL_VALUE_PTR_ULEN(prefetch_rows), 0); - rc = db2CheckErr(rc, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); - if (rc != SQL_SUCCESS) { - db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error executing query: SQLSetStmtAttr failed to set number of prefetched rows in statement handle", db2Message); + + /* + * If we plan to use SQLGetData for result retrieval, disable retrieval into + * bound columns during SQLFetch. + * + * This avoids DB2 CLI conversion at fetch time (which can throw CLI0111E / + * SQLSTATE 22003 for DECIMAL/NUMERIC/DECFLOAT), and instead retrieves data + * per-column via SQLGetData after a successful SQLFetch. + */ + if (need_getdata) { + rc = SQLSetStmtAttr(session->stmtp->hsql, SQL_ATTR_RETRIEVE_DATA, (SQLPOINTER)SQL_RD_OFF, 0); + rc = db2CheckErr(rc, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); + if (rc != SQL_SUCCESS) { + db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error executing query: SQLSetStmtAttr failed to set SQL_ATTR_RETRIEVE_DATA=SQL_RD_OFF", db2Message); + } + db2Debug3("set SQL_ATTR_RETRIEVE_DATA = SQL_RD_OFF"); + } + /* Prefetch rows is only applied for scrollable (non-forward-only) cursors. */ + if (for_update) { + rc = SQLSetStmtAttr(session->stmtp->hsql, SQL_ATTR_PREFETCH_NROWS, SQL_VALUE_PTR_ULEN(prefetch_rows), 0); + rc = db2CheckErr(rc, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); + if (rc != SQL_SUCCESS) { + db2Error_d (FDW_UNABLE_TO_CREATE_EXECUTION, "error executing query: SQLSetStmtAttr failed to set number of prefetched rows in statement handle", db2Message); + } + db2Debug2("set cursor prefetch: %d",prefetch_rows); } - db2Debug2("set cursor prefetch: %d",prefetch_rows); } /* prepare the statement */ @@ -112,6 +155,8 @@ void db2PrepareQuery (DB2Session* session, const char *query, DB2ResultColumn* r /* loop through expected result columns */ for (res = resultList; res; res = res->next){ SQLSMALLINT fparamType = c2param((SQLSMALLINT)res->colType); + int use_getdata = 0; + size_t needed = 0; /* Unfortunately DB2 handles DML statements with a RETURNING clause quite different from SELECT statements. * In the latter, the result columns are "defined", i.e. bound to some storage space. * This definition is only necessary once, even if the query is executed multiple times, so we do this here. @@ -121,6 +166,54 @@ void db2PrepareQuery (DB2Session* session, const char *query, DB2ResultColumn* r if (res->pgtype == UUIDOID) { fparamType = SQL_C_CHAR; } + + /* + * For DECIMAL/NUMERIC/DECFLOAT results, avoid binding with SQLBindCol. + * Some DB2 CLI setups throw CLI0111E / SQLSTATE 22003 during SQLFetch when + * converting bound numeric columns. + * + * We instead fetch these columns via SQLGetData after SQLFetch. + */ + use_getdata = (res->colType == SQL_DECIMAL || res->colType == SQL_NUMERIC || res->colType == SQL_DECFLOAT); + + /* + * Numeric result columns are typically bound as SQL_C_CHAR. + * + * Some DB2 CLI setups report CLI0111E / SQLSTATE 22003 during SQLFetch when + * converting DECIMAL/NUMERIC into too-small output buffers. + * + * Ensure the buffer is large enough for the textual representation: + * - precision digits + * - optional sign + * - optional decimal point + * - NUL terminator + * For DECFLOAT, use a conservative minimum to accommodate exponent forms. + */ + if (res->colType == SQL_DECIMAL || res->colType == SQL_NUMERIC || res->colType == SQL_DECFLOAT) { + size_t prec = (res->colSize > 0 ? res->colSize : 32); + size_t scale = (res->colScale > 0 ? res->colScale : 0); + needed = prec + 2 /* sign + NUL */ + (scale > 0 ? 1 /* '.' */ : 0); + if (res->colType == SQL_DECFLOAT && needed < 64) + needed = 64; + + if (res->val_size < needed) { + res->val = (char*) db2realloc(needed, res->val, "res->val"); + res->val_size = needed; + } + } + + /* + * In SQL_RD_OFF mode we fetch (most) result columns via SQLGetData(SQL_C_CHAR). + * Avoid truncation/retry cycles by ensuring a sane minimum buffer size. + */ + if (need_getdata && fparamType == SQL_C_CHAR) { + if (needed < 128) + needed = 128; + if (res->val_size < needed) { + res->val = (char*) db2realloc(needed, res->val, "res->val"); + res->val_size = needed; + } + } db2Debug2("res->colName : %s" ,res->colName); db2Debug2("res->colSize : %ld",res->colSize); db2Debug2("res->colType : %d" ,res->colType); @@ -132,15 +225,29 @@ void db2PrepareQuery (DB2Session* session, const char *query, DB2ResultColumn* r db2Debug2("res->colCodepage : %d" ,res->colCodepage); db2Debug2("res->val : %x" ,res->val); db2Debug2("res->val_size : %ld",res->val_size); - db2Debug2("res->val_len : %d" ,res->val_len); - db2Debug2("res->val_null : %d" ,res->val_null); + db2Debug2("res->val_len : %ld" ,(long) res->val_len); + db2Debug2("res->val_null : %ld" ,(long) res->val_null); db2Debug2("res->resnum : %d" ,res->resnum); db2Debug2("fparamType: %d (%s)",fparamType,param2name(fparamType)); - db2Debug2("SQLBindCol(%d,%d,%d(%s),%x,%ld,%x)",session->stmtp->hsql,res->resnum, fparamType, param2name(fparamType), res->val, res->val_size, &res->val_null); - rc = SQLBindCol (session->stmtp->hsql,res->resnum, fparamType, res->val, res->val_size, &res->val_null); - rc = db2CheckErr(rc, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); - if (rc != SQL_SUCCESS) { - db2Error_d(FDW_UNABLE_TO_CREATE_EXECUTION, "error executing query: SQLBindCol failed to define result value", db2Message); + + if (need_getdata && fparamType == SQL_C_CHAR) { + /* + * In SQL_RD_OFF mode, SQLFetch will not populate bound columns. + * Initialize as NULL; db2FetchNext() will populate via SQLGetData. + */ + res->val_null = (intptr_t) SQL_NULL_DATA; + res->val_len = 0; + } else if (use_getdata) { + /* DECIMAL/NUMERIC/DECFLOAT are always fetched via SQLGetData. */ + res->val_null = (intptr_t) SQL_NULL_DATA; + res->val_len = 0; + } else { + db2Debug2("SQLBindCol(%d,%d,%d(%s),%x,%ld,%x)",session->stmtp->hsql,res->resnum, fparamType, param2name(fparamType), res->val, res->val_size, &res->val_null); + rc = SQLBindCol (session->stmtp->hsql,res->resnum, fparamType, res->val, res->val_size, (SQLLEN*) &res->val_null); + rc = db2CheckErr(rc, session->stmtp->hsql, session->stmtp->type, __LINE__, __FILE__); + if (rc != SQL_SUCCESS) { + db2Error_d(FDW_UNABLE_TO_CREATE_EXECUTION, "error executing query: SQLBindCol failed to define result value", db2Message); + } } col_pos++; } diff --git a/source/db2_deparse.c b/source/db2_deparse.c index 6f844a6..5e736b6 100644 --- a/source/db2_deparse.c +++ b/source/db2_deparse.c @@ -1437,14 +1437,24 @@ static void deparseSelectSql(List *tlist, bool is_subquery, List **retrieved_att // For a join or upper relation the input tlist gives the list of columns required to be fetched from the foreign server. deparseExplicitTargetList(tlist, false, retrieved_attrs, context); } else { - // For a base relation fpinfo->attrs_used gives the list of columns* required to be fetched from the foreign server. - RangeTblEntry *rte = planner_rt_fetch(foreignrel->relid, root); + /* + * For base relations, prefer the caller-provided tlist (fdw_scan_tlist) when + * present. This ensures the remote SELECT list includes any resjunk Vars + * needed locally (e.g., for EPQ recheck quals), avoiding result-column list + * mismatches. + */ + if (tlist != NIL) { + deparseExplicitTargetList(tlist, false, retrieved_attrs, context); + } else { + // Fallback: use fpinfo->attrs_used. + RangeTblEntry *rte = planner_rt_fetch(foreignrel->relid, root); - // Core code already has some lock on each rel being planned, so we can use NoLock here. - Relation rel = table_open(rte->relid, NoLock); + // Core code already has some lock on each rel being planned, so we can use NoLock here. + Relation rel = table_open(rte->relid, NoLock); - deparseTargetList(buf, rte, foreignrel->relid, rel, false, fpinfo->attrs_used, false, retrieved_attrs); - table_close(rel, NoLock); + deparseTargetList(buf, rte, foreignrel->relid, rel, false, fpinfo->attrs_used, false, retrieved_attrs); + table_close(rel, NoLock); + } } db2Exit1(": %s", buf->data); } diff --git a/source/db2_fdw_utils.c b/source/db2_fdw_utils.c index 315e81e..839b28d 100644 --- a/source/db2_fdw_utils.c +++ b/source/db2_fdw_utils.c @@ -221,8 +221,8 @@ void convertTuple (DB2Session* session, DB2Table* db2Table, DB2ResultColumn* res db2Debug5("res->pgtype : %d" ,res->pgtype ); db2Debug5("res->pgtypmod : %d" ,res->pgtypmod); db2Debug5("res->val : %s" ,res->val ); - db2Debug5("res->val_len : %d" ,res->val_len ); - db2Debug5("res->val_null : %d" ,res->val_null); + db2Debug5("res->val_len : %ld" ,(long) res->val_len ); + db2Debug5("res->val_null : %ld" ,(long) res->val_null); if (res->val_null >= 0) { short db2Type = 0; diff --git a/source/db2_utils.c b/source/db2_utils.c index bc4a61b..5a67024 100644 --- a/source/db2_utils.c +++ b/source/db2_utils.c @@ -8,7 +8,6 @@ char* param2name (SQLSMALLINT fparamType); SQLSMALLINT param2c (SQLSMALLINT fcType); short c2dbType (short fcType); char* c2name (short fcType); -void parse2num_struct (const char* s, SQL_NUMERIC_STRUCT* ns); /** c2param * Find db2's c-Type (SQL_) from a fParamType (SQL_C_). @@ -94,71 +93,6 @@ SQLSMALLINT c2param (SQLSMALLINT fcType) { return fparamType; } -/** parse2num_struct - * Parsing a string containing a numeric value to a NUM_STRUCT - * to be used in a query. - */ -void parse2num_struct(const char* s, SQL_NUMERIC_STRUCT* ns) { - const char* dot = strstr(s,"."); - unsigned long long mag = 0; - long long fracPart = 0; - long long scaled = 0; - long int intPart = 0; - int negative = 0; - int fracLen = 0; - db2Entry4("(s: %s)",s); - // Simple, minimal parser: handles optional leading '-' and '.'; no thousands sep. - memset(ns, 0, sizeof(*ns)); - ns->precision = 18; // set to your target - ns->scale = 3; // e.g., DECIMAL(18,3) - - if (*s == '-') { negative = 1; s++; } - - // split integer.fraction - - if (dot) { - // integer - for (const char* p = s; p < dot; ++p) { - if (!isdigit((unsigned char)*p)) - abort(); - intPart = intPart*10 + (*p - '0'); - } - // fraction (trim/round to scale as needed) - for (const char* p = dot+1; *p && fracLen < ns->scale; ++p, ++fracLen) { - if (!isdigit((unsigned char)*p)) - abort(); - fracPart = fracPart*10 + (*p - '0'); - } - // pad fraction if shorter than scale - for (; fracLen < ns->scale; ++fracLen) - fracPart *= 10; - } else { - for (const char* p = s; *p; ++p) { - if (!isdigit((unsigned char)*p)) - abort(); - intPart = intPart*10 + (*p - '0'); - } - } - - // combine into scaled integer: value = intPart * 10^scale + fracPart - scaled = intPart; - for (int i = 0; i < ns->scale; ++i) - scaled *= 10; - scaled += fracPart; - if (negative) - scaled = -scaled; - ns->sign = (scaled < 0) ? 0 : 1; // per ODBC: 1 = positive, 0 = negative - mag = (scaled < 0) ? (unsigned long long)(-scaled) : (unsigned long long)scaled; - - // Fill little-endian 16-byte bcd-ish buffer; DB2 reads the integer bytes. - // Store as binary integer magnitude; DB2 accepts this layout for SQL_NUMERIC_STRUCT. - for (int i = 0; i < SQL_MAX_NUMERIC_LEN; ++i) { - ns->val[i] = (SQLCHAR)(mag & 0xFF); - mag >>= 8; - } - db2Exit4(); -} - /** c2dbType * Map a fcType to the fdw internal value representation. * This is required for all functions that cannot use sqlcli1.h diff --git a/test/expected/base.out b/test/expected/base.out index 485d23e..5d05218 100644 --- a/test/expected/base.out +++ b/test/expected/base.out @@ -1,96 +1,98 @@ -CREATE DATABASE regtest; +\pset null '[NULL]' +Null display is "[NULL]". +\i ./test/sql/tccdb.sql +SELECT 'CREATE DATABASE regtest' +WHERE NOT EXISTS ( + SELECT FROM pg_database + WHERE datname = 'regtest' +)\gexec +CREATE DATABASE regtest CREATE DATABASE GRANT ALL PRIVILEGES ON DATABASE regtest to postgres; GRANT \c regtest You are now connected to database "regtest" as user "postgres". +\i ./test/sql/tcfdw.sql -- Install extension CREATE EXTENSION IF NOT EXISTS db2_fdw; CREATE EXTENSION +select db2_diag(); + db2_diag +------------------------------------------------------------------------------------------------------------------------- + db2_fdw 18.2.0, PostgreSQL 18.1, DB2INSTANCE=postgres, DB2_HOME=/var/lib/pgsql/sqllib, DB2LIB=/var/lib/pgsql/sqllib/lib +(1 row) + -- Install FDW Server CREATE SERVER IF NOT EXISTS sample FOREIGN DATA WRAPPER db2_fdw OPTIONS (dbserver 'SAMPLE'); CREATE SERVER -- Map a user CREATE USER MAPPING FOR PUBLIC SERVER sample OPTIONS (user 'db2inst1', password 'db2inst1'); CREATE USER MAPPING --- CREATE USER MAPPING FOR PUBLIC SERVER sample OPTIONS (user '', password ''); +\i ./test/sql/tcstart.sql -- Prepare a local schema CREATE SCHEMA IF NOT EXISTS sample; CREATE SCHEMA -- Import the complete sample db into the local schema IMPORT FOREIGN SCHEMA "DB2INST1" FROM SERVER sample INTO sample; IMPORT FOREIGN SCHEMA --- For UPDATE/DELETE, db2_fdw requires at least one primary key column marked --- with the column option "key". --- The DB2 SAMPLE.ORG table uses DEPTNUMB as its primary key. -ALTER FOREIGN TABLE sample.org - ALTER COLUMN deptnumb OPTIONS (ADD key 'true'); -ALTER FOREIGN TABLE -- list imported tables \detr+ sample.* List of foreign tables Schema | Table | Server | FDW options | Description --------+-------------------+--------+--------------------------------------------------+------------- - sample | act | sample | (schema 'DB2INST1', "table" 'ACT') | - sample | bintypes | sample | (schema 'DB2INST1', "table" 'BINTYPES') | - sample | catalog | sample | (schema 'DB2INST1', "table" 'CATALOG') | - sample | cl_sched | sample | (schema 'DB2INST1', "table" 'CL_SCHED') | - sample | customer | sample | (schema 'DB2INST1', "table" 'CUSTOMER') | - sample | department | sample | (schema 'DB2INST1', "table" 'DEPARTMENT') | - sample | emp_photo | sample | (schema 'DB2INST1', "table" 'EMP_PHOTO') | - sample | emp_resume | sample | (schema 'DB2INST1', "table" 'EMP_RESUME') | - sample | employee | sample | (schema 'DB2INST1', "table" 'EMPLOYEE') | - sample | empmdc | sample | (schema 'DB2INST1', "table" 'EMPMDC') | - sample | empprojact | sample | (schema 'DB2INST1', "table" 'EMPPROJACT') | - sample | in_tray | sample | (schema 'DB2INST1', "table" 'IN_TRAY') | - sample | inventory | sample | (schema 'DB2INST1', "table" 'INVENTORY') | - sample | iot_gre_space | sample | (schema 'DB2INST1', "table" 'IOT_GRE_SPACE') | - sample | numerictypes | sample | (schema 'DB2INST1', "table" 'NUMERICTYPES') | - sample | org | sample | (schema 'DB2INST1', "table" 'ORG') | - sample | product | sample | (schema 'DB2INST1', "table" 'PRODUCT') | - sample | productsupplier | sample | (schema 'DB2INST1', "table" 'PRODUCTSUPPLIER') | - sample | projact | sample | (schema 'DB2INST1', "table" 'PROJACT') | - sample | project | sample | (schema 'DB2INST1', "table" 'PROJECT') | - sample | purchaseorder | sample | (schema 'DB2INST1', "table" 'PURCHASEORDER') | - sample | remotetable | sample | (schema 'DB2INST1', "table" 'REMOTETABLE') | - sample | sales | sample | (schema 'DB2INST1', "table" 'SALES') | - sample | staff | sample | (schema 'DB2INST1', "table" 'STAFF') | - sample | staffg | sample | (schema 'DB2INST1', "table" 'STAFFG') | - sample | suppliers | sample | (schema 'DB2INST1', "table" 'SUPPLIERS') | - sample | test_hidden | sample | (schema 'DB2INST1', "table" 'TEST_HIDDEN') | - sample | testbigint | sample | (schema 'DB2INST1', "table" 'TESTBIGINT') | - sample | timetypes | sample | (schema 'DB2INST1', "table" 'TIMETYPES') | - sample | tm2acct | sample | (schema 'DB2INST1', "table" 'TM2ACCT') | - sample | uaci_rtdeployment | sample | (schema 'DB2INST1', "table" 'UACI_RTDEPLOYMENT') | - sample | vact | sample | (schema 'DB2INST1', "table" 'VACT') | - sample | vastrde1 | sample | (schema 'DB2INST1', "table" 'VASTRDE1') | - sample | vastrde2 | sample | (schema 'DB2INST1', "table" 'VASTRDE2') | - sample | vdepmg1 | sample | (schema 'DB2INST1', "table" 'VDEPMG1') | - sample | vdept | sample | (schema 'DB2INST1', "table" 'VDEPT') | - sample | vemp | sample | (schema 'DB2INST1', "table" 'VEMP') | - sample | vempdpt1 | sample | (schema 'DB2INST1', "table" 'VEMPDPT1') | - sample | vemplp | sample | (schema 'DB2INST1', "table" 'VEMPLP') | - sample | vempprojact | sample | (schema 'DB2INST1', "table" 'VEMPPROJACT') | - sample | vforpla | sample | (schema 'DB2INST1', "table" 'VFORPLA') | - sample | vhdept | sample | (schema 'DB2INST1', "table" 'VHDEPT') | - sample | vm2acct | sample | (schema 'DB2INST1', "table" 'VM2ACCT') | - sample | vphone | sample | (schema 'DB2INST1', "table" 'VPHONE') | - sample | vproj | sample | (schema 'DB2INST1', "table" 'VPROJ') | - sample | vprojact | sample | (schema 'DB2INST1', "table" 'VPROJACT') | - sample | vprojre1 | sample | (schema 'DB2INST1', "table" 'VPROJRE1') | - sample | vpstrde1 | sample | (schema 'DB2INST1', "table" 'VPSTRDE1') | - sample | vpstrde2 | sample | (schema 'DB2INST1', "table" 'VPSTRDE2') | - sample | vstafac1 | sample | (schema 'DB2INST1', "table" 'VSTAFAC1') | - sample | vstafac2 | sample | (schema 'DB2INST1', "table" 'VSTAFAC2') | + sample | act | sample | (schema 'DB2INST1', "table" 'ACT') | [NULL] + sample | bintypes | sample | (schema 'DB2INST1', "table" 'BINTYPES') | [NULL] + sample | catalog | sample | (schema 'DB2INST1', "table" 'CATALOG') | [NULL] + sample | cl_sched | sample | (schema 'DB2INST1', "table" 'CL_SCHED') | [NULL] + sample | customer | sample | (schema 'DB2INST1', "table" 'CUSTOMER') | [NULL] + sample | department | sample | (schema 'DB2INST1', "table" 'DEPARTMENT') | [NULL] + sample | emp_photo | sample | (schema 'DB2INST1', "table" 'EMP_PHOTO') | [NULL] + sample | emp_resume | sample | (schema 'DB2INST1', "table" 'EMP_RESUME') | [NULL] + sample | employee | sample | (schema 'DB2INST1', "table" 'EMPLOYEE') | [NULL] + sample | empmdc | sample | (schema 'DB2INST1', "table" 'EMPMDC') | [NULL] + sample | empprojact | sample | (schema 'DB2INST1', "table" 'EMPPROJACT') | [NULL] + sample | in_tray | sample | (schema 'DB2INST1', "table" 'IN_TRAY') | [NULL] + sample | inventory | sample | (schema 'DB2INST1', "table" 'INVENTORY') | [NULL] + sample | iot_gre_space | sample | (schema 'DB2INST1', "table" 'IOT_GRE_SPACE') | [NULL] + sample | numerictypes | sample | (schema 'DB2INST1', "table" 'NUMERICTYPES') | [NULL] + sample | org | sample | (schema 'DB2INST1', "table" 'ORG') | [NULL] + sample | product | sample | (schema 'DB2INST1', "table" 'PRODUCT') | [NULL] + sample | productsupplier | sample | (schema 'DB2INST1', "table" 'PRODUCTSUPPLIER') | [NULL] + sample | projact | sample | (schema 'DB2INST1', "table" 'PROJACT') | [NULL] + sample | project | sample | (schema 'DB2INST1', "table" 'PROJECT') | [NULL] + sample | purchaseorder | sample | (schema 'DB2INST1', "table" 'PURCHASEORDER') | [NULL] + sample | remotetable | sample | (schema 'DB2INST1', "table" 'REMOTETABLE') | [NULL] + sample | sales | sample | (schema 'DB2INST1', "table" 'SALES') | [NULL] + sample | staff | sample | (schema 'DB2INST1', "table" 'STAFF') | [NULL] + sample | staffg | sample | (schema 'DB2INST1', "table" 'STAFFG') | [NULL] + sample | suppliers | sample | (schema 'DB2INST1', "table" 'SUPPLIERS') | [NULL] + sample | test_hidden | sample | (schema 'DB2INST1', "table" 'TEST_HIDDEN') | [NULL] + sample | testbigint | sample | (schema 'DB2INST1', "table" 'TESTBIGINT') | [NULL] + sample | timetypes | sample | (schema 'DB2INST1', "table" 'TIMETYPES') | [NULL] + sample | tm2acct | sample | (schema 'DB2INST1', "table" 'TM2ACCT') | [NULL] + sample | uaci_rtdeployment | sample | (schema 'DB2INST1', "table" 'UACI_RTDEPLOYMENT') | [NULL] + sample | vact | sample | (schema 'DB2INST1', "table" 'VACT') | [NULL] + sample | vastrde1 | sample | (schema 'DB2INST1', "table" 'VASTRDE1') | [NULL] + sample | vastrde2 | sample | (schema 'DB2INST1', "table" 'VASTRDE2') | [NULL] + sample | vdepmg1 | sample | (schema 'DB2INST1', "table" 'VDEPMG1') | [NULL] + sample | vdept | sample | (schema 'DB2INST1', "table" 'VDEPT') | [NULL] + sample | vemp | sample | (schema 'DB2INST1', "table" 'VEMP') | [NULL] + sample | vempdpt1 | sample | (schema 'DB2INST1', "table" 'VEMPDPT1') | [NULL] + sample | vemplp | sample | (schema 'DB2INST1', "table" 'VEMPLP') | [NULL] + sample | vempprojact | sample | (schema 'DB2INST1', "table" 'VEMPPROJACT') | [NULL] + sample | vforpla | sample | (schema 'DB2INST1', "table" 'VFORPLA') | [NULL] + sample | vhdept | sample | (schema 'DB2INST1', "table" 'VHDEPT') | [NULL] + sample | vm2acct | sample | (schema 'DB2INST1', "table" 'VM2ACCT') | [NULL] + sample | vphone | sample | (schema 'DB2INST1', "table" 'VPHONE') | [NULL] + sample | vproj | sample | (schema 'DB2INST1', "table" 'VPROJ') | [NULL] + sample | vprojact | sample | (schema 'DB2INST1', "table" 'VPROJACT') | [NULL] + sample | vprojre1 | sample | (schema 'DB2INST1', "table" 'VPROJRE1') | [NULL] + sample | vpstrde1 | sample | (schema 'DB2INST1', "table" 'VPSTRDE1') | [NULL] + sample | vpstrde2 | sample | (schema 'DB2INST1', "table" 'VPSTRDE2') | [NULL] + sample | vstafac1 | sample | (schema 'DB2INST1', "table" 'VSTAFAC1') | [NULL] + sample | vstafac2 | sample | (schema 'DB2INST1', "table" 'VSTAFAC2') | [NULL] (51 rows) --- -select db2_diag(); - db2_diag -------------------------------------------------------------------------------------------------------------------------- - db2_fdw 18.2.0, PostgreSQL 18.1, DB2INSTANCE=postgres, DB2_HOME=/var/lib/pgsql/sqllib, DB2LIB=/var/lib/pgsql/sqllib/lib -(1 row) - set log_min_messages=debug5; SET -- starting testcases @@ -101,14 +103,14 @@ SET -- -- drop an imported table \d+ sample.org; - Foreign table "sample.org" - Column | Type | Collation | Nullable | Default | FDW options | Storage | Stats target | Description -----------+-----------------------+-----------+----------+---------+-------------------------------------------------------------------------------------------------------------+----------+--------------+------------- - deptnumb | smallint | | not null | | (db2type '5', db2size '5', db2bytes '2', db2chars '5', db2scale '0', db2null '0', db2ccsid '0', key 'true') | plain | | - deptname | character varying(14) | | | | (db2type '12', db2size '14', db2bytes '14', db2chars '0', db2scale '0', db2null '1', db2ccsid '1208') | extended | | - manager | smallint | | | | (db2type '5', db2size '5', db2bytes '2', db2chars '5', db2scale '0', db2null '1', db2ccsid '0') | plain | | - division | character varying(10) | | | | (db2type '12', db2size '10', db2bytes '10', db2chars '0', db2scale '0', db2null '1', db2ccsid '1208') | extended | | - location | character varying(13) | | | | (db2type '12', db2size '13', db2bytes '13', db2chars '0', db2scale '0', db2null '1', db2ccsid '1208') | extended | | + Foreign table "sample.org" + Column | Type | Collation | Nullable | Default | FDW options | Storage | Stats target | Description +----------+-----------------------+-----------+----------+---------+-------------------------------------------------------------------------------------------------------+----------+--------------+------------- + deptnumb | smallint | | not null | | (db2type '5', db2size '5', db2bytes '2', db2chars '5', db2scale '0', db2null '0', db2ccsid '0') | plain | | + deptname | character varying(14) | | | | (db2type '12', db2size '14', db2bytes '14', db2chars '0', db2scale '0', db2null '1', db2ccsid '1208') | extended | | + manager | smallint | | | | (db2type '5', db2size '5', db2bytes '2', db2chars '5', db2scale '0', db2null '1', db2ccsid '0') | plain | | + division | character varying(10) | | | | (db2type '12', db2size '10', db2bytes '10', db2chars '0', db2scale '0', db2null '1', db2ccsid '1208') | extended | | + location | character varying(13) | | | | (db2type '12', db2size '13', db2bytes '13', db2chars '0', db2scale '0', db2null '1', db2ccsid '1208') | extended | | Not-null constraints: "org_deptnumb_not_null" NOT NULL "deptnumb" Server: sample @@ -147,7 +149,7 @@ FDW options: (schema 'DB2INST1', "table" 'ORG') -- -- remove its content delete from sample.org; -DELETE 9 +DELETE 8 SELECT * FROM sample.org; deptnumb | deptname | manager | division | location ----------+----------+---------+----------+---------- @@ -323,50 +325,147 @@ Server: sample FDW options: (schema 'DB2INST1', "table" 'SALES') -- test a simple join +explain (analyze,verbose) select * from sample.employee a, sample.sales b where a.lastname = b.sales_person; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan (cost=200.00..424.60 rows=928 width=362) (actual time=4.438..34.250 rows=41.00 loops=1) + Output: a.empno, a.firstnme, a.midinit, a.lastname, a.workdept, a.phoneno, a.hiredate, a.job, a.edlevel, a.sex, a.birthdate, a.salary, a.bonus, a.comm, b.sales_date, b.sales_person, b.region, b.sales + DB2 query: SELECT r1."EMPNO", r1."FIRSTNME", r1."MIDINIT", r1."LASTNAME", r1."WORKDEPT", r1."PHONENO", r1."HIREDATE", r1."JOB", r1."EDLEVEL", r1."SEX", r1."BIRTHDATE", r1."SALARY", r1."BONUS", r1."COMM", r2."SALES_DATE", r2."SALES_PERSON", r2."REGION", r2."SALES" FROM ("DB2INST1"."EMPLOYEE" r1 INNER JOIN "DB2INST1"."SALES" r2 ON (((r1."LASTNAME" = r2."SALES_PERSON")))) + DB2 plan: + DB2 plan: DB2 Universal Database Version 11.5, 5622-044 (c) Copyright IBM Corp. 1991, 2019 + DB2 plan: Licensed Material - Program Property of IBM + DB2 plan: IBM DB2 Universal Database SQL and XQUERY Explain Tool + DB2 plan: + DB2 plan: DB2 Universal Database Version 12.1, 5622-044 (c) Copyright IBM Corp. 1991, 2022 + DB2 plan: Licensed Material - Program Property of IBM + DB2 plan: IBM DB2 Universal Database SQL and XQUERY Explain Tool + DB2 plan: + DB2 plan: ******************** DYNAMIC *************************************** + DB2 plan: + DB2 plan: ==================== STATEMENT ========================================== + DB2 plan: + DB2 plan: Isolation Level = Cursor Stability + DB2 plan: Blocking = Block Unambiguous Cursors + DB2 plan: Query Optimization Class = 5 + DB2 plan: + DB2 plan: Partition Parallel = No + DB2 plan: Intra-Partition Parallel = No + DB2 plan: + DB2 plan: SQL Path = "SYSIBM", "SYSFUN", "SYSPROC", "SYSIBMADM", + DB2 plan: "SYSHADOOP", "DB2INST1" + DB2 plan: + DB2 plan: + DB2 plan: Statement: + DB2 plan: + DB2 plan: SELECT r1."EMPNO" , r1."FIRSTNME" , r1."MIDINIT" , r1."LASTNAME" , + DB2 plan: r1."WORKDEPT" , r1."PHONENO" , r1."HIREDATE" , r1."JOB" , + DB2 plan: r1."EDLEVEL" , r1."SEX" , r1."BIRTHDATE" , r1."SALARY" , r1. + DB2 plan: "BONUS" , r1."COMM" , r2."SALES_DATE" , r2."SALES_PERSON" , + DB2 plan: r2."REGION" , r2."SALES" + DB2 plan: FROM ("DB2INST1" ."EMPLOYEE" r1 INNER JOIN "DB2INST1" ."SALES" r2 + DB2 plan: ON (((r1."LASTNAME" =r2."SALES_PERSON" )))) + DB2 plan: + DB2 plan: + DB2 plan: compiledInTenantID = 0 + DB2 plan: + DB2 plan: Section Code Page = 1208 + DB2 plan: + DB2 plan: Estimated Cost = 13,629282 + DB2 plan: Estimated Cardinality = 41,999996 + DB2 plan: + DB2 plan: Access Table Name = DB2INST1.SALES ID = 2,16 + DB2 plan: | tenantID = 0 + DB2 plan: | #Columns = 4 + DB2 plan: | Skip Inserted Rows + DB2 plan: | Avoid Locking Committed Data + DB2 plan: | Currently Committed for Cursor Stability + DB2 plan: | May participate in Scan Sharing structures + DB2 plan: | Scan may start anywhere and wrap, for completion + DB2 plan: | Fast scan, for purposes of scan sharing management + DB2 plan: | Scan can be throttled in scan sharing management + DB2 plan: | Relation Scan + DB2 plan: | | Prefetch: Eligible + DB2 plan: | Lock Intents + DB2 plan: | | Table: Intent Share + DB2 plan: | | Row : Next Key Share + DB2 plan: | Sargable Predicate(s) + DB2 plan: | | Process Build Table for Hash Join + DB2 plan: Hash Join + DB2 plan: | Estimated Build Size: 4000 + DB2 plan: | Estimated Probe Size: 8000 + DB2 plan: | Access Table Name = DB2INST1.EMPLOYEE ID = 2,6 + DB2 plan: | | tenantID = 0 + DB2 plan: | | #Columns = 14 + DB2 plan: | | Skip Inserted Rows + DB2 plan: | | Avoid Locking Committed Data + DB2 plan: | | Currently Committed for Cursor Stability + DB2 plan: | | May participate in Scan Sharing structures + DB2 plan: | | Scan may start anywhere and wrap, for completion + DB2 plan: | | Fast scan, for purposes of scan sharing management + DB2 plan: | | Scan can be throttled in scan sharing management + DB2 plan: | | Relation Scan + DB2 plan: | | | Prefetch: Eligible + DB2 plan: | | Lock Intents + DB2 plan: | | | Table: Intent Share + DB2 plan: | | | Row : Next Key Share + DB2 plan: | | Sargable Predicate(s) + DB2 plan: | | | Process Probe Table for Hash Join + DB2 plan: Return Data to Application + DB2 plan: | #Columns = 18 + DB2 plan: + DB2 plan: End of section + DB2 plan: + DB2 plan: + Planning: + Buffers: shared hit=106 + Planning Time: 5.038 ms + Execution Time: 36.439 ms +(92 rows) + select * from sample.employee a, sample.sales b where a.lastname = b.sales_person; - empno | firstnme | midinit | lastname | workdept | phoneno | hiredate | job | edlevel | sex | birthdate | salary | bonus | comm | sales_date | sales_person | region | sales ---------+----------+---------+-----------+----------+---------+------------+----------+---------+-----+------------+----------+--------+---------+------------+--------------+---------------+------- - 000110 | VINCENZO | G | LUCCHESSI | A00 | 3490 | 1988-05-16 | SALESREP | 19 | M | 1959-11-05 | 66500.00 | 900.00 | 3720.00 | 2005-12-31 | LUCCHESSI | Ontario-South | 1 - 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2005-12-31 | LEE | Ontario-South | 3 - 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2005-12-31 | LEE | Quebec | 1 - 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2005-12-31 | LEE | Manitoba | 2 - 000340 | JASON | R | GOUNOT | E21 | 5698 | 1977-05-05 | FIELDREP | 16 | M | 1956-05-17 | 43840.00 | 500.00 | 1907.00 | 2005-12-31 | GOUNOT | Quebec | 1 - 000110 | VINCENZO | G | LUCCHESSI | A00 | 3490 | 1988-05-16 | SALESREP | 19 | M | 1959-11-05 | 66500.00 | 900.00 | 3720.00 | 2006-03-29 | LUCCHESSI | Ontario-South | 3 - 000110 | VINCENZO | G | LUCCHESSI | A00 | 3490 | 1988-05-16 | SALESREP | 19 | M | 1959-11-05 | 66500.00 | 900.00 | 3720.00 | 2006-03-29 | LUCCHESSI | Quebec | 1 - 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-03-29 | LEE | Ontario-South | 2 - 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 1996-03-29 | LEE | Ontario-North | 2 - 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-03-29 | LEE | Quebec | 3 - 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-03-29 | LEE | Manitoba | 5 - 000340 | JASON | R | GOUNOT | E21 | 5698 | 1977-05-05 | FIELDREP | 16 | M | 1956-05-17 | 43840.00 | 500.00 | 1907.00 | 2006-03-29 | GOUNOT | Ontario-South | 3 - 000340 | JASON | R | GOUNOT | E21 | 5698 | 1977-05-05 | FIELDREP | 16 | M | 1956-05-17 | 43840.00 | 500.00 | 1907.00 | 2006-03-29 | GOUNOT | Quebec | 1 - 000340 | JASON | R | GOUNOT | E21 | 5698 | 1977-05-05 | FIELDREP | 16 | M | 1956-05-17 | 43840.00 | 500.00 | 1907.00 | 2006-03-29 | GOUNOT | Manitoba | 7 - 000110 | VINCENZO | G | LUCCHESSI | A00 | 3490 | 1988-05-16 | SALESREP | 19 | M | 1959-11-05 | 66500.00 | 900.00 | 3720.00 | 2006-03-30 | LUCCHESSI | Ontario-South | 1 - 000110 | VINCENZO | G | LUCCHESSI | A00 | 3490 | 1988-05-16 | SALESREP | 19 | M | 1959-11-05 | 66500.00 | 900.00 | 3720.00 | 2006-03-30 | LUCCHESSI | Quebec | 2 - 000110 | VINCENZO | G | LUCCHESSI | A00 | 3490 | 1988-05-16 | SALESREP | 19 | M | 1959-11-05 | 66500.00 | 900.00 | 3720.00 | 2006-03-30 | LUCCHESSI | Manitoba | 1 - 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-03-30 | LEE | Ontario-South | 7 - 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-03-30 | LEE | Ontario-North | 3 - 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-03-30 | LEE | Quebec | 7 - 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-03-30 | LEE | Manitoba | 4 - 000340 | JASON | R | GOUNOT | E21 | 5698 | 1977-05-05 | FIELDREP | 16 | M | 1956-05-17 | 43840.00 | 500.00 | 1907.00 | 2006-03-30 | GOUNOT | Ontario-South | 2 - 000340 | JASON | R | GOUNOT | E21 | 5698 | 1977-05-05 | FIELDREP | 16 | M | 1956-05-17 | 43840.00 | 500.00 | 1907.00 | 2006-03-30 | GOUNOT | Quebec | 18 - 000340 | JASON | R | GOUNOT | E21 | 5698 | 1977-05-05 | FIELDREP | 16 | M | 1956-05-17 | 43840.00 | 500.00 | 1907.00 | 2006-03-31 | GOUNOT | Manitoba | 1 - 000110 | VINCENZO | G | LUCCHESSI | A00 | 3490 | 1988-05-16 | SALESREP | 19 | M | 1959-11-05 | 66500.00 | 900.00 | 3720.00 | 2006-03-31 | LUCCHESSI | Manitoba | 1 - 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-03-31 | LEE | Ontario-South | 14 - 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-03-31 | LEE | Ontario-North | 3 - 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-03-31 | LEE | Quebec | 7 - 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-03-31 | LEE | Manitoba | 3 - 000340 | JASON | R | GOUNOT | E21 | 5698 | 1977-05-05 | FIELDREP | 16 | M | 1956-05-17 | 43840.00 | 500.00 | 1907.00 | 2006-03-31 | GOUNOT | Ontario-South | 2 - 000340 | JASON | R | GOUNOT | E21 | 5698 | 1977-05-05 | FIELDREP | 16 | M | 1956-05-17 | 43840.00 | 500.00 | 1907.00 | 2006-03-31 | GOUNOT | Quebec | 1 - 000110 | VINCENZO | G | LUCCHESSI | A00 | 3490 | 1988-05-16 | SALESREP | 19 | M | 1959-11-05 | 66500.00 | 900.00 | 3720.00 | 2006-04-01 | LUCCHESSI | Ontario-South | 3 - 000110 | VINCENZO | G | LUCCHESSI | A00 | 3490 | 1988-05-16 | SALESREP | 19 | M | 1959-11-05 | 66500.00 | 900.00 | 3720.00 | 2006-04-01 | LUCCHESSI | Manitoba | 1 - 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-04-01 | LEE | Ontario-South | 8 - 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-04-01 | LEE | Ontario-North | - 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-04-01 | LEE | Quebec | 8 - 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-04-01 | LEE | Manitoba | 9 - 000340 | JASON | R | GOUNOT | E21 | 5698 | 1977-05-05 | FIELDREP | 16 | M | 1956-05-17 | 43840.00 | 500.00 | 1907.00 | 2006-04-01 | GOUNOT | Ontario-South | 3 - 000340 | JASON | R | GOUNOT | E21 | 5698 | 1977-05-05 | FIELDREP | 16 | M | 1956-05-17 | 43840.00 | 500.00 | 1907.00 | 2006-04-01 | GOUNOT | Ontario-North | 1 - 000340 | JASON | R | GOUNOT | E21 | 5698 | 1977-05-05 | FIELDREP | 16 | M | 1956-05-17 | 43840.00 | 500.00 | 1907.00 | 2006-04-01 | GOUNOT | Quebec | 3 - 000340 | JASON | R | GOUNOT | E21 | 5698 | 1977-05-05 | FIELDREP | 16 | M | 1956-05-17 | 43840.00 | 500.00 | 1907.00 | 2006-04-01 | GOUNOT | Manitoba | 7 + empno | firstnme | midinit | lastname | workdept | phoneno | hiredate | job | edlevel | sex | birthdate | salary | bonus | comm | sales_date | sales_person | region | sales +--------+----------+---------+-----------+----------+---------+------------+----------+---------+-----+------------+----------+--------+---------+------------+--------------+---------------+-------- + 000110 | VINCENZO | G | LUCCHESSI | A00 | 3490 | 1988-05-16 | SALESREP | 19 | M | 1959-11-05 | 66500.00 | 900.00 | 3720.00 | 2005-12-31 | LUCCHESSI | Ontario-South | 1 + 000110 | VINCENZO | G | LUCCHESSI | A00 | 3490 | 1988-05-16 | SALESREP | 19 | M | 1959-11-05 | 66500.00 | 900.00 | 3720.00 | 2006-04-01 | LUCCHESSI | Manitoba | 1 + 000110 | VINCENZO | G | LUCCHESSI | A00 | 3490 | 1988-05-16 | SALESREP | 19 | M | 1959-11-05 | 66500.00 | 900.00 | 3720.00 | 2006-04-01 | LUCCHESSI | Ontario-South | 3 + 000110 | VINCENZO | G | LUCCHESSI | A00 | 3490 | 1988-05-16 | SALESREP | 19 | M | 1959-11-05 | 66500.00 | 900.00 | 3720.00 | 2006-03-31 | LUCCHESSI | Manitoba | 1 + 000110 | VINCENZO | G | LUCCHESSI | A00 | 3490 | 1988-05-16 | SALESREP | 19 | M | 1959-11-05 | 66500.00 | 900.00 | 3720.00 | 2006-03-30 | LUCCHESSI | Manitoba | 1 + 000110 | VINCENZO | G | LUCCHESSI | A00 | 3490 | 1988-05-16 | SALESREP | 19 | M | 1959-11-05 | 66500.00 | 900.00 | 3720.00 | 2006-03-30 | LUCCHESSI | Quebec | 2 + 000110 | VINCENZO | G | LUCCHESSI | A00 | 3490 | 1988-05-16 | SALESREP | 19 | M | 1959-11-05 | 66500.00 | 900.00 | 3720.00 | 2006-03-30 | LUCCHESSI | Ontario-South | 1 + 000110 | VINCENZO | G | LUCCHESSI | A00 | 3490 | 1988-05-16 | SALESREP | 19 | M | 1959-11-05 | 66500.00 | 900.00 | 3720.00 | 2006-03-29 | LUCCHESSI | Quebec | 1 + 000110 | VINCENZO | G | LUCCHESSI | A00 | 3490 | 1988-05-16 | SALESREP | 19 | M | 1959-11-05 | 66500.00 | 900.00 | 3720.00 | 2006-03-29 | LUCCHESSI | Ontario-South | 3 + 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2005-12-31 | LEE | Ontario-South | 3 + 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-04-01 | LEE | Manitoba | 9 + 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-04-01 | LEE | Quebec | 8 + 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-04-01 | LEE | Ontario-North | [NULL] + 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-04-01 | LEE | Ontario-South | 8 + 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-03-31 | LEE | Manitoba | 3 + 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-03-31 | LEE | Quebec | 7 + 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-03-31 | LEE | Ontario-North | 3 + 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-03-31 | LEE | Ontario-South | 14 + 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-03-30 | LEE | Manitoba | 4 + 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-03-30 | LEE | Quebec | 7 + 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-03-30 | LEE | Ontario-North | 3 + 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-03-30 | LEE | Ontario-South | 7 + 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-03-29 | LEE | Manitoba | 5 + 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-03-29 | LEE | Quebec | 3 + 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 1996-03-29 | LEE | Ontario-North | 2 + 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2006-03-29 | LEE | Ontario-South | 2 + 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2005-12-31 | LEE | Manitoba | 2 + 000330 | WING | | LEE | E21 | 2103 | 2006-02-23 | FIELDREP | 14 | M | 1971-07-18 | 45370.00 | 500.00 | 2030.00 | 2005-12-31 | LEE | Quebec | 1 + 000340 | JASON | R | GOUNOT | E21 | 5698 | 1977-05-05 | FIELDREP | 16 | M | 1956-05-17 | 43840.00 | 500.00 | 1907.00 | 2005-12-31 | GOUNOT | Quebec | 1 + 000340 | JASON | R | GOUNOT | E21 | 5698 | 1977-05-05 | FIELDREP | 16 | M | 1956-05-17 | 43840.00 | 500.00 | 1907.00 | 2006-04-01 | GOUNOT | Manitoba | 7 + 000340 | JASON | R | GOUNOT | E21 | 5698 | 1977-05-05 | FIELDREP | 16 | M | 1956-05-17 | 43840.00 | 500.00 | 1907.00 | 2006-04-01 | GOUNOT | Quebec | 3 + 000340 | JASON | R | GOUNOT | E21 | 5698 | 1977-05-05 | FIELDREP | 16 | M | 1956-05-17 | 43840.00 | 500.00 | 1907.00 | 2006-04-01 | GOUNOT | Ontario-North | 1 + 000340 | JASON | R | GOUNOT | E21 | 5698 | 1977-05-05 | FIELDREP | 16 | M | 1956-05-17 | 43840.00 | 500.00 | 1907.00 | 2006-04-01 | GOUNOT | Ontario-South | 3 + 000340 | JASON | R | GOUNOT | E21 | 5698 | 1977-05-05 | FIELDREP | 16 | M | 1956-05-17 | 43840.00 | 500.00 | 1907.00 | 2006-03-31 | GOUNOT | Quebec | 1 + 000340 | JASON | R | GOUNOT | E21 | 5698 | 1977-05-05 | FIELDREP | 16 | M | 1956-05-17 | 43840.00 | 500.00 | 1907.00 | 2006-03-31 | GOUNOT | Ontario-South | 2 + 000340 | JASON | R | GOUNOT | E21 | 5698 | 1977-05-05 | FIELDREP | 16 | M | 1956-05-17 | 43840.00 | 500.00 | 1907.00 | 2006-03-31 | GOUNOT | Manitoba | 1 + 000340 | JASON | R | GOUNOT | E21 | 5698 | 1977-05-05 | FIELDREP | 16 | M | 1956-05-17 | 43840.00 | 500.00 | 1907.00 | 2006-03-30 | GOUNOT | Quebec | 18 + 000340 | JASON | R | GOUNOT | E21 | 5698 | 1977-05-05 | FIELDREP | 16 | M | 1956-05-17 | 43840.00 | 500.00 | 1907.00 | 2006-03-30 | GOUNOT | Ontario-South | 2 + 000340 | JASON | R | GOUNOT | E21 | 5698 | 1977-05-05 | FIELDREP | 16 | M | 1956-05-17 | 43840.00 | 500.00 | 1907.00 | 2006-03-29 | GOUNOT | Manitoba | 7 + 000340 | JASON | R | GOUNOT | E21 | 5698 | 1977-05-05 | FIELDREP | 16 | M | 1956-05-17 | 43840.00 | 500.00 | 1907.00 | 2006-03-29 | GOUNOT | Quebec | 1 + 000340 | JASON | R | GOUNOT | E21 | 5698 | 1977-05-05 | FIELDREP | 16 | M | 1956-05-17 | 43840.00 | 500.00 | 1907.00 | 2006-03-29 | GOUNOT | Ontario-South | 3 (41 rows) -- @@ -403,6 +502,19 @@ FDW options: (schema 'DB2INST1', "table" 'ORG') location | character varying(13) | | | | extended | | | Access method: heap +select * from sample.orgcopy; + deptnumb | deptname | manager | division | location +----------+----------------+---------+-----------+--------------- + 10 | Head Office | 160 | Corporate | New York + 15 | New England | 50 | Eastern | Boston + 20 | Mid Atlantic | 10 | Eastern | Washington + 38 | South Atlantic | 30 | Eastern | Atlanta + 42 | Great Lakes | 100 | Midwest | Chicago + 51 | Plains | 140 | Midwest | Dallas + 66 | Pacific | 270 | Western | San Francisco + 84 | Mountain | 290 | Western | Denver +(8 rows) + drop table sample.orgcopy; DROP TABLE -- @@ -442,7 +554,7 @@ FDW options: (schema 'DB2INST1', "table" 'EMPLOYEE') explain (analyze,verbose) select min(salary),max(salary),avg(salary),sum(salary +comm + bonus),count(*) from sample.employee; QUERY PLAN -------------------------------------------------------------------------------------------------------------------------------------------- - Foreign Scan (cost=121.72..144.35 rows=1 width=136) (actual time=4.902..5.289 rows=1.00 loops=1) + Foreign Scan (cost=121.72..144.35 rows=1 width=136) (actual time=3.258..3.344 rows=1.00 loops=1) Output: (min(salary)), (max(salary)), (avg(salary)), (sum(((salary + comm) + bonus))), (count(*)) DB2 query: SELECT MIN("SALARY"), MAX("SALARY"), AVG("SALARY"), SUM((("SALARY" + "COMM") + "BONUS")), COUNT(*) FROM "DB2INST1"."EMPLOYEE" DB2 plan: @@ -511,8 +623,8 @@ explain (analyze,verbose) select min(salary),max(salary),avg(salary),sum(salary DB2 plan: Planning: Buffers: shared hit=20 - Planning Time: 10.731 ms - Execution Time: 8.132 ms + Planning Time: 4.241 ms + Execution Time: 4.535 ms (71 rows) select min(salary),max(salary),avg(salary),sum(salary +comm + bonus),count(*) from sample.employee; @@ -522,12 +634,12 @@ select min(salary),max(salary),avg(salary),sum(salary +comm + bonus),count(*) fr (1 row) -- -explain (analyze,verbose) select empno, firstnme,lastname, salary + bonus + comm from sample.employee where salary > 8000 and lastname like 'L%'; - QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - Foreign Scan on sample.employee (cost=100.00..116.89 rows=1 width=150) (actual time=3.637..4.420 rows=3.00 loops=1) +explain (analyze,verbose) select empno, firstnme,lastname, salary + bonus + comm from sample.employee where salary > 43840.01 and lastname like 'L%'; + QUERY PLAN +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on sample.employee (cost=100.00..116.89 rows=1 width=150) (actual time=2.729..3.355 rows=3.00 loops=1) Output: empno, firstnme, lastname, ((salary + bonus) + comm) - DB2 query: SELECT "EMPNO", "FIRSTNME", "LASTNAME", "SALARY", "BONUS", "COMM" FROM "DB2INST1"."EMPLOYEE" WHERE (("SALARY" > 8000)) AND (("LASTNAME" LIKE 'L%' ESCAPE '\')) + DB2 query: SELECT "EMPNO", "FIRSTNME", "LASTNAME", "SALARY", "BONUS", "COMM" FROM "DB2INST1"."EMPLOYEE" WHERE (("SALARY" > 43840.01)) AND (("LASTNAME" LIKE 'L%' ESCAPE '\')) DB2 plan: DB2 plan: DB2 Universal Database Version 11.5, 5622-044 (c) Copyright IBM Corp. 1991, 2019 DB2 plan: Licensed Material - Program Property of IBM @@ -557,15 +669,16 @@ explain (analyze,verbose) select empno, firstnme,lastname, salary + bonus + comm DB2 plan: SELECT "EMPNO" , "FIRSTNME" , "LASTNAME" , "SALARY" , "BONUS" , DB2 plan: "COMM" DB2 plan: FROM "DB2INST1" ."EMPLOYEE" - DB2 plan: WHERE (("SALARY" > 8000))AND (("LASTNAME" LIKE 'L%' ESCAPE '\' )) + DB2 plan: WHERE (("SALARY" > 43840.01))AND (("LASTNAME" LIKE 'L%' ESCAPE '\' ) + DB2 plan: ) DB2 plan: DB2 plan: DB2 plan: compiledInTenantID = 0 DB2 plan: DB2 plan: Section Code Page = 1208 DB2 plan: - DB2 plan: Estimated Cost = 6,816737 - DB2 plan: Estimated Cardinality = 3,714774 + DB2 plan: Estimated Cost = 6,816642 + DB2 plan: Estimated Cardinality = 2,627403 DB2 plan: DB2 plan: Access Table Name = DB2INST1.EMPLOYEE ID = 2,6 DB2 plan: | tenantID = 0 @@ -593,11 +706,11 @@ explain (analyze,verbose) select empno, firstnme,lastname, salary + bonus + comm DB2 plan: Planning: Buffers: shared hit=3 - Planning Time: 3.369 ms - Execution Time: 5.638 ms -(70 rows) + Planning Time: 3.236 ms + Execution Time: 4.581 ms +(71 rows) -select empno, firstnme,lastname, salary + bonus + comm from sample.employee where salary > 8000 and lastname like 'L%'; +select empno, firstnme,lastname, salary + bonus + comm from sample.employee where salary > 43840.01 and lastname like 'L%'; empno | firstnme | lastname | ?column? --------+----------+-----------+---------- 000110 | VINCENZO | LUCCHESSI | 71120.00 @@ -613,6 +726,12 @@ select empno, firstnme,lastname, salary + bonus + comm from sample.employee wher -- -- TC006: UPDATE/DELETE against a key column and revert to baseline -- +-- For UPDATE/DELETE, db2_fdw requires at least one primary key column marked +-- with the column option "key". +-- The DB2 SAMPLE.ORG table uses DEPTNUMB as its primary key. +ALTER FOREIGN TABLE sample.org + ALTER COLUMN deptnumb OPTIONS (ADD key 'true'); +psql:test/sql/tc006.sql:8: FEHLER: Option »key« mehrmals angegeben \d+ sample.org; Foreign table "sample.org" Column | Type | Collation | Nullable | Default | FDW options | Storage | Stats target | Description @@ -636,16 +755,16 @@ SELECT deptnumb, deptname, location FROM sample.org WHERE deptnumb = 10; -- UPDATE + verify UPDATE sample.org SET location = 'New York City' WHERE deptnumb = 10; -psql:test/sql/tc006.sql:10: FEHLER: pfree called with invalid pointer 0xc546050 (header 0x0000000000000002) +UPDATE 1 SELECT deptnumb, deptname, location FROM sample.org WHERE deptnumb = 10; - deptnumb | deptname | location -----------+-------------+---------- - 10 | Head Office | New York + deptnumb | deptname | location +----------+-------------+--------------- + 10 | Head Office | New York City (1 row) -- Revert + verify UPDATE sample.org SET location = 'New York' WHERE deptnumb = 10; -psql:test/sql/tc006.sql:14: FEHLER: pfree called with invalid pointer 0xc546050 (header 0x0000000000000002) +UPDATE 1 SELECT deptnumb, deptname, location FROM sample.org WHERE deptnumb = 10; deptnumb | deptname | location ----------+-------------+---------- @@ -660,12 +779,11 @@ SELECT * FROM sample.org WHERE deptnumb = 84; (1 row) DELETE FROM sample.org WHERE deptnumb = 84; -psql:test/sql/tc006.sql:19: FEHLER: pfree called with invalid pointer 0xc546050 (header 0x000000000c546018) +DELETE 1 SELECT * FROM sample.org WHERE deptnumb = 84; deptnumb | deptname | manager | division | location ----------+----------+---------+----------+---------- - 84 | Mountain | 290 | Western | Denver -(1 row) +(0 rows) INSERT INTO sample.org (deptnumb, deptname, manager, division, location) VALUES (84, 'Mountain', 290, 'Western', 'Denver'); @@ -674,8 +792,7 @@ SELECT * FROM sample.org WHERE deptnumb = 84; deptnumb | deptname | manager | division | location ----------+----------+---------+----------+---------- 84 | Mountain | 290 | Western | Denver - 84 | Mountain | 290 | Western | Denver -(2 rows) +(1 row) -- -- END of TC006 @@ -694,9 +811,13 @@ SELECT deptnumb, location FROM sample.org WHERE deptnumb = 15; BEGIN; BEGIN UPDATE sample.org SET location = 'Boston (temp)' WHERE deptnumb = 15; -psql:test/sql/tc007.sql:8: FEHLER: pfree called with invalid pointer 0xc546050 (header 0x0000000000000002) +UPDATE 1 SELECT deptnumb, location FROM sample.org WHERE deptnumb = 15; -psql:test/sql/tc007.sql:9: FEHLER: aktuelle Transaktion wurde abgebrochen, Befehle werden bis zum Ende der Transaktion ignoriert + deptnumb | location +----------+--------------- + 15 | Boston (temp) +(1 row) + ROLLBACK; ROLLBACK -- Should be back to original @@ -823,9 +944,96 @@ SELECT count(*) FROM sample.employee; -- TC010: Pushdown predicates (IN/BETWEEN/OR/IS NULL) -- EXPLAIN (analyze,verbose) SELECT empno, lastname, salary FROM sample.employee WHERE workdept IN ('A00','B01','E21') AND salary BETWEEN 40000 AND 70000 AND (lastname LIKE 'L%' OR lastname LIKE 'G%'); -psql:test/sql/tc010.sql:4: FEHLER: variable not found in subplan target list + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan on sample.employee (cost=100.00..127.26 rows=1 width=90) (actual time=4.343..5.054 rows=3.00 loops=1) + Output: empno, lastname, salary, workdept + DB2 query: SELECT "EMPNO", "LASTNAME", "SALARY", "WORKDEPT" FROM "DB2INST1"."EMPLOYEE" WHERE (("SALARY" >= 40000)) AND (("SALARY" <= 70000)) AND (("WORKDEPT" IN ('A00', 'B01', 'E21'))) AND ((("LASTNAME" LIKE 'L%' ESCAPE '\') OR ("LASTNAME" LIKE 'G%' ESCAPE '\'))) + DB2 plan: + DB2 plan: DB2 Universal Database Version 11.5, 5622-044 (c) Copyright IBM Corp. 1991, 2019 + DB2 plan: Licensed Material - Program Property of IBM + DB2 plan: IBM DB2 Universal Database SQL and XQUERY Explain Tool + DB2 plan: + DB2 plan: DB2 Universal Database Version 12.1, 5622-044 (c) Copyright IBM Corp. 1991, 2022 + DB2 plan: Licensed Material - Program Property of IBM + DB2 plan: IBM DB2 Universal Database SQL and XQUERY Explain Tool + DB2 plan: + DB2 plan: ******************** DYNAMIC *************************************** + DB2 plan: + DB2 plan: ==================== STATEMENT ========================================== + DB2 plan: + DB2 plan: Isolation Level = Cursor Stability + DB2 plan: Blocking = Block Unambiguous Cursors + DB2 plan: Query Optimization Class = 5 + DB2 plan: + DB2 plan: Partition Parallel = No + DB2 plan: Intra-Partition Parallel = No + DB2 plan: + DB2 plan: SQL Path = "SYSIBM", "SYSFUN", "SYSPROC", "SYSIBMADM", + DB2 plan: "SYSHADOOP", "DB2INST1" + DB2 plan: + DB2 plan: + DB2 plan: Statement: + DB2 plan: + DB2 plan: SELECT "EMPNO" , "LASTNAME" , "SALARY" , "WORKDEPT" + DB2 plan: FROM "DB2INST1" ."EMPLOYEE" + DB2 plan: WHERE (("SALARY" >=40000))AND (("SALARY" <=70000))AND (("WORKDEPT" + DB2 plan: IN ('A00' , 'B01' , 'E21' )))AND ((("LASTNAME" LIKE 'L%' + DB2 plan: ESCAPE '\' )OR ("LASTNAME" LIKE 'G%' ESCAPE '\' ))) + DB2 plan: + DB2 plan: + DB2 plan: compiledInTenantID = 0 + DB2 plan: + DB2 plan: Section Code Page = 1208 + DB2 plan: + DB2 plan: Estimated Cost = 6,816905 + DB2 plan: Estimated Cardinality = 1,019460 + DB2 plan: + DB2 plan: Table Constructor + DB2 plan: | 3-Row(s) + DB2 plan: Nested Loop Join + DB2 plan: | Access Table Name = DB2INST1.EMPLOYEE ID = 2,6 + DB2 plan: | | tenantID = 0 + DB2 plan: | | Index Scan: Name = DB2INST1.XEMP2 ID = 2 + DB2 plan: | | | Regular Index (Not Clustered) + DB2 plan: | | | Index Columns: + DB2 plan: | | | | 1: WORKDEPT (Ascending) + DB2 plan: | | #Columns = 4 + DB2 plan: | | Skip Inserted Rows + DB2 plan: | | Avoid Locking Committed Data + DB2 plan: | | Currently Committed for Cursor Stability + DB2 plan: | | Evaluate Predicates Before Locking for Key + DB2 plan: | | #Key Columns = 1 + DB2 plan: | | | Start Key: Inclusive Value + DB2 plan: | | | | 1: ? + DB2 plan: | | | Stop Key: Inclusive Value + DB2 plan: | | | | 1: ? + DB2 plan: | | Data Prefetch: Sequential(0), Readahead + DB2 plan: | | Index Prefetch: None + DB2 plan: | | Lock Intents + DB2 plan: | | | Table: Intent Share + DB2 plan: | | | Row : Next Key Share + DB2 plan: | | Sargable Predicate(s) + DB2 plan: | | | #Predicates = 4 + DB2 plan: | | | Return Data to Application + DB2 plan: | | | | #Columns = 4 + DB2 plan: Return Data Completion + DB2 plan: + DB2 plan: End of section + DB2 plan: + DB2 plan: + Planning Time: 8.318 ms + Execution Time: 6.574 ms +(78 rows) + SELECT empno, lastname, salary FROM sample.employee WHERE workdept IN ('A00','B01','E21') AND salary BETWEEN 40000 AND 70000 AND (lastname LIKE 'L%' OR lastname LIKE 'G%') ORDER BY empno; -psql:test/sql/tc010.sql:6: FEHLER: variable not found in subplan target list + empno | lastname | salary +--------+-----------+---------- + 000110 | LUCCHESSI | 66500.00 + 000330 | LEE | 45370.00 + 000340 | GOUNOT | 43840.00 +(3 rows) + -- NULL handling sanity check SELECT count(*) AS total, @@ -848,7 +1056,7 @@ FROM sample.employee; EXPLAIN (analyze,verbose) SELECT empno, lastname, salary FROM sample.employee LIMIT 5; QUERY PLAN --------------------------------------------------------------------------------------------------------------------- - Foreign Scan on sample.employee (cost=100.00..101.11 rows=5 width=90) (actual time=3.069..3.794 rows=5.00 loops=1) + Foreign Scan on sample.employee (cost=100.00..101.11 rows=5 width=90) (actual time=2.016..2.781 rows=5.00 loops=1) Output: empno, lastname, salary DB2 query: SELECT "EMPNO", "LASTNAME", "SALARY" FROM "DB2INST1"."EMPLOYEE" FETCH FIRST 5 ROWS ONLY DB2 plan: @@ -910,8 +1118,8 @@ EXPLAIN (analyze,verbose) SELECT empno, lastname, salary FROM sample.employee LI DB2 plan: End of section DB2 plan: DB2 plan: - Planning Time: 2.437 ms - Execution Time: 4.851 ms + Planning Time: 2.544 ms + Execution Time: 3.833 ms (64 rows) SELECT empno, lastname, salary FROM sample.employee LIMIT 5; @@ -927,7 +1135,7 @@ SELECT empno, lastname, salary FROM sample.employee LIMIT 5; EXPLAIN (analyze,verbose) SELECT empno, lastname, salary FROM sample.employee LIMIT 5 OFFSET 2; QUERY PLAN --------------------------------------------------------------------------------------------------------------------- - Foreign Scan on sample.employee (cost=100.05..101.16 rows=5 width=90) (actual time=3.593..4.494 rows=5.00 loops=1) + Foreign Scan on sample.employee (cost=100.05..101.16 rows=5 width=90) (actual time=2.590..3.293 rows=5.00 loops=1) Output: empno, lastname, salary DB2 query: SELECT "EMPNO", "LASTNAME", "SALARY" FROM "DB2INST1"."EMPLOYEE" OFFSET 2 ROWS FETCH NEXT 5 ROWS ONLY DB2 plan: @@ -991,8 +1199,8 @@ EXPLAIN (analyze,verbose) SELECT empno, lastname, salary FROM sample.employee LI DB2 plan: End of section DB2 plan: DB2 plan: - Planning Time: 2.557 ms - Execution Time: 5.589 ms + Planning Time: 2.408 ms + Execution Time: 4.365 ms (66 rows) SELECT empno, lastname, salary FROM sample.employee LIMIT 5 OFFSET 2; @@ -1008,7 +1216,7 @@ SELECT empno, lastname, salary FROM sample.employee LIMIT 5 OFFSET 2; EXPLAIN (analyze,verbose) SELECT empno, lastname, salary FROM sample.employee ORDER BY salary DESC, empno LIMIT 5 OFFSET 2; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - Foreign Scan on sample.employee (cost=100.06..101.19 rows=5 width=90) (actual time=5.042..6.655 rows=5.00 loops=1) + Foreign Scan on sample.employee (cost=100.06..101.19 rows=5 width=90) (actual time=2.783..3.474 rows=5.00 loops=1) Output: empno, lastname, salary DB2 query: SELECT "EMPNO", "LASTNAME", "SALARY" FROM "DB2INST1"."EMPLOYEE" ORDER BY "SALARY" DESC NULLS FIRST, "EMPNO" ASC NULLS LAST OFFSET 2 ROWS FETCH NEXT 5 ROWS ONLY DB2 plan: @@ -1094,8 +1302,8 @@ EXPLAIN (analyze,verbose) SELECT empno, lastname, salary FROM sample.employee OR DB2 plan: Planning: Buffers: shared hit=12 - Planning Time: 4.396 ms - Execution Time: 7.734 ms + Planning Time: 2.802 ms + Execution Time: 4.529 ms (88 rows) SELECT empno, lastname, salary FROM sample.employee ORDER BY salary DESC, empno LIMIT 5 OFFSET 2; @@ -1122,7 +1330,7 @@ PREPARE EXPLAIN (analyze,verbose) EXECUTE act_by_no(10); QUERY PLAN ---------------------------------------------------------------------------------------------------------------- - Foreign Scan on sample.act (cost=100.00..119.98 rows=4 width=88) (actual time=5.000..5.322 rows=1.00 loops=1) + Foreign Scan on sample.act (cost=100.00..119.98 rows=4 width=88) (actual time=4.606..4.678 rows=1.00 loops=1) Output: actno, actkwd, actdesc DB2 query: SELECT "ACTNO", "ACTKWD", "ACTDESC" FROM "DB2INST1"."ACT" WHERE (("ACTNO" = 10)) DB2 plan: @@ -1195,8 +1403,8 @@ EXPLAIN (analyze,verbose) EXECUTE act_by_no(10); DB2 plan: End of section DB2 plan: DB2 plan: - Planning Time: 2.139 ms - Execution Time: 5.820 ms + Planning Time: 2.443 ms + Execution Time: 5.135 ms (75 rows) EXECUTE act_by_no(10); @@ -1210,8 +1418,35 @@ DEALLOCATE -- -- END of TC012 -- +-- running tc013.sql +\i ./test/sql/tc013.sql +-- +-- TC013: Obtain LOB and NULLs +-- +select deploymentdata from sample.uaci_rtdeployment; + deploymentdata +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + \x5c78333134363338343233303338333033303330333033303330333033303330333033303330333033303435343433373434333034323339333433313433343333353337333533363338343534343334343634363432343433373330343133303331343634313436333033313434333833343336333533303333333034353333343234343431343433353331343633383434333933363339343433393434343433393339333533343336343334333337343534343338333133393436333133393331333833323339331c2b04 +(1 row) + +select rtdeploymentid,icid,deploymentdata from sample.uaci_rtdeployment; + rtdeploymentid | icid | deploymentdata +----------------+------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + 1 | 1 | \x5c78333134363338343233303338333033303330333033303330333033303330333033303330333033303435343433373434333034323339333433313433343333353337333533363338343534343334343634363432343433373330343133303331343634313436333033313434333833343336333533303333333034353333343234343431343433353331343633383434333933363339343433393434343433393339333533343336343334333337343534343338333133393436333133393331333833323339331c2b04 +(1 row) + +select * from sample.uaci_rtdeployment; + rtdeploymentid | icid | deploymentdata | rtdepstatusid | deploymentversion | createdate | createby | updatedate | updateby | version | isactive +----------------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------+-------------------+------------+----------+------------+----------+---------+---------- + 1 | 1 | \x5c78333134363338343233303338333033303330333033303330333033303330333033303330333033303435343433373434333034323339333433313433343333353337333533363338343534343334343634363432343433373330343133303331343634313436333033313434333833343336333533303333333034353333343234343431343433353331343633383434333933363339343433393434343433393339333533343336343334333337343534343338333133393436333133393331333833323339331c2b04 | 1 | 1 | [NULL] | [NULL] | [NULL] | [NULL] | 1 | 1 +(1 row) + +-- +-- END of TC013 +-- -- testcases ended -- starting cleanup +\i ./test/sql/tcend.sql \c postgres You are now connected to database "postgres" as user "postgres". DROP DATABASE regtest; From 1ae53ca93d2ae2399335ac0abdcd5c8d784e5392 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Sun, 17 May 2026 10:19:49 +0200 Subject: [PATCH 117/120] more tests --- test/expected/base.out | 131 +++++++++++++++++++++++++++++++++-------- test/sql/base.sql | 2 + test/sql/tc014.sql | 25 ++++++++ 3 files changed, 134 insertions(+), 24 deletions(-) create mode 100644 test/sql/tc014.sql diff --git a/test/expected/base.out b/test/expected/base.out index 5d05218..a45ac3f 100644 --- a/test/expected/base.out +++ b/test/expected/base.out @@ -328,7 +328,7 @@ FDW options: (schema 'DB2INST1', "table" 'SALES') explain (analyze,verbose) select * from sample.employee a, sample.sales b where a.lastname = b.sales_person; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - Foreign Scan (cost=200.00..424.60 rows=928 width=362) (actual time=4.438..34.250 rows=41.00 loops=1) + Foreign Scan (cost=200.00..424.60 rows=928 width=362) (actual time=4.273..34.474 rows=41.00 loops=1) Output: a.empno, a.firstnme, a.midinit, a.lastname, a.workdept, a.phoneno, a.hiredate, a.job, a.edlevel, a.sex, a.birthdate, a.salary, a.bonus, a.comm, b.sales_date, b.sales_person, b.region, b.sales DB2 query: SELECT r1."EMPNO", r1."FIRSTNME", r1."MIDINIT", r1."LASTNAME", r1."WORKDEPT", r1."PHONENO", r1."HIREDATE", r1."JOB", r1."EDLEVEL", r1."SEX", r1."BIRTHDATE", r1."SALARY", r1."BONUS", r1."COMM", r2."SALES_DATE", r2."SALES_PERSON", r2."REGION", r2."SALES" FROM ("DB2INST1"."EMPLOYEE" r1 INNER JOIN "DB2INST1"."SALES" r2 ON (((r1."LASTNAME" = r2."SALES_PERSON")))) DB2 plan: @@ -418,8 +418,8 @@ explain (analyze,verbose) select * from sample.employee a, sample.sales b where DB2 plan: Planning: Buffers: shared hit=106 - Planning Time: 5.038 ms - Execution Time: 36.439 ms + Planning Time: 4.967 ms + Execution Time: 36.629 ms (92 rows) select * from sample.employee a, sample.sales b where a.lastname = b.sales_person; @@ -554,7 +554,7 @@ FDW options: (schema 'DB2INST1', "table" 'EMPLOYEE') explain (analyze,verbose) select min(salary),max(salary),avg(salary),sum(salary +comm + bonus),count(*) from sample.employee; QUERY PLAN -------------------------------------------------------------------------------------------------------------------------------------------- - Foreign Scan (cost=121.72..144.35 rows=1 width=136) (actual time=3.258..3.344 rows=1.00 loops=1) + Foreign Scan (cost=121.72..144.35 rows=1 width=136) (actual time=3.646..3.913 rows=1.00 loops=1) Output: (min(salary)), (max(salary)), (avg(salary)), (sum(((salary + comm) + bonus))), (count(*)) DB2 query: SELECT MIN("SALARY"), MAX("SALARY"), AVG("SALARY"), SUM((("SALARY" + "COMM") + "BONUS")), COUNT(*) FROM "DB2INST1"."EMPLOYEE" DB2 plan: @@ -623,8 +623,8 @@ explain (analyze,verbose) select min(salary),max(salary),avg(salary),sum(salary DB2 plan: Planning: Buffers: shared hit=20 - Planning Time: 4.241 ms - Execution Time: 4.535 ms + Planning Time: 13.537 ms + Execution Time: 6.703 ms (71 rows) select min(salary),max(salary),avg(salary),sum(salary +comm + bonus),count(*) from sample.employee; @@ -637,7 +637,7 @@ select min(salary),max(salary),avg(salary),sum(salary +comm + bonus),count(*) fr explain (analyze,verbose) select empno, firstnme,lastname, salary + bonus + comm from sample.employee where salary > 43840.01 and lastname like 'L%'; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - Foreign Scan on sample.employee (cost=100.00..116.89 rows=1 width=150) (actual time=2.729..3.355 rows=3.00 loops=1) + Foreign Scan on sample.employee (cost=100.00..116.89 rows=1 width=150) (actual time=2.830..3.457 rows=3.00 loops=1) Output: empno, firstnme, lastname, ((salary + bonus) + comm) DB2 query: SELECT "EMPNO", "FIRSTNME", "LASTNAME", "SALARY", "BONUS", "COMM" FROM "DB2INST1"."EMPLOYEE" WHERE (("SALARY" > 43840.01)) AND (("LASTNAME" LIKE 'L%' ESCAPE '\')) DB2 plan: @@ -706,8 +706,8 @@ explain (analyze,verbose) select empno, firstnme,lastname, salary + bonus + comm DB2 plan: Planning: Buffers: shared hit=3 - Planning Time: 3.236 ms - Execution Time: 4.581 ms + Planning Time: 5.221 ms + Execution Time: 4.697 ms (71 rows) select empno, firstnme,lastname, salary + bonus + comm from sample.employee where salary > 43840.01 and lastname like 'L%'; @@ -946,7 +946,7 @@ SELECT count(*) FROM sample.employee; EXPLAIN (analyze,verbose) SELECT empno, lastname, salary FROM sample.employee WHERE workdept IN ('A00','B01','E21') AND salary BETWEEN 40000 AND 70000 AND (lastname LIKE 'L%' OR lastname LIKE 'G%'); QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - Foreign Scan on sample.employee (cost=100.00..127.26 rows=1 width=90) (actual time=4.343..5.054 rows=3.00 loops=1) + Foreign Scan on sample.employee (cost=100.00..127.26 rows=1 width=90) (actual time=3.859..4.593 rows=3.00 loops=1) Output: empno, lastname, salary, workdept DB2 query: SELECT "EMPNO", "LASTNAME", "SALARY", "WORKDEPT" FROM "DB2INST1"."EMPLOYEE" WHERE (("SALARY" >= 40000)) AND (("SALARY" <= 70000)) AND (("WORKDEPT" IN ('A00', 'B01', 'E21'))) AND ((("LASTNAME" LIKE 'L%' ESCAPE '\') OR ("LASTNAME" LIKE 'G%' ESCAPE '\'))) DB2 plan: @@ -1022,8 +1022,8 @@ EXPLAIN (analyze,verbose) SELECT empno, lastname, salary FROM sample.employee WH DB2 plan: End of section DB2 plan: DB2 plan: - Planning Time: 8.318 ms - Execution Time: 6.574 ms + Planning Time: 4.428 ms + Execution Time: 5.784 ms (78 rows) SELECT empno, lastname, salary FROM sample.employee WHERE workdept IN ('A00','B01','E21') AND salary BETWEEN 40000 AND 70000 AND (lastname LIKE 'L%' OR lastname LIKE 'G%') ORDER BY empno; @@ -1056,7 +1056,7 @@ FROM sample.employee; EXPLAIN (analyze,verbose) SELECT empno, lastname, salary FROM sample.employee LIMIT 5; QUERY PLAN --------------------------------------------------------------------------------------------------------------------- - Foreign Scan on sample.employee (cost=100.00..101.11 rows=5 width=90) (actual time=2.016..2.781 rows=5.00 loops=1) + Foreign Scan on sample.employee (cost=100.00..101.11 rows=5 width=90) (actual time=1.978..2.682 rows=5.00 loops=1) Output: empno, lastname, salary DB2 query: SELECT "EMPNO", "LASTNAME", "SALARY" FROM "DB2INST1"."EMPLOYEE" FETCH FIRST 5 ROWS ONLY DB2 plan: @@ -1118,8 +1118,8 @@ EXPLAIN (analyze,verbose) SELECT empno, lastname, salary FROM sample.employee LI DB2 plan: End of section DB2 plan: DB2 plan: - Planning Time: 2.544 ms - Execution Time: 3.833 ms + Planning Time: 2.362 ms + Execution Time: 3.729 ms (64 rows) SELECT empno, lastname, salary FROM sample.employee LIMIT 5; @@ -1135,7 +1135,7 @@ SELECT empno, lastname, salary FROM sample.employee LIMIT 5; EXPLAIN (analyze,verbose) SELECT empno, lastname, salary FROM sample.employee LIMIT 5 OFFSET 2; QUERY PLAN --------------------------------------------------------------------------------------------------------------------- - Foreign Scan on sample.employee (cost=100.05..101.16 rows=5 width=90) (actual time=2.590..3.293 rows=5.00 loops=1) + Foreign Scan on sample.employee (cost=100.05..101.16 rows=5 width=90) (actual time=2.462..3.180 rows=5.00 loops=1) Output: empno, lastname, salary DB2 query: SELECT "EMPNO", "LASTNAME", "SALARY" FROM "DB2INST1"."EMPLOYEE" OFFSET 2 ROWS FETCH NEXT 5 ROWS ONLY DB2 plan: @@ -1199,8 +1199,8 @@ EXPLAIN (analyze,verbose) SELECT empno, lastname, salary FROM sample.employee LI DB2 plan: End of section DB2 plan: DB2 plan: - Planning Time: 2.408 ms - Execution Time: 4.365 ms + Planning Time: 2.385 ms + Execution Time: 4.263 ms (66 rows) SELECT empno, lastname, salary FROM sample.employee LIMIT 5 OFFSET 2; @@ -1216,7 +1216,7 @@ SELECT empno, lastname, salary FROM sample.employee LIMIT 5 OFFSET 2; EXPLAIN (analyze,verbose) SELECT empno, lastname, salary FROM sample.employee ORDER BY salary DESC, empno LIMIT 5 OFFSET 2; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - Foreign Scan on sample.employee (cost=100.06..101.19 rows=5 width=90) (actual time=2.783..3.474 rows=5.00 loops=1) + Foreign Scan on sample.employee (cost=100.06..101.19 rows=5 width=90) (actual time=2.671..3.373 rows=5.00 loops=1) Output: empno, lastname, salary DB2 query: SELECT "EMPNO", "LASTNAME", "SALARY" FROM "DB2INST1"."EMPLOYEE" ORDER BY "SALARY" DESC NULLS FIRST, "EMPNO" ASC NULLS LAST OFFSET 2 ROWS FETCH NEXT 5 ROWS ONLY DB2 plan: @@ -1302,8 +1302,8 @@ EXPLAIN (analyze,verbose) SELECT empno, lastname, salary FROM sample.employee OR DB2 plan: Planning: Buffers: shared hit=12 - Planning Time: 2.802 ms - Execution Time: 4.529 ms + Planning Time: 2.803 ms + Execution Time: 4.423 ms (88 rows) SELECT empno, lastname, salary FROM sample.employee ORDER BY salary DESC, empno LIMIT 5 OFFSET 2; @@ -1330,7 +1330,7 @@ PREPARE EXPLAIN (analyze,verbose) EXECUTE act_by_no(10); QUERY PLAN ---------------------------------------------------------------------------------------------------------------- - Foreign Scan on sample.act (cost=100.00..119.98 rows=4 width=88) (actual time=4.606..4.678 rows=1.00 loops=1) + Foreign Scan on sample.act (cost=100.00..119.98 rows=4 width=88) (actual time=4.188..4.262 rows=1.00 loops=1) Output: actno, actkwd, actdesc DB2 query: SELECT "ACTNO", "ACTKWD", "ACTDESC" FROM "DB2INST1"."ACT" WHERE (("ACTNO" = 10)) DB2 plan: @@ -1403,8 +1403,8 @@ EXPLAIN (analyze,verbose) EXECUTE act_by_no(10); DB2 plan: End of section DB2 plan: DB2 plan: - Planning Time: 2.443 ms - Execution Time: 5.135 ms + Planning Time: 1.429 ms + Execution Time: 4.744 ms (75 rows) EXECUTE act_by_no(10); @@ -1444,6 +1444,89 @@ select * from sample.uaci_rtdeployment; -- -- END of TC013 -- +-- running tc014.sql +\i ./test/sql/tc014.sql +-- +-- TC014: INSERT +-- +\d+ sample.remotetable + Foreign table "sample.remotetable" + Column | Type | Collation | Nullable | Default | FDW options | Storage | Stats target | Description +--------+--------------------------------+-----------+----------+---------+---------------------------------------------------------------------------------------------------------------+----------+--------------+------------- + col1 | integer | | not null | | (db2type '4', db2size '10', db2bytes '4', db2chars '10', db2scale '0', db2null '0', db2ccsid '0', key 'true') | plain | | + col2 | character varying(99) | | not null | | (db2type '12', db2size '99', db2bytes '99', db2chars '0', db2scale '0', db2null '0', db2ccsid '1208') | extended | | + col3 | character varying(99) | | not null | | (db2type '12', db2size '99', db2bytes '99', db2chars '0', db2scale '0', db2null '0', db2ccsid '1208') | extended | | + col4 | character varying(99) | | | | (db2type '12', db2size '99', db2bytes '99', db2chars '0', db2scale '0', db2null '1', db2ccsid '1208') | extended | | + col5 | character varying(1000) | | | | (db2type '12', db2size '1000', db2bytes '1000', db2chars '0', db2scale '0', db2null '1', db2ccsid '1208') | extended | | + col6 | character varying(255) | | | | (db2type '12', db2size '255', db2bytes '255', db2chars '0', db2scale '0', db2null '1', db2ccsid '1208') | extended | | + col7 | character varying(255) | | | | (db2type '12', db2size '255', db2bytes '255', db2chars '0', db2scale '0', db2null '1', db2ccsid '1208') | extended | | + col8 | date | | | | (db2type '91', db2size '10', db2bytes '6', db2chars '0', db2scale '0', db2null '1', db2ccsid '0') | plain | | + col9 | time(0) without time zone | | | | (db2type '92', db2size '8', db2bytes '6', db2chars '0', db2scale '0', db2null '1', db2ccsid '0') | plain | | + col10 | date | | | | (db2type '91', db2size '10', db2bytes '6', db2chars '0', db2scale '0', db2null '1', db2ccsid '0') | plain | | + col11 | time(0) without time zone | | | | (db2type '92', db2size '8', db2bytes '6', db2chars '0', db2scale '0', db2null '1', db2ccsid '0') | plain | | + col12 | integer | | | | (db2type '4', db2size '10', db2bytes '4', db2chars '10', db2scale '0', db2null '1', db2ccsid '0') | plain | | + col13 | character(2) | | not null | | (db2type '1', db2size '2', db2bytes '2', db2chars '0', db2scale '0', db2null '0', db2ccsid '1208') | extended | | + col14 | character(2) | | | | (db2type '1', db2size '2', db2bytes '2', db2chars '0', db2scale '0', db2null '1', db2ccsid '1208') | extended | | + col15 | character(3) | | | | (db2type '1', db2size '3', db2bytes '3', db2chars '0', db2scale '0', db2null '1', db2ccsid '1208') | extended | | + col16 | character varying(99) | | | | (db2type '12', db2size '99', db2bytes '99', db2chars '0', db2scale '0', db2null '1', db2ccsid '1208') | extended | | + col17 | character(3) | | | | (db2type '1', db2size '3', db2bytes '3', db2chars '0', db2scale '0', db2null '1', db2ccsid '1208') | extended | | + col18 | character(2) | | | | (db2type '1', db2size '2', db2bytes '2', db2chars '0', db2scale '0', db2null '1', db2ccsid '1208') | extended | | + col19 | character varying(99) | | | | (db2type '12', db2size '99', db2bytes '99', db2chars '0', db2scale '0', db2null '1', db2ccsid '1208') | extended | | + col20 | character varying(99) | | | | (db2type '12', db2size '99', db2bytes '99', db2chars '0', db2scale '0', db2null '1', db2ccsid '1208') | extended | | + col21 | character varying(99) | | | | (db2type '12', db2size '99', db2bytes '99', db2chars '0', db2scale '0', db2null '1', db2ccsid '1208') | extended | | + col22 | character varying(99) | | | | (db2type '12', db2size '99', db2bytes '99', db2chars '0', db2scale '0', db2null '1', db2ccsid '1208') | extended | | + col23 | character varying(99) | | | | (db2type '12', db2size '99', db2bytes '99', db2chars '0', db2scale '0', db2null '1', db2ccsid '1208') | extended | | + col24 | timestamp(6) without time zone | | not null | | (db2type '93', db2size '26', db2bytes '16', db2chars '26', db2scale '6', db2null '0', db2ccsid '0') | plain | | + col25 | character varying(256) | | not null | | (db2type '12', db2size '256', db2bytes '256', db2chars '0', db2scale '0', db2null '0', db2ccsid '1208') | extended | | + col26 | character varying(255) | | | | (db2type '12', db2size '255', db2bytes '255', db2chars '0', db2scale '0', db2null '1', db2ccsid '1208') | extended | | + col27 | character varying(999) | | | | (db2type '12', db2size '999', db2bytes '999', db2chars '0', db2scale '0', db2null '1', db2ccsid '1208') | extended | | + col28 | character(3) | | | | (db2type '1', db2size '3', db2bytes '3', db2chars '0', db2scale '0', db2null '1', db2ccsid '1208') | extended | | +Not-null constraints: + "remotetable_col1_not_null" NOT NULL "col1" + "remotetable_col2_not_null" NOT NULL "col2" + "remotetable_col3_not_null" NOT NULL "col3" + "remotetable_col13_not_null" NOT NULL "col13" + "remotetable_col24_not_null" NOT NULL "col24" + "remotetable_col25_not_null" NOT NULL "col25" +Server: sample +FDW options: (schema 'DB2INST1', "table" 'REMOTETABLE') + +SELECT * FROM sample.remotetable; + col1 | col2 | col3 | col4 | col5 | col6 | col7 | col8 | col9 | col10 | col11 | col12 | col13 | col14 | col15 | col16 | col17 | col18 | col19 | col20 | col21 | col22 | col23 | col24 | col25 | col26 | col27 | col28 +--------+----------+----------+----------+--------+--------+----------+------------+----------+------------+----------+-------+-------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+-------------------------+----------+--------+--------+-------- + 581928 | sometext | sometext | sometext | [NULL] | [NULL] | sometext | 2025-12-31 | 12:24:00 | 2025-12-31 | 12:24:00 | 999 | AA | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | 2025-12-31 12:24:00.988 | sometext | [NULL] | [NULL] | [NULL] + 681928 | sometext | sometext | sometext | [NULL] | [NULL] | sometext | 2025-12-31 | 12:24:00 | 2025-12-31 | 12:24:00 | 998 | AA | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | 2025-12-31 12:24:00.988 | sometext | [NULL] | [NULL] | [NULL] +(2 rows) + +INSERT INTO sample.remotetable (col1, col2, col3, col4, col7, col8, col9, col10, col11, col12, col13, col24, col25) +VALUES ( 581927, + 'sometext', + 'sometext', + 'sometext', + 'sometext', + current_date, + current_time, + current_date, + current_time, + 999, + 'AA', + current_timestamp, + 'sometext' + ); +INSERT 0 1 +SELECT * FROM sample.remotetable; + col1 | col2 | col3 | col4 | col5 | col6 | col7 | col8 | col9 | col10 | col11 | col12 | col13 | col14 | col15 | col16 | col17 | col18 | col19 | col20 | col21 | col22 | col23 | col24 | col25 | col26 | col27 | col28 +--------+----------+----------+----------+--------+--------+----------+------------+----------+------------+----------+-------+-------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+----------------------------+----------+--------+--------+-------- + 581927 | sometext | sometext | sometext | [NULL] | [NULL] | sometext | 2026-05-17 | 10:13:05 | 2026-05-17 | 10:13:05 | 999 | AA | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | 2026-05-17 10:13:04.938175 | sometext | [NULL] | [NULL] | [NULL] + 581928 | sometext | sometext | sometext | [NULL] | [NULL] | sometext | 2025-12-31 | 12:24:00 | 2025-12-31 | 12:24:00 | 999 | AA | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | 2025-12-31 12:24:00.988 | sometext | [NULL] | [NULL] | [NULL] + 681928 | sometext | sometext | sometext | [NULL] | [NULL] | sometext | 2025-12-31 | 12:24:00 | 2025-12-31 | 12:24:00 | 998 | AA | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | 2025-12-31 12:24:00.988 | sometext | [NULL] | [NULL] | [NULL] +(3 rows) + +DELETE FROM sample.remotetable where col1 = 581927; +DELETE 1 +-- +-- END of TC014 +-- -- testcases ended -- starting cleanup \i ./test/sql/tcend.sql diff --git a/test/sql/base.sql b/test/sql/base.sql index 3b1ab7b..a886253 100644 --- a/test/sql/base.sql +++ b/test/sql/base.sql @@ -34,6 +34,8 @@ set log_min_messages=debug5; \i ./test/sql/tc012.sql -- running tc013.sql \i ./test/sql/tc013.sql +-- running tc014.sql +\i ./test/sql/tc014.sql -- testcases ended -- starting cleanup \i ./test/sql/tcend.sql diff --git a/test/sql/tc014.sql b/test/sql/tc014.sql new file mode 100644 index 0000000..ebf2642 --- /dev/null +++ b/test/sql/tc014.sql @@ -0,0 +1,25 @@ +-- +-- TC014: INSERT +-- +\d+ sample.remotetable +SELECT * FROM sample.remotetable; +INSERT INTO sample.remotetable (col1, col2, col3, col4, col7, col8, col9, col10, col11, col12, col13, col24, col25) +VALUES ( 581927, + 'sometext', + 'sometext', + 'sometext', + 'sometext', + current_date, + current_time, + current_date, + current_time, + 999, + 'AA', + current_timestamp, + 'sometext' + ); +SELECT * FROM sample.remotetable; +DELETE FROM sample.remotetable where col1 = 581927; +-- +-- END of TC014 +-- From 250c96e50a8d80cc662b4e36856c9356143e3f8d Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Sun, 17 May 2026 13:34:28 +0200 Subject: [PATCH 118/120] reword fdw options and follow up on documentation --- README.md | 89 ++++++++++++++++++++++++++++++++++++----- include/db2_fdw.h | 1 + source/db2Describe.c | 4 +- source/db2GetFdwState.c | 28 ++++--------- source/db2_fdw.c | 1 - 5 files changed, 91 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index e7ba75f..49064ee 100644 --- a/README.md +++ b/README.md @@ -170,20 +170,33 @@ Foreign server options This can be in any of the forms that DB2 supports as long as your DB2 client is configured accordingly. +- **batch_size** (optional, default 100) + + In future enhancements batch_size will be used to operate cunks of rows of that size in batch mode. + Usage in LOAD and INSERT BATCH scenarios is envisaged. + +- **no_encoding_error** (optional, "ON", "OFF", "YES", "NO", "TRUE", "FALSE") + + If this option is set to ON, YES, TRUE errors are generated on encoding problems. + By default these errors are suppressed. + User mapping options -------------------- -- **user** (required) +- **user** (optional - mutual exclusive to jwt_password, one use is required) The DB2 user name for the session. Set this to an empty string for *external authentication* if you don't want to store DB2 credentials in the PostgreSQL database (one simple way is to use an *external password store*). -- **password** (required) +- **password** (optional - mutual exclusive to jwt_password, one use is required) The password for the DB2 user. +- **jwt_token** (optional - mutual exclusive to user&password, one use is required) + + The password for the DB2 user. Foreign table options --------------------- @@ -209,15 +222,12 @@ Foreign table options occurs in DB2's system catalog, so normally consist of uppercase letters only. -- **max_long** (optional, defaults to "32767") +- **max_long** (optional, now deprecated - no longer used) The maximal length of any LONG or LONG RAW columns in the DB2 table. Possible values are integers between 1 and 1073741823 (the maximal size of a `bytea` in PostgreSQL). This amount of memory will be allocated at least twice, so large values will consume a lot of memory. - If **max_long** is less than the length of the longest value retrieved, - you will receive the error message `ORA-01406: fetched column value was - truncated`. - **readonly** (optional, defaults to "false") @@ -228,7 +238,7 @@ Foreign table options on tables that you do not wish to be changed, to be prepared for an upgrade to PostgreSQL 9.3 or later. -- **sample_percent** (optional, defaults to "100") +- **sample_percent** (optional, defaults to "100.0") This option only influences ANALYZE processing and can be useful to ANALYZE very large tables in a reasonable time. @@ -241,16 +251,35 @@ Foreign table options ANALYZE will fail with ORA-00933 for tables defined with DB2 queries and may fail with ORA-01446 for tables defined with complex DB2 views. -- **prefetch** (optional, defaults to "200") +- **prefetch** (aka SQL_ATTR_PREFETCH_NROWS optional, defaults to "100") Sets the number of rows that will be fetched with a single round-trip between PostgreSQL and DB2 during a foreign table scan. This is implemented using - DB2 row prefetching. The value must be between 0 and 10240, where a value + DB2 row prefetching. The value must be between 0 and 1024, where a value of zero disables prefetching. Higher values can speed up performance, but will use more memory on the PostgreSQL server. +- **fetch_size** (optional, fix 1) + + In future enhancements fetch_size will be used to enable fetching of that number of rows at once. + Currently any number is ignored and 1 is used as a fix value, until the logic to cope with a larger result that one is implemnted. + +- **batch_size** (optional, default 100) + + In future enhancements batch_size will be used to operate cunks of rows of that size in batch mode. + Usage in LOAD and INSERT BATCH scenarios is envisaged. + A value defined on a table will override the value provided on the server, just for that table. + +- **no_encoding_error** (optional, "ON", "OFF", "YES", "NO", "TRUE", "FALSE") + + If this option is set to ON, YES, TRUE errors are generated on encoding problems. + By default these errors are suppressed. + Providing this on a table level sets all columns to this value, diffenent from the system wide setting given + explicitly or implicitly on the server. + + Column options (from PostgreSQL 9.2 on) --------------------------------------- @@ -261,6 +290,48 @@ Column options (from PostgreSQL 9.2 on) For UPDATE and DELETE to work, you must set this option on all columns that belong to the table's primary key. +- **db2type** () + + On import the SQL value of the datatype is stored, identifying the DB2 datatype to db2_fdw. + It is required to re-construct the table description internally for db2_fdw. + +- **db2size** () + + On import the length of the column as defined in the DB2 catalog is stored. + It is required to re-construct the table description internally for db2_fdw. + +- **db2bytes** () + + On import the number of bytes derived from the datatype, encoding as provided by DB2 is stored. + It is required to re-construct the table description internally for db2_fdw. + +- **db2chars** () + + On import the number of characters derived from the datatype, encoding as provided by DB2 is stored. + It is required to re-construct the table description internally for db2_fdw. + +- **db2scale** () + + On import the scale of a decimal value as provided by DB2 is stored. + It is required to re-construct the table description internally for db2_fdw. + +- **db2null** () + + The nullable characteristic (0 = NULLABLE, 1 = NOT NULL) as provided by DB2 is stored. + It is required to re-construct the table description internally for db2_fdw. + +- **db2ccsid** () + + On import the ccsid of the encoding as provided by DB2 is stored. + It is required to re-construct the table description internally for db2_fdw. + +- **no_encoding_error** (optional, "ON", "OFF", "YES", "NO", "TRUE", "FALSE") + + If this option is set to ON, YES, TRUE errors are generated on encoding problems. + By default these errors are suppressed. + Providing this on a column level sets this columns to this value, diffenent from the system wide setting given + explicitly or implicitly on the server, or the table wide settings given on the table level. + 4 Usage ======= diff --git a/include/db2_fdw.h b/include/db2_fdw.h index 1cccc20..e555e90 100644 --- a/include/db2_fdw.h +++ b/include/db2_fdw.h @@ -50,6 +50,7 @@ #define DEFAULT_MAX_LONG 32767 #define DEFAULT_PREFETCH 100 #define DEFAULT_FETCHSZ 1 +#define DEFAULT_SAMPLE_PERCENT 100.0 #define FIXED_FETCH_SIZE #define DEFAULT_BATCHSZ 100 #define TABLE_NAME_LEN 129 diff --git a/source/db2Describe.c b/source/db2Describe.c index dae58b6..86d4bb8 100644 --- a/source/db2Describe.c +++ b/source/db2Describe.c @@ -19,13 +19,13 @@ extern HdlEntry* db2AllocStmtHdl (SQLSMALLINT type, DB2ConnEntry* connp, extern void db2FreeStmtHdl (HdlEntry* handlep, DB2ConnEntry* connp); /** internal prototypes */ -DB2Table* db2Describe (DB2Session* session, char* schema, char* table, char* pgname, long max_long, char* noencerr, char* batchsz); +DB2Table* db2Describe (DB2Session* session, char* schema, char* table, char* pgname, char* noencerr, char* batchsz); /* db2Describe * Find the remote DB2 table and describe it. * Returns an allocated data structure with the results. */ -DB2Table* db2Describe (DB2Session* session, char* schema, char* table, char* pgname, long max_long, char* noencerr, char* batchsz) { +DB2Table* db2Describe (DB2Session* session, char* schema, char* table, char* pgname, char* noencerr, char* batchsz) { DB2Table* reply; HdlEntry* stmthp; char* qtable = NULL; diff --git a/source/db2GetFdwState.c b/source/db2GetFdwState.c index 2d088c7..c4c0af2 100644 --- a/source/db2GetFdwState.c +++ b/source/db2GetFdwState.c @@ -16,14 +16,14 @@ extern char* guessNlsLang (char* nls_lang); extern bool optionIsTrue (const char* value); extern DB2Session* db2GetSession (const char* connectstring, char* user, char* password, char* jwt_token, const char* nls_lang, int curlevel); -extern DB2Table* db2Describe (DB2Session* session, char* schema, char* table, char* pgname, long max_long, char* noencerr, char* batchsz); +extern DB2Table* db2Describe (DB2Session* session, char* schema, char* table, char* pgname, char* noencerr, char* batchsz); extern char* db2CopyText (const char* string, int size, int quote); extern char* c2name (short fcType); /** local prototypes */ DB2FdwState* db2GetFdwState (Oid foreigntableid, double* sample_percent, bool describe); DB2FdwDirectModifyState* db2GetFdwDirectModifyState(Oid foreigntableid, double* sample_percent, bool describe); -static DB2Table* describeForeignTable (Oid foreigntableid, char* schema, char* table, char* pgname, long max_long, char* noencerr, char* batchsz); +static DB2Table* describeForeignTable (Oid foreigntableid, char* schema, char* table, char* pgname, char* noencerr, char* batchsz); static void getColumnData (DB2Table* db2Table, Oid foreigntableid); static void getOptions (Oid foreigntableid, List** options); @@ -41,13 +41,11 @@ DB2FdwState* db2GetFdwState (Oid foreigntableid, double* sample_percent, bool de ListCell* cell = NULL; char* schema = NULL; char* table = NULL; - char* maxlong = NULL; char* sample = NULL; char* prefetch = NULL; char* fetchsz = NULL; char* noencerr = NULL; char* batchsz = NULL; - long max_long = 0; db2Entry1(); /* Get all relevant options from the foreign table, the user mapping, the foreign server and the foreign data wrapper. */ @@ -62,7 +60,6 @@ DB2FdwState* db2GetFdwState (Oid foreigntableid, double* sample_percent, bool de fdwState->jwt_token = (strcmp (def->defname, OPT_JWT_TOKEN) == 0) ? STRVAL(def->arg) : fdwState->jwt_token; schema = (strcmp (def->defname, OPT_SCHEMA) == 0) ? STRVAL(def->arg) : schema; table = (strcmp (def->defname, OPT_TABLE) == 0) ? STRVAL(def->arg) : table; - maxlong = (strcmp (def->defname, OPT_MAX_LONG) == 0) ? STRVAL(def->arg) : maxlong; sample = (strcmp (def->defname, OPT_SAMPLE) == 0) ? STRVAL(def->arg) : sample; prefetch = (strcmp (def->defname, OPT_PREFETCH) == 0) ? STRVAL(def->arg) : prefetch; fetchsz = (strcmp (def->defname, OPT_FETCHSZ) == 0) ? STRVAL(def->arg) : fetchsz; @@ -70,13 +67,10 @@ DB2FdwState* db2GetFdwState (Oid foreigntableid, double* sample_percent, bool de batchsz = (strcmp (def->defname, OPT_BATCH_SIZE) == 0) ? STRVAL(def->arg) : batchsz; } - /* convert "max_long" option to number or use default */ - max_long = (maxlong == NULL) ? DEFAULT_MAX_LONG : strtol (maxlong, NULL, 0); - /* convert "sample_percent" to double */ if (sample_percent != NULL) { if (sample == NULL) - *sample_percent = 100.0; + *sample_percent = DEFAULT_SAMPLE_PERCENT; else *sample_percent = strtod (sample, NULL); } @@ -95,12 +89,12 @@ DB2FdwState* db2GetFdwState (Oid foreigntableid, double* sample_percent, bool de fdwState->nls_lang = guessNlsLang (fdwState->nls_lang); if (describe) { - fdwState->db2Table = describeForeignTable(foreigntableid, schema, table, pgtablename, max_long, noencerr, batchsz); + fdwState->db2Table = describeForeignTable(foreigntableid, schema, table, pgtablename, noencerr, batchsz); if (fdwState->db2Table == NULL) { /* connect to DB2 database */ fdwState->session = db2GetSession (fdwState->dbserver, fdwState->user, fdwState->password, fdwState->jwt_token, fdwState->nls_lang, GetCurrentTransactionNestLevel () ); /* get remote table description */ - fdwState->db2Table = db2Describe (fdwState->session, schema, table, pgtablename, max_long, noencerr, batchsz); + fdwState->db2Table = db2Describe (fdwState->session, schema, table, pgtablename, noencerr, batchsz); /* add PostgreSQL data to table description */ getColumnData (fdwState->db2Table, foreigntableid); } @@ -123,13 +117,11 @@ DB2FdwDirectModifyState* db2GetFdwDirectModifyState (Oid foreigntableid, double* ListCell* cell = NULL; char* schema = NULL; char* table = NULL; - char* maxlong = NULL; char* sample = NULL; char* prefetch = NULL; char* fetchsz = NULL; char* noencerr = NULL; char* batchsz = NULL; - long max_long = 0; db2Entry1(); /* Get all relevant options from the foreign table, the user mapping, the foreign server and the foreign data wrapper. */ @@ -144,7 +136,6 @@ DB2FdwDirectModifyState* db2GetFdwDirectModifyState (Oid foreigntableid, double* fdwState->jwt_token = (strcmp (def->defname, OPT_JWT_TOKEN) == 0) ? STRVAL(def->arg) : fdwState->jwt_token; schema = (strcmp (def->defname, OPT_SCHEMA) == 0) ? STRVAL(def->arg) : schema; table = (strcmp (def->defname, OPT_TABLE) == 0) ? STRVAL(def->arg) : table; - maxlong = (strcmp (def->defname, OPT_MAX_LONG) == 0) ? STRVAL(def->arg) : maxlong; sample = (strcmp (def->defname, OPT_SAMPLE) == 0) ? STRVAL(def->arg) : sample; prefetch = (strcmp (def->defname, OPT_PREFETCH) == 0) ? STRVAL(def->arg) : prefetch; fetchsz = (strcmp (def->defname, OPT_FETCHSZ) == 0) ? STRVAL(def->arg) : fetchsz; @@ -152,13 +143,10 @@ DB2FdwDirectModifyState* db2GetFdwDirectModifyState (Oid foreigntableid, double* batchsz = (strcmp (def->defname, OPT_BATCH_SIZE) == 0) ? STRVAL(def->arg) : batchsz; } - /* convert "max_long" option to number or use default */ - max_long = (maxlong == NULL) ? DEFAULT_MAX_LONG : strtol (maxlong, NULL, 0); - /* convert "sample_percent" to double */ if (sample_percent != NULL) { if (sample == NULL) - *sample_percent = 100.0; + *sample_percent = DEFAULT_SAMPLE_PERCENT; else *sample_percent = strtod (sample, NULL); } @@ -177,7 +165,7 @@ DB2FdwDirectModifyState* db2GetFdwDirectModifyState (Oid foreigntableid, double* fdwState->nls_lang = guessNlsLang (fdwState->nls_lang); if (describe) { - fdwState->db2Table = describeForeignTable(foreigntableid, schema, table, pgtablename, max_long, noencerr, batchsz); + fdwState->db2Table = describeForeignTable(foreigntableid, schema, table, pgtablename, noencerr, batchsz); } fdwState->session = db2GetSession (fdwState->dbserver, fdwState->user, fdwState->password, fdwState->jwt_token, fdwState->nls_lang, GetCurrentTransactionNestLevel () ); @@ -186,7 +174,7 @@ DB2FdwDirectModifyState* db2GetFdwDirectModifyState (Oid foreigntableid, double* } -static DB2Table* describeForeignTable (Oid foreigntableid, char* schema, char* table, char* pgname, long max_long, char* noencerr, char* batchsz) { +static DB2Table* describeForeignTable (Oid foreigntableid, char* schema, char* table, char* pgname, char* noencerr, char* batchsz) { DB2Table* db2Table = NULL; char* qtable = NULL; char* qschema = NULL; diff --git a/source/db2_fdw.c b/source/db2_fdw.c index dc602f2..5c8093d 100644 --- a/source/db2_fdw.c +++ b/source/db2_fdw.c @@ -73,7 +73,6 @@ DB2FdwOption valid_options[] = { #if PG_VERSION_NUM >= 140000 {OPT_BATCH_SIZE , ForeignServerRelationId , false}, {OPT_BATCH_SIZE , ForeignTableRelationId , false}, - {OPT_BATCH_SIZE , AttributeRelationId , false}, #endif {OPT_NO_ENCODING_ERROR, ForeignDataWrapperRelationId, false}, {OPT_NO_ENCODING_ERROR, ForeignTableRelationId , false}, From 924dcee3e24c0fb2132740f7ea0e59ea801ef2e9 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Sun, 17 May 2026 13:59:23 +0200 Subject: [PATCH 119/120] update doc --- README.md | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 49064ee..9f7a050 100644 --- a/README.md +++ b/README.md @@ -290,37 +290,37 @@ Column options (from PostgreSQL 9.2 on) For UPDATE and DELETE to work, you must set this option on all columns that belong to the table's primary key. -- **db2type** () +- **db2type** (required) On import the SQL value of the datatype is stored, identifying the DB2 datatype to db2_fdw. It is required to re-construct the table description internally for db2_fdw. -- **db2size** () +- **db2size** (required) On import the length of the column as defined in the DB2 catalog is stored. It is required to re-construct the table description internally for db2_fdw. -- **db2bytes** () +- **db2bytes** (required) On import the number of bytes derived from the datatype, encoding as provided by DB2 is stored. It is required to re-construct the table description internally for db2_fdw. -- **db2chars** () +- **db2chars** (required) On import the number of characters derived from the datatype, encoding as provided by DB2 is stored. It is required to re-construct the table description internally for db2_fdw. -- **db2scale** () +- **db2scale** (required) On import the scale of a decimal value as provided by DB2 is stored. It is required to re-construct the table description internally for db2_fdw. -- **db2null** () +- **db2null** (required) The nullable characteristic (0 = NULLABLE, 1 = NOT NULL) as provided by DB2 is stored. It is required to re-construct the table description internally for db2_fdw. -- **db2ccsid** () +- **db2ccsid** (required) On import the ccsid of the encoding as provided by DB2 is stored. It is required to re-construct the table description internally for db2_fdw. @@ -372,6 +372,13 @@ If you want to UPDATE or DELETE, make sure that the `key` option is set on all columns that belong to the table's primary key. Failure to do so will result in errors. +If the remote table does not have a key defined you may alter the foreign table +manually for defining a column key: + + ALTER FOREIGN TABLE . + ALTER COLUMN OPTIONS (ADD key 'true'); + + Data types ---------- From 6783115bd9543bb6069b6cda14621387250d0d04 Mon Sep 17 00:00:00 2001 From: Thomas Muenz Date: Sun, 17 May 2026 13:59:39 +0200 Subject: [PATCH 120/120] current testresult --- test/expected/base.out | 50 +++++++++++++++++++++--------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/test/expected/base.out b/test/expected/base.out index a45ac3f..b3a22d7 100644 --- a/test/expected/base.out +++ b/test/expected/base.out @@ -328,7 +328,7 @@ FDW options: (schema 'DB2INST1', "table" 'SALES') explain (analyze,verbose) select * from sample.employee a, sample.sales b where a.lastname = b.sales_person; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - Foreign Scan (cost=200.00..424.60 rows=928 width=362) (actual time=4.273..34.474 rows=41.00 loops=1) + Foreign Scan (cost=200.00..424.60 rows=928 width=362) (actual time=5.030..53.148 rows=41.00 loops=1) Output: a.empno, a.firstnme, a.midinit, a.lastname, a.workdept, a.phoneno, a.hiredate, a.job, a.edlevel, a.sex, a.birthdate, a.salary, a.bonus, a.comm, b.sales_date, b.sales_person, b.region, b.sales DB2 query: SELECT r1."EMPNO", r1."FIRSTNME", r1."MIDINIT", r1."LASTNAME", r1."WORKDEPT", r1."PHONENO", r1."HIREDATE", r1."JOB", r1."EDLEVEL", r1."SEX", r1."BIRTHDATE", r1."SALARY", r1."BONUS", r1."COMM", r2."SALES_DATE", r2."SALES_PERSON", r2."REGION", r2."SALES" FROM ("DB2INST1"."EMPLOYEE" r1 INNER JOIN "DB2INST1"."SALES" r2 ON (((r1."LASTNAME" = r2."SALES_PERSON")))) DB2 plan: @@ -418,8 +418,8 @@ explain (analyze,verbose) select * from sample.employee a, sample.sales b where DB2 plan: Planning: Buffers: shared hit=106 - Planning Time: 4.967 ms - Execution Time: 36.629 ms + Planning Time: 4.981 ms + Execution Time: 55.245 ms (92 rows) select * from sample.employee a, sample.sales b where a.lastname = b.sales_person; @@ -554,7 +554,7 @@ FDW options: (schema 'DB2INST1', "table" 'EMPLOYEE') explain (analyze,verbose) select min(salary),max(salary),avg(salary),sum(salary +comm + bonus),count(*) from sample.employee; QUERY PLAN -------------------------------------------------------------------------------------------------------------------------------------------- - Foreign Scan (cost=121.72..144.35 rows=1 width=136) (actual time=3.646..3.913 rows=1.00 loops=1) + Foreign Scan (cost=121.72..144.35 rows=1 width=136) (actual time=2.818..2.889 rows=1.00 loops=1) Output: (min(salary)), (max(salary)), (avg(salary)), (sum(((salary + comm) + bonus))), (count(*)) DB2 query: SELECT MIN("SALARY"), MAX("SALARY"), AVG("SALARY"), SUM((("SALARY" + "COMM") + "BONUS")), COUNT(*) FROM "DB2INST1"."EMPLOYEE" DB2 plan: @@ -623,8 +623,8 @@ explain (analyze,verbose) select min(salary),max(salary),avg(salary),sum(salary DB2 plan: Planning: Buffers: shared hit=20 - Planning Time: 13.537 ms - Execution Time: 6.703 ms + Planning Time: 3.456 ms + Execution Time: 4.079 ms (71 rows) select min(salary),max(salary),avg(salary),sum(salary +comm + bonus),count(*) from sample.employee; @@ -637,7 +637,7 @@ select min(salary),max(salary),avg(salary),sum(salary +comm + bonus),count(*) fr explain (analyze,verbose) select empno, firstnme,lastname, salary + bonus + comm from sample.employee where salary > 43840.01 and lastname like 'L%'; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - Foreign Scan on sample.employee (cost=100.00..116.89 rows=1 width=150) (actual time=2.830..3.457 rows=3.00 loops=1) + Foreign Scan on sample.employee (cost=100.00..116.89 rows=1 width=150) (actual time=2.668..3.310 rows=3.00 loops=1) Output: empno, firstnme, lastname, ((salary + bonus) + comm) DB2 query: SELECT "EMPNO", "FIRSTNME", "LASTNAME", "SALARY", "BONUS", "COMM" FROM "DB2INST1"."EMPLOYEE" WHERE (("SALARY" > 43840.01)) AND (("LASTNAME" LIKE 'L%' ESCAPE '\')) DB2 plan: @@ -706,8 +706,8 @@ explain (analyze,verbose) select empno, firstnme,lastname, salary + bonus + comm DB2 plan: Planning: Buffers: shared hit=3 - Planning Time: 5.221 ms - Execution Time: 4.697 ms + Planning Time: 3.304 ms + Execution Time: 4.535 ms (71 rows) select empno, firstnme,lastname, salary + bonus + comm from sample.employee where salary > 43840.01 and lastname like 'L%'; @@ -946,7 +946,7 @@ SELECT count(*) FROM sample.employee; EXPLAIN (analyze,verbose) SELECT empno, lastname, salary FROM sample.employee WHERE workdept IN ('A00','B01','E21') AND salary BETWEEN 40000 AND 70000 AND (lastname LIKE 'L%' OR lastname LIKE 'G%'); QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - Foreign Scan on sample.employee (cost=100.00..127.26 rows=1 width=90) (actual time=3.859..4.593 rows=3.00 loops=1) + Foreign Scan on sample.employee (cost=100.00..127.26 rows=1 width=90) (actual time=3.625..4.188 rows=3.00 loops=1) Output: empno, lastname, salary, workdept DB2 query: SELECT "EMPNO", "LASTNAME", "SALARY", "WORKDEPT" FROM "DB2INST1"."EMPLOYEE" WHERE (("SALARY" >= 40000)) AND (("SALARY" <= 70000)) AND (("WORKDEPT" IN ('A00', 'B01', 'E21'))) AND ((("LASTNAME" LIKE 'L%' ESCAPE '\') OR ("LASTNAME" LIKE 'G%' ESCAPE '\'))) DB2 plan: @@ -1022,8 +1022,8 @@ EXPLAIN (analyze,verbose) SELECT empno, lastname, salary FROM sample.employee WH DB2 plan: End of section DB2 plan: DB2 plan: - Planning Time: 4.428 ms - Execution Time: 5.784 ms + Planning Time: 4.072 ms + Execution Time: 5.328 ms (78 rows) SELECT empno, lastname, salary FROM sample.employee WHERE workdept IN ('A00','B01','E21') AND salary BETWEEN 40000 AND 70000 AND (lastname LIKE 'L%' OR lastname LIKE 'G%') ORDER BY empno; @@ -1056,7 +1056,7 @@ FROM sample.employee; EXPLAIN (analyze,verbose) SELECT empno, lastname, salary FROM sample.employee LIMIT 5; QUERY PLAN --------------------------------------------------------------------------------------------------------------------- - Foreign Scan on sample.employee (cost=100.00..101.11 rows=5 width=90) (actual time=1.978..2.682 rows=5.00 loops=1) + Foreign Scan on sample.employee (cost=100.00..101.11 rows=5 width=90) (actual time=2.025..2.731 rows=5.00 loops=1) Output: empno, lastname, salary DB2 query: SELECT "EMPNO", "LASTNAME", "SALARY" FROM "DB2INST1"."EMPLOYEE" FETCH FIRST 5 ROWS ONLY DB2 plan: @@ -1118,8 +1118,8 @@ EXPLAIN (analyze,verbose) SELECT empno, lastname, salary FROM sample.employee LI DB2 plan: End of section DB2 plan: DB2 plan: - Planning Time: 2.362 ms - Execution Time: 3.729 ms + Planning Time: 2.417 ms + Execution Time: 3.783 ms (64 rows) SELECT empno, lastname, salary FROM sample.employee LIMIT 5; @@ -1135,7 +1135,7 @@ SELECT empno, lastname, salary FROM sample.employee LIMIT 5; EXPLAIN (analyze,verbose) SELECT empno, lastname, salary FROM sample.employee LIMIT 5 OFFSET 2; QUERY PLAN --------------------------------------------------------------------------------------------------------------------- - Foreign Scan on sample.employee (cost=100.05..101.16 rows=5 width=90) (actual time=2.462..3.180 rows=5.00 loops=1) + Foreign Scan on sample.employee (cost=100.05..101.16 rows=5 width=90) (actual time=2.460..3.162 rows=5.00 loops=1) Output: empno, lastname, salary DB2 query: SELECT "EMPNO", "LASTNAME", "SALARY" FROM "DB2INST1"."EMPLOYEE" OFFSET 2 ROWS FETCH NEXT 5 ROWS ONLY DB2 plan: @@ -1199,8 +1199,8 @@ EXPLAIN (analyze,verbose) SELECT empno, lastname, salary FROM sample.employee LI DB2 plan: End of section DB2 plan: DB2 plan: - Planning Time: 2.385 ms - Execution Time: 4.263 ms + Planning Time: 2.427 ms + Execution Time: 4.391 ms (66 rows) SELECT empno, lastname, salary FROM sample.employee LIMIT 5 OFFSET 2; @@ -1216,7 +1216,7 @@ SELECT empno, lastname, salary FROM sample.employee LIMIT 5 OFFSET 2; EXPLAIN (analyze,verbose) SELECT empno, lastname, salary FROM sample.employee ORDER BY salary DESC, empno LIMIT 5 OFFSET 2; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - Foreign Scan on sample.employee (cost=100.06..101.19 rows=5 width=90) (actual time=2.671..3.373 rows=5.00 loops=1) + Foreign Scan on sample.employee (cost=100.06..101.19 rows=5 width=90) (actual time=2.928..3.635 rows=5.00 loops=1) Output: empno, lastname, salary DB2 query: SELECT "EMPNO", "LASTNAME", "SALARY" FROM "DB2INST1"."EMPLOYEE" ORDER BY "SALARY" DESC NULLS FIRST, "EMPNO" ASC NULLS LAST OFFSET 2 ROWS FETCH NEXT 5 ROWS ONLY DB2 plan: @@ -1302,8 +1302,8 @@ EXPLAIN (analyze,verbose) SELECT empno, lastname, salary FROM sample.employee OR DB2 plan: Planning: Buffers: shared hit=12 - Planning Time: 2.803 ms - Execution Time: 4.423 ms + Planning Time: 2.916 ms + Execution Time: 4.742 ms (88 rows) SELECT empno, lastname, salary FROM sample.employee ORDER BY salary DESC, empno LIMIT 5 OFFSET 2; @@ -1330,7 +1330,7 @@ PREPARE EXPLAIN (analyze,verbose) EXECUTE act_by_no(10); QUERY PLAN ---------------------------------------------------------------------------------------------------------------- - Foreign Scan on sample.act (cost=100.00..119.98 rows=4 width=88) (actual time=4.188..4.262 rows=1.00 loops=1) + Foreign Scan on sample.act (cost=100.00..119.98 rows=4 width=88) (actual time=3.927..4.002 rows=1.00 loops=1) Output: actno, actkwd, actdesc DB2 query: SELECT "ACTNO", "ACTKWD", "ACTDESC" FROM "DB2INST1"."ACT" WHERE (("ACTNO" = 10)) DB2 plan: @@ -1403,8 +1403,8 @@ EXPLAIN (analyze,verbose) EXECUTE act_by_no(10); DB2 plan: End of section DB2 plan: DB2 plan: - Planning Time: 1.429 ms - Execution Time: 4.744 ms + Planning Time: 1.305 ms + Execution Time: 4.481 ms (75 rows) EXECUTE act_by_no(10); @@ -1517,7 +1517,7 @@ INSERT 0 1 SELECT * FROM sample.remotetable; col1 | col2 | col3 | col4 | col5 | col6 | col7 | col8 | col9 | col10 | col11 | col12 | col13 | col14 | col15 | col16 | col17 | col18 | col19 | col20 | col21 | col22 | col23 | col24 | col25 | col26 | col27 | col28 --------+----------+----------+----------+--------+--------+----------+------------+----------+------------+----------+-------+-------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+----------------------------+----------+--------+--------+-------- - 581927 | sometext | sometext | sometext | [NULL] | [NULL] | sometext | 2026-05-17 | 10:13:05 | 2026-05-17 | 10:13:05 | 999 | AA | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | 2026-05-17 10:13:04.938175 | sometext | [NULL] | [NULL] | [NULL] + 581927 | sometext | sometext | sometext | [NULL] | [NULL] | sometext | 2026-05-17 | 13:49:31 | 2026-05-17 | 13:49:31 | 999 | AA | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | 2026-05-17 13:49:30.932557 | sometext | [NULL] | [NULL] | [NULL] 581928 | sometext | sometext | sometext | [NULL] | [NULL] | sometext | 2025-12-31 | 12:24:00 | 2025-12-31 | 12:24:00 | 999 | AA | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | 2025-12-31 12:24:00.988 | sometext | [NULL] | [NULL] | [NULL] 681928 | sometext | sometext | sometext | [NULL] | [NULL] | sometext | 2025-12-31 | 12:24:00 | 2025-12-31 | 12:24:00 | 998 | AA | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | [NULL] | 2025-12-31 12:24:00.988 | sometext | [NULL] | [NULL] | [NULL] (3 rows)