Add GaussDB Database Support (#7508)

* support gaussdb

* use github CI

* change function name

* use gorm.io/driver/gaussdb

---------

Co-authored-by: bing.ma <bing.ma@daocloud.io>
This commit is contained in:
moseszane168 2025-07-21 10:46:58 +08:00 committed by GitHub
parent 751a6dde7a
commit 991c2d4891
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 597 additions and 22 deletions

View File

@ -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

View File

@ -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
}

View File

@ -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
}

248
tests/gaussdb_test.go Normal file
View File

@ -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{})
}

View File

@ -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 => ../

View File

@ -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"`

View File

@ -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)

View File

@ -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
}

View File

@ -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, "`", `"`)

View File

@ -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

View File

@ -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 ..

View File

@ -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

View File

@ -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
}