featurebase/sql3/planner/expressionpql.go
Pat Okeeffe 5c74b64722
A bug fix roundup (#2242)
* fb-1940 re-implemented some changes that got missed private-public

* fb-1939 fixes to between + decimals

* fb-1935 - avg() on and id type + fixed some tests

* fb-1953 add min/max for string types

* fb-1938 - remove internal_type column from show columns

* fb-1964 - fix space_used in fb_cluster_nodes to be int

* fb-1996 - make sure all Idents that are being used as object references to schema objects are lowercased

* fixed failing test

* added some missed changes

* fb-1969 found another case issue with identifier used for column idents
2023-02-13 12:28:34 -06:00

507 lines
12 KiB
Go

// Copyright 2022 Molecula Corp. All rights reserved.
package planner
import (
"context"
"strconv"
"strings"
"github.com/featurebasedb/featurebase/v3/pql"
"github.com/featurebasedb/featurebase/v3/sql3"
"github.com/featurebasedb/featurebase/v3/sql3/parser"
"github.com/featurebasedb/featurebase/v3/sql3/planner/types"
)
// generatePQLCallFromExpr returns a *pql.Call tree for a given plan expression
func (p *ExecutionPlanner) generatePQLCallFromExpr(ctx context.Context, expr types.PlanExpression) (_ *pql.Call, err error) {
if expr == nil {
return nil, nil
}
switch expr := expr.(type) {
case *binOpPlanExpression:
return p.generatePQLCallFromBinaryExpr(ctx, expr)
case *callPlanExpression:
switch strings.ToUpper(expr.name) {
case "SETCONTAINS":
col := expr.args[0].(*qualifiedRefPlanExpression)
pqlValue, err := planExprToValue(expr.args[1])
if err != nil {
return nil, err
}
return &pql.Call{
Name: "Row",
Args: map[string]interface{}{
col.columnName: pqlValue,
},
}, nil
case "SETCONTAINSALL":
col := expr.args[0].(*qualifiedRefPlanExpression)
set, ok := expr.args[1].(*exprSetLiteralPlanExpression)
if !ok {
return nil, sql3.NewErrInternalf("unexpected argument type '%T'", expr.args[1])
}
call := &pql.Call{
Name: "Intersect",
Children: []*pql.Call{},
}
for _, m := range set.members {
pqlValue, err := planExprToValue(m)
if err != nil {
return nil, err
}
rc := &pql.Call{
Name: "Row",
Args: map[string]interface{}{
col.columnName: pqlValue,
},
}
call.Children = append(call.Children, rc)
}
return call, nil
case "SETCONTAINSANY":
col := expr.args[0].(*qualifiedRefPlanExpression)
set, ok := expr.args[1].(*exprSetLiteralPlanExpression)
if !ok {
return nil, sql3.NewErrInternalf("unexpected argument type '%T'", expr.args[1])
}
call := &pql.Call{
Name: "Union",
Children: []*pql.Call{},
}
for _, m := range set.members {
pqlValue, err := planExprToValue(m)
if err != nil {
return nil, err
}
rc := &pql.Call{
Name: "Row",
Args: map[string]interface{}{
col.columnName: pqlValue,
},
}
call.Children = append(call.Children, rc)
}
return call, nil
default:
return nil, sql3.NewErrInternalf("unsupported scalar function '%s'", expr.name)
}
case *inOpPlanExpression:
// lhs will be qualified ref
lhs, ok := expr.lhs.(*qualifiedRefPlanExpression)
if !ok {
return nil, sql3.NewErrInternalf("unexpected lhs %T", expr.lhs)
}
// rhs is expression list - need to convert to a big OR
list, ok := expr.rhs.(*exprListPlanExpression)
if !ok {
return nil, sql3.NewErrInternalf("unexpected argument type '%T'", expr.rhs)
}
// if it is the _id column, we can use ConstRow with a list
if strings.EqualFold(lhs.columnName, "_id") {
values := make([]interface{}, len(list.exprs))
for i, m := range list.exprs {
pqlValue, err := planExprToValue(m)
if err != nil {
return nil, err
}
values[i] = pqlValue
}
call := &pql.Call{
Name: "ConstRow",
Args: map[string]interface{}{
"columns": values,
},
Type: pql.PrecallGlobal,
}
return call, nil
}
// otherwise, OR them all
call := &pql.Call{
Name: "Union",
Children: []*pql.Call{},
}
for _, m := range list.exprs {
pqlValue, err := planExprToValue(m)
if err != nil {
return nil, err
}
rc := &pql.Call{
Name: "Row",
Args: map[string]interface{}{
lhs.columnName: pqlValue,
},
}
call.Children = append(call.Children, rc)
}
return call, nil
default:
return nil, sql3.NewErrInternalf("unexpected expression type: %T", expr)
}
}
func (p *ExecutionPlanner) generatePQLCallFromBinaryExpr(ctx context.Context, expr *binOpPlanExpression) (_ *pql.Call, err error) {
switch op := expr.op; op {
case parser.AND, parser.OR:
name := "Intersect"
if op == parser.OR {
name = "Union"
}
x, err := p.generatePQLCallFromExpr(ctx, expr.lhs)
if err != nil {
return nil, err
}
y, err := p.generatePQLCallFromExpr(ctx, expr.rhs)
if err != nil {
return nil, err
}
return &pql.Call{
Name: name,
Children: []*pql.Call{x, y},
}, nil
case parser.EQ:
lhs, ok := expr.lhs.(*qualifiedRefPlanExpression)
if !ok {
return nil, sql3.NewErrInternalf("unexpected lhs %T", expr.lhs)
}
pqlValue, err := planExprToValue(expr.rhs)
if err != nil {
return nil, err
}
switch typ := expr.lhs.Type().(type) {
case *parser.DataTypeInt:
return &pql.Call{
Name: "Row",
Args: map[string]interface{}{
lhs.columnName: &pql.Condition{
Op: pql.EQ,
Value: pqlValue,
},
},
}, nil
case *parser.DataTypeID:
if strings.EqualFold(lhs.columnName, "_id") {
return &pql.Call{
Name: "ConstRow",
Args: map[string]interface{}{
"columns": []interface{}{pqlValue},
},
Type: pql.PrecallGlobal,
}, nil
}
return &pql.Call{
Name: "Row",
Args: map[string]interface{}{
lhs.columnName: pqlValue,
},
}, nil
case *parser.DataTypeString:
if strings.EqualFold(lhs.columnName, "_id") {
return &pql.Call{
Name: "ConstRow",
Args: map[string]interface{}{
"columns": []interface{}{pqlValue},
},
Type: pql.PrecallGlobal,
}, nil
}
return &pql.Call{
Name: "Row",
Args: map[string]interface{}{
lhs.columnName: pqlValue,
},
}, nil
case *parser.DataTypeTimestamp:
return &pql.Call{
Name: "Row",
Args: map[string]interface{}{
lhs.columnName: &pql.Condition{
Op: pql.EQ,
Value: pqlValue,
},
},
}, nil
case *parser.DataTypeBool:
return &pql.Call{
Name: "Row",
Args: map[string]interface{}{
lhs.columnName: pqlValue,
},
}, nil
case *parser.DataTypeDecimal:
val, ok := pqlValue.(float64)
if !ok {
return nil, sql3.NewErrInternalf("unexpected type '%T", pqlValue)
}
d := pql.FromFloat64(val)
return &pql.Call{
Name: "Row",
Args: map[string]interface{}{
lhs.columnName: &pql.Condition{
Op: pql.EQ,
Value: d,
},
},
}, nil
default:
return nil, sql3.NewErrInternalf("unsupported type for binary expression: %v (%T)", typ, typ)
}
case parser.NE:
lhs, ok := expr.lhs.(*qualifiedRefPlanExpression)
if !ok {
return nil, sql3.NewErrInternalf("unexpected lhs %T", expr.lhs)
}
pqlValue, err := planExprToValue(expr.rhs)
if err != nil {
return nil, err
}
switch typ := expr.lhs.Type().(type) {
case *parser.DataTypeInt:
return &pql.Call{
Name: "Row",
Args: map[string]interface{}{
lhs.columnName: &pql.Condition{
Op: pql.NEQ,
Value: pqlValue,
},
},
}, nil
case *parser.DataTypeID:
return nil, sql3.NewErrUnsupported(0, 0, true, "not equal operator on id typed columns")
case *parser.DataTypeString:
return nil, sql3.NewErrUnsupported(0, 0, true, "not equal operator on string typed columns")
case *parser.DataTypeTimestamp:
return &pql.Call{
Name: "Row",
Args: map[string]interface{}{
lhs.columnName: &pql.Condition{
Op: pql.NEQ,
Value: pqlValue,
},
},
}, nil
case *parser.DataTypeBool:
return nil, sql3.NewErrUnsupported(0, 0, true, "not equal operator on bool typed columns")
case *parser.DataTypeDecimal:
val, ok := pqlValue.(float64)
if !ok {
return nil, sql3.NewErrInternalf("unexpected type '%T", pqlValue)
}
d := pql.FromFloat64(val)
return &pql.Call{
Name: "Row",
Args: map[string]interface{}{
lhs.columnName: &pql.Condition{
Op: pql.NEQ,
Value: d,
},
},
}, nil
default:
return nil, sql3.NewErrInternalf("unsupported type for binary expression: %v (%T)", typ, typ)
}
case parser.LT, parser.LE, parser.GT, parser.GE:
lhs, ok := expr.lhs.(*qualifiedRefPlanExpression)
if !ok {
return nil, sql3.NewErrInternalf("unexpected lhs %T", expr.lhs)
}
pqlValue, err := planExprToValue(expr.rhs)
if err != nil {
return nil, err
}
switch typ := expr.lhs.Type().(type) {
case *parser.DataTypeInt:
pqlOp, err := sqlToPQLOp(op)
if err != nil {
return nil, err
}
return &pql.Call{
Name: "Row",
Args: map[string]interface{}{
lhs.columnName: &pql.Condition{
Op: pqlOp,
Value: pqlValue,
},
},
}, nil
case *parser.DataTypeID:
return nil, sql3.NewErrUnsupported(0, 0, false, "range queries on id typed columns")
case *parser.DataTypeString:
return nil, sql3.NewErrUnsupported(0, 0, false, "range queries on string typed columns")
case *parser.DataTypeTimestamp:
pqlOp, err := sqlToPQLOp(op)
if err != nil {
return nil, err
}
return &pql.Call{
Name: "Row",
Args: map[string]interface{}{
lhs.columnName: &pql.Condition{
Op: pqlOp,
Value: pqlValue,
},
},
}, nil
case *parser.DataTypeDecimal:
pqlOp, err := sqlToPQLOp(op)
if err != nil {
return nil, err
}
val, ok := pqlValue.(float64)
if !ok {
return nil, sql3.NewErrInternalf("unexpected type '%T", pqlValue)
}
d := pql.FromFloat64(val)
return &pql.Call{
Name: "Row",
Args: map[string]interface{}{
lhs.columnName: &pql.Condition{
Op: pqlOp,
Value: d,
},
},
}, nil
default:
return nil, sql3.NewErrInternalf("unsupported type for binary expression: %v (%T)", typ, typ)
}
case parser.BITAND, parser.BITOR, parser.BITNOT, parser.LSHIFT, parser.RSHIFT:
return nil, sql3.NewErrInternal("bitwise operators are not supported here")
case parser.PLUS, parser.MINUS, parser.STAR, parser.SLASH, parser.REM: // +
return nil, sql3.NewErrInternal("aritmetic operators are not supported here")
case parser.CONCAT:
return nil, sql3.NewErrInternal("concatenation operator is not supported here")
case parser.IN, parser.NOTIN:
return nil, sql3.NewErrInternal("IN operator is not supported")
case parser.IS, parser.ISNOT:
lhs, ok := expr.lhs.(*qualifiedRefPlanExpression)
if !ok {
return nil, sql3.NewErrInternalf("unexpected lhs %T", expr.lhs)
}
pqlOp := pql.EQ
if op == parser.ISNOT {
pqlOp = pql.NEQ
}
switch typ := expr.lhs.Type().(type) {
case *parser.DataTypeID:
if strings.EqualFold(lhs.columnName, "_id") {
return nil, sql3.NewErrInvalidColumnInFilterExpression(0, 0, "_id", "is/is not null")
}
return nil, sql3.NewErrInvalidTypeInFilterExpression(0, 0, typ.TypeDescription(), "is/is not null")
case *parser.DataTypeInt, *parser.DataTypeDecimal, *parser.DataTypeTimestamp:
return &pql.Call{
Name: "Row",
Args: map[string]interface{}{
lhs.columnName: &pql.Condition{
Op: pqlOp,
Value: nil,
},
},
}, nil
default:
return nil, sql3.NewErrInvalidTypeInFilterExpression(0, 0, typ.TypeDescription(), "is/is not null")
}
case parser.BETWEEN, parser.NOTBETWEEN:
return nil, sql3.NewErrInternal("BETWEEN operator is not supported")
default:
return nil, sql3.NewErrInternalf("unexpected binary expression operator: %s", expr.op)
}
}
// sqlToPQLOp converts a parser operation token to PQL.
func sqlToPQLOp(op parser.Token) (pql.Token, error) {
switch op {
case parser.EQ:
return pql.EQ, nil
case parser.NE:
return pql.NEQ, nil
case parser.LT:
return pql.LT, nil
case parser.LE:
return pql.LTE, nil
case parser.GT:
return pql.GT, nil
case parser.GE:
return pql.GTE, nil
default:
return pql.ILLEGAL, sql3.NewErrInternalf("cannot convert SQL op %q to PQL", op)
}
}
// planExprToValue converts a literal parser expression node to a value.
func planExprToValue(expr types.PlanExpression) (interface{}, error) {
switch expr := expr.(type) {
case *intLiteralPlanExpression:
return expr.value, nil
case *stringLiteralPlanExpression:
return expr.value, nil
case *dateLiteralPlanExpression:
return expr.value, nil
case *boolLiteralPlanExpression:
return expr.value, nil
case *floatLiteralPlanExpression:
f, err := strconv.ParseFloat(expr.value, 64)
if err != nil {
return nil, err
}
return f, nil
default:
return nil, sql3.NewErrInternalf("cannot convert SQL expression %T to a literal value", expr)
}
}