diff --git a/cmd/apply.go b/cmd/apply.go index 5fca21c..8f6472b 100644 --- a/cmd/apply.go +++ b/cmd/apply.go @@ -46,11 +46,16 @@ var ( if err != nil { return err } + ignoreCreateSchema, err := cmd.Flags().GetBool("ignore-create-schema") + if err != nil { + return err + } ddlOption := &hammer.DDLOption{ IgnoreAlterDatabase: ignoreAlterDatabase, IgnoreChangeStreams: ignoreChangeStreams, IgnoreModels: ignoreModels, IgnoreProtoBundles: ignoreProtoBundles, + IgnoreCreateSchema: ignoreCreateSchema, } if hammer.Scheme(databaseURI) != "spanner" { @@ -95,6 +100,7 @@ func init() { applyCmd.Flags().Bool("ignore-change-streams", false, "ignore change streams statements") applyCmd.Flags().Bool("ignore-models", false, "ignore model statements") applyCmd.Flags().Bool("ignore-proto-bundles", false, "ignore proto bundle statements") + applyCmd.Flags().Bool("ignore-create-schema", false, "ignore create schema statements") rootCmd.AddCommand(applyCmd) } diff --git a/cmd/create.go b/cmd/create.go index f64b938..ccfa2b5 100644 --- a/cmd/create.go +++ b/cmd/create.go @@ -48,11 +48,16 @@ var ( if err != nil { return err } + ignoreCreateSchema, err := cmd.Flags().GetBool("ignore-create-schema") + if err != nil { + return err + } ddlOption := &hammer.DDLOption{ IgnoreAlterDatabase: ignoreAlterDatabase, IgnoreChangeStreams: ignoreChangeStreams, IgnoreModels: ignoreModels, IgnoreProtoBundles: ignoreProtoBundles, + IgnoreCreateSchema: ignoreCreateSchema, } if hammer.Scheme(databaseURI) != "spanner" { @@ -81,6 +86,7 @@ func init() { createCmd.Flags().Bool("ignore-change-streams", false, "ignore change streams statements") createCmd.Flags().Bool("ignore-models", false, "ignore model statements") createCmd.Flags().Bool("ignore-proto-bundles", false, "ignore proto bundle statements") + createCmd.Flags().Bool("ignore-create-schema", false, "ignore create schema statements") rootCmd.AddCommand(createCmd) } diff --git a/cmd/diff.go b/cmd/diff.go index 87b4ba3..c0ca25d 100644 --- a/cmd/diff.go +++ b/cmd/diff.go @@ -52,11 +52,16 @@ var ( if err != nil { return err } + ignoreCreateSchema, err := cmd.Flags().GetBool("ignore-create-schema") + if err != nil { + return err + } ddlOption := &hammer.DDLOption{ IgnoreAlterDatabase: ignoreAlterDatabase, IgnoreChangeStreams: ignoreChangeStreams, IgnoreModels: ignoreModels, IgnoreProtoBundles: ignoreProtoBundles, + IgnoreCreateSchema: ignoreCreateSchema, } source1, err := hammer.NewSource(ctx, sourceURI1) @@ -94,6 +99,7 @@ func init() { diffCmd.Flags().Bool("ignore-change-streams", false, "ignore change streams statements") diffCmd.Flags().Bool("ignore-models", false, "ignore model statements") diffCmd.Flags().Bool("ignore-proto-bundles", false, "ignore proto bundle statements") + diffCmd.Flags().Bool("ignore-create-schema", false, "ignore create schema statements") rootCmd.AddCommand(diffCmd) } diff --git a/cmd/export.go b/cmd/export.go index 34e27eb..4c29f78 100644 --- a/cmd/export.go +++ b/cmd/export.go @@ -45,11 +45,16 @@ var ( if err != nil { return err } + ignoreCreateSchema, err := cmd.Flags().GetBool("ignore-create-schema") + if err != nil { + return err + } ddlOption := &hammer.DDLOption{ IgnoreAlterDatabase: ignoreAlterDatabase, IgnoreChangeStreams: ignoreChangeStreams, IgnoreModels: ignoreModels, IgnoreProtoBundles: ignoreProtoBundles, + IgnoreCreateSchema: ignoreCreateSchema, } source, err := hammer.NewSource(ctx, sourceURI) @@ -74,6 +79,7 @@ func init() { exportCmd.Flags().Bool("ignore-change-streams", false, "ignore change streams statements") exportCmd.Flags().Bool("ignore-models", false, "ignore model statements") exportCmd.Flags().Bool("ignore-proto-bundles", false, "ignore proto bundle statements") + exportCmd.Flags().Bool("ignore-create-schema", false, "ignore create schema statements") rootCmd.AddCommand(exportCmd) } diff --git a/internal/hammer/ddl.go b/internal/hammer/ddl.go index d62c0c0..d1923a2 100644 --- a/internal/hammer/ddl.go +++ b/internal/hammer/ddl.go @@ -52,6 +52,9 @@ func ParseDDL(uri, schema string, option *DDLOption) (DDL, error) { if _, ok := stmt.(*ast.CreateProtoBundle); ok && option.IgnoreProtoBundles { continue } + if _, ok := stmt.(*ast.CreateSchema); ok && option.IgnoreCreateSchema { + continue + } list = append(list, stmt) } return DDL{List: list}, nil diff --git a/internal/hammer/ddl_test.go b/internal/hammer/ddl_test.go index a837464..b9125fa 100644 --- a/internal/hammer/ddl_test.go +++ b/internal/hammer/ddl_test.go @@ -138,6 +138,23 @@ OPTIONS ( want: `CREATE TABLE Users ( UserID STRING(10) NOT NULL, Name STRING(10) NOT NULL +) PRIMARY KEY (UserID);`, + }, + { + name: "Ignore create schema", + schema: `CREATE SCHEMA sch1; + +CREATE TABLE Users ( + UserID STRING(10) NOT NULL, -- comment + Name STRING(10) NOT NULL, -- comment +) PRIMARY KEY(UserID); +`, + option: &hammer.DDLOption{ + IgnoreCreateSchema: true, + }, + want: `CREATE TABLE Users ( + UserID STRING(10) NOT NULL, + Name STRING(10) NOT NULL ) PRIMARY KEY (UserID);`, }, } diff --git a/internal/hammer/diff.go b/internal/hammer/diff.go index 7d051ae..7aad24d 100644 --- a/internal/hammer/diff.go +++ b/internal/hammer/diff.go @@ -31,6 +31,7 @@ func Diff(ddl1, ddl2 DDL) (DDL, error) { func NewDatabase(ddl DDL) (*Database, error) { var ( + schemas []*Schema tables []*Table changeStreams []*ChangeStream views []*View @@ -43,6 +44,8 @@ func NewDatabase(ddl DDL) (*Database, error) { m := make(map[string]*Table) for _, istmt := range ddl.List { switch stmt := istmt.(type) { + case *ast.CreateSchema: + schemas = append(schemas, &Schema{CreateSchema: stmt}) case *ast.CreateTable: t := &Table{CreateTable: stmt} tables = append(tables, t) @@ -105,10 +108,11 @@ func NewDatabase(ddl DDL) (*Database, error) { } } - return &Database{tables: tables, changeStreams: changeStreams, views: views, roles: roles, grants: grants, alterDatabaseOptions: alterDatabaseOptions, options: options}, nil + return &Database{schemas: schemas, tables: tables, changeStreams: changeStreams, views: views, roles: roles, grants: grants, alterDatabaseOptions: alterDatabaseOptions, options: options}, nil } type Database struct { + schemas []*Schema tables []*Table changeStreams []*ChangeStream views []*View @@ -211,6 +215,10 @@ type Table struct { changeStreams []*ChangeStream } +type Schema struct { + *ast.CreateSchema +} + type View struct { *ast.CreateView } @@ -266,6 +274,13 @@ func (g *Generator) GenerateDDL() DDL { // for alter database ddl.AppendDDL(g.generateDDLForAlterDatabaseOptions()) + // create schemas before any object that may be created inside them + for _, toSchema := range g.to.schemas { + if _, exists := g.findSchemaByName(g.from.schemas, identsToComparable(toSchema.Name)); !exists { + ddl.Append(toSchema) + } + } + // for alter table var constraintTargets []*Table for _, toTable := range g.to.tables { @@ -404,6 +419,13 @@ func (g *Generator) GenerateDDL() DDL { } } + // drop schemas after every object inside them has been dropped + for _, fromSchema := range g.from.schemas { + if _, exists := g.findSchemaByName(g.to.schemas, identsToComparable(fromSchema.Name)); !exists { + ddl.Append(&ast.DropSchema{Name: fromSchema.Name}) + } + } + return ddl } @@ -1514,6 +1536,17 @@ func (g *Generator) generateDDLForDropChangeStream(changeStream *ChangeStream) D return ddl } +func (g *Generator) findSchemaByName(schemas []*Schema, name string) (schema *Schema, exists bool) { + for _, s := range schemas { + if identsToComparable(s.Name) == name { + schema = s + exists = true + break + } + } + return +} + func (g *Generator) findViewByName(views []*View, name string) (view *View, exists bool) { for _, v := range views { if identsToComparable(v.Name.Idents...) == name { diff --git a/internal/hammer/diff_test.go b/internal/hammer/diff_test.go index 93ea620..ee37891 100644 --- a/internal/hammer/diff_test.go +++ b/internal/hammer/diff_test.go @@ -2557,6 +2557,113 @@ AS SELECT * FROM t1; `DROP ROLE role2`, }, }, + // === SCHEMA === + { + name: "create schema", + from: ``, + to: ` + CREATE SCHEMA sch1; + `, + expected: []string{ + `CREATE SCHEMA sch1`, + }, + }, + { + name: "drop schema", + from: ` + CREATE SCHEMA sch1; + CREATE SCHEMA sch2; + `, + to: ` + CREATE SCHEMA sch1; + `, + expected: []string{ + `DROP SCHEMA sch2`, + }, + }, + { + name: "schema unchanged is a no-op", + from: ` + CREATE SCHEMA sch1; + `, + to: ` + CREATE SCHEMA sch1; + `, + expected: nil, + }, + { + name: "create schema before table in that schema", + from: ``, + to: ` + CREATE SCHEMA sch1; + CREATE TABLE sch1.t1 ( + t1_1 INT64 NOT NULL, + ) PRIMARY KEY(t1_1); + `, + expected: []string{ + `CREATE SCHEMA sch1`, + ` +CREATE TABLE sch1.t1 ( + t1_1 INT64 NOT NULL +) PRIMARY KEY (t1_1)`, + }, + }, + { + name: "drop schema after table in that schema", + from: ` + CREATE SCHEMA sch1; + CREATE TABLE sch1.t1 ( + t1_1 INT64 NOT NULL, + ) PRIMARY KEY(t1_1); + `, + to: ``, + expected: []string{ + `DROP TABLE sch1.t1`, + `DROP SCHEMA sch1`, + }, + }, + { + name: "create schema before schema-qualified table, index and view", + from: ``, + to: ` + CREATE SCHEMA sch1; + CREATE TABLE sch1.t1 ( + id INT64 NOT NULL, + name STRING(MAX), + ) PRIMARY KEY(id); + CREATE INDEX idx1 ON sch1.t1(name); + CREATE VIEW sch1.v1 SQL SECURITY INVOKER AS SELECT id FROM sch1.t1; + `, + expected: []string{ + `CREATE SCHEMA sch1`, + ` +CREATE TABLE sch1.t1 ( + id INT64 NOT NULL, + name STRING(MAX) +) PRIMARY KEY (id)`, + `CREATE INDEX idx1 ON sch1.t1(name)`, + `CREATE VIEW sch1.v1 SQL SECURITY INVOKER AS SELECT id FROM sch1.t1`, + }, + }, + { + name: "drop schema after its qualified table, index and view", + from: ` + CREATE SCHEMA sch1; + CREATE TABLE sch1.t1 ( + id INT64 NOT NULL, + name STRING(MAX), + ) PRIMARY KEY(id); + CREATE INDEX idx1 ON sch1.t1(name); + CREATE VIEW sch1.v1 SQL SECURITY INVOKER AS SELECT id FROM sch1.t1; + `, + to: ``, + expected: []string{ + `DROP INDEX idx1`, + `DROP TABLE sch1.t1`, + `DROP VIEW sch1.v1`, + `DROP SCHEMA sch1`, + }, + }, // === GRANT / REVOKE === { name: "grant role", diff --git a/internal/hammer/source.go b/internal/hammer/source.go index ed8d118..e5c0f1e 100644 --- a/internal/hammer/source.go +++ b/internal/hammer/source.go @@ -18,6 +18,7 @@ type DDLOption struct { IgnoreChangeStreams bool IgnoreModels bool IgnoreProtoBundles bool + IgnoreCreateSchema bool } func NewSource(ctx context.Context, uri string) (Source, error) {