mirror of
https://gitee.com/rulego/streamsql.git
synced 2026-03-13 13:57:29 +00:00
313 lines
7.2 KiB
Go
313 lines
7.2 KiB
Go
package expr
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// TestParseExpression 测试表达式解析功能
|
|
func TestParseExpression(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
tokens []string
|
|
expectError bool
|
|
description string
|
|
}{
|
|
{
|
|
name: "empty tokens",
|
|
tokens: []string{},
|
|
expectError: true,
|
|
description: "should return error for empty tokens",
|
|
},
|
|
{
|
|
name: "valid simple expression",
|
|
tokens: []string{"field1"},
|
|
expectError: false,
|
|
description: "should parse simple field",
|
|
},
|
|
{
|
|
name: "valid case expression",
|
|
tokens: []string{"CASE", "WHEN", "field1", "THEN", "1", "END"},
|
|
expectError: false,
|
|
description: "should parse CASE expression",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
_, err := ParseExpression(tt.tokens)
|
|
if tt.expectError && err == nil {
|
|
t.Errorf("expected error but got none: %s", tt.description)
|
|
}
|
|
if !tt.expectError && err != nil {
|
|
t.Errorf("unexpected error: %v", err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParseUnaryExpression(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
tokens []string
|
|
expectError bool
|
|
description string
|
|
}{
|
|
{
|
|
name: "empty tokens",
|
|
tokens: []string{},
|
|
expectError: true,
|
|
description: "should return error for empty tokens",
|
|
},
|
|
{
|
|
name: "unary minus with number",
|
|
tokens: []string{"-", "5"},
|
|
expectError: false,
|
|
description: "should parse unary minus with number",
|
|
},
|
|
{
|
|
name: "unary minus with field",
|
|
tokens: []string{"-", "field1"},
|
|
expectError: false,
|
|
description: "should parse unary minus with field",
|
|
},
|
|
{
|
|
name: "unary minus with expression",
|
|
tokens: []string{"-", "(", "field1", "+", "field2", ")"},
|
|
expectError: false,
|
|
description: "should parse unary minus with expression",
|
|
},
|
|
{
|
|
name: "unary minus with function",
|
|
tokens: []string{"-", "sum", "(", "field1", ")"},
|
|
expectError: false,
|
|
description: "should parse unary minus with function",
|
|
},
|
|
{
|
|
name: "unary minus with string",
|
|
tokens: []string{"-", "'value'"},
|
|
expectError: false,
|
|
description: "should parse unary minus with string",
|
|
},
|
|
{
|
|
name: "unary minus with missing operand",
|
|
tokens: []string{"-"},
|
|
expectError: true,
|
|
description: "should return error for missing operand",
|
|
},
|
|
{
|
|
name: "nested unary minus",
|
|
tokens: []string{"-", "-", "5"},
|
|
expectError: false,
|
|
description: "should parse nested unary minus",
|
|
},
|
|
{
|
|
name: "unary minus with complex expression",
|
|
tokens: []string{"-", "field1", "*", "field2"},
|
|
expectError: false,
|
|
description: "should parse unary minus with complex expression",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
_, _, err := parseUnaryExpression(tt.tokens)
|
|
if tt.expectError && err == nil {
|
|
t.Errorf("expected error but got none: %s", tt.description)
|
|
}
|
|
if !tt.expectError && err != nil {
|
|
t.Errorf("unexpected error: %v", err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestParseExpressionWithPrecedence 测试运算符优先级解析
|
|
func TestParseExpressionWithPrecedence(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
tokens []string
|
|
expected string // 用字符串表示预期的树结构
|
|
}{
|
|
{"加法和乘法", []string{"a", "+", "b", "*", "c"}, "(a + (b * c))"},
|
|
{"乘法和除法", []string{"a", "*", "b", "/", "c"}, "((a * b) / c)"},
|
|
{"幂运算", []string{"a", "^", "b", "^", "c"}, "(a ^ (b ^ c))"},
|
|
{"混合运算", []string{"a", "+", "b", "*", "c", "^", "d"}, "(a + (b * (c ^ d)))"},
|
|
{"比较运算符", []string{"a", "+", "b", ">", "c"}, "((a + b) > c)"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result, err := parseExpression(tt.tokens)
|
|
require.NoError(t, err, "解析不应该失败")
|
|
actual := nodeToString(result)
|
|
assert.Equal(t, tt.expected, actual, "运算符优先级应该正确")
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestParseFunction 测试函数解析
|
|
func TestParseFunction(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
tokens []string
|
|
pos int
|
|
expected *ExprNode
|
|
newPos int
|
|
wantErr bool
|
|
}{
|
|
{
|
|
"无参数函数",
|
|
[]string{"now", "(", ")"},
|
|
0,
|
|
&ExprNode{Type: TypeFunction, Value: "now", Args: []*ExprNode{}},
|
|
3,
|
|
false,
|
|
},
|
|
{
|
|
"单参数函数",
|
|
[]string{"abs", "(", "x", ")"},
|
|
0,
|
|
&ExprNode{
|
|
Type: TypeFunction,
|
|
Value: "abs",
|
|
Args: []*ExprNode{{Type: TypeField, Value: "x"}},
|
|
},
|
|
4,
|
|
false,
|
|
},
|
|
{
|
|
"多参数函数",
|
|
[]string{"max", "(", "a", ",", "b", ",", "c", ")"},
|
|
0,
|
|
&ExprNode{
|
|
Type: TypeFunction,
|
|
Value: "max",
|
|
Args: []*ExprNode{
|
|
{Type: TypeField, Value: "a"},
|
|
{Type: TypeField, Value: "b"},
|
|
{Type: TypeField, Value: "c"},
|
|
},
|
|
},
|
|
8,
|
|
false,
|
|
},
|
|
{
|
|
"嵌套函数",
|
|
[]string{"sqrt", "(", "pow", "(", "x", ",", "2", ")", ")"},
|
|
0,
|
|
&ExprNode{
|
|
Type: TypeFunction,
|
|
Value: "sqrt",
|
|
Args: []*ExprNode{
|
|
{
|
|
Type: TypeFunction,
|
|
Value: "pow",
|
|
Args: []*ExprNode{
|
|
{Type: TypeField, Value: "x"},
|
|
{Type: TypeNumber, Value: "2"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
9,
|
|
false,
|
|
},
|
|
// 错误情况
|
|
{"缺少左括号", []string{"abs", "x", ")"}, 0, nil, 0, true},
|
|
{"缺少右括号", []string{"abs", "(", "x"}, 0, nil, 0, true},
|
|
{"参数分隔符错误", []string{"max", "(", "a", ";", "b", ")"}, 0, nil, 0, true},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result, newPos, err := parseFunction(tt.tokens, tt.pos)
|
|
if tt.wantErr {
|
|
assert.Error(t, err, "应该返回错误")
|
|
} else {
|
|
require.NoError(t, err, "函数解析不应该失败")
|
|
assert.Equal(t, tt.newPos, newPos, "位置应该正确")
|
|
assertNodeEqual(t, tt.expected, result)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestGetOperatorPrecedence 测试运算符优先级获取
|
|
func TestGetOperatorPrecedence(t *testing.T) {
|
|
tests := []struct {
|
|
op string
|
|
expected int
|
|
}{
|
|
{"OR", 1},
|
|
{"AND", 2},
|
|
{"NOT", 3},
|
|
{"=", 4},
|
|
{"==", 4},
|
|
{"!=", 4},
|
|
{"<>", 4},
|
|
{"<", 4},
|
|
{">", 4},
|
|
{"<=", 4},
|
|
{">=", 4},
|
|
{"LIKE", 4},
|
|
{"IS", 4},
|
|
{"+", 5},
|
|
{"-", 5},
|
|
{"*", 6},
|
|
{"/", 6},
|
|
{"%", 6},
|
|
{"^", 7},
|
|
{"unknown", 0},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.op, func(t *testing.T) {
|
|
result := getOperatorPrecedence(tt.op)
|
|
assert.Equal(t, tt.expected, result, "运算符优先级应该正确")
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestIsRightAssociative 测试右结合性判断
|
|
func TestIsRightAssociative(t *testing.T) {
|
|
tests := []struct {
|
|
op string
|
|
expected bool
|
|
}{
|
|
{"^", true},
|
|
{"+", false},
|
|
{"-", false},
|
|
{"*", false},
|
|
{"/", false},
|
|
{"=", false},
|
|
{"AND", false},
|
|
{"OR", false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.op, func(t *testing.T) {
|
|
result := isRightAssociative(tt.op)
|
|
assert.Equal(t, tt.expected, result, "右结合性判断应该正确")
|
|
})
|
|
}
|
|
}
|
|
|
|
// 辅助函数:连接字符串数组
|
|
func joinStrings(strs []string, sep string) string {
|
|
if len(strs) == 0 {
|
|
return ""
|
|
}
|
|
if len(strs) == 1 {
|
|
return strs[0]
|
|
}
|
|
|
|
result := strs[0]
|
|
for i := 1; i < len(strs); i++ {
|
|
result += sep + strs[i]
|
|
}
|
|
return result
|
|
}
|