github.com/jxskiss/gopkg/v2@v2.14.9-0.20240514120614-899f3e7952b4/utils/sqlutil/condition.go (about)

     1  package sqlutil
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  	"unsafe"
     7  )
     8  
     9  // And returns a new *Condition which is combination of given conditions
    10  // using the "AND" operator.
    11  func And(conds ...*Condition) *Condition {
    12  	f := new(Condition)
    13  	for _, c := range conds {
    14  		clause, args := c.Build()
    15  		f.And(clause, args...)
    16  	}
    17  	return f
    18  }
    19  
    20  // Or returns a new *Condition which is combination of given conditions
    21  // using the "OR" operator.
    22  func Or(conds ...*Condition) *Condition {
    23  	f := new(Condition)
    24  	for _, c := range conds {
    25  		clause, args := c.Build()
    26  		f.Or(clause, args...)
    27  	}
    28  	return f
    29  }
    30  
    31  // Cond creates a new *Condition from the given params.
    32  func Cond(clause string, args ...any) *Condition {
    33  	return new(Condition).And(clause, args...)
    34  }
    35  
    36  // Condition represents a query filter to work with SQL query.
    37  type Condition struct {
    38  	builder strings.Builder
    39  	prefix  []byte
    40  	args    []any
    41  }
    42  
    43  // And combines the given query filter to Condition using "AND" operator.
    44  func (p *Condition) And(clause string, args ...any) *Condition {
    45  	if clause == "" {
    46  		return p
    47  	}
    48  
    49  	// encapsulate with brackets to avoid misuse
    50  	clause = strings.TrimSpace(clause)
    51  	if containsOr(clause) {
    52  		clause = "(" + clause + ")"
    53  	}
    54  
    55  	if p.builder.Len() == 0 {
    56  		p.builder.WriteString(clause)
    57  	} else {
    58  		p.builder.WriteString(" AND ")
    59  		p.builder.WriteString(clause)
    60  	}
    61  	p.args = append(p.args, args...)
    62  	return p
    63  }
    64  
    65  // AndCond combines the given Condition using "AND" operator.
    66  func (p *Condition) AndCond(c *Condition) *Condition {
    67  	clause, args := c.Build()
    68  	return p.And(clause, args...)
    69  }
    70  
    71  // Or combines the given query filter to Condition using "OR" operator.
    72  func (p *Condition) Or(clause string, args ...any) *Condition {
    73  	if clause == "" {
    74  		return p
    75  	}
    76  
    77  	// encapsulate with brackets to avoid misuse
    78  	clause = strings.TrimSpace(clause)
    79  	if containsAnd(clause) {
    80  		clause = "(" + clause + ")"
    81  	}
    82  
    83  	if p.builder.Len() == 0 {
    84  		p.builder.WriteString(clause)
    85  	} else {
    86  		p.prefix = append(p.prefix, '(')
    87  		p.builder.WriteString(" OR ")
    88  		p.builder.WriteString(clause)
    89  		p.builder.WriteByte(')')
    90  	}
    91  	p.args = append(p.args, args...)
    92  	return p
    93  }
    94  
    95  // OrCond combines the given Condition using "OR" operator.
    96  func (p *Condition) OrCond(c *Condition) *Condition {
    97  	clause, args := c.Build()
    98  	return p.Or(clause, args...)
    99  }
   100  
   101  // IfAnd checks cond, if cond is true, it combines the query filter
   102  // to Condition using "AND" operator.
   103  func (p *Condition) IfAnd(cond bool, clause string, args ...any) *Condition {
   104  	if cond {
   105  		return p.And(clause, args...)
   106  	}
   107  	return p
   108  }
   109  
   110  // IfAndCond checks cond, if cond is true, it combines the given Condition
   111  // using "AND" operator.
   112  func (p *Condition) IfAndCond(cond bool, c *Condition) *Condition {
   113  	if cond {
   114  		return p.AndCond(c)
   115  	}
   116  	return p
   117  }
   118  
   119  // IfOr checks cond, it cond is true, it combines the query filter
   120  // to Condition using "OR" operator.
   121  func (p *Condition) IfOr(cond bool, clause string, args ...any) *Condition {
   122  	if cond {
   123  		return p.Or(clause, args...)
   124  	}
   125  	return p
   126  }
   127  
   128  // IfOrCond checks cond, if cond is true, it combines the given Condition
   129  // using "OR" operator.
   130  func (p *Condition) IfOrCond(cond bool, c *Condition) *Condition {
   131  	if cond {
   132  		return p.OrCond(c)
   133  	}
   134  	return p
   135  }
   136  
   137  // Build returns the query filter clause and parameters of the Condition.
   138  func (p *Condition) Build() (string, []any) {
   139  	buf := make([]byte, len(p.prefix)+p.builder.Len())
   140  	copy(buf, p.prefix)
   141  	copy(buf[len(p.prefix):], p.builder.String())
   142  	clause := *(*string)(unsafe.Pointer(&buf))
   143  	return clause, p.args
   144  }
   145  
   146  // String returns the string representation of the Condition.
   147  func (p *Condition) String() string {
   148  	clause, args := p.Build()
   149  	format := strings.Replace(clause, "?", "%v", -1)
   150  	return fmt.Sprintf(format, args...)
   151  }
   152  
   153  func containsAnd(clause string) bool {
   154  	parenCnt := 0
   155  	clause = strings.ToLower(clause)
   156  	for i := 0; i < len(clause)-4; i++ {
   157  		switch clause[i] {
   158  		case '(':
   159  			parenCnt++
   160  		case ')':
   161  			parenCnt--
   162  		case 'a':
   163  			if clause[i:i+3] == "and" &&
   164  				i > 0 && isWhitespace(clause[i-1]) && isWhitespace(clause[i+3]) &&
   165  				parenCnt == 0 {
   166  				return true
   167  			}
   168  		}
   169  	}
   170  	return false
   171  }
   172  
   173  func containsOr(clause string) bool {
   174  	parenCnt := 0
   175  	clause = strings.ToLower(clause)
   176  	for i := 0; i < len(clause)-3; i++ {
   177  		switch clause[i] {
   178  		case '(':
   179  			parenCnt++
   180  		case ')':
   181  			parenCnt--
   182  		case 'o':
   183  			if clause[i:i+2] == "or" &&
   184  				i > 0 && isWhitespace(clause[i-1]) && isWhitespace(clause[i+2]) &&
   185  				parenCnt == 0 {
   186  				return true
   187  			}
   188  		}
   189  	}
   190  	return false
   191  }
   192  
   193  func isWhitespace(b byte) bool {
   194  	switch b {
   195  	case '\t', '\n', '\v', '\f', '\r', ' ':
   196  		return true
   197  	}
   198  	return false
   199  }