From 36d73655e6cb83a85cdd1b567f75bc8f9f531aa6 Mon Sep 17 00:00:00 2001 From: Devrim Gunduz Date: Mon, 29 Jun 2026 15:24:32 +0300 Subject: [PATCH] Add PostgreSQL 18 and 19 support to sqlite_fdw MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes all build failures and warnings against PostgreSQL 18 and 19 while preserving full compatibility with PG13 through PG17. connection.c — PostgreSQL 18 and 19 utils/hsearch.h and utils/tuplestore.h are no longer pulled in transitively in recent PostgreSQL versions. Add them explicitly and unconditionally so that HASHCTL, HASH_SEQ_STATUS, hash_create(), hash_search(), hash_seq_init(), hash_seq_search(), HASH_ELEM, HASH_BLOBS, HASH_ENTER and tuplestore_putvalues() are visible. deparse.c — PostgreSQL 19 Three headers dropped from the transitive include chain by PG19's IWYU cleanup pass; add them explicitly (unconditionally): catalog/pg_type.h — all *OID and *ARRAYOID type constants access/htup_details.h — GETSTRUCT() access/transam.h — FirstGenbkiObjectId deparse.c — PostgreSQL 18 and 19 PathKey.pk_strategy (int, btree StrategyNumber) was replaced by pk_cmptype (CompareType) in PG18. The lookup function changed from get_opfamily_member() to get_opfamily_member_for_cmptype(). BTLessStrategyNumber comparisons use COMPARE_LT instead. All guarded by #if PG_VERSION_NUM >= 180000. Affects: sqlite_is_foreign_pathkey() and sqlite_append_order_by_clause(). deparse.c — all versions (warning fix) Remove dead pindex variable (AttrNumber) from sqlite_deparse_insert() and sqlite_deparse_update(). The variable was only ever incremented, never read; SQLite uses unnumbered '?' placeholders. sqlite_fdw.c — PostgreSQL 19 access/htup_details.h is no longer transitively included for sqlite_fdw.c in PG19, making SizeofHeapTupleHeader undeclared. Add the header explicitly (unconditionally; safe on all PG versions). sqlite_fdw.c — PostgreSQL 19 In PG19, PlanState.instrument changed type from Instrumentation * to NodeInstrumentation *. The Instrumentation base struct no longer carries tuplecount (that field moved to NodeInstrumentation). Use NodeInstrumentation * under #if PG_VERSION_NUM >= 190000 in sqliteIterateDirectModify(), leaving PG13–18 behaviour unchanged. sqlite_fdw.c — PostgreSQL 18 create_foreignscan_path(), create_foreign_join_path() and create_foreign_upper_path() each gained "int disabled_nodes" between "rows" and "startup_cost". Pass 0 at all ten call sites under a #if PG_VERSION_NUM >= 180000 guard. cost_sort() gained "int input_disabled_nodes" between "pathkeys" and "input_cost". Pass 0 at the one call site under the same guard. ExplainPropertyText(), ExplainPropertyInteger() and the ExplainState struct were split into commands/explain_format.h and commands/explain_state.h; include both for PG18+. sqlite_fdw.c — all versions (warning fix) Remove dead variable i (int) from sqliteExecForeignUpdate(); it was only incremented, never read. sqlite_query.c — PostgreSQL 19 VARDATA() and SET_VARSIZE() became strict-typed static inline functions in PG19 requiring a pointer argument. Wrap the raw Datum value_datum with DatumGetPointer() at both call sites in sqlite_convert_to_pg(). DatumGetPointer() is a safe portable cast on all supported PG versions. Coded by Claude, tested by me. Per https://github.com/pgdg-packaging/pgdg-rpms/issues/213 --- Makefile | 4 ++-- connection.c | 2 ++ deparse.c | 38 ++++++++++++++++++++++++++++++++------ sqlite_fdw.c | 44 ++++++++++++++++++++++++++++++++++++++++++-- sqlite_query.c | 4 ++-- 5 files changed, 80 insertions(+), 12 deletions(-) diff --git a/Makefile b/Makefile index e01ed134..b45bbfeb 100644 --- a/Makefile +++ b/Makefile @@ -65,8 +65,8 @@ include $(PGXS) ifndef MAJORVERSION MAJORVERSION := $(basename $(VERSION)) endif -ifeq (,$(findstring $(MAJORVERSION), 13 14 15 16 17)) -$(error PostgreSQL 13, 14, 15, 16 or 17 is required to compile this extension) +ifeq (,$(findstring $(MAJORVERSION), 13 14 15 16 17 18 19)) +$(error PostgreSQL 13, 14, 15, 16, 17, 18 or 19 is required to compile this extension) endif else subdir = contrib/sqlite_fdw diff --git a/connection.c b/connection.c index ba28120d..067bf30e 100644 --- a/connection.c +++ b/connection.c @@ -20,7 +20,9 @@ #endif #include "optimizer/cost.h" #include "utils/builtins.h" +#include "utils/hsearch.h" /* HASHCTL, hash_create/search/seq_* */ #include "utils/inval.h" +#include "utils/tuplestore.h" /* tuplestore_putvalues */ #include "utils/syscache.h" diff --git a/deparse.c b/deparse.c index e7927965..a5838601 100644 --- a/deparse.c +++ b/deparse.c @@ -18,6 +18,9 @@ #include "catalog/pg_namespace.h" #include "catalog/pg_operator.h" #include "catalog/pg_proc.h" +#include "catalog/pg_type.h" /* INT2OID, FLOAT4OID, UUIDOID, etc. */ +#include "access/htup_details.h" /* GETSTRUCT */ +#include "access/transam.h" /* FirstGenbkiObjectId */ #if PG_VERSION_NUM >= 160000 #include "catalog/pg_ts_config.h" #endif @@ -406,6 +409,16 @@ sqlite_is_foreign_pathkey(PlannerInfo *root, Oid oprid; TypeCacheEntry *typentry; +#if PG_VERSION_NUM >= 180000 + 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", + (int) pathkey->pk_cmptype, em->em_datatype, em->em_datatype, + pathkey->pk_opfamily); +#else oprid = get_opfamily_member(pathkey->pk_opfamily, em->em_datatype, em->em_datatype, @@ -414,6 +427,7 @@ sqlite_is_foreign_pathkey(PlannerInfo *root, elog(ERROR, "missing operator %d(%u,%u) in opfamily %u", pathkey->pk_strategy, em->em_datatype, em->em_datatype, pathkey->pk_opfamily); +#endif /* See whether operator is default < or > for sort expr's datatype. */ typentry = lookup_type_cache(exprType((Node *) em->em_expr), @@ -1960,7 +1974,6 @@ sqlite_deparse_insert(StringInfo buf, PlannerInfo *root, TupleDesc tupdesc = RelationGetDescr(rel); bool all_columns_generated = true; #endif - AttrNumber pindex; bool first; ListCell *lc; @@ -2020,7 +2033,6 @@ sqlite_deparse_insert(StringInfo buf, PlannerInfo *root, appendStringInfoString(buf, ") VALUES ("); - pindex = 1; first = true; foreach(lc, targetAttrs) { @@ -2035,7 +2047,6 @@ sqlite_deparse_insert(StringInfo buf, PlannerInfo *root, appendStringInfoString(buf, ", "); first = false; appendStringInfo(buf, "?"); - pindex++; #if PG_VERSION_NUM >= 140000 } #endif @@ -2649,7 +2660,6 @@ sqlite_deparse_update(StringInfo buf, PlannerInfo *root, #if PG_VERSION_NUM >= 140000 TupleDesc tupdesc = RelationGetDescr(rel); #endif - AttrNumber pindex; bool first; ListCell *lc; int i; @@ -2658,7 +2668,6 @@ sqlite_deparse_update(StringInfo buf, PlannerInfo *root, sqlite_deparse_relation(buf, rel); appendStringInfoString(buf, " SET "); - pindex = 2; first = true; foreach(lc, targetAttrs) { @@ -2674,7 +2683,6 @@ sqlite_deparse_update(StringInfo buf, PlannerInfo *root, first = false; sqlite_deparse_column_ref(buf, rtindex, attnum, root, false, true); appendStringInfo(buf, " = ?"); - pindex++; #if PG_VERSION_NUM >= 140000 } #endif @@ -4163,6 +4171,16 @@ sqlite_append_order_by_clause(List *pathkeys, bool has_final_sort, deparse_expr_ * 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_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", + (int) pathkey->pk_cmptype, em->em_datatype, em->em_datatype, + pathkey->pk_opfamily); +#else oprid = get_opfamily_member(pathkey->pk_opfamily, em->em_datatype, em->em_datatype, @@ -4171,6 +4189,7 @@ sqlite_append_order_by_clause(List *pathkeys, bool has_final_sort, deparse_expr_ elog(ERROR, "missing operator %d(%u,%u) in opfamily %u", pathkey->pk_strategy, em->em_datatype, em->em_datatype, pathkey->pk_opfamily); +#endif sqlite_deparse_expr(em_expr, context); /* @@ -4193,10 +4212,17 @@ sqlite_append_order_by_clause(List *pathkeys, bool has_final_sort, deparse_expr_ * warning message because NULLS FIRST/LAST is not implemented in * this SQLite version. */ +#if PG_VERSION_NUM >= 180000 + if (!pathkey->pk_nulls_first && pathkey->pk_cmptype == COMPARE_LT) + elog(WARNING, "Current Sqlite Version (%d) does not support NULLS LAST for ORDER BY ASC, degraded emitted query to ORDER BY ASC NULLS FIRST (default sqlite behaviour).", sqliteVersion); + else if (pathkey->pk_nulls_first && pathkey->pk_cmptype != COMPARE_LT) + elog(WARNING, "Current Sqlite Version (%d) does not support NULLS FIRST for ORDER BY DESC, degraded emitted query to ORDER BY DESC NULLS LAST (default sqlite behaviour).", sqliteVersion); +#else if (!pathkey->pk_nulls_first && pathkey->pk_strategy == BTLessStrategyNumber) elog(WARNING, "Current Sqlite Version (%d) does not support NULLS LAST for ORDER BY ASC, degraded emitted query to ORDER BY ASC NULLS FIRST (default sqlite behaviour).", sqliteVersion); else if (pathkey->pk_nulls_first && pathkey->pk_strategy != BTLessStrategyNumber) elog(WARNING, "Current Sqlite Version (%d) does not support NULLS FIRST for ORDER BY DESC, degraded emitted query to ORDER BY DESC NULLS LAST (default sqlite behaviour).", sqliteVersion); +#endif } } sqlite_reset_transmission_modes(nestlevel); diff --git a/sqlite_fdw.c b/sqlite_fdw.c index bfa56787..1ec26b2b 100644 --- a/sqlite_fdw.c +++ b/sqlite_fdw.c @@ -16,9 +16,14 @@ #include #include "catalog/pg_collation.h" +#include "access/htup_details.h" /* SizeofHeapTupleHeader */ #include "catalog/pg_type.h" #include "commands/defrem.h" #include "commands/explain.h" +#if PG_VERSION_NUM >= 180000 +#include "commands/explain_format.h" /* ExplainPropertyText, ExplainPropertyInteger */ +#include "commands/explain_state.h" /* ExplainState struct definition */ +#endif #include "foreign/fdwapi.h" #include "funcapi.h" #include "mb/pg_wchar.h" @@ -807,6 +812,9 @@ sqlite_add_paths_with_pathkeys_for_rel(PlannerInfo *root, RelOptInfo *rel, List create_foreignscan_path(root, rel, NULL, rows, +#if PG_VERSION_NUM >= 180000 + 0, /* disabled_nodes */ +#endif startup_cost, total_cost, useful_pathkeys, @@ -830,6 +838,9 @@ sqlite_add_paths_with_pathkeys_for_rel(PlannerInfo *root, RelOptInfo *rel, List #endif NULL, rows, +#if PG_VERSION_NUM >= 180000 + 0, /* disabled_nodes */ +#endif startup_cost, total_cost, useful_pathkeys, @@ -924,6 +935,9 @@ sqliteGetForeignPaths(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid NULL, /* default pathtarget */ #endif baserel->rows, +#if PG_VERSION_NUM >= 180000 + 0, /* disabled_nodes */ +#endif startup_cost, total_cost, NIL, /* no pathkeys */ @@ -2500,7 +2514,11 @@ sqliteIterateDirectModify(ForeignScanState *node) SqliteFdwDirectModifyState *dmstate = (SqliteFdwDirectModifyState *) node->fdw_state; EState *estate = node->ss.ps.state; TupleTableSlot *slot = node->ss.ss_ScanTupleSlot; +#if PG_VERSION_NUM >= 190000 + NodeInstrumentation *instr = node->ss.ps.instrument; +#else Instrumentation *instr = node->ss.ps.instrument; +#endif elog(DEBUG1, "sqlite_fdw : %s", __func__); @@ -2719,7 +2737,6 @@ sqliteExecForeignUpdate(EState *estate, Oid foreignTableId = RelationGetRelid(rel); ListCell *lc = NULL; int bindnum = 0; - int i = 0; int rc = 0; elog(DEBUG1, "sqlite_fdw : %s", __func__); @@ -2745,7 +2762,6 @@ sqliteExecForeignUpdate(EState *estate, sqlite_bind_sql_var(bind_att, bindnum, value, fmstate->stmt, &is_null, foreignTableId); bindnum++; - i++; } bindJunkColumnValue(fmstate, slot, planSlot, foreignTableId, bindnum); @@ -3384,6 +3400,9 @@ sqlite_adjust_foreign_grouping_path_cost(PlannerInfo *root, cost_sort(&sort_path, root, pathkeys, +#if PG_VERSION_NUM >= 180000 + 0, /* input_disabled_nodes */ +#endif *p_startup_cost + *p_run_cost, retrieved_rows, width, @@ -3535,6 +3554,9 @@ sqliteGetForeignJoinPaths(PlannerInfo *root, joinrel, NULL, /* default pathtarget */ rows, +#if PG_VERSION_NUM >= 180000 + 0, /* disabled_nodes */ +#endif startup_cost, total_cost, NIL, /* no pathkeys */ @@ -3963,6 +3985,9 @@ sqlite_add_foreign_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel, grouped_rel, grouped_rel->reltarget, rows, +#if PG_VERSION_NUM >= 180000 + 0, /* disabled_nodes */ +#endif startup_cost, total_cost, NIL, /* no pathkeys */ @@ -3976,6 +4001,9 @@ sqlite_add_foreign_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel, grouped_rel, root->upper_targets[UPPERREL_GROUP_AGG], rows, +#if PG_VERSION_NUM >= 180000 + 0, /* disabled_nodes */ +#endif startup_cost, total_cost, NIL, /* no pathkeys */ @@ -4121,6 +4149,9 @@ sqlite_add_foreign_ordered_paths(PlannerInfo *root, RelOptInfo *input_rel, input_rel, root->upper_targets[UPPERREL_ORDERED], rows, +#if PG_VERSION_NUM >= 180000 + 0, /* disabled_nodes */ +#endif startup_cost, total_cost, root->sort_pathkeys, @@ -4144,6 +4175,9 @@ sqlite_add_foreign_ordered_paths(PlannerInfo *root, RelOptInfo *input_rel, input_rel, root->upper_targets[UPPERREL_FINAL], rows, +#if PG_VERSION_NUM >= 180000 + 0, /* disabled_nodes */ +#endif startup_cost, total_cost, root->sort_pathkeys, @@ -4331,6 +4365,9 @@ sqlite_add_foreign_final_paths(PlannerInfo *root, RelOptInfo *input_rel, input_rel, root->upper_targets[UPPERREL_FINAL], rows, +#if PG_VERSION_NUM >= 180000 + 0, /* disabled_nodes */ +#endif startup_cost, total_cost, pathkeys, @@ -4344,6 +4381,9 @@ sqlite_add_foreign_final_paths(PlannerInfo *root, RelOptInfo *input_rel, input_rel, root->upper_targets[UPPERREL_FINAL], rows, +#if PG_VERSION_NUM >= 180000 + 0, /* disabled_nodes */ +#endif startup_cost, total_cost, pathkeys, diff --git a/sqlite_query.c b/sqlite_query.c index 20602919..94c025bc 100644 --- a/sqlite_query.c +++ b/sqlite_query.c @@ -138,8 +138,8 @@ sqlite_convert_to_pg(Form_pg_attribute att, sqlite3_value * val, AttInMetadata * case SQLITE3_TEXT: /* treated as UTF-8 text BLOB */ { value_datum = (Datum) palloc0(value_byte_size_blob_or_utf8 + VARHDRSZ); - memcpy(VARDATA(value_datum), sqlite3_value_blob(val), value_byte_size_blob_or_utf8); - SET_VARSIZE(value_datum, value_byte_size_blob_or_utf8 + VARHDRSZ); + memcpy(VARDATA(DatumGetPointer(value_datum)), sqlite3_value_blob(val), value_byte_size_blob_or_utf8); + SET_VARSIZE(DatumGetPointer(value_datum), value_byte_size_blob_or_utf8 + VARHDRSZ); return (struct NullableDatum) {PointerGetDatum((const void *)value_datum), false}; } }