mirror of
https://github.com/zhufuyi/sponge.git
synced 2025-09-27 04:55:57 +08:00
282 lines
6.5 KiB
Go
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()
|
|
}
|