implement CREATE/ALTER/DROP VIEW (fb-1592) (#2408)

* implement CREATE/ALTER/DROP VIEW

* fixed failing test

* another failing test

* fixed some broken serverless tests

(cherry picked from commit c620aae350)
This commit is contained in:
pokeeffe-molecula 2023-01-13 18:57:31 -06:00 committed by Joe Friedrich
parent fc1b8fdfb8
commit 5d025e399b
29 changed files with 1125 additions and 122 deletions

View file

@ -112,6 +112,9 @@ func TestDAXIntegration(t *testing.T) {
"alterTable/alterTableBadTable", // looks like table does not exist is a different error in DAX
"top-tests/test-1", // don't know why this is failing at all
"delete_tests",
"viewtests/drop-view", // drop view does a delete
"viewtests/drop-view-if-exists-after-drop",
"viewtests/select-view-after-drop",
}
doSkip := func(name string) bool {

View file

@ -28,6 +28,9 @@ func NewOnPremSchema(api *API) *onPremSchema {
func (s *onPremSchema) TableByName(ctx context.Context, tname dax.TableName) (*dax.Table, error) {
idx, err := s.api.IndexInfo(context.Background(), string(tname))
if err != nil {
if err == ErrIndexNotFound {
return nil, dax.NewErrTableNameDoesNotExist(tname)
}
return nil, errors.Wrapf(err, "getting index info for table name: %s", tname)
}

View file

@ -79,6 +79,11 @@ const (
ErrTableColumnNotFound errors.Code = "ErrTableColumnNotFound"
ErrInvalidKeyPartitionsValue errors.Code = "ErrInvalidKeyPartitionsValue"
ErrTableOrViewNotFound errors.Code = "ErrTableOrViewNotFound"
ErrViewExists errors.Code = "ErrViewExists"
ErrViewNotFound errors.Code = "ErrViewNotFound"
ErrBadColumnConstraint errors.Code = "ErrBadColumnConstraint"
ErrConflictingColumnConstraint errors.Code = "ErrConflictingColumnConstraint"
@ -507,6 +512,13 @@ func NewErrTableNotFound(line, col int, tableName string) error {
)
}
func NewErrTableOrViewNotFound(line, col int, tableName string) error {
return errors.New(
ErrTableOrViewNotFound,
fmt.Sprintf("[%d:%d] table or view '%s' not found", line, col, tableName),
)
}
func NewErrTableExists(line, col int, tableName string) error {
return errors.New(
ErrTableExists,
@ -535,6 +547,20 @@ func NewErrInvalidKeyPartitionsValue(line, col int, keypartitions int64) error {
)
}
func NewErrViewNotFound(line, col int, viewName string) error {
return errors.New(
ErrViewNotFound,
fmt.Sprintf("[%d:%d] view '%s' not found", line, col, viewName),
)
}
func NewErrViewExists(line, col int, viewName string) error {
return errors.New(
ErrViewExists,
fmt.Sprintf("[%d:%d] view '%s' already exists", line, col, viewName),
)
}
func NewErrBadColumnConstraint(line, col int, constraint, columnType string) error {
return errors.New(
ErrBadColumnConstraint,

View file

@ -36,6 +36,7 @@ func (*CreateIndexStatement) node() {}
func (*CreateTableStatement) node() {}
func (*CreateFunctionStatement) node() {}
func (*CreateViewStatement) node() {}
func (*AlterViewStatement) node() {}
func (*DateLit) node() {}
func (*DefaultConstraint) node() {}
func (*DeleteStatement) node() {}
@ -111,6 +112,7 @@ func (*CreateIndexStatement) stmt() {}
func (*CreateTableStatement) stmt() {}
func (*CreateFunctionStatement) stmt() {}
func (*CreateViewStatement) stmt() {}
func (*AlterViewStatement) stmt() {}
func (*DeleteStatement) stmt() {}
func (*DropIndexStatement) stmt() {}
func (*DropTableStatement) stmt() {}
@ -2487,17 +2489,18 @@ func (s *DropTableStatement) String() string {
}
type CreateViewStatement struct {
Create Pos // position of CREATE keyword
View Pos // position of VIEW keyword
If Pos // position of IF keyword
IfNot Pos // position of NOT keyword after IF
IfNotExists Pos // position of EXISTS keyword after IF NOT
Name *Ident // view name
Lparen Pos // position of column list left paren
Columns []*Ident // column list
Rparen Pos // position of column list right paren
As Pos // position of AS keyword
Select *SelectStatement // source statement
Create Pos // position of CREATE keyword
View Pos // position of VIEW keyword
If Pos // position of IF keyword
IfNot Pos // position of NOT keyword after IF
IfNotExists Pos // position of EXISTS keyword after IF NOT
Name *Ident // view name
// TODO(pok) - we'll do this later - see note in parseCompileView()
// Lparen Pos // position of column list left paren
// Columns []*Ident // column list
// Rparen Pos // position of column list right paren
As Pos // position of AS keyword
Select *SelectStatement // source statement
}
// Clone returns a deep copy of s.
@ -2507,7 +2510,7 @@ func (s *CreateViewStatement) Clone() *CreateViewStatement {
}
other := *s
other.Name = s.Name.Clone()
other.Columns = cloneIdents(s.Columns)
// other.Columns = cloneIdents(s.Columns)
other.Select = s.Select.Clone()
return &other
}
@ -2521,22 +2524,67 @@ func (s *CreateViewStatement) String() string {
}
fmt.Fprintf(&buf, " %s", s.Name.String())
if len(s.Columns) > 0 {
buf.WriteString(" (")
for i, col := range s.Columns {
if i != 0 {
buf.WriteString(", ")
}
buf.WriteString(col.String())
}
buf.WriteString(")")
}
// if len(s.Columns) > 0 {
// buf.WriteString(" (")
// for i, col := range s.Columns {
// if i != 0 {
// buf.WriteString(", ")
// }
// buf.WriteString(col.String())
// }
// buf.WriteString(")")
// }
fmt.Fprintf(&buf, " AS %s", s.Select.String())
return buf.String()
}
type AlterViewStatement struct {
Alter Pos // position of CREATE keyword
View Pos // position of VIEW keyword
Name *Ident // view name
// TODO(pok) - we'll do this later - see note in parseCompileView()
// Lparen Pos // position of column list left paren
// Columns []*Ident // column list
// Rparen Pos // position of column list right paren
As Pos // position of AS keyword
Select *SelectStatement // source statement
}
// Clone returns a deep copy of s.
func (s *AlterViewStatement) Clone() *AlterViewStatement {
if s == nil {
return nil
}
other := *s
other.Name = s.Name.Clone()
// other.Columns = cloneIdents(s.Columns)
other.Select = s.Select.Clone()
return &other
}
// String returns the string representation of the statement.
func (s *AlterViewStatement) String() string {
var buf bytes.Buffer
buf.WriteString("ALTER VIEW")
fmt.Fprintf(&buf, " %s", s.Name.String())
// if len(s.Columns) > 0 {
// buf.WriteString(" (")
// for i, col := range s.Columns {
// if i != 0 {
// buf.WriteString(", ")
// }
// buf.WriteString(col.String())
// }
// buf.WriteString(")")
// }
fmt.Fprintf(&buf, " AS %s", s.Select.String())
return buf.String()
}
type DropViewStatement struct {
Drop Pos // position of DROP keyword
View Pos // position of VIEW keyword

View file

@ -395,14 +395,15 @@ func TestCreateFunctionStatement_String(t *testing.T) {
func TestCreateViewStatement_String(t *testing.T) {
AssertStatementStringer(t, &parser.CreateViewStatement{
Name: &parser.Ident{Name: "vw"},
Columns: []*parser.Ident{
{Name: "x"},
{Name: "y"},
},
// Columns: []*parser.Ident{
// {Name: "x"},
// {Name: "y"},
// },
Select: &parser.SelectStatement{
Columns: []*parser.ResultColumn{{Star: pos(0)}},
},
}, `CREATE VIEW vw (x, y) AS SELECT *`)
//}, `CREATE VIEW vw (x, y) AS SELECT *`)
}, `CREATE VIEW vw AS SELECT *`)
AssertStatementStringer(t, &parser.CreateViewStatement{
IfNotExists: pos(0),
@ -413,6 +414,20 @@ func TestCreateViewStatement_String(t *testing.T) {
}, `CREATE VIEW IF NOT EXISTS vw AS SELECT *`)
}
func TestAlterViewStatement_String(t *testing.T) {
AssertStatementStringer(t, &parser.AlterViewStatement{
Name: &parser.Ident{Name: "vw"},
// Columns: []*parser.Ident{
// {Name: "x"},
// {Name: "y"},
// },
Select: &parser.SelectStatement{
Columns: []*parser.ResultColumn{{Star: pos(0)}},
},
//}, `CREATE VIEW vw (x, y) AS SELECT *`)
}, `ALTER VIEW vw AS SELECT *`)
}
func TestDeleteStatement_String(t *testing.T) {
AssertStatementStringer(t, &parser.DeleteStatement{
TableName: &parser.QualifiedTableName{Name: &parser.Ident{Name: "tbl"}, Alias: &parser.Ident{Name: "tbl2"}},
@ -487,15 +502,14 @@ func TestDropTriggerStatement_String(t *testing.T) {
}
func TestDropViewStatement_String(t *testing.T) {
t.Skip("DROP VIEW is currently disabled in the parser")
AssertStatementStringer(t, &parser.DropViewStatement{
Name: &parser.Ident{Name: "vw"},
}, `DROP VIEW "vw"`)
}, `DROP VIEW vw`)
AssertStatementStringer(t, &parser.DropViewStatement{
IfExists: pos(0),
Name: &parser.Ident{Name: "vw"},
}, `DROP VIEW IF EXISTS "vw"`)
}, `DROP VIEW IF EXISTS vw`)
}
func TestExplainStatement_String(t *testing.T) {

View file

@ -98,17 +98,7 @@ func (p *Parser) parseNonExplainStatement() (Statement, error) {
//case ANALYZE:
// return p.parseAnalyzeStatement()
case ALTER:
return p.parseAlterTableStatement()
// case BEGIN:
// return p.parseBeginStatement()
// case COMMIT, END:
// return p.parseCommitStatement()
// case ROLLBACK:
// return p.parseRollbackStatement()
// case SAVEPOINT:
// return p.parseSavepointStatement()
// case RELEASE:
// return p.parseReleaseStatement()
return p.parseAlterStatement()
case BULK:
return p.parseBulkInsertStatement()
case CREATE:
@ -337,6 +327,20 @@ func (p *Parser) parseCreateStatement() (Statement, error) {
}
}
func (p *Parser) parseAlterStatement() (Statement, error) {
assert(p.peek() == ALTER)
pos, tok, _ := p.scan()
switch p.peek() {
case TABLE:
return p.parseAlterTableStatement(pos)
case VIEW:
return p.parseAlterViewStatement(pos)
default:
return nil, p.errorExpected(pos, tok, "TABLE or VIEW")
}
}
func (p *Parser) parseDropStatement() (Statement, error) {
assert(p.peek() == DROP)
pos, tok, _ := p.scan()
@ -1054,25 +1058,73 @@ func (p *Parser) parseCreateViewStatement(createPos Pos) (_ *CreateViewStatement
return &stmt, err
}
// TODO(pok) - we'll do this later - right now views are implemented as
// jit compiled from text, which makes implementing these columns a pain
// when we can pre-compile a plan op subgraph and stored it, we can put this
// back in
// Parse optional column list.
if p.peek() == LP {
stmt.Lparen, _, _ = p.scan()
for {
col, err := p.parseIdent("column name")
if err != nil {
return &stmt, err
}
stmt.Columns = append(stmt.Columns, col)
// if p.peek() == LP {
// stmt.Lparen, _, _ = p.scan()
// for {
// col, err := p.parseIdent("column name")
// if err != nil {
// return &stmt, err
// }
// stmt.Columns = append(stmt.Columns, col)
if p.peek() == RP {
break
} else if p.peek() != COMMA {
return &stmt, p.errorExpected(p.pos, p.tok, "comma or right paren")
}
p.scan()
}
stmt.Rparen, _, _ = p.scan()
// if p.peek() == RP {
// break
// } else if p.peek() != COMMA {
// return &stmt, p.errorExpected(p.pos, p.tok, "comma or right paren")
// }
// p.scan()
// }
// stmt.Rparen, _, _ = p.scan()
// }
// Parse "AS select-stmt"
if p.peek() != AS {
return &stmt, p.errorExpected(p.pos, p.tok, "AS")
}
stmt.As, _, _ = p.scan()
if stmt.Select, err = p.parseSelectStatement(false, nil); err != nil {
return &stmt, err
}
return &stmt, nil
}
func (p *Parser) parseAlterViewStatement(alterPos Pos) (_ *AlterViewStatement, err error) {
var stmt AlterViewStatement
stmt.Alter = alterPos
if p.peek() != VIEW {
return &stmt, p.errorExpected(p.pos, p.tok, "VIEW")
}
stmt.View, _, _ = p.scan()
if stmt.Name, err = p.parseIdent("view name"); err != nil {
return &stmt, err
}
// TODO(pok) - we'll do this later - see note in parseCompileView()
// Parse optional column list.
// if p.peek() == LP {
// stmt.Lparen, _, _ = p.scan()
// for {
// col, err := p.parseIdent("column name")
// if err != nil {
// return &stmt, err
// }
// stmt.Columns = append(stmt.Columns, col)
// if p.peek() == RP {
// break
// } else if p.peek() != COMMA {
// return &stmt, p.errorExpected(p.pos, p.tok, "comma or right paren")
// }
// p.scan()
// }
// stmt.Rparen, _, _ = p.scan()
// }
// Parse "AS select-stmt"
if p.peek() != AS {
@ -3225,11 +3277,9 @@ func (p *Parser) parseIntegerLiteral(desc string) (*IntegerLit, error) {
}
}
func (p *Parser) parseAlterTableStatement() (_ *AlterTableStatement, err error) {
assert(p.peek() == ALTER)
func (p *Parser) parseAlterTableStatement(alterPos Pos) (_ *AlterTableStatement, err error) {
var stmt AlterTableStatement
stmt.Alter, _, _ = p.scan()
stmt.Alter = alterPos
if p.peek() != TABLE {
return &stmt, p.errorExpected(p.pos, p.tok, "TABLE")
}

View file

@ -427,7 +427,7 @@ func TestParser_ParseAlterStatement(t *testing.T) {
DropColumnName: &parser.Ident{NamePos: pos(28), Name: "col"},
})
AssertParseStatementError(t, `ALTER`, `1:5: expected TABLE, found 'EOF'`)
AssertParseStatementError(t, `ALTER`, `1:1: expected TABLE or VIEW`)
AssertParseStatementError(t, `ALTER TABLE`, `1:11: expected table name, found 'EOF'`)
AssertParseStatementError(t, `ALTER TABLE tbl`, `1:15: expected ADD, DROP or RENAME, found 'EOF'`)
AssertParseStatementError(t, `ALTER TABLE tbl RENAME`, `1:22: expected COLUMN keyword or column name, found 'EOF'`)
@ -1566,22 +1566,23 @@ func TestParser_ParseStatement(t *testing.T) {
})
t.Run("CreateView", func(t *testing.T) {
AssertParseStatement(t, `CREATE VIEW vw (col1, col2) AS SELECT x, y`, &parser.CreateViewStatement{
//AssertParseStatement(t, `CREATE VIEW vw (col1, col2) AS SELECT x, y`, &parser.CreateViewStatement{
AssertParseStatement(t, `CREATE VIEW vw AS SELECT x, y`, &parser.CreateViewStatement{
Create: pos(0),
View: pos(7),
Name: &parser.Ident{NamePos: pos(12), Name: "vw"},
Lparen: pos(15),
Columns: []*parser.Ident{
{NamePos: pos(16), Name: "col1"},
{NamePos: pos(22), Name: "col2"},
},
Rparen: pos(26),
As: pos(28),
// Lparen: pos(15),
// Columns: []*parser.Ident{
// {NamePos: pos(16), Name: "col1"},
// {NamePos: pos(22), Name: "col2"},
// },
// Rparen: pos(26),
As: pos(15),
Select: &parser.SelectStatement{
Select: pos(31),
Select: pos(18),
Columns: []*parser.ResultColumn{
{Expr: &parser.Ident{NamePos: pos(38), Name: "x"}},
{Expr: &parser.Ident{NamePos: pos(41), Name: "y"}},
{Expr: &parser.Ident{NamePos: pos(25), Name: "x"}},
{Expr: &parser.Ident{NamePos: pos(28), Name: "y"}},
},
},
})
@ -1616,8 +1617,8 @@ func TestParser_ParseStatement(t *testing.T) {
AssertParseStatementError(t, `CREATE VIEW IF`, `1:14: expected NOT, found 'EOF'`)
AssertParseStatementError(t, `CREATE VIEW IF NOT`, `1:18: expected EXISTS, found 'EOF'`)
AssertParseStatementError(t, `CREATE VIEW vw`, `1:14: expected AS, found 'EOF'`)
AssertParseStatementError(t, `CREATE VIEW vw (`, `1:16: expected column name, found 'EOF'`)
AssertParseStatementError(t, `CREATE VIEW vw (x`, `1:17: expected comma or right paren, found 'EOF'`)
//AssertParseStatementError(t, `CREATE VIEW vw (`, `1:16: expected column name, found 'EOF'`)
//AssertParseStatementError(t, `CREATE VIEW vw (x`, `1:17: expected comma or right paren, found 'EOF'`)
AssertParseStatementError(t, `CREATE VIEW vw AS`, `1:17: expected SELECT, found 'EOF'`)
AssertParseStatementError(t, `CREATE VIEW vw AS SELECT`, `1:24: expected expression, found 'EOF'`)
})

View file

@ -114,9 +114,9 @@ func walk(v Visitor, node Node) (_ Node, err error) {
if err := walkIdent(v, &n.Name); err != nil {
return node, err
}
if err := walkIdentList(v, n.Columns); err != nil {
return node, err
}
// if err := walkIdentList(v, n.Columns); err != nil {
// return node, err
// }
if n.Select != nil {
if sel, err := walk(v, n.Select); err != nil {
return node, err

View file

@ -31,7 +31,7 @@ func (p *ExecutionPlanner) compileAlterTableStatement(stmt *parser.AlterTableSta
tname := dax.TableName(tableName)
tbl, err := p.schemaAPI.TableByName(context.Background(), tname)
if err != nil {
if errors.Is(err, pilosa.ErrIndexNotFound) {
if isTableNotFoundError(err) {
return nil, sql3.NewErrTableNotFound(stmt.Name.NamePos.Line, stmt.Name.NamePos.Column, tableName)
}
return nil, err

View file

@ -25,7 +25,7 @@ func (p *ExecutionPlanner) compileBulkInsertStatement(stmt *parser.BulkInsertSta
tname := dax.TableName(tableName)
tbl, err := p.schemaAPI.TableByName(context.Background(), tname)
if err != nil {
if errors.Is(err, pilosa.ErrIndexNotFound) {
if isTableNotFoundError(err) {
return nil, sql3.NewErrTableNotFound(stmt.Table.NamePos.Line, stmt.Table.NamePos.Column, tableName)
}
return nil, err
@ -63,7 +63,7 @@ func (p *ExecutionPlanner) compileBulkInsertStatement(stmt *parser.BulkInsertSta
switch strings.ToUpper(options.input) {
case "FILE":
// file should exist
if _, err := os.Stat(options.sourceData); errors.Is(err, os.ErrNotExist) {
if _, err := os.Stat(options.sourceData); goerrors.Is(err, os.ErrNotExist) {
return nil, sql3.NewErrReadingDatasource(stmt.DataSource.Pos().Line, stmt.DataSource.Pos().Column, options.sourceData, fmt.Sprintf("file '%s' does not exist", options.sourceData))
}
case "URL", "STREAM":
@ -163,7 +163,7 @@ func (p *ExecutionPlanner) analyzeBulkInsertStatement(stmt *parser.BulkInsertSta
tname := dax.TableName(tableName)
tbl, err := p.schemaAPI.TableByName(context.Background(), tname)
if err != nil {
if errors.Is(err, pilosa.ErrIndexNotFound) {
if isTableNotFoundError(err) {
return sql3.NewErrTableNotFound(stmt.Table.NamePos.Line, stmt.Table.NamePos.Column, tableName)
}
return err

View file

@ -0,0 +1,64 @@
// Copyright 2022 Molecula Corp. All rights reserved.
package planner
import (
"github.com/molecula/featurebase/v3/sql3/parser"
"github.com/molecula/featurebase/v3/sql3/planner/types"
)
// compileCreateViewStatement compiles a parser.CreateViewStatement AST into a PlanOperator
func (p *ExecutionPlanner) compileCreateViewStatement(stmt *parser.CreateViewStatement) (types.PlanOperator, error) {
viewName := parser.IdentName(stmt.Name)
view := &viewSystemObject{
name: viewName,
}
// compile select
_, err := p.compileSelectStatement(stmt.Select, true)
if err != nil {
return nil, err
}
view.statement = stmt.Select.String()
query := NewPlanOpQuery(p, NewPlanOpCreateView(p, stmt.IfNotExists.IsValid(), view), p.sql)
return query, nil
}
// compileAlterViewStatement compiles a parser.AlterViewStatement AST into a PlanOperator
func (p *ExecutionPlanner) compileAlterViewStatement(stmt *parser.AlterViewStatement) (types.PlanOperator, error) {
viewName := parser.IdentName(stmt.Name)
view := &viewSystemObject{
name: viewName,
}
// compile select
_, err := p.compileSelectStatement(stmt.Select, true)
if err != nil {
return nil, err
}
view.statement = stmt.Select.String()
query := NewPlanOpQuery(p, NewPlanOpAlterView(p, view), p.sql)
return query, nil
}
func (p *ExecutionPlanner) analyzeCreateViewStatement(stmt *parser.CreateViewStatement) error {
//analyze the select
_, err := p.analyzeSelectStatement(stmt.Select)
if err != nil {
return err
}
return nil
}
func (p *ExecutionPlanner) analyzeAlterViewStatement(stmt *parser.AlterViewStatement) error {
//analyze the select
_, err := p.analyzeSelectStatement(stmt.Select)
if err != nil {
return err
}
return nil
}

View file

@ -54,7 +54,7 @@ func (p *ExecutionPlanner) compileDeleteStatement(stmt *parser.DeleteStatement)
func (p *ExecutionPlanner) analyzeDeleteStatement(stmt *parser.DeleteStatement) error {
err := p.analyzeSource(stmt.Source, stmt)
_, err := p.analyzeSource(stmt.Source, stmt)
if err != nil {
return err
}

View file

@ -20,7 +20,7 @@ func (p *ExecutionPlanner) compileDropTableStatement(stmt *parser.DropTableState
tname := dax.TableName(tableName)
tbl, err := p.schemaAPI.TableByName(context.Background(), tname)
if err != nil {
if errors.Is(err, pilosa.ErrIndexNotFound) {
if isTableNotFoundError(err) {
return nil, sql3.NewErrTableNotFound(stmt.Name.NamePos.Line, stmt.Name.NamePos.Column, tableName)
}
return nil, err

View file

@ -0,0 +1,23 @@
// Copyright 2023 Molecula Corp. All rights reserved.
package planner
import (
"github.com/molecula/featurebase/v3/sql3"
"github.com/molecula/featurebase/v3/sql3/parser"
"github.com/molecula/featurebase/v3/sql3/planner/types"
)
// compileDropViewStatement compiles a DROP VIEW statement into a PlanOperator.
func (p *ExecutionPlanner) compileDropViewStatement(stmt *parser.DropViewStatement) (_ types.PlanOperator, err error) {
viewName := parser.IdentName(stmt.Name)
v, err := p.getViewByName(viewName)
if err != nil {
return nil, err
}
if v == nil && !stmt.IfExists.IsValid() {
return nil, sql3.NewErrViewNotFound(0, 0, viewName)
}
return NewPlanOpQuery(p, NewPlanOpDropView(p, stmt.IfExists.IsValid(), viewName), p.sql), nil
}

View file

@ -24,7 +24,7 @@ func (p *ExecutionPlanner) compileInsertStatement(stmt *parser.InsertStatement)
tname := dax.TableName(tableName)
tbl, err := p.schemaAPI.TableByName(context.Background(), tname)
if err != nil {
if errors.Is(err, pilosa.ErrIndexNotFound) {
if isTableNotFoundError(err) {
return nil, sql3.NewErrTableNotFound(stmt.Table.NamePos.Line, stmt.Table.NamePos.Column, tableName)
}
return nil, err
@ -79,7 +79,7 @@ func (p *ExecutionPlanner) analyzeInsertStatement(stmt *parser.InsertStatement)
tname := dax.TableName(tableName)
tbl, err := p.schemaAPI.TableByName(context.Background(), tname)
if err != nil {
if errors.Is(err, pilosa.ErrIndexNotFound) {
if isTableNotFoundError(err) {
return sql3.NewErrTableNotFound(stmt.Table.NamePos.Line, stmt.Table.NamePos.Column, tableName)
}
return err

View file

@ -366,57 +366,97 @@ func (p *ExecutionPlanner) compileSource(scope *PlanOpQuery, source parser.Sourc
}
}
func (p *ExecutionPlanner) analyzeSource(source parser.Source, scope parser.Statement) error {
func (p *ExecutionPlanner) analyzeSource(source parser.Source, scope parser.Statement) (parser.Source, error) {
if source == nil {
return nil
return nil, nil
}
switch source := source.(type) {
case *parser.JoinClause:
err := p.analyzeSource(source.X, scope)
x, err := p.analyzeSource(source.X, scope)
if err != nil {
return err
return nil, err
}
err = p.analyzeSource(source.Y, scope)
y, err := p.analyzeSource(source.Y, scope)
if err != nil {
return err
return nil, err
}
if source.Constraint != nil {
switch join := source.Constraint.(type) {
case *parser.OnConstraint:
ex, err := p.analyzeExpression(join.X, scope)
if err != nil {
return err
return nil, err
}
join.X = ex
default:
return sql3.NewErrInternalf("unexpected constraint type '%T'", join)
return nil, sql3.NewErrInternalf("unexpected constraint type '%T'", join)
}
}
return nil
source.X = x
source.Y = y
return source, nil
case *parser.ParenSource:
err := p.analyzeSource(source.X, scope)
x, err := p.analyzeSource(source.X, scope)
if err != nil {
return err
return nil, err
}
return nil
source.X = x
return source, nil
case *parser.QualifiedTableName:
objectName := parser.IdentName(source.Name)
// check views first
view, err := p.getViewByName(objectName)
if err != nil {
return nil, err
}
// if view is not null, it exists
if view != nil {
// parse the select statement
ast, err := parser.NewParser(strings.NewReader(view.statement)).ParseStatement()
if err != nil {
return nil, err
}
sel, ok := ast.(*parser.SelectStatement)
if !ok {
return nil, sql3.NewErrInternalf("unexpected ast type")
}
// analyze the select statement
expr, err := p.analyzeSelectStatement(sel)
if err != nil {
return nil, err
}
selExpr, ok := expr.(*parser.SelectStatement)
if !ok {
return nil, sql3.NewErrInternalf("unexpected analyzed type")
}
// rewrite as a paren source with the select
paren := &parser.ParenSource{
X: selExpr,
Alias: source.Alias,
}
return paren, nil
}
// check table exists
tableName := parser.IdentName(source.Name)
tname := dax.TableName(tableName)
tname := dax.TableName(objectName)
tbl, err := p.schemaAPI.TableByName(context.Background(), tname)
if err != nil {
if errors.Is(err, pilosa.ErrIndexNotFound) {
return sql3.NewErrTableNotFound(source.Name.NamePos.Line, source.Name.NamePos.Column, tableName)
if isTableNotFoundError(err) {
return nil, sql3.NewErrTableOrViewNotFound(source.Name.NamePos.Line, source.Name.NamePos.Column, objectName)
}
return err
return nil, err
}
// populate the output columns from the source
for i, fld := range tbl.Fields {
soc := &parser.SourceOutputColumn{
TableName: tableName,
TableName: objectName,
ColumnName: string(fld.Name),
ColumnIndex: i,
Datatype: fieldSQLDataType(pilosa.FieldToFieldInfo(fld)),
@ -424,7 +464,7 @@ func (p *ExecutionPlanner) analyzeSource(source parser.Source, scope parser.Stat
source.OutputColumns = append(source.OutputColumns, soc)
}
return nil
return source, nil
case *parser.TableValuedFunction:
// check it actually is a table valued function - we only support one right now; subtable()
@ -432,12 +472,12 @@ func (p *ExecutionPlanner) analyzeSource(source parser.Source, scope parser.Stat
case "SUBTABLE":
_, err := p.analyzeCallExpression(source.Call, scope)
if err != nil {
return err
return nil, err
}
tvfResultType, ok := source.Call.ResultDataType.(*parser.DataTypeSubtable)
if !ok {
return sql3.NewErrInternalf("unexepected tvf return type")
return nil, sql3.NewErrInternalf("unexepected tvf return type")
}
// populate the output columns from the source
@ -452,29 +492,34 @@ func (p *ExecutionPlanner) analyzeSource(source parser.Source, scope parser.Stat
}
default:
return sql3.NewErrInternalf("table valued function expected")
return nil, sql3.NewErrInternalf("table valued function expected")
}
return nil
return source, nil
case *parser.SelectStatement:
_, err := p.analyzeSelectStatement(source)
expr, err := p.analyzeSelectStatement(source)
if err != nil {
return err
return nil, err
}
return nil
selExpr, ok := expr.(*parser.SelectStatement)
if !ok {
return nil, sql3.NewErrInternalf("unexpected analyzed type")
}
return selExpr, nil
default:
return sql3.NewErrInternalf("unexpected source type: %T", source)
return nil, sql3.NewErrInternalf("unexpected source type: %T", source)
}
}
func (p *ExecutionPlanner) analyzeSelectStatement(stmt *parser.SelectStatement) (parser.Expr, error) {
// analyze source first - needed for name resolution
err := p.analyzeSource(stmt.Source, stmt)
source, err := p.analyzeSource(stmt.Source, stmt)
if err != nil {
return nil, err
}
stmt.Source = source
if err := p.analyzeSelectStatementWildcards(stmt); err != nil {
return nil, err
@ -617,7 +662,17 @@ func (p *ExecutionPlanner) columnsFromSource(source parser.Source) ([]*parser.Re
return result, nil
case *parser.SelectStatement:
return nil, sql3.NewErrInternal("sub-selects are not currently supported")
for _, oc := range src.PossibleOutputColumns() {
result = append(result, &parser.ResultColumn{
Expr: &parser.QualifiedRef{
Table: &parser.Ident{Name: oc.TableName},
Column: &parser.Ident{Name: oc.ColumnName},
ColumnIndex: oc.ColumnIndex,
},
})
}
return result, nil
default:
return nil, sql3.NewErrInternalf("unexpected source type: %T", source)
}

View file

@ -84,7 +84,7 @@ func (p *ExecutionPlanner) compileShowColumnsStatement(stmt *parser.ShowColumnsS
tname := dax.TableName(tableName)
tbl, err := p.schemaAPI.TableByName(context.Background(), tname)
if err != nil {
if errors.Is(err, pilosa.ErrIndexNotFound) {
if isTableNotFoundError(err) {
return nil, sql3.NewErrTableNotFound(stmt.TableName.NamePos.Line, stmt.TableName.NamePos.Column, tableName)
}
return nil, err
@ -174,7 +174,7 @@ func (p *ExecutionPlanner) compileShowCreateTableStatement(stmt *parser.ShowCrea
tableName := parser.IdentName(stmt.TableName)
tname := dax.TableName(tableName)
if _, err := p.schemaAPI.TableByName(context.Background(), tname); err != nil {
if errors.Is(err, pilosa.ErrIndexNotFound) {
if isTableNotFoundError(err) {
return nil, sql3.NewErrTableNotFound(stmt.TableName.NamePos.Line, stmt.TableName.NamePos.Column, tableName)
}
return nil, err

View file

@ -9,6 +9,7 @@ import (
"io"
"net/http"
"strconv"
"strings"
pilosa "github.com/featurebasedb/featurebase/v3"
"github.com/featurebasedb/featurebase/v3/logger"
@ -17,6 +18,11 @@ import (
"github.com/featurebasedb/featurebase/v3/sql3/planner/types"
)
func isTableNotFoundError(err error) bool {
// TODO (pok) take out the second part of this check once we return correct error types across network boundaries
return errors.Is(err, dax.ErrTableNameDoesNotExist) || strings.Contains(err.Error(), "does not exist")
}
// ExecutionPlanner compiles SQL text into a query plan
type ExecutionPlanner struct {
executor pilosa.Executor
@ -64,10 +70,16 @@ func (p *ExecutionPlanner) CompilePlan(ctx context.Context, stmt parser.Statemen
rootOperator, err = p.compileShowCreateTableStatement(stmt)
case *parser.CreateTableStatement:
rootOperator, err = p.compileCreateTableStatement(stmt)
case *parser.CreateViewStatement:
rootOperator, err = p.compileCreateViewStatement(stmt)
case *parser.AlterTableStatement:
rootOperator, err = p.compileAlterTableStatement(stmt)
case *parser.AlterViewStatement:
rootOperator, err = p.compileAlterViewStatement(stmt)
case *parser.DropTableStatement:
rootOperator, err = p.compileDropTableStatement(stmt)
case *parser.DropViewStatement:
rootOperator, err = p.compileDropViewStatement(stmt)
case *parser.InsertStatement:
rootOperator, err = p.compileInsertStatement(stmt)
case *parser.BulkInsertStatement:
@ -111,10 +123,16 @@ func (p *ExecutionPlanner) analyzePlan(stmt parser.Statement) error {
return nil
case *parser.CreateTableStatement:
return p.analyzeCreateTableStatement(stmt)
case *parser.CreateViewStatement:
return p.analyzeCreateViewStatement(stmt)
case *parser.AlterTableStatement:
return p.analyzeAlterTableStatement(stmt)
case *parser.AlterViewStatement:
return p.analyzeAlterViewStatement(stmt)
case *parser.DropTableStatement:
return nil
case *parser.DropViewStatement:
return nil
case *parser.InsertStatement:
return p.analyzeInsertStatement(stmt)
case *parser.BulkInsertStatement:

View file

@ -28,10 +28,10 @@ func newSystemTableDefintionsWrapper(api pilosa.SchemaAPI) *systemTableDefintion
func (s *systemTableDefintionsWrapper) TableByName(ctx context.Context, tname dax.TableName) (*dax.Table, error) {
tbl, err := s.schemaAPI.TableByName(ctx, tname)
if err != nil {
if errors.Is(err, pilosa.ErrIndexNotFound) {
if isTableNotFoundError(err) {
st, ok := systemTables[string(tname)]
if !ok {
return nil, pilosa.ErrIndexNotFound
return nil, dax.NewErrTableNameDoesNotExist(tname)
}
return indexInfoFromSystemTableB(st)

View file

@ -0,0 +1,97 @@
// Copyright 2022 Molecula Corp. All rights reserved.
package planner
import (
"context"
"fmt"
"github.com/molecula/featurebase/v3/sql3"
"github.com/molecula/featurebase/v3/sql3/planner/types"
)
// PlanOpAlterView implements the ALTER VIEW operator
type PlanOpAlterView struct {
planner *ExecutionPlanner
view *viewSystemObject
warnings []string
}
func NewPlanOpAlterView(planner *ExecutionPlanner, view *viewSystemObject) *PlanOpAlterView {
return &PlanOpAlterView{
planner: planner,
view: view,
warnings: make([]string, 0),
}
}
func (p *PlanOpAlterView) Schema() types.Schema {
return types.Schema{}
}
func (p *PlanOpAlterView) Iterator(ctx context.Context, row types.Row) (types.RowIterator, error) {
return newAlterViewIter(p.planner, p.view), nil
}
func (p *PlanOpAlterView) Children() []types.PlanOperator {
return []types.PlanOperator{}
}
func (p *PlanOpAlterView) WithChildren(children ...types.PlanOperator) (types.PlanOperator, error) {
if len(children) != 0 {
return nil, sql3.NewErrInternalf("unexpected number of children '%d'", len(children))
}
return NewPlanOpAlterView(p.planner, p.view), nil
}
func (p *PlanOpAlterView) Plan() map[string]interface{} {
result := make(map[string]interface{})
result["_op"] = fmt.Sprintf("%T", p)
result["_schema"] = p.Schema().Plan()
result["model"] = p.view.name
return result
}
func (p *PlanOpAlterView) String() string {
return ""
}
func (p *PlanOpAlterView) AddWarning(warning string) {
p.warnings = append(p.warnings, warning)
}
func (p *PlanOpAlterView) Warnings() []string {
var w []string
w = append(w, p.warnings...)
return w
}
type alterViewIter struct {
planner *ExecutionPlanner
view *viewSystemObject
}
func newAlterViewIter(planner *ExecutionPlanner, view *viewSystemObject) *alterViewIter {
return &alterViewIter{
planner: planner,
view: view,
}
}
func (i *alterViewIter) Next(ctx context.Context) (types.Row, error) {
// now check in the views table to see if it exists
v, err := i.planner.getViewByName(i.view.name)
if err != nil {
return nil, err
}
if v == nil {
return nil, sql3.NewErrViewNotFound(0, 0, i.view.name)
}
// now store the view into fb_views
err = i.planner.updateView(i.view)
if err != nil {
return nil, err
}
return nil, types.ErrNoMoreRows
}

View file

@ -0,0 +1,120 @@
// Copyright 2022 Molecula Corp. All rights reserved.
package planner
import (
"context"
"fmt"
"github.com/molecula/featurebase/v3/dax"
"github.com/molecula/featurebase/v3/sql3"
"github.com/molecula/featurebase/v3/sql3/planner/types"
)
// PlanOpCreateView implements the CREATE VIEW operator
type PlanOpCreateView struct {
planner *ExecutionPlanner
view *viewSystemObject
ifNotExists bool
warnings []string
}
func NewPlanOpCreateView(planner *ExecutionPlanner, ifNotExists bool, view *viewSystemObject) *PlanOpCreateView {
return &PlanOpCreateView{
planner: planner,
view: view,
ifNotExists: ifNotExists,
warnings: make([]string, 0),
}
}
func (p *PlanOpCreateView) Schema() types.Schema {
return types.Schema{}
}
func (p *PlanOpCreateView) Iterator(ctx context.Context, row types.Row) (types.RowIterator, error) {
return newCreateViewIter(p.planner, p.ifNotExists, p.view), nil
}
func (p *PlanOpCreateView) Children() []types.PlanOperator {
return []types.PlanOperator{}
}
func (p *PlanOpCreateView) WithChildren(children ...types.PlanOperator) (types.PlanOperator, error) {
if len(children) != 0 {
return nil, sql3.NewErrInternalf("unexpected number of children '%d'", len(children))
}
return NewPlanOpCreateView(p.planner, p.ifNotExists, p.view), nil
}
func (p *PlanOpCreateView) Plan() map[string]interface{} {
result := make(map[string]interface{})
result["_op"] = fmt.Sprintf("%T", p)
result["_schema"] = p.Schema().Plan()
result["model"] = p.view.name
return result
}
func (p *PlanOpCreateView) String() string {
return ""
}
func (p *PlanOpCreateView) AddWarning(warning string) {
p.warnings = append(p.warnings, warning)
}
func (p *PlanOpCreateView) Warnings() []string {
var w []string
w = append(w, p.warnings...)
return w
}
type createViewIter struct {
planner *ExecutionPlanner
view *viewSystemObject
ifNotExists bool
}
func newCreateViewIter(planner *ExecutionPlanner, ifNotExists bool, view *viewSystemObject) *createViewIter {
return &createViewIter{
planner: planner,
view: view,
ifNotExists: ifNotExists,
}
}
func (i *createViewIter) Next(ctx context.Context) (types.Row, error) {
// make sure we have no existing table named the same as our view
viewName := dax.TableName(i.view.name)
tbl, err := i.planner.schemaAPI.TableByName(context.Background(), viewName)
if err != nil {
if !isTableNotFoundError(err) {
return nil, err
}
}
if tbl != nil {
if i.ifNotExists {
return nil, types.ErrNoMoreRows
}
return nil, sql3.NewErrViewExists(0, 0, i.view.name)
}
// now check in the views table to see if it is exists
v, err := i.planner.getViewByName(i.view.name)
if err != nil {
return nil, err
}
if v != nil {
if i.ifNotExists {
return nil, types.ErrNoMoreRows
}
return nil, sql3.NewErrViewExists(0, 0, i.view.name)
}
// now store the view into fb_views
err = i.planner.insertView(i.view)
if err != nil {
return nil, err
}
return nil, types.ErrNoMoreRows
}

102
sql3/planner/opdropview.go Normal file
View file

@ -0,0 +1,102 @@
// Copyright 2023 Molecula Corp. All rights reserved.
package planner
import (
"context"
"fmt"
"github.com/molecula/featurebase/v3/sql3"
"github.com/molecula/featurebase/v3/sql3/planner/types"
)
// PlanOpDropView plan operator to drop a view.
type PlanOpDropView struct {
planner *ExecutionPlanner
viewName string
ifExists bool
warnings []string
}
func NewPlanOpDropView(p *ExecutionPlanner, ifExists bool, viewName string) *PlanOpDropView {
return &PlanOpDropView{
planner: p,
viewName: viewName,
ifExists: ifExists,
warnings: make([]string, 0),
}
}
func (p *PlanOpDropView) Plan() map[string]interface{} {
result := make(map[string]interface{})
result["_op"] = fmt.Sprintf("%T", p)
result["viewName"] = p.viewName
result["isExists"] = p.ifExists
return result
}
func (p *PlanOpDropView) String() string {
return ""
}
func (p *PlanOpDropView) AddWarning(warning string) {
p.warnings = append(p.warnings, warning)
}
func (p *PlanOpDropView) Warnings() []string {
return p.warnings
}
func (p *PlanOpDropView) Schema() types.Schema {
return types.Schema{}
}
func (p *PlanOpDropView) Children() []types.PlanOperator {
return []types.PlanOperator{}
}
func (p *PlanOpDropView) Iterator(ctx context.Context, row types.Row) (types.RowIterator, error) {
return &dropViewRowIter{
planner: p.planner,
ifExists: p.ifExists,
viewName: p.viewName,
}, nil
}
func (p *PlanOpDropView) WithChildren(children ...types.PlanOperator) (types.PlanOperator, error) {
return nil, nil
}
type dropViewRowIter struct {
planner *ExecutionPlanner
ifExists bool
viewName string
}
var _ types.RowIterator = (*dropViewRowIter)(nil)
func (i *dropViewRowIter) Next(ctx context.Context) (types.Row, error) {
err := i.planner.checkAccess(ctx, i.viewName, accessTypeDropObject)
if err != nil {
return nil, err
}
// check in the views table to see if it exists
v, err := i.planner.getViewByName(i.viewName)
if err != nil {
return nil, err
}
if v == nil {
if i.ifExists {
return nil, types.ErrNoMoreRows
}
return nil, sql3.NewErrViewNotFound(0, 0, i.viewName)
}
err = i.planner.deleteView(i.viewName)
if err != nil {
return nil, err
}
return nil, types.ErrNoMoreRows
}

View file

@ -142,7 +142,7 @@ func (i *distinctScanRowIter) Next(ctx context.Context) (types.Row, error) {
tname := dax.TableName(i.tableName)
table, err := i.planner.schemaAPI.TableByName(context.Background(), tname)
if err != nil {
if errors.Is(err, pilosa.ErrIndexNotFound) {
if isTableNotFoundError(err) {
return nil, sql3.NewErrInternalf("table not found '%s'", i.tableName)
}
return nil, err

View file

@ -146,7 +146,7 @@ func (i *tableScanRowIter) Next(ctx context.Context) (types.Row, error) {
tname := dax.TableName(i.tableName)
table, err := i.planner.schemaAPI.TableByName(context.Background(), tname)
if err != nil {
if errors.Is(err, pilosa.ErrIndexNotFound) {
if isTableNotFoundError(err) {
return nil, sql3.NewErrInternalf("table not found '%s'", i.tableName)
}
return nil, err

View file

@ -18,6 +18,8 @@ import (
//TODO(pok) you can't group by _id in PQL, so we need to not use a PQL group by operator here
//TODO(pok) move constant folding to in here
//TODO(pok) push down filters thru subqueries with aliases
// a function prototype for all optimizer rules
type OptimizerFunc func(context.Context, *ExecutionPlanner, types.PlanOperator, *OptimizerScope) (types.PlanOperator, bool, error)

View file

@ -0,0 +1,247 @@
// 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
}

View file

@ -193,7 +193,10 @@ func TestPlanner_Show(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if len(results) != 7 {
// we keep adding system tables on the fly so as
// long as we get more than 0 tables, we're good
if len(results) == 0 {
t.Fatal(fmt.Errorf("unexpected result set length"))
}

View file

@ -20,6 +20,7 @@ var TableTests []TableTest = []TableTest{
distinctTests,
subqueryTests,
viewTests,
topTests,

View file

@ -0,0 +1,126 @@
package defs
var viewTests = TableTest{
name: "viewtests",
Table: tbl(
"viewtable",
srcHdrs(
srcHdr("_id", fldTypeID),
srcHdr("a_string", fldTypeString),
srcHdr("a_int", fldTypeInt),
),
srcRows(
srcRow(int64(1), "str1", int64(10)),
srcRow(int64(2), "str1", int64(20)),
srcRow(int64(3), "str2", int64(30)),
srcRow(int64(4), "str2", int64(40)),
srcRow(int64(5), "str3", int64(50)),
),
),
SQLTests: []SQLTest{
{
name: "create-view",
SQLs: sqls(
"create view viewonviewtable as select _id, a_string, a_int from viewtable;",
),
ExpHdrs: hdrs(),
ExpRows: rows(),
Compare: CompareExactUnordered,
SortStringKeys: true,
},
{
name: "create-view-should-fail",
SQLs: sqls(
"create view viewonviewtable as select _id, a_string, a_int from viewtable;",
),
ExpErr: "view 'viewonviewtable' already exists",
},
{
name: "create-view-should-not-fail",
SQLs: sqls(
"create view if not exists viewonviewtable as select _id, a_string, a_int from viewtable;",
),
ExpHdrs: hdrs(),
ExpRows: rows(),
Compare: CompareExactUnordered,
SortStringKeys: true,
},
{
name: "select-view",
SQLs: sqls(
"select * from viewonviewtable;",
"select _id, a_string, a_int from viewonviewtable;",
),
ExpHdrs: hdrs(
hdr("_id", fldTypeID),
hdr("a_string", fldTypeString),
hdr("a_int", fldTypeInt),
),
ExpRows: rows(
row(int64(1), "str1", int64(10)),
row(int64(2), "str1", int64(20)),
row(int64(3), "str2", int64(30)),
row(int64(4), "str2", int64(40)),
row(int64(5), "str3", int64(50)),
),
Compare: CompareExactUnordered,
SortStringKeys: true,
},
{
name: "alter-view",
SQLs: sqls(
"alter view viewonviewtable as select _id, a_string, a_int from viewtable where a_int > 20;",
),
ExpHdrs: hdrs(),
ExpRows: rows(),
Compare: CompareExactUnordered,
SortStringKeys: true,
},
{
name: "select-alter-view",
SQLs: sqls(
"select * from viewonviewtable;",
"select _id, a_string, a_int from viewonviewtable;",
),
ExpHdrs: hdrs(
hdr("_id", fldTypeID),
hdr("a_string", fldTypeString),
hdr("a_int", fldTypeInt),
),
ExpRows: rows(
row(int64(3), "str2", int64(30)),
row(int64(4), "str2", int64(40)),
row(int64(5), "str3", int64(50)),
),
Compare: CompareExactUnordered,
SortStringKeys: true,
},
{
name: "drop-view",
SQLs: sqls(
"drop view viewonviewtable;",
),
ExpHdrs: hdrs(),
ExpRows: rows(),
Compare: CompareExactUnordered,
SortStringKeys: true,
},
{
name: "drop-view-if-exists-after-drop",
SQLs: sqls(
"drop view if exists viewonviewtable;",
),
ExpHdrs: hdrs(),
ExpRows: rows(),
Compare: CompareExactUnordered,
SortStringKeys: true,
},
{
name: "select-view-after-drop",
SQLs: sqls(
"select * from viewonviewtable;",
),
ExpErr: "table or view 'viewonviewtable' not found",
},
},
}