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. diff --git a/META.json b/META.json index 5593082..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.2", + "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.2" + "version": "18.2.0" } }, "resources": { diff --git a/Makefile b/Makefile index 101d509..413ac3c 100644 --- a/Makefile +++ b/Makefile @@ -2,8 +2,11 @@ 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/db2_de_serialize.o\ source/db2GetForeignPlan.o\ source/db2GetForeignPaths.o\ + source/db2GetForeignUpperPaths.o\ source/db2GetForeignJoinPaths.o\ source/db2AnalyzeForeignTable.o\ source/db2ExplainForeignScan.o\ @@ -26,12 +29,12 @@ 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\ source/db2SetHandlers.o\ source/db2Callbacks.o\ - source/db2GetOptions.o\ source/db2Debug.o\ source/db2ServerVersion.o\ source/db2ClientVersion.o\ @@ -44,7 +47,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\ @@ -64,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) @@ -76,7 +82,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/README.md b/README.md index e7ba75f..9f7a050 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** (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** (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** (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** (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** (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** (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** (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. + +- **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 ======= @@ -301,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 ---------- diff --git a/db2_fdw.control b/db2_fdw.control index 1bd1c62..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.2' +default_version = '18.2.0' module_pathname = '$libdir/db2_fdw' relocatable = true diff --git a/include/DB2Column.h b/include/DB2Column.h index f71d74e..cf70d05 100644 --- a/include/DB2Column.h +++ b/include/DB2Column.h @@ -7,27 +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 - 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 + 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/DB2FdwDirectModifyState.h b/include/DB2FdwDirectModifyState.h new file mode 100644 index 0000000..33e7798 --- /dev/null +++ b/include/DB2FdwDirectModifyState.h @@ -0,0 +1,68 @@ +#ifndef DB2FDWDIRECTMODIFYSTATE_H +#define DB2FDWDIRECTMODIFYSTATE_H + +#include +#include +#include +#include "ParamDesc.h" +#include "DB2ResultColumn.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 + 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) + 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/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/DB2FdwState.h b/include/DB2FdwState.h index 2c831cc..7a38e8e 100644 --- a/include/DB2FdwState.h +++ b/include/DB2FdwState.h @@ -1,9 +1,10 @@ #ifndef DB2FDWSTATE_H #define DB2FDWSTATE_H -#ifndef PARAMDESC_H +#include +#include #include "ParamDesc.h" -#endif +#include "DB2ResultColumn.h" /** DB2FdwState * FDW-specific information for RelOptInfo.fdw_private and ForeignScanState.fdw_state. @@ -13,47 +14,78 @@ * 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 + 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 + 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* 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) + 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/DB2ResultColumn.h b/include/DB2ResultColumn.h new file mode 100644 index 0000000..c16be75 --- /dev/null +++ b/include/DB2ResultColumn.h @@ -0,0 +1,47 @@ +#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. + * + * @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 + 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 + 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 + intptr_t val_null; // NULL indicator (cast to SQLLEN* at SQLBindCol) + db2NoEncErrType noencerr; // no encoding error produced + struct db2ResultColumn* next; +} DB2ResultColumn; + +#endif \ No newline at end of file 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/include/db2_fdw.h b/include/db2_fdw.h index 9b2f0ff..410ce14 100644 --- a/include/db2_fdw.h +++ b/include/db2_fdw.h @@ -1,17 +1,23 @@ -/*------------------------------------------------------------------------- +/*------------------------------------------------------------------------------- * * 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_ #ifdef POSTGRES_H + +#include +#include +#include + +#define PG_SUPPORTED_MIN_VERSION 130000 + #if PG_VERSION_NUM >= 150000 #define STRVAL(arg) ((String *)(arg))->sval #else @@ -22,58 +28,49 @@ #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 */ - -/* 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 - -/* 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) +#if PG_VERSION_NUM < PG_SUPPORTED_MIN_VERSION +#error "This extension requires PostgreSQL version 13.0 or higher." #endif -/* GetConfigOptionByName has a new signature from 9.6 on */ -#define GetConfigOptionByName(name, varname) GetConfigOptionByName(name, varname, false) - +#ifndef PQ_QUERY_PARAM_MAX_LIMIT +#define PQ_QUERY_PARAM_MAX_LIMIT 65535 +#endif /* PQ_QUERY_PARAM_MAX_LIMIT */ -/* 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)) +#else /* POSTGRES_H */ -/* older versions don't have JSONOID */ -#ifndef JSONOID -#define JSONOID InvalidOid -#endif +#include +#include -/* "table_open" was "heap_open" before v12 */ #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.2" +#define DB2_FDW_VERSION "18.2.0" /* 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 200 -#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 DEFAULT_SAMPLE_PERCENT 100.0 +#define FIXED_FETCH_SIZE +#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" @@ -118,13 +115,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" @@ -135,10 +125,20 @@ 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_DB2BYTES "db2bytes" +#define OPT_DB2CHARS "db2chars" +#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" #define OPT_BATCH_SIZE "batch_size" +#define OPT_FETCHSZ "fetchsize" /* types for the DB2 table description */ typedef enum { @@ -177,16 +177,106 @@ 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" + /* 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) #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 +#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, ...) __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) + +#define db2Entry1(fmt, ...) \ + db2EntryExit(DB2DEBUG1, 1, "> %s:%d:%s" fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) +#define db2Entry2(fmt, ...) \ + db2EntryExit(DB2DEBUG2, 1, "> %s:%d:%s" fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) +#define db2Entry3(fmt, ...) \ + db2EntryExit(DB2DEBUG3, 1, "> %s:%d:%s" fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) +#define db2Entry4(fmt, ...) \ + db2EntryExit(DB2DEBUG4, 1, "> %s:%d:%s" fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) +#define db2Entry5(fmt, ...) \ + db2EntryExit(DB2DEBUG5, 1, "> %s:%d:%s" fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) + +#define db2Exit1(fmt, ...) \ + db2EntryExit(DB2DEBUG1, 0, "< %s:%d:%s" fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) +#define db2Exit2(fmt, ...) \ + db2EntryExit(DB2DEBUG2, 0, "< %s:%d:%s" fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) +#define db2Exit3(fmt, ...) \ + db2EntryExit(DB2DEBUG3, 0, "< %s:%d:%s" fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) +#define db2Exit4(fmt, ...) \ + db2EntryExit(DB2DEBUG4, 0, "< %s:%d:%s" fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) +#define db2Exit5(fmt, ...) \ + db2EntryExit(DB2DEBUG5, 0, "< %s:%d:%s" fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) + +#define db2Debug1(fmt, ...) \ + db2Debug(DB2DEBUG1, "1 %s:%d:%s " fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) +#define db2Debug2(fmt, ...) \ + db2Debug(DB2DEBUG2, "2 %s:%d:%s " fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) +#define db2Debug3(fmt, ...) \ + db2Debug(DB2DEBUG3, "3 %s:%d:%s " fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) +#define db2Debug4(fmt, ...) \ + db2Debug(DB2DEBUG4, "4 %s:%d:%s " fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) +#define db2Debug5(fmt, ...) \ + 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__) + #ifndef MIN #define MIN(a, b) ((a) < (b) ? (a) : (b)) #endif diff --git a/source/db2AddForeignUpdateTargets.c b/source/db2AddForeignUpdateTargets.c index c6f9d82..b3437ba 100644 --- a/source/db2AddForeignUpdateTargets.c +++ b/source/db2AddForeignUpdateTargets.c @@ -1,20 +1,15 @@ #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, ...); -#if PG_VERSION_NUM < 140000 -extern char* db2strdup (const char* source); -#endif +extern bool optionIsTrue (const char* value); /** local prototypes */ #if PG_VERSION_NUM < 140000 @@ -23,8 +18,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){ @@ -35,14 +30,18 @@ void db2AddForeignUpdateTargets (PlannerInfo* root, Index rtindex,RangeTblEntry* TupleDesc tupdesc = target_relation->rd_att; int i = 0; bool has_key = false; - db2Debug1("> db2AddForeignUpdateTargets"); - db2Debug1(" add target columns for update on %d", 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); 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 +49,8 @@ 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; + #if PG_VERSION_NUM < 140000 TargetEntry *tle; /* Make a Var representing the desired value */ @@ -62,14 +62,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 + , NameStr(att->attname) + , 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,12 +78,13 @@ 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. */ 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; } - } else { - elog (ERROR, "impossible column option \"%s\"", def->defname); } } } @@ -95,5 +97,5 @@ void db2AddForeignUpdateTargets (PlannerInfo* root, Index rtindex,RangeTblEntry* ) ); } - db2Debug1("< db2AddForeignUpdateTargets"); + db2Exit1(); } diff --git a/source/db2AllocConnHdl.c b/source/db2AllocConnHdl.c index 62f3738..07161fb 100644 --- a/source/db2AllocConnHdl.c +++ b/source/db2AllocConnHdl.c @@ -1,39 +1,31 @@ #include #include -#include -#include #include "db2_fdw.h" /** external variables */ 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 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); 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); -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); + 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); - db2Debug3(" 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); @@ -43,7 +35,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]; @@ -53,7 +45,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); + 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); @@ -62,7 +54,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"); + 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 */ @@ -78,14 +70,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)"); + 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); - db2Debug1(" 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); @@ -93,9 +85,9 @@ DB2ConnEntry* db2AllocConnHdl(DB2EnvEntry* envp,const char* srvname, char* user, } } else { /* Traditional user/password authentication */ - db2Debug1(" using user/password authentication"); + db2Debug2("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); + 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); @@ -120,45 +112,45 @@ DB2ConnEntry* db2AllocConnHdl(DB2EnvEntry* envp,const char* srvname, char* user, } } } - db2Debug1("< db2AllocConnHdl - returns: %x",connp); + db2Exit1(": %x",connp); return connp; } -/** findconnEntry - * - */ -DB2ConnEntry* findconnEntry(DB2ConnEntry* start, const char* srvname, const char* user) { +/* findconnEntry */ +DB2ConnEntry* findconnEntry(DB2ConnEntry* start, const char* srvname, const char* user, const char* jwttok) { DB2ConnEntry* step = NULL; - db2Debug2(" > findconnEntry"); + db2Entry2(); for (step = start; step != NULL; step = step->right){ - /* NULL-safe comparison for JWT auth where user may be NULL or empty */ - /* Treat NULL and empty string as equivalent */ - int srv_null_or_empty = (!step->srvname || step->srvname[0] == '\0'); + /* NULL-safe comparison for JWT auth where user may be NULL */ + 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 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); + 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) { + if (srv_match && uid_match && jwt_match) { break; } } - db2Debug2(" < findconnEntry - returns: %x", step); + db2Exit2(": %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"); + db2Entry2(); new = malloc(sizeof(DB2ConnEntry)); if (start == NULL){ /* first entry in list */ new->right = new->left = NULL; @@ -176,6 +168,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); + db2Exit2(": %x",new); return new; } diff --git a/source/db2AllocEnvHdl.c b/source/db2AllocEnvHdl.c index 7d74118..04f58bc 100644 --- a/source/db2AllocEnvHdl.c +++ b/source/db2AllocEnvHdl.c @@ -1,6 +1,4 @@ #include -#include -#include #include "db2_fdw.h" /** global variables */ @@ -12,31 +10,24 @@ extern char db2Message[ERRBUFSIZE];/* contains DB2 error messages, set b /** external prototypes */ 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"); + 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 */ @@ -44,29 +35,27 @@ 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); + 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); } /* we can call db2Shutdown now */ sql_initialized = 1; - db2Debug3(" sql_initialized: %d",sql_initialized); + db2Debug3("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); + 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); } - /* - * 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,79 +64,77 @@ DB2EnvEntry* db2AllocEnvHdl(const char* nls_lang){ rootenvEntry = envp; } - db2Debug1("< db2AllocEnvHdl - returns: %x",envp); + db2Exit1(": %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) { + 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."); } - db2Debug2(" < setDB2Environment"); + db2Exit4(); } -/** 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); + db2Entry2("(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 +154,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); + 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 737b63e..759deeb 100644 --- a/source/db2AllocStmtHdl.c +++ b/source/db2AllocStmtHdl.c @@ -7,90 +7,79 @@ /* Windows doesn't have snprintf */ #define snprintf _snprintf #endif -#include -#include #include "db2_fdw.h" /** external variables */ 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 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(); + db2Entry1(); + if (db2IsLogEnabled(DB2DEBUG5)) { + DB2EnvEntry* envstep = NULL; + DB2ConnEntry* constep = NULL; + HdlEntry* hdlstep = NULL; + + db2Debug5("struct 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); + } + } + } + } /* 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); + db2Debug2("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); + 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 */ - db2Debug3(" entry->hsql: %d",entry->hsql); + db2Debug3("entry->hsql: %d",entry->hsql); entry->type = type; - db2Debug3(" entry->type: %d",entry->type); + db2Debug3("entry->type: %d",entry->type); entry->next = connp->handlelist; - db2Debug3(" 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; - db2Debug3(" set entry %x to start connp->handlelist: %x",entry,connp->handlelist); + db2Debug3("set entry %x to start connp->handlelist: %x",entry,connp->handlelist); } - db2Debug1("< db2AllocStmtHdl - returns: %x",entry); + db2Exit1(": %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 a21658a..c1eacd5 100644 --- a/source/db2AnalyzeForeignTable.c +++ b/source/db2AnalyzeForeignTable.c @@ -1,60 +1,59 @@ #include +#include +#if PG_VERSION_NUM < 140000 +#include +#endif #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, DB2Table* db2Table, unsigned long prefetch); -extern int db2ExecuteQuery (DB2Session* session, const DB2Table* db2Table, ParamDesc* paramList); -extern int db2FetchNext (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, 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 (DB2FdwState* fdw_state, Datum* values, bool* nulls) ; -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 convertTuple (DB2Session* session, DB2Table* db2Table, DB2ResultColumn* reslist, int natts, Datum* values, bool* nulls); /** 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"); + db2Entry1(); *func = acquireSampleRowsFunc; /* use positive page count as a sign that the table has been ANALYZEd */ *totalpages = 42; - db2Debug1("< db2AnalyzeForeignTable"); + db2Exit1(": true"); return true; } -/** acquireSampleRowsFunc - * Perform a sequential scan on the DB2 table and return a sampe of rows. - * 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; - - db2Debug1("> acquireSampleRowsFunc"); - elog (DEBUG1, "db2_fdw: analyze foreign table %d", RelationGetRelid (relation)); +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; + StringInfoData query; + TupleDesc tupDesc = RelationGetDescr (relation); + 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; + MemoryContext old_cxt; + MemoryContext tmp_cxt; + + db2Entry1(); + db2Debug2("db2_fdw: analyze foreign table %d", RelationGetRelid (relation)); *totalrows = 0; @@ -65,42 +64,23 @@ int acquireSampleRowsFunc (Relation relation, int elevel, HeapTuple * rows, int 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); + 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; /* 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); - - /* 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 - 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; } } @@ -116,16 +96,12 @@ int acquireSampleRowsFunc (Relation relation, int elevel, HeapTuple * rows, int appendStringInfo (&query, " SAMPLE BLOCK (%f)", sample_percent); 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); + db2Debug2("fdw_state->query: '%s'", fdw_state->query); - db2Debug3(" loop through query results"); + 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))) { + fdw_state->rowcount = -1; + 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); @@ -139,13 +115,12 @@ 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); + 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); } 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) { @@ -156,7 +131,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); + 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); @@ -166,13 +141,13 @@ int acquireSampleRowsFunc (Relation relation, int elevel, HeapTuple * rows, int 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))); + ereport (elevel, (errmsg ("\"%s\": table contains %lu rows; %d rows in sample", RelationGetRelationName (relation), fdw_state->rowcount, collected_rows-1))); - db2Debug1("< acquireSampleRowsFunc"); + db2Exit1(); return collected_rows; } diff --git a/source/db2BeginDirectModify.c b/source/db2BeginDirectModify.c new file mode 100644 index 0000000..488ff9f --- /dev/null +++ b/source/db2BeginDirectModify.c @@ -0,0 +1,236 @@ +#include +#include +#include +#include +#include +#include + +#include "db2_fdw.h" +#include "DB2FdwDirectModifyState.h" + +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; + + db2Entry1(); + /* 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); + 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)); + 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. */ + 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 + ); + } + } + db2Exit1(); +} + +/* + * 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; + + 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. + */ + 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 */ + } + db2Exit4(); + 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; + + 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 + * 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(resultTupType->natts * sizeof(AttrNumber), "attnoMap"); + 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++; + } + db2Exit4(); +} + +/* 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; + + db2Entry4(); + Assert(numParams > 0); + + /* Prepare for output conversion of parameters used in remote query. */ + *param_flinfo = db2alloc(sizeof(FmgrInfo) * numParams,"param_flinfo"); + + 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(numParams * sizeof(char *),"param_values"); + db2Exit4(); +} + diff --git a/source/db2BeginForeignInsert.c b/source/db2BeginForeignInsert.c index b2a6d8d..7f13d98 100644 --- a/source/db2BeginForeignInsert.c +++ b/source/db2BeginForeignInsert.c @@ -3,18 +3,16 @@ #include #include #include -#include #include #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, ...); extern void appendAsType (StringInfoData* dest, Oid type); extern void db2BeginForeignModifyCommon(ModifyTableState* mtstate, ResultRelInfo* rinfo, DB2FdwState* fdw_state, Plan* subplan); @@ -23,70 +21,55 @@ 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; + + db2Entry1(); + fdw_state = db2BuildInsertFdwState(rel); + /* subplan is irrelevant for pure INSERT/COPY */ + db2BeginForeignModifyCommon(mtstate, rinfo, fdw_state, NULL); + db2Exit1(); } 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); + db2Entry1(); + /* 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], i, 0); + if (!firstcol) + appendStringInfo(&sql, ", "); + else + firstcol = false; + appendAsType(&sql, fdwState->db2Table->cols[i]->pgtype); + } + appendStringInfo(&sql, ")"); + fdwState->query = sql.data; + db2Debug(2,"fdwState->query: '%s'",sql.data); + db2Exit1(": %x",fdwState); return fdwState; } \ No newline at end of file diff --git a/source/db2BeginForeignModify.c b/source/db2BeginForeignModify.c index 66ea779..e14a3e7 100644 --- a/source/db2BeginForeignModify.c +++ b/source/db2BeginForeignModify.c @@ -1,42 +1,24 @@ #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); +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) { +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"); - db2Debug2(" relid: %d", RelationGetRelid (rinfo->ri_RelationDesc)); + db2Entry1(); #if PG_VERSION_NUM < 140000 subplan = mtstate->mt_plans[subplan_index]->plan; #else @@ -44,192 +26,5 @@ 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); - - /* jwt_token */ - state->jwt_token = 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); - - /* 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", MIN(state->db2Table->cols[i]->val_size, 1073741823)) : 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; -} + db2Exit1(); +} \ No newline at end of file diff --git a/source/db2BeginForeignModifyCommon.c b/source/db2BeginForeignModifyCommon.c index f4f562d..eb4f270 100644 --- a/source/db2BeginForeignModifyCommon.c +++ b/source/db2BeginForeignModifyCommon.c @@ -3,7 +3,6 @@ #include #include #include -#include #include #include #include @@ -15,9 +14,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 db2Debug1 (const char* message, ...); -extern void* db2alloc (const char* type, size_t size); +extern void db2PrepareQuery (DB2Session* session, const char *query, DB2ResultColumn* db2ResultList, unsigned long prefetch, int fetchsize); /** local prototypes */ void db2BeginForeignModifyCommon(ModifyTableState* mtstate, ResultRelInfo* rinfo, DB2FdwState* fdw_state, Plan* subplan); @@ -28,15 +25,15 @@ void db2BeginForeignModifyCommon(ModifyTableState* mtstate, ResultRelInfo* rinfo HeapTuple tuple; int i; - db2Debug1("> db2BeginForeignModifyCommon"); + db2Entry1(); rinfo->ri_FdwState = fdw_state; /* 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->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) @@ -50,15 +47,18 @@ 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); } } /* 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"); + db2Exit1(); } diff --git a/source/db2BeginForeignScan.c b/source/db2BeginForeignScan.c index 2df26e2..5c658bb 100644 --- a/source/db2BeginForeignScan.c +++ b/source/db2BeginForeignScan.c @@ -1,7 +1,6 @@ #include #include #include -#include #include #include #include @@ -10,49 +9,80 @@ /** 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); -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 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; - List* fdw_private = fsplan->fdw_private; - List* exec_exprs = NULL; - ListCell* cell = NULL; - int index = 0; - ParamDesc* paramDesc = NULL; DB2FdwState* fdw_state = NULL; - db2Debug1("> db2BeginForeignScan"); + db2Entry1(); /* 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); + addExprParams(node); + + /* add a fake parameter "if that string appears in the query */ + if (strstr (fdw_state->query, "?/*:now*/") != NULL) { + ParamDesc* paramDesc = (ParamDesc*) db2alloc (sizeof (ParamDesc), "fdw_state->paramList->next"); + 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) + db2Debug3("begin foreign table scan on relid: %d", RelationGetRelid (node->ss.ss_currentRelation)); + else + db2Debug3("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; + db2Exit1(); +} +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; + + db2Entry1(); + /* 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); /* count, but skip deleted entries */ - ++index; if (expr == NULL) 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 @@ -73,38 +103,8 @@ void db2BeginForeignScan(ForeignScanState* node, int eflags) { paramDesc->colnum = -1; paramDesc->txts = 0; paramDesc->next = fdw_state->paramList; - db2Debug2(" paramDesc->colnum: %d ",paramDesc->colnum); - fdw_state->paramList = paramDesc; - } - - /* 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->colnum = -1; - paramDesc->txts = 1; - 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"); + db2Exit1(); } diff --git a/source/db2BindParameter.c b/source/db2BindParameter.c index 08dc0f0..f185af1 100644 --- a/source/db2BindParameter.c +++ b/source/db2BindParameter.c @@ -1,7 +1,5 @@ #include #include -#include -#include #include "db2_fdw.h" #include "ParamDesc.h" @@ -11,50 +9,45 @@ extern char db2Message[ERRBUFSIZE];/* contains DB2 error messages, set by db2CheckErr() */ /** 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 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 */ -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); - db2Debug2(" col_num : %d",col_num); - db2Debug2(" param->value : %s",param->value); - db2Debug2(" param->colnum : %d",param->colnum); - db2Debug2(" 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) { - db2Debug2(" colName : %s",db2Table->cols[param->colnum]->colName); + db2Debug2("colName : %s",param->colName); } switch (param->bindType) { case BIND_NUMBER: { - db2Debug3(" param->bindType: 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("param_ind : %d",*indicator); + db2Debug2("colType : %d - %s",param->colType,c2name(param->colType)); + switch (param->colType) { case SQL_BIGINT:{ 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); + db2Debug2("sqlbint: %d",*sqlbint); } rc = SQLBindParameter( session->stmtp->hsql , col_num , SQL_PARAM_INPUT , SQL_C_SBIGINT - , db2Table->cols[param->colnum]->colType + , param->colType , 0 , 0 , sqlbint @@ -67,15 +60,15 @@ void db2BindParameter (DB2Session* session, const DB2Table* db2Table, ParamDesc* 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); + db2Debug2("sqlsint: %d",*sqlsint); } rc = SQLBindParameter( session->stmtp->hsql , col_num , SQL_PARAM_INPUT , SQL_C_SSHORT - , db2Table->cols[param->colnum]->colType + , param->colType , 0 , 0 , sqlsint @@ -88,15 +81,15 @@ void db2BindParameter (DB2Session* session, const DB2Table* db2Table, ParamDesc* 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); + db2Debug2("sqlint: %d",*sqlint); } rc = SQLBindParameter( session->stmtp->hsql , col_num , SQL_PARAM_INPUT , SQL_C_SLONG - , db2Table->cols[param->colnum]->colType + , param->colType , 0 , 0 , sqlint @@ -111,30 +104,29 @@ void db2BindParameter (DB2Session* session, const DB2Table* db2Table, ParamDesc* case SQL_REAL: case SQL_DOUBLE: case SQL_DECFLOAT: { - SQL_NUMERIC_STRUCT* num = NULL; - if (param->value != NULL) { - num = db2alloc("SQL_NUMERIC_STRUCT",sizeof(SQL_NUMERIC_STRUCT)); - 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 - , db2Table->cols[param->colnum]->colType - , num->precision - , num->scale - , num - , sizeof(*num) + , SQL_C_CHAR + , param->colType + , param->colSize + , 0 + , (SQLPOINTER) param->value + , 0 , indicator ); } 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; @@ -142,15 +134,15 @@ void db2BindParameter (DB2Session* session, const DB2Table* db2Table, ParamDesc* } break; case BIND_STRING: { - db2Debug3(" param->bindType: BIND_STRING"); + db2Debug3("param->bindType: BIND_STRING"); *indicator = (SQLLEN) ((param->value == NULL) ? SQL_NULL_DATA : SQL_NTS); - db2Debug2(" param_ind : %d",*indicator); + db2Debug2("param_ind : %d",*indicator); rc = SQLBindParameter( session->stmtp->hsql , col_num , SQL_PARAM_INPUT , SQL_C_CHAR , SQL_VARCHAR - , db2Table->cols[param->colnum]->colSize + , param->colSize , 0 , (SQLPOINTER) param->value , 0 @@ -159,15 +151,15 @@ void db2BindParameter (DB2Session* session, const DB2Table* db2Table, ParamDesc* } break; case BIND_LONGRAW: { - db2Debug3(" param->bindType: BIND_LONGRAW"); + db2Debug3("param->bindType: BIND_LONGRAW"); *indicator = (SQLLEN) ((param->value == NULL) ? SQL_NULL_DATA : SQL_NTS); - db2Debug2(" param_ind : %d",*indicator); + db2Debug2("param_ind : %d",*indicator); rc = SQLBindParameter( session->stmtp->hsql , col_num , SQL_PARAM_INPUT , SQL_C_BINARY , SQL_LONGVARBINARY - , db2Table->cols[param->colnum]->colSize + , param->colSize , 0 , (SQLPOINTER) param->value , 0 @@ -176,16 +168,16 @@ void db2BindParameter (DB2Session* session, const DB2Table* db2Table, ParamDesc* } break; case BIND_LONG: { - db2Debug3(" param->bindType: BIND_LONG"); + db2Debug3("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); + db2Debug2("param_ind : %d",*indicator); + db2Debug2("param->value : '%s'",param->value); rc = SQLBindParameter( session->stmtp->hsql , col_num , SQL_PARAM_INPUT , SQL_C_CHAR , SQL_LONGVARCHAR - , db2Table->cols[param->colnum]->colSize + , param->colSize , 0 , (SQLPOINTER) param->value , 0 @@ -196,14 +188,14 @@ void db2BindParameter (DB2Session* session, const DB2Table* db2Table, ParamDesc* case BIND_OUTPUT: { SQLSMALLINT fcType; SQLSMALLINT fParamType; - db2Debug2(" param->bindType: BIND_OUTPUT"); + 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) { + db2Debug2("param_ind : %d",*indicator); + 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 +203,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 ); } @@ -225,5 +217,5 @@ void db2BindParameter (DB2Session* session, const DB2Table* db2Table, ParamDesc* if (rc != SQL_SUCCESS) { db2Error_d(FDW_UNABLE_TO_CREATE_EXECUTION, "error executing query: SQLBindParameter failed to bind parameter", db2Message); } - db2Debug1("< db2BindParameter"); + db2Exit1(); } \ No newline at end of file diff --git a/source/db2Callbacks.c b/source/db2Callbacks.c index 31844e5..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,7 +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 db2Debug1 (const char* message, ...); /** local prototypes */ void db2RegisterCallback (void* arg); @@ -16,31 +16,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); + db2Entry1("(arg: %x)",arg); RegisterXactCallback (transactionCallback, arg); RegisterSubXactCallback (subtransactionCallback, arg); - db2Debug1("< db2RegisterCallback"); + db2Exit1(); } -/** db2UnregisterCallback - * Unregister a callback for PostgreSQL transaction events. +/* db2UnregisterCallback + * Unregister a callback for PostgreSQL transaction events. */ void db2UnregisterCallback (void *arg) { - db2Debug1("> db2UnregisterCallback(%x)",arg); + db2Entry1("(arg: %x)",arg); UnregisterXactCallback (transactionCallback, arg); UnregisterSubXactCallback (subtransactionCallback, arg); - db2Debug1("< db2UnregisterCallback"); + db2Exit1(); } -/** transactionCallback - * Commit or rollback DB2 transactions when appropriate. +/* transactionCallback + * Commit or rollback DB2 transactions when appropriate. */ void transactionCallback (XactEvent event, void *arg) { - db2Debug1("> transactionCallback"); + db2Entry1("(event: %d, arg: %x)", event, arg); switch (event) { case XACT_EVENT_PRE_COMMIT: case XACT_EVENT_PARALLEL_PRE_COMMIT: @@ -67,16 +67,16 @@ void transactionCallback (XactEvent event, void *arg) { break; } dml_in_transaction = false; - db2Debug1("< transactionCallback"); + db2Exit1(); } -/** 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"); + 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); - db2Debug1("< subtransactionCallback"); + db2Exit1(); } diff --git a/source/db2Cancel.c b/source/db2Cancel.c index 432d7c9..d63ec11 100644 --- a/source/db2Cancel.c +++ b/source/db2Cancel.c @@ -1,5 +1,3 @@ -#include -#include #include "db2_fdw.h" /** global variables */ @@ -8,7 +6,6 @@ extern DB2EnvEntry* rootenvEntry; /* contains DB2 error messages, set by db2CheckErr() */ /** external prototypes */ -extern void db2Debug1 (const char* message, ...); /** local prototypes */ void db2Cancel (void); @@ -17,11 +14,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"); + 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) { @@ -32,5 +29,5 @@ void db2Cancel (void) { } } } - db2Debug1("< db2Cancel"); + db2Exit1(); } diff --git a/source/db2CheckErr.c b/source/db2CheckErr.c index 5c076eb..989282e 100644 --- a/source/db2CheckErr.c +++ b/source/db2CheckErr.c @@ -1,7 +1,5 @@ #include #include -#include -#include #include "db2_fdw.h" /** global variables */ @@ -11,30 +9,28 @@ 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, ...); /** 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"); + db2Entry4(); memset (db2Message,0x00,sizeof(db2Message)); switch (status) { case SQL_INVALID_HANDLE: { @@ -47,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(" MESSAGE : '%s'",message); + db2Debug5("SQLCODE : %d ",sqlcode); + 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); @@ -85,8 +112,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); + 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 95732b8..d5ea9ca 100644 --- a/source/db2ClientVersion.c +++ b/source/db2ClientVersion.c @@ -1,7 +1,5 @@ #include #include -#include -#include #include "db2_fdw.h" /** global variables */ @@ -9,24 +7,22 @@ /** external variables */ /** external prototypes */ -extern void db2Debug1 (const char* message, ...); -extern void db2Debug2 (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"); + db2Entry1(); 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); + db2Debug2("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); + db2Exit1(": '%s'", version); } diff --git a/source/db2CloseConnections.c b/source/db2CloseConnections.c index 62d28cb..1d5109b 100644 --- a/source/db2CloseConnections.c +++ b/source/db2CloseConnections.c @@ -1,5 +1,3 @@ -#include -#include #include "db2_fdw.h" /** global variables */ @@ -11,64 +9,58 @@ 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 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 db2UnregisterCallback(void* arg); 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"); + db2Entry1(); while (rootenvEntry != NULL) { while (rootenvEntry->connlist != NULL) { db2FreeConnHdl(rootenvEntry, rootenvEntry->connlist); - db2Debug3(" rootenvEntry: %x, rootenvEntry->connlist: %x",rootenvEntry, rootenvEntry->connlist); + db2Debug3("rootenvEntry: %x, rootenvEntry->connlist: %x",rootenvEntry, rootenvEntry->connlist); } db2FreeEnvHdl(rootenvEntry, NULL); } - db2Debug1("< db2CloseConnections"); + db2Exit1(); } -/** 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); + 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 */ - db2Debug2(" connp->hdbc: %x",connp->hdbc); + db2Debug3("connp->hdbc: %x",connp->hdbc); rc = SQLDisconnect(connp->hdbc); - db2Debug3(" 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 */ - db2Debug2(" connp->hdbc: %x",connp->hdbc); + db2Debug3("connp->hdbc: %x",connp->hdbc); rc = SQLFreeHandle(SQL_HANDLE_DBC, connp->hdbc); - db2Debug3(" 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); } @@ -79,30 +71,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); + db2Debug4("envp->connlist: %x",envp->connlist); } - db2Debug1("< db2FreeConnHdl"); + db2Exit2(); } -/** deleteconnEntry - * - */ +/* deleteconnEntry */ int deleteconnEntry(DB2ConnEntry* start, DB2ConnEntry* node) { int result = 0; DB2ConnEntry* step = NULL; - db2Debug1("> 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) { - db2Debug3(" 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){ - db2Debug3(" 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) { - db2Debug3(" step left null"); + db2Debug4("step left null"); step->right->left = NULL; } else if (step->right == NULL) { - db2Debug3(" step right null"); + db2Debug4("step right null"); step->left->right = NULL; } else { step->left->right = step->right; @@ -113,16 +103,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); + db2Debug4("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 (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); + } } - db2Debug1("< deleteconnEntry - returns: %d", result); + db2Exit2(": %d", result); return result; } diff --git a/source/db2CloseStatement.c b/source/db2CloseStatement.c index 665b836..edfe398 100644 --- a/source/db2CloseStatement.c +++ b/source/db2CloseStatement.c @@ -1,5 +1,3 @@ -#include -#include #include "db2_fdw.h" /** global variables */ @@ -7,25 +5,23 @@ /** external variables */ /** external prototypes */ -extern void db2Debug1 (const char* message, ...); -extern void db2Debug3 (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"); + db2Entry1(); /* 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"); + db2Debug3("no handle to close"); } - db2Debug1("< db2CloseStatement"); + db2Exit1(); } diff --git a/source/db2CopyText.c b/source/db2CopyText.c index 8d13ab6..3b214e2 100644 --- a/source/db2CopyText.c +++ b/source/db2CopyText.c @@ -1,6 +1,4 @@ #include -#include -#include #include "db2_fdw.h" /** global variables */ @@ -8,16 +6,13 @@ /** external variables */ /** external prototypes */ -extern void* db2alloc (const char* type, size_t size); -extern void db2Debug1 (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,10 +20,10 @@ 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); + 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; @@ -41,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) { @@ -53,6 +48,6 @@ char* db2CopyText (const char* string, int size, int quote) { result[++j] = '"'; result[j + 1] = '\0'; - db2Debug1("< db2CopyText - result: %s",result); + db2Exit4(": %s",result); return result; } diff --git a/source/db2Debug.c b/source/db2Debug.c index 7e9e6e1..7ea3ec4 100644 --- a/source/db2Debug.c +++ b/source/db2Debug.c @@ -1,12 +1,11 @@ +#include #include #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,16 +16,11 @@ (x==FDW_SERIALIZATION_FAILURE ? ERRCODE_T_R_SERIALIZATION_FAILURE : ERRCODE_FDW_ERROR)))))) /** local prototype */ -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 db2Error (db2error sqlstate, const char* message); +void db2Error_d (db2error sqlstate, const char* message, const char* detail, ...) __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 +33,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 */ @@ -51,58 +45,59 @@ 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); -} -/** 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); +int isLogLevel(int level) { + return (level >= log_min_messages); } -/** 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 db2EntryExit(int level, int entry, const char* message, ...) { + if (db2IsLogEnabled(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; + + } else { + --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 (db2IsLogEnabled(level)) { + char cBuffer [4000]; + int dLevel = DEBUG5; + int offset = (2*debug_depth); + va_list arg_marker; + + memset(cBuffer, ' ', offset); + cBuffer[offset] = '\0'; + + va_start (arg_marker, message); + vsnprintf (cBuffer+offset, sizeof(cBuffer)-offset, 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", cBuffer); + va_end (arg_marker); + } } diff --git a/source/db2Describe.c b/source/db2Describe.c index 558a8f4..86d4bb8 100644 --- a/source/db2Describe.c +++ b/source/db2Describe.c @@ -1,7 +1,5 @@ #include #include -#include -#include #include #include "db2_fdw.h" @@ -13,10 +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 void db2Debug1 (const char* message, ...); -extern void db2Debug2 (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); @@ -25,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. +/* 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; @@ -52,7 +46,7 @@ DB2Table* db2Describe (DB2Session* session, char* schema, char* table, char* pgn SQLINTEGER codepage = 0; SQLRETURN rc = 0; - db2Debug1("> db2Describe"); + db2Entry1(); /* get a complete quoted table name */ qtable = db2CopyText (table, strlen (table), 1); length = strlen (qtable); @@ -60,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 */ @@ -87,7 +81,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", @@ -96,15 +90,15 @@ 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); + db2Debug2("table description"); + db2Debug2("reply->name : '%s'", reply->name); reply->pgname = pgname; - db2Debug2(" reply->pgname : '%s'", reply->pgname); + db2Debug2("reply->pgname : '%s'", reply->pgname); reply->npgcols = 0; reply->batchsz = DEFAULT_BATCHSZ; @@ -121,24 +115,21 @@ 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); + 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]->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]->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) { @@ -161,20 +152,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); + 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)); + 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); + 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); + 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); + 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); @@ -183,7 +174,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); + 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); @@ -192,7 +183,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); + 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); @@ -201,7 +192,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); + 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,18 +257,12 @@ 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); + 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); - db2Debug1("< db2Describe - returns: %x", reply); + db2Exit1(": %x", reply); return reply; } diff --git a/source/db2EndDirectModify.c b/source/db2EndDirectModify.c new file mode 100644 index 0000000..efe4343 --- /dev/null +++ b/source/db2EndDirectModify.c @@ -0,0 +1,52 @@ +#include +#include "db2_fdw.h" +#include "DB2FdwDirectModifyState.h" + +/** external variables */ +extern regproc* output_funcs; + +/** external prototypes */ +extern void db2CloseStatement (DB2Session* session); + +/** local prototypes */ +void db2EndDirectModify(ForeignScanState* node); + +/* postgresEndDirectModify + * Finish a direct foreign table modification + */ +void db2EndDirectModify(ForeignScanState* node) { + DB2FdwDirectModifyState* fdw_state = (DB2FdwDirectModifyState*) node->fdw_state; + + db2Entry1(); + /* 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"); + 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,"output_funcs"); + output_funcs = NULL; + } + + db2free(fdw_state,"fdw_state"); + node->fdw_state = NULL; + db2Exit1(); +} \ No newline at end of file diff --git a/source/db2EndForeignInsert.c b/source/db2EndForeignInsert.c index 070c4b6..439b8bb 100644 --- a/source/db2EndForeignInsert.c +++ b/source/db2EndForeignInsert.c @@ -1,21 +1,21 @@ #include #include +#include "db2_fdw.h" /** external variables */ /** external prototypes */ -extern void db2Debug1 (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) { - db2Debug1("> db2EndForeignInsert"); + db2Entry1(); db2EndForeignModifyCommon(estate, rinfo); - db2Debug1("< db2EndForeignInsert"); + db2Exit1(); } diff --git a/source/db2EndForeignModify.c b/source/db2EndForeignModify.c index 48335b1..8925062 100644 --- a/source/db2EndForeignModify.c +++ b/source/db2EndForeignModify.c @@ -1,21 +1,21 @@ #include #include +#include "db2_fdw.h" /** external variables */ /** external prototypes */ extern void db2EndForeignModifyCommon(EState *estate, ResultRelInfo *rinfo); -extern void db2Debug1 (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"); + db2Entry1(); db2EndForeignModifyCommon(estate, rinfo); - db2Debug1("< db2EndForeignModify"); + db2Exit1(); } diff --git a/source/db2EndForeignModifyCommon.c b/source/db2EndForeignModifyCommon.c index eeca861..138f126 100644 --- a/source/db2EndForeignModifyCommon.c +++ b/source/db2EndForeignModifyCommon.c @@ -1,19 +1,16 @@ #include #include #include -#include #include #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 db2Debug1 (const char* message, ...); -extern void db2Debug2 (const char* message, ...); /** local prototypes */ void db2EndForeignModifyCommon(EState *estate, ResultRelInfo *rinfo); @@ -21,12 +18,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)); + db2Entry1(); + db2Debug2("relid: %d", RelationGetRelid (rinfo->ri_RelationDesc)); fdw_state = (DB2FdwState*) rinfo->ri_FdwState; if (fdw_state == NULL) { - db2Debug2(" no fdw_state, nothing to do"); + db2Debug2("no fdw_state, nothing to do"); return; } @@ -38,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; } @@ -48,10 +45,11 @@ void db2EndForeignModifyCommon(EState *estate, ResultRelInfo *rinfo) { } if (output_funcs){ - db2free(output_funcs); + db2free(output_funcs,"output_funcs"); + output_funcs = NULL; } rinfo->ri_FdwState = NULL; - db2free(fdw_state); - db2Debug1("< db2EndForeignModifyCommon"); + db2free(fdw_state,"fdw_state"); + db2Exit1(); } diff --git a/source/db2EndForeignScan.c b/source/db2EndForeignScan.c index 7e01279..7f58305 100644 --- a/source/db2EndForeignScan.c +++ b/source/db2EndForeignScan.c @@ -1,32 +1,29 @@ #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); /** 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"); + db2Entry1(); /* 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); - db2Debug1("< db2EndForeignScan"); + db2free(fdw_state,"fdw_state"); + db2Exit1(); } diff --git a/source/db2EndSubtransaction.c b/source/db2EndSubtransaction.c index 00212e3..bd7ce4e 100644 --- a/source/db2EndSubtransaction.c +++ b/source/db2EndSubtransaction.c @@ -1,6 +1,4 @@ #include -#include -#include #include "db2_fdw.h" /** external variables */ @@ -8,8 +6,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 db2Debug1 (const char* message, ...); -extern void db2Debug2 (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 +15,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 +29,7 @@ void db2EndSubtransaction (void* arg, int nest_level, int is_commit) { SQLRETURN rc = 0; HdlEntry* hstmtp = NULL; - db2Debug1("> db2EndSubtransaction"); + db2Entry1(); /* do nothing if the transaction level is lower than nest_level */ if (con->xact_level < nest_level) return; @@ -41,8 +37,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 +58,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); + db2Debug2("rollback to savepoint s%d", nest_level); snprintf ((char*)query, 49, "ROLLBACK TO SAVEPOINT s%d", nest_level); /* create statement handle */ @@ -83,5 +78,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"); + db2Exit1(); } diff --git a/source/db2EndTransaction.c b/source/db2EndTransaction.c index d5b7131..fdc232c 100644 --- a/source/db2EndTransaction.c +++ b/source/db2EndTransaction.c @@ -1,5 +1,3 @@ -#include -#include #include "db2_fdw.h" /** external variables */ @@ -7,8 +5,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 db2Debug1 (const char* message, ...); -extern void db2Debug2 (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,10 +13,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; @@ -28,50 +24,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); + 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) { - 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; + 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; + 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) { + 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); + } } + connp->xact_level = 0; + db2Debug2("connp->xact_level: %d",connp->xact_level); } - connp->xact_level = 0; - db2Debug2(" connp->xact_level: %d",connp->xact_level); - db2Debug1("< db2EndTransaction"); + db2Exit1(); } diff --git a/source/db2ExecForeignBatchInsert.c b/source/db2ExecForeignBatchInsert.c index 0da1e3c..e1f7f8e 100644 --- a/source/db2ExecForeignBatchInsert.c +++ b/source/db2ExecForeignBatchInsert.c @@ -1,36 +1,27 @@ #include - #if PG_VERSION_NUM >= 140000 #include +#include "db2_fdw.h" /** external variables */ /** external prototypes */ -extern void db2Debug1 (const char* message, ...); extern TupleTableSlot* db2ExecForeignInsert (EState* estate, ResultRelInfo* rinfo, TupleTableSlot* slot, TupleTableSlot* planSlot); /** 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. + 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. */ for (i = 0; i < *numSlots; i++) { if (slots[i] == NULL) @@ -38,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]. */ - db2Debug1("< 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 f4a517b..ec8c594 100644 --- a/source/db2ExecForeignDelete.c +++ b/source/db2ExecForeignDelete.c @@ -1,6 +1,5 @@ #include #include -#include #include #include #include "db2_fdw.h" @@ -11,29 +10,27 @@ extern bool dml_in_transaction; extern regproc* output_funcs; /** external prototypes */ -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) ; +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); 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); 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)); + db2Entry1(); + db2Debug2("relid: %d", RelationGetRelid (rinfo->ri_RelationDesc)); ++fdw_state->rowcount; dml_in_transaction = true; @@ -45,7 +42,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 @@ -61,12 +58,12 @@ 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); + 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); - db2Debug1("< db2ExecForeignDelete"); + db2Exit1(); return slot; } @@ -75,94 +72,139 @@ 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; + + db2Entry1(); 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("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) + 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 { + db2Debug3("db2Table->cols[%d]->colPrimKeyPart: %d ",param->colnum,db2Table->cols[param->colnum]->colPrimKeyPart); + if (db2Table->cols[param->colnum]->colPrimKeyPart != 0) { + 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 */ + 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); + 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); + } + 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); } - *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; + } } + } 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(value_len,"param->value"); + 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"); + db2Exit1(); } diff --git a/source/db2ExecForeignInsert.c b/source/db2ExecForeignInsert.c index d5fc892..0d9b0e1 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,24 @@ extern bool dml_in_transaction; /** external prototypes */ -extern int db2ExecuteInsert (DB2Session* session, const DB2Table* db2Table, 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, Datum* values, bool* nulls) ; +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); /** 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)); + db2Entry1(); + db2Debug2("relid: %d", RelationGetRelid (rinfo->ri_RelationDesc)); ++fdw_state->rowcount; dml_in_transaction = true; @@ -41,7 +39,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))); @@ -52,11 +50,11 @@ 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); + 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); - db2Debug1("< db2ExecForeignInsert"); + db2Exit1(); return slot; } diff --git a/source/db2ExecForeignTruncate.c b/source/db2ExecForeignTruncate.c index 8a5f835..18f011e 100644 --- a/source/db2ExecForeignTruncate.c +++ b/source/db2ExecForeignTruncate.c @@ -1,28 +1,21 @@ #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 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, ...); 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. @@ -32,11 +25,10 @@ void db2ExecForeignTruncate(List *rels, DropBehavior behavior, bool restart_seqs DB2FdwState* fdw_state = NULL; ListCell* lc; - db2Debug1("> 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 + /* 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) { @@ -48,23 +40,21 @@ 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; } } - db2Debug1("< db2ExecForeignTruncate"); + db2Exit1(); } -/** 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"); + db2Entry1(); /** Map Postgres' RESTART/CONTINUE IDENTITY to Db2's TRUNCATE options. */ if (restart_seqs) @@ -76,7 +66,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" @@ -89,8 +79,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); + 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 02a773e..e95333e 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,24 @@ extern bool dml_in_transaction; /** external prototypes */ -extern int db2ExecuteQuery (DB2Session* session, const DB2Table* db2Table, 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, Datum* values, bool* nulls) ; +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); /** 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)); + db2Entry1(); + db2Debug2("relid: %d", RelationGetRelid (rinfo->ri_RelationDesc)); ++fdw_state->rowcount; dml_in_transaction = true; @@ -42,7 +39,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 @@ -58,12 +55,12 @@ 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); + 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); - db2Debug1("< db2ExecForeignUpdate"); + db2Exit1(); return slot; } diff --git a/source/db2ExecuteInsert.c b/source/db2ExecuteInsert.c index afeae68..051f6f8 100644 --- a/source/db2ExecuteInsert.c +++ b/source/db2ExecuteInsert.c @@ -1,7 +1,5 @@ #include #include -#include -#include #include "db2_fdw.h" #include "ParamDesc.h" @@ -12,61 +10,61 @@ 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 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 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. - * 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, const DB2Table* db2Table, ParamDesc* paramList) { +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; + int indicator_count = 0; - db2Debug1("> db2ExecuteInsert"); + db2Entry1(); for (param = paramList; param != NULL; param = param->next) { ++param_count; } - db2Debug2(" paramcount: %d",param_count); - /* allocate a temporary array of indicators */ - indicators = db2alloc ("indicators", param_count * sizeof (SQLLEN)); + db2Debug2("paramcount: %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; 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 */ - db2Debug2(" 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); } - db2Debug2(" 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) { @@ -75,21 +73,21 @@ int db2ExecuteInsert (DB2Session* session, const DB2Table* db2Table, ParamDesc* } /* db2free all indicators */ - db2free (indicators); + db2free (indicators, "indicators[%d]", indicator_count); if (rc == SQL_NO_DATA) { - db2Debug3(" SQL_NO_DATA"); - db2Debug1("< db2ExecuteInsert - returns: 0"); - return 0; - } + db2Debug3("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); + } + db2Debug2("rowcount_val: %lld", rowcount_val); + rowcount = (int) rowcount_val; } - db2Debug2(" rowcount_val: %lld", rowcount_val); - rowcount = (int) rowcount_val; - db2Debug1("< db2ExecuteInsert - returns: %d",rowcount); + db2Exit1(": %d",rowcount); return rowcount; } diff --git a/source/db2ExecuteQuery.c b/source/db2ExecuteQuery.c index 1f35a75..45ec7ce 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" @@ -13,81 +11,81 @@ 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 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 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. - * 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, const DB2Table* db2Table, ParamDesc* paramList) { +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; + int indicator_count = 0; - db2Debug1("> db2ExecureQuery"); + db2Entry1(); for (param = paramList; param != NULL; param = param->next) { ++param_count; } - db2Debug2(" paramcount: %d",param_count); - /* allocate a temporary array of indicators */ - indicators = db2alloc ("indicators", param_count * sizeof (SQLLEN)); + db2Debug2("paramcount: %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; 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); + 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); } - db2Debug2(" 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) { /* 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]", indicator_count); if (rc == SQL_NO_DATA) { - db2Debug3(" SQL_NO_DATA"); - db2Debug1("< db2ExecureQuery - returns: 0"); - return 0; - } + db2Debug3("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); + } + db2Debug2("rowcount_val: %lld", rowcount_val); + rowcount = (int) rowcount_val; } - db2Debug2(" rowcount_val: %lld", rowcount_val); - rowcount = (int) rowcount_val; - db2Debug1("< db2ExecureQuery - returns: %d",rowcount); + db2Exit1(": %d",rowcount); return rowcount; } diff --git a/source/db2ExecuteTruncate.c b/source/db2ExecuteTruncate.c index f07bbb9..1d26879 100644 --- a/source/db2ExecuteTruncate.c +++ b/source/db2ExecuteTruncate.c @@ -1,6 +1,4 @@ #include -#include -#include #include "db2_fdw.h" #include "ParamDesc.h" @@ -11,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 db2Debug1 (const char* message, ...); -extern void db2Debug2 (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 +16,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); + 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 +37,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); + db2Debug2("rowcount_val: %lld", rowcount_val); rowcount = (int) rowcount_val; - db2Debug1("< db2ExecuteTruncate - returns: %d",rowcount); + db2Exit1(": %d",rowcount); return rowcount; } diff --git a/source/db2ExplainForeignModify.c b/source/db2ExplainForeignModify.c index 51cf8bf..39b4229 100644 --- a/source/db2ExplainForeignModify.c +++ b/source/db2ExplainForeignModify.c @@ -3,29 +3,26 @@ #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, ...); /** 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)); + db2Entry1(); + db2Debug2("relid: %d", RelationGetRelid (rinfo->ri_RelationDesc)); /* show query */ ExplainPropertyText ("DB2 statement", fdw_state->query, es); - db2Debug1("< db2ExplainForeignModify"); + db2Exit1(); } diff --git a/source/db2ExplainForeignScan.c b/source/db2ExplainForeignScan.c index 680b42c..5f8d2e0 100644 --- a/source/db2ExplainForeignScan.c +++ b/source/db2ExplainForeignScan.c @@ -4,54 +4,47 @@ #include #include #endif -#include #include #include #include "db2_fdw.h" #include "DB2FdwState.h" /** 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, ...); /** 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"); - 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); - db2Debug1("< db2ExplainForeignScan"); + db2Exit1(); } -/** db2Explain - * - */ -void db2Explain (void* fdw, ExplainState* es) { +/* db2Explain */ +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); char* tempQuery = NULL; char* src = fdw_state->query; char* dest = NULL; - db2Debug1("> db2Explain"); + db2Entry1(); 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){ @@ -63,26 +56,47 @@ 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. */ @@ -92,6 +106,7 @@ void db2Explain (void* fdw, ExplainState* es) { } /* close */ pclose(fp); - db2free(tempQuery); - db2Debug1("< db2Explain"); + db2free(tempQuery,"tempQuery"); + pfree(execution_cmd.data); + db2Exit1(); } diff --git a/source/db2FetchNext.c b/source/db2FetchNext.c index e98d02d..71fd5e2 100644 --- a/source/db2FetchNext.c +++ b/source/db2FetchNext.c @@ -1,6 +1,6 @@ -#include -#include +#include #include "db2_fdw.h" +#include "DB2ResultColumn.h" /** global variables */ @@ -9,30 +9,208 @@ 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 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); /** 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. +/* 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; - db2Debug1("> db2FetchNext"); + 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); } - db2Debug1("< db2FetchNext - returns: %d",(rc == SQL_SUCCESS)); + + /* 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/db2FreeEnvHdl.c b/source/db2FreeEnvHdl.c index 5684dc1..728a1a5 100644 --- a/source/db2FreeEnvHdl.c +++ b/source/db2FreeEnvHdl.c @@ -1,6 +1,4 @@ #include -#include -#include #include "db2_fdw.h" /** global variables */ @@ -12,40 +10,34 @@ 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 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"); + db2Entry1(); /* search environment handle in cache */ envp = findenvEntryHandle (rootenvEntry, envp->henv); if (envp == NULL) { - db2Debug3(" 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); - db2Debug3(" 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); @@ -55,45 +47,43 @@ void db2FreeEnvHdl(DB2EnvEntry* envp, const char* nls_lang){ } deleteenvEntry(rootenvEntry,envp); sql_initialized = 0; - db2Debug3(" sql_initialized: %d",sql_initialized); + db2Debug3("sql_initialized: %d",sql_initialized); } - db2Debug1("< db2FreeEnvHdl"); + db2Exit1(); } -/** 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); + 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; - db2Debug3(" rootenvEntry : %x", rootenvEntry); - db2Debug3(" 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; - db2Debug3(" rootenvEntry : %x", rootenvEntry); - db2Debug3(" 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; - db2Debug3(" rootenvEntry : %x", rootenvEntry); - db2Debug3(" 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; - db2Debug3(" rootenvEntry : %x", rootenvEntry); - db2Debug3(" DB2Enventry freed: %x", step); + db2Debug3("rootenvEntry : %x", rootenvEntry); + db2Debug3("DB2Enventry freed: %x", step); free (step); step = NULL; } @@ -101,43 +91,41 @@ int deleteenvEntry(DB2EnvEntry* start, DB2EnvEntry* node) { break; } } - db2Debug2(" < deleteenvEntry - returns: %d",result); + db2Exit1(": %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); + 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; - db2Debug3(" rootenvEntry : %x", rootenvEntry); - db2Debug3(" 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; - db2Debug3(" rootenvEntry : %x", rootenvEntry); - db2Debug3(" 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; - db2Debug3(" rootenvEntry : %x", rootenvEntry); - db2Debug3(" 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; - db2Debug3(" rootenvEntry : %x", rootenvEntry); - db2Debug3(" DB2Enventry freed: %x", step); + db2Debug3("rootenvEntry : %x", rootenvEntry); + db2Debug3("DB2Enventry freed: %x", step); free (step); step = NULL; } @@ -145,42 +133,38 @@ int deleteenvEntryLang(DB2EnvEntry* start, const char* nlslang) { break; } } - db2Debug2(" < deleteenvEntryLang - returns: %d",result); + db2Exit1(": %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); + db2Entry1("(start: %x, SQLHENV: %d)",start, henv); for (step = start; step != NULL; step = step->right){ if (step->henv == henv) { break; } } - db2Debug2(" < findenvEntryHandle - returns: %x",step); + db2Exit1(": %x",step); return step; } -/** findenvEntry - * - */ +/* findenvEntry */ DB2EnvEntry* findenvEntry(DB2EnvEntry* start, const char* nlslang) { DB2EnvEntry* step = NULL; - db2Debug2(" > findenvEntry(start: %x, nlslang: '%s')", start, nlslang); + db2Entry1("(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)); + 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) { - db2Debug3(" 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); } - db2Debug2(" < findenvEntry - returns: %x", step); + db2Exit1(": %x", step); return step; } diff --git a/source/db2FreeStmtHdl.c b/source/db2FreeStmtHdl.c index e017211..14fa38f 100644 --- a/source/db2FreeStmtHdl.c +++ b/source/db2FreeStmtHdl.c @@ -1,19 +1,14 @@ -#include -#include #include "db2_fdw.h" /** external variables */ /** external prototypes */ -extern void db2Debug1 (const char* message, ...); -extern void db2Debug2 (const char* message, ...); -extern void db2Debug3 (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 +18,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); + 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*/ - db2Debug3(" 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,31 +36,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); + 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; - db2Debug3(" prev_entryp->next: '%x'", prev_entryp->next); + db2Debug3("prev_entryp->next: '%x'", prev_entryp->next); } - db2Debug1(" HdlEntry freeed: %x",entryp); + db2Debug2("HdlEntry freeed: %x",entryp); free (entryp); - db2Debug1("< db2FreeStmtHdl"); + db2Exit1(); } -/** findhdlEntry - * - */ -HdlEntry* findhdlEntry (HdlEntry* start, SQLHANDLE hsql) { +/* findhdlEntry */ +static HdlEntry* findhdlEntry (HdlEntry* start, SQLHANDLE hsql) { HdlEntry* step = NULL; HdlEntry* prev = start; - db2Debug2(" > findhdlEntry"); + db2Entry4(); for (step = start; step != NULL; step = step->next){ if (step->hsql == hsql) { break; } prev = step; } - db2Debug2(" < findhdlEntry - returns: %x", prev); + db2Exit4(": %x", prev); return prev; } diff --git a/source/db2GetFdwState.c b/source/db2GetFdwState.c index 663c986..c4c0af2 100644 --- a/source/db2GetFdwState.c +++ b/source/db2GetFdwState.c @@ -1,130 +1,414 @@ #include -#include -#include -#include -#include +#include #include +#if PG_VERSION_NUM < 140000 #include +#endif +#include +#include +#include +#include #include "db2_fdw.h" #include "DB2FdwState.h" +#include "DB2FdwDirectModifyState.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 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, 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); -void getColumnData (DB2Table* db2Table, Oid foreigntableid); -bool optionIsTrue (const char* value); - -/** 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); + DB2FdwDirectModifyState* db2GetFdwDirectModifyState(Oid foreigntableid, double* sample_percent, bool describe); +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); + + +/* 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)); - char* pgtablename = get_rel_name (foreigntableid); - List* options = NULL; - ListCell* cell = NULL; - char* schema = NULL; - char* table = NULL; - char* maxlong = NULL; - char* sample = NULL; - char* fetch = NULL; - char* noencerr = NULL; - char* batchsz = NULL; - long max_long = DEFAULT_MAX_LONG; - - db2Debug1("> db2GetFdwState"); - /* Get all relevant options from the foreign table, the user mapping, - * the foreign server and the foreign data wrapper. - */ - db2GetOptions (foreigntableid, &options); +DB2FdwState* db2GetFdwState (Oid foreigntableid, double* sample_percent, bool describe) { + DB2FdwState* fdwState = db2alloc(sizeof (DB2FdwState),"DB2FdwState* fdwState"); + char* pgtablename = get_rel_name (foreigntableid); + List* options = NIL; + ListCell* cell = NULL; + char* schema = NULL; + char* table = NULL; + char* sample = NULL; + char* prefetch = NULL; + char* fetchsz = NULL; + char* noencerr = NULL; + char* batchsz = NULL; + + db2Entry1(); + /* 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); - 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) - fetch = 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; + 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) ? strtol (maxlong, NULL, 0): max_long; - /* 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); } - /* convert "prefetch" to number (or use default) */ - fdwState->prefetch = (fetch == NULL) ? DEFAULT_PREFETCH : (unsigned long) strtoul (fetch, NULL, 0); + fdwState->prefetch = (prefetch == NULL) ? DEFAULT_PREFETCH : (unsigned long) strtoul (prefetch, NULL, 0); - /* check if options are ok */ - if (table == NULL) + /* 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); - /* 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); + 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, noencerr, batchsz); + /* add PostgreSQL data to table description */ + getColumnData (fdwState->db2Table, foreigntableid); + } + } + + db2Exit1(": %x", fdwState); + return fdwState; +} + +/* 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(sizeof (DB2FdwDirectModifyState),"DB2FdwDirectModifyState* fdwState"); + char* pgtablename = get_rel_name (foreigntableid); + List* options = NIL; + ListCell* cell = NULL; + char* schema = NULL; + char* table = NULL; + char* sample = NULL; + char* prefetch = NULL; + char* fetchsz = NULL; + char* noencerr = NULL; + char* batchsz = NULL; - /* add PostgreSQL data to table description */ - getColumnData (fdwState->db2Table, foreigntableid); + db2Entry1(); + /* 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; + 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 "sample_percent" to double */ + if (sample_percent != NULL) { + if (sample == NULL) + *sample_percent = DEFAULT_SAMPLE_PERCENT; + 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, noencerr, batchsz); } + fdwState->session = db2GetSession (fdwState->dbserver, fdwState->user, fdwState->password, fdwState->jwt_token, fdwState->nls_lang, GetCurrentTransactionNestLevel () ); - db2Debug1("< db2GetFdwState"); + db2Exit1(); return fdwState; } + +static DB2Table* describeForeignTable (Oid foreigntableid, char* schema, char* table, char* pgname, char* noencerr, char* batchsz) { + DB2Table* db2Table = NULL; + char* qtable = NULL; + char* qschema = NULL; + char* tablename = NULL; + Relation rel; + TupleDesc tupdesc; + int length = 0; + + db2Entry2(); + + db2Table = (DB2Table*)db2alloc(sizeof (DB2Table),"DB2Table* db2_table"); + /* 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 (length + 1,"db2Table->name"); + tablename[0] = '\0'; /* empty */ + if (schema != NULL) { + strncat (tablename, qschema, length); + strncat (tablename, ".", length); + } + strncat (tablename, qtable,length); + db2free (qtable,"qtable"); + if (schema != NULL) + db2free (qschema,"qschema"); + + db2Table->name = tablename; + db2Debug3("table description"); + db2Debug3("db2Table->name : '%s'", db2Table->name); + db2Table->pgname = pgname; + db2Debug3("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); + } + + rel = table_open (foreigntableid, NoLock); + tupdesc = rel->rd_att; + + db2Table->npgcols = tupdesc->natts; + db2Debug3("db2Table->npgcols : %d", db2Table->npgcols); + db2Table->ncols = tupdesc->natts; + db2Debug3("db2Table->ncols : %d", 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 */ + 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 (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[%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 + ); + /* 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) { + db2Debug2("INFO: column %d - %s without required options, discarding db2Table", cidx, db2Table->cols[cidx]->pgname); + db2free (db2Table,"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; + } + 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); + db2Exit2(": %x", 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. */ -void getColumnData (DB2Table* db2Table, Oid foreigntableid) { +static void getColumnData (DB2Table* db2Table, Oid foreigntableid) { Relation rel; TupleDesc tupdesc; int i, index; - db2Debug2(" > getColumnData"); + db2Entry4(); rel = table_open (foreigntableid, NoLock); tupdesc = rel->rd_att; @@ -148,7 +432,7 @@ 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 */ @@ -168,17 +452,48 @@ void getColumnData (DB2Table* db2Table, Oid foreigntableid) { } table_close (rel, NoLock); - db2Debug2(" < getColumnData"); + db2Exit4(); } -/* optionIsTrue - * Returns true if the string is "true", "on" or "yes". +/* 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. */ -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; -} +static void getOptions (Oid foreigntableid, List** options) { + ForeignDataWrapper* wrapper = NULL; + ForeignServer* server = NULL; + UserMapping* mapping = NULL; + ForeignTable* table = NULL; + db2Entry4(); + /** 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); + } + db2Exit4(); +} \ No newline at end of file diff --git a/source/db2GetForeignJoinPaths.c b/source/db2GetForeignJoinPaths.c index 934a903..753ed84 100644 --- a/source/db2GetForeignJoinPaths.c +++ b/source/db2GetForeignJoinPaths.c @@ -1,125 +1,152 @@ #include #include #include -#include #include #include #include "db2_fdw.h" #include "DB2FdwState.h" /** external prototypes */ -extern void db2Debug1 (const char* message, ...); -extern char* deparseExpr (DB2Session* session, RelOptInfo * foreignrel, Expr* expr, const DB2Table* db2Table, List** params); -extern char* db2strdup (const char* source); -extern void* db2alloc (const char* type, size_t size); +extern char* deparseExpr (PlannerInfo* root, RelOptInfo* foreignrel, Expr* expr, List** params); /** 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); +static DB2Column* db2FindColumnByRelidAttnum(DB2Table* db2Table, int pgrelid, int pgattnum); -/** 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. +/* 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. + * 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; + DB2FdwState* fdwState_o = NULL; + DB2FdwState* fdwState_i = NULL; ForeignPath* joinpath = NULL; double joinclauses_selectivity = 0; double rows = 0; /* estimated number of returned rows */ Cost startup_cost; Cost total_cost; - db2Debug1("> db2GetForeignJoinPaths"); - /* - * Currently we don't push-down joins in query for UPDATE/DELETE. + 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) { - 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); + db2Debug2("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)) { + db2Debug2("either outerrel or ínnerel is not a simple relation"); + } else { + /* skip if this join combination has been considered already */ + if (joinrel->fdw_private) { + 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 + * 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(sizeof (DB2FdwState),"DB2FdwState* fdwState"); + + joinrel->fdw_private = fdwState; + + /* 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) + #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"); + /* + * 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; + 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); + } 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; + } + } + } + } + db2Exit1(); } -/** 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. */ -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; @@ -127,9 +154,8 @@ bool foreign_join_ok (PlannerInfo * root, RelOptInfo * joinrel, JoinType jointyp DB2Table* db2Table_i = NULL; ListCell* lc = NULL; List* otherclauses = NULL; - char* tabname = NULL;/* for warning messages */ - db2Debug1("> foreign_join_ok"); + db2Entry1(); /* we only support pushing down INNER joins */ if (jointype != JOIN_INNER) return false; @@ -143,8 +169,7 @@ bool foreign_join_ok (PlannerInfo * root, RelOptInfo * joinrel, JoinType jointyp 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,22 +178,20 @@ bool foreign_join_ok (PlannerInfo * root, RelOptInfo * joinrel, JoinType jointyp /* 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) { - 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); @@ -176,8 +199,7 @@ bool foreign_join_ok (PlannerInfo * root, RelOptInfo * joinrel, JoinType jointyp 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 +219,7 @@ bool foreign_join_ok (PlannerInfo * root, RelOptInfo * joinrel, JoinType jointyp 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 +233,7 @@ bool foreign_join_ok (PlannerInfo * root, RelOptInfo * joinrel, JoinType jointyp 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. */ @@ -236,80 +256,98 @@ bool foreign_join_ok (PlannerInfo * root, RelOptInfo * joinrel, JoinType jointyp 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"); + /* 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("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. + /* 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. */ - 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->varno == 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->varno == 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("fdw_state->db2Table->cols[idx]", sizeof (DB2Column)); + 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; - db2Debug1("< foreign_join_ok"); + db2Exit1(); return true; } diff --git a/source/db2GetForeignModifyBatchSize.c b/source/db2GetForeignModifyBatchSize.c index cc0cb5d..965ef71 100644 --- a/source/db2GetForeignModifyBatchSize.c +++ b/source/db2GetForeignModifyBatchSize.c @@ -1,63 +1,85 @@ #include #if PG_VERSION_NUM >= 140000 -#include #include #include #include #include "db2_fdw.h" +#include "DB2FdwState.h" /** external variables */ /** external prototypes */ -extern void db2Debug1 (const char* message, ...); /** local prototypes */ -int db2_get_batch_size_option (Relation rel); -int db2GetForeignModifyBatchSize(ResultRelInfo *rinfo); + 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; + int batch_size = 1; + DB2FdwState* fmstate = (DB2FdwState*) rinfo->ri_FdwState; + + db2Entry1(); + /* should be called only once */ + Assert(rinfo->ri_BatchSize == 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} + /* 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. */ - if (rel->trigdesc && (rel->trigdesc->trig_insert_before_row || rel->trigdesc->trig_insert_after_row)) { + 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 (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); + db2Exit1(": %d", batch_size); return batch_size; } -int db2_get_batch_size_option(Relation rel) { - Oid relid = RelationGetRelid(rel); - ForeignTable* table; - ForeignServer* server; - ListCell* lc; +static int db2_get_batch_size_option(Relation rel) { + Oid relid = RelationGetRelid(rel); + ForeignTable* table = NULL; + ForeignServer* server = NULL; + ListCell* lc = NULL; int batch_size = 0; - db2Debug1("> db2_get_batch_size_option"); + db2Entry1(); table = GetForeignTable(relid); server = GetForeignServer(table->serverid); @@ -98,10 +120,9 @@ 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); + 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 14bdf37..e403071 100644 --- a/source/db2GetForeignPaths.c +++ b/source/db2GetForeignPaths.c @@ -1,62 +1,71 @@ #include #include #include -#include #include #include #include "db2_fdw.h" #include "DB2FdwState.h" /** 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); + 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; - /* - * 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; char* delim = " "; - db2Debug1("> db2GetForeignPaths"); + db2Entry1(); 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; - - /** deparseExpr would detect volatile expressions as well, but - * ec_has_volatile saves some cycles. - */ + 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); if (can_pushdown) { 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 (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); @@ -72,12 +81,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; @@ -108,29 +115,25 @@ void db2GetForeignPaths(PlannerInfo* root, RelOptInfo* baserel, Oid foreigntable ,NIL ) ); - db2Debug1("< db2GetForeignPaths"); + db2Exit1(); } /* find_em_expr_for_rel - * Find an equivalence class member expression, all of whose Vars come from - * the indicated relation. + * 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) { - ListCell* lc_em = NULL; +static Expr* find_em_expr_for_rel (EquivalenceClass* ec, RelOptInfo* rel) { + ListCell* lc_em = NULL; Expr* result = NULL; - db2Debug1("> 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)) { - /* - * 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); + db2Exit4(": %x", result); return result; } diff --git a/source/db2GetForeignPlan.c b/source/db2GetForeignPlan.c index 87eda29..9080ab1 100644 --- a/source/db2GetForeignPlan.c +++ b/source/db2GetForeignPlan.c @@ -1,380 +1,365 @@ #include -#include +#include +#include +#include +#include +#include +#include #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 char* deparseExpr (DB2Session* session, RelOptInfo * foreignrel, Expr* expr, const DB2Table* db2Table, List** params); -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 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 */ -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); - -/** 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); +static void getUsedColumns (Expr* expr, RelOptInfo* foreignrel, DB2ResultColumn* resCol); +static void copyCol2Result (DB2ResultColumn* resCol, DB2Column* db2Column); + +/* 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; - - 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 */ - 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 */ - fdwState->query = createQuery (fdwState, 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 - ); - } +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_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; + Index scan_relid; + StringInfoData sql; + + 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 } - fdw_private = serializePlanData (fdwState); + + db2Debug2("length of tlist: %d", list_length(tlist)); + /* - * Create the ForeignScan node for the given relation. + * fdw_scan_tlist must contain all base Vars required to evaluate local + * quals and to compute any non-pushed-down target expressions. * - * 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. + * 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); - result = make_foreignscan (tlist, local_exprs, scan_relid, fdwState->params, fdw_private, fdw_scan_tlist, NIL, outer_plan); - db2Debug1("< db2GetForeignPlan"); - return result; -} + if (IS_SIMPLE_REL(foreignrel)) { +// DB2ResultColumn** cols = NULL; + ListCell* cell = NULL; +// int iResCol = 0; -/** 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. - */ -char* createQuery (DB2FdwState* fdwState, RelOptInfo* foreignrel, bool modify, List* query_pathkeys) { - 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); - } - } + 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; - /* 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 = ", "; - } - } + /* + * 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; - /* dummy column if there is no result column we need from DB2 */ - if (separator[0] == '\0') - appendStringInfo (&query, "'1'"); + 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)); + } - /* append FROM clause */ - appendStringInfo (&query, " FROM "); - deparseFromExprForRel (fdwState, &query, foreignrel, &(fdwState->params)); + 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)); + } - /* - * 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); - } + if (qual_vars != NIL) { + ListCell* v = NULL; - /* 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 = ' '; - } + ptlist = add_to_flat_tlist(ptlist, qual_vars); + ptlist_len = list_length(ptlist); + + foreach (v, qual_vars) { + Var* var = lfirst_node(Var, v); - /* 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; + /* 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); + } + } + } } - } - db2free (wherecopy); + 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; + 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; + } - /* - * 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); - - db2Debug1("< createQuery returns: '%s'",result.data); - return result.data; -} + /* De-duplicate by pgattnum (same base relation). */ + for (scan = fpinfo->resultList; scan; scan = scan->next) { + if (scan->pgattnum == resCol->pgattnum) { + dup = true; + break; + } + } + if (dup) { + db2free(resCol, "resCol"); + continue; + } -/** 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); + 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); + } + } + /* 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. + */ + 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); + } - appendStringInfo (buf, " %s%d", REL_ALIAS_PREFIX, foreignrel->relid); + /* For a base-relation scan, we have to support EPQ recheck, which should recheck all the remote quals. */ + fdw_recheck_quals = remote_exprs; } 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); + db2Debug3("join relation scan: set scan_relid to 0"); + /* Join relation or upper relation - set scan_relid to 0. */ + scan_relid = 0; - /* - * For a join relation FROM clause entry is deparsed as - * - * (outer relation) (inner relation) ON joinclauses + /* 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. */ - appendStringInfo (buf, "(%s %s JOIN %s ON ", join_sql_o.data, get_jointype_name (fdwState->jointype), join_sql_i.data); + 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. + */ + 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", ptlist_len); + foreach (cell, ptlist) { + DB2ResultColumn* resCol = (DB2ResultColumn*)db2alloc(sizeof(DB2ResultColumn),"DB2ResultColumn* resCol"); + db2Debug3("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); + resnum++; + } - /* 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); + /* + * 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; + } - /* End the FROM clause entry. */ - appendStringInfo (buf, ")"); - } - db2Debug2(" buf: '%s'",buf->data); - db2Debug1("< deparseFromExprForRel"); -} + fpinfo->resultList = prev; + } + } -/** 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; + /* 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) { + 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. + */ + 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 (IsA(outer_plan, NestLoop) || IsA(outer_plan, MergeJoin) || IsA(outer_plan, HashJoin)) { + Join* join_plan = (Join*) outer_plan; - 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; + 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, tlist, best_path->path.parallel_safe); } - db2Debug2(" buf.data: '%s'", buf->data); - db2Debug1("< appendConditions"); + } + + /* 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); + /* Remember remote_exprs for possible use by postgresPlanDirectModify */ + fpinfo->final_remote_exprs = remote_exprs; + + /* 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); + + /* 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. + */ + fscan = make_foreignscan(tlist, local_exprs, scan_relid, params_list, fdw_private, ptlist, fdw_recheck_quals, outer_plan); + db2Exit1(": %x",fscan); + 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. */ -void getUsedColumns (Expr* expr, DB2Table* db2Table, int foreignrelid) { - ListCell* cell; - Var* variable; - int index; +static void getUsedColumns (Expr* expr, RelOptInfo* foreignrel, DB2ResultColumn* resCol) { + ListCell* cell = NULL; - db2Debug1("> getUsedColumns"); + db2Entry3(); if (expr != NULL) { + db2Debug4("examine node of type: %d", expr->type); switch (expr->type) { case T_RestrictInfo: - getUsedColumns (((RestrictInfo*) expr)->clause, db2Table, foreignrelid); + getUsedColumns (((RestrictInfo*) expr)->clause, foreignrel, resCol); break; case T_TargetEntry: - getUsedColumns (((TargetEntry*) expr)->expr, db2Table, foreignrelid); + getUsedColumns (((TargetEntry*) expr)->expr, foreignrel, resCol); break; case T_Const: case T_Param: @@ -383,238 +368,303 @@ void getUsedColumns (Expr* expr, DB2Table* db2Table, int foreignrelid) { case T_CurrentOfExpr: case T_NextValueExpr: break; - case T_Var: - variable = (Var*) expr; + case T_Var: { + 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 (variable->varattno < 0) + if (var->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; + if (var->varattno == 0) { + DB2ResultColumn* tmpCol = NULL; + 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(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); + tmpCol->next = fpinfo->resultList; + db2Debug4("tmpCol-next: %x", tmpCol->next); + fpinfo->resultList = tmpCol; + 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; + db2Debug4("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 = 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; + /* 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 || + (relid != 0 && fpinfo->db2Table->cols[index]->pgrelid != relid))) { + --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; - case T_Aggref: - foreach (cell, ((Aggref*) expr)->args) { - getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + case T_Aggref: { + Aggref* aggref = (Aggref*) expr; + /* 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*) expr)->aggorder) { - getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); - } - foreach (cell, ((Aggref*) expr)->aggdistinct) { - getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + 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(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(*) + 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_size = 24; + col->noencerr = fpinfo->db2Table->cols[0]->noencerr; // use same noencerr as first column + copyCol2Result(resCol,col); + } else { + db2Debug4("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), db2Table, foreignrelid); + getUsedColumns ((Expr*) lfirst (cell), foreignrel, resCol); } break; case T_SubscriptingRef: { SubscriptingRef* ref = (SubscriptingRef*) expr; foreach(cell, ref->refupperindexpr) { - getUsedColumns((Expr*)lfirst(cell), db2Table, foreignrelid); + getUsedColumns((Expr*)lfirst(cell), foreignrel, resCol); } foreach(cell, ref->reflowerindexpr) { - getUsedColumns((Expr*)lfirst(cell), db2Table, foreignrelid); + getUsedColumns((Expr*)lfirst(cell), foreignrel, resCol); } - getUsedColumns(ref->refexpr, db2Table, foreignrelid); - getUsedColumns(ref->refassgnexpr, db2Table, foreignrelid); + getUsedColumns(ref->refexpr, foreignrel, resCol); + getUsedColumns(ref->refassgnexpr, foreignrel, resCol); } break; case T_FuncExpr: foreach (cell, ((FuncExpr*) expr)->args) { - getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + getUsedColumns ((Expr*) lfirst (cell), foreignrel, resCol); } break; case T_OpExpr: foreach (cell, ((OpExpr*) expr)->args) { - getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + getUsedColumns ((Expr*) lfirst (cell), foreignrel, resCol); } break; case T_DistinctExpr: foreach (cell, ((DistinctExpr*) expr)->args) { - getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + getUsedColumns ((Expr*) lfirst (cell), foreignrel, resCol); } break; case T_NullIfExpr: foreach (cell, ((NullIfExpr*) expr)->args) { - getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + getUsedColumns ((Expr*) lfirst (cell), foreignrel, resCol); } break; case T_ScalarArrayOpExpr: foreach (cell, ((ScalarArrayOpExpr*) expr)->args) { - getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + getUsedColumns ((Expr*) lfirst (cell), foreignrel, resCol); } break; case T_BoolExpr: foreach (cell, ((BoolExpr*) expr)->args) { - getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + getUsedColumns ((Expr*) lfirst (cell), foreignrel, resCol); } break; case T_SubPlan: foreach (cell, ((SubPlan*) expr)->args) { - getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + getUsedColumns ((Expr*) lfirst (cell), foreignrel, resCol); } break; case T_AlternativeSubPlan: /* examine only first alternative */ - getUsedColumns ((Expr*) linitial (((AlternativeSubPlan*) expr)->subplans), db2Table, foreignrelid); + getUsedColumns ((Expr*) linitial (((AlternativeSubPlan*) expr)->subplans), foreignrel, resCol); break; case T_NamedArgExpr: - getUsedColumns (((NamedArgExpr*) expr)->arg, db2Table, foreignrelid); + getUsedColumns (((NamedArgExpr*) expr)->arg, foreignrel, resCol); break; case T_FieldSelect: - getUsedColumns (((FieldSelect*) expr)->arg, db2Table, foreignrelid); + getUsedColumns (((FieldSelect*) expr)->arg, foreignrel, resCol); break; case T_RelabelType: - getUsedColumns (((RelabelType*) expr)->arg, db2Table, foreignrelid); + getUsedColumns (((RelabelType*) expr)->arg, foreignrel, resCol); break; case T_CoerceViaIO: - getUsedColumns (((CoerceViaIO*) expr)->arg, db2Table, foreignrelid); + getUsedColumns (((CoerceViaIO*) expr)->arg, foreignrel, resCol); break; case T_ArrayCoerceExpr: - getUsedColumns (((ArrayCoerceExpr*) expr)->arg, db2Table, foreignrelid); + getUsedColumns (((ArrayCoerceExpr*) expr)->arg, foreignrel, resCol); break; case T_ConvertRowtypeExpr: - getUsedColumns (((ConvertRowtypeExpr*) expr)->arg, db2Table, foreignrelid); + getUsedColumns (((ConvertRowtypeExpr*) expr)->arg, foreignrel, resCol); break; case T_CollateExpr: - getUsedColumns (((CollateExpr*) expr)->arg, db2Table, foreignrelid); + getUsedColumns (((CollateExpr*) expr)->arg, foreignrel, resCol); break; case T_CaseExpr: foreach (cell, ((CaseExpr*) expr)->args) { - getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + getUsedColumns ((Expr*) lfirst (cell), foreignrel, resCol); } - getUsedColumns (((CaseExpr*) expr)->arg, db2Table, foreignrelid); - getUsedColumns (((CaseExpr*) expr)->defresult, db2Table, foreignrelid); + getUsedColumns (((CaseExpr*) expr)->arg, foreignrel, resCol); + getUsedColumns (((CaseExpr*) expr)->defresult, foreignrel, resCol); break; case T_CaseWhen: - getUsedColumns (((CaseWhen*) expr)->expr, db2Table, foreignrelid); - getUsedColumns (((CaseWhen*) expr)->result, db2Table, foreignrelid); + 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), db2Table, foreignrelid); + getUsedColumns ((Expr*) lfirst (cell), foreignrel, resCol); } break; case T_RowExpr: foreach (cell, ((RowExpr*) expr)->args) { - getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + getUsedColumns ((Expr*) lfirst (cell), foreignrel, resCol); } break; case T_RowCompareExpr: foreach (cell, ((RowCompareExpr*) expr)->largs) { - getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + getUsedColumns ((Expr*) lfirst (cell), foreignrel, resCol); } foreach (cell, ((RowCompareExpr*) expr)->rargs) { - getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + getUsedColumns ((Expr*) lfirst (cell), foreignrel, resCol); } break; case T_CoalesceExpr: foreach (cell, ((CoalesceExpr*) expr)->args) { - getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + getUsedColumns ((Expr*) lfirst (cell), foreignrel, resCol); } break; case T_MinMaxExpr: foreach (cell, ((MinMaxExpr*) expr)->args) { - getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + getUsedColumns ((Expr*) lfirst (cell), foreignrel, resCol); } break; case T_XmlExpr: foreach (cell, ((XmlExpr*) expr)->named_args) { - getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + getUsedColumns ((Expr*) lfirst (cell), foreignrel, resCol); } foreach (cell, ((XmlExpr*) expr)->args) { - getUsedColumns ((Expr*) lfirst (cell), db2Table, foreignrelid); + getUsedColumns ((Expr*) lfirst (cell), foreignrel, resCol); } break; case T_NullTest: - getUsedColumns (((NullTest*) expr)->arg, db2Table, foreignrelid); + getUsedColumns (((NullTest*) expr)->arg, foreignrel, resCol); break; case T_BooleanTest: - getUsedColumns (((BooleanTest*) expr)->arg, db2Table, foreignrelid); + getUsedColumns (((BooleanTest*) expr)->arg, foreignrel, resCol); break; case T_CoerceToDomain: - getUsedColumns (((CoerceToDomain*) expr)->arg, db2Table, foreignrelid); + getUsedColumns (((CoerceToDomain*) expr)->arg, foreignrel, resCol); break; case T_PlaceHolderVar: - getUsedColumns (((PlaceHolderVar*) expr)->phexpr, db2Table, foreignrelid); + getUsedColumns (((PlaceHolderVar*) expr)->phexpr, foreignrel, resCol); 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. + /* 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. - */ -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; + db2Exit3(); } -/** Output join name for given join type +/* copyCol2Result + * Copy the column information from the db2Table column to the result column. */ -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; +static void copyCol2Result(DB2ResultColumn* resCol, DB2Column* column) { + db2Entry4(); + if (resCol && resCol->colName == NULL) { + resCol->colName = db2strdup(column->colName,"resCol->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->pgbaserelid = column->pgrelid; + resCol->pgname = db2strdup(column->pgname,"resCol->pgname"); + resCol->pgattnum = column->pgattnum; + resCol->pgtype = column->pgtype; + resCol->pgtypmod = column->pgtypmod; + resCol->pkey = column->pkey; + resCol->val_size = column->val_size; + resCol->noencerr = column->noencerr; } - db2Debug2(" type: '%s'",type); - db2Debug1("< get_jointype_name"); - return type; + db2Exit4(); } diff --git a/source/db2GetForeignPlanOld.c b/source/db2GetForeignPlanOld.c new file mode 100644 index 0000000..e08083d --- /dev/null +++ b/source/db2GetForeignPlanOld.c @@ -0,0 +1,576 @@ +#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 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,"wherecopy"); + 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,"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,"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"); +} + +/** 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 diff --git a/source/db2GetForeignRelSize.c b/source/db2GetForeignRelSize.c index 770f9f9..796d0d6 100644 --- a/source/db2GetForeignRelSize.c +++ b/source/db2GetForeignRelSize.c @@ -1,19 +1,30 @@ #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* deparseExpr (DB2Session* session, RelOptInfo * foreignrel, Expr* expr, const DB2Table* db2Table, List** params); -extern void db2free (void* p); +extern char* deparseWhereConditions (PlannerInfo* root, RelOptInfo* baserel); +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); -char* deparseWhereConditions(DB2FdwState* fdwState, RelOptInfo* baserel, List** local_conds, List** remote_conds); + 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. @@ -22,32 +33,37 @@ char* deparseWhereConditions(DB2FdwState* fdwState, RelOptInfo* baserel, List** */ void db2GetForeignRelSize (PlannerInfo* root, RelOptInfo* baserel, Oid foreigntableid) { DB2FdwState* fdwState = NULL; - int i = 0; - double ntuples = -1; - db2Debug1("> db2GetForeignRelSize"); + db2Entry1(); /* get connection options, connect and get the remote table description */ fdwState = db2GetFdwState(foreigntableid, NULL, true); - /** 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. + /* 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); + db2Exit1(); +} + +static void db2PopulateFdwStateOld(PlannerInfo* root, RelOptInfo* baserel, Oid foreigntableid){ + DB2FdwState* fdwState = (DB2FdwState*)baserel->fdw_private; + int i = 0; + double ntuples = -1; + + 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]->varno = baserel->relid; + 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 ( fdwState - , baserel - , &(fdwState->local_conds) - , &(fdwState->remote_conds) - ); + 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; @@ -65,39 +81,192 @@ 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"); + db2Exit1(); +} + +static void db2PopulateFdwStateNew(PlannerInfo* root, RelOptInfo* baserel, Oid foreigntableid){ + DB2FdwState* fpinfo = (DB2FdwState*)baserel->fdw_private; + ListCell* lc = NULL; + + db2Entry1(); + /* 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; + db2Exit1(); } -/** 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. +/* Parse options from foreign server and apply them to fpinfo. + * New options might also require tweaking merge_fdw_options(). */ -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); +static void apply_server_options(DB2FdwState* fpinfo) { + ListCell* lc; + + db2Entry4(); + 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); + } + db2Exit4(); +} + +/* 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; + + db2Entry4(); + 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); + } + 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 + * 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; + + 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 */ + 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))); } } - db2Debug1("< deparseWhereCondition - where_clause: '%s'",where_clause.data); - return where_clause.data; + list_free(extlist); + db2Exit4(": %x", extensionOids); + return extensionOids; } diff --git a/source/db2GetForeignUpperPaths.c b/source/db2GetForeignUpperPaths.c new file mode 100644 index 0000000..f397aa5 --- /dev/null +++ b/source/db2GetForeignUpperPaths.c @@ -0,0 +1,1331 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "db2_fdw.h" +#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 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); +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 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); + 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) { + db2Entry1(); + if (root != NULL && root->parse != NULL && input_rel->fdw_private != NULL && output_rel->fdw_private == NULL) { + 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"); + 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"); + db2Debug3("query->hasRowSecurity : %s", query->hasRowSecurity ? "true" : "false"); + + 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_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; + 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; + #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); + /* + * 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); + 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); + } + db2Exit1(); +} + +/* 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. + */ +static void db2CloneFdwStateUpper(PlannerInfo* root, RelOptInfo* input_rel, RelOptInfo* output_rel) { + DB2FdwState* fdw_in = (DB2FdwState*)input_rel->fdw_private; + DB2FdwState* copy = NULL; + + db2Entry4(); + if (fdw_in != NULL) { + copy = (DB2FdwState*) db2alloc(sizeof(DB2FdwState), "copy"); + + /* Start from a full struct copy so we don't leave fields uninitialized. */ + *copy = *fdw_in; + + /* 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; + + /* 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(); +} + +static DB2Table* db2CloneDb2TableForPlan(const DB2Table* src) { + DB2Table* dst = NULL; + int i; + + db2Entry4(); + if (src != NULL) { + dst = (DB2Table*) db2alloc(sizeof(DB2Table),"DB2Table* dst"); + + 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(sizeof(DB2Column*) * src->ncols,"dst->cols(%d)",src->ncols); + for (i = 0; i < src->ncols; ++i) { + dst->cols[i] = db2CloneDb2ColumnForPlan(src->cols[i]); + } + } else { + dst->cols = NULL; + } + } + db2Exit4(": %x", dst); + return dst; +} + +static DB2Column* db2CloneDb2ColumnForPlan(const DB2Column* src) { + DB2Column* dst = NULL; + + db2Entry4(); + if (src != NULL) { + 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,"dst->colName") : NULL; + dst->pgname = src->pgname ? db2strdup(src->pgname,"dst->pgname") : NULL; + } + db2Exit4(": %x", dst); + return dst; +} + +/* 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; + + db2Entry4(); + /* Nothing to be done, if there is no grouping or aggregation required. */ + if (!parse->groupClause && !parse->groupingSets && !parse->hasAggs && !root->hasHavingQual) { + 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 */ + 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)) { + 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 + * 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 + #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); + } + } + db2Exit4(); +} + +/* 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; + + 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) { + db2Debug5("no support where there are any SRFs in the targetlist"); + } else { + bool isSafe = true; + + // 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->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 (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; + 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); + /* 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) { + 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)) { + 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) { + db2Debug5("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 = 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 */ + 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. + */ + #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); + } + } + } + db2Exit4(); +} + +/* 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; + + db2Entry4(); + + /** Currently, we only support this for SELECT commands */ + if (parse->commandType != CMD_SELECT) { + 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) { + 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) { + db2Debug5("no support for any SRFs in the targetlist"); + db2Exit4(); + 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->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 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 + #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 */ + add_path(final_rel, (Path *) final_path); + + /* Safe to push down */ + fpinfo->pushdown_safe = true; + 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; + } + } + + /* 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. + */ + 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; + } + + 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) { + db2Debug5("the underlying relation has any local conditions, the LIMIT/OFFSET cannot be pushed down"); + db2Exit4(); + 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) { + 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)) { + db2Debug5("the LIMIT/OFFSET cannot be pushed down, if their expressions are not safe to remote"); + db2Exit4(); + return; + } + + /* Safe to push down */ + fpinfo->pushdown_safe = true; + + /* Construct 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; + 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. */ + #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 */ + add_path(final_rel, (Path*) final_path); + 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) { + 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. + * 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 + #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; + } 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; + } + db2Exit4(); +} + +/* 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; + + db2Entry4(); + /* We currently don't support pushing Grouping Sets. */ + if (query->groupingSets) { + db2Debug5("no support pushing Grouping Sets"); + db2Exit4(); + 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) { + db2Debug5("foreign_grouping_ok: local_conds found"); + db2Exit4(": 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. + * + * 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)) { + 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)) { + db2Debug5("foreign_grouping_ok: foreign param found"); + db2Exit4(": 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. + */ + 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)) { + db2Debug5("foreign_grouping_ok: non-foreign aggvar found"); + db2Exit4(": 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. + */ + 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)); + #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 + 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)) { + db2Exit4(": false"); + 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); + db2Exit4(": 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. + * + * 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. + */ +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; + + db2Entry4(); + /* 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 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 + , fpinfo->grouped_tlist + ) + , input_rows, NULL, NULL + ); + #endif + + /* 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; + db2Exit4(); +} + +/* 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) { + db2Entry4(); + /* 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; + } + db2Exit4(); +} diff --git a/source/db2GetImportColumn.c b/source/db2GetImportColumn.c deleted file mode 100644 index 5df3c9b..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 UPPER(T.TABSCHEMA) = UPPER(?) AND T.TYPE IN ('T','V') AND COALESCE(C.HIDDEN,'') = '' 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 UPPER(T.TABSCHEMA) = UPPER(?) AND T.TYPE IN ('T','V') AND UPPER(T.TABNAME) IN (%s) AND COALESCE(C.HIDDEN,'') = '' 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 UPPER(T.TABSCHEMA) = UPPER(?) AND T.TYPE IN ('T','V') AND UPPER(T.TABNAME) NOT IN (%s) AND COALESCE(C.HIDDEN,'') = '' 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, 1, SQL_PARAM_INPUT, 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/db2GetLob.c b/source/db2GetLob.c index 8600639..336e260 100644 --- a/source/db2GetLob.c +++ b/source/db2GetLob.c @@ -1,7 +1,6 @@ #include -#include -#include #include "db2_fdw.h" +#include "DB2ResultColumn.h" /** global variables */ @@ -9,38 +8,31 @@ 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 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, ...); /** internal prototypes */ -void db2GetLob (DB2Session* session, DB2Column* column, int cidx, char** value, long* value_len); +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. +/* db2GetLob + * Get the LOB contents and store them in *value and *value_len. */ -void db2GetLob (DB2Session* session, DB2Column* column, int cidx, char** value, long* value_len) { +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; - db2Debug1("> db2GetLob"); - db2Debug2(" column->colName : '%s'",column->colName); - db2Debug2(" cidx : %d ",cidx); - db2Debug2(" column->pgattnum : %d ",column->pgattnum); + 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 { - db2Debug2(" value_len: %ld",*value_len); - db2Debug2(" reading %d byte chunck of data",sizeof(buf)); - rc = SQLGetData(session->stmtp->hsql, cidx, fcType, buf, sizeof(buf), &ind); + db2Debug2("value_len: %ld",*value_len); + db2Debug2("reading %d byte chunck of data",sizeof(buf)); + 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); @@ -48,54 +40,54 @@ void db2GetLob (DB2Session* session, DB2Column* column, int cidx, char** value, if (rc != 100) { switch(ind) { case SQL_NULL_DATA: - db2Debug3(" data length is null (SQL_NULL_DATA)"); + db2Debug3("data length is null (SQL_NULL_DATA)"); extend = 0; break; case SQL_NO_TOTAL: - db2Debug3(" undefined data length (SQL_NO_TOTAL)"); + db2Debug3("undefined data length (SQL_NO_TOTAL)"); extend = LOB_CHUNK_SIZE; break; default: - db2Debug3(" 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 */ - db2Debug2(" value_len: %ld", *value_len); - db2Debug2(" 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); + *value = db2alloc (*value_len + extend + 1,"*value"); } else { *value = NULL; - db2Debug3(" 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); + *value = db2realloc (*value_len + extend, *value, "*value"); } // append the buffer read to the value excluding 0 termination byte - db2Debug2(" *value : %x", *value); - db2Debug2(" *value_len: %x", *value_len); + db2Debug2("*value : %x", *value); + db2Debug2("*value_len: %x", *value_len); if (*value != NULL) { - db2Debug3(" 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 { - db2Debug3(" 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 */ - db2Debug2(" *value : %x" , *value); - db2Debug2(" value_len: %ld", *value_len); + db2Debug2("*value : %x" , *value); + db2Debug2("value_len: %ld", *value_len); if (*value != NULL) { (*value)[*value_len] = '\0'; - db2Debug2(" strlen of lob: %ld", strlen(*value)); + db2Debug2("strlen of lob: %ld", strlen(*value)); } else { - db2Debug2(" strlen of lob: 0 since *value is NULL"); + db2Debug2("strlen of lob: 0 since *value is NULL"); } - db2Debug1("< db2GetLob"); + db2Exit1(); } diff --git a/source/db2GetOptions.c b/source/db2GetOptions.c deleted file mode 100644 index e1c190f..0000000 --- a/source/db2GetOptions.c +++ /dev/null @@ -1,57 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include "db2_fdw.h" - -/** external prototypes */ -extern void db2Debug1 (const char* message, ...); - -/** local prototypes */ -void db2GetOptions(Oid foreigntableid, List** options); - -/** 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) { - ForeignTable* table = NULL; - ForeignServer* server = NULL; - UserMapping* mapping = NULL; - ForeignDataWrapper* wrapper = 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"); -} \ No newline at end of file diff --git a/source/db2GetSession.c b/source/db2GetSession.c index 035ae5b..4941a99 100644 --- a/source/db2GetSession.c +++ b/source/db2GetSession.c @@ -1,5 +1,3 @@ -#include -#include #include "db2_fdw.h" /** global variables */ @@ -8,19 +6,16 @@ extern DB2EnvEntry* rootenvEntry; /* contains DB2 error messages, set by db2CheckErr() */ /** external prototypes */ -extern void* db2alloc (const char* type, size_t size); -extern void db2Debug1 (const char* message, ...); -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 */ 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 +25,7 @@ DB2Session* db2GetSession (const char* srvname, char* user, char* password, char DB2EnvEntry* envp = NULL; DB2ConnEntry* connp = NULL; - db2Debug1("> db2GetSession"); + db2Entry1(); /* it's easier to deal with empty strings */ if (!srvname) srvname = ""; if (!user) user = ""; @@ -39,22 +34,22 @@ 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); + db2Debug2("rootenvEntry: %x", rootenvEntry); envp = findenvEntry (rootenvEntry, nls_lang); 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); } if (connp->xact_level <= 0) { - db2Debug2(" db2_fdw::db2GetSession: begin serializable remote transaction"); + db2Debug2("db2_fdw::db2GetSession: begin serializable remote transaction"); connp->xact_level = 1; } /* 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; @@ -62,6 +57,6 @@ DB2Session* db2GetSession (const char* srvname, char* user, char* password, char /* set savepoints up to the current level */ db2SetSavepoint (session, curlevel); - db2Debug1("< db2GetSession"); + db2Exit1(); return session; } diff --git a/source/db2GetShareFileName.c b/source/db2GetShareFileName.c index c3dbedf..3e0f0a6 100644 --- a/source/db2GetShareFileName.c +++ b/source/db2GetShareFileName.c @@ -1,27 +1,26 @@ #include #include -#include #include #include #include #include "db2_fdw.h" /** external prototypes */ -extern void db2Debug1 (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"); + 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); - db2Debug1("< db2GetShareFileName - returns: '%s'",result); + db2Exit1(": %s",result); return result; } diff --git a/source/db2ImportForeignSchema.c b/source/db2ImportForeignSchema.c index c95b937..521718d 100644 --- a/source/db2ImportForeignSchema.c +++ b/source/db2ImportForeignSchema.c @@ -1,89 +1,61 @@ #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 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 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 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); -char* fold_case (char* name, fold_t foldcase); + 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 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; + List* options = NULL; + ListCell* cell = NULL; + DB2Session* session = NULL; fold_t foldcase = CASE_SMART; StringInfoData buf; - StringInfoData tblist; bool readonly = false; - bool firstcol = true; - db2Debug1("> db2ImportForeignSchema"); - - /* 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); + List* result = NIL; + ForeignServer* server = NULL; + db2Entry1(); + /* 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 */ foreach (cell, stmt->options) { DefElem *def = (DefElem *) lfirst (cell); - db2Debug2(" 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) @@ -93,21 +65,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) - 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))); + 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"))); @@ -119,191 +84,79 @@ 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->tabel_list : '%x'",stmt->table_list); - db2Debug2(" 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); - initStringInfo (&tblist); - if (stmt->list_type != FDW_IMPORT_SCHEMA_ALL) { - foreach (cell, stmt->table_list) { - RangeVar* rVar = lfirst(cell); - char* uppername; - char* folded; - db2Debug2(" rVar : %x ", rVar); - if (rVar == NULL || rVar->relname == NULL) - continue; + if (isForeignSchema (session, stmt->remote_schema)) { + StringInfoData tblist; + char** tablist = NULL; + initStringInfo(&buf); + initStringInfo(&tblist); - /* - * 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); + if (stmt->list_type != FDW_IMPORT_SCHEMA_ALL) { + foreach (cell, stmt->table_list) { + RangeVar* rVar = lfirst(cell); + char* uppername = NULL; + char* folded = NULL; + db2Debug2("rVar : %x ", rVar); + if (rVar == NULL || rVar->relname == NULL) + continue; - folded = fold_case (rVar->relname, foldcase); - rVar->relname = folded; - } - } - 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); + /* + * 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"); - 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; - } + folded = fold_case (rVar->relname, foldcase); + rVar->relname = folded; - 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'"); } - appendStringInfo (&buf, ")"); - db2Debug2 (" pg fdw table ddl: '%s'",buf.data); - result = lappend (result, db2strdup (buf.data)); - db2Debug2("result(%d):%x",list_length(result), result); - } - - 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,"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,"buf.data")); + resetStringInfo (&buf); } - /* part of the primary key */ - if (key) - appendStringInfo (&buf, " OPTIONS (key 'true')"); - /* not nullable */ - if (!colNulls) - appendStringInfo (&buf, " NOT NULL"); } - } while (rc == 1); - db2Debug2("result(%d):%x",list_length(result), result); - db2Debug1("< db2ImportForeignSchema : result(%d):%x",list_length(result), result); + db2free (tablist,"tablist"); + } + db2Exit1(": %d", list_length(result)); return result; } /* 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"); + 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); @@ -314,13 +167,167 @@ 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"); } } } if (result == NULL) { elog (ERROR, "impossible case folding type %d", foldcase); } - db2Debug1("< fold_case - returns: '%s'", result); + db2Exit4(": '%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; + + db2Entry4(); + initStringInfo(&coldef); + foldedname = fold_case (db2Table->name, foldcase); + appendStringInfo( buf + , "CREATE FOREIGN TABLE \"%s\".\"%s\" (" + , local_schema + , 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,"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, ")"); + db2Exit4(": %s", buf->data); +} + +/* 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; + + db2Entry4(); + /* 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); + db2Exit4(": %x", server); + return server; +} \ No newline at end of file diff --git a/source/db2ImportForeignSchemaData.c b/source/db2ImportForeignSchemaData.c new file mode 100644 index 0000000..1cd3117 --- /dev/null +++ b/source/db2ImportForeignSchemaData.c @@ -0,0 +1,535 @@ +#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 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, ...); +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 = ?"; + + 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"); + 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); + db2Exit1(": %s, result = %s", 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; + 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 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 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 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); + } + 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( (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(strlen((char*)tab_buf)+1, "tabname"); + 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((tabidx + 1) * sizeof(char*), tabnames, "tabnames"); + } + tabnames[tabidx] = NULL; + /* release the statement handle */ + db2FreeStmtHdl(stmtp, session->connp); + stmtp = NULL; + db2free(column_query,"column_query"); + db2Exit1(": [%d]", 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; + + db2Entry1("(schema: %s, tablename: %s)", 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 (length + 1,"tablename"); + tablename[0] = '\0'; /* empty */ + if (schema != NULL) { + strncat (tablename, qschema,length); + strncat (tablename, ".",length); + } + strncat (tablename, qtable,length); + db2free (qtable,"qtable"); + if (schema != NULL) + db2free (qschema,"qschema"); + + /* construct a "SELECT * FROM ..." query to describe columns */ + length += 40; + query = db2alloc (length + 1, "query"); + 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,"query"); + + /* allocate an db2Table struct for the results */ + reply = db2alloc (sizeof (DB2Table),"DB2Table* reply"); + 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 (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 (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; + 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,"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; + 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); + } + db2Exit1(": %x", 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 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); + /* 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); + db2Exit1(); +} \ No newline at end of file diff --git a/source/db2IsForeignRelUpdatable.c b/source/db2IsForeignRelUpdatable.c index e263d14..8674a19 100644 --- a/source/db2IsForeignRelUpdatable.c +++ b/source/db2IsForeignRelUpdatable.c @@ -1,25 +1,21 @@ #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); /** 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"); + db2Entry1(); /* loop foreign table options */ foreach (cell, GetForeignTable (RelationGetRelid (rel))->options) { DefElem *def = (DefElem *) lfirst (cell); @@ -28,7 +24,7 @@ int db2IsForeignRelUpdatable(Relation rel) { return 0; } result = (1 << CMD_UPDATE) | (1 << CMD_INSERT) | (1 << CMD_DELETE); - db2Debug1("< db2IsForeignRelUpdatable - returns: %d", result); + db2Exit1(": %d", result); return result; } diff --git a/source/db2IsStatementOpen.c b/source/db2IsStatementOpen.c index dcab4e5..5e68969 100644 --- a/source/db2IsStatementOpen.c +++ b/source/db2IsStatementOpen.c @@ -1,5 +1,3 @@ -#include -#include #include "db2_fdw.h" /** global variables */ @@ -7,18 +5,17 @@ /** external variables */ /** external prototypes */ -extern void db2Debug1 (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"); + db2Entry1(); result = (session->stmtp != NULL && session->stmtp->hsql != SQL_NULL_HSTMT); - db2Debug1("< db2IsStatementOpen - result: %d",result); + db2Exit1(": %d",result); return result; } diff --git a/source/db2IterateDirectModify.c b/source/db2IterateDirectModify.c new file mode 100644 index 0000000..e8b8e3b --- /dev/null +++ b/source/db2IterateDirectModify.c @@ -0,0 +1,64 @@ +#include +#include +#include +#include "db2_fdw.h" +#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, DB2ResultColumn* resultList); +extern void db2CloseStatement (DB2Session* session); + +/** 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; + TupleTableSlot* slot = node->ss.ss_ScanTupleSlot; +// ResultRelInfo* rtinfo = node->resultRelInfo; + int have_result = 0; + 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; + + 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); + + slot = ExecClearTuple (slot); + if (dmstate->num_tuples) { + Instrumentation* instr = node->ss.ps.instrument; + + /* 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 */ + } else { + /* close the statement */ + db2CloseStatement (dmstate->session); + } + + db2Exit1(": %x", slot); + return slot; +} diff --git a/source/db2IterateForeignScan.c b/source/db2IterateForeignScan.c index 403c1b3..a784050 100644 --- a/source/db2IterateForeignScan.c +++ b/source/db2IterateForeignScan.c @@ -2,7 +2,6 @@ #include #include #include -#include #include #include #include @@ -11,48 +10,43 @@ /** external prototypes */ extern int db2IsStatementOpen (DB2Session* session); -extern void db2PrepareQuery (DB2Session* session, const char* query, DB2Table* db2Table, unsigned long prefetch); -extern int db2ExecuteQuery (DB2Session* session, const DB2Table* db2Table, ParamDesc* paramList); -extern int db2FetchNext (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, DB2ResultColumn* resultList); 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) ; +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); /** 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"); + + db2Entry1(); if (db2IsStatementOpen (fdw_state->session)) { - db2Debug3(" 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); + 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); /* 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); - have_result = db2ExecuteQuery (fdw_state->session, fdw_state->db2Table, fdw_state->paramList); - have_result = db2FetchNext (fdw_state->session); + 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, fdw_state->resultList); } /* initialize virtual tuple */ ExecClearTuple (slot); @@ -60,14 +54,15 @@ 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); + 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); /* store the virtual tuple */ ExecStoreVirtualTuple (slot); } else { /* close the statement */ db2CloseStatement (fdw_state->session); } - db2Debug1("< db2IterateForeignScan"); + db2Exit1(); return slot; } @@ -75,7 +70,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; @@ -85,9 +80,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); + db2Entry4(); + db2Debug5("paramList: %x",paramList); + db2Debug5("econtext : %x",econtext); initStringInfo (&info); @@ -103,10 +98,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) { @@ -146,7 +143,7 @@ char* setSelectParameters (ParamDesc* paramList, ExprContext* econtext) { /* reset memory context */ MemoryContextSwitchTo (oldcontext); - db2Debug1("< setSelectParameters - returns: '%s'",info.data); + db2Exit4(": %s", info.data); return info.data; } diff --git a/source/db2PlanDirectModify.c b/source/db2PlanDirectModify.c new file mode 100644 index 0000000..0a64032 --- /dev/null +++ b/source/db2PlanDirectModify.c @@ -0,0 +1,285 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "db2_fdw.h" +#include "DB2FdwState.h" + +/** external prototypes */ +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); +#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. + */ +bool db2PlanDirectModify(PlannerInfo* root, ModifyTable* plan, Index rtindex, int subplan_index) { + bool fResult = true; + + 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) { + #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) { + 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]; + /* skip deserialization of plan data*/ + 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) { + #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; + + /* 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; + } + } + #endif + } + db2Debug2("fResult: %s",(fResult) ? "true" : "false"); + 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; + #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); + } + #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 { + fResult = false; + } + } else { + fResult = false; + } + } else { + fResult = false; + } + db2Exit1(": %s", (fResult) ? "true" : "false"); + 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. + * + * 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); + + 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. + * 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. + */ + db2Debug3("subplan from outerplan: %x",subplan); + if (IsA(subplan, Append)) { + Append* append = (Append*) subplan; + + db2Debug4("subplan is Append"); + if (subplan_index < list_length(append->appendplans)) { + subplan = (Plan*) list_nth(append->appendplans, subplan_index); + db2Debug3("subplan from appendplan: %x",subplan); + } + } else if (IsA(subplan, Result) && outerPlan(subplan) != NULL && IsA(outerPlan(subplan), Append)) { + Append* append = (Append*) outerPlan(subplan); + + db2Debug4("subplan is Result"); + if (subplan_index < list_length(append->appendplans)) { + subplan = (Plan*) list_nth(append->appendplans, subplan_index); + db2Debug3("subplan from resultplan: %x",subplan); + } + } + + /* 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/db2PlanForeignModify.c b/source/db2PlanForeignModify.c index 32c469d..10255f1 100644 --- a/source/db2PlanForeignModify.c +++ b/source/db2PlanForeignModify.c @@ -2,37 +2,29 @@ #include #include #include -#include -#include #include #include #include "db2_fdw.h" #include "DB2FdwState.h" +#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 void db2Debug1 (const char* message, ...); -extern void db2Debug2 (const char* message, ...); -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); -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); -List* serializePlanData (DB2FdwState* fdwState); -Const* serializeString (const char* s); -Const* serializeLong (long i); + 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); -/** 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; @@ -52,18 +44,20 @@ 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 */ + + 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. */ #if PG_VERSION_NUM >= 160000 - RTEPermissionInfo *perminfo = getRTEPermissionInfo(root->parse->rteperminfos, rte); + perminfo = getRTEPermissionInfo(root->parse->rteperminfos, rte); updated_cols = bms_copy(perminfo->updatedCols); #else updated_cols = bms_copy(rte->updatedCols); #endif /* PG_VERSION_NUM >= 160000 */ - db2Debug1("> db2PlanForeignModify"); /* we don't support INSERT ... ON CONFLICT */ if (plan->onConflictAction != ONCONFLICT_NONE) @@ -76,19 +70,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 */ @@ -196,7 +185,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; @@ -222,7 +211,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; @@ -231,7 +220,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); + db2Debug2("sql: '%s'",sql.data); /* throw a meaningful error if nothing is updated */ if (firstcol) ereport (ERROR @@ -256,7 +245,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"); @@ -290,7 +279,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; @@ -308,39 +297,65 @@ 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); + db2Debug2("fdwState->query: '%s'", fdwState->query); /* return a serialized form of the plan state */ result = serializePlanData (fdwState); - db2Debug1("< db2PlanForeignModify"); + db2Exit1(": %x", result); return result; } -/* copyPlanData - * Create a deep copy of the argument, copy only those fields needed for planning. +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. */ -DB2FdwState* copyPlanData (DB2FdwState* orig) { +static DB2FdwState* copyPlanData (DB2FdwState* orig) { int i = 0; DB2FdwState* copy = NULL; - db2Debug1("> copyPlanData"); - 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); + db2Entry4(); + 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; @@ -349,41 +364,44 @@ 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; 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; copy->rowcount = 0; - copy->columnindex = 0; copy->temp_cxt = NULL; copy->order_clause = NULL; - db2Debug1("< copyPlanData"); + db2Exit4(": %x", copy); 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, Oid pgtype, short colType, int colnum, int txts) { +void addParam (ParamDesc **paramList, DB2Column* db2col, int colnum, int txts) { ParamDesc *param; - db2Debug1("> addParam"); - param = db2alloc("paramList->next",sizeof (ParamDesc)); - param->type = pgtype; - switch (c2dbType(colType)) { + db2Entry1(); + db2Debug2("pgtype: %d",db2col->pgtype); + db2Debug2("colType: %d",db2col->colType); + db2Debug2("colnum: %d",colnum); + db2Debug2("txts: %d",txts); + 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); + 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: @@ -402,39 +420,44 @@ void addParam (ParamDesc **paramList, Oid pgtype, short colType, int colnum, int default: param->bindType = BIND_STRING; } - param->value = NULL; - param->node = NULL; - param->colnum = colnum; - param->txts = txts; - db2Debug2(" param->colnum: '%d'",param->colnum); - param->next = *paramList; - *paramList = param; - db2Debug1("> addParam"); + db2Debug2("param->bindType: '%d'",param->bindType); + param->value = NULL; + db2Debug2("param->value: %x",param->value); + 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; + db2Debug2("param->colnum: %d",param->colnum); + param->txts = txts; + db2Debug2("param->txts: %d",param->txts); + param->next = *paramList; + *paramList = param; + db2Exit1(); } /* checkDataType - * Check that the DB2 data type of a column can be - * converted to the PostgreSQL data type, raise an error if not. + * 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); + 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) { - db2Debug5(" DB2_BLOB can be converted into BYTEAOID"); + db2Debug5("DB2_BLOB can be converted into BYTEAOID"); } else if (db2type == DB2_XML && pgtype == XMLOID) { - db2Debug5(" 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)) { - db2Debug5(" 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)) { - db2Debug5(" 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)) { - db2Debug5(" 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)) { - db2Debug5(" 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) { - db2Debug5(" 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 @@ -447,113 +470,5 @@ 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)); - /* jwt_token */ - result = lappend (result, serializeString (fdwState->jwt_token)); - /* 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 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; + db2Exit4(); } diff --git a/source/db2PrepareQuery.c b/source/db2PrepareQuery.c index 6018912..008aca9 100644 --- a/source/db2PrepareQuery.c +++ b/source/db2PrepareQuery.c @@ -1,8 +1,10 @@ #include -#include -#include +#include #include "db2_fdw.h" #include "ParamDesc.h" +#include "DB2ResultColumn.h" + +#define SQL_VALUE_PTR_ULEN(v) ((SQLPOINTER)(uintptr_t)(SQLULEN)(v)) /** global variables */ @@ -10,9 +12,6 @@ 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 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, ...); @@ -21,26 +20,48 @@ extern SQLSMALLINT c2param (SQLSMALLINT fparamType); extern char* param2name (SQLSMALLINT fparamType); /** internal prototypes */ -void db2PrepareQuery (DB2Session* session, const char *query, DB2Table* db2Table, unsigned long prefetch); - -/** 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); + +/* 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, DB2Table* db2Table, unsigned long prefetch) { - int i = 0; - int col_pos = 0; - int is_select = 0; - int for_update = 0; - SQLRETURN rc = 0; - - db2Debug1("> db2PrepareQuery"); - db2Debug2(" query : '%s'",query); - db2Debug2(" prefetch: %d ",prefetch); +void db2PrepareQuery (DB2Session* session, const char *query, DB2ResultColumn* resultList, unsigned long prefetch, int fetchsize) { + int col_pos = 0; + int is_select = 0; + 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); + 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); @@ -52,95 +73,189 @@ void db2PrepareQuery (DB2Session* session, const char *query, DB2Table* db2Table /* 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); + db2Debug2("session->stmtp->hsql: %d",session->stmtp->hsql); /* set prefetch options */ if (is_select) { - unsigned long prefetch_rows = prefetch; - db2Debug3(" IS_SELECT"); + SQLULEN prefetch_rows = prefetch; + SQLULEN cur_fetchsize = fetchsize; + db2Debug3("IS_SELECT"); if (for_update) { - db2Debug3(" 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); } - db2Debug3(" 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); } - db2Debug3(" 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); + /* + * 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"); } - // Prefetch rows per block for scrollable (non-dynamic) cursors - rc = SQLSetStmtAttr(session->stmtp->hsql, SQL_ATTR_PREFETCH_NROWS, (SQLPOINTER)prefetch_rows, 0); + // Fetch rows per network roundtrip + 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 number of prefetched rows in statement handle", db2Message); + 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); + + /* + * 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 */ - db2Debug2(" 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) { 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); + /* 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. + * 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; + } + + /* + * 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); + 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 : %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)); + + if (need_getdata && fparamType == SQL_C_CHAR) { /* - * 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. + * In SQL_RD_OFF mode, SQLFetch will not populate bound columns. + * Initialize as NULL; db2FetchNext() will populate via SQLGetData. */ - /* 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); + 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++; } + + 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(*)). + /* 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. */ @@ -150,6 +265,5 @@ void db2PrepareQuery (DB2Session* session, const char *query, DB2Table* db2Table db2Error_d ( FDW_UNABLE_TO_CREATE_EXECUTION, "error executing query: SQLBindCol failed to define result value", db2Message); } } - - db2Debug1("< db2PrepareQuery"); + db2Exit1(); } diff --git a/source/db2ReAllocFree.c b/source/db2ReAllocFree.c index ec6f787..fd42513 100644 --- a/source/db2ReAllocFree.c +++ b/source/db2ReAllocFree.c @@ -1,50 +1,76 @@ #include -#include -#include -#include #include "db2_fdw.h" /*+ external prototypes */ -extern void db2Debug5 (const char* message, ...); /** 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 - * Expose palloc() to DB2 functions. +/* 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 - * Expose repalloc() to DB2 functions. +/* 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 - * Expose pfree() to DB2 functions. + +/* 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); } } -char* db2strdup(const char* source) { - char* target = NULL; +/* db2StrDup + * Expose pstrdup() to DB2 functions. + */ +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/db2ReScanForeignScan.c b/source/db2ReScanForeignScan.c index 898dc2a..d6e7e12 100644 --- a/source/db2ReScanForeignScan.c +++ b/source/db2ReScanForeignScan.c @@ -1,29 +1,27 @@ #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); /** 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"); + db2Entry1(); /* close open DB2 statement if there is one */ db2CloseStatement(fdw_state->session); /* reset row count to zero */ fdw_state->rowcount = 0; - db2Debug1("< db2ReScanForeignScan"); + db2Exit1(); } diff --git a/source/db2ServerVersion.c b/source/db2ServerVersion.c index ac31e11..de2d4a6 100644 --- a/source/db2ServerVersion.c +++ b/source/db2ServerVersion.c @@ -1,6 +1,4 @@ #include -#include -#include #include "db2_fdw.h" /** global variables */ @@ -8,24 +6,22 @@ /** external variables */ /** external prototypes */ -extern void db2Debug1 (const char* message, ...); -extern void db2Debug2 (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"); + db2Entry1(); 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); + db2Debug2("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); + db2Exit1(": '%s'", version); } diff --git a/source/db2SetHandlers.c b/source/db2SetHandlers.c index c24fff0..cfbb0d0 100644 --- a/source/db2SetHandlers.c +++ b/source/db2SetHandlers.c @@ -1,42 +1,41 @@ #include #include #include -#include #include #include #include "db2_fdw.h" /** external prototypes */ extern void db2Cancel (void); -extern void db2Debug1 (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) { + db2Entry5(); pqsignal (SIGTERM, db2Die); + db2Exit5(); } -/** 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. + db2Entry1(); + /* 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"); + db2Exit1(); } \ No newline at end of file diff --git a/source/db2SetSavepoint.c b/source/db2SetSavepoint.c index 2066e89..26f2347 100644 --- a/source/db2SetSavepoint.c +++ b/source/db2SetSavepoint.c @@ -1,6 +1,4 @@ #include -#include -#include #include "db2_fdw.h" /** global variables */ @@ -9,8 +7,6 @@ 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 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 +15,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); + + 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]; - db2Debug2(" 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); - db2Debug2(" 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"); @@ -55,6 +52,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"); + db2Debug2("xact_level: %d",session->connp->xact_level); + db2Exit1(); } diff --git a/source/db2Shutdown.c b/source/db2Shutdown.c index db9ae5b..978c1cb 100644 --- a/source/db2Shutdown.c +++ b/source/db2Shutdown.c @@ -1,5 +1,3 @@ -#include -#include #include "db2_fdw.h" /** global variables */ @@ -10,24 +8,23 @@ 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 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"); + db2Entry1(); /* don't report error messages */ silent = 1; db2CloseConnections(); /* done with DB2 */ if (sql_initialized) db2FreeEnvHdl(rootenvEntry, NULL); - db2Debug1("< db2Shutdown"); + db2Exit1(); } diff --git a/source/db2_de_serialize.c b/source/db2_de_serialize.c new file mode 100644 index 0000000..8c1c12f --- /dev/null +++ b/source/db2_de_serialize.c @@ -0,0 +1,405 @@ +#include +#include +#include +#include "db2_fdw.h" +#include "DB2FdwState.h" + +/** external prototypes */ +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 (sizeof(DB2FdwState),"DB2FdwState"); + int idx = 0; + int i = 0; + int len = 0; + ParamDesc* param = NULL; + + db2Entry1(); + /* 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->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++)); + /* jwt-token */ + state->jwt_token = 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++)); + /* table data */ + 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 (sizeof (DB2Column*) * state->db2Table->ncols,"state->db2Table->cols"); + + /* loop columns */ + for (i = 0; i < state->db2Table->ncols; ++i) { + 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); + 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); + 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); + 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); + 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); + 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); + 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); + 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); + db2Debug3("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); + 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); + state->db2Table->cols[i]->pgtype = DatumGetObjectId(((Const*)list_nth(list, idx++))->constvalue); + 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); + 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); + 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); + db2Debug3("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); + state->db2Table->cols[i]->noencerr = deserializeLong(list_nth(list, idx++)); + db2Debug3("deserialize col[%d].noencerr: %ld" ,i, state->db2Table->cols[i]->noencerr); + } + + /* 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 (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); + db2Debug3("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); + param->type = DatumGetObjectId(((Const*)list_nth(list, idx++))->constvalue); + db2Debug3("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); + if (param->bindType == BIND_OUTPUT) + param->value = (void *) 42; /* something != NULL */ + else + param->value = NULL; + db2Debug3("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); + param->node = NULL; + db2Debug3("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); + param->txts = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); + db2Debug3("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 (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); + db2Debug3("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); + res->colScale = (short) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); + db2Debug3("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); + res->colChars = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); + db2Debug3("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); + res->colPrimKeyPart = (size_t) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); + db2Debug3("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); + res->pgname = deserializeString(list_nth(list, idx++)); + db2Debug3("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); + res->pgtype = DatumGetObjectId(((Const*)list_nth(list, idx++))->constvalue); + db2Debug3("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); + res->pkey = (int) DatumGetInt32(((Const*)list_nth(list, idx++))->constvalue); + db2Debug3("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); + res->noencerr = deserializeLong(list_nth(list, idx++)); + 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 (MIN(res->val_size + 1, 1073741823), "res->val"); + res->val_len = 0; + res->val_null = 1; + res->next = state->resultList; + state->resultList = res; + } + db2Exit1(": %x", state); + return state; +} + +/** deserializeString + * Extracts a string from a Const, returns a deep copy. + */ +static char* deserializeString (Const* constant) { + char* result = NULL; + db2Entry5(); + if (!constant->constisnull) + result = text_to_cstring (DatumGetTextP (constant->constvalue)); + db2Exit5(": '%s'", result); + return result; +} + +/** deserializeLong + * Extracts a long integer from a Const. + */ +static long deserializeLong (Const* constant) { + long result = 0L; + db2Entry5(); + result = (sizeof (long) <= 4) ? (long) DatumGetInt32 (constant->constvalue) + : (long) DatumGetInt64 (constant->constvalue); + db2Exit5(": %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; + DB2ResultColumn* rcol = NULL; + + db2Entry1(); + 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)); + /* jwt_token */ + result = lappend (result, serializeString (fdwState->jwt_token)); + /* 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)); + db2Debug3("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); + result = lappend (result, serializeInt (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)); + db2Debug3("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); + result = lappend (result, serializeInt (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)); + db2Debug3("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); + result = lappend (result, serializeInt (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)); + db2Debug3("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); + result = lappend (result, serializeOid (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)); + db2Debug3("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); + result = lappend (result, serializeInt (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)); + db2Debug3("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); + /* 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)); + db2Debug3("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); + result = lappend (result, serializeInt (param->colType)); + db2Debug3("serialize param.colType: %d" , param->colType); + result = lappend (result, serializeInt (param->colSize)); + db2Debug3("serialize param.colSize: %d" , param->colSize); + result = lappend (result, serializeOid (param->type)); + db2Debug3("serialize param.type: %d" , param->type); + result = lappend (result, serializeInt ((int) param->bindType)); + db2Debug3("serialize param.bindType: %d" , param->bindType); + result = lappend (result, serializeLong (param->val_size)); + db2Debug3("serialize param.val_size: %ld" , param->val_size); + result = lappend (result, serializeInt ((int) param->colnum)); + db2Debug3("serialize param.colnum: %d" , param->colnum); + result = lappend (result, serializeInt ((int) param->txts)); + db2Debug3("serialize param.txts: %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)); + db2Debug3("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); + result = lappend (result, serializeInt (rcol->colType)); + db2Debug3("serialize res.colType: %d" , rcol->colType); + result = lappend (result, serializeInt (rcol->colSize)); + db2Debug3("serialize res.colSize: %d" , rcol->colSize); + result = lappend (result, serializeInt (rcol->colScale)); + db2Debug3("serialize res.colScale: %d" , rcol->colScale); + result = lappend (result, serializeInt (rcol->colNulls)); + db2Debug3("serialize res.colNulls: %d", rcol->colNulls); + result = lappend (result, serializeInt (rcol->colChars)); + db2Debug3("serialize res.colChars: %d" , rcol->colChars); + result = lappend (result, serializeInt (rcol->colBytes)); + db2Debug3("serialize res.colBytes: %d" , rcol->colBytes); + result = lappend (result, serializeInt (rcol->colPrimKeyPart)); + db2Debug3("serialize res.colPrimKeyPart: %d", rcol->colPrimKeyPart); + result = lappend (result, serializeInt (rcol->colCodepage)); + db2Debug3("serialize res.codepage: %d" , rcol->colCodepage); + result = lappend (result, serializeString (rcol->pgname)); + db2Debug3("serialize res.pgname: %s" , rcol->pgname); + result = lappend (result, serializeInt (rcol->pgattnum)); + db2Debug3("serialize res.pgattnum: %d" , rcol->pgattnum); + result = lappend (result, serializeOid (rcol->pgtype)); + db2Debug3("serialize res.pgtype: %d" , rcol->pgtype); + result = lappend (result, serializeInt (rcol->pgtypmod)); + db2Debug3("serialize res.pgtypmod: %d" , rcol->pgtypmod); + result = lappend (result, serializeInt (rcol->pkey)); + db2Debug3("serialize res.pkey: %d" , rcol->pkey); + result = lappend (result, serializeLong (rcol->val_size)); + db2Debug3("serialize res.val_size: %ld", rcol->val_size); + result = lappend (result, serializeInt (rcol->noencerr)); + db2Debug3("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); + lenParam--; + } + + /* don't serialize params, startup_cost, total_cost, rowcount, temp_cxt, order_clause and where_clause */ + db2Exit1(": %x",result); + return result; +} + +/** serializeString + * Create a Const that contains the string. + */ +static Const* serializeString (const char* s) { + Const* result = NULL; + db2Entry5(); + result = (s == NULL) ? makeNullConst (TEXTOID, -1, InvalidOid) + : makeConst (TEXTOID, -1, InvalidOid, -1, PointerGetDatum (cstring_to_text (s)), false, false); + db2Exit5(": %x",result); + return result; +} + +/** serializeLong + * Create a Const that contains the long integer. + */ +static Const* serializeLong (long i) { + Const* result = NULL; + db2Entry5(); + 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 */ + ); + db2Exit5(": %x",result); + return result; +} diff --git a/source/db2_deparse.c b/source/db2_deparse.c new file mode 100644 index 0000000..5e736b6 --- /dev/null +++ b/source/db2_deparse.c @@ -0,0 +1,3814 @@ +#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 + +#include "db2_fdw.h" +#include "DB2FdwState.h" + +/** 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 */ + 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 */ + 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 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 */ +void appendAsType (StringInfoData* dest, Oid type); +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); +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); +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 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 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); + + 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 + */ +void classifyConditions(PlannerInfo* root, RelOptInfo* baserel, List* input_conds, List** remote_conds, List** local_conds) { + ListCell* lc = NULL; + + db2Entry1(); + *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); + } + db2Exit1(); +} + +/** 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 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. + */ + 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)) { + // 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)) { + fResult = true; + } + } + } + db2Exit1(": %s", (fResult) ? "true": "false"); + /* OK to evaluate on the remote server */ + return fResult; +} + +/** 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 fResult = true; + + db2Entry1(); + /* Need do nothing for empty subexpressions */ + 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 (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 = 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 */ + 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 */ + db2Exit1(": %s", (fResult) ? "true" : "false"); + return fResult; +} + +/** 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 fResult = false; + db2Entry1(); + 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; + DB2FdwState* fpinfo = (DB2FdwState*) (baserel->fdw_private); + Relids relids; + + relids = (IS_UPPER_REL(baserel)) ? fpinfo->outerrel->relids : baserel->relids; + fResult = !(bms_is_member(var->varno, relids) && var->varlevelsup == 0); + } + break; + case T_Param: + /* Params always have to be sent to the foreign server */ + fResult = true; + default: + break; + } + } + db2Exit1(": %s", (fResult) ? "true" : "false"); + return fResult; +} + +/** 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 fResult = false; + + 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 */ + if (is_shippable(pathkey->pk_opfamily, OperatorFamilyRelationId, fpinfo)) { + /* can push if a suitable EC member exists */ + fResult = (find_em_for_rel(root, pathkey_ec, baserel) != NULL); + } + } + db2Exit1(": %s", (fResult) ? "true" : "false"); + return fResult; +} + +/* 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; + char* result = NULL; + + db2Entry1(); + if (!is_builtin(type_oid)) + flags |= FORMAT_TYPE_FORCE_QUALIFY; + + result = format_type_extended(type_oid, typemod, flags); + db2Exit1(": %s", result); + return result; +} + +/** appendAsType + * Append "s" to "dest", adding appropriate casts for datetime "type". + */ +void appendAsType (StringInfoData* dest, Oid type) { + db2Entry1(); + 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); + db2Exit1(); +} + +/** Deparse GROUP BY clause. + */ +static void appendGroupByClause(List* tlist, deparse_expr_cxt* context) { + Query* query = context->root->parse; + + db2Entry1(); + /* Nothing to be done, if there's no GROUP BY clause in the query. */ + 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); + } + db2Exit1(); +} + +/** 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 = NULL; + int nestlevel = 0; + StringInfo buf = context->buf; + bool gotone = false; + + db2Entry1(); + /* 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). + */ + #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. */ + appendOrderBySuffix(oprid, exprType((Node *) em_expr), pathkey->pk_nulls_first, context); + } + reset_transmission_modes(nestlevel); + db2Debug5("clause: %s", context->buf->data); + db2Exit1(); +} + +/** Deparse LIMIT/OFFSET clause. + */ +static void appendLimitClause(deparse_expr_cxt* context) { + PlannerInfo* root = context->root; + StringInfo buf = context->buf; + int nestlevel = 0; + + db2Entry1(); + /* Make sure any constants in the exprs are printed portably */ + nestlevel = set_transmission_modes(); + + /* + * 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); + db2Exit1(); +} + +/** 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; +// +// db2Entry1(); +// 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); +// db2Exit1(": %s", context->buf->data); +//} + +/** 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; + + db2Entry1(); + /* 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); + } + appendStringInfo(buf, " NULLS %s", (nulls_first) ? "FIRST" : "LAST"); + + db2Exit1(": %s", buf->data); +} + +/* 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); + + db2Entry1(); +// appendStringInfo(buf, "$%d::%s", paramindex, ptypename); + appendStringInfo(buf, ":p%d", paramindex); + db2Exit1(": %s", buf->data); +} + +/* 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); + + db2Entry1(); + appendStringInfo(buf, "((SELECT null::%s)::%s)", ptypename, ptypename); + db2Exit1(": %s", buf->data); +} + +/** 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; + + db2Entry1(); + /* 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); + db2Exit1(": %s", buf->data); +} + +/* 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; + + db2Entry1(); + 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; + } + db2Exit1(": %s", buf->data); +} + +/** 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 = get_sortgroupref_tle(ref, tlist); + Expr* expr = tle->expr; + + db2Entry1(); + 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, ')'); + } + db2Debug5("clause: %s", buf->data); + db2Exit1(": %x", 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) { + bool fResult = false; + + 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. */ + 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); + } + } + } + } + db2Exit1(": %s", (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 = 1; + ListCell* lc = NULL; + bool fFound = false; + + db2Entry1(); + /* Get the relation alias ID */ + *relno = fpinfo->relation_index; + db2Debug5("relno: %d", *relno); + + /* Get the column alias ID */ + 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; + db2Debug5("colno: %d", *colno); + fFound = true; + break; + } + i++; + } + + if (!fFound) { + /* Shouldn't get here */ + elog(ERROR, "unexpected expression in subquery output"); + } + db2Exit1(); +} + +/* 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; + + 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; + 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); + tlist = add_to_flat_tlist(tlist, pull_var_clause((Node*) rinfo->clause, PVC_RECURSE_PLACEHOLDERS)); + } + } + db2Exit1(": %x", tlist); + 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; + + 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)); + + // 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); + db2Exit1(": %s", buf->data); +} + +/* + * 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; + + db2Entry1(); + // 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 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); + + deparseTargetList(buf, rte, foreignrel->relid, rel, false, fpinfo->attrs_used, false, retrieved_attrs); + table_close(rel, NoLock); + } + } + db2Exit1(": %s", buf->data); +} + +/* + * 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; + + 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)); + + /* 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); + db2Exit1(": %s",buf->data); +} + +/* + * 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; + + db2Entry1(); + 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; + } + db2Exit1(); + 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; + } + db2Exit1(); + 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); + } + db2Exit1(); +} + +/* 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. + */ +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; + + db2Entry1(); + /* 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 { + deparseFromExprForRel(buf, root, foreignrel, true, ignore_rel, ignore_conds, additional_conds, params_list); + } + db2Exit1(": %s", buf->data); +} + +/** 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; + + db2Entry1(); + 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, "where"); + } else { + fdwState->local_conds = lappend (fdwState->local_conds, ((RestrictInfo*) lfirst (cell))->clause); + } + } + db2Exit1(": %s",where_clause.data); + return where_clause.data; +} + +/* Construct a simple "TRUNCATE rel" statement */ +void deparseTruncateSql(StringInfo buf, List* rels, DropBehavior behavior, bool restart_seqs) { + ListCell* cell = NULL; + + db2Entry1(); + 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"); + db2Exit1(": %s",buf->data); +} + +/* 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) { + db2Entry1(); + /* 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(str_toupper (colname, strlen (colname), DEFAULT_COLLATION_OID))); + } + db2Exit1(": %s", buf->data); +} + +/* 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 = NULL; + const char* nspname = NULL; + const char* relname = NULL; + ListCell* lc = NULL; + + db2Entry1(); + /* 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") == 0) + nspname = defGetString(def); + else if (strcmp(def->defname, "table") == 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)); + 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; + + 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. + */ + 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, '\''); + db2Debug5("literal: %s",buf->data); + db2Exit1(); +} + +/** 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; + db2Entry1(); + if (expr != NULL) { + deparse_expr_cxt* ctx = db2alloc(sizeof(deparse_expr_cxt),"deparseExpr.context"); + 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,"buf.data") : NULL; + db2free(ctx->buf->data,"ctx->buf->data"); + db2free(ctx, "ctx"); + } + db2Exit1(": %s", retValue); + return retValue; +} + +static void deparseExprInt (Expr* expr, deparse_expr_cxt* ctx) { + db2Entry1(); + 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); + deparseVar ((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"); + } + break; + } + } + db2Exit1(": %s", ctx->buf->data); +} + +static void deparseConstExpr (Const* expr, deparse_expr_cxt* ctx) { + db2Entry1(); + 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); + } + } + db2Exit1(); +} + +static void deparseParamExpr (Param* expr, deparse_expr_cxt* ctx) { + ListCell* cell = NULL; + char parname[10]; + + db2Entry1(); + /* 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); + } + db2Exit1(); +} + +//static void deparseVarExpr (Var* expr, deparse_expr_cxt* ctx) { +// const DB2Table* var_table = NULL; /* db2Table that belongs to a Var */ +// +// 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) +// var_table = ((DB2FdwState*)ctx->foreignrel->fdw_private)->db2Table; +// } 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; +// } +// 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)"); +// } +// } +// } +// } +// } +// } +// db2Exit1(); +//} + +/* 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) { + int relno = 0; + int colno = 0; + /* Qualify columns when multiple relations are involved. */ + bool qualify_col = (bms_membership( ctx->scanrel->relids) == BMS_MULTIPLE); + bool is_query_var = false; + + 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. + */ + 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; + } + #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); + } 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); + } + } + db2Exit1(": %s",ctx->buf->data); +} + +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 ; + + db2Entry1(); + /* 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,"opername"); + 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,"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,"left"); + } + } else { + /* cannot translate this operator */ + db2Debug2("cannot translate this opername: %s", opername); + } + } + } + } + db2free (opername,"opername"); + db2Exit1(); +} + +static void deparseScalarArrayOpExpr (ScalarArrayOpExpr* expr, deparse_expr_cxt* ctx) { + char* opername; + Oid leftargtype; + Oid schema; + HeapTuple tuple; + + db2Entry1(); + 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,"opername"); + 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,"buf.data"); + } else { + Datum datum; + bool isNull; + ArrayIterator iterator = array_create_iterator (DatumGetArrayTypeP (constant->constvalue), 0, NULL); + 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,"buf.data"); + } + } + db2free(buf.data,"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,"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,"buf.data"); + bResult = false; + break; + } + right = (bResult) ? db2strdup(buf.data,"buf.data") : NULL; + db2free(buf.data,"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,"left"); + db2free(right,"right"); + } + } + } + } + db2Exit1(); +} + +static void deparseDistinctExpr (DistinctExpr* expr, deparse_expr_cxt* ctx) { + Oid rightargtype = 0; + HeapTuple tuple; + + db2Entry1(); + 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,"right"); + } + db2free(left,"left"); + } + db2Exit1(); +} + +/** Deparse IS [NOT] NULL expression. + */ +static void deparseNullTest (NullTest* expr, deparse_expr_cxt* ctx) { + StringInfo buf = ctx->buf; + + db2Entry1(); + 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)"); + } + db2Exit1(": %s", buf->data); +} + +static void deparseNullIfExpr (NullIfExpr* expr, deparse_expr_cxt* ctx) { + Oid rightargtype = 0; + HeapTuple tuple; + + db2Entry1(); + 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,"right"); + } + db2free(left,"left"); + } + db2Exit1(": %s", ctx->buf->data); +} + +static void deparseBoolExpr (BoolExpr* expr, deparse_expr_cxt* ctx) { + ListCell* cell = NULL; + char* arg = NULL; + StringInfoData buf; + + db2Entry1(); + 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); + for_each_cell(cell, expr->args, lnext(expr->args, list_head(expr->args))) { + 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); + } else { + bBreak = true; + break; + } + } + if (!bBreak) { + appendStringInfo (ctx->buf, "%s)", buf.data); + } + } + db2free(buf.data,"buf.data"); + db2free(arg,"arg"); + db2Exit1(": %s", ctx->buf->data); +} + +static void deparseCaseExpr (CaseExpr* expr, deparse_expr_cxt* ctx) { + db2Entry1(); + 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,"buf.data"); + } + db2Exit1(": %s", ctx->buf->data); +} + +static void deparseCoalesceExpr (CoalesceExpr* expr, deparse_expr_cxt* ctx) { + db2Entry1(); + 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,"result.data"); + } + db2Exit1(": %s", ctx->buf->data); +} + +static void deparseFuncExpr (FuncExpr* expr, deparse_expr_cxt* ctx) { + db2Entry1(); + 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,"opername"); + 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,"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,"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,"right"); + } else { + db2Debug2("T_FuncExpr: function %s that we cannot render for DB2", opername); + } + } + 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))"); + } else { + /* function that we cannot render for DB2 */ + db2Debug2("T_FuncExpr: function %s that we cannot render for DB2", opername); + } + } + db2free (opername,"opername"); + } + db2Exit1(": %s", ctx->buf->data); +} + +static void deparseCoerceViaIOExpr (CoerceViaIO* expr, deparse_expr_cxt* ctx) { + 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) { + 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; + } + } + } + } + db2Exit1(": %s", ctx->buf->data); +} + +static void deparseSQLValueFuncExpr (SQLValueFunction* expr, deparse_expr_cxt* ctx) { + db2Entry1(); + 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; + } + db2Exit1(": %s", ctx->buf->data); +} + +static void deparseAggref (Aggref* expr, deparse_expr_cxt* ctx) { + db2Entry1(); + 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; + 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; + break; + } + if (argnode->type == T_TargetEntry) { + argexpr = ((TargetEntry*) argnode)->expr; + } else { + argexpr = (Expr*) argnode; + } + deparseExprInt(argexpr, &context); + if (cbuf.len <= 0) { + ok = false; + break; + } + appendStringInfo(&result, "%s%s", cbuf.data, first_arg ? "" : ", "); + db2free(cbuf.data,"cbuf.data"); + 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,"result.data"); + } + } + } + db2Exit1(": %s", 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; + db2Entry1(); + /* 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; + } + db2Exit1(": %s", 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; + db2Entry1(); + 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); + db2Exit1(": %s", 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; + 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"))); + + /* 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); + db2Exit1(": '%s'", s.data); + return s.data; +} + +/* 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; + + db2Entry1(); + 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) { + 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. + * 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)); + db2Exit1(); +} + +/** Print the name of an operator. + */ +static void deparseOperatorName(StringInfo buf, Form_pg_operator opform) { + char* opname = NULL; + + db2Entry1(); + /* 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); + } + db2Exit1(); +} + +/** 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; + + db2Entry1(); + #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 + db2Exit1(": '%s'",s.data); + 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; + + 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)) + 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 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"); + + /* 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); + } + } + } + db2Exit1(); +} + +/** Output join name for given join type + */ +char* get_jointype_name (JoinType jointype) { + char* type = NULL; + db2Entry1(); + 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; + } + db2Exit1(": %s", type); + 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; + + db2Entry1(); + *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++) { + #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) + 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"); + db2Exit1(": %s", buf->data); +} + +/** 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; + + db2Entry1(); + *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"); + db2Exit1(": %s", buf->data); +} + +/* 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; + + 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) { + 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"); + 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. + * + * 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; + EquivalenceMember* em = NULL; + #if PG_VERSION_NUM < 180000 + ListCell* lc = NULL; + #else + EquivalenceMemberIterator it; + #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) + && bms_is_empty(bms_intersect(em->em_relids, fpinfo->hidden_subquery_rels)) + && is_foreign_expr(root, rel, em->em_expr)) + break; + } + db2Exit1(": %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. + * + * 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; + + db2Entry1(); + 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)) { + db2Exit1(": %x", em); + return em; + } + } + i++; + } + db2Exit1(": %x", NULL); + 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 = 0; + bool first = true; + RangeTblEntry* rte = planner_rt_fetch(rtindex, root); + ListCell* lc = NULL; + ListCell* lc2 = NULL; + List* additional_conds = NIL; + + db2Entry1(); + /* 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); + db2Exit1(": %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; + + db2Entry1(); + 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; + 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) { + 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); + + db2Exit1(": %s", buf->data); +} + +/* 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; + + db2Entry1(); + /* 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); + db2Exit1(": %s", buf->data); +} diff --git a/source/db2_fdw.c b/source/db2_fdw.c index 7d0304d..5c8093d 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" @@ -64,11 +61,18 @@ DB2FdwOption valid_options[] = { {OPT_READONLY , ForeignTableRelationId , false}, {OPT_SAMPLE , ForeignTableRelationId , false}, {OPT_PREFETCH , ForeignTableRelationId , false}, + {OPT_FETCHSZ , ForeignTableRelationId , false}, {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}, #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}, @@ -93,6 +97,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); @@ -116,6 +121,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); @@ -126,14 +136,14 @@ 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; + fdwroutine->GetForeignUpperPaths = db2GetForeignUpperPaths; fdwroutine->GetForeignJoinPaths = db2GetForeignJoinPaths; fdwroutine->GetForeignPlan = db2GetForeignPlan; fdwroutine->AnalyzeForeignTable = db2AnalyzeForeignTable; @@ -154,6 +164,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; @@ -163,12 +179,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)); @@ -234,12 +247,20 @@ 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_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) { 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) @@ -267,11 +288,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 %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 0 and 10240.") + , errhint ("Valid values in this context are integers between 1 and %d.", DB2_MAX_ATTR_ROW_ARRAY_SIZE) ) ); } @@ -343,8 +377,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) @@ -359,21 +393,21 @@ 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); + pgversion = GetConfigOptionByName ("server_version", NULL, false); initStringInfo (&version); appendStringInfo (&version, "db2_fdw %s, PostgreSQL %s", DB2_FDW_VERSION, pgversion); @@ -418,6 +452,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); @@ -451,11 +486,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, false); + 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) + ) + ); + } } diff --git a/source/db2_fdw_utils.c b/source/db2_fdw_utils.c index d70813d..839b28d 100644 --- a/source/db2_fdw_utils.c +++ b/source/db2_fdw_utils.c @@ -1,1132 +1,83 @@ #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" -/** external prototypes */ -extern void db2GetLob (DB2Session* session, DB2Column* column, int cidx, char** value, long* value_len); -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); - -/** 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* deparseCoerceViaIOExpr (CoerceViaIO* expr); -char* deparseSQLValueFuncExpr (SQLValueFunction* expr); -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) ; -void errorContextCallback (void* arg); - -/** 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__); -} +/* Hash table for caching the results of shippability lookups */ +static HTAB* ShippableCacheHash = NULL; -/** This macro is used by deparseExpr to identify PostgreSQL - * types that can be translated to DB2 SQL. +/* 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. */ -#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) - -/** 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* retValue = NULL; - db2Debug1("> %s::deparseExpr", __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); - } - break; - case T_Param: { - retValue = deparseParamExpr(session, foreignrel, (Param*) expr, db2Table, params); - } - break; - case T_Var: { - retValue = deparseVarExpr (session, foreignrel, (Var*)expr, db2Table, params); - } - break; - case T_OpExpr: { - retValue = deparseOpExpr (session, foreignrel, (OpExpr*)expr, db2Table, params); - } - break; - case T_ScalarArrayOpExpr: { - retValue = deparseScalarArrayOpExpr (session, foreignrel, (ScalarArrayOpExpr*)expr, db2Table, params); - } - break; - case T_DistinctExpr: { - retValue = deparseDistinctExpr(session, foreignrel, (DistinctExpr*)expr, db2Table, params); - } - break; - case T_NullIfExpr: { - retValue = deparseNullIfExpr(session, foreignrel, (NullIfExpr*)expr, db2Table, params); - } - break; - case T_BoolExpr: { - retValue = deparseBoolExpr(session, foreignrel, (BoolExpr*)expr, db2Table, params); - } - break; - case T_RelabelType: { - retValue = deparseExpr (session, foreignrel, ((RelabelType*)expr)->arg, db2Table, params); - } - break; - case T_CoerceToDomain: { - retValue = deparseExpr (session, foreignrel, ((CoerceToDomain*)expr)->arg, db2Table, params); - } - break; - case T_CaseExpr: { - retValue = deparseCaseExpr(session, foreignrel, (CaseExpr*)expr, db2Table, params); - } - break; - case T_CoalesceExpr: { - retValue = deparseCoalesceExpr(session, foreignrel, (CoalesceExpr*)expr, db2Table, params); - } - break; - case T_NullTest: { - StringInfoData result; - char* arg = NULL; - db2Debug2(" T_NullTest"); - arg = deparseExpr(session, foreignrel, ((NullTest*) expr)->arg, db2Table, params); - db2Debug2(" T_NullTest arg: %s", arg); - if (arg != NULL) { - initStringInfo (&result); - appendStringInfo (&result, "(%s IS %sNULL)", arg, ((NullTest*)expr)->nulltesttype == IS_NOT_NULL ? "NOT " : ""); - } - retValue = (arg == NULL) ? arg : result.data; - } - break; - case T_FuncExpr: { - retValue = deparseFuncExpr(session, foreignrel, (FuncExpr*)expr, db2Table, params); - } - break; - case T_CoerceViaIO: { - retValue = deparseCoerceViaIOExpr((CoerceViaIO*) expr); - } - break; - case T_SQLValueFunction: { - retValue = deparseSQLValueFuncExpr((SQLValueFunction*)expr); - } - break; - default: { - /* we cannot translate this to DB2 */ - db2Debug2(" expression cannot be translated to DB2", __FILE__); - } - break; - } - } - db2Debug1("< %s::deparseExpr: %s", __FILE__, retValue); - return retValue; -} - -char* deparseConstExpr (DB2Session* session, RelOptInfo* foreignrel, Const* expr, const DB2Table* db2Table, List** params) { - char* value = NULL; - - 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; - } - } 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; - } - } - db2Debug1("< %s::deparseConstExpr: %s", __FILE__, value); - return value; -} - -char* deparseParamExpr (DB2Session* session, RelOptInfo* foreignrel, Param* expr, const DB2Table* db2Table, List** params) { - char* value = NULL; - 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 { - StringInfoData result; - /* find the index in the parameter list */ - int index = 0; - foreach (cell, *params) { - ++index; - if (equal (expr, (Node *) lfirst (cell))) - break; - } - if (cell == NULL) { - /* add the parameter to the list */ - ++index; - *params = lappend (*params, expr); - } - /* parameters will be called :p1, :p2 etc. */ - snprintf (parname, 10, ":p%d", index); - initStringInfo (&result); - appendAsType (&result, expr->paramtype); - value = result.data; - } - db2Debug1("< %s::deparseParamExpr: %s", __FILE__, value); - return value; -} - -char* deparseVarExpr (DB2Session* session, RelOptInfo* foreignrel, Var* expr, const DB2Table* db2Table, List** params) { - char* value = NULL; - 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)) { - #endif /* JOIN_API */ - if (expr->varno == foreignrel->relid && expr->varlevelsup == 0) - var_table = db2Table; - #ifdef JOIN_API - } else { - DB2FdwState* joinstate = (DB2FdwState*) 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) { - StringInfoData result; - initStringInfo (&result); - appendStringInfo (&result, "NULL"); - value = result.data; - } 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 { - StringInfoData result; - StringInfoData alias; - - initStringInfo (&result); - /* work around the lack of booleans in DB2 */ - if (expr->vartype == BOOLOID) { - appendStringInfo (&result, "("); - } - /* 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); - /* work around the lack of booleans in DB2 */ - if (expr->vartype == BOOLOID) { - appendStringInfo (&result, " <> 0)"); - } - value = result.data; - } - } - } - } - } else { - // don't try to handle type interval - 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) { - ++index; - if (equal (expr, (Node*) lfirst (cell))) - break; - } - if (cell == NULL) { - /* add the parameter to the list */ - ++index; - *params = lappend (*params, expr); - } - /* parameters will be called :p1, :p2 etc. */ - initStringInfo (&result); - appendStringInfo (&result, ":p%d", index); - value = result.data; - } - } - db2Debug1("< %s::deparseVarExpr: %s", __FILE__, value); - return value; -} - -char* deparseOpExpr (DB2Session* session, RelOptInfo* foreignrel, OpExpr* expr, const DB2Table* db2Table, List** params) { - char* value = NULL; - char* opername = NULL; - char oprkind = 0x00; - Oid rightargtype= 0; - Oid leftargtype = 0; - Oid schema = 0; - HeapTuple tuple ; - - /* 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 = deparseExpr (session, foreignrel, linitial (expr->args), db2Table, params); - db2Debug2(" left: %s", left); - if (left != NULL) { - if (oprkind == 'b') { - /* binary operator */ - char* right = deparseExpr (session, foreignrel, lsecond (expr->args), db2Table, params); - db2Debug2(" right: %s", right); - if (right != NULL) { - StringInfoData result; - initStringInfo (&result); - if (strcmp (opername, "~~") == 0) { - appendStringInfo (&result, "(%s LIKE %s ESCAPE '\\')", left, right); - } else if (strcmp (opername, "!~~") == 0) { - appendStringInfo (&result, "(%s NOT LIKE %s ESCAPE '\\')", left, right); - } else if (strcmp (opername, "~~*") == 0) { - appendStringInfo (&result, "(UPPER(%s) LIKE UPPER(%s) ESCAPE '\\')", left, right); - } else if (strcmp (opername, "!~~*") == 0) { - appendStringInfo (&result, "(UPPER(%s) NOT LIKE UPPER(%s) ESCAPE '\\')", left, right); - } else if (strcmp (opername, "^") == 0) { - appendStringInfo (&result, "POWER(%s, %s)", left, right); - } else if (strcmp (opername, "%") == 0) { - appendStringInfo (&result, "MOD(%s, %s)", left, right); - } else if (strcmp (opername, "&") == 0) { - appendStringInfo (&result, "BITAND(%s, %s)", left, right); - } else { - /* the other operators have the same name in DB2 */ - appendStringInfo (&result, "(%s %s %s)", left, opername, right); - } - value = result.data; - } - } else { - StringInfoData result; - initStringInfo (&result); - /* unary operator */ - if (strcmp (opername, "|/") == 0) { - appendStringInfo (&result, "SQRT(%s)", left); - } else if (strcmp (opername, "@") == 0) { - appendStringInfo (&result, "ABS(%s)", left); - } else { - /* unary + or - */ - appendStringInfo (&result, "(%s%s)", opername, left); - } - value = result.data; - } - } - } else { - /* cannot translate this operator */ - db2Debug2(" cannot translate this opername: %s", opername); - } - } - } - } - db2free (opername); - return value; -} - -char* deparseScalarArrayOpExpr (DB2Session* session, RelOptInfo* foreignrel, ScalarArrayOpExpr* expr, const DB2Table* db2Table, List** params) { - char* value = NULL; - char* opername; - Oid leftargtype; - Oid schema; - HeapTuple tuple; - - db2Debug1("> %s::deparseExpr", __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 = deparseExpr (session, foreignrel, linitial (expr->args), db2Table, params); - db2Debug2(" left: %s", left); - 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"); - /* the second (=last) argument can be Const, ArrayExpr or ArrayCoerceExpr */ - rightexpr = (Expr*)llast(expr->args); - switch (rightexpr->type) { - case T_Const: { - /* 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 */ - if (constant->constisnull) { - appendStringInfo (&result, "NULL"); - } 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 (&result, "%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; - } - } - } - 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 */ - ArrayExpr* array = (ArrayExpr*) rightexpr; - ListCell* cell = NULL; - bool first_arg = true; - /* 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); - if (element == NULL) { - /* if any element cannot be converted, give up */ - bResult = false; - break; - } - /* append the argument */ - appendStringInfo (&result, "%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 */ - bResult = false; - break; - } - } - 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) { - /* two parentheses close the expression */ - appendStringInfo (&result, "))"); - value = result.data; - } - } - } - } - } - db2Debug1("< %s::deparseExpr: %s", __FILE__, value); - return value; -} - -char* deparseDistinctExpr (DB2Session* session, RelOptInfo* foreignrel, DistinctExpr* expr, const DB2Table* db2Table, List** params) { - char* value = NULL; - 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 = deparseExpr (session, foreignrel, linitial ((expr)->args), db2Table, params); - db2Debug2(" left: %s", left); - if (left != NULL) { - char* right = deparseExpr (session, foreignrel, lsecond ((expr)->args), db2Table, params); - db2Debug2(" right: %s", right); - if (right != NULL) { - StringInfoData result; - initStringInfo (&result); - appendStringInfo (&result, "(%s IS DISTINCT FROM %s)", left, right); - value = result.data; - } - } - } - db2Debug1("1 %s::deparseDistinctExpr: %s", __FILE__, value); - return value; -} - -char* deparseNullIfExpr (DB2Session* session, RelOptInfo* foreignrel, NullIfExpr* expr, const DB2Table* db2Table, List** params) { - char* value = NULL; - 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 = deparseExpr (session, foreignrel, linitial((expr)->args), db2Table, params); - db2Debug2(" left: %s", left); - if (left != NULL) { - char* right = deparseExpr (session, foreignrel, lsecond((expr)->args), db2Table, params); - db2Debug2(" right: %s", right); - if (right != NULL) { - StringInfoData result; - initStringInfo (&result); - appendStringInfo (&result, "NULLIF(%s, %s)", left, right); - value = result.data; - } - } - } - db2Debug1("< %s::deparseNullIfExpr: %s", __FILE__, value); - return value; -} - -char* deparseBoolExpr (DB2Session* session, RelOptInfo* foreignrel, BoolExpr* expr, const DB2Table* db2Table, List** params) { - ListCell* cell = NULL; - char* arg = NULL; - db2Debug1("> %s::deparseBoolExpr", __FILE__); - arg = deparseExpr (session, foreignrel, linitial(expr->args), db2Table, params); - if (arg != NULL) { - StringInfoData result; - bool bBreak = false; - initStringInfo (&result); - appendStringInfo (&result, "(%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); - if (arg != NULL) { - appendStringInfo (&result, " %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; - } - } - db2Debug1("< %s::deparseBoolExpr: %s", __FILE__, arg); - return arg; -} - -char* deparseCaseExpr (DB2Session* session, RelOptInfo* foreignrel, CaseExpr* expr, const DB2Table* db2Table, List** params) { - char* value = NULL; - db2Debug1("> %s::deparseCaseExpr", __FILE__); - if (!canHandleType (expr->casetype)) { - db2Debug2(" cannot Handle Type caseexpr->casetype (%d)", expr->casetype); - } else { - StringInfoData result; - bool bBreak = false; - char* arg = NULL; - ListCell* cell = NULL; - - initStringInfo (&result); - appendStringInfo (&result, "CASE"); - - if (expr->arg != NULL) { - /* for the form "CASE arg WHEN ...", add first expression */ - arg = deparseExpr (session, foreignrel, expr->arg, db2Table, params); - db2Debug2(" CASE %s WHEN ...", arg); - if (arg == NULL) { - appendStringInfo (&result, " %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 (session, foreignrel, whenclause->expr, db2Table, params); - } else { - /* for CASE arg WHEN ..., use only the right branch of the equality */ - arg = deparseExpr (session, foreignrel, lsecond (((OpExpr*) whenclause->expr)->args), db2Table, params); - } - db2Debug2(" WHEN %s ", arg); - if (arg != NULL) { - appendStringInfo (&result, " WHEN %s", arg); - } else { - bBreak = true; - break; - } /* THEN */ - arg = deparseExpr (session, foreignrel, whenclause->result, db2Table, params); - db2Debug2(" THEN %s ", arg); - if (arg != NULL) { - appendStringInfo (&result, " THEN %s", arg); - } else { - bBreak = true; - break; - } - } - if (!bBreak) { - /* append ELSE clause if appropriate */ - if (expr->defresult != NULL) { - arg = deparseExpr (session, foreignrel, expr->defresult, db2Table, params); - db2Debug2(" ELSE %s", arg); - if (arg != NULL) { - appendStringInfo (&result, " ELSE %s", arg); - } else { - bBreak = true; - } - } - /* append END */ - appendStringInfo (&result, " END"); - } - } - if (!bBreak) { - value = result.data; - } else { - // in case somwhere within the construct we encountered an expression not supported - db2free(result.data); - value = NULL; - } - } - db2Debug1("< %s::deparseCaseExpr: %s", __FILE__, value); - return value; -} - -char* deparseCoalesceExpr (DB2Session* session, RelOptInfo* foreignrel, CoalesceExpr* expr, const DB2Table* db2Table, List** params) { - char* value = NULL; - 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 (session, foreignrel, (Expr*)lfirst(cell), db2Table, params); - db2Debug2(" arg: %s", arg); - if (arg != NULL) { - appendStringInfo(&result, ((first_arg) ? "%s" : ", %s"), arg); - first_arg = false; - } else { - break; - } - } - appendStringInfo (&result, ")"); - value = (arg == NULL) ? arg : result.data; - } - db2Debug1("< %s::deparseCoalesceExpr: %s", __FILE__, value); - return value; -} +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; -char* deparseFuncExpr (DB2Session* session, RelOptInfo* foreignrel, FuncExpr* expr, const DB2Table* db2Table, List** params) { - char* value = NULL; - 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); - } 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)) { - 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); - 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; - } - if (first_arg) { - first_arg = false; - appendStringInfo (&result, "%s", arg); - } else { - appendStringInfo (&result, ", %s", arg); - } - 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) { - db2Debug2(" T_FuncExpr: function %s that we cannot render for DB2", opername); - db2free (opername); - db2free (left); - db2Debug1("< %s::deparseExpr: NULL", __FILE__); - return NULL; - } - initStringInfo (&result); - appendStringInfo (&result, "EXTRACT(%s FROM %s)", left + 1, right); - } else { - 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; -} +typedef struct { + ShippableCacheKey key; /* hash key - must be first */ + bool shippable; +} ShippableCacheEntry; -char* deparseCoerceViaIOExpr (CoerceViaIO* expr) { - char* value = NULL; - 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: - value = "TRUNC(CAST (CAST(?/*:now*/ AS TIMESTAMP) AS DATE))"; - break; - case TIMESTAMPOID: - value = "(CAST (CAST (?/*:now*/ AS TIMESTAMP) AS TIMESTAMP))"; - break; - case TIMESTAMPTZOID: - value = "(CAST (?/*:now*/ AS TIMESTAMP))"; - break; - case TIMEOID: - value = "(CAST (CAST (?/*:now*/ AS TIME) AS TIME))"; - break; - case TIMETZOID: - value = "(CAST (?/*:now*/ AS TIME))"; - break; - } - } - } - } - db2Debug1("< %s::deparseCoerceViaIOExpr: %s", __FILE__, value); - return value; -} - -#if PG_VERSION_NUM >= 100000 -char* deparseSQLValueFuncExpr (SQLValueFunction* expr) { - char* value = NULL; - db2Debug1("> %s::deparseSQLValueFuncExpr", __FILE__); - switch (expr->op) { - case SVFOP_CURRENT_DATE: - value = "TRUNC(CAST (CAST(?/*:now*/ AS TIMESTAMP) AS DATE))"; - break; - case SVFOP_CURRENT_TIMESTAMP: - value = "(CAST (?/*:now*/ AS TIMESTAMP))"; - break; - case SVFOP_LOCALTIMESTAMP: - value = "(CAST (CAST (?/*:now*/ AS TIMESTAMP) AS TIMESTAMP))"; - break; - case SVFOP_CURRENT_TIME: - value = "(CAST (?/*:now*/ AS TIME))"; - break; - case SVFOP_LOCALTIME: - value = "(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; -} -#endif +/** external prototypes */ +extern void db2GetLob (DB2Session* session, DB2ResultColumn* column, char** value, long* value_len); +extern void db2Shutdown (void); +extern short c2dbType (short fcType); -/** 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. +/** local prototypes */ +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); +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); + +/* optionIsTrue + * Returns true if the string is "true", "on" or "yes". */ -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; +bool optionIsTrue (const char* value) { + bool result = false; + db2Entry4("(value: '%s')", value); + result = (pg_strcasecmp (value, "on") == 0 || pg_strcasecmp (value, "yes") == 0 || pg_strcasecmp (value, "true") == 0); + db2Exit4(": '%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. +/* 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); + + 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"; @@ -1196,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"; @@ -1222,355 +173,336 @@ 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); } - db2Debug1("< %s::guessNlsLang - returns: '%s'", __FILE__, buf.data); + db2Exit4(": %s", buf.data); 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. +/* exitHook + * Close all DB2 connections on process exit. */ -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. - */ -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) { + db2Entry4(); + db2Shutdown (); + db2Exit4(); } -/** convertTuple - * Convert a result row from DB2 stored in db2Table - * into arrays of values and null indicators. +/* convertTuple + * Convert a result row from DB2 stored in db2Table into arrays of values and null indicators. */ -void convertTuple (DB2FdwState* fdw_state, Datum* values, bool* nulls) { - char* tmp_value = NULL; - char* value = NULL; - long value_len = 0; - int j = 0; - int index = -1; - int result_idx = 0; -// ErrorContextCallback errcb; - Oid pgtype; +void convertTuple (DB2Session* session, DB2Table* db2Table, DB2ResultColumn* reslist, int natts, Datum* values, bool* nulls) { + char* value = NULL; + long value_len = 0; + int j = 0; + 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; + db2Entry4(); + db2Debug5("natts: %d", natts); /* assign result values */ - for (j = 0; j < fdw_state->db2Table->npgcols; ++j) { - short db2Type; - db2Debug2(" start processing column %d of %d",j + 1, fdw_state->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; - 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. - */ - if (index >= fdw_state->db2Table->ncols || fdw_state->db2Table->cols[index]->used == 0 || fdw_state->db2Table->cols[index]->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; - result_idx += (fdw_state->db2Table->cols[index]->used) ? 1 : 0; - /* get the data and its length */ - switch(c2dbType(fdw_state->db2Table->cols[index]->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, fdw_state->db2Table->cols[index], result_idx, &value, &value_len); - } - break; - 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); - /* the rest is the actual data */ - value = fdw_state->db2Table->cols[index]->val; - /* terminating zero byte (needed for LONGs) */ - value[value_len] = '\0'; + isSimpleSelect = (db2Table && natts == db2Table->npgcols); + db2Debug5("isSimpleSelect: %s", isSimpleSelect ? "true": "false"); + + // initialize all columns to NULL + for (j = 0; j < natts; j++) { + nulls[j] = true; + values[j] = PointerGetDatum (NULL); + } + + 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 ); + 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 : %ld" ,(long) res->val_len ); + db2Debug5("res->val_null : %ld" ,(long) res->val_null); + + if (res->val_null >= 0) { + short db2Type = 0; + /* from here on, we can assume columns to be NOT NULL */ + nulls[j] = false; + + /* get the data and its length */ + switch(c2dbType(res->colType)) { + case DB2_BLOB: + case DB2_CLOB: { + db2Debug5("DB2_BLOB or DB2CLOB"); + /* for LOBs, get the actual LOB contents (allocated), truncated if desired */ + db2GetLob (session, res, &value, &value_len); + } + break; + case DB2_LONGVARBINARY: { + 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 */ + 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; + + 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; + tmp_value = value; + if((tmp_value = strchr(value,',')) != NULL) { + *tmp_value = '.'; + } + } + break; + default: { + db2Debug5("should 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; - case DB2_FLOAT: - case DB2_DECIMAL: - case DB2_SMALLINT: - case DB2_INTEGER: - case DB2_REAL: - 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_len = (value_len == 0) ? strlen(value) : value_len; - tmp_value = value; - if((tmp_value = strchr(value,','))!=NULL) { - *tmp_value = '.'; + 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 */ + bytea* result = (bytea*) db2alloc (value_len + VARHDRSZ,"result"); + memcpy (VARDATA (result), value, value_len); + SET_VARSIZE (result, value_len + VARHDRSZ); + + values[j] = PointerGetDatum (result); + } else { + regproc typinput; + HeapTuple tuple; + Datum dat; + db2Debug5("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); + 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) { + db2Debug5("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)); + db2Debug5("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]); } } - break; - 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_len = (value_len == 0) ? strlen(value) : value_len; + /* release the data buffer for LOBs */ + db2Type = c2dbType(res->colType); + if (db2Type == DB2_BLOB || db2Type == DB2_CLOB) { + if (value != NULL) { + db2free (value,"value"); + } else { + db2Debug5("not freeing value, since it is null"); + } } - break; - } - db2Debug2(" value : '%x'", value); - if (value != NULL) { - db2Debug2(" value : '%s'", value); + } else { + db2Debug5("column %d is NULL", res->resnum); } - 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); - /* 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); + } + db2Exit4(); +} - 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); - db2Debug3(" CStringGetDatum"); - dat = CStringGetDatum (value); - /* 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; +/* Undo the effects of set_transmission_modes(). */ +void reset_transmission_modes(int nestlevel) { + db2Entry4(); + AtEOXact_GUC(true, nestlevel); + db2Exit4(); +} - /* 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); - } - /* call the type input function */ - switch (pgtype) { - case BPCHAROID: - case VARCHAROID: - case TIMESTAMPOID: - case TIMESTAMPTZOID: - case TIMEOID: - 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)); - break; - default: - db2Debug3(" Calling OidFunctionCall1"); - /* the others don't */ - values[j] = OidFunctionCall1 (typinput, dat); - } - /* uninstall error context callback */ -// error_context_stack = errcb.previous; - } +/* 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(); + + 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); + 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); + + db2Exit4(": %d", nestlevel); + 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) { + bool isBuiltin = (objectId < FirstGenbkiObjectId); + db2Entry4(); + db2Exit4(": %s", (isBuiltin) ? "true": "false"); + return isBuiltin; +} - /* release the data buffer for LOBs */ - db2Type = c2dbType(fdw_state->db2Table->cols[index]->colType); - if (db2Type == DB2_BLOB || db2Type == DB2_CLOB) { - if (value != NULL) { - db2free (value); +/* 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 = NULL; + bool shippable = false; + + db2Entry4(); + /* Built-in objects are presumed 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 { - db2Debug2(" not freeing value, since it is null"); + db2Debug4("no shippable cache entry (%x) found shippable is set to false", entry); + shippable = false; } } } - db2Debug1("< %s::convertTuple",__FILE__); + db2Exit4(": %s", shippable ? "true" : "false"); + return shippable; } -/** errorContextCallback - * Provides the context for an error message during a type input conversion. - * The argument must be a pointer to a DB2FdwState. +/* 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. */ -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__); +static void InvalidateShippableCacheCbk(Datum arg, int cacheid, uint32 hashvalue) { + HASH_SEQ_STATUS status; + ShippableCacheEntry* entry; + + 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. + */ + 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"); + } + db2Exit5(); } -/** exitHook - * Close all DB2 connections on process exit. - */ -void exitHook (int code, Datum arg) { - db2Debug1("> %s::exitHook",__FILE__); - db2Shutdown (); - db2Debug1("< %s::exitHook",__FILE__); +/* Initialize the backend-lifespan cache of shippability decisions. */ +static void InitializeShippableCache(void) { + HASHCTL ctl; + + db2Entry5(); + /* 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, InvalidateShippableCacheCbk, (Datum) 0); + db2Exit5(); } + +/* 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 = 0; + bool isValid = false; + + 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)); + 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 30efef2..5a67024 100644 --- a/source/db2_utils.c +++ b/source/db2_utils.c @@ -1,20 +1,13 @@ #include #include -#include -#include #include "db2_fdw.h" -/** external variables */ -extern void db2Debug4 (const char* message, ...); -extern void db2Debug5 (const char* message, ...); - /** local prototypes */ SQLSMALLINT c2param (SQLSMALLINT fparamType); 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_). @@ -80,91 +73,26 @@ char* param2name(SQLSMALLINT fparamType){ */ SQLSMALLINT c2param (SQLSMALLINT fcType) { SQLSMALLINT fparamType = SQL_C_CHAR; - db2Debug4("> c2param(fcType: %d)",fcType); + db2Entry4("(fcType: %d)",fcType); switch (fcType) { case SQL_BLOB: fparamType = SQL_C_BLOB_LOCATOR; - db2Debug5(" SQL_BLOB => SQL_C_LOCATOR"); + db2Debug5("SQL_BLOB => SQL_C_LOCATOR"); break; case SQL_CLOB: fparamType = SQL_C_CLOB_LOCATOR; - db2Debug5(" 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; - db2Debug5(" %s => SQL_C_CHAR",c2name(fcType)); + db2Debug5("%s => SQL_C_CHAR",c2name(fcType)); break; } - db2Debug4("< c2param - fparamType: %d)",fparamType); + db2Exit4(": %d",fparamType); 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; - db2Debug4("> 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 - 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; - } - db2Debug4("< parse2num_struct"); -} - /** c2dbType * Map a fcType to the fdw internal value representation. * This is required for all functions that cannot use sqlcli1.h diff --git a/sql/db2_fdw--18.1.2.sql b/sql/db2_fdw--18.2.0.sql similarity index 100% rename from sql/db2_fdw--18.1.2.sql rename to sql/db2_fdw--18.2.0.sql diff --git a/sql/uninstall_db2_fdw--18.1.2.sql b/sql/uninstall_db2_fdw--18.2.0.sql similarity index 100% rename from sql/uninstall_db2_fdw--18.1.2.sql rename to sql/uninstall_db2_fdw--18.2.0.sql diff --git a/test/expected/base.out b/test/expected/base.out index fca9ef9..b3a22d7 100644 --- a/test/expected/base.out +++ b/test/expected/base.out @@ -1,19 +1,34 @@ -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 -Sie sind jetzt verbunden mit der Datenbank »regtest« als Benutzer »postgres«. +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 @@ -22,61 +37,90 @@ 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') | [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) +set log_min_messages=debug5; +SET +-- starting testcases +-- running 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 | | +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: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) , @@ -86,9 +130,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 +173,1365 @@ 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 ./test/sql/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 ./test/sql/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 +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=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: + 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: 4.981 ms + Execution Time: 55.245 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 | 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 - 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) - --- create a local table importing its structure and content from an fdw table + 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) + +-- +-- End of TC003 +-- +-- running tc004.sql +\i ./test/sql/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 + +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 --- cleanup +-- +-- END of TC004 +-- +-- running tc005.sql +\i ./test/sql/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=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: + 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: 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; + 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 > 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.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: + 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" > 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,816642 + DB2 plan: Estimated Cardinality = 2,627403 + 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: 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%'; + empno | firstnme | lastname | ?column? +--------+----------+-----------+---------- + 000110 | VINCENZO | LUCCHESSI | 71120.00 + 000220 | JENNIFER | LUTZ | 52827.00 + 000330 | WING | LEE | 47900.00 +(3 rows) + +-- +-- END of TC004 +-- +-- running tc006.sql +\i ./test/sql/tc006.sql +-- +-- 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 +----------+-----------------------+-----------+----------+---------+-------------+----------+--------------+------------- + 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; +UPDATE 1 +SELECT deptnumb, deptname, location FROM sample.org WHERE deptnumb = 10; + deptnumb | deptname | location +----------+-------------+--------------- + 10 | Head Office | New York City +(1 row) + +-- Revert + verify +UPDATE sample.org SET location = 'New York' WHERE deptnumb = 10; +UPDATE 1 +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; +DELETE 1 +SELECT * FROM sample.org WHERE deptnumb = 84; + deptnumb | deptname | manager | division | location +----------+----------+---------+----------+---------- +(0 rows) + +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 +(1 row) + +-- +-- 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; +UPDATE 1 + SELECT deptnumb, location FROM sample.org WHERE deptnumb = 15; + deptnumb | location +----------+--------------- + 15 | Boston (temp) +(1 row) + +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%'); + QUERY PLAN +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + 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: + 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: 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; + 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, + 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 +-- +-- 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=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: + 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.417 ms + Execution Time: 3.783 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=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: + 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.427 ms + Execution Time: 4.391 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=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: + 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: 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; + 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 +-- +-- 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=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: + 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.305 ms + Execution Time: 4.481 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 +-- +-- 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 +-- +-- 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 | 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) + +DELETE FROM sample.remotetable where col1 = 581927; +DELETE 1 +-- +-- END of TC014 +-- +-- testcases ended +-- starting cleanup +\i ./test/sql/tcend.sql \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 diff --git a/test/sql/base.sql b/test/sql/base.sql index eb65a22..a886253 100644 --- a/test/sql/base.sql +++ b/test/sql/base.sql @@ -1,52 +1,42 @@ \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; --- 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 -\c postgres -DROP DATABASE regtest; --- \ No newline at end of file +\pset null '[NULL]' + +\i ./test/sql/tccdb.sql +\i ./test/sql/tcfdw.sql +\i ./test/sql/tcstart.sql + +set log_min_messages=debug5; + +-- starting testcases +-- running tc001.sql +\i ./test/sql/tc001.sql +-- running tc002.sql +\i ./test/sql/tc002.sql +-- running tc003.sql +\i ./test/sql/tc003.sql +-- running tc004.sql +\i ./test/sql/tc004.sql +-- running 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 +-- 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 +-- test finished \ No newline at end of file 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 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 diff --git a/test/sql/tc003.sql b/test/sql/tc003.sql new file mode 100644 index 0000000..cb2594a --- /dev/null +++ b/test/sql/tc003.sql @@ -0,0 +1,11 @@ +-- +-- TC003: Run a simple join +-- +\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 +-- \ No newline at end of file diff --git a/test/sql/tc004.sql b/test/sql/tc004.sql new file mode 100644 index 0000000..96597f6 --- /dev/null +++ b/test/sql/tc004.sql @@ -0,0 +1,10 @@ +-- +-- TC004: clone a foreign table into a local table including content +-- +create table sample.orgcopy as select * from sample.org; +\d+ sample.org* +select * from sample.orgcopy; +drop table sample.orgcopy; +-- +-- END of TC004 +-- \ No newline at end of file diff --git a/test/sql/tc005.sql b/test/sql/tc005.sql new file mode 100644 index 0000000..3e9d9fc --- /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 > 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 new file mode 100644 index 0000000..da24597 --- /dev/null +++ b/test/sql/tc006.sql @@ -0,0 +1,34 @@ +-- +-- 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 +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..f660d76 --- /dev/null +++ b/test/sql/tc011.sql @@ -0,0 +1,14 @@ +-- +-- TC011: ORDER BY with LIMIT/OFFSET +-- +EXPLAIN (analyze,verbose) SELECT empno, lastname, salary FROM sample.employee LIMIT 5; +SELECT empno, lastname, salary FROM sample.employee LIMIT 5; + +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 +-- 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 +-- 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/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 +-- 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.*