Skip to content

Functions: fix json valid and json extract in conditions - #11036

Open
yongman wants to merge 2 commits into
pingcap:masterfrom
yongman:fix-json-valid
Open

Functions: fix json valid and json extract in conditions#11036
yongman wants to merge 2 commits into
pingcap:masterfrom
yongman:fix-json-valid

Conversation

@yongman

@yongman yongman commented Aug 11, 2026

Copy link
Copy Markdown
Member

What problem does this PR solve?

Issue Number: close #11011

Problem Summary:
JSON_EXTRACT error even with JSON_VALID in conditions.

What is changed and how it works?

  The fix tracks preceding JSON_VALID(expr) predicates within the same AND chain. When a later CastStringAsJson(expr) parses
  the same expression, invalid input is converted to an internal JSON null placeholder instead of throwing. The final AND
  predicate then removes that row through JSON_VALID.

  The behavior remains strict for unguarded casts, casts appearing before JSON_VALID, and casts in unrelated OR or non-AND
  branches. Guarded and strict casts also use distinct expression-action names to prevent common-subexpression reuse from
  leaking tolerant behavior.

  The fix is implemented in the shared expression analyzer, so it applies to regular MPP filters, DeltaMerge pushed-down
  filters, Columnar filter replay, and late materialization without storage-engine-specific changes.

Check List

Tests

  • Unit test
  • Integration test
  • Manual test (add detailed scripts or steps below)
  • No code

Side effects

  • Performance regression: Consumes more CPU
  • Performance regression: Consumes more Memory
  • Breaking backward compatibility

Documentation

  • Affects user behaviors
  • Contains syntax changes
  • Contains variable changes
  • Contains experimental features
  • Changes MySQL compatibility

Release note

None

Summary by CodeRabbit

  • Bug Fixes

    • Improved JSON filtering so validated JSON strings are parsed safely during query execution.
    • Invalid JSON values now produce JSON nulls when safely guarded by a validity check, while unguarded invalid inputs continue to raise errors.
    • Preserved correct behavior across nested logical conditions and late materialization scenarios.
  • Tests

    • Added coverage for guarded and unguarded JSON parsing, invalid conditions, and JSON extraction queries.

Signed-off-by: yongman <yming0221@gmail.com>
@ti-chi-bot ti-chi-bot Bot added do-not-merge/needs-triage-completed release-note-none Denotes a PR that doesn't merit a release note. labels Aug 11, 2026
@ti-chi-bot

ti-chi-bot Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign yongman for approval. For more information see the Code Review Process.
Please ensure that each of them provides their approval before proceeding.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@ti-chi-bot ti-chi-bot Bot added the size/L Denotes a PR that changes 100-499 lines, ignoring generated files. label Aug 11, 2026
@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d54040a5-0130-4b19-a426-5e9aaf335279

📥 Commits

Reviewing files that changed from the base of the PR and between 0e374eb and d225461.

📒 Files selected for processing (1)
  • dbms/src/Functions/tests/gtest_json_valid.cpp
🚧 Files skipped from review as they are similar to previous changes (1)
  • dbms/src/Functions/tests/gtest_json_valid.cpp

📝 Walkthrough

Walkthrough

Changes

The filter analyzer tracks JSON_VALID guards across conjunctions. Guarded string-to-JSON casts ignore invalid inputs. Tests cover nested, reversed, unsafe, and late-materialized query paths.

Guarded JSON filtering

Layer / File(s) Summary
Filter guard tracking
dbms/src/Flash/Coprocessor/DAGExpressionAnalyzer.h, dbms/src/Flash/Coprocessor/DAGExpressionAnalyzer.cpp
The analyzer records JSON guards during filter construction and restores state between conjuncts and nested logical expressions.
Guarded JSON parsing
dbms/src/Flash/Coprocessor/DAGExpressionAnalyzerHelper.cpp, dbms/src/Functions/FunctionsJson.h
Logical analysis configures guarded casts. FunctionCastStringAsJson emits JSON null for invalid inputs when configured to ignore them.
JSON guard validation
dbms/src/Functions/tests/gtest_json_valid.cpp, tests/fullstack-test/expr/json_valid.test
Tests cover guarded, nested, reversed, and unsafe conditions. Full-stack tests cover both late-materialization settings.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Poem

A rabbit checks each JSON gate,
JSON_VALID decides the fate.
Guarded casts turn bad rows null,
Safe AND paths pass the test.
Nested checks now work as well.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.88% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the fix for JSON_VALID and JSON_EXTRACT conditions.
Description check ✅ Passed The description covers the problem, implementation, integration test, side effects, documentation, and release note sections.
Linked Issues check ✅ Passed The changes address issue #11011 by preventing invalid JSON errors after JSON_VALID filtering in AND conditions.
Out of Scope Changes check ✅ Passed The implementation and tests are directly related to the JSON_VALID and JSON_EXTRACT condition fix.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
dbms/src/Functions/FunctionsJson.h (1)

1741-1751: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add a comment that explains the checkJsonValid re-check.

The condition !ignore_invalid_json || checkJsonValid(...) reads as inverted at first glance. It throws in tolerant mode when TiFlash's own validator accepts the input.

The intent is correct: a simdjson error on input that checkJsonValid accepts is not an invalidity error. It indicates a different failure, for example a depth or capacity limit. Such an error must not be converted into a filtered-out row.

State that intent inline so a later reader does not "simplify" the condition.

📝 Proposed comment
             const auto & json_elem = parser.parse(slice.data, slice.size);
             if (unlikely(json_elem.error()))
             {
+                // In tolerant mode, only true invalidity may become a JSON null placeholder.
+                // If checkJsonValid accepts the input, simdjson failed for another reason
+                // (for example a depth or capacity limit), so keep throwing.
                 if (!ignore_invalid_json || checkJsonValid(reinterpret_cast<const char *>(slice.data), slice.size))
                 {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dbms/src/Functions/FunctionsJson.h` around lines 1741 - 1751, Add an inline
comment immediately above the condition in the JSON error-handling block
explaining that checkJsonValid distinguishes true invalid JSON from simdjson
failures such as depth or capacity limits; in tolerant mode, throw when TiFlash
accepts the input so non-invalidity errors are not converted into filtered rows.
Do not change the condition or surrounding behavior.
dbms/src/Functions/tests/gtest_json_valid.cpp (1)

156-199: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the exception message in the negative cases.

ASSERT_THROW(..., Exception) passes for any DB::Exception. These four cases are the core negative assertions of this PR. Each one must fail because JSON parsing stayed strict, not because of an unrelated setup error such as a bad field type.

Use a matcher on the message so the test proves the intended cause.

♻️ Proposed change
+    auto assert_invalid_json_throw = [&](const google::protobuf::RepeatedPtrField<tipb::Expr> & conditions) {
+        try
+        {
+            execute_filter(conditions);
+            FAIL() << "expected an Invalid JSON text exception";
+        }
+        catch (const Exception & e)
+        {
+            ASSERT_TRUE(e.message().find("Invalid JSON text") != String::npos) << e.message();
+        }
+    };
+
     google::protobuf::RepeatedPtrField<tipb::Expr> reversed_conditions;
     *reversed_conditions.Add() = is_not_null;
     *reversed_conditions.Add() = json_valid;
-    ASSERT_THROW(execute_filter(reversed_conditions), Exception);
+    assert_invalid_json_throw(reversed_conditions);

Apply the same replacement to unguarded_conditions, or_conditions, and wrapped_conditions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dbms/src/Functions/tests/gtest_json_valid.cpp` around lines 156 - 199, Update
the four negative assertions in the test around reversed_conditions,
unguarded_conditions, or_conditions, and wrapped_conditions to verify that
execute_filter throws DB::Exception with a message matching the expected strict
JSON parsing failure. Apply the same message matcher consistently to each
ASSERT_THROW-style check, preserving the existing condition setup.
dbms/src/Flash/Coprocessor/DAGExpressionAnalyzerHelper.cpp (1)

205-218: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Extract the guard snapshot-and-restore into an RAII scope on DAGExpressionAnalyzer. The copy-build-restore sequence for json_valid_guarded_exprs is written by hand in two translation units, and the helper reaches directly into a private analyzer member to do it. One small RAII type removes both the duplication and the cross-class member access, and it also makes the restore exception-safe at each site.

  • dbms/src/Flash/Coprocessor/DAGExpressionAnalyzerHelper.cpp#L205-L218: replace the three manual analyzer->json_valid_guarded_exprs copies and moves with two nested scope objects, one for the whole function and one per child.
  • dbms/src/Flash/Coprocessor/DAGExpressionAnalyzer.cpp#L1033-L1038: replace guards_before_condition copy and move-back with the same scope object around the getActions call.
  • dbms/src/Flash/Coprocessor/DAGExpressionAnalyzer.h#L323-L333: declare the scope type, for example class JsonValidGuardScope, that saves json_valid_guarded_exprs on construction and restores it on destruction, and expose a recordJsonValidGuards entry point so the helper no longer touches the member directly.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dbms/src/Flash/Coprocessor/DAGExpressionAnalyzerHelper.cpp` around lines 205
- 218, Introduce DAGExpressionAnalyzer::JsonValidGuardScope in
dbms/src/Flash/Coprocessor/DAGExpressionAnalyzer.h (lines 323-333) to snapshot
json_valid_guarded_exprs on construction and restore it on destruction, and
expose recordJsonValidGuards for helper use. In
dbms/src/Flash/Coprocessor/DAGExpressionAnalyzerHelper.cpp (lines 205-218),
replace the manual whole-function and per-child copies/moves with nested scope
objects, routing guard recording through the public entry point. In
dbms/src/Flash/Coprocessor/DAGExpressionAnalyzer.cpp (lines 1033-1038), replace
the guards_before_condition copy and restoration with the same scope around
getActions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/fullstack-test/expr/json_valid.test`:
- Around line 31-44: Update both regression queries around the
late-materialization settings to also set tidb_allow_mpp=1, tidb_enforce_mpp=1,
and tidb_isolation_read_engines='tiflash' in each independent mysql session,
ensuring both queries exercise the TiFlash MPP path.

---

Nitpick comments:
In `@dbms/src/Flash/Coprocessor/DAGExpressionAnalyzerHelper.cpp`:
- Around line 205-218: Introduce DAGExpressionAnalyzer::JsonValidGuardScope in
dbms/src/Flash/Coprocessor/DAGExpressionAnalyzer.h (lines 323-333) to snapshot
json_valid_guarded_exprs on construction and restore it on destruction, and
expose recordJsonValidGuards for helper use. In
dbms/src/Flash/Coprocessor/DAGExpressionAnalyzerHelper.cpp (lines 205-218),
replace the manual whole-function and per-child copies/moves with nested scope
objects, routing guard recording through the public entry point. In
dbms/src/Flash/Coprocessor/DAGExpressionAnalyzer.cpp (lines 1033-1038), replace
the guards_before_condition copy and restoration with the same scope around
getActions.

In `@dbms/src/Functions/FunctionsJson.h`:
- Around line 1741-1751: Add an inline comment immediately above the condition
in the JSON error-handling block explaining that checkJsonValid distinguishes
true invalid JSON from simdjson failures such as depth or capacity limits; in
tolerant mode, throw when TiFlash accepts the input so non-invalidity errors are
not converted into filtered rows. Do not change the condition or surrounding
behavior.

In `@dbms/src/Functions/tests/gtest_json_valid.cpp`:
- Around line 156-199: Update the four negative assertions in the test around
reversed_conditions, unguarded_conditions, or_conditions, and wrapped_conditions
to verify that execute_filter throws DB::Exception with a message matching the
expected strict JSON parsing failure. Apply the same message matcher
consistently to each ASSERT_THROW-style check, preserving the existing condition
setup.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 30c06a7c-b05b-4054-86d2-600052930fc0

📥 Commits

Reviewing files that changed from the base of the PR and between 08be6d5 and 0e374eb.

📒 Files selected for processing (6)
  • dbms/src/Flash/Coprocessor/DAGExpressionAnalyzer.cpp
  • dbms/src/Flash/Coprocessor/DAGExpressionAnalyzer.h
  • dbms/src/Flash/Coprocessor/DAGExpressionAnalyzerHelper.cpp
  • dbms/src/Functions/FunctionsJson.h
  • dbms/src/Functions/tests/gtest_json_valid.cpp
  • tests/fullstack-test/expr/json_valid.test

Comment thread tests/fullstack-test/expr/json_valid.test
Signed-off-by: yongman <yming0221@gmail.com>
@JaySon-Huang

Copy link
Copy Markdown
Contributor

/cc @windtalker

@ti-chi-bot
ti-chi-bot Bot requested a review from windtalker August 12, 2026 08:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release-note-none Denotes a PR that doesn't merit a release note. size/L Denotes a PR that changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Invalid JSON text: The document root must not be followed by other values

2 participants