From 93f3841340fa4f790e5e203745bdda024b3df566 Mon Sep 17 00:00:00 2001 From: kai Date: Mon, 18 May 2026 12:38:16 +0100 Subject: [PATCH 1/6] Add PostgreSQL 18 build support (v2.2.3) Three localized, version-guarded source changes so the FDW compiles and links against PostgreSQL 18 while leaving PostgreSQL 16-and-earlier behaviour byte-identical (every change is behind - fdw/common.h: include commands/explain_format.h on PG18 (PG18 split the EXPLAIN property-output API, incl. ExplainPropertyText, out of commands/explain.h into a new header). - fdw/query.c: use PathKey.pk_cmptype == COMPARE_GT on PG18 (PG18 renamed pk_strategy:int to pk_cmptype:CompareType; COMPARE_GT is the documented equivalent of BTGreaterStrategyNumber). pre-18 path unchanged in the #else arm. - fdw/fdw.c (2 sites) and fdw/query.c (1 site): pass the two new create_foreignscan_path arguments on PG18 - disabled_nodes = 0 (after rows) and fdw_restrictinfo = NIL (before fdw_private). make_foreignscan is unchanged in PG18 and is left as-is. Verified: PG18 build compiles + links (steampipe_postgres_fdw.dylib; only the pre-existing macOS .so/.dylib inst-step quirk remains); PG14 build make exit 0 (guards proven inert, identical to baseline); go test ./types/... ./sql/... no worse than the develop baseline (types green; sql/ TestGetSQLForTable already red on develop, unchanged). Bump fdwVersion 2.2.2 -> 2.2.3 and add the CHANGELOG entry. --- CHANGELOG.md | 4 ++++ fdw/common.h | 4 ++++ fdw/fdw.c | 12 ++++++++++++ fdw/query.c | 10 ++++++++++ version/version.go | 2 +- 5 files changed, 31 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c953db6..6ff6f0be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## v2.3.0 [2026-06-10] +_Whats new_ +- Add support for building against PostgreSQL 18. Three localized, version-guarded source changes (`#if PG_VERSION_NUM >= 180000`): include `commands/explain_format.h` (PG18 EXPLAIN header split), use `PathKey.pk_cmptype`/`COMPARE_GT` (renamed from `pk_strategy`/`BTGreaterStrategyNumber`), and pass the new `disabled_nodes` and `fdw_restrictinfo` arguments to `create_foreignscan_path`. Behaviour on PostgreSQL 16 and earlier is unchanged. + ## v2.2.4 [2026-05-25] _Bug fixes_ - Fix `statement_timeout`, `pg_cancel_backend`, and `pg_terminate_backend` having no effect when a plugin's gRPC stream stalls — a hung scan held `AccessShareLock` indefinitely, blocking partition swaps and other DDL until restart. ([#671](https://github.com/turbot/steampipe-postgres-fdw/issues/671), [#672](https://github.com/turbot/steampipe-postgres-fdw/pull/672)) diff --git a/fdw/common.h b/fdw/common.h index 209bafec..f7385099 100644 --- a/fdw/common.h +++ b/fdw/common.h @@ -6,6 +6,10 @@ #include "catalog/pg_type.h" #include "commands/defrem.h" #include "commands/explain.h" +#if PG_VERSION_NUM >= 180000 +/* PG18 moved the EXPLAIN property-output API into a separate header */ +#include "commands/explain_format.h" +#endif #include "foreign/fdwapi.h" #include "foreign/foreign.h" #include "funcapi.h" diff --git a/fdw/fdw.c b/fdw/fdw.c index 8401d3fe..57ae9fb8 100644 --- a/fdw/fdw.c +++ b/fdw/fdw.c @@ -436,11 +436,17 @@ static void fdwGetForeignPaths(PlannerInfo *root, RelOptInfo *baserel, Oid forei baserel, NULL, /* default pathtarget */ baserel->rows, +#if PG_VERSION_NUM >= 180000 + 0, /* disabled_nodes (PG18) */ +#endif planstate->startupCost, baserel->rows * baserel->reltarget->width * 100000, // table scan is very expensive NIL, /* no pathkeys */ NULL, NULL, +#if PG_VERSION_NUM >= 180000 + NIL, /* fdw_restrictinfo (PG18) */ +#endif (void *)fdw_private)); /* Add each ForeignPath previously found */ @@ -458,9 +464,15 @@ static void fdwGetForeignPaths(PlannerInfo *root, RelOptInfo *baserel, Oid forei baserel, NULL, /* default pathtarget */ path->path.rows, +#if PG_VERSION_NUM >= 180000 + 0, /* disabled_nodes (PG18) */ +#endif path->path.startup_cost, path->path.total_cost, apply_pathkeys, NULL, NULL, +#if PG_VERSION_NUM >= 180000 + NIL, /* fdw_restrictinfo (PG18) */ +#endif (void *)fdw_private); newpath->path.param_info = path->path.param_info; add_path(baserel, (Path *)newpath); diff --git a/fdw/query.c b/fdw/query.c index 7452e6ca..48b10a16 100644 --- a/fdw/query.c +++ b/fdw/query.c @@ -528,6 +528,9 @@ findPaths(PlannerInfo *root, RelOptInfo *baserel, List *possiblePaths, NULL, /* default pathtarget */ #endif nbrows, +#if PG_VERSION_NUM >= 180000 + 0, /* disabled_nodes (PG18) */ +#endif startupCost, #if PG_VERSION_NUM >= 90600 nbrows * baserel->reltarget->width, @@ -538,6 +541,9 @@ findPaths(PlannerInfo *root, RelOptInfo *baserel, List *possiblePaths, NULL, #if PG_VERSION_NUM >= 90500 NULL, +#endif +#if PG_VERSION_NUM >= 180000 + NIL, /* fdw_restrictinfo (PG18) */ #endif NULL); @@ -574,7 +580,11 @@ deparse_sortgroup(PlannerInfo *root, Oid foreigntableid, RelOptInfo *rel) if ((expr = fdw_get_em_expr(ec, rel))) { +#if PG_VERSION_NUM >= 180000 + md->reversed = (key->pk_cmptype == COMPARE_GT); +#else md->reversed = (key->pk_strategy == BTGreaterStrategyNumber); +#endif md->nulls_first = key->pk_nulls_first; md->key = key; diff --git a/version/version.go b/version/version.go index db45b9da..083a78e0 100644 --- a/version/version.go +++ b/version/version.go @@ -11,7 +11,7 @@ import ( ) // The main version number that is being run at the moment. -var fdwVersion = "2.2.4" +var fdwVersion = "2.3.0" // A pre-release marker for the version. If this is "" (empty string) // then it means that it is a final release. Otherwise, this is a pre-release From ec252040b2b4b2631c3386c2350b857f9a674fdc Mon Sep 17 00:00:00 2001 From: kai Date: Tue, 19 May 2026 10:54:18 +0100 Subject: [PATCH 2/6] Gate fdw_restrictinfo at PG17 (not PG18); correct CHANGELOG Code-review finding: create_foreignscan_path's fdw_restrictinfo parameter was added in PostgreSQL 17, not 18 (verified against REL_16/17/18 optimizer/pathnode.h). disabled_nodes is the PG18 addition. Gating fdw_restrictinfo at >= 180000 was correct for the 14->18 build matrix but would fail a PG17 build (arg omitted) and the CHANGELOG mislabelled it as a PG18 argument. fdw_restrictinfo now guarded #if PG_VERSION_NUM >= 170000 at all three create_foreignscan_path sites; disabled_nodes stays >= 180000. PG18 is unchanged (>=170000 is also true there); PG<=16 still excludes both. CHANGELOG reworded to state the correct per-arg version. --- CHANGELOG.md | 2 +- fdw/fdw.c | 8 ++++---- fdw/query.c | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ff6f0be..3f7c7793 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ ## v2.3.0 [2026-06-10] _Whats new_ -- Add support for building against PostgreSQL 18. Three localized, version-guarded source changes (`#if PG_VERSION_NUM >= 180000`): include `commands/explain_format.h` (PG18 EXPLAIN header split), use `PathKey.pk_cmptype`/`COMPARE_GT` (renamed from `pk_strategy`/`BTGreaterStrategyNumber`), and pass the new `disabled_nodes` and `fdw_restrictinfo` arguments to `create_foreignscan_path`. Behaviour on PostgreSQL 16 and earlier is unchanged. +- Add support for building against PostgreSQL 18. Localized, version-guarded source changes: include `commands/explain_format.h` (PG18 EXPLAIN header split) and use `PathKey.pk_cmptype`/`COMPARE_GT` instead of `pk_strategy`/`BTGreaterStrategyNumber` (both `#if PG_VERSION_NUM >= 180000`); and pass the extra `create_foreignscan_path` arguments — `fdw_restrictinfo` (added in PostgreSQL 17, `#if PG_VERSION_NUM >= 170000`) and `disabled_nodes` (added in PostgreSQL 18, `#if PG_VERSION_NUM >= 180000`). Behaviour on PostgreSQL 16 and earlier is unchanged. ## v2.2.4 [2026-05-25] _Bug fixes_ diff --git a/fdw/fdw.c b/fdw/fdw.c index 57ae9fb8..e28ac706 100644 --- a/fdw/fdw.c +++ b/fdw/fdw.c @@ -444,8 +444,8 @@ static void fdwGetForeignPaths(PlannerInfo *root, RelOptInfo *baserel, Oid forei NIL, /* no pathkeys */ NULL, NULL, -#if PG_VERSION_NUM >= 180000 - NIL, /* fdw_restrictinfo (PG18) */ +#if PG_VERSION_NUM >= 170000 + NIL, /* fdw_restrictinfo (added in PG17) */ #endif (void *)fdw_private)); @@ -470,8 +470,8 @@ static void fdwGetForeignPaths(PlannerInfo *root, RelOptInfo *baserel, Oid forei path->path.startup_cost, path->path.total_cost, apply_pathkeys, NULL, NULL, -#if PG_VERSION_NUM >= 180000 - NIL, /* fdw_restrictinfo (PG18) */ +#if PG_VERSION_NUM >= 170000 + NIL, /* fdw_restrictinfo (added in PG17) */ #endif (void *)fdw_private); newpath->path.param_info = path->path.param_info; diff --git a/fdw/query.c b/fdw/query.c index 48b10a16..ac3fa235 100644 --- a/fdw/query.c +++ b/fdw/query.c @@ -542,8 +542,8 @@ findPaths(PlannerInfo *root, RelOptInfo *baserel, List *possiblePaths, #if PG_VERSION_NUM >= 90500 NULL, #endif -#if PG_VERSION_NUM >= 180000 - NIL, /* fdw_restrictinfo (PG18) */ +#if PG_VERSION_NUM >= 170000 + NIL, /* fdw_restrictinfo (added in PG17) */ #endif NULL); From f3bf1b9cb0fb5e59a32f318f49a94567907135a0 Mon Sep 17 00:00:00 2001 From: kai Date: Wed, 20 May 2026 17:57:30 +0100 Subject: [PATCH 3/6] Handle nested RestrictInfo in pull_var_clause on PG18 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PG18 dropped the T_RestrictInfo case from expression_tree_walker (src/backend/nodes/nodeFuncs.c — RestrictInfo is now annotated `pg_node_attr(no_read, no_query_jumble)` and the auto-generated copy/equal/out functions don't include it either). When the FDW's isAttrInRestrictInfo / extractColumns call pull_var_clause on a restrictinfo->clause subtree, and that subtree contains a nested RestrictInfo (the planner's orclause cache, certain equivalence-class derived join predicates), pull_var_clause's expression_tree_walker hits the unrecognized tag (318) and elogs: ERROR: unrecognized node type: 318 (SQLSTATE XX000) LOCATION: expression_tree_walker_impl, nodeFuncs.c:2669 Empirically confirmed via backtrace_functions on a multi-table join on PG18.4 (`select r.name, count(b.name) from r left join b on b.region = r.name group by r.name`). Stack: expression_tree_walker_impl pull_var_clause_walker pull_var_clause steampipe_postgres_fdw.so findPaths + 564 steampipe_postgres_fdw.so fdwGetForeignPaths Fix: a small pre-pass mutator under #if PG_VERSION_NUM >= 180000 that replaces each RestrictInfo with its wrapped clause before pull_var_clause's walker descends. Wrapped behind a PULL_VAR_CLAUSE_PG18_SAFE macro so the three pull_var_clause call sites in fdw/query.c (extractColumns reltargetlist loop, extractColumns restrictinfolist loop, isAttrInRestrictInfo) get the same treatment uniformly. PG14 builds are byte-identical to before (the macro expands to plain pull_var_clause when PG_VERSION_NUM < 180000). Reference: contrib/postgres_fdw/deparse.c:1585 does the equivalent one-step unwrap (`expr = ((RestrictInfo *) expr)->clause`) as the canonical PG18 pattern; this commit applies the recursive form so deeper nesting (encountered with equivalence-class derived join predicates) is also handled. Verified post-fix: the previously failing multi-table join now plans successfully and reaches plugin execution; chaos plugin self-join returns rows end-to-end; no regression on the existing chaos query battery (count(*), get-by-key, ORDER BY + LIMIT, LIKE). --- fdw/query.c | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/fdw/query.c b/fdw/query.c index ac3fa235..738df9f8 100644 --- a/fdw/query.c +++ b/fdw/query.c @@ -13,6 +13,7 @@ #include "catalog/pg_operator.h" #include "mb/pg_wchar.h" #include "nodes/makefuncs.h" +#include "nodes/nodeFuncs.h" #include "utils/lsyscache.h" #include "miscadmin.h" #include "parser/parsetree.h" @@ -24,6 +25,34 @@ #define get_attname(x, y) get_attname(x, y, true) #endif +#if PG_VERSION_NUM >= 180000 +/* + * PG18 dropped the T_RestrictInfo case from expression_tree_walker + * (src/backend/nodes/nodeFuncs.c). When the FDW walks a clause subtree + * that contains a nested RestrictInfo (the orclause cache, certain + * join-equivalence-class derived predicates), the walker hits the + * unrecognized node tag (318) and elogs + * "unrecognized node type: 318". Restore the PG<=17 behaviour with a + * pre-pass mutator that replaces each RestrictInfo with its wrapped + * clause before pull_var_clause's walker sees it. + */ +static Node * +strip_restrictinfo_mutator(Node *node, void *context) +{ + if (node == NULL) + return NULL; + if (IsA(node, RestrictInfo)) + return strip_restrictinfo_mutator( + (Node *) ((RestrictInfo *) node)->clause, context); + return expression_tree_mutator(node, strip_restrictinfo_mutator, context); +} +#define PULL_VAR_CLAUSE_PG18_SAFE(node, flags) \ + pull_var_clause(strip_restrictinfo_mutator((node), NULL), (flags)) +#else +#define PULL_VAR_CLAUSE_PG18_SAFE(node, flags) \ + pull_var_clause((node), (flags)) +#endif + char *getOperatorString(Oid opoid); Node *unnestClause(Node *node); @@ -59,7 +88,7 @@ extractColumns(List *reltargetlist, List *restrictinfolist) List *targetcolumns; Node *node = (Node *)lfirst(lc); - targetcolumns = pull_var_clause(node, + targetcolumns = PULL_VAR_CLAUSE_PG18_SAFE(node, #if PG_VERSION_NUM >= 90600 PVC_RECURSE_AGGREGATES | PVC_RECURSE_PLACEHOLDERS); @@ -74,7 +103,7 @@ extractColumns(List *reltargetlist, List *restrictinfolist) { List *targetcolumns; RestrictInfo *node = (RestrictInfo *)lfirst(lc); - targetcolumns = pull_var_clause((Node *)node->clause, + targetcolumns = PULL_VAR_CLAUSE_PG18_SAFE((Node *)node->clause, #if PG_VERSION_NUM >= 90600 PVC_RECURSE_AGGREGATES | PVC_RECURSE_PLACEHOLDERS); @@ -341,7 +370,7 @@ colnameFromVar(Var *var, PlannerInfo *root, FdwPlanState *planstate) */ bool isAttrInRestrictInfo(Index relid, AttrNumber attno, RestrictInfo *restrictinfo) { - List *vars = pull_var_clause((Node *)restrictinfo->clause, + List *vars = PULL_VAR_CLAUSE_PG18_SAFE((Node *)restrictinfo->clause, #if PG_VERSION_NUM >= 90600 PVC_RECURSE_AGGREGATES | PVC_RECURSE_PLACEHOLDERS); From 64779e6e0eb0174d9458270915e3d6fbf2dd0f88 Mon Sep 17 00:00:00 2001 From: kai Date: Tue, 9 Jun 2026 12:24:47 +0100 Subject: [PATCH 4/6] Build the FDW against PostgreSQL 18 in buildimage.yml The draft-release build still installed/built against PostgreSQL 14, so a tag would produce a PG14 FDW (incompatible with the PG18 db image). Switch all four platform jobs to PG18: - macOS: brew install postgresql@18 and put it first on PATH via GITHUB_PATH so pg_config resolves to 18, not the runner's preinstalled PG. - macOS: rename the PGXS .dylib output to .so before gzip (PG18 macOS DLSUFFIX is .dylib; the shipped artifact + control file are .so-pinned). - Linux: install postgresql-server-dev-18 and point the server include at /18. Source PG18 support (guard ladder + T_RestrictInfo fix) already on this branch. --- .github/workflows/buildimage.yml | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/.github/workflows/buildimage.yml b/.github/workflows/buildimage.yml index 647bc86e..55c0624f 100644 --- a/.github/workflows/buildimage.yml +++ b/.github/workflows/buildimage.yml @@ -21,8 +21,10 @@ jobs: name: Build for Darwin x86_64 runs-on: macos-15-intel steps: - - name: Install PostgreSQL@14 - run: brew install --force postgresql@14 + - name: Install PostgreSQL@18 + run: |- + brew install --force postgresql@18 + echo "$(brew --prefix postgresql@18)/bin" >> "$GITHUB_PATH" - name: PGConfig run: |- @@ -112,6 +114,7 @@ jobs: - name: gzip the steampipe_postgres_fdw.so run: |- + if [ -f build-Darwin/steampipe_postgres_fdw.dylib ]; then mv build-Darwin/steampipe_postgres_fdw.dylib build-Darwin/steampipe_postgres_fdw.so; fi gzip build-Darwin/steampipe_postgres_fdw.so mv build-Darwin/steampipe_postgres_fdw.so.gz build-Darwin/steampipe_postgres_fdw.so.darwin_amd64.gz @@ -140,8 +143,10 @@ jobs: name: Build for Darwin ARM64 runs-on: macos-latest steps: - - name: Install PostgreSQL@14 - run: brew install --force postgresql@14 + - name: Install PostgreSQL@18 + run: |- + brew install --force postgresql@18 + echo "$(brew --prefix postgresql@18)/bin" >> "$GITHUB_PATH" - name: PGConfig run: |- @@ -231,6 +236,7 @@ jobs: - name: gzip the steampipe_postgres_fdw.so run: |- + if [ -f build-Darwin/steampipe_postgres_fdw.dylib ]; then mv build-Darwin/steampipe_postgres_fdw.dylib build-Darwin/steampipe_postgres_fdw.so; fi gzip build-Darwin/steampipe_postgres_fdw.so mv build-Darwin/steampipe_postgres_fdw.so.gz build-Darwin/steampipe_postgres_fdw.so.darwin_arm64.gz @@ -280,9 +286,9 @@ jobs: sudo env ACCEPT_EULA=Y apt-get update sudo env ACCEPT_EULA=Y apt-get upgrade - - name: Install PostgreSQL14 Dev + - name: Install PostgreSQL18 Dev run: |- - sudo apt-get -y install postgresql-server-dev-14 + sudo apt-get -y install postgresql-server-dev-18 - name: Find stuff and set env run: |- @@ -293,7 +299,7 @@ jobs: export PATH=$(pg_config --bindir):$PATH export PGXS=$(pg_config --pgxs) - export SERVER_LIB=$(pg_config --includedir)/14/server + export SERVER_LIB=$(pg_config --includedir)/18/server export INTERNAL_LIB=$(pg_config --includedir)/internal export CFLAGS="$(pg_config --cflags) -I${SERVER_LIB} -I${INTERNAL_LIB} -g" @@ -365,9 +371,9 @@ jobs: sudo env ACCEPT_EULA=Y apt-get update sudo env ACCEPT_EULA=Y apt-get upgrade - - name: Install PostgreSQL14 Dev + - name: Install PostgreSQL18 Dev run: |- - sudo apt-get -y install postgresql-server-dev-14 + sudo apt-get -y install postgresql-server-dev-18 - name: Find stuff and set env run: |- @@ -378,7 +384,7 @@ jobs: export PATH=$(pg_config --bindir):$PATH export PGXS=$(pg_config --pgxs) - export SERVER_LIB=$(pg_config --includedir)/14/server + export SERVER_LIB=$(pg_config --includedir)/18/server export INTERNAL_LIB=$(pg_config --includedir)/internal export CFLAGS="$(pg_config --cflags) -I${SERVER_LIB} -I${INTERNAL_LIB} -g" From 9cf96291cce229365f601a8134a3caad099a248b Mon Sep 17 00:00:00 2001 From: kai Date: Tue, 9 Jun 2026 12:35:09 +0100 Subject: [PATCH 5/6] Ship the PG18 macOS FDW module as .so (inst handles .dylib) On macOS PG18, PGXS builds steampipe_postgres_fdw.dylib (DLSUFFIX=.dylib), but the fdw Makefile `inst` target hardcoded `cp steampipe_postgres_fdw.so`, so the build- copy failed ("No such file or directory") and the draft-release build aborted (compile+link had succeeded). `inst` now copies the .dylib to build-/steampipe_postgres_fdw.so when present (falling back to .so on Linux / PG<=17 macOS), matching the .so-pinned control file + the CLI's FdwBinaryFileName - the proven darwin/arm64 config. Linux unaffected. --- fdw/Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fdw/Makefile b/fdw/Makefile index fb9508bc..e85b7ccc 100644 --- a/fdw/Makefile +++ b/fdw/Makefile @@ -58,11 +58,11 @@ inst: mkdir -p ../build-${PLATFORM} rm -f ../build-${PLATFORM}/* - cp steampipe_postgres_fdw.so ../build-${PLATFORM} + @if [ -f steampipe_postgres_fdw.dylib ]; then cp steampipe_postgres_fdw.dylib ../build-${PLATFORM}/steampipe_postgres_fdw.so; else cp steampipe_postgres_fdw.so ../build-${PLATFORM}/steampipe_postgres_fdw.so; fi cp steampipe_postgres_fdw.control ../build-${PLATFORM} cp steampipe_postgres_fdw--1.0.sql ../build-${PLATFORM} - - rm steampipe_postgres_fdw.so + + rm -f steampipe_postgres_fdw.so steampipe_postgres_fdw.dylib rm steampipe_postgres_fdw.a rm steampipe_postgres_fdw.h From bf01ef907eedb69c08c3dc21fd211d6649a247b9 Mon Sep 17 00:00:00 2001 From: kai Date: Wed, 10 Jun 2026 09:25:39 +0100 Subject: [PATCH 6/6] Add v2.3.0 changelog entry for the PG18 nested-RestrictInfo JOIN fix --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f7c7793..d1dff7ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## v2.3.0 [2026-06-10] _Whats new_ - Add support for building against PostgreSQL 18. Localized, version-guarded source changes: include `commands/explain_format.h` (PG18 EXPLAIN header split) and use `PathKey.pk_cmptype`/`COMPARE_GT` instead of `pk_strategy`/`BTGreaterStrategyNumber` (both `#if PG_VERSION_NUM >= 180000`); and pass the extra `create_foreignscan_path` arguments — `fdw_restrictinfo` (added in PostgreSQL 17, `#if PG_VERSION_NUM >= 170000`) and `disabled_nodes` (added in PostgreSQL 18, `#if PG_VERSION_NUM >= 180000`). Behaviour on PostgreSQL 16 and earlier is unchanged. +- Handle nested `RestrictInfo` nodes in `pull_var_clause` on PostgreSQL 18 — multi-table JOINs on foreign tables failed with `unrecognized node type: 318` without this. (`#if PG_VERSION_NUM >= 180000`; no change on earlier versions.) ## v2.2.4 [2026-05-25] _Bug fixes_