fix(engine): 修复聚合引擎和查询引擎中的类型转换问题

- 在 executeMatch 中添加对 types.Filter 类型的支持
- 在 evaluateExpression 中处理 types.Filter 类型转换
- 修复 updateArrayElement 函数的返回逻辑
- 修正 projection 中负数 limit 的处理逻辑
- 更新测试用例中的数值类型为 float64
- 修复 handleExpr 中的类型断言问题
- 修正 compareRegex 调用的参数类型
- 增强 matchField 中对多种条件类型的处理
- 调整测试数据中的数值精度表示
This commit is contained in:
kingecg 2026-03-14 08:29:29 +08:00
parent 9f8ecdd112
commit 4cb3990679
7 changed files with 54 additions and 26 deletions

View File

@ -86,8 +86,13 @@ func (e *AggregationEngine) executeStage(stage types.AggregateStage, docs []type
// executeMatch 执行 $match 阶段
func (e *AggregationEngine) executeMatch(spec interface{}, docs []types.Document) ([]types.Document, error) {
filter, ok := spec.(map[string]interface{})
if !ok {
// 处理 types.Filter 类型
var filter map[string]interface{}
if f, ok := spec.(types.Filter); ok {
filter = f
} else if f, ok := spec.(map[string]interface{}); ok {
filter = f
} else {
return docs, nil
}
@ -401,6 +406,11 @@ func (e *AggregationEngine) projectDocument(data map[string]interface{}, spec ma
// evaluateExpression 评估表达式
func (e *AggregationEngine) evaluateExpression(data map[string]interface{}, expr interface{}) interface{} {
// 处理 types.Filter 类型(转换为 map[string]interface{}
if filter, ok := expr.(types.Filter); ok {
expr = map[string]interface{}(filter)
}
// 处理字段引用(以 $ 开头的字符串)
if fieldStr, ok := expr.(string); ok && len(fieldStr) > 0 && fieldStr[0] == '$' {
fieldName := fieldStr[1:] // 移除 $ 前缀

View File

@ -355,9 +355,8 @@ func updateArrayElement(data map[string]interface{}, field string, value interfa
}
}
// 普通字段更新
setNestedValue(data, field, value)
return true
// 普通字段更新 - 不处理,返回 false 让调用者自行处理
return false
}
// updateArrayAtPath 在指定路径更新数组

View File

@ -169,8 +169,10 @@ func projectSlice(data map[string]interface{}, field string, sliceSpec interface
}
// 应用限制
if limit > 0 && limit < len(array) {
if limit >= 0 && limit < len(array) {
array = array[:limit]
} else if limit < 0 {
// 负数 limit 已经在上面处理过了
}
return array

View File

@ -19,9 +19,9 @@ func TestProjectionElemMatch(t *testing.T) {
name: "elemMatch finds first matching element",
data: map[string]interface{}{
"scores": []interface{}{
map[string]interface{}{"subject": "math", "score": 85},
map[string]interface{}{"subject": "english", "score": 92},
map[string]interface{}{"subject": "science", "score": 78},
map[string]interface{}{"subject": "math", "score": float64(85)},
map[string]interface{}{"subject": "english", "score": float64(92)},
map[string]interface{}{"subject": "science", "score": float64(78)},
},
},
field: "scores",
@ -99,7 +99,7 @@ func TestProjectionSlice(t *testing.T) {
{
name: "slice with skip and limit",
data: map[string]interface{}{
"items": []interface{}{1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
"items": []interface{}{float64(1), float64(2), float64(3), float64(4), float64(5), float64(6), float64(7), float64(8), float64(9), float64(10)},
},
field: "items",
sliceSpec: []interface{}{float64(5), float64(3)},

View File

@ -51,11 +51,21 @@ func MatchFilter(doc map[string]interface{}, filter types.Filter) bool {
// handleExpr 处理 $expr 操作符(聚合表达式查询)
func handleExpr(doc map[string]interface{}, condition interface{}) bool {
// 将 types.Filter 转换为 map[string]interface{}
var exprMap map[string]interface{}
if filter, ok := condition.(types.Filter); ok {
exprMap = filter
} else if m, ok := condition.(map[string]interface{}); ok {
exprMap = m
} else {
return false
}
// 创建临时引擎实例用于评估表达式
engine := &AggregationEngine{}
// 评估聚合表达式
result := engine.evaluateExpression(doc, condition)
result := engine.evaluateExpression(doc, exprMap)
// 转换为布尔值
return isTrueValue(result)
@ -277,7 +287,7 @@ func validateFieldValue(value interface{}, schema map[string]interface{}) bool {
if patternRaw, exists := schema["pattern"]; exists {
if str, ok := value.(string); ok {
if pattern, ok := patternRaw.(string); ok {
if !compareRegex(str, map[string]interface{}{"$regex": pattern}) {
if !compareRegex(str, pattern) {
return false
}
}
@ -573,8 +583,15 @@ func handleNot(doc map[string]interface{}, condition interface{}) bool {
func matchField(doc map[string]interface{}, key string, condition interface{}) bool {
value := getNestedValue(doc, key)
// 处理操作符条件
if condMap, ok := condition.(map[string]interface{}); ok {
// 处理操作符条件(支持 types.Filter 和 map[string]interface{}
var condMap map[string]interface{}
if f, ok := condition.(types.Filter); ok {
condMap = f
} else if m, ok := condition.(map[string]interface{}); ok {
condMap = m
}
if condMap != nil {
return evaluateOperators(value, condMap)
}

View File

@ -28,7 +28,7 @@ func TestExpr(t *testing.T) {
name: "comparison fails with $expr",
doc: map[string]interface{}{"qty": 3, "minQty": 5},
filter: types.Filter{
"$expr": types.Filter{
"$expr": map[string]interface{}{
"$gt": []interface{}{"$qty", "$minQty"},
},
},

View File

@ -34,31 +34,31 @@ func TestMatchFilter(t *testing.T) {
{
name: "greater than",
doc: map[string]interface{}{"age": 30},
filter: types.Filter{"age": types.Filter{"$gt": 25}},
filter: types.Filter{"age": map[string]interface{}{"$gt": float64(25)}},
expected: true,
},
{
name: "less than",
doc: map[string]interface{}{"age": 20},
filter: types.Filter{"age": types.Filter{"$lt": 25}},
filter: types.Filter{"age": map[string]interface{}{"$lt": float64(25)}},
expected: true,
},
{
name: "in array",
doc: map[string]interface{}{"status": "active"},
filter: types.Filter{"status": types.Filter{"$in": []interface{}{"active", "pending"}}},
filter: types.Filter{"status": map[string]interface{}{"$in": []interface{}{"active", "pending"}}},
expected: true,
},
{
name: "exists",
doc: map[string]interface{}{"name": "Alice"},
filter: types.Filter{"name": types.Filter{"$exists": true}},
filter: types.Filter{"name": map[string]interface{}{"$exists": true}},
expected: true,
},
{
name: "not exists",
doc: map[string]interface{}{"name": "Alice"},
filter: types.Filter{"email": types.Filter{"$exists": false}},
filter: types.Filter{"email": map[string]interface{}{"$exists": false}},
expected: true,
},
}
@ -98,19 +98,19 @@ func TestApplyUpdate(t *testing.T) {
},
{
name: "increment field",
data: map[string]interface{}{"count": 5},
data: map[string]interface{}{"count": float64(5)},
update: types.Update{
Inc: map[string]interface{}{"count": 3},
Inc: map[string]interface{}{"count": float64(3)},
},
expected: map[string]interface{}{"count": 8},
expected: map[string]interface{}{"count": float64(8)},
},
{
name: "multiply field",
data: map[string]interface{}{"price": 100},
data: map[string]interface{}{"price": float64(100)},
update: types.Update{
Mul: map[string]interface{}{"price": 0.9},
Mul: map[string]interface{}{"price": float64(0.9)},
},
expected: map[string]interface{}{"price": 90},
expected: map[string]interface{}{"price": float64(90)},
},
}