ORDER BY ....what now!? (fb-1954) (#2257)

* can now order by columns not in the select list

* added testing coverage
This commit is contained in:
Pat Okeeffe 2023-02-17 13:35:36 -06:00 committed by GitHub
parent c749e07d03
commit e755fecf63
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 434 additions and 96 deletions

View file

@ -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

View file

@ -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)
}
}

View file

@ -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
}

View file

@ -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) {

View file

@ -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{
{

View file

@ -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
}
}

View file

@ -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()

View file

@ -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,
},
},
}