From 57a6bb1157849fcc3b46d2293233a610c5098177 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 21 Apr 2014 16:31:56 +0800 Subject: [PATCH 01/19] bug fixed for mssql dialect --- mssql_dialect.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/mssql_dialect.go b/mssql_dialect.go index 55940136..292bb67c 100644 --- a/mssql_dialect.go +++ b/mssql_dialect.go @@ -203,10 +203,7 @@ func (db *mssql) GetIndexes(tableName string) (map[string]*core.Index, error) { s := `SELECT IXS.NAME AS [INDEX_NAME], C.NAME AS [COLUMN_NAME], -IXS.is_unique AS [IS_UNIQUE], -CASE IXCS.IS_INCLUDED_COLUMN -WHEN 0 THEN 'NONE' -ELSE 'INCLUDED' END AS [IS_INCLUDED_COLUMN] +IXS.is_unique AS [IS_UNIQUE] FROM SYS.INDEXES IXS INNER JOIN SYS.INDEX_COLUMNS IXCS ON IXS.OBJECT_ID=IXCS.OBJECT_ID AND IXS.INDEX_ID = IXCS.INDEX_ID From f76246da0af031d4b2e6ce9db74fac9d3f456ebd Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 21 Apr 2014 17:49:34 +0800 Subject: [PATCH 02/19] small improvement --- oracle_dialect.go | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/oracle_dialect.go b/oracle_dialect.go index 36a2f62d..b22db57a 100644 --- a/oracle_dialect.go +++ b/oracle_dialect.go @@ -223,19 +223,6 @@ func (db *oracle) GetIndexes(tableName string) (map[string]*core.Index, error) { return indexes, nil } -// PgSeqFilter filter SQL replace ?, ? ... to :1, :2 ... -type OracleSeqFilter struct { -} - -func (s *OracleSeqFilter) Do(sql string, dialect core.Dialect, table *core.Table) string { - counts := strings.Count(sql, "?") - for i := 1; i <= counts; i++ { - newstr := ":" + fmt.Sprintf("%v", i) - sql = strings.Replace(sql, "?", newstr, 1) - } - return sql -} - func (db *oracle) Filters() []core.Filter { - return []core.Filter{&core.QuoteFilter{}, &OracleSeqFilter{}, &core.IdFilter{}} + return []core.Filter{&core.QuoteFilter{}, &core.SeqFilter{":", 1}, &core.IdFilter{}} } From a25f371cbf90a962b83dab9eb94ffbd0203afbab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=95=86=E8=AE=AF=E5=9C=A8=E7=BA=BF?= Date: Mon, 21 Apr 2014 23:07:37 +0800 Subject: [PATCH 03/19] update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 商讯在线 --- engine.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/engine.go b/engine.go index 671c1b87..0d7eb18f 100644 --- a/engine.go +++ b/engine.go @@ -34,6 +34,7 @@ type Engine struct { ShowErr bool ShowDebug bool ShowWarn bool + ShowInfo bool //Pool IConnectPool //Filters []core.Filter Logger ILogger // io.Writer @@ -190,7 +191,9 @@ func (engine *Engine) LogError(contents ...interface{}) { // logging error func (engine *Engine) LogInfo(contents ...interface{}) { - engine.Logger.Info(fmt.Sprintln(contents...)) + if engine.ShowInfo { + engine.Logger.Info(fmt.Sprintln(contents...)) + } } // logging debug From a4d3be797f14a400767abbcfe99b2e67c95cb4ce Mon Sep 17 00:00:00 2001 From: Nash Tsai Date: Tue, 22 Apr 2014 18:51:56 +0800 Subject: [PATCH 04/19] Revert "update" This reverts commit a25f371cbf90a962b83dab9eb94ffbd0203afbab. --- engine.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/engine.go b/engine.go index 0d7eb18f..671c1b87 100644 --- a/engine.go +++ b/engine.go @@ -34,7 +34,6 @@ type Engine struct { ShowErr bool ShowDebug bool ShowWarn bool - ShowInfo bool //Pool IConnectPool //Filters []core.Filter Logger ILogger // io.Writer @@ -191,9 +190,7 @@ func (engine *Engine) LogError(contents ...interface{}) { // logging error func (engine *Engine) LogInfo(contents ...interface{}) { - if engine.ShowInfo { - engine.Logger.Info(fmt.Sprintln(contents...)) - } + engine.Logger.Info(fmt.Sprintln(contents...)) } // logging debug From 129f77f43c86c4da81646d45bcb6592b530fd471 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 23 Apr 2014 14:01:53 +0800 Subject: [PATCH 05/19] bug fixed --- session.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/session.go b/session.go index feeda785..c1074601 100644 --- a/session.go +++ b/session.go @@ -3314,7 +3314,7 @@ func genCols(table *core.Table, session *Session, bean interface{}, useCol bool, } if (col.IsCreated || col.IsUpdated) && session.Statement.UseAutoTime { - args = append(args, time.Now()) + args = append(args, session.Engine.NowTime(col.SQLType.Name)) } else if col.IsVersion && session.Statement.checkVersion { args = append(args, 1) } else { From edbdf1e6122fe30667953e198ce428a997053ae0 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 23 Apr 2014 14:57:40 +0800 Subject: [PATCH 06/19] docs small improved --- README.md | 5 +---- README_CN.md | 5 +---- docs/QuickStart.md | 2 +- docs/QuickStartCn.md | 2 +- 4 files changed, 4 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 9c9ecfa7..dc8e4b2a 100644 --- a/README.md +++ b/README.md @@ -123,13 +123,10 @@ Or Please visit [Xorm on Google Groups](https://groups.google.com/forum/#!forum/xorm) -# Contributors +# Contributing If you want to pull request, please see [CONTRIBUTING](https://github.com/go-xorm/xorm/blob/master/CONTRIBUTING.md) -* [Lunny](https://github.com/lunny) -* [Nashtsai](https://github.com/nashtsai) - # LICENSE BSD License diff --git a/README_CN.md b/README_CN.md index c8f3f180..ce9b3f3c 100644 --- a/README_CN.md +++ b/README_CN.md @@ -124,13 +124,10 @@ xorm是一个简单而强大的Go语言ORM库. 通过它可以使数据库操作 请加入QQ群:280360085 进行讨论。 -# 贡献者 +## 贡献 如果您也想为Xorm贡献您的力量,请查看 [CONTRIBUTING](https://github.com/go-xorm/xorm/blob/master/CONTRIBUTING.md) -* [Lunny](https://github.com/lunny) -* [Nashtsai](https://github.com/nashtsai) - ## LICENSE BSD License diff --git a/docs/QuickStart.md b/docs/QuickStart.md index f240806d..5ea49bdc 100644 --- a/docs/QuickStart.md +++ b/docs/QuickStart.md @@ -385,7 +385,7 @@ engine.Cols("age", "name").Update(&user) * Omit(...string) 和cols相反,此函数指定排除某些指定的字段。注意:此方法和Cols方法不可同时使用 ```Go -engine.Cols("age").Update(&user) +engine.Omit("age").Update(&user) // UPDATE user SET name = ? AND department = ? ``` diff --git a/docs/QuickStartCn.md b/docs/QuickStartCn.md index 2b76ca8e..11ea03e2 100644 --- a/docs/QuickStartCn.md +++ b/docs/QuickStartCn.md @@ -419,7 +419,7 @@ engine.Cols("age", "name").Update(&user) * Omit(...string) 和cols相反,此函数指定排除某些指定的字段。注意:此方法和Cols方法不可同时使用 ```Go -engine.Cols("age").Update(&user) +engine.Omit("age").Update(&user) // UPDATE user SET name = ? AND department = ? ``` From f419c31da18a782ad89c235f4640ce8ac5e17be0 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 23 Apr 2014 15:25:27 +0800 Subject: [PATCH 07/19] bug fixs #105 --- statement.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/statement.go b/statement.go index 2de1f84a..b591f189 100644 --- a/statement.go +++ b/statement.go @@ -762,11 +762,12 @@ func (statement *Statement) genCountSql(bean interface{}) (string, []interface{} statement.ConditionStr = strings.Join(colNames, " AND ") statement.BeanArgs = args // count(index fieldname) > count(0) > count(*) - var id string = "0" + // for compitable on kinds of database, just use * + /*var id string = "0" if len(table.PrimaryKeys) == 1 { - id = statement.Engine.Quote(table.PrimaryKeys[0]) - } - return statement.genSelectSql(fmt.Sprintf("COUNT(%v) AS %v", id, statement.Engine.Quote("total"))), append(statement.Params, statement.BeanArgs...) + id = statement.Engine.Quote(statement.TableName()) + "." + statement.Engine.Quote(table.PrimaryKeys[0]) + }*/ + return statement.genSelectSql(fmt.Sprintf("COUNT(*) AS %v", statement.Engine.Quote("total"))), append(statement.Params, statement.BeanArgs...) } func (statement *Statement) genSelectSql(columnStr string) (a string) { From 96d483e8457c554362342cd2a1c75a44c3f63088 Mon Sep 17 00:00:00 2001 From: Holo Date: Wed, 23 Apr 2014 16:47:52 +0800 Subject: [PATCH 08/19] Format Format directory 6.1 --- docs/QuickStartCn.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/QuickStartCn.md b/docs/QuickStartCn.md index 11ea03e2..e5734824 100644 --- a/docs/QuickStartCn.md +++ b/docs/QuickStartCn.md @@ -23,7 +23,7 @@ xorm 快速入门 * [5.6.Count方法](#66) * [5.7.Rows方法](#67) * [6.更新数据](#70) -* [6.1.乐观锁](#71) + * [6.1.乐观锁](#71) * [7.删除数据](#80) * [8.执行SQL查询](#90) * [9.执行SQL命令](#100) From 62e08a781bb31392bd131e8215efd8be39141369 Mon Sep 17 00:00:00 2001 From: Chris Dillon Date: Fri, 25 Apr 2014 11:18:46 -0400 Subject: [PATCH 09/19] Update QuickStart.md "go routines" typo. --- docs/QuickStart.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/QuickStart.md b/docs/QuickStart.md index 5ea49bdc..2b1f5829 100644 --- a/docs/QuickStart.md +++ b/docs/QuickStart.md @@ -58,7 +58,7 @@ engine, err = xorm.NewEngine("sqlite3", "./test.db") defer engine.Close() ``` -Generally, you can only create one engine. Engine supports run on go rutines. +Generally, you can only create one engine. Engine supports run on go routines. xorm supports four drivers now: From f3fc0b9976337ffa0af7fc06688a7b8cd6556e9a Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 30 Apr 2014 10:19:24 +0800 Subject: [PATCH 10/19] remove trace string --- statement.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/statement.go b/statement.go index b591f189..5c08a7f9 100644 --- a/statement.go +++ b/statement.go @@ -353,7 +353,7 @@ func buildConditions(engine *Engine, table *core.Table, bean interface{}, continue } val = engine.FormatTime(col.SQLType.Name, t) - fmt.Println("-------", t, val, col.Name) + //fmt.Println("-------", t, val, col.Name) } else { engine.autoMapType(fieldValue) if table, ok := engine.Tables[fieldValue.Type()]; ok { From 685a4a63fd438a3e2eb30dbd3090fede51a3dfad Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 2 May 2014 08:48:51 +0800 Subject: [PATCH 11/19] bug fixed and docs improved --- docs/QuickStart.md | 2 +- docs/QuickStartCn.md | 2 +- engine.go | 34 ++++----- session.go | 2 +- statement.go | 168 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 184 insertions(+), 24 deletions(-) diff --git a/docs/QuickStart.md b/docs/QuickStart.md index 2b1f5829..a53673e1 100644 --- a/docs/QuickStart.md +++ b/docs/QuickStart.md @@ -58,7 +58,7 @@ engine, err = xorm.NewEngine("sqlite3", "./test.db") defer engine.Close() ``` -Generally, you can only create one engine. Engine supports run on go routines. +You can create many engines for different databases.Generally, you just need create only one engine. Engine supports run on go routines. xorm supports four drivers now: diff --git a/docs/QuickStartCn.md b/docs/QuickStartCn.md index e5734824..607bdda6 100644 --- a/docs/QuickStartCn.md +++ b/docs/QuickStartCn.md @@ -62,7 +62,7 @@ engine, err = xorm.NewEngine("sqlite3", "./test.db") defer engine.Close() ``` -一般如果只针对一个数据库进行操作,只需要创建一个Engine即可。Engine支持在多GoRutine下使用。 +你可以创建一个或多个engine, 不过一般如果操作一个数据库,只需要创建一个Engine即可。Engine支持在多GoRutine下使用。 xorm当前支持五种驱动四个数据库如下: diff --git a/engine.go b/engine.go index 671c1b87..50b45a78 100644 --- a/engine.go +++ b/engine.go @@ -34,8 +34,7 @@ type Engine struct { ShowErr bool ShowDebug bool ShowWarn bool - //Pool IConnectPool - //Filters []core.Filter + Logger ILogger // io.Writer TZLocation *time.Location } @@ -456,15 +455,6 @@ func (engine *Engine) autoMap(bean interface{}) *core.Table { return engine.autoMapType(v) } -/*func (engine *Engine) mapType(t reflect.Type) *core.Table { - return mappingTable(t, engine.TableMapper, engine.ColumnMapper, engine.dialect, engine.TagIdentifier) -}*/ - -/* -func mappingTable(t reflect.Type, tableMapper core.IMapper, colMapper core.IMapper, dialect core.Dialect, tagId string) *core.Table { - table := core.NewEmptyTable() - table.Name = tableMapper.Obj2Table(t.Name()) -*/ func addIndex(indexName string, table *core.Table, col *core.Column, indexType int) { if index, ok := table.Indexes[indexName]; ok { index.AddColumn(col.Name) @@ -524,17 +514,19 @@ func (engine *Engine) mapType(v reflect.Value) *core.Table { if tags[0] == "-" { continue } - if (strings.ToUpper(tags[0]) == "EXTENDS") && - (fieldType.Kind() == reflect.Struct) { + if strings.ToUpper(tags[0]) == "EXTENDS" { + fieldValue = reflect.Indirect(fieldValue) + if fieldValue.Kind() == reflect.Struct { + //parentTable := mappingTable(fieldType, tableMapper, colMapper, dialect, tagId) + parentTable := engine.mapType(fieldValue) + for _, col := range parentTable.Columns() { + col.FieldName = fmt.Sprintf("%v.%v", fieldValue.Type().Name(), col.FieldName) + table.AddColumn(col) + } - //parentTable := mappingTable(fieldType, tableMapper, colMapper, dialect, tagId) - parentTable := engine.mapType(fieldValue) - for _, col := range parentTable.Columns() { - col.FieldName = fmt.Sprintf("%v.%v", fieldType.Name(), col.FieldName) - table.AddColumn(col) + continue } - - continue + //TODO: warning } indexNames := make(map[string]int) @@ -701,7 +693,7 @@ func (engine *Engine) IsTableEmpty(bean interface{}) (bool, error) { session := engine.NewSession() defer session.Close() rows, err := session.Count(bean) - return rows > 0, err + return rows == 0, err } // If a table is exist diff --git a/session.go b/session.go index c1074601..921849eb 100644 --- a/session.go +++ b/session.go @@ -2947,7 +2947,7 @@ func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int6 session.Statement.RefTable = table if session.Statement.ColumnStr == "" { - colNames, args = buildConditions(session.Engine, table, bean, false, false, + colNames, args = buildUpdates(session.Engine, table, bean, false, false, false, false, session.Statement.allUseBool, session.Statement.useAllCols, session.Statement.mustColumnMap) } else { diff --git a/statement.go b/statement.go index 5c08a7f9..1ae46f3d 100644 --- a/statement.go +++ b/statement.go @@ -253,6 +253,174 @@ func (statement *Statement) Table(tableNameOrBean interface{}) *Statement { return results }*/ +// Auto generating conditions according a struct +func buildUpdates(engine *Engine, table *core.Table, bean interface{}, + includeVersion bool, includeUpdated bool, includeNil bool, + includeAutoIncr bool, allUseBool bool, useAllCols bool, + mustColumnMap map[string]bool) ([]string, []interface{}) { + + colNames := make([]string, 0) + var args = make([]interface{}, 0) + for _, col := range table.Columns() { + if !includeVersion && col.IsVersion { + continue + } + if col.IsCreated { + continue + } + if !includeUpdated && col.IsUpdated { + continue + } + if !includeAutoIncr && col.IsAutoIncrement { + continue + } + // + //fmt.Println(engine.dialect.DBType(), Text) + if engine.dialect.DBType() == core.MSSQL && col.SQLType.Name == core.Text { + continue + } + fieldValuePtr, err := col.ValueOf(bean) + if err != nil { + engine.LogError(err) + continue + } + + fieldValue := *fieldValuePtr + 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 { + args = append(args, nil) + colNames = append(colNames, fmt.Sprintf("%v=?", engine.Quote(col.Name))) + } + continue + } else if !fieldValue.IsValid() { + continue + } else { + // dereference ptr type to instance type + fieldValue = fieldValue.Elem() + fieldType = reflect.TypeOf(fieldValue.Interface()) + requiredField = true + } + } + + var val interface{} + switch fieldType.Kind() { + case reflect.Bool: + if allUseBool || requiredField { + 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 + continue + } + case reflect.String: + if !requiredField && fieldValue.String() == "" { + continue + } + // for MyString, should convert to string or panic + if fieldType.String() != reflect.String.String() { + val = fieldValue.String() + } else { + val = fieldValue.Interface() + } + case reflect.Int8, reflect.Int16, reflect.Int, reflect.Int32, reflect.Int64: + if !requiredField && fieldValue.Int() == 0 { + continue + } + val = fieldValue.Interface() + case reflect.Float32, reflect.Float64: + if !requiredField && fieldValue.Float() == 0.0 { + continue + } + val = fieldValue.Interface() + case reflect.Uint8, reflect.Uint16, reflect.Uint, reflect.Uint32, reflect.Uint64: + if !requiredField && fieldValue.Uint() == 0 { + continue + } + val = fieldValue.Interface() + case reflect.Struct: + if fieldType == reflect.TypeOf(time.Now()) { + t := fieldValue.Interface().(time.Time) + if !requiredField && (t.IsZero() || !fieldValue.IsValid()) { + continue + } + val = engine.FormatTime(col.SQLType.Name, t) + //fmt.Println("-------", t, val, col.Name) + } else { + 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) + if pkField.Int() != 0 { + val = pkField.Interface() + } else { + continue + } + } else { + //TODO: how to handler? + } + } else { + val = fieldValue.Interface() + } + } + case reflect.Array, reflect.Slice, reflect.Map: + if fieldValue == reflect.Zero(fieldType) { + continue + } + if fieldValue.IsNil() || !fieldValue.IsValid() || fieldValue.Len() == 0 { + continue + } + + if col.SQLType.IsText() { + bytes, err := json.Marshal(fieldValue.Interface()) + if err != nil { + engine.LogError(err) + continue + } + val = string(bytes) + } else if col.SQLType.IsBlob() { + var bytes []byte + var err error + if (fieldType.Kind() == reflect.Array || fieldType.Kind() == reflect.Slice) && + fieldType.Elem().Kind() == reflect.Uint8 { + if fieldValue.Len() > 0 { + val = fieldValue.Bytes() + } else { + continue + } + } else { + bytes, err = json.Marshal(fieldValue.Interface()) + if err != nil { + engine.LogError(err) + continue + } + val = bytes + } + } else { + continue + } + default: + val = fieldValue.Interface() + } + + args = append(args, val) + colNames = append(colNames, fmt.Sprintf("%v=?", engine.Quote(col.Name))) + } + + return colNames, args +} + // Auto generating conditions according a struct func buildConditions(engine *Engine, table *core.Table, bean interface{}, includeVersion bool, includeUpdated bool, includeNil bool, From 93209fa5b2be4c1e77b90f1bddfa1f07b6dcd2c3 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 2 May 2014 11:11:19 +0800 Subject: [PATCH 12/19] fixed reference of README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dc8e4b2a..f001357f 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Xorm is a simple and powerful ORM for Go. * Query Cache speed up -* Database Reverse support, See [Xorm Tool README](https://github.com/go-xorm/xorm/blob/master/xorm/README.md) +* Database Reverse support, See [Xorm Tool README](https://github.com/go-xorm/cmd/blob/master/README.md) * Simple cascade loading support From 9e147d2fde1acb426b0463c53180512cd652232e Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 4 May 2014 13:53:38 +0800 Subject: [PATCH 13/19] add BeforeSet processor and Dump function --- engine.go | 72 ++++++++++++++++++++++++++++++++++++++++++++++ examples/tables.go | 34 ++++++++++++++++++++++ processors.go | 4 +++ session.go | 65 ++++++++++++++++++++++------------------- sqlite3_dialect.go | 5 ++++ statement.go | 5 +++- 6 files changed, 154 insertions(+), 31 deletions(-) create mode 100644 examples/tables.go diff --git a/engine.go b/engine.go index 50b45a78..d9d49b09 100644 --- a/engine.go +++ b/engine.go @@ -6,6 +6,7 @@ import ( "database/sql" "errors" "fmt" + "io" "os" "reflect" "strconv" @@ -265,6 +266,77 @@ func (engine *Engine) DBMetas() ([]*core.Table, error) { return tables, nil } +func (engine *Engine) DumpAllToFile(fp string) error { + f, err := os.Create(fp) + if err != nil { + return err + } + defer f.Close() + return engine.DumpAll(f) +} + +func (engine *Engine) DumpAll(w io.Writer) error { + tables, err := engine.DBMetas() + if err != nil { + return err + } + + for _, table := range tables { + _, err = io.WriteString(w, engine.dialect.CreateTableSql(table, "", "", "")+"\n\n") + if err != nil { + return err + } + for _, index := range table.Indexes { + _, err = io.WriteString(w, engine.dialect.CreateIndexSql(table.Name, index)+"\n\n") + if err != nil { + return err + } + } + + rows, err := engine.DB().Query("SELECT * FROM " + engine.Quote(table.Name)) + if err != nil { + return err + } + cols, err := rows.Columns() + if err != nil { + return err + } + if len(cols) == 0 { + continue + } + for rows.Next() { + dest := make([]interface{}, len(cols)) + err = rows.ScanSlice(&dest) + if err != nil { + return err + } + + _, err = io.WriteString(w, "INSERT INTO "+engine.Quote(table.Name)+" ("+engine.Quote(strings.Join(cols, engine.Quote(", ")))+") VALUES (") + if err != nil { + return err + } + + var temp string + for _, d := range dest { + if d == nil { + temp += ", NULL" + } else if reflect.TypeOf(d).Kind() == reflect.String { + temp += ", '" + strings.Replace(d.(string), "'", "''", -1) + "'" + } else if reflect.TypeOf(d).Kind() == reflect.Slice { + temp += fmt.Sprintf(", %s", engine.dialect.FormatBytes(d.([]byte))) + } else { + temp += fmt.Sprintf(", %v", d) + } + } + _, err = io.WriteString(w, temp[2:]+");\n\n") + if err != nil { + return err + } + } + } + return nil +} + // use cascade or not func (engine *Engine) Cascade(trueOrFalse ...bool) *Session { session := engine.NewSession() diff --git a/examples/tables.go b/examples/tables.go new file mode 100644 index 00000000..97c842be --- /dev/null +++ b/examples/tables.go @@ -0,0 +1,34 @@ +package main + +import ( + "fmt" + "os" + + "github.com/go-xorm/xorm" + _ "github.com/mattn/go-sqlite3" +) + +func main() { + if len(os.Args) < 2 { + fmt.Println("need db path") + return + } + + orm, err := xorm.NewEngine("sqlite3", os.Args[1]) + if err != nil { + fmt.Println(err) + return + } + defer orm.Close() + orm.ShowSQL = true + + tables, err := orm.DBMetas() + if err != nil { + fmt.Println(err) + return + } + + for _, table := range tables { + fmt.Println(table.Name) + } +} diff --git a/processors.go b/processors.go index 770515e6..03ae8e0f 100644 --- a/processors.go +++ b/processors.go @@ -15,6 +15,10 @@ type BeforeDeleteProcessor interface { BeforeDelete() } +type BeforeSetProcessor interface { + BeforeSet(string, Cell) +} + // !nashtsai! TODO enable BeforeValidateProcessor when xorm start to support validations //// Executed before an object is validated //type BeforeValidateProcessor interface { diff --git a/session.go b/session.go index 921849eb..f312e41b 100644 --- a/session.go +++ b/session.go @@ -883,7 +883,6 @@ func (session *Session) Rows(bean interface{}) (*Rows, error) { // are conditions. beans could be []Struct, []*Struct, map[int64]Struct // map[int64]*Struct func (session *Session) Iterate(bean interface{}, fun IterFunc) error { - rows, err := session.Rows(bean) if err != nil { return err @@ -982,24 +981,6 @@ func (session *Session) Get(bean interface{}) (bool, error) { } else { return false, nil } - - // resultsSlice, err := session.query(sqlStr, args...) - // if err != nil { - // return false, err - // } - // if len(resultsSlice) < 1 { - // return false, nil - // } - - // err = session.scanMapIntoStruct(bean, resultsSlice[0]) - // if err != nil { - // return true, err - // } - // if len(resultsSlice) == 1 { - // return true, nil - // } else { - // return true, errors.New("More than one record") - // } } // Count counts the records. bean's non-empty fields @@ -1441,6 +1422,8 @@ func (session *Session) getField(dataStruct *reflect.Value, key string, table *c return fieldValue } +type Cell *interface{} + func (session *Session) row2Bean(rows *core.Rows, fields []string, fieldsCount int, bean interface{}) error { dataStruct := rValue(bean) if dataStruct.Kind() != reflect.Struct { @@ -1449,18 +1432,24 @@ func (session *Session) row2Bean(rows *core.Rows, fields []string, fieldsCount i table := session.Engine.autoMapType(dataStruct) - scanResultContainers := make([]interface{}, len(fields)) + scanResults := make([]interface{}, len(fields)) for i := 0; i < len(fields); i++ { - var scanResultContainer interface{} - scanResultContainers[i] = &scanResultContainer + var cell interface{} + scanResults[i] = &cell } - if err := rows.Scan(scanResultContainers...); err != nil { + if err := rows.Scan(scanResults...); err != nil { return err } + b, hasBeforeSet := bean.(BeforeSetProcessor) + for ii, key := range fields { + if hasBeforeSet { + b.BeforeSet(fields[ii], Cell(scanResults[ii].(*interface{}))) + } + if fieldValue := session.getField(&dataStruct, key, table); fieldValue != nil { - rawValue := reflect.Indirect(reflect.ValueOf(scanResultContainers[ii])) + rawValue := reflect.Indirect(reflect.ValueOf(scanResults[ii])) //if row is null then ignore if rawValue.Interface() == nil { @@ -1468,7 +1457,18 @@ func (session *Session) row2Bean(rows *core.Rows, fields []string, fieldsCount i continue } - if structConvert, ok := fieldValue.Addr().Interface().(core.Conversion); ok { + if fieldValue.CanAddr() { + if structConvert, ok := fieldValue.Addr().Interface().(core.Conversion); ok { + if data, err := value2Bytes(&rawValue); err == nil { + structConvert.FromDB(data) + } else { + session.Engine.LogError(err) + } + continue + } + } + + if structConvert, ok := fieldValue.Interface().(core.Conversion); ok { if data, err := value2Bytes(&rawValue); err == nil { structConvert.FromDB(data) } else { @@ -2451,6 +2451,16 @@ func (session *Session) value2Interface(col *core.Column, fieldValue reflect.Val } } } + + if fieldConvert, ok := fieldValue.Interface().(core.Conversion); ok { + data, err := fieldConvert.ToDB() + if err != nil { + return 0, err + } else { + return string(data), nil + } + } + fieldType := fieldValue.Type() k := fieldType.Kind() if k == reflect.Ptr { @@ -2470,11 +2480,6 @@ func (session *Session) value2Interface(col *core.Column, fieldValue reflect.Val switch k { case reflect.Bool: return fieldValue.Bool(), nil - /*if fieldValue.Bool() { - return 1, nil - } else { - return 0, nil - }*/ case reflect.String: return fieldValue.String(), nil case reflect.Struct: diff --git a/sqlite3_dialect.go b/sqlite3_dialect.go index 0e19f96c..a889a0ab 100644 --- a/sqlite3_dialect.go +++ b/sqlite3_dialect.go @@ -1,6 +1,7 @@ package xorm import ( + "fmt" "strings" "github.com/go-xorm/core" @@ -44,6 +45,10 @@ func (db *sqlite3) SqlType(c *core.Column) string { } } +func (db *sqlite3) FormatBytes(bs []byte) string { + return fmt.Sprintf("X'%x'", bs) +} + func (db *sqlite3) SupportInsertMany() bool { return true } diff --git a/statement.go b/statement.go index 1ae46f3d..0c2207d8 100644 --- a/statement.go +++ b/statement.go @@ -451,8 +451,11 @@ func buildConditions(engine *Engine, table *core.Table, bean interface{}, } fieldValue := *fieldValuePtr - fieldType := reflect.TypeOf(fieldValue.Interface()) + if fieldValue.Interface() == nil { + continue + } + fieldType := reflect.TypeOf(fieldValue.Interface()) requiredField := useAllCols if b, ok := mustColumnMap[strings.ToLower(col.Name)]; ok { if b { From 53dfe7747a924776657a94d9c5dd4fad2250c3e8 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 5 May 2014 14:45:42 +0800 Subject: [PATCH 14/19] bug fixed for Conversion --- statement.go | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/statement.go b/statement.go index 0c2207d8..02078cb9 100644 --- a/statement.go +++ b/statement.go @@ -297,6 +297,30 @@ func buildUpdates(engine *Engine, table *core.Table, bean interface{}, } } + var val interface{} + + if fieldValue.CanAddr() { + if structConvert, ok := fieldValue.Addr().Interface().(core.Conversion); ok { + data, err := structConvert.ToDB() + if err != nil { + engine.LogError(err) + } else { + val = data + } + continue + } + } + + if structConvert, ok := fieldValue.Interface().(core.Conversion); ok { + data, err := structConvert.ToDB() + if err != nil { + engine.LogError(err) + } else { + val = data + } + continue + } + if fieldType.Kind() == reflect.Ptr { if fieldValue.IsNil() { if includeNil { @@ -314,7 +338,6 @@ func buildUpdates(engine *Engine, table *core.Table, bean interface{}, } } - var val interface{} switch fieldType.Kind() { case reflect.Bool: if allUseBool || requiredField { From 9d2160a82643564f90f3f8cce1a5948de20adaa6 Mon Sep 17 00:00:00 2001 From: Nash Tsai Date: Mon, 5 May 2014 17:19:52 +0800 Subject: [PATCH 15/19] update CONTRIBUTING.md --- CONTRIBUTING.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fcbf9e31..4b806704 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -21,11 +21,11 @@ We appreciate any bug reports, but especially ones with self-contained further) test cases. It's especially helpful if you can submit a pull request with just the failing test case (you'll probably want to pattern it after the tests in -[base_test.go](https://github.com/go-xorm/xorm/blob/master/base_test.go) AND -[benchmark_base_test.go](https://github.com/go-xorm/xorm/blob/master/benchmark_base_test.go). +[base.go](https://github.com/go-xorm/tests/blob/master/base.go) AND +[benchmark.go](https://github.com/go-xorm/tests/blob/master/benchmark.go). If you implements a new database interface, you maybe need to add a _test.go file. -For example, [mysql_test.go](https://github.com/go-xorm/xorm/blob/master/mysql_test.go) +For example, [mysql_test.go](https://github.com/go-xorm/tests/blob/master/mysql/mysql_test.go) ### New functionality From 76598912d7aecd815efd9f66a62f1a0507c036ca Mon Sep 17 00:00:00 2001 From: Nash Tsai Date: Mon, 5 May 2014 17:46:09 +0800 Subject: [PATCH 16/19] updat QuickStart.md --- docs/QuickStart.md | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/docs/QuickStart.md b/docs/QuickStart.md index a53673e1..2e1439c1 100644 --- a/docs/QuickStart.md +++ b/docs/QuickStart.md @@ -335,44 +335,43 @@ affected, err := engine.Insert(user, &questions) Notice: If you want to use transaction on inserting, you should use session.Begin() before calling Insert. -## 5.Query and count - -所有的查询条件不区分调用顺序,但必须在调用Get,Find,Count这三个函数之前调用。同时需要注意的一点是,在调用的参数中,所有的字符字段名均为映射后的数据库的字段名,而不是field的名字。 +## 5. Chainable APIs -### 5.1.查询条件方法 +### 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(int64) -传入一个PK字段的值,作为查询条件 +* Id([]interface{}) +Primary Key lookup * Where(string, …interface{}) -和Where语句中的条件基本相同,作为条件 +As SQL conditional WHERE clause * And(string, …interface{}) -和Where函数中的条件基本相同,作为条件 +Conditional AND * Or(string, …interface{}) -和Where函数中的条件基本相同,作为条件 +Conditional OR * Sql(string, …interface{}) 执行指定的Sql语句,并把结果映射到结构体 * Asc(…string) -指定字段名正序排序 +Ascending ordering on 1 or more fields * Desc(…string) -指定字段名逆序排序 +Descending ordering on 1 or more fields * OrderBy(string) -按照指定的顺序进行排序 +Custom ordering * In(string, …interface{}) -某字段在一些值中 +Conditional IN * Cols(…string) -只查询或更新某些指定的字段,默认是查询所有映射的字段或者根据Update的第一个参数来判断更新的字段。例如: +Explicity specify query or update columns. e.g.,: ```Go engine.Cols("age", "name").Find(&users) // SELECT age, name FROM user @@ -380,10 +379,8 @@ engine.Cols("age", "name").Update(&user) // UPDATE user SET age=? AND name=? ``` -其中的参数"age", "name"也可以写成"age, name",两种写法均可 - * Omit(...string) -和cols相反,此函数指定排除某些指定的字段。注意:此方法和Cols方法不可同时使用 +Inverse function to Cols, to exclude specify query or update columns. Warning: Don't use with Cols() ```Go engine.Omit("age").Update(&user) // UPDATE user SET name = ? AND department = ? From 23ffb3d2a0799ce319696d8821afd9551131ea7a Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 5 May 2014 17:56:04 +0800 Subject: [PATCH 17/19] bug fixed for dump --- engine.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/engine.go b/engine.go index d9d49b09..0143e5b9 100644 --- a/engine.go +++ b/engine.go @@ -297,6 +297,7 @@ func (engine *Engine) DumpAll(w io.Writer) error { if err != nil { return err } + cols, err := rows.Columns() if err != nil { return err @@ -317,15 +318,17 @@ func (engine *Engine) DumpAll(w io.Writer) error { } var temp string - for _, d := range dest { + for i, d := range dest { + col := table.GetColumn(cols[i]) if d == nil { temp += ", NULL" - } else if reflect.TypeOf(d).Kind() == reflect.String { - temp += ", '" + strings.Replace(d.(string), "'", "''", -1) + "'" - } else if reflect.TypeOf(d).Kind() == reflect.Slice { + } else if col.SQLType.IsText() || col.SQLType.IsTime() { + var v = fmt.Sprintf("%s", d) + temp += ", '" + strings.Replace(v, "'", "''", -1) + "'" + } else if col.SQLType.IsBlob() /*reflect.TypeOf(d).Kind() == reflect.Slice*/ { temp += fmt.Sprintf(", %s", engine.dialect.FormatBytes(d.([]byte))) } else { - temp += fmt.Sprintf(", %v", d) + temp += fmt.Sprintf(", %s", d) } } _, err = io.WriteString(w, temp[2:]+");\n\n") From a190f71d40036fb8f208d26b3e9e9ec2af0c5687 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 5 May 2014 18:00:10 +0800 Subject: [PATCH 18/19] quick start docs link changed --- README.md | 2 +- README_CN.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f001357f..730f890a 100644 --- a/README.md +++ b/README.md @@ -96,7 +96,7 @@ Or * [GoWalker](http://gowalker.org/github.com/go-xorm/xorm) -* [Quick Start](https://github.com/go-xorm/xorm/blob/master/docs/QuickStartEn.md) +* [Quick Start](https://github.com/go-xorm/xorm/blob/master/docs/QuickStart.md) # Cases diff --git a/README_CN.md b/README_CN.md index ce9b3f3c..f3974aa3 100644 --- a/README_CN.md +++ b/README_CN.md @@ -94,7 +94,7 @@ xorm是一个简单而强大的Go语言ORM库. 通过它可以使数据库操作 ## 文档 -* [快速开始](https://github.com/go-xorm/xorm/blob/master/docs/QuickStart.md) +* [快速开始](https://github.com/go-xorm/xorm/blob/master/docs/QuickStartCN.md) * [GoWalker代码文档](http://gowalker.org/github.com/go-xorm/xorm) From 84b89289a3bc95f1d27860b222ae2abc73cd5fa5 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 6 May 2014 11:11:44 +0800 Subject: [PATCH 19/19] start & limit implementation for mssql --- statement.go | 53 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 40 insertions(+), 13 deletions(-) diff --git a/statement.go b/statement.go index 02078cb9..5185c91e 100644 --- a/statement.go +++ b/statement.go @@ -974,20 +974,52 @@ func (statement *Statement) genSelectSql(columnStr string) (a string) { distinct = "DISTINCT " } - // !nashtsai! REVIEW Sprintf is considered slowest mean of string concatnation, better to work with builder pattern - a = fmt.Sprintf("SELECT %v%v FROM %v", distinct, columnStr, - statement.Engine.Quote(statement.TableName())) - if statement.JoinStr != "" { - a = fmt.Sprintf("%v %v", a, statement.JoinStr) + var top string + var mssqlCondi string + var orderBy string + if statement.OrderStr != "" { + orderBy = fmt.Sprintf(" ORDER BY %v", statement.OrderStr) } statement.processIdParam() + var whereStr string if statement.WhereStr != "" { - a = fmt.Sprintf("%v WHERE %v", a, statement.WhereStr) + whereStr = fmt.Sprintf(" WHERE %v", statement.WhereStr) if statement.ConditionStr != "" { - a = fmt.Sprintf("%v AND %v", a, statement.ConditionStr) + whereStr = fmt.Sprintf("%v AND %v", whereStr, statement.ConditionStr) } } else if statement.ConditionStr != "" { - a = fmt.Sprintf("%v WHERE %v", a, statement.ConditionStr) + whereStr = fmt.Sprintf(" WHERE %v", statement.ConditionStr) + } + var fromStr string = " FROM " + statement.Engine.Quote(statement.TableName()) + if statement.JoinStr != "" { + fromStr = fmt.Sprintf("%v %v", fromStr, statement.JoinStr) + } + + if statement.Engine.dialect.DBType() == core.MSSQL { + top = fmt.Sprintf(" TOP %d", statement.LimitN) + if statement.Start > 0 { + var column string = "(id)" + if len(statement.RefTable.PKColumns()) == 0 { + for _, index := range statement.RefTable.Indexes { + if len(index.Cols) == 1 { + column = index.Cols[0] + break + } + } + if len(column) == 0 { + column = statement.RefTable.ColumnsSeq()[0] + } + } + mssqlCondi = fmt.Sprintf("(%s NOT IN (SELECT TOP %d %s%s%s%s))", + column, statement.Start, column, fromStr, whereStr, orderBy) + } + } + + // !nashtsai! REVIEW Sprintf is considered slowest mean of string concatnation, better to work with builder pattern + a = fmt.Sprintf("SELECT %v%v%v%v%v", top, distinct, columnStr, + fromStr, whereStr) + if mssqlCondi != "" { + a += " AND " + mssqlCondi } if statement.GroupByStr != "" { @@ -1005,11 +1037,6 @@ func (statement *Statement) genSelectSql(columnStr string) (a string) { } else if statement.LimitN > 0 { a = fmt.Sprintf("%v LIMIT %v", a, statement.LimitN) } - } else { - //TODO: for mssql, should handler limit. - /*SELECT * FROM ( - SELECT *, ROW_NUMBER() OVER (ORDER BY id desc) as row FROM "userinfo" - ) a WHERE row > [start] and row <= [start+limit] order by id desc*/ } return