diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..90773768 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,17 @@ +language: go + +go: + - 1.6 + - 1.7 + +before_install: + +install: + - go get github.com/go-xorm/core + - go get github.com/go-xorm/builder + +script: + - go test + +after_success: + - bash <(curl -s https://codecov.io/bash) \ No newline at end of file diff --git a/README.md b/README.md index 6a224f33..12f6ca90 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,7 @@ Xorm is a simple and powerful ORM for Go. -[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/go-xorm/xorm?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) - -[![Build Status](https://drone.io/github.com/go-xorm/tests/status.png)](https://drone.io/github.com/go-xorm/tests/latest) +[![CircleCI](https://circleci.com/gh/go-xorm/xorm/tree/master.svg?style=svg)](https://circleci.com/gh/go-xorm/xorm/tree/master) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/go-xorm/xorm?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) # Notice @@ -30,6 +28,7 @@ The last master version is not backwards compatible. You should use `engine.Show * Optimistic Locking support +* SQL Builder support via [github.com/go-xorm/builder](https://github.com/go-xorm/builder) # Drivers Support @@ -51,10 +50,15 @@ Drivers for Go's sql package which currently support database/sql includes: * Oracle: [github.com/mattn/go-oci8](https://github.com/mattn/go-oci8) (experiment) -* ql: [github.com/cznic/ql](https://github.com/cznic/ql) (experiment) - # Changelog +* **v0.6.0** + * remove support for ql + * add query condition builder support via [github.com/go-xorm/builder](https://github.com/go-xorm/builder), so `Where`, `And`, `Or` +methods can use `builder.Cond` as parameter + * add Sum, SumInt, SumInt64 and NotIn methods + * some bugs fixed + * **v0.5.0** * logging interface changed * some bugs fixed @@ -234,6 +238,13 @@ counts, err := engine.Count(&user) // SELECT count(*) AS total FROM user ``` +* Query conditions builder + +```Go +err := engine.Where(builder.NotIn("a", 1, 2).And(builder.In("b", "c", "d", "e"))).Find(&users) +// SELECT id, name ... FROM user WHERE a NOT IN (?, ?) AND b IN (?, ?, ?) +``` + # Cases * [github.com/m3ng9i/qreader](https://github.com/m3ng9i/qreader) diff --git a/README_CN.md b/README_CN.md index ac6aa410..3919b3ed 100644 --- a/README_CN.md +++ b/README_CN.md @@ -4,9 +4,7 @@ xorm是一个简单而强大的Go语言ORM库. 通过它可以使数据库操作非常简便。 -[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/go-xorm/xorm?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) - -[![Build Status](https://drone.io/github.com/go-xorm/tests/status.png)](https://drone.io/github.com/go-xorm/tests/latest) [![Go Walker](http://gowalker.org/api/v1/badge)](http://gowalker.org/github.com/go-xorm/xorm) +[![CircleCI](https://circleci.com/gh/go-xorm/xorm/tree/master.svg?style=svg)](https://circleci.com/gh/go-xorm/xorm/tree/master) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/go-xorm/xorm?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) # 注意 @@ -32,6 +30,8 @@ xorm是一个简单而强大的Go语言ORM库. 通过它可以使数据库操作 * 支持记录版本(即乐观锁) +* 内置SQL Builder支持 + ## 驱动支持 目前支持的Go数据库驱动和对应的数据库如下: @@ -52,10 +52,15 @@ xorm是一个简单而强大的Go语言ORM库. 通过它可以使数据库操作 * Oracle: [github.com/mattn/go-oci8](https://github.com/mattn/go-oci8) (试验性支持) -* ql: [github.com/cznic/ql](https://github.com/cznic/ql) (试验性支持) - ## 更新日志 +* **v0.6.0** + * 去除对 ql 的支持 + * 新增条件查询分析器 [github.com/go-xorm/builder](https://github.com/go-xorm/builder), 从因此 `Where, And, Or` 函数 +将可以用 `builder.Cond` 作为条件组合 + * 新增 Sum, SumInt, SumInt64 和 NotIn 函数 + * Bug修正 + * **v0.5.0** * logging接口进行不兼容改变 * Bug修正 @@ -234,6 +239,13 @@ counts, err := engine.Count(&user) // SELECT count(*) AS total FROM user ``` +* 条件编辑器 + +```Go +err := engine.Where(builder.NotIn("a", 1, 2).And(builder.In("b", "c", "d", "e"))).Find(&users) +// SELECT id, name ... FROM user WHERE a NOT IN (?, ?) AND b IN (?, ?, ?) +``` + # 案例 * [github.com/m3ng9i/qreader](https://github.com/m3ng9i/qreader) diff --git a/VERSION b/VERSION index 0976f6e1..22c1aa4d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -xorm v0.5.5.0711 +xorm v0.6.0.1022 \ No newline at end of file diff --git a/circle.yml b/circle.yml new file mode 100644 index 00000000..8faa627d --- /dev/null +++ b/circle.yml @@ -0,0 +1,25 @@ +dependencies: + override: + # './...' is a relative pattern which means all subdirectories + - go get -t -d -v ./... + - go get -t -d -v github.com/go-xorm/tests + - go build -v + +database: + override: + - mysql -u root -e "CREATE DATABASE xorm_test DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci" + - mysql -u root -e "CREATE DATABASE xorm_test1 DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci" + - mysql -u root -e "CREATE DATABASE xorm_test2 DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci" + - mysql -u root -e "CREATE DATABASE xorm_test3 DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci" + - createdb -p 5432 -e -U postgres xorm_test + - createdb -p 5432 -e -U postgres xorm_test1 + - createdb -p 5432 -e -U postgres xorm_test2 + - createdb -p 5432 -e -U postgres xorm_test3 + +test: + override: + # './...' is a relative pattern which means all subdirectories + - go test -v -race + - cd /home/ubuntu/.go_workspace/src/github.com/go-xorm/tests && ./sqlite3.sh + - cd /home/ubuntu/.go_workspace/src/github.com/go-xorm/tests && ./mysql.sh + - cd /home/ubuntu/.go_workspace/src/github.com/go-xorm/tests && ./postgres.sh \ No newline at end of file diff --git a/doc.go b/doc.go index e5c35c67..cc71930d 100644 --- a/doc.go +++ b/doc.go @@ -38,7 +38,7 @@ ORM Methods There are 7 major ORM methods and many helpful methods to use to operate database. -1. Insert one or multipe records to database +1. Insert one or multiple records to database affected, err := engine.Insert(&struct) // INSERT INTO struct () values () @@ -81,7 +81,7 @@ another is Rows affected, err := engine.Id(...).Update(&user) // UPDATE user SET ... -6. Delete one or more records, Delete MUST has conditon +6. Delete one or more records, Delete MUST has condition affected, err := engine.Where(...).Delete(&user) // DELETE FROM user Where ... diff --git a/engine.go b/engine.go index 2e389055..8591785e 100644 --- a/engine.go +++ b/engine.go @@ -46,7 +46,7 @@ type Engine struct { disableGlobalCache bool } -// ShowSQL show SQL statment or not on logger if log level is great than INFO +// ShowSQL show SQL statement or not on logger if log level is great than INFO func (engine *Engine) ShowSQL(show ...bool) { engine.logger.ShowSQL(show...) if len(show) == 0 { @@ -56,7 +56,7 @@ func (engine *Engine) ShowSQL(show ...bool) { } } -// ShowExecTime show SQL statment and execute time or not on logger if log level is great than INFO +// ShowExecTime show SQL statement and execute time or not on logger if log level is great than INFO func (engine *Engine) ShowExecTime(show ...bool) { if len(show) == 0 { engine.showExecTime = true @@ -117,54 +117,61 @@ func (engine *Engine) SupportInsertMany() bool { return engine.dialect.SupportInsertMany() } -// QuoteStr Engine's database use which charactor as quote. +// QuoteStr Engine's database use which character as quote. // mysql, sqlite use ` and postgres use " func (engine *Engine) QuoteStr() string { return engine.dialect.QuoteStr() } // Quote Use QuoteStr quote the string sql -func (engine *Engine) Quote(sql string) string { - return engine.quoteTable(sql) +func (engine *Engine) Quote(value string) string { + value = strings.TrimSpace(value) + if len(value) == 0 { + return value + } + + if string(value[0]) == engine.dialect.QuoteStr() || value[0] == '`' { + return value + } + + value = strings.Replace(value, ".", engine.dialect.QuoteStr()+"."+engine.dialect.QuoteStr(), -1) + + return engine.dialect.QuoteStr() + value + engine.dialect.QuoteStr() +} + +// QuoteTo quotes string and writes into the buffer +func (engine *Engine) QuoteTo(buf *bytes.Buffer, value string) { + + if buf == nil { + return + } + + value = strings.TrimSpace(value) + if value == "" { + return + } + + if string(value[0]) == engine.dialect.QuoteStr() || value[0] == '`' { + buf.WriteString(value) + return + } + + value = strings.Replace(value, ".", engine.dialect.QuoteStr()+"."+engine.dialect.QuoteStr(), -1) + + buf.WriteString(engine.dialect.QuoteStr()) + buf.WriteString(value) + buf.WriteString(engine.dialect.QuoteStr()) } func (engine *Engine) quote(sql string) string { return engine.dialect.QuoteStr() + sql + engine.dialect.QuoteStr() } -func (engine *Engine) quoteColumn(keyName string) string { - if len(keyName) == 0 { - return keyName - } - - keyName = strings.TrimSpace(keyName) - keyName = strings.Replace(keyName, "`", "", -1) - keyName = strings.Replace(keyName, engine.QuoteStr(), "", -1) - - keyName = strings.Replace(keyName, ",", engine.dialect.QuoteStr()+","+engine.dialect.QuoteStr(), -1) - keyName = strings.Replace(keyName, ".", engine.dialect.QuoteStr()+"."+engine.dialect.QuoteStr(), -1) - - return engine.dialect.QuoteStr() + keyName + engine.dialect.QuoteStr() -} - -func (engine *Engine) quoteTable(keyName string) string { - keyName = strings.TrimSpace(keyName) - if len(keyName) == 0 { - return keyName - } - - if string(keyName[0]) == engine.dialect.QuoteStr() || keyName[0] == '`' { - return keyName - } - - keyName = strings.Replace(keyName, ".", engine.dialect.QuoteStr()+"."+engine.dialect.QuoteStr(), -1) - - return engine.dialect.QuoteStr() + keyName + engine.dialect.QuoteStr() -} - // SqlType will be depracated, please use SQLType instead +// +// Deprecated: use SQLType instead func (engine *Engine) SqlType(c *core.Column) string { - return engine.dialect.SqlType(c) + return engine.SQLType(c) } // SQLType A simple wrapper to dialect's core.SqlType method @@ -290,23 +297,24 @@ func (engine *Engine) logSQLExecutionTime(sqlStr string, args []interface{}, exe return executionBlock() } -// Sql will be depracated, please use SQL instead +// Sql provides raw sql input parameter. When you have a complex SQL statement +// and cannot use Where, Id, In and etc. Methods to describe, you can use SQL. +// +// Deprecated: use SQL instead. func (engine *Engine) Sql(querystring string, args ...interface{}) *Session { - session := engine.NewSession() - session.IsAutoClose = true - return session.Sql(querystring, args...) + return engine.SQL(querystring, args...) } -// SQL method let's you manualy write raw SQL and operate +// SQL method let's you manually write raw SQL and operate // For example: // // engine.SQL("select * from user").Find(&users) // // This code will execute "select * from user" and set the records to users -func (engine *Engine) SQL(querystring string, args ...interface{}) *Session { +func (engine *Engine) SQL(query interface{}, args ...interface{}) *Session { session := engine.NewSession() session.IsAutoClose = true - return session.SQL(querystring, args...) + return session.SQL(query, args...) } // NoAutoTime Default if your struct has "created" or "updated" filed tag, the fields @@ -340,8 +348,6 @@ func (engine *Engine) DBMetas() ([]*core.Table, error) { for _, name := range colSeq { table.AddColumn(cols[name]) } - //table.Columns = cols - //table.ColumnsSeq = colSeq indexes, err := engine.dialect.GetIndexes(table.Name) if err != nil { return nil, err @@ -353,7 +359,7 @@ func (engine *Engine) DBMetas() ([]*core.Table, error) { if col := table.GetColumn(name); col != nil { col.Indexes[index.Name] = index.Type } else { - return nil, fmt.Errorf("Unknown col "+name+" in indexes %v of table", index, table.ColumnsSeq()) + return nil, fmt.Errorf("Unknown col %s in indexe %v of table %v, columns %v", name, index.Name, table.Name, table.ColumnsSeq()) } } } @@ -362,18 +368,22 @@ func (engine *Engine) DBMetas() ([]*core.Table, error) { } // DumpAllToFile dump database all table structs and data to a file -func (engine *Engine) DumpAllToFile(fp string) error { +func (engine *Engine) DumpAllToFile(fp string, tp ...core.DbType) error { f, err := os.Create(fp) if err != nil { return err } defer f.Close() - return engine.DumpAll(f) + return engine.DumpAll(f, tp...) } // DumpAll dump database all table structs and data to w -func (engine *Engine) DumpAll(w io.Writer) error { - return engine.dumpAll(w, engine.dialect.DBType()) +func (engine *Engine) DumpAll(w io.Writer, tp ...core.DbType) error { + tables, err := engine.DBMetas() + if err != nil { + return err + } + return engine.DumpTables(tables, w, tp...) } // DumpTablesToFile dump specified tables to SQL file. @@ -391,149 +401,24 @@ func (engine *Engine) DumpTables(tables []*core.Table, w io.Writer, tp ...core.D return engine.dumpTables(tables, w, tp...) } -func (engine *Engine) tableName(beanOrTableName interface{}) (string, error) { - v := rValue(beanOrTableName) - if v.Type().Kind() == reflect.String { - return beanOrTableName.(string), nil - } else if v.Type().Kind() == reflect.Struct { - return engine.tbName(v), nil - } - return "", errors.New("bean should be a struct or struct's point") -} - -func (engine *Engine) tbName(v reflect.Value) string { - if tb, ok := v.Interface().(TableName); ok { - return tb.TableName() - } - if v.CanAddr() { - if tb, ok := v.Addr().Interface().(TableName); ok { - return tb.TableName() - } - } - return engine.TableMapper.Obj2Table(v.Type().Name()) -} - -// DumpAll dump database all table structs and data to w with specify db type -func (engine *Engine) dumpAll(w io.Writer, tp ...core.DbType) error { - tables, err := engine.DBMetas() - if err != nil { - return err - } - - var dialect core.Dialect - if len(tp) == 0 { - dialect = engine.dialect - } else { - dialect = core.QueryDialect(tp[0]) - if dialect == nil { - return errors.New("Unsupported database type.") - } - dialect.Init(nil, engine.dialect.URI(), "", "") - } - - _, err = io.WriteString(w, fmt.Sprintf("/*Generated by xorm v%s %s*/\n\n", - Version, time.Now().In(engine.TZLocation).Format("2006-01-02 15:04:05"))) - if err != nil { - return err - } - - for i, table := range tables { - if i > 0 { - _, err = io.WriteString(w, "\n") - if err != nil { - return err - } - } - _, err = io.WriteString(w, dialect.CreateTableSql(table, "", table.StoreEngine, "")+";\n") - if err != nil { - return err - } - for _, index := range table.Indexes { - _, err = io.WriteString(w, dialect.CreateIndexSql(table.Name, index)+";\n") - if err != nil { - return err - } - } - - rows, err := engine.DB().Query("SELECT * FROM " + engine.Quote(table.Name)) - if err != nil { - return err - } - defer rows.Close() - - 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 "+dialect.Quote(table.Name)+" ("+dialect.Quote(strings.Join(cols, dialect.Quote(", ")))+") VALUES (") - if err != nil { - return err - } - - var temp string - for i, d := range dest { - col := table.GetColumn(cols[i]) - if d == nil { - temp += ", NULL" - } else if col.SQLType.IsText() || col.SQLType.IsTime() { - var v = fmt.Sprintf("%s", d) - temp += ", '" + strings.Replace(v, "'", "''", -1) + "'" - } else if col.SQLType.IsBlob() { - if reflect.TypeOf(d).Kind() == reflect.Slice { - temp += fmt.Sprintf(", %s", dialect.FormatBytes(d.([]byte))) - } else if reflect.TypeOf(d).Kind() == reflect.String { - temp += fmt.Sprintf(", '%s'", d.(string)) - } - } else if col.SQLType.IsNumeric() { - switch reflect.TypeOf(d).Kind() { - case reflect.Slice: - temp += fmt.Sprintf(", %s", string(d.([]byte))) - default: - temp += fmt.Sprintf(", %v", d) - } - } else { - s := fmt.Sprintf("%v", d) - if strings.Contains(s, ":") || strings.Contains(s, "-") { - temp += fmt.Sprintf(", '%s'", s) - } else { - temp += fmt.Sprintf(", %s", s) - } - } - } - _, err = io.WriteString(w, temp[2:]+");\n") - if err != nil { - return err - } - } - } - return nil -} - -// DumpAll dump database all table structs and data to w with specify db type +// dumpTables dump database all table structs and data to w with specify db type func (engine *Engine) dumpTables(tables []*core.Table, w io.Writer, tp ...core.DbType) error { var dialect core.Dialect + var distDBName string if len(tp) == 0 { dialect = engine.dialect + distDBName = string(engine.dialect.DBType()) } else { dialect = core.QueryDialect(tp[0]) if dialect == nil { - return errors.New("Unsupported database type.") + return errors.New("Unsupported database type") } dialect.Init(nil, engine.dialect.URI(), "", "") + distDBName = string(tp[0]) } _, err := io.WriteString(w, fmt.Sprintf("/*Generated by xorm v%s %s, from %s to %s*/\n\n", - Version, time.Now().In(engine.TZLocation).Format("2006-01-02 15:04:05"), engine.dialect.DBType(), dialect.DBType())) + Version, time.Now().In(engine.TZLocation).Format("2006-01-02 15:04:05"), engine.dialect.DBType(), strings.ToUpper(distDBName))) if err != nil { return err } @@ -556,19 +441,15 @@ func (engine *Engine) dumpTables(tables []*core.Table, w io.Writer, tp ...core.D } } - rows, err := engine.DB().Query("SELECT * FROM " + engine.Quote(table.Name)) + cols := table.ColumnsSeq() + colNames := dialect.Quote(strings.Join(cols, dialect.Quote(", "))) + + rows, err := engine.DB().Query("SELECT " + colNames + " FROM " + engine.Quote(table.Name)) if err != nil { return err } defer rows.Close() - 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) @@ -576,7 +457,7 @@ func (engine *Engine) dumpTables(tables []*core.Table, w io.Writer, tp ...core.D return err } - _, err = io.WriteString(w, "INSERT INTO "+dialect.Quote(table.Name)+" ("+dialect.Quote(strings.Join(cols, dialect.Quote(", ")))+") VALUES (") + _, err = io.WriteString(w, "INSERT INTO "+dialect.Quote(table.Name)+" ("+colNames+") VALUES (") if err != nil { return err } @@ -584,6 +465,10 @@ func (engine *Engine) dumpTables(tables []*core.Table, w io.Writer, tp ...core.D var temp string for i, d := range dest { col := table.GetColumn(cols[i]) + if col == nil { + return errors.New("unknow column error") + } + if d == nil { temp += ", NULL" } else if col.SQLType.IsText() || col.SQLType.IsTime() { @@ -603,6 +488,18 @@ func (engine *Engine) dumpTables(tables []*core.Table, w io.Writer, tp ...core.D switch reflect.TypeOf(d).Kind() { case reflect.Slice: temp += fmt.Sprintf(", %s", string(d.([]byte))) + case reflect.Int16, reflect.Int8, reflect.Int32, reflect.Int64, reflect.Int: + if col.SQLType.Name == core.Bool { + temp += fmt.Sprintf(", %v", strconv.FormatBool(reflect.ValueOf(d).Int() > 0)) + } else { + temp += fmt.Sprintf(", %v", d) + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + if col.SQLType.Name == core.Bool { + temp += fmt.Sprintf(", %v", strconv.FormatBool(reflect.ValueOf(d).Uint() > 0)) + } else { + temp += fmt.Sprintf(", %v", d) + } default: temp += fmt.Sprintf(", %v", d) } @@ -628,6 +525,33 @@ func (engine *Engine) dumpTables(tables []*core.Table, w io.Writer, tp ...core.D return nil } +func (engine *Engine) tableName(beanOrTableName interface{}) (string, error) { + v := rValue(beanOrTableName) + if v.Type().Kind() == reflect.String { + return beanOrTableName.(string), nil + } else if v.Type().Kind() == reflect.Struct { + return engine.tbName(v), nil + } + return "", errors.New("bean should be a struct or struct's point") +} + +func (engine *Engine) tbName(v reflect.Value) string { + if tb, ok := v.Interface().(TableName); ok { + return tb.TableName() + } + + if v.Type().Kind() == reflect.Ptr { + if tb, ok := reflect.Indirect(v).Interface().(TableName); ok { + return tb.TableName() + } + } else if v.CanAddr() { + if tb, ok := v.Addr().Interface().(TableName); ok { + return tb.TableName() + } + } + return engine.TableMapper.Obj2Table(reflect.Indirect(v).Type().Name()) +} + // Cascade use cascade or not func (engine *Engine) Cascade(trueOrFalse ...bool) *Session { session := engine.NewSession() @@ -636,10 +560,10 @@ func (engine *Engine) Cascade(trueOrFalse ...bool) *Session { } // Where method provide a condition query -func (engine *Engine) Where(querystring string, args ...interface{}) *Session { +func (engine *Engine) Where(query interface{}, args ...interface{}) *Session { session := engine.NewSession() session.IsAutoClose = true - return session.Where(querystring, args...) + return session.Where(query, args...) } // Id will be depracated, please use ID instead @@ -700,7 +624,7 @@ func (engine *Engine) Select(str string) *Session { return session.Select(str) } -// Cols only use the paramters as select or update columns +// Cols only use the parameters as select or update columns func (engine *Engine) Cols(columns ...string) *Session { session := engine.NewSession() session.IsAutoClose = true @@ -724,15 +648,15 @@ func (engine *Engine) MustCols(columns ...string) *Session { // UseBool 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. -// If no paramters, it will use all the bool field of struct, or -// it will use paramters's columns +// If no parameters, it will use all the bool field of struct, or +// it will use parameters's columns func (engine *Engine) UseBool(columns ...string) *Session { session := engine.NewSession() session.IsAutoClose = true return session.UseBool(columns...) } -// Omit only not use the paramters as select or update columns +// Omit only not use the parameters as select or update columns func (engine *Engine) Omit(columns ...string) *Session { session := engine.NewSession() session.IsAutoClose = true @@ -845,6 +769,7 @@ func (engine *Engine) Having(conditions string) *Session { func (engine *Engine) autoMapType(v reflect.Value) *core.Table { t := v.Type() engine.mutex.Lock() + defer engine.mutex.Unlock() table, ok := engine.Tables[t] if !ok { table = engine.mapType(v) @@ -857,7 +782,6 @@ func (engine *Engine) autoMapType(v reflect.Value) *core.Table { } } } - engine.mutex.Unlock() return table } @@ -906,6 +830,10 @@ type TableName interface { TableName() string } +var ( + tpTableName = reflect.TypeOf((*TableName)(nil)).Elem() +) + func (engine *Engine) mapType(v reflect.Value) *core.Table { t := v.Type() table := engine.newTable() @@ -1016,7 +944,7 @@ func (engine *Engine) mapType(v reflect.Value) *core.Table { case k == "LOCAL": col.TimeZone = time.Local case strings.HasPrefix(k, "LOCALE(") && strings.HasSuffix(k, ")"): - location := k[len("INDEX")+1 : len(k)-1] + location := k[len("LOCALE")+1 : len(k)-1] col.TimeZone, err = time.LoadLocation(location) if err != nil { engine.logger.Error(err) @@ -1196,12 +1124,26 @@ func (engine *Engine) IsTableExist(beanOrTableName interface{}) (bool, error) { } // IdOf get id from one struct +// +// Deprecated: use IDOf instead. func (engine *Engine) IdOf(bean interface{}) core.PK { + return engine.IDOf(bean) +} + +// IDOf get id from one struct +func (engine *Engine) IDOf(bean interface{}) core.PK { return engine.IdOfV(reflect.ValueOf(bean)) } // IdOfV get id from one value of struct +// +// Deprecated: use IDOfV instead. func (engine *Engine) IdOfV(rv reflect.Value) core.PK { + return engine.IDOfV(rv) +} + +// IDOfV get id from one value of struct +func (engine *Engine) IDOfV(rv reflect.Value) core.PK { v := reflect.Indirect(rv) table := engine.autoMapType(v) pk := make([]interface{}, len(table.PrimaryKeys)) @@ -1323,16 +1265,13 @@ func (engine *Engine) Sync(beans ...interface{}) error { } } else { for _, col := range table.Columns() { - session := engine.NewSession() - session.Statement.RefTable = table - defer session.Close() - isExist, err := session.Engine.dialect.IsColumnExist(tableName, col.Name) + isExist, err := engine.dialect.IsColumnExist(tableName, col.Name) if err != nil { return err } if !isExist { session := engine.NewSession() - session.Statement.RefTable = table + session.Statement.setRefValue(v) defer session.Close() err = session.addColumn(col.Name) if err != nil { @@ -1343,7 +1282,7 @@ func (engine *Engine) Sync(beans ...interface{}) error { for name, index := range table.Indexes { session := engine.NewSession() - session.Statement.RefTable = table + session.Statement.setRefValue(v) defer session.Close() if index.Type == core.UniqueType { //isExist, err := session.isIndexExist(table.Name, name, true) @@ -1353,7 +1292,7 @@ func (engine *Engine) Sync(beans ...interface{}) error { } if !isExist { session := engine.NewSession() - session.Statement.RefTable = table + session.Statement.setRefValue(v) defer session.Close() err = session.addUnique(tableName, name) if err != nil { @@ -1367,7 +1306,7 @@ func (engine *Engine) Sync(beans ...interface{}) error { } if !isExist { session := engine.NewSession() - session.Statement.RefTable = table + session.Statement.setRefValue(v) defer session.Close() err = session.addIndex(tableName, name) if err != nil { diff --git a/error.go b/error.go index 61537a34..2a334f47 100644 --- a/error.go +++ b/error.go @@ -9,11 +9,18 @@ import ( ) var ( - ErrParamsType error = errors.New("Params type error") - ErrTableNotFound error = errors.New("Not found table") - ErrUnSupportedType error = errors.New("Unsupported type error") - ErrNotExist error = errors.New("Not exist error") - ErrCacheFailed error = errors.New("Cache failed") - ErrNeedDeletedCond error = errors.New("Delete need at least one condition") - ErrNotImplemented error = errors.New("Not implemented.") + // ErrParamsType params error + ErrParamsType = errors.New("Params type error") + // ErrTableNotFound table not found error + ErrTableNotFound = errors.New("Not found table") + // ErrUnSupportedType unsupported error + ErrUnSupportedType = errors.New("Unsupported type error") + // ErrNotExist record is not exist error + ErrNotExist = errors.New("Not exist error") + // ErrCacheFailed cache failed error + ErrCacheFailed = errors.New("Cache failed") + // ErrNeedDeletedCond delete needs less one condition error + ErrNeedDeletedCond = errors.New("Delete need at least one condition") + // ErrNotImplemented not implemented + ErrNotImplemented = errors.New("Not implemented") ) diff --git a/examples/cache.go b/examples/cache.go index 54525838..72d987df 100644 --- a/examples/cache.go +++ b/examples/cache.go @@ -8,6 +8,7 @@ import ( _ "github.com/mattn/go-sqlite3" ) +// User describes a user type User struct { Id int64 Name string @@ -38,7 +39,7 @@ func main() { return } - users := make([]User, 0) + var users []User err = Orm.Find(&users) if err != nil { fmt.Println(err) @@ -47,8 +48,7 @@ func main() { fmt.Println("users:", users) - users2 := make([]User, 0) - + var users2 []User err = Orm.Find(&users2) if err != nil { fmt.Println(err) @@ -57,8 +57,7 @@ func main() { fmt.Println("users2:", users2) - users3 := make([]User, 0) - + var users3 []User err = Orm.Find(&users3) if err != nil { fmt.Println(err) diff --git a/examples/cachegoroutine.go b/examples/cachegoroutine.go index 6e2028b6..815e0ad1 100644 --- a/examples/cachegoroutine.go +++ b/examples/cachegoroutine.go @@ -10,6 +10,7 @@ import ( _ "github.com/mattn/go-sqlite3" ) +// User describes a user type User struct { Id int64 Name string @@ -24,7 +25,7 @@ func mysqlEngine() (*xorm.Engine, error) { return xorm.NewEngine("mysql", "root:@/test?charset=utf8") } -var u *User = &User{} +var u = &User{} func test(engine *xorm.Engine) { err := engine.CreateTables(u) diff --git a/examples/conversion.go b/examples/conversion.go index 95736d0d..c0646ee1 100644 --- a/examples/conversion.go +++ b/examples/conversion.go @@ -9,35 +9,39 @@ import ( _ "github.com/mattn/go-sqlite3" ) +// Status describes a status type Status struct { Name string Color string } +// defines some statuses var ( - Registed Status = Status{"Registed", "white"} - Approved Status = Status{"Approved", "green"} - Removed Status = Status{"Removed", "red"} - Statuses map[string]Status = map[string]Status{ - Registed.Name: Registed, - Approved.Name: Approved, - Removed.Name: Removed, + Registered = Status{"Registered", "white"} + Approved = Status{"Approved", "green"} + Removed = Status{"Removed", "red"} + Statuses = map[string]Status{ + Registered.Name: Registered, + Approved.Name: Approved, + Removed.Name: Removed, } ) +// FromDB implemented xorm.Conversion convent database data to self func (s *Status) FromDB(bytes []byte) error { if r, ok := Statuses[string(bytes)]; ok { *s = r return nil - } else { - return errors.New("no this data") } + return errors.New("no this data") } +// ToDB implemented xorm.Conversion convent to database data func (s *Status) ToDB() ([]byte, error) { return []byte(s.Name), nil } +// User describes a user type User struct { Id int64 Name string @@ -60,7 +64,7 @@ func main() { return } - _, err = Orm.Insert(&User{1, "xlw", Registed}) + _, err = Orm.Insert(&User{1, "xlw", Registered}) if err != nil { fmt.Println(err) return diff --git a/examples/derive.go b/examples/derive.go index fa1e0b1a..86529eea 100644 --- a/examples/derive.go +++ b/examples/derive.go @@ -8,17 +8,20 @@ import ( _ "github.com/mattn/go-sqlite3" ) +// User describes a user type User struct { Id int64 Name string } +// LoginInfo describes a login information type LoginInfo struct { Id int64 IP string UserId int64 } +// LoginInfo1 describes a login information type LoginInfo1 struct { LoginInfo `xorm:"extends"` UserName string @@ -28,27 +31,27 @@ func main() { f := "derive.db" os.Remove(f) - Orm, err := xorm.NewEngine("sqlite3", f) + orm, err := xorm.NewEngine("sqlite3", f) if err != nil { fmt.Println(err) return } - defer Orm.Close() - Orm.ShowSQL(true) - err = Orm.CreateTables(&User{}, &LoginInfo{}) + defer orm.Close() + orm.ShowSQL(true) + err = orm.CreateTables(&User{}, &LoginInfo{}) if err != nil { fmt.Println(err) return } - _, err = Orm.Insert(&User{1, "xlw"}, &LoginInfo{1, "127.0.0.1", 1}) + _, err = orm.Insert(&User{1, "xlw"}, &LoginInfo{1, "127.0.0.1", 1}) if err != nil { fmt.Println(err) return } info := LoginInfo{} - _, err = Orm.Id(1).Get(&info) + _, err = orm.Id(1).Get(&info) if err != nil { fmt.Println(err) return @@ -56,7 +59,7 @@ func main() { fmt.Println(info) infos := make([]LoginInfo1, 0) - err = Orm.Sql(`select *, (select name from user where id = login_info.user_id) as user_name from + err = orm.Sql(`select *, (select name from user where id = login_info.user_id) as user_name from login_info limit 10`).Find(&infos) if err != nil { fmt.Println(err) diff --git a/examples/find.go b/examples/find.go index e47bc28d..d80a698c 100644 --- a/examples/find.go +++ b/examples/find.go @@ -8,6 +8,7 @@ import ( "github.com/go-xorm/xorm" ) +// User describes a user type User struct { Id int64 Name string @@ -19,27 +20,27 @@ func main() { f := "conversion.db" os.Remove(f) - Orm, err := xorm.NewEngine("sqlite3", f) + orm, err := xorm.NewEngine("sqlite3", f) if err != nil { fmt.Println(err) return } - Orm.ShowSQL(true) + orm.ShowSQL(true) - err = Orm.CreateTables(&User{}) + err = orm.CreateTables(&User{}) if err != nil { fmt.Println(err) return } - _, err = Orm.Insert(&User{Id: 1, Name: "xlw"}) + _, err = orm.Insert(&User{Id: 1, Name: "xlw"}) if err != nil { fmt.Println(err) return } users := make([]User, 0) - err = Orm.Find(&users) + err = orm.Find(&users) if err != nil { fmt.Println(err) return diff --git a/examples/goroutine.go b/examples/goroutine.go index f99c9fbe..629ea9ac 100644 --- a/examples/goroutine.go +++ b/examples/goroutine.go @@ -10,6 +10,7 @@ import ( _ "github.com/mattn/go-sqlite3" ) +// User describes a user type User struct { Id int64 Name string @@ -24,7 +25,7 @@ func mysqlEngine() (*xorm.Engine, error) { return xorm.NewEngine("mysql", "root:@/test?charset=utf8") } -var u *User = &User{} +var u = &User{} func test(engine *xorm.Engine) { err := engine.CreateTables(u) diff --git a/examples/maxconnect.go b/examples/maxconnect.go index 4fdfdf78..507cbc3c 100644 --- a/examples/maxconnect.go +++ b/examples/maxconnect.go @@ -10,6 +10,7 @@ import ( _ "github.com/mattn/go-sqlite3" ) +// User describes a user type User struct { Id int64 Name string @@ -24,7 +25,7 @@ func mysqlEngine() (*xorm.Engine, error) { return xorm.NewEngine("mysql", "root:@/test?charset=utf8") } -var u *User = &User{} +var u = &User{} func test(engine *xorm.Engine) { err := engine.CreateTables(u) diff --git a/examples/singlemapping.go b/examples/singlemapping.go index f6113827..3ae0fd1a 100644 --- a/examples/singlemapping.go +++ b/examples/singlemapping.go @@ -8,11 +8,13 @@ import ( _ "github.com/mattn/go-sqlite3" ) +// User describes a user type User struct { Id int64 Name string } +// LoginInfo describes a login information type LoginInfo struct { Id int64 IP string @@ -27,26 +29,26 @@ func main() { f := "singleMapping.db" os.Remove(f) - Orm, err := xorm.NewEngine("sqlite3", f) + orm, err := xorm.NewEngine("sqlite3", f) if err != nil { fmt.Println(err) return } - Orm.ShowSQL(true) - err = Orm.CreateTables(&User{}, &LoginInfo{}) + orm.ShowSQL(true) + err = orm.CreateTables(&User{}, &LoginInfo{}) if err != nil { fmt.Println(err) return } - _, err = Orm.Insert(&User{1, "xlw"}, &LoginInfo{1, "127.0.0.1", 1, "", 23}) + _, err = orm.Insert(&User{1, "xlw"}, &LoginInfo{1, "127.0.0.1", 1, "", 23}) if err != nil { fmt.Println(err) return } info := LoginInfo{} - _, err = Orm.Id(1).Get(&info) + _, err = orm.Id(1).Get(&info) if err != nil { fmt.Println(err) return diff --git a/examples/sync.go b/examples/sync.go index 8f2eaf21..134e2a77 100644 --- a/examples/sync.go +++ b/examples/sync.go @@ -9,6 +9,7 @@ import ( _ "github.com/mattn/go-sqlite3" ) +// SyncUser2 describes a user type SyncUser2 struct { Id int64 Name string `xorm:"unique"` @@ -20,6 +21,7 @@ type SyncUser2 struct { Date int } +// SyncLoginInfo2 describes a login information type SyncLoginInfo2 struct { Id int64 IP string `xorm:"index"` @@ -60,7 +62,7 @@ func main() { engines := []engineFunc{postgresEngine} for _, enginefunc := range engines { Orm, err := enginefunc() - fmt.Println("--------", Orm.DriverName, "----------") + fmt.Println("--------", Orm.DriverName(), "----------") if err != nil { fmt.Println(err) return diff --git a/helpers.go b/helpers.go index 0d81f3e0..3b603148 100644 --- a/helpers.go +++ b/helpers.go @@ -102,7 +102,7 @@ func splitTag(tag string) (tags []string) { } } if lastIdx < len(tag) { - tags = append(tags, strings.TrimSpace(tag[lastIdx:len(tag)])) + tags = append(tags, strings.TrimSpace(tag[lastIdx:])) } return } @@ -207,10 +207,6 @@ func isPKZero(pk core.PK) bool { return false } -func equalNoCase(s1, s2 string) bool { - return strings.ToLower(s1) == strings.ToLower(s2) -} - func indexNoCase(s, sep string) int { return strings.Index(strings.ToLower(s), strings.ToLower(sep)) } @@ -583,9 +579,8 @@ func genCols(table *core.Table, session *Session, bean interface{}, useCol bool, args := make([]interface{}, 0, len(table.ColumnsSeq())) for _, col := range table.Columns() { - lColName := strings.ToLower(col.Name) if useCol && !col.IsVersion && !col.IsCreated && !col.IsUpdated { - if _, ok := session.Statement.columnMap[lColName]; !ok { + if _, ok := getFlagForColumn(session.Statement.columnMap, col); !ok { continue } } @@ -621,25 +616,26 @@ func genCols(table *core.Table, session *Session, bean interface{}, useCol bool, } if session.Statement.ColumnStr != "" { - if _, ok := session.Statement.columnMap[lColName]; !ok { + if _, ok := getFlagForColumn(session.Statement.columnMap, col); !ok { continue } } if session.Statement.OmitStr != "" { - if _, ok := session.Statement.columnMap[lColName]; ok { + if _, ok := getFlagForColumn(session.Statement.columnMap, col); ok { continue } } // !evalphobia! set fieldValue as nil when column is nullable and zero-value - if _, ok := session.Statement.nullableMap[lColName]; ok { + if _, ok := getFlagForColumn(session.Statement.nullableMap, col); ok { if col.Nullable && isZero(fieldValue.Interface()) { var nilValue *int fieldValue = reflect.ValueOf(nilValue) } } - if (col.IsCreated || col.IsUpdated) && session.Statement.UseAutoTime { + if (col.IsCreated || col.IsUpdated) && session.Statement.UseAutoTime /*&& isZero(fieldValue.Interface())*/ { + // if time is non-empty, then set to auto time val, t := session.Engine.NowTime2(col.SQLType.Name) args = append(args, val) @@ -670,3 +666,23 @@ func genCols(table *core.Table, session *Session, bean interface{}, useCol bool, func indexName(tableName, idxName string) string { return fmt.Sprintf("IDX_%v_%v", tableName, idxName) } + +func getFlagForColumn(m map[string]bool, col *core.Column) (val bool, has bool) { + + if len(m) == 0 { + return false, false + } + + n := len(col.Name) + + for mk := range m { + if len(mk) != n { + continue + } + if strings.EqualFold(mk, col.Name) { + return m[mk], true + } + } + + return false, false +} diff --git a/logger.go b/logger.go index b4f97378..727d030a 100644 --- a/logger.go +++ b/logger.go @@ -12,6 +12,7 @@ import ( "github.com/go-xorm/core" ) +// default log options const ( DEFAULT_LOG_PREFIX = "[xorm]" DEFAULT_LOG_FLAG = log.Ldate | log.Lmicroseconds @@ -20,21 +21,45 @@ const ( var _ core.ILogger = DiscardLogger{} +// DiscardLogger don't log implementation for core.ILogger type DiscardLogger struct{} -func (DiscardLogger) Debug(v ...interface{}) {} +// Debug empty implementation +func (DiscardLogger) Debug(v ...interface{}) {} + +// Debugf empty implementation func (DiscardLogger) Debugf(format string, v ...interface{}) {} -func (DiscardLogger) Error(v ...interface{}) {} + +// Error empty implementation +func (DiscardLogger) Error(v ...interface{}) {} + +// Errorf empty implementation func (DiscardLogger) Errorf(format string, v ...interface{}) {} -func (DiscardLogger) Info(v ...interface{}) {} -func (DiscardLogger) Infof(format string, v ...interface{}) {} -func (DiscardLogger) Warn(v ...interface{}) {} -func (DiscardLogger) Warnf(format string, v ...interface{}) {} + +// Info empty implementation +func (DiscardLogger) Info(v ...interface{}) {} + +// Infof empty implementation +func (DiscardLogger) Infof(format string, v ...interface{}) {} + +// Warn empty implementation +func (DiscardLogger) Warn(v ...interface{}) {} + +// Warnf empty implementation +func (DiscardLogger) Warnf(format string, v ...interface{}) {} + +// Level empty implementation func (DiscardLogger) Level() core.LogLevel { return core.LOG_UNKNOWN } + +// SetLevel empty implementation func (DiscardLogger) SetLevel(l core.LogLevel) {} -func (DiscardLogger) ShowSQL(show ...bool) {} + +// ShowSQL empty implementation +func (DiscardLogger) ShowSQL(show ...bool) {} + +// IsShowSQL empty implementation func (DiscardLogger) IsShowSQL() bool { return false } diff --git a/lru_cacher.go b/lru_cacher.go index 855cce2e..4a745043 100644 --- a/lru_cacher.go +++ b/lru_cacher.go @@ -13,6 +13,7 @@ import ( "github.com/go-xorm/core" ) +// LRUCacher implments cache object facilities type LRUCacher struct { idList *list.List sqlList *list.List @@ -26,10 +27,12 @@ type LRUCacher struct { GcInterval time.Duration } +// NewLRUCacher creates a cacher func NewLRUCacher(store core.CacheStore, maxElementSize int) *LRUCacher { return NewLRUCacher2(store, 3600*time.Second, maxElementSize) } +// NewLRUCacher2 creates a cache include different params func NewLRUCacher2(store core.CacheStore, expired time.Duration, maxElementSize int) *LRUCacher { cacher := &LRUCacher{store: store, idList: list.New(), sqlList: list.New(), Expired: expired, @@ -41,10 +44,6 @@ func NewLRUCacher2(store core.CacheStore, expired time.Duration, maxElementSize return cacher } -//func NewLRUCacher3(store CacheStore, expired time.Duration, maxSize int) *LRUCacher { -// return newLRUCacher(store, expired, maxSize, 0) -//} - // RunGC run once every m.GcInterval func (m *LRUCacher) RunGC() { time.AfterFunc(m.GcInterval, func() { @@ -92,7 +91,7 @@ func (m *LRUCacher) GC() { } } -// Get all bean's ids according to sql and parameter from cache +// GetIds returns all bean's ids according to sql and parameter from cache func (m *LRUCacher) GetIds(tableName, sql string) interface{} { m.mutex.Lock() defer m.mutex.Unlock() @@ -101,7 +100,7 @@ func (m *LRUCacher) GetIds(tableName, sql string) interface{} { } if v, err := m.store.Get(sql); err == nil { if el, ok := m.sqlIndex[tableName][sql]; !ok { - el = m.sqlList.PushBack(newSqlNode(tableName, sql)) + el = m.sqlList.PushBack(newSQLNode(tableName, sql)) m.sqlIndex[tableName][sql] = el } else { lastTime := el.Value.(*sqlNode).lastVisit @@ -114,21 +113,21 @@ func (m *LRUCacher) GetIds(tableName, sql string) interface{} { el.Value.(*sqlNode).lastVisit = time.Now() } return v - } else { - m.delIds(tableName, sql) } + m.delIds(tableName, sql) + return nil } -// Get bean according tableName and id from cache +// GetBean returns bean according tableName and id from cache func (m *LRUCacher) GetBean(tableName string, id string) interface{} { m.mutex.Lock() defer m.mutex.Unlock() if _, ok := m.idIndex[tableName]; !ok { m.idIndex[tableName] = make(map[string]*list.Element) } - tid := genId(tableName, id) + tid := genID(tableName, id) if v, err := m.store.Get(tid); err == nil { if el, ok := m.idIndex[tableName][id]; ok { lastTime := el.Value.(*idNode).lastVisit @@ -141,19 +140,19 @@ func (m *LRUCacher) GetBean(tableName string, id string) interface{} { m.idList.MoveToBack(el) el.Value.(*idNode).lastVisit = time.Now() } else { - el = m.idList.PushBack(newIdNode(tableName, id)) + el = m.idList.PushBack(newIDNode(tableName, id)) m.idIndex[tableName][id] = el } return v - } else { - // store bean is not exist, then remove memory's index - m.delBean(tableName, id) - //m.clearIds(tableName) - return nil } + + // store bean is not exist, then remove memory's index + m.delBean(tableName, id) + //m.clearIds(tableName) + return nil } -// Clear all sql-ids mapping on table tableName from cache +// clearIds clears all sql-ids mapping on table tableName from cache func (m *LRUCacher) clearIds(tableName string) { if tis, ok := m.sqlIndex[tableName]; ok { for sql, v := range tis { @@ -164,6 +163,7 @@ func (m *LRUCacher) clearIds(tableName string) { m.sqlIndex[tableName] = make(map[string]*list.Element) } +// ClearIds clears all sql-ids mapping on table tableName from cache func (m *LRUCacher) ClearIds(tableName string) { m.mutex.Lock() defer m.mutex.Unlock() @@ -174,19 +174,21 @@ func (m *LRUCacher) clearBeans(tableName string) { if tis, ok := m.idIndex[tableName]; ok { for id, v := range tis { m.idList.Remove(v) - tid := genId(tableName, id) + tid := genID(tableName, id) m.store.Del(tid) } } m.idIndex[tableName] = make(map[string]*list.Element) } +// ClearBeans clears all beans in some table func (m *LRUCacher) ClearBeans(tableName string) { m.mutex.Lock() defer m.mutex.Unlock() m.clearBeans(tableName) } +// PutIds pus ids into table func (m *LRUCacher) PutIds(tableName, sql string, ids interface{}) { m.mutex.Lock() defer m.mutex.Unlock() @@ -194,7 +196,7 @@ func (m *LRUCacher) PutIds(tableName, sql string, ids interface{}) { m.sqlIndex[tableName] = make(map[string]*list.Element) } if el, ok := m.sqlIndex[tableName][sql]; !ok { - el = m.sqlList.PushBack(newSqlNode(tableName, sql)) + el = m.sqlList.PushBack(newSQLNode(tableName, sql)) m.sqlIndex[tableName][sql] = el } else { el.Value.(*sqlNode).lastVisit = time.Now() @@ -207,6 +209,7 @@ func (m *LRUCacher) PutIds(tableName, sql string, ids interface{}) { } } +// PutBean puts beans into table func (m *LRUCacher) PutBean(tableName string, id string, obj interface{}) { m.mutex.Lock() defer m.mutex.Unlock() @@ -214,13 +217,13 @@ func (m *LRUCacher) PutBean(tableName string, id string, obj interface{}) { var ok bool if el, ok = m.idIndex[tableName][id]; !ok { - el = m.idList.PushBack(newIdNode(tableName, id)) + el = m.idList.PushBack(newIDNode(tableName, id)) m.idIndex[tableName][id] = el } else { el.Value.(*idNode).lastVisit = time.Now() } - m.store.Put(genId(tableName, id), obj) + m.store.Put(genID(tableName, id), obj) if m.idList.Len() > m.MaxElementSize { e := m.idList.Front() node := e.Value.(*idNode) @@ -238,6 +241,7 @@ func (m *LRUCacher) delIds(tableName, sql string) { m.store.Del(sql) } +// DelIds deletes ids func (m *LRUCacher) DelIds(tableName, sql string) { m.mutex.Lock() defer m.mutex.Unlock() @@ -245,7 +249,7 @@ func (m *LRUCacher) DelIds(tableName, sql string) { } func (m *LRUCacher) delBean(tableName string, id string) { - tid := genId(tableName, id) + tid := genID(tableName, id) if el, ok := m.idIndex[tableName][id]; ok { delete(m.idIndex[tableName], id) m.idList.Remove(el) @@ -254,6 +258,7 @@ func (m *LRUCacher) delBean(tableName string, id string) { m.store.Del(tid) } +// DelBean deletes beans in some table func (m *LRUCacher) DelBean(tableName string, id string) { m.mutex.Lock() defer m.mutex.Unlock() @@ -272,18 +277,18 @@ type sqlNode struct { lastVisit time.Time } -func genSqlKey(sql string, args interface{}) string { +func genSQLKey(sql string, args interface{}) string { return fmt.Sprintf("%v-%v", sql, args) } -func genId(prefix string, id string) string { +func genID(prefix string, id string) string { return fmt.Sprintf("%v-%v", prefix, id) } -func newIdNode(tbName string, id string) *idNode { +func newIDNode(tbName string, id string) *idNode { return &idNode{tbName, id, time.Now()} } -func newSqlNode(tbName, sql string) *sqlNode { +func newSQLNode(tbName, sql string) *sqlNode { return &sqlNode{tbName, sql, time.Now()} } diff --git a/memory_store.go b/memory_store.go index 60330995..36853b19 100644 --- a/memory_store.go +++ b/memory_store.go @@ -12,16 +12,18 @@ import ( var _ core.CacheStore = NewMemoryStore() -// memory store +// MemoryStore represents in-memory store type MemoryStore struct { store map[interface{}]interface{} mutex sync.RWMutex } +// NewMemoryStore creates a new store in memory func NewMemoryStore() *MemoryStore { return &MemoryStore{store: make(map[interface{}]interface{})} } +// Put puts object into store func (s *MemoryStore) Put(key string, value interface{}) error { s.mutex.Lock() defer s.mutex.Unlock() @@ -29,6 +31,7 @@ func (s *MemoryStore) Put(key string, value interface{}) error { return nil } +// Get gets object from store func (s *MemoryStore) Get(key string) (interface{}, error) { s.mutex.RLock() defer s.mutex.RUnlock() @@ -39,6 +42,7 @@ func (s *MemoryStore) Get(key string) (interface{}, error) { return nil, ErrNotExist } +// Del deletes object func (s *MemoryStore) Del(key string) error { s.mutex.Lock() defer s.mutex.Unlock() diff --git a/mssql_dialect.go b/mssql_dialect.go index 966c7fc2..e9bda1fd 100644 --- a/mssql_dialect.go +++ b/mssql_dialect.go @@ -5,7 +5,6 @@ package xorm import ( - "errors" "fmt" "strconv" "strings" @@ -243,10 +242,13 @@ func (db *mssql) SqlType(c *core.Column) string { c.Length = 7 case core.MediumInt: res = core.Int - case core.MediumText, core.TinyText, core.LongText, core.Json: - res = core.Text + case core.Text, core.MediumText, core.TinyText, core.LongText, core.Json: + res = core.Varchar + "(MAX)" case core.Double: res = core.Real + case core.Uuid: + res = core.Varchar + c.Length = 40 default: res = t } @@ -255,8 +257,9 @@ func (db *mssql) SqlType(c *core.Column) string { return core.Int } - var hasLen1 bool = (c.Length > 0) - var hasLen2 bool = (c.Length2 > 0) + hasLen1 := (c.Length > 0) + hasLen2 := (c.Length2 > 0) + if hasLen2 { res += "(" + strconv.Itoa(c.Length) + "," + strconv.Itoa(c.Length2) + ")" } else if hasLen1 { @@ -330,9 +333,11 @@ func (db *mssql) TableCheckSql(tableName string) (string, []interface{}) { func (db *mssql) GetColumns(tableName string) ([]string, map[string]*core.Column, error) { args := []interface{}{} - s := `select a.name as name, b.name as ctype,a.max_length,a.precision,a.scale -from sys.columns a left join sys.types b on a.user_type_id=b.user_type_id -where a.object_id=object_id('` + tableName + `')` + s := `select a.name as name, b.name as ctype,a.max_length,a.precision,a.scale,a.is_nullable as nullable, + replace(replace(isnull(c.text,''),'(',''),')','') as vdefault + from sys.columns a left join sys.types b on a.user_type_id=b.user_type_id + left join sys.syscomments c on a.default_object_id=c.id + where a.object_id=object_id('` + tableName + `')` db.LogSQL(s, args) rows, err := db.DB().Query(s, args...) @@ -344,32 +349,38 @@ where a.object_id=object_id('` + tableName + `')` cols := make(map[string]*core.Column) colSeq := make([]string, 0) for rows.Next() { - var name, ctype, precision, scale string - var maxLen int - err = rows.Scan(&name, &ctype, &maxLen, &precision, &scale) + var name, ctype, vdefault string + var maxLen, precision, scale int + var nullable bool + err = rows.Scan(&name, &ctype, &maxLen, &precision, &scale, &nullable, &vdefault) if err != nil { return nil, nil, err } col := new(core.Column) col.Indexes = make(map[string]int) - col.Length = maxLen col.Name = strings.Trim(name, "` ") - + col.Nullable = nullable + col.Default = vdefault ct := strings.ToUpper(ctype) + if ct == "DECIMAL" { + col.Length = precision + col.Length2 = scale + } else { + col.Length = maxLen + } switch ct { case "DATETIMEOFFSET": - col.SQLType = core.SQLType{core.TimeStampz, 0, 0} + col.SQLType = core.SQLType{Name: core.TimeStampz, DefaultLength: 0, DefaultLength2: 0} case "NVARCHAR": - col.SQLType = core.SQLType{core.NVarchar, 0, 0} + col.SQLType = core.SQLType{Name: core.NVarchar, DefaultLength: 0, DefaultLength2: 0} case "IMAGE": - col.SQLType = core.SQLType{core.VarBinary, 0, 0} + col.SQLType = core.SQLType{Name: core.VarBinary, DefaultLength: 0, DefaultLength2: 0} default: if _, ok := core.SqlTypes[ct]; ok { - col.SQLType = core.SQLType{ct, 0, 0} + col.SQLType = core.SQLType{Name: ct, DefaultLength: 0, DefaultLength2: 0} } else { - return nil, nil, errors.New(fmt.Sprintf("unknow colType %v for %v - %v", - ct, tableName, col.Name)) + return nil, nil, fmt.Errorf("Unknown colType %v for %v - %v", ct, tableName, col.Name) } } @@ -458,7 +469,7 @@ WHERE IXS.TYPE_DESC='NONCLUSTERED' and OBJECT_NAME(IXS.OBJECT_ID) =? colName = strings.Trim(colName, "` ") if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) { - indexName = indexName[5+len(tableName) : len(indexName)] + indexName = indexName[5+len(tableName):] } var index *core.Index diff --git a/mysql_dialect.go b/mysql_dialect.go index 5c789a14..ab756f35 100644 --- a/mysql_dialect.go +++ b/mysql_dialect.go @@ -6,7 +6,6 @@ package xorm import ( "crypto/tls" - "errors" "fmt" "strconv" "strings" @@ -202,7 +201,7 @@ func (db *mysql) SqlType(c *core.Column) string { res = core.Enum res += "(" opts := "" - for v, _ := range c.EnumOptions { + for v := range c.EnumOptions { opts += fmt.Sprintf(",'%v'", v) } res += strings.TrimLeft(opts, ",") @@ -211,7 +210,7 @@ func (db *mysql) SqlType(c *core.Column) string { res = core.Set res += "(" opts := "" - for v, _ := range c.SetOptions { + for v := range c.SetOptions { opts += fmt.Sprintf(",'%v'", v) } res += strings.TrimLeft(opts, ",") @@ -227,8 +226,8 @@ func (db *mysql) SqlType(c *core.Column) string { res = t } - var hasLen1 bool = (c.Length > 0) - var hasLen2 bool = (c.Length2 > 0) + hasLen1 := (c.Length > 0) + hasLen2 := (c.Length2 > 0) if res == core.BigInt && !hasLen1 && !hasLen2 { c.Length = 20 @@ -373,9 +372,9 @@ func (db *mysql) GetColumns(tableName string) ([]string, map[string]*core.Column col.Length = len1 col.Length2 = len2 if _, ok := core.SqlTypes[colType]; ok { - col.SQLType = core.SQLType{colType, len1, len2} + col.SQLType = core.SQLType{Name: colType, DefaultLength: len1, DefaultLength2: len2} } else { - return nil, nil, errors.New(fmt.Sprintf("unkonw colType %v", colType)) + return nil, nil, fmt.Errorf("Unknown colType %v", colType) } if colKey == "PRI" { @@ -466,7 +465,7 @@ func (db *mysql) GetIndexes(tableName string) (map[string]*core.Index, error) { colName = strings.Trim(colName, "` ") var isRegular bool if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) { - indexName = indexName[5+len(tableName) : len(indexName)] + indexName = indexName[5+len(tableName):] isRegular = true } diff --git a/oracle_dialect.go b/oracle_dialect.go index f074a4ec..b19ea38b 100644 --- a/oracle_dialect.go +++ b/oracle_dialect.go @@ -5,7 +5,6 @@ package xorm import ( - "errors" "fmt" "strconv" "strings" @@ -526,8 +525,9 @@ func (db *oracle) SqlType(c *core.Column) string { res = t } - var hasLen1 bool = (c.Length > 0) - var hasLen2 bool = (c.Length2 > 0) + hasLen1 := (c.Length > 0) + hasLen2 := (c.Length2 > 0) + if hasLen2 { res += "(" + strconv.Itoa(c.Length) + "," + strconv.Itoa(c.Length2) + ")" } else if hasLen1 { @@ -577,14 +577,14 @@ func (db *oracle) DropTableSql(tableName string) string { return fmt.Sprintf("DROP TABLE `%s`", tableName) } -func (b *oracle) CreateTableSql(table *core.Table, tableName, storeEngine, charset string) string { +func (db *oracle) CreateTableSql(table *core.Table, tableName, storeEngine, charset string) string { var sql string sql = "CREATE TABLE " if tableName == "" { tableName = table.Name } - sql += b.Quote(tableName) + " (" + sql += db.Quote(tableName) + " (" pkList := table.PrimaryKeys @@ -593,7 +593,7 @@ func (b *oracle) CreateTableSql(table *core.Table, tableName, storeEngine, chars /*if col.IsPrimaryKey && len(pkList) == 1 { sql += col.String(b.dialect) } else {*/ - sql += col.StringNoPk(b) + sql += col.StringNoPk(db) //} sql = strings.TrimSpace(sql) sql += ", " @@ -601,17 +601,17 @@ func (b *oracle) CreateTableSql(table *core.Table, tableName, storeEngine, chars if len(pkList) > 0 { sql += "PRIMARY KEY ( " - sql += b.Quote(strings.Join(pkList, b.Quote(","))) + sql += db.Quote(strings.Join(pkList, db.Quote(","))) sql += " ), " } sql = sql[:len(sql)-2] + ")" - if b.SupportEngine() && storeEngine != "" { + if db.SupportEngine() && storeEngine != "" { sql += " ENGINE=" + storeEngine } - if b.SupportCharset() { + if db.SupportCharset() { if len(charset) == 0 { - charset = b.URI().Charset + charset = db.URI().Charset } if len(charset) > 0 { sql += " DEFAULT CHARSET " + charset @@ -733,23 +733,23 @@ func (db *oracle) GetColumns(tableName string) ([]string, map[string]*core.Colum switch dt { case "VARCHAR2": - col.SQLType = core.SQLType{core.Varchar, len1, len2} + col.SQLType = core.SQLType{Name: core.Varchar, DefaultLength: len1, DefaultLength2: len2} case "NVARCHAR2": - col.SQLType = core.SQLType{core.NVarchar, len1, len2} + col.SQLType = core.SQLType{Name: core.NVarchar, DefaultLength: len1, DefaultLength2: len2} case "TIMESTAMP WITH TIME ZONE": - col.SQLType = core.SQLType{core.TimeStampz, 0, 0} + col.SQLType = core.SQLType{Name: core.TimeStampz, DefaultLength: 0, DefaultLength2: 0} case "NUMBER": - col.SQLType = core.SQLType{core.Double, len1, len2} + col.SQLType = core.SQLType{Name: core.Double, DefaultLength: len1, DefaultLength2: len2} case "LONG", "LONG RAW": - col.SQLType = core.SQLType{core.Text, 0, 0} + col.SQLType = core.SQLType{Name: core.Text, DefaultLength: 0, DefaultLength2: 0} case "RAW": - col.SQLType = core.SQLType{core.Binary, 0, 0} + col.SQLType = core.SQLType{Name: core.Binary, DefaultLength: 0, DefaultLength2: 0} case "ROWID": - col.SQLType = core.SQLType{core.Varchar, 18, 0} + col.SQLType = core.SQLType{Name: core.Varchar, DefaultLength: 18, DefaultLength2: 0} case "AQ$_SUBSCRIBERS": ignore = true default: - col.SQLType = core.SQLType{strings.ToUpper(dt), len1, len2} + col.SQLType = core.SQLType{Name: strings.ToUpper(dt), DefaultLength: len1, DefaultLength2: len2} } if ignore { @@ -757,7 +757,7 @@ func (db *oracle) GetColumns(tableName string) ([]string, map[string]*core.Colum } if _, ok := core.SqlTypes[col.SQLType.Name]; !ok { - return nil, nil, errors.New(fmt.Sprintf("unkonw colType %v %v", *dataType, col.SQLType)) + return nil, nil, fmt.Errorf("Unknown colType %v %v", *dataType, col.SQLType) } col.Length = dataLen @@ -842,5 +842,5 @@ func (db *oracle) GetIndexes(tableName string) (map[string]*core.Index, error) { } func (db *oracle) Filters() []core.Filter { - return []core.Filter{&core.QuoteFilter{}, &core.SeqFilter{":", 1}, &core.IdFilter{}} + return []core.Filter{&core.QuoteFilter{}, &core.SeqFilter{Prefix: ":", Start: 1}, &core.IdFilter{}} } diff --git a/postgres_dialect.go b/postgres_dialect.go index 8316e29c..c23ab6f3 100644 --- a/postgres_dialect.go +++ b/postgres_dialect.go @@ -5,7 +5,6 @@ package xorm import ( - "errors" "fmt" "strconv" "strings" @@ -784,6 +783,11 @@ func (db *postgres) SqlType(c *core.Column) string { return core.Serial } return core.Integer + case core.BigInt: + if c.IsAutoIncrement { + return core.BigSerial + } + return core.BigInt case core.Serial, core.BigSerial: c.IsAutoIncrement = true c.Nullable = false @@ -813,8 +817,9 @@ func (db *postgres) SqlType(c *core.Column) string { res = t } - var hasLen1 bool = (c.Length > 0) - var hasLen2 bool = (c.Length2 > 0) + hasLen1 := (c.Length > 0) + hasLen2 := (c.Length2 > 0) + if hasLen2 { res += "(" + strconv.Itoa(c.Length) + "," + strconv.Itoa(c.Length2) + ")" } else if hasLen1 { @@ -880,9 +885,10 @@ func (db *postgres) ModifyColumnSql(tableName string, col *core.Column) string { } func (db *postgres) DropIndexSql(tableName string, index *core.Index) string { - quote := db.Quote //var unique string - var idxName string = index.Name + quote := db.Quote + idxName := index.Name + if !strings.HasPrefix(idxName, "UQE_") && !strings.HasPrefix(idxName, "IDX_") { if index.Type == core.UniqueType { @@ -973,24 +979,24 @@ WHERE c.relkind = 'r'::char AND c.relname = $1 AND s.table_schema = $2 AND f.att switch dataType { case "character varying", "character": - col.SQLType = core.SQLType{core.Varchar, 0, 0} + col.SQLType = core.SQLType{Name: core.Varchar, DefaultLength: 0, DefaultLength2: 0} case "timestamp without time zone": - col.SQLType = core.SQLType{core.DateTime, 0, 0} + col.SQLType = core.SQLType{Name: core.DateTime, DefaultLength: 0, DefaultLength2: 0} case "timestamp with time zone": - col.SQLType = core.SQLType{core.TimeStampz, 0, 0} + col.SQLType = core.SQLType{Name: core.TimeStampz, DefaultLength: 0, DefaultLength2: 0} case "double precision": - col.SQLType = core.SQLType{core.Double, 0, 0} + col.SQLType = core.SQLType{Name: core.Double, DefaultLength: 0, DefaultLength2: 0} case "boolean": - col.SQLType = core.SQLType{core.Bool, 0, 0} + col.SQLType = core.SQLType{Name: core.Bool, DefaultLength: 0, DefaultLength2: 0} case "time without time zone": - col.SQLType = core.SQLType{core.Time, 0, 0} + col.SQLType = core.SQLType{Name: core.Time, DefaultLength: 0, DefaultLength2: 0} case "oid": - col.SQLType = core.SQLType{core.BigInt, 0, 0} + col.SQLType = core.SQLType{Name: core.BigInt, DefaultLength: 0, DefaultLength2: 0} default: - col.SQLType = core.SQLType{strings.ToUpper(dataType), 0, 0} + col.SQLType = core.SQLType{Name: strings.ToUpper(dataType), DefaultLength: 0, DefaultLength2: 0} } if _, ok := core.SqlTypes[col.SQLType.Name]; !ok { - return nil, nil, errors.New(fmt.Sprintf("unknow colType: %v", dataType)) + return nil, nil, fmt.Errorf("Unknown colType: %v", dataType) } col.Length = maxLen @@ -1071,7 +1077,7 @@ func (db *postgres) GetIndexes(tableName string) (map[string]*core.Index, error) colNames = strings.Split(cs[1][0:len(cs[1])-1], ",") if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) { - newIdxName := indexName[5+len(tableName) : len(indexName)] + newIdxName := indexName[5+len(tableName):] if newIdxName != "" { indexName = newIdxName } @@ -1087,5 +1093,5 @@ func (db *postgres) GetIndexes(tableName string) (map[string]*core.Index, error) } func (db *postgres) Filters() []core.Filter { - return []core.Filter{&core.IdFilter{}, &core.QuoteFilter{}, &core.SeqFilter{"$", 1}} + return []core.Filter{&core.IdFilter{}, &core.QuoteFilter{}, &core.SeqFilter{Prefix: "$", Start: 1}} } diff --git a/processors.go b/processors.go index 8f95ae3b..77dd30e5 100644 --- a/processors.go +++ b/processors.go @@ -4,25 +4,27 @@ package xorm -// Executed before an object is initially persisted to the database +// BeforeInsertProcessor executed before an object is initially persisted to the database type BeforeInsertProcessor interface { BeforeInsert() } -// Executed before an object is updated +// BeforeUpdateProcessor executed before an object is updated type BeforeUpdateProcessor interface { BeforeUpdate() } -// Executed before an object is deleted +// BeforeDeleteProcessor executed before an object is deleted type BeforeDeleteProcessor interface { BeforeDelete() } +// BeforeSetProcessor executed before data set to the struct fields type BeforeSetProcessor interface { BeforeSet(string, Cell) } +// AfterSetProcessor executed after data set to the struct fields type AfterSetProcessor interface { AfterSet(string, Cell) } @@ -34,17 +36,17 @@ type AfterSetProcessor interface { //} // -- -// Executed after an object is persisted to the database +// AfterInsertProcessor executed after an object is persisted to the database type AfterInsertProcessor interface { AfterInsert() } -// Executed after an object has been updated +// AfterUpdateProcessor executed after an object has been updated type AfterUpdateProcessor interface { AfterUpdate() } -// Executed after an object has been deleted +// AfterDeleteProcessor executed after an object has been deleted type AfterDeleteProcessor interface { AfterDelete() } diff --git a/rows.go b/rows.go index 2ef8d986..d35040cd 100644 --- a/rows.go +++ b/rows.go @@ -41,7 +41,7 @@ func newRows(session *Session, bean interface{}) (*Rows, error) { } if rows.session.Statement.RawSQL == "" { - sqlStr, args = rows.session.Statement.genGetSql(bean) + sqlStr, args = rows.session.Statement.genGetSQL(bean) } else { sqlStr = rows.session.Statement.RawSQL args = rows.session.Statement.RawParams @@ -51,7 +51,7 @@ func newRows(session *Session, bean interface{}) (*Rows, error) { sqlStr = filter.Do(sqlStr, session.Engine.dialect, rows.session.Statement.RefTable) } - rows.session.saveLastSQL(sqlStr, args) + rows.session.saveLastSQL(sqlStr, args...) var err error if rows.session.prepareStmt { rows.stmt, err = rows.session.DB().Prepare(sqlStr) diff --git a/session.go b/session.go index b978ba87..db040d32 100644 --- a/session.go +++ b/session.go @@ -16,6 +16,7 @@ import ( "strings" "time" + "github.com/go-xorm/builder" "github.com/go-xorm/core" ) @@ -106,52 +107,55 @@ func (session *Session) resetStatement() { } } -// Prepare set a flag to session that should be prepare statment before execute query +// Prepare set a flag to session that should be prepare statement before execute query func (session *Session) Prepare() *Session { session.prepareStmt = true return session } -// Sql will be deprecated, please use SQL instead. -func (session *Session) Sql(querystring string, args ...interface{}) *Session { - session.Statement.Sql(querystring, args...) - return session +// Sql provides raw sql input parameter. When you have a complex SQL statement +// and cannot use Where, Id, In and etc. Methods to describe, you can use SQL. +// +// Deprecated: use SQL instead. +func (session *Session) Sql(query string, args ...interface{}) *Session { + return session.SQL(query, args...) } // SQL provides raw sql input parameter. When you have a complex SQL statement // and cannot use Where, Id, In and etc. Methods to describe, you can use SQL. -func (session *Session) SQL(querystring string, args ...interface{}) *Session { - session.Statement.Sql(querystring, args...) +func (session *Session) SQL(query interface{}, args ...interface{}) *Session { + session.Statement.SQL(query, args...) return session } // Where provides custom query condition. -func (session *Session) Where(querystring string, args ...interface{}) *Session { - session.Statement.Where(querystring, args...) +func (session *Session) Where(query interface{}, args ...interface{}) *Session { + session.Statement.Where(query, args...) return session } // And provides custom query condition. -func (session *Session) And(querystring string, args ...interface{}) *Session { - session.Statement.And(querystring, args...) +func (session *Session) And(query interface{}, args ...interface{}) *Session { + session.Statement.And(query, args...) return session } // Or provides custom query condition. -func (session *Session) Or(querystring string, args ...interface{}) *Session { - session.Statement.Or(querystring, args...) +func (session *Session) Or(query interface{}, args ...interface{}) *Session { + session.Statement.Or(query, args...) return session } -// Id will be deprecated, please use ID instead +// Id provides converting id as a query condition +// +// Deprecated: use ID instead func (session *Session) Id(id interface{}) *Session { - session.Statement.Id(id) - return session + return session.ID(id) } // ID provides converting id as a query condition func (session *Session) ID(id interface{}) *Session { - session.Statement.Id(id) + session.Statement.ID(id) return session } @@ -189,6 +193,12 @@ func (session *Session) In(column string, args ...interface{}) *Session { return session } +// NotIn provides a query string like "id in (1, 2, 3)" +func (session *Session) NotIn(column string, args ...interface{}) *Session { + session.Statement.NotIn(column, args...) + return session +} + // Incr provides a query string like "count = count + 1" func (session *Session) Incr(column string, arg ...interface{}) *Session { session.Statement.Incr(column, arg...) @@ -240,8 +250,8 @@ func (session *Session) NoCascade() *Session { // UseBool 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. -// If no paramters, it will use all the bool field of struct, or -// it will use paramters's columns +// If no parameters, it will use all the bool field of struct, or +// it will use parameters's columns func (session *Session) UseBool(columns ...string) *Session { session.Statement.UseBool(columns...) return session @@ -261,7 +271,7 @@ func (session *Session) ForUpdate() *Session { return session } -// Omit Only not use the paramters as select or update columns +// Omit Only not use the parameters as select or update columns func (session *Session) Omit(columns ...string) *Session { session.Statement.Omit(columns...) return session @@ -365,82 +375,9 @@ func (session *Session) DB() *core.DB { return session.db } -// Begin a transaction -func (session *Session) Begin() error { - if session.IsAutoCommit { - tx, err := session.DB().Begin() - if err != nil { - return err - } - session.IsAutoCommit = false - session.IsCommitedOrRollbacked = false - session.Tx = tx - session.saveLastSQL("BEGIN TRANSACTION") - } - return nil -} - -// Rollback When using transaction, you can rollback if any error -func (session *Session) Rollback() error { - if !session.IsAutoCommit && !session.IsCommitedOrRollbacked { - session.saveLastSQL(session.Engine.dialect.RollBackStr()) - session.IsCommitedOrRollbacked = true - return session.Tx.Rollback() - } - return nil -} - -// Commit When using transaction, Commit will commit all operations. -func (session *Session) Commit() error { - if !session.IsAutoCommit && !session.IsCommitedOrRollbacked { - session.saveLastSQL("COMMIT") - session.IsCommitedOrRollbacked = true - var err error - if err = session.Tx.Commit(); err == nil { - // handle processors after tx committed - - closureCallFunc := func(closuresPtr *[]func(interface{}), bean interface{}) { - - if closuresPtr != nil { - for _, closure := range *closuresPtr { - closure(bean) - } - } - } - - for bean, closuresPtr := range session.afterInsertBeans { - closureCallFunc(closuresPtr, bean) - - if processor, ok := interface{}(bean).(AfterInsertProcessor); ok { - processor.AfterInsert() - } - } - for bean, closuresPtr := range session.afterUpdateBeans { - closureCallFunc(closuresPtr, bean) - - if processor, ok := interface{}(bean).(AfterUpdateProcessor); ok { - processor.AfterUpdate() - } - } - for bean, closuresPtr := range session.afterDeleteBeans { - closureCallFunc(closuresPtr, bean) - - if processor, ok := interface{}(bean).(AfterDeleteProcessor); ok { - processor.AfterDelete() - } - } - cleanUpFunc := func(slices *map[interface{}]*[]func(interface{})) { - if len(*slices) > 0 { - *slices = make(map[interface{}]*[]func(interface{}), 0) - } - } - cleanUpFunc(&session.afterInsertBeans) - cleanUpFunc(&session.afterUpdateBeans) - cleanUpFunc(&session.afterDeleteBeans) - } - return err - } - return nil +// Conds returns session query conditions +func (session *Session) Conds() builder.Cond { + return session.Statement.cond } func cleanupProcessorsClosures(slices *[]func(interface{})) { @@ -495,183 +432,12 @@ func (session *Session) scanMapIntoStruct(obj interface{}, objMap map[string][]b return nil } -// Execute sql -func (session *Session) innerExec(sqlStr string, args ...interface{}) (sql.Result, error) { - if session.prepareStmt { - stmt, err := session.doPrepare(sqlStr) - if err != nil { - return nil, err - } - - res, err := stmt.Exec(args...) - if err != nil { - return nil, err - } - return res, nil - } - - return session.DB().Exec(sqlStr, args...) -} - -func (session *Session) exec(sqlStr string, args ...interface{}) (sql.Result, error) { - for _, filter := range session.Engine.dialect.Filters() { - // TODO: for table name, it's no need to RefTable - sqlStr = filter.Do(sqlStr, session.Engine.dialect, session.Statement.RefTable) - } - - session.saveLastSQL(sqlStr, args...) - - return session.Engine.logSQLExecutionTime(sqlStr, args, func() (sql.Result, error) { - if session.IsAutoCommit { - // FIXME: oci8 can not auto commit (github.com/mattn/go-oci8) - if session.Engine.dialect.DBType() == core.ORACLE { - session.Begin() - r, err := session.Tx.Exec(sqlStr, args...) - session.Commit() - return r, err - } - return session.innerExec(sqlStr, args...) - } - return session.Tx.Exec(sqlStr, args...) - }) -} - -// Exec raw sql -func (session *Session) Exec(sqlStr string, args ...interface{}) (sql.Result, error) { - defer session.resetStatement() - if session.IsAutoClose { - defer session.Close() - } - - return session.exec(sqlStr, args...) -} - -// CreateTable create a table according a bean -func (session *Session) CreateTable(bean interface{}) error { - v := rValue(bean) - session.Statement.setRefValue(v) - - defer session.resetStatement() - if session.IsAutoClose { - defer session.Close() - } - - return session.createOneTable() -} - -// CreateIndexes create indexes -func (session *Session) CreateIndexes(bean interface{}) error { - v := rValue(bean) - session.Statement.setRefValue(v) - - defer session.resetStatement() - if session.IsAutoClose { - defer session.Close() - } - - sqls := session.Statement.genIndexSQL() - for _, sqlStr := range sqls { - _, err := session.exec(sqlStr) - if err != nil { - return err - } - } - return nil -} - -// CreateUniques create uniques -func (session *Session) CreateUniques(bean interface{}) error { - v := rValue(bean) - session.Statement.setRefValue(v) - - defer session.resetStatement() - if session.IsAutoClose { - defer session.Close() - } - - sqls := session.Statement.genUniqueSQL() - for _, sqlStr := range sqls { - _, err := session.exec(sqlStr) - if err != nil { - return err - } - } - return nil -} - -func (session *Session) createOneTable() error { - sqlStr := session.Statement.genCreateTableSQL() - _, err := session.exec(sqlStr) - return err -} - -// to be deleted -func (session *Session) createAll() error { - if session.IsAutoClose { - defer session.Close() - } - - for _, table := range session.Engine.Tables { - session.Statement.RefTable = table - session.Statement.tableName = table.Name - err := session.createOneTable() - session.resetStatement() - if err != nil { - return err - } - } - return nil -} - -// DropIndexes drop indexes -func (session *Session) DropIndexes(bean interface{}) error { - v := rValue(bean) - session.Statement.setRefValue(v) - - defer session.resetStatement() - if session.IsAutoClose { - defer session.Close() - } - - sqls := session.Statement.genDelIndexSQL() - for _, sqlStr := range sqls { - _, err := session.exec(sqlStr) - if err != nil { - return err - } - } - return nil -} - -// DropTable drop table will drop table if exist, if drop failed, it will return error -func (session *Session) DropTable(beanOrTableName interface{}) error { - tableName, err := session.Engine.tableName(beanOrTableName) - if err != nil { - return err - } - - var needDrop = true - if !session.Engine.dialect.SupportDropIfExists() { - sqlStr, args := session.Engine.dialect.TableCheckSql(tableName) - results, err := session.query(sqlStr, args...) - if err != nil { - return err - } - needDrop = len(results) > 0 - } - - if needDrop { - sqlStr := session.Engine.Dialect().DropTableSql(tableName) - _, err = session.exec(sqlStr) - return err - } - return nil -} - func (session *Session) canCache() bool { if session.Statement.RefTable == nil || session.Statement.JoinStr != "" || session.Statement.RawSQL != "" || + !session.Statement.UseCache || + session.Statement.IsForUpdate || session.Tx != nil || len(session.Statement.selectStr) > 0 { return false @@ -679,330 +445,6 @@ func (session *Session) canCache() bool { return true } -func (session *Session) cacheGet(bean interface{}, sqlStr string, args ...interface{}) (has bool, err error) { - // if has no reftable, then don't use cache currently - if !session.canCache() { - return false, ErrCacheFailed - } - - for _, filter := range session.Engine.dialect.Filters() { - sqlStr = filter.Do(sqlStr, session.Engine.dialect, session.Statement.RefTable) - } - newsql := session.Statement.convertIdSql(sqlStr) - if newsql == "" { - return false, ErrCacheFailed - } - - cacher := session.Engine.getCacher2(session.Statement.RefTable) - tableName := session.Statement.TableName() - session.Engine.logger.Debug("[cacheGet] find sql:", newsql, args) - ids, err := core.GetCacheSql(cacher, tableName, newsql, args) - table := session.Statement.RefTable - if err != nil { - var res = make([]string, len(table.PrimaryKeys)) - rows, err := session.DB().Query(newsql, args...) - if err != nil { - return false, err - } - defer rows.Close() - - if rows.Next() { - err = rows.ScanSlice(&res) - if err != nil { - return false, err - } - } else { - return false, ErrCacheFailed - } - - var pk core.PK = make([]interface{}, len(table.PrimaryKeys)) - for i, col := range table.PKColumns() { - if col.SQLType.IsText() { - pk[i] = res[i] - } else if col.SQLType.IsNumeric() { - n, err := strconv.ParseInt(res[i], 10, 64) - if err != nil { - return false, err - } - pk[i] = n - } else { - return false, errors.New("unsupported") - } - } - - ids = []core.PK{pk} - session.Engine.logger.Debug("[cacheGet] cache ids:", newsql, ids) - err = core.PutCacheSql(cacher, ids, tableName, newsql, args) - if err != nil { - return false, err - } - } else { - session.Engine.logger.Debug("[cacheGet] cache hit sql:", newsql) - } - - if len(ids) > 0 { - structValue := reflect.Indirect(reflect.ValueOf(bean)) - id := ids[0] - session.Engine.logger.Debug("[cacheGet] get bean:", tableName, id) - sid, err := id.ToString() - if err != nil { - return false, err - } - cacheBean := cacher.GetBean(tableName, sid) - if cacheBean == nil { - newSession := session.Engine.NewSession() - defer newSession.Close() - cacheBean = reflect.New(structValue.Type()).Interface() - newSession.Id(id).NoCache() - if session.Statement.AltTableName != "" { - newSession.Table(session.Statement.AltTableName) - } - if !session.Statement.UseCascade { - newSession.NoCascade() - } - has, err = newSession.Get(cacheBean) - if err != nil || !has { - return has, err - } - - session.Engine.logger.Debug("[cacheGet] cache bean:", tableName, id, cacheBean) - cacher.PutBean(tableName, sid, cacheBean) - } else { - session.Engine.logger.Debug("[cacheGet] cache hit bean:", tableName, id, cacheBean) - has = true - } - structValue.Set(reflect.Indirect(reflect.ValueOf(cacheBean))) - - return has, nil - } - return false, nil -} - -func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr interface{}, args ...interface{}) (err error) { - if !session.canCache() || - indexNoCase(sqlStr, "having") != -1 || - indexNoCase(sqlStr, "group by") != -1 { - return ErrCacheFailed - } - - for _, filter := range session.Engine.dialect.Filters() { - sqlStr = filter.Do(sqlStr, session.Engine.dialect, session.Statement.RefTable) - } - - newsql := session.Statement.convertIdSql(sqlStr) - if newsql == "" { - return ErrCacheFailed - } - - tableName := session.Statement.TableName() - - table := session.Statement.RefTable - cacher := session.Engine.getCacher2(table) - ids, err := core.GetCacheSql(cacher, tableName, newsql, args) - if err != nil { - rows, err := session.DB().Query(newsql, args...) - if err != nil { - return err - } - defer rows.Close() - - var i int - ids = make([]core.PK, 0) - for rows.Next() { - i++ - if i > 500 { - session.Engine.logger.Debug("[cacheFind] ids length > 500, no cache") - return ErrCacheFailed - } - var res = make([]string, len(table.PrimaryKeys)) - err = rows.ScanSlice(&res) - if err != nil { - return err - } - - var pk core.PK = make([]interface{}, len(table.PrimaryKeys)) - for i, col := range table.PKColumns() { - if col.SQLType.IsNumeric() { - n, err := strconv.ParseInt(res[i], 10, 64) - if err != nil { - return err - } - pk[i] = n - } else if col.SQLType.IsText() { - pk[i] = res[i] - } else { - return errors.New("not supported") - } - } - - ids = append(ids, pk) - } - - session.Engine.logger.Debug("[cacheFind] cache sql:", ids, tableName, newsql, args) - err = core.PutCacheSql(cacher, ids, tableName, newsql, args) - if err != nil { - return err - } - } else { - session.Engine.logger.Debug("[cacheFind] cache hit sql:", newsql, args) - } - - sliceValue := reflect.Indirect(reflect.ValueOf(rowsSlicePtr)) - - ididxes := make(map[string]int) - var ides []core.PK - var temps = make([]interface{}, len(ids)) - - for idx, id := range ids { - sid, err := id.ToString() - if err != nil { - return err - } - bean := cacher.GetBean(tableName, sid) - if bean == nil { - ides = append(ides, id) - ididxes[sid] = idx - } else { - session.Engine.logger.Debug("[cacheFind] cache hit bean:", tableName, id, bean) - - pk := session.Engine.IdOf(bean) - xid, err := pk.ToString() - if err != nil { - return err - } - - if sid != xid { - session.Engine.logger.Error("[cacheFind] error cache", xid, sid, bean) - return ErrCacheFailed - } - temps[idx] = bean - } - } - - if len(ides) > 0 { - newSession := session.Engine.NewSession() - defer newSession.Close() - - slices := reflect.New(reflect.SliceOf(t)) - beans := slices.Interface() - - if len(table.PrimaryKeys) == 1 { - ff := make([]interface{}, 0, len(ides)) - for _, ie := range ides { - ff = append(ff, ie[0]) - } - - newSession.In(table.PrimaryKeys[0], ff...) - } else { - var kn = make([]string, 0) - for _, name := range table.PrimaryKeys { - kn = append(kn, name+" = ?") - } - condi := "(" + strings.Join(kn, " AND ") + ")" - for _, ie := range ides { - newSession.Or(condi, ie...) - } - } - - err = newSession.NoCache().Find(beans) - if err != nil { - return err - } - - vs := reflect.Indirect(reflect.ValueOf(beans)) - for i := 0; i < vs.Len(); i++ { - rv := vs.Index(i) - if rv.Kind() != reflect.Ptr { - rv = rv.Addr() - } - bean := rv.Interface() - id := session.Engine.IdOf(bean) - sid, err := id.ToString() - if err != nil { - return err - } - - temps[ididxes[sid]] = bean - session.Engine.logger.Debug("[cacheFind] cache bean:", tableName, id, bean, temps) - cacher.PutBean(tableName, sid, bean) - } - } - - for j := 0; j < len(temps); j++ { - bean := temps[j] - if bean == nil { - session.Engine.logger.Warn("[cacheFind] cache no hit:", tableName, ids[j], temps) - // return errors.New("cache error") // !nashtsai! no need to return error, but continue instead - continue - } - if sliceValue.Kind() == reflect.Slice { - if t.Kind() == reflect.Ptr { - sliceValue.Set(reflect.Append(sliceValue, reflect.ValueOf(bean))) - } else { - sliceValue.Set(reflect.Append(sliceValue, reflect.Indirect(reflect.ValueOf(bean)))) - } - } else if sliceValue.Kind() == reflect.Map { - var key = ids[j] - keyType := sliceValue.Type().Key() - var ikey interface{} - if len(key) == 1 { - ikey, err = str2PK(fmt.Sprintf("%v", key[0]), keyType) - if err != nil { - return err - } - } else { - if keyType.Kind() != reflect.Slice { - return errors.New("table have multiple primary keys, key is not core.PK or slice") - } - ikey = key - } - - if t.Kind() == reflect.Ptr { - sliceValue.SetMapIndex(reflect.ValueOf(ikey), reflect.ValueOf(bean)) - } else { - sliceValue.SetMapIndex(reflect.ValueOf(ikey), reflect.Indirect(reflect.ValueOf(bean))) - } - } - } - - return nil -} - -// IterFunc only use by Iterate -type IterFunc func(idx int, bean interface{}) error - -// Rows return sql.Rows compatible Rows obj, as a forward Iterator object for iterating record by record, bean's non-empty fields -// are conditions. -func (session *Session) Rows(bean interface{}) (*Rows, error) { - return newRows(session, bean) -} - -// Iterate record by record handle records from table, condiBeans's non-empty fields -// 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 - } - defer rows.Close() - //b := reflect.New(iterator.beanType).Interface() - i := 0 - for rows.Next() { - b := reflect.New(rows.beanType).Interface() - err = rows.Scan(b) - if err != nil { - return err - } - err = fun(i, b) - if err != nil { - return err - } - i++ - } - return err -} - func (session *Session) doPrepare(sqlStr string) (stmt *core.Stmt, err error) { crc := crc32.ChecksumIEEE([]byte(sqlStr)) // TODO try hash(sqlStr+len(sqlStr)) @@ -1018,572 +460,10 @@ func (session *Session) doPrepare(sqlStr string) (stmt *core.Stmt, err error) { return } -// Get retrieve one record from database, bean's non-empty fields -// will be as conditions -func (session *Session) Get(bean interface{}) (bool, error) { - defer session.resetStatement() - if session.IsAutoClose { - defer session.Close() - } - - session.Statement.setRefValue(rValue(bean)) - - var sqlStr string - var args []interface{} - - if session.Statement.RawSQL == "" { - if len(session.Statement.TableName()) <= 0 { - return false, ErrTableNotFound - } - session.Statement.Limit(1) - sqlStr, args = session.Statement.genGetSql(bean) - } else { - sqlStr = session.Statement.RawSQL - args = session.Statement.RawParams - } - - if session.Statement.JoinStr == "" { - if cacher := session.Engine.getCacher2(session.Statement.RefTable); cacher != nil && - session.Statement.UseCache && - !session.Statement.unscoped { - has, err := session.cacheGet(bean, sqlStr, args...) - if err != ErrCacheFailed { - return has, err - } - } - } - - var rawRows *core.Rows - var err error - session.queryPreprocess(&sqlStr, args...) - if session.IsAutoCommit { - _, rawRows, err = session.innerQuery(sqlStr, args...) - } else { - rawRows, err = session.Tx.Query(sqlStr, args...) - } - if err != nil { - return false, err - } - - defer rawRows.Close() - - if rawRows.Next() { - if fields, err := rawRows.Columns(); err == nil { - err = session.row2Bean(rawRows, fields, len(fields), bean) - } - return true, err - } - return false, nil -} - -// Count counts the records. bean's non-empty fields -// are conditions. -func (session *Session) Count(bean interface{}) (int64, error) { - defer session.resetStatement() - if session.IsAutoClose { - defer session.Close() - } - - var sqlStr string - var args []interface{} - if session.Statement.RawSQL == "" { - sqlStr, args = session.Statement.genCountSql(bean) - } else { - sqlStr = session.Statement.RawSQL - args = session.Statement.RawParams - } - - session.queryPreprocess(&sqlStr, args...) - - var err error - var total int64 - if session.IsAutoCommit { - err = session.DB().QueryRow(sqlStr, args...).Scan(&total) - } else { - err = session.Tx.QueryRow(sqlStr, args...).Scan(&total) - } - if err != nil { - return 0, err - } - - return total, nil -} - -// Sum call sum some column. bean's non-empty fields are conditions. -func (session *Session) Sum(bean interface{}, columnName string) (float64, error) { - defer session.resetStatement() - if session.IsAutoClose { - defer session.Close() - } - - var sqlStr string - var args []interface{} - if len(session.Statement.RawSQL) == 0 { - sqlStr, args = session.Statement.genSumSql(bean, columnName) - } else { - sqlStr = session.Statement.RawSQL - args = session.Statement.RawParams - } - - session.queryPreprocess(&sqlStr, args...) - - var err error - var res float64 - if session.IsAutoCommit { - err = session.DB().QueryRow(sqlStr, args...).Scan(&res) - } else { - err = session.Tx.QueryRow(sqlStr, args...).Scan(&res) - } - if err != nil { - return 0, err - } - - return res, nil -} - -// Sums call sum some columns. bean's non-empty fields are conditions. -func (session *Session) Sums(bean interface{}, columnNames ...string) ([]float64, error) { - defer session.resetStatement() - if session.IsAutoClose { - defer session.Close() - } - - var sqlStr string - var args []interface{} - if len(session.Statement.RawSQL) == 0 { - sqlStr, args = session.Statement.genSumSql(bean, columnNames...) - } else { - sqlStr = session.Statement.RawSQL - args = session.Statement.RawParams - } - - session.queryPreprocess(&sqlStr, args...) - - var err error - var res = make([]float64, len(columnNames), len(columnNames)) - if session.IsAutoCommit { - err = session.DB().QueryRow(sqlStr, args...).ScanSlice(&res) - } else { - err = session.Tx.QueryRow(sqlStr, args...).ScanSlice(&res) - } - if err != nil { - return nil, err - } - - return res, nil -} - -// SumsInt sum specify columns and return as []int64 instead of []float64 -func (session *Session) SumsInt(bean interface{}, columnNames ...string) ([]int64, error) { - defer session.resetStatement() - if session.IsAutoClose { - defer session.Close() - } - - var sqlStr string - var args []interface{} - if len(session.Statement.RawSQL) == 0 { - sqlStr, args = session.Statement.genSumSql(bean, columnNames...) - } else { - sqlStr = session.Statement.RawSQL - args = session.Statement.RawParams - } - - session.queryPreprocess(&sqlStr, args...) - - var err error - var res = make([]int64, 0, len(columnNames)) - if session.IsAutoCommit { - err = session.DB().QueryRow(sqlStr, args...).ScanSlice(&res) - } else { - err = session.Tx.QueryRow(sqlStr, args...).ScanSlice(&res) - } - if err != nil { - return nil, err - } - - return res, nil -} - -// Find retrieve records from table, condiBeans's non-empty fields -// are conditions. beans could be []Struct, []*Struct, map[int64]Struct -// map[int64]*Struct -func (session *Session) Find(rowsSlicePtr interface{}, condiBean ...interface{}) error { - defer session.resetStatement() - if session.IsAutoClose { - defer session.Close() - } - - sliceValue := reflect.Indirect(reflect.ValueOf(rowsSlicePtr)) - if sliceValue.Kind() != reflect.Slice && sliceValue.Kind() != reflect.Map { - return errors.New("needs a pointer to a slice or a map") - } - - sliceElementType := sliceValue.Type().Elem() - - if session.Statement.RefTable == nil { - if sliceElementType.Kind() == reflect.Ptr { - if sliceElementType.Elem().Kind() == reflect.Struct { - pv := reflect.New(sliceElementType.Elem()) - session.Statement.setRefValue(pv.Elem()) - } else { - return errors.New("slice type") - } - } else if sliceElementType.Kind() == reflect.Struct { - pv := reflect.New(sliceElementType) - session.Statement.setRefValue(pv.Elem()) - } else { - return errors.New("slice type") - } - } - - var table = session.Statement.RefTable - - var addedTableName = (len(session.Statement.JoinStr) > 0) - if !session.Statement.noAutoCondition && len(condiBean) > 0 { - colNames, args := session.Statement.buildConditions(table, condiBean[0], true, true, false, true, addedTableName) - session.Statement.ConditionStr = strings.Join(colNames, " AND ") - session.Statement.BeanArgs = args - } else { - // !oinume! Add " IS NULL" to WHERE whatever condiBean is given. - // See https://github.com/go-xorm/xorm/issues/179 - if col := table.DeletedColumn(); col != nil && !session.Statement.unscoped { // tag "deleted" is enabled - var colName = session.Engine.Quote(col.Name) - if addedTableName { - var nm = session.Statement.TableName() - if len(session.Statement.TableAlias) > 0 { - nm = session.Statement.TableAlias - } - colName = session.Engine.Quote(nm) + "." + colName - } - session.Statement.ConditionStr = fmt.Sprintf("(%v IS NULL OR %v = '0001-01-01 00:00:00')", - colName, colName) - } - } - - var sqlStr string - var args []interface{} - if session.Statement.RawSQL == "" { - if len(session.Statement.TableName()) <= 0 { - return ErrTableNotFound - } - - var columnStr = session.Statement.ColumnStr - if len(session.Statement.selectStr) > 0 { - columnStr = session.Statement.selectStr - } else { - if session.Statement.JoinStr == "" { - if columnStr == "" { - if session.Statement.GroupByStr != "" { - columnStr = session.Statement.Engine.Quote(strings.Replace(session.Statement.GroupByStr, ",", session.Engine.Quote(","), -1)) - } else { - columnStr = session.Statement.genColumnStr() - } - } - } else { - if columnStr == "" { - if session.Statement.GroupByStr != "" { - columnStr = session.Statement.Engine.Quote(strings.Replace(session.Statement.GroupByStr, ",", session.Engine.Quote(","), -1)) - } else { - columnStr = "*" - } - } - } - } - - session.Statement.Params = append(session.Statement.joinArgs, append(session.Statement.Params, session.Statement.BeanArgs...)...) - - session.Statement.attachInSql() - - sqlStr = session.Statement.genSelectSQL(columnStr) - args = session.Statement.Params - // for mssql and use limit - qs := strings.Count(sqlStr, "?") - if len(args)*2 == qs { - args = append(args, args...) - } - } else { - sqlStr = session.Statement.RawSQL - args = session.Statement.RawParams - } - - var err error - if session.Statement.JoinStr == "" { - if cacher := session.Engine.getCacher2(table); cacher != nil && - session.Statement.UseCache && - !session.Statement.IsDistinct && - !session.Statement.unscoped { - err = session.cacheFind(sliceElementType, sqlStr, rowsSlicePtr, args...) - if err != ErrCacheFailed { - return err - } - err = nil // !nashtsai! reset err to nil for ErrCacheFailed - session.Engine.logger.Warn("Cache Find Failed") - } - } - - if sliceValue.Kind() != reflect.Map { - var rawRows *core.Rows - - session.queryPreprocess(&sqlStr, args...) - if session.IsAutoCommit { - _, rawRows, err = session.innerQuery(sqlStr, args...) - } else { - rawRows, err = session.Tx.Query(sqlStr, args...) - } - if err != nil { - return err - } - defer rawRows.Close() - - fields, err := rawRows.Columns() - if err != nil { - return err - } - - var newElemFunc func() reflect.Value - if sliceElementType.Kind() == reflect.Ptr { - newElemFunc = func() reflect.Value { - return reflect.New(sliceElementType.Elem()) - } - } else { - newElemFunc = func() reflect.Value { - return reflect.New(sliceElementType) - } - } - - var sliceValueSetFunc func(*reflect.Value) - - if sliceValue.Kind() == reflect.Slice { - if sliceElementType.Kind() == reflect.Ptr { - sliceValueSetFunc = func(newValue *reflect.Value) { - sliceValue.Set(reflect.Append(sliceValue, reflect.ValueOf(newValue.Interface()))) - } - } else { - sliceValueSetFunc = func(newValue *reflect.Value) { - sliceValue.Set(reflect.Append(sliceValue, reflect.Indirect(reflect.ValueOf(newValue.Interface())))) - } - } - } - - var newValue = newElemFunc() - dataStruct := rValue(newValue.Interface()) - if dataStruct.Kind() != reflect.Struct { - return errors.New("Expected a pointer to a struct") - } - - return session.rows2Beans(rawRows, fields, len(fields), session.Engine.autoMapType(dataStruct), newElemFunc, sliceValueSetFunc) - } - - resultsSlice, err := session.query(sqlStr, args...) - if err != nil { - return err - } - - keyType := sliceValue.Type().Key() - - for _, results := range resultsSlice { - var newValue reflect.Value - if sliceElementType.Kind() == reflect.Ptr { - newValue = reflect.New(sliceElementType.Elem()) - } else { - newValue = reflect.New(sliceElementType) - } - err := session.scanMapIntoStruct(newValue.Interface(), results) - if err != nil { - return err - } - var key interface{} - // if there is only one pk, we can put the id as map key. - if len(table.PrimaryKeys) == 1 { - key, err = str2PK(string(results[table.PrimaryKeys[0]]), keyType) - if err != nil { - return err - } - } else { - if keyType.Kind() != reflect.Slice { - panic("don't support multiple primary key's map has non-slice key type") - } else { - var keys core.PK = make([]interface{}, 0, len(table.PrimaryKeys)) - for _, pk := range table.PrimaryKeys { - skey, err := str2PK(string(results[pk]), keyType) - if err != nil { - return err - } - keys = append(keys, skey) - } - key = keys - } - } - - if sliceElementType.Kind() == reflect.Ptr { - sliceValue.SetMapIndex(reflect.ValueOf(key), reflect.ValueOf(newValue.Interface())) - } else { - sliceValue.SetMapIndex(reflect.ValueOf(key), reflect.Indirect(reflect.ValueOf(newValue.Interface()))) - } - } - - return nil -} - -// Ping test if database is ok -func (session *Session) Ping() error { - defer session.resetStatement() - if session.IsAutoClose { - defer session.Close() - } - - return session.DB().Ping() -} - -// IsTableExist if a table is exist -func (session *Session) IsTableExist(beanOrTableName interface{}) (bool, error) { - tableName, err := session.Engine.tableName(beanOrTableName) - if err != nil { - return false, err - } - - return session.isTableExist(tableName) -} - -func (session *Session) isTableExist(tableName string) (bool, error) { - defer session.resetStatement() - if session.IsAutoClose { - defer session.Close() - } - sqlStr, args := session.Engine.dialect.TableCheckSql(tableName) - results, err := session.query(sqlStr, args...) - return len(results) > 0, err -} - -// IsTableEmpty if table have any records -func (session *Session) IsTableEmpty(bean interface{}) (bool, error) { - v := rValue(bean) - t := v.Type() - - if t.Kind() == reflect.String { - return session.isTableEmpty(bean.(string)) - } else if t.Kind() == reflect.Struct { - rows, err := session.Count(bean) - return rows == 0, err - } - return false, errors.New("bean should be a struct or struct's point") -} - -func (session *Session) isTableEmpty(tableName string) (bool, error) { - defer session.resetStatement() - if session.IsAutoClose { - defer session.Close() - } - - var total int64 - sql := fmt.Sprintf("select count(*) from %s", session.Engine.Quote(tableName)) - err := session.DB().QueryRow(sql).Scan(&total) - session.saveLastSQL(sql) - if err != nil { - return true, err - } - - return total == 0, nil -} - -func (session *Session) isIndexExist(tableName, idxName string, unique bool) (bool, error) { - defer session.resetStatement() - if session.IsAutoClose { - defer session.Close() - } - var idx string - if unique { - idx = uniqueName(tableName, idxName) - } else { - idx = indexName(tableName, idxName) - } - sqlStr, args := session.Engine.dialect.IndexCheckSql(tableName, idx) - results, err := session.query(sqlStr, args...) - return len(results) > 0, err -} - -// find if index is exist according cols -func (session *Session) isIndexExist2(tableName string, cols []string, unique bool) (bool, error) { - defer session.resetStatement() - if session.IsAutoClose { - defer session.Close() - } - - indexes, err := session.Engine.dialect.GetIndexes(tableName) - if err != nil { - return false, err - } - - for _, index := range indexes { - if sliceEq(index.Cols, cols) { - if unique { - return index.Type == core.UniqueType, nil - } - return index.Type == core.IndexType, nil - } - } - return false, nil -} - -func (session *Session) addColumn(colName string) error { - defer session.resetStatement() - if session.IsAutoClose { - defer session.Close() - } - - col := session.Statement.RefTable.GetColumn(colName) - sql, args := session.Statement.genAddColumnStr(col) - _, err := session.exec(sql, args...) - return err -} - -func (session *Session) addIndex(tableName, idxName string) error { - defer session.resetStatement() - if session.IsAutoClose { - defer session.Close() - } - index := session.Statement.RefTable.Indexes[idxName] - sqlStr := session.Engine.dialect.CreateIndexSql(tableName, index) - - _, err := session.exec(sqlStr) - return err -} - -func (session *Session) addUnique(tableName, uqeName string) error { - defer session.resetStatement() - if session.IsAutoClose { - defer session.Close() - } - index := session.Statement.RefTable.Indexes[uqeName] - sqlStr := session.Engine.dialect.CreateIndexSql(tableName, index) - _, err := session.exec(sqlStr) - return err -} - -// To be deleted -func (session *Session) dropAll() error { - defer session.resetStatement() - if session.IsAutoClose { - defer session.Close() - } - - for _, table := range session.Engine.Tables { - session.Statement.Init() - session.Statement.RefTable = table - sqlStr := session.Engine.Dialect().DropTableSql(session.Statement.TableName()) - _, err := session.exec(sqlStr) - if err != nil { - return err - } - } - return nil -} - func (session *Session) getField(dataStruct *reflect.Value, key string, table *core.Table, idx int) *reflect.Value { var col *core.Column if col = table.GetColumnIdx(key, idx); col == nil { - session.Engine.logger.Warnf("table %v has no column %v. %v", table.Name, key, table.ColumnsSeq()) + //session.Engine.logger.Warnf("table %v has no column %v. %v", table.Name, key, table.ColumnsSeq()) return nil } @@ -1594,8 +474,7 @@ func (session *Session) getField(dataStruct *reflect.Value, key string, table *c } if !fieldValue.IsValid() || !fieldValue.CanSet() { - session.Engine.logger.Warnf("table %v's column %v is not valid or cannot set", - table.Name, key) + session.Engine.logger.Warnf("table %v's column %v is not valid or cannot set", table.Name, key) return nil } return fieldValue @@ -1814,16 +693,24 @@ func (session *Session) _row2Bean(rows *core.Rows, fields []string, fieldsCount hasAssigned = true t := vv.Convert(core.TimeType).Interface().(time.Time) + z, _ := t.Zone() - if len(z) == 0 || t.Year() == 0 { // !nashtsai! HACK tmp work around for lib/pq doesn't properly time with location - dbTZ := session.Engine.DatabaseTZ - if dbTZ == nil { + dbTZ := session.Engine.DatabaseTZ + if dbTZ == nil { + if session.Engine.dialect.DBType() == core.SQLITE { + dbTZ = time.UTC + } else { dbTZ = time.Local } + } + + // set new location if database don't save timezone or give an incorrect timezone + if len(z) == 0 || t.Year() == 0 || t.Location().String() != dbTZ.String() { // !nashtsai! HACK tmp work around for lib/pq doesn't properly time with location session.Engine.logger.Debugf("empty zone key[%v] : %v | zone: %v | location: %+v\n", key, t, z, *t.Location()) t = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), dbTZ) } + // !nashtsai! convert to engine location if col.TimeZone == nil { t = t.In(session.Engine.TZLocation) @@ -1874,7 +761,6 @@ func (session *Session) _row2Bean(rows *core.Rows, fields []string, fieldsCount // !! 增加支持sql.Scanner接口的结构,如sql.NullString hasAssigned = true if err := nulVal.Scan(vv.Interface()); err != nil { - //fmt.Println("sql.Sanner error:", err.Error()) session.Engine.logger.Error("sql.Sanner error:", err.Error()) hasAssigned = false } @@ -2961,6 +1847,685 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value, } fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) // case "*SomeStruct": + case reflect.Struct: + switch fieldType { + // case "*.time.Time": + case core.PtrTimeType: + x, err := session.byte2Time(col, data) + if err != nil { + return err + } + v = x + fieldValue.Set(reflect.ValueOf(&x)) + default: + if session.Statement.UseCascade { + structInter := reflect.New(fieldType.Elem()) + table := session.Engine.autoMapType(structInter.Elem()) + if table != nil { + hasAssigned = true + if len(table.PrimaryKeys) != 1 { + panic("unsupported non or composited primary key cascade") + } + var pk = make(core.PK, len(table.PrimaryKeys)) + + switch rawValueType.Kind() { + case reflect.Int64: + pk[0] = vv.Int() + case reflect.Int: + pk[0] = int(vv.Int()) + case reflect.Int32: + pk[0] = int32(vv.Int()) + case reflect.Int16: + pk[0] = int16(vv.Int()) + case reflect.Int8: + pk[0] = int8(vv.Int()) + case reflect.Uint64: + pk[0] = vv.Uint() + case reflect.Uint: + pk[0] = uint(vv.Uint()) + case reflect.Uint32: + pk[0] = uint32(vv.Uint()) + case reflect.Uint16: + pk[0] = uint16(vv.Uint()) + case reflect.Uint8: + pk[0] = uint8(vv.Uint()) + case reflect.String: + pk[0] = vv.String() + case reflect.Slice: + pk[0], _ = strconv.ParseInt(string(rawValue.Interface().([]byte)), 10, 64) + default: + panic(fmt.Sprintf("unsupported primary key type: %v, %v", rawValueType, fieldValue)) + } + + if !isPKZero(pk) { + // !nashtsai! TODO for hasOne relationship, it's preferred to use join query for eager fetch + // however, also need to consider adding a 'lazy' attribute to xorm tag which allow hasOne + // property to be fetched lazily + structInter := reflect.New(fieldValue.Type()) + newsession := session.Engine.NewSession() + defer newsession.Close() + has, err := newsession.Id(pk).NoCascade().Get(structInter.Interface()) + if err != nil { + return err + } + if has { + //v := structInter.Elem().Interface() + //fieldValue.Set(reflect.ValueOf(v)) + fieldValue.Set(structInter.Elem()) + } else { + return errors.New("cascade obj is not exist") + } + } + } else { + session.Engine.logger.Error("unsupported struct type in Scan: ", fieldValue.Type().String()) + } + } + case reflect.Ptr: + // !nashtsai! TODO merge duplicated codes above + //typeStr := fieldType.String() + switch fieldType { + // following types case matching ptr's native type, therefore assign ptr directly + case core.PtrStringType: + if rawValueType.Kind() == reflect.String { + x := vv.String() + hasAssigned = true + fieldValue.Set(reflect.ValueOf(&x)) + } + case core.PtrBoolType: + if rawValueType.Kind() == reflect.Bool { + x := vv.Bool() + hasAssigned = true + fieldValue.Set(reflect.ValueOf(&x)) + } + case core.PtrTimeType: + if rawValueType == core.PtrTimeType { + hasAssigned = true + var x = rawValue.Interface().(time.Time) + fieldValue.Set(reflect.ValueOf(&x)) + } + case core.PtrFloat64Type: + if rawValueType.Kind() == reflect.Float64 { + x := vv.Float() + hasAssigned = true + fieldValue.Set(reflect.ValueOf(&x)) + } + case core.PtrUint64Type: + if rawValueType.Kind() == reflect.Int64 { + var x = uint64(vv.Int()) + hasAssigned = true + fieldValue.Set(reflect.ValueOf(&x)) + } + case core.PtrInt64Type: + if rawValueType.Kind() == reflect.Int64 { + x := vv.Int() + hasAssigned = true + fieldValue.Set(reflect.ValueOf(&x)) + } + case core.PtrFloat32Type: + if rawValueType.Kind() == reflect.Float64 { + var x = float32(vv.Float()) + hasAssigned = true + fieldValue.Set(reflect.ValueOf(&x)) + } + case core.PtrIntType: + if rawValueType.Kind() == reflect.Int64 { + var x = int(vv.Int()) + hasAssigned = true + fieldValue.Set(reflect.ValueOf(&x)) + } + case core.PtrInt32Type: + if rawValueType.Kind() == reflect.Int64 { + var x = int32(vv.Int()) + hasAssigned = true + fieldValue.Set(reflect.ValueOf(&x)) + } + case core.PtrInt8Type: + if rawValueType.Kind() == reflect.Int64 { + var x = int8(vv.Int()) + hasAssigned = true + fieldValue.Set(reflect.ValueOf(&x)) + } + case core.PtrInt16Type: + if rawValueType.Kind() == reflect.Int64 { + var x = int16(vv.Int()) + hasAssigned = true + fieldValue.Set(reflect.ValueOf(&x)) + } + case core.PtrUintType: + if rawValueType.Kind() == reflect.Int64 { + var x = uint(vv.Int()) + hasAssigned = true + fieldValue.Set(reflect.ValueOf(&x)) + } + case core.PtrUint32Type: + if rawValueType.Kind() == reflect.Int64 { + var x = uint32(vv.Int()) + hasAssigned = true + fieldValue.Set(reflect.ValueOf(&x)) + } + case core.Uint8Type: + if rawValueType.Kind() == reflect.Int64 { + var x = uint8(vv.Int()) + hasAssigned = true + fieldValue.Set(reflect.ValueOf(&x)) + } + case core.Uint16Type: + if rawValueType.Kind() == reflect.Int64 { + var x = uint16(vv.Int()) + hasAssigned = true + fieldValue.Set(reflect.ValueOf(&x)) + } + case core.Complex64Type: + var x complex64 + if len([]byte(vv.String())) > 0 { + err := json.Unmarshal([]byte(vv.String()), &x) + if err != nil { + session.Engine.logger.Error(err) + } else { + fieldValue.Set(reflect.ValueOf(&x)) + } + } + hasAssigned = true + case core.Complex128Type: + var x complex128 + if len([]byte(vv.String())) > 0 { + err := json.Unmarshal([]byte(vv.String()), &x) + if err != nil { + session.Engine.logger.Error(err) + } else { + fieldValue.Set(reflect.ValueOf(&x)) + } + } + hasAssigned = true + } // switch fieldType + // default: + // session.Engine.LogError("unsupported type in Scan: ", reflect.TypeOf(v).String()) + } // switch fieldType.Kind() + + // !nashtsai! for value can't be assigned directly fallback to convert to []byte then back to value + if !hasAssigned { + data, err := value2Bytes(&rawValue) + if err == nil { + session.bytes2Value(col, fieldValue, data) + } else { + session.Engine.logger.Error(err.Error()) + } + } + } + } + return nil +} + +func (session *Session) queryPreprocess(sqlStr *string, paramStr ...interface{}) { + for _, filter := range session.Engine.dialect.Filters() { + *sqlStr = filter.Do(*sqlStr, session.Engine.dialect, session.Statement.RefTable) + } + + session.saveLastSQL(*sqlStr, paramStr...) +} + +func (session *Session) str2Time(col *core.Column, data string) (outTime time.Time, outErr error) { + sdata := strings.TrimSpace(data) + var x time.Time + var err error + + if sdata == "0000-00-00 00:00:00" || + sdata == "0001-01-01 00:00:00" { + } else if !strings.ContainsAny(sdata, "- :") { // !nashtsai! has only found that mymysql driver is using this for time type column + // time stamp + sd, err := strconv.ParseInt(sdata, 10, 64) + if err == nil { + x = time.Unix(sd, 0) + // !nashtsai! HACK mymysql driver is causing Local location being change to CHAT and cause wrong time conversion + if col.TimeZone == nil { + x = x.In(session.Engine.TZLocation) + } else { + x = x.In(col.TimeZone) + } + session.Engine.logger.Debugf("time(0) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata) + } else { + session.Engine.logger.Debugf("time(0) err key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata) + } + } else if len(sdata) > 19 && strings.Contains(sdata, "-") { + x, err = time.ParseInLocation(time.RFC3339Nano, sdata, session.Engine.TZLocation) + session.Engine.logger.Debugf("time(1) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata) + if err != nil { + x, err = time.ParseInLocation("2006-01-02 15:04:05.999999999", sdata, session.Engine.TZLocation) + session.Engine.logger.Debugf("time(2) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata) + } + if err != nil { + x, err = time.ParseInLocation("2006-01-02 15:04:05.9999999 Z07:00", sdata, session.Engine.TZLocation) + session.Engine.logger.Debugf("time(3) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata) + } + + } else if len(sdata) == 19 && strings.Contains(sdata, "-") { + x, err = time.ParseInLocation("2006-01-02 15:04:05", sdata, session.Engine.TZLocation) + session.Engine.logger.Debugf("time(4) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata) + } else if len(sdata) == 10 && sdata[4] == '-' && sdata[7] == '-' { + x, err = time.ParseInLocation("2006-01-02", sdata, session.Engine.TZLocation) + session.Engine.logger.Debugf("time(5) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata) + } else if col.SQLType.Name == core.Time { + if strings.Contains(sdata, " ") { + ssd := strings.Split(sdata, " ") + sdata = ssd[1] + } + + sdata = strings.TrimSpace(sdata) + if session.Engine.dialect.DBType() == core.MYSQL && len(sdata) > 8 { + sdata = sdata[len(sdata)-8:] + } + + st := fmt.Sprintf("2006-01-02 %v", sdata) + x, err = time.ParseInLocation("2006-01-02 15:04:05", st, session.Engine.TZLocation) + session.Engine.logger.Debugf("time(6) key[%v]: %+v | sdata: [%v]\n", col.FieldName, x, sdata) + } else { + outErr = fmt.Errorf("unsupported time format %v", sdata) + return + } + if err != nil { + outErr = fmt.Errorf("unsupported time format %v: %v", sdata, err) + return + } + outTime = x + return +} + +func (session *Session) byte2Time(col *core.Column, data []byte) (outTime time.Time, outErr error) { + return session.str2Time(col, string(data)) +} + +// convert a db data([]byte) to a field value +func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value, data []byte) error { + if structConvert, ok := fieldValue.Addr().Interface().(core.Conversion); ok { + return structConvert.FromDB(data) + } + + if structConvert, ok := fieldValue.Interface().(core.Conversion); ok { + return structConvert.FromDB(data) + } + + var v interface{} + key := col.Name + fieldType := fieldValue.Type() + + switch fieldType.Kind() { + case reflect.Complex64, reflect.Complex128: + x := reflect.New(fieldType) + if len(data) > 0 { + err := json.Unmarshal(data, x.Interface()) + if err != nil { + session.Engine.logger.Error(err) + return err + } + fieldValue.Set(x.Elem()) + } + case reflect.Slice, reflect.Array, reflect.Map: + v = data + t := fieldType.Elem() + k := t.Kind() + if col.SQLType.IsText() { + x := reflect.New(fieldType) + if len(data) > 0 { + err := json.Unmarshal(data, x.Interface()) + if err != nil { + session.Engine.logger.Error(err) + return err + } + fieldValue.Set(x.Elem()) + } + } else if col.SQLType.IsBlob() { + if k == reflect.Uint8 { + fieldValue.Set(reflect.ValueOf(v)) + } else { + x := reflect.New(fieldType) + if len(data) > 0 { + err := json.Unmarshal(data, x.Interface()) + if err != nil { + session.Engine.logger.Error(err) + return err + } + fieldValue.Set(x.Elem()) + } + } + } else { + return ErrUnSupportedType + } + case reflect.String: + fieldValue.SetString(string(data)) + case reflect.Bool: + d := string(data) + v, err := strconv.ParseBool(d) + if err != nil { + return fmt.Errorf("arg %v as bool: %s", key, err.Error()) + } + fieldValue.Set(reflect.ValueOf(v)) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + sdata := string(data) + var x int64 + var err error + // for mysql, when use bit, it returned \x01 + if col.SQLType.Name == core.Bit && + session.Engine.dialect.DBType() == core.MYSQL { // !nashtsai! TODO dialect needs to provide conversion interface API + if len(data) == 1 { + x = int64(data[0]) + } else { + x = 0 + } + } else if strings.HasPrefix(sdata, "0x") { + x, err = strconv.ParseInt(sdata, 16, 64) + } else if strings.HasPrefix(sdata, "0") { + x, err = strconv.ParseInt(sdata, 8, 64) + } else if strings.EqualFold(sdata, "true") { + x = 1 + } else if strings.EqualFold(sdata, "false") { + x = 0 + } else { + x, err = strconv.ParseInt(sdata, 10, 64) + } + if err != nil { + return fmt.Errorf("arg %v as int: %s", key, err.Error()) + } + fieldValue.SetInt(x) + case reflect.Float32, reflect.Float64: + x, err := strconv.ParseFloat(string(data), 64) + if err != nil { + return fmt.Errorf("arg %v as float64: %s", key, err.Error()) + } + fieldValue.SetFloat(x) + case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: + x, err := strconv.ParseUint(string(data), 10, 64) + if err != nil { + return fmt.Errorf("arg %v as int: %s", key, err.Error()) + } + fieldValue.SetUint(x) + //Currently only support Time type + case reflect.Struct: + // !! 增加支持sql.Scanner接口的结构,如sql.NullString + if nulVal, ok := fieldValue.Addr().Interface().(sql.Scanner); ok { + if err := nulVal.Scan(data); err != nil { + return fmt.Errorf("sql.Scan(%v) failed: %s ", data, err.Error()) + } + } else { + if fieldType.ConvertibleTo(core.TimeType) { + x, err := session.byte2Time(col, data) + if err != nil { + return err + } + v = x + fieldValue.Set(reflect.ValueOf(v).Convert(fieldType)) + } else if session.Statement.UseCascade { + table := session.Engine.autoMapType(*fieldValue) + if table != nil { + // TODO: current only support 1 primary key + if len(table.PrimaryKeys) > 1 { + panic("unsupported composited primary key cascade") + } + var pk = make(core.PK, len(table.PrimaryKeys)) + rawValueType := table.ColumnType(table.PKColumns()[0].FieldName) + var err error + pk[0], err = str2PK(string(data), rawValueType) + if err != nil { + return err + } + + if !isPKZero(pk) { + // !nashtsai! TODO for hasOne relationship, it's preferred to use join query for eager fetch + // however, also need to consider adding a 'lazy' attribute to xorm tag which allow hasOne + // property to be fetched lazily + structInter := reflect.New(fieldValue.Type()) + newsession := session.Engine.NewSession() + defer newsession.Close() + has, err := newsession.Id(pk).NoCascade().Get(structInter.Interface()) + if err != nil { + return err + } + if has { + v = structInter.Elem().Interface() + fieldValue.Set(reflect.ValueOf(v)) + } else { + return errors.New("cascade obj is not exist") + } + } + } else { + return fmt.Errorf("unsupported struct type in Scan: %s", fieldValue.Type().String()) + } + } + } + case reflect.Ptr: + // !nashtsai! TODO merge duplicated codes above + //typeStr := fieldType.String() + switch fieldType.Elem().Kind() { + // case "*string": + case core.StringType.Kind(): + x := string(data) + fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) + // case "*bool": + case core.BoolType.Kind(): + d := string(data) + v, err := strconv.ParseBool(d) + if err != nil { + return fmt.Errorf("arg %v as bool: %s", key, err.Error()) + } + fieldValue.Set(reflect.ValueOf(&v).Convert(fieldType)) + // case "*complex64": + case core.Complex64Type.Kind(): + var x complex64 + if len(data) > 0 { + err := json.Unmarshal(data, &x) + if err != nil { + session.Engine.logger.Error(err) + return err + } + fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) + } + // case "*complex128": + case core.Complex128Type.Kind(): + var x complex128 + if len(data) > 0 { + err := json.Unmarshal(data, &x) + if err != nil { + session.Engine.logger.Error(err) + return err + } + fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) + } + // case "*float64": + case core.Float64Type.Kind(): + x, err := strconv.ParseFloat(string(data), 64) + if err != nil { + return fmt.Errorf("arg %v as float64: %s", key, err.Error()) + } + fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) + // case "*float32": + case core.Float32Type.Kind(): + var x float32 + x1, err := strconv.ParseFloat(string(data), 32) + if err != nil { + return fmt.Errorf("arg %v as float32: %s", key, err.Error()) + } + x = float32(x1) + fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) + // case "*uint64": + case core.Uint64Type.Kind(): + var x uint64 + x, err := strconv.ParseUint(string(data), 10, 64) + if err != nil { + return fmt.Errorf("arg %v as int: %s", key, err.Error()) + } + fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) + // case "*uint": + case core.UintType.Kind(): + var x uint + x1, err := strconv.ParseUint(string(data), 10, 64) + if err != nil { + return fmt.Errorf("arg %v as int: %s", key, err.Error()) + } + x = uint(x1) + fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) + // case "*uint32": + case core.Uint32Type.Kind(): + var x uint32 + x1, err := strconv.ParseUint(string(data), 10, 64) + if err != nil { + return fmt.Errorf("arg %v as int: %s", key, err.Error()) + } + x = uint32(x1) + fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) + // case "*uint8": + case core.Uint8Type.Kind(): + var x uint8 + x1, err := strconv.ParseUint(string(data), 10, 64) + if err != nil { + return fmt.Errorf("arg %v as int: %s", key, err.Error()) + } + x = uint8(x1) + fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) + // case "*uint16": + case core.Uint16Type.Kind(): + var x uint16 + x1, err := strconv.ParseUint(string(data), 10, 64) + if err != nil { + return fmt.Errorf("arg %v as int: %s", key, err.Error()) + } + x = uint16(x1) + fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) + // case "*int64": + case core.Int64Type.Kind(): + sdata := string(data) + var x int64 + var err error + // for mysql, when use bit, it returned \x01 + if col.SQLType.Name == core.Bit && + strings.Contains(session.Engine.DriverName(), "mysql") { + if len(data) == 1 { + x = int64(data[0]) + } else { + x = 0 + } + } else if strings.HasPrefix(sdata, "0x") { + x, err = strconv.ParseInt(sdata, 16, 64) + } else if strings.HasPrefix(sdata, "0") { + x, err = strconv.ParseInt(sdata, 8, 64) + } else { + x, err = strconv.ParseInt(sdata, 10, 64) + } + if err != nil { + return fmt.Errorf("arg %v as int: %s", key, err.Error()) + } + fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) + // case "*int": + case core.IntType.Kind(): + sdata := string(data) + var x int + var x1 int64 + var err error + // for mysql, when use bit, it returned \x01 + if col.SQLType.Name == core.Bit && + strings.Contains(session.Engine.DriverName(), "mysql") { + if len(data) == 1 { + x = int(data[0]) + } else { + x = 0 + } + } else if strings.HasPrefix(sdata, "0x") { + x1, err = strconv.ParseInt(sdata, 16, 64) + x = int(x1) + } else if strings.HasPrefix(sdata, "0") { + x1, err = strconv.ParseInt(sdata, 8, 64) + x = int(x1) + } else { + x1, err = strconv.ParseInt(sdata, 10, 64) + x = int(x1) + } + if err != nil { + return fmt.Errorf("arg %v as int: %s", key, err.Error()) + } + fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) + // case "*int32": + case core.Int32Type.Kind(): + sdata := string(data) + var x int32 + var x1 int64 + var err error + // for mysql, when use bit, it returned \x01 + if col.SQLType.Name == core.Bit && + session.Engine.dialect.DBType() == core.MYSQL { + if len(data) == 1 { + x = int32(data[0]) + } else { + x = 0 + } + } else if strings.HasPrefix(sdata, "0x") { + x1, err = strconv.ParseInt(sdata, 16, 64) + x = int32(x1) + } else if strings.HasPrefix(sdata, "0") { + x1, err = strconv.ParseInt(sdata, 8, 64) + x = int32(x1) + } else { + x1, err = strconv.ParseInt(sdata, 10, 64) + x = int32(x1) + } + if err != nil { + return fmt.Errorf("arg %v as int: %s", key, err.Error()) + } + fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) + // case "*int8": + case core.Int8Type.Kind(): + sdata := string(data) + var x int8 + var x1 int64 + var err error + // for mysql, when use bit, it returned \x01 + if col.SQLType.Name == core.Bit && + strings.Contains(session.Engine.DriverName(), "mysql") { + if len(data) == 1 { + x = int8(data[0]) + } else { + x = 0 + } + } else if strings.HasPrefix(sdata, "0x") { + x1, err = strconv.ParseInt(sdata, 16, 64) + x = int8(x1) + } else if strings.HasPrefix(sdata, "0") { + x1, err = strconv.ParseInt(sdata, 8, 64) + x = int8(x1) + } else { + x1, err = strconv.ParseInt(sdata, 10, 64) + x = int8(x1) + } + if err != nil { + return fmt.Errorf("arg %v as int: %s", key, err.Error()) + } + fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) + // case "*int16": + case core.Int16Type.Kind(): + sdata := string(data) + var x int16 + var x1 int64 + var err error + // for mysql, when use bit, it returned \x01 + if col.SQLType.Name == core.Bit && + strings.Contains(session.Engine.DriverName(), "mysql") { + if len(data) == 1 { + x = int16(data[0]) + } else { + x = 0 + } + } else if strings.HasPrefix(sdata, "0x") { + x1, err = strconv.ParseInt(sdata, 16, 64) + x = int16(x1) + } else if strings.HasPrefix(sdata, "0") { + x1, err = strconv.ParseInt(sdata, 8, 64) + x = int16(x1) + } else { + x1, err = strconv.ParseInt(sdata, 10, 64) + x = int16(x1) + } + if err != nil { + return fmt.Errorf("arg %v as int: %s", key, err.Error()) + } + fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) + // case "*SomeStruct": case reflect.Struct: switch fieldType { // case "*.time.Time": @@ -3027,6 +2592,9 @@ func (session *Session) value2Interface(col *core.Column, fieldValue reflect.Val if err != nil { return 0, err } + if col.SQLType.IsBlob() { + return data, nil + } return string(data), nil } } @@ -3036,6 +2604,9 @@ func (session *Session) value2Interface(col *core.Column, fieldValue reflect.Val if err != nil { return 0, err } + if col.SQLType.IsBlob() { + return data, nil + } return string(data), nil } @@ -3144,863 +2715,6 @@ func (session *Session) value2Interface(col *core.Column, fieldValue reflect.Val } } -func (session *Session) innerInsert(bean interface{}) (int64, error) { - session.Statement.setRefValue(rValue(bean)) - if len(session.Statement.TableName()) <= 0 { - return 0, ErrTableNotFound - } - - table := session.Statement.RefTable - - // handle BeforeInsertProcessor - for _, closure := range session.beforeClosures { - closure(bean) - } - cleanupProcessorsClosures(&session.beforeClosures) // cleanup after used - - if processor, ok := interface{}(bean).(BeforeInsertProcessor); ok { - processor.BeforeInsert() - } - // -- - colNames, args, err := genCols(session.Statement.RefTable, session, bean, false, false) - if err != nil { - return 0, err - } - // insert expr columns, override if exists - exprColumns := session.Statement.getExpr() - exprColVals := make([]string, 0, len(exprColumns)) - for _, v := range exprColumns { - // remove the expr columns - for i, colName := range colNames { - if colName == v.colName { - colNames = append(colNames[:i], colNames[i+1:]...) - args = append(args[:i], args[i+1:]...) - } - } - - // append expr column to the end - colNames = append(colNames, v.colName) - exprColVals = append(exprColVals, v.expr) - } - - colPlaces := strings.Repeat("?, ", len(colNames)-len(exprColumns)) - if len(exprColVals) > 0 { - colPlaces = colPlaces + strings.Join(exprColVals, ", ") - } else { - colPlaces = colPlaces[0 : len(colPlaces)-2] - } - - sqlStr := fmt.Sprintf("INSERT INTO %s (%v%v%v) VALUES (%v)", - session.Engine.Quote(session.Statement.TableName()), - session.Engine.QuoteStr(), - strings.Join(colNames, session.Engine.Quote(", ")), - session.Engine.QuoteStr(), - colPlaces) - - handleAfterInsertProcessorFunc := func(bean interface{}) { - - if session.IsAutoCommit { - for _, closure := range session.afterClosures { - closure(bean) - } - if processor, ok := interface{}(bean).(AfterInsertProcessor); ok { - processor.AfterInsert() - } - } else { - lenAfterClosures := len(session.afterClosures) - if lenAfterClosures > 0 { - if value, has := session.afterInsertBeans[bean]; has && value != nil { - *value = append(*value, session.afterClosures...) - } else { - afterClosures := make([]func(interface{}), lenAfterClosures) - copy(afterClosures, session.afterClosures) - session.afterInsertBeans[bean] = &afterClosures - } - - } else { - if _, ok := interface{}(bean).(AfterInsertProcessor); ok { - session.afterInsertBeans[bean] = nil - } - } - } - cleanupProcessorsClosures(&session.afterClosures) // cleanup after used - } - - // for postgres, many of them didn't implement lastInsertId, so we should - // implemented it ourself. - if session.Engine.dialect.DBType() == core.ORACLE && len(table.AutoIncrement) > 0 { - //assert table.AutoIncrement != "" - res, err := session.query("select seq_atable.currval from dual", args...) - - if err != nil { - return 0, err - } - - handleAfterInsertProcessorFunc(bean) - - if cacher := session.Engine.getCacher2(table); cacher != nil && session.Statement.UseCache { - session.cacheInsert(session.Statement.TableName()) - } - - if table.Version != "" && session.Statement.checkVersion { - verValue, err := table.VersionColumn().ValueOf(bean) - if err != nil { - session.Engine.logger.Error(err) - } else if verValue.IsValid() && verValue.CanSet() { - verValue.SetInt(1) - } - } - - if len(res) < 1 { - return 0, errors.New("insert no error but not returned id") - } - - idByte := res[0][table.AutoIncrement] - id, err := strconv.ParseInt(string(idByte), 10, 64) - if err != nil || id <= 0 { - return 1, err - } - - aiValue, err := table.AutoIncrColumn().ValueOf(bean) - if err != nil { - session.Engine.logger.Error(err) - } - - if aiValue == nil || !aiValue.IsValid() || !aiValue.CanSet() { - return 1, nil - } - - aiValue.Set(int64ToIntValue(id, aiValue.Type())) - - return 1, nil - } else if session.Engine.dialect.DBType() == core.POSTGRES && len(table.AutoIncrement) > 0 { - //assert table.AutoIncrement != "" - sqlStr = sqlStr + " RETURNING " + session.Engine.Quote(table.AutoIncrement) - res, err := session.query(sqlStr, args...) - - if err != nil { - return 0, err - } - handleAfterInsertProcessorFunc(bean) - - if cacher := session.Engine.getCacher2(table); cacher != nil && session.Statement.UseCache { - session.cacheInsert(session.Statement.TableName()) - } - - if table.Version != "" && session.Statement.checkVersion { - verValue, err := table.VersionColumn().ValueOf(bean) - if err != nil { - session.Engine.logger.Error(err) - } else if verValue.IsValid() && verValue.CanSet() { - verValue.SetInt(1) - } - } - - if len(res) < 1 { - return 0, errors.New("insert no error but not returned id") - } - - idByte := res[0][table.AutoIncrement] - id, err := strconv.ParseInt(string(idByte), 10, 64) - if err != nil || id <= 0 { - return 1, err - } - - aiValue, err := table.AutoIncrColumn().ValueOf(bean) - if err != nil { - session.Engine.logger.Error(err) - } - - if aiValue == nil || !aiValue.IsValid() || !aiValue.CanSet() { - return 1, nil - } - - aiValue.Set(int64ToIntValue(id, aiValue.Type())) - - return 1, nil - } else { - res, err := session.exec(sqlStr, args...) - if err != nil { - return 0, err - } - handleAfterInsertProcessorFunc(bean) - - if cacher := session.Engine.getCacher2(table); cacher != nil && session.Statement.UseCache { - session.cacheInsert(session.Statement.TableName()) - } - - if table.Version != "" && session.Statement.checkVersion { - verValue, err := table.VersionColumn().ValueOf(bean) - if err != nil { - session.Engine.logger.Error(err) - } else if verValue.IsValid() && verValue.CanSet() { - verValue.SetInt(1) - } - } - - if table.AutoIncrement == "" { - return res.RowsAffected() - } - - var id int64 - id, err = res.LastInsertId() - if err != nil || id <= 0 { - return res.RowsAffected() - } - - aiValue, err := table.AutoIncrColumn().ValueOf(bean) - if err != nil { - session.Engine.logger.Error(err) - } - - if aiValue == nil || !aiValue.IsValid() || !aiValue.CanSet() { - return res.RowsAffected() - } - - aiValue.Set(int64ToIntValue(id, aiValue.Type())) - - return res.RowsAffected() - } -} - -// InsertOne insert only one struct into database as a record. -// The in parameter bean must a struct or a point to struct. The return -// parameter is inserted and error -func (session *Session) InsertOne(bean interface{}) (int64, error) { - defer session.resetStatement() - if session.IsAutoClose { - defer session.Close() - } - - return session.innerInsert(bean) -} - -func (session *Session) cacheInsert(tables ...string) error { - if session.Statement.RefTable == nil { - return ErrCacheFailed - } - - table := session.Statement.RefTable - cacher := session.Engine.getCacher2(table) - - for _, t := range tables { - session.Engine.logger.Debug("[cache] clear sql:", t) - cacher.ClearIds(t) - } - - return nil -} - -func (session *Session) cacheUpdate(sqlStr string, args ...interface{}) error { - if session.Statement.RefTable == nil || - session.Tx != nil { - return ErrCacheFailed - } - - oldhead, newsql := session.Statement.convertUpdateSQL(sqlStr) - if newsql == "" { - return ErrCacheFailed - } - for _, filter := range session.Engine.dialect.Filters() { - newsql = filter.Do(newsql, session.Engine.dialect, session.Statement.RefTable) - } - session.Engine.logger.Debug("[cacheUpdate] new sql", oldhead, newsql) - - var nStart int - if len(args) > 0 { - if strings.Index(sqlStr, "?") > -1 { - nStart = strings.Count(oldhead, "?") - } else { - // only for pq, TODO: if any other databse? - nStart = strings.Count(oldhead, "$") - } - } - table := session.Statement.RefTable - cacher := session.Engine.getCacher2(table) - tableName := session.Statement.TableName() - session.Engine.logger.Debug("[cacheUpdate] get cache sql", newsql, args[nStart:]) - ids, err := core.GetCacheSql(cacher, tableName, newsql, args[nStart:]) - if err != nil { - rows, err := session.DB().Query(newsql, args[nStart:]...) - if err != nil { - return err - } - defer rows.Close() - - ids = make([]core.PK, 0) - for rows.Next() { - var res = make([]string, len(table.PrimaryKeys)) - err = rows.ScanSlice(&res) - if err != nil { - return err - } - var pk core.PK = make([]interface{}, len(table.PrimaryKeys)) - for i, col := range table.PKColumns() { - if col.SQLType.IsNumeric() { - n, err := strconv.ParseInt(res[i], 10, 64) - if err != nil { - return err - } - pk[i] = n - } else if col.SQLType.IsText() { - pk[i] = res[i] - } else { - return errors.New("not supported") - } - } - - ids = append(ids, pk) - } - session.Engine.logger.Debug("[cacheUpdate] find updated id", ids) - } /*else { - session.Engine.LogDebug("[xorm:cacheUpdate] del cached sql:", tableName, newsql, args) - cacher.DelIds(tableName, genSqlKey(newsql, args)) - }*/ - - for _, id := range ids { - sid, err := id.ToString() - if err != nil { - return err - } - if bean := cacher.GetBean(tableName, sid); bean != nil { - sqls := splitNNoCase(sqlStr, "where", 2) - if len(sqls) == 0 || len(sqls) > 2 { - return ErrCacheFailed - } - - sqls = splitNNoCase(sqls[0], "set", 2) - if len(sqls) != 2 { - return ErrCacheFailed - } - kvs := strings.Split(strings.TrimSpace(sqls[1]), ",") - for idx, kv := range kvs { - sps := strings.SplitN(kv, "=", 2) - sps2 := strings.Split(sps[0], ".") - colName := sps2[len(sps2)-1] - if strings.Contains(colName, "`") { - colName = strings.TrimSpace(strings.Replace(colName, "`", "", -1)) - } else if strings.Contains(colName, session.Engine.QuoteStr()) { - colName = strings.TrimSpace(strings.Replace(colName, session.Engine.QuoteStr(), "", -1)) - } else { - session.Engine.logger.Debug("[cacheUpdate] cannot find column", tableName, colName) - return ErrCacheFailed - } - - if col := table.GetColumn(colName); col != nil { - fieldValue, err := col.ValueOf(bean) - if err != nil { - session.Engine.logger.Error(err) - } else { - session.Engine.logger.Debug("[cacheUpdate] set bean field", bean, colName, fieldValue.Interface()) - if col.IsVersion && session.Statement.checkVersion { - fieldValue.SetInt(fieldValue.Int() + 1) - } else { - fieldValue.Set(reflect.ValueOf(args[idx])) - } - } - } else { - session.Engine.logger.Errorf("[cacheUpdate] ERROR: column %v is not table %v's", - colName, table.Name) - } - } - - session.Engine.logger.Debug("[cacheUpdate] update cache", tableName, id, bean) - cacher.PutBean(tableName, sid, bean) - } - } - session.Engine.logger.Debug("[cacheUpdate] clear cached table sql:", tableName) - cacher.ClearIds(tableName) - return nil -} - -// Update records, bean's non-empty fields are updated contents, -// condiBean' non-empty filds are conditions -// CAUTION: -// 1.bool will defaultly be updated content nor conditions -// You should call UseBool if you have bool to use. -// 2.float32 & float64 may be not inexact as conditions -func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int64, error) { - defer session.resetStatement() - if session.IsAutoClose { - defer session.Close() - } - - v := rValue(bean) - t := v.Type() - - var colNames []string - var args []interface{} - - // handle before update processors - for _, closure := range session.beforeClosures { - closure(bean) - } - cleanupProcessorsClosures(&session.beforeClosures) // cleanup after used - if processor, ok := interface{}(bean).(BeforeUpdateProcessor); ok { - processor.BeforeUpdate() - } - // -- - - var err error - var isMap = t.Kind() == reflect.Map - var isStruct = t.Kind() == reflect.Struct - if isStruct { - session.Statement.setRefValue(v) - - if len(session.Statement.TableName()) <= 0 { - return 0, ErrTableNotFound - } - - if session.Statement.ColumnStr == "" { - colNames, args = buildUpdates(session.Engine, session.Statement.RefTable, bean, false, false, - false, false, session.Statement.allUseBool, session.Statement.useAllCols, - session.Statement.mustColumnMap, session.Statement.nullableMap, - session.Statement.columnMap, true, session.Statement.unscoped) - } else { - colNames, args, err = genCols(session.Statement.RefTable, session, bean, true, true) - if err != nil { - return 0, err - } - } - } else if isMap { - colNames = make([]string, 0) - args = make([]interface{}, 0) - bValue := reflect.Indirect(reflect.ValueOf(bean)) - - for _, v := range bValue.MapKeys() { - colNames = append(colNames, session.Engine.Quote(v.String())+" = ?") - args = append(args, bValue.MapIndex(v).Interface()) - } - } else { - return 0, ErrParamsType - } - - table := session.Statement.RefTable - - if session.Statement.UseAutoTime && table != nil && table.Updated != "" { - colNames = append(colNames, session.Engine.Quote(table.Updated)+" = ?") - col := table.UpdatedColumn() - val, t := session.Engine.NowTime2(col.SQLType.Name) - args = append(args, val) - - var colName = col.Name - if isStruct { - session.afterClosures = append(session.afterClosures, func(bean interface{}) { - col := table.GetColumn(colName) - setColumnTime(bean, col, t) - }) - } - } - - //for update action to like "column = column + ?" - incColumns := session.Statement.getInc() - for _, v := range incColumns { - colNames = append(colNames, session.Engine.Quote(v.colName)+" = "+session.Engine.Quote(v.colName)+" + ?") - args = append(args, v.arg) - } - //for update action to like "column = column - ?" - decColumns := session.Statement.getDec() - for _, v := range decColumns { - colNames = append(colNames, session.Engine.Quote(v.colName)+" = "+session.Engine.Quote(v.colName)+" - ?") - args = append(args, v.arg) - } - //for update action to like "column = expression" - exprColumns := session.Statement.getExpr() - for _, v := range exprColumns { - colNames = append(colNames, session.Engine.Quote(v.colName)+" = "+v.expr) - } - - var condiColNames []string - var condiArgs []interface{} - - if !session.Statement.noAutoCondition && len(condiBean) > 0 { - condiColNames, condiArgs = session.Statement.buildConditions(session.Statement.RefTable, condiBean[0], true, true, false, true, false) - } - - var condition = "" - session.Statement.processIdParam() - st := session.Statement - defer session.resetStatement() - if st.WhereStr != "" { - condition = fmt.Sprintf("%v", st.WhereStr) - } - - if condition == "" { - if len(condiColNames) > 0 { - condition = fmt.Sprintf("%v", strings.Join(condiColNames, " "+session.Engine.Dialect().AndStr()+" ")) - } - } else { - if len(condiColNames) > 0 { - condition = fmt.Sprintf("(%v) %v (%v)", condition, - session.Engine.Dialect().AndStr(), strings.Join(condiColNames, " "+session.Engine.Dialect().AndStr()+" ")) - } - } - - var sqlStr, inSQL string - var inArgs []interface{} - doIncVer := false - var verValue *reflect.Value - if table != nil && table.Version != "" && session.Statement.checkVersion { - if condition != "" { - condition = fmt.Sprintf("WHERE (%v) %v %v = ?", condition, session.Engine.Dialect().AndStr(), - session.Engine.Quote(table.Version)) - } else { - condition = fmt.Sprintf("WHERE %v = ?", session.Engine.Quote(table.Version)) - } - inSQL, inArgs = session.Statement.genInSql() - if len(inSQL) > 0 { - if condition != "" { - condition += " " + session.Engine.Dialect().AndStr() + " " + inSQL - } else { - condition = "WHERE " + inSQL - } - } - - if st.LimitN > 0 { - condition = condition + fmt.Sprintf(" LIMIT %d", st.LimitN) - } - - sqlStr = fmt.Sprintf("UPDATE %v SET %v, %v %v", - session.Engine.Quote(session.Statement.TableName()), - strings.Join(colNames, ", "), - session.Engine.Quote(table.Version)+" = "+session.Engine.Quote(table.Version)+" + 1", - condition) - - 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 - } - inSQL, inArgs = session.Statement.genInSql() - if len(inSQL) > 0 { - if condition != "" { - condition += " " + session.Engine.Dialect().AndStr() + " " + inSQL - } else { - condition = "WHERE " + inSQL - } - } - - if st.LimitN > 0 { - condition = condition + fmt.Sprintf(" LIMIT %d", st.LimitN) - } - - sqlStr = fmt.Sprintf("UPDATE %v SET %v %v", - session.Engine.Quote(session.Statement.TableName()), - strings.Join(colNames, ", "), - condition) - } - - args = append(args, st.Params...) - args = append(args, inArgs...) - args = append(args, condiArgs...) - - res, err := session.exec(sqlStr, args...) - if err != nil { - return 0, err - } else if doIncVer { - if verValue != nil && verValue.IsValid() && verValue.CanSet() { - verValue.SetInt(verValue.Int() + 1) - } - } - - if table != nil { - if cacher := session.Engine.getCacher2(table); cacher != nil && session.Statement.UseCache { - cacher.ClearIds(session.Statement.TableName()) - cacher.ClearBeans(session.Statement.TableName()) - } - } - - // handle after update processors - if session.IsAutoCommit { - for _, closure := range session.afterClosures { - closure(bean) - } - if processor, ok := interface{}(bean).(AfterUpdateProcessor); ok { - session.Engine.logger.Debug("[event]", session.Statement.TableName(), " has after update processor") - processor.AfterUpdate() - } - } else { - lenAfterClosures := len(session.afterClosures) - if lenAfterClosures > 0 { - if value, has := session.afterUpdateBeans[bean]; has && value != nil { - *value = append(*value, session.afterClosures...) - } else { - afterClosures := make([]func(interface{}), lenAfterClosures) - copy(afterClosures, session.afterClosures) - // FIXME: if bean is a map type, it will panic because map cannot be as map key - session.afterUpdateBeans[bean] = &afterClosures - } - - } else { - if _, ok := interface{}(bean).(AfterInsertProcessor); ok { - session.afterUpdateBeans[bean] = nil - } - } - } - cleanupProcessorsClosures(&session.afterClosures) // cleanup after used - // -- - - return res.RowsAffected() -} - -func (session *Session) cacheDelete(sqlStr string, args ...interface{}) error { - if session.Statement.RefTable == nil || - session.Tx != nil { - return ErrCacheFailed - } - - for _, filter := range session.Engine.dialect.Filters() { - sqlStr = filter.Do(sqlStr, session.Engine.dialect, session.Statement.RefTable) - } - - newsql := session.Statement.convertIdSql(sqlStr) - if newsql == "" { - return ErrCacheFailed - } - - cacher := session.Engine.getCacher2(session.Statement.RefTable) - tableName := session.Statement.TableName() - ids, err := core.GetCacheSql(cacher, tableName, newsql, args) - if err != nil { - resultsSlice, err := session.query(newsql, args...) - if err != nil { - return err - } - ids = make([]core.PK, 0) - if len(resultsSlice) > 0 { - for _, data := range resultsSlice { - var id int64 - var pk core.PK = make([]interface{}, 0) - for _, col := range session.Statement.RefTable.PKColumns() { - if v, ok := data[col.Name]; !ok { - return errors.New("no id") - } else if col.SQLType.IsText() { - pk = append(pk, string(v)) - } else if col.SQLType.IsNumeric() { - id, err = strconv.ParseInt(string(v), 10, 64) - if err != nil { - return err - } - pk = append(pk, id) - } else { - return errors.New("not supported primary key type") - } - } - ids = append(ids, pk) - } - } - } /*else { - session.Engine.LogDebug("delete cache sql %v", newsql) - cacher.DelIds(tableName, genSqlKey(newsql, args)) - }*/ - - for _, id := range ids { - session.Engine.logger.Debug("[cacheDelete] delete cache obj", tableName, id) - sid, err := id.ToString() - if err != nil { - return err - } - cacher.DelBean(tableName, sid) - } - session.Engine.logger.Debug("[cacheDelete] clear cache sql", tableName) - cacher.ClearIds(tableName) - return nil -} - -// Delete records, bean's non-empty fields are conditions -func (session *Session) Delete(bean interface{}) (int64, error) { - defer session.resetStatement() - if session.IsAutoClose { - defer session.Close() - } - - session.Statement.setRefValue(rValue(bean)) - var table = session.Statement.RefTable - - // handle before delete processors - for _, closure := range session.beforeClosures { - closure(bean) - } - cleanupProcessorsClosures(&session.beforeClosures) - - if processor, ok := interface{}(bean).(BeforeDeleteProcessor); ok { - processor.BeforeDelete() - } - // -- - - var colNames []string - var args []interface{} - - if !session.Statement.noAutoCondition { - colNames, args = session.Statement.buildConditions(table, bean, true, true, false, true, false) - } - var condition = "" - var andStr = session.Engine.dialect.AndStr() - - session.Statement.processIdParam() - if session.Statement.WhereStr != "" { - condition = session.Statement.WhereStr - if len(colNames) > 0 { - condition += " " + andStr + " " + strings.Join(colNames, " "+andStr+" ") - } - } else { - condition = strings.Join(colNames, " "+andStr+" ") - } - inSQL, inArgs := session.Statement.genInSql() - if len(inSQL) > 0 { - if len(condition) > 0 { - condition += " " + andStr + " " - } - condition += inSQL - args = append(args, inArgs...) - } - if len(condition) == 0 && session.Statement.LimitN == 0 { - return 0, ErrNeedDeletedCond - } - - var deleteSQL, realSQL string - var tableName = session.Engine.Quote(session.Statement.TableName()) - - if len(condition) > 0 { - deleteSQL = fmt.Sprintf("DELETE FROM %v WHERE %v", tableName, condition) - } else { - deleteSQL = fmt.Sprintf("DELETE FROM %v", tableName) - } - - var orderSQL string - if len(session.Statement.OrderStr) > 0 { - orderSQL += fmt.Sprintf(" ORDER BY %s", session.Statement.OrderStr) - } - if session.Statement.LimitN > 0 { - orderSQL += fmt.Sprintf(" LIMIT %d", session.Statement.LimitN) - } - - if len(orderSQL) > 0 { - switch session.Engine.dialect.DBType() { - case core.POSTGRES: - inSQL := fmt.Sprintf("ctid IN (SELECT ctid FROM %s%s)", tableName, orderSQL) - if len(condition) > 0 { - deleteSQL += " AND " + inSQL - } else { - deleteSQL += " WHERE " + inSQL - } - case core.SQLITE: - inSQL := fmt.Sprintf("rowid IN (SELECT rowid FROM %s%s)", tableName, orderSQL) - if len(condition) > 0 { - deleteSQL += " AND " + inSQL - } else { - deleteSQL += " WHERE " + inSQL - } - // TODO: how to handle delete limit on mssql? - case core.MSSQL: - return 0, ErrNotImplemented - default: - deleteSQL += orderSQL - } - } - - argsForCache := make([]interface{}, 0, len(args)*2) - if session.Statement.unscoped || table.DeletedColumn() == nil { // tag "deleted" is disabled - realSQL = deleteSQL - copy(argsForCache, args) - argsForCache = append(session.Statement.Params, argsForCache...) - } else { - // !oinume! sqlStrForCache and argsForCache is needed to behave as executing "DELETE FROM ..." for cache. - copy(argsForCache, args) - argsForCache = append(session.Statement.Params, argsForCache...) - - deletedColumn := table.DeletedColumn() - realSQL = fmt.Sprintf("UPDATE %v SET %v = ? WHERE %v", - session.Engine.Quote(session.Statement.TableName()), - session.Engine.Quote(deletedColumn.Name), - condition) - - if len(orderSQL) > 0 { - switch session.Engine.dialect.DBType() { - case core.POSTGRES: - inSQL := fmt.Sprintf("ctid IN (SELECT ctid FROM %s%s)", tableName, orderSQL) - if len(condition) > 0 { - realSQL += " AND " + inSQL - } else { - realSQL += " WHERE " + inSQL - } - case core.SQLITE: - inSQL := fmt.Sprintf("rowid IN (SELECT rowid FROM %s%s)", tableName, orderSQL) - if len(condition) > 0 { - realSQL += " AND " + inSQL - } else { - realSQL += " WHERE " + inSQL - } - // TODO: how to handle delete limit on mssql? - case core.MSSQL: - return 0, ErrNotImplemented - default: - realSQL += orderSQL - } - } - - // !oinume! Insert NowTime to the head of session.Statement.Params - session.Statement.Params = append(session.Statement.Params, "") - paramsLen := len(session.Statement.Params) - copy(session.Statement.Params[1:paramsLen], session.Statement.Params[0:paramsLen-1]) - - val, t := session.Engine.NowTime2(deletedColumn.SQLType.Name) - session.Statement.Params[0] = val - - var colName = deletedColumn.Name - session.afterClosures = append(session.afterClosures, func(bean interface{}) { - col := table.GetColumn(colName) - setColumnTime(bean, col, t) - }) - } - - args = append(session.Statement.Params, args...) - - if cacher := session.Engine.getCacher2(session.Statement.RefTable); cacher != nil && session.Statement.UseCache { - session.cacheDelete(deleteSQL, argsForCache...) - } - - res, err := session.exec(realSQL, args...) - if err != nil { - return 0, err - } - - // handle after delete processors - if session.IsAutoCommit { - for _, closure := range session.afterClosures { - closure(bean) - } - if processor, ok := interface{}(bean).(AfterDeleteProcessor); ok { - processor.AfterDelete() - } - } else { - lenAfterClosures := len(session.afterClosures) - if lenAfterClosures > 0 { - if value, has := session.afterDeleteBeans[bean]; has && value != nil { - *value = append(*value, session.afterClosures...) - } else { - afterClosures := make([]func(interface{}), lenAfterClosures) - copy(afterClosures, session.afterClosures) - session.afterDeleteBeans[bean] = &afterClosures - } - } else { - if _, ok := interface{}(bean).(AfterInsertProcessor); ok { - session.afterDeleteBeans[bean] = nil - } - } - } - cleanupProcessorsClosures(&session.afterClosures) - // -- - - return res.RowsAffected() -} - // saveLastSQL stores executed query information func (session *Session) saveLastSQL(sql string, args ...interface{}) { session.lastSQL = sql @@ -4022,198 +2736,6 @@ func (session *Session) tbNameNoSchema(table *core.Table) string { return table.Name } -// Sync2 synchronize structs to database tables -func (session *Session) Sync2(beans ...interface{}) error { - engine := session.Engine - - tables, err := engine.DBMetas() - if err != nil { - return err - } - - var structTables []*core.Table - - for _, bean := range beans { - v := rValue(bean) - table := engine.mapType(v) - structTables = append(structTables, table) - var tbName = session.tbNameNoSchema(table) - - var oriTable *core.Table - for _, tb := range tables { - if equalNoCase(tb.Name, tbName) { - oriTable = tb - break - } - } - - if oriTable == nil { - err = session.StoreEngine(session.Statement.StoreEngine).CreateTable(bean) - if err != nil { - return err - } - - err = session.CreateUniques(bean) - if err != nil { - return err - } - - err = session.CreateIndexes(bean) - if err != nil { - return err - } - } else { - for _, col := range table.Columns() { - var oriCol *core.Column - for _, col2 := range oriTable.Columns() { - if equalNoCase(col.Name, col2.Name) { - oriCol = col2 - break - } - } - - if oriCol != nil { - expectedType := engine.dialect.SqlType(col) - curType := engine.dialect.SqlType(oriCol) - if expectedType != curType { - if expectedType == core.Text && - strings.HasPrefix(curType, core.Varchar) { - // currently only support mysql & postgres - if engine.dialect.DBType() == core.MYSQL || - engine.dialect.DBType() == core.POSTGRES { - engine.logger.Infof("Table %s column %s change type from %s to %s\n", - tbName, col.Name, curType, expectedType) - _, err = engine.Exec(engine.dialect.ModifyColumnSql(table.Name, col)) - } else { - engine.logger.Warnf("Table %s column %s db type is %s, struct type is %s\n", - tbName, col.Name, curType, expectedType) - } - } else if strings.HasPrefix(curType, core.Varchar) && strings.HasPrefix(expectedType, core.Varchar) { - if engine.dialect.DBType() == core.MYSQL { - if oriCol.Length < col.Length { - engine.logger.Infof("Table %s column %s change type from varchar(%d) to varchar(%d)\n", - tbName, col.Name, oriCol.Length, col.Length) - _, err = engine.Exec(engine.dialect.ModifyColumnSql(table.Name, col)) - } - } - } else { - if !(strings.HasPrefix(curType, expectedType) && curType[len(expectedType)] == '(') { - engine.logger.Warnf("Table %s column %s db type is %s, struct type is %s", - tbName, col.Name, curType, expectedType) - } - } - } else if expectedType == core.Varchar { - if engine.dialect.DBType() == core.MYSQL { - if oriCol.Length < col.Length { - engine.logger.Infof("Table %s column %s change type from varchar(%d) to varchar(%d)\n", - tbName, col.Name, oriCol.Length, col.Length) - _, err = engine.Exec(engine.dialect.ModifyColumnSql(table.Name, col)) - } - } - } - if col.Default != oriCol.Default { - engine.logger.Warnf("Table %s Column %s db default is %s, struct default is %s", - tbName, col.Name, oriCol.Default, col.Default) - } - if col.Nullable != oriCol.Nullable { - engine.logger.Warnf("Table %s Column %s db nullable is %v, struct nullable is %v", - tbName, col.Name, oriCol.Nullable, col.Nullable) - } - } else { - session := engine.NewSession() - session.Statement.RefTable = table - session.Statement.tableName = tbName - defer session.Close() - err = session.addColumn(col.Name) - } - if err != nil { - return err - } - } - - var foundIndexNames = make(map[string]bool) - var addedNames = make(map[string]*core.Index) - - for name, index := range table.Indexes { - var oriIndex *core.Index - for name2, index2 := range oriTable.Indexes { - if index.Equal(index2) { - oriIndex = index2 - foundIndexNames[name2] = true - break - } - } - - if oriIndex != nil { - if oriIndex.Type != index.Type { - sql := engine.dialect.DropIndexSql(tbName, oriIndex) - _, err = engine.Exec(sql) - if err != nil { - return err - } - oriIndex = nil - } - } - - if oriIndex == nil { - addedNames[name] = index - } - } - - for name2, index2 := range oriTable.Indexes { - if _, ok := foundIndexNames[name2]; !ok { - sql := engine.dialect.DropIndexSql(tbName, index2) - _, err = engine.Exec(sql) - if err != nil { - return err - } - } - } - - for name, index := range addedNames { - if index.Type == core.UniqueType { - session := engine.NewSession() - session.Statement.RefTable = table - session.Statement.tableName = tbName - defer session.Close() - err = session.addUnique(tbName, name) - } else if index.Type == core.IndexType { - session := engine.NewSession() - session.Statement.RefTable = table - session.Statement.tableName = tbName - defer session.Close() - err = session.addIndex(tbName, name) - } - if err != nil { - return err - } - } - } - } - - for _, table := range tables { - var oriTable *core.Table - for _, structTable := range structTables { - if equalNoCase(table.Name, session.tbNameNoSchema(structTable)) { - oriTable = structTable - break - } - } - - if oriTable == nil { - //engine.LogWarnf("Table %s has no struct to mapping it", table.Name) - continue - } - - for _, colName := range table.ColumnsSeq() { - if oriTable.GetColumn(colName) == nil { - engine.logger.Warnf("Table %s has column %s but struct has not related field", table.Name, colName) - } - } - } - return nil -} - // Unscoped always disable struct tag "deleted" func (session *Session) Unscoped() *Session { session.Statement.Unscoped() diff --git a/session_delete.go b/session_delete.go new file mode 100644 index 00000000..8c9193c2 --- /dev/null +++ b/session_delete.go @@ -0,0 +1,238 @@ +// Copyright 2016 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package xorm + +import ( + "errors" + "fmt" + "strconv" + + "github.com/go-xorm/core" +) + +func (session *Session) cacheDelete(sqlStr string, args ...interface{}) error { + if session.Statement.RefTable == nil || + session.Tx != nil { + return ErrCacheFailed + } + + for _, filter := range session.Engine.dialect.Filters() { + sqlStr = filter.Do(sqlStr, session.Engine.dialect, session.Statement.RefTable) + } + + newsql := session.Statement.convertIDSQL(sqlStr) + if newsql == "" { + return ErrCacheFailed + } + + cacher := session.Engine.getCacher2(session.Statement.RefTable) + tableName := session.Statement.TableName() + ids, err := core.GetCacheSql(cacher, tableName, newsql, args) + if err != nil { + resultsSlice, err := session.query(newsql, args...) + if err != nil { + return err + } + ids = make([]core.PK, 0) + if len(resultsSlice) > 0 { + for _, data := range resultsSlice { + var id int64 + var pk core.PK = make([]interface{}, 0) + for _, col := range session.Statement.RefTable.PKColumns() { + if v, ok := data[col.Name]; !ok { + return errors.New("no id") + } else if col.SQLType.IsText() { + pk = append(pk, string(v)) + } else if col.SQLType.IsNumeric() { + id, err = strconv.ParseInt(string(v), 10, 64) + if err != nil { + return err + } + pk = append(pk, id) + } else { + return errors.New("not supported primary key type") + } + } + ids = append(ids, pk) + } + } + } /*else { + session.Engine.LogDebug("delete cache sql %v", newsql) + cacher.DelIds(tableName, genSqlKey(newsql, args)) + }*/ + + for _, id := range ids { + session.Engine.logger.Debug("[cacheDelete] delete cache obj", tableName, id) + sid, err := id.ToString() + if err != nil { + return err + } + cacher.DelBean(tableName, sid) + } + session.Engine.logger.Debug("[cacheDelete] clear cache sql", tableName) + cacher.ClearIds(tableName) + return nil +} + +// Delete records, bean's non-empty fields are conditions +func (session *Session) Delete(bean interface{}) (int64, error) { + defer session.resetStatement() + if session.IsAutoClose { + defer session.Close() + } + + session.Statement.setRefValue(rValue(bean)) + var table = session.Statement.RefTable + + // handle before delete processors + for _, closure := range session.beforeClosures { + closure(bean) + } + cleanupProcessorsClosures(&session.beforeClosures) + + if processor, ok := interface{}(bean).(BeforeDeleteProcessor); ok { + processor.BeforeDelete() + } + + // -- + condSQL, condArgs, _ := session.Statement.genConds(bean) + if len(condSQL) == 0 && session.Statement.LimitN == 0 { + return 0, ErrNeedDeletedCond + } + + var tableName = session.Engine.Quote(session.Statement.TableName()) + var deleteSQL string + if len(condSQL) > 0 { + deleteSQL = fmt.Sprintf("DELETE FROM %v WHERE %v", tableName, condSQL) + } else { + deleteSQL = fmt.Sprintf("DELETE FROM %v", tableName) + } + + var orderSQL string + if len(session.Statement.OrderStr) > 0 { + orderSQL += fmt.Sprintf(" ORDER BY %s", session.Statement.OrderStr) + } + if session.Statement.LimitN > 0 { + orderSQL += fmt.Sprintf(" LIMIT %d", session.Statement.LimitN) + } + + if len(orderSQL) > 0 { + switch session.Engine.dialect.DBType() { + case core.POSTGRES: + inSQL := fmt.Sprintf("ctid IN (SELECT ctid FROM %s%s)", tableName, orderSQL) + if len(condSQL) > 0 { + deleteSQL += " AND " + inSQL + } else { + deleteSQL += " WHERE " + inSQL + } + case core.SQLITE: + inSQL := fmt.Sprintf("rowid IN (SELECT rowid FROM %s%s)", tableName, orderSQL) + if len(condSQL) > 0 { + deleteSQL += " AND " + inSQL + } else { + deleteSQL += " WHERE " + inSQL + } + // TODO: how to handle delete limit on mssql? + case core.MSSQL: + return 0, ErrNotImplemented + default: + deleteSQL += orderSQL + } + } + + var realSQL string + argsForCache := make([]interface{}, 0, len(condArgs)*2) + if session.Statement.unscoped || table.DeletedColumn() == nil { // tag "deleted" is disabled + realSQL = deleteSQL + copy(argsForCache, condArgs) + argsForCache = append(condArgs, argsForCache...) + } else { + // !oinume! sqlStrForCache and argsForCache is needed to behave as executing "DELETE FROM ..." for cache. + copy(argsForCache, condArgs) + argsForCache = append(condArgs, argsForCache...) + + deletedColumn := table.DeletedColumn() + realSQL = fmt.Sprintf("UPDATE %v SET %v = ? WHERE %v", + session.Engine.Quote(session.Statement.TableName()), + session.Engine.Quote(deletedColumn.Name), + condSQL) + + if len(orderSQL) > 0 { + switch session.Engine.dialect.DBType() { + case core.POSTGRES: + inSQL := fmt.Sprintf("ctid IN (SELECT ctid FROM %s%s)", tableName, orderSQL) + if len(condSQL) > 0 { + realSQL += " AND " + inSQL + } else { + realSQL += " WHERE " + inSQL + } + case core.SQLITE: + inSQL := fmt.Sprintf("rowid IN (SELECT rowid FROM %s%s)", tableName, orderSQL) + if len(condSQL) > 0 { + realSQL += " AND " + inSQL + } else { + realSQL += " WHERE " + inSQL + } + // TODO: how to handle delete limit on mssql? + case core.MSSQL: + return 0, ErrNotImplemented + default: + realSQL += orderSQL + } + } + + // !oinume! Insert NowTime to the head of session.Statement.Params + condArgs = append(condArgs, "") + paramsLen := len(condArgs) + copy(condArgs[1:paramsLen], condArgs[0:paramsLen-1]) + + val, t := session.Engine.NowTime2(deletedColumn.SQLType.Name) + condArgs[0] = val + + var colName = deletedColumn.Name + session.afterClosures = append(session.afterClosures, func(bean interface{}) { + col := table.GetColumn(colName) + setColumnTime(bean, col, t) + }) + } + + if cacher := session.Engine.getCacher2(session.Statement.RefTable); cacher != nil && session.Statement.UseCache { + session.cacheDelete(deleteSQL, argsForCache...) + } + + res, err := session.exec(realSQL, condArgs...) + if err != nil { + return 0, err + } + + // handle after delete processors + if session.IsAutoCommit { + for _, closure := range session.afterClosures { + closure(bean) + } + if processor, ok := interface{}(bean).(AfterDeleteProcessor); ok { + processor.AfterDelete() + } + } else { + lenAfterClosures := len(session.afterClosures) + if lenAfterClosures > 0 { + if value, has := session.afterDeleteBeans[bean]; has && value != nil { + *value = append(*value, session.afterClosures...) + } else { + afterClosures := make([]func(interface{}), lenAfterClosures) + copy(afterClosures, session.afterClosures) + session.afterDeleteBeans[bean] = &afterClosures + } + } else { + if _, ok := interface{}(bean).(AfterInsertProcessor); ok { + session.afterDeleteBeans[bean] = nil + } + } + } + cleanupProcessorsClosures(&session.afterClosures) + // -- + + return res.RowsAffected() +} diff --git a/session_find.go b/session_find.go new file mode 100644 index 00000000..2e52fff3 --- /dev/null +++ b/session_find.go @@ -0,0 +1,458 @@ +// Copyright 2016 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package xorm + +import ( + "errors" + "fmt" + "reflect" + "strconv" + "strings" + + "github.com/go-xorm/builder" + "github.com/go-xorm/core" +) + +const ( + tpStruct = iota + tpNonStruct +) + +// Find retrieve records from table, condiBeans's non-empty fields +// are conditions. beans could be []Struct, []*Struct, map[int64]Struct +// map[int64]*Struct +func (session *Session) Find(rowsSlicePtr interface{}, condiBean ...interface{}) error { + defer session.resetStatement() + if session.IsAutoClose { + defer session.Close() + } + + sliceValue := reflect.Indirect(reflect.ValueOf(rowsSlicePtr)) + if sliceValue.Kind() != reflect.Slice && sliceValue.Kind() != reflect.Map { + return errors.New("needs a pointer to a slice or a map") + } + + sliceElementType := sliceValue.Type().Elem() + + var tp = tpStruct + if session.Statement.RefTable == nil { + if sliceElementType.Kind() == reflect.Ptr { + if sliceElementType.Elem().Kind() == reflect.Struct { + pv := reflect.New(sliceElementType.Elem()) + session.Statement.setRefValue(pv.Elem()) + } else { + //return errors.New("slice type") + tp = tpNonStruct + } + } else if sliceElementType.Kind() == reflect.Struct { + pv := reflect.New(sliceElementType) + session.Statement.setRefValue(pv.Elem()) + } else { + //return errors.New("slice type") + tp = tpNonStruct + } + } + + var table = session.Statement.RefTable + + var addedTableName = (len(session.Statement.JoinStr) > 0) + var autoCond builder.Cond + if tp == tpStruct { + if !session.Statement.noAutoCondition && len(condiBean) > 0 { + var err error + autoCond, err = session.Statement.buildConds(table, condiBean[0], true, true, false, true, addedTableName) + if err != nil { + panic(err) + } + } else { + // !oinume! Add " IS NULL" to WHERE whatever condiBean is given. + // See https://github.com/go-xorm/xorm/issues/179 + if col := table.DeletedColumn(); col != nil && !session.Statement.unscoped { // tag "deleted" is enabled + var colName = session.Engine.Quote(col.Name) + if addedTableName { + var nm = session.Statement.TableName() + if len(session.Statement.TableAlias) > 0 { + nm = session.Statement.TableAlias + } + colName = session.Engine.Quote(nm) + "." + colName + } + if session.Engine.dialect.DBType() == core.MSSQL { + autoCond = builder.IsNull{colName} + } else { + autoCond = builder.IsNull{colName}.Or(builder.Eq{colName: "0001-01-01 00:00:00"}) + } + } + } + } + + var sqlStr string + var args []interface{} + if session.Statement.RawSQL == "" { + if len(session.Statement.TableName()) <= 0 { + return ErrTableNotFound + } + + var columnStr = session.Statement.ColumnStr + if len(session.Statement.selectStr) > 0 { + columnStr = session.Statement.selectStr + } else { + if session.Statement.JoinStr == "" { + if columnStr == "" { + if session.Statement.GroupByStr != "" { + columnStr = session.Statement.Engine.Quote(strings.Replace(session.Statement.GroupByStr, ",", session.Engine.Quote(","), -1)) + } else { + columnStr = session.Statement.genColumnStr() + } + } + } else { + if columnStr == "" { + if session.Statement.GroupByStr != "" { + columnStr = session.Statement.Engine.Quote(strings.Replace(session.Statement.GroupByStr, ",", session.Engine.Quote(","), -1)) + } else { + columnStr = "*" + } + } + } + if columnStr == "" { + columnStr = "*" + } + } + + condSQL, condArgs, _ := builder.ToSQL(session.Statement.cond.And(autoCond)) + + args = append(session.Statement.joinArgs, condArgs...) + sqlStr = session.Statement.genSelectSQL(columnStr, condSQL) + // for mssql and use limit + qs := strings.Count(sqlStr, "?") + if len(args)*2 == qs { + args = append(args, args...) + } + } else { + sqlStr = session.Statement.RawSQL + args = session.Statement.RawParams + } + + var err error + if session.canCache() { + if cacher := session.Engine.getCacher2(table); cacher != nil && + !session.Statement.IsDistinct && + !session.Statement.unscoped { + err = session.cacheFind(sliceElementType, sqlStr, rowsSlicePtr, args...) + if err != ErrCacheFailed { + return err + } + err = nil // !nashtsai! reset err to nil for ErrCacheFailed + session.Engine.logger.Warn("Cache Find Failed") + } + } + + if sliceValue.Kind() != reflect.Map { + return session.noCacheFind(sliceValue, sqlStr, args...) + } + + resultsSlice, err := session.query(sqlStr, args...) + if err != nil { + return err + } + + keyType := sliceValue.Type().Key() + + for _, results := range resultsSlice { + var newValue reflect.Value + if sliceElementType.Kind() == reflect.Ptr { + newValue = reflect.New(sliceElementType.Elem()) + } else { + newValue = reflect.New(sliceElementType) + } + err := session.scanMapIntoStruct(newValue.Interface(), results) + if err != nil { + return err + } + var key interface{} + // if there is only one pk, we can put the id as map key. + if len(table.PrimaryKeys) == 1 { + key, err = str2PK(string(results[table.PrimaryKeys[0]]), keyType) + if err != nil { + return err + } + } else { + if keyType.Kind() != reflect.Slice { + panic("don't support multiple primary key's map has non-slice key type") + } else { + var keys core.PK = make([]interface{}, 0, len(table.PrimaryKeys)) + for _, pk := range table.PrimaryKeys { + skey, err := str2PK(string(results[pk]), keyType) + if err != nil { + return err + } + keys = append(keys, skey) + } + key = keys + } + } + + if sliceElementType.Kind() == reflect.Ptr { + sliceValue.SetMapIndex(reflect.ValueOf(key), reflect.ValueOf(newValue.Interface())) + } else { + sliceValue.SetMapIndex(reflect.ValueOf(key), reflect.Indirect(reflect.ValueOf(newValue.Interface()))) + } + } + + return nil +} + +func (session *Session) noCacheFind(sliceValue reflect.Value, sqlStr string, args ...interface{}) error { + var rawRows *core.Rows + var err error + + session.queryPreprocess(&sqlStr, args...) + if session.IsAutoCommit { + _, rawRows, err = session.innerQuery(sqlStr, args...) + } else { + rawRows, err = session.Tx.Query(sqlStr, args...) + } + if err != nil { + return err + } + defer rawRows.Close() + + fields, err := rawRows.Columns() + if err != nil { + return err + } + + var newElemFunc func() reflect.Value + sliceElementType := sliceValue.Type().Elem() + if sliceElementType.Kind() == reflect.Ptr { + newElemFunc = func() reflect.Value { + return reflect.New(sliceElementType.Elem()) + } + } else { + newElemFunc = func() reflect.Value { + return reflect.New(sliceElementType) + } + } + + var sliceValueSetFunc func(*reflect.Value) + + if sliceValue.Kind() == reflect.Slice { + if sliceElementType.Kind() == reflect.Ptr { + sliceValueSetFunc = func(newValue *reflect.Value) { + sliceValue.Set(reflect.Append(sliceValue, reflect.ValueOf(newValue.Interface()))) + } + } else { + sliceValueSetFunc = func(newValue *reflect.Value) { + sliceValue.Set(reflect.Append(sliceValue, reflect.Indirect(reflect.ValueOf(newValue.Interface())))) + } + } + } + + var newValue = newElemFunc() + dataStruct := rValue(newValue.Interface()) + if dataStruct.Kind() == reflect.Struct { + return session.rows2Beans(rawRows, fields, len(fields), session.Engine.autoMapType(dataStruct), newElemFunc, sliceValueSetFunc) + } + + for rawRows.Next() { + var newValue = newElemFunc() + bean := newValue.Interface() + + if err := rawRows.Scan(bean); err != nil { + return err + } + + sliceValueSetFunc(&newValue) + } + return nil +} + +func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr interface{}, args ...interface{}) (err error) { + if !session.canCache() || + indexNoCase(sqlStr, "having") != -1 || + indexNoCase(sqlStr, "group by") != -1 { + return ErrCacheFailed + } + + for _, filter := range session.Engine.dialect.Filters() { + sqlStr = filter.Do(sqlStr, session.Engine.dialect, session.Statement.RefTable) + } + + newsql := session.Statement.convertIDSQL(sqlStr) + if newsql == "" { + return ErrCacheFailed + } + + tableName := session.Statement.TableName() + + table := session.Statement.RefTable + cacher := session.Engine.getCacher2(table) + ids, err := core.GetCacheSql(cacher, tableName, newsql, args) + if err != nil { + rows, err := session.DB().Query(newsql, args...) + if err != nil { + return err + } + defer rows.Close() + + var i int + ids = make([]core.PK, 0) + for rows.Next() { + i++ + if i > 500 { + session.Engine.logger.Debug("[cacheFind] ids length > 500, no cache") + return ErrCacheFailed + } + var res = make([]string, len(table.PrimaryKeys)) + err = rows.ScanSlice(&res) + if err != nil { + return err + } + + var pk core.PK = make([]interface{}, len(table.PrimaryKeys)) + for i, col := range table.PKColumns() { + if col.SQLType.IsNumeric() { + n, err := strconv.ParseInt(res[i], 10, 64) + if err != nil { + return err + } + pk[i] = n + } else if col.SQLType.IsText() { + pk[i] = res[i] + } else { + return errors.New("not supported") + } + } + + ids = append(ids, pk) + } + + session.Engine.logger.Debug("[cacheFind] cache sql:", ids, tableName, newsql, args) + err = core.PutCacheSql(cacher, ids, tableName, newsql, args) + if err != nil { + return err + } + } else { + session.Engine.logger.Debug("[cacheFind] cache hit sql:", newsql, args) + } + + sliceValue := reflect.Indirect(reflect.ValueOf(rowsSlicePtr)) + + ididxes := make(map[string]int) + var ides []core.PK + var temps = make([]interface{}, len(ids)) + + for idx, id := range ids { + sid, err := id.ToString() + if err != nil { + return err + } + bean := cacher.GetBean(tableName, sid) + if bean == nil { + ides = append(ides, id) + ididxes[sid] = idx + } else { + session.Engine.logger.Debug("[cacheFind] cache hit bean:", tableName, id, bean) + + pk := session.Engine.IdOf(bean) + xid, err := pk.ToString() + if err != nil { + return err + } + + if sid != xid { + session.Engine.logger.Error("[cacheFind] error cache", xid, sid, bean) + return ErrCacheFailed + } + temps[idx] = bean + } + } + + if len(ides) > 0 { + newSession := session.Engine.NewSession() + defer newSession.Close() + + slices := reflect.New(reflect.SliceOf(t)) + beans := slices.Interface() + + if len(table.PrimaryKeys) == 1 { + ff := make([]interface{}, 0, len(ides)) + for _, ie := range ides { + ff = append(ff, ie[0]) + } + + newSession.In("`"+table.PrimaryKeys[0]+"`", ff...) + } else { + for _, ie := range ides { + cond := builder.NewCond() + for i, name := range table.PrimaryKeys { + cond = cond.And(builder.Eq{"`" + name + "`": ie[i]}) + } + newSession.Or(cond) + } + } + + err = newSession.NoCache().Find(beans) + if err != nil { + return err + } + + vs := reflect.Indirect(reflect.ValueOf(beans)) + for i := 0; i < vs.Len(); i++ { + rv := vs.Index(i) + if rv.Kind() != reflect.Ptr { + rv = rv.Addr() + } + id := session.Engine.IdOfV(rv) + sid, err := id.ToString() + if err != nil { + return err + } + + bean := rv.Interface() + temps[ididxes[sid]] = bean + session.Engine.logger.Debug("[cacheFind] cache bean:", tableName, id, bean, temps) + cacher.PutBean(tableName, sid, bean) + } + } + + for j := 0; j < len(temps); j++ { + bean := temps[j] + if bean == nil { + session.Engine.logger.Warn("[cacheFind] cache no hit:", tableName, ids[j], temps) + // return errors.New("cache error") // !nashtsai! no need to return error, but continue instead + continue + } + if sliceValue.Kind() == reflect.Slice { + if t.Kind() == reflect.Ptr { + sliceValue.Set(reflect.Append(sliceValue, reflect.ValueOf(bean))) + } else { + sliceValue.Set(reflect.Append(sliceValue, reflect.Indirect(reflect.ValueOf(bean)))) + } + } else if sliceValue.Kind() == reflect.Map { + var key = ids[j] + keyType := sliceValue.Type().Key() + var ikey interface{} + if len(key) == 1 { + ikey, err = str2PK(fmt.Sprintf("%v", key[0]), keyType) + if err != nil { + return err + } + } else { + if keyType.Kind() != reflect.Slice { + return errors.New("table have multiple primary keys, key is not core.PK or slice") + } + ikey = key + } + + if t.Kind() == reflect.Ptr { + sliceValue.SetMapIndex(reflect.ValueOf(ikey), reflect.ValueOf(bean)) + } else { + sliceValue.SetMapIndex(reflect.ValueOf(ikey), reflect.Indirect(reflect.ValueOf(bean))) + } + } + } + + return nil +} diff --git a/session_get.go b/session_get.go new file mode 100644 index 00000000..f32bf481 --- /dev/null +++ b/session_get.go @@ -0,0 +1,177 @@ +// Copyright 2016 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package xorm + +import ( + "errors" + "reflect" + "strconv" + + "github.com/go-xorm/core" +) + +// Get retrieve one record from database, bean's non-empty fields +// will be as conditions +func (session *Session) Get(bean interface{}) (bool, error) { + defer session.resetStatement() + if session.IsAutoClose { + defer session.Close() + } + + session.Statement.setRefValue(rValue(bean)) + + var sqlStr string + var args []interface{} + + if session.Statement.RawSQL == "" { + if len(session.Statement.TableName()) <= 0 { + return false, ErrTableNotFound + } + session.Statement.Limit(1) + sqlStr, args = session.Statement.genGetSQL(bean) + } else { + sqlStr = session.Statement.RawSQL + args = session.Statement.RawParams + } + + if session.canCache() { + if cacher := session.Engine.getCacher2(session.Statement.RefTable); cacher != nil && + !session.Statement.unscoped { + has, err := session.cacheGet(bean, sqlStr, args...) + if err != ErrCacheFailed { + return has, err + } + } + } + + return session.nocacheGet(bean, sqlStr, args...) +} + +func (session *Session) nocacheGet(bean interface{}, sqlStr string, args ...interface{}) (bool, error) { + var rawRows *core.Rows + var err error + session.queryPreprocess(&sqlStr, args...) + if session.IsAutoCommit { + _, rawRows, err = session.innerQuery(sqlStr, args...) + } else { + rawRows, err = session.Tx.Query(sqlStr, args...) + } + if err != nil { + return false, err + } + + defer rawRows.Close() + + if rawRows.Next() { + fields, err := rawRows.Columns() + if err == nil { + err = session.row2Bean(rawRows, fields, len(fields), bean) + } + return true, err + } + return false, nil +} + +func (session *Session) cacheGet(bean interface{}, sqlStr string, args ...interface{}) (has bool, err error) { + // if has no reftable, then don't use cache currently + if !session.canCache() { + return false, ErrCacheFailed + } + + for _, filter := range session.Engine.dialect.Filters() { + sqlStr = filter.Do(sqlStr, session.Engine.dialect, session.Statement.RefTable) + } + newsql := session.Statement.convertIDSQL(sqlStr) + if newsql == "" { + return false, ErrCacheFailed + } + + cacher := session.Engine.getCacher2(session.Statement.RefTable) + tableName := session.Statement.TableName() + session.Engine.logger.Debug("[cacheGet] find sql:", newsql, args) + ids, err := core.GetCacheSql(cacher, tableName, newsql, args) + table := session.Statement.RefTable + if err != nil { + var res = make([]string, len(table.PrimaryKeys)) + rows, err := session.DB().Query(newsql, args...) + if err != nil { + return false, err + } + defer rows.Close() + + if rows.Next() { + err = rows.ScanSlice(&res) + if err != nil { + return false, err + } + } else { + return false, ErrCacheFailed + } + + var pk core.PK = make([]interface{}, len(table.PrimaryKeys)) + for i, col := range table.PKColumns() { + if col.SQLType.IsText() { + pk[i] = res[i] + } else if col.SQLType.IsNumeric() { + n, err := strconv.ParseInt(res[i], 10, 64) + if err != nil { + return false, err + } + pk[i] = n + } else { + return false, errors.New("unsupported") + } + } + + ids = []core.PK{pk} + session.Engine.logger.Debug("[cacheGet] cache ids:", newsql, ids) + err = core.PutCacheSql(cacher, ids, tableName, newsql, args) + if err != nil { + return false, err + } + } else { + session.Engine.logger.Debug("[cacheGet] cache hit sql:", newsql) + } + + if len(ids) > 0 { + structValue := reflect.Indirect(reflect.ValueOf(bean)) + id := ids[0] + session.Engine.logger.Debug("[cacheGet] get bean:", tableName, id) + sid, err := id.ToString() + if err != nil { + return false, err + } + cacheBean := cacher.GetBean(tableName, sid) + if cacheBean == nil { + /*newSession := session.Engine.NewSession() + defer newSession.Close() + cacheBean = reflect.New(structValue.Type()).Interface() + newSession.Id(id).NoCache() + if session.Statement.AltTableName != "" { + newSession.Table(session.Statement.AltTableName) + } + if !session.Statement.UseCascade { + newSession.NoCascade() + } + has, err = newSession.Get(cacheBean) + */ + cacheBean = bean + has, err = session.nocacheGet(cacheBean, sqlStr, args...) + if err != nil || !has { + return has, err + } + + session.Engine.logger.Debug("[cacheGet] cache bean:", tableName, id, cacheBean) + cacher.PutBean(tableName, sid, cacheBean) + } else { + session.Engine.logger.Debug("[cacheGet] cache hit bean:", tableName, id, cacheBean) + has = true + } + structValue.Set(reflect.Indirect(reflect.ValueOf(cacheBean))) + + return has, nil + } + return false, nil +} diff --git a/session_insert.go b/session_insert.go new file mode 100644 index 00000000..96e969c2 --- /dev/null +++ b/session_insert.go @@ -0,0 +1,527 @@ +// Copyright 2016 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package xorm + +import ( + "errors" + "fmt" + "reflect" + "strconv" + "strings" + + "github.com/go-xorm/core" +) + +// Insert insert one or more beans +func (session *Session) Insert(beans ...interface{}) (int64, error) { + var affected int64 + var err error + + if session.IsAutoClose { + defer session.Close() + } + defer session.resetStatement() + + for _, bean := range beans { + sliceValue := reflect.Indirect(reflect.ValueOf(bean)) + if sliceValue.Kind() == reflect.Slice { + size := sliceValue.Len() + if size > 0 { + if session.Engine.SupportInsertMany() { + cnt, err := session.innerInsertMulti(bean) + if err != nil { + return affected, err + } + affected += cnt + } else { + for i := 0; i < size; i++ { + cnt, err := session.innerInsert(sliceValue.Index(i).Interface()) + if err != nil { + return affected, err + } + affected += cnt + } + } + } + } else { + cnt, err := session.innerInsert(bean) + if err != nil { + return affected, err + } + affected += cnt + } + } + + return affected, err +} + +func (session *Session) innerInsertMulti(rowsSlicePtr interface{}) (int64, error) { + sliceValue := reflect.Indirect(reflect.ValueOf(rowsSlicePtr)) + if sliceValue.Kind() != reflect.Slice { + return 0, errors.New("needs a pointer to a slice") + } + + if sliceValue.Len() <= 0 { + return 0, errors.New("could not insert a empty slice") + } + + session.Statement.setRefValue(sliceValue.Index(0)) + + if len(session.Statement.TableName()) <= 0 { + return 0, ErrTableNotFound + } + + table := session.Statement.RefTable + size := sliceValue.Len() + + var colNames []string + var colMultiPlaces []string + var args []interface{} + var cols []*core.Column + + for i := 0; i < size; i++ { + v := sliceValue.Index(i) + vv := reflect.Indirect(v) + elemValue := v.Interface() + var colPlaces []string + + // handle BeforeInsertProcessor + // !nashtsai! does user expect it's same slice to passed closure when using Before()/After() when insert multi?? + for _, closure := range session.beforeClosures { + closure(elemValue) + } + + if processor, ok := interface{}(elemValue).(BeforeInsertProcessor); ok { + processor.BeforeInsert() + } + // -- + + if i == 0 { + for _, col := range table.Columns() { + ptrFieldValue, err := col.ValueOfV(&vv) + if err != nil { + return 0, err + } + fieldValue := *ptrFieldValue + if col.IsAutoIncrement && isZero(fieldValue.Interface()) { + continue + } + if col.MapType == core.ONLYFROMDB { + continue + } + if col.IsDeleted { + continue + } + if session.Statement.ColumnStr != "" { + if _, ok := getFlagForColumn(session.Statement.columnMap, col); !ok { + continue + } + } + if session.Statement.OmitStr != "" { + if _, ok := getFlagForColumn(session.Statement.columnMap, col); ok { + continue + } + } + if (col.IsCreated || col.IsUpdated) && session.Statement.UseAutoTime { + val, t := session.Engine.NowTime2(col.SQLType.Name) + args = append(args, val) + + var colName = col.Name + session.afterClosures = append(session.afterClosures, func(bean interface{}) { + col := table.GetColumn(colName) + setColumnTime(bean, col, t) + }) + } else if col.IsVersion && session.Statement.checkVersion { + args = append(args, 1) + var colName = col.Name + session.afterClosures = append(session.afterClosures, func(bean interface{}) { + col := table.GetColumn(colName) + setColumnInt(bean, col, 1) + }) + } else { + arg, err := session.value2Interface(col, fieldValue) + if err != nil { + return 0, err + } + args = append(args, arg) + } + + colNames = append(colNames, col.Name) + cols = append(cols, col) + colPlaces = append(colPlaces, "?") + } + } else { + for _, col := range cols { + ptrFieldValue, err := col.ValueOfV(&vv) + if err != nil { + return 0, err + } + fieldValue := *ptrFieldValue + + if col.IsAutoIncrement && isZero(fieldValue.Interface()) { + continue + } + if col.MapType == core.ONLYFROMDB { + continue + } + if col.IsDeleted { + continue + } + if session.Statement.ColumnStr != "" { + if _, ok := getFlagForColumn(session.Statement.columnMap, col); !ok { + continue + } + } + if session.Statement.OmitStr != "" { + if _, ok := getFlagForColumn(session.Statement.columnMap, col); ok { + continue + } + } + if (col.IsCreated || col.IsUpdated) && session.Statement.UseAutoTime { + val, t := session.Engine.NowTime2(col.SQLType.Name) + args = append(args, val) + + var colName = col.Name + session.afterClosures = append(session.afterClosures, func(bean interface{}) { + col := table.GetColumn(colName) + setColumnTime(bean, col, t) + }) + } else if col.IsVersion && session.Statement.checkVersion { + args = append(args, 1) + var colName = col.Name + session.afterClosures = append(session.afterClosures, func(bean interface{}) { + col := table.GetColumn(colName) + setColumnInt(bean, col, 1) + }) + } else { + arg, err := session.value2Interface(col, fieldValue) + if err != nil { + return 0, err + } + args = append(args, arg) + } + + colPlaces = append(colPlaces, "?") + } + } + colMultiPlaces = append(colMultiPlaces, strings.Join(colPlaces, ", ")) + } + cleanupProcessorsClosures(&session.beforeClosures) + + statement := fmt.Sprintf("INSERT INTO %s (%v%v%v) VALUES (%v)", + session.Engine.Quote(session.Statement.TableName()), + session.Engine.QuoteStr(), + strings.Join(colNames, session.Engine.QuoteStr()+", "+session.Engine.QuoteStr()), + session.Engine.QuoteStr(), + strings.Join(colMultiPlaces, "),(")) + + res, err := session.exec(statement, args...) + if err != nil { + return 0, err + } + + if cacher := session.Engine.getCacher2(table); cacher != nil && session.Statement.UseCache { + session.cacheInsert(session.Statement.TableName()) + } + + lenAfterClosures := len(session.afterClosures) + for i := 0; i < size; i++ { + elemValue := reflect.Indirect(sliceValue.Index(i)).Addr().Interface() + + // handle AfterInsertProcessor + if session.IsAutoCommit { + // !nashtsai! does user expect it's same slice to passed closure when using Before()/After() when insert multi?? + for _, closure := range session.afterClosures { + closure(elemValue) + } + if processor, ok := interface{}(elemValue).(AfterInsertProcessor); ok { + processor.AfterInsert() + } + } else { + if lenAfterClosures > 0 { + if value, has := session.afterInsertBeans[elemValue]; has && value != nil { + *value = append(*value, session.afterClosures...) + } else { + afterClosures := make([]func(interface{}), lenAfterClosures) + copy(afterClosures, session.afterClosures) + session.afterInsertBeans[elemValue] = &afterClosures + } + } else { + if _, ok := interface{}(elemValue).(AfterInsertProcessor); ok { + session.afterInsertBeans[elemValue] = nil + } + } + } + } + + cleanupProcessorsClosures(&session.afterClosures) + return res.RowsAffected() +} + +// InsertMulti insert multiple records +func (session *Session) InsertMulti(rowsSlicePtr interface{}) (int64, error) { + defer session.resetStatement() + if session.IsAutoClose { + defer session.Close() + } + + sliceValue := reflect.Indirect(reflect.ValueOf(rowsSlicePtr)) + if sliceValue.Kind() != reflect.Slice { + return 0, ErrParamsType + + } + + if sliceValue.Len() <= 0 { + return 0, nil + } + + return session.innerInsertMulti(rowsSlicePtr) +} + +func (session *Session) innerInsert(bean interface{}) (int64, error) { + session.Statement.setRefValue(rValue(bean)) + if len(session.Statement.TableName()) <= 0 { + return 0, ErrTableNotFound + } + + table := session.Statement.RefTable + + // handle BeforeInsertProcessor + for _, closure := range session.beforeClosures { + closure(bean) + } + cleanupProcessorsClosures(&session.beforeClosures) // cleanup after used + + if processor, ok := interface{}(bean).(BeforeInsertProcessor); ok { + processor.BeforeInsert() + } + // -- + colNames, args, err := genCols(session.Statement.RefTable, session, bean, false, false) + if err != nil { + return 0, err + } + // insert expr columns, override if exists + exprColumns := session.Statement.getExpr() + exprColVals := make([]string, 0, len(exprColumns)) + for _, v := range exprColumns { + // remove the expr columns + for i, colName := range colNames { + if colName == v.colName { + colNames = append(colNames[:i], colNames[i+1:]...) + args = append(args[:i], args[i+1:]...) + } + } + + // append expr column to the end + colNames = append(colNames, v.colName) + exprColVals = append(exprColVals, v.expr) + } + + colPlaces := strings.Repeat("?, ", len(colNames)-len(exprColumns)) + if len(exprColVals) > 0 { + colPlaces = colPlaces + strings.Join(exprColVals, ", ") + } else { + colPlaces = colPlaces[0 : len(colPlaces)-2] + } + + sqlStr := fmt.Sprintf("INSERT INTO %s (%v%v%v) VALUES (%v)", + session.Engine.Quote(session.Statement.TableName()), + session.Engine.QuoteStr(), + strings.Join(colNames, session.Engine.Quote(", ")), + session.Engine.QuoteStr(), + colPlaces) + + handleAfterInsertProcessorFunc := func(bean interface{}) { + if session.IsAutoCommit { + for _, closure := range session.afterClosures { + closure(bean) + } + if processor, ok := interface{}(bean).(AfterInsertProcessor); ok { + processor.AfterInsert() + } + } else { + lenAfterClosures := len(session.afterClosures) + if lenAfterClosures > 0 { + if value, has := session.afterInsertBeans[bean]; has && value != nil { + *value = append(*value, session.afterClosures...) + } else { + afterClosures := make([]func(interface{}), lenAfterClosures) + copy(afterClosures, session.afterClosures) + session.afterInsertBeans[bean] = &afterClosures + } + + } else { + if _, ok := interface{}(bean).(AfterInsertProcessor); ok { + session.afterInsertBeans[bean] = nil + } + } + } + cleanupProcessorsClosures(&session.afterClosures) // cleanup after used + } + + // for postgres, many of them didn't implement lastInsertId, so we should + // implemented it ourself. + if session.Engine.dialect.DBType() == core.ORACLE && len(table.AutoIncrement) > 0 { + //assert table.AutoIncrement != "" + res, err := session.query("select seq_atable.currval from dual", args...) + if err != nil { + return 0, err + } + + handleAfterInsertProcessorFunc(bean) + + if cacher := session.Engine.getCacher2(table); cacher != nil && session.Statement.UseCache { + session.cacheInsert(session.Statement.TableName()) + } + + if table.Version != "" && session.Statement.checkVersion { + verValue, err := table.VersionColumn().ValueOf(bean) + if err != nil { + session.Engine.logger.Error(err) + } else if verValue.IsValid() && verValue.CanSet() { + verValue.SetInt(1) + } + } + + if len(res) < 1 { + return 0, errors.New("insert no error but not returned id") + } + + idByte := res[0][table.AutoIncrement] + id, err := strconv.ParseInt(string(idByte), 10, 64) + if err != nil || id <= 0 { + return 1, err + } + + aiValue, err := table.AutoIncrColumn().ValueOf(bean) + if err != nil { + session.Engine.logger.Error(err) + } + + if aiValue == nil || !aiValue.IsValid() || !aiValue.CanSet() { + return 1, nil + } + + aiValue.Set(int64ToIntValue(id, aiValue.Type())) + + return 1, nil + } else if session.Engine.dialect.DBType() == core.POSTGRES && len(table.AutoIncrement) > 0 { + //assert table.AutoIncrement != "" + sqlStr = sqlStr + " RETURNING " + session.Engine.Quote(table.AutoIncrement) + res, err := session.query(sqlStr, args...) + + if err != nil { + return 0, err + } + handleAfterInsertProcessorFunc(bean) + + if cacher := session.Engine.getCacher2(table); cacher != nil && session.Statement.UseCache { + session.cacheInsert(session.Statement.TableName()) + } + + if table.Version != "" && session.Statement.checkVersion { + verValue, err := table.VersionColumn().ValueOf(bean) + if err != nil { + session.Engine.logger.Error(err) + } else if verValue.IsValid() && verValue.CanSet() { + verValue.SetInt(1) + } + } + + if len(res) < 1 { + return 0, errors.New("insert no error but not returned id") + } + + idByte := res[0][table.AutoIncrement] + id, err := strconv.ParseInt(string(idByte), 10, 64) + if err != nil || id <= 0 { + return 1, err + } + + aiValue, err := table.AutoIncrColumn().ValueOf(bean) + if err != nil { + session.Engine.logger.Error(err) + } + + if aiValue == nil || !aiValue.IsValid() || !aiValue.CanSet() { + return 1, nil + } + + aiValue.Set(int64ToIntValue(id, aiValue.Type())) + + return 1, nil + } else { + res, err := session.exec(sqlStr, args...) + if err != nil { + return 0, err + } + + defer handleAfterInsertProcessorFunc(bean) + + if cacher := session.Engine.getCacher2(table); cacher != nil && session.Statement.UseCache { + session.cacheInsert(session.Statement.TableName()) + } + + if table.Version != "" && session.Statement.checkVersion { + verValue, err := table.VersionColumn().ValueOf(bean) + if err != nil { + session.Engine.logger.Error(err) + } else if verValue.IsValid() && verValue.CanSet() { + verValue.SetInt(1) + } + } + + if table.AutoIncrement == "" { + return res.RowsAffected() + } + + var id int64 + id, err = res.LastInsertId() + if err != nil || id <= 0 { + return res.RowsAffected() + } + + aiValue, err := table.AutoIncrColumn().ValueOf(bean) + if err != nil { + session.Engine.logger.Error(err) + } + + if aiValue == nil || !aiValue.IsValid() || !aiValue.CanSet() { + return res.RowsAffected() + } + + aiValue.Set(int64ToIntValue(id, aiValue.Type())) + + return res.RowsAffected() + } +} + +// InsertOne insert only one struct into database as a record. +// The in parameter bean must a struct or a point to struct. The return +// parameter is inserted and error +func (session *Session) InsertOne(bean interface{}) (int64, error) { + defer session.resetStatement() + if session.IsAutoClose { + defer session.Close() + } + + return session.innerInsert(bean) +} + +func (session *Session) cacheInsert(tables ...string) error { + if session.Statement.RefTable == nil { + return ErrCacheFailed + } + + table := session.Statement.RefTable + cacher := session.Engine.getCacher2(table) + + for _, t := range tables { + session.Engine.logger.Debug("[cache] clear sql:", t) + cacher.ClearIds(t) + } + + return nil +} diff --git a/session_iterate.go b/session_iterate.go new file mode 100644 index 00000000..7c148095 --- /dev/null +++ b/session_iterate.go @@ -0,0 +1,42 @@ +// Copyright 2016 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package xorm + +import "reflect" + +// IterFunc only use by Iterate +type IterFunc func(idx int, bean interface{}) error + +// Rows return sql.Rows compatible Rows obj, as a forward Iterator object for iterating record by record, bean's non-empty fields +// are conditions. +func (session *Session) Rows(bean interface{}) (*Rows, error) { + return newRows(session, bean) +} + +// Iterate record by record handle records from table, condiBeans's non-empty fields +// 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 + } + defer rows.Close() + + i := 0 + for rows.Next() { + b := reflect.New(rows.beanType).Interface() + err = rows.Scan(b) + if err != nil { + return err + } + err = fun(i, b) + if err != nil { + return err + } + i++ + } + return err +} diff --git a/session_raw.go b/session_raw.go new file mode 100644 index 00000000..9351d5cf --- /dev/null +++ b/session_raw.go @@ -0,0 +1,144 @@ +// Copyright 2016 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package xorm + +import ( + "database/sql" + + "github.com/go-xorm/core" +) + +func (session *Session) query(sqlStr string, paramStr ...interface{}) (resultsSlice []map[string][]byte, err error) { + session.queryPreprocess(&sqlStr, paramStr...) + + if session.IsAutoCommit { + return session.innerQuery2(sqlStr, paramStr...) + } + return session.txQuery(session.Tx, sqlStr, paramStr...) +} + +func (session *Session) txQuery(tx *core.Tx, sqlStr string, params ...interface{}) (resultsSlice []map[string][]byte, err error) { + rows, err := tx.Query(sqlStr, params...) + if err != nil { + return nil, err + } + defer rows.Close() + + return rows2maps(rows) +} + +func (session *Session) innerQuery(sqlStr string, params ...interface{}) (*core.Stmt, *core.Rows, error) { + var callback func() (*core.Stmt, *core.Rows, error) + if session.prepareStmt { + callback = func() (*core.Stmt, *core.Rows, error) { + stmt, err := session.doPrepare(sqlStr) + if err != nil { + return nil, nil, err + } + rows, err := stmt.Query(params...) + if err != nil { + return nil, nil, err + } + return stmt, rows, nil + } + } else { + callback = func() (*core.Stmt, *core.Rows, error) { + rows, err := session.DB().Query(sqlStr, params...) + if err != nil { + return nil, nil, err + } + return nil, rows, err + } + } + stmt, rows, err := session.Engine.logSQLQueryTime(sqlStr, params, callback) + if err != nil { + return nil, nil, err + } + return stmt, rows, nil +} + +func (session *Session) innerQuery2(sqlStr string, params ...interface{}) ([]map[string][]byte, error) { + _, rows, err := session.innerQuery(sqlStr, params...) + if rows != nil { + defer rows.Close() + } + if err != nil { + return nil, err + } + return rows2maps(rows) +} + +// Query a raw sql and return records as []map[string][]byte +func (session *Session) Query(sqlStr string, paramStr ...interface{}) (resultsSlice []map[string][]byte, err error) { + defer session.resetStatement() + if session.IsAutoClose { + defer session.Close() + } + + return session.query(sqlStr, paramStr...) +} + +// ============================= +// for string +// ============================= +func (session *Session) query2(sqlStr string, paramStr ...interface{}) (resultsSlice []map[string]string, err error) { + session.queryPreprocess(&sqlStr, paramStr...) + + if session.IsAutoCommit { + return query2(session.DB(), sqlStr, paramStr...) + } + return txQuery2(session.Tx, sqlStr, paramStr...) +} + +// Execute sql +func (session *Session) innerExec(sqlStr string, args ...interface{}) (sql.Result, error) { + if session.prepareStmt { + stmt, err := session.doPrepare(sqlStr) + if err != nil { + return nil, err + } + + res, err := stmt.Exec(args...) + if err != nil { + return nil, err + } + return res, nil + } + + return session.DB().Exec(sqlStr, args...) +} + +func (session *Session) exec(sqlStr string, args ...interface{}) (sql.Result, error) { + for _, filter := range session.Engine.dialect.Filters() { + // TODO: for table name, it's no need to RefTable + sqlStr = filter.Do(sqlStr, session.Engine.dialect, session.Statement.RefTable) + } + + session.saveLastSQL(sqlStr, args...) + + return session.Engine.logSQLExecutionTime(sqlStr, args, func() (sql.Result, error) { + if session.IsAutoCommit { + // FIXME: oci8 can not auto commit (github.com/mattn/go-oci8) + if session.Engine.dialect.DBType() == core.ORACLE { + session.Begin() + r, err := session.Tx.Exec(sqlStr, args...) + session.Commit() + return r, err + } + return session.innerExec(sqlStr, args...) + } + return session.Tx.Exec(sqlStr, args...) + }) +} + +// Exec raw sql +func (session *Session) Exec(sqlStr string, args ...interface{}) (sql.Result, error) { + defer session.resetStatement() + if session.IsAutoClose { + defer session.Close() + } + + return session.exec(sqlStr, args...) +} diff --git a/session_schema.go b/session_schema.go new file mode 100644 index 00000000..9011adad --- /dev/null +++ b/session_schema.go @@ -0,0 +1,486 @@ +// Copyright 2016 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package xorm + +import ( + "database/sql" + "errors" + "fmt" + "reflect" + "strings" + + "github.com/go-xorm/core" +) + +// Ping test if database is ok +func (session *Session) Ping() error { + defer session.resetStatement() + if session.IsAutoClose { + defer session.Close() + } + + return session.DB().Ping() +} + +// CreateTable create a table according a bean +func (session *Session) CreateTable(bean interface{}) error { + v := rValue(bean) + session.Statement.setRefValue(v) + + defer session.resetStatement() + if session.IsAutoClose { + defer session.Close() + } + + return session.createOneTable() +} + +// CreateIndexes create indexes +func (session *Session) CreateIndexes(bean interface{}) error { + v := rValue(bean) + session.Statement.setRefValue(v) + + defer session.resetStatement() + if session.IsAutoClose { + defer session.Close() + } + + sqls := session.Statement.genIndexSQL() + for _, sqlStr := range sqls { + _, err := session.exec(sqlStr) + if err != nil { + return err + } + } + return nil +} + +// CreateUniques create uniques +func (session *Session) CreateUniques(bean interface{}) error { + v := rValue(bean) + session.Statement.setRefValue(v) + + defer session.resetStatement() + if session.IsAutoClose { + defer session.Close() + } + + sqls := session.Statement.genUniqueSQL() + for _, sqlStr := range sqls { + _, err := session.exec(sqlStr) + if err != nil { + return err + } + } + return nil +} + +func (session *Session) createOneTable() error { + sqlStr := session.Statement.genCreateTableSQL() + _, err := session.exec(sqlStr) + return err +} + +// to be deleted +func (session *Session) createAll() error { + if session.IsAutoClose { + defer session.Close() + } + + for _, table := range session.Engine.Tables { + session.Statement.RefTable = table + session.Statement.tableName = table.Name + err := session.createOneTable() + session.resetStatement() + if err != nil { + return err + } + } + return nil +} + +// DropIndexes drop indexes +func (session *Session) DropIndexes(bean interface{}) error { + v := rValue(bean) + session.Statement.setRefValue(v) + + defer session.resetStatement() + if session.IsAutoClose { + defer session.Close() + } + + sqls := session.Statement.genDelIndexSQL() + for _, sqlStr := range sqls { + _, err := session.exec(sqlStr) + if err != nil { + return err + } + } + return nil +} + +// DropTable drop table will drop table if exist, if drop failed, it will return error +func (session *Session) DropTable(beanOrTableName interface{}) error { + tableName, err := session.Engine.tableName(beanOrTableName) + if err != nil { + return err + } + + var needDrop = true + if !session.Engine.dialect.SupportDropIfExists() { + sqlStr, args := session.Engine.dialect.TableCheckSql(tableName) + results, err := session.query(sqlStr, args...) + if err != nil { + return err + } + needDrop = len(results) > 0 + } + + if needDrop { + sqlStr := session.Engine.Dialect().DropTableSql(tableName) + _, err = session.exec(sqlStr) + return err + } + return nil +} + +// IsTableExist if a table is exist +func (session *Session) IsTableExist(beanOrTableName interface{}) (bool, error) { + tableName, err := session.Engine.tableName(beanOrTableName) + if err != nil { + return false, err + } + + return session.isTableExist(tableName) +} + +func (session *Session) isTableExist(tableName string) (bool, error) { + defer session.resetStatement() + if session.IsAutoClose { + defer session.Close() + } + sqlStr, args := session.Engine.dialect.TableCheckSql(tableName) + results, err := session.query(sqlStr, args...) + return len(results) > 0, err +} + +// IsTableEmpty if table have any records +func (session *Session) IsTableEmpty(bean interface{}) (bool, error) { + v := rValue(bean) + t := v.Type() + + if t.Kind() == reflect.String { + return session.isTableEmpty(bean.(string)) + } else if t.Kind() == reflect.Struct { + rows, err := session.Count(bean) + return rows == 0, err + } + return false, errors.New("bean should be a struct or struct's point") +} + +func (session *Session) isTableEmpty(tableName string) (bool, error) { + defer session.resetStatement() + if session.IsAutoClose { + defer session.Close() + } + + var total int64 + sqlStr := fmt.Sprintf("select count(*) from %s", session.Engine.Quote(tableName)) + err := session.DB().QueryRow(sqlStr).Scan(&total) + session.saveLastSQL(sqlStr) + if err != nil { + if err == sql.ErrNoRows { + err = nil + } + return true, err + } + + return total == 0, nil +} + +func (session *Session) isIndexExist(tableName, idxName string, unique bool) (bool, error) { + defer session.resetStatement() + if session.IsAutoClose { + defer session.Close() + } + var idx string + if unique { + idx = uniqueName(tableName, idxName) + } else { + idx = indexName(tableName, idxName) + } + sqlStr, args := session.Engine.dialect.IndexCheckSql(tableName, idx) + results, err := session.query(sqlStr, args...) + return len(results) > 0, err +} + +// find if index is exist according cols +func (session *Session) isIndexExist2(tableName string, cols []string, unique bool) (bool, error) { + defer session.resetStatement() + if session.IsAutoClose { + defer session.Close() + } + + indexes, err := session.Engine.dialect.GetIndexes(tableName) + if err != nil { + return false, err + } + + for _, index := range indexes { + if sliceEq(index.Cols, cols) { + if unique { + return index.Type == core.UniqueType, nil + } + return index.Type == core.IndexType, nil + } + } + return false, nil +} + +func (session *Session) addColumn(colName string) error { + defer session.resetStatement() + if session.IsAutoClose { + defer session.Close() + } + + col := session.Statement.RefTable.GetColumn(colName) + sql, args := session.Statement.genAddColumnStr(col) + _, err := session.exec(sql, args...) + return err +} + +func (session *Session) addIndex(tableName, idxName string) error { + defer session.resetStatement() + if session.IsAutoClose { + defer session.Close() + } + index := session.Statement.RefTable.Indexes[idxName] + sqlStr := session.Engine.dialect.CreateIndexSql(tableName, index) + + _, err := session.exec(sqlStr) + return err +} + +func (session *Session) addUnique(tableName, uqeName string) error { + defer session.resetStatement() + if session.IsAutoClose { + defer session.Close() + } + index := session.Statement.RefTable.Indexes[uqeName] + sqlStr := session.Engine.dialect.CreateIndexSql(tableName, index) + _, err := session.exec(sqlStr) + return err +} + +// To be deleted +func (session *Session) dropAll() error { + defer session.resetStatement() + if session.IsAutoClose { + defer session.Close() + } + + for _, table := range session.Engine.Tables { + session.Statement.Init() + session.Statement.RefTable = table + sqlStr := session.Engine.Dialect().DropTableSql(session.Statement.TableName()) + _, err := session.exec(sqlStr) + if err != nil { + return err + } + } + return nil +} + +// Sync2 synchronize structs to database tables +func (session *Session) Sync2(beans ...interface{}) error { + engine := session.Engine + + tables, err := engine.DBMetas() + if err != nil { + return err + } + + var structTables []*core.Table + + for _, bean := range beans { + v := rValue(bean) + table := engine.mapType(v) + structTables = append(structTables, table) + var tbName = session.tbNameNoSchema(table) + + var oriTable *core.Table + for _, tb := range tables { + if strings.EqualFold(tb.Name, tbName) { + oriTable = tb + break + } + } + + if oriTable == nil { + err = session.StoreEngine(session.Statement.StoreEngine).CreateTable(bean) + if err != nil { + return err + } + + err = session.CreateUniques(bean) + if err != nil { + return err + } + + err = session.CreateIndexes(bean) + if err != nil { + return err + } + } else { + for _, col := range table.Columns() { + var oriCol *core.Column + for _, col2 := range oriTable.Columns() { + if strings.EqualFold(col.Name, col2.Name) { + oriCol = col2 + break + } + } + + if oriCol != nil { + expectedType := engine.dialect.SqlType(col) + curType := engine.dialect.SqlType(oriCol) + if expectedType != curType { + if expectedType == core.Text && + strings.HasPrefix(curType, core.Varchar) { + // currently only support mysql & postgres + if engine.dialect.DBType() == core.MYSQL || + engine.dialect.DBType() == core.POSTGRES { + engine.logger.Infof("Table %s column %s change type from %s to %s\n", + tbName, col.Name, curType, expectedType) + _, err = engine.Exec(engine.dialect.ModifyColumnSql(table.Name, col)) + } else { + engine.logger.Warnf("Table %s column %s db type is %s, struct type is %s\n", + tbName, col.Name, curType, expectedType) + } + } else if strings.HasPrefix(curType, core.Varchar) && strings.HasPrefix(expectedType, core.Varchar) { + if engine.dialect.DBType() == core.MYSQL { + if oriCol.Length < col.Length { + engine.logger.Infof("Table %s column %s change type from varchar(%d) to varchar(%d)\n", + tbName, col.Name, oriCol.Length, col.Length) + _, err = engine.Exec(engine.dialect.ModifyColumnSql(table.Name, col)) + } + } + } else { + if !(strings.HasPrefix(curType, expectedType) && curType[len(expectedType)] == '(') { + engine.logger.Warnf("Table %s column %s db type is %s, struct type is %s", + tbName, col.Name, curType, expectedType) + } + } + } else if expectedType == core.Varchar { + if engine.dialect.DBType() == core.MYSQL { + if oriCol.Length < col.Length { + engine.logger.Infof("Table %s column %s change type from varchar(%d) to varchar(%d)\n", + tbName, col.Name, oriCol.Length, col.Length) + _, err = engine.Exec(engine.dialect.ModifyColumnSql(table.Name, col)) + } + } + } + if col.Default != oriCol.Default { + engine.logger.Warnf("Table %s Column %s db default is %s, struct default is %s", + tbName, col.Name, oriCol.Default, col.Default) + } + if col.Nullable != oriCol.Nullable { + engine.logger.Warnf("Table %s Column %s db nullable is %v, struct nullable is %v", + tbName, col.Name, oriCol.Nullable, col.Nullable) + } + } else { + session := engine.NewSession() + session.Statement.RefTable = table + session.Statement.tableName = tbName + defer session.Close() + err = session.addColumn(col.Name) + } + if err != nil { + return err + } + } + + var foundIndexNames = make(map[string]bool) + var addedNames = make(map[string]*core.Index) + + for name, index := range table.Indexes { + var oriIndex *core.Index + for name2, index2 := range oriTable.Indexes { + if index.Equal(index2) { + oriIndex = index2 + foundIndexNames[name2] = true + break + } + } + + if oriIndex != nil { + if oriIndex.Type != index.Type { + sql := engine.dialect.DropIndexSql(tbName, oriIndex) + _, err = engine.Exec(sql) + if err != nil { + return err + } + oriIndex = nil + } + } + + if oriIndex == nil { + addedNames[name] = index + } + } + + for name2, index2 := range oriTable.Indexes { + if _, ok := foundIndexNames[name2]; !ok { + sql := engine.dialect.DropIndexSql(tbName, index2) + _, err = engine.Exec(sql) + if err != nil { + return err + } + } + } + + for name, index := range addedNames { + if index.Type == core.UniqueType { + session := engine.NewSession() + session.Statement.RefTable = table + session.Statement.tableName = tbName + defer session.Close() + err = session.addUnique(tbName, name) + } else if index.Type == core.IndexType { + session := engine.NewSession() + session.Statement.RefTable = table + session.Statement.tableName = tbName + defer session.Close() + err = session.addIndex(tbName, name) + } + if err != nil { + return err + } + } + } + } + + for _, table := range tables { + var oriTable *core.Table + for _, structTable := range structTables { + if strings.EqualFold(table.Name, session.tbNameNoSchema(structTable)) { + oriTable = structTable + break + } + } + + if oriTable == nil { + //engine.LogWarnf("Table %s has no struct to mapping it", table.Name) + continue + } + + for _, colName := range table.ColumnsSeq() { + if oriTable.GetColumn(colName) == nil { + engine.logger.Warnf("Table %s has column %s but struct has not related field", table.Name, colName) + } + } + } + return nil +} diff --git a/session_sum.go b/session_sum.go new file mode 100644 index 00000000..127f83f2 --- /dev/null +++ b/session_sum.go @@ -0,0 +1,137 @@ +// Copyright 2016 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package xorm + +import "database/sql" + +// Count counts the records. bean's non-empty fields +// are conditions. +func (session *Session) Count(bean interface{}) (int64, error) { + defer session.resetStatement() + if session.IsAutoClose { + defer session.Close() + } + + var sqlStr string + var args []interface{} + if session.Statement.RawSQL == "" { + sqlStr, args = session.Statement.genCountSQL(bean) + } else { + sqlStr = session.Statement.RawSQL + args = session.Statement.RawParams + } + + session.queryPreprocess(&sqlStr, args...) + + var err error + var total int64 + if session.IsAutoCommit { + err = session.DB().QueryRow(sqlStr, args...).Scan(&total) + } else { + err = session.Tx.QueryRow(sqlStr, args...).Scan(&total) + } + + if err == sql.ErrNoRows || err == nil { + return total, nil + } + + return 0, err +} + +// Sum call sum some column. bean's non-empty fields are conditions. +func (session *Session) Sum(bean interface{}, columnName string) (float64, error) { + defer session.resetStatement() + if session.IsAutoClose { + defer session.Close() + } + + var sqlStr string + var args []interface{} + if len(session.Statement.RawSQL) == 0 { + sqlStr, args = session.Statement.genSumSQL(bean, columnName) + } else { + sqlStr = session.Statement.RawSQL + args = session.Statement.RawParams + } + + session.queryPreprocess(&sqlStr, args...) + + var err error + var res float64 + if session.IsAutoCommit { + err = session.DB().QueryRow(sqlStr, args...).Scan(&res) + } else { + err = session.Tx.QueryRow(sqlStr, args...).Scan(&res) + } + + if err == sql.ErrNoRows || err == nil { + return res, nil + } + return 0, err +} + +// Sums call sum some columns. bean's non-empty fields are conditions. +func (session *Session) Sums(bean interface{}, columnNames ...string) ([]float64, error) { + defer session.resetStatement() + if session.IsAutoClose { + defer session.Close() + } + + var sqlStr string + var args []interface{} + if len(session.Statement.RawSQL) == 0 { + sqlStr, args = session.Statement.genSumSQL(bean, columnNames...) + } else { + sqlStr = session.Statement.RawSQL + args = session.Statement.RawParams + } + + session.queryPreprocess(&sqlStr, args...) + + var err error + var res = make([]float64, len(columnNames), len(columnNames)) + if session.IsAutoCommit { + err = session.DB().QueryRow(sqlStr, args...).ScanSlice(&res) + } else { + err = session.Tx.QueryRow(sqlStr, args...).ScanSlice(&res) + } + + if err == sql.ErrNoRows || err == nil { + return res, nil + } + return nil, err +} + +// SumsInt sum specify columns and return as []int64 instead of []float64 +func (session *Session) SumsInt(bean interface{}, columnNames ...string) ([]int64, error) { + defer session.resetStatement() + if session.IsAutoClose { + defer session.Close() + } + + var sqlStr string + var args []interface{} + if len(session.Statement.RawSQL) == 0 { + sqlStr, args = session.Statement.genSumSQL(bean, columnNames...) + } else { + sqlStr = session.Statement.RawSQL + args = session.Statement.RawParams + } + + session.queryPreprocess(&sqlStr, args...) + + var err error + var res = make([]int64, 0, len(columnNames)) + if session.IsAutoCommit { + err = session.DB().QueryRow(sqlStr, args...).ScanSlice(&res) + } else { + err = session.Tx.QueryRow(sqlStr, args...).ScanSlice(&res) + } + + if err == sql.ErrNoRows || err == nil { + return res, nil + } + return nil, err +} diff --git a/session_tx.go b/session_tx.go new file mode 100644 index 00000000..302bc104 --- /dev/null +++ b/session_tx.go @@ -0,0 +1,83 @@ +// Copyright 2016 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package xorm + +// Begin a transaction +func (session *Session) Begin() error { + if session.IsAutoCommit { + tx, err := session.DB().Begin() + if err != nil { + return err + } + session.IsAutoCommit = false + session.IsCommitedOrRollbacked = false + session.Tx = tx + session.saveLastSQL("BEGIN TRANSACTION") + } + return nil +} + +// Rollback When using transaction, you can rollback if any error +func (session *Session) Rollback() error { + if !session.IsAutoCommit && !session.IsCommitedOrRollbacked { + session.saveLastSQL(session.Engine.dialect.RollBackStr()) + session.IsCommitedOrRollbacked = true + return session.Tx.Rollback() + } + return nil +} + +// Commit When using transaction, Commit will commit all operations. +func (session *Session) Commit() error { + if !session.IsAutoCommit && !session.IsCommitedOrRollbacked { + session.saveLastSQL("COMMIT") + session.IsCommitedOrRollbacked = true + var err error + if err = session.Tx.Commit(); err == nil { + // handle processors after tx committed + + closureCallFunc := func(closuresPtr *[]func(interface{}), bean interface{}) { + + if closuresPtr != nil { + for _, closure := range *closuresPtr { + closure(bean) + } + } + } + + for bean, closuresPtr := range session.afterInsertBeans { + closureCallFunc(closuresPtr, bean) + + if processor, ok := interface{}(bean).(AfterInsertProcessor); ok { + processor.AfterInsert() + } + } + for bean, closuresPtr := range session.afterUpdateBeans { + closureCallFunc(closuresPtr, bean) + + if processor, ok := interface{}(bean).(AfterUpdateProcessor); ok { + processor.AfterUpdate() + } + } + for bean, closuresPtr := range session.afterDeleteBeans { + closureCallFunc(closuresPtr, bean) + + if processor, ok := interface{}(bean).(AfterDeleteProcessor); ok { + processor.AfterDelete() + } + } + cleanUpFunc := func(slices *map[interface{}]*[]func(interface{})) { + if len(*slices) > 0 { + *slices = make(map[interface{}]*[]func(interface{}), 0) + } + } + cleanUpFunc(&session.afterInsertBeans) + cleanUpFunc(&session.afterUpdateBeans) + cleanUpFunc(&session.afterDeleteBeans) + } + return err + } + return nil +} diff --git a/session_update.go b/session_update.go new file mode 100644 index 00000000..17e5672e --- /dev/null +++ b/session_update.go @@ -0,0 +1,345 @@ +// Copyright 2016 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package xorm + +import ( + "errors" + "fmt" + "reflect" + "strconv" + "strings" + + "github.com/go-xorm/builder" + "github.com/go-xorm/core" +) + +func (session *Session) cacheUpdate(sqlStr string, args ...interface{}) error { + if session.Statement.RefTable == nil || + session.Tx != nil { + return ErrCacheFailed + } + + oldhead, newsql := session.Statement.convertUpdateSQL(sqlStr) + if newsql == "" { + return ErrCacheFailed + } + for _, filter := range session.Engine.dialect.Filters() { + newsql = filter.Do(newsql, session.Engine.dialect, session.Statement.RefTable) + } + session.Engine.logger.Debug("[cacheUpdate] new sql", oldhead, newsql) + + var nStart int + if len(args) > 0 { + if strings.Index(sqlStr, "?") > -1 { + nStart = strings.Count(oldhead, "?") + } else { + // only for pq, TODO: if any other databse? + nStart = strings.Count(oldhead, "$") + } + } + table := session.Statement.RefTable + cacher := session.Engine.getCacher2(table) + tableName := session.Statement.TableName() + session.Engine.logger.Debug("[cacheUpdate] get cache sql", newsql, args[nStart:]) + ids, err := core.GetCacheSql(cacher, tableName, newsql, args[nStart:]) + if err != nil { + rows, err := session.DB().Query(newsql, args[nStart:]...) + if err != nil { + return err + } + defer rows.Close() + + ids = make([]core.PK, 0) + for rows.Next() { + var res = make([]string, len(table.PrimaryKeys)) + err = rows.ScanSlice(&res) + if err != nil { + return err + } + var pk core.PK = make([]interface{}, len(table.PrimaryKeys)) + for i, col := range table.PKColumns() { + if col.SQLType.IsNumeric() { + n, err := strconv.ParseInt(res[i], 10, 64) + if err != nil { + return err + } + pk[i] = n + } else if col.SQLType.IsText() { + pk[i] = res[i] + } else { + return errors.New("not supported") + } + } + + ids = append(ids, pk) + } + session.Engine.logger.Debug("[cacheUpdate] find updated id", ids) + } /*else { + session.Engine.LogDebug("[xorm:cacheUpdate] del cached sql:", tableName, newsql, args) + cacher.DelIds(tableName, genSqlKey(newsql, args)) + }*/ + + for _, id := range ids { + sid, err := id.ToString() + if err != nil { + return err + } + if bean := cacher.GetBean(tableName, sid); bean != nil { + sqls := splitNNoCase(sqlStr, "where", 2) + if len(sqls) == 0 || len(sqls) > 2 { + return ErrCacheFailed + } + + sqls = splitNNoCase(sqls[0], "set", 2) + if len(sqls) != 2 { + return ErrCacheFailed + } + kvs := strings.Split(strings.TrimSpace(sqls[1]), ",") + for idx, kv := range kvs { + sps := strings.SplitN(kv, "=", 2) + sps2 := strings.Split(sps[0], ".") + colName := sps2[len(sps2)-1] + if strings.Contains(colName, "`") { + colName = strings.TrimSpace(strings.Replace(colName, "`", "", -1)) + } else if strings.Contains(colName, session.Engine.QuoteStr()) { + colName = strings.TrimSpace(strings.Replace(colName, session.Engine.QuoteStr(), "", -1)) + } else { + session.Engine.logger.Debug("[cacheUpdate] cannot find column", tableName, colName) + return ErrCacheFailed + } + + if col := table.GetColumn(colName); col != nil { + fieldValue, err := col.ValueOf(bean) + if err != nil { + session.Engine.logger.Error(err) + } else { + session.Engine.logger.Debug("[cacheUpdate] set bean field", bean, colName, fieldValue.Interface()) + if col.IsVersion && session.Statement.checkVersion { + fieldValue.SetInt(fieldValue.Int() + 1) + } else { + fieldValue.Set(reflect.ValueOf(args[idx])) + } + } + } else { + session.Engine.logger.Errorf("[cacheUpdate] ERROR: column %v is not table %v's", + colName, table.Name) + } + } + + session.Engine.logger.Debug("[cacheUpdate] update cache", tableName, id, bean) + cacher.PutBean(tableName, sid, bean) + } + } + session.Engine.logger.Debug("[cacheUpdate] clear cached table sql:", tableName) + cacher.ClearIds(tableName) + return nil +} + +// Update records, bean's non-empty fields are updated contents, +// condiBean' non-empty filds are conditions +// CAUTION: +// 1.bool will defaultly be updated content nor conditions +// You should call UseBool if you have bool to use. +// 2.float32 & float64 may be not inexact as conditions +func (session *Session) Update(bean interface{}, condiBean ...interface{}) (int64, error) { + defer session.resetStatement() + if session.IsAutoClose { + defer session.Close() + } + + v := rValue(bean) + t := v.Type() + + var colNames []string + var args []interface{} + + // handle before update processors + for _, closure := range session.beforeClosures { + closure(bean) + } + cleanupProcessorsClosures(&session.beforeClosures) // cleanup after used + if processor, ok := interface{}(bean).(BeforeUpdateProcessor); ok { + processor.BeforeUpdate() + } + // -- + + var err error + var isMap = t.Kind() == reflect.Map + var isStruct = t.Kind() == reflect.Struct + if isStruct { + session.Statement.setRefValue(v) + + if len(session.Statement.TableName()) <= 0 { + return 0, ErrTableNotFound + } + + if session.Statement.ColumnStr == "" { + colNames, args = buildUpdates(session.Engine, session.Statement.RefTable, bean, false, false, + false, false, session.Statement.allUseBool, session.Statement.useAllCols, + session.Statement.mustColumnMap, session.Statement.nullableMap, + session.Statement.columnMap, true, session.Statement.unscoped) + } else { + colNames, args, err = genCols(session.Statement.RefTable, session, bean, true, true) + if err != nil { + return 0, err + } + } + } else if isMap { + colNames = make([]string, 0) + args = make([]interface{}, 0) + bValue := reflect.Indirect(reflect.ValueOf(bean)) + + for _, v := range bValue.MapKeys() { + colNames = append(colNames, session.Engine.Quote(v.String())+" = ?") + args = append(args, bValue.MapIndex(v).Interface()) + } + } else { + return 0, ErrParamsType + } + + table := session.Statement.RefTable + + if session.Statement.UseAutoTime && table != nil && table.Updated != "" { + colNames = append(colNames, session.Engine.Quote(table.Updated)+" = ?") + col := table.UpdatedColumn() + val, t := session.Engine.NowTime2(col.SQLType.Name) + args = append(args, val) + + var colName = col.Name + if isStruct { + session.afterClosures = append(session.afterClosures, func(bean interface{}) { + col := table.GetColumn(colName) + setColumnTime(bean, col, t) + }) + } + } + + //for update action to like "column = column + ?" + incColumns := session.Statement.getInc() + for _, v := range incColumns { + colNames = append(colNames, session.Engine.Quote(v.colName)+" = "+session.Engine.Quote(v.colName)+" + ?") + args = append(args, v.arg) + } + //for update action to like "column = column - ?" + decColumns := session.Statement.getDec() + for _, v := range decColumns { + colNames = append(colNames, session.Engine.Quote(v.colName)+" = "+session.Engine.Quote(v.colName)+" - ?") + args = append(args, v.arg) + } + //for update action to like "column = expression" + exprColumns := session.Statement.getExpr() + for _, v := range exprColumns { + colNames = append(colNames, session.Engine.Quote(v.colName)+" = "+v.expr) + } + + session.Statement.processIDParam() + + var autoCond builder.Cond + if !session.Statement.noAutoCondition && len(condiBean) > 0 { + var err error + autoCond, err = session.Statement.buildConds(session.Statement.RefTable, condiBean[0], true, true, false, true, false) + if err != nil { + return 0, err + } + } + + st := session.Statement + defer session.resetStatement() + + var sqlStr string + var condArgs []interface{} + var condSQL string + cond := session.Statement.cond.And(autoCond) + + doIncVer := false + var verValue *reflect.Value + if table != nil && table.Version != "" && session.Statement.checkVersion { + verValue, err = table.VersionColumn().ValueOf(bean) + if err != nil { + return 0, err + } + + cond = cond.And(builder.Eq{session.Engine.Quote(table.Version): verValue.Interface()}) + condSQL, condArgs, _ = builder.ToSQL(cond) + + if len(condSQL) > 0 { + condSQL = "WHERE " + condSQL + } + + if st.LimitN > 0 { + condSQL = condSQL + fmt.Sprintf(" LIMIT %d", st.LimitN) + } + + sqlStr = fmt.Sprintf("UPDATE %v SET %v, %v %v", + session.Engine.Quote(session.Statement.TableName()), + strings.Join(colNames, ", "), + session.Engine.Quote(table.Version)+" = "+session.Engine.Quote(table.Version)+" + 1", + condSQL) + + doIncVer = true + } else { + condSQL, condArgs, _ = builder.ToSQL(cond) + if len(condSQL) > 0 { + condSQL = "WHERE " + condSQL + } + + if st.LimitN > 0 { + condSQL = condSQL + fmt.Sprintf(" LIMIT %d", st.LimitN) + } + + sqlStr = fmt.Sprintf("UPDATE %v SET %v %v", + session.Engine.Quote(session.Statement.TableName()), + strings.Join(colNames, ", "), + condSQL) + } + + res, err := session.exec(sqlStr, append(args, condArgs...)...) + if err != nil { + return 0, err + } else if doIncVer { + if verValue != nil && verValue.IsValid() && verValue.CanSet() { + verValue.SetInt(verValue.Int() + 1) + } + } + + if table != nil { + if cacher := session.Engine.getCacher2(table); cacher != nil && session.Statement.UseCache { + cacher.ClearIds(session.Statement.TableName()) + cacher.ClearBeans(session.Statement.TableName()) + } + } + + // handle after update processors + if session.IsAutoCommit { + for _, closure := range session.afterClosures { + closure(bean) + } + if processor, ok := interface{}(bean).(AfterUpdateProcessor); ok { + session.Engine.logger.Debug("[event]", session.Statement.TableName(), " has after update processor") + processor.AfterUpdate() + } + } else { + lenAfterClosures := len(session.afterClosures) + if lenAfterClosures > 0 { + if value, has := session.afterUpdateBeans[bean]; has && value != nil { + *value = append(*value, session.afterClosures...) + } else { + afterClosures := make([]func(interface{}), lenAfterClosures) + copy(afterClosures, session.afterClosures) + // FIXME: if bean is a map type, it will panic because map cannot be as map key + session.afterUpdateBeans[bean] = &afterClosures + } + + } else { + if _, ok := interface{}(bean).(AfterInsertProcessor); ok { + session.afterUpdateBeans[bean] = nil + } + } + } + cleanupProcessorsClosures(&session.afterClosures) // cleanup after used + // -- + + return res.RowsAffected() +} diff --git a/sqlite3_dialect.go b/sqlite3_dialect.go index 20c75a4a..7ad153a3 100644 --- a/sqlite3_dialect.go +++ b/sqlite3_dialect.go @@ -237,9 +237,10 @@ func (db *sqlite3) TableCheckSql(tableName string) (string, []interface{}) { } func (db *sqlite3) DropIndexSql(tableName string, index *core.Index) string { - quote := db.Quote //var unique string - var idxName string = index.Name + quote := db.Quote + idxName := index.Name + if !strings.HasPrefix(idxName, "UQE_") && !strings.HasPrefix(idxName, "IDX_") { if index.Type == core.UniqueType { @@ -319,7 +320,7 @@ func (db *sqlite3) GetColumns(tableName string) ([]string, map[string]*core.Colu col.Name = strings.Trim(field, "`[] ") continue } else if idx == 1 { - col.SQLType = core.SQLType{field, 0, 0} + col.SQLType = core.SQLType{Name: field, DefaultLength: 0, DefaultLength2: 0} } switch field { case "PRIMARY": @@ -385,16 +386,16 @@ func (db *sqlite3) GetIndexes(tableName string) (map[string]*core.Index, error) indexes := make(map[string]*core.Index, 0) for rows.Next() { - var tmpSql sql.NullString - err = rows.Scan(&tmpSql) + var tmpSQL sql.NullString + err = rows.Scan(&tmpSQL) if err != nil { return nil, err } - if !tmpSql.Valid { + if !tmpSQL.Valid { continue } - sql := tmpSql.String + sql := tmpSQL.String index := new(core.Index) nNStart := strings.Index(sql, "INDEX") @@ -405,7 +406,7 @@ func (db *sqlite3) GetIndexes(tableName string) (map[string]*core.Index, error) indexName := strings.Trim(sql[nNStart+6:nNEnd], "` []") if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) { - index.Name = indexName[5+len(tableName) : len(indexName)] + index.Name = indexName[5+len(tableName):] } else { index.Name = indexName } diff --git a/statement.go b/statement.go index b8b0c1b4..fb116b94 100644 --- a/statement.go +++ b/statement.go @@ -14,14 +14,10 @@ import ( "strings" "time" + "github.com/go-xorm/builder" "github.com/go-xorm/core" ) -type inParam struct { - colName string - args []interface{} -} - type incrParam struct { colName string arg interface{} @@ -43,9 +39,7 @@ type Statement struct { Engine *Engine Start int LimitN int - WhereStr string IdParam *core.PK - Params []interface{} OrderStr string JoinStr string joinArgs []interface{} @@ -56,7 +50,6 @@ type Statement struct { columnMap map[string]bool useAllCols bool OmitStr string - ConditionStr string AltTableName string tableName string RawSQL string @@ -65,7 +58,6 @@ type Statement struct { UseAutoJoin bool StoreEngine string Charset string - BeanArgs []interface{} UseCache bool UseAutoTime bool noAutoCondition bool @@ -77,19 +69,17 @@ type Statement struct { unscoped bool mustColumnMap map[string]bool nullableMap map[string]bool - inColumns map[string]*inParam incrColumns map[string]incrParam decrColumns map[string]decrParam exprColumns map[string]exprParam + cond builder.Cond } -// Init reset all the statment's fields +// Init reset all the statement's fields func (statement *Statement) Init() { statement.RefTable = nil statement.Start = 0 statement.LimitN = 0 - statement.WhereStr = "" - statement.Params = make([]interface{}, 0) statement.OrderStr = "" statement.UseCascade = true statement.JoinStr = "" @@ -99,13 +89,11 @@ func (statement *Statement) Init() { statement.ColumnStr = "" statement.OmitStr = "" statement.columnMap = make(map[string]bool) - statement.ConditionStr = "" statement.AltTableName = "" statement.tableName = "" statement.IdParam = nil statement.RawSQL = "" statement.RawParams = make([]interface{}, 0) - statement.BeanArgs = make([]interface{}, 0) statement.UseCache = true statement.UseAutoTime = true statement.noAutoCondition = false @@ -119,10 +107,10 @@ func (statement *Statement) Init() { statement.nullableMap = make(map[string]bool) statement.checkVersion = true statement.unscoped = false - statement.inColumns = make(map[string]*inParam) statement.incrColumns = make(map[string]incrParam) statement.decrColumns = make(map[string]decrParam) statement.exprColumns = make(map[string]exprParam) + statement.cond = builder.NewCond() } // NoAutoCondition if you do not want convert bean's field as query condition, then use this function @@ -134,64 +122,101 @@ func (statement *Statement) NoAutoCondition(no ...bool) *Statement { return statement } -// Sql add the raw sql statement -func (statement *Statement) Sql(querystring string, args ...interface{}) *Statement { - statement.RawSQL = querystring - statement.RawParams = args - return statement -} - // Alias set the table alias func (statement *Statement) Alias(alias string) *Statement { statement.TableAlias = alias return statement } -// Where add Where statment -func (statement *Statement) Where(querystring string, args ...interface{}) *Statement { - // The second where will be triggered as And - if len(statement.WhereStr) > 0 { - return statement.And(querystring, args...) +// SQL adds raw sql statement +func (statement *Statement) SQL(query interface{}, args ...interface{}) *Statement { + switch query.(type) { + case (*builder.Builder): + var err error + statement.RawSQL, statement.RawParams, err = query.(*builder.Builder).ToSQL() + if err != nil { + statement.Engine.logger.Error(err) + } + case string: + statement.RawSQL = query.(string) + statement.RawParams = args + default: + statement.Engine.logger.Error("unsupported sql type") } - if !strings.Contains(querystring, statement.Engine.dialect.EqStr()) { - querystring = strings.Replace(querystring, "=", statement.Engine.dialect.EqStr(), -1) - } - statement.WhereStr = querystring - statement.Params = args return statement } -// And add Where & and statment -func (statement *Statement) And(querystring string, args ...interface{}) *Statement { - if len(statement.WhereStr) > 0 { - var buf bytes.Buffer - fmt.Fprintf(&buf, "(%v) %s (%v)", statement.WhereStr, - statement.Engine.dialect.AndStr(), querystring) - statement.WhereStr = buf.String() - } else { - statement.WhereStr = querystring +// Where add Where statement +func (statement *Statement) Where(query interface{}, args ...interface{}) *Statement { + return statement.And(query, args...) +} + +// And add Where & and statement +func (statement *Statement) And(query interface{}, args ...interface{}) *Statement { + switch query.(type) { + case string: + cond := builder.Expr(query.(string), args...) + statement.cond = statement.cond.And(cond) + case builder.Cond: + cond := query.(builder.Cond) + statement.cond = statement.cond.And(cond) + for _, v := range args { + if vv, ok := v.(builder.Cond); ok { + statement.cond = statement.cond.And(vv) + } + } + default: + // TODO: not support condition type } - statement.Params = append(statement.Params, args...) + return statement } -// Or add Where & Or statment -func (statement *Statement) Or(querystring string, args ...interface{}) *Statement { - if len(statement.WhereStr) > 0 { - var buf bytes.Buffer - fmt.Fprintf(&buf, "(%v) %s (%v)", statement.WhereStr, - statement.Engine.dialect.OrStr(), querystring) - statement.WhereStr = buf.String() - } else { - statement.WhereStr = querystring +// Or add Where & Or statement +func (statement *Statement) Or(query interface{}, args ...interface{}) *Statement { + switch query.(type) { + case string: + cond := builder.Expr(query.(string), args...) + statement.cond = statement.cond.Or(cond) + case builder.Cond: + cond := query.(builder.Cond) + statement.cond = statement.cond.Or(cond) + for _, v := range args { + if vv, ok := v.(builder.Cond); ok { + statement.cond = statement.cond.Or(vv) + } + } + default: + // TODO: not support condition type } - statement.Params = append(statement.Params, args...) + return statement +} + +// In generate "Where column IN (?) " statement +func (statement *Statement) In(column string, args ...interface{}) *Statement { + if len(args) == 0 { + return statement + } + + in := builder.In(column, args...) + statement.cond = statement.cond.And(in) + return statement +} + +// NotIn generate "Where column NOT IN (?) " statement +func (statement *Statement) NotIn(column string, args ...interface{}) *Statement { + if len(args) == 0 { + return statement + } + + in := builder.NotIn(column, args...) + statement.cond = statement.cond.And(in) return statement } func (statement *Statement) setRefValue(v reflect.Value) { - statement.RefTable = statement.Engine.autoMapType(v) + statement.RefTable = statement.Engine.autoMapType(reflect.Indirect(v)) statement.tableName = statement.Engine.tbName(v) } @@ -233,7 +258,7 @@ func buildUpdates(engine *Engine, table *core.Table, bean interface{}, if col.IsDeleted && !unscoped { continue } - if use, ok := columnMap[col.Name]; ok && !use { + if use, ok := columnMap[strings.ToLower(col.Name)]; ok && !use { continue } @@ -248,9 +273,8 @@ func buildUpdates(engine *Engine, table *core.Table, bean interface{}, requiredField := useAllCols includeNil := useAllCols - lColName := strings.ToLower(col.Name) - if b, ok := mustColumnMap[lColName]; ok { + if b, ok := getFlagForColumn(mustColumnMap, col); ok { if b { requiredField = true } else { @@ -259,7 +283,7 @@ func buildUpdates(engine *Engine, table *core.Table, bean interface{}, } // !evalphobia! set fieldValue as nil when column is nullable and zero-value - if b, ok := nullableMap[lColName]; ok { + if b, ok := getFlagForColumn(nullableMap, col); ok { if b && col.Nullable && isZero(fieldValue.Interface()) { var nilValue *int fieldValue = reflect.ValueOf(nilValue) @@ -432,7 +456,6 @@ func buildUpdates(engine *Engine, table *core.Table, bean interface{}, } APPEND: - //fmt.Println("==", col.Name, "==", fmt.Sprintf("%v", val)) args = append(args, val) if col.IsPrimaryKey && engine.dialect.DBType() == "ql" { continue @@ -458,13 +481,11 @@ func (statement *Statement) colName(col *core.Column, tableName string) string { return statement.Engine.Quote(col.Name) } -// Auto generating conditions according a struct -func buildConditions(engine *Engine, table *core.Table, bean interface{}, +func buildConds(engine *Engine, table *core.Table, bean interface{}, includeVersion bool, includeUpdated bool, includeNil bool, includeAutoIncr bool, allUseBool bool, useAllCols bool, unscoped bool, - mustColumnMap map[string]bool, tableName, aliasName string, addedTableName bool) ([]string, []interface{}) { - var colNames []string - var args = make([]interface{}, 0) + mustColumnMap map[string]bool, tableName, aliasName string, addedTableName bool) (builder.Cond, error) { + var conds []builder.Cond for _, col := range table.Columns() { if !includeVersion && col.IsVersion { continue @@ -476,7 +497,7 @@ func buildConditions(engine *Engine, table *core.Table, bean interface{}, continue } - if engine.dialect.DBType() == core.MSSQL && col.SQLType.Name == core.Text { + if engine.dialect.DBType() == core.MSSQL && (col.SQLType.Name == core.Text || col.SQLType.IsBlob() || col.SQLType.Name == core.TimeStampz) { continue } if col.SQLType.IsJson() { @@ -501,8 +522,11 @@ func buildConditions(engine *Engine, table *core.Table, bean interface{}, } if col.IsDeleted && !unscoped { // tag "deleted" is enabled - colNames = append(colNames, fmt.Sprintf("(%v IS NULL OR %v = '0001-01-01 00:00:00')", - colName, colName)) + if engine.dialect.DBType() == core.MSSQL { + conds = append(conds, builder.IsNull{colName}) + } else { + conds = append(conds, builder.IsNull{colName}.Or(builder.Eq{colName: "0001-01-01 00:00:00"})) + } } fieldValue := *fieldValuePtr @@ -512,7 +536,8 @@ func buildConditions(engine *Engine, table *core.Table, bean interface{}, fieldType := reflect.TypeOf(fieldValue.Interface()) requiredField := useAllCols - if b, ok := mustColumnMap[strings.ToLower(col.Name)]; ok { + + if b, ok := getFlagForColumn(mustColumnMap, col); ok { if b { requiredField = true } else { @@ -523,8 +548,7 @@ func buildConditions(engine *Engine, table *core.Table, bean interface{}, if fieldType.Kind() == reflect.Ptr { if fieldValue.IsNil() { if includeNil { - args = append(args, nil) - colNames = append(colNames, fmt.Sprintf("%v %s ?", colName, engine.dialect.EqStr())) + conds = append(conds, builder.Eq{colName: nil}) } continue } else if !fieldValue.IsValid() { @@ -667,17 +691,10 @@ func buildConditions(engine *Engine, table *core.Table, bean interface{}, val = fieldValue.Interface() } - args = append(args, val) - var condi string - if col.IsPrimaryKey && engine.dialect.DBType() == "ql" { - condi = "id() == ?" - } else { - condi = fmt.Sprintf("%v %s ?", colName, engine.dialect.EqStr()) - } - colNames = append(colNames, condi) + conds = append(conds, builder.Eq{colName: val}) } - return colNames, args + return builder.And(conds...), nil } // TableName return current tableName @@ -689,8 +706,15 @@ func (statement *Statement) TableName() string { return statement.tableName } -// Id generate "where id = ? " statment or for composite key "where key1 = ? and key2 = ?" +// Id generate "where id = ? " statement or for composite key "where key1 = ? and key2 = ?" +// +// Deprecated: use ID instead func (statement *Statement) Id(id interface{}) *Statement { + return statement.ID(id) +} + +// ID generate "where id = ? " statement or for composite key "where key1 = ? and key2 = ?" +func (statement *Statement) ID(id interface{}) *Statement { idValue := reflect.ValueOf(id) idType := reflect.TypeOf(idValue.Interface()) @@ -717,7 +741,7 @@ func (statement *Statement) Id(id interface{}) *Statement { return statement } -// Incr Generate "Update ... Set column = column + arg" statment +// Incr Generate "Update ... Set column = column + arg" statement func (statement *Statement) Incr(column string, arg ...interface{}) *Statement { k := strings.ToLower(column) if len(arg) > 0 { @@ -728,7 +752,7 @@ func (statement *Statement) Incr(column string, arg ...interface{}) *Statement { return statement } -// Decr Generate "Update ... Set column = column - arg" statment +// Decr Generate "Update ... Set column = column - arg" statement func (statement *Statement) Decr(column string, arg ...interface{}) *Statement { k := strings.ToLower(column) if len(arg) > 0 { @@ -739,92 +763,28 @@ func (statement *Statement) Decr(column string, arg ...interface{}) *Statement { return statement } -// SetExpr Generate "Update ... Set column = {expression}" statment +// SetExpr Generate "Update ... Set column = {expression}" statement func (statement *Statement) SetExpr(column string, expression string) *Statement { k := strings.ToLower(column) statement.exprColumns[k] = exprParam{column, expression} return statement } -// Generate "Update ... Set column = column + arg" statment +// Generate "Update ... Set column = column + arg" statement func (statement *Statement) getInc() map[string]incrParam { return statement.incrColumns } -// Generate "Update ... Set column = column - arg" statment +// Generate "Update ... Set column = column - arg" statement func (statement *Statement) getDec() map[string]decrParam { return statement.decrColumns } -// Generate "Update ... Set column = {expression}" statment +// Generate "Update ... Set column = {expression}" statement func (statement *Statement) getExpr() map[string]exprParam { return statement.exprColumns } -// In generate "Where column IN (?) " statment -func (statement *Statement) In(column string, args ...interface{}) *Statement { - length := len(args) - if length == 0 { - return statement - } - - k := strings.ToLower(column) - var newargs []interface{} - if length == 1 && - reflect.TypeOf(args[0]).Kind() == reflect.Slice { - newargs = make([]interface{}, 0) - v := reflect.ValueOf(args[0]) - for i := 0; i < v.Len(); i++ { - newargs = append(newargs, v.Index(i).Interface()) - } - } else { - newargs = args - } - - if _, ok := statement.inColumns[k]; ok { - statement.inColumns[k].args = append(statement.inColumns[k].args, newargs...) - } else { - statement.inColumns[k] = &inParam{column, newargs} - } - return statement -} - -func (statement *Statement) genInSql() (string, []interface{}) { - if len(statement.inColumns) == 0 { - return "", []interface{}{} - } - - inStrs := make([]string, len(statement.inColumns), len(statement.inColumns)) - args := make([]interface{}, 0, len(statement.inColumns)) - var buf bytes.Buffer - var i int - for _, params := range statement.inColumns { - buf.Reset() - fmt.Fprintf(&buf, "(%v IN (%v))", - statement.Engine.quoteColumn(params.colName), - strings.Join(makeArray("?", len(params.args)), ",")) - inStrs[i] = buf.String() - i++ - args = append(args, params.args...) - } - - if len(statement.inColumns) == 1 { - return inStrs[0], args - } - return fmt.Sprintf("(%v)", strings.Join(inStrs, " "+statement.Engine.dialect.AndStr()+" ")), args -} - -func (statement *Statement) attachInSql() { - inSql, inArgs := statement.genInSql() - if len(inSql) > 0 { - if len(statement.ConditionStr) > 0 { - statement.ConditionStr += " " + statement.Engine.dialect.AndStr() + " " - } - statement.ConditionStr += inSql - statement.Params = append(statement.Params, inArgs...) - } -} - func (statement *Statement) col2NewColsWithQuote(columns ...string) []string { newColumns := make([]string, 0) for _, col := range columns { @@ -846,23 +806,23 @@ func (statement *Statement) col2NewColsWithQuote(columns ...string) []string { return newColumns } -// Generate "Distince col1, col2 " statment +// Distinct generates "DISTINCT col1, col2 " statement func (statement *Statement) Distinct(columns ...string) *Statement { statement.IsDistinct = true statement.Cols(columns...) return statement } -// Generate "SELECT ... FOR UPDATE" statment +// ForUpdate generates "SELECT ... FOR UPDATE" statement func (statement *Statement) ForUpdate() *Statement { statement.IsForUpdate = true return statement } // Select replace select -func (s *Statement) Select(str string) *Statement { - s.selectStr = str - return s +func (statement *Statement) Select(str string) *Statement { + statement.selectStr = str + return statement } // Cols generate "col1, col2" statement @@ -873,7 +833,6 @@ func (statement *Statement) Cols(columns ...string) *Statement { } newColumns := statement.col2NewColsWithQuote(columns...) - //fmt.Println("=====", columns, newColumns, cols) statement.ColumnStr = strings.Join(newColumns, ", ") statement.ColumnStr = strings.Replace(statement.ColumnStr, statement.Engine.quote("*"), "*", -1) return statement @@ -1037,41 +996,46 @@ func (statement *Statement) Unscoped() *Statement { } func (statement *Statement) genColumnStr() string { - table := statement.RefTable - var colNames []string - for _, col := range table.Columns() { + var buf bytes.Buffer + if statement.RefTable == nil { + return "" + } + + columns := statement.RefTable.Columns() + + for _, col := range columns { if statement.OmitStr != "" { - if _, ok := statement.columnMap[strings.ToLower(col.Name)]; ok { + if _, ok := getFlagForColumn(statement.columnMap, col); ok { continue } } + if col.MapType == core.ONLYTODB { continue } - if statement.JoinStr != "" { - var name string - if statement.TableAlias != "" { - name = statement.Engine.Quote(statement.TableAlias) - } else { - name = statement.Engine.Quote(statement.TableName()) - } - name += "." + statement.Engine.Quote(col.Name) - if col.IsPrimaryKey && statement.Engine.Dialect().DBType() == "ql" { - colNames = append(colNames, "id() AS "+name) - } else { - colNames = append(colNames, name) - } - } else { - name := statement.Engine.Quote(col.Name) - if col.IsPrimaryKey && statement.Engine.Dialect().DBType() == "ql" { - colNames = append(colNames, "id() AS "+name) - } else { - colNames = append(colNames, name) - } + if buf.Len() != 0 { + buf.WriteString(", ") } + + if col.IsPrimaryKey && statement.Engine.Dialect().DBType() == "ql" { + buf.WriteString("id() AS ") + } + + if statement.JoinStr != "" { + if statement.TableAlias != "" { + buf.WriteString(statement.TableAlias) + } else { + buf.WriteString(statement.TableName()) + } + + buf.WriteString(".") + } + + statement.Engine.QuoteTo(&buf, col.Name) } - return strings.Join(colNames, ", ") + + return buf.String() } func (statement *Statement) genCreateTableSQL() string { @@ -1079,11 +1043,11 @@ func (statement *Statement) genCreateTableSQL() string { statement.StoreEngine, statement.Charset) } -func (s *Statement) genIndexSQL() []string { +func (statement *Statement) genIndexSQL() []string { var sqls []string - tbName := s.TableName() - quote := s.Engine.Quote - for idxName, index := range s.RefTable.Indexes { + tbName := statement.TableName() + quote := statement.Engine.Quote + for idxName, index := range statement.RefTable.Indexes { if index.Type == core.IndexType { sql := fmt.Sprintf("CREATE INDEX %v ON %v (%v);", quote(indexName(tbName, idxName)), quote(tbName), quote(strings.Join(index.Cols, quote(",")))) @@ -1097,50 +1061,67 @@ func uniqueName(tableName, uqeName string) string { return fmt.Sprintf("UQE_%v_%v", tableName, uqeName) } -func (s *Statement) genUniqueSQL() []string { +func (statement *Statement) genUniqueSQL() []string { var sqls []string - tbName := s.TableName() - for _, index := range s.RefTable.Indexes { + tbName := statement.TableName() + for _, index := range statement.RefTable.Indexes { if index.Type == core.UniqueType { - sql := s.Engine.dialect.CreateIndexSql(tbName, index) + sql := statement.Engine.dialect.CreateIndexSql(tbName, index) sqls = append(sqls, sql) } } return sqls } -func (s *Statement) genDelIndexSQL() []string { +func (statement *Statement) genDelIndexSQL() []string { var sqls []string - tbName := s.TableName() - for idxName, index := range s.RefTable.Indexes { + tbName := statement.TableName() + for idxName, index := range statement.RefTable.Indexes { var rIdxName string if index.Type == core.UniqueType { rIdxName = uniqueName(tbName, idxName) } else if index.Type == core.IndexType { rIdxName = indexName(tbName, idxName) } - sql := fmt.Sprintf("DROP INDEX %v", s.Engine.Quote(rIdxName)) - if s.Engine.dialect.IndexOnTable() { - sql += fmt.Sprintf(" ON %v", s.Engine.Quote(s.TableName())) + sql := fmt.Sprintf("DROP INDEX %v", statement.Engine.Quote(rIdxName)) + if statement.Engine.dialect.IndexOnTable() { + sql += fmt.Sprintf(" ON %v", statement.Engine.Quote(statement.TableName())) } sqls = append(sqls, sql) } return sqls } -func (statement *Statement) genGetSql(bean interface{}) (string, []interface{}) { - statement.setRefValue(rValue(bean)) +func (statement *Statement) genAddColumnStr(col *core.Column) (string, []interface{}) { + quote := statement.Engine.Quote + sql := fmt.Sprintf("ALTER TABLE %v ADD %v;", quote(statement.TableName()), + col.String(statement.Engine.dialect)) + return sql, []interface{}{} +} - var table = statement.RefTable - var addedTableName = (len(statement.JoinStr) > 0) +func (statement *Statement) buildConds(table *core.Table, bean interface{}, includeVersion bool, includeUpdated bool, includeNil bool, includeAutoIncr bool, addedTableName bool) (builder.Cond, error) { + return buildConds(statement.Engine, table, bean, includeVersion, includeUpdated, includeNil, includeAutoIncr, statement.allUseBool, statement.useAllCols, + statement.unscoped, statement.mustColumnMap, statement.TableName(), statement.TableAlias, addedTableName) +} +func (statement *Statement) genConds(bean interface{}) (string, []interface{}, error) { if !statement.noAutoCondition { - colNames, args := statement.buildConditions(table, bean, true, true, false, true, addedTableName) - - statement.ConditionStr = strings.Join(colNames, " "+statement.Engine.dialect.AndStr()+" ") - statement.BeanArgs = args + var addedTableName = (len(statement.JoinStr) > 0) + autoCond, err := statement.buildConds(statement.RefTable, bean, true, true, false, true, addedTableName) + if err != nil { + return "", nil, err + } + statement.cond = statement.cond.And(autoCond) } + statement.processIDParam() + + return builder.ToSQL(statement.cond) +} + +func (statement *Statement) genGetSQL(bean interface{}) (string, []interface{}) { + statement.setRefValue(rValue(bean)) + var columnStr = statement.ColumnStr if len(statement.selectStr) > 0 { columnStr = statement.selectStr @@ -1165,78 +1146,41 @@ func (statement *Statement) genGetSql(bean interface{}) (string, []interface{}) } } - statement.attachInSql() // !admpub! fix bug:Iterate func missing "... IN (...)" - return statement.genSelectSQL(columnStr), append(append(statement.joinArgs, statement.Params...), statement.BeanArgs...) + condSQL, condArgs, _ := statement.genConds(bean) + + return statement.genSelectSQL(columnStr, condSQL), append(statement.joinArgs, condArgs...) } -func (s *Statement) genAddColumnStr(col *core.Column) (string, []interface{}) { - quote := s.Engine.Quote - sql := fmt.Sprintf("ALTER TABLE %v ADD %v;", quote(s.TableName()), - col.String(s.Engine.dialect)) - return sql, []interface{}{} -} - -/*func (s *Statement) genAddIndexStr(idxName string, cols []string) (string, []interface{}) { - quote := s.Engine.Quote - colstr := quote(strings.Join(cols, quote(", "))) - sql := fmt.Sprintf("CREATE INDEX %v ON %v (%v);", quote(idxName), quote(s.TableName()), colstr) - return sql, []interface{}{} -} - -func (s *Statement) genAddUniqueStr(uqeName string, cols []string) (string, []interface{}) { - quote := s.Engine.Quote - colstr := quote(strings.Join(cols, quote(", "))) - sql := fmt.Sprintf("CREATE UNIQUE INDEX %v ON %v (%v);", quote(uqeName), quote(s.TableName()), colstr) - return sql, []interface{}{} -}*/ - -func (statement *Statement) buildConditions(table *core.Table, bean interface{}, includeVersion bool, includeUpdated bool, includeNil bool, includeAutoIncr bool, addedTableName bool) ([]string, []interface{}) { - return buildConditions(statement.Engine, table, bean, includeVersion, includeUpdated, includeNil, includeAutoIncr, statement.allUseBool, statement.useAllCols, - statement.unscoped, statement.mustColumnMap, statement.TableName(), statement.TableAlias, addedTableName) -} - -func (statement *Statement) genCountSql(bean interface{}) (string, []interface{}) { +func (statement *Statement) genCountSQL(bean interface{}) (string, []interface{}) { statement.setRefValue(rValue(bean)) - var addedTableName = (len(statement.JoinStr) > 0) + condSQL, condArgs, _ := statement.genConds(bean) - if !statement.noAutoCondition { - colNames, args := statement.buildConditions(statement.RefTable, bean, true, true, false, true, addedTableName) - - statement.ConditionStr = strings.Join(colNames, " "+statement.Engine.Dialect().AndStr()+" ") - statement.BeanArgs = args + var selectSQL = statement.selectStr + if len(selectSQL) <= 0 { + if statement.IsDistinct { + selectSQL = fmt.Sprintf("count(DISTINCT %s)", statement.ColumnStr) + } else { + selectSQL = "count(*)" + } } - - // count(index fieldname) > count(0) > count(*) - var id = "*" - if statement.Engine.Dialect().DBType() == "ql" { - id = "" - } - statement.attachInSql() - return statement.genSelectSQL(fmt.Sprintf("count(%v)", id)), append(append(statement.joinArgs, statement.Params...), statement.BeanArgs...) + return statement.genSelectSQL(selectSQL, condSQL), append(statement.joinArgs, condArgs...) } -func (statement *Statement) genSumSql(bean interface{}, columns ...string) (string, []interface{}) { +func (statement *Statement) genSumSQL(bean interface{}, columns ...string) (string, []interface{}) { statement.setRefValue(rValue(bean)) - var addedTableName = (len(statement.JoinStr) > 0) - - if !statement.noAutoCondition { - colNames, args := statement.buildConditions(statement.RefTable, bean, true, true, false, true, addedTableName) - - statement.ConditionStr = strings.Join(colNames, " "+statement.Engine.Dialect().AndStr()+" ") - statement.BeanArgs = args - } - - statement.attachInSql() var sumStrs = make([]string, 0, len(columns)) for _, colName := range columns { - sumStrs = append(sumStrs, fmt.Sprintf("sum(%s)", colName)) + sumStrs = append(sumStrs, fmt.Sprintf("COALESCE(sum(%s),0)", colName)) } - return statement.genSelectSQL(strings.Join(sumStrs, ", ")), append(append(statement.joinArgs, statement.Params...), statement.BeanArgs...) + + condSQL, condArgs, _ := statement.genConds(bean) + + return statement.genSelectSQL(strings.Join(sumStrs, ", "), condSQL), append(statement.joinArgs, condArgs...) } -func (statement *Statement) genSelectSQL(columnStr string) (a string) { +func (statement *Statement) genSelectSQL(columnStr, condSQL string) (a string) { var distinct string if statement.IsDistinct { distinct = "DISTINCT " @@ -1247,20 +1191,11 @@ func (statement *Statement) genSelectSQL(columnStr string) (a string) { var top string var mssqlCondi string - statement.processIdParam() + statement.processIDParam() var buf bytes.Buffer - if len(statement.WhereStr) > 0 { - if len(statement.ConditionStr) > 0 { - fmt.Fprintf(&buf, " WHERE (%v)", statement.WhereStr) - } else { - fmt.Fprintf(&buf, " WHERE %v", statement.WhereStr) - } - if statement.ConditionStr != "" { - fmt.Fprintf(&buf, " %s (%v)", dialect.AndStr(), statement.ConditionStr) - } - } else if len(statement.ConditionStr) > 0 { - fmt.Fprintf(&buf, " WHERE %v", statement.ConditionStr) + if len(condSQL) > 0 { + fmt.Fprintf(&buf, " WHERE %v", condSQL) } var whereStr = buf.String() @@ -1281,7 +1216,7 @@ func (statement *Statement) genSelectSQL(columnStr string) (a string) { top = fmt.Sprintf(" TOP %d ", statement.LimitN) } if statement.Start > 0 { - var column = "(id)" + var column string if len(statement.RefTable.PKColumns()) == 0 { for _, index := range statement.RefTable.Indexes { if len(index.Cols) == 1 { @@ -1292,7 +1227,17 @@ func (statement *Statement) genSelectSQL(columnStr string) (a string) { if len(column) == 0 { column = statement.RefTable.ColumnsSeq()[0] } + } else { + column = statement.RefTable.PKColumns()[0].Name } + if statement.needTableName() { + if len(statement.TableAlias) > 0 { + column = statement.TableAlias + "." + column + } else { + column = statement.TableName() + "." + column + } + } + var orderStr string if len(statement.OrderStr) > 0 { orderStr = " ORDER BY " + statement.OrderStr @@ -1343,28 +1288,22 @@ func (statement *Statement) genSelectSQL(columnStr string) (a string) { return } -func (statement *Statement) processIdParam() { - if statement.IdParam != nil { - if statement.Engine.dialect.DBType() != "ql" { - for i, col := range statement.RefTable.PKColumns() { - var colName = statement.colName(col, statement.TableName()) - if i < len(*(statement.IdParam)) { - statement.And(fmt.Sprintf("%v %s ?", colName, - statement.Engine.dialect.EqStr()), (*(statement.IdParam))[i]) - } else { - statement.And(fmt.Sprintf("%v %s ?", colName, - statement.Engine.dialect.EqStr()), "") - } - } +func (statement *Statement) processIDParam() { + if statement.IdParam == nil { + return + } + + for i, col := range statement.RefTable.PKColumns() { + var colName = statement.colName(col, statement.TableName()) + if i < len(*(statement.IdParam)) { + statement.cond = statement.cond.And(builder.Eq{colName: (*(statement.IdParam))[i]}) } else { - if len(*(statement.IdParam)) <= 1 { - statement.And("id() == ?", (*(statement.IdParam))[0]) - } + statement.cond = statement.cond.And(builder.Eq{colName: ""}) } } } -func (statement *Statement) JoinColumns(cols []*core.Column, includeTableName bool) string { +func (statement *Statement) joinColumns(cols []*core.Column, includeTableName bool) string { var colnames = make([]string, len(cols)) for i, col := range cols { if includeTableName { @@ -1377,22 +1316,25 @@ func (statement *Statement) JoinColumns(cols []*core.Column, includeTableName bo return strings.Join(colnames, ", ") } -func (statement *Statement) convertIdSql(sqlStr string) string { +func (statement *Statement) convertIDSQL(sqlStr string) string { if statement.RefTable != nil { cols := statement.RefTable.PKColumns() if len(cols) == 0 { return "" } - colstrs := statement.JoinColumns(cols, false) + colstrs := statement.joinColumns(cols, false) sqls := splitNNoCase(sqlStr, " from ", 2) if len(sqls) != 2 { return "" } - if statement.Engine.dialect.DBType() == "ql" { - return fmt.Sprintf("SELECT id() FROM %v", sqls[1]) + + var top string + if statement.LimitN > 0 && statement.Engine.dialect.DBType() == core.MSSQL { + top = fmt.Sprintf("TOP %d ", statement.LimitN) } - return fmt.Sprintf("SELECT %s FROM %v", colstrs, sqls[1]) + + return fmt.Sprintf("SELECT %s%s FROM %v", top, colstrs, sqls[1]) } return "" } @@ -1402,7 +1344,7 @@ func (statement *Statement) convertUpdateSQL(sqlStr string) (string, string) { return "", "" } - colstrs := statement.JoinColumns(statement.RefTable.PKColumns(), true) + colstrs := statement.joinColumns(statement.RefTable.PKColumns(), true) sqls := splitNNoCase(sqlStr, "where", 2) if len(sqls) != 2 { if len(sqls) == 1 { diff --git a/statement_test.go b/statement_test.go new file mode 100644 index 00000000..7e8d6c0c --- /dev/null +++ b/statement_test.go @@ -0,0 +1,205 @@ +package xorm + +import ( + "reflect" + "sync" + "testing" + "time" + + "strings" + + "github.com/go-xorm/core" +) + +var colStrTests = []struct { + omitColumn string + onlyToDBColumnNdx int + expected string +}{ + {"", -1, "`ID`, `IsDeleted`, `Caption`, `Code1`, `Code2`, `Code3`, `ParentID`, `Latitude`, `Longitude`"}, + {"Code2", -1, "`ID`, `IsDeleted`, `Caption`, `Code1`, `Code3`, `ParentID`, `Latitude`, `Longitude`"}, + {"", 1, "`ID`, `Caption`, `Code1`, `Code2`, `Code3`, `ParentID`, `Latitude`, `Longitude`"}, + {"Code3", 1, "`ID`, `Caption`, `Code1`, `Code2`, `ParentID`, `Latitude`, `Longitude`"}, + {"Longitude", 1, "`ID`, `Caption`, `Code1`, `Code2`, `Code3`, `ParentID`, `Latitude`"}, + {"", 8, "`ID`, `IsDeleted`, `Caption`, `Code1`, `Code2`, `Code3`, `ParentID`, `Latitude`"}, +} + +// !nemec784! Only for Statement object creation +const driverName = "mysql" +const dataSourceName = "Server=TestServer;Database=TestDB;Uid=testUser;Pwd=testPassword;" + +func init() { + core.RegisterDriver(driverName, &mysqlDriver{}) +} + +func TestColumnsStringGeneration(t *testing.T) { + + var statement *Statement + + for ndx, testCase := range colStrTests { + + statement = createTestStatement() + + if testCase.omitColumn != "" { + statement.Omit(testCase.omitColumn) // !nemec784! Column must be skipped + } + + if testCase.onlyToDBColumnNdx >= 0 { + columns := statement.RefTable.Columns() + columns[testCase.onlyToDBColumnNdx].MapType = core.ONLYTODB // !nemec784! Column must be skipped + } + + actual := statement.genColumnStr() + + if actual != testCase.expected { + t.Errorf("[test #%d] Unexpected columns string:\nwant:\t%s\nhave:\t%s", ndx, testCase.expected, actual) + } + } +} + +func BenchmarkColumnsStringGeneration(b *testing.B) { + + b.StopTimer() + + statement := createTestStatement() + + testCase := colStrTests[0] + + if testCase.omitColumn != "" { + statement.Omit(testCase.omitColumn) // !nemec784! Column must be skipped + } + + if testCase.onlyToDBColumnNdx >= 0 { + columns := statement.RefTable.Columns() + columns[testCase.onlyToDBColumnNdx].MapType = core.ONLYTODB // !nemec784! Column must be skipped + } + + b.StartTimer() + + for i := 0; i < b.N; i++ { + actual := statement.genColumnStr() + + if actual != testCase.expected { + b.Errorf("Unexpected columns string:\nwant:\t%s\nhave:\t%s", testCase.expected, actual) + } + } +} + +func BenchmarkGetFlagForColumnWithICKey_ContainsKey(b *testing.B) { + + b.StopTimer() + + mapCols := make(map[string]bool) + cols := []*core.Column{ + {Name: `ID`}, + {Name: `IsDeleted`}, + {Name: `Caption`}, + {Name: `Code1`}, + {Name: `Code2`}, + {Name: `Code3`}, + {Name: `ParentID`}, + {Name: `Latitude`}, + {Name: `Longitude`}, + } + + for _, col := range cols { + mapCols[strings.ToLower(col.Name)] = true + } + + b.StartTimer() + + for i := 0; i < b.N; i++ { + + for _, col := range cols { + + if _, ok := getFlagForColumn(mapCols, col); !ok { + b.Fatal("Unexpected result") + } + } + } +} + +func BenchmarkGetFlagForColumnWithICKey_EmptyMap(b *testing.B) { + + b.StopTimer() + + mapCols := make(map[string]bool) + cols := []*core.Column{ + {Name: `ID`}, + {Name: `IsDeleted`}, + {Name: `Caption`}, + {Name: `Code1`}, + {Name: `Code2`}, + {Name: `Code3`}, + {Name: `ParentID`}, + {Name: `Latitude`}, + {Name: `Longitude`}, + } + + b.StartTimer() + + for i := 0; i < b.N; i++ { + + for _, col := range cols { + + if _, ok := getFlagForColumn(mapCols, col); ok { + b.Fatal("Unexpected result") + } + } + } +} + +type TestType struct { + ID int64 `xorm:"ID PK"` + IsDeleted bool `xorm:"IsDeleted"` + Caption string `xorm:"Caption"` + Code1 string `xorm:"Code1"` + Code2 string `xorm:"Code2"` + Code3 string `xorm:"Code3"` + ParentID int64 `xorm:"ParentID"` + Latitude float64 `xorm:"Latitude"` + Longitude float64 `xorm:"Longitude"` +} + +func (TestType) TableName() string { + return "TestTable" +} + +func createTestStatement() *Statement { + + engine := createTestEngine() + + statement := &Statement{} + statement.Init() + statement.Engine = engine + statement.setRefValue(reflect.ValueOf(TestType{})) + + return statement +} + +func createTestEngine() *Engine { + driver := core.QueryDriver(driverName) + uri, err := driver.Parse(driverName, dataSourceName) + + if err != nil { + panic(err) + } + + dialect := &mysql{} + err = dialect.Init(nil, uri, driverName, dataSourceName) + + if err != nil { + panic(err) + } + + engine := &Engine{ + dialect: dialect, + Tables: make(map[reflect.Type]*core.Table), + mutex: &sync.RWMutex{}, + TagIdentifier: "xorm", + TZLocation: time.Local, + } + engine.SetMapper(core.NewCacheMapper(new(core.SnakeMapper))) + + return engine +} diff --git a/syslogger.go b/syslogger.go index 799bcca3..8840635d 100644 --- a/syslogger.go +++ b/syslogger.go @@ -21,42 +21,52 @@ type SyslogLogger struct { showSQL bool } +// NewSyslogLogger implements core.ILogger func NewSyslogLogger(w *syslog.Writer) *SyslogLogger { return &SyslogLogger{w: w} } +// Debug log content as Debug func (s *SyslogLogger) Debug(v ...interface{}) { s.w.Debug(fmt.Sprint(v...)) } +// Debugf log content as Debug and format func (s *SyslogLogger) Debugf(format string, v ...interface{}) { s.w.Debug(fmt.Sprintf(format, v...)) } +// Error log content as Error func (s *SyslogLogger) Error(v ...interface{}) { s.w.Err(fmt.Sprint(v...)) } +// Errorf log content as Errorf and format func (s *SyslogLogger) Errorf(format string, v ...interface{}) { s.w.Err(fmt.Sprintf(format, v...)) } +// Info log content as Info func (s *SyslogLogger) Info(v ...interface{}) { s.w.Info(fmt.Sprint(v...)) } +// Infof log content as Infof and format func (s *SyslogLogger) Infof(format string, v ...interface{}) { s.w.Info(fmt.Sprintf(format, v...)) } +// Warn log content as Warn func (s *SyslogLogger) Warn(v ...interface{}) { s.w.Warning(fmt.Sprint(v...)) } +// Warnf log content as Warnf and format func (s *SyslogLogger) Warnf(format string, v ...interface{}) { s.w.Warning(fmt.Sprintf(format, v...)) } +// Level shows log level func (s *SyslogLogger) Level() core.LogLevel { return core.LOG_UNKNOWN } @@ -64,6 +74,7 @@ func (s *SyslogLogger) Level() core.LogLevel { // SetLevel always return error, as current log/syslog package doesn't allow to set priority level after syslog.Writer created func (s *SyslogLogger) SetLevel(l core.LogLevel) {} +// ShowSQL set if logging SQL func (s *SyslogLogger) ShowSQL(show ...bool) { if len(show) == 0 { s.showSQL = true @@ -72,6 +83,7 @@ func (s *SyslogLogger) ShowSQL(show ...bool) { s.showSQL = show[0] } +// IsShowSQL if logging SQL func (s *SyslogLogger) IsShowSQL() bool { return s.showSQL } diff --git a/types.go b/types.go index 8bf85d7a..99d761c2 100644 --- a/types.go +++ b/types.go @@ -1,9 +1,9 @@ package xorm import ( - "reflect" + "reflect" - "github.com/go-xorm/core" + "github.com/go-xorm/core" ) var ( diff --git a/xorm.go b/xorm.go index fdd995bd..6414d8a2 100644 --- a/xorm.go +++ b/xorm.go @@ -17,7 +17,7 @@ import ( const ( // Version show the xorm's version - Version string = "0.5.5.0711" + Version string = "0.6.0.1022" ) func regDrvsNDialects() bool { @@ -31,6 +31,7 @@ func regDrvsNDialects() bool { "mysql": {"mysql", func() core.Driver { return &mysqlDriver{} }, func() core.Dialect { return &mysql{} }}, "mymysql": {"mysql", func() core.Driver { return &mymysqlDriver{} }, func() core.Dialect { return &mysql{} }}, "postgres": {"postgres", func() core.Driver { return &pqDriver{} }, func() core.Dialect { return &postgres{} }}, + "pgx": {"postgres", func() core.Driver { return &pqDriver{} }, func() core.Dialect { return &postgres{} }}, "sqlite3": {"sqlite3", func() core.Driver { return &sqlite3Driver{} }, func() core.Dialect { return &sqlite3{} }}, "oci8": {"oracle", func() core.Driver { return &oci8Driver{} }, func() core.Dialect { return &oracle{} }}, "goracle": {"oracle", func() core.Driver { return &goracleDriver{} }, func() core.Dialect { return &oracle{} }},