mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-08-28 10:54:59 +00:00
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:
parent
c749e07d03
commit
e755fecf63
8 changed files with 434 additions and 96 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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{
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue