// Copyright 2022 Molecula Corp. All rights reserved. package planner import ( "context" "time" pilosa "github.com/featurebasedb/featurebase/v3" "github.com/featurebasedb/featurebase/v3/dax" "github.com/featurebasedb/featurebase/v3/sql3" "github.com/featurebasedb/featurebase/v3/sql3/parser" "github.com/featurebasedb/featurebase/v3/sql3/planner/types" ) type viewSystemObject struct { name string statement string } func (p *ExecutionPlanner) ensureViewsSystemTableExists(ctx context.Context) error { _, err := p.schemaAPI.TableByName(ctx, "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(ctx) if err != nil && err != types.ErrNoMoreRows { return err } } return nil } func (p *ExecutionPlanner) getViewByName(ctx context.Context, name string) (*viewSystemObject, error) { err := p.ensureViewsSystemTableExists(ctx) if err != nil { return nil, err } tbl, err := p.schemaAPI.TableByName(ctx, "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", string(dax.PrimaryKeyFieldName), 0, parser.NewDataTypeString()), parser.EQ, newStringLiteralPlanExpression(name), parser.NewDataTypeBool(), ), topExpr: nil, } row, err := iter.Next(ctx) 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(ctx context.Context, view *viewSystemObject) error { err := p.ensureViewsSystemTableExists(ctx) if err != nil { return err } createTime := time.Now().UTC() iter := &insertRowIter{ planner: p, tableName: "fb_views", targetColumns: []*qualifiedRefPlanExpression{ newQualifiedRefPlanExpression("fb_views", string(dax.PrimaryKeyFieldName), 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(ctx) if err != nil && err != types.ErrNoMoreRows { return err } return nil } func (p *ExecutionPlanner) updateView(ctx context.Context, view *viewSystemObject) error { err := p.ensureViewsSystemTableExists(ctx) if err != nil { return err } updateTime := time.Now().UTC() iter := &insertRowIter{ planner: p, tableName: "fb_views", targetColumns: []*qualifiedRefPlanExpression{ newQualifiedRefPlanExpression("fb_views", string(dax.PrimaryKeyFieldName), 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(ctx) if err != nil && err != types.ErrNoMoreRows { return err } return nil } func (p *ExecutionPlanner) deleteView(ctx context.Context, viewName string) error { err := p.ensureViewsSystemTableExists(ctx) if err != nil { return err } iter := &filteredDeleteRowIter{ planner: p, tableName: "fb_views", filter: newBinOpPlanExpression( newQualifiedRefPlanExpression("fb_views", string(dax.PrimaryKeyFieldName), 0, parser.NewDataTypeString()), parser.EQ, newStringLiteralPlanExpression(viewName), parser.NewDataTypeBool(), ), } _, err = iter.Next(ctx) if err != nil && err != types.ErrNoMoreRows { return err } return nil }