diff --git a/errors.go b/errors.go index 0f486c5e..5bfd0f82 100644 --- a/errors.go +++ b/errors.go @@ -23,6 +23,8 @@ var ( ErrModelValueRequired = errors.New("model value required") // ErrModelAccessibleFieldsRequired model accessible fields required ErrModelAccessibleFieldsRequired = errors.New("model accessible fields required") + // ErrSubQueryRequired sub query required + ErrSubQueryRequired = errors.New("sub query required") // ErrInvalidData unsupported data ErrInvalidData = errors.New("unsupported data") // ErrUnsupportedDriver unsupported driver diff --git a/migrator.go b/migrator.go index 882fc4cc..9c7cc2c4 100644 --- a/migrator.go +++ b/migrator.go @@ -30,9 +30,9 @@ func (db *DB) AutoMigrate(dst ...interface{}) error { // ViewOption view option type ViewOption struct { - Replace bool - CheckOption string - Query *DB + Replace bool // If true, exec `CREATE`. If false, exec `CREATE OR REPLACE` + CheckOption string // optional. e.g. `WITH [ CASCADED | LOCAL ] CHECK OPTION` + Query *DB // required subquery. } // ColumnType column type interface diff --git a/migrator/migrator.go b/migrator/migrator.go index 12c2df46..389ce008 100644 --- a/migrator/migrator.go +++ b/migrator/migrator.go @@ -557,14 +557,44 @@ func (m Migrator) ColumnTypes(value interface{}) ([]gorm.ColumnType, error) { return columnTypes, execErr } -// CreateView create view +// CreateView create view from Query in gorm.ViewOption. +// Query in gorm.ViewOption is a [subquery] +// +// // CREATE VIEW `user_view` AS SELECT * FROM `users` WHERE age > 20 +// q := DB.Model(&User{}).Where("age > ?", 20) +// DB.Debug().Migrator().CreateView("user_view", gorm.ViewOption{Query: q}) +// +// // CREATE OR REPLACE VIEW `users_view` AS SELECT * FROM `users` WITH CHECK OPTION +// q := DB.Model(&User{}) +// DB.Debug().Migrator().CreateView("user_view", gorm.ViewOption{Query: q, Replace: true, CheckOption: "WITH CHECK OPTION"}) +// +// [subquery]: https://gorm.io/docs/advanced_query.html#SubQuery func (m Migrator) CreateView(name string, option gorm.ViewOption) error { - return gorm.ErrNotImplemented + if option.Query == nil { + return gorm.ErrSubQueryRequired + } + + sql := new(strings.Builder) + sql.WriteString("CREATE ") + if option.Replace { + sql.WriteString("OR REPLACE ") + } + sql.WriteString("VIEW ") + m.QuoteTo(sql, name) + sql.WriteString(" AS ") + + m.DB.Statement.AddVar(sql, option.Query) + + if option.CheckOption != "" { + sql.WriteString(" ") + sql.WriteString(option.CheckOption) + } + return m.DB.Exec(m.Explain(sql.String(), m.DB.Statement.Vars...)).Error } // DropView drop view func (m Migrator) DropView(name string) error { - return gorm.ErrNotImplemented + return m.DB.Exec("DROP VIEW IF EXISTS ?", clause.Table{Name: name}).Error } func buildConstraint(constraint *schema.Constraint) (sql string, results []interface{}) { diff --git a/tests/migrate_test.go b/tests/migrate_test.go index 5a220ca4..11a0afda 100644 --- a/tests/migrate_test.go +++ b/tests/migrate_test.go @@ -1509,3 +1509,36 @@ func TestMigrateIgnoreRelations(t *testing.T) { t.Errorf("RelationModel2 should not be migrated") } } + +func TestMigrateView(t *testing.T) { + DB.Save(GetUser("joins-args-db", Config{Pets: 2})) + + if err := DB.Migrator().CreateView("invalid_users_pets", gorm.ViewOption{Query: nil}); err != gorm.ErrSubQueryRequired { + t.Fatalf("no view should be created, got %v", err) + } + + query := DB.Model(&User{}). + Select("users.id as users_id, users.name as users_name, pets.id as pets_id, pets.name as pets_name"). + Joins("inner join pets on pets.user_id = users.id") + + if err := DB.Migrator().CreateView("users_pets", gorm.ViewOption{Query: query}); err != nil { + t.Fatalf("Failed to crate view, got %v", err) + } + + var count int64 + if err := DB.Table("users_pets").Count(&count).Error; err != nil { + t.Fatalf("should found created view") + } + + if err := DB.Migrator().DropView("users_pets"); err != nil { + t.Fatalf("Failed to drop view, got %v", err) + } + + query = DB.Model(&User{}).Where("age > ?", 20) + if err := DB.Migrator().CreateView("users_view", gorm.ViewOption{Query: query}); err != nil { + t.Fatalf("Failed to crate view, got %v", err) + } + if err := DB.Migrator().DropView("users_view"); err != nil { + t.Fatalf("Failed to drop view, got %v", err) + } +}