From e755fecf637cf81a51f6b66b70ccaedec0a83614 Mon Sep 17 00:00:00 2001 From: Pat Okeeffe <85502298+paddyjok@users.noreply.github.com> Date: Fri, 17 Feb 2023 13:35:36 -0600 Subject: [PATCH] ORDER BY ....what now!? (fb-1954) (#2257) * can now order by columns not in the select list * added testing coverage --- sql3/planner/compileselect.go | 171 ++++++++++++++++++++------ sql3/planner/expression.go | 52 ++++++-- sql3/planner/expressionanalyzer.go | 64 +++++----- sql3/planner/expressiontypes.go | 12 +- sql3/planner/inbuiltfunctionstable.go | 2 +- sql3/planner/oporderby.go | 58 +++++++-- sql3/planner/planoptimizer.go | 41 +++++- sql3/test/defs/defs_orderby.go | 130 +++++++++++++++++++- 8 files changed, 434 insertions(+), 96 deletions(-) diff --git a/sql3/planner/compileselect.go b/sql3/planner/compileselect.go index 9a03bda0f..940aff215 100644 --- a/sql3/planner/compileselect.go +++ b/sql3/planner/compileselect.go @@ -11,7 +11,6 @@ import ( "github.com/featurebasedb/featurebase/v3/sql3" "github.com/featurebasedb/featurebase/v3/sql3/parser" "github.com/featurebasedb/featurebase/v3/sql3/planner/types" - "github.com/pkg/errors" ) // compileSelectStatment compiles a parser.SelectStatment AST into a PlanOperator @@ -20,12 +19,12 @@ func (p *ExecutionPlanner) compileSelectStatement(stmt *parser.SelectStatement, aggregates := make([]types.PlanExpression, 0) - // handle projections + // compile select list and generate a list of projections projections := make([]types.PlanExpression, 0) for _, c := range stmt.Columns { planExpr, err := p.compileExpr(c.Expr) if err != nil { - return nil, errors.Wrap(err, "planning select column expression") + return nil, err } if c.Alias != nil { planExpr = newAliasPlanExpression(c.Alias.Name, planExpr) @@ -34,7 +33,7 @@ func (p *ExecutionPlanner) compileSelectStatement(stmt *parser.SelectStatement, aggregates = p.gatherExprAggregates(planExpr, aggregates) } - // group by clause. + // compile group by clause and generate a list of group by expressions groupByExprs := make([]types.PlanExpression, 0) for _, expr := range stmt.GroupByExprs { switch expr := expr.(type) { @@ -44,32 +43,32 @@ func (p *ExecutionPlanner) compileSelectStatement(stmt *parser.SelectStatement, return nil, sql3.NewErrInternalf("unsupported expression type in GROUP BY clause: %T", expr) } } - var err error - // handle the where clause + // compile the where clause where, err := p.compileExpr(stmt.WhereExpr) if err != nil { return nil, err } - // source expression + // compile source expression source, err := p.compileSource(query, stmt.Source) if err != nil { return nil, err } - // if we did have a where, insert the filter op + // if we did have a where, insert the filter op after source if where != nil { aggregates = p.gatherExprAggregates(where, aggregates) source = NewPlanOpFilter(p, where, source) } - // handle the having clause + // compile the having clause having, err := p.compileExpr(stmt.HavingExpr) if err != nil { return nil, err } + // if we have a having, check references if having != nil { // gather aggregates aggregates = p.gatherExprAggregates(having, aggregates) @@ -129,9 +128,49 @@ func (p *ExecutionPlanner) compileSelectStatement(stmt *parser.SelectStatement, } } - // do we have straight projection or a group by? + // compile order by and generate a list of ordering expressions + orderByExprs := make([]*OrderByExpression, 0) + nonReferenceOrderByExpressions := make([]types.PlanExpression, 0) + if len(stmt.OrderingTerms) > 0 { + for _, ot := range stmt.OrderingTerms { + // compile the ordering term + expr, err := p.compileOrderingTermExpr(ot.X, projections, stmt.Source) + if err != nil { + return nil, err + } + + f := &OrderByExpression{ + Expr: expr, + } + f.Order = orderByAsc + if ot.Desc.IsValid() { + f.Order = orderByDesc + } + orderByExprs = append(orderByExprs, f) + } + + // if the expression is just references, we + // can put the sort directly after the source + for _, oe := range orderByExprs { + _, ok := oe.Expr.(*qualifiedRefPlanExpression) + if !ok { + nonReferenceOrderByExpressions = append(nonReferenceOrderByExpressions, oe.Expr) + } + } + + // all the order by expressions are references, so we can put the order by before the + // projection + if len(nonReferenceOrderByExpressions) == 0 { + source = NewPlanOpOrderBy(orderByExprs, source) + } + } + var compiledOp types.PlanOperator + + // do we have straight projection or a group by? if len(aggregates) > 0 { + // we have a group by + //check that any projections that are not aggregates are in the group by list nonAggregateReferences := make([]*qualifiedRefPlanExpression, 0) for _, expr := range projections { @@ -172,37 +211,98 @@ func (p *ExecutionPlanner) compileSelectStatement(stmt *parser.SelectStatement, } compiledOp = NewPlanOpProjection(projections, groupByOp) } else { + // no group by, just a straight projection compiledOp = NewPlanOpProjection(projections, source) } - // handle order by - if len(stmt.OrderingTerms) > 0 { - orderByFields := make([]*OrderByExpression, 0) - for _, ot := range stmt.OrderingTerms { - index, err := p.compileOrderingTermExpr(ot.X) - if err != nil { - return nil, err - } - // get the data type from the projection - projDataType := projections[index].Type() + // handle the case where we have order by expressions and they are not references + // in this case we need to put the order by after the projection + if len(orderByExprs) > 0 && len(nonReferenceOrderByExpressions) > 0 { - // don't let a sort happen on something unsortable right now - switch projDataType.(type) { - case *parser.DataTypeStringSet, *parser.DataTypeIDSet: - return nil, sql3.NewErrExpectedSortableExpression(0, 0, projDataType.TypeDescription()) - } + // if the order by expressions contain a reference not in the projection list, + // we have to create a new projection, add references to current projection, + // and place the new order by in between - f := &OrderByExpression{ - Index: index, - ExprType: projDataType, + // get a list of all the refs for the order by exprs + orderByRefs := make(map[string]*qualifiedRefPlanExpression) + for _, oe := range orderByExprs { + ex, ok := oe.Expr.(*qualifiedRefPlanExpression) + if ok { + orderByRefs[ex.String()] = ex } - f.Order = orderByAsc - if ot.Desc.IsValid() { - f.Order = orderByDesc - } - orderByFields = append(orderByFields, f) } - compiledOp = NewPlanOpOrderBy(orderByFields, compiledOp) + + // get a list of all the projection refs + projRefs := make(map[string]*qualifiedRefPlanExpression) + for _, p := range projections { + InspectExpression(p, func(expr types.PlanExpression) bool { + switch ex := expr.(type) { + case *qualifiedRefPlanExpression: + projRefs[ex.String()] = ex + return false + } + return true + }) + } + + // iterate the order by terms, make a list of the ones not projected + unprojectedRefs := make([]*qualifiedRefPlanExpression, 0) + for kobr, obr := range orderByRefs { + _, found := projRefs[kobr] + if !found { + unprojectedRefs = append(unprojectedRefs, obr) + } + } + + // sigh - ok. If we have unprojected refs, we need to insert a projection + if len(unprojectedRefs) > 0 { + // create the final projection list - this will go before the order by + newProjections := make([]types.PlanExpression, len(projections)) + for i, p := range projections { + switch pe := p.(type) { + case *aliasPlanExpression: + newProjections[i] = newQualifiedRefPlanExpression("", pe.aliasName, i, pe.Type()) + case *qualifiedRefPlanExpression: + newProjections[i] = newQualifiedRefPlanExpression(pe.tableName, pe.columnName, i, pe.Type()) + default: + newProjections[i] = newQualifiedRefPlanExpression("", p.String(), i, p.Type()) + } + } + + // add the unprojected refs to the existing projection op + projectionOp, ok := compiledOp.(*PlanOpProjection) + if !ok { + return nil, sql3.NewErrInternalf("unexpected compiledOp type '%T'", compiledOp) + } + for _, uref := range unprojectedRefs { + projectionOp.Projections = append(projectionOp.Projections, uref) + } + + // add the order by on top of this + // rewrite all the order by expressions that are not qualified refs to be qualified + // refs referring to the expression + for i, oe := range orderByExprs { + _, ok := oe.Expr.(*qualifiedRefPlanExpression) + if !ok { + orderByExprs[i].Expr = newQualifiedRefPlanExpression("", oe.Expr.String(), 0, oe.Expr.Type()) + } + } + compiledOp = NewPlanOpOrderBy(orderByExprs, compiledOp) + + // add the final projection on top of this + compiledOp = NewPlanOpProjection(newProjections, compiledOp) + + } else { + // rewrite all the order by expressions that are not qualified refs to be qualified + // refs referring to the expression + for i, oe := range orderByExprs { + _, ok := oe.Expr.(*qualifiedRefPlanExpression) + if !ok { + orderByExprs[i].Expr = newQualifiedRefPlanExpression("", oe.Expr.String(), 0, oe.Expr.Type()) + } + } + compiledOp = NewPlanOpOrderBy(orderByExprs, compiledOp) + } } // insert the top operator if it exists @@ -583,11 +683,10 @@ func (p *ExecutionPlanner) analyzeSelectStatement(ctx context.Context, stmt *par } for _, term := range stmt.OrderingTerms { - expr, err := p.analyzeOrderingTermExpression(term.X, stmt) + err := p.analyzeOrderingTermExpression(term.X, stmt) if err != nil { return nil, err } - term.X = expr } return stmt, nil diff --git a/sql3/planner/expression.go b/sql3/planner/expression.go index 292e0716d..c014fe140 100644 --- a/sql3/planner/expression.go +++ b/sql3/planner/expression.go @@ -2817,26 +2817,62 @@ func (p *ExecutionPlanner) compileCallExpr(expr *parser.Call) (_ types.PlanExpre } } -func (p *ExecutionPlanner) compileOrderingTermExpr(expr parser.Expr) (index int, err error) { +func (p *ExecutionPlanner) compileOrderingTermExpr(expr parser.Expr, projections []types.PlanExpression, source parser.Source) (types.PlanExpression, error) { if expr == nil { - return 0, nil + return nil, nil } switch thisExpr := expr.(type) { - case *parser.QualifiedRef: - return thisExpr.ColumnIndex, nil + case *parser.Ident: + for _, proj := range projections { + switch p := proj.(type) { + case *qualifiedRefPlanExpression: + if strings.EqualFold(thisExpr.Name, p.columnName) { + if !typeCanBeSortedOn(p.Type()) { + return nil, sql3.NewErrExpectedSortableExpression(0, 0, p.Type().TypeDescription()) + } + return p, nil + } + case *aliasPlanExpression: + if strings.EqualFold(thisExpr.Name, p.aliasName) { + if !typeCanBeSortedOn(p.expr.Type()) { + return nil, sql3.NewErrExpectedSortableExpression(0, 0, p.expr.Type().TypeDescription()) + } + return p.expr, nil + } + + } + } + + // we didn't find in projection list so go look in the source columns + for _, col := range source.PossibleOutputColumns() { + if strings.EqualFold(thisExpr.Name, col.ColumnName) { + orderExpr := newQualifiedRefPlanExpression(col.TableName, col.ColumnName, col.ColumnIndex, col.Datatype) + if !typeCanBeSortedOn(orderExpr.Type()) { + return nil, sql3.NewErrExpectedSortableExpression(0, 0, orderExpr.Type().TypeDescription()) + } + return orderExpr, nil + } + } + + return nil, sql3.NewErrColumnNotFound(thisExpr.NamePos.Line, thisExpr.NamePos.Column, thisExpr.Name) case *parser.IntegerLit: val, err := strconv.ParseInt(thisExpr.Value, 10, 64) if err != nil { - return 0, err + return nil, err } // subtract one because ordering terms are 1 based, not 0 based - return int(val - 1), nil + index := int(val - 1) + // get the expr from the projection + orderExpr := projections[index] + if !typeCanBeSortedOn(orderExpr.Type()) { + return nil, sql3.NewErrExpectedSortableExpression(0, 0, orderExpr.Type().TypeDescription()) + } + return orderExpr, nil default: - return 0, sql3.NewErrInternalf("unexpected ordering expression type: %T", expr) - + return nil, sql3.NewErrInternalf("unexpected ordering expression type: %T", expr) } } diff --git a/sql3/planner/expressionanalyzer.go b/sql3/planner/expressionanalyzer.go index 1214bd05a..78aa5d5b7 100644 --- a/sql3/planner/expressionanalyzer.go +++ b/sql3/planner/expressionanalyzer.go @@ -831,60 +831,54 @@ func (p *ExecutionPlanner) analyzeCaseBlockExpression(ctx context.Context, expr return expr, nil } -func (p *ExecutionPlanner) analyzeOrderingTermExpression(expr parser.Expr, scope parser.Statement) (parser.Expr, error) { +func (p *ExecutionPlanner) analyzeOrderingTermExpression(expr parser.Expr, scope parser.Statement) error { if expr == nil { - return nil, nil + return nil } - // ordering terms need to be either a column name, an alias name or an integer literal representing - // position of column in the select list + // ordering terms can be: + // 1. a *parser.Ident reference to either a column name in the source, or a reference to a a column or alias name in the projection list + // 2. a *parser.IntegerLit representing position of column in the projection list switch thisExpr := expr.(type) { case *parser.Ident: switch sc := scope.(type) { case *parser.SelectStatement: - // go find the first ident in the projection list that matches - columnIndex := 0 - found := false - for idx, proj := range sc.Columns { + // go look for the first ident in the projection list that matches + foundInProjectionList := false + for _, proj := range sc.Columns { // if the expression is a qualified ref, check the name colExpr, ok := proj.Expr.(*parser.QualifiedRef) if ok && strings.EqualFold(thisExpr.Name, colExpr.Column.Name) { - columnIndex = idx - found = true + foundInProjectionList = true break } // try the alias is there is one if proj.Alias != nil && strings.EqualFold(thisExpr.Name, proj.Alias.Name) { - columnIndex = idx - found = true + foundInProjectionList = true break } } - if !found { - return nil, sql3.NewErrColumnNotFound(thisExpr.NamePos.Line, thisExpr.NamePos.Column, thisExpr.Name) - } + if !foundInProjectionList { + // we didn't find in projection list so go look in the source columns + foundInSource := false + for _, col := range sc.Source.PossibleOutputColumns() { + if strings.EqualFold(thisExpr.Name, col.ColumnName) { + foundInSource = true + break + } + } - // turn *parser.Ident into *parser.QualifiedRef - ident := &parser.QualifiedRef{ - Table: &parser.Ident{ - Name: "", - NamePos: parser.Pos{Line: 0, Column: 0}, - }, - Column: &parser.Ident{ - Name: thisExpr.Name, - NamePos: thisExpr.NamePos, - }, - ColumnIndex: columnIndex, - // since this is a ordring term, we don't care about the type - RefDataType: parser.NewDataTypeVoid(), + if !foundInSource { + return sql3.NewErrColumnNotFound(thisExpr.NamePos.Line, thisExpr.NamePos.Column, thisExpr.Name) + } } - return ident, nil + return nil default: - return nil, sql3.NewErrInternalf("unhandled scope type '%T'", sc) + return sql3.NewErrInternalf("unhandled scope type '%T'", sc) } case *parser.IntegerLit: @@ -893,17 +887,17 @@ func (p *ExecutionPlanner) analyzeOrderingTermExpression(expr parser.Expr, scope // check to see if the offset is in the range value, err := strconv.ParseInt(thisExpr.Value, 10, 64) if err != nil { - return nil, sql3.NewErrInternalf("unexpected integer literal value") + return sql3.NewErrInternalf("unexpected integer literal value") } if value < 1 || value > int64(len(sc.Columns)) { - return nil, sql3.NewErrExpectedSortExpressionReference(0, 0) + return sql3.NewErrExpectedSortExpressionReference(0, 0) } default: - return nil, sql3.NewErrInternalf("unhandled scope type '%T'", sc) + return sql3.NewErrInternalf("unhandled scope type '%T'", sc) } + return nil default: - return nil, sql3.NewErrExpectedSortExpressionReference(expr.Pos().Line, expr.Pos().Column) + return sql3.NewErrExpectedSortExpressionReference(expr.Pos().Line, expr.Pos().Column) } - return expr, nil } diff --git a/sql3/planner/expressiontypes.go b/sql3/planner/expressiontypes.go index 43ff26f3c..242bf287d 100644 --- a/sql3/planner/expressiontypes.go +++ b/sql3/planner/expressiontypes.go @@ -466,7 +466,7 @@ func typeIsTimeQuantum(testType parser.ExprDataType) (bool, parser.ExprDataType) switch testType.(type) { case *parser.DataTypeIDSetQuantum: return true, parser.NewDataTypeIDSet() - case *parser.DataTypeStringSet: + case *parser.DataTypeStringSetQuantum: return true, parser.NewDataTypeStringSet() default: return false, nil @@ -542,6 +542,16 @@ func typeIsBSI(testType parser.ExprDataType) bool { } } +// returns true if we can sort on a type +func typeCanBeSortedOn(testType parser.ExprDataType) bool { + switch testType.(type) { + case *parser.DataTypeStringSet, *parser.DataTypeIDSet: + return false + default: + return true + } +} + // returns true if the types can be compared func typesAreComparable(testTypeL parser.ExprDataType, testTypeR parser.ExprDataType) bool { switch testTypeL.(type) { diff --git a/sql3/planner/inbuiltfunctionstable.go b/sql3/planner/inbuiltfunctionstable.go index 0968e659f..19bc67c93 100644 --- a/sql3/planner/inbuiltfunctionstable.go +++ b/sql3/planner/inbuiltfunctionstable.go @@ -47,7 +47,7 @@ func (p *ExecutionPlanner) analyzeFunctionSubtable(call *parser.Call, scope pars ok, _ := typeIsTimeQuantum(call.Args[0].DataType()) if !ok { // TODO (pok) send back the right error - return nil, sql3.NewErrSetExpressionExpected(call.Args[1].Pos().Line, call.Args[1].Pos().Column) + return nil, sql3.NewErrSetExpressionExpected(call.Args[0].Pos().Line, call.Args[0].Pos().Column) } call.ResultDataType = parser.NewDataTypeSubtable([]*parser.SubtableColumn{ { diff --git a/sql3/planner/oporderby.go b/sql3/planner/oporderby.go index b11c431a1..feced2451 100644 --- a/sql3/planner/oporderby.go +++ b/sql3/planner/oporderby.go @@ -32,8 +32,7 @@ const ( // OrderByExpression is the expression on which an order by can be computed type OrderByExpression struct { - Index int - ExprType parser.ExprDataType + Expr types.PlanExpression Order orderByOrder NullOrdering nullOrdering } @@ -79,6 +78,24 @@ func (n *PlanOpOrderBy) WithChildren(children ...types.PlanOperator) (types.Plan return NewPlanOpOrderBy(n.orderByFields, children[0]), nil } +func (n *PlanOpOrderBy) Expressions() []types.PlanExpression { + res := make([]types.PlanExpression, 0) + for _, e := range n.orderByFields { + res = append(res, e.Expr) + } + return res +} + +func (n *PlanOpOrderBy) WithUpdatedExpressions(exprs ...types.PlanExpression) (types.PlanOperator, error) { + if len(exprs) != len(n.orderByFields) { + return nil, sql3.NewErrInternalf("unexpected number of exprs '%d'", len(exprs)) + } + for i, e := range exprs { + n.orderByFields[i].Expr = e + } + return n, nil +} + func (n *PlanOpOrderBy) String() string { return "" } @@ -92,8 +109,7 @@ func (n *PlanOpOrderBy) Plan() map[string]interface{} { ps := make([]interface{}, 0) for _, e := range n.orderByFields { ps = append(ps, &map[string]interface{}{ - "index": e.Index, - "exprType": e.ExprType.TypeDescription(), + "expr": e.Expr.Plan(), "order": e.Order, "nullOrdering": e.NullOrdering, }) @@ -200,8 +216,20 @@ func (s *OrderBySorter) Less(i, j int) bool { a := s.Rows[i] b := s.Rows[j] for _, sf := range s.SortFields { - av := a[sf.Index] - bv := b[sf.Index] + + var sortIndex int + switch se := sf.Expr.(type) { + case *qualifiedRefPlanExpression: + sortIndex = se.columnIndex + case *intLiteralPlanExpression: + sortIndex = int(se.value) + default: + s.LastError = sql3.NewErrInternalf("unexpected sort field expression type '%T'", se) + return false + } + + av := a[sortIndex] + bv := b[sortIndex] if sf.Order == orderByDesc { av, bv = bv, av @@ -215,8 +243,8 @@ func (s *OrderBySorter) Less(i, j int) bool { return sf.NullOrdering != nullOrderingFirst } - switch sf.ExprType.(type) { - case *parser.DataTypeInt, *parser.DataTypeID: + switch t := sf.Expr.Type().(type) { + case *parser.DataTypeInt: avInt, aok := av.(int64) bvInt, bok := bv.(int64) if !(aok && bok) { @@ -228,6 +256,18 @@ func (s *OrderBySorter) Less(i, j int) bool { } return true + case *parser.DataTypeID: + avInt, aok := av.(uint64) + bvInt, bok := bv.(uint64) + if !(aok && bok) { + s.LastError = sql3.NewErrInternalf("unexpected type conversion result") + return false + } + if avInt > bvInt { + return false + } + return true + case *parser.DataTypeBool: avBool, aok := av.(bool) bvBool, bok := bv.(bool) @@ -277,7 +317,7 @@ func (s *OrderBySorter) Less(i, j int) bool { return true default: - s.LastError = sql3.NewErrInternalf("unhandled data type '%T'", sf.ExprType) + s.LastError = sql3.NewErrInternalf("unhandled data type '%T'", t) return false } } diff --git a/sql3/planner/planoptimizer.go b/sql3/planner/planoptimizer.go index e5c1761ef..114c29922 100644 --- a/sql3/planner/planoptimizer.go +++ b/sql3/planner/planoptimizer.go @@ -16,7 +16,6 @@ import ( //TODO(pok) push filter down into join condition if terms reference either side of join //TODO(pok) push order by down as far as possible -//TODO(pok) handle the case of the order by expressions not being in a projection list //TODO(pok) you can't group by _id in PQL, so we need to not use a PQL group by operator here //TODO(pok) move constant folding to in here @@ -1052,7 +1051,7 @@ func fixProjectionReferences(ctx context.Context, a *ExecutionPlanner, n types.P return thisNode, false, nil // everything else that can be a child of projection - case *PlanOpRelAlias, *PlanOpFilter, *PlanOpPQLTableScan, *PlanOpPQLDistinctScan, *PlanOpNestedLoops: + case *PlanOpRelAlias, *PlanOpFilter, *PlanOpPQLTableScan, *PlanOpPQLDistinctScan, *PlanOpNestedLoops, *PlanOpOrderBy: exprs, same, err := fixFieldRefIndexesOnExpressions(ctx, scope, a, childOp.Schema(), thisNode.Projections...) if err != nil { return thisNode, true, err @@ -1073,6 +1072,44 @@ func fixProjectionReferences(ctx context.Context, a *ExecutionPlanner, n types.P func fixFieldRefs(ctx context.Context, a *ExecutionPlanner, n types.PlanOperator, scope *OptimizerScope) (types.PlanOperator, bool, error) { return TransformPlanOp(n, func(node types.PlanOperator) (types.PlanOperator, bool, error) { switch thisNode := node.(type) { + case *PlanOpOrderBy: + switch childOp := thisNode.ChildOp.(type) { + case *PlanOpProjection: + expressions := thisNode.Expressions() + + for _, ex := range expressions { + ref, ok := ex.(*qualifiedRefPlanExpression) + if !ok { + return nil, true, sql3.NewErrInternalf("unexpected expression type '%T'", ex) + } + for i, proj := range childOp.Projections { + if strings.EqualFold(ref.String(), proj.String()) { + ref.columnIndex = i + break + } + } + } + newNode, err := thisNode.WithUpdatedExpressions(expressions...) + if err != nil { + return nil, true, err + } + return newNode, false, nil + + default: + // fix references for the expressions referenced in the order by list + schema := childOp.Schema() + expressions := thisNode.Expressions() + fixed, same, err := fixFieldRefIndexesOnExpressions(ctx, scope, a, schema, expressions...) + if err != nil { + return nil, true, err + } + newNode, err := thisNode.WithUpdatedExpressions(fixed...) + if err != nil { + return nil, true, err + } + return newNode, same, nil + } + case *PlanOpFilter: // fix references for the expressions referenced in the filter predicate expression schema := thisNode.Schema() diff --git a/sql3/test/defs/defs_orderby.go b/sql3/test/defs/defs_orderby.go index 8d2deb7fa..aded96bb8 100644 --- a/sql3/test/defs/defs_orderby.go +++ b/sql3/test/defs/defs_orderby.go @@ -14,10 +14,10 @@ var orderByTests = TableTest{ srcHdr("a_decimal", fldTypeDecimal2), ), srcRows( - srcRow(int64(1), int64(11), []int64{11, 12, 13}, int64(101), "str1", []string{"a1", "b1", "c1"}, float64(123.45)), - srcRow(int64(2), int64(22), []int64{21, 22, 23}, int64(201), "str2", []string{"a2", "b2", "c2"}, float64(234.56)), - srcRow(int64(3), int64(33), []int64{31, 32, 33}, int64(301), "str3", []string{"a3", "b3", "c3"}, float64(345.67)), - srcRow(int64(4), int64(44), []int64{41, 42, 43}, int64(401), "str4", []string{"a4", "b4", "c4"}, float64(456.78)), + srcRow(int64(1), int64(44), []int64{11, 12, 13}, int64(101), "str1", []string{"a1", "b1", "c1"}, float64(123.45)), + srcRow(int64(2), int64(33), []int64{21, 22, 23}, int64(201), "str2", []string{"a2", "b2", "c2"}, float64(234.56)), + srcRow(int64(3), int64(21), []int64{31, 32, 33}, int64(301), "str3", []string{"a3", "b3", "c3"}, float64(345.67)), + srcRow(int64(4), int64(10), []int64{41, 42, 43}, int64(401), "str4", []string{"a4", "b4", "c4"}, float64(456.78)), ), ), SQLTests: []SQLTest{ @@ -35,5 +35,127 @@ var orderByTests = TableTest{ ), ExpErr: "unable to sort a column of type 'idset'", }, + { + SQLs: sqls( + "select an_int from order_by_test order by an_id asc", + ), + ExpHdrs: hdrs( + hdr("an_int", fldTypeInt), + ), + ExpRows: rows( + row(int64(44)), + row(int64(33)), + row(int64(21)), + row(int64(10)), + ), + Compare: CompareExactOrdered, + }, + { + SQLs: sqls( + "select an_int, an_id from order_by_test order by a_decimal asc", + ), + ExpHdrs: hdrs( + hdr("an_int", fldTypeInt), + hdr("an_id", fldTypeID), + ), + ExpRows: rows( + row(int64(44), int64(101)), + row(int64(33), int64(201)), + row(int64(21), int64(301)), + row(int64(10), int64(401)), + ), + Compare: CompareExactOrdered, + }, + { + SQLs: sqls( + "select an_int + 1 as foo, an_id from order_by_test order by foo asc, a_decimal asc", + ), + ExpHdrs: hdrs( + hdr("foo", fldTypeInt), + hdr("an_id", fldTypeID), + ), + ExpRows: rows( + row(int64(11), int64(401)), + row(int64(22), int64(301)), + row(int64(34), int64(201)), + row(int64(45), int64(101)), + ), + Compare: CompareExactOrdered, + }, + { + SQLs: sqls( + "select an_int from order_by_test order by an_int asc", + ), + ExpHdrs: hdrs( + hdr("an_int", fldTypeInt), + ), + ExpRows: rows( + row(int64(10)), + row(int64(21)), + row(int64(33)), + row(int64(44)), + ), + Compare: CompareExactOrdered, + }, + { + SQLs: sqls( + "select an_int as foo from order_by_test order by foo asc", + ), + ExpHdrs: hdrs( + hdr("foo", fldTypeInt), + ), + ExpRows: rows( + row(int64(10)), + row(int64(21)), + row(int64(33)), + row(int64(44)), + ), + Compare: CompareExactOrdered, + }, + { + SQLs: sqls( + "select an_int as foo from order_by_test order by 1 asc", + ), + ExpHdrs: hdrs( + hdr("foo", fldTypeInt), + ), + ExpRows: rows( + row(int64(10)), + row(int64(21)), + row(int64(33)), + row(int64(44)), + ), + Compare: CompareExactOrdered, + }, + { + SQLs: sqls( + "select an_int + 1 from order_by_test order by 1 asc", + ), + ExpHdrs: hdrs( + hdr("", fldTypeInt), + ), + ExpRows: rows( + row(int64(11)), + row(int64(22)), + row(int64(34)), + row(int64(45)), + ), + Compare: CompareExactOrdered, + }, + { + SQLs: sqls( + "select an_int + 1 as bar from order_by_test order by bar desc", + ), + ExpHdrs: hdrs( + hdr("bar", fldTypeInt), + ), + ExpRows: rows( + row(int64(45)), + row(int64(34)), + row(int64(22)), + row(int64(11)), + ), + Compare: CompareExactOrdered, + }, }, }