refactor orm
This commit is contained in:
		
							parent
							
								
									d9c016ed98
								
							
						
					
					
						commit
						32da446eb1
					
				| @ -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 | ||||
|  | ||||
							
								
								
									
										307
									
								
								pkg/orm/orm.go
									
									
									
									
									
								
							
							
						
						
									
										307
									
								
								pkg/orm/orm.go
									
									
									
									
									
								
							| @ -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) | ||||
| 	} | ||||
| 	return nil | ||||
| 		panic(fmt.Errorf("<Ormer.Using> unknown db alias name `%s`", name)) | ||||
| 	} | ||||
| 
 | ||||
| // 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 | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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)) | ||||
| } | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										130
									
								
								pkg/orm/types.go
									
									
									
									
									
								
							
							
						
						
									
										130
									
								
								pkg/orm/types.go
									
									
									
									
									
								
							| @ -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,8 +82,41 @@ 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. | ||||
| 	// | ||||
| @ -89,48 +130,45 @@ type Ormer interface { | ||||
| 	// 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) | ||||
| 	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 | ||||
| 	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) | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user