1
0
mirror of https://github.com/astaxie/beego.git synced 2024-11-22 14:40:57 +00:00

Merge pull request #3149 from liaoishere/feature/support-begintx

Support DB.BeginTx in go 1.8
This commit is contained in:
astaxie 2018-07-23 20:25:17 +08:00 committed by GitHub
commit d55f54a8ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 107 additions and 8 deletions

View File

@ -1,7 +1,7 @@
language: go language: go
go: go:
- "1.9.2" - "1.9.7"
- "1.10.3" - "1.10.3"
services: services:
- redis-server - redis-server
@ -44,8 +44,8 @@ before_script:
- sh -c "if [ '$ORM_DRIVER' = 'postgres' ]; then psql -c 'create database orm_test;' -U postgres; fi" - sh -c "if [ '$ORM_DRIVER' = 'postgres' ]; then psql -c 'create database orm_test;' -U postgres; fi"
- sh -c "if [ '$ORM_DRIVER' = 'mysql' ]; then mysql -u root -e 'create database orm_test;'; fi" - sh -c "if [ '$ORM_DRIVER' = 'mysql' ]; then mysql -u root -e 'create database orm_test;'; fi"
- sh -c "if [ '$ORM_DRIVER' = 'sqlite' ]; then touch $TRAVIS_BUILD_DIR/orm_test.db; fi" - sh -c "if [ '$ORM_DRIVER' = 'sqlite' ]; then touch $TRAVIS_BUILD_DIR/orm_test.db; fi"
- sh -c "if [ $(go version) == *1.[5-9]* ]; then go get github.com/golang/lint/golint; golint ./...; fi" - sh -c "go get github.com/golang/lint/golint; golint ./...;"
- sh -c "if [ $(go version) == *1.[5-9]* ]; then go tool vet .; fi" - sh -c "go tool vet ."
- mkdir -p res/var - mkdir -p res/var
- ./ssdb/ssdb-server ./ssdb/ssdb.conf -d - ./ssdb/ssdb-server ./ssdb/ssdb.conf -d
after_script: after_script:
@ -59,4 +59,4 @@ script:
- find . ! \( -path './vendor' -prune \) -type f -name '*.go' -print0 | xargs -0 gofmt -l -s - find . ! \( -path './vendor' -prune \) -type f -name '*.go' -print0 | xargs -0 gofmt -l -s
- golint ./... - golint ./...
addons: addons:
postgresql: "9.4" postgresql: "9.6"

View File

