From 991c2d4891ddf17eccc2758c84c8061ef6d0b7b5 Mon Sep 17 00:00:00 2001 From: moseszane168 <916868287@qq.com> Date: Mon, 21 Jul 2025 10:46:58 +0800 Subject: [PATCH] Add GaussDB Database Support (#7508) * support gaussdb * use github CI * change function name * use gorm.io/driver/gaussdb --------- Co-authored-by: bing.ma --- .github/workflows/tests.yml | 68 +++++++++ tests/delete_test.go | 6 +- tests/error_translator_test.go | 4 +- tests/gaussdb_test.go | 248 +++++++++++++++++++++++++++++++ tests/go.mod | 5 +- tests/migrate_test.go | 177 +++++++++++++++++++++- tests/multi_primary_keys_test.go | 9 +- tests/serializer_test.go | 2 +- tests/sql_builder_test.go | 2 +- tests/table_test.go | 77 ++++++++++ tests/tests_all.sh | 2 +- tests/tests_test.go | 11 ++ tests/update_test.go | 8 +- 13 files changed, 597 insertions(+), 22 deletions(-) create mode 100644 tests/gaussdb_test.go diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e70d2bdd..f1d27d76 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -240,3 +240,71 @@ jobs: - name: Tests run: GITHUB_ACTION=true GORM_DIALECT=tidb GORM_DSN="root:@tcp(localhost:9940)/test?charset=utf8&parseTime=True&loc=Local" ./tests/tests_all.sh + + gaussdb: + strategy: + matrix: + dbversion: ['opengauss/opengauss:7.0.0-RC1.B023'] + go: ['1.23', '1.24'] + platform: [ubuntu-latest] # can not run in macOS and Windows + runs-on: ${{ matrix.platform }} + + services: + gaussdb: + image: ${{ matrix.dbversion }} + env: + # GaussDB has password limitations + GS_PASSWORD: Gaussdb@123 + TZ: Asia/Shanghai + ports: + - 9950:5432 + + steps: + - name: Set up Go 1.x + uses: actions/setup-go@v4 + with: + go-version: ${{ matrix.go }} + + - name: Check out code into the Go module directory + uses: actions/checkout@v4 + + - name: Waiting for GaussDB to be ready + run: | + container_name=$(docker ps --filter "ancestor=opengauss/opengauss:7.0.0-RC1.B023" --format "{{.Names}}") + if [ -z "$container_name" ]; then + echo "Error: failed to find a container created from the 'opengauss/opengauss:7.0.0-RC1.B023' image." + exit 1 + fi + max_retries=12 + retry_count=0 + if [ -t 0 ]; then + TTY_FLAG="-t" + else + TTY_FLAG="" + fi + while [ $retry_count -lt $max_retries ]; do + if docker exec -i "${container_name}" bash -c "su - omm -c 'gsql -U omm -c \"select 1;\"'" + then + echo "Creating database gorm..." + sql_file='/tmp/create_database.sql' + echo "CREATE DATABASE gorm DBCOMPATIBILITY 'PG';" > ${sql_file} + docker cp "${sql_file}" "${container_name}":"${sql_file}" + docker exec -i ${TTY_FLAG} "${container_name}" bash -c "su - omm -c 'gsql -U omm -f ${sql_file}'" + echo "Database initialization completed." + break + fi + + echo "Waiting for database to be ready... (attempt $((retry_count + 1))/$max_retries)" + sleep 10 + ((++retry_count)) + done + exit 0 + + - name: go mod package cache + uses: actions/cache@v4 + with: + path: ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ matrix.go }}-${{ hashFiles('tests/go.mod') }} + + - name: Tests + run: GITHUB_ACTION=true GORM_DIALECT=gaussdb GORM_DSN="user=gaussdb password=Gaussdb@123 dbname=gorm host=localhost port=9950 sslmode=disable TimeZone=Asia/Shanghai" ./tests/tests_all.sh \ No newline at end of file diff --git a/tests/delete_test.go b/tests/delete_test.go index 5d112b4e..b9b5289c 100644 --- a/tests/delete_test.go +++ b/tests/delete_test.go @@ -206,9 +206,9 @@ func TestDeleteSliceWithAssociations(t *testing.T) { } } -// only sqlite, postgres, sqlserver support returning +// only sqlite, postgres, gaussdb, sqlserver support returning func TestSoftDeleteReturning(t *testing.T) { - if DB.Dialector.Name() != "sqlite" && DB.Dialector.Name() != "postgres" && DB.Dialector.Name() != "sqlserver" { + if DB.Dialector.Name() != "sqlite" && DB.Dialector.Name() != "postgres" && DB.Dialector.Name() != "gaussdb" && DB.Dialector.Name() != "sqlserver" { return } @@ -233,7 +233,7 @@ func TestSoftDeleteReturning(t *testing.T) { } func TestDeleteReturning(t *testing.T) { - if DB.Dialector.Name() != "sqlite" && DB.Dialector.Name() != "postgres" && DB.Dialector.Name() != "sqlserver" { + if DB.Dialector.Name() != "sqlite" && DB.Dialector.Name() != "postgres" && DB.Dialector.Name() != "gaussdb" && DB.Dialector.Name() != "sqlserver" { return } diff --git a/tests/error_translator_test.go b/tests/error_translator_test.go index ee54300e..765ea574 100644 --- a/tests/error_translator_test.go +++ b/tests/error_translator_test.go @@ -39,7 +39,7 @@ func TestSupportedDialectorWithErrDuplicatedKey(t *testing.T) { t.Fatalf("failed to connect database, got error %v", err) } - dialectors := map[string]bool{"sqlite": true, "postgres": true, "mysql": true, "sqlserver": true} + dialectors := map[string]bool{"sqlite": true, "postgres": true, "gaussdb": true, "mysql": true, "sqlserver": true} if supported, found := dialectors[db.Dialector.Name()]; !(found && supported) { return } @@ -81,7 +81,7 @@ func TestSupportedDialectorWithErrForeignKeyViolated(t *testing.T) { t.Fatalf("failed to connect database, got error %v", err) } - dialectors := map[string]bool{"sqlite": true, "postgres": true, "mysql": true, "sqlserver": true} + dialectors := map[string]bool{"sqlite": true, "postgres": true, "gaussdb": true, "mysql": true, "sqlserver": true} if supported, found := dialectors[db.Dialector.Name()]; !(found && supported) { return } diff --git a/tests/gaussdb_test.go b/tests/gaussdb_test.go new file mode 100644 index 00000000..1aca302f --- /dev/null +++ b/tests/gaussdb_test.go @@ -0,0 +1,248 @@ +package tests_test + +import ( + "testing" + "time" + + "github.com/google/uuid" + "github.com/lib/pq" + "gorm.io/gorm" + "gorm.io/gorm/clause" + . "gorm.io/gorm/utils/tests" +) + +func TestGaussDBReturningIDWhichHasStringType(t *testing.T) { + t.Skipf("This test case skipped, because of gaussdb not support pgcrypto extension and gen_random_uuid() function") + if DB.Dialector.Name() != "gaussdb" { + t.Skip() + } + + type Yasuo struct { + // TODO: function gen_random_uuid() does not exist + ID string `gorm:"default:gen_random_uuid()"` + Name string + CreatedAt time.Time `gorm:"type:TIMESTAMP WITHOUT TIME ZONE"` + UpdatedAt time.Time `gorm:"type:TIMESTAMP WITHOUT TIME ZONE;default:current_timestamp"` + } + + if err := DB.Exec("CREATE EXTENSION IF NOT EXISTS pgcrypto;").Error; err != nil { + t.Errorf("Failed to create extension pgcrypto, got error %v", err) + } + + DB.Migrator().DropTable(&Yasuo{}) + + if err := DB.AutoMigrate(&Yasuo{}); err != nil { + t.Fatalf("Failed to migrate for uuid default value, got error: %v", err) + } + + yasuo := Yasuo{Name: "jinzhu"} + if err := DB.Create(&yasuo).Error; err != nil { + t.Fatalf("should be able to create data, but got %v", err) + } + + if yasuo.ID == "" { + t.Fatal("should be able to has ID, but got zero value") + } + + var result Yasuo + if err := DB.First(&result, "id = ?", yasuo.ID).Error; err != nil || yasuo.Name != "jinzhu" { + t.Errorf("No error should happen, but got %v", err) + } + + if err := DB.Where("id = $1", yasuo.ID).First(&Yasuo{}).Error; err != nil || yasuo.Name != "jinzhu" { + t.Errorf("No error should happen, but got %v", err) + } + + yasuo.Name = "jinzhu1" + if err := DB.Save(&yasuo).Error; err != nil { + t.Errorf("Failed to update date, got error %v", err) + } + + if err := DB.First(&result, "id = ?", yasuo.ID).Error; err != nil || yasuo.Name != "jinzhu1" { + t.Errorf("No error should happen, but got %v", err) + } +} + +func TestGaussDB(t *testing.T) { + t.Skipf("This test case skipped, because of gaussdb not support pgcrypto extension and gen_random_uuid() function") + if DB.Dialector.Name() != "gaussdb" { + t.Skip() + } + + type Harumph struct { + gorm.Model + Name string `gorm:"check:name_checker,name <> ''"` + // TODO: function gen_random_uuid() does not exist + Test uuid.UUID `gorm:"type:uuid;not null;default:gen_random_uuid()"` + CreatedAt time.Time `gorm:"type:TIMESTAMP WITHOUT TIME ZONE"` + UpdatedAt time.Time `gorm:"type:TIMESTAMP WITHOUT TIME ZONE;default:current_timestamp"` + Things pq.StringArray `gorm:"type:text[]"` + } + + if err := DB.Exec("CREATE EXTENSION IF NOT EXISTS pgcrypto;").Error; err != nil { + t.Errorf("Failed to create extension pgcrypto, got error %v", err) + } + + DB.Migrator().DropTable(&Harumph{}) + + if err := DB.AutoMigrate(&Harumph{}); err != nil { + t.Fatalf("Failed to migrate for uuid default value, got error: %v", err) + } + + harumph := Harumph{} + if err := DB.Create(&harumph).Error; err == nil { + t.Fatalf("should failed to create data, name can't be blank") + } + + harumph = Harumph{Name: "jinzhu"} + if err := DB.Create(&harumph).Error; err != nil { + t.Fatalf("should be able to create data, but got %v", err) + } + + var result Harumph + if err := DB.First(&result, "id = ?", harumph.ID).Error; err != nil || harumph.Name != "jinzhu" { + t.Errorf("No error should happen, but got %v", err) + } + + if err := DB.Where("id = $1", harumph.ID).First(&Harumph{}).Error; err != nil || harumph.Name != "jinzhu" { + t.Errorf("No error should happen, but got %v", err) + } + + harumph.Name = "jinzhu1" + if err := DB.Save(&harumph).Error; err != nil { + t.Errorf("Failed to update date, got error %v", err) + } + + if err := DB.First(&result, "id = ?", harumph.ID).Error; err != nil || harumph.Name != "jinzhu1" { + t.Errorf("No error should happen, but got %v", err) + } + + DB.Migrator().DropTable("log_usage") + + if err := DB.Exec(` +CREATE TABLE public.log_usage ( + log_id bigint NOT NULL +); + +ALTER TABLE public.log_usage ALTER COLUMN log_id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.log_usage_log_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + `).Error; err != nil { + t.Fatalf("failed to create table, got error %v", err) + } + + columns, err := DB.Migrator().ColumnTypes("log_usage") + if err != nil { + t.Fatalf("failed to get columns, got error %v", err) + } + + hasLogID := false + for _, column := range columns { + if column.Name() == "log_id" { + hasLogID = true + autoIncrement, ok := column.AutoIncrement() + if !ok || !autoIncrement { + t.Fatalf("column log_id should be auto incrementment") + } + } + } + + if !hasLogID { + t.Fatalf("failed to found column log_id") + } +} + +func TestGaussDBMany2ManyWithDefaultValueUUID(t *testing.T) { + t.Skipf("This test case skipped, because of gaussdb does not have 'uuid-ossp' extension") + if DB.Dialector.Name() != "gaussdb" { + t.Skip() + } + + if err := DB.Exec(`create extension if not exists "uuid-ossp"`).Error; err != nil { + t.Fatalf("Failed to create 'uuid-ossp' extension, but got error %v", err) + } + + DB.Migrator().DropTable(&Post{}, &Category{}, "post_categories") + DB.AutoMigrate(&Post{}, &Category{}) + + post := Post{ + Title: "Hello World", + Categories: []*Category{ + {Title: "Coding"}, + {Title: "Golang"}, + }, + } + + if err := DB.Create(&post).Error; err != nil { + t.Errorf("Failed, got error: %v", err) + } +} + +func TestGaussDBOnConstraint(t *testing.T) { + t.Skipf("This test case skipped, because of gaussdb not support 'ON CONSTRAINT' statement") + if DB.Dialector.Name() != "gaussdb" { + t.Skip() + } + + type Thing struct { + gorm.Model + SomeID string + OtherID string + Data string + } + + DB.Migrator().DropTable(&Thing{}) + DB.Migrator().CreateTable(&Thing{}) + if err := DB.Exec("ALTER TABLE things ADD CONSTRAINT some_id_other_id_unique UNIQUE (some_id, other_id)").Error; err != nil { + t.Error(err) + } + + thing := Thing{ + SomeID: "1234", + OtherID: "1234", + Data: "something", + } + + DB.Create(&thing) + + thing2 := Thing{ + SomeID: "1234", + OtherID: "1234", + Data: "something else", + } + + result := DB.Clauses(clause.OnConflict{ + OnConstraint: "some_id_other_id_unique", + UpdateAll: true, + }).Create(&thing2) + if result.Error != nil { + t.Errorf("creating second thing: %v", result.Error) + } + + var things []Thing + if err := DB.Find(&things).Error; err != nil { + t.Errorf("Failed, got error: %v", err) + } + + if len(things) > 1 { + t.Errorf("expected 1 thing got more") + } +} + +func TestGaussDBAlterColumnDataType(t *testing.T) { + if DB.Dialector.Name() != "gaussdb" { + t.Skip() + } + DB.Migrator().DropTable(&Company{}) + DB.AutoMigrate(Company{}) + if err := DB.Table("companies").Migrator().AlterColumn(CompanyNew{}, "name"); err != nil { + t.Fatalf("failed to alter column from string to int, got error %v", err) + } + + DB.AutoMigrate(Company{}) +} diff --git a/tests/go.mod b/tests/go.mod index 1074f2b9..aa8b8e53 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -7,6 +7,7 @@ require ( github.com/jinzhu/now v1.1.5 github.com/lib/pq v1.10.9 github.com/stretchr/testify v1.10.0 + gorm.io/driver/gaussdb v0.1.0 gorm.io/driver/mysql v1.6.0 gorm.io/driver/postgres v1.6.0 gorm.io/driver/sqlite v1.6.0 @@ -16,6 +17,7 @@ require ( require ( filippo.io/edwards25519 v1.1.0 // indirect + github.com/HuaweiCloudDeveloper/gaussdb-go v1.0.0-rc1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-sql-driver/mysql v1.9.2 // indirect github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect @@ -30,10 +32,11 @@ require ( github.com/microsoft/go-mssqldb v1.8.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect + github.com/tjfoc/gmsm v1.4.1 // indirect golang.org/x/crypto v0.38.0 // indirect golang.org/x/sync v0.14.0 // indirect golang.org/x/text v0.25.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) -replace gorm.io/gorm => ../ +replace gorm.io/gorm => ../ \ No newline at end of file diff --git a/tests/migrate_test.go b/tests/migrate_test.go index b60a6df5..e04a42fb 100644 --- a/tests/migrate_test.go +++ b/tests/migrate_test.go @@ -12,6 +12,7 @@ import ( "time" "github.com/stretchr/testify/assert" + "gorm.io/driver/gaussdb" "gorm.io/driver/postgres" "gorm.io/gorm" @@ -82,8 +83,8 @@ func TestMigrate(t *testing.T) { } } -func TestAutoMigrateInt8PG(t *testing.T) { - if DB.Dialector.Name() != "postgres" { +func TestAutoMigrateInt8PGAndGaussDB(t *testing.T) { + if DB.Dialector.Name() != "postgres" && DB.Dialector.Name() != "gaussdb" { return } @@ -182,7 +183,94 @@ func TestAutoMigrateNullable(t *testing.T) { } func TestSmartMigrateColumn(t *testing.T) { - fullSupported := map[string]bool{"mysql": true, "postgres": true}[DB.Dialector.Name()] + fullSupported := map[string]bool{"mysql": true, "postgres": true, "gaussdb": true}[DB.Dialector.Name()] + + type UserMigrateColumn struct { + ID uint + Name string + Salary float64 + Birthday time.Time `gorm:"precision:4"` + } + + DB.Migrator().DropTable(&UserMigrateColumn{}) + + DB.AutoMigrate(&UserMigrateColumn{}) + + type UserMigrateColumn2 struct { + ID uint + Name string `gorm:"size:128"` + Salary float64 `gorm:"precision:2"` + Birthday time.Time `gorm:"precision:2"` + NameIgnoreMigration string `gorm:"size:100"` + } + + if err := DB.Table("user_migrate_columns").AutoMigrate(&UserMigrateColumn2{}); err != nil { + t.Fatalf("failed to auto migrate, got error: %v", err) + } + + columnTypes, err := DB.Table("user_migrate_columns").Migrator().ColumnTypes(&UserMigrateColumn{}) + if err != nil { + t.Fatalf("failed to get column types, got error: %v", err) + } + + for _, columnType := range columnTypes { + switch columnType.Name() { + case "name": + if length, _ := columnType.Length(); (fullSupported || length != 0) && length != 128 { + t.Fatalf("name's length should be 128, but got %v", length) + } + case "salary": + if precision, o, _ := columnType.DecimalSize(); (fullSupported || precision != 0) && precision != 2 { + t.Fatalf("salary's precision should be 2, but got %v %v", precision, o) + } + case "birthday": + if precision, _, _ := columnType.DecimalSize(); (fullSupported || precision != 0) && precision != 2 { + t.Fatalf("birthday's precision should be 2, but got %v", precision) + } + } + } + + type UserMigrateColumn3 struct { + ID uint + Name string `gorm:"size:256"` + Salary float64 `gorm:"precision:3"` + Birthday time.Time `gorm:"precision:3"` + NameIgnoreMigration string `gorm:"size:128;-:migration"` + } + + if err := DB.Table("user_migrate_columns").AutoMigrate(&UserMigrateColumn3{}); err != nil { + t.Fatalf("failed to auto migrate, got error: %v", err) + } + + columnTypes, err = DB.Table("user_migrate_columns").Migrator().ColumnTypes(&UserMigrateColumn{}) + if err != nil { + t.Fatalf("failed to get column types, got error: %v", err) + } + + for _, columnType := range columnTypes { + switch columnType.Name() { + case "name": + if length, _ := columnType.Length(); (fullSupported || length != 0) && length != 256 { + t.Fatalf("name's length should be 128, but got %v", length) + } + case "salary": + if precision, _, _ := columnType.DecimalSize(); (fullSupported || precision != 0) && precision != 3 { + t.Fatalf("salary's precision should be 2, but got %v", precision) + } + case "birthday": + if precision, _, _ := columnType.DecimalSize(); (fullSupported || precision != 0) && precision != 3 { + t.Fatalf("birthday's precision should be 2, but got %v", precision) + } + case "name_ignore_migration": + if length, _ := columnType.Length(); (fullSupported || length != 0) && length != 100 { + t.Fatalf("name_ignore_migration's length should still be 100 but got %v", length) + } + } + } +} + +func TestSmartMigrateColumnGaussDB(t *testing.T) { + fullSupported := map[string]bool{"mysql": true, "gaussdb": true}[DB.Dialector.Name()] type UserMigrateColumn struct { ID uint @@ -850,7 +938,7 @@ func TestMigrateColumnOrder(t *testing.T) { // https://github.com/go-gorm/gorm/issues/5047 func TestMigrateSerialColumn(t *testing.T) { - if DB.Dialector.Name() != "postgres" { + if DB.Dialector.Name() != "postgres" && DB.Dialector.Name() != "gaussdb" { return } @@ -1009,6 +1097,42 @@ func TestPrimarykeyID(t *testing.T) { } } +func TestPrimarykeyIDGaussDB(t *testing.T) { + t.Skipf("This test case skipped, because of gaussdb not support uuid-ossp plugin (SQLSTATE 58P01)") + if DB.Dialector.Name() != "gaussdb" { + return + } + + type MissPKLanguage struct { + ID string `gorm:"type:uuid;default:uuid_generate_v4()"` + Name string + } + + type MissPKUser struct { + ID string `gorm:"type:uuid;default:uuid_generate_v4()"` + MissPKLanguages []MissPKLanguage `gorm:"many2many:miss_pk_user_languages;"` + } + + var err error + err = DB.Migrator().DropTable(&MissPKUser{}, &MissPKLanguage{}) + if err != nil { + t.Fatalf("DropTable err:%v", err) + } + // TODO: ERROR: could not open extension control file: No such file or directory (SQLSTATE 58P01) + DB.Exec(`CREATE EXTENSION IF NOT EXISTS "uuid-ossp";`) + + err = DB.AutoMigrate(&MissPKUser{}, &MissPKLanguage{}) + if err != nil { + t.Fatalf("AutoMigrate err:%v", err) + } + + // patch + err = DB.AutoMigrate(&MissPKUser{}, &MissPKLanguage{}) + if err != nil { + t.Fatalf("AutoMigrate err:%v", err) + } +} + func TestCurrentTimestamp(t *testing.T) { if DB.Dialector.Name() != "mysql" { return @@ -1209,6 +1333,43 @@ func TestInvalidCachedPlanSimpleProtocol(t *testing.T) { } } +// TODO: ERROR: must have at least one column (SQLSTATE 0A000) +func TestInvalidCachedPlanSimpleProtocolGaussDB(t *testing.T) { + t.Skipf("This test case skipped, because of gaussdb not support creaing empty table(SQLSTATE 0A000)") + if DB.Dialector.Name() != "gaussdb" { + return + } + + db, err := gorm.Open(gaussdb.Open(gaussdbDSN), &gorm.Config{}) + if err != nil { + t.Errorf("Open err:%v", err) + } + + type Object1 struct{} + type Object2 struct { + Field1 string + } + type Object3 struct { + Field2 string + } + db.Migrator().DropTable("objects") + + err = db.Table("objects").AutoMigrate(&Object1{}) + if err != nil { + t.Errorf("AutoMigrate err:%v", err) + } + + err = db.Table("objects").AutoMigrate(&Object2{}) + if err != nil { + t.Errorf("AutoMigrate err:%v", err) + } + + err = db.Table("objects").AutoMigrate(&Object3{}) + if err != nil { + t.Errorf("AutoMigrate err:%v", err) + } +} + func TestDifferentTypeWithoutDeclaredLength(t *testing.T) { type DiffType struct { ID uint @@ -1249,7 +1410,7 @@ func TestDifferentTypeWithoutDeclaredLength(t *testing.T) { } func TestMigrateArrayTypeModel(t *testing.T) { - if DB.Dialector.Name() != "postgres" { + if DB.Dialector.Name() != "postgres" && DB.Dialector.Name() != "gaussdb" { return } @@ -1571,8 +1732,8 @@ func TestMigrateView(t *testing.T) { } } -func TestMigrateExistingBoolColumnPG(t *testing.T) { - if DB.Dialector.Name() != "postgres" { +func TestMigrateExistingBoolColumnPGAndGaussDB(t *testing.T) { + if DB.Dialector.Name() != "postgres" && DB.Dialector.Name() != "gaussdb" { return } @@ -1965,7 +2126,7 @@ func TestAutoMigrateDecimal(t *testing.T) { `ALTER TABLE "migrate_decimal_columns" ALTER COLUMN "recid3" decimal(9,2) NOT NULL`, } decimalColumnsTest[MigrateDecimalColumn, MigrateDecimalColumn2](t, expectedSql) - } else if DB.Dialector.Name() == "postgres" { + } else if DB.Dialector.Name() == "postgres" || DB.Dialector.Name() == "gaussdb" { type MigrateDecimalColumn struct { RecID1 int64 `gorm:"column:recid1;type:numeric(9,0);not null" json:"recid1"` RecID2 int64 `gorm:"column:recid2;type:numeric(8);not null" json:"recid2"` diff --git a/tests/multi_primary_keys_test.go b/tests/multi_primary_keys_test.go index 4ecbff6a..28eb03f8 100644 --- a/tests/multi_primary_keys_test.go +++ b/tests/multi_primary_keys_test.go @@ -41,7 +41,7 @@ func TestManyToManyWithMultiPrimaryKeys(t *testing.T) { t.Skip("skip sqlite, sqlserver due to it doesn't support multiple primary keys with auto increment") } - if name := DB.Dialector.Name(); name == "postgres" || name == "mysql" { + if name := DB.Dialector.Name(); name == "postgres" || name == "mysql" || name == "gaussdb" { stmt := gorm.Statement{DB: DB} stmt.Parse(&Blog{}) stmt.Schema.LookUpField("ID").Unique = true @@ -142,6 +142,9 @@ func TestManyToManyWithCustomizedForeignKeys(t *testing.T) { if name := DB.Dialector.Name(); name == "postgres" { t.Skip("skip postgres due to it only allow unique constraint matching given keys") } + if name := DB.Dialector.Name(); name == "gaussdb" { + t.Skip("skip gaussdb due to it only allow unique constraint matching given keys") + } DB.Migrator().DropTable(&Blog{}, &Tag{}, "blog_tags", "locale_blog_tags", "shared_blog_tags") if err := DB.AutoMigrate(&Blog{}, &Tag{}); err != nil { @@ -268,6 +271,10 @@ func TestManyToManyWithCustomizedForeignKeys2(t *testing.T) { t.Skip("skip postgres due to it only allow unique constraint matching given keys") } + if name := DB.Dialector.Name(); name == "gaussdb" { + t.Skip("skip gaussdb due to it only allow unique constraint matching given keys") + } + DB.Migrator().DropTable(&Blog{}, &Tag{}, "blog_tags", "locale_blog_tags", "shared_blog_tags") if err := DB.AutoMigrate(&Blog{}, &Tag{}); err != nil { t.Fatalf("Failed to auto migrate, got error: %v", err) diff --git a/tests/serializer_test.go b/tests/serializer_test.go index f1b8a336..dbcc49e4 100644 --- a/tests/serializer_test.go +++ b/tests/serializer_test.go @@ -45,7 +45,7 @@ type SerializerPostgresStruct struct { func (*SerializerPostgresStruct) TableName() string { return "serializer_structs" } func adaptorSerializerModel(s *SerializerStruct) interface{} { - if DB.Dialector.Name() == "postgres" { + if DB.Dialector.Name() == "postgres" || DB.Dialector.Name() == "gaussdb" { sps := SerializerPostgresStruct(*s) return &sps } diff --git a/tests/sql_builder_test.go b/tests/sql_builder_test.go index 0c204db4..13fcb648 100644 --- a/tests/sql_builder_test.go +++ b/tests/sql_builder_test.go @@ -487,7 +487,7 @@ func replaceQuoteInSQL(sql string) string { // convert dialect special quote into double quote switch DB.Dialector.Name() { - case "postgres": + case "postgres", "gaussdb": sql = strings.ReplaceAll(sql, `"`, `"`) case "mysql", "sqlite": sql = strings.ReplaceAll(sql, "`", `"`) diff --git a/tests/table_test.go b/tests/table_test.go index 0d44a15b..85ae2ab9 100644 --- a/tests/table_test.go +++ b/tests/table_test.go @@ -5,6 +5,7 @@ import ( "sync" "testing" + "gorm.io/driver/gaussdb" "gorm.io/driver/postgres" "gorm.io/gorm" "gorm.io/gorm/schema" @@ -251,6 +252,82 @@ func TestPostgresTableWithIdentifierLength(t *testing.T) { }) } +func TestGaussDBTableWithIdentifierLength(t *testing.T) { + if DB.Dialector.Name() != "gaussdb" { + return + } + + type LongString struct { + ThisIsAVeryVeryVeryVeryVeryVeryVeryVeryVeryLongString string `gorm:"unique"` + } + + t.Run("default", func(t *testing.T) { + db, _ := gorm.Open(gaussdb.Open(gaussdbDSN), &gorm.Config{}) + user, err := schema.Parse(&LongString{}, &sync.Map{}, db.Config.NamingStrategy) + if err != nil { + t.Fatalf("failed to parse user unique, got error %v", err) + } + + constraints := user.ParseUniqueConstraints() + if len(constraints) != 1 { + t.Fatalf("failed to find unique constraint, got %v", constraints) + } + + for key := range constraints { + if len(key) != 63 { + t.Errorf("failed to find unique constraint, got %v", constraints) + } + } + }) + + t.Run("naming strategy", func(t *testing.T) { + db, _ := gorm.Open(gaussdb.Open(gaussdbDSN), &gorm.Config{ + NamingStrategy: schema.NamingStrategy{}, + }) + + user, err := schema.Parse(&LongString{}, &sync.Map{}, db.Config.NamingStrategy) + if err != nil { + t.Fatalf("failed to parse user unique, got error %v", err) + } + + constraints := user.ParseUniqueConstraints() + if len(constraints) != 1 { + t.Fatalf("failed to find unique constraint, got %v", constraints) + } + + for key := range constraints { + if len(key) != 63 { + t.Errorf("failed to find unique constraint, got %v", constraints) + } + } + }) + + t.Run("namer", func(t *testing.T) { + uname := "custom_unique_name" + db, _ := gorm.Open(gaussdb.Open(gaussdbDSN), &gorm.Config{ + NamingStrategy: mockUniqueNamingStrategy{ + UName: uname, + }, + }) + + user, err := schema.Parse(&LongString{}, &sync.Map{}, db.Config.NamingStrategy) + if err != nil { + t.Fatalf("failed to parse user unique, got error %v", err) + } + + constraints := user.ParseUniqueConstraints() + if len(constraints) != 1 { + t.Fatalf("failed to find unique constraint, got %v", constraints) + } + + for key := range constraints { + if key != uname { + t.Errorf("failed to find unique constraint, got %v", constraints) + } + } + }) +} + type mockUniqueNamingStrategy struct { UName string schema.NamingStrategy diff --git a/tests/tests_all.sh b/tests/tests_all.sh index b221a7d8..b0635084 100755 --- a/tests/tests_all.sh +++ b/tests/tests_all.sh @@ -1,6 +1,6 @@ #!/bin/bash -e -dialects=("sqlite" "mysql" "postgres" "sqlserver" "tidb") +dialects=("sqlite" "mysql" "postgres" "gaussdb" "sqlserver" "tidb") if [[ $(pwd) == *"gorm/tests"* ]]; then cd .. diff --git a/tests/tests_test.go b/tests/tests_test.go index e1b33ea7..a7d03972 100644 --- a/tests/tests_test.go +++ b/tests/tests_test.go @@ -8,6 +8,7 @@ import ( "path/filepath" "time" + "gorm.io/driver/gaussdb" "gorm.io/driver/mysql" "gorm.io/driver/postgres" "gorm.io/driver/sqlite" @@ -21,6 +22,7 @@ var DB *gorm.DB var ( mysqlDSN = "gorm:gorm@tcp(localhost:9910)/gorm?charset=utf8&parseTime=True&loc=Local" postgresDSN = "user=gorm password=gorm dbname=gorm host=localhost port=9920 sslmode=disable TimeZone=Asia/Shanghai" + gaussdbDSN = "user=gaussdb password=Gaussdb@123 dbname=gorm host=localhost port=9950 sslmode=disable TimeZone=Asia/Shanghai" sqlserverDSN = "sqlserver://sa:LoremIpsum86@localhost:9930?database=master" tidbDSN = "root:@tcp(localhost:9940)/test?charset=utf8&parseTime=True&loc=Local" ) @@ -65,6 +67,15 @@ func OpenTestConnection(cfg *gorm.Config) (db *gorm.DB, err error) { DSN: dbDSN, PreferSimpleProtocol: true, }), cfg) + case "gaussdb": + log.Println("testing gaussdb...") + if dbDSN == "" { + dbDSN = gaussdbDSN + } + db, err = gorm.Open(gaussdb.New(gaussdb.Config{ + DSN: dbDSN, + PreferSimpleProtocol: true, + }), cfg) case "sqlserver": // go install github.com/microsoft/go-sqlcmd/cmd/sqlcmd@latest // SQLCMDPASSWORD=LoremIpsum86 sqlcmd -U sa -S localhost:9930 diff --git a/tests/update_test.go b/tests/update_test.go index 9eb9dbfc..1e3216af 100644 --- a/tests/update_test.go +++ b/tests/update_test.go @@ -765,9 +765,9 @@ func TestSaveWithPrimaryValue(t *testing.T) { } } -// only sqlite, postgres, sqlserver support returning +// only sqlite, postgres, gaussdb, sqlserver support returning func TestUpdateReturning(t *testing.T) { - if DB.Dialector.Name() != "sqlite" && DB.Dialector.Name() != "postgres" && DB.Dialector.Name() != "sqlserver" { + if DB.Dialector.Name() != "sqlite" && DB.Dialector.Name() != "postgres" && DB.Dialector.Name() != "gaussdb" && DB.Dialector.Name() != "sqlserver" { return } @@ -883,9 +883,9 @@ func TestSaveWithHooks(t *testing.T) { } } -// only postgres, sqlserver, sqlite support update from +// only postgres, gaussdb, sqlserver, sqlite support update from func TestUpdateFrom(t *testing.T) { - if DB.Dialector.Name() != "postgres" && DB.Dialector.Name() != "sqlite" && DB.Dialector.Name() != "sqlserver" { + if DB.Dialector.Name() != "postgres" && DB.Dialector.Name() != "gaussdb" && DB.Dialector.Name() != "sqlite" && DB.Dialector.Name() != "sqlserver" { return }