Added support for 'ARRAY_CONTAINS', 'ARRAY_CONTAINS_ANY' and 'ARRAY_CONTAINS_ALL' functions

This commit is contained in:
Pijus Kamandulis
2024-12-26 20:27:59 +02:00
parent f5b8453995
commit 8e3db3e44d
9 changed files with 2025 additions and 1118 deletions

View File

@@ -16,6 +16,86 @@ func (r rowContext) array_Concat(arguments []interface{}) []interface{} {
return result
}
func (r rowContext) array_Contains(arguments []interface{}) bool {
array := r.parseArray(arguments[0])
if array == nil {
return false
}
exprToSearch := r.resolveSelectItem(arguments[1].(parsers.SelectItem))
partialSearch := false
if len(arguments) > 2 {
boolExpr := r.resolveSelectItem(arguments[2].(parsers.SelectItem))
if boolValue, ok := boolExpr.(bool); ok {
partialSearch = boolValue
} else {
logger.Error("array_Contains - got parameters of wrong type")
return false
}
}
for _, item := range array {
if partialSearch {
if r.partialMatch(item, exprToSearch) {
return true
}
} else {
if reflect.DeepEqual(item, exprToSearch) {
return true
}
}
}
return false
}
func (r rowContext) array_Contains_Any(arguments []interface{}) bool {
array := r.parseArray(arguments[0])
if array == nil {
return false
}
valueSelectItems := arguments[1:]
for _, valueSelectItem := range valueSelectItems {
value := r.resolveSelectItem(valueSelectItem.(parsers.SelectItem))
for _, item := range array {
if reflect.DeepEqual(item, value) {
return true
}
}
}
return false
}
func (r rowContext) array_Contains_All(arguments []interface{}) bool {
array := r.parseArray(arguments[0])
if array == nil {
return false
}
valueSelectItems := arguments[1:]
for _, valueSelectItem := range valueSelectItems {
value := r.resolveSelectItem(valueSelectItem.(parsers.SelectItem))
found := false
for _, item := range array {
if reflect.DeepEqual(item, value) {
found = true
break
}
}
if !found {
return false
}
}
return true
}
func (r rowContext) array_Length(arguments []interface{}) int {
array := r.parseArray(arguments[0])
if array == nil {
@@ -129,3 +209,21 @@ func (r rowContext) parseArray(argument interface{}) []interface{} {
return result
}
func (r rowContext) partialMatch(item interface{}, exprToSearch interface{}) bool {
itemValue := reflect.ValueOf(item)
exprValue := reflect.ValueOf(exprToSearch)
if itemValue.Kind() != reflect.Map || exprValue.Kind() != reflect.Map {
logger.Error("partialMatch got parameters of wrong type")
return false
}
for _, key := range exprValue.MapKeys() {
if itemValue.MapIndex(key).Interface() != exprValue.MapIndex(key).Interface() {
return false
}
}
return true
}

View File

@@ -5,6 +5,7 @@ import (
"github.com/pikami/cosmium/parsers"
memoryexecutor "github.com/pikami/cosmium/query_executors/memory_executor"
testutils "github.com/pikami/cosmium/test_utils"
)
func Test_Execute_ArrayFunctions(t *testing.T) {
@@ -52,6 +53,286 @@ func Test_Execute_ArrayFunctions(t *testing.T) {
)
})
t.Run("Should execute function ARRAY_CONTAINS()", func(t *testing.T) {
testQueryExecute(
t,
parsers.SelectStmt{
Parameters: map[string]interface{}{
"@categories": []interface{}{"coats", "jackets", "sweatshirts"},
"@objectArray": []interface{}{map[string]interface{}{"category": "shirts", "color": "blue"}},
"@fullMatchObject": map[string]interface{}{"category": "shirts", "color": "blue"},
"@partialMatchObject": map[string]interface{}{"category": "shirts"},
"@missingPartialMatchObject": map[string]interface{}{"category": "shorts", "color": "blue"},
},
SelectItems: []parsers.SelectItem{
{
Alias: "ContainsItem",
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: parsers.FunctionCallArrayContains,
Arguments: []interface{}{
testutils.SelectItem_Constant_Parameter("@categories"),
testutils.SelectItem_Constant_String("coats"),
},
},
},
{
Alias: "MissingItem",
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: parsers.FunctionCallArrayContains,
Arguments: []interface{}{
testutils.SelectItem_Constant_Parameter("@categories"),
testutils.SelectItem_Constant_String("hoodies"),
},
},
},
{
Alias: "ContainsFullMatchObject",
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: parsers.FunctionCallArrayContains,
Arguments: []interface{}{
testutils.SelectItem_Constant_Parameter("@objectArray"),
testutils.SelectItem_Constant_Parameter("@fullMatchObject"),
},
},
},
{
Alias: "MissingFullMatchObject",
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: parsers.FunctionCallArrayContains,
Arguments: []interface{}{
testutils.SelectItem_Constant_Parameter("@objectArray"),
testutils.SelectItem_Constant_Parameter("@partialMatchObject"),
},
},
},
{
Alias: "ContainsPartialMatchObject",
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: parsers.FunctionCallArrayContains,
Arguments: []interface{}{
testutils.SelectItem_Constant_Parameter("@objectArray"),
testutils.SelectItem_Constant_Parameter("@partialMatchObject"),
testutils.SelectItem_Constant_Bool(true),
},
},
},
{
Alias: "MissingPartialMatchObject",
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: parsers.FunctionCallArrayContains,
Arguments: []interface{}{
testutils.SelectItem_Constant_Parameter("@objectArray"),
testutils.SelectItem_Constant_Parameter("@missingPartialMatchObject"),
testutils.SelectItem_Constant_Bool(true),
},
},
},
},
},
[]memoryexecutor.RowType{map[string]interface{}{"id": "123"}},
[]memoryexecutor.RowType{
map[string]interface{}{
"ContainsItem": true,
"MissingItem": false,
"ContainsFullMatchObject": true,
"MissingFullMatchObject": false,
"ContainsPartialMatchObject": true,
"MissingPartialMatchObject": false,
},
},
)
})
t.Run("Should execute function ARRAY_CONTAINS_ANY()", func(t *testing.T) {
testQueryExecute(
t,
parsers.SelectStmt{
Parameters: map[string]interface{}{
"@mixedArray": []interface{}{1, true, "3", []int{1, 2, 3}},
"@numbers": []interface{}{1, 2, 3, 4},
"@emptyArray": []interface{}{},
"@arr123": []interface{}{1, 2, 3},
},
SelectItems: []parsers.SelectItem{
{
Alias: "matchesEntireArray",
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: parsers.FunctionCallArrayContainsAny,
Arguments: []interface{}{
testutils.SelectItem_Constant_Parameter("@mixedArray"),
testutils.SelectItem_Constant_Int(1),
testutils.SelectItem_Constant_Bool(true),
testutils.SelectItem_Constant_String("3"),
testutils.SelectItem_Constant_Parameter("@arr123"),
},
},
},
{
Alias: "matchesSomeValues",
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: parsers.FunctionCallArrayContainsAny,
Arguments: []interface{}{
testutils.SelectItem_Constant_Parameter("@numbers"),
testutils.SelectItem_Constant_Int(2),
testutils.SelectItem_Constant_Int(3),
testutils.SelectItem_Constant_Int(4),
testutils.SelectItem_Constant_Int(5),
},
},
},
{
Alias: "matchSingleValue",
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: parsers.FunctionCallArrayContainsAny,
Arguments: []interface{}{
testutils.SelectItem_Constant_Parameter("@numbers"),
testutils.SelectItem_Constant_Int(1),
},
},
},
{
Alias: "noMatches",
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: parsers.FunctionCallArrayContainsAny,
Arguments: []interface{}{
testutils.SelectItem_Constant_Parameter("@numbers"),
testutils.SelectItem_Constant_Int(5),
testutils.SelectItem_Constant_Int(6),
testutils.SelectItem_Constant_Int(7),
testutils.SelectItem_Constant_Int(8),
},
},
},
{
Alias: "emptyArray",
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: parsers.FunctionCallArrayContainsAny,
Arguments: []interface{}{
testutils.SelectItem_Constant_Parameter("@emptyArray"),
testutils.SelectItem_Constant_Int(1),
testutils.SelectItem_Constant_Int(2),
testutils.SelectItem_Constant_Int(3),
},
},
},
},
},
[]memoryexecutor.RowType{map[string]interface{}{"id": "123"}},
[]memoryexecutor.RowType{
map[string]interface{}{
"matchesEntireArray": true,
"matchesSomeValues": true,
"matchSingleValue": true,
"noMatches": false,
"emptyArray": false,
},
},
)
})
t.Run("Should execute function ARRAY_CONTAINS_ALL()", func(t *testing.T) {
testQueryExecute(
t,
parsers.SelectStmt{
Parameters: map[string]interface{}{
"@mixedArray": []interface{}{1, true, "3", []interface{}{1, 2, 3}},
"@numbers": []interface{}{1, 2, 3, 4},
"@emptyArray": []interface{}{},
"@arr123": []interface{}{1, 2, 3},
},
SelectItems: []parsers.SelectItem{
{
Alias: "matchesEntireArray",
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: parsers.FunctionCallArrayContainsAll,
Arguments: []interface{}{
testutils.SelectItem_Constant_Parameter("@mixedArray"),
testutils.SelectItem_Constant_Int(1),
testutils.SelectItem_Constant_Bool(true),
testutils.SelectItem_Constant_String("3"),
testutils.SelectItem_Constant_Parameter("@arr123"),
},
},
},
{
Alias: "matchesSomeValues",
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: parsers.FunctionCallArrayContainsAll,
Arguments: []interface{}{
testutils.SelectItem_Constant_Parameter("@numbers"),
testutils.SelectItem_Constant_Int(2),
testutils.SelectItem_Constant_Int(3),
testutils.SelectItem_Constant_Int(4),
testutils.SelectItem_Constant_Int(5),
},
},
},
{
Alias: "matchSingleValue",
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: parsers.FunctionCallArrayContainsAll,
Arguments: []interface{}{
testutils.SelectItem_Constant_Parameter("@numbers"),
testutils.SelectItem_Constant_Int(1),
},
},
},
{
Alias: "noMatches",
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: parsers.FunctionCallArrayContainsAll,
Arguments: []interface{}{
testutils.SelectItem_Constant_Parameter("@numbers"),
testutils.SelectItem_Constant_Int(5),
testutils.SelectItem_Constant_Int(6),
testutils.SelectItem_Constant_Int(7),
testutils.SelectItem_Constant_Int(8),
},
},
},
{
Alias: "emptyArray",
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: parsers.FunctionCallArrayContainsAll,
Arguments: []interface{}{
testutils.SelectItem_Constant_Parameter("@emptyArray"),
testutils.SelectItem_Constant_Int(1),
testutils.SelectItem_Constant_Int(2),
testutils.SelectItem_Constant_Int(3),
},
},
},
},
},
[]memoryexecutor.RowType{map[string]interface{}{"id": "123"}},
[]memoryexecutor.RowType{
map[string]interface{}{
"matchesEntireArray": true,
"matchesSomeValues": false,
"matchSingleValue": true,
"noMatches": false,
"emptyArray": false,
},
},
)
})
t.Run("Should execute function ARRAY_LENGTH()", func(t *testing.T) {
testQueryExecute(
t,
@@ -105,20 +386,8 @@ func Test_Execute_ArrayFunctions(t *testing.T) {
Path: []string{"c", "arr2"},
Type: parsers.SelectItemTypeField,
},
parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeInteger,
Value: 1,
},
},
parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeInteger,
Value: 2,
},
},
testutils.SelectItem_Constant_Int(1),
testutils.SelectItem_Constant_Int(2),
},
},
},

View File

@@ -470,6 +470,12 @@ func (r rowContext) selectItem_SelectItemTypeFunctionCall(functionCall parsers.F
case parsers.FunctionCallArrayConcat:
return r.array_Concat(functionCall.Arguments)
case parsers.FunctionCallArrayContains:
return r.array_Contains(functionCall.Arguments)
case parsers.FunctionCallArrayContainsAny:
return r.array_Contains_Any(functionCall.Arguments)
case parsers.FunctionCallArrayContainsAll:
return r.array_Contains_All(functionCall.Arguments)
case parsers.FunctionCallArrayLength:
return r.array_Length(functionCall.Arguments)
case parsers.FunctionCallArraySlice: