xorm/internal/statements/statement.go

722 lines
21 KiB
Go
Raw Normal View History

2015-04-28 08:25:04 +00:00
// Copyright 2015 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 statements
2013-05-03 07:26:51 +00:00
import (
"database/sql/driver"
"errors"
2013-12-18 03:31:32 +00:00
"fmt"
"math/big"
2013-12-18 03:31:32 +00:00
"reflect"
"strings"
"time"
2014-01-07 09:33:27 +00:00
"xorm.io/builder"
2023-10-27 07:32:38 +00:00
"xorm.io/xorm/v2/contexts"
"xorm.io/xorm/v2/convert"
"xorm.io/xorm/v2/dialects"
"xorm.io/xorm/v2/internal/json"
"xorm.io/xorm/v2/internal/utils"
"xorm.io/xorm/v2/schemas"
"xorm.io/xorm/v2/tags"
)
var (
// ErrConditionType condition type unsupported
ErrConditionType = errors.New("Unsupported condition type")
// ErrUnSupportedSQLType parameter of SQL is not supported
ErrUnSupportedSQLType = errors.New("Unsupported sql type")
// ErrUnSupportedType unsupported error
ErrUnSupportedType = errors.New("Unsupported type error")
// ErrTableNotFound table not found error
ErrTableNotFound = errors.New("Table not found")
2013-05-03 07:26:51 +00:00
)
type join struct {
op string
table interface{}
condition interface{}
args []interface{}
}
// Statement save all the sql info for executing SQL
2013-05-03 07:26:51 +00:00
type Statement struct {
RefTable *schemas.Table
dialect dialects.Dialect
defaultTimeZone *time.Location
tagParser *tags.Parser
2016-01-02 15:55:01 +00:00
Start int
fix statement.LimitN(0) will delete or update all data (#1119) fix test fix nil pointer fix statement.Limit(0) will update or delete all data fix bug when buffersize with iterate (#941) Merge branch 'master' into lunny/fix_buffer_iterate Exclude schema from index name (#1505) Merge branch 'master' into fix-schema-idx SetExpr support more go types (#1499) Improve tests SetExpr support more go types fix vet fix drone lint remove go1.10 test on drone Reviewed-on: https://gitea.com/xorm/xorm/pulls/1499 fix vet fix drone lint remove go1.10 test on drone Exclude schema from the index name Co-authored-by: Guillermo Prandi <guillep2k@users.noreply.github.com> Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com> Reviewed-on: https://gitea.com/xorm/xorm/pulls/1505 fix test fix bug fix bug when buffersize with iterate SetExpr support more go types (#1499) Improve tests SetExpr support more go types fix vet fix drone lint remove go1.10 test on drone Reviewed-on: https://gitea.com/xorm/xorm/pulls/1499 fix vet fix drone lint remove go1.10 test on drone Fix update with Alias (#1455) Co-authored-by: Guillermo Prandi <guillep2k@noreply.gitea.io> Reviewed-on: https://gitea.com/xorm/xorm/pulls/941 fix update map with version (#1448) fix test fix update map with version SetExpr support more go types (#1499) Improve tests SetExpr support more go types fix vet fix drone lint remove go1.10 test on drone Reviewed-on: https://gitea.com/xorm/xorm/pulls/1499 fix vet fix drone lint remove go1.10 test on drone Fix update with Alias (#1455) Reviewed-on: https://gitea.com/xorm/xorm/pulls/1448 Exclude schema from index name (#1505) Merge branch 'master' into fix-schema-idx SetExpr support more go types (#1499) Improve tests SetExpr support more go types fix vet fix drone lint remove go1.10 test on drone Reviewed-on: https://gitea.com/xorm/xorm/pulls/1499 fix vet fix drone lint remove go1.10 test on drone Exclude schema from the index name Co-authored-by: Guillermo Prandi <guillep2k@users.noreply.github.com> Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com> Reviewed-on: https://gitea.com/xorm/xorm/pulls/1505 SetExpr support more go types (#1499) Improve tests SetExpr support more go types fix vet fix drone lint remove go1.10 test on drone Reviewed-on: https://gitea.com/xorm/xorm/pulls/1499 fix vet fix drone lint remove go1.10 test on drone Fix update with Alias (#1455) Improve ci tests (#1477) Rewrite Engine.QuoteTo() to accept multi-part identifiers (#1476) Support local sql log (#1338) Fix go mod and update version (#1460) Move github.com/go-xorm/xorm to xorm.io/xorm (#1459) add support custom type Nullfloat64 (#1450) fix bug when query map condtion with no quote (#1449) Don't warn when bool column default is 1 but not true (#1447) * don't warn when bool column default is 1 but not true * fix default case sensitive Fix sync2 with custom table name (#1445) * fix sync2 with custom table name * fix bug on postgres * fix bug on postgres fix bug when update with setexpr (#1446) add tidb tests on drone ci (#1444) improve sync2 (#1443) Fix wrong dbmetas (#1442) * add tests for db metas * add more tests * fix bug on mssql Fix default value parse bugs (#1437) * fix default value * fix default value tags * fix postgres default * fix default on postgres * fix default on postgres * fix mssql default fix arg conversion (#1441) * fix arg conversion * fix bugs * fix bug on postgres * use traditional positional parameters on insert into select * remove unnecessary tests upgrade core (#1440) add tests (#1439) add go1.13 tests on drone (#1416) Fix bug on insert where (#1436) * fix bug on insert where * fix bug * fix lint fix bug when insert multiple slices with customize table name (#1433) * fix bug when insert multiple slices with customize table name * fix tests on mssql * fix tests fix insert where with bool bug on mssql (#1432) fix setexpr missing big quotes (#1431) * fix setexpr missing big quotes * fix tests * fix tests Add support subquery on SetExpr (#1428) * add support subquery on SetExpr * fix tests fix go mod (#1427) fix tests (#1429) Use strings.Builder instead of builder.StringBuilder (#1417) * use strings.Builder instead of builder.StringBuilder * fix dependency * fix dependency Remove unuse get cols code (#1413) Add mssql ci test (#1410) * add mssql ci test * fix drone test Add insert select where support (#1401) Use drone new format (#1388) * use drone new format fix get customize type bug (#1382) fix bugs (#1375) update drone (#1374) Add tests for get var (#1305) * add test for SQL get * fix tests fix error when get null var (#890) * fix error when get null var * add support get for null var * fix bug Remove quotestr totally (#1366) * remove QuoteStr() totally * update xorm.core -> v0.7.0 * update dialect Quote remove QuoteStr() usage in dialects (#1364) document of FindAndCount() (#1365) remove QuoteStr() usage (#1360) Co-authored-by: yifhao <yifhao@tencent.com> Co-authored-by: yifhao <1124210681@qq.com> Co-authored-by: Guillermo Prandi <guillep2k@noreply.gitea.io> Co-authored-by: Guillermo Prandi <guillep2k@noreply@gitea.io> Co-authored-by: yudppp <yu.d.ppp@gmail.com> Co-authored-by: BetaCat <outman99@hotmail.com> Reviewed-on: https://gitea.com/xorm/xorm/pulls/1119
2020-01-20 08:22:06 +00:00
LimitN *int
idParam schemas.PK
orderBy []orderBy
joins []join
2016-01-02 15:55:01 +00:00
GroupByStr string
HavingStr string
SelectStr string
2016-01-02 15:55:01 +00:00
useAllCols bool
AltTableName string
tableName string
2016-01-02 15:55:01 +00:00
RawSQL string
RawParams []interface{}
UseCascade bool
UseAutoJoin bool
StoreEngine string
Charset string
UseCache bool
UseAutoTime bool
NoAutoCondition bool
2016-01-02 15:55:01 +00:00
IsDistinct bool
IsForUpdate bool
TableAlias string
allUseBool bool
CheckVersion bool
2016-01-02 15:55:01 +00:00
unscoped bool
ColumnMap columnMap
OmitColumnMap columnMap
MustColumnMap map[string]bool
NullableMap map[string]bool
IncrColumns exprParams
DecrColumns exprParams
ExprColumns exprParams
cond builder.Cond
BufferSize int
Context contexts.ContextCache
LastError error
2013-05-03 07:26:51 +00:00
}
// NewStatement creates a new statement
func NewStatement(dialect dialects.Dialect, tagParser *tags.Parser, defaultTimeZone *time.Location) *Statement {
statement := &Statement{
dialect: dialect,
tagParser: tagParser,
defaultTimeZone: defaultTimeZone,
}
statement.Reset()
return statement
}
// SetTableName set table name
func (statement *Statement) SetTableName(tableName string) {
statement.tableName = tableName
}
// GenRawSQL generates correct raw sql
func (statement *Statement) GenRawSQL() string {
return statement.ReplaceQuote(statement.RawSQL)
}
// ReplaceQuote replace sql key words with quote
func (statement *Statement) ReplaceQuote(sql string) string {
if sql == "" || statement.dialect.URI().DBType == schemas.MYSQL ||
statement.dialect.URI().DBType == schemas.SQLITE {
return sql
}
return statement.dialect.Quoter().Replace(sql)
}
// SetContextCache sets context cache
func (statement *Statement) SetContextCache(ctxCache contexts.ContextCache) {
statement.Context = ctxCache
}
// Reset reset all the statement's fields
func (statement *Statement) Reset() {
2013-12-18 03:31:32 +00:00
statement.RefTable = nil
statement.Start = 0
fix statement.LimitN(0) will delete or update all data (#1119) fix test fix nil pointer fix statement.Limit(0) will update or delete all data fix bug when buffersize with iterate (#941) Merge branch 'master' into lunny/fix_buffer_iterate Exclude schema from index name (#1505) Merge branch 'master' into fix-schema-idx SetExpr support more go types (#1499) Improve tests SetExpr support more go types fix vet fix drone lint remove go1.10 test on drone Reviewed-on: https://gitea.com/xorm/xorm/pulls/1499 fix vet fix drone lint remove go1.10 test on drone Exclude schema from the index name Co-authored-by: Guillermo Prandi <guillep2k@users.noreply.github.com> Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com> Reviewed-on: https://gitea.com/xorm/xorm/pulls/1505 fix test fix bug fix bug when buffersize with iterate SetExpr support more go types (#1499) Improve tests SetExpr support more go types fix vet fix drone lint remove go1.10 test on drone Reviewed-on: https://gitea.com/xorm/xorm/pulls/1499 fix vet fix drone lint remove go1.10 test on drone Fix update with Alias (#1455) Co-authored-by: Guillermo Prandi <guillep2k@noreply.gitea.io> Reviewed-on: https://gitea.com/xorm/xorm/pulls/941 fix update map with version (#1448) fix test fix update map with version SetExpr support more go types (#1499) Improve tests SetExpr support more go types fix vet fix drone lint remove go1.10 test on drone Reviewed-on: https://gitea.com/xorm/xorm/pulls/1499 fix vet fix drone lint remove go1.10 test on drone Fix update with Alias (#1455) Reviewed-on: https://gitea.com/xorm/xorm/pulls/1448 Exclude schema from index name (#1505) Merge branch 'master' into fix-schema-idx SetExpr support more go types (#1499) Improve tests SetExpr support more go types fix vet fix drone lint remove go1.10 test on drone Reviewed-on: https://gitea.com/xorm/xorm/pulls/1499 fix vet fix drone lint remove go1.10 test on drone Exclude schema from the index name Co-authored-by: Guillermo Prandi <guillep2k@users.noreply.github.com> Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com> Reviewed-on: https://gitea.com/xorm/xorm/pulls/1505 SetExpr support more go types (#1499) Improve tests SetExpr support more go types fix vet fix drone lint remove go1.10 test on drone Reviewed-on: https://gitea.com/xorm/xorm/pulls/1499 fix vet fix drone lint remove go1.10 test on drone Fix update with Alias (#1455) Improve ci tests (#1477) Rewrite Engine.QuoteTo() to accept multi-part identifiers (#1476) Support local sql log (#1338) Fix go mod and update version (#1460) Move github.com/go-xorm/xorm to xorm.io/xorm (#1459) add support custom type Nullfloat64 (#1450) fix bug when query map condtion with no quote (#1449) Don't warn when bool column default is 1 but not true (#1447) * don't warn when bool column default is 1 but not true * fix default case sensitive Fix sync2 with custom table name (#1445) * fix sync2 with custom table name * fix bug on postgres * fix bug on postgres fix bug when update with setexpr (#1446) add tidb tests on drone ci (#1444) improve sync2 (#1443) Fix wrong dbmetas (#1442) * add tests for db metas * add more tests * fix bug on mssql Fix default value parse bugs (#1437) * fix default value * fix default value tags * fix postgres default * fix default on postgres * fix default on postgres * fix mssql default fix arg conversion (#1441) * fix arg conversion * fix bugs * fix bug on postgres * use traditional positional parameters on insert into select * remove unnecessary tests upgrade core (#1440) add tests (#1439) add go1.13 tests on drone (#1416) Fix bug on insert where (#1436) * fix bug on insert where * fix bug * fix lint fix bug when insert multiple slices with customize table name (#1433) * fix bug when insert multiple slices with customize table name * fix tests on mssql * fix tests fix insert where with bool bug on mssql (#1432) fix setexpr missing big quotes (#1431) * fix setexpr missing big quotes * fix tests * fix tests Add support subquery on SetExpr (#1428) * add support subquery on SetExpr * fix tests fix go mod (#1427) fix tests (#1429) Use strings.Builder instead of builder.StringBuilder (#1417) * use strings.Builder instead of builder.StringBuilder * fix dependency * fix dependency Remove unuse get cols code (#1413) Add mssql ci test (#1410) * add mssql ci test * fix drone test Add insert select where support (#1401) Use drone new format (#1388) * use drone new format fix get customize type bug (#1382) fix bugs (#1375) update drone (#1374) Add tests for get var (#1305) * add test for SQL get * fix tests fix error when get null var (#890) * fix error when get null var * add support get for null var * fix bug Remove quotestr totally (#1366) * remove QuoteStr() totally * update xorm.core -> v0.7.0 * update dialect Quote remove QuoteStr() usage in dialects (#1364) document of FindAndCount() (#1365) remove QuoteStr() usage (#1360) Co-authored-by: yifhao <yifhao@tencent.com> Co-authored-by: yifhao <1124210681@qq.com> Co-authored-by: Guillermo Prandi <guillep2k@noreply.gitea.io> Co-authored-by: Guillermo Prandi <guillep2k@noreply@gitea.io> Co-authored-by: yudppp <yu.d.ppp@gmail.com> Co-authored-by: BetaCat <outman99@hotmail.com> Reviewed-on: https://gitea.com/xorm/xorm/pulls/1119
2020-01-20 08:22:06 +00:00
statement.LimitN = nil
statement.ResetOrderBy()
2013-12-18 03:31:32 +00:00
statement.UseCascade = true
statement.joins = nil
2013-12-18 03:31:32 +00:00
statement.GroupByStr = ""
statement.HavingStr = ""
statement.ColumnMap = columnMap{}
statement.OmitColumnMap = columnMap{}
2013-12-18 03:31:32 +00:00
statement.AltTableName = ""
statement.tableName = ""
2017-03-01 08:55:53 +00:00
statement.idParam = nil
2013-12-18 03:31:32 +00:00
statement.RawSQL = ""
statement.RawParams = make([]interface{}, 0)
2014-01-07 09:33:27 +00:00
statement.UseCache = true
2013-12-18 03:31:32 +00:00
statement.UseAutoTime = true
statement.NoAutoCondition = false
2013-12-18 03:31:32 +00:00
statement.IsDistinct = false
statement.IsForUpdate = false
2014-11-18 16:41:03 +00:00
statement.TableAlias = ""
statement.SelectStr = ""
2013-12-18 03:31:32 +00:00
statement.allUseBool = false
statement.useAllCols = false
statement.MustColumnMap = make(map[string]bool)
statement.NullableMap = make(map[string]bool)
statement.CheckVersion = true
statement.unscoped = false
statement.IncrColumns = exprParams{}
statement.DecrColumns = exprParams{}
statement.ExprColumns = exprParams{}
statement.cond = builder.NewCond()
statement.BufferSize = 0
statement.Context = nil
statement.LastError = nil
2013-05-06 08:01:17 +00:00
}
// SQL adds raw sql statement
func (statement *Statement) SQL(query interface{}, args ...interface{}) *Statement {
switch t := query.(type) {
case (*builder.Builder):
var err error
statement.RawSQL, statement.RawParams, err = t.ToSQL()
if err != nil {
statement.LastError = err
}
case string:
statement.RawSQL = t
statement.RawParams = args
default:
statement.LastError = ErrUnSupportedSQLType
}
2013-12-18 03:31:32 +00:00
return statement
2013-06-16 03:05:16 +00:00
}
func (statement *Statement) quote(s string) string {
return statement.dialect.Quoter().Quote(s)
}
// SetRefValue set ref value
func (statement *Statement) SetRefValue(v reflect.Value) error {
2017-04-02 10:02:47 +00:00
var err error
statement.RefTable, err = statement.tagParser.ParseWithCache(reflect.Indirect(v))
2017-04-02 10:02:47 +00:00
if err != nil {
return err
}
statement.tableName = dialects.FullTableName(statement.dialect, statement.tagParser.GetTableMapper(), v, true)
2017-04-02 10:02:47 +00:00
return nil
}
func rValue(bean interface{}) reflect.Value {
return reflect.Indirect(reflect.ValueOf(bean))
}
// SetRefBean set ref bean
func (statement *Statement) SetRefBean(bean interface{}) error {
var err error
statement.RefTable, err = statement.tagParser.ParseWithCache(rValue(bean))
if err != nil {
return err
2013-12-18 03:31:32 +00:00
}
statement.tableName = dialects.FullTableName(statement.dialect, statement.tagParser.GetTableMapper(), bean, true)
return nil
2013-05-19 05:25:52 +00:00
}
func (statement *Statement) NeedTableName() bool {
return len(statement.joins) > 0
}
2017-01-09 01:52:23 +00:00
// Incr Generate "Update ... Set column = column + arg" statement
2014-04-15 04:14:18 +00:00
func (statement *Statement) Incr(column string, arg ...interface{}) *Statement {
if len(arg) > 0 {
statement.IncrColumns.Add(column, arg[0])
2014-04-15 04:14:18 +00:00
} else {
statement.IncrColumns.Add(column, 1)
2014-04-15 04:14:18 +00:00
}
2013-12-18 03:31:32 +00:00
return statement
2013-05-09 01:56:58 +00:00
}
2017-01-09 01:52:23 +00:00
// Decr Generate "Update ... Set column = column - arg" statement
2014-07-15 15:25:24 +00:00
func (statement *Statement) Decr(column string, arg ...interface{}) *Statement {
if len(arg) > 0 {
statement.DecrColumns.Add(column, arg[0])
2014-07-15 15:25:24 +00:00
} else {
statement.DecrColumns.Add(column, 1)
2014-07-15 15:25:24 +00:00
}
return statement
}
2017-01-09 01:52:23 +00:00
// SetExpr Generate "Update ... Set column = {expression}" statement
func (statement *Statement) SetExpr(column string, expression interface{}) *Statement {
if e, ok := expression.(string); ok {
statement.ExprColumns.Add(column, statement.dialect.Quoter().Replace(e))
} else {
statement.ExprColumns.Add(column, expression)
}
2015-01-28 05:23:01 +00:00
return statement
}
// ForUpdate generates "SELECT ... FOR UPDATE" statement
func (statement *Statement) ForUpdate() *Statement {
statement.IsForUpdate = true
return statement
}
2016-04-07 00:54:02 +00:00
// Nullable Update use only: update columns to null when value is nullable and zero-value
func (statement *Statement) Nullable(columns ...string) {
newColumns := col2NewCols(columns...)
for _, nc := range newColumns {
statement.NullableMap[strings.ToLower(nc)] = true
}
}
2016-04-07 00:54:02 +00:00
// Top generate LIMIT limit statement
2013-11-20 17:26:07 +00:00
func (statement *Statement) Top(limit int) *Statement {
2013-12-18 03:31:32 +00:00
statement.Limit(limit)
return statement
2013-11-20 17:26:07 +00:00
}
2016-04-07 00:54:02 +00:00
// Limit generate LIMIT start, limit statement
2013-11-22 06:11:07 +00:00
func (statement *Statement) Limit(limit int, start ...int) *Statement {
fix statement.LimitN(0) will delete or update all data (#1119) fix test fix nil pointer fix statement.Limit(0) will update or delete all data fix bug when buffersize with iterate (#941) Merge branch 'master' into lunny/fix_buffer_iterate Exclude schema from index name (#1505) Merge branch 'master' into fix-schema-idx SetExpr support more go types (#1499) Improve tests SetExpr support more go types fix vet fix drone lint remove go1.10 test on drone Reviewed-on: https://gitea.com/xorm/xorm/pulls/1499 fix vet fix drone lint remove go1.10 test on drone Exclude schema from the index name Co-authored-by: Guillermo Prandi <guillep2k@users.noreply.github.com> Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com> Reviewed-on: https://gitea.com/xorm/xorm/pulls/1505 fix test fix bug fix bug when buffersize with iterate SetExpr support more go types (#1499) Improve tests SetExpr support more go types fix vet fix drone lint remove go1.10 test on drone Reviewed-on: https://gitea.com/xorm/xorm/pulls/1499 fix vet fix drone lint remove go1.10 test on drone Fix update with Alias (#1455) Co-authored-by: Guillermo Prandi <guillep2k@noreply.gitea.io> Reviewed-on: https://gitea.com/xorm/xorm/pulls/941 fix update map with version (#1448) fix test fix update map with version SetExpr support more go types (#1499) Improve tests SetExpr support more go types fix vet fix drone lint remove go1.10 test on drone Reviewed-on: https://gitea.com/xorm/xorm/pulls/1499 fix vet fix drone lint remove go1.10 test on drone Fix update with Alias (#1455) Reviewed-on: https://gitea.com/xorm/xorm/pulls/1448 Exclude schema from index name (#1505) Merge branch 'master' into fix-schema-idx SetExpr support more go types (#1499) Improve tests SetExpr support more go types fix vet fix drone lint remove go1.10 test on drone Reviewed-on: https://gitea.com/xorm/xorm/pulls/1499 fix vet fix drone lint remove go1.10 test on drone Exclude schema from the index name Co-authored-by: Guillermo Prandi <guillep2k@users.noreply.github.com> Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com> Reviewed-on: https://gitea.com/xorm/xorm/pulls/1505 SetExpr support more go types (#1499) Improve tests SetExpr support more go types fix vet fix drone lint remove go1.10 test on drone Reviewed-on: https://gitea.com/xorm/xorm/pulls/1499 fix vet fix drone lint remove go1.10 test on drone Fix update with Alias (#1455) Improve ci tests (#1477) Rewrite Engine.QuoteTo() to accept multi-part identifiers (#1476) Support local sql log (#1338) Fix go mod and update version (#1460) Move github.com/go-xorm/xorm to xorm.io/xorm (#1459) add support custom type Nullfloat64 (#1450) fix bug when query map condtion with no quote (#1449) Don't warn when bool column default is 1 but not true (#1447) * don't warn when bool column default is 1 but not true * fix default case sensitive Fix sync2 with custom table name (#1445) * fix sync2 with custom table name * fix bug on postgres * fix bug on postgres fix bug when update with setexpr (#1446) add tidb tests on drone ci (#1444) improve sync2 (#1443) Fix wrong dbmetas (#1442) * add tests for db metas * add more tests * fix bug on mssql Fix default value parse bugs (#1437) * fix default value * fix default value tags * fix postgres default * fix default on postgres * fix default on postgres * fix mssql default fix arg conversion (#1441) * fix arg conversion * fix bugs * fix bug on postgres * use traditional positional parameters on insert into select * remove unnecessary tests upgrade core (#1440) add tests (#1439) add go1.13 tests on drone (#1416) Fix bug on insert where (#1436) * fix bug on insert where * fix bug * fix lint fix bug when insert multiple slices with customize table name (#1433) * fix bug when insert multiple slices with customize table name * fix tests on mssql * fix tests fix insert where with bool bug on mssql (#1432) fix setexpr missing big quotes (#1431) * fix setexpr missing big quotes * fix tests * fix tests Add support subquery on SetExpr (#1428) * add support subquery on SetExpr * fix tests fix go mod (#1427) fix tests (#1429) Use strings.Builder instead of builder.StringBuilder (#1417) * use strings.Builder instead of builder.StringBuilder * fix dependency * fix dependency Remove unuse get cols code (#1413) Add mssql ci test (#1410) * add mssql ci test * fix drone test Add insert select where support (#1401) Use drone new format (#1388) * use drone new format fix get customize type bug (#1382) fix bugs (#1375) update drone (#1374) Add tests for get var (#1305) * add test for SQL get * fix tests fix error when get null var (#890) * fix error when get null var * add support get for null var * fix bug Remove quotestr totally (#1366) * remove QuoteStr() totally * update xorm.core -> v0.7.0 * update dialect Quote remove QuoteStr() usage in dialects (#1364) document of FindAndCount() (#1365) remove QuoteStr() usage (#1360) Co-authored-by: yifhao <yifhao@tencent.com> Co-authored-by: yifhao <1124210681@qq.com> Co-authored-by: Guillermo Prandi <guillep2k@noreply.gitea.io> Co-authored-by: Guillermo Prandi <guillep2k@noreply@gitea.io> Co-authored-by: yudppp <yu.d.ppp@gmail.com> Co-authored-by: BetaCat <outman99@hotmail.com> Reviewed-on: https://gitea.com/xorm/xorm/pulls/1119
2020-01-20 08:22:06 +00:00
statement.LimitN = &limit
2013-12-18 03:31:32 +00:00
if len(start) > 0 {
statement.Start = start[0]
}
return statement
2013-05-03 07:26:51 +00:00
}
// SetTable tempororily set table name, the parameter could be a string or a pointer of struct
func (statement *Statement) SetTable(tableNameOrBean interface{}) error {
v := rValue(tableNameOrBean)
t := v.Type()
if t.Kind() == reflect.Struct {
var err error
statement.RefTable, err = statement.tagParser.ParseWithCache(v)
if err != nil {
return err
}
}
statement.AltTableName = dialects.FullTableName(statement.dialect, statement.tagParser.GetTableMapper(), tableNameOrBean, true)
return nil
}
2016-04-07 00:54:02 +00:00
// GroupBy generate "Group By keys" statement
2013-11-22 06:11:07 +00:00
func (statement *Statement) GroupBy(keys string) *Statement {
statement.GroupByStr = statement.ReplaceQuote(keys)
2013-12-18 03:31:32 +00:00
return statement
2013-05-06 08:01:17 +00:00
}
func (statement *Statement) writeGroupBy(w *builder.BytesWriter) error {
if statement.GroupByStr == "" {
return nil
}
_, err := fmt.Fprint(w, " GROUP BY ", statement.GroupByStr)
return err
}
2016-04-07 00:54:02 +00:00
// Having generate "Having conditions" statement
2013-11-22 06:11:07 +00:00
func (statement *Statement) Having(conditions string) *Statement {
statement.HavingStr = conditions
2013-12-18 03:31:32 +00:00
return statement
2013-05-03 07:26:51 +00:00
}
func (statement *Statement) writeHaving(w *builder.BytesWriter) error {
if statement.HavingStr == "" {
return nil
}
_, err := fmt.Fprint(w, " HAVING ", statement.ReplaceQuote(statement.HavingStr))
return err
}
// SetUnscoped always disable struct tag "deleted"
func (statement *Statement) SetUnscoped() *Statement {
statement.unscoped = true
return statement
}
// GetUnscoped return true if it's unscoped
func (statement *Statement) GetUnscoped() bool {
return statement.unscoped
}
// GenIndexSQL generated create index SQL
func (statement *Statement) GenIndexSQL() []string {
var sqls []string
tbName := statement.TableName()
for _, index := range statement.RefTable.Indexes {
if index.Type == schemas.IndexType {
sql := statement.dialect.CreateIndexSQL(tbName, index)
2013-12-18 03:31:32 +00:00
sqls = append(sqls, sql)
}
}
return sqls
}
// GenUniqueSQL generates unique SQL
func (statement *Statement) GenUniqueSQL() []string {
var sqls []string
tbName := statement.TableName()
for _, index := range statement.RefTable.Indexes {
if index.Type == schemas.UniqueType {
sql := statement.dialect.CreateIndexSQL(tbName, index)
2013-12-18 03:31:32 +00:00
sqls = append(sqls, sql)
}
}
return sqls
2013-05-19 05:25:52 +00:00
}
// GenDelIndexSQL generate delete index SQL
func (statement *Statement) GenDelIndexSQL() []string {
var sqls []string
tbName := statement.TableName()
idx := strings.Index(tbName, ".")
if idx > -1 {
tbName = tbName[idx+1:]
}
for _, index := range statement.RefTable.Indexes {
sqls = append(sqls, statement.dialect.DropIndexSQL(tbName, index))
2013-12-18 03:31:32 +00:00
}
return sqls
2013-09-26 07:19:39 +00:00
}
func (statement *Statement) asDBCond(fieldValue reflect.Value, fieldType reflect.Type, col *schemas.Column, allUseBool, requiredField bool) (interface{}, bool, error) {
switch fieldType.Kind() {
case reflect.Ptr:
if fieldValue.IsNil() {
return nil, true, nil
}
return statement.asDBCond(fieldValue.Elem(), fieldType.Elem(), col, allUseBool, requiredField)
case reflect.Bool:
if allUseBool || requiredField {
return fieldValue.Interface(), true, nil
}
// if a bool in a struct, it will not be as a condition because it default is false,
// please use Where() instead
return nil, false, nil
case reflect.String:
if !requiredField && fieldValue.String() == "" {
return nil, false, nil
}
// for MyString, should convert to string or panic
if fieldType.String() != reflect.String.String() {
return fieldValue.String(), true, nil
}
return fieldValue.Interface(), true, nil
case reflect.Int8, reflect.Int16, reflect.Int, reflect.Int32, reflect.Int64:
if !requiredField && fieldValue.Int() == 0 {
return nil, false, nil
}
return fieldValue.Interface(), true, nil
case reflect.Float32, reflect.Float64:
if !requiredField && fieldValue.Float() == 0.0 {
return nil, false, nil
}
return fieldValue.Interface(), true, nil
case reflect.Uint8, reflect.Uint16, reflect.Uint, reflect.Uint32, reflect.Uint64:
if !requiredField && fieldValue.Uint() == 0 {
return nil, false, nil
}
return fieldValue.Interface(), true, nil
case reflect.Struct:
if fieldType.ConvertibleTo(schemas.TimeType) {
t := fieldValue.Convert(schemas.TimeType).Interface().(time.Time)
if !requiredField && (t.IsZero() || !fieldValue.IsValid()) {
return nil, false, nil
}
res, err := dialects.FormatColumnTime(statement.dialect, statement.defaultTimeZone, col, t)
if err != nil {
return nil, false, err
}
return res, true, nil
} else if fieldType.ConvertibleTo(schemas.BigFloatType) {
t := fieldValue.Convert(schemas.BigFloatType).Interface().(big.Float)
v := t.String()
if v == "0" {
return nil, false, nil
}
return t.String(), true, nil
} else if _, ok := reflect.New(fieldType).Interface().(convert.Conversion); ok {
return nil, false, nil
} else if valNul, ok := fieldValue.Interface().(driver.Valuer); ok {
val, _ := valNul.Value()
if val == nil && !requiredField {
return nil, false, nil
}
return val, true, nil
} else {
if col.IsJSON {
if col.SQLType.IsText() {
bytes, err := json.DefaultJSONHandler.Marshal(fieldValue.Interface())
if err != nil {
return nil, false, err
}
return string(bytes), true, nil
} else if col.SQLType.IsBlob() {
var bytes []byte
var err error
bytes, err = json.DefaultJSONHandler.Marshal(fieldValue.Interface())
if err != nil {
return nil, false, err
}
return bytes, true, nil
}
} else {
table, err := statement.tagParser.ParseWithCache(fieldValue)
if err != nil {
return fieldValue.Interface(), true, nil
}
if len(table.PrimaryKeys) == 1 {
pkField := reflect.Indirect(fieldValue).FieldByName(table.PKColumns()[0].FieldName)
// fix non-int pk issues
// if pkField.Int() != 0 {
if pkField.IsValid() && !utils.IsZero(pkField.Interface()) {
return pkField.Interface(), true, nil
}
return nil, false, nil
}
return nil, false, fmt.Errorf("not supported %v as %v", fieldValue.Interface(), table.PrimaryKeys)
}
}
case reflect.Array:
return nil, false, nil
case reflect.Slice, reflect.Map:
if fieldValue == reflect.Zero(fieldType) {
return nil, false, nil
}
if fieldValue.IsNil() || !fieldValue.IsValid() || fieldValue.Len() == 0 {
return nil, false, nil
}
if col.SQLType.IsText() {
bytes, err := json.DefaultJSONHandler.Marshal(fieldValue.Interface())
if err != nil {
return nil, false, err
}
return string(bytes), true, nil
} else if col.SQLType.IsBlob() {
var bytes []byte
var err error
if (fieldType.Kind() == reflect.Array || fieldType.Kind() == reflect.Slice) &&
fieldType.Elem().Kind() == reflect.Uint8 {
if fieldValue.Len() > 0 {
return fieldValue.Bytes(), true, nil
}
return nil, false, nil
}
bytes, err = json.DefaultJSONHandler.Marshal(fieldValue.Interface())
if err != nil {
return nil, false, err
}
return bytes, true, nil
}
return nil, false, nil
}
return fieldValue.Interface(), true, nil
}
func (statement *Statement) buildConds2(table *schemas.Table, bean interface{},
includeVersion bool, includeUpdated bool, includeNil bool,
includeAutoIncr bool, allUseBool bool, useAllCols bool, unscoped bool,
mustColumnMap map[string]bool, tableName, aliasName string, addedTableName bool,
) (builder.Cond, error) {
var conds []builder.Cond
for _, col := range table.Columns() {
if !includeVersion && col.IsVersion {
continue
}
if !includeUpdated && col.IsUpdated {
continue
}
if !includeAutoIncr && col.IsAutoIncrement {
continue
2014-05-23 06:18:45 +00:00
}
2017-04-01 02:09:00 +00:00
if col.IsJSON {
continue
}
var colName string
if addedTableName {
nm := tableName
if len(aliasName) > 0 {
nm = aliasName
}
colName = statement.quote(nm) + "." + statement.quote(col.Name)
2016-12-10 02:04:48 +00:00
} else {
colName = statement.quote(col.Name)
2016-09-29 06:13:15 +00:00
}
2013-12-11 08:27:33 +00:00
fieldValuePtr, err := col.ValueOf(bean)
if err != nil {
continue
} else if fieldValuePtr == nil {
continue
2017-04-14 02:49:02 +00:00
}
2016-09-19 03:13:40 +00:00
if col.IsDeleted && !unscoped { // tag "deleted" is enabled
conds = append(conds, statement.CondDeleted(col))
}
2016-07-07 14:34:43 +00:00
fieldValue := *fieldValuePtr
if fieldValue.Interface() == nil {
continue
}
2017-04-14 05:55:55 +00:00
if statement.dialect.URI().DBType == schemas.MSSQL && (col.SQLType.Name == schemas.Text ||
col.SQLType.IsBlob() || col.SQLType.Name == schemas.TimeStampz) {
if utils.IsValueZero(fieldValue) {
continue
}
return nil, fmt.Errorf("column %s is a TEXT type with data %#v which cannot be as compare condition", col.Name, fieldValue.Interface())
}
requiredField := useAllCols
if b, ok := getFlagForColumn(mustColumnMap, col); ok {
if b {
requiredField = true
} else {
continue
}
}
2014-05-06 03:11:44 +00:00
fieldType := reflect.TypeOf(fieldValue.Interface())
if fieldType.Kind() == reflect.Ptr {
if fieldValue.IsNil() {
if includeNil {
conds = append(conds, builder.Eq{colName: nil})
}
continue
} else if !fieldValue.IsValid() {
continue
} else {
// dereference ptr type to instance type
fieldValue = fieldValue.Elem()
fieldType = reflect.TypeOf(fieldValue.Interface())
requiredField = true
}
2014-05-06 06:19:37 +00:00
}
val, ok, err := statement.asDBCond(fieldValue, fieldType, col, allUseBool, requiredField)
if err != nil {
return nil, err
}
if !ok {
continue
2014-05-06 03:11:44 +00:00
}
conds = append(conds, builder.Eq{colName: val})
2014-05-06 03:11:44 +00:00
}
return builder.And(conds...), nil
}
// BuildConds builds condition
func (statement *Statement) BuildConds(table *schemas.Table, bean interface{}, includeVersion bool, includeUpdated bool, includeNil bool, includeAutoIncr bool, addedTableName bool) (builder.Cond, error) {
return statement.buildConds2(table, bean, includeVersion, includeUpdated, includeNil, includeAutoIncr, statement.allUseBool, statement.useAllCols,
statement.unscoped, statement.MustColumnMap, statement.TableName(), statement.TableAlias, addedTableName)
}
// MergeConds merge conditions from bean and id
func (statement *Statement) MergeConds(bean interface{}) error {
if !statement.NoAutoCondition && statement.RefTable != nil {
addedTableName := (len(statement.joins) > 0)
autoCond, err := statement.BuildConds(statement.RefTable, bean, true, true, false, true, addedTableName)
if err != nil {
return err
2014-05-06 06:19:37 +00:00
}
statement.cond = statement.cond.And(autoCond)
2013-12-18 03:31:32 +00:00
}
return statement.ProcessIDParam()
}
func (statement *Statement) quoteColumnStr(columnStr string) string {
columns := strings.Split(columnStr, ",")
return statement.dialect.Quoter().Join(columns, ",")
}
// ConvertSQLOrArgs converts sql or args
func (statement *Statement) ConvertSQLOrArgs(sqlOrArgs ...interface{}) (string, []interface{}, error) {
sql, args, err := statement.convertSQLOrArgs(sqlOrArgs...)
if err != nil {
return "", nil, err
}
return statement.ReplaceQuote(sql), args, nil
}
func (statement *Statement) convertSQLOrArgs(sqlOrArgs ...interface{}) (string, []interface{}, error) {
switch sqlOrArgs[0].(type) {
case string:
if len(sqlOrArgs) > 1 {
newArgs := make([]interface{}, 0, len(sqlOrArgs)-1)
for _, arg := range sqlOrArgs[1:] {
if v, ok := arg.(time.Time); ok {
newArgs = append(newArgs, v.In(statement.defaultTimeZone).Format("2006-01-02 15:04:05"))
} else if v, ok := arg.(*time.Time); ok && v != nil {
newArgs = append(newArgs, v.In(statement.defaultTimeZone).Format("2006-01-02 15:04:05"))
} else if v, ok := arg.(convert.ConversionTo); ok {
r, err := v.ToDB()
if err != nil {
return "", nil, err
}
if r != nil {
// for nvarchar column on mssql, bytes have to be converted as ucs-2 external of driver
// for binary column, a string will be converted as bytes directly. So we have to
// convert bytes as string
if statement.dialect.URI().DBType == schemas.MSSQL {
newArgs = append(newArgs, string(r))
} else {
newArgs = append(newArgs, r)
}
} else {
newArgs = append(newArgs, nil)
}
} else {
newArgs = append(newArgs, arg)
}
}
return sqlOrArgs[0].(string), newArgs, nil
}
return sqlOrArgs[0].(string), sqlOrArgs[1:], nil
case *builder.Builder:
return sqlOrArgs[0].(*builder.Builder).ToSQL()
case builder.Builder:
bd := sqlOrArgs[0].(builder.Builder)
return bd.ToSQL()
}
2013-12-20 06:53:40 +00:00
return "", nil, ErrUnSupportedType
2013-05-03 07:26:51 +00:00
}
2013-12-17 01:38:20 +00:00
func (statement *Statement) joinColumns(cols []*schemas.Column, includeTableName bool) string {
colnames := make([]string, len(cols))
2016-05-13 10:09:11 +00:00
for i, col := range cols {
if includeTableName {
colnames[i] = statement.quote(statement.TableName()) +
"." + statement.quote(col.Name)
2016-05-13 10:09:11 +00:00
} else {
colnames[i] = statement.quote(col.Name)
2016-05-13 10:09:11 +00:00
}
}
return strings.Join(colnames, ", ")
}
// CondDeleted returns the conditions whether a record is soft deleted.
func (statement *Statement) CondDeleted(col *schemas.Column) builder.Cond {
colName := statement.quote(col.Name)
if len(statement.joins) > 0 {
var prefix string
if statement.TableAlias != "" {
prefix = statement.TableAlias
} else {
prefix = statement.TableName()
}
colName = statement.quote(prefix) + "." + statement.quote(col.Name)
}
cond := builder.NewCond()
if col.SQLType.IsNumeric() {
cond = builder.Eq{colName: 0}
} else {
// FIXME: mssql: The conversion of a nvarchar data type to a datetime data type resulted in an out-of-range value.
if statement.dialect.URI().DBType != schemas.MSSQL {
cond = builder.Eq{colName: utils.ZeroTime1}
}
2016-05-13 10:09:11 +00:00
}
if col.Nullable {
cond = cond.Or(builder.IsNull{colName})
2016-05-13 10:09:11 +00:00
}
return cond
2016-05-13 10:09:11 +00:00
}