From 615894b361ae14e4f6e4b3074d88cc653eb5bdde Mon Sep 17 00:00:00 2001 From: Karina Litskevich Date: Mon, 24 Aug 2026 13:59:53 +0300 Subject: [PATCH 1/6] Refactoring: remove never used field. --- jsquery_op.c | 1 - 1 file changed, 1 deletion(-) diff --git a/jsquery_op.c b/jsquery_op.c index 29cfe3d..cdeb0b1 100644 --- a/jsquery_op.c +++ b/jsquery_op.c @@ -31,7 +31,6 @@ #include "jsquery.h" typedef struct ResultAccum { - StringInfo buf; bool missAppend; JsonbParseState *jbArrayState; } ResultAccum; From 54a169011dcc5dc1ee5a27b5cc1117c54b3c03e5 Mon Sep 17 00:00:00 2001 From: Karina Litskevich Date: Mon, 24 Aug 2026 14:02:34 +0300 Subject: [PATCH 2/6] Refactoring before adapting to new Jsonb API. 1. Add initializing and finalizing functions for ResultAccum. 2. Rename functions working with ResultAccum. 3. Hide all ResultAccum guts in 4 functions: - raInitialize - raFinalize - raAppendElement - raAppendArray 4. Let "jbArrayState" be always initialized (not NULL). --- jsquery_op.c | 113 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 71 insertions(+), 42 deletions(-) diff --git a/jsquery_op.c b/jsquery_op.c index cdeb0b1..f3b76fd 100644 --- a/jsquery_op.c +++ b/jsquery_op.c @@ -30,7 +30,9 @@ #include "jsquery.h" -typedef struct ResultAccum { +/* ResultAccum is used to accumulate values in a Jsonb array. */ +typedef struct ResultAccum +{ bool missAppend; JsonbParseState *jbArrayState; } ResultAccum; @@ -39,35 +41,63 @@ typedef struct ResultAccum { static bool recursiveExecute(JsQueryItem *jsq, JsonbValue *jb, JsQueryItem *jsqLeftArg, ResultAccum *ra); +/* + * Initialize ResultAccum with the opening WJB_BEGIN_ARRAY token. + */ static void -appendResult(ResultAccum *ra, JsonbValue *jb) +raInitialize(ResultAccum *ra) +{ + Assert(ra); + + memset(ra, 0, sizeof(*ra)); + pushJsonbValue(&ra->jbArrayState, WJB_BEGIN_ARRAY, NULL); +} + +/* + * Finalize ResultAccum with the closing WJB_END_ARRAY token, and return the + * resulting Jsonb. + */ +static Jsonb * +raFinalize(ResultAccum *ra) +{ + Assert(ra); + Assert(ra->jbArrayState); + + return JsonbValueToJsonb(pushJsonbValue(&ra->jbArrayState, WJB_END_ARRAY, NULL)); +} + +/* + * Append one element. + * Note: append only if "ra" is vaild and appendable; otherwise, do nothing. + */ +static void +raAppendElement(ResultAccum *ra, JsonbValue *jb) { if (ra == NULL || ra->missAppend == true) return; - if (ra->jbArrayState == NULL) - pushJsonbValue(&ra->jbArrayState, WJB_BEGIN_ARRAY, NULL); + Assert(ra->jbArrayState); pushJsonbValue(&ra->jbArrayState, WJB_ELEM, jb); } +/* + * Iterate over the Jsonb array and append its elements to "ra". + * Note: "ra" must be valid and appendable. + */ static void -concatResult(ResultAccum *ra, JsonbParseState *a, JsonbParseState *b) +raAppendArray(ResultAccum *ra, Jsonb *array) { - Jsonb *value; JsonbIterator *it; int32 r; JsonbValue v; - Assert(a); - Assert(b); - - ra->jbArrayState = a; + Assert(ra && ra->missAppend == false); + Assert(ra->jbArrayState); - value = JsonbValueToJsonb(pushJsonbValue(&b, WJB_END_ARRAY, NULL)); - it = JsonbIteratorInit(&value->root); + it = JsonbIteratorInit(&array->root); - while((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE) + while ((r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE) if (r == WJB_ELEM) pushJsonbValue(&ra->jbArrayState, WJB_ELEM, &v); } @@ -459,32 +489,35 @@ recursiveExecute(JsQueryItem *jsq, JsonbValue *jb, JsQueryItem *jsqLeftArg, switch(jsq->type) { case jqiAnd: { - JsonbParseState *saveJbArrayState = NULL; + /* + * Accumulate the array in "raTmp" if needed. Don't mess with + * "ra" in case the result turns out to be false. + */ + ResultAccum raTmpData; + ResultAccum *raTmp = NULL; - jsqGetLeftArg(jsq, &elem); if (ra && ra->missAppend == false) { - saveJbArrayState = ra->jbArrayState; - ra->jbArrayState = NULL; + /* need to accumulate */ + raTmp = &raTmpData; + raInitialize(raTmp); } - res = recursiveExecute(&elem, jb, jsqLeftArg, ra); + jsqGetLeftArg(jsq, &elem); + res = recursiveExecute(&elem, jb, jsqLeftArg, raTmp); if (res == true) { jsqGetRightArg(jsq, &elem); - res = recursiveExecute(&elem, jb, jsqLeftArg, ra); + res = recursiveExecute(&elem, jb, jsqLeftArg, raTmp); } + /* Append the array accumulated in "raTmp" to "ra" if needed. */ if (ra && ra->missAppend == false) { + Jsonb *array = raFinalize(raTmp); + if (res == true) - { - if (saveJbArrayState != NULL) - /* append args lists to current */ - concatResult(ra, saveJbArrayState, ra->jbArrayState); - } - else - ra->jbArrayState = saveJbArrayState; + raAppendArray(ra, array); } break; @@ -524,7 +557,7 @@ recursiveExecute(JsQueryItem *jsq, JsonbValue *jb, JsQueryItem *jsqLeftArg, { if (jsqGetNext(jsq, &elem) == false) { - appendResult(ra, v); + raAppendElement(ra, v); res = true; } else @@ -536,7 +569,7 @@ recursiveExecute(JsQueryItem *jsq, JsonbValue *jb, JsQueryItem *jsqLeftArg, case jqiCurrent: if (jsqGetNext(jsq, &elem) == false) { - appendResult(ra, jb); + raAppendElement(ra, jb); res = true; } else if (JsonbType(jb) == jbvScalar) @@ -566,7 +599,7 @@ recursiveExecute(JsQueryItem *jsq, JsonbValue *jb, JsQueryItem *jsqLeftArg, if (jsqGetNext(jsq, &elem) == false) { res = true; - appendResult(ra, jb); + raAppendElement(ra, jb); } else if (recursiveExecute(&elem, jb, NULL, ra)) res = true; @@ -577,7 +610,7 @@ recursiveExecute(JsQueryItem *jsq, JsonbValue *jb, JsQueryItem *jsqLeftArg, if (jsqGetNext(jsq, &elem) == false) { res = true; - appendResult(ra, jb); + raAppendElement(ra, jb); } if ((res = recursiveExecute(&elem, jb, NULL, ra)) == true) { @@ -604,7 +637,7 @@ recursiveExecute(JsQueryItem *jsq, JsonbValue *jb, JsQueryItem *jsqLeftArg, while(ra && (r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE) if (r == WJB_ELEM) - appendResult(ra, &v); + raAppendElement(ra, &v); break; } @@ -650,7 +683,7 @@ recursiveExecute(JsQueryItem *jsq, JsonbValue *jb, JsQueryItem *jsqLeftArg, if (jsqGetNext(jsq, &elem) == false) { res = true; - appendResult(ra, v); + raAppendElement(ra, v); } else res = recursiveExecute(&elem, v, NULL, ra); @@ -676,7 +709,7 @@ recursiveExecute(JsQueryItem *jsq, JsonbValue *jb, JsQueryItem *jsqLeftArg, while(ra && (r = JsonbIteratorNext(&it, &v, true)) != WJB_DONE) if (r == WJB_VALUE) - appendResult(ra, &v); + raAppendElement(ra, &v); break; } @@ -762,7 +795,7 @@ recursiveExecute(JsQueryItem *jsq, JsonbValue *jb, JsQueryItem *jsqLeftArg, if (res) { if (jsqGetNext(jsq, &elem) == false) { - appendResult(ra, jb); + raAppendElement(ra, jb); res = true; } else @@ -841,21 +874,17 @@ json_jsquery_filter(PG_FUNCTION_ARGS) jbv.val.binary.len = VARSIZE_ANY_EXHDR(jb); jsqInit(&jsq, jq); - memset(&ra, 0, sizeof(ra)); + raInitialize(&ra); recursiveExecute(&jsq, &jbv, NULL, &ra); - if (ra.jbArrayState) - { - res = JsonbValueToJsonb( - pushJsonbValue(&ra.jbArrayState, WJB_END_ARRAY, NULL) - ); - } + res = raFinalize(&ra); PG_FREE_IF_COPY(jb, 0); PG_FREE_IF_COPY(jq, 1); - if (res) + /* If array is empty, we must return NULL. */ + if (res && JsonContainerSize(&res->root) > 0) PG_RETURN_JSONB_P(res); PG_RETURN_NULL(); From d5eb418e20f435daa9345558212234f845464c00 Mon Sep 17 00:00:00 2001 From: Karina Litskevich Date: Mon, 24 Aug 2026 14:04:39 +0300 Subject: [PATCH 3/6] PG19 support: Adapt to new Jsonb API. Caused by: - 0986e951 Revise APIs for pushJsonbValue() and associated routines. --- jsquery_op.c | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/jsquery_op.c b/jsquery_op.c index f3b76fd..b8405f7 100644 --- a/jsquery_op.c +++ b/jsquery_op.c @@ -34,7 +34,11 @@ typedef struct ResultAccum { bool missAppend; - JsonbParseState *jbArrayState; +#if PG_VERSION_NUM >= 190000 + JsonbInState jbArrayState; /* initialize by zeroing this structure */ +#else + JsonbParseState *jbArrayState; /* initialize with NULL */ +#endif } ResultAccum; @@ -49,6 +53,7 @@ raInitialize(ResultAccum *ra) { Assert(ra); + /* This initialization works for both PG19+ and earlier */ memset(ra, 0, sizeof(*ra)); pushJsonbValue(&ra->jbArrayState, WJB_BEGIN_ARRAY, NULL); } @@ -61,9 +66,14 @@ static Jsonb * raFinalize(ResultAccum *ra) { Assert(ra); - Assert(ra->jbArrayState); +#if PG_VERSION_NUM >= 190000 + pushJsonbValue(&ra->jbArrayState, WJB_END_ARRAY, NULL); + return JsonbValueToJsonb(ra->jbArrayState.result); +#else + Assert(ra->jbArrayState); /* it's a pointer only before PG19 */ return JsonbValueToJsonb(pushJsonbValue(&ra->jbArrayState, WJB_END_ARRAY, NULL)); +#endif } /* @@ -76,7 +86,9 @@ raAppendElement(ResultAccum *ra, JsonbValue *jb) if (ra == NULL || ra->missAppend == true) return; - Assert(ra->jbArrayState); +#if PG_VERSION_NUM < 190000 + Assert(ra->jbArrayState); /* it's a pointer only before PG19 */ +#endif pushJsonbValue(&ra->jbArrayState, WJB_ELEM, jb); } @@ -93,7 +105,9 @@ raAppendArray(ResultAccum *ra, Jsonb *array) JsonbValue v; Assert(ra && ra->missAppend == false); - Assert(ra->jbArrayState); +#if PG_VERSION_NUM < 190000 + Assert(ra->jbArrayState); /* it's a pointer only before PG19 */ +#endif it = JsonbIteratorInit(&array->root); From 82cd96101889591eb1dbddf0501c13f6d7e5ac5d Mon Sep 17 00:00:00 2001 From: Karina Litskevich Date: Mon, 24 Aug 2026 14:06:51 +0300 Subject: [PATCH 4/6] PG19 support: Add pg_fallthrough to keep compiler silent. Caused by: - 8354b9d6 Use fallthrough attribute instead of comment - 0284e075 Enable -Wimplicit-fallthrough option for clang Also to keep compiler silent add "break;" in one particular place. We could add another pg_fallthrough in here. Result is the same, but "break;" is shorter and easier to read given differences between PG versions. --- jsonb_gin_ops.c | 3 +++ jsquery_extract.c | 5 ++++- jsquery_io.c | 14 ++++++++++++-- jsquery_support.c | 35 +++++++++++++++++++++++++++++++---- 4 files changed, 50 insertions(+), 7 deletions(-) diff --git a/jsonb_gin_ops.c b/jsonb_gin_ops.c index 3dd2f4d..7addd3b 100644 --- a/jsonb_gin_ops.c +++ b/jsonb_gin_ops.c @@ -1153,6 +1153,9 @@ gin_extract_jsonb_path_value_internal(Jsonb *jb, int32 *nentries) case WJB_END_ARRAY: if (!stack->parent) break; /* raw scalar array */ +#if PG_VERSION_NUM >= 190000 + pg_fallthrough; +#endif /* fall through */ case WJB_END_OBJECT: /* Pop the stack */ diff --git a/jsquery_extract.c b/jsquery_extract.c index 4517706..2cd8f0e 100644 --- a/jsquery_extract.c +++ b/jsquery_extract.c @@ -179,8 +179,11 @@ recursiveExtract(JsQueryItem *jsq, bool not, bool indirect, PathItem *path) *result->exactValue = e; return result; } - /* fall through */ /* jqiEqual with jqiArray follows */ +#if PG_VERSION_NUM >= 190000 + pg_fallthrough; +#endif + /* fall through */ case jqiIn: case jqiOverlap: case jqiContains: diff --git a/jsquery_io.c b/jsquery_io.c index 8a4b9ac..83de020 100644 --- a/jsquery_io.c +++ b/jsquery_io.c @@ -45,6 +45,9 @@ flattenJsQueryParseItem(StringInfo buf, JsQueryParseItem *item, bool onlyCurrent case jqiKey: if (onlyCurrentInPath) elog(ERROR,"Array length should be last in path"); +#if PG_VERSION_NUM >= 190000 + pg_fallthrough; +#endif /* fall through */ case jqiString: appendBinaryStringInfo(buf, (char*)&item->string.len, sizeof(item->string.len)); @@ -119,7 +122,10 @@ flattenJsQueryParseItem(StringInfo buf, JsQueryParseItem *item, bool onlyCurrent case jqiIndexArray: appendBinaryStringInfo(buf, (char*)&item->arrayIndex, sizeof(item->arrayIndex)); - /* FALLTHROUGH */ /* keep svace quiet */ +#if PG_VERSION_NUM >= 190000 + pg_fallthrough; +#endif + /* FALLTHROUGH */ case jqiAny: case jqiAnyArray: case jqiAnyKey: @@ -128,6 +134,7 @@ flattenJsQueryParseItem(StringInfo buf, JsQueryParseItem *item, bool onlyCurrent case jqiAllKey: if (onlyCurrentInPath) elog(ERROR,"Array length should be last in path"); + break; case jqiCurrent: case jqiNull: break; @@ -236,8 +243,11 @@ printJsQueryItem(StringInfo buf, JsQueryItem *v, bool inKey, bool printBracketes case jqiKey: if (inKey) appendStringInfoChar(buf, '.'); - /* fall through */ /* follow next */ +#if PG_VERSION_NUM >= 190000 + pg_fallthrough; +#endif + /* fall through */ case jqiString: escape_json(buf, jsqGetString(v, NULL)); break; diff --git a/jsquery_support.c b/jsquery_support.c index 747776b..616d247 100644 --- a/jsquery_support.c +++ b/jsquery_support.c @@ -34,12 +34,21 @@ alignStringInfoInt(StringInfo buf) { case 3: appendStringInfoCharMacro(buf, 0); +#if PG_VERSION_NUM >= 190000 + pg_fallthrough; +#endif /* fall through */ case 2: appendStringInfoCharMacro(buf, 0); +#if PG_VERSION_NUM >= 190000 + pg_fallthrough; +#endif /* fall through */ case 1: appendStringInfoCharMacro(buf, 0); +#if PG_VERSION_NUM >= 190000 + pg_fallthrough; +#endif /* fall through */ default: break; @@ -64,9 +73,24 @@ jsqInitByBuffer(JsQueryItem *v, char *base, int32 pos) switch(INTALIGN(pos) - pos) { - case 3: pos++; /* fall through */ - case 2: pos++; /* fall through */ - case 1: pos++; /* fall through */ + case 3: + pos++; +#if PG_VERSION_NUM >= 190000 + pg_fallthrough; +#endif + /* fall through */ + case 2: + pos++; +#if PG_VERSION_NUM >= 190000 + pg_fallthrough; +#endif + /* fall through */ + case 1: + pos++; +#if PG_VERSION_NUM >= 190000 + pg_fallthrough; +#endif + /* fall through */ default: break; } @@ -90,8 +114,11 @@ jsqInitByBuffer(JsQueryItem *v, char *base, int32 pos) case jqiKey: case jqiString: read_int32(v->value.datalen, base, pos); - /* fall through */ /* follow next */ +#if PG_VERSION_NUM >= 190000 + pg_fallthrough; +#endif + /* fall through */ case jqiNumeric: case jqiBool: case jqiIs: From 07a27402b5853dad8d77cb5aa1858cd415a7e5c8 Mon Sep 17 00:00:00 2001 From: Karina Litskevich Date: Mon, 24 Aug 2026 14:08:56 +0300 Subject: [PATCH 5/6] PG19 support: Use "foo(void)" for definitions of functions with no parameters. See PostgreSQL commit: - 9b05e2ec Use "foo(void)" for definitions of functions with no parameters. Remove extra space while we are here. --- jsquery_scan.l | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jsquery_scan.l b/jsquery_scan.l index f145650..1452e13 100644 --- a/jsquery_scan.l +++ b/jsquery_scan.l @@ -248,9 +248,9 @@ static keyword keywords[] = { }; static int -checkSpecialVal() +checkSpecialVal(void) { - int res = STRING_P; + int res = STRING_P; int diff; keyword *StopLow = keywords, *StopHigh = keywords + lengthof(keywords), @@ -288,7 +288,7 @@ checkSpecialVal() } static JsQueryHint -checkHint() +checkHint(void) { if (scanstring.len <= 2 || strncmp(scanstring.val, "--", 2) != 0) return jsqIndexDefault; From af39f4f0d0cb6d46a3f52be3925e46f78275e9d2 Mon Sep 17 00:00:00 2001 From: Karina Litskevich Date: Mon, 24 Aug 2026 14:11:11 +0300 Subject: [PATCH 6/6] PG19 support: Adapt test for PG19. Caused by: - 45762084 Force standard_conforming_strings to always be ON. --- expected/jsquery.out | 5 +++++ expected/jsquery_1.out | 5 +++++ sql/jsquery.sql | 6 ++++++ 3 files changed, 16 insertions(+) diff --git a/expected/jsquery.out b/expected/jsquery.out index 8c8d3b1..8f2edd8 100644 --- a/expected/jsquery.out +++ b/expected/jsquery.out @@ -1,6 +1,11 @@ CREATE EXTENSION jsquery; +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int < 19 AS server_version_lt_19 +\gset +\if :server_version_lt_19 set escape_string_warning=off; set standard_conforming_strings=on; +\endif CREATE TABLE test_jsquery (v jsonb); \copy test_jsquery from 'data/test_jsquery.data' select ''::jsquery; diff --git a/expected/jsquery_1.out b/expected/jsquery_1.out index ea1f6dc..8011209 100644 --- a/expected/jsquery_1.out +++ b/expected/jsquery_1.out @@ -1,6 +1,11 @@ CREATE EXTENSION jsquery; +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int < 19 AS server_version_lt_19 +\gset +\if :server_version_lt_19 set escape_string_warning=off; set standard_conforming_strings=on; +\endif CREATE TABLE test_jsquery (v jsonb); \copy test_jsquery from 'data/test_jsquery.data' select ''::jsquery; diff --git a/sql/jsquery.sql b/sql/jsquery.sql index d183741..a124eff 100644 --- a/sql/jsquery.sql +++ b/sql/jsquery.sql @@ -1,7 +1,13 @@ CREATE EXTENSION jsquery; +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int < 19 AS server_version_lt_19 +\gset + +\if :server_version_lt_19 set escape_string_warning=off; set standard_conforming_strings=on; +\endif CREATE TABLE test_jsquery (v jsonb);