From a18c751d4449c2033dc8022273dc05b24b3497b8 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 8 Apr 2014 20:57:04 +0800 Subject: [PATCH 001/135] init core after sperated repository --- benchmark.sh | 1 + cache.go | 77 ++++++ column.go | 113 +++++++++ converstion.go | 8 + db.go | 566 +++++++++++++++++++++++++++++++++++++++++++ db_test.go | 634 +++++++++++++++++++++++++++++++++++++++++++++++++ dialect.go | 144 +++++++++++ driver.go | 23 ++ filter.go | 42 ++++ index.go | 25 ++ mapper.go | 176 ++++++++++++++ pk.go | 25 ++ pk_test.go | 22 ++ table.go | 94 ++++++++ type.go | 288 ++++++++++++++++++++++ 15 files changed, 2238 insertions(+) create mode 100755 benchmark.sh create mode 100644 cache.go create mode 100644 column.go create mode 100644 converstion.go create mode 100644 db.go create mode 100644 db_test.go create mode 100644 dialect.go create mode 100644 driver.go create mode 100644 filter.go create mode 100644 index.go create mode 100644 mapper.go create mode 100644 pk.go create mode 100644 pk_test.go create mode 100644 table.go create mode 100644 type.go diff --git a/benchmark.sh b/benchmark.sh new file mode 100755 index 00000000..eab9e57e --- /dev/null +++ b/benchmark.sh @@ -0,0 +1 @@ +go test -v -bench=. -run=XXX diff --git a/cache.go b/cache.go new file mode 100644 index 00000000..bedd22a7 --- /dev/null +++ b/cache.go @@ -0,0 +1,77 @@ +package core + +import ( + "encoding/json" + "errors" + "fmt" + "time" +) + +const ( + // default cache expired time + CacheExpired = 60 * time.Minute + // not use now + CacheMaxMemory = 256 + // evey ten minutes to clear all expired nodes + CacheGcInterval = 10 * time.Minute + // each time when gc to removed max nodes + CacheGcMaxRemoved = 20 +) + +// CacheStore is a interface to store cache +type CacheStore interface { + // key is primary key or composite primary key or unique key's value + // 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 []PK) (string, error) { + b, err := json.Marshal(ids) + if err != nil { + return "", err + } + return string(b), nil +} + +func decodeIds(s string) ([]PK, error) { + pks := make([]PK, 0) + err := json.Unmarshal([]byte(s), &pks) + return pks, err +} + +func GetCacheSql(m Cacher, tableName, sql string, args interface{}) ([]PK, error) { + bytes := m.GetIds(tableName, GenSqlKey(sql, args)) + if bytes == nil { + return nil, errors.New("Not Exist") + } + return decodeIds(bytes.(string)) +} + +func PutCacheSql(m Cacher, ids []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 +} + +func GenSqlKey(sql string, args interface{}) string { + return fmt.Sprintf("%v-%v", sql, args) +} diff --git a/column.go b/column.go new file mode 100644 index 00000000..8bea883e --- /dev/null +++ b/column.go @@ -0,0 +1,113 @@ +package core + +import ( + "fmt" + "reflect" + "strings" +) + +const ( + TWOSIDES = iota + 1 + ONLYTODB + ONLYFROMDB +) + +// database column +type Column struct { + Name string + FieldName string + SQLType SQLType + Length int + Length2 int + Nullable bool + Default string + Indexes map[string]bool + IsPrimaryKey bool + IsAutoIncrement bool + MapType int + IsCreated bool + IsUpdated bool + IsCascade bool + IsVersion bool + fieldPath []string +} + +func NewColumn(name, fieldName string, sqlType SQLType, len1, len2 int, nullable bool) *Column { + return &Column{name, fieldName, sqlType, len1, len2, nullable, "", make(map[string]bool), false, false, + TWOSIDES, false, false, false, false, nil} +} + +// generate column description string according dialect +func (col *Column) String(d Dialect) string { + sql := d.QuoteStr() + col.Name + d.QuoteStr() + " " + + sql += d.SqlType(col) + " " + + if col.IsPrimaryKey { + sql += "PRIMARY KEY " + if col.IsAutoIncrement { + sql += d.AutoIncrStr() + " " + } + } + + if col.Nullable { + sql += "NULL " + } else { + sql += "NOT NULL " + } + + if col.Default != "" { + sql += "DEFAULT " + col.Default + " " + } + + return sql +} + +func (col *Column) StringNoPk(d Dialect) string { + sql := d.QuoteStr() + col.Name + d.QuoteStr() + " " + + sql += d.SqlType(col) + " " + + if col.Nullable { + sql += "NULL " + } else { + sql += "NOT NULL " + } + + if col.Default != "" { + sql += "DEFAULT " + col.Default + " " + } + + return sql +} + +// return col'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) +} + +func (col *Column) ValueOfV(dataStruct *reflect.Value) (*reflect.Value, error) { + var fieldValue reflect.Value + var err error + if col.fieldPath == nil { + col.fieldPath = strings.Split(col.FieldName, ".") + } + + if len(col.fieldPath) == 1 { + fieldValue = dataStruct.FieldByName(col.FieldName) + } else if len(col.fieldPath) == 2 { + parentField := dataStruct.FieldByName(col.fieldPath[0]) + if parentField.IsValid() { + fieldValue = parentField.FieldByName(col.fieldPath[1]) + } else { + err = fmt.Errorf("field %v is not valid", col.FieldName) + } + } else { + err = fmt.Errorf("Unsupported mutliderive %v", col.FieldName) + } + if err != nil { + return nil, err + } + return &fieldValue, nil +} diff --git a/converstion.go b/converstion.go new file mode 100644 index 00000000..18522fbe --- /dev/null +++ b/converstion.go @@ -0,0 +1,8 @@ +package core + +// 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/db.go b/db.go new file mode 100644 index 00000000..ed97987a --- /dev/null +++ b/db.go @@ -0,0 +1,566 @@ +package core + +import ( + "database/sql" + "errors" + "reflect" + "regexp" + "sync" +) + +var ( + ErrNoMapPointer = errors.New("mp should be a map's pointer") + ErrNoStructPointer = errors.New("mp should be a map's pointer") +) + +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) + query = re.ReplaceAllStringFunc(query, func(src string) string { + args = append(args, vv.Elem().MapIndex(reflect.ValueOf(src[1:])).Interface()) + return "?" + }) + return query, args, nil +} + +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) + query = re.ReplaceAllStringFunc(query, func(src string) string { + args = append(args, vv.Elem().FieldByName(src[1:]).Interface()) + return "?" + }) + return query, args, nil +} + +type DB struct { + *sql.DB + Mapper IMapper +} + +func Open(driverName, dataSourceName string) (*DB, error) { + db, err := sql.Open(driverName, dataSourceName) + return &DB{db, NewCacheMapper(&SnakeMapper{})}, err +} + +func (db *DB) Query(query string, args ...interface{}) (*Rows, error) { + rows, err := db.DB.Query(query, args...) + return &Rows{rows, db.Mapper}, err +} + +func (db *DB) QueryMap(query string, mp interface{}) (*Rows, error) { + query, args, err := MapToSlice(query, mp) + if err != nil { + return nil, err + } + return db.Query(query, args...) +} + +func (db *DB) QueryStruct(query string, st interface{}) (*Rows, error) { + query, args, err := StructToSlice(query, st) + if err != nil { + return nil, err + } + return db.Query(query, args...) +} + +type Row struct { + *sql.Row + // One of these two will be non-nil: + err error // deferred error for easy chaining + Mapper IMapper +} + +func (row *Row) Scan(dest ...interface{}) error { + if row.err != nil { + return row.err + } + return row.Row.Scan(dest...) +} + +func (db *DB) QueryRow(query string, args ...interface{}) *Row { + row := db.DB.QueryRow(query, args...) + return &Row{row, nil, db.Mapper} +} + +func (db *DB) QueryRowMap(query string, mp interface{}) *Row { + query, args, err := MapToSlice(query, mp) + if err != nil { + return &Row{nil, err, db.Mapper} + } + return db.QueryRow(query, args...) +} + +func (db *DB) QueryRowStruct(query string, st interface{}) *Row { + query, args, err := StructToSlice(query, st) + if err != nil { + return &Row{nil, err, db.Mapper} + } + return db.QueryRow(query, args...) +} + +type Stmt struct { + *sql.Stmt + Mapper IMapper + names map[string]int +} + +func (db *DB) Prepare(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.Prepare(query) + if err != nil { + return nil, err + } + return &Stmt{stmt, db.Mapper, names}, nil +} + +func (s *Stmt) ExecMap(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.Exec(args...) +} + +func (s *Stmt) ExecStruct(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.Exec(args...) +} + +func (s *Stmt) Query(args ...interface{}) (*Rows, error) { + rows, err := s.Stmt.Query(args...) + if err != nil { + return nil, err + } + return &Rows{rows, s.Mapper}, nil +} + +func (s *Stmt) QueryMap(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.Query(args...) +} + +func (s *Stmt) QueryStruct(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) QueryRow(args ...interface{}) *Row { + row := s.Stmt.QueryRow(args...) + return &Row{row, nil, s.Mapper} +} + +func (s *Stmt) QueryRowMap(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"), s.Mapper} + } + + args := make([]interface{}, len(s.names)) + for k, i := range s.names { + args[i] = vv.Elem().MapIndex(reflect.ValueOf(k)).Interface() + } + + return s.QueryRow(args...) +} + +func (s *Stmt) QueryRowStruct(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"), s.Mapper} + } + + args := make([]interface{}, len(s.names)) + for k, i := range s.names { + args[i] = vv.Elem().FieldByName(k).Interface() + } + + return s.QueryRow(args...) +} + +var ( + re = regexp.MustCompile(`[?](\w+)`) +) + +// insert into (name) values (?) +// insert into (name) values (?name) +func (db *DB) ExecMap(query string, mp interface{}) (sql.Result, error) { + query, args, err := MapToSlice(query, mp) + if err != nil { + return nil, err + } + return db.DB.Exec(query, args...) +} + +func (db *DB) ExecStruct(query string, st interface{}) (sql.Result, error) { + query, args, err := StructToSlice(query, st) + if err != nil { + return nil, err + } + return db.DB.Exec(query, args...) +} + +type Rows struct { + *sql.Rows + Mapper IMapper +} + +// scan data to a struct's pointer according field index +func (rs *Rows) ScanStruct(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...) +} + +type EmptyScanner struct { +} + +func (EmptyScanner) Scan(src interface{}) error { + return nil +} + +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) ScanStruct2(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.Mapper.Table2Obj(name)) + if f.IsValid() { + newDest[j] = f.Addr().Interface() + } else { + newDest[j] = &v + } + } + + return rs.Rows.Scan(newDest...) +} + +type cacheStruct struct { + value reflect.Value + idx int +} + +var ( + reflectCache = make(map[reflect.Type]*cacheStruct) + reflectCacheMutex sync.RWMutex +) + +func ReflectNew(typ reflect.Type) reflect.Value { + reflectCacheMutex.RLock() + cs, ok := reflectCache[typ] + reflectCacheMutex.RUnlock() + + const newSize = 200 + + if !ok || cs.idx+1 > newSize-1 { + cs = &cacheStruct{reflect.MakeSlice(reflect.SliceOf(typ), newSize, newSize), 0} + reflectCacheMutex.Lock() + reflectCache[typ] = cs + reflectCacheMutex.Unlock() + } else { + reflectCacheMutex.Lock() + cs.idx = cs.idx + 1 + reflectCacheMutex.Unlock() + } + return cs.value.Index(cs.idx).Addr() +} + +// 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] = ReflectNew(vvv.Type().Elem()).Interface() + //v := reflect.New(vvv.Type().Elem()) + //newDest[i] = v.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 +} + +/*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)) + err = rs.ScanSlice(newDest) + if err != nil { + return err + } + + vvv := vv.Elem() + + for i, name := range cols { + vname := reflect.ValueOf(name) + vvv.SetMapIndex(vname, reflect.ValueOf(newDest[i]).Elem()) + } + + return nil +}*/ + +type Tx struct { + *sql.Tx + Mapper IMapper +} + +func (db *DB) Begin() (*Tx, error) { + tx, err := db.DB.Begin() + if err != nil { + return nil, err + } + return &Tx{tx, db.Mapper}, nil +} + +func (tx *Tx) Prepare(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.Prepare(query) + if err != nil { + return nil, err + } + return &Stmt{stmt, tx.Mapper, names}, nil +} + +func (tx *Tx) Stmt(stmt *Stmt) *Stmt { + // TODO: + return stmt +} + +func (tx *Tx) ExecMap(query string, mp interface{}) (sql.Result, error) { + query, args, err := MapToSlice(query, mp) + if err != nil { + return nil, err + } + return tx.Tx.Exec(query, args...) +} + +func (tx *Tx) ExecStruct(query string, st interface{}) (sql.Result, error) { + query, args, err := StructToSlice(query, st) + if err != nil { + return nil, err + } + return tx.Tx.Exec(query, args...) +} + +func (tx *Tx) Query(query string, args ...interface{}) (*Rows, error) { + rows, err := tx.Tx.Query(query, args...) + if err != nil { + return nil, err + } + return &Rows{rows, tx.Mapper}, nil +} + +func (tx *Tx) QueryMap(query string, mp interface{}) (*Rows, error) { + query, args, err := MapToSlice(query, mp) + if err != nil { + return nil, err + } + return tx.Query(query, args...) +} + +func (tx *Tx) QueryStruct(query string, st interface{}) (*Rows, error) { + query, args, err := StructToSlice(query, st) + if err != nil { + return nil, err + } + return tx.Query(query, args...) +} + +func (tx *Tx) QueryRow(query string, args ...interface{}) *Row { + row := tx.Tx.QueryRow(query, args...) + return &Row{row, nil, tx.Mapper} +} + +func (tx *Tx) QueryRowMap(query string, mp interface{}) *Row { + query, args, err := MapToSlice(query, mp) + if err != nil { + return &Row{nil, err, tx.Mapper} + } + return tx.QueryRow(query, args...) +} + +func (tx *Tx) QueryRowStruct(query string, st interface{}) *Row { + query, args, err := StructToSlice(query, st) + if err != nil { + return &Row{nil, err, tx.Mapper} + } + return tx.QueryRow(query, args...) +} diff --git a/db_test.go b/db_test.go new file mode 100644 index 00000000..9836a589 --- /dev/null +++ b/db_test.go @@ -0,0 +1,634 @@ +package core + +import ( + "errors" + "fmt" + "os" + "testing" + "time" + + _ "github.com/mattn/go-sqlite3" +) + +var ( + createTableSqlite3 = "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);" +) + +type User struct { + Id int64 + Name string + Title string + Age float32 + Alias string + NickName string + Created time.Time +} + +func BenchmarkOriQuery(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(createTableSqlite3) + 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 time.Time + 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() + } +} + +func BenchmarkStructQuery(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(createTableSqlite3) + 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.ScanStruct(&user) + if err != nil { + b.Error(err) + } + if user.Name != "xlw" { + fmt.Println(user) + b.Error(errors.New("name should be xlw")) + } + } + rows.Close() + } +} + +func BenchmarkStruct2Query(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(createTableSqlite3) + 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 = NewCacheMapper(&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.ScanStruct2(&user) + if err != nil { + b.Error(err) + } + if user.Name != "xlw" { + fmt.Println(user) + b.Error(errors.New("name should be xlw")) + } + } + rows.Close() + } +} + +func BenchmarkSliceInterfaceQuery(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(createTableSqlite3) + 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) + } + if slice[1].(string) != "xlw" { + fmt.Println(slice) + 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(createTableSqlite3) + 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() + os.Remove("./test.db") + db, err := Open("sqlite3", "./test.db") + if err != nil { + b.Error(err) + } + defer db.Close() + + _, err = db.Exec(createTableSqlite3) + 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" { + fmt.Println(slice) + b.Error(errors.New("name should be xlw")) + } + } + + rows.Close() + } +}*/ + +func BenchmarkMapInterfaceQuery(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(createTableSqlite3) + 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) + } + if m["name"].(string) != "xlw" { + fmt.Println(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(createTableSqlite3) + 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(createTableSqlite3) + 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() + os.Remove("./test.db") + db, err := Open("sqlite3", "./test.db") + if err != nil { + b.Error(err) + } + defer db.Close() + + _, err = db.Exec(createTableSqlite3) + 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() + os.Remove("./test.db") + db, err := Open("sqlite3", "./test.db") + if err != nil { + b.Error(err) + } + defer db.Close() + + _, err = db.Exec(createTableSqlite3) + 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) { + os.Remove("./test.db") + db, err := Open("sqlite3", "./test.db") + if err != nil { + t.Error(err) + } + defer db.Close() + + _, err = db.Exec(createTableSqlite3) + 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.ScanStruct2(&user) + if err != nil { + t.Error(err) + } + fmt.Println("--", user) + } +} + +func TestExecStruct(t *testing.T) { + os.Remove("./test.db") + db, err := Open("sqlite3", "./test.db") + if err != nil { + t.Error(err) + } + defer db.Close() + + _, err = db.Exec(createTableSqlite3) + if err != nil { + t.Error(err) + } + + user := User{Name: "xlw", + Title: "tester", + Age: 1.2, + Alias: "lunny", + NickName: "lunny xiao", + Created: 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.ScanStruct2(&user) + if err != nil { + t.Error(err) + } + fmt.Println("1--", user) + } +} + +func BenchmarkExecStruct(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(createTableSqlite3) + if err != nil { + b.Error(err) + } + + b.StartTimer() + + user := User{Name: "xlw", + Title: "tester", + Age: 1.2, + Alias: "lunny", + NickName: "lunny xiao", + Created: 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/dialect.go b/dialect.go new file mode 100644 index 00000000..ecba0844 --- /dev/null +++ b/dialect.go @@ -0,0 +1,144 @@ +package core + +import ( + "strings" + "time" +) + +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 +} + +// a dialect is a driver's wrapper +type Dialect interface { + Init(*Uri, string, string) error + URI() *Uri + DBType() dbType + SqlType(t *Column) string + SupportInsertMany() bool + QuoteStr() string + AutoIncrStr() string + SupportEngine() bool + SupportCharset() bool + IndexOnTable() bool + + IndexCheckSql(tableName, idxName string) (string, []interface{}) + TableCheckSql(tableName string) (string, []interface{}) + ColumnCheckSql(tableName, colName string) (string, []interface{}) + + GetColumns(tableName string) ([]string, map[string]*Column, error) + GetTables() ([]*Table, error) + GetIndexes(tableName string) (map[string]*Index, error) + + CreateTableSql(table *Table, tableName, storeEngine, charset string) string + Filters() []Filter + + DriverName() string + DataSourceName() string +} + +type Base struct { + dialect Dialect + driverName string + dataSourceName string + *Uri +} + +func (b *Base) Init(dialect Dialect, uri *Uri, drivername, dataSourceName string) error { + b.dialect = dialect + b.driverName, b.dataSourceName = drivername, dataSourceName + b.Uri = uri + return nil +} + +func (b *Base) URI() *Uri { + return b.Uri +} + +func (b *Base) DBType() dbType { + return b.Uri.DbType +} + +func (b *Base) DriverName() string { + return b.driverName +} + +func (b *Base) DataSourceName() string { + return b.dataSourceName +} + +func (b *Base) Quote(c string) string { + return b.dialect.QuoteStr() + c + b.dialect.QuoteStr() +} + +func (b *Base) CreateTableSql(table *Table, tableName, storeEngine, charset string) string { + var sql string + sql = "CREATE TABLE IF NOT EXISTS " + if tableName == "" { + tableName = table.Name + } + + sql += b.Quote(tableName) + " (" + + pkList := table.PrimaryKeys + + for _, colName := range table.ColumnsSeq() { + col := table.GetColumn(colName) + if col.IsPrimaryKey && len(pkList) == 1 { + sql += col.String(b.dialect) + } else { + sql += col.StringNoPk(b.dialect) + } + sql = strings.TrimSpace(sql) + sql += ", " + } + + if len(pkList) > 1 { + sql += "PRIMARY KEY ( " + sql += b.Quote(strings.Join(pkList, b.Quote(","))) + sql += " ), " + } + + sql = sql[:len(sql)-2] + ")" + if b.dialect.SupportEngine() && storeEngine != "" { + sql += " ENGINE=" + storeEngine + } + if b.dialect.SupportCharset() { + if charset == "" { + charset = b.dialect.URI().Charset + } + sql += " DEFAULT CHARSET " + charset + } + sql += ";" + return sql +} + +var ( + dialects = map[dbType]Dialect{} +) + +func RegisterDialect(dbName dbType, dialect Dialect) { + if dialect == nil { + panic("core: Register dialect is nil") + } + if _, dup := dialects[dbName]; dup { + panic("core: Register called twice for dialect " + dbName) + } + dialects[dbName] = dialect +} + +func QueryDialect(dbName dbType) Dialect { + return dialects[dbName] +} diff --git a/driver.go b/driver.go new file mode 100644 index 00000000..b008a0ac --- /dev/null +++ b/driver.go @@ -0,0 +1,23 @@ +package core + +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] +} diff --git a/filter.go b/filter.go new file mode 100644 index 00000000..072bc589 --- /dev/null +++ b/filter.go @@ -0,0 +1,42 @@ +package core + +import "strings" + +// Filter is an interface to filter SQL +type Filter interface { + Do(sql string, dialect Dialect, table *Table) string +} + +// QuoteFilter filter SQL replace ` to database's own quote character +type QuoteFilter struct { +} + +func (s *QuoteFilter) Do(sql string, dialect Dialect, table *Table) string { + return strings.Replace(sql, "`", dialect.QuoteStr(), -1) +} + +// 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.QuoteStr() + content + q.dialect.QuoteStr() +} + +func (i *IdFilter) Do(sql string, dialect Dialect, table *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 +} diff --git a/index.go b/index.go new file mode 100644 index 00000000..eb088850 --- /dev/null +++ b/index.go @@ -0,0 +1,25 @@ +package core + +const ( + IndexType = iota + 1 + UniqueType +) + +// database index +type Index struct { + Name string + Type int + Cols []string +} + +// add columns which will be composite index +func (index *Index) AddColumn(cols ...string) { + for _, col := range cols { + index.Cols = append(index.Cols, col) + } +} + +// new an index +func NewIndex(name string, indexType int) *Index { + return &Index{name, indexType, make([]string, 0)} +} diff --git a/mapper.go b/mapper.go new file mode 100644 index 00000000..fb209a7c --- /dev/null +++ b/mapper.go @@ -0,0 +1,176 @@ +package core + +import ( + "strings" + "sync" +) + +// name translation between struct, fields names and table, column names +type IMapper interface { + Obj2Table(string) string + Table2Obj(string) string +} + +type CacheMapper struct { + oriMapper IMapper + obj2tableCache map[string]string + obj2tableMutex sync.RWMutex + table2objCache map[string]string + table2objMutex sync.RWMutex +} + +func NewCacheMapper(mapper IMapper) *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 pascal2Sql(s string) (d string) { + d = "" + lastIdx := 0 + for i := 0; i < len(s); i++ { + if s[i] >= 'A' && s[i] <= 'Z' { + if lastIdx < i { + d += s[lastIdx+1 : i] + } + if i != 0 { + d += "_" + } + d += string(s[i] + 32) + lastIdx = i + } + } + d += s[lastIdx+1:] + return +}*/ + +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) +} + +// provide prefix table name support +type PrefixMapper struct { + Mapper IMapper + 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 IMapper, prefix string) PrefixMapper { + return PrefixMapper{mapper, prefix} +} + +// provide suffix table name support +type SuffixMapper struct { + Mapper IMapper + Suffix string +} + +func (mapper SuffixMapper) Obj2Table(name string) string { + return mapper.Suffix + mapper.Mapper.Obj2Table(name) +} + +func (mapper SuffixMapper) Table2Obj(name string) string { + return mapper.Mapper.Table2Obj(name[len(mapper.Suffix):]) +} + +func NewSuffixMapper(mapper IMapper, suffix string) SuffixMapper { + return SuffixMapper{mapper, suffix} +} diff --git a/pk.go b/pk.go new file mode 100644 index 00000000..61d1371e --- /dev/null +++ b/pk.go @@ -0,0 +1,25 @@ +package core + +import ( + "encoding/json" +) + +type PK []interface{} + +func NewPK(pks ...interface{}) *PK { + p := PK(pks) + return &p +} + +func (p *PK) ToString() (string, error) { + bs, err := json.Marshal(*p) + if err != nil { + return "", nil + } + + return string(bs), nil +} + +func (p *PK) FromString(content string) error { + return json.Unmarshal([]byte(content), p) +} diff --git a/pk_test.go b/pk_test.go new file mode 100644 index 00000000..5245e574 --- /dev/null +++ b/pk_test.go @@ -0,0 +1,22 @@ +package core + +import ( + "fmt" + "testing" +) + +func TestPK(t *testing.T) { + p := NewPK(1, 3, "string") + str, err := p.ToString() + if err != nil { + t.Error(err) + } + fmt.Println(str) + + s := &PK{} + err = s.FromString(str) + if err != nil { + t.Error(err) + } + fmt.Println(s) +} diff --git a/table.go b/table.go new file mode 100644 index 00000000..554d5a5c --- /dev/null +++ b/table.go @@ -0,0 +1,94 @@ +package core + +import ( + "reflect" + "strings" +) + +// database table +type Table struct { + Name string + Type reflect.Type + columnsSeq []string + columns map[string]*Column + Indexes map[string]*Index + PrimaryKeys []string + AutoIncrement string + Created map[string]bool + Updated string + Version string +} + +func (table *Table) Columns() map[string]*Column { + return table.columns +} + +func (table *Table) ColumnsSeq() []string { + return table.columnsSeq +} + +func NewEmptyTable() *Table { + return &Table{columnsSeq: make([]string, 0), + columns: make(map[string]*Column), + Indexes: make(map[string]*Index), + Created: make(map[string]bool), + PrimaryKeys: make([]string, 0), + } +} + +func NewTable(name string, t reflect.Type) *Table { + return &Table{Name: name, Type: t, + columnsSeq: make([]string, 0), + columns: make(map[string]*Column), + Indexes: make(map[string]*Index), + Created: make(map[string]bool), + PrimaryKeys: make([]string, 0), + } +} + +func (table *Table) GetColumn(name string) *Column { + return table.columns[strings.ToLower(name)] +} + +// if has primary key, return column +func (table *Table) PKColumns() []*Column { + columns := make([]*Column, 0) + for _, name := range table.PrimaryKeys { + columns = append(columns, table.GetColumn(name)) + } + return columns +} + +func (table *Table) AutoIncrColumn() *Column { + return table.GetColumn(table.AutoIncrement) +} + +func (table *Table) VersionColumn() *Column { + return table.GetColumn(table.Version) +} + +// add a column to table +func (table *Table) AddColumn(col *Column) { + table.columnsSeq = append(table.columnsSeq, col.Name) + table.columns[strings.ToLower(col.Name)] = 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.IsVersion { + table.Version = col.Name + } +} + +// add an index or an unique to table +func (table *Table) AddIndex(index *Index) { + table.Indexes[index.Name] = index +} diff --git a/type.go b/type.go new file mode 100644 index 00000000..68e2a23c --- /dev/null +++ b/type.go @@ -0,0 +1,288 @@ +package core + +import ( + "reflect" + "sort" + "strings" + "time" +) + +const ( + POSTGRES = "postgres" + SQLITE = "sqlite3" + MYSQL = "mysql" + MSSQL = "mssql" + ORACLE = "oracle" +) + +// xorm SQL types +type SQLType struct { + Name string + DefaultLength int + DefaultLength2 int +} + +func (s *SQLType) IsText() bool { + return s.Name == Char || s.Name == Varchar || s.Name == TinyText || + s.Name == Text || s.Name == MediumText || s.Name == LongText +} + +func (s *SQLType) IsBlob() bool { + return (s.Name == TinyBlob) || (s.Name == Blob) || + s.Name == MediumBlob || s.Name == LongBlob || + s.Name == Binary || s.Name == VarBinary || s.Name == Bytea +} + +var ( + Bit = "BIT" + TinyInt = "TINYINT" + SmallInt = "SMALLINT" + MediumInt = "MEDIUMINT" + Int = "INT" + Integer = "INTEGER" + BigInt = "BIGINT" + + Char = "CHAR" + Varchar = "VARCHAR" + TinyText = "TINYTEXT" + Text = "TEXT" + MediumText = "MEDIUMTEXT" + LongText = "LONGTEXT" + + Date = "DATE" + DateTime = "DATETIME" + Time = "TIME" + TimeStamp = "TIMESTAMP" + TimeStampz = "TIMESTAMPZ" + + Decimal = "DECIMAL" + Numeric = "NUMERIC" + + Real = "REAL" + Float = "FLOAT" + Double = "DOUBLE" + + Binary = "BINARY" + VarBinary = "VARBINARY" + TinyBlob = "TINYBLOB" + Blob = "BLOB" + MediumBlob = "MEDIUMBLOB" + LongBlob = "LONGBLOB" + Bytea = "BYTEA" + + Bool = "BOOL" + + Serial = "SERIAL" + BigSerial = "BIGSERIAL" + + SqlTypes = map[string]bool{ + Bit: true, + TinyInt: true, + SmallInt: true, + MediumInt: true, + Int: true, + Integer: true, + BigInt: true, + + Char: true, + Varchar: true, + TinyText: true, + Text: true, + MediumText: true, + LongText: true, + + Date: true, + DateTime: true, + Time: true, + TimeStamp: true, + TimeStampz: true, + + Decimal: true, + Numeric: true, + + Binary: true, + VarBinary: true, + Real: true, + Float: true, + Double: true, + TinyBlob: true, + Blob: true, + MediumBlob: true, + LongBlob: true, + Bytea: true, + + Bool: true, + + Serial: true, + BigSerial: true, + } + + 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 comparision +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) + + 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) +) + +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 == reflect.TypeOf(c_TIME_DEFAULT) { + st = SQLType{DateTime, 0, 0} + } else { + // TODO need to handle association struct + st = SQLType{Text, 0, 0} + } + case reflect.Ptr: + st, _ = ptrType2SQLType(t) + default: + st = SQLType{Text, 0, 0} + } + return +} + +func ptrType2SQLType(t reflect.Type) (st SQLType, has bool) { + has = true + + switch t { + case reflect.TypeOf(&c_EMPTY_STRING): + st = SQLType{Varchar, 255, 0} + return + case reflect.TypeOf(&c_BOOL_DEFAULT): + st = SQLType{Bool, 0, 0} + case reflect.TypeOf(&c_COMPLEX64_DEFAULT), reflect.TypeOf(&c_COMPLEX128_DEFAULT): + st = SQLType{Varchar, 64, 0} + case reflect.TypeOf(&c_FLOAT32_DEFAULT): + st = SQLType{Float, 0, 0} + case reflect.TypeOf(&c_FLOAT64_DEFAULT): + st = SQLType{Double, 0, 0} + case reflect.TypeOf(&c_INT64_DEFAULT), reflect.TypeOf(&c_UINT64_DEFAULT): + st = SQLType{BigInt, 0, 0} + case reflect.TypeOf(&c_TIME_DEFAULT): + st = SQLType{DateTime, 0, 0} + case reflect.TypeOf(&c_INT_DEFAULT), reflect.TypeOf(&c_INT32_DEFAULT), reflect.TypeOf(&c_INT8_DEFAULT), reflect.TypeOf(&c_INT16_DEFAULT), reflect.TypeOf(&c_UINT_DEFAULT), reflect.TypeOf(&c_UINT32_DEFAULT), reflect.TypeOf(&c_UINT8_DEFAULT), reflect.TypeOf(&c_UINT16_DEFAULT): + st = SQLType{Int, 0, 0} + default: + has = false + } + 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, Varchar, TinyText, Text, MediumText, LongText: + return reflect.TypeOf("") + case TinyBlob, Blob, LongBlob, Bytea, Binary, MediumBlob, VarBinary: + return reflect.TypeOf([]byte{}) + case Bool: + return reflect.TypeOf(true) + case DateTime, Date, Time, TimeStamp, TimeStampz: + return reflect.TypeOf(c_TIME_DEFAULT) + case Decimal, Numeric: + return reflect.TypeOf("") + default: + return reflect.TypeOf("") + } +} From 22c515f1e0d7314d9255951074ee753ab2984767 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 11 Apr 2014 15:38:09 +0800 Subject: [PATCH 002/135] fixed --- column.go | 3 ++- dialect.go | 11 +++++++++++ table.go | 1 + 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/column.go b/column.go index 8bea883e..4e179a14 100644 --- a/column.go +++ b/column.go @@ -30,11 +30,12 @@ type Column struct { IsCascade bool IsVersion bool fieldPath []string + DefaultIsEmpty bool } func NewColumn(name, fieldName string, sqlType SQLType, len1, len2 int, nullable bool) *Column { return &Column{name, fieldName, sqlType, len1, len2, nullable, "", make(map[string]bool), false, false, - TWOSIDES, false, false, false, false, nil} + TWOSIDES, false, false, false, false, nil, false} } // generate column description string according dialect diff --git a/dialect.go b/dialect.go index ecba0844..00fba638 100644 --- a/dialect.go +++ b/dialect.go @@ -1,6 +1,7 @@ package core import ( + "fmt" "strings" "time" ) @@ -29,6 +30,8 @@ type Dialect interface { SqlType(t *Column) string SupportInsertMany() bool QuoteStr() string + RollBackStr() string + DropTableSql(tableName string) string AutoIncrStr() string SupportEngine() bool SupportCharset() bool @@ -83,6 +86,14 @@ func (b *Base) Quote(c string) string { return b.dialect.QuoteStr() + c + b.dialect.QuoteStr() } +func (db *Base) RollBackStr() string { + return "ROLL BACK" +} + +func (db *Base) DropTableSql(tableName string) string { + return fmt.Sprintf("DROP TABLE IF EXISTS `%s`", tableName) +} + func (b *Base) CreateTableSql(table *Table, tableName, storeEngine, charset string) string { var sql string sql = "CREATE TABLE IF NOT EXISTS " diff --git a/table.go b/table.go index 554d5a5c..aadcc9a8 100644 --- a/table.go +++ b/table.go @@ -17,6 +17,7 @@ type Table struct { Created map[string]bool Updated string Version string + Cacher Cacher } func (table *Table) Columns() map[string]*Column { From dbcb6fe17335e78b5f14885bbdcdde08d0d6e485 Mon Sep 17 00:00:00 2001 From: Nash Tsai Date: Fri, 11 Apr 2014 21:05:08 +0800 Subject: [PATCH 003/135] make DbType public --- dialect.go | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/dialect.go b/dialect.go index 00fba638..2837d23e 100644 --- a/dialect.go +++ b/dialect.go @@ -6,10 +6,10 @@ import ( "time" ) -type dbType string +type DbType string type Uri struct { - DbType dbType + DbType DbType Proto string Host string Port string @@ -26,7 +26,7 @@ type Uri struct { type Dialect interface { Init(*Uri, string, string) error URI() *Uri - DBType() dbType + DBType() DbType SqlType(t *Column) string SupportInsertMany() bool QuoteStr() string @@ -70,7 +70,7 @@ func (b *Base) URI() *Uri { return b.Uri } -func (b *Base) DBType() dbType { +func (b *Base) DBType() DbType { return b.Uri.DbType } @@ -137,19 +137,16 @@ func (b *Base) CreateTableSql(table *Table, tableName, storeEngine, charset stri } var ( - dialects = map[dbType]Dialect{} + dialects = map[DbType]Dialect{} ) -func RegisterDialect(dbName dbType, dialect Dialect) { +func RegisterDialect(dbName DbType, dialect Dialect) { if dialect == nil { panic("core: Register dialect is nil") } - if _, dup := dialects[dbName]; dup { - panic("core: Register called twice for dialect " + dbName) - } - dialects[dbName] = dialect + dialects[dbName] = dialect // !nashtsai! allow override dialect } -func QueryDialect(dbName dbType) Dialect { +func QueryDialect(dbName DbType) Dialect { return dialects[dbName] } From 3dd33af2d66785075d1b2f00d9bc0306dceb9005 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 11 Apr 2014 23:32:10 +0800 Subject: [PATCH 004/135] add SeqFilter --- filter.go | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/filter.go b/filter.go index 072bc589..60caaf29 100644 --- a/filter.go +++ b/filter.go @@ -1,6 +1,9 @@ package core -import "strings" +import ( + "fmt" + "strings" +) // Filter is an interface to filter SQL type Filter interface { @@ -40,3 +43,22 @@ func (i *IdFilter) Do(sql string, dialect Dialect, table *Table) string { } return sql } + +// SeqFilter filter SQL replace ?, ? ... to $1, $2 ... +type SeqFilter struct { + Prefix string + Start int +} + +func (s *SeqFilter) Do(sql string, dialect Dialect, table *Table) string { + segs := strings.Split(sql, "?") + size := len(segs) + res := "" + for i, c := range segs { + if i < size-1 { + res += c + fmt.Sprintf("%s%v", s.Prefix, i+s.Start) + } + } + res += segs[size-1] + return res +} From 46546777ae6535e8ad2395ecfecd879bfa9eaf6f Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 13 Apr 2014 15:37:25 +0800 Subject: [PATCH 005/135] add some interface for dialect --- column.go | 20 ++++++++++++-------- dialect.go | 15 +++++++++++++++ 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/column.go b/column.go index 4e179a14..20b3ad01 100644 --- a/column.go +++ b/column.go @@ -51,10 +51,12 @@ func (col *Column) String(d Dialect) string { } } - if col.Nullable { - sql += "NULL " - } else { - sql += "NOT NULL " + if d.ShowCreateNull() { + if col.Nullable { + sql += "NULL " + } else { + sql += "NOT NULL " + } } if col.Default != "" { @@ -69,10 +71,12 @@ func (col *Column) StringNoPk(d Dialect) string { sql += d.SqlType(col) + " " - if col.Nullable { - sql += "NULL " - } else { - sql += "NOT NULL " + if d.ShowCreateNull() { + if col.Nullable { + sql += "NULL " + } else { + sql += "NOT NULL " + } } if col.Default != "" { diff --git a/dialect.go b/dialect.go index 2837d23e..8fcf78ff 100644 --- a/dialect.go +++ b/dialect.go @@ -30,12 +30,15 @@ type Dialect interface { SqlType(t *Column) string SupportInsertMany() bool QuoteStr() string + AndStr() string + EqStr() string RollBackStr() string DropTableSql(tableName string) string AutoIncrStr() string SupportEngine() bool SupportCharset() bool IndexOnTable() bool + ShowCreateNull() bool IndexCheckSql(tableName, idxName string) (string, []interface{}) TableCheckSql(tableName string) (string, []interface{}) @@ -78,6 +81,10 @@ func (b *Base) DriverName() string { return b.driverName } +func (b *Base) ShowCreateNull() bool { + return true +} + func (b *Base) DataSourceName() string { return b.dataSourceName } @@ -86,6 +93,14 @@ func (b *Base) Quote(c string) string { return b.dialect.QuoteStr() + c + b.dialect.QuoteStr() } +func (b *Base) AndStr() string { + return "AND" +} + +func (b *Base) EqStr() string { + return "=" +} + func (db *Base) RollBackStr() string { return "ROLL BACK" } From cbc40d3a01464569650fcdef48735fc77c53d521 Mon Sep 17 00:00:00 2001 From: Nash Tsai Date: Mon, 14 Apr 2014 15:30:31 +0800 Subject: [PATCH 006/135] public Driver interface --- driver.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/driver.go b/driver.go index b008a0ac..209e65c4 100644 --- a/driver.go +++ b/driver.go @@ -1,14 +1,14 @@ package core -type driver interface { +type Driver interface { Parse(string, string) (*Uri, error) } var ( - drivers = map[string]driver{} + drivers = map[string]Driver{} ) -func RegisterDriver(driverName string, driver driver) { +func RegisterDriver(driverName string, driver Driver) { if driver == nil { panic("core: Register driver is nil") } @@ -18,6 +18,6 @@ func RegisterDriver(driverName string, driver driver) { drivers[driverName] = driver } -func QueryDriver(driverName string) driver { +func QueryDriver(driverName string) Driver { return drivers[driverName] } From d029b1ca86a57132014867a99c15f6dc9839a0b6 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 15 Apr 2014 10:45:38 +0800 Subject: [PATCH 007/135] add OpenDialect --- dialect.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dialect.go b/dialect.go index 8fcf78ff..dc1eb1e3 100644 --- a/dialect.go +++ b/dialect.go @@ -55,6 +55,10 @@ type Dialect interface { DataSourceName() string } +func OpenDialect(dialect Dialect) (*DB, error) { + return Open(dialect.DriverName(), dialect.DataSourceName()) +} + type Base struct { dialect Dialect driverName string From fdb300db20167a57b08456932e72d2e76d66e870 Mon Sep 17 00:00:00 2001 From: Nash Tsai Date: Wed, 16 Apr 2014 01:11:50 +0800 Subject: [PATCH 008/135] add RegisteredDriverSize() --- driver.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/driver.go b/driver.go index 209e65c4..0f1020b4 100644 --- a/driver.go +++ b/driver.go @@ -21,3 +21,7 @@ func RegisterDriver(driverName string, driver Driver) { func QueryDriver(driverName string) Driver { return drivers[driverName] } + +func RegisteredDriverSize() int { + return len(drivers) +} From a3a11834dc83320f8f37b70d6ca52ed16126c32f Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 17 Apr 2014 10:13:51 +0800 Subject: [PATCH 009/135] improved --- dialect.go | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/dialect.go b/dialect.go index dc1eb1e3..becd448e 100644 --- a/dialect.go +++ b/dialect.go @@ -27,28 +27,32 @@ type Dialect interface { Init(*Uri, string, string) error URI() *Uri DBType() DbType - SqlType(t *Column) string - SupportInsertMany() bool + SqlType(*Column) string + QuoteStr() string AndStr() string + OrStr() string EqStr() string RollBackStr() string - DropTableSql(tableName string) string AutoIncrStr() string + + SupportInsertMany() bool SupportEngine() bool SupportCharset() bool IndexOnTable() bool ShowCreateNull() bool + DropTableSql(tableName string) string IndexCheckSql(tableName, idxName string) (string, []interface{}) TableCheckSql(tableName string) (string, []interface{}) ColumnCheckSql(tableName, colName string) (string, []interface{}) + CreateTableSql(table *Table, tableName, storeEngine, charset string) string + CreateIndexSql(tableName string, index *Index) string GetColumns(tableName string) ([]string, map[string]*Column, error) GetTables() ([]*Table, error) GetIndexes(tableName string) (map[string]*Index, error) - CreateTableSql(table *Table, tableName, storeEngine, charset string) string Filters() []Filter DriverName() string @@ -101,6 +105,10 @@ func (b *Base) AndStr() string { return "AND" } +func (b *Base) OrStr() string { + return "OR" +} + func (b *Base) EqStr() string { return "=" } @@ -113,6 +121,21 @@ func (db *Base) DropTableSql(tableName string) string { return fmt.Sprintf("DROP TABLE IF EXISTS `%s`", tableName) } +func (db *Base) CreateIndexSql(tableName string, index *Index) string { + quote := db.Quote + var unique string + var idxName string + if index.Type == UniqueType { + unique = " UNIQUE" + idxName = fmt.Sprintf("UQE_%v_%v", tableName, index.Name) + } else { + idxName = fmt.Sprintf("IDX_%v_%v", tableName, index.Name) + } + return fmt.Sprintf("CREATE%s INDEX %v ON %v (%v);", unique, + quote(idxName), quote(tableName), + quote(strings.Join(index.Cols, quote(",")))) +} + func (b *Base) CreateTableSql(table *Table, tableName, storeEngine, charset string) string { var sql string sql = "CREATE TABLE IF NOT EXISTS " From d3d1b7ce9a9ca55899a495fca26149548fc79184 Mon Sep 17 00:00:00 2001 From: Nash Tsai Date: Thu, 17 Apr 2014 20:55:06 +0800 Subject: [PATCH 010/135] check for empty charset --- dialect.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/dialect.go b/dialect.go index dc1eb1e3..728a2a01 100644 --- a/dialect.go +++ b/dialect.go @@ -146,10 +146,12 @@ func (b *Base) CreateTableSql(table *Table, tableName, storeEngine, charset stri sql += " ENGINE=" + storeEngine } if b.dialect.SupportCharset() { - if charset == "" { + if len(charset) == 0 { charset = b.dialect.URI().Charset } - sql += " DEFAULT CHARSET " + charset + if len(charset) > 0 { + sql += " DEFAULT CHARSET " + charset + } } sql += ";" return sql From ea82aa0fbf3c292a1b400fafb19fc20b7cebdc43 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 18 Apr 2014 18:39:59 +0800 Subject: [PATCH 011/135] add set db to dialect --- .gitignore | 1 + dialect.go | 13 +++++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..98e6ef67 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.db diff --git a/dialect.go b/dialect.go index 51c4cd4d..f17cf784 100644 --- a/dialect.go +++ b/dialect.go @@ -24,8 +24,9 @@ type Uri struct { // a dialect is a driver's wrapper type Dialect interface { - Init(*Uri, string, string) error + Init(*DB, *Uri, string, string) error URI() *Uri + DB() *DB DBType() DbType SqlType(*Column) string @@ -64,16 +65,20 @@ func OpenDialect(dialect Dialect) (*DB, error) { } type Base struct { + db *DB dialect Dialect driverName string dataSourceName string *Uri } -func (b *Base) Init(dialect Dialect, uri *Uri, drivername, dataSourceName string) error { - b.dialect = dialect +func (b *Base) DB() *DB { + return b.db +} + +func (b *Base) Init(db *DB, dialect Dialect, uri *Uri, drivername, dataSourceName string) error { + b.db, b.dialect, b.Uri = db, dialect, uri b.driverName, b.dataSourceName = drivername, dataSourceName - b.Uri = uri return nil } From 05943b9679408e25698c0f186c182037c53e322f Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 21 Apr 2014 11:15:12 +0800 Subject: [PATCH 012/135] new interface --- dialect.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dialect.go b/dialect.go index f17cf784..08a4a4c5 100644 --- a/dialect.go +++ b/dialect.go @@ -46,7 +46,7 @@ type Dialect interface { DropTableSql(tableName string) string IndexCheckSql(tableName, idxName string) (string, []interface{}) TableCheckSql(tableName string) (string, []interface{}) - ColumnCheckSql(tableName, colName string) (string, []interface{}) + ColumnCheckSql(tableName, colName string, isPK bool) (string, []interface{}) CreateTableSql(table *Table, tableName, storeEngine, charset string) string CreateIndexSql(tableName string, index *Index) string From 4bcb0fbd6ad8532f27aa57f7aeaa97e3b1d2559d Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 21 Apr 2014 15:13:32 +0800 Subject: [PATCH 013/135] rename ScanStruct to ScanStructByIndex and ScanStruct2 to ScanStructByName --- db.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/db.go b/db.go index ed97987a..92691d0e 100644 --- a/db.go +++ b/db.go @@ -252,7 +252,7 @@ type Rows struct { } // scan data to a struct's pointer according field index -func (rs *Rows) ScanStruct(dest ...interface{}) error { +func (rs *Rows) ScanStructByIndex(dest ...interface{}) error { if len(dest) == 0 { return errors.New("at least one struct") } @@ -319,7 +319,7 @@ func fieldByName(v reflect.Value, name string) reflect.Value { } // scan data to a struct's pointer according field name -func (rs *Rows) ScanStruct2(dest interface{}) error { +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") From aafdd49780037e395c5b6f6806d585225a205da4 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 23 Apr 2014 15:23:03 +0800 Subject: [PATCH 014/135] new dialect interface --- cache.go | 2 +- db.go | 9 ++------- dialect.go | 36 +++++++++++++++++++++++++++++++----- error.go | 10 ++++++++++ table.go | 2 ++ 5 files changed, 46 insertions(+), 13 deletions(-) create mode 100644 error.go diff --git a/cache.go b/cache.go index bedd22a7..b9bf6e0d 100644 --- a/cache.go +++ b/cache.go @@ -20,7 +20,7 @@ const ( // CacheStore is a interface to store cache type CacheStore interface { - // key is primary key or composite primary key or unique key's value + // key is primary key or composite primary key // value is struct's pointer // key format : -p--... Put(key string, value interface{}) error diff --git a/db.go b/db.go index ed97987a..1f70a926 100644 --- a/db.go +++ b/db.go @@ -8,11 +8,6 @@ import ( "sync" ) -var ( - ErrNoMapPointer = errors.New("mp should be a map's pointer") - ErrNoStructPointer = errors.New("mp should be a map's pointer") -) - func MapToSlice(query string, mp interface{}) (string, []interface{}, error) { vv := reflect.ValueOf(mp) if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Map { @@ -252,7 +247,7 @@ type Rows struct { } // scan data to a struct's pointer according field index -func (rs *Rows) ScanStruct(dest ...interface{}) error { +func (rs *Rows) ScanStructByIndex(dest ...interface{}) error { if len(dest) == 0 { return errors.New("at least one struct") } @@ -319,7 +314,7 @@ func fieldByName(v reflect.Value, name string) reflect.Value { } // scan data to a struct's pointer according field name -func (rs *Rows) ScanStruct2(dest interface{}) error { +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") diff --git a/dialect.go b/dialect.go index 08a4a4c5..f419cdb6 100644 --- a/dialect.go +++ b/dialect.go @@ -30,6 +30,9 @@ type Dialect interface { DBType() DbType SqlType(*Column) string + DriverName() string + DataSourceName() string + QuoteStr() string AndStr() string OrStr() string @@ -43,21 +46,28 @@ type Dialect interface { IndexOnTable() bool ShowCreateNull() bool - DropTableSql(tableName string) string IndexCheckSql(tableName, idxName string) (string, []interface{}) TableCheckSql(tableName string) (string, []interface{}) - ColumnCheckSql(tableName, colName string, isPK bool) (string, []interface{}) + //ColumnCheckSql(tableName, colName string) (string, []interface{}) + + //IsTableExist(tableName string) (bool, error) + //IsIndexExist(tableName string, idx *Index) (bool, error) + IsColumnExist(tableName string, col *Column) (bool, error) + CreateTableSql(table *Table, tableName, storeEngine, charset string) string + DropTableSql(tableName string) string CreateIndexSql(tableName string, index *Index) string GetColumns(tableName string) ([]string, map[string]*Column, error) GetTables() ([]*Table, error) GetIndexes(tableName string) (map[string]*Index, error) - Filters() []Filter + // Get data from db cell to a struct's field + //GetData(col *Column, fieldValue *reflect.Value, cellData interface{}) error + // Set field data to db + //SetData(col *Column, fieldValue *refelct.Value) (interface{}, error) - DriverName() string - DataSourceName() string + Filters() []Filter } func OpenDialect(dialect Dialect) (*DB, error) { @@ -126,6 +136,22 @@ func (db *Base) DropTableSql(tableName string) string { return fmt.Sprintf("DROP TABLE IF EXISTS `%s`", tableName) } +func (db *Base) IsColumnExist(tableName string, col *Column) (bool, error) { + args := []interface{}{db.DbName, tableName, col.Name} + query := "SELECT `COLUMN_NAME` FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ? AND `COLUMN_NAME` = ?" + + rows, err := db.DB().Query(query, args...) + if err != nil { + return false, err + } + defer rows.Close() + + if rows.Next() { + return true, nil + } + return false, ErrNotExist +} + func (db *Base) CreateIndexSql(tableName string, index *Index) string { quote := db.Quote var unique string diff --git a/error.go b/error.go new file mode 100644 index 00000000..b79f188e --- /dev/null +++ b/error.go @@ -0,0 +1,10 @@ +package core + +import "errors" + +var ( + ErrNoMapPointer = errors.New("mp should be a map's pointer") + ErrNoStructPointer = errors.New("mp should be a map's pointer") + ErrNotExist = errors.New("Not exist") + ErrIgnore = errors.New("Ignore") +) diff --git a/table.go b/table.go index aadcc9a8..9ccae74e 100644 --- a/table.go +++ b/table.go @@ -18,6 +18,8 @@ type Table struct { Updated string Version string Cacher Cacher + storeEngine string + charset string } func (table *Table) Columns() map[string]*Column { From e649ef538f981cc7240c73ba89862e956f00a64a Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 4 May 2014 13:52:56 +0800 Subject: [PATCH 015/135] add FormatBytes for dialect --- dialect.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/dialect.go b/dialect.go index f17cf784..3291d069 100644 --- a/dialect.go +++ b/dialect.go @@ -29,6 +29,7 @@ type Dialect interface { DB() *DB DBType() DbType SqlType(*Column) string + FormatBytes(b []byte) string QuoteStr() string AndStr() string @@ -90,6 +91,10 @@ 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 } From 724a99021f942c570f74d4cc365d5c2d46762d90 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 5 May 2014 17:55:25 +0800 Subject: [PATCH 016/135] add sql type bool methods --- type.go | 99 ++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 60 insertions(+), 39 deletions(-) diff --git a/type.go b/type.go index 68e2a23c..4f0b4d48 100644 --- a/type.go +++ b/type.go @@ -22,15 +22,35 @@ type SQLType struct { 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.Name == Char || s.Name == Varchar || s.Name == TinyText || - s.Name == Text || s.Name == MediumText || s.Name == LongText + return s.IsType(TEXT_TYPE) } func (s *SQLType) IsBlob() bool { - return (s.Name == TinyBlob) || (s.Name == Blob) || - s.Name == MediumBlob || s.Name == LongBlob || - s.Name == Binary || s.Name == VarBinary || s.Name == Bytea + 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) } var ( @@ -75,46 +95,47 @@ var ( Serial = "SERIAL" BigSerial = "BIGSERIAL" - SqlTypes = map[string]bool{ - Bit: true, - TinyInt: true, - SmallInt: true, - MediumInt: true, - Int: true, - Integer: true, - BigInt: true, + 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, - Char: true, - Varchar: true, - TinyText: true, - Text: true, - MediumText: true, - LongText: true, + Char: TEXT_TYPE, + Varchar: TEXT_TYPE, + TinyText: TEXT_TYPE, + Text: TEXT_TYPE, + MediumText: TEXT_TYPE, + LongText: TEXT_TYPE, - Date: true, - DateTime: true, - Time: true, - TimeStamp: true, - TimeStampz: true, + Date: TIME_TYPE, + DateTime: TIME_TYPE, + Time: TIME_TYPE, + TimeStamp: TIME_TYPE, + TimeStampz: TIME_TYPE, - Decimal: true, - Numeric: true, + Decimal: NUMERIC_TYPE, + Numeric: NUMERIC_TYPE, + Real: NUMERIC_TYPE, + Float: NUMERIC_TYPE, + Double: NUMERIC_TYPE, - Binary: true, - VarBinary: true, - Real: true, - Float: true, - Double: true, - TinyBlob: true, - Blob: true, - MediumBlob: true, - LongBlob: true, - Bytea: true, + Binary: BLOB_TYPE, + VarBinary: BLOB_TYPE, - Bool: true, + TinyBlob: BLOB_TYPE, + Blob: BLOB_TYPE, + MediumBlob: BLOB_TYPE, + LongBlob: BLOB_TYPE, + Bytea: BLOB_TYPE, - Serial: true, - BigSerial: true, + Bool: NUMERIC_TYPE, + + Serial: NUMERIC_TYPE, + BigSerial: NUMERIC_TYPE, } intTypes = sort.StringSlice{"*int", "*int16", "*int32", "*int8"} From c42f893230cd34310e11cbb40617cadd8929f0f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=95=86=E8=AE=AF=E5=9C=A8=E7=BA=BF?= Date: Mon, 5 May 2014 22:33:28 +0800 Subject: [PATCH 017/135] support enum type for mysql MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 商讯在线 --- column.go | 23 +++++++++++++++++++++-- type.go | 4 +++- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/column.go b/column.go index 20b3ad01..b4aa5cfd 100644 --- a/column.go +++ b/column.go @@ -31,11 +31,30 @@ type Column struct { IsVersion bool fieldPath []string DefaultIsEmpty bool + EnumOptions map[string]int } func NewColumn(name, fieldName string, sqlType SQLType, len1, len2 int, nullable bool) *Column { - return &Column{name, fieldName, sqlType, len1, len2, nullable, "", make(map[string]bool), false, false, - TWOSIDES, false, false, false, false, nil, false} + return &Column{ + Name: name, + FieldName: fieldName, + SQLType: sqlType, + Length: len1, + Length2: len2, + Nullable: nullable, + Default: "", + Indexes: make(map[string]bool), + IsPrimaryKey: false, + IsAutoIncrement: false, + MapType: TWOSIDES, + IsCreated: false, + IsUpdated: false, + IsCascade: false, + IsVersion: false, + fieldPath: nil, + DefaultIsEmpty: false, + EnumOptions: make(map[string]int), + } } // generate column description string according dialect diff --git a/type.go b/type.go index 4f0b4d48..c1e54efe 100644 --- a/type.go +++ b/type.go @@ -62,6 +62,7 @@ var ( Integer = "INTEGER" BigInt = "BIGINT" + Enum = "ENUM" Char = "CHAR" Varchar = "VARCHAR" TinyText = "TINYTEXT" @@ -104,6 +105,7 @@ var ( Integer: NUMERIC_TYPE, BigInt: NUMERIC_TYPE, + Enum: TEXT_TYPE, Char: TEXT_TYPE, Varchar: TEXT_TYPE, TinyText: TEXT_TYPE, @@ -293,7 +295,7 @@ func SQLType2Type(st SQLType) reflect.Type { return reflect.TypeOf(float32(1)) case Double: return reflect.TypeOf(float64(1)) - case Char, Varchar, TinyText, Text, MediumText, LongText: + case Char, Varchar, TinyText, Text, MediumText, LongText, Enum: return reflect.TypeOf("") case TinyBlob, Blob, LongBlob, Bytea, Binary, MediumBlob, VarBinary: return reflect.TypeOf([]byte{}) From aed19a5f8179d21a33c536b7b40083a6799cb529 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 8 May 2014 13:24:07 +0800 Subject: [PATCH 018/135] add support for ptr to extends struct --- column.go | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/column.go b/column.go index b4aa5cfd..f600719c 100644 --- a/column.go +++ b/column.go @@ -123,7 +123,21 @@ func (col *Column) ValueOfV(dataStruct *reflect.Value) (*reflect.Value, error) { } else if len(col.fieldPath) == 2 { parentField := dataStruct.FieldByName(col.fieldPath[0]) if parentField.IsValid() { - fieldValue = parentField.FieldByName(col.fieldPath[1]) + if parentField.Kind() == reflect.Struct { + fieldValue = parentField.FieldByName(col.fieldPath[1]) + } else if parentField.Kind() == reflect.Ptr { + if parentField.IsNil() { + parentField.Set(reflect.New(parentField.Type().Elem())) + fieldValue = parentField.Elem().FieldByName(col.fieldPath[1]) + } else { + parentField = parentField.Elem() + if parentField.IsValid() { + fieldValue = parentField.FieldByName(col.fieldPath[1]) + } else { + err = fmt.Errorf("field %v is not valid", col.FieldName) + } + } + } } else { err = fmt.Errorf("field %v is not valid", col.FieldName) } From adc62c0bd45bc8c219a1c0881f139d8829b17903 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 9 May 2014 09:43:35 +0800 Subject: [PATCH 019/135] bug fixed --- dialect.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dialect.go b/dialect.go index 22ecb730..1dcbd0fc 100644 --- a/dialect.go +++ b/dialect.go @@ -154,7 +154,7 @@ func (db *Base) IsColumnExist(tableName string, col *Column) (bool, error) { if rows.Next() { return true, nil } - return false, ErrNotExist + return false, nil } func (db *Base) CreateIndexSql(tableName string, index *Index) string { From 8904d06558a914a6d1d2f2bba1b77a9ddd85eaca Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 9 May 2014 12:51:11 +0800 Subject: [PATCH 020/135] improved IsColumnExist --- dialect.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/dialect.go b/dialect.go index 1dcbd0fc..59f7a886 100644 --- a/dialect.go +++ b/dialect.go @@ -141,10 +141,7 @@ func (db *Base) DropTableSql(tableName string) string { return fmt.Sprintf("DROP TABLE IF EXISTS `%s`", tableName) } -func (db *Base) IsColumnExist(tableName string, col *Column) (bool, error) { - args := []interface{}{db.DbName, tableName, col.Name} - query := "SELECT `COLUMN_NAME` FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ? AND `COLUMN_NAME` = ?" - +func (db *Base) HasRecords(query string, args ...interface{}) (bool, error) { rows, err := db.DB().Query(query, args...) if err != nil { return false, err @@ -157,6 +154,12 @@ func (db *Base) IsColumnExist(tableName string, col *Column) (bool, error) { return false, nil } +func (db *Base) IsColumnExist(tableName string, col *Column) (bool, error) { + query := "SELECT `COLUMN_NAME` FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ? AND `COLUMN_NAME` = ?" + query = strings.Replace(query, "`", db.dialect.QuoteStr(), -1) + return db.HasRecords(query, db.DbName, tableName, col.Name) +} + func (db *Base) CreateIndexSql(tableName string, index *Index) string { quote := db.Quote var unique string From c513c134ef47a6e636f648dba13aaad4ef3b4f51 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 9 May 2014 21:52:51 +0800 Subject: [PATCH 021/135] remove unused codes --- error.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/error.go b/error.go index b79f188e..414ba037 100644 --- a/error.go +++ b/error.go @@ -5,6 +5,6 @@ import "errors" var ( ErrNoMapPointer = errors.New("mp should be a map's pointer") ErrNoStructPointer = errors.New("mp should be a map's pointer") - ErrNotExist = errors.New("Not exist") - ErrIgnore = errors.New("Ignore") + //ErrNotExist = errors.New("Not exist") + //ErrIgnore = errors.New("Ignore") ) From 269c3fffe1541072cc52d361693d7990537677ae Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 11 May 2014 13:24:41 +0800 Subject: [PATCH 022/135] bug fixed for SuffixMapper --- mapper.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mapper.go b/mapper.go index fb209a7c..8fc115f1 100644 --- a/mapper.go +++ b/mapper.go @@ -164,11 +164,11 @@ type SuffixMapper struct { } func (mapper SuffixMapper) Obj2Table(name string) string { - return mapper.Suffix + mapper.Mapper.Obj2Table(name) + return mapper.Mapper.Obj2Table(name) + mapper.Suffix } func (mapper SuffixMapper) Table2Obj(name string) string { - return mapper.Mapper.Table2Obj(name[len(mapper.Suffix):]) + return mapper.Mapper.Table2Obj(name[:len(name)-len(mapper.Suffix)]) } func NewSuffixMapper(mapper IMapper, suffix string) SuffixMapper { From 267e37572ea33e494bb50d3e926ce1f84cbcda41 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 23 May 2014 14:17:56 +0800 Subject: [PATCH 023/135] add support for Join --- table.go | 42 +++++++++++++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/table.go b/table.go index 9ccae74e..0eae89e8 100644 --- a/table.go +++ b/table.go @@ -10,7 +10,8 @@ type Table struct { Name string Type reflect.Type columnsSeq []string - columns map[string]*Column + columnsMap map[string][]*Column + columns []*Column Indexes map[string]*Index PrimaryKeys []string AutoIncrement string @@ -22,7 +23,7 @@ type Table struct { charset string } -func (table *Table) Columns() map[string]*Column { +func (table *Table) Columns() []*Column { return table.columns } @@ -31,18 +32,14 @@ func (table *Table) ColumnsSeq() []string { } func NewEmptyTable() *Table { - return &Table{columnsSeq: make([]string, 0), - columns: make(map[string]*Column), - Indexes: make(map[string]*Index), - Created: make(map[string]bool), - PrimaryKeys: make([]string, 0), - } + return NewTable("", nil) } func NewTable(name string, t reflect.Type) *Table { return &Table{Name: name, Type: t, columnsSeq: make([]string, 0), - columns: make(map[string]*Column), + columns: make([]*Column, 0), + columnsMap: make(map[string][]*Column), Indexes: make(map[string]*Index), Created: make(map[string]bool), PrimaryKeys: make([]string, 0), @@ -50,7 +47,19 @@ func NewTable(name string, t reflect.Type) *Table { } func (table *Table) GetColumn(name string) *Column { - return table.columns[strings.ToLower(name)] + if c, ok := table.columnsMap[strings.ToLower(name)]; ok { + return c[0] + } + return nil +} + +func (table *Table) GetColumnIdx(name string, idx int) *Column { + if c, ok := table.columnsMap[strings.ToLower(name)]; ok { + if idx < len(c) { + return c[idx] + } + } + return nil } // if has primary key, return column @@ -70,10 +79,21 @@ func (table *Table) VersionColumn() *Column { return table.GetColumn(table.Version) } +func (table *Table) UpdatedColumn() *Column { + return table.GetColumn(table.Updated) +} + // add a column to table func (table *Table) AddColumn(col *Column) { table.columnsSeq = append(table.columnsSeq, col.Name) - table.columns[strings.ToLower(col.Name)] = col + 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) } From 9184dbce1ecb192f88bb380199d42909eeb8d767 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 11 Jun 2014 14:02:22 +0800 Subject: [PATCH 024/135] add drop index sql and modify column sql interface --- dialect.go | 23 +++++++++++++++++++++++ index.go | 19 +++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/dialect.go b/dialect.go index 59f7a886..64819ec3 100644 --- a/dialect.go +++ b/dialect.go @@ -58,6 +58,9 @@ type Dialect interface { CreateTableSql(table *Table, tableName, storeEngine, charset string) string DropTableSql(tableName string) string CreateIndexSql(tableName string, index *Index) string + DropIndexSql(tableName string, index *Index) string + + ModifyColumnSql(tableName string, col *Column) string GetColumns(tableName string) ([]string, map[string]*Column, error) GetTables() ([]*Table, error) @@ -175,6 +178,26 @@ func (db *Base) CreateIndexSql(tableName string, index *Index) string { quote(strings.Join(index.Cols, quote(",")))) } +func (db *Base) DropIndexSql(tableName string, index *Index) string { + quote := db.Quote + //var unique string + var idxName string = index.Name + if !strings.HasPrefix(idxName, "UQE_") && + !strings.HasPrefix(idxName, "IDX_") { + if index.Type == UniqueType { + idxName = fmt.Sprintf("UQE_%v_%v", tableName, index.Name) + } else { + idxName = fmt.Sprintf("IDX_%v_%v", tableName, index.Name) + } + } + return fmt.Sprintf("DROP INDEX %v ON %s", + quote(idxName), quote(tableName)) +} + +func (db *Base) ModifyColumnSql(tableName string, col *Column) string { + return fmt.Sprintf("alter table %s MODIFY COLUMN %s", tableName, col.StringNoPk(db.dialect)) +} + func (b *Base) CreateTableSql(table *Table, tableName, storeEngine, charset string) string { var sql string sql = "CREATE TABLE IF NOT EXISTS " diff --git a/index.go b/index.go index eb088850..e8f447d7 100644 --- a/index.go +++ b/index.go @@ -1,5 +1,9 @@ package core +import ( + "sort" +) + const ( IndexType = iota + 1 UniqueType @@ -19,6 +23,21 @@ func (index *Index) AddColumn(cols ...string) { } } +func (index *Index) Equal(dst *Index) bool { + if len(index.Cols) != len(dst.Cols) { + return false + } + sort.StringSlice(index.Cols).Sort() + sort.StringSlice(dst.Cols).Sort() + + for i := 0; i < len(index.Cols); i++ { + if index.Cols[i] != dst.Cols[i] { + return false + } + } + return true +} + // new an index func NewIndex(name string, indexType int) *Index { return &Index{name, indexType, make([]string, 0)} From 1a1e9ee8df4cb99f603f833f1db6f3a1bb2a6e37 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 16 Jun 2014 16:52:06 +0800 Subject: [PATCH 025/135] bug fixed for test --- db_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/db_test.go b/db_test.go index 9836a589..4784de93 100644 --- a/db_test.go +++ b/db_test.go @@ -102,7 +102,7 @@ func BenchmarkStructQuery(b *testing.B) { for rows.Next() { var user User - err = rows.ScanStruct(&user) + err = rows.ScanStructByIndex(&user) if err != nil { b.Error(err) } @@ -148,7 +148,7 @@ func BenchmarkStruct2Query(b *testing.B) { for rows.Next() { var user User - err = rows.ScanStruct2(&user) + err = rows.ScanStructByName(&user) if err != nil { b.Error(err) } @@ -548,7 +548,7 @@ func TestExecMap(t *testing.T) { for rows.Next() { var user User - err = rows.ScanStruct2(&user) + err = rows.ScanStructByName(&user) if err != nil { t.Error(err) } @@ -591,7 +591,7 @@ func TestExecStruct(t *testing.T) { for rows.Next() { var user User - err = rows.ScanStruct2(&user) + err = rows.ScanStructByName(&user) if err != nil { t.Error(err) } From a0e7f24f6c4ed452d02a781a60fd5b101e5c9c90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A0=BC=E7=A0=BC?= Date: Mon, 23 Jun 2014 21:46:52 +0800 Subject: [PATCH 026/135] pqsql-uuid --- type.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/type.go b/type.go index c1e54efe..37c4c0b5 100644 --- a/type.go +++ b/type.go @@ -69,6 +69,7 @@ var ( Text = "TEXT" MediumText = "MEDIUMTEXT" LongText = "LONGTEXT" + Uuid = "UUID" Date = "DATE" DateTime = "DATETIME" @@ -112,6 +113,7 @@ var ( Text: TEXT_TYPE, MediumText: TEXT_TYPE, LongText: TEXT_TYPE, + Uuid: TEXT_TYPE, Date: TIME_TYPE, DateTime: TIME_TYPE, @@ -295,7 +297,7 @@ func SQLType2Type(st SQLType) reflect.Type { return reflect.TypeOf(float32(1)) case Double: return reflect.TypeOf(float64(1)) - case Char, Varchar, TinyText, Text, MediumText, LongText, Enum: + case Char, Varchar, TinyText, Text, MediumText, LongText, Enum,Uuid: return reflect.TypeOf("") case TinyBlob, Blob, LongBlob, Bytea, Binary, MediumBlob, VarBinary: return reflect.TypeOf([]byte{}) From 865fc8c0fb3fe9167f055cad842b0ed2002c0d62 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 7 Aug 2014 22:17:14 +0800 Subject: [PATCH 027/135] add set type --- column.go | 1 + db_test.go | 136 +++++++++++++++++++++++++++++++---------------------- type.go | 12 +++-- 3 files changed, 89 insertions(+), 60 deletions(-) diff --git a/column.go b/column.go index f600719c..8656d4f6 100644 --- a/column.go +++ b/column.go @@ -32,6 +32,7 @@ type Column struct { fieldPath []string DefaultIsEmpty bool EnumOptions map[string]int + SetOptions map[string]int } func NewColumn(name, fieldName string, sqlType SQLType, len1, len2 int, nullable bool) *Column { diff --git a/db_test.go b/db_test.go index 4784de93..cf4caf37 100644 --- a/db_test.go +++ b/db_test.go @@ -7,12 +7,14 @@ import ( "testing" "time" + _ "github.com/go-sql-driver/mysql" _ "github.com/mattn/go-sqlite3" ) var ( - createTableSqlite3 = "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);" + //dbtype string = "sqlite3" + dbtype string = "mysql" + createTableSql string ) type User struct { @@ -25,22 +27,46 @@ type User struct { Created time.Time } +func init() { + 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": + 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);" + default: + panic("no db type") + } +} + +func testOpen() (*DB, error) { + switch dbtype { + case "sqlite3": + os.Remove("./test.db") + return Open("sqlite3", "./test.db") + case "mysql": + return Open("mysql", "root:@/core_test?charset=utf8&parseTime=true") + default: + panic("no db type") + } +} + func BenchmarkOriQuery(b *testing.B) { b.StopTimer() - os.Remove("./test.db") - db, err := Open("sqlite3", "./test.db") + db, err := testOpen() if err != nil { b.Error(err) } defer db.Close() - _, err = db.Exec(createTableSqlite3) + _, 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 (?,?,?,?,?, ?)", + _, 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) @@ -72,20 +98,20 @@ func BenchmarkOriQuery(b *testing.B) { func BenchmarkStructQuery(b *testing.B) { b.StopTimer() - os.Remove("./test.db") - db, err := Open("sqlite3", "./test.db") + + db, err := testOpen() if err != nil { b.Error(err) } defer db.Close() - _, err = db.Exec(createTableSqlite3) + _, 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 (?,?,?,?,?, ?)", + _, 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) @@ -117,20 +143,20 @@ func BenchmarkStructQuery(b *testing.B) { func BenchmarkStruct2Query(b *testing.B) { b.StopTimer() - os.Remove("./test.db") - db, err := Open("sqlite3", "./test.db") + + db, err := testOpen() if err != nil { b.Error(err) } defer db.Close() - _, err = db.Exec(createTableSqlite3) + _, 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 (?,?,?,?,?,?)", + _, 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) @@ -163,20 +189,20 @@ func BenchmarkStruct2Query(b *testing.B) { func BenchmarkSliceInterfaceQuery(b *testing.B) { b.StopTimer() - os.Remove("./test.db") - db, err := Open("sqlite3", "./test.db") + + db, err := testOpen() if err != nil { b.Error(err) } defer db.Close() - _, err = db.Exec(createTableSqlite3) + _, 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 (?,?,?,?,?,?)", + _, 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) @@ -221,7 +247,7 @@ func BenchmarkSliceInterfaceQuery(b *testing.B) { } defer db.Close() - _, err = db.Exec(createTableSqlite3) + _, err = db.Exec(createTableSql) if err != nil { b.Error(err) } @@ -262,17 +288,17 @@ func BenchmarkSliceInterfaceQuery(b *testing.B) { rows.Close() } } +*/ func BenchmarkSliceStringQuery(b *testing.B) { b.StopTimer() - os.Remove("./test.db") - db, err := Open("sqlite3", "./test.db") + db, err := testOpen() if err != nil { b.Error(err) } defer db.Close() - _, err = db.Exec(createTableSqlite3) + _, err = db.Exec(createTableSql) if err != nil { b.Error(err) } @@ -299,12 +325,12 @@ func BenchmarkSliceStringQuery(b *testing.B) { } for rows.Next() { - slice := make([]string, len(cols)) + slice := make([]*string, len(cols)) err = rows.ScanSlice(&slice) if err != nil { b.Error(err) } - if slice[1] != "xlw" { + if (*slice[1]) != "xlw" { fmt.Println(slice) b.Error(errors.New("name should be xlw")) } @@ -312,18 +338,18 @@ func BenchmarkSliceStringQuery(b *testing.B) { rows.Close() } -}*/ +} func BenchmarkMapInterfaceQuery(b *testing.B) { b.StopTimer() - os.Remove("./test.db") - db, err := Open("sqlite3", "./test.db") + + db, err := testOpen() if err != nil { b.Error(err) } defer db.Close() - _, err = db.Exec(createTableSqlite3) + _, err = db.Exec(createTableSql) if err != nil { b.Error(err) } @@ -369,7 +395,7 @@ func BenchmarkMapInterfaceQuery(b *testing.B) { } defer db.Close() - _, err = db.Exec(createTableSqlite3) + _, err = db.Exec(createTableSql) if err != nil { b.Error(err) } @@ -405,7 +431,8 @@ func BenchmarkMapInterfaceQuery(b *testing.B) { rows.Close() } } - +*/ +/* func BenchmarkMapStringQuery(b *testing.B) { b.StopTimer() os.Remove("./test.db") @@ -415,7 +442,7 @@ func BenchmarkMapStringQuery(b *testing.B) { } defer db.Close() - _, err = db.Exec(createTableSqlite3) + _, err = db.Exec(createTableSql) if err != nil { b.Error(err) } @@ -454,14 +481,14 @@ func BenchmarkMapStringQuery(b *testing.B) { func BenchmarkExec(b *testing.B) { b.StopTimer() - os.Remove("./test.db") - db, err := Open("sqlite3", "./test.db") + + db, err := testOpen() if err != nil { b.Error(err) } defer db.Close() - _, err = db.Exec(createTableSqlite3) + _, err = db.Exec(createTableSql) if err != nil { b.Error(err) } @@ -469,7 +496,7 @@ func BenchmarkExec(b *testing.B) { b.StartTimer() for i := 0; i < b.N; i++ { - _, err = db.Exec("insert into user (name, title, age, alias, nick_name,created) values (?,?,?,?,?,?)", + _, 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) @@ -479,14 +506,14 @@ func BenchmarkExec(b *testing.B) { func BenchmarkExecMap(b *testing.B) { b.StopTimer() - os.Remove("./test.db") - db, err := Open("sqlite3", "./test.db") + + db, err := testOpen() if err != nil { b.Error(err) } defer db.Close() - _, err = db.Exec(createTableSqlite3) + _, err = db.Exec(createTableSql) if err != nil { b.Error(err) } @@ -503,8 +530,8 @@ func BenchmarkExecMap(b *testing.B) { } 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)`, + _, 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) @@ -513,14 +540,13 @@ func BenchmarkExecMap(b *testing.B) { } func TestExecMap(t *testing.T) { - os.Remove("./test.db") - db, err := Open("sqlite3", "./test.db") + db, err := testOpen() if err != nil { t.Error(err) } defer db.Close() - _, err = db.Exec(createTableSqlite3) + _, err = db.Exec(createTableSql) if err != nil { t.Error(err) } @@ -534,8 +560,8 @@ func TestExecMap(t *testing.T) { "created": time.Now(), } - _, err = db.ExecMap(`insert into user (name, title, age, alias, nick_name,created) - values (?name,?title,?age,?alias,?nick_name,?created)`, + _, 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) @@ -557,14 +583,13 @@ func TestExecMap(t *testing.T) { } func TestExecStruct(t *testing.T) { - os.Remove("./test.db") - db, err := Open("sqlite3", "./test.db") + db, err := testOpen() if err != nil { t.Error(err) } defer db.Close() - _, err = db.Exec(createTableSqlite3) + _, err = db.Exec(createTableSql) if err != nil { t.Error(err) } @@ -577,14 +602,14 @@ func TestExecStruct(t *testing.T) { Created: time.Now(), } - _, err = db.ExecStruct(`insert into user (name, title, age, alias, nick_name,created) - values (?Name,?Title,?Age,?Alias,?NickName,?Created)`, + _, 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) + rows, err := db.QueryStruct("select * from user where `name` = ?Name", &user) if err != nil { t.Error(err) } @@ -601,14 +626,13 @@ func TestExecStruct(t *testing.T) { func BenchmarkExecStruct(b *testing.B) { b.StopTimer() - os.Remove("./test.db") - db, err := Open("sqlite3", "./test.db") + db, err := testOpen() if err != nil { b.Error(err) } defer db.Close() - _, err = db.Exec(createTableSqlite3) + _, err = db.Exec(createTableSql) if err != nil { b.Error(err) } @@ -624,8 +648,8 @@ func BenchmarkExecStruct(b *testing.B) { } 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)`, + _, 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/type.go b/type.go index 37c4c0b5..f1a609b5 100644 --- a/type.go +++ b/type.go @@ -62,14 +62,16 @@ var ( Integer = "INTEGER" BigInt = "BIGINT" - Enum = "ENUM" + Enum = "ENUM" + Set = "SET" + Char = "CHAR" Varchar = "VARCHAR" TinyText = "TINYTEXT" Text = "TEXT" MediumText = "MEDIUMTEXT" LongText = "LONGTEXT" - Uuid = "UUID" + Uuid = "UUID" Date = "DATE" DateTime = "DATETIME" @@ -106,7 +108,9 @@ var ( Integer: NUMERIC_TYPE, BigInt: NUMERIC_TYPE, - Enum: TEXT_TYPE, + Enum: TEXT_TYPE, + Set: TEXT_TYPE, + Char: TEXT_TYPE, Varchar: TEXT_TYPE, TinyText: TEXT_TYPE, @@ -297,7 +301,7 @@ func SQLType2Type(st SQLType) reflect.Type { return reflect.TypeOf(float32(1)) case Double: return reflect.TypeOf(float64(1)) - case Char, Varchar, TinyText, Text, MediumText, LongText, Enum,Uuid: + case Char, Varchar, TinyText, Text, MediumText, LongText, Enum, Set, Uuid: return reflect.TypeOf("") case TinyBlob, Blob, LongBlob, Bytea, Binary, MediumBlob, VarBinary: return reflect.TypeOf([]byte{}) From 116e4818df03235e1eecebdbfe834095fecd2efe Mon Sep 17 00:00:00 2001 From: Nash Tsai Date: Mon, 18 Aug 2014 11:05:32 +0800 Subject: [PATCH 028/135] add defined caching error --- cache.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cache.go b/cache.go index b9bf6e0d..2a61ef75 100644 --- a/cache.go +++ b/cache.go @@ -18,6 +18,11 @@ const ( CacheGcMaxRemoved = 20 ) +var ( + ErrCacheMiss = errors.New("xorm/cache: key not found.") + ErrNotStored = errors.New("xorm/cache: not stored.") +) + // CacheStore is a interface to store cache type CacheStore interface { // key is primary key or composite primary key From 5510997442e7084b8fa87607b455c61287d149e5 Mon Sep 17 00:00:00 2001 From: Nash Tsai Date: Wed, 20 Aug 2014 09:38:03 +0800 Subject: [PATCH 029/135] move ILogger interface from xorm package to core package --- ilogger.go | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 ilogger.go diff --git a/ilogger.go b/ilogger.go new file mode 100644 index 00000000..531f97ba --- /dev/null +++ b/ilogger.go @@ -0,0 +1,9 @@ +package core + +// logger interface, log/syslog conform with this interface +type ILogger interface { + Debug(m string) (err error) + Err(m string) (err error) + Info(m string) (err error) + Warning(m string) (err error) +} From c6ccf96040888ae4fb7c97f09f4ed50c04b34766 Mon Sep 17 00:00:00 2001 From: Nash Tsai Date: Wed, 20 Aug 2014 10:39:35 +0800 Subject: [PATCH 030/135] update ILogger interface, and it's no longer compatible with log/syslog.Writer, due to performance design --- ilogger.go | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/ilogger.go b/ilogger.go index 531f97ba..e7f08b37 100644 --- a/ilogger.go +++ b/ilogger.go @@ -1,9 +1,28 @@ package core -// logger interface, log/syslog conform with this interface +type LogLevel int + +const ( + // !nashtsai! following level also match syslog.Priority value + LOG_UNKNOWN LogLevel = iota - 2 + LOG_OFF LogLevel = iota - 1 + LOG_ERR LogLevel = iota + 3 + LOG_WARNING + LOG_INFO LogLevel = iota + 6 + LOG_DEBUG +) + +// logger interface type ILogger interface { - Debug(m string) (err error) - Err(m string) (err error) - Info(m string) (err error) - Warning(m string) (err error) + Debug(v ...interface{}) (err error) + Debugf(format string, v ...interface{}) (err error) + Err(v ...interface{}) (err error) + Errf(format string, v ...interface{}) (err error) + Info(v ...interface{}) (err error) + Infof(format string, v ...interface{}) (err error) + Warning(v ...interface{}) (err error) + Warningf(format string, v ...interface{}) (err error) + + Level() LogLevel + SetLevel(l LogLevel) (err error) } From 933034a47606f6a1bddc5ee11b32c8545e457cd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=95=86=E8=AE=AF=E5=9C=A8=E7=BA=BF?= Date: Sat, 30 Aug 2014 22:14:12 +0800 Subject: [PATCH 031/135] improved --- table.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/table.go b/table.go index 0eae89e8..78eeacf9 100644 --- a/table.go +++ b/table.go @@ -19,8 +19,8 @@ type Table struct { Updated string Version string Cacher Cacher - storeEngine string - charset string + StoreEngine string + Charset string } func (table *Table) Columns() []*Column { From 750aae0fa54602847660f0d518620f087907ac55 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 6 Sep 2014 23:26:23 +0800 Subject: [PATCH 032/135] add reserved --- dialect.go | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/dialect.go b/dialect.go index 64819ec3..54be5ac7 100644 --- a/dialect.go +++ b/dialect.go @@ -35,6 +35,8 @@ type Dialect interface { DataSourceName() string QuoteStr() string + IsReserved(string) bool + Quote(string) string AndStr() string OrStr() string EqStr() string @@ -120,10 +122,6 @@ func (b *Base) DataSourceName() string { return b.dataSourceName } -func (b *Base) Quote(c string) string { - return b.dialect.QuoteStr() + c + b.dialect.QuoteStr() -} - func (b *Base) AndStr() string { return "AND" } @@ -164,7 +162,7 @@ func (db *Base) IsColumnExist(tableName string, col *Column) (bool, error) { } func (db *Base) CreateIndexSql(tableName string, index *Index) string { - quote := db.Quote + quote := db.dialect.Quote var unique string var idxName string if index.Type == UniqueType { @@ -179,7 +177,7 @@ func (db *Base) CreateIndexSql(tableName string, index *Index) string { } func (db *Base) DropIndexSql(tableName string, index *Index) string { - quote := db.Quote + quote := db.dialect.Quote //var unique string var idxName string = index.Name if !strings.HasPrefix(idxName, "UQE_") && @@ -205,7 +203,7 @@ func (b *Base) CreateTableSql(table *Table, tableName, storeEngine, charset stri tableName = table.Name } - sql += b.Quote(tableName) + " (" + sql += b.dialect.Quote(tableName) + " (" pkList := table.PrimaryKeys @@ -222,7 +220,7 @@ func (b *Base) CreateTableSql(table *Table, tableName, storeEngine, charset stri if len(pkList) > 1 { sql += "PRIMARY KEY ( " - sql += b.Quote(strings.Join(pkList, b.Quote(","))) + sql += b.dialect.Quote(strings.Join(pkList, b.dialect.Quote(","))) sql += " ), " } From c7420dd938dde2e6033277822107376246e9ab11 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 24 Oct 2014 12:43:32 +0800 Subject: [PATCH 033/135] add nvarchar type --- type.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/type.go b/type.go index f1a609b5..ee765682 100644 --- a/type.go +++ b/type.go @@ -67,6 +67,7 @@ var ( Char = "CHAR" Varchar = "VARCHAR" + NVarchar = "NVARCHAR" TinyText = "TINYTEXT" Text = "TEXT" MediumText = "MEDIUMTEXT" @@ -113,6 +114,7 @@ var ( Char: TEXT_TYPE, Varchar: TEXT_TYPE, + NVarchar: TEXT_TYPE, TinyText: TEXT_TYPE, Text: TEXT_TYPE, MediumText: TEXT_TYPE, @@ -301,7 +303,7 @@ func SQLType2Type(st SQLType) reflect.Type { return reflect.TypeOf(float32(1)) case Double: return reflect.TypeOf(float64(1)) - case Char, Varchar, TinyText, Text, MediumText, LongText, Enum, Set, Uuid: + case Char, Varchar, NVarchar, TinyText, Text, MediumText, LongText, Enum, Set, Uuid: return reflect.TypeOf("") case TinyBlob, Blob, LongBlob, Bytea, Binary, MediumBlob, VarBinary: return reflect.TypeOf([]byte{}) From dabc8824066d5c311a57932d03dc7b17decd9a2b Mon Sep 17 00:00:00 2001 From: Kazuhiro Oinuma Date: Mon, 3 Nov 2014 22:02:45 +0900 Subject: [PATCH 034/135] Add softdelete feature --- column.go | 2 ++ table.go | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/column.go b/column.go index 8656d4f6..ce0a7891 100644 --- a/column.go +++ b/column.go @@ -29,6 +29,7 @@ type Column struct { IsUpdated bool IsCascade bool IsVersion bool + IsSoftDelete bool fieldPath []string DefaultIsEmpty bool EnumOptions map[string]int @@ -52,6 +53,7 @@ func NewColumn(name, fieldName string, sqlType SQLType, len1, len2 int, nullable IsUpdated: false, IsCascade: false, IsVersion: false, + IsSoftDelete: false, fieldPath: nil, DefaultIsEmpty: false, EnumOptions: make(map[string]int), diff --git a/table.go b/table.go index 78eeacf9..ee15d113 100644 --- a/table.go +++ b/table.go @@ -18,6 +18,7 @@ type Table struct { Created map[string]bool Updated string Version string + SoftDelete string Cacher Cacher StoreEngine string Charset string @@ -83,6 +84,10 @@ func (table *Table) UpdatedColumn() *Column { return table.GetColumn(table.Updated) } +func (table *Table) SoftDeleteColumn() *Column { + return table.GetColumn(table.SoftDelete) +} + // add a column to table func (table *Table) AddColumn(col *Column) { table.columnsSeq = append(table.columnsSeq, col.Name) @@ -109,6 +114,9 @@ func (table *Table) AddColumn(col *Column) { if col.IsVersion { table.Version = col.Name } + if col.IsSoftDelete { + table.SoftDelete = col.Name + } } // add an index or an unique to table From 3e9907056787698c9419074d5a54b265a86d492f Mon Sep 17 00:00:00 2001 From: oinume Date: Wed, 5 Nov 2014 15:28:37 +0900 Subject: [PATCH 035/135] Tag name changed: softdelete -> deleted --- column.go | 4 ++-- table.go | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/column.go b/column.go index ce0a7891..34223de8 100644 --- a/column.go +++ b/column.go @@ -27,9 +27,9 @@ type Column struct { MapType int IsCreated bool IsUpdated bool + IsDeleted bool IsCascade bool IsVersion bool - IsSoftDelete bool fieldPath []string DefaultIsEmpty bool EnumOptions map[string]int @@ -51,9 +51,9 @@ func NewColumn(name, fieldName string, sqlType SQLType, len1, len2 int, nullable MapType: TWOSIDES, IsCreated: false, IsUpdated: false, + IsDeleted: false, IsCascade: false, IsVersion: false, - IsSoftDelete: false, fieldPath: nil, DefaultIsEmpty: false, EnumOptions: make(map[string]int), diff --git a/table.go b/table.go index ee15d113..f7c9d464 100644 --- a/table.go +++ b/table.go @@ -17,8 +17,8 @@ type Table struct { AutoIncrement string Created map[string]bool Updated string + Deleted string Version string - SoftDelete string Cacher Cacher StoreEngine string Charset string @@ -84,8 +84,8 @@ func (table *Table) UpdatedColumn() *Column { return table.GetColumn(table.Updated) } -func (table *Table) SoftDeleteColumn() *Column { - return table.GetColumn(table.SoftDelete) +func (table *Table) DeletedColumn() *Column { + return table.GetColumn(table.Deleted) } // add a column to table @@ -111,12 +111,12 @@ func (table *Table) AddColumn(col *Column) { if col.IsUpdated { table.Updated = col.Name } + if col.IsDeleted { + table.Deleted = col.Name + } if col.IsVersion { table.Version = col.Name } - if col.IsSoftDelete { - table.SoftDelete = col.Name - } } // add an index or an unique to table From 9829026953cfd5476a2f17b995ce16778fcb696b Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 8 Nov 2014 18:51:42 +0800 Subject: [PATCH 036/135] cache support --- db_test.go | 5 +++-- dialect.go | 9 +++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/db_test.go b/db_test.go index cf4caf37..65bf2414 100644 --- a/db_test.go +++ b/db_test.go @@ -46,7 +46,7 @@ func testOpen() (*DB, error) { os.Remove("./test.db") return Open("sqlite3", "./test.db") case "mysql": - return Open("mysql", "root:@/core_test?charset=utf8&parseTime=true") + return Open("mysql", "root:@/core_test?charset=utf8") default: panic("no db type") } @@ -228,7 +228,8 @@ func BenchmarkSliceInterfaceQuery(b *testing.B) { if err != nil { b.Error(err) } - if slice[1].(string) != "xlw" { + fmt.Println(slice) + if *slice[1].(*string) != "xlw" { fmt.Println(slice) b.Error(errors.New("name should be xlw")) } diff --git a/dialect.go b/dialect.go index 54be5ac7..05375642 100644 --- a/dialect.go +++ b/dialect.go @@ -24,6 +24,7 @@ type Uri struct { // a dialect is a driver's wrapper type Dialect interface { + SetLogger(logger ILogger) Init(*DB, *Uri, string, string) error URI() *Uri DB() *DB @@ -85,6 +86,7 @@ type Base struct { dialect Dialect driverName string dataSourceName string + Logger ILogger *Uri } @@ -92,6 +94,10 @@ func (b *Base) DB() *DB { return b.db } +func (b *Base) SetLogger(logger ILogger) { + b.Logger = logger +} + func (b *Base) Init(db *DB, dialect Dialect, uri *Uri, drivername, dataSourceName string) error { b.db, b.dialect, b.Uri = db, dialect, uri b.driverName, b.dataSourceName = drivername, dataSourceName @@ -144,6 +150,9 @@ func (db *Base) DropTableSql(tableName string) string { func (db *Base) HasRecords(query string, args ...interface{}) (bool, error) { rows, err := db.DB().Query(query, args...) + if db.Logger != nil { + db.Logger.Info("[sql]", query, args) + } if err != nil { return false, err } From 7b90ea8d13bb65b943eff55c82c19846c8a402b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=95=86=E8=AE=AF=E5=9C=A8=E7=BA=BF?= Date: Wed, 19 Nov 2014 00:42:40 +0800 Subject: [PATCH 037/135] Increase TableName method used to obtain the full table name --- mapper.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/mapper.go b/mapper.go index 8fc115f1..c00dc395 100644 --- a/mapper.go +++ b/mapper.go @@ -9,6 +9,7 @@ import ( type IMapper interface { Obj2Table(string) string Table2Obj(string) string + TableName(string) string } type CacheMapper struct { @@ -55,6 +56,10 @@ func (m *CacheMapper) Table2Obj(t string) string { return o } +func (m *CacheMapper) TableName(t string) string { + return t +} + // SameMapper implements IMapper and provides same name between struct and // database table type SameMapper struct { @@ -68,6 +73,10 @@ func (m SameMapper) Table2Obj(t string) string { return t } +func (m SameMapper) TableName(t string) string { + return t +} + // SnakeMapper implements IMapper and provides name transaltion between // struct and database table type SnakeMapper struct { @@ -139,6 +148,9 @@ func (mapper SnakeMapper) Table2Obj(name string) string { return titleCasedName(name) } +func (mapper SnakeMapper) TableName(t string) string { + return t +} // provide prefix table name support type PrefixMapper struct { Mapper IMapper @@ -153,6 +165,10 @@ func (mapper PrefixMapper) Table2Obj(name string) string { return mapper.Mapper.Table2Obj(name[len(mapper.Prefix):]) } +func (mapper PrefixMapper) TableName(name string) string { + return mapper.Prefix + name +} + func NewPrefixMapper(mapper IMapper, prefix string) PrefixMapper { return PrefixMapper{mapper, prefix} } @@ -171,6 +187,10 @@ func (mapper SuffixMapper) Table2Obj(name string) string { return mapper.Mapper.Table2Obj(name[:len(name)-len(mapper.Suffix)]) } +func (mapper SuffixMapper) TableName(name string) string { + return name + mapper.Suffix +} + func NewSuffixMapper(mapper IMapper, suffix string) SuffixMapper { return SuffixMapper{mapper, suffix} } From a949e067ced1cb6e6ef5c38b6f28b074fa718f1e Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 22 Nov 2014 12:23:31 +0800 Subject: [PATCH 038/135] support Find(struct1, struct2), param1 is different from param2 --- column.go | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/column.go b/column.go index 34223de8..18921ca7 100644 --- a/column.go +++ b/column.go @@ -1,6 +1,7 @@ package core import ( + "errors" "fmt" "reflect" "strings" @@ -116,7 +117,6 @@ func (col *Column) ValueOf(bean interface{}) (*reflect.Value, error) { func (col *Column) ValueOfV(dataStruct *reflect.Value) (*reflect.Value, error) { var fieldValue reflect.Value - var err error if col.fieldPath == nil { col.fieldPath = strings.Split(col.FieldName, ".") } @@ -137,18 +137,21 @@ func (col *Column) ValueOfV(dataStruct *reflect.Value) (*reflect.Value, error) { if parentField.IsValid() { fieldValue = parentField.FieldByName(col.fieldPath[1]) } else { - err = fmt.Errorf("field %v is not valid", col.FieldName) + return nil, fmt.Errorf("field %v is not valid", col.FieldName) } } } } else { - err = fmt.Errorf("field %v is not valid", col.FieldName) + // so we can use a different struct as conditions + fieldValue = dataStruct.FieldByName(col.fieldPath[1]) } } else { - err = fmt.Errorf("Unsupported mutliderive %v", col.FieldName) + return nil, fmt.Errorf("Unsupported mutliderive %v", col.FieldName) } - if err != nil { - return nil, err + + if !fieldValue.IsValid() { + return nil, errors.New("no find field matched") } + return &fieldValue, nil } From e7882d8b00249a615906ac092c118f499df8de8b Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 15 Dec 2014 13:26:57 +0800 Subject: [PATCH 039/135] add some fixes --- mapper.go | 1 + 1 file changed, 1 insertion(+) diff --git a/mapper.go b/mapper.go index c00dc395..3cc4735e 100644 --- a/mapper.go +++ b/mapper.go @@ -151,6 +151,7 @@ func (mapper SnakeMapper) Table2Obj(name string) string { func (mapper SnakeMapper) TableName(t string) string { return t } + // provide prefix table name support type PrefixMapper struct { Mapper IMapper From c6b0cd81d36776f3ee6f6e5f9a8c8edff29358e9 Mon Sep 17 00:00:00 2001 From: Peter Smit Date: Tue, 20 Jan 2015 16:43:30 +0200 Subject: [PATCH 040/135] Implement GonicMapper --- mapper.go | 105 +++++++++++++++++++++++++++++++++++++++++++++++++ mapper_test.go | 45 +++++++++++++++++++++ 2 files changed, 150 insertions(+) create mode 100644 mapper_test.go diff --git a/mapper.go b/mapper.go index 3cc4735e..1c080a66 100644 --- a/mapper.go +++ b/mapper.go @@ -152,6 +152,111 @@ func (mapper SnakeMapper) TableName(t string) string { return t } +// 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 struct{} + +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) +} + +// A list of common initialisms taken from golang/lint +var commonInitialisms = map[string]bool{ + "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, +} + +func gonicTitleCasedName(name string) string { + newstr := make([]rune, 0) + + name = strings.ToLower(name) + parts := strings.Split(name, "_") + + for _, p := range parts { + _, isInitialism := commonInitialisms[strings.ToUpper(p)] + for i, r := range p { + if i == 0 || isInitialism { + r = toASCIIUpper(r) + } + newstr = append(newstr, r) + } + } + + return string(newstr) +} + +func (mapper GonicMapper) Table2Obj(name string) string { + return gonicTitleCasedName(name) +} + +func (mapper GonicMapper) TableName(t string) string { + return t +} + // provide prefix table name support type PrefixMapper struct { Mapper IMapper diff --git a/mapper_test.go b/mapper_test.go new file mode 100644 index 00000000..7dff090a --- /dev/null +++ b/mapper_test.go @@ -0,0 +1,45 @@ +package core + +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 := gonicTitleCasedName(in) + if out != expected { + t.Errorf("Given %s, expected %s but got %s", in, expected, out) + } + } +} From 8ee59e351b1d200af9f3f174c8008ffe4c5dd3c7 Mon Sep 17 00:00:00 2001 From: Peter Smit Date: Tue, 20 Jan 2015 17:11:10 +0200 Subject: [PATCH 041/135] Make initialisms configurable --- mapper.go | 56 +++++++++++++++++++++++--------------------------- mapper_test.go | 2 +- 2 files changed, 27 insertions(+), 31 deletions(-) diff --git a/mapper.go b/mapper.go index 1c080a66..e0ce24aa 100644 --- a/mapper.go +++ b/mapper.go @@ -154,7 +154,7 @@ func (mapper SnakeMapper) TableName(t string) string { // 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 struct{} +type GonicMapper map[string]bool func isASCIIUpper(r rune) bool { return 'A' <= r && r <= 'Z' @@ -193,8 +193,31 @@ func (mapper GonicMapper) Obj2Table(name string) string { return gonicCasedName(name) } -// A list of common initialisms taken from golang/lint -var commonInitialisms = map[string]bool{ +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) +} + +func (mapper GonicMapper) TableName(t string) string { + return t +} + +// A GonicMapper that contains a list of common initialisms taken from golang/lint +var LintGonicMapper = GonicMapper{ "API": true, "ASCII": true, "CPU": true, @@ -230,33 +253,6 @@ var commonInitialisms = map[string]bool{ "XSS": true, } -func gonicTitleCasedName(name string) string { - newstr := make([]rune, 0) - - name = strings.ToLower(name) - parts := strings.Split(name, "_") - - for _, p := range parts { - _, isInitialism := commonInitialisms[strings.ToUpper(p)] - for i, r := range p { - if i == 0 || isInitialism { - r = toASCIIUpper(r) - } - newstr = append(newstr, r) - } - } - - return string(newstr) -} - -func (mapper GonicMapper) Table2Obj(name string) string { - return gonicTitleCasedName(name) -} - -func (mapper GonicMapper) TableName(t string) string { - return t -} - // provide prefix table name support type PrefixMapper struct { Mapper IMapper diff --git a/mapper_test.go b/mapper_test.go index 7dff090a..043087a2 100644 --- a/mapper_test.go +++ b/mapper_test.go @@ -37,7 +37,7 @@ func TestGonicMapperToObj(t *testing.T) { } for in, expected := range testCases { - out := gonicTitleCasedName(in) + out := LintGonicMapper.Table2Obj(in) if out != expected { t.Errorf("Given %s, expected %s but got %s", in, expected, out) } From 6f7c9a71a7aef15f5f3240c6d06a46408b9765c0 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 12 Feb 2015 14:47:02 +0800 Subject: [PATCH 042/135] revert: remove TableName method from Mapper interface --- mapper.go | 40 ---------------------------------------- 1 file changed, 40 deletions(-) diff --git a/mapper.go b/mapper.go index 3cc4735e..627f54bf 100644 --- a/mapper.go +++ b/mapper.go @@ -9,7 +9,6 @@ import ( type IMapper interface { Obj2Table(string) string Table2Obj(string) string - TableName(string) string } type CacheMapper struct { @@ -56,10 +55,6 @@ func (m *CacheMapper) Table2Obj(t string) string { return o } -func (m *CacheMapper) TableName(t string) string { - return t -} - // SameMapper implements IMapper and provides same name between struct and // database table type SameMapper struct { @@ -73,10 +68,6 @@ func (m SameMapper) Table2Obj(t string) string { return t } -func (m SameMapper) TableName(t string) string { - return t -} - // SnakeMapper implements IMapper and provides name transaltion between // struct and database table type SnakeMapper struct { @@ -97,25 +88,6 @@ func snakeCasedName(name string) string { return string(newstr) } -/*func pascal2Sql(s string) (d string) { - d = "" - lastIdx := 0 - for i := 0; i < len(s); i++ { - if s[i] >= 'A' && s[i] <= 'Z' { - if lastIdx < i { - d += s[lastIdx+1 : i] - } - if i != 0 { - d += "_" - } - d += string(s[i] + 32) - lastIdx = i - } - } - d += s[lastIdx+1:] - return -}*/ - func (mapper SnakeMapper) Obj2Table(name string) string { return snakeCasedName(name) } @@ -148,10 +120,6 @@ func (mapper SnakeMapper) Table2Obj(name string) string { return titleCasedName(name) } -func (mapper SnakeMapper) TableName(t string) string { - return t -} - // provide prefix table name support type PrefixMapper struct { Mapper IMapper @@ -166,10 +134,6 @@ func (mapper PrefixMapper) Table2Obj(name string) string { return mapper.Mapper.Table2Obj(name[len(mapper.Prefix):]) } -func (mapper PrefixMapper) TableName(name string) string { - return mapper.Prefix + name -} - func NewPrefixMapper(mapper IMapper, prefix string) PrefixMapper { return PrefixMapper{mapper, prefix} } @@ -188,10 +152,6 @@ func (mapper SuffixMapper) Table2Obj(name string) string { return mapper.Mapper.Table2Obj(name[:len(name)-len(mapper.Suffix)]) } -func (mapper SuffixMapper) TableName(name string) string { - return name + mapper.Suffix -} - func NewSuffixMapper(mapper IMapper, suffix string) SuffixMapper { return SuffixMapper{mapper, suffix} } From 16cb27928fdacf0502da3b20fe853befb3c09c26 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 13 Feb 2015 18:46:32 +0800 Subject: [PATCH 043/135] add support for get column value of map --- column.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/column.go b/column.go index 18921ca7..52468aa2 100644 --- a/column.go +++ b/column.go @@ -121,6 +121,21 @@ func (col *Column) ValueOfV(dataStruct *reflect.Value) (*reflect.Value, error) { col.fieldPath = strings.Split(col.FieldName, ".") } + if dataStruct.Type().Kind() == reflect.Map { + var keyValue reflect.Value + + if len(col.fieldPath) == 1 { + keyValue = reflect.ValueOf(col.FieldName) + } else if len(col.fieldPath) == 2 { + keyValue = reflect.ValueOf(col.fieldPath[1]) + } else { + return nil, fmt.Errorf("Unsupported mutliderive %v", col.FieldName) + } + + fieldValue = dataStruct.MapIndex(keyValue) + return &fieldValue, nil + } + if len(col.fieldPath) == 1 { fieldValue = dataStruct.FieldByName(col.FieldName) } else if len(col.fieldPath) == 2 { From 833d5b7beb43e6dd2a50dd65f0993068abd4c316 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 17 Feb 2015 15:00:51 +0800 Subject: [PATCH 044/135] add isregular for index --- dialect.go | 24 +++++++----------------- index.go | 25 +++++++++++++++++++++---- 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/dialect.go b/dialect.go index 05375642..29486d5d 100644 --- a/dialect.go +++ b/dialect.go @@ -52,10 +52,7 @@ type Dialect interface { IndexCheckSql(tableName, idxName string) (string, []interface{}) TableCheckSql(tableName string) (string, []interface{}) - //ColumnCheckSql(tableName, colName string) (string, []interface{}) - //IsTableExist(tableName string) (bool, error) - //IsIndexExist(tableName string, idx *Index) (bool, error) IsColumnExist(tableName string, col *Column) (bool, error) CreateTableSql(table *Table, tableName, storeEngine, charset string) string @@ -176,10 +173,8 @@ func (db *Base) CreateIndexSql(tableName string, index *Index) string { var idxName string if index.Type == UniqueType { unique = " UNIQUE" - idxName = fmt.Sprintf("UQE_%v_%v", tableName, index.Name) - } else { - idxName = fmt.Sprintf("IDX_%v_%v", tableName, index.Name) } + 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(",")))) @@ -187,18 +182,13 @@ func (db *Base) CreateIndexSql(tableName string, index *Index) string { func (db *Base) DropIndexSql(tableName string, index *Index) string { quote := db.dialect.Quote - //var unique string - var idxName string = index.Name - if !strings.HasPrefix(idxName, "UQE_") && - !strings.HasPrefix(idxName, "IDX_") { - if index.Type == UniqueType { - idxName = fmt.Sprintf("UQE_%v_%v", tableName, index.Name) - } else { - idxName = fmt.Sprintf("IDX_%v_%v", tableName, index.Name) - } + var name string + if index.IsRegular { + name = index.XName(tableName) + } else { + name = index.Name } - return fmt.Sprintf("DROP INDEX %v ON %s", - quote(idxName), quote(tableName)) + return fmt.Sprintf("DROP INDEX %v ON %s", quote(name), quote(tableName)) } func (db *Base) ModifyColumnSql(tableName string, col *Column) string { diff --git a/index.go b/index.go index e8f447d7..73b95175 100644 --- a/index.go +++ b/index.go @@ -1,7 +1,9 @@ package core import ( + "fmt" "sort" + "strings" ) const ( @@ -11,9 +13,21 @@ const ( // database index type Index struct { - Name string - Type int - Cols []string + 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_") { + 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 } // add columns which will be composite index @@ -24,6 +38,9 @@ func (index *Index) AddColumn(cols ...string) { } func (index *Index) Equal(dst *Index) bool { + if index.Type != dst.Type { + return false + } if len(index.Cols) != len(dst.Cols) { return false } @@ -40,5 +57,5 @@ func (index *Index) Equal(dst *Index) bool { // new an index func NewIndex(name string, indexType int) *Index { - return &Index{name, indexType, make([]string, 0)} + return &Index{true, name, indexType, make([]string, 0)} } From 67595488654a06be0fb353cbb68ce54e1f25d01e Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 23 Feb 2015 00:00:10 +0800 Subject: [PATCH 045/135] add columntype method for table --- table.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/table.go b/table.go index f7c9d464..aba1f96e 100644 --- a/table.go +++ b/table.go @@ -65,13 +65,18 @@ func (table *Table) GetColumnIdx(name string, idx int) *Column { // if has primary key, return column func (table *Table) PKColumns() []*Column { - columns := make([]*Column, 0) - for _, name := range table.PrimaryKeys { - columns = append(columns, table.GetColumn(name)) + 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) } From b7558de38a2f628b3c80ae84d251f07f0112d37e Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 25 Feb 2015 15:48:20 +0800 Subject: [PATCH 046/135] add readme --- README.md | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..e36c7621 --- /dev/null +++ b/README.md @@ -0,0 +1,74 @@ +[中文](https://github.com/go-xorm/xorm/blob/master/README_CN.md) + +Core is a light wrapper of sql.DB. + +# Open +```Go +db, _ := core.Open(db, connstr) +``` + +# SetMapper +```Go +db.SetMapper(SameMapper()) +``` + +# More scan usage +```Go + +rows, _ := db.Query() +for rows.Next() { + rows.Scan() + rows.ScanMap() + rows.ScanSlice() + rows.ScanStructByName() + rows.ScanStructByIndex() +} +``` + +# More Query usage +```Go +rows, err := db.Query("select * from table where name = ?", name) + +rows, err := db.QueryStruct("select * from table where name = ?Name", + &user) + +var user = map[string]interface{}{ + "name": "lunny", +} +rows, err = db.QueryMap("select * from table where name = ?name", + &user) +``` + +# More QueryRow usage +```Go +rows, err := db.QueryRow("select * from table where name = ?", name) + +rows, err := db.QueryRowStruct("select * from table where name = ?Name", + &user) +var user = map[string]interface{}{ + "name": "lunny", +} +rows, err = db.QueryRowMap("select * from table where name = ?name", + &user) +``` + +# More Exec usage +```Go +db.Exec("insert into user (`name`, title, age, alias, nick_name,created) values (?,?,?,?,?,?)", name, title, age, alias...) + +user = User{ + Name:"lunny", + Title:"test", + Age: 18, +} +result, err = db.ExecStruct("insert into user (`name`, title, age, alias, nick_name,created) values (?Name,?Title,?Age,?Alias,?NickName,?Created)", + &user) + +var user = map[string]interface{}{ + "Name": "lunny", + "Title": "test", + "Age": 18, +} +result, err = db.ExecMap("insert into user (`name`, title, age, alias, nick_name,created) values (?Name,?Title,?Age,?Alias,?NickName,?Created)", + &user) +``` \ No newline at end of file From 182b0c437ac81d4abaecf68446031f9c2a200742 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 25 Feb 2015 15:49:18 +0800 Subject: [PATCH 047/135] improved readme --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index e36c7621..8fe83853 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,4 @@ -[中文](https://github.com/go-xorm/xorm/blob/master/README_CN.md) - -Core is a light wrapper of sql.DB. +Core is a lightweight wrapper of sql.DB. # Open ```Go From 8d883536e3ec69f5553f6846454e6c6cfe33e504 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 25 Feb 2015 16:01:40 +0800 Subject: [PATCH 048/135] improved readme --- README.md | 60 ++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 51 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 8fe83853..0ae94a58 100644 --- a/README.md +++ b/README.md @@ -10,23 +10,61 @@ db, _ := core.Open(db, connstr) db.SetMapper(SameMapper()) ``` -# More scan usage -```Go +## Scan usage +### Scan +```Go rows, _ := db.Query() for rows.Next() { rows.Scan() +} +``` + +### ScanMap +```Go +rows, _ := db.Query() +for rows.Next() { rows.ScanMap() - rows.ScanSlice() +``` + +### ScanSlice + +You can use `[]string`, `[][]byte`, `[]interface{}`, `[]*string`, `[]sql.NullString` to ScanSclice. Notice, slice's length should be equal or less than select columns. + +```Go +rows, _ := db.Query() +cols, _ := rows.Columns() +for rows.Next() { + var s = make([]string, len(cols)) + rows.ScanSlice(&s) +} +``` + +```Go +rows, _ := db.Query() +cols, _ := rows.Columns() +for rows.Next() { + var s = make([]*string, len(cols)) + rows.ScanSlice(&s) +} +``` + +### ScanStruct +```Go +rows, _ := db.Query() +for rows.Next() { rows.ScanStructByName() rows.ScanStructByIndex() } ``` -# More Query usage +## Query usage ```Go rows, err := db.Query("select * from table where name = ?", name) +user = User{ + Name:"lunny", +} rows, err := db.QueryStruct("select * from table where name = ?Name", &user) @@ -37,20 +75,24 @@ rows, err = db.QueryMap("select * from table where name = ?name", &user) ``` -# More QueryRow usage +## QueryRow usage ```Go -rows, err := db.QueryRow("select * from table where name = ?", name) +row := db.QueryRow("select * from table where name = ?", name) -rows, err := db.QueryRowStruct("select * from table where name = ?Name", +user = User{ + Name:"lunny", +} +row := db.QueryRowStruct("select * from table where name = ?Name", &user) + var user = map[string]interface{}{ "name": "lunny", } -rows, err = db.QueryRowMap("select * from table where name = ?name", +row = db.QueryRowMap("select * from table where name = ?name", &user) ``` -# More Exec usage +## Exec usage ```Go db.Exec("insert into user (`name`, title, age, alias, nick_name,created) values (?,?,?,?,?,?)", name, title, age, alias...) From 076f7bbf08221b6bcad6afaa9cb290c17e318fbc Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 3 Mar 2015 15:06:48 +0800 Subject: [PATCH 049/135] add MustDropTable method for dialect --- dialect.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/dialect.go b/dialect.go index 29486d5d..dc8b73ca 100644 --- a/dialect.go +++ b/dialect.go @@ -56,21 +56,17 @@ type Dialect interface { IsColumnExist(tableName string, col *Column) (bool, error) CreateTableSql(table *Table, tableName, storeEngine, charset string) string - DropTableSql(tableName string) string + //DropTableSql(tableName string) string CreateIndexSql(tableName string, index *Index) string DropIndexSql(tableName string, index *Index) string ModifyColumnSql(tableName string, col *Column) string + MustDropTable(tableName string) error GetColumns(tableName string) ([]string, map[string]*Column, error) GetTables() ([]*Table, error) GetIndexes(tableName string) (map[string]*Index, error) - // Get data from db cell to a struct's field - //GetData(col *Column, fieldValue *reflect.Value, cellData interface{}) error - // Set field data to db - //SetData(col *Column, fieldValue *refelct.Value) (interface{}, error) - Filters() []Filter } @@ -145,6 +141,11 @@ func (db *Base) DropTableSql(tableName string) string { return fmt.Sprintf("DROP TABLE IF EXISTS `%s`", tableName) } +func (db *Base) MustDropTable(tableName string) error { + _, err := db.db.Exec(db.DropTableSql(tableName)) + return err +} + func (db *Base) HasRecords(query string, args ...interface{}) (bool, error) { rows, err := db.DB().Query(query, args...) if db.Logger != nil { From a9aba4b93553420e2c5fd8b0dccfc2ee6f56bee1 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 12 Mar 2015 18:04:32 +0800 Subject: [PATCH 050/135] add oci8 support --- dialect.go | 38 ++++++++++++++++++++++++++++++++++---- type.go | 6 ++++-- 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/dialect.go b/dialect.go index dc8b73ca..183f85d0 100644 --- a/dialect.go +++ b/dialect.go @@ -56,13 +56,15 @@ type Dialect interface { IsColumnExist(tableName string, col *Column) (bool, error) CreateTableSql(table *Table, tableName, storeEngine, charset string) string - //DropTableSql(tableName string) string + DropTableSql(tableName string) string CreateIndexSql(tableName string, index *Index) string DropIndexSql(tableName string, index *Index) string ModifyColumnSql(tableName string, col *Column) string + //CreateTableIfNotExists(table *Table, tableName, storeEngine, charset string) error MustDropTable(tableName string) error + GetColumns(tableName string) ([]string, map[string]*Column, error) GetTables() ([]*Table, error) GetIndexes(tableName string) (map[string]*Index, error) @@ -138,11 +140,15 @@ func (db *Base) RollBackStr() string { } func (db *Base) DropTableSql(tableName string) string { - return fmt.Sprintf("DROP TABLE IF EXISTS `%s`", tableName) + return fmt.Sprintf("DROP TABLE IF EXISTS `%s`;", tableName) } func (db *Base) MustDropTable(tableName string) error { - _, err := db.db.Exec(db.DropTableSql(tableName)) + query := db.DropTableSql(tableName) + _, err := db.db.Exec(query) + if db.Logger != nil { + db.Logger.Info("[sql]", query) + } return err } @@ -168,6 +174,30 @@ func (db *Base) IsColumnExist(tableName string, col *Column) (bool, error) { return db.HasRecords(query, db.DbName, tableName, col.Name) } +/* +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 *Index) string { quote := db.dialect.Quote var unique string @@ -176,7 +206,7 @@ func (db *Base) CreateIndexSql(tableName string, index *Index) string { unique = " UNIQUE" } idxName = index.XName(tableName) - return fmt.Sprintf("CREATE%s INDEX %v ON %v (%v);", unique, + return fmt.Sprintf("CREATE%s INDEX %v ON %v (%v)", unique, quote(idxName), quote(tableName), quote(strings.Join(index.Cols, quote(",")))) } diff --git a/type.go b/type.go index ee765682..73b9921e 100644 --- a/type.go +++ b/type.go @@ -70,6 +70,7 @@ var ( NVarchar = "NVARCHAR" TinyText = "TINYTEXT" Text = "TEXT" + Clob = "CLOB" MediumText = "MEDIUMTEXT" LongText = "LONGTEXT" Uuid = "UUID" @@ -120,6 +121,7 @@ var ( MediumText: TEXT_TYPE, LongText: TEXT_TYPE, Uuid: TEXT_TYPE, + Clob: TEXT_TYPE, Date: TIME_TYPE, DateTime: TIME_TYPE, @@ -250,7 +252,7 @@ func Type2SQLType(t reflect.Type) (st SQLType) { case reflect.String: st = SQLType{Varchar, 255, 0} case reflect.Struct: - if t == reflect.TypeOf(c_TIME_DEFAULT) { + if t.ConvertibleTo(reflect.TypeOf(c_TIME_DEFAULT)) { st = SQLType{DateTime, 0, 0} } else { // TODO need to handle association struct @@ -303,7 +305,7 @@ func SQLType2Type(st SQLType) reflect.Type { return reflect.TypeOf(float32(1)) case Double: return reflect.TypeOf(float64(1)) - case Char, Varchar, NVarchar, TinyText, Text, MediumText, LongText, Enum, Set, Uuid: + case Char, Varchar, NVarchar, TinyText, Text, MediumText, LongText, Enum, Set, Uuid, Clob: return reflect.TypeOf("") case TinyBlob, Blob, LongBlob, Bytea, Binary, MediumBlob, VarBinary: return reflect.TypeOf([]byte{}) From c6d2321d6328ca56d559cee71beb88f2533f233d Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 13 Mar 2015 15:19:50 +0800 Subject: [PATCH 051/135] interface changed for adding dropifexist --- dialect.go | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/dialect.go b/dialect.go index 183f85d0..43a22670 100644 --- a/dialect.go +++ b/dialect.go @@ -47,6 +47,7 @@ type Dialect interface { SupportInsertMany() bool SupportEngine() bool SupportCharset() bool + SupportDropIfExists() bool IndexOnTable() bool ShowCreateNull() bool @@ -63,7 +64,7 @@ type Dialect interface { ModifyColumnSql(tableName string, col *Column) string //CreateTableIfNotExists(table *Table, tableName, storeEngine, charset string) error - MustDropTable(tableName string) error + //MustDropTable(tableName string) error GetColumns(tableName string) ([]string, map[string]*Column, error) GetTables() ([]*Table, error) @@ -139,17 +140,12 @@ func (db *Base) RollBackStr() string { return "ROLL BACK" } -func (db *Base) DropTableSql(tableName string) string { - return fmt.Sprintf("DROP TABLE IF EXISTS `%s`;", tableName) +func (db *Base) SupportDropIfExists() bool { + return true } -func (db *Base) MustDropTable(tableName string) error { - query := db.DropTableSql(tableName) - _, err := db.db.Exec(query) - if db.Logger != nil { - db.Logger.Info("[sql]", query) - } - return err +func (db *Base) DropTableSql(tableName string) string { + return fmt.Sprintf("DROP TABLE IF EXISTS `%s`", tableName) } func (db *Base) HasRecords(query string, args ...interface{}) (bool, error) { From be6e7ac47dc57bd0ada25322fa526944f66ccaa6 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 14 Mar 2015 22:16:36 +0800 Subject: [PATCH 052/135] bug fixed for PK marshal & unmarshl --- pk.go | 17 +++++++++-------- pk_test.go | 11 +++++++++++ 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/pk.go b/pk.go index 61d1371e..1810dd94 100644 --- a/pk.go +++ b/pk.go @@ -1,7 +1,8 @@ package core import ( - "encoding/json" + "bytes" + "encoding/gob" ) type PK []interface{} @@ -12,14 +13,14 @@ func NewPK(pks ...interface{}) *PK { } func (p *PK) ToString() (string, error) { - bs, err := json.Marshal(*p) - if err != nil { - return "", nil - } - - return string(bs), nil + buf := new(bytes.Buffer) + enc := gob.NewEncoder(buf) + err := enc.Encode(*p) + return buf.String(), err } func (p *PK) FromString(content string) error { - return json.Unmarshal([]byte(content), p) + dec := gob.NewDecoder(bytes.NewBufferString(content)) + err := dec.Decode(p) + return err } diff --git a/pk_test.go b/pk_test.go index 5245e574..05486086 100644 --- a/pk_test.go +++ b/pk_test.go @@ -2,6 +2,7 @@ package core import ( "fmt" + "reflect" "testing" ) @@ -19,4 +20,14 @@ func TestPK(t *testing.T) { t.Error(err) } fmt.Println(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])) + } + } } From 8cc025cd8e89196cf8932ef7e3b102e71772b8aa Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 1 Apr 2015 16:16:22 +0800 Subject: [PATCH 053/135] use gob to instead json to store pk & added json type --- cache.go | 19 ++++++++++++------- type.go | 4 +++- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/cache.go b/cache.go index 2a61ef75..bf81bd52 100644 --- a/cache.go +++ b/cache.go @@ -1,10 +1,11 @@ package core import ( - "encoding/json" "errors" "fmt" "time" + "bytes" + "encoding/gob" ) const ( @@ -47,16 +48,20 @@ type Cacher interface { } func encodeIds(ids []PK) (string, error) { - b, err := json.Marshal(ids) - if err != nil { - return "", err - } - return string(b), nil + buf := new(bytes.Buffer) + enc := gob.NewEncoder(buf) + err := enc.Encode(ids) + + return buf.String(), err } + func decodeIds(s string) ([]PK, error) { pks := make([]PK, 0) - err := json.Unmarshal([]byte(s), &pks) + + dec := gob.NewDecoder(bytes.NewBufferString(s)) + err := dec.Decode(&pks) + return pks, err } diff --git a/type.go b/type.go index 73b9921e..d577adac 100644 --- a/type.go +++ b/type.go @@ -101,6 +101,8 @@ var ( Serial = "SERIAL" BigSerial = "BIGSERIAL" + Json = "JSON" + SqlTypes = map[string]int{ Bit: NUMERIC_TYPE, TinyInt: NUMERIC_TYPE, @@ -112,6 +114,7 @@ var ( Enum: TEXT_TYPE, Set: TEXT_TYPE, + Json: TEXT_TYPE, Char: TEXT_TYPE, Varchar: TEXT_TYPE, @@ -229,7 +232,6 @@ var ( ) 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} From 0c84d4c49e1d83083d451918456c5fc75da741e2 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 7 Apr 2015 20:15:05 +0800 Subject: [PATCH 054/135] add FromDB function --- db.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/db.go b/db.go index 1f70a926..3b1d70dd 100644 --- a/db.go +++ b/db.go @@ -46,6 +46,10 @@ func Open(driverName, dataSourceName string) (*DB, error) { return &DB{db, NewCacheMapper(&SnakeMapper{})}, err } +func FromDB(db *sql.DB) *DB { + return &DB{db, NewCacheMapper(&SnakeMapper{})} +} + func (db *DB) Query(query string, args ...interface{}) (*Rows, error) { rows, err := db.DB.Query(query, args...) return &Rows{rows, db.Mapper}, err From 7623fc1c67510734bc2547a40e9e8cd52967eb84 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 19 May 2015 22:38:07 +0800 Subject: [PATCH 055/135] dialect interface changed --- dialect.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dialect.go b/dialect.go index 43a22670..c39a1771 100644 --- a/dialect.go +++ b/dialect.go @@ -54,7 +54,7 @@ type Dialect interface { IndexCheckSql(tableName, idxName string) (string, []interface{}) TableCheckSql(tableName string) (string, []interface{}) - IsColumnExist(tableName string, col *Column) (bool, error) + IsColumnExist(tableName string, colName string) (bool, error) CreateTableSql(table *Table, tableName, storeEngine, charset string) string DropTableSql(tableName string) string @@ -164,10 +164,10 @@ func (db *Base) HasRecords(query string, args ...interface{}) (bool, error) { return false, nil } -func (db *Base) IsColumnExist(tableName string, col *Column) (bool, error) { +func (db *Base) IsColumnExist(tableName, colName string) (bool, error) { query := "SELECT `COLUMN_NAME` FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ? AND `COLUMN_NAME` = ?" query = strings.Replace(query, "`", db.dialect.QuoteStr(), -1) - return db.HasRecords(query, db.DbName, tableName, col.Name) + return db.HasRecords(query, db.DbName, tableName, colName) } /* From bacc62db6eb774727b3546dde7020c0bbff0f1ca Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 26 Jun 2015 21:20:29 +0800 Subject: [PATCH 056/135] bug fixed for go-xorm/xorm#261 --- dialect.go | 10 +++++----- type.go | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/dialect.go b/dialect.go index c39a1771..b72aa8e1 100644 --- a/dialect.go +++ b/dialect.go @@ -267,16 +267,16 @@ func (b *Base) CreateTableSql(table *Table, tableName, storeEngine, charset stri } var ( - dialects = map[DbType]Dialect{} + dialects = map[DbType]func() Dialect{} ) -func RegisterDialect(dbName DbType, dialect Dialect) { - if dialect == nil { +func RegisterDialect(dbName DbType, dialectFunc func() Dialect) { + if dialectFunc == nil { panic("core: Register dialect is nil") } - dialects[dbName] = dialect // !nashtsai! allow override dialect + dialects[dbName] = dialectFunc // !nashtsai! allow override dialect } func QueryDialect(dbName DbType) Dialect { - return dialects[dbName] + return dialects[dbName]() } diff --git a/type.go b/type.go index d577adac..a3c15ff9 100644 --- a/type.go +++ b/type.go @@ -254,7 +254,7 @@ func Type2SQLType(t reflect.Type) (st SQLType) { case reflect.String: st = SQLType{Varchar, 255, 0} case reflect.Struct: - if t.ConvertibleTo(reflect.TypeOf(c_TIME_DEFAULT)) { + if t.ConvertibleTo(TimeType) { st = SQLType{DateTime, 0, 0} } else { // TODO need to handle association struct From 4813c0110dc4ea27e05d77425385b4cd4abfe81b Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 6 Aug 2015 15:24:21 +0800 Subject: [PATCH 057/135] added IsJson for SQLType --- type.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/type.go b/type.go index a3c15ff9..c7c4d7bd 100644 --- a/type.go +++ b/type.go @@ -53,6 +53,10 @@ func (s *SQLType) IsNumeric() bool { return s.IsType(NUMERIC_TYPE) } +func (s *SQLType) IsJson() bool { + return s.Name == Json +} + var ( Bit = "BIT" TinyInt = "TINYINT" From 1246ee6c93b704abbd1aed48af869c16483903d3 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 28 Aug 2015 17:00:48 +0800 Subject: [PATCH 058/135] create empty table support for postgres --- dialect.go | 41 +++++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/dialect.go b/dialect.go index b72aa8e1..8615f17d 100644 --- a/dialect.go +++ b/dialect.go @@ -229,28 +229,33 @@ func (b *Base) CreateTableSql(table *Table, tableName, storeEngine, charset stri tableName = table.Name } - sql += b.dialect.Quote(tableName) + " (" + sql += b.dialect.Quote(tableName) + sql += " (" - pkList := table.PrimaryKeys + if len(table.ColumnsSeq()) > 0 { + pkList := table.PrimaryKeys - for _, colName := range table.ColumnsSeq() { - col := table.GetColumn(colName) - if col.IsPrimaryKey && len(pkList) == 1 { - sql += col.String(b.dialect) - } else { - sql += col.StringNoPk(b.dialect) + for _, colName := range table.ColumnsSeq() { + col := table.GetColumn(colName) + if col.IsPrimaryKey && len(pkList) == 1 { + sql += col.String(b.dialect) + } else { + sql += col.StringNoPk(b.dialect) + } + sql = strings.TrimSpace(sql) + sql += ", " } - sql = strings.TrimSpace(sql) - sql += ", " - } - if len(pkList) > 1 { - sql += "PRIMARY KEY ( " - sql += b.dialect.Quote(strings.Join(pkList, b.dialect.Quote(","))) - 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 += ")" - sql = sql[:len(sql)-2] + ")" if b.dialect.SupportEngine() && storeEngine != "" { sql += " ENGINE=" + storeEngine } @@ -262,7 +267,7 @@ func (b *Base) CreateTableSql(table *Table, tableName, storeEngine, charset stri sql += " DEFAULT CHARSET " + charset } } - sql += ";" + return sql } From 1686396e05a832dbda7d34ce6c9b2611b17e1e33 Mon Sep 17 00:00:00 2001 From: evalphobia Date: Sun, 30 Aug 2015 20:02:25 +0900 Subject: [PATCH 059/135] Added feature for SELECT ... FOR UPDATE go-xorm/xorm #290 --- dialect.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/dialect.go b/dialect.go index 8615f17d..45bc5c20 100644 --- a/dialect.go +++ b/dialect.go @@ -63,6 +63,8 @@ type Dialect interface { ModifyColumnSql(tableName string, col *Column) string + ForUpdateSql(query string) string + //CreateTableIfNotExists(table *Table, tableName, storeEngine, charset string) error //MustDropTable(tableName string) error @@ -271,6 +273,10 @@ func (b *Base) CreateTableSql(table *Table, tableName, storeEngine, charset stri return sql } +func (b *Base) ForUpdateSql(query string) string { + return query + " FOR UPDATE" +} + var ( dialects = map[DbType]func() Dialect{} ) From 8e56c9684cbb946ebcd849079cf6510ec5578148 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 4 Sep 2015 23:59:42 +0800 Subject: [PATCH 060/135] resolved #8 --- LICENSE | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..9c1d175d --- /dev/null +++ b/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2013 - 2015 +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the {organization} nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. From 02a443a685c0e265dc33e5ff4e8e045685d8f4f8 Mon Sep 17 00:00:00 2001 From: Alexandre Viau Date: Fri, 4 Sep 2015 12:26:30 -0400 Subject: [PATCH 061/135] include copyright name + email --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 9c1d175d..11307978 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2013 - 2015 +Copyright (c) 2013 - 2015 Lunny Xiao All rights reserved. Redistribution and use in source and binary forms, with or without From 5d275bdbcdab384b12b3647eebd4b0f5177a86c1 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 9 Sep 2015 09:24:26 +0800 Subject: [PATCH 062/135] error message fixed --- error.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/error.go b/error.go index 414ba037..13c17925 100644 --- a/error.go +++ b/error.go @@ -4,7 +4,7 @@ import "errors" var ( ErrNoMapPointer = errors.New("mp should be a map's pointer") - ErrNoStructPointer = errors.New("mp should be a map's pointer") + ErrNoStructPointer = errors.New("mp should be a struct's pointer") //ErrNotExist = errors.New("Not exist") //ErrIgnore = errors.New("Ignore") ) From acb6f00daf10d838c98599c81562f65c850dfbde Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 25 Nov 2015 17:54:33 +0800 Subject: [PATCH 063/135] extends unlimit levels support --- column.go | 54 +++++++++++++++++------------------------------------- 1 file changed, 17 insertions(+), 37 deletions(-) diff --git a/column.go b/column.go index 52468aa2..98d22c22 100644 --- a/column.go +++ b/column.go @@ -1,7 +1,6 @@ package core import ( - "errors" "fmt" "reflect" "strings" @@ -122,50 +121,31 @@ func (col *Column) ValueOfV(dataStruct *reflect.Value) (*reflect.Value, error) { } if dataStruct.Type().Kind() == reflect.Map { - var keyValue reflect.Value - - if len(col.fieldPath) == 1 { - keyValue = reflect.ValueOf(col.FieldName) - } else if len(col.fieldPath) == 2 { - keyValue = reflect.ValueOf(col.fieldPath[1]) - } else { - return nil, fmt.Errorf("Unsupported mutliderive %v", col.FieldName) - } - + keyValue := reflect.ValueOf(col.fieldPath[len(col.fieldPath)-1]) fieldValue = dataStruct.MapIndex(keyValue) return &fieldValue, nil } - if len(col.fieldPath) == 1 { - fieldValue = dataStruct.FieldByName(col.FieldName) - } else if len(col.fieldPath) == 2 { - parentField := dataStruct.FieldByName(col.fieldPath[0]) - if parentField.IsValid() { - if parentField.Kind() == reflect.Struct { - fieldValue = parentField.FieldByName(col.fieldPath[1]) - } else if parentField.Kind() == reflect.Ptr { - if parentField.IsNil() { - parentField.Set(reflect.New(parentField.Type().Elem())) - fieldValue = parentField.Elem().FieldByName(col.fieldPath[1]) - } else { - parentField = parentField.Elem() - if parentField.IsValid() { - fieldValue = parentField.FieldByName(col.fieldPath[1]) - } else { - return nil, fmt.Errorf("field %v is not valid", col.FieldName) - } - } - } - } else { - // so we can use a different struct as conditions - fieldValue = dataStruct.FieldByName(col.fieldPath[1]) + level := len(col.fieldPath) + fieldValue = dataStruct.FieldByName(col.fieldPath[0]) + for i := 0; i < level-1; i++ { + if !fieldValue.IsValid() { + break + } + if fieldValue.Kind() == reflect.Struct { + fieldValue = fieldValue.FieldByName(col.fieldPath[i+1]) + } else if fieldValue.Kind() == reflect.Ptr { + if fieldValue.IsNil() { + fieldValue.Set(reflect.New(fieldValue.Type().Elem())) + } + fieldValue = fieldValue.Elem().FieldByName(col.fieldPath[i+1]) + } else { + return nil, fmt.Errorf("field %v is not valid", col.FieldName) } - } else { - return nil, fmt.Errorf("Unsupported mutliderive %v", col.FieldName) } if !fieldValue.IsValid() { - return nil, errors.New("no find field matched") + return nil, fmt.Errorf("field %v is not valid", col.FieldName) } return &fieldValue, nil From 7da81a8908a84590e2877dbf8120ca1b62078e13 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 30 Dec 2015 16:14:49 +0800 Subject: [PATCH 064/135] improved error check --- db.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/db.go b/db.go index 3b1d70dd..a7947b05 100644 --- a/db.go +++ b/db.go @@ -43,7 +43,10 @@ type DB struct { func Open(driverName, dataSourceName string) (*DB, error) { db, err := sql.Open(driverName, dataSourceName) - return &DB{db, NewCacheMapper(&SnakeMapper{})}, err + if err != nil { + return nil, err + } + return &DB{db, NewCacheMapper(&SnakeMapper{})}, nil } func FromDB(db *sql.DB) *DB { @@ -52,7 +55,13 @@ func FromDB(db *sql.DB) *DB { func (db *DB) Query(query string, args ...interface{}) (*Rows, error) { rows, err := db.DB.Query(query, args...) - return &Rows{rows, db.Mapper}, err + if err != nil { + if rows != nil { + rows.Close() + } + return nil, err + } + return &Rows{rows, db.Mapper}, nil } func (db *DB) QueryMap(query string, mp interface{}) (*Rows, error) { From 85579d38adea6d9c4e01b339129ba077d98e55e8 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 8 Jan 2016 14:33:03 +0800 Subject: [PATCH 065/135] added QueryMap QueryStruct and etc. for Row --- db.go | 108 ++++++++++++++++++++++++++++++++++++++++++++--------- db_test.go | 8 ++-- scan.go | 52 ++++++++++++++++++++++++++ 3 files changed, 147 insertions(+), 21 deletions(-) create mode 100644 scan.go diff --git a/db.go b/db.go index a7947b05..169d8553 100644 --- a/db.go +++ b/db.go @@ -2,6 +2,7 @@ package core import ( "database/sql" + "database/sql/driver" "errors" "reflect" "regexp" @@ -29,10 +30,24 @@ func StructToSlice(query string, st interface{}) (string, []interface{}, error) } args := make([]interface{}, 0) + var err error query = re.ReplaceAllStringFunc(query, func(src string) string { - args = append(args, vv.Elem().FieldByName(src[1:]).Interface()) + 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 } @@ -81,28 +96,87 @@ func (db *DB) QueryStruct(query string, st interface{}) (*Rows, error) { } type Row struct { - *sql.Row + rows *Rows // One of these two will be non-nil: - err error // deferred error for easy chaining - Mapper IMapper + err error // deferred error for easy chaining +} + +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 } - return row.Row.Scan(dest...) + 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. + if err := row.rows.Close(); err != nil { + return err + } + + return nil +} + +func (row *Row) ScanStructByName(dest interface{}) error { + if row.err != nil { + return row.err + } + return row.rows.ScanStructByName(dest) +} + +func (row *Row) ScanStructByIndex(dest interface{}) error { + if row.err != nil { + return row.err + } + return row.rows.ScanStructByIndex(dest) +} + +// 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 + } + return row.rows.ScanSlice(dest) +} + +// scan data to a map's pointer +func (row *Row) ScanMap(dest interface{}) error { + if row.err != nil { + return row.err + } + return row.rows.ScanMap(dest) } func (db *DB) QueryRow(query string, args ...interface{}) *Row { - row := db.DB.QueryRow(query, args...) - return &Row{row, nil, db.Mapper} + rows, err := db.Query(query, args...) + return &Row{rows, err} } func (db *DB) QueryRowMap(query string, mp interface{}) *Row { query, args, err := MapToSlice(query, mp) if err != nil { - return &Row{nil, err, db.Mapper} + return &Row{nil, err} } return db.QueryRow(query, args...) } @@ -110,7 +184,7 @@ func (db *DB) QueryRowMap(query string, mp interface{}) *Row { func (db *DB) QueryRowStruct(query string, st interface{}) *Row { query, args, err := StructToSlice(query, st) if err != nil { - return &Row{nil, err, db.Mapper} + return &Row{nil, err} } return db.QueryRow(query, args...) } @@ -200,14 +274,14 @@ func (s *Stmt) QueryStruct(st interface{}) (*Rows, error) { } func (s *Stmt) QueryRow(args ...interface{}) *Row { - row := s.Stmt.QueryRow(args...) - return &Row{row, nil, s.Mapper} + rows, err := s.Query(args...) + return &Row{rows, err} } func (s *Stmt) QueryRowMap(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"), s.Mapper} + return &Row{nil, errors.New("mp should be a map's pointer")} } args := make([]interface{}, len(s.names)) @@ -221,7 +295,7 @@ func (s *Stmt) QueryRowMap(mp interface{}) *Row { func (s *Stmt) QueryRowStruct(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"), s.Mapper} + return &Row{nil, errors.New("st should be a struct's pointer")} } args := make([]interface{}, len(s.names)) @@ -553,14 +627,14 @@ func (tx *Tx) QueryStruct(query string, st interface{}) (*Rows, error) { } func (tx *Tx) QueryRow(query string, args ...interface{}) *Row { - row := tx.Tx.QueryRow(query, args...) - return &Row{row, nil, tx.Mapper} + rows, err := tx.Query(query, args...) + return &Row{rows, err} } func (tx *Tx) QueryRowMap(query string, mp interface{}) *Row { query, args, err := MapToSlice(query, mp) if err != nil { - return &Row{nil, err, tx.Mapper} + return &Row{nil, err} } return tx.QueryRow(query, args...) } @@ -568,7 +642,7 @@ func (tx *Tx) QueryRowMap(query string, mp interface{}) *Row { func (tx *Tx) QueryRowStruct(query string, st interface{}) *Row { query, args, err := StructToSlice(query, st) if err != nil { - return &Row{nil, err, tx.Mapper} + return &Row{nil, err} } return tx.QueryRow(query, args...) } diff --git a/db_test.go b/db_test.go index 65bf2414..94c4ea4f 100644 --- a/db_test.go +++ b/db_test.go @@ -24,7 +24,7 @@ type User struct { Age float32 Alias string NickName string - Created time.Time + Created NullTime } func init() { @@ -85,7 +85,7 @@ func BenchmarkOriQuery(b *testing.B) { var Id int64 var Name, Title, Alias, NickName string var Age float32 - var Created time.Time + var Created NullTime err = rows.Scan(&Id, &Name, &Title, &Age, &Alias, &NickName, &Created) if err != nil { b.Error(err) @@ -600,7 +600,7 @@ func TestExecStruct(t *testing.T) { Age: 1.2, Alias: "lunny", NickName: "lunny xiao", - Created: time.Now(), + Created: NullTime(time.Now()), } _, err = db.ExecStruct("insert into user (`name`, title, age, alias, nick_name,created) "+ @@ -645,7 +645,7 @@ func BenchmarkExecStruct(b *testing.B) { Age: 1.2, Alias: "lunny", NickName: "lunny xiao", - Created: time.Now(), + Created: NullTime(time.Now()), } for i := 0; i < b.N; i++ { diff --git a/scan.go b/scan.go new file mode 100644 index 00000000..7da338d8 --- /dev/null +++ b/scan.go @@ -0,0 +1,52 @@ +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 nil: + default: + return fmt.Errorf("unsupported driver -> Scan pair: %T -> %T", src, dest) + } + return nil +} From 41d16df21c2ed69a5cf547b1f734a9c6e4dacfe8 Mon Sep 17 00:00:00 2001 From: Henry Huang Date: Thu, 21 Jan 2016 15:44:47 +0800 Subject: [PATCH 066/135] improved: dataStruct's type equals interface --- column.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/column.go b/column.go index 98d22c22..4257f750 100644 --- a/column.go +++ b/column.go @@ -124,6 +124,9 @@ func (col *Column) ValueOfV(dataStruct *reflect.Value) (*reflect.Value, error) { keyValue := reflect.ValueOf(col.fieldPath[len(col.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(col.fieldPath) From 7aa53cce177bf856e0adf22fc654cbfeb19276ad Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 3 Feb 2016 11:10:20 +0800 Subject: [PATCH 067/135] added column specify time zone support --- column.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/column.go b/column.go index 98d22c22..72ee31b5 100644 --- a/column.go +++ b/column.go @@ -4,6 +4,7 @@ import ( "fmt" "reflect" "strings" + "time" ) const ( @@ -34,6 +35,8 @@ type Column struct { DefaultIsEmpty bool EnumOptions map[string]int SetOptions map[string]int + DisableTimeZone bool + TimeZone *time.Location // column specified time zone } func NewColumn(name, fieldName string, sqlType SQLType, len1, len2 int, nullable bool) *Column { From 0c32ca567a19e4a595fbf2b85044be16e7aabd39 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 16 Feb 2016 17:14:10 +0800 Subject: [PATCH 068/135] improved logging --- dialect.go | 18 +++++++++++++----- ilogger.go | 3 +++ 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/dialect.go b/dialect.go index 45bc5c20..34c5e5a9 100644 --- a/dialect.go +++ b/dialect.go @@ -84,7 +84,7 @@ type Base struct { dialect Dialect driverName string dataSourceName string - Logger ILogger + logger ILogger *Uri } @@ -93,7 +93,7 @@ func (b *Base) DB() *DB { } func (b *Base) SetLogger(logger ILogger) { - b.Logger = logger + b.logger = logger } func (b *Base) Init(db *DB, dialect Dialect, uri *Uri, drivername, dataSourceName string) error { @@ -151,10 +151,8 @@ func (db *Base) DropTableSql(tableName string) string { } func (db *Base) HasRecords(query string, args ...interface{}) (bool, error) { + db.LogSQL(query, args) rows, err := db.DB().Query(query, args...) - if db.Logger != nil { - db.Logger.Info("[sql]", query, args) - } if err != nil { return false, err } @@ -277,6 +275,16 @@ 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.Info("[sql]", sql, args) + } else { + b.logger.Info("[sql]", sql) + } + } +} + var ( dialects = map[DbType]func() Dialect{} ) diff --git a/ilogger.go b/ilogger.go index e7f08b37..730c7000 100644 --- a/ilogger.go +++ b/ilogger.go @@ -25,4 +25,7 @@ type ILogger interface { Level() LogLevel SetLevel(l LogLevel) (err error) + + ShowSQL(show ...bool) + IsShowSQL() bool } From 1948b7077e8683759762c307ad4e07b06f968dad Mon Sep 17 00:00:00 2001 From: leafsoar Date: Thu, 18 Feb 2016 14:47:48 +0800 Subject: [PATCH 069/135] support jsonb --- type.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/type.go b/type.go index c7c4d7bd..2201adfc 100644 --- a/type.go +++ b/type.go @@ -105,7 +105,8 @@ var ( Serial = "SERIAL" BigSerial = "BIGSERIAL" - Json = "JSON" + Json = "JSON" + Jsonb = "JSONB" SqlTypes = map[string]int{ Bit: NUMERIC_TYPE, @@ -116,9 +117,10 @@ var ( Integer: NUMERIC_TYPE, BigInt: NUMERIC_TYPE, - Enum: TEXT_TYPE, - Set: TEXT_TYPE, - Json: TEXT_TYPE, + Enum: TEXT_TYPE, + Set: TEXT_TYPE, + Json: TEXT_TYPE, + Jsonb: TEXT_TYPE, Char: TEXT_TYPE, Varchar: TEXT_TYPE, From ec9a49656ed012a95175459498d6b8819a063771 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 19 Feb 2016 17:23:00 +0800 Subject: [PATCH 070/135] bug fixed go-xorm/xorm#357 --- db.go | 29 ++++++++++++++++++++++++++++- error.go | 2 -- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/db.go b/db.go index 169d8553..e518f14f 100644 --- a/db.go +++ b/db.go @@ -142,6 +142,12 @@ func (row *Row) ScanStructByName(dest interface{}) error { if row.err != nil { return row.err } + if !row.rows.Next() { + if err := row.rows.Err(); err != nil { + return err + } + return sql.ErrNoRows + } return row.rows.ScanStructByName(dest) } @@ -149,6 +155,12 @@ func (row *Row) ScanStructByIndex(dest interface{}) error { if row.err != nil { return row.err } + if !row.rows.Next() { + if err := row.rows.Err(); err != nil { + return err + } + return sql.ErrNoRows + } return row.rows.ScanStructByIndex(dest) } @@ -157,6 +169,12 @@ func (row *Row) ScanSlice(dest interface{}) error { if row.err != nil { return row.err } + if !row.rows.Next() { + if err := row.rows.Err(); err != nil { + return err + } + return sql.ErrNoRows + } return row.rows.ScanSlice(dest) } @@ -165,12 +183,21 @@ func (row *Row) ScanMap(dest interface{}) error { if row.err != nil { return row.err } + if !row.rows.Next() { + if err := row.rows.Err(); err != nil { + return err + } + return sql.ErrNoRows + } return row.rows.ScanMap(dest) } func (db *DB) QueryRow(query string, args ...interface{}) *Row { rows, err := db.Query(query, args...) - return &Row{rows, err} + if err != nil { + return &Row{nil, err} + } + return &Row{rows, nil} } func (db *DB) QueryRowMap(query string, mp interface{}) *Row { diff --git a/error.go b/error.go index 13c17925..640e6036 100644 --- a/error.go +++ b/error.go @@ -5,6 +5,4 @@ import "errors" var ( ErrNoMapPointer = errors.New("mp should be a map's pointer") ErrNoStructPointer = errors.New("mp should be a struct's pointer") - //ErrNotExist = errors.New("Not exist") - //ErrIgnore = errors.New("Ignore") ) From 502158401cde814951eae62f064d9e5ff39e13ce Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 3 Mar 2016 11:01:50 +0800 Subject: [PATCH 071/135] added schema for uri --- dialect.go | 1 + 1 file changed, 1 insertion(+) diff --git a/dialect.go b/dialect.go index 34c5e5a9..7bedc27d 100644 --- a/dialect.go +++ b/dialect.go @@ -20,6 +20,7 @@ type Uri struct { Laddr string Raddr string Timeout time.Duration + Schema string } // a dialect is a driver's wrapper From 42bacbe76fb75f773f7aab4e7644377ef9315e82 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 24 Mar 2016 22:45:18 +0800 Subject: [PATCH 072/135] redefine log level --- ilogger.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ilogger.go b/ilogger.go index 730c7000..374b6267 100644 --- a/ilogger.go +++ b/ilogger.go @@ -4,12 +4,12 @@ type LogLevel int const ( // !nashtsai! following level also match syslog.Priority value - LOG_UNKNOWN LogLevel = iota - 2 - LOG_OFF LogLevel = iota - 1 - LOG_ERR LogLevel = iota + 3 + LOG_DEBUG LogLevel = iota + LOG_INFO LOG_WARNING - LOG_INFO LogLevel = iota + 6 - LOG_DEBUG + LOG_ERR + LOG_OFF + LOG_UNKNOWN ) // logger interface From b9277f807c011387baed9748a45f26da65b42077 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 31 Mar 2016 18:16:24 +0800 Subject: [PATCH 073/135] ILogger interface changed --- ilogger.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/ilogger.go b/ilogger.go index 374b6267..c8d78496 100644 --- a/ilogger.go +++ b/ilogger.go @@ -14,17 +14,17 @@ const ( // logger interface type ILogger interface { - Debug(v ...interface{}) (err error) - Debugf(format string, v ...interface{}) (err error) - Err(v ...interface{}) (err error) - Errf(format string, v ...interface{}) (err error) - Info(v ...interface{}) (err error) - Infof(format string, v ...interface{}) (err error) - Warning(v ...interface{}) (err error) - Warningf(format string, v ...interface{}) (err error) + 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{}) Level() LogLevel - SetLevel(l LogLevel) (err error) + SetLevel(l LogLevel) ShowSQL(show ...bool) IsShowSQL() bool From 8d208d9ad82b57577d9aa3ab4555703fb1e3716b Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 28 Apr 2016 22:24:07 +0800 Subject: [PATCH 074/135] bug fixed for pointer to alias type --- type.go | 30 ++---------------------------- 1 file changed, 2 insertions(+), 28 deletions(-) diff --git a/type.go b/type.go index 2201adfc..90dc3482 100644 --- a/type.go +++ b/type.go @@ -237,6 +237,7 @@ var ( 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: @@ -267,40 +268,13 @@ func Type2SQLType(t reflect.Type) (st SQLType) { st = SQLType{Text, 0, 0} } case reflect.Ptr: - st, _ = ptrType2SQLType(t) + st = Type2SQLType(t.Elem()) default: st = SQLType{Text, 0, 0} } return } -func ptrType2SQLType(t reflect.Type) (st SQLType, has bool) { - has = true - - switch t { - case reflect.TypeOf(&c_EMPTY_STRING): - st = SQLType{Varchar, 255, 0} - return - case reflect.TypeOf(&c_BOOL_DEFAULT): - st = SQLType{Bool, 0, 0} - case reflect.TypeOf(&c_COMPLEX64_DEFAULT), reflect.TypeOf(&c_COMPLEX128_DEFAULT): - st = SQLType{Varchar, 64, 0} - case reflect.TypeOf(&c_FLOAT32_DEFAULT): - st = SQLType{Float, 0, 0} - case reflect.TypeOf(&c_FLOAT64_DEFAULT): - st = SQLType{Double, 0, 0} - case reflect.TypeOf(&c_INT64_DEFAULT), reflect.TypeOf(&c_UINT64_DEFAULT): - st = SQLType{BigInt, 0, 0} - case reflect.TypeOf(&c_TIME_DEFAULT): - st = SQLType{DateTime, 0, 0} - case reflect.TypeOf(&c_INT_DEFAULT), reflect.TypeOf(&c_INT32_DEFAULT), reflect.TypeOf(&c_INT8_DEFAULT), reflect.TypeOf(&c_INT16_DEFAULT), reflect.TypeOf(&c_UINT_DEFAULT), reflect.TypeOf(&c_UINT32_DEFAULT), reflect.TypeOf(&c_UINT8_DEFAULT), reflect.TypeOf(&c_UINT16_DEFAULT): - st = SQLType{Int, 0, 0} - default: - has = false - } - return -} - // default sql type change to go types func SQLType2Type(st SQLType) reflect.Type { name := strings.ToUpper(st.Name) From 49d0620cff55dc6b8fd681acce2f3632db8f6396 Mon Sep 17 00:00:00 2001 From: Victor Gaydov Date: Thu, 28 Apr 2016 16:27:13 +0300 Subject: [PATCH 075/135] Add Column.TableName field --- column.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/column.go b/column.go index 0e0def08..7d963231 100644 --- a/column.go +++ b/column.go @@ -16,6 +16,7 @@ const ( // database column type Column struct { Name string + TableName string FieldName string SQLType SQLType Length int @@ -42,6 +43,7 @@ type Column struct { func NewColumn(name, fieldName string, sqlType SQLType, len1, len2 int, nullable bool) *Column { return &Column{ Name: name, + TableName: "", FieldName: fieldName, SQLType: sqlType, Length: len1, From 147b5cf5c2d617af43f4c802c45939538543928c Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 13 May 2016 10:51:19 +0800 Subject: [PATCH 076/135] added bytes type --- type.go | 1 + 1 file changed, 1 insertion(+) diff --git a/type.go b/type.go index 90dc3482..be5aa2a1 100644 --- a/type.go +++ b/type.go @@ -207,6 +207,7 @@ var ( 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) ) From 1768a08d8228dc49e4304d4eb66a7d22369a328b Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 29 Jun 2016 17:56:53 +0800 Subject: [PATCH 077/135] MapToSlice: return error instead of panic --- db.go | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/db.go b/db.go index e518f14f..1e0ae170 100644 --- a/db.go +++ b/db.go @@ -4,6 +4,7 @@ import ( "database/sql" "database/sql/driver" "errors" + "fmt" "reflect" "regexp" "sync" @@ -15,12 +16,19 @@ func MapToSlice(query string, mp interface{}) (string, []interface{}, error) { return "", []interface{}{}, ErrNoMapPointer } - args := make([]interface{}, 0) + args := make([]interface{}, 0, len(vv.Elem().MapKeys())) + var err error query = re.ReplaceAllStringFunc(query, func(src string) string { - args = append(args, vv.Elem().MapIndex(reflect.ValueOf(src[1:])).Interface()) + 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, nil + + return query, args, err } func StructToSlice(query string, st interface{}) (string, []interface{}, error) { From b1f8a9cefbe364a932989f6ac8e132da2e0831b2 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 30 Jun 2016 16:33:09 +0800 Subject: [PATCH 078/135] resolved go-xorm/xorm#416 --- column.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/column.go b/column.go index 7d963231..36016ef6 100644 --- a/column.go +++ b/column.go @@ -23,7 +23,7 @@ type Column struct { Length2 int Nullable bool Default string - Indexes map[string]bool + Indexes map[string]int IsPrimaryKey bool IsAutoIncrement bool MapType int @@ -50,7 +50,7 @@ func NewColumn(name, fieldName string, sqlType SQLType, len1, len2 int, nullable Length2: len2, Nullable: nullable, Default: "", - Indexes: make(map[string]bool), + Indexes: make(map[string]int), IsPrimaryKey: false, IsAutoIncrement: false, MapType: TWOSIDES, From bc1b7f81f0e369289078424064634a5ee7d21051 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 7 Jul 2016 11:32:53 +0800 Subject: [PATCH 079/135] bug fixed --- db.go | 36 +++++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/db.go b/db.go index 1e0ae170..e04bf59b 100644 --- a/db.go +++ b/db.go @@ -139,11 +139,7 @@ func (row *Row) Scan(dest ...interface{}) error { return err } // Make sure the query can be processed to completion with no errors. - if err := row.rows.Close(); err != nil { - return err - } - - return nil + return row.rows.Close() } func (row *Row) ScanStructByName(dest interface{}) error { @@ -156,7 +152,12 @@ func (row *Row) ScanStructByName(dest interface{}) error { } return sql.ErrNoRows } - return row.rows.ScanStructByName(dest) + 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 { @@ -169,7 +170,12 @@ func (row *Row) ScanStructByIndex(dest interface{}) error { } return sql.ErrNoRows } - return row.rows.ScanStructByIndex(dest) + 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 @@ -183,7 +189,13 @@ func (row *Row) ScanSlice(dest interface{}) error { } return sql.ErrNoRows } - return row.rows.ScanSlice(dest) + 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 @@ -197,7 +209,13 @@ func (row *Row) ScanMap(dest interface{}) error { } return sql.ErrNoRows } - return row.rows.ScanMap(dest) + 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 (db *DB) QueryRow(query string, args ...interface{}) *Row { From 5bf745d7d163f4380e6c2bba8c4afa60534dd087 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 5 Aug 2016 23:24:16 +0800 Subject: [PATCH 080/135] add ToMapString method for rows and row --- db.go | 333 ------------------------------------------------- rows.go | 380 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 380 insertions(+), 333 deletions(-) create mode 100644 rows.go diff --git a/db.go b/db.go index e04bf59b..6111c4b3 100644 --- a/db.go +++ b/db.go @@ -7,7 +7,6 @@ import ( "fmt" "reflect" "regexp" - "sync" ) func MapToSlice(query string, mp interface{}) (string, []interface{}, error) { @@ -103,121 +102,6 @@ func (db *DB) QueryStruct(query string, st interface{}) (*Rows, error) { return db.Query(query, args...) } -type Row struct { - rows *Rows - // One of these two will be non-nil: - err error // deferred error for easy chaining -} - -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 - } - 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 - } - 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 - } - 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 - } - 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 (db *DB) QueryRow(query string, args ...interface{}) *Row { rows, err := db.Query(query, args...) if err != nil { @@ -381,44 +265,6 @@ func (db *DB) ExecStruct(query string, st interface{}) (sql.Result, error) { return db.DB.Exec(query, args...) } -type Rows struct { - *sql.Rows - Mapper IMapper -} - -// 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...) -} - type EmptyScanner struct { } @@ -426,185 +272,6 @@ func (EmptyScanner) Scan(src interface{}) error { return nil } -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.Mapper.Table2Obj(name)) - if f.IsValid() { - newDest[j] = f.Addr().Interface() - } else { - newDest[j] = &v - } - } - - return rs.Rows.Scan(newDest...) -} - -type cacheStruct struct { - value reflect.Value - idx int -} - -var ( - reflectCache = make(map[reflect.Type]*cacheStruct) - reflectCacheMutex sync.RWMutex -) - -func ReflectNew(typ reflect.Type) reflect.Value { - reflectCacheMutex.RLock() - cs, ok := reflectCache[typ] - reflectCacheMutex.RUnlock() - - const newSize = 200 - - if !ok || cs.idx+1 > newSize-1 { - cs = &cacheStruct{reflect.MakeSlice(reflect.SliceOf(typ), newSize, newSize), 0} - reflectCacheMutex.Lock() - reflectCache[typ] = cs - reflectCacheMutex.Unlock() - } else { - reflectCacheMutex.Lock() - cs.idx = cs.idx + 1 - reflectCacheMutex.Unlock() - } - return cs.value.Index(cs.idx).Addr() -} - -// 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] = ReflectNew(vvv.Type().Elem()).Interface() - //v := reflect.New(vvv.Type().Elem()) - //newDest[i] = v.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 -} - -/*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)) - err = rs.ScanSlice(newDest) - if err != nil { - return err - } - - vvv := vv.Elem() - - for i, name := range cols { - vname := reflect.ValueOf(name) - vvv.SetMapIndex(vname, reflect.ValueOf(newDest[i]).Elem()) - } - - return nil -}*/ - type Tx struct { *sql.Tx Mapper IMapper diff --git a/rows.go b/rows.go new file mode 100644 index 00000000..c4dec23e --- /dev/null +++ b/rows.go @@ -0,0 +1,380 @@ +package core + +import ( + "database/sql" + "errors" + "reflect" + "sync" +) + +type Rows struct { + *sql.Rows + Mapper IMapper +} + +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.Mapper.Table2Obj(name)) + if f.IsValid() { + newDest[j] = f.Addr().Interface() + } else { + newDest[j] = &v + } + } + + return rs.Rows.Scan(newDest...) +} + +type cacheStruct struct { + value reflect.Value + idx int +} + +var ( + reflectCache = make(map[reflect.Type]*cacheStruct) + reflectCacheMutex sync.RWMutex +) + +func ReflectNew(typ reflect.Type) reflect.Value { + reflectCacheMutex.RLock() + cs, ok := reflectCache[typ] + reflectCacheMutex.RUnlock() + + const newSize = 200 + + if !ok || cs.idx+1 > newSize-1 { + cs = &cacheStruct{reflect.MakeSlice(reflect.SliceOf(typ), newSize, newSize), 0} + reflectCacheMutex.Lock() + reflectCache[typ] = cs + reflectCacheMutex.Unlock() + } else { + reflectCacheMutex.Lock() + cs.idx = cs.idx + 1 + reflectCacheMutex.Unlock() + } + return cs.value.Index(cs.idx).Addr() +} + +// 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] = ReflectNew(vvv.Type().Elem()).Interface() + //v := reflect.New(vvv.Type().Elem()) + //newDest[i] = v.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 +} + +/*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)) + err = rs.ScanSlice(newDest) + if err != nil { + return err + } + + vvv := vv.Elem() + + 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 +} + +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 +} From 5caa28d411e717d26f7cea786e680687d686df51 Mon Sep 17 00:00:00 2001 From: Sergey Kurt Date: Fri, 28 Oct 2016 13:52:58 +0300 Subject: [PATCH 081/135] Remove allocations from GetColumn & GetColumnIdx --- table.go | 37 +++++++++++++---- table_test.go | 107 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 136 insertions(+), 8 deletions(-) create mode 100644 table_test.go diff --git a/table.go b/table.go index aba1f96e..e6f6a751 100644 --- a/table.go +++ b/table.go @@ -47,19 +47,40 @@ func NewTable(name string, t reflect.Type) *Table { } } -func (table *Table) GetColumn(name string) *Column { - if c, ok := table.columnsMap[strings.ToLower(name)]; ok { - return c[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) GetColumnIdx(name string, idx int) *Column { - if c, ok := table.columnsMap[strings.ToLower(name)]; ok { - if idx < len(c) { - return c[idx] - } +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 } diff --git a/table_test.go b/table_test.go new file mode 100644 index 00000000..efa3face --- /dev/null +++ b/table_test.go @@ -0,0 +1,107 @@ +package core + +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) + } + } + } +} From 46c93adfe5a7f1217de38b38d2e3b7370ccca635 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 13 Dec 2016 15:00:20 +0800 Subject: [PATCH 082/135] add circle ci config file (#20) --- circle.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 circle.yml diff --git a/circle.yml b/circle.yml new file mode 100644 index 00000000..006e1230 --- /dev/null +++ b/circle.yml @@ -0,0 +1,14 @@ +dependencies: + override: + # './...' is a relative pattern which means all subdirectories + - go get -t -d -v ./... + - go build -v + +database: + override: + - mysql -u root -e "CREATE DATABASE core_test DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci" + +test: + override: + # './...' is a relative pattern which means all subdirectories + - go test -v -race \ No newline at end of file From 67db61fb964a5b47156709ab909f3481e259a45a Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 13 Dec 2016 15:01:02 +0800 Subject: [PATCH 083/135] add circleci status on README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 0ae94a58..09b72c74 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ Core is a lightweight wrapper of sql.DB. +[![CircleCI](https://circleci.com/gh/go-xorm/core/tree/master.svg?style=svg)](https://circleci.com/gh/go-xorm/core/tree/master) + # Open ```Go db, _ := core.Open(db, connstr) From 87aca223378aab7a4bf31ef53f20fde4997ad793 Mon Sep 17 00:00:00 2001 From: u0x01 <233355@gmail.com> Date: Wed, 14 Dec 2016 11:05:06 +0800 Subject: [PATCH 084/135] Add support for jsonb (#19) `jsonb` type can work like `json` type now. --- type.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/type.go b/type.go index be5aa2a1..86048225 100644 --- a/type.go +++ b/type.go @@ -54,7 +54,7 @@ func (s *SQLType) IsNumeric() bool { } func (s *SQLType) IsJson() bool { - return s.Name == Json + return s.Name == Json || s.Name == Jsonb } var ( From 60b1704516c3af6ff969d81f359cf310ad43a2e0 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 3 Jan 2017 14:17:17 +0800 Subject: [PATCH 085/135] don't panic but return nil when query dialect --- dialect.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/dialect.go b/dialect.go index 7bedc27d..8c950da0 100644 --- a/dialect.go +++ b/dialect.go @@ -290,6 +290,7 @@ var ( dialects = map[DbType]func() Dialect{} ) +// RegisterDialect register database dialect func RegisterDialect(dbName DbType, dialectFunc func() Dialect) { if dialectFunc == nil { panic("core: Register dialect is nil") @@ -297,6 +298,10 @@ func RegisterDialect(dbName DbType, dialectFunc func() Dialect) { dialects[dbName] = dialectFunc // !nashtsai! allow override dialect } +// QueryDialect query if registed database dialect func QueryDialect(dbName DbType) Dialect { - return dialects[dbName]() + if d, ok := dialects[dbName]; ok { + return d() + } + return nil } From 2fbe2c76c6781d9e1c0398fc25386426e611f975 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 3 Jan 2017 14:45:57 +0800 Subject: [PATCH 086/135] query dialect ignore case sensitive --- dialect.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dialect.go b/dialect.go index 8c950da0..70420ee5 100644 --- a/dialect.go +++ b/dialect.go @@ -287,7 +287,7 @@ func (b *Base) LogSQL(sql string, args []interface{}) { } var ( - dialects = map[DbType]func() Dialect{} + dialects = map[string]func() Dialect{} ) // RegisterDialect register database dialect @@ -295,12 +295,12 @@ func RegisterDialect(dbName DbType, dialectFunc func() Dialect) { if dialectFunc == nil { panic("core: Register dialect is nil") } - dialects[dbName] = dialectFunc // !nashtsai! allow override dialect + dialects[strings.ToLower(string(dbName))] = dialectFunc // !nashtsai! allow override dialect } // QueryDialect query if registed database dialect func QueryDialect(dbName DbType) Dialect { - if d, ok := dialects[dbName]; ok { + if d, ok := dialects[strings.ToLower(string(dbName))]; ok { return d() } return nil From 7daacb215ed03af093a72e0af32a5fe79458613d Mon Sep 17 00:00:00 2001 From: Kenichi Yonekawa Date: Tue, 7 Feb 2017 00:24:21 +0900 Subject: [PATCH 087/135] Fix potential data race in Column.fieldPath (#21) --- column.go | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/column.go b/column.go index 36016ef6..c59d0102 100644 --- a/column.go +++ b/column.go @@ -32,7 +32,6 @@ type Column struct { IsDeleted bool IsCascade bool IsVersion bool - fieldPath []string DefaultIsEmpty bool EnumOptions map[string]int SetOptions map[string]int @@ -59,7 +58,6 @@ func NewColumn(name, fieldName string, sqlType SQLType, len1, len2 int, nullable IsDeleted: false, IsCascade: false, IsVersion: false, - fieldPath: nil, DefaultIsEmpty: false, EnumOptions: make(map[string]int), } @@ -121,12 +119,10 @@ func (col *Column) ValueOf(bean interface{}) (*reflect.Value, error) { func (col *Column) ValueOfV(dataStruct *reflect.Value) (*reflect.Value, error) { var fieldValue reflect.Value - if col.fieldPath == nil { - col.fieldPath = strings.Split(col.FieldName, ".") - } + fieldPath := strings.Split(col.FieldName, ".") if dataStruct.Type().Kind() == reflect.Map { - keyValue := reflect.ValueOf(col.fieldPath[len(col.fieldPath)-1]) + keyValue := reflect.ValueOf(fieldPath[len(fieldPath)-1]) fieldValue = dataStruct.MapIndex(keyValue) return &fieldValue, nil } else if dataStruct.Type().Kind() == reflect.Interface { @@ -134,19 +130,19 @@ func (col *Column) ValueOfV(dataStruct *reflect.Value) (*reflect.Value, error) { dataStruct = &structValue } - level := len(col.fieldPath) - fieldValue = dataStruct.FieldByName(col.fieldPath[0]) + 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(col.fieldPath[i+1]) + 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(col.fieldPath[i+1]) + fieldValue = fieldValue.Elem().FieldByName(fieldPath[i+1]) } else { return nil, fmt.Errorf("field %v is not valid", col.FieldName) } From e8409d73255791843585964791443dbad877058c Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 17 Mar 2017 20:25:07 +0800 Subject: [PATCH 088/135] fix log space --- dialect.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dialect.go b/dialect.go index 70420ee5..74478301 100644 --- a/dialect.go +++ b/dialect.go @@ -279,9 +279,9 @@ func (b *Base) ForUpdateSql(query string) string { func (b *Base) LogSQL(sql string, args []interface{}) { if b.logger != nil && b.logger.IsShowSQL() { if len(args) > 0 { - b.logger.Info("[sql]", sql, args) + b.logger.Infof("[SQL] %v %v", sql, args) } else { - b.logger.Info("[sql]", sql) + b.logger.Infof("[SQL] %v", sql) } } } From 6c9f9bf3130d143937e4adcef1cf1bb9f6899260 Mon Sep 17 00:00:00 2001 From: wangsongyan Date: Wed, 3 May 2017 20:16:46 +0800 Subject: [PATCH 089/135] =?UTF-8?q?column,table=E5=AE=9E=E4=BD=93=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E6=B3=A8=E9=87=8A=E5=B1=9E=E6=80=A7=20(#24)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- column.go | 2 ++ table.go | 1 + 2 files changed, 3 insertions(+) diff --git a/column.go b/column.go index c59d0102..d4d83145 100644 --- a/column.go +++ b/column.go @@ -37,6 +37,7 @@ type Column struct { SetOptions map[string]int DisableTimeZone bool TimeZone *time.Location // column specified time zone + Comment string } func NewColumn(name, fieldName string, sqlType SQLType, len1, len2 int, nullable bool) *Column { @@ -60,6 +61,7 @@ func NewColumn(name, fieldName string, sqlType SQLType, len1, len2 int, nullable IsVersion: false, DefaultIsEmpty: false, EnumOptions: make(map[string]int), + Comment: "", } } diff --git a/table.go b/table.go index e6f6a751..88199bed 100644 --- a/table.go +++ b/table.go @@ -22,6 +22,7 @@ type Table struct { Cacher Cacher StoreEngine string Charset string + Comment string } func (table *Table) Columns() []*Column { From e9b7412d750de4920b1c7b3ede5a98fd76d38308 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 14 Jun 2017 14:05:17 +0800 Subject: [PATCH 090/135] add comment create support for mysql --- dialect.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dialect.go b/dialect.go index 74478301..6f2e81d0 100644 --- a/dialect.go +++ b/dialect.go @@ -244,6 +244,9 @@ func (b *Base) CreateTableSql(table *Table, tableName, storeEngine, charset stri sql += col.StringNoPk(b.dialect) } sql = strings.TrimSpace(sql) + if b.DriverName() == MYSQL && len(col.Comment) > 0 { + sql += " COMMENT '" + col.Comment + "'" + } sql += ", " } From d33f21fcb7f9014a3294b7a6379e807bcd8c080d Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 20 Jul 2017 20:04:00 +0800 Subject: [PATCH 091/135] add IsJSON on Column --- column.go | 3 ++- type.go | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/column.go b/column.go index d4d83145..d9362e98 100644 --- a/column.go +++ b/column.go @@ -13,12 +13,13 @@ const ( ONLYFROMDB ) -// database column +// Column defines database column type Column struct { Name string TableName string FieldName string SQLType SQLType + IsJSON bool Length int Length2 int Nullable bool diff --git a/type.go b/type.go index 86048225..bea8d235 100644 --- a/type.go +++ b/type.go @@ -117,9 +117,9 @@ var ( Integer: NUMERIC_TYPE, BigInt: NUMERIC_TYPE, - Enum: TEXT_TYPE, - Set: TEXT_TYPE, - Json: TEXT_TYPE, + Enum: TEXT_TYPE, + Set: TEXT_TYPE, + //Json: TEXT_TYPE, Jsonb: TEXT_TYPE, Char: TEXT_TYPE, From cd38c8eb3375b27cbc8c7f3f4f6501cce1eb4b4c Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 20 Jul 2017 20:05:00 +0800 Subject: [PATCH 092/135] fix compitable --- type.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/type.go b/type.go index bea8d235..86048225 100644 --- a/type.go +++ b/type.go @@ -117,9 +117,9 @@ var ( Integer: NUMERIC_TYPE, BigInt: NUMERIC_TYPE, - Enum: TEXT_TYPE, - Set: TEXT_TYPE, - //Json: TEXT_TYPE, + Enum: TEXT_TYPE, + Set: TEXT_TYPE, + Json: TEXT_TYPE, Jsonb: TEXT_TYPE, Char: TEXT_TYPE, From 690212708374f3e7143d034adb8f80a942598117 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 22 Aug 2017 13:30:02 +0800 Subject: [PATCH 093/135] add ErrorRow function --- rows.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/rows.go b/rows.go index c4dec23e..eaee7dcf 100644 --- a/rows.go +++ b/rows.go @@ -247,6 +247,13 @@ type Row struct { err error // deferred error for easy chaining } +// ErrorRow return an error row +func ErrorRow(err error) *Row { + return &Row{ + err: err, + } +} + func (row *Row) Columns() ([]string, error) { if row.err != nil { return nil, row.err From 71c1070a861118827352b1394eb86cbfeef5c513 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 22 Aug 2017 13:50:40 +0800 Subject: [PATCH 094/135] add NewRow from rows --- rows.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rows.go b/rows.go index eaee7dcf..4a4acaa4 100644 --- a/rows.go +++ b/rows.go @@ -254,6 +254,11 @@ func ErrorRow(err error) *Row { } } +// 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 From 4fef474e22e1289c48648c382abcd9770b17af69 Mon Sep 17 00:00:00 2001 From: Edward Betts Date: Sat, 2 Sep 2017 11:12:19 +0100 Subject: [PATCH 095/135] correct spelling mistake (#28) --- type.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/type.go b/type.go index 86048225..5b7d9c6a 100644 --- a/type.go +++ b/type.go @@ -163,7 +163,7 @@ var ( uintTypes = sort.StringSlice{"*uint", "*uint16", "*uint32", "*uint8"} ) -// !nashtsai! treat following var as interal const values, these are used for reflect.TypeOf comparision +// !nashtsai! treat following var as interal const values, these are used for reflect.TypeOf comparison var ( c_EMPTY_STRING string c_BOOL_DEFAULT bool From 36c6ea37846156457848bb9e3aaa6d7dd19307f5 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 9 Sep 2017 16:56:39 +0800 Subject: [PATCH 096/135] add boolean type --- type.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/type.go b/type.go index 86048225..adb86437 100644 --- a/type.go +++ b/type.go @@ -100,7 +100,8 @@ var ( LongBlob = "LONGBLOB" Bytea = "BYTEA" - Bool = "BOOL" + Bool = "BOOL" + Boolean = "BOOLEAN" Serial = "SERIAL" BigSerial = "BIGSERIAL" From 7cd4078f0b35ac497ec3cf1b8caf3327f0d38440 Mon Sep 17 00:00:00 2001 From: zy Date: Mon, 6 Nov 2017 14:23:03 +0800 Subject: [PATCH 097/135] fix: oracle default key in xorm tag error (#30) --- column.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/column.go b/column.go index d9362e98..65370bb5 100644 --- a/column.go +++ b/column.go @@ -79,6 +79,10 @@ func (col *Column) String(d Dialect) string { } } + if col.Default != "" { + sql += "DEFAULT " + col.Default + " " + } + if d.ShowCreateNull() { if col.Nullable { sql += "NULL " @@ -87,10 +91,6 @@ func (col *Column) String(d Dialect) string { } } - if col.Default != "" { - sql += "DEFAULT " + col.Default + " " - } - return sql } @@ -99,6 +99,10 @@ func (col *Column) StringNoPk(d Dialect) string { sql += d.SqlType(col) + " " + if col.Default != "" { + sql += "DEFAULT " + col.Default + " " + } + if d.ShowCreateNull() { if col.Nullable { sql += "NULL " @@ -107,10 +111,6 @@ func (col *Column) StringNoPk(d Dialect) string { } } - if col.Default != "" { - sql += "DEFAULT " + col.Default + " " - } - return sql } From 7ba98d23bc4218169d31ab64e93313edb5a5fb27 Mon Sep 17 00:00:00 2001 From: micanzhang Date: Thu, 23 Nov 2017 20:03:32 +0800 Subject: [PATCH 098/135] fix Scan NullTime type failed at sqlite3 (#33) --- circle.yml | 3 ++- db_test.go | 25 ++++++++++++------------- pk_test.go | 5 ++--- scan.go | 3 +++ 4 files changed, 19 insertions(+), 17 deletions(-) diff --git a/circle.yml b/circle.yml index 006e1230..e6a05be2 100644 --- a/circle.yml +++ b/circle.yml @@ -11,4 +11,5 @@ database: test: override: # './...' is a relative pattern which means all subdirectories - - go test -v -race \ No newline at end of file + - go test -v -race + - go test -v -race --dbtype=sqlite3 diff --git a/db_test.go b/db_test.go index 94c4ea4f..43637e8c 100644 --- a/db_test.go +++ b/db_test.go @@ -2,7 +2,7 @@ package core import ( "errors" - "fmt" + "flag" "os" "testing" "time" @@ -12,8 +12,7 @@ import ( ) var ( - //dbtype string = "sqlite3" - dbtype string = "mysql" + dbtype = flag.String("dbtype", "mysql", "database type") createTableSql string ) @@ -28,7 +27,8 @@ type User struct { } func init() { - switch dbtype { + 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);" @@ -41,7 +41,7 @@ func init() { } func testOpen() (*DB, error) { - switch dbtype { + switch *dbtype { case "sqlite3": os.Remove("./test.db") return Open("sqlite3", "./test.db") @@ -133,7 +133,7 @@ func BenchmarkStructQuery(b *testing.B) { b.Error(err) } if user.Name != "xlw" { - fmt.Println(user) + b.Log(user) b.Error(errors.New("name should be xlw")) } } @@ -179,7 +179,7 @@ func BenchmarkStruct2Query(b *testing.B) { b.Error(err) } if user.Name != "xlw" { - fmt.Println(user) + b.Log(user) b.Error(errors.New("name should be xlw")) } } @@ -228,9 +228,8 @@ func BenchmarkSliceInterfaceQuery(b *testing.B) { if err != nil { b.Error(err) } - fmt.Println(slice) + b.Log(slice) if *slice[1].(*string) != "xlw" { - fmt.Println(slice) b.Error(errors.New("name should be xlw")) } } @@ -332,7 +331,7 @@ func BenchmarkSliceStringQuery(b *testing.B) { b.Error(err) } if (*slice[1]) != "xlw" { - fmt.Println(slice) + b.Log(slice) b.Error(errors.New("name should be xlw")) } } @@ -378,7 +377,7 @@ func BenchmarkMapInterfaceQuery(b *testing.B) { b.Error(err) } if m["name"].(string) != "xlw" { - fmt.Println(m) + b.Log(m) b.Error(errors.New("name should be xlw")) } } @@ -579,7 +578,7 @@ func TestExecMap(t *testing.T) { if err != nil { t.Error(err) } - fmt.Println("--", user) + t.Log("--", user) } } @@ -621,7 +620,7 @@ func TestExecStruct(t *testing.T) { if err != nil { t.Error(err) } - fmt.Println("1--", user) + t.Log("1--", user) } } diff --git a/pk_test.go b/pk_test.go index 05486086..7986ae41 100644 --- a/pk_test.go +++ b/pk_test.go @@ -1,7 +1,6 @@ package core import ( - "fmt" "reflect" "testing" ) @@ -12,14 +11,14 @@ func TestPK(t *testing.T) { if err != nil { t.Error(err) } - fmt.Println(str) + t.Log(str) s := &PK{} err = s.FromString(str) if err != nil { t.Error(err) } - fmt.Println(s) + t.Log(s) if len(*p) != len(*s) { t.Fatal("p", *p, "should be equal", *s) diff --git a/scan.go b/scan.go index 7da338d8..b7c159b2 100644 --- a/scan.go +++ b/scan.go @@ -44,6 +44,9 @@ func convertTime(dest *NullTime, src interface{}) error { } *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) From 589eefd4f452f2bdb7cb2df841a548d5f6e2d48a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=95=E6=99=A8?= Date: Wed, 6 Dec 2017 21:58:25 +0800 Subject: [PATCH 099/135] gofmt (#35) --- cache.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cache.go b/cache.go index bf81bd52..cd95e548 100644 --- a/cache.go +++ b/cache.go @@ -1,11 +1,11 @@ package core import ( + "bytes" + "encoding/gob" "errors" "fmt" "time" - "bytes" - "encoding/gob" ) const ( @@ -55,7 +55,6 @@ func encodeIds(ids []PK) (string, error) { return buf.String(), err } - func decodeIds(s string) ([]PK, error) { pks := make([]PK, 0) From cfbdcb6ce79d2122848b54df13892e37fc87dd73 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 21 Dec 2017 09:38:32 +0800 Subject: [PATCH 100/135] small refactor --- cache.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cache.go b/cache.go index bf81bd52..8f9531da 100644 --- a/cache.go +++ b/cache.go @@ -1,11 +1,12 @@ package core import ( - "errors" - "fmt" - "time" "bytes" "encoding/gob" + "errors" + "fmt" + "strings" + "time" ) const ( @@ -55,11 +56,10 @@ func encodeIds(ids []PK) (string, error) { return buf.String(), err } - func decodeIds(s string) ([]PK, error) { pks := make([]PK, 0) - dec := gob.NewDecoder(bytes.NewBufferString(s)) + dec := gob.NewDecoder(strings.NewReader(s)) err := dec.Decode(&pks) return pks, err From c624c83edade294b480e46ae0ed19f8a60600196 Mon Sep 17 00:00:00 2001 From: Boyi Wu Date: Fri, 23 Feb 2018 10:03:34 +0800 Subject: [PATCH 101/135] add UniqueIdentifier type for supporting mssql (#38) --- type.go | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/type.go b/type.go index 8010a222..86cc3920 100644 --- a/type.go +++ b/type.go @@ -69,15 +69,16 @@ var ( Enum = "ENUM" Set = "SET" - Char = "CHAR" - Varchar = "VARCHAR" - NVarchar = "NVARCHAR" - TinyText = "TINYTEXT" - Text = "TEXT" - Clob = "CLOB" - MediumText = "MEDIUMTEXT" - LongText = "LONGTEXT" - Uuid = "UUID" + Char = "CHAR" + Varchar = "VARCHAR" + NVarchar = "NVARCHAR" + TinyText = "TINYTEXT" + Text = "TEXT" + Clob = "CLOB" + MediumText = "MEDIUMTEXT" + LongText = "LONGTEXT" + Uuid = "UUID" + UniqueIdentifier = "UNIQUEIDENTIFIER" Date = "DATE" DateTime = "DATETIME" @@ -148,11 +149,12 @@ var ( Binary: BLOB_TYPE, VarBinary: BLOB_TYPE, - TinyBlob: BLOB_TYPE, - Blob: BLOB_TYPE, - MediumBlob: BLOB_TYPE, - LongBlob: BLOB_TYPE, - Bytea: BLOB_TYPE, + TinyBlob: BLOB_TYPE, + Blob: BLOB_TYPE, + MediumBlob: BLOB_TYPE, + LongBlob: BLOB_TYPE, + Bytea: BLOB_TYPE, + UniqueIdentifier: BLOB_TYPE, Bool: NUMERIC_TYPE, @@ -291,7 +293,7 @@ func SQLType2Type(st SQLType) reflect.Type { return reflect.TypeOf(float64(1)) case Char, Varchar, NVarchar, TinyText, Text, MediumText, LongText, Enum, Set, Uuid, Clob: return reflect.TypeOf("") - case TinyBlob, Blob, LongBlob, Bytea, Binary, MediumBlob, VarBinary: + case TinyBlob, Blob, LongBlob, Bytea, Binary, MediumBlob, VarBinary, UniqueIdentifier: return reflect.TypeOf([]byte{}) case Bool: return reflect.TypeOf(true) From 11bcc82696a93ff9339bd30ff0035f2d6b61d17b Mon Sep 17 00:00:00 2001 From: Boyi Wu Date: Tue, 27 Feb 2018 16:09:17 +0800 Subject: [PATCH 102/135] add SYSNAME type for supporting mssql's in-built sysdiagrams (#39) --- type.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/type.go b/type.go index 86cc3920..9171ce2d 100644 --- a/type.go +++ b/type.go @@ -79,6 +79,7 @@ var ( LongText = "LONGTEXT" Uuid = "UUID" UniqueIdentifier = "UNIQUEIDENTIFIER" + SysName = "SYSNAME" Date = "DATE" DateTime = "DATETIME" @@ -133,6 +134,7 @@ var ( LongText: TEXT_TYPE, Uuid: TEXT_TYPE, Clob: TEXT_TYPE, + SysName: TEXT_TYPE, Date: TIME_TYPE, DateTime: TIME_TYPE, @@ -291,7 +293,7 @@ func SQLType2Type(st SQLType) reflect.Type { return reflect.TypeOf(float32(1)) case Double: return reflect.TypeOf(float64(1)) - case Char, Varchar, NVarchar, TinyText, Text, MediumText, LongText, Enum, Set, Uuid, Clob: + case Char, Varchar, NVarchar, TinyText, Text, MediumText, LongText, Enum, Set, Uuid, Clob, SysName: return reflect.TypeOf("") case TinyBlob, Blob, LongBlob, Bytea, Binary, MediumBlob, VarBinary, UniqueIdentifier: return reflect.TypeOf([]byte{}) From 29b157685d96b0c8477bc18342251b71b91cec8c Mon Sep 17 00:00:00 2001 From: m2nlight Date: Wed, 7 Mar 2018 17:19:18 +0800 Subject: [PATCH 103/135] Add SetArguments(args) method for dialect_xxxxx.go support. (https://github.com/go-xorm/xorm/issues/793) (#36) --- dialect.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dialect.go b/dialect.go index 6f2e81d0..d4bab7b6 100644 --- a/dialect.go +++ b/dialect.go @@ -74,6 +74,7 @@ type Dialect interface { GetIndexes(tableName string) (map[string]*Index, error) Filters() []Filter + SetArguments(args map[string]string) } func OpenDialect(dialect Dialect) (*DB, error) { @@ -289,6 +290,9 @@ func (b *Base) LogSQL(sql string, args []interface{}) { } } +func (b *Base) SetArguments(args map[string]string) { +} + var ( dialects = map[string]func() Dialect{} ) From 7d5d723a716c3c1adf5f478990ff7a2ff77d3abe Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 8 Mar 2018 09:08:13 +0800 Subject: [PATCH 104/135] rename args to params (#40) --- dialect.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dialect.go b/dialect.go index d4bab7b6..824d1efe 100644 --- a/dialect.go +++ b/dialect.go @@ -74,7 +74,7 @@ type Dialect interface { GetIndexes(tableName string) (map[string]*Index, error) Filters() []Filter - SetArguments(args map[string]string) + SetParams(params map[string]string) } func OpenDialect(dialect Dialect) (*DB, error) { @@ -290,7 +290,7 @@ func (b *Base) LogSQL(sql string, args []interface{}) { } } -func (b *Base) SetArguments(args map[string]string) { +func (b *Base) SetParams(params map[string]string) { } var ( From 49b899949a1f54eb9e880bf39dfcdca3864a7ed3 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 15 Mar 2018 17:10:08 +0800 Subject: [PATCH 105/135] fix scanmap (#42) --- db.go | 57 +++++++++++++++++++++++++++++++++++++++----------- rows.go | 64 +++------------------------------------------------------ 2 files changed, 48 insertions(+), 73 deletions(-) diff --git a/db.go b/db.go index 6111c4b3..9969fa43 100644 --- a/db.go +++ b/db.go @@ -7,6 +7,11 @@ import ( "fmt" "reflect" "regexp" + "sync" +) + +var ( + DefaultCacheSize = 200 ) func MapToSlice(query string, mp interface{}) (string, []interface{}, error) { @@ -58,9 +63,16 @@ func StructToSlice(query string, st interface{}) (string, []interface{}, error) return query, args, nil } +type cacheStruct struct { + value reflect.Value + idx int +} + type DB struct { *sql.DB - Mapper IMapper + Mapper IMapper + reflectCache map[reflect.Type]*cacheStruct + reflectCacheMutex sync.RWMutex } func Open(driverName, dataSourceName string) (*DB, error) { @@ -68,11 +80,32 @@ func Open(driverName, dataSourceName string) (*DB, error) { if err != nil { return nil, err } - return &DB{db, NewCacheMapper(&SnakeMapper{})}, nil + return &DB{ + DB: db, + Mapper: NewCacheMapper(&SnakeMapper{}), + reflectCache: make(map[reflect.Type]*cacheStruct), + }, nil } func FromDB(db *sql.DB) *DB { - return &DB{db, NewCacheMapper(&SnakeMapper{})} + return &DB{ + DB: db, + Mapper: NewCacheMapper(&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() } func (db *DB) Query(query string, args ...interface{}) (*Rows, error) { @@ -83,7 +116,7 @@ func (db *DB) Query(query string, args ...interface{}) (*Rows, error) { } return nil, err } - return &Rows{rows, db.Mapper}, nil + return &Rows{rows, db}, nil } func (db *DB) QueryMap(query string, mp interface{}) (*Rows, error) { @@ -128,8 +161,8 @@ func (db *DB) QueryRowStruct(query string, st interface{}) *Row { type Stmt struct { *sql.Stmt - Mapper IMapper - names map[string]int + db *DB + names map[string]int } func (db *DB) Prepare(query string) (*Stmt, error) { @@ -145,7 +178,7 @@ func (db *DB) Prepare(query string) (*Stmt, error) { if err != nil { return nil, err } - return &Stmt{stmt, db.Mapper, names}, nil + return &Stmt{stmt, db, names}, nil } func (s *Stmt) ExecMap(mp interface{}) (sql.Result, error) { @@ -179,7 +212,7 @@ func (s *Stmt) Query(args ...interface{}) (*Rows, error) { if err != nil { return nil, err } - return &Rows{rows, s.Mapper}, nil + return &Rows{rows, s.db}, nil } func (s *Stmt) QueryMap(mp interface{}) (*Rows, error) { @@ -274,7 +307,7 @@ func (EmptyScanner) Scan(src interface{}) error { type Tx struct { *sql.Tx - Mapper IMapper + db *DB } func (db *DB) Begin() (*Tx, error) { @@ -282,7 +315,7 @@ func (db *DB) Begin() (*Tx, error) { if err != nil { return nil, err } - return &Tx{tx, db.Mapper}, nil + return &Tx{tx, db}, nil } func (tx *Tx) Prepare(query string) (*Stmt, error) { @@ -298,7 +331,7 @@ func (tx *Tx) Prepare(query string) (*Stmt, error) { if err != nil { return nil, err } - return &Stmt{stmt, tx.Mapper, names}, nil + return &Stmt{stmt, tx.db, names}, nil } func (tx *Tx) Stmt(stmt *Stmt) *Stmt { @@ -327,7 +360,7 @@ func (tx *Tx) Query(query string, args ...interface{}) (*Rows, error) { if err != nil { return nil, err } - return &Rows{rows, tx.Mapper}, nil + return &Rows{rows, tx.db}, nil } func (tx *Tx) QueryMap(query string, mp interface{}) (*Rows, error) { diff --git a/rows.go b/rows.go index 4a4acaa4..580de4f9 100644 --- a/rows.go +++ b/rows.go @@ -9,7 +9,7 @@ import ( type Rows struct { *sql.Rows - Mapper IMapper + db *DB } func (rs *Rows) ToMapString() ([]map[string]string, error) { @@ -105,7 +105,7 @@ func (rs *Rows) ScanStructByName(dest interface{}) error { newDest := make([]interface{}, len(cols)) var v EmptyScanner for j, name := range cols { - f := fieldByName(vv.Elem(), rs.Mapper.Table2Obj(name)) + f := fieldByName(vv.Elem(), rs.db.Mapper.Table2Obj(name)) if f.IsValid() { newDest[j] = f.Addr().Interface() } else { @@ -116,36 +116,6 @@ func (rs *Rows) ScanStructByName(dest interface{}) error { return rs.Rows.Scan(newDest...) } -type cacheStruct struct { - value reflect.Value - idx int -} - -var ( - reflectCache = make(map[reflect.Type]*cacheStruct) - reflectCacheMutex sync.RWMutex -) - -func ReflectNew(typ reflect.Type) reflect.Value { - reflectCacheMutex.RLock() - cs, ok := reflectCache[typ] - reflectCacheMutex.RUnlock() - - const newSize = 200 - - if !ok || cs.idx+1 > newSize-1 { - cs = &cacheStruct{reflect.MakeSlice(reflect.SliceOf(typ), newSize, newSize), 0} - reflectCacheMutex.Lock() - reflectCache[typ] = cs - reflectCacheMutex.Unlock() - } else { - reflectCacheMutex.Lock() - cs.idx = cs.idx + 1 - reflectCacheMutex.Unlock() - } - return cs.value.Index(cs.idx).Addr() -} - // 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) @@ -197,9 +167,7 @@ func (rs *Rows) ScanMap(dest interface{}) error { vvv := vv.Elem() for i, _ := range cols { - newDest[i] = ReflectNew(vvv.Type().Elem()).Interface() - //v := reflect.New(vvv.Type().Elem()) - //newDest[i] = v.Interface() + newDest[i] = rs.db.reflectNew(vvv.Type().Elem()).Interface() } err = rs.Rows.Scan(newDest...) @@ -215,32 +183,6 @@ func (rs *Rows) ScanMap(dest interface{}) error { return nil } -/*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)) - err = rs.ScanSlice(newDest) - if err != nil { - return err - } - - vvv := vv.Elem() - - 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: From 0177c08cee8834c6aa53764e9d6a5f1ffb2c74f7 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 22 Mar 2018 22:29:44 +0800 Subject: [PATCH 106/135] vgo support --- go.mod | 1 + 1 file changed, 1 insertion(+) create mode 100644 go.mod diff --git a/go.mod b/go.mod new file mode 100644 index 00000000..70c86bcb --- /dev/null +++ b/go.mod @@ -0,0 +1 @@ +module "github.com/go-xorm/core" From cb67f794cef1aec10d8eea4899cd4ab125f50345 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 10 Apr 2018 00:05:20 +0800 Subject: [PATCH 107/135] fix schema for postgres --- dialect.go | 3 ++- index.go | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/dialect.go b/dialect.go index 824d1efe..c288a084 100644 --- a/dialect.go +++ b/dialect.go @@ -149,7 +149,8 @@ func (db *Base) SupportDropIfExists() bool { } func (db *Base) DropTableSql(tableName string) string { - return fmt.Sprintf("DROP TABLE IF EXISTS `%s`", tableName) + quote := db.dialect.Quote + return fmt.Sprintf("DROP TABLE IF EXISTS %s", quote(tableName)) } func (db *Base) HasRecords(query string, args ...interface{}) (bool, error) { diff --git a/index.go b/index.go index 73b95175..9aa1b7ac 100644 --- a/index.go +++ b/index.go @@ -22,6 +22,8 @@ type Index struct { func (index *Index) XName(tableName string) string { if !strings.HasPrefix(index.Name, "UQE_") && !strings.HasPrefix(index.Name, "IDX_") { + tableName = strings.Replace(tableName, `"`, "", -1) + tableName = strings.Replace(tableName, `.`, "_", -1) if index.Type == UniqueType { return fmt.Sprintf("UQE_%v_%v", tableName, index.Name) } From f43c33d9a48db006417a7ac4c16b08897e3e1458 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 10 Apr 2018 10:57:29 +0800 Subject: [PATCH 108/135] fix (id) bug --- filter.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/filter.go b/filter.go index 60caaf29..35b0ece6 100644 --- a/filter.go +++ b/filter.go @@ -37,9 +37,9 @@ func (q *Quoter) Quote(content string) string { func (i *IdFilter) Do(sql string, dialect Dialect, table *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) + 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 } From 34a5be723a93814005d74e958e2ee211aaf56f2f Mon Sep 17 00:00:00 2001 From: Boyi Wu Date: Thu, 3 May 2018 20:06:48 +0800 Subject: [PATCH 109/135] Add NTEXT type for supporting mssql (#43) --- type.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/type.go b/type.go index 9171ce2d..5cbf9305 100644 --- a/type.go +++ b/type.go @@ -74,6 +74,7 @@ var ( NVarchar = "NVARCHAR" TinyText = "TINYTEXT" Text = "TEXT" + NText = "NTEXT" Clob = "CLOB" MediumText = "MEDIUMTEXT" LongText = "LONGTEXT" @@ -130,6 +131,7 @@ var ( NVarchar: TEXT_TYPE, TinyText: TEXT_TYPE, Text: TEXT_TYPE, + NText: TEXT_TYPE, MediumText: TEXT_TYPE, LongText: TEXT_TYPE, Uuid: TEXT_TYPE, @@ -293,7 +295,7 @@ func SQLType2Type(st SQLType) reflect.Type { return reflect.TypeOf(float32(1)) case Double: return reflect.TypeOf(float64(1)) - case Char, Varchar, NVarchar, TinyText, Text, MediumText, LongText, Enum, Set, Uuid, Clob, SysName: + case Char, 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{}) From 7cc3bbdbf2f8960debe243dfbdd7f37c8ac469fc Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 4 May 2018 13:52:29 +0800 Subject: [PATCH 110/135] remove unnecessary space --- column.go | 4 ++-- table.go | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/column.go b/column.go index 65370bb5..20912b71 100644 --- a/column.go +++ b/column.go @@ -147,12 +147,12 @@ func (col *Column) ValueOfV(dataStruct *reflect.Value) (*reflect.Value, error) { } fieldValue = fieldValue.Elem().FieldByName(fieldPath[i+1]) } else { - return nil, fmt.Errorf("field %v is not valid", col.FieldName) + 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 nil, fmt.Errorf("field %v is not valid", col.FieldName) } return &fieldValue, nil diff --git a/table.go b/table.go index 88199bed..b5d07940 100644 --- a/table.go +++ b/table.go @@ -49,7 +49,6 @@ func NewTable(name string, t reflect.Type) *Table { } func (table *Table) columnsByName(name string) []*Column { - n := len(name) for k := range table.columnsMap { @@ -75,7 +74,6 @@ func (table *Table) GetColumn(name string) *Column { } func (table *Table) GetColumnIdx(name string, idx int) *Column { - cols := table.columnsByName(name) if cols != nil && idx < len(cols) { From b8d39bd0ff10e7262fa46dbc18c784308bd47e14 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 9 Aug 2018 22:33:34 +0800 Subject: [PATCH 111/135] upgrade circleci from 1 to 2 --- .circleci/config.yml | 42 ++++++++++++++++++++++++++++++++++++++++++ circle.yml | 15 --------------- 2 files changed, 42 insertions(+), 15 deletions(-) create mode 100644 .circleci/config.yml delete mode 100644 circle.yml diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 00000000..825ce686 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,42 @@ +# Golang CircleCI 2.0 configuration file +# +# Check https://circleci.com/docs/2.0/language-go/ for more details +version: 2 +jobs: + build: + docker: + # specify the version + - image: circleci/golang:1.8 + + # CircleCI PostgreSQL images available at: https://hub.docker.com/r/circleci/postgres/ + - image: circleci/mysql:5.7 + environment: + MYSQL_ALLOW_EMPTY_PASSWORD: true + MYSQL_DATABASE: core_test + MYSQL_HOST: 127.0.0.1 + MYSQL_ROOT_HOST: % + MYSQL_USER: root + + # Specify service dependencies here if necessary + # CircleCI maintains a library of pre-built images + # documented at https://circleci.com/docs/2.0/circleci-images/ + # - image: circleci/postgres:9.4 + + #### TEMPLATE_NOTE: go expects specific checkout path representing url + #### expecting it in the form of + #### /go/src/github.com/circleci/go-tool + #### /go/src/bitbucket.org/circleci/go-tool + working_directory: /go/src/github.com/go-xorm/core + steps: + - checkout + + # specify any bash command here prefixed with `run: ` + - run: mysql -u root -e "CREATE DATABASE core_test DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci" + - run: go get -u github.com/golang/lint/golint + - run: go get -u github.com/wadey/gocovmerge + - run: golint ./... + - run: go get -t -d -v ./... + - run: go test -v -race -coverprofile=coverage-1.txt -covermode=atomic + - run: go test -v -race --dbtype=sqlite3 -coverprofile=coverage-2.txt -covermode=atomic + - run: gocovmerge coverage-1.txt coverage-2.txt > coverage.txt + - run: bash <(curl -s https://codecov.io/bash) \ No newline at end of file diff --git a/circle.yml b/circle.yml deleted file mode 100644 index e6a05be2..00000000 --- a/circle.yml +++ /dev/null @@ -1,15 +0,0 @@ -dependencies: - override: - # './...' is a relative pattern which means all subdirectories - - go get -t -d -v ./... - - go build -v - -database: - override: - - mysql -u root -e "CREATE DATABASE core_test DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci" - -test: - override: - # './...' is a relative pattern which means all subdirectories - - go test -v -race - - go test -v -race --dbtype=sqlite3 From 6bc9412b1c4d00c0a5cc9dd939a245a82cc50227 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 8 Oct 2018 21:23:26 +0800 Subject: [PATCH 112/135] don't change index's columns sort in Equal --- index.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/index.go b/index.go index 9aa1b7ac..3786f52b 100644 --- a/index.go +++ b/index.go @@ -2,7 +2,6 @@ package core import ( "fmt" - "sort" "strings" ) @@ -46,11 +45,16 @@ func (index *Index) Equal(dst *Index) bool { if len(index.Cols) != len(dst.Cols) { return false } - sort.StringSlice(index.Cols).Sort() - sort.StringSlice(dst.Cols).Sort() for i := 0; i < len(index.Cols); i++ { - if index.Cols[i] != dst.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 } } From f50e09f2ad40f169e6078fff42829cc485f6af4e Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 17 Jan 2019 14:40:11 +0800 Subject: [PATCH 113/135] Add context support (#46) * add context support * fix config * fix circle config * golang test to go1.10 --- .circleci/config.yml | 6 +- cache.go | 4 + column.go | 11 +- converstion.go | 4 + db.go | 296 +++++++++---------------------------------- db_test.go | 4 + dialect.go | 4 + driver.go | 4 + error.go | 4 + filter.go | 4 + ilogger.go | 4 + index.go | 4 + mapper.go | 4 + mapper_test.go | 4 + pk.go | 4 + pk_test.go | 4 + rows.go | 4 + scan.go | 11 ++ stmt.go | 165 ++++++++++++++++++++++++ table.go | 4 + table_test.go | 4 + tx.go | 153 ++++++++++++++++++++++ type.go | 4 + 23 files changed, 468 insertions(+), 242 deletions(-) create mode 100644 stmt.go create mode 100644 tx.go diff --git a/.circleci/config.yml b/.circleci/config.yml index 825ce686..ce15f60e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -6,7 +6,7 @@ jobs: build: docker: # specify the version - - image: circleci/golang:1.8 + - image: circleci/golang:1.10 # CircleCI PostgreSQL images available at: https://hub.docker.com/r/circleci/postgres/ - image: circleci/mysql:5.7 @@ -14,7 +14,7 @@ jobs: MYSQL_ALLOW_EMPTY_PASSWORD: true MYSQL_DATABASE: core_test MYSQL_HOST: 127.0.0.1 - MYSQL_ROOT_HOST: % + MYSQL_ROOT_HOST: '%' MYSQL_USER: root # Specify service dependencies here if necessary @@ -31,7 +31,7 @@ jobs: - checkout # specify any bash command here prefixed with `run: ` - - run: mysql -u root -e "CREATE DATABASE core_test DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci" + #- run: mysql -u root -e "CREATE DATABASE core_test DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci" - run: go get -u github.com/golang/lint/golint - run: go get -u github.com/wadey/gocovmerge - run: golint ./... diff --git a/cache.go b/cache.go index 8f9531da..dc4992df 100644 --- a/cache.go +++ b/cache.go @@ -1,3 +1,7 @@ +// 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 ( diff --git a/column.go b/column.go index 20912b71..40d8f926 100644 --- a/column.go +++ b/column.go @@ -1,3 +1,7 @@ +// 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 ( @@ -41,6 +45,7 @@ type Column struct { Comment string } +// NewColumn creates a new column func NewColumn(name, fieldName string, sqlType SQLType, len1, len2 int, nullable bool) *Column { return &Column{ Name: name, @@ -66,7 +71,7 @@ func NewColumn(name, fieldName string, sqlType SQLType, len1, len2 int, nullable } } -// generate column description string according dialect +// String generate column description string according dialect func (col *Column) String(d Dialect) string { sql := d.QuoteStr() + col.Name + d.QuoteStr() + " " @@ -94,6 +99,7 @@ func (col *Column) String(d Dialect) string { return sql } +// StringNoPk generate column description string according dialect without primary keys func (col *Column) StringNoPk(d Dialect) string { sql := d.QuoteStr() + col.Name + d.QuoteStr() + " " @@ -114,12 +120,13 @@ func (col *Column) StringNoPk(d Dialect) string { return sql } -// return col's filed of struct's value +// 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, ".") diff --git a/converstion.go b/converstion.go index 18522fbe..9703c36e 100644 --- a/converstion.go +++ b/converstion.go @@ -1,3 +1,7 @@ +// 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 // Conversion is an interface. A type implements Conversion will according diff --git a/db.go b/db.go index 9969fa43..3e50a147 100644 --- a/db.go +++ b/db.go @@ -1,9 +1,13 @@ +// 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" - "errors" "fmt" "reflect" "regexp" @@ -68,6 +72,7 @@ type cacheStruct struct { idx int } +// DB is a wrap of sql.DB with extra contents type DB struct { *sql.DB Mapper IMapper @@ -75,6 +80,7 @@ type DB struct { reflectCacheMutex sync.RWMutex } +// Open opens a database func Open(driverName, dataSourceName string) (*DB, error) { db, err := sql.Open(driverName, dataSourceName) if err != nil { @@ -87,6 +93,7 @@ func Open(driverName, dataSourceName string) (*DB, error) { }, nil } +// FromDB creates a DB from a sql.DB func FromDB(db *sql.DB) *DB { return &DB{ DB: db, @@ -108,8 +115,9 @@ func (db *DB) reflectNew(typ reflect.Type) reflect.Value { return cs.value.Index(cs.idx).Addr() } -func (db *DB) Query(query string, args ...interface{}) (*Rows, error) { - rows, err := db.DB.Query(query, args...) +// 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() @@ -119,161 +127,69 @@ func (db *DB) Query(query string, args ...interface{}) (*Rows, error) { return &Rows{rows, db}, nil } -func (db *DB) QueryMap(query string, mp interface{}) (*Rows, error) { +// Query overwrites sql.DB.Query +func (db *DB) Query(query string, args ...interface{}) (*Rows, error) { + return db.QueryContext(context.Background(), query, args...) +} + +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.Query(query, args...) + return db.QueryContext(ctx, query, args...) } -func (db *DB) QueryStruct(query string, st interface{}) (*Rows, error) { +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.Query(query, args...) + return db.QueryContext(ctx, query, args...) } -func (db *DB) QueryRow(query string, args ...interface{}) *Row { - rows, err := db.Query(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) QueryRowMap(query string, mp interface{}) *Row { +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.QueryRow(query, args...) + return db.QueryRowContext(ctx, query, args...) } -func (db *DB) QueryRowStruct(query string, st interface{}) *Row { +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.QueryRow(query, args...) + return db.QueryRowContext(ctx, query, args...) } -type Stmt struct { - *sql.Stmt - db *DB - names map[string]int -} - -func (db *DB) Prepare(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.Prepare(query) - if err != nil { - return nil, err - } - return &Stmt{stmt, db, names}, nil -} - -func (s *Stmt) ExecMap(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.Exec(args...) -} - -func (s *Stmt) ExecStruct(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.Exec(args...) -} - -func (s *Stmt) Query(args ...interface{}) (*Rows, error) { - rows, err := s.Stmt.Query(args...) - if err != nil { - return nil, err - } - return &Rows{rows, s.db}, nil -} - -func (s *Stmt) QueryMap(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.Query(args...) -} - -func (s *Stmt) QueryStruct(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) QueryRow(args ...interface{}) *Row { - rows, err := s.Query(args...) - return &Row{rows, err} -} - -func (s *Stmt) QueryRowMap(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.QueryRow(args...) -} - -func (s *Stmt) QueryRowStruct(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.QueryRow(args...) +func (db *DB) QueryRowStruct(query string, st interface{}) *Row { + return db.QueryRowStructContext(context.Background(), query, st) } var ( @@ -282,120 +198,26 @@ var ( // insert into (name) values (?) // insert into (name) values (?name) -func (db *DB) ExecMap(query string, mp interface{}) (sql.Result, error) { +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.Exec(query, args...) + 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) { - query, args, err := StructToSlice(query, st) - if err != nil { - return nil, err - } - return db.DB.Exec(query, args...) -} - -type EmptyScanner struct { -} - -func (EmptyScanner) Scan(src interface{}) error { - return nil -} - -type Tx struct { - *sql.Tx - db *DB -} - -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) Prepare(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.Prepare(query) - if err != nil { - return nil, err - } - return &Stmt{stmt, tx.db, names}, nil -} - -func (tx *Tx) Stmt(stmt *Stmt) *Stmt { - // TODO: - return stmt -} - -func (tx *Tx) ExecMap(query string, mp interface{}) (sql.Result, error) { - query, args, err := MapToSlice(query, mp) - if err != nil { - return nil, err - } - return tx.Tx.Exec(query, args...) -} - -func (tx *Tx) ExecStruct(query string, st interface{}) (sql.Result, error) { - query, args, err := StructToSlice(query, st) - if err != nil { - return nil, err - } - return tx.Tx.Exec(query, args...) -} - -func (tx *Tx) Query(query string, args ...interface{}) (*Rows, error) { - rows, err := tx.Tx.Query(query, args...) - if err != nil { - return nil, err - } - return &Rows{rows, tx.db}, nil -} - -func (tx *Tx) QueryMap(query string, mp interface{}) (*Rows, error) { - query, args, err := MapToSlice(query, mp) - if err != nil { - return nil, err - } - return tx.Query(query, args...) -} - -func (tx *Tx) QueryStruct(query string, st interface{}) (*Rows, error) { - query, args, err := StructToSlice(query, st) - if err != nil { - return nil, err - } - return tx.Query(query, args...) -} - -func (tx *Tx) QueryRow(query string, args ...interface{}) *Row { - rows, err := tx.Query(query, args...) - return &Row{rows, err} -} - -func (tx *Tx) QueryRowMap(query string, mp interface{}) *Row { - query, args, err := MapToSlice(query, mp) - if err != nil { - return &Row{nil, err} - } - return tx.QueryRow(query, args...) -} - -func (tx *Tx) QueryRowStruct(query string, st interface{}) *Row { - query, args, err := StructToSlice(query, st) - if err != nil { - return &Row{nil, err} - } - return tx.QueryRow(query, args...) + return db.ExecStructContext(context.Background(), query, st) } diff --git a/db_test.go b/db_test.go index 43637e8c..5505bc5f 100644 --- a/db_test.go +++ b/db_test.go @@ -1,3 +1,7 @@ +// 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 ( diff --git a/dialect.go b/dialect.go index c288a084..5d35a4f1 100644 --- a/dialect.go +++ b/dialect.go @@ -1,3 +1,7 @@ +// 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 ( diff --git a/driver.go b/driver.go index 0f1020b4..ceef4ba6 100644 --- a/driver.go +++ b/driver.go @@ -1,3 +1,7 @@ +// 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 type Driver interface { diff --git a/error.go b/error.go index 640e6036..63ea53e4 100644 --- a/error.go +++ b/error.go @@ -1,3 +1,7 @@ +// 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" diff --git a/filter.go b/filter.go index 35b0ece6..6aeed424 100644 --- a/filter.go +++ b/filter.go @@ -1,3 +1,7 @@ +// 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 ( diff --git a/ilogger.go b/ilogger.go index c8d78496..348ab88f 100644 --- a/ilogger.go +++ b/ilogger.go @@ -1,3 +1,7 @@ +// 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 type LogLevel int diff --git a/index.go b/index.go index 3786f52b..ac97b685 100644 --- a/index.go +++ b/index.go @@ -1,3 +1,7 @@ +// 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 ( diff --git a/mapper.go b/mapper.go index bb72a156..ec44ea0d 100644 --- a/mapper.go +++ b/mapper.go @@ -1,3 +1,7 @@ +// 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 ( diff --git a/mapper_test.go b/mapper_test.go index 043087a2..aa21c5c1 100644 --- a/mapper_test.go +++ b/mapper_test.go @@ -1,3 +1,7 @@ +// 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 ( diff --git a/pk.go b/pk.go index 1810dd94..05a7672d 100644 --- a/pk.go +++ b/pk.go @@ -1,3 +1,7 @@ +// 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 ( diff --git a/pk_test.go b/pk_test.go index 7986ae41..8430f95e 100644 --- a/pk_test.go +++ b/pk_test.go @@ -1,3 +1,7 @@ +// 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 ( diff --git a/rows.go b/rows.go index 580de4f9..2b046d84 100644 --- a/rows.go +++ b/rows.go @@ -1,3 +1,7 @@ +// 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 ( diff --git a/scan.go b/scan.go index b7c159b2..897b5341 100644 --- a/scan.go +++ b/scan.go @@ -1,3 +1,7 @@ +// 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 ( @@ -53,3 +57,10 @@ func convertTime(dest *NullTime, src interface{}) error { } return nil } + +type EmptyScanner struct { +} + +func (EmptyScanner) Scan(src interface{}) error { + return nil +} diff --git a/stmt.go b/stmt.go new file mode 100644 index 00000000..20ee202b --- /dev/null +++ b/stmt.go @@ -0,0 +1,165 @@ +// 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" +) + +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/table.go b/table.go index b5d07940..d129e60f 100644 --- a/table.go +++ b/table.go @@ -1,3 +1,7 @@ +// 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 ( diff --git a/table_test.go b/table_test.go index efa3face..ea31afbc 100644 --- a/table_test.go +++ b/table_test.go @@ -1,3 +1,7 @@ +// 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 ( diff --git a/tx.go b/tx.go new file mode 100644 index 00000000..a56b7006 --- /dev/null +++ b/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/type.go b/type.go index 5cbf9305..81dc1913 100644 --- a/type.go +++ b/type.go @@ -1,3 +1,7 @@ +// 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 ( From f7f8c2814882a0cb5c5f4e192b87a107da2f0a6c Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 17 Jan 2019 14:47:46 +0800 Subject: [PATCH 114/135] fix bench tests (#47) --- db_test.go | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/db_test.go b/db_test.go index 5505bc5f..ff14a487 100644 --- a/db_test.go +++ b/db_test.go @@ -233,8 +233,15 @@ func BenchmarkSliceInterfaceQuery(b *testing.B) { b.Error(err) } b.Log(slice) - if *slice[1].(*string) != "xlw" { - b.Error(errors.New("name should be xlw")) + 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")) + } } } @@ -380,9 +387,17 @@ func BenchmarkMapInterfaceQuery(b *testing.B) { if err != nil { b.Error(err) } - if m["name"].(string) != "xlw" { - b.Log(m) - b.Error(errors.New("name should be xlw")) + 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")) + } } } From 568ffe1f0f887a91eaea9ef8092ca5d4164a4c26 Mon Sep 17 00:00:00 2001 From: Robert Gabriel Jakabosky Date: Thu, 17 Jan 2019 15:33:21 +0800 Subject: [PATCH 115/135] Add some missing MS SQL Server types: NChar, SmallDateTime, Money, SmallMoney (#45) --- type.go | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/type.go b/type.go index 81dc1913..81649536 100644 --- a/type.go +++ b/type.go @@ -75,6 +75,7 @@ var ( Char = "CHAR" Varchar = "VARCHAR" + NChar = "NCHAR" NVarchar = "NVARCHAR" TinyText = "TINYTEXT" Text = "TEXT" @@ -88,12 +89,15 @@ var ( Date = "DATE" DateTime = "DATETIME" + SmallDateTime = "SMALLDATETIME" Time = "TIME" TimeStamp = "TIMESTAMP" TimeStampz = "TIMESTAMPZ" Decimal = "DECIMAL" Numeric = "NUMERIC" + Money = "MONEY" + SmallMoney = "SMALLMONEY" Real = "REAL" Float = "FLOAT" @@ -131,6 +135,7 @@ var ( Jsonb: TEXT_TYPE, Char: TEXT_TYPE, + NChar: TEXT_TYPE, Varchar: TEXT_TYPE, NVarchar: TEXT_TYPE, TinyText: TEXT_TYPE, @@ -147,12 +152,15 @@ var ( Time: TIME_TYPE, TimeStamp: TIME_TYPE, TimeStampz: TIME_TYPE, + SmallDateTime: 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, @@ -299,15 +307,15 @@ func SQLType2Type(st SQLType) reflect.Type { return reflect.TypeOf(float32(1)) case Double: return reflect.TypeOf(float64(1)) - case Char, Varchar, NVarchar, TinyText, Text, NText, MediumText, LongText, Enum, Set, Uuid, Clob, SysName: + 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: + case DateTime, Date, Time, TimeStamp, TimeStampz, SmallDateTime: return reflect.TypeOf(c_TIME_DEFAULT) - case Decimal, Numeric: + case Decimal, Numeric, Money, SmallMoney: return reflect.TypeOf("") default: return reflect.TypeOf("") From ccc80c1adf1f6172bbc548877f50a1163041a40a Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 24 Jan 2019 15:01:53 +0800 Subject: [PATCH 116/135] add go mod support (#48) --- go.mod | 8 +++++++- go.sum | 9 +++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 go.sum diff --git a/go.mod b/go.mod index 70c86bcb..b68392bb 100644 --- a/go.mod +++ b/go.mod @@ -1 +1,7 @@ -module "github.com/go-xorm/core" +module github.com/go-xorm/core + +require ( + github.com/go-sql-driver/mysql v1.4.1 + github.com/mattn/go-sqlite3 v1.10.0 + google.golang.org/appengine v1.4.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 00000000..8f20f8bc --- /dev/null +++ b/go.sum @@ -0,0 +1,9 @@ +github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= +github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o= +github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= From a31f53637037b3461649b8a9a03e13b26d31f12d Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 17 Jun 2019 13:27:01 +0800 Subject: [PATCH 117/135] change package name to xorm.io/core --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index b68392bb..2703545e 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/go-xorm/core +module xorm.io/core require ( github.com/go-sql-driver/mysql v1.4.1 From 82a58f487de6426d6ae552df84a12007405ca962 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 19 Jun 2019 17:40:11 +0800 Subject: [PATCH 118/135] add drone and update README --- .drone.yml | 30 ++++++++++++++++++++++++++++++ README.md | 2 +- 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 .drone.yml diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 00000000..dac9d08c --- /dev/null +++ b/.drone.yml @@ -0,0 +1,30 @@ +workspace: + base: /go + path: src/xorm.io/core + +clone: + git: + image: plugins/git:next + depth: 50 + tags: true + +matrix: + GO_VERSION: + - 1.9 + - 1.10 + - 1.11 + - 1.12 + +pipeline: + test: + image: golang:${GO_VERSION} + environment: + GOPROXY: https://goproxy.cn + commands: + - go get -u golang.org/x/lint/golint + - go get -u github.com/stretchr/testify/assert + - go get -u github.com/go-xorm/sqlfiddle + - golint ./... + - go test -v -race -coverprofile=coverage.txt -covermode=atomic + when: + event: [ push, tag, pull_request ] \ No newline at end of file diff --git a/README.md b/README.md index 09b72c74..2277163a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ Core is a lightweight wrapper of sql.DB. -[![CircleCI](https://circleci.com/gh/go-xorm/core/tree/master.svg?style=svg)](https://circleci.com/gh/go-xorm/core/tree/master) +[![Build Status](https://drone.gitea.com/api/badges/xorm/core/status.svg)](https://drone.gitea.com/xorm/core) # Open ```Go From fb7843378293ef3d53c03ddd2d7ec8c271a9aa56 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 20 Jun 2019 09:10:01 +0800 Subject: [PATCH 119/135] comment go lint --- .drone.yml | 2 +- cache.go | 16 ++++++++++------ db.go | 2 ++ dialect.go | 1 + 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/.drone.yml b/.drone.yml index dac9d08c..c7533f26 100644 --- a/.drone.yml +++ b/.drone.yml @@ -24,7 +24,7 @@ pipeline: - go get -u golang.org/x/lint/golint - go get -u github.com/stretchr/testify/assert - go get -u github.com/go-xorm/sqlfiddle - - golint ./... + #- golint ./... - go test -v -race -coverprofile=coverage.txt -covermode=atomic when: event: [ push, tag, pull_request ] \ No newline at end of file diff --git a/cache.go b/cache.go index dc4992df..982abe6a 100644 --- a/cache.go +++ b/cache.go @@ -14,19 +14,20 @@ import ( ) const ( - // default cache expired time + // CacheExpired is default cache expired time CacheExpired = 60 * time.Minute - // not use now + // CacheMaxMemory is not use now CacheMaxMemory = 256 - // evey ten minutes to clear all expired nodes + // CacheGcInterval represents interval time to clear all expired nodes CacheGcInterval = 10 * time.Minute - // each time when gc to removed max nodes + // 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.") + ErrCacheMiss = errors.New("xorm/cache: key not found") + ErrNotStored = errors.New("xorm/cache: not stored") ) // CacheStore is a interface to store cache @@ -69,6 +70,7 @@ func decodeIds(s string) ([]PK, error) { return pks, err } +// GetCacheSql returns cacher PKs via SQL func GetCacheSql(m Cacher, tableName, sql string, args interface{}) ([]PK, error) { bytes := m.GetIds(tableName, GenSqlKey(sql, args)) if bytes == nil { @@ -77,6 +79,7 @@ func GetCacheSql(m Cacher, tableName, sql string, args interface{}) ([]PK, error return decodeIds(bytes.(string)) } +// PutCacheSql puts cacher SQL and PKs func PutCacheSql(m Cacher, ids []PK, tableName, sql string, args interface{}) error { bytes, err := encodeIds(ids) if err != nil { @@ -86,6 +89,7 @@ func PutCacheSql(m Cacher, ids []PK, tableName, sql string, args interface{}) er return nil } +// GenSqlKey generates cache key func GenSqlKey(sql string, args interface{}) string { return fmt.Sprintf("%v-%v", sql, args) } diff --git a/db.go b/db.go index 3e50a147..5a7d59a0 100644 --- a/db.go +++ b/db.go @@ -132,6 +132,7 @@ 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 { @@ -140,6 +141,7 @@ func (db *DB) QueryMapContext(ctx context.Context, query string, mp interface{}) 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) } diff --git a/dialect.go b/dialect.go index 5d35a4f1..6c88e6c3 100644 --- a/dialect.go +++ b/dialect.go @@ -85,6 +85,7 @@ func OpenDialect(dialect Dialect) (*DB, error) { return Open(dialect.DriverName(), dialect.DataSourceName()) } +// Base represents a basic dialect and all real dialects could embed this struct type Base struct { db *DB dialect Dialect From 09e45b5cfc68ae8e73e0692de062488c316608c6 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 20 Jun 2019 09:13:29 +0800 Subject: [PATCH 120/135] update drone --- .drone.yml | 1 + go.mod | 8 +++++++- go.sum | 14 ++++++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index c7533f26..a21a1cfe 100644 --- a/.drone.yml +++ b/.drone.yml @@ -24,6 +24,7 @@ pipeline: - go get -u golang.org/x/lint/golint - go get -u github.com/stretchr/testify/assert - go get -u github.com/go-xorm/sqlfiddle + - go get -u github.com/go-sql-driver/mysql #- golint ./... - go test -v -race -coverprofile=coverage.txt -covermode=atomic when: diff --git a/go.mod b/go.mod index 2703545e..2ee35968 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,12 @@ module xorm.io/core require ( github.com/go-sql-driver/mysql v1.4.1 + github.com/golang/protobuf v1.3.1 // indirect github.com/mattn/go-sqlite3 v1.10.0 - google.golang.org/appengine v1.4.0 // indirect + golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 // indirect + golang.org/x/net v0.0.0-20190603091049-60506f45cf65 // indirect + golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed // indirect + golang.org/x/text v0.3.2 // indirect + golang.org/x/tools v0.0.0-20190606050223-4d9ae51c2468 // indirect + google.golang.org/appengine v1.6.0 // indirect ) diff --git a/go.sum b/go.sum index 8f20f8bc..4248a625 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,23 @@ github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o= github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190606050223-4d9ae51c2468/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= From bd332985500a74104d73d83b63538a5a9964eeb0 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 20 Jun 2019 09:19:10 +0800 Subject: [PATCH 121/135] update drone --- .drone.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.drone.yml b/.drone.yml index a21a1cfe..823bde9c 100644 --- a/.drone.yml +++ b/.drone.yml @@ -25,6 +25,7 @@ pipeline: - go get -u github.com/stretchr/testify/assert - go get -u github.com/go-xorm/sqlfiddle - go get -u github.com/go-sql-driver/mysql + - go get -u github.com/mattn/go-sqlite3 #- golint ./... - go test -v -race -coverprofile=coverage.txt -covermode=atomic when: From 7d1d621d68a90324854beeda1c334b0e4e741529 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 20 Jun 2019 11:36:10 +0800 Subject: [PATCH 122/135] update drone --- .drone.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.drone.yml b/.drone.yml index 823bde9c..dbaddefe 100644 --- a/.drone.yml +++ b/.drone.yml @@ -28,5 +28,15 @@ pipeline: - go get -u github.com/mattn/go-sqlite3 #- golint ./... - go test -v -race -coverprofile=coverage.txt -covermode=atomic + when: + event: [ push, tag, pull_request ] + + +services: + mysql: + image: mysql:5.7 + environment: + - MYSQL_DATABASE=core_test + - MYSQL_ALLOW_EMPTY_PASSWORD=yes when: event: [ push, tag, pull_request ] \ No newline at end of file From 817c743b933141ce059099001cb13e178c2a12bf Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 15 Jul 2019 08:27:12 +0000 Subject: [PATCH 123/135] lunny/fix_drone (#52) --- .drone.yml | 3 +-- db_test.go | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.drone.yml b/.drone.yml index dbaddefe..41390c8d 100644 --- a/.drone.yml +++ b/.drone.yml @@ -26,8 +26,7 @@ pipeline: - go get -u github.com/go-xorm/sqlfiddle - go get -u github.com/go-sql-driver/mysql - go get -u github.com/mattn/go-sqlite3 - #- golint ./... - - go test -v -race -coverprofile=coverage.txt -covermode=atomic + - go test -v -race -coverprofile=coverage.txt -covermode=atomic -dbConn="root:@tcp(mysql:3306)/core_test?charset=utf8mb4" when: event: [ push, tag, pull_request ] diff --git a/db_test.go b/db_test.go index ff14a487..348bbb07 100644 --- a/db_test.go +++ b/db_test.go @@ -17,6 +17,7 @@ import ( var ( dbtype = flag.String("dbtype", "mysql", "database type") + dbConn = flag.String("dbConn", "root:@/core_test?charset=utf8", "database connect string") createTableSql string ) @@ -50,7 +51,7 @@ func testOpen() (*DB, error) { os.Remove("./test.db") return Open("sqlite3", "./test.db") case "mysql": - return Open("mysql", "root:@/core_test?charset=utf8") + return Open("mysql", *dbConn) default: panic("no db type") } From 5d3ffb50debff768df1d50d08fc99474fca1cf46 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 15 Jul 2019 13:42:17 +0000 Subject: [PATCH 124/135] lunny/fix_fmt_lint (#53) --- .drone.yml | 1 + README.md | 2 ++ db.go | 2 ++ dialect.go | 2 +- error.go | 4 +++- ilogger.go | 4 +++- index.go | 7 ++++--- mapper.go | 8 ++++---- rows.go | 2 +- stmt.go | 1 + table.go | 9 +++++---- type.go | 42 +++++++++++++++++++++--------------------- 12 files changed, 48 insertions(+), 36 deletions(-) diff --git a/.drone.yml b/.drone.yml index 41390c8d..1ef2b813 100644 --- a/.drone.yml +++ b/.drone.yml @@ -26,6 +26,7 @@ pipeline: - go get -u github.com/go-xorm/sqlfiddle - go get -u github.com/go-sql-driver/mysql - go get -u github.com/mattn/go-sqlite3 + - go vet - go test -v -race -coverprofile=coverage.txt -covermode=atomic -dbConn="root:@tcp(mysql:3306)/core_test?charset=utf8mb4" when: event: [ push, tag, pull_request ] diff --git a/README.md b/README.md index 2277163a..c2cedcae 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ Core is a lightweight wrapper of sql.DB. [![Build Status](https://drone.gitea.com/api/badges/xorm/core/status.svg)](https://drone.gitea.com/xorm/core) +[![](http://gocover.io/_badge/xorm.io/core)](http://gocover.io/xorm.io/core) +[![Go Report Card](https://goreportcard.com/badge/code.gitea.io/gitea)](https://goreportcard.com/report/xorm.io/core) # Open ```Go diff --git a/db.go b/db.go index 5a7d59a0..4847937c 100644 --- a/db.go +++ b/db.go @@ -15,6 +15,7 @@ import ( ) var ( + // DefaultCacheSize sets the default cache size DefaultCacheSize = 200 ) @@ -198,6 +199,7 @@ 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) { diff --git a/dialect.go b/dialect.go index 6c88e6c3..9d214f31 100644 --- a/dialect.go +++ b/dialect.go @@ -311,7 +311,7 @@ func RegisterDialect(dbName DbType, dialectFunc func() Dialect) { dialects[strings.ToLower(string(dbName))] = dialectFunc // !nashtsai! allow override dialect } -// QueryDialect query if registed database dialect +// QueryDialect query if registered database dialect func QueryDialect(dbName DbType) Dialect { if d, ok := dialects[strings.ToLower(string(dbName))]; ok { return d() diff --git a/error.go b/error.go index 63ea53e4..1fd18348 100644 --- a/error.go +++ b/error.go @@ -7,6 +7,8 @@ package core import "errors" var ( - ErrNoMapPointer = errors.New("mp should be a map's pointer") + // 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/ilogger.go b/ilogger.go index 348ab88f..0c17750c 100644 --- a/ilogger.go +++ b/ilogger.go @@ -4,8 +4,10 @@ package 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 @@ -16,7 +18,7 @@ const ( LOG_UNKNOWN ) -// logger interface +// ILogger is a logger interface type ILogger interface { Debug(v ...interface{}) Debugf(format string, v ...interface{}) diff --git a/index.go b/index.go index ac97b685..2915428f 100644 --- a/index.go +++ b/index.go @@ -9,12 +9,13 @@ import ( "strings" ) +// enumerate all index types const ( IndexType = iota + 1 UniqueType ) -// database index +// Index represents a database index type Index struct { IsRegular bool Name string @@ -35,7 +36,7 @@ func (index *Index) XName(tableName string) string { return index.Name } -// add columns which will be composite index +// AddColumn add columns which will be composite index func (index *Index) AddColumn(cols ...string) { for _, col := range cols { index.Cols = append(index.Cols, col) @@ -65,7 +66,7 @@ func (index *Index) Equal(dst *Index) bool { return true } -// new an index +// NewIndex new an index object func NewIndex(name string, indexType int) *Index { return &Index{true, name, indexType, make([]string, 0)} } diff --git a/mapper.go b/mapper.go index ec44ea0d..4df05cb8 100644 --- a/mapper.go +++ b/mapper.go @@ -9,7 +9,7 @@ import ( "sync" ) -// name translation between struct, fields names and table, column names +// IMapper represents a name convertation between struct's fields name and table's column name type IMapper interface { Obj2Table(string) string Table2Obj(string) string @@ -184,7 +184,7 @@ func (mapper GonicMapper) Table2Obj(name string) string { return string(newstr) } -// A GonicMapper that contains a list of common initialisms taken from golang/lint +// LintGonicMapper is A GonicMapper that contains a list of common initialisms taken from golang/lint var LintGonicMapper = GonicMapper{ "API": true, "ASCII": true, @@ -221,7 +221,7 @@ var LintGonicMapper = GonicMapper{ "XSS": true, } -// provide prefix table name support +// PrefixMapper provides prefix table name support type PrefixMapper struct { Mapper IMapper Prefix string @@ -239,7 +239,7 @@ func NewPrefixMapper(mapper IMapper, prefix string) PrefixMapper { return PrefixMapper{mapper, prefix} } -// provide suffix table name support +// SuffixMapper provides suffix table name support type SuffixMapper struct { Mapper IMapper Suffix string diff --git a/rows.go b/rows.go index 2b046d84..a1e8bfbc 100644 --- a/rows.go +++ b/rows.go @@ -170,7 +170,7 @@ func (rs *Rows) ScanMap(dest interface{}) error { newDest := make([]interface{}, len(cols)) vvv := vv.Elem() - for i, _ := range cols { + for i := range cols { newDest[i] = rs.db.reflectNew(vvv.Type().Elem()).Interface() } diff --git a/stmt.go b/stmt.go index 20ee202b..8a21541a 100644 --- a/stmt.go +++ b/stmt.go @@ -11,6 +11,7 @@ import ( "reflect" ) +// Stmt reprents a stmt objects type Stmt struct { *sql.Stmt db *DB diff --git a/table.go b/table.go index d129e60f..0a3889e1 100644 --- a/table.go +++ b/table.go @@ -9,7 +9,7 @@ import ( "strings" ) -// database table +// Table represents a database table type Table struct { Name string Type reflect.Type @@ -41,6 +41,7 @@ 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), @@ -87,7 +88,7 @@ func (table *Table) GetColumnIdx(name string, idx int) *Column { return nil } -// if has primary key, return column +// PKColumns reprents all primary key columns func (table *Table) PKColumns() []*Column { columns := make([]*Column, len(table.PrimaryKeys)) for i, name := range table.PrimaryKeys { @@ -117,7 +118,7 @@ func (table *Table) DeletedColumn() *Column { return table.GetColumn(table.Deleted) } -// add a column to table +// 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) @@ -148,7 +149,7 @@ func (table *Table) AddColumn(col *Column) { } } -// add an index or an unique to table +// AddIndex adds an index or an unique to table func (table *Table) AddIndex(index *Index) { table.Indexes[index.Name] = index } diff --git a/type.go b/type.go index 81649536..14d6e12e 100644 --- a/type.go +++ b/type.go @@ -87,16 +87,16 @@ var ( UniqueIdentifier = "UNIQUEIDENTIFIER" SysName = "SYSNAME" - Date = "DATE" - DateTime = "DATETIME" - SmallDateTime = "SMALLDATETIME" - Time = "TIME" - TimeStamp = "TIMESTAMP" - TimeStampz = "TIMESTAMPZ" + Date = "DATE" + DateTime = "DATETIME" + SmallDateTime = "SMALLDATETIME" + Time = "TIME" + TimeStamp = "TIMESTAMP" + TimeStampz = "TIMESTAMPZ" - Decimal = "DECIMAL" - Numeric = "NUMERIC" - Money = "MONEY" + Decimal = "DECIMAL" + Numeric = "NUMERIC" + Money = "MONEY" SmallMoney = "SMALLMONEY" Real = "REAL" @@ -147,19 +147,19 @@ var ( Clob: TEXT_TYPE, SysName: TEXT_TYPE, - Date: TIME_TYPE, - DateTime: TIME_TYPE, - Time: TIME_TYPE, - TimeStamp: TIME_TYPE, - TimeStampz: TIME_TYPE, - SmallDateTime: TIME_TYPE, + Date: TIME_TYPE, + DateTime: TIME_TYPE, + Time: TIME_TYPE, + TimeStamp: TIME_TYPE, + TimeStampz: TIME_TYPE, + SmallDateTime: TIME_TYPE, - Decimal: NUMERIC_TYPE, - Numeric: NUMERIC_TYPE, - Real: NUMERIC_TYPE, - Float: NUMERIC_TYPE, - Double: NUMERIC_TYPE, - Money: NUMERIC_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, From 65822bd505b2c408a3aeb20cc3495925b84e8627 Mon Sep 17 00:00:00 2001 From: zhanghelong Date: Tue, 16 Jul 2019 08:03:49 +0000 Subject: [PATCH 125/135] Remove func QuoteStr() in interface Dialect (#51) --- column.go | 4 ++-- dialect.go | 18 +++++++++++++----- filter.go | 20 ++++++++++++++++++-- filter_test.go | 25 +++++++++++++++++++++++++ 4 files changed, 58 insertions(+), 9 deletions(-) create mode 100644 filter_test.go diff --git a/column.go b/column.go index 40d8f926..8eaa5445 100644 --- a/column.go +++ b/column.go @@ -73,7 +73,7 @@ func NewColumn(name, fieldName string, sqlType SQLType, len1, len2 int, nullable // String generate column description string according dialect func (col *Column) String(d Dialect) string { - sql := d.QuoteStr() + col.Name + d.QuoteStr() + " " + sql := d.Quote(col.Name) + " " sql += d.SqlType(col) + " " @@ -101,7 +101,7 @@ func (col *Column) String(d Dialect) string { // StringNoPk generate column description string according dialect without primary keys func (col *Column) StringNoPk(d Dialect) string { - sql := d.QuoteStr() + col.Name + d.QuoteStr() + " " + sql := d.Quote(col.Name) sql += d.SqlType(col) + " " diff --git a/dialect.go b/dialect.go index 9d214f31..0a0c2d0d 100644 --- a/dialect.go +++ b/dialect.go @@ -40,9 +40,10 @@ type Dialect interface { DriverName() string DataSourceName() string - QuoteStr() string IsReserved(string) bool Quote(string) string + // Deprecated: use Quote(string) string instead + QuoteStr() string AndStr() string OrStr() string EqStr() string @@ -70,8 +71,8 @@ type Dialect interface { ForUpdateSql(query string) string - //CreateTableIfNotExists(table *Table, tableName, storeEngine, charset string) error - //MustDropTable(tableName string) error + // CreateTableIfNotExists(table *Table, tableName, storeEngine, charset string) error + // MustDropTable(tableName string) error GetColumns(tableName string) ([]string, map[string]*Column, error) GetTables() ([]*Table, error) @@ -173,8 +174,15 @@ func (db *Base) HasRecords(query string, args ...interface{}) (bool, error) { } func (db *Base) IsColumnExist(tableName, colName string) (bool, error) { - query := "SELECT `COLUMN_NAME` FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ? AND `COLUMN_NAME` = ?" - query = strings.Replace(query, "`", db.dialect.QuoteStr(), -1) + 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.DbName, tableName, colName) } diff --git a/filter.go b/filter.go index 6aeed424..aeea1223 100644 --- a/filter.go +++ b/filter.go @@ -19,7 +19,23 @@ type QuoteFilter struct { } func (s *QuoteFilter) Do(sql string, dialect Dialect, table *Table) string { - return strings.Replace(sql, "`", dialect.QuoteStr(), -1) + 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 @@ -35,7 +51,7 @@ func NewQuoter(dialect Dialect) *Quoter { } func (q *Quoter) Quote(content string) string { - return q.dialect.QuoteStr() + content + q.dialect.QuoteStr() + return q.dialect.Quote(content) } func (i *IdFilter) Do(sql string, dialect Dialect, table *Table) string { diff --git a/filter_test.go b/filter_test.go new file mode 100644 index 00000000..c9d836b9 --- /dev/null +++ b/filter_test.go @@ -0,0 +1,25 @@ +package core + +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, + ) +} From bac7a226ecac053bc2fed9bb466e77d23f8c9d79 Mon Sep 17 00:00:00 2001 From: helong zhang Date: Tue, 23 Jul 2019 07:26:21 +0000 Subject: [PATCH 126/135] Add missing whitespace in StringNoPk in column.go (#54) --- column.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/column.go b/column.go index 8eaa5445..b5906a98 100644 --- a/column.go +++ b/column.go @@ -101,7 +101,7 @@ func (col *Column) String(d Dialect) string { // StringNoPk generate column description string according dialect without primary keys func (col *Column) StringNoPk(d Dialect) string { - sql := d.Quote(col.Name) + sql := d.Quote(col.Name) + " " sql += d.SqlType(col) + " " From 25c56ece6dfd7d0cf0f54a9a324117e9e2f37c8f Mon Sep 17 00:00:00 2001 From: helong zhang Date: Wed, 24 Jul 2019 05:07:17 +0000 Subject: [PATCH 127/135] Remove QuoteStr() in interface Dialect (#55) --- dialect.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dialect.go b/dialect.go index 0a0c2d0d..c166596c 100644 --- a/dialect.go +++ b/dialect.go @@ -42,8 +42,7 @@ type Dialect interface { IsReserved(string) bool Quote(string) string - // Deprecated: use Quote(string) string instead - QuoteStr() string + AndStr() string OrStr() string EqStr() string From b7dc77e1bf3a62cec7cb99d8076cc3e23a99662c Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 30 Aug 2019 03:53:40 +0000 Subject: [PATCH 128/135] Fix flag.Parse on init (#57) --- .drone.yml | 177 +++++++++++++++++++++++++++++++++++++++++++---------- db_test.go | 31 ++++++---- go.mod | 7 +-- go.sum | 27 ++++---- 4 files changed, 175 insertions(+), 67 deletions(-) diff --git a/.drone.yml b/.drone.yml index 1ef2b813..4cb2fb4a 100644 --- a/.drone.yml +++ b/.drone.yml @@ -1,42 +1,153 @@ +--- +kind: pipeline +name: go1.10 + +platform: + os: linux + arch: amd64 + +clone: + disable: true + workspace: base: /go path: src/xorm.io/core -clone: - git: - image: plugins/git:next +steps: +- name: git + pull: default + image: plugins/git:next + settings: depth: 50 tags: true -matrix: - GO_VERSION: - - 1.9 - - 1.10 - - 1.11 - - 1.12 - -pipeline: - test: - image: golang:${GO_VERSION} - environment: - GOPROXY: https://goproxy.cn - commands: - - go get -u golang.org/x/lint/golint - - go get -u github.com/stretchr/testify/assert - - go get -u github.com/go-xorm/sqlfiddle - - go get -u github.com/go-sql-driver/mysql - - go get -u github.com/mattn/go-sqlite3 - - go vet - - go test -v -race -coverprofile=coverage.txt -covermode=atomic -dbConn="root:@tcp(mysql:3306)/core_test?charset=utf8mb4" - when: - event: [ push, tag, pull_request ] - +- name: test + pull: default + image: golang:1.10 + commands: + - go get github.com/stretchr/testify/assert + - go get github.com/go-xorm/sqlfiddle + - go get github.com/go-sql-driver/mysql + - go get github.com/mattn/go-sqlite3 + - go vet + - "go test -v -race -coverprofile=coverage.txt -covermode=atomic -dbConn=\"root:@tcp(mysql:3306)/core_test?charset=utf8mb4\"" + when: + event: + - push + - tag + - pull_request services: - mysql: - image: mysql:5.7 - environment: - - MYSQL_DATABASE=core_test - - MYSQL_ALLOW_EMPTY_PASSWORD=yes - when: - event: [ push, tag, pull_request ] \ No newline at end of file +- name: mysql + pull: default + image: mysql:5.7 + environment: + MYSQL_ALLOW_EMPTY_PASSWORD: yes + MYSQL_DATABASE: core_test + when: + event: + - push + - tag + - pull_request + +--- +kind: pipeline +name: go1.11 + +platform: + os: linux + arch: amd64 + +clone: + disable: true + +workspace: + base: /go + path: src/xorm.io/core + +steps: +- name: git + pull: default + image: plugins/git:next + settings: + depth: 50 + tags: true + +- name: test + pull: default + image: golang:1.11 + commands: + - go vet + - "go test -v -race -coverprofile=coverage.txt -covermode=atomic -dbConn=\"root:@tcp(mysql:3306)/core_test?charset=utf8mb4\"" + environment: + GO111MODULE: "on" + GOPROXY: https://goproxy.cn + when: + event: + - push + - tag + - pull_request + +services: +- name: mysql + pull: default + image: mysql:5.7 + environment: + MYSQL_ALLOW_EMPTY_PASSWORD: yes + MYSQL_DATABASE: core_test + when: + event: + - push + - tag + - pull_request + +--- +kind: pipeline +name: go1.12 + +platform: + os: linux + arch: amd64 + +clone: + disable: true + +workspace: + base: /go + path: src/xorm.io/core + +steps: +- name: git + pull: default + image: plugins/git:next + settings: + depth: 50 + tags: true + +- name: test + pull: default + image: golang:1.12 + commands: + - go vet + - "go test -v -race -coverprofile=coverage.txt -covermode=atomic -dbConn=\"root:@tcp(mysql:3306)/core_test?charset=utf8mb4\"" + environment: + GO111MODULE: "on" + GOPROXY: https://goproxy.cn + when: + event: + - push + - tag + - pull_request + +services: +- name: mysql + pull: default + image: mysql:5.7 + environment: + MYSQL_ALLOW_EMPTY_PASSWORD: yes + MYSQL_DATABASE: core_test + when: + event: + - push + - tag + - pull_request \ No newline at end of file diff --git a/db_test.go b/db_test.go index 348bbb07..affc1f91 100644 --- a/db_test.go +++ b/db_test.go @@ -21,28 +21,23 @@ var ( createTableSql string ) -type User struct { - Id int64 - Name string - Title string - Age float32 - Alias string - NickName string - Created NullTime -} - -func init() { +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);" - default: - panic("no db type") } + + exitCode := m.Run() + + os.Exit(exitCode) } func testOpen() (*DB, error) { @@ -101,6 +96,16 @@ func BenchmarkOriQuery(b *testing.B) { } } +type User struct { + Id int64 + Name string + Title string + Age float32 + Alias string + NickName string + Created NullTime +} + func BenchmarkStructQuery(b *testing.B) { b.StopTimer() diff --git a/go.mod b/go.mod index 2ee35968..9dc9c1f2 100644 --- a/go.mod +++ b/go.mod @@ -2,12 +2,7 @@ module xorm.io/core require ( github.com/go-sql-driver/mysql v1.4.1 - github.com/golang/protobuf v1.3.1 // indirect github.com/mattn/go-sqlite3 v1.10.0 - golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 // indirect - golang.org/x/net v0.0.0-20190603091049-60506f45cf65 // indirect - golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed // indirect - golang.org/x/text v0.3.2 // indirect - golang.org/x/tools v0.0.0-20190606050223-4d9ae51c2468 // indirect + github.com/stretchr/testify v1.4.0 google.golang.org/appengine v1.6.0 // indirect ) diff --git a/go.sum b/go.sum index 4248a625..172009ed 100644 --- a/go.sum +++ b/go.sum @@ -1,23 +1,20 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o= github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190606050223-4d9ae51c2468/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.0 h1:Tfd7cKwKbFRsI8RMAD3oqqw7JPFRrvFlOsfbgVkjOOw= google.golang.org/appengine v1.6.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= From 0f412da7e997fbc1a3fd32f287dce23ee1b778bf Mon Sep 17 00:00:00 2001 From: longdahai Date: Fri, 6 Sep 2019 12:36:29 +0000 Subject: [PATCH 129/135] Add Year type for supporting mysql (#58) --- type.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/type.go b/type.go index 14d6e12e..75b6c363 100644 --- a/type.go +++ b/type.go @@ -93,6 +93,7 @@ var ( Time = "TIME" TimeStamp = "TIMESTAMP" TimeStampz = "TIMESTAMPZ" + Year = "YEAR" Decimal = "DECIMAL" Numeric = "NUMERIC" @@ -153,6 +154,7 @@ var ( TimeStamp: TIME_TYPE, TimeStampz: TIME_TYPE, SmallDateTime: TIME_TYPE, + Year: TIME_TYPE, Decimal: NUMERIC_TYPE, Numeric: NUMERIC_TYPE, @@ -313,7 +315,7 @@ func SQLType2Type(st SQLType) reflect.Type { return reflect.TypeOf([]byte{}) case Bool: return reflect.TypeOf(true) - case DateTime, Date, Time, TimeStamp, TimeStampz, SmallDateTime: + case DateTime, Date, Time, TimeStamp, TimeStampz, SmallDateTime, Year: return reflect.TypeOf(c_TIME_DEFAULT) case Decimal, Numeric, Money, SmallMoney: return reflect.TypeOf("") From c96a57cc406ddba4f4b0cfedc635c38dddddcee1 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 28 Sep 2019 01:43:44 +0000 Subject: [PATCH 130/135] Fix question mark conversion bug (#60) --- filter.go | 33 ++++++++++++++++++++++++--------- filter_test.go | 11 +++++++++++ 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/filter.go b/filter.go index aeea1223..7079b822 100644 --- a/filter.go +++ b/filter.go @@ -70,15 +70,30 @@ type SeqFilter struct { Start int } -func (s *SeqFilter) Do(sql string, dialect Dialect, table *Table) string { - segs := strings.Split(sql, "?") - size := len(segs) - res := "" - for i, c := range segs { - if i < size-1 { - res += c + fmt.Sprintf("%s%v", s.Prefix, i+s.Start) +func convertQuestionMark(sql, prefix string, start int) string { + var buf strings.Builder + var beginSingleQuote bool + var beginTransfer bool + var index = start + for _, c := range sql { + if !beginSingleQuote && c == '?' { + buf.WriteString(fmt.Sprintf("%s%v", prefix, index)) + index++ + } else { + if c == '\\' { + beginTransfer = true + } else { + if !beginTransfer && c == '\'' { + beginSingleQuote = !beginSingleQuote + } + beginTransfer = false + } + buf.WriteRune(c) } } - res += segs[size-1] - return res + return buf.String() +} + +func (s *SeqFilter) Do(sql string, dialect Dialect, table *Table) string { + return convertQuestionMark(sql, s.Prefix, s.Start) } diff --git a/filter_test.go b/filter_test.go index c9d836b9..54a1cf6a 100644 --- a/filter_test.go +++ b/filter_test.go @@ -23,3 +23,14 @@ func TestQuoteFilter_Do(t *testing.T) { 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", + } + for sql, result := range kases { + assert.EqualValues(t, result, convertQuestionMark(sql, "$", 1)) + } +} From 90aeac8d08eb0774246cb35e1102858e0b9cd044 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 28 Sep 2019 05:59:35 +0000 Subject: [PATCH 131/135] fix arg conversion (#61) --- filter.go | 10 ++-------- filter_test.go | 5 ++++- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/filter.go b/filter.go index 7079b822..55be9562 100644 --- a/filter.go +++ b/filter.go @@ -73,20 +73,14 @@ type SeqFilter struct { func convertQuestionMark(sql, prefix string, start int) string { var buf strings.Builder var beginSingleQuote bool - var beginTransfer bool var index = start for _, c := range sql { if !beginSingleQuote && c == '?' { buf.WriteString(fmt.Sprintf("%s%v", prefix, index)) index++ } else { - if c == '\\' { - beginTransfer = true - } else { - if !beginTransfer && c == '\'' { - beginSingleQuote = !beginSingleQuote - } - beginTransfer = false + if c == '\'' { + beginSingleQuote = !beginSingleQuote } buf.WriteRune(c) } diff --git a/filter_test.go b/filter_test.go index 54a1cf6a..a6b7b3bb 100644 --- a/filter_test.go +++ b/filter_test.go @@ -28,7 +28,10 @@ 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\\??' 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)) From 4e5be27089e42fe5d3c340857966bf56fc146c9e Mon Sep 17 00:00:00 2001 From: SijmenSchoon Date: Sun, 3 Nov 2019 03:34:15 +0000 Subject: [PATCH 132/135] Use https for gocover badge (#64) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c2cedcae..54436b68 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ Core is a lightweight wrapper of sql.DB. [![Build Status](https://drone.gitea.com/api/badges/xorm/core/status.svg)](https://drone.gitea.com/xorm/core) -[![](http://gocover.io/_badge/xorm.io/core)](http://gocover.io/xorm.io/core) +[![Test Coverage](https://gocover.io/_badge/xorm.io/core)](https://gocover.io/xorm.io/core) [![Go Report Card](https://goreportcard.com/badge/code.gitea.io/gitea)](https://goreportcard.com/report/xorm.io/core) # Open From 909f0a5fefb9821f95856a38aff8f854d6ea6b64 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 13 Nov 2019 11:09:21 +0000 Subject: [PATCH 133/135] fix column default value be empty (#59) --- column.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/column.go b/column.go index b5906a98..8f375db5 100644 --- a/column.go +++ b/column.go @@ -37,7 +37,7 @@ type Column struct { IsDeleted bool IsCascade bool IsVersion bool - DefaultIsEmpty 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 @@ -65,7 +65,7 @@ func NewColumn(name, fieldName string, sqlType SQLType, len1, len2 int, nullable IsDeleted: false, IsCascade: false, IsVersion: false, - DefaultIsEmpty: false, + DefaultIsEmpty: true, // default should be no default EnumOptions: make(map[string]int), Comment: "", } From 6a6d853b6dfe7318b1aaa23875976dcad38b491d Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 19 Jan 2020 16:59:09 +0800 Subject: [PATCH 134/135] simple drone test --- .drone.yml | 120 ----------------------------------------------------- 1 file changed, 120 deletions(-) diff --git a/.drone.yml b/.drone.yml index 4cb2fb4a..3c118d4c 100644 --- a/.drone.yml +++ b/.drone.yml @@ -1,128 +1,8 @@ ---- -kind: pipeline -name: go1.10 - -platform: - os: linux - arch: amd64 - -clone: - disable: true - -workspace: - base: /go - path: src/xorm.io/core - -steps: -- name: git - pull: default - image: plugins/git:next - settings: - depth: 50 - tags: true - -- name: test - pull: default - image: golang:1.10 - commands: - - go get github.com/stretchr/testify/assert - - go get github.com/go-xorm/sqlfiddle - - go get github.com/go-sql-driver/mysql - - go get github.com/mattn/go-sqlite3 - - go vet - - "go test -v -race -coverprofile=coverage.txt -covermode=atomic -dbConn=\"root:@tcp(mysql:3306)/core_test?charset=utf8mb4\"" - when: - event: - - push - - tag - - pull_request - -services: -- name: mysql - pull: default - image: mysql:5.7 - environment: - MYSQL_ALLOW_EMPTY_PASSWORD: yes - MYSQL_DATABASE: core_test - when: - event: - - push - - tag - - pull_request - ---- -kind: pipeline -name: go1.11 - -platform: - os: linux - arch: amd64 - -clone: - disable: true - -workspace: - base: /go - path: src/xorm.io/core - -steps: -- name: git - pull: default - image: plugins/git:next - settings: - depth: 50 - tags: true - -- name: test - pull: default - image: golang:1.11 - commands: - - go vet - - "go test -v -race -coverprofile=coverage.txt -covermode=atomic -dbConn=\"root:@tcp(mysql:3306)/core_test?charset=utf8mb4\"" - environment: - GO111MODULE: "on" - GOPROXY: https://goproxy.cn - when: - event: - - push - - tag - - pull_request - -services: -- name: mysql - pull: default - image: mysql:5.7 - environment: - MYSQL_ALLOW_EMPTY_PASSWORD: yes - MYSQL_DATABASE: core_test - when: - event: - - push - - tag - - pull_request - --- kind: pipeline name: go1.12 -platform: - os: linux - arch: amd64 - -clone: - disable: true - -workspace: - base: /go - path: src/xorm.io/core - steps: -- name: git - pull: default - image: plugins/git:next - settings: - depth: 50 - tags: true - name: test pull: default From 1e7bccc690201fe9d8c5b0de4403c055de3052c2 Mon Sep 17 00:00:00 2001 From: Guillermo Prandi Date: Sun, 19 Jan 2020 09:04:11 +0000 Subject: [PATCH 135/135] Exclude schema from index name (#65) Exclude schema from the index name simple drone test fix column default value be empty (#59) Use https for gocover badge (#64) Co-authored-by: Guillermo Prandi Co-authored-by: Lunny Xiao Co-authored-by: SijmenSchoon Reviewed-on: https://gitea.com/xorm/core/pulls/65 Reviewed-by: Lunny Xiao --- index.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.go b/index.go index 2915428f..129b5439 100644 --- a/index.go +++ b/index.go @@ -26,8 +26,8 @@ type Index struct { func (index *Index) XName(tableName string) string { if !strings.HasPrefix(index.Name, "UQE_") && !strings.HasPrefix(index.Name, "IDX_") { - tableName = strings.Replace(tableName, `"`, "", -1) - tableName = strings.Replace(tableName, `.`, "_", -1) + 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) }