diff --git a/cmd/dump.go b/cmd/dump.go index 325ad4045..5c31ac66d 100644 --- a/cmd/dump.go +++ b/cmd/dump.go @@ -41,6 +41,10 @@ It can be used for backup and capture Gitea server image to send to maintainer`, Value: os.TempDir(), Usage: "Temporary dir path", }, + cli.StringFlag{ + Name: "database, d", + Usage: "Specify the database SQL syntax", + }, }, } @@ -71,8 +75,14 @@ func runDump(ctx *cli.Context) error { log.Fatalf("Fail to dump local repositories: %v", err) } - log.Printf("Dumping database...") - if err := models.DumpDatabase(dbDump); err != nil { + targetDBType := ctx.String("database") + if len(targetDBType) > 0 && targetDBType != models.DbCfg.Type { + log.Printf("Dumping database %s => %s...", models.DbCfg.Type, targetDBType) + } else { + log.Printf("Dumping database...") + } + + if err := models.DumpDatabase(dbDump, targetDBType); err != nil { log.Fatalf("Fail to dump database: %v", err) } diff --git a/models/models.go b/models/models.go index e937580d1..131fd4fdb 100644 --- a/models/models.go +++ b/models/models.go @@ -319,7 +319,14 @@ func Ping() error { return x.Ping() } -// DumpDatabase dumps all data from database to file system. -func DumpDatabase(filePath string) error { - return x.DumpAllToFile(filePath) +// DumpDatabase dumps all data from database according the special database SQL syntax to file system. +func DumpDatabase(filePath string, dbType string) error { + var tbs []*core.Table + for _, t := range tables { + tbs = append(tbs, x.TableInfo(t).Table) + } + if len(dbType) > 0 { + return x.DumpTablesToFile(tbs, filePath, core.DbType(dbType)) + } + return x.DumpTablesToFile(tbs, filePath) } diff --git a/vendor/github.com/go-xorm/builder/README.md b/vendor/github.com/go-xorm/builder/README.md index 50b9a37eb..162873637 100644 --- a/vendor/github.com/go-xorm/builder/README.md +++ b/vendor/github.com/go-xorm/builder/README.md @@ -1,6 +1,8 @@ # SQL builder -Package builder is a simple and powerful sql builder for Go. +[![CircleCI](https://circleci.com/gh/go-xorm/builder/tree/master.svg?style=svg)](https://circleci.com/gh/go-xorm/builder/tree/master) + +Package builder is a lightweight and fast SQL builder for Go and XORM. Make sure you have installed Go 1.1+ and then: diff --git a/vendor/github.com/go-xorm/builder/builder.go b/vendor/github.com/go-xorm/builder/builder.go index 26533ba0a..1253b9887 100644 --- a/vendor/github.com/go-xorm/builder/builder.go +++ b/vendor/github.com/go-xorm/builder/builder.go @@ -1,3 +1,7 @@ +// 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 builder type optype byte @@ -16,6 +20,7 @@ type join struct { joinCond Cond } +// Builder describes a SQL statement type Builder struct { optype tableName string @@ -26,41 +31,49 @@ type Builder struct { updates []Eq } +// Select creates a select Builder func Select(cols ...string) *Builder { builder := &Builder{cond: NewCond()} return builder.Select(cols...) } +// Insert creates an insert Builder func Insert(eq Eq) *Builder { builder := &Builder{cond: NewCond()} return builder.Insert(eq) } +// Update creates an update Builder func Update(updates ...Eq) *Builder { builder := &Builder{cond: NewCond()} return builder.Update(updates...) } +// Delete creates a delete Builder func Delete(conds ...Cond) *Builder { builder := &Builder{cond: NewCond()} return builder.Delete(conds...) } +// Where sets where SQL func (b *Builder) Where(cond Cond) *Builder { b.cond = b.cond.And(cond) return b } +// From sets the table name func (b *Builder) From(tableName string) *Builder { b.tableName = tableName return b } +// Into sets insert table name func (b *Builder) Into(tableName string) *Builder { b.tableName = tableName return b } +// Join sets join table and contions func (b *Builder) Join(joinType, joinTable string, joinCond interface{}) *Builder { switch joinCond.(type) { case Cond: @@ -72,60 +85,72 @@ func (b *Builder) Join(joinType, joinTable string, joinCond interface{}) *Builde return b } +// InnerJoin sets inner join func (b *Builder) InnerJoin(joinTable string, joinCond interface{}) *Builder { return b.Join("INNER", joinTable, joinCond) } +// LeftJoin sets left join SQL func (b *Builder) LeftJoin(joinTable string, joinCond interface{}) *Builder { return b.Join("LEFT", joinTable, joinCond) } +// RightJoin sets right join SQL func (b *Builder) RightJoin(joinTable string, joinCond interface{}) *Builder { return b.Join("RIGHT", joinTable, joinCond) } +// CrossJoin sets cross join SQL func (b *Builder) CrossJoin(joinTable string, joinCond interface{}) *Builder { return b.Join("CROSS", joinTable, joinCond) } +// FullJoin sets full join SQL func (b *Builder) FullJoin(joinTable string, joinCond interface{}) *Builder { return b.Join("FULL", joinTable, joinCond) } +// Select sets select SQL func (b *Builder) Select(cols ...string) *Builder { b.selects = cols b.optype = selectType return b } +// And sets AND condition func (b *Builder) And(cond Cond) *Builder { b.cond = And(b.cond, cond) return b } +// Or sets OR condition func (b *Builder) Or(cond Cond) *Builder { b.cond = Or(b.cond, cond) return b } +// Insert sets insert SQL func (b *Builder) Insert(eq Eq) *Builder { b.inserts = eq b.optype = insertType return b } +// Update sets update SQL func (b *Builder) Update(updates ...Eq) *Builder { b.updates = updates b.optype = updateType return b } +// Delete sets delete SQL func (b *Builder) Delete(conds ...Cond) *Builder { b.cond = b.cond.And(conds...) b.optype = deleteType return b } +// WriteTo implements Writer interface func (b *Builder) WriteTo(w Writer) error { switch b.optype { case condType: diff --git a/vendor/github.com/go-xorm/builder/builder_delete.go b/vendor/github.com/go-xorm/builder/builder_delete.go index 88379c187..743f1a4a9 100644 --- a/vendor/github.com/go-xorm/builder/builder_delete.go +++ b/vendor/github.com/go-xorm/builder/builder_delete.go @@ -1,3 +1,7 @@ +// 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 builder import ( diff --git a/vendor/github.com/go-xorm/builder/builder_insert.go b/vendor/github.com/go-xorm/builder/builder_insert.go index 996ca8d07..decec9313 100644 --- a/vendor/github.com/go-xorm/builder/builder_insert.go +++ b/vendor/github.com/go-xorm/builder/builder_insert.go @@ -1,3 +1,7 @@ +// 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 builder import ( diff --git a/vendor/github.com/go-xorm/builder/builder_select.go b/vendor/github.com/go-xorm/builder/builder_select.go index 555caa24e..05f116e00 100644 --- a/vendor/github.com/go-xorm/builder/builder_select.go +++ b/vendor/github.com/go-xorm/builder/builder_select.go @@ -1,3 +1,7 @@ +// 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 builder import ( diff --git a/vendor/github.com/go-xorm/builder/builder_update.go b/vendor/github.com/go-xorm/builder/builder_update.go index 83e0cd4fb..182af830f 100644 --- a/vendor/github.com/go-xorm/builder/builder_update.go +++ b/vendor/github.com/go-xorm/builder/builder_update.go @@ -1,3 +1,7 @@ +// 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 builder import ( diff --git a/vendor/github.com/go-xorm/builder/circle.yml b/vendor/github.com/go-xorm/builder/circle.yml new file mode 100644 index 000000000..b2a8bfc9e --- /dev/null +++ b/vendor/github.com/go-xorm/builder/circle.yml @@ -0,0 +1,12 @@ +dependencies: + override: + # './...' is a relative pattern which means all subdirectories + - go get -t -d -v ./... + - go build -v + - go get -u github.com/golang/lint/golint + +test: + override: + # './...' is a relative pattern which means all subdirectories + - golint ./... + - go test -v -race \ No newline at end of file diff --git a/vendor/github.com/go-xorm/builder/cond.go b/vendor/github.com/go-xorm/builder/cond.go index a5f1bb594..77dd139bf 100644 --- a/vendor/github.com/go-xorm/builder/cond.go +++ b/vendor/github.com/go-xorm/builder/cond.go @@ -1,3 +1,7 @@ +// 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 builder import ( @@ -5,31 +9,39 @@ import ( "io" ) +// Writer defines the interface type Writer interface { io.Writer Append(...interface{}) } -type stringWriter struct { +var _ Writer = NewWriter() + +// BytesWriter implments Writer and save SQL in bytes.Buffer +type BytesWriter struct { writer *bytes.Buffer buffer []byte args []interface{} } -func NewWriter() *stringWriter { - w := &stringWriter{} +// NewWriter creates a new string writer +func NewWriter() *BytesWriter { + w := &BytesWriter{} w.writer = bytes.NewBuffer(w.buffer) return w } -func (s *stringWriter) Write(buf []byte) (int, error) { +// Write writes data to Writer +func (s *BytesWriter) Write(buf []byte) (int, error) { return s.writer.Write(buf) } -func (s *stringWriter) Append(args ...interface{}) { +// Append appends args to Writer +func (s *BytesWriter) Append(args ...interface{}) { s.args = append(s.args, args...) } +// Cond defines an interface type Cond interface { WriteTo(Writer) error And(...Cond) Cond @@ -41,6 +53,7 @@ type condEmpty struct{} var _ Cond = condEmpty{} +// NewCond creates an empty condition func NewCond() Cond { return condEmpty{} } diff --git a/vendor/github.com/go-xorm/builder/cond_and.go b/vendor/github.com/go-xorm/builder/cond_and.go index 5063c79bf..9c30e9c2e 100644 --- a/vendor/github.com/go-xorm/builder/cond_and.go +++ b/vendor/github.com/go-xorm/builder/cond_and.go @@ -1,3 +1,7 @@ +// 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 builder import "fmt" @@ -6,6 +10,7 @@ type condAnd []Cond var _ Cond = condAnd{} +// And generates AND conditions func And(conds ...Cond) Cond { var result = make(condAnd, 0, len(conds)) for _, cond := range conds { diff --git a/vendor/github.com/go-xorm/builder/cond_between.go b/vendor/github.com/go-xorm/builder/cond_between.go index b3dce3563..f2b29ed15 100644 --- a/vendor/github.com/go-xorm/builder/cond_between.go +++ b/vendor/github.com/go-xorm/builder/cond_between.go @@ -1,8 +1,12 @@ +// 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 builder import "fmt" -// Between +// Between implmentes between condition type Between struct { Col string LessVal interface{} @@ -11,6 +15,7 @@ type Between struct { var _ Cond = Between{} +// WriteTo write data to Writer func (between Between) WriteTo(w Writer) error { if _, err := fmt.Fprintf(w, "%s BETWEEN ? AND ?", between.Col); err != nil { return err @@ -19,14 +24,17 @@ func (between Between) WriteTo(w Writer) error { return nil } +// And implments And with other conditions func (between Between) And(conds ...Cond) Cond { return And(between, And(conds...)) } +// Or implments Or with other conditions func (between Between) Or(conds ...Cond) Cond { return Or(between, Or(conds...)) } +// IsValid tests if the condition is valid func (between Between) IsValid() bool { return len(between.Col) > 0 } diff --git a/vendor/github.com/go-xorm/builder/cond_compare.go b/vendor/github.com/go-xorm/builder/cond_compare.go index 54785d480..e10ef7447 100644 --- a/vendor/github.com/go-xorm/builder/cond_compare.go +++ b/vendor/github.com/go-xorm/builder/cond_compare.go @@ -1,8 +1,12 @@ +// 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 builder import "fmt" -// WriteMap +// WriteMap writes conditions' SQL to Writer, op could be =, <>, >, <, <=, >= and etc. func WriteMap(w Writer, data map[string]interface{}, op string) error { var args = make([]interface{}, 0, len(data)) var i = 0 @@ -49,86 +53,102 @@ func WriteMap(w Writer, data map[string]interface{}, op string) error { return nil } -// Lt +// Lt defines < condition type Lt map[string]interface{} var _ Cond = Lt{} +// WriteTo write SQL to Writer func (lt Lt) WriteTo(w Writer) error { return WriteMap(w, lt, "<") } +// And implements And with other conditions func (lt Lt) And(conds ...Cond) Cond { return condAnd{lt, And(conds...)} } +// Or implements Or with other conditions func (lt Lt) Or(conds ...Cond) Cond { return condOr{lt, Or(conds...)} } +// IsValid tests if this Eq is valid func (lt Lt) IsValid() bool { return len(lt) > 0 } -// Lte +// Lte defines <= condition type Lte map[string]interface{} var _ Cond = Lte{} +// WriteTo write SQL to Writer func (lte Lte) WriteTo(w Writer) error { return WriteMap(w, lte, "<=") } +// And implements And with other conditions func (lte Lte) And(conds ...Cond) Cond { return And(lte, And(conds...)) } +// Or implements Or with other conditions func (lte Lte) Or(conds ...Cond) Cond { return Or(lte, Or(conds...)) } +// IsValid tests if this Eq is valid func (lte Lte) IsValid() bool { return len(lte) > 0 } -// Gt +// Gt defines > condition type Gt map[string]interface{} var _ Cond = Gt{} +// WriteTo write SQL to Writer func (gt Gt) WriteTo(w Writer) error { return WriteMap(w, gt, ">") } +// And implements And with other conditions func (gt Gt) And(conds ...Cond) Cond { return And(gt, And(conds...)) } +// Or implements Or with other conditions func (gt Gt) Or(conds ...Cond) Cond { return Or(gt, Or(conds...)) } +// IsValid tests if this Eq is valid func (gt Gt) IsValid() bool { return len(gt) > 0 } -// Gte +// Gte defines >= condition type Gte map[string]interface{} var _ Cond = Gte{} +// WriteTo write SQL to Writer func (gte Gte) WriteTo(w Writer) error { return WriteMap(w, gte, ">=") } +// And implements And with other conditions func (gte Gte) And(conds ...Cond) Cond { return And(gte, And(conds...)) } +// Or implements Or with other conditions func (gte Gte) Or(conds ...Cond) Cond { return Or(gte, Or(conds...)) } +// IsValid tests if this Eq is valid func (gte Gte) IsValid() bool { return len(gte) > 0 } diff --git a/vendor/github.com/go-xorm/builder/cond_eq.go b/vendor/github.com/go-xorm/builder/cond_eq.go index 9b530857c..8777727ff 100644 --- a/vendor/github.com/go-xorm/builder/cond_eq.go +++ b/vendor/github.com/go-xorm/builder/cond_eq.go @@ -1,11 +1,18 @@ +// 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 builder import "fmt" +// Incr implements a type used by Eq type Incr int +// Decr implements a type used by Eq type Decr int +// Eq defines equals conditions type Eq map[string]interface{} var _ Cond = Eq{} @@ -68,18 +75,22 @@ func (eq Eq) opWriteTo(op string, w Writer) error { return nil } +// WriteTo writes SQL to Writer func (eq Eq) WriteTo(w Writer) error { return eq.opWriteTo(" AND ", w) } +// And implements And with other conditions func (eq Eq) And(conds ...Cond) Cond { return And(eq, And(conds...)) } +// Or implements Or with other conditions func (eq Eq) Or(conds ...Cond) Cond { return Or(eq, Or(conds...)) } +// IsValid tests if this Eq is valid func (eq Eq) IsValid() bool { return len(eq) > 0 } diff --git a/vendor/github.com/go-xorm/builder/cond_expr.go b/vendor/github.com/go-xorm/builder/cond_expr.go index 2e616f80f..e5ed572b1 100644 --- a/vendor/github.com/go-xorm/builder/cond_expr.go +++ b/vendor/github.com/go-xorm/builder/cond_expr.go @@ -1,3 +1,7 @@ +// 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 builder import "fmt" @@ -9,6 +13,7 @@ type expr struct { var _ Cond = expr{} +// Expr generate customerize SQL func Expr(sql string, args ...interface{}) Cond { return expr{sql, args} } diff --git a/vendor/github.com/go-xorm/builder/cond_in.go b/vendor/github.com/go-xorm/builder/cond_in.go index fbd443014..9ada13b70 100644 --- a/vendor/github.com/go-xorm/builder/cond_in.go +++ b/vendor/github.com/go-xorm/builder/cond_in.go @@ -1,3 +1,7 @@ +// 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 builder import ( @@ -12,6 +16,7 @@ type condIn struct { var _ Cond = condIn{} +// In generates IN condition func In(col string, values ...interface{}) Cond { return condIn{col, values} } diff --git a/vendor/github.com/go-xorm/builder/cond_like.go b/vendor/github.com/go-xorm/builder/cond_like.go index a310ba5a6..0648badbe 100644 --- a/vendor/github.com/go-xorm/builder/cond_like.go +++ b/vendor/github.com/go-xorm/builder/cond_like.go @@ -1,11 +1,17 @@ +// 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 builder import "fmt" +// Like defines like condition type Like [2]string var _ Cond = Like{"", ""} +// WriteTo write SQL to Writer func (like Like) WriteTo(w Writer) error { if _, err := fmt.Fprintf(w, "%s LIKE ?", like[0]); err != nil { return err @@ -14,14 +20,17 @@ func (like Like) WriteTo(w Writer) error { return nil } +// And implements And with other conditions func (like Like) And(conds ...Cond) Cond { return And(like, And(conds...)) } +// Or implements Or with other conditions func (like Like) Or(conds ...Cond) Cond { return Or(like, Or(conds...)) } +// IsValid tests if this condition is valid func (like Like) IsValid() bool { return len(like[0]) > 0 && len(like[1]) > 0 } diff --git a/vendor/github.com/go-xorm/builder/cond_neq.go b/vendor/github.com/go-xorm/builder/cond_neq.go index e12e75b47..d07b2b18e 100644 --- a/vendor/github.com/go-xorm/builder/cond_neq.go +++ b/vendor/github.com/go-xorm/builder/cond_neq.go @@ -1,11 +1,17 @@ +// 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 builder import "fmt" +// Neq defines not equal conditions type Neq map[string]interface{} var _ Cond = Neq{} +// WriteTo writes SQL to Writer func (neq Neq) WriteTo(w Writer) error { var args = make([]interface{}, 0, len(neq)) var i = 0 @@ -56,14 +62,17 @@ func (neq Neq) WriteTo(w Writer) error { return nil } +// And implements And with other conditions func (neq Neq) And(conds ...Cond) Cond { return And(neq, And(conds...)) } +// Or implements Or with other conditions func (neq Neq) Or(conds ...Cond) Cond { return Or(neq, Or(conds...)) } +// IsValid tests if this condition is valid func (neq Neq) IsValid() bool { return len(neq) > 0 } diff --git a/vendor/github.com/go-xorm/builder/cond_not.go b/vendor/github.com/go-xorm/builder/cond_not.go index 33625c534..294a7e072 100644 --- a/vendor/github.com/go-xorm/builder/cond_not.go +++ b/vendor/github.com/go-xorm/builder/cond_not.go @@ -1,11 +1,17 @@ +// 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 builder import "fmt" +// Not defines NOT condition type Not [1]Cond var _ Cond = Not{} +// WriteTo writes SQL to Writer func (not Not) WriteTo(w Writer) error { if _, err := fmt.Fprint(w, "NOT "); err != nil { return err @@ -31,14 +37,17 @@ func (not Not) WriteTo(w Writer) error { return nil } +// And implements And with other conditions func (not Not) And(conds ...Cond) Cond { return And(not, And(conds...)) } +// Or implements Or with other conditions func (not Not) Or(conds ...Cond) Cond { return Or(not, Or(conds...)) } +// IsValid tests if this condition is valid func (not Not) IsValid() bool { return not[0] != nil && not[0].IsValid() } diff --git a/vendor/github.com/go-xorm/builder/cond_notin.go b/vendor/github.com/go-xorm/builder/cond_notin.go index 9176dacdd..47c16be27 100644 --- a/vendor/github.com/go-xorm/builder/cond_notin.go +++ b/vendor/github.com/go-xorm/builder/cond_notin.go @@ -1,3 +1,7 @@ +// 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 builder import ( @@ -9,6 +13,7 @@ type condNotIn condIn var _ Cond = condNotIn{} +// NotIn generate NOT IN condition func NotIn(col string, values ...interface{}) Cond { return condNotIn{col, values} } diff --git a/vendor/github.com/go-xorm/builder/cond_null.go b/vendor/github.com/go-xorm/builder/cond_null.go index a89e5233b..bf2aaf851 100644 --- a/vendor/github.com/go-xorm/builder/cond_null.go +++ b/vendor/github.com/go-xorm/builder/cond_null.go @@ -1,47 +1,59 @@ +// 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 builder import "fmt" -// IsNull +// IsNull defines IS NULL condition type IsNull [1]string var _ Cond = IsNull{""} +// WriteTo write SQL to Writer func (isNull IsNull) WriteTo(w Writer) error { _, err := fmt.Fprintf(w, "%s IS NULL", isNull[0]) return err } +// And implements And with other conditions func (isNull IsNull) And(conds ...Cond) Cond { return And(isNull, And(conds...)) } +// Or implements Or with other conditions func (isNull IsNull) Or(conds ...Cond) Cond { return Or(isNull, Or(conds...)) } +// IsValid tests if this condition is valid func (isNull IsNull) IsValid() bool { return len(isNull[0]) > 0 } -// NotNull +// NotNull defines NOT NULL condition type NotNull [1]string var _ Cond = NotNull{""} +// WriteTo write SQL to Writer func (notNull NotNull) WriteTo(w Writer) error { _, err := fmt.Fprintf(w, "%s IS NOT NULL", notNull[0]) return err } +// And implements And with other conditions func (notNull NotNull) And(conds ...Cond) Cond { return And(notNull, And(conds...)) } +// Or implements Or with other conditions func (notNull NotNull) Or(conds ...Cond) Cond { return Or(notNull, Or(conds...)) } +// IsValid tests if this condition is valid func (notNull NotNull) IsValid() bool { return len(notNull[0]) > 0 } diff --git a/vendor/github.com/go-xorm/builder/cond_or.go b/vendor/github.com/go-xorm/builder/cond_or.go index 1f9dc0174..35c3da025 100644 --- a/vendor/github.com/go-xorm/builder/cond_or.go +++ b/vendor/github.com/go-xorm/builder/cond_or.go @@ -1,3 +1,7 @@ +// 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 builder import "fmt" @@ -6,6 +10,7 @@ type condOr []Cond var _ Cond = condOr{} +// Or sets OR conditions func Or(conds ...Cond) Cond { var result = make(condOr, 0, len(conds)) for _, cond := range conds { @@ -17,8 +22,9 @@ func Or(conds ...Cond) Cond { return result } -func (or condOr) WriteTo(w Writer) error { - for i, cond := range or { +// WriteTo implments Cond +func (o condOr) WriteTo(w Writer) error { + for i, cond := range o { var needQuote bool switch cond.(type) { case condAnd: @@ -40,7 +46,7 @@ func (or condOr) WriteTo(w Writer) error { fmt.Fprint(w, ")") } - if i != len(or)-1 { + if i != len(o)-1 { fmt.Fprint(w, " OR ") } } diff --git a/vendor/github.com/go-xorm/builder/error.go b/vendor/github.com/go-xorm/builder/error.go index ed4dbe302..d7ac51ea1 100644 --- a/vendor/github.com/go-xorm/builder/error.go +++ b/vendor/github.com/go-xorm/builder/error.go @@ -1,9 +1,16 @@ +// 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 builder import "errors" var ( - ErrNotSupportType = errors.New("not supported SQL type") + // ErrNotSupportType not supported SQL type error + ErrNotSupportType = errors.New("not supported SQL type") + // ErrNoNotInConditions no NOT IN params error ErrNoNotInConditions = errors.New("No NOT IN conditions") - ErrNoInConditions = errors.New("No IN conditions") + // ErrNoInConditions no IN params error + ErrNoInConditions = errors.New("No IN conditions") ) diff --git a/vendor/github.com/go-xorm/core/README.md b/vendor/github.com/go-xorm/core/README.md index 0ae94a584..09b72c74b 100644 --- a/vendor/github.com/go-xorm/core/README.md +++ b/vendor/github.com/go-xorm/core/README.md @@ -1,5 +1,7 @@ Core is a lightweight wrapper of sql.DB. +[![CircleCI](https://circleci.com/gh/go-xorm/core/tree/master.svg?style=svg)](https://circleci.com/gh/go-xorm/core/tree/master) + # Open ```Go db, _ := core.Open(db, connstr) diff --git a/vendor/github.com/go-xorm/core/circle.yml b/vendor/github.com/go-xorm/core/circle.yml new file mode 100644 index 000000000..006e1230b --- /dev/null +++ b/vendor/github.com/go-xorm/core/circle.yml @@ -0,0 +1,14 @@ +dependencies: + override: + # './...' is a relative pattern which means all subdirectories + - go get -t -d -v ./... + - go build -v + +database: + override: + - mysql -u root -e "CREATE DATABASE core_test DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci" + +test: + override: + # './...' is a relative pattern which means all subdirectories + - go test -v -race \ No newline at end of file diff --git a/vendor/github.com/go-xorm/core/dialect.go b/vendor/github.com/go-xorm/core/dialect.go index 7bedc27d3..70420ee5a 100644 --- a/vendor/github.com/go-xorm/core/dialect.go +++ b/vendor/github.com/go-xorm/core/dialect.go @@ -287,16 +287,21 @@ func (b *Base) LogSQL(sql string, args []interface{}) { } var ( - dialects = map[DbType]func() Dialect{} + dialects = map[string]func() Dialect{} ) +// RegisterDialect register database dialect func RegisterDialect(dbName DbType, dialectFunc func() Dialect) { if dialectFunc == nil { panic("core: Register dialect is nil") } - dialects[dbName] = dialectFunc // !nashtsai! allow override dialect + dialects[strings.ToLower(string(dbName))] = dialectFunc // !nashtsai! allow override dialect } +// QueryDialect query if registed database dialect func QueryDialect(dbName DbType) Dialect { - return dialects[dbName]() + if d, ok := dialects[strings.ToLower(string(dbName))]; ok { + return d() + } + return nil } diff --git a/vendor/github.com/go-xorm/core/type.go b/vendor/github.com/go-xorm/core/type.go index be5aa2a16..860482251 100644 --- a/vendor/github.com/go-xorm/core/type.go +++ b/vendor/github.com/go-xorm/core/type.go @@ -54,7 +54,7 @@ func (s *SQLType) IsNumeric() bool { } func (s *SQLType) IsJson() bool { - return s.Name == Json + return s.Name == Json || s.Name == Jsonb } var ( diff --git a/vendor/github.com/go-xorm/xorm/README.md b/vendor/github.com/go-xorm/xorm/README.md index 7ee669e82..12f6ca90b 100644 --- a/vendor/github.com/go-xorm/xorm/README.md +++ b/vendor/github.com/go-xorm/xorm/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 diff --git a/vendor/github.com/go-xorm/xorm/README_CN.md b/vendor/github.com/go-xorm/xorm/README_CN.md index f71cd7059..3919b3ed8 100644 --- a/vendor/github.com/go-xorm/xorm/README_CN.md +++ b/vendor/github.com/go-xorm/xorm/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) # 注意 diff --git a/vendor/github.com/go-xorm/xorm/circle.yml b/vendor/github.com/go-xorm/xorm/circle.yml new file mode 100644 index 000000000..8faa627d1 --- /dev/null +++ b/vendor/github.com/go-xorm/xorm/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/vendor/github.com/go-xorm/xorm/engine.go b/vendor/github.com/go-xorm/xorm/engine.go index f92302e98..c56aa0638 100644 --- a/vendor/github.com/go-xorm/xorm/engine.go +++ b/vendor/github.com/go-xorm/xorm/engine.go @@ -168,8 +168,10 @@ func (engine *Engine) quote(sql string) string { } // 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 @@ -295,7 +297,10 @@ 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 { return engine.SQL(querystring, args...) } @@ -434,7 +439,7 @@ func (engine *Engine) dumpAll(w io.Writer, tp ...core.DbType) error { } 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(), "", "") } @@ -530,18 +535,21 @@ func (engine *Engine) dumpAll(w io.Writer, tp ...core.DbType) error { // DumpAll 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 } @@ -1208,12 +1216,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)) diff --git a/vendor/github.com/go-xorm/xorm/error.go b/vendor/github.com/go-xorm/xorm/error.go index 61537a346..2a334f47c 100644 --- a/vendor/github.com/go-xorm/xorm/error.go +++ b/vendor/github.com/go-xorm/xorm/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/vendor/github.com/go-xorm/xorm/helpers.go b/vendor/github.com/go-xorm/xorm/helpers.go index f59db48b1..7efa72943 100644 --- a/vendor/github.com/go-xorm/xorm/helpers.go +++ b/vendor/github.com/go-xorm/xorm/helpers.go @@ -490,9 +490,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 } } @@ -528,18 +527,18 @@ 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) @@ -578,3 +577,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/vendor/github.com/go-xorm/xorm/logger.go b/vendor/github.com/go-xorm/xorm/logger.go index b4f973781..428ae8f2e 100644 --- a/vendor/github.com/go-xorm/xorm/logger.go +++ b/vendor/github.com/go-xorm/xorm/logger.go @@ -20,21 +20,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/vendor/github.com/go-xorm/xorm/lru_cacher.go b/vendor/github.com/go-xorm/xorm/lru_cacher.go index 9b995c2d9..4a7450435 100644 --- a/vendor/github.com/go-xorm/xorm/lru_cacher.go +++ b/vendor/github.com/go-xorm/xorm/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() { @@ -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,10 +113,10 @@ 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 } @@ -128,7 +127,7 @@ func (m *LRUCacher) GetBean(tableName string, id string) interface{} { 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/vendor/github.com/go-xorm/xorm/memory_store.go b/vendor/github.com/go-xorm/xorm/memory_store.go index caf12b10a..36853b19e 100644 --- a/vendor/github.com/go-xorm/xorm/memory_store.go +++ b/vendor/github.com/go-xorm/xorm/memory_store.go @@ -18,10 +18,12 @@ type MemoryStore struct { 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/vendor/github.com/go-xorm/xorm/mssql_dialect.go b/vendor/github.com/go-xorm/xorm/mssql_dialect.go index 023f4de84..0cfa93bf5 100644 --- a/vendor/github.com/go-xorm/xorm/mssql_dialect.go +++ b/vendor/github.com/go-xorm/xorm/mssql_dialect.go @@ -242,8 +242,8 @@ 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: diff --git a/vendor/github.com/go-xorm/xorm/postgres_dialect.go b/vendor/github.com/go-xorm/xorm/postgres_dialect.go index 2edc07ccd..ad7a76f81 100644 --- a/vendor/github.com/go-xorm/xorm/postgres_dialect.go +++ b/vendor/github.com/go-xorm/xorm/postgres_dialect.go @@ -783,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 diff --git a/vendor/github.com/go-xorm/xorm/processors.go b/vendor/github.com/go-xorm/xorm/processors.go index 271bad2b4..77dd30e57 100644 --- a/vendor/github.com/go-xorm/xorm/processors.go +++ b/vendor/github.com/go-xorm/xorm/processors.go @@ -19,10 +19,12 @@ 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) } diff --git a/vendor/github.com/go-xorm/xorm/session.go b/vendor/github.com/go-xorm/xorm/session.go index 10033313b..1eabdc897 100644 --- a/vendor/github.com/go-xorm/xorm/session.go +++ b/vendor/github.com/go-xorm/xorm/session.go @@ -113,7 +113,10 @@ func (session *Session) Prepare() *Session { return session } -// Sql !DEPRECIATED! will be deprecated, 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 (session *Session) Sql(query string, args ...interface{}) *Session { return session.SQL(query, args...) } @@ -143,15 +146,16 @@ func (session *Session) Or(query interface{}, args ...interface{}) *Session { 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 } @@ -376,84 +380,6 @@ func (session *Session) Conds() builder.Cond { return session.Statement.cond } -// 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 -} - func cleanupProcessorsClosures(slices *[]func(interface{})) { if len(*slices) > 0 { *slices = make([]func(interface{}), 0) @@ -506,2475 +432,405 @@ 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 - } +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 + } + return true +} - res, err := stmt.Exec(args...) +func (session *Session) doPrepare(sqlStr string) (stmt *core.Stmt, err error) { + crc := crc32.ChecksumIEEE([]byte(sqlStr)) + // TODO try hash(sqlStr+len(sqlStr)) + var has bool + stmt, has = session.stmtCache[crc] + if !has { + stmt, err = session.DB().Prepare(sqlStr) if err != nil { return nil, err } - return res, nil + session.stmtCache[crc] = stmt } - - return session.DB().Exec(sqlStr, args...) + return } -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) +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()) + return nil } - 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() + fieldValue, err := col.ValueOfV(dataStruct) + if err != nil { + session.Engine.logger.Error(err) + return nil } - 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() + if !fieldValue.IsValid() || !fieldValue.CanSet() { + session.Engine.logger.Warnf("table %v's column %v is not valid or cannot set", table.Name, key) + return nil } - - return session.createOneTable() + return fieldValue } -// 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() - } +// Cell cell is a result of one column field +type Cell *interface{} - sqls := session.Statement.genIndexSQL() - for _, sqlStr := range sqls { - _, err := session.exec(sqlStr) +func (session *Session) rows2Beans(rows *core.Rows, fields []string, fieldsCount int, + table *core.Table, newElemFunc func() reflect.Value, + sliceValueSetFunc func(*reflect.Value)) error { + for rows.Next() { + var newValue = newElemFunc() + bean := newValue.Interface() + dataStruct := rValue(bean) + err := session._row2Bean(rows, fields, fieldsCount, bean, &dataStruct, table) if err != nil { return err } + sliceValueSetFunc(&newValue) } 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 - } +func (session *Session) row2Bean(rows *core.Rows, fields []string, fieldsCount int, bean interface{}) error { + dataStruct := rValue(bean) + if dataStruct.Kind() != reflect.Struct { + return errors.New("Expected a pointer to a struct") } - 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() - } + session.Statement.setRefValue(dataStruct) - 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 + return session._row2Bean(rows, fields, fieldsCount, bean, &dataStruct, session.Statement.RefTable) } -// 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 - } +func (session *Session) _row2Bean(rows *core.Rows, fields []string, fieldsCount int, bean interface{}, dataStruct *reflect.Value, table *core.Table) error { + scanResults := make([]interface{}, fieldsCount) + for i := 0; i < len(fields); i++ { + var cell interface{} + scanResults[i] = &cell } - 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 { + if err := rows.Scan(scanResults...); 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 + if b, hasBeforeSet := bean.(BeforeSetProcessor); hasBeforeSet { + for ii, key := range fields { + b.BeforeSet(key, Cell(scanResults[ii].(*interface{}))) } - 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.Tx != nil || - len(session.Statement.selectStr) > 0 { - return false - } - 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") + defer func() { + if b, hasAfterSet := bean.(AfterSetProcessor); hasAfterSet { + for ii, key := range fields { + b.AfterSet(key, Cell(scanResults[ii].(*interface{}))) } } + }() - 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) + var tempMap = make(map[string]int) + for ii, key := range fields { + var idx int + var ok bool + var lKey = strings.ToLower(key) + if idx, ok = tempMap[lKey]; !ok { + idx = 0 } else { - session.Engine.logger.Debug("[cacheGet] cache hit bean:", tableName, id, cacheBean) - has = true + idx = idx + 1 } - 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() + tempMap[lKey] = idx - 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() + if fieldValue := session.getField(dataStruct, key, table, idx); fieldValue != nil { + rawValue := reflect.Indirect(reflect.ValueOf(scanResults[ii])) - 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 + // if row is null then ignore + if rawValue.Interface() == nil { + continue } - var res = make([]string, len(table.PrimaryKeys)) - err = rows.ScanSlice(&res) - if err != nil { - return err + + if fieldValue.CanAddr() { + if structConvert, ok := fieldValue.Addr().Interface().(core.Conversion); ok { + if data, err := value2Bytes(&rawValue); err == nil { + structConvert.FromDB(data) + } else { + session.Engine.logger.Error(err) + } + continue + } } - 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 + if _, ok := fieldValue.Interface().(core.Conversion); ok { + if data, err := value2Bytes(&rawValue); err == nil { + if fieldValue.Kind() == reflect.Ptr && fieldValue.IsNil() { + fieldValue.Set(reflect.New(fieldValue.Type().Elem())) } - pk[i] = n - } else if col.SQLType.IsText() { - pk[i] = res[i] + fieldValue.Interface().(core.Conversion).FromDB(data) } else { - return errors.New("not supported") + session.Engine.logger.Error(err) } + continue } - 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)) + rawValueType := reflect.TypeOf(rawValue.Interface()) + vv := reflect.ValueOf(rawValue.Interface()) - ididxes := make(map[string]int) - var ides []core.PK - var temps = make([]interface{}, len(ids)) + fieldType := fieldValue.Type() + hasAssigned := false + col := table.GetColumnIdx(key, idx) - 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 col.SQLType.IsJson() { + var bs []byte + if rawValueType.Kind() == reflect.String { + bs = []byte(vv.String()) + } else if rawValueType.ConvertibleTo(core.BytesType) { + bs = vv.Bytes() + } else { + return fmt.Errorf("unsupported database data type: %s %v", key, rawValueType.Kind()) + } - if len(table.PrimaryKeys) == 1 { - ff := make([]interface{}, 0, len(ides)) - for _, ie := range ides { - ff = append(ff, ie[0]) - } + hasAssigned = true - 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]}) + if len(bs) > 0 { + if fieldValue.CanAddr() { + err := json.Unmarshal(bs, fieldValue.Addr().Interface()) + if err != nil { + session.Engine.logger.Error(key, err) + return err + } + } else { + x := reflect.New(fieldType) + err := json.Unmarshal(bs, x.Interface()) + if err != nil { + session.Engine.logger.Error(key, err) + return err + } + fieldValue.Set(x.Elem()) + } } - 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 + continue } - bean := rv.Interface() - temps[ididxes[sid]] = bean - session.Engine.logger.Debug("[cacheFind] cache bean:", tableName, id, bean, temps) - cacher.PutBean(tableName, sid, bean) - } - } + switch fieldType.Kind() { + case reflect.Complex64, reflect.Complex128: + // TODO: reimplement this + var bs []byte + if rawValueType.Kind() == reflect.String { + bs = []byte(vv.String()) + } else if rawValueType.ConvertibleTo(core.BytesType) { + bs = vv.Bytes() + } - 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 + hasAssigned = true + if len(bs) > 0 { + if fieldValue.CanAddr() { + err := json.Unmarshal(bs, fieldValue.Addr().Interface()) + if err != nil { + session.Engine.logger.Error(err) + return err + } + } else { + x := reflect.New(fieldType) + err := json.Unmarshal(bs, x.Interface()) + if err != nil { + session.Engine.logger.Error(err) + return err + } + fieldValue.Set(x.Elem()) + } } - } else { - if keyType.Kind() != reflect.Slice { - return errors.New("table have multiple primary keys, key is not core.PK or slice") + case reflect.Slice, reflect.Array: + switch rawValueType.Kind() { + case reflect.Slice, reflect.Array: + switch rawValueType.Elem().Kind() { + case reflect.Uint8: + if fieldType.Elem().Kind() == reflect.Uint8 { + hasAssigned = true + fieldValue.Set(vv) + } + } } - 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() - - 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)) - var has bool - stmt, has = session.stmtCache[crc] - if !has { - stmt, err = session.DB().Prepare(sqlStr) - if err != nil { - return nil, err - } - session.stmtCache[crc] = stmt - } - return -} - -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() { - if fields, err := rawRows.Columns(); err == nil { - err = session.row2Bean(rawRows, fields, len(fields), bean) - } - return true, err - } - return false, nil -} - -// 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...) -} - -// 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...) + case reflect.String: + if rawValueType.Kind() == reflect.String { + hasAssigned = true + fieldValue.SetString(vv.String()) + } + case reflect.Bool: + if rawValueType.Kind() == reflect.Bool { + hasAssigned = true + fieldValue.SetBool(vv.Bool()) + } + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + switch rawValueType.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + hasAssigned = true + fieldValue.SetInt(vv.Int()) + } + case reflect.Float32, reflect.Float64: + switch rawValueType.Kind() { + case reflect.Float32, reflect.Float64: + hasAssigned = true + fieldValue.SetFloat(vv.Float()) + } + case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: + switch rawValueType.Kind() { + case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: + hasAssigned = true + fieldValue.SetUint(vv.Uint()) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + hasAssigned = true + fieldValue.SetUint(uint64(vv.Int())) + } + case reflect.Struct: + if fieldType.ConvertibleTo(core.TimeType) { + if rawValueType == core.TimeType { + hasAssigned = true - 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) - } + t := vv.Convert(core.TimeType).Interface().(time.Time) - if err == sql.ErrNoRows || err == nil { - return res, nil - } - return nil, err -} + z, _ := t.Zone() + dbTZ := session.Engine.DatabaseTZ + if dbTZ == nil { + if session.Engine.dialect.DBType() == core.SQLITE { + dbTZ = time.UTC + } else { + dbTZ = time.Local + } + } -// 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() - } + // 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) + } - 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 - } + // !nashtsai! convert to engine location + if col.TimeZone == nil { + t = t.In(session.Engine.TZLocation) + } else { + t = t.In(col.TimeZone) + } + fieldValue.Set(reflect.ValueOf(t).Convert(fieldType)) - 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 -} - -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 errors.New("Expected a pointer to a struct") - } - - return session.rows2Beans(rawRows, fields, len(fields), session.Engine.autoMapType(dataStruct), newElemFunc, sliceValueSetFunc) -} - -// 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) - var autoCond builder.Cond - 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 - } - 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 = "*" - } - } - } - } - - 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 -} - -// 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 - 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 -} - -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()) - return nil - } - - fieldValue, err := col.ValueOfV(dataStruct) - if err != nil { - session.Engine.logger.Error(err) - return nil - } - - if !fieldValue.IsValid() || !fieldValue.CanSet() { - session.Engine.logger.Warnf("table %v's column %v is not valid or cannot set", table.Name, key) - return nil - } - return fieldValue -} - -// Cell cell is a result of one column field -type Cell *interface{} - -func (session *Session) rows2Beans(rows *core.Rows, fields []string, fieldsCount int, - table *core.Table, newElemFunc func() reflect.Value, - sliceValueSetFunc func(*reflect.Value)) error { - for rows.Next() { - var newValue = newElemFunc() - bean := newValue.Interface() - dataStruct := rValue(bean) - err := session._row2Bean(rows, fields, fieldsCount, bean, &dataStruct, table) - if err != nil { - return err - } - sliceValueSetFunc(&newValue) - } - return nil -} - -func (session *Session) row2Bean(rows *core.Rows, fields []string, fieldsCount int, bean interface{}) error { - dataStruct := rValue(bean) - if dataStruct.Kind() != reflect.Struct { - return errors.New("Expected a pointer to a struct") - } - - session.Statement.setRefValue(dataStruct) - - return session._row2Bean(rows, fields, fieldsCount, bean, &dataStruct, session.Statement.RefTable) -} - -func (session *Session) _row2Bean(rows *core.Rows, fields []string, fieldsCount int, bean interface{}, dataStruct *reflect.Value, table *core.Table) error { - scanResults := make([]interface{}, fieldsCount) - for i := 0; i < len(fields); i++ { - var cell interface{} - scanResults[i] = &cell - } - if err := rows.Scan(scanResults...); err != nil { - return err - } - - if b, hasBeforeSet := bean.(BeforeSetProcessor); hasBeforeSet { - for ii, key := range fields { - b.BeforeSet(key, Cell(scanResults[ii].(*interface{}))) - } - } - - defer func() { - if b, hasAfterSet := bean.(AfterSetProcessor); hasAfterSet { - for ii, key := range fields { - b.AfterSet(key, Cell(scanResults[ii].(*interface{}))) - } - } - }() - - var tempMap = make(map[string]int) - for ii, key := range fields { - var idx int - var ok bool - var lKey = strings.ToLower(key) - if idx, ok = tempMap[lKey]; !ok { - idx = 0 - } else { - idx = idx + 1 - } - tempMap[lKey] = idx - - if fieldValue := session.getField(dataStruct, key, table, idx); fieldValue != nil { - rawValue := reflect.Indirect(reflect.ValueOf(scanResults[ii])) - - // if row is null then ignore - if rawValue.Interface() == nil { - continue - } - - if fieldValue.CanAddr() { - if structConvert, ok := fieldValue.Addr().Interface().(core.Conversion); ok { - if data, err := value2Bytes(&rawValue); err == nil { - structConvert.FromDB(data) - } else { - session.Engine.logger.Error(err) - } - continue - } - } - - if _, ok := fieldValue.Interface().(core.Conversion); ok { - if data, err := value2Bytes(&rawValue); err == nil { - if fieldValue.Kind() == reflect.Ptr && fieldValue.IsNil() { - fieldValue.Set(reflect.New(fieldValue.Type().Elem())) - } - fieldValue.Interface().(core.Conversion).FromDB(data) - } else { - session.Engine.logger.Error(err) - } - continue - } - - rawValueType := reflect.TypeOf(rawValue.Interface()) - vv := reflect.ValueOf(rawValue.Interface()) - - fieldType := fieldValue.Type() - hasAssigned := false - col := table.GetColumnIdx(key, idx) - - if col.SQLType.IsJson() { - var bs []byte - if rawValueType.Kind() == reflect.String { - bs = []byte(vv.String()) - } else if rawValueType.ConvertibleTo(core.BytesType) { - bs = vv.Bytes() - } else { - return fmt.Errorf("unsupported database data type: %s %v", key, rawValueType.Kind()) - } - - hasAssigned = true - - if len(bs) > 0 { - if fieldValue.CanAddr() { - err := json.Unmarshal(bs, fieldValue.Addr().Interface()) - if err != nil { - session.Engine.logger.Error(key, err) - return err - } - } else { - x := reflect.New(fieldType) - err := json.Unmarshal(bs, x.Interface()) - if err != nil { - session.Engine.logger.Error(key, err) - return err - } - fieldValue.Set(x.Elem()) - } - } - - continue - } - - switch fieldType.Kind() { - case reflect.Complex64, reflect.Complex128: - // TODO: reimplement this - var bs []byte - if rawValueType.Kind() == reflect.String { - bs = []byte(vv.String()) - } else if rawValueType.ConvertibleTo(core.BytesType) { - bs = vv.Bytes() - } - - hasAssigned = true - if len(bs) > 0 { - if fieldValue.CanAddr() { - err := json.Unmarshal(bs, fieldValue.Addr().Interface()) - if err != nil { - session.Engine.logger.Error(err) - return err - } - } else { - x := reflect.New(fieldType) - err := json.Unmarshal(bs, x.Interface()) - if err != nil { - session.Engine.logger.Error(err) - return err - } - fieldValue.Set(x.Elem()) - } - } - case reflect.Slice, reflect.Array: - switch rawValueType.Kind() { - case reflect.Slice, reflect.Array: - switch rawValueType.Elem().Kind() { - case reflect.Uint8: - if fieldType.Elem().Kind() == reflect.Uint8 { - hasAssigned = true - fieldValue.Set(vv) - } - } - } - case reflect.String: - if rawValueType.Kind() == reflect.String { - hasAssigned = true - fieldValue.SetString(vv.String()) - } - case reflect.Bool: - if rawValueType.Kind() == reflect.Bool { - hasAssigned = true - fieldValue.SetBool(vv.Bool()) - } - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - switch rawValueType.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - hasAssigned = true - fieldValue.SetInt(vv.Int()) - } - case reflect.Float32, reflect.Float64: - switch rawValueType.Kind() { - case reflect.Float32, reflect.Float64: - hasAssigned = true - fieldValue.SetFloat(vv.Float()) - } - case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: - switch rawValueType.Kind() { - case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: - hasAssigned = true - fieldValue.SetUint(vv.Uint()) - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - hasAssigned = true - fieldValue.SetUint(uint64(vv.Int())) - } - case reflect.Struct: - if fieldType.ConvertibleTo(core.TimeType) { - if rawValueType == core.TimeType { - 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 = time.Local - } - 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) - } else { - t = t.In(col.TimeZone) - } - fieldValue.Set(reflect.ValueOf(t).Convert(fieldType)) - - // t = fieldValue.Interface().(time.Time) - // z, _ = t.Zone() - // session.Engine.LogDebug("fieldValue key[%v]: %v | zone: %v | location: %+v\n", key, t, z, *t.Location()) - } else if rawValueType == core.IntType || rawValueType == core.Int64Type || - rawValueType == core.Int32Type { - hasAssigned = true - var tz *time.Location - if col.TimeZone == nil { - tz = session.Engine.TZLocation - } else { - tz = col.TimeZone - } - t := time.Unix(vv.Int(), 0).In(tz) - //vv = reflect.ValueOf(t) - fieldValue.Set(reflect.ValueOf(t).Convert(fieldType)) - } else { - if d, ok := vv.Interface().([]uint8); ok { - hasAssigned = true - t, err := session.byte2Time(col, d) - if err != nil { - session.Engine.logger.Error("byte2Time error:", err.Error()) - hasAssigned = false - } else { - fieldValue.Set(reflect.ValueOf(t).Convert(fieldType)) - } - } else if d, ok := vv.Interface().(string); ok { - hasAssigned = true - t, err := session.str2Time(col, d) - if err != nil { - session.Engine.logger.Error("byte2Time error:", err.Error()) - hasAssigned = false - } else { - fieldValue.Set(reflect.ValueOf(t).Convert(fieldType)) - } - } else { - panic(fmt.Sprintf("rawValueType is %v, value is %v", rawValueType, vv.Interface())) - } - } - } else if nulVal, ok := fieldValue.Addr().Interface().(sql.Scanner); ok { - // !! 增加支持sql.Scanner接口的结构,如sql.NullString - hasAssigned = true - if err := nulVal.Scan(vv.Interface()); err != nil { - session.Engine.logger.Error("sql.Sanner error:", err.Error()) - hasAssigned = false - } - } else if col.SQLType.IsJson() { - if rawValueType.Kind() == reflect.String { - hasAssigned = true - x := reflect.New(fieldType) - if len([]byte(vv.String())) > 0 { - err := json.Unmarshal([]byte(vv.String()), x.Interface()) - if err != nil { - session.Engine.logger.Error(err) - return err - } - fieldValue.Set(x.Elem()) - } - } else if rawValueType.Kind() == reflect.Slice { - hasAssigned = true - x := reflect.New(fieldType) - if len(vv.Bytes()) > 0 { - err := json.Unmarshal(vv.Bytes(), x.Interface()) - if err != nil { - session.Engine.logger.Error(err) - return err - } - fieldValue.Set(x.Elem()) - } - } - } else if session.Statement.UseCascade { - table := session.Engine.autoMapType(*fieldValue) - 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) 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...) -} - -// 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() - } - - 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) - session.resetStatement() - if err != nil { - return affected, err - } - affected += cnt - } else { - for i := 0; i < size; i++ { - cnt, err := session.innerInsert(sliceValue.Index(i).Interface()) - session.resetStatement() - if err != nil { - return affected, err - } - affected += cnt - } - } - } - } else { - cnt, err := session.innerInsert(bean) - session.resetStatement() - 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 := session.Statement.columnMap[strings.ToLower(col.Name)]; !ok { - continue - } - } - if session.Statement.OmitStr != "" { - if _, ok := session.Statement.columnMap[strings.ToLower(col.Name)]; 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 := session.Statement.columnMap[strings.ToLower(col.Name)]; !ok { - continue - } - } - if session.Statement.OmitStr != "" { - if _, ok := session.Statement.columnMap[strings.ToLower(col.Name)]; 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) 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 casuing 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": - 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()) + // t = fieldValue.Interface().(time.Time) + // z, _ = t.Zone() + // session.Engine.LogDebug("fieldValue key[%v]: %v | zone: %v | location: %+v\n", key, t, z, *t.Location()) + } else if rawValueType == core.IntType || rawValueType == core.Int64Type || + rawValueType == core.Int32Type { + hasAssigned = true + var tz *time.Location + if col.TimeZone == nil { + tz = session.Engine.TZLocation + } else { + tz = col.TimeZone + } + t := time.Unix(vv.Int(), 0).In(tz) + //vv = reflect.ValueOf(t) + fieldValue.Set(reflect.ValueOf(t).Convert(fieldType)) + } else { + if d, ok := vv.Interface().([]uint8); ok { + hasAssigned = true + t, err := session.byte2Time(col, d) + if err != nil { + session.Engine.logger.Error("byte2Time error:", err.Error()) + hasAssigned = false + } else { + fieldValue.Set(reflect.ValueOf(t).Convert(fieldType)) + } + } else if d, ok := vv.Interface().(string); ok { + hasAssigned = true + t, err := session.str2Time(col, d) + if err != nil { + session.Engine.logger.Error("byte2Time error:", err.Error()) + hasAssigned = false + } else { + fieldValue.Set(reflect.ValueOf(t).Convert(fieldType)) + } + } else { + panic(fmt.Sprintf("rawValueType is %v, value is %v", rawValueType, vv.Interface())) + } + } + } else if nulVal, ok := fieldValue.Addr().Interface().(sql.Scanner); ok { + // !! 增加支持sql.Scanner接口的结构,如sql.NullString + hasAssigned = true + if err := nulVal.Scan(vv.Interface()); err != nil { + session.Engine.logger.Error("sql.Sanner error:", err.Error()) + hasAssigned = false + } + } else if col.SQLType.IsJson() { + if rawValueType.Kind() == reflect.String { + hasAssigned = true + x := reflect.New(fieldType) + if len([]byte(vv.String())) > 0 { + err := json.Unmarshal([]byte(vv.String()), x.Interface()) + if err != nil { + session.Engine.logger.Error(err) + return err + } + fieldValue.Set(x.Elem()) + } + } else if rawValueType.Kind() == reflect.Slice { + hasAssigned = true + x := reflect.New(fieldType) + if len(vv.Bytes()) > 0 { + err := json.Unmarshal(vv.Bytes(), x.Interface()) + if err != nil { + session.Engine.logger.Error(err) + return err + } + fieldValue.Set(x.Elem()) + } + } + } else if session.Statement.UseCascade { + table := session.Engine.autoMapType(*fieldValue) if table != nil { - if len(table.PrimaryKeys) > 1 { - panic("unsupported composited primary key cascade") + hasAssigned = true + if len(table.PrimaryKeys) != 1 { + panic("unsupported non or composited primary key cascade") } var pk = make(core.PK, len(table.PrimaryKeys)) - var err error - rawValueType := table.ColumnType(table.PKColumns()[0].FieldName) - pk[0], err = str2PK(string(data), rawValueType) - if err != nil { - return err + + 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()) @@ -2982,950 +838,810 @@ func (session *Session) bytes2Value(col *core.Column, fieldValue *reflect.Value, return err } if has { - v = structInter.Interface() - fieldValue.Set(reflect.ValueOf(v)) + //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 { - return fmt.Errorf("unsupported struct type in Scan: %s", fieldValue.Type().String()) + session.Engine.logger.Error(err.Error()) } } - default: - return fmt.Errorf("unsupported type in Scan: %s", fieldValue.Type().String()) } - default: - return fmt.Errorf("unsupported type in Scan: %s", fieldValue.Type().String()) } + 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 casuing 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 +} - return nil +func (session *Session) byte2Time(col *core.Column, data []byte) (outTime time.Time, outErr error) { + return session.str2Time(col, string(data)) } -// convert a field value of a struct to interface for put into db -func (session *Session) value2Interface(col *core.Column, fieldValue reflect.Value) (interface{}, error) { - if fieldValue.CanAddr() { - if fieldConvert, ok := fieldValue.Addr().Interface().(core.Conversion); ok { - data, err := fieldConvert.ToDB() - if err != nil { - return 0, err - } - return string(data), nil - } +// 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 fieldConvert, ok := fieldValue.Interface().(core.Conversion); ok { - data, err := fieldConvert.ToDB() - if err != nil { - return 0, err - } - return string(data), nil + if structConvert, ok := fieldValue.Interface().(core.Conversion); ok { + return structConvert.FromDB(data) } + var v interface{} + key := col.Name fieldType := fieldValue.Type() - k := fieldType.Kind() - if k == reflect.Ptr { - if fieldValue.IsNil() { - return nil, nil - } else if !fieldValue.IsValid() { - session.Engine.logger.Warn("the field[", col.FieldName, "] is invalid") - return nil, nil - } else { - // !nashtsai! deference pointer type to instance type - fieldValue = fieldValue.Elem() - fieldType = fieldValue.Type() - k = fieldType.Kind() - } - } - switch k { - case reflect.Bool: - return fieldValue.Bool(), nil - case reflect.String: - return fieldValue.String(), nil - case reflect.Struct: - if fieldType.ConvertibleTo(core.TimeType) { - t := fieldValue.Convert(core.TimeType).Interface().(time.Time) - if session.Engine.dialect.DBType() == core.MSSQL { - if t.IsZero() { - return nil, nil - } + 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 } - tf := session.Engine.FormatTime(col.SQLType.Name, t) - return tf, nil + fieldValue.Set(x.Elem()) } - - if !col.SQLType.IsJson() { - // !! 增加支持driver.Valuer接口的结构,如sql.NullString - if v, ok := fieldValue.Interface().(driver.Valuer); ok { - return v.Value() + 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()) } - - fieldTable := session.Engine.autoMapType(fieldValue) - if len(fieldTable.PrimaryKeys) == 1 { - pkField := reflect.Indirect(fieldValue).FieldByName(fieldTable.PKColumns()[0].FieldName) - return pkField.Interface(), nil + } 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()) + } } - return 0, fmt.Errorf("no primary key for col %v", col.Name) + } else { + return ErrUnSupportedType } - - if col.SQLType.IsText() { - bytes, err := json.Marshal(fieldValue.Interface()) - if err != nil { - session.Engine.logger.Error(err) - return 0, err + 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 } - return string(bytes), nil - } else if col.SQLType.IsBlob() { - bytes, err := json.Marshal(fieldValue.Interface()) - if err != nil { - session.Engine.logger.Error(err) - return 0, err + } 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()) + } } - return bytes, nil - } - return nil, fmt.Errorf("Unsupported type %v", fieldValue.Type()) - case reflect.Complex64, reflect.Complex128: - bytes, err := json.Marshal(fieldValue.Interface()) - if err != nil { - session.Engine.logger.Error(err) - return 0, err - } - return string(bytes), nil - case reflect.Array, reflect.Slice, reflect.Map: - if !fieldValue.IsValid() { - return fieldValue.Interface(), nil } - - if col.SQLType.IsText() { - bytes, err := json.Marshal(fieldValue.Interface()) + 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 { - session.Engine.logger.Error(err) - return 0, err + return fmt.Errorf("arg %v as bool: %s", key, err.Error()) } - return string(bytes), nil - } else if col.SQLType.IsBlob() { - var bytes []byte - var err error - if (k == reflect.Array || k == reflect.Slice) && - (fieldValue.Type().Elem().Kind() == reflect.Uint8) { - bytes = fieldValue.Bytes() - } else { - bytes, err = json.Marshal(fieldValue.Interface()) + 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 0, err + return err } + fieldValue.Set(reflect.ValueOf(&x).Convert(fieldType)) } - return bytes, nil - } - return nil, ErrUnSupportedType - case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: - return int64(fieldValue.Uint()), nil - default: - return fieldValue.Interface(), nil - } -} - -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:]...) + // 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)) } - } - - // 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) + // 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()) } - if processor, ok := interface{}(bean).(AfterInsertProcessor); ok { - processor.AfterInsert() + 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()) } - } 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 - } + 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()) } - } - 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) + 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 { - session.Engine.logger.Error(err) - } else if verValue.IsValid() && verValue.CanSet() { - verValue.SetInt(1) + return fmt.Errorf("arg %v as int: %s", key, err.Error()) } - } - - 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) + 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 { - session.Engine.logger.Error(err) - } else if verValue.IsValid() && verValue.CanSet() { - verValue.SetInt(1) + return fmt.Errorf("arg %v as int: %s", key, err.Error()) } - } - - 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) + 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 { - session.Engine.logger.Error(err) - } else if verValue.IsValid() && verValue.CanSet() { - verValue.SetInt(1) + return fmt.Errorf("arg %v as int: %s", key, err.Error()) } - } - - 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) + 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 err + return fmt.Errorf("arg %v as int: %s", key, err.Error()) } - 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] + 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 { - return errors.New("not supported") + 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) } - - 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 + if err != nil { + return fmt.Errorf("arg %v as int: %s", key, err.Error()) } - - sqls = splitNNoCase(sqls[0], "set", 2) - if len(sqls) != 2 { - return ErrCacheFailed + 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()) } - 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)) + 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 { - session.Engine.logger.Debug("[cacheUpdate] cannot find column", tableName, colName) - return ErrCacheFailed + x = 0 } - - 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 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 { - session.Engine.logger.Errorf("[cacheUpdate] ERROR: column %v is not table %v's", - colName, table.Name) + 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) } - - 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 + return fmt.Errorf("arg %v as int: %s", key, err.Error()) } - } - } 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...) + 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 { - 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 + x1, err = strconv.ParseInt(sdata, 10, 64) + x = int16(x1) } - - } else { - if _, ok := interface{}(bean).(AfterInsertProcessor); ok { - session.afterUpdateBeans[bean] = nil + if err != nil { + return fmt.Errorf("arg %v as int: %s", key, err.Error()) } - } - } - 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) + 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 { + if len(table.PrimaryKeys) > 1 { + panic("unsupported composited primary key cascade") + } + var pk = make(core.PK, len(table.PrimaryKeys)) + var err error + rawValueType := table.ColumnType(table.PKColumns()[0].FieldName) + pk[0], err = str2PK(string(data), rawValueType) if err != nil { return err } - pk = append(pk, id) - } else { - return errors.New("not supported primary key type") + + 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 + 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.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()) } - ids = append(ids, pk) } + default: + return fmt.Errorf("unsupported type in Scan: %s", fieldValue.Type().String()) } - } /*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) + default: + return fmt.Errorf("unsupported type in Scan: %s", fieldValue.Type().String()) } - 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 +// convert a field value of a struct to interface for put into db +func (session *Session) value2Interface(col *core.Column, fieldValue reflect.Value) (interface{}, error) { + if fieldValue.CanAddr() { + if fieldConvert, ok := fieldValue.Addr().Interface().(core.Conversion); ok { + data, err := fieldConvert.ToDB() + if err != nil { + return 0, err } - 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 + if col.SQLType.IsBlob() { + return data, nil } - // TODO: how to handle delete limit on mssql? - case core.MSSQL: - return 0, ErrNotImplemented - default: - deleteSQL += orderSQL + return string(data), nil } } - 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...) + if fieldConvert, ok := fieldValue.Interface().(core.Conversion); ok { + data, err := fieldConvert.ToDB() + if err != nil { + return 0, err + } + if col.SQLType.IsBlob() { + return data, nil + } + return string(data), nil + } - deletedColumn := table.DeletedColumn() - realSQL = fmt.Sprintf("UPDATE %v SET %v = ? WHERE %v", - session.Engine.Quote(session.Statement.TableName()), - session.Engine.Quote(deletedColumn.Name), - condSQL) + fieldType := fieldValue.Type() + k := fieldType.Kind() + if k == reflect.Ptr { + if fieldValue.IsNil() { + return nil, nil + } else if !fieldValue.IsValid() { + session.Engine.logger.Warn("the field[", col.FieldName, "] is invalid") + return nil, nil + } else { + // !nashtsai! deference pointer type to instance type + fieldValue = fieldValue.Elem() + fieldType = fieldValue.Type() + k = fieldType.Kind() + } + } - 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 + switch k { + case reflect.Bool: + return fieldValue.Bool(), nil + case reflect.String: + return fieldValue.String(), nil + case reflect.Struct: + if fieldType.ConvertibleTo(core.TimeType) { + t := fieldValue.Convert(core.TimeType).Interface().(time.Time) + if session.Engine.dialect.DBType() == core.MSSQL { + if t.IsZero() { + return nil, nil } - // TODO: how to handle delete limit on mssql? - case core.MSSQL: - return 0, ErrNotImplemented - default: - realSQL += orderSQL } + tf := session.Engine.FormatTime(col.SQLType.Name, t) + return tf, nil } - // !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...) - } + if !col.SQLType.IsJson() { + // !! 增加支持driver.Valuer接口的结构,如sql.NullString + if v, ok := fieldValue.Interface().(driver.Valuer); ok { + return v.Value() + } - res, err := session.exec(realSQL, condArgs...) - if err != nil { - return 0, err - } + fieldTable := session.Engine.autoMapType(fieldValue) + if len(fieldTable.PrimaryKeys) == 1 { + pkField := reflect.Indirect(fieldValue).FieldByName(fieldTable.PKColumns()[0].FieldName) + return pkField.Interface(), nil + } + return 0, fmt.Errorf("no primary key for col %v", col.Name) + } - // handle after delete processors - if session.IsAutoCommit { - for _, closure := range session.afterClosures { - closure(bean) + if col.SQLType.IsText() { + bytes, err := json.Marshal(fieldValue.Interface()) + if err != nil { + session.Engine.logger.Error(err) + return 0, err + } + return string(bytes), nil + } else if col.SQLType.IsBlob() { + bytes, err := json.Marshal(fieldValue.Interface()) + if err != nil { + session.Engine.logger.Error(err) + return 0, err + } + return bytes, nil } - if processor, ok := interface{}(bean).(AfterDeleteProcessor); ok { - processor.AfterDelete() + return nil, fmt.Errorf("Unsupported type %v", fieldValue.Type()) + case reflect.Complex64, reflect.Complex128: + bytes, err := json.Marshal(fieldValue.Interface()) + if err != nil { + session.Engine.logger.Error(err) + return 0, err } - } 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 + return string(bytes), nil + case reflect.Array, reflect.Slice, reflect.Map: + if !fieldValue.IsValid() { + return fieldValue.Interface(), nil + } + + if col.SQLType.IsText() { + bytes, err := json.Marshal(fieldValue.Interface()) + if err != nil { + session.Engine.logger.Error(err) + return 0, err } - } else { - if _, ok := interface{}(bean).(AfterInsertProcessor); ok { - session.afterDeleteBeans[bean] = nil + return string(bytes), nil + } else if col.SQLType.IsBlob() { + var bytes []byte + var err error + if (k == reflect.Array || k == reflect.Slice) && + (fieldValue.Type().Elem().Kind() == reflect.Uint8) { + bytes = fieldValue.Bytes() + } else { + bytes, err = json.Marshal(fieldValue.Interface()) + if err != nil { + session.Engine.logger.Error(err) + return 0, err + } } + return bytes, nil } + return nil, ErrUnSupportedType + case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: + return int64(fieldValue.Uint()), nil + default: + return fieldValue.Interface(), nil } - cleanupProcessorsClosures(&session.afterClosures) - // -- - - return res.RowsAffected() } // saveLastSQL stores executed query information @@ -3949,198 +1665,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 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 -} - // Unscoped always disable struct tag "deleted" func (session *Session) Unscoped() *Session { session.Statement.Unscoped() diff --git a/vendor/github.com/go-xorm/xorm/session_delete.go b/vendor/github.com/go-xorm/xorm/session_delete.go new file mode 100644 index 000000000..8c9193c20 --- /dev/null +++ b/vendor/github.com/go-xorm/xorm/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/vendor/github.com/go-xorm/xorm/session_find.go b/vendor/github.com/go-xorm/xorm/session_find.go new file mode 100644 index 000000000..2e52fff3c --- /dev/null +++ b/vendor/github.com/go-xorm/xorm/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/vendor/github.com/go-xorm/xorm/session_get.go b/vendor/github.com/go-xorm/xorm/session_get.go new file mode 100644 index 000000000..bafed9362 --- /dev/null +++ b/vendor/github.com/go-xorm/xorm/session_get.go @@ -0,0 +1,176 @@ +// 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() { + if fields, err := rawRows.Columns(); 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/vendor/github.com/go-xorm/xorm/session_insert.go b/vendor/github.com/go-xorm/xorm/session_insert.go new file mode 100644 index 000000000..96e969c2a --- /dev/null +++ b/vendor/github.com/go-xorm/xorm/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/vendor/github.com/go-xorm/xorm/session_iterate.go b/vendor/github.com/go-xorm/xorm/session_iterate.go new file mode 100644 index 000000000..7c1480959 --- /dev/null +++ b/vendor/github.com/go-xorm/xorm/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/vendor/github.com/go-xorm/xorm/session_raw.go b/vendor/github.com/go-xorm/xorm/session_raw.go new file mode 100644 index 000000000..9351d5cf9 --- /dev/null +++ b/vendor/github.com/go-xorm/xorm/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/vendor/github.com/go-xorm/xorm/session_schema.go b/vendor/github.com/go-xorm/xorm/session_schema.go new file mode 100644 index 000000000..9011adadf --- /dev/null +++ b/vendor/github.com/go-xorm/xorm/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/vendor/github.com/go-xorm/xorm/session_sum.go b/vendor/github.com/go-xorm/xorm/session_sum.go new file mode 100644 index 000000000..127f83f28 --- /dev/null +++ b/vendor/github.com/go-xorm/xorm/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/vendor/github.com/go-xorm/xorm/session_tx.go b/vendor/github.com/go-xorm/xorm/session_tx.go new file mode 100644 index 000000000..302bc104d --- /dev/null +++ b/vendor/github.com/go-xorm/xorm/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/vendor/github.com/go-xorm/xorm/session_update.go b/vendor/github.com/go-xorm/xorm/session_update.go new file mode 100644 index 000000000..17e5672ed --- /dev/null +++ b/vendor/github.com/go-xorm/xorm/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/vendor/github.com/go-xorm/xorm/sqlite3_dialect.go b/vendor/github.com/go-xorm/xorm/sqlite3_dialect.go index afc51b84c..4cc021167 100644 --- a/vendor/github.com/go-xorm/xorm/sqlite3_dialect.go +++ b/vendor/github.com/go-xorm/xorm/sqlite3_dialect.go @@ -386,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") diff --git a/vendor/github.com/go-xorm/xorm/statement.go b/vendor/github.com/go-xorm/xorm/statement.go index 07fa14bfa..2989114ff 100644 --- a/vendor/github.com/go-xorm/xorm/statement.go +++ b/vendor/github.com/go-xorm/xorm/statement.go @@ -258,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 } @@ -273,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 { @@ -284,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) @@ -498,7 +497,7 @@ func buildConds(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() { @@ -523,7 +522,11 @@ func buildConds(engine *Engine, table *core.Table, bean interface{}, } if col.IsDeleted && !unscoped { // tag "deleted" is enabled - conds = append(conds, builder.IsNull{colName}.Or(builder.Eq{colName: "0001-01-01 00:00:00"})) + 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 @@ -533,7 +536,8 @@ func buildConds(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 { @@ -703,7 +707,14 @@ func (statement *Statement) TableName() string { } // Id generate "where id = ? " statment 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 = ? " statment or for composite key "where key1 = ? and key2 = ?" +func (statement *Statement) ID(id interface{}) *Statement { idValue := reflect.ValueOf(id) idType := reflect.TypeOf(idValue.Interface()) @@ -985,15 +996,16 @@ func (statement *Statement) Unscoped() *Statement { } func (statement *Statement) genColumnStr() string { - 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 } } @@ -1102,7 +1114,7 @@ func (statement *Statement) genConds(bean interface{}) (string, []interface{}, e statement.cond = statement.cond.And(autoCond) } - statement.processIdParam() + statement.processIDParam() return builder.ToSQL(statement.cond) } @@ -1144,14 +1156,15 @@ func (statement *Statement) genCountSQL(bean interface{}) (string, []interface{} condSQL, condArgs, _ := statement.genConds(bean) - var selectSql = statement.selectStr - if len(selectSql) <= 0 { + var selectSQL = statement.selectStr + if len(selectSQL) <= 0 { if statement.IsDistinct { - selectSql = fmt.Sprintf("count(DISTINCT %s)", statement.ColumnStr) + selectSQL = fmt.Sprintf("count(DISTINCT %s)", statement.ColumnStr) + } else { + selectSQL = "count(*)" } - selectSql = "count(*)" } - return statement.genSelectSQL(selectSql, condSQL), append(statement.joinArgs, condArgs...) + return statement.genSelectSQL(selectSQL, condSQL), append(statement.joinArgs, condArgs...) } func (statement *Statement) genSumSQL(bean interface{}, columns ...string) (string, []interface{}) { @@ -1178,7 +1191,7 @@ func (statement *Statement) genSelectSQL(columnStr, condSQL string) (a string) { var top string var mssqlCondi string - statement.processIdParam() + statement.processIDParam() var buf bytes.Buffer if len(condSQL) > 0 { @@ -1275,7 +1288,7 @@ func (statement *Statement) genSelectSQL(columnStr, condSQL string) (a string) { return } -func (statement *Statement) processIdParam() { +func (statement *Statement) processIDParam() { if statement.IdParam == nil { return } @@ -1316,7 +1329,12 @@ func (statement *Statement) convertIDSQL(sqlStr string) string { return "" } - return fmt.Sprintf("SELECT %s FROM %v", colstrs, 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%s FROM %v", top, colstrs, sqls[1]) } return "" } diff --git a/vendor/github.com/go-xorm/xorm/syslogger.go b/vendor/github.com/go-xorm/xorm/syslogger.go index 799bcca3c..8840635d4 100644 --- a/vendor/github.com/go-xorm/xorm/syslogger.go +++ b/vendor/github.com/go-xorm/xorm/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/vendor/vendor.json b/vendor/vendor.json index 61e4e5fc6..190435071 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -207,16 +207,16 @@ "revisionTime": "2016-11-01T11:13:14Z" }, { - "checksumSHA1": "PPOvZWzZLvyb0lB4efMAHW9qkd8=", + "checksumSHA1": "sDRC697KuCbpI+1i2VPhyqgAvjs=", "path": "github.com/go-xorm/builder", - "revision": "cd42e8337f3031e737c3f415b6354793cf30f1c9", - "revisionTime": "2016-10-21T06:05:34Z" + "revision": "db75972580de4a7c6c20fff5b16a924c3de3fa12", + "revisionTime": "2016-12-14T02:05:24Z" }, { - "checksumSHA1": "ex5USc7tJNNS0nW/O+/69/kMHtM=", + "checksumSHA1": "T1DSQqU9e7oW0up0W55TkG4Cb7M=", "path": "github.com/go-xorm/core", - "revision": "2ec3936f8a8a3fb6397c6c10185acffa87488534", - "revisionTime": "2016-11-01T01:36:51Z" + "revision": "60b1704516c3af6ff969d81f359cf310ad43a2e0", + "revisionTime": "2017-01-03T06:17:17Z" }, { "checksumSHA1": "k52lEKLp8j5M+jFpe+3u+bIFpxQ=", @@ -225,10 +225,10 @@ "revisionTime": "2016-08-11T02:11:45Z" }, { - "checksumSHA1": "eOdqyQnmqdF0VZpBgYoOaYQDaiw=", + "checksumSHA1": "eEVm8NprkjOX2HZVxIJwbOLD5Hg=", "path": "github.com/go-xorm/xorm", - "revision": "f2610e02a1a1240d663ebe5bcde6c9ad4f1372cb", - "revisionTime": "2016-11-11T16:13:12Z" + "revision": "44b70cccf417e3d4a565aaa3128853b5cd553767", + "revisionTime": "2017-01-03T05:31:47Z" }, { "checksumSHA1": "1ft/4j5MFa7C9dPI9whL03HSUzk=",