featurebase/sql3/planner/opcreatetable.go
Seebs c8c88ab0ee don't panic on failed table creation
The attempt to set the TrackExistence option for fields
happened before checking whether the field was created
successfully or not. Credit to Rachith for spotting this.
Bug was introduced with the TrackExistence stuff, but
we apparently never had a test case for invalid min/max
values.
2023-04-03 16:29:21 -05:00

143 lines
3.7 KiB
Go

// Copyright 2021 Molecula Corp. All rights reserved.
package planner
import (
"context"
"fmt"
pilosa "github.com/featurebasedb/featurebase/v3"
"github.com/featurebasedb/featurebase/v3/dax"
"github.com/featurebasedb/featurebase/v3/sql3"
"github.com/featurebasedb/featurebase/v3/sql3/planner/types"
"github.com/pkg/errors"
)
// PlanOpCreateTable plan operator that creates a table.
type PlanOpCreateTable struct {
planner *ExecutionPlanner
tableName string
failIfExists bool
isKeyed bool
keyPartitions int
description string
columns []*createTableField
warnings []string
}
// NewPlanOpCreateTable returns a new PlanOpCreateTable planoperator
func NewPlanOpCreateTable(p *ExecutionPlanner, tableName string, failIfExists bool, isKeyed bool, keyPartitions int, description string, columns []*createTableField) *PlanOpCreateTable {
return &PlanOpCreateTable{
planner: p,
tableName: tableName,
failIfExists: failIfExists,
isKeyed: isKeyed,
keyPartitions: keyPartitions,
columns: columns,
description: description,
warnings: make([]string, 0),
}
}
func (p *PlanOpCreateTable) Plan() map[string]interface{} {
result := make(map[string]interface{})
result["_op"] = fmt.Sprintf("%T", p)
result["name"] = p.tableName
result["failIfExists"] = p.failIfExists
return result
}
func (p *PlanOpCreateTable) String() string {
return ""
}
func (p *PlanOpCreateTable) AddWarning(warning string) {
p.warnings = append(p.warnings, warning)
}
func (p *PlanOpCreateTable) Warnings() []string {
return p.warnings
}
func (p *PlanOpCreateTable) Schema() types.Schema {
return types.Schema{}
}
func (p *PlanOpCreateTable) Children() []types.PlanOperator {
return []types.PlanOperator{}
}
func (p *PlanOpCreateTable) Iterator(ctx context.Context, row types.Row) (types.RowIterator, error) {
return &createTableRowIter{
planner: p.planner,
tableName: p.tableName,
failIfExists: p.failIfExists,
isKeyed: p.isKeyed,
keyPartitions: p.keyPartitions,
columns: p.columns,
description: p.description,
}, nil
}
func (p *PlanOpCreateTable) WithChildren(children ...types.PlanOperator) (types.PlanOperator, error) {
return nil, nil
}
type createTableRowIter struct {
planner *ExecutionPlanner
tableName string
failIfExists bool
isKeyed bool
keyPartitions int
description string
columns []*createTableField
}
var _ types.RowIterator = (*createTableRowIter)(nil)
func (i *createTableRowIter) Next(ctx context.Context) (types.Row, error) {
//create the table
fields := make([]*dax.Field, 0, len(i.columns)+1)
// add the _id column
var idType dax.BaseType = dax.BaseTypeID
if i.isKeyed {
idType = dax.BaseTypeString
}
fields = append(fields, &dax.Field{
Name: dax.PrimaryKeyFieldName,
Type: idType,
})
for _, f := range i.columns {
fld, err := pilosa.FieldFromFieldOptions(dax.FieldName(f.name), f.fos...)
if err != nil {
return nil, errors.Wrapf(err, "creating field from field options: %s", f.name)
}
// We unconditionally turn on TrackExistence for all newly-created fields.
fld.Options.TrackExistence = true
fields = append(fields, fld)
}
tbl := &dax.Table{
Name: dax.TableName(i.tableName),
Fields: fields,
// TODO(tlt): once we can support different partitionN's per table,
// replace dax.DefaultPartitionN with i.keyPartitions.
PartitionN: dax.DefaultPartitionN,
Description: i.description,
}
if err := i.planner.schemaAPI.CreateTable(ctx, tbl); err != nil {
if _, ok := errors.Cause(err).(pilosa.ConflictError); ok {
if i.failIfExists {
return nil, sql3.NewErrTableExists(0, 0, i.tableName)
}
} else {
return nil, err
}
}
return nil, types.ErrNoMoreRows
}