From 06ed6acde1d9cbb0d45fc851befe43089d9a5111 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 24 Feb 2020 13:28:02 +0800 Subject: [PATCH 1/4] Move core as a sub package --- .gitignore | 2 + cache_test.go | 8 +- caches/cache.go | 99 + cache_lru.go => caches/cache_lru.go | 16 +- cache_lru_test.go => caches/cache_lru_test.go | 6 +- .../cache_memory_store.go | 6 +- .../cache_memory_store_test.go | 2 +- convert.go | 7 + core/db.go | 229 ++ core/db_test.go | 684 +++++ core/error.go | 14 + core/rows.go | 338 +++ core/scan.go | 66 + core/stmt.go | 166 ++ core/tx.go | 153 ++ dialects/dialect.go | 410 +++ dialects/driver.go | 31 + dialects/filter.go | 95 + dialects/filter_test.go | 39 + dialect_mssql.go => dialects/mssql.go | 117 +- .../mssql_test.go | 10 +- dialect_mysql.go => dialects/mysql.go | 135 +- dialect_oracle.go => dialects/oracle.go | 103 +- dialect_postgres.go => dialects/postgres.go | 173 +- .../postgres_test.go | 19 +- dialect_sqlite3.go => dialects/sqlite3.go | 93 +- .../sqlite3_test.go | 2 +- doc.go | 2 +- docs/images/cache_design.graffle | 2295 ----------------- docs/images/cache_design.png | Bin 243596 -> 0 bytes engine.go | 175 +- engine_cond.go | 12 +- engine_group.go | 16 +- engine_table.go | 16 +- examples/cache/cache.go | 5 +- examples/cache_gorountine/cache_goroutine.go | 5 +- helpers.go | 24 +- interface.go | 36 +- logger.go => log/logger.go | 98 +- syslogger.go => log/syslogger.go | 14 +- migrate/migrate.go | 4 +- names/mapper.go | 258 ++ names/mapper_test.go | 49 + table_name.go => names/table_name.go | 15 +- .../table_name_test.go | 54 +- rows.go | 2 +- schemas/column.go | 117 + schemas/index.go | 72 + schemas/pk.go | 30 + schemas/pk_test.go | 36 + schemas/table.go | 156 ++ schemas/table_test.go | 111 + schemas/type.go | 325 +++ session.go | 73 +- session_cols.go | 8 +- session_cols_test.go | 4 +- session_convert.go | 82 +- session_delete.go | 25 +- session_delete_test.go | 9 +- session_exist.go | 10 +- session_find.go | 25 +- session_find_test.go | 6 +- session_get.go | 13 +- session_get_test.go | 8 +- session_insert.go | 22 +- session_pk_test.go | 28 +- session_query.go | 7 +- session_query_test.go | 10 +- session_raw.go | 2 +- session_schema.go | 60 +- session_schema_test.go | 2 +- session_tx_test.go | 4 +- session_update.go | 25 +- session_update_test.go | 6 +- statement.go | 71 +- statement_args.go | 6 +- statement_test.go | 12 +- tag.go | 43 +- tag_extends_test.go | 14 +- tag_id_test.go | 6 +- tag_test.go | 4 +- types.go | 6 +- types_test.go | 12 +- xorm.go | 57 +- xorm_test.go | 34 +- 85 files changed, 4450 insertions(+), 3194 deletions(-) create mode 100644 caches/cache.go rename cache_lru.go => caches/cache_lru.go (94%) rename cache_lru_test.go => caches/cache_lru_test.go (94%) rename cache_memory_store.go => caches/cache_memory_store.go (93%) rename cache_memory_store_test.go => caches/cache_memory_store_test.go (97%) create mode 100644 core/db.go create mode 100644 core/db_test.go create mode 100644 core/error.go create mode 100644 core/rows.go create mode 100644 core/scan.go create mode 100644 core/stmt.go create mode 100644 core/tx.go create mode 100644 dialects/dialect.go create mode 100644 dialects/driver.go create mode 100644 dialects/filter.go create mode 100644 dialects/filter_test.go rename dialect_mssql.go => dialects/mssql.go (86%) rename dialect_mssql_test.go => dialects/mssql_test.go (84%) rename dialect_mysql.go => dialects/mysql.go (86%) rename dialect_oracle.go => dialects/oracle.go (90%) rename dialect_postgres.go => dialects/postgres.go (91%) rename dialect_postgres_test.go => dialects/postgres_test.go (92%) rename dialect_sqlite3.go => dialects/sqlite3.go (83%) rename dialect_sqlite3_test.go => dialects/sqlite3_test.go (97%) delete mode 100644 docs/images/cache_design.graffle delete mode 100644 docs/images/cache_design.png rename logger.go => log/logger.go (67%) rename syslogger.go => log/syslogger.go (88%) create mode 100644 names/mapper.go create mode 100644 names/mapper_test.go rename table_name.go => names/table_name.go (70%) rename table_name_test.go => names/table_name_test.go (55%) create mode 100644 schemas/column.go create mode 100644 schemas/index.go create mode 100644 schemas/pk.go create mode 100644 schemas/pk_test.go create mode 100644 schemas/table.go create mode 100644 schemas/table_test.go create mode 100644 schemas/type.go diff --git a/.gitignore b/.gitignore index 3629e49a..0d321a6b 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,5 @@ xorm.test test.db.sql .idea/ + +*coverage.out diff --git a/cache_test.go b/cache_test.go index 26d7ac68..1606d5bc 100644 --- a/cache_test.go +++ b/cache_test.go @@ -8,6 +8,8 @@ import ( "testing" "time" + "xorm.io/xorm/caches" + "github.com/stretchr/testify/assert" ) @@ -21,7 +23,7 @@ func TestCacheFind(t *testing.T) { } oldCacher := testEngine.GetDefaultCacher() - cacher := NewLRUCacher2(NewMemoryStore(), time.Hour, 10000) + cacher := caches.NewLRUCacher2(caches.NewMemoryStore(), time.Hour, 10000) testEngine.SetDefaultCacher(cacher) assert.NoError(t, testEngine.Sync2(new(MailBox))) @@ -96,7 +98,7 @@ func TestCacheFind2(t *testing.T) { } oldCacher := testEngine.GetDefaultCacher() - cacher := NewLRUCacher2(NewMemoryStore(), time.Hour, 10000) + cacher := caches.NewLRUCacher2(caches.NewMemoryStore(), time.Hour, 10000) testEngine.SetDefaultCacher(cacher) assert.NoError(t, testEngine.Sync2(new(MailBox2))) @@ -147,7 +149,7 @@ func TestCacheGet(t *testing.T) { } oldCacher := testEngine.GetDefaultCacher() - cacher := NewLRUCacher2(NewMemoryStore(), time.Hour, 10000) + cacher := caches.NewLRUCacher2(caches.NewMemoryStore(), time.Hour, 10000) testEngine.SetDefaultCacher(cacher) assert.NoError(t, testEngine.Sync2(new(MailBox3))) diff --git a/caches/cache.go b/caches/cache.go new file mode 100644 index 00000000..7b80eb88 --- /dev/null +++ b/caches/cache.go @@ -0,0 +1,99 @@ +// Copyright 2019 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package caches + +import ( + "bytes" + "encoding/gob" + "errors" + "fmt" + "strings" + "time" + + "xorm.io/xorm/schemas" +) + +const ( + // CacheExpired is default cache expired time + CacheExpired = 60 * time.Minute + // CacheMaxMemory is not use now + CacheMaxMemory = 256 + // CacheGcInterval represents interval time to clear all expired nodes + CacheGcInterval = 10 * time.Minute + // CacheGcMaxRemoved represents max nodes removed when gc + CacheGcMaxRemoved = 20 +) + +// list all the errors +var ( + ErrCacheMiss = errors.New("xorm/cache: key not found") + ErrNotStored = errors.New("xorm/cache: not stored") + // ErrNotExist record does not exist error + ErrNotExist = errors.New("Record does not exist") +) + +// CacheStore is a interface to store cache +type CacheStore interface { + // key is primary key or composite primary key + // value is struct's pointer + // key format : -p--... + Put(key string, value interface{}) error + Get(key string) (interface{}, error) + Del(key string) error +} + +// Cacher is an interface to provide cache +// id format : u--... +type Cacher interface { + GetIds(tableName, sql string) interface{} + GetBean(tableName string, id string) interface{} + PutIds(tableName, sql string, ids interface{}) + PutBean(tableName string, id string, obj interface{}) + DelIds(tableName, sql string) + DelBean(tableName string, id string) + ClearIds(tableName string) + ClearBeans(tableName string) +} + +func encodeIds(ids []schemas.PK) (string, error) { + buf := new(bytes.Buffer) + enc := gob.NewEncoder(buf) + err := enc.Encode(ids) + + return buf.String(), err +} + +func decodeIds(s string) ([]schemas.PK, error) { + pks := make([]schemas.PK, 0) + + dec := gob.NewDecoder(strings.NewReader(s)) + err := dec.Decode(&pks) + + return pks, err +} + +// GetCacheSql returns cacher PKs via SQL +func GetCacheSql(m Cacher, tableName, sql string, args interface{}) ([]schemas.PK, error) { + bytes := m.GetIds(tableName, GenSqlKey(sql, args)) + if bytes == nil { + return nil, errors.New("Not Exist") + } + return decodeIds(bytes.(string)) +} + +// PutCacheSql puts cacher SQL and PKs +func PutCacheSql(m Cacher, ids []schemas.PK, tableName, sql string, args interface{}) error { + bytes, err := encodeIds(ids) + if err != nil { + return err + } + m.PutIds(tableName, GenSqlKey(sql, args), bytes) + return nil +} + +// GenSqlKey generates cache key +func GenSqlKey(sql string, args interface{}) string { + return fmt.Sprintf("%v-%v", sql, args) +} diff --git a/cache_lru.go b/caches/cache_lru.go similarity index 94% rename from cache_lru.go rename to caches/cache_lru.go index ab948bd2..fe9c7c53 100644 --- a/cache_lru.go +++ b/caches/cache_lru.go @@ -2,15 +2,13 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package caches import ( "container/list" "fmt" "sync" "time" - - "xorm.io/core" ) // LRUCacher implments cache object facilities @@ -19,7 +17,7 @@ type LRUCacher struct { sqlList *list.List idIndex map[string]map[string]*list.Element sqlIndex map[string]map[string]*list.Element - store core.CacheStore + store CacheStore mutex sync.Mutex MaxElementSize int Expired time.Duration @@ -27,15 +25,15 @@ type LRUCacher struct { } // NewLRUCacher creates a cacher -func NewLRUCacher(store core.CacheStore, maxElementSize int) *LRUCacher { +func NewLRUCacher(store CacheStore, maxElementSize int) *LRUCacher { return NewLRUCacher2(store, 3600*time.Second, maxElementSize) } // NewLRUCacher2 creates a cache include different params -func NewLRUCacher2(store core.CacheStore, expired time.Duration, maxElementSize int) *LRUCacher { +func NewLRUCacher2(store CacheStore, expired time.Duration, maxElementSize int) *LRUCacher { cacher := &LRUCacher{store: store, idList: list.New(), sqlList: list.New(), Expired: expired, - GcInterval: core.CacheGcInterval, MaxElementSize: maxElementSize, + GcInterval: CacheGcInterval, MaxElementSize: maxElementSize, sqlIndex: make(map[string]map[string]*list.Element), idIndex: make(map[string]map[string]*list.Element), } @@ -57,7 +55,7 @@ func (m *LRUCacher) GC() { defer m.mutex.Unlock() var removedNum int for e := m.idList.Front(); e != nil; { - if removedNum <= core.CacheGcMaxRemoved && + if removedNum <= CacheGcMaxRemoved && time.Now().Sub(e.Value.(*idNode).lastVisit) > m.Expired { removedNum++ next := e.Next() @@ -71,7 +69,7 @@ func (m *LRUCacher) GC() { removedNum = 0 for e := m.sqlList.Front(); e != nil; { - if removedNum <= core.CacheGcMaxRemoved && + if removedNum <= CacheGcMaxRemoved && time.Now().Sub(e.Value.(*sqlNode).lastVisit) > m.Expired { removedNum++ next := e.Next() diff --git a/cache_lru_test.go b/caches/cache_lru_test.go similarity index 94% rename from cache_lru_test.go rename to caches/cache_lru_test.go index 06373f79..771b924c 100644 --- a/cache_lru_test.go +++ b/caches/cache_lru_test.go @@ -2,13 +2,13 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package caches import ( "testing" "github.com/stretchr/testify/assert" - "xorm.io/core" + "xorm.io/xorm/schemas" ) func TestLRUCache(t *testing.T) { @@ -20,7 +20,7 @@ func TestLRUCache(t *testing.T) { cacher := NewLRUCacher(store, 10000) tableName := "cache_object1" - pks := []core.PK{ + pks := []schemas.PK{ {1}, {2}, } diff --git a/cache_memory_store.go b/caches/cache_memory_store.go similarity index 93% rename from cache_memory_store.go rename to caches/cache_memory_store.go index 0c483f45..f16254d8 100644 --- a/cache_memory_store.go +++ b/caches/cache_memory_store.go @@ -2,15 +2,13 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package caches import ( "sync" - - "xorm.io/core" ) -var _ core.CacheStore = NewMemoryStore() +var _ CacheStore = NewMemoryStore() // MemoryStore represents in-memory store type MemoryStore struct { diff --git a/cache_memory_store_test.go b/caches/cache_memory_store_test.go similarity index 97% rename from cache_memory_store_test.go rename to caches/cache_memory_store_test.go index 8e267683..12db4ea7 100644 --- a/cache_memory_store_test.go +++ b/caches/cache_memory_store_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package caches import ( "testing" diff --git a/convert.go b/convert.go index 2316ca0b..b8e1c4fc 100644 --- a/convert.go +++ b/convert.go @@ -346,3 +346,10 @@ func asBool(bs []byte) (bool, error) { } return strconv.ParseBool(string(bs)) } + +// Conversion is an interface. A type implements Conversion will according +// the custom method to fill into database and retrieve from database. +type Conversion interface { + FromDB([]byte) error + ToDB() ([]byte, error) +} diff --git a/core/db.go b/core/db.go new file mode 100644 index 00000000..8f16e848 --- /dev/null +++ b/core/db.go @@ -0,0 +1,229 @@ +// Copyright 2019 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package core + +import ( + "context" + "database/sql" + "database/sql/driver" + "fmt" + "reflect" + "regexp" + "sync" + + "xorm.io/xorm/names" +) + +var ( + // DefaultCacheSize sets the default cache size + DefaultCacheSize = 200 +) + +func MapToSlice(query string, mp interface{}) (string, []interface{}, error) { + vv := reflect.ValueOf(mp) + if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Map { + return "", []interface{}{}, ErrNoMapPointer + } + + args := make([]interface{}, 0, len(vv.Elem().MapKeys())) + var err error + query = re.ReplaceAllStringFunc(query, func(src string) string { + v := vv.Elem().MapIndex(reflect.ValueOf(src[1:])) + if !v.IsValid() { + err = fmt.Errorf("map key %s is missing", src[1:]) + } else { + args = append(args, v.Interface()) + } + return "?" + }) + + return query, args, err +} + +func StructToSlice(query string, st interface{}) (string, []interface{}, error) { + vv := reflect.ValueOf(st) + if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Struct { + return "", []interface{}{}, ErrNoStructPointer + } + + args := make([]interface{}, 0) + var err error + query = re.ReplaceAllStringFunc(query, func(src string) string { + fv := vv.Elem().FieldByName(src[1:]).Interface() + if v, ok := fv.(driver.Valuer); ok { + var value driver.Value + value, err = v.Value() + if err != nil { + return "?" + } + args = append(args, value) + } else { + args = append(args, fv) + } + return "?" + }) + if err != nil { + return "", []interface{}{}, err + } + return query, args, nil +} + +type cacheStruct struct { + value reflect.Value + idx int +} + +// DB is a wrap of sql.DB with extra contents +type DB struct { + *sql.DB + Mapper names.Mapper + reflectCache map[reflect.Type]*cacheStruct + reflectCacheMutex sync.RWMutex +} + +// Open opens a database +func Open(driverName, dataSourceName string) (*DB, error) { + db, err := sql.Open(driverName, dataSourceName) + if err != nil { + return nil, err + } + return &DB{ + DB: db, + Mapper: names.NewCacheMapper(&names.SnakeMapper{}), + reflectCache: make(map[reflect.Type]*cacheStruct), + }, nil +} + +// FromDB creates a DB from a sql.DB +func FromDB(db *sql.DB) *DB { + return &DB{ + DB: db, + Mapper: names.NewCacheMapper(&names.SnakeMapper{}), + reflectCache: make(map[reflect.Type]*cacheStruct), + } +} + +func (db *DB) reflectNew(typ reflect.Type) reflect.Value { + db.reflectCacheMutex.Lock() + defer db.reflectCacheMutex.Unlock() + cs, ok := db.reflectCache[typ] + if !ok || cs.idx+1 > DefaultCacheSize-1 { + cs = &cacheStruct{reflect.MakeSlice(reflect.SliceOf(typ), DefaultCacheSize, DefaultCacheSize), 0} + db.reflectCache[typ] = cs + } else { + cs.idx = cs.idx + 1 + } + return cs.value.Index(cs.idx).Addr() +} + +// QueryContext overwrites sql.DB.QueryContext +func (db *DB) QueryContext(ctx context.Context, query string, args ...interface{}) (*Rows, error) { + rows, err := db.DB.QueryContext(ctx, query, args...) + if err != nil { + if rows != nil { + rows.Close() + } + return nil, err + } + return &Rows{rows, db}, nil +} + +// Query overwrites sql.DB.Query +func (db *DB) Query(query string, args ...interface{}) (*Rows, error) { + return db.QueryContext(context.Background(), query, args...) +} + +// QueryMapContext executes query with parameters via map and context +func (db *DB) QueryMapContext(ctx context.Context, query string, mp interface{}) (*Rows, error) { + query, args, err := MapToSlice(query, mp) + if err != nil { + return nil, err + } + return db.QueryContext(ctx, query, args...) +} + +// QueryMap executes query with parameters via map +func (db *DB) QueryMap(query string, mp interface{}) (*Rows, error) { + return db.QueryMapContext(context.Background(), query, mp) +} + +func (db *DB) QueryStructContext(ctx context.Context, query string, st interface{}) (*Rows, error) { + query, args, err := StructToSlice(query, st) + if err != nil { + return nil, err + } + return db.QueryContext(ctx, query, args...) +} + +func (db *DB) QueryStruct(query string, st interface{}) (*Rows, error) { + return db.QueryStructContext(context.Background(), query, st) +} + +func (db *DB) QueryRowContext(ctx context.Context, query string, args ...interface{}) *Row { + rows, err := db.QueryContext(ctx, query, args...) + if err != nil { + return &Row{nil, err} + } + return &Row{rows, nil} +} + +func (db *DB) QueryRow(query string, args ...interface{}) *Row { + return db.QueryRowContext(context.Background(), query, args...) +} + +func (db *DB) QueryRowMapContext(ctx context.Context, query string, mp interface{}) *Row { + query, args, err := MapToSlice(query, mp) + if err != nil { + return &Row{nil, err} + } + return db.QueryRowContext(ctx, query, args...) +} + +func (db *DB) QueryRowMap(query string, mp interface{}) *Row { + return db.QueryRowMapContext(context.Background(), query, mp) +} + +func (db *DB) QueryRowStructContext(ctx context.Context, query string, st interface{}) *Row { + query, args, err := StructToSlice(query, st) + if err != nil { + return &Row{nil, err} + } + return db.QueryRowContext(ctx, query, args...) +} + +func (db *DB) QueryRowStruct(query string, st interface{}) *Row { + return db.QueryRowStructContext(context.Background(), query, st) +} + +var ( + re = regexp.MustCompile(`[?](\w+)`) +) + +// ExecMapContext exec map with context.Context +// insert into (name) values (?) +// insert into (name) values (?name) +func (db *DB) ExecMapContext(ctx context.Context, query string, mp interface{}) (sql.Result, error) { + query, args, err := MapToSlice(query, mp) + if err != nil { + return nil, err + } + return db.DB.ExecContext(ctx, query, args...) +} + +func (db *DB) ExecMap(query string, mp interface{}) (sql.Result, error) { + return db.ExecMapContext(context.Background(), query, mp) +} + +func (db *DB) ExecStructContext(ctx context.Context, query string, st interface{}) (sql.Result, error) { + query, args, err := StructToSlice(query, st) + if err != nil { + return nil, err + } + return db.DB.ExecContext(ctx, query, args...) +} + +func (db *DB) ExecStruct(query string, st interface{}) (sql.Result, error) { + return db.ExecStructContext(context.Background(), query, st) +} diff --git a/core/db_test.go b/core/db_test.go new file mode 100644 index 00000000..9abc7a64 --- /dev/null +++ b/core/db_test.go @@ -0,0 +1,684 @@ +// Copyright 2019 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package core + +import ( + "errors" + "flag" + "os" + "testing" + "time" + + _ "github.com/go-sql-driver/mysql" + _ "github.com/mattn/go-sqlite3" + "xorm.io/xorm/names" +) + +var ( + dbtype = flag.String("dbtype", "mysql", "database type") + dbConn = flag.String("dbConn", "root:@/core_test?charset=utf8", "database connect string") + createTableSql string +) + +func TestMain(m *testing.M) { + flag.Parse() + + switch *dbtype { + case "sqlite3": + createTableSql = "CREATE TABLE IF NOT EXISTS `user` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NULL, " + + "`title` TEXT NULL, `age` FLOAT NULL, `alias` TEXT NULL, `nick_name` TEXT NULL, `created` datetime);" + case "mysql": + fallthrough + default: + createTableSql = "CREATE TABLE IF NOT EXISTS `user` (`id` INTEGER PRIMARY KEY AUTO_INCREMENT NOT NULL, `name` TEXT NULL, " + + "`title` TEXT NULL, `age` FLOAT NULL, `alias` TEXT NULL, `nick_name` TEXT NULL, `created` datetime);" + } + + exitCode := m.Run() + + os.Exit(exitCode) +} + +func testOpen() (*DB, error) { + switch *dbtype { + case "sqlite3": + os.Remove("./test.db") + return Open("sqlite3", "./test.db") + case "mysql": + return Open("mysql", *dbConn) + default: + panic("no db type") + } +} + +func BenchmarkOriQuery(b *testing.B) { + b.StopTimer() + db, err := testOpen() + if err != nil { + b.Error(err) + } + defer db.Close() + + _, err = db.Exec(createTableSql) + if err != nil { + b.Error(err) + } + + for i := 0; i < 50; i++ { + _, err = db.Exec("insert into user (`name`, title, age, alias, nick_name, created) values (?,?,?,?,?, ?)", + "xlw", "tester", 1.2, "lunny", "lunny xiao", time.Now()) + if err != nil { + b.Error(err) + } + } + + b.StartTimer() + + for i := 0; i < b.N; i++ { + rows, err := db.Query("select * from user") + if err != nil { + b.Error(err) + } + + for rows.Next() { + var Id int64 + var Name, Title, Alias, NickName string + var Age float32 + var Created NullTime + err = rows.Scan(&Id, &Name, &Title, &Age, &Alias, &NickName, &Created) + if err != nil { + b.Error(err) + } + //fmt.Println(Id, Name, Title, Age, Alias, NickName) + } + rows.Close() + } +} + +type User struct { + Id int64 + Name string + Title string + Age float32 + Alias string + NickName string + Created NullTime +} + +func BenchmarkStructQuery(b *testing.B) { + b.StopTimer() + + db, err := testOpen() + if err != nil { + b.Error(err) + } + defer db.Close() + + _, err = db.Exec(createTableSql) + if err != nil { + b.Error(err) + } + + for i := 0; i < 50; i++ { + _, err = db.Exec("insert into user (`name`, title, age, alias, nick_name, created) values (?,?,?,?,?, ?)", + "xlw", "tester", 1.2, "lunny", "lunny xiao", time.Now()) + if err != nil { + b.Error(err) + } + } + + b.StartTimer() + + for i := 0; i < b.N; i++ { + rows, err := db.Query("select * from user") + if err != nil { + b.Error(err) + } + + for rows.Next() { + var user User + err = rows.ScanStructByIndex(&user) + if err != nil { + b.Error(err) + } + if user.Name != "xlw" { + b.Log(user) + b.Error(errors.New("name should be xlw")) + } + } + rows.Close() + } +} + +func BenchmarkStruct2Query(b *testing.B) { + b.StopTimer() + + db, err := testOpen() + if err != nil { + b.Error(err) + } + defer db.Close() + + _, err = db.Exec(createTableSql) + if err != nil { + b.Error(err) + } + + for i := 0; i < 50; i++ { + _, err = db.Exec("insert into user (`name`, title, age, alias, nick_name, created) values (?,?,?,?,?,?)", + "xlw", "tester", 1.2, "lunny", "lunny xiao", time.Now()) + if err != nil { + b.Error(err) + } + } + + db.Mapper = names.NewCacheMapper(&names.SnakeMapper{}) + b.StartTimer() + + for i := 0; i < b.N; i++ { + rows, err := db.Query("select * from user") + if err != nil { + b.Error(err) + } + + for rows.Next() { + var user User + err = rows.ScanStructByName(&user) + if err != nil { + b.Error(err) + } + if user.Name != "xlw" { + b.Log(user) + b.Error(errors.New("name should be xlw")) + } + } + rows.Close() + } +} + +func BenchmarkSliceInterfaceQuery(b *testing.B) { + b.StopTimer() + + db, err := testOpen() + if err != nil { + b.Error(err) + } + defer db.Close() + + _, err = db.Exec(createTableSql) + if err != nil { + b.Error(err) + } + + for i := 0; i < 50; i++ { + _, err = db.Exec("insert into user (`name`, title, age, alias, nick_name,created) values (?,?,?,?,?,?)", + "xlw", "tester", 1.2, "lunny", "lunny xiao", time.Now()) + if err != nil { + b.Error(err) + } + } + + b.StartTimer() + + for i := 0; i < b.N; i++ { + rows, err := db.Query("select * from user") + if err != nil { + b.Error(err) + } + + cols, err := rows.Columns() + if err != nil { + b.Error(err) + } + + for rows.Next() { + slice := make([]interface{}, len(cols)) + err = rows.ScanSlice(&slice) + if err != nil { + b.Error(err) + } + b.Log(slice) + switch slice[1].(type) { + case *string: + if *slice[1].(*string) != "xlw" { + b.Error(errors.New("name should be xlw")) + } + case []byte: + if string(slice[1].([]byte)) != "xlw" { + b.Error(errors.New("name should be xlw")) + } + } + } + + rows.Close() + } +} + +/*func BenchmarkSliceBytesQuery(b *testing.B) { + b.StopTimer() + os.Remove("./test.db") + db, err := Open("sqlite3", "./test.db") + if err != nil { + b.Error(err) + } + defer db.Close() + + _, err = db.Exec(createTableSql) + if err != nil { + b.Error(err) + } + + for i := 0; i < 50; i++ { + _, err = db.Exec("insert into user (name, title, age, alias, nick_name,created) values (?,?,?,?,?,?)", + "xlw", "tester", 1.2, "lunny", "lunny xiao", time.Now()) + if err != nil { + b.Error(err) + } + } + + b.StartTimer() + + for i := 0; i < b.N; i++ { + rows, err := db.Query("select * from user") + if err != nil { + b.Error(err) + } + + cols, err := rows.Columns() + if err != nil { + b.Error(err) + } + + for rows.Next() { + slice := make([][]byte, len(cols)) + err = rows.ScanSlice(&slice) + if err != nil { + b.Error(err) + } + if string(slice[1]) != "xlw" { + fmt.Println(slice) + b.Error(errors.New("name should be xlw")) + } + } + + rows.Close() + } +} +*/ + +func BenchmarkSliceStringQuery(b *testing.B) { + b.StopTimer() + db, err := testOpen() + if err != nil { + b.Error(err) + } + defer db.Close() + + _, err = db.Exec(createTableSql) + if err != nil { + b.Error(err) + } + + for i := 0; i < 50; i++ { + _, err = db.Exec("insert into user (name, title, age, alias, nick_name, created) values (?,?,?,?,?,?)", + "xlw", "tester", 1.2, "lunny", "lunny xiao", time.Now()) + if err != nil { + b.Error(err) + } + } + + b.StartTimer() + + for i := 0; i < b.N; i++ { + rows, err := db.Query("select * from user") + if err != nil { + b.Error(err) + } + + cols, err := rows.Columns() + if err != nil { + b.Error(err) + } + + for rows.Next() { + slice := make([]*string, len(cols)) + err = rows.ScanSlice(&slice) + if err != nil { + b.Error(err) + } + if (*slice[1]) != "xlw" { + b.Log(slice) + b.Error(errors.New("name should be xlw")) + } + } + + rows.Close() + } +} + +func BenchmarkMapInterfaceQuery(b *testing.B) { + b.StopTimer() + + db, err := testOpen() + if err != nil { + b.Error(err) + } + defer db.Close() + + _, err = db.Exec(createTableSql) + if err != nil { + b.Error(err) + } + + for i := 0; i < 50; i++ { + _, err = db.Exec("insert into user (name, title, age, alias, nick_name,created) values (?,?,?,?,?,?)", + "xlw", "tester", 1.2, "lunny", "lunny xiao", time.Now()) + if err != nil { + b.Error(err) + } + } + + b.StartTimer() + + for i := 0; i < b.N; i++ { + rows, err := db.Query("select * from user") + if err != nil { + b.Error(err) + } + + for rows.Next() { + m := make(map[string]interface{}) + err = rows.ScanMap(&m) + if err != nil { + b.Error(err) + } + switch m["name"].(type) { + case string: + if m["name"].(string) != "xlw" { + b.Log(m) + b.Error(errors.New("name should be xlw")) + } + case []byte: + if string(m["name"].([]byte)) != "xlw" { + b.Log(m) + b.Error(errors.New("name should be xlw")) + } + } + } + + rows.Close() + } +} + +/*func BenchmarkMapBytesQuery(b *testing.B) { + b.StopTimer() + os.Remove("./test.db") + db, err := Open("sqlite3", "./test.db") + if err != nil { + b.Error(err) + } + defer db.Close() + + _, err = db.Exec(createTableSql) + if err != nil { + b.Error(err) + } + + for i := 0; i < 50; i++ { + _, err = db.Exec("insert into user (name, title, age, alias, nick_name,created) values (?,?,?,?,?,?)", + "xlw", "tester", 1.2, "lunny", "lunny xiao", time.Now()) + if err != nil { + b.Error(err) + } + } + + b.StartTimer() + + for i := 0; i < b.N; i++ { + rows, err := db.Query("select * from user") + if err != nil { + b.Error(err) + } + + for rows.Next() { + m := make(map[string][]byte) + err = rows.ScanMap(&m) + if err != nil { + b.Error(err) + } + if string(m["name"]) != "xlw" { + fmt.Println(m) + b.Error(errors.New("name should be xlw")) + } + } + + rows.Close() + } +} +*/ +/* +func BenchmarkMapStringQuery(b *testing.B) { + b.StopTimer() + os.Remove("./test.db") + db, err := Open("sqlite3", "./test.db") + if err != nil { + b.Error(err) + } + defer db.Close() + + _, err = db.Exec(createTableSql) + if err != nil { + b.Error(err) + } + + for i := 0; i < 50; i++ { + _, err = db.Exec("insert into user (name, title, age, alias, nick_name,created) values (?,?,?,?,?,?)", + "xlw", "tester", 1.2, "lunny", "lunny xiao", time.Now()) + if err != nil { + b.Error(err) + } + } + + b.StartTimer() + + for i := 0; i < b.N; i++ { + rows, err := db.Query("select * from user") + if err != nil { + b.Error(err) + } + + for rows.Next() { + m := make(map[string]string) + err = rows.ScanMap(&m) + if err != nil { + b.Error(err) + } + if m["name"] != "xlw" { + fmt.Println(m) + b.Error(errors.New("name should be xlw")) + } + } + + rows.Close() + } +}*/ + +func BenchmarkExec(b *testing.B) { + b.StopTimer() + + db, err := testOpen() + if err != nil { + b.Error(err) + } + defer db.Close() + + _, err = db.Exec(createTableSql) + if err != nil { + b.Error(err) + } + + b.StartTimer() + + for i := 0; i < b.N; i++ { + _, err = db.Exec("insert into user (`name`, title, age, alias, nick_name,created) values (?,?,?,?,?,?)", + "xlw", "tester", 1.2, "lunny", "lunny xiao", time.Now()) + if err != nil { + b.Error(err) + } + } +} + +func BenchmarkExecMap(b *testing.B) { + b.StopTimer() + + db, err := testOpen() + if err != nil { + b.Error(err) + } + defer db.Close() + + _, err = db.Exec(createTableSql) + if err != nil { + b.Error(err) + } + + b.StartTimer() + + mp := map[string]interface{}{ + "name": "xlw", + "title": "tester", + "age": 1.2, + "alias": "lunny", + "nick_name": "lunny xiao", + "created": time.Now(), + } + + for i := 0; i < b.N; i++ { + _, err = db.ExecMap("insert into user (`name`, title, age, alias, nick_name, created) "+ + "values (?name,?title,?age,?alias,?nick_name,?created)", + &mp) + if err != nil { + b.Error(err) + } + } +} + +func TestExecMap(t *testing.T) { + db, err := testOpen() + if err != nil { + t.Error(err) + } + defer db.Close() + + _, err = db.Exec(createTableSql) + if err != nil { + t.Error(err) + } + + mp := map[string]interface{}{ + "name": "xlw", + "title": "tester", + "age": 1.2, + "alias": "lunny", + "nick_name": "lunny xiao", + "created": time.Now(), + } + + _, err = db.ExecMap("insert into user (`name`, title, age, alias, nick_name,created) "+ + "values (?name,?title,?age,?alias,?nick_name,?created)", + &mp) + if err != nil { + t.Error(err) + } + + rows, err := db.Query("select * from user") + if err != nil { + t.Error(err) + } + + for rows.Next() { + var user User + err = rows.ScanStructByName(&user) + if err != nil { + t.Error(err) + } + t.Log("--", user) + } +} + +func TestExecStruct(t *testing.T) { + db, err := testOpen() + if err != nil { + t.Error(err) + } + defer db.Close() + + _, err = db.Exec(createTableSql) + if err != nil { + t.Error(err) + } + + user := User{Name: "xlw", + Title: "tester", + Age: 1.2, + Alias: "lunny", + NickName: "lunny xiao", + Created: NullTime(time.Now()), + } + + _, err = db.ExecStruct("insert into user (`name`, title, age, alias, nick_name,created) "+ + "values (?Name,?Title,?Age,?Alias,?NickName,?Created)", + &user) + if err != nil { + t.Error(err) + } + + rows, err := db.QueryStruct("select * from user where `name` = ?Name", &user) + if err != nil { + t.Error(err) + } + + for rows.Next() { + var user User + err = rows.ScanStructByName(&user) + if err != nil { + t.Error(err) + } + t.Log("1--", user) + } +} + +func BenchmarkExecStruct(b *testing.B) { + b.StopTimer() + db, err := testOpen() + if err != nil { + b.Error(err) + } + defer db.Close() + + _, err = db.Exec(createTableSql) + if err != nil { + b.Error(err) + } + + b.StartTimer() + + user := User{Name: "xlw", + Title: "tester", + Age: 1.2, + Alias: "lunny", + NickName: "lunny xiao", + Created: NullTime(time.Now()), + } + + for i := 0; i < b.N; i++ { + _, err = db.ExecStruct("insert into user (`name`, title, age, alias, nick_name,created) "+ + "values (?Name,?Title,?Age,?Alias,?NickName,?Created)", + &user) + if err != nil { + b.Error(err) + } + } +} diff --git a/core/error.go b/core/error.go new file mode 100644 index 00000000..1fd18348 --- /dev/null +++ b/core/error.go @@ -0,0 +1,14 @@ +// Copyright 2019 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package core + +import "errors" + +var ( + // ErrNoMapPointer represents error when no map pointer + ErrNoMapPointer = errors.New("mp should be a map's pointer") + // ErrNoStructPointer represents error when no struct pointer + ErrNoStructPointer = errors.New("mp should be a struct's pointer") +) diff --git a/core/rows.go b/core/rows.go new file mode 100644 index 00000000..a1e8bfbc --- /dev/null +++ b/core/rows.go @@ -0,0 +1,338 @@ +// Copyright 2019 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package core + +import ( + "database/sql" + "errors" + "reflect" + "sync" +) + +type Rows struct { + *sql.Rows + db *DB +} + +func (rs *Rows) ToMapString() ([]map[string]string, error) { + cols, err := rs.Columns() + if err != nil { + return nil, err + } + + var results = make([]map[string]string, 0, 10) + for rs.Next() { + var record = make(map[string]string, len(cols)) + err = rs.ScanMap(&record) + if err != nil { + return nil, err + } + results = append(results, record) + } + return results, nil +} + +// scan data to a struct's pointer according field index +func (rs *Rows) ScanStructByIndex(dest ...interface{}) error { + if len(dest) == 0 { + return errors.New("at least one struct") + } + + vvvs := make([]reflect.Value, len(dest)) + for i, s := range dest { + vv := reflect.ValueOf(s) + if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Struct { + return errors.New("dest should be a struct's pointer") + } + + vvvs[i] = vv.Elem() + } + + cols, err := rs.Columns() + if err != nil { + return err + } + newDest := make([]interface{}, len(cols)) + + var i = 0 + for _, vvv := range vvvs { + for j := 0; j < vvv.NumField(); j++ { + newDest[i] = vvv.Field(j).Addr().Interface() + i = i + 1 + } + } + + return rs.Rows.Scan(newDest...) +} + +var ( + fieldCache = make(map[reflect.Type]map[string]int) + fieldCacheMutex sync.RWMutex +) + +func fieldByName(v reflect.Value, name string) reflect.Value { + t := v.Type() + fieldCacheMutex.RLock() + cache, ok := fieldCache[t] + fieldCacheMutex.RUnlock() + if !ok { + cache = make(map[string]int) + for i := 0; i < v.NumField(); i++ { + cache[t.Field(i).Name] = i + } + fieldCacheMutex.Lock() + fieldCache[t] = cache + fieldCacheMutex.Unlock() + } + + if i, ok := cache[name]; ok { + return v.Field(i) + } + + return reflect.Zero(t) +} + +// scan data to a struct's pointer according field name +func (rs *Rows) ScanStructByName(dest interface{}) error { + vv := reflect.ValueOf(dest) + if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Struct { + return errors.New("dest should be a struct's pointer") + } + + cols, err := rs.Columns() + if err != nil { + return err + } + + newDest := make([]interface{}, len(cols)) + var v EmptyScanner + for j, name := range cols { + f := fieldByName(vv.Elem(), rs.db.Mapper.Table2Obj(name)) + if f.IsValid() { + newDest[j] = f.Addr().Interface() + } else { + newDest[j] = &v + } + } + + return rs.Rows.Scan(newDest...) +} + +// scan data to a slice's pointer, slice's length should equal to columns' number +func (rs *Rows) ScanSlice(dest interface{}) error { + vv := reflect.ValueOf(dest) + if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Slice { + return errors.New("dest should be a slice's pointer") + } + + vvv := vv.Elem() + cols, err := rs.Columns() + if err != nil { + return err + } + + newDest := make([]interface{}, len(cols)) + + for j := 0; j < len(cols); j++ { + if j >= vvv.Len() { + newDest[j] = reflect.New(vvv.Type().Elem()).Interface() + } else { + newDest[j] = vvv.Index(j).Addr().Interface() + } + } + + err = rs.Rows.Scan(newDest...) + if err != nil { + return err + } + + srcLen := vvv.Len() + for i := srcLen; i < len(cols); i++ { + vvv = reflect.Append(vvv, reflect.ValueOf(newDest[i]).Elem()) + } + return nil +} + +// scan data to a map's pointer +func (rs *Rows) ScanMap(dest interface{}) error { + vv := reflect.ValueOf(dest) + if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Map { + return errors.New("dest should be a map's pointer") + } + + cols, err := rs.Columns() + if err != nil { + return err + } + + newDest := make([]interface{}, len(cols)) + vvv := vv.Elem() + + for i := range cols { + newDest[i] = rs.db.reflectNew(vvv.Type().Elem()).Interface() + } + + err = rs.Rows.Scan(newDest...) + if err != nil { + return err + } + + for i, name := range cols { + vname := reflect.ValueOf(name) + vvv.SetMapIndex(vname, reflect.ValueOf(newDest[i]).Elem()) + } + + return nil +} + +type Row struct { + rows *Rows + // One of these two will be non-nil: + err error // deferred error for easy chaining +} + +// ErrorRow return an error row +func ErrorRow(err error) *Row { + return &Row{ + err: err, + } +} + +// NewRow from rows +func NewRow(rows *Rows, err error) *Row { + return &Row{rows, err} +} + +func (row *Row) Columns() ([]string, error) { + if row.err != nil { + return nil, row.err + } + return row.rows.Columns() +} + +func (row *Row) Scan(dest ...interface{}) error { + if row.err != nil { + return row.err + } + defer row.rows.Close() + + for _, dp := range dest { + if _, ok := dp.(*sql.RawBytes); ok { + return errors.New("sql: RawBytes isn't allowed on Row.Scan") + } + } + + if !row.rows.Next() { + if err := row.rows.Err(); err != nil { + return err + } + return sql.ErrNoRows + } + err := row.rows.Scan(dest...) + if err != nil { + return err + } + // Make sure the query can be processed to completion with no errors. + return row.rows.Close() +} + +func (row *Row) ScanStructByName(dest interface{}) error { + if row.err != nil { + return row.err + } + defer row.rows.Close() + + if !row.rows.Next() { + if err := row.rows.Err(); err != nil { + return err + } + return sql.ErrNoRows + } + err := row.rows.ScanStructByName(dest) + if err != nil { + return err + } + // Make sure the query can be processed to completion with no errors. + return row.rows.Close() +} + +func (row *Row) ScanStructByIndex(dest interface{}) error { + if row.err != nil { + return row.err + } + defer row.rows.Close() + + if !row.rows.Next() { + if err := row.rows.Err(); err != nil { + return err + } + return sql.ErrNoRows + } + err := row.rows.ScanStructByIndex(dest) + if err != nil { + return err + } + // Make sure the query can be processed to completion with no errors. + return row.rows.Close() +} + +// scan data to a slice's pointer, slice's length should equal to columns' number +func (row *Row) ScanSlice(dest interface{}) error { + if row.err != nil { + return row.err + } + defer row.rows.Close() + + if !row.rows.Next() { + if err := row.rows.Err(); err != nil { + return err + } + return sql.ErrNoRows + } + err := row.rows.ScanSlice(dest) + if err != nil { + return err + } + + // Make sure the query can be processed to completion with no errors. + return row.rows.Close() +} + +// scan data to a map's pointer +func (row *Row) ScanMap(dest interface{}) error { + if row.err != nil { + return row.err + } + defer row.rows.Close() + + if !row.rows.Next() { + if err := row.rows.Err(); err != nil { + return err + } + return sql.ErrNoRows + } + err := row.rows.ScanMap(dest) + if err != nil { + return err + } + + // Make sure the query can be processed to completion with no errors. + return row.rows.Close() +} + +func (row *Row) ToMapString() (map[string]string, error) { + cols, err := row.Columns() + if err != nil { + return nil, err + } + + var record = make(map[string]string, len(cols)) + err = row.ScanMap(&record) + if err != nil { + return nil, err + } + + return record, nil +} diff --git a/core/scan.go b/core/scan.go new file mode 100644 index 00000000..897b5341 --- /dev/null +++ b/core/scan.go @@ -0,0 +1,66 @@ +// Copyright 2019 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package core + +import ( + "database/sql/driver" + "fmt" + "time" +) + +type NullTime time.Time + +var ( + _ driver.Valuer = NullTime{} +) + +func (ns *NullTime) Scan(value interface{}) error { + if value == nil { + return nil + } + return convertTime(ns, value) +} + +// Value implements the driver Valuer interface. +func (ns NullTime) Value() (driver.Value, error) { + if (time.Time)(ns).IsZero() { + return nil, nil + } + return (time.Time)(ns).Format("2006-01-02 15:04:05"), nil +} + +func convertTime(dest *NullTime, src interface{}) error { + // Common cases, without reflect. + switch s := src.(type) { + case string: + t, err := time.Parse("2006-01-02 15:04:05", s) + if err != nil { + return err + } + *dest = NullTime(t) + return nil + case []uint8: + t, err := time.Parse("2006-01-02 15:04:05", string(s)) + if err != nil { + return err + } + *dest = NullTime(t) + return nil + case time.Time: + *dest = NullTime(s) + return nil + case nil: + default: + return fmt.Errorf("unsupported driver -> Scan pair: %T -> %T", src, dest) + } + return nil +} + +type EmptyScanner struct { +} + +func (EmptyScanner) Scan(src interface{}) error { + return nil +} diff --git a/core/stmt.go b/core/stmt.go new file mode 100644 index 00000000..8a21541a --- /dev/null +++ b/core/stmt.go @@ -0,0 +1,166 @@ +// Copyright 2019 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package core + +import ( + "context" + "database/sql" + "errors" + "reflect" +) + +// Stmt reprents a stmt objects +type Stmt struct { + *sql.Stmt + db *DB + names map[string]int +} + +func (db *DB) PrepareContext(ctx context.Context, query string) (*Stmt, error) { + names := make(map[string]int) + var i int + query = re.ReplaceAllStringFunc(query, func(src string) string { + names[src[1:]] = i + i += 1 + return "?" + }) + + stmt, err := db.DB.PrepareContext(ctx, query) + if err != nil { + return nil, err + } + return &Stmt{stmt, db, names}, nil +} + +func (db *DB) Prepare(query string) (*Stmt, error) { + return db.PrepareContext(context.Background(), query) +} + +func (s *Stmt) ExecMapContext(ctx context.Context, mp interface{}) (sql.Result, error) { + vv := reflect.ValueOf(mp) + if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Map { + return nil, errors.New("mp should be a map's pointer") + } + + args := make([]interface{}, len(s.names)) + for k, i := range s.names { + args[i] = vv.Elem().MapIndex(reflect.ValueOf(k)).Interface() + } + return s.Stmt.ExecContext(ctx, args...) +} + +func (s *Stmt) ExecMap(mp interface{}) (sql.Result, error) { + return s.ExecMapContext(context.Background(), mp) +} + +func (s *Stmt) ExecStructContext(ctx context.Context, st interface{}) (sql.Result, error) { + vv := reflect.ValueOf(st) + if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Struct { + return nil, errors.New("mp should be a map's pointer") + } + + args := make([]interface{}, len(s.names)) + for k, i := range s.names { + args[i] = vv.Elem().FieldByName(k).Interface() + } + return s.Stmt.ExecContext(ctx, args...) +} + +func (s *Stmt) ExecStruct(st interface{}) (sql.Result, error) { + return s.ExecStructContext(context.Background(), st) +} + +func (s *Stmt) QueryContext(ctx context.Context, args ...interface{}) (*Rows, error) { + rows, err := s.Stmt.QueryContext(ctx, args...) + if err != nil { + return nil, err + } + return &Rows{rows, s.db}, nil +} + +func (s *Stmt) Query(args ...interface{}) (*Rows, error) { + return s.QueryContext(context.Background(), args...) +} + +func (s *Stmt) QueryMapContext(ctx context.Context, mp interface{}) (*Rows, error) { + vv := reflect.ValueOf(mp) + if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Map { + return nil, errors.New("mp should be a map's pointer") + } + + args := make([]interface{}, len(s.names)) + for k, i := range s.names { + args[i] = vv.Elem().MapIndex(reflect.ValueOf(k)).Interface() + } + + return s.QueryContext(ctx, args...) +} + +func (s *Stmt) QueryMap(mp interface{}) (*Rows, error) { + return s.QueryMapContext(context.Background(), mp) +} + +func (s *Stmt) QueryStructContext(ctx context.Context, st interface{}) (*Rows, error) { + vv := reflect.ValueOf(st) + if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Struct { + return nil, errors.New("mp should be a map's pointer") + } + + args := make([]interface{}, len(s.names)) + for k, i := range s.names { + args[i] = vv.Elem().FieldByName(k).Interface() + } + + return s.Query(args...) +} + +func (s *Stmt) QueryStruct(st interface{}) (*Rows, error) { + return s.QueryStructContext(context.Background(), st) +} + +func (s *Stmt) QueryRowContext(ctx context.Context, args ...interface{}) *Row { + rows, err := s.QueryContext(ctx, args...) + return &Row{rows, err} +} + +func (s *Stmt) QueryRow(args ...interface{}) *Row { + return s.QueryRowContext(context.Background(), args...) +} + +func (s *Stmt) QueryRowMapContext(ctx context.Context, mp interface{}) *Row { + vv := reflect.ValueOf(mp) + if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Map { + return &Row{nil, errors.New("mp should be a map's pointer")} + } + + args := make([]interface{}, len(s.names)) + for k, i := range s.names { + args[i] = vv.Elem().MapIndex(reflect.ValueOf(k)).Interface() + } + + return s.QueryRowContext(ctx, args...) +} + +func (s *Stmt) QueryRowMap(mp interface{}) *Row { + return s.QueryRowMapContext(context.Background(), mp) +} + +func (s *Stmt) QueryRowStructContext(ctx context.Context, st interface{}) *Row { + vv := reflect.ValueOf(st) + if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Struct { + return &Row{nil, errors.New("st should be a struct's pointer")} + } + + args := make([]interface{}, len(s.names)) + for k, i := range s.names { + args[i] = vv.Elem().FieldByName(k).Interface() + } + + return s.QueryRowContext(ctx, args...) +} + +func (s *Stmt) QueryRowStruct(st interface{}) *Row { + return s.QueryRowStructContext(context.Background(), st) +} diff --git a/core/tx.go b/core/tx.go new file mode 100644 index 00000000..a56b7006 --- /dev/null +++ b/core/tx.go @@ -0,0 +1,153 @@ +// Copyright 2019 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package core + +import ( + "context" + "database/sql" +) + +type Tx struct { + *sql.Tx + db *DB +} + +func (db *DB) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) { + tx, err := db.DB.BeginTx(ctx, opts) + if err != nil { + return nil, err + } + return &Tx{tx, db}, nil +} + +func (db *DB) Begin() (*Tx, error) { + tx, err := db.DB.Begin() + if err != nil { + return nil, err + } + return &Tx{tx, db}, nil +} + +func (tx *Tx) PrepareContext(ctx context.Context, query string) (*Stmt, error) { + names := make(map[string]int) + var i int + query = re.ReplaceAllStringFunc(query, func(src string) string { + names[src[1:]] = i + i += 1 + return "?" + }) + + stmt, err := tx.Tx.PrepareContext(ctx, query) + if err != nil { + return nil, err + } + return &Stmt{stmt, tx.db, names}, nil +} + +func (tx *Tx) Prepare(query string) (*Stmt, error) { + return tx.PrepareContext(context.Background(), query) +} + +func (tx *Tx) StmtContext(ctx context.Context, stmt *Stmt) *Stmt { + stmt.Stmt = tx.Tx.StmtContext(ctx, stmt.Stmt) + return stmt +} + +func (tx *Tx) Stmt(stmt *Stmt) *Stmt { + return tx.StmtContext(context.Background(), stmt) +} + +func (tx *Tx) ExecMapContext(ctx context.Context, query string, mp interface{}) (sql.Result, error) { + query, args, err := MapToSlice(query, mp) + if err != nil { + return nil, err + } + return tx.Tx.ExecContext(ctx, query, args...) +} + +func (tx *Tx) ExecMap(query string, mp interface{}) (sql.Result, error) { + return tx.ExecMapContext(context.Background(), query, mp) +} + +func (tx *Tx) ExecStructContext(ctx context.Context, query string, st interface{}) (sql.Result, error) { + query, args, err := StructToSlice(query, st) + if err != nil { + return nil, err + } + return tx.Tx.ExecContext(ctx, query, args...) +} + +func (tx *Tx) ExecStruct(query string, st interface{}) (sql.Result, error) { + return tx.ExecStructContext(context.Background(), query, st) +} + +func (tx *Tx) QueryContext(ctx context.Context, query string, args ...interface{}) (*Rows, error) { + rows, err := tx.Tx.QueryContext(ctx, query, args...) + if err != nil { + return nil, err + } + return &Rows{rows, tx.db}, nil +} + +func (tx *Tx) Query(query string, args ...interface{}) (*Rows, error) { + return tx.QueryContext(context.Background(), query, args...) +} + +func (tx *Tx) QueryMapContext(ctx context.Context, query string, mp interface{}) (*Rows, error) { + query, args, err := MapToSlice(query, mp) + if err != nil { + return nil, err + } + return tx.QueryContext(ctx, query, args...) +} + +func (tx *Tx) QueryMap(query string, mp interface{}) (*Rows, error) { + return tx.QueryMapContext(context.Background(), query, mp) +} + +func (tx *Tx) QueryStructContext(ctx context.Context, query string, st interface{}) (*Rows, error) { + query, args, err := StructToSlice(query, st) + if err != nil { + return nil, err + } + return tx.QueryContext(ctx, query, args...) +} + +func (tx *Tx) QueryStruct(query string, st interface{}) (*Rows, error) { + return tx.QueryStructContext(context.Background(), query, st) +} + +func (tx *Tx) QueryRowContext(ctx context.Context, query string, args ...interface{}) *Row { + rows, err := tx.QueryContext(ctx, query, args...) + return &Row{rows, err} +} + +func (tx *Tx) QueryRow(query string, args ...interface{}) *Row { + return tx.QueryRowContext(context.Background(), query, args...) +} + +func (tx *Tx) QueryRowMapContext(ctx context.Context, query string, mp interface{}) *Row { + query, args, err := MapToSlice(query, mp) + if err != nil { + return &Row{nil, err} + } + return tx.QueryRowContext(ctx, query, args...) +} + +func (tx *Tx) QueryRowMap(query string, mp interface{}) *Row { + return tx.QueryRowMapContext(context.Background(), query, mp) +} + +func (tx *Tx) QueryRowStructContext(ctx context.Context, query string, st interface{}) *Row { + query, args, err := StructToSlice(query, st) + if err != nil { + return &Row{nil, err} + } + return tx.QueryRowContext(ctx, query, args...) +} + +func (tx *Tx) QueryRowStruct(query string, st interface{}) *Row { + return tx.QueryRowStructContext(context.Background(), query, st) +} diff --git a/dialects/dialect.go b/dialects/dialect.go new file mode 100644 index 00000000..b88d4e61 --- /dev/null +++ b/dialects/dialect.go @@ -0,0 +1,410 @@ +// Copyright 2019 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package dialects + +import ( + "fmt" + "strings" + "time" + + "xorm.io/xorm/core" + "xorm.io/xorm/log" + "xorm.io/xorm/schemas" +) + +type DBType string + +type URI struct { + DBType DBType + Proto string + Host string + Port string + DBName string + User string + Passwd string + Charset string + Laddr string + Raddr string + Timeout time.Duration + Schema string +} + +// a dialect is a driver's wrapper +type Dialect interface { + SetLogger(logger log.Logger) + Init(*core.DB, *URI, string, string) error + URI() *URI + DB() *core.DB + DBType() DBType + SQLType(*schemas.Column) string + FormatBytes(b []byte) string + + DriverName() string + DataSourceName() string + + IsReserved(string) bool + Quote(string) string + + AndStr() string + OrStr() string + EqStr() string + RollBackStr() string + AutoIncrStr() string + + SupportInsertMany() bool + SupportEngine() bool + SupportCharset() bool + SupportDropIfExists() bool + IndexOnTable() bool + ShowCreateNull() bool + + IndexCheckSQL(tableName, idxName string) (string, []interface{}) + TableCheckSQL(tableName string) (string, []interface{}) + + IsColumnExist(tableName string, colName string) (bool, error) + + CreateTableSQL(table *schemas.Table, tableName, storeEngine, charset string) string + DropTableSQL(tableName string) string + CreateIndexSQL(tableName string, index *schemas.Index) string + DropIndexSQL(tableName string, index *schemas.Index) string + + ModifyColumnSQL(tableName string, col *schemas.Column) string + + ForUpdateSQL(query string) string + + // CreateTableIfNotExists(table *Table, tableName, storeEngine, charset string) error + // MustDropTable(tableName string) error + + GetColumns(tableName string) ([]string, map[string]*schemas.Column, error) + GetTables() ([]*schemas.Table, error) + GetIndexes(tableName string) (map[string]*schemas.Index, error) + + Filters() []Filter + SetParams(params map[string]string) +} + +func OpenDialect(dialect Dialect) (*core.DB, error) { + return core.Open(dialect.DriverName(), dialect.DataSourceName()) +} + +// Base represents a basic dialect and all real dialects could embed this struct +type Base struct { + db *core.DB + dialect Dialect + driverName string + dataSourceName string + logger log.Logger + uri *URI +} + +// String generate column description string according dialect +func String(d Dialect, col *schemas.Column) string { + sql := d.Quote(col.Name) + " " + + sql += d.SQLType(col) + " " + + if col.IsPrimaryKey { + sql += "PRIMARY KEY " + if col.IsAutoIncrement { + sql += d.AutoIncrStr() + " " + } + } + + if col.Default != "" { + sql += "DEFAULT " + col.Default + " " + } + + if d.ShowCreateNull() { + if col.Nullable { + sql += "NULL " + } else { + sql += "NOT NULL " + } + } + + return sql +} + +// StringNoPk generate column description string according dialect without primary keys +func StringNoPk(d Dialect, col *schemas.Column) string { + sql := d.Quote(col.Name) + " " + + sql += d.SQLType(col) + " " + + if col.Default != "" { + sql += "DEFAULT " + col.Default + " " + } + + if d.ShowCreateNull() { + if col.Nullable { + sql += "NULL " + } else { + sql += "NOT NULL " + } + } + + return sql +} + +func (b *Base) DB() *core.DB { + return b.db +} + +func (b *Base) SetLogger(logger log.Logger) { + b.logger = logger +} + +func (b *Base) Init(db *core.DB, dialect Dialect, uri *URI, drivername, dataSourceName string) error { + b.db, b.dialect, b.uri = db, dialect, uri + b.driverName, b.dataSourceName = drivername, dataSourceName + return nil +} + +func (b *Base) URI() *URI { + return b.uri +} + +func (b *Base) DBType() DBType { + return b.uri.DBType +} + +func (b *Base) FormatBytes(bs []byte) string { + return fmt.Sprintf("0x%x", bs) +} + +func (b *Base) DriverName() string { + return b.driverName +} + +func (b *Base) ShowCreateNull() bool { + return true +} + +func (b *Base) DataSourceName() string { + return b.dataSourceName +} + +func (b *Base) AndStr() string { + return "AND" +} + +func (b *Base) OrStr() string { + return "OR" +} + +func (b *Base) EqStr() string { + return "=" +} + +func (db *Base) RollBackStr() string { + return "ROLL BACK" +} + +func (db *Base) SupportDropIfExists() bool { + return true +} + +func (db *Base) DropTableSQL(tableName string) string { + quote := db.dialect.Quote + return fmt.Sprintf("DROP TABLE IF EXISTS %s", quote(tableName)) +} + +func (db *Base) HasRecords(query string, args ...interface{}) (bool, error) { + db.LogSQL(query, args) + rows, err := db.DB().Query(query, args...) + if err != nil { + return false, err + } + defer rows.Close() + + if rows.Next() { + return true, nil + } + return false, nil +} + +func (db *Base) IsColumnExist(tableName, colName string) (bool, error) { + query := fmt.Sprintf( + "SELECT %v FROM %v.%v WHERE %v = ? AND %v = ? AND %v = ?", + db.dialect.Quote("COLUMN_NAME"), + db.dialect.Quote("INFORMATION_SCHEMA"), + db.dialect.Quote("COLUMNS"), + db.dialect.Quote("TABLE_SCHEMA"), + db.dialect.Quote("TABLE_NAME"), + db.dialect.Quote("COLUMN_NAME"), + ) + return db.HasRecords(query, db.uri.DBName, tableName, colName) +} + +/* +func (db *Base) CreateTableIfNotExists(table *Table, tableName, storeEngine, charset string) error { + sql, args := db.dialect.TableCheckSQL(tableName) + rows, err := db.DB().Query(sql, args...) + if db.Logger != nil { + db.Logger.Info("[sql]", sql, args) + } + if err != nil { + return err + } + defer rows.Close() + + if rows.Next() { + return nil + } + + sql = db.dialect.CreateTableSQL(table, tableName, storeEngine, charset) + _, err = db.DB().Exec(sql) + if db.Logger != nil { + db.Logger.Info("[sql]", sql) + } + return err +}*/ + +func (db *Base) CreateIndexSQL(tableName string, index *schemas.Index) string { + quote := db.dialect.Quote + var unique string + var idxName string + if index.Type == schemas.UniqueType { + unique = " UNIQUE" + } + idxName = index.XName(tableName) + return fmt.Sprintf("CREATE%s INDEX %v ON %v (%v)", unique, + quote(idxName), quote(tableName), + quote(strings.Join(index.Cols, quote(",")))) +} + +func (db *Base) DropIndexSQL(tableName string, index *schemas.Index) string { + quote := db.dialect.Quote + var name string + if index.IsRegular { + name = index.XName(tableName) + } else { + name = index.Name + } + return fmt.Sprintf("DROP INDEX %v ON %s", quote(name), quote(tableName)) +} + +func (db *Base) ModifyColumnSQL(tableName string, col *schemas.Column) string { + return fmt.Sprintf("alter table %s MODIFY COLUMN %s", tableName, StringNoPk(db.dialect, col)) +} + +func (b *Base) CreateTableSQL(table *schemas.Table, tableName, storeEngine, charset string) string { + var sql string + sql = "CREATE TABLE IF NOT EXISTS " + if tableName == "" { + tableName = table.Name + } + + sql += b.dialect.Quote(tableName) + sql += " (" + + if len(table.ColumnsSeq()) > 0 { + pkList := table.PrimaryKeys + + for _, colName := range table.ColumnsSeq() { + col := table.GetColumn(colName) + if col.IsPrimaryKey && len(pkList) == 1 { + sql += String(b.dialect, col) + } else { + sql += StringNoPk(b.dialect, col) + } + sql = strings.TrimSpace(sql) + if b.DriverName() == schemas.MYSQL && len(col.Comment) > 0 { + sql += " COMMENT '" + col.Comment + "'" + } + sql += ", " + } + + if len(pkList) > 1 { + sql += "PRIMARY KEY ( " + sql += b.dialect.Quote(strings.Join(pkList, b.dialect.Quote(","))) + sql += " ), " + } + + sql = sql[:len(sql)-2] + } + sql += ")" + + if b.dialect.SupportEngine() && storeEngine != "" { + sql += " ENGINE=" + storeEngine + } + if b.dialect.SupportCharset() { + if len(charset) == 0 { + charset = b.dialect.URI().Charset + } + if len(charset) > 0 { + sql += " DEFAULT CHARSET " + charset + } + } + + return sql +} + +func (b *Base) ForUpdateSQL(query string) string { + return query + " FOR UPDATE" +} + +func (b *Base) LogSQL(sql string, args []interface{}) { + if b.logger != nil && b.logger.IsShowSQL() { + if len(args) > 0 { + b.logger.Infof("[SQL] %v %v", sql, args) + } else { + b.logger.Infof("[SQL] %v", sql) + } + } +} + +func (b *Base) SetParams(params map[string]string) { +} + +var ( + dialects = map[string]func() Dialect{} +) + +// RegisterDialect register database dialect +func RegisterDialect(dbName DBType, dialectFunc func() Dialect) { + if dialectFunc == nil { + panic("core: Register dialect is nil") + } + dialects[strings.ToLower(string(dbName))] = dialectFunc // !nashtsai! allow override dialect +} + +// QueryDialect query if registered database dialect +func QueryDialect(dbName DBType) Dialect { + if d, ok := dialects[strings.ToLower(string(dbName))]; ok { + return d() + } + return nil +} + +func regDrvsNDialects() bool { + providedDrvsNDialects := map[string]struct { + dbType DBType + getDriver func() Driver + getDialect func() Dialect + }{ + "mssql": {"mssql", func() Driver { return &odbcDriver{} }, func() Dialect { return &mssql{} }}, + "odbc": {"mssql", func() Driver { return &odbcDriver{} }, func() Dialect { return &mssql{} }}, // !nashtsai! TODO change this when supporting MS Access + "mysql": {"mysql", func() Driver { return &mysqlDriver{} }, func() Dialect { return &mysql{} }}, + "mymysql": {"mysql", func() Driver { return &mymysqlDriver{} }, func() Dialect { return &mysql{} }}, + "postgres": {"postgres", func() Driver { return &pqDriver{} }, func() Dialect { return &postgres{} }}, + "pgx": {"postgres", func() Driver { return &pqDriverPgx{} }, func() Dialect { return &postgres{} }}, + "sqlite3": {"sqlite3", func() Driver { return &sqlite3Driver{} }, func() Dialect { return &sqlite3{} }}, + "oci8": {"oracle", func() Driver { return &oci8Driver{} }, func() Dialect { return &oracle{} }}, + "goracle": {"oracle", func() Driver { return &goracleDriver{} }, func() Dialect { return &oracle{} }}, + } + + for driverName, v := range providedDrvsNDialects { + if driver := QueryDriver(driverName); driver == nil { + RegisterDriver(driverName, v.getDriver()) + RegisterDialect(v.dbType, v.getDialect) + } + } + return true +} + +func init() { + regDrvsNDialects() +} diff --git a/dialects/driver.go b/dialects/driver.go new file mode 100644 index 00000000..5343d594 --- /dev/null +++ b/dialects/driver.go @@ -0,0 +1,31 @@ +// Copyright 2019 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package dialects + +type Driver interface { + Parse(string, string) (*URI, error) +} + +var ( + drivers = map[string]Driver{} +) + +func RegisterDriver(driverName string, driver Driver) { + if driver == nil { + panic("core: Register driver is nil") + } + if _, dup := drivers[driverName]; dup { + panic("core: Register called twice for driver " + driverName) + } + drivers[driverName] = driver +} + +func QueryDriver(driverName string) Driver { + return drivers[driverName] +} + +func RegisteredDriverSize() int { + return len(drivers) +} diff --git a/dialects/filter.go b/dialects/filter.go new file mode 100644 index 00000000..f7bad1a9 --- /dev/null +++ b/dialects/filter.go @@ -0,0 +1,95 @@ +// Copyright 2019 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package dialects + +import ( + "fmt" + "strings" + + "xorm.io/xorm/schemas" +) + +// Filter is an interface to filter SQL +type Filter interface { + Do(sql string, dialect Dialect, table *schemas.Table) string +} + +// QuoteFilter filter SQL replace ` to database's own quote character +type QuoteFilter struct { +} + +func (s *QuoteFilter) Do(sql string, dialect Dialect, table *schemas.Table) string { + dummy := dialect.Quote("") + if len(dummy) != 2 { + return sql + } + prefix, suffix := dummy[0], dummy[1] + raw := []byte(sql) + for i, cnt := 0, 0; i < len(raw); i = i + 1 { + if raw[i] == '`' { + if cnt%2 == 0 { + raw[i] = prefix + } else { + raw[i] = suffix + } + cnt++ + } + } + return string(raw) +} + +// IdFilter filter SQL replace (id) to primary key column name +type IdFilter struct { +} + +type Quoter struct { + dialect Dialect +} + +func NewQuoter(dialect Dialect) *Quoter { + return &Quoter{dialect} +} + +func (q *Quoter) Quote(content string) string { + return q.dialect.Quote(content) +} + +func (i *IdFilter) Do(sql string, dialect Dialect, table *schemas.Table) string { + quoter := NewQuoter(dialect) + if table != nil && len(table.PrimaryKeys) == 1 { + sql = strings.Replace(sql, " `(id)` ", " "+quoter.Quote(table.PrimaryKeys[0])+" ", -1) + sql = strings.Replace(sql, " "+quoter.Quote("(id)")+" ", " "+quoter.Quote(table.PrimaryKeys[0])+" ", -1) + return strings.Replace(sql, " (id) ", " "+quoter.Quote(table.PrimaryKeys[0])+" ", -1) + } + return sql +} + +// SeqFilter filter SQL replace ?, ? ... to $1, $2 ... +type SeqFilter struct { + Prefix string + Start int +} + +func convertQuestionMark(sql, prefix string, start int) string { + var buf strings.Builder + var beginSingleQuote bool + var index = start + for _, c := range sql { + if !beginSingleQuote && c == '?' { + buf.WriteString(fmt.Sprintf("%s%v", prefix, index)) + index++ + } else { + if c == '\'' { + beginSingleQuote = !beginSingleQuote + } + buf.WriteRune(c) + } + } + return buf.String() +} + +func (s *SeqFilter) Do(sql string, dialect Dialect, table *schemas.Table) string { + return convertQuestionMark(sql, s.Prefix, s.Start) +} diff --git a/dialects/filter_test.go b/dialects/filter_test.go new file mode 100644 index 00000000..e5430bab --- /dev/null +++ b/dialects/filter_test.go @@ -0,0 +1,39 @@ +package dialects + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +type quoterOnly struct { + Dialect +} + +func (q *quoterOnly) Quote(item string) string { + return "[" + item + "]" +} + +func TestQuoteFilter_Do(t *testing.T) { + f := QuoteFilter{} + sql := "SELECT `COLUMN_NAME` FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ? AND `COLUMN_NAME` = ?" + res := f.Do(sql, new(quoterOnly), nil) + assert.EqualValues(t, + "SELECT [COLUMN_NAME] FROM [INFORMATION_SCHEMA].[COLUMNS] WHERE [TABLE_SCHEMA] = ? AND [TABLE_NAME] = ? AND [COLUMN_NAME] = ?", + res, + ) +} + +func TestSeqFilter(t *testing.T) { + var kases = map[string]string{ + "SELECT * FROM TABLE1 WHERE a=? AND b=?": "SELECT * FROM TABLE1 WHERE a=$1 AND b=$2", + "SELECT 1, '???', '2006-01-02 15:04:05' FROM TABLE1 WHERE a=? AND b=?": "SELECT 1, '???', '2006-01-02 15:04:05' FROM TABLE1 WHERE a=$1 AND b=$2", + "select '1''?' from issue": "select '1''?' from issue", + "select '1\\??' from issue": "select '1\\??' from issue", + "select '1\\\\',? from issue": "select '1\\\\',$1 from issue", + "select '1\\''?',? from issue": "select '1\\''?',$1 from issue", + } + for sql, result := range kases { + assert.EqualValues(t, result, convertQuestionMark(sql, "$", 1)) + } +} diff --git a/dialect_mssql.go b/dialects/mssql.go similarity index 86% rename from dialect_mssql.go rename to dialects/mssql.go index 29070da2..99b1c782 100644 --- a/dialect_mssql.go +++ b/dialects/mssql.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package dialects import ( "errors" @@ -11,7 +11,8 @@ import ( "strconv" "strings" - "xorm.io/core" + "xorm.io/xorm/core" + "xorm.io/xorm/schemas" ) var ( @@ -205,64 +206,64 @@ var ( ) type mssql struct { - core.Base + Base } -func (db *mssql) Init(d *core.DB, uri *core.Uri, drivername, dataSourceName string) error { +func (db *mssql) Init(d *core.DB, uri *URI, drivername, dataSourceName string) error { return db.Base.Init(d, db, uri, drivername, dataSourceName) } -func (db *mssql) SqlType(c *core.Column) string { +func (db *mssql) SQLType(c *schemas.Column) string { var res string switch t := c.SQLType.Name; t { - case core.Bool: - res = core.Bit + case schemas.Bool: + res = schemas.Bit if strings.EqualFold(c.Default, "true") { c.Default = "1" } else if strings.EqualFold(c.Default, "false") { c.Default = "0" } - case core.Serial: + case schemas.Serial: c.IsAutoIncrement = true c.IsPrimaryKey = true c.Nullable = false - res = core.Int - case core.BigSerial: + res = schemas.Int + case schemas.BigSerial: c.IsAutoIncrement = true c.IsPrimaryKey = true c.Nullable = false - res = core.BigInt - case core.Bytea, core.Blob, core.Binary, core.TinyBlob, core.MediumBlob, core.LongBlob: - res = core.VarBinary + res = schemas.BigInt + case schemas.Bytea, schemas.Blob, schemas.Binary, schemas.TinyBlob, schemas.MediumBlob, schemas.LongBlob: + res = schemas.VarBinary if c.Length == 0 { c.Length = 50 } - case core.TimeStamp: - res = core.DateTime - case core.TimeStampz: + case schemas.TimeStamp: + res = schemas.DateTime + case schemas.TimeStampz: res = "DATETIMEOFFSET" c.Length = 7 - case core.MediumInt: - res = core.Int - case core.Text, core.MediumText, core.TinyText, core.LongText, core.Json: - res = core.Varchar + "(MAX)" - case core.Double: - res = core.Real - case core.Uuid: - res = core.Varchar + case schemas.MediumInt: + res = schemas.Int + case schemas.Text, schemas.MediumText, schemas.TinyText, schemas.LongText, schemas.Json: + res = schemas.Varchar + "(MAX)" + case schemas.Double: + res = schemas.Real + case schemas.Uuid: + res = schemas.Varchar c.Length = 40 - case core.TinyInt: - res = core.TinyInt + case schemas.TinyInt: + res = schemas.TinyInt c.Length = 0 - case core.BigInt: - res = core.BigInt + case schemas.BigInt: + res = schemas.BigInt c.Length = 0 default: res = t } - if res == core.Int { - return core.Int + if res == schemas.Int { + return schemas.Int } hasLen1 := (c.Length > 0) @@ -297,7 +298,7 @@ func (db *mssql) AutoIncrStr() string { return "IDENTITY" } -func (db *mssql) DropTableSql(tableName string) string { +func (db *mssql) DropTableSQL(tableName string) string { return fmt.Sprintf("IF EXISTS (SELECT * FROM sysobjects WHERE id = "+ "object_id(N'%s') and OBJECTPROPERTY(id, N'IsUserTable') = 1) "+ "DROP TABLE \"%s\"", tableName, tableName) @@ -311,7 +312,7 @@ func (db *mssql) IndexOnTable() bool { return true } -func (db *mssql) IndexCheckSql(tableName, idxName string) (string, []interface{}) { +func (db *mssql) IndexCheckSQL(tableName, idxName string) (string, []interface{}) { args := []interface{}{idxName} sql := "select name from sysindexes where id=object_id('" + tableName + "') and name=?" return sql, args @@ -329,13 +330,13 @@ func (db *mssql) IsColumnExist(tableName, colName string) (bool, error) { return db.HasRecords(query, tableName, colName) } -func (db *mssql) TableCheckSql(tableName string) (string, []interface{}) { +func (db *mssql) TableCheckSQL(tableName string) (string, []interface{}) { args := []interface{}{} sql := "select * from sysobjects where id = object_id(N'" + tableName + "') and OBJECTPROPERTY(id, N'IsUserTable') = 1" return sql, args } -func (db *mssql) GetColumns(tableName string) ([]string, map[string]*core.Column, error) { +func (db *mssql) GetColumns(tableName string) ([]string, map[string]*schemas.Column, error) { args := []interface{}{} s := `select a.name as name, b.name as ctype,a.max_length,a.precision,a.scale,a.is_nullable as nullable, "default_is_null" = (CASE WHEN c.text is null THEN 1 ELSE 0 END), @@ -357,7 +358,7 @@ func (db *mssql) GetColumns(tableName string) ([]string, map[string]*core.Column } defer rows.Close() - cols := make(map[string]*core.Column) + cols := make(map[string]*schemas.Column) colSeq := make([]string, 0) for rows.Next() { var name, ctype, vdefault string @@ -368,7 +369,7 @@ func (db *mssql) GetColumns(tableName string) ([]string, map[string]*core.Column return nil, nil, err } - col := new(core.Column) + col := new(schemas.Column) col.Indexes = make(map[string]int) col.Name = strings.Trim(name, "` ") col.Nullable = nullable @@ -387,14 +388,14 @@ func (db *mssql) GetColumns(tableName string) ([]string, map[string]*core.Column } switch ct { case "DATETIMEOFFSET": - col.SQLType = core.SQLType{Name: core.TimeStampz, DefaultLength: 0, DefaultLength2: 0} + col.SQLType = schemas.SQLType{Name: schemas.TimeStampz, DefaultLength: 0, DefaultLength2: 0} case "NVARCHAR": - col.SQLType = core.SQLType{Name: core.NVarchar, DefaultLength: 0, DefaultLength2: 0} + col.SQLType = schemas.SQLType{Name: schemas.NVarchar, DefaultLength: 0, DefaultLength2: 0} case "IMAGE": - col.SQLType = core.SQLType{Name: core.VarBinary, DefaultLength: 0, DefaultLength2: 0} + col.SQLType = schemas.SQLType{Name: schemas.VarBinary, DefaultLength: 0, DefaultLength2: 0} default: - if _, ok := core.SqlTypes[ct]; ok { - col.SQLType = core.SQLType{Name: ct, DefaultLength: 0, DefaultLength2: 0} + if _, ok := schemas.SqlTypes[ct]; ok { + col.SQLType = schemas.SQLType{Name: ct, DefaultLength: 0, DefaultLength2: 0} } else { return nil, nil, fmt.Errorf("Unknown colType %v for %v - %v", ct, tableName, col.Name) } @@ -406,7 +407,7 @@ func (db *mssql) GetColumns(tableName string) ([]string, map[string]*core.Column return colSeq, cols, nil } -func (db *mssql) GetTables() ([]*core.Table, error) { +func (db *mssql) GetTables() ([]*schemas.Table, error) { args := []interface{}{} s := `select name from sysobjects where xtype ='U'` db.LogSQL(s, args) @@ -417,9 +418,9 @@ func (db *mssql) GetTables() ([]*core.Table, error) { } defer rows.Close() - tables := make([]*core.Table, 0) + tables := make([]*schemas.Table, 0) for rows.Next() { - table := core.NewEmptyTable() + table := schemas.NewEmptyTable() var name string err = rows.Scan(&name) if err != nil { @@ -431,7 +432,7 @@ func (db *mssql) GetTables() ([]*core.Table, error) { return tables, nil } -func (db *mssql) GetIndexes(tableName string) (map[string]*core.Index, error) { +func (db *mssql) GetIndexes(tableName string) (map[string]*schemas.Index, error) { args := []interface{}{tableName} s := `SELECT IXS.NAME AS [INDEX_NAME], @@ -452,7 +453,7 @@ WHERE IXS.TYPE_DESC='NONCLUSTERED' and OBJECT_NAME(IXS.OBJECT_ID) =? } defer rows.Close() - indexes := make(map[string]*core.Index, 0) + indexes := make(map[string]*schemas.Index, 0) for rows.Next() { var indexType int var indexName, colName, isUnique string @@ -468,9 +469,9 @@ WHERE IXS.TYPE_DESC='NONCLUSTERED' and OBJECT_NAME(IXS.OBJECT_ID) =? } if i { - indexType = core.UniqueType + indexType = schemas.UniqueType } else { - indexType = core.IndexType + indexType = schemas.IndexType } colName = strings.Trim(colName, "` ") @@ -480,10 +481,10 @@ WHERE IXS.TYPE_DESC='NONCLUSTERED' and OBJECT_NAME(IXS.OBJECT_ID) =? isRegular = true } - var index *core.Index + var index *schemas.Index var ok bool if index, ok = indexes[indexName]; !ok { - index = new(core.Index) + index = new(schemas.Index) index.Type = indexType index.Name = indexName index.IsRegular = isRegular @@ -494,7 +495,7 @@ WHERE IXS.TYPE_DESC='NONCLUSTERED' and OBJECT_NAME(IXS.OBJECT_ID) =? return indexes, nil } -func (db *mssql) CreateTableSql(table *core.Table, tableName, storeEngine, charset string) string { +func (db *mssql) CreateTableSQL(table *schemas.Table, tableName, storeEngine, charset string) string { var sql string if tableName == "" { tableName = table.Name @@ -509,9 +510,9 @@ func (db *mssql) CreateTableSql(table *core.Table, tableName, storeEngine, chars for _, colName := range table.ColumnsSeq() { col := table.GetColumn(colName) if col.IsPrimaryKey && len(pkList) == 1 { - sql += col.String(db) + sql += String(db, col) } else { - sql += col.StringNoPk(db) + sql += StringNoPk(db, col) } sql = strings.TrimSpace(sql) sql += ", " @@ -528,18 +529,18 @@ func (db *mssql) CreateTableSql(table *core.Table, tableName, storeEngine, chars return sql } -func (db *mssql) ForUpdateSql(query string) string { +func (db *mssql) ForUpdateSQL(query string) string { return query } -func (db *mssql) Filters() []core.Filter { - return []core.Filter{&core.IdFilter{}, &core.QuoteFilter{}} +func (db *mssql) Filters() []Filter { + return []Filter{&IdFilter{}, &QuoteFilter{}} } type odbcDriver struct { } -func (p *odbcDriver) Parse(driverName, dataSourceName string) (*core.Uri, error) { +func (p *odbcDriver) Parse(driverName, dataSourceName string) (*URI, error) { var dbName string if strings.HasPrefix(dataSourceName, "sqlserver://") { @@ -563,5 +564,5 @@ func (p *odbcDriver) Parse(driverName, dataSourceName string) (*core.Uri, error) if dbName == "" { return nil, errors.New("no db name provided") } - return &core.Uri{DbName: dbName, DbType: core.MSSQL}, nil + return &URI{DBName: dbName, DBType: schemas.MSSQL}, nil } diff --git a/dialect_mssql_test.go b/dialects/mssql_test.go similarity index 84% rename from dialect_mssql_test.go rename to dialects/mssql_test.go index acd1d059..168f1777 100644 --- a/dialect_mssql_test.go +++ b/dialects/mssql_test.go @@ -2,13 +2,11 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package dialects import ( "reflect" "testing" - - "xorm.io/core" ) func TestParseMSSQL(t *testing.T) { @@ -21,15 +19,15 @@ func TestParseMSSQL(t *testing.T) { {"server=localhost;user id=sa;password=yourStrong(!)Password;database=db", "db", true}, } - driver := core.QueryDriver("mssql") + driver := QueryDriver("mssql") for _, test := range tests { uri, err := driver.Parse("mssql", test.in) if err != nil && test.valid { t.Errorf("%q got unexpected error: %s", test.in, err) - } else if err == nil && !reflect.DeepEqual(test.expected, uri.DbName) { - t.Errorf("%q got: %#v want: %#v", test.in, uri.DbName, test.expected) + } else if err == nil && !reflect.DeepEqual(test.expected, uri.DBName) { + t.Errorf("%q got: %#v want: %#v", test.in, uri.DBName, test.expected) } } } diff --git a/dialect_mysql.go b/dialects/mysql.go similarity index 86% rename from dialect_mysql.go rename to dialects/mysql.go index cf1dbb6f..39ed4b83 100644 --- a/dialect_mysql.go +++ b/dialects/mysql.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package dialects import ( "crypto/tls" @@ -13,7 +13,8 @@ import ( "strings" "time" - "xorm.io/core" + "xorm.io/xorm/core" + "xorm.io/xorm/schemas" ) var ( @@ -162,7 +163,7 @@ var ( ) type mysql struct { - core.Base + Base net string addr string params map[string]string @@ -175,7 +176,7 @@ type mysql struct { rowFormat string } -func (db *mysql) Init(d *core.DB, uri *core.Uri, drivername, dataSourceName string) error { +func (db *mysql) Init(d *core.DB, uri *URI, drivername, dataSourceName string) error { return db.Base.Init(d, db, uri, drivername, dataSourceName) } @@ -199,29 +200,29 @@ func (db *mysql) SetParams(params map[string]string) { } } -func (db *mysql) SqlType(c *core.Column) string { +func (db *mysql) SQLType(c *schemas.Column) string { var res string switch t := c.SQLType.Name; t { - case core.Bool: - res = core.TinyInt + case schemas.Bool: + res = schemas.TinyInt c.Length = 1 - case core.Serial: + case schemas.Serial: c.IsAutoIncrement = true c.IsPrimaryKey = true c.Nullable = false - res = core.Int - case core.BigSerial: + res = schemas.Int + case schemas.BigSerial: c.IsAutoIncrement = true c.IsPrimaryKey = true c.Nullable = false - res = core.BigInt - case core.Bytea: - res = core.Blob - case core.TimeStampz: - res = core.Char + res = schemas.BigInt + case schemas.Bytea: + res = schemas.Blob + case schemas.TimeStampz: + res = schemas.Char c.Length = 64 - case core.Enum: // mysql enum - res = core.Enum + case schemas.Enum: // mysql enum + res = schemas.Enum res += "(" opts := "" for v := range c.EnumOptions { @@ -229,8 +230,8 @@ func (db *mysql) SqlType(c *core.Column) string { } res += strings.TrimLeft(opts, ",") res += ")" - case core.Set: // mysql set - res = core.Set + case schemas.Set: // mysql set + res = schemas.Set res += "(" opts := "" for v := range c.SetOptions { @@ -238,13 +239,13 @@ func (db *mysql) SqlType(c *core.Column) string { } res += strings.TrimLeft(opts, ",") res += ")" - case core.NVarchar: - res = core.Varchar - case core.Uuid: - res = core.Varchar + case schemas.NVarchar: + res = schemas.Varchar + case schemas.Uuid: + res = schemas.Varchar c.Length = 40 - case core.Json: - res = core.Text + case schemas.Json: + res = schemas.Text default: res = t } @@ -252,7 +253,7 @@ func (db *mysql) SqlType(c *core.Column) string { hasLen1 := (c.Length > 0) hasLen2 := (c.Length2 > 0) - if res == core.BigInt && !hasLen1 && !hasLen2 { + if res == schemas.BigInt && !hasLen1 && !hasLen2 { c.Length = 20 hasLen1 = true } @@ -294,8 +295,8 @@ func (db *mysql) IndexOnTable() bool { return true } -func (db *mysql) IndexCheckSql(tableName, idxName string) (string, []interface{}) { - args := []interface{}{db.DbName, tableName, idxName} +func (db *mysql) IndexCheckSQL(tableName, idxName string) (string, []interface{}) { + args := []interface{}{db.uri.DBName, tableName, idxName} sql := "SELECT `INDEX_NAME` FROM `INFORMATION_SCHEMA`.`STATISTICS`" sql += " WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ? AND `INDEX_NAME`=?" return sql, args @@ -307,14 +308,14 @@ func (db *mysql) IndexCheckSql(tableName, idxName string) (string, []interface{} return sql, args }*/ -func (db *mysql) TableCheckSql(tableName string) (string, []interface{}) { - args := []interface{}{db.DbName, tableName} +func (db *mysql) TableCheckSQL(tableName string) (string, []interface{}) { + args := []interface{}{db.uri.DBName, tableName} sql := "SELECT `TABLE_NAME` from `INFORMATION_SCHEMA`.`TABLES` WHERE `TABLE_SCHEMA`=? and `TABLE_NAME`=?" return sql, args } -func (db *mysql) GetColumns(tableName string) ([]string, map[string]*core.Column, error) { - args := []interface{}{db.DbName, tableName} +func (db *mysql) GetColumns(tableName string) ([]string, map[string]*schemas.Column, error) { + args := []interface{}{db.uri.DBName, tableName} s := "SELECT `COLUMN_NAME`, `IS_NULLABLE`, `COLUMN_DEFAULT`, `COLUMN_TYPE`," + " `COLUMN_KEY`, `EXTRA`,`COLUMN_COMMENT` FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ?" db.LogSQL(s, args) @@ -325,10 +326,10 @@ func (db *mysql) GetColumns(tableName string) ([]string, map[string]*core.Column } defer rows.Close() - cols := make(map[string]*core.Column) + cols := make(map[string]*schemas.Column) colSeq := make([]string, 0) for rows.Next() { - col := new(core.Column) + col := new(schemas.Column) col.Indexes = make(map[string]int) var columnName, isNullable, colType, colKey, extra, comment string @@ -356,7 +357,7 @@ func (db *mysql) GetColumns(tableName string) ([]string, map[string]*core.Column var len1, len2 int if len(cts) == 2 { idx := strings.Index(cts[1], ")") - if colType == core.Enum && cts[1][0] == '\'' { // enum + if colType == schemas.Enum && cts[1][0] == '\'' { // enum options := strings.Split(cts[1][0:idx], ",") col.EnumOptions = make(map[string]int) for k, v := range options { @@ -364,7 +365,7 @@ func (db *mysql) GetColumns(tableName string) ([]string, map[string]*core.Column v = strings.Trim(v, "'") col.EnumOptions[v] = k } - } else if colType == core.Set && cts[1][0] == '\'' { + } else if colType == schemas.Set && cts[1][0] == '\'' { options := strings.Split(cts[1][0:idx], ",") col.SetOptions = make(map[string]int) for k, v := range options { @@ -394,8 +395,8 @@ func (db *mysql) GetColumns(tableName string) ([]string, map[string]*core.Column } col.Length = len1 col.Length2 = len2 - if _, ok := core.SqlTypes[colType]; ok { - col.SQLType = core.SQLType{Name: colType, DefaultLength: len1, DefaultLength2: len2} + if _, ok := schemas.SqlTypes[colType]; ok { + col.SQLType = schemas.SQLType{Name: colType, DefaultLength: len1, DefaultLength2: len2} } else { return nil, nil, fmt.Errorf("Unknown colType %v", colType) } @@ -424,8 +425,8 @@ func (db *mysql) GetColumns(tableName string) ([]string, map[string]*core.Column return colSeq, cols, nil } -func (db *mysql) GetTables() ([]*core.Table, error) { - args := []interface{}{db.DbName} +func (db *mysql) GetTables() ([]*schemas.Table, error) { + args := []interface{}{db.uri.DBName} s := "SELECT `TABLE_NAME`, `ENGINE`, `TABLE_ROWS`, `AUTO_INCREMENT`, `TABLE_COMMENT` from " + "`INFORMATION_SCHEMA`.`TABLES` WHERE `TABLE_SCHEMA`=? AND (`ENGINE`='MyISAM' OR `ENGINE` = 'InnoDB' OR `ENGINE` = 'TokuDB')" db.LogSQL(s, args) @@ -436,9 +437,9 @@ func (db *mysql) GetTables() ([]*core.Table, error) { } defer rows.Close() - tables := make([]*core.Table, 0) + tables := make([]*schemas.Table, 0) for rows.Next() { - table := core.NewEmptyTable() + table := schemas.NewEmptyTable() var name, engine, tableRows, comment string var autoIncr *string err = rows.Scan(&name, &engine, &tableRows, &autoIncr, &comment) @@ -454,8 +455,8 @@ func (db *mysql) GetTables() ([]*core.Table, error) { return tables, nil } -func (db *mysql) GetIndexes(tableName string) (map[string]*core.Index, error) { - args := []interface{}{db.DbName, tableName} +func (db *mysql) GetIndexes(tableName string) (map[string]*schemas.Index, error) { + args := []interface{}{db.uri.DBName, tableName} s := "SELECT `INDEX_NAME`, `NON_UNIQUE`, `COLUMN_NAME` FROM `INFORMATION_SCHEMA`.`STATISTICS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ?" db.LogSQL(s, args) @@ -465,7 +466,7 @@ func (db *mysql) GetIndexes(tableName string) (map[string]*core.Index, error) { } defer rows.Close() - indexes := make(map[string]*core.Index, 0) + indexes := make(map[string]*schemas.Index, 0) for rows.Next() { var indexType int var indexName, colName, nonUnique string @@ -479,9 +480,9 @@ func (db *mysql) GetIndexes(tableName string) (map[string]*core.Index, error) { } if "YES" == nonUnique || nonUnique == "1" { - indexType = core.IndexType + indexType = schemas.IndexType } else { - indexType = core.UniqueType + indexType = schemas.UniqueType } colName = strings.Trim(colName, "` ") @@ -491,10 +492,10 @@ func (db *mysql) GetIndexes(tableName string) (map[string]*core.Index, error) { isRegular = true } - var index *core.Index + var index *schemas.Index var ok bool if index, ok = indexes[indexName]; !ok { - index = new(core.Index) + index = new(schemas.Index) index.IsRegular = isRegular index.Type = indexType index.Name = indexName @@ -505,7 +506,7 @@ func (db *mysql) GetIndexes(tableName string) (map[string]*core.Index, error) { return indexes, nil } -func (db *mysql) CreateTableSql(table *core.Table, tableName, storeEngine, charset string) string { +func (db *mysql) CreateTableSQL(table *schemas.Table, tableName, storeEngine, charset string) string { var sql string sql = "CREATE TABLE IF NOT EXISTS " if tableName == "" { @@ -521,9 +522,9 @@ func (db *mysql) CreateTableSql(table *core.Table, tableName, storeEngine, chars for _, colName := range table.ColumnsSeq() { col := table.GetColumn(colName) if col.IsPrimaryKey && len(pkList) == 1 { - sql += col.String(db) + sql += String(db, col) } else { - sql += col.StringNoPk(db) + sql += StringNoPk(db, col) } sql = strings.TrimSpace(sql) if len(col.Comment) > 0 { @@ -559,15 +560,15 @@ func (db *mysql) CreateTableSql(table *core.Table, tableName, storeEngine, chars return sql } -func (db *mysql) Filters() []core.Filter { - return []core.Filter{&core.IdFilter{}} +func (db *mysql) Filters() []Filter { + return []Filter{&IdFilter{}} } type mymysqlDriver struct { } -func (p *mymysqlDriver) Parse(driverName, dataSourceName string) (*core.Uri, error) { - db := &core.Uri{DbType: core.MYSQL} +func (p *mymysqlDriver) Parse(driverName, dataSourceName string) (*URI, error) { + uri := &URI{DBType: schemas.MYSQL} pd := strings.SplitN(dataSourceName, "*", 2) if len(pd) == 2 { @@ -576,9 +577,9 @@ func (p *mymysqlDriver) Parse(driverName, dataSourceName string) (*core.Uri, err if len(p) != 2 { return nil, errors.New("Wrong protocol part of URI") } - db.Proto = p[0] + uri.Proto = p[0] options := strings.Split(p[1], ",") - db.Raddr = options[0] + uri.Raddr = options[0] for _, o := range options[1:] { kv := strings.SplitN(o, "=", 2) var k, v string @@ -589,13 +590,13 @@ func (p *mymysqlDriver) Parse(driverName, dataSourceName string) (*core.Uri, err } switch k { case "laddr": - db.Laddr = v + uri.Laddr = v case "timeout": to, err := time.ParseDuration(v) if err != nil { return nil, err } - db.Timeout = to + uri.Timeout = to default: return nil, errors.New("Unknown option: " + k) } @@ -608,17 +609,17 @@ func (p *mymysqlDriver) Parse(driverName, dataSourceName string) (*core.Uri, err if len(dup) != 3 { return nil, errors.New("Wrong database part of URI") } - db.DbName = dup[0] - db.User = dup[1] - db.Passwd = dup[2] + uri.DBName = dup[0] + uri.User = dup[1] + uri.Passwd = dup[2] - return db, nil + return uri, nil } type mysqlDriver struct { } -func (p *mysqlDriver) Parse(driverName, dataSourceName string) (*core.Uri, error) { +func (p *mysqlDriver) Parse(driverName, dataSourceName string) (*URI, error) { dsnPattern := regexp.MustCompile( `^(?:(?P.*?)(?::(?P.*))?@)?` + // [user[:password]@] `(?:(?P[^\(]*)(?:\((?P[^\)]*)\))?)?` + // [net[(addr)]] @@ -628,12 +629,12 @@ func (p *mysqlDriver) Parse(driverName, dataSourceName string) (*core.Uri, error // tlsConfigRegister := make(map[string]*tls.Config) names := dsnPattern.SubexpNames() - uri := &core.Uri{DbType: core.MYSQL} + uri := &URI{DBType: schemas.MYSQL} for i, match := range matches { switch names[i] { case "dbname": - uri.DbName = match + uri.DBName = match case "params": if len(match) > 0 { kvs := strings.Split(match, "&") diff --git a/dialect_oracle.go b/dialects/oracle.go similarity index 90% rename from dialect_oracle.go rename to dialects/oracle.go index 15010ca5..501a22c7 100644 --- a/dialect_oracle.go +++ b/dialects/oracle.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package dialects import ( "errors" @@ -11,7 +11,8 @@ import ( "strconv" "strings" - "xorm.io/core" + "xorm.io/xorm/core" + "xorm.io/xorm/schemas" ) var ( @@ -499,29 +500,29 @@ var ( ) type oracle struct { - core.Base + Base } -func (db *oracle) Init(d *core.DB, uri *core.Uri, drivername, dataSourceName string) error { +func (db *oracle) Init(d *core.DB, uri *URI, drivername, dataSourceName string) error { return db.Base.Init(d, db, uri, drivername, dataSourceName) } -func (db *oracle) SqlType(c *core.Column) string { +func (db *oracle) SQLType(c *schemas.Column) string { var res string switch t := c.SQLType.Name; t { - case core.Bit, core.TinyInt, core.SmallInt, core.MediumInt, core.Int, core.Integer, core.BigInt, core.Bool, core.Serial, core.BigSerial: + case schemas.Bit, schemas.TinyInt, schemas.SmallInt, schemas.MediumInt, schemas.Int, schemas.Integer, schemas.BigInt, schemas.Bool, schemas.Serial, schemas.BigSerial: res = "NUMBER" - case core.Binary, core.VarBinary, core.Blob, core.TinyBlob, core.MediumBlob, core.LongBlob, core.Bytea: - return core.Blob - case core.Time, core.DateTime, core.TimeStamp: - res = core.TimeStamp - case core.TimeStampz: + case schemas.Binary, schemas.VarBinary, schemas.Blob, schemas.TinyBlob, schemas.MediumBlob, schemas.LongBlob, schemas.Bytea: + return schemas.Blob + case schemas.Time, schemas.DateTime, schemas.TimeStamp: + res = schemas.TimeStamp + case schemas.TimeStampz: res = "TIMESTAMP WITH TIME ZONE" - case core.Float, core.Double, core.Numeric, core.Decimal: + case schemas.Float, schemas.Double, schemas.Numeric, schemas.Decimal: res = "NUMBER" - case core.Text, core.MediumText, core.LongText, core.Json: + case schemas.Text, schemas.MediumText, schemas.LongText, schemas.Json: res = "CLOB" - case core.Char, core.Varchar, core.TinyText: + case schemas.Char, schemas.Varchar, schemas.TinyText: res = "VARCHAR2" default: res = t @@ -571,11 +572,11 @@ func (db *oracle) IndexOnTable() bool { return false } -func (db *oracle) DropTableSql(tableName string) string { +func (db *oracle) DropTableSQL(tableName string) string { return fmt.Sprintf("DROP TABLE `%s`", tableName) } -func (db *oracle) CreateTableSql(table *core.Table, tableName, storeEngine, charset string) string { +func (db *oracle) CreateTableSQL(table *schemas.Table, tableName, storeEngine, charset string) string { var sql string sql = "CREATE TABLE " if tableName == "" { @@ -591,7 +592,7 @@ func (db *oracle) CreateTableSql(table *core.Table, tableName, storeEngine, char /*if col.IsPrimaryKey && len(pkList) == 1 { sql += col.String(b.dialect) } else {*/ - sql += col.StringNoPk(db) + sql += StringNoPk(db, col) // } sql = strings.TrimSpace(sql) sql += ", " @@ -618,19 +619,19 @@ func (db *oracle) CreateTableSql(table *core.Table, tableName, storeEngine, char return sql } -func (db *oracle) IndexCheckSql(tableName, idxName string) (string, []interface{}) { +func (db *oracle) IndexCheckSQL(tableName, idxName string) (string, []interface{}) { args := []interface{}{tableName, idxName} return `SELECT INDEX_NAME FROM USER_INDEXES ` + `WHERE TABLE_NAME = :1 AND INDEX_NAME = :2`, args } -func (db *oracle) TableCheckSql(tableName string) (string, []interface{}) { +func (db *oracle) TableCheckSQL(tableName string) (string, []interface{}) { args := []interface{}{tableName} return `SELECT table_name FROM user_tables WHERE table_name = :1`, args } func (db *oracle) MustDropTable(tableName string) error { - sql, args := db.TableCheckSql(tableName) + sql, args := db.TableCheckSQL(tableName) db.LogSQL(sql, args) rows, err := db.DB().Query(sql, args...) @@ -674,7 +675,7 @@ func (db *oracle) IsColumnExist(tableName, colName string) (bool, error) { return false, nil } -func (db *oracle) GetColumns(tableName string) ([]string, map[string]*core.Column, error) { +func (db *oracle) GetColumns(tableName string) ([]string, map[string]*schemas.Column, error) { args := []interface{}{tableName} s := "SELECT column_name,data_default,data_type,data_length,data_precision,data_scale," + "nullable FROM USER_TAB_COLUMNS WHERE table_name = :1" @@ -686,10 +687,10 @@ func (db *oracle) GetColumns(tableName string) ([]string, map[string]*core.Colum } defer rows.Close() - cols := make(map[string]*core.Column) + cols := make(map[string]*schemas.Column) colSeq := make([]string, 0) for rows.Next() { - col := new(core.Column) + col := new(schemas.Column) col.Indexes = make(map[string]int) var colName, colDefault, nullable, dataType, dataPrecision, dataScale *string @@ -731,30 +732,30 @@ func (db *oracle) GetColumns(tableName string) ([]string, map[string]*core.Colum switch dt { case "VARCHAR2": - col.SQLType = core.SQLType{Name: core.Varchar, DefaultLength: len1, DefaultLength2: len2} + col.SQLType = schemas.SQLType{Name: schemas.Varchar, DefaultLength: len1, DefaultLength2: len2} case "NVARCHAR2": - col.SQLType = core.SQLType{Name: core.NVarchar, DefaultLength: len1, DefaultLength2: len2} + col.SQLType = schemas.SQLType{Name: schemas.NVarchar, DefaultLength: len1, DefaultLength2: len2} case "TIMESTAMP WITH TIME ZONE": - col.SQLType = core.SQLType{Name: core.TimeStampz, DefaultLength: 0, DefaultLength2: 0} + col.SQLType = schemas.SQLType{Name: schemas.TimeStampz, DefaultLength: 0, DefaultLength2: 0} case "NUMBER": - col.SQLType = core.SQLType{Name: core.Double, DefaultLength: len1, DefaultLength2: len2} + col.SQLType = schemas.SQLType{Name: schemas.Double, DefaultLength: len1, DefaultLength2: len2} case "LONG", "LONG RAW": - col.SQLType = core.SQLType{Name: core.Text, DefaultLength: 0, DefaultLength2: 0} + col.SQLType = schemas.SQLType{Name: schemas.Text, DefaultLength: 0, DefaultLength2: 0} case "RAW": - col.SQLType = core.SQLType{Name: core.Binary, DefaultLength: 0, DefaultLength2: 0} + col.SQLType = schemas.SQLType{Name: schemas.Binary, DefaultLength: 0, DefaultLength2: 0} case "ROWID": - col.SQLType = core.SQLType{Name: core.Varchar, DefaultLength: 18, DefaultLength2: 0} + col.SQLType = schemas.SQLType{Name: schemas.Varchar, DefaultLength: 18, DefaultLength2: 0} case "AQ$_SUBSCRIBERS": ignore = true default: - col.SQLType = core.SQLType{Name: strings.ToUpper(dt), DefaultLength: len1, DefaultLength2: len2} + col.SQLType = schemas.SQLType{Name: strings.ToUpper(dt), DefaultLength: len1, DefaultLength2: len2} } if ignore { continue } - if _, ok := core.SqlTypes[col.SQLType.Name]; !ok { + if _, ok := schemas.SqlTypes[col.SQLType.Name]; !ok { return nil, nil, fmt.Errorf("Unknown colType %v %v", *dataType, col.SQLType) } @@ -772,7 +773,7 @@ func (db *oracle) GetColumns(tableName string) ([]string, map[string]*core.Colum return colSeq, cols, nil } -func (db *oracle) GetTables() ([]*core.Table, error) { +func (db *oracle) GetTables() ([]*schemas.Table, error) { args := []interface{}{} s := "SELECT table_name FROM user_tables" db.LogSQL(s, args) @@ -783,9 +784,9 @@ func (db *oracle) GetTables() ([]*core.Table, error) { } defer rows.Close() - tables := make([]*core.Table, 0) + tables := make([]*schemas.Table, 0) for rows.Next() { - table := core.NewEmptyTable() + table := schemas.NewEmptyTable() err = rows.Scan(&table.Name) if err != nil { return nil, err @@ -796,7 +797,7 @@ func (db *oracle) GetTables() ([]*core.Table, error) { return tables, nil } -func (db *oracle) GetIndexes(tableName string) (map[string]*core.Index, error) { +func (db *oracle) GetIndexes(tableName string) (map[string]*schemas.Index, error) { args := []interface{}{tableName} s := "SELECT t.column_name,i.uniqueness,i.index_name FROM user_ind_columns t,user_indexes i " + "WHERE t.index_name = i.index_name and t.table_name = i.table_name and t.table_name =:1" @@ -808,7 +809,7 @@ func (db *oracle) GetIndexes(tableName string) (map[string]*core.Index, error) { } defer rows.Close() - indexes := make(map[string]*core.Index, 0) + indexes := make(map[string]*schemas.Index, 0) for rows.Next() { var indexType int var indexName, colName, uniqueness string @@ -827,15 +828,15 @@ func (db *oracle) GetIndexes(tableName string) (map[string]*core.Index, error) { } if uniqueness == "UNIQUE" { - indexType = core.UniqueType + indexType = schemas.UniqueType } else { - indexType = core.IndexType + indexType = schemas.IndexType } - var index *core.Index + var index *schemas.Index var ok bool if index, ok = indexes[indexName]; !ok { - index = new(core.Index) + index = new(schemas.Index) index.Type = indexType index.Name = indexName index.IsRegular = isRegular @@ -846,15 +847,15 @@ func (db *oracle) GetIndexes(tableName string) (map[string]*core.Index, error) { return indexes, nil } -func (db *oracle) Filters() []core.Filter { - return []core.Filter{&core.QuoteFilter{}, &core.SeqFilter{Prefix: ":", Start: 1}, &core.IdFilter{}} +func (db *oracle) Filters() []Filter { + return []Filter{&QuoteFilter{}, &SeqFilter{Prefix: ":", Start: 1}, &IdFilter{}} } type goracleDriver struct { } -func (cfg *goracleDriver) Parse(driverName, dataSourceName string) (*core.Uri, error) { - db := &core.Uri{DbType: core.ORACLE} +func (cfg *goracleDriver) Parse(driverName, dataSourceName string) (*URI, error) { + db := &URI{DBType: schemas.ORACLE} dsnPattern := regexp.MustCompile( `^(?:(?P.*?)(?::(?P.*))?@)?` + // [user[:password]@] `(?:(?P[^\(]*)(?:\((?P[^\)]*)\))?)?` + // [net[(addr)]] @@ -867,10 +868,10 @@ func (cfg *goracleDriver) Parse(driverName, dataSourceName string) (*core.Uri, e for i, match := range matches { switch names[i] { case "dbname": - db.DbName = match + db.DBName = match } } - if db.DbName == "" { + if db.DBName == "" { return nil, errors.New("dbname is empty") } return db, nil @@ -881,8 +882,8 @@ type oci8Driver struct { // dataSourceName=user/password@ipv4:port/dbname // dataSourceName=user/password@[ipv6]:port/dbname -func (p *oci8Driver) Parse(driverName, dataSourceName string) (*core.Uri, error) { - db := &core.Uri{DbType: core.ORACLE} +func (p *oci8Driver) Parse(driverName, dataSourceName string) (*URI, error) { + db := &URI{DBType: schemas.ORACLE} dsnPattern := regexp.MustCompile( `^(?P.*)\/(?P.*)@` + // user:password@ `(?P.*)` + // ip:port @@ -892,10 +893,10 @@ func (p *oci8Driver) Parse(driverName, dataSourceName string) (*core.Uri, error) for i, match := range matches { switch names[i] { case "dbname": - db.DbName = match + db.DBName = match } } - if db.DbName == "" { + if db.DBName == "" { return nil, errors.New("dbname is empty") } return db, nil diff --git a/dialect_postgres.go b/dialects/postgres.go similarity index 91% rename from dialect_postgres.go rename to dialects/postgres.go index ac6d4fe8..e4f4b89b 100644 --- a/dialect_postgres.go +++ b/dialects/postgres.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package dialects import ( "errors" @@ -11,7 +11,8 @@ import ( "strconv" "strings" - "xorm.io/core" + "xorm.io/xorm/core" + "xorm.io/xorm/schemas" ) // from http://www.postgresql.org/docs/current/static/sql-keywords-appendix.html @@ -769,67 +770,67 @@ var ( DefaultPostgresSchema = "public" ) -const postgresPublicSchema = "public" +const PostgresPublicSchema = "public" type postgres struct { - core.Base + Base } -func (db *postgres) Init(d *core.DB, uri *core.Uri, drivername, dataSourceName string) error { +func (db *postgres) Init(d *core.DB, uri *URI, drivername, dataSourceName string) error { err := db.Base.Init(d, db, uri, drivername, dataSourceName) if err != nil { return err } - if db.Schema == "" { - db.Schema = DefaultPostgresSchema + if db.uri.Schema == "" { + db.uri.Schema = DefaultPostgresSchema } return nil } -func (db *postgres) SqlType(c *core.Column) string { +func (db *postgres) SQLType(c *schemas.Column) string { var res string switch t := c.SQLType.Name; t { - case core.TinyInt: - res = core.SmallInt + case schemas.TinyInt: + res = schemas.SmallInt return res - case core.Bit: - res = core.Boolean + case schemas.Bit: + res = schemas.Boolean return res - case core.MediumInt, core.Int, core.Integer: + case schemas.MediumInt, schemas.Int, schemas.Integer: if c.IsAutoIncrement { - return core.Serial + return schemas.Serial } - return core.Integer - case core.BigInt: + return schemas.Integer + case schemas.BigInt: if c.IsAutoIncrement { - return core.BigSerial + return schemas.BigSerial } - return core.BigInt - case core.Serial, core.BigSerial: + return schemas.BigInt + case schemas.Serial, schemas.BigSerial: c.IsAutoIncrement = true c.Nullable = false res = t - case core.Binary, core.VarBinary: - return core.Bytea - case core.DateTime: - res = core.TimeStamp - case core.TimeStampz: + case schemas.Binary, schemas.VarBinary: + return schemas.Bytea + case schemas.DateTime: + res = schemas.TimeStamp + case schemas.TimeStampz: return "timestamp with time zone" - case core.Float: - res = core.Real - case core.TinyText, core.MediumText, core.LongText: - res = core.Text - case core.NVarchar: - res = core.Varchar - case core.Uuid: - return core.Uuid - case core.Blob, core.TinyBlob, core.MediumBlob, core.LongBlob: - return core.Bytea - case core.Double: + case schemas.Float: + res = schemas.Real + case schemas.TinyText, schemas.MediumText, schemas.LongText: + res = schemas.Text + case schemas.NVarchar: + res = schemas.Varchar + case schemas.Uuid: + return schemas.Uuid + case schemas.Blob, schemas.TinyBlob, schemas.MediumBlob, schemas.LongBlob: + return schemas.Bytea + case schemas.Double: return "DOUBLE PRECISION" default: if c.IsAutoIncrement { - return core.Serial + return schemas.Serial } res = t } @@ -879,37 +880,37 @@ func (db *postgres) IndexOnTable() bool { return false } -func (db *postgres) IndexCheckSql(tableName, idxName string) (string, []interface{}) { - if len(db.Schema) == 0 { +func (db *postgres) IndexCheckSQL(tableName, idxName string) (string, []interface{}) { + if len(db.uri.Schema) == 0 { args := []interface{}{tableName, idxName} return `SELECT indexname FROM pg_indexes WHERE tablename = ? AND indexname = ?`, args } - args := []interface{}{db.Schema, tableName, idxName} + args := []interface{}{db.uri.Schema, tableName, idxName} return `SELECT indexname FROM pg_indexes ` + `WHERE schemaname = ? AND tablename = ? AND indexname = ?`, args } -func (db *postgres) TableCheckSql(tableName string) (string, []interface{}) { - if len(db.Schema) == 0 { +func (db *postgres) TableCheckSQL(tableName string) (string, []interface{}) { + if len(db.uri.Schema) == 0 { args := []interface{}{tableName} return `SELECT tablename FROM pg_tables WHERE tablename = ?`, args } - args := []interface{}{db.Schema, tableName} + args := []interface{}{db.uri.Schema, tableName} return `SELECT tablename FROM pg_tables WHERE schemaname = ? AND tablename = ?`, args } -func (db *postgres) ModifyColumnSql(tableName string, col *core.Column) string { - if len(db.Schema) == 0 || strings.Contains(tableName, ".") { +func (db *postgres) ModifyColumnSQL(tableName string, col *schemas.Column) string { + if len(db.uri.Schema) == 0 || strings.Contains(tableName, ".") { return fmt.Sprintf("alter table %s ALTER COLUMN %s TYPE %s", - tableName, col.Name, db.SqlType(col)) + tableName, col.Name, db.SQLType(col)) } return fmt.Sprintf("alter table %s.%s ALTER COLUMN %s TYPE %s", - db.Schema, tableName, col.Name, db.SqlType(col)) + db.uri.Schema, tableName, col.Name, db.SQLType(col)) } -func (db *postgres) DropIndexSql(tableName string, index *core.Index) string { +func (db *postgres) DropIndexSQL(tableName string, index *schemas.Index) string { quote := db.Quote idxName := index.Name @@ -918,23 +919,23 @@ func (db *postgres) DropIndexSql(tableName string, index *core.Index) string { if !strings.HasPrefix(idxName, "UQE_") && !strings.HasPrefix(idxName, "IDX_") { - if index.Type == core.UniqueType { + if index.Type == schemas.UniqueType { idxName = fmt.Sprintf("UQE_%v_%v", tableName, index.Name) } else { idxName = fmt.Sprintf("IDX_%v_%v", tableName, index.Name) } } - if db.Uri.Schema != "" { - idxName = db.Uri.Schema + "." + idxName + if db.uri.Schema != "" { + idxName = db.uri.Schema + "." + idxName } return fmt.Sprintf("DROP INDEX %v", quote(idxName)) } func (db *postgres) IsColumnExist(tableName, colName string) (bool, error) { - args := []interface{}{db.Schema, tableName, colName} + args := []interface{}{db.uri.Schema, tableName, colName} query := "SELECT column_name FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = $1 AND table_name = $2" + " AND column_name = $3" - if len(db.Schema) == 0 { + if len(db.uri.Schema) == 0 { args = []interface{}{tableName, colName} query = "SELECT column_name FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = $1" + " AND column_name = $2" @@ -950,7 +951,7 @@ func (db *postgres) IsColumnExist(tableName, colName string) (bool, error) { return rows.Next(), nil } -func (db *postgres) GetColumns(tableName string) ([]string, map[string]*core.Column, error) { +func (db *postgres) GetColumns(tableName string) ([]string, map[string]*schemas.Column, error) { args := []interface{}{tableName} s := `SELECT column_name, column_default, is_nullable, data_type, character_maximum_length, CASE WHEN p.contype = 'p' THEN true ELSE false END AS primarykey, @@ -965,8 +966,8 @@ FROM pg_attribute f WHERE c.relkind = 'r'::char AND c.relname = $1%s AND f.attnum > 0 ORDER BY f.attnum;` var f string - if len(db.Schema) != 0 { - args = append(args, db.Schema) + if len(db.uri.Schema) != 0 { + args = append(args, db.uri.Schema) f = " AND s.table_schema = $2" } s = fmt.Sprintf(s, f) @@ -979,11 +980,11 @@ WHERE c.relkind = 'r'::char AND c.relname = $1%s AND f.attnum > 0 ORDER BY f.att } defer rows.Close() - cols := make(map[string]*core.Column) + cols := make(map[string]*schemas.Column) colSeq := make([]string, 0) for rows.Next() { - col := new(core.Column) + col := new(schemas.Column) col.Indexes = make(map[string]int) var colName, isNullable, dataType string @@ -1023,23 +1024,23 @@ WHERE c.relkind = 'r'::char AND c.relname = $1%s AND f.attnum > 0 ORDER BY f.att switch dataType { case "character varying", "character": - col.SQLType = core.SQLType{Name: core.Varchar, DefaultLength: 0, DefaultLength2: 0} + col.SQLType = schemas.SQLType{Name: schemas.Varchar, DefaultLength: 0, DefaultLength2: 0} case "timestamp without time zone": - col.SQLType = core.SQLType{Name: core.DateTime, DefaultLength: 0, DefaultLength2: 0} + col.SQLType = schemas.SQLType{Name: schemas.DateTime, DefaultLength: 0, DefaultLength2: 0} case "timestamp with time zone": - col.SQLType = core.SQLType{Name: core.TimeStampz, DefaultLength: 0, DefaultLength2: 0} + col.SQLType = schemas.SQLType{Name: schemas.TimeStampz, DefaultLength: 0, DefaultLength2: 0} case "double precision": - col.SQLType = core.SQLType{Name: core.Double, DefaultLength: 0, DefaultLength2: 0} + col.SQLType = schemas.SQLType{Name: schemas.Double, DefaultLength: 0, DefaultLength2: 0} case "boolean": - col.SQLType = core.SQLType{Name: core.Bool, DefaultLength: 0, DefaultLength2: 0} + col.SQLType = schemas.SQLType{Name: schemas.Bool, DefaultLength: 0, DefaultLength2: 0} case "time without time zone": - col.SQLType = core.SQLType{Name: core.Time, DefaultLength: 0, DefaultLength2: 0} + col.SQLType = schemas.SQLType{Name: schemas.Time, DefaultLength: 0, DefaultLength2: 0} case "oid": - col.SQLType = core.SQLType{Name: core.BigInt, DefaultLength: 0, DefaultLength2: 0} + col.SQLType = schemas.SQLType{Name: schemas.BigInt, DefaultLength: 0, DefaultLength2: 0} default: - col.SQLType = core.SQLType{Name: strings.ToUpper(dataType), DefaultLength: 0, DefaultLength2: 0} + col.SQLType = schemas.SQLType{Name: strings.ToUpper(dataType), DefaultLength: 0, DefaultLength2: 0} } - if _, ok := core.SqlTypes[col.SQLType.Name]; !ok { + if _, ok := schemas.SqlTypes[col.SQLType.Name]; !ok { return nil, nil, fmt.Errorf("Unknown colType: %v", dataType) } @@ -1065,11 +1066,11 @@ WHERE c.relkind = 'r'::char AND c.relname = $1%s AND f.attnum > 0 ORDER BY f.att return colSeq, cols, nil } -func (db *postgres) GetTables() ([]*core.Table, error) { +func (db *postgres) GetTables() ([]*schemas.Table, error) { args := []interface{}{} s := "SELECT tablename FROM pg_tables" - if len(db.Schema) != 0 { - args = append(args, db.Schema) + if len(db.uri.Schema) != 0 { + args = append(args, db.uri.Schema) s = s + " WHERE schemaname = $1" } @@ -1081,9 +1082,9 @@ func (db *postgres) GetTables() ([]*core.Table, error) { } defer rows.Close() - tables := make([]*core.Table, 0) + tables := make([]*schemas.Table, 0) for rows.Next() { - table := core.NewEmptyTable() + table := schemas.NewEmptyTable() var name string err = rows.Scan(&name) if err != nil { @@ -1106,11 +1107,11 @@ func getIndexColName(indexdef string) []string { return colNames } -func (db *postgres) GetIndexes(tableName string) (map[string]*core.Index, error) { +func (db *postgres) GetIndexes(tableName string) (map[string]*schemas.Index, error) { args := []interface{}{tableName} s := fmt.Sprintf("SELECT indexname, indexdef FROM pg_indexes WHERE tablename=$1") - if len(db.Schema) != 0 { - args = append(args, db.Schema) + if len(db.uri.Schema) != 0 { + args = append(args, db.uri.Schema) s = s + " AND schemaname=$2" } db.LogSQL(s, args) @@ -1121,7 +1122,7 @@ func (db *postgres) GetIndexes(tableName string) (map[string]*core.Index, error) } defer rows.Close() - indexes := make(map[string]*core.Index, 0) + indexes := make(map[string]*schemas.Index, 0) for rows.Next() { var indexType int var indexName, indexdef string @@ -1135,9 +1136,9 @@ func (db *postgres) GetIndexes(tableName string) (map[string]*core.Index, error) continue } if strings.HasPrefix(indexdef, "CREATE UNIQUE INDEX") { - indexType = core.UniqueType + indexType = schemas.UniqueType } else { - indexType = core.IndexType + indexType = schemas.IndexType } colNames = getIndexColName(indexdef) var isRegular bool @@ -1149,7 +1150,7 @@ func (db *postgres) GetIndexes(tableName string) (map[string]*core.Index, error) } } - index := &core.Index{Name: indexName, Type: indexType, Cols: make([]string, 0)} + index := &schemas.Index{Name: indexName, Type: indexType, Cols: make([]string, 0)} for _, colName := range colNames { index.Cols = append(index.Cols, strings.Trim(colName, `" `)) } @@ -1159,8 +1160,8 @@ func (db *postgres) GetIndexes(tableName string) (map[string]*core.Index, error) return indexes, nil } -func (db *postgres) Filters() []core.Filter { - return []core.Filter{&core.IdFilter{}, &core.QuoteFilter{}, &core.SeqFilter{Prefix: "$", Start: 1}} +func (db *postgres) Filters() []Filter { + return []Filter{&IdFilter{}, &QuoteFilter{}, &SeqFilter{Prefix: "$", Start: 1}} } type pqDriver struct { @@ -1214,12 +1215,12 @@ func parseOpts(name string, o values) error { return nil } -func (p *pqDriver) Parse(driverName, dataSourceName string) (*core.Uri, error) { - db := &core.Uri{DbType: core.POSTGRES} +func (p *pqDriver) Parse(driverName, dataSourceName string) (*URI, error) { + db := &URI{DBType: schemas.POSTGRES} var err error if strings.HasPrefix(dataSourceName, "postgresql://") || strings.HasPrefix(dataSourceName, "postgres://") { - db.DbName, err = parseURL(dataSourceName) + db.DBName, err = parseURL(dataSourceName) if err != nil { return nil, err } @@ -1230,10 +1231,10 @@ func (p *pqDriver) Parse(driverName, dataSourceName string) (*core.Uri, error) { return nil, err } - db.DbName = o.Get("dbname") + db.DBName = o.Get("dbname") } - if db.DbName == "" { + if db.DBName == "" { return nil, errors.New("dbname is empty") } @@ -1244,7 +1245,7 @@ type pqDriverPgx struct { pqDriver } -func (pgx *pqDriverPgx) Parse(driverName, dataSourceName string) (*core.Uri, error) { +func (pgx *pqDriverPgx) Parse(driverName, dataSourceName string) (*URI, error) { // Remove the leading characters for driver to work if len(dataSourceName) >= 9 && dataSourceName[0] == 0 { dataSourceName = dataSourceName[9:] diff --git a/dialect_postgres_test.go b/dialects/postgres_test.go similarity index 92% rename from dialect_postgres_test.go rename to dialects/postgres_test.go index f2afdefc..c0a8eb6f 100644 --- a/dialect_postgres_test.go +++ b/dialects/postgres_test.go @@ -1,11 +1,10 @@ -package xorm +package dialects import ( "reflect" "testing" "github.com/stretchr/testify/assert" - "xorm.io/core" ) func TestParsePostgres(t *testing.T) { @@ -27,15 +26,15 @@ func TestParsePostgres(t *testing.T) { {"dbname=db =disable", "db", false}, } - driver := core.QueryDriver("postgres") + driver := QueryDriver("postgres") for _, test := range tests { uri, err := driver.Parse("postgres", test.in) if err != nil && test.valid { t.Errorf("%q got unexpected error: %s", test.in, err) - } else if err == nil && !reflect.DeepEqual(test.expected, uri.DbName) { - t.Errorf("%q got: %#v want: %#v", test.in, uri.DbName, test.expected) + } else if err == nil && !reflect.DeepEqual(test.expected, uri.DBName) { + t.Errorf("%q got: %#v want: %#v", test.in, uri.DBName, test.expected) } } } @@ -59,23 +58,23 @@ func TestParsePgx(t *testing.T) { {"dbname=db =disable", "db", false}, } - driver := core.QueryDriver("pgx") + driver := QueryDriver("pgx") for _, test := range tests { uri, err := driver.Parse("pgx", test.in) if err != nil && test.valid { t.Errorf("%q got unexpected error: %s", test.in, err) - } else if err == nil && !reflect.DeepEqual(test.expected, uri.DbName) { - t.Errorf("%q got: %#v want: %#v", test.in, uri.DbName, test.expected) + } else if err == nil && !reflect.DeepEqual(test.expected, uri.DBName) { + t.Errorf("%q got: %#v want: %#v", test.in, uri.DBName, test.expected) } // Register DriverConfig uri, err = driver.Parse("pgx", test.in) if err != nil && test.valid { t.Errorf("%q got unexpected error: %s", test.in, err) - } else if err == nil && !reflect.DeepEqual(test.expected, uri.DbName) { - t.Errorf("%q got: %#v want: %#v", test.in, uri.DbName, test.expected) + } else if err == nil && !reflect.DeepEqual(test.expected, uri.DBName) { + t.Errorf("%q got: %#v want: %#v", test.in, uri.DBName, test.expected) } } diff --git a/dialect_sqlite3.go b/dialects/sqlite3.go similarity index 83% rename from dialect_sqlite3.go rename to dialects/sqlite3.go index 0a290f3c..b7ff2147 100644 --- a/dialect_sqlite3.go +++ b/dialects/sqlite3.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package dialects import ( "database/sql" @@ -11,7 +11,8 @@ import ( "regexp" "strings" - "xorm.io/core" + "xorm.io/xorm/core" + "xorm.io/xorm/schemas" ) var ( @@ -144,42 +145,42 @@ var ( ) type sqlite3 struct { - core.Base + Base } -func (db *sqlite3) Init(d *core.DB, uri *core.Uri, drivername, dataSourceName string) error { +func (db *sqlite3) Init(d *core.DB, uri *URI, drivername, dataSourceName string) error { return db.Base.Init(d, db, uri, drivername, dataSourceName) } -func (db *sqlite3) SqlType(c *core.Column) string { +func (db *sqlite3) SQLType(c *schemas.Column) string { switch t := c.SQLType.Name; t { - case core.Bool: + case schemas.Bool: if c.Default == "true" { c.Default = "1" } else if c.Default == "false" { c.Default = "0" } - return core.Integer - case core.Date, core.DateTime, core.TimeStamp, core.Time: - return core.DateTime - case core.TimeStampz: - return core.Text - case core.Char, core.Varchar, core.NVarchar, core.TinyText, - core.Text, core.MediumText, core.LongText, core.Json: - return core.Text - case core.Bit, core.TinyInt, core.SmallInt, core.MediumInt, core.Int, core.Integer, core.BigInt: - return core.Integer - case core.Float, core.Double, core.Real: - return core.Real - case core.Decimal, core.Numeric: - return core.Numeric - case core.TinyBlob, core.Blob, core.MediumBlob, core.LongBlob, core.Bytea, core.Binary, core.VarBinary: - return core.Blob - case core.Serial, core.BigSerial: + return schemas.Integer + case schemas.Date, schemas.DateTime, schemas.TimeStamp, schemas.Time: + return schemas.DateTime + case schemas.TimeStampz: + return schemas.Text + case schemas.Char, schemas.Varchar, schemas.NVarchar, schemas.TinyText, + schemas.Text, schemas.MediumText, schemas.LongText, schemas.Json: + return schemas.Text + case schemas.Bit, schemas.TinyInt, schemas.SmallInt, schemas.MediumInt, schemas.Int, schemas.Integer, schemas.BigInt: + return schemas.Integer + case schemas.Float, schemas.Double, schemas.Real: + return schemas.Real + case schemas.Decimal, schemas.Numeric: + return schemas.Numeric + case schemas.TinyBlob, schemas.Blob, schemas.MediumBlob, schemas.LongBlob, schemas.Bytea, schemas.Binary, schemas.VarBinary: + return schemas.Blob + case schemas.Serial, schemas.BigSerial: c.IsPrimaryKey = true c.IsAutoIncrement = true c.Nullable = false - return core.Integer + return schemas.Integer default: return t } @@ -218,24 +219,24 @@ func (db *sqlite3) IndexOnTable() bool { return false } -func (db *sqlite3) IndexCheckSql(tableName, idxName string) (string, []interface{}) { +func (db *sqlite3) IndexCheckSQL(tableName, idxName string) (string, []interface{}) { args := []interface{}{idxName} return "SELECT name FROM sqlite_master WHERE type='index' and name = ?", args } -func (db *sqlite3) TableCheckSql(tableName string) (string, []interface{}) { +func (db *sqlite3) TableCheckSQL(tableName string) (string, []interface{}) { args := []interface{}{tableName} return "SELECT name FROM sqlite_master WHERE type='table' and name = ?", args } -func (db *sqlite3) DropIndexSql(tableName string, index *core.Index) string { +func (db *sqlite3) DropIndexSQL(tableName string, index *schemas.Index) string { // var unique string quote := db.Quote idxName := index.Name if !strings.HasPrefix(idxName, "UQE_") && !strings.HasPrefix(idxName, "IDX_") { - if index.Type == core.UniqueType { + if index.Type == schemas.UniqueType { idxName = fmt.Sprintf("UQE_%v_%v", tableName, index.Name) } else { idxName = fmt.Sprintf("IDX_%v_%v", tableName, index.Name) @@ -244,7 +245,7 @@ func (db *sqlite3) DropIndexSql(tableName string, index *core.Index) string { return fmt.Sprintf("DROP INDEX %v", quote(idxName)) } -func (db *sqlite3) ForUpdateSql(query string) string { +func (db *sqlite3) ForUpdateSQL(query string) string { return query } @@ -298,9 +299,9 @@ func splitColStr(colStr string) []string { return results } -func parseString(colStr string) (*core.Column, error) { +func parseString(colStr string) (*schemas.Column, error) { fields := splitColStr(colStr) - col := new(core.Column) + col := new(schemas.Column) col.Indexes = make(map[string]int) col.Nullable = true col.DefaultIsEmpty = true @@ -310,7 +311,7 @@ func parseString(colStr string) (*core.Column, error) { col.Name = strings.Trim(strings.Trim(field, "`[] "), `"`) continue } else if idx == 1 { - col.SQLType = core.SQLType{Name: field, DefaultLength: 0, DefaultLength2: 0} + col.SQLType = schemas.SQLType{Name: field, DefaultLength: 0, DefaultLength2: 0} continue } switch field { @@ -332,7 +333,7 @@ func parseString(colStr string) (*core.Column, error) { return col, nil } -func (db *sqlite3) GetColumns(tableName string) ([]string, map[string]*core.Column, error) { +func (db *sqlite3) GetColumns(tableName string) ([]string, map[string]*schemas.Column, error) { args := []interface{}{tableName} s := "SELECT sql FROM sqlite_master WHERE type='table' and name = ?" db.LogSQL(s, args) @@ -359,7 +360,7 @@ func (db *sqlite3) GetColumns(tableName string) ([]string, map[string]*core.Colu nEnd := strings.LastIndex(name, ")") reg := regexp.MustCompile(`[^\(,\)]*(\([^\(]*\))?`) colCreates := reg.FindAllString(name[nStart+1:nEnd], -1) - cols := make(map[string]*core.Column) + cols := make(map[string]*schemas.Column) colSeq := make([]string, 0) for _, colStr := range colCreates { @@ -389,7 +390,7 @@ func (db *sqlite3) GetColumns(tableName string) ([]string, map[string]*core.Colu return colSeq, cols, nil } -func (db *sqlite3) GetTables() ([]*core.Table, error) { +func (db *sqlite3) GetTables() ([]*schemas.Table, error) { args := []interface{}{} s := "SELECT name FROM sqlite_master WHERE type='table'" db.LogSQL(s, args) @@ -400,9 +401,9 @@ func (db *sqlite3) GetTables() ([]*core.Table, error) { } defer rows.Close() - tables := make([]*core.Table, 0) + tables := make([]*schemas.Table, 0) for rows.Next() { - table := core.NewEmptyTable() + table := schemas.NewEmptyTable() err = rows.Scan(&table.Name) if err != nil { return nil, err @@ -415,7 +416,7 @@ func (db *sqlite3) GetTables() ([]*core.Table, error) { return tables, nil } -func (db *sqlite3) GetIndexes(tableName string) (map[string]*core.Index, error) { +func (db *sqlite3) GetIndexes(tableName string) (map[string]*schemas.Index, error) { args := []interface{}{tableName} s := "SELECT sql FROM sqlite_master WHERE type='index' and tbl_name = ?" db.LogSQL(s, args) @@ -426,7 +427,7 @@ func (db *sqlite3) GetIndexes(tableName string) (map[string]*core.Index, error) } defer rows.Close() - indexes := make(map[string]*core.Index, 0) + indexes := make(map[string]*schemas.Index, 0) for rows.Next() { var tmpSQL sql.NullString err = rows.Scan(&tmpSQL) @@ -439,7 +440,7 @@ func (db *sqlite3) GetIndexes(tableName string) (map[string]*core.Index, error) } sql := tmpSQL.String - index := new(core.Index) + index := new(schemas.Index) nNStart := strings.Index(sql, "INDEX") nNEnd := strings.Index(sql, "ON") if nNStart == -1 || nNEnd == -1 { @@ -456,9 +457,9 @@ func (db *sqlite3) GetIndexes(tableName string) (map[string]*core.Index, error) } if strings.HasPrefix(sql, "CREATE UNIQUE INDEX") { - index.Type = core.UniqueType + index.Type = schemas.UniqueType } else { - index.Type = core.IndexType + index.Type = schemas.IndexType } nStart := strings.Index(sql, "(") @@ -476,17 +477,17 @@ func (db *sqlite3) GetIndexes(tableName string) (map[string]*core.Index, error) return indexes, nil } -func (db *sqlite3) Filters() []core.Filter { - return []core.Filter{&core.IdFilter{}} +func (db *sqlite3) Filters() []Filter { + return []Filter{&IdFilter{}} } type sqlite3Driver struct { } -func (p *sqlite3Driver) Parse(driverName, dataSourceName string) (*core.Uri, error) { +func (p *sqlite3Driver) Parse(driverName, dataSourceName string) (*URI, error) { if strings.Contains(dataSourceName, "?") { dataSourceName = dataSourceName[:strings.Index(dataSourceName, "?")] } - return &core.Uri{DbType: core.SQLITE, DbName: dataSourceName}, nil + return &URI{DBType: schemas.SQLITE, DBName: dataSourceName}, nil } diff --git a/dialect_sqlite3_test.go b/dialects/sqlite3_test.go similarity index 97% rename from dialect_sqlite3_test.go rename to dialects/sqlite3_test.go index a2036159..aa6c3cea 100644 --- a/dialect_sqlite3_test.go +++ b/dialects/sqlite3_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package dialects import ( "testing" diff --git a/doc.go b/doc.go index 9620bca1..8df4fb30 100644 --- a/doc.go +++ b/doc.go @@ -126,7 +126,7 @@ Attention: the above 8 methods should be the last chainable method. engine.ID(1).Get(&user) // for single primary key // SELECT * FROM user WHERE id = 1 - engine.ID(core.PK{1, 2}).Get(&user) // for composite primary keys + engine.ID(schemas.PK{1, 2}).Get(&user) // for composite primary keys // SELECT * FROM user WHERE id1 = 1 AND id2 = 2 engine.In("id", 1, 2, 3).Find(&users) // SELECT * FROM user WHERE id IN (1, 2, 3) diff --git a/docs/images/cache_design.graffle b/docs/images/cache_design.graffle deleted file mode 100644 index 5b7c487b..00000000 --- a/docs/images/cache_design.graffle +++ /dev/null @@ -1,2295 +0,0 @@ - - - - - ActiveLayerIndex - 0 - ApplicationVersion - - com.omnigroup.OmniGrafflePro - 139.16.0.171715 - - AutoAdjust - - BackgroundGraphic - - Bounds - {{0, 0}, {771, 554.18930041152259}} - Class - SolidGraphic - ID - 2 - Style - - fill - - Color - - b - 0.989303 - g - 0.907286 - r - 0.795377 - - FillType - 2 - GradientAngle - 78 - GradientColor - - b - 1 - g - 0.854588 - r - 0.623912 - - MiddleColor - - b - 1 - g - 0.856844 - r - 0.43695 - - TrippleBlend - YES - - shadow - - Draws - NO - - stroke - - Draws - NO - - - - BaseZoom - 0 - CanvasOrigin - {0, 0} - CanvasSize - {771, 554.18930041152259} - ColumnAlign - 1 - ColumnSpacing - 36 - CreationDate - 2013-09-29 07:57:57 +0000 - Creator - Lunny Xiao - DisplayScale - 1.000 cm = 1.000 cm - FileType - flat - GraphDocumentVersion - 8 - GraphicsList - - - Bounds - {{409.89504441572683, 415.64570506990464}, {104.42639923095703, 79.447883605957031}} - Class - ShapedGraphic - FontInfo - - Color - - b - 0.8 - g - 0.8 - r - 0.8 - - Font - Verdana - Size - 18 - - ID - 30 - Shape - Rectangle - Style - - fill - - Color - - b - 0.6 - g - 0.6 - r - 0.6 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.4 - g - 0.4 - r - 0.4 - - - shadow - - Beneath - YES - Color - - a - 0.15 - b - 0 - g - 0 - r - 0 - - Fuzziness - 0.0 - ShadowVector - {2, 2} - - stroke - - Color - - b - 0.590997 - g - 0.18677 - r - 0.567819 - - Draws - NO - Width - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 -\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} -{\colortbl;\red255\green255\blue255;\red204\green204\blue204;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs36 \cf2 .\ -.\ -.\ -} - VerticalPad - 0 - - TextRelativeArea - {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} - - - Bounds - {{276.44083898205287, 413.07252538018992}, {112.36092376708984, 79.447883605957031}} - Class - ShapedGraphic - FontInfo - - Color - - archive - - YnBsaXN0MDDUAQIDBAUGBwpZJGFyY2hpdmVy - WCR2ZXJzaW9uVCR0b3BYJG9iamVjdHNfEA9O - U0tleWVkQXJjaGl2ZXISAAGGoNEICVRyb290 - gAGlCwwVGR5VJG51bGzUDQ4PEBESExRfEBJO - U0N1c3RvbUNvbG9yU3BhY2VXTlNXaGl0ZVxO - U0NvbG9yU3BhY2VWJGNsYXNzgAJCMAAQA4AE - 0hYQFxhUTlNJRBACgAPSGhscD1gkY2xhc3Nl - c1okY2xhc3NuYW1log8dWE5TT2JqZWN00hob - HyCiIB1XTlNDb2xvcggRGyQpMkRJTFFTWV9o - fYWSmZueoKKnrK6wtb7JzNXa3QAAAAAAAAEB - AAAAAAAAACEAAAAAAAAAAAAAAAAAAADl - - b - 0 - g - 0 - r - 0 - - Font - Verdana - Size - 18 - - ID - 29 - Shape - Rectangle - Style - - fill - - Color - - b - 0.776486 - g - 0.588495 - r - 0.670497 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.618021 - g - 0.412924 - r - 0.50312 - - - shadow - - Beneath - YES - Color - - a - 0.15 - b - 0 - g - 0 - r - 0 - - Fuzziness - 0.0 - ShadowVector - {2, 2} - - stroke - - Color - - b - 0.590997 - g - 0.18677 - r - 0.567819 - - Draws - NO - Width - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 -\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs36 \cf0 .\ -.\ -.} - VerticalPad - 0 - - TextRelativeArea - {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} - - - Bounds - {{409.89504441572689, 337.17180246145824}, {104.42639923095703, 51}} - Class - ShapedGraphic - FontInfo - - Color - - b - 0.8 - g - 0.8 - r - 0.8 - - Font - Verdana - Size - 18 - - ID - 28 - Shape - Rectangle - Style - - fill - - Color - - b - 0.6 - g - 0.6 - r - 0.6 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.4 - g - 0.4 - r - 0.4 - - - shadow - - Beneath - YES - Color - - a - 0.15 - b - 0 - g - 0 - r - 0 - - Fuzziness - 0.0 - ShadowVector - {2, 2} - - stroke - - Color - - b - 0.590997 - g - 0.18677 - r - 0.567819 - - Draws - NO - Width - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 -\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} -{\colortbl;\red255\green255\blue255;\red204\green204\blue204;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs36 \cf2 user-2:User\{\}} - VerticalPad - 0 - - TextRelativeArea - {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} - - - Bounds - {{274.32251833907753, 322.94787397618234}, {112.36092376708984, 79.447883605957031}} - Class - ShapedGraphic - FontInfo - - Color - - archive - - YnBsaXN0MDDUAQIDBAUGBwpZJGFyY2hpdmVy - WCR2ZXJzaW9uVCR0b3BYJG9iamVjdHNfEA9O - U0tleWVkQXJjaGl2ZXISAAGGoNEICVRyb290 - gAGlCwwVGR5VJG51bGzUDQ4PEBESExRfEBJO - U0N1c3RvbUNvbG9yU3BhY2VXTlNXaGl0ZVxO - U0NvbG9yU3BhY2VWJGNsYXNzgAJCMAAQA4AE - 0hYQFxhUTlNJRBACgAPSGhscD1gkY2xhc3Nl - c1okY2xhc3NuYW1log8dWE5TT2JqZWN00hob - HyCiIB1XTlNDb2xvcggRGyQpMkRJTFFTWV9o - fYWSmZueoKKnrK6wtb7JzNXa3QAAAAAAAAEB - AAAAAAAAACEAAAAAAAAAAAAAAAAAAADl - - b - 0 - g - 0 - r - 0 - - Font - Verdana - Size - 18 - - ID - 27 - Shape - Rectangle - Style - - fill - - Color - - b - 0.776486 - g - 0.588495 - r - 0.670497 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.618021 - g - 0.412924 - r - 0.50312 - - - shadow - - Beneath - YES - Color - - a - 0.15 - b - 0 - g - 0 - r - 0 - - Fuzziness - 0.0 - ShadowVector - {2, 2} - - stroke - - Color - - b - 0.590997 - g - 0.18677 - r - 0.567819 - - Draws - NO - Width - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 -\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs36 \cf0 select id from tb3:[2,5]} - VerticalPad - 0 - - TextRelativeArea - {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} - - - Bounds - {{406.08888702072045, 256.42244420026987}, {104.42639923095703, 51}} - Class - ShapedGraphic - FontInfo - - Color - - b - 0.8 - g - 0.8 - r - 0.8 - - Font - Verdana - Size - 18 - - ID - 25 - Shape - Rectangle - Style - - fill - - Color - - b - 0.6 - g - 0.6 - r - 0.6 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.4 - g - 0.4 - r - 0.4 - - - shadow - - Beneath - YES - Color - - a - 0.15 - b - 0 - g - 0 - r - 0 - - Fuzziness - 0.0 - ShadowVector - {2, 2} - - stroke - - Color - - b - 0.590997 - g - 0.18677 - r - 0.567819 - - Draws - NO - Width - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 -\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} -{\colortbl;\red255\green255\blue255;\red204\green204\blue204;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs36 \cf2 user-2:User\{\}} - VerticalPad - 0 - - TextRelativeArea - {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} - - - Bounds - {{406.08888302813585, 187.47137690331695}, {104.42639923095703, 51}} - Class - ShapedGraphic - FontInfo - - Color - - b - 0.8 - g - 0.8 - r - 0.8 - - Font - Verdana - Size - 18 - - ID - 24 - Shape - Rectangle - Style - - fill - - Color - - b - 0.6 - g - 0.6 - r - 0.6 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.4 - g - 0.4 - r - 0.4 - - - shadow - - Beneath - YES - Color - - a - 0.15 - b - 0 - g - 0 - r - 0 - - Fuzziness - 0.0 - ShadowVector - {2, 2} - - stroke - - Color - - b - 0.590997 - g - 0.18677 - r - 0.567819 - - Draws - NO - Width - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 -\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} -{\colortbl;\red255\green255\blue255;\red204\green204\blue204;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs36 \cf2 table-1:Table\{\}} - VerticalPad - 0 - - TextRelativeArea - {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} - - - Bounds - {{406.08887903555114, 118.52029512620169}, {104.42639923095703, 51}} - Class - ShapedGraphic - FontInfo - - Color - - b - 0.8 - g - 0.8 - r - 0.8 - - Font - Verdana - Size - 18 - - ID - 23 - Shape - Rectangle - Style - - fill - - Color - - b - 0.6 - g - 0.6 - r - 0.6 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.4 - g - 0.4 - r - 0.4 - - - shadow - - Beneath - YES - Color - - a - 0.15 - b - 0 - g - 0 - r - 0 - - Fuzziness - 0.0 - ShadowVector - {2, 2} - - stroke - - Color - - b - 0.590997 - g - 0.18677 - r - 0.567819 - - Draws - NO - Width - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 -\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} -{\colortbl;\red255\green255\blue255;\red204\green204\blue204;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs36 \cf2 user-1:User\{\}} - VerticalPad - 0 - - TextRelativeArea - {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} - - - Bounds - {{556.54055354053583, 325.93280718390133}, {124.41892177446698, 51}} - Class - ShapedGraphic - FontInfo - - Color - - archive - - YnBsaXN0MDDUAQIDBAUGBwpZJGFyY2hpdmVy - WCR2ZXJzaW9uVCR0b3BYJG9iamVjdHNfEA9O - U0tleWVkQXJjaGl2ZXISAAGGoNEICVRyb290 - gAGlCwwVGR5VJG51bGzUDQ4PEBESExRfEBJO - U0N1c3RvbUNvbG9yU3BhY2VXTlNXaGl0ZVxO - U0NvbG9yU3BhY2VWJGNsYXNzgAJCMAAQA4AE - 0hYQFxhUTlNJRBACgAPSGhscD1gkY2xhc3Nl - c1okY2xhc3NuYW1log8dWE5TT2JqZWN00hob - HyCiIB1XTlNDb2xvcggRGyQpMkRJTFFTWV9o - fYWSmZueoKKnrK6wtb7JzNXa3QAAAAAAAAEB - AAAAAAAAACEAAAAAAAAAAAAAAAAAAADl - - b - 0 - g - 0 - r - 0 - - Font - Verdana - Size - 18 - - ID - 22 - Shape - Rectangle - Style - - fill - - Color - - b - 0.793851 - g - 0.625208 - r - 0.562982 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.639673 - g - 0.450584 - r - 0.381079 - - - shadow - - Beneath - YES - Color - - a - 0.15 - b - 0 - g - 0 - r - 0 - - Fuzziness - 0.0 - ShadowVector - {2, 2} - - stroke - - Color - - b - 0.511421 - g - 0.637255 - r - 0.120867 - - Draws - NO - Width - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 -\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs36 \cf0 Del(k, v)} - VerticalPad - 0 - - TextRelativeArea - {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} - - - Bounds - {{556.54055354053583, 240.81081643580876}, {124.41892177446698, 51}} - Class - ShapedGraphic - FontInfo - - Color - - archive - - YnBsaXN0MDDUAQIDBAUGBwpZJGFyY2hpdmVy - WCR2ZXJzaW9uVCR0b3BYJG9iamVjdHNfEA9O - U0tleWVkQXJjaGl2ZXISAAGGoNEICVRyb290 - gAGlCwwVGR5VJG51bGzUDQ4PEBESExRfEBJO - U0N1c3RvbUNvbG9yU3BhY2VXTlNXaGl0ZVxO - U0NvbG9yU3BhY2VWJGNsYXNzgAJCMAAQA4AE - 0hYQFxhUTlNJRBACgAPSGhscD1gkY2xhc3Nl - c1okY2xhc3NuYW1log8dWE5TT2JqZWN00hob - HyCiIB1XTlNDb2xvcggRGyQpMkRJTFFTWV9o - fYWSmZueoKKnrK6wtb7JzNXa3QAAAAAAAAEB - AAAAAAAAACEAAAAAAAAAAAAAAAAAAADl - - b - 0 - g - 0 - r - 0 - - Font - Verdana - Size - 18 - - ID - 21 - Shape - Rectangle - Style - - fill - - Color - - b - 0.793851 - g - 0.625208 - r - 0.562982 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.639673 - g - 0.450584 - r - 0.381079 - - - shadow - - Beneath - YES - Color - - a - 0.15 - b - 0 - g - 0 - r - 0 - - Fuzziness - 0.0 - ShadowVector - {2, 2} - - stroke - - Color - - b - 0.511421 - g - 0.637255 - r - 0.120867 - - Draws - NO - Width - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 -\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs36 \cf0 Put(k, v)} - VerticalPad - 0 - - TextRelativeArea - {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} - - - Bounds - {{556.54054526129187, 150.52220626433376}, {124.41893005371094, 51}} - Class - ShapedGraphic - FontInfo - - Color - - archive - - YnBsaXN0MDDUAQIDBAUGBwpZJGFyY2hpdmVy - WCR2ZXJzaW9uVCR0b3BYJG9iamVjdHNfEA9O - U0tleWVkQXJjaGl2ZXISAAGGoNEICVRyb290 - gAGlCwwVGR5VJG51bGzUDQ4PEBESExRfEBJO - U0N1c3RvbUNvbG9yU3BhY2VXTlNXaGl0ZVxO - U0NvbG9yU3BhY2VWJGNsYXNzgAJCMAAQA4AE - 0hYQFxhUTlNJRBACgAPSGhscD1gkY2xhc3Nl - c1okY2xhc3NuYW1log8dWE5TT2JqZWN00hob - HyCiIB1XTlNDb2xvcggRGyQpMkRJTFFTWV9o - fYWSmZueoKKnrK6wtb7JzNXa3QAAAAAAAAEB - AAAAAAAAACEAAAAAAAAAAAAAAAAAAADl - - b - 0 - g - 0 - r - 0 - - Font - Verdana - Size - 18 - - ID - 20 - Shape - Rectangle - Style - - fill - - Color - - b - 0.793851 - g - 0.625208 - r - 0.562982 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.639673 - g - 0.450584 - r - 0.381079 - - - shadow - - Beneath - YES - Color - - a - 0.15 - b - 0 - g - 0 - r - 0 - - Fuzziness - 0.0 - ShadowVector - {2, 2} - - stroke - - Color - - b - 0.511421 - g - 0.637255 - r - 0.120867 - - Draws - NO - Width - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 -\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs36 \cf0 Get(k, v)} - VerticalPad - 0 - - TextRelativeArea - {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} - - - Bounds - {{276.44083898205287, 214.72973288487913}, {112.36092376708984, 79.447883605957031}} - Class - ShapedGraphic - FontInfo - - Color - - archive - - YnBsaXN0MDDUAQIDBAUGBwpZJGFyY2hpdmVy - WCR2ZXJzaW9uVCR0b3BYJG9iamVjdHNfEA9O - U0tleWVkQXJjaGl2ZXISAAGGoNEICVRyb290 - gAGlCwwVGR5VJG51bGzUDQ4PEBESExRfEBJO - U0N1c3RvbUNvbG9yU3BhY2VXTlNXaGl0ZVxO - U0NvbG9yU3BhY2VWJGNsYXNzgAJCMAAQA4AE - 0hYQFxhUTlNJRBACgAPSGhscD1gkY2xhc3Nl - c1okY2xhc3NuYW1log8dWE5TT2JqZWN00hob - HyCiIB1XTlNDb2xvcggRGyQpMkRJTFFTWV9o - fYWSmZueoKKnrK6wtb7JzNXa3QAAAAAAAAEB - AAAAAAAAACEAAAAAAAAAAAAAAAAAAADl - - b - 0 - g - 0 - r - 0 - - Font - Verdana - Size - 18 - - ID - 19 - Shape - Rectangle - Style - - fill - - Color - - b - 0.776486 - g - 0.588495 - r - 0.670497 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.618021 - g - 0.412924 - r - 0.50312 - - - shadow - - Beneath - YES - Color - - a - 0.15 - b - 0 - g - 0 - r - 0 - - Fuzziness - 0.0 - ShadowVector - {2, 2} - - stroke - - Color - - b - 0.590997 - g - 0.18677 - r - 0.567819 - - Draws - NO - Width - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 -\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs36 \cf0 select id from tb2:[2,5]} - VerticalPad - 0 - - TextRelativeArea - {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} - - - Bounds - {{274.32251963877655, 117.18245433711769}, {112.36092376708984, 68.777008056640625}} - Class - ShapedGraphic - FontInfo - - Color - - archive - - YnBsaXN0MDDUAQIDBAUGBwpZJGFyY2hpdmVy - WCR2ZXJzaW9uVCR0b3BYJG9iamVjdHNfEA9O - U0tleWVkQXJjaGl2ZXISAAGGoNEICVRyb290 - gAGlCwwVGR5VJG51bGzUDQ4PEBESExRfEBJO - U0N1c3RvbUNvbG9yU3BhY2VXTlNXaGl0ZVxO - U0NvbG9yU3BhY2VWJGNsYXNzgAJCMAAQA4AE - 0hYQFxhUTlNJRBACgAPSGhscD1gkY2xhc3Nl - c1okY2xhc3NuYW1log8dWE5TT2JqZWN00hob - HyCiIB1XTlNDb2xvcggRGyQpMkRJTFFTWV9o - fYWSmZueoKKnrK6wtb7JzNXa3QAAAAAAAAEB - AAAAAAAAACEAAAAAAAAAAAAAAAAAAADl - - b - 0 - g - 0 - r - 0 - - Font - Verdana - Size - 18 - - ID - 18 - Shape - Rectangle - Style - - fill - - Color - - b - 0.776486 - g - 0.588495 - r - 0.670497 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.618021 - g - 0.412924 - r - 0.50312 - - - shadow - - Beneath - YES - Color - - a - 0.15 - b - 0 - g - 0 - r - 0 - - Fuzziness - 0.0 - ShadowVector - {2, 2} - - stroke - - Color - - b - 0.590997 - g - 0.18677 - r - 0.567819 - - Draws - NO - Width - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 -\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs36 \cf0 select id from tb1:[1,2,3]} - VerticalPad - 0 - - TextRelativeArea - {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} - - - Bounds - {{103.00000194954862, 30.108108889738791}, {80, 51}} - Class - ShapedGraphic - FontInfo - - Color - - archive - - YnBsaXN0MDDUAQIDBAUGBwpZJGFyY2hpdmVy - WCR2ZXJzaW9uVCR0b3BYJG9iamVjdHNfEA9O - U0tleWVkQXJjaGl2ZXISAAGGoNEICVRyb290 - gAGlCwwVGR5VJG51bGzUDQ4PEBESExRfEBJO - U0N1c3RvbUNvbG9yU3BhY2VXTlNXaGl0ZVxO - U0NvbG9yU3BhY2VWJGNsYXNzgAJCMAAQA4AE - 0hYQFxhUTlNJRBACgAPSGhscD1gkY2xhc3Nl - c1okY2xhc3NuYW1log8dWE5TT2JqZWN00hob - HyCiIB1XTlNDb2xvcggRGyQpMkRJTFFTWV9o - fYWSmZueoKKnrK6wtb7JzNXa3QAAAAAAAAEB - AAAAAAAAACEAAAAAAAAAAAAAAAAAAADl - - b - 0 - g - 0 - r - 0 - - Font - Verdana - Size - 18 - - ID - 14 - Shape - Rectangle - Style - - fill - - Color - - b - 0.776486 - g - 0.588495 - r - 0.670497 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.618021 - g - 0.412924 - r - 0.50312 - - - shadow - - Beneath - YES - Color - - a - 0.15 - b - 0 - g - 0 - r - 0 - - Fuzziness - 0.0 - ShadowVector - {2, 2} - - stroke - - Color - - b - 0.590997 - g - 0.18677 - r - 0.567819 - - Draws - NO - Width - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 -\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset134 STHeitiSC-Light;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs36 \cf0 SQL} - VerticalPad - 0 - - TextRelativeArea - {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} - - - Bounds - {{532.04631600645348, 25.096524339927051}, {166.93052673339844, 79.447883605957031}} - Class - ShapedGraphic - FontInfo - - Color - - archive - - YnBsaXN0MDDUAQIDBAUGBwpZJGFyY2hpdmVy - WCR2ZXJzaW9uVCR0b3BYJG9iamVjdHNfEA9O - U0tleWVkQXJjaGl2ZXISAAGGoNEICVRyb290 - gAGlCwwVGR5VJG51bGzUDQ4PEBESExRfEBJO - U0N1c3RvbUNvbG9yU3BhY2VXTlNXaGl0ZVxO - U0NvbG9yU3BhY2VWJGNsYXNzgAJCMAAQA4AE - 0hYQFxhUTlNJRBACgAPSGhscD1gkY2xhc3Nl - c1okY2xhc3NuYW1log8dWE5TT2JqZWN00hob - HyCiIB1XTlNDb2xvcggRGyQpMkRJTFFTWV9o - fYWSmZueoKKnrK6wtb7JzNXa3QAAAAAAAAEB - AAAAAAAAACEAAAAAAAAAAAAAAAAAAADl - - b - 0 - g - 0 - r - 0 - - Font - Verdana - Size - 18 - - ID - 13 - Shape - Rectangle - Style - - fill - - Color - - b - 0.793851 - g - 0.625208 - r - 0.562982 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.639673 - g - 0.450584 - r - 0.381079 - - - shadow - - Beneath - YES - Color - - a - 0.15 - b - 0 - g - 0 - r - 0 - - Fuzziness - 0.0 - ShadowVector - {2, 2} - - stroke - - Color - - b - 0.511421 - g - 0.637255 - r - 0.120867 - - Draws - NO - Width - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 -\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;\f1\fnil\fcharset134 STHeitiSC-Light;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs36 \cf0 Cache\ - -\f1 Store} - VerticalPad - 0 - - TextRelativeArea - {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} - - - Bounds - {{298.9845516069733, 37.412179328792348}, {173.03089904785156, 51}} - Class - ShapedGraphic - FontInfo - - Color - - b - 0.8 - g - 0.8 - r - 0.8 - - Font - Verdana - Size - 18 - - ID - 17 - Shape - Rectangle - Style - - fill - - Color - - b - 0.6 - g - 0.6 - r - 0.6 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.4 - g - 0.4 - r - 0.4 - - - shadow - - Beneath - YES - Color - - a - 0.15 - b - 0 - g - 0 - r - 0 - - Fuzziness - 0.0 - ShadowVector - {2, 2} - - stroke - - Color - - b - 0.590997 - g - 0.18677 - r - 0.567819 - - Draws - NO - Width - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 -\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} -{\colortbl;\red255\green255\blue255;\red204\green204\blue204;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs36 \cf2 LRUCacher} - VerticalPad - 0 - - TextRelativeArea - {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} - - - Bounds - {{99.822519035537013, 333.12161552787364}, {88, 51}} - Class - ShapedGraphic - FontInfo - - Color - - archive - - YnBsaXN0MDDUAQIDBAUGBwpZJGFyY2hpdmVy - WCR2ZXJzaW9uVCR0b3BYJG9iamVjdHNfEA9O - U0tleWVkQXJjaGl2ZXISAAGGoNEICVRyb290 - gAGlCwwVGR5VJG51bGzUDQ4PEBESExRfEBJO - U0N1c3RvbUNvbG9yU3BhY2VXTlNXaGl0ZVxO - U0NvbG9yU3BhY2VWJGNsYXNzgAJCMAAQA4AE - 0hYQFxhUTlNJRBACgAPSGhscD1gkY2xhc3Nl - c1okY2xhc3NuYW1log8dWE5TT2JqZWN00hob - HyCiIB1XTlNDb2xvcggRGyQpMkRJTFFTWV9o - fYWSmZueoKKnrK6wtb7JzNXa3QAAAAAAAAEB - AAAAAAAAACEAAAAAAAAAAAAAAAAAAADl - - b - 0 - g - 0 - r - 0 - - Font - Verdana - NSKern - 0.0 - Size - 15 - - ID - 12 - Magnets - - {1, 0} - {-1, 0} - - Shape - Rectangle - Style - - fill - - Color - - b - 0.806569 - g - 0.806569 - r - 0.806569 - - FillType - 2 - GradientAngle - 90 - GradientColor - - w - 0.653285 - - - shadow - - Beneath - YES - Color - - a - 0.15 - b - 0 - g - 0 - r - 0 - - Fuzziness - 0.0 - ShadowVector - {2, 2} - - stroke - - Color - - b - 0.2 - g - 0.2 - r - 0.2 - - Draws - NO - - - Text - - Text - {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 -\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} -{\colortbl;\red255\green255\blue255;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc - -\f0\fs30 \cf0 \expnd0\expndtw0\kerning0 -Delet\ -SQL} - VerticalPad - 0 - - TextRelativeArea - {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} - - - Bounds - {{97.118322659032714, 226.09466078541047}, {88, 51}} - Class - ShapedGraphic - FontInfo - - Color - - b - 0 - g - 0 - r - 0.501961 - - Font - Verdana - NSKern - 0.0 - Size - 15 - - ID - 15 - Magnets - - {1, 0} - {-1, 0} - - Shape - Rectangle - Style - - fill - - Color - - b - 0 - g - 0.389485 - r - 1 - - FillType - 3 - GradientCenter - {-0.34285700000000002, -0.114286} - GradientColor - - b - 0 - g - 0.495748 - r - 1 - - MiddleColor - - b - 0 - g - 0.887657 - r - 1 - - MiddleFraction - 0.6269841194152832 - TrippleBlend - YES - - shadow - - Beneath - YES - Color - - a - 0.15 - b - 0 - g - 0 - r - 0 - - Fuzziness - 0.0 - ShadowVector - {2, 2} - - stroke - - Color - - b - 0.2 - g - 0.2 - r - 0.2 - - Draws - NO - - - Text - - Text - {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 -\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Verdana;} -{\colortbl;\red255\green255\blue255;\red128\green0\blue0;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc - -\f0\fs30 \cf2 \expnd0\expndtw0\kerning0 -Update\ -SQL} - VerticalPad - 0 - - TextRelativeArea - {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} - - - Bounds - {{103, 123.08108395608006}, {80, 51}} - Class - ShapedGraphic - FontInfo - - Color - - b - 0.821332 - g - 0.672602 - r - 0.928374 - - Font - Verdana - Size - 18 - - ID - 16 - Shape - Rectangle - Style - - fill - - Color - - b - 0.436973 - g - 0.155566 - r - 0.758999 - - FillType - 2 - GradientAngle - 90 - GradientColor - - b - 0.25098 - g - 0 - r - 0.501961 - - - shadow - - Beneath - YES - Color - - a - 0.15 - b - 0 - g - 0 - r - 0 - - Fuzziness - 0.0 - ShadowVector - {2, 2} - - stroke - - Color - - b - 0.511421 - g - 0.637255 - r - 0.120867 - - Draws - NO - Width - 2 - - - Text - - Text - {\rtf1\ansi\ansicpg936\cocoartf1187\cocoasubrtf390 -\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset134 STHeitiSC-Light;\f1\fnil\fcharset0 Verdana;} -{\colortbl;\red255\green255\blue255;\red237\green172\blue209;} -\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc - -\f0\fs36 \cf2 select -\f1 -\f0 SQL} - VerticalPad - 0 - - TextRelativeArea - {{0.10000000000000001, 0.14999999999999999}, {0.80000000000000004, 0.69999999999999996}} - - - GridInfo - - GuidesLocked - NO - GuidesVisible - YES - HPages - 2 - ImageCounter - 3 - KeepToScale - - Layers - - - Lock - NO - Name - 图层 1 - Print - YES - View - YES - - - LayoutInfo - - Animate - NO - AutoLayout - 2 - circoMinDist - 18 - circoSeparation - 0.0 - layoutEngine - neato - neatoLineLength - 0.92083334922790527 - neatoSeparation - 0.0 - twopiSeparation - 0.0 - - LinksVisible - NO - MagnetsVisible - NO - MasterSheets - - ModificationDate - 2013-09-29 08:24:57 +0000 - Modifier - Lunny Xiao - NotesVisible - NO - Orientation - 2 - OriginVisible - NO - OutlineStyle - Brainstorming/Clouds - PageBreaks - NO - PrintInfo - - NSBottomMargin - - float - 41 - - NSHorizonalPagination - - coded - BAtzdHJlYW10eXBlZIHoA4QBQISEhAhOU051bWJlcgCEhAdOU1ZhbHVlAISECE5TT2JqZWN0AIWEASqEhAFxlwCG - - NSLeftMargin - - float - 18 - - NSPaperSize - - size - {595, 842} - - NSPrintReverseOrientation - - int - 0 - - NSRightMargin - - float - 18 - - NSTopMargin - - float - 18 - - - PrintOnePage - - ReadOnly - NO - RowAlign - 1 - RowSpacing - 36 - SheetTitle - 版面 1 - SmartAlignmentGuidesActive - YES - SmartDistanceGuidesActive - YES - UniqueID - 1 - UseEntirePage - - VPages - 1 - WindowInfo - - CurrentSheet - 0 - ExpandedCanvases - - FitInWindow - - Frame - {{138, 197}, {869, 617}} - ListView - - OutlineWidth - 142 - RightSidebar - - Sidebar - - SidebarWidth - 138 - VisibleRegion - {{1.0591603214876664, 1.0591603214876664}, {770.00955372153339, 553.94084813804955}} - Zoom - 0.94414412975311279 - ZoomValues - - - 版面 1 - 0.0 - 1 - - - - - diff --git a/docs/images/cache_design.png b/docs/images/cache_design.png deleted file mode 100644 index 11ce817663c8941c266abdd4dae450c277233420..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 243596 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
Q}&q4+j^^$+5QTmA+dnUnvir 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

E!EASHl6N8}Ct(#6gt)wqlE`eGY_> zzE0+9xWJnEuBaoo8J@$LWY(c`w}EV>Q;EohUAZvoL-1a-Jw&m1@JGxjkU#n?Jze`X z`ydNJRQ+W45;U*!)lwTt4F3q?Q>D$R&9;(rj><^1@22o4A0Wq;j@Em ze&XM^lcW+KH%3L3%PZV=Pk(Rd=T5 zcfC^kq@&boM&>zDwP}n-#BHRUbTs}!Ess%cfGN4dcd39P#(NVkH)Jh<+jCOV>P`Pd zIIJGU?)%IE_dBXmi2G+~+~~BQU&sQ)u+el+MN#R)&&LpjYy(9D1q^6Sh%AbP zJT*#KV-IA2La%l$%20>OhE$-W@S2zEIsuGT(ZU5#wrzSxf2qEwkee7IAJO*{+-?3=ZF175iI z$K>6iiXekyZtMW#)US31u)8Bh6!!bJ6lf^565JeRFUd}vvNOQ*v zs^O8bI8Z0Z`s>JO|69nOQJnyT0O!jt+6&(^Q-$|xl*0JF5`Yo#?`d{9pj^}qzwpPh|n-d5ryw%bzA&dw^r+!JbCo@AQCV0B_SlN%V9tY6^^*=&Nq?zbL4c)c9WpLETXJsrhZv7H9PzT7J-A_@AYGs*L z@fLSi@p>l2J6nD~Tz0B5_ncyH*OL-`5%-jyx8C*RHP?f&zwiTG&rwrV0_o`xG31$! zYTBOTc%n;eSfHmBpaJtvo&vOxgeO`()SO~zMRJshczAyxy(k;lMQ(G+@sw#K>yMwR z%xZ$2xlpVfdrA*$X8G|T^4dvsUYEH;VZ*zu-T`Gv z+JkKEI>TijQ~oOL_E37bF($V1mH}D+n^Dr&)IMJ>*(`bRI5 zO_%%sPP0I%P)yiY1a9FtRs0u_{771pvh#BXoxJTEcMmd40PmPUG6R@Z8UOsr7rUZU_DX3ii)#{(L){wSBF_GivJ}qM!ImtLM=yYfb4t z&=Yzp_S|%XYlSYEQ1WF0JJp_lYIm`&7b-FDK=J0{bQ?`}7cIYa%0mEbSf8TEtdsH= zo(wv|~dlvONz~@KF&xI@rxcOUXI=I1{Qem}o7KYcAiZ<} z{l|Un&ONQ=3W}mvPglPD*{fQRmEU{byYXhl#kgs8$nVxVw?@Bv)+bywJHoGZNKI!V z7W&&`_s@iN*3VUMJf~QHB(bdRA9+rAoVJ|N2SDZf0tO}=abaTt%nHS7I&gMXG${E+ zzvM~Ap23PxFh$Hz!$o-x-0!dGWV(p+@qFZXSe2n}+!R*}w@%|hGHmZBfjqb6-}Kv6 z-lSu`OZ)1_^|A|{aOU@6YiwX&4nOH~uv6D^g0^VhDeTE|JKPw}&xDm=j3@@q@&p}V zz`Y#7lxz9*1-b7+ql`uyfUxfHxrJ_(q+YCkNuMo<7z<3ORRmi2?Ia zniZc8`zYhEwW|JnF=Ft9*#%t(kJ|N2+HJc_>okejTUnqFjZ&FzzO3qDt#3z3fpVX^ zPUtGb>>`hkx7u*s|5M%b%YQEM(HAVX&)S^79!qvwV2G z(ZPkDo^# 0)) } else { temp += fmt.Sprintf(", %v", d) } case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - if col.SQLType.Name == core.Bool { + if col.SQLType.Name == schemas.Bool { temp += fmt.Sprintf(", %v", strconv.FormatBool(reflect.ValueOf(d).Uint() > 0)) } else { temp += fmt.Sprintf(", %v", d) @@ -611,7 +616,7 @@ func (engine *Engine) dumpTables(tables []*core.Table, w io.Writer, tp ...core.D } // FIXME: Hack for postgres - if string(dialect.DBType()) == core.POSTGRES && table.AutoIncrColumn() != nil { + if string(dialect.DBType()) == schemas.POSTGRES && table.AutoIncrColumn() != nil { _, err = io.WriteString(w, "SELECT setval('"+table.Name+"_id_seq', COALESCE((SELECT MAX("+table.AutoIncrColumn().Name+") + 1 FROM "+dialect.Quote(table.Name)+"), 1), false);\n") if err != nil { return err @@ -856,7 +861,7 @@ func (engine *Engine) UnMapType(t reflect.Type) { delete(engine.Tables, t) } -func (engine *Engine) autoMapType(v reflect.Value) (*core.Table, error) { +func (engine *Engine) autoMapType(v reflect.Value) (*schemas.Table, error) { t := v.Type() engine.mutex.Lock() defer engine.mutex.Unlock() @@ -888,7 +893,7 @@ func (engine *Engine) GobRegister(v interface{}) *Engine { // Table table struct type Table struct { - *core.Table + *schemas.Table Name string } @@ -907,12 +912,12 @@ func (engine *Engine) TableInfo(bean interface{}) *Table { return &Table{tb, engine.TableName(bean)} } -func addIndex(indexName string, table *core.Table, col *core.Column, indexType int) { +func addIndex(indexName string, table *schemas.Table, col *schemas.Column, indexType int) { if index, ok := table.Indexes[indexName]; ok { index.AddColumn(col.Name) col.Indexes[index.Name] = indexType } else { - index := core.NewIndex(indexName, indexType) + index := schemas.NewIndex(indexName, indexType) index.AddColumn(col.Name) table.AddIndex(index) col.Indexes[index.Name] = indexType @@ -928,11 +933,11 @@ var ( tpTableName = reflect.TypeOf((*TableName)(nil)).Elem() ) -func (engine *Engine) mapType(v reflect.Value) (*core.Table, error) { +func (engine *Engine) mapType(v reflect.Value) (*schemas.Table, error) { t := v.Type() - table := core.NewEmptyTable() + table := schemas.NewEmptyTable() table.Type = t - table.Name = getTableName(engine.TableMapper, v) + table.Name = names.GetTableName(engine.TableMapper, v) var idFieldColName string var hasCacheTag, hasNoCacheTag bool @@ -941,17 +946,17 @@ func (engine *Engine) mapType(v reflect.Value) (*core.Table, error) { tag := t.Field(i).Tag ormTagStr := tag.Get(engine.TagIdentifier) - var col *core.Column + var col *schemas.Column fieldValue := v.Field(i) fieldType := fieldValue.Type() if ormTagStr != "" { - col = &core.Column{ + col = &schemas.Column{ FieldName: t.Field(i).Name, Nullable: true, IsPrimaryKey: false, IsAutoIncrement: false, - MapType: core.TWOSIDES, + MapType: schemas.TWOSIDES, Indexes: make(map[string]int), DefaultIsEmpty: true, } @@ -1039,9 +1044,9 @@ func (engine *Engine) mapType(v reflect.Value) (*core.Table, error) { } if col.SQLType.Name == "" { - col.SQLType = core.Type2SQLType(fieldType) + col.SQLType = schemas.Type2SQLType(fieldType) } - engine.dialect.SqlType(col) + engine.dialect.SQLType(col) if col.Length == 0 { col.Length = col.SQLType.DefaultLength } @@ -1053,9 +1058,9 @@ func (engine *Engine) mapType(v reflect.Value) (*core.Table, error) { } if ctx.isUnique { - ctx.indexNames[col.Name] = core.UniqueType + ctx.indexNames[col.Name] = schemas.UniqueType } else if ctx.isIndex { - ctx.indexNames[col.Name] = core.IndexType + ctx.indexNames[col.Name] = schemas.IndexType } for indexName, indexType := range ctx.indexNames { @@ -1063,18 +1068,18 @@ func (engine *Engine) mapType(v reflect.Value) (*core.Table, error) { } } } else { - var sqlType core.SQLType + var sqlType schemas.SQLType if fieldValue.CanAddr() { - if _, ok := fieldValue.Addr().Interface().(core.Conversion); ok { - sqlType = core.SQLType{Name: core.Text} + if _, ok := fieldValue.Addr().Interface().(Conversion); ok { + sqlType = schemas.SQLType{Name: schemas.Text} } } - if _, ok := fieldValue.Interface().(core.Conversion); ok { - sqlType = core.SQLType{Name: core.Text} + if _, ok := fieldValue.Interface().(Conversion); ok { + sqlType = schemas.SQLType{Name: schemas.Text} } else { - sqlType = core.Type2SQLType(fieldType) + sqlType = schemas.Type2SQLType(fieldType) } - col = core.NewColumn(engine.ColumnMapper.Obj2Table(t.Field(i).Name), + col = schemas.NewColumn(engine.ColumnMapper.Obj2Table(t.Field(i).Name), t.Field(i).Name, sqlType, sqlType.DefaultLength, sqlType.DefaultLength2, true) @@ -1105,7 +1110,7 @@ func (engine *Engine) mapType(v reflect.Value) (*core.Table, error) { engine.setCacher(table.Name, engine.Cacher) } else { engine.logger.Info("enable LRU cache on table:", table.Name) - engine.setCacher(table.Name, NewLRUCacher2(NewMemoryStore(), time.Hour, 10000)) + engine.setCacher(table.Name, caches.NewLRUCacher2(caches.NewMemoryStore(), time.Hour, 10000)) } } if hasNoCacheTag { @@ -1133,24 +1138,24 @@ func (engine *Engine) IsTableExist(beanOrTableName interface{}) (bool, error) { // IdOf get id from one struct // // Deprecated: use IDOf instead. -func (engine *Engine) IdOf(bean interface{}) core.PK { +func (engine *Engine) IdOf(bean interface{}) schemas.PK { return engine.IDOf(bean) } // IDOf get id from one struct -func (engine *Engine) IDOf(bean interface{}) core.PK { +func (engine *Engine) IDOf(bean interface{}) schemas.PK { return engine.IdOfV(reflect.ValueOf(bean)) } // IdOfV get id from one value of struct // // Deprecated: use IDOfV instead. -func (engine *Engine) IdOfV(rv reflect.Value) core.PK { +func (engine *Engine) IdOfV(rv reflect.Value) schemas.PK { return engine.IDOfV(rv) } // IDOfV get id from one value of struct -func (engine *Engine) IDOfV(rv reflect.Value) core.PK { +func (engine *Engine) IDOfV(rv reflect.Value) schemas.PK { pk, err := engine.idOfV(rv) if err != nil { engine.logger.Error(err) @@ -1159,7 +1164,7 @@ func (engine *Engine) IDOfV(rv reflect.Value) core.PK { return pk } -func (engine *Engine) idOfV(rv reflect.Value) (core.PK, error) { +func (engine *Engine) idOfV(rv reflect.Value) (schemas.PK, error) { v := reflect.Indirect(rv) table, err := engine.autoMapType(v) if err != nil { @@ -1202,10 +1207,10 @@ func (engine *Engine) idOfV(rv reflect.Value) (core.PK, error) { return nil, err } } - return core.PK(pk), nil + return schemas.PK(pk), nil } -func (engine *Engine) idTypeAssertion(col *core.Column, sid string) (interface{}, error) { +func (engine *Engine) idTypeAssertion(col *schemas.Column, sid string) (interface{}, error) { if col.SQLType.IsNumeric() { n, err := strconv.ParseInt(sid, 10, 64) if err != nil { @@ -1317,7 +1322,7 @@ func (engine *Engine) Sync(beans ...interface{}) error { if err := session.statement.setRefBean(bean); err != nil { return err } - if index.Type == core.UniqueType { + if index.Type == schemas.UniqueType { isExist, err := session.isIndexExist2(tableNameNoSchema, index.Cols, true) if err != nil { return err @@ -1332,7 +1337,7 @@ func (engine *Engine) Sync(beans ...interface{}) error { return err } } - } else if index.Type == core.IndexType { + } else if index.Type == schemas.IndexType { isExist, err := session.isIndexExist2(tableNameNoSchema, index.Cols, false) if err != nil { return err @@ -1601,7 +1606,7 @@ func (engine *Engine) Import(r io.Reader) ([]sql.Result, error) { } // nowTime return current time -func (engine *Engine) nowTime(col *core.Column) (interface{}, time.Time) { +func (engine *Engine) nowTime(col *schemas.Column) (interface{}, time.Time) { t := time.Now() var tz = engine.DatabaseTZ if !col.DisableTimeZone && col.TimeZone != nil { @@ -1610,7 +1615,7 @@ func (engine *Engine) nowTime(col *core.Column) (interface{}, time.Time) { return engine.formatTime(col.SQLType.Name, t.In(tz)), t.In(engine.TZLocation) } -func (engine *Engine) formatColTime(col *core.Column, t time.Time) (v interface{}) { +func (engine *Engine) formatColTime(col *schemas.Column, t time.Time) (v interface{}) { if t.IsZero() { if col.Nullable { return nil @@ -1627,20 +1632,20 @@ func (engine *Engine) formatColTime(col *core.Column, t time.Time) (v interface{ // formatTime format time as column type func (engine *Engine) formatTime(sqlTypeName string, t time.Time) (v interface{}) { switch sqlTypeName { - case core.Time: + case schemas.Time: s := t.Format("2006-01-02 15:04:05") // time.RFC3339 v = s[11:19] - case core.Date: + case schemas.Date: v = t.Format("2006-01-02") - case core.DateTime, core.TimeStamp, core.Varchar: // !DarthPestilane! format time when sqlTypeName is core.Varchar. + case schemas.DateTime, schemas.TimeStamp, schemas.Varchar: // !DarthPestilane! format time when sqlTypeName is schemas.Varchar. v = t.Format("2006-01-02 15:04:05") - case core.TimeStampz: - if engine.dialect.DBType() == core.MSSQL { + case schemas.TimeStampz: + if engine.dialect.DBType() == schemas.MSSQL { v = t.Format("2006-01-02T15:04:05.9999999Z07:00") } else { v = t.Format(time.RFC3339Nano) } - case core.BigInt, core.Int: + case schemas.BigInt, schemas.Int: v = t.Unix() default: v = t @@ -1649,12 +1654,12 @@ func (engine *Engine) formatTime(sqlTypeName string, t time.Time) (v interface{} } // GetColumnMapper returns the column name mapper -func (engine *Engine) GetColumnMapper() core.IMapper { +func (engine *Engine) GetColumnMapper() names.Mapper { return engine.ColumnMapper } // GetTableMapper returns the table name mapper -func (engine *Engine) GetTableMapper() core.IMapper { +func (engine *Engine) GetTableMapper() names.Mapper { return engine.TableMapper } diff --git a/engine_cond.go b/engine_cond.go index 17f50bd7..480e7ded 100644 --- a/engine_cond.go +++ b/engine_cond.go @@ -12,10 +12,10 @@ import ( "time" "xorm.io/builder" - "xorm.io/core" + "xorm.io/xorm/schemas" ) -func (engine *Engine) buildConds(table *core.Table, bean interface{}, +func (engine *Engine) buildConds(table *schemas.Table, bean interface{}, includeVersion bool, includeUpdated bool, includeNil bool, includeAutoIncr bool, allUseBool bool, useAllCols bool, unscoped bool, mustColumnMap map[string]bool, tableName, aliasName string, addedTableName bool) (builder.Cond, error) { @@ -31,7 +31,7 @@ func (engine *Engine) buildConds(table *core.Table, bean interface{}, continue } - if engine.dialect.DBType() == core.MSSQL && (col.SQLType.Name == core.Text || col.SQLType.IsBlob() || col.SQLType.Name == core.TimeStampz) { + if engine.dialect.DBType() == schemas.MSSQL && (col.SQLType.Name == schemas.Text || col.SQLType.IsBlob() || col.SQLType.Name == schemas.TimeStampz) { continue } if col.SQLType.IsJson() { @@ -130,13 +130,13 @@ func (engine *Engine) buildConds(table *core.Table, bean interface{}, t := int64(fieldValue.Uint()) val = reflect.ValueOf(&t).Interface() case reflect.Struct: - if fieldType.ConvertibleTo(core.TimeType) { - t := fieldValue.Convert(core.TimeType).Interface().(time.Time) + if fieldType.ConvertibleTo(schemas.TimeType) { + t := fieldValue.Convert(schemas.TimeType).Interface().(time.Time) if !requiredField && (t.IsZero() || !fieldValue.IsValid()) { continue } val = engine.formatColTime(col, t) - } else if _, ok := reflect.New(fieldType).Interface().(core.Conversion); ok { + } else if _, ok := reflect.New(fieldType).Interface().(Conversion); ok { continue } else if valNul, ok := fieldValue.Interface().(driver.Valuer); ok { val, _ = valNul.Value() diff --git a/engine_group.go b/engine_group.go index 42d49eca..24dc0103 100644 --- a/engine_group.go +++ b/engine_group.go @@ -8,7 +8,9 @@ import ( "context" "time" - "xorm.io/core" + "xorm.io/xorm/caches" + "xorm.io/xorm/log" + "xorm.io/xorm/names" ) // EngineGroup defines an engine group @@ -109,7 +111,7 @@ func (eg *EngineGroup) Ping() error { } // SetColumnMapper set the column name mapping rule -func (eg *EngineGroup) SetColumnMapper(mapper core.IMapper) { +func (eg *EngineGroup) SetColumnMapper(mapper names.Mapper) { eg.Engine.ColumnMapper = mapper for i := 0; i < len(eg.slaves); i++ { eg.slaves[i].ColumnMapper = mapper @@ -125,7 +127,7 @@ func (eg *EngineGroup) SetConnMaxLifetime(d time.Duration) { } // SetDefaultCacher set the default cacher -func (eg *EngineGroup) SetDefaultCacher(cacher core.Cacher) { +func (eg *EngineGroup) SetDefaultCacher(cacher caches.Cacher) { eg.Engine.SetDefaultCacher(cacher) for i := 0; i < len(eg.slaves); i++ { eg.slaves[i].SetDefaultCacher(cacher) @@ -133,7 +135,7 @@ func (eg *EngineGroup) SetDefaultCacher(cacher core.Cacher) { } // SetLogger set the new logger -func (eg *EngineGroup) SetLogger(logger core.ILogger) { +func (eg *EngineGroup) SetLogger(logger log.Logger) { eg.Engine.SetLogger(logger) for i := 0; i < len(eg.slaves); i++ { eg.slaves[i].SetLogger(logger) @@ -141,7 +143,7 @@ func (eg *EngineGroup) SetLogger(logger core.ILogger) { } // SetLogLevel sets the logger level -func (eg *EngineGroup) SetLogLevel(level core.LogLevel) { +func (eg *EngineGroup) SetLogLevel(level log.LogLevel) { eg.Engine.SetLogLevel(level) for i := 0; i < len(eg.slaves); i++ { eg.slaves[i].SetLogLevel(level) @@ -149,7 +151,7 @@ func (eg *EngineGroup) SetLogLevel(level core.LogLevel) { } // SetMapper set the name mapping rules -func (eg *EngineGroup) SetMapper(mapper core.IMapper) { +func (eg *EngineGroup) SetMapper(mapper names.Mapper) { eg.Engine.SetMapper(mapper) for i := 0; i < len(eg.slaves); i++ { eg.slaves[i].SetMapper(mapper) @@ -179,7 +181,7 @@ func (eg *EngineGroup) SetPolicy(policy GroupPolicy) *EngineGroup { } // SetTableMapper set the table name mapping rule -func (eg *EngineGroup) SetTableMapper(mapper core.IMapper) { +func (eg *EngineGroup) SetTableMapper(mapper names.Mapper) { eg.Engine.TableMapper = mapper for i := 0; i < len(eg.slaves); i++ { eg.slaves[i].TableMapper = mapper diff --git a/engine_table.go b/engine_table.go index 001d72e7..e60d8fce 100644 --- a/engine_table.go +++ b/engine_table.go @@ -9,16 +9,18 @@ import ( "reflect" "strings" - "xorm.io/core" + "xorm.io/xorm/dialects" + "xorm.io/xorm/names" + "xorm.io/xorm/schemas" ) // tbNameWithSchema will automatically add schema prefix on table name func (engine *Engine) tbNameWithSchema(v string) string { // Add schema name as prefix of table name. // Only for postgres database. - if engine.dialect.DBType() == core.POSTGRES && + if engine.dialect.DBType() == schemas.POSTGRES && engine.dialect.URI().Schema != "" && - engine.dialect.URI().Schema != postgresPublicSchema && + engine.dialect.URI().Schema != dialects.PostgresPublicSchema && strings.Index(v, ".") == -1 { return engine.dialect.URI().Schema + "." + v } @@ -44,7 +46,7 @@ func (engine *Engine) TableName(bean interface{}, includeSchema ...bool) string } // tbName get some table's table name -func (session *Session) tbNameNoSchema(table *core.Table) string { +func (session *Session) tbNameNoSchema(table *schemas.Table) string { if len(session.statement.AltTableName) > 0 { return session.statement.AltTableName } @@ -76,7 +78,7 @@ func (engine *Engine) tbNameNoSchema(tablename interface{}) string { v := rValue(f) t := v.Type() if t.Kind() == reflect.Struct { - table = getTableName(engine.TableMapper, v) + table = names.GetTableName(engine.TableMapper, v) } else { table = engine.Quote(fmt.Sprintf("%v", f)) } @@ -94,12 +96,12 @@ func (engine *Engine) tbNameNoSchema(tablename interface{}) string { return tablename.(string) case reflect.Value: v := tablename.(reflect.Value) - return getTableName(engine.TableMapper, v) + return names.GetTableName(engine.TableMapper, v) default: v := rValue(tablename) t := v.Type() if t.Kind() == reflect.Struct { - return getTableName(engine.TableMapper, v) + return names.GetTableName(engine.TableMapper, v) } return engine.Quote(fmt.Sprintf("%v", tablename)) } diff --git a/examples/cache/cache.go b/examples/cache/cache.go index ba756813..4ac8eedf 100644 --- a/examples/cache/cache.go +++ b/examples/cache/cache.go @@ -6,6 +6,7 @@ import ( _ "github.com/mattn/go-sqlite3" "xorm.io/xorm" + "xorm.io/xorm/caches" ) // User describes a user @@ -15,7 +16,7 @@ type User struct { } func main() { - f := "cache.db" + f := "caches.db" os.Remove(f) Orm, err := xorm.NewEngine("sqlite3", f) @@ -24,7 +25,7 @@ func main() { return } Orm.ShowSQL(true) - cacher := xorm.NewLRUCacher(xorm.NewMemoryStore(), 1000) + cacher := caches.NewLRUCacher(caches.NewMemoryStore(), 1000) Orm.SetDefaultCacher(cacher) err = Orm.CreateTables(&User{}) diff --git a/examples/cache_gorountine/cache_goroutine.go b/examples/cache_gorountine/cache_goroutine.go index 94b29e82..3543cba8 100644 --- a/examples/cache_gorountine/cache_goroutine.go +++ b/examples/cache_gorountine/cache_goroutine.go @@ -8,6 +8,7 @@ import ( _ "github.com/go-sql-driver/mysql" _ "github.com/mattn/go-sqlite3" "xorm.io/xorm" + "xorm.io/xorm/caches" ) // User describes a user @@ -87,7 +88,7 @@ func main() { return } engine.ShowSQL(true) - cacher := xorm.NewLRUCacher2(xorm.NewMemoryStore(), time.Hour, 1000) + cacher := caches.NewLRUCacher2(caches.NewMemoryStore(), time.Hour, 1000) engine.SetDefaultCacher(cacher) fmt.Println(engine) test(engine) @@ -97,7 +98,7 @@ func main() { fmt.Println("-----start mysql go routines-----") engine, err = mysqlEngine() engine.ShowSQL(true) - cacher = xorm.NewLRUCacher2(xorm.NewMemoryStore(), time.Hour, 1000) + cacher = caches.NewLRUCacher2(caches.NewMemoryStore(), time.Hour, 1000) engine.SetDefaultCacher(cacher) if err != nil { fmt.Println(err) diff --git a/helpers.go b/helpers.go index aadeb6dc..b7c583f7 100644 --- a/helpers.go +++ b/helpers.go @@ -12,7 +12,7 @@ import ( "strconv" "strings" - "xorm.io/core" + "xorm.io/xorm/schemas" ) // str2PK convert string value to primary key value according to tp @@ -95,26 +95,6 @@ func str2PK(s string, tp reflect.Type) (interface{}, error) { return v.Interface(), nil } -func splitTag(tag string) (tags []string) { - tag = strings.TrimSpace(tag) - var hasQuote = false - var lastIdx = 0 - for i, t := range tag { - if t == '\'' { - hasQuote = !hasQuote - } else if t == ' ' { - if lastIdx < i && !hasQuote { - tags = append(tags, strings.TrimSpace(tag[lastIdx:i])) - lastIdx = i + 1 - } - } - } - if lastIdx < len(tag) { - tags = append(tags, strings.TrimSpace(tag[lastIdx:])) - } - return -} - type zeroable interface { IsZero() bool } @@ -249,7 +229,7 @@ func int64ToInt(id int64, tp reflect.Type) interface{} { return int64ToIntValue(id, tp).Interface() } -func isPKZero(pk core.PK) bool { +func isPKZero(pk schemas.PK) bool { for _, k := range pk { if isZero(k) { return true diff --git a/interface.go b/interface.go index 81a4b68a..43b4b5f4 100644 --- a/interface.go +++ b/interface.go @@ -10,7 +10,11 @@ import ( "reflect" "time" - "xorm.io/core" + "xorm.io/xorm/caches" + "xorm.io/xorm/dialects" + "xorm.io/xorm/log" + "xorm.io/xorm/names" + "xorm.io/xorm/schemas" ) // Interface defines the interface which Engine, EngineGroup and Session will implementate. @@ -76,31 +80,31 @@ type EngineInterface interface { ClearCache(...interface{}) error Context(context.Context) *Session CreateTables(...interface{}) error - DBMetas() ([]*core.Table, error) - Dialect() core.Dialect + DBMetas() ([]*schemas.Table, error) + Dialect() dialects.Dialect DropTables(...interface{}) error - DumpAllToFile(fp string, tp ...core.DbType) error - GetCacher(string) core.Cacher - GetColumnMapper() core.IMapper - GetDefaultCacher() core.Cacher - GetTableMapper() core.IMapper + DumpAllToFile(fp string, tp ...dialects.DBType) error + GetCacher(string) caches.Cacher + GetColumnMapper() names.Mapper + GetDefaultCacher() caches.Cacher + GetTableMapper() names.Mapper GetTZDatabase() *time.Location GetTZLocation() *time.Location - MapCacher(interface{}, core.Cacher) error + MapCacher(interface{}, caches.Cacher) error NewSession() *Session NoAutoTime() *Session Quote(string) string - SetCacher(string, core.Cacher) + SetCacher(string, caches.Cacher) SetConnMaxLifetime(time.Duration) - SetColumnMapper(core.IMapper) - SetDefaultCacher(core.Cacher) - SetLogger(logger core.ILogger) - SetLogLevel(core.LogLevel) - SetMapper(core.IMapper) + SetColumnMapper(names.Mapper) + SetDefaultCacher(caches.Cacher) + SetLogger(logger log.Logger) + SetLogLevel(log.LogLevel) + SetMapper(names.Mapper) SetMaxOpenConns(int) SetMaxIdleConns(int) SetSchema(string) - SetTableMapper(core.IMapper) + SetTableMapper(names.Mapper) SetTZDatabase(tz *time.Location) SetTZLocation(tz *time.Location) ShowExecTime(...bool) diff --git a/logger.go b/log/logger.go similarity index 67% rename from logger.go rename to log/logger.go index 7b26e77f..b5ab9019 100644 --- a/logger.go +++ b/log/logger.go @@ -2,26 +2,56 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package log import ( "fmt" "io" "log" +) - "xorm.io/core" +// LogLevel defines a log level +type LogLevel int + +// enumerate all LogLevels +const ( + // !nashtsai! following level also match syslog.Priority value + LOG_DEBUG LogLevel = iota + LOG_INFO + LOG_WARNING + LOG_ERR + LOG_OFF + LOG_UNKNOWN ) // default log options const ( DEFAULT_LOG_PREFIX = "[xorm]" DEFAULT_LOG_FLAG = log.Ldate | log.Lmicroseconds - DEFAULT_LOG_LEVEL = core.LOG_DEBUG + DEFAULT_LOG_LEVEL = LOG_DEBUG ) -var _ core.ILogger = DiscardLogger{} +// Logger is a logger interface +type Logger interface { + Debug(v ...interface{}) + Debugf(format string, v ...interface{}) + Error(v ...interface{}) + Errorf(format string, v ...interface{}) + Info(v ...interface{}) + Infof(format string, v ...interface{}) + Warn(v ...interface{}) + Warnf(format string, v ...interface{}) -// DiscardLogger don't log implementation for core.ILogger + Level() LogLevel + SetLevel(l LogLevel) + + ShowSQL(show ...bool) + IsShowSQL() bool +} + +var _ Logger = DiscardLogger{} + +// DiscardLogger don't log implementation for ILogger type DiscardLogger struct{} // Debug empty implementation @@ -49,12 +79,12 @@ func (DiscardLogger) Warn(v ...interface{}) {} func (DiscardLogger) Warnf(format string, v ...interface{}) {} // Level empty implementation -func (DiscardLogger) Level() core.LogLevel { - return core.LOG_UNKNOWN +func (DiscardLogger) Level() LogLevel { + return LOG_UNKNOWN } // SetLevel empty implementation -func (DiscardLogger) SetLevel(l core.LogLevel) {} +func (DiscardLogger) SetLevel(l LogLevel) {} // ShowSQL empty implementation func (DiscardLogger) ShowSQL(show ...bool) {} @@ -64,17 +94,17 @@ func (DiscardLogger) IsShowSQL() bool { return false } -// SimpleLogger is the default implment of core.ILogger +// SimpleLogger is the default implment of ILogger type SimpleLogger struct { DEBUG *log.Logger ERR *log.Logger INFO *log.Logger WARN *log.Logger - level core.LogLevel + level LogLevel showSQL bool } -var _ core.ILogger = &SimpleLogger{} +var _ Logger = &SimpleLogger{} // NewSimpleLogger use a special io.Writer as logger output func NewSimpleLogger(out io.Writer) *SimpleLogger { @@ -87,7 +117,7 @@ func NewSimpleLogger2(out io.Writer, prefix string, flag int) *SimpleLogger { } // NewSimpleLogger3 let you customrize your logger prefix and flag and logLevel -func NewSimpleLogger3(out io.Writer, prefix string, flag int, l core.LogLevel) *SimpleLogger { +func NewSimpleLogger3(out io.Writer, prefix string, flag int, l LogLevel) *SimpleLogger { return &SimpleLogger{ DEBUG: log.New(out, fmt.Sprintf("%s [debug] ", prefix), flag), ERR: log.New(out, fmt.Sprintf("%s [error] ", prefix), flag), @@ -97,82 +127,82 @@ func NewSimpleLogger3(out io.Writer, prefix string, flag int, l core.LogLevel) * } } -// Error implement core.ILogger +// Error implement ILogger func (s *SimpleLogger) Error(v ...interface{}) { - if s.level <= core.LOG_ERR { + if s.level <= LOG_ERR { s.ERR.Output(2, fmt.Sprint(v...)) } return } -// Errorf implement core.ILogger +// Errorf implement ILogger func (s *SimpleLogger) Errorf(format string, v ...interface{}) { - if s.level <= core.LOG_ERR { + if s.level <= LOG_ERR { s.ERR.Output(2, fmt.Sprintf(format, v...)) } return } -// Debug implement core.ILogger +// Debug implement ILogger func (s *SimpleLogger) Debug(v ...interface{}) { - if s.level <= core.LOG_DEBUG { + if s.level <= LOG_DEBUG { s.DEBUG.Output(2, fmt.Sprint(v...)) } return } -// Debugf implement core.ILogger +// Debugf implement ILogger func (s *SimpleLogger) Debugf(format string, v ...interface{}) { - if s.level <= core.LOG_DEBUG { + if s.level <= LOG_DEBUG { s.DEBUG.Output(2, fmt.Sprintf(format, v...)) } return } -// Info implement core.ILogger +// Info implement ILogger func (s *SimpleLogger) Info(v ...interface{}) { - if s.level <= core.LOG_INFO { + if s.level <= LOG_INFO { s.INFO.Output(2, fmt.Sprint(v...)) } return } -// Infof implement core.ILogger +// Infof implement ILogger func (s *SimpleLogger) Infof(format string, v ...interface{}) { - if s.level <= core.LOG_INFO { + if s.level <= LOG_INFO { s.INFO.Output(2, fmt.Sprintf(format, v...)) } return } -// Warn implement core.ILogger +// Warn implement ILogger func (s *SimpleLogger) Warn(v ...interface{}) { - if s.level <= core.LOG_WARNING { + if s.level <= LOG_WARNING { s.WARN.Output(2, fmt.Sprint(v...)) } return } -// Warnf implement core.ILogger +// Warnf implement ILogger func (s *SimpleLogger) Warnf(format string, v ...interface{}) { - if s.level <= core.LOG_WARNING { + if s.level <= LOG_WARNING { s.WARN.Output(2, fmt.Sprintf(format, v...)) } return } -// Level implement core.ILogger -func (s *SimpleLogger) Level() core.LogLevel { +// Level implement ILogger +func (s *SimpleLogger) Level() LogLevel { return s.level } -// SetLevel implement core.ILogger -func (s *SimpleLogger) SetLevel(l core.LogLevel) { +// SetLevel implement ILogger +func (s *SimpleLogger) SetLevel(l LogLevel) { s.level = l return } -// ShowSQL implement core.ILogger +// ShowSQL implement ILogger func (s *SimpleLogger) ShowSQL(show ...bool) { if len(show) == 0 { s.showSQL = true @@ -181,7 +211,7 @@ func (s *SimpleLogger) ShowSQL(show ...bool) { s.showSQL = show[0] } -// IsShowSQL implement core.ILogger +// IsShowSQL implement ILogger func (s *SimpleLogger) IsShowSQL() bool { return s.showSQL } diff --git a/syslogger.go b/log/syslogger.go similarity index 88% rename from syslogger.go rename to log/syslogger.go index 11ba01e7..0b3e381c 100644 --- a/syslogger.go +++ b/log/syslogger.go @@ -4,16 +4,14 @@ // +build !windows,!nacl,!plan9 -package xorm +package log import ( "fmt" "log/syslog" - - "xorm.io/core" ) -var _ core.ILogger = &SyslogLogger{} +var _ Logger = &SyslogLogger{} // SyslogLogger will be depricated type SyslogLogger struct { @@ -21,7 +19,7 @@ type SyslogLogger struct { showSQL bool } -// NewSyslogLogger implements core.ILogger +// NewSyslogLogger implements Logger func NewSyslogLogger(w *syslog.Writer) *SyslogLogger { return &SyslogLogger{w: w} } @@ -67,12 +65,12 @@ func (s *SyslogLogger) Warnf(format string, v ...interface{}) { } // Level shows log level -func (s *SyslogLogger) Level() core.LogLevel { - return core.LOG_UNKNOWN +func (s *SyslogLogger) Level() LogLevel { + return LOG_UNKNOWN } // SetLevel always return error, as current log/syslog package doesn't allow to set priority level after syslog.Writer created -func (s *SyslogLogger) SetLevel(l core.LogLevel) {} +func (s *SyslogLogger) SetLevel(l LogLevel) {} // ShowSQL set if logging SQL func (s *SyslogLogger) ShowSQL(show ...bool) { diff --git a/migrate/migrate.go b/migrate/migrate.go index ed7b401c..82c58f90 100644 --- a/migrate/migrate.go +++ b/migrate/migrate.go @@ -13,7 +13,7 @@ type MigrateFunc func(*xorm.Engine) error // RollbackFunc is the func signature for rollbacking. type RollbackFunc func(*xorm.Engine) error -// InitSchemaFunc is the func signature for initializing the schema. +// InitSchemaFunc is the func signature for initializing the schemas. type InitSchemaFunc func(*xorm.Engine) error // Options define options for all migrations. @@ -34,7 +34,7 @@ type Migration struct { Rollback RollbackFunc } -// Migrate represents a collection of all migrations of a database schema. +// Migrate represents a collection of all migrations of a database schemas. type Migrate struct { db *xorm.Engine options *Options diff --git a/names/mapper.go b/names/mapper.go new file mode 100644 index 00000000..4aaf0844 --- /dev/null +++ b/names/mapper.go @@ -0,0 +1,258 @@ +// Copyright 2019 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package names + +import ( + "strings" + "sync" +) + +// Mapper represents a name convertation between struct's fields name and table's column name +type Mapper interface { + Obj2Table(string) string + Table2Obj(string) string +} + +type CacheMapper struct { + oriMapper Mapper + obj2tableCache map[string]string + obj2tableMutex sync.RWMutex + table2objCache map[string]string + table2objMutex sync.RWMutex +} + +func NewCacheMapper(mapper Mapper) *CacheMapper { + return &CacheMapper{oriMapper: mapper, obj2tableCache: make(map[string]string), + table2objCache: make(map[string]string), + } +} + +func (m *CacheMapper) Obj2Table(o string) string { + m.obj2tableMutex.RLock() + t, ok := m.obj2tableCache[o] + m.obj2tableMutex.RUnlock() + if ok { + return t + } + + t = m.oriMapper.Obj2Table(o) + m.obj2tableMutex.Lock() + m.obj2tableCache[o] = t + m.obj2tableMutex.Unlock() + return t +} + +func (m *CacheMapper) Table2Obj(t string) string { + m.table2objMutex.RLock() + o, ok := m.table2objCache[t] + m.table2objMutex.RUnlock() + if ok { + return o + } + + o = m.oriMapper.Table2Obj(t) + m.table2objMutex.Lock() + m.table2objCache[t] = o + m.table2objMutex.Unlock() + return o +} + +// SameMapper implements IMapper and provides same name between struct and +// database table +type SameMapper struct { +} + +func (m SameMapper) Obj2Table(o string) string { + return o +} + +func (m SameMapper) Table2Obj(t string) string { + return t +} + +// SnakeMapper implements IMapper and provides name transaltion between +// struct and database table +type SnakeMapper struct { +} + +func snakeCasedName(name string) string { + newstr := make([]rune, 0) + for idx, chr := range name { + if isUpper := 'A' <= chr && chr <= 'Z'; isUpper { + if idx > 0 { + newstr = append(newstr, '_') + } + chr -= ('A' - 'a') + } + newstr = append(newstr, chr) + } + + return string(newstr) +} + +func (mapper SnakeMapper) Obj2Table(name string) string { + return snakeCasedName(name) +} + +func titleCasedName(name string) string { + newstr := make([]rune, 0) + upNextChar := true + + name = strings.ToLower(name) + + for _, chr := range name { + switch { + case upNextChar: + upNextChar = false + if 'a' <= chr && chr <= 'z' { + chr -= ('a' - 'A') + } + case chr == '_': + upNextChar = true + continue + } + + newstr = append(newstr, chr) + } + + return string(newstr) +} + +func (mapper SnakeMapper) Table2Obj(name string) string { + return titleCasedName(name) +} + +// GonicMapper implements IMapper. It will consider initialisms when mapping names. +// E.g. id -> ID, user -> User and to table names: UserID -> user_id, MyUID -> my_uid +type GonicMapper map[string]bool + +func isASCIIUpper(r rune) bool { + return 'A' <= r && r <= 'Z' +} + +func toASCIIUpper(r rune) rune { + if 'a' <= r && r <= 'z' { + r -= ('a' - 'A') + } + return r +} + +func gonicCasedName(name string) string { + newstr := make([]rune, 0, len(name)+3) + for idx, chr := range name { + if isASCIIUpper(chr) && idx > 0 { + if !isASCIIUpper(newstr[len(newstr)-1]) { + newstr = append(newstr, '_') + } + } + + if !isASCIIUpper(chr) && idx > 1 { + l := len(newstr) + if isASCIIUpper(newstr[l-1]) && isASCIIUpper(newstr[l-2]) { + newstr = append(newstr, newstr[l-1]) + newstr[l-1] = '_' + } + } + + newstr = append(newstr, chr) + } + return strings.ToLower(string(newstr)) +} + +func (mapper GonicMapper) Obj2Table(name string) string { + return gonicCasedName(name) +} + +func (mapper GonicMapper) Table2Obj(name string) string { + newstr := make([]rune, 0) + + name = strings.ToLower(name) + parts := strings.Split(name, "_") + + for _, p := range parts { + _, isInitialism := mapper[strings.ToUpper(p)] + for i, r := range p { + if i == 0 || isInitialism { + r = toASCIIUpper(r) + } + newstr = append(newstr, r) + } + } + + return string(newstr) +} + +// LintGonicMapper is A GonicMapper that contains a list of common initialisms taken from golang/lint +var LintGonicMapper = GonicMapper{ + "API": true, + "ASCII": true, + "CPU": true, + "CSS": true, + "DNS": true, + "EOF": true, + "GUID": true, + "HTML": true, + "HTTP": true, + "HTTPS": true, + "ID": true, + "IP": true, + "JSON": true, + "LHS": true, + "QPS": true, + "RAM": true, + "RHS": true, + "RPC": true, + "SLA": true, + "SMTP": true, + "SSH": true, + "TLS": true, + "TTL": true, + "UI": true, + "UID": true, + "UUID": true, + "URI": true, + "URL": true, + "UTF8": true, + "VM": true, + "XML": true, + "XSRF": true, + "XSS": true, +} + +// PrefixMapper provides prefix table name support +type PrefixMapper struct { + Mapper Mapper + Prefix string +} + +func (mapper PrefixMapper) Obj2Table(name string) string { + return mapper.Prefix + mapper.Mapper.Obj2Table(name) +} + +func (mapper PrefixMapper) Table2Obj(name string) string { + return mapper.Mapper.Table2Obj(name[len(mapper.Prefix):]) +} + +func NewPrefixMapper(mapper Mapper, prefix string) PrefixMapper { + return PrefixMapper{mapper, prefix} +} + +// SuffixMapper provides suffix table name support +type SuffixMapper struct { + Mapper Mapper + Suffix string +} + +func (mapper SuffixMapper) Obj2Table(name string) string { + return mapper.Mapper.Obj2Table(name) + mapper.Suffix +} + +func (mapper SuffixMapper) Table2Obj(name string) string { + return mapper.Mapper.Table2Obj(name[:len(name)-len(mapper.Suffix)]) +} + +func NewSuffixMapper(mapper Mapper, suffix string) SuffixMapper { + return SuffixMapper{mapper, suffix} +} diff --git a/names/mapper_test.go b/names/mapper_test.go new file mode 100644 index 00000000..0edfd2a8 --- /dev/null +++ b/names/mapper_test.go @@ -0,0 +1,49 @@ +// Copyright 2019 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package names + +import ( + "testing" +) + +func TestGonicMapperFromObj(t *testing.T) { + testCases := map[string]string{ + "HTTPLib": "http_lib", + "id": "id", + "ID": "id", + "IDa": "i_da", + "iDa": "i_da", + "IDAa": "id_aa", + "aID": "a_id", + "aaID": "aa_id", + "aaaID": "aaa_id", + "MyREalFunkYLONgNAME": "my_r_eal_funk_ylo_ng_name", + } + + for in, expected := range testCases { + out := gonicCasedName(in) + if out != expected { + t.Errorf("Given %s, expected %s but got %s", in, expected, out) + } + } +} + +func TestGonicMapperToObj(t *testing.T) { + testCases := map[string]string{ + "http_lib": "HTTPLib", + "id": "ID", + "ida": "Ida", + "id_aa": "IDAa", + "aa_id": "AaID", + "my_r_eal_funk_ylo_ng_name": "MyREalFunkYloNgName", + } + + for in, expected := range testCases { + out := LintGonicMapper.Table2Obj(in) + if out != expected { + t.Errorf("Given %s, expected %s but got %s", in, expected, out) + } + } +} diff --git a/table_name.go b/names/table_name.go similarity index 70% rename from table_name.go rename to names/table_name.go index 632c2879..6dd4e552 100644 --- a/table_name.go +++ b/names/table_name.go @@ -2,15 +2,22 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package names import ( "reflect" - - "xorm.io/core" ) -func getTableName(mapper core.IMapper, v reflect.Value) string { +// TableName table name interface to define customerize table name +type TableName interface { + TableName() string +} + +var ( + tpTableName = reflect.TypeOf((*TableName)(nil)).Elem() +) + +func GetTableName(mapper Mapper, v reflect.Value) string { if t, ok := v.Interface().(TableName); ok { return t.TableName() } diff --git a/table_name_test.go b/names/table_name_test.go similarity index 55% rename from table_name_test.go rename to names/table_name_test.go index 6cb0ceaa..1f20bfaa 100644 --- a/table_name_test.go +++ b/names/table_name_test.go @@ -2,17 +2,45 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package xorm +package names import ( "reflect" "testing" + "time" "github.com/stretchr/testify/assert" - - "xorm.io/core" ) +type Userinfo struct { + Uid int64 `xorm:"id pk not null autoincr"` + Username string `xorm:"unique"` + Departname string + Alias string `xorm:"-"` + Created time.Time + Detail Userdetail `xorm:"detail_id int(11)"` + Height float64 + Avatar []byte + IsMan bool +} + +type Userdetail struct { + Id int64 + Intro string `xorm:"text"` + Profile string `xorm:"varchar(2000)"` +} + +type MyGetCustomTableImpletation struct { + Id int64 `json:"id"` + Name string `json:"name"` +} + +const getCustomTableName = "GetCustomTableInterface" + +func (MyGetCustomTableImpletation) TableName() string { + return getCustomTableName +} + type TestTableNameStruct struct{} func (t *TestTableNameStruct) TableName() string { @@ -21,53 +49,53 @@ func (t *TestTableNameStruct) TableName() string { func TestGetTableName(t *testing.T) { var kases = []struct { - mapper core.IMapper + mapper Mapper v reflect.Value expectedTableName string }{ { - core.SnakeMapper{}, + SnakeMapper{}, reflect.ValueOf(new(Userinfo)), "userinfo", }, { - core.SnakeMapper{}, + SnakeMapper{}, reflect.ValueOf(Userinfo{}), "userinfo", }, { - core.SameMapper{}, + SameMapper{}, reflect.ValueOf(new(Userinfo)), "Userinfo", }, { - core.SameMapper{}, + SameMapper{}, reflect.ValueOf(Userinfo{}), "Userinfo", }, { - core.SnakeMapper{}, + SnakeMapper{}, reflect.ValueOf(new(MyGetCustomTableImpletation)), getCustomTableName, }, { - core.SnakeMapper{}, + SnakeMapper{}, reflect.ValueOf(MyGetCustomTableImpletation{}), getCustomTableName, }, { - core.SnakeMapper{}, + SnakeMapper{}, reflect.ValueOf(MyGetCustomTableImpletation{}), getCustomTableName, }, { - core.SnakeMapper{}, + SnakeMapper{}, reflect.ValueOf(new(TestTableNameStruct)), new(TestTableNameStruct).TableName(), }, } for _, kase := range kases { - assert.EqualValues(t, kase.expectedTableName, getTableName(kase.mapper, kase.v)) + assert.EqualValues(t, kase.expectedTableName, GetTableName(kase.mapper, kase.v)) } } diff --git a/rows.go b/rows.go index bdd44589..b52b889d 100644 --- a/rows.go +++ b/rows.go @@ -9,7 +9,7 @@ import ( "fmt" "reflect" - "xorm.io/core" + "xorm.io/xorm/core" ) // Rows rows wrapper a rows to diff --git a/schemas/column.go b/schemas/column.go new file mode 100644 index 00000000..9466f6a5 --- /dev/null +++ b/schemas/column.go @@ -0,0 +1,117 @@ +// Copyright 2019 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package schemas + +import ( + "fmt" + "reflect" + "strings" + "time" +) + +const ( + TWOSIDES = iota + 1 + ONLYTODB + ONLYFROMDB +) + +// Column defines database column +type Column struct { + Name string + TableName string + FieldName string + SQLType SQLType + IsJSON bool + Length int + Length2 int + Nullable bool + Default string + Indexes map[string]int + IsPrimaryKey bool + IsAutoIncrement bool + MapType int + IsCreated bool + IsUpdated bool + IsDeleted bool + IsCascade bool + IsVersion bool + DefaultIsEmpty bool // false means column has no default set, but not default value is empty + EnumOptions map[string]int + SetOptions map[string]int + DisableTimeZone bool + TimeZone *time.Location // column specified time zone + Comment string +} + +// NewColumn creates a new column +func NewColumn(name, fieldName string, sqlType SQLType, len1, len2 int, nullable bool) *Column { + return &Column{ + Name: name, + TableName: "", + FieldName: fieldName, + SQLType: sqlType, + Length: len1, + Length2: len2, + Nullable: nullable, + Default: "", + Indexes: make(map[string]int), + IsPrimaryKey: false, + IsAutoIncrement: false, + MapType: TWOSIDES, + IsCreated: false, + IsUpdated: false, + IsDeleted: false, + IsCascade: false, + IsVersion: false, + DefaultIsEmpty: true, // default should be no default + EnumOptions: make(map[string]int), + Comment: "", + } +} + +// ValueOf returns column's filed of struct's value +func (col *Column) ValueOf(bean interface{}) (*reflect.Value, error) { + dataStruct := reflect.Indirect(reflect.ValueOf(bean)) + return col.ValueOfV(&dataStruct) +} + +// ValueOfV returns column's filed of struct's value accept reflevt value +func (col *Column) ValueOfV(dataStruct *reflect.Value) (*reflect.Value, error) { + var fieldValue reflect.Value + fieldPath := strings.Split(col.FieldName, ".") + + if dataStruct.Type().Kind() == reflect.Map { + keyValue := reflect.ValueOf(fieldPath[len(fieldPath)-1]) + fieldValue = dataStruct.MapIndex(keyValue) + return &fieldValue, nil + } else if dataStruct.Type().Kind() == reflect.Interface { + structValue := reflect.ValueOf(dataStruct.Interface()) + dataStruct = &structValue + } + + level := len(fieldPath) + fieldValue = dataStruct.FieldByName(fieldPath[0]) + for i := 0; i < level-1; i++ { + if !fieldValue.IsValid() { + break + } + if fieldValue.Kind() == reflect.Struct { + fieldValue = fieldValue.FieldByName(fieldPath[i+1]) + } else if fieldValue.Kind() == reflect.Ptr { + if fieldValue.IsNil() { + fieldValue.Set(reflect.New(fieldValue.Type().Elem())) + } + fieldValue = fieldValue.Elem().FieldByName(fieldPath[i+1]) + } else { + return nil, fmt.Errorf("field %v is not valid", col.FieldName) + } + } + + if !fieldValue.IsValid() { + return nil, fmt.Errorf("field %v is not valid", col.FieldName) + } + + return &fieldValue, nil +} diff --git a/schemas/index.go b/schemas/index.go new file mode 100644 index 00000000..e5738c93 --- /dev/null +++ b/schemas/index.go @@ -0,0 +1,72 @@ +// Copyright 2019 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package schemas + +import ( + "fmt" + "strings" +) + +// enumerate all index types +const ( + IndexType = iota + 1 + UniqueType +) + +// Index represents a database index +type Index struct { + IsRegular bool + Name string + Type int + Cols []string +} + +func (index *Index) XName(tableName string) string { + if !strings.HasPrefix(index.Name, "UQE_") && + !strings.HasPrefix(index.Name, "IDX_") { + tableParts := strings.Split(strings.Replace(tableName, `"`, "", -1), ".") + tableName = tableParts[len(tableParts)-1] + if index.Type == UniqueType { + return fmt.Sprintf("UQE_%v_%v", tableName, index.Name) + } + return fmt.Sprintf("IDX_%v_%v", tableName, index.Name) + } + return index.Name +} + +// AddColumn add columns which will be composite index +func (index *Index) AddColumn(cols ...string) { + for _, col := range cols { + index.Cols = append(index.Cols, col) + } +} + +func (index *Index) Equal(dst *Index) bool { + if index.Type != dst.Type { + return false + } + if len(index.Cols) != len(dst.Cols) { + return false + } + + for i := 0; i < len(index.Cols); i++ { + var found bool + for j := 0; j < len(dst.Cols); j++ { + if index.Cols[i] == dst.Cols[j] { + found = true + break + } + } + if !found { + return false + } + } + return true +} + +// NewIndex new an index object +func NewIndex(name string, indexType int) *Index { + return &Index{true, name, indexType, make([]string, 0)} +} diff --git a/schemas/pk.go b/schemas/pk.go new file mode 100644 index 00000000..3fd3d28b --- /dev/null +++ b/schemas/pk.go @@ -0,0 +1,30 @@ +// Copyright 2019 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package schemas + +import ( + "bytes" + "encoding/gob" +) + +type PK []interface{} + +func NewPK(pks ...interface{}) *PK { + p := PK(pks) + return &p +} + +func (p *PK) ToString() (string, error) { + buf := new(bytes.Buffer) + enc := gob.NewEncoder(buf) + err := enc.Encode(*p) + return buf.String(), err +} + +func (p *PK) FromString(content string) error { + dec := gob.NewDecoder(bytes.NewBufferString(content)) + err := dec.Decode(p) + return err +} diff --git a/schemas/pk_test.go b/schemas/pk_test.go new file mode 100644 index 00000000..a88b70da --- /dev/null +++ b/schemas/pk_test.go @@ -0,0 +1,36 @@ +// Copyright 2019 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package schemas + +import ( + "reflect" + "testing" +) + +func TestPK(t *testing.T) { + p := NewPK(1, 3, "string") + str, err := p.ToString() + if err != nil { + t.Error(err) + } + t.Log(str) + + s := &PK{} + err = s.FromString(str) + if err != nil { + t.Error(err) + } + t.Log(s) + + if len(*p) != len(*s) { + t.Fatal("p", *p, "should be equal", *s) + } + + for i, ori := range *p { + if ori != (*s)[i] { + t.Fatal("ori", ori, reflect.ValueOf(ori), "should be equal", (*s)[i], reflect.ValueOf((*s)[i])) + } + } +} diff --git a/schemas/table.go b/schemas/table.go new file mode 100644 index 00000000..b32a276a --- /dev/null +++ b/schemas/table.go @@ -0,0 +1,156 @@ +// Copyright 2019 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package schemas + +import ( + "reflect" + "strings" + //"xorm.io/xorm/cache" +) + +// Table represents a database table +type Table struct { + Name string + Type reflect.Type + columnsSeq []string + columnsMap map[string][]*Column + columns []*Column + Indexes map[string]*Index + PrimaryKeys []string + AutoIncrement string + Created map[string]bool + Updated string + Deleted string + Version string + //Cacher caches.Cacher + StoreEngine string + Charset string + Comment string +} + +func (table *Table) Columns() []*Column { + return table.columns +} + +func (table *Table) ColumnsSeq() []string { + return table.columnsSeq +} + +func NewEmptyTable() *Table { + return NewTable("", nil) +} + +// NewTable creates a new Table object +func NewTable(name string, t reflect.Type) *Table { + return &Table{Name: name, Type: t, + columnsSeq: make([]string, 0), + columns: make([]*Column, 0), + columnsMap: make(map[string][]*Column), + Indexes: make(map[string]*Index), + Created: make(map[string]bool), + PrimaryKeys: make([]string, 0), + } +} + +func (table *Table) columnsByName(name string) []*Column { + n := len(name) + + for k := range table.columnsMap { + if len(k) != n { + continue + } + if strings.EqualFold(k, name) { + return table.columnsMap[k] + } + } + return nil +} + +func (table *Table) GetColumn(name string) *Column { + + cols := table.columnsByName(name) + + if cols != nil { + return cols[0] + } + + return nil +} + +func (table *Table) GetColumnIdx(name string, idx int) *Column { + cols := table.columnsByName(name) + + if cols != nil && idx < len(cols) { + return cols[idx] + } + + return nil +} + +// PKColumns reprents all primary key columns +func (table *Table) PKColumns() []*Column { + columns := make([]*Column, len(table.PrimaryKeys)) + for i, name := range table.PrimaryKeys { + columns[i] = table.GetColumn(name) + } + return columns +} + +func (table *Table) ColumnType(name string) reflect.Type { + t, _ := table.Type.FieldByName(name) + return t.Type +} + +func (table *Table) AutoIncrColumn() *Column { + return table.GetColumn(table.AutoIncrement) +} + +func (table *Table) VersionColumn() *Column { + return table.GetColumn(table.Version) +} + +func (table *Table) UpdatedColumn() *Column { + return table.GetColumn(table.Updated) +} + +func (table *Table) DeletedColumn() *Column { + return table.GetColumn(table.Deleted) +} + +// AddColumn adds a column to table +func (table *Table) AddColumn(col *Column) { + table.columnsSeq = append(table.columnsSeq, col.Name) + table.columns = append(table.columns, col) + colName := strings.ToLower(col.Name) + if c, ok := table.columnsMap[colName]; ok { + table.columnsMap[colName] = append(c, col) + } else { + table.columnsMap[colName] = []*Column{col} + } + + if col.IsPrimaryKey { + table.PrimaryKeys = append(table.PrimaryKeys, col.Name) + } + if col.IsAutoIncrement { + table.AutoIncrement = col.Name + } + if col.IsCreated { + table.Created[col.Name] = true + } + if col.IsUpdated { + table.Updated = col.Name + } + if col.IsDeleted { + table.Deleted = col.Name + } + if col.IsVersion { + table.Version = col.Name + } +} + +// AddIndex adds an index or an unique to table +func (table *Table) AddIndex(index *Index) { + table.Indexes[index.Name] = index +} diff --git a/schemas/table_test.go b/schemas/table_test.go new file mode 100644 index 00000000..9bf10e33 --- /dev/null +++ b/schemas/table_test.go @@ -0,0 +1,111 @@ +// Copyright 2019 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package schemas + +import ( + "strings" + "testing" +) + +var testsGetColumn = []struct { + name string + idx int +}{ + {"Id", 0}, + {"Deleted", 0}, + {"Caption", 0}, + {"Code_1", 0}, + {"Code_2", 0}, + {"Code_3", 0}, + {"Parent_Id", 0}, + {"Latitude", 0}, + {"Longitude", 0}, +} + +var table *Table + +func init() { + + table = NewEmptyTable() + + var name string + + for i := 0; i < len(testsGetColumn); i++ { + // as in Table.AddColumn func + name = strings.ToLower(testsGetColumn[i].name) + + table.columnsMap[name] = append(table.columnsMap[name], &Column{}) + } +} + +func TestGetColumn(t *testing.T) { + + for _, test := range testsGetColumn { + if table.GetColumn(test.name) == nil { + t.Error("Column not found!") + } + } +} + +func TestGetColumnIdx(t *testing.T) { + + for _, test := range testsGetColumn { + if table.GetColumnIdx(test.name, test.idx) == nil { + t.Errorf("Column %s with idx %d not found!", test.name, test.idx) + } + } +} + +func BenchmarkGetColumnWithToLower(b *testing.B) { + + for i := 0; i < b.N; i++ { + for _, test := range testsGetColumn { + + if _, ok := table.columnsMap[strings.ToLower(test.name)]; !ok { + b.Errorf("Column not found:%s", test.name) + } + } + } +} + +func BenchmarkGetColumnIdxWithToLower(b *testing.B) { + + for i := 0; i < b.N; i++ { + for _, test := range testsGetColumn { + + if c, ok := table.columnsMap[strings.ToLower(test.name)]; ok { + if test.idx < len(c) { + continue + } else { + b.Errorf("Bad idx in: %s, %d", test.name, test.idx) + } + } else { + b.Errorf("Column not found: %s, %d", test.name, test.idx) + } + } + } +} + +func BenchmarkGetColumn(b *testing.B) { + + for i := 0; i < b.N; i++ { + for _, test := range testsGetColumn { + if table.GetColumn(test.name) == nil { + b.Errorf("Column not found:%s", test.name) + } + } + } +} + +func BenchmarkGetColumnIdx(b *testing.B) { + + for i := 0; i < b.N; i++ { + for _, test := range testsGetColumn { + if table.GetColumnIdx(test.name, test.idx) == nil { + b.Errorf("Column not found:%s, %d", test.name, test.idx) + } + } + } +} diff --git a/schemas/type.go b/schemas/type.go new file mode 100644 index 00000000..2aaa2a44 --- /dev/null +++ b/schemas/type.go @@ -0,0 +1,325 @@ +// Copyright 2019 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package schemas + +import ( + "reflect" + "sort" + "strings" + "time" +) + +const ( + POSTGRES = "postgres" + SQLITE = "sqlite3" + MYSQL = "mysql" + MSSQL = "mssql" + ORACLE = "oracle" +) + +// SQLType represents SQL types +type SQLType struct { + Name string + DefaultLength int + DefaultLength2 int +} + +const ( + UNKNOW_TYPE = iota + TEXT_TYPE + BLOB_TYPE + TIME_TYPE + NUMERIC_TYPE +) + +func (s *SQLType) IsType(st int) bool { + if t, ok := SqlTypes[s.Name]; ok && t == st { + return true + } + return false +} + +func (s *SQLType) IsText() bool { + return s.IsType(TEXT_TYPE) +} + +func (s *SQLType) IsBlob() bool { + return s.IsType(BLOB_TYPE) +} + +func (s *SQLType) IsTime() bool { + return s.IsType(TIME_TYPE) +} + +func (s *SQLType) IsNumeric() bool { + return s.IsType(NUMERIC_TYPE) +} + +func (s *SQLType) IsJson() bool { + return s.Name == Json || s.Name == Jsonb +} + +var ( + Bit = "BIT" + TinyInt = "TINYINT" + SmallInt = "SMALLINT" + MediumInt = "MEDIUMINT" + Int = "INT" + Integer = "INTEGER" + BigInt = "BIGINT" + + Enum = "ENUM" + Set = "SET" + + Char = "CHAR" + Varchar = "VARCHAR" + NChar = "NCHAR" + NVarchar = "NVARCHAR" + TinyText = "TINYTEXT" + Text = "TEXT" + NText = "NTEXT" + Clob = "CLOB" + MediumText = "MEDIUMTEXT" + LongText = "LONGTEXT" + Uuid = "UUID" + UniqueIdentifier = "UNIQUEIDENTIFIER" + SysName = "SYSNAME" + + Date = "DATE" + DateTime = "DATETIME" + SmallDateTime = "SMALLDATETIME" + Time = "TIME" + TimeStamp = "TIMESTAMP" + TimeStampz = "TIMESTAMPZ" + Year = "YEAR" + + Decimal = "DECIMAL" + Numeric = "NUMERIC" + Money = "MONEY" + SmallMoney = "SMALLMONEY" + + Real = "REAL" + Float = "FLOAT" + Double = "DOUBLE" + + Binary = "BINARY" + VarBinary = "VARBINARY" + TinyBlob = "TINYBLOB" + Blob = "BLOB" + MediumBlob = "MEDIUMBLOB" + LongBlob = "LONGBLOB" + Bytea = "BYTEA" + + Bool = "BOOL" + Boolean = "BOOLEAN" + + Serial = "SERIAL" + BigSerial = "BIGSERIAL" + + Json = "JSON" + Jsonb = "JSONB" + + SqlTypes = map[string]int{ + Bit: NUMERIC_TYPE, + TinyInt: NUMERIC_TYPE, + SmallInt: NUMERIC_TYPE, + MediumInt: NUMERIC_TYPE, + Int: NUMERIC_TYPE, + Integer: NUMERIC_TYPE, + BigInt: NUMERIC_TYPE, + + Enum: TEXT_TYPE, + Set: TEXT_TYPE, + Json: TEXT_TYPE, + Jsonb: TEXT_TYPE, + + Char: TEXT_TYPE, + NChar: TEXT_TYPE, + Varchar: TEXT_TYPE, + NVarchar: TEXT_TYPE, + TinyText: TEXT_TYPE, + Text: TEXT_TYPE, + NText: TEXT_TYPE, + MediumText: TEXT_TYPE, + LongText: TEXT_TYPE, + Uuid: TEXT_TYPE, + Clob: TEXT_TYPE, + SysName: TEXT_TYPE, + + Date: TIME_TYPE, + DateTime: TIME_TYPE, + Time: TIME_TYPE, + TimeStamp: TIME_TYPE, + TimeStampz: TIME_TYPE, + SmallDateTime: TIME_TYPE, + Year: TIME_TYPE, + + Decimal: NUMERIC_TYPE, + Numeric: NUMERIC_TYPE, + Real: NUMERIC_TYPE, + Float: NUMERIC_TYPE, + Double: NUMERIC_TYPE, + Money: NUMERIC_TYPE, + SmallMoney: NUMERIC_TYPE, + + Binary: BLOB_TYPE, + VarBinary: BLOB_TYPE, + + TinyBlob: BLOB_TYPE, + Blob: BLOB_TYPE, + MediumBlob: BLOB_TYPE, + LongBlob: BLOB_TYPE, + Bytea: BLOB_TYPE, + UniqueIdentifier: BLOB_TYPE, + + Bool: NUMERIC_TYPE, + + Serial: NUMERIC_TYPE, + BigSerial: NUMERIC_TYPE, + } + + intTypes = sort.StringSlice{"*int", "*int16", "*int32", "*int8"} + uintTypes = sort.StringSlice{"*uint", "*uint16", "*uint32", "*uint8"} +) + +// !nashtsai! treat following var as interal const values, these are used for reflect.TypeOf comparison +var ( + c_EMPTY_STRING string + c_BOOL_DEFAULT bool + c_BYTE_DEFAULT byte + c_COMPLEX64_DEFAULT complex64 + c_COMPLEX128_DEFAULT complex128 + c_FLOAT32_DEFAULT float32 + c_FLOAT64_DEFAULT float64 + c_INT64_DEFAULT int64 + c_UINT64_DEFAULT uint64 + c_INT32_DEFAULT int32 + c_UINT32_DEFAULT uint32 + c_INT16_DEFAULT int16 + c_UINT16_DEFAULT uint16 + c_INT8_DEFAULT int8 + c_UINT8_DEFAULT uint8 + c_INT_DEFAULT int + c_UINT_DEFAULT uint + c_TIME_DEFAULT time.Time +) + +var ( + IntType = reflect.TypeOf(c_INT_DEFAULT) + Int8Type = reflect.TypeOf(c_INT8_DEFAULT) + Int16Type = reflect.TypeOf(c_INT16_DEFAULT) + Int32Type = reflect.TypeOf(c_INT32_DEFAULT) + Int64Type = reflect.TypeOf(c_INT64_DEFAULT) + + UintType = reflect.TypeOf(c_UINT_DEFAULT) + Uint8Type = reflect.TypeOf(c_UINT8_DEFAULT) + Uint16Type = reflect.TypeOf(c_UINT16_DEFAULT) + Uint32Type = reflect.TypeOf(c_UINT32_DEFAULT) + Uint64Type = reflect.TypeOf(c_UINT64_DEFAULT) + + Float32Type = reflect.TypeOf(c_FLOAT32_DEFAULT) + Float64Type = reflect.TypeOf(c_FLOAT64_DEFAULT) + + Complex64Type = reflect.TypeOf(c_COMPLEX64_DEFAULT) + Complex128Type = reflect.TypeOf(c_COMPLEX128_DEFAULT) + + StringType = reflect.TypeOf(c_EMPTY_STRING) + BoolType = reflect.TypeOf(c_BOOL_DEFAULT) + ByteType = reflect.TypeOf(c_BYTE_DEFAULT) + BytesType = reflect.SliceOf(ByteType) + + TimeType = reflect.TypeOf(c_TIME_DEFAULT) +) + +var ( + PtrIntType = reflect.PtrTo(IntType) + PtrInt8Type = reflect.PtrTo(Int8Type) + PtrInt16Type = reflect.PtrTo(Int16Type) + PtrInt32Type = reflect.PtrTo(Int32Type) + PtrInt64Type = reflect.PtrTo(Int64Type) + + PtrUintType = reflect.PtrTo(UintType) + PtrUint8Type = reflect.PtrTo(Uint8Type) + PtrUint16Type = reflect.PtrTo(Uint16Type) + PtrUint32Type = reflect.PtrTo(Uint32Type) + PtrUint64Type = reflect.PtrTo(Uint64Type) + + PtrFloat32Type = reflect.PtrTo(Float32Type) + PtrFloat64Type = reflect.PtrTo(Float64Type) + + PtrComplex64Type = reflect.PtrTo(Complex64Type) + PtrComplex128Type = reflect.PtrTo(Complex128Type) + + PtrStringType = reflect.PtrTo(StringType) + PtrBoolType = reflect.PtrTo(BoolType) + PtrByteType = reflect.PtrTo(ByteType) + + PtrTimeType = reflect.PtrTo(TimeType) +) + +// Type2SQLType generate SQLType acorrding Go's type +func Type2SQLType(t reflect.Type) (st SQLType) { + switch k := t.Kind(); k { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32: + st = SQLType{Int, 0, 0} + case reflect.Int64, reflect.Uint64: + st = SQLType{BigInt, 0, 0} + case reflect.Float32: + st = SQLType{Float, 0, 0} + case reflect.Float64: + st = SQLType{Double, 0, 0} + case reflect.Complex64, reflect.Complex128: + st = SQLType{Varchar, 64, 0} + case reflect.Array, reflect.Slice, reflect.Map: + if t.Elem() == reflect.TypeOf(c_BYTE_DEFAULT) { + st = SQLType{Blob, 0, 0} + } else { + st = SQLType{Text, 0, 0} + } + case reflect.Bool: + st = SQLType{Bool, 0, 0} + case reflect.String: + st = SQLType{Varchar, 255, 0} + case reflect.Struct: + if t.ConvertibleTo(TimeType) { + st = SQLType{DateTime, 0, 0} + } else { + // TODO need to handle association struct + st = SQLType{Text, 0, 0} + } + case reflect.Ptr: + st = Type2SQLType(t.Elem()) + default: + st = SQLType{Text, 0, 0} + } + return +} + +// default sql type change to go types +func SQLType2Type(st SQLType) reflect.Type { + name := strings.ToUpper(st.Name) + switch name { + case Bit, TinyInt, SmallInt, MediumInt, Int, Integer, Serial: + return reflect.TypeOf(1) + case BigInt, BigSerial: + return reflect.TypeOf(int64(1)) + case Float, Real: + return reflect.TypeOf(float32(1)) + case Double: + return reflect.TypeOf(float64(1)) + case Char, NChar, Varchar, NVarchar, TinyText, Text, NText, MediumText, LongText, Enum, Set, Uuid, Clob, SysName: + return reflect.TypeOf("") + case TinyBlob, Blob, LongBlob, Bytea, Binary, MediumBlob, VarBinary, UniqueIdentifier: + return reflect.TypeOf([]byte{}) + case Bool: + return reflect.TypeOf(true) + case DateTime, Date, Time, TimeStamp, TimeStampz, SmallDateTime, Year: + return reflect.TypeOf(c_TIME_DEFAULT) + case Decimal, Numeric, Money, SmallMoney: + return reflect.TypeOf("") + default: + return reflect.TypeOf("") + } +} diff --git a/session.go b/session.go index 83071935..57e4055b 100644 --- a/session.go +++ b/session.go @@ -14,7 +14,8 @@ import ( "strings" "time" - "xorm.io/core" + "xorm.io/xorm/core" + "xorm.io/xorm/schemas" ) type sessionType int @@ -306,8 +307,8 @@ func (session *Session) doPrepare(db *core.DB, sqlStr string) (stmt *core.Stmt, return } -func (session *Session) getField(dataStruct *reflect.Value, key string, table *core.Table, idx int) (*reflect.Value, error) { - var col *core.Column +func (session *Session) getField(dataStruct *reflect.Value, key string, table *schemas.Table, idx int) (*reflect.Value, error) { + var col *schemas.Column if col = table.GetColumnIdx(key, idx); col == nil { return nil, ErrFieldIsNotExist{key, table.Name} } @@ -328,8 +329,8 @@ func (session *Session) getField(dataStruct *reflect.Value, key string, table *c type Cell *interface{} func (session *Session) rows2Beans(rows *core.Rows, fields []string, - table *core.Table, newElemFunc func([]string) reflect.Value, - sliceValueSetFunc func(*reflect.Value, core.PK) error) error { + table *schemas.Table, newElemFunc func([]string) reflect.Value, + sliceValueSetFunc func(*reflect.Value, schemas.PK) error) error { for rows.Next() { var newValue = newElemFunc(fields) bean := newValue.Interface() @@ -377,7 +378,7 @@ func (session *Session) row2Slice(rows *core.Rows, fields []string, bean interfa return scanResults, nil } -func (session *Session) slice2Bean(scanResults []interface{}, fields []string, bean interface{}, dataStruct *reflect.Value, table *core.Table) (core.PK, error) { +func (session *Session) slice2Bean(scanResults []interface{}, fields []string, bean interface{}, dataStruct *reflect.Value, table *schemas.Table) (schemas.PK, error) { defer func() { if b, hasAfterSet := bean.(AfterSetProcessor); hasAfterSet { for ii, key := range fields { @@ -421,7 +422,7 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b } var tempMap = make(map[string]int) - var pk core.PK + var pk schemas.PK for ii, key := range fields { var idx int var ok bool @@ -451,7 +452,7 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b } if fieldValue.CanAddr() { - if structConvert, ok := fieldValue.Addr().Interface().(core.Conversion); ok { + if structConvert, ok := fieldValue.Addr().Interface().(Conversion); ok { if data, err := value2Bytes(&rawValue); err == nil { if err := structConvert.FromDB(data); err != nil { return nil, err @@ -463,12 +464,12 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b } } - if _, ok := fieldValue.Interface().(core.Conversion); ok { + if _, ok := fieldValue.Interface().(Conversion); ok { if data, err := value2Bytes(&rawValue); err == nil { if fieldValue.Kind() == reflect.Ptr && fieldValue.IsNil() { fieldValue.Set(reflect.New(fieldValue.Type().Elem())) } - fieldValue.Interface().(core.Conversion).FromDB(data) + fieldValue.Interface().(Conversion).FromDB(data) } else { return nil, err } @@ -488,7 +489,7 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b var bs []byte if rawValueType.Kind() == reflect.String { bs = []byte(vv.String()) - } else if rawValueType.ConvertibleTo(core.BytesType) { + } else if rawValueType.ConvertibleTo(schemas.BytesType) { bs = vv.Bytes() } else { return nil, fmt.Errorf("unsupported database data type: %s %v", key, rawValueType.Kind()) @@ -525,7 +526,7 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b var bs []byte if rawValueType.Kind() == reflect.String { bs = []byte(vv.String()) - } else if rawValueType.ConvertibleTo(core.BytesType) { + } else if rawValueType.ConvertibleTo(schemas.BytesType) { bs = vv.Bytes() } @@ -607,16 +608,16 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b fieldValue.SetUint(uint64(vv.Int())) } case reflect.Struct: - if fieldType.ConvertibleTo(core.TimeType) { + if fieldType.ConvertibleTo(schemas.TimeType) { dbTZ := session.engine.DatabaseTZ if col.TimeZone != nil { dbTZ = col.TimeZone } - if rawValueType == core.TimeType { + if rawValueType == schemas.TimeType { hasAssigned = true - t := vv.Convert(core.TimeType).Interface().(time.Time) + t := vv.Convert(schemas.TimeType).Interface().(time.Time) z, _ := t.Zone() // set new location if database don't save timezone or give an incorrect timezone @@ -628,8 +629,8 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b t = t.In(session.engine.TZLocation) fieldValue.Set(reflect.ValueOf(t).Convert(fieldType)) - } else if rawValueType == core.IntType || rawValueType == core.Int64Type || - rawValueType == core.Int32Type { + } else if rawValueType == schemas.IntType || rawValueType == schemas.Int64Type || + rawValueType == schemas.Int32Type { hasAssigned = true t := time.Unix(vv.Int(), 0).In(session.engine.TZLocation) @@ -696,7 +697,7 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b if len(table.PrimaryKeys) != 1 { return nil, errors.New("unsupported non or composited primary key cascade") } - var pk = make(core.PK, len(table.PrimaryKeys)) + var pk = make(schemas.PK, len(table.PrimaryKeys)) pk[0], err = asKind(vv, rawValueType) if err != nil { return nil, err @@ -722,97 +723,97 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b // !nashtsai! TODO merge duplicated codes above switch fieldType { // following types case matching ptr's native type, therefore assign ptr directly - case core.PtrStringType: + case schemas.PtrStringType: if rawValueType.Kind() == reflect.String { x := vv.String() hasAssigned = true fieldValue.Set(reflect.ValueOf(&x)) } - case core.PtrBoolType: + case schemas.PtrBoolType: if rawValueType.Kind() == reflect.Bool { x := vv.Bool() hasAssigned = true fieldValue.Set(reflect.ValueOf(&x)) } - case core.PtrTimeType: - if rawValueType == core.PtrTimeType { + case schemas.PtrTimeType: + if rawValueType == schemas.PtrTimeType { hasAssigned = true var x = rawValue.Interface().(time.Time) fieldValue.Set(reflect.ValueOf(&x)) } - case core.PtrFloat64Type: + case schemas.PtrFloat64Type: if rawValueType.Kind() == reflect.Float64 { x := vv.Float() hasAssigned = true fieldValue.Set(reflect.ValueOf(&x)) } - case core.PtrUint64Type: + case schemas.PtrUint64Type: if rawValueType.Kind() == reflect.Int64 { var x = uint64(vv.Int()) hasAssigned = true fieldValue.Set(reflect.ValueOf(&x)) } - case core.PtrInt64Type: + case schemas.PtrInt64Type: if rawValueType.Kind() == reflect.Int64 { x := vv.Int() hasAssigned = true fieldValue.Set(reflect.ValueOf(&x)) } - case core.PtrFloat32Type: + case schemas.PtrFloat32Type: if rawValueType.Kind() == reflect.Float64 { var x = float32(vv.Float()) hasAssigned = true fieldValue.Set(reflect.ValueOf(&x)) } - case core.PtrIntType: + case schemas.PtrIntType: if rawValueType.Kind() == reflect.Int64 { var x = int(vv.Int()) hasAssigned = true fieldValue.Set(reflect.ValueOf(&x)) } - case core.PtrInt32Type: + case schemas.PtrInt32Type: if rawValueType.Kind() == reflect.Int64 { var x = int32(vv.Int()) hasAssigned = true fieldValue.Set(reflect.ValueOf(&x)) } - case core.PtrInt8Type: + case schemas.PtrInt8Type: if rawValueType.Kind() == reflect.Int64 { var x = int8(vv.Int()) hasAssigned = true fieldValue.Set(reflect.ValueOf(&x)) } - case core.PtrInt16Type: + case schemas.PtrInt16Type: if rawValueType.Kind() == reflect.Int64 { var x = int16(vv.Int()) hasAssigned = true fieldValue.Set(reflect.ValueOf(&x)) } - case core.PtrUintType: + case schemas.PtrUintType: if rawValueType.Kind() == reflect.Int64 { var x = uint(vv.Int()) hasAssigned = true fieldValue.Set(reflect.ValueOf(&x)) } - case core.PtrUint32Type: + case schemas.PtrUint32Type: if rawValueType.Kind() == reflect.Int64 { var x = uint32(vv.Int()) hasAssigned = true fieldValue.Set(reflect.ValueOf(&x)) } - case core.Uint8Type: + case schemas.Uint8Type: if rawValueType.Kind() == reflect.Int64 { var x = uint8(vv.Int()) hasAssigned = true fieldValue.Set(reflect.ValueOf(&x)) } - case core.Uint16Type: + case schemas.Uint16Type: if rawValueType.Kind() == reflect.Int64 { var x = uint16(vv.Int()) hasAssigned = true fieldValue.Set(reflect.ValueOf(&x)) } - case core.Complex64Type: + case schemas.Complex64Type: var x complex64 if len([]byte(vv.String())) > 0 { err := DefaultJSONHandler.Unmarshal([]byte(vv.String()), &x) @@ -822,7 +823,7 @@ func (session *Session) slice2Bean(scanResults []interface{}, fields []string, b fieldValue.Set(reflect.ValueOf(&x)) } hasAssigned = true - case core.Complex128Type: + case schemas.Complex128Type: var x complex128 if len([]byte(vv.String())) > 0 { err := DefaultJSONHandler.Unmarshal([]byte(vv.String()), &x) diff --git a/session_cols.go b/session_cols.go index 1558074f..4f7dc6cf 100644 --- a/session_cols.go +++ b/session_cols.go @@ -9,10 +9,10 @@ import ( "strings" "time" - "xorm.io/core" + "xorm.io/xorm/schemas" ) -func setColumnInt(bean interface{}, col *core.Column, t int64) { +func setColumnInt(bean interface{}, col *schemas.Column, t int64) { v, err := col.ValueOf(bean) if err != nil { return @@ -27,7 +27,7 @@ func setColumnInt(bean interface{}, col *core.Column, t int64) { } } -func setColumnTime(bean interface{}, col *core.Column, t time.Time) { +func setColumnTime(bean interface{}, col *schemas.Column, t time.Time) { v, err := col.ValueOf(bean) if err != nil { return @@ -44,7 +44,7 @@ func setColumnTime(bean interface{}, col *core.Column, t time.Time) { } } -func getFlagForColumn(m map[string]bool, col *core.Column) (val bool, has bool) { +func getFlagForColumn(m map[string]bool, col *schemas.Column) (val bool, has bool) { if len(m) == 0 { return false, false } diff --git a/session_cols_test.go b/session_cols_test.go index 96cb1620..58b4e841 100644 --- a/session_cols_test.go +++ b/session_cols_test.go @@ -9,7 +9,7 @@ import ( "github.com/stretchr/testify/assert" "xorm.io/builder" - "xorm.io/core" + "xorm.io/xorm/schemas" ) func TestSetExpr(t *testing.T) { @@ -45,7 +45,7 @@ func TestSetExpr(t *testing.T) { assert.EqualValues(t, 1, cnt) var not = "NOT" - if testEngine.Dialect().DBType() == core.MSSQL { + if testEngine.Dialect().DBType() == schemas.MSSQL { not = "~" } cnt, err = testEngine.SetExpr("show", not+" `show`").ID(1).Update(new(UserExpr)) diff --git a/session_convert.go b/session_convert.go index 7f11354d..24c51011 100644 --- a/session_convert.go +++ b/session_convert.go @@ -14,10 +14,10 @@ import ( "strings" "time" - "xorm.io/core" + "xorm.io/xorm/schemas" ) -func (session *Session) str2Time(col *core.Column, data string) (outTime time.Time, outErr error) { +func (session *Session) str2Time(col *schemas.Column, data string) (outTime time.Time, outErr error) { sdata := strings.TrimSpace(data) var x time.Time var err error @@ -54,14 +54,14 @@ func (session *Session) str2Time(col *core.Column, data string) (outTime time.Ti } else if len(sdata) == 10 && sdata[4] == '-' && sdata[7] == '-' { x, err = time.ParseInLocation("2006-01-02", sdata, parseLoc) //session.engine.logger.Debugf("time(5) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata) - } else if col.SQLType.Name == core.Time { + } else if col.SQLType.Name == schemas.Time { if strings.Contains(sdata, " ") { ssd := strings.Split(sdata, " ") sdata = ssd[1] } sdata = strings.TrimSpace(sdata) - if session.engine.dialect.DBType() == core.MYSQL && len(sdata) > 8 { + if session.engine.dialect.DBType() == schemas.MYSQL && len(sdata) > 8 { sdata = sdata[len(sdata)-8:] } @@ -80,7 +80,7 @@ func (session *Session) str2Time(col *core.Column, data string) (outTime time.Ti return } -func (session *Session) byte2Time(col *core.Column, data []byte) (outTime time.Time, outErr error) { +func (session *Session) byte2Time(col *schemas.Column, data []byte) (outTime time.Time, outErr error) { return session.str2Time(col, string(data)) } @@ -89,12 +89,12 @@ var ( ) // convert a db data([]byte) to a field value -func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value, data []byte) error { - if structConvert, ok := fieldValue.Addr().Interface().(core.Conversion); ok { +func (session *Session) bytes2Value(col *schemas.Column, fieldValue *reflect.Value, data []byte) error { + if structConvert, ok := fieldValue.Addr().Interface().(Conversion); ok { return structConvert.FromDB(data) } - if structConvert, ok := fieldValue.Interface().(core.Conversion); ok { + if structConvert, ok := fieldValue.Interface().(Conversion); ok { return structConvert.FromDB(data) } @@ -157,8 +157,8 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value, var x int64 var err error // for mysql, when use bit, it returned \x01 - if col.SQLType.Name == core.Bit && - session.engine.dialect.DBType() == core.MYSQL { // !nashtsai! TODO dialect needs to provide conversion interface API + if col.SQLType.Name == schemas.Bit && + session.engine.dialect.DBType() == schemas.MYSQL { // !nashtsai! TODO dialect needs to provide conversion interface API if len(data) == 1 { x = int64(data[0]) } else { @@ -199,7 +199,7 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value, return fmt.Errorf("sql.Scan(%v) failed: %s ", data, err.Error()) } } else { - if fieldType.ConvertibleTo(core.TimeType) { + if fieldType.ConvertibleTo(schemas.TimeType) { x, err := session.byte2Time(col, data) if err != nil { return err @@ -217,7 +217,7 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value, return errors.New("unsupported composited primary key cascade") } - var pk = make(core.PK, len(table.PrimaryKeys)) + var pk = make(schemas.PK, len(table.PrimaryKeys)) rawValueType := table.ColumnType(table.PKColumns()[0].FieldName) pk[0], err = str2PK(string(data), rawValueType) if err != nil { @@ -247,11 +247,11 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value, //typeStr := fieldType.String() switch fieldType.Elem().Kind() { // case "*string": - case core.StringType.Kind(): + case schemas.StringType.Kind(): x := string(data) fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) // case "*bool": - case core.BoolType.Kind(): + case schemas.BoolType.Kind(): d := string(data) v, err := strconv.ParseBool(d) if err != nil { @@ -259,7 +259,7 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value, } fieldValue.Set(reflect.ValueOf(&v).Convert(fieldType)) // case "*complex64": - case core.Complex64Type.Kind(): + case schemas.Complex64Type.Kind(): var x complex64 if len(data) > 0 { err := DefaultJSONHandler.Unmarshal(data, &x) @@ -270,7 +270,7 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value, fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) } // case "*complex128": - case core.Complex128Type.Kind(): + case schemas.Complex128Type.Kind(): var x complex128 if len(data) > 0 { err := DefaultJSONHandler.Unmarshal(data, &x) @@ -281,14 +281,14 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value, fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) } // case "*float64": - case core.Float64Type.Kind(): + case schemas.Float64Type.Kind(): x, err := strconv.ParseFloat(string(data), 64) if err != nil { return fmt.Errorf("arg %v as float64: %s", key, err.Error()) } fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) // case "*float32": - case core.Float32Type.Kind(): + case schemas.Float32Type.Kind(): var x float32 x1, err := strconv.ParseFloat(string(data), 32) if err != nil { @@ -297,7 +297,7 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value, x = float32(x1) fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) // case "*uint64": - case core.Uint64Type.Kind(): + case schemas.Uint64Type.Kind(): var x uint64 x, err := strconv.ParseUint(string(data), 10, 64) if err != nil { @@ -305,7 +305,7 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value, } fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) // case "*uint": - case core.UintType.Kind(): + case schemas.UintType.Kind(): var x uint x1, err := strconv.ParseUint(string(data), 10, 64) if err != nil { @@ -314,7 +314,7 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value, x = uint(x1) fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) // case "*uint32": - case core.Uint32Type.Kind(): + case schemas.Uint32Type.Kind(): var x uint32 x1, err := strconv.ParseUint(string(data), 10, 64) if err != nil { @@ -323,7 +323,7 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value, x = uint32(x1) fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) // case "*uint8": - case core.Uint8Type.Kind(): + case schemas.Uint8Type.Kind(): var x uint8 x1, err := strconv.ParseUint(string(data), 10, 64) if err != nil { @@ -332,7 +332,7 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value, x = uint8(x1) fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) // case "*uint16": - case core.Uint16Type.Kind(): + case schemas.Uint16Type.Kind(): var x uint16 x1, err := strconv.ParseUint(string(data), 10, 64) if err != nil { @@ -341,12 +341,12 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value, x = uint16(x1) fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) // case "*int64": - case core.Int64Type.Kind(): + case schemas.Int64Type.Kind(): sdata := string(data) var x int64 var err error // for mysql, when use bit, it returned \x01 - if col.SQLType.Name == core.Bit && + if col.SQLType.Name == schemas.Bit && strings.Contains(session.engine.DriverName(), "mysql") { if len(data) == 1 { x = int64(data[0]) @@ -365,13 +365,13 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value, } fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) // case "*int": - case core.IntType.Kind(): + case schemas.IntType.Kind(): sdata := string(data) var x int var x1 int64 var err error // for mysql, when use bit, it returned \x01 - if col.SQLType.Name == core.Bit && + if col.SQLType.Name == schemas.Bit && strings.Contains(session.engine.DriverName(), "mysql") { if len(data) == 1 { x = int(data[0]) @@ -393,14 +393,14 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value, } fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) // case "*int32": - case core.Int32Type.Kind(): + case schemas.Int32Type.Kind(): sdata := string(data) var x int32 var x1 int64 var err error // for mysql, when use bit, it returned \x01 - if col.SQLType.Name == core.Bit && - session.engine.dialect.DBType() == core.MYSQL { + if col.SQLType.Name == schemas.Bit && + session.engine.dialect.DBType() == schemas.MYSQL { if len(data) == 1 { x = int32(data[0]) } else { @@ -421,13 +421,13 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value, } fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) // case "*int8": - case core.Int8Type.Kind(): + case schemas.Int8Type.Kind(): sdata := string(data) var x int8 var x1 int64 var err error // for mysql, when use bit, it returned \x01 - if col.SQLType.Name == core.Bit && + if col.SQLType.Name == schemas.Bit && strings.Contains(session.engine.DriverName(), "mysql") { if len(data) == 1 { x = int8(data[0]) @@ -449,13 +449,13 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value, } fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) // case "*int16": - case core.Int16Type.Kind(): + case schemas.Int16Type.Kind(): sdata := string(data) var x int16 var x1 int64 var err error // for mysql, when use bit, it returned \x01 - if col.SQLType.Name == core.Bit && + if col.SQLType.Name == schemas.Bit && strings.Contains(session.engine.DriverName(), "mysql") { if len(data) == 1 { x = int16(data[0]) @@ -480,7 +480,7 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value, case reflect.Struct: switch fieldType { // case "*.time.Time": - case core.PtrTimeType: + case schemas.PtrTimeType: x, err := session.byte2Time(col, data) if err != nil { return err @@ -499,7 +499,7 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value, return errors.New("unsupported composited primary key cascade") } - var pk = make(core.PK, len(table.PrimaryKeys)) + var pk = make(schemas.PK, len(table.PrimaryKeys)) rawValueType := table.ColumnType(table.PKColumns()[0].FieldName) pk[0], err = str2PK(string(data), rawValueType) if err != nil { @@ -536,9 +536,9 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value, } // convert a field value of a struct to interface for put into db -func (session *Session) value2Interface(col *core.Column, fieldValue reflect.Value) (interface{}, error) { +func (session *Session) value2Interface(col *schemas.Column, fieldValue reflect.Value) (interface{}, error) { if fieldValue.CanAddr() { - if fieldConvert, ok := fieldValue.Addr().Interface().(core.Conversion); ok { + if fieldConvert, ok := fieldValue.Addr().Interface().(Conversion); ok { data, err := fieldConvert.ToDB() if err != nil { return 0, err @@ -550,7 +550,7 @@ func (session *Session) value2Interface(col *core.Column, fieldValue reflect.Val } } - if fieldConvert, ok := fieldValue.Interface().(core.Conversion); ok { + if fieldConvert, ok := fieldValue.Interface().(Conversion); ok { data, err := fieldConvert.ToDB() if err != nil { return 0, err @@ -583,8 +583,8 @@ func (session *Session) value2Interface(col *core.Column, fieldValue reflect.Val case reflect.String: return fieldValue.String(), nil case reflect.Struct: - if fieldType.ConvertibleTo(core.TimeType) { - t := fieldValue.Convert(core.TimeType).Interface().(time.Time) + if fieldType.ConvertibleTo(schemas.TimeType) { + t := fieldValue.Convert(schemas.TimeType).Interface().(time.Time) tf := session.engine.formatColTime(col, t) return tf, nil } else if fieldType.ConvertibleTo(nullFloatType) { diff --git a/session_delete.go b/session_delete.go index 7b0a0641..2afe068d 100644 --- a/session_delete.go +++ b/session_delete.go @@ -9,10 +9,11 @@ import ( "fmt" "strconv" - "xorm.io/core" + "xorm.io/xorm/caches" + "xorm.io/xorm/schemas" ) -func (session *Session) cacheDelete(table *core.Table, tableName, sqlStr string, args ...interface{}) error { +func (session *Session) cacheDelete(table *schemas.Table, tableName, sqlStr string, args ...interface{}) error { if table == nil || session.tx != nil { return ErrCacheFailed @@ -29,17 +30,17 @@ func (session *Session) cacheDelete(table *core.Table, tableName, sqlStr string, cacher := session.engine.getCacher(tableName) pkColumns := table.PKColumns() - ids, err := core.GetCacheSql(cacher, tableName, newsql, args) + ids, err := caches.GetCacheSql(cacher, tableName, newsql, args) if err != nil { resultsSlice, err := session.queryBytes(newsql, args...) if err != nil { return err } - ids = make([]core.PK, 0) + ids = make([]schemas.PK, 0) if len(resultsSlice) > 0 { for _, data := range resultsSlice { var id int64 - var pk core.PK = make([]interface{}, 0) + var pk schemas.PK = make([]interface{}, 0) for _, col := range pkColumns { if v, ok := data[col.Name]; !ok { return errors.New("no id") @@ -127,14 +128,14 @@ func (session *Session) Delete(bean interface{}) (int64, error) { if len(orderSQL) > 0 { switch session.engine.dialect.DBType() { - case core.POSTGRES: + case schemas.POSTGRES: inSQL := fmt.Sprintf("ctid IN (SELECT ctid FROM %s%s)", tableName, orderSQL) if len(condSQL) > 0 { deleteSQL += " AND " + inSQL } else { deleteSQL += " WHERE " + inSQL } - case core.SQLITE: + case schemas.SQLITE: inSQL := fmt.Sprintf("rowid IN (SELECT rowid FROM %s%s)", tableName, orderSQL) if len(condSQL) > 0 { deleteSQL += " AND " + inSQL @@ -142,7 +143,7 @@ func (session *Session) Delete(bean interface{}) (int64, error) { deleteSQL += " WHERE " + inSQL } // TODO: how to handle delete limit on mssql? - case core.MSSQL: + case schemas.MSSQL: return 0, ErrNotImplemented default: deleteSQL += orderSQL @@ -156,7 +157,7 @@ func (session *Session) Delete(bean interface{}) (int64, error) { copy(argsForCache, condArgs) argsForCache = append(condArgs, argsForCache...) } else { - // !oinume! sqlStrForCache and argsForCache is needed to behave as executing "DELETE FROM ..." for cache. + // !oinume! sqlStrForCache and argsForCache is needed to behave as executing "DELETE FROM ..." for caches. copy(argsForCache, condArgs) argsForCache = append(condArgs, argsForCache...) @@ -168,14 +169,14 @@ func (session *Session) Delete(bean interface{}) (int64, error) { if len(orderSQL) > 0 { switch session.engine.dialect.DBType() { - case core.POSTGRES: + case schemas.POSTGRES: inSQL := fmt.Sprintf("ctid IN (SELECT ctid FROM %s%s)", tableName, orderSQL) if len(condSQL) > 0 { realSQL += " AND " + inSQL } else { realSQL += " WHERE " + inSQL } - case core.SQLITE: + case schemas.SQLITE: inSQL := fmt.Sprintf("rowid IN (SELECT rowid FROM %s%s)", tableName, orderSQL) if len(condSQL) > 0 { realSQL += " AND " + inSQL @@ -183,7 +184,7 @@ func (session *Session) Delete(bean interface{}) (int64, error) { realSQL += " WHERE " + inSQL } // TODO: how to handle delete limit on mssql? - case core.MSSQL: + case schemas.MSSQL: return 0, ErrNotImplemented default: realSQL += orderSQL diff --git a/session_delete_test.go b/session_delete_test.go index ca0402fd..3d0fa1a8 100644 --- a/session_delete_test.go +++ b/session_delete_test.go @@ -9,7 +9,8 @@ import ( "time" "github.com/stretchr/testify/assert" - "xorm.io/core" + "xorm.io/xorm/caches" + "xorm.io/xorm/schemas" ) func TestDelete(t *testing.T) { @@ -26,7 +27,7 @@ func TestDelete(t *testing.T) { defer session.Close() var err error - if testEngine.Dialect().DBType() == core.MSSQL { + if testEngine.Dialect().DBType() == schemas.MSSQL { err = session.Begin() assert.NoError(t, err) _, err = session.Exec("SET IDENTITY_INSERT userinfo_delete ON") @@ -38,7 +39,7 @@ func TestDelete(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 1, cnt) - if testEngine.Dialect().DBType() == core.MSSQL { + if testEngine.Dialect().DBType() == schemas.MSSQL { err = session.Commit() assert.NoError(t, err) } @@ -159,7 +160,7 @@ func TestCacheDelete(t *testing.T) { assert.NoError(t, prepareEngine()) oldCacher := testEngine.GetDefaultCacher() - cacher := NewLRUCacher(NewMemoryStore(), 1000) + cacher := caches.NewLRUCacher(caches.NewMemoryStore(), 1000) testEngine.SetDefaultCacher(cacher) type CacheDeleteStruct struct { diff --git a/session_exist.go b/session_exist.go index bce2758d..153bb219 100644 --- a/session_exist.go +++ b/session_exist.go @@ -10,7 +10,7 @@ import ( "reflect" "xorm.io/builder" - "xorm.io/core" + "xorm.io/xorm/schemas" ) // Exist returns true if the record exist otherwise return false @@ -45,18 +45,18 @@ func (session *Session) Exist(bean ...interface{}) (bool, error) { return false, err } - if session.engine.dialect.DBType() == core.MSSQL { + if session.engine.dialect.DBType() == schemas.MSSQL { sqlStr = fmt.Sprintf("SELECT TOP 1 * FROM %s %s WHERE %s", tableName, joinStr, condSQL) - } else if session.engine.dialect.DBType() == core.ORACLE { + } else if session.engine.dialect.DBType() == schemas.ORACLE { sqlStr = fmt.Sprintf("SELECT * FROM %s WHERE (%s) %s AND ROWNUM=1", tableName, joinStr, condSQL) } else { sqlStr = fmt.Sprintf("SELECT * FROM %s %s WHERE %s LIMIT 1", tableName, joinStr, condSQL) } args = condArgs } else { - if session.engine.dialect.DBType() == core.MSSQL { + if session.engine.dialect.DBType() == schemas.MSSQL { sqlStr = fmt.Sprintf("SELECT TOP 1 * FROM %s %s", tableName, joinStr) - } else if session.engine.dialect.DBType() == core.ORACLE { + } else if session.engine.dialect.DBType() == schemas.ORACLE { sqlStr = fmt.Sprintf("SELECT * FROM %s %s WHERE ROWNUM=1", tableName, joinStr) } else { sqlStr = fmt.Sprintf("SELECT * FROM %s %s LIMIT 1", tableName, joinStr) diff --git a/session_find.go b/session_find.go index c7043ea6..7ae54a5f 100644 --- a/session_find.go +++ b/session_find.go @@ -11,7 +11,8 @@ import ( "strings" "xorm.io/builder" - "xorm.io/core" + "xorm.io/xorm/caches" + "xorm.io/xorm/schemas" ) const ( @@ -197,7 +198,7 @@ func (session *Session) find(rowsSlicePtr interface{}, condiBean ...interface{}) return session.noCacheFind(table, sliceValue, sqlStr, args...) } -func (session *Session) noCacheFind(table *core.Table, containerValue reflect.Value, sqlStr string, args ...interface{}) error { +func (session *Session) noCacheFind(table *schemas.Table, containerValue reflect.Value, sqlStr string, args ...interface{}) error { rows, err := session.queryRows(sqlStr, args...) if err != nil { return err @@ -236,10 +237,10 @@ func (session *Session) noCacheFind(table *core.Table, containerValue reflect.Va return reflect.New(elemType) } - var containerValueSetFunc func(*reflect.Value, core.PK) error + var containerValueSetFunc func(*reflect.Value, schemas.PK) error if containerValue.Kind() == reflect.Slice { - containerValueSetFunc = func(newValue *reflect.Value, pk core.PK) error { + containerValueSetFunc = func(newValue *reflect.Value, pk schemas.PK) error { if isPointer { containerValue.Set(reflect.Append(containerValue, newValue.Elem().Addr())) } else { @@ -256,7 +257,7 @@ func (session *Session) noCacheFind(table *core.Table, containerValue reflect.Va return errors.New("don't support multiple primary key's map has non-slice key type") } - containerValueSetFunc = func(newValue *reflect.Value, pk core.PK) error { + containerValueSetFunc = func(newValue *reflect.Value, pk schemas.PK) error { keyValue := reflect.New(keyType) err := convertPKToValue(table, keyValue.Interface(), pk) if err != nil { @@ -310,7 +311,7 @@ func (session *Session) noCacheFind(table *core.Table, containerValue reflect.Va return nil } -func convertPKToValue(table *core.Table, dst interface{}, pk core.PK) error { +func convertPKToValue(table *schemas.Table, dst interface{}, pk schemas.PK) error { cols := table.PKColumns() if len(cols) == 1 { return convertAssign(dst, pk[0]) @@ -343,7 +344,7 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in } table := session.statement.RefTable - ids, err := core.GetCacheSql(cacher, tableName, newsql, args) + ids, err := caches.GetCacheSql(cacher, tableName, newsql, args) if err != nil { rows, err := session.queryRows(newsql, args...) if err != nil { @@ -352,7 +353,7 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in defer rows.Close() var i int - ids = make([]core.PK, 0) + ids = make([]schemas.PK, 0) for rows.Next() { i++ if i > 500 { @@ -364,7 +365,7 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in if err != nil { return err } - var pk core.PK = make([]interface{}, len(table.PrimaryKeys)) + var pk schemas.PK = make([]interface{}, len(table.PrimaryKeys)) for i, col := range table.PKColumns() { pk[i], err = session.engine.idTypeAssertion(col, res[i]) if err != nil { @@ -376,7 +377,7 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in } session.engine.logger.Debug("[cacheFind] cache sql:", ids, tableName, sqlStr, newsql, args) - err = core.PutCacheSql(cacher, ids, tableName, newsql, args) + err = caches.PutCacheSql(cacher, ids, tableName, newsql, args) if err != nil { return err } @@ -387,7 +388,7 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in sliceValue := reflect.Indirect(reflect.ValueOf(rowsSlicePtr)) ididxes := make(map[string]int) - var ides []core.PK + var ides []schemas.PK var temps = make([]interface{}, len(ids)) for idx, id := range ids { @@ -502,7 +503,7 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in } } else { if keyType.Kind() != reflect.Slice { - return errors.New("table have multiple primary keys, key is not core.PK or slice") + return errors.New("table have multiple primary keys, key is not schemas.PK or slice") } ikey = key } diff --git a/session_find_test.go b/session_find_test.go index a5c7ae4f..7847e284 100644 --- a/session_find_test.go +++ b/session_find_test.go @@ -11,7 +11,7 @@ import ( "time" "github.com/stretchr/testify/assert" - "xorm.io/core" + "xorm.io/xorm/names" ) func TestJoinLimit(t *testing.T) { @@ -300,7 +300,7 @@ func TestOrderSameMapper(t *testing.T) { testEngine.UnMapType(rValue(new(Userinfo)).Type()) mapper := testEngine.GetTableMapper() - testEngine.SetMapper(core.SameMapper{}) + testEngine.SetMapper(names.SameMapper{}) defer func() { testEngine.UnMapType(rValue(new(Userinfo)).Type()) @@ -325,7 +325,7 @@ func TestHavingSameMapper(t *testing.T) { testEngine.UnMapType(rValue(new(Userinfo)).Type()) mapper := testEngine.GetTableMapper() - testEngine.SetMapper(core.SameMapper{}) + testEngine.SetMapper(names.SameMapper{}) defer func() { testEngine.UnMapType(rValue(new(Userinfo)).Type()) testEngine.SetMapper(mapper) diff --git a/session_get.go b/session_get.go index cc0a2019..376ac2c1 100644 --- a/session_get.go +++ b/session_get.go @@ -11,7 +11,8 @@ import ( "reflect" "strconv" - "xorm.io/core" + "xorm.io/xorm/caches" + "xorm.io/xorm/schemas" ) // Get retrieve one record from database, bean's non-empty fields @@ -99,7 +100,7 @@ func (session *Session) get(bean interface{}) (bool, error) { return true, nil } -func (session *Session) nocacheGet(beanKind reflect.Kind, table *core.Table, bean interface{}, sqlStr string, args ...interface{}) (bool, error) { +func (session *Session) nocacheGet(beanKind reflect.Kind, table *schemas.Table, bean interface{}, sqlStr string, args ...interface{}) (bool, error) { rows, err := session.queryRows(sqlStr, args...) if err != nil { return false, err @@ -283,7 +284,7 @@ func (session *Session) cacheGet(bean interface{}, sqlStr string, args ...interf session.engine.logger.Debug("[cacheGet] find sql:", newsql, args) table := session.statement.RefTable - ids, err := core.GetCacheSql(cacher, tableName, newsql, args) + ids, err := caches.GetCacheSql(cacher, tableName, newsql, args) if err != nil { var res = make([]string, len(table.PrimaryKeys)) rows, err := session.NoCache().queryRows(newsql, args...) @@ -301,7 +302,7 @@ func (session *Session) cacheGet(bean interface{}, sqlStr string, args ...interf return false, ErrCacheFailed } - var pk core.PK = make([]interface{}, len(table.PrimaryKeys)) + var pk schemas.PK = make([]interface{}, len(table.PrimaryKeys)) for i, col := range table.PKColumns() { if col.SQLType.IsText() { pk[i] = res[i] @@ -316,9 +317,9 @@ func (session *Session) cacheGet(bean interface{}, sqlStr string, args ...interf } } - ids = []core.PK{pk} + ids = []schemas.PK{pk} session.engine.logger.Debug("[cacheGet] cache ids:", newsql, ids) - err = core.PutCacheSql(cacher, ids, tableName, newsql, args) + err = caches.PutCacheSql(cacher, ids, tableName, newsql, args) if err != nil { return false, err } diff --git a/session_get_test.go b/session_get_test.go index 54ba8916..f1e8c7f6 100644 --- a/session_get_test.go +++ b/session_get_test.go @@ -11,7 +11,7 @@ import ( "time" "github.com/stretchr/testify/assert" - "xorm.io/core" + "xorm.io/xorm/schemas" ) func TestGetVar(t *testing.T) { @@ -153,7 +153,7 @@ func TestGetVar(t *testing.T) { assert.Equal(t, "1.5", fmt.Sprintf("%.1f", money)) var money2 float64 - if testEngine.Dialect().DBType() == core.MSSQL { + if testEngine.Dialect().DBType() == schemas.MSSQL { has, err = testEngine.SQL("SELECT TOP 1 money FROM " + testEngine.TableName("get_var", true)).Get(&money2) } else { has, err = testEngine.SQL("SELECT money FROM " + testEngine.TableName("get_var", true) + " LIMIT 1").Get(&money2) @@ -233,7 +233,7 @@ func TestGetStruct(t *testing.T) { defer session.Close() var err error - if testEngine.Dialect().DBType() == core.MSSQL { + if testEngine.Dialect().DBType() == schemas.MSSQL { err = session.Begin() assert.NoError(t, err) _, err = session.Exec("SET IDENTITY_INSERT userinfo_get ON") @@ -242,7 +242,7 @@ func TestGetStruct(t *testing.T) { cnt, err := session.Insert(&UserinfoGet{Uid: 2}) assert.NoError(t, err) assert.EqualValues(t, 1, cnt) - if testEngine.Dialect().DBType() == core.MSSQL { + if testEngine.Dialect().DBType() == schemas.MSSQL { err = session.Commit() assert.NoError(t, err) } diff --git a/session_insert.go b/session_insert.go index fb67db16..4a026b78 100644 --- a/session_insert.go +++ b/session_insert.go @@ -13,7 +13,7 @@ import ( "strings" "xorm.io/builder" - "xorm.io/core" + "xorm.io/xorm/schemas" ) // ErrNoElementsOnSlice represents an error there is no element when insert @@ -127,7 +127,7 @@ func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error var colNames []string var colMultiPlaces []string var args []interface{} - var cols []*core.Column + var cols []*schemas.Column for i := 0; i < size; i++ { v := sliceValue.Index(i) @@ -156,7 +156,7 @@ func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error if col.IsAutoIncrement && isZero(fieldValue.Interface()) { continue } - if col.MapType == core.ONLYFROMDB { + if col.MapType == schemas.ONLYFROMDB { continue } if col.IsDeleted { @@ -207,7 +207,7 @@ func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error if col.IsAutoIncrement && isZero(fieldValue.Interface()) { continue } - if col.MapType == core.ONLYFROMDB { + if col.MapType == schemas.ONLYFROMDB { continue } if col.IsDeleted { @@ -251,7 +251,7 @@ func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error cleanupProcessorsClosures(&session.beforeClosures) var sql string - if session.engine.dialect.DBType() == core.ORACLE { + if session.engine.dialect.DBType() == schemas.ORACLE { temp := fmt.Sprintf(") INTO %s (%v) VALUES (", session.engine.Quote(tableName), quoteColumns(colNames, session.engine.Quote, ",")) @@ -358,7 +358,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { var tableName = session.statement.TableName() var output string - if session.engine.dialect.DBType() == core.MSSQL && len(table.AutoIncrement) > 0 { + if session.engine.dialect.DBType() == schemas.MSSQL && len(table.AutoIncrement) > 0 { output = fmt.Sprintf(" OUTPUT Inserted.%s", table.AutoIncrement) } @@ -368,7 +368,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { } if len(colPlaces) <= 0 { - if session.engine.dialect.DBType() == core.MYSQL { + if session.engine.dialect.DBType() == schemas.MYSQL { if _, err := buf.WriteString(" VALUES ()"); err != nil { return 0, err } @@ -430,7 +430,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { } } - if len(table.AutoIncrement) > 0 && session.engine.dialect.DBType() == core.POSTGRES { + if len(table.AutoIncrement) > 0 && session.engine.dialect.DBType() == schemas.POSTGRES { if _, err := buf.WriteString(" RETURNING " + session.engine.Quote(table.AutoIncrement)); err != nil { return 0, err } @@ -469,7 +469,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { // for postgres, many of them didn't implement lastInsertId, so we should // implemented it ourself. - if session.engine.dialect.DBType() == core.ORACLE && len(table.AutoIncrement) > 0 { + if session.engine.dialect.DBType() == schemas.ORACLE && len(table.AutoIncrement) > 0 { res, err := session.queryBytes("select seq_atable.currval from dual", args...) if err != nil { return 0, err @@ -510,7 +510,7 @@ func (session *Session) innerInsert(bean interface{}) (int64, error) { aiValue.Set(int64ToIntValue(id, aiValue.Type())) return 1, nil - } else if len(table.AutoIncrement) > 0 && (session.engine.dialect.DBType() == core.POSTGRES || session.engine.dialect.DBType() == core.MSSQL) { + } else if len(table.AutoIncrement) > 0 && (session.engine.dialect.DBType() == schemas.POSTGRES || session.engine.dialect.DBType() == schemas.MSSQL) { res, err := session.queryBytes(sqlStr, args...) if err != nil { @@ -626,7 +626,7 @@ func (session *Session) genInsertColumns(bean interface{}) ([]string, []interfac args := make([]interface{}, 0, len(table.ColumnsSeq())) for _, col := range table.Columns() { - if col.MapType == core.ONLYFROMDB { + if col.MapType == schemas.ONLYFROMDB { continue } diff --git a/session_pk_test.go b/session_pk_test.go index 039ab367..ec5a611b 100644 --- a/session_pk_test.go +++ b/session_pk_test.go @@ -11,7 +11,7 @@ import ( "time" "github.com/stretchr/testify/assert" - "xorm.io/core" + "xorm.io/xorm/schemas" ) type IntId struct { @@ -726,7 +726,7 @@ func TestCompositeKey(t *testing.T) { } var compositeKeyVal CompositeKey - has, err := testEngine.ID(core.PK{11, 22}).Get(&compositeKeyVal) + has, err := testEngine.ID(schemas.PK{11, 22}).Get(&compositeKeyVal) if err != nil { t.Error(err) } else if !has { @@ -735,7 +735,7 @@ func TestCompositeKey(t *testing.T) { var compositeKeyVal2 CompositeKey // test passing PK ptr, this test seem failed withCache - has, err = testEngine.ID(&core.PK{11, 22}).Get(&compositeKeyVal2) + has, err = testEngine.ID(&schemas.PK{11, 22}).Get(&compositeKeyVal2) if err != nil { t.Error(err) } else if !has { @@ -772,14 +772,14 @@ func TestCompositeKey(t *testing.T) { assert.EqualValues(t, compositeKeyVal, cps[0], "should be equeal") compositeKeyVal = CompositeKey{UpdateStr: "test1"} - cnt, err = testEngine.ID(core.PK{11, 22}).Update(&compositeKeyVal) + cnt, err = testEngine.ID(schemas.PK{11, 22}).Update(&compositeKeyVal) if err != nil { t.Error(err) } else if cnt != 1 { t.Error(errors.New("can't update CompositeKey{11, 22}")) } - cnt, err = testEngine.ID(core.PK{11, 22}).Delete(&CompositeKey{}) + cnt, err = testEngine.ID(schemas.PK{11, 22}).Delete(&CompositeKey{}) if err != nil { t.Error(err) } else if cnt != 1 { @@ -823,7 +823,7 @@ func TestCompositeKey2(t *testing.T) { } var user User - has, err := testEngine.ID(core.PK{"11", 22}).Get(&user) + has, err := testEngine.ID(schemas.PK{"11", 22}).Get(&user) if err != nil { t.Error(err) } else if !has { @@ -831,7 +831,7 @@ func TestCompositeKey2(t *testing.T) { } // test passing PK ptr, this test seem failed withCache - has, err = testEngine.ID(&core.PK{"11", 22}).Get(&user) + has, err = testEngine.ID(&schemas.PK{"11", 22}).Get(&user) if err != nil { t.Error(err) } else if !has { @@ -839,14 +839,14 @@ func TestCompositeKey2(t *testing.T) { } user = User{NickName: "test1"} - cnt, err = testEngine.ID(core.PK{"11", 22}).Update(&user) + cnt, err = testEngine.ID(schemas.PK{"11", 22}).Update(&user) if err != nil { t.Error(err) } else if cnt != 1 { t.Error(errors.New("can't update User{11, 22}")) } - cnt, err = testEngine.ID(core.PK{"11", 22}).Delete(&User{}) + cnt, err = testEngine.ID(schemas.PK{"11", 22}).Delete(&User{}) if err != nil { t.Error(err) } else if cnt != 1 { @@ -891,7 +891,7 @@ func TestCompositeKey3(t *testing.T) { } var user UserPK2 - has, err := testEngine.ID(core.PK{"11", 22}).Get(&user) + has, err := testEngine.ID(schemas.PK{"11", 22}).Get(&user) if err != nil { t.Error(err) } else if !has { @@ -899,7 +899,7 @@ func TestCompositeKey3(t *testing.T) { } // test passing PK ptr, this test seem failed withCache - has, err = testEngine.ID(&core.PK{"11", 22}).Get(&user) + has, err = testEngine.ID(&schemas.PK{"11", 22}).Get(&user) if err != nil { t.Error(err) } else if !has { @@ -907,14 +907,14 @@ func TestCompositeKey3(t *testing.T) { } user = UserPK2{NickName: "test1"} - cnt, err = testEngine.ID(core.PK{"11", 22}).Update(&user) + cnt, err = testEngine.ID(schemas.PK{"11", 22}).Update(&user) if err != nil { t.Error(err) } else if cnt != 1 { t.Error(errors.New("can't update User{11, 22}")) } - cnt, err = testEngine.ID(core.PK{"11", 22}).Delete(&UserPK2{}) + cnt, err = testEngine.ID(schemas.PK{"11", 22}).Delete(&UserPK2{}) if err != nil { t.Error(err) } else if cnt != 1 { @@ -1130,7 +1130,7 @@ func TestCompositePK(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 1+len(tables1), len(tables2)) - var table *core.Table + var table *schemas.Table for _, t := range tables2 { if t.Name == testEngine.GetTableMapper().Obj2Table("TaskSolution") { table = t diff --git a/session_query.go b/session_query.go index 21c00b8d..0623c90c 100644 --- a/session_query.go +++ b/session_query.go @@ -12,7 +12,8 @@ import ( "time" "xorm.io/builder" - "xorm.io/core" + "xorm.io/xorm/core" + "xorm.io/xorm/schemas" ) func (session *Session) genQuerySQL(sqlOrArgs ...interface{}) (string, []interface{}, error) { @@ -116,8 +117,8 @@ func value2String(rawValue *reflect.Value) (str string, err error) { } // time type case reflect.Struct: - if aa.ConvertibleTo(core.TimeType) { - str = vv.Convert(core.TimeType).Interface().(time.Time).Format(time.RFC3339Nano) + if aa.ConvertibleTo(schemas.TimeType) { + str = vv.Convert(schemas.TimeType).Interface().(time.Time).Format(time.RFC3339Nano) } else { err = fmt.Errorf("Unsupported struct type %v", vv.Type().Name()) } diff --git a/session_query_test.go b/session_query_test.go index 7e72357f..e4635d64 100644 --- a/session_query_test.go +++ b/session_query_test.go @@ -11,7 +11,7 @@ import ( "time" "xorm.io/builder" - "xorm.io/core" + "xorm.io/xorm/schemas" "github.com/stretchr/testify/assert" ) @@ -207,7 +207,7 @@ func TestQueryStringNoParam(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 1, len(records)) assert.EqualValues(t, "1", records[0]["id"]) - if testEngine.Dialect().DBType() == core.POSTGRES || testEngine.Dialect().DBType() == core.MSSQL { + if testEngine.Dialect().DBType() == schemas.POSTGRES || testEngine.Dialect().DBType() == schemas.MSSQL { assert.EqualValues(t, "false", records[0]["msg"]) } else { assert.EqualValues(t, "0", records[0]["msg"]) @@ -217,7 +217,7 @@ func TestQueryStringNoParam(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 1, len(records)) assert.EqualValues(t, "1", records[0]["id"]) - if testEngine.Dialect().DBType() == core.POSTGRES || testEngine.Dialect().DBType() == core.MSSQL { + if testEngine.Dialect().DBType() == schemas.POSTGRES || testEngine.Dialect().DBType() == schemas.MSSQL { assert.EqualValues(t, "false", records[0]["msg"]) } else { assert.EqualValues(t, "0", records[0]["msg"]) @@ -244,7 +244,7 @@ func TestQuerySliceStringNoParam(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 1, len(records)) assert.EqualValues(t, "1", records[0][0]) - if testEngine.Dialect().DBType() == core.POSTGRES || testEngine.Dialect().DBType() == core.MSSQL { + if testEngine.Dialect().DBType() == schemas.POSTGRES || testEngine.Dialect().DBType() == schemas.MSSQL { assert.EqualValues(t, "false", records[0][1]) } else { assert.EqualValues(t, "0", records[0][1]) @@ -254,7 +254,7 @@ func TestQuerySliceStringNoParam(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 1, len(records)) assert.EqualValues(t, "1", records[0][0]) - if testEngine.Dialect().DBType() == core.POSTGRES || testEngine.Dialect().DBType() == core.MSSQL { + if testEngine.Dialect().DBType() == schemas.POSTGRES || testEngine.Dialect().DBType() == schemas.MSSQL { assert.EqualValues(t, "false", records[0][1]) } else { assert.EqualValues(t, "0", records[0][1]) diff --git a/session_raw.go b/session_raw.go index a9298296..4a2e2777 100644 --- a/session_raw.go +++ b/session_raw.go @@ -10,7 +10,7 @@ import ( "time" "xorm.io/builder" - "xorm.io/core" + "xorm.io/xorm/core" ) func (session *Session) queryPreprocess(sqlStr *string, paramStr ...interface{}) { diff --git a/session_schema.go b/session_schema.go index d4c71374..31b2ea42 100644 --- a/session_schema.go +++ b/session_schema.go @@ -9,7 +9,7 @@ import ( "fmt" "strings" - "xorm.io/core" + "xorm.io/xorm/schemas" ) // Ping test if database is ok @@ -125,7 +125,7 @@ func (session *Session) dropTable(beanOrTableName interface{}) error { tableName := session.engine.TableName(beanOrTableName) var needDrop = true if !session.engine.dialect.SupportDropIfExists() { - sqlStr, args := session.engine.dialect.TableCheckSql(tableName) + sqlStr, args := session.engine.dialect.TableCheckSQL(tableName) results, err := session.queryBytes(sqlStr, args...) if err != nil { return err @@ -134,7 +134,7 @@ func (session *Session) dropTable(beanOrTableName interface{}) error { } if needDrop { - sqlStr := session.engine.Dialect().DropTableSql(session.engine.TableName(tableName, true)) + sqlStr := session.engine.Dialect().DropTableSQL(session.engine.TableName(tableName, true)) _, err := session.exec(sqlStr) return err } @@ -153,7 +153,7 @@ func (session *Session) IsTableExist(beanOrTableName interface{}) (bool, error) } func (session *Session) isTableExist(tableName string) (bool, error) { - sqlStr, args := session.engine.dialect.TableCheckSql(tableName) + sqlStr, args := session.engine.dialect.TableCheckSQL(tableName) results, err := session.queryBytes(sqlStr, args...) return len(results) > 0, err } @@ -190,9 +190,9 @@ func (session *Session) isIndexExist2(tableName string, cols []string, unique bo for _, index := range indexes { if sliceEq(index.Cols, cols) { if unique { - return index.Type == core.UniqueType, nil + return index.Type == schemas.UniqueType, nil } - return index.Type == core.IndexType, nil + return index.Type == schemas.IndexType, nil } } return false, nil @@ -207,14 +207,14 @@ func (session *Session) addColumn(colName string) error { func (session *Session) addIndex(tableName, idxName string) error { index := session.statement.RefTable.Indexes[idxName] - sqlStr := session.engine.dialect.CreateIndexSql(tableName, index) + sqlStr := session.engine.dialect.CreateIndexSQL(tableName, index) _, err := session.exec(sqlStr) return err } func (session *Session) addUnique(tableName, uqeName string) error { index := session.statement.RefTable.Indexes[uqeName] - sqlStr := session.engine.dialect.CreateIndexSql(tableName, index) + sqlStr := session.engine.dialect.CreateIndexSQL(tableName, index) _, err := session.exec(sqlStr) return err } @@ -253,7 +253,7 @@ func (session *Session) Sync2(beans ...interface{}) error { } tbNameWithSchema := engine.tbNameWithSchema(tbName) - var oriTable *core.Table + var oriTable *schemas.Table for _, tb := range tables { if strings.EqualFold(engine.tbNameWithSchema(tb.Name), engine.tbNameWithSchema(tbName)) { oriTable = tb @@ -287,7 +287,7 @@ func (session *Session) Sync2(beans ...interface{}) error { // check columns for _, col := range table.Columns() { - var oriCol *core.Column + var oriCol *schemas.Column for _, col2 := range oriTable.Columns() { if strings.EqualFold(col.Name, col2.Name) { oriCol = col2 @@ -306,27 +306,27 @@ func (session *Session) Sync2(beans ...interface{}) error { } err = nil - expectedType := engine.dialect.SqlType(col) - curType := engine.dialect.SqlType(oriCol) + expectedType := engine.dialect.SQLType(col) + curType := engine.dialect.SQLType(oriCol) if expectedType != curType { - if expectedType == core.Text && - strings.HasPrefix(curType, core.Varchar) { + if expectedType == schemas.Text && + strings.HasPrefix(curType, schemas.Varchar) { // currently only support mysql & postgres - if engine.dialect.DBType() == core.MYSQL || - engine.dialect.DBType() == core.POSTGRES { + if engine.dialect.DBType() == schemas.MYSQL || + engine.dialect.DBType() == schemas.POSTGRES { engine.logger.Infof("Table %s column %s change type from %s to %s\n", tbNameWithSchema, col.Name, curType, expectedType) - _, err = session.exec(engine.dialect.ModifyColumnSql(tbNameWithSchema, col)) + _, err = session.exec(engine.dialect.ModifyColumnSQL(tbNameWithSchema, col)) } else { engine.logger.Warnf("Table %s column %s db type is %s, struct type is %s\n", tbNameWithSchema, col.Name, curType, expectedType) } - } else if strings.HasPrefix(curType, core.Varchar) && strings.HasPrefix(expectedType, core.Varchar) { - if engine.dialect.DBType() == core.MYSQL { + } else if strings.HasPrefix(curType, schemas.Varchar) && strings.HasPrefix(expectedType, schemas.Varchar) { + if engine.dialect.DBType() == schemas.MYSQL { if oriCol.Length < col.Length { engine.logger.Infof("Table %s column %s change type from varchar(%d) to varchar(%d)\n", tbNameWithSchema, col.Name, oriCol.Length, col.Length) - _, err = session.exec(engine.dialect.ModifyColumnSql(tbNameWithSchema, col)) + _, err = session.exec(engine.dialect.ModifyColumnSQL(tbNameWithSchema, col)) } } } else { @@ -335,12 +335,12 @@ func (session *Session) Sync2(beans ...interface{}) error { tbNameWithSchema, col.Name, curType, expectedType) } } - } else if expectedType == core.Varchar { - if engine.dialect.DBType() == core.MYSQL { + } else if expectedType == schemas.Varchar { + if engine.dialect.DBType() == schemas.MYSQL { if oriCol.Length < col.Length { engine.logger.Infof("Table %s column %s change type from varchar(%d) to varchar(%d)\n", tbNameWithSchema, col.Name, oriCol.Length, col.Length) - _, err = session.exec(engine.dialect.ModifyColumnSql(tbNameWithSchema, col)) + _, err = session.exec(engine.dialect.ModifyColumnSQL(tbNameWithSchema, col)) } } } @@ -348,7 +348,7 @@ func (session *Session) Sync2(beans ...interface{}) error { if col.Default != oriCol.Default { switch { case col.IsAutoIncrement: // For autoincrement column, don't check default - case (col.SQLType.Name == core.Bool || col.SQLType.Name == core.Boolean) && + case (col.SQLType.Name == schemas.Bool || col.SQLType.Name == schemas.Boolean) && ((strings.EqualFold(col.Default, "true") && oriCol.Default == "1") || (strings.EqualFold(col.Default, "false") && oriCol.Default == "0")): default: @@ -367,10 +367,10 @@ func (session *Session) Sync2(beans ...interface{}) error { } var foundIndexNames = make(map[string]bool) - var addedNames = make(map[string]*core.Index) + var addedNames = make(map[string]*schemas.Index) for name, index := range table.Indexes { - var oriIndex *core.Index + var oriIndex *schemas.Index for name2, index2 := range oriTable.Indexes { if index.Equal(index2) { oriIndex = index2 @@ -381,7 +381,7 @@ func (session *Session) Sync2(beans ...interface{}) error { if oriIndex != nil { if oriIndex.Type != index.Type { - sql := engine.dialect.DropIndexSql(tbNameWithSchema, oriIndex) + sql := engine.dialect.DropIndexSQL(tbNameWithSchema, oriIndex) _, err = session.exec(sql) if err != nil { return err @@ -397,7 +397,7 @@ func (session *Session) Sync2(beans ...interface{}) error { for name2, index2 := range oriTable.Indexes { if _, ok := foundIndexNames[name2]; !ok { - sql := engine.dialect.DropIndexSql(tbNameWithSchema, index2) + sql := engine.dialect.DropIndexSQL(tbNameWithSchema, index2) _, err = session.exec(sql) if err != nil { return err @@ -406,11 +406,11 @@ func (session *Session) Sync2(beans ...interface{}) error { } for name, index := range addedNames { - if index.Type == core.UniqueType { + if index.Type == schemas.UniqueType { session.statement.RefTable = table session.statement.tableName = tbNameWithSchema err = session.addUnique(tbNameWithSchema, name) - } else if index.Type == core.IndexType { + } else if index.Type == schemas.IndexType { session.statement.RefTable = table session.statement.tableName = tbNameWithSchema err = session.addIndex(tbNameWithSchema, name) diff --git a/session_schema_test.go b/session_schema_test.go index f24d1405..86f9315e 100644 --- a/session_schema_test.go +++ b/session_schema_test.go @@ -213,7 +213,7 @@ func TestCustomTableName(t *testing.T) { func TestDump(t *testing.T) { assert.NoError(t, prepareEngine()) - fp := testEngine.Dialect().URI().DbName + ".sql" + fp := testEngine.Dialect().URI().DBName + ".sql" os.Remove(fp) assert.NoError(t, testEngine.DumpAllToFile(fp)) } diff --git a/session_tx_test.go b/session_tx_test.go index 7c66b7c7..da3f0f04 100644 --- a/session_tx_test.go +++ b/session_tx_test.go @@ -10,7 +10,7 @@ import ( "time" "github.com/stretchr/testify/assert" - "xorm.io/core" + "xorm.io/xorm/names" ) func TestTransaction(t *testing.T) { @@ -89,7 +89,7 @@ func TestCombineTransactionSameMapper(t *testing.T) { oldMapper := testEngine.GetColumnMapper() testEngine.UnMapType(rValue(new(Userinfo)).Type()) - testEngine.SetMapper(core.SameMapper{}) + testEngine.SetMapper(names.SameMapper{}) defer func() { testEngine.UnMapType(rValue(new(Userinfo)).Type()) testEngine.SetMapper(oldMapper) diff --git a/session_update.go b/session_update.go index ceaade46..7316c3b2 100644 --- a/session_update.go +++ b/session_update.go @@ -12,10 +12,11 @@ import ( "strings" "xorm.io/builder" - "xorm.io/core" + "xorm.io/xorm/caches" + "xorm.io/xorm/schemas" ) -func (session *Session) cacheUpdate(table *core.Table, tableName, sqlStr string, args ...interface{}) error { +func (session *Session) cacheUpdate(table *schemas.Table, tableName, sqlStr string, args ...interface{}) error { if table == nil || session.tx != nil { return ErrCacheFailed @@ -42,7 +43,7 @@ func (session *Session) cacheUpdate(table *core.Table, tableName, sqlStr string, cacher := session.engine.getCacher(tableName) session.engine.logger.Debug("[cacheUpdate] get cache sql", newsql, args[nStart:]) - ids, err := core.GetCacheSql(cacher, tableName, newsql, args[nStart:]) + ids, err := caches.GetCacheSql(cacher, tableName, newsql, args[nStart:]) if err != nil { rows, err := session.NoCache().queryRows(newsql, args[nStart:]...) if err != nil { @@ -50,14 +51,14 @@ func (session *Session) cacheUpdate(table *core.Table, tableName, sqlStr string, } defer rows.Close() - ids = make([]core.PK, 0) + ids = make([]schemas.PK, 0) for rows.Next() { var res = make([]string, len(table.PrimaryKeys)) err = rows.ScanSlice(&res) if err != nil { return err } - var pk core.PK = make([]interface{}, len(table.PrimaryKeys)) + var pk schemas.PK = make([]interface{}, len(table.PrimaryKeys)) for i, col := range table.PKColumns() { if col.SQLType.IsNumeric() { n, err := strconv.ParseInt(res[i], 10, 64) @@ -339,9 +340,9 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 var top string if st.LimitN != nil { limitValue := *st.LimitN - if st.Engine.dialect.DBType() == core.MYSQL { + if st.Engine.dialect.DBType() == schemas.MYSQL { condSQL = condSQL + fmt.Sprintf(" LIMIT %d", limitValue) - } else if st.Engine.dialect.DBType() == core.SQLITE { + } else if st.Engine.dialect.DBType() == schemas.SQLITE { tempCondSQL := condSQL + fmt.Sprintf(" LIMIT %d", limitValue) cond = cond.And(builder.Expr(fmt.Sprintf("rowid IN (SELECT rowid FROM %v %v)", session.engine.Quote(tableName), tempCondSQL), condArgs...)) @@ -352,7 +353,7 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 if len(condSQL) > 0 { condSQL = "WHERE " + condSQL } - } else if st.Engine.dialect.DBType() == core.POSTGRES { + } else if st.Engine.dialect.DBType() == schemas.POSTGRES { tempCondSQL := condSQL + fmt.Sprintf(" LIMIT %d", limitValue) cond = cond.And(builder.Expr(fmt.Sprintf("CTID IN (SELECT CTID FROM %v %v)", session.engine.Quote(tableName), tempCondSQL), condArgs...)) @@ -364,8 +365,8 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 if len(condSQL) > 0 { condSQL = "WHERE " + condSQL } - } else if st.Engine.dialect.DBType() == core.MSSQL { - if st.OrderStr != "" && st.Engine.dialect.DBType() == core.MSSQL && + } else if st.Engine.dialect.DBType() == schemas.MSSQL { + if st.OrderStr != "" && st.Engine.dialect.DBType() == schemas.MSSQL && table != nil && len(table.PrimaryKeys) == 1 { cond = builder.Expr(fmt.Sprintf("%s IN (SELECT TOP (%d) %s FROM %v%v)", table.PrimaryKeys[0], limitValue, table.PrimaryKeys[0], @@ -392,7 +393,7 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 var fromSQL string if session.statement.TableAlias != "" { switch session.engine.dialect.DBType() { - case core.MSSQL: + case schemas.MSSQL: fromSQL = fmt.Sprintf("FROM %s %s ", tableAlias, session.statement.TableAlias) tableAlias = session.statement.TableAlias default: @@ -467,7 +468,7 @@ func (session *Session) genUpdateColumns(bean interface{}) ([]string, []interfac continue } } - if col.MapType == core.ONLYFROMDB { + if col.MapType == schemas.ONLYFROMDB { continue } diff --git a/session_update_test.go b/session_update_test.go index d646ff2d..cb79bad0 100644 --- a/session_update_test.go +++ b/session_update_test.go @@ -12,7 +12,7 @@ import ( "time" "github.com/stretchr/testify/assert" - "xorm.io/core" + "xorm.io/xorm/names" ) func TestUpdateMap(t *testing.T) { @@ -691,7 +691,7 @@ func TestUpdateSameMapper(t *testing.T) { testEngine.UnMapType(rValue(new(UpdateAllCols)).Type()) testEngine.UnMapType(rValue(new(UpdateMustCols)).Type()) testEngine.UnMapType(rValue(new(UpdateIncr)).Type()) - testEngine.SetMapper(core.SameMapper{}) + testEngine.SetMapper(names.SameMapper{}) defer func() { testEngine.UnMapType(rValue(new(Userinfo)).Type()) testEngine.UnMapType(rValue(new(Condi)).Type()) @@ -1419,7 +1419,7 @@ func TestUpdateMap3(t *testing.T) { testEngine.SetColumnMapper(oldMapper) }() - mapper := core.NewPrefixMapper(core.SnakeMapper{}, "F") + mapper := names.NewPrefixMapper(names.SnakeMapper{}, "F") testEngine.SetColumnMapper(mapper) assertSync(t, new(UpdateMapUser)) diff --git a/statement.go b/statement.go index ea3ecabe..e61b76b7 100644 --- a/statement.go +++ b/statement.go @@ -12,16 +12,17 @@ import ( "time" "xorm.io/builder" - "xorm.io/core" + "xorm.io/xorm/dialects" + "xorm.io/xorm/schemas" ) // Statement save all the sql info for executing SQL type Statement struct { - RefTable *core.Table + RefTable *schemas.Table Engine *Engine Start int LimitN *int - idParam *core.PK + idParam *schemas.PK OrderStr string JoinStr string joinArgs []interface{} @@ -266,7 +267,7 @@ func (statement *Statement) buildUpdates(bean interface{}, continue } - if col.MapType == core.ONLYFROMDB { + if col.MapType == schemas.ONLYFROMDB { continue } @@ -314,7 +315,7 @@ func (statement *Statement) buildUpdates(bean interface{}, var val interface{} if fieldValue.CanAddr() { - if structConvert, ok := fieldValue.Addr().Interface().(core.Conversion); ok { + if structConvert, ok := fieldValue.Addr().Interface().(Conversion); ok { data, err := structConvert.ToDB() if err != nil { engine.logger.Error(err) @@ -325,7 +326,7 @@ func (statement *Statement) buildUpdates(bean interface{}, } } - if structConvert, ok := fieldValue.Interface().(core.Conversion); ok { + if structConvert, ok := fieldValue.Interface().(Conversion); ok { data, err := structConvert.ToDB() if err != nil { engine.logger.Error(err) @@ -388,8 +389,8 @@ func (statement *Statement) buildUpdates(bean interface{}, t := int64(fieldValue.Uint()) val = reflect.ValueOf(&t).Interface() case reflect.Struct: - if fieldType.ConvertibleTo(core.TimeType) { - t := fieldValue.Convert(core.TimeType).Interface().(time.Time) + if fieldType.ConvertibleTo(schemas.TimeType) { + t := fieldValue.Convert(schemas.TimeType).Interface().(time.Time) if !requiredField && (t.IsZero() || !fieldValue.IsValid()) { continue } @@ -496,7 +497,7 @@ func (statement *Statement) needTableName() bool { return len(statement.JoinStr) > 0 } -func (statement *Statement) colName(col *core.Column, tableName string) string { +func (statement *Statement) colName(col *schemas.Column, tableName string) string { if statement.needTableName() { var nm = tableName if len(statement.TableAlias) > 0 { @@ -523,12 +524,12 @@ func (statement *Statement) ID(id interface{}) *Statement { switch idType { case ptrPkType: - if pkPtr, ok := (id).(*core.PK); ok { + if pkPtr, ok := (id).(*schemas.PK); ok { statement.idParam = pkPtr return statement } case pkType: - if pk, ok := (id).(core.PK); ok { + if pk, ok := (id).(schemas.PK); ok { statement.idParam = &pk return statement } @@ -536,11 +537,11 @@ func (statement *Statement) ID(id interface{}) *Statement { switch idType.Kind() { case reflect.String: - statement.idParam = &core.PK{idValue.Convert(reflect.TypeOf("")).Interface()} + statement.idParam = &schemas.PK{idValue.Convert(reflect.TypeOf("")).Interface()} return statement } - statement.idParam = &core.PK{id} + statement.idParam = &schemas.PK{id} return statement } @@ -807,7 +808,7 @@ func (statement *Statement) genColumnStr() string { continue } - if col.MapType == core.ONLYTODB { + if col.MapType == schemas.ONLYTODB { continue } @@ -832,7 +833,7 @@ func (statement *Statement) genColumnStr() string { } func (statement *Statement) genCreateTableSQL() string { - return statement.Engine.dialect.CreateTableSql(statement.RefTable, statement.TableName(), + return statement.Engine.dialect.CreateTableSQL(statement.RefTable, statement.TableName(), statement.StoreEngine, statement.Charset) } @@ -840,8 +841,8 @@ func (statement *Statement) genIndexSQL() []string { var sqls []string tbName := statement.TableName() for _, index := range statement.RefTable.Indexes { - if index.Type == core.IndexType { - sql := statement.Engine.dialect.CreateIndexSql(tbName, index) + if index.Type == schemas.IndexType { + sql := statement.Engine.dialect.CreateIndexSQL(tbName, index) /*idxTBName := strings.Replace(tbName, ".", "_", -1) idxTBName = strings.Replace(idxTBName, `"`, "", -1) sql := fmt.Sprintf("CREATE INDEX %v ON %v (%v);", quote(indexName(idxTBName, idxName)), @@ -860,8 +861,8 @@ func (statement *Statement) genUniqueSQL() []string { var sqls []string tbName := statement.TableName() for _, index := range statement.RefTable.Indexes { - if index.Type == core.UniqueType { - sql := statement.Engine.dialect.CreateIndexSql(tbName, index) + if index.Type == schemas.UniqueType { + sql := statement.Engine.dialect.CreateIndexSQL(tbName, index) sqls = append(sqls, sql) } } @@ -875,9 +876,9 @@ func (statement *Statement) genDelIndexSQL() []string { idxPrefixName = strings.Replace(idxPrefixName, `.`, "_", -1) for idxName, index := range statement.RefTable.Indexes { var rIdxName string - if index.Type == core.UniqueType { + if index.Type == schemas.UniqueType { rIdxName = uniqueName(idxPrefixName, idxName) - } else if index.Type == core.IndexType { + } else if index.Type == schemas.IndexType { rIdxName = indexName(idxPrefixName, idxName) } sql := fmt.Sprintf("DROP INDEX %v", statement.Engine.Quote(statement.Engine.TableName(rIdxName, true))) @@ -889,18 +890,18 @@ func (statement *Statement) genDelIndexSQL() []string { return sqls } -func (statement *Statement) genAddColumnStr(col *core.Column) (string, []interface{}) { +func (statement *Statement) genAddColumnStr(col *schemas.Column) (string, []interface{}) { quote := statement.Engine.Quote sql := fmt.Sprintf("ALTER TABLE %v ADD %v", quote(statement.TableName()), - col.String(statement.Engine.dialect)) - if statement.Engine.dialect.DBType() == core.MYSQL && len(col.Comment) > 0 { + dialects.String(statement.Engine.dialect, col)) + if statement.Engine.dialect.DBType() == schemas.MYSQL && len(col.Comment) > 0 { sql += " COMMENT '" + col.Comment + "'" } sql += ";" return sql, []interface{}{} } -func (statement *Statement) buildConds(table *core.Table, bean interface{}, includeVersion bool, includeUpdated bool, includeNil bool, includeAutoIncr bool, addedTableName bool) (builder.Cond, error) { +func (statement *Statement) buildConds(table *schemas.Table, bean interface{}, includeVersion bool, includeUpdated bool, includeNil bool, includeAutoIncr bool, addedTableName bool) (builder.Cond, error) { return statement.Engine.buildConds(table, bean, includeVersion, includeUpdated, includeNil, includeAutoIncr, statement.allUseBool, statement.useAllCols, statement.unscoped, statement.mustColumnMap, statement.TableName(), statement.TableAlias, addedTableName) } @@ -1054,14 +1055,14 @@ func (statement *Statement) genSelectSQL(columnStr, condSQL string, needLimit, n whereStr = " WHERE " + condSQL } - if dialect.DBType() == core.MSSQL && strings.Contains(statement.TableName(), "..") { + if dialect.DBType() == schemas.MSSQL && strings.Contains(statement.TableName(), "..") { fromStr += statement.TableName() } else { fromStr += quote(statement.TableName()) } if statement.TableAlias != "" { - if dialect.DBType() == core.ORACLE { + if dialect.DBType() == schemas.ORACLE { fromStr += " " + quote(statement.TableAlias) } else { fromStr += " AS " + quote(statement.TableAlias) @@ -1072,7 +1073,7 @@ func (statement *Statement) genSelectSQL(columnStr, condSQL string, needLimit, n } pLimitN := statement.LimitN - if dialect.DBType() == core.MSSQL { + if dialect.DBType() == schemas.MSSQL { if pLimitN != nil { LimitNValue := *pLimitN top = fmt.Sprintf("TOP %d ", LimitNValue) @@ -1134,7 +1135,7 @@ func (statement *Statement) genSelectSQL(columnStr, condSQL string, needLimit, n fmt.Fprint(&buf, " ORDER BY ", statement.OrderStr) } if needLimit { - if dialect.DBType() != core.MSSQL && dialect.DBType() != core.ORACLE { + if dialect.DBType() != schemas.MSSQL && dialect.DBType() != schemas.ORACLE { if statement.Start > 0 { if pLimitN != nil { fmt.Fprintf(&buf, " LIMIT %v OFFSET %v", *pLimitN, statement.Start) @@ -1144,7 +1145,7 @@ func (statement *Statement) genSelectSQL(columnStr, condSQL string, needLimit, n } else if pLimitN != nil { fmt.Fprint(&buf, " LIMIT ", *pLimitN) } - } else if dialect.DBType() == core.ORACLE { + } else if dialect.DBType() == schemas.ORACLE { if statement.Start != 0 || pLimitN != nil { oldString := buf.String() buf.Reset() @@ -1158,7 +1159,7 @@ func (statement *Statement) genSelectSQL(columnStr, condSQL string, needLimit, n } } if statement.IsForUpdate { - return dialect.ForUpdateSql(buf.String()), nil + return dialect.ForUpdateSQL(buf.String()), nil } return buf.String(), nil @@ -1183,7 +1184,7 @@ func (statement *Statement) processIDParam() error { return nil } -func (statement *Statement) joinColumns(cols []*core.Column, includeTableName bool) string { +func (statement *Statement) joinColumns(cols []*schemas.Column, includeTableName bool) string { var colnames = make([]string, len(cols)) for i, col := range cols { if includeTableName { @@ -1211,7 +1212,7 @@ func (statement *Statement) convertIDSQL(sqlStr string) string { var top string pLimitN := statement.LimitN - if pLimitN != nil && statement.Engine.dialect.DBType() == core.MSSQL { + if pLimitN != nil && statement.Engine.dialect.DBType() == schemas.MSSQL { top = fmt.Sprintf("TOP %d ", *pLimitN) } @@ -1240,9 +1241,9 @@ func (statement *Statement) convertUpdateSQL(sqlStr string) (string, string) { // TODO: for postgres only, if any other database? var paraStr string - if statement.Engine.dialect.DBType() == core.POSTGRES { + if statement.Engine.dialect.DBType() == schemas.POSTGRES { paraStr = "$" - } else if statement.Engine.dialect.DBType() == core.MSSQL { + } else if statement.Engine.dialect.DBType() == schemas.MSSQL { paraStr = ":" } diff --git a/statement_args.go b/statement_args.go index 310f24d6..4f35ce6e 100644 --- a/statement_args.go +++ b/statement_args.go @@ -11,7 +11,7 @@ import ( "time" "xorm.io/builder" - "xorm.io/core" + "xorm.io/xorm/schemas" ) func quoteNeeded(a interface{}) bool { @@ -80,7 +80,7 @@ const insertSelectPlaceHolder = true func (statement *Statement) writeArg(w *builder.BytesWriter, arg interface{}) error { switch argv := arg.(type) { case bool: - if statement.Engine.dialect.DBType() == core.MSSQL { + if statement.Engine.dialect.DBType() == schemas.MSSQL { if argv { if _, err := w.WriteString("1"); err != nil { return err @@ -119,7 +119,7 @@ func (statement *Statement) writeArg(w *builder.BytesWriter, arg interface{}) er w.Append(arg) } else { var convertFunc = convertStringSingleQuote - if statement.Engine.dialect.DBType() == core.MYSQL { + if statement.Engine.dialect.DBType() == schemas.MYSQL { convertFunc = convertString } if _, err := w.WriteString(convertArg(arg, convertFunc)); err != nil { diff --git a/statement_test.go b/statement_test.go index acc542ab..bc5fc5dd 100644 --- a/statement_test.go +++ b/statement_test.go @@ -10,7 +10,7 @@ import ( "testing" "github.com/stretchr/testify/assert" - "xorm.io/core" + "xorm.io/xorm/schemas" ) var colStrTests = []struct { @@ -42,7 +42,7 @@ func TestColumnsStringGeneration(t *testing.T) { columns := statement.RefTable.Columns() if testCase.onlyToDBColumnNdx >= 0 { - columns[testCase.onlyToDBColumnNdx].MapType = core.ONLYTODB + columns[testCase.onlyToDBColumnNdx].MapType = schemas.ONLYTODB } actual := statement.genColumnStr() @@ -51,7 +51,7 @@ func TestColumnsStringGeneration(t *testing.T) { t.Errorf("[test #%d] Unexpected columns string:\nwant:\t%s\nhave:\t%s", ndx, testCase.expected, actual) } if testCase.onlyToDBColumnNdx >= 0 { - columns[testCase.onlyToDBColumnNdx].MapType = core.TWOSIDES + columns[testCase.onlyToDBColumnNdx].MapType = schemas.TWOSIDES } } } @@ -69,7 +69,7 @@ func BenchmarkColumnsStringGeneration(b *testing.B) { if testCase.onlyToDBColumnNdx >= 0 { columns := statement.RefTable.Columns() - columns[testCase.onlyToDBColumnNdx].MapType = core.ONLYTODB // !nemec784! Column must be skipped + columns[testCase.onlyToDBColumnNdx].MapType = schemas.ONLYTODB // !nemec784! Column must be skipped } b.StartTimer() @@ -88,7 +88,7 @@ func BenchmarkGetFlagForColumnWithICKey_ContainsKey(b *testing.B) { b.StopTimer() mapCols := make(map[string]bool) - cols := []*core.Column{ + cols := []*schemas.Column{ {Name: `ID`}, {Name: `IsDeleted`}, {Name: `Caption`}, @@ -122,7 +122,7 @@ func BenchmarkGetFlagForColumnWithICKey_EmptyMap(b *testing.B) { b.StopTimer() mapCols := make(map[string]bool) - cols := []*core.Column{ + cols := []*schemas.Column{ {Name: `ID`}, {Name: `IsDeleted`}, {Name: `Caption`}, diff --git a/tag.go b/tag.go index 2add6485..92fcdb5e 100644 --- a/tag.go +++ b/tag.go @@ -11,15 +11,36 @@ import ( "strings" "time" - "xorm.io/core" + "xorm.io/xorm/schemas" ) +func splitTag(tag string) (tags []string) { + tag = strings.TrimSpace(tag) + var hasQuote = false + var lastIdx = 0 + for i, t := range tag { + if t == '\'' { + hasQuote = !hasQuote + } else if t == ' ' { + if lastIdx < i && !hasQuote { + tags = append(tags, strings.TrimSpace(tag[lastIdx:i])) + lastIdx = i + 1 + } + } + } + if lastIdx < len(tag) { + tags = append(tags, strings.TrimSpace(tag[lastIdx:])) + } + return +} + +// tagContext represents a context for xorm tag parse. type tagContext struct { tagName string params []string preTag, nextTag string - table *core.Table - col *core.Column + table *schemas.Table + col *schemas.Column fieldValue reflect.Value isIndex bool isUnique bool @@ -59,7 +80,7 @@ var ( ) func init() { - for k := range core.SqlTypes { + for k := range schemas.SqlTypes { defaultTagHandlers[k] = SQLTypeTagHandler } } @@ -71,13 +92,13 @@ func IgnoreTagHandler(ctx *tagContext) error { // OnlyFromDBTagHandler describes mapping direction tag handler func OnlyFromDBTagHandler(ctx *tagContext) error { - ctx.col.MapType = core.ONLYFROMDB + ctx.col.MapType = schemas.ONLYFROMDB return nil } // OnlyToDBTagHandler describes mapping direction tag handler func OnlyToDBTagHandler(ctx *tagContext) error { - ctx.col.MapType = core.ONLYTODB + ctx.col.MapType = schemas.ONLYTODB return nil } @@ -177,7 +198,7 @@ func DeletedTagHandler(ctx *tagContext) error { // IndexTagHandler describes index tag handler func IndexTagHandler(ctx *tagContext) error { if len(ctx.params) > 0 { - ctx.indexNames[ctx.params[0]] = core.IndexType + ctx.indexNames[ctx.params[0]] = schemas.IndexType } else { ctx.isIndex = true } @@ -187,7 +208,7 @@ func IndexTagHandler(ctx *tagContext) error { // UniqueTagHandler describes unique tag handler func UniqueTagHandler(ctx *tagContext) error { if len(ctx.params) > 0 { - ctx.indexNames[ctx.params[0]] = core.UniqueType + ctx.indexNames[ctx.params[0]] = schemas.UniqueType } else { ctx.isUnique = true } @@ -204,16 +225,16 @@ func CommentTagHandler(ctx *tagContext) error { // SQLTypeTagHandler describes SQL Type tag handler func SQLTypeTagHandler(ctx *tagContext) error { - ctx.col.SQLType = core.SQLType{Name: ctx.tagName} + ctx.col.SQLType = schemas.SQLType{Name: ctx.tagName} if len(ctx.params) > 0 { - if ctx.tagName == core.Enum { + if ctx.tagName == schemas.Enum { ctx.col.EnumOptions = make(map[string]int) for k, v := range ctx.params { v = strings.TrimSpace(v) v = strings.Trim(v, "'") ctx.col.EnumOptions[v] = k } - } else if ctx.tagName == core.Set { + } else if ctx.tagName == schemas.Set { ctx.col.SetOptions = make(map[string]int) for k, v := range ctx.params { v = strings.TrimSpace(v) diff --git a/tag_extends_test.go b/tag_extends_test.go index b23b8181..80540b57 100644 --- a/tag_extends_test.go +++ b/tag_extends_test.go @@ -11,7 +11,7 @@ import ( "time" "github.com/stretchr/testify/assert" - "xorm.io/core" + "xorm.io/xorm/schemas" ) type tempUser struct { @@ -269,7 +269,7 @@ func TestExtends2(t *testing.T) { defer session.Close() // MSSQL deny insert identity column excep declare as below - if testEngine.Dialect().DBType() == core.MSSQL { + if testEngine.Dialect().DBType() == schemas.MSSQL { err = session.Begin() assert.NoError(t, err) _, err = session.Exec("SET IDENTITY_INSERT message ON") @@ -279,7 +279,7 @@ func TestExtends2(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 1, cnt) - if testEngine.Dialect().DBType() == core.MSSQL { + if testEngine.Dialect().DBType() == schemas.MSSQL { err = session.Commit() assert.NoError(t, err) } @@ -339,7 +339,7 @@ func TestExtends3(t *testing.T) { defer session.Close() // MSSQL deny insert identity column excep declare as below - if testEngine.Dialect().DBType() == core.MSSQL { + if testEngine.Dialect().DBType() == schemas.MSSQL { err = session.Begin() assert.NoError(t, err) _, err = session.Exec("SET IDENTITY_INSERT message ON") @@ -348,7 +348,7 @@ func TestExtends3(t *testing.T) { _, err = session.Insert(&msg) assert.NoError(t, err) - if testEngine.Dialect().DBType() == core.MSSQL { + if testEngine.Dialect().DBType() == schemas.MSSQL { err = session.Commit() assert.NoError(t, err) } @@ -433,7 +433,7 @@ func TestExtends4(t *testing.T) { defer session.Close() // MSSQL deny insert identity column excep declare as below - if testEngine.Dialect().DBType() == core.MSSQL { + if testEngine.Dialect().DBType() == schemas.MSSQL { err = session.Begin() assert.NoError(t, err) _, err = session.Exec("SET IDENTITY_INSERT message ON") @@ -442,7 +442,7 @@ func TestExtends4(t *testing.T) { _, err = session.Insert(&msg) assert.NoError(t, err) - if testEngine.Dialect().DBType() == core.MSSQL { + if testEngine.Dialect().DBType() == schemas.MSSQL { err = session.Commit() assert.NoError(t, err) } diff --git a/tag_id_test.go b/tag_id_test.go index dce5f688..a386b783 100644 --- a/tag_id_test.go +++ b/tag_id_test.go @@ -8,7 +8,7 @@ import ( "testing" "github.com/stretchr/testify/assert" - "xorm.io/core" + "xorm.io/xorm/names" ) type IDGonicMapper struct { @@ -20,7 +20,7 @@ func TestGonicMapperID(t *testing.T) { oldMapper := testEngine.GetColumnMapper() testEngine.UnMapType(rValue(new(IDGonicMapper)).Type()) - testEngine.SetMapper(core.LintGonicMapper) + testEngine.SetMapper(names.LintGonicMapper) defer func() { testEngine.UnMapType(rValue(new(IDGonicMapper)).Type()) testEngine.SetMapper(oldMapper) @@ -57,7 +57,7 @@ func TestSameMapperID(t *testing.T) { oldMapper := testEngine.GetColumnMapper() testEngine.UnMapType(rValue(new(IDSameMapper)).Type()) - testEngine.SetMapper(core.SameMapper{}) + testEngine.SetMapper(names.SameMapper{}) defer func() { testEngine.UnMapType(rValue(new(IDSameMapper)).Type()) testEngine.SetMapper(oldMapper) diff --git a/tag_test.go b/tag_test.go index 979ba929..894addac 100644 --- a/tag_test.go +++ b/tag_test.go @@ -11,7 +11,7 @@ import ( "time" "github.com/stretchr/testify/assert" - "xorm.io/core" + "xorm.io/xorm/schemas" ) type UserCU struct { @@ -203,7 +203,7 @@ func TestAutoIncrTag(t *testing.T) { func TestTagComment(t *testing.T) { assert.NoError(t, prepareEngine()) // FIXME: only support mysql - if testEngine.Dialect().DriverName() != core.MYSQL { + if testEngine.Dialect().DriverName() != schemas.MYSQL { return } diff --git a/types.go b/types.go index c76a5460..ee725dae 100644 --- a/types.go +++ b/types.go @@ -7,10 +7,10 @@ package xorm import ( "reflect" - "xorm.io/core" + "xorm.io/xorm/schemas" ) var ( - ptrPkType = reflect.TypeOf(&core.PK{}) - pkType = reflect.TypeOf(core.PK{}) + ptrPkType = reflect.TypeOf(&schemas.PK{}) + pkType = reflect.TypeOf(schemas.PK{}) ) diff --git a/types_test.go b/types_test.go index c979ded4..1e21907c 100644 --- a/types_test.go +++ b/types_test.go @@ -10,7 +10,7 @@ import ( "testing" "github.com/stretchr/testify/assert" - "xorm.io/core" + "xorm.io/xorm/schemas" ) func TestArrayField(t *testing.T) { @@ -137,8 +137,8 @@ type ConvStruct struct { Conv ConvString Conv2 *ConvString Cfg1 ConvConfig - Cfg2 *ConvConfig `xorm:"TEXT"` - Cfg3 core.Conversion `xorm:"BLOB"` + Cfg2 *ConvConfig `xorm:"TEXT"` + Cfg3 Conversion `xorm:"BLOB"` Slice SliceType } @@ -267,7 +267,7 @@ type Status struct { } var ( - _ core.Conversion = &Status{} + _ Conversion = &Status{} Registered Status = Status{"Registered", "white"} Approved Status = Status{"Approved", "green"} Removed Status = Status{"Removed", "red"} @@ -311,7 +311,7 @@ func TestCustomType2(t *testing.T) { session := testEngine.NewSession() defer session.Close() - if testEngine.Dialect().DBType() == core.MSSQL { + if testEngine.Dialect().DBType() == schemas.MSSQL { err = session.Begin() assert.NoError(t, err) _, err = session.Exec("set IDENTITY_INSERT " + tableName + " on") @@ -322,7 +322,7 @@ func TestCustomType2(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 1, cnt) - if testEngine.Dialect().DBType() == core.MSSQL { + if testEngine.Dialect().DBType() == schemas.MSSQL { err = session.Commit() assert.NoError(t, err) } diff --git a/xorm.go b/xorm.go index e1c83b56..e7e98739 100644 --- a/xorm.go +++ b/xorm.go @@ -15,7 +15,12 @@ import ( "sync" "time" - "xorm.io/core" + "xorm.io/xorm/caches" + "xorm.io/xorm/core" + "xorm.io/xorm/dialects" + "xorm.io/xorm/log" + "xorm.io/xorm/names" + "xorm.io/xorm/schemas" ) const ( @@ -23,44 +28,14 @@ const ( Version string = "0.8.0.1015" ) -func regDrvsNDialects() bool { - providedDrvsNDialects := map[string]struct { - dbType core.DbType - getDriver func() core.Driver - getDialect func() core.Dialect - }{ - "mssql": {"mssql", func() core.Driver { return &odbcDriver{} }, func() core.Dialect { return &mssql{} }}, - "odbc": {"mssql", func() core.Driver { return &odbcDriver{} }, func() core.Dialect { return &mssql{} }}, // !nashtsai! TODO change this when supporting MS Access - "mysql": {"mysql", func() core.Driver { return &mysqlDriver{} }, func() core.Dialect { return &mysql{} }}, - "mymysql": {"mysql", func() core.Driver { return &mymysqlDriver{} }, func() core.Dialect { return &mysql{} }}, - "postgres": {"postgres", func() core.Driver { return &pqDriver{} }, func() core.Dialect { return &postgres{} }}, - "pgx": {"postgres", func() core.Driver { return &pqDriverPgx{} }, func() core.Dialect { return &postgres{} }}, - "sqlite3": {"sqlite3", func() core.Driver { return &sqlite3Driver{} }, func() core.Dialect { return &sqlite3{} }}, - "oci8": {"oracle", func() core.Driver { return &oci8Driver{} }, func() core.Dialect { return &oracle{} }}, - "goracle": {"oracle", func() core.Driver { return &goracleDriver{} }, func() core.Dialect { return &oracle{} }}, - } - - for driverName, v := range providedDrvsNDialects { - if driver := core.QueryDriver(driverName); driver == nil { - core.RegisterDriver(driverName, v.getDriver()) - core.RegisterDialect(v.dbType, v.getDialect) - } - } - return true -} - func close(engine *Engine) { engine.Close() } -func init() { - regDrvsNDialects() -} - // NewEngine new a db manager according to the parameter. Currently support four // drivers func NewEngine(driverName string, dataSourceName string) (*Engine, error) { - driver := core.QueryDriver(driverName) + driver := dialects.QueryDriver(driverName) if driver == nil { return nil, fmt.Errorf("Unsupported driver name: %v", driverName) } @@ -70,9 +45,9 @@ func NewEngine(driverName string, dataSourceName string) (*Engine, error) { return nil, err } - dialect := core.QueryDialect(uri.DbType) + dialect := dialects.QueryDialect(uri.DBType) if dialect == nil { - return nil, fmt.Errorf("Unsupported dialect type: %v", uri.DbType) + return nil, fmt.Errorf("Unsupported dialect type: %v", uri.DBType) } db, err := core.Open(driverName, dataSourceName) @@ -88,32 +63,32 @@ func NewEngine(driverName string, dataSourceName string) (*Engine, error) { engine := &Engine{ db: db, dialect: dialect, - Tables: make(map[reflect.Type]*core.Table), + Tables: make(map[reflect.Type]*schemas.Table), mutex: &sync.RWMutex{}, TagIdentifier: "xorm", TZLocation: time.Local, tagHandlers: defaultTagHandlers, - cachers: make(map[string]core.Cacher), + cachers: make(map[string]caches.Cacher), defaultContext: context.Background(), } - if uri.DbType == core.SQLITE { + if uri.DBType == schemas.SQLITE { engine.DatabaseTZ = time.UTC } else { engine.DatabaseTZ = time.Local } - logger := NewSimpleLogger(os.Stdout) - logger.SetLevel(core.LOG_INFO) + logger := log.NewSimpleLogger(os.Stdout) + logger.SetLevel(log.LOG_INFO) engine.SetLogger(logger) - engine.SetMapper(core.NewCacheMapper(new(core.SnakeMapper))) + engine.SetMapper(names.NewCacheMapper(new(names.SnakeMapper))) runtime.SetFinalizer(engine, close) return engine, nil } -// NewEngineWithParams new a db manager with params. The params will be passed to dialect. +// NewEngineWithParams new a db manager with params. The params will be passed to dialects. func NewEngineWithParams(driverName string, dataSourceName string, params map[string]string) (*Engine, error) { engine, err := NewEngine(driverName, dataSourceName) engine.dialect.SetParams(params) diff --git a/xorm_test.go b/xorm_test.go index 21715256..2a24edb3 100644 --- a/xorm_test.go +++ b/xorm_test.go @@ -8,7 +8,6 @@ import ( "database/sql" "flag" "fmt" - "log" "os" "strings" "testing" @@ -18,7 +17,10 @@ import ( _ "github.com/lib/pq" _ "github.com/mattn/go-sqlite3" _ "github.com/ziutek/mymysql/godrv" - "xorm.io/core" + "xorm.io/xorm/caches" + "xorm.io/xorm/log" + "xorm.io/xorm/names" + "xorm.io/xorm/schemas" ) var ( @@ -30,14 +32,14 @@ var ( showSQL = flag.Bool("show_sql", true, "show generated SQLs") ptrConnStr = flag.String("conn_str", "./test.db?cache=shared&mode=rwc", "test database connection string") mapType = flag.String("map_type", "snake", "indicate the name mapping") - cache = flag.Bool("cache", false, "if enable cache") + cacheFlag = flag.Bool("cache", false, "if enable cache") cluster = flag.Bool("cluster", false, "if this is a cluster") splitter = flag.String("splitter", ";", "the splitter on connstr for cluster") schema = flag.String("schema", "", "specify the schema") ignoreSelectUpdate = flag.Bool("ignore_select_update", false, "ignore select update if implementation difference, only for tidb") - tableMapper core.IMapper - colMapper core.IMapper + tableMapper names.Mapper + colMapper names.Mapper ) func createEngine(dbType, connStr string) error { @@ -46,7 +48,7 @@ func createEngine(dbType, connStr string) error { if !*cluster { switch strings.ToLower(dbType) { - case core.MSSQL: + case schemas.MSSQL: db, err := sql.Open(dbType, strings.Replace(connStr, "xorm_test", "master", -1)) if err != nil { return err @@ -56,7 +58,7 @@ func createEngine(dbType, connStr string) error { } db.Close() *ignoreSelectUpdate = true - case core.POSTGRES: + case schemas.POSTGRES: db, err := sql.Open(dbType, connStr) if err != nil { return err @@ -79,7 +81,7 @@ func createEngine(dbType, connStr string) error { } db.Close() *ignoreSelectUpdate = true - case core.MYSQL: + case schemas.MYSQL: db, err := sql.Open(dbType, strings.Replace(connStr, "xorm_test", "mysql", -1)) if err != nil { return err @@ -107,20 +109,20 @@ func createEngine(dbType, connStr string) error { testEngine.SetSchema(*schema) } testEngine.ShowSQL(*showSQL) - testEngine.SetLogLevel(core.LOG_DEBUG) - if *cache { - cacher := NewLRUCacher(NewMemoryStore(), 100000) + testEngine.SetLogLevel(log.LOG_DEBUG) + if *cacheFlag { + cacher := caches.NewLRUCacher(caches.NewMemoryStore(), 100000) testEngine.SetDefaultCacher(cacher) } if len(*mapType) > 0 { switch *mapType { case "snake": - testEngine.SetMapper(core.SnakeMapper{}) + testEngine.SetMapper(names.SnakeMapper{}) case "same": - testEngine.SetMapper(core.SameMapper{}) + testEngine.SetMapper(names.SameMapper{}) case "gonic": - testEngine.SetMapper(core.LintGonicMapper) + testEngine.SetMapper(names.LintGonicMapper) } } } @@ -158,7 +160,7 @@ func TestMain(m *testing.M) { } } else { if ptrConnStr == nil { - log.Fatal("you should indicate conn string") + fmt.Println("you should indicate conn string") return } connString = *ptrConnStr @@ -175,7 +177,7 @@ func TestMain(m *testing.M) { fmt.Println("testing", dbType, connString) if err := prepareEngine(); err != nil { - log.Fatal(err) + fmt.Println(err) return } -- 2.40.1 From fb440d015a2aaedb9930ebf08572317ea5580445 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 24 Feb 2020 13:28:34 +0800 Subject: [PATCH 2/4] update go.mod --- go.mod | 2 +- go.sum | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 6d8b58f4..07bccdc6 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,6 @@ require ( github.com/mattn/go-sqlite3 v1.10.0 github.com/stretchr/testify v1.4.0 github.com/ziutek/mymysql v1.5.4 + google.golang.org/appengine v1.6.0 // indirect xorm.io/builder v0.3.6 - xorm.io/core v0.7.2 ) diff --git a/go.sum b/go.sum index 2102cc5b..4666c8ae 100644 --- a/go.sum +++ b/go.sum @@ -145,5 +145,3 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= xorm.io/builder v0.3.6 h1:ha28mQ2M+TFx96Hxo+iq6tQgnkC9IZkM6D8w9sKHHF8= xorm.io/builder v0.3.6/go.mod h1:LEFAPISnRzG+zxaxj2vPicRwz67BdhFreKg8yv8/TgU= -xorm.io/core v0.7.2 h1:mEO22A2Z7a3fPaZMk6gKL/jMD80iiyNwRrX5HOv3XLw= -xorm.io/core v0.7.2/go.mod h1:jJfd0UAEzZ4t87nbQYtVjmqpIODugN6PD2D9E+dJvdM= -- 2.40.1 From 1837d652a0d3d17da785f2a12487ef7746b30bcb Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 24 Feb 2020 13:54:12 +0800 Subject: [PATCH 3/4] Improve fmt --- caches/cache_lru.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/caches/cache_lru.go b/caches/cache_lru.go index fe9c7c53..6b45ac94 100644 --- a/caches/cache_lru.go +++ b/caches/cache_lru.go @@ -266,11 +266,11 @@ type sqlNode struct { } func genSQLKey(sql string, args interface{}) string { - return fmt.Sprintf("%v-%v", sql, args) + return fmt.Sprintf("%s-%v", sql, args) } func genID(prefix string, id string) string { - return fmt.Sprintf("%v-%v", prefix, id) + return fmt.Sprintf("%s-%s", prefix, id) } func newIDNode(tbName string, id string) *idNode { -- 2.40.1 From 01174e9f7161874744b576d743fa7435183e781c Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 24 Feb 2020 14:26:57 +0800 Subject: [PATCH 4/4] Fix test --- statement.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/statement.go b/statement.go index e61b76b7..87cab7cc 100644 --- a/statement.go +++ b/statement.go @@ -872,6 +872,10 @@ func (statement *Statement) genUniqueSQL() []string { func (statement *Statement) genDelIndexSQL() []string { var sqls []string tbName := statement.TableName() + idx := strings.Index(tbName, ".") + if idx > -1 { + tbName = tbName[idx+1:] + } idxPrefixName := strings.Replace(tbName, `"`, "", -1) idxPrefixName = strings.Replace(idxPrefixName, `.`, "_", -1) for idxName, index := range statement.RefTable.Indexes { -- 2.40.1