Files
streamsql/expr/tokenizer_test.go
T
2025-08-07 19:18:40 +08:00

353 lines
8.8 KiB
Go

package expr
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestTokenize 测试分词功能
func TestTokenize(t *testing.T) {
tests := []struct {
name string
expr string
expected []string
wantErr bool
}{
// 基本分词测试
{"简单表达式", "a + b", []string{"a", "+", "b"}, false},
{"数字和运算符", "123 + 456", []string{"123", "+", "456"}, false},
{"小数", "3.14 * 2", []string{"3.14", "*", "2"}, false},
{"负数", "-5 + 3", []string{"-5", "+", "3"}, false},
{"负小数", "-3.14 * 2", []string{"-3.14", "*", "2"}, false},
// 括号和函数
{"括号表达式", "(a + b) * c", []string{"(", "a", "+", "b", ")", "*", "c"}, false},
{"函数调用", "abs(x)", []string{"abs", "(", "x", ")"}, false},
{"函数参数", "max(a, b)", []string{"max", "(", "a", ",", "b", ")"}, false},
// 比较运算符
{"等于运算符", "a == b", []string{"a", "==", "b"}, false},
{"不等于运算符", "a != b", []string{"a", "!=", "b"}, false},
{"大于等于", "a >= b", []string{"a", ">=", "b"}, false},
{"小于等于", "a <= b", []string{"a", "<=", "b"}, false},
{"不等于SQL风格", "a <> b", []string{"a", "<>", "b"}, false},
// 字符串字面量
{"单引号字符串", "'hello'", []string{"'hello'"}, false},
{"双引号字符串", "\"world\"", []string{"\"world\""}, false},
{"字符串比较", "name == 'test'", []string{"name", "==", "'test'"}, false},
{"包含转义的字符串", "'hello\\world'", []string{"'hello\\world'"}, false},
// 反引号标识符
{"反引号字段", "`field name`", []string{"`field name`"}, false},
{"反引号表达式", "`user.name` + `user.age`", []string{"`user.name`", "+", "`user.age`"}, false},
// CASE表达式
{"简单CASE", "CASE WHEN a > 0 THEN 1 ELSE 0 END", []string{"CASE", "WHEN", "a", ">", "0", "THEN", "1", "ELSE", "0", "END"}, false},
// 复杂表达式
{"复杂算术", "a + b * c - d / e", []string{"a", "+", "b", "*", "c", "-", "d", "/", "e"}, false},
{"幂运算", "a ^ b", []string{"a", "^", "b"}, false},
{"取模运算", "a % b", []string{"a", "%", "b"}, false},
// 空白字符处理
{"多个空格", "a + b", []string{"a", "+", "b"}, false},
{"制表符", "a\t+\tb", []string{"a", "+", "b"}, false},
{"换行符", "a\n+\nb", []string{"a", "+", "b"}, false},
// 错误情况
{"空表达式", "", nil, true},
{"只有空格", " ", nil, true},
{"未闭合字符串", "'hello", nil, true},
{"未闭合反引号", "`field", nil, true},
{"无效字符", "a @ b", nil, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := tokenize(tt.expr)
if tt.wantErr {
assert.Error(t, err, "应该返回错误")
} else {
require.NoError(t, err, "分词不应该失败")
assert.Equal(t, tt.expected, result, "分词结果应该匹配")
}
})
}
}
// TestIsDigit 测试数字字符判断
func TestIsDigit(t *testing.T) {
tests := []struct {
ch byte
expected bool
}{
{'0', true},
{'5', true},
{'9', true},
{'a', false},
{'A', false},
{' ', false},
{'.', false},
{'+', false},
}
for _, tt := range tests {
t.Run(string(tt.ch), func(t *testing.T) {
result := isDigit(tt.ch)
assert.Equal(t, tt.expected, result, "数字字符判断应该正确")
})
}
}
// TestIsLetter 测试字母字符判断
func TestIsLetter(t *testing.T) {
tests := []struct {
ch byte
expected bool
}{
{'a', true},
{'z', true},
{'A', true},
{'Z', true},
{'0', false},
{'9', false},
{' ', false},
{'_', false},
{'+', false},
}
for _, tt := range tests {
t.Run(string(tt.ch), func(t *testing.T) {
result := isLetter(tt.ch)
assert.Equal(t, tt.expected, result, "字母字符判断应该正确")
})
}
}
// TestIsNumber 测试数字字符串判断
func TestIsNumber(t *testing.T) {
tests := []struct {
s string
expected bool
}{
{"123", true},
{"0", true},
{"3.14", true},
{"-5", true},
{"-3.14", true},
{"1e10", true},
{"1.5e-3", true},
{"abc", false},
{"12a", false},
{"", false},
{".", false},
{"--5", false},
}
for _, tt := range tests {
t.Run(tt.s, func(t *testing.T) {
result := isNumber(tt.s)
assert.Equal(t, tt.expected, result, "数字字符串判断应该正确")
})
}
}
// TestIsIdentifier 测试标识符判断
func TestIsIdentifier(t *testing.T) {
tests := []struct {
s string
expected bool
}{
{"abc", true},
{"_var", true},
{"var123", true},
{"CamelCase", true},
{"snake_case", true},
{"123abc", false},
{"", false},
{"var-name", false},
{"var.name", false},
{"var name", false},
}
for _, tt := range tests {
t.Run(tt.s, func(t *testing.T) {
result := isIdentifier(tt.s)
assert.Equal(t, tt.expected, result, "标识符判断应该正确")
})
}
}
// TestIsOperator 测试运算符判断
func TestIsOperator(t *testing.T) {
tests := []struct {
s string
expected bool
}{
{"+", true},
{"-", true},
{"*", true},
{"/", true},
{"%", true},
{"^", true},
{">", true},
{"<", true},
{">=", true},
{"<=", true},
{"==", true},
{"=", true},
{"!=", true},
{"<>", true},
{"AND", true},
{"OR", true},
{"NOT", true},
{"LIKE", true},
{"IS", true},
{"abc", false},
{"123", false},
{"(", false},
{")", false},
}
for _, tt := range tests {
t.Run(tt.s, func(t *testing.T) {
result := isOperator(tt.s)
assert.Equal(t, tt.expected, result, "运算符判断应该正确")
})
}
}
// TestIsComparisonOperator 测试比较运算符判断
func TestIsComparisonOperator(t *testing.T) {
tests := []struct {
s string
expected bool
}{
{">", true},
{"<", true},
{">=", true},
{"<=", true},
{"==", true},
{"=", true},
{"!=", true},
{"<>", true},
{"+", false},
{"-", false},
{"*", false},
{"/", false},
{"AND", false},
{"OR", false},
}
for _, tt := range tests {
t.Run(tt.s, func(t *testing.T) {
result := isComparisonOperator(tt.s)
assert.Equal(t, tt.expected, result, "比较运算符判断应该正确")
})
}
}
// TestIsStringLiteral 测试字符串字面量判断
func TestIsStringLiteral(t *testing.T) {
tests := []struct {
s string
expected bool
}{
{"'hello'", true},
{"\"world\"", true},
{"''", true},
{"\"\"", true},
{"'hello", false},
{"hello'", false},
{"\"hello", false},
{"hello\"", false},
{"hello", false},
{"", false},
{"'", false},
{"\"", false},
}
for _, tt := range tests {
t.Run(tt.s, func(t *testing.T) {
result := isStringLiteral(tt.s)
assert.Equal(t, tt.expected, result, "字符串字面量判断应该正确")
})
}
}
// TestTokenizeComplexExpressions 测试复杂表达式分词
func TestTokenizeComplexExpressions(t *testing.T) {
tests := []struct {
name string
expr string
expected []string
}{
{
"温度转换表达式",
"temperature * 1.8 + 32",
[]string{"temperature", "*", "1.8", "+", "32"},
},
{
"复杂CASE表达式",
"CASE WHEN temperature > 30 AND humidity < 60 THEN 'HOT' ELSE 'NORMAL' END",
[]string{"CASE", "WHEN", "temperature", ">", "30", "AND", "humidity", "<", "60", "THEN", "'HOT'", "ELSE", "'NORMAL'", "END"},
},
{
"嵌套函数调用",
"sqrt(pow(a, 2) + pow(b, 2))",
[]string{"sqrt", "(", "pow", "(", "a", ",", "2", ")", "+", "pow", "(", "b", ",", "2", ")", ")"},
},
{
"负数在比较运算符后",
"a > -5 AND b <= -3.14",
[]string{"a", ">", "-5", "AND", "b", "<=", "-3.14"},
},
{
"幂运算后的负数",
"a ^ -2",
[]string{"a", "^", "-2"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := tokenize(tt.expr)
require.NoError(t, err, "复杂表达式分词不应该失败")
assert.Equal(t, tt.expected, result, "复杂表达式分词结果应该匹配")
})
}
}
// TestTokenizeEdgeCases 测试边界情况
func TestTokenizeEdgeCases(t *testing.T) {
tests := []struct {
name string
expr string
expected []string
wantErr bool
}{
{"只有数字", "123", []string{"123"}, false},
{"只有小数点开头的数字", ".5", []string{".5"}, false},
{"连续运算符(应该在解析阶段检测)", "a + + b", []string{"a", "+", "+", "b"}, false},
{"多个小数点", "3.14.15", []string{"3.14", ".", "15"}, false}, // 分词器不检查语法错误
{"空字符串转义", "''", []string{"''"}, false},
{"包含空格的反引号标识符", "`user name`", []string{"`user name`"}, false},
{"特殊字符在字符串中", "'hello@world#test'", []string{"'hello@world#test'"}, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := tokenize(tt.expr)
if tt.wantErr {
assert.Error(t, err, "应该返回错误")
} else {
require.NoError(t, err, "分词不应该失败")
assert.Equal(t, tt.expected, result, "分词结果应该匹配")
}
})
}
}