From f4f07ec079a7151eff4930d05320da95acb1b376 Mon Sep 17 00:00:00 2001 From: Nash Tsai Date: Sat, 17 May 2014 00:39:30 +0800 Subject: [PATCH 01/10] #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/10] 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/10] 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/10] 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/10] 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/10] 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 05d01bf16592731f963d058bd11e260010e37399 Mon Sep 17 00:00:00 2001 From: Nash Tsai Date: Thu, 28 Aug 2014 23:13:41 +0800 Subject: [PATCH 07/10] add TODO comments --- session.go | 1 + 1 file changed, 1 insertion(+) diff --git a/session.go b/session.go index 009a758a..91037457 100644 --- a/session.go +++ b/session.go @@ -651,6 +651,7 @@ func (session *Session) cacheGet(bean interface{}, sqlStr string, args ...interf if v, ok := data[session.Statement.RefTable.PrimaryKeys[0]]; !ok { return false, ErrCacheFailed } else { + // TODO https://github.com/go-xorm/xorm/issues/144, PK may not always be int64 id, err = strconv.ParseInt(string(v), 10, 64) if err != nil { return false, err From c8fdc8ec21cce606a38efc2646c74bef579812c8 Mon Sep 17 00:00:00 2001 From: Nash Tsai Date: Fri, 29 Aug 2014 00:06:15 +0800 Subject: [PATCH 08/10] 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 09/10] 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 2f4fa912b99f78b330ca277b8c27f4872289d208 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=95=86=E8=AE=AF=E5=9C=A8=E7=BA=BF?= Date: Sat, 30 Aug 2014 22:17:59 +0800 Subject: [PATCH 10/10] improved --- engine.go | 2 +- helpers.go | 48 ++++++++++++++++++++------- mysql_dialect.go | 1 + session.go | 84 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 123 insertions(+), 12 deletions(-) diff --git a/engine.go b/engine.go index 08a4bd66..dfe146d4 100644 --- a/engine.go +++ b/engine.go @@ -330,7 +330,7 @@ func (engine *Engine) DumpAll(w io.Writer) error { } for _, table := range tables { - _, err = io.WriteString(w, engine.dialect.CreateTableSql(table, "", "", "")+"\n\n") + _, err = io.WriteString(w, engine.dialect.CreateTableSql(table, "", table.StoreEngine, "")+"\n\n") if err != nil { return err } diff --git a/helpers.go b/helpers.go index 71b29074..4d20141c 100644 --- a/helpers.go +++ b/helpers.go @@ -70,28 +70,23 @@ func sliceEq(left, right []string) bool { return true } -func value2Bytes(rawValue *reflect.Value) (data []byte, err error) { +func reflect2value(rawValue *reflect.Value) (str string, err error) { aa := reflect.TypeOf((*rawValue).Interface()) vv := reflect.ValueOf((*rawValue).Interface()) - - var str string switch aa.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: str = strconv.FormatInt(vv.Int(), 10) - data = []byte(str) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: str = strconv.FormatUint(vv.Uint(), 10) - data = []byte(str) case reflect.Float32, reflect.Float64: str = strconv.FormatFloat(vv.Float(), 'f', -1, 64) - data = []byte(str) case reflect.String: str = vv.String() - data = []byte(str) case reflect.Array, reflect.Slice: switch aa.Elem().Kind() { case reflect.Uint8: - data = rawValue.Interface().([]byte) + data := rawValue.Interface().([]byte) + str = string(data) default: err = fmt.Errorf("Unsupported struct type %v", vv.Type().Name()) } @@ -99,16 +94,13 @@ func value2Bytes(rawValue *reflect.Value) (data []byte, err error) { case reflect.Struct: if aa == core.TimeType { str = rawValue.Interface().(time.Time).Format(time.RFC3339Nano) - data = []byte(str) } else { err = fmt.Errorf("Unsupported struct type %v", vv.Type().Name()) } case reflect.Bool: str = strconv.FormatBool(vv.Bool()) - data = []byte(str) case reflect.Complex128, reflect.Complex64: str = fmt.Sprintf("%v", vv.Complex()) - data = []byte(str) /* TODO: unsupported types below case reflect.Map: case reflect.Ptr: @@ -122,6 +114,40 @@ func value2Bytes(rawValue *reflect.Value) (data []byte, err error) { return } +func value2Bytes(rawValue *reflect.Value) (data []byte, err error) { + var str string + str, err = reflect2value(rawValue) + if err != nil { + return + } + data = []byte(str) + return +} + +func value2String(rawValue *reflect.Value) (data string, err error) { + data, err = reflect2value(rawValue) + if err != nil { + return + } + return +} + +func rows2Strings(rows *core.Rows) (resultsSlice []map[string]string, err error) { + fields, err := rows.Columns() + if err != nil { + return nil, err + } + for rows.Next() { + result, err := row2mapStr(rows, fields) + if err != nil { + return nil, err + } + resultsSlice = append(resultsSlice, result) + } + + return resultsSlice, nil +} + func rows2maps(rows *core.Rows) (resultsSlice []map[string][]byte, err error) { fields, err := rows.Columns() if err != nil { diff --git a/mysql_dialect.go b/mysql_dialect.go index ee1f4e06..40d699ce 100644 --- a/mysql_dialect.go +++ b/mysql_dialect.go @@ -264,6 +264,7 @@ func (db *mysql) GetTables() ([]*core.Table, error) { } table.Name = name + table.StoreEngine = engine tables = append(tables, table) } return tables, nil diff --git a/session.go b/session.go index 5d127c74..8c80c575 100644 --- a/session.go +++ b/session.go @@ -1393,6 +1393,34 @@ func (session *Session) dropAll() error { return nil } +func row2mapStr(rows *core.Rows, fields []string) (resultsMap map[string]string, err error) { + result := make(map[string]string) + scanResultContainers := make([]interface{}, len(fields)) + for i := 0; i < len(fields); i++ { + var scanResultContainer interface{} + scanResultContainers[i] = &scanResultContainer + } + if err := rows.Scan(scanResultContainers...); err != nil { + return nil, err + } + + for ii, key := range fields { + rawValue := reflect.Indirect(reflect.ValueOf(scanResultContainers[ii])) + //if row is null then ignore + if rawValue.Interface() == nil { + //fmt.Println("ignore ...", key, rawValue) + continue + } + + if data, err := value2String(&rawValue); err == nil { + result[key] = data + } else { + return nil, err // !nashtsai! REVIEW, should return err or just error log? + } + } + return result, nil +} + func row2map(rows *core.Rows, fields []string) (resultsMap map[string][]byte, err error) { result := make(map[string][]byte) scanResultContainers := make([]interface{}, len(fields)) @@ -1831,6 +1859,62 @@ func (session *Session) Query(sqlStr string, paramStr ...interface{}) (resultsSl return session.query(sqlStr, paramStr...) } + + + + +// ============================= +// for string +// ============================= +func (session *Session) query2(sqlStr string, paramStr ...interface{}) (resultsSlice []map[string]string, err error) { + session.queryPreprocess(&sqlStr, paramStr...) + + if session.IsAutoCommit { + return query2(session.Db, sqlStr, paramStr...) + } + return txQuery2(session.Tx, sqlStr, paramStr...) +} + +func txQuery2(tx *core.Tx, sqlStr string, params ...interface{}) (resultsSlice []map[string]string, err error) { + rows, err := tx.Query(sqlStr, params...) + if err != nil { + return nil, err + } + defer rows.Close() + + return rows2Strings(rows) +} + +func query2(db *core.DB, sqlStr string, params ...interface{}) (resultsSlice []map[string]string, err error) { + s, err := db.Prepare(sqlStr) + if err != nil { + return nil, err + } + defer s.Close() + rows, err := s.Query(params...) + if err != nil { + return nil, err + } + defer rows.Close() + return rows2Strings(rows) +} + +// Exec a raw sql and return records as []map[string]string +func (session *Session) Q(sqlStr string, paramStr ...interface{}) (resultsSlice []map[string]string, err error) { + err = session.newDb() + if err != nil { + return nil, err + } + defer session.resetStatement() + if session.IsAutoClose { + defer session.Close() + } + return session.query2(sqlStr, paramStr...) +} + + + + // insert one or more beans func (session *Session) Insert(beans ...interface{}) (int64, error) { var affected int64 = 0