Skip to content

Commit 7090f07

Browse files
author
James Cor
committed
support versioned views
1 parent e48937b commit 7090f07

File tree

4 files changed

+83
-40
lines changed

4 files changed

+83
-40
lines changed

memory/database.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -589,6 +589,12 @@ func (d *Database) AllViews(ctx *sql.Context) ([]sql.ViewDefinition, error) {
589589
return views, nil
590590
}
591591

592+
// GetViewDefinitionAsOf implements the interface sql.ViewDatabase.
593+
func (d *Database) GetViewDefinitionAsOf(ctx *sql.Context, viewName string, asOf interface{}) (sql.ViewDefinition, bool, error) {
594+
viewDef, ok := d.views[strings.ToLower(viewName)]
595+
return viewDef, ok, nil
596+
}
597+
592598
// GetViewDefinition implements the interface sql.ViewDatabase.
593599
func (d *Database) GetViewDefinition(ctx *sql.Context, viewName string) (sql.ViewDefinition, bool, error) {
594600
viewDef, ok := d.views[strings.ToLower(viewName)]

sql/databases.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,14 @@ type EventDatabase interface {
288288
NeedsToReloadEvents(ctx *Context, token interface{}) (bool, error)
289289
}
290290

291+
// VersionedViewDatabase is a Database that can return views as they existed at different points in time.
292+
type VersionedViewDatabase interface {
293+
ViewDatabase
294+
295+
// GetViewDefinitionAsOf retrieves the ViewDefinition with the name given at the asOf given.
296+
GetViewDefinitionAsOf(ctx *Context, viewName string, asOf interface{}) (ViewDefinition, bool, error)
297+
}
298+
291299
// ViewDatabase is implemented by databases that persist view definitions
292300
type ViewDatabase interface {
293301
// CreateView persists the definition a view with the name and select statement given. If a view with that name

sql/mysql_db/privileged_database_provider.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ var _ sql.ReadOnlyDatabase = PrivilegedDatabase{}
150150
var _ sql.TemporaryTableDatabase = PrivilegedDatabase{}
151151
var _ sql.CollatedDatabase = PrivilegedDatabase{}
152152
var _ sql.ViewDatabase = PrivilegedDatabase{}
153+
var _ sql.VersionedViewDatabase = PrivilegedDatabase{}
153154
var _ fulltext.Database = PrivilegedDatabase{}
154155

155156
// NewPrivilegedDatabase returns a new PrivilegedDatabase.
@@ -395,6 +396,14 @@ func (pdb PrivilegedDatabase) DropView(ctx *sql.Context, name string) error {
395396
return sql.ErrViewsNotSupported.New(pdb.db.Name())
396397
}
397398

399+
// GetViewDefinitionAsOf implements sql.ViewDatabase
400+
func (pdb PrivilegedDatabase) GetViewDefinitionAsOf(ctx *sql.Context, viewName string, asOf interface{}) (sql.ViewDefinition, bool, error) {
401+
if db, ok := pdb.db.(sql.VersionedViewDatabase); ok {
402+
return db.GetViewDefinitionAsOf(ctx, viewName, asOf)
403+
}
404+
return sql.ViewDefinition{}, false, sql.ErrAsOfNotSupported.New(pdb.db.Name())
405+
}
406+
398407
// GetViewDefinition implements sql.ViewDatabase
399408
func (pdb PrivilegedDatabase) GetViewDefinition(ctx *sql.Context, viewName string) (sql.ViewDefinition, bool, error) {
400409
if db, ok := pdb.db.(sql.ViewDatabase); ok {

sql/planbuilder/from.go

Lines changed: 60 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -682,6 +682,8 @@ func (b *Builder) buildResolvedTable(inScope *scope, db, schema, name string, as
682682
asOfLit = asof
683683
}
684684

685+
686+
// TODO: even if it's a view here; it might not have been before?
685687
if view := b.resolveView(name, database, asOfLit); view != nil {
686688
// TODO: Schema name
687689
return resolvedViewScope(outScope, view, db, name)
@@ -812,56 +814,74 @@ func resolvedViewScope(outScope *scope, view sql.Node, db string, name string) (
812814
return outScope, true
813815
}
814816

817+
func (b *Builder) resolveViewDef(name string, database sql.Database, viewDef sql.ViewDefinition, asOf interface{}) *sql.View {
818+
oldOpts := b.parserOpts
819+
defer func() {
820+
b.parserOpts = oldOpts
821+
}()
822+
outerAsOf := b.ViewCtx().AsOf
823+
outerDb := b.ViewCtx().DbName
824+
b.ViewCtx().AsOf = asOf
825+
b.ViewCtx().DbName = database.Name()
826+
defer func() {
827+
b.ViewCtx().AsOf = outerAsOf
828+
b.ViewCtx().DbName = outerDb
829+
}()
830+
b.parserOpts = sql.NewSqlModeFromString(viewDef.SqlMode).ParserOptions()
831+
stmt, _, _, err := b.parser.ParseWithOptions(b.ctx, viewDef.CreateViewStatement, ';', false, b.parserOpts)
832+
if err != nil {
833+
b.handleErr(err)
834+
}
835+
node, _, err := b.bindOnlyWithDatabase(database, stmt, viewDef.CreateViewStatement)
836+
if err != nil {
837+
// TODO: Need to account for non-existing functions or
838+
// users without appropriate privilege to the referenced table/column/function.
839+
if sql.ErrTableNotFound.Is(err) || sql.ErrColumnNotFound.Is(err) {
840+
// TODO: ALTER VIEW should not return this error
841+
err = sql.ErrInvalidRefInView.New(database.Name(), name)
842+
}
843+
b.handleErr(err)
844+
}
845+
create, ok := node.(*plan.CreateView)
846+
if !ok {
847+
err = fmt.Errorf("expected create view statement, found: %T", node)
848+
b.handleErr(err)
849+
}
850+
switch n := create.Child.(type) {
851+
case *plan.SubqueryAlias:
852+
return n.AsView(viewDef.CreateViewStatement)
853+
default:
854+
view := plan.NewSubqueryAlias(name, create.Definition.TextDefinition, n).AsView(viewDef.CreateViewStatement)
855+
b.qFlags.Set(sql.QFlagRelSubquery)
856+
return view
857+
}
858+
}
859+
815860
func (b *Builder) resolveView(name string, database sql.Database, asOf interface{}) sql.Node {
816861
var view *sql.View
817862

818-
if vdb, vok := database.(sql.ViewDatabase); vok {
863+
if asOf != nil {
864+
vdb, vok := database.(sql.VersionedViewDatabase)
865+
if !vok {
866+
b.handleErr(sql.ErrAsOfNotSupported.New(database.Name()))
867+
}
868+
viewDef, vdok, err := vdb.GetViewDefinitionAsOf(b.ctx, name, asOf)
869+
if err != nil {
870+
b.handleErr(err)
871+
}
872+
if vdok {
873+
view = b.resolveViewDef(name, database, viewDef, asOf)
874+
}
875+
} else if vdb, vok := database.(sql.ViewDatabase); vok {
819876
viewDef, vdok, err := vdb.GetViewDefinition(b.ctx, name)
820877
if err != nil {
821878
b.handleErr(err)
822879
}
823-
oldOpts := b.parserOpts
824-
defer func() {
825-
b.parserOpts = oldOpts
826-
}()
827880
if vdok {
828-
outerAsOf := b.ViewCtx().AsOf
829-
outerDb := b.ViewCtx().DbName
830-
b.ViewCtx().AsOf = asOf
831-
b.ViewCtx().DbName = database.Name()
832-
defer func() {
833-
b.ViewCtx().AsOf = outerAsOf
834-
b.ViewCtx().DbName = outerDb
835-
}()
836-
b.parserOpts = sql.NewSqlModeFromString(viewDef.SqlMode).ParserOptions()
837-
stmt, _, _, err := b.parser.ParseWithOptions(b.ctx, viewDef.CreateViewStatement, ';', false, b.parserOpts)
838-
if err != nil {
839-
b.handleErr(err)
840-
}
841-
node, _, err := b.bindOnlyWithDatabase(database, stmt, viewDef.CreateViewStatement)
842-
if err != nil {
843-
// TODO: Need to account for non-existing functions or
844-
// users without appropriate privilege to the referenced table/column/function.
845-
if sql.ErrTableNotFound.Is(err) || sql.ErrColumnNotFound.Is(err) {
846-
// TODO: ALTER VIEW should not return this error
847-
err = sql.ErrInvalidRefInView.New(database.Name(), name)
848-
}
849-
b.handleErr(err)
850-
}
851-
create, ok := node.(*plan.CreateView)
852-
if !ok {
853-
err = fmt.Errorf("expected create view statement, found: %T", node)
854-
b.handleErr(err)
855-
}
856-
switch n := create.Child.(type) {
857-
case *plan.SubqueryAlias:
858-
view = n.AsView(viewDef.CreateViewStatement)
859-
default:
860-
view = plan.NewSubqueryAlias(name, create.Definition.TextDefinition, n).AsView(viewDef.CreateViewStatement)
861-
b.qFlags.Set(sql.QFlagRelSubquery)
862-
}
881+
view = b.resolveViewDef(name, database, viewDef, asOf)
863882
}
864883
}
884+
865885
// If we didn't find the view from the database directly, use the in-session registry
866886
if view == nil {
867887
view, _ = b.ctx.GetViewRegistry().View(database.Name(), name)

0 commit comments

Comments
 (0)