-
Notifications
You must be signed in to change notification settings - Fork 5.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
planner: support converting json_overlaps/contains
to IndexMerge to access MVIndex
#40195
Changes from 24 commits
adb0477
473bf38
5f90e39
f198d87
b5ad12f
9f40ba6
becc594
78e1f19
c57b3f7
575b43b
196ef40
7a177b9
11d71c6
9139768
56edff6
558af9b
4e7799e
7227c45
26d4e5a
a5004fd
920e8c5
5706d09
ab00124
f557807
7114797
feeccdb
c293240
27a8ee7
10a94b5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,7 +26,9 @@ import ( | |
"github.com/pingcap/tidb/parser/model" | ||
"github.com/pingcap/tidb/parser/mysql" | ||
"github.com/pingcap/tidb/planner/util" | ||
"github.com/pingcap/tidb/sessionctx" | ||
"github.com/pingcap/tidb/types" | ||
"github.com/pingcap/tidb/util/chunk" | ||
"github.com/pingcap/tidb/util/logutil" | ||
"github.com/pingcap/tidb/util/ranger" | ||
"go.uber.org/zap" | ||
|
@@ -552,6 +554,7 @@ func (ds *DataSource) generateIndexMergeJSONMVIndexPath(normalPathCnt int, filte | |
|
||
var jsonPath expression.Expression | ||
var vals []expression.Expression | ||
var indexMergeIsIntersection bool | ||
switch sf.FuncName.L { | ||
case ast.JSONMemberOf: // (1 member of a->'$.zip') | ||
jsonPath = sf.GetArgs()[1] | ||
|
@@ -560,10 +563,29 @@ func (ds *DataSource) generateIndexMergeJSONMVIndexPath(normalPathCnt int, filte | |
continue | ||
} | ||
vals = append(vals, v) | ||
case ast.JSONOverlaps: // (json_overlaps(a->'$.zip', '[1, 2, 3]') | ||
continue // TODO: support json_overlaps | ||
case ast.JSONContains: // (json_contains(a->'$.zip', '[1, 2, 3]') | ||
continue // TODO: support json_contains | ||
indexMergeIsIntersection = true | ||
jsonPath = sf.GetArgs()[0] | ||
var ok bool | ||
vals, ok = jsonArrayExpr2Exprs(ds.ctx, sf.GetArgs()[1]) | ||
if !ok { | ||
continue | ||
} | ||
case ast.JSONOverlaps: // (json_overlaps(a->'$.zip', '[1, 2, 3]') | ||
var jsonPathIdx int | ||
if sf.GetArgs()[0].Equal(ds.ctx, targetJSONPath) { | ||
jsonPathIdx = 0 // (json_overlaps(a->'$.zip', '[1, 2, 3]') | ||
} else if sf.GetArgs()[1].Equal(ds.ctx, targetJSONPath) { | ||
jsonPathIdx = 1 // (json_overlaps('[1, 2, 3]', a->'$.zip') | ||
} else { | ||
continue | ||
} | ||
jsonPath = sf.GetArgs()[jsonPathIdx] | ||
var ok bool | ||
vals, ok = jsonArrayExpr2Exprs(ds.ctx, sf.GetArgs()[1-jsonPathIdx]) | ||
if !ok { | ||
continue | ||
} | ||
default: | ||
continue | ||
} | ||
|
@@ -612,12 +634,62 @@ func (ds *DataSource) generateIndexMergeJSONMVIndexPath(normalPathCnt int, filte | |
partialPaths = append(partialPaths, partialPath) | ||
} | ||
indexMergePath := ds.buildIndexMergeOrPath(filters, partialPaths, filterIdx) | ||
indexMergePath.IndexMergeIsIntersection = indexMergeIsIntersection | ||
mvIndexPaths = append(mvIndexPaths, indexMergePath) | ||
} | ||
} | ||
return | ||
} | ||
|
||
// jsonArrayExpr2Exprs converts a JsonArray expression to expression list: cast('[1, 2, 3]' as JSON) --> []expr{1, 2, 3} | ||
func jsonArrayExpr2Exprs(sctx sessionctx.Context, jsonArrayExpr expression.Expression) ([]expression.Expression, bool) { | ||
// only support cast(const as JSON) | ||
jsonCast, ok := jsonArrayExpr.(*expression.ScalarFunction) | ||
if !ok { | ||
return nil, false | ||
} | ||
if jsonCast.FuncName.L != ast.Cast && jsonCast.GetType().EvalType() != types.ETJson { | ||
return nil, false | ||
} | ||
time-and-fate marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if _, isConst := jsonCast.GetArgs()[0].(*expression.Constant); !isConst { | ||
return nil, false | ||
} | ||
|
||
jsonArray, isNull, err := jsonArrayExpr.EvalJSON(sctx, chunk.Row{}) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have two questions about this:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we don't use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Got it. What about the second question? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK, I use |
||
if isNull || err != nil { | ||
return nil, false | ||
} | ||
if jsonArray.TypeCode != types.JSONTypeCodeArray { | ||
single, ok := jsonValue2Expr(jsonArray) // '1' -> []expr{1} | ||
if ok { | ||
return []expression.Expression{single}, true | ||
} | ||
return nil, false | ||
} | ||
var exprs []expression.Expression | ||
for i := 0; i < jsonArray.GetElemCount(); i++ { // '[1, 2, 3]' -> []expr{1, 2, 3} | ||
expr, ok := jsonValue2Expr(jsonArray.ArrayGetElem(i)) | ||
if !ok { | ||
return nil, false | ||
} | ||
exprs = append(exprs, expr) | ||
} | ||
return exprs, true | ||
} | ||
|
||
func jsonValue2Expr(v types.BinaryJSON) (expression.Expression, bool) { | ||
if v.TypeCode != types.JSONTypeCodeInt64 { | ||
// only support INT now | ||
// TODO: support more types | ||
return nil, false | ||
} | ||
val := v.GetInt64() | ||
return &expression.Constant{ | ||
Value: types.NewDatum(val), | ||
RetType: types.NewFieldType(mysql.TypeLonglong), | ||
}, true | ||
} | ||
|
||
func unwrapJSONCast(expr expression.Expression) (expression.Expression, bool) { | ||
if expr == nil { | ||
return nil, false | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -49,6 +49,128 @@ | |
" └─Selection(Probe) 0.00 cop[tikv] lt(test.t.a, 10)", | ||
" └─TableRowIDScan 0.00 cop[tikv] table:t keep order:false, stats:pseudo" | ||
] | ||
}, | ||
{ | ||
"SQL": "select /*+ use_index_merge(t, j0_0) */ * from t where json_contains((j0->'$.path0'), '[1, 2, 3]')", | ||
"Plan": [ | ||
"IndexMerge 0.00 root type: intersection", | ||
"├─IndexRangeScan(Build) 10.00 cop[tikv] table:t, index:j0_0(cast(json_extract(`j0`, _utf8mb4'$.path0') as signed array)) range:[1,1], keep order:false, stats:pseudo", | ||
"├─IndexRangeScan(Build) 10.00 cop[tikv] table:t, index:j0_0(cast(json_extract(`j0`, _utf8mb4'$.path0') as signed array)) range:[2,2], keep order:false, stats:pseudo", | ||
"├─IndexRangeScan(Build) 10.00 cop[tikv] table:t, index:j0_0(cast(json_extract(`j0`, _utf8mb4'$.path0') as signed array)) range:[3,3], keep order:false, stats:pseudo", | ||
"└─TableRowIDScan(Probe) 0.00 cop[tikv] table:t keep order:false, stats:pseudo" | ||
] | ||
}, | ||
{ | ||
"SQL": "select /*+ use_index_merge(t, j0_0) */ * from t where json_overlaps((j0->'$.path0'), '[1, 2, 3]')", | ||
"Plan": [ | ||
"Selection 0.00 root json_overlaps(json_extract(test.t.j0, \"$.path0\"), cast(\"[1, 2, 3]\", json BINARY))", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks like this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The reason is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. YES, but it's another problem and I'll submit another PR to fix it after. Otherwise, this PR will become hard to review. |
||
"└─IndexMerge 0.00 root type: union", | ||
" ├─IndexRangeScan(Build) 10.00 cop[tikv] table:t, index:j0_0(cast(json_extract(`j0`, _utf8mb4'$.path0') as signed array)) range:[1,1], keep order:false, stats:pseudo", | ||
" ├─IndexRangeScan(Build) 10.00 cop[tikv] table:t, index:j0_0(cast(json_extract(`j0`, _utf8mb4'$.path0') as signed array)) range:[2,2], keep order:false, stats:pseudo", | ||
" ├─IndexRangeScan(Build) 10.00 cop[tikv] table:t, index:j0_0(cast(json_extract(`j0`, _utf8mb4'$.path0') as signed array)) range:[3,3], keep order:false, stats:pseudo", | ||
" └─TableRowIDScan(Probe) 0.00 cop[tikv] table:t keep order:false, stats:pseudo" | ||
] | ||
}, | ||
{ | ||
"SQL": "select /*+ use_index_merge(t, j0_0) */ * from t where json_overlaps('[1, 2, 3]', (j0->'$.path0'))", | ||
"Plan": [ | ||
"Selection 0.00 root json_overlaps(cast(\"[1, 2, 3]\", json BINARY), json_extract(test.t.j0, \"$.path0\"))", | ||
"└─IndexMerge 0.00 root type: union", | ||
" ├─IndexRangeScan(Build) 10.00 cop[tikv] table:t, index:j0_0(cast(json_extract(`j0`, _utf8mb4'$.path0') as signed array)) range:[1,1], keep order:false, stats:pseudo", | ||
" ├─IndexRangeScan(Build) 10.00 cop[tikv] table:t, index:j0_0(cast(json_extract(`j0`, _utf8mb4'$.path0') as signed array)) range:[2,2], keep order:false, stats:pseudo", | ||
" ├─IndexRangeScan(Build) 10.00 cop[tikv] table:t, index:j0_0(cast(json_extract(`j0`, _utf8mb4'$.path0') as signed array)) range:[3,3], keep order:false, stats:pseudo", | ||
" └─TableRowIDScan(Probe) 0.00 cop[tikv] table:t keep order:false, stats:pseudo" | ||
] | ||
}, | ||
{ | ||
"SQL": "select /*+ use_index_merge(t, j0_0) */ * from t where json_contains((j0->'$.path0'), '[1, 2, 3]') and a<10", | ||
"Plan": [ | ||
"IndexMerge 0.00 root type: intersection", | ||
"├─IndexRangeScan(Build) 10.00 cop[tikv] table:t, index:j0_0(cast(json_extract(`j0`, _utf8mb4'$.path0') as signed array)) range:[1,1], keep order:false, stats:pseudo", | ||
"├─IndexRangeScan(Build) 10.00 cop[tikv] table:t, index:j0_0(cast(json_extract(`j0`, _utf8mb4'$.path0') as signed array)) range:[2,2], keep order:false, stats:pseudo", | ||
"├─IndexRangeScan(Build) 10.00 cop[tikv] table:t, index:j0_0(cast(json_extract(`j0`, _utf8mb4'$.path0') as signed array)) range:[3,3], keep order:false, stats:pseudo", | ||
"└─Selection(Probe) 0.00 cop[tikv] lt(test.t.a, 10)", | ||
" └─TableRowIDScan 0.00 cop[tikv] table:t keep order:false, stats:pseudo" | ||
] | ||
}, | ||
{ | ||
"SQL": "select /*+ use_index_merge(t, j0_0) */ * from t where json_overlaps((j0->'$.path0'), '[1, 2, 3]') and a<10", | ||
"Plan": [ | ||
"Selection 0.00 root json_overlaps(json_extract(test.t.j0, \"$.path0\"), cast(\"[1, 2, 3]\", json BINARY))", | ||
"└─IndexMerge 0.00 root type: union", | ||
" ├─IndexRangeScan(Build) 10.00 cop[tikv] table:t, index:j0_0(cast(json_extract(`j0`, _utf8mb4'$.path0') as signed array)) range:[1,1], keep order:false, stats:pseudo", | ||
" ├─IndexRangeScan(Build) 10.00 cop[tikv] table:t, index:j0_0(cast(json_extract(`j0`, _utf8mb4'$.path0') as signed array)) range:[2,2], keep order:false, stats:pseudo", | ||
" ├─IndexRangeScan(Build) 10.00 cop[tikv] table:t, index:j0_0(cast(json_extract(`j0`, _utf8mb4'$.path0') as signed array)) range:[3,3], keep order:false, stats:pseudo", | ||
" └─Selection(Probe) 0.00 cop[tikv] lt(test.t.a, 10)", | ||
" └─TableRowIDScan 0.00 cop[tikv] table:t keep order:false, stats:pseudo" | ||
] | ||
}, | ||
{ | ||
"SQL": "select /*+ use_index_merge(t, j0_0) */ * from t where json_overlaps('[1, 2, 3]', (j0->'$.path0')) and a<10", | ||
"Plan": [ | ||
"Selection 0.00 root json_overlaps(cast(\"[1, 2, 3]\", json BINARY), json_extract(test.t.j0, \"$.path0\"))", | ||
"└─IndexMerge 0.00 root type: union", | ||
" ├─IndexRangeScan(Build) 10.00 cop[tikv] table:t, index:j0_0(cast(json_extract(`j0`, _utf8mb4'$.path0') as signed array)) range:[1,1], keep order:false, stats:pseudo", | ||
" ├─IndexRangeScan(Build) 10.00 cop[tikv] table:t, index:j0_0(cast(json_extract(`j0`, _utf8mb4'$.path0') as signed array)) range:[2,2], keep order:false, stats:pseudo", | ||
" ├─IndexRangeScan(Build) 10.00 cop[tikv] table:t, index:j0_0(cast(json_extract(`j0`, _utf8mb4'$.path0') as signed array)) range:[3,3], keep order:false, stats:pseudo", | ||
" └─Selection(Probe) 0.00 cop[tikv] lt(test.t.a, 10)", | ||
" └─TableRowIDScan 0.00 cop[tikv] table:t keep order:false, stats:pseudo" | ||
] | ||
}, | ||
{ | ||
"SQL": "select /*+ use_index_merge(t, j0_0) */ * from t where json_contains((j0->'$.path0'), '1')", | ||
"Plan": [ | ||
"IndexMerge 0.00 root type: intersection", | ||
"├─IndexRangeScan(Build) 10.00 cop[tikv] table:t, index:j0_0(cast(json_extract(`j0`, _utf8mb4'$.path0') as signed array)) range:[1,1], keep order:false, stats:pseudo", | ||
"└─TableRowIDScan(Probe) 0.00 cop[tikv] table:t keep order:false, stats:pseudo" | ||
] | ||
}, | ||
{ | ||
"SQL": "select /*+ use_index_merge(t, j0_0) */ * from t where json_overlaps((j0->'$.path0'), '1')", | ||
"Plan": [ | ||
"Selection 0.00 root json_overlaps(json_extract(test.t.j0, \"$.path0\"), cast(\"1\", json BINARY))", | ||
"└─IndexMerge 0.00 root type: union", | ||
" ├─IndexRangeScan(Build) 10.00 cop[tikv] table:t, index:j0_0(cast(json_extract(`j0`, _utf8mb4'$.path0') as signed array)) range:[1,1], keep order:false, stats:pseudo", | ||
" └─TableRowIDScan(Probe) 0.00 cop[tikv] table:t keep order:false, stats:pseudo" | ||
] | ||
}, | ||
{ | ||
"SQL": "select /*+ use_index_merge(t, j0_0) */ * from t where json_overlaps('1', (j0->'$.path0'))", | ||
"Plan": [ | ||
"Selection 0.00 root json_overlaps(cast(\"1\", json BINARY), json_extract(test.t.j0, \"$.path0\"))", | ||
"└─IndexMerge 0.00 root type: union", | ||
" ├─IndexRangeScan(Build) 10.00 cop[tikv] table:t, index:j0_0(cast(json_extract(`j0`, _utf8mb4'$.path0') as signed array)) range:[1,1], keep order:false, stats:pseudo", | ||
" └─TableRowIDScan(Probe) 0.00 cop[tikv] table:t keep order:false, stats:pseudo" | ||
] | ||
}, | ||
{ | ||
"SQL": "select /*+ use_index_merge(t, j0_0) */ * from t where json_contains((j0->'$.path0'), '1') and a<10", | ||
"Plan": [ | ||
"IndexMerge 0.00 root type: intersection", | ||
"├─IndexRangeScan(Build) 10.00 cop[tikv] table:t, index:j0_0(cast(json_extract(`j0`, _utf8mb4'$.path0') as signed array)) range:[1,1], keep order:false, stats:pseudo", | ||
"└─Selection(Probe) 0.00 cop[tikv] lt(test.t.a, 10)", | ||
" └─TableRowIDScan 0.00 cop[tikv] table:t keep order:false, stats:pseudo" | ||
] | ||
}, | ||
{ | ||
"SQL": "select /*+ use_index_merge(t, j0_0) */ * from t where json_overlaps((j0->'$.path0'), '1') and a<10", | ||
"Plan": [ | ||
"Selection 0.00 root json_overlaps(json_extract(test.t.j0, \"$.path0\"), cast(\"1\", json BINARY))", | ||
"└─IndexMerge 0.00 root type: union", | ||
" ├─IndexRangeScan(Build) 10.00 cop[tikv] table:t, index:j0_0(cast(json_extract(`j0`, _utf8mb4'$.path0') as signed array)) range:[1,1], keep order:false, stats:pseudo", | ||
" └─Selection(Probe) 0.00 cop[tikv] lt(test.t.a, 10)", | ||
" └─TableRowIDScan 0.00 cop[tikv] table:t keep order:false, stats:pseudo" | ||
] | ||
}, | ||
{ | ||
"SQL": "select /*+ use_index_merge(t, j0_0) */ * from t where json_overlaps('1', (j0->'$.path0')) and a<10", | ||
"Plan": [ | ||
"Selection 0.00 root json_overlaps(cast(\"1\", json BINARY), json_extract(test.t.j0, \"$.path0\"))", | ||
"└─IndexMerge 0.00 root type: union", | ||
" ├─IndexRangeScan(Build) 10.00 cop[tikv] table:t, index:j0_0(cast(json_extract(`j0`, _utf8mb4'$.path0') as signed array)) range:[1,1], keep order:false, stats:pseudo", | ||
" └─Selection(Probe) 0.00 cop[tikv] lt(test.t.a, 10)", | ||
" └─TableRowIDScan 0.00 cop[tikv] table:t keep order:false, stats:pseudo" | ||
] | ||
} | ||
] | ||
}, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are using
buildIndexMergeOrPath
to handle bothOR
andAND
cases. Is that expected and correct?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now
indexMergeIsIntersection
only works forast.JSONContains
. Is it any problem here?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From its name,
buildIndexMergeOrPath
is for theOR
, notAND
.