From 7a9249de33244c72b3f437f31189b3cefc0a6ad4 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 25 Sep 2018 21:31:44 +0800 Subject: [PATCH] Get adds context cache feature (#1102) * context * add context cache feature * remove global context cache * remove global context cache * reset statment * fix bug * remove unused params * refactor ContextCache * refactor ContextCache * update README * update README * disable global cache on context cache test --- README.md | 52 +++++++++++++++++++++++++++++++++++ README_CN.md | 52 +++++++++++++++++++++++++++++++++++ context_cache.go | 30 ++++++++++++++++++++ session.go | 6 ++++ session_get.go | 24 +++++++++++++++- session_get_test.go | 67 +++++++++++++++++++++++++++++++++++++++++++++ statement.go | 2 ++ 7 files changed, 232 insertions(+), 1 deletion(-) create mode 100644 context_cache.go diff --git a/README.md b/README.md index 2443e4ef..6a57606e 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,8 @@ Xorm is a simple and powerful ORM for Go. * Postgres schema support +* Context Cache support + ## Drivers Support Drivers for Go's sql package which currently support database/sql includes: @@ -358,6 +360,56 @@ if _, err := session.Exec("delete from userinfo where username = ?", user2.Usern return session.Commit() ``` +* Or you can use `Transaction` to replace above codes. + +```Go +res, err := engine.Transaction(func(sess *xorm.Session) (interface{}, error) { + user1 := Userinfo{Username: "xiaoxiao", Departname: "dev", Alias: "lunny", Created: time.Now()} + if _, err := session.Insert(&user1); err != nil { + return nil, err + } + + user2 := Userinfo{Username: "yyy"} + if _, err := session.Where("id = ?", 2).Update(&user2); err != nil { + return nil, err + } + + if _, err := session.Exec("delete from userinfo where username = ?", user2.Username); err != nil { + return nil, err + } + return nil, nil +}) +``` + +* Context Cache, if enabled, current query result will be cached on session and be used by next same statement on the same session. + +```Go + sess := engine.NewSession() + defer sess.Close() + + var context = xorm.NewMemoryContextCache() + + var c2 ContextGetStruct + has, err := sess.ID(1).ContextCache(context).Get(&c2) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, 1, c2.Id) + assert.EqualValues(t, "1", c2.Name) + sql, args := sess.LastSQL() + assert.True(t, len(sql) > 0) + assert.True(t, len(args) > 0) + + var c3 ContextGetStruct + has, err = sess.ID(1).ContextCache(context).Get(&c3) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, 1, c3.Id) + assert.EqualValues(t, "1", c3.Name) + sql, args = sess.LastSQL() + assert.True(t, len(sql) == 0) + assert.True(t, len(args) == 0) +``` + ## Contributing If you want to pull request, please see [CONTRIBUTING](https://github.com/go-xorm/xorm/blob/master/CONTRIBUTING.md). And we also provide [Xorm on Google Groups](https://groups.google.com/forum/#!forum/xorm) to discuss. diff --git a/README_CN.md b/README_CN.md index 5a1a0c7e..baf14b85 100644 --- a/README_CN.md +++ b/README_CN.md @@ -32,6 +32,8 @@ xorm是一个简单而强大的Go语言ORM库. 通过它可以使数据库操作 * 内置SQL Builder支持 +* 上下文缓存支持 + ## 驱动支持 目前支持的Go数据库驱动和对应的数据库如下: @@ -360,6 +362,56 @@ if _, err := session.Exec("delete from userinfo where username = ?", user2.Usern return session.Commit() ``` +* 事物的简写方法 + +```Go +res, err := engine.Transaction(func(sess *xorm.Session) (interface{}, error) { + user1 := Userinfo{Username: "xiaoxiao", Departname: "dev", Alias: "lunny", Created: time.Now()} + if _, err := session.Insert(&user1); err != nil { + return nil, err + } + + user2 := Userinfo{Username: "yyy"} + if _, err := session.Where("id = ?", 2).Update(&user2); err != nil { + return nil, err + } + + if _, err := session.Exec("delete from userinfo where username = ?", user2.Username); err != nil { + return nil, err + } + return nil, nil +}) +``` + +* Context Cache, if enabled, current query result will be cached on session and be used by next same statement on the same session. + +```Go + sess := engine.NewSession() + defer sess.Close() + + var context = xorm.NewMemoryContextCache() + + var c2 ContextGetStruct + has, err := sess.ID(1).ContextCache(context).Get(&c2) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, 1, c2.Id) + assert.EqualValues(t, "1", c2.Name) + sql, args := sess.LastSQL() + assert.True(t, len(sql) > 0) + assert.True(t, len(args) > 0) + + var c3 ContextGetStruct + has, err = sess.ID(1).ContextCache(context).Get(&c3) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, 1, c3.Id) + assert.EqualValues(t, "1", c3.Name) + sql, args = sess.LastSQL() + assert.True(t, len(sql) == 0) + assert.True(t, len(args) == 0) +``` + ## 贡献 如果您也想为Xorm贡献您的力量,请查看 [CONTRIBUTING](https://github.com/go-xorm/xorm/blob/master/CONTRIBUTING.md)。您也可以加入QQ群 技术帮助和讨论。 diff --git a/context_cache.go b/context_cache.go new file mode 100644 index 00000000..1bc22884 --- /dev/null +++ b/context_cache.go @@ -0,0 +1,30 @@ +// Copyright 2018 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package xorm + +// ContextCache is the interface that operates the cache data. +type ContextCache interface { + // Put puts value into cache with key. + Put(key string, val interface{}) + // Get gets cached value by given key. + Get(key string) interface{} +} + +type memoryContextCache map[string]interface{} + +// NewMemoryContextCache return memoryContextCache +func NewMemoryContextCache() memoryContextCache { + return make(map[string]interface{}) +} + +// Put puts value into cache with key. +func (m memoryContextCache) Put(key string, val interface{}) { + m[key] = val +} + +// Get gets cached value by given key. +func (m memoryContextCache) Get(key string) interface{} { + return m[key] +} diff --git a/session.go b/session.go index 3775eb01..b4750891 100644 --- a/session.go +++ b/session.go @@ -102,6 +102,12 @@ func (session *Session) Close() { } } +// ContextCache enable context cache or not +func (session *Session) ContextCache(context ContextCache) *Session { + session.statement.context = context + return session +} + // IsClosed returns if session is closed func (session *Session) IsClosed() bool { return session.db == nil diff --git a/session_get.go b/session_get.go index 69194a23..887a0aeb 100644 --- a/session_get.go +++ b/session_get.go @@ -7,6 +7,7 @@ package xorm import ( "database/sql" "errors" + "fmt" "reflect" "strconv" @@ -66,7 +67,28 @@ func (session *Session) get(bean interface{}) (bool, error) { } } - return session.nocacheGet(beanValue.Elem().Kind(), table, bean, sqlStr, args...) + context := session.statement.context + if context != nil { + res := context.Get(fmt.Sprintf("%v-%v", sqlStr, args)) + if res != nil { + structValue := reflect.Indirect(reflect.ValueOf(bean)) + structValue.Set(reflect.Indirect(reflect.ValueOf(res))) + session.lastSQL = "" + session.lastSQLArgs = nil + return true, nil + } + } + + has, err := session.nocacheGet(beanValue.Elem().Kind(), table, bean, sqlStr, args...) + if err != nil || !has { + return has, err + } + + if context != nil { + context.Put(fmt.Sprintf("%v-%v", sqlStr, args), bean) + } + + return true, nil } func (session *Session) nocacheGet(beanKind reflect.Kind, table *core.Table, bean interface{}, sqlStr string, args ...interface{}) (bool, error) { diff --git a/session_get_test.go b/session_get_test.go index 4ec7cf02..02148df4 100644 --- a/session_get_test.go +++ b/session_get_test.go @@ -319,3 +319,70 @@ func TestGetStructId(t *testing.T) { assert.True(t, has) assert.EqualValues(t, 2, maxid.Id) } + +func TestContextGet(t *testing.T) { + type ContextGetStruct struct { + Id int64 + Name string + } + + assert.NoError(t, prepareEngine()) + assertSync(t, new(ContextGetStruct)) + + _, err := testEngine.Insert(&ContextGetStruct{Name: "1"}) + assert.NoError(t, err) + + sess := testEngine.NewSession() + defer sess.Close() + + context := NewMemoryContextCache() + + var c2 ContextGetStruct + has, err := sess.ID(1).NoCache().ContextCache(context).Get(&c2) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, 1, c2.Id) + assert.EqualValues(t, "1", c2.Name) + sql, args := sess.LastSQL() + assert.True(t, len(sql) > 0) + assert.True(t, len(args) > 0) + + var c3 ContextGetStruct + has, err = sess.ID(1).NoCache().ContextCache(context).Get(&c3) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, 1, c3.Id) + assert.EqualValues(t, "1", c3.Name) + sql, args = sess.LastSQL() + assert.True(t, len(sql) == 0) + assert.True(t, len(args) == 0) +} + +func TestContextGet2(t *testing.T) { + type ContextGetStruct2 struct { + Id int64 + Name string + } + + assert.NoError(t, prepareEngine()) + assertSync(t, new(ContextGetStruct2)) + + _, err := testEngine.Insert(&ContextGetStruct2{Name: "1"}) + assert.NoError(t, err) + + context := NewMemoryContextCache() + + var c2 ContextGetStruct2 + has, err := testEngine.ID(1).NoCache().ContextCache(context).Get(&c2) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, 1, c2.Id) + assert.EqualValues(t, "1", c2.Name) + + var c3 ContextGetStruct2 + has, err = testEngine.ID(1).NoCache().ContextCache(context).Get(&c3) + assert.NoError(t, err) + assert.True(t, has) + assert.EqualValues(t, 1, c3.Id) + assert.EqualValues(t, "1", c3.Name) +} diff --git a/statement.go b/statement.go index 56644036..a7f7010a 100644 --- a/statement.go +++ b/statement.go @@ -59,6 +59,7 @@ type Statement struct { exprColumns map[string]exprParam cond builder.Cond bufferSize int + context ContextCache } // Init reset all the statement's fields @@ -99,6 +100,7 @@ func (statement *Statement) Init() { statement.exprColumns = make(map[string]exprParam) statement.cond = builder.NewCond() statement.bufferSize = 0 + statement.context = nil } // NoAutoCondition if you do not want convert bean's field as query condition, then use this function