From 396e6537b4ae2721e5febed669fcd1771ef15a6b Mon Sep 17 00:00:00 2001 From: Nuno Cruces Date: Sat, 10 Feb 2024 10:03:12 +0000 Subject: [PATCH] GORM v1.25.7. (#59) --- .github/workflows/gorm.yml | 19 +++++++ README.md | 5 +- go.work.sum | 1 + gormlite/ddlmod.go | 43 +++++++-------- gormlite/ddlmod_test.go | 55 +++++++++++++++----- gormlite/download.sh | 12 ++--- gormlite/go.mod | 4 +- gormlite/go.sum | 8 +-- gormlite/migrator.go | 104 +++++++++++++++++++++++-------------- gormlite/test.sh | 5 +- 10 files changed, 160 insertions(+), 96 deletions(-) create mode 100644 .github/workflows/gorm.yml diff --git a/.github/workflows/gorm.yml b/.github/workflows/gorm.yml new file mode 100644 index 0000000..e024438 --- /dev/null +++ b/.github/workflows/gorm.yml @@ -0,0 +1,19 @@ +name: Cross compile + +on: + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up + uses: actions/setup-go@v5 + with: + go-version: stable + + - name: Test + run: gormlite/test.sh diff --git a/README.md b/README.md index 96bdfc5..1682770 100644 --- a/README.md +++ b/README.md @@ -109,14 +109,13 @@ On all other platforms, file locking is not supported, and you must use [`nolock=1`](https://sqlite.org/uri.html#urinolock) (or [`immutable=1`](https://sqlite.org/uri.html#uriimmutable)) to open database files. +You can use [`vfs.SupportsFileLocking`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs#SupportsFileLocking) +to check if your platform supports file locking. To use the [`database/sql`](https://pkg.go.dev/database/sql) driver with `nolock=1` you must disable connection pooling by calling [`db.SetMaxOpenConns(1)`](https://pkg.go.dev/database/sql#DB.SetMaxOpenConns). -You can use [`vfs.SupportsFileLocking`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs#SupportsFileLocking) -to check if your platform supports file locking. - ### Testing This project aims for [high test coverage](https://github.com/ncruces/go-sqlite3/wiki/Test-coverage-report). diff --git a/go.work.sum b/go.work.sum index d3b1c91..320aeb7 100644 --- a/go.work.sum +++ b/go.work.sum @@ -1,4 +1,5 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= diff --git a/gormlite/ddlmod.go b/gormlite/ddlmod.go index 0382757..0030ab9 100644 --- a/gormlite/ddlmod.go +++ b/gormlite/ddlmod.go @@ -13,6 +13,7 @@ import ( var ( sqliteSeparator = "`|\"|'|\t" + uniqueRegexp = regexp.MustCompile(fmt.Sprintf(`^CONSTRAINT [%v]?[\w-]+[%v]? UNIQUE (.*)$`, sqliteSeparator, sqliteSeparator)) indexRegexp = regexp.MustCompile(fmt.Sprintf(`(?is)CREATE(?: UNIQUE)? INDEX [%v]?[\w\d-]+[%v]?(?s:.*?)ON (.*)$`, sqliteSeparator, sqliteSeparator)) tableRegexp = regexp.MustCompile(fmt.Sprintf(`(?is)(CREATE TABLE [%v]?[\w\d-]+[%v]?)(?:\s*\((.*)\))?`, sqliteSeparator, sqliteSeparator)) separatorRegexp = regexp.MustCompile(fmt.Sprintf("[%v]", sqliteSeparator)) @@ -103,11 +104,24 @@ func parseDDL(strs ...string) (*ddl, error) { for _, f := range result.fields { fUpper := strings.ToUpper(f) - if strings.HasPrefix(fUpper, "CHECK") || - strings.HasPrefix(fUpper, "CONSTRAINT") { + if strings.HasPrefix(fUpper, "CHECK") { + continue + } + if strings.HasPrefix(fUpper, "CONSTRAINT") { + matches := uniqueRegexp.FindStringSubmatch(f) + if len(matches) > 0 { + if columns := getAllColumns(matches[1]); len(columns) == 1 { + for idx, column := range result.columns { + if column.NameValue.String == columns[0] { + column.UniqueValue = sql.NullBool{Bool: true, Valid: true} + result.columns[idx] = column + break + } + } + } + } continue } - if strings.HasPrefix(fUpper, "PRIMARY KEY") { for _, name := range getAllColumns(f) { for idx, column := range result.columns { @@ -159,14 +173,7 @@ func parseDDL(strs ...string) (*ddl, error) { } } } else if matches := indexRegexp.FindStringSubmatch(str); len(matches) > 0 { - for _, column := range getAllColumns(matches[1]) { - for idx, c := range result.columns { - if c.NameValue.String == column { - c.UniqueValue = sql.NullBool{Bool: strings.ToUpper(strings.Fields(str)[1]) == "UNIQUE", Valid: true} - result.columns[idx] = c - } - } - } + // don't report Unique by UniqueIndex } else { return nil, errors.New("invalid DDL") } @@ -269,20 +276,6 @@ func (d *ddl) getColumns() []string { return res } -func (d *ddl) alterColumn(name, sql string) bool { - reg := regexp.MustCompile("^(`|'|\"| )" + regexp.QuoteMeta(name) + "(`|'|\"| ) .*?$") - - for i := 0; i < len(d.fields); i++ { - if reg.MatchString(d.fields[i]) { - d.fields[i] = sql - return false - } - } - - d.fields = append(d.fields, sql) - return true -} - func (d *ddl) removeColumn(name string) bool { reg := regexp.MustCompile("^(`|'|\"| )" + regexp.QuoteMeta(name) + "(`|'|\"| ) .*?$") diff --git a/gormlite/ddlmod_test.go b/gormlite/ddlmod_test.go index c7124c0..94898e7 100644 --- a/gormlite/ddlmod_test.go +++ b/gormlite/ddlmod_test.go @@ -16,11 +16,12 @@ func TestParseDDL(t *testing.T) { columns []migrator.ColumnType }{ {"with_fk", []string{ - "CREATE TABLE `notes` (`id` integer NOT NULL,`text` varchar(500) DEFAULT \"hello\",`age` integer DEFAULT 18,`user_id` integer,PRIMARY KEY (`id`),CONSTRAINT `fk_users_notes` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`))", + "CREATE TABLE `notes` (" + + "`id` integer NOT NULL,`text` varchar(500) DEFAULT \"hello\",`age` integer DEFAULT 18,`user_id` integer,PRIMARY KEY (`id`),CONSTRAINT `fk_users_notes` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`))", "CREATE UNIQUE INDEX `idx_profiles_refer` ON `profiles`(`text`)", }, 6, []migrator.ColumnType{ {NameValue: sql.NullString{String: "id", Valid: true}, DataTypeValue: sql.NullString{String: "integer", Valid: true}, ColumnTypeValue: sql.NullString{String: "integer", Valid: true}, PrimaryKeyValue: sql.NullBool{Bool: true, Valid: true}, NullableValue: sql.NullBool{Valid: true}, UniqueValue: sql.NullBool{Valid: true}, DefaultValueValue: sql.NullString{Valid: false}}, - {NameValue: sql.NullString{String: "text", Valid: true}, DataTypeValue: sql.NullString{String: "varchar", Valid: true}, LengthValue: sql.NullInt64{Int64: 500, Valid: true}, ColumnTypeValue: sql.NullString{String: "varchar(500)", Valid: true}, DefaultValueValue: sql.NullString{String: "hello", Valid: true}, NullableValue: sql.NullBool{Bool: true, Valid: true}, UniqueValue: sql.NullBool{Bool: true, Valid: true}, PrimaryKeyValue: sql.NullBool{Valid: true}}, + {NameValue: sql.NullString{String: "text", Valid: true}, DataTypeValue: sql.NullString{String: "varchar", Valid: true}, LengthValue: sql.NullInt64{Int64: 500, Valid: true}, ColumnTypeValue: sql.NullString{String: "varchar(500)", Valid: true}, DefaultValueValue: sql.NullString{String: "hello", Valid: true}, NullableValue: sql.NullBool{Bool: true, Valid: true}, UniqueValue: sql.NullBool{Bool: false, Valid: true}, PrimaryKeyValue: sql.NullBool{Valid: true}}, {NameValue: sql.NullString{String: "age", Valid: true}, DataTypeValue: sql.NullString{String: "integer", Valid: true}, ColumnTypeValue: sql.NullString{String: "integer", Valid: true}, DefaultValueValue: sql.NullString{String: "18", Valid: true}, NullableValue: sql.NullBool{Bool: true, Valid: true}, UniqueValue: sql.NullBool{Valid: true}, PrimaryKeyValue: sql.NullBool{Valid: true}}, {NameValue: sql.NullString{String: "user_id", Valid: true}, DataTypeValue: sql.NullString{String: "integer", Valid: true}, ColumnTypeValue: sql.NullString{String: "integer", Valid: true}, DefaultValueValue: sql.NullString{Valid: false}, NullableValue: sql.NullBool{Bool: true, Valid: true}, UniqueValue: sql.NullBool{Valid: true}, PrimaryKeyValue: sql.NullBool{Valid: true}}, }, @@ -56,28 +57,54 @@ func TestParseDDL(t *testing.T) { ColumnTypeValue: sql.NullString{String: "int", Valid: true}, NullableValue: sql.NullBool{Bool: false, Valid: true}, DefaultValueValue: sql.NullString{Valid: false}, - UniqueValue: sql.NullBool{Bool: true, Valid: true}, + UniqueValue: sql.NullBool{Bool: false, Valid: true}, PrimaryKeyValue: sql.NullBool{Valid: true}, }, }, - }, - { + }, { "unique index", []string{ "CREATE TABLE `test-b` (`field` integer NOT NULL)", "CREATE UNIQUE INDEX `idx_uq` ON `test-b`(`field`) WHERE field = 0", }, 1, - []migrator.ColumnType{ - { - NameValue: sql.NullString{String: "field", Valid: true}, - DataTypeValue: sql.NullString{String: "integer", Valid: true}, - ColumnTypeValue: sql.NullString{String: "integer", Valid: true}, - PrimaryKeyValue: sql.NullBool{Bool: false, Valid: true}, - UniqueValue: sql.NullBool{Bool: true, Valid: true}, - NullableValue: sql.NullBool{Bool: false, Valid: true}, - }, + []migrator.ColumnType{{ + NameValue: sql.NullString{String: "field", Valid: true}, + DataTypeValue: sql.NullString{String: "integer", Valid: true}, + ColumnTypeValue: sql.NullString{String: "integer", Valid: true}, + PrimaryKeyValue: sql.NullBool{Bool: false, Valid: true}, + UniqueValue: sql.NullBool{Bool: false, Valid: true}, + NullableValue: sql.NullBool{Bool: false, Valid: true}, + }}, + }, { + "normal index", + []string{ + "CREATE TABLE `test-c` (`field` integer NOT NULL)", + "CREATE INDEX `idx_uq` ON `test-c`(`field`)", }, + 1, + []migrator.ColumnType{{ + NameValue: sql.NullString{String: "field", Valid: true}, + DataTypeValue: sql.NullString{String: "integer", Valid: true}, + ColumnTypeValue: sql.NullString{String: "integer", Valid: true}, + PrimaryKeyValue: sql.NullBool{Bool: false, Valid: true}, + UniqueValue: sql.NullBool{Bool: false, Valid: true}, + NullableValue: sql.NullBool{Bool: false, Valid: true}, + }}, + }, { + "unique constraint", + []string{ + "CREATE TABLE `unique_struct` (`name` text,CONSTRAINT `uni_unique_struct_name` UNIQUE (`name`))", + }, + 2, + []migrator.ColumnType{{ + NameValue: sql.NullString{String: "name", Valid: true}, + DataTypeValue: sql.NullString{String: "text", Valid: true}, + ColumnTypeValue: sql.NullString{String: "text", Valid: true}, + PrimaryKeyValue: sql.NullBool{Bool: false, Valid: true}, + UniqueValue: sql.NullBool{Bool: true, Valid: true}, + NullableValue: sql.NullBool{Bool: true, Valid: true}, + }}, }, { "non-unique index", diff --git a/gormlite/download.sh b/gormlite/download.sh index fcf9a70..5c7a862 100755 --- a/gormlite/download.sh +++ b/gormlite/download.sh @@ -3,9 +3,9 @@ set -euo pipefail cd -P -- "$(dirname -- "$0")" -curl -#OL "https://github.com/go-gorm/sqlite/raw/master/ddlmod.go" -curl -#OL "https://github.com/go-gorm/sqlite/raw/master/ddlmod_test.go" -curl -#OL "https://github.com/go-gorm/sqlite/raw/master/error_translator.go" -curl -#OL "https://github.com/go-gorm/sqlite/raw/master/migrator.go" -curl -#OL "https://github.com/go-gorm/sqlite/raw/master/sqlite.go" -curl -#OL "https://github.com/go-gorm/sqlite/raw/master/sqlite_test.go" \ No newline at end of file +curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.5.5/ddlmod.go" +curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.5.5/ddlmod_test.go" +curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.5.5/error_translator.go" +curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.5.5/migrator.go" +curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.5.5/sqlite.go" +curl -#OL "https://github.com/go-gorm/sqlite/raw/v1.5.5/sqlite_test.go" \ No newline at end of file diff --git a/gormlite/go.mod b/gormlite/go.mod index 5f018b8..f807298 100644 --- a/gormlite/go.mod +++ b/gormlite/go.mod @@ -4,7 +4,7 @@ go 1.21 require ( github.com/ncruces/go-sqlite3 v0.12.2 - gorm.io/gorm v1.25.6 + gorm.io/gorm v1.25.7 ) require ( @@ -12,5 +12,5 @@ require ( github.com/jinzhu/now v1.1.5 // indirect github.com/ncruces/julianday v1.0.0 // indirect github.com/tetratelabs/wazero v1.6.0 // indirect - golang.org/x/sys v0.16.0 // indirect + golang.org/x/sys v0.17.0 // indirect ) diff --git a/gormlite/go.sum b/gormlite/go.sum index 9b5c796..598def1 100644 --- a/gormlite/go.sum +++ b/gormlite/go.sum @@ -8,9 +8,9 @@ github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g= github.com/tetratelabs/wazero v1.6.0 h1:z0H1iikCdP8t+q341xqepY4EWvHEw8Es7tlqiVzlP3g= github.com/tetratelabs/wazero v1.6.0/go.mod h1:0U0G41+ochRKoPKCJlh0jMg1CHkyfK8kDqiirMmKY8A= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -gorm.io/gorm v1.25.6 h1:V92+vVda1wEISSOMtodHVRcUIOPYa2tgQtyF+DfFx+A= -gorm.io/gorm v1.25.6/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +gorm.io/gorm v1.25.7 h1:VsD6acwRjz2zFxGO50gPO6AkNs7KKnvfzUjHQhZDz/A= +gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= diff --git a/gormlite/migrator.go b/gormlite/migrator.go index 95801ab..5a3b343 100644 --- a/gormlite/migrator.go +++ b/gormlite/migrator.go @@ -79,14 +79,28 @@ func (m _Migrator) AlterColumn(value interface{}, name string) error { return m.RunWithoutForeignKey(func() error { return m.recreateTable(value, nil, func(ddl *ddl, stmt *gorm.Statement) (*ddl, []interface{}, error) { if field := stmt.Schema.LookUpField(name); field != nil { - if ddl.alterColumn(field.DBName, fmt.Sprintf("`%s` ?", field.DBName)) { - return nil, nil, fmt.Errorf("field `%s` not found in origin ddl, ddl= '%s'", name, ddl.compile()) + var sqlArgs []interface{} + for i, f := range ddl.fields { + if matches := columnRegexp.FindStringSubmatch(f); len(matches) > 1 && matches[1] == field.DBName { + ddl.fields[i] = fmt.Sprintf("`%v` ?", field.DBName) + sqlArgs = []interface{}{m.FullDataTypeOf(field)} + // table created by old version might look like `CREATE TABLE ? (? varchar(10) UNIQUE)`. + // FullDataTypeOf doesn't contain UNIQUE, so we need to add unique constraint. + if strings.Contains(strings.ToUpper(matches[3]), " UNIQUE") { + uniName := m.DB.NamingStrategy.UniqueName(stmt.Table, field.DBName) + uni, _ := m.GuessConstraintInterfaceAndTable(stmt, uniName) + if uni != nil { + uniSQL, uniArgs := uni.Build() + ddl.addConstraint(uniName, uniSQL) + sqlArgs = append(sqlArgs, uniArgs...) + } + } + break + } } - - return ddl, []interface{}{m.FullDataTypeOf(field)}, nil + return ddl, sqlArgs, nil } - - return nil, nil, fmt.Errorf("failed to alter field with name `%s`", name) + return nil, nil, fmt.Errorf("failed to alter field with name %v", name) }) }) } @@ -153,7 +167,7 @@ func (m _Migrator) DropColumn(value interface{}, name string) error { func (m _Migrator) CreateConstraint(value interface{}, name string) error { return m.RunWithValue(value, func(stmt *gorm.Statement) error { - constraint, chk, table := m.GuessConstraintAndTable(stmt, name) + constraint, table := m.GuessConstraintInterfaceAndTable(stmt, name) return m.recreateTable(value, &table, func(ddl *ddl, stmt *gorm.Statement) (*ddl, []interface{}, error) { @@ -164,12 +178,8 @@ func (m _Migrator) CreateConstraint(value interface{}, name string) error { ) if constraint != nil { - constraintName = constraint.Name - constraintSql, constraintValues = buildConstraint(constraint) - } else if chk != nil { - constraintName = chk.Name - constraintSql = "CONSTRAINT ? CHECK (?)" - constraintValues = []interface{}{clause.Column{Name: chk.Name}, clause.Expr{SQL: chk.Constraint}} + constraintName = constraint.GetName() + constraintSql, constraintValues = constraint.Build() } else { return nil, nil, nil } @@ -182,11 +192,9 @@ func (m _Migrator) CreateConstraint(value interface{}, name string) error { func (m _Migrator) DropConstraint(value interface{}, name string) error { return m.RunWithValue(value, func(stmt *gorm.Statement) error { - constraint, chk, table := m.GuessConstraintAndTable(stmt, name) + constraint, table := m.GuessConstraintInterfaceAndTable(stmt, name) if constraint != nil { - name = constraint.Name - } else if chk != nil { - name = chk.Name + name = constraint.GetName() } return m.recreateTable(value, &table, @@ -200,11 +208,9 @@ func (m _Migrator) DropConstraint(value interface{}, name string) error { func (m _Migrator) HasConstraint(value interface{}, name string) bool { var count int64 m.RunWithValue(value, func(stmt *gorm.Statement) error { - constraint, chk, table := m.GuessConstraintAndTable(stmt, name) + constraint, table := m.GuessConstraintInterfaceAndTable(stmt, name) if constraint != nil { - name = constraint.Name - } else if chk != nil { - name = chk.Name + name = constraint.GetName() } m.DB.Raw( @@ -317,26 +323,44 @@ func (m _Migrator) DropIndex(value interface{}, name string) error { }) } -func buildConstraint(constraint *schema.Constraint) (sql string, results []interface{}) { - sql = "CONSTRAINT ? FOREIGN KEY ? REFERENCES ??" - if constraint.OnDelete != "" { - sql += " ON DELETE " + constraint.OnDelete - } +type _Index struct { + Seq int + Name string + Unique bool + Origin string + Partial bool +} - if constraint.OnUpdate != "" { - sql += " ON UPDATE " + constraint.OnUpdate - } - - var foreignKeys, references []interface{} - for _, field := range constraint.ForeignKeys { - foreignKeys = append(foreignKeys, clause.Column{Name: field.DBName}) - } - - for _, field := range constraint.References { - references = append(references, clause.Column{Name: field.DBName}) - } - results = append(results, clause.Table{Name: constraint.Name}, foreignKeys, clause.Table{Name: constraint.ReferenceSchema.Table}, references) - return +// GetIndexes return Indexes []gorm.Index and execErr error, +// See the [doc] +// +// [doc]: https://www.sqlite.org/pragma.html#pragma_index_list +func (m _Migrator) GetIndexes(value interface{}) ([]gorm.Index, error) { + indexes := make([]gorm.Index, 0) + err := m.RunWithValue(value, func(stmt *gorm.Statement) error { + rst := make([]*_Index, 0) + if err := m.DB.Debug().Raw("SELECT * FROM PRAGMA_index_list(?)", stmt.Table).Scan(&rst).Error; err != nil { // alias `PRAGMA index_list(?)` + return err + } + for _, index := range rst { + if index.Origin == "u" { // skip the index was created by a UNIQUE constraint + continue + } + var columns []string + if err := m.DB.Raw("SELECT name FROM PRAGMA_index_info(?)", index.Name).Scan(&columns).Error; err != nil { // alias `PRAGMA index_info(?)` + return err + } + indexes = append(indexes, &migrator.Index{ + TableName: stmt.Table, + NameValue: index.Name, + ColumnList: columns, + PrimaryKeyValue: sql.NullBool{Bool: index.Origin == "pk", Valid: true}, // The exceptions are INTEGER PRIMARY KEY + UniqueValue: sql.NullBool{Bool: index.Unique, Valid: true}, + }) + } + return nil + }) + return indexes, err } func (m _Migrator) getRawDDL(table string) (string, error) { diff --git a/gormlite/test.sh b/gormlite/test.sh index f569cfc..2abd813 100755 --- a/gormlite/test.sh +++ b/gormlite/test.sh @@ -3,10 +3,11 @@ set -euo pipefail cd -P -- "$(dirname -- "$0")" +rm -rf gorm/ tests/ +go work use -r . go test -rm -rf gorm/ tests/ -git clone --branch v1.25.6 --filter=blob:none https://github.com/go-gorm/gorm.git +git clone --branch v1.25.7 --filter=blob:none https://github.com/go-gorm/gorm.git mv gorm/tests tests rm -rf gorm/