mirror of
https://github.com/zhufuyi/sponge.git
synced 2025-09-26 20:51:14 +08:00
feat: support null conditions in custom queries
This commit is contained in:
@@ -97,7 +97,7 @@ type Column struct {
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name"` // column name
|
||||
Exp string `protobuf:"bytes,2,opt,name=exp,proto3" json:"exp"` // expressions, default value is "=", support =, !=, >, >=, <, <=, like, in
|
||||
Exp string `protobuf:"bytes,2,opt,name=exp,proto3" json:"exp"` // expressions, default value is "=", support =, !=, >, >=, <, <=, like, in, notin
|
||||
Value string `protobuf:"bytes,3,opt,name=value,proto3" json:"value"` // column value
|
||||
Logic string `protobuf:"bytes,4,opt,name=logic,proto3" json:"logic"` // logical type, default value is "and", support &, and, ||, or
|
||||
}
|
||||
|
@@ -13,7 +13,7 @@ message Params {
|
||||
|
||||
message Column {
|
||||
string name = 1; // column name
|
||||
string exp = 2; // expressions, default value is "=", support =, !=, >, >=, <, <=, like, in
|
||||
string exp = 2; // expressions, default value is "=", support =, !=, >, >=, <, <=, like, in, notin
|
||||
string value = 3; // column value
|
||||
string logic = 4; // logical type, default value is "and", support &, and, ||, or
|
||||
}
|
||||
|
@@ -188,7 +188,7 @@ func (d *userExampleDao) GetByID(ctx context.Context, id uint64) (*model.UserExa
|
||||
// query parameters (not required):
|
||||
//
|
||||
// name: column name
|
||||
// exp: expressions, which default is "=", support =, !=, >, >=, <, <=, like, in
|
||||
// exp: expressions, which default is "=", support =, !=, >, >=, <, <=, like, in, notin, isnull, isnotnull
|
||||
// value: column value, if exp=in, multiple values are separated by commas
|
||||
// logic: logical type, default value is "and", support &, and, ||, or
|
||||
//
|
||||
|
@@ -194,7 +194,7 @@ func (d *userExampleDao) GetByID(ctx context.Context, id uint64) (*model.UserExa
|
||||
// query parameters (not required):
|
||||
//
|
||||
// name: column name
|
||||
// exp: expressions, which default is "=", support =, !=, >, >=, <, <=, like, in
|
||||
// exp: expressions, which default is "=", support =, !=, >, >=, <, <=, like, in, notin, isnull, isnotnull
|
||||
// value: column value, if exp=in, multiple values are separated by commas
|
||||
// logic: logical type, default value is "and", support &, and, ||, or
|
||||
//
|
||||
@@ -260,7 +260,7 @@ func (d *userExampleDao) DeleteByIDs(ctx context.Context, ids []uint64) error {
|
||||
// query conditions:
|
||||
//
|
||||
// name: column name
|
||||
// exp: expressions, which default is "=", support =, !=, >, >=, <, <=, like, in
|
||||
// exp: expressions, which default is "=", support =, !=, >, >=, <, <=, like, in, notin, isnull, isnotnull
|
||||
// value: column value, if exp=in, multiple values are separated by commas
|
||||
// logic: logical type, default value is "and", support &, and, ||, or
|
||||
//
|
||||
|
@@ -200,7 +200,7 @@ func (d *{{.TableNameCamelFCL}}Dao) GetBy{{.ColumnNameCamel}}(ctx context.Contex
|
||||
// query parameters (not required):
|
||||
//
|
||||
// name: column name
|
||||
// exp: expressions, which default is "=", support =, !=, >, >=, <, <=, like, in
|
||||
// exp: expressions, which default is "=", support =, !=, >, >=, <, <=, like, in, notin, isnull, isnotnull
|
||||
// value: column value, if exp=in, multiple values are separated by commas
|
||||
// logic: logical type, default value is "and", support &, and, ||, or
|
||||
//
|
||||
@@ -269,7 +269,7 @@ func (d *{{.TableNameCamelFCL}}Dao) DeleteBy{{.ColumnNamePluralCamel}}(ctx conte
|
||||
// query conditions:
|
||||
//
|
||||
// name: column name
|
||||
// exp: expressions, which default is "=", support =, !=, >, >=, <, <=, like, in
|
||||
// exp: expressions, which default is "=", support =, !=, >, >=, <, <=, like, in, notin, isnull, isnotnull
|
||||
// value: column value, if exp=in, multiple values are separated by commas
|
||||
// logic: logical type, default value is "and", support &, and, ||, or
|
||||
//
|
||||
|
@@ -206,7 +206,7 @@ func (d *userExampleDao) GetByID(ctx context.Context, id string) (*model.UserExa
|
||||
// query parameters (not required):
|
||||
//
|
||||
// name: column name, if value is of type objectId, the suffix :oid must be added, e.g. order_id:oid
|
||||
// exp: expressions, which default is "=", support =, !=, >, >=, <, <=, like, in
|
||||
// exp: expressions, which default is "=", support =, !=, >, >=, <, <=, like, in, notin
|
||||
// value: column value, if exp=in, multiple values are separated by commas
|
||||
// logic: logical type, default value is "and", support &, and, ||, or
|
||||
//
|
||||
|
@@ -211,7 +211,7 @@ func (d *userExampleDao) GetByID(ctx context.Context, id string) (*model.UserExa
|
||||
// query parameters (not required):
|
||||
//
|
||||
// name: column name, if value is of type objectId, the suffix :oid must be added, e.g. order_id:oid
|
||||
// exp: expressions, which default is "=", support =, !=, >, >=, <, <=, like, in
|
||||
// exp: expressions, which default is "=", support =, !=, >, >=, <, <=, like, in, notin
|
||||
// value: column value, if exp=in, multiple values are separated by commas
|
||||
// logic: logical type, default value is "and", support &, and, ||, or
|
||||
//
|
||||
@@ -284,7 +284,7 @@ func (d *userExampleDao) DeleteByIDs(ctx context.Context, ids []string) error {
|
||||
// query conditions:
|
||||
//
|
||||
// name: column name, if value is of type objectId, the suffix :oid must be added, e.g. post_id:oid
|
||||
// exp: expressions, which default is "=", support =, !=, >, >=, <, <=, like, in
|
||||
// exp: expressions, which default is "=", support =, !=, >, >=, <, <=, like, in, notin
|
||||
// value: column value, if exp=in, multiple values are separated by commas
|
||||
// logic: logical type, default value is "and", support &, and, ||, or
|
||||
//
|
||||
|
@@ -194,7 +194,7 @@ func (d *{{.TableNameCamelFCL}}Dao) GetBy{{.ColumnNameCamel}}(ctx context.Contex
|
||||
// query parameters (not required):
|
||||
//
|
||||
// name: column name
|
||||
// exp: expressions, which default is "=", support =, !=, >, >=, <, <=, like, in
|
||||
// exp: expressions, which default is "=", support =, !=, >, >=, <, <=, like, in, notin, isnull, isnotnull
|
||||
// value: column value, if exp=in, multiple values are separated by commas
|
||||
// logic: logical type, default value is "and", support &, and, ||, or
|
||||
//
|
||||
|
@@ -13,6 +13,7 @@
|
||||
|
||||
// case 1: specify options in dsn
|
||||
db, err := mgo.Init("mongodb://root:123456@192.168.3.37:27017/account?socketTimeoutMS=30000&maxPoolSize=100&minPoolSize=1&maxConnIdleTimeMS=300000")
|
||||
|
||||
// case 2: specify options in code
|
||||
db, err := mgo.Init("mongodb://root:123456@192.168.3.37:27017/account",
|
||||
mgo.WithOption().SetMaxPoolSize(100),
|
||||
|
@@ -33,6 +33,8 @@ const (
|
||||
Like = "like"
|
||||
// In include
|
||||
In = "in"
|
||||
// NotIn exclude
|
||||
NotIn = "nin"
|
||||
|
||||
// AND logic and
|
||||
AND string = "and" //nolint
|
||||
@@ -62,6 +64,9 @@ var expMap = map[string]string{
|
||||
lteSymbol: lteSymbol,
|
||||
Like: Like,
|
||||
In: In,
|
||||
NotIn: NotIn,
|
||||
"notin": NotIn,
|
||||
"not in": NotIn,
|
||||
}
|
||||
|
||||
var logicMap = map[string]string{
|
||||
@@ -152,9 +157,9 @@ func (c *Column) convert() error {
|
||||
case Like:
|
||||
escapedValue := regexp.QuoteMeta(fmt.Sprintf("%v", c.Value))
|
||||
c.Value = bson.M{"$regex": escapedValue, "$options": "i"}
|
||||
case In:
|
||||
val, ok := c.Value.(string)
|
||||
if !ok {
|
||||
case In, NotIn:
|
||||
val, ok2 := c.Value.(string)
|
||||
if !ok2 {
|
||||
return fmt.Errorf("invalid value type '%s'", c.Value)
|
||||
}
|
||||
values := []interface{}{}
|
||||
@@ -162,10 +167,10 @@ func (c *Column) convert() error {
|
||||
for _, s := range ss {
|
||||
values = append(values, s)
|
||||
}
|
||||
c.Value = bson.M{"$in": values}
|
||||
c.Value = bson.M{"$" + c.Exp: values}
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("unknown exp type '%s'", c.Exp)
|
||||
return fmt.Errorf("unsported exp type '%s'", c.Exp)
|
||||
}
|
||||
|
||||
return c.convertLogic()
|
||||
|
@@ -23,6 +23,12 @@ const (
|
||||
Like = "like"
|
||||
// In include
|
||||
In = "in"
|
||||
// NotIN not include
|
||||
NotIN = "notin"
|
||||
// IsNull is null
|
||||
IsNull = "isnull"
|
||||
// IsNotNull is not null
|
||||
IsNotNull = "isnotnull"
|
||||
|
||||
// AND logic and
|
||||
AND string = "and"
|
||||
@@ -31,21 +37,27 @@ const (
|
||||
)
|
||||
|
||||
var expMap = map[string]string{
|
||||
Eq: " = ",
|
||||
Neq: " <> ",
|
||||
Gt: " > ",
|
||||
Gte: " >= ",
|
||||
Lt: " < ",
|
||||
Lte: " <= ",
|
||||
Like: " LIKE ",
|
||||
In: " IN ",
|
||||
Eq: " = ",
|
||||
Neq: " <> ",
|
||||
Gt: " > ",
|
||||
Gte: " >= ",
|
||||
Lt: " < ",
|
||||
Lte: " <= ",
|
||||
Like: " LIKE ",
|
||||
In: " IN ",
|
||||
NotIN: " NOT IN ",
|
||||
IsNull: " IS NULL ",
|
||||
IsNotNull: " IS NOT NULL ",
|
||||
|
||||
"=": " = ",
|
||||
"!=": " <> ",
|
||||
">": " > ",
|
||||
">=": " >= ",
|
||||
"<": " < ",
|
||||
"<=": " <= ",
|
||||
"=": " = ",
|
||||
"!=": " <> ",
|
||||
">": " > ",
|
||||
">=": " >= ",
|
||||
"<": " < ",
|
||||
"<=": " <= ",
|
||||
"not in": " NOT IN ",
|
||||
"is null": " IS NULL ",
|
||||
"is not null": " IS NOT NULL ",
|
||||
}
|
||||
|
||||
var logicMap = map[string]string{
|
||||
@@ -75,7 +87,7 @@ type Params struct {
|
||||
// Column query info
|
||||
type Column struct {
|
||||
Name string `json:"name" form:"name"` // column name
|
||||
Exp string `json:"exp" form:"exp"` // expressions, default value is "=", support =, !=, >, >=, <, <=, like, in
|
||||
Exp string `json:"exp" form:"exp"` // expressions, default value is "=", support =, !=, >, >=, <, <=, like, in, notin, isnull, isnotnull
|
||||
Value interface{} `json:"value" form:"value"` // column value
|
||||
Logic string `json:"logic" form:"logic"` // logical type, defaults to and when the value is null, with &(and), ||(or)
|
||||
}
|
||||
@@ -85,25 +97,48 @@ func (c *Column) checkValid() error {
|
||||
return fmt.Errorf("field 'name' cannot be empty")
|
||||
}
|
||||
if c.Value == nil {
|
||||
v := expMap[strings.ToLower(c.Exp)]
|
||||
if v == " IS NULL " || v == " IS NOT NULL " {
|
||||
return 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 {
|
||||
func (c *Column) convert() (string, error) {
|
||||
symbol := "?"
|
||||
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)
|
||||
switch c.Exp {
|
||||
case " LIKE ":
|
||||
val, ok1 := c.Value.(string)
|
||||
if !ok1 {
|
||||
return symbol, fmt.Errorf("invalid value type '%s'", c.Value)
|
||||
}
|
||||
l := len(val)
|
||||
if l > 2 {
|
||||
val2 := val[1 : l-1]
|
||||
val2 = strings.ReplaceAll(val2, "%", "\\%")
|
||||
val2 = strings.ReplaceAll(val2, "_", "\\_")
|
||||
val = string(val[0]) + val2 + string(val[l-1])
|
||||
}
|
||||
if strings.HasPrefix(val, "%") ||
|
||||
strings.HasPrefix(val, "_") ||
|
||||
strings.HasSuffix(val, "%") ||
|
||||
strings.HasSuffix(val, "_") {
|
||||
c.Value = val
|
||||
} else {
|
||||
c.Value = "%" + val + "%"
|
||||
}
|
||||
case " IN ", " NOT IN ":
|
||||
val, ok1 := c.Value.(string)
|
||||
if !ok1 {
|
||||
return symbol, fmt.Errorf("invalid value type '%s'", c.Value)
|
||||
}
|
||||
iVal := []interface{}{}
|
||||
ss := strings.Split(val, ",")
|
||||
@@ -111,9 +146,13 @@ func (c *Column) convert() error {
|
||||
iVal = append(iVal, s)
|
||||
}
|
||||
c.Value = iVal
|
||||
symbol = "(?)"
|
||||
case " IS NULL ", " IS NOT NULL ":
|
||||
c.Value = nil
|
||||
symbol = ""
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("unknown exp type '%s'", c.Exp)
|
||||
return symbol, fmt.Errorf("unsported exp type '%s'", c.Exp)
|
||||
}
|
||||
|
||||
if c.Logic == "" {
|
||||
@@ -122,10 +161,10 @@ func (c *Column) convert() error {
|
||||
if v, ok := logicMap[strings.ToLower(c.Logic)]; ok { //nolint
|
||||
c.Logic = v
|
||||
} else {
|
||||
return fmt.Errorf("unknown logic type '%s'", c.Logic)
|
||||
return symbol, fmt.Errorf("unknown logic type '%s'", c.Logic)
|
||||
}
|
||||
|
||||
return nil
|
||||
return symbol, nil
|
||||
}
|
||||
|
||||
// ConvertToPage converted to page
|
||||
@@ -158,22 +197,19 @@ func (p *Params) ConvertToGormConditions() (string, []interface{}, error) {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
err := column.convert()
|
||||
symbol, 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)
|
||||
|
||||
if column.Value != nil {
|
||||
args = append(args, column.Value)
|
||||
}
|
||||
// when multiple columns are the same, determine whether the use of IN
|
||||
if isUseIN {
|
||||
if field != column.Name {
|
||||
|
@@ -141,13 +141,58 @@ func TestParams_ConvertToGormConditions(t *testing.T) {
|
||||
columns: []Column{
|
||||
{
|
||||
Name: "name",
|
||||
Value: "Li",
|
||||
Value: "foo",
|
||||
Exp: Like,
|
||||
},
|
||||
},
|
||||
},
|
||||
want: "name LIKE ?",
|
||||
want1: []interface{}{"%Li%"},
|
||||
want1: []interface{}{"%foo%"},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "1 column like prefix",
|
||||
args: args{
|
||||
columns: []Column{
|
||||
{
|
||||
Name: "name",
|
||||
Value: "%foo",
|
||||
Exp: Like,
|
||||
},
|
||||
},
|
||||
},
|
||||
want: "name LIKE ?",
|
||||
want1: []interface{}{"%foo"},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "1 column like suffix",
|
||||
args: args{
|
||||
columns: []Column{
|
||||
{
|
||||
Name: "name",
|
||||
Value: "foo%",
|
||||
Exp: Like,
|
||||
},
|
||||
},
|
||||
},
|
||||
want: "name LIKE ?",
|
||||
want1: []interface{}{"foo%"},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "1 column like with %",
|
||||
args: args{
|
||||
columns: []Column{
|
||||
{
|
||||
Name: "name",
|
||||
Value: "f%o_o",
|
||||
Exp: Like,
|
||||
},
|
||||
},
|
||||
},
|
||||
want: "name LIKE ?",
|
||||
want1: []interface{}{"%f\\%o\\_o%"},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
@@ -165,6 +210,49 @@ func TestParams_ConvertToGormConditions(t *testing.T) {
|
||||
want1: []interface{}{[]interface{}{"ab", "cd", "ef"}},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "1 column NOT IN",
|
||||
args: args{
|
||||
columns: []Column{
|
||||
{
|
||||
Name: "name",
|
||||
Value: "ab,cd,ef",
|
||||
Exp: NotIN,
|
||||
},
|
||||
},
|
||||
},
|
||||
want: "name NOT IN (?)",
|
||||
want1: []interface{}{[]interface{}{"ab", "cd", "ef"}},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "1 column IS NULL",
|
||||
args: args{
|
||||
columns: []Column{
|
||||
{
|
||||
Name: "name",
|
||||
Exp: IsNull,
|
||||
},
|
||||
},
|
||||
},
|
||||
want: "name IS NULL ",
|
||||
want1: []interface{}{},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "1 column IS NOT NULL",
|
||||
args: args{
|
||||
columns: []Column{
|
||||
{
|
||||
Name: "name",
|
||||
Exp: IsNotNull,
|
||||
},
|
||||
},
|
||||
},
|
||||
want: "name IS NOT NULL ",
|
||||
want1: []interface{}{},
|
||||
wantErr: false,
|
||||
},
|
||||
|
||||
// --------------------------- query 2 columns ------------------------------
|
||||
{
|
||||
@@ -444,14 +532,14 @@ func TestParams_ConvertToGormConditions(t *testing.T) {
|
||||
}
|
||||
got, got1, err := params.ConvertToGormConditions()
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ConvertToGormConditions() error = %v, wantErr %v", err, tt.wantErr)
|
||||
t.Errorf("ConvertToGormConditions() error = %v, wantErr = %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("ConvertToGormConditions() got = %v, want %v", got, tt.want)
|
||||
t.Errorf("ConvertToGormConditions() got = %v, want = %v", got, tt.want)
|
||||
}
|
||||
if !reflect.DeepEqual(got1, tt.want1) {
|
||||
t.Errorf("ConvertToGormConditions() got1 = %v, want %v", got1, tt.want1)
|
||||
t.Errorf("ConvertToGormConditions() got1 = %v, want = %v", got1, tt.want1)
|
||||
}
|
||||
|
||||
got = strings.Replace(got, "?", "%v", -1)
|
||||
|
Reference in New Issue
Block a user