From 9c2512ea961d48efc3ea5a21a394c6dec0d26d1a Mon Sep 17 00:00:00 2001 From: kingecg Date: Thu, 19 Jun 2025 20:22:04 +0800 Subject: [PATCH] =?UTF-8?q?feat(coperator):=20=E5=AE=9E=E7=8E=B0=E6=AF=94?= =?UTF-8?q?=E8=BE=83=E8=BF=90=E7=AE=97=E7=AC=A6=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 compare 函数用于比较两个任意类型的值 - 实现了 $gt、$ge、$lt、$le、$eq、$ne、$in、$nin 等比较运算符 - 更新了 DocumentOperator 函数,支持使用比较运算符进行过滤 - 添加了比较运算符相关的单元测试 --- compare.go | 188 ++++++++++++++++++++++++++++++++++++++++++++++ compare_test.go | 75 ++++++++++++++++++ coperator.go | 11 ++- coperator_test.go | 49 ++++++++++++ 4 files changed, 321 insertions(+), 2 deletions(-) create mode 100644 compare.go create mode 100644 compare_test.go diff --git a/compare.go b/compare.go new file mode 100644 index 0000000..78c48d1 --- /dev/null +++ b/compare.go @@ -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 +} diff --git a/compare_test.go b/compare_test.go new file mode 100644 index 0000000..3eb768d --- /dev/null +++ b/compare_test.go @@ -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) + } + }) + } +} diff --git a/coperator.go b/coperator.go index a7fcb5a..d37ea73 100644 --- a/coperator.go +++ b/coperator.go @@ -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 } diff --git a/coperator_test.go b/coperator_test.go index 49801d6..8c409aa 100644 --- a/coperator_test.go +++ b/coperator_test.go @@ -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 {