diff --git a/.github/workflows/release-on-tag.yml b/.github/workflows/release-on-tag.yml new file mode 100644 index 000000000..3868a8f56 --- /dev/null +++ b/.github/workflows/release-on-tag.yml @@ -0,0 +1,23 @@ +name: Create Release on Tag + +on: + push: + tags: + - '*' + +jobs: + create_release: + runs-on: ubuntu-latest + + steps: + - name: Create Release + uses: actions/create-release@v1 + with: + tag_name: ${{ github.ref_name }} + release_name: ${{ github.ref_name }} + body: | + Release ${{ github.ref_name }} of GORM. + draft: false + prerelease: false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0601f0063..24eab55ab 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -176,17 +176,15 @@ jobs: services: mssql: - image: mcmoe/mssqldocker:latest + image: mcr.microsoft.com/mssql/server:2022-latest env: + TZ: Asia/Shanghai ACCEPT_EULA: Y - SA_PASSWORD: LoremIpsum86 - MSSQL_DB: gorm - MSSQL_USER: gorm - MSSQL_PASSWORD: LoremIpsum86 + MSSQL_SA_PASSWORD: LoremIpsum86 ports: - 9930:1433 options: >- - --health-cmd="/opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P LoremIpsum86 -l 30 -Q \"SELECT 1\" || exit 1" + --health-cmd="/opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P ${MSSQL_SA_PASSWORD} -N -C -l 30 -Q \"SELECT 1\" || exit 1" --health-start-period 10s --health-interval 10s --health-timeout 5s @@ -208,7 +206,7 @@ jobs: key: ${{ runner.os }}-go-${{ matrix.go }}-${{ hashFiles('tests/go.mod') }} - name: Tests - run: GITHUB_ACTION=true GORM_DIALECT=sqlserver GORM_DSN="sqlserver://gorm:LoremIpsum86@localhost:9930?database=gorm" ./tests/tests_all.sh + run: GITHUB_ACTION=true GORM_DIALECT=sqlserver GORM_DSN="sqlserver://sa:LoremIpsum86@localhost:9930?database=master" ./tests/tests_all.sh tidb: strategy: diff --git a/finisher_api.go b/finisher_api.go index f97571ed0..6802945cc 100644 --- a/finisher_api.go +++ b/finisher_api.go @@ -4,6 +4,7 @@ import ( "database/sql" "errors" "fmt" + "hash/maphash" "reflect" "strings" @@ -623,14 +624,15 @@ func (db *DB) Transaction(fc func(tx *DB) error, opts ...*sql.TxOptions) (err er if committer, ok := db.Statement.ConnPool.(TxCommitter); ok && committer != nil { // nested transaction if !db.DisableNestedTransaction { - err = db.SavePoint(fmt.Sprintf("sp%p", fc)).Error + spID := new(maphash.Hash).Sum64() + err = db.SavePoint(fmt.Sprintf("sp%d", spID)).Error if err != nil { return } defer func() { // Make sure to rollback when panic, Block error or Commit error if panicked || err != nil { - db.RollbackTo(fmt.Sprintf("sp%p", fc)) + db.RollbackTo(fmt.Sprintf("sp%d", spID)) } }() } diff --git a/schema/field.go b/schema/field.go index a16c98ab0..bdc0b761c 100644 --- a/schema/field.go +++ b/schema/field.go @@ -12,6 +12,7 @@ import ( "time" "github.com/jinzhu/now" + "gorm.io/gorm/clause" "gorm.io/gorm/utils" ) @@ -922,7 +923,7 @@ func (field *Field) setupValuerAndSetter() { if !reflectV.IsValid() { field.ReflectValueOf(ctx, value).Set(reflect.New(field.FieldType).Elem()) } else if reflectV.Kind() == reflect.Ptr && reflectV.IsNil() { - return + field.ReflectValueOf(ctx, value).Set(reflect.New(field.FieldType).Elem()) } else if reflectV.Type().AssignableTo(field.FieldType) { field.ReflectValueOf(ctx, value).Set(reflectV) } else if reflectV.Kind() == reflect.Ptr { diff --git a/tests/docker-compose.yml b/tests/compose.yml similarity index 79% rename from tests/docker-compose.yml rename to tests/compose.yml index 5a3a10f2a..66f2daee7 100644 --- a/tests/docker-compose.yml +++ b/tests/compose.yml @@ -1,5 +1,3 @@ -version: '3' - services: mysql: image: 'mysql/mysql-server:latest' @@ -20,16 +18,13 @@ services: - POSTGRES_USER=gorm - POSTGRES_PASSWORD=gorm mssql: - image: '${MSSQL_IMAGE:-mcmoe/mssqldocker}:latest' + image: '${MSSQL_IMAGE}:2022-latest' ports: - "127.0.0.1:9930:1433" environment: - TZ=Asia/Shanghai - ACCEPT_EULA=Y - - SA_PASSWORD=LoremIpsum86 - - MSSQL_DB=gorm - - MSSQL_USER=gorm - - MSSQL_PASSWORD=LoremIpsum86 + - MSSQL_SA_PASSWORD=LoremIpsum86 tidb: image: 'pingcap/tidb:v6.5.0' ports: diff --git a/tests/go.mod b/tests/go.mod index 350d17946..44535b030 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -11,7 +11,7 @@ require ( gorm.io/driver/postgres v1.5.9 gorm.io/driver/sqlite v1.5.6 gorm.io/driver/sqlserver v1.5.3 - gorm.io/gorm v1.25.10 + gorm.io/gorm v1.25.12 ) require ( @@ -22,20 +22,18 @@ require ( github.com/golang-sql/sqlexp v0.1.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect - github.com/jackc/pgx/v5 v5.6.0 // indirect + github.com/jackc/pgx/v5 v5.7.1 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/kr/text v0.2.0 // indirect - github.com/mattn/go-sqlite3 v1.14.22 // indirect + github.com/mattn/go-sqlite3 v1.14.23 // indirect github.com/microsoft/go-mssqldb v1.7.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect - golang.org/x/crypto v0.24.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/crypto v0.27.0 // indirect + golang.org/x/text v0.18.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) replace gorm.io/gorm => ../ -replace github.com/jackc/pgx/v5 => github.com/jackc/pgx/v5 v5.4.3 - replace github.com/microsoft/go-mssqldb => github.com/microsoft/go-mssqldb v1.7.0 diff --git a/tests/tests_all.sh b/tests/tests_all.sh index ee9e76754..67c6938ea 100755 --- a/tests/tests_all.sh +++ b/tests/tests_all.sh @@ -21,13 +21,13 @@ if [[ -z $GITHUB_ACTION ]]; then then cd tests if [[ $(uname -a) == *" arm64" ]]; then - MSSQL_IMAGE=mcr.microsoft.com/azure-sql-edge docker-compose start || true + MSSQL_IMAGE=mcr.microsoft.com/azure-sql-edge docker compose up -d || true go install github.com/microsoft/go-sqlcmd/cmd/sqlcmd@latest || true SQLCMDPASSWORD=LoremIpsum86 sqlcmd -U sa -S localhost:9930 -Q "IF DB_ID('gorm') IS NULL CREATE DATABASE gorm" > /dev/null || true SQLCMDPASSWORD=LoremIpsum86 sqlcmd -U sa -S localhost:9930 -Q "IF SUSER_ID (N'gorm') IS NULL CREATE LOGIN gorm WITH PASSWORD = 'LoremIpsum86';" > /dev/null || true SQLCMDPASSWORD=LoremIpsum86 sqlcmd -U sa -S localhost:9930 -Q "IF USER_ID (N'gorm') IS NULL CREATE USER gorm FROM LOGIN gorm; ALTER SERVER ROLE sysadmin ADD MEMBER [gorm];" > /dev/null || true else - docker-compose start + MSSQL_IMAGE=mcr.microsoft.com/mssql/server docker compose up -d fi cd .. fi diff --git a/tests/tests_test.go b/tests/tests_test.go index a127734ed..e84162cd3 100644 --- a/tests/tests_test.go +++ b/tests/tests_test.go @@ -20,7 +20,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" - sqlserverDSN = "sqlserver://gorm:LoremIpsum86@localhost:9930?database=gorm" + sqlserverDSN = "sqlserver://sa:LoremIpsum86@localhost:9930?database=master" tidbDSN = "root:@tcp(localhost:9940)/test?charset=utf8&parseTime=True&loc=Local" ) diff --git a/tests/transaction_test.go b/tests/transaction_test.go index d2cbc9a95..9f0f067c8 100644 --- a/tests/transaction_test.go +++ b/tests/transaction_test.go @@ -297,6 +297,74 @@ func TestNestedTransactionWithBlock(t *testing.T) { } } +func TestDeeplyNestedTransactionWithBlockAndWrappedCallback(t *testing.T) { + transaction := func(ctx context.Context, db *gorm.DB, callback func(ctx context.Context, db *gorm.DB) error) error { + return db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + return callback(ctx, tx) + }) + } + var ( + user = *GetUser("transaction-nested", Config{}) + user1 = *GetUser("transaction-nested-1", Config{}) + user2 = *GetUser("transaction-nested-2", Config{}) + ) + + if err := transaction(context.Background(), DB, func(ctx context.Context, tx *gorm.DB) error { + tx.Create(&user) + + if err := tx.First(&User{}, "name = ?", user.Name).Error; err != nil { + t.Fatalf("Should find saved record") + } + + if err := transaction(ctx, tx, func(ctx context.Context, tx1 *gorm.DB) error { + tx1.Create(&user1) + + if err := tx1.First(&User{}, "name = ?", user1.Name).Error; err != nil { + t.Fatalf("Should find saved record") + } + + if err := transaction(ctx, tx1, func(ctx context.Context, tx2 *gorm.DB) error { + tx2.Create(&user2) + + if err := tx2.First(&User{}, "name = ?", user2.Name).Error; err != nil { + t.Fatalf("Should find saved record") + } + + return errors.New("inner rollback") + }); err == nil { + t.Fatalf("nested transaction has no error") + } + + return errors.New("rollback") + }); err == nil { + t.Fatalf("nested transaction should returns error") + } + + if err := tx.First(&User{}, "name = ?", user1.Name).Error; err == nil { + t.Fatalf("Should not find rollbacked record") + } + + if err := tx.First(&User{}, "name = ?", user2.Name).Error; err != nil { + t.Fatalf("Should find saved record") + } + return nil + }); err != nil { + t.Fatalf("no error should return, but got %v", err) + } + + if err := DB.First(&User{}, "name = ?", user.Name).Error; err != nil { + t.Fatalf("Should find saved record") + } + + if err := DB.First(&User{}, "name = ?", user1.Name).Error; err == nil { + t.Fatalf("Should not find rollbacked parent record") + } + + if err := DB.First(&User{}, "name = ?", user2.Name).Error; err != nil { + t.Fatalf("Should not find rollbacked nested record") + } +} + func TestDisabledNestedTransaction(t *testing.T) { var ( user = *GetUser("transaction-nested", Config{})