gormをしらべた(1)
- https://gorm.io/docs/
- gormを使っていたら中身が気になったので追ってみた
- 公式のサンプルからとりあえず追う
dialectsMapはどこで初期化されているか
- gorm.initからRegisterDialectが呼ばれている
- ちなみに、このinitはpostgresなど各種にあり、それぞれ初期化が順に呼ばれる
func init() { RegisterDialect("common", &commonDialect{}) }
- dialectsMapに渡されて初期化されている
// RegisterDialect register new dialect func RegisterDialect(name string, dialect Dialect) { dialectsMap[name] = dialect }
gorm.Open
func Open(dialect string, args ...interface{}) (db *DB, err error) { if len(args) == 0 { err = errors.New("invalid database source") return nil, err } var source string var dbSQL SQLCommon var ownDbSQL bool switch value := args[0].(type) { case string: var driver = dialect if len(args) == 1 { source = value } else if len(args) >= 2 { driver = value source = args[1].(string) } dbSQL, err = sql.Open(driver, source) ownDbSQL = true case SQLCommon: dbSQL = value ownDbSQL = false default: return nil, fmt.Errorf("invalid database source: %v is not a valid type", value) } db = &DB{ db: dbSQL, logger: defaultLogger, callbacks: DefaultCallback, dialect: newDialect(dialect, dbSQL), } db.parent = db if err != nil { return } // Send a ping to make sure the database connection is alive. if d, ok := dbSQL.(*sql.DB); ok { if err = d.Ping(); err != nil && ownDbSQL { d.Close() } } return }
AutoMigrate
// Migrate the schema
db.AutoMigrate(&Product{})
- AutoMigrateの中身がこれ
func (s *DB) AutoMigrate(values ...interface{}) *DB { db := s.Unscoped() for _, value := range values { db = db.NewScope(value).autoMigrate().db } return db }
- Unscopedというメソッドが実行されている
- このメソッドは削除されたレコードを含む全てのレコードを返すようだ
// Unscoped return all record including deleted record, refer Soft Delete https://jinzhu.github.io/gorm/crud.html#soft-delete func (s *DB) Unscoped() *DB { return s.clone().search.unscoped().db }
- 最初のcloneはこう
- 新しいdbオブジェクトを生成している
- 基本的に元のdbの情報を参照している大本は同じ物を参照しているので
- 名前の通りcloneしている
- newDialectはnewしているので別の物がいる
- direct自体はsqlの方言を吸収するための物らしい
func (s *DB) clone() *DB { db := &DB{ db: s.db, parent: s.parent, logger: s.logger, logMode: s.logMode, Value: s.Value, Error: s.Error, blockGlobalUpdate: s.blockGlobalUpdate, dialect: newDialect(s.dialect.GetName(), s.db), nowFuncOverride: s.nowFuncOverride, } s.values.Range(func(k, v interface{}) bool { db.values.Store(k, v) return true }) if s.search == nil { db.search = &search{limit: -1, offset: -1} } else { db.search = s.search.clone() } db.search.db = db return db }
- s.dialect.GetName()はこの場合接続しているのがsqlite3なので
- このようにsqlite3という文字列を返している
func (sqlite3) GetName() string { return "sqlite3" }
- newDialectはsqlite3とdbを引数に取る
- dialectsMapにデータが入っているので
- 特定dbのdirectへ接続情報をもたせて返す
- valuesにはsync.Mapが入っているのでこのデータも新しいdbへ引き継がせる
func newDialect(name string, db SQLCommon) Dialect { if value, ok := dialectsMap[name]; ok { dialect := reflect.New(reflect.TypeOf(value).Elem()).Interface().(Dialect) dialect.SetDB(db) return dialect } fmt.Printf("`%v` is not officially supported, running under compatibility mode.\n", name) commontDialect := &commonDialect{} commontDialect.SetDB(db) return commontDialect }
- unscopedはsearchのUnscopedへtrueを立てて返す
func (s *search) unscoped() *search { s.Unscoped = true return s }
First
- Firstを呼んだ場合
- 最初にNewCospeが呼ばれる
- search.Limitを1に設定
func (s *DB) First(out interface{}, where ...interface{}) *DB { newScope := s.NewScope(out) newScope.Search.Limit(1) return newScope.Set("gorm:order_by_primary_key", "ASC"). inlineCondition(where...).callCallbacks(s.parent.callbacks.queries).db }
NewScope
- 現在実行している命令のスコープを作成して返す
func (s *DB) NewScope(value interface{}) *Scope { dbClone := s.clone() dbClone.Value = value scope := &Scope{db: dbClone, Value: value} if s.search != nil { scope.Search = s.search.clone() } else { scope.Search = &search{} } return scope }
callCallbacks
- callCallbacksはs.parent.callbacks.queriesに設定されているクエリを順に実行する
- queryCallback
- preloadCallback
- afterQueryCallback
func (scope *Scope) callCallbacks(funcs []*func(s *Scope)) *Scope { defer func() { if err := recover(); err != nil { if db, ok := scope.db.db.(sqlTx); ok { db.Rollback() } panic(err) } }() for _, f := range funcs { (*f)(scope) if scope.skipLeft { break } } return scope }
- callbacksはinitで登録されている
// Define callbacks for querying func init() { DefaultCallback.Query().Register("gorm:query", queryCallback) DefaultCallback.Query().Register("gorm:preload", preloadCallback) DefaultCallback.Query().Register("gorm:after_query", afterQueryCallback) }
queryCallback
- ここまでの情報をもってqueryCallbackでクエリを組み立ててリクエストを送っている
// queryCallback used to query data from database func queryCallback(scope *Scope) { if _, skip := scope.InstanceGet("gorm:skip_query_callback"); skip { return } //we are only preloading relations, dont touch base model if _, skip := scope.InstanceGet("gorm:only_preload"); skip { return } defer scope.trace(scope.db.nowFunc()) var ( isSlice, isPtr bool resultType reflect.Type results = scope.IndirectValue() ) if orderBy, ok := scope.Get("gorm:order_by_primary_key"); ok { if primaryField := scope.PrimaryField(); primaryField != nil { scope.Search.Order(fmt.Sprintf("%v.%v %v", scope.QuotedTableName(), scope.Quote(primaryField.DBName), orderBy)) } } if value, ok := scope.Get("gorm:query_destination"); ok { results = indirect(reflect.ValueOf(value)) } if kind := results.Kind(); kind == reflect.Slice { isSlice = true resultType = results.Type().Elem() results.Set(reflect.MakeSlice(results.Type(), 0, 0)) if resultType.Kind() == reflect.Ptr { isPtr = true resultType = resultType.Elem() } } else if kind != reflect.Struct { scope.Err(errors.New("unsupported destination, should be slice or struct")) return } scope.prepareQuerySQL() if !scope.HasError() { scope.db.RowsAffected = 0 if str, ok := scope.Get("gorm:query_option"); ok { scope.SQL += addExtraSpaceIfExist(fmt.Sprint(str)) } if rows, err := scope.SQLDB().Query(scope.SQL, scope.SQLVars...); scope.Err(err) == nil { defer rows.Close() columns, _ := rows.Columns() for rows.Next() { scope.db.RowsAffected++ elem := results if isSlice { elem = reflect.New(resultType).Elem() } scope.scan(rows, columns, scope.New(elem.Addr().Interface()).Fields()) if isSlice { if isPtr { results.Set(reflect.Append(results, elem.Addr())) } else { results.Set(reflect.Append(results, elem)) } } } if err := rows.Err(); err != nil { scope.Err(err) } else if scope.db.RowsAffected == 0 && !isSlice { scope.Err(ErrRecordNotFound) } } } }
気が向いたらもう少し追います