[SkipRecovery] Fix get_json_object runtime path semantics [databricks] - #15850
[SkipRecovery] Fix get_json_object runtime path semantics [databricks]#15850wjxiz1992 wants to merge 7 commits into
Conversation
Signed-off-by: Allen Xu <allxu@nvidia.com>
Greptile SummaryThis PR moves JSON-path parsing into version-specific shims so GPU
Confidence Score: 5/5The PR appears safe to merge because no blocking failure remains. No blocking failure remains. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
P[Literal JSON path] --> V{Spark shim}
V -->|Spark 3 except 3.5.3| L[Legacy parser]
V -->|Spark 3.5.3| D{Dataproc marker present?}
D -->|No| L
D -->|Yes| F[Fixed parser]
V -->|Spark 4| F
L --> I[Path instructions]
F --> I
I --> E{Any valid paths?}
E -->|Yes| J[JNI multi-path evaluation]
E -->|No| N[Construct null output columns]
Reviews (7): Last reviewed commit: "Merge branch 'main' into fix/14290-get-j..." | Re-trigger Greptile |
There was a problem hiding this comment.
Pull request overview
This pull request fixes GPU/CPU divergence for get_json_object when JSON paths contain quoted ? keys on some Spark 3.5 runtimes with vendor backports (e.g., Dataproc), by detecting the runtime’s Catalyst semantics once and selecting the matching JSON-path parser dialect (or failing closed to CPU when unknown).
Changes:
- Added a shared
GetJsonObjectRuntimeSemanticsclassifier and updated shims to exposequotedQuestionMarkSupport(Spark 4 hard-coded modern; pre-Spark-4 probes Catalyst at runtime). - Updated
GpuGetJsonObject/JsonPathParserto parse quoted names using the selected dialect, and fail closed to CPU when runtime semantics are unknown. - Added Scala unit tests plus Python integration tests for quoted
?keys and invalid-path batches (with GPU plan capture assertions).
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| tests/src/test/scala/com/nvidia/spark/rapids/JsonPathParserSuite.scala | New unit coverage for parser dialect behavior and runtime probe classification. |
| sql-plugin/src/main/spark400/scala/com/nvidia/spark/rapids/shims/GetJsonObjectShim.scala | Spark 4 shim now reports modern quoted-? support. |
| sql-plugin/src/main/spark330/scala/com/nvidia/spark/rapids/shims/GetJsonObjectShim.scala | Pre-Spark-4 shim probes Catalyst once to detect quoted-? support at runtime. |
| sql-plugin/src/main/scala/com/nvidia/spark/rapids/shims/GetJsonObjectRuntimeSemantics.scala | New shared helper to classify probe results (modern/legacy/unknown). |
| sql-plugin/src/main/scala/com/nvidia/spark/rapids/GpuGetJsonObject.scala | Plumbs dialect into parsing and expression conversion; adds fail-closed behavior and fixes empty multi-path handling. |
| integration_tests/src/main/python/get_json_test.py | Adds/updates ITs for quoted ? keys and invalid paths with executed-plan capture. |
Suppressed comments (1)
integration_tests/src/main/python/get_json_test.py:158
- This query projects multiple
get_json_objectexpressions, so it is expected to be combined intoGpuMultiGetJsonObject(and not necessarily containGpuGetJsonObjectnodes). AssertingGpuGetJsonObjecthere can cause a false failure if expression combining is enabled (default). AssertGpuMultiGetJsonObjectinstead.
assert_cpu_and_gpu_are_equal_collect_with_capture(
lambda spark: spark.createDataFrame(data,schema=schema).selectExpr(
'get_json_object(jsonStr, CAST(NULL AS STRING)) AS null_path',
'get_json_object(jsonStr, "$[") AS malformed_path',
'get_json_object(jsonStr, "not_a_path") AS missing_root'),
exist_classes='GpuProjectExec,GpuGetJsonObject')
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| assert_cpu_and_gpu_are_equal_collect_with_capture( | ||
| lambda spark: spark.createDataFrame(data,schema=schema).select( | ||
| f.get_json_object('jsonStr',r'''$['?']''').alias('question'))) | ||
| f.get_json_object('jsonStr',r'''$['?']''').alias('question'), | ||
| f.get_json_object('jsonStr',r'''$['a?b']''').alias('embedded'), | ||
| f.get_json_object('jsonStr',r'''$.outer['?']''').alias('nested')), | ||
| exist_classes='GpuProjectExec,GpuGetJsonObject') |
There was a problem hiding this comment.
I kept the GpuGetJsonObject plan assertion. GpuEquivalentExpressions.replaceMultiExpressions runs inside GpuProjectExec.internalDoExecuteColumnar while binding a GpuTieredProject, so it does not rewrite the Spark executedPlan tree inspected by exist_classes. Changing both assertions to GpuMultiGetJsonObject made the focused Spark 3.3 GPU IT fail; restoring GpuGetJsonObject passed both tests (2 passed).
Signed-off-by: Allen Xu <allxu@nvidia.com>
|
build |
|
|
||
| import org.apache.spark.unsafe.types.UTF8String | ||
|
|
||
| private[rapids] object GetJsonObjectRuntimeSemantics { |
There was a problem hiding this comment.
Personally GetJsonObjectRuntimeSemantics is not necessary, we can merge it with object GetJsonObjectShim.
It is not a good idea to put the expected result "QUESTION" and the Json test literal string {"?":"QUESTION"} into different classes/objects.
If you want keep GetJsonObjectRuntimeSemantics common, maybe we need callers to provide the expect results instead of the hardcode. e.g. def classifyQuotedQuestionMarkResult(result: => Any, expected: Any): Option[Boolean]
Signed-off-by: Allen Xu <allxu@nvidia.com>
|
build |
revans2
left a comment
There was a problem hiding this comment.
I also am not thrilled with us trying to combine the different parser versions. For me if we are copying code from Spark, it would be nice to make sure it stays copied, even if it means more duplicate code. It just makes it that much simpler to maintain because we can diff it with the original code. I get in this case the change is very small, so for me it is just a nit.
Signed-off-by: Allen Xu <allxu@nvidia.com>
|
build |
Keep the legacy Spark 3 and SPARK-46761 parser implementations as independent shim-local copies so each remains directly diffable with Apache Spark. Select the known Dataproc backport from spark.dataproc.engine without probing Catalyst behavior. Performance: This changes only driver-side literal JSON path parsing. Parser selection is cached once, and no executor row or batch loop is affected. Signed-off-by: Allen Xu <allxu@nvidia.com>
Signed-off-by: Allen Xu <allxu@nvidia.com>
|
build |
2 similar comments
|
build |
|
build |
Signed-off-by: Allen Xu <allxu@nvidia.com>
|
build |
1 similar comment
|
build |
|
Hi @revans2, when you have a chance, could you please take another look at this PR? I’ve followed up on your review feedback. Thanks! |
JaCoCo production line coverage: +83 lines (
sql-plugin +83: shared parser +4, Spark 330 shim +18, Spark 353 shim +43, Spark 400 shim +18;JsonPathParserSuitefix-line intersection)Fixes #14290.
Description
Dataproc backported SPARK-46761 while retaining Spark 3.5.3. A Spark version check alone therefore cannot distinguish vanilla Spark 3.5.3 from the Google-patched runtime, and the previous revision inferred the behavior by evaluating a Catalyst expression at runtime.
This revision removes that semantic probe and uses the mapping established by testing every supported Dataproc line:
spark.dataproc.engineplatform marker identifies Dataproc. Vanilla Spark 3.5.3 remains legacy so CPU and GPU behavior stay aligned.The change is cold-path only. Non-3.5.3 shims select statically, and the 3.5.3 shim performs one lazy SparkConf marker lookup. No per-row, per-batch, or JNI work was added; parsed instructions remain cached.
Dataproc runtime matrix
The quoted question-mark key was evaluated with the CPU Spark expression on every supported Classic and Serverless line.
NULL— legacy2.2-ubuntu22, resolved to 2.2.84QUESTION— fixedQUESTION— fixedNULL— legacyQUESTION— fixedQUESTION— fixedQUESTION— fixedThe exact Classic 2.2.86 image is retired and was rejected before resource creation, so the Classic 2.2 check used the currently resolvable 2.2 image, 2.2.84. All submitted Classic probes reached
DONEwith YARNFINISHED; all Serverless batches reachedSUCCEEDED.Exact-head Dataproc GPU validation
A clean custom JAR built from commit
bc7d89bfa5aba4b3a558441994d331a4846b97abwas validated on Dataproc Classic 2.2.84-debian12 with Spark 3.5.3 and two Tesla T4 workers.69194905a24df75828273219dd9c72c55625b272a5dea6a49c6575cdf703ca909fe997a7047f45e29cfa6f18663f07e0:DONE, YARNFINISHED.QUESTION; GPU result:QUESTION;CPU_GPU_EQUAL=true.GpuProjectwithget_json_objectandGpuRange;GPU_PLAN_EVIDENCE=PASS.Local validation
JsonPathParserSuite: Spark build versions 330, 332, 340, 351, 353, 400, and 401 all passed withsucceeded 4, failed 0; Spark 400 passed with ANSI both off and on.BUILD SUCCESS.3 passed, 32630 deselected; CPU/GPU equality and GPU-plan capture passed.sql-plugin +83distinct covered production lines.AI assistance: This change and PR description were prepared with AI assistance.
Checklists
Documentation
Testing
(Please provide the names of the existing tests in the PR description.)
Performance