mirror of
https://github.com/pikami/cosmium.git
synced 2026-06-10 06:18:09 +01:00
Implement REGEXMATCH function (#15)
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
This commit is contained in:
@@ -162,6 +162,8 @@ func (r rowContext) selectItem_SelectItemTypeFunctionCall(functionCall parsers.F
|
||||
return r.strings_EndsWith(functionCall.Arguments)
|
||||
case parsers.FunctionCallStartsWith:
|
||||
return r.strings_StartsWith(functionCall.Arguments)
|
||||
case parsers.FunctionCallRegexMatch:
|
||||
return r.strings_RegexMatch(functionCall.Arguments)
|
||||
case parsers.FunctionCallConcat:
|
||||
return r.strings_Concat(functionCall.Arguments)
|
||||
case parsers.FunctionCallIndexOf:
|
||||
|
||||
@@ -2,6 +2,7 @@ package memoryexecutor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/pikami/cosmium/internal/logger"
|
||||
@@ -75,6 +76,46 @@ func (r rowContext) strings_StartsWith(arguments []interface{}) bool {
|
||||
return strings.HasPrefix(str1, str2)
|
||||
}
|
||||
|
||||
func (r rowContext) strings_RegexMatch(arguments []interface{}) bool {
|
||||
value, valueOk := r.parseString(arguments[0])
|
||||
pattern, patternOk := r.parseString(arguments[1])
|
||||
if !valueOk || !patternOk {
|
||||
return false
|
||||
}
|
||||
|
||||
modifiers, ok := r.getStringFlag(arguments)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
regexPattern := pattern
|
||||
if strings.Contains(modifiers, "x") {
|
||||
regexPattern = stripRegexIgnoredWhitespace(regexPattern)
|
||||
}
|
||||
|
||||
var flags strings.Builder
|
||||
if strings.Contains(modifiers, "i") {
|
||||
flags.WriteByte('i')
|
||||
}
|
||||
if strings.Contains(modifiers, "m") {
|
||||
flags.WriteByte('m')
|
||||
}
|
||||
if strings.Contains(modifiers, "s") {
|
||||
flags.WriteByte('s')
|
||||
}
|
||||
if flags.Len() > 0 {
|
||||
regexPattern = "(?" + flags.String() + ")" + regexPattern
|
||||
}
|
||||
|
||||
matched, err := regexp.MatchString(regexPattern, value)
|
||||
if err != nil {
|
||||
logger.Errorf("strings_RegexMatch - invalid pattern %q: %v", pattern, err)
|
||||
return false
|
||||
}
|
||||
|
||||
return matched
|
||||
}
|
||||
|
||||
func (r rowContext) strings_Concat(arguments []interface{}) string {
|
||||
result := ""
|
||||
|
||||
@@ -318,6 +359,20 @@ func (r rowContext) getBoolFlag(arguments []interface{}) bool {
|
||||
return ignoreCase
|
||||
}
|
||||
|
||||
func (r rowContext) getStringFlag(arguments []interface{}) (string, bool) {
|
||||
if len(arguments) <= 2 || arguments[2] == nil {
|
||||
return "", true
|
||||
}
|
||||
|
||||
flagItem := arguments[2].(parsers.SelectItem)
|
||||
if value, ok := r.resolveSelectItem(flagItem).(string); ok {
|
||||
return value, true
|
||||
}
|
||||
|
||||
logger.ErrorLn("getStringFlag - got parameters of wrong type")
|
||||
return "", false
|
||||
}
|
||||
|
||||
func (r rowContext) parseString(argument interface{}) (value string, ok bool) {
|
||||
exItem := argument.(parsers.SelectItem)
|
||||
ex := r.resolveSelectItem(exItem)
|
||||
@@ -329,6 +384,41 @@ func (r rowContext) parseString(argument interface{}) (value string, ok bool) {
|
||||
return "", false
|
||||
}
|
||||
|
||||
func stripRegexIgnoredWhitespace(pattern string) string {
|
||||
var result strings.Builder
|
||||
inCharClass := false
|
||||
escaped := false
|
||||
|
||||
for _, r := range pattern {
|
||||
if escaped {
|
||||
result.WriteRune(r)
|
||||
escaped = false
|
||||
continue
|
||||
}
|
||||
|
||||
if r == '\\' {
|
||||
result.WriteRune(r)
|
||||
escaped = true
|
||||
continue
|
||||
}
|
||||
|
||||
switch r {
|
||||
case '[':
|
||||
inCharClass = true
|
||||
case ']':
|
||||
inCharClass = false
|
||||
}
|
||||
|
||||
if !inCharClass && (r == ' ' || r == '\t' || r == '\n' || r == '\r' || r == '\f') {
|
||||
continue
|
||||
}
|
||||
|
||||
result.WriteRune(r)
|
||||
}
|
||||
|
||||
return result.String()
|
||||
}
|
||||
|
||||
func convertToString(value interface{}) string {
|
||||
switch v := value.(type) {
|
||||
case string:
|
||||
|
||||
@@ -231,6 +231,42 @@ func Test_Execute_StringFunctions(t *testing.T) {
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("Should execute function REGEXMATCH()", func(t *testing.T) {
|
||||
testQueryExecute(
|
||||
t,
|
||||
parsers.SelectStmt{
|
||||
SelectItems: []parsers.SelectItem{
|
||||
{
|
||||
Path: []string{"c", "id"},
|
||||
Type: parsers.SelectItemTypeField,
|
||||
},
|
||||
{
|
||||
Alias: "regexMatch",
|
||||
Type: parsers.SelectItemTypeFunctionCall,
|
||||
Value: parsers.FunctionCall{
|
||||
Type: parsers.FunctionCallRegexMatch,
|
||||
Arguments: []interface{}{
|
||||
parsers.SelectItem{
|
||||
Path: []string{"c", "str"},
|
||||
Type: parsers.SelectItemTypeField,
|
||||
},
|
||||
testutils.SelectItem_Constant_String("COOL WORLD"),
|
||||
testutils.SelectItem_Constant_String("i"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Table: parsers.Table{SelectItem: testutils.SelectItem_Path("c")},
|
||||
},
|
||||
mockData,
|
||||
[]memoryexecutor.RowType{
|
||||
map[string]interface{}{"id": "123", "regexMatch": false},
|
||||
map[string]interface{}{"id": "456", "regexMatch": false},
|
||||
map[string]interface{}{"id": "789", "regexMatch": true},
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("Should execute function INDEX_OF()", func(t *testing.T) {
|
||||
testQueryExecute(
|
||||
t,
|
||||
|
||||
Reference in New Issue
Block a user