
Method-chaining in gorm is predicated on a `Clause`'s `MergeClause` method ensuring that the two clauses are disconnected in terms of pointers (at least in the Wherec case). However, the original Where implementation used `append`, which only returns a new instance if the backing array needs to be resized. In some cases, this is true. Practically, go doubles the size of the slice once it gets full, so the following slice `append` calls would result in a new slice: * 0 -> 1 * 1 -> 2 * 2 -> 4 * 4 -> 8 * and so on. So, when the number of "where" conditions was 0, 1, 2, or 4, method-chaining would work as expected. However, when it was 3, 5, 6, or 7, modifying the copy would modify the original. This also updates the "order by", "group by" and "set" clauses.
134 lines
2.5 KiB
Go
134 lines
2.5 KiB
Go
package clause
|
|
|
|
// Where where clause
|
|
type Where struct {
|
|
Exprs []Expression
|
|
}
|
|
|
|
// Name where clause name
|
|
func (where Where) Name() string {
|
|
return "WHERE"
|
|
}
|
|
|
|
// Build build where clause
|
|
func (where Where) Build(builder Builder) {
|
|
// Switch position if the first query expression is a single Or condition
|
|
for idx, expr := range where.Exprs {
|
|
if v, ok := expr.(OrConditions); !ok || len(v.Exprs) > 1 {
|
|
if idx != 0 {
|
|
where.Exprs[0], where.Exprs[idx] = where.Exprs[idx], where.Exprs[0]
|
|
}
|
|
break
|
|
}
|
|
}
|
|
|
|
for idx, expr := range where.Exprs {
|
|
if idx > 0 {
|
|
if v, ok := expr.(OrConditions); ok && len(v.Exprs) == 1 {
|
|
builder.WriteString(" OR ")
|
|
} else {
|
|
builder.WriteString(" AND ")
|
|
}
|
|
}
|
|
|
|
expr.Build(builder)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// MergeClause merge where clauses
|
|
func (where Where) MergeClause(clause *Clause) {
|
|
if w, ok := clause.Expression.(Where); ok {
|
|
copiedExpressions := make([]Expression, len(w.Exprs))
|
|
copy(copiedExpressions, w.Exprs)
|
|
where.Exprs = append(copiedExpressions, where.Exprs...)
|
|
}
|
|
|
|
clause.Expression = where
|
|
}
|
|
|
|
func And(exprs ...Expression) Expression {
|
|
if len(exprs) == 0 {
|
|
return nil
|
|
}
|
|
return AndConditions{Exprs: exprs}
|
|
}
|
|
|
|
type AndConditions struct {
|
|
Exprs []Expression
|
|
}
|
|
|
|
func (and AndConditions) Build(builder Builder) {
|
|
if len(and.Exprs) > 1 {
|
|
builder.WriteByte('(')
|
|
}
|
|
for idx, c := range and.Exprs {
|
|
if idx > 0 {
|
|
builder.WriteString(" AND ")
|
|
}
|
|
c.Build(builder)
|
|
}
|
|
if len(and.Exprs) > 1 {
|
|
builder.WriteByte(')')
|
|
}
|
|
}
|
|
|
|
func Or(exprs ...Expression) Expression {
|
|
if len(exprs) == 0 {
|
|
return nil
|
|
}
|
|
return OrConditions{Exprs: exprs}
|
|
}
|
|
|
|
type OrConditions struct {
|
|
Exprs []Expression
|
|
}
|
|
|
|
func (or OrConditions) Build(builder Builder) {
|
|
if len(or.Exprs) > 1 {
|
|
builder.WriteByte('(')
|
|
}
|
|
for idx, c := range or.Exprs {
|
|
if idx > 0 {
|
|
builder.WriteString(" OR ")
|
|
}
|
|
c.Build(builder)
|
|
}
|
|
if len(or.Exprs) > 1 {
|
|
builder.WriteByte(')')
|
|
}
|
|
}
|
|
|
|
func Not(exprs ...Expression) Expression {
|
|
if len(exprs) == 0 {
|
|
return nil
|
|
}
|
|
return NotConditions{Exprs: exprs}
|
|
}
|
|
|
|
type NotConditions struct {
|
|
Exprs []Expression
|
|
}
|
|
|
|
func (not NotConditions) Build(builder Builder) {
|
|
if len(not.Exprs) > 1 {
|
|
builder.WriteByte('(')
|
|
}
|
|
for idx, c := range not.Exprs {
|
|
if idx > 0 {
|
|
builder.WriteString(" AND ")
|
|
}
|
|
|
|
if negationBuilder, ok := c.(NegationExpressionBuilder); ok {
|
|
negationBuilder.NegationBuild(builder)
|
|
} else {
|
|
builder.WriteString(" NOT ")
|
|
c.Build(builder)
|
|
}
|
|
}
|
|
if len(not.Exprs) > 1 {
|
|
builder.WriteByte(')')
|
|
}
|
|
}
|