Implement IS_DEFINED function

This commit is contained in:
Pijus Kamandulis 2024-02-19 00:08:51 +02:00
parent c17509df38
commit f4dd150bc8
8 changed files with 634 additions and 402 deletions

View File

@ -81,6 +81,7 @@ type FunctionCallType string
const ( const (
FunctionCallStringEquals FunctionCallType = "StringEquals" FunctionCallStringEquals FunctionCallType = "StringEquals"
FunctionCallIsDefined FunctionCallType = "IsDefined"
) )
type FunctionCall struct { type FunctionCall struct {

File diff suppressed because it is too large Load Diff

View File

@ -15,6 +15,8 @@ func makeSelectStmt(columns, table, whereClause interface{}, count interface{},
selectStmt.Filters = filters selectStmt.Filters = filters
} else if filters, ok := whereClause.(parsers.Constant); ok { } else if filters, ok := whereClause.(parsers.Constant); ok {
selectStmt.Filters = filters selectStmt.Filters = filters
} else if filters, ok := whereClause.(parsers.SelectItem); ok {
selectStmt.Filters = filters
} }
if n, ok := count.(int); ok { if n, ok := count.(int); ok {
@ -186,7 +188,7 @@ SelectProperty <- name:Identifier path:(DotFieldAccess / ArrayFieldAccess)* {
return makeSelectItem(name, path, parsers.SelectItemTypeField) return makeSelectItem(name, path, parsers.SelectItemTypeField)
} }
SelectItem <- selectItem:(Literal / StringEqualsExpression / SelectArray / SelectObject / SelectProperty) asClause:AsClause? { SelectItem <- selectItem:(Literal / FunctionCall / 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:
@ -240,6 +242,7 @@ ComparisonExpression <- "(" ws ex:OrExpression ws ")" { return ex, nil }
/ left:SelectItem ws op:ComparisonOperator ws right:SelectItem { / left:SelectItem ws op:ComparisonOperator ws right:SelectItem {
return parsers.ComparisonExpression{Left:left,Right:right,Operation:string(op.([]uint8))}, nil return parsers.ComparisonExpression{Left:left,Right:right,Operation:string(op.([]uint8))}, nil
} / ex:BooleanLiteral { return ex, nil } } / ex:BooleanLiteral { return ex, nil }
/ ex:SelectItem { return ex, nil }
OrderByClause <- OrderBy ws ex1:OrderExpression others:(ws "," ws ex:OrderExpression { return ex, nil })* { OrderByClause <- OrderBy ws ex1:OrderExpression others:(ws "," ws ex:OrderExpression { return ex, nil })* {
return makeOrderByClause(ex1, others) return makeOrderByClause(ex1, others)
@ -303,10 +306,16 @@ BooleanLiteral <- ("true"i / "false"i) {
return parsers.Constant{Type: parsers.ConstantTypeBoolean, Value: boolValue}, nil return parsers.Constant{Type: parsers.ConstantTypeBoolean, Value: boolValue}, nil
} }
FunctionCall <- StringEqualsExpression / IsDefined
StringEqualsExpression <- StringEquals ws "(" ws ex1:SelectItem ws "," ws ex2:SelectItem ws ignoreCase:("," ws boolean:SelectItem { return boolean, 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 return parsers.FunctionCall{Type: parsers.FunctionCallStringEquals, Arguments: []interface{}{ex1, ex2, ignoreCase}}, nil
} }
IsDefined <- "IS_DEFINED"i ws "(" ws ex:SelectItem ws ")" {
return parsers.FunctionCall{Type: parsers.FunctionCallIsDefined, Arguments: []interface{}{ex}}, nil
}
Integer <- [0-9]+ { Integer <- [0-9]+ {
return strconv.Atoi(string(c.text)) return strconv.Atoi(string(c.text))
} }

View File

@ -8,7 +8,7 @@ import (
func Test_Execute_StringFunctions(t *testing.T) { func Test_Execute_StringFunctions(t *testing.T) {
t.Run("Should execute function STRINGEQUALS(ex1, ex2, ignoreCase)", func(t *testing.T) { t.Run("Should parse function STRINGEQUALS(ex1, ex2, ignoreCase)", func(t *testing.T) {
testQueryParse( testQueryParse(
t, t,
`SELECT STRINGEQUALS(c.id, "123", true) FROM c`, `SELECT STRINGEQUALS(c.id, "123", true) FROM c`,
@ -46,7 +46,7 @@ func Test_Execute_StringFunctions(t *testing.T) {
) )
}) })
t.Run("Should execute function STRINGEQUALS(ex1, ex2)", func(t *testing.T) { t.Run("Should parse function STRINGEQUALS(ex1, ex2)", func(t *testing.T) {
testQueryParse( testQueryParse(
t, t,
`SELECT STRINGEQUALS(c.id, "123") FROM c`, `SELECT STRINGEQUALS(c.id, "123") FROM c`,

View File

@ -0,0 +1,46 @@
package nosql_test
import (
"testing"
"github.com/pikami/cosmium/parsers"
)
func Test_Execute_TypeCheckingFunctions(t *testing.T) {
t.Run("Should parse function IS_DEFINED", func(t *testing.T) {
testQueryParse(
t,
`SELECT IS_DEFINED(c.id) FROM c WHERE IS_DEFINED(c.pk)`,
parsers.SelectStmt{
SelectItems: []parsers.SelectItem{
{
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: parsers.FunctionCallIsDefined,
Arguments: []interface{}{
parsers.SelectItem{
Path: []string{"c", "id"},
Type: parsers.SelectItemTypeField,
},
},
},
},
},
Table: parsers.Table{Value: "c"},
Filters: parsers.SelectItem{
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: parsers.FunctionCallIsDefined,
Arguments: []interface{}{
parsers.SelectItem{
Path: []string{"c", "pk"},
Type: parsers.SelectItemTypeField,
},
},
},
},
},
)
})
}

View File

@ -2,6 +2,7 @@ package memoryexecutor
import ( import (
"fmt" "fmt"
"reflect"
"sort" "sort"
"strings" "strings"
@ -122,6 +123,11 @@ func evaluateFilters(expr ExpressionType, queryParameters map[string]interface{}
return value return value
} }
return false return false
case parsers.SelectItem:
resolvedValue := getFieldValue(typedValue, queryParameters, row)
if value, ok := resolvedValue.(bool); ok {
return value
}
} }
return false return false
} }
@ -172,6 +178,8 @@ func getFieldValue(field parsers.SelectItem, queryParameters map[string]interfac
switch typedValue.Type { switch typedValue.Type {
case parsers.FunctionCallStringEquals: case parsers.FunctionCallStringEquals:
return strings_StringEquals(typedValue.Arguments, queryParameters, row) return strings_StringEquals(typedValue.Arguments, queryParameters, row)
case parsers.FunctionCallIsDefined:
return typeChecking_IsDefined(typedValue.Arguments, queryParameters, row)
} }
} }
@ -224,6 +232,10 @@ func orderBy(orderBy []parsers.OrderExpression, queryParameters map[string]inter
} }
func compareValues(val1, val2 interface{}) int { func compareValues(val1, val2 interface{}) int {
if reflect.TypeOf(val1) != reflect.TypeOf(val2) {
return 1
}
switch val1 := val1.(type) { switch val1 := val1.(type) {
case int: case int:
val2 := val2.(int) val2 := val2.(int)
@ -255,6 +267,9 @@ func compareValues(val1, val2 interface{}) int {
} }
// TODO: Add more types // TODO: Add more types
default: default:
return 0 if reflect.DeepEqual(val1, val2) {
return 0
}
return 1
} }
} }

View File

@ -0,0 +1,12 @@
package memoryexecutor
import (
"github.com/pikami/cosmium/parsers"
)
func typeChecking_IsDefined(arguments []interface{}, queryParameters map[string]interface{}, row RowType) bool {
exItem := arguments[0].(parsers.SelectItem)
ex := getFieldValue(exItem, queryParameters, row)
return ex != nil
}

View File

@ -0,0 +1,47 @@
package memoryexecutor_test
import (
"testing"
"github.com/pikami/cosmium/parsers"
memoryexecutor "github.com/pikami/cosmium/query_executors/memory_executor"
)
func Test_Execute_TypeCheckingFunctions(t *testing.T) {
mockData := []memoryexecutor.RowType{
map[string]interface{}{"id": "123", "pk": "aaa"},
map[string]interface{}{"id": "456"},
map[string]interface{}{"id": "789", "pk": ""},
}
t.Run("Should execute function IS_DEFINED(path)", func(t *testing.T) {
testQueryExecute(
t,
parsers.SelectStmt{
SelectItems: []parsers.SelectItem{
{Path: []string{"c", "id"}},
{
Alias: "defined",
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: parsers.FunctionCallIsDefined,
Arguments: []interface{}{
parsers.SelectItem{
Path: []string{"c", "pk"},
Type: parsers.SelectItemTypeField,
},
},
},
},
},
Table: parsers.Table{Value: "c"},
},
mockData,
[]memoryexecutor.RowType{
map[string]interface{}{"id": "123", "defined": true},
map[string]interface{}{"id": "456", "defined": false},
map[string]interface{}{"id": "789", "defined": true},
},
)
})
}