Repository navigation
feat(deparser): support JSON_QUERY, JSON_VALUE, JSON_EXISTS and JSON_TABLE #369
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
pyramation
wants to merge
1
commit into
main
Choose a base branch
from
devin/1792901235-deparser-json-query-table
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| SELECT JSON_EXISTS(jsonb '{"a": 1}', '$.a'); | ||
| SELECT JSON_EXISTS(jsonb '{"a": [1,2,3]}', 'strict $.a[5]' ERROR ON ERROR); | ||
| SELECT JSON_EXISTS(jsonb '[1,2,3]', '$[*] ? (@ > $x)' PASSING 2 AS x, 'b' AS "Y" UNKNOWN ON ERROR); | ||
| SELECT JSON_EXISTS(js FORMAT JSON, '$.a' TRUE ON ERROR) FROM t; | ||
| SELECT JSON_VALUE(jsonb '"aaa"', '$'); | ||
| SELECT JSON_VALUE(jsonb '123', '$' RETURNING int); | ||
| SELECT JSON_VALUE(jsonb '"2017-02-20"', '$' RETURNING date DEFAULT '2000-01-01' ON EMPTY NULL ON ERROR); | ||
| SELECT JSON_VALUE(jsonb '[1,2]', 'strict $[*]' DEFAULT 0 + 1 ON ERROR); | ||
| SELECT JSON_VALUE(js, '$.a' PASSING x + 1 AS x RETURNING text ERROR ON EMPTY ERROR ON ERROR) FROM t; | ||
| SELECT JSON_QUERY(jsonb '[1,2]', '$[*]'); | ||
| SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text KEEP QUOTES); | ||
| SELECT JSON_QUERY(jsonb '"aaa"', '$' RETURNING text OMIT QUOTES ON SCALAR STRING); | ||
| SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' WITH WRAPPER); | ||
| SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' WITH CONDITIONAL ARRAY WRAPPER); | ||
| SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' WITH UNCONDITIONAL WRAPPER); | ||
| SELECT JSON_QUERY(jsonb '[1,2]', '$[*]' WITHOUT WRAPPER); | ||
| SELECT JSON_QUERY(jsonb '[]', '$[*]' EMPTY ON EMPTY); | ||
| SELECT JSON_QUERY(jsonb '[]', '$[*]' EMPTY ARRAY ON EMPTY EMPTY OBJECT ON ERROR); | ||
| SELECT JSON_QUERY(js FORMAT JSON, '$.a' PASSING 1 AS x RETURNING bytea FORMAT JSON ENCODING UTF8 WITH CONDITIONAL WRAPPER KEEP QUOTES DEFAULT '"x"' ON EMPTY ERROR ON ERROR) FROM t; | ||
| CREATE VIEW json_query_v AS SELECT JSON_QUERY(jsonb '{"a": 1}', '$.a' RETURNING jsonb WITH WRAPPER NULL ON ERROR) AS q, JSON_VALUE(jsonb '1', '$' RETURNING int) AS v, JSON_EXISTS(jsonb '1', '$') AS e; | ||
| CREATE TABLE json_check (js jsonb CHECK (JSON_EXISTS(js, '$.a')), v text DEFAULT JSON_VALUE(jsonb '"x"', '$')); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| SELECT * FROM JSON_TABLE(jsonb '[1, 2]', '$[*]' COLUMNS (a int PATH '$')); | ||
| SELECT * FROM JSON_TABLE(jsonb '[1, 2]', '$[*]' COLUMNS (id FOR ORDINALITY, a int)) jt; | ||
| SELECT * FROM JSON_TABLE(jsonb '[1, 2]', '$[*]' AS root COLUMNS (a int PATH '$')) AS jt (x); | ||
| SELECT * FROM JSON_TABLE(jsonb '{"a": [1]}', '$' PASSING 1 AS x, 'y' AS "Y" COLUMNS (a int PATH '$.a[*] ? (@ > $x)' DEFAULT -1 ON EMPTY ERROR ON ERROR) ERROR ON ERROR) jt; | ||
| SELECT * FROM JSON_TABLE(jsonb '{"a": 1}', '$' COLUMNS (e bool EXISTS PATH '$.a', e2 int EXISTS PATH '$.b' TRUE ON ERROR)) jt; | ||
| SELECT * FROM JSON_TABLE(jsonb '{"a": "x"}', '$' COLUMNS (js jsonb FORMAT JSON PATH '$.a' WITH CONDITIONAL WRAPPER OMIT QUOTES, js2 text FORMAT JSON ENCODING UTF8 PATH '$' WITHOUT WRAPPER KEEP QUOTES EMPTY OBJECT ON ERROR)) jt; | ||
| SELECT * FROM JSON_TABLE(jsonb '{"a": "x"}', '$' COLUMNS (q jsonb PATH '$.a' WITH WRAPPER EMPTY ARRAY ON EMPTY, "Mixed Col" text PATH '$.a' NULL ON ERROR)) jt; | ||
| SELECT * FROM JSON_TABLE(jsonb '{"a": [{"b": 1}]}', '$' COLUMNS (id FOR ORDINALITY, NESTED PATH '$.a[*]' COLUMNS (b int PATH '$.b', NESTED PATH '$.c[*]' AS c_path COLUMNS (c text)), NESTED '$.d' AS d COLUMNS (d int))) jt; | ||
| SELECT jt.* FROM t, LATERAL JSON_TABLE(t.js, '$[*]' COLUMNS (a text PATH '$.a')) AS jt; | ||
| SELECT jt.* FROM t CROSS JOIN JSON_TABLE(t.js FORMAT JSON, '$[*]' COLUMNS (a text)) jt; | ||
| CREATE VIEW json_table_view AS SELECT * FROM JSON_TABLE(jsonb 'null', 'lax $[*]' PASSING 1 + 2 AS a, json '"foo"' AS "b c" COLUMNS (id FOR ORDINALITY, "int" int PATH '$', js json PATH '$', jsb jsonb PATH '$', exists1 bool EXISTS PATH '$.aaa', NESTED PATH '$[1]' AS p1 COLUMNS (a1 int, NESTED PATH '$[*]' AS "p1 1" COLUMNS (a11 text)))) jt; |
29 changes: 29 additions & 0 deletions
29
packages/deparser/__tests__/kitchen-sink/misc-json-query-functions.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
|
|
||
| import { FixtureTestUtils } from '../../test-utils'; | ||
| const fixtures = new FixtureTestUtils(); | ||
|
|
||
| it('misc-json-query-functions', async () => { | ||
| await fixtures.runFixtureTests([ | ||
| "misc/json-query-functions-1.sql", | ||
| "misc/json-query-functions-2.sql", | ||
| "misc/json-query-functions-3.sql", | ||
| "misc/json-query-functions-4.sql", | ||
| "misc/json-query-functions-5.sql", | ||
| "misc/json-query-functions-6.sql", | ||
| "misc/json-query-functions-7.sql", | ||
| "misc/json-query-functions-8.sql", | ||
| "misc/json-query-functions-9.sql", | ||
| "misc/json-query-functions-10.sql", | ||
| "misc/json-query-functions-11.sql", | ||
| "misc/json-query-functions-12.sql", | ||
| "misc/json-query-functions-13.sql", | ||
| "misc/json-query-functions-14.sql", | ||
| "misc/json-query-functions-15.sql", | ||
| "misc/json-query-functions-16.sql", | ||
| "misc/json-query-functions-17.sql", | ||
| "misc/json-query-functions-18.sql", | ||
| "misc/json-query-functions-19.sql", | ||
| "misc/json-query-functions-20.sql", | ||
| "misc/json-query-functions-21.sql" | ||
| ]); | ||
| }); |
19 changes: 19 additions & 0 deletions
19
packages/deparser/__tests__/kitchen-sink/misc-json-table.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
|
|
||
| import { FixtureTestUtils } from '../../test-utils'; | ||
| const fixtures = new FixtureTestUtils(); | ||
|
|
||
| it('misc-json-table', async () => { | ||
| await fixtures.runFixtureTests([ | ||
| "misc/json-table-1.sql", | ||
| "misc/json-table-2.sql", | ||
| "misc/json-table-3.sql", | ||
| "misc/json-table-4.sql", | ||
| "misc/json-table-5.sql", | ||
| "misc/json-table-6.sql", | ||
| "misc/json-table-7.sql", | ||
| "misc/json-table-8.sql", | ||
| "misc/json-table-9.sql", | ||
| "misc/json-table-10.sql", | ||
| "misc/json-table-11.sql" | ||
| ]); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 bug · medium
ON SCALAR STRING silently dropped from JSON quote clauses
The
quotesfield is a bare enum (JS_QUOTES_KEEP/JS_QUOTES_OMIT), soformatJsonQuotescan only emitKEEP QUOTES/OMIT QUOTESand silently drops theON SCALAR STRINGmodifier (packages/deparser/src/deparser.ts:11149). Upstream PostgreSQL carries the ON SCALAR STRING flag as a separate boolean on the JSON quotes node and its ruleutils prints it back, but this repo's libpg-query protobuf (JsonQuotesenum in__fixtures__/proto/17-latest.proto:2900) has no such field, so the flag is already lost in the parse tree.JSON_QUERY(x, '$' OMIT QUOTES ON SCALAR STRING)therefore deparses asOMIT QUOTES, which omits quotes unconditionally instead of only for scalar strings — semantically different SQL with no error. The kitchen-sink fixture__fixtures__/kitchen-sink/misc/json-query-functions.sql:12exercises exactly this clause, and round-trip tests pass only because reparse matches the already-lossy AST.📋 Prompt for AI Agents
In packages/deparser/src/deparser.ts around lines 11149-11158 (formatJsonQuotes) and its callers JsonFuncExpr (line ~11196) and JsonTableColumn (line ~11245), handle the PostgreSQL
ON SCALAR STRINGmodifier that upstream attaches to the JSON quotes node. First inspect the libpg-query version used at runtime to see whether it exposes a scalar_string/on_scalar field next to the JsonQuotes enum (the fixture protos in fixtures/proto/17-latest.proto lack it); if the runtime parse tree carries it, thread it through the types and emitON SCALAR STRINGwhen set. If the proto cannot represent the flag, add a test/comment documenting thatOMIT QUOTES ON SCALAR STRINGround-trips lossily (e.g. in fixtures/kitchen-sink/misc/json-query-functions.sql line 12) instead of silently emitting semantically different SQL.