From 197c7977ef31c3f236bb5f1a81f3537233636c23 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 15 Jan 2026 18:26:35 +0000 Subject: [PATCH 1/2] Add jwt_token to plan serialization/deserialization The jwt_token was missing from serializePlanData() and deserializePlanData(), causing SELECT queries on foreign tables to fail with JWT authentication. IMPORT FOREIGN SCHEMA worked because it reads options directly, but SELECT uses the serialized plan data where jwt_token was not included. This fixes the "cannot authenticate" error (line 99) when using JWT auth for queries on foreign tables. --- source/db2BeginForeignModify.c | 4 ++++ source/db2PlanForeignModify.c | 2 ++ 2 files changed, 6 insertions(+) diff --git a/source/db2BeginForeignModify.c b/source/db2BeginForeignModify.c index 22af5f5..9c3d11d 100644 --- a/source/db2BeginForeignModify.c +++ b/source/db2BeginForeignModify.c @@ -87,6 +87,10 @@ DB2FdwState* deserializePlanData (List* list) { 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); diff --git a/source/db2PlanForeignModify.c b/source/db2PlanForeignModify.c index 02dd39a..0307761 100644 --- a/source/db2PlanForeignModify.c +++ b/source/db2PlanForeignModify.c @@ -483,6 +483,8 @@ List* serializePlanData (DB2FdwState* fdwState) { result = lappend (result, serializeString (fdwState->user)); /* password */ result = lappend (result, serializeString (fdwState->password)); + /* jwt_token */ + result = lappend (result, serializeString (fdwState->jwt_token)); /* nls_lang */ result = lappend (result, serializeString (fdwState->nls_lang)); /* query */ From e30c6fc77ce758c5a35e27dc6a6228336769726a Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 15 Jan 2026 18:56:17 +0000 Subject: [PATCH 2/2] Fix connection cache lookup to treat NULL and empty string as equivalent When JWT auth is used, user is NULL. But db2GetSession converts NULL to "". Meanwhile insertconnEntry stores "" as NULL. This caused findconnEntry to fail matching: - Stored: step->uid = NULL - Searching: user = "" - Old code: NULL == "" -> false (no match!) Now both NULL and "" are treated as "empty" and match each other. --- source/db2AllocConnHdl.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/source/db2AllocConnHdl.c b/source/db2AllocConnHdl.c index 15daf78..62f3738 100644 --- a/source/db2AllocConnHdl.c +++ b/source/db2AllocConnHdl.c @@ -131,9 +131,18 @@ DB2ConnEntry* findconnEntry(DB2ConnEntry* start, const char* srvname, const char DB2ConnEntry* step = NULL; db2Debug2(" > findconnEntry"); for (step = start; step != NULL; step = step->right){ - /* NULL-safe comparison for JWT auth where user may be NULL */ - int srv_match = (step->srvname && srvname) ? strcmp(step->srvname, srvname) == 0 : step->srvname == srvname; - int uid_match = (step->uid && user) ? strcmp(step->uid, user) == 0 : step->uid == user; + /* 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'); + int srvname_null_or_empty = (!srvname || srvname[0] == '\0'); + int srv_match = (srv_null_or_empty && srvname_null_or_empty) || + (!srv_null_or_empty && !srvname_null_or_empty && strcmp(step->srvname, srvname) == 0); + + int uid_null_or_empty = (!step->uid || step->uid[0] == '\0'); + int user_null_or_empty = (!user || user[0] == '\0'); + int uid_match = (uid_null_or_empty && user_null_or_empty) || + (!uid_null_or_empty && !user_null_or_empty && strcmp(step->uid, user) == 0); + if (srv_match && uid_match) { break; }