refactor:重构表达式引擎模块

This commit is contained in:
rulego-team
2025-08-07 19:18:40 +08:00
parent 57983f19d7
commit a066a4df1b
15 changed files with 7633 additions and 2984 deletions
+245
View File
@@ -0,0 +1,245 @@
package expr
import (
"fmt"
"strings"
)
// parseCaseExpression parses CASE expression
func parseCaseExpression(tokens []string) (*ExprNode, []string, error) {
if len(tokens) == 0 || strings.ToUpper(tokens[0]) != "CASE" {
return nil, nil, fmt.Errorf("expected CASE keyword")
}
remaining := tokens[1:]
caseExpr := &CaseExpression{}
// Check if it's a simple CASE expression (CASE expr WHEN value THEN result)
if len(remaining) > 0 && strings.ToUpper(remaining[0]) != "WHEN" {
// Simple CASE expression
value, newRemaining, err := parseOrExpression(remaining)
if err != nil {
return nil, nil, fmt.Errorf("error parsing CASE expression: %v", err)
}
caseExpr.Value = value
remaining = newRemaining
}
// Parse WHEN clauses
for len(remaining) > 0 && strings.ToUpper(remaining[0]) == "WHEN" {
remaining = remaining[1:] // Skip WHEN
// Parse WHEN condition
condition, newRemaining, err := parseOrExpression(remaining)
if err != nil {
return nil, nil, fmt.Errorf("error parsing WHEN condition: %v", err)
}
remaining = newRemaining
// Check THEN keyword
if len(remaining) == 0 || strings.ToUpper(remaining[0]) != "THEN" {
return nil, nil, fmt.Errorf("expected THEN after WHEN condition")
}
remaining = remaining[1:] // Skip THEN
// Parse THEN result
result, newRemaining, err := parseOrExpression(remaining)
if err != nil {
return nil, nil, fmt.Errorf("error parsing THEN result: %v", err)
}
remaining = newRemaining
// Add WHEN clause
caseExpr.WhenClauses = append(caseExpr.WhenClauses, WhenClause{
Condition: condition,
Result: result,
})
}
// Parse optional ELSE clause
if len(remaining) > 0 && strings.ToUpper(remaining[0]) == "ELSE" {
remaining = remaining[1:] // Skip ELSE
elseExpr, newRemaining, err := parseOrExpression(remaining)
if err != nil {
return nil, nil, fmt.Errorf("error parsing ELSE expression: %v", err)
}
caseExpr.ElseResult = elseExpr
remaining = newRemaining
}
// Check END keyword
if len(remaining) == 0 || strings.ToUpper(remaining[0]) != "END" {
return nil, nil, fmt.Errorf("expected END to close CASE expression")
}
// Create ExprNode containing CaseExpression
caseNode := &ExprNode{
Type: TypeCase,
CaseExpr: caseExpr,
}
return caseNode, remaining[1:], nil
}
// evaluateCaseExpression evaluates the value of CASE expression
func evaluateCaseExpression(node *ExprNode, data map[string]interface{}) (float64, error) {
if node.Type != TypeCase {
return 0, fmt.Errorf("not a CASE expression")
}
if node.CaseExpr == nil {
return 0, fmt.Errorf("invalid CASE expression")
}
// Simple CASE expression: CASE expr WHEN value THEN result
if node.CaseExpr.Value != nil {
return evaluateSimpleCaseExpression(node, data)
}
// Search CASE expression: CASE WHEN condition THEN result
return evaluateSearchCaseExpression(node, data)
}
// evaluateSimpleCaseExpression evaluates simple CASE expression
func evaluateSimpleCaseExpression(node *ExprNode, data map[string]interface{}) (float64, error) {
caseExpr := node.CaseExpr
if caseExpr == nil {
return 0, fmt.Errorf("invalid CASE expression")
}
// Evaluate CASE expression value
caseValue, err := evaluateNodeValue(caseExpr.Value, data)
if err != nil {
return 0, err
}
// Iterate through WHEN clauses
for _, whenClause := range caseExpr.WhenClauses {
// Evaluate WHEN value
whenValue, err := evaluateNodeValue(whenClause.Condition, data)
if err != nil {
return 0, err
}
// Compare values
if compareValuesForEquality(caseValue, whenValue) {
// Evaluate and return THEN result
return evaluateNode(whenClause.Result, data)
}
}
// If no matching WHEN clause, evaluate ELSE expression
if caseExpr.ElseResult != nil {
return evaluateNode(caseExpr.ElseResult, data)
}
// If no ELSE clause, return NULL (return 0 here)
return 0, nil
}
// evaluateSearchCaseExpression evaluates search CASE expression
func evaluateSearchCaseExpression(node *ExprNode, data map[string]interface{}) (float64, error) {
caseExpr := node.CaseExpr
if caseExpr == nil {
return 0, fmt.Errorf("invalid CASE expression")
}
// Iterate through WHEN clauses
for _, whenClause := range caseExpr.WhenClauses {
// Evaluate WHEN condition - use boolean evaluation to handle logical operators
conditionResult, err := evaluateBoolNode(whenClause.Condition, data)
if err != nil {
return 0, err
}
// If condition is true, return THEN result
if conditionResult {
return evaluateNode(whenClause.Result, data)
}
}
// If no matching WHEN clause, evaluate ELSE expression
if caseExpr.ElseResult != nil {
return evaluateNode(caseExpr.ElseResult, data)
}
// If no ELSE clause, return NULL (return 0 here)
return 0, nil
}
// evaluateCaseExpressionWithNull evaluates CASE expression with NULL value support
func evaluateCaseExpressionWithNull(node *ExprNode, data map[string]interface{}) (interface{}, bool, error) {
if node.Type != TypeCase {
return nil, false, fmt.Errorf("not a CASE expression")
}
caseExpr := node.CaseExpr
if caseExpr == nil {
return nil, false, fmt.Errorf("invalid CASE expression")
}
// Simple CASE expression: CASE expr WHEN value THEN result
if caseExpr.Value != nil {
return evaluateCaseExpressionValueWithNull(node, data)
}
// Search CASE expression: CASE WHEN condition THEN result
for _, whenClause := range caseExpr.WhenClauses {
// Evaluate WHEN condition - use boolean evaluation to handle logical operators
conditionResult, err := evaluateBoolNode(whenClause.Condition, data)
if err != nil {
return nil, false, err
}
// If condition is true, return THEN result
if conditionResult {
return evaluateNodeValueWithNull(whenClause.Result, data)
}
}
// If no matching WHEN clause, evaluate ELSE expression
if caseExpr.ElseResult != nil {
return evaluateNodeValueWithNull(caseExpr.ElseResult, data)
}
// If no ELSE clause, return NULL
return nil, true, nil
}
// evaluateCaseExpressionValueWithNull evaluates simple CASE expression (with NULL support)
func evaluateCaseExpressionValueWithNull(node *ExprNode, data map[string]interface{}) (interface{}, bool, error) {
caseExpr := node.CaseExpr
if caseExpr == nil {
return nil, false, fmt.Errorf("invalid CASE expression")
}
// Evaluate CASE expression value
caseValue, caseIsNull, err := evaluateNodeValueWithNull(caseExpr.Value, data)
if err != nil {
return nil, false, err
}
// Iterate through WHEN clauses
for _, whenClause := range caseExpr.WhenClauses {
// Evaluate WHEN value
whenValue, whenIsNull, err := evaluateNodeValueWithNull(whenClause.Condition, data)
if err != nil {
return nil, false, err
}
// Compare values (with NULL comparison support)
if compareValuesWithNullForEquality(caseValue, caseIsNull, whenValue, whenIsNull) {
// Evaluate and return THEN result
return evaluateNodeValueWithNull(whenClause.Result, data)
}
}
// If no matching WHEN clause, evaluate ELSE expression
if caseExpr.ElseResult != nil {
return evaluateNodeValueWithNull(caseExpr.ElseResult, data)
}
// If no ELSE clause, return NULL
return nil, true, nil
}
File diff suppressed because it is too large Load Diff
+10 -10
View File
@@ -67,7 +67,7 @@ Function call expression:
CASE expression for conditional logic:
expr, err := NewExpression(`
CASE
CASE
WHEN temperature > 30 THEN 'hot'
WHEN temperature > 20 THEN 'warm'
ELSE 'cold'
@@ -79,14 +79,14 @@ CASE expression for conditional logic:
The expression parser follows standard mathematical precedence rules:
1. Parentheses (highest)
2. Power (^)
3. Multiplication, Division, Modulo (*, /, %)
4. Addition, Subtraction (+, -)
5. Comparison (>, <, >=, <=, LIKE, IS)
6. Equality (=, ==, !=, <>)
7. Logical AND
8. Logical OR (lowest)
1. Parentheses (highest)
2. Power (^)
3. Multiplication, Division, Modulo (*, /, %)
4. Addition, Subtraction (+, -)
5. Comparison (>, <, >=, <=, LIKE, IS)
6. Equality (=, ==, !=, <>)
7. Logical AND
8. Logical OR (lowest)
# Error Handling
@@ -114,4 +114,4 @@ This package integrates seamlessly with other StreamSQL components:
• Stream package - For real-time expression evaluation in data streams
• RSQL package - For SQL parsing and expression extraction
*/
package expr
package expr
+962
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+64 -2155
View File
File diff suppressed because it is too large Load Diff
+1205 -819
View File
File diff suppressed because it is too large Load Diff
+342
View File
File diff suppressed because it is too large Load Diff
+312
View File
File diff suppressed because it is too large Load Diff
+280
View File
@@ -0,0 +1,280 @@
package expr
import (
"fmt"
"strings"
"unicode"
)
// TokenType represents token type
type TokenType int
const (
// TokenKeyword keyword token
TokenKeyword TokenType = iota
// TokenField field token
TokenField
// TokenOperator operator token
TokenOperator
// TokenNumber number token
TokenNumber
// TokenString string token
TokenString
// TokenLeftParen left parenthesis token
TokenLeftParen
// TokenRightParen right parenthesis token
TokenRightParen
// TokenComma comma token
TokenComma
)
// Token represents a token
type Token struct {
// Type token type
Type TokenType
// Value token value
Value string
}
// tokenize breaks expression string into token list
// Supports numbers, identifiers, operators, parentheses, string literals, etc.
func tokenize(expr string) ([]string, error) {
// Check empty expression
if len(strings.TrimSpace(expr)) == 0 {
return nil, fmt.Errorf("empty expression")
}
var tokens []string
i := 0
for i < len(expr) {
// Skip whitespace characters
if unicode.IsSpace(rune(expr[i])) {
i++
continue
}
// Handle string literals
if expr[i] == '\'' || expr[i] == '"' {
quote := expr[i]
start := i
i++ // Skip opening quote
// Find closing quote
for i < len(expr) && expr[i] != quote {
if expr[i] == '\\' && i+1 < len(expr) {
i += 2 // Skip escape character
} else {
i++
}
}
if i >= len(expr) {
return nil, fmt.Errorf("unterminated string literal")
}
i++ // Skip closing quote
tokens = append(tokens, expr[start:i])
continue
}
// Handle backtick identifiers
if expr[i] == '`' {
start := i
i++ // Skip opening backtick
// Find closing backtick
for i < len(expr) && expr[i] != '`' {
i++
}
if i >= len(expr) {
return nil, fmt.Errorf("unterminated backtick identifier")
}
i++ // Skip closing backtick
tokens = append(tokens, expr[start:i])
continue
}
// Handle numbers (including negative numbers and numbers starting with decimal point)
// Note: Numbers starting with decimal point are only valid when not preceded by digit character
if isDigit(expr[i]) || (expr[i] == '-' && i+1 < len(expr) && isDigit(expr[i+1])) || (expr[i] == '.' && i+1 < len(expr) && isDigit(expr[i+1]) && (i == 0 || (!isDigit(expr[i-1]) && expr[i-1] != '.'))) {
start := i
if expr[i] == '-' {
i++ // Skip negative sign
}
// Read integer part
for i < len(expr) && isDigit(expr[i]) {
i++
}
// Handle decimal point (only one decimal point allowed)
hasDecimal := false
if i < len(expr) && expr[i] == '.' {
// Check if there's already a decimal point or next character is not a digit
if !hasDecimal && i+1 < len(expr) && isDigit(expr[i+1]) {
hasDecimal = true
i++
// Read decimal part
for i < len(expr) && isDigit(expr[i]) {
i++
}
}
}
// Handle scientific notation
if i < len(expr) && (expr[i] == 'e' || expr[i] == 'E') {
i++
if i < len(expr) && (expr[i] == '+' || expr[i] == '-') {
i++
}
for i < len(expr) && isDigit(expr[i]) {
i++
}
}
tokens = append(tokens, expr[start:i])
continue
}
// Handle multi-character operators
if i+1 < len(expr) {
twoChar := expr[i : i+2]
if isOperator(twoChar) {
tokens = append(tokens, twoChar)
i += 2
continue
}
}
// Handle single-character operators and parentheses (including standalone decimal point)
if isOperator(string(expr[i])) || expr[i] == '(' || expr[i] == ')' || expr[i] == ',' || expr[i] == '.' {
tokens = append(tokens, string(expr[i]))
i++
continue
}
// Handle identifiers and keywords
if isLetter(expr[i]) || expr[i] == '_' || expr[i] == '$' {
start := i
for i < len(expr) && (isLetter(expr[i]) || isDigit(expr[i]) || expr[i] == '_' || expr[i] == '.' || expr[i] == '$') {
i++
}
tokens = append(tokens, expr[start:i])
continue
}
// Unknown character
return nil, fmt.Errorf("unexpected character '%c' at position %d", expr[i], i)
}
return tokens, nil
}
// isDigit checks if character is a digit
func isDigit(ch byte) bool {
return ch >= '0' && ch <= '9'
}
// isLetter checks if character is a letter
func isLetter(ch byte) bool {
return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')
}
// isNumber checks if string is a number
func isNumber(s string) bool {
if len(s) == 0 {
return false
}
i := 0
// Handle negative sign
if s[0] == '-' {
i = 1
if len(s) == 1 {
return false
}
}
hasDigit := false
hasDot := false
for i < len(s) {
if isDigit(s[i]) {
hasDigit = true
} else if s[i] == '.' && !hasDot {
hasDot = true
} else if s[i] == 'e' || s[i] == 'E' {
// Scientific notation
i++
if i < len(s) && (s[i] == '+' || s[i] == '-') {
i++
}
for i < len(s) && isDigit(s[i]) {
i++
}
break
} else {
return false
}
i++
}
return hasDigit
}
// isIdentifier checks if string is a valid identifier
func isIdentifier(s string) bool {
if len(s) == 0 {
return false
}
// First character must be letter or underscore
if !isLetter(s[0]) && s[0] != '_' {
return false
}
// Remaining characters can be letters, digits, or underscores
for i := 1; i < len(s); i++ {
if !isLetter(s[i]) && !isDigit(s[i]) && s[i] != '_' {
return false
}
}
return true
}
// isOperator checks if string is an operator
func isOperator(s string) bool {
operators := []string{
"+", "-", "*", "/", "%", "^",
"=", "==", "!=", "<>", ">", "<", ">=", "<=",
"AND", "OR", "NOT", "LIKE", "IS",
}
for _, op := range operators {
if strings.EqualFold(s, op) {
return true
}
}
return false
}
// isComparisonOperator checks if it's a comparison operator
func isComparisonOperator(op string) bool {
comparisonOps := []string{"==", "=", "!=", "<>", ">", "<", ">=", "<=", "LIKE", "IS"}
for _, compOp := range comparisonOps {
if strings.EqualFold(op, compOp) {
return true
}
}
return false
}
// isStringLiteral checks if it's a string literal
func isStringLiteral(s string) bool {
return len(s) >= 2 && ((s[0] == '\'' && s[len(s)-1] == '\'') || (s[0] == '"' && s[len(s)-1] == '"'))
}
File diff suppressed because it is too large Load Diff
+261
View File
@@ -0,0 +1,261 @@
package expr
import (
"fmt"
"github.com/rulego/streamsql/utils/cast"
"strings"
)
// unquoteString removes quotes from both ends of string
func unquoteString(s string) string {
if len(s) >= 2 {
if (s[0] == '"' && s[len(s)-1] == '"') || (s[0] == '\'' && s[len(s)-1] == '\'') {
return s[1 : len(s)-1]
}
}
return s
}
// unquoteBacktick removes backticks from both ends of string
func unquoteBacktick(s string) string {
if len(s) >= 2 && s[0] == '`' && s[len(s)-1] == '`' {
return s[1 : len(s)-1]
}
return s
}
// getNodeType gets node type
func getNodeType(node *ExprNode) string {
if node == nil {
return "nil"
}
return node.Type
}
// getNodeValue gets node value
func getNodeValue(node *ExprNode) string {
if node == nil {
return ""
}
return node.Value
}
// setNodeValue sets node value
func setNodeValue(node *ExprNode, value string) {
if node != nil {
node.Value = value
}
}
// isArithmeticOperator checks if it's an arithmetic operator
func isArithmeticOperator(op string) bool {
switch op {
case "+", "-", "*", "/", "%", "^":
return true
default:
return false
}
}
// isLogicalOperator checks if it's a logical operator
func isLogicalOperator(op string) bool {
switch strings.ToUpper(op) {
case "AND", "OR", "NOT":
return true
default:
return false
}
}
// isUnaryOperator checks if it's a unary operator
func isUnaryOperator(op string) bool {
switch strings.ToUpper(op) {
case "NOT":
return true
default:
return false
}
}
// isKeyword checks if it's a keyword
func isKeyword(word string) bool {
switch strings.ToUpper(word) {
case "CASE", "WHEN", "THEN", "ELSE", "END", "AND", "OR", "NOT", "LIKE", "IS", "NULL", "TRUE", "FALSE":
return true
default:
return false
}
}
// normalizeIdentifier normalizes identifier (convert to lowercase)
func normalizeIdentifier(identifier string) string {
return strings.ToLower(identifier)
}
// convertToFloat converts any type to float64
func convertToFloat(value interface{}) (float64, error) {
return cast.ToFloat64E(value)
}
// convertToFloatSafe safely converts any type to float64, returns conversion result and success status
func convertToFloatSafe(value interface{}) (float64, bool) {
result, err := convertToFloat(value)
return result, err == nil
}
// convertToBool converts any type to boolean
func convertToBool(value interface{}) bool {
return cast.ToBool(value)
}
// getOperatorPrecedence gets operator precedence
func getOperatorPrecedence(op string) int {
switch op {
case "OR":
return 1
case "AND":
return 2
case "NOT":
return 3
case "=", "==", "!=", "<>", ">", "<", ">=", "<=", "LIKE", "NOT LIKE", "IS", "IS NOT":
return 4
case "+", "-":
return 5
case "*", "/", "%":
return 6
case "^":
return 7
default:
return 0
}
}
// isRightAssociative checks if operator is right associative
func isRightAssociative(op string) bool {
// Only power operator is right associative
return op == "^"
}
// parseFunction parses function call (test helper function)
func parseFunction(tokens []string, pos int) (*ExprNode, int, error) {
if pos >= len(tokens) {
return nil, pos, fmt.Errorf("unexpected end of tokens")
}
// Call existing parseFunctionCall function
node, remaining, err := parseFunctionCall(tokens[pos:])
if err != nil {
return nil, pos, err
}
// Calculate new position
newPos := len(tokens) - len(remaining)
return node, newPos, nil
}
// formatError formats error message
func formatError(message string, args ...interface{}) error {
if len(args) == 0 {
return fmt.Errorf("%s", message)
}
return fmt.Errorf(message, args...)
}
// copyNode deep copies expression node
func copyNode(node *ExprNode) *ExprNode {
if node == nil {
return nil
}
newNode := &ExprNode{
Type: node.Type,
Value: node.Value,
Left: copyNode(node.Left),
Right: copyNode(node.Right),
}
// Copy function arguments
if len(node.Args) > 0 {
newNode.Args = make([]*ExprNode, len(node.Args))
for i, arg := range node.Args {
newNode.Args[i] = copyNode(arg)
}
}
// Copy CASE expression
if node.CaseExpr != nil {
newNode.CaseExpr = &CaseExpression{
Value: copyNode(node.CaseExpr.Value),
ElseResult: copyNode(node.CaseExpr.ElseResult),
}
// Copy WHEN clauses
if len(node.CaseExpr.WhenClauses) > 0 {
newNode.CaseExpr.WhenClauses = make([]WhenClause, len(node.CaseExpr.WhenClauses))
for i, whenClause := range node.CaseExpr.WhenClauses {
newNode.CaseExpr.WhenClauses[i] = WhenClause{
Condition: copyNode(whenClause.Condition),
Result: copyNode(whenClause.Result),
}
}
}
}
return newNode
}
// nodeToString converts expression node to string representation
func nodeToString(node *ExprNode) string {
if node == nil {
return "<nil>"
}
switch node.Type {
case TypeNumber, TypeString, TypeField:
return node.Value
case TypeOperator:
left := nodeToString(node.Left)
right := nodeToString(node.Right)
return fmt.Sprintf("(%s %s %s)", left, node.Value, right)
case TypeFunction:
args := make([]string, len(node.Args))
for i, arg := range node.Args {
args[i] = nodeToString(arg)
}
return fmt.Sprintf("%s(%s)", node.Value, strings.Join(args, ", "))
case TypeCase:
return caseExprToString(node.CaseExpr)
default:
return fmt.Sprintf("<%s:%s>", node.Type, node.Value)
}
}
// caseExprToString converts CASE expression to string representation
func caseExprToString(caseExpr *CaseExpression) string {
if caseExpr == nil {
return "<nil case>"
}
var result strings.Builder
result.WriteString("CASE")
if caseExpr.Value != nil {
result.WriteString(" ")
result.WriteString(nodeToString(caseExpr.Value))
}
for _, whenClause := range caseExpr.WhenClauses {
result.WriteString(" WHEN ")
result.WriteString(nodeToString(whenClause.Condition))
result.WriteString(" THEN ")
result.WriteString(nodeToString(whenClause.Result))
}
if caseExpr.ElseResult != nil {
result.WriteString(" ELSE ")
result.WriteString(nodeToString(caseExpr.ElseResult))
}
result.WriteString(" END")
return result.String()
}
+596
View File
File diff suppressed because it is too large Load Diff
+449
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff