mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-08-28 10:54:59 +00:00
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:
parent
fc1b8fdfb8
commit
5d025e399b
29 changed files with 1125 additions and 122 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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'`)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
64
sql3/planner/compilecreateview.go
Normal file
64
sql3/planner/compilecreateview.go
Normal 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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
23
sql3/planner/compiledropview.go
Normal file
23
sql3/planner/compiledropview.go
Normal 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
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
97
sql3/planner/opalterview.go
Normal file
97
sql3/planner/opalterview.go
Normal 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
|
||||
}
|
||||
120
sql3/planner/opcreateview.go
Normal file
120
sql3/planner/opcreateview.go
Normal 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
102
sql3/planner/opdropview.go
Normal 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
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
247
sql3/planner/systemobjects.go
Normal file
247
sql3/planner/systemobjects.go
Normal 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
|
||||
}
|
||||
|
|
@ -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"))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ var TableTests []TableTest = []TableTest{
|
|||
distinctTests,
|
||||
|
||||
subqueryTests,
|
||||
viewTests,
|
||||
|
||||
topTests,
|
||||
|
||||
|
|
|
|||
126
sql3/test/defs/defs_views.go
Normal file
126
sql3/test/defs/defs_views.go
Normal 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",
|
||||
},
|
||||
},
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue