Implement STRINGEQUALS

This commit is contained in:
Pijus Kamandulis 2024-02-18 22:37:09 +02:00
parent 2702156cb3
commit c17509df38
8 changed files with 799 additions and 501 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
dist/ dist/
ignored/

View File

@ -20,6 +20,7 @@ const (
SelectItemTypeObject SelectItemTypeObject
SelectItemTypeArray SelectItemTypeArray
SelectItemTypeConstant SelectItemTypeConstant
SelectItemTypeFunctionCall
) )
type SelectItem struct { type SelectItem struct {
@ -75,3 +76,14 @@ type OrderExpression struct {
SelectItem SelectItem SelectItem SelectItem
Direction OrderDirection Direction OrderDirection
} }
type FunctionCallType string
const (
FunctionCallStringEquals FunctionCallType = "StringEquals"
)
type FunctionCall struct {
Arguments []interface{}
Type FunctionCallType
}

File diff suppressed because it is too large Load Diff

View File

@ -158,7 +158,7 @@ ColumnList <- column:SelectItem other_columns:(ws "," ws coll:SelectItem {return
return makeColumnList(column, other_columns) return makeColumnList(column, other_columns)
} }
SelectValueSpec <- "VALUE" ws column:SelectItem { SelectValueSpec <- "VALUE"i ws column:SelectItem {
selectItem := column.(parsers.SelectItem) selectItem := column.(parsers.SelectItem)
selectItem.IsTopLevel = true selectItem.IsTopLevel = true
return makeColumnList(selectItem, make([]interface{}, 0)) return makeColumnList(selectItem, make([]interface{}, 0))
@ -186,7 +186,7 @@ SelectProperty <- name:Identifier path:(DotFieldAccess / ArrayFieldAccess)* {
return makeSelectItem(name, path, parsers.SelectItemTypeField) return makeSelectItem(name, path, parsers.SelectItemTypeField)
} }
SelectItem <- selectItem:(Literal / SelectArray / SelectObject / SelectProperty) asClause:AsClause? { SelectItem <- selectItem:(Literal / StringEqualsExpression / SelectArray / SelectObject / SelectProperty) asClause:AsClause? {
var itemResult parsers.SelectItem var itemResult parsers.SelectItem
switch typedValue := selectItem.(type) { switch typedValue := selectItem.(type) {
case parsers.SelectItem: case parsers.SelectItem:
@ -196,6 +196,11 @@ SelectItem <- selectItem:(Literal / SelectArray / SelectObject / SelectProperty)
Type: parsers.SelectItemTypeConstant, Type: parsers.SelectItemTypeConstant,
Value: typedValue, Value: typedValue,
} }
case parsers.FunctionCall:
itemResult = parsers.SelectItem{
Type: parsers.SelectItemTypeFunctionCall,
Value: typedValue,
}
} }
if aliasValue, ok := asClause.(string); ok { if aliasValue, ok := asClause.(string); ok {
@ -244,34 +249,36 @@ OrderExpression <- field:SelectProperty ws order:OrderDirection? {
return makeOrderExpression(field, order) return makeOrderExpression(field, order)
} }
OrderDirection <- ("ASC" / "asc" / "DESC" / "desc") { OrderDirection <- ("ASC"i / "DESC"i) {
switch string(c.text) { if strings.EqualFold(string(c.text), "DESC") {
case "DESC", "desc":
return parsers.OrderDirectionDesc, nil return parsers.OrderDirectionDesc, nil
} }
return parsers.OrderDirectionAsc, nil return parsers.OrderDirectionAsc, nil
} }
Select <- ("select" / "SELECT") Select <- "SELECT"i
Top <- ("top" / "TOP") Top <- "TOP"i
As <- ("as" / "AS") As <- "AS"i
From <- ("from" / "FROM") From <- "FROM"i
Where <- ("where" / "WHERE") Where <- "WHERE"i
And <- ("and" / "AND") And <- "AND"i
Or <- ("or" / "OR") Or <- "OR"i
OrderBy <- ("order" / "ORDER") ws ("by" / "BY") OrderBy <- "ORDER"i ws "BY"i
ComparisonOperator <- "=" / "!=" / "<" / "<=" / ">" / ">=" { ComparisonOperator <- "=" / "!=" / "<" / "<=" / ">" / ">=" {
return string(c.text), nil return string(c.text), nil
} }
StringEquals <- "STRINGEQUALS"i
Literal <- FloatLiteral / IntegerLiteral / StringLiteral / BooleanLiteral / ParameterConstant / NullConstant Literal <- FloatLiteral / IntegerLiteral / StringLiteral / BooleanLiteral / ParameterConstant / NullConstant
ParameterConstant <- "@" Identifier { ParameterConstant <- "@" Identifier {
@ -291,11 +298,15 @@ FloatLiteral <- [0-9]+"."[0-9]+ {
floatValue, _ := strconv.ParseFloat(string(c.text), 64) floatValue, _ := strconv.ParseFloat(string(c.text), 64)
return parsers.Constant{Type: parsers.ConstantTypeFloat, Value: floatValue}, nil return parsers.Constant{Type: parsers.ConstantTypeFloat, Value: floatValue}, nil
} }
BooleanLiteral <- ("true" / "false") { BooleanLiteral <- ("true"i / "false"i) {
boolValue, _ := strconv.ParseBool(string(c.text)) boolValue, _ := strconv.ParseBool(string(c.text))
return parsers.Constant{Type: parsers.ConstantTypeBoolean, Value: boolValue}, nil return parsers.Constant{Type: parsers.ConstantTypeBoolean, Value: boolValue}, nil
} }
StringEqualsExpression <- StringEquals ws "(" ws ex1:SelectItem ws "," ws ex2:SelectItem ws ignoreCase:("," ws boolean:SelectItem { return boolean, nil })? ")" {
return parsers.FunctionCall{Type: parsers.FunctionCallStringEquals, Arguments: []interface{}{ex1, ex2, ignoreCase}}, nil
}
Integer <- [0-9]+ { Integer <- [0-9]+ {
return strconv.Atoi(string(c.text)) return strconv.Atoi(string(c.text))
} }

View File

@ -0,0 +1,80 @@
package nosql_test
import (
"testing"
"github.com/pikami/cosmium/parsers"
)
func Test_Execute_StringFunctions(t *testing.T) {
t.Run("Should execute function STRINGEQUALS(ex1, ex2, ignoreCase)", func(t *testing.T) {
testQueryParse(
t,
`SELECT STRINGEQUALS(c.id, "123", true) FROM c`,
parsers.SelectStmt{
SelectItems: []parsers.SelectItem{
{
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: parsers.FunctionCallStringEquals,
Arguments: []interface{}{
parsers.SelectItem{
Path: []string{"c", "id"},
Type: parsers.SelectItemTypeField,
},
parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeString,
Value: "123",
},
},
parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeBoolean,
Value: true,
},
},
},
},
},
},
Table: parsers.Table{Value: "c"},
},
)
})
t.Run("Should execute function STRINGEQUALS(ex1, ex2)", func(t *testing.T) {
testQueryParse(
t,
`SELECT STRINGEQUALS(c.id, "123") FROM c`,
parsers.SelectStmt{
SelectItems: []parsers.SelectItem{
{
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: parsers.FunctionCallStringEquals,
Arguments: []interface{}{
parsers.SelectItem{
Path: []string{"c", "id"},
Type: parsers.SelectItemTypeField,
},
parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeString,
Value: "123",
},
},
nil,
},
},
},
},
Table: parsers.Table{Value: "c"},
},
)
})
}

View File

@ -161,6 +161,20 @@ func getFieldValue(field parsers.SelectItem, queryParameters map[string]interfac
return typedValue.Value return typedValue.Value
} }
if field.Type == parsers.SelectItemTypeFunctionCall {
var typedValue parsers.FunctionCall
var ok bool
if typedValue, ok = field.Value.(parsers.FunctionCall); !ok {
// TODO: Handle error
fmt.Println("parsers.Constant has incorrect Value type")
}
switch typedValue.Type {
case parsers.FunctionCallStringEquals:
return strings_StringEquals(typedValue.Arguments, queryParameters, row)
}
}
value := row value := row
if len(field.Path) > 1 { if len(field.Path) > 1 {
for _, pathSegment := range field.Path[1:] { for _, pathSegment := range field.Path[1:] {

View File

@ -0,0 +1,106 @@
package memoryexecutor_test
import (
"testing"
"github.com/pikami/cosmium/parsers"
memoryexecutor "github.com/pikami/cosmium/query_executors/memory_executor"
)
func Test_Execute_StringFunctions(t *testing.T) {
mockData := []memoryexecutor.RowType{
map[string]interface{}{"id": "123", "pk": "aaa"},
map[string]interface{}{"id": "456", "pk": "bbb"},
map[string]interface{}{"id": "789", "pk": "AAA"},
}
t.Run("Should execute function STRINGEQUALS(ex1, ex2, ignoreCase)", func(t *testing.T) {
testQueryExecute(
t,
parsers.SelectStmt{
SelectItems: []parsers.SelectItem{
{
Path: []string{"c", "id"},
Type: parsers.SelectItemTypeField,
},
{
Alias: "stringEquals",
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: parsers.FunctionCallStringEquals,
Arguments: []interface{}{
parsers.SelectItem{
Path: []string{"c", "pk"},
Type: parsers.SelectItemTypeField,
},
parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeString,
Value: "aaa",
},
},
parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeBoolean,
Value: true,
},
},
},
},
},
},
Table: parsers.Table{Value: "c"},
},
mockData,
[]memoryexecutor.RowType{
map[string]interface{}{"id": "123", "stringEquals": true},
map[string]interface{}{"id": "456", "stringEquals": false},
map[string]interface{}{"id": "789", "stringEquals": true},
},
)
})
t.Run("Should execute function STRINGEQUALS(ex1, ex2)", func(t *testing.T) {
testQueryExecute(
t,
parsers.SelectStmt{
SelectItems: []parsers.SelectItem{
{
Path: []string{"c", "id"},
Type: parsers.SelectItemTypeField,
},
{
Alias: "stringEquals",
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: parsers.FunctionCallStringEquals,
Arguments: []interface{}{
parsers.SelectItem{
Path: []string{"c", "pk"},
Type: parsers.SelectItemTypeField,
},
parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeString,
Value: "aaa",
},
},
nil,
},
},
},
},
Table: parsers.Table{Value: "c"},
},
mockData,
[]memoryexecutor.RowType{
map[string]interface{}{"id": "123", "stringEquals": true},
map[string]interface{}{"id": "456", "stringEquals": false},
map[string]interface{}{"id": "789", "stringEquals": false},
},
)
})
}

View File

@ -0,0 +1,41 @@
package memoryexecutor
import (
"fmt"
"strings"
"github.com/pikami/cosmium/parsers"
)
func strings_StringEquals(arguments []interface{}, queryParameters map[string]interface{}, row RowType) bool {
ignoreCase := false
if len(arguments) > 2 && arguments[2] != nil {
ignoreCaseItem := arguments[2].(parsers.SelectItem)
if value, ok := getFieldValue(ignoreCaseItem, queryParameters, row).(bool); ok {
ignoreCase = value
}
}
ex1Item := arguments[0].(parsers.SelectItem)
ex2Item := arguments[1].(parsers.SelectItem)
ex1 := getFieldValue(ex1Item, queryParameters, row)
ex2 := getFieldValue(ex2Item, queryParameters, row)
var ok bool
var str1 string
var str2 string
if str1, ok = ex1.(string); !ok {
fmt.Println("StringEquals got parameters of wrong type")
}
if str2, ok = ex2.(string); !ok {
fmt.Println("StringEquals got parameters of wrong type")
}
if ignoreCase {
return strings.EqualFold(str1, str2)
}
return str1 == str2
}