@ -536,6 +536,8 @@ func (d *dbBase) InsertOrUpdate(q dbQuerier, mi *modelInfo, ind reflect.Value, a
updates := make([]string, len(names)) updates := make([]string, len(names))
var conflitValue interface{} var conflitValue interface{}
for i, v := range names { for i, v := range names {
// identifier in database may not be case-sensitive, so quote it
v = fmt.Sprintf("%s%s%s", Q, v, Q)
marks[i] = "?" marks[i] = "?"
valueStr := argsMap[strings.ToLower(v)] valueStr := argsMap[strings.ToLower(v)]
if v == args0 { if v == args0 {

View File

@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// +build go1.8
// Package orm provide ORM for MySQL/PostgreSQL/sqlite // Package orm provide ORM for MySQL/PostgreSQL/sqlite
// Simple Usage // Simple Usage
// //
@ -52,6 +54,7 @@
package orm package orm
import ( import (
"context"
"database/sql" "database/sql"
"errors" "errors"
"fmt" "fmt"
@ -458,11 +461,15 @@ func (o *orm) Using(name string) error {
// begin transaction // begin transaction
func (o *orm) Begin() error { func (o *orm) Begin() error {
return o.BeginTx(context.Background(), nil)
}
func (o *orm) BeginTx(ctx context.Context, opts *sql.TxOptions) error {
if o.isTx { if o.isTx {
return ErrTxHasBegan return ErrTxHasBegan
} }
var tx *sql.Tx var tx *sql.Tx
tx, err := o.db.(txer).Begin() tx, err := o.db.(txer).BeginTx(ctx, opts)
if err != nil { if err != nil {
return err return err
} }

View File

@ -15,6 +15,7 @@
package orm package orm
import ( import (
"context"
"database/sql" "database/sql"
"fmt" "fmt"
"io" "io"
@ -150,6 +151,13 @@ func (d *dbQueryLog) Begin() (*sql.Tx, error) {
return tx, err return tx, err
} }
func (d *dbQueryLog) BeginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error) {
a := time.Now()
tx, err := d.db.(txer).BeginTx(ctx, opts)
debugLogQueies(d.alias, "db.BeginTx", "START TRANSACTION", a, err)
return tx, err
}
func (d *dbQueryLog) Commit() error { func (d *dbQueryLog) Commit() error {
a := time.Now() a := time.Now()
err := d.db.(txEnder).Commit() err := d.db.(txEnder).Commit()

View File

@ -12,10 +12,13 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// +build go1.8
package orm package orm
import ( import (
"bytes" "bytes"
"context"
"database/sql" "database/sql"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
@ -452,9 +455,9 @@ func TestNullDataTypes(t *testing.T) {
throwFail(t, AssertIs(*d.Float32Ptr, float32Ptr)) throwFail(t, AssertIs(*d.Float32Ptr, float32Ptr))
throwFail(t, AssertIs(*d.Float64Ptr, float64Ptr)) throwFail(t, AssertIs(*d.Float64Ptr, float64Ptr))
throwFail(t, AssertIs(*d.DecimalPtr, decimalPtr)) throwFail(t, AssertIs(*d.DecimalPtr, decimalPtr))
throwFail(t, AssertIs((*d.TimePtr).Format(testTime), timePtr.Format(testTime))) throwFail(t, AssertIs((*d.TimePtr).UTC().Format(testTime), timePtr.UTC().Format(testTime)))
throwFail(t, AssertIs((*d.DatePtr).Format(testDate), datePtr.Format(testDate))) throwFail(t, AssertIs((*d.DatePtr).UTC().Format(testDate), datePtr.UTC().Format(testDate)))
throwFail(t, AssertIs((*d.DateTimePtr).Format(testDateTime), dateTimePtr.Format(testDateTime))) throwFail(t, AssertIs((*d.DateTimePtr).UTC().Format(testDateTime), dateTimePtr.UTC().Format(testDateTime)))
} }
func TestDataCustomTypes(t *testing.T) { func TestDataCustomTypes(t *testing.T) {
@ -1990,6 +1993,66 @@ func TestTransaction(t *testing.T) {
} }
func TestTransactionIsolationLevel(t *testing.T) {
// this test worked when database support transaction isolation level
if IsSqlite {
return
}
o1 := NewOrm()
o2 := NewOrm()
// start two transaction with isolation level repeatable read
err := o1.BeginTx(context.Background(), &sql.TxOptions{Isolation: sql.LevelRepeatableRead})
throwFail(t, err)
err = o2.BeginTx(context.Background(), &sql.TxOptions{Isolation: sql.LevelRepeatableRead})
throwFail(t, err)
// o1 insert tag
var tag Tag
tag.Name = "test-transaction"
id, err := o1.Insert(&tag)
throwFail(t, err)
throwFail(t, AssertIs(id > 0, true))
// o2 query tag table, no result
num, err := o2.QueryTable("tag").Filter("name", "test-transaction").Count()
throwFail(t, err)
throwFail(t, AssertIs(num, 0))
// o1 commit
o1.Commit()
// o2 query tag table, still no result
num, err = o2.QueryTable("tag").Filter("name", "test-transaction").Count()
throwFail(t, err)
throwFail(t, AssertIs(num, 0))
// o2 commit and query tag table, get the result
o2.Commit()
num, err = o2.QueryTable("tag").Filter("name", "test-transaction").Count()
throwFail(t, err)
throwFail(t, AssertIs(num, 1))
num, err = o1.QueryTable("tag").Filter("name", "test-transaction").Delete()
throwFail(t, err)
throwFail(t, AssertIs(num, 1))
}
func TestBeginTxWithContextCanceled(t *testing.T) {
o := NewOrm()
ctx, cancel := context.WithCancel(context.Background())
o.BeginTx(ctx, nil)
id, err := o.Insert(&Tag{Name: "test-context"})
throwFail(t, err)
throwFail(t, AssertIs(id > 0, true))
// cancel the context before commit to make it error
cancel()
err = o.Commit()
throwFail(t, AssertIs(err, context.Canceled))
}
func TestReadOrCreate(t *testing.T) { func TestReadOrCreate(t *testing.T) {
u := &User{ u := &User{
UserName: "Kyle", UserName: "Kyle",
@ -2260,6 +2323,7 @@ func TestIgnoreCaseTag(t *testing.T) {
throwFail(t, AssertIs(info.fields.GetByName("Name02").column, "Name")) throwFail(t, AssertIs(info.fields.GetByName("Name02").column, "Name"))
throwFail(t, AssertIs(info.fields.GetByName("Name03").column, "name")) throwFail(t, AssertIs(info.fields.GetByName("Name03").column, "name"))
} }
func TestInsertOrUpdate(t *testing.T) { func TestInsertOrUpdate(t *testing.T) {
RegisterModel(new(User)) RegisterModel(new(User))
user := User{UserName: "unique_username133", Status: 1, Password: "o"} user := User{UserName: "unique_username133", Status: 1, Password: "o"}
@ -2297,6 +2361,11 @@ func TestInsertOrUpdate(t *testing.T) {
throwFailNow(t, AssertIs(user2.Status, test.Status)) throwFailNow(t, AssertIs(user2.Status, test.Status))
throwFailNow(t, AssertIs(user2.Password, strings.TrimSpace(test.Password))) throwFailNow(t, AssertIs(user2.Password, strings.TrimSpace(test.Password)))
} }
//postgres ON CONFLICT DO UPDATE SET can`t use colu=colu+values
if IsPostgres {
return
}
//test3 + //test3 +
_, err = dORM.InsertOrUpdate(&user2, "user_name", "status=status+1") _, err = dORM.InsertOrUpdate(&user2, "user_name", "status=status+1")
if err != nil { if err != nil {

View File

@ -15,6 +15,7 @@
package orm package orm
import ( import (
"context"
"database/sql" "database/sql"
"reflect" "reflect"
"time" "time"
@ -106,6 +107,17 @@ type Ormer interface {
// ... // ...
// err = o.Rollback() // err = o.Rollback()
Begin() error Begin() error
// begin transaction with provided context and option
// the provided context is used until the transaction is committed or rolled back.
// if the context is canceled, the transaction will be rolled back.
// the provided TxOptions is optional and may be nil if defaults should be used.
// if a non-default isolation level is used that the driver doesn't support, an error will be returned.
// for example:
// o := NewOrm()
// err := o.BeginTx(context.Background(), &sql.TxOptions{Isolation: sql.LevelRepeatableRead})
// ...
// err = o.Rollback()
BeginTx(ctx context.Context, opts *sql.TxOptions) error
// commit transaction // commit transaction
Commit() error Commit() error
// rollback transaction // rollback transaction
@ -401,6 +413,7 @@ type dbQuerier interface {
// transaction beginner // transaction beginner
type txer interface { type txer interface {
Begin() (*sql.Tx, error) Begin() (*sql.Tx, error)
BeginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error)
} }
// transaction ending // transaction ending