diff options
Diffstat (limited to 'weed/query/engine/string_functions.go')
| -rw-r--r-- | weed/query/engine/string_functions.go | 354 |
1 files changed, 354 insertions, 0 deletions
diff --git a/weed/query/engine/string_functions.go b/weed/query/engine/string_functions.go new file mode 100644 index 000000000..2143a75bc --- /dev/null +++ b/weed/query/engine/string_functions.go @@ -0,0 +1,354 @@ +package engine + +import ( + "fmt" + "math" + "strings" + + "github.com/seaweedfs/seaweedfs/weed/pb/schema_pb" +) + +// =============================== +// STRING FUNCTIONS +// =============================== + +// Length returns the length of a string +func (e *SQLEngine) Length(value *schema_pb.Value) (*schema_pb.Value, error) { + if value == nil { + return nil, fmt.Errorf("LENGTH function requires non-null value") + } + + str, err := e.valueToString(value) + if err != nil { + return nil, fmt.Errorf("LENGTH function conversion error: %v", err) + } + + length := int64(len(str)) + return &schema_pb.Value{ + Kind: &schema_pb.Value_Int64Value{Int64Value: length}, + }, nil +} + +// Upper converts a string to uppercase +func (e *SQLEngine) Upper(value *schema_pb.Value) (*schema_pb.Value, error) { + if value == nil { + return nil, fmt.Errorf("UPPER function requires non-null value") + } + + str, err := e.valueToString(value) + if err != nil { + return nil, fmt.Errorf("UPPER function conversion error: %v", err) + } + + return &schema_pb.Value{ + Kind: &schema_pb.Value_StringValue{StringValue: strings.ToUpper(str)}, + }, nil +} + +// Lower converts a string to lowercase +func (e *SQLEngine) Lower(value *schema_pb.Value) (*schema_pb.Value, error) { + if value == nil { + return nil, fmt.Errorf("LOWER function requires non-null value") + } + + str, err := e.valueToString(value) + if err != nil { + return nil, fmt.Errorf("LOWER function conversion error: %v", err) + } + + return &schema_pb.Value{ + Kind: &schema_pb.Value_StringValue{StringValue: strings.ToLower(str)}, + }, nil +} + +// Trim removes leading and trailing whitespace from a string +func (e *SQLEngine) Trim(value *schema_pb.Value) (*schema_pb.Value, error) { + if value == nil { + return nil, fmt.Errorf("TRIM function requires non-null value") + } + + str, err := e.valueToString(value) + if err != nil { + return nil, fmt.Errorf("TRIM function conversion error: %v", err) + } + + return &schema_pb.Value{ + Kind: &schema_pb.Value_StringValue{StringValue: strings.TrimSpace(str)}, + }, nil +} + +// LTrim removes leading whitespace from a string +func (e *SQLEngine) LTrim(value *schema_pb.Value) (*schema_pb.Value, error) { + if value == nil { + return nil, fmt.Errorf("LTRIM function requires non-null value") + } + + str, err := e.valueToString(value) + if err != nil { + return nil, fmt.Errorf("LTRIM function conversion error: %v", err) + } + + return &schema_pb.Value{ + Kind: &schema_pb.Value_StringValue{StringValue: strings.TrimLeft(str, " \t\n\r")}, + }, nil +} + +// RTrim removes trailing whitespace from a string +func (e *SQLEngine) RTrim(value *schema_pb.Value) (*schema_pb.Value, error) { + if value == nil { + return nil, fmt.Errorf("RTRIM function requires non-null value") + } + + str, err := e.valueToString(value) + if err != nil { + return nil, fmt.Errorf("RTRIM function conversion error: %v", err) + } + + return &schema_pb.Value{ + Kind: &schema_pb.Value_StringValue{StringValue: strings.TrimRight(str, " \t\n\r")}, + }, nil +} + +// Substring extracts a substring from a string +func (e *SQLEngine) Substring(value *schema_pb.Value, start *schema_pb.Value, length ...*schema_pb.Value) (*schema_pb.Value, error) { + if value == nil || start == nil { + return nil, fmt.Errorf("SUBSTRING function requires non-null value and start position") + } + + str, err := e.valueToString(value) + if err != nil { + return nil, fmt.Errorf("SUBSTRING function value conversion error: %v", err) + } + + startPos, err := e.valueToInt64(start) + if err != nil { + return nil, fmt.Errorf("SUBSTRING function start position conversion error: %v", err) + } + + // Convert to 0-based indexing (SQL uses 1-based) + if startPos < 1 { + startPos = 1 + } + startIdx := int(startPos - 1) + + if startIdx >= len(str) { + return &schema_pb.Value{ + Kind: &schema_pb.Value_StringValue{StringValue: ""}, + }, nil + } + + var result string + if len(length) > 0 && length[0] != nil { + lengthVal, err := e.valueToInt64(length[0]) + if err != nil { + return nil, fmt.Errorf("SUBSTRING function length conversion error: %v", err) + } + + if lengthVal <= 0 { + result = "" + } else { + if lengthVal > int64(math.MaxInt) || lengthVal < int64(math.MinInt) { + // If length is out-of-bounds for int, take substring from startIdx to end + result = str[startIdx:] + } else { + // Safe conversion after bounds check + endIdx := startIdx + int(lengthVal) + if endIdx > len(str) { + endIdx = len(str) + } + result = str[startIdx:endIdx] + } + } + } else { + result = str[startIdx:] + } + + return &schema_pb.Value{ + Kind: &schema_pb.Value_StringValue{StringValue: result}, + }, nil +} + +// Concat concatenates multiple strings +func (e *SQLEngine) Concat(values ...*schema_pb.Value) (*schema_pb.Value, error) { + if len(values) == 0 { + return &schema_pb.Value{ + Kind: &schema_pb.Value_StringValue{StringValue: ""}, + }, nil + } + + var result strings.Builder + for i, value := range values { + if value == nil { + continue // Skip null values + } + + str, err := e.valueToString(value) + if err != nil { + return nil, fmt.Errorf("CONCAT function value %d conversion error: %v", i, err) + } + result.WriteString(str) + } + + return &schema_pb.Value{ + Kind: &schema_pb.Value_StringValue{StringValue: result.String()}, + }, nil +} + +// Replace replaces all occurrences of a substring with another substring +func (e *SQLEngine) Replace(value, oldStr, newStr *schema_pb.Value) (*schema_pb.Value, error) { + if value == nil || oldStr == nil || newStr == nil { + return nil, fmt.Errorf("REPLACE function requires non-null values") + } + + str, err := e.valueToString(value) + if err != nil { + return nil, fmt.Errorf("REPLACE function value conversion error: %v", err) + } + + old, err := e.valueToString(oldStr) + if err != nil { + return nil, fmt.Errorf("REPLACE function old string conversion error: %v", err) + } + + new, err := e.valueToString(newStr) + if err != nil { + return nil, fmt.Errorf("REPLACE function new string conversion error: %v", err) + } + + result := strings.ReplaceAll(str, old, new) + + return &schema_pb.Value{ + Kind: &schema_pb.Value_StringValue{StringValue: result}, + }, nil +} + +// Position returns the position of a substring in a string (1-based, 0 if not found) +func (e *SQLEngine) Position(substring, value *schema_pb.Value) (*schema_pb.Value, error) { + if substring == nil || value == nil { + return nil, fmt.Errorf("POSITION function requires non-null values") + } + + str, err := e.valueToString(value) + if err != nil { + return nil, fmt.Errorf("POSITION function string conversion error: %v", err) + } + + substr, err := e.valueToString(substring) + if err != nil { + return nil, fmt.Errorf("POSITION function substring conversion error: %v", err) + } + + pos := strings.Index(str, substr) + if pos == -1 { + pos = 0 // SQL returns 0 for not found + } else { + pos = pos + 1 // Convert to 1-based indexing + } + + return &schema_pb.Value{ + Kind: &schema_pb.Value_Int64Value{Int64Value: int64(pos)}, + }, nil +} + +// Left returns the leftmost characters of a string +func (e *SQLEngine) Left(value *schema_pb.Value, length *schema_pb.Value) (*schema_pb.Value, error) { + if value == nil || length == nil { + return nil, fmt.Errorf("LEFT function requires non-null values") + } + + str, err := e.valueToString(value) + if err != nil { + return nil, fmt.Errorf("LEFT function string conversion error: %v", err) + } + + lengthVal, err := e.valueToInt64(length) + if err != nil { + return nil, fmt.Errorf("LEFT function length conversion error: %v", err) + } + + if lengthVal <= 0 { + return &schema_pb.Value{ + Kind: &schema_pb.Value_StringValue{StringValue: ""}, + }, nil + } + + if lengthVal > int64(len(str)) { + return &schema_pb.Value{ + Kind: &schema_pb.Value_StringValue{StringValue: str}, + }, nil + } + + if lengthVal > int64(math.MaxInt) || lengthVal < int64(math.MinInt) { + return &schema_pb.Value{ + Kind: &schema_pb.Value_StringValue{StringValue: str}, + }, nil + } + + // Safe conversion after bounds check + return &schema_pb.Value{ + Kind: &schema_pb.Value_StringValue{StringValue: str[:int(lengthVal)]}, + }, nil +} + +// Right returns the rightmost characters of a string +func (e *SQLEngine) Right(value *schema_pb.Value, length *schema_pb.Value) (*schema_pb.Value, error) { + if value == nil || length == nil { + return nil, fmt.Errorf("RIGHT function requires non-null values") + } + + str, err := e.valueToString(value) + if err != nil { + return nil, fmt.Errorf("RIGHT function string conversion error: %v", err) + } + + lengthVal, err := e.valueToInt64(length) + if err != nil { + return nil, fmt.Errorf("RIGHT function length conversion error: %v", err) + } + + if lengthVal <= 0 { + return &schema_pb.Value{ + Kind: &schema_pb.Value_StringValue{StringValue: ""}, + }, nil + } + + if lengthVal > int64(len(str)) { + return &schema_pb.Value{ + Kind: &schema_pb.Value_StringValue{StringValue: str}, + }, nil + } + + if lengthVal > int64(math.MaxInt) || lengthVal < int64(math.MinInt) { + return &schema_pb.Value{ + Kind: &schema_pb.Value_StringValue{StringValue: str}, + }, nil + } + + // Safe conversion after bounds check + startPos := len(str) - int(lengthVal) + return &schema_pb.Value{ + Kind: &schema_pb.Value_StringValue{StringValue: str[startPos:]}, + }, nil +} + +// Reverse reverses a string +func (e *SQLEngine) Reverse(value *schema_pb.Value) (*schema_pb.Value, error) { + if value == nil { + return nil, fmt.Errorf("REVERSE function requires non-null value") + } + + str, err := e.valueToString(value) + if err != nil { + return nil, fmt.Errorf("REVERSE function conversion error: %v", err) + } + + // Reverse the string rune by rune to handle Unicode correctly + runes := []rune(str) + for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 { + runes[i], runes[j] = runes[j], runes[i] + } + + return &schema_pb.Value{ + Kind: &schema_pb.Value_StringValue{StringValue: string(runes)}, + }, nil +} |
