mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-08-28 10:54:59 +00:00
This forward-ports a number of tests from the previous SQL implementation. The porting is approximate in a number of ways, and not all tests are implemented/tested yet. In particular, several tests are currently disabled because we don't support `limit n` constructs. The tests that were primarily tests of the parser have been brought forward as parser tests. One of them has been altered to add parentheses, because our parser interprets fld1 between 1 and 3 and fld2 = 2 as: fld1 between (1 and 3) and (fld2 = 2) which is invalid, while the old parser apparently interpreted it as: (fld1 between 1 and 3) and (fld2 = 2) We have not yet verified the SQL spec's requirements here, but sqlite agrees with our old parser, not our new parser, so this may be a regression. The old tests expected an INNER JOIN to suppress duplicate values. Our new code does not, which is consistent with other SQL implementations. This is a change, but the old behavior appears to have been wrong. (You can still suppress duplicate values by specifying DISTINCT.) In the previous implementations, a value like `count(*)` had `count(*)` as its column name. In the new implementation, it has an empty string as its column name. Related to this, the prior implementation allowed you to write select age, count(*) from grouper group by age having count > 1 but the new implementationt requires that to be spelled as having count(*) > 1 This is consistent with other SQL implementations, so I think the new behavior is correct. The behavior of SHOW COLUMNS and SHOW TABLES has changed, in that the specific results returned are significantly different. Perhaps more significantly, the old system spelled the former query as SHOW FIELDS, rather than SHOW COLUMNS. This may be considered a regression, in that `SHOW FIELDS` no longer works, and we should consider whether any hypothetical users might have been relying on the output of either of these. (I hope not, the new output is much better.) Some of the old tests (the ones in handler_test) were accommodated by adding a couple of specific test cases to existing tests, specifically: * handling timestamp values with `Z` rather than `+00:00` * a join with a WHERE clause referring to fields in both source tables We introduce a new "partial" comparison type, because there's no way for a test of `SHOW TABLES` to contain a correct table row, because `SHOW TABLES` includes timestamps from when tables were created. I'm not sure this is the right way to do this. We add corresponding changes to dax_test, because the DAX tree tests against the SQL tests. We change the returned types of field names and field types to plain strings, ironically because DAX needs this -- the test code in the DAX tree is getting them back as plain strings, rather than as dax.FieldName and dax.BaseType. The tests using `having` are commented out because they don't seem to be working, a ticket has been filed for this. Two of the tests that should return strings are instead returning untranslated integer IDs, but only for DAX, not for the regular SQL tests, and the `delete` test has been commented out for DAX-specific errors. If we merge this, the next step is to ticket those and address them separately.
171 lines
4.4 KiB
Go
171 lines
4.4 KiB
Go
// Copyright 2021 Molecula Corp. All rights reserved.
|
|
|
|
package planner
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/featurebasedb/featurebase/v3/dax"
|
|
"github.com/featurebasedb/featurebase/v3/sql3/parser"
|
|
"github.com/featurebasedb/featurebase/v3/sql3/planner/types"
|
|
)
|
|
|
|
// PlanOpFeatureBaseColumns wraps an Index that is returned from schemaAPI.Schema().
|
|
type PlanOpFeatureBaseColumns struct {
|
|
tbl *dax.Table
|
|
warnings []string
|
|
}
|
|
|
|
func NewPlanOpFeatureBaseColumns(tbl *dax.Table) *PlanOpFeatureBaseColumns {
|
|
node := &PlanOpFeatureBaseColumns{
|
|
tbl: tbl,
|
|
warnings: make([]string, 0),
|
|
}
|
|
return node
|
|
}
|
|
|
|
func (p *PlanOpFeatureBaseColumns) Plan() map[string]interface{} {
|
|
result := make(map[string]interface{})
|
|
result["_op"] = fmt.Sprintf("%T", p)
|
|
result["_schema"] = p.Schema().Plan()
|
|
return result
|
|
}
|
|
|
|
func (p *PlanOpFeatureBaseColumns) String() string {
|
|
return ""
|
|
}
|
|
|
|
func (p *PlanOpFeatureBaseColumns) AddWarning(warning string) {
|
|
p.warnings = append(p.warnings, warning)
|
|
}
|
|
|
|
func (p *PlanOpFeatureBaseColumns) Warnings() []string {
|
|
return p.warnings
|
|
}
|
|
|
|
func (p *PlanOpFeatureBaseColumns) Schema() types.Schema {
|
|
return types.Schema{
|
|
&types.PlannerColumn{
|
|
RelationName: "fb_table_columns",
|
|
ColumnName: string(dax.PrimaryKeyFieldName),
|
|
Type: parser.NewDataTypeString(),
|
|
},
|
|
&types.PlannerColumn{
|
|
RelationName: "fb_table_columns",
|
|
ColumnName: "name",
|
|
Type: parser.NewDataTypeString(),
|
|
},
|
|
&types.PlannerColumn{
|
|
RelationName: "fb_table_columns",
|
|
ColumnName: "type",
|
|
Type: parser.NewDataTypeString(),
|
|
},
|
|
&types.PlannerColumn{
|
|
RelationName: "fb_table_columns",
|
|
ColumnName: "created_at",
|
|
Type: parser.NewDataTypeTimestamp(),
|
|
},
|
|
&types.PlannerColumn{
|
|
RelationName: "fb_table_columns",
|
|
ColumnName: "keys",
|
|
Type: parser.NewDataTypeBool(),
|
|
},
|
|
&types.PlannerColumn{
|
|
RelationName: "fb_table_columns",
|
|
ColumnName: "cache_type",
|
|
Type: parser.NewDataTypeString(),
|
|
},
|
|
&types.PlannerColumn{
|
|
RelationName: "fb_table_columns",
|
|
ColumnName: "cache_size",
|
|
Type: parser.NewDataTypeInt(),
|
|
},
|
|
&types.PlannerColumn{
|
|
RelationName: "fb_table_columns",
|
|
ColumnName: "scale",
|
|
Type: parser.NewDataTypeInt(),
|
|
},
|
|
&types.PlannerColumn{
|
|
RelationName: "fb_table_columns",
|
|
ColumnName: "min",
|
|
Type: parser.NewDataTypeInt(),
|
|
},
|
|
&types.PlannerColumn{
|
|
RelationName: "fb_table_columns",
|
|
ColumnName: "max",
|
|
Type: parser.NewDataTypeInt(),
|
|
},
|
|
&types.PlannerColumn{
|
|
RelationName: "fb_table_columns",
|
|
ColumnName: "timeunit",
|
|
Type: parser.NewDataTypeString(),
|
|
},
|
|
&types.PlannerColumn{
|
|
RelationName: "fb_table_columns",
|
|
ColumnName: "epoch",
|
|
Type: parser.NewDataTypeInt(),
|
|
},
|
|
&types.PlannerColumn{
|
|
RelationName: "fb_table_columns",
|
|
ColumnName: "timequantum",
|
|
Type: parser.NewDataTypeString(),
|
|
},
|
|
&types.PlannerColumn{
|
|
RelationName: "fb_table_columns",
|
|
ColumnName: "ttl",
|
|
Type: parser.NewDataTypeInt(),
|
|
},
|
|
}
|
|
}
|
|
|
|
func (p *PlanOpFeatureBaseColumns) Children() []types.PlanOperator {
|
|
return []types.PlanOperator{}
|
|
}
|
|
|
|
func (p *PlanOpFeatureBaseColumns) Iterator(ctx context.Context, row types.Row) (types.RowIterator, error) {
|
|
return &showColumnsRowIter{
|
|
tbl: p.tbl,
|
|
}, nil
|
|
}
|
|
|
|
func (p *PlanOpFeatureBaseColumns) WithChildren(children ...types.PlanOperator) (types.PlanOperator, error) {
|
|
return NewPlanOpFeatureBaseColumns(p.tbl), nil
|
|
}
|
|
|
|
type showColumnsRowIter struct {
|
|
tbl *dax.Table
|
|
rowIndex int
|
|
}
|
|
|
|
var _ types.RowIterator = (*showColumnsRowIter)(nil)
|
|
|
|
func (i *showColumnsRowIter) Next(ctx context.Context) (types.Row, error) {
|
|
if i.rowIndex < len(i.tbl.Fields) {
|
|
fields := i.tbl.Fields
|
|
|
|
tm := time.Unix(0, fields[i.rowIndex].CreatedAt)
|
|
|
|
row := []interface{}{
|
|
string(fields[i.rowIndex].Name),
|
|
string(fields[i.rowIndex].Name),
|
|
string(fields[i.rowIndex].Type),
|
|
tm.Format(time.RFC3339),
|
|
fields[i.rowIndex].StringKeys(),
|
|
fields[i.rowIndex].Options.CacheType,
|
|
fields[i.rowIndex].Options.CacheSize,
|
|
fields[i.rowIndex].Options.Scale,
|
|
fields[i.rowIndex].Options.Min.ToInt64(0),
|
|
fields[i.rowIndex].Options.Max.ToInt64(0),
|
|
fields[i.rowIndex].Options.TimeUnit,
|
|
0, //TODO(pok) get Epoch from somewhere?
|
|
fields[i.rowIndex].Options.TimeQuantum.String(),
|
|
fields[i.rowIndex].Options.TTL.String(),
|
|
}
|
|
|
|
i.rowIndex += 1
|
|
return row, nil
|
|
}
|
|
return nil, types.ErrNoMoreRows
|
|
}
|