featurebase/sql3/planner/systemobjects.go
pokeeffe-molecula c620aae350
implement CREATE/ALTER/DROP VIEW (fb-1592) (#2408)
* implement CREATE/ALTER/DROP VIEW

* fixed failing test

* another failing test

* fixed some broken serverless tests
2023-01-13 18:57:31 -06:00

247 lines
6.5 KiB
Go

// Copyright 2022 Molecula Corp. All rights reserved.
package planner
import (
"context"
"time"
pilosa "github.com/molecula/featurebase/v3"
"github.com/molecula/featurebase/v3/dax"
"github.com/molecula/featurebase/v3/sql3"
"github.com/molecula/featurebase/v3/sql3/parser"
"github.com/molecula/featurebase/v3/sql3/planner/types"
)
type viewSystemObject struct {
name string
statement string
}
func (p *ExecutionPlanner) ensureViewsSystemTableExists() error {
_, err := p.schemaAPI.TableByName(context.Background(), "fb_views")
if err != nil {
if !isTableNotFoundError(err) {
return err
}
// create table fb_views (
// _id string
// name string
// statement string
// owner string
// updated_by string
// created_at timestamp
// updated_at timestamp
// );
// if it doesn't, create it by making the appropriate iterator
iter := &createTableRowIter{
planner: p,
tableName: "fb_views",
failIfExists: false,
isKeyed: true,
keyPartitions: 0,
columns: []*createTableField{
{
planner: p,
name: "name",
typeName: dax.BaseTypeString,
fos: []pilosa.FieldOption{
pilosa.OptFieldTypeMutex(pilosa.DefaultCacheType, pilosa.DefaultCacheSize),
pilosa.OptFieldKeys(),
},
},
{
planner: p,
name: "statement",
typeName: dax.BaseTypeString,
fos: []pilosa.FieldOption{
pilosa.OptFieldTypeMutex(pilosa.DefaultCacheType, pilosa.DefaultCacheSize),
pilosa.OptFieldKeys(),
},
},
{
planner: p,
name: "owner",
typeName: dax.BaseTypeString,
fos: []pilosa.FieldOption{
pilosa.OptFieldTypeMutex(pilosa.DefaultCacheType, pilosa.DefaultCacheSize),
pilosa.OptFieldKeys(),
},
},
{
planner: p,
name: "updated_by",
typeName: dax.BaseTypeString,
fos: []pilosa.FieldOption{
pilosa.OptFieldTypeMutex(pilosa.DefaultCacheType, pilosa.DefaultCacheSize),
pilosa.OptFieldKeys(),
},
},
{
planner: p,
name: "created_at",
typeName: dax.BaseTypeTimestamp,
fos: []pilosa.FieldOption{
pilosa.OptFieldTypeTimestamp(pilosa.DefaultEpoch, pilosa.TimeUnitSeconds),
},
},
{
planner: p,
name: "updated_at",
typeName: dax.BaseTypeTimestamp,
fos: []pilosa.FieldOption{
pilosa.OptFieldTypeTimestamp(pilosa.DefaultEpoch, pilosa.TimeUnitSeconds),
},
},
},
description: "system table for views",
}
// call next on our iterator to create the table
_, err := iter.Next(context.Background())
if err != nil && err != types.ErrNoMoreRows {
return err
}
}
return nil
}
func (p *ExecutionPlanner) getViewByName(name string) (*viewSystemObject, error) {
err := p.ensureViewsSystemTableExists()
if err != nil {
return nil, err
}
tbl, err := p.schemaAPI.TableByName(context.Background(), "fb_views")
if err != nil {
return nil, sql3.NewErrTableNotFound(0, 0, "fb_views")
}
cols := make([]string, len(tbl.Fields))
for i, c := range tbl.Fields {
cols[i] = string(c.Name)
}
iter := &tableScanRowIter{
planner: p,
tableName: "fb_views",
columns: cols,
predicate: newBinOpPlanExpression(
newQualifiedRefPlanExpression("fb_views", "_id", 0, parser.NewDataTypeString()),
parser.EQ,
newStringLiteralPlanExpression(name),
parser.NewDataTypeBool(),
),
topExpr: nil,
}
row, err := iter.Next(context.Background())
if err != nil {
if err == types.ErrNoMoreRows {
// view does not exist
return nil, nil
}
return nil, err
}
return &viewSystemObject{
name: row[1].(string),
statement: row[2].(string),
}, nil
}
func (p *ExecutionPlanner) insertView(view *viewSystemObject) error {
err := p.ensureViewsSystemTableExists()
if err != nil {
return err
}
createTime := time.Now().UTC()
iter := &insertRowIter{
planner: p,
tableName: "fb_views",
targetColumns: []*qualifiedRefPlanExpression{
newQualifiedRefPlanExpression("fb_views", "_id", 0, parser.NewDataTypeString()),
newQualifiedRefPlanExpression("fb_views", "name", 0, parser.NewDataTypeString()),
newQualifiedRefPlanExpression("fb_views", "statement", 0, parser.NewDataTypeString()),
newQualifiedRefPlanExpression("fb_views", "owner", 0, parser.NewDataTypeString()),
newQualifiedRefPlanExpression("fb_views", "updated_by", 0, parser.NewDataTypeString()),
newQualifiedRefPlanExpression("fb_views", "created_at", 0, parser.NewDataTypeTimestamp()),
newQualifiedRefPlanExpression("fb_views", "updated_at", 0, parser.NewDataTypeTimestamp()),
},
insertValues: [][]types.PlanExpression{
{
newStringLiteralPlanExpression(view.name),
newStringLiteralPlanExpression(view.name),
newStringLiteralPlanExpression(view.statement),
newStringLiteralPlanExpression(""),
newStringLiteralPlanExpression(""),
newDateLiteralPlanExpression(createTime),
newDateLiteralPlanExpression(createTime),
},
},
}
_, err = iter.Next(context.Background())
if err != nil && err != types.ErrNoMoreRows {
return err
}
return nil
}
func (p *ExecutionPlanner) updateView(view *viewSystemObject) error {
err := p.ensureViewsSystemTableExists()
if err != nil {
return err
}
updateTime := time.Now().UTC()
iter := &insertRowIter{
planner: p,
tableName: "fb_views",
targetColumns: []*qualifiedRefPlanExpression{
newQualifiedRefPlanExpression("fb_views", "_id", 0, parser.NewDataTypeString()),
newQualifiedRefPlanExpression("fb_views", "statement", 0, parser.NewDataTypeString()),
newQualifiedRefPlanExpression("fb_views", "updated_by", 0, parser.NewDataTypeString()),
newQualifiedRefPlanExpression("fb_views", "updated_at", 0, parser.NewDataTypeTimestamp()),
},
insertValues: [][]types.PlanExpression{
{
newStringLiteralPlanExpression(view.name),
newStringLiteralPlanExpression(view.statement),
newStringLiteralPlanExpression(""),
newDateLiteralPlanExpression(updateTime),
},
},
}
_, err = iter.Next(context.Background())
if err != nil && err != types.ErrNoMoreRows {
return err
}
return nil
}
func (p *ExecutionPlanner) deleteView(viewName string) error {
err := p.ensureViewsSystemTableExists()
if err != nil {
return err
}
iter := &filteredDeleteRowIter{
planner: p,
tableName: "fb_views",
filter: newBinOpPlanExpression(
newQualifiedRefPlanExpression("fb_views", "_id", 0, parser.NewDataTypeString()),
parser.EQ,
newStringLiteralPlanExpression(viewName),
parser.NewDataTypeBool(),
),
}
_, err = iter.Next(context.Background())
if err != nil && err != types.ErrNoMoreRows {
return err
}
return nil
}