Esempio n. 1
0
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
}
Esempio n. 2
0
// 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)
	}
}
Esempio n. 3
0
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
}
Esempio n. 4
0
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)
				}
			}
		})
	}
}