1
0
mirror of https://github.com/astaxie/beego.git synced 2024-06-02 02:43:27 +00:00

refactor orm

This commit is contained in:
jianzhiyao 2020-07-19 23:46:42 +08:00
parent d9c016ed98
commit 32da446eb1
8 changed files with 370 additions and 200 deletions

View File

@ -111,6 +111,9 @@ type DB struct {
stmtDecorators *lru.Cache
}
var _ dbQuerier = new(DB)
var _ txer = new(DB)
func (d *DB) Begin() (*sql.Tx, error) {
return d.DB.Begin()
}
@ -220,6 +223,56 @@ func (d *DB) QueryRowContext(ctx context.Context, query string, args ...interfac
return stmt.QueryRowContext(ctx, args)
}
type TxDB struct {
tx *sql.Tx
}
var _ dbQuerier = new(TxDB)
var _ txEnder = new(TxDB)
func (t *TxDB) Commit() error {
return t.tx.Commit()
}
func (t *TxDB) Rollback() error {
return t.tx.Rollback()
}
var _ dbQuerier = new(TxDB)
var _ txEnder = new(TxDB)
func (t *TxDB) Prepare(query string) (*sql.Stmt, error) {
return t.PrepareContext(context.Background(),query)
}
func (t *TxDB) PrepareContext(ctx context.Context, query string) (*sql.Stmt, error) {
return t.tx.PrepareContext(ctx, query)
}
func (t *TxDB) Exec(query string, args ...interface{}) (sql.Result, error) {
return t.ExecContext(context.Background(), query, args...)
}
func (t *TxDB) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) {
return t.tx.ExecContext(ctx, query, args...)
}
func (t *TxDB) Query(query string, args ...interface{}) (*sql.Rows, error) {
return t.QueryContext(context.Background(),query,args...)
}
func (t *TxDB) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) {
return t.tx.QueryContext(ctx, query, args...)
}
func (t *TxDB) QueryRow(query string, args ...interface{}) *sql.Row {
return t.QueryRowContext(context.Background(),query,args...)
}
func (t *TxDB) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row {
return t.tx.QueryRowContext(ctx, query, args...)
}
type alias struct {
Name string
Driver DriverType

View File

@ -62,6 +62,8 @@ import (
"reflect"
"sync"
"time"
"github.com/astaxie/beego/logs"
)
// DebugQueries define the debug
@ -76,8 +78,7 @@ var (
DefaultRowsLimit = -1
DefaultRelsDepth = 2
DefaultTimeLoc = time.Local
ErrTxHasBegan = errors.New("<Ormer.Begin> transaction already begin")
ErrTxDone = errors.New("<Ormer.Commit/Rollback> transaction not begin")
ErrTxDone = errors.New("<TxOrmer.Commit/Rollback> transaction already done")
ErrMultiRows = errors.New("<QuerySeter> return multi rows")
ErrNoRows = errors.New("<QuerySeter> no row found")
ErrStmtClosed = errors.New("<QuerySeter> stmt already closed")
@ -91,16 +92,16 @@ type Params map[string]interface{}
// ParamsList stores paramslist
type ParamsList []interface{}
type orm struct {
type ormBase struct {
alias *alias
db dbQuerier
isTx bool
}
var _ Ormer = new(orm)
var _ DQL = new(ormBase)
var _ DML = new(ormBase)
// get model info and model reflect value
func (o *orm) getMiInd(md interface{}, needPtr bool) (mi *modelInfo, ind reflect.Value) {
func (o *ormBase) getMiInd(md interface{}, needPtr bool) (mi *modelInfo, ind reflect.Value) {
val := reflect.ValueOf(md)
ind = reflect.Indirect(val)
typ := ind.Type()
@ -115,7 +116,7 @@ func (o *orm) getMiInd(md interface{}, needPtr bool) (mi *modelInfo, ind reflect
}
// get field info from model info by given field name
func (o *orm) getFieldInfo(mi *modelInfo, name string) *fieldInfo {
func (o *ormBase) getFieldInfo(mi *modelInfo, name string) *fieldInfo {
fi, ok := mi.fields.GetByAny(name)
if !ok {
panic(fmt.Errorf("<Ormer> cannot find field `%s` for model `%s`", name, mi.fullName))
@ -124,33 +125,42 @@ func (o *orm) getFieldInfo(mi *modelInfo, name string) *fieldInfo {
}
// read data to model
func (o *orm) Read(md interface{}, cols ...string) error {
func (o *ormBase) Read(md interface{}, cols ...string) error {
return o.ReadWithCtx(context.Background(), md, cols...)
}
func (o *ormBase) ReadWithCtx(ctx context.Context, md interface{}, cols ...string) error {
mi, ind := o.getMiInd(md, true)
return o.alias.DbBaser.Read(o.db, mi, ind, o.alias.TZ, cols, false)
}
// read data to model, like Read(), but use "SELECT FOR UPDATE" form
func (o *orm) ReadForUpdate(md interface{}, cols ...string) error {
func (o *ormBase) ReadForUpdate(md interface{}, cols ...string) error {
return o.ReadForUpdateWithCtx(context.Background(), md, cols...)
}
func (o *ormBase) ReadForUpdateWithCtx(ctx context.Context, md interface{}, cols ...string) error {
mi, ind := o.getMiInd(md, true)
return o.alias.DbBaser.Read(o.db, mi, ind, o.alias.TZ, cols, true)
}
// Try to read a row from the database, or insert one if it doesn't exist
func (o *orm) ReadOrCreate(md interface{}, col1 string, cols ...string) (bool, int64, error) {
func (o *ormBase) ReadOrCreate(md interface{}, col1 string, cols ...string) (bool, int64, error) {
return o.ReadOrCreateWithCtx(context.Background(), md, col1, cols...)
}
func (o *ormBase) ReadOrCreateWithCtx(ctx context.Context, md interface{}, col1 string, cols ...string) (bool, int64, error) {
cols = append([]string{col1}, cols...)
mi, ind := o.getMiInd(md, true)
err := o.alias.DbBaser.Read(o.db, mi, ind, o.alias.TZ, cols, false)
if err == ErrNoRows {
// Create
id, err := o.Insert(md)
return (err == nil), id, err
id, err := o.InsertWithCtx(ctx, md)
return err == nil, id, err
}
id, vid := int64(0), ind.FieldByIndex(mi.fields.pk.fieldIndex)
if mi.fields.pk.fieldType&IsPositiveIntegerField > 0 {
id = int64(vid.Uint())
} else if mi.fields.pk.rel {
return o.ReadOrCreate(vid.Interface(), mi.fields.pk.relModelInfo.fields.pk.name)
return o.ReadOrCreateWithCtx(ctx, vid.Interface(), mi.fields.pk.relModelInfo.fields.pk.name)
} else {
id = vid.Int()
}
@ -159,7 +169,10 @@ func (o *orm) ReadOrCreate(md interface{}, col1 string, cols ...string) (bool, i
}
// insert model data to database
func (o *orm) Insert(md interface{}) (int64, error) {
func (o *ormBase) Insert(md interface{}) (int64, error) {
return o.InsertWithCtx(context.Background(), md)
}
func (o *ormBase) InsertWithCtx(ctx context.Context, md interface{}) (int64, error) {
mi, ind := o.getMiInd(md, true)
id, err := o.alias.DbBaser.Insert(o.db, mi, ind, o.alias.TZ)
if err != nil {
@ -172,7 +185,7 @@ func (o *orm) Insert(md interface{}) (int64, error) {
}
// set auto pk field
func (o *orm) setPk(mi *modelInfo, ind reflect.Value, id int64) {
func (o *ormBase) setPk(mi *modelInfo, ind reflect.Value, id int64) {
if mi.fields.pk.auto {
if mi.fields.pk.fieldType&IsPositiveIntegerField > 0 {
ind.FieldByIndex(mi.fields.pk.fieldIndex).SetUint(uint64(id))
@ -183,7 +196,10 @@ func (o *orm) setPk(mi *modelInfo, ind reflect.Value, id int64) {
}
// insert some models to database
func (o *orm) InsertMulti(bulk int, mds interface{}) (int64, error) {
func (o *ormBase) InsertMulti(bulk int, mds interface{}) (int64, error) {
return o.InsertMultiWithCtx(context.Background(), bulk, mds)
}
func (o *ormBase) InsertMultiWithCtx(ctx context.Context, bulk int, mds interface{}) (int64, error) {
var cnt int64
sind := reflect.Indirect(reflect.ValueOf(mds))
@ -218,7 +234,10 @@ func (o *orm) InsertMulti(bulk int, mds interface{}) (int64, error) {
}
// InsertOrUpdate data to database
func (o *orm) InsertOrUpdate(md interface{}, colConflitAndArgs ...string) (int64, error) {
func (o *ormBase) InsertOrUpdate(md interface{}, colConflictAndArgs ...string) (int64, error) {
return o.InsertOrUpdateWithCtx(context.Background(), md, colConflictAndArgs...)
}
func (o *ormBase) InsertOrUpdateWithCtx(ctx context.Context, md interface{}, colConflitAndArgs ...string) (int64, error) {
mi, ind := o.getMiInd(md, true)
id, err := o.alias.DbBaser.InsertOrUpdate(o.db, mi, ind, o.alias, colConflitAndArgs...)
if err != nil {
@ -232,14 +251,20 @@ func (o *orm) InsertOrUpdate(md interface{}, colConflitAndArgs ...string) (int64
// update model to database.
// cols set the columns those want to update.
func (o *orm) Update(md interface{}, cols ...string) (int64, error) {
func (o *ormBase) Update(md interface{}, cols ...string) (int64, error) {
return o.UpdateWithCtx(context.Background(), md, cols...)
}
func (o *ormBase) UpdateWithCtx(ctx context.Context, md interface{}, cols ...string) (int64, error) {
mi, ind := o.getMiInd(md, true)
return o.alias.DbBaser.Update(o.db, mi, ind, o.alias.TZ, cols)
}
// delete model in database
// cols shows the delete conditions values read from. default is pk
func (o *orm) Delete(md interface{}, cols ...string) (int64, error) {
func (o *ormBase) Delete(md interface{}, cols ...string) (int64, error) {
return o.DeleteWithCtx(context.Background(), md, cols...)
}
func (o *ormBase) DeleteWithCtx(ctx context.Context, md interface{}, cols ...string) (int64, error) {
mi, ind := o.getMiInd(md, true)
num, err := o.alias.DbBaser.Delete(o.db, mi, ind, o.alias.TZ, cols)
if err != nil {
@ -252,7 +277,10 @@ func (o *orm) Delete(md interface{}, cols ...string) (int64, error) {
}
// create a models to models queryer
func (o *orm) QueryM2M(md interface{}, name string) QueryM2Mer {
func (o *ormBase) QueryM2M(md interface{}, name string) QueryM2Mer {
return o.QueryM2MWithCtx(context.Background(), md, name)
}
func (o *ormBase) QueryM2MWithCtx(ctx context.Context, md interface{}, name string) QueryM2Mer {
mi, ind := o.getMiInd(md, true)
fi := o.getFieldInfo(mi, name)
@ -274,7 +302,10 @@ func (o *orm) QueryM2M(md interface{}, name string) QueryM2Mer {
// for _,tag := range post.Tags{...}
//
// make sure the relation is defined in model struct tags.
func (o *orm) LoadRelated(md interface{}, name string, args ...interface{}) (int64, error) {
func (o *ormBase) LoadRelated(md interface{}, name string, args ...interface{}) (int64, error) {
return o.LoadRelatedWithCtx(context.Background(), md, name, args...)
}
func (o *ormBase) LoadRelatedWithCtx(ctx context.Context, md interface{}, name string, args ...interface{}) (int64, error) {
_, fi, ind, qseter := o.queryRelated(md, name)
qs := qseter.(*querySet)
@ -341,14 +372,17 @@ func (o *orm) LoadRelated(md interface{}, name string, args ...interface{}) (int
// qs := orm.QueryRelated(post,"Tag")
// qs.All(&[]*Tag{})
//
func (o *orm) QueryRelated(md interface{}, name string) QuerySeter {
func (o *ormBase) QueryRelated(md interface{}, name string) QuerySeter {
return o.QueryRelatedWithCtx(context.Background(), md, name)
}
func (o *ormBase) QueryRelatedWithCtx(ctx context.Context, md interface{}, name string) QuerySeter {
// is this api needed ?
_, _, _, qs := o.queryRelated(md, name)
return qs
}
// get QuerySeter for related models to md model
func (o *orm) queryRelated(md interface{}, name string) (*modelInfo, *fieldInfo, reflect.Value, QuerySeter) {
func (o *ormBase) queryRelated(md interface{}, name string) (*modelInfo, *fieldInfo, reflect.Value, QuerySeter) {
mi, ind := o.getMiInd(md, true)
fi := o.getFieldInfo(mi, name)
@ -380,7 +414,7 @@ func (o *orm) queryRelated(md interface{}, name string) (*modelInfo, *fieldInfo,
}
// get reverse relation QuerySeter
func (o *orm) getReverseQs(md interface{}, mi *modelInfo, fi *fieldInfo) *querySet {
func (o *ormBase) getReverseQs(md interface{}, mi *modelInfo, fi *fieldInfo) *querySet {
switch fi.fieldType {
case RelReverseOne, RelReverseMany:
default:
@ -401,7 +435,7 @@ func (o *orm) getReverseQs(md interface{}, mi *modelInfo, fi *fieldInfo) *queryS
}
// get relation QuerySeter
func (o *orm) getRelQs(md interface{}, mi *modelInfo, fi *fieldInfo) *querySet {
func (o *ormBase) getRelQs(md interface{}, mi *modelInfo, fi *fieldInfo) *querySet {
switch fi.fieldType {
case RelOneToOne, RelForeignKey, RelManyToMany:
default:
@ -423,7 +457,10 @@ func (o *orm) getRelQs(md interface{}, mi *modelInfo, fi *fieldInfo) *querySet {
// return a QuerySeter for table operations.
// table name can be string or struct.
// e.g. QueryTable("user"), QueryTable(&user{}) or QueryTable((*User)(nil)),
func (o *orm) QueryTable(ptrStructOrTableName interface{}) (qs QuerySeter) {
func (o *ormBase) QueryTable(ptrStructOrTableName interface{}) (qs QuerySeter) {
return o.QueryTableWithCtx(context.Background(), ptrStructOrTableName)
}
func (o *ormBase) QueryTableWithCtx(ctx context.Context, ptrStructOrTableName interface{}) (qs QuerySeter) {
var name string
if table, ok := ptrStructOrTableName.(string); ok {
name = nameStrategyMap[defaultNameStrategy](table)
@ -442,11 +479,136 @@ func (o *orm) QueryTable(ptrStructOrTableName interface{}) (qs QuerySeter) {
return
}
// switch to another registered database driver by given name.
func (o *orm) Using(name string) error {
if o.isTx {
panic(fmt.Errorf("<Ormer.Using> transaction has been start, cannot change db"))
// return a raw query seter for raw sql string.
func (o *ormBase) Raw(query string, args ...interface{}) RawSeter {
return o.RawWithCtx(context.Background(), query, args...)
}
func (o *ormBase) RawWithCtx(ctx context.Context, query string, args ...interface{}) RawSeter {
return newRawSet(o, query, args)
}
// return current using database Driver
func (o *ormBase) Driver() Driver {
return driver(o.alias.Name)
}
// return sql.DBStats for current database
func (o *ormBase) DBStats() *sql.DBStats {
if o.alias != nil && o.alias.DB != nil {
stats := o.alias.DB.DB.Stats()
return &stats
}
return nil
}
type orm struct {
ormBase
}
var _ Ormer = new(orm)
func (o *orm) Begin() (TxOrmer, error) {
return o.BeginWithCtx(context.Background())
}
func (o *orm) BeginWithCtx(ctx context.Context) (TxOrmer, error) {
return o.BeginWithCtxAndOpts(ctx, nil)
}
func (o *orm) BeginWithOpts(opts *sql.TxOptions) (TxOrmer, error) {
return o.BeginWithCtxAndOpts(context.Background(), opts)
}
func (o *orm) BeginWithCtxAndOpts(ctx context.Context, opts *sql.TxOptions) (TxOrmer, error) {
tx, err := o.db.(txer).BeginTx(ctx, opts)
if err != nil {
return nil, err
}
_txOrm := &txOrm{
ormBase: ormBase{
alias: o.alias,
db: &TxDB{tx: tx},
},
isClosed: false,
}
var taskTxOrm TxOrmer = _txOrm
return taskTxOrm, nil
}
func (o *orm) DoTx(task func(txOrm TxOrmer) error) error {
return o.DoTxWithCtx(context.Background(), task)
}
func (o *orm) DoTxWithCtx(ctx context.Context, task func(txOrm TxOrmer) error) error {
return o.DoTxWithCtxAndOpts(ctx, nil, task)
}
func (o *orm) DoTxWithOpts(opts *sql.TxOptions, task func(txOrm TxOrmer) error) error {
return o.DoTxWithCtxAndOpts(context.Background(), opts, task)
}
func (o *orm) DoTxWithCtxAndOpts(ctx context.Context, opts *sql.TxOptions, task func(txOrm TxOrmer) error) error {
_txOrm, err := o.BeginWithCtxAndOpts(ctx, opts)
if err != nil {
return err
}
panicked := true
defer func() {
if panicked || err != nil {
e := _txOrm.Rollback()
logs.Error("rollback transaction failed: %v", e)
} else {
e := _txOrm.Commit()
logs.Error("commit transaction failed: %v", e)
}
}()
var taskTxOrm = _txOrm
err = task(taskTxOrm)
panicked = false
return err
}
type txOrm struct {
ormBase
isClosed bool
closeMutex sync.Mutex
}
var _ TxOrmer = new(txOrm)
func (t *txOrm) Commit() error {
t.closeMutex.Lock()
defer t.closeMutex.Unlock()
if t.isClosed {
return ErrTxDone
}
t.isClosed = true
return t.db.(txEnder).Commit()
}
func (t *txOrm) Rollback() error {
t.closeMutex.Lock()
defer t.closeMutex.Unlock()
if t.isClosed {
return ErrTxDone
}
t.isClosed = true
return t.db.(txEnder).Rollback()
}
// NewOrm create new orm
func NewOrm() Ormer {
BootStrap() // execute only once
o := new(orm)
name := `default`
if al, ok := dataBaseCache.get(name); ok {
o.alias = al
if Debug {
@ -455,92 +617,9 @@ func (o *orm) Using(name string) error {
o.db = al.DB
}
} else {
return fmt.Errorf("<Ormer.Using> unknown db alias name `%s`", name)
panic(fmt.Errorf("<Ormer.Using> unknown db alias name `%s`", name))
}
return nil
}
// begin transaction
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 {
return ErrTxHasBegan
}
var tx *sql.Tx
tx, err := o.db.(txer).BeginTx(ctx, opts)
if err != nil {
return err
}
o.isTx = true
if Debug {
o.db.(*dbQueryLog).SetDB(tx)
} else {
o.db = tx
}
return nil
}
// commit transaction
func (o *orm) Commit() error {
if !o.isTx {
return ErrTxDone
}
err := o.db.(txEnder).Commit()
if err == nil {
o.isTx = false
o.Using(o.alias.Name)
} else if err == sql.ErrTxDone {
return ErrTxDone
}
return err
}
// rollback transaction
func (o *orm) Rollback() error {
if !o.isTx {
return ErrTxDone
}
err := o.db.(txEnder).Rollback()
if err == nil {
o.isTx = false
o.Using(o.alias.Name)
} else if err == sql.ErrTxDone {
return ErrTxDone
}
return err
}
// return a raw query seter for raw sql string.
func (o *orm) Raw(query string, args ...interface{}) RawSeter {
return newRawSet(o, query, args)
}
// return current using database Driver
func (o *orm) Driver() Driver {
return driver(o.alias.Name)
}
// return sql.DBStats for current database
func (o *orm) DBStats() *sql.DBStats {
if o.alias != nil && o.alias.DB != nil {
stats := o.alias.DB.DB.Stats()
return &stats
}
return nil
}
// NewOrm create new orm
func NewOrm() Ormer {
BootStrap() // execute only once
o := new(orm)
err := o.Using("default")
if err != nil {
panic(err)
}
return o
}

View File

@ -22,7 +22,7 @@ import (
// an insert queryer struct
type insertSet struct {
mi *modelInfo
orm *orm
orm *ormBase
stmt stmtQuerier
closed bool
}
@ -70,7 +70,7 @@ func (o *insertSet) Close() error {
}
// create new insert queryer.
func newInsertSet(orm *orm, mi *modelInfo) (Inserter, error) {
func newInsertSet(orm *ormBase, mi *modelInfo) (Inserter, error) {
bi := new(insertSet)
bi.orm = orm
bi.mi = mi

View File

@ -129,7 +129,7 @@ func (o *queryM2M) Count() (int64, error) {
var _ QueryM2Mer = new(queryM2M)
// create new M2M queryer.
func newQueryM2M(md interface{}, o *orm, mi *modelInfo, fi *fieldInfo, ind reflect.Value) QueryM2Mer {
func newQueryM2M(md interface{}, o *ormBase, mi *modelInfo, fi *fieldInfo, ind reflect.Value) QueryM2Mer {
qm2m := new(queryM2M)
qm2m.md = md
qm2m.mi = mi

View File

@ -72,7 +72,7 @@ type querySet struct {
orders []string
distinct bool
forupdate bool
orm *orm
orm *ormBase
ctx context.Context
forContext bool
}
@ -292,7 +292,7 @@ func (o querySet) WithContext(ctx context.Context) QuerySeter {
}
// create new QuerySeter.
func newQuerySet(orm *orm, mi *modelInfo) QuerySeter {
func newQuerySet(orm *ormBase, mi *modelInfo) QuerySeter {
o := new(querySet)
o.mi = mi
o.orm = orm

View File

@ -63,7 +63,7 @@ func newRawPreparer(rs *rawSet) (RawPreparer, error) {
type rawSet struct {
query string
args []interface{}
orm *orm
orm *ormBase
}
var _ RawSeter = new(rawSet)
@ -858,7 +858,7 @@ func (o *rawSet) Prepare() (RawPreparer, error) {
return newRawPreparer(o)
}
func newRawSet(orm *orm, query string, args []interface{}) RawSeter {
func newRawSet(orm *ormBase, query string, args []interface{}) RawSeter {
o := new(rawSet)
o.query = query
o.args = args

View File

@ -2026,24 +2026,24 @@ func TestTransaction(t *testing.T) {
// this test worked when database support transaction
o := NewOrm()
err := o.Begin()
to, err := o.Begin()
throwFail(t, err)
var names = []string{"1", "2", "3"}
var tag Tag
tag.Name = names[0]
id, err := o.Insert(&tag)
id, err := to.Insert(&tag)
throwFail(t, err)
throwFail(t, AssertIs(id > 0, true))
num, err := o.QueryTable("tag").Filter("name", "golang").Update(Params{"name": names[1]})
num, err := to.QueryTable("tag").Filter("name", "golang").Update(Params{"name": names[1]})
throwFail(t, err)
throwFail(t, AssertIs(num, 1))
switch {
case IsMysql || IsSqlite:
res, err := o.Raw("INSERT INTO tag (name) VALUES (?)", names[2]).Exec()
res, err := to.Raw("INSERT INTO tag (name) VALUES (?)", names[2]).Exec()
throwFail(t, err)
if err == nil {
id, err = res.LastInsertId()
@ -2052,22 +2052,22 @@ func TestTransaction(t *testing.T) {
}
}
err = o.Rollback()
err = to.Rollback()
throwFail(t, err)
num, err = o.QueryTable("tag").Filter("name__in", names).Count()
throwFail(t, err)
throwFail(t, AssertIs(num, 0))
err = o.Begin()
to, err = o.Begin()
throwFail(t, err)
tag.Name = "commit"
id, err = o.Insert(&tag)
id, err = to.Insert(&tag)
throwFail(t, err)
throwFail(t, AssertIs(id > 0, true))
o.Commit()
to.Commit()
throwFail(t, err)
num, err = o.QueryTable("tag").Filter("name", "commit").Delete()
@ -2086,15 +2086,15 @@ func TestTransactionIsolationLevel(t *testing.T) {
o2 := NewOrm()
// start two transaction with isolation level repeatable read
err := o1.BeginTx(context.Background(), &sql.TxOptions{Isolation: sql.LevelRepeatableRead})
to1, err := o1.BeginWithCtxAndOpts(context.Background(), &sql.TxOptions{Isolation: sql.LevelRepeatableRead})
throwFail(t, err)
err = o2.BeginTx(context.Background(), &sql.TxOptions{Isolation: sql.LevelRepeatableRead})
to2, err := o2.BeginWithCtxAndOpts(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)
id, err := to1.Insert(&tag)
throwFail(t, err)
throwFail(t, AssertIs(id > 0, true))
@ -2104,15 +2104,15 @@ func TestTransactionIsolationLevel(t *testing.T) {
throwFail(t, AssertIs(num, 0))
// o1 commit
o1.Commit()
to1.Commit()
// o2 query tag table, still no result
num, err = o2.QueryTable("tag").Filter("name", "test-transaction").Count()
num, err = to2.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()
to2.Commit()
num, err = o2.QueryTable("tag").Filter("name", "test-transaction").Count()
throwFail(t, err)
throwFail(t, AssertIs(num, 1))
@ -2125,14 +2125,14 @@ func TestTransactionIsolationLevel(t *testing.T) {
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"})
to, _ := o.BeginWithCtx(ctx)
id, err := to.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()
err = to.Commit()
throwFail(t, AssertIs(err, context.Canceled))
}

View File

@ -35,35 +35,43 @@ type Fielder interface {
RawValue() interface{}
}
// Ormer define the orm interface
type Ormer interface {
// read data to model
// for example:
// this will find User by Id field
// u = &User{Id: user.Id}
// err = Ormer.Read(u)
// this will find User by UserName field
// u = &User{UserName: "astaxie", Password: "pass"}
// err = Ormer.Read(u, "UserName")
Read(md interface{}, cols ...string) error
// Like Read(), but with "FOR UPDATE" clause, useful in transaction.
// Some databases are not support this feature.
ReadForUpdate(md interface{}, cols ...string) error
// Try to read a row from the database, or insert one if it doesn't exist
ReadOrCreate(md interface{}, col1 string, cols ...string) (bool, int64, error)
type TxBeginner interface {
//self control transaction
Begin() (TxOrmer, error)
BeginWithCtx(ctx context.Context) (TxOrmer, error)
BeginWithOpts(opts *sql.TxOptions) (TxOrmer, error)
BeginWithCtxAndOpts(ctx context.Context, opts *sql.TxOptions) (TxOrmer, error)
//closure control transaction
DoTx(task func(txOrm TxOrmer) error) error
DoTxWithCtx(ctx context.Context, task func(txOrm TxOrmer) error) error
DoTxWithOpts(opts *sql.TxOptions, task func(txOrm TxOrmer) error) error
DoTxWithCtxAndOpts(ctx context.Context, opts *sql.TxOptions, task func(txOrm TxOrmer) error) error
}
type TxCommitter interface {
Commit() error
Rollback() error
}
//Data Manipulation Language
type DML interface {
// insert model data to database
// for example:
// user := new(User)
// id, err = Ormer.Insert(user)
// user must be a pointer and Insert will set user's pk field
Insert(interface{}) (int64, error)
Insert(md interface{}) (int64, error)
InsertWithCtx(ctx context.Context, md interface{}) (int64, error)
// mysql:InsertOrUpdate(model) or InsertOrUpdate(model,"colu=colu+value")
// if colu type is integer : can use(+-*/), string : convert(colu,"value")
// postgres: InsertOrUpdate(model,"conflictColumnName") or InsertOrUpdate(model,"conflictColumnName","colu=colu+value")
// if colu type is integer : can use(+-*/), string : colu || "value"
InsertOrUpdate(md interface{}, colConflitAndArgs ...string) (int64, error)
InsertOrUpdateWithCtx(ctx context.Context, md interface{}, colConflitAndArgs ...string) (int64, error)
// insert some models to database
InsertMulti(bulk int, mds interface{}) (int64, error)
InsertMultiWithCtx(ctx context.Context, bulk int, mds interface{}) (int64, error)
// update model to database.
// cols set the columns those want to update.
// find model by Id(pk) field and update columns specified by fields, if cols is null then update all columns
@ -74,63 +82,93 @@ type Ormer interface {
// user.Extra.Data = "orm"
// num, err = Ormer.Update(&user, "Langs", "Extra")
Update(md interface{}, cols ...string) (int64, error)
UpdateWithCtx(ctx context.Context, md interface{}, cols ...string) (int64, error)
// delete model in database
Delete(md interface{}, cols ...string) (int64, error)
DeleteWithCtx(ctx context.Context, md interface{}, cols ...string) (int64, error)
// return a raw query seter for raw sql string.
// for example:
// ormer.Raw("UPDATE `user` SET `user_name` = ? WHERE `user_name` = ?", "slene", "testing").Exec()
// // update user testing's name to slene
Raw(query string, args ...interface{}) RawSeter
RawWithCtx(ctx context.Context, query string, args ...interface{}) RawSeter
}
// Data Query Language
type DQL interface {
// read data to model
// for example:
// this will find User by Id field
// u = &User{Id: user.Id}
// err = Ormer.Read(u)
// this will find User by UserName field
// u = &User{UserName: "astaxie", Password: "pass"}
// err = Ormer.Read(u, "UserName")
Read(md interface{}, cols ...string) error
ReadWithCtx(ctx context.Context, md interface{}, cols ...string) error
// Like Read(), but with "FOR UPDATE" clause, useful in transaction.
// Some databases are not support this feature.
ReadForUpdate( md interface{}, cols ...string) error
ReadForUpdateWithCtx(ctx context.Context, md interface{}, cols ...string) error
// Try to read a row from the database, or insert one if it doesn't exist
ReadOrCreate(md interface{}, col1 string, cols ...string) (bool, int64, error)
ReadOrCreateWithCtx(ctx context.Context, md interface{}, col1 string, cols ...string) (bool, int64, error)
// load related models to md model.
// args are limit, offset int and order string.
//
// example:
// Ormer.LoadRelated(post,"Tags")
// for _,tag := range post.Tags{...}
//args[0] bool true useDefaultRelsDepth ; false depth 0
//args[0] int loadRelationDepth
//args[1] int limit default limit 1000
//args[2] int offset default offset 0
//args[3] string order for example : "-Id"
// args[0] bool true useDefaultRelsDepth ; false depth 0
// args[0] int loadRelationDepth
// args[1] int limit default limit 1000
// args[2] int offset default offset 0
// args[3] string order for example : "-Id"
// make sure the relation is defined in model struct tags.
LoadRelated(md interface{}, name string, args ...interface{}) (int64, error)
LoadRelated( md interface{}, name string, args ...interface{}) (int64, error)
LoadRelatedWithCtx(ctx context.Context, md interface{}, name string, args ...interface{}) (int64, error)
// create a models to models queryer
// for example:
// post := Post{Id: 4}
// m2m := Ormer.QueryM2M(&post, "Tags")
QueryM2M(md interface{}, name string) QueryM2Mer
QueryM2M( md interface{}, name string) QueryM2Mer
QueryM2MWithCtx(ctx context.Context, md interface{}, name string) QueryM2Mer
// return a QuerySeter for table operations.
// table name can be string or struct.
// e.g. QueryTable("user"), QueryTable(&user{}) or QueryTable((*User)(nil)),
QueryTable(ptrStructOrTableName interface{}) QuerySeter
QueryTableWithCtx(ctx context.Context, ptrStructOrTableName interface{}) QuerySeter
// switch to another registered database driver by given name.
Using(name string) error
// begin transaction
// for example:
// o := NewOrm()
// err := o.Begin()
// ...
// err = o.Rollback()
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() error
// rollback transaction
Rollback() error
// return a raw query seter for raw sql string.
// for example:
// ormer.Raw("UPDATE `user` SET `user_name` = ? WHERE `user_name` = ?", "slene", "testing").Exec()
// // update user testing's name to slene
Raw(query string, args ...interface{}) RawSeter
Driver() Driver
// Using(name string) error
DBStats() *sql.DBStats
}
type DriverGetter interface {
Driver() Driver
}
type Ormer interface {
DQL
DML
DriverGetter
TxBeginner
}
type TxOrmer interface {
DQL
DML
DriverGetter
TxCommitter
}
// Inserter insert prepared statement
type Inserter interface {
Insert(interface{}) (int64, error)
@ -229,7 +267,7 @@ type QuerySeter interface {
// }) // user slene's name will change to slene2
Update(values Params) (int64, error)
// delete from table
//for example:
// for example:
// num ,err = qs.Filter("user_name__in", "testing1", "testing2").Delete()
// //delete two user who's name is testing1 or testing2
Delete() (int64, error)
@ -314,8 +352,8 @@ type QueryM2Mer interface {
// remove models following the origin model relationship
// only delete rows from m2m table
// for example:
//tag3 := &Tag{Id:5,Name: "TestTag3"}
//num, err = m2m.Remove(tag3)
// tag3 := &Tag{Id:5,Name: "TestTag3"}
// num, err = m2m.Remove(tag3)
Remove(...interface{}) (int64, error)
// check model is existed in relationship of origin model
Exist(interface{}) bool
@ -337,10 +375,10 @@ type RawPreparer interface {
// sql := fmt.Sprintf("SELECT %sid%s,%sname%s FROM %suser%s WHERE id = ?",Q,Q,Q,Q,Q,Q)
// rs := Ormer.Raw(sql, 1)
type RawSeter interface {
//execute sql and get result
// execute sql and get result
Exec() (sql.Result, error)
//query data and map to container
//for example:
// query data and map to container
// for example:
// var name string
// var id int
// rs.QueryRow(&id,&name) // id==2 name=="slene"
@ -396,11 +434,11 @@ type RawSeter interface {
type stmtQuerier interface {
Close() error
Exec(args ...interface{}) (sql.Result, error)
//ExecContext(ctx context.Context, args ...interface{}) (sql.Result, error)
// ExecContext(ctx context.Context, args ...interface{}) (sql.Result, error)
Query(args ...interface{}) (*sql.Rows, error)
//QueryContext(args ...interface{}) (*sql.Rows, error)
// QueryContext(args ...interface{}) (*sql.Rows, error)
QueryRow(args ...interface{}) *sql.Row
//QueryRowContext(ctx context.Context, args ...interface{}) *sql.Row
// QueryRowContext(ctx context.Context, args ...interface{}) *sql.Row
}
// db querier