xormplus 10 سال پیش
والد
کامیت
a2e615076f
21فایلهای تغییر یافته به همراه2894 افزوده شده و 0 حذف شده
  1. 114 0
      README.md
  2. 1 0
      benchmark.sh
  3. 87 0
      cache.go
  4. 172 0
      column.go
  5. 6 0
      config
  6. 8 0
      converstion.go
  7. 563 0
      db.go
  8. 659 0
      db_test.go
  9. 1 0
      description
  10. 282 0
      dialect.go
  11. 27 0
      driver.go
  12. 10 0
      error.go
  13. 64 0
      filter.go
  14. 28 0
      ilogger.go
  15. 61 0
      index.go
  16. 254 0
      mapper.go
  17. 45 0
      mapper_test.go
  18. 26 0
      pk.go
  19. 33 0
      pk_test.go
  20. 130 0
      table.go
  21. 323 0
      type.go

+ 114 - 0
README.md

@@ -0,0 +1,114 @@
+Core is a lightweight wrapper of sql.DB.
+
+# Open
+```Go
+db, _ := core.Open(db, connstr)
+```
+
+# SetMapper
+```Go
+db.SetMapper(SameMapper())
+```
+
+## Scan usage
+
+### Scan
+```Go
+rows, _ := db.Query()
+for rows.Next() {
+    rows.Scan()
+}
+```
+
+### ScanMap
+```Go
+rows, _ := db.Query()
+for rows.Next() {
+    rows.ScanMap()
+```
+
+### 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()
+}
+```
+
+## 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)
+
+var user = map[string]interface{}{
+    "name": "lunny",
+}
+rows, err = db.QueryMap("select * from table where name = ?name",
+            &user)
+```
+
+## QueryRow usage
+```Go
+row := db.QueryRow("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",
+}
+row = db.QueryRowMap("select * from table where name = ?name",
+            &user)
+```
+
+## 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)
+```

+ 1 - 0
benchmark.sh

@@ -0,0 +1 @@
+go test -v -bench=. -run=XXX

+ 87 - 0
cache.go

@@ -0,0 +1,87 @@
+package core
+
+import (
+	"errors"
+	"fmt"
+	"time"
+	"bytes"
+	"encoding/gob"
+)
+
+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
+)
+
+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
+	// value is struct's pointer
+	// key format : <tablename>-p-<pk1>-<pk2>...
+	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-<pk1>-<pk2>...
+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) {
+	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)
+
+	dec := gob.NewDecoder(bytes.NewBufferString(s))
+	err := dec.Decode(&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)
+}

+ 172 - 0
column.go

@@ -0,0 +1,172 @@
+package core
+
+import (
+	"errors"
+	"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
+	IsDeleted       bool
+	IsCascade       bool
+	IsVersion       bool
+	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 {
+	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,
+		IsDeleted:       false,
+		IsCascade:       false,
+		IsVersion:       false,
+		fieldPath:       nil,
+		DefaultIsEmpty:  false,
+		EnumOptions:     make(map[string]int),
+	}
+}
+
+// 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 d.ShowCreateNull() {
+		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 d.ShowCreateNull() {
+		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
+	if col.fieldPath == nil {
+		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 {
+		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])
+		}
+	} else {
+		return nil, fmt.Errorf("Unsupported mutliderive %v", col.FieldName)
+	}
+
+	if !fieldValue.IsValid() {
+		return nil, errors.New("no find field matched")
+	}
+
+	return &fieldValue, nil
+}

+ 6 - 0
config

@@ -0,0 +1,6 @@
+[core]
+	bare = true
+	repositoryformatversion = 0
+	filemode = false
+	symlinks = false
+	ignorecase = true

+ 8 - 0
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)
+}

+ 563 - 0
db.go

@@ -0,0 +1,563 @@
+package core
+
+import (
+	"database/sql"
+	"errors"
+	"reflect"
+	"regexp"
+	"sync"
+)
+
+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 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
+}
+
+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) 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 {
+}
+
+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()
+	}
+
+	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...)
+}

+ 659 - 0
db_test.go

@@ -0,0 +1,659 @@
+package core
+
+import (
+	"errors"
+	"fmt"
+	"os"
+	"testing"
+	"time"
+
+	_ "github.com/go-sql-driver/mysql"
+	_ "github.com/mattn/go-sqlite3"
+)
+
+var (
+	//dbtype         string = "sqlite3"
+	dbtype         string = "mysql"
+	createTableSql string
+)
+
+type User struct {
+	Id       int64
+	Name     string
+	Title    string
+	Age      float32
+	Alias    string
+	NickName string
+	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")
+	default:
+		panic("no db type")
+	}
+}
+
+func BenchmarkOriQuery(b *testing.B) {
+	b.StopTimer()
+	db, err := testOpen()
+	if err != nil {
+		b.Error(err)
+	}
+	defer db.Close()
+
+	_, err = db.Exec(createTableSql)
+	if err != nil {
+		b.Error(err)
+	}
+
+	for i := 0; i < 50; i++ {
+		_, err = db.Exec("insert into user (`name`, title, age, alias, nick_name, created) values (?,?,?,?,?, ?)",
+			"xlw", "tester", 1.2, "lunny", "lunny xiao", time.Now())
+		if err != nil {
+			b.Error(err)
+		}
+	}
+
+	b.StartTimer()
+
+	for i := 0; i < b.N; i++ {
+		rows, err := db.Query("select * from user")
+		if err != nil {
+			b.Error(err)
+		}
+
+		for rows.Next() {
+			var Id int64
+			var Name, Title, Alias, NickName string
+			var Age float32
+			var Created 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()
+
+	db, err := testOpen()
+	if err != nil {
+		b.Error(err)
+	}
+	defer db.Close()
+
+	_, err = db.Exec(createTableSql)
+	if err != nil {
+		b.Error(err)
+	}
+
+	for i := 0; i < 50; i++ {
+		_, err = db.Exec("insert into user (`name`, title, age, alias, nick_name, created) values (?,?,?,?,?, ?)",
+			"xlw", "tester", 1.2, "lunny", "lunny xiao", time.Now())
+		if err != nil {
+			b.Error(err)
+		}
+	}
+
+	b.StartTimer()
+
+	for i := 0; i < b.N; i++ {
+		rows, err := db.Query("select * from user")
+		if err != nil {
+			b.Error(err)
+		}
+
+		for rows.Next() {
+			var user User
+			err = rows.ScanStructByIndex(&user)
+			if err != nil {
+				b.Error(err)
+			}
+			if user.Name != "xlw" {
+				fmt.Println(user)
+				b.Error(errors.New("name should be xlw"))
+			}
+		}
+		rows.Close()
+	}
+}
+
+func BenchmarkStruct2Query(b *testing.B) {
+	b.StopTimer()
+
+	db, err := testOpen()
+	if err != nil {
+		b.Error(err)
+	}
+	defer db.Close()
+
+	_, err = db.Exec(createTableSql)
+	if err != nil {
+		b.Error(err)
+	}
+
+	for i := 0; i < 50; i++ {
+		_, err = db.Exec("insert into user (`name`, title, age, alias, nick_name, created) values (?,?,?,?,?,?)",
+			"xlw", "tester", 1.2, "lunny", "lunny xiao", time.Now())
+		if err != nil {
+			b.Error(err)
+		}
+	}
+
+	db.Mapper = 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.ScanStructByName(&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()
+
+	db, err := testOpen()
+	if err != nil {
+		b.Error(err)
+	}
+	defer db.Close()
+
+	_, err = db.Exec(createTableSql)
+	if err != nil {
+		b.Error(err)
+	}
+
+	for i := 0; i < 50; i++ {
+		_, err = db.Exec("insert into user (`name`, title, age, alias, nick_name,created) values (?,?,?,?,?,?)",
+			"xlw", "tester", 1.2, "lunny", "lunny xiao", time.Now())
+		if err != nil {
+			b.Error(err)
+		}
+	}
+
+	b.StartTimer()
+
+	for i := 0; i < b.N; i++ {
+		rows, err := db.Query("select * from user")
+		if err != nil {
+			b.Error(err)
+		}
+
+		cols, err := rows.Columns()
+		if err != nil {
+			b.Error(err)
+		}
+
+		for rows.Next() {
+			slice := make([]interface{}, len(cols))
+			err = rows.ScanSlice(&slice)
+			if err != nil {
+				b.Error(err)
+			}
+			fmt.Println(slice)
+			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(createTableSql)
+	if err != nil {
+		b.Error(err)
+	}
+
+	for i := 0; i < 50; i++ {
+		_, err = db.Exec("insert into user (name, title, age, alias, nick_name,created) values (?,?,?,?,?,?)",
+			"xlw", "tester", 1.2, "lunny", "lunny xiao", time.Now())
+		if err != nil {
+			b.Error(err)
+		}
+	}
+
+	b.StartTimer()
+
+	for i := 0; i < b.N; i++ {
+		rows, err := db.Query("select * from user")
+		if err != nil {
+			b.Error(err)
+		}
+
+		cols, err := rows.Columns()
+		if err != nil {
+			b.Error(err)
+		}
+
+		for rows.Next() {
+			slice := make([][]byte, len(cols))
+			err = rows.ScanSlice(&slice)
+			if err != nil {
+				b.Error(err)
+			}
+			if string(slice[1]) != "xlw" {
+				fmt.Println(slice)
+				b.Error(errors.New("name should be xlw"))
+			}
+		}
+
+		rows.Close()
+	}
+}
+*/
+
+func BenchmarkSliceStringQuery(b *testing.B) {
+	b.StopTimer()
+	db, err := testOpen()
+	if err != nil {
+		b.Error(err)
+	}
+	defer db.Close()
+
+	_, err = db.Exec(createTableSql)
+	if err != nil {
+		b.Error(err)
+	}
+
+	for i := 0; i < 50; i++ {
+		_, err = db.Exec("insert into user (name, title, age, alias, nick_name, created) values (?,?,?,?,?,?)",
+			"xlw", "tester", 1.2, "lunny", "lunny xiao", time.Now())
+		if err != nil {
+			b.Error(err)
+		}
+	}
+
+	b.StartTimer()
+
+	for i := 0; i < b.N; i++ {
+		rows, err := db.Query("select * from user")
+		if err != nil {
+			b.Error(err)
+		}
+
+		cols, err := rows.Columns()
+		if err != nil {
+			b.Error(err)
+		}
+
+		for rows.Next() {
+			slice := make([]*string, len(cols))
+			err = rows.ScanSlice(&slice)
+			if err != nil {
+				b.Error(err)
+			}
+			if (*slice[1]) != "xlw" {
+				fmt.Println(slice)
+				b.Error(errors.New("name should be xlw"))
+			}
+		}
+
+		rows.Close()
+	}
+}
+
+func BenchmarkMapInterfaceQuery(b *testing.B) {
+	b.StopTimer()
+
+	db, err := testOpen()
+	if err != nil {
+		b.Error(err)
+	}
+	defer db.Close()
+
+	_, err = db.Exec(createTableSql)
+	if err != nil {
+		b.Error(err)
+	}
+
+	for i := 0; i < 50; i++ {
+		_, err = db.Exec("insert into user (name, title, age, alias, nick_name,created) values (?,?,?,?,?,?)",
+			"xlw", "tester", 1.2, "lunny", "lunny xiao", time.Now())
+		if err != nil {
+			b.Error(err)
+		}
+	}
+
+	b.StartTimer()
+
+	for i := 0; i < b.N; i++ {
+		rows, err := db.Query("select * from user")
+		if err != nil {
+			b.Error(err)
+		}
+
+		for rows.Next() {
+			m := make(map[string]interface{})
+			err = rows.ScanMap(&m)
+			if err != nil {
+				b.Error(err)
+			}
+			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(createTableSql)
+	if err != nil {
+		b.Error(err)
+	}
+
+	for i := 0; i < 50; i++ {
+		_, err = db.Exec("insert into user (name, title, age, alias, nick_name,created) values (?,?,?,?,?,?)",
+			"xlw", "tester", 1.2, "lunny", "lunny xiao", time.Now())
+		if err != nil {
+			b.Error(err)
+		}
+	}
+
+	b.StartTimer()
+
+	for i := 0; i < b.N; i++ {
+		rows, err := db.Query("select * from user")
+		if err != nil {
+			b.Error(err)
+		}
+
+		for rows.Next() {
+			m := make(map[string][]byte)
+			err = rows.ScanMap(&m)
+			if err != nil {
+				b.Error(err)
+			}
+			if string(m["name"]) != "xlw" {
+				fmt.Println(m)
+				b.Error(errors.New("name should be xlw"))
+			}
+		}
+
+		rows.Close()
+	}
+}
+*/
+/*
+func BenchmarkMapStringQuery(b *testing.B) {
+	b.StopTimer()
+	os.Remove("./test.db")
+	db, err := Open("sqlite3", "./test.db")
+	if err != nil {
+		b.Error(err)
+	}
+	defer db.Close()
+
+	_, err = db.Exec(createTableSql)
+	if err != nil {
+		b.Error(err)
+	}
+
+	for i := 0; i < 50; i++ {
+		_, err = db.Exec("insert into user (name, title, age, alias, nick_name,created) values (?,?,?,?,?,?)",
+			"xlw", "tester", 1.2, "lunny", "lunny xiao", time.Now())
+		if err != nil {
+			b.Error(err)
+		}
+	}
+
+	b.StartTimer()
+
+	for i := 0; i < b.N; i++ {
+		rows, err := db.Query("select * from user")
+		if err != nil {
+			b.Error(err)
+		}
+
+		for rows.Next() {
+			m := make(map[string]string)
+			err = rows.ScanMap(&m)
+			if err != nil {
+				b.Error(err)
+			}
+			if m["name"] != "xlw" {
+				fmt.Println(m)
+				b.Error(errors.New("name should be xlw"))
+			}
+		}
+
+		rows.Close()
+	}
+}*/
+
+func BenchmarkExec(b *testing.B) {
+	b.StopTimer()
+
+	db, err := testOpen()
+	if err != nil {
+		b.Error(err)
+	}
+	defer db.Close()
+
+	_, err = db.Exec(createTableSql)
+	if err != nil {
+		b.Error(err)
+	}
+
+	b.StartTimer()
+
+	for i := 0; i < b.N; i++ {
+		_, err = db.Exec("insert into user (`name`, title, age, alias, nick_name,created) values (?,?,?,?,?,?)",
+			"xlw", "tester", 1.2, "lunny", "lunny xiao", time.Now())
+		if err != nil {
+			b.Error(err)
+		}
+	}
+}
+
+func BenchmarkExecMap(b *testing.B) {
+	b.StopTimer()
+
+	db, err := testOpen()
+	if err != nil {
+		b.Error(err)
+	}
+	defer db.Close()
+
+	_, err = db.Exec(createTableSql)
+	if err != nil {
+		b.Error(err)
+	}
+
+	b.StartTimer()
+
+	mp := map[string]interface{}{
+		"name":      "xlw",
+		"title":     "tester",
+		"age":       1.2,
+		"alias":     "lunny",
+		"nick_name": "lunny xiao",
+		"created":   time.Now(),
+	}
+
+	for i := 0; i < b.N; i++ {
+		_, err = db.ExecMap("insert into user (`name`, title, age, alias, nick_name, created) "+
+			"values (?name,?title,?age,?alias,?nick_name,?created)",
+			&mp)
+		if err != nil {
+			b.Error(err)
+		}
+	}
+}
+
+func TestExecMap(t *testing.T) {
+	db, err := testOpen()
+	if err != nil {
+		t.Error(err)
+	}
+	defer db.Close()
+
+	_, err = db.Exec(createTableSql)
+	if err != nil {
+		t.Error(err)
+	}
+
+	mp := map[string]interface{}{
+		"name":      "xlw",
+		"title":     "tester",
+		"age":       1.2,
+		"alias":     "lunny",
+		"nick_name": "lunny xiao",
+		"created":   time.Now(),
+	}
+
+	_, err = db.ExecMap("insert into user (`name`, title, age, alias, nick_name,created) "+
+		"values (?name,?title,?age,?alias,?nick_name,?created)",
+		&mp)
+	if err != nil {
+		t.Error(err)
+	}
+
+	rows, err := db.Query("select * from user")
+	if err != nil {
+		t.Error(err)
+	}
+
+	for rows.Next() {
+		var user User
+		err = rows.ScanStructByName(&user)
+		if err != nil {
+			t.Error(err)
+		}
+		fmt.Println("--", user)
+	}
+}
+
+func TestExecStruct(t *testing.T) {
+	db, err := testOpen()
+	if err != nil {
+		t.Error(err)
+	}
+	defer db.Close()
+
+	_, err = db.Exec(createTableSql)
+	if err != nil {
+		t.Error(err)
+	}
+
+	user := User{Name: "xlw",
+		Title:    "tester",
+		Age:      1.2,
+		Alias:    "lunny",
+		NickName: "lunny xiao",
+		Created:  time.Now(),
+	}
+
+	_, err = db.ExecStruct("insert into user (`name`, title, age, alias, nick_name,created) "+
+		"values (?Name,?Title,?Age,?Alias,?NickName,?Created)",
+		&user)
+	if err != nil {
+		t.Error(err)
+	}
+
+	rows, err := db.QueryStruct("select * from user where `name` = ?Name", &user)
+	if err != nil {
+		t.Error(err)
+	}
+
+	for rows.Next() {
+		var user User
+		err = rows.ScanStructByName(&user)
+		if err != nil {
+			t.Error(err)
+		}
+		fmt.Println("1--", user)
+	}
+}
+
+func BenchmarkExecStruct(b *testing.B) {
+	b.StopTimer()
+	db, err := testOpen()
+	if err != nil {
+		b.Error(err)
+	}
+	defer db.Close()
+
+	_, err = db.Exec(createTableSql)
+	if err != nil {
+		b.Error(err)
+	}
+
+	b.StartTimer()
+
+	user := User{Name: "xlw",
+		Title:    "tester",
+		Age:      1.2,
+		Alias:    "lunny",
+		NickName: "lunny xiao",
+		Created:  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)
+		}
+	}
+}

+ 1 - 0
description

@@ -0,0 +1 @@
+Unnamed repository; edit this file 'description' to name the repository.

+ 282 - 0
dialect.go

@@ -0,0 +1,282 @@
+package core
+
+import (
+	"fmt"
+	"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 {
+	SetLogger(logger ILogger)
+	Init(*DB, *Uri, string, string) error
+	URI() *Uri
+	DB() *DB
+	DBType() DbType
+	SqlType(*Column) string
+	FormatBytes(b []byte) string
+
+	DriverName() string
+	DataSourceName() string
+
+	QuoteStr() string
+	IsReserved(string) bool
+	Quote(string) string
+	AndStr() string
+	OrStr() string
+	EqStr() string
+	RollBackStr() string
+	AutoIncrStr() string
+
+	SupportInsertMany() bool
+	SupportEngine() bool
+	SupportCharset() bool
+	SupportDropIfExists() bool
+	IndexOnTable() bool
+	ShowCreateNull() bool
+
+	IndexCheckSql(tableName, idxName string) (string, []interface{})
+	TableCheckSql(tableName string) (string, []interface{})
+
+	IsColumnExist(tableName string, colName string) (bool, error)
+
+	CreateTableSql(table *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
+
+	//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)
+
+	Filters() []Filter
+}
+
+func OpenDialect(dialect Dialect) (*DB, error) {
+	return Open(dialect.DriverName(), dialect.DataSourceName())
+}
+
+type Base struct {
+	db             *DB
+	dialect        Dialect
+	driverName     string
+	dataSourceName string
+	Logger         ILogger
+	*Uri
+}
+
+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
+	return nil
+}
+
+func (b *Base) URI() *Uri {
+	return b.Uri
+}
+
+func (b *Base) DBType() DbType {
+	return b.Uri.DbType
+}
+
+func (b *Base) FormatBytes(bs []byte) string {
+	return fmt.Sprintf("0x%x", bs)
+}
+
+func (b *Base) DriverName() string {
+	return b.driverName
+}
+
+func (b *Base) ShowCreateNull() bool {
+	return true
+}
+
+func (b *Base) DataSourceName() string {
+	return b.dataSourceName
+}
+
+func (b *Base) AndStr() string {
+	return "AND"
+}
+
+func (b *Base) OrStr() string {
+	return "OR"
+}
+
+func (b *Base) EqStr() string {
+	return "="
+}
+
+func (db *Base) RollBackStr() string {
+	return "ROLL BACK"
+}
+
+func (db *Base) SupportDropIfExists() bool {
+	return true
+}
+
+func (db *Base) DropTableSql(tableName string) string {
+	return fmt.Sprintf("DROP TABLE IF EXISTS `%s`", tableName)
+}
+
+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
+	}
+	defer rows.Close()
+
+	if rows.Next() {
+		return true, nil
+	}
+	return false, nil
+}
+
+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, colName)
+}
+
+/*
+func (db *Base) CreateTableIfNotExists(table *Table, tableName, storeEngine, charset string) error {
+	sql, args := db.dialect.TableCheckSql(tableName)
+	rows, err := db.DB().Query(sql, args...)
+	if db.Logger != nil {
+		db.Logger.Info("[sql]", sql, args)
+	}
+	if err != nil {
+		return err
+	}
+	defer rows.Close()
+
+	if rows.Next() {
+		return nil
+	}
+
+	sql = db.dialect.CreateTableSql(table, tableName, storeEngine, charset)
+	_, err = db.DB().Exec(sql)
+	if db.Logger != nil {
+		db.Logger.Info("[sql]", sql)
+	}
+	return err
+}*/
+
+func (db *Base) CreateIndexSql(tableName string, index *Index) string {
+	quote := db.dialect.Quote
+	var unique string
+	var idxName string
+	if index.Type == UniqueType {
+		unique = " UNIQUE"
+	}
+	idxName = index.XName(tableName)
+	return fmt.Sprintf("CREATE%s INDEX %v ON %v (%v)", unique,
+		quote(idxName), quote(tableName),
+		quote(strings.Join(index.Cols, quote(","))))
+}
+
+func (db *Base) DropIndexSql(tableName string, index *Index) string {
+	quote := db.dialect.Quote
+	var name string
+	if index.IsRegular {
+		name = index.XName(tableName)
+	} else {
+		name = index.Name
+	}
+	return fmt.Sprintf("DROP INDEX %v ON %s", quote(name), quote(tableName))
+}
+
+func (db *Base) ModifyColumnSql(tableName string, col *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 "
+	if tableName == "" {
+		tableName = table.Name
+	}
+
+	sql += b.dialect.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.dialect.Quote(strings.Join(pkList, b.dialect.Quote(",")))
+		sql += " ), "
+	}
+
+	sql = sql[:len(sql)-2] + ")"
+	if b.dialect.SupportEngine() && storeEngine != "" {
+		sql += " ENGINE=" + storeEngine
+	}
+	if b.dialect.SupportCharset() {
+		if len(charset) == 0 {
+			charset = b.dialect.URI().Charset
+		}
+		if len(charset) > 0 {
+			sql += " DEFAULT CHARSET " + charset
+		}
+	}
+	sql += ";"
+	return sql
+}
+
+var (
+	dialects = map[DbType]Dialect{}
+)
+
+func RegisterDialect(dbName DbType, dialect Dialect) {
+	if dialect == nil {
+		panic("core: Register dialect is nil")
+	}
+	dialects[dbName] = dialect // !nashtsai! allow override dialect
+}
+
+func QueryDialect(dbName DbType) Dialect {
+	return dialects[dbName]
+}

+ 27 - 0
driver.go

@@ -0,0 +1,27 @@
+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]
+}
+
+func RegisteredDriverSize() int {
+	return len(drivers)
+}

+ 10 - 0
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")
+)

+ 64 - 0
filter.go

@@ -0,0 +1,64 @@
+package core
+
+import (
+	"fmt"
+	"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
+}
+
+// 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
+}

+ 28 - 0
ilogger.go

@@ -0,0 +1,28 @@
+package core
+
+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(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)
+}

+ 61 - 0
index.go

@@ -0,0 +1,61 @@
+package core
+
+import (
+	"fmt"
+	"sort"
+	"strings"
+)
+
+const (
+	IndexType = iota + 1
+	UniqueType
+)
+
+// database index
+type Index struct {
+	IsRegular bool
+	Name      string
+	Type      int
+	Cols      []string
+}
+
+func (index *Index) XName(tableName string) string {
+	if !strings.HasPrefix(index.Name, "UQE_") &&
+		!strings.HasPrefix(index.Name, "IDX_") {
+		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
+func (index *Index) AddColumn(cols ...string) {
+	for _, col := range cols {
+		index.Cols = append(index.Cols, col)
+	}
+}
+
+func (index *Index) Equal(dst *Index) bool {
+	if index.Type != dst.Type {
+		return false
+	}
+	if len(index.Cols) != len(dst.Cols) {
+		return false
+	}
+	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{true, name, indexType, make([]string, 0)}
+}

+ 254 - 0
mapper.go

@@ -0,0 +1,254 @@
+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 (mapper SnakeMapper) Obj2Table(name string) string {
+	return snakeCasedName(name)
+}
+
+func titleCasedName(name string) string {
+	newstr := make([]rune, 0)
+	upNextChar := true
+
+	name = strings.ToLower(name)
+
+	for _, chr := range name {
+		switch {
+		case upNextChar:
+			upNextChar = false
+			if 'a' <= chr && chr <= 'z' {
+				chr -= ('a' - 'A')
+			}
+		case chr == '_':
+			upNextChar = true
+			continue
+		}
+
+		newstr = append(newstr, chr)
+	}
+
+	return string(newstr)
+}
+
+func (mapper SnakeMapper) Table2Obj(name string) string {
+	return titleCasedName(name)
+}
+
+// GonicMapper implements IMapper. It will consider initialisms when mapping names.
+// E.g. id -> ID, user -> User and to table names: UserID -> user_id, MyUID -> my_uid
+type GonicMapper map[string]bool
+
+func isASCIIUpper(r rune) bool {
+	return 'A' <= r && r <= 'Z'
+}
+
+func toASCIIUpper(r rune) rune {
+	if 'a' <= r && r <= 'z' {
+		r -= ('a' - 'A')
+	}
+	return r
+}
+
+func gonicCasedName(name string) string {
+	newstr := make([]rune, 0, len(name)+3)
+	for idx, chr := range name {
+		if isASCIIUpper(chr) && idx > 0 {
+			if !isASCIIUpper(newstr[len(newstr)-1]) {
+				newstr = append(newstr, '_')
+			}
+		}
+
+		if !isASCIIUpper(chr) && idx > 1 {
+			l := len(newstr)
+			if isASCIIUpper(newstr[l-1]) && isASCIIUpper(newstr[l-2]) {
+				newstr = append(newstr, newstr[l-1])
+				newstr[l-1] = '_'
+			}
+		}
+
+		newstr = append(newstr, chr)
+	}
+	return strings.ToLower(string(newstr))
+}
+
+func (mapper GonicMapper) Obj2Table(name string) string {
+	return gonicCasedName(name)
+}
+
+func (mapper GonicMapper) Table2Obj(name string) string {
+	newstr := make([]rune, 0)
+
+	name = strings.ToLower(name)
+	parts := strings.Split(name, "_")
+
+	for _, p := range parts {
+		_, isInitialism := mapper[strings.ToUpper(p)]
+		for i, r := range p {
+			if i == 0 || isInitialism {
+				r = toASCIIUpper(r)
+			}
+			newstr = append(newstr, r)
+		}
+	}
+
+	return string(newstr)
+}
+
+// A GonicMapper that contains a list of common initialisms taken from golang/lint
+var LintGonicMapper = GonicMapper{
+	"API":   true,
+	"ASCII": true,
+	"CPU":   true,
+	"CSS":   true,
+	"DNS":   true,
+	"EOF":   true,
+	"GUID":  true,
+	"HTML":  true,
+	"HTTP":  true,
+	"HTTPS": true,
+	"ID":    true,
+	"IP":    true,
+	"JSON":  true,
+	"LHS":   true,
+	"QPS":   true,
+	"RAM":   true,
+	"RHS":   true,
+	"RPC":   true,
+	"SLA":   true,
+	"SMTP":  true,
+	"SSH":   true,
+	"TLS":   true,
+	"TTL":   true,
+	"UI":    true,
+	"UID":   true,
+	"UUID":  true,
+	"URI":   true,
+	"URL":   true,
+	"UTF8":  true,
+	"VM":    true,
+	"XML":   true,
+	"XSRF":  true,
+	"XSS":   true,
+}
+
+// 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.Mapper.Obj2Table(name) + mapper.Suffix
+}
+
+func (mapper SuffixMapper) Table2Obj(name string) string {
+	return mapper.Mapper.Table2Obj(name[:len(name)-len(mapper.Suffix)])
+}
+
+func NewSuffixMapper(mapper IMapper, suffix string) SuffixMapper {
+	return SuffixMapper{mapper, suffix}
+}

+ 45 - 0
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 := LintGonicMapper.Table2Obj(in)
+		if out != expected {
+			t.Errorf("Given %s, expected %s but got %s", in, expected, out)
+		}
+	}
+}

+ 26 - 0
pk.go

@@ -0,0 +1,26 @@
+package core
+
+import (
+	"bytes"
+	"encoding/gob"
+)
+
+type PK []interface{}
+
+func NewPK(pks ...interface{}) *PK {
+	p := PK(pks)
+	return &p
+}
+
+func (p *PK) ToString() (string, error) {
+	buf := new(bytes.Buffer)
+	enc := gob.NewEncoder(buf)
+	err := enc.Encode(*p)
+	return buf.String(), err
+}
+
+func (p *PK) FromString(content string) error {
+	dec := gob.NewDecoder(bytes.NewBufferString(content))
+	err := dec.Decode(p)
+	return err
+}

+ 33 - 0
pk_test.go

@@ -0,0 +1,33 @@
+package core
+
+import (
+	"fmt"
+	"reflect"
+	"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)
+
+	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]))
+		}
+	}
+}

+ 130 - 0
table.go

@@ -0,0 +1,130 @@
+package core
+
+import (
+	"reflect"
+	"strings"
+)
+
+// database table
+type Table struct {
+	Name          string
+	Type          reflect.Type
+	columnsSeq    []string
+	columnsMap    map[string][]*Column
+	columns       []*Column
+	Indexes       map[string]*Index
+	PrimaryKeys   []string
+	AutoIncrement string
+	Created       map[string]bool
+	Updated       string
+	Deleted       string
+	Version       string
+	Cacher        Cacher
+	StoreEngine   string
+	Charset       string
+}
+
+func (table *Table) Columns() []*Column {
+	return table.columns
+}
+
+func (table *Table) ColumnsSeq() []string {
+	return table.columnsSeq
+}
+
+func NewEmptyTable() *Table {
+	return NewTable("", nil)
+}
+
+func NewTable(name string, t reflect.Type) *Table {
+	return &Table{Name: name, Type: t,
+		columnsSeq:  make([]string, 0),
+		columns:     make([]*Column, 0),
+		columnsMap:  make(map[string][]*Column),
+		Indexes:     make(map[string]*Index),
+		Created:     make(map[string]bool),
+		PrimaryKeys: make([]string, 0),
+	}
+}
+
+func (table *Table) GetColumn(name string) *Column {
+	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
+func (table *Table) PKColumns() []*Column {
+	columns := make([]*Column, len(table.PrimaryKeys))
+	for i, name := range table.PrimaryKeys {
+		columns[i] = table.GetColumn(name)
+	}
+	return columns
+}
+
+func (table *Table) ColumnType(name string) reflect.Type {
+	t, _ := table.Type.FieldByName(name)
+	return t.Type
+}
+
+func (table *Table) AutoIncrColumn() *Column {
+	return table.GetColumn(table.AutoIncrement)
+}
+
+func (table *Table) VersionColumn() *Column {
+	return table.GetColumn(table.Version)
+}
+
+func (table *Table) UpdatedColumn() *Column {
+	return table.GetColumn(table.Updated)
+}
+
+func (table *Table) DeletedColumn() *Column {
+	return table.GetColumn(table.Deleted)
+}
+
+// add a column to table
+func (table *Table) AddColumn(col *Column) {
+	table.columnsSeq = append(table.columnsSeq, col.Name)
+	table.columns = append(table.columns, col)
+	colName := strings.ToLower(col.Name)
+	if c, ok := table.columnsMap[colName]; ok {
+		table.columnsMap[colName] = append(c, col)
+	} else {
+		table.columnsMap[colName] = []*Column{col}
+	}
+
+	if col.IsPrimaryKey {
+		table.PrimaryKeys = append(table.PrimaryKeys, col.Name)
+	}
+	if col.IsAutoIncrement {
+		table.AutoIncrement = col.Name
+	}
+	if col.IsCreated {
+		table.Created[col.Name] = true
+	}
+	if col.IsUpdated {
+		table.Updated = col.Name
+	}
+	if col.IsDeleted {
+		table.Deleted = col.Name
+	}
+	if col.IsVersion {
+		table.Version = col.Name
+	}
+}
+
+// add an index or an unique to table
+func (table *Table) AddIndex(index *Index) {
+	table.Indexes[index.Name] = index
+}

+ 323 - 0
type.go

@@ -0,0 +1,323 @@
+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
+}
+
+const (
+	UNKNOW_TYPE = iota
+	TEXT_TYPE
+	BLOB_TYPE
+	TIME_TYPE
+	NUMERIC_TYPE
+)
+
+func (s *SQLType) IsType(st int) bool {
+	if t, ok := SqlTypes[s.Name]; ok && t == st {
+		return true
+	}
+	return false
+}
+
+func (s *SQLType) IsText() bool {
+	return s.IsType(TEXT_TYPE)
+}
+
+func (s *SQLType) IsBlob() bool {
+	return s.IsType(BLOB_TYPE)
+}
+
+func (s *SQLType) IsTime() bool {
+	return s.IsType(TIME_TYPE)
+}
+
+func (s *SQLType) IsNumeric() bool {
+	return s.IsType(NUMERIC_TYPE)
+}
+
+var (
+	Bit       = "BIT"
+	TinyInt   = "TINYINT"
+	SmallInt  = "SMALLINT"
+	MediumInt = "MEDIUMINT"
+	Int       = "INT"
+	Integer   = "INTEGER"
+	BigInt    = "BIGINT"
+
+	Enum = "ENUM"
+	Set  = "SET"
+
+	Char       = "CHAR"
+	Varchar    = "VARCHAR"
+	NVarchar   = "NVARCHAR"
+	TinyText   = "TINYTEXT"
+	Text       = "TEXT"
+	Clob       = "CLOB"
+	MediumText = "MEDIUMTEXT"
+	LongText   = "LONGTEXT"
+	Uuid       = "UUID"
+
+	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"
+
+	Json = "JSON"
+
+	SqlTypes = map[string]int{
+		Bit:       NUMERIC_TYPE,
+		TinyInt:   NUMERIC_TYPE,
+		SmallInt:  NUMERIC_TYPE,
+		MediumInt: NUMERIC_TYPE,
+		Int:       NUMERIC_TYPE,
+		Integer:   NUMERIC_TYPE,
+		BigInt:    NUMERIC_TYPE,
+
+		Enum: TEXT_TYPE,
+		Set:  TEXT_TYPE,
+		Json: TEXT_TYPE,
+
+		Char:       TEXT_TYPE,
+		Varchar:    TEXT_TYPE,
+		NVarchar:   TEXT_TYPE,
+		TinyText:   TEXT_TYPE,
+		Text:       TEXT_TYPE,
+		MediumText: TEXT_TYPE,
+		LongText:   TEXT_TYPE,
+		Uuid:       TEXT_TYPE,
+		Clob:       TEXT_TYPE,
+
+		Date:       TIME_TYPE,
+		DateTime:   TIME_TYPE,
+		Time:       TIME_TYPE,
+		TimeStamp:  TIME_TYPE,
+		TimeStampz: TIME_TYPE,
+
+		Decimal: NUMERIC_TYPE,
+		Numeric: NUMERIC_TYPE,
+		Real:    NUMERIC_TYPE,
+		Float:   NUMERIC_TYPE,
+		Double:  NUMERIC_TYPE,
+
+		Binary:    BLOB_TYPE,
+		VarBinary: BLOB_TYPE,
+
+		TinyBlob:   BLOB_TYPE,
+		Blob:       BLOB_TYPE,
+		MediumBlob: BLOB_TYPE,
+		LongBlob:   BLOB_TYPE,
+		Bytea:      BLOB_TYPE,
+
+		Bool: NUMERIC_TYPE,
+
+		Serial:    NUMERIC_TYPE,
+		BigSerial: NUMERIC_TYPE,
+	}
+
+	intTypes  = sort.StringSlice{"*int", "*int16", "*int32", "*int8"}
+	uintTypes = sort.StringSlice{"*uint", "*uint16", "*uint32", "*uint8"}
+)
+
+// !nashtsai! treat following var as interal const values, these are used for reflect.TypeOf 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.ConvertibleTo(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, NVarchar, TinyText, Text, MediumText, LongText, Enum, Set, Uuid, Clob:
+		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("")
+	}
+}