mirror of
https://gitee.com/rulego/streamsql.git
synced 2026-04-13 23:58:22 +00:00
refactor:重构表达式引擎模块
This commit is contained in:
@@ -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
@@ -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
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+64
-2155
File diff suppressed because it is too large
Load Diff
+1205
-819
File diff suppressed because it is too large
Load Diff
+342
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -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()
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user