feat(coperator): 实现比较运算符功能

- 新增 compare 函数用于比较两个任意类型的值
- 实现了 $gt、$ge、$lt、$le、$eq、$ne、$in、$nin 等比较运算符
- 更新了 DocumentOperator 函数,支持使用比较运算符进行过滤
- 添加了比较运算符相关的单元测试
This commit is contained in:
kingecg 2025-06-19 20:22:04 +08:00
parent 93fd7363a3
commit 9c2512ea96
4 changed files with 321 additions and 2 deletions

188
compare.go Normal file
View File

@ -0,0 +1,188 @@
package coperator
import (
"errors"
"reflect"
)
/*
Compare 比较两个任意类型的值
参数:
a: 第一个比较值
b: 第二个比较值
返回值:
int: 比较结果 (-1:a < b, 0:a == b, 1:a > b)
error: 类型不匹配或不可比较时返回错误
*/
func compare(a, b interface{}) (int, error) {
// 快速返回如果值完全相等直接返回0
// 获取类型信息并验证类型一致性
atype := reflect.TypeOf(a)
btype := reflect.TypeOf(b)
if atype != btype {
return 0, errors.New("types are not equal: " + atype.String() + " != " + btype.String())
}
// 验证类型是否可比较
if !atype.Comparable() {
return 0, errors.New("types are not comparable: " + atype.String())
}
if reflect.DeepEqual(a, b) {
return 0, nil
}
// 处理不同类型的基础类型比较
switch atype.Kind() {
// 处理有符号整数类型比较
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
av := reflect.ValueOf(a).Int()
bv := reflect.ValueOf(b).Int()
if av < bv {
return -1, nil
}
if av > bv {
return 1, nil
}
return 0, nil
// 处理无符号整数类型比较
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
av := reflect.ValueOf(a).Uint()
bv := reflect.ValueOf(b).Uint()
if av < bv {
return -1, nil
}
if av > bv {
return 1, nil
}
return 0, nil
// 处理浮点数类型比较
case reflect.Float32, reflect.Float64:
av := reflect.ValueOf(a).Float()
bv := reflect.ValueOf(b).Float()
if av < bv {
return -1, nil
}
if av > bv {
return 1, nil
}
return 0, nil
// 处理字符串类型比较
case reflect.String:
av := reflect.ValueOf(a).String()
bv := reflect.ValueOf(b).String()
if av < bv {
return -1, nil
}
if av > bv {
return 1, nil
}
return 0, nil
// 布尔类型特殊处理
case reflect.Bool:
return 0, errors.New("cannot compare boolean values")
// 默认不支持的类型
default:
return 0, errors.New("unsupported type for comparison: " + atype.String())
}
}
func comparOp(doc *Document, key string, value interface{}) (int, error) {
v := doc.Get(key)
if v == nil {
return 0, nil
}
ret, err := compare(v, value)
if err != nil {
return 0, err
}
return ret, nil
}
func operatorGt(doc *Document, filter Filter, key string, value interface{}) (ret bool, err error) {
v, e := comparOp(doc, key, value)
if e != nil {
return false, e
}
return v > 0, nil
}
func operatorGe(doc *Document, filter Filter, key string, value interface{}) (ret bool, err error) {
v, e := comparOp(doc, key, value)
if e != nil {
return false, e
}
return v >= 0, nil
}
func operatorLt(doc *Document, filter Filter, key string, value interface{}) (ret bool, err error) {
v, e := comparOp(doc, key, value)
if e != nil {
return false, e
}
return v < 0, nil
}
func operatorLe(doc *Document, filter Filter, key string, value interface{}) (ret bool, err error) {
v, e := comparOp(doc, key, value)
if e != nil {
return false, e
}
return v <= 0, nil
}
func operatorEq(doc *Document, filter Filter, key string, value interface{}) (ret bool, err error) {
v, e := comparOp(doc, key, value)
if e != nil {
return false, e
}
return v == 0, nil
}
func operatorNe(doc *Document, filter Filter, key string, value interface{}) (ret bool, err error) {
v, e := comparOp(doc, key, value)
if e != nil {
return false, e
}
return v != 0, nil
}
func operatorIn(doc *Document, filter Filter, key string, value interface{}) (ret bool, err error) {
if value == nil {
return false, nil
}
slice, ok := value.([]interface{})
if !ok {
return false, errors.New("value for $in operator must be a slice")
}
if len(slice) == 0 {
return false, nil
}
for _, v := range slice {
cmp, err := compare(doc.Get(key), v)
if err != nil {
return false, err
}
if cmp == 0 {
return true, nil
}
}
return false, nil
}
func operatorNotIn(doc *Document, filter Filter, key string, value interface{}) (ret bool, err error) {
lret, err := operatorIn(doc, filter, key, value)
if err != nil {
return false, err
}
return !lret, nil
}

75
compare_test.go Normal file
View File

@ -0,0 +1,75 @@
package coperator
import (
"testing"
)
func TestCompare(t *testing.T) {
testCases := []struct {
name string
a, b interface{}
expected int
err bool
}{
{
name: "int equal",
a: 1,
b: 1,
expected: 0,
err: false,
},
{
name: "int less",
a: 1,
b: 2,
expected: -1,
err: false,
},
{
name: "int greater",
a: 3,
b: 2,
expected: 1,
err: false,
},
{
name: "string less",
a: "a",
b: "b",
expected: -1,
err: false,
},
{
name: "different types",
a: 1,
b: "1",
expected: 0,
err: true,
},
{
name: "unsupported type",
a: []int{1},
b: []int{1},
expected: 0,
err: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
res, err := compare(tc.a, tc.b)
if tc.err {
if err == nil {
t.Errorf("expected error but got nil")
}
} else {
if err != nil {
t.Errorf("unexpected error: %v", err)
}
}
if res != tc.expected {
t.Errorf("expected %d but got %d", tc.expected, res)
}
})
}
}

View File

@ -143,6 +143,13 @@ func FieldOperator(doc *Document, filter Filter, key string, value interface{})
return true, nil
}
func operatorGt(doc *Document, filter Filter, key string, value interface{}) (ret bool, err error) {
return false, nil
func init() {
operatorMap["$gt"] = operatorGt
operatorMap["$ge"] = operatorGe
operatorMap["$lt"] = operatorLt
operatorMap["$le"] = operatorLe
operatorMap["$eq"] = operatorEq
operatorMap["$ne"] = operatorNe
operatorMap["$in"] = operatorIn
operatorMap["$nin"] = operatorNotIn
}

View File

@ -132,6 +132,55 @@ func TestDocumentOperator(t *testing.T) {
},
want: true,
},
{
name: "test with operator",
doc: Document{
"age": 18,
},
filter: Filter{
"age": Filter{
"$gt": 17,
},
},
want: true,
},
{
name: "test with multi operator",
doc: Document{
"age": 18,
},
filter: Filter{
"age": Filter{
"$gt": 17,
"$lt": 16,
},
},
want: false,
},
{
name: "test with multi operator",
doc: Document{
"age": 18,
},
filter: Filter{
"age": Filter{
"$in": []interface{}{16, 17, 18},
},
},
want: true,
},
{
name: "test with multi operator",
doc: Document{
"age": 22,
},
filter: Filter{
"age": Filter{
"$nin": []interface{}{16, 17, 18},
},
},
want: true,
},
}
for _, tt := range tests {