From f4f07ec079a7151eff4930d05320da95acb1b376 Mon Sep 17 00:00:00 2001 From: Nash Tsai Date: Sat, 17 May 2014 00:39:30 +0800 Subject: [PATCH 01/15] #66 partial implement xorm_cache tag add Engine.disableGlobalCache and explicit using cache via xorm_cache tag --- engine.go | 49 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/engine.go b/engine.go index 107a9d69..66606b76 100644 --- a/engine.go +++ b/engine.go @@ -38,6 +38,14 @@ type Engine struct { Logger ILogger // io.Writer TZLocation *time.Location + + disableGlobalCache bool +} + +func (engine *Engine) SetDisableGlobalCache(disable bool) { + if engine.disableGlobalCache != disable { + engine.disableGlobalCache = disable + } } func (engine *Engine) DriverName() string { @@ -544,10 +552,39 @@ func addIndex(indexName string, table *core.Table, col *core.Column, indexType i func (engine *Engine) newTable() *core.Table { table := core.NewEmptyTable() - table.Cacher = engine.Cacher + + if !engine.disableGlobalCache { + table.Cacher = engine.Cacher + } return table } +func (engine *Engine) processCacherTag(table *core.Table, v reflect.Value, cacherTagStr string) { + + for _, part := range strings.Split(cacherTagStr, ",") { + switch { + case part == "false": // even if engine has assigned cacher, this table will not have cache support + table.Cacher = nil + return + + case part == "true": // use default 'read-write' cache + if engine.Cacher != nil { // !nash! use engine's cacher if provided + table.Cacher = engine.Cacher + } else { + table.Cacher = NewLRUCacher2(NewMemoryStore(), time.Hour, 10000) // !nashtsai! HACK use LRU cacher for now + } + return + // TODO + // case strings.HasPrefix(part, "usage:"): + // usageStr := part[len("usage:"):] + + // case strings.HasPrefix(part, "include:"): + // includeStr := part[len("include:"):] + } + + } +} + func (engine *Engine) mapType(v reflect.Value) *core.Table { t := v.Type() table := engine.newTable() @@ -573,9 +610,19 @@ func (engine *Engine) mapType(v reflect.Value) *core.Table { var idFieldColName string var err error + hasProcessedCacheTag := false + for i := 0; i < t.NumField(); i++ { tag := t.Field(i).Tag ormTagStr := tag.Get(engine.TagIdentifier) + if !hasProcessedCacheTag { + cacheTagStr := tag.Get("xorm_cache") + if cacheTagStr != "" { + hasProcessedCacheTag = true + engine.processCacherTag(table, v, cacheTagStr) + } + } + var col *core.Column fieldValue := v.Field(i) fieldType := fieldValue.Type() From 40d500a8b9df0d055b5f23ac49cfabe171fdb6c4 Mon Sep 17 00:00:00 2001 From: Nash Tsai Date: Mon, 21 Jul 2014 14:56:26 +0800 Subject: [PATCH 02/15] add Engine.LogSQLQueryTime and Engine.LogSQLExecutionTime for SQL execution time logging --- engine.go | 28 ++++++++++++++++++++++++++-- session.go | 43 ++++++++++++++++++++++++++++--------------- 2 files changed, 54 insertions(+), 17 deletions(-) diff --git a/engine.go b/engine.go index 9d52a9b4..b234a80c 100644 --- a/engine.go +++ b/engine.go @@ -183,13 +183,37 @@ func (engine *Engine) Ping() error { func (engine *Engine) logSQL(sqlStr string, sqlArgs ...interface{}) { if engine.ShowSQL { if len(sqlArgs) > 0 { - engine.Logger.Info(fmt.Sprintln("[sql]", sqlStr, "[args]", sqlArgs)) + engine.Logger.Info(fmt.Sprintf("[sql]", sqlStr, "[args]", sqlArgs)) } else { - engine.Logger.Info(fmt.Sprintln("[sql]", sqlStr)) + engine.Logger.Info(fmt.Sprintf("[sql]", sqlStr)) } } } +func (engine *Engine) LogSQLQueryTime(sqlStr string, args interface{}, executionBlock func() (*core.Stmt, *core.Rows, error)) (*core.Stmt, *core.Rows, error) { + if engine.ShowDebug { + b4ExecTime := time.Now() + stmt, res, err := executionBlock() + execDuration := time.Since(b4ExecTime) + engine.LogDebugf("sql [%s] - args [%v] - query took: %vns", sqlStr, args, execDuration.Nanoseconds()) + return stmt, res, err + } else { + return executionBlock() + } +} + +func (engine *Engine) LogSQLExecutionTime(sqlStr string, args interface{}, executionBlock func() (sql.Result, error)) (sql.Result, error) { + if engine.ShowDebug { + b4ExecTime := time.Now() + res, err := executionBlock() + execDuration := time.Since(b4ExecTime) + engine.LogDebugf("sql [%s] - args [%v] - execution took: %vns", sqlStr, args, execDuration.Nanoseconds()) + return res, err + } else { + return executionBlock() + } +} + // logging error func (engine *Engine) LogError(contents ...interface{}) { if engine.ShowErr { diff --git a/session.go b/session.go index f0a345b2..da825161 100644 --- a/session.go +++ b/session.go @@ -452,10 +452,12 @@ func (session *Session) exec(sqlStr string, args ...interface{}) (sql.Result, er session.Engine.logSQL(sqlStr, args...) - if session.IsAutoCommit { - return session.innerExec(sqlStr, args...) - } - return session.Tx.Exec(sqlStr, args...) + return session.Engine.LogSQLExecutionTime(sqlStr, args, func() (sql.Result, error) { + if session.IsAutoCommit { + return session.innerExec(sqlStr, args...) + } + return session.Tx.Exec(sqlStr, args...) + }) } // Exec raw sql @@ -1761,15 +1763,16 @@ func (session *Session) queryPreprocess(sqlStr *string, paramStr ...interface{}) } func (session *Session) query(sqlStr string, paramStr ...interface{}) (resultsSlice []map[string][]byte, err error) { + session.queryPreprocess(&sqlStr, paramStr...) if session.IsAutoCommit { - return query(session.Db, sqlStr, paramStr...) + return session.innerQuery(session.Db, sqlStr, paramStr...) } - return txQuery(session.Tx, sqlStr, paramStr...) + return session.txQuery(session.Tx, sqlStr, paramStr...) } -func txQuery(tx *core.Tx, sqlStr string, params ...interface{}) (resultsSlice []map[string][]byte, err error) { +func (session *Session) txQuery(tx *core.Tx, sqlStr string, params ...interface{}) (resultsSlice []map[string][]byte, err error) { rows, err := tx.Query(sqlStr, params...) if err != nil { return nil, err @@ -1779,17 +1782,26 @@ func txQuery(tx *core.Tx, sqlStr string, params ...interface{}) (resultsSlice [] return rows2maps(rows) } -func query(db *core.DB, sqlStr string, params ...interface{}) (resultsSlice []map[string][]byte, err error) { - s, err := db.Prepare(sqlStr) +func (session *Session) innerQuery(db *core.DB, sqlStr string, params ...interface{}) (resultsSlice []map[string][]byte, err error) { + + stmt, rows, err := session.Engine.LogSQLQueryTime(sqlStr, params, func() (*core.Stmt, *core.Rows, error) { + stmt, err := db.Prepare(sqlStr) + if err != nil { + return stmt, nil, err + } + rows, err := stmt.Query(params...) + + return stmt, rows, err + }) + if rows != nil { + defer rows.Close() + } + if stmt != nil { + defer stmt.Close() + } if err != nil { return nil, err } - defer s.Close() - rows, err := s.Query(params...) - if err != nil { - return nil, err - } - defer rows.Close() return rows2maps(rows) } @@ -1955,6 +1967,7 @@ func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error strings.Join(colMultiPlaces, "),(")) res, err := session.exec(statement, args...) + if err != nil { return 0, err } From 2420e531935aad663de8fe8294cfd8c6f2cd07ea Mon Sep 17 00:00:00 2001 From: Nash Tsai Date: Tue, 29 Jul 2014 17:18:13 +0800 Subject: [PATCH 03/15] redis cacher implementation, incomplete --- redis_cacher.go | 248 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 248 insertions(+) create mode 100644 redis_cacher.go diff --git a/redis_cacher.go b/redis_cacher.go new file mode 100644 index 00000000..b67035f2 --- /dev/null +++ b/redis_cacher.go @@ -0,0 +1,248 @@ +package xorm + +import ( + "bytes" + "encoding/gob" + "fmt" + "github.com/garyburd/redigo/redis" + //"github.com/go-xorm/core" + "reflect" + "strconv" + "time" +) + +const ( + DEFAULT = time.Duration(0) + FOREVER = time.Duration(-1) +) + +// Wraps the Redis client to meet the Cache interface. +type RedisCacher struct { + pool *redis.Pool + defaultExpiration time.Duration +} + +// until redigo supports sharding/clustering, only one host will be in hostList +func NewRedisCacher(host string, password string, defaultExpiration time.Duration) *RedisCacher { + var pool = &redis.Pool{ + MaxIdle: 5, + IdleTimeout: 240 * time.Second, + Dial: func() (redis.Conn, error) { + // the redis protocol should probably be made sett-able + c, err := redis.Dial("tcp", host) + if err != nil { + return nil, err + } + if len(password) > 0 { + if _, err := c.Do("AUTH", password); err != nil { + c.Close() + return nil, err + } + } else { + // check with PING + if _, err := c.Do("PING"); err != nil { + c.Close() + return nil, err + } + } + return c, err + }, + // custom connection test method + TestOnBorrow: func(c redis.Conn, t time.Time) error { + if _, err := c.Do("PING"); err != nil { + return err + } + return nil + }, + } + return &RedisCacher{pool, defaultExpiration} +} + +func exists(conn redis.Conn, key string) bool { + existed, _ := redis.Bool(conn.Do("EXISTS", key)) + return existed +} + +func (c *RedisCacher) getBeanKey(tableName string, id string) string { + return fmt.Sprintf("bean:%s:%s", tableName, id) +} + +func (c *RedisCacher) getSqlKey(tableName string, sql string) string { + return fmt.Sprintf("sql:%s:%s", tableName, sql) +} + +func (c *RedisCacher) Flush() error { + conn := c.pool.Get() + defer conn.Close() + _, err := conn.Do("FLUSHALL") + return err +} + +func (c *RedisCacher) getObject(key string) interface{} { + + conn := c.pool.Get() + defer conn.Close() + raw, err := conn.Do("GET", key) + if raw == nil { + return nil + } + _, err = redis.Bytes(raw, err) // TODO: item, err := redis.Bytes(raw, err) + if err != nil { + return err + } + return nil //Deserialize(item, ptrValue) // TODO +} + +func (c *RedisCacher) GetIds(tableName, sql string) interface{} { + + return c.getObject(c.getSqlKey(tableName, sql)) +} + +func (c *RedisCacher) GetBean(tableName string, id string) interface{} { + return c.getObject(c.getBeanKey(tableName, id)) +} + +func (c *RedisCacher) putObject(key string, value interface{}) { + c.invoke(c.pool.Get().Do, key, value, c.defaultExpiration) +} + +func (c *RedisCacher) PutIds(tableName, sql string, ids interface{}) { + c.putObject(c.getBeanKey(tableName, sql), ids) +} + +func (c *RedisCacher) PutBean(tableName string, id string, obj interface{}) { + c.putObject(c.getBeanKey(tableName, id), obj) +} + +func (c *RedisCacher) delObject(key string) { + conn := c.pool.Get() + defer conn.Close() + if !exists(conn, key) { + return // core.ErrCacheMiss + } + conn.Do("DEL", key) + + // _, err := conn.Do("DEL", key) + // return err +} + +func (c *RedisCacher) DelIds(tableName, sql string) { + c.delObject(c.getSqlKey(tableName, sql)) + // TODO +} + +func (c *RedisCacher) DelBean(tableName string, id string) { + c.delObject(c.getBeanKey(tableName, id)) +} + +func (c *RedisCacher) clearObjects(key string) { + conn := c.pool.Get() + defer conn.Close() + if exists(conn, key) { + // _, err := conn.Do("DEL", key) + // return err + conn.Do("DEL", key) + } else { + // return ErrCacheMiss + } +} + +func (c *RedisCacher) ClearIds(tableName string) { + // TODO + c.clearObjects(c.getSqlKey(tableName, "*")) +} + +func (c *RedisCacher) ClearBeans(tableName string) { + c.clearObjects(c.getBeanKey(tableName, "*")) +} + +func (c *RedisCacher) invoke(f func(string, ...interface{}) (interface{}, error), + key string, value interface{}, expires time.Duration) error { + + switch expires { + case DEFAULT: + expires = c.defaultExpiration + case FOREVER: + expires = time.Duration(0) + } + + b, err := Serialize(value) + if err != nil { + return err + } + conn := c.pool.Get() + defer conn.Close() + if expires > 0 { + _, err := f("SETEX", key, int32(expires/time.Second), b) + return err + } else { + _, err := f("SET", key, b) + return err + } +} + +// Serialize transforms the given value into bytes following these rules: +// - If value is a byte array, it is returned as-is. +// - If value is an int or uint type, it is returned as the ASCII representation +// - Else, encoding/gob is used to serialize +func Serialize(value interface{}) ([]byte, error) { + if bytes, ok := value.([]byte); ok { + return bytes, nil + } + + switch v := reflect.ValueOf(value); v.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return []byte(strconv.FormatInt(v.Int(), 10)), nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return []byte(strconv.FormatUint(v.Uint(), 10)), nil + } + + var b bytes.Buffer + encoder := gob.NewEncoder(&b) + if err := encoder.Encode(value); err != nil { + // revel.ERROR.Printf("revel/cache: gob encoding '%s' failed: %s", value, err) + return nil, err + } + return b.Bytes(), nil +} + +// Deserialize transforms bytes produced by Serialize back into a Go object, +// storing it into "ptr", which must be a pointer to the value type. +func Deserialize(byt []byte, ptr interface{}) (err error) { + if bytes, ok := ptr.(*[]byte); ok { + *bytes = byt + return + } + + if v := reflect.ValueOf(ptr); v.Kind() == reflect.Ptr { + switch p := v.Elem(); p.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + var i int64 + i, err = strconv.ParseInt(string(byt), 10, 64) + if err != nil { + // revel.ERROR.Printf("revel/cache: failed to parse int '%s': %s", string(byt), err) + } else { + p.SetInt(i) + } + return + + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + var i uint64 + i, err = strconv.ParseUint(string(byt), 10, 64) + if err != nil { + // revel.ERROR.Printf("revel/cache: failed to parse uint '%s': %s", string(byt), err) + } else { + p.SetUint(i) + } + return + } + } + + b := bytes.NewBuffer(byt) + decoder := gob.NewDecoder(b) + if err = decoder.Decode(ptr); err != nil { + // revel.ERROR.Printf("revel/cache: gob decoding failed: %s", err) + return + } + return +} From 4c93eaf1f4bac4183a42d46b5797e81231b41c46 Mon Sep 17 00:00:00 2001 From: Nash Tsai Date: Fri, 8 Aug 2014 04:10:35 +0800 Subject: [PATCH 04/15] implemented interface gob encode and decode for redit cacher --- redis_cacher.go | 75 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 65 insertions(+), 10 deletions(-) diff --git a/redis_cacher.go b/redis_cacher.go index b67035f2..7da7a7f5 100644 --- a/redis_cacher.go +++ b/redis_cacher.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/garyburd/redigo/redis" //"github.com/go-xorm/core" + "log" "reflect" "strconv" "time" @@ -86,11 +87,15 @@ func (c *RedisCacher) getObject(key string) interface{} { if raw == nil { return nil } - _, err = redis.Bytes(raw, err) // TODO: item, err := redis.Bytes(raw, err) + item, err := redis.Bytes(raw, err) if err != nil { - return err + log.Fatalf("xorm/cache: redis.Bytes failed: %s", err) + return nil } - return nil //Deserialize(item, ptrValue) // TODO + + value, err := Deserialize(item) + + return value } func (c *RedisCacher) GetIds(tableName, sql string) interface{} { @@ -197,10 +202,12 @@ func Serialize(value interface{}) ([]byte, error) { return []byte(strconv.FormatUint(v.Uint(), 10)), nil } + RegisterGobConcreteType(value) + var b bytes.Buffer encoder := gob.NewEncoder(&b) - if err := encoder.Encode(value); err != nil { - // revel.ERROR.Printf("revel/cache: gob encoding '%s' failed: %s", value, err) + if err := interfaceEncode(encoder, value); err != nil { + log.Fatalf("xorm/cache: gob encoding '%s' failed: %s", value, err) return nil, err } return b.Bytes(), nil @@ -208,7 +215,7 @@ func Serialize(value interface{}) ([]byte, error) { // Deserialize transforms bytes produced by Serialize back into a Go object, // storing it into "ptr", which must be a pointer to the value type. -func Deserialize(byt []byte, ptr interface{}) (err error) { +func Deserialize(byt []byte) (ptr interface{}, err error) { if bytes, ok := ptr.(*[]byte); ok { *bytes = byt return @@ -220,7 +227,7 @@ func Deserialize(byt []byte, ptr interface{}) (err error) { var i int64 i, err = strconv.ParseInt(string(byt), 10, 64) if err != nil { - // revel.ERROR.Printf("revel/cache: failed to parse int '%s': %s", string(byt), err) + log.Fatalf("xorm/cache: failed to parse int '%s': %s", string(byt), err) } else { p.SetInt(i) } @@ -230,7 +237,7 @@ func Deserialize(byt []byte, ptr interface{}) (err error) { var i uint64 i, err = strconv.ParseUint(string(byt), 10, 64) if err != nil { - // revel.ERROR.Printf("revel/cache: failed to parse uint '%s': %s", string(byt), err) + log.Fatalf("xorm/cache: failed to parse uint '%s': %s", string(byt), err) } else { p.SetUint(i) } @@ -240,9 +247,57 @@ func Deserialize(byt []byte, ptr interface{}) (err error) { b := bytes.NewBuffer(byt) decoder := gob.NewDecoder(b) - if err = decoder.Decode(ptr); err != nil { - // revel.ERROR.Printf("revel/cache: gob decoding failed: %s", err) + + if ptr, err = interfaceDecode(decoder); err != nil { + log.Fatalf("xorm/cache: gob decoding failed: %s", err) return } return } + +func RegisterGobConcreteType(value interface{}) { + + t := reflect.TypeOf(value) + + switch t.Kind() { + case reflect.Ptr: + v := reflect.ValueOf(value) + i := v.Elem().Interface() + gob.Register(i) + case reflect.Struct: + gob.Register(value) + case reflect.Slice: + fallthrough + case reflect.Map: + fallthrough + default: + panic(fmt.Errorf("unhandled type: %v", t)) + } +} + +// interfaceEncode encodes the interface value into the encoder. +func interfaceEncode(enc *gob.Encoder, p interface{}) error { + // The encode will fail unless the concrete type has been + // registered. We registered it in the calling function. + + // Pass pointer to interface so Encode sees (and hence sends) a value of + // interface type. If we passed p directly it would see the concrete type instead. + // See the blog post, "The Laws of Reflection" for background. + err := enc.Encode(&p) + if err != nil { + log.Fatal("encode:", err) + } + return err +} + +// interfaceDecode decodes the next interface value from the stream and returns it. +func interfaceDecode(dec *gob.Decoder) (interface{}, error) { + // The decode will fail unless the concrete type on the wire has been + // registered. We registered it in the calling function. + var p interface{} + err := dec.Decode(&p) + if err != nil { + log.Fatal("decode:", err) + } + return p, err +} From a562f9b6aa1a4d20e0a3539cffda60a7799a1fbe Mon Sep 17 00:00:00 2001 From: Nash Tsai Date: Tue, 12 Aug 2014 03:20:27 +0800 Subject: [PATCH 05/15] code tidy up logger tidy up private serialize and deserialize func --- redis_cacher.go | 106 +++++++++++++++++------------------------------- 1 file changed, 38 insertions(+), 68 deletions(-) diff --git a/redis_cacher.go b/redis_cacher.go index 7da7a7f5..87f321bc 100644 --- a/redis_cacher.go +++ b/redis_cacher.go @@ -6,15 +6,16 @@ import ( "fmt" "github.com/garyburd/redigo/redis" //"github.com/go-xorm/core" + "hash/crc32" "log" "reflect" - "strconv" + // "strconv" "time" ) const ( - DEFAULT = time.Duration(0) - FOREVER = time.Duration(-1) + DEFAULT_EXPIRATION = time.Duration(0) + FOREVER_EXPIRATION = time.Duration(-1) ) // Wraps the Redis client to meet the Cache interface. @@ -69,7 +70,9 @@ func (c *RedisCacher) getBeanKey(tableName string, id string) string { } func (c *RedisCacher) getSqlKey(tableName string, sql string) string { - return fmt.Sprintf("sql:%s:%s", tableName, sql) + // hash sql to minimize key length + crc := crc32.ChecksumIEEE([]byte(sql)) + return fmt.Sprintf("sql:%s:%d", tableName, crc) } func (c *RedisCacher) Flush() error { @@ -80,7 +83,6 @@ func (c *RedisCacher) Flush() error { } func (c *RedisCacher) getObject(key string) interface{} { - conn := c.pool.Get() defer conn.Close() raw, err := conn.Do("GET", key) @@ -89,21 +91,23 @@ func (c *RedisCacher) getObject(key string) interface{} { } item, err := redis.Bytes(raw, err) if err != nil { - log.Fatalf("xorm/cache: redis.Bytes failed: %s", err) + log.Fatalf("[xorm/redis_cacher] redis.Bytes failed: %s", err) return nil } - value, err := Deserialize(item) + value, err := deserialize(item) return value } func (c *RedisCacher) GetIds(tableName, sql string) interface{} { + log.Printf("[xorm/redis_cacher] GetIds|tableName:%s|sql:%s", tableName, sql) return c.getObject(c.getSqlKey(tableName, sql)) } func (c *RedisCacher) GetBean(tableName string, id string) interface{} { + log.Printf("[xorm/redis_cacher] GetBean|tableName:%s|id:%s", tableName, id) return c.getObject(c.getBeanKey(tableName, id)) } @@ -112,10 +116,13 @@ func (c *RedisCacher) putObject(key string, value interface{}) { } func (c *RedisCacher) PutIds(tableName, sql string, ids interface{}) { - c.putObject(c.getBeanKey(tableName, sql), ids) + log.Printf("[xorm/redis_cacher] PutIds|tableName:%s|sql:%s|type:%v", tableName, sql, reflect.TypeOf(ids)) + + c.putObject(c.getSqlKey(tableName, sql), ids) } func (c *RedisCacher) PutBean(tableName string, id string, obj interface{}) { + log.Printf("[xorm/redis_cacher] PutBean|tableName:%s|id:%s|type:%v", tableName, id, reflect.TypeOf(obj)) c.putObject(c.getBeanKey(tableName, id), obj) } @@ -133,7 +140,6 @@ func (c *RedisCacher) delObject(key string) { func (c *RedisCacher) DelIds(tableName, sql string) { c.delObject(c.getSqlKey(tableName, sql)) - // TODO } func (c *RedisCacher) DelBean(tableName string, id string) { @@ -165,13 +171,13 @@ func (c *RedisCacher) invoke(f func(string, ...interface{}) (interface{}, error) key string, value interface{}, expires time.Duration) error { switch expires { - case DEFAULT: + case DEFAULT_EXPIRATION: expires = c.defaultExpiration - case FOREVER: + case FOREVER_EXPIRATION: expires = time.Duration(0) } - b, err := Serialize(value) + b, err := serialize(value) if err != nil { return err } @@ -186,72 +192,28 @@ func (c *RedisCacher) invoke(f func(string, ...interface{}) (interface{}, error) } } -// Serialize transforms the given value into bytes following these rules: -// - If value is a byte array, it is returned as-is. -// - If value is an int or uint type, it is returned as the ASCII representation -// - Else, encoding/gob is used to serialize -func Serialize(value interface{}) ([]byte, error) { - if bytes, ok := value.([]byte); ok { - return bytes, nil - } - - switch v := reflect.ValueOf(value); v.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return []byte(strconv.FormatInt(v.Int(), 10)), nil - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return []byte(strconv.FormatUint(v.Uint(), 10)), nil - } +func serialize(value interface{}) ([]byte, error) { RegisterGobConcreteType(value) var b bytes.Buffer encoder := gob.NewEncoder(&b) if err := interfaceEncode(encoder, value); err != nil { - log.Fatalf("xorm/cache: gob encoding '%s' failed: %s", value, err) + log.Fatalf("[xorm/redis_cacher] gob encoding '%s' failed: %s", value, err) return nil, err } return b.Bytes(), nil } -// Deserialize transforms bytes produced by Serialize back into a Go object, -// storing it into "ptr", which must be a pointer to the value type. -func Deserialize(byt []byte) (ptr interface{}, err error) { - if bytes, ok := ptr.(*[]byte); ok { - *bytes = byt - return - } - - if v := reflect.ValueOf(ptr); v.Kind() == reflect.Ptr { - switch p := v.Elem(); p.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - var i int64 - i, err = strconv.ParseInt(string(byt), 10, 64) - if err != nil { - log.Fatalf("xorm/cache: failed to parse int '%s': %s", string(byt), err) - } else { - p.SetInt(i) - } - return - - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - var i uint64 - i, err = strconv.ParseUint(string(byt), 10, 64) - if err != nil { - log.Fatalf("xorm/cache: failed to parse uint '%s': %s", string(byt), err) - } else { - p.SetUint(i) - } - return - } - } - +func deserialize(byt []byte) (ptr interface{}, err error) { b := bytes.NewBuffer(byt) decoder := gob.NewDecoder(b) if ptr, err = interfaceDecode(decoder); err != nil { - log.Fatalf("xorm/cache: gob decoding failed: %s", err) + log.Fatalf("[xorm/redis_cacher] gob decoding failed: %s", err) return } + return } @@ -259,17 +221,17 @@ func RegisterGobConcreteType(value interface{}) { t := reflect.TypeOf(value) + log.Printf("[xorm/redis_cacher] RegisterGobConcreteType:%v", t) + switch t.Kind() { case reflect.Ptr: v := reflect.ValueOf(value) i := v.Elem().Interface() gob.Register(i) - case reflect.Struct: + case reflect.Struct, reflect.Map, reflect.Slice: gob.Register(value) - case reflect.Slice: - fallthrough - case reflect.Map: - fallthrough + case reflect.String, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Bool, reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128: + // do nothing since already registered known type default: panic(fmt.Errorf("unhandled type: %v", t)) } @@ -283,9 +245,11 @@ func interfaceEncode(enc *gob.Encoder, p interface{}) error { // Pass pointer to interface so Encode sees (and hence sends) a value of // interface type. If we passed p directly it would see the concrete type instead. // See the blog post, "The Laws of Reflection" for background. + + log.Printf("[xorm/redis_cacher] interfaceEncode type:%v", reflect.TypeOf(p)) err := enc.Encode(&p) if err != nil { - log.Fatal("encode:", err) + log.Fatal("[xorm/redis_cacher] encode:", err) } return err } @@ -297,7 +261,13 @@ func interfaceDecode(dec *gob.Decoder) (interface{}, error) { var p interface{} err := dec.Decode(&p) if err != nil { - log.Fatal("decode:", err) + log.Fatal("[xorm/redis_cacher] decode:", err) } + log.Printf("[xorm/redis_cacher] interfaceDecode type:%v", reflect.TypeOf(p)) + + if reflect.TypeOf(p).Kind() == reflect.Struct { + // TODO need to convert p to pointer of struct, however, encountered reflect.ValueOf(p).CanAddr() == false + } + return p, err } From e41af85ab47c5921f18f76280540ffea34e22858 Mon Sep 17 00:00:00 2001 From: Nash Tsai Date: Fri, 22 Aug 2014 16:56:37 +0800 Subject: [PATCH 06/15] remove redis_cacher.go --- redis_cacher.go | 273 ------------------------------------------------ 1 file changed, 273 deletions(-) delete mode 100644 redis_cacher.go diff --git a/redis_cacher.go b/redis_cacher.go deleted file mode 100644 index 87f321bc..00000000 --- a/redis_cacher.go +++ /dev/null @@ -1,273 +0,0 @@ -package xorm - -import ( - "bytes" - "encoding/gob" - "fmt" - "github.com/garyburd/redigo/redis" - //"github.com/go-xorm/core" - "hash/crc32" - "log" - "reflect" - // "strconv" - "time" -) - -const ( - DEFAULT_EXPIRATION = time.Duration(0) - FOREVER_EXPIRATION = time.Duration(-1) -) - -// Wraps the Redis client to meet the Cache interface. -type RedisCacher struct { - pool *redis.Pool - defaultExpiration time.Duration -} - -// until redigo supports sharding/clustering, only one host will be in hostList -func NewRedisCacher(host string, password string, defaultExpiration time.Duration) *RedisCacher { - var pool = &redis.Pool{ - MaxIdle: 5, - IdleTimeout: 240 * time.Second, - Dial: func() (redis.Conn, error) { - // the redis protocol should probably be made sett-able - c, err := redis.Dial("tcp", host) - if err != nil { - return nil, err - } - if len(password) > 0 { - if _, err := c.Do("AUTH", password); err != nil { - c.Close() - return nil, err - } - } else { - // check with PING - if _, err := c.Do("PING"); err != nil { - c.Close() - return nil, err - } - } - return c, err - }, - // custom connection test method - TestOnBorrow: func(c redis.Conn, t time.Time) error { - if _, err := c.Do("PING"); err != nil { - return err - } - return nil - }, - } - return &RedisCacher{pool, defaultExpiration} -} - -func exists(conn redis.Conn, key string) bool { - existed, _ := redis.Bool(conn.Do("EXISTS", key)) - return existed -} - -func (c *RedisCacher) getBeanKey(tableName string, id string) string { - return fmt.Sprintf("bean:%s:%s", tableName, id) -} - -func (c *RedisCacher) getSqlKey(tableName string, sql string) string { - // hash sql to minimize key length - crc := crc32.ChecksumIEEE([]byte(sql)) - return fmt.Sprintf("sql:%s:%d", tableName, crc) -} - -func (c *RedisCacher) Flush() error { - conn := c.pool.Get() - defer conn.Close() - _, err := conn.Do("FLUSHALL") - return err -} - -func (c *RedisCacher) getObject(key string) interface{} { - conn := c.pool.Get() - defer conn.Close() - raw, err := conn.Do("GET", key) - if raw == nil { - return nil - } - item, err := redis.Bytes(raw, err) - if err != nil { - log.Fatalf("[xorm/redis_cacher] redis.Bytes failed: %s", err) - return nil - } - - value, err := deserialize(item) - - return value -} - -func (c *RedisCacher) GetIds(tableName, sql string) interface{} { - log.Printf("[xorm/redis_cacher] GetIds|tableName:%s|sql:%s", tableName, sql) - - return c.getObject(c.getSqlKey(tableName, sql)) -} - -func (c *RedisCacher) GetBean(tableName string, id string) interface{} { - log.Printf("[xorm/redis_cacher] GetBean|tableName:%s|id:%s", tableName, id) - return c.getObject(c.getBeanKey(tableName, id)) -} - -func (c *RedisCacher) putObject(key string, value interface{}) { - c.invoke(c.pool.Get().Do, key, value, c.defaultExpiration) -} - -func (c *RedisCacher) PutIds(tableName, sql string, ids interface{}) { - log.Printf("[xorm/redis_cacher] PutIds|tableName:%s|sql:%s|type:%v", tableName, sql, reflect.TypeOf(ids)) - - c.putObject(c.getSqlKey(tableName, sql), ids) -} - -func (c *RedisCacher) PutBean(tableName string, id string, obj interface{}) { - log.Printf("[xorm/redis_cacher] PutBean|tableName:%s|id:%s|type:%v", tableName, id, reflect.TypeOf(obj)) - c.putObject(c.getBeanKey(tableName, id), obj) -} - -func (c *RedisCacher) delObject(key string) { - conn := c.pool.Get() - defer conn.Close() - if !exists(conn, key) { - return // core.ErrCacheMiss - } - conn.Do("DEL", key) - - // _, err := conn.Do("DEL", key) - // return err -} - -func (c *RedisCacher) DelIds(tableName, sql string) { - c.delObject(c.getSqlKey(tableName, sql)) -} - -func (c *RedisCacher) DelBean(tableName string, id string) { - c.delObject(c.getBeanKey(tableName, id)) -} - -func (c *RedisCacher) clearObjects(key string) { - conn := c.pool.Get() - defer conn.Close() - if exists(conn, key) { - // _, err := conn.Do("DEL", key) - // return err - conn.Do("DEL", key) - } else { - // return ErrCacheMiss - } -} - -func (c *RedisCacher) ClearIds(tableName string) { - // TODO - c.clearObjects(c.getSqlKey(tableName, "*")) -} - -func (c *RedisCacher) ClearBeans(tableName string) { - c.clearObjects(c.getBeanKey(tableName, "*")) -} - -func (c *RedisCacher) invoke(f func(string, ...interface{}) (interface{}, error), - key string, value interface{}, expires time.Duration) error { - - switch expires { - case DEFAULT_EXPIRATION: - expires = c.defaultExpiration - case FOREVER_EXPIRATION: - expires = time.Duration(0) - } - - b, err := serialize(value) - if err != nil { - return err - } - conn := c.pool.Get() - defer conn.Close() - if expires > 0 { - _, err := f("SETEX", key, int32(expires/time.Second), b) - return err - } else { - _, err := f("SET", key, b) - return err - } -} - -func serialize(value interface{}) ([]byte, error) { - - RegisterGobConcreteType(value) - - var b bytes.Buffer - encoder := gob.NewEncoder(&b) - if err := interfaceEncode(encoder, value); err != nil { - log.Fatalf("[xorm/redis_cacher] gob encoding '%s' failed: %s", value, err) - return nil, err - } - return b.Bytes(), nil -} - -func deserialize(byt []byte) (ptr interface{}, err error) { - b := bytes.NewBuffer(byt) - decoder := gob.NewDecoder(b) - - if ptr, err = interfaceDecode(decoder); err != nil { - log.Fatalf("[xorm/redis_cacher] gob decoding failed: %s", err) - return - } - - return -} - -func RegisterGobConcreteType(value interface{}) { - - t := reflect.TypeOf(value) - - log.Printf("[xorm/redis_cacher] RegisterGobConcreteType:%v", t) - - switch t.Kind() { - case reflect.Ptr: - v := reflect.ValueOf(value) - i := v.Elem().Interface() - gob.Register(i) - case reflect.Struct, reflect.Map, reflect.Slice: - gob.Register(value) - case reflect.String, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Bool, reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128: - // do nothing since already registered known type - default: - panic(fmt.Errorf("unhandled type: %v", t)) - } -} - -// interfaceEncode encodes the interface value into the encoder. -func interfaceEncode(enc *gob.Encoder, p interface{}) error { - // The encode will fail unless the concrete type has been - // registered. We registered it in the calling function. - - // Pass pointer to interface so Encode sees (and hence sends) a value of - // interface type. If we passed p directly it would see the concrete type instead. - // See the blog post, "The Laws of Reflection" for background. - - log.Printf("[xorm/redis_cacher] interfaceEncode type:%v", reflect.TypeOf(p)) - err := enc.Encode(&p) - if err != nil { - log.Fatal("[xorm/redis_cacher] encode:", err) - } - return err -} - -// interfaceDecode decodes the next interface value from the stream and returns it. -func interfaceDecode(dec *gob.Decoder) (interface{}, error) { - // The decode will fail unless the concrete type on the wire has been - // registered. We registered it in the calling function. - var p interface{} - err := dec.Decode(&p) - if err != nil { - log.Fatal("[xorm/redis_cacher] decode:", err) - } - log.Printf("[xorm/redis_cacher] interfaceDecode type:%v", reflect.TypeOf(p)) - - if reflect.TypeOf(p).Kind() == reflect.Struct { - // TODO need to convert p to pointer of struct, however, encountered reflect.ValueOf(p).CanAddr() == false - } - - return p, err -} From c8fdc8ec21cce606a38efc2646c74bef579812c8 Mon Sep 17 00:00:00 2001 From: Nash Tsai Date: Fri, 29 Aug 2014 00:06:15 +0800 Subject: [PATCH 07/15] update docs/QuickStart.md --- docs/QuickStart.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/QuickStart.md b/docs/QuickStart.md index 3b9f0aab..a363fdf2 100644 --- a/docs/QuickStart.md +++ b/docs/QuickStart.md @@ -346,7 +346,6 @@ Notice: If you want to use transaction on inserting, you should use session.Begi ### 5.1. Chainable APIs for Queries, Execusions and Aggregations Queries and Aggregations is basically formed by using `Get`, `Find`, `Count` methods, with conjunction of following chainable APIs to form conditions, grouping and ordering: -查询和统计主要使用`Get`, `Find`, `Count`三个方法。在进行查询时可以使用多个方法来形成查询条件,条件函数如下: * Id([]interface{}) Primary Key lookup @@ -535,16 +534,16 @@ affected, err := engine.Table(new(User)).Id(id).Update(map[string]interface{}{"a ``` -### 6.1.乐观锁 +### 6.1.Optimistic Lock -要使用乐观锁,需要使用version标记 +To enable object optimistic lock, add 'version' tag value: type User struct { Id int64 Name string Version int `xorm:"version"` } -在Insert时,version标记的字段将会被设置为1,在Update时,Update的内容必须包含version原来的值。 +The version starts with 1 when inserted to DB. For updating make sure originated version value is used for optimistic lock check. ```Go var user User From 0491cec2e60328ea04a466fa81c07d29008b69d6 Mon Sep 17 00:00:00 2001 From: Nash Tsai Date: Fri, 29 Aug 2014 00:34:09 +0800 Subject: [PATCH 08/15] changed to cache and nocache tag value support public Engine.TableInfo() API --- engine.go | 73 ++++++++++++++++++++++------------------------------ rows.go | 2 +- session.go | 16 ++++++------ statement.go | 6 ++--- 4 files changed, 43 insertions(+), 54 deletions(-) diff --git a/engine.go b/engine.go index ef588583..608d14d7 100644 --- a/engine.go +++ b/engine.go @@ -623,7 +623,7 @@ func (engine *Engine) autoMapType(v reflect.Value) *core.Table { return table } -func (engine *Engine) autoMap(bean interface{}) *core.Table { +func (engine *Engine) TableInfo(bean interface{}) *core.Table { v := rValue(bean) return engine.autoMapType(v) } @@ -649,32 +649,6 @@ func (engine *Engine) newTable() *core.Table { return table } -func (engine *Engine) processCacherTag(table *core.Table, v reflect.Value, cacherTagStr string) { - - for _, part := range strings.Split(cacherTagStr, ",") { - switch { - case part == "false": // even if engine has assigned cacher, this table will not have cache support - table.Cacher = nil - return - - case part == "true": // use default 'read-write' cache - if engine.Cacher != nil { // !nash! use engine's cacher if provided - table.Cacher = engine.Cacher - } else { - table.Cacher = NewLRUCacher2(NewMemoryStore(), time.Hour, 10000) // !nashtsai! HACK use LRU cacher for now - } - return - // TODO - // case strings.HasPrefix(part, "usage:"): - // usageStr := part[len("usage:"):] - - // case strings.HasPrefix(part, "include:"): - // includeStr := part[len("include:"):] - } - - } -} - func (engine *Engine) mapType(v reflect.Value) *core.Table { t := v.Type() table := engine.newTable() @@ -700,20 +674,13 @@ func (engine *Engine) mapType(v reflect.Value) *core.Table { var idFieldColName string var err error - hasProcessedCacheTag := false + hasCacheTag := false + hasNoCacheTag := false for i := 0; i < t.NumField(); i++ { tag := t.Field(i).Tag ormTagStr := tag.Get(engine.TagIdentifier) - if !hasProcessedCacheTag { - cacheTagStr := tag.Get("xorm_cache") - if cacheTagStr != "" { - hasProcessedCacheTag = true - engine.processCacherTag(table, v, cacheTagStr) - } - } - var col *core.Column fieldValue := v.Field(i) fieldType := fieldValue.Type() @@ -804,6 +771,14 @@ func (engine *Engine) mapType(v reflect.Value) *core.Table { isUnique = true case k == "NOTNULL": col.Nullable = false + case k == "CACHE": + if !hasCacheTag { + hasCacheTag = true + } + case k == "NOCACHE": + if !hasNoCacheTag { + hasNoCacheTag = true + } case k == "NOT": default: if strings.HasPrefix(k, "'") && strings.HasSuffix(k, "'") { @@ -912,7 +887,7 @@ func (engine *Engine) mapType(v reflect.Value) *core.Table { if fieldType.Kind() == reflect.Int64 && (col.FieldName == "Id" || strings.HasSuffix(col.FieldName, ".Id")) { idFieldColName = col.Name } - } + } // end for if idFieldColName != "" && len(table.PrimaryKeys) == 0 { col := table.GetColumn(idFieldColName) @@ -923,6 +898,20 @@ func (engine *Engine) mapType(v reflect.Value) *core.Table { table.AutoIncrement = col.Name } + if hasCacheTag { + if engine.Cacher != nil { // !nash! use engine's cacher if provided + engine.Logger.Info("enable cache on table:", table.Name) + table.Cacher = engine.Cacher + } else { + engine.Logger.Info("enable LRU cache on table:", table.Name) + table.Cacher = NewLRUCacher2(NewMemoryStore(), time.Hour, 10000) // !nashtsai! HACK use LRU cacher for now + } + } + if hasNoCacheTag { + engine.Logger.Info("no cache on table:", table.Name) + table.Cacher = nil + } + return table } @@ -971,7 +960,7 @@ func (engine *Engine) IsTableExist(bean interface{}) (bool, error) { } func (engine *Engine) IdOf(bean interface{}) core.PK { - table := engine.autoMap(bean) + table := engine.TableInfo(bean) v := reflect.Indirect(reflect.ValueOf(bean)) pk := make([]interface{}, len(table.PrimaryKeys)) for i, col := range table.PKColumns() { @@ -1019,7 +1008,7 @@ func (engine *Engine) ClearCacheBean(bean interface{}, id string) error { if t.Kind() != reflect.Struct { return errors.New("error params") } - table := engine.autoMap(bean) + table := engine.TableInfo(bean) cacher := table.Cacher if cacher == nil { cacher = engine.Cacher @@ -1038,7 +1027,7 @@ func (engine *Engine) ClearCache(beans ...interface{}) error { if t.Kind() != reflect.Struct { return errors.New("error params") } - table := engine.autoMap(bean) + table := engine.TableInfo(bean) cacher := table.Cacher if cacher == nil { cacher = engine.Cacher @@ -1056,7 +1045,7 @@ func (engine *Engine) ClearCache(beans ...interface{}) error { // If you change some field, you should change the database manually. func (engine *Engine) Sync(beans ...interface{}) error { for _, bean := range beans { - table := engine.autoMap(bean) + table := engine.TableInfo(bean) s := engine.NewSession() defer s.Close() @@ -1153,7 +1142,7 @@ func (engine *Engine) Sync2(beans ...interface{}) error { } for _, bean := range beans { - table := engine.autoMap(bean) + table := engine.TableInfo(bean) var oriTable *core.Table for _, tb := range tables { diff --git a/rows.go b/rows.go index 99f724dd..c566b125 100644 --- a/rows.go +++ b/rows.go @@ -34,7 +34,7 @@ func newRows(session *Session, bean interface{}) (*Rows, error) { var sqlStr string var args []interface{} - rows.session.Statement.RefTable = rows.session.Engine.autoMap(bean) + rows.session.Statement.RefTable = rows.session.Engine.TableInfo(bean) if rows.session.Statement.RawSQL == "" { sqlStr, args = rows.session.Statement.genGetSql(bean) } else { diff --git a/session.go b/session.go index 055d7250..07a25bef 100644 --- a/session.go +++ b/session.go @@ -471,7 +471,7 @@ func (session *Session) Exec(sqlStr string, args ...interface{}) (sql.Result, er // this function create a table according a bean func (session *Session) CreateTable(bean interface{}) error { - session.Statement.RefTable = session.Engine.autoMap(bean) + session.Statement.RefTable = session.Engine.TableInfo(bean) err := session.newDb() if err != nil { @@ -487,7 +487,7 @@ func (session *Session) CreateTable(bean interface{}) error { // create indexes func (session *Session) CreateIndexes(bean interface{}) error { - session.Statement.RefTable = session.Engine.autoMap(bean) + session.Statement.RefTable = session.Engine.TableInfo(bean) err := session.newDb() if err != nil { @@ -510,7 +510,7 @@ func (session *Session) CreateIndexes(bean interface{}) error { // create uniques func (session *Session) CreateUniques(bean interface{}) error { - session.Statement.RefTable = session.Engine.autoMap(bean) + session.Statement.RefTable = session.Engine.TableInfo(bean) err := session.newDb() if err != nil { @@ -597,7 +597,7 @@ func (session *Session) DropTable(bean interface{}) error { if t.Kind() == reflect.String { session.Statement.AltTableName = bean.(string) } else if t.Kind() == reflect.Struct { - session.Statement.RefTable = session.Engine.autoMap(bean) + session.Statement.RefTable = session.Engine.TableInfo(bean) } else { return errors.New("Unsupported type") } @@ -954,7 +954,7 @@ func (session *Session) Get(bean interface{}) (bool, error) { var args []interface{} if session.Statement.RefTable == nil { - session.Statement.RefTable = session.Engine.autoMap(bean) + session.Statement.RefTable = session.Engine.TableInfo(bean) } if session.Statement.RawSQL == "" { @@ -2649,7 +2649,7 @@ func (session *Session) value2Interface(col *core.Column, fieldValue reflect.Val } func (session *Session) innerInsert(bean interface{}) (int64, error) { - table := session.Engine.autoMap(bean) + table := session.Engine.TableInfo(bean) session.Statement.RefTable = table // handle BeforeInsertProcessor @@ -3046,7 +3046,7 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 // -- if t.Kind() == reflect.Struct { - table = session.Engine.autoMap(bean) + table = session.Engine.TableInfo(bean) session.Statement.RefTable = table if session.Statement.ColumnStr == "" { @@ -3300,7 +3300,7 @@ func (session *Session) Delete(bean interface{}) (int64, error) { } // -- - table := session.Engine.autoMap(bean) + table := session.Engine.TableInfo(bean) session.Statement.RefTable = table colNames, args := buildConditions(session.Engine, table, bean, true, true, false, true, session.Statement.allUseBool, session.Statement.useAllCols, diff --git a/statement.go b/statement.go index e7a253d6..14645f58 100644 --- a/statement.go +++ b/statement.go @@ -151,7 +151,7 @@ func (statement *Statement) Table(tableNameOrBean interface{}) *Statement { /*func (statement *Statement) genFields(bean interface{}) map[string]interface{} { results := make(map[string]interface{}) - table := statement.Engine.autoMap(bean) + table := statement.Engine.TableInfo(bean) for _, col := range table.Columns { fieldValue := col.ValueOf(bean) fieldType := reflect.TypeOf(fieldValue.Interface()) @@ -1020,7 +1020,7 @@ func (s *Statement) genDropSQL() string { func (statement *Statement) genGetSql(bean interface{}) (string, []interface{}) { var table *core.Table if statement.RefTable == nil { - table = statement.Engine.autoMap(bean) + table = statement.Engine.TableInfo(bean) statement.RefTable = table } else { table = statement.RefTable @@ -1070,7 +1070,7 @@ func (s *Statement) genAddUniqueStr(uqeName string, cols []string) (string, []in }*/ func (statement *Statement) genCountSql(bean interface{}) (string, []interface{}) { - table := statement.Engine.autoMap(bean) + table := statement.Engine.TableInfo(bean) statement.RefTable = table colNames, args := buildConditions(statement.Engine, table, bean, true, true, false, From fa34521ab93fda67f14facd74a0a9907ba4f1791 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 5 Sep 2014 10:30:41 +0800 Subject: [PATCH 09/15] add reversed words for mysql --- mysql_dialect.go | 116 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/mysql_dialect.go b/mysql_dialect.go index ee1f4e06..ef44ffc9 100644 --- a/mysql_dialect.go +++ b/mysql_dialect.go @@ -15,6 +15,122 @@ import ( // RegisterDialect("mysql", &mysql{}) // } +var ( + reservedWords = map[string]bool{ + "ADD":true, + "ALL":true, + "ALTER":true, +"ANALYZE":true, +"AND":true, +"AS": true, +"ASC":true, +"ASENSITIVE":true, +"BEFORE":true, +"BETWEEN":true, +"BIGINT":true, +"BINARY":true, +"BLOB":true, +"BOTH":true, +"BY":true, +"CALL":true, +"CASCADE":true, +"CASE":true, +"CHANGE":true, +"CHAR":true, +"CHARACTER":true, +"CHECK":true, +"COLLATE":true, +"COLUMN":true, +"CONDITION":true, +"CONNECTION":true, +"CONSTRAINT":true, +"CONTINUE":true, +"CONVERT":true, +"CREATE":true, +"CROSS":true, +"CURRENT_DATE":true, +"CURRENT_TIME":true, +"CURRENT_TIMESTAMP":true, +"CURRENT_USER":true, +"CURSOR":true, +"DATABASE":true, +"DATABASES":true, +"DAY_HOUR":true, +"DAY_MICROSECOND":true, +"DAY_MINUTE":true, +"DAY_SECOND":true, +DEC DECIMAL DECLARE +DEFAULT DELAYED DELETE +DESC DESCRIBE DETERMINISTIC +DISTINCT DISTINCTROW DIV +DOUBLE DROP DUAL +EACH ELSE ELSEIF +ENCLOSED ESCAPED EXISTS +EXIT EXPLAIN FALSE +FETCH FLOAT FLOAT4 +FLOAT8 FOR FORCE +FOREIGN FROM FULLTEXT +GOTO GRANT GROUP +HAVING HIGH_PRIORITY HOUR_MICROSECOND +HOUR_MINUTE HOUR_SECOND IF +IGNORE IN INDEX +INFILE INNER INOUT +INSENSITIVE INSERT INT +INT1 INT2 INT3 +INT4 INT8 INTEGER +INTERVAL INTO IS +ITERATE JOIN KEY +KEYS KILL LABEL +LEADING LEAVE LEFT +LIKE LIMIT LINEAR +LINES LOAD LOCALTIME +LOCALTIMESTAMP LOCK LONG +LONGBLOB LONGTEXT LOOP +LOW_PRIORITY MATCH MEDIUMBLOB +MEDIUMINT MEDIUMTEXT MIDDLEINT +MINUTE_MICROSECOND MINUTE_SECOND MOD +MODIFIES NATURAL NOT +NO_WRITE_TO_BINLOG NULL NUMERIC +ON OPTIMIZE OPTION +OPTIONALLY OR ORDER +OUT OUTER OUTFILE +PRECISION PRIMARY PROCEDURE +PURGE RAID0 RANGE +READ READS REAL +REFERENCES REGEXP RELEASE +RENAME REPEAT REPLACE +REQUIRE RESTRICT RETURN +REVOKE RIGHT RLIKE +SCHEMA SCHEMAS SECOND_MICROSECOND +SELECT SENSITIVE SEPARATOR +SET SHOW SMALLINT +SPATIAL SPECIFIC SQL +SQLEXCEPTION SQLSTATE SQLWARNING +SQL_BIG_RESULT SQL_CALC_FOUND_ROWS SQL_SMALL_RESULT +SSL STARTING STRAIGHT_JOIN +TABLE TERMINATED THEN +TINYBLOB TINYINT TINYTEXT +TO TRAILING TRIGGER +TRUE UNDO UNION +UNIQUE UNLOCK UNSIGNED +UPDATE USAGE USE +USING UTC_DATE UTC_TIME +UTC_TIMESTAMP VALUES VARBINARY +"VARCHAR":true, +"VARCHARACTER":true, +"VARYING":true, +"WHEN":true, +"WHERE":true, +"WHILE":true, +"WITH":true, +"WRITE":true, +"X509":true, +"XOR":true, +"YEAR_MONTH":true, +"ZEROFILL":true, + } +) + type mysql struct { core.Base net string From c350aa328882b8cb239a3f4fa8acdd4a37a43001 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 5 Sep 2014 11:13:23 +0800 Subject: [PATCH 10/15] bug fixed --- engine.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/engine.go b/engine.go index 806b5130..3c49d947 100644 --- a/engine.go +++ b/engine.go @@ -412,7 +412,12 @@ func (engine *Engine) DumpAll(w io.Writer) error { temp += fmt.Sprintf(", '%s'", d.(string)) } } else if col.SQLType.IsNumeric() { - temp += fmt.Sprintf(", %s", string(d.([]byte))) + switch reflect.TypeOf(d).Kind() { + case reflect.Slice: + temp += fmt.Sprintf(", %s", string(d.([]byte))) + default: + temp += fmt.Sprintf(", %v", d) + } } else { s := fmt.Sprintf("%v", d) if strings.Contains(s, ":") || strings.Contains(s, "-") { From 6ea489a1a948273363dce7cf71cb3c2cc1b33fde Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 6 Sep 2014 23:25:46 +0800 Subject: [PATCH 11/15] add reserved words for mysql,sqlite,mssql,oracle --- mssql_dialect.go | 199 ++++++++++++++++++ mysql_dialect.go | 262 +++++++++++++---------- oracle_dialect.go | 493 ++++++++++++++++++++++++++++++++++++++++++++ postgres_dialect.go | 12 ++ sqlite3_dialect.go | 138 +++++++++++++ 5 files changed, 992 insertions(+), 112 deletions(-) diff --git a/mssql_dialect.go b/mssql_dialect.go index eee50795..49e4c0ab 100644 --- a/mssql_dialect.go +++ b/mssql_dialect.go @@ -13,6 +13,196 @@ import ( // RegisterDialect("mssql", &mssql{}) // } +var ( + mssqlReservedWords = map[string]bool{ + "ADD": true, + "EXTERNAL": true, + "PROCEDURE": true, + "ALL": true, + "FETCH": true, + "PUBLIC": true, + "ALTER": true, + "FILE": true, + "RAISERROR": true, + "AND": true, + "FILLFACTOR": true, + "READ": true, + "ANY": true, + "FOR": true, + "READTEXT": true, + "AS": true, + "FOREIGN": true, + "RECONFIGURE": true, + "ASC": true, + "FREETEXT": true, + "REFERENCES": true, + "AUTHORIZATION": true, + "FREETEXTTABLE": true, + "REPLICATION": true, + "BACKUP": true, + "FROM": true, + "RESTORE": true, + "BEGIN": true, + "FULL": true, + "RESTRICT": true, + "BETWEEN": true, + "FUNCTION": true, + "RETURN": true, + "BREAK": true, + "GOTO": true, + "REVERT": true, + "BROWSE": true, + "GRANT": true, + "REVOKE": true, + "BULK": true, + "GROUP": true, + "RIGHT": true, + "BY": true, + "HAVING": true, + "ROLLBACK": true, + "CASCADE": true, + "HOLDLOCK": true, + "ROWCOUNT": true, + "CASE": true, + "IDENTITY": true, + "ROWGUIDCOL": true, + "CHECK": true, + "IDENTITY_INSERT": true, + "RULE": true, + "CHECKPOINT": true, + "IDENTITYCOL": true, + "SAVE": true, + "CLOSE": true, + "IF": true, + "SCHEMA": true, + "CLUSTERED": true, + "IN": true, + "SECURITYAUDIT": true, + "COALESCE": true, + "INDEX": true, + "SELECT": true, + "COLLATE": true, + "INNER": true, + "SEMANTICKEYPHRASETABLE": true, + "COLUMN": true, + "INSERT": true, + "SEMANTICSIMILARITYDETAILSTABLE": true, + "COMMIT": true, + "INTERSECT": true, + "SEMANTICSIMILARITYTABLE": true, + "COMPUTE": true, + "INTO": true, + "SESSION_USER": true, + "CONSTRAINT": true, + "IS": true, + "SET": true, + "CONTAINS": true, + "JOIN": true, + "SETUSER": true, + "CONTAINSTABLE": true, + "KEY": true, + "SHUTDOWN": true, + "CONTINUE": true, + "KILL": true, + "SOME": true, + "CONVERT": true, + "LEFT": true, + "STATISTICS": true, + "CREATE": true, + "LIKE": true, + "SYSTEM_USER": true, + "CROSS": true, + "LINENO": true, + "TABLE": true, + "CURRENT": true, + "LOAD": true, + "TABLESAMPLE": true, + "CURRENT_DATE": true, + "MERGE": true, + "TEXTSIZE": true, + "CURRENT_TIME": true, + "NATIONAL": true, + "THEN": true, + "CURRENT_TIMESTAMP": true, + "NOCHECK": true, + "TO": true, + "CURRENT_USER": true, + "NONCLUSTERED": true, + "TOP": true, + "CURSOR": true, + "NOT": true, + "TRAN": true, + "DATABASE": true, + "NULL": true, + "TRANSACTION": true, + "DBCC": true, + "NULLIF": true, + "TRIGGER": true, + "DEALLOCATE": true, + "OF": true, + "TRUNCATE": true, + "DECLARE": true, + "OFF": true, + "TRY_CONVERT": true, + "DEFAULT": true, + "OFFSETS": true, + "TSEQUAL": true, + "DELETE": true, + "ON": true, + "UNION": true, + "DENY": true, + "OPEN": true, + "UNIQUE": true, + "DESC": true, + "OPENDATASOURCE": true, + "UNPIVOT": true, + "DISK": true, + "OPENQUERY": true, + "UPDATE": true, + "DISTINCT": true, + "OPENROWSET": true, + "UPDATETEXT": true, + "DISTRIBUTED": true, + "OPENXML": true, + "USE": true, + "DOUBLE": true, + "OPTION": true, + "USER": true, + "DROP": true, + "OR": true, + "VALUES": true, + "DUMP": true, + "ORDER": true, + "VARYING": true, + "ELSE": true, + "OUTER": true, + "VIEW": true, + "END": true, + "OVER": true, + "WAITFOR": true, + "ERRLVL": true, + "PERCENT": true, + "WHEN": true, + "ESCAPE": true, + "PIVOT": true, + "WHERE": true, + "EXCEPT": true, + "PLAN": true, + "WHILE": true, + "EXEC": true, + "PRECISION": true, + "WITH": true, + "EXECUTE": true, + "PRIMARY": true, + "WITHIN": true, + "EXISTS": true, + "PRINT": true, + "WRITETEXT": true, + "EXIT": true, + "PROC": true, + } +) + type mssql struct { core.Base } @@ -74,6 +264,15 @@ func (db *mssql) SupportInsertMany() bool { return true } +func (db *mssql) IsReserved(name string) bool { + _, ok := mssqlReservedWords[name] + return ok +} + +func (db *mssql) Quote(name string) string { + return "[" + name + "]" +} + func (db *mssql) QuoteStr() string { return "\"" } diff --git a/mysql_dialect.go b/mysql_dialect.go index ef44ffc9..b86d2bc9 100644 --- a/mysql_dialect.go +++ b/mysql_dialect.go @@ -16,118 +16,147 @@ import ( // } var ( - reservedWords = map[string]bool{ - "ADD":true, - "ALL":true, - "ALTER":true, -"ANALYZE":true, -"AND":true, -"AS": true, -"ASC":true, -"ASENSITIVE":true, -"BEFORE":true, -"BETWEEN":true, -"BIGINT":true, -"BINARY":true, -"BLOB":true, -"BOTH":true, -"BY":true, -"CALL":true, -"CASCADE":true, -"CASE":true, -"CHANGE":true, -"CHAR":true, -"CHARACTER":true, -"CHECK":true, -"COLLATE":true, -"COLUMN":true, -"CONDITION":true, -"CONNECTION":true, -"CONSTRAINT":true, -"CONTINUE":true, -"CONVERT":true, -"CREATE":true, -"CROSS":true, -"CURRENT_DATE":true, -"CURRENT_TIME":true, -"CURRENT_TIMESTAMP":true, -"CURRENT_USER":true, -"CURSOR":true, -"DATABASE":true, -"DATABASES":true, -"DAY_HOUR":true, -"DAY_MICROSECOND":true, -"DAY_MINUTE":true, -"DAY_SECOND":true, -DEC DECIMAL DECLARE -DEFAULT DELAYED DELETE -DESC DESCRIBE DETERMINISTIC -DISTINCT DISTINCTROW DIV -DOUBLE DROP DUAL -EACH ELSE ELSEIF -ENCLOSED ESCAPED EXISTS -EXIT EXPLAIN FALSE -FETCH FLOAT FLOAT4 -FLOAT8 FOR FORCE -FOREIGN FROM FULLTEXT -GOTO GRANT GROUP -HAVING HIGH_PRIORITY HOUR_MICROSECOND -HOUR_MINUTE HOUR_SECOND IF -IGNORE IN INDEX -INFILE INNER INOUT -INSENSITIVE INSERT INT -INT1 INT2 INT3 -INT4 INT8 INTEGER -INTERVAL INTO IS -ITERATE JOIN KEY -KEYS KILL LABEL -LEADING LEAVE LEFT -LIKE LIMIT LINEAR -LINES LOAD LOCALTIME -LOCALTIMESTAMP LOCK LONG -LONGBLOB LONGTEXT LOOP -LOW_PRIORITY MATCH MEDIUMBLOB -MEDIUMINT MEDIUMTEXT MIDDLEINT -MINUTE_MICROSECOND MINUTE_SECOND MOD -MODIFIES NATURAL NOT -NO_WRITE_TO_BINLOG NULL NUMERIC -ON OPTIMIZE OPTION -OPTIONALLY OR ORDER -OUT OUTER OUTFILE -PRECISION PRIMARY PROCEDURE -PURGE RAID0 RANGE -READ READS REAL -REFERENCES REGEXP RELEASE -RENAME REPEAT REPLACE -REQUIRE RESTRICT RETURN -REVOKE RIGHT RLIKE -SCHEMA SCHEMAS SECOND_MICROSECOND -SELECT SENSITIVE SEPARATOR -SET SHOW SMALLINT -SPATIAL SPECIFIC SQL -SQLEXCEPTION SQLSTATE SQLWARNING -SQL_BIG_RESULT SQL_CALC_FOUND_ROWS SQL_SMALL_RESULT -SSL STARTING STRAIGHT_JOIN -TABLE TERMINATED THEN -TINYBLOB TINYINT TINYTEXT -TO TRAILING TRIGGER -TRUE UNDO UNION -UNIQUE UNLOCK UNSIGNED -UPDATE USAGE USE -USING UTC_DATE UTC_TIME -UTC_TIMESTAMP VALUES VARBINARY -"VARCHAR":true, -"VARCHARACTER":true, -"VARYING":true, -"WHEN":true, -"WHERE":true, -"WHILE":true, -"WITH":true, -"WRITE":true, -"X509":true, -"XOR":true, -"YEAR_MONTH":true, -"ZEROFILL":true, + mysqlReservedWords = map[string]bool{ + "ADD": true, + "ALL": true, + "ALTER": true, + "ANALYZE": true, + "AND": true, + "AS": true, + "ASC": true, + "ASENSITIVE": true, + "BEFORE": true, + "BETWEEN": true, + "BIGINT": true, + "BINARY": true, + "BLOB": true, + "BOTH": true, + "BY": true, + "CALL": true, + "CASCADE": true, + "CASE": true, + "CHANGE": true, + "CHAR": true, + "CHARACTER": true, + "CHECK": true, + "COLLATE": true, + "COLUMN": true, + "CONDITION": true, + "CONNECTION": true, + "CONSTRAINT": true, + "CONTINUE": true, + "CONVERT": true, + "CREATE": true, + "CROSS": true, + "CURRENT_DATE": true, + "CURRENT_TIME": true, + "CURRENT_TIMESTAMP": true, + "CURRENT_USER": true, + "CURSOR": true, + "DATABASE": true, + "DATABASES": true, + "DAY_HOUR": true, + "DAY_MICROSECOND": true, + "DAY_MINUTE": true, + "DAY_SECOND": true, + "DEC": true, + "DECIMAL": true, + "DECLARE": true, + "DEFAULT": true, + "DELAYED": true, + "DELETE": true, + "DESC": true, + "DESCRIBE": true, + "DETERMINISTIC": true, + "DISTINCT": true, + "DISTINCTROW": true, + "DIV": true, + "DOUBLE": true, + "DROP": true, + "DUAL": true, + "EACH": true, + "ELSE": true, + "ELSEIF": true, + "ENCLOSED": true, + "ESCAPED": true, + "EXISTS": true, + "EXIT": true, + "EXPLAIN": true, + "FALSE": true, + "FETCH": true, + "FLOAT": true, + "FLOAT4": true, + "FLOAT8": true, + "FOR": true, + "FORCE": true, + "FOREIGN": true, + "FROM": true, + "FULLTEXT": true, + "GOTO": true, + "GRANT": true, + "GROUP": true, + "HAVING": true, + "HIGH_PRIORITY": true, + "HOUR_MICROSECOND": true, + "HOUR_MINUTE": true, + "HOUR_SECOND": true, + "IF": true, + "IGNORE": true, + "IN": true, "INDEX": true, + "INFILE": true, "INNER": true, "INOUT": true, + "INSENSITIVE": true, "INSERT": true, "INT": true, + "INT1": true, "INT2": true, "INT3": true, + "INT4": true, "INT8": true, "INTEGER": true, + "INTERVAL": true, "INTO": true, "IS": true, + "ITERATE": true, "JOIN": true, "KEY": true, + "KEYS": true, "KILL": true, "LABEL": true, + "LEADING": true, "LEAVE": true, "LEFT": true, + "LIKE": true, "LIMIT": true, "LINEAR": true, + "LINES": true, "LOAD": true, "LOCALTIME": true, + "LOCALTIMESTAMP": true, "LOCK": true, "LONG": true, + "LONGBLOB": true, "LONGTEXT": true, "LOOP": true, + "LOW_PRIORITY": true, "MATCH": true, "MEDIUMBLOB": true, + "MEDIUMINT": true, "MEDIUMTEXT": true, "MIDDLEINT": true, + "MINUTE_MICROSECOND": true, "MINUTE_SECOND": true, "MOD": true, + "MODIFIES": true, "NATURAL": true, "NOT": true, + "NO_WRITE_TO_BINLOG": true, "NULL": true, "NUMERIC": true, + "ON OPTIMIZE": true, "OPTION": true, + "OPTIONALLY": true, "OR": true, "ORDER": true, + "OUT": true, "OUTER": true, "OUTFILE": true, + "PRECISION": true, "PRIMARY": true, "PROCEDURE": true, + "PURGE": true, "RAID0": true, "RANGE": true, + "READ": true, "READS": true, "REAL": true, + "REFERENCES": true, "REGEXP": true, "RELEASE": true, + "RENAME": true, "REPEAT": true, "REPLACE": true, + "REQUIRE": true, "RESTRICT": true, "RETURN": true, + "REVOKE": true, "RIGHT": true, "RLIKE": true, + "SCHEMA": true, "SCHEMAS": true, "SECOND_MICROSECOND": true, + "SELECT": true, "SENSITIVE": true, "SEPARATOR": true, + "SET": true, "SHOW": true, "SMALLINT": true, + "SPATIAL": true, "SPECIFIC": true, "SQL": true, + "SQLEXCEPTION": true, "SQLSTATE": true, "SQLWARNING": true, + "SQL_BIG_RESULT": true, "SQL_CALC_FOUND_ROWS": true, "SQL_SMALL_RESULT": true, + "SSL": true, "STARTING": true, "STRAIGHT_JOIN": true, + "TABLE": true, "TERMINATED": true, "THEN": true, + "TINYBLOB": true, "TINYINT": true, "TINYTEXT": true, + "TO": true, "TRAILING": true, "TRIGGER": true, + "TRUE": true, "UNDO": true, "UNION": true, + "UNIQUE": true, "UNLOCK": true, "UNSIGNED": true, + "UPDATE": true, "USAGE": true, "USE": true, + "USING": true, "UTC_DATE": true, "UTC_TIME": true, + "UTC_TIMESTAMP": true, "VALUES": true, "VARBINARY": true, + "VARCHAR": true, + "VARCHARACTER": true, + "VARYING": true, + "WHEN": true, + "WHERE": true, + "WHILE": true, + "WITH": true, + "WRITE": true, + "X509": true, + "XOR": true, + "YEAR_MONTH": true, + "ZEROFILL": true, } ) @@ -211,6 +240,15 @@ func (db *mysql) SupportInsertMany() bool { return true } +func (db *mysql) IsReserved(name string) bool { + _, ok := mysqlReservedWords[name] + return ok +} + +func (db *mysql) Quote(name string) string { + return "`" + name + "`" +} + func (db *mysql) QuoteStr() string { return "`" } diff --git a/oracle_dialect.go b/oracle_dialect.go index 57da2c50..fc603686 100644 --- a/oracle_dialect.go +++ b/oracle_dialect.go @@ -13,6 +13,490 @@ import ( // RegisterDialect("oracle", &oracle{}) // } +var ( + oracleReservedWords = map[string]bool{ + "ACCESS": true, + "ACCOUNT": true, + "ACTIVATE": true, + "ADD": true, + "ADMIN": true, + "ADVISE": true, + "AFTER": true, + "ALL": true, + "ALL_ROWS": true, + "ALLOCATE": true, + "ALTER": true, + "ANALYZE": true, + "AND": true, + "ANY": true, + "ARCHIVE": true, + "ARCHIVELOG": true, + "ARRAY": true, + "AS": true, + "ASC": true, + "AT": true, + "AUDIT": true, + "AUTHENTICATED": true, + "AUTHORIZATION": true, + "AUTOEXTEND": true, + "AUTOMATIC": true, + "BACKUP": true, + "BECOME": true, + "BEFORE": true, + "BEGIN": true, + "BETWEEN": true, + "BFILE": true, + "BITMAP": true, + "BLOB": true, + "BLOCK": true, + "BODY": true, + "BY": true, + "CACHE": true, + "CACHE_INSTANCES": true, + "CANCEL": true, + "CASCADE": true, + "CAST": true, + "CFILE": true, + "CHAINED": true, + "CHANGE": true, + "CHAR": true, + "CHAR_CS": true, + "CHARACTER": true, + "CHECK": true, + "CHECKPOINT": true, + "CHOOSE": true, + "CHUNK": true, + "CLEAR": true, + "CLOB": true, + "CLONE": true, + "CLOSE": true, + "CLOSE_CACHED_OPEN_CURSORS": true, + "CLUSTER": true, + "COALESCE": true, + "COLUMN": true, + "COLUMNS": true, + "COMMENT": true, + "COMMIT": true, + "COMMITTED": true, + "COMPATIBILITY": true, + "COMPILE": true, + "COMPLETE": true, + "COMPOSITE_LIMIT": true, + "COMPRESS": true, + "COMPUTE": true, + "CONNECT": true, + "CONNECT_TIME": true, + "CONSTRAINT": true, + "CONSTRAINTS": true, + "CONTENTS": true, + "CONTINUE": true, + "CONTROLFILE": true, + "CONVERT": true, + "COST": true, + "CPU_PER_CALL": true, + "CPU_PER_SESSION": true, + "CREATE": true, + "CURRENT": true, + "CURRENT_SCHEMA": true, + "CURREN_USER": true, + "CURSOR": true, + "CYCLE": true, + "DANGLING": true, + "DATABASE": true, + "DATAFILE": true, + "DATAFILES": true, + "DATAOBJNO": true, + "DATE": true, + "DBA": true, + "DBHIGH": true, + "DBLOW": true, + "DBMAC": true, + "DEALLOCATE": true, + "DEBUG": true, + "DEC": true, + "DECIMAL": true, + "DECLARE": true, + "DEFAULT": true, + "DEFERRABLE": true, + "DEFERRED": true, + "DEGREE": true, + "DELETE": true, + "DEREF": true, + "DESC": true, + "DIRECTORY": true, + "DISABLE": true, + "DISCONNECT": true, + "DISMOUNT": true, + "DISTINCT": true, + "DISTRIBUTED": true, + "DML": true, + "DOUBLE": true, + "DROP": true, + "DUMP": true, + "EACH": true, + "ELSE": true, + "ENABLE": true, + "END": true, + "ENFORCE": true, + "ENTRY": true, + "ESCAPE": true, + "EXCEPT": true, + "EXCEPTIONS": true, + "EXCHANGE": true, + "EXCLUDING": true, + "EXCLUSIVE": true, + "EXECUTE": true, + "EXISTS": true, + "EXPIRE": true, + "EXPLAIN": true, + "EXTENT": true, + "EXTENTS": true, + "EXTERNALLY": true, + "FAILED_LOGIN_ATTEMPTS": true, + "FALSE": true, + "FAST": true, + "FILE": true, + "FIRST_ROWS": true, + "FLAGGER": true, + "FLOAT": true, + "FLOB": true, + "FLUSH": true, + "FOR": true, + "FORCE": true, + "FOREIGN": true, + "FREELIST": true, + "FREELISTS": true, + "FROM": true, + "FULL": true, + "FUNCTION": true, + "GLOBAL": true, + "GLOBALLY": true, + "GLOBAL_NAME": true, + "GRANT": true, + "GROUP": true, + "GROUPS": true, + "HASH": true, + "HASHKEYS": true, + "HAVING": true, + "HEADER": true, + "HEAP": true, + "IDENTIFIED": true, + "IDGENERATORS": true, + "IDLE_TIME": true, + "IF": true, + "IMMEDIATE": true, + "IN": true, + "INCLUDING": true, + "INCREMENT": true, + "INDEX": true, + "INDEXED": true, + "INDEXES": true, + "INDICATOR": true, + "IND_PARTITION": true, + "INITIAL": true, + "INITIALLY": true, + "INITRANS": true, + "INSERT": true, + "INSTANCE": true, + "INSTANCES": true, + "INSTEAD": true, + "INT": true, + "INTEGER": true, + "INTERMEDIATE": true, + "INTERSECT": true, + "INTO": true, + "IS": true, + "ISOLATION": true, + "ISOLATION_LEVEL": true, + "KEEP": true, + "KEY": true, + "KILL": true, + "LABEL": true, + "LAYER": true, + "LESS": true, + "LEVEL": true, + "LIBRARY": true, + "LIKE": true, + "LIMIT": true, + "LINK": true, + "LIST": true, + "LOB": true, + "LOCAL": true, + "LOCK": true, + "LOCKED": true, + "LOG": true, + "LOGFILE": true, + "LOGGING": true, + "LOGICAL_READS_PER_CALL": true, + "LOGICAL_READS_PER_SESSION": true, + "LONG": true, + "MANAGE": true, + "MASTER": true, + "MAX": true, + "MAXARCHLOGS": true, + "MAXDATAFILES": true, + "MAXEXTENTS": true, + "MAXINSTANCES": true, + "MAXLOGFILES": true, + "MAXLOGHISTORY": true, + "MAXLOGMEMBERS": true, + "MAXSIZE": true, + "MAXTRANS": true, + "MAXVALUE": true, + "MIN": true, + "MEMBER": true, + "MINIMUM": true, + "MINEXTENTS": true, + "MINUS": true, + "MINVALUE": true, + "MLSLABEL": true, + "MLS_LABEL_FORMAT": true, + "MODE": true, + "MODIFY": true, + "MOUNT": true, + "MOVE": true, + "MTS_DISPATCHERS": true, + "MULTISET": true, + "NATIONAL": true, + "NCHAR": true, + "NCHAR_CS": true, + "NCLOB": true, + "NEEDED": true, + "NESTED": true, + "NETWORK": true, + "NEW": true, + "NEXT": true, + "NOARCHIVELOG": true, + "NOAUDIT": true, + "NOCACHE": true, + "NOCOMPRESS": true, + "NOCYCLE": true, + "NOFORCE": true, + "NOLOGGING": true, + "NOMAXVALUE": true, + "NOMINVALUE": true, + "NONE": true, + "NOORDER": true, + "NOOVERRIDE": true, + "NOPARALLEL": true, + "NOREVERSE": true, + "NORMAL": true, + "NOSORT": true, + "NOT": true, + "NOTHING": true, + "NOWAIT": true, + "NULL": true, + "NUMBER": true, + "NUMERIC": true, + "NVARCHAR2": true, + "OBJECT": true, + "OBJNO": true, + "OBJNO_REUSE": true, + "OF": true, + "OFF": true, + "OFFLINE": true, + "OID": true, + "OIDINDEX": true, + "OLD": true, + "ON": true, + "ONLINE": true, + "ONLY": true, + "OPCODE": true, + "OPEN": true, + "OPTIMAL": true, + "OPTIMIZER_GOAL": true, + "OPTION": true, + "OR": true, + "ORDER": true, + "ORGANIZATION": true, + "OSLABEL": true, + "OVERFLOW": true, + "OWN": true, + "PACKAGE": true, + "PARALLEL": true, + "PARTITION": true, + "PASSWORD": true, + "PASSWORD_GRACE_TIME": true, + "PASSWORD_LIFE_TIME": true, + "PASSWORD_LOCK_TIME": true, + "PASSWORD_REUSE_MAX": true, + "PASSWORD_REUSE_TIME": true, + "PASSWORD_VERIFY_FUNCTION": true, + "PCTFREE": true, + "PCTINCREASE": true, + "PCTTHRESHOLD": true, + "PCTUSED": true, + "PCTVERSION": true, + "PERCENT": true, + "PERMANENT": true, + "PLAN": true, + "PLSQL_DEBUG": true, + "POST_TRANSACTION": true, + "PRECISION": true, + "PRESERVE": true, + "PRIMARY": true, + "PRIOR": true, + "PRIVATE": true, + "PRIVATE_SGA": true, + "PRIVILEGE": true, + "PRIVILEGES": true, + "PROCEDURE": true, + "PROFILE": true, + "PUBLIC": true, + "PURGE": true, + "QUEUE": true, + "QUOTA": true, + "RANGE": true, + "RAW": true, + "RBA": true, + "READ": true, + "READUP": true, + "REAL": true, + "REBUILD": true, + "RECOVER": true, + "RECOVERABLE": true, + "RECOVERY": true, + "REF": true, + "REFERENCES": true, + "REFERENCING": true, + "REFRESH": true, + "RENAME": true, + "REPLACE": true, + "RESET": true, + "RESETLOGS": true, + "RESIZE": true, + "RESOURCE": true, + "RESTRICTED": true, + "RETURN": true, + "RETURNING": true, + "REUSE": true, + "REVERSE": true, + "REVOKE": true, + "ROLE": true, + "ROLES": true, + "ROLLBACK": true, + "ROW": true, + "ROWID": true, + "ROWNUM": true, + "ROWS": true, + "RULE": true, + "SAMPLE": true, + "SAVEPOINT": true, + "SB4": true, + "SCAN_INSTANCES": true, + "SCHEMA": true, + "SCN": true, + "SCOPE": true, + "SD_ALL": true, + "SD_INHIBIT": true, + "SD_SHOW": true, + "SEGMENT": true, + "SEG_BLOCK": true, + "SEG_FILE": true, + "SELECT": true, + "SEQUENCE": true, + "SERIALIZABLE": true, + "SESSION": true, + "SESSION_CACHED_CURSORS": true, + "SESSIONS_PER_USER": true, + "SET": true, + "SHARE": true, + "SHARED": true, + "SHARED_POOL": true, + "SHRINK": true, + "SIZE": true, + "SKIP": true, + "SKIP_UNUSABLE_INDEXES": true, + "SMALLINT": true, + "SNAPSHOT": true, + "SOME": true, + "SORT": true, + "SPECIFICATION": true, + "SPLIT": true, + "SQL_TRACE": true, + "STANDBY": true, + "START": true, + "STATEMENT_ID": true, + "STATISTICS": true, + "STOP": true, + "STORAGE": true, + "STORE": true, + "STRUCTURE": true, + "SUCCESSFUL": true, + "SWITCH": true, + "SYS_OP_ENFORCE_NOT_NULL$": true, + "SYS_OP_NTCIMG$": true, + "SYNONYM": true, + "SYSDATE": true, + "SYSDBA": true, + "SYSOPER": true, + "SYSTEM": true, + "TABLE": true, + "TABLES": true, + "TABLESPACE": true, + "TABLESPACE_NO": true, + "TABNO": true, + "TEMPORARY": true, + "THAN": true, + "THE": true, + "THEN": true, + "THREAD": true, + "TIMESTAMP": true, + "TIME": true, + "TO": true, + "TOPLEVEL": true, + "TRACE": true, + "TRACING": true, + "TRANSACTION": true, + "TRANSITIONAL": true, + "TRIGGER": true, + "TRIGGERS": true, + "TRUE": true, + "TRUNCATE": true, + "TX": true, + "TYPE": true, + "UB2": true, + "UBA": true, + "UID": true, + "UNARCHIVED": true, + "UNDO": true, + "UNION": true, + "UNIQUE": true, + "UNLIMITED": true, + "UNLOCK": true, + "UNRECOVERABLE": true, + "UNTIL": true, + "UNUSABLE": true, + "UNUSED": true, + "UPDATABLE": true, + "UPDATE": true, + "USAGE": true, + "USE": true, + "USER": true, + "USING": true, + "VALIDATE": true, + "VALIDATION": true, + "VALUE": true, + "VALUES": true, + "VARCHAR": true, + "VARCHAR2": true, + "VARYING": true, + "VIEW": true, + "WHEN": true, + "WHENEVER": true, + "WHERE": true, + "WITH": true, + "WITHOUT": true, + "WORK": true, + "WRITE": true, + "WRITEDOWN": true, + "WRITEUP": true, + "XID": true, + "YEAR": true, + "ZONE": true, + } +) + type oracle struct { core.Base } @@ -56,6 +540,15 @@ func (db *oracle) SupportInsertMany() bool { return true } +func (db *oracle) IsReserved(name string) bool { + _, ok := oracleReservedWords[name] + return ok +} + +func (db *oracle) Quote(name string) string { + return "\"" + name + "\"" +} + func (db *oracle) QuoteStr() string { return "\"" } diff --git a/postgres_dialect.go b/postgres_dialect.go index fcdd1fd1..5acd0614 100644 --- a/postgres_dialect.go +++ b/postgres_dialect.go @@ -12,6 +12,9 @@ import ( // func init() { // RegisterDialect("postgres", &postgres{}) // } +var ( + postgresReservedWords = map[string]bool{} +) type postgres struct { core.Base @@ -73,6 +76,15 @@ func (db *postgres) SupportInsertMany() bool { return true } +func (db *postgres) IsReserved(name string) bool { + _, ok := postgresReservedWords[name] + return ok +} + +func (db *postgres) Quote(name string) string { + return "\"" + name + "\"" +} + func (db *postgres) QuoteStr() string { return "\"" } diff --git a/sqlite3_dialect.go b/sqlite3_dialect.go index e8d6cf2c..0b9c4298 100644 --- a/sqlite3_dialect.go +++ b/sqlite3_dialect.go @@ -12,6 +12,135 @@ import ( // RegisterDialect("sqlite3", &sqlite3{}) // } +var ( + sqlite3ReservedWords = map[string]bool{ + "ABORT": true, + "ACTION": true, + "ADD": true, + "AFTER": true, + "ALL": true, + "ALTER": true, + "ANALYZE": true, + "AND": true, + "AS": true, + "ASC": true, + "ATTACH": true, + "AUTOINCREMENT": true, + "BEFORE": true, + "BEGIN": true, + "BETWEEN": true, + "BY": true, + "CASCADE": true, + "CASE": true, + "CAST": true, + "CHECK": true, + "COLLATE": true, + "COLUMN": true, + "COMMIT": true, + "CONFLICT": true, + "CONSTRAINT": true, + "CREATE": true, + "CROSS": true, + "CURRENT_DATE": true, + "CURRENT_TIME": true, + "CURRENT_TIMESTAMP": true, + "DATABASE": true, + "DEFAULT": true, + "DEFERRABLE": true, + "DEFERRED": true, + "DELETE": true, + "DESC": true, + "DETACH": true, + "DISTINCT": true, + "DROP": true, + "EACH": true, + "ELSE": true, + "END": true, + "ESCAPE": true, + "EXCEPT": true, + "EXCLUSIVE": true, + "EXISTS": true, + "EXPLAIN": true, + "FAIL": true, + "FOR": true, + "FOREIGN": true, + "FROM": true, + "FULL": true, + "GLOB": true, + "GROUP": true, + "HAVING": true, + "IF": true, + "IGNORE": true, + "IMMEDIATE": true, + "IN": true, + "INDEX": true, + "INDEXED": true, + "INITIALLY": true, + "INNER": true, + "INSERT": true, + "INSTEAD": true, + "INTERSECT": true, + "INTO": true, + "IS": true, + "ISNULL": true, + "JOIN": true, + "KEY": true, + "LEFT": true, + "LIKE": true, + "LIMIT": true, + "MATCH": true, + "NATURAL": true, + "NO": true, + "NOT": true, + "NOTNULL": true, + "NULL": true, + "OF": true, + "OFFSET": true, + "ON": true, + "OR": true, + "ORDER": true, + "OUTER": true, + "PLAN": true, + "PRAGMA": true, + "PRIMARY": true, + "QUERY": true, + "RAISE": true, + "RECURSIVE": true, + "REFERENCES": true, + "REGEXP": true, + "REINDEX": true, + "RELEASE": true, + "RENAME": true, + "REPLACE": true, + "RESTRICT": true, + "RIGHT": true, + "ROLLBACK": true, + "ROW": true, + "SAVEPOINT": true, + "SELECT": true, + "SET": true, + "TABLE": true, + "TEMP": true, + "TEMPORARY": true, + "THEN": true, + "TO": true, + "TRANSACTI": true, + "TRIGGER": true, + "UNION": true, + "UNIQUE": true, + "UPDATE": true, + "USING": true, + "VACUUM": true, + "VALUES": true, + "VIEW": true, + "VIRTUAL": true, + "WHEN": true, + "WHERE": true, + "WITH": true, + "WITHOUT": true, + } +) + type sqlite3 struct { core.Base } @@ -54,6 +183,15 @@ func (db *sqlite3) SupportInsertMany() bool { return true } +func (db *sqlite3) IsReserved(name string) bool { + _, ok := sqlite3ReservedWords[name] + return ok +} + +func (db *sqlite3) Quote(name string) string { + return "`" + name + "`" +} + func (db *sqlite3) QuoteStr() string { return "`" } From 954c413725279d0103e8690ab8d9a008d7f1ec51 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 7 Sep 2014 08:32:58 +0800 Subject: [PATCH 12/15] add postgres reserved words --- postgres_dialect.go | 318 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 317 insertions(+), 1 deletion(-) diff --git a/postgres_dialect.go b/postgres_dialect.go index 5acd0614..4f2d193a 100644 --- a/postgres_dialect.go +++ b/postgres_dialect.go @@ -13,7 +13,323 @@ import ( // RegisterDialect("postgres", &postgres{}) // } var ( - postgresReservedWords = map[string]bool{} + postgresReservedWords = map[string]bool{ + "A": true, + "ABORT": true, + "ABS": true, + "ABSENT": true, + "ABSOLUTE": true, + "ACCESS": true, + "ACCORDING": true, + "ACTION": true, + "ADA": true, + "ADD": true, + "ADMIN": true, + "AFTER": true, + "AGGREGATE": true, + "ALL": true, + "ALLOCATE": true, + "ALSO": true, + "ALTER": true, + "ALWAYS": true, + "ANALYSE": true, + "ANALYZE": true, + "AND": true, + "ANY": true, + "ARE": true, + "ARRAY": true, + "ARRAY_AGG": true, + "ARRAY_MAX_CARDINALITY": true, + "AS": true, + "ASC": true, + "ASENSITIVE": true, + "ASSERTION": true, + "ASSIGNMENT": true, + "ASYMMETRIC": true, + "AT": true, + "ATOMIC": true, + "ATTRIBUTE": true, + "ATTRIBUTES": true, + "AUTHORIZATION": true, + "AVG": true, + "BACKWARD": true, + "BASE64": true, + "BEFORE": true, + "BEGIN": true, + "BEGIN_FRAME": true, + "BEGIN_PARTITION": true, + "BERNOULLI": true, + "BETWEEN": true, + "BIGINT": true, + "BINARY": true, + "BIT": true, + "BIT_LENGTH": true, + "BLOB": true, + "BLOCKED": true, + "BOM": true, + "BOOLEAN": true, + "BOTH": true, + "BREADTH": true, + "BY": true, + "C": true, + "CACHE": true, + "CALL": true, + "CALLED": true, + "CARDINALITY": true, + "CASCADE": true, + "CASCADED": true, + "CASE": true, + "CAST": true, + "CATALOG": true, + "CATALOG_NAME": true, + "CEIL": true, + "CEILING": true, + "CHAIN": true, + "CHAR": true, + "CHARACTER": true, + "CHARACTERISTICS": true, + "CHARACTERS": true, + "CHARACTER_LENGTH": true, + "CHARACTER_SET_CATALOG": true, + "CHARACTER_SET_NAME": true, + "CHARACTER_SET_SCHEMA": true, + "CHAR_LENGTH": true, + "CHECK": true, + "CHECKPOINT": true, + "CLASS": true, + "CLASS_ORIGIN": true, + "CLOB": true, + "CLOSE": true, + "CLUSTER": true, + "COALESCE": true, + "COBOL": true, + "COLLATE": true, + "COLLATION": true, + "COLLATION_CATALOG": true, + "COLLATION_NAME": true, + "COLLATION_SCHEMA": true, + "COLLECT": true, + "COLUMN": true, + "COLUMNS": true, + "COLUMN_NAME": true, + "COMMAND_FUNCTION": true, + "COMMAND_FUNCTION_CODE": true, + "COMMENT": true, + "COMMENTS": true, + "COMMIT": true, + "COMMITTED": true, + "CONCURRENTLY": true, + "CONDITION": true, + "CONDITION_NUMBER": true, + "CONFIGURATION": true, + "CONNECT": true, + "CONNECTION": true, + "CONNECTION_NAME": true, + "CONSTRAINT": true, + "CONSTRAINTS": true, + "CONSTRAINT_CATALOG": true, + "CONSTRAINT_NAME": true, + "CONSTRAINT_SCHEMA": true, + "CONSTRUCTOR": true, + "CONTAINS": true, + "CONTENT": true, + "CONTINUE": true, + "CONTROL": true, + "CONVERSION": true, + "CONVERT": true, + "COPY": true, + "CORR": true, + "CORRESPONDING": true, + "COST": true, + "COUNT": true, + "COVAR_POP": true, + "COVAR_SAMP": true, + "CREATE": true, + "CROSS": true, + "CSV": true, + "CUBE": true, + "CUME_DIST": true, + "CURRENT": true, + "CURRENT_CATALOG": true, + "CURRENT_DATE": true, + "CURRENT_DEFAULT_TRANSFORM_GROUP": true, + "CURRENT_PATH": true, + "CURRENT_ROLE": true, + "CURRENT_ROW": true, + "CURRENT_SCHEMA": true, + "CURRENT_TIME": true, + "CURRENT_TIMESTAMP": true, + "CURRENT_TRANSFORM_GROUP_FOR_TYPE": true, + "CURRENT_USER": true, + "CURSOR": true, + "CURSOR_NAME": true, + "CYCLE": true, + "DATA": true, + "DATABASE": true, + "DATALINK": true, + "DATE": true, + "DATETIME_INTERVAL_CODE": true, + "DATETIME_INTERVAL_PRECISION": true, + "DAY": true, + "DB": true, + "DEALLOCATE": true, + "DEC": true, + "DECIMAL": true, + "DECLARE": true, + "DEFAULT": true, + "DEFAULTS": true, + "DEFERRABLE": true, + "DEFERRED": true, + "DEFINED": true, + "DEFINER": true, + "DEGREE": true, + "DELETE": true, + "DELIMITER": true, + "DELIMITERS": true, + "DENSE_RANK": true, + "DEPTH": true, + "DEREF": true, + "DERIVED": true, + "DESC": true, + "DESCRIBE": true, + "DESCRIPTOR": true, + "DETERMINISTIC": true, + "DIAGNOSTICS": true, + "DICTIONARY": true, + "DISABLE": true, + "DISCARD": true, + "DISCONNECT": true, + "DISPATCH": true, + "DISTINCT": true, + "DLNEWCOPY": true, + "DLPREVIOUSCOPY": true, + "DLURLCOMPLETE": true, + "DLURLCOMPLETEONLY": true, + "DLURLCOMPLETEWRITE": true, + "DLURLPATH": true, + "DLURLPATHONLY": true, + "DLURLPATHWRITE": true, + "DLURLSCHEME": true, + "DLURLSERVER": true, + "DLVALUE": true, + "DO": true, + "DOCUMENT": true, + "DOMAIN": true, + "DOUBLE": true, + "DROP": true, + "DYNAMIC": true, + "DYNAMIC_FUNCTION": true, + "DYNAMIC_FUNCTION_CODE": true, + "EACH": true, + "ELEMENT": true, + "ELSE": true, + "EMPTY": true, + "ENABLE": true, + "ENCODING": true, + "ENCRYPTED": true, + "END": true, + "END-EXEC": true, + "END_FRAME": true, + "END_PARTITION": true, + "ENFORCED": true, + "ENUM": true, + "EQUALS": true, + "ESCAPE": true, + "EVENT": true, + "EVERY": true, + "EXCEPT": true, + "EXCEPTION": true, + "EXCLUDE": true, + "EXCLUDING": true, + "EXCLUSIVE": true, + "EXEC": true, + "EXECUTE": true, + "EXISTS": true, + "EXP": true, + "EXPLAIN": true, + "EXPRESSION": true, + "EXTENSION": true, + "EXTERNAL": true, + "EXTRACT": true, + "FALSE": true, + "FAMILY": true, + "FETCH": true, + "FILE": true, + "FILTER": true, + "FINAL": true, + "FIRST": true, + "FIRST_VALUE": true, + "FLAG": true, + "FLOAT": true, + "FLOOR": true, + "FOLLOWING": true, + "FOR": true, + "FORCE": true, + "FOREIGN": true, + "FORTRAN": true, + "FORWARD": true, + "FOUND": true, + "FRAME_ROW": true, + "FREE": true, + "FREEZE": true, + "FROM": true, + "FS": true, + "FULL": true, + "FUNCTION": true, + "FUNCTIONS": true, + "FUSION": true, + "G": true, + "GENERAL": true, + "GENERATED": true, + "GET": true, + "GLOBAL": true, + "GO": true, + "GOTO": true, + "GRANT": true, + "GRANTED": true, + "GREATEST": true, + "GROUP": true, + "GROUPING": true, + "GROUPS": true, + "HANDLER": true, + "HAVING": true, + "HEADER": true, + "HEX": true, + "HIERARCHY": true, + "HOLD": true, + "HOUR": true, + "ID": true, + "IDENTITY": true, + "IF": true, + "IGNORE": true, + "ILIKE": true, + "IMMEDIATE": true, + "IMMEDIATELY": true, + "IMMUTABLE": true, + "IMPLEMENTATION": true, + "IMPLICIT": true, + "IMPORT": true, + "IN": true, + "INCLUDING": true, + "INCREMENT": true, + "INDENT": true, + "INDEX": true, + "INDEXES": true, + "INDICATOR": true, + "INHERIT": true, + "INHERITS": true, + "INITIALLY": true, + "INLINE": true, + "INNER": true, + "INOUT": true, + "INPUT": true, + "INSENSITIVE": true, + "INSERT": true, + "INSTANCE": true, + "INSTANTIABLE": true, + "INSTEAD": true, + "INT": true, + } ) type postgres struct { From e09d89acb34fafb29db2bc1a83202e4f6c7342a3 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 7 Sep 2014 09:18:54 +0800 Subject: [PATCH 13/15] bug fixed #152 --- statement.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/statement.go b/statement.go index 14645f58..9094492e 100644 --- a/statement.go +++ b/statement.go @@ -905,9 +905,11 @@ func (statement *Statement) Asc(colNames ...string) *Statement { //The join_operator should be one of INNER, LEFT OUTER, CROSS etc - this will be prepended to JOIN func (statement *Statement) Join(join_operator, tablename, condition string) *Statement { if statement.JoinStr != "" { - statement.JoinStr = statement.JoinStr + fmt.Sprintf(" %v JOIN %v ON %v", join_operator, tablename, condition) + statement.JoinStr = statement.JoinStr + fmt.Sprintf(" %v JOIN %v ON %v", join_operator, + statement.Engine.Quote(tablename), condition) } else { - statement.JoinStr = fmt.Sprintf("%v JOIN %v ON %v", join_operator, tablename, condition) + statement.JoinStr = fmt.Sprintf("%v JOIN %v ON %v", join_operator, + statement.Engine.Quote(tablename), condition) } return statement } From f2c6aab707fcc34aeb1eb11281b9426ef7c026d8 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 7 Sep 2014 11:50:11 +0800 Subject: [PATCH 14/15] improved doc --- doc.go | 48 ++++++++++++++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/doc.go b/doc.go index b183e8d2..4980a372 100644 --- a/doc.go +++ b/doc.go @@ -19,17 +19,17 @@ Firstly, we should new an engine for a database Method NewEngine's parameters is the same as sql.Open. It depends drivers' implementation. -Generally, one engine is enough. You can set it as package variable. +Generally, one engine for an application is enough. You can set it as package variable. Raw Methods Xorm also support raw sql execution: -1. query sql, the returned results is []map[string][]byte +1. query a SQL string, the returned results is []map[string][]byte results, err := engine.Query("select * from user") -2. exec sql, the returned results +2. execute a SQL string, the returned results affected, err := engine.Exec("update user set .... where ...") @@ -57,29 +57,31 @@ There are 7 major ORM methods and many helpful methods to use to operate databas 3. Query multiple records from database - err := engine.Find(...) + sliceOfStructs := new(Struct) + err := engine.Find(sliceOfStructs) // SELECT * FROM user 4. Query multiple records and record by record handle, there two methods, one is Iterate, another is Raws - raws, err := engine.Raws(...) - // SELECT * FROM user - for raws.Next() { - raws.Scan(bean) - } - err := engine.Iterate(...) // SELECT * FROM user + raws, err := engine.Raws(...) + // SELECT * FROM user + bean := new(Struct) + for raws.Next() { + err = raws.Scan(bean) + } + 5. Update one or more records affected, err := engine.Update(&user) - // UPDATE user SET + // UPDATE user SET ... -6. Delete one or more records +6. Delete one or more records, Delete MUST has conditon - affected, err := engine.Delete(&user) + affected, err := engine.Where(...).Delete(&user) // DELETE FROM user Where ... 7. Count records @@ -89,14 +91,19 @@ another is Raws Conditions -The above 7 methods could use with condition methods. +The above 7 methods could use with condition methods chainable. +Attention: the above 7 methods should be the last chainable method. 1. Id, In - engine.Id(1).Get(&user) + engine.Id(1).Get(&user) // for single primary key // SELECT * FROM user WHERE id = 1 + engine.Id(core.PK{1, 2}).Get(&user) // for composite primary keys + // SELECT * FROM user WHERE id1 = 1 AND id2 = 2 engine.In("id", 1, 2, 3).Find(&users) // SELECT * FROM user WHERE id IN (1, 2, 3) + engine.In("id", []int{1, 2, 3}) + // SELECT * FROM user WHERE id IN (1, 2, 3) 2. Where, And, Or @@ -114,10 +121,11 @@ The above 7 methods could use with condition methods. engine.Limit().Find() // SELECT * FROM user LIMIT .. OFFSET .. - engine.Top().Find() - // SELECT * FROM user LIMIT .. + engine.Top(5).Find() + // SELECT TOP 5 * FROM user // for mssql + // SELECT * FROM user LIMIT .. OFFSET 0 //for other databases -5. Sql +5. Sql, let you custom SQL engine.Sql("select * from user").Find() @@ -125,8 +133,12 @@ The above 7 methods could use with condition methods. engine.Cols("col1, col2").Find() // SELECT col1, col2 FROM user + engine.Cols("col1", "col2").Where().Update(user) + // UPDATE user set col1 = ?, col2 = ? Where ... engine.Omit("col1").Find() // SELECT col2, col3 FROM user + engine.Omit("col1").Insert() + // INSERT INTO table (non-col1) VALUES () engine.Distinct("col1").Find() // SELECT DISTINCT col1 FROM user From 6b7fc81941222435b188437f6969f1417022badb Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 8 Sep 2014 11:09:42 +0800 Subject: [PATCH 15/15] add auto-migrate from varchar to text for postgres --- engine.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/engine.go b/engine.go index 3c49d947..08b17872 100644 --- a/engine.go +++ b/engine.go @@ -1191,16 +1191,19 @@ func (engine *Engine) Sync2(beans ...interface{}) error { curType := engine.dialect.SqlType(oriCol) if expectedType != curType { if expectedType == core.Text && - curType == core.Varchar { + strings.HasPrefix(curType, core.Varchar) { // currently only support mysql - if engine.dialect.DBType() == core.MYSQL { + if engine.dialect.DBType() == core.MYSQL || + engine.dialect.DBType() == core.POSTGRES { + engine.LogInfof("Table %s column %s change type from %s to %s\n", + table.Name, col.Name, curType, expectedType) _, err = engine.Exec(engine.dialect.ModifyColumnSql(table.Name, col)) } else { - engine.LogWarnf("Table %s Column %s db type is %s, struct type is %s\n", + engine.LogWarnf("Table %s column %s db type is %s, struct type is %s\n", table.Name, col.Name, curType, expectedType) } } else { - engine.LogWarnf("Table %s Column %s db type is %s, struct type is %s", + engine.LogWarnf("Table %s column %s db type is %s, struct type is %s", table.Name, col.Name, curType, expectedType) } }