diff --git a/pkg/sql/logictest/testdata/logic_test/inverted_index b/pkg/sql/logictest/testdata/logic_test/inverted_index index f18dc6119a53..2a32af03f97a 100644 --- a/pkg/sql/logictest/testdata/logic_test/inverted_index +++ b/pkg/sql/logictest/testdata/logic_test/inverted_index @@ -806,7 +806,100 @@ INSERT INTO f VALUES (36, '[[]]'), (37, '[{"a": [0, "b"]}, null, 1]'), (38, '[[0, 1, 2], {"b": "c"}]'), - (39, '[[0, [1, 2]]]') + (39, '[[0, [1, 2]]]'), + (40, '[[0, 1, 2]]'), + (41, '[[{"a": {"b": []}}]]') + +query T +SELECT j FROM f@i WHERE j->0 @> '[0, 1, 2, 3]' +---- + +query T +SELECT j FROM f@i WHERE j->0 @> '[0]' +---- +[[0, 1, 2], {"b": "c"}] +[[0, [1, 2]]] +[[0, 1, 2]] + +query T +SELECT j FROM f@i WHERE j->0->1 @> '[1, 2, 3]' +---- + +query T +SELECT j FROM f@i WHERE j->0->1 @> '[1, 2]' +---- +[[0, [1, 2]]] + +query T +SELECT j FROM f@i WHERE j->0 @> '{"a": {}}' +---- +[{"a": {"b": "c"}}, "d", "e"] + +query T +SELECT j FROM f@i WHERE j->0 @> '{"a": {"b": "c"}}' +---- +[{"a": {"b": "c"}}, "d", "e"] + +query T +SELECT j FROM f@i WHERE j->0->1 @> '{"a": {"b": []}}' +---- + +query T +SELECT j FROM f@i WHERE j->0->0 @> '{"a": {"b": []}}' +---- +[[{"a": {"b": []}}]] + +query T +SELECT j FROM f@i WHERE j->'a'->0 @> '1' +---- +{"a": [1, 2]} +{"a": [1, 2, null]} + +query T +SELECT j FROM f@i WHERE j->0->'a' @> '{"b": "c"}' +---- +[{"a": {"b": "c"}}, "d", "e"] + +query T +SELECT j FROM f@i WHERE j->0 <@ '[1, 2, 3]' +---- +[1, 2, {"b": "c"}] +[[]] + +query T +SELECT j FROM f@i WHERE j->1 <@ '[1, 2, 3]' +---- +[1, 2, {"b": "c"}] + +query T +SELECT j FROM f@i WHERE j->0->0 <@ '[1, 2, 3]' +---- + +query T +SELECT j FROM f@i WHERE j->2 <@ '["d", "e"]' +---- +[{"a": {"b": "c"}}, "d", "e"] + +query T +SELECT j FROM f@i WHERE j->0 <@ '{"a": {"b": "c"}}' +---- +[{"a": {"b": "c"}}, "d", "e"] + +query T +SELECT j FROM f@i WHERE j->0 <@ '["a", "b"]' +---- +["a"] +[[]] + +query T +SELECT j FROM f@i WHERE j->0 <@ '"a"' +---- +["a"] + +query T +SELECT j FROM f@i WHERE j->0 <@ '1' +---- +[1, 2, {"b": "c"}] query T SELECT j FROM f@i WHERE j->0 = '[]' ORDER BY k @@ -873,6 +966,7 @@ SELECT j FROM f@i WHERE j->0->0 = '0' ORDER BY k ---- [[0, 1, 2], {"b": "c"}] [[0, [1, 2]]] +[[0, 1, 2]] query T SELECT j FROM f@i WHERE j->0->1 = '[1, 2]' ORDER BY k diff --git a/pkg/sql/opt/exec/execbuilder/testdata/inverted_index b/pkg/sql/opt/exec/execbuilder/testdata/inverted_index index ac6629c6055e..5c57f23e3238 100644 --- a/pkg/sql/opt/exec/execbuilder/testdata/inverted_index +++ b/pkg/sql/opt/exec/execbuilder/testdata/inverted_index @@ -1484,7 +1484,7 @@ vectorized: true right columns: (a, b_inverted_key) right fixed values: 1 column -# Inverted indices won't be used for queries of the form +# Inverted indices will be used for queries of the form # b->0 @> '{"b": "c"}' query T EXPLAIN (VERBOSE) SELECT b FROM d WHERE b->0 @> '{"b": "c"}' @@ -1492,18 +1492,19 @@ EXPLAIN (VERBOSE) SELECT b FROM d WHERE b->0 @> '{"b": "c"}' distribution: local vectorized: true · -• filter +• index join │ columns: (b) │ estimated row count: 111 (missing stats) -│ filter: (b->0) @> '{"b": "c"}' +│ table: d@d_pkey +│ key columns: a │ └── • scan - columns: (b) - estimated row count: 1,000 (missing stats) - table: d@d_pkey - spans: FULL SCAN + columns: (a) + estimated row count: 111 (missing stats) + table: d@foo_inv + spans: /Arr/"b"/"c"-/Arr/"b"/"c"/PrefixEnd -# Inverted indices won't be used for queries of the form +# Inverted indices will be used for queries of the form # b->0 <@ '{"b": "c"}' query T EXPLAIN (VERBOSE) SELECT b FROM d WHERE b->0 <@ '{"b": "c"}' @@ -1516,11 +1517,178 @@ vectorized: true │ estimated row count: 333 (missing stats) │ filter: (b->0) <@ '{"b": "c"}' │ -└── • scan - columns: (b) - estimated row count: 1,000 (missing stats) - table: d@d_pkey - spans: FULL SCAN +└── • index join + │ columns: (b) + │ estimated row count: 111 (missing stats) + │ table: d@d_pkey + │ key columns: a + │ + └── • project + │ columns: (a) + │ + └── • inverted filter + │ columns: (a, b_inverted_key) + │ estimated row count: 111 (missing stats) + │ inverted column: b_inverted_key + │ num spans: 3 + │ + └── • scan + columns: (a, b_inverted_key) + estimated row count: 111 (missing stats) + table: d@foo_inv + spans: /[]-/{} /Arr/{}-/Arr/{}/PrefixEnd /Arr/"b"/"c"-/Arr/"b"/"c"/PrefixEnd + +query T +EXPLAIN (VERBOSE) SELECT b FROM d WHERE b->0 <@ '1' +---- +distribution: local +vectorized: true +· +• filter +│ columns: (b) +│ estimated row count: 333 (missing stats) +│ filter: (b->0) <@ '1' +│ +└── • index join + │ columns: (b) + │ estimated row count: 111 (missing stats) + │ table: d@d_pkey + │ key columns: a + │ + └── • project + │ columns: (a) + │ + └── • inverted filter + │ columns: (a, b_inverted_key) + │ estimated row count: 111 (missing stats) + │ inverted column: b_inverted_key + │ num spans: 3 + │ + └── • scan + columns: (a, b_inverted_key) + estimated row count: 111 (missing stats) + table: d@foo_inv + spans: /1-/1/PrefixEnd /[]-/{} /Arr/1-/Arr/1/PrefixEnd + +query T +EXPLAIN (VERBOSE) SELECT b FROM d WHERE b->0 @> '1' +---- +distribution: local +vectorized: true +· +• index join +│ columns: (b) +│ estimated row count: 111 (missing stats) +│ table: d@d_pkey +│ key columns: a +│ +└── • project + │ columns: (a) + │ + └── • inverted filter + │ columns: (a, b_inverted_key) + │ estimated row count: 111 (missing stats) + │ inverted column: b_inverted_key + │ num spans: 2 + │ + └── • scan + columns: (a, b_inverted_key) + estimated row count: 111 (missing stats) + table: d@foo_inv + spans: /Arr/1-/Arr/1/PrefixEnd /Arr/Arr/1-/Arr/Arr/1/PrefixEnd + +query T +EXPLAIN (VERBOSE) SELECT b FROM d WHERE b->0 @> '[1, 2]' +---- +distribution: local +vectorized: true +· +• project +│ columns: (b) +│ +└── • lookup join (inner) + │ columns: (a, b) + │ estimated row count: 12 (missing stats) + │ table: d@d_pkey + │ equality: (a) = (a) + │ equality cols are key + │ pred: (b->0) @> '[1, 2]' + │ + └── • project + │ columns: (a) + │ + └── • zigzag join + columns: (a, b_inverted_key, a, b_inverted_key) + estimated row count: 12 (missing stats) + left table: d@foo_inv + left columns: (a, b_inverted_key) + left fixed values: 1 column + right table: d@foo_inv + right columns: (a, b_inverted_key) + right fixed values: 1 column + +query T +EXPLAIN (VERBOSE) SELECT b FROM d WHERE b->0 <@ '[1, 2]' +---- +distribution: local +vectorized: true +· +• filter +│ columns: (b) +│ estimated row count: 333 (missing stats) +│ filter: (b->0) <@ '[1, 2]' +│ +└── • index join + │ columns: (b) + │ estimated row count: 111 (missing stats) + │ table: d@d_pkey + │ key columns: a + │ + └── • project + │ columns: (a) + │ + └── • inverted filter + │ columns: (a, b_inverted_key) + │ estimated row count: 111 (missing stats) + │ inverted column: b_inverted_key + │ num spans: 8 + │ + └── • scan + columns: (a, b_inverted_key) + estimated row count: 111 (missing stats) + table: d@foo_inv + spans: /1-/1/PrefixEnd /2-/2/PrefixEnd /[]-/{} /Arr/1-/Arr/1/PrefixEnd /Arr/2-/Arr/2/PrefixEnd /Arr/[]-/Arr/{} /Arr/Arr/1-/Arr/Arr/1/PrefixEnd /Arr/Arr/2-/Arr/Arr/2/PrefixEnd + +query T +EXPLAIN (VERBOSE) SELECT b FROM d WHERE (b->0 @> '[1, 2]') AND (b->1 <@ '[1]') +---- +distribution: local +vectorized: true +· +• project +│ columns: (b) +│ +└── • lookup join (inner) + │ columns: (a, b) + │ estimated row count: 4 (missing stats) + │ table: d@d_pkey + │ equality: (a) = (a) + │ equality cols are key + │ pred: ((b->0) @> '[1, 2]') AND ((b->1) <@ '[1]') + │ + └── • project + │ columns: (a) + │ + └── • zigzag join + columns: (a, b_inverted_key, a, b_inverted_key) + estimated row count: 12 (missing stats) + left table: d@foo_inv + left columns: (a, b_inverted_key) + left fixed values: 1 column + right table: d@foo_inv + right columns: (a, b_inverted_key) + right fixed values: 1 column + # Stats reflect the following, with some histogram buckets removed: # insert into d select g, '[1,2]' from generate_series(1,1000) g(g); diff --git a/pkg/sql/opt/invertedidx/json_array.go b/pkg/sql/opt/invertedidx/json_array.go index ff91d93ad594..6a17f70bec8b 100644 --- a/pkg/sql/opt/invertedidx/json_array.go +++ b/pkg/sql/opt/invertedidx/json_array.go @@ -612,14 +612,6 @@ func (j *jsonOrArrayFilterPlanner) extractJSONFetchValContainsCondition( return inverted.NonInvertedColExpression{} } - // Not using inverted indices, yet, for filters of the form - // j->0 @> '{"b": "c"}' or j->0 <@ '{"b": "c"}' - for i := range keys { - if _, ok := keys[i].(*tree.DString); !ok { - return inverted.NonInvertedColExpression{} - } - } - // Build a new JSON object with the collected keys and val. obj := buildObject(keys, val.JSON) @@ -647,6 +639,29 @@ func (j *jsonOrArrayFilterPlanner) extractJSONFetchValContainsCondition( invertedExpr = inverted.Or(invertedExpr, expr) } } + + // If a key is of the type DInt, the InvertedExpression generated is + // not tight. This is because key encodings for JSON arrays don't + // contain the respective index positions for each of their elements. + // A JSON array of the form ["a", 31] will have the following encoding + // into an index key: + // + // 1/2/arr/a/pk1 + // 1/2/arr/31/pk1 + // + // where arr is an ARRAY type tag used to indicate that the next key is + // part of an array, 1 is the table id, 2 is the inverted index id and + // pk is a primary key of a row in the table. Since the array + // elements do not have their respective indices stored in + // the encoding, the original filter needs to be applied after the initial + // scan. + for i := range keys { + if _, ok := keys[i].(*tree.DInt); ok { + invertedExpr.SetNotTight() + break + } + } + return invertedExpr } diff --git a/pkg/sql/opt/invertedidx/json_array_test.go b/pkg/sql/opt/invertedidx/json_array_test.go index 542b3d869ec3..8f99eb117ffd 100644 --- a/pkg/sql/opt/invertedidx/json_array_test.go +++ b/pkg/sql/opt/invertedidx/json_array_test.go @@ -546,6 +546,62 @@ func TestTryFilterJsonOrArrayIndex(t *testing.T) { unique: false, remainingFilters: "j->'a'->0 = '1' OR j->'a'->1 = '1'", }, + { + filters: "j->0 @> '2'", + indexOrd: jsonOrd, + ok: true, + tight: false, + unique: false, + remainingFilters: "j->0 @> '2'", + }, + { + filters: "j->0 <@ '2'", + indexOrd: jsonOrd, + ok: true, + tight: false, + unique: false, + remainingFilters: "j->0 <@ '2'", + }, + { + filters: "j->0 @> '[1,2]'", + indexOrd: jsonOrd, + ok: true, + tight: false, + unique: true, + remainingFilters: "j->0 @> '[1,2]'", + }, + { + filters: "j->0 <@ '[1,2]'", + indexOrd: jsonOrd, + ok: true, + tight: false, + unique: false, + remainingFilters: "j->0 <@ '[1,2]'", + }, + { + filters: `j->0 <@ '{"b": "c"}'`, + indexOrd: jsonOrd, + ok: true, + tight: false, + unique: false, + remainingFilters: `j->0 <@ '{"b": "c"}'`, + }, + { + filters: `j->0 @> '{"b": "c"}'`, + indexOrd: jsonOrd, + ok: true, + tight: false, + unique: true, + remainingFilters: `j->0 @> '{"b": "c"}'`, + }, + { + filters: `j->0 @> '[1,2]'`, + indexOrd: jsonOrd, + ok: true, + tight: false, + unique: true, + remainingFilters: `j->0 @> '[1,2]'`, + }, { // The inner most expression is not a fetch val expression with an // indexed column on the left. diff --git a/pkg/sql/opt/xform/testdata/rules/select b/pkg/sql/opt/xform/testdata/rules/select index 06b9de2b5479..be697125fe02 100644 --- a/pkg/sql/opt/xform/testdata/rules/select +++ b/pkg/sql/opt/xform/testdata/rules/select @@ -2897,6 +2897,57 @@ project └── filters └── (j:4->0) = '{"a": "b"}' [outer=(4), immutable] +# Generate an inverted scan when right side of the equality is an array. +opt expect=GenerateInvertedIndexScans +SELECT k FROM b WHERE j->'a' = '["b"]' +---- +project + ├── columns: k:1!null + ├── immutable + ├── key: (1) + └── select + ├── columns: k:1!null j:4 + ├── immutable + ├── key: (1) + ├── fd: (1)-->(4) + ├── index-join b + │ ├── columns: k:1!null j:4 + │ ├── key: (1) + │ ├── fd: (1)-->(4) + │ └── scan b@j_inv_idx + │ ├── columns: k:1!null + │ ├── inverted constraint: /7/1 + │ │ └── spans: ["7a\x00\x02\x00\x03\x00\x01\x12b\x00\x01", "7a\x00\x02\x00\x03\x00\x01\x12b\x00\x01"] + │ └── key: (1) + └── filters + └── (j:4->'a') = '["b"]' [outer=(4), immutable] + +# Generate an inverted scan when right side of the equality is an object. +opt expect=GenerateInvertedIndexScans +SELECT k FROM b WHERE j->'a' = '{"b": "c"}' +---- +project + ├── columns: k:1!null + ├── immutable + ├── key: (1) + └── select + ├── columns: k:1!null j:4 + ├── immutable + ├── key: (1) + ├── fd: (1)-->(4) + ├── index-join b + │ ├── columns: k:1!null j:4 + │ ├── key: (1) + │ ├── fd: (1)-->(4) + │ └── scan b@j_inv_idx + │ ├── columns: k:1!null + │ ├── inverted constraint: /7/1 + │ │ └── spans: ["7a\x00\x02b\x00\x01\x12c\x00\x01", "7a\x00\x02b\x00\x01\x12c\x00\x01"] + │ └── key: (1) + └── filters + └── (j:4->'a') = '{"b": "c"}' [outer=(4), immutable] + + # Generate an inverted scan when the index of the fetch val operator is # an integer and the right side to the equality is a JSON # array. Since the inverted expression is not tight, a @@ -2924,9 +2975,9 @@ project └── filters └── (j:4->0) = '[1, 2]' [outer=(4), immutable] -# Do not generate an inverted scan when the index of the fetch val operator -# is not a string and there is a containment operator. -opt expect-not=GenerateInvertedIndexScans +# Generate an inverted scan when the index of the fetch val operator +# is an integer and there is a containment operator with a JSON object. +opt expect=GenerateInvertedIndexScans SELECT k FROM b WHERE j->0 @> '{"b": "c"}' ---- project @@ -2938,16 +2989,21 @@ project ├── immutable ├── key: (1) ├── fd: (1)-->(4) - ├── scan b + ├── index-join b │ ├── columns: k:1!null j:4 │ ├── key: (1) - │ └── fd: (1)-->(4) + │ ├── fd: (1)-->(4) + │ └── scan b@j_inv_idx + │ ├── columns: k:1!null + │ ├── inverted constraint: /7/1 + │ │ └── spans: ["7\x00\x03b\x00\x01\x12c\x00\x01", "7\x00\x03b\x00\x01\x12c\x00\x01"] + │ └── key: (1) └── filters └── (j:4->0) @> '{"b": "c"}' [outer=(4), immutable] -# Do not generate an inverted scan when the index of the fetch val operator -# is not a string and there is a containment operator. -opt expect-not=GenerateInvertedIndexScans +# Generate an inverted scan when the index of the fetch val operator +# is an integer and there is a containment operator with a JSON object. +opt expect=GenerateInvertedIndexScans SELECT k FROM b WHERE j->0 <@ '{"b": "c"}' ---- project @@ -2959,17 +3015,110 @@ project ├── immutable ├── key: (1) ├── fd: (1)-->(4) - ├── scan b + ├── index-join b │ ├── columns: k:1!null j:4 │ ├── key: (1) - │ └── fd: (1)-->(4) + │ ├── fd: (1)-->(4) + │ └── inverted-filter + │ ├── columns: k:1!null + │ ├── inverted expression: /7 + │ │ ├── tight: false, unique: false + │ │ └── union spans + │ │ ├── ["7\x00\x018", "7\x00\x018"] + │ │ ├── ["7\x00\x03\x00\x019", "7\x00\x03\x00\x019"] + │ │ └── ["7\x00\x03b\x00\x01\x12c\x00\x01", "7\x00\x03b\x00\x01\x12c\x00\x01"] + │ ├── key: (1) + │ └── scan b@j_inv_idx + │ ├── columns: k:1!null j_inverted_key:7!null + │ ├── inverted constraint: /7/1 + │ │ └── spans + │ │ ├── ["7\x00\x018", "7\x00\x018"] + │ │ ├── ["7\x00\x03\x00\x019", "7\x00\x03\x00\x019"] + │ │ └── ["7\x00\x03b\x00\x01\x12c\x00\x01", "7\x00\x03b\x00\x01\x12c\x00\x01"] + │ ├── key: (1) + │ └── fd: (1)-->(7) └── filters └── (j:4->0) <@ '{"b": "c"}' [outer=(4), immutable] +# Generate an inverted scan when the index of the fetch val operator +# is an integer and there is a containment operator with a JSON array. +opt expect=GenerateInvertedIndexScans +SELECT k FROM b WHERE j->0 <@ '[1, 2]' +---- +project + ├── columns: k:1!null + ├── immutable + ├── key: (1) + └── select + ├── columns: k:1!null j:4 + ├── immutable + ├── key: (1) + ├── fd: (1)-->(4) + ├── index-join b + │ ├── columns: k:1!null j:4 + │ ├── key: (1) + │ ├── fd: (1)-->(4) + │ └── inverted-filter + │ ├── columns: k:1!null + │ ├── inverted expression: /7 + │ │ ├── tight: false, unique: false + │ │ └── union spans + │ │ ├── ["7\x00\x01*\x02\x00", "7\x00\x01*\x02\x00"] + │ │ ├── ["7\x00\x01*\x04\x00", "7\x00\x01*\x04\x00"] + │ │ ├── ["7\x00\x018", "7\x00\x018"] + │ │ ├── ["7\x00\x03\x00\x01*\x02\x00", "7\x00\x03\x00\x01*\x02\x00"] + │ │ ├── ["7\x00\x03\x00\x01*\x04\x00", "7\x00\x03\x00\x01*\x04\x00"] + │ │ ├── ["7\x00\x03\x00\x018", "7\x00\x03\x00\x018"] + │ │ ├── ["7\x00\x03\x00\x03\x00\x01*\x02\x00", "7\x00\x03\x00\x03\x00\x01*\x02\x00"] + │ │ └── ["7\x00\x03\x00\x03\x00\x01*\x04\x00", "7\x00\x03\x00\x03\x00\x01*\x04\x00"] + │ ├── key: (1) + │ └── scan b@j_inv_idx + │ ├── columns: k:1!null j_inverted_key:7!null + │ ├── inverted constraint: /7/1 + │ │ └── spans + │ │ ├── ["7\x00\x01*\x02\x00", "7\x00\x01*\x02\x00"] + │ │ ├── ["7\x00\x01*\x04\x00", "7\x00\x01*\x04\x00"] + │ │ ├── ["7\x00\x018", "7\x00\x018"] + │ │ ├── ["7\x00\x03\x00\x01*\x02\x00", "7\x00\x03\x00\x01*\x02\x00"] + │ │ ├── ["7\x00\x03\x00\x01*\x04\x00", "7\x00\x03\x00\x01*\x04\x00"] + │ │ ├── ["7\x00\x03\x00\x018", "7\x00\x03\x00\x018"] + │ │ ├── ["7\x00\x03\x00\x03\x00\x01*\x02\x00", "7\x00\x03\x00\x03\x00\x01*\x02\x00"] + │ │ └── ["7\x00\x03\x00\x03\x00\x01*\x04\x00", "7\x00\x03\x00\x03\x00\x01*\x04\x00"] + │ ├── key: (1) + │ └── fd: (1)-->(7) + └── filters + └── (j:4->0) <@ '[1, 2]' [outer=(4), immutable] -# Generate an inverted scan when right side of the equality is an array. +# Generate an inverted scan when the index of the fetch val operator +# is an integer and there is a containment operator with a JSON array. opt expect=GenerateInvertedIndexScans -SELECT k FROM b WHERE j->'a' = '["b"]' +SELECT k FROM b WHERE j->0 @> '[1, 2]' +---- +project + ├── columns: k:1!null + ├── immutable + ├── key: (1) + └── inner-join (lookup b) + ├── columns: k:1!null j:4 + ├── key columns: [1] = [1] + ├── lookup columns are key + ├── immutable + ├── key: (1) + ├── fd: (1)-->(4) + ├── inner-join (zigzag b@j_inv_idx b@j_inv_idx) + │ ├── columns: k:1!null + │ ├── eq columns: [1] = [1] + │ ├── left fixed columns: [7] = ['\x370003000300012a0200'] + │ ├── right fixed columns: [7] = ['\x370003000300012a0400'] + │ └── filters (true) + └── filters + └── (j:4->0) @> '[1, 2]' [outer=(4), immutable] + + +# Generate an inverted scan when the index of the fetch val operator +# is an integer and there is a containment operator with a JSON string. +opt expect=GenerateInvertedIndexScans +SELECT k FROM b WHERE j->0 @> '"a"' ---- project ├── columns: k:1!null @@ -2984,17 +3133,29 @@ project │ ├── columns: k:1!null j:4 │ ├── key: (1) │ ├── fd: (1)-->(4) - │ └── scan b@j_inv_idx + │ └── inverted-filter │ ├── columns: k:1!null - │ ├── inverted constraint: /7/1 - │ │ └── spans: ["7a\x00\x02\x00\x03\x00\x01\x12b\x00\x01", "7a\x00\x02\x00\x03\x00\x01\x12b\x00\x01"] - │ └── key: (1) + │ ├── inverted expression: /7 + │ │ ├── tight: false, unique: false + │ │ └── union spans + │ │ ├── ["7\x00\x03\x00\x01\x12a\x00\x01", "7\x00\x03\x00\x01\x12a\x00\x01"] + │ │ └── ["7\x00\x03\x00\x03\x00\x01\x12a\x00\x01", "7\x00\x03\x00\x03\x00\x01\x12a\x00\x01"] + │ ├── key: (1) + │ └── scan b@j_inv_idx + │ ├── columns: k:1!null j_inverted_key:7!null + │ ├── inverted constraint: /7/1 + │ │ └── spans + │ │ ├── ["7\x00\x03\x00\x01\x12a\x00\x01", "7\x00\x03\x00\x01\x12a\x00\x01"] + │ │ └── ["7\x00\x03\x00\x03\x00\x01\x12a\x00\x01", "7\x00\x03\x00\x03\x00\x01\x12a\x00\x01"] + │ ├── key: (1) + │ └── fd: (1)-->(7) └── filters - └── (j:4->'a') = '["b"]' [outer=(4), immutable] + └── (j:4->0) @> '"a"' [outer=(4), immutable] -# Generate an inverted scan when right side of the equality is an object. +# Generate an inverted scan when the index of the fetch val operator +# is an integer and there is a containment operator with a JSON string. opt expect=GenerateInvertedIndexScans -SELECT k FROM b WHERE j->'a' = '{"b": "c"}' +SELECT k FROM b WHERE j->0 <@ '"a"' ---- project ├── columns: k:1!null @@ -3009,13 +3170,102 @@ project │ ├── columns: k:1!null j:4 │ ├── key: (1) │ ├── fd: (1)-->(4) - │ └── scan b@j_inv_idx + │ └── inverted-filter │ ├── columns: k:1!null - │ ├── inverted constraint: /7/1 - │ │ └── spans: ["7a\x00\x02b\x00\x01\x12c\x00\x01", "7a\x00\x02b\x00\x01\x12c\x00\x01"] - │ └── key: (1) + │ ├── inverted expression: /7 + │ │ ├── tight: false, unique: false + │ │ └── union spans + │ │ ├── ["7\x00\x01\x12a\x00\x01", "7\x00\x01\x12a\x00\x01"] + │ │ ├── ["7\x00\x018", "7\x00\x018"] + │ │ └── ["7\x00\x03\x00\x01\x12a\x00\x01", "7\x00\x03\x00\x01\x12a\x00\x01"] + │ ├── key: (1) + │ └── scan b@j_inv_idx + │ ├── columns: k:1!null j_inverted_key:7!null + │ ├── inverted constraint: /7/1 + │ │ └── spans + │ │ ├── ["7\x00\x01\x12a\x00\x01", "7\x00\x01\x12a\x00\x01"] + │ │ ├── ["7\x00\x018", "7\x00\x018"] + │ │ └── ["7\x00\x03\x00\x01\x12a\x00\x01", "7\x00\x03\x00\x01\x12a\x00\x01"] + │ ├── key: (1) + │ └── fd: (1)-->(7) └── filters - └── (j:4->'a') = '{"b": "c"}' [outer=(4), immutable] + └── (j:4->0) <@ '"a"' [outer=(4), immutable] + +# Generate an inverted scan when the index of the fetch val operator +# is an integer and there is a containment operator with a JSON integer. +opt expect=GenerateInvertedIndexScans +SELECT k FROM b WHERE j->0 @> '1' +---- +project + ├── columns: k:1!null + ├── immutable + ├── key: (1) + └── select + ├── columns: k:1!null j:4 + ├── immutable + ├── key: (1) + ├── fd: (1)-->(4) + ├── index-join b + │ ├── columns: k:1!null j:4 + │ ├── key: (1) + │ ├── fd: (1)-->(4) + │ └── inverted-filter + │ ├── columns: k:1!null + │ ├── inverted expression: /7 + │ │ ├── tight: false, unique: false + │ │ └── union spans + │ │ ├── ["7\x00\x03\x00\x01*\x02\x00", "7\x00\x03\x00\x01*\x02\x00"] + │ │ └── ["7\x00\x03\x00\x03\x00\x01*\x02\x00", "7\x00\x03\x00\x03\x00\x01*\x02\x00"] + │ ├── key: (1) + │ └── scan b@j_inv_idx + │ ├── columns: k:1!null j_inverted_key:7!null + │ ├── inverted constraint: /7/1 + │ │ └── spans + │ │ ├── ["7\x00\x03\x00\x01*\x02\x00", "7\x00\x03\x00\x01*\x02\x00"] + │ │ └── ["7\x00\x03\x00\x03\x00\x01*\x02\x00", "7\x00\x03\x00\x03\x00\x01*\x02\x00"] + │ ├── key: (1) + │ └── fd: (1)-->(7) + └── filters + └── (j:4->0) @> '1' [outer=(4), immutable] + +# Generate an inverted scan when the index of the fetch val operator +# is an integer and there is a containment operator with a JSON integer. +opt expect=GenerateInvertedIndexScans +SELECT k FROM b WHERE j->0 <@ '1' +---- +project + ├── columns: k:1!null + ├── immutable + ├── key: (1) + └── select + ├── columns: k:1!null j:4 + ├── immutable + ├── key: (1) + ├── fd: (1)-->(4) + ├── index-join b + │ ├── columns: k:1!null j:4 + │ ├── key: (1) + │ ├── fd: (1)-->(4) + │ └── inverted-filter + │ ├── columns: k:1!null + │ ├── inverted expression: /7 + │ │ ├── tight: false, unique: false + │ │ └── union spans + │ │ ├── ["7\x00\x01*\x02\x00", "7\x00\x01*\x02\x00"] + │ │ ├── ["7\x00\x018", "7\x00\x018"] + │ │ └── ["7\x00\x03\x00\x01*\x02\x00", "7\x00\x03\x00\x01*\x02\x00"] + │ ├── key: (1) + │ └── scan b@j_inv_idx + │ ├── columns: k:1!null j_inverted_key:7!null + │ ├── inverted constraint: /7/1 + │ │ └── spans + │ │ ├── ["7\x00\x01*\x02\x00", "7\x00\x01*\x02\x00"] + │ │ ├── ["7\x00\x018", "7\x00\x018"] + │ │ └── ["7\x00\x03\x00\x01*\x02\x00", "7\x00\x03\x00\x01*\x02\x00"] + │ ├── key: (1) + │ └── fd: (1)-->(7) + └── filters + └── (j:4->0) <@ '1' [outer=(4), immutable] # Query using the fetch val and equality operators in a conjunction. opt expect=GenerateInvertedIndexScans disable=GenerateInvertedIndexZigzagJoins