[SPARK-58366][SQL] Support JSON_TABLE table-valued function - #57559
Open
ganeshashree wants to merge 3 commits into
Open
[SPARK-58366][SQL] Support JSON_TABLE table-valued function#57559ganeshashree wants to merge 3 commits into
ganeshashree wants to merge 3 commits into
Conversation
ganeshashree
force-pushed
the
SPARK-58366
branch
5 times, most recently
from
July 28, 2026 03:31
efcc563 to
d8b3a24
Compare
cloud-fan
reviewed
Jul 29, 2026
cloud-fan
left a comment
Contributor
There was a problem hiding this comment.
0 blocking, 1 non-blocking, 1 nit.
The semantics are coherent and well covered, but wide projections should avoid reparsing every row once per output column.
Suggestions (1)
- sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/jsonExpressions.scala:548: JSON_TABLE reparses each row item once per projected value or EXISTS column, so wide projections multiply parsing work by the column count. -- see inline
Nits: 1 minor item (see inline comments).
Verification
I traced the new grammar through AstBuilder into JsonTable and the existing Generate execution path. I also checked path validation, missing-versus-null handling, ANSI cast-mode capture, lazy parser cleanup, lateral activation, and the corresponding focused tests.
ganeshashree
force-pushed
the
SPARK-58366
branch
from
July 30, 2026 02:56
50f3b5e to
d141b62
Compare
Contributor
|
can we fix merge conflicts? |
Add the ANSI SQL:2016 `JSON_TABLE` table-valued function, which shreds a JSON
document into a relational table. A row path selects a sequence of JSON items
and a `COLUMNS` clause projects a typed value out of each item into a column.
Syntax (flat, non-nested subset):
JSON_TABLE(json_expr, row_path
COLUMNS (
col1 FOR ORDINALITY,
col2 <type> [PATH '<json_path>'],
col3 <type> EXISTS [PATH '<json_path>']
)
[ { NULL | ERROR } ON ERROR ]
) [AS] alias
- A row path ending in `[*]` expands a JSON array into one row per element; a
non-wildcard path yields a single row for the matched value.
- `FOR ORDINALITY`: a 1-based BIGINT row counter.
- Value columns are extracted and cast to the declared type; the path may be
explicit (`PATH '...'`) or implicit (`'$.<columnName>'`).
- `EXISTS` columns are a presence test cast to the declared type; a
present-but-null JSON value counts as existing, only an absent path is false.
- `{ NULL | ERROR } ON ERROR`: `NULL ON ERROR` (default) yields no rows on
null/malformed input; `ERROR ON ERROR` raises.
- Usable in a comma join and with `LATERAL`.
Implementation notes:
- A `JsonTable` `Generator` expression is wrapped by the existing `Generate`
operator, so no new execution operator is introduced.
- A dedicated grammar production handles the `COLUMNS(...)` clause;
`AstBuilder.visitJsonTableRelation` builds the plan. `ERROR`, `ORDINALITY`,
and `JSON_TABLE` are added as non-reserved keywords.
- A token-aware navigator (`JsonTableEvaluator`) extracts values so that a
missing path, a JSON `null`, and the literal string `"null"` are all
distinguished (unlike `get_json_object`). Non-trailing wildcard paths are
rejected during analysis via `DATATYPE_MISMATCH.INVALID_JSON_TABLE_PATH`.
- Only the flat subset is implemented; `NESTED PATH`, `FORMAT JSON` query
columns, and `DEFAULT ... ON EMPTY` are left as follow-ups.
`JSON_TABLE` is the SQL-standard way to turn JSON into rows and columns, and is
supported by Oracle, DB2, MySQL 8, PostgreSQL 17, Snowflake, and Trino. Spark
previously required chaining `from_json` + `explode`/`inline` + `get_json_object`
to achieve the same result. This folds that into one declarative, standard
construct and eases migration from those systems.
Yes. It adds the `JSON_TABLE` SQL table-valued function and a SQL reference
documentation page. `ERROR`, `ORDINALITY`, and `JSON_TABLE` are added as
non-reserved keywords, so existing queries using them as identifiers continue to
parse. There is no change to existing behavior.
New end-to-end suite `JsonTableSuite` covering array expansion, ordinality,
explicit/implicit paths, missing-vs-null semantics for value and EXISTS columns,
`NULL`/`ERROR ON ERROR`, `[*]` over a non-array, mid-path wildcard rejection,
non-explode string row items, arrays of scalars/strings, duplicate keys,
structure-valued columns, `LATERAL` joins, nested-field extraction, and column
aliases. Existing `JsonExpressionsSuite`, `JsonFunctionsSuite`,
`GeneratorFunctionSuite`, `PlanParserSuite`, `DDLParserSuite`, `SQLKeywordSuite`,
and `SparkThrowableSuite` continue to pass.
Generated-by: Claude Code (Claude Opus 4.8)
Address review comments: JSON_TABLE previously created a fresh Jackson parser and rescanned each row item once per projected value/EXISTS column, making wide projections O(columns * row size) per row. Build a prefix trie over the column paths once per invocation and resolve every column in a single traversal of each row item via navigateColumns. Ordinality-only tables skip parsing entirely, and object/array subtrees with no matching column paths are skipped without descending. Also fix the JsonPathResult.Found doc to match serializeCurrentValue. Co-authored-by: Isaac
Address review comments on the JSON_TABLE feature: - Add a `sql` override to `JsonTable` so analysis/type-check diagnostics render the full `JSON_TABLE(...)` syntax (row path, columns, ON ERROR) instead of only `json_table(<json_expr>)`. - Fix the default-column-path docs: an omitted PATH reads the column name as a single object key (`$.name` for simple identifiers, `$['a.b']` for dotted/special names), not the nested path `$.<column_name>`. - Link JSON_TABLE from the SELECT page's from_item list so it is discoverable from the main SELECT syntax page. Co-authored-by: Isaac
ganeshashree
force-pushed
the
SPARK-58366
branch
from
August 1, 2026 01:06
d141b62 to
6ab8dba
Compare
Contributor
Author
Fixed merge conflicts. |
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
What changes were proposed in this pull request?
Add the ANSI SQL:2016
JSON_TABLEtable-valued function, which shreds a JSON document into a relational table. A row path selects a sequence of JSON items and aCOLUMNSclause projects a typed value out of each item into a column.Syntax (flat, non-nested subset):
[*]expands a JSON array into one row per element; a non-wildcard path yields a single row for the matched value.FOR ORDINALITY: a 1-based BIGINT row counter.PATH '...') or implicit ('$.<columnName>').EXISTScolumns are a presence test cast to the declared type; a present-but-null JSON value counts as existing, only an absent path is false.{ NULL | ERROR } ON ERROR:NULL ON ERROR(default) yields no rows on null/malformed input;ERROR ON ERRORraises.LATERAL.Implementation notes:
JsonTableGeneratorexpression is wrapped by the existingGenerateoperator, so no new execution operator is introduced.COLUMNS(...)clause;AstBuilder.visitJsonTableRelationbuilds the plan.ERROR,ORDINALITY, andJSON_TABLEare added as non-reserved keywords.JsonTableEvaluator) extracts values so that a missing path, a JSONnull, and the literal string"null"are all distinguished (unlikeget_json_object). Non-trailing wildcard paths are rejected during analysis viaDATATYPE_MISMATCH.INVALID_JSON_TABLE_PATH.NESTED PATH,FORMAT JSONquery columns, andDEFAULT ... ON EMPTYare left as follow-ups.Why are the changes needed?
JSON_TABLEis the SQL-standard way to turn JSON into rows and columns, and is supported by Oracle, DB2, MySQL 8, PostgreSQL 17, Snowflake, and Trino. Spark previously required chainingfrom_json+explode/inline+get_json_objectto achieve the same result. This folds that into one declarative, standard construct and eases migration from those systems.Does this PR introduce any user-facing change?
Yes. It adds the
JSON_TABLESQL table-valued function and a SQL reference documentation page.ERROR,ORDINALITY, andJSON_TABLEare added as non-reserved keywords, so existing queries using them as identifiers continue to parse. There is no change to existing behavior.How was this patch tested?
New end-to-end suite
JsonTableSuitecovering array expansion, ordinality, explicit/implicit paths, missing-vs-null semantics for value and EXISTS columns,NULL/ERROR ON ERROR,[*]over a non-array, mid-path wildcard rejection, non-explode string row items, arrays of scalars/strings, duplicate keys, structure-valued columns,LATERALjoins, nested-field extraction, and column aliases. ExistingJsonExpressionsSuite,JsonFunctionsSuite,GeneratorFunctionSuite,PlanParserSuite,DDLParserSuite,SQLKeywordSuite, andSparkThrowableSuitecontinue to pass.Was this patch authored or co-authored using generative AI tooling?
Generated-by: Claude Code (Claude Opus 4.8)