Files
streamsql/expr/expression_test.go
T
2025-06-17 14:20:40 +08:00

468 lines
14 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package expr
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestExpressionEvaluation(t *testing.T) {
tests := []struct {
name string
expr string
data map[string]interface{}
expected float64
hasError bool
}{
// 基本运算测试
{"Simple Addition", "a + b", map[string]interface{}{"a": 5, "b": 3}, 8, false},
{"Simple Subtraction", "a - b", map[string]interface{}{"a": 5, "b": 3}, 2, false},
{"Simple Multiplication", "a * b", map[string]interface{}{"a": 5, "b": 3}, 15, false},
{"Simple Division", "a / b", map[string]interface{}{"a": 6, "b": 3}, 2, false},
{"Modulo", "a % b", map[string]interface{}{"a": 7, "b": 4}, 3, false},
{"Power", "a ^ b", map[string]interface{}{"a": 2, "b": 3}, 8, false},
// 复合表达式测试
{"Complex Expression", "a + b * c", map[string]interface{}{"a": 5, "b": 3, "c": 2}, 11, false},
{"Complex Expression With Parentheses", "(a + b) * c", map[string]interface{}{"a": 5, "b": 3, "c": 2}, 16, false},
{"Multiple Operations", "a + b * c - d / e", map[string]interface{}{"a": 5, "b": 3, "c": 2, "d": 8, "e": 4}, 9, false},
// 函数调用测试
{"Abs Function", "abs(a - b)", map[string]interface{}{"a": 3, "b": 5}, 2, false},
{"Sqrt Function", "sqrt(a)", map[string]interface{}{"a": 16}, 4, false},
{"Round Function", "round(a)", map[string]interface{}{"a": 3.7}, 4, false},
// 转换测试
{"String to Number", "a + b", map[string]interface{}{"a": "5", "b": 3}, 8, false},
// 复杂表达式测试
{"Temperature Conversion", "temperature * 1.8 + 32", map[string]interface{}{"temperature": 25}, 77, false},
{"Complex Math", "sqrt(abs(a * b - c / d))", map[string]interface{}{"a": 10, "b": 2, "c": 5, "d": 1}, 3.872983346207417, false},
// 错误测试
{"Division by Zero", "a / b", map[string]interface{}{"a": 5, "b": 0}, 0, true},
{"Missing Field", "a + b", map[string]interface{}{"a": 5}, 0, true},
{"Invalid Function", "unknown(a)", map[string]interface{}{"a": 5}, 0, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
expr, err := NewExpression(tt.expr)
assert.NoError(t, err, "Expression parsing should not fail")
result, err := expr.Evaluate(tt.data)
if tt.hasError {
assert.Error(t, err, "Expected error")
} else {
assert.NoError(t, err, "Evaluation should not fail")
assert.InDelta(t, tt.expected, result, 0.001, "Result should match expected value")
}
})
}
}
// TestCaseExpressionParsing 测试CASE表达式的解析功能
func TestCaseExpressionParsing(t *testing.T) {
tests := []struct {
name string
exprStr string
data map[string]interface{}
expected float64
wantErr bool
}{
{
name: "简单的搜索CASE表达式",
exprStr: "CASE WHEN temperature > 30 THEN 1 ELSE 0 END",
data: map[string]interface{}{"temperature": 35.0},
expected: 1.0,
wantErr: false,
},
{
name: "简单CASE表达式 - 值匹配",
exprStr: "CASE status WHEN 'active' THEN 1 WHEN 'inactive' THEN 0 ELSE -1 END",
data: map[string]interface{}{"status": "active"},
expected: 1.0,
wantErr: false,
},
{
name: "CASE表达式 - ELSE分支",
exprStr: "CASE WHEN temperature > 50 THEN 1 ELSE 0 END",
data: map[string]interface{}{"temperature": 25.5},
expected: 0.0,
wantErr: false,
},
{
name: "复杂搜索CASE表达式",
exprStr: "CASE WHEN temperature > 30 THEN 'HOT' WHEN temperature > 20 THEN 'WARM' ELSE 'COLD' END",
data: map[string]interface{}{"temperature": 25.0},
expected: 4.0, // 字符串"WARM"的长度
wantErr: false,
},
{
name: "数值比较的简单CASE",
exprStr: "CASE temperature WHEN 25 THEN 1 WHEN 30 THEN 2 ELSE 0 END",
data: map[string]interface{}{"temperature": 30.0},
expected: 2.0,
wantErr: false,
},
{
name: "布尔值CASE表达式",
exprStr: "CASE WHEN temperature > 25 AND humidity > 50 THEN 1 ELSE 0 END",
data: map[string]interface{}{"temperature": 30.0, "humidity": 60.0},
expected: 1.0,
wantErr: false,
},
{
name: "多条件CASE表达式_AND",
exprStr: "CASE WHEN temperature > 30 AND humidity < 60 THEN 1 WHEN temperature > 20 THEN 2 ELSE 0 END",
data: map[string]interface{}{"temperature": 35.0, "humidity": 50.0},
expected: 1.0,
wantErr: false,
},
{
name: "多条件CASE表达式_OR",
exprStr: "CASE WHEN temperature > 40 OR humidity > 80 THEN 1 ELSE 0 END",
data: map[string]interface{}{"temperature": 25.0, "humidity": 85.0},
expected: 1.0,
wantErr: false,
},
{
name: "函数调用在CASE中_ABS",
exprStr: "CASE WHEN ABS(temperature) > 30 THEN 1 ELSE 0 END",
data: map[string]interface{}{"temperature": -35.0},
expected: 1.0,
wantErr: false,
},
{
name: "函数调用在CASE中_ROUND",
exprStr: "CASE WHEN ROUND(temperature) = 25 THEN 1 ELSE 0 END",
data: map[string]interface{}{"temperature": 24.7},
expected: 1.0,
wantErr: false,
},
{
name: "复杂条件组合",
exprStr: "CASE WHEN temperature > 30 AND (humidity > 60 OR pressure < 1000) THEN 1 ELSE 0 END",
data: map[string]interface{}{"temperature": 35.0, "humidity": 55.0, "pressure": 950.0},
expected: 1.0,
wantErr: false,
},
{
name: "CASE中的算术表达式",
exprStr: "CASE WHEN temperature * 1.8 + 32 > 100 THEN 1 ELSE 0 END",
data: map[string]interface{}{"temperature": 40.0}, // 40*1.8+32 = 104
expected: 1.0,
wantErr: false,
},
{
name: "字符串函数在CASE中",
exprStr: "CASE WHEN LENGTH(device_name) > 5 THEN 1 ELSE 0 END",
data: map[string]interface{}{"device_name": "sensor123"},
expected: 1.0, // LENGTH函数正常工作"sensor123"长度为9 > 5返回1
wantErr: false,
},
{
name: "简单CASE与函数",
exprStr: "CASE ABS(temperature) WHEN 30 THEN 1 WHEN 25 THEN 2 ELSE 0 END",
data: map[string]interface{}{"temperature": -30.0},
expected: 1.0,
wantErr: false,
},
{
name: "CASE结果中的函数",
exprStr: "CASE WHEN temperature > 30 THEN ABS(temperature) ELSE ROUND(temperature) END",
data: map[string]interface{}{"temperature": 35.5},
expected: 35.5,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
expression, err := NewExpression(tt.exprStr)
if tt.wantErr {
assert.Error(t, err)
return
}
assert.NoError(t, err, "Expression creation should not fail")
assert.NotNil(t, expression, "Expression should not be nil")
// 测试表达式计算
result, err := expression.Evaluate(tt.data)
if tt.wantErr {
assert.Error(t, err)
return
}
assert.NoError(t, err, "Expression evaluation should not fail")
assert.Equal(t, tt.expected, result, "Expression result should match expected value")
})
}
}
// TestCaseExpressionFieldExtraction 测试CASE表达式的字段提取功能
func TestCaseExpressionFieldExtraction(t *testing.T) {
testCases := []struct {
name string
exprStr string
expectedFields []string
}{
{
name: "简单CASE字段提取",
exprStr: "CASE WHEN temperature > 30 THEN 1 ELSE 0 END",
expectedFields: []string{"temperature"},
},
{
name: "多字段CASE字段提取",
exprStr: "CASE WHEN temperature > 30 AND humidity < 60 THEN 1 ELSE 0 END",
expectedFields: []string{"temperature", "humidity"},
},
{
name: "简单CASE字段提取",
exprStr: "CASE status WHEN 'active' THEN temperature ELSE humidity END",
expectedFields: []string{"status", "temperature", "humidity"},
},
{
name: "函数CASE字段提取",
exprStr: "CASE WHEN ABS(temperature) > 30 THEN device_id ELSE location END",
expectedFields: []string{"temperature", "device_id", "location"},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
expression, err := NewExpression(tc.exprStr)
assert.NoError(t, err, "表达式创建应该成功")
fields := expression.GetFields()
// 验证所有期望的字段都被提取到了
for _, expectedField := range tc.expectedFields {
assert.Contains(t, fields, expectedField, "应该包含字段: %s", expectedField)
}
})
}
}
// TestCaseExpressionWithNullComparisons 测试CASE表达式中的NULL比较
func TestCaseExpressionWithNullComparisons(t *testing.T) {
tests := []struct {
name string
exprStr string
data map[string]interface{}
expected interface{} // 使用interface{}以支持NULL值
isNull bool
}{
{
name: "NULL值在CASE条件中 - 应该走ELSE分支",
exprStr: "CASE WHEN temperature > 30 THEN 1 ELSE 0 END",
data: map[string]interface{}{"temperature": nil},
expected: 0.0,
isNull: false,
},
{
name: "IS NULL条件 - 应该匹配",
exprStr: "CASE WHEN temperature IS NULL THEN 1 ELSE 0 END",
data: map[string]interface{}{"temperature": nil},
expected: 1.0,
isNull: false,
},
{
name: "IS NOT NULL条件 - 不应该匹配",
exprStr: "CASE WHEN temperature IS NOT NULL THEN 1 ELSE 0 END",
data: map[string]interface{}{"temperature": nil},
expected: 0.0,
isNull: false,
},
{
name: "CASE表达式返回NULL",
exprStr: "CASE WHEN temperature > 30 THEN temperature ELSE NULL END",
data: map[string]interface{}{"temperature": 25.0},
expected: nil,
isNull: true,
},
{
name: "CASE表达式返回有效值",
exprStr: "CASE WHEN temperature > 30 THEN temperature ELSE NULL END",
data: map[string]interface{}{"temperature": 35.0},
expected: 35.0,
isNull: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
expression, err := NewExpression(tt.exprStr)
assert.NoError(t, err, "表达式解析应该成功")
// 测试支持NULL的计算方法
result, isNull, err := expression.EvaluateWithNull(tt.data)
assert.NoError(t, err, "表达式计算应该成功")
if tt.isNull {
assert.True(t, isNull, "表达式应该返回NULL")
} else {
assert.False(t, isNull, "表达式不应该返回NULL")
assert.Equal(t, tt.expected, result, "表达式结果应该匹配期望值")
}
})
}
}
// TestNegativeNumberSupport 专门测试负数支持
func TestNegativeNumberSupport(t *testing.T) {
tests := []struct {
name string
exprStr string
data map[string]interface{}
expected float64
wantErr bool
}{
{
name: "负数常量在THEN中",
exprStr: "CASE WHEN temperature > 0 THEN 1 ELSE -1 END",
data: map[string]interface{}{"temperature": -5.0},
expected: -1.0,
wantErr: false,
},
{
name: "负数常量在WHEN中",
exprStr: "CASE WHEN temperature < -10 THEN 1 ELSE 0 END",
data: map[string]interface{}{"temperature": -15.0},
expected: 1.0,
wantErr: false,
},
{
name: "负数小数",
exprStr: "CASE WHEN temperature > 0 THEN 1.5 ELSE -2.5 END",
data: map[string]interface{}{"temperature": -1.0},
expected: -2.5,
wantErr: false,
},
{
name: "负数在算术表达式中",
exprStr: "CASE WHEN temperature + (-10) > 0 THEN 1 ELSE 0 END",
data: map[string]interface{}{"temperature": 15.0},
expected: 1.0,
wantErr: false,
},
{
name: "负数与函数",
exprStr: "CASE WHEN ABS(temperature) > 10 THEN 1 ELSE 0 END",
data: map[string]interface{}{"temperature": -15.0},
expected: 1.0,
wantErr: false,
},
{
name: "负数在简单CASE中",
exprStr: "CASE temperature WHEN -10 THEN 1 WHEN -20 THEN 2 ELSE 0 END",
data: map[string]interface{}{"temperature": -10.0},
expected: 1.0,
wantErr: false,
},
{
name: "负零",
exprStr: "CASE WHEN temperature = -0 THEN 1 ELSE 0 END",
data: map[string]interface{}{"temperature": 0.0},
expected: 1.0,
wantErr: false,
},
// 基本负数运算
{
name: "直接负数",
exprStr: "-5",
data: map[string]interface{}{},
expected: -5.0,
wantErr: false,
},
{
name: "负数加法",
exprStr: "-5 + 3",
data: map[string]interface{}{},
expected: -2.0,
wantErr: false,
},
{
name: "负数乘法",
exprStr: "-3 * 4",
data: map[string]interface{}{},
expected: -12.0,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
expression, err := NewExpression(tt.exprStr)
if tt.wantErr {
assert.Error(t, err)
return
}
assert.NoError(t, err, "负数表达式解析应该成功")
assert.NotNil(t, expression, "表达式不应为空")
// 测试表达式计算
result, err := expression.Evaluate(tt.data)
assert.NoError(t, err, "负数表达式计算应该成功")
assert.Equal(t, tt.expected, result, "负数表达式结果应该匹配期望值")
})
}
}
func TestGetFields(t *testing.T) {
tests := []struct {
expr string
expectedFields []string
}{
{"a + b", []string{"a", "b"}},
{"a + b * c", []string{"a", "b", "c"}},
{"temperature * 1.8 + 32", []string{"temperature"}},
{"abs(humidity - 50)", []string{"humidity"}},
{"sqrt(x^2 + y^2)", []string{"x", "y"}},
}
for _, tt := range tests {
t.Run(tt.expr, func(t *testing.T) {
expr, err := NewExpression(tt.expr)
assert.NoError(t, err, "Expression parsing should not fail")
fields := expr.GetFields()
// 由于map迭代顺序不确定我们只检查长度和包含关系
assert.Equal(t, len(tt.expectedFields), len(fields), "Number of fields should match")
for _, field := range tt.expectedFields {
found := false
for _, f := range fields {
if f == field {
found = true
break
}
}
assert.True(t, found, "Field %s should be found", field)
}
})
}
}
func TestParseError(t *testing.T) {
tests := []struct {
name string
expr string
}{
{"Empty Expression", ""},
{"Mismatched Parentheses", "a + (b * c"},
{"Invalid Character", "a # b"},
{"Double Operator", "a + * b"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := NewExpression(tt.expr)
assert.Error(t, err, "Expression parsing should fail")
})
}
}