diff --git a/executor.go b/executor.go index 7bec99f58..c63771c36 100644 --- a/executor.go +++ b/executor.go @@ -810,7 +810,7 @@ func (e *executor) executeCall(ctx context.Context, qcx *Qcx, index string, c *p return res, errors.Wrap(err, "executeUnionRows") case "ConstRow": statFn(CounterQueryConstRowTotal) - res, err := e.executeConstRow(ctx, index, c) + res, err := e.executeConstRow(ctx, qcx, index, c, opt) return res, errors.Wrap(err, "executeConstRow") case "Limit": statFn(CounterQueryLimitTotal) @@ -5356,14 +5356,96 @@ func (e *executor) executeNotShard(ctx context.Context, qcx *Qcx, index string, return existenceRow.Difference(row), nil } -func (e *executor) executeConstRow(ctx context.Context, index string, c *pql.Call) (res *Row, err error) { +func (e *executor) executeConstRow(ctx context.Context, qcx *Qcx, index string, c *pql.Call, opt *ExecOptions) (res *Row, err error) { + idx := e.Holder.Index(index) + if idx == nil { + return nil, newNotFoundError(ErrIndexNotFound, index) + } else if idx.existenceField() == nil { + ids, ok := c.Args["columns"].([]uint64) + if !ok { + return nil, errors.New("missing columns list") + } + return NewRow(ids...), nil + } // Fetch user-provided columns list. - ids, ok := c.Args["columns"].([]uint64) - if !ok { - return nil, errors.New("missing columns list") + availableBitmap := roaring.NewBitmap() + var records []uint64 + if opt.Remote { + ids, _ := c.Args["columns"].([]interface{}) + if len(ids) == 0 { + return NewRow(), nil + } + for _, idi := range ids { + id := idi.(int64) + availableBitmap.Add(uint64(id) / ShardWidth) + records = append(records, uint64(id)) + } + + } else { + ids, ok := c.Args["columns"].([]uint64) + if !ok { + return nil, errors.New("missing columns list") + } + if len(ids) == 0 { + return NewRow(), nil + } + for _, id := range ids { + availableBitmap.Add(id / ShardWidth) + records = append(records, id) + } } - return NewRow(ids...), nil + filteredShards := availableBitmap.Slice() + // Execute calls in bulk on each remote node and merge. + mapFn := func(ctx context.Context, shard uint64, mopt *mapOptions) (_ interface{}, err error) { + return e.executeConstRowShard(ctx, qcx, index, c, shard, NewRow(records...)) + } + // Merge returned results at coordinating node. + reduceFn := func(ctx context.Context, prev, v interface{}) interface{} { + other, _ := prev.(*Row) + if other == nil { + other = NewRow() + } + if err := ctx.Err(); err != nil { + return err + } + other.Merge(v.(*Row)) + return other + } + + other, err := e.mapReduce(ctx, index, filteredShards, c, opt, mapFn, reduceFn) + if err != nil { + return nil, err + } + if err != nil { + return nil, errors.Wrap(err, "map reduce") + } + + row, _ := other.(*Row) + + return row, nil +} + +func (e *executor) executeConstRowShard(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shard uint64, src *Row) (res *Row, err error) { + idx := e.Holder.Index(index) + var existenceRow *Row + existenceFrag := e.Holder.fragment(index, existenceFieldName, viewStandard, shard) + if existenceFrag == nil { + existenceRow = NewRow() + } else { + tx, finisher, err := qcx.GetTx(Txo{Write: !writable, Index: idx, Fragment: existenceFrag, Shard: shard}) + if err != nil { + return nil, err + } + + defer finisher(&err) + + if existenceRow, err = existenceFrag.row(tx, 0); err != nil { + return nil, err + } + + } + return src.Intersect(existenceRow), nil } func (e *executor) executeUnionRows(ctx context.Context, qcx *Qcx, index string, c *pql.Call, shards []uint64, opt *ExecOptions) (*Row, error) { diff --git a/executor_test.go b/executor_test.go index fc2aabe86..1b3b5db7d 100644 --- a/executor_test.go +++ b/executor_test.go @@ -38,9 +38,7 @@ import ( "github.com/stretchr/testify/assert" ) -var ( - TempDir = getTempDirString() -) +var TempDir = getTempDirString() func getTempDirString() (td *string) { tdflag := flag.Lookup("temp-dir") @@ -1024,7 +1022,6 @@ func TestExecutor(t *testing.T) { if columns := responses[4].Results[0].(*pilosa.Row).Columns(); !reflect.DeepEqual(columns, []uint64{2}) { t.Fatalf("unexpected columns: %+v", columns) } - }) // Ensure that ClearRow returns false when the row to clear needs translation. t.Run("WithKeys", func(t *testing.T) { @@ -1198,6 +1195,7 @@ func TestExecutor_Execute_ConstRow(t *testing.T) { c := test.MustRunCluster(t, 3) defer c.Close() + // without track existnce you just get back the columns you request c.CreateField(t, c.Idx(), pilosa.IndexOptions{}, "h") c.ImportBits(t, c.Idx(), "h", [][2]uint64{ {1, 2}, @@ -1205,7 +1203,27 @@ func TestExecutor_Execute_ConstRow(t *testing.T) { {5, 6}, }) - resp := c.Query(t, c.Idx(), `ConstRow(columns=[2,6])`) + resp := c.Query(t, c.Idx(), `ConstRow(columns=[2,6,7])`) + expect := []uint64{2, 6, 7} + got := resp.Results[0].(*pilosa.Row).Columns() + if !reflect.DeepEqual(expect, got) { + t.Errorf("expected %v but got %v", expect, got) + } +} + +func TestExecutor_Execute_ConstRowTrackExistence(t *testing.T) { + c := test.MustRunCluster(t, 3) + defer c.Close() + + // with track existnce you + c.CreateField(t, c.Idx(), pilosa.IndexOptions{TrackExistence: true}, "h") + c.ImportBits(t, c.Idx(), "h", [][2]uint64{ + {1, 2}, + {3, 4}, + {5, 6}, + }) + + resp := c.Query(t, c.Idx(), `ConstRow(columns=[2,6,7])`) expect := []uint64{2, 6} got := resp.Results[0].(*pilosa.Row).Columns() if !reflect.DeepEqual(expect, got) { @@ -1332,7 +1350,6 @@ func TestExecutor_Execute_Xor(t *testing.T) { t.Fatalf("unexpected columns: %+v", columns) } }) - } // Ensure a count query can be executed. @@ -1476,7 +1493,6 @@ func TestExecutor_Execute_Set(t *testing.T) { } else if !res.Results[0].(bool) { t.Fatalf("expected column changed with integer column key") } - }) }) } @@ -3049,7 +3065,8 @@ func TestExecutor_Execute_Row_BSIGroup(t *testing.T) { // EQ null if result, err := c.GetNode(0).API.Query(context.Background(), &pilosa.QueryRequest{Index: c.Idx(), Query: `Row(other == null)`}); err != nil { t.Fatal(err) - } else if !reflect.DeepEqual([]uint64{1, + } else if !reflect.DeepEqual([]uint64{ + 1, 50, ShardWidth, ShardWidth + 1, @@ -3148,7 +3165,7 @@ func TestExecutor_Execute_Row_BSIGroup(t *testing.T) { } for i, test := range tests { t.Run(fmt.Sprintf("#%d_%s", i, test.q), func(t *testing.T) { - var expected = []uint64{} + expected := []uint64{} if test.exp { expected = []uint64{0} } @@ -3585,7 +3602,7 @@ func TestExecutor_Execute_Remote_Row(t *testing.T) { }) t.Run("json format groupBy on timestamps", func(t *testing.T) { - //SUP-138 + // SUP-138 c.CreateField(t, c.Idx("t"), pilosa.IndexOptions{TrackExistence: true}, "timestamp", pilosa.OptFieldTypeTimestamp(pilosa.DefaultEpoch, pilosa.TimeUnitSeconds)) c.Query(t, c.Idx("t"), ` Set(8, timestamp='2021-01-27T08:00:00Z') @@ -3816,7 +3833,7 @@ func TestExecutor_Time_Clear_Quantums(t *testing.T) { defer c.Close() hldr := c.GetHolder(0) - var rangeTests = []struct { + rangeTests := []struct { quantum pilosa.TimeQuantum expected []uint64 }{ @@ -3942,10 +3959,11 @@ func TestExecutor_Execute_Existence(t *testing.T) { node0 := c.GetNode(0) // Set bits. - if _, err := node0.API.Query(context.Background(), &pilosa.QueryRequest{Index: c.Idx(), Query: `` + - fmt.Sprintf("Set(%d, f=%d)\n", 3, 10) + - fmt.Sprintf("Set(%d, f=%d)\n", ShardWidth+1, 10) + - fmt.Sprintf("Set(%d, f=%d)\n", ShardWidth+2, 20), + if _, err := node0.API.Query(context.Background(), &pilosa.QueryRequest{ + Index: c.Idx(), Query: `` + + fmt.Sprintf("Set(%d, f=%d)\n", 3, 10) + + fmt.Sprintf("Set(%d, f=%d)\n", ShardWidth+1, 10) + + fmt.Sprintf("Set(%d, f=%d)\n", ShardWidth+2, 20), }); err != nil { t.Fatal(err) } @@ -9199,6 +9217,8 @@ func TestExternalLookup(t *testing.T) { // Populate a field with some data that can be used in queries. c.CreateField(t, c.Idx(), pilosa.IndexOptions{TrackExistence: true}, "f") c.ImportBits(t, c.Idx(), "f", [][2]uint64{ + {0, 0}, + {0, 4}, {1, 1}, {1, 3}, {2, 2}, diff --git a/sql3/errors.go b/sql3/errors.go index ea7e506d3..f04d1e401 100644 --- a/sql3/errors.go +++ b/sql3/errors.go @@ -17,26 +17,27 @@ const ( ErrUnknownType errors.Code = "ErrUnknownType" ErrUnknownIdentifier errors.Code = "ErrUnknownIdentifier" - ErrTypeIncompatibleWithBitwiseOperator errors.Code = "ErrTypeIncompatibleWithBitwiseOperator" - ErrTypeIncompatibleWithLogicalOperator errors.Code = "ErrTypeIncompatibleWithLogicalOperator" - ErrTypeIncompatibleWithEqualityOperator errors.Code = "ErrTypeIncompatibleWithEqualityOperator" - ErrTypeIncompatibleWithComparisonOperator errors.Code = "ErrTypeIncompatibleWithComparisonOperator" - ErrTypeIncompatibleWithArithmeticOperator errors.Code = "ErrTypeIncompatibleWithArithmeticOperator" - ErrTypeIncompatibleWithConcatOperator errors.Code = "ErrTypeIncompatibleWithConcatOperator" - ErrTypeIncompatibleWithLikeOperator errors.Code = "ErrTypeIncompatibleWithLikeOperator" - ErrTypeIncompatibleWithBetweenOperator errors.Code = "ErrTypeIncompatibleWithBetweenOperator" - ErrTypeCannotBeUsedAsRangeSubscript errors.Code = "ErrTypeCannotBeUsedAsRangeSubscript" - ErrTypesAreNotEquatable errors.Code = "ErrTypesAreNotEquatable" - ErrTypeMismatch errors.Code = "ErrTypeMismatch" - ErrIncompatibleTypesForRangeSubscripts errors.Code = "ErrIncompatibleTypesForRangeSubscripts" - ErrExpressionListExpected errors.Code = "ErrExpressionListExpected" - ErrBooleanExpressionExpected errors.Code = "ErrBooleanExpressionExpected" - ErrIntExpressionExpected errors.Code = "ErrIntExpressionExpected" - ErrIntOrDecimalExpressionExpected errors.Code = "ErrIntOrDecimalExpressionExpected" - ErrIntOrDecimalOrTimestampExpressionExpected errors.Code = "ErrIntOrDecimalOrTimestampExpressionExpected" - ErrStringExpressionExpected errors.Code = "ErrStringExpressionExpected" - ErrSetExpressionExpected errors.Code = "ErrSetExpressionExpected" - ErrSingleRowExpected errors.Code = "ErrSingleRowExpected" + ErrTypeIncompatibleWithBitwiseOperator errors.Code = "ErrTypeIncompatibleWithBitwiseOperator" + ErrTypeIncompatibleWithLogicalOperator errors.Code = "ErrTypeIncompatibleWithLogicalOperator" + ErrTypeIncompatibleWithEqualityOperator errors.Code = "ErrTypeIncompatibleWithEqualityOperator" + ErrTypeIncompatibleWithComparisonOperator errors.Code = "ErrTypeIncompatibleWithComparisonOperator" + ErrTypeIncompatibleWithArithmeticOperator errors.Code = "ErrTypeIncompatibleWithArithmeticOperator" + ErrTypeIncompatibleWithConcatOperator errors.Code = "ErrTypeIncompatibleWithConcatOperator" + ErrTypeIncompatibleWithLikeOperator errors.Code = "ErrTypeIncompatibleWithLikeOperator" + ErrTypeIncompatibleWithBetweenOperator errors.Code = "ErrTypeIncompatibleWithBetweenOperator" + ErrTypeCannotBeUsedAsRangeSubscript errors.Code = "ErrTypeCannotBeUsedAsRangeSubscript" + ErrTypesAreNotEquatable errors.Code = "ErrTypesAreNotEquatable" + ErrTypeMismatch errors.Code = "ErrTypeMismatch" + ErrIncompatibleTypesForRangeSubscripts errors.Code = "ErrIncompatibleTypesForRangeSubscripts" + ErrExpressionListExpected errors.Code = "ErrExpressionListExpected" + ErrBooleanExpressionExpected errors.Code = "ErrBooleanExpressionExpected" + ErrIntExpressionExpected errors.Code = "ErrIntExpressionExpected" + ErrIntOrDecimalExpressionExpected errors.Code = "ErrIntOrDecimalExpressionExpected" + ErrIntOrDecimalOrTimestampExpressionExpected errors.Code = "ErrIntOrDecimalOrTimestampExpressionExpected" + ErrIntOrDecimalOrTimestampOrStringExpressionExpected errors.Code = "ErrIntOrDecimalOrTimestampOrStringExpressionExpected" + ErrStringExpressionExpected errors.Code = "ErrStringExpressionExpected" + ErrSetExpressionExpected errors.Code = "ErrSetExpressionExpected" + ErrSingleRowExpected errors.Code = "ErrSingleRowExpected" // type related errors @@ -369,7 +370,7 @@ func NewErrTypeCannotBeUsedAsRangeSubscript(line, col int, type1 string) error { func NewErrIncompatibleTypesForRangeSubscripts(line, col int, type1 string, type2 string) error { return errors.New( ErrIncompatibleTypesForRangeSubscripts, - fmt.Sprintf("[%d:%d] incompatible types '%s' and '%s' useds as range subscripts", line, col, type1, type2), + fmt.Sprintf("[%d:%d] incompatible types '%s' and '%s' used as range subscripts", line, col, type1, type2), ) } @@ -422,6 +423,13 @@ func NewErrIntOrDecimalOrTimestampExpressionExpected(line, col int) error { ) } +func NewErrIntOrDecimalOrTimestampOrStringExpressionExpected(line, col int) error { + return errors.New( + ErrIntOrDecimalOrTimestampOrStringExpressionExpected, + fmt.Sprintf("[%d:%d] integer, decimal, timestamp or string expression expected", line, col), + ) +} + func NewErrStringExpressionExpected(line, col int) error { return errors.New( ErrStringExpressionExpected, diff --git a/sql3/planner/compilealterdatabase.go b/sql3/planner/compilealterdatabase.go index d25524a18..45e38e4ac 100644 --- a/sql3/planner/compilealterdatabase.go +++ b/sql3/planner/compilealterdatabase.go @@ -4,6 +4,7 @@ package planner import ( "context" + "strings" "github.com/featurebasedb/featurebase/v3/dax" "github.com/featurebasedb/featurebase/v3/sql3" @@ -14,7 +15,7 @@ import ( // compileAlterDatabaseStatement compiles an ALTER DATABASE statement into a // PlanOperator. func (p *ExecutionPlanner) compileAlterDatabaseStatement(ctx context.Context, stmt *parser.AlterDatabaseStatement) (_ types.PlanOperator, err error) { - databaseName := parser.IdentName(stmt.Name) + databaseName := strings.ToLower(parser.IdentName(stmt.Name)) // does the database exist dbname := dax.DatabaseName(databaseName) diff --git a/sql3/planner/compilealtertable.go b/sql3/planner/compilealtertable.go index adfdce639..b3c215179 100644 --- a/sql3/planner/compilealtertable.go +++ b/sql3/planner/compilealtertable.go @@ -24,7 +24,7 @@ const ( // compileAlterTableStatement compiles an ALTER TABLE statement into a // PlanOperator. func (p *ExecutionPlanner) compileAlterTableStatement(ctx context.Context, stmt *parser.AlterTableStatement) (_ types.PlanOperator, err error) { - tableName := parser.IdentName(stmt.Name) + tableName := strings.ToLower(parser.IdentName(stmt.Name)) // does the table exist tname := dax.TableName(tableName) @@ -37,7 +37,7 @@ func (p *ExecutionPlanner) compileAlterTableStatement(ctx context.Context, stmt } if stmt.Drop.IsValid() { - columnName := parser.IdentName(stmt.DropColumnName) + columnName := strings.ToLower(parser.IdentName(stmt.DropColumnName)) // does this column exist found := false @@ -54,7 +54,7 @@ func (p *ExecutionPlanner) compileAlterTableStatement(ctx context.Context, stmt return NewPlanOpQuery(p, NewPlanOpAlterTable(p, tableName, alterOpDrop, columnName, "", nil), p.sql), nil } else if stmt.Add.IsValid() { col := stmt.ColumnDef - columnName := parser.IdentName(col.Name) + columnName := strings.ToLower(parser.IdentName(col.Name)) // does this column exist for _, f := range tbl.Fields { @@ -70,8 +70,8 @@ func (p *ExecutionPlanner) compileAlterTableStatement(ctx context.Context, stmt return NewPlanOpQuery(p, NewPlanOpAlterTable(p, tableName, alterOpAdd, "", columnName, column), p.sql), nil } else if stmt.Rename.IsValid() { - oldColumnName := parser.IdentName(stmt.OldColumnName) - newColumnName := parser.IdentName(stmt.NewColumnName) + oldColumnName := strings.ToLower(parser.IdentName(stmt.OldColumnName)) + newColumnName := strings.ToLower(parser.IdentName(stmt.NewColumnName)) return NewPlanOpQuery(p, NewPlanOpAlterTable(p, tableName, alterOpRename, oldColumnName, newColumnName, nil), p.sql), nil } else { return nil, sql3.NewErrInternal("unhandled alter operation") @@ -86,7 +86,7 @@ func (p *ExecutionPlanner) analyzeAlterTableStatement(stmt *parser.AlterTableSta //no checks for now } else if stmt.Add.IsValid() { col := stmt.ColumnDef - columnName := parser.IdentName(col.Name) + columnName := strings.ToLower(parser.IdentName(col.Name)) typeName := parser.IdentName(col.Type.Name) if !parser.IsValidTypeName(typeName) { return sql3.NewErrUnknownType(col.Type.Name.NamePos.Line, col.Type.Name.NamePos.Column, typeName) @@ -103,8 +103,8 @@ func (p *ExecutionPlanner) analyzeAlterTableStatement(stmt *parser.AlterTableSta } } else if stmt.Rename.IsValid() { //check the new and old are not the same - oldColumnName := parser.IdentName(stmt.OldColumnName) - newColumnName := parser.IdentName(stmt.NewColumnName) + oldColumnName := strings.ToLower(parser.IdentName(stmt.OldColumnName)) + newColumnName := strings.ToLower(parser.IdentName(stmt.NewColumnName)) if strings.EqualFold(oldColumnName, newColumnName) { return sql3.NewErrDuplicateColumn(stmt.NewColumnName.NamePos.Line, stmt.NewColumnName.NamePos.Column, newColumnName) } diff --git a/sql3/planner/compilebulkinsert.go b/sql3/planner/compilebulkinsert.go index 80e2ab6ff..b5cb7b1ea 100644 --- a/sql3/planner/compilebulkinsert.go +++ b/sql3/planner/compilebulkinsert.go @@ -20,7 +20,7 @@ import ( // compileBulkInsertStatement compiles a BULK INSERT statement into a // PlanOperator. func (p *ExecutionPlanner) compileBulkInsertStatement(ctx context.Context, stmt *parser.BulkInsertStatement) (_ types.PlanOperator, err error) { - tableName := parser.IdentName(stmt.Table) + tableName := strings.ToLower(parser.IdentName(stmt.Table)) tname := dax.TableName(tableName) tbl, err := p.schemaAPI.TableByName(ctx, tname) @@ -114,7 +114,7 @@ func (p *ExecutionPlanner) compileBulkInsertStatement(ctx context.Context, stmt for _, m := range stmt.Columns { for idx, fld := range tbl.Fields { if strings.EqualFold(string(fld.Name), m.Name) { - options.targetColumns = append(options.targetColumns, newQualifiedRefPlanExpression(tableName, m.Name, idx, fieldSQLDataType(pilosa.FieldToFieldInfo(fld)))) + options.targetColumns = append(options.targetColumns, newQualifiedRefPlanExpression(tableName, strings.ToLower(m.Name), idx, fieldSQLDataType(pilosa.FieldToFieldInfo(fld)))) break } } @@ -159,7 +159,7 @@ func (p *ExecutionPlanner) compileBulkInsertStatement(ctx context.Context, stmt // error if anything is invalid. func (p *ExecutionPlanner) analyzeBulkInsertStatement(ctx context.Context, stmt *parser.BulkInsertStatement) error { // check referred to table exists - tableName := parser.IdentName(stmt.Table) + tableName := strings.ToLower(parser.IdentName(stmt.Table)) tname := dax.TableName(tableName) tbl, err := p.schemaAPI.TableByName(ctx, tname) if err != nil { diff --git a/sql3/planner/compilecreatedatabase.go b/sql3/planner/compilecreatedatabase.go index f78127127..a235e174d 100644 --- a/sql3/planner/compilecreatedatabase.go +++ b/sql3/planner/compilecreatedatabase.go @@ -4,6 +4,7 @@ package planner import ( "strconv" + "strings" "github.com/featurebasedb/featurebase/v3/sql3" "github.com/featurebasedb/featurebase/v3/sql3/parser" @@ -13,7 +14,7 @@ import ( // compileCreateDatabaseStatement compiles a CREATE DATABASE statement into a // PlanOperator. func (p *ExecutionPlanner) compileCreateDatabaseStatement(stmt *parser.CreateDatabaseStatement) (_ types.PlanOperator, err error) { - databaseName := parser.IdentName(stmt.Name) + databaseName := strings.ToLower(parser.IdentName(stmt.Name)) failIfExists := !stmt.IfNotExists.IsValid() units := 0 diff --git a/sql3/planner/compilecreatetable.go b/sql3/planner/compilecreatetable.go index fd68ce173..4eb3b859c 100644 --- a/sql3/planner/compilecreatetable.go +++ b/sql3/planner/compilecreatetable.go @@ -26,7 +26,7 @@ type createTableField struct { // compileCreateTableStatement compiles a CREATE TABLE statement into a // PlanOperator. func (p *ExecutionPlanner) compileCreateTableStatement(ctx context.Context, stmt *parser.CreateTableStatement) (_ types.PlanOperator, err error) { - tableName := parser.IdentName(stmt.Name) + tableName := strings.ToLower(parser.IdentName(stmt.Name)) failIfExists := !stmt.IfNotExists.IsValid() // apply table options @@ -51,7 +51,7 @@ func (p *ExecutionPlanner) compileCreateTableStatement(ctx context.Context, stmt var columns = []*createTableField{} for _, col := range stmt.Columns { - columnName := parser.IdentName(col.Name) + columnName := strings.ToLower(parser.IdentName(col.Name)) typeName := parser.IdentName(col.Type.Name) if strings.ToLower(columnName) == "_id" { @@ -78,7 +78,7 @@ func (p *ExecutionPlanner) compileCreateTableStatement(ctx context.Context, stmt // compiles a column def func (p *ExecutionPlanner) compileColumn(ctx context.Context, col *parser.ColumnDefinition) (*createTableField, error) { var err error - columnName := parser.IdentName(col.Name) + columnName := strings.ToLower(parser.IdentName(col.Name)) typeName := parser.IdentName(col.Type.Name) column := &createTableField{ @@ -251,7 +251,7 @@ func (p *ExecutionPlanner) analyzeCreateTableStatement(stmt *parser.CreateTableS //iterate columns, check types, check constraints, ensure we have no dupe names and make sure there is an _id column checkedColumns := make(map[string]string) for _, col := range stmt.Columns { - columnName := parser.IdentName(col.Name) + columnName := strings.ToLower(parser.IdentName(col.Name)) _, ok := checkedColumns[strings.ToLower(columnName)] if ok { return sql3.NewErrDuplicateColumn(col.Name.NamePos.Line, col.Name.NamePos.Column, columnName) diff --git a/sql3/planner/compilecreateview.go b/sql3/planner/compilecreateview.go index 04bd7ad27..6201a2f40 100644 --- a/sql3/planner/compilecreateview.go +++ b/sql3/planner/compilecreateview.go @@ -4,6 +4,7 @@ package planner import ( "context" + "strings" "github.com/featurebasedb/featurebase/v3/sql3/parser" "github.com/featurebasedb/featurebase/v3/sql3/planner/types" @@ -11,7 +12,7 @@ import ( // compileCreateViewStatement compiles a parser.CreateViewStatement AST into a PlanOperator func (p *ExecutionPlanner) compileCreateViewStatement(stmt *parser.CreateViewStatement) (types.PlanOperator, error) { - viewName := parser.IdentName(stmt.Name) + viewName := strings.ToLower(parser.IdentName(stmt.Name)) view := &viewSystemObject{ name: viewName, } @@ -29,7 +30,7 @@ func (p *ExecutionPlanner) compileCreateViewStatement(stmt *parser.CreateViewSta // compileAlterViewStatement compiles a parser.AlterViewStatement AST into a PlanOperator func (p *ExecutionPlanner) compileAlterViewStatement(stmt *parser.AlterViewStatement) (types.PlanOperator, error) { - viewName := parser.IdentName(stmt.Name) + viewName := strings.ToLower(parser.IdentName(stmt.Name)) view := &viewSystemObject{ name: viewName, } diff --git a/sql3/planner/compiledelete.go b/sql3/planner/compiledelete.go index 1280480ae..ddc5fb692 100644 --- a/sql3/planner/compiledelete.go +++ b/sql3/planner/compiledelete.go @@ -4,6 +4,7 @@ package planner import ( "context" + "strings" "github.com/featurebasedb/featurebase/v3/sql3/parser" "github.com/featurebasedb/featurebase/v3/sql3/planner/types" @@ -13,7 +14,7 @@ import ( func (p *ExecutionPlanner) compileDeleteStatement(stmt *parser.DeleteStatement) (types.PlanOperator, error) { query := NewPlanOpQuery(p, NewPlanOpNullTable(), p.sql) - tableName := parser.IdentName(stmt.TableName.Name) + tableName := strings.ToLower(parser.IdentName(stmt.TableName.Name)) // source expression source, err := p.compileSource(query, stmt.Source) diff --git a/sql3/planner/compiledropdatabase.go b/sql3/planner/compiledropdatabase.go index 403716b5d..7e1cda314 100644 --- a/sql3/planner/compiledropdatabase.go +++ b/sql3/planner/compiledropdatabase.go @@ -4,6 +4,7 @@ package planner import ( "context" + "strings" "github.com/featurebasedb/featurebase/v3/dax" "github.com/featurebasedb/featurebase/v3/sql3" @@ -14,7 +15,7 @@ import ( // compileDropDatabaseStatement compiles a DROP DATABASE statement into a // PlanOperator. func (p *ExecutionPlanner) compileDropDatabaseStatement(ctx context.Context, stmt *parser.DropDatabaseStatement) (_ types.PlanOperator, err error) { - databaseName := parser.IdentName(stmt.Name) + databaseName := strings.ToLower(parser.IdentName(stmt.Name)) dbname := dax.DatabaseName(databaseName) db, err := p.schemaAPI.DatabaseByName(ctx, dbname) if err != nil { diff --git a/sql3/planner/compiledroptable.go b/sql3/planner/compiledroptable.go index 5a030e73a..717523ecc 100644 --- a/sql3/planner/compiledroptable.go +++ b/sql3/planner/compiledroptable.go @@ -4,6 +4,7 @@ package planner import ( "context" + "strings" pilosa "github.com/featurebasedb/featurebase/v3" "github.com/featurebasedb/featurebase/v3/dax" @@ -15,7 +16,7 @@ import ( // compileDropTableStatement compiles a DROP TABLE statement into a // PlanOperator. func (p *ExecutionPlanner) compileDropTableStatement(ctx context.Context, stmt *parser.DropTableStatement) (_ types.PlanOperator, err error) { - tableName := parser.IdentName(stmt.Name) + tableName := strings.ToLower(parser.IdentName(stmt.Name)) tname := dax.TableName(tableName) tbl, err := p.schemaAPI.TableByName(ctx, tname) if err != nil { diff --git a/sql3/planner/compiledropview.go b/sql3/planner/compiledropview.go index 10ac570d5..65c901e82 100644 --- a/sql3/planner/compiledropview.go +++ b/sql3/planner/compiledropview.go @@ -4,6 +4,7 @@ package planner import ( "context" + "strings" "github.com/featurebasedb/featurebase/v3/sql3" "github.com/featurebasedb/featurebase/v3/sql3/parser" @@ -12,7 +13,7 @@ import ( // compileDropViewStatement compiles a DROP VIEW statement into a PlanOperator. func (p *ExecutionPlanner) compileDropViewStatement(ctx context.Context, stmt *parser.DropViewStatement) (_ types.PlanOperator, err error) { - viewName := parser.IdentName(stmt.Name) + viewName := strings.ToLower(parser.IdentName(stmt.Name)) v, err := p.getViewByName(ctx, viewName) if err != nil { return nil, err diff --git a/sql3/planner/compileinsert.go b/sql3/planner/compileinsert.go index 6069774fe..86c21c511 100644 --- a/sql3/planner/compileinsert.go +++ b/sql3/planner/compileinsert.go @@ -15,7 +15,7 @@ import ( // compileInsertStatement compiles an INSERT statement into a PlanOperator. func (p *ExecutionPlanner) compileInsertStatement(ctx context.Context, stmt *parser.InsertStatement) (_ types.PlanOperator, err error) { - tableName := parser.IdentName(stmt.Table) + tableName := strings.ToLower(parser.IdentName(stmt.Table)) targetColumns := []*qualifiedRefPlanExpression{} insertValues := [][]types.PlanExpression{} @@ -31,7 +31,7 @@ func (p *ExecutionPlanner) compileInsertStatement(ctx context.Context, stmt *par if len(stmt.Columns) > 0 { for _, columnIdent := range stmt.Columns { - colName := parser.IdentName(columnIdent) + colName := strings.ToLower(parser.IdentName(columnIdent)) if strings.EqualFold(colName, "_id") { targetColumns = append(targetColumns, newQualifiedRefPlanExpression(tableName, colName, 0, parser.NewDataTypeID())) @@ -74,7 +74,7 @@ func (p *ExecutionPlanner) compileInsertStatement(ctx context.Context, stmt *par // anything is invalid. func (p *ExecutionPlanner) analyzeInsertStatement(ctx context.Context, stmt *parser.InsertStatement) error { // Check that referred table exists. - tableName := parser.IdentName(stmt.Table) + tableName := strings.ToLower(parser.IdentName(stmt.Table)) tname := dax.TableName(tableName) tbl, err := p.schemaAPI.TableByName(ctx, tname) if err != nil { @@ -108,7 +108,7 @@ func (p *ExecutionPlanner) analyzeInsertStatement(ctx context.Context, stmt *par // dupes. columnNameMap := make(map[string]struct{}) for _, columnIdent := range stmt.Columns { - colName := parser.IdentName(columnIdent) + colName := strings.ToLower(parser.IdentName(columnIdent)) var typeName parser.ExprDataType if strings.EqualFold(colName, "_id") { diff --git a/sql3/planner/compileselect.go b/sql3/planner/compileselect.go index 68184a47b..a40723523 100644 --- a/sql3/planner/compileselect.go +++ b/sql3/planner/compileselect.go @@ -309,10 +309,10 @@ func (p *ExecutionPlanner) compileSource(scope *PlanOpQuery, source parser.Sourc case *parser.QualifiedTableName: - tableName := parser.IdentName(sourceExpr.Name) + tableName := strings.ToLower(parser.IdentName(sourceExpr.Name)) // doing this check here because we don't have a 'system' flag that exists in the FB schema - st, ok := systemTables[strings.ToLower(tableName)] + st, ok := systemTables[tableName] if ok { var op types.PlanOperator op = NewPlanOpSystemTable(p, st) @@ -418,7 +418,7 @@ func (p *ExecutionPlanner) analyzeSource(ctx context.Context, source parser.Sour case *parser.QualifiedTableName: - objectName := parser.IdentName(source.Name) + objectName := strings.ToLower(parser.IdentName(source.Name)) // check views first view, err := p.getViewByName(ctx, objectName) @@ -620,7 +620,7 @@ func (p *ExecutionPlanner) analyzeSelectStatementWildcards(stmt *parser.SelectSt } 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) + refName := strings.ToLower(parser.IdentName(ref.Table)) src := stmt.Source.SourceFromAlias(refName) if src == nil { return sql3.NewErrTableNotFound(ref.Table.NamePos.Line, ref.Table.NamePos.Column, refName) diff --git a/sql3/planner/compileshow.go b/sql3/planner/compileshow.go index 51f5f7867..35a28b0a3 100644 --- a/sql3/planner/compileshow.go +++ b/sql3/planner/compileshow.go @@ -139,7 +139,7 @@ func (p *ExecutionPlanner) compileShowTablesStatement(ctx context.Context, stmt } func (p *ExecutionPlanner) compileShowColumnsStatement(ctx context.Context, stmt *parser.ShowColumnsStatement) (_ types.PlanOperator, err error) { - tableName := parser.IdentName(stmt.TableName) + tableName := strings.ToLower(parser.IdentName(stmt.TableName)) tname := dax.TableName(tableName) tbl, err := p.schemaAPI.TableByName(ctx, tname) if err != nil { @@ -159,70 +159,65 @@ func (p *ExecutionPlanner) compileShowColumnsStatement(ctx context.Context, stmt columnName: "name", columnIndex: 1, dataType: parser.NewDataTypeString(), - }, &qualifiedRefPlanExpression{ // the SQL3 data type description + }, &qualifiedRefPlanExpression{ tableName: "fb_table_columns", columnName: "type", columnIndex: 2, dataType: parser.NewDataTypeString(), - }, &qualifiedRefPlanExpression{ // the FeatureBase 'native' data type description - tableName: "fb_table_columns", - columnName: "internal_type", - columnIndex: 3, - dataType: parser.NewDataTypeString(), }, &qualifiedRefPlanExpression{ tableName: "fb_table_columns", columnName: "created_at", - columnIndex: 4, + columnIndex: 3, dataType: parser.NewDataTypeTimestamp(), }, &qualifiedRefPlanExpression{ tableName: "fb_table_columns", columnName: "keys", - columnIndex: 5, + columnIndex: 4, dataType: parser.NewDataTypeBool(), }, &qualifiedRefPlanExpression{ tableName: "fb_table_columns", columnName: "cache_type", - columnIndex: 6, + columnIndex: 5, dataType: parser.NewDataTypeString(), }, &qualifiedRefPlanExpression{ tableName: "fb_table_columns", columnName: "cache_size", - columnIndex: 7, + columnIndex: 6, dataType: parser.NewDataTypeInt(), }, &qualifiedRefPlanExpression{ tableName: "fb_table_columns", columnName: "scale", - columnIndex: 8, + columnIndex: 7, dataType: parser.NewDataTypeInt(), }, &qualifiedRefPlanExpression{ tableName: "fb_table_columns", columnName: "min", - columnIndex: 9, + columnIndex: 8, dataType: parser.NewDataTypeInt(), }, &qualifiedRefPlanExpression{ tableName: "fb_table_columns", columnName: "max", - columnIndex: 10, + columnIndex: 9, dataType: parser.NewDataTypeInt(), }, &qualifiedRefPlanExpression{ tableName: "fb_table_columns", columnName: "timeunit", - columnIndex: 11, + columnIndex: 10, dataType: parser.NewDataTypeString(), }, &qualifiedRefPlanExpression{ tableName: "fb_table_columns", columnName: "epoch", - columnIndex: 12, + columnIndex: 11, dataType: parser.NewDataTypeInt(), }, &qualifiedRefPlanExpression{ tableName: "fb_table_columns", columnName: "timequantum", - columnIndex: 13, + columnIndex: 12, dataType: parser.NewDataTypeString(), }, &qualifiedRefPlanExpression{ tableName: "fb_table_columns", columnName: "ttl", - columnIndex: 14, + columnIndex: 13, dataType: parser.NewDataTypeString(), }} @@ -230,7 +225,7 @@ func (p *ExecutionPlanner) compileShowColumnsStatement(ctx context.Context, stmt } func (p *ExecutionPlanner) compileShowCreateTableStatement(ctx context.Context, stmt *parser.ShowCreateTableStatement) (_ types.PlanOperator, err error) { - tableName := parser.IdentName(stmt.TableName) + tableName := strings.ToLower(parser.IdentName(stmt.TableName)) tname := dax.TableName(tableName) if _, err := p.schemaAPI.TableByName(ctx, tname); err != nil { if isTableNotFoundError(err) { diff --git a/sql3/planner/expression.go b/sql3/planner/expression.go index d4a61d390..8edb9a6ac 100644 --- a/sql3/planner/expression.go +++ b/sql3/planner/expression.go @@ -1170,7 +1170,7 @@ func (n *betweenOpPlanExpression) Evaluate(currentRow []interface{}) (interface{ switch rType := n.rhs.Type().(type) { case *parser.DataTypeRange: - switch rType.SubscriptType.(type) { + switch sType := rType.SubscriptType.(type) { case *parser.DataTypeInt: nl, nlok := evalLhs.(int64) @@ -1201,8 +1201,40 @@ func (n *betweenOpPlanExpression) Evaluate(currentRow []interface{}) (interface{ } return result, nil + case *parser.DataTypeDecimal: + + nl, nlok := evalLhs.(pql.Decimal) + if !(nlok) { + return nil, sql3.NewErrInternalf("unexpected type conversion error '%t'", nlok) + } + + crl, err := coerceValue(exprRange.lhs.Type(), sType, rangeLower, parser.Pos{Line: 0, Column: 0}) + if err != nil { + return nil, err + } + + rl, ok := crl.(pql.Decimal) + if !(ok) { + return nil, sql3.NewErrInternalf("unexpected type conversion error '%t'", crl) + } + + cru, err := coerceValue(exprRange.rhs.Type(), sType, rangeUpper, parser.Pos{Line: 0, Column: 0}) + if err != nil { + return nil, err + } + ru, ok := cru.(pql.Decimal) + if !(ok) { + return nil, sql3.NewErrInternalf("unexpected type conversion error '%t'", cru) + } + + result := nl.GreaterThanOrEqualTo(rl) && nl.LessThanOrEqualTo(ru) + if n.op == parser.NOTBETWEEN { + result = !result + } + return result, nil + default: - return nil, sql3.NewErrInternalf("unexpected range type '%T'", rType.SubscriptType) + return nil, sql3.NewErrInternalf("unexpected range type '%T'", sType) } default: @@ -2560,7 +2592,7 @@ func (p *ExecutionPlanner) compileExpr(expr parser.Expr) (_ types.PlanExpression return ref, nil case *parser.QualifiedRef: - ref := newQualifiedRefPlanExpression(parser.IdentName(expr.Table), parser.IdentName(expr.Column), expr.ColumnIndex, expr.DataType()) + ref := newQualifiedRefPlanExpression(strings.ToLower(parser.IdentName(expr.Table)), strings.ToLower(parser.IdentName(expr.Column)), expr.ColumnIndex, expr.DataType()) return ref, nil case *parser.Range: diff --git a/sql3/planner/expressionagg.go b/sql3/planner/expressionagg.go index 5d5ad01a3..ad7aff1ee 100644 --- a/sql3/planner/expressionagg.go +++ b/sql3/planner/expressionagg.go @@ -435,6 +435,15 @@ func (a *aggregateAvg) Update(ctx context.Context, row types.Row) error { thisVal := pql.FromInt64(thisIVal, returnType.Scale) a.sum = pql.AddDecimal(thisVal, aggVal) + case *parser.DataTypeID: + thisIVal, ok := v.(uint64) + if !ok { + return sql3.NewErrInternalf("unexpected type conversion '%T'", v) + } + + thisVal := pql.FromInt64(int64(thisIVal), returnType.Scale) + a.sum = pql.AddDecimal(thisVal, aggVal) + default: return sql3.NewErrInternalf("unhandled aggregate expression datatype '%T'", dataType) } @@ -610,6 +619,21 @@ func (m *aggregateMin) Update(ctx context.Context, row types.Row) error { m.val = thisVal } + case *parser.DataTypeString: + thisVal, ok := v.(string) + if !ok { + return sql3.NewErrInternalf("unexpected type conversion '%T'", v) + } + + aggVal, ok := m.val.(string) + if !ok { + return sql3.NewErrInternalf("unexpected type conversion '%T'", v) + } + + if thisVal < aggVal { + m.val = thisVal + } + default: return sql3.NewErrInternalf("unhandled aggregate expression datatype '%T'", dataType) } @@ -752,6 +776,21 @@ func (m *aggregateMax) Update(ctx context.Context, row types.Row) error { m.val = thisVal } + case *parser.DataTypeString: + thisVal, ok := v.(string) + if !ok { + return sql3.NewErrInternalf("unexpected type conversion '%T'", v) + } + + aggVal, ok := m.val.(string) + if !ok { + return sql3.NewErrInternalf("unexpected type conversion '%T'", v) + } + + if thisVal > aggVal { + m.val = thisVal + } + default: return sql3.NewErrInternalf("unhandled aggregate expression datatype '%T'", dataType) } diff --git a/sql3/planner/expressionanalyzer.go b/sql3/planner/expressionanalyzer.go index b3f4a1666..1214bd05a 100644 --- a/sql3/planner/expressionanalyzer.go +++ b/sql3/planner/expressionanalyzer.go @@ -440,6 +440,19 @@ func (p *ExecutionPlanner) analyzeBinaryExpression(ctx context.Context, expr *pa if !typeIsCompatibleWithEqualityOperator(y.DataType()) { return nil, sql3.NewErrTypeIncompatibleWithEqualityOperator(y.Pos().Line, y.Pos().Column, op.String(), y.DataType().TypeDescription()) } + if typeIsTimestamp(x.DataType()) && y.IsLiteral() && typeIsString(y.DataType()) { + // we have a string literal on the rhs being compared to a date so + // try to convert to a date literal + rhs, ok := y.(*parser.StringLit) + if !ok { + return nil, sql3.NewErrInternalf("unexpected expression type '%T'", y) + } + newRhs := rhs.ConvertToTimestamp() + if newRhs != nil { + expr.Y = newRhs + y = newRhs + } + } if !typesAreComparable(x.DataType(), y.DataType()) { return nil, sql3.NewErrTypesAreNotEquatable(x.Pos().Line, x.Pos().Column, x.DataType().TypeDescription(), y.DataType().TypeDescription()) } @@ -794,11 +807,11 @@ func (p *ExecutionPlanner) analyzeRangeExpression(ctx context.Context, expr *par if !typeCanBeUsedInRange(expr.Y.DataType()) { return nil, sql3.NewErrTypeCannotBeUsedAsRangeSubscript(expr.Y.Pos().Line, expr.Y.Pos().Column, expr.Y.DataType().TypeDescription()) } - if !typesOfRangeBoundsAreTheSame(expr.X.DataType(), expr.Y.DataType()) { + canbeUsed, coercedType := typesOfRangeBoundsAreTheSame(expr.X.DataType(), expr.Y.DataType()) + if !canbeUsed { return nil, sql3.NewErrIncompatibleTypesForRangeSubscripts(expr.Pos().Line, expr.Pos().Column, expr.X.DataType().TypeDescription(), expr.Y.DataType().TypeDescription()) } - - expr.ResultDataType = parser.NewDataTypeRange(expr.X.DataType()) + expr.ResultDataType = parser.NewDataTypeRange(coercedType) return expr, nil } diff --git a/sql3/planner/expressionanalyzercall.go b/sql3/planner/expressionanalyzercall.go index e4ff33108..90d13d522 100644 --- a/sql3/planner/expressionanalyzercall.go +++ b/sql3/planner/expressionanalyzercall.go @@ -165,8 +165,8 @@ func (p *ExecutionPlanner) analyzeCallExpression(ctx context.Context, call *pars } // make sure the ref is min/max-able - if !(typeIsInteger(ref.DataType()) || typeIsDecimal(ref.DataType()) || typeIsTimestamp(ref.DataType())) { - return nil, sql3.NewErrIntOrDecimalOrTimestampExpressionExpected(ref.Table.NamePos.Line, ref.Table.NamePos.Column) + if !(typeIsInteger(ref.DataType()) || typeIsDecimal(ref.DataType()) || typeIsTimestamp(ref.DataType()) || typeIsString(ref.DataType())) { + return nil, sql3.NewErrIntOrDecimalOrTimestampOrStringExpressionExpected(ref.Table.NamePos.Line, ref.Table.NamePos.Column) } // return the data type of the referenced column diff --git a/sql3/planner/expressionpql.go b/sql3/planner/expressionpql.go index 4af10a11a..edb32f25e 100644 --- a/sql3/planner/expressionpql.go +++ b/sql3/planner/expressionpql.go @@ -180,7 +180,166 @@ func (p *ExecutionPlanner) generatePQLCallFromBinaryExpr(ctx context.Context, ex Children: []*pql.Call{x, y}, }, nil - case parser.EQ, parser.NE, parser.LT, parser.LE, parser.GT, parser.GE: + 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) @@ -208,82 +367,32 @@ func (p *ExecutionPlanner) generatePQLCallFromBinaryExpr(ctx context.Context, ex }, nil case *parser.DataTypeID: - // TODO (pok) range queries on _id are not supported - if strings.EqualFold(lhs.columnName, "_id") { - cr := &pql.Call{ - Name: "ConstRow", - Args: map[string]interface{}{ - "columns": []interface{}{pqlValue}, - }, - Type: pql.PrecallGlobal, - } - // TODO (pok) when we fix FB-1828 (https://molecula.atlassian.net/browse/FB-1828) - // we can remove this - ConstRow returns a ghost record, thus to eliminate - // we interset with All - return &pql.Call{ - Name: "Intersect", - Children: []*pql.Call{ - { - Name: "All", - }, - cr, - }, - }, nil - } - return &pql.Call{ - Name: "Row", - Args: map[string]interface{}{ - lhs.columnName: pqlValue, - }, - }, nil + return nil, sql3.NewErrUnsupported(0, 0, false, "range queries on id typed columns") case *parser.DataTypeString: - // TODO (pok) range queries on _id are not supported - if strings.EqualFold(lhs.columnName, "_id") { - cr := &pql.Call{ - Name: "ConstRow", - Args: map[string]interface{}{ - "columns": []interface{}{pqlValue}, - }, - Type: pql.PrecallGlobal, - } - // TODO (pok) when we fix FB-1828 (https://molecula.atlassian.net/browse/FB-1828) - // we can remove this - ConstRow returns a ghost record, thus to eliminate - // we interset with All - return &pql.Call{ - Name: "Intersect", - Children: []*pql.Call{ - { - Name: "All", - }, - cr, - }, - }, nil + 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: pqlValue, - }, - }, nil - - case *parser.DataTypeTimestamp: - return &pql.Call{ - Name: "Row", - Args: map[string]interface{}{ - lhs.columnName: pqlValue, - }, - }, nil - - case *parser.DataTypeBool: - return &pql.Call{ - Name: "Row", - Args: map[string]interface{}{ - lhs.columnName: pqlValue, + 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) @@ -292,7 +401,10 @@ func (p *ExecutionPlanner) generatePQLCallFromBinaryExpr(ctx context.Context, ex return &pql.Call{ Name: "Row", Args: map[string]interface{}{ - lhs.columnName: d, + lhs.columnName: &pql.Condition{ + Op: pqlOp, + Value: d, + }, }, }, nil diff --git a/sql3/planner/expressiontypes.go b/sql3/planner/expressiontypes.go index 8be5d2fb1..22e74cbf8 100644 --- a/sql3/planner/expressiontypes.go +++ b/sql3/planner/expressiontypes.go @@ -181,20 +181,34 @@ func typesAreRangeComparable(testTypeL parser.ExprDataType, testTypeR parser.Exp default: return false, sql3.NewErrInternalf("unhandled rhs type '%T' for lhs type '%T'", rhsType, lhsType) - } + case *parser.DataTypeInt, *parser.DataTypeID: switch lhsType := testTypeL.(type) { case *parser.DataTypeID: return true, nil case *parser.DataTypeInt: return true, nil + case *parser.DataTypeDecimal: + // change subscript type to be decimal + rhsType.SubscriptType = lhsType + return true, nil default: return false, sql3.NewErrInternalf("unhandled rhs type '%T' for lhs type '%T'", rhsType, lhsType) - } + + case *parser.DataTypeDecimal: + switch lhsType := testTypeL.(type) { + case *parser.DataTypeDecimal: + return true, nil + + default: + return false, sql3.NewErrInternalf("unhandled rhs type '%T' for lhs type '%T'", rhsType, lhsType) + } + } + default: return false, sql3.NewErrInternalf("type '%T' is not a range type", rhsType) } @@ -363,7 +377,7 @@ func typesAreAssignmentCompatible(targetType parser.ExprDataType, sourceType par // returns true if the type can be used as a range subscript func typeCanBeUsedInRange(testType parser.ExprDataType) bool { switch testType.(type) { - case *parser.DataTypeID, *parser.DataTypeInt, *parser.DataTypeTimestamp: + case *parser.DataTypeID, *parser.DataTypeInt, *parser.DataTypeTimestamp, *parser.DataTypeDecimal: return true default: return false @@ -371,43 +385,62 @@ func typeCanBeUsedInRange(testType parser.ExprDataType) bool { } // returns true if the types can be considered the same when used as a range bounds -func typesOfRangeBoundsAreTheSame(testTypeL parser.ExprDataType, testTypeR parser.ExprDataType) bool { +func typesOfRangeBoundsAreTheSame(testTypeL parser.ExprDataType, testTypeR parser.ExprDataType) (bool, parser.ExprDataType) { switch testTypeL.(type) { case *parser.DataTypeInt: switch testTypeR.(type) { case *parser.DataTypeInt: - return true + return true, testTypeL case *parser.DataTypeID: - return true + return true, testTypeL + + case *parser.DataTypeDecimal: + // 'widen' both to decimal + return true, testTypeR default: - return false + return false, nil } case *parser.DataTypeID: switch testTypeR.(type) { case *parser.DataTypeID: - return true + return true, testTypeL case *parser.DataTypeInt: - return true + return true, testTypeL default: - return false + return false, nil } case *parser.DataTypeTimestamp: switch testTypeR.(type) { case *parser.DataTypeTimestamp: - return true + return true, testTypeL default: - return false + return false, nil + } + + case *parser.DataTypeDecimal: + switch testTypeR.(type) { + case *parser.DataTypeInt: + return true, testTypeL + + case *parser.DataTypeID: + return true, testTypeL + + case *parser.DataTypeDecimal: + return true, testTypeL + + default: + return false, nil } default: - return false + return false, nil } } diff --git a/sql3/planner/opfeaturebasecolumns.go b/sql3/planner/opfeaturebasecolumns.go index d0d73b5ee..444e5137c 100644 --- a/sql3/planner/opfeaturebasecolumns.go +++ b/sql3/planner/opfeaturebasecolumns.go @@ -62,11 +62,6 @@ func (p *PlanOpFeatureBaseColumns) Schema() types.Schema { ColumnName: "type", Type: parser.NewDataTypeString(), }, - &types.PlannerColumn{ - RelationName: "fb_table_columns", - ColumnName: "internal_type", - Type: parser.NewDataTypeString(), - }, &types.PlannerColumn{ RelationName: "fb_table_columns", ColumnName: "created_at", @@ -156,7 +151,6 @@ func (i *showColumnsRowIter) Next(ctx context.Context) (types.Row, error) { fields[i.rowIndex].Name, fields[i.rowIndex].Name, fields[i.rowIndex].Type, - fields[i.rowIndex].Type, tm.Format(time.RFC3339), fields[i.rowIndex].StringKeys(), fields[i.rowIndex].Options.CacheType, diff --git a/sql3/planner/opsystemtable.go b/sql3/planner/opsystemtable.go index 23a810ec4..1b19c59d3 100644 --- a/sql3/planner/opsystemtable.go +++ b/sql3/planner/opsystemtable.go @@ -109,7 +109,7 @@ var systemTables = map[string]*systemTable{ &types.PlannerColumn{ RelationName: fbClusterNodes, ColumnName: "space_used", - Type: parser.NewDataTypeBool(), + Type: parser.NewDataTypeInt(), }, }, requiresFanout: false, diff --git a/sql3/planner/planoptimizer.go b/sql3/planner/planoptimizer.go index b3adaf73a..3fc2ecdd6 100644 --- a/sql3/planner/planoptimizer.go +++ b/sql3/planner/planoptimizer.go @@ -636,6 +636,18 @@ func tryToReplaceGroupByWithPQLAggregate(ctx context.Context, a *ExecutionPlanne return thisNode, true, nil } + // make sure all the aggregates are bsi types + for _, agg := range thisNode.Aggregates { + aggregable, ok := agg.(types.Aggregable) + if !ok { + return n, false, sql3.NewErrInternalf("unexpected aggregate function arg type '%T'", agg) + } + switch aggregable.AggExpression().Type().(type) { + case *parser.DataTypeID, *parser.DataTypeString: + return thisNode, true, nil + } + } + ops := make([]*PlanOpPQLAggregate, 0) for _, agg := range thisNode.Aggregates { diff --git a/sql3/sql_complex_test.go b/sql3/sql_complex_test.go index 7a31913c3..dffcdcdb7 100644 --- a/sql3/sql_complex_test.go +++ b/sql3/sql_complex_test.go @@ -188,7 +188,7 @@ func TestPlanner_Show(t *testing.T) { wireQueryFieldString("uri"), wireQueryFieldString("grpc_uri"), wireQueryFieldBool("is_primary"), - wireQueryFieldBool("space_used"), + wireQueryFieldInt("space_used"), }, columns); diff != "" { t.Fatal(diff) } @@ -318,7 +318,6 @@ func TestPlanner_Show(t *testing.T) { wireQueryFieldString("_id"), wireQueryFieldString("name"), wireQueryFieldString("type"), - wireQueryFieldString("internal_type"), wireQueryFieldTimestamp("created_at"), wireQueryFieldBool("keys"), wireQueryFieldString("cache_type"), @@ -348,7 +347,6 @@ func TestPlanner_Show(t *testing.T) { wireQueryFieldString("_id"), wireQueryFieldString("name"), wireQueryFieldString("type"), - wireQueryFieldString("internal_type"), wireQueryFieldTimestamp("created_at"), wireQueryFieldBool("keys"), wireQueryFieldString("cache_type"), @@ -688,6 +686,20 @@ func TestPlanner_CreateTable(t *testing.T) { } }) + t.Run("CreateTableMixedCaseColumn", func(t *testing.T) { + _, _, err := sql_test.MustQueryRows(t, server, `create table lowercase (_id id, name string, SomeColumn string, legalname string);`) + if err != nil { + t.Fatal(err) + } + }) + + t.Run("CreateTableMixedCaseColumn", func(t *testing.T) { + _, _, err := sql_test.MustQueryRows(t, server, `create table MixedCcase (_id id, name string, SomeColumn string, legalname string);`) + if err != nil { + t.Fatal(err) + } + }) + t.Run("DropTable1", func(t *testing.T) { _, _, err := sql_test.MustQueryRows(t, server, `drop table allcoltypes`) if err != nil { @@ -730,7 +742,6 @@ func TestPlanner_CreateTable(t *testing.T) { wireQueryFieldString("_id"), wireQueryFieldString("name"), wireQueryFieldString("type"), - wireQueryFieldString("internal_type"), wireQueryFieldTimestamp("created_at"), wireQueryFieldBool("keys"), wireQueryFieldString("cache_type"), diff --git a/sql3/test/defs/defs.go b/sql3/test/defs/defs.go index b6b37ad17..2faad2d6e 100644 --- a/sql3/test/defs/defs.go +++ b/sql3/test/defs/defs.go @@ -16,6 +16,14 @@ var TableTests []TableTest = []TableTest{ selectTests, selectKeyedTests, selectHavingTests, + filterPredicates, + filterPredicatesIdKey, + filterPredicatesId, + filterPredicatesInt, + filterPredicatesBool, + filterPredicatesTimestamp, + filterPredicatesDecimal, + filterPredicatesString, orderByTests, distinctTests, diff --git a/sql3/test/defs/defs_aggregate.go b/sql3/test/defs/defs_aggregate.go index f77bc6194..a7dad9b33 100644 --- a/sql3/test/defs/defs_aggregate.go +++ b/sql3/test/defs/defs_aggregate.go @@ -288,14 +288,15 @@ var avgTests = TableTest{ srcHdr("i1", fldTypeInt, "min 0", "max 1000"), srcHdr("d1", fldTypeDecimal2), srcHdr("s1", fldTypeString), + srcHdr("id1", fldTypeID), ), srcRows( - srcRow(int64(1), int64(10), float64(10), string("foo")), - srcRow(int64(2), int64(10), float64(10), string("foo")), - srcRow(int64(3), int64(11), float64(11), string("foo")), - srcRow(int64(4), int64(12), float64(12), string("foo")), - srcRow(int64(5), int64(12), float64(12), string("foo")), - srcRow(int64(6), int64(13), float64(13), string("foo")), + srcRow(int64(1), int64(10), float64(10), string("foo"), int64(10)), + srcRow(int64(2), int64(10), float64(10), string("foo"), int64(11)), + srcRow(int64(3), int64(11), float64(11), string("foo"), int64(12)), + srcRow(int64(4), int64(12), float64(12), string("foo"), int64(13)), + srcRow(int64(5), int64(12), float64(12), string("foo"), int64(14)), + srcRow(int64(6), int64(13), float64(13), string("foo"), int64(15)), ), ), SQLTests: []SQLTest{ @@ -323,6 +324,22 @@ var avgTests = TableTest{ ), ExpErr: "integer or decimal expression expected", }, + { + SQLs: sqls( + "SELECT avg(id1) AS avg_rows FROM avg_test", + ), + ExpHdrs: hdrs( + hdr("avg_rows", featurebase.WireQueryField{ + Type: dax.BaseTypeDecimal + "(4)", + BaseType: dax.BaseTypeDecimal, + TypeInfo: map[string]interface{}{"scale": int64(4)}, + }), + ), + ExpRows: rows( + row(pql.NewDecimal(125000, 4)), + ), + Compare: CompareExactUnordered, + }, { SQLs: sqls( "SELECT avg(i1) AS avg_rows FROM avg_test", @@ -430,12 +447,12 @@ var minmaxTests = TableTest{ srcHdr("ts1", fldTypeTimestamp), ), srcRows( - srcRow(int64(1), int64(10), float64(10), string("foo"), timestampFromString("2013-07-15T01:18:46Z")), - srcRow(int64(2), int64(10), float64(10), string("foo"), timestampFromString("2014-07-15T01:18:46Z")), - srcRow(int64(3), int64(11), float64(11), string("foo"), timestampFromString("2015-07-15T01:18:46Z")), - srcRow(int64(4), int64(12), float64(12), string("foo"), timestampFromString("2016-07-15T01:18:46Z")), - srcRow(int64(5), int64(12), float64(12), string("foo"), timestampFromString("2017-07-15T01:18:46Z")), - srcRow(int64(6), int64(13), float64(13), string("foo"), timestampFromString("2018-07-15T01:18:46Z")), + srcRow(int64(1), int64(10), float64(10), string("afoo"), timestampFromString("2013-07-15T01:18:46Z")), + srcRow(int64(2), int64(10), float64(10), string("bfoo"), timestampFromString("2014-07-15T01:18:46Z")), + srcRow(int64(3), int64(11), float64(11), string("cfoo"), timestampFromString("2015-07-15T01:18:46Z")), + srcRow(int64(4), int64(12), float64(12), string("dfoo"), timestampFromString("2016-07-15T01:18:46Z")), + srcRow(int64(5), int64(12), float64(12), string("efoo"), timestampFromString("2017-07-15T01:18:46Z")), + srcRow(int64(6), int64(13), float64(13), string("ffoo"), timestampFromString("2018-07-15T01:18:46Z")), ), ), SQLTests: []SQLTest{ @@ -470,9 +487,26 @@ var minmaxTests = TableTest{ { SQLs: sqls( "SELECT min(s1) AS p_rows FROM minmax_test", + ), + ExpHdrs: hdrs( + hdr("p_rows", fldTypeString), + ), + ExpRows: rows( + row(string("afoo")), + ), + Compare: CompareExactUnordered, + }, + { + SQLs: sqls( "SELECT max(s1) AS p_rows FROM minmax_test", ), - ExpErr: "integer, decimal or timestamp expression expected", + ExpHdrs: hdrs( + hdr("p_rows", fldTypeString), + ), + ExpRows: rows( + row(string("ffoo")), + ), + Compare: CompareExactUnordered, }, { SQLs: sqls( diff --git a/sql3/test/defs/defs_between.go b/sql3/test/defs/defs_between.go index 1b654c858..cb6d582e3 100644 --- a/sql3/test/defs/defs_between.go +++ b/sql3/test/defs/defs_between.go @@ -44,6 +44,18 @@ var betweenTests = TableTest{ ), Compare: CompareExactUnordered, }, + { + SQLs: sqls( + "select d1 between 10 and 15 from between_all_types", + ), + ExpHdrs: hdrs( + hdr("", fldTypeBool), + ), + ExpRows: rows( + row(bool(true)), + ), + Compare: CompareExactUnordered, + }, { SQLs: sqls( "select b1 between true and false from between_all_types", @@ -54,7 +66,13 @@ var betweenTests = TableTest{ SQLs: sqls( "select d1 between 1.23 and 4.56 from between_all_types", ), - ExpErr: "type 'decimal(2)' cannot be used as a range subscript", + ExpHdrs: hdrs( + hdr("", fldTypeBool), + ), + ExpRows: rows( + row(bool(false)), + ), + Compare: CompareExactUnordered, }, { SQLs: sqls( @@ -145,6 +163,18 @@ var notBetweenTests = TableTest{ ), Compare: CompareExactUnordered, }, + { + SQLs: sqls( + "select d1 not between 10 and 15 from between_all_types", + ), + ExpHdrs: hdrs( + hdr("", fldTypeBool), + ), + ExpRows: rows( + row(bool(false)), + ), + Compare: CompareExactUnordered, + }, { SQLs: sqls( "select b1 not between true and false from not_between_all_types", @@ -155,7 +185,13 @@ var notBetweenTests = TableTest{ SQLs: sqls( "select d1 not between 1.23 and 4.56 from not_between_all_types", ), - ExpErr: "type 'decimal(2)' cannot be used as a range subscript", + ExpHdrs: hdrs( + hdr("", fldTypeBool), + ), + ExpRows: rows( + row(bool(true)), + ), + Compare: CompareExactUnordered, }, { SQLs: sqls( diff --git a/sql3/test/defs/defs_filterpredicates.go b/sql3/test/defs/defs_filterpredicates.go new file mode 100644 index 000000000..2ff02fe1c --- /dev/null +++ b/sql3/test/defs/defs_filterpredicates.go @@ -0,0 +1,507 @@ +package defs + +var filterPredicates = TableTest{ + Table: tbl( + "filter_predicates", + srcHdrs( + srcHdr("_id", fldTypeID), + srcHdr("i1", fldTypeInt), + srcHdr("b1", fldTypeBool), + srcHdr("id1", fldTypeID), + srcHdr("ids1", fldTypeIDSet), + srcHdr("d1", fldTypeDecimal2), + srcHdr("s1", fldTypeString), + srcHdr("ss1", fldTypeStringSet), + srcHdr("ts1", fldTypeTimestamp), + ), + srcRows( + srcRow(int64(1), int64(10), bool(false), int64(1), []int64{10, 20, 30}, float64(10.00), string("10"), []string{"10", "20", "30"}, string("2001-11-01T22:08:41+00:00")), + srcRow(int64(2), int64(20), bool(true), int64(2), []int64{11, 21, 31}, float64(20.00), string("20"), []string{"11", "21", "31"}, string("2002-11-01T22:08:41+00:00")), + srcRow(int64(3), int64(30), bool(false), int64(3), []int64{12, 22, 32}, float64(30.00), string("30"), []string{"12", "22", "32"}, string("2003-11-01T22:08:41+00:00")), + srcRow(int64(4), int64(40), bool(false), int64(4), []int64{10, 20, 30}, float64(40.00), string("40"), []string{"10", "20", "30"}, string("2004-11-01T22:08:41+00:00")), + srcRow(int64(5), int64(50), bool(true), int64(5), []int64{11, 21, 31}, float64(50.00), string("50"), []string{"11", "21", "31"}, string("2005-11-01T22:08:41+00:00")), + srcRow(int64(6), int64(60), bool(false), int64(6), []int64{12, 22, 32}, float64(60.00), string("60"), []string{"12", "22", "32"}, string("2006-11-01T22:08:41+00:00")), + ), + ), +} + +var filterPredicatesIdKey = TableTest{ + SQLTests: []SQLTest{ + { + SQLs: sqls( + "select _id from filter_predicates where _id != 1", + ), + ExpHdrs: hdrs( + hdr("_id", fldTypeID), + ), + ExpRows: rows( + row(int64(2)), + row(int64(3)), + row(int64(4)), + row(int64(5)), + row(int64(6)), + ), + Compare: CompareExactUnordered, + }, + { + SQLs: sqls( + "select _id from filter_predicates where _id = 1", + ), + ExpHdrs: hdrs( + hdr("_id", fldTypeID), + ), + ExpRows: rows( + row(int64(1)), + ), + Compare: CompareExactUnordered, + }, + { + SQLs: sqls( + "select _id from filter_predicates where _id > 5", + ), + ExpHdrs: hdrs( + hdr("_id", fldTypeID), + ), + ExpRows: rows( + row(int64(6)), + ), + Compare: CompareExactUnordered, + }, + { + SQLs: sqls( + "select _id from filter_predicates where _id >= 5", + ), + ExpHdrs: hdrs( + hdr("_id", fldTypeID), + ), + ExpRows: rows( + row(int64(5)), + row(int64(6)), + ), + Compare: CompareExactUnordered, + }, + { + SQLs: sqls( + "select _id from filter_predicates where _id < 2", + ), + ExpHdrs: hdrs( + hdr("_id", fldTypeID), + ), + ExpRows: rows( + row(int64(1)), + ), + Compare: CompareExactUnordered, + }, + { + SQLs: sqls( + "select _id from filter_predicates where _id <= 2", + ), + ExpHdrs: hdrs( + hdr("_id", fldTypeID), + ), + ExpRows: rows( + row(int64(1)), + row(int64(2)), + ), + Compare: CompareExactUnordered, + }, + }, +} + +var filterPredicatesId = TableTest{ + SQLTests: []SQLTest{ + { + SQLs: sqls( + "select _id from filter_predicates where id1 != 1", + ), + ExpHdrs: hdrs( + hdr("_id", fldTypeID), + ), + ExpRows: rows( + row(int64(2)), + row(int64(3)), + row(int64(4)), + row(int64(5)), + row(int64(6)), + ), + Compare: CompareExactUnordered, + }, + { + SQLs: sqls( + "select _id from filter_predicates where id1 = 1", + ), + ExpHdrs: hdrs( + hdr("_id", fldTypeID), + ), + ExpRows: rows( + row(int64(1)), + ), + Compare: CompareExactUnordered, + }, + { + SQLs: sqls( + "select _id from filter_predicates where id1 > 5", + ), + ExpHdrs: hdrs( + hdr("_id", fldTypeID), + ), + ExpRows: rows( + row(int64(6)), + ), + Compare: CompareExactUnordered, + }, + { + SQLs: sqls( + "select _id from filter_predicates where id1 >= 5", + ), + ExpHdrs: hdrs( + hdr("_id", fldTypeID), + ), + ExpRows: rows( + row(int64(5)), + row(int64(6)), + ), + Compare: CompareExactUnordered, + }, + { + SQLs: sqls( + "select _id from filter_predicates where id1 < 2", + ), + ExpHdrs: hdrs( + hdr("_id", fldTypeID), + ), + ExpRows: rows( + row(int64(1)), + ), + Compare: CompareExactUnordered, + }, + { + SQLs: sqls( + "select _id from filter_predicates where id1 <= 2", + ), + ExpHdrs: hdrs( + hdr("_id", fldTypeID), + ), + ExpRows: rows( + row(int64(1)), + row(int64(2)), + ), + Compare: CompareExactUnordered, + }, + }, +} + +var filterPredicatesInt = TableTest{ + SQLTests: []SQLTest{ + { + SQLs: sqls( + "select _id from filter_predicates where i1 != 10", + ), + ExpHdrs: hdrs( + hdr("_id", fldTypeID), + ), + ExpRows: rows( + row(int64(2)), + row(int64(3)), + row(int64(4)), + row(int64(5)), + row(int64(6)), + ), + Compare: CompareExactUnordered, + }, + { + SQLs: sqls( + "select _id from filter_predicates where i1 = 10", + ), + ExpHdrs: hdrs( + hdr("_id", fldTypeID), + ), + ExpRows: rows( + row(int64(1)), + ), + Compare: CompareExactUnordered, + }, + { + SQLs: sqls( + "select _id from filter_predicates where i1 > 50", + ), + ExpHdrs: hdrs( + hdr("_id", fldTypeID), + ), + ExpRows: rows( + row(int64(6)), + ), + Compare: CompareExactUnordered, + }, + { + SQLs: sqls( + "select _id from filter_predicates where i1 >= 50", + ), + ExpHdrs: hdrs( + hdr("_id", fldTypeID), + ), + ExpRows: rows( + row(int64(5)), + row(int64(6)), + ), + Compare: CompareExactUnordered, + }, + { + SQLs: sqls( + "select _id from filter_predicates where i1 < 20", + ), + ExpHdrs: hdrs( + hdr("_id", fldTypeID), + ), + ExpRows: rows( + row(int64(1)), + ), + Compare: CompareExactUnordered, + }, + { + SQLs: sqls( + "select _id from filter_predicates where i1 <= 20", + ), + ExpHdrs: hdrs( + hdr("_id", fldTypeID), + ), + ExpRows: rows( + row(int64(1)), + row(int64(2)), + ), + Compare: CompareExactUnordered, + }, + }, +} + +var filterPredicatesBool = TableTest{ + SQLTests: []SQLTest{ + { + SQLs: sqls( + "select _id from filter_predicates where b1 != true", + ), + ExpHdrs: hdrs( + hdr("_id", fldTypeID), + ), + ExpRows: rows( + row(int64(1)), + row(int64(3)), + row(int64(4)), + row(int64(6)), + ), + Compare: CompareExactUnordered, + }, + { + SQLs: sqls( + "select _id from filter_predicates where b1 = true", + ), + ExpHdrs: hdrs( + hdr("_id", fldTypeID), + ), + ExpRows: rows( + row(int64(2)), + row(int64(5)), + ), + Compare: CompareExactUnordered, + }, + }, +} + +var filterPredicatesTimestamp = TableTest{ + SQLTests: []SQLTest{ + { + SQLs: sqls( + "select _id from filter_predicates where ts1 != '2001-11-01T22:08:41+00:00'", + ), + ExpHdrs: hdrs( + hdr("_id", fldTypeID), + ), + ExpRows: rows( + row(int64(2)), + row(int64(3)), + row(int64(4)), + row(int64(5)), + row(int64(6)), + ), + Compare: CompareExactUnordered, + }, + { + SQLs: sqls( + "select _id from filter_predicates where ts1 = '2001-11-01T22:08:41+00:00'", + ), + ExpHdrs: hdrs( + hdr("_id", fldTypeID), + ), + ExpRows: rows( + row(int64(1)), + ), + Compare: CompareExactUnordered, + }, + { + SQLs: sqls( + "select _id from filter_predicates where ts1 > '2005-11-01T22:08:41+00:00'", + ), + ExpHdrs: hdrs( + hdr("_id", fldTypeID), + ), + ExpRows: rows( + row(int64(6)), + ), + Compare: CompareExactUnordered, + }, + { + SQLs: sqls( + "select _id from filter_predicates where ts1 >= '2005-11-01T22:08:41+00:00'", + ), + ExpHdrs: hdrs( + hdr("_id", fldTypeID), + ), + ExpRows: rows( + row(int64(5)), + row(int64(6)), + ), + Compare: CompareExactUnordered, + }, + { + SQLs: sqls( + "select _id from filter_predicates where ts1 < '2002-11-01T22:08:41+00:00'", + ), + ExpHdrs: hdrs( + hdr("_id", fldTypeID), + ), + ExpRows: rows( + row(int64(1)), + ), + Compare: CompareExactUnordered, + }, + { + SQLs: sqls( + "select _id from filter_predicates where ts1 <= '2002-11-01T22:08:41+00:00'", + ), + ExpHdrs: hdrs( + hdr("_id", fldTypeID), + ), + ExpRows: rows( + row(int64(1)), + row(int64(2)), + ), + Compare: CompareExactUnordered, + }, + }, +} + +var filterPredicatesDecimal = TableTest{ + SQLTests: []SQLTest{ + { + SQLs: sqls( + "select _id from filter_predicates where d1 != 10.00", + ), + ExpHdrs: hdrs( + hdr("_id", fldTypeID), + ), + ExpRows: rows( + row(int64(2)), + row(int64(3)), + row(int64(4)), + row(int64(5)), + row(int64(6)), + ), + Compare: CompareExactUnordered, + }, + { + SQLs: sqls( + "select _id from filter_predicates where d1 = 10.00", + ), + ExpHdrs: hdrs( + hdr("_id", fldTypeID), + ), + ExpRows: rows( + row(int64(1)), + ), + Compare: CompareExactUnordered, + }, + { + SQLs: sqls( + "select _id from filter_predicates where d1 > 50.00", + ), + ExpHdrs: hdrs( + hdr("_id", fldTypeID), + ), + ExpRows: rows( + row(int64(6)), + ), + Compare: CompareExactUnordered, + }, + { + SQLs: sqls( + "select _id from filter_predicates where d1 >= 50.00", + ), + ExpHdrs: hdrs( + hdr("_id", fldTypeID), + ), + ExpRows: rows( + row(int64(5)), + row(int64(6)), + ), + Compare: CompareExactUnordered, + }, + { + SQLs: sqls( + "select _id from filter_predicates where d1 < 20.00", + ), + ExpHdrs: hdrs( + hdr("_id", fldTypeID), + ), + ExpRows: rows( + row(int64(1)), + ), + Compare: CompareExactUnordered, + }, + { + SQLs: sqls( + "select _id from filter_predicates where d1 <= 20.00", + ), + ExpHdrs: hdrs( + hdr("_id", fldTypeID), + ), + ExpRows: rows( + row(int64(1)), + row(int64(2)), + ), + Compare: CompareExactUnordered, + }, + }, +} + +var filterPredicatesString = TableTest{ + SQLTests: []SQLTest{ + { + SQLs: sqls( + "select _id from filter_predicates where s1 != '10'", + ), + ExpHdrs: hdrs( + hdr("_id", fldTypeID), + ), + ExpRows: rows( + row(int64(2)), + row(int64(3)), + row(int64(4)), + row(int64(5)), + row(int64(6)), + ), + Compare: CompareExactUnordered, + }, + { + SQLs: sqls( + "select _id from filter_predicates where s1 = '10'", + ), + ExpHdrs: hdrs( + hdr("_id", fldTypeID), + ), + ExpRows: rows( + row(int64(1)), + ), + Compare: CompareExactUnordered, + }, + }, +}