featurebase/sql3/planner/compileselect.go
2022-09-30 11:20:58 -07:00

376 lines
9.4 KiB
Go

// Copyright 2022 Molecula Corp. All rights reserved.
package planner
import (
"context"
pilosa "github.com/featurebasedb/featurebase/v3"
"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
func (p *ExecutionPlanner) compileSelectStatement(stmt *parser.SelectStatement, isSubquery bool) (types.PlanOperator, error) {
query := NewPlanOpQuery(NewPlanOpNullTable(), p.sql)
//p.pushPlannerScope(query)
p.scopeStack.push(query)
// handle select list
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")
}
if c.Alias != nil {
planExpr = newAliasPlanExpression(c.Alias.Name, planExpr)
}
projections = append(projections, planExpr)
}
// group by clause.
groupByExprs := make([]types.PlanExpression, 0)
for _, expr := range stmt.GroupByExprs {
switch expr := expr.(type) {
case *parser.QualifiedRef:
groupByExprs = append(groupByExprs, newQualifiedRefPlanExpression(expr.Table.Name, expr.Column.Name, expr.ColumnIndex, expr.DataType()))
default:
return nil, sql3.NewErrInternalf("unsupported expression type in GROUP BY clause: %T", expr)
}
}
var err error
if stmt.Having.IsValid() {
query.AddWarning("HAVING is not yet supported")
}
// handle distinct
if stmt.Distinct.IsValid() {
query.AddWarning("DISTINCT not yet implemented")
}
// source expression last
source, err := p.compileSelectSource(query, stmt.WhereExpr, stmt.Source)
if err != nil {
return nil, err
}
//do we have straight projection or a group by?
var compiledOp types.PlanOperator
if len(query.aggregates) > 0 {
compiledOp = NewPlanOpProjection(projections, NewPlanOpGroupBy(query.aggregates, groupByExprs, source))
} else {
compiledOp = NewPlanOpProjection(projections, source)
}
// handle order by
if len(stmt.OrderingTerms) > 0 {
orderByFields := make([]*OrderByExpression, 0)
for _, ot := range stmt.OrderingTerms {
otExpr, err := p.compileExpr(ot.X)
if err != nil {
return nil, err
}
f := &OrderByExpression{
Expr: otExpr,
}
f.Order = orderByAsc
if ot.Desc.IsValid() {
f.Order = orderByDesc
}
orderByFields = append(orderByFields, f)
}
compiledOp = NewPlanOpOrderBy(orderByFields, compiledOp)
}
//insert the top operator if it exists
if stmt.Top.IsValid() {
topExpr, err := p.compileExpr(stmt.TopExpr)
if err != nil {
return nil, err
}
compiledOp = NewPlanOpTop(topExpr, compiledOp)
}
//pop the scope
//p.popPlannerScope()
_ = p.scopeStack.pop()
//if it is a subquery, don't wrap in a PlanOpQuery
if isSubquery {
return compiledOp, nil
}
children := []types.PlanOperator{
compiledOp,
}
return query.WithChildren(children...)
}
func (p *ExecutionPlanner) compileSelectSource(scope *PlanOpQuery, whereExpr parser.Expr, source parser.Source) (types.PlanOperator, error) {
if source == nil {
return NewPlanOpNullTable(), nil
}
switch sourceExpr := source.(type) {
case *parser.JoinClause:
topOp, err := p.compileSelectSource(scope, whereExpr, sourceExpr.X)
if err != nil {
return nil, err
}
bottomOp, err := p.compileSelectSource(scope, whereExpr, sourceExpr.Y)
if err != nil {
return nil, err
}
scope.AddWarning("🦖 here there be dragons! JOINS are experimental.")
if sourceExpr.Constraint == nil {
scope.AddWarning("⚠️ cartesian products are never a good idea - are you missing a join constraint?")
}
return NewPlanOpNestedLoops(topOp, bottomOp), nil
case *parser.QualifiedTableName:
//get all the qualified refs that refer to this table
extractColumns := []types.PlanExpression{}
for _, r := range scope.referenceList {
if sourceExpr.MatchesTablenameOrAlias(r.tableName) {
extractColumns = append(extractColumns, r)
}
}
// handle the where clause
where, err := p.compileExpr(whereExpr)
if err != nil {
return nil, err
}
//get for the table name
tableName := parser.IdentName(sourceExpr.Name)
return NewPlanOpPQLTableScan(p, tableName, extractColumns, where), nil
case *parser.ParenSource:
return p.compileSelectSource(scope, whereExpr, sourceExpr.X)
case *parser.SelectStatement:
subQuery, err := p.compileSelectStatement(sourceExpr, true)
if err != nil {
return nil, err
}
return NewPlanOpSubquery(subQuery), nil
default:
return nil, sql3.NewErrInternalf("unexpected source type: %T", source)
}
}
func (p *ExecutionPlanner) analyzeSource(source parser.Source) error {
if source == nil {
return nil
}
switch source := source.(type) {
case *parser.JoinClause:
err := p.analyzeSource(source.X)
if err != nil {
return err
}
err = p.analyzeSource(source.Y)
if err != nil {
return err
}
return nil
case *parser.ParenSource:
err := p.analyzeSource(source.X)
if err != nil {
return err
}
return nil
case *parser.QualifiedTableName:
//check table exists
tableName := parser.IdentName(source.Name)
table, err := p.schemaAPI.IndexInfo(context.Background(), tableName)
if err != nil {
if errors.Is(err, pilosa.ErrIndexNotFound) {
return sql3.NewErrTableNotFound(source.Name.NamePos.Line, source.Name.NamePos.Column, tableName)
}
return err
}
// populate the output columns from the source
for idx, fld := range table.Fields {
soc := &parser.SourceOutputColumn{
TableName: tableName,
ColumnName: fld.Name,
ColumnIndex: idx,
Datatype: fieldSQLDataType(fld),
}
source.OutputColumns = append(source.OutputColumns, soc)
}
return nil
case *parser.SelectStatement:
err := p.analyzeSelectStatement(source)
if err != nil {
return err
}
return nil
default:
return sql3.NewErrInternalf("unexpected source type: %T", source)
}
}
func (p *ExecutionPlanner) analyzeSelectStatement(stmt *parser.SelectStatement) error {
//analyze source first - needed for name resolution
err := p.analyzeSource(stmt.Source)
if err != nil {
return err
}
if err := p.analyzeSelectStatementWildcards(stmt); err != nil {
return err
}
for _, col := range stmt.Columns {
expr, err := p.analyzeExpression(col.Expr, stmt)
if err != nil {
return err
}
if expr != nil {
col.Expr = expr
}
}
expr, err := p.analyzeExpression(stmt.TopExpr, stmt)
if err != nil {
return err
}
if expr != nil {
if !(expr.IsLiteral() && typeIsInteger(expr.DataType())) {
return sql3.NewErrIntegerLiteral(stmt.TopExpr.Pos().Line, stmt.TopExpr.Pos().Column)
}
stmt.TopExpr = expr
}
expr, err = p.analyzeExpression(stmt.WhereExpr, stmt)
if err != nil {
return err
}
stmt.WhereExpr = expr
for i, g := range stmt.GroupByExprs {
expr, err = p.analyzeExpression(g, stmt)
if err != nil {
return err
}
if expr != nil {
stmt.GroupByExprs[i] = expr
}
}
expr, err = p.analyzeExpression(stmt.HavingExpr, stmt)
if err != nil {
return err
}
if expr != nil {
stmt.HavingExpr = expr
}
for _, term := range stmt.OrderingTerms {
expr, err = p.analyzeExpression(term.X, stmt)
if err != nil {
return err
}
if expr != nil {
term.X = expr
}
}
return nil
}
func (p *ExecutionPlanner) analyzeSelectStatementWildcards(stmt *parser.SelectStatement) error {
if !stmt.HasWildcard() {
return nil
}
// replace wildcards with column references
newColumns := make([]*parser.ResultColumn, 0, len(stmt.Columns))
for _, col := range stmt.Columns {
//handle the case of unqualified *
if col.Star.IsValid() {
cols, err := p.columnsFromSource(stmt.Source)
if err != nil {
return err
}
newColumns = append(newColumns, cols...)
} else {
//handle the case of a qualified ref with a *
if ref, ok := col.Expr.(*parser.QualifiedRef); ok && ref.Star.IsValid() {
refName := parser.IdentName(ref.Table)
src := stmt.Source.SourceFromAlias(refName)
if src == nil {
return sql3.NewErrTableNotFound(ref.Table.NamePos.Line, ref.Table.NamePos.Column, refName)
}
cols, err := p.columnsFromSource(src)
if err != nil {
return err
}
newColumns = append(newColumns, cols...)
} else {
//add the column as is...
newColumns = append(newColumns, col)
}
}
}
stmt.Columns = newColumns
return nil
}
func (p *ExecutionPlanner) columnsFromSource(source parser.Source) ([]*parser.ResultColumn, error) {
result := []*parser.ResultColumn{}
switch src := source.(type) {
case *parser.JoinClause:
return nil, sql3.NewErrInternal("joins are not currently supported")
case *parser.ParenSource:
for _, oc := range src.PossibleOutputColumns() {
result = append(result, &parser.ResultColumn{
Expr: &parser.QualifiedRef{
Table: &parser.Ident{Name: oc.TableName},
Column: &parser.Ident{Name: oc.ColumnName},
ColumnIndex: oc.ColumnIndex,
},
})
}
return result, nil
case *parser.QualifiedTableName:
for _, oc := range src.PossibleOutputColumns() {
result = append(result, &parser.ResultColumn{
Expr: &parser.QualifiedRef{
Table: &parser.Ident{Name: oc.TableName},
Column: &parser.Ident{Name: oc.ColumnName},
ColumnIndex: oc.ColumnIndex,
},
})
}
return result, nil
case *parser.SelectStatement:
return nil, sql3.NewErrInternal("sub-selects are not currently supported")
default:
return nil, sql3.NewErrInternalf("unexpected source type: %T", source)
}
}