From 68e75c589bf679029ea72024b99312f4cc7582b3 Mon Sep 17 00:00:00 2001 From: John Guo Date: Sat, 30 Apr 2022 15:53:56 +0800 Subject: [PATCH 1/7] add WhereBuilder feature for package gdb --- database/gdb/gdb_func.go | 2 +- database/gdb/gdb_model.go | 95 +++++------ database/gdb/gdb_model_builder.go | 143 ++++++++++++++++ database/gdb/gdb_model_builder_where.go | 159 ++++++++++++++++++ .../gdb/gdb_model_builder_where_prefix.go | 99 +++++++++++ database/gdb/gdb_model_builder_whereor.go | 118 +++++++++++++ .../gdb/gdb_model_builder_whereor_prefix.go | 91 ++++++++++ database/gdb/gdb_model_select.go | 115 ++----------- database/gdb/gdb_model_where.go | 108 +++--------- database/gdb/gdb_model_where_prefix.go | 50 ++---- database/gdb/gdb_model_whereor.go | 69 ++------ database/gdb/gdb_model_whereor_prefix.go | 48 ++---- 12 files changed, 731 insertions(+), 366 deletions(-) create mode 100644 database/gdb/gdb_model_builder.go create mode 100644 database/gdb/gdb_model_builder_where.go create mode 100644 database/gdb/gdb_model_builder_where_prefix.go create mode 100644 database/gdb/gdb_model_builder_whereor.go create mode 100644 database/gdb/gdb_model_builder_whereor_prefix.go diff --git a/database/gdb/gdb_func.go b/database/gdb/gdb_func.go index a687c4dbb..4682f7dab 100644 --- a/database/gdb/gdb_func.go +++ b/database/gdb/gdb_func.go @@ -332,7 +332,7 @@ func formatSql(sql string, args []interface{}) (newSql string, newArgs []interfa } type formatWhereHolderInput struct { - ModelWhereHolder + WhereHolder OmitNil bool OmitEmpty bool Schema string diff --git a/database/gdb/gdb_model.go b/database/gdb/gdb_model.go index 6d72f9b21..4769aaa4d 100644 --- a/database/gdb/gdb_model.go +++ b/database/gdb/gdb_model.go @@ -17,39 +17,39 @@ import ( // Model is core struct implementing the DAO for ORM. type Model struct { - db DB // Underlying DB interface. - tx *TX // Underlying TX interface. - rawSql string // rawSql is the raw SQL string which marks a raw SQL based Model not a table based Model. - schema string // Custom database schema. - linkType int // Mark for operation on master or slave. - tablesInit string // Table names when model initialization. - tables string // Operation table names, which can be more than one table names and aliases, like: "user", "user u", "user u, user_detail ud". - fields string // Operation fields, multiple fields joined using char ','. - fieldsEx string // Excluded operation fields, multiple fields joined using char ','. - withArray []interface{} // Arguments for With feature. - withAll bool // Enable model association operations on all objects that have "with" tag in the struct. - extraArgs []interface{} // Extra custom arguments for sql, which are prepended to the arguments before sql committed to underlying driver. - whereHolder []ModelWhereHolder // Condition strings for where operation. - groupBy string // Used for "group by" statement. - orderBy string // Used for "order by" statement. - having []interface{} // Used for "having..." statement. - start int // Used for "select ... start, limit ..." statement. - limit int // Used for "select ... start, limit ..." statement. - option int // Option for extra operation features. - offset int // Offset statement for some databases grammar. - data interface{} // Data for operation, which can be type of map/[]map/struct/*struct/string, etc. - batch int // Batch number for batch Insert/Replace/Save operations. - filter bool // Filter data and where key-value pairs according to the fields of the table. - distinct string // Force the query to only return distinct results. - lockInfo string // Lock for update or in shared lock. - cacheEnabled bool // Enable sql result cache feature, which is mainly for indicating cache duration(especially 0) usage. - cacheOption CacheOption // Cache option for query statement. - hookHandler HookHandler // Hook functions for model hook feature. - shardingHandler ShardingHandler // Custom sharding handler for sharding feature. - unscoped bool // Disables soft deleting features when select/delete operations. - safe bool // If true, it clones and returns a new model object whenever operation done; or else it changes the attribute of current model. - onDuplicate interface{} // onDuplicate is used for ON "DUPLICATE KEY UPDATE" statement. - onDuplicateEx interface{} // onDuplicateEx is used for excluding some columns ON "DUPLICATE KEY UPDATE" statement. + db DB // Underlying DB interface. + tx *TX // Underlying TX interface. + rawSql string // rawSql is the raw SQL string which marks a raw SQL based Model not a table based Model. + schema string // Custom database schema. + linkType int // Mark for operation on master or slave. + tablesInit string // Table names when model initialization. + tables string // Operation table names, which can be more than one table names and aliases, like: "user", "user u", "user u, user_detail ud". + fields string // Operation fields, multiple fields joined using char ','. + fieldsEx string // Excluded operation fields, multiple fields joined using char ','. + withArray []interface{} // Arguments for With feature. + withAll bool // Enable model association operations on all objects that have "with" tag in the struct. + extraArgs []interface{} // Extra custom arguments for sql, which are prepended to the arguments before sql committed to underlying driver. + whereBuilder *WhereBuilder // Condition builder for where operation. + groupBy string // Used for "group by" statement. + orderBy string // Used for "order by" statement. + having []interface{} // Used for "having..." statement. + start int // Used for "select ... start, limit ..." statement. + limit int // Used for "select ... start, limit ..." statement. + option int // Option for extra operation features. + offset int // Offset statement for some databases grammar. + data interface{} // Data for operation, which can be type of map/[]map/struct/*struct/string, etc. + batch int // Batch number for batch Insert/Replace/Save operations. + filter bool // Filter data and where key-value pairs according to the fields of the table. + distinct string // Force the query to only return distinct results. + lockInfo string // Lock for update or in shared lock. + cacheEnabled bool // Enable sql result cache feature, which is mainly for indicating cache duration(especially 0) usage. + cacheOption CacheOption // Cache option for query statement. + hookHandler HookHandler // Hook functions for model hook feature. + shardingHandler ShardingHandler // Custom sharding handler for sharding feature. + unscoped bool // Disables soft deleting features when select/delete operations. + safe bool // If true, it clones and returns a new model object whenever operation done; or else it changes the attribute of current model. + onDuplicate interface{} // onDuplicate is used for ON "DUPLICATE KEY UPDATE" statement. + onDuplicateEx interface{} // onDuplicateEx is used for excluding some columns ON "DUPLICATE KEY UPDATE" statement. } // ModelHandler is a function that handles given Model and returns a new Model that is custom modified. @@ -59,15 +59,6 @@ type ModelHandler func(m *Model) *Model // It returns true if it wants to continue chunking, or else it returns false to stop chunking. type ChunkHandler func(result Result, err error) bool -// ModelWhereHolder is the holder for where condition preparing. -type ModelWhereHolder struct { - Type string // Type of this holder. - Operator int // Operator for this holder. - Where interface{} // Where parameter, which can commonly be type of string/map/struct. - Args []interface{} // Arguments for where parameter. - Prefix string // Field prefix, eg: "user.", "order.". -} - const ( linkTypeMaster = 1 linkTypeSlave = 2 @@ -102,16 +93,16 @@ func (c *Core) Model(tableNameQueryOrStruct ...interface{}) *Model { if len(tableNameQueryOrStruct) > 1 { conditionStr := gconv.String(tableNameQueryOrStruct[0]) if gstr.Contains(conditionStr, "?") { - whereHolder := ModelWhereHolder{ + whereHolder := WhereHolder{ Where: conditionStr, Args: tableNameQueryOrStruct[1:], } tableStr, extraArgs = formatWhereHolder(ctx, c.db, formatWhereHolderInput{ - ModelWhereHolder: whereHolder, - OmitNil: false, - OmitEmpty: false, - Schema: "", - Table: "", + WhereHolder: whereHolder, + OmitNil: false, + OmitEmpty: false, + Schema: "", + Table: "", }) } } @@ -144,6 +135,7 @@ func (c *Core) Model(tableNameQueryOrStruct ...interface{}) *Model { filter: true, extraArgs: extraArgs, } + m.whereBuilder = m.Builder() if defaultModelSafe { m.safe = true } @@ -269,15 +261,14 @@ func (m *Model) Clone() *Model { } // Basic attributes copy. *newModel = *m + // WhereBuilder copy, note the attribute pointer. + newModel.whereBuilder = m.whereBuilder.Clone() + newModel.whereBuilder.model = newModel // Shallow copy slice attributes. if n := len(m.extraArgs); n > 0 { newModel.extraArgs = make([]interface{}, n) copy(newModel.extraArgs, m.extraArgs) } - if n := len(m.whereHolder); n > 0 { - newModel.whereHolder = make([]ModelWhereHolder, n) - copy(newModel.whereHolder, m.whereHolder) - } if n := len(m.withArray); n > 0 { newModel.withArray = make([]interface{}, n) copy(newModel.withArray, m.withArray) diff --git a/database/gdb/gdb_model_builder.go b/database/gdb/gdb_model_builder.go new file mode 100644 index 000000000..0a1ccd6ff --- /dev/null +++ b/database/gdb/gdb_model_builder.go @@ -0,0 +1,143 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gdb + +import ( + "fmt" + + "github.com/gogf/gf/v2/text/gstr" +) + +type WhereBuilder struct { + model *Model // Bound to parent model. + whereHolder []WhereHolder // Condition strings for where operation. +} + +// WhereHolder is the holder for where condition preparing. +type WhereHolder struct { + Type string // Type of this holder. + Operator int // Operator for this holder. + Where interface{} // Where parameter, which can commonly be type of string/map/struct. + Args []interface{} // Arguments for where parameter. + Prefix string // Field prefix, eg: "user.", "order.". +} + +func (m *Model) Builder() *WhereBuilder { + b := &WhereBuilder{ + model: m, + whereHolder: make([]WhereHolder, 0), + } + return b +} + +// getBuilder creates and returns a cloned WhereBuilder of current WhereBuilder if `safe` is true, +// or else it returns the current WhereBuilder. +func (b *WhereBuilder) getBuilder() *WhereBuilder { + if !b.model.safe { + return b + } else { + return b.Clone() + } +} + +func (b *WhereBuilder) Clone() *WhereBuilder { + newBuilder := b.model.Builder() + newBuilder.whereHolder = make([]WhereHolder, len(b.whereHolder)) + copy(newBuilder.whereHolder, b.whereHolder) + return newBuilder +} + +func (b *WhereBuilder) Build() (conditionWhere string, conditionArgs []interface{}) { + var ( + ctx = b.model.GetCtx() + autoPrefix = b.model.getAutoPrefix() + tableForMappingAndFiltering = b.model.tables + ) + if len(b.whereHolder) > 0 { + for _, holder := range b.whereHolder { + if holder.Prefix == "" { + holder.Prefix = autoPrefix + } + switch holder.Operator { + case whereHolderOperatorWhere: + if conditionWhere == "" { + newWhere, newArgs := formatWhereHolder(ctx, b.model.db, formatWhereHolderInput{ + WhereHolder: holder, + OmitNil: b.model.option&optionOmitNilWhere > 0, + OmitEmpty: b.model.option&optionOmitEmptyWhere > 0, + Schema: b.model.schema, + Table: tableForMappingAndFiltering, + }) + if len(newWhere) > 0 { + conditionWhere = newWhere + conditionArgs = newArgs + } + continue + } + fallthrough + + case whereHolderOperatorAnd: + newWhere, newArgs := formatWhereHolder(ctx, b.model.db, formatWhereHolderInput{ + WhereHolder: holder, + OmitNil: b.model.option&optionOmitNilWhere > 0, + OmitEmpty: b.model.option&optionOmitEmptyWhere > 0, + Schema: b.model.schema, + Table: tableForMappingAndFiltering, + }) + if len(newWhere) > 0 { + if len(conditionWhere) == 0 { + conditionWhere = newWhere + } else if conditionWhere[0] == '(' { + conditionWhere = fmt.Sprintf(`%s AND (%s)`, conditionWhere, newWhere) + } else { + conditionWhere = fmt.Sprintf(`(%s) AND (%s)`, conditionWhere, newWhere) + } + conditionArgs = append(conditionArgs, newArgs...) + } + + case whereHolderOperatorOr: + newWhere, newArgs := formatWhereHolder(ctx, b.model.db, formatWhereHolderInput{ + WhereHolder: holder, + OmitNil: b.model.option&optionOmitNilWhere > 0, + OmitEmpty: b.model.option&optionOmitEmptyWhere > 0, + Schema: b.model.schema, + Table: tableForMappingAndFiltering, + }) + if len(newWhere) > 0 { + if len(conditionWhere) == 0 { + conditionWhere = newWhere + } else if conditionWhere[0] == '(' { + conditionWhere = fmt.Sprintf(`%s OR (%s)`, conditionWhere, newWhere) + } else { + conditionWhere = fmt.Sprintf(`(%s) OR (%s)`, conditionWhere, newWhere) + } + conditionArgs = append(conditionArgs, newArgs...) + } + } + } + } + // Soft deletion. + softDeletingCondition := b.model.getConditionForSoftDeleting() + if b.model.rawSql != "" && conditionWhere != "" { + if gstr.ContainsI(b.model.rawSql, " WHERE ") { + conditionWhere = " AND " + conditionWhere + } else { + conditionWhere = " WHERE " + conditionWhere + } + } else if !b.model.unscoped && softDeletingCondition != "" { + if conditionWhere == "" { + conditionWhere = fmt.Sprintf(` WHERE %s`, softDeletingCondition) + } else { + conditionWhere = fmt.Sprintf(` WHERE (%s) AND %s`, conditionWhere, softDeletingCondition) + } + } else { + if conditionWhere != "" { + conditionWhere = " WHERE " + conditionWhere + } + } + return +} diff --git a/database/gdb/gdb_model_builder_where.go b/database/gdb/gdb_model_builder_where.go new file mode 100644 index 000000000..5803f8d82 --- /dev/null +++ b/database/gdb/gdb_model_builder_where.go @@ -0,0 +1,159 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gdb + +import ( + "fmt" + + "github.com/gogf/gf/v2/text/gstr" +) + +// doWhereType sets the condition statement for the model. The parameter `where` can be type of +// string/map/gmap/slice/struct/*struct, etc. Note that, if it's called more than one times, +// multiple conditions will be joined into where statement using "AND". +func (b *WhereBuilder) doWhereType(t string, where interface{}, args ...interface{}) *WhereBuilder { + builder := b.getBuilder() + if builder.whereHolder == nil { + builder.whereHolder = make([]WhereHolder, 0) + } + if t == "" { + if len(args) == 0 { + t = whereHolderTypeNoArgs + } else { + t = whereHolderTypeDefault + } + } + builder.whereHolder = append(builder.whereHolder, WhereHolder{ + Type: t, + Operator: whereHolderOperatorWhere, + Where: where, + Args: args, + }) + return builder +} + +// doWherefType builds condition string using fmt.Sprintf and arguments. +// Note that if the number of `args` is more than the placeholder in `format`, +// the extra `args` will be used as the where condition arguments of the Model. +func (b *WhereBuilder) doWherefType(t string, format string, args ...interface{}) *WhereBuilder { + var ( + placeHolderCount = gstr.Count(format, "?") + conditionStr = fmt.Sprintf(format, args[:len(args)-placeHolderCount]...) + ) + return b.doWhereType(t, conditionStr, args[len(args)-placeHolderCount:]...) +} + +// Where sets the condition statement for the builder. The parameter `where` can be type of +// string/map/gmap/slice/struct/*struct, etc. Note that, if it's called more than one times, +// multiple conditions will be joined into where statement using "AND". +// Eg: +// Where("uid=10000") +// Where("uid", 10000) +// Where("money>? AND name like ?", 99999, "vip_%") +// Where("uid", 1).Where("name", "john") +// Where("status IN (?)", g.Slice{1,2,3}) +// Where("age IN(?,?)", 18, 50) +// Where(User{ Id : 1, UserName : "john"}). +func (b *WhereBuilder) Where(where interface{}, args ...interface{}) *WhereBuilder { + return b.doWhereType(``, where, args...) +} + +// Wheref builds condition string using fmt.Sprintf and arguments. +// Note that if the number of `args` is more than the placeholder in `format`, +// the extra `args` will be used as the where condition arguments of the Model. +// Eg: +// Wheref(`amount WHERE `amount`<100 and status='paid' +// Wheref(`amount<%d and status=%s`, 100, "paid") => WHERE `amount`<100 and status='paid' +func (b *WhereBuilder) Wheref(format string, args ...interface{}) *WhereBuilder { + return b.doWherefType(``, format, args...) +} + +// WherePri does the same logic as Model.Where except that if the parameter `where` +// is a single condition like int/string/float/slice, it treats the condition as the primary +// key value. That is, if primary key is "id" and given `where` parameter as "123", the +// WherePri function treats the condition as "id=123", but Model.Where treats the condition +// as string "123". +func (b *WhereBuilder) WherePri(where interface{}, args ...interface{}) *WhereBuilder { + if len(args) > 0 { + return b.Where(where, args...) + } + newWhere := GetPrimaryKeyCondition(b.model.getPrimaryKey(), where) + return b.Where(newWhere[0], newWhere[1:]...) +} + +// WhereLT builds `column < value` statement. +func (b *WhereBuilder) WhereLT(column string, value interface{}) *WhereBuilder { + return b.Wheref(`%s < ?`, b.model.QuoteWord(column), value) +} + +// WhereLTE builds `column <= value` statement. +func (b *WhereBuilder) WhereLTE(column string, value interface{}) *WhereBuilder { + return b.Wheref(`%s <= ?`, b.model.QuoteWord(column), value) +} + +// WhereGT builds `column > value` statement. +func (b *WhereBuilder) WhereGT(column string, value interface{}) *WhereBuilder { + return b.Wheref(`%s > ?`, b.model.QuoteWord(column), value) +} + +// WhereGTE builds `column >= value` statement. +func (b *WhereBuilder) WhereGTE(column string, value interface{}) *WhereBuilder { + return b.Wheref(`%s >= ?`, b.model.QuoteWord(column), value) +} + +// WhereBetween builds `column BETWEEN min AND max` statement. +func (b *WhereBuilder) WhereBetween(column string, min, max interface{}) *WhereBuilder { + return b.Wheref(`%s BETWEEN ? AND ?`, b.model.QuoteWord(column), min, max) +} + +// WhereLike builds `column LIKE like` statement. +func (b *WhereBuilder) WhereLike(column string, like string) *WhereBuilder { + return b.Wheref(`%s LIKE ?`, b.model.QuoteWord(column), like) +} + +// WhereIn builds `column IN (in)` statement. +func (b *WhereBuilder) WhereIn(column string, in interface{}) *WhereBuilder { + return b.doWherefType(whereHolderTypeIn, `%s IN (?)`, b.model.QuoteWord(column), in) +} + +// WhereNull builds `columns[0] IS NULL AND columns[1] IS NULL ...` statement. +func (b *WhereBuilder) WhereNull(columns ...string) *WhereBuilder { + builder := b + for _, column := range columns { + builder = builder.Wheref(`%s IS NULL`, b.model.QuoteWord(column)) + } + return builder +} + +// WhereNotBetween builds `column NOT BETWEEN min AND max` statement. +func (b *WhereBuilder) WhereNotBetween(column string, min, max interface{}) *WhereBuilder { + return b.Wheref(`%s NOT BETWEEN ? AND ?`, b.model.QuoteWord(column), min, max) +} + +// WhereNotLike builds `column NOT LIKE like` statement. +func (b *WhereBuilder) WhereNotLike(column string, like interface{}) *WhereBuilder { + return b.Wheref(`%s NOT LIKE ?`, b.model.QuoteWord(column), like) +} + +// WhereNot builds `column != value` statement. +func (b *WhereBuilder) WhereNot(column string, value interface{}) *WhereBuilder { + return b.Wheref(`%s != ?`, b.model.QuoteWord(column), value) +} + +// WhereNotIn builds `column NOT IN (in)` statement. +func (b *WhereBuilder) WhereNotIn(column string, in interface{}) *WhereBuilder { + return b.doWherefType(whereHolderTypeIn, `%s NOT IN (?)`, b.model.QuoteWord(column), in) +} + +// WhereNotNull builds `columns[0] IS NOT NULL AND columns[1] IS NOT NULL ...` statement. +func (b *WhereBuilder) WhereNotNull(columns ...string) *WhereBuilder { + builder := b + for _, column := range columns { + builder = builder.Wheref(`%s IS NOT NULL`, b.model.QuoteWord(column)) + } + return builder +} diff --git a/database/gdb/gdb_model_builder_where_prefix.go b/database/gdb/gdb_model_builder_where_prefix.go new file mode 100644 index 000000000..7cc189901 --- /dev/null +++ b/database/gdb/gdb_model_builder_where_prefix.go @@ -0,0 +1,99 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gdb + +// WherePrefix performs as Where, but it adds prefix to each field in where statement. +// Eg: +// WherePrefix("order", "status", "paid") => WHERE `order`.`status`='paid' +// WherePrefix("order", struct{Status:"paid", "channel":"bank"}) => WHERE `order`.`status`='paid' AND `order`.`channel`='bank' +func (b *WhereBuilder) WherePrefix(prefix string, where interface{}, args ...interface{}) *WhereBuilder { + builder := b.getBuilder() + if builder.whereHolder == nil { + builder.whereHolder = make([]WhereHolder, 0) + } + builder.whereHolder = append(builder.whereHolder, WhereHolder{ + Type: whereHolderTypeDefault, + Operator: whereHolderOperatorWhere, + Where: where, + Args: args, + Prefix: prefix, + }) + return builder +} + +// WherePrefixLT builds `prefix.column < value` statement. +func (b *WhereBuilder) WherePrefixLT(prefix string, column string, value interface{}) *WhereBuilder { + return b.Wheref(`%s.%s < ?`, b.model.QuoteWord(prefix), b.model.QuoteWord(column), value) +} + +// WherePrefixLTE builds `prefix.column <= value` statement. +func (b *WhereBuilder) WherePrefixLTE(prefix string, column string, value interface{}) *WhereBuilder { + return b.Wheref(`%s.%s <= ?`, b.model.QuoteWord(prefix), b.model.QuoteWord(column), value) +} + +// WherePrefixGT builds `prefix.column > value` statement. +func (b *WhereBuilder) WherePrefixGT(prefix string, column string, value interface{}) *WhereBuilder { + return b.Wheref(`%s.%s > ?`, b.model.QuoteWord(prefix), b.model.QuoteWord(column), value) +} + +// WherePrefixGTE builds `prefix.column >= value` statement. +func (b *WhereBuilder) WherePrefixGTE(prefix string, column string, value interface{}) *WhereBuilder { + return b.Wheref(`%s.%s >= ?`, b.model.QuoteWord(prefix), b.model.QuoteWord(column), value) +} + +// WherePrefixBetween builds `prefix.column BETWEEN min AND max` statement. +func (b *WhereBuilder) WherePrefixBetween(prefix string, column string, min, max interface{}) *WhereBuilder { + return b.Wheref(`%s.%s BETWEEN ? AND ?`, b.model.QuoteWord(prefix), b.model.QuoteWord(column), min, max) +} + +// WherePrefixLike builds `prefix.column LIKE like` statement. +func (b *WhereBuilder) WherePrefixLike(prefix string, column string, like interface{}) *WhereBuilder { + return b.Wheref(`%s.%s LIKE ?`, b.model.QuoteWord(prefix), b.model.QuoteWord(column), like) +} + +// WherePrefixIn builds `prefix.column IN (in)` statement. +func (b *WhereBuilder) WherePrefixIn(prefix string, column string, in interface{}) *WhereBuilder { + return b.doWherefType(whereHolderTypeIn, `%s.%s IN (?)`, b.model.QuoteWord(prefix), b.model.QuoteWord(column), in) +} + +// WherePrefixNull builds `prefix.columns[0] IS NULL AND prefix.columns[1] IS NULL ...` statement. +func (b *WhereBuilder) WherePrefixNull(prefix string, columns ...string) *WhereBuilder { + builder := b + for _, column := range columns { + builder = builder.Wheref(`%s.%s IS NULL`, b.model.QuoteWord(prefix), b.model.QuoteWord(column)) + } + return builder +} + +// WherePrefixNotBetween builds `prefix.column NOT BETWEEN min AND max` statement. +func (b *WhereBuilder) WherePrefixNotBetween(prefix string, column string, min, max interface{}) *WhereBuilder { + return b.Wheref(`%s.%s NOT BETWEEN ? AND ?`, b.model.QuoteWord(prefix), b.model.QuoteWord(column), min, max) +} + +// WherePrefixNotLike builds `prefix.column NOT LIKE like` statement. +func (b *WhereBuilder) WherePrefixNotLike(prefix string, column string, like interface{}) *WhereBuilder { + return b.Wheref(`%s.%s NOT LIKE ?`, b.model.QuoteWord(prefix), b.model.QuoteWord(column), like) +} + +// WherePrefixNot builds `prefix.column != value` statement. +func (b *WhereBuilder) WherePrefixNot(prefix string, column string, value interface{}) *WhereBuilder { + return b.Wheref(`%s.%s != ?`, b.model.QuoteWord(prefix), b.model.QuoteWord(column), value) +} + +// WherePrefixNotIn builds `prefix.column NOT IN (in)` statement. +func (b *WhereBuilder) WherePrefixNotIn(prefix string, column string, in interface{}) *WhereBuilder { + return b.doWherefType(whereHolderTypeIn, `%s.%s NOT IN (?)`, b.model.QuoteWord(prefix), b.model.QuoteWord(column), in) +} + +// WherePrefixNotNull builds `prefix.columns[0] IS NOT NULL AND prefix.columns[1] IS NOT NULL ...` statement. +func (b *WhereBuilder) WherePrefixNotNull(prefix string, columns ...string) *WhereBuilder { + builder := b + for _, column := range columns { + builder = builder.Wheref(`%s.%s IS NOT NULL`, b.model.QuoteWord(prefix), b.model.QuoteWord(column)) + } + return builder +} diff --git a/database/gdb/gdb_model_builder_whereor.go b/database/gdb/gdb_model_builder_whereor.go new file mode 100644 index 000000000..993c4def3 --- /dev/null +++ b/database/gdb/gdb_model_builder_whereor.go @@ -0,0 +1,118 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gdb + +import ( + "fmt" + + "github.com/gogf/gf/v2/text/gstr" +) + +// WhereOr adds "OR" condition to the where statement. +func (b *WhereBuilder) doWhereOrType(t string, where interface{}, args ...interface{}) *WhereBuilder { + builder := b.getBuilder() + if builder.whereHolder == nil { + builder.whereHolder = make([]WhereHolder, 0) + } + builder.whereHolder = append(builder.whereHolder, WhereHolder{ + Type: t, + Operator: whereHolderOperatorOr, + Where: where, + Args: args, + }) + return builder +} + +// WhereOrf builds `OR` condition string using fmt.Sprintf and arguments. +func (b *WhereBuilder) doWhereOrfType(t string, format string, args ...interface{}) *WhereBuilder { + var ( + placeHolderCount = gstr.Count(format, "?") + conditionStr = fmt.Sprintf(format, args[:len(args)-placeHolderCount]...) + ) + return b.doWhereOrType(t, conditionStr, args[len(args)-placeHolderCount:]...) +} + +// WhereOr adds "OR" condition to the where statement. +func (b *WhereBuilder) WhereOr(where interface{}, args ...interface{}) *WhereBuilder { + return b.doWhereOrType(``, where, args...) +} + +// WhereOrf builds `OR` condition string using fmt.Sprintf and arguments. +// Eg: +// WhereOrf(`amount WHERE xxx OR `amount`<100 and status='paid' +// WhereOrf(`amount<%d and status=%s`, 100, "paid") => WHERE xxx OR `amount`<100 and status='paid' +func (b *WhereBuilder) WhereOrf(format string, args ...interface{}) *WhereBuilder { + return b.doWhereOrfType(``, format, args...) +} + +// WhereOrLT builds `column < value` statement in `OR` conditions.. +func (b *WhereBuilder) WhereOrLT(column string, value interface{}) *WhereBuilder { + return b.WhereOrf(`%s < ?`, column, value) +} + +// WhereOrLTE builds `column <= value` statement in `OR` conditions.. +func (b *WhereBuilder) WhereOrLTE(column string, value interface{}) *WhereBuilder { + return b.WhereOrf(`%s <= ?`, column, value) +} + +// WhereOrGT builds `column > value` statement in `OR` conditions.. +func (b *WhereBuilder) WhereOrGT(column string, value interface{}) *WhereBuilder { + return b.WhereOrf(`%s > ?`, column, value) +} + +// WhereOrGTE builds `column >= value` statement in `OR` conditions.. +func (b *WhereBuilder) WhereOrGTE(column string, value interface{}) *WhereBuilder { + return b.WhereOrf(`%s >= ?`, column, value) +} + +// WhereOrBetween builds `column BETWEEN min AND max` statement in `OR` conditions. +func (b *WhereBuilder) WhereOrBetween(column string, min, max interface{}) *WhereBuilder { + return b.WhereOrf(`%s BETWEEN ? AND ?`, b.model.QuoteWord(column), min, max) +} + +// WhereOrLike builds `column LIKE like` statement in `OR` conditions. +func (b *WhereBuilder) WhereOrLike(column string, like interface{}) *WhereBuilder { + return b.WhereOrf(`%s LIKE ?`, b.model.QuoteWord(column), like) +} + +// WhereOrIn builds `column IN (in)` statement in `OR` conditions. +func (b *WhereBuilder) WhereOrIn(column string, in interface{}) *WhereBuilder { + return b.doWhereOrfType(whereHolderTypeIn, `%s IN (?)`, b.model.QuoteWord(column), in) +} + +// WhereOrNull builds `columns[0] IS NULL OR columns[1] IS NULL ...` statement in `OR` conditions. +func (b *WhereBuilder) WhereOrNull(columns ...string) *WhereBuilder { + var builder *WhereBuilder + for _, column := range columns { + builder = b.WhereOrf(`%s IS NULL`, b.model.QuoteWord(column)) + } + return builder +} + +// WhereOrNotBetween builds `column NOT BETWEEN min AND max` statement in `OR` conditions. +func (b *WhereBuilder) WhereOrNotBetween(column string, min, max interface{}) *WhereBuilder { + return b.WhereOrf(`%s NOT BETWEEN ? AND ?`, b.model.QuoteWord(column), min, max) +} + +// WhereOrNotLike builds `column NOT LIKE like` statement in `OR` conditions. +func (b *WhereBuilder) WhereOrNotLike(column string, like interface{}) *WhereBuilder { + return b.WhereOrf(`%s NOT LIKE ?`, b.model.QuoteWord(column), like) +} + +// WhereOrNotIn builds `column NOT IN (in)` statement. +func (b *WhereBuilder) WhereOrNotIn(column string, in interface{}) *WhereBuilder { + return b.doWhereOrfType(whereHolderTypeIn, `%s NOT IN (?)`, b.model.QuoteWord(column), in) +} + +// WhereOrNotNull builds `columns[0] IS NOT NULL OR columns[1] IS NOT NULL ...` statement in `OR` conditions. +func (b *WhereBuilder) WhereOrNotNull(columns ...string) *WhereBuilder { + builder := b + for _, column := range columns { + builder = builder.WhereOrf(`%s IS NOT NULL`, b.model.QuoteWord(column)) + } + return builder +} diff --git a/database/gdb/gdb_model_builder_whereor_prefix.go b/database/gdb/gdb_model_builder_whereor_prefix.go new file mode 100644 index 000000000..c720b7c3f --- /dev/null +++ b/database/gdb/gdb_model_builder_whereor_prefix.go @@ -0,0 +1,91 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gdb + +// WhereOrPrefix performs as WhereOr, but it adds prefix to each field in where statement. +// Eg: +// WhereOrPrefix("order", "status", "paid") => WHERE xxx OR (`order`.`status`='paid') +// WhereOrPrefix("order", struct{Status:"paid", "channel":"bank"}) => WHERE xxx OR (`order`.`status`='paid' AND `order`.`channel`='bank') +func (b *WhereBuilder) WhereOrPrefix(prefix string, where interface{}, args ...interface{}) *WhereBuilder { + builder := b.getBuilder() + builder.whereHolder = append(builder.whereHolder, WhereHolder{ + Type: whereHolderTypeDefault, + Operator: whereHolderOperatorOr, + Where: where, + Args: args, + Prefix: prefix, + }) + return builder +} + +// WhereOrPrefixLT builds `prefix.column < value` statement in `OR` conditions.. +func (b *WhereBuilder) WhereOrPrefixLT(prefix string, column string, value interface{}) *WhereBuilder { + return b.WhereOrf(`%s.%s < ?`, b.model.QuoteWord(prefix), b.model.QuoteWord(column), value) +} + +// WhereOrPrefixLTE builds `prefix.column <= value` statement in `OR` conditions.. +func (b *WhereBuilder) WhereOrPrefixLTE(prefix string, column string, value interface{}) *WhereBuilder { + return b.WhereOrf(`%s.%s <= ?`, b.model.QuoteWord(prefix), b.model.QuoteWord(column), value) +} + +// WhereOrPrefixGT builds `prefix.column > value` statement in `OR` conditions.. +func (b *WhereBuilder) WhereOrPrefixGT(prefix string, column string, value interface{}) *WhereBuilder { + return b.WhereOrf(`%s.%s > ?`, b.model.QuoteWord(prefix), b.model.QuoteWord(column), value) +} + +// WhereOrPrefixGTE builds `prefix.column >= value` statement in `OR` conditions.. +func (b *WhereBuilder) WhereOrPrefixGTE(prefix string, column string, value interface{}) *WhereBuilder { + return b.WhereOrf(`%s.%s >= ?`, b.model.QuoteWord(prefix), b.model.QuoteWord(column), value) +} + +// WhereOrPrefixBetween builds `prefix.column BETWEEN min AND max` statement in `OR` conditions. +func (b *WhereBuilder) WhereOrPrefixBetween(prefix string, column string, min, max interface{}) *WhereBuilder { + return b.WhereOrf(`%s.%s BETWEEN ? AND ?`, b.model.QuoteWord(prefix), b.model.QuoteWord(column), min, max) +} + +// WhereOrPrefixLike builds `prefix.column LIKE like` statement in `OR` conditions. +func (b *WhereBuilder) WhereOrPrefixLike(prefix string, column string, like interface{}) *WhereBuilder { + return b.WhereOrf(`%s.%s LIKE ?`, b.model.QuoteWord(prefix), b.model.QuoteWord(column), like) +} + +// WhereOrPrefixIn builds `prefix.column IN (in)` statement in `OR` conditions. +func (b *WhereBuilder) WhereOrPrefixIn(prefix string, column string, in interface{}) *WhereBuilder { + return b.doWhereOrfType(whereHolderTypeIn, `%s.%s IN (?)`, b.model.QuoteWord(prefix), b.model.QuoteWord(column), in) +} + +// WhereOrPrefixNull builds `prefix.columns[0] IS NULL OR prefix.columns[1] IS NULL ...` statement in `OR` conditions. +func (b *WhereBuilder) WhereOrPrefixNull(prefix string, columns ...string) *WhereBuilder { + builder := b + for _, column := range columns { + builder = builder.WhereOrf(`%s.%s IS NULL`, b.model.QuoteWord(prefix), b.model.QuoteWord(column)) + } + return builder +} + +// WhereOrPrefixNotBetween builds `prefix.column NOT BETWEEN min AND max` statement in `OR` conditions. +func (b *WhereBuilder) WhereOrPrefixNotBetween(prefix string, column string, min, max interface{}) *WhereBuilder { + return b.WhereOrf(`%s.%s NOT BETWEEN ? AND ?`, b.model.QuoteWord(prefix), b.model.QuoteWord(column), min, max) +} + +// WhereOrPrefixNotLike builds `prefix.column NOT LIKE like` statement in `OR` conditions. +func (b *WhereBuilder) WhereOrPrefixNotLike(prefix string, column string, like interface{}) *WhereBuilder { + return b.WhereOrf(`%s.%s NOT LIKE ?`, b.model.QuoteWord(prefix), b.model.QuoteWord(column), like) +} + +// WhereOrPrefixNotIn builds `prefix.column NOT IN (in)` statement. +func (b *WhereBuilder) WhereOrPrefixNotIn(prefix string, column string, in interface{}) *WhereBuilder { + return b.doWhereOrfType(whereHolderTypeIn, `%s.%s NOT IN (?)`, b.model.QuoteWord(prefix), b.model.QuoteWord(column), in) +} + +// WhereOrPrefixNotNull builds `prefix.columns[0] IS NOT NULL OR prefix.columns[1] IS NOT NULL ...` statement in `OR` conditions. +func (b *WhereBuilder) WhereOrPrefixNotNull(prefix string, columns ...string) *WhereBuilder { + builder := b + for _, column := range columns { + builder = builder.WhereOrf(`%s.%s IS NOT NULL`, b.model.QuoteWord(prefix), b.model.QuoteWord(column)) + } + return builder +} diff --git a/database/gdb/gdb_model_select.go b/database/gdb/gdb_model_select.go index 27b1ac185..b7f782f4a 100644 --- a/database/gdb/gdb_model_select.go +++ b/database/gdb/gdb_model_select.go @@ -594,122 +594,41 @@ func (m *Model) getFormattedSqlAndArgs(ctx context.Context, queryType int, limit } } -// formatCondition formats where arguments of the model and returns a new condition sql and its arguments. -// Note that this function does not change any attribute value of the `m`. -// -// The parameter `limit1` specifies whether limits querying only one record if m.limit is not set. -func (m *Model) formatCondition(ctx context.Context, limit1 bool, isCountStatement bool) (conditionWhere string, conditionExtra string, conditionArgs []interface{}) { +func (m *Model) getAutoPrefix() string { autoPrefix := "" if gstr.Contains(m.tables, " JOIN ") { autoPrefix = m.db.GetCore().QuoteWord( m.db.GetCore().guessPrimaryTableName(m.tablesInit), ) } - var ( - tableForMappingAndFiltering = m.tables - ) - if len(m.whereHolder) > 0 { - for _, holder := range m.whereHolder { - tableForMappingAndFiltering = m.tables - if holder.Prefix == "" { - holder.Prefix = autoPrefix - } - - switch holder.Operator { - case whereHolderOperatorWhere: - if conditionWhere == "" { - newWhere, newArgs := formatWhereHolder(ctx, m.db, formatWhereHolderInput{ - ModelWhereHolder: holder, - OmitNil: m.option&optionOmitNilWhere > 0, - OmitEmpty: m.option&optionOmitEmptyWhere > 0, - Schema: m.schema, - Table: tableForMappingAndFiltering, - }) - if len(newWhere) > 0 { - conditionWhere = newWhere - conditionArgs = newArgs - } - continue - } - fallthrough - - case whereHolderOperatorAnd: - newWhere, newArgs := formatWhereHolder(ctx, m.db, formatWhereHolderInput{ - ModelWhereHolder: holder, - OmitNil: m.option&optionOmitNilWhere > 0, - OmitEmpty: m.option&optionOmitEmptyWhere > 0, - Schema: m.schema, - Table: tableForMappingAndFiltering, - }) - if len(newWhere) > 0 { - if len(conditionWhere) == 0 { - conditionWhere = newWhere - } else if conditionWhere[0] == '(' { - conditionWhere = fmt.Sprintf(`%s AND (%s)`, conditionWhere, newWhere) - } else { - conditionWhere = fmt.Sprintf(`(%s) AND (%s)`, conditionWhere, newWhere) - } - conditionArgs = append(conditionArgs, newArgs...) - } - - case whereHolderOperatorOr: - newWhere, newArgs := formatWhereHolder(ctx, m.db, formatWhereHolderInput{ - ModelWhereHolder: holder, - OmitNil: m.option&optionOmitNilWhere > 0, - OmitEmpty: m.option&optionOmitEmptyWhere > 0, - Schema: m.schema, - Table: tableForMappingAndFiltering, - }) - if len(newWhere) > 0 { - if len(conditionWhere) == 0 { - conditionWhere = newWhere - } else if conditionWhere[0] == '(' { - conditionWhere = fmt.Sprintf(`%s OR (%s)`, conditionWhere, newWhere) - } else { - conditionWhere = fmt.Sprintf(`(%s) OR (%s)`, conditionWhere, newWhere) - } - conditionArgs = append(conditionArgs, newArgs...) - } - } - } - } - // Soft deletion. - softDeletingCondition := m.getConditionForSoftDeleting() - if m.rawSql != "" && conditionWhere != "" { - if gstr.ContainsI(m.rawSql, " WHERE ") { - conditionWhere = " AND " + conditionWhere - } else { - conditionWhere = " WHERE " + conditionWhere - } - } else if !m.unscoped && softDeletingCondition != "" { - if conditionWhere == "" { - conditionWhere = fmt.Sprintf(` WHERE %s`, softDeletingCondition) - } else { - conditionWhere = fmt.Sprintf(` WHERE (%s) AND %s`, conditionWhere, softDeletingCondition) - } - } else { - if conditionWhere != "" { - conditionWhere = " WHERE " + conditionWhere - } - } + return autoPrefix +} +// formatCondition formats where arguments of the model and returns a new condition sql and its arguments. +// Note that this function does not change any attribute value of the `m`. +// +// The parameter `limit1` specifies whether limits querying only one record if m.limit is not set. +func (m *Model) formatCondition(ctx context.Context, limit1 bool, isCountStatement bool) (conditionWhere string, conditionExtra string, conditionArgs []interface{}) { + var autoPrefix = m.getAutoPrefix() // GROUP BY. if m.groupBy != "" { conditionExtra += " GROUP BY " + m.groupBy } + // WHERE + conditionWhere, conditionArgs = m.whereBuilder.Build() // HAVING. if len(m.having) > 0 { - havingHolder := ModelWhereHolder{ + havingHolder := WhereHolder{ Where: m.having[0], Args: gconv.Interfaces(m.having[1]), Prefix: autoPrefix, } havingStr, havingArgs := formatWhereHolder(ctx, m.db, formatWhereHolderInput{ - ModelWhereHolder: havingHolder, - OmitNil: m.option&optionOmitNilWhere > 0, - OmitEmpty: m.option&optionOmitEmptyWhere > 0, - Schema: m.schema, - Table: m.tables, + WhereHolder: havingHolder, + OmitNil: m.option&optionOmitNilWhere > 0, + OmitEmpty: m.option&optionOmitEmptyWhere > 0, + Schema: m.schema, + Table: m.tables, }) if len(havingStr) > 0 { conditionExtra += " HAVING " + havingStr diff --git a/database/gdb/gdb_model_where.go b/database/gdb/gdb_model_where.go index 0b168eb64..09566db91 100644 --- a/database/gdb/gdb_model_where.go +++ b/database/gdb/gdb_model_where.go @@ -2,158 +2,88 @@ // // This Source Code Form is subject to the terms of the MIT License. // If a copy of the MIT was not distributed with this file, -// You can obtain one at https://github.com/gogf/gf. +// You can obtain one at https://githum.com/gogf/gf. package gdb -import ( - "fmt" - - "github.com/gogf/gf/v2/text/gstr" -) - -// doWhereType sets the condition statement for the model. The parameter `where` can be type of -// string/map/gmap/slice/struct/*struct, etc. Note that, if it's called more than one times, -// multiple conditions will be joined into where statement using "AND". -func (m *Model) doWhereType(t string, where interface{}, args ...interface{}) *Model { +func (m *Model) callWhereBuilder(builder *WhereBuilder) *Model { model := m.getModel() - if model.whereHolder == nil { - model.whereHolder = make([]ModelWhereHolder, 0) - } - if t == "" { - if len(args) == 0 { - t = whereHolderTypeNoArgs - } else { - t = whereHolderTypeDefault - } - } - model.whereHolder = append(model.whereHolder, ModelWhereHolder{ - Type: t, - Operator: whereHolderOperatorWhere, - Where: where, - Args: args, - }) + model.whereBuilder = builder return model } -// doWherefType builds condition string using fmt.Sprintf and arguments. -// Note that if the number of `args` is more than the placeholder in `format`, -// the extra `args` will be used as the where condition arguments of the Model. -func (m *Model) doWherefType(t string, format string, args ...interface{}) *Model { - var ( - placeHolderCount = gstr.Count(format, "?") - conditionStr = fmt.Sprintf(format, args[:len(args)-placeHolderCount]...) - ) - return m.doWhereType(t, conditionStr, args[len(args)-placeHolderCount:]...) -} - -// Where sets the condition statement for the model. The parameter `where` can be type of -// string/map/gmap/slice/struct/*struct, etc. Note that, if it's called more than one times, -// multiple conditions will be joined into where statement using "AND". -// Eg: -// Where("uid=10000") -// Where("uid", 10000) -// Where("money>? AND name like ?", 99999, "vip_%") -// Where("uid", 1).Where("name", "john") -// Where("status IN (?)", g.Slice{1,2,3}) -// Where("age IN(?,?)", 18, 50) -// Where(User{ Id : 1, UserName : "john"}). func (m *Model) Where(where interface{}, args ...interface{}) *Model { - return m.doWhereType(``, where, args...) + return m.callWhereBuilder(m.whereBuilder.Where(where, args...)) } -// Wheref builds condition string using fmt.Sprintf and arguments. -// Note that if the number of `args` is more than the placeholder in `format`, -// the extra `args` will be used as the where condition arguments of the Model. -// Eg: -// Wheref(`amount WHERE `amount`<100 and status='paid' -// Wheref(`amount<%d and status=%s`, 100, "paid") => WHERE `amount`<100 and status='paid' func (m *Model) Wheref(format string, args ...interface{}) *Model { - return m.doWherefType(``, format, args...) + return m.callWhereBuilder(m.whereBuilder.Wheref(format, args...)) } -// WherePri does the same logic as Model.Where except that if the parameter `where` -// is a single condition like int/string/float/slice, it treats the condition as the primary -// key value. That is, if primary key is "id" and given `where` parameter as "123", the -// WherePri function treats the condition as "id=123", but Model.Where treats the condition -// as string "123". func (m *Model) WherePri(where interface{}, args ...interface{}) *Model { - if len(args) > 0 { - return m.Where(where, args...) - } - newWhere := GetPrimaryKeyCondition(m.getPrimaryKey(), where) - return m.Where(newWhere[0], newWhere[1:]...) + return m.callWhereBuilder(m.whereBuilder.WherePri(where, args...)) } -// WhereLT builds `column < value` statement. func (m *Model) WhereLT(column string, value interface{}) *Model { - return m.Wheref(`%s < ?`, m.QuoteWord(column), value) + return m.callWhereBuilder(m.whereBuilder.WhereLT(column, value)) } // WhereLTE builds `column <= value` statement. func (m *Model) WhereLTE(column string, value interface{}) *Model { - return m.Wheref(`%s <= ?`, m.QuoteWord(column), value) + return m.callWhereBuilder(m.whereBuilder.WhereLTE(column, value)) } // WhereGT builds `column > value` statement. func (m *Model) WhereGT(column string, value interface{}) *Model { - return m.Wheref(`%s > ?`, m.QuoteWord(column), value) + return m.callWhereBuilder(m.whereBuilder.WhereGT(column, value)) } // WhereGTE builds `column >= value` statement. func (m *Model) WhereGTE(column string, value interface{}) *Model { - return m.Wheref(`%s >= ?`, m.QuoteWord(column), value) + return m.callWhereBuilder(m.whereBuilder.WhereGTE(column, value)) } // WhereBetween builds `column BETWEEN min AND max` statement. func (m *Model) WhereBetween(column string, min, max interface{}) *Model { - return m.Wheref(`%s BETWEEN ? AND ?`, m.QuoteWord(column), min, max) + return m.callWhereBuilder(m.whereBuilder.WhereBetween(column, min, max)) } // WhereLike builds `column LIKE like` statement. func (m *Model) WhereLike(column string, like string) *Model { - return m.Wheref(`%s LIKE ?`, m.QuoteWord(column), like) + return m.callWhereBuilder(m.whereBuilder.WhereLike(column, like)) } // WhereIn builds `column IN (in)` statement. func (m *Model) WhereIn(column string, in interface{}) *Model { - return m.doWherefType(whereHolderTypeIn, `%s IN (?)`, m.QuoteWord(column), in) + return m.callWhereBuilder(m.whereBuilder.WhereIn(column, in)) } // WhereNull builds `columns[0] IS NULL AND columns[1] IS NULL ...` statement. func (m *Model) WhereNull(columns ...string) *Model { - model := m - for _, column := range columns { - model = m.Wheref(`%s IS NULL`, m.QuoteWord(column)) - } - return model + return m.callWhereBuilder(m.whereBuilder.WhereNull(columns...)) } // WhereNotBetween builds `column NOT BETWEEN min AND max` statement. func (m *Model) WhereNotBetween(column string, min, max interface{}) *Model { - return m.Wheref(`%s NOT BETWEEN ? AND ?`, m.QuoteWord(column), min, max) + return m.callWhereBuilder(m.whereBuilder.WhereNotBetween(column, min, max)) } // WhereNotLike builds `column NOT LIKE like` statement. func (m *Model) WhereNotLike(column string, like interface{}) *Model { - return m.Wheref(`%s NOT LIKE ?`, m.QuoteWord(column), like) + return m.callWhereBuilder(m.whereBuilder.WhereNotLike(column, like)) } // WhereNot builds `column != value` statement. func (m *Model) WhereNot(column string, value interface{}) *Model { - return m.Wheref(`%s != ?`, m.QuoteWord(column), value) + return m.callWhereBuilder(m.whereBuilder.WhereNot(column, value)) } // WhereNotIn builds `column NOT IN (in)` statement. func (m *Model) WhereNotIn(column string, in interface{}) *Model { - return m.doWherefType(whereHolderTypeIn, `%s NOT IN (?)`, m.QuoteWord(column), in) + return m.callWhereBuilder(m.whereBuilder.WhereNotIn(column, in)) } // WhereNotNull builds `columns[0] IS NOT NULL AND columns[1] IS NOT NULL ...` statement. func (m *Model) WhereNotNull(columns ...string) *Model { - model := m - for _, column := range columns { - model = m.Wheref(`%s IS NOT NULL`, m.QuoteWord(column)) - } - return model + return m.callWhereBuilder(m.whereBuilder.WhereNotNull(columns...)) } diff --git a/database/gdb/gdb_model_where_prefix.go b/database/gdb/gdb_model_where_prefix.go index 382a0b1a7..1ec8a466a 100644 --- a/database/gdb/gdb_model_where_prefix.go +++ b/database/gdb/gdb_model_where_prefix.go @@ -7,93 +7,71 @@ package gdb // WherePrefix performs as Where, but it adds prefix to each field in where statement. -// Eg: -// WherePrefix("order", "status", "paid") => WHERE `order`.`status`='paid' -// WherePrefix("order", struct{Status:"paid", "channel":"bank"}) => WHERE `order`.`status`='paid' AND `order`.`channel`='bank' func (m *Model) WherePrefix(prefix string, where interface{}, args ...interface{}) *Model { - model := m.getModel() - if model.whereHolder == nil { - model.whereHolder = make([]ModelWhereHolder, 0) - } - model.whereHolder = append(model.whereHolder, ModelWhereHolder{ - Type: whereHolderTypeDefault, - Operator: whereHolderOperatorWhere, - Where: where, - Args: args, - Prefix: prefix, - }) - return model + return m.callWhereBuilder(m.whereBuilder.WherePrefix(prefix, where, args...)) } // WherePrefixLT builds `prefix.column < value` statement. func (m *Model) WherePrefixLT(prefix string, column string, value interface{}) *Model { - return m.Wheref(`%s.%s < ?`, m.QuoteWord(prefix), m.QuoteWord(column), value) + return m.callWhereBuilder(m.whereBuilder.WherePrefixLT(prefix, column, value)) } // WherePrefixLTE builds `prefix.column <= value` statement. func (m *Model) WherePrefixLTE(prefix string, column string, value interface{}) *Model { - return m.Wheref(`%s.%s <= ?`, m.QuoteWord(prefix), m.QuoteWord(column), value) + return m.callWhereBuilder(m.whereBuilder.WherePrefixLTE(prefix, column, value)) } // WherePrefixGT builds `prefix.column > value` statement. func (m *Model) WherePrefixGT(prefix string, column string, value interface{}) *Model { - return m.Wheref(`%s.%s > ?`, m.QuoteWord(prefix), m.QuoteWord(column), value) + return m.callWhereBuilder(m.whereBuilder.WherePrefixGT(prefix, column, value)) } // WherePrefixGTE builds `prefix.column >= value` statement. func (m *Model) WherePrefixGTE(prefix string, column string, value interface{}) *Model { - return m.Wheref(`%s.%s >= ?`, m.QuoteWord(prefix), m.QuoteWord(column), value) + return m.callWhereBuilder(m.whereBuilder.WherePrefixGTE(prefix, column, value)) } // WherePrefixBetween builds `prefix.column BETWEEN min AND max` statement. func (m *Model) WherePrefixBetween(prefix string, column string, min, max interface{}) *Model { - return m.Wheref(`%s.%s BETWEEN ? AND ?`, m.QuoteWord(prefix), m.QuoteWord(column), min, max) + return m.callWhereBuilder(m.whereBuilder.WherePrefixBetween(prefix, column, min, max)) } // WherePrefixLike builds `prefix.column LIKE like` statement. func (m *Model) WherePrefixLike(prefix string, column string, like interface{}) *Model { - return m.Wheref(`%s.%s LIKE ?`, m.QuoteWord(prefix), m.QuoteWord(column), like) + return m.callWhereBuilder(m.whereBuilder.WherePrefixLike(prefix, column, like)) } // WherePrefixIn builds `prefix.column IN (in)` statement. func (m *Model) WherePrefixIn(prefix string, column string, in interface{}) *Model { - return m.doWherefType(whereHolderTypeIn, `%s.%s IN (?)`, m.QuoteWord(prefix), m.QuoteWord(column), in) + return m.callWhereBuilder(m.whereBuilder.WherePrefixIn(prefix, column, in)) } // WherePrefixNull builds `prefix.columns[0] IS NULL AND prefix.columns[1] IS NULL ...` statement. func (m *Model) WherePrefixNull(prefix string, columns ...string) *Model { - model := m - for _, column := range columns { - model = m.Wheref(`%s.%s IS NULL`, m.QuoteWord(prefix), m.QuoteWord(column)) - } - return model + return m.callWhereBuilder(m.whereBuilder.WherePrefixNull(prefix, columns...)) } // WherePrefixNotBetween builds `prefix.column NOT BETWEEN min AND max` statement. func (m *Model) WherePrefixNotBetween(prefix string, column string, min, max interface{}) *Model { - return m.Wheref(`%s.%s NOT BETWEEN ? AND ?`, m.QuoteWord(prefix), m.QuoteWord(column), min, max) + return m.callWhereBuilder(m.whereBuilder.WherePrefixNotBetween(prefix, column, min, max)) } // WherePrefixNotLike builds `prefix.column NOT LIKE like` statement. func (m *Model) WherePrefixNotLike(prefix string, column string, like interface{}) *Model { - return m.Wheref(`%s.%s NOT LIKE ?`, m.QuoteWord(prefix), m.QuoteWord(column), like) + return m.callWhereBuilder(m.whereBuilder.WherePrefixNotLike(prefix, column, like)) } // WherePrefixNot builds `prefix.column != value` statement. func (m *Model) WherePrefixNot(prefix string, column string, value interface{}) *Model { - return m.Wheref(`%s.%s != ?`, m.QuoteWord(prefix), m.QuoteWord(column), value) + return m.callWhereBuilder(m.whereBuilder.WherePrefixNot(prefix, column, value)) } // WherePrefixNotIn builds `prefix.column NOT IN (in)` statement. func (m *Model) WherePrefixNotIn(prefix string, column string, in interface{}) *Model { - return m.doWherefType(whereHolderTypeIn, `%s.%s NOT IN (?)`, m.QuoteWord(prefix), m.QuoteWord(column), in) + return m.callWhereBuilder(m.whereBuilder.WherePrefixNot(prefix, column, in)) } // WherePrefixNotNull builds `prefix.columns[0] IS NOT NULL AND prefix.columns[1] IS NOT NULL ...` statement. func (m *Model) WherePrefixNotNull(prefix string, columns ...string) *Model { - model := m - for _, column := range columns { - model = m.Wheref(`%s.%s IS NOT NULL`, m.QuoteWord(prefix), m.QuoteWord(column)) - } - return model + return m.callWhereBuilder(m.whereBuilder.WherePrefixNotNull(prefix, columns...)) } diff --git a/database/gdb/gdb_model_whereor.go b/database/gdb/gdb_model_whereor.go index 3d288459e..9dcf14ae6 100644 --- a/database/gdb/gdb_model_whereor.go +++ b/database/gdb/gdb_model_whereor.go @@ -6,113 +6,72 @@ package gdb -import ( - "fmt" - - "github.com/gogf/gf/v2/text/gstr" -) - -// WhereOr adds "OR" condition to the where statement. -func (m *Model) doWhereOrType(t string, where interface{}, args ...interface{}) *Model { - model := m.getModel() - if model.whereHolder == nil { - model.whereHolder = make([]ModelWhereHolder, 0) - } - model.whereHolder = append(model.whereHolder, ModelWhereHolder{ - Type: t, - Operator: whereHolderOperatorOr, - Where: where, - Args: args, - }) - return model -} - -// WhereOrf builds `OR` condition string using fmt.Sprintf and arguments. -func (m *Model) doWhereOrfType(t string, format string, args ...interface{}) *Model { - var ( - placeHolderCount = gstr.Count(format, "?") - conditionStr = fmt.Sprintf(format, args[:len(args)-placeHolderCount]...) - ) - return m.doWhereOrType(t, conditionStr, args[len(args)-placeHolderCount:]...) -} - // WhereOr adds "OR" condition to the where statement. func (m *Model) WhereOr(where interface{}, args ...interface{}) *Model { - return m.doWhereOrType(``, where, args...) + return m.callWhereBuilder(m.whereBuilder.WhereOr(where, args...)) } // WhereOrf builds `OR` condition string using fmt.Sprintf and arguments. -// Eg: -// WhereOrf(`amount WHERE xxx OR `amount`<100 and status='paid' -// WhereOrf(`amount<%d and status=%s`, 100, "paid") => WHERE xxx OR `amount`<100 and status='paid' func (m *Model) WhereOrf(format string, args ...interface{}) *Model { - return m.doWhereOrfType(``, format, args...) + return m.callWhereBuilder(m.whereBuilder.WhereOrf(format, args...)) } // WhereOrLT builds `column < value` statement in `OR` conditions.. func (m *Model) WhereOrLT(column string, value interface{}) *Model { - return m.WhereOrf(`%s < ?`, column, value) + return m.callWhereBuilder(m.whereBuilder.WhereOrLT(column, value)) } // WhereOrLTE builds `column <= value` statement in `OR` conditions.. func (m *Model) WhereOrLTE(column string, value interface{}) *Model { - return m.WhereOrf(`%s <= ?`, column, value) + return m.callWhereBuilder(m.whereBuilder.WhereOrLTE(column, value)) } // WhereOrGT builds `column > value` statement in `OR` conditions.. func (m *Model) WhereOrGT(column string, value interface{}) *Model { - return m.WhereOrf(`%s > ?`, column, value) + return m.callWhereBuilder(m.whereBuilder.WhereOrGT(column, value)) } // WhereOrGTE builds `column >= value` statement in `OR` conditions.. func (m *Model) WhereOrGTE(column string, value interface{}) *Model { - return m.WhereOrf(`%s >= ?`, column, value) + return m.callWhereBuilder(m.whereBuilder.WhereOrGTE(column, value)) } // WhereOrBetween builds `column BETWEEN min AND max` statement in `OR` conditions. func (m *Model) WhereOrBetween(column string, min, max interface{}) *Model { - return m.WhereOrf(`%s BETWEEN ? AND ?`, m.QuoteWord(column), min, max) + return m.callWhereBuilder(m.whereBuilder.WhereOrBetween(column, min, max)) } // WhereOrLike builds `column LIKE like` statement in `OR` conditions. func (m *Model) WhereOrLike(column string, like interface{}) *Model { - return m.WhereOrf(`%s LIKE ?`, m.QuoteWord(column), like) + return m.callWhereBuilder(m.whereBuilder.WhereOrLike(column, like)) } // WhereOrIn builds `column IN (in)` statement in `OR` conditions. func (m *Model) WhereOrIn(column string, in interface{}) *Model { - return m.doWhereOrfType(whereHolderTypeIn, `%s IN (?)`, m.QuoteWord(column), in) + return m.callWhereBuilder(m.whereBuilder.WhereOrIn(column, in)) } // WhereOrNull builds `columns[0] IS NULL OR columns[1] IS NULL ...` statement in `OR` conditions. func (m *Model) WhereOrNull(columns ...string) *Model { - model := m - for _, column := range columns { - model = m.WhereOrf(`%s IS NULL`, m.QuoteWord(column)) - } - return model + return m.callWhereBuilder(m.whereBuilder.WhereOrNull(columns...)) } // WhereOrNotBetween builds `column NOT BETWEEN min AND max` statement in `OR` conditions. func (m *Model) WhereOrNotBetween(column string, min, max interface{}) *Model { - return m.WhereOrf(`%s NOT BETWEEN ? AND ?`, m.QuoteWord(column), min, max) + return m.callWhereBuilder(m.whereBuilder.WhereOrNotBetween(column, min, max)) } // WhereOrNotLike builds `column NOT LIKE like` statement in `OR` conditions. func (m *Model) WhereOrNotLike(column string, like interface{}) *Model { - return m.WhereOrf(`%s NOT LIKE ?`, m.QuoteWord(column), like) + return m.callWhereBuilder(m.whereBuilder.WhereOrNotLike(column, like)) } // WhereOrNotIn builds `column NOT IN (in)` statement. func (m *Model) WhereOrNotIn(column string, in interface{}) *Model { - return m.doWhereOrfType(whereHolderTypeIn, `%s NOT IN (?)`, m.QuoteWord(column), in) + return m.callWhereBuilder(m.whereBuilder.WhereOrNotIn(column, in)) } // WhereOrNotNull builds `columns[0] IS NOT NULL OR columns[1] IS NOT NULL ...` statement in `OR` conditions. func (m *Model) WhereOrNotNull(columns ...string) *Model { - model := m - for _, column := range columns { - model = m.WhereOrf(`%s IS NOT NULL`, m.QuoteWord(column)) - } - return model + return m.callWhereBuilder(m.whereBuilder.WhereOrNotNull(columns...)) } diff --git a/database/gdb/gdb_model_whereor_prefix.go b/database/gdb/gdb_model_whereor_prefix.go index 7230895cb..ebf5ad575 100644 --- a/database/gdb/gdb_model_whereor_prefix.go +++ b/database/gdb/gdb_model_whereor_prefix.go @@ -7,88 +7,66 @@ package gdb // WhereOrPrefix performs as WhereOr, but it adds prefix to each field in where statement. -// Eg: -// WhereOrPrefix("order", "status", "paid") => WHERE xxx OR (`order`.`status`='paid') -// WhereOrPrefix("order", struct{Status:"paid", "channel":"bank"}) => WHERE xxx OR (`order`.`status`='paid' AND `order`.`channel`='bank') func (m *Model) WhereOrPrefix(prefix string, where interface{}, args ...interface{}) *Model { - model := m.getModel() - if model.whereHolder == nil { - model.whereHolder = make([]ModelWhereHolder, 0) - } - model.whereHolder = append(model.whereHolder, ModelWhereHolder{ - Type: whereHolderTypeDefault, - Operator: whereHolderOperatorOr, - Where: where, - Args: args, - Prefix: prefix, - }) - return model + return m.callWhereBuilder(m.whereBuilder.WhereOrPrefix(prefix, where, args...)) } // WhereOrPrefixLT builds `prefix.column < value` statement in `OR` conditions.. func (m *Model) WhereOrPrefixLT(prefix string, column string, value interface{}) *Model { - return m.WhereOrf(`%s.%s < ?`, m.QuoteWord(prefix), m.QuoteWord(column), value) + return m.callWhereBuilder(m.whereBuilder.WhereOrPrefixLT(prefix, column, value)) } // WhereOrPrefixLTE builds `prefix.column <= value` statement in `OR` conditions.. func (m *Model) WhereOrPrefixLTE(prefix string, column string, value interface{}) *Model { - return m.WhereOrf(`%s.%s <= ?`, m.QuoteWord(prefix), m.QuoteWord(column), value) + return m.callWhereBuilder(m.whereBuilder.WhereOrPrefixLTE(prefix, column, value)) } // WhereOrPrefixGT builds `prefix.column > value` statement in `OR` conditions.. func (m *Model) WhereOrPrefixGT(prefix string, column string, value interface{}) *Model { - return m.WhereOrf(`%s.%s > ?`, m.QuoteWord(prefix), m.QuoteWord(column), value) + return m.callWhereBuilder(m.whereBuilder.WhereOrPrefixGT(prefix, column, value)) } // WhereOrPrefixGTE builds `prefix.column >= value` statement in `OR` conditions.. func (m *Model) WhereOrPrefixGTE(prefix string, column string, value interface{}) *Model { - return m.WhereOrf(`%s.%s >= ?`, m.QuoteWord(prefix), m.QuoteWord(column), value) + return m.callWhereBuilder(m.whereBuilder.WhereOrPrefixGTE(prefix, column, value)) } // WhereOrPrefixBetween builds `prefix.column BETWEEN min AND max` statement in `OR` conditions. func (m *Model) WhereOrPrefixBetween(prefix string, column string, min, max interface{}) *Model { - return m.WhereOrf(`%s.%s BETWEEN ? AND ?`, m.QuoteWord(prefix), m.QuoteWord(column), min, max) + return m.callWhereBuilder(m.whereBuilder.WhereOrPrefixBetween(prefix, column, min, max)) } // WhereOrPrefixLike builds `prefix.column LIKE like` statement in `OR` conditions. func (m *Model) WhereOrPrefixLike(prefix string, column string, like interface{}) *Model { - return m.WhereOrf(`%s.%s LIKE ?`, m.QuoteWord(prefix), m.QuoteWord(column), like) + return m.callWhereBuilder(m.whereBuilder.WhereOrPrefixLike(prefix, column, like)) } // WhereOrPrefixIn builds `prefix.column IN (in)` statement in `OR` conditions. func (m *Model) WhereOrPrefixIn(prefix string, column string, in interface{}) *Model { - return m.doWhereOrfType(whereHolderTypeIn, `%s.%s IN (?)`, m.QuoteWord(prefix), m.QuoteWord(column), in) + return m.callWhereBuilder(m.whereBuilder.WhereOrPrefixIn(prefix, column, in)) } // WhereOrPrefixNull builds `prefix.columns[0] IS NULL OR prefix.columns[1] IS NULL ...` statement in `OR` conditions. func (m *Model) WhereOrPrefixNull(prefix string, columns ...string) *Model { - model := m - for _, column := range columns { - model = m.WhereOrf(`%s.%s IS NULL`, m.QuoteWord(prefix), m.QuoteWord(column)) - } - return model + return m.callWhereBuilder(m.whereBuilder.WhereOrPrefixNull(prefix, columns...)) } // WhereOrPrefixNotBetween builds `prefix.column NOT BETWEEN min AND max` statement in `OR` conditions. func (m *Model) WhereOrPrefixNotBetween(prefix string, column string, min, max interface{}) *Model { - return m.WhereOrf(`%s.%s NOT BETWEEN ? AND ?`, m.QuoteWord(prefix), m.QuoteWord(column), min, max) + return m.callWhereBuilder(m.whereBuilder.WhereOrPrefixNotBetween(prefix, column, min, max)) } // WhereOrPrefixNotLike builds `prefix.column NOT LIKE like` statement in `OR` conditions. func (m *Model) WhereOrPrefixNotLike(prefix string, column string, like interface{}) *Model { - return m.WhereOrf(`%s.%s NOT LIKE ?`, m.QuoteWord(prefix), m.QuoteWord(column), like) + return m.callWhereBuilder(m.whereBuilder.WhereOrPrefixNotLike(prefix, column, like)) } // WhereOrPrefixNotIn builds `prefix.column NOT IN (in)` statement. func (m *Model) WhereOrPrefixNotIn(prefix string, column string, in interface{}) *Model { - return m.doWhereOrfType(whereHolderTypeIn, `%s.%s NOT IN (?)`, m.QuoteWord(prefix), m.QuoteWord(column), in) + return m.callWhereBuilder(m.whereBuilder.WhereOrPrefixNotIn(prefix, column, in)) } // WhereOrPrefixNotNull builds `prefix.columns[0] IS NOT NULL OR prefix.columns[1] IS NOT NULL ...` statement in `OR` conditions. func (m *Model) WhereOrPrefixNotNull(prefix string, columns ...string) *Model { - model := m - for _, column := range columns { - model = m.WhereOrf(`%s.%s IS NOT NULL`, m.QuoteWord(prefix), m.QuoteWord(column)) - } - return model + return m.callWhereBuilder(m.whereBuilder.WhereOrPrefixNotNull(prefix, columns...)) } From ec3a4532b8c81203aaa792a2554ef5643820e2d8 Mon Sep 17 00:00:00 2001 From: John Guo Date: Fri, 6 May 2022 10:34:08 +0800 Subject: [PATCH 2/7] comment updates for package clilckhouse --- contrib/drivers/clickhouse/clickhouse.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contrib/drivers/clickhouse/clickhouse.go b/contrib/drivers/clickhouse/clickhouse.go index cc54a65a0..0af84eaea 100644 --- a/contrib/drivers/clickhouse/clickhouse.go +++ b/contrib/drivers/clickhouse/clickhouse.go @@ -216,10 +216,10 @@ func (d *Driver) DoFilter( ctx context.Context, link gdb.Link, originSql string, args []interface{}, ) (newSql string, newArgs []interface{}, err error) { // It replaces STD SQL to Clickhouse SQL grammar. - // MySQL eg: UPDATE visits SET xxx - // Clickhouse eg: ALTER TABLE visits UPDATE xxx - // MySQL eg: DELETE FROM VISIT - // Clickhouse eg: ALTER TABLE VISIT DELETE WHERE filter_expr + // MySQL eg: UPDATE `table` SET xxx + // Clickhouse eg: ALTER TABLE `table` UPDATE xxx + // MySQL eg: DELETE FROM `table` + // Clickhouse eg: ALTER TABLE `table` DELETE WHERE filter_expr result, err := gregex.MatchString("(?i)^UPDATE|DELETE", originSql) if err != nil { return "", nil, err From 8c40a53b8014ba5df8760ff927d8938bcfeffd2b Mon Sep 17 00:00:00 2001 From: John Guo Date: Fri, 6 May 2022 22:21:43 +0800 Subject: [PATCH 3/7] improve WhereBuilder for package gdb --- database/gdb/gdb_model.go | 5 +- database/gdb/gdb_model_builder.go | 59 +++++-------------- database/gdb/gdb_model_builder_where.go | 24 ++++++-- database/gdb/gdb_model_select.go | 18 ++++++ ...core_sharding.go => gdb_model_sharding.go} | 0 .../gdb_z_mysql_feature_model_builder_test.go | 46 +++++++++++++++ 6 files changed, 103 insertions(+), 49 deletions(-) rename database/gdb/{gdb_core_sharding.go => gdb_model_sharding.go} (100%) create mode 100644 database/gdb/gdb_z_mysql_feature_model_builder_test.go diff --git a/database/gdb/gdb_model.go b/database/gdb/gdb_model.go index 4769aaa4d..7ce229346 100644 --- a/database/gdb/gdb_model.go +++ b/database/gdb/gdb_model.go @@ -136,6 +136,9 @@ func (c *Core) Model(tableNameQueryOrStruct ...interface{}) *Model { extraArgs: extraArgs, } m.whereBuilder = m.Builder() + // Assign the safe attribute of WhereBuilder to nil, + // to make it use the safe attribute of its bound model. + m.whereBuilder.safe = nil if defaultModelSafe { m.safe = true } @@ -262,7 +265,7 @@ func (m *Model) Clone() *Model { // Basic attributes copy. *newModel = *m // WhereBuilder copy, note the attribute pointer. - newModel.whereBuilder = m.whereBuilder.Clone() + newModel.whereBuilder = m.whereBuilder.clone() newModel.whereBuilder.model = newModel // Shallow copy slice attributes. if n := len(m.extraArgs); n > 0 { diff --git a/database/gdb/gdb_model_builder.go b/database/gdb/gdb_model_builder.go index 0a1ccd6ff..7687da675 100644 --- a/database/gdb/gdb_model_builder.go +++ b/database/gdb/gdb_model_builder.go @@ -8,12 +8,11 @@ package gdb import ( "fmt" - - "github.com/gogf/gf/v2/text/gstr" ) type WhereBuilder struct { - model *Model // Bound to parent model. + safe *bool // If nil, it uses the safe attribute of its model. + model *Model // A WhereBuilder should be bound to certain Model. whereHolder []WhereHolder // Condition strings for where operation. } @@ -27,7 +26,10 @@ type WhereHolder struct { } func (m *Model) Builder() *WhereBuilder { + // The WhereBuilder is safe in default when it is created using Builder(). + var isSafe = true b := &WhereBuilder{ + safe: &isSafe, model: m, whereHolder: make([]WhereHolder, 0), } @@ -37,15 +39,22 @@ func (m *Model) Builder() *WhereBuilder { // getBuilder creates and returns a cloned WhereBuilder of current WhereBuilder if `safe` is true, // or else it returns the current WhereBuilder. func (b *WhereBuilder) getBuilder() *WhereBuilder { - if !b.model.safe { + var isSafe bool + if b.safe != nil { + isSafe = *b.safe + } else { + isSafe = b.model.safe + } + if !isSafe { return b } else { - return b.Clone() + return b.clone() } } -func (b *WhereBuilder) Clone() *WhereBuilder { +func (b *WhereBuilder) clone() *WhereBuilder { newBuilder := b.model.Builder() + newBuilder.safe = b.safe newBuilder.whereHolder = make([]WhereHolder, len(b.whereHolder)) copy(newBuilder.whereHolder, b.whereHolder) return newBuilder @@ -63,24 +72,7 @@ func (b *WhereBuilder) Build() (conditionWhere string, conditionArgs []interface holder.Prefix = autoPrefix } switch holder.Operator { - case whereHolderOperatorWhere: - if conditionWhere == "" { - newWhere, newArgs := formatWhereHolder(ctx, b.model.db, formatWhereHolderInput{ - WhereHolder: holder, - OmitNil: b.model.option&optionOmitNilWhere > 0, - OmitEmpty: b.model.option&optionOmitEmptyWhere > 0, - Schema: b.model.schema, - Table: tableForMappingAndFiltering, - }) - if len(newWhere) > 0 { - conditionWhere = newWhere - conditionArgs = newArgs - } - continue - } - fallthrough - - case whereHolderOperatorAnd: + case whereHolderOperatorWhere, whereHolderOperatorAnd: newWhere, newArgs := formatWhereHolder(ctx, b.model.db, formatWhereHolderInput{ WhereHolder: holder, OmitNil: b.model.option&optionOmitNilWhere > 0, @@ -120,24 +112,5 @@ func (b *WhereBuilder) Build() (conditionWhere string, conditionArgs []interface } } } - // Soft deletion. - softDeletingCondition := b.model.getConditionForSoftDeleting() - if b.model.rawSql != "" && conditionWhere != "" { - if gstr.ContainsI(b.model.rawSql, " WHERE ") { - conditionWhere = " AND " + conditionWhere - } else { - conditionWhere = " WHERE " + conditionWhere - } - } else if !b.model.unscoped && softDeletingCondition != "" { - if conditionWhere == "" { - conditionWhere = fmt.Sprintf(` WHERE %s`, softDeletingCondition) - } else { - conditionWhere = fmt.Sprintf(` WHERE (%s) AND %s`, conditionWhere, softDeletingCondition) - } - } else { - if conditionWhere != "" { - conditionWhere = " WHERE " + conditionWhere - } - } return } diff --git a/database/gdb/gdb_model_builder_where.go b/database/gdb/gdb_model_builder_where.go index 5803f8d82..0a4e45de7 100644 --- a/database/gdb/gdb_model_builder_where.go +++ b/database/gdb/gdb_model_builder_where.go @@ -15,20 +15,20 @@ import ( // doWhereType sets the condition statement for the model. The parameter `where` can be type of // string/map/gmap/slice/struct/*struct, etc. Note that, if it's called more than one times, // multiple conditions will be joined into where statement using "AND". -func (b *WhereBuilder) doWhereType(t string, where interface{}, args ...interface{}) *WhereBuilder { +func (b *WhereBuilder) doWhereType(whereType string, where interface{}, args ...interface{}) *WhereBuilder { builder := b.getBuilder() if builder.whereHolder == nil { builder.whereHolder = make([]WhereHolder, 0) } - if t == "" { + if whereType == "" { if len(args) == 0 { - t = whereHolderTypeNoArgs + whereType = whereHolderTypeNoArgs } else { - t = whereHolderTypeDefault + whereType = whereHolderTypeDefault } } builder.whereHolder = append(builder.whereHolder, WhereHolder{ - Type: t, + Type: whereType, Operator: whereHolderOperatorWhere, Where: where, Args: args, @@ -59,6 +59,20 @@ func (b *WhereBuilder) doWherefType(t string, format string, args ...interface{} // Where("age IN(?,?)", 18, 50) // Where(User{ Id : 1, UserName : "john"}). func (b *WhereBuilder) Where(where interface{}, args ...interface{}) *WhereBuilder { + var builder *WhereBuilder + switch v := where.(type) { + case WhereBuilder: + builder = &v + case *WhereBuilder: + builder = v + } + if builder != nil { + conditionWhere, conditionArgs := builder.Build() + if len(b.whereHolder) == 0 { + conditionWhere = "(" + conditionWhere + ")" + } + return b.doWhereType(``, conditionWhere, conditionArgs...) + } return b.doWhereType(``, where, args...) } diff --git a/database/gdb/gdb_model_select.go b/database/gdb/gdb_model_select.go index b7f782f4a..d39fc4728 100644 --- a/database/gdb/gdb_model_select.go +++ b/database/gdb/gdb_model_select.go @@ -616,6 +616,24 @@ func (m *Model) formatCondition(ctx context.Context, limit1 bool, isCountStateme } // WHERE conditionWhere, conditionArgs = m.whereBuilder.Build() + softDeletingCondition := m.getConditionForSoftDeleting() + if m.rawSql != "" && conditionWhere != "" { + if gstr.ContainsI(m.rawSql, " WHERE ") { + conditionWhere = " AND " + conditionWhere + } else { + conditionWhere = " WHERE " + conditionWhere + } + } else if !m.unscoped && softDeletingCondition != "" { + if conditionWhere == "" { + conditionWhere = fmt.Sprintf(` WHERE %s`, softDeletingCondition) + } else { + conditionWhere = fmt.Sprintf(` WHERE (%s) AND %s`, conditionWhere, softDeletingCondition) + } + } else { + if conditionWhere != "" { + conditionWhere = " WHERE " + conditionWhere + } + } // HAVING. if len(m.having) > 0 { havingHolder := WhereHolder{ diff --git a/database/gdb/gdb_core_sharding.go b/database/gdb/gdb_model_sharding.go similarity index 100% rename from database/gdb/gdb_core_sharding.go rename to database/gdb/gdb_model_sharding.go diff --git a/database/gdb/gdb_z_mysql_feature_model_builder_test.go b/database/gdb/gdb_z_mysql_feature_model_builder_test.go new file mode 100644 index 000000000..43e319731 --- /dev/null +++ b/database/gdb/gdb_z_mysql_feature_model_builder_test.go @@ -0,0 +1,46 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gdb_test + +import ( + "testing" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/test/gtest" +) + +func Test_Model_Builder(t *testing.T) { + table := createInitTable() + defer dropTable(table) + + gtest.C(t, func(t *gtest.T) { + m := db.Model(table) + b := m.Builder() + + all, err := m.Where( + b.Where("id", g.Slice{1, 2, 3}).WhereOr("id", g.Slice{4, 5, 6}), + ).All() + t.AssertNil(err) + t.Assert(len(all), 6) + }) + + gtest.C(t, func(t *gtest.T) { + m := db.Model(table) + b := m.Builder() + + all, err := m.Where( + b.Where("id", g.Slice{1, 2, 3}).WhereOr("id", g.Slice{4, 5, 6}), + ).Where( + b.Where("id", g.Slice{2, 3}).WhereOr("id", g.Slice{5, 6}), + ).Where( + b.Where("id", g.Slice{3}).Where("id", g.Slice{1, 2, 3}), + ).All() + t.AssertNil(err) + t.Assert(len(all), 1) + }) + +} From eaae7f46d2397f993c86eee7490762e28cb2519e Mon Sep 17 00:00:00 2001 From: John Guo Date: Sat, 7 May 2022 14:26:56 +0800 Subject: [PATCH 4/7] improve WhereBuilder feature for package gdb --- database/gdb/gdb_model.go | 10 ++++----- database/gdb/gdb_model_builder.go | 22 +++++++++++++++++-- database/gdb/gdb_model_builder_where.go | 16 ++------------ .../gdb/gdb_model_builder_where_prefix.go | 2 ++ database/gdb/gdb_model_builder_whereor.go | 2 ++ .../gdb/gdb_model_builder_whereor_prefix.go | 2 ++ 6 files changed, 32 insertions(+), 22 deletions(-) diff --git a/database/gdb/gdb_model.go b/database/gdb/gdb_model.go index 7ce229346..70429a339 100644 --- a/database/gdb/gdb_model.go +++ b/database/gdb/gdb_model.go @@ -136,9 +136,7 @@ func (c *Core) Model(tableNameQueryOrStruct ...interface{}) *Model { extraArgs: extraArgs, } m.whereBuilder = m.Builder() - // Assign the safe attribute of WhereBuilder to nil, - // to make it use the safe attribute of its bound model. - m.whereBuilder.safe = nil + m.whereBuilder.safe = &m.safe if defaultModelSafe { m.safe = true } @@ -253,8 +251,8 @@ func (m *Model) Schema(schema string) *Model { return model } -// Clone creates and returns a new model which is a clone of current model. -// Note that it uses deep-copy for the clone. +// Clone creates and returns a new model which is a Clone of current model. +// Note that it uses deep-copy for the Clone. func (m *Model) Clone() *Model { newModel := (*Model)(nil) if m.tx != nil { @@ -265,7 +263,7 @@ func (m *Model) Clone() *Model { // Basic attributes copy. *newModel = *m // WhereBuilder copy, note the attribute pointer. - newModel.whereBuilder = m.whereBuilder.clone() + newModel.whereBuilder = m.whereBuilder.Clone() newModel.whereBuilder.model = newModel // Shallow copy slice attributes. if n := len(m.extraArgs); n > 0 { diff --git a/database/gdb/gdb_model_builder.go b/database/gdb/gdb_model_builder.go index 7687da675..a271e33f5 100644 --- a/database/gdb/gdb_model_builder.go +++ b/database/gdb/gdb_model_builder.go @@ -48,11 +48,11 @@ func (b *WhereBuilder) getBuilder() *WhereBuilder { if !isSafe { return b } else { - return b.clone() + return b.Clone() } } -func (b *WhereBuilder) clone() *WhereBuilder { +func (b *WhereBuilder) Clone() *WhereBuilder { newBuilder := b.model.Builder() newBuilder.safe = b.safe newBuilder.whereHolder = make([]WhereHolder, len(b.whereHolder)) @@ -114,3 +114,21 @@ func (b *WhereBuilder) Build() (conditionWhere string, conditionArgs []interface } return } + +func (b *WhereBuilder) convertWrappedBuilder(where interface{}, args []interface{}) (newWhere interface{}, newArgs []interface{}) { + var builder *WhereBuilder + switch v := where.(type) { + case WhereBuilder: + builder = &v + case *WhereBuilder: + builder = v + } + if builder != nil { + conditionWhere, conditionArgs := builder.Build() + if len(b.whereHolder) == 0 { + conditionWhere = "(" + conditionWhere + ")" + } + return conditionWhere, conditionArgs + } + return where, args +} diff --git a/database/gdb/gdb_model_builder_where.go b/database/gdb/gdb_model_builder_where.go index 0a4e45de7..4980175c0 100644 --- a/database/gdb/gdb_model_builder_where.go +++ b/database/gdb/gdb_model_builder_where.go @@ -16,6 +16,8 @@ import ( // string/map/gmap/slice/struct/*struct, etc. Note that, if it's called more than one times, // multiple conditions will be joined into where statement using "AND". func (b *WhereBuilder) doWhereType(whereType string, where interface{}, args ...interface{}) *WhereBuilder { + where, args = b.convertWrappedBuilder(where, args) + builder := b.getBuilder() if builder.whereHolder == nil { builder.whereHolder = make([]WhereHolder, 0) @@ -59,20 +61,6 @@ func (b *WhereBuilder) doWherefType(t string, format string, args ...interface{} // Where("age IN(?,?)", 18, 50) // Where(User{ Id : 1, UserName : "john"}). func (b *WhereBuilder) Where(where interface{}, args ...interface{}) *WhereBuilder { - var builder *WhereBuilder - switch v := where.(type) { - case WhereBuilder: - builder = &v - case *WhereBuilder: - builder = v - } - if builder != nil { - conditionWhere, conditionArgs := builder.Build() - if len(b.whereHolder) == 0 { - conditionWhere = "(" + conditionWhere + ")" - } - return b.doWhereType(``, conditionWhere, conditionArgs...) - } return b.doWhereType(``, where, args...) } diff --git a/database/gdb/gdb_model_builder_where_prefix.go b/database/gdb/gdb_model_builder_where_prefix.go index 7cc189901..5fbebb6a0 100644 --- a/database/gdb/gdb_model_builder_where_prefix.go +++ b/database/gdb/gdb_model_builder_where_prefix.go @@ -11,6 +11,8 @@ package gdb // WherePrefix("order", "status", "paid") => WHERE `order`.`status`='paid' // WherePrefix("order", struct{Status:"paid", "channel":"bank"}) => WHERE `order`.`status`='paid' AND `order`.`channel`='bank' func (b *WhereBuilder) WherePrefix(prefix string, where interface{}, args ...interface{}) *WhereBuilder { + where, args = b.convertWrappedBuilder(where, args) + builder := b.getBuilder() if builder.whereHolder == nil { builder.whereHolder = make([]WhereHolder, 0) diff --git a/database/gdb/gdb_model_builder_whereor.go b/database/gdb/gdb_model_builder_whereor.go index 993c4def3..ebd5157b3 100644 --- a/database/gdb/gdb_model_builder_whereor.go +++ b/database/gdb/gdb_model_builder_whereor.go @@ -14,6 +14,8 @@ import ( // WhereOr adds "OR" condition to the where statement. func (b *WhereBuilder) doWhereOrType(t string, where interface{}, args ...interface{}) *WhereBuilder { + where, args = b.convertWrappedBuilder(where, args) + builder := b.getBuilder() if builder.whereHolder == nil { builder.whereHolder = make([]WhereHolder, 0) diff --git a/database/gdb/gdb_model_builder_whereor_prefix.go b/database/gdb/gdb_model_builder_whereor_prefix.go index c720b7c3f..102c4936a 100644 --- a/database/gdb/gdb_model_builder_whereor_prefix.go +++ b/database/gdb/gdb_model_builder_whereor_prefix.go @@ -11,6 +11,8 @@ package gdb // WhereOrPrefix("order", "status", "paid") => WHERE xxx OR (`order`.`status`='paid') // WhereOrPrefix("order", struct{Status:"paid", "channel":"bank"}) => WHERE xxx OR (`order`.`status`='paid' AND `order`.`channel`='bank') func (b *WhereBuilder) WhereOrPrefix(prefix string, where interface{}, args ...interface{}) *WhereBuilder { + where, args = b.convertWrappedBuilder(where, args) + builder := b.getBuilder() builder.whereHolder = append(builder.whereHolder, WhereHolder{ Type: whereHolderTypeDefault, From 5332ce4c79b590e2ccacdce914b43cb18c9089ad Mon Sep 17 00:00:00 2001 From: John Guo Date: Sat, 7 May 2022 15:11:31 +0800 Subject: [PATCH 5/7] improve WhereBuilder feature for package gdb --- database/gdb/gdb_model.go | 3 ++ database/gdb/gdb_model_builder.go | 7 ++++- database/gdb/gdb_model_builder_where.go | 2 +- .../gdb/gdb_model_builder_where_prefix.go | 2 +- database/gdb/gdb_model_builder_whereor.go | 2 +- .../gdb/gdb_model_builder_whereor_prefix.go | 2 +- database/gdb/gdb_model_where.go | 30 +++++++++++++++++++ database/gdb/gdb_model_where_prefix.go | 14 +++++++++ database/gdb/gdb_model_whereor.go | 14 +++++++++ database/gdb/gdb_model_whereor_prefix.go | 13 ++++++++ .../gdb_z_mysql_feature_model_builder_test.go | 17 +++++++++++ 11 files changed, 101 insertions(+), 5 deletions(-) diff --git a/database/gdb/gdb_model.go b/database/gdb/gdb_model.go index 70429a339..98010a070 100644 --- a/database/gdb/gdb_model.go +++ b/database/gdb/gdb_model.go @@ -17,6 +17,7 @@ import ( // Model is core struct implementing the DAO for ORM. type Model struct { + *modelWhereBuilder db DB // Underlying DB interface. tx *TX // Underlying TX interface. rawSql string // rawSql is the raw SQL string which marks a raw SQL based Model not a table based Model. @@ -52,6 +53,8 @@ type Model struct { onDuplicateEx interface{} // onDuplicateEx is used for excluding some columns ON "DUPLICATE KEY UPDATE" statement. } +type modelWhereBuilder = WhereBuilder + // ModelHandler is a function that handles given Model and returns a new Model that is custom modified. type ModelHandler func(m *Model) *Model diff --git a/database/gdb/gdb_model_builder.go b/database/gdb/gdb_model_builder.go index a271e33f5..83a95ade5 100644 --- a/database/gdb/gdb_model_builder.go +++ b/database/gdb/gdb_model_builder.go @@ -10,6 +10,7 @@ import ( "fmt" ) +// WhereBuilder holds multiple where conditions in a group. type WhereBuilder struct { safe *bool // If nil, it uses the safe attribute of its model. model *Model // A WhereBuilder should be bound to certain Model. @@ -25,6 +26,7 @@ type WhereHolder struct { Prefix string // Field prefix, eg: "user.", "order.". } +// Builder creates and returns a WhereBuilder. func (m *Model) Builder() *WhereBuilder { // The WhereBuilder is safe in default when it is created using Builder(). var isSafe = true @@ -52,6 +54,7 @@ func (b *WhereBuilder) getBuilder() *WhereBuilder { } } +// Clone clones and returns a WhereBuilder that is a copy of current one. func (b *WhereBuilder) Clone() *WhereBuilder { newBuilder := b.model.Builder() newBuilder.safe = b.safe @@ -60,6 +63,7 @@ func (b *WhereBuilder) Clone() *WhereBuilder { return newBuilder } +// Build builds current WhereBuilder and returns the condition string and parameters. func (b *WhereBuilder) Build() (conditionWhere string, conditionArgs []interface{}) { var ( ctx = b.model.GetCtx() @@ -115,7 +119,8 @@ func (b *WhereBuilder) Build() (conditionWhere string, conditionArgs []interface return } -func (b *WhereBuilder) convertWrappedBuilder(where interface{}, args []interface{}) (newWhere interface{}, newArgs []interface{}) { +// convertWhereBuilder converts parameter `where` to condition string and parameters if `where` is also a WhereBuilder. +func (b *WhereBuilder) convertWhereBuilder(where interface{}, args []interface{}) (newWhere interface{}, newArgs []interface{}) { var builder *WhereBuilder switch v := where.(type) { case WhereBuilder: diff --git a/database/gdb/gdb_model_builder_where.go b/database/gdb/gdb_model_builder_where.go index 4980175c0..4ae43f856 100644 --- a/database/gdb/gdb_model_builder_where.go +++ b/database/gdb/gdb_model_builder_where.go @@ -16,7 +16,7 @@ import ( // string/map/gmap/slice/struct/*struct, etc. Note that, if it's called more than one times, // multiple conditions will be joined into where statement using "AND". func (b *WhereBuilder) doWhereType(whereType string, where interface{}, args ...interface{}) *WhereBuilder { - where, args = b.convertWrappedBuilder(where, args) + where, args = b.convertWhereBuilder(where, args) builder := b.getBuilder() if builder.whereHolder == nil { diff --git a/database/gdb/gdb_model_builder_where_prefix.go b/database/gdb/gdb_model_builder_where_prefix.go index 5fbebb6a0..eec46db1d 100644 --- a/database/gdb/gdb_model_builder_where_prefix.go +++ b/database/gdb/gdb_model_builder_where_prefix.go @@ -11,7 +11,7 @@ package gdb // WherePrefix("order", "status", "paid") => WHERE `order`.`status`='paid' // WherePrefix("order", struct{Status:"paid", "channel":"bank"}) => WHERE `order`.`status`='paid' AND `order`.`channel`='bank' func (b *WhereBuilder) WherePrefix(prefix string, where interface{}, args ...interface{}) *WhereBuilder { - where, args = b.convertWrappedBuilder(where, args) + where, args = b.convertWhereBuilder(where, args) builder := b.getBuilder() if builder.whereHolder == nil { diff --git a/database/gdb/gdb_model_builder_whereor.go b/database/gdb/gdb_model_builder_whereor.go index ebd5157b3..1ab91a77c 100644 --- a/database/gdb/gdb_model_builder_whereor.go +++ b/database/gdb/gdb_model_builder_whereor.go @@ -14,7 +14,7 @@ import ( // WhereOr adds "OR" condition to the where statement. func (b *WhereBuilder) doWhereOrType(t string, where interface{}, args ...interface{}) *WhereBuilder { - where, args = b.convertWrappedBuilder(where, args) + where, args = b.convertWhereBuilder(where, args) builder := b.getBuilder() if builder.whereHolder == nil { diff --git a/database/gdb/gdb_model_builder_whereor_prefix.go b/database/gdb/gdb_model_builder_whereor_prefix.go index 102c4936a..75c71c207 100644 --- a/database/gdb/gdb_model_builder_whereor_prefix.go +++ b/database/gdb/gdb_model_builder_whereor_prefix.go @@ -11,7 +11,7 @@ package gdb // WhereOrPrefix("order", "status", "paid") => WHERE xxx OR (`order`.`status`='paid') // WhereOrPrefix("order", struct{Status:"paid", "channel":"bank"}) => WHERE xxx OR (`order`.`status`='paid' AND `order`.`channel`='bank') func (b *WhereBuilder) WhereOrPrefix(prefix string, where interface{}, args ...interface{}) *WhereBuilder { - where, args = b.convertWrappedBuilder(where, args) + where, args = b.convertWhereBuilder(where, args) builder := b.getBuilder() builder.whereHolder = append(builder.whereHolder, WhereHolder{ diff --git a/database/gdb/gdb_model_where.go b/database/gdb/gdb_model_where.go index 09566db91..96ea722b6 100644 --- a/database/gdb/gdb_model_where.go +++ b/database/gdb/gdb_model_where.go @@ -6,84 +6,114 @@ package gdb +// callWhereBuilder creates and returns a new Model, and sets its WhereBuilder if current Model is safe. +// It sets the WhereBuilder and returns current Model directly if it is not safe. func (m *Model) callWhereBuilder(builder *WhereBuilder) *Model { model := m.getModel() model.whereBuilder = builder return model } +// Where sets the condition statement for the builder. The parameter `where` can be type of +// string/map/gmap/slice/struct/*struct, etc. Note that, if it's called more than one times, +// multiple conditions will be joined into where statement using "AND". +// See WhereBuilder.Where. func (m *Model) Where(where interface{}, args ...interface{}) *Model { return m.callWhereBuilder(m.whereBuilder.Where(where, args...)) } +// Wheref builds condition string using fmt.Sprintf and arguments. +// Note that if the number of `args` is more than the placeholder in `format`, +// the extra `args` will be used as the where condition arguments of the Model. +// See WhereBuilder.Wheref. func (m *Model) Wheref(format string, args ...interface{}) *Model { return m.callWhereBuilder(m.whereBuilder.Wheref(format, args...)) } +// WherePri does the same logic as Model.Where except that if the parameter `where` +// is a single condition like int/string/float/slice, it treats the condition as the primary +// key value. That is, if primary key is "id" and given `where` parameter as "123", the +// WherePri function treats the condition as "id=123", but Model.Where treats the condition +// as string "123". +// See WhereBuilder.WherePri. func (m *Model) WherePri(where interface{}, args ...interface{}) *Model { return m.callWhereBuilder(m.whereBuilder.WherePri(where, args...)) } +// WhereLT builds `column < value` statement. +// See WhereBuilder.WhereLT. func (m *Model) WhereLT(column string, value interface{}) *Model { return m.callWhereBuilder(m.whereBuilder.WhereLT(column, value)) } // WhereLTE builds `column <= value` statement. +// See WhereBuilder.WhereLTE. func (m *Model) WhereLTE(column string, value interface{}) *Model { return m.callWhereBuilder(m.whereBuilder.WhereLTE(column, value)) } // WhereGT builds `column > value` statement. +// See WhereBuilder.WhereGT. func (m *Model) WhereGT(column string, value interface{}) *Model { return m.callWhereBuilder(m.whereBuilder.WhereGT(column, value)) } // WhereGTE builds `column >= value` statement. +// See WhereBuilder.WhereGTE. func (m *Model) WhereGTE(column string, value interface{}) *Model { return m.callWhereBuilder(m.whereBuilder.WhereGTE(column, value)) } // WhereBetween builds `column BETWEEN min AND max` statement. +// See WhereBuilder.WhereBetween. func (m *Model) WhereBetween(column string, min, max interface{}) *Model { return m.callWhereBuilder(m.whereBuilder.WhereBetween(column, min, max)) } // WhereLike builds `column LIKE like` statement. +// See WhereBuilder.WhereLike. func (m *Model) WhereLike(column string, like string) *Model { return m.callWhereBuilder(m.whereBuilder.WhereLike(column, like)) } // WhereIn builds `column IN (in)` statement. +// See WhereBuilder.WhereIn. func (m *Model) WhereIn(column string, in interface{}) *Model { return m.callWhereBuilder(m.whereBuilder.WhereIn(column, in)) } // WhereNull builds `columns[0] IS NULL AND columns[1] IS NULL ...` statement. +// See WhereBuilder.WhereNull. func (m *Model) WhereNull(columns ...string) *Model { return m.callWhereBuilder(m.whereBuilder.WhereNull(columns...)) } // WhereNotBetween builds `column NOT BETWEEN min AND max` statement. +// See WhereBuilder.WhereNotBetween. func (m *Model) WhereNotBetween(column string, min, max interface{}) *Model { return m.callWhereBuilder(m.whereBuilder.WhereNotBetween(column, min, max)) } // WhereNotLike builds `column NOT LIKE like` statement. +// See WhereBuilder.WhereNotLike. func (m *Model) WhereNotLike(column string, like interface{}) *Model { return m.callWhereBuilder(m.whereBuilder.WhereNotLike(column, like)) } // WhereNot builds `column != value` statement. +// See WhereBuilder.WhereNot. func (m *Model) WhereNot(column string, value interface{}) *Model { return m.callWhereBuilder(m.whereBuilder.WhereNot(column, value)) } // WhereNotIn builds `column NOT IN (in)` statement. +// See WhereBuilder.WhereNotIn. func (m *Model) WhereNotIn(column string, in interface{}) *Model { return m.callWhereBuilder(m.whereBuilder.WhereNotIn(column, in)) } // WhereNotNull builds `columns[0] IS NOT NULL AND columns[1] IS NOT NULL ...` statement. +// See WhereBuilder.WhereNotNull. func (m *Model) WhereNotNull(columns ...string) *Model { return m.callWhereBuilder(m.whereBuilder.WhereNotNull(columns...)) } diff --git a/database/gdb/gdb_model_where_prefix.go b/database/gdb/gdb_model_where_prefix.go index 1ec8a466a..99dfd5dfb 100644 --- a/database/gdb/gdb_model_where_prefix.go +++ b/database/gdb/gdb_model_where_prefix.go @@ -7,71 +7,85 @@ package gdb // WherePrefix performs as Where, but it adds prefix to each field in where statement. +// See WhereBuilder.WherePrefix. func (m *Model) WherePrefix(prefix string, where interface{}, args ...interface{}) *Model { return m.callWhereBuilder(m.whereBuilder.WherePrefix(prefix, where, args...)) } // WherePrefixLT builds `prefix.column < value` statement. +// See WhereBuilder.WherePrefixLT. func (m *Model) WherePrefixLT(prefix string, column string, value interface{}) *Model { return m.callWhereBuilder(m.whereBuilder.WherePrefixLT(prefix, column, value)) } // WherePrefixLTE builds `prefix.column <= value` statement. +// See WhereBuilder.WherePrefixLTE. func (m *Model) WherePrefixLTE(prefix string, column string, value interface{}) *Model { return m.callWhereBuilder(m.whereBuilder.WherePrefixLTE(prefix, column, value)) } // WherePrefixGT builds `prefix.column > value` statement. +// See WhereBuilder.WherePrefixGT. func (m *Model) WherePrefixGT(prefix string, column string, value interface{}) *Model { return m.callWhereBuilder(m.whereBuilder.WherePrefixGT(prefix, column, value)) } // WherePrefixGTE builds `prefix.column >= value` statement. +// See WhereBuilder.WherePrefixGTE. func (m *Model) WherePrefixGTE(prefix string, column string, value interface{}) *Model { return m.callWhereBuilder(m.whereBuilder.WherePrefixGTE(prefix, column, value)) } // WherePrefixBetween builds `prefix.column BETWEEN min AND max` statement. +// See WhereBuilder.WherePrefixBetween. func (m *Model) WherePrefixBetween(prefix string, column string, min, max interface{}) *Model { return m.callWhereBuilder(m.whereBuilder.WherePrefixBetween(prefix, column, min, max)) } // WherePrefixLike builds `prefix.column LIKE like` statement. +// See WhereBuilder.WherePrefixLike. func (m *Model) WherePrefixLike(prefix string, column string, like interface{}) *Model { return m.callWhereBuilder(m.whereBuilder.WherePrefixLike(prefix, column, like)) } // WherePrefixIn builds `prefix.column IN (in)` statement. +// See WhereBuilder.WherePrefixIn. func (m *Model) WherePrefixIn(prefix string, column string, in interface{}) *Model { return m.callWhereBuilder(m.whereBuilder.WherePrefixIn(prefix, column, in)) } // WherePrefixNull builds `prefix.columns[0] IS NULL AND prefix.columns[1] IS NULL ...` statement. +// See WhereBuilder.WherePrefixNull. func (m *Model) WherePrefixNull(prefix string, columns ...string) *Model { return m.callWhereBuilder(m.whereBuilder.WherePrefixNull(prefix, columns...)) } // WherePrefixNotBetween builds `prefix.column NOT BETWEEN min AND max` statement. +// See WhereBuilder.WherePrefixNotBetween. func (m *Model) WherePrefixNotBetween(prefix string, column string, min, max interface{}) *Model { return m.callWhereBuilder(m.whereBuilder.WherePrefixNotBetween(prefix, column, min, max)) } // WherePrefixNotLike builds `prefix.column NOT LIKE like` statement. +// See WhereBuilder.WherePrefixNotLike. func (m *Model) WherePrefixNotLike(prefix string, column string, like interface{}) *Model { return m.callWhereBuilder(m.whereBuilder.WherePrefixNotLike(prefix, column, like)) } // WherePrefixNot builds `prefix.column != value` statement. +// See WhereBuilder.WherePrefixNot. func (m *Model) WherePrefixNot(prefix string, column string, value interface{}) *Model { return m.callWhereBuilder(m.whereBuilder.WherePrefixNot(prefix, column, value)) } // WherePrefixNotIn builds `prefix.column NOT IN (in)` statement. +// See WhereBuilder.WherePrefixNotIn. func (m *Model) WherePrefixNotIn(prefix string, column string, in interface{}) *Model { return m.callWhereBuilder(m.whereBuilder.WherePrefixNot(prefix, column, in)) } // WherePrefixNotNull builds `prefix.columns[0] IS NOT NULL AND prefix.columns[1] IS NOT NULL ...` statement. +// See WhereBuilder.WherePrefixNotNull. func (m *Model) WherePrefixNotNull(prefix string, columns ...string) *Model { return m.callWhereBuilder(m.whereBuilder.WherePrefixNotNull(prefix, columns...)) } diff --git a/database/gdb/gdb_model_whereor.go b/database/gdb/gdb_model_whereor.go index 9dcf14ae6..e9e914723 100644 --- a/database/gdb/gdb_model_whereor.go +++ b/database/gdb/gdb_model_whereor.go @@ -7,71 +7,85 @@ package gdb // WhereOr adds "OR" condition to the where statement. +// See WhereBuilder.WhereOr. func (m *Model) WhereOr(where interface{}, args ...interface{}) *Model { return m.callWhereBuilder(m.whereBuilder.WhereOr(where, args...)) } // WhereOrf builds `OR` condition string using fmt.Sprintf and arguments. +// See WhereBuilder.WhereOrf. func (m *Model) WhereOrf(format string, args ...interface{}) *Model { return m.callWhereBuilder(m.whereBuilder.WhereOrf(format, args...)) } // WhereOrLT builds `column < value` statement in `OR` conditions.. +// See WhereBuilder.WhereOrLT. func (m *Model) WhereOrLT(column string, value interface{}) *Model { return m.callWhereBuilder(m.whereBuilder.WhereOrLT(column, value)) } // WhereOrLTE builds `column <= value` statement in `OR` conditions.. +// See WhereBuilder.WhereOrLTE. func (m *Model) WhereOrLTE(column string, value interface{}) *Model { return m.callWhereBuilder(m.whereBuilder.WhereOrLTE(column, value)) } // WhereOrGT builds `column > value` statement in `OR` conditions.. +// See WhereBuilder.WhereOrGT. func (m *Model) WhereOrGT(column string, value interface{}) *Model { return m.callWhereBuilder(m.whereBuilder.WhereOrGT(column, value)) } // WhereOrGTE builds `column >= value` statement in `OR` conditions.. +// See WhereBuilder.WhereOrGTE. func (m *Model) WhereOrGTE(column string, value interface{}) *Model { return m.callWhereBuilder(m.whereBuilder.WhereOrGTE(column, value)) } // WhereOrBetween builds `column BETWEEN min AND max` statement in `OR` conditions. +// See WhereBuilder.WhereOrBetween. func (m *Model) WhereOrBetween(column string, min, max interface{}) *Model { return m.callWhereBuilder(m.whereBuilder.WhereOrBetween(column, min, max)) } // WhereOrLike builds `column LIKE like` statement in `OR` conditions. +// See WhereBuilder.WhereOrLike. func (m *Model) WhereOrLike(column string, like interface{}) *Model { return m.callWhereBuilder(m.whereBuilder.WhereOrLike(column, like)) } // WhereOrIn builds `column IN (in)` statement in `OR` conditions. +// See WhereBuilder.WhereOrIn. func (m *Model) WhereOrIn(column string, in interface{}) *Model { return m.callWhereBuilder(m.whereBuilder.WhereOrIn(column, in)) } // WhereOrNull builds `columns[0] IS NULL OR columns[1] IS NULL ...` statement in `OR` conditions. +// See WhereBuilder.WhereOrNull. func (m *Model) WhereOrNull(columns ...string) *Model { return m.callWhereBuilder(m.whereBuilder.WhereOrNull(columns...)) } // WhereOrNotBetween builds `column NOT BETWEEN min AND max` statement in `OR` conditions. +// See WhereBuilder.WhereOrNotBetween. func (m *Model) WhereOrNotBetween(column string, min, max interface{}) *Model { return m.callWhereBuilder(m.whereBuilder.WhereOrNotBetween(column, min, max)) } // WhereOrNotLike builds `column NOT LIKE like` statement in `OR` conditions. +// See WhereBuilder.WhereOrNotLike. func (m *Model) WhereOrNotLike(column string, like interface{}) *Model { return m.callWhereBuilder(m.whereBuilder.WhereOrNotLike(column, like)) } // WhereOrNotIn builds `column NOT IN (in)` statement. +// See WhereBuilder.WhereOrNotIn. func (m *Model) WhereOrNotIn(column string, in interface{}) *Model { return m.callWhereBuilder(m.whereBuilder.WhereOrNotIn(column, in)) } // WhereOrNotNull builds `columns[0] IS NOT NULL OR columns[1] IS NOT NULL ...` statement in `OR` conditions. +// See WhereBuilder.WhereOrNotNull. func (m *Model) WhereOrNotNull(columns ...string) *Model { return m.callWhereBuilder(m.whereBuilder.WhereOrNotNull(columns...)) } diff --git a/database/gdb/gdb_model_whereor_prefix.go b/database/gdb/gdb_model_whereor_prefix.go index ebf5ad575..e336ed57c 100644 --- a/database/gdb/gdb_model_whereor_prefix.go +++ b/database/gdb/gdb_model_whereor_prefix.go @@ -7,66 +7,79 @@ package gdb // WhereOrPrefix performs as WhereOr, but it adds prefix to each field in where statement. +// See WhereBuilder.WhereOrPrefix. func (m *Model) WhereOrPrefix(prefix string, where interface{}, args ...interface{}) *Model { return m.callWhereBuilder(m.whereBuilder.WhereOrPrefix(prefix, where, args...)) } // WhereOrPrefixLT builds `prefix.column < value` statement in `OR` conditions.. +// See WhereBuilder.WhereOrPrefixLT. func (m *Model) WhereOrPrefixLT(prefix string, column string, value interface{}) *Model { return m.callWhereBuilder(m.whereBuilder.WhereOrPrefixLT(prefix, column, value)) } // WhereOrPrefixLTE builds `prefix.column <= value` statement in `OR` conditions.. +// See WhereBuilder.WhereOrPrefixLTE. func (m *Model) WhereOrPrefixLTE(prefix string, column string, value interface{}) *Model { return m.callWhereBuilder(m.whereBuilder.WhereOrPrefixLTE(prefix, column, value)) } // WhereOrPrefixGT builds `prefix.column > value` statement in `OR` conditions.. +// See WhereBuilder.WhereOrPrefixGT. func (m *Model) WhereOrPrefixGT(prefix string, column string, value interface{}) *Model { return m.callWhereBuilder(m.whereBuilder.WhereOrPrefixGT(prefix, column, value)) } // WhereOrPrefixGTE builds `prefix.column >= value` statement in `OR` conditions.. +// See WhereBuilder.WhereOrPrefixGTE. func (m *Model) WhereOrPrefixGTE(prefix string, column string, value interface{}) *Model { return m.callWhereBuilder(m.whereBuilder.WhereOrPrefixGTE(prefix, column, value)) } // WhereOrPrefixBetween builds `prefix.column BETWEEN min AND max` statement in `OR` conditions. +// See WhereBuilder.WhereOrPrefixBetween. func (m *Model) WhereOrPrefixBetween(prefix string, column string, min, max interface{}) *Model { return m.callWhereBuilder(m.whereBuilder.WhereOrPrefixBetween(prefix, column, min, max)) } // WhereOrPrefixLike builds `prefix.column LIKE like` statement in `OR` conditions. +// See WhereBuilder.WhereOrPrefixLike. func (m *Model) WhereOrPrefixLike(prefix string, column string, like interface{}) *Model { return m.callWhereBuilder(m.whereBuilder.WhereOrPrefixLike(prefix, column, like)) } // WhereOrPrefixIn builds `prefix.column IN (in)` statement in `OR` conditions. +// See WhereBuilder.WhereOrPrefixIn. func (m *Model) WhereOrPrefixIn(prefix string, column string, in interface{}) *Model { return m.callWhereBuilder(m.whereBuilder.WhereOrPrefixIn(prefix, column, in)) } // WhereOrPrefixNull builds `prefix.columns[0] IS NULL OR prefix.columns[1] IS NULL ...` statement in `OR` conditions. +// See WhereBuilder.WhereOrPrefixNull. func (m *Model) WhereOrPrefixNull(prefix string, columns ...string) *Model { return m.callWhereBuilder(m.whereBuilder.WhereOrPrefixNull(prefix, columns...)) } // WhereOrPrefixNotBetween builds `prefix.column NOT BETWEEN min AND max` statement in `OR` conditions. +// See WhereBuilder.WhereOrPrefixNotBetween. func (m *Model) WhereOrPrefixNotBetween(prefix string, column string, min, max interface{}) *Model { return m.callWhereBuilder(m.whereBuilder.WhereOrPrefixNotBetween(prefix, column, min, max)) } // WhereOrPrefixNotLike builds `prefix.column NOT LIKE like` statement in `OR` conditions. +// See WhereBuilder.WhereOrPrefixNotLike. func (m *Model) WhereOrPrefixNotLike(prefix string, column string, like interface{}) *Model { return m.callWhereBuilder(m.whereBuilder.WhereOrPrefixNotLike(prefix, column, like)) } // WhereOrPrefixNotIn builds `prefix.column NOT IN (in)` statement. +// See WhereBuilder.WhereOrPrefixNotIn. func (m *Model) WhereOrPrefixNotIn(prefix string, column string, in interface{}) *Model { return m.callWhereBuilder(m.whereBuilder.WhereOrPrefixNotIn(prefix, column, in)) } // WhereOrPrefixNotNull builds `prefix.columns[0] IS NOT NULL OR prefix.columns[1] IS NOT NULL ...` statement in `OR` conditions. +// See WhereBuilder.WhereOrPrefixNotNull. func (m *Model) WhereOrPrefixNotNull(prefix string, columns ...string) *Model { return m.callWhereBuilder(m.whereBuilder.WhereOrPrefixNotNull(prefix, columns...)) } diff --git a/database/gdb/gdb_z_mysql_feature_model_builder_test.go b/database/gdb/gdb_z_mysql_feature_model_builder_test.go index 43e319731..c38fabe61 100644 --- a/database/gdb/gdb_z_mysql_feature_model_builder_test.go +++ b/database/gdb/gdb_z_mysql_feature_model_builder_test.go @@ -28,6 +28,7 @@ func Test_Model_Builder(t *testing.T) { t.Assert(len(all), 6) }) + // Where And gtest.C(t, func(t *gtest.T) { m := db.Model(table) b := m.Builder() @@ -43,4 +44,20 @@ func Test_Model_Builder(t *testing.T) { t.Assert(len(all), 1) }) + db.SetDebug(true) + // Where Or + gtest.C(t, func(t *gtest.T) { + m := db.Model(table) + b := m.Builder() + + all, err := m.WhereOr( + b.Where("id", g.Slice{1, 2, 3}).WhereOr("id", g.Slice{4, 5, 6}), + ).WhereOr( + b.Where("id", g.Slice{2, 3}).WhereOr("id", g.Slice{5, 6}), + ).WhereOr( + b.Where("id", g.Slice{3}).Where("id", g.Slice{1, 2, 3}), + ).All() + t.AssertNil(err) + t.Assert(len(all), 6) + }) } From c90f91dcbecc1370254914c00b7abd90eb592a8b Mon Sep 17 00:00:00 2001 From: John Guo Date: Sat, 7 May 2022 16:38:17 +0800 Subject: [PATCH 6/7] remove Sharding feature --- contrib/drivers/mssql/mssql.go | 2 +- contrib/drivers/oracle/oracle.go | 2 +- contrib/drivers/pgsql/pgsql.go | 2 +- database/gdb/gdb_core_underlying.go | 37 --- database/gdb/gdb_driver_mysql.go | 1 - database/gdb/gdb_model.go | 65 +++-- database/gdb/gdb_model_sharding.go | 245 ------------------ .../gdb/gdb_z_mysql_feature_sharding_test.go | 138 ---------- 8 files changed, 35 insertions(+), 457 deletions(-) delete mode 100644 database/gdb/gdb_model_sharding.go delete mode 100644 database/gdb/gdb_z_mysql_feature_sharding_test.go diff --git a/contrib/drivers/mssql/mssql.go b/contrib/drivers/mssql/mssql.go index c32c90a4f..787a73d5a 100644 --- a/contrib/drivers/mssql/mssql.go +++ b/contrib/drivers/mssql/mssql.go @@ -104,7 +104,7 @@ func (d *Driver) FilteredLink() string { // GetChars returns the security char for this type of database. func (d *Driver) GetChars() (charLeft string, charRight string) { - return "\"", "\"" + return `"`, `"` } // DoFilter deals with the sql string before commits it to underlying sql driver. diff --git a/contrib/drivers/oracle/oracle.go b/contrib/drivers/oracle/oracle.go index 3bab5bfc1..e6db60586 100644 --- a/contrib/drivers/oracle/oracle.go +++ b/contrib/drivers/oracle/oracle.go @@ -108,7 +108,7 @@ func (d *Driver) FilteredLink() string { // GetChars returns the security char for this type of database. func (d *Driver) GetChars() (charLeft string, charRight string) { - return "\"", "\"" + return `"`, `"` } // DoFilter deals with the sql string before commits it to underlying sql driver. diff --git a/contrib/drivers/pgsql/pgsql.go b/contrib/drivers/pgsql/pgsql.go index e9d2ccfbe..d474ecc73 100644 --- a/contrib/drivers/pgsql/pgsql.go +++ b/contrib/drivers/pgsql/pgsql.go @@ -106,7 +106,7 @@ func (d *Driver) FilteredLink() string { // GetChars returns the security char for this type of database. func (d *Driver) GetChars() (charLeft string, charRight string) { - return "\"", "\"" + return `"`, `"` } // DoFilter deals with the sql string before commits it to underlying sql driver. diff --git a/database/gdb/gdb_core_underlying.go b/database/gdb/gdb_core_underlying.go index 86857157a..a3c177cb6 100644 --- a/database/gdb/gdb_core_underlying.go +++ b/database/gdb/gdb_core_underlying.go @@ -137,31 +137,6 @@ type sqlParsingHandlerOutput struct { DoCommitInput } -func (c *Core) sqlParsingHandler(ctx context.Context, in sqlParsingHandlerInput) (out *sqlParsingHandlerOutput, err error) { - var shardingOut *callShardingHandlerFromCtxOutput - // Sharding handling. - shardingOut, err = c.callShardingHandlerFromCtx(ctx, callShardingHandlerFromCtxInput{ - Sql: in.Sql, - FormattedSql: in.FormattedSql, - }) - if err != nil { - return - } - if shardingOut != nil { - if shardingOut.Sql != "" { - in.Sql = shardingOut.Sql - } - // If schema changes, it here creates and uses a new DB link operation object. - if shardingOut.Schema != c.db.GetSchema() { - in.Link, err = c.db.GetCore().GetLink(ctx, in.Link.IsOnMaster(), shardingOut.Schema) - } - } - out = &sqlParsingHandlerOutput{ - DoCommitInput: in.DoCommitInput, - } - return -} - // DoCommit commits current sql and arguments to underlying sql driver. func (c *Core) DoCommit(ctx context.Context, in DoCommitInput) (out DoCommitOutput, err error) { // Inject internal data into ctx, especially for transaction creating. @@ -180,18 +155,6 @@ func (c *Core) DoCommit(ctx context.Context, in DoCommitInput) (out DoCommitOutp timestampMilli1 = gtime.TimestampMilli() ) - // SQL parser handler. - sqlParsingHandlerOut, err := c.sqlParsingHandler(ctx, sqlParsingHandlerInput{ - DoCommitInput: in, - FormattedSql: formattedSql, - }) - if err != nil { - return - } - if sqlParsingHandlerOut != nil { - in = sqlParsingHandlerOut.DoCommitInput - } - // Trace span start. tr := otel.GetTracerProvider().Tracer(traceInstrumentName, trace.WithInstrumentationVersion(gf.VERSION)) ctx, span := tr.Start(ctx, in.Type, trace.WithSpanKind(trace.SpanKindInternal)) diff --git a/database/gdb/gdb_driver_mysql.go b/database/gdb/gdb_driver_mysql.go index cc9fcb247..ec84128ac 100644 --- a/database/gdb/gdb_driver_mysql.go +++ b/database/gdb/gdb_driver_mysql.go @@ -13,7 +13,6 @@ import ( "net/url" _ "github.com/go-sql-driver/mysql" - "github.com/gogf/gf/v2/errors/gcode" "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/internal/intlog" diff --git a/database/gdb/gdb_model.go b/database/gdb/gdb_model.go index 98010a070..148e09929 100644 --- a/database/gdb/gdb_model.go +++ b/database/gdb/gdb_model.go @@ -18,39 +18,38 @@ import ( // Model is core struct implementing the DAO for ORM. type Model struct { *modelWhereBuilder - db DB // Underlying DB interface. - tx *TX // Underlying TX interface. - rawSql string // rawSql is the raw SQL string which marks a raw SQL based Model not a table based Model. - schema string // Custom database schema. - linkType int // Mark for operation on master or slave. - tablesInit string // Table names when model initialization. - tables string // Operation table names, which can be more than one table names and aliases, like: "user", "user u", "user u, user_detail ud". - fields string // Operation fields, multiple fields joined using char ','. - fieldsEx string // Excluded operation fields, multiple fields joined using char ','. - withArray []interface{} // Arguments for With feature. - withAll bool // Enable model association operations on all objects that have "with" tag in the struct. - extraArgs []interface{} // Extra custom arguments for sql, which are prepended to the arguments before sql committed to underlying driver. - whereBuilder *WhereBuilder // Condition builder for where operation. - groupBy string // Used for "group by" statement. - orderBy string // Used for "order by" statement. - having []interface{} // Used for "having..." statement. - start int // Used for "select ... start, limit ..." statement. - limit int // Used for "select ... start, limit ..." statement. - option int // Option for extra operation features. - offset int // Offset statement for some databases grammar. - data interface{} // Data for operation, which can be type of map/[]map/struct/*struct/string, etc. - batch int // Batch number for batch Insert/Replace/Save operations. - filter bool // Filter data and where key-value pairs according to the fields of the table. - distinct string // Force the query to only return distinct results. - lockInfo string // Lock for update or in shared lock. - cacheEnabled bool // Enable sql result cache feature, which is mainly for indicating cache duration(especially 0) usage. - cacheOption CacheOption // Cache option for query statement. - hookHandler HookHandler // Hook functions for model hook feature. - shardingHandler ShardingHandler // Custom sharding handler for sharding feature. - unscoped bool // Disables soft deleting features when select/delete operations. - safe bool // If true, it clones and returns a new model object whenever operation done; or else it changes the attribute of current model. - onDuplicate interface{} // onDuplicate is used for ON "DUPLICATE KEY UPDATE" statement. - onDuplicateEx interface{} // onDuplicateEx is used for excluding some columns ON "DUPLICATE KEY UPDATE" statement. + db DB // Underlying DB interface. + tx *TX // Underlying TX interface. + rawSql string // rawSql is the raw SQL string which marks a raw SQL based Model not a table based Model. + schema string // Custom database schema. + linkType int // Mark for operation on master or slave. + tablesInit string // Table names when model initialization. + tables string // Operation table names, which can be more than one table names and aliases, like: "user", "user u", "user u, user_detail ud". + fields string // Operation fields, multiple fields joined using char ','. + fieldsEx string // Excluded operation fields, multiple fields joined using char ','. + withArray []interface{} // Arguments for With feature. + withAll bool // Enable model association operations on all objects that have "with" tag in the struct. + extraArgs []interface{} // Extra custom arguments for sql, which are prepended to the arguments before sql committed to underlying driver. + whereBuilder *WhereBuilder // Condition builder for where operation. + groupBy string // Used for "group by" statement. + orderBy string // Used for "order by" statement. + having []interface{} // Used for "having..." statement. + start int // Used for "select ... start, limit ..." statement. + limit int // Used for "select ... start, limit ..." statement. + option int // Option for extra operation features. + offset int // Offset statement for some databases grammar. + data interface{} // Data for operation, which can be type of map/[]map/struct/*struct/string, etc. + batch int // Batch number for batch Insert/Replace/Save operations. + filter bool // Filter data and where key-value pairs according to the fields of the table. + distinct string // Force the query to only return distinct results. + lockInfo string // Lock for update or in shared lock. + cacheEnabled bool // Enable sql result cache feature, which is mainly for indicating cache duration(especially 0) usage. + cacheOption CacheOption // Cache option for query statement. + hookHandler HookHandler // Hook functions for model hook feature. + unscoped bool // Disables soft deleting features when select/delete operations. + safe bool // If true, it clones and returns a new model object whenever operation done; or else it changes the attribute of current model. + onDuplicate interface{} // onDuplicate is used for ON "DUPLICATE KEY UPDATE" statement. + onDuplicateEx interface{} // onDuplicateEx is used for excluding some columns ON "DUPLICATE KEY UPDATE" statement. } type modelWhereBuilder = WhereBuilder diff --git a/database/gdb/gdb_model_sharding.go b/database/gdb/gdb_model_sharding.go deleted file mode 100644 index 4869f6f20..000000000 --- a/database/gdb/gdb_model_sharding.go +++ /dev/null @@ -1,245 +0,0 @@ -// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. -// -// This Source Code Form is subject to the terms of the MIT License. -// If a copy of the MIT was not distributed with this file, -// You can obtain one at https://github.com/gogf/gf. - -package gdb - -import ( - "context" - "strings" - - "github.com/gogf/gf/v2/container/gvar" - "github.com/gogf/gf/v2/errors/gerror" - "github.com/gogf/gf/v2/os/gctx" - "github.com/longbridgeapp/sqlparser" -) - -// ShardingInput is input parameters for custom sharding handler. -type ShardingInput struct { - Table string // Current operation table name. - Schema string // Current operation schema, usually empty string which means uses default schema from configuration. - OperationData map[string]Value // Accurate readonly key-value data pairs from INSERT/UPDATE statement. - ConditionData map[string]Value // Accurate readonly key-value condition pairs from SELECT/UPDATE/DELETE statement. -} - -// ShardingOutput is output parameters for custom sharding handler. -type ShardingOutput struct { - Table string // New table name for current operation. Use empty string for no changes of table name. - Schema string // New schema name for current operation. Use empty string for using default schema from configuration. -} - -// ShardingHandler is a custom function for custom sharding table and schema for DB operation. -type ShardingHandler func(ctx context.Context, in ShardingInput) (out *ShardingOutput, err error) - -const ( - ctxKeyForShardingHandler gctx.StrKey = "ShardingHandler" -) - -// Sharding creates and returns a new model with sharding handler. -func (m *Model) Sharding(handler ShardingHandler) *Model { - var ( - ctx = m.GetCtx() - model = m.getModel() - ) - model.shardingHandler = handler - // Inject sharding handler into context. - model = model.Ctx(model.injectShardingInputCaller(ctx)) - return model -} - -// injectShardingInputCaller injects custom sharding handler into context. -func (m *Model) injectShardingInputCaller(ctx context.Context) context.Context { - if m.shardingHandler == nil { - return ctx - } - if ctx.Value(ctxKeyForShardingHandler) != nil { - return ctx - } - return context.WithValue(ctx, ctxKeyForShardingHandler, m.shardingHandler) -} - -type callShardingHandlerFromCtxInput struct { - Sql string - FormattedSql string -} - -type callShardingHandlerFromCtxOutput struct { - Sql string - Table string - Schema string - ParsedSqlOutput *parseFormattedSqlOutput -} - -func (c *Core) callShardingHandlerFromCtx( - ctx context.Context, in callShardingHandlerFromCtxInput, -) (out *callShardingHandlerFromCtxOutput, err error) { - var ( - newSql = in.Sql - ctxValue interface{} - shardingHandler ShardingHandler - ok bool - ) - // If no sharding handler, it does nothing. - if ctxValue = ctx.Value(ctxKeyForShardingHandler); ctxValue == nil { - return nil, nil - } - if shardingHandler, ok = ctxValue.(ShardingHandler); !ok { - return nil, nil - } - parsedOut, err := c.parseFormattedSql(in.FormattedSql) - if err != nil { - return nil, err - } - var shardingIn = ShardingInput{ - Table: parsedOut.Table, - Schema: c.db.GetSchema(), - OperationData: parsedOut.OperationData, - ConditionData: parsedOut.ConditionData, - } - shardingOut, err := shardingHandler(ctx, shardingIn) - if err != nil { - return nil, gerror.Wrap(err, `calling sharding handler failed`) - } - if shardingOut.Table != shardingIn.Table || shardingOut.Schema != shardingIn.Schema { - if shardingOut.Table != shardingIn.Table { - newSql, err = c.formatSqlWithNewTable(in.Sql, shardingOut.Table) - if err != nil { - return nil, err - } - } - out = &callShardingHandlerFromCtxOutput{ - Sql: newSql, - Table: shardingOut.Table, - Schema: shardingOut.Schema, - ParsedSqlOutput: parsedOut, - } - return out, nil - } - return nil, nil -} - -// formatSqlWithNewTable modifies given `sql` and returns a sql with new table name `table`. -func (c *Core) formatSqlWithNewTable(sql, table string) (newSql string, err error) { - parsedStmt, err := sqlparser.NewParser(strings.NewReader(sql)).ParseStatement() - if err != nil { - return "", gerror.Wrapf(err, `parse failed for SQL: %s`, sql) - } - newTable := &sqlparser.TableName{Name: &sqlparser.Ident{Name: table}} - switch stmt := parsedStmt.(type) { - case *sqlparser.SelectStatement: - stmt.FromItems = newTable - return stmt.String(), nil - case *sqlparser.InsertStatement: - stmt.TableName = newTable - return stmt.String(), nil - case *sqlparser.UpdateStatement: - stmt.TableName = newTable - return stmt.String(), nil - case *sqlparser.DeleteStatement: - stmt.TableName = newTable - return stmt.String(), nil - default: - return "", gerror.Wrapf(err, `unsupported SQL: %s`, sql) - } -} - -type parseFormattedSqlOutput struct { - Table string - OperationData map[string]Value - ConditionData map[string]Value - ParsedStmt sqlparser.Statement - SelectedFields []string -} - -func (c *Core) parseFormattedSql(formattedSql string) (*parseFormattedSqlOutput, error) { - var ( - condition sqlparser.Expr - err error - out = &parseFormattedSqlOutput{ - SelectedFields: make([]string, 0), - OperationData: make(map[string]Value), - ConditionData: make(map[string]Value), - } - ) - out.ParsedStmt, err = sqlparser.NewParser(strings.NewReader(formattedSql)).ParseStatement() - if err != nil { - return nil, gerror.Wrapf(err, `parse failed for SQL: %s`, formattedSql) - } - switch stmt := out.ParsedStmt.(type) { - case *sqlparser.SelectStatement: - if stmt.FromItems != nil { - table, ok := stmt.FromItems.(*sqlparser.TableName) - if !ok { - return nil, gerror.Newf( - `invalid table name "%s" in SQL: %s`, - stmt.FromItems.String(), formattedSql, - ) - } - out.Table = table.TableName() - } - condition = stmt.Condition - if stmt.Columns != nil { - for _, column := range *stmt.Columns { - if column.Alias != nil { - out.SelectedFields = append(out.SelectedFields, column.Alias.Name) - } else if column.Expr != nil { - out.SelectedFields = append(out.SelectedFields, column.Expr.String()) - } - } - } - - case *sqlparser.InsertStatement: - out.Table = stmt.TableName.TableName() - if len(stmt.Expressions) > 0 && len(stmt.ColumnNames) > 0 { - names := make([]string, len(stmt.ColumnNames)) - for i, ident := range stmt.ColumnNames { - names[i] = ident.Name - } - // It just uses the first item. - for i, expr := range stmt.Expressions[0].Exprs { - c.injectDataByExpr(out.OperationData, names[i], expr) - } - } - case *sqlparser.UpdateStatement: - out.Table = stmt.TableName.TableName() - condition = stmt.Condition - if len(stmt.Assignments) > 0 { - for _, assignment := range stmt.Assignments { - if len(assignment.Columns) > 0 { - c.injectDataByExpr(out.OperationData, assignment.Columns[0].Name, assignment.Expr) - } - } - } - case *sqlparser.DeleteStatement: - out.Table = stmt.TableName.TableName() - condition = stmt.Condition - - default: - return nil, gerror.Wrapf(err, `unsupported SQL: %s`, formattedSql) - } - - err = sqlparser.Walk(sqlparser.VisitFunc(func(node sqlparser.Node) error { - if n, ok := node.(*sqlparser.BinaryExpr); ok { - if x, ok := n.X.(*sqlparser.Ident); ok { - if n.Op == sqlparser.EQ { - c.injectDataByExpr(out.ConditionData, x.Name, n.Y) - } - } - } - return nil - }), condition) - return out, err -} - -func (c *Core) injectDataByExpr(data map[string]Value, name string, expr sqlparser.Expr) { - switch exprImp := expr.(type) { - case *sqlparser.StringLit: - data[name] = gvar.New(exprImp.Value) - case *sqlparser.NumberLit: - data[name] = gvar.New(exprImp.Value) - default: - data[name] = gvar.New(exprImp.String()) - } -} diff --git a/database/gdb/gdb_z_mysql_feature_sharding_test.go b/database/gdb/gdb_z_mysql_feature_sharding_test.go deleted file mode 100644 index aed2b1823..000000000 --- a/database/gdb/gdb_z_mysql_feature_sharding_test.go +++ /dev/null @@ -1,138 +0,0 @@ -// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. -// -// This Source Code Form is subject to the terms of the MIT License. -// If a copy of the MIT was not distributed with this file, -// You can obtain one at https://github.com/gogf/gf. - -package gdb_test - -import ( - "context" - "testing" - - "github.com/gogf/gf/v2/database/gdb" - "github.com/gogf/gf/v2/frame/g" - "github.com/gogf/gf/v2/test/gtest" -) - -func Test_Model_Sharding(t *testing.T) { - table1 := createTable() - table2 := createTable() - defer dropTable(table1) - defer dropTable(table2) - - gtest.C(t, func(t *gtest.T) { - _, err1 := db.Model(table1).Data(g.Map{ - "id": 1, - }).Insert() - t.AssertNil(err1) - _, err2 := db.Model(table2).Data(g.Map{ - "id": 2, - }).Insert() - t.AssertNil(err2) - }) - // no sharding. - gtest.C(t, func(t *gtest.T) { - all, err := db.Model(table1).All() - t.AssertNil(err) - t.Assert(len(all), 1) - t.Assert(all[0]["id"].String(), 1) - }) - // with sharding handler. - gtest.C(t, func(t *gtest.T) { - all, err := db.Model(table1).Sharding(func(ctx context.Context, in gdb.ShardingInput) (out *gdb.ShardingOutput, err error) { - out = &gdb.ShardingOutput{ - Table: table2, - } - return - }).All() - t.AssertNil(err) - t.Assert(len(all), 1) - t.Assert(all[0]["id"].String(), 2) - }) - // with sharding handler and no existence table name. - gtest.C(t, func(t *gtest.T) { - all, err := db.Model("none").Sharding(func(ctx context.Context, in gdb.ShardingInput) (out *gdb.ShardingOutput, err error) { - out = &gdb.ShardingOutput{ - Table: table2, - } - return - }).All() - t.AssertNil(err) - t.Assert(len(all), 1) - t.Assert(all[0]["id"].String(), 2) - }) - // with sharding handler and no existence table name and tables fields retrieving. - gtest.C(t, func(t *gtest.T) { - type User struct { - Id int - Passport string - Password string - NickName string - } - var users []User - err := db.Model("none").Sharding(func(ctx context.Context, in gdb.ShardingInput) (out *gdb.ShardingOutput, err error) { - out = &gdb.ShardingOutput{ - Table: table2, - } - return - }).Scan(&users) - t.AssertNil(err) - t.Assert(len(users), 1) - t.Assert(users[0].Id, 2) - }) -} - -func Test_Model_Sharding_Schema(t *testing.T) { - var ( - db1 = db - db2 = db.Schema(TestSchema2) - table1 = createTableWithDb(db1) - table2 = createTableWithDb(db2) - ) - - defer dropTableWithDb(db1, table1) - defer dropTableWithDb(db2, table2) - - gtest.C(t, func(t *gtest.T) { - _, err1 := db1.Model(table1).Data(g.Map{ - "id": 1, - }).Insert() - t.AssertNil(err1) - _, err2 := db2.Model(table2).Data(g.Map{ - "id": 2, - }).Insert() - t.AssertNil(err2) - }) - // no sharding. - gtest.C(t, func(t *gtest.T) { - all, err := db1.Model(table1).All() - t.AssertNil(err) - t.Assert(len(all), 1) - t.Assert(all[0]["id"].String(), 1) - }) - gtest.C(t, func(t *gtest.T) { - _, err := db1.Model(table2).All() - // Table not exist error. - t.AssertNE(err, nil) - }) - gtest.C(t, func(t *gtest.T) { - all, err := db2.Model(table2).All() - t.AssertNil(err) - t.Assert(len(all), 1) - t.Assert(all[0]["id"].String(), 2) - }) - // with sharding handler and no existence table name and schema change. - gtest.C(t, func(t *gtest.T) { - all, err := db1.Model("none").Sharding(func(ctx context.Context, in gdb.ShardingInput) (out *gdb.ShardingOutput, err error) { - out = &gdb.ShardingOutput{ - Table: table2, - Schema: TestSchema2, - } - return - }).All() - t.AssertNil(err) - t.Assert(len(all), 1) - t.Assert(all[0]["id"].String(), 2) - }) -} From e4ec1be948ede67e78f163f49c5884de9589e31b Mon Sep 17 00:00:00 2001 From: John Guo Date: Sat, 7 May 2022 16:43:28 +0800 Subject: [PATCH 7/7] workflow updates --- .github/workflows/gf.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/gf.yml b/.github/workflows/gf.yml index 50f573870..7dbad5417 100644 --- a/.github/workflows/gf.yml +++ b/.github/workflows/gf.yml @@ -6,11 +6,14 @@ on: branches: - master - develop + - feature/** + - fix/** pull_request: branches: - master - develop - + - feature/** + - fix/** env: GF_DEBUG: 0