func (n *scanNode) ExplainPlan(_ bool) (name, description string, children []planNode) { if n.reverse { name = "revscan" } else { name = "scan" } var desc bytes.Buffer fmt.Fprintf(&desc, "%s@%s", n.desc.Name, n.index.Name) spans := sqlbase.PrettySpans(n.spans, 2) if spans != "" { if spans == "-" { spans = "ALL" } fmt.Fprintf(&desc, " %s", spans) } if n.limitHint > 0 && !n.limitSoft { if n.limitHint == 1 { desc.WriteString(" (max 1 row)") } else { fmt.Fprintf(&desc, " (max %d rows)", n.limitHint) } } subplans := n.p.collectSubqueryPlans(n.filter, nil) return name, desc.String(), subplans }
// visit is the recursive function that supports walkPlan(). func (v *planVisitor) visit(plan planNode) { if v.err != nil { return } if plan == nil { return } lv := *v lv.nodeName = nodeName(plan) recurse := v.observer.enterNode(lv.nodeName, plan) defer lv.observer.leaveNode(lv.nodeName) if !recurse { return } switch n := plan.(type) { case *valuesNode: suffix := "not yet populated" if n.rows != nil { suffix = fmt.Sprintf("%d row%s", n.rows.Len(), util.Pluralize(int64(n.rows.Len()))) } else if n.tuples != nil { suffix = fmt.Sprintf("%d row%s", len(n.tuples), util.Pluralize(int64(len(n.tuples)))) } description := fmt.Sprintf("%d column%s, %s", len(n.columns), util.Pluralize(int64(len(n.columns))), suffix) lv.attr("size", description) var subplans []planNode for i, tuple := range n.tuples { for j, expr := range tuple { if n.columns[j].omitted { continue } subplans = lv.expr(fmt.Sprintf("row %d, expr", i), j, expr, subplans) } } lv.subqueries(subplans) case *valueGenerator: subplans := lv.expr("expr", -1, n.expr, nil) lv.subqueries(subplans) case *scanNode: lv.attr("table", fmt.Sprintf("%s@%s", n.desc.Name, n.index.Name)) if n.noIndexJoin { lv.attr("hint", "no index join") } if n.specifiedIndex != nil { lv.attr("hint", fmt.Sprintf("force index @%s", n.specifiedIndex.Name)) } spans := sqlbase.PrettySpans(n.spans, 2) if spans != "" { if spans == "-" { spans = "ALL" } lv.attr("spans", spans) } if n.limitHint > 0 && !n.limitSoft { lv.attr("limit", fmt.Sprintf("%d", n.limitHint)) } subplans := lv.expr("filter", -1, n.filter, nil) lv.subqueries(subplans) case *filterNode: subplans := lv.expr("filter", -1, n.filter, nil) if n.explain != explainNone { lv.attr("mode", explainStrings[n.explain]) } lv.subqueries(subplans) lv.visit(n.source.plan) case *renderNode: var subplans []planNode for i, r := range n.render { subplans = lv.expr("render", i, r, subplans) } lv.subqueries(subplans) lv.visit(n.source.plan) case *indexJoinNode: lv.visit(n.index) lv.visit(n.table) case *joinNode: jType := "" switch n.joinType { case joinTypeInner: jType = "inner" if len(n.pred.leftColNames) == 0 && n.pred.onCond == nil { jType = "cross" } case joinTypeLeftOuter: jType = "left outer" case joinTypeRightOuter: jType = "right outer" case joinTypeFullOuter: jType = "full outer" } lv.attr("type", jType) if len(n.pred.leftColNames) > 0 { var buf bytes.Buffer buf.WriteByte('(') n.pred.leftColNames.Format(&buf, parser.FmtSimple) buf.WriteString(") = (") n.pred.rightColNames.Format(&buf, parser.FmtSimple) buf.WriteByte(')') lv.attr("equality", buf.String()) } subplans := lv.expr("pred", -1, n.pred.onCond, nil) lv.subqueries(subplans) lv.visit(n.left.plan) lv.visit(n.right.plan) case *selectTopNode: if n.plan != nil { lv.visit(n.plan) } else { if n.limit != nil { lv.visit(n.limit) } if n.distinct != nil { lv.visit(n.distinct) } if n.sort != nil { lv.visit(n.sort) } if n.window != nil { lv.visit(n.window) } if n.group != nil { lv.visit(n.group) } lv.visit(n.source) } case *limitNode: subplans := lv.expr("count", -1, n.countExpr, nil) subplans = lv.expr("offset", -1, n.offsetExpr, subplans) lv.subqueries(subplans) lv.visit(n.plan) case *distinctNode: if n.columnsInOrder != nil { var buf bytes.Buffer prefix := "" columns := n.Columns() for i, key := range n.columnsInOrder { if key { buf.WriteString(prefix) buf.WriteString(columns[i].Name) prefix = ", " } } lv.attr("key", buf.String()) } lv.visit(n.plan) case *sortNode: var columns ResultColumns if n.plan != nil { columns = n.plan.Columns() } // We use n.ordering and not plan.Ordering() because // plan.Ordering() does not include the added sort columns not // present in the output. order := orderingInfo{ordering: n.ordering} lv.attr("order", order.AsString(columns)) switch ss := n.sortStrategy.(type) { case *iterativeSortStrategy: lv.attr("strategy", "iterative") case *sortTopKStrategy: lv.attr("strategy", fmt.Sprintf("top %d", ss.topK)) } lv.visit(n.plan) case *groupNode: var subplans []planNode for i, agg := range n.funcs { subplans = lv.expr("aggregate", i, agg.expr, subplans) } for i, rexpr := range n.render { subplans = lv.expr("render", i, rexpr, subplans) } subplans = lv.expr("having", -1, n.having, subplans) lv.subqueries(subplans) lv.visit(n.plan) case *windowNode: var subplans []planNode for i, agg := range n.funcs { subplans = lv.expr("window", i, agg.expr, subplans) } for i, rexpr := range n.windowRender { subplans = lv.expr("render", i, rexpr, subplans) } lv.subqueries(subplans) lv.visit(n.plan) case *unionNode: lv.visit(n.left) lv.visit(n.right) case *splitNode: var subplans []planNode for i, e := range n.exprs { subplans = lv.expr("expr", i, e, subplans) } lv.subqueries(subplans) case *insertNode: var buf bytes.Buffer buf.WriteString(n.tableDesc.Name) buf.WriteByte('(') for i, col := range n.insertCols { if i > 0 { buf.WriteString(", ") } buf.WriteString(col.Name) } buf.WriteByte(')') lv.attr("into", buf.String()) var subplans []planNode for i, dexpr := range n.defaultExprs { subplans = lv.expr("default", i, dexpr, subplans) } for i, cexpr := range n.checkHelper.exprs { subplans = lv.expr("check", i, cexpr, subplans) } for i, rexpr := range n.rh.exprs { subplans = lv.expr("returning", i, rexpr, subplans) } n.tw.walkExprs(func(d string, i int, e parser.TypedExpr) { subplans = lv.expr(d, i, e, subplans) }) lv.subqueries(subplans) lv.visit(n.run.rows) case *updateNode: lv.attr("table", n.tableDesc.Name) if len(n.tw.ru.updateCols) > 0 { var buf bytes.Buffer for i, col := range n.tw.ru.updateCols { if i > 0 { buf.WriteString(", ") } buf.WriteString(col.Name) } lv.attr("set", buf.String()) } var subplans []planNode for i, rexpr := range n.rh.exprs { subplans = lv.expr("returning", i, rexpr, subplans) } n.tw.walkExprs(func(d string, i int, e parser.TypedExpr) { subplans = lv.expr(d, i, e, subplans) }) lv.subqueries(subplans) lv.visit(n.run.rows) case *deleteNode: lv.attr("from", n.tableDesc.Name) var subplans []planNode for i, rexpr := range n.rh.exprs { subplans = lv.expr("returning", i, rexpr, subplans) } n.tw.walkExprs(func(d string, i int, e parser.TypedExpr) { subplans = lv.expr(d, i, e, subplans) }) lv.subqueries(subplans) lv.visit(n.run.rows) case *createTableNode: if n.n.As() { lv.visit(n.sourcePlan) } case *createViewNode: lv.attr("query", n.sourceQuery) lv.visit(n.sourcePlan) case *delayedNode: lv.attr("source", n.name) lv.visit(n.plan) case *explainDebugNode: lv.visit(n.plan) case *ordinalityNode: lv.visit(n.source) case *explainTraceNode: lv.visit(n.plan) case *explainPlanNode: lv.attr("expanded", strconv.FormatBool(n.expanded)) lv.visit(n.plan) } }
func (n *indexJoinNode) Next() (bool, error) { // Loop looking up the next row. We either are going to pull a row from the // table or a batch of rows from the index. If we pull a batch of rows from // the index we perform another iteration of the loop looking for rows in the // table. This outer loop is necessary because a batch of rows from the index // might all be filtered when the resulting rows are read from the table. for tableLookup := (len(n.table.spans) > 0); true; tableLookup = true { // First, try to pull a row from the table. if tableLookup { next, err := n.table.Next() if err != nil { return false, err } if next { if n.explain == explainDebug { n.debugVals = n.table.DebugValues() } return true, nil } } // The table is out of rows. Pull primary keys from the index. n.table.scanInitialized = false n.table.spans = n.table.spans[:0] for len(n.table.spans) < indexJoinBatchSize { if next, err := n.index.Next(); !next { // The index is out of rows or an error occurred. if err != nil { return false, err } if len(n.table.spans) == 0 { // The index is out of rows. return false, nil } break } if n.explain == explainDebug { n.debugVals = n.index.DebugValues() if n.debugVals.output != debugValueRow { return true, nil } } vals := n.index.Values() primaryIndexKey, _, err := sqlbase.EncodeIndexKey( &n.table.desc, n.table.index, n.colIDtoRowIndex, vals, n.primaryKeyPrefix) if err != nil { return false, err } key := roachpb.Key(primaryIndexKey) n.table.spans = append(n.table.spans, roachpb.Span{ Key: key, EndKey: key.PrefixEnd(), }) if n.explain == explainDebug { // In debug mode, return the index information as a "partial" row. n.debugVals.output = debugValuePartial return true, nil } } if log.V(3) { log.Infof(n.index.p.ctx(), "table scan: %s", sqlbase.PrettySpans(n.table.spans, 0)) } } return false, nil }
func TestMakeSpans(t *testing.T) { defer leaktest.AfterTest(t)() testData := []struct { expr string columns string expectedAsc string expectedDesc string }{ {`a = 1`, `a`, `/1-/2`, `/1-/0`}, {`a != 1`, `a`, `/#-`, `-/#`}, {`a > 1`, `a`, `/2-`, `-/1`}, {`a >= 1`, `a`, `/1-`, `-/0`}, {`a < 1`, `a`, `/#-/1`, `/0-/#`}, {`a <= 1`, `a`, `/#-/2`, `/1-/#`}, {`a IS NULL`, `a`, `-/#`, `/NULL-`}, {`a IS NOT NULL`, `a`, `/#-`, `-/#`}, {`a IN (1,2,3)`, `a`, `/1-/4`, `/3-/0`}, {`a IN (1,3,5)`, `a`, `/1-/2 /3-/4 /5-/6`, `/5-/4 /3-/2 /1-/0`}, {`a IN (1,2,3) AND b = 1`, `a,b`, `/1/1-/1/2 /2/1-/2/2 /3/1-/3/2`, `/3/1-/3/0 /2/1-/2/0 /1/1-/1/0`}, {`a = 1 AND b IN (1,2,3)`, `a,b`, `/1/1-/1/4`, `/1/3-/1/0`}, {`a = 1 AND b IN (1,3,5)`, `a,b`, `/1/1-/1/2 /1/3-/1/4 /1/5-/1/6`, `/1/5-/1/4 /1/3-/1/2 /1/1-/1/0`}, {`a >= 1 AND b IN (1,2,3)`, `a,b`, `/1-`, `-/0`}, {`a <= 1 AND b IN (1,2,3)`, `a,b`, `/#-/2`, `/1-/#`}, {`(a, b) IN ((1, 2), (3, 4))`, `a,b`, `/1/2-/1/3 /3/4-/3/5`, `/3/4-/3/3 /1/2-/1/1`}, {`(b, a) IN ((1, 2), (3, 4))`, `a,b`, `/2/1-/2/2 /4/3-/4/4`, `/4/3-/4/2 /2/1-/2/0`}, {`(a, b) IN ((1, 2), (3, 4))`, `b`, `/2-/3 /4-/5`, `/4-/3 /2-/1`}, {`a = 1 AND b = 1`, `a,b`, `/1/1-/1/2`, `/1/1-/1/0`}, {`a = 1 AND b != 1`, `a,b`, `/1/#-/2`, `/1-/1/#`}, {`a = 1 AND b > 1`, `a,b`, `/1/2-/2`, `/1-/1/1`}, {`a = 1 AND b >= 1`, `a,b`, `/1/1-/2`, `/1-/1/0`}, {`a = 1 AND b < 1`, `a,b`, `/1/#-/1/1`, `/1/0-/1/#`}, {`a = 1 AND b <= 1`, `a,b`, `/1/#-/1/2`, `/1/1-/1/#`}, {`a = 1 AND b IS NULL`, `a,b`, `/1-/1/#`, `/1/NULL-/0`}, {`a = 1 AND b IS NOT NULL`, `a,b`, `/1/#-/2`, `/1-/1/#`}, {`a != 1 AND b = 1`, `a,b`, `/#-`, `-/#`}, {`a != 1 AND b != 1`, `a,b`, `/#-`, `-/#`}, {`a != 1 AND b > 1`, `a,b`, `/#-`, `-/#`}, {`a != 1 AND b >= 1`, `a,b`, `/#-`, `-/#`}, {`a != 1 AND b < 1`, `a,b`, `/#-`, `-/#`}, {`a != 1 AND b <= 1`, `a,b`, `/#-`, `-/#`}, {`a != 1 AND b IS NULL`, `a,b`, `/#-`, `-/#`}, {`a != 1 AND b IS NOT NULL`, `a,b`, `/#-`, `-/#`}, {`a > 1 AND b = 1`, `a,b`, `/2/1-`, `-/2/0`}, {`a > 1 AND b != 1`, `a,b`, `/2/#-`, `-/2/#`}, {`a > 1 AND b > 1`, `a,b`, `/2/2-`, `-/2/1`}, {`a > 1 AND b >= 1`, `a,b`, `/2/1-`, `-/2/0`}, {`a > 1 AND b < 1`, `a,b`, `/2-`, `-/1`}, {`a > 1 AND b <= 1`, `a,b`, `/2-`, `-/1`}, {`a > 1 AND b IS NULL`, `a,b`, `/2-`, `-/1`}, {`a > 1 AND b IS NOT NULL`, `a,b`, `/2/#-`, `-/2/#`}, {`a >= 1 AND b = 1`, `a,b`, `/1/1-`, `-/1/0`}, {`a >= 1 AND b != 1`, `a,b`, `/1/#-`, `-/1/#`}, {`a >= 1 AND b > 1`, `a,b`, `/1/2-`, `-/1/1`}, {`a >= 1 AND b >= 1`, `a,b`, `/1/1-`, `-/1/0`}, {`a >= 1 AND b < 1`, `a,b`, `/1-`, `-/0`}, {`a >= 1 AND b <= 1`, `a,b`, `/1-`, `-/0`}, {`a >= 1 AND b IS NULL`, `a,b`, `/1-`, `-/0`}, {`a >= 1 AND b IS NOT NULL`, `a,b`, `/1/#-`, `-/1/#`}, {`a < 1 AND b = 1`, `a,b`, `/#-/0/2`, `/0/1-/#`}, {`a < 1 AND b != 1`, `a,b`, `/#-/1`, `/0-/#`}, {`a < 1 AND b > 1`, `a,b`, `/#-/1`, `/0-/#`}, {`a < 1 AND b >= 1`, `a,b`, `/#-/1`, `/0-/#`}, {`a < 1 AND b < 1`, `a,b`, `/#-/0/1`, `/0/0-/#`}, {`a < 1 AND b <= 1`, `a,b`, `/#-/0/2`, `/0/1-/#`}, {`a < 1 AND b IS NULL`, `a,b`, `/#-/0/#`, `/0/NULL-/#`}, {`a < 1 AND b IS NOT NULL`, `a,b`, `/#-/1`, `/0-/#`}, {`a <= 1 AND b = 1`, `a,b`, `/#-/1/2`, `/1/1-/#`}, {`a <= 1 AND b != 1`, `a,b`, `/#-/2`, `/1-/#`}, {`a <= 1 AND b > 1`, `a,b`, `/#-/2`, `/1-/#`}, {`a <= 1 AND b >= 1`, `a,b`, `/#-/2`, `/1-/#`}, {`a <= 1 AND b < 1`, `a,b`, `/#-/1/1`, `/1/0-/#`}, {`a <= 1 AND b <= 1`, `a,b`, `/#-/1/2`, `/1/1-/#`}, {`a <= 1 AND b IS NULL`, `a,b`, `/#-/1/#`, `/1/NULL-/#`}, {`a <= 1 AND b IS NOT NULL`, `a,b`, `/#-/2`, `/1-/#`}, {`a IN (1) AND b = 1`, `a,b`, `/1/1-/1/2`, `/1/1-/1/0`}, {`a IN (1) AND b != 1`, `a,b`, `/1/#-/2`, `/1-/1/#`}, {`a IN (1) AND b > 1`, `a,b`, `/1/2-/2`, `/1-/1/1`}, {`a IN (1) AND b >= 1`, `a,b`, `/1/1-/2`, `/1-/1/0`}, {`a IN (1) AND b < 1`, `a,b`, `/1/#-/1/1`, `/1/0-/1/#`}, {`a IN (1) AND b <= 1`, `a,b`, `/1/#-/1/2`, `/1/1-/1/#`}, {`a IN (1) AND b IS NULL`, `a,b`, `/1-/1/#`, `/1/NULL-/0`}, {`a IN (1) AND b IS NOT NULL`, `a,b`, `/1/#-/2`, `/1-/1/#`}, {`(a, b) = (1, 2)`, `a`, `/1-/2`, `/1-/0`}, {`(a, b) = (1, 2)`, `a,b`, `/1/2-/1/3`, `/1/2-/1/1`}, {`a > 1 OR a >= 5`, `a`, `/2-`, `-/1`}, {`a < 5 OR a >= 1`, `a`, `/#-`, `-/#`}, {`a < 1 OR a >= 5`, `a`, `/#-/1 /5-`, `-/4 /0-/#`}, {`a = 1 OR a > 8`, `a`, `/1-/2 /9-`, `-/8 /1-/0`}, {`a = 8 OR a > 1`, `a`, `/2-`, `-/1`}, {`a < 1 OR a = 5 OR a > 8`, `a`, `/#-/1 /5-/6 /9-`, `-/8 /5-/4 /0-/#`}, {`a < 8 OR a = 8 OR a > 8`, `a`, `/#-`, `-/#`}, {`(a = 1 AND b = 5) OR (a = 3 AND b = 7)`, `a`, `/1-/2 /3-/4`, `/3-/2 /1-/0`}, {`(a = 1 AND b = 5) OR (a = 3 AND b = 7)`, `b`, `/5-/6 /7-/8`, `/7-/6 /5-/4`}, {`(a = 1 AND b = 5) OR (a = 3 AND b = 7)`, `a,b`, `/1/5-/1/6 /3/7-/3/8`, `/3/7-/3/6 /1/5-/1/4`}, {`(a = 1 AND b < 5) OR (a = 3 AND b > 7)`, `a`, `/1-/2 /3-/4`, `/3-/2 /1-/0`}, {`(a = 1 AND b < 5) OR (a = 3 AND b > 7)`, `b`, `/#-/5 /8-`, `-/7 /4-/#`}, {`(a = 1 AND b < 5) OR (a = 3 AND b > 7)`, `a,b`, `/1/#-/1/5 /3/8-/4`, `/3-/3/7 /1/4-/1/#`}, {`(a = 1 AND b > 5) OR (a = 3 AND b > 7)`, `a`, `/1-/2 /3-/4`, `/3-/2 /1-/0`}, {`(a = 1 AND b > 5) OR (a = 3 AND b > 7)`, `b`, `/6-`, `-/5`}, {`(a = 1 AND b > 5) OR (a = 3 AND b > 7)`, `a,b`, `/1/6-/2 /3/8-/4`, `/3-/3/7 /1-/1/5`}, {`(a = 1 AND b > 5) OR (a = 3 AND b < 7)`, `a`, `/1-/2 /3-/4`, `/3-/2 /1-/0`}, {`(a = 1 AND b > 5) OR (a = 3 AND b < 7)`, `b`, `/#-`, `-/#`}, {`(a = 1 AND b > 5) OR (a = 3 AND b < 7)`, `a,b`, `/1/6-/2 /3/#-/3/7`, `/3/6-/3/# /1-/1/5`}, {`(a < 1 AND b < 5) OR (a > 3 AND b > 7)`, `a`, `/#-/1 /4-`, `-/3 /0-/#`}, {`(a < 1 AND b < 5) OR (a > 3 AND b > 7)`, `b`, `/#-/5 /8-`, `-/7 /4-/#`}, {`(a < 1 AND b < 5) OR (a > 3 AND b > 7)`, `a,b`, `/#-/0/5 /4/8-`, `-/4/7 /0/4-/#`}, {`(a > 3 AND b < 5) OR (a < 1 AND b > 7)`, `a`, `/#-/1 /4-`, `-/3 /0-/#`}, {`(a > 3 AND b < 5) OR (a < 1 AND b > 7)`, `b`, `/#-/5 /8-`, `-/7 /4-/#`}, {`(a > 3 AND b < 5) OR (a < 1 AND b > 7)`, `a,b`, `/#-/1 /4-`, `-/3 /0-/#`}, {`(a > 1 AND b < 5) OR (a < 3 AND b > 7)`, `a`, `/#-`, `-/#`}, {`(a > 1 AND b < 5) OR (a < 3 AND b > 7)`, `b`, `/#-/5 /8-`, `-/7 /4-/#`}, {`(a > 1 AND b < 5) OR (a < 3 AND b > 7)`, `a,b`, `/#-`, `-/#`}, {`(a = 5) OR (a, b) IN ((1, 1), (3, 3))`, `a`, `/1-/2 /3-/4 /5-/6`, `/5-/4 /3-/2 /1-/0`}, {`(a = 5) OR (a, b) IN ((1, 1), (3, 3))`, `b`, `-`, `-`}, {`(a = 5) OR (a, b) IN ((1, 1), (3, 3))`, `a,b`, `/1/1-/1/2 /3/3-/3/4 /5-/6`, `/5-/4 /3/3-/3/2 /1/1-/1/0`}, // When encoding an end constraint for a maximal datum, we use // bytes.PrefixEnd() to go beyond the normal encodings of that datatype. {fmt.Sprintf(`a = %d`, math.MaxInt64), `a`, `/9223372036854775807-/<varint 9223372036854775808 overflows int64>`, `/9223372036854775807-/9223372036854775806`}, {fmt.Sprintf(`a = %d`, math.MinInt64), `a`, `/-9223372036854775808-/-9223372036854775807`, `/-9223372036854775808-/<varint 9223372036854775808 overflows int64>`}, {`(a, b) >= (1, 4)`, `a,b`, `/1/4-`, `-/1/3`}, {`(a, b) > (1, 4)`, `a,b`, `/1/5-`, `-/1/4`}, {`(a, b) < (1, 4)`, `a,b`, `/#-/1/4`, `/1/3-/#`}, {`(a, b) <= (1, 4)`, `a,b`, `/#-/1/5`, `/1/4-/#`}, {`(a, b) = (1, 4)`, `a,b`, `/1/4-/1/5`, `/1/4-/1/3`}, {`(a, b) != (1, 4)`, `a,b`, `/#-`, `-/#`}, } for _, d := range testData { for _, dir := range []encoding.Direction{encoding.Ascending, encoding.Descending} { var expected string if dir == encoding.Ascending { expected = d.expectedAsc } else { expected = d.expectedDesc } t.Run(d.expr+"~"+expected, func(t *testing.T) { sel := makeSelectNode(t) columns := strings.Split(d.columns, ",") dirs := make([]encoding.Direction, 0, len(columns)) for range columns { dirs = append(dirs, dir) } desc, index := makeTestIndex(t, columns, dirs) constraints, _ := makeConstraints(t, d.expr, desc, index, sel) spans := makeSpans(constraints, desc, index) s := sqlbase.PrettySpans(spans, 2) s = keys.MassagePrettyPrintedSpanForTest(s, indexToDirs(index)) if expected != s { t.Errorf("[index direction: %d] %s: expected %s, but found %s", dir, d.expr, expected, s) } }) } } // Test indexes with mixed-directions (some cols Asc, some cols Desc) and other edge cases. testData2 := []struct { expr string columns string expected string }{ {`a = 1 AND b = 5`, `a,b-,c`, `/1/5-/1/4`}, {`a = 7 AND b IN (1,2,3) AND c = false`, `a,b-,c`, `/7/3/0-/7/3/1 /7/2/0-/7/2/1 /7/1/0-/7/1/1`}, // Test different directions for te columns inside a tuple. {`(a,b,j) IN ((1,2,3), (4,5,6))`, `a-,b,j-`, `/4/5/6-/4/5/5 /1/2/3-/1/2/2`}, {`k = b'\xff'`, `k`, `/"\xff"-/"\xff\x00"`}, // Test that limits on bytes work correctly: when encoding a descending limit for bytes, // we need to go outside the bytes encoding. // "\xaa" is encoded as [bytesDescMarker, ^0xaa, <term escape sequence>] {`k = b'\xaa'`, `k-`, fmt.Sprintf("raw:%c%c\xff\xfe-%c%c\xff\xff", encoding.BytesDescMarker, ^byte(0xaa), encoding.BytesDescMarker, ^byte(0xaa))}, // Ensure tuples with differing index directions aren't constrained. // TODO(mjibson): fix this, see #6346 {`(a, b) >= (1, 4)`, `a-,b`, `-`}, {`(a, b) >= (1, 4)`, `a,b-`, `-`}, } for _, d := range testData2 { t.Run(d.expr+"~"+d.expected, func(t *testing.T) { sel := makeSelectNode(t) desc, index := makeTestIndexFromStr(t, d.columns) constraints, _ := makeConstraints(t, d.expr, desc, index, sel) spans := makeSpans(constraints, desc, index) var got string raw := false if strings.HasPrefix(d.expected, "raw:") { raw = true span := spans[0] d.expected = d.expected[4:] // Trim the index prefix from the span. prefix := string(sqlbase.MakeIndexKeyPrefix(desc, index.ID)) got = strings.TrimPrefix(string(span.Key), prefix) + "-" + strings.TrimPrefix(string(span.EndKey), prefix) } else { got = keys.MassagePrettyPrintedSpanForTest(sqlbase.PrettySpans(spans, 2), indexToDirs(index)) } if d.expected != got { if !raw { t.Errorf("%s: expected %s, but found %s", d.expr, d.expected, got) } else { t.Errorf("%s: expected %# x, but found %# x", d.expr, []byte(d.expected), got) } } }) } }