Skip to content

planner: enable cd-c algorithm by default for join reorder | tidb-test=pr/2676 (#66349)#67289

Open
ti-chi-bot wants to merge 1 commit intopingcap:release-7.5from
ti-chi-bot:cherry-pick-66349-to-release-7.5
Open

planner: enable cd-c algorithm by default for join reorder | tidb-test=pr/2676 (#66349)#67289
ti-chi-bot wants to merge 1 commit intopingcap:release-7.5from
ti-chi-bot:cherry-pick-66349-to-release-7.5

Conversation

@ti-chi-bot
Copy link
Copy Markdown
Member

@ti-chi-bot ti-chi-bot commented Mar 25, 2026

This is an automated cherry-pick of #66349

What problem does this PR solve?

Issue Number: close #63887

Problem Summary:

  1. enable new join reorder impl by default
  2. clean all the cases

What changed and how does it work?

Check List

Tests

  • Unit test
  • Integration test
  • Manual test (add detailed scripts or steps below)
  • No need to test
    • I checked and no code files have been changed.

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

Please refer to Release Notes Language Style Guide to write a quality release note.

None

Summary by CodeRabbit

  • New Features

    • Added advanced join order optimization with enhanced conflict detection for complex queries, particularly improving outer join reordering and Cartesian join handling.
  • Tests

    • Expanded test coverage for join optimization scenarios, including outer joins, semi joins, and multi-table reordering strategies.

Signed-off-by: ti-chi-bot <ti-community-prow-bot@tidb.io>
@ti-chi-bot ti-chi-bot added do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. ok-to-test Indicates a PR is ready to be tested. release-note-none Denotes a PR that doesn't merit a release note. sig/planner SIG: Planner size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. type/cherry-pick-for-release-7.5 This PR is cherry-picked to release-7.5 from a source PR. labels Mar 25, 2026
@ti-chi-bot
Copy link
Copy Markdown

ti-chi-bot Bot commented Mar 25, 2026

This cherry pick PR is for a release branch and has not yet been approved by triage owners.
Adding the do-not-merge/cherry-pick-not-approved label.

To merge this cherry pick:

  1. It must be LGTMed and approved by the reviewers firstly.
  2. For pull requests to TiDB-x branches, it must have no failed tests.
  3. AFTER it has lgtm and approved labels, please wait for the cherry-pick merging approval from triage owners.
Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@ti-chi-bot
Copy link
Copy Markdown

ti-chi-bot Bot commented Mar 25, 2026

[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 winoros 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
Copy link
Copy Markdown
Member Author

@guo-shaoge This PR has conflicts, I have hold it.
Please resolve them or ask others to resolve them, then comment /unhold to remove the hold label.

@ti-chi-bot
Copy link
Copy Markdown

ti-chi-bot Bot commented Mar 25, 2026

@ti-chi-bot: ## If you want to know how to resolve it, please read the guide in TiDB Dev Guide.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the ti-community-infra/tichi repository.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 25, 2026

📝 Walkthrough

Walkthrough

This PR implements a new join order optimizer based on the CD-C (Conflict Detector with Conflict Rules) algorithm for handling outer, semi, and anti joins. It adds a new joinorder package with conflict detection logic, integrates it into the planner's join reorder rule, and updates numerous test expectations to reflect changes in query plan generation under the new optimization strategy.

Changes

Cohort / File(s) Summary
Join Order Optimizer Implementation
pkg/planner/core/joinorder/BUILD.bazel, pkg/planner/core/joinorder/conflict_detector.go, pkg/planner/core/joinorder/join_order.go
New package implementing CD-C-based join reordering with conflict detection, edge conflict rules, cartesian-join handling, and greedy/bushy join tree assembly. Includes exported ConflictDetector type and Optimize function for plan optimization.
Join Reorder Rule Integration
pkg/planner/core/rule_join_reorder.go
Updated join reorder entrypoint to conditionally route to new joinorder.Optimize() when TiDBOptEnableAdvancedJoinReorder is enabled; exports JoinReOrderSolver.Optimize() method and removes previous tracing logic.
Binary Plan Test Fixtures
pkg/planner/core/casetest/binaryplan/testdata/binary_plan_suite_out.json, ...binary_plan_suite_xut.json
Updated and added binary plan expected outputs for explain analyze with new operator numbering and node IDs; includes merge conflict markers in one file.
CBO Test Fixtures
pkg/planner/core/casetest/cbotest/testdata/analyze_suite_out.json, ...analyze_suite_xut.json
Updated inequality ordering in join range expressions and added comprehensive CBO test suite with multiple test groups covering join planning, estimation, and optimization scenarios.
Join Reorder Test Fixtures
pkg/planner/core/casetest/rule/testdata/cdc_join_reorder_suite_out.json, ...cdc_join_reorder_suite_xut.json, ...join_reorder_suite_out.json
Added and updated join reorder test suites for CDC-aware reordering and standard reordering, including multi-table join scenarios; includes unresolved merge markers.
Outer-to-Semi/Inner Join Test Fixtures
pkg/planner/core/casetest/rule/testdata/outer2inner_out.json, ...outer2inner_xut.json, ...outer_to_semi_join_suite_out.json, ...outer_to_semi_join_suite_xut.json
Added new test suites for outer-to-inner and outer-to-semi join transformations with various predicate patterns and null-handling semantics.
JSON Plan Test Fixtures
pkg/planner/core/casetest/testdata/json_plan_suite_out.json, ...json_plan_suite_xut.json, pkg/planner/core/casetest/hint/testdata/integration_suite_out.json, pkg/planner/core/casetest/mpp/testdata/integration_suite_out.json
Updated JSON plan outputs with new operator metadata, column ID projections, and execution info; includes merge conflict markers; removed expected warnings in one case.
TPC-DS Test Fixtures
pkg/planner/core/casetest/tpcds/testdata/tpcds_suite_out.json, ...tpcds_suite_xut.json
Added new TPC-DS Q64 test suite fixtures with expected physical plan and result specifications.
Integration Test Results
tests/integrationtest/r/planner/core/casetest/index/index.result, tests/integrationtest/r/planner/core/issuetest/planner_issue.result, tests/integrationtest/r/planner/core/join_reorder2.result, tests/integrationtest/r/planner/core/plan_cache.result, tests/integrationtest/r/planner/core/rule_join_reorder.result, tests/integrationtest/r/select.result, tests/integrationtest/r/tpch.result
Updated expected EXPLAIN outputs across multiple integration tests reflecting new plan shapes, join strategies, and operator numbering under the new join reorder optimization; several files contain unresolved merge conflict markers.

Sequence Diagram(s)

sequenceDiagram
    actor Planner
    participant JoinReorderRule as Join Reorder Rule
    participant JoinOrder as joinorder.Optimize()
    participant ConflictDetector as Conflict Detector
    participant JoinGroup as Join Group
    participant EdgeValidator as Edge Validator
    participant TreeBuilder as Tree Builder

    Planner->>JoinReorderRule: Optimize(plan)
    alt Advanced Reorder Enabled
        JoinReorderRule->>JoinOrder: Optimize(plan)
        JoinOrder->>ConflictDetector: Build(joinGroup)
        ConflictDetector->>JoinGroup: Extract vertexes, hints, edges
        ConflictDetector->>EdgeValidator: Compute conflict rules for edges
        JoinOrder->>TreeBuilder: optimizeForJoinGroup()
        TreeBuilder->>TreeBuilder: Greedy join enumeration
        TreeBuilder->>EdgeValidator: CheckConnection(node1, node2)
        EdgeValidator-->>TreeBuilder: Valid connection + rules satisfied?
        TreeBuilder->>ConflictDetector: MakeJoin(checkResult)
        ConflictDetector-->>TreeBuilder: New reordered join node
        TreeBuilder->>TreeBuilder: Build remaining bushy tree
        TreeBuilder-->>JoinOrder: Optimized join tree
        JoinOrder-->>JoinReorderRule: Reordered plan
    else Standard Reorder
        JoinReorderRule->>JoinReorderRule: optimizeRecursive(plan)
    end
    JoinReorderRule-->>Planner: Optimized plan
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested labels

cherry-pick-approved

Suggested reviewers

  • guo-shaoge
  • qw4990
  • winoros
  • AilinKid

Poem

🐰 A conflict detector hops through joins with care,
Reordering tables while preserving what's fair,
Semi and outer joins now find their place,
As vertices dance in the optimizer's space,
Each edge checked thrice before the bind—
Join order perfected, no bugs left behind! ✨

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 19.23% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The description is incomplete. It is missing detailed information about what changed and how it works. While it mentions the issue number and intent to enable the new join reorder implementation, it lacks technical details and explanation. Add more detailed explanation of the changes made, implementation approach, and how the cd-c algorithm fix addresses the incorrect join reorder issue described in #63887.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: enabling the cd-c algorithm by default for join reorder, which is the primary purpose of this PR.
Linked Issues check ✅ Passed The PR changes address the requirements of linked issue #63887 by implementing and enabling the cd-c algorithm for join reorder, which fixes the incorrect outer-join reordering behavior that was causing incorrect query results.
Out of Scope Changes check ✅ Passed The PR contains only test data updates for the join reorder implementation and does not introduce unrelated changes outside the scope of enabling the cd-c algorithm for join reorder.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.11.3)

Error: can't load config: unsupported version of the configuration: "" See https://golangci-lint.run/docs/product/migration-guide for migration instructions
The command is terminated due to an error: can't load config: unsupported version of the configuration: "" See https://golangci-lint.run/docs/product/migration-guide for migration instructions


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 and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (3)
pkg/planner/core/casetest/rule/testdata/cdc_join_reorder_suite_out.json (1)

1208-1340: Add a leading() case that explicitly requires a cartesian edge.

TestJoinReorderPushSelection exercises leading(...), but none of these cases pins the special exception where a hint is allowed to force a cartesian join. That edge regressed before, so I’d add one positive hinted-cartesian case and one comparable non-hinted case to keep the gating behavior locked down.

Based on learnings: "In pkg/planner/core/joinorder/ (TiDB join reorder), cartesian edges should only be considered in two specific scenarios: 1. After the entire join reorder is complete and there are still disjoint join groups that need to be connected. 2. When a leading hint explicitly requires a cartesian join."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/planner/core/casetest/rule/testdata/cdc_join_reorder_suite_out.json`
around lines 1208 - 1340, Add two cases to the TestJoinReorderPushSelection
suite: (1) a SQL with a leading(...) hint that forces a cartesian edge (so the
expected Plan shows a cartesian join/connect between disjoint join groups) and
(2) a comparable SQL without the leading hint that must NOT produce that
cartesian edge (expected Plan should show normal reorder without cartesian).
Locate the JSON array under "Cases" for TestJoinReorderPushSelection and add the
two entries with unique SQL strings and matching "Plan" arrays; ensure the
hinted case references the same table groups as the non-hinted case and that the
expected plan text explicitly indicates the cartesian/connect behavior so the
test locks the special-case allowing cartesian only when leading(...) forces it.
pkg/planner/core/casetest/rule/testdata/cdc_join_reorder_suite_xut.json (1)

3-5: Add a regression for cartesian-edge gating.

This suite still misses the case that previously regressed: disconnected join subgraphs with no leading(...) should only get a cartesian join in the final stitch, while a leading(...) case may require one earlier. Please lock that down here.

Based on learnings: planner rule changes should add targeted planner tests, and in pkg/planner/core/joinorder/ cartesian edges should only be considered after the entire join reorder is complete with disjoint groups, or when a leading hint explicitly requires a cartesian join.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/planner/core/casetest/rule/testdata/cdc_join_reorder_suite_xut.json`
around lines 3 - 5, Add a targeted regression case to the TestCDCJoinReorder
suite that covers disconnected join subgraphs: create a test input where two
disjoint join groups are present with no leading(...) hints and assert that a
cartesian edge is only introduced in the final stitch (not during intermediate
reorder steps); also add a companion case where a leading(...) hint forces an
earlier cartesian join so the planner emits the cartesian edge earlier. Ensure
the new cases exercise the planner join reorder logic (joinorder/cartesian-edge
handling) and assert the presence/absence and position of cartesian joins
accordingly.
pkg/planner/core/casetest/testdata/json_plan_suite_xut.json (1)

101-162: Add one outer/semi/anti-join tidb_json case here.

The new explain analyze format = tidb_json coverage only exercises a plain inner join. Since this PR turns cd-c on by default for outer/semi/anti reorder, one golden from that family would make this suite hit the changed planner path instead of only a stable inner-join plan.

Based on learnings: For planner rule or logical/physical plan changes, perform targeted planner unit tests and update rule testdata when needed.

Also applies to: 272-333

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/planner/core/casetest/testdata/json_plan_suite_xut.json` around lines 101
- 162, Add a new test case object alongside the existing one that uses "SQL":
"explain analyze format = tidb_json ..." with an outer/semi/anti join (e.g., a
LEFT JOIN like "select * from t1 left join t2 on t1.id = t2.id" or a SEMI/ANTI
variant) so the suite exercises the CD-C outer/semi/anti reorder path; place it
as another top-level entry with its own "JSONPlan" stub/expected plan (mirroring
structure of the existing "JSONPlan" key) and ensure a corresponding expected
operator id (e.g., an outer-join operator id or semi/anti join id) is present so
the planner unit test will hit the changed planner path—repeat the same addition
in the other suite block noted by the reviewer.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pkg/planner/core/casetest/testdata/json_plan_suite_xut.json`:
- Around line 1-4: The new json_plan_suite_xut.json fixture is not picked up
because LoadTestSuiteData in pkg/testkit/testdata/testdata.go only looks for
*_in.json/*_out.json pairs; either rename json_plan_suite_xut.json to follow the
existing *_in.json/*_out.json convention so it will be discovered, or update
LoadTestSuiteData (and any helper functions/matchers it uses) to also recognize
the _xut.json suffix by adding that pattern to the file discovery logic and
ensure it is parsed/paired the same way as the existing in/out files.

In `@pkg/planner/core/joinorder/join_order.go`:
- Around line 454-473: The greedy pass currently conflates allowing non-equi
joins and permitting cartesian stitching via allowNoEQ; change the API and call
sites so greedyConnectJoinNodes only controls non-equi edge usage (rename/keep
allowNonEq), and add a separate boolean or mode (e.g., allowCartesian) that is
NOT set in the normal greedy passes. Remove any calls inside
greedyConnectJoinNodes that invoke TryCreateCartesianCheckResult when
allowCartesian is false. Ensure cartesian creation is only invoked from
buildJoinByHint (when a leading hint explicitly requests it) and from the final
fallback (makeBushyTree) after the whole reorder if
detector.HasRemainingEdges(usedEdges) still indicates disconnected groups; keep
the cartesianFactor logic and clamping only in those cartesian-specific paths
and do not enable cartesian stitching in the intermediate retry where you only
want to enable non-equi joins (the second greedyConnectJoinNodes call should
pass allowCartesian=false but allowNonEq=true).

In `@tests/integrationtest/r/planner/core/casetest/index/index.result`:
- Around line 1-823: Missing pairing for the integration test result:
tests/integrationtest/r/planner/core/casetest/index/index.result has no
corresponding test input file; either add
tests/integrationtest/t/planner/core/casetest/index/index.test containing the
test SQLs that produced this result or confirm that the result should be merged
into an existing file such as
tests/integrationtest/t/planner/core/casetest/integration.test, then regenerate
the .result by running the framework's recording command so the result and test
file are in sync.

In `@tests/integrationtest/r/planner/core/issuetest/planner_issue.result`:
- Around line 668-679: The test's non-deterministic LIMIT usage (the SQL "select
* from t t1 left join t t2 on t1.a=t2.a limit 1, 1" and its corresponding
"explain format='plan_tree' ... limit 1, 1") must include an explicit ORDER BY
to guarantee a stable returned row; update both the query and the EXPLAIN query
to add a deterministic ordering key (for example "ORDER BY t1.a" or a stable
primary key/row id) so the expected row assertion is deterministic and matches
the recorded plan output.

---

Nitpick comments:
In `@pkg/planner/core/casetest/rule/testdata/cdc_join_reorder_suite_out.json`:
- Around line 1208-1340: Add two cases to the TestJoinReorderPushSelection
suite: (1) a SQL with a leading(...) hint that forces a cartesian edge (so the
expected Plan shows a cartesian join/connect between disjoint join groups) and
(2) a comparable SQL without the leading hint that must NOT produce that
cartesian edge (expected Plan should show normal reorder without cartesian).
Locate the JSON array under "Cases" for TestJoinReorderPushSelection and add the
two entries with unique SQL strings and matching "Plan" arrays; ensure the
hinted case references the same table groups as the non-hinted case and that the
expected plan text explicitly indicates the cartesian/connect behavior so the
test locks the special-case allowing cartesian only when leading(...) forces it.

In `@pkg/planner/core/casetest/rule/testdata/cdc_join_reorder_suite_xut.json`:
- Around line 3-5: Add a targeted regression case to the TestCDCJoinReorder
suite that covers disconnected join subgraphs: create a test input where two
disjoint join groups are present with no leading(...) hints and assert that a
cartesian edge is only introduced in the final stitch (not during intermediate
reorder steps); also add a companion case where a leading(...) hint forces an
earlier cartesian join so the planner emits the cartesian edge earlier. Ensure
the new cases exercise the planner join reorder logic (joinorder/cartesian-edge
handling) and assert the presence/absence and position of cartesian joins
accordingly.

In `@pkg/planner/core/casetest/testdata/json_plan_suite_xut.json`:
- Around line 101-162: Add a new test case object alongside the existing one
that uses "SQL": "explain analyze format = tidb_json ..." with an
outer/semi/anti join (e.g., a LEFT JOIN like "select * from t1 left join t2 on
t1.id = t2.id" or a SEMI/ANTI variant) so the suite exercises the CD-C
outer/semi/anti reorder path; place it as another top-level entry with its own
"JSONPlan" stub/expected plan (mirroring structure of the existing "JSONPlan"
key) and ensure a corresponding expected operator id (e.g., an outer-join
operator id or semi/anti join id) is present so the planner unit test will hit
the changed planner path—repeat the same addition in the other suite block noted
by the reviewer.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 915dd777-f94c-43ec-8892-9dc6ff0427fa

📥 Commits

Reviewing files that changed from the base of the PR and between edcf353 and 3d6e2eb.

📒 Files selected for processing (34)
  • pkg/planner/core/casetest/binaryplan/testdata/binary_plan_suite_out.json
  • pkg/planner/core/casetest/binaryplan/testdata/binary_plan_suite_xut.json
  • pkg/planner/core/casetest/cbotest/testdata/analyze_suite_out.json
  • pkg/planner/core/casetest/cbotest/testdata/analyze_suite_xut.json
  • pkg/planner/core/casetest/hint/testdata/integration_suite_out.json
  • pkg/planner/core/casetest/hint/testdata/integration_suite_xut.json
  • pkg/planner/core/casetest/mpp/testdata/integration_suite_out.json
  • pkg/planner/core/casetest/mpp/testdata/integration_suite_xut.json
  • pkg/planner/core/casetest/rule/testdata/cdc_join_reorder_suite_out.json
  • pkg/planner/core/casetest/rule/testdata/cdc_join_reorder_suite_xut.json
  • pkg/planner/core/casetest/rule/testdata/join_reorder_suite_out.json
  • pkg/planner/core/casetest/rule/testdata/join_reorder_suite_xut.json
  • pkg/planner/core/casetest/rule/testdata/outer2inner_out.json
  • pkg/planner/core/casetest/rule/testdata/outer2inner_xut.json
  • pkg/planner/core/casetest/rule/testdata/outer_to_semi_join_suite_out.json
  • pkg/planner/core/casetest/rule/testdata/outer_to_semi_join_suite_xut.json
  • pkg/planner/core/casetest/testdata/json_plan_suite_out.json
  • pkg/planner/core/casetest/testdata/json_plan_suite_xut.json
  • pkg/planner/core/casetest/tpcds/testdata/tpcds_suite_out.json
  • pkg/planner/core/casetest/tpcds/testdata/tpcds_suite_xut.json
  • pkg/planner/core/casetest/tpch/testdata/tpch_suite_out.json
  • pkg/planner/core/casetest/tpch/testdata/tpch_suite_xut.json
  • pkg/planner/core/joinorder/BUILD.bazel
  • pkg/planner/core/joinorder/conflict_detector.go
  • pkg/planner/core/joinorder/join_order.go
  • pkg/planner/core/rule_join_reorder.go
  • tests/integrationtest/r/planner/core/casetest/index/index.result
  • tests/integrationtest/r/planner/core/casetest/rule/rule_join_reorder.result
  • tests/integrationtest/r/planner/core/issuetest/planner_issue.result
  • tests/integrationtest/r/planner/core/join_reorder2.result
  • tests/integrationtest/r/planner/core/plan_cache.result
  • tests/integrationtest/r/planner/core/rule_join_reorder.result
  • tests/integrationtest/r/select.result
  • tests/integrationtest/r/tpch.result

Comment on lines +1 to +4
[
{
"Name": "TestJSONPlanInExplain",
"Cases": [
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "New xut fixtures under pkg/planner:"
fd '_xut\.json$' pkg/planner

echo
echo "Direct references to the new suffix or exact filenames:"
rg -n --glob '*.go' 'json_plan_suite_xut|binary_plan_suite_xut|_xut\.json|xut'

echo
echo "Planner casetest fixture-loading code paths:"
rg -n --glob '*.go' 'testdata|_out\.json|Load.*json|Read.*json' pkg/planner

Repository: pingcap/tidb

Length of output: 877


🏁 Script executed:

# Find the test runner/loader code in casetest packages
find pkg/planner/core/casetest -name "*test.go" -type f | head -5

# Look for the main test file that loads these fixtures
find pkg/planner/core/casetest -name "casetest_test.go" -o -name "*_test.go" | grep -E "(json_plan|casetest)" | head -10

Repository: pingcap/tidb

Length of output: 809


🏁 Script executed:

# Search for file discovery patterns and glob expressions in casetest
rg -n -A5 -B5 'json_plan_suite|testdata' pkg/planner/core/casetest --glob '*.go' | head -100

Repository: pingcap/tidb

Length of output: 9057


🏁 Script executed:

# Check the json plan test file specifically
find pkg/planner/core/casetest -name "*json*" -type f | grep -E '\.go$'

Repository: pingcap/tidb

Length of output: 38


🏁 Script executed:

# Look for any references to file patterns like *_out.json or fixture loading
rg -n '\*_out\.json|\*\.json|filepath.Glob|ioutil.ReadDir' pkg/planner/core/casetest --glob '*.go' -A2

Repository: pingcap/tidb

Length of output: 38


🏁 Script executed:

# Find the json plan test file and its suite definition
find pkg/planner/core/casetest -name "*json_plan*" -type f

Repository: pingcap/tidb

Length of output: 234


🏁 Script executed:

# Search for GetJSONPlanSuiteData or similar suite getters
rg -n "GetJSONPlanSuiteData|JSONPlanSuite|json_plan" pkg/planner/core/casetest --glob '*.go'

Repository: pingcap/tidb

Length of output: 414


🏁 Script executed:

# Look for suite_test.go files that typically define loaders
find pkg/planner/core/casetest -name "suite_test.go" -o -name "*suite*.go" | grep -v '.json'

Repository: pingcap/tidb

Length of output: 38


🏁 Script executed:

# Examine main_test.go to see how LoadTestSuiteData works and understand the loader pattern
cat -n pkg/planner/core/casetest/main_test.go | head -100

Repository: pingcap/tidb

Length of output: 2772


🏁 Script executed:

# Check plan_test.go to see how it uses the suite data
sed -n '250,270p' pkg/planner/core/casetest/plan_test.go

Repository: pingcap/tidb

Length of output: 853


🏁 Script executed:

# Search for LoadTestSuiteData implementation
rg -n "LoadTestSuiteData" --glob '*.go' -A10 | head -80

Repository: pingcap/tidb

Length of output: 6247


🏁 Script executed:

# Look at the loadTestSuiteData implementation
sed -n '100,250p' pkg/testkit/testdata/testdata.go

Repository: pingcap/tidb

Length of output: 4558


🏁 Script executed:

# Also search for how the suite data discovery works - looking for file pattern matching
rg -n "loadTestSuiteData|_in\.json|_out\.json|_xut\.json" pkg/testkit/testdata --glob '*.go' -B2 -A5 | head -100

Repository: pingcap/tidb

Length of output: 2391


The _xut.json files are not loaded by the test framework and will not execute.

The testdata loader in pkg/testkit/testdata/testdata.go (lines 59–70) only discovers *_in.json and *_out.json file pairs via LoadTestSuiteData(dir, suiteName). It does not load _xut.json files. The new json_plan_suite_xut.json fixture exists on disk but is orphaned and will never run. Either rename the files to match the expected _in.json/_out.json naming convention or extend the loader to support the _xut.json suffix.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/planner/core/casetest/testdata/json_plan_suite_xut.json` around lines 1 -
4, The new json_plan_suite_xut.json fixture is not picked up because
LoadTestSuiteData in pkg/testkit/testdata/testdata.go only looks for
*_in.json/*_out.json pairs; either rename json_plan_suite_xut.json to follow the
existing *_in.json/*_out.json convention so it will be discovered, or update
LoadTestSuiteData (and any helper functions/matchers it uses) to also recognize
the _xut.json suffix by adding that pattern to the file discovery logic and
ensure it is parsed/paired the same way as the existing in/out files.

Comment on lines +454 to +473
var cartesianFactor float64 = j.ctx.GetSessionVars().CartesianJoinOrderThreshold
var disableCartesian = cartesianFactor <= 0
allowNoEQ := !disableCartesian && j.group.allInnerJoin
if nodes, err = greedyConnectJoinNodes(detector, nodes, j.group.vertexHints, cartesianFactor, allowNoEQ); err != nil {
return nil, err
}

usedEdges := collectUsedEdges(nodes)
if !allowNoEQ && detector.HasRemainingEdges(usedEdges) {
// After the first round of greedy connection, there are still some remaining edges,
// for example: R1 INNER JOIN R2 ON R1.c1 < R2.c2
// the above join can only be connected when non-eq edges are allowed,
// and the first round of greedy enumeration is not allowed to use non-eq edges.
// So we got here and we need to the second round of enumeration with `allowNoEQ` as true.
befLen := len(nodes)
// Clamp to 1 to avoid cumCost*0=0 making non-EQ joins appear free.
if cartesianFactor <= 0 {
cartesianFactor = 1
}
if nodes, err = greedyConnectJoinNodes(detector, nodes, j.group.vertexHints, cartesianFactor, true); err != nil {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Keep cartesian joins out of the greedy passes.

allowNoEQ is doing double duty here: once it is true, greedyConnectJoinNodes() can both accept non-equi edges and call TryCreateCartesianCheckResult() on disconnected subgraphs. That lets the main pass stitch disjoint groups together early, and the retry path does the same even when CartesianJoinOrderThreshold <= 0. Please split those controls and reserve cartesian creation for buildJoinByHint() and the final makeBushyTree() fallback.

Based on learnings: in pkg/planner/core/joinorder/, cartesian edges should only be considered in two specific scenarios — after the entire join reorder is complete and there are still disjoint join groups that need to be connected, or when a leading hint explicitly requires a cartesian join.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/planner/core/joinorder/join_order.go` around lines 454 - 473, The greedy
pass currently conflates allowing non-equi joins and permitting cartesian
stitching via allowNoEQ; change the API and call sites so greedyConnectJoinNodes
only controls non-equi edge usage (rename/keep allowNonEq), and add a separate
boolean or mode (e.g., allowCartesian) that is NOT set in the normal greedy
passes. Remove any calls inside greedyConnectJoinNodes that invoke
TryCreateCartesianCheckResult when allowCartesian is false. Ensure cartesian
creation is only invoked from buildJoinByHint (when a leading hint explicitly
requests it) and from the final fallback (makeBushyTree) after the whole reorder
if detector.HasRemainingEdges(usedEdges) still indicates disconnected groups;
keep the cartesianFactor logic and clamping only in those cartesian-specific
paths and do not enable cartesian stitching in the intermediate retry where you
only want to enable non-equi joins (the second greedyConnectJoinNodes call
should pass allowCartesian=false but allowNonEq=true).

Comment on lines +1 to +823
drop table if exists t1, t2;
set tidb_enable_clustered_index=int_only;
create table t1(a int not null, c int not null);
create table t2(a int not null, b int not null, c int not null, primary key(a,b));
insert into t1 values(1,1);
insert into t2 values(1,1,1),(1,2,1);
analyze table t1,t2 all columns;
explain format='plan_tree' select /*+ TIDB_INLJ(t2) */ * from t1 join t2 on t1.a = t2.a and t1.c = t2.c;
id task access object operator info
IndexJoin root inner join, inner:IndexLookUp, outer key:planner__core__casetest__index__index.t1.a, inner key:planner__core__casetest__index__index.t2.a, equal cond:eq(planner__core__casetest__index__index.t1.a, planner__core__casetest__index__index.t2.a), eq(planner__core__casetest__index__index.t1.c, planner__core__casetest__index__index.t2.c)
├─TableReader(Build) root data:TableFullScan
│ └─TableFullScan cop[tikv] table:t1 keep order:false
└─IndexLookUp(Probe) root
├─IndexRangeScan(Build) cop[tikv] table:t2, index:PRIMARY(a, b) range: decided by [eq(planner__core__casetest__index__index.t2.a, planner__core__casetest__index__index.t1.a)], keep order:false
└─TableRowIDScan(Probe) cop[tikv] table:t2 keep order:false
explain format='plan_tree' select /*+ TIDB_INLJ(t2) */ * from t1 join t2 on t1.a = t2.a and t1.c <= t2.b;
id task access object operator info
IndexJoin root inner join, inner:IndexLookUp, outer key:planner__core__casetest__index__index.t1.a, inner key:planner__core__casetest__index__index.t2.a, equal cond:eq(planner__core__casetest__index__index.t1.a, planner__core__casetest__index__index.t2.a), other cond:le(planner__core__casetest__index__index.t1.c, planner__core__casetest__index__index.t2.b)
├─TableReader(Build) root data:TableFullScan
│ └─TableFullScan cop[tikv] table:t1 keep order:false
└─IndexLookUp(Probe) root
├─IndexRangeScan(Build) cop[tikv] table:t2, index:PRIMARY(a, b) range: decided by [eq(planner__core__casetest__index__index.t2.a, planner__core__casetest__index__index.t1.a) le(planner__core__casetest__index__index.t1.c, planner__core__casetest__index__index.t2.b)], keep order:false
└─TableRowIDScan(Probe) cop[tikv] table:t2 keep order:false
explain format='plan_tree' select /*+ TIDB_INLJ(t2) */ * from t1 join t2 on t1.a = t2.a and t2.b = 1;
id task access object operator info
IndexJoin root inner join, inner:IndexLookUp, outer key:planner__core__casetest__index__index.t1.a, inner key:planner__core__casetest__index__index.t2.a, equal cond:eq(planner__core__casetest__index__index.t1.a, planner__core__casetest__index__index.t2.a)
├─TableReader(Build) root data:TableFullScan
│ └─TableFullScan cop[tikv] table:t1 keep order:false
└─IndexLookUp(Probe) root
├─IndexRangeScan(Build) cop[tikv] table:t2, index:PRIMARY(a, b) range: decided by [eq(planner__core__casetest__index__index.t2.a, planner__core__casetest__index__index.t1.a) eq(planner__core__casetest__index__index.t2.b, 1)], keep order:false
└─TableRowIDScan(Probe) cop[tikv] table:t2 keep order:false
set tidb_enable_clustered_index=default;
drop table if exists t2;
create table t2(pk int primary key, a json, b json, c int, d int, e int, index idx(c, (cast(a as signed array))), index idx2((cast(b as signed array)), c), index idx3(c, d), index idx4(d));
explain format='plan_tree' select /*+ use_index_merge(t2, idx2, idx) */ * from t2 where (1 member of (a) and c=1) or (2 member of (b) and c=1); -- 1: OR index merge from multi complicated mv index (memberof);
id task access object operator info
IndexMerge root type: union
├─IndexRangeScan(Build) cop[tikv] table:t2, index:idx(c, cast(`a` as signed array)) range:[1 1,1 1], keep order:false, stats:pseudo
├─IndexRangeScan(Build) cop[tikv] table:t2, index:idx2(cast(`b` as signed array), c) range:[2 1,2 1], keep order:false, stats:pseudo
└─TableRowIDScan(Probe) cop[tikv] table:t2 keep order:false, stats:pseudo
explain format='plan_tree' select /*+ use_index_merge(t2, idx2, idx) */ * from t2 where (1 member of (a) and c=1) or (2 member of (b) and c=1); -- 2: OR index merge from multi complicated mv index (memberof);
id task access object operator info
IndexMerge root type: union
├─IndexRangeScan(Build) cop[tikv] table:t2, index:idx(c, cast(`a` as signed array)) range:[1 1,1 1], keep order:false, stats:pseudo
├─IndexRangeScan(Build) cop[tikv] table:t2, index:idx2(cast(`b` as signed array), c) range:[2 1,2 1], keep order:false, stats:pseudo
└─TableRowIDScan(Probe) cop[tikv] table:t2 keep order:false, stats:pseudo
explain format='plan_tree' select /*+ use_index_merge(t2, idx2, idx) */ * from t2 where (1 member of (a) and c=1 and d=2) or (2 member of (b) and c=3 and d=2); -- 3: OR index merge from multi complicated mv index (memberof),while each DNF item contains redundant condition, which should be remained as table filters;
id task access object operator info
IndexMerge root type: union
├─IndexRangeScan(Build) cop[tikv] table:t2, index:idx(c, cast(`a` as signed array)) range:[1 1,1 1], keep order:false, stats:pseudo
├─IndexRangeScan(Build) cop[tikv] table:t2, index:idx2(cast(`b` as signed array), c) range:[2 3,2 3], keep order:false, stats:pseudo
└─Selection(Probe) cop[tikv] or(and(json_memberof(cast(1, json BINARY), planner__core__casetest__index__index.t2.a), and(eq(planner__core__casetest__index__index.t2.c, 1), eq(planner__core__casetest__index__index.t2.d, 2))), and(json_memberof(cast(2, json BINARY), planner__core__casetest__index__index.t2.b), and(eq(planner__core__casetest__index__index.t2.c, 3), eq(planner__core__casetest__index__index.t2.d, 2))))
└─TableRowIDScan cop[tikv] table:t2 keep order:false, stats:pseudo
explain format='plan_tree' select /*+ use_index_merge(t2, idx2, idx) */ * from t2 where ( json_contains(a, '[1, 2, 3]') and c=1 and d=2) or (2 member of (b) and c=3 and d=2); -- 4: OR index merge from multi complicated mv index (memberof),make full use of DNF item's condition even if the predicate is intersection case (json_contains);
id task access object operator info
IndexLookUp root
├─IndexRangeScan(Build) cop[tikv] table:t2, index:idx3(c, d) range:[1 2,1 2], [3 2,3 2], keep order:false, stats:pseudo
└─Selection(Probe) cop[tikv] or(and(json_contains(planner__core__casetest__index__index.t2.a, cast("[1, 2, 3]", json BINARY)), and(eq(planner__core__casetest__index__index.t2.c, 1), eq(planner__core__casetest__index__index.t2.d, 2))), and(json_memberof(cast(2, json BINARY), planner__core__casetest__index__index.t2.b), and(eq(planner__core__casetest__index__index.t2.c, 3), eq(planner__core__casetest__index__index.t2.d, 2))))
└─TableRowIDScan cop[tikv] table:t2 keep order:false, stats:pseudo
explain format='plan_tree' select /*+ use_index_merge(t2, idx2, idx) */ * from t2 where ( json_overlaps(a, '[1, 2, 3]') and c=1 and d=2) or (2 member of (b) and c=3 and d=2); -- 5: OR index merge from multi complicated mv index (memberof),make full use of DNF item's condition even if the predicate is intersection case (json_contains);
id task access object operator info
Selection root or(and(json_overlaps(planner__core__casetest__index__index.t2.a, cast("[1, 2, 3]", json BINARY)), and(eq(planner__core__casetest__index__index.t2.c, 1), eq(planner__core__casetest__index__index.t2.d, 2))), and(json_memberof(cast(2, json BINARY), planner__core__casetest__index__index.t2.b), and(eq(planner__core__casetest__index__index.t2.c, 3), eq(planner__core__casetest__index__index.t2.d, 2))))
└─IndexMerge root type: union
├─IndexRangeScan(Build) cop[tikv] table:t2, index:idx(c, cast(`a` as signed array)) range:[1 1,1 1], keep order:false, stats:pseudo
├─IndexRangeScan(Build) cop[tikv] table:t2, index:idx(c, cast(`a` as signed array)) range:[1 2,1 2], keep order:false, stats:pseudo
├─IndexRangeScan(Build) cop[tikv] table:t2, index:idx(c, cast(`a` as signed array)) range:[1 3,1 3], keep order:false, stats:pseudo
├─IndexRangeScan(Build) cop[tikv] table:t2, index:idx2(cast(`b` as signed array), c) range:[2 3,2 3], keep order:false, stats:pseudo
└─TableRowIDScan(Probe) cop[tikv] table:t2 keep order:false, stats:pseudo
explain format='plan_tree' select /*+ use_index_merge(t2, idx2, idx, idx4) */ * from t2 where ( json_contains(a, '[1, 2, 3]') and d=2) or (2 member of (b) and c=3 and d=2); -- 6: OR index merge from multi complicated mv index (memberof),make full use of other DNF items even if one of the DNF items fails;
id task access object operator info
IndexMerge root type: union
├─IndexRangeScan(Build) cop[tikv] table:t2, index:idx4(d) range:[2,2], keep order:false, stats:pseudo
├─IndexRangeScan(Build) cop[tikv] table:t2, index:idx2(cast(`b` as signed array), c) range:[2 3,2 3], keep order:false, stats:pseudo
└─Selection(Probe) cop[tikv] or(and(json_contains(planner__core__casetest__index__index.t2.a, cast("[1, 2, 3]", json BINARY)), eq(planner__core__casetest__index__index.t2.d, 2)), and(json_memberof(cast(2, json BINARY), planner__core__casetest__index__index.t2.b), and(eq(planner__core__casetest__index__index.t2.c, 3), eq(planner__core__casetest__index__index.t2.d, 2))))
└─TableRowIDScan cop[tikv] table:t2 keep order:false, stats:pseudo
explain format='plan_tree' select /*+ use_index_merge(t2, idx2, idx) */ * from t2 where (1 member of (a) and 1 member of (b) and c=3) or (3 member of (b) and c=4); -- 7: OR index merge from multi complicated mv index (memberof),each DNF item can be more complicated like a another embedded CNF member-of composition.;
id task access object operator info
IndexMerge root type: union
├─IndexRangeScan(Build) cop[tikv] table:t2, index:idx(c, cast(`a` as signed array)) range:[3 1,3 1], keep order:false, stats:pseudo
├─IndexRangeScan(Build) cop[tikv] table:t2, index:idx2(cast(`b` as signed array), c) range:[3 4,3 4], keep order:false, stats:pseudo
└─Selection(Probe) cop[tikv] or(and(json_memberof(cast(1, json BINARY), planner__core__casetest__index__index.t2.a), and(json_memberof(cast(1, json BINARY), planner__core__casetest__index__index.t2.b), eq(planner__core__casetest__index__index.t2.c, 3))), and(json_memberof(cast(3, json BINARY), planner__core__casetest__index__index.t2.b), eq(planner__core__casetest__index__index.t2.c, 4)))
└─TableRowIDScan cop[tikv] table:t2 keep order:false, stats:pseudo
explain format='plan_tree' select /*+ use_index_merge(t2, idx2, idx) */ * from t2 where (1 member of (a) and 1 member of (b) and c=3) or (3 member of (b) and c=4) or e=1; -- 8: OR index merge from multi complicated mv index (memberof), each DNF item should be strict or lax used as index partial path.;
id task access object operator info
TableReader root data:Selection
└─Selection cop[tikv] or(and(json_memberof(cast(1, json BINARY), planner__core__casetest__index__index.t2.a), and(json_memberof(cast(1, json BINARY), planner__core__casetest__index__index.t2.b), eq(planner__core__casetest__index__index.t2.c, 3))), or(and(json_memberof(cast(3, json BINARY), planner__core__casetest__index__index.t2.b), eq(planner__core__casetest__index__index.t2.c, 4)), eq(planner__core__casetest__index__index.t2.e, 1)))
└─TableFullScan cop[tikv] table:t2 keep order:false, stats:pseudo
explain format='plan_tree' select /*+ use_index_merge(t2, idx2, idx, idx4) */ * from t2 where (1 member of (a) and 1 member of (b) and c=3) or (3 member of (b) and c=4) or d=1; -- 9: OR index merge from multi complicated mv index (memberof), each DNF item should be strict or lax used as index partial path, specify the index in index merge hint;
id task access object operator info
IndexMerge root type: union
├─IndexRangeScan(Build) cop[tikv] table:t2, index:idx(c, cast(`a` as signed array)) range:[3 1,3 1], keep order:false, stats:pseudo
├─IndexRangeScan(Build) cop[tikv] table:t2, index:idx2(cast(`b` as signed array), c) range:[3 4,3 4], keep order:false, stats:pseudo
├─IndexRangeScan(Build) cop[tikv] table:t2, index:idx4(d) range:[1,1], keep order:false, stats:pseudo
└─Selection(Probe) cop[tikv] or(and(json_memberof(cast(1, json BINARY), planner__core__casetest__index__index.t2.a), and(json_memberof(cast(1, json BINARY), planner__core__casetest__index__index.t2.b), eq(planner__core__casetest__index__index.t2.c, 3))), or(and(json_memberof(cast(3, json BINARY), planner__core__casetest__index__index.t2.b), eq(planner__core__casetest__index__index.t2.c, 4)), eq(planner__core__casetest__index__index.t2.d, 1)))
└─TableRowIDScan cop[tikv] table:t2 keep order:false, stats:pseudo
drop table if exists t1, t2;
create table t1(pk int primary key, a json, b json, c int, d int, index idx((cast(a as signed array))), index idx2((cast(b as signed array))));
create table t2(pk int primary key, a json, b json, c int, d int, index idx(c, (cast(a as signed array))), index idx2((cast(b as signed array)), c), index idx3(c, d), index idx4(d));
explain format = 'brief' select /*+ use_index_merge(t1, idx2, idx) */ * from t1 where 1 member of (a) and 2 member of (b); -- 1: AND index merge from multi member mv index predicate, since member of is single partial path, it can be merged with outer index merge.;
id estRows task access object operator info
IndexMerge 0.01 root type: intersection
├─IndexRangeScan(Build) 10.00 cop[tikv] table:t1, index:idx(cast(`a` as signed array)) range:[1,1], keep order:false, stats:pseudo
├─IndexRangeScan(Build) 10.00 cop[tikv] table:t1, index:idx2(cast(`b` as signed array)) range:[2,2], keep order:false, stats:pseudo
└─TableRowIDScan(Probe) 0.01 cop[tikv] table:t1 keep order:false, stats:pseudo
explain format = 'brief' select /*+ use_index_merge(t2, idx2, idx) */ * from t2 where 1 member of (a) and c=1 and 2 member of (b); -- 2: AND index merge from multi complicated mv index;
id estRows task access object operator info
IndexMerge 0.00 root type: intersection
├─IndexRangeScan(Build) 0.10 cop[tikv] table:t2, index:idx(c, cast(`a` as signed array)) range:[1 1,1 1], keep order:false, stats:pseudo
├─IndexRangeScan(Build) 0.10 cop[tikv] table:t2, index:idx2(cast(`b` as signed array), c) range:[2 1,2 1], keep order:false, stats:pseudo
└─TableRowIDScan(Probe) 0.00 cop[tikv] table:t2 keep order:false, stats:pseudo
explain format = 'brief' select /*+ use_index_merge(t2, idx2, idx, idx4) */ * from t2 where 1 member of (a) and c=1 and 2 member of (b) and d=3; -- 3: AND index merge from multi complicated mv indexes and normal indexes;
id estRows task access object operator info
IndexMerge 0.00 root type: intersection
├─IndexRangeScan(Build) 10.00 cop[tikv] table:t2, index:idx4(d) range:[3,3], keep order:false, stats:pseudo
├─IndexRangeScan(Build) 0.10 cop[tikv] table:t2, index:idx(c, cast(`a` as signed array)) range:[1 1,1 1], keep order:false, stats:pseudo
├─IndexRangeScan(Build) 0.10 cop[tikv] table:t2, index:idx2(cast(`b` as signed array), c) range:[2 1,2 1], keep order:false, stats:pseudo
└─TableRowIDScan(Probe) 0.00 cop[tikv] table:t2 keep order:false, stats:pseudo
explain format = 'brief' select /*+ use_index_merge(t2, idx2, idx, idx3) */ * from t2 where json_contains(a, '[1, 2, 3]') and c=1 and 2 member of (b) and d=3; -- 4: AND index merge from multi complicated mv indexes (json_contains (intersection))and normal indexes;
id estRows task access object operator info
IndexMerge 0.00 root type: intersection
├─IndexRangeScan(Build) 1.25 cop[tikv] table:t2, index:idx3(c, d) range:[1 3,1 3], keep order:false, stats:pseudo
├─IndexRangeScan(Build) 0.10 cop[tikv] table:t2, index:idx(c, cast(`a` as signed array)) range:[1 1,1 1], keep order:false, stats:pseudo
├─IndexRangeScan(Build) 0.10 cop[tikv] table:t2, index:idx(c, cast(`a` as signed array)) range:[1 2,1 2], keep order:false, stats:pseudo
├─IndexRangeScan(Build) 0.10 cop[tikv] table:t2, index:idx(c, cast(`a` as signed array)) range:[1 3,1 3], keep order:false, stats:pseudo
├─IndexRangeScan(Build) 0.10 cop[tikv] table:t2, index:idx2(cast(`b` as signed array), c) range:[2 1,2 1], keep order:false, stats:pseudo
└─TableRowIDScan(Probe) 0.00 cop[tikv] table:t2 keep order:false, stats:pseudo
explain format = 'brief' select /*+ use_index_merge(t2, idx2, idx, idx3) */ * from t2 where json_overlaps(a, '[1, 2, 3]') and c=1 and 2 member of (b) and d=3; -- 5: AND index merge from multi complicated mv indexes (json_overlap (intersection))and normal indexes;
id estRows task access object operator info
Selection 0.24 root json_overlaps(planner__core__casetest__index__index.t2.a, cast("[1, 2, 3]", json BINARY))
└─IndexMerge 0.00 root type: intersection
├─IndexRangeScan(Build) 1.25 cop[tikv] table:t2, index:idx3(c, d) range:[1 3,1 3], keep order:false, stats:pseudo
├─IndexRangeScan(Build) 0.10 cop[tikv] table:t2, index:idx2(cast(`b` as signed array), c) range:[2 1,2 1], keep order:false, stats:pseudo
└─TableRowIDScan(Probe) 0.00 cop[tikv] table:t2 keep order:false, stats:pseudo
explain format = 'brief' select /*+ use_index_merge(t2, idx2, idx) */ * from t2 where 1 member of (a) and c=1 and c=2; -- 6: AND index merge from multi complicated mv indexes (empty range);
id estRows task access object operator info
TableDual 0.00 root rows:0
drop table if exists t;
create table t(a int, b int, c int, unique index(a), unique index(b), primary key(c));
explain format='plan_tree' select /*+ USE_INDEX_MERGE(t, a, b) */ * from t where a = 1 or b = 2;
id task access object operator info
IndexMerge root type: union
├─IndexRangeScan(Build) cop[tikv] table:t, index:a(a) range:[1,1], keep order:false, stats:pseudo
├─IndexRangeScan(Build) cop[tikv] table:t, index:b(b) range:[2,2], keep order:false, stats:pseudo
└─TableRowIDScan(Probe) cop[tikv] table:t keep order:false, stats:pseudo
explain format='plan_tree' select /*+ USE_INDEX_MERGE(t, A, B) */ * from t where a = 1 or b = 2;
id task access object operator info
IndexMerge root type: union
├─IndexRangeScan(Build) cop[tikv] table:t, index:a(a) range:[1,1], keep order:false, stats:pseudo
├─IndexRangeScan(Build) cop[tikv] table:t, index:b(b) range:[2,2], keep order:false, stats:pseudo
└─TableRowIDScan(Probe) cop[tikv] table:t keep order:false, stats:pseudo
explain format='plan_tree' select /*+ USE_INDEX_MERGE(t, primary) */ * from t where 1 or t.c;
id task access object operator info
TableReader root data:TableFullScan
└─TableFullScan cop[tikv] table:t keep order:false, stats:pseudo
explain format='plan_tree' select /*+ USE_INDEX_MERGE(t, a, b, c) */ * from t where 1 or t.a = 1 or t.b = 2;
id task access object operator info
TableReader root data:TableFullScan
└─TableFullScan cop[tikv] table:t keep order:false, stats:pseudo
drop table if exists t1, t2, t3, t4;
create table t1(a int, b int, primary key (a), key idx_t1_b (b));
create table t2(a int, b int, primary key (a), key idx_t1_b (b));
create table t3(a int, b int, c int);
create table t4(a int, b int, c int, primary key (a, b) clustered);
explain format='plan_tree' select /*+ TIDB_INLJ(t2)*/ * from t1, t2 where t1.a = t2.a and t1.b = t2.b;
id task access object operator info
IndexJoin root inner join, inner:TableReader, outer key:planner__core__casetest__index__index.t1.a, inner key:planner__core__casetest__index__index.t2.a, equal cond:eq(planner__core__casetest__index__index.t1.a, planner__core__casetest__index__index.t2.a), eq(planner__core__casetest__index__index.t1.b, planner__core__casetest__index__index.t2.b)
├─IndexReader(Build) root index:IndexFullScan
│ └─IndexFullScan cop[tikv] table:t1, index:idx_t1_b(b) keep order:false, stats:pseudo
└─TableReader(Probe) root data:Selection
└─Selection cop[tikv] not(isnull(planner__core__casetest__index__index.t2.b))
└─TableRangeScan cop[tikv] table:t2 range: decided by [planner__core__casetest__index__index.t1.a], keep order:false, stats:pseudo
explain format='plan_tree' select /*+ TIDB_INLJ(t2)*/ * from t1, t2 where t1.a = t2.a and t1.b = t2.a and t1.b = t2.b;
id task access object operator info
IndexJoin root inner join, inner:TableReader, outer key:planner__core__casetest__index__index.t1.a, planner__core__casetest__index__index.t1.b, inner key:planner__core__casetest__index__index.t2.a, planner__core__casetest__index__index.t2.a, equal cond:eq(planner__core__casetest__index__index.t1.a, planner__core__casetest__index__index.t2.a), eq(planner__core__casetest__index__index.t1.b, planner__core__casetest__index__index.t2.a), eq(planner__core__casetest__index__index.t1.b, planner__core__casetest__index__index.t2.b)
├─IndexReader(Build) root index:IndexFullScan
│ └─IndexFullScan cop[tikv] table:t1, index:idx_t1_b(b) keep order:false, stats:pseudo
└─TableReader(Probe) root data:Selection
└─Selection cop[tikv] not(isnull(planner__core__casetest__index__index.t2.b))
└─TableRangeScan cop[tikv] table:t2 range: decided by [planner__core__casetest__index__index.t1.a planner__core__casetest__index__index.t1.b], keep order:false, stats:pseudo
explain format='plan_tree' select /*+ INL_JOIN(t4) */ * from t3 join t4 on t3.a = t4.a where t4.b = 1;
id task access object operator info
Projection root planner__core__casetest__index__index.t3.a, planner__core__casetest__index__index.t3.b, planner__core__casetest__index__index.t3.c, planner__core__casetest__index__index.t4.a, planner__core__casetest__index__index.t4.b, planner__core__casetest__index__index.t4.c
└─IndexJoin root inner join, inner:TableReader, outer key:planner__core__casetest__index__index.t3.a, inner key:planner__core__casetest__index__index.t4.a, equal cond:eq(planner__core__casetest__index__index.t3.a, planner__core__casetest__index__index.t4.a)
├─TableReader(Build) root data:Selection
│ └─Selection cop[tikv] not(isnull(planner__core__casetest__index__index.t3.a))
│ └─TableFullScan cop[tikv] table:t3 keep order:false, stats:pseudo
└─TableReader(Probe) root data:Selection
└─Selection cop[tikv] eq(planner__core__casetest__index__index.t4.b, 1)
└─TableRangeScan cop[tikv] table:t4 range: decided by [eq(planner__core__casetest__index__index.t4.a, planner__core__casetest__index__index.t3.a) eq(planner__core__casetest__index__index.t4.b, 1)], keep order:false, stats:pseudo
explain format='plan_tree' select /*+ INL_JOIN(t4) */ * from t3 join t4 on t3.b = t4.b where t4.a = 1;
id task access object operator info
Projection root planner__core__casetest__index__index.t3.a, planner__core__casetest__index__index.t3.b, planner__core__casetest__index__index.t3.c, planner__core__casetest__index__index.t4.a, planner__core__casetest__index__index.t4.b, planner__core__casetest__index__index.t4.c
└─IndexJoin root inner join, inner:TableReader, outer key:planner__core__casetest__index__index.t3.b, inner key:planner__core__casetest__index__index.t4.b, equal cond:eq(planner__core__casetest__index__index.t3.b, planner__core__casetest__index__index.t4.b)
├─TableReader(Build) root data:Selection
│ └─Selection cop[tikv] not(isnull(planner__core__casetest__index__index.t3.b))
│ └─TableFullScan cop[tikv] table:t3 keep order:false, stats:pseudo
└─TableReader(Probe) root data:Selection
└─Selection cop[tikv] eq(planner__core__casetest__index__index.t4.a, 1)
└─TableRangeScan cop[tikv] table:t4 range: decided by [eq(planner__core__casetest__index__index.t4.b, planner__core__casetest__index__index.t3.b) eq(planner__core__casetest__index__index.t4.a, 1)], keep order:false, stats:pseudo
drop table if exists t1, t2;
create table t1(a int not null, b int not null, c int not null);
create table t2(a int not null, b int not null, c int not null, index idx1(a,b), index idx2(c));
insert into t1 values(1,1,1),(1,1,1),(1,1,1);
insert into t2 values(1,1,1),(1,1,2),(1,1,3);
analyze table t1, t2 all columns;
explain format='plan_tree' select /*+ inl_join(t2) */ * from t1, t2 where t1.a = t2.a and t1.b = t2.b and t1.c = t2.c;
id task access object operator info
IndexJoin root inner join, inner:IndexLookUp, outer key:planner__core__casetest__index__index.t1.a, planner__core__casetest__index__index.t1.b, inner key:planner__core__casetest__index__index.t2.a, planner__core__casetest__index__index.t2.b, equal cond:eq(planner__core__casetest__index__index.t1.a, planner__core__casetest__index__index.t2.a), eq(planner__core__casetest__index__index.t1.b, planner__core__casetest__index__index.t2.b), eq(planner__core__casetest__index__index.t1.c, planner__core__casetest__index__index.t2.c)
├─TableReader(Build) root data:TableFullScan
│ └─TableFullScan cop[tikv] table:t1 keep order:false
└─IndexLookUp(Probe) root
├─IndexRangeScan(Build) cop[tikv] table:t2, index:idx1(a, b) range: decided by [eq(planner__core__casetest__index__index.t2.a, planner__core__casetest__index__index.t1.a) eq(planner__core__casetest__index__index.t2.b, planner__core__casetest__index__index.t1.b)], keep order:false
└─TableRowIDScan(Probe) cop[tikv] table:t2 keep order:false
drop table if exists t;
create table t (a int, b int, unique key(a), unique key(b));
insert into t value (1, 5), (2, 4), (3, 3), (4, 2), (5, 1);
insert into t value (6, 0), (7, -1), (8, -2), (9, -3), (10, -4);
analyze table t;
explain format='plan_tree' select /*+ use_index_merge(t) */ * from t where a =1 or (b=1 and b+2>1);
id task access object operator info
IndexMerge root type: union
├─IndexRangeScan(Build) cop[tikv] table:t, index:a(a) range:[1,1], keep order:false
├─IndexRangeScan(Build) cop[tikv] table:t, index:b(b) range:[1,1], keep order:false
└─TableRowIDScan(Probe) cop[tikv] table:t keep order:false
show warnings;
Level Code Message
explain format='plan_tree' select /*+ use_index_merge(t) */ * from t where a =1 or (b=1 and length(b)=1);
id task access object operator info
IndexMerge root type: union
├─IndexRangeScan(Build) cop[tikv] table:t, index:a(a) range:[1,1], keep order:false
├─IndexRangeScan(Build) cop[tikv] table:t, index:b(b) range:[1,1], keep order:false
└─TableRowIDScan(Probe) cop[tikv] table:t keep order:false
show warnings;
Level Code Message
explain format='plan_tree' select /*+ use_index_merge(t) */ * from t where (a=1 and length(a)=1) or (b=1 and length(b)=1);
id task access object operator info
IndexMerge root type: union
├─IndexRangeScan(Build) cop[tikv] table:t, index:a(a) range:[1,1], keep order:false
├─IndexRangeScan(Build) cop[tikv] table:t, index:b(b) range:[1,1], keep order:false
└─TableRowIDScan(Probe) cop[tikv] table:t keep order:false
show warnings;
Level Code Message
explain format='plan_tree' select /*+ use_index_merge(t) */ * from t where (a=1 and length(b)=1) or (b=1 and length(a)=1);
id task access object operator info
IndexMerge root type: union
├─IndexRangeScan(Build) cop[tikv] table:t, index:a(a) range:[1,1], keep order:false
├─IndexRangeScan(Build) cop[tikv] table:t, index:b(b) range:[1,1], keep order:false
└─Selection(Probe) cop[tikv] or(and(eq(planner__core__casetest__index__index.t.a, 1), eq(length(cast(planner__core__casetest__index__index.t.b, var_string(20))), 1)), and(eq(planner__core__casetest__index__index.t.b, 1), eq(length(cast(planner__core__casetest__index__index.t.a, var_string(20))), 1)))
└─TableRowIDScan cop[tikv] table:t keep order:false
show warnings;
Level Code Message
set tidb_enable_clustered_index=on;
drop table if exists t;
create table t (a int, b varchar(20), c decimal(40,10), d int, primary key(a,b), key(c));
insert into t values (1,"111",1.1,11), (2,"222",2.2,12), (3,"333",3.3,13);
analyze table t;
explain format='plan_tree' select /*+ inl_join(t1, t2) */ * from t t1 join t t2 on t1.a = t2.a;
id task access object operator info
IndexJoin root inner join, inner:TableReader, outer key:planner__core__casetest__index__index.t.a, inner key:planner__core__casetest__index__index.t.a, equal cond:eq(planner__core__casetest__index__index.t.a, planner__core__casetest__index__index.t.a)
├─TableReader(Build) root data:TableFullScan
│ └─TableFullScan cop[tikv] table:t1 keep order:false
└─TableReader(Probe) root data:TableRangeScan
└─TableRangeScan cop[tikv] table:t2 range: decided by [eq(planner__core__casetest__index__index.t.a, planner__core__casetest__index__index.t.a)], keep order:false
select /*+ inl_join(t1, t2) */ * from t t1 join t t2 on t1.a = t2.a;
a b c d a b c d
1 111 1.1000000000 11 1 111 1.1000000000 11
2 222 2.2000000000 12 2 222 2.2000000000 12
3 333 3.3000000000 13 3 333 3.3000000000 13
explain format='plan_tree' select /*+ inl_merge_join(t1, t2) */ * from t t1 join t t2 on t1.a = t2.a;
id task access object operator info
MergeJoin root inner join, left key:planner__core__casetest__index__index.t.a, right key:planner__core__casetest__index__index.t.a
├─TableReader(Build) root data:TableFullScan
│ └─TableFullScan cop[tikv] table:t2 keep order:true
└─TableReader(Probe) root data:TableFullScan
└─TableFullScan cop[tikv] table:t1 keep order:true
select /*+ inl_merge_join(t1, t2) */ * from t t1 join t t2 on t1.a = t2.a;
a b c d a b c d
1 111 1.1000000000 11 1 111 1.1000000000 11
2 222 2.2000000000 12 2 222 2.2000000000 12
3 333 3.3000000000 13 3 333 3.3000000000 13
explain format='plan_tree' select /*+ inl_hash_join(t1, t2) */ * from t t1 join t t2 on t1.a = t2.a;
id task access object operator info
IndexHashJoin root inner join, inner:TableReader, outer key:planner__core__casetest__index__index.t.a, inner key:planner__core__casetest__index__index.t.a, equal cond:eq(planner__core__casetest__index__index.t.a, planner__core__casetest__index__index.t.a)
├─TableReader(Build) root data:TableFullScan
│ └─TableFullScan cop[tikv] table:t1 keep order:false
└─TableReader(Probe) root data:TableRangeScan
└─TableRangeScan cop[tikv] table:t2 range: decided by [eq(planner__core__casetest__index__index.t.a, planner__core__casetest__index__index.t.a)], keep order:false
select /*+ inl_hash_join(t1, t2) */ * from t t1 join t t2 on t1.a = t2.a;
a b c d a b c d
1 111 1.1000000000 11 1 111 1.1000000000 11
2 222 2.2000000000 12 2 222 2.2000000000 12
3 333 3.3000000000 13 3 333 3.3000000000 13
explain format='plan_tree' select /*+ inl_join(t1, t2) */ * from t t1 join t t2 on t1.a = t2.a and t1.b = t2.b;
id task access object operator info
IndexJoin root inner join, inner:TableReader, outer key:planner__core__casetest__index__index.t.a, planner__core__casetest__index__index.t.b, inner key:planner__core__casetest__index__index.t.a, planner__core__casetest__index__index.t.b, equal cond:eq(planner__core__casetest__index__index.t.a, planner__core__casetest__index__index.t.a), eq(planner__core__casetest__index__index.t.b, planner__core__casetest__index__index.t.b)
├─TableReader(Build) root data:TableFullScan
│ └─TableFullScan cop[tikv] table:t1 keep order:false
└─TableReader(Probe) root data:TableRangeScan
└─TableRangeScan cop[tikv] table:t2 range: decided by [eq(planner__core__casetest__index__index.t.a, planner__core__casetest__index__index.t.a) eq(planner__core__casetest__index__index.t.b, planner__core__casetest__index__index.t.b)], keep order:false
select /*+ inl_join(t1, t2) */ * from t t1 join t t2 on t1.a = t2.a and t1.b = t2.b;
a b c d a b c d
1 111 1.1000000000 11 1 111 1.1000000000 11
2 222 2.2000000000 12 2 222 2.2000000000 12
3 333 3.3000000000 13 3 333 3.3000000000 13
explain format='plan_tree' select /*+ inl_join(t1, t2) */ * from t t1 join t t2 on t1.c = t2.c;
id task access object operator info
IndexJoin root inner join, inner:IndexLookUp, outer key:planner__core__casetest__index__index.t.c, inner key:planner__core__casetest__index__index.t.c, equal cond:eq(planner__core__casetest__index__index.t.c, planner__core__casetest__index__index.t.c)
├─TableReader(Build) root data:Selection
│ └─Selection cop[tikv] not(isnull(planner__core__casetest__index__index.t.c))
│ └─TableFullScan cop[tikv] table:t1 keep order:false
└─IndexLookUp(Probe) root
├─Selection(Build) cop[tikv] not(isnull(planner__core__casetest__index__index.t.c))
│ └─IndexRangeScan cop[tikv] table:t2, index:c(c) range: decided by [eq(planner__core__casetest__index__index.t.c, planner__core__casetest__index__index.t.c)], keep order:false
└─TableRowIDScan(Probe) cop[tikv] table:t2 keep order:false
select /*+ inl_join(t1, t2) */ * from t t1 join t t2 on t1.c = t2.c;
a b c d a b c d
1 111 1.1000000000 11 1 111 1.1000000000 11
2 222 2.2000000000 12 2 222 2.2000000000 12
3 333 3.3000000000 13 3 333 3.3000000000 13
explain format='plan_tree' select /*+ inl_merge_join(t1,t2) */ t2.a, t2.c, t2.d from t t1 left join t t2 on t1.a = t2.c;
id task access object operator info
HashJoin root left outer join, left side:Projection, equal:[eq(Column, planner__core__casetest__index__index.t.c)]
├─Projection(Build) root cast(planner__core__casetest__index__index.t.a, decimal(10,0) BINARY)->Column
│ └─IndexReader root index:IndexFullScan
│ └─IndexFullScan cop[tikv] table:t1, index:c(c) keep order:false
└─TableReader(Probe) root data:TableFullScan
└─TableFullScan cop[tikv] table:t2 keep order:false
select /*+ inl_merge_join(t1,t2) */ t2.a, t2.c, t2.d from t t1 left join t t2 on t1.a = t2.c;
a c d
NULL NULL NULL
NULL NULL NULL
NULL NULL NULL
set tidb_enable_clustered_index=default;
drop table if exists t1, t2;
create table t1(c1 int, c2 int, c3 int, primary key(c1), key(c2));
insert into t1 values(1, 1, 1);
insert into t1 values(2, 2, 2);
create table t2(c1 int, c2 int, c3 int);
insert into t2 values(1, 1, 1);
insert into t2 values(2, 2, 2);
drop table if exists tt1, tt2;
create table tt1 (c_int int, c_str varchar(40), c_datetime datetime, c_decimal decimal(12, 6), primary key(c_int), key(c_int), key(c_str), unique key(c_decimal), key(c_datetime));
create table tt2 like tt1 ;
insert into tt1 (c_int, c_str, c_datetime, c_decimal) values (6, 'sharp payne', '2020-06-07 10:40:39', 6.117000) ,
(7, 'objective kare', '2020-02-05 18:47:26', 1.053000) ,
(8, 'thirsty pasteur', '2020-01-02 13:06:56', 2.506000) ,
(9, 'blissful wilbur', '2020-06-04 11:34:04', 9.144000) ,
(10, 'reverent mclean', '2020-02-12 07:36:26', 7.751000) ;
insert into tt2 (c_int, c_str, c_datetime, c_decimal) values (6, 'beautiful joliot', '2020-01-16 01:44:37', 5.627000) ,
(7, 'hopeful blackburn', '2020-05-23 21:44:20', 7.890000) ,
(8, 'ecstatic davinci', '2020-02-01 12:27:17', 5.648000) ,
(9, 'hopeful lewin', '2020-05-05 05:58:25', 7.288000) ,
(10, 'sharp jennings', '2020-01-28 04:35:03', 9.758000) ;
explain format='plan_tree' select * from t2 where c1 < all(select /*+ use_index_merge(t1) */ c1 from t1 where (c1 = 10 and c1 = t2.c3 or c2 = 1 and c2 = t2.c3) and substring(c3, 10)) order by c1;
id task access object operator info
Sort root planner__core__casetest__index__index.t2.c1
└─Projection root planner__core__casetest__index__index.t2.c1, planner__core__casetest__index__index.t2.c2, planner__core__casetest__index__index.t2.c3
└─Apply root CARTESIAN inner join, other cond:or(and(lt(planner__core__casetest__index__index.t2.c1, Column), if(ne(Column, 0), NULL, 1)), or(eq(Column, 0), if(isnull(planner__core__casetest__index__index.t2.c1), NULL, 0)))
├─TableReader(Build) root data:TableFullScan
│ └─TableFullScan cop[tikv] table:t2 keep order:false, stats:pseudo
└─StreamAgg(Probe) root funcs:min(planner__core__casetest__index__index.t1.c1)->Column, funcs:sum(0)->Column, funcs:count(1)->Column
└─IndexMerge root type: union
├─Selection(Build) cop[tikv] eq(planner__core__casetest__index__index.t2.c3, 10)
│ └─TableRangeScan cop[tikv] table:t1 range:[10,10], keep order:false, stats:pseudo
├─Selection(Build) cop[tikv] eq(planner__core__casetest__index__index.t2.c3, 1)
│ └─IndexRangeScan cop[tikv] table:t1, index:c2(c2) range:[1,1], keep order:false, stats:pseudo
└─Selection(Probe) cop[tikv] or(and(eq(planner__core__casetest__index__index.t1.c1, 10), eq(planner__core__casetest__index__index.t2.c3, 10)), and(eq(planner__core__casetest__index__index.t1.c2, 1), eq(planner__core__casetest__index__index.t2.c3, 1))), substring(cast(planner__core__casetest__index__index.t1.c3, var_string(20)), 10)
└─TableRowIDScan cop[tikv] table:t1 keep order:false, stats:pseudo
select * from t2 where c1 < all(select /*+ use_index_merge(t1) */ c1 from t1 where (c1 = 10 and c1 = t2.c3 or c2 = 1 and c2 = t2.c3) and substring(c3, 10)) order by c1;
c1 c2 c3
1 1 1
2 2 2
explain format='plan_tree' select * from t2 where c1 < all(select /*+ use_index_merge(t1) */ c1 from t1 where (c1 = 10 and c1 = t2.c3 or c2 = 1 and c2 = t2.c3) and reverse(c3)) order by c1;
id task access object operator info
Sort root planner__core__casetest__index__index.t2.c1
└─Projection root planner__core__casetest__index__index.t2.c1, planner__core__casetest__index__index.t2.c2, planner__core__casetest__index__index.t2.c3
└─Apply root CARTESIAN inner join, other cond:or(and(lt(planner__core__casetest__index__index.t2.c1, Column), if(ne(Column, 0), NULL, 1)), or(eq(Column, 0), if(isnull(planner__core__casetest__index__index.t2.c1), NULL, 0)))
├─TableReader(Build) root data:TableFullScan
│ └─TableFullScan cop[tikv] table:t2 keep order:false, stats:pseudo
└─StreamAgg(Probe) root funcs:min(planner__core__casetest__index__index.t1.c1)->Column, funcs:sum(0)->Column, funcs:count(1)->Column
└─IndexMerge root type: union
├─Selection(Build) cop[tikv] eq(planner__core__casetest__index__index.t2.c3, 10)
│ └─TableRangeScan cop[tikv] table:t1 range:[10,10], keep order:false, stats:pseudo
├─Selection(Build) cop[tikv] eq(planner__core__casetest__index__index.t2.c3, 1)
│ └─IndexRangeScan cop[tikv] table:t1, index:c2(c2) range:[1,1], keep order:false, stats:pseudo
└─Selection(Probe) cop[tikv] or(and(eq(planner__core__casetest__index__index.t1.c1, 10), eq(planner__core__casetest__index__index.t2.c3, 10)), and(eq(planner__core__casetest__index__index.t1.c2, 1), eq(planner__core__casetest__index__index.t2.c3, 1))), reverse(cast(planner__core__casetest__index__index.t1.c3, var_string(20)))
└─TableRowIDScan cop[tikv] table:t1 keep order:false, stats:pseudo
select * from t2 where c1 < all(select /*+ use_index_merge(t1) */ c1 from t1 where (c1 = 10 and c1 = t2.c3 or c2 = 1 and c2 = t2.c3) and reverse(c3)) order by c1;
c1 c2 c3
2 2 2
explain format='plan_tree' select * from t2 where c1 < all(select /*+ use_index_merge(t1) */ c1 from t1 where (c1 >= 10 and c1 = t2.c3 or c2 = 1 and c2 = t2.c3) and substring(c3, 10)) order by c1;
id task access object operator info
Sort root planner__core__casetest__index__index.t2.c1
└─Projection root planner__core__casetest__index__index.t2.c1, planner__core__casetest__index__index.t2.c2, planner__core__casetest__index__index.t2.c3
└─Apply root CARTESIAN inner join, other cond:or(and(lt(planner__core__casetest__index__index.t2.c1, Column), if(ne(Column, 0), NULL, 1)), or(eq(Column, 0), if(isnull(planner__core__casetest__index__index.t2.c1), NULL, 0)))
├─TableReader(Build) root data:TableFullScan
│ └─TableFullScan cop[tikv] table:t2 keep order:false, stats:pseudo
└─StreamAgg(Probe) root funcs:min(planner__core__casetest__index__index.t1.c1)->Column, funcs:sum(0)->Column, funcs:count(1)->Column
└─IndexMerge root type: union
├─Selection(Build) cop[tikv] eq(planner__core__casetest__index__index.t1.c1, planner__core__casetest__index__index.t2.c3)
│ └─TableRangeScan cop[tikv] table:t1 range:[10,+inf], keep order:false, stats:pseudo
├─Selection(Build) cop[tikv] eq(planner__core__casetest__index__index.t2.c3, 1)
│ └─IndexRangeScan cop[tikv] table:t1, index:c2(c2) range:[1,1], keep order:false, stats:pseudo
└─Selection(Probe) cop[tikv] or(and(ge(planner__core__casetest__index__index.t1.c1, 10), eq(planner__core__casetest__index__index.t1.c1, planner__core__casetest__index__index.t2.c3)), and(eq(planner__core__casetest__index__index.t1.c2, 1), eq(planner__core__casetest__index__index.t2.c3, 1))), substring(cast(planner__core__casetest__index__index.t1.c3, var_string(20)), 10)
└─TableRowIDScan cop[tikv] table:t1 keep order:false, stats:pseudo
select * from t2 where c1 < all(select /*+ use_index_merge(t1) */ c1 from t1 where (c1 >= 10 and c1 = t2.c3 or c2 = 1 and c2 = t2.c3) and substring(c3, 10)) order by c1;
c1 c2 c3
1 1 1
2 2 2
explain format='plan_tree' select c_int from tt1 where c_decimal < all (select /*+ use_index_merge(tt2) */ c_decimal from tt2 where tt1.c_int = tt2.c_int and tt1.c_datetime > tt2.c_datetime and tt2.c_decimal = 9.060 or tt2.c_str <= 'interesting shtern' and tt1.c_int = tt2.c_int) order by 1;
id task access object operator info
Projection root planner__core__casetest__index__index.tt1.c_int
└─Apply root CARTESIAN inner join, other cond:or(and(lt(planner__core__casetest__index__index.tt1.c_decimal, Column), if(ne(Column, 0), NULL, 1)), or(eq(Column, 0), if(isnull(planner__core__casetest__index__index.tt1.c_decimal), NULL, 0)))
├─TableReader(Build) root data:TableFullScan
│ └─TableFullScan cop[tikv] table:tt1 keep order:true, stats:pseudo
└─StreamAgg(Probe) root funcs:min(Column)->Column, funcs:sum(Column)->Column, funcs:count(1)->Column
└─Projection root planner__core__casetest__index__index.tt2.c_decimal->Column, cast(isnull(planner__core__casetest__index__index.tt2.c_decimal), decimal(20,0) BINARY)->Column
└─IndexMerge root type: union
├─Selection(Build) cop[tikv] eq(planner__core__casetest__index__index.tt1.c_int, planner__core__casetest__index__index.tt2.c_int)
│ └─IndexRangeScan cop[tikv] table:tt2, index:c_decimal(c_decimal) range:[9.060000,9.060000], keep order:false, stats:pseudo
├─Selection(Build) cop[tikv] eq(planner__core__casetest__index__index.tt1.c_int, planner__core__casetest__index__index.tt2.c_int)
│ └─IndexRangeScan cop[tikv] table:tt2, index:c_str(c_str) range:[-inf,"interesting shtern"], keep order:false, stats:pseudo
└─Selection(Probe) cop[tikv] or(and(eq(planner__core__casetest__index__index.tt1.c_int, planner__core__casetest__index__index.tt2.c_int), and(gt(planner__core__casetest__index__index.tt1.c_datetime, planner__core__casetest__index__index.tt2.c_datetime), eq(planner__core__casetest__index__index.tt2.c_decimal, 9.060))), and(le(planner__core__casetest__index__index.tt2.c_str, "interesting shtern"), eq(planner__core__casetest__index__index.tt1.c_int, planner__core__casetest__index__index.tt2.c_int)))
└─TableRowIDScan cop[tikv] table:tt2 keep order:false, stats:pseudo
select c_int from tt1 where c_decimal < all (select /*+ use_index_merge(tt2) */ c_decimal from tt2 where tt1.c_int = tt2.c_int and tt1.c_datetime > tt2.c_datetime and tt2.c_decimal = 9.060 or tt2.c_str <= 'interesting shtern' and tt1.c_int = tt2.c_int) order by 1;
c_int
7
8
10
explain format='plan_tree' select c_int from tt1 where c_decimal > all (select /*+ use_index_merge(tt2) */ c_decimal from tt2 where tt2.c_int = 7 and tt2.c_int < tt1.c_decimal or tt2.c_str >= 'zzzzzzzzzzzzzzzzzzz' and tt1.c_int = tt2.c_int) order by 1;
id task access object operator info
Projection root planner__core__casetest__index__index.tt1.c_int
└─Apply root CARTESIAN inner join, other cond:or(and(gt(planner__core__casetest__index__index.tt1.c_decimal, Column), if(ne(Column, 0), NULL, 1)), or(eq(Column, 0), if(isnull(planner__core__casetest__index__index.tt1.c_decimal), NULL, 0)))
├─TableReader(Build) root data:TableFullScan
│ └─TableFullScan cop[tikv] table:tt1 keep order:true, stats:pseudo
└─StreamAgg(Probe) root funcs:max(Column)->Column, funcs:sum(Column)->Column, funcs:count(1)->Column
└─Projection root planner__core__casetest__index__index.tt2.c_decimal->Column, cast(isnull(planner__core__casetest__index__index.tt2.c_decimal), decimal(20,0) BINARY)->Column
└─IndexMerge root type: union
├─Selection(Build) cop[tikv] lt(7, planner__core__casetest__index__index.tt1.c_decimal)
│ └─TableRangeScan cop[tikv] table:tt2 range:[7,7], keep order:false, stats:pseudo
├─Selection(Build) cop[tikv] eq(planner__core__casetest__index__index.tt1.c_int, planner__core__casetest__index__index.tt2.c_int)
│ └─IndexRangeScan cop[tikv] table:tt2, index:c_str(c_str) range:["zzzzzzzzzzzzzzzzzzz",+inf], keep order:false, stats:pseudo
└─Selection(Probe) cop[tikv] or(and(eq(planner__core__casetest__index__index.tt2.c_int, 7), lt(7, planner__core__casetest__index__index.tt1.c_decimal)), and(ge(planner__core__casetest__index__index.tt2.c_str, "zzzzzzzzzzzzzzzzzzz"), eq(planner__core__casetest__index__index.tt1.c_int, planner__core__casetest__index__index.tt2.c_int)))
└─TableRowIDScan cop[tikv] table:tt2 keep order:false, stats:pseudo
select c_int from tt1 where c_decimal > all (select /*+ use_index_merge(tt2) */ c_decimal from tt2 where tt2.c_int = 7 and tt2.c_int < tt1.c_decimal or tt2.c_str >= 'zzzzzzzzzzzzzzzzzzz' and tt1.c_int = tt2.c_int) order by 1;
c_int
6
7
8
9
drop table if exists t1, t2;
create table t1(a int, b int, c varchar(10), d varchar(10), index idx_a_b_c_d(a, b, c(2), d(2)));
create table t2(e int, f int, g varchar(10), h varchar(10));
set @@tidb_opt_range_max_size = 0;
explain format='plan_tree' select /*+ inl_join(t1) */ * from t1 join t2 on t1.b = t2.e and t1.d = t2.g where t1.a in (1, 3) and t1.c in ('aaa', 'bbb');
id task access object operator info
IndexJoin root inner join, inner:IndexLookUp, outer key:planner__core__casetest__index__index.t2.e, planner__core__casetest__index__index.t2.g, inner key:planner__core__casetest__index__index.t1.b, planner__core__casetest__index__index.t1.d, equal cond:eq(planner__core__casetest__index__index.t2.e, planner__core__casetest__index__index.t1.b), eq(planner__core__casetest__index__index.t2.g, planner__core__casetest__index__index.t1.d)
├─TableReader(Build) root data:Selection
│ └─Selection cop[tikv] not(isnull(planner__core__casetest__index__index.t2.e)), not(isnull(planner__core__casetest__index__index.t2.g))
│ └─TableFullScan cop[tikv] table:t2 keep order:false, stats:pseudo
└─IndexLookUp(Probe) root
├─Selection(Build) cop[tikv] not(isnull(planner__core__casetest__index__index.t1.b)), not(isnull(planner__core__casetest__index__index.t1.d))
│ └─IndexRangeScan cop[tikv] table:t1, index:idx_a_b_c_d(a, b, c, d) range: decided by [eq(planner__core__casetest__index__index.t1.b, planner__core__casetest__index__index.t2.e) eq(planner__core__casetest__index__index.t1.d, planner__core__casetest__index__index.t2.g) in(planner__core__casetest__index__index.t1.a, 1, 3) in(planner__core__casetest__index__index.t1.c, aaa, bbb)], keep order:false, stats:pseudo
└─Selection(Probe) cop[tikv] in(planner__core__casetest__index__index.t1.c, "aaa", "bbb")
└─TableRowIDScan cop[tikv] table:t1 keep order:false, stats:pseudo
show warnings;
Level Code Message
set @@tidb_opt_range_max_size = 2900;
explain format='plan_tree' select /*+ inl_join(t1) */ * from t1 join t2 on t1.b = t2.e and t1.d = t2.g where t1.a in (1, 3) and t1.c in ('aaa', 'bbb');
id task access object operator info
IndexJoin root inner join, inner:IndexLookUp, outer key:planner__core__casetest__index__index.t2.e, inner key:planner__core__casetest__index__index.t1.b, equal cond:eq(planner__core__casetest__index__index.t2.e, planner__core__casetest__index__index.t1.b), eq(planner__core__casetest__index__index.t2.g, planner__core__casetest__index__index.t1.d)
├─TableReader(Build) root data:Selection
│ └─Selection cop[tikv] not(isnull(planner__core__casetest__index__index.t2.e)), not(isnull(planner__core__casetest__index__index.t2.g))
│ └─TableFullScan cop[tikv] table:t2 keep order:false, stats:pseudo
└─IndexLookUp(Probe) root
├─Selection(Build) cop[tikv] not(isnull(planner__core__casetest__index__index.t1.b)), not(isnull(planner__core__casetest__index__index.t1.d))
│ └─IndexRangeScan cop[tikv] table:t1, index:idx_a_b_c_d(a, b, c, d) range: decided by [eq(planner__core__casetest__index__index.t1.b, planner__core__casetest__index__index.t2.e) in(planner__core__casetest__index__index.t1.a, 1, 3) in(planner__core__casetest__index__index.t1.c, aaa, bbb)], keep order:false, stats:pseudo
└─Selection(Probe) cop[tikv] in(planner__core__casetest__index__index.t1.c, "aaa", "bbb")
└─TableRowIDScan cop[tikv] table:t1 keep order:false, stats:pseudo
show warnings;
Level Code Message
Warning 1105 Memory capacity of 2900 bytes for 'tidb_opt_range_max_size' exceeded when building ranges. Less accurate ranges such as full range are chosen
set @@tidb_opt_range_max_size = 2300;
explain format='plan_tree' select /*+ inl_join(t1) */ * from t1 join t2 on t1.b = t2.e and t1.d = t2.g where t1.a in (1, 3) and t1.c in ('aaa', 'bbb');
id task access object operator info
IndexJoin root inner join, inner:IndexLookUp, outer key:planner__core__casetest__index__index.t2.e, inner key:planner__core__casetest__index__index.t1.b, equal cond:eq(planner__core__casetest__index__index.t2.e, planner__core__casetest__index__index.t1.b), eq(planner__core__casetest__index__index.t2.g, planner__core__casetest__index__index.t1.d)
├─TableReader(Build) root data:Selection
│ └─Selection cop[tikv] not(isnull(planner__core__casetest__index__index.t2.e)), not(isnull(planner__core__casetest__index__index.t2.g))
│ └─TableFullScan cop[tikv] table:t2 keep order:false, stats:pseudo
└─IndexLookUp(Probe) root
├─Selection(Build) cop[tikv] not(isnull(planner__core__casetest__index__index.t1.b)), not(isnull(planner__core__casetest__index__index.t1.d))
│ └─IndexRangeScan cop[tikv] table:t1, index:idx_a_b_c_d(a, b, c, d) range: decided by [eq(planner__core__casetest__index__index.t1.b, planner__core__casetest__index__index.t2.e) in(planner__core__casetest__index__index.t1.a, 1, 3)], keep order:false, stats:pseudo
└─Selection(Probe) cop[tikv] in(planner__core__casetest__index__index.t1.c, "aaa", "bbb")
└─TableRowIDScan cop[tikv] table:t1 keep order:false, stats:pseudo
show warnings;
Level Code Message
Warning 1105 Memory capacity of 2300 bytes for 'tidb_opt_range_max_size' exceeded when building ranges. Less accurate ranges such as full range are chosen
set @@tidb_opt_range_max_size = 700;
explain format='plan_tree' select /*+ inl_join(t1) */ * from t1 join t2 on t1.b = t2.e and t1.d = t2.g where t1.a in (1, 3) and t1.c in ('aaa', 'bbb');
id task access object operator info
HashJoin root inner join, equal:[eq(planner__core__casetest__index__index.t1.b, planner__core__casetest__index__index.t2.e) eq(planner__core__casetest__index__index.t1.d, planner__core__casetest__index__index.t2.g)]
├─IndexLookUp(Build) root
│ ├─Selection(Build) cop[tikv] not(isnull(planner__core__casetest__index__index.t1.b)), not(isnull(planner__core__casetest__index__index.t1.d))
│ │ └─IndexRangeScan cop[tikv] table:t1, index:idx_a_b_c_d(a, b, c, d) range:[1,1], [3,3], keep order:false, stats:pseudo
│ └─Selection(Probe) cop[tikv] in(planner__core__casetest__index__index.t1.c, "aaa", "bbb")
│ └─TableRowIDScan cop[tikv] table:t1 keep order:false, stats:pseudo
└─TableReader(Probe) root data:Selection
└─Selection cop[tikv] not(isnull(planner__core__casetest__index__index.t2.e)), not(isnull(planner__core__casetest__index__index.t2.g))
└─TableFullScan cop[tikv] table:t2 keep order:false, stats:pseudo
show warnings;
Level Code Message
Warning 1105 Memory capacity of 700 bytes for 'tidb_opt_range_max_size' exceeded when building ranges. Less accurate ranges such as full range are chosen
Warning 1815 Optimizer Hint /*+ INL_JOIN(t1) */ or /*+ TIDB_INLJ(t1) */ is inapplicable
set @@tidb_opt_range_max_size = 0;
explain format='plan_tree' select /*+ inl_join(t1) */ * from t1 join t2 on t1.a = t2.e where t1.b > 1 and t1.b < 10;
id task access object operator info
IndexJoin root inner join, inner:IndexLookUp, outer key:planner__core__casetest__index__index.t2.e, inner key:planner__core__casetest__index__index.t1.a, equal cond:eq(planner__core__casetest__index__index.t2.e, planner__core__casetest__index__index.t1.a)
├─TableReader(Build) root data:Selection
│ └─Selection cop[tikv] not(isnull(planner__core__casetest__index__index.t2.e))
│ └─TableFullScan cop[tikv] table:t2 keep order:false, stats:pseudo
└─IndexLookUp(Probe) root
├─Selection(Build) cop[tikv] not(isnull(planner__core__casetest__index__index.t1.a))
│ └─IndexRangeScan cop[tikv] table:t1, index:idx_a_b_c_d(a, b, c, d) range: decided by [eq(planner__core__casetest__index__index.t1.a, planner__core__casetest__index__index.t2.e) gt(planner__core__casetest__index__index.t1.b, 1) lt(planner__core__casetest__index__index.t1.b, 10)], keep order:false, stats:pseudo
└─TableRowIDScan(Probe) cop[tikv] table:t1 keep order:false, stats:pseudo
show warnings;
Level Code Message
set @@tidb_opt_range_max_size = 300;
explain format='plan_tree' select /*+ inl_join(t1) */ * from t1 join t2 on t1.a = t2.e where t1.b > 1 and t1.b < 10;
id task access object operator info
IndexJoin root inner join, inner:IndexLookUp, outer key:planner__core__casetest__index__index.t2.e, inner key:planner__core__casetest__index__index.t1.a, equal cond:eq(planner__core__casetest__index__index.t2.e, planner__core__casetest__index__index.t1.a)
├─TableReader(Build) root data:Selection
│ └─Selection cop[tikv] not(isnull(planner__core__casetest__index__index.t2.e))
│ └─TableFullScan cop[tikv] table:t2 keep order:false, stats:pseudo
└─IndexLookUp(Probe) root
├─Selection(Build) cop[tikv] gt(planner__core__casetest__index__index.t1.b, 1), lt(planner__core__casetest__index__index.t1.b, 10), not(isnull(planner__core__casetest__index__index.t1.a))
│ └─IndexRangeScan cop[tikv] table:t1, index:idx_a_b_c_d(a, b, c, d) range: decided by [eq(planner__core__casetest__index__index.t1.a, planner__core__casetest__index__index.t2.e)], keep order:false, stats:pseudo
└─TableRowIDScan(Probe) cop[tikv] table:t1 keep order:false, stats:pseudo
show warnings;
Level Code Message
Warning 1105 Memory capacity of 300 bytes for 'tidb_opt_range_max_size' exceeded when building ranges. Less accurate ranges such as full range are chosen
set @@tidb_opt_range_max_size = 0;
explain format='plan_tree' select /*+ inl_join(t1) */ * from t1 join t2 on t1.a = t2.e where t1.b > t2.f and t1.b < t2.f + 10;
id task access object operator info
IndexJoin root inner join, inner:IndexLookUp, outer key:planner__core__casetest__index__index.t2.e, inner key:planner__core__casetest__index__index.t1.a, equal cond:eq(planner__core__casetest__index__index.t2.e, planner__core__casetest__index__index.t1.a), other cond:gt(planner__core__casetest__index__index.t1.b, planner__core__casetest__index__index.t2.f), lt(planner__core__casetest__index__index.t1.b, plus(planner__core__casetest__index__index.t2.f, 10))
├─TableReader(Build) root data:Selection
│ └─Selection cop[tikv] not(isnull(planner__core__casetest__index__index.t2.e)), not(isnull(planner__core__casetest__index__index.t2.f))
│ └─TableFullScan cop[tikv] table:t2 keep order:false, stats:pseudo
└─IndexLookUp(Probe) root
├─Selection(Build) cop[tikv] not(isnull(planner__core__casetest__index__index.t1.a)), not(isnull(planner__core__casetest__index__index.t1.b))
│ └─IndexRangeScan cop[tikv] table:t1, index:idx_a_b_c_d(a, b, c, d) range: decided by [eq(planner__core__casetest__index__index.t1.a, planner__core__casetest__index__index.t2.e) gt(planner__core__casetest__index__index.t1.b, planner__core__casetest__index__index.t2.f) lt(planner__core__casetest__index__index.t1.b, plus(planner__core__casetest__index__index.t2.f, 10))], keep order:false, stats:pseudo
└─TableRowIDScan(Probe) cop[tikv] table:t1 keep order:false, stats:pseudo
show warnings;
Level Code Message
set @@tidb_opt_range_max_size = 300;
explain format='plan_tree' select /*+ inl_join(t1) */ * from t1 join t2 on t1.a = t2.e where t1.b > t2.f and t1.b < t2.f + 10;
id task access object operator info
IndexJoin root inner join, inner:IndexLookUp, outer key:planner__core__casetest__index__index.t2.e, inner key:planner__core__casetest__index__index.t1.a, equal cond:eq(planner__core__casetest__index__index.t2.e, planner__core__casetest__index__index.t1.a), other cond:gt(planner__core__casetest__index__index.t1.b, planner__core__casetest__index__index.t2.f), lt(planner__core__casetest__index__index.t1.b, plus(planner__core__casetest__index__index.t2.f, 10))
├─TableReader(Build) root data:Selection
│ └─Selection cop[tikv] not(isnull(planner__core__casetest__index__index.t2.e)), not(isnull(planner__core__casetest__index__index.t2.f))
│ └─TableFullScan cop[tikv] table:t2 keep order:false, stats:pseudo
└─IndexLookUp(Probe) root
├─Selection(Build) cop[tikv] not(isnull(planner__core__casetest__index__index.t1.a)), not(isnull(planner__core__casetest__index__index.t1.b))
│ └─IndexRangeScan cop[tikv] table:t1, index:idx_a_b_c_d(a, b, c, d) range: decided by [eq(planner__core__casetest__index__index.t1.a, planner__core__casetest__index__index.t2.e)], keep order:false, stats:pseudo
└─TableRowIDScan(Probe) cop[tikv] table:t1 keep order:false, stats:pseudo
show warnings;
Level Code Message
Warning 1105 Memory capacity of 300 bytes for 'tidb_opt_range_max_size' exceeded when building ranges. Less accurate ranges such as full range are chosen
set @@tidb_opt_range_max_size = default;
drop table if exists t1, t2, t3, t4;
create table t1(a int, b int, c int, d int, e int, f int, g int, primary key (a), unique key c_d_e (c, d, e), unique key f (f), unique key f_g (f, g), key g (g));
create table t2(a int, b int, c int, d int, unique index idx_a (a), unique index idx_b_c (b, c), unique index idx_b_c_a_d (b, c, a, d));
create table t3(a bigint, b varchar(255), c bigint, primary key(a, b) clustered);
create table t4(a bigint, b varchar(255), c bigint, primary key(a, b) nonclustered);
set @@tidb_enable_chunk_rpc = on;
explain format = 'brief' select * from t1 where a = 3 or a = 5;
id estRows task access object operator info
Batch_Point_Get 2.00 root table:t1 handle:[3 5], keep order:false, desc:false
show warnings;
Level Code Message
explain format = 'brief' select f, g from t1 where f = 2 and g in (3, 4, 5);
id estRows task access object operator info
Batch_Point_Get 3.00 root table:t1, index:f_g(f, g) keep order:false, desc:false
show warnings;
Level Code Message
explain format = 'brief' select * from t1 where c = 1 and (d = 2 or d = 3) and e in (4, 5);
id estRows task access object operator info
Batch_Point_Get 4.00 root table:t1, index:c_d_e(c, d, e) keep order:false, desc:false
show warnings;
Level Code Message
explain format = 'brief' select f, g from t1 where f = 2 and g > 3;
id estRows task access object operator info
IndexReader 33.33 root index:IndexRangeScan
└─IndexRangeScan 33.33 cop[tikv] table:t1, index:f_g(f, g) range:(2 3,2 +inf], keep order:false, stats:pseudo
show warnings;
Level Code Message
explain format = 'brief' select a, b, c from t2 where a = 1 and b = 2 and c in (1, 2, 3, 4, 5);
id estRows task access object operator info
Selection 1.00 root eq(planner__core__casetest__index__index.t2.b, 2), in(planner__core__casetest__index__index.t2.c, 1, 2, 3, 4, 5)
└─Point_Get 1.00 root table:t2, index:idx_a(a)
show warnings;
Level Code Message
explain format = 'brief' select * from t3 where (a = 1 or a = 3) and b = 'xx';
id estRows task access object operator info
Batch_Point_Get 2.00 root table:t3, clustered index:PRIMARY(a, b) keep order:false, desc:false
show warnings;
Level Code Message
explain format = 'brief' select * from t4 where (a = 1 or a = 3) and b = 'xx';
id estRows task access object operator info
Batch_Point_Get 2.00 root table:t4, index:PRIMARY(a, b) keep order:false, desc:false
show warnings;
Level Code Message
explain format = 'brief' select a, b from t3 where (a = 1 or a = 3) and b = 'xx';
id estRows task access object operator info
Batch_Point_Get 2.00 root table:t3, clustered index:PRIMARY(a, b) keep order:false, desc:false
show warnings;
Level Code Message
explain format = 'brief' select a, b from t4 where (a = 1 or a = 3) and b = 'xx';
id estRows task access object operator info
Batch_Point_Get 2.00 root table:t4, index:PRIMARY(a, b) keep order:false, desc:false
show warnings;
Level Code Message
explain format = 'brief' update t1 set b = 2 where a = 4 or a = 6;
id estRows task access object operator info
Update N/A root N/A
└─Batch_Point_Get 2.00 root table:t1 handle:[4 6], keep order:false, desc:false
show warnings;
Level Code Message
explain format = 'brief' delete from t1 where f = 2 and g in (3, 4);
id estRows task access object operator info
Delete N/A root N/A
└─Selection 2.00 root in(planner__core__casetest__index__index.t1.g, 3, 4)
└─Point_Get 1.00 root table:t1, index:f(f)
show warnings;
Level Code Message
explain format = 'brief' insert into t3 select a, b, c from t1 where f = 2;
id estRows task access object operator info
Insert N/A root N/A
└─Projection 1.00 root planner__core__casetest__index__index.t1.a, planner__core__casetest__index__index.t1.b, planner__core__casetest__index__index.t1.c
└─Point_Get 1.00 root table:t1, index:f(f)
show warnings;
Level Code Message
explain format = 'brief' replace into t3 select a, b, c from t1 where a = 3;
id estRows task access object operator info
Insert N/A root N/A
└─Point_Get 1.00 root table:t1 handle:3
show warnings;
Level Code Message
set @@tidb_enable_chunk_rpc = default;
drop table if exists t;
create table t(a int, b int, c int, d int, index idx(a,b,c));
explain format='plan_tree' select * from t where a = 1 and b > 2 and b < 10 and d = 10 order by b,c limit 10;
id task access object operator info
Limit root offset:0, count:10
└─Projection root planner__core__casetest__index__index.t.a, planner__core__casetest__index__index.t.b, planner__core__casetest__index__index.t.c, planner__core__casetest__index__index.t.d
└─IndexLookUp root
├─IndexRangeScan(Build) cop[tikv] table:t, index:idx(a, b, c) range:(1 2,1 10), keep order:true, stats:pseudo
└─Selection(Probe) cop[tikv] eq(planner__core__casetest__index__index.t.d, 10)
└─TableRowIDScan cop[tikv] table:t keep order:false, stats:pseudo
explain format='plan_tree' select * from t where a = 1 and b > 2 and b < 10 and d = 10 order by b desc, c desc limit 10;
id task access object operator info
Limit root offset:0, count:10
└─Projection root planner__core__casetest__index__index.t.a, planner__core__casetest__index__index.t.b, planner__core__casetest__index__index.t.c, planner__core__casetest__index__index.t.d
└─IndexLookUp root
├─IndexRangeScan(Build) cop[tikv] table:t, index:idx(a, b, c) range:(1 2,1 10), keep order:true, desc, stats:pseudo
└─Selection(Probe) cop[tikv] eq(planner__core__casetest__index__index.t.d, 10)
└─TableRowIDScan cop[tikv] table:t keep order:false, stats:pseudo
set tidb_enable_clustered_index=on;
drop table if exists t1;
create table t1 (a int, b varchar(20), c decimal(40,10), d int, primary key(a,b), key(c));
insert into t1 values (1,"111",1.1,11), (2,"222",2.2,12), (3,"333",3.3,13);
analyze table t1;
explain format='plan_tree' select * from t1;
id task access object operator info
TableReader root data:TableFullScan
└─TableFullScan cop[tikv] table:t1 keep order:false
select * from t1;
a b c d
1 111 1.1000000000 11
2 222 2.2000000000 12
3 333 3.3000000000 13
explain format='plan_tree' select * from t1 where t1.a >= 1 and t1.a < 4;
id task access object operator info
TableReader root data:TableRangeScan
└─TableRangeScan cop[tikv] table:t1 range:[1,4), keep order:false
select * from t1 where t1.a >= 1 and t1.a < 4;
a b c d
1 111 1.1000000000 11
2 222 2.2000000000 12
3 333 3.3000000000 13
explain format='plan_tree' select * from t1 where t1.a = 1 and t1.b < "333";
id task access object operator info
TableReader root data:TableRangeScan
└─TableRangeScan cop[tikv] table:t1 range:[1 -inf,1 "333"), keep order:false
select * from t1 where t1.a = 1 and t1.b < "333";
a b c d
1 111 1.1000000000 11
explain format='plan_tree' select t1.a, t1.b, t1.c from t1 where t1.c = 3.3;
id task access object operator info
IndexReader root index:IndexRangeScan
└─IndexRangeScan cop[tikv] table:t1, index:c(c) range:[3.3000000000,3.3000000000], keep order:false
select t1.a, t1.b, t1.c from t1 where t1.c = 3.3;
a b c
3 333 3.3000000000
explain format='plan_tree' select t1.b, t1.c from t1 where t1.c = 2.2;
id task access object operator info
IndexReader root index:IndexRangeScan
└─IndexRangeScan cop[tikv] table:t1, index:c(c) range:[2.2000000000,2.2000000000], keep order:false
select t1.b, t1.c from t1 where t1.c = 2.2;
b c
222 2.2000000000
explain format='plan_tree' select /*+ use_index(t1, c) */ * from t1;
id task access object operator info
IndexLookUp root
├─IndexFullScan(Build) cop[tikv] table:t1, index:c(c) keep order:false
└─TableRowIDScan(Probe) cop[tikv] table:t1 keep order:false
select /*+ use_index(t1, c) */ * from t1;
a b c d
1 111 1.1000000000 11
2 222 2.2000000000 12
3 333 3.3000000000 13
explain format='plan_tree' select * from t1 use index(c) where t1.c in (2.2, 3.3);
id task access object operator info
IndexLookUp root
├─IndexRangeScan(Build) cop[tikv] table:t1, index:c(c) range:[2.2000000000,2.2000000000], [3.3000000000,3.3000000000], keep order:false
└─TableRowIDScan(Probe) cop[tikv] table:t1 keep order:false
select * from t1 use index(c) where t1.c in (2.2, 3.3);
a b c d
2 222 2.2000000000 12
3 333 3.3000000000 13
explain format='plan_tree' select * from t1 where t1.a = 1 order by b;
id task access object operator info
TableReader root data:TableRangeScan
└─TableRangeScan cop[tikv] table:t1 range:[1,1], keep order:true
select * from t1 where t1.a = 1 order by b;
a b c d
1 111 1.1000000000 11
explain format='plan_tree' select * from t1 order by a, b limit 1;
id task access object operator info
Limit root offset:0, count:1
└─TableReader root data:Limit
└─Limit cop[tikv] offset:0, count:1
└─TableFullScan cop[tikv] table:t1 keep order:true
select * from t1 order by a, b limit 1;
a b c d
1 111 1.1000000000 11
explain format='plan_tree' select /*+ use_index_merge(t1 primary, c) */ * from t1 where t1.a >= 1 or t1.c = 2.2;
id task access object operator info
IndexMerge root type: union
├─TableRangeScan(Build) cop[tikv] table:t1 range:[1,+inf], keep order:false
├─IndexRangeScan(Build) cop[tikv] table:t1, index:c(c) range:[2.2000000000,2.2000000000], keep order:false
└─TableRowIDScan(Probe) cop[tikv] table:t1 keep order:false
select /*+ use_index_merge(t1 primary, c) */ * from t1 where t1.a >= 1 or t1.c = 2.2;
a b c d
1 111 1.1000000000 11
2 222 2.2000000000 12
3 333 3.3000000000 13
explain format='plan_tree' select /*+ use_index_merge(t1 primary, c) */ * from t1 where t1.a = 1 and t1.b = '111' or t1.c = 3.3;
id task access object operator info
IndexMerge root type: union
├─TableRangeScan(Build) cop[tikv] table:t1 range:[1 "111",1 "111"], keep order:false
├─IndexRangeScan(Build) cop[tikv] table:t1, index:c(c) range:[3.3000000000,3.3000000000], keep order:false
└─TableRowIDScan(Probe) cop[tikv] table:t1 keep order:false
select /*+ use_index_merge(t1 primary, c) */ * from t1 where t1.a = 1 and t1.b = '111' or t1.c = 3.3;
a b c d
1 111 1.1000000000 11
3 333 3.3000000000 13
set tidb_enable_clustered_index=default;
drop table if exists t;
create table t(pk varbinary(255) NOT NULL, domains json null, image_signatures json null, canonical_links json null, fpi json null, KEY `domains` ((cast(`domains` as char(253) array))), KEY `image_signatures` ((cast(`image_signatures` as char(32) array))),KEY `canonical_links` ((cast(`canonical_links` as char(1000) array))), KEY `fpi` ((cast(`fpi` as unsigned array))));
explain format='plan_tree' SELECT pk FROM t WHERE "B2c32" member of (domains) OR "2eoqyp6399" member of (image_signatures) OR "E33EAdAc2Bee.com/s/link" member of (canonical_links) OR json_contains(fpi, '[69236881]') LIMIT 10;
id task access object operator info
Projection root planner__core__casetest__index__index.t.pk
└─IndexMerge root type: union, limit embedded(offset:0, count:10)
├─Limit(Build) cop[tikv] offset:0, count:10
│ └─IndexRangeScan cop[tikv] table:t, index:domains(cast(`domains` as char(253) array)) range:["B2c32","B2c32"], keep order:false, stats:pseudo
├─Limit(Build) cop[tikv] offset:0, count:10
│ └─IndexRangeScan cop[tikv] table:t, index:image_signatures(cast(`image_signatures` as char(32) array)) range:["2eoqyp6399","2eoqyp6399"], keep order:false, stats:pseudo
├─Limit(Build) cop[tikv] offset:0, count:10
│ └─IndexRangeScan cop[tikv] table:t, index:canonical_links(cast(`canonical_links` as char(1000) array)) range:["E33EAdAc2Bee.com/s/link","E33EAdAc2Bee.com/s/link"], keep order:false, stats:pseudo
├─Limit(Build) cop[tikv] offset:0, count:10
│ └─IndexRangeScan cop[tikv] table:t, index:fpi(cast(`fpi` as unsigned array)) range:[69236881,69236881], keep order:false, stats:pseudo
└─TableRowIDScan(Probe) cop[tikv] table:t keep order:false, stats:pseudo
drop table if exists t1, t2;
CREATE TABLE `t1` (`a` varchar(488) COLLATE utf8_general_ci DEFAULT NULL, `b` binary(206) DEFAULT '0', `c` json DEFAULT NULL, UNIQUE KEY `idx_29` (`a`,(cast(`c` as signed array)),`b`), UNIQUE KEY `idx_30` ((cast(`c` as signed array)),`a`(5))) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
CREATE TABLE `t2` (`a` float NOT NULL DEFAULT '5217.6055',`b` json NOT NULL,`c` json NOT NULL,`d` varchar(181) COLLATE gbk_bin NOT NULL DEFAULT 'FbVkA~^', KEY `idx_26` (`a`),PRIMARY KEY (`a`,`d`) /*T![clustered_index] NONCLUSTERED */,UNIQUE KEY `idx_28` (`a`,(cast(`b` as binary(64) array)),`d`)) ENGINE=InnoDB DEFAULT CHARSET=gbk COLLATE=gbk_bin;
explain format='plan_tree' SELECT /*+ inl_join(t1)*/ `t2`.`c` AS `r0` FROM `t1` JOIN `t2` ON `t1`.`a`=`t2`.`d` WHERE `t2`.`d`='' AND NOT (6252179388429456273 MEMBER OF (`t1`.`c`));
id task access object operator info
HashJoin root inner join, equal:[eq(planner__core__casetest__index__index.t2.d, planner__core__casetest__index__index.t1.a)]
├─TableReader(Build) root data:Selection
│ └─Selection cop[tikv] eq(planner__core__casetest__index__index.t2.d, "")
│ └─TableFullScan cop[tikv] table:t2 keep order:false, stats:pseudo
└─TableReader(Probe) root data:Selection
└─Selection cop[tikv] not(isnull(planner__core__casetest__index__index.t1.a)), not(istrue_with_null(json_memberof(cast(6252179388429456273, json BINARY), planner__core__casetest__index__index.t1.c)))
└─TableFullScan cop[tikv] table:t1 keep order:false, stats:pseudo
SHOW WARNINGS;
Level Code Message
Warning 1815 Optimizer Hint /*+ INL_JOIN(t1) */ or /*+ TIDB_INLJ(t1) */ is inapplicable
DROP table if exists t;
CREATE TABLE `t` (`pk` varbinary(255) NOT NULL,`nslc` json DEFAULT NULL,`fpi` json DEFAULT NULL,`point_of_sale_country` varchar(2) DEFAULT NULL,PRIMARY KEY (`pk`) /*T![clustered_index] CLUSTERED */,KEY `fpi` ((cast(`fpi` as unsigned array))),KEY `nslc` ((cast(`nslc` as char(1000) array)),`point_of_sale_country`),KEY `nslc_old` ((cast(`nslc` as char(1000) array))));
explain format='plan_tree' SELECT /*+ use_index_merge(t, fpi, nslc_old, nslc) */ * FROM t WHERE 15975127 member of (fpi) AND "OC8p0106XTkt.org/s/link" member of (nslc) LIMIT 10;
id task access object operator info
IndexMerge root type: intersection, limit embedded(offset:0, count:10)
├─IndexRangeScan(Build) cop[tikv] table:t, index:fpi(cast(`fpi` as unsigned array)) range:[15975127,15975127], keep order:false, stats:pseudo
├─IndexRangeScan(Build) cop[tikv] table:t, index:nslc(cast(`nslc` as char(1000) array), point_of_sale_country) range:["OC8p0106XTkt.org/s/link","OC8p0106XTkt.org/s/link"], keep order:false, stats:pseudo
└─TableRowIDScan(Probe) cop[tikv] table:t keep order:false, stats:pseudo
drop table if exists t;
CREATE TABLE t (nslc json DEFAULT NULL,fpi json DEFAULT NULL,point_of_sale_country int,KEY nslc ((cast(nslc as char(1000) array)),point_of_sale_country),KEY fpi ((cast(fpi as unsigned array))));
explain format='plan_tree' SELECT /*+ use_index_merge(t, nslc) */ * FROM t WHERE 57260686 member of (fpi) AND "OC8p1763XTkt.org/s/link" member of (nslc) LIMIT 1;
id task access object operator info
Limit root offset:0, count:1
└─IndexMerge root type: union
├─IndexRangeScan(Build) cop[tikv] table:t, index:nslc(cast(`nslc` as char(1000) array), point_of_sale_country) range:["OC8p1763XTkt.org/s/link","OC8p1763XTkt.org/s/link"], keep order:false, stats:pseudo
└─Limit(Probe) cop[tikv] offset:0, count:1
└─Selection cop[tikv] json_memberof(cast(57260686, json BINARY), planner__core__casetest__index__index.t.fpi)
└─TableRowIDScan cop[tikv] table:t keep order:false, stats:pseudo
explain format='plan_tree' SELECT /*+ use_index_merge(t, fpi) */ * FROM t WHERE 57260686 member of (fpi) AND "OC8p1763XTkt.org/s/link" member of (nslc) LIMIT 1;
id task access object operator info
Limit root offset:0, count:1
└─IndexMerge root type: union
├─IndexRangeScan(Build) cop[tikv] table:t, index:fpi(cast(`fpi` as unsigned array)) range:[57260686,57260686], keep order:false, stats:pseudo
└─Limit(Probe) cop[tikv] offset:0, count:1
└─Selection cop[tikv] json_memberof(cast("OC8p1763XTkt.org/s/link", json BINARY), planner__core__casetest__index__index.t.nslc)
└─TableRowIDScan cop[tikv] table:t keep order:false, stats:pseudo
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify the corresponding .t test file exists
fd -t f 'index.test' tests/integrationtest/t/planner/core/casetest/index/

# Check if there's documentation about how to record integration tests
if [ -f "docs/agents/testing-flow.md" ]; then
  echo "=== Recording instructions from testing-flow.md ==="
  rg -A 10 -i "integration test" docs/agents/testing-flow.md || echo "No integration test section found"
fi

# Verify the result file line count matches expected structure
echo "=== Result file statistics ==="
wc -l tests/integrationtest/r/planner/core/casetest/index/index.result

Repository: pingcap/tidb

Length of output: 312


🏁 Script executed:

# Find the actual structure of integrationtest directories
fd -t d 'integrationtest' . | head -20

# Look for the index test file with broader search
find . -name '*index*' -path '*/integrationtest/*' -type f 2>/dev/null | grep -E '\.(t|result)$' | head -20

# Check for the docs directory and testing guidance
ls -la docs/ 2>/dev/null | head -20

# Look for any testing documentation
find . -name '*testing*' -o -name '*TESTING*' 2>/dev/null | grep -v '.git' | head -20

Repository: pingcap/tidb

Length of output: 1280


🏁 Script executed:

# Find .t test files (test input files)
find tests/integrationtest/t -name '*.t' -o -name '*.test' 2>/dev/null | head -20

# Look for agents documentation referenced in learnings
find . -path '*/.agents/*' -o -path '*/agents/*' 2>/dev/null | grep -i test | head -20

# Search for the specific testing documentation mentioned in learnings
find . -name 'testing-flow.md' -o -name '*testing*flow*' 2>/dev/null | head -10

# Check if there's a .agents/skills directory
ls -la .agents/skills/ 2>/dev/null || echo "No .agents/skills found"

# List top-level test files in integrationtest
ls -la tests/integrationtest/t/ 2>/dev/null | head -30

Repository: pingcap/tidb

Length of output: 3075


🏁 Script executed:

# Search for index.test file in planner/core/casetest directories
find tests/integrationtest/t -path '*planner*' -name '*.test' 2>/dev/null | head -20

# List contents of planner directory if it exists
ls -la tests/integrationtest/t/planner/ 2>/dev/null || echo "No planner directory"

# Search for any documentation about integration tests
rg -l "integration.*test" docs/ 2>/dev/null | head -10

# Look for README or instructions in integrationtest directory
ls -la tests/integrationtest/ 2>/dev/null

# Check if there's any CI/workflow documentation that mentions recording
find . -name '*.md' -path '*docs*' 2>/dev/null | xargs grep -l "integration.*test" 2>/dev/null | head -10

Repository: pingcap/tidb

Length of output: 3210


🏁 Script executed:

# Check the casetest directory structure more carefully
ls -la tests/integrationtest/t/planner/core/casetest/ 2>/dev/null

# Look for any integration test that might contain index tests
find tests/integrationtest/t -name '*index*' 2>/dev/null

# Check if the result file directory has a corresponding test file
ls -la tests/integrationtest/r/planner/core/casetest/ 2>/dev/null

# Look at README for integration test instructions
cat tests/integrationtest/README.md 2>/dev/null

# Check the run-tests.sh script to understand test execution
head -100 tests/integrationtest/run-tests.sh

Repository: pingcap/tidb

Length of output: 7387


🏁 Script executed:

# Check what's in the result index directory
ls -la tests/integrationtest/r/planner/core/casetest/index/ 2>/dev/null

# Search for any test file containing index-related tests in casetest
find tests/integrationtest/t/planner/core/casetest -name '*.test' -exec grep -l "index\|INDEX" {} \; 2>/dev/null | head -10

# Check if index test file exists elsewhere
find tests/integrationtest -name 'index.test' 2>/dev/null

# Look at the integration.test file to see if it contains index tests
head -50 tests/integrationtest/t/planner/core/casetest/integration.test

# Check if there's a planner/core/index subdirectory
find tests/integrationtest/t/planner/core -type d -name 'index*' 2>/dev/null

Repository: pingcap/tidb

Length of output: 3361


🏁 Script executed:

# Verify: the result file exists but check if there's a corresponding .test file
echo "=== Checking for index.test file ==="
[ -f tests/integrationtest/t/planner/core/casetest/index/index.test ] && echo "FOUND: index.test" || echo "NOT FOUND: index.test"

# List all files in both directories
echo ""
echo "=== Files in t/planner/core/casetest/index/ ==="
ls -la tests/integrationtest/t/planner/core/casetest/index/ 2>/dev/null || echo "Directory does not exist"

echo ""
echo "=== Files in r/planner/core/casetest/index/ ==="
ls -la tests/integrationtest/r/planner/core/casetest/index/ 2>/dev/null

# Check git status to see if this is a new file
echo ""
echo "=== Git status of the result file ==="
cd tests/integrationtest && git status r/planner/core/casetest/index/index.result 2>/dev/null || echo "Not in git or no git info"

Repository: pingcap/tidb

Length of output: 519


🏁 Script executed:

# Double-check: search for any .test file that might generate this index.result
rg -l "planner.*index" tests/integrationtest/t --type test | head -20

# Check what the README says about the structure
echo "=== Test file discovery ==="
find tests/integrationtest/t -name '*.test' | wc -l
echo "Test files found. Now checking result files:"
find tests/integrationtest/r -name '*.result' | wc -l
echo "Result files found."

# Verify if there's any pairing of test and result files with different paths
echo ""
echo "=== Checking result files without test counterparts ==="
for result in $(find tests/integrationtest/r -name '*.result' -type f); do
  testfile="${result/\/r\//\/t\/}"
  testfile="${testfile/\.result/.test}"
  if [ ! -f "$testfile" ]; then
    echo "ORPHAN RESULT: $result (no test file at $testfile)"
  fi
done | head -20

Repository: pingcap/tidb

Length of output: 1978


🏁 Script executed:

# Verify the structure - check how other result files map to test files
echo "=== Sample of properly paired test/result files ==="
ls tests/integrationtest/t/explain*.test | head -5 | while read f; do
  result="${f/\/t\//\/r\/}"
  result="${result/\.test/.result}"
  echo "Test: $f"
  echo "Result: $result"
  [ -f "$result" ] && echo "✓ Result exists" || echo "✗ Result missing"
  echo ""
done

# Specifically check if index.result should be paired with a test file
echo ""
echo "=== Files in integration test directories ==="
echo "Test count in planner/core/casetest:"
find tests/integrationtest/t/planner/core/casetest -name '*.test' | wc -l
echo ""
echo "Result count in planner/core/casetest:"
find tests/integrationtest/r/planner/core/casetest -name '*.result' | wc -l

# Get file size to understand scale
echo ""
echo "=== File size comparison ==="
echo "index.result size:"
wc -c tests/integrationtest/r/planner/core/casetest/index/index.result

# Check integration.test which seems to be the main test file
echo ""
echo "=== Checking integration.test ==="
wc -l tests/integrationtest/t/planner/core/casetest/integration.test
echo ""
echo "First lines of integration.test:"
head -5 tests/integrationtest/t/planner/core/casetest/integration.test

Repository: pingcap/tidb

Length of output: 1240


🏁 Script executed:

# Critical: check if there's a test file that should be generating this result
echo "=== Looking for test files that might generate index.result ==="
rg -l "index" tests/integrationtest/t/planner/core/casetest/*.test 2>/dev/null

# Check the integration.test to see if it contains index-related tests
echo ""
echo "=== Searching for index-related tests in integration.test ==="
rg -c "index|INDEX" tests/integrationtest/t/planner/core/casetest/integration.test

# Check the result files to see which test file they correspond to
echo ""
echo "=== List of result files in casetest ==="
ls -1 tests/integrationtest/r/planner/core/casetest/*.result 2>/dev/null

# Check size of integration.result
echo ""
echo "=== Comparing sizes ==="
echo "integration.result:"
wc -c tests/integrationtest/r/planner/core/casetest/integration.result
echo ""
echo "index.result:"
wc -c tests/integrationtest/r/planner/core/casetest/index/index.result

Repository: pingcap/tidb

Length of output: 919


🏁 Script executed:

# Critical verification: check if there's any indication of where this should come from
echo "=== Checking git log for this file ==="
cd tests/integrationtest && git log --oneline -20 -- r/planner/core/casetest/index/index.result 2>/dev/null || echo "No recent changes"

# Check for any test setup or configuration that might generate this
echo ""
echo "=== Searching for 'index' test references in the codebase ==="
rg "index\.result|index\.test" tests/integrationtest/ --type-list 2>/dev/null | head -20

# Look at the first few lines of the orphaned result to understand its source
echo ""
echo "=== First 30 lines of index.result ==="
head -30 tests/integrationtest/r/planner/core/casetest/index/index.result

Repository: pingcap/tidb

Length of output: 390


🏁 Script executed:

# Trace back: check the git commit that added this file
echo "=== Git commit details ==="
cd tests/integrationtest && git show --stat 3d6e2ebc 2>/dev/null | head -50

# Look for PR reference
echo ""
echo "=== Commit message ==="
cd tests/integrationtest && git log -1 --format="%B" 3d6e2ebc 2>/dev/null

Repository: pingcap/tidb

Length of output: 3043


🏁 Script executed:

# Go back to repo root and check the file directly
echo "=== Current directory ==="
pwd

echo ""
echo "=== Checking if index.result exists ==="
ls -la tests/integrationtest/r/planner/core/casetest/index/index.result 2>/dev/null && echo "FILE EXISTS" || echo "FILE NOT FOUND"

# Try to read the file from repo root
echo ""
echo "=== First 30 lines of index.result ==="
head -30 tests/integrationtest/r/planner/core/casetest/index/index.result 2>/dev/null || echo "Cannot read file"

# Check the git log from repo root
echo ""
echo "=== Git log for index.result ==="
git log --oneline -5 -- tests/integrationtest/r/planner/core/casetest/index/index.result 2>/dev/null

# Check if this is part of the PR being reviewed
echo ""
echo "=== Current HEAD ==="
git log --oneline -1

Repository: pingcap/tidb

Length of output: 3391


🏁 Script executed:

# Final verification: check if there's supposed to be a test file
echo "=== Checking PR `#66349` reference ==="
git log --all --grep="66349" --oneline | head -5

echo ""
echo "=== Verify file pairing requirement ==="
# The README says test files in t/ and result files in r/ should be paired
# Check the integration.test size and structure
echo "Integration.test lines:"
wc -l tests/integrationtest/t/planner/core/casetest/integration.test
echo ""
echo "Integration.result lines:"
wc -l tests/integrationtest/r/planner/core/casetest/integration.result

echo ""
echo "=== Checking if index.result content is in integration.test ==="
# Search if the content of index.result is actually in integration.test
rg "drop table if exists t1, t2;" tests/integrationtest/t/planner/core/casetest/integration.test

echo ""
echo "=== Alternative: check if this should be in a different test file ==="
rg "TIDB_INLJ\|IndexMerge" tests/integrationtest/t/planner/core/casetest/*.test | cut -d: -f1 | sort -u

Repository: pingcap/tidb

Length of output: 615


🏁 Script executed:

# Final check: verify the actual issue
echo "=== Summary of findings ==="
echo ""
echo "1. Result file exists:"
[ -f tests/integrationtest/r/planner/core/casetest/index/index.result ] && echo "   ✓ tests/integrationtest/r/planner/core/casetest/index/index.result (74352 bytes)"

echo ""
echo "2. Test file exists:"
[ -f tests/integrationtest/t/planner/core/casetest/index/index.test ] && echo "   ✓ tests/integrationtest/t/planner/core/casetest/index/index.test" || echo "   ✗ tests/integrationtest/t/planner/core/casetest/index/index.test NOT FOUND"

echo ""
echo "3. Test/result pairing:"
echo "   Expected structure: t/<name>.test → r/<name>.result"
echo "   Actual for index: No test file found"

echo ""
echo "4. File origin (from git):"
git show 3d6e2ebc:tests/integrationtest/r/planner/core/casetest/index/index.result 2>/dev/null | wc -l
echo "   Lines in commit 3d6e2ebc: $(git show 3d6e2ebc:tests/integrationtest/r/planner/core/casetest/index/index.result 2>/dev/null | wc -l)"

echo ""
echo "5. PR context:"
git log --all --oneline | grep -i "66349" | head -1

Repository: pingcap/tidb

Length of output: 544


Missing test file for integration test result.

The result file tests/integrationtest/r/planner/core/casetest/index/index.result (823 lines) has no corresponding test file at tests/integrationtest/t/planner/core/casetest/index/index.test. Integration test result files must be paired with test files to be regenerated via the recording command per the framework's expectations. Add the missing test file or verify this result should be incorporated into an existing test file like tests/integrationtest/t/planner/core/casetest/integration.test.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/integrationtest/r/planner/core/casetest/index/index.result` around
lines 1 - 823, Missing pairing for the integration test result:
tests/integrationtest/r/planner/core/casetest/index/index.result has no
corresponding test input file; either add
tests/integrationtest/t/planner/core/casetest/index/index.test containing the
test SQLs that produced this result or confirm that the result should be merged
into an existing file such as
tests/integrationtest/t/planner/core/casetest/integration.test, then regenerate
the .result by running the framework's recording command so the result and test
file are in sync.

Comment on lines +668 to +679
select * from t t1 left join t t2 on t1.a=t2.a limit 1, 1;
a a
2 2
explain format='plan_tree' select * from t t1 left join t t2 on t1.a=t2.a limit 1, 1;
id task access object operator info
IndexJoin root left outer join, inner:TableReader, left side:Limit, outer key:test.t.a, inner key:test.t.a, equal cond:eq(test.t.a, test.t.a)
├─Limit(Build) root offset:1, count:1
│ └─TableReader root data:Limit
│ └─Limit cop[tikv] offset:0, count:2
│ └─TableFullScan cop[tikv] table:t1 keep order:false, stats:pseudo
└─TableReader(Probe) root data:TableRangeScan
└─TableRangeScan cop[tikv] table:t2 range: decided by [test.t.a], keep order:false, stats:pseudo
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Make this LIMIT assertion deterministic.

LIMIT 1, 1 without ORDER BY does not guarantee which row is returned, and the adjacent plan_tree already shows keep order:false. As written, this result can flap when planner/executor behavior changes. Please add an explicit ordering key before recording the expected row.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/integrationtest/r/planner/core/issuetest/planner_issue.result` around
lines 668 - 679, The test's non-deterministic LIMIT usage (the SQL "select *
from t t1 left join t t2 on t1.a=t2.a limit 1, 1" and its corresponding "explain
format='plan_tree' ... limit 1, 1") must include an explicit ORDER BY to
guarantee a stable returned row; update both the query and the EXPLAIN query to
add a deterministic ordering key (for example "ORDER BY t1.a" or a stable
primary key/row id) so the expected row assertion is deterministic and matches
the recorded plan output.

@ti-chi-bot
Copy link
Copy Markdown

ti-chi-bot Bot commented Mar 25, 2026

@ti-chi-bot: The following tests failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
idc-jenkins-ci-tidb/build 3d6e2eb link true /test build
idc-jenkins-ci-tidb/check_dev 3d6e2eb link true /test check-dev
idc-jenkins-ci-tidb/check_dev_2 3d6e2eb link true /test check-dev2
idc-jenkins-ci-tidb/unit-test 3d6e2eb link true /test unit-test
idc-jenkins-ci-tidb/mysql-test 3d6e2eb link true /test mysql-test

Full PR test history. Your PR dashboard.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

do-not-merge/cherry-pick-not-approved do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. ok-to-test Indicates a PR is ready to be tested. release-note-none Denotes a PR that doesn't merit a release note. sig/planner SIG: Planner size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. type/cherry-pick-for-release-7.5 This PR is cherry-picked to release-7.5 from a source PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants