Files
sponge/pkg/mysql/query/query_condition.go
2024-02-08 16:35:33 +08:00

282 lines
6.5 KiB
Go

// Package query is a library for mysql query, support for complex conditional paging queries.
package query
import (
"fmt"
"strings"
)
const (
// Eq equal
Eq = "eq"
// Neq not equal
Neq = "neq"
// Gt greater than
Gt = "gt"
// Gte greater than or equal
Gte = "gte"
// Lt less than
Lt = "lt"
// Lte less than or equal
Lte = "lte"
// Like fuzzy lookup
Like = "like"
// In include
In = "in"
// AND logic and
AND string = "and"
// OR logic or
OR string = "or"
)
var expMap = map[string]string{
Eq: " = ",
Neq: " <> ",
Gt: " > ",
Gte: " >= ",
Lt: " < ",
Lte: " <= ",
Like: " LIKE ",
In: " IN ",
"=": " = ",
"!=": " <> ",
">": " > ",
">=": " >= ",
"<": " < ",
"<=": " <= ",
}
var logicMap = map[string]string{
AND: " AND ",
OR: " OR ",
"&": " AND ",
"&&": " AND ",
"|": " OR ",
"||": " OR ",
"AND": " AND ",
"OR": " OR ",
}
// Params query parameters
type Params struct {
Page int `json:"page" form:"page" binding:"gte=0"`
Size int `json:"size" form:"size" binding:"gt=0"`
Sort string `json:"sort,omitempty" form:"sort" binding:""`
Columns []Column `json:"columns,omitempty" form:"columns"` // not required
}
// Column query info
type Column struct {
Name string `json:"name" form:"columns"` // column name
Exp string `json:"exp" form:"columns"` // expressions, which default to = when the value is null, have =, !=, >, >=, <, <=, like, in
Value interface{} `json:"value" form:"columns"` // column value
Logic string `json:"logic" form:"columns"` // logical type, defaults to and when the value is null, with &(and), ||(or)
}
func (c *Column) checkValid() error {
if c.Name == "" {
return fmt.Errorf("field 'name' cannot be empty")
}
if c.Value == nil {
return fmt.Errorf("field 'value' cannot be nil")
}
return nil
}
// converting ExpType to sql expressions and LogicType to sql using characters
func (c *Column) convert() error {
if c.Exp == "" {
c.Exp = Eq
}
if v, ok := expMap[strings.ToLower(c.Exp)]; ok { //nolint
c.Exp = v
if c.Exp == " LIKE " {
c.Value = fmt.Sprintf("%%%v%%", c.Value)
}
if c.Exp == " IN " {
val, ok := c.Value.(string)
if !ok {
return fmt.Errorf("invalid value type '%s'", c.Value)
}
iVal := []interface{}{}
ss := strings.Split(val, ",")
for _, s := range ss {
iVal = append(iVal, s)
}
c.Value = iVal
}
} else {
return fmt.Errorf("unknown exp type '%s'", c.Exp)
}
if c.Logic == "" {
c.Logic = AND
}
if v, ok := logicMap[strings.ToLower(c.Logic)]; ok { //nolint
c.Logic = v
} else {
return fmt.Errorf("unknown logic type '%s'", c.Logic)
}
return nil
}
// ConvertToPage converted to conform to gorm rules based on the page size sort parameter
// Deprecated: will be moved to package pkg/gorm/query ConvertToPage
func (p *Params) ConvertToPage() (order string, limit int, offset int) { //nolint
page := NewPage(p.Page, p.Size, p.Sort)
order = page.sort
limit = page.size
offset = page.page * page.size
return //nolint
}
// ConvertToGormConditions conversion to gorm-compliant parameters based on the Columns parameter
// ignore the logical type of the last column, whether it is a one-column or multi-column query
// Deprecated: will be moved to package pkg/gorm/query ConvertToGormConditions
func (p *Params) ConvertToGormConditions() (string, []interface{}, error) {
str := ""
args := []interface{}{}
l := len(p.Columns)
if l == 0 {
return "", nil, nil
}
isUseIN := true
if l == 1 {
isUseIN = false
}
field := p.Columns[0].Name
for i, column := range p.Columns {
if err := column.checkValid(); err != nil {
return "", nil, err
}
err := column.convert()
if err != nil {
return "", nil, err
}
symbol := "?"
if column.Exp == " IN " {
symbol = "(?)"
}
if i == l-1 { // ignore the logical type of the last column
str += column.Name + column.Exp + symbol
} else {
str += column.Name + column.Exp + symbol + column.Logic
}
args = append(args, column.Value)
// when multiple columns are the same, determine whether the use of IN
if isUseIN {
if field != column.Name {
isUseIN = false
continue
}
if column.Exp != expMap[Eq] {
isUseIN = false
}
}
}
if isUseIN {
str = field + " IN (?)"
args = []interface{}{args}
}
return str, args, nil
}
func getExpsAndLogics(keyLen int, paramSrc string) ([]string, []string) { //nolint
exps, logics := []string{}, []string{}
param := strings.Replace(paramSrc, " ", "", -1)
sps := strings.SplitN(param, "?", 2)
if len(sps) == 2 {
param = sps[1]
}
num := keyLen
if num == 0 {
return exps, logics
}
fields := []string{}
kvs := strings.Split(param, "&")
for _, kv := range kvs {
if strings.Contains(kv, "page=") || strings.Contains(kv, "size=") || strings.Contains(kv, "sort=") {
continue
}
fields = append(fields, kv)
}
// divide into num groups based on non-repeating keys, and determine in each group whether exp and logic exist
group := map[string]string{}
for _, field := range fields {
split := strings.SplitN(field, "=", 2)
if len(split) != 2 {
continue
}
if _, ok := group[split[0]]; ok {
// if exp does not exist, the default value of null is filled, and if logic does not exist, the default value of null is filled.
exps = append(exps, group["exp"])
logics = append(logics, group["logic"])
group = map[string]string{}
continue
}
group[split[0]] = split[1]
}
// handling the last group
exps = append(exps, group["exp"])
logics = append(logics, group["logic"])
return exps, logics
}
// Conditions query conditions
type Conditions struct {
Columns []Column `json:"columns" form:"columns" binding:"min=1"` // columns info
}
// CheckValid check valid
func (c *Conditions) CheckValid() error {
if len(c.Columns) == 0 {
return fmt.Errorf("field 'columns' cannot be empty")
}
for _, column := range c.Columns {
err := column.checkValid()
if err != nil {
return err
}
if column.Exp != "" {
if _, ok := expMap[column.Exp]; !ok {
return fmt.Errorf("unknown exp type '%s'", column.Exp)
}
}
if column.Logic != "" {
if _, ok := logicMap[column.Logic]; !ok {
return fmt.Errorf("unknown logic type '%s'", column.Logic)
}
}
}
return nil
}
// ConvertToGorm conversion to gorm-compliant parameters based on the Columns parameter
// ignore the logical type of the last column, whether it is a one-column or multi-column query
// Deprecated: will be moved to package pkg/gorm/query ConvertToGorm
func (c *Conditions) ConvertToGorm() (string, []interface{}, error) {
p := &Params{Columns: c.Columns}
return p.ConvertToGormConditions()
}