From d97ff5ee86a0bc7d2f75e850bb7acec785303b6e Mon Sep 17 00:00:00 2001 From: xiaoxiao Date: Wed, 19 Mar 2014 16:55:50 +0800 Subject: [PATCH 01/14] Init commit --- .gitignore | 22 ++++++++++++++++++++++ LICENSE | 27 +++++++++++++++++++++++++++ README.md | 4 ++++ 3 files changed, 53 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..6cd1df2b --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..3af16b07 --- /dev/null +++ b/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2014 +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the {organization} nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 00000000..523200ea --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +xorm +==== + +xorm \ No newline at end of file From 11aa2a973cba567f294c4516c30cc5852c9e3ff9 Mon Sep 17 00:00:00 2001 From: lunny Date: Thu, 20 Mar 2014 13:46:32 +0000 Subject: [PATCH 02/14] Init commit --- .gitignore | 22 ++++++++++++++++++++++ LICENSE | 27 +++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..6cd1df2b --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..3af16b07 --- /dev/null +++ b/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2014 +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the {organization} nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file From 8d5bd092d8d2f471daeff3f22cc39c210feb9851 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 23 Mar 2014 17:16:11 +0800 Subject: [PATCH 03/14] license --- LICENSE | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/LICENSE b/LICENSE index 3af16b07..9ac0c261 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2014 +Copyright (c) 2013 - 2014 All rights reserved. Redistribution and use in source and binary forms, with or without @@ -24,4 +24,4 @@ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. From 3aed2090a024c88472e7d5f5db97c38adfa564ea Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 24 Mar 2014 20:41:07 +0800 Subject: [PATCH 04/14] add AllCols for update all cols --- base_test.go | 47 +++++++++++++++++++++++++++++++++++++++++++++++ session.go | 17 +++++++++++++---- statement.go | 16 ++++++++++++---- 3 files changed, 72 insertions(+), 8 deletions(-) diff --git a/base_test.go b/base_test.go index 92eb6290..53d15bfc 100644 --- a/base_test.go +++ b/base_test.go @@ -370,6 +370,53 @@ func update(engine *Engine, t *testing.T) { panic(err) return } + + type UpdateAllCols struct { + Id int64 + Bool bool + String string + } + + col1 := &UpdateAllCols{} + err = engine.Sync(col1) + if err != nil { + t.Error(err) + panic(err) + } + + _, err = engine.Insert(col1) + if err != nil { + t.Error(err) + panic(err) + } + + col2 := &UpdateAllCols{col1.Id, true, ""} + _, err = engine.Id(col2.Id).AllCols().Update(col2) + if err != nil { + t.Error(err) + panic(err) + } + + col3 := &UpdateAllCols{} + has, err := engine.Id(col2.Id).Get(col3) + if err != nil { + t.Error(err) + panic(err) + } + + if !has { + err = errors.New(fmt.Sprintf("cannot get id %d", col2.Id)) + t.Error(err) + panic(err) + return + } + + if *col2 != *col3 { + err = errors.New(fmt.Sprintf("col2 should eq col3")) + t.Error(err) + panic(err) + return + } } func updateSameMapper(engine *Engine, t *testing.T) { diff --git a/session.go b/session.go index 825acc46..731027ce 100644 --- a/session.go +++ b/session.go @@ -126,6 +126,11 @@ func (session *Session) Cols(columns ...string) *Session { return session } +func (session *Session) AllCols() *Session { + session.Statement.AllCols() + return session +} + func (session *Session) NoCascade() *Session { session.Statement.UseCascade = false return session @@ -1023,7 +1028,8 @@ func (session *Session) Find(rowsSlicePtr interface{}, condiBean ...interface{}) if len(condiBean) > 0 { colNames, args := buildConditions(session.Engine, table, condiBean[0], true, true, - false, true, session.Statement.allUseBool, session.Statement.boolColumnMap) + false, true, session.Statement.allUseBool, session.Statement.useAllCols, + session.Statement.boolColumnMap) session.Statement.ConditionStr = strings.Join(colNames, " AND ") session.Statement.BeanArgs = args } @@ -2838,7 +2844,8 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 if session.Statement.ColumnStr == "" { colNames, args = buildConditions(session.Engine, table, bean, false, false, - false, false, session.Statement.allUseBool, session.Statement.boolColumnMap) + false, false, session.Statement.allUseBool, session.Statement.useAllCols, + session.Statement.boolColumnMap) } else { colNames, args, err = table.genCols(session, bean, true, true) if err != nil { @@ -2872,7 +2879,8 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 if len(condiBean) > 0 { condiColNames, condiArgs = buildConditions(session.Engine, session.Statement.RefTable, condiBean[0], true, true, - false, true, session.Statement.allUseBool, session.Statement.boolColumnMap) + false, true, session.Statement.allUseBool, session.Statement.useAllCols, + session.Statement.boolColumnMap) } var condition = "" @@ -3060,7 +3068,8 @@ func (session *Session) Delete(bean interface{}) (int64, error) { table := session.Engine.autoMap(bean) session.Statement.RefTable = table colNames, args := buildConditions(session.Engine, table, bean, true, true, - false, true, session.Statement.allUseBool, session.Statement.boolColumnMap) + false, true, session.Statement.allUseBool, session.Statement.useAllCols, + session.Statement.boolColumnMap) var condition = "" diff --git a/statement.go b/statement.go index 4bde5c7b..80c5dccb 100644 --- a/statement.go +++ b/statement.go @@ -25,6 +25,7 @@ type Statement struct { HavingStr string ColumnStr string columnMap map[string]bool + useAllCols bool OmitStr string ConditionStr string AltTableName string @@ -239,7 +240,8 @@ func (statement *Statement) Table(tableNameOrBean interface{}) *Statement { // Auto generating conditions according a struct func buildConditions(engine *Engine, table *Table, bean interface{}, - includeVersion bool, includeUpdated bool, includeNil bool, includeAutoIncr bool, allUseBool bool, + includeVersion bool, includeUpdated bool, includeNil bool, + includeAutoIncr bool, allUseBool bool, useAllCols bool, boolColumnMap map[string]bool) ([]string, []interface{}) { colNames := make([]string, 0) @@ -262,7 +264,7 @@ func buildConditions(engine *Engine, table *Table, bean interface{}, fieldValue := col.ValueOf(bean) fieldType := reflect.TypeOf(fieldValue.Interface()) - requiredField := false + requiredField := useAllCols if fieldType.Kind() == reflect.Ptr { if fieldValue.IsNil() { if includeNil { @@ -517,6 +519,11 @@ func (statement *Statement) Cols(columns ...string) *Statement { return statement } +func (statement *Statement) AllCols() *Statement { + statement.useAllCols = true + return statement +} + // indicates that use bool fields as update contents and query contiditions func (statement *Statement) UseBool(columns ...string) *Statement { if len(columns) > 0 { @@ -719,7 +726,8 @@ func (statement *Statement) genGetSql(bean interface{}) (string, []interface{}) statement.RefTable = table colNames, args := buildConditions(statement.Engine, table, bean, true, true, - false, true, statement.allUseBool, statement.boolColumnMap) + false, true, statement.allUseBool, statement.useAllCols, + statement.boolColumnMap) statement.ConditionStr = strings.Join(colNames, " AND ") statement.BeanArgs = args @@ -758,7 +766,7 @@ func (statement *Statement) genCountSql(bean interface{}) (string, []interface{} statement.RefTable = table colNames, args := buildConditions(statement.Engine, table, bean, true, true, false, - true, statement.allUseBool, statement.boolColumnMap) + true, statement.allUseBool, statement.useAllCols,statement.boolColumnMap) statement.ConditionStr = strings.Join(colNames, " AND ") statement.BeanArgs = args From 182428e13db4959977eb01db61e17eba560b2bd4 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 27 Mar 2014 22:16:06 +0800 Subject: [PATCH 05/14] add use case gogs --- README.md | 8 +++----- README_CN.md | 9 +++------ 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 1ed46fed..4f85368b 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,8 @@ Or # Cases +* [Gogs](http://try.gogits.org) - [github.com/gogits/gogs](http://github.com/gogits/gogs) + * [Gowalker](http://gowalker.org) - [github.com/Unknwon/gowalker](http://github.com/Unknwon/gowalker) * [Gobuild.io](http://gobuild.io) - [github.com/shxsun/gobuild](http://github.com/shxsun/gobuild) @@ -86,11 +88,7 @@ Or * [Very Hour](http://veryhour.com/) -* [GoCMS](https://github.com/zzdboy/GoCMS) - -# Todo - -[Todo List](https://trello.com/b/IHsuAnhk/xorm) +* [GoCMS - github.com/zzboy/GoCMS](https://github.com/zzdboy/GoCMS) # Discuss diff --git a/README_CN.md b/README_CN.md index d7e8de80..35b7ed70 100644 --- a/README_CN.md +++ b/README_CN.md @@ -79,6 +79,8 @@ xorm是一个简单而强大的Go语言ORM库. 通过它可以使数据库操作 ## 案例 +* [Gogs](http://try.gogits.org) - [github.com/gogits/gogs](http://github.com/gogits/gogs) + * [Gowalker](http://gowalker.org) - [github.com/Unknwon/gowalker](http://github.com/Unknwon/gowalker) * [Gobuild.io](http://gobuild.io) - [github.com/shxsun/gobuild](http://github.com/shxsun/gobuild) @@ -89,12 +91,7 @@ xorm是一个简单而强大的Go语言ORM库. 通过它可以使数据库操作 * [Very Hour](http://veryhour.com/) -* [GoCMS](https://github.com/zzdboy/GoCMS) - - -## Todo - -[开发计划](https://trello.com/b/IHsuAnhk/xorm) +* [GoCMS - github.com/zzboy/GoCMS](https://github.com/zzdboy/GoCMS) ## 讨论 From ff3a06b3dc619d096025235a3e49af40e1bd4e6a Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 28 Mar 2014 15:03:35 +0800 Subject: [PATCH 06/14] ql support --- engine.go | 1 + ql.go | 232 +++++++++++++++++++++++++++++++++++++++++++++++++++++ ql_test.go | 140 ++++++++++++++++++++++++++++++++ xorm.go | 4 + 4 files changed, 377 insertions(+) create mode 100644 ql.go create mode 100644 ql_test.go diff --git a/engine.go b/engine.go index 6e2cbad5..23038e1d 100644 --- a/engine.go +++ b/engine.go @@ -23,6 +23,7 @@ const ( MSSQL = "mssql" ORACLE_OCI = "oci8" + QL = "ql" ) // a dialect is a driver's wrapper diff --git a/ql.go b/ql.go new file mode 100644 index 00000000..8f26f3b5 --- /dev/null +++ b/ql.go @@ -0,0 +1,232 @@ +package xorm + +import ( + "database/sql" + "strings" +) + +type ql struct { + base +} + +type qlParser struct { +} + +func (p *qlParser) parse(driverName, dataSourceName string) (*uri, error) { + return &uri{dbType: QL, dbName: dataSourceName}, nil +} + +func (db *ql) Init(drivername, dataSourceName string) error { + return db.base.init(&qlParser{}, drivername, dataSourceName) +} + +func (db *ql) SqlType(c *Column) string { + switch t := c.SQLType.Name; t { + case Date, DateTime, TimeStamp, Time: + return Numeric + case TimeStampz: + return Text + case Char, Varchar, TinyText, Text, MediumText, LongText: + return Text + case Bit, TinyInt, SmallInt, MediumInt, Int, Integer, BigInt, Bool: + return Integer + case Float, Double, Real: + return Real + case Decimal, Numeric: + return Numeric + case TinyBlob, Blob, MediumBlob, LongBlob, Bytea, Binary, VarBinary: + return Blob + case Serial, BigSerial: + c.IsPrimaryKey = true + c.IsAutoIncrement = true + c.Nullable = false + return Integer + default: + return t + } +} + +func (db *ql) SupportInsertMany() bool { + return true +} + +func (db *ql) QuoteStr() string { + return "" +} + +func (db *ql) AutoIncrStr() string { + return "AUTOINCREMENT" +} + +func (db *ql) SupportEngine() bool { + return false +} + +func (db *ql) SupportCharset() bool { + return false +} + +func (db *ql) IndexOnTable() bool { + return false +} + +func (db *ql) IndexCheckSql(tableName, idxName string) (string, []interface{}) { + args := []interface{}{idxName} + return "SELECT name FROM sqlite_master WHERE type='index' and name = ?", args +} + +func (db *ql) TableCheckSql(tableName string) (string, []interface{}) { + args := []interface{}{tableName} + return "SELECT name FROM sqlite_master WHERE type='table' and name = ?", args +} + +func (db *ql) ColumnCheckSql(tableName, colName string) (string, []interface{}) { + args := []interface{}{tableName} + sql := "SELECT name FROM sqlite_master WHERE type='table' and name = ? and ((sql like '%`" + colName + "`%') or (sql like '%[" + colName + "]%'))" + return sql, args +} + +func (db *ql) GetColumns(tableName string) ([]string, map[string]*Column, error) { + args := []interface{}{tableName} + s := "SELECT sql FROM sqlite_master WHERE type='table' and name = ?" + cnn, err := sql.Open(db.driverName, db.dataSourceName) + if err != nil { + return nil, nil, err + } + defer cnn.Close() + res, err := query(cnn, s, args...) + if err != nil { + return nil, nil, err + } + + var sql string + for _, record := range res { + for name, content := range record { + if name == "sql" { + sql = string(content) + } + } + } + + nStart := strings.Index(sql, "(") + nEnd := strings.Index(sql, ")") + colCreates := strings.Split(sql[nStart+1:nEnd], ",") + cols := make(map[string]*Column) + colSeq := make([]string, 0) + for _, colStr := range colCreates { + fields := strings.Fields(strings.TrimSpace(colStr)) + col := new(Column) + col.Indexes = make(map[string]bool) + col.Nullable = true + for idx, field := range fields { + if idx == 0 { + col.Name = strings.Trim(field, "`[] ") + continue + } else if idx == 1 { + col.SQLType = SQLType{field, 0, 0} + } + switch field { + case "PRIMARY": + col.IsPrimaryKey = true + case "AUTOINCREMENT": + col.IsAutoIncrement = true + case "NULL": + if fields[idx-1] == "NOT" { + col.Nullable = false + } else { + col.Nullable = true + } + } + } + cols[col.Name] = col + colSeq = append(colSeq, col.Name) + } + return colSeq, cols, nil +} + +func (db *ql) GetTables() ([]*Table, error) { + args := []interface{}{} + s := "SELECT name FROM sqlite_master WHERE type='table'" + + cnn, err := sql.Open(db.driverName, db.dataSourceName) + if err != nil { + return nil, err + } + defer cnn.Close() + res, err := query(cnn, s, args...) + if err != nil { + return nil, err + } + + tables := make([]*Table, 0) + for _, record := range res { + table := new(Table) + for name, content := range record { + switch name { + case "name": + table.Name = string(content) + } + } + if table.Name == "sqlite_sequence" { + continue + } + tables = append(tables, table) + } + return tables, nil +} + +func (db *ql) GetIndexes(tableName string) (map[string]*Index, error) { + args := []interface{}{tableName} + s := "SELECT sql FROM sqlite_master WHERE type='index' and tbl_name = ?" + cnn, err := sql.Open(db.driverName, db.dataSourceName) + if err != nil { + return nil, err + } + defer cnn.Close() + res, err := query(cnn, s, args...) + if err != nil { + return nil, err + } + + indexes := make(map[string]*Index, 0) + for _, record := range res { + index := new(Index) + sql := string(record["sql"]) + + if sql == "" { + continue + } + + nNStart := strings.Index(sql, "INDEX") + nNEnd := strings.Index(sql, "ON") + if nNStart == -1 || nNEnd == -1 { + continue + } + + indexName := strings.Trim(sql[nNStart+6:nNEnd], "` []") + //fmt.Println(indexName) + if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) { + index.Name = indexName[5+len(tableName) : len(indexName)] + } else { + index.Name = indexName + } + + if strings.HasPrefix(sql, "CREATE UNIQUE INDEX") { + index.Type = UniqueType + } else { + index.Type = IndexType + } + + nStart := strings.Index(sql, "(") + nEnd := strings.Index(sql, ")") + colIndexes := strings.Split(sql[nStart+1:nEnd], ",") + + index.Cols = make([]string, 0) + for _, col := range colIndexes { + index.Cols = append(index.Cols, strings.Trim(col, "` []")) + } + indexes[index.Name] = index + } + + return indexes, nil +} diff --git a/ql_test.go b/ql_test.go new file mode 100644 index 00000000..46d0104d --- /dev/null +++ b/ql_test.go @@ -0,0 +1,140 @@ +package xorm + +import ( + "database/sql" + "os" + "testing" + + _ "github.com/mattn/ql-driver" +) + +func newQlEngine() (*Engine, error) { + os.Remove("./ql.db") + return NewEngine("ql", "./ql.db") +} + +func newQlDriverDB() (*sql.DB, error) { + os.Remove("./ql.db") + return sql.Open("ql", "./ql.db") +} + +func TestQl(t *testing.T) { + engine, err := newQlEngine() + defer engine.Close() + if err != nil { + t.Error(err) + return + } + engine.ShowSQL = showTestSql + engine.ShowErr = showTestSql + engine.ShowWarn = showTestSql + engine.ShowDebug = showTestSql + + testAll(engine, t) + testAll2(engine, t) + testAll3(engine, t) +} + +func TestQlWithCache(t *testing.T) { + engine, err := newQlEngine() + defer engine.Close() + if err != nil { + t.Error(err) + return + } + engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) + engine.ShowSQL = showTestSql + engine.ShowErr = showTestSql + engine.ShowWarn = showTestSql + engine.ShowDebug = showTestSql + + testAll(engine, t) + testAll2(engine, t) +} + +const ( + createTableQl = "CREATE TABLE IF NOT EXISTS `big_struct` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NULL, `title` TEXT NULL, `age` TEXT NULL, `alias` TEXT NULL, `nick_name` TEXT NULL);" + dropTableQl = "DROP TABLE IF EXISTS `big_struct`;" +) + +func BenchmarkQlDriverInsert(t *testing.B) { + doBenchDriver(newQlDriverDB, createTableQl, dropTableQl, + doBenchDriverInsert, t) +} + +func BenchmarkQlDriverFind(t *testing.B) { + doBenchDriver(newQlDriverDB, createTableQl, dropTableQl, + doBenchDriverFind, t) +} + +func BenchmarkQlNoCacheInsert(t *testing.B) { + t.StopTimer() + engine, err := newQlEngine() + defer engine.Close() + if err != nil { + t.Error(err) + return + } + //engine.ShowSQL = true + doBenchInsert(engine, t) +} + +func BenchmarkQlNoCacheFind(t *testing.B) { + t.StopTimer() + engine, err := newQlEngine() + defer engine.Close() + if err != nil { + t.Error(err) + return + } + //engine.ShowSQL = true + doBenchFind(engine, t) +} + +func BenchmarkQlNoCacheFindPtr(t *testing.B) { + t.StopTimer() + engine, err := newQlEngine() + defer engine.Close() + if err != nil { + t.Error(err) + return + } + //engine.ShowSQL = true + doBenchFindPtr(engine, t) +} + +func BenchmarkQlCacheInsert(t *testing.B) { + t.StopTimer() + engine, err := newQlEngine() + defer engine.Close() + if err != nil { + t.Error(err) + return + } + engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) + doBenchInsert(engine, t) +} + +func BenchmarkQlCacheFind(t *testing.B) { + t.StopTimer() + engine, err := newQlEngine() + defer engine.Close() + if err != nil { + t.Error(err) + return + } + engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) + doBenchFind(engine, t) +} + +func BenchmarkQlCacheFindPtr(t *testing.B) { + t.StopTimer() + engine, err := newQlEngine() + defer engine.Close() + if err != nil { + t.Error(err) + return + } + engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) + doBenchFindPtr(engine, t) +} diff --git a/xorm.go b/xorm.go index 0a18d5e3..a4bded59 100644 --- a/xorm.go +++ b/xorm.go @@ -40,6 +40,10 @@ func NewEngine(driverName string, dataSourceName string) (*Engine, error) { } else if driverName == ORACLE_OCI { engine.dialect = &oracle{} engine.Filters = append(engine.Filters, &QuoteFilter{}) + } else if driverName == QL { + engine.dialect = &ql{} + engine.Filters = append(engine.Filters, &PgSeqFilter{}) + engine.Filters = append(engine.Filters, &QuoteFilter{}) } else { return nil, errors.New(fmt.Sprintf("Unsupported driver name: %v", driverName)) } From 9d64ef50133ec413bcd426f55c65d331c2af6f49 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 5 Apr 2014 22:14:00 +0800 Subject: [PATCH 07/14] bug fixed --- engine.go | 67 ++++++++------- mssql.go | 8 +- mysql.go | 8 ++ ql.go | 232 --------------------------------------------------- ql_test.go | 140 ------------------------------- session.go | 2 +- statement.go | 14 +--- 7 files changed, 53 insertions(+), 418 deletions(-) delete mode 100644 ql.go delete mode 100644 ql_test.go diff --git a/engine.go b/engine.go index 23038e1d..205ed873 100644 --- a/engine.go +++ b/engine.go @@ -34,6 +34,8 @@ type dialect interface { SqlType(t *Column) string SupportInsertMany() bool QuoteStr() string + RollBackStr() string + DropTableSql(tableName string) string AutoIncrStr() string SupportEngine() bool SupportCharset() bool @@ -449,6 +451,18 @@ func (engine *Engine) newTable() *Table { return table } +func addIndex(indexName string, table *Table, col *Column, indexType int) { + if index, ok := table.Indexes[indexName]; ok { + index.AddColumn(col.Name) + col.Indexes[index.Name] = true + } else { + index := NewIndex(indexName, indexType) + index.AddColumn(col.Name) + table.AddIndex(index) + col.Indexes[index.Name] = true + } +} + func (engine *Engine) mapType(t reflect.Type) *Table { table := engine.newTable() table.Name = engine.tableMapper.Obj2Table(t.Name()) @@ -484,8 +498,9 @@ func (engine *Engine) mapType(t reflect.Type) *Table { table.PrimaryKeys = parentTable.PrimaryKeys continue } - var indexType int - var indexName string + + indexNames := make(map[string]int) + var isIndex, isUnique bool var preKey string for j, key := range tags { k := strings.ToUpper(key) @@ -521,15 +536,15 @@ func (engine *Engine) mapType(t reflect.Type) *Table { case k == "UPDATED": col.IsUpdated = true case strings.HasPrefix(k, "INDEX(") && strings.HasSuffix(k, ")"): - indexType = IndexType - indexName = k[len("INDEX")+1 : len(k)-1] + indexName := k[len("INDEX")+1 : len(k)-1] + indexNames[indexName] = IndexType case k == "INDEX": - indexType = IndexType + isIndex = true case strings.HasPrefix(k, "UNIQUE(") && strings.HasSuffix(k, ")"): - indexName = k[len("UNIQUE")+1 : len(k)-1] - indexType = UniqueType + indexName := k[len("UNIQUE")+1 : len(k)-1] + indexNames[indexName] = UniqueType case k == "UNIQUE": - indexType = UniqueType + isUnique = true case k == "NOTNULL": col.Nullable = false case k == "NOT": @@ -584,32 +599,15 @@ func (engine *Engine) mapType(t reflect.Type) *Table { if col.Name == "" { col.Name = engine.columnMapper.Obj2Table(t.Field(i).Name) } - if indexType == IndexType { - if indexName == "" { - indexName = col.Name - } - if index, ok := table.Indexes[indexName]; ok { - index.AddColumn(col.Name) - col.Indexes[index.Name] = true - } else { - index := NewIndex(indexName, IndexType) - index.AddColumn(col.Name) - table.AddIndex(index) - col.Indexes[index.Name] = true - } - } else if indexType == UniqueType { - if indexName == "" { - indexName = col.Name - } - if index, ok := table.Indexes[indexName]; ok { - index.AddColumn(col.Name) - col.Indexes[index.Name] = true - } else { - index := NewIndex(indexName, UniqueType) - index.AddColumn(col.Name) - table.AddIndex(index) - col.Indexes[index.Name] = true - } + + if isUnique { + indexNames[col.Name] = UniqueType + } else if isIndex { + indexNames[col.Name] = IndexType + } + + for indexName, indexType := range indexNames { + addIndex(indexName, table, col, indexType) } } } else { @@ -810,6 +808,7 @@ func (engine *Engine) Sync(beans ...interface{}) error { } } } else if index.Type == IndexType { + fmt.Println("index:", table.Name, name, index) isExist, err := session.isIndexExist2(table.Name, index.Cols, false) if err != nil { return err diff --git a/mssql.go b/mssql.go index 6e9776d2..54c93e71 100644 --- a/mssql.go +++ b/mssql.go @@ -108,6 +108,12 @@ func (db *mssql) AutoIncrStr() string { return "IDENTITY" } +func (db *mssql) DropTableSql(tableName string) string { + return fmt.Sprintf("IF EXISTS (SELECT * FROM sysobjects WHERE id = "+ + "object_id(N'%s') and OBJECTPROPERTY(id, N'IsUserTable') = 1) "+ + "DROP TABLE \"%s\"", tableName, tableName) +} + func (db *mssql) SupportCharset() bool { return false } @@ -187,7 +193,7 @@ where a.object_id=object_id('` + tableName + `')` if col.SQLType.IsText() { if col.Default != "" { col.Default = "'" + col.Default + "'" - }else{ + } else { if col.DefaultIsEmpty { col.Default = "''" } diff --git a/mysql.go b/mysql.go index 23b53641..8d0cfaa3 100644 --- a/mysql.go +++ b/mysql.go @@ -89,6 +89,14 @@ func (b *base) DBType() string { return b.uri.dbType } +func (db *base) RollBackStr() string { + return "ROLL BACK" +} + +func (db *base) DropTableSql(tableName string) string { + return fmt.Sprintf("DROP TABLE IF EXISTS `%s`", tableName) +} + type mysql struct { base net string diff --git a/ql.go b/ql.go deleted file mode 100644 index 8f26f3b5..00000000 --- a/ql.go +++ /dev/null @@ -1,232 +0,0 @@ -package xorm - -import ( - "database/sql" - "strings" -) - -type ql struct { - base -} - -type qlParser struct { -} - -func (p *qlParser) parse(driverName, dataSourceName string) (*uri, error) { - return &uri{dbType: QL, dbName: dataSourceName}, nil -} - -func (db *ql) Init(drivername, dataSourceName string) error { - return db.base.init(&qlParser{}, drivername, dataSourceName) -} - -func (db *ql) SqlType(c *Column) string { - switch t := c.SQLType.Name; t { - case Date, DateTime, TimeStamp, Time: - return Numeric - case TimeStampz: - return Text - case Char, Varchar, TinyText, Text, MediumText, LongText: - return Text - case Bit, TinyInt, SmallInt, MediumInt, Int, Integer, BigInt, Bool: - return Integer - case Float, Double, Real: - return Real - case Decimal, Numeric: - return Numeric - case TinyBlob, Blob, MediumBlob, LongBlob, Bytea, Binary, VarBinary: - return Blob - case Serial, BigSerial: - c.IsPrimaryKey = true - c.IsAutoIncrement = true - c.Nullable = false - return Integer - default: - return t - } -} - -func (db *ql) SupportInsertMany() bool { - return true -} - -func (db *ql) QuoteStr() string { - return "" -} - -func (db *ql) AutoIncrStr() string { - return "AUTOINCREMENT" -} - -func (db *ql) SupportEngine() bool { - return false -} - -func (db *ql) SupportCharset() bool { - return false -} - -func (db *ql) IndexOnTable() bool { - return false -} - -func (db *ql) IndexCheckSql(tableName, idxName string) (string, []interface{}) { - args := []interface{}{idxName} - return "SELECT name FROM sqlite_master WHERE type='index' and name = ?", args -} - -func (db *ql) TableCheckSql(tableName string) (string, []interface{}) { - args := []interface{}{tableName} - return "SELECT name FROM sqlite_master WHERE type='table' and name = ?", args -} - -func (db *ql) ColumnCheckSql(tableName, colName string) (string, []interface{}) { - args := []interface{}{tableName} - sql := "SELECT name FROM sqlite_master WHERE type='table' and name = ? and ((sql like '%`" + colName + "`%') or (sql like '%[" + colName + "]%'))" - return sql, args -} - -func (db *ql) GetColumns(tableName string) ([]string, map[string]*Column, error) { - args := []interface{}{tableName} - s := "SELECT sql FROM sqlite_master WHERE type='table' and name = ?" - cnn, err := sql.Open(db.driverName, db.dataSourceName) - if err != nil { - return nil, nil, err - } - defer cnn.Close() - res, err := query(cnn, s, args...) - if err != nil { - return nil, nil, err - } - - var sql string - for _, record := range res { - for name, content := range record { - if name == "sql" { - sql = string(content) - } - } - } - - nStart := strings.Index(sql, "(") - nEnd := strings.Index(sql, ")") - colCreates := strings.Split(sql[nStart+1:nEnd], ",") - cols := make(map[string]*Column) - colSeq := make([]string, 0) - for _, colStr := range colCreates { - fields := strings.Fields(strings.TrimSpace(colStr)) - col := new(Column) - col.Indexes = make(map[string]bool) - col.Nullable = true - for idx, field := range fields { - if idx == 0 { - col.Name = strings.Trim(field, "`[] ") - continue - } else if idx == 1 { - col.SQLType = SQLType{field, 0, 0} - } - switch field { - case "PRIMARY": - col.IsPrimaryKey = true - case "AUTOINCREMENT": - col.IsAutoIncrement = true - case "NULL": - if fields[idx-1] == "NOT" { - col.Nullable = false - } else { - col.Nullable = true - } - } - } - cols[col.Name] = col - colSeq = append(colSeq, col.Name) - } - return colSeq, cols, nil -} - -func (db *ql) GetTables() ([]*Table, error) { - args := []interface{}{} - s := "SELECT name FROM sqlite_master WHERE type='table'" - - cnn, err := sql.Open(db.driverName, db.dataSourceName) - if err != nil { - return nil, err - } - defer cnn.Close() - res, err := query(cnn, s, args...) - if err != nil { - return nil, err - } - - tables := make([]*Table, 0) - for _, record := range res { - table := new(Table) - for name, content := range record { - switch name { - case "name": - table.Name = string(content) - } - } - if table.Name == "sqlite_sequence" { - continue - } - tables = append(tables, table) - } - return tables, nil -} - -func (db *ql) GetIndexes(tableName string) (map[string]*Index, error) { - args := []interface{}{tableName} - s := "SELECT sql FROM sqlite_master WHERE type='index' and tbl_name = ?" - cnn, err := sql.Open(db.driverName, db.dataSourceName) - if err != nil { - return nil, err - } - defer cnn.Close() - res, err := query(cnn, s, args...) - if err != nil { - return nil, err - } - - indexes := make(map[string]*Index, 0) - for _, record := range res { - index := new(Index) - sql := string(record["sql"]) - - if sql == "" { - continue - } - - nNStart := strings.Index(sql, "INDEX") - nNEnd := strings.Index(sql, "ON") - if nNStart == -1 || nNEnd == -1 { - continue - } - - indexName := strings.Trim(sql[nNStart+6:nNEnd], "` []") - //fmt.Println(indexName) - if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) { - index.Name = indexName[5+len(tableName) : len(indexName)] - } else { - index.Name = indexName - } - - if strings.HasPrefix(sql, "CREATE UNIQUE INDEX") { - index.Type = UniqueType - } else { - index.Type = IndexType - } - - nStart := strings.Index(sql, "(") - nEnd := strings.Index(sql, ")") - colIndexes := strings.Split(sql[nStart+1:nEnd], ",") - - index.Cols = make([]string, 0) - for _, col := range colIndexes { - index.Cols = append(index.Cols, strings.Trim(col, "` []")) - } - indexes[index.Name] = index - } - - return indexes, nil -} diff --git a/ql_test.go b/ql_test.go deleted file mode 100644 index 46d0104d..00000000 --- a/ql_test.go +++ /dev/null @@ -1,140 +0,0 @@ -package xorm - -import ( - "database/sql" - "os" - "testing" - - _ "github.com/mattn/ql-driver" -) - -func newQlEngine() (*Engine, error) { - os.Remove("./ql.db") - return NewEngine("ql", "./ql.db") -} - -func newQlDriverDB() (*sql.DB, error) { - os.Remove("./ql.db") - return sql.Open("ql", "./ql.db") -} - -func TestQl(t *testing.T) { - engine, err := newQlEngine() - defer engine.Close() - if err != nil { - t.Error(err) - return - } - engine.ShowSQL = showTestSql - engine.ShowErr = showTestSql - engine.ShowWarn = showTestSql - engine.ShowDebug = showTestSql - - testAll(engine, t) - testAll2(engine, t) - testAll3(engine, t) -} - -func TestQlWithCache(t *testing.T) { - engine, err := newQlEngine() - defer engine.Close() - if err != nil { - t.Error(err) - return - } - engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) - engine.ShowSQL = showTestSql - engine.ShowErr = showTestSql - engine.ShowWarn = showTestSql - engine.ShowDebug = showTestSql - - testAll(engine, t) - testAll2(engine, t) -} - -const ( - createTableQl = "CREATE TABLE IF NOT EXISTS `big_struct` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NULL, `title` TEXT NULL, `age` TEXT NULL, `alias` TEXT NULL, `nick_name` TEXT NULL);" - dropTableQl = "DROP TABLE IF EXISTS `big_struct`;" -) - -func BenchmarkQlDriverInsert(t *testing.B) { - doBenchDriver(newQlDriverDB, createTableQl, dropTableQl, - doBenchDriverInsert, t) -} - -func BenchmarkQlDriverFind(t *testing.B) { - doBenchDriver(newQlDriverDB, createTableQl, dropTableQl, - doBenchDriverFind, t) -} - -func BenchmarkQlNoCacheInsert(t *testing.B) { - t.StopTimer() - engine, err := newQlEngine() - defer engine.Close() - if err != nil { - t.Error(err) - return - } - //engine.ShowSQL = true - doBenchInsert(engine, t) -} - -func BenchmarkQlNoCacheFind(t *testing.B) { - t.StopTimer() - engine, err := newQlEngine() - defer engine.Close() - if err != nil { - t.Error(err) - return - } - //engine.ShowSQL = true - doBenchFind(engine, t) -} - -func BenchmarkQlNoCacheFindPtr(t *testing.B) { - t.StopTimer() - engine, err := newQlEngine() - defer engine.Close() - if err != nil { - t.Error(err) - return - } - //engine.ShowSQL = true - doBenchFindPtr(engine, t) -} - -func BenchmarkQlCacheInsert(t *testing.B) { - t.StopTimer() - engine, err := newQlEngine() - defer engine.Close() - if err != nil { - t.Error(err) - return - } - engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) - doBenchInsert(engine, t) -} - -func BenchmarkQlCacheFind(t *testing.B) { - t.StopTimer() - engine, err := newQlEngine() - defer engine.Close() - if err != nil { - t.Error(err) - return - } - engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) - doBenchFind(engine, t) -} - -func BenchmarkQlCacheFindPtr(t *testing.B) { - t.StopTimer() - engine, err := newQlEngine() - defer engine.Close() - if err != nil { - t.Error(err) - return - } - engine.SetDefaultCacher(NewLRUCacher(NewMemoryStore(), 1000)) - doBenchFindPtr(engine, t) -} diff --git a/session.go b/session.go index 731027ce..10bba1bb 100644 --- a/session.go +++ b/session.go @@ -286,7 +286,7 @@ func (session *Session) Begin() error { // When using transaction, you can rollback if any error func (session *Session) Rollback() error { if !session.IsAutoCommit && !session.IsCommitedOrRollbacked { - session.Engine.LogSQL("ROLL BACK") + session.Engine.LogSQL(session.Engine.dialect.RollBackStr()) session.IsCommitedOrRollbacked = true return session.Tx.Rollback() } diff --git a/statement.go b/statement.go index 80c5dccb..e4ea1a2c 100644 --- a/statement.go +++ b/statement.go @@ -25,7 +25,7 @@ type Statement struct { HavingStr string ColumnStr string columnMap map[string]bool - useAllCols bool + useAllCols bool OmitStr string ConditionStr string AltTableName string @@ -240,7 +240,7 @@ func (statement *Statement) Table(tableNameOrBean interface{}) *Statement { // Auto generating conditions according a struct func buildConditions(engine *Engine, table *Table, bean interface{}, - includeVersion bool, includeUpdated bool, includeNil bool, + includeVersion bool, includeUpdated bool, includeNil bool, includeAutoIncr bool, allUseBool bool, useAllCols bool, boolColumnMap map[string]bool) ([]string, []interface{}) { @@ -712,13 +712,7 @@ func (s *Statement) genDelIndexSQL() []string { } func (s *Statement) genDropSQL() string { - if s.Engine.dialect.DBType() == MSSQL { - return "IF EXISTS (SELECT * FROM sysobjects WHERE id = object_id(N'" + - s.TableName() + "') and OBJECTPROPERTY(id, N'IsUserTable') = 1) " + - "DROP TABLE " + s.Engine.Quote(s.TableName()) + ";" - } else { - return "DROP TABLE IF EXISTS " + s.Engine.Quote(s.TableName()) + ";" - } + return s.Engine.dialect.DropTableSql(s.TableName()) + ";" } func (statement *Statement) genGetSql(bean interface{}) (string, []interface{}) { @@ -766,7 +760,7 @@ func (statement *Statement) genCountSql(bean interface{}) (string, []interface{} statement.RefTable = table colNames, args := buildConditions(statement.Engine, table, bean, true, true, false, - true, statement.allUseBool, statement.useAllCols,statement.boolColumnMap) + true, statement.allUseBool, statement.useAllCols, statement.boolColumnMap) statement.ConditionStr = strings.Join(colNames, " AND ") statement.BeanArgs = args From a1e3dd8db0ec2440ccf13d5ac090a839e158cd8d Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 5 Apr 2014 22:17:12 +0800 Subject: [PATCH 08/14] comment ql --- xorm.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/xorm.go b/xorm.go index a4bded59..0a18d5e3 100644 --- a/xorm.go +++ b/xorm.go @@ -40,10 +40,6 @@ func NewEngine(driverName string, dataSourceName string) (*Engine, error) { } else if driverName == ORACLE_OCI { engine.dialect = &oracle{} engine.Filters = append(engine.Filters, &QuoteFilter{}) - } else if driverName == QL { - engine.dialect = &ql{} - engine.Filters = append(engine.Filters, &PgSeqFilter{}) - engine.Filters = append(engine.Filters, &QuoteFilter{}) } else { return nil, errors.New(fmt.Sprintf("Unsupported driver name: %v", driverName)) } From 6e7cead1ec71ad5ea0369b86e1cdf9c64adaf883 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 5 Apr 2014 22:18:25 +0800 Subject: [PATCH 09/14] remove debug info --- engine.go | 1 - 1 file changed, 1 deletion(-) diff --git a/engine.go b/engine.go index 205ed873..a4d1912a 100644 --- a/engine.go +++ b/engine.go @@ -808,7 +808,6 @@ func (engine *Engine) Sync(beans ...interface{}) error { } } } else if index.Type == IndexType { - fmt.Println("index:", table.Name, name, index) isExist, err := session.isIndexExist2(table.Name, index.Cols, false) if err != nil { return err From 9d5f834eb2e8996509c6928a02fc73c935be63f6 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 6 Apr 2014 12:58:16 +0800 Subject: [PATCH 10/14] bug fixed & add MustCols function & improved docs --- base_test.go | 57 ++++++++++++++++++++++++++++++++++++-- docs/QuickStart.md | 65 ++++++++++++++++++++++++++++++++------------ docs/QuickStartEn.md | 29 ++++++++++++++++---- engine.go | 12 ++++++++ session.go | 13 ++++++--- statement.go | 44 ++++++++++++++++++++++-------- 6 files changed, 179 insertions(+), 41 deletions(-) diff --git a/base_test.go b/base_test.go index 53d15bfc..3199b850 100644 --- a/base_test.go +++ b/base_test.go @@ -372,8 +372,8 @@ func update(engine *Engine, t *testing.T) { } type UpdateAllCols struct { - Id int64 - Bool bool + Id int64 + Bool bool String string } @@ -383,7 +383,7 @@ func update(engine *Engine, t *testing.T) { t.Error(err) panic(err) } - + _, err = engine.Insert(col1) if err != nil { t.Error(err) @@ -417,6 +417,57 @@ func update(engine *Engine, t *testing.T) { panic(err) return } + + { + type UpdateMustCols struct { + Id int64 + Bool bool + String string + } + + col1 := &UpdateMustCols{} + err = engine.Sync(col1) + if err != nil { + t.Error(err) + panic(err) + } + + _, err = engine.Insert(col1) + if err != nil { + t.Error(err) + panic(err) + } + + col2 := &UpdateMustCols{col1.Id, true, ""} + boolStr := engine.columnMapper.Obj2Table("Bool") + stringStr := engine.columnMapper.Obj2Table("String") + _, err = engine.Id(col2.Id).MustCols(boolStr, stringStr).Update(col2) + if err != nil { + t.Error(err) + panic(err) + } + + col3 := &UpdateMustCols{} + has, err := engine.Id(col2.Id).Get(col3) + if err != nil { + t.Error(err) + panic(err) + } + + if !has { + err = errors.New(fmt.Sprintf("cannot get id %d", col2.Id)) + t.Error(err) + panic(err) + return + } + + if *col2 != *col3 { + err = errors.New(fmt.Sprintf("col2 should eq col3")) + t.Error(err) + panic(err) + return + } + } } func updateSameMapper(engine *Engine, t *testing.T) { diff --git a/docs/QuickStart.md b/docs/QuickStart.md index dc2f0cd3..d787cb0b 100644 --- a/docs/QuickStart.md +++ b/docs/QuickStart.md @@ -4,7 +4,7 @@ xorm 快速入门 * [1.创建Orm引擎](#10) * [2.定义表结构体](#20) * [2.1.名称映射规则](#21) - * [2.2.前缀映射规则和后缀映射规则](#22) + * [2.2.前缀映射,后缀映射和缓存映射](#22) * [2.3.使用Table和Tag改变名称映射](#23) * [2.4.Column属性定义](#24) * [2.5.Go与字段类型对应表](#25) @@ -29,12 +29,13 @@ xorm 快速入门 * [9.执行SQL命令](#100) * [10.事务处理](#110) * [11.缓存](#120) -* [12.xorm工具](#130) - * [12.1.反转命令](#131) -* [13.Examples](#140) -* [14.案例](#150) -* [15.那些年我们踩过的坑](#160) -* [16.讨论](#170) +* [12.事件](#125) +* [13.xorm工具](#130) + * [13.1.反转命令](#131) +* [14.Examples](#140) +* [15.案例](#150) +* [16.那些年我们踩过的坑](#160) +* [17.讨论](#170) ## 1.创建Orm引擎 @@ -129,11 +130,11 @@ engine.SetColumnMapper(SnakeMapper{}) ``` -### 2.2.前缀映射规则和后缀映射规则 +### 2.2.前缀映射,后缀映射和缓存映射 * 通过`engine.NewPrefixMapper(SnakeMapper{}, "prefix")`可以在SnakeMapper的基础上在命名中添加统一的前缀,当然也可以把SnakeMapper{}换成SameMapper或者你自定义的Mapper。 * 通过`engine.NewSufffixMapper(SnakeMapper{}, "suffix")`可以在SnakeMapper的基础上在命名中添加统一的后缀,当然也可以把SnakeMapper{}换成SameMapper或者你自定义的Mapper。 -* +* 通过`eneing.NewCacheMapper(SnakeMapper{})`可以组合其它的映射规则,起到在内存中缓存曾经映射过的命名映射。 ### 2.3.使用Table和Tag改变名称映射 @@ -153,7 +154,7 @@ type User struct { } ``` -对于不同的数据库系统,数据类型其实是有些差异的。因此xorm中对数据类型有自己的定义,基本的原则是尽量兼容各种数据库的字段类型,具体的字段对应关系可以查看[字段类型对应表](https://github.com/lunny/xorm/blob/master/docs/COLUMNTYPE.md)。 +对于不同的数据库系统,数据类型其实是有些差异的。因此xorm中对数据类型有自己的定义,基本的原则是尽量兼容各种数据库的字段类型,具体的字段对应关系可以查看[字段类型对应表](https://github.com/lunny/xorm/blob/master/docs/COLUMNTYPE.md)。对于使用者,一般只要使用自己熟悉的数据库字段定义即可。 具体的映射规则如下,另Tag中的关键字均不区分大小写,字段名区分大小写: @@ -407,7 +408,11 @@ engine.Cols("age", "name").Update(&user) // UPDATE user SET age=? AND name=? ``` -其中的参数"age", "name"也可以写成"age, name",两种写法均可 +* AllCols() +查询或更新所有字段。 + +* MustCols(…string) +某些字段必须更新。 * Omit(...string) 和cols相反,此函数指定排除某些指定的字段。注意:此方法和Cols方法不可同时使用 @@ -729,20 +734,46 @@ engine.ClearCache(new(User)) ![cache design](https://raw.github.com/lunny/xorm/master/docs/cache_design.png) + +## 12.事件 +xorm支持两种方式的事件,一种是在Struct中的特定方法来作为事件的方法,一种是在执行语句的过程中执行事件。 + +在Struct中作为成员方法的事件如下: + +* BeforeInsert() + +* BeforeUpdate() + +* BeforeDelete() + +* AfterInsert() + +* AfterUpdate() + +* AfterDelete() + +在语句执行过程中的事件方法为: + +* Before(beforeFunc interface{}) + +* After(afterFunc interface{}) + +其中beforeFunc和afterFunc的原型为func(bean interface{}). + -## 12.xorm工具 +## 13.xorm工具 xorm工具提供了xorm命令,能够帮助做很多事情。 -### 12.1.反转命令 +### 13.1.反转命令 参见 [xorm工具](https://github.com/lunny/xorm/tree/master/xorm) -## 13.Examples +## 14.Examples 请访问[https://github.com/lunny/xorm/tree/master/examples](https://github.com/lunny/xorm/tree/master/examples) -## 14.案例 +## 15.案例 * [Gowalker](http://gowalker.org),源代码 [github.com/Unknwon/gowalker](http://github.com/Unknwon/gowalker) @@ -753,7 +784,7 @@ xorm工具提供了xorm命令,能够帮助做很多事情。 * [VeryHour](http://veryhour.com) -## 15.那些年我们踩过的坑 +## 16.那些年我们踩过的坑 * 怎么同时使用xorm的tag和json的tag? 答:使用空格 @@ -797,5 +828,5 @@ money float64 `xorm:"Numeric"` -## 16.讨论 +## 17.讨论 请加入QQ群:280360085 进行讨论。 diff --git a/docs/QuickStartEn.md b/docs/QuickStartEn.md index 5249c81f..7cc08e2e 100644 --- a/docs/QuickStartEn.md +++ b/docs/QuickStartEn.md @@ -100,30 +100,47 @@ engine.Logger = f ## 2.Define struct -xorm支持将一个struct映射为数据库中对应的一张表。映射规则如下: +xorm map a struct to a database table, the rule is below. -### 2.1.名称映射规则 +### 2.1.name mapping rule -名称映射规则主要负责结构体名称到表名和结构体field到表字段的名称映射。由xorm.IMapper接口的实现者来管理,xorm内置了两种IMapper实现:`SnakeMapper` 和 `SameMapper`。SnakeMapper支持struct为驼峰式命名,表结构为下划线命名之间的转换;SameMapper支持相同的命名。 +use xorm.IMapper interface to implement. There are two IMapper implemented: `SnakeMapper` and `SameMapper`. SnakeMapper means struct name is word by word and table name or column name as 下划线. SameMapper means same name between struct and table. -当前SnakeMapper为默认值,如果需要改变时,在engine创建完成后使用 +SnakeMapper is the default. ```Go engine.Mapper = SameMapper{} ``` +同时需要注意的是: + +* 如果你使用了别的命名规则映射方案,也可以自己实现一个IMapper。 +* 表名称和字段名称的映射规则默认是相同的,当然也可以设置为不同,如: + +```Go +engine.SetTableMapper(SameMapper{}) +engine.SetColumnMapper(SnakeMapper{}) +``` + + +### 2.2.前缀映射规则,后缀映射规则和缓存映射规则 + +* 通过`engine.NewPrefixMapper(SnakeMapper{}, "prefix")`可以在SnakeMapper的基础上在命名中添加统一的前缀,当然也可以把SnakeMapper{}换成SameMapper或者你自定义的Mapper。 +* 通过`engine.NewSufffixMapper(SnakeMapper{}, "suffix")`可以在SnakeMapper的基础上在命名中添加统一的后缀,当然也可以把SnakeMapper{}换成SameMapper或者你自定义的Mapper。 +* 通过`eneing.NewCacheMapper(SnakeMapper{})`可以起到在内存中缓存曾经映射过的命名映射。 + 当然,如果你使用了别的命名规则映射方案,也可以自己实现一个IMapper。 -### 2.2.使用Table和Tag改变名称映射 +### 2.3.使用Table和Tag改变名称映射 如果所有的命名都是按照IMapper的映射来操作的,那当然是最理想的。但是如果碰到某个表名或者某个字段名跟映射规则不匹配时,我们就需要别的机制来改变。 通过`engine.Table()`方法可以改变struct对应的数据库表的名称,通过sturct中field对应的Tag中使用`xorm:"'table_name'"`可以使该field对应的Column名称为指定名称。这里使用两个单引号将Column名称括起来是为了防止名称冲突,因为我们在Tag中还可以对这个Column进行更多的定义。如果名称不冲突的情况,单引号也可以不使用。 -### 2.3.Column属性定义 +### 2.4.Column属性定义 我们在field对应的Tag中对Column的一些属性进行定义,定义的方法基本和我们写SQL定义表结构类似,比如: ``` diff --git a/engine.go b/engine.go index a4d1912a..070c718c 100644 --- a/engine.go +++ b/engine.go @@ -336,6 +336,18 @@ func (engine *Engine) Cols(columns ...string) *Session { return session.Cols(columns...) } +func (engine *Engine) AllCols() *Session { + session := engine.NewSession() + session.IsAutoClose = true + return session.AllCols() +} + +func (engine *Engine) MustCols(columns ...string) *Session { + session := engine.NewSession() + session.IsAutoClose = true + return session.MustCols(columns...) +} + // Xorm automatically retrieve condition according struct, but // if struct has bool field, it will ignore them. So use UseBool // to tell system to do not ignore them. diff --git a/session.go b/session.go index 10bba1bb..bc718b9b 100644 --- a/session.go +++ b/session.go @@ -131,6 +131,11 @@ func (session *Session) AllCols() *Session { return session } +func (session *Session) MustCols(columns ...string) *Session { + session.Statement.MustCols(columns...) + return session +} + func (session *Session) NoCascade() *Session { session.Statement.UseCascade = false return session @@ -1029,7 +1034,7 @@ func (session *Session) Find(rowsSlicePtr interface{}, condiBean ...interface{}) if len(condiBean) > 0 { colNames, args := buildConditions(session.Engine, table, condiBean[0], true, true, false, true, session.Statement.allUseBool, session.Statement.useAllCols, - session.Statement.boolColumnMap) + session.Statement.mustColumnMap) session.Statement.ConditionStr = strings.Join(colNames, " AND ") session.Statement.BeanArgs = args } @@ -2845,7 +2850,7 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 if session.Statement.ColumnStr == "" { colNames, args = buildConditions(session.Engine, table, bean, false, false, false, false, session.Statement.allUseBool, session.Statement.useAllCols, - session.Statement.boolColumnMap) + session.Statement.mustColumnMap) } else { colNames, args, err = table.genCols(session, bean, true, true) if err != nil { @@ -2880,7 +2885,7 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 if len(condiBean) > 0 { condiColNames, condiArgs = buildConditions(session.Engine, session.Statement.RefTable, condiBean[0], true, true, false, true, session.Statement.allUseBool, session.Statement.useAllCols, - session.Statement.boolColumnMap) + session.Statement.mustColumnMap) } var condition = "" @@ -3069,7 +3074,7 @@ func (session *Session) Delete(bean interface{}) (int64, error) { session.Statement.RefTable = table colNames, args := buildConditions(session.Engine, table, bean, true, true, false, true, session.Statement.allUseBool, session.Statement.useAllCols, - session.Statement.boolColumnMap) + session.Statement.mustColumnMap) var condition = "" diff --git a/statement.go b/statement.go index e4ea1a2c..805f5955 100644 --- a/statement.go +++ b/statement.go @@ -41,7 +41,7 @@ type Statement struct { IsDistinct bool allUseBool bool checkVersion bool - boolColumnMap map[string]bool + mustColumnMap map[string]bool inColumns map[string][]interface{} } @@ -70,7 +70,7 @@ func (statement *Statement) Init() { statement.UseAutoTime = true statement.IsDistinct = false statement.allUseBool = false - statement.boolColumnMap = make(map[string]bool) + statement.mustColumnMap = make(map[string]bool) statement.checkVersion = true statement.inColumns = make(map[string][]interface{}) } @@ -242,7 +242,7 @@ func (statement *Statement) Table(tableNameOrBean interface{}) *Statement { func buildConditions(engine *Engine, table *Table, bean interface{}, includeVersion bool, includeUpdated bool, includeNil bool, includeAutoIncr bool, allUseBool bool, useAllCols bool, - boolColumnMap map[string]bool) ([]string, []interface{}) { + mustColumnMap map[string]bool) ([]string, []interface{}) { colNames := make([]string, 0) var args = make([]interface{}, 0) @@ -265,6 +265,14 @@ func buildConditions(engine *Engine, table *Table, bean interface{}, fieldType := reflect.TypeOf(fieldValue.Interface()) requiredField := useAllCols + if b, ok := mustColumnMap[strings.ToLower(col.Name)]; ok { + if b { + requiredField = true + } else { + continue + } + } + if fieldType.Kind() == reflect.Ptr { if fieldValue.IsNil() { if includeNil { @@ -287,8 +295,6 @@ func buildConditions(engine *Engine, table *Table, bean interface{}, case reflect.Bool: if allUseBool || requiredField { val = fieldValue.Interface() - } else if _, ok := boolColumnMap[col.Name]; ok { - val = fieldValue.Interface() } else { // if a bool in a struct, it will not be as a condition because it default is false, // please use Where() instead @@ -519,18 +525,34 @@ func (statement *Statement) Cols(columns ...string) *Statement { return statement } +// Update use only: update all columns func (statement *Statement) AllCols() *Statement { statement.useAllCols = true return statement } +// Update use only: must update columns +func (statement *Statement) MustCols(columns ...string) *Statement { + newColumns := col2NewCols(columns...) + for _, nc := range newColumns { + statement.mustColumnMap[strings.ToLower(nc)] = true + } + return statement +} + +// Update use only: not update columns +/*func (statement *Statement) NotCols(columns ...string) *Statement { + newColumns := col2NewCols(columns...) + for _, nc := range newColumns { + statement.mustColumnMap[strings.ToLower(nc)] = false + } + return statement +}*/ + // indicates that use bool fields as update contents and query contiditions func (statement *Statement) UseBool(columns ...string) *Statement { if len(columns) > 0 { - newColumns := col2NewCols(columns...) - for _, nc := range newColumns { - statement.boolColumnMap[strings.ToLower(nc)] = true - } + statement.MustCols(columns...) } else { statement.allUseBool = true } @@ -721,7 +743,7 @@ func (statement *Statement) genGetSql(bean interface{}) (string, []interface{}) colNames, args := buildConditions(statement.Engine, table, bean, true, true, false, true, statement.allUseBool, statement.useAllCols, - statement.boolColumnMap) + statement.mustColumnMap) statement.ConditionStr = strings.Join(colNames, " AND ") statement.BeanArgs = args @@ -760,7 +782,7 @@ func (statement *Statement) genCountSql(bean interface{}) (string, []interface{} statement.RefTable = table colNames, args := buildConditions(statement.Engine, table, bean, true, true, false, - true, statement.allUseBool, statement.useAllCols, statement.boolColumnMap) + true, statement.allUseBool, statement.useAllCols, statement.mustColumnMap) statement.ConditionStr = strings.Join(colNames, " AND ") statement.BeanArgs = args From 9b23e7d6c089c72ab9abeac334fd3f8bb4acb64f Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 8 Apr 2014 16:46:23 +0800 Subject: [PATCH 11/14] resolved #89: if struct has func, the struct's name is the result --- base_test.go | 24 +++++++++++++++++++++++ engine.go | 54 ++++++++++++++++++++++++++++++++++------------------ helpers.go | 7 ++++++- session.go | 24 ++++++++++++----------- statement.go | 7 ++++--- 5 files changed, 83 insertions(+), 33 deletions(-) diff --git a/base_test.go b/base_test.go index 3199b850..f8d9d794 100644 --- a/base_test.go +++ b/base_test.go @@ -3990,6 +3990,28 @@ func testCompositeKey2(engine *Engine, t *testing.T) { } } +type CustomTableName struct { + Id int64 + Name string +} + +func (c *CustomTableName) TableName() string { + return "customtablename" +} + +func testCustomTableName(engine *Engine, t *testing.T) { + c := new(CustomTableName) + err := engine.DropTables(c) + if err != nil { + t.Error(err) + } + + err = engine.CreateTables(c) + if err != nil { + t.Error(err) + } +} + func testAll(engine *Engine, t *testing.T) { fmt.Println("-------------- directCreateTable --------------") directCreateTable(engine, t) @@ -4100,6 +4122,8 @@ func testAll2(engine *Engine, t *testing.T) { testProcessors(engine, t) fmt.Println("-------------- transaction --------------") transaction(engine, t) + fmt.Println("-------------- testCustomTableName --------------") + testCustomTableName(engine, t) } // !nash! the 3rd set of the test is intended for non-cache enabled engine diff --git a/engine.go b/engine.go index 070c718c..2499d494 100644 --- a/engine.go +++ b/engine.go @@ -157,9 +157,9 @@ func (engine *Engine) NoCascade() *Session { // Set a table use a special cacher func (engine *Engine) MapCacher(bean interface{}, cacher Cacher) { - t := rType(bean) - engine.autoMapType(t) - engine.Tables[t].Cacher = cacher + v := rValue(bean) + engine.autoMapType(v) + engine.Tables[v.Type()].Cacher = cacher } // OpenDB provides a interface to operate database directly. @@ -435,12 +435,13 @@ func (engine *Engine) Having(conditions string) *Session { return session.Having(conditions) } -func (engine *Engine) autoMapType(t reflect.Type) *Table { +func (engine *Engine) autoMapType(v reflect.Value) *Table { + t := v.Type() engine.mutex.RLock() table, ok := engine.Tables[t] engine.mutex.RUnlock() if !ok { - table = engine.mapType(t) + table = engine.mapType(v) engine.mutex.Lock() engine.Tables[t] = table engine.mutex.Unlock() @@ -449,8 +450,8 @@ func (engine *Engine) autoMapType(t reflect.Type) *Table { } func (engine *Engine) autoMap(bean interface{}) *Table { - t := rType(bean) - return engine.autoMapType(t) + v := rValue(bean) + return engine.autoMapType(v) } func (engine *Engine) newTable() *Table { @@ -475,9 +476,24 @@ func addIndex(indexName string, table *Table, col *Column, indexType int) { } } -func (engine *Engine) mapType(t reflect.Type) *Table { +func (engine *Engine) mapType(v reflect.Value) *Table { + t := v.Type() table := engine.newTable() - table.Name = engine.tableMapper.Obj2Table(t.Name()) + method := v.MethodByName("TableName") + if !method.IsValid() { + method = v.Addr().MethodByName("TableName") + } + if method.IsValid() { + params := []reflect.Value{} + results := method.Call(params) + if len(results) == 1 { + table.Name = results[0].Interface().(string) + } + } + + if table.Name == "" { + table.Name = engine.tableMapper.Obj2Table(t.Name()) + } table.Type = t var idFieldColName string @@ -487,7 +503,8 @@ func (engine *Engine) mapType(t reflect.Type) *Table { tag := t.Field(i).Tag ormTagStr := tag.Get(engine.TagIdentifier) var col *Column - fieldType := t.Field(i).Type + fieldValue := v.Field(i) + fieldType := fieldValue.Type() if ormTagStr != "" { col = &Column{FieldName: t.Field(i).Name, Nullable: true, IsPrimaryKey: false, @@ -500,7 +517,7 @@ func (engine *Engine) mapType(t reflect.Type) *Table { } if (strings.ToUpper(tags[0]) == "EXTENDS") && (fieldType.Kind() == reflect.Struct) { - parentTable := engine.mapType(fieldType) + parentTable := engine.mapType(fieldValue) for name, col := range parentTable.Columns { col.FieldName = fmt.Sprintf("%v.%v", fieldType.Name(), col.FieldName) table.Columns[strings.ToLower(name)] = col @@ -671,19 +688,20 @@ func (engine *Engine) mapping(beans ...interface{}) (e error) { engine.mutex.Lock() defer engine.mutex.Unlock() for _, bean := range beans { - t := rType(bean) - engine.Tables[t] = engine.mapType(t) + v := rValue(bean) + engine.Tables[v.Type()] = engine.mapType(v) } return } // If a table has any reocrd func (engine *Engine) IsTableEmpty(bean interface{}) (bool, error) { - t := rType(bean) + v := rValue(bean) + t := v.Type() if t.Kind() != reflect.Struct { return false, errors.New("bean should be a struct or struct's point") } - engine.autoMapType(t) + engine.autoMapType(v) session := engine.NewSession() defer session.Close() rows, err := session.Count(bean) @@ -692,11 +710,11 @@ func (engine *Engine) IsTableEmpty(bean interface{}) (bool, error) { // If a table is exist func (engine *Engine) IsTableExist(bean interface{}) (bool, error) { - t := rType(bean) - if t.Kind() != reflect.Struct { + v := rValue(bean) + if v.Type().Kind() != reflect.Struct { return false, errors.New("bean should be a struct or struct's point") } - table := engine.autoMapType(t) + table := engine.autoMapType(v) session := engine.NewSession() defer session.Close() has, err := session.isTableExist(table.Name) diff --git a/helpers.go b/helpers.go index 96f118f2..25b6ddc8 100644 --- a/helpers.go +++ b/helpers.go @@ -37,9 +37,14 @@ func makeArray(elem string, count int) []string { return res } +func rValue(bean interface{}) reflect.Value { + return reflect.Indirect(reflect.ValueOf(bean)) +} + func rType(bean interface{}) reflect.Type { sliceValue := reflect.Indirect(reflect.ValueOf(bean)) - return reflect.TypeOf(sliceValue.Interface()) + //return reflect.TypeOf(sliceValue.Interface()) + return sliceValue.Type() } func structName(v reflect.Type) string { diff --git a/session.go b/session.go index bc718b9b..b8035acc 100644 --- a/session.go +++ b/session.go @@ -358,12 +358,12 @@ func cleanupProcessorsClosures(slices *[]func(interface{})) { } func (session *Session) scanMapIntoStruct(obj interface{}, objMap map[string][]byte) error { - dataStruct := reflect.Indirect(reflect.ValueOf(obj)) + dataStruct := rValue(obj) if dataStruct.Kind() != reflect.Struct { return errors.New("Expected a pointer to a struct") } - table := session.Engine.autoMapType(rType(obj)) + table := session.Engine.autoMapType(dataStruct) for key, data := range objMap { key = strings.ToLower(key) @@ -1017,12 +1017,14 @@ func (session *Session) Find(rowsSlicePtr interface{}, condiBean ...interface{}) if session.Statement.RefTable == nil { if sliceElementType.Kind() == reflect.Ptr { if sliceElementType.Elem().Kind() == reflect.Struct { - table = session.Engine.autoMapType(sliceElementType.Elem()) + pv := reflect.New(sliceElementType.Elem()) + table = session.Engine.autoMapType(pv.Elem()) } else { return errors.New("slice type") } } else if sliceElementType.Kind() == reflect.Struct { - table = session.Engine.autoMapType(sliceElementType) + pv := reflect.New(sliceElementType) + table = session.Engine.autoMapType(pv.Elem()) } else { return errors.New("slice type") } @@ -1386,13 +1388,12 @@ func (session *Session) getField(dataStruct *reflect.Value, key string, table *T } func (session *Session) row2Bean(rows *sql.Rows, fields []string, fieldsCount int, bean interface{}) error { - - dataStruct := reflect.Indirect(reflect.ValueOf(bean)) + dataStruct := rValue(bean) if dataStruct.Kind() != reflect.Struct { return errors.New("Expected a pointer to a struct") } - table := session.Engine.autoMapType(rType(bean)) + table := session.Engine.autoMapType(dataStruct) var scanResultContainers []interface{} for i := 0; i < fieldsCount; i++ { @@ -1494,7 +1495,7 @@ func (session *Session) row2Bean(rows *sql.Rows, fields []string, fieldsCount in fieldValue.Set(vv) } } else if session.Statement.UseCascade { - table := session.Engine.autoMapType(fieldValue.Type()) + table := session.Engine.autoMapType(*fieldValue) if table != nil { var x int64 if rawValueType.Kind() == reflect.Int64 { @@ -1763,9 +1764,10 @@ func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error } bean := sliceValue.Index(0).Interface() - sliceElementType := rType(bean) + elementValue := rValue(bean) + //sliceElementType := elementValue.Type() - table := session.Engine.autoMapType(sliceElementType) + table := session.Engine.autoMapType(elementValue) session.Statement.RefTable = table size := sliceValue.Len() @@ -2073,7 +2075,7 @@ func (session *Session) bytes2Value(col *Column, fieldValue *reflect.Value, data v = x fieldValue.Set(reflect.ValueOf(v)) } else if session.Statement.UseCascade { - table := session.Engine.autoMapType(fieldValue.Type()) + table := session.Engine.autoMapType(*fieldValue) if table != nil { x, err := strconv.ParseInt(string(data), 10, 64) if err != nil { diff --git a/statement.go b/statement.go index 805f5955..773dd378 100644 --- a/statement.go +++ b/statement.go @@ -113,11 +113,12 @@ func (statement *Statement) Or(querystring string, args ...interface{}) *Stateme // tempororily set table name func (statement *Statement) Table(tableNameOrBean interface{}) *Statement { - t := rType(tableNameOrBean) + v := rValue(tableNameOrBean) + t := v.Type() if t.Kind() == reflect.String { statement.AltTableName = tableNameOrBean.(string) } else if t.Kind() == reflect.Struct { - statement.RefTable = statement.Engine.autoMapType(t) + statement.RefTable = statement.Engine.autoMapType(v) } return statement } @@ -342,7 +343,7 @@ func buildConditions(engine *Engine, table *Table, bean interface{}, val = t } } else { - engine.autoMapType(fieldValue.Type()) + engine.autoMapType(fieldValue) if table, ok := engine.Tables[fieldValue.Type()]; ok { if len(table.PrimaryKeys) == 1 { pkField := reflect.Indirect(fieldValue).FieldByName(table.PKColumns()[0].FieldName) From 61813611169ae385bb2bca36e74015d774e4bd35 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 8 Apr 2014 16:50:43 +0800 Subject: [PATCH 12/14] docs added TableName --- docs/QuickStart.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/QuickStart.md b/docs/QuickStart.md index d787cb0b..c00e9727 100644 --- a/docs/QuickStart.md +++ b/docs/QuickStart.md @@ -141,7 +141,9 @@ engine.SetColumnMapper(SnakeMapper{}) 如果所有的命名都是按照IMapper的映射来操作的,那当然是最理想的。但是如果碰到某个表名或者某个字段名跟映射规则不匹配时,我们就需要别的机制来改变。 -通过`engine.Table()`方法可以改变struct对应的数据库表的名称,通过sturct中field对应的Tag中使用`xorm:"'column_name'"`可以使该field对应的Column名称为指定名称。这里使用两个单引号将Column名称括起来是为了防止名称冲突,因为我们在Tag中还可以对这个Column进行更多的定义。如果名称不冲突的情况,单引号也可以不使用。 +* 如果struct拥有`Tablename() string`的成员方法,那么此方法的返回值即是该struct默认对应的数据库表名。 + +* 通过`engine.Table()`方法可以改变struct对应的数据库表的名称,通过sturct中field对应的Tag中使用`xorm:"'column_name'"`可以使该field对应的Column名称为指定名称。这里使用两个单引号将Column名称括起来是为了防止名称冲突,因为我们在Tag中还可以对这个Column进行更多的定义。如果名称不冲突的情况,单引号也可以不使用。 ### 2.4.Column属性定义 From 81e690f8a84066e543d3b25544fb919cd12941eb Mon Sep 17 00:00:00 2001 From: Nash Tsai Date: Fri, 11 Apr 2014 21:31:44 +0800 Subject: [PATCH 13/14] resolved merge from b1dfd648f25bc8048b36a8c6269e8b84d3bad940 --- session.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/session.go b/session.go index b8035acc..a6c6e54f 100644 --- a/session.go +++ b/session.go @@ -2910,6 +2910,8 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 var sqlStr, inSql string var inArgs []interface{} + doIncVer := false + var verValue *reflect.Value if table.Version != "" && session.Statement.checkVersion { if condition != "" { condition = fmt.Sprintf("WHERE (%v) AND %v = ?", condition, @@ -2932,7 +2934,13 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 session.Engine.Quote(table.Version)+" = "+session.Engine.Quote(table.Version)+" + 1", condition) - condiArgs = append(condiArgs, table.VersionColumn().ValueOf(bean).Interface()) + verValue, err = table.VersionColumn().ValueOf(bean) + if err != nil { + return 0, err + } + + condiArgs = append(condiArgs, verValue.Interface()) + doIncVer = true } else { if condition != "" { condition = "WHERE " + condition @@ -2959,6 +2967,8 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 res, err := session.exec(sqlStr, args...) if err != nil { return 0, err + } else if doIncVer { + verValue.SetInt(verValue.Int() + 1) } if table.Cacher != nil && session.Statement.UseCache { From 69c057be1c39bedec01c0e13c73495c356bddae7 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 12 Apr 2014 21:59:43 +0800 Subject: [PATCH 14/14] bug fixed --- session.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/session.go b/session.go index a6c6e54f..2298e839 100644 --- a/session.go +++ b/session.go @@ -2911,7 +2911,7 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 var sqlStr, inSql string var inArgs []interface{} doIncVer := false - var verValue *reflect.Value + var verValue reflect.Value if table.Version != "" && session.Statement.checkVersion { if condition != "" { condition = fmt.Sprintf("WHERE (%v) AND %v = ?", condition, @@ -2934,10 +2934,10 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 session.Engine.Quote(table.Version)+" = "+session.Engine.Quote(table.Version)+" + 1", condition) - verValue, err = table.VersionColumn().ValueOf(bean) - if err != nil { - return 0, err - } + verValue = table.VersionColumn().ValueOf(bean) + //if err != nil { + // return 0, err + //} condiArgs = append(condiArgs, verValue.Interface()) doIncVer = true