Implement ARRAY_CONCAT, ARRAY_LENGTH, ARRAY_SLICE, SetIntersect, SetUnion functions

This commit is contained in:
Pijus Kamandulis 2024-02-25 00:25:51 +02:00
parent b29608e4c8
commit 1c5e5ce85d
7 changed files with 1553 additions and 510 deletions

View File

@ -111,6 +111,12 @@ const (
FunctionCallIsPrimitive FunctionCallType = "IsPrimitive" FunctionCallIsPrimitive FunctionCallType = "IsPrimitive"
FunctionCallIsString FunctionCallType = "IsString" FunctionCallIsString FunctionCallType = "IsString"
FunctionCallArrayConcat FunctionCallType = "ArrayConcat"
FunctionCallArrayLength FunctionCallType = "ArrayLength"
FunctionCallArraySlice FunctionCallType = "ArraySlice"
FunctionCallSetIntersect FunctionCallType = "SetIntersect"
FunctionCallSetUnion FunctionCallType = "SetUnion"
FunctionCallIn FunctionCallType = "In" FunctionCallIn FunctionCallType = "In"
) )

File diff suppressed because it is too large Load Diff

View File

@ -312,19 +312,9 @@ BooleanLiteral <- ("true"i / "false"i) {
FunctionCall <- StringFunctions FunctionCall <- StringFunctions
/ TypeCheckingFunctions / TypeCheckingFunctions
/ ArrayFunctions
/ InFunction / InFunction
TypeCheckingFunctions <- IsDefined
/ IsArray
/ IsBool
/ IsFiniteNumber
/ IsInteger
/ IsNull
/ IsNumber
/ IsObject
/ IsPrimitive
/ IsString
StringFunctions <- StringEqualsExpression StringFunctions <- StringEqualsExpression
/ ToStringExpression / ToStringExpression
/ ConcatExpression / ConcatExpression
@ -342,6 +332,23 @@ StringFunctions <- StringEqualsExpression
/ SubstringExpression / SubstringExpression
/ TrimExpression / TrimExpression
TypeCheckingFunctions <- IsDefined
/ IsArray
/ IsBool
/ IsFiniteNumber
/ IsInteger
/ IsNull
/ IsNumber
/ IsObject
/ IsPrimitive
/ IsString
ArrayFunctions <- ArrayConcatExpression
/ ArrayLengthExpression
/ ArraySliceExpression
/ SetIntersectExpression
/ SetUnionExpression
UpperExpression <- "UPPER"i ws "(" ex:SelectItem ")" { UpperExpression <- "UPPER"i ws "(" ex:SelectItem ")" {
return createFunctionCall(parsers.FunctionCallUpper, []interface{}{ex}) return createFunctionCall(parsers.FunctionCallUpper, []interface{}{ex})
} }
@ -465,6 +472,26 @@ IsString <- "IS_STRING"i ws "(" ws ex:SelectItem ws ")" {
return createFunctionCall(parsers.FunctionCallIsString, []interface{}{ex}) return createFunctionCall(parsers.FunctionCallIsString, []interface{}{ex})
} }
ArrayConcatExpression <- "ARRAY_CONCAT"i ws "(" ws arrays:SelectItem others:(ws "," ws ex:SelectItem { return ex, nil })+ ws ")" {
return createFunctionCall(parsers.FunctionCallArrayConcat, append([]interface{}{arrays}, others.([]interface{})...))
}
ArrayLengthExpression <- "ARRAY_LENGTH"i ws "(" ws array:SelectItem ws ")" {
return createFunctionCall(parsers.FunctionCallArrayLength, []interface{}{array})
}
ArraySliceExpression <- "ARRAY_SLICE"i ws "(" ws array:SelectItem ws "," ws start:SelectItem length:(ws "," ws ex:SelectItem { return ex, nil })? ws ")" {
return createFunctionCall(parsers.FunctionCallArraySlice, []interface{}{array, start, length})
}
SetIntersectExpression <- "SetIntersect"i ws "(" ws set1:SelectItem ws "," ws set2:SelectItem ws ")" {
return createFunctionCall(parsers.FunctionCallSetIntersect, []interface{}{set1, set2})
}
SetUnionExpression <- "SetUnion"i ws "(" ws set1:SelectItem ws "," ws set2:SelectItem ws ")" {
return createFunctionCall(parsers.FunctionCallSetUnion, []interface{}{set1, set2})
}
InFunction <- ex1:SelectProperty ws "IN"i ws "(" ws ex2:SelectItem others:(ws "," ws ex:SelectItem { return ex, nil })* ws ")" { InFunction <- ex1:SelectProperty ws "IN"i ws "(" ws ex2:SelectItem others:(ws "," ws ex:SelectItem { return ex, nil })* ws ")" {
arguments := append([]interface{}{ex1, ex2}, others.([]interface{})...) arguments := append([]interface{}{ex1, ex2}, others.([]interface{})...)
return parsers.FunctionCall{Type: parsers.FunctionCallIn, Arguments: arguments}, nil return parsers.FunctionCall{Type: parsers.FunctionCallIn, Arguments: arguments}, nil

View File

@ -0,0 +1,156 @@
package nosql_test
import (
"testing"
"github.com/pikami/cosmium/parsers"
)
func Test_Parse_ArrayFunctions(t *testing.T) {
t.Run("Should parse function ARRAY_CONCAT()", func(t *testing.T) {
testQueryParse(
t,
`SELECT ARRAY_CONCAT(c.a1, c.a2) FROM c`,
parsers.SelectStmt{
SelectItems: []parsers.SelectItem{
{
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: parsers.FunctionCallArrayConcat,
Arguments: []interface{}{
parsers.SelectItem{
Path: []string{"c", "a1"},
Type: parsers.SelectItemTypeField,
},
parsers.SelectItem{
Path: []string{"c", "a2"},
Type: parsers.SelectItemTypeField,
},
},
},
},
},
Table: parsers.Table{Value: "c"},
},
)
})
t.Run("Should parse function ARRAY_LENGTH()", func(t *testing.T) {
testQueryParse(
t,
`SELECT ARRAY_LENGTH(c.array) FROM c`,
parsers.SelectStmt{
SelectItems: []parsers.SelectItem{
{
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: parsers.FunctionCallArrayLength,
Arguments: []interface{}{
parsers.SelectItem{
Path: []string{"c", "array"},
Type: parsers.SelectItemTypeField,
},
},
},
},
},
Table: parsers.Table{Value: "c"},
},
)
})
t.Run("Should parse function ARRAY_SLICE()", func(t *testing.T) {
testQueryParse(
t,
`SELECT ARRAY_SLICE(c.array, 0, 2) FROM c`,
parsers.SelectStmt{
SelectItems: []parsers.SelectItem{
{
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: parsers.FunctionCallArraySlice,
Arguments: []interface{}{
parsers.SelectItem{
Path: []string{"c", "array"},
Type: parsers.SelectItemTypeField,
},
parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeInteger,
Value: 0,
},
},
parsers.SelectItem{
Type: parsers.SelectItemTypeConstant,
Value: parsers.Constant{
Type: parsers.ConstantTypeInteger,
Value: 2,
},
},
},
},
},
},
Table: parsers.Table{Value: "c"},
},
)
})
t.Run("Should parse function SetIntersect()", func(t *testing.T) {
testQueryParse(
t,
`SELECT SetIntersect(c.set1, c.set2) FROM c`,
parsers.SelectStmt{
SelectItems: []parsers.SelectItem{
{
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: parsers.FunctionCallSetIntersect,
Arguments: []interface{}{
parsers.SelectItem{
Path: []string{"c", "set1"},
Type: parsers.SelectItemTypeField,
},
parsers.SelectItem{
Path: []string{"c", "set2"},
Type: parsers.SelectItemTypeField,
},
},
},
},
},
Table: parsers.Table{Value: "c"},
},
)
})
t.Run("Should parse function SetUnion()", func(t *testing.T) {
testQueryParse(
t,
`SELECT SetUnion(c.set1, c.set2) FROM c`,
parsers.SelectStmt{
SelectItems: []parsers.SelectItem{
{
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: parsers.FunctionCallSetUnion,
Arguments: []interface{}{
parsers.SelectItem{
Path: []string{"c", "set1"},
Type: parsers.SelectItemTypeField,
},
parsers.SelectItem{
Path: []string{"c", "set2"},
Type: parsers.SelectItemTypeField,
},
},
},
},
},
Table: parsers.Table{Value: "c"},
},
)
})
}

View File

@ -0,0 +1,131 @@
package memoryexecutor
import (
"fmt"
"reflect"
"github.com/pikami/cosmium/parsers"
)
func array_Concat(arguments []interface{}, queryParameters map[string]interface{}, row RowType) []interface{} {
var result []interface{}
for _, arg := range arguments {
array := parseArray(arg, queryParameters, row)
result = append(result, array...)
}
return result
}
func array_Length(arguments []interface{}, queryParameters map[string]interface{}, row RowType) int {
array := parseArray(arguments[0], queryParameters, row)
if array == nil {
return 0
}
return len(array)
}
func array_Slice(arguments []interface{}, queryParameters map[string]interface{}, row RowType) []interface{} {
var ok bool
var start int
var length int
array := parseArray(arguments[0], queryParameters, row)
startEx := getFieldValue(arguments[1].(parsers.SelectItem), queryParameters, row)
if arguments[2] != nil {
lengthEx := getFieldValue(arguments[2].(parsers.SelectItem), queryParameters, row)
if length, ok = lengthEx.(int); !ok {
fmt.Println("array_Slice - got length parameters of wrong type")
return []interface{}{}
}
}
if start, ok = startEx.(int); !ok {
fmt.Println("array_Slice - got start parameters of wrong type")
return []interface{}{}
}
if start < 0 {
start = len(array) + start
}
if start < 0 {
start = 0
}
if array == nil || start >= len(array) {
return []interface{}{}
}
end := start + length
if end > len(array) {
end = len(array)
}
return array[start:end]
}
func set_Intersect(arguments []interface{}, queryParameters map[string]interface{}, row RowType) []interface{} {
set1 := parseArray(arguments[0], queryParameters, row)
set2 := parseArray(arguments[1], queryParameters, row)
intersection := make(map[interface{}]struct{})
if set1 == nil || set2 == nil {
return []interface{}{}
}
for _, item := range set1 {
intersection[item] = struct{}{}
}
var result []interface{}
for _, item := range set2 {
if _, exists := intersection[item]; exists {
result = append(result, item)
}
}
return result
}
func set_Union(arguments []interface{}, queryParameters map[string]interface{}, row RowType) []interface{} {
set1 := parseArray(arguments[0], queryParameters, row)
set2 := parseArray(arguments[1], queryParameters, row)
var result []interface{}
union := make(map[interface{}]struct{})
for _, item := range set1 {
if _, ok := union[item]; !ok {
union[item] = struct{}{}
result = append(result, item)
}
}
for _, item := range set2 {
if _, ok := union[item]; !ok {
union[item] = struct{}{}
result = append(result, item)
}
}
return result
}
func parseArray(argument interface{}, queryParameters map[string]interface{}, row RowType) []interface{} {
exItem := argument.(parsers.SelectItem)
ex := getFieldValue(exItem, queryParameters, row)
arrValue := reflect.ValueOf(ex)
if arrValue.Kind() != reflect.Slice {
fmt.Println("parseArray got parameters of wrong type")
return nil
}
result := make([]interface{}, arrValue.Len())
for i := 0; i < arrValue.Len(); i++ {
result[i] = arrValue.Index(i).Interface()
}
return result
}

View File

@ -0,0 +1,212 @@
package memoryexecutor_test
import (
"testing"
"github.com/pikami/cosmium/parsers"
memoryexecutor "github.com/pikami/cosmium/query_executors/memory_executor"
)
func Test_Execute_ArrayFunctions(t *testing.T) {
mockData := []memoryexecutor.RowType{
map[string]interface{}{"id": "123", "arr1": []int{1, 2, 3}, "arr2": []int{3, 4, 5}},
map[string]interface{}{"id": "456", "arr1": []int{4, 5, 6}, "arr2": []int{5, 6, 7, 8}},
map[string]interface{}{"id": "789", "arr1": []int{7, 8, 9}, "arr2": []int{7, 8, 9, 10, 11}},
}
t.Run("Should execute function CONCAT()", func(t *testing.T) {
testQueryExecute(
t,
parsers.SelectStmt{
SelectItems: []parsers.SelectItem{
{
Path: []string{"c", "id"},
Type: parsers.SelectItemTypeField,
},
{
Alias: "Concat",
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: parsers.FunctionCallArrayConcat,
Arguments: []interface{}{
parsers.SelectItem{
Path: []string{"c", "arr1"},
Type: parsers.SelectItemTypeField,
},
parsers.SelectItem{
Path: []string{"c", "arr2"},
Type: parsers.SelectItemTypeField,
},
},
},
},
},
Table: parsers.Table{Value: "c"},
},
mockData,
[]memoryexecutor.RowType{
map[string]interface{}{"id": "123", "Concat": []interface{}{1, 2, 3, 3, 4, 5}},
map[string]interface{}{"id": "456", "Concat": []interface{}{4, 5, 6, 5, 6, 7, 8}},
map[string]interface{}{"id": "789", "Concat": []interface{}{7, 8, 9, 7, 8, 9, 10, 11}},
},
)
})
t.Run("Should execute function ARRAY_LENGTH()", func(t *testing.T) {
testQueryExecute(
t,
parsers.SelectStmt{
SelectItems: []parsers.SelectItem{
{
Path: []string{"c", "id"},
Type: parsers.SelectItemTypeField,
},
{
Alias: "Length",
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: parsers.FunctionCallArrayLength,
Arguments: []interface{}{
parsers.SelectItem{
Path: []string{"c", "arr2"},
Type: parsers.SelectItemTypeField,
},
},
},
},
},
Table: parsers.Table{Value: "c"},
},
mockData,
[]memoryexecutor.RowType{
map[string]interface{}{"id": "123", "Length": 3},
map[string]interface{}{"id": "456", "Length": 4},
map[string]interface{}{"id": "789", "Length": 5},
},
)
})
t.Run("Should execute function ARRAY_SLICE()", func(t *testing.T) {
testQueryExecute(
t,
parsers.SelectStmt{
SelectItems: []parsers.SelectItem{
{
Path: []string{"c", "id"},
Type: parsers.SelectItemTypeField,
},
{
Alias: "Slice",
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: parsers.FunctionCallArraySlice,
Arguments: []interface{}{
parsers.SelectItem{
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,
},
},
},
},
},
},
Table: parsers.Table{Value: "c"},
},
mockData,
[]memoryexecutor.RowType{
map[string]interface{}{"id": "123", "Slice": []interface{}{4, 5}},
map[string]interface{}{"id": "456", "Slice": []interface{}{6, 7}},
map[string]interface{}{"id": "789", "Slice": []interface{}{8, 9}},
},
)
})
t.Run("Should execute function SET_INTERSECT()", func(t *testing.T) {
testQueryExecute(
t,
parsers.SelectStmt{
SelectItems: []parsers.SelectItem{
{
Path: []string{"c", "id"},
Type: parsers.SelectItemTypeField,
},
{
Alias: "Intersection",
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: parsers.FunctionCallSetIntersect,
Arguments: []interface{}{
parsers.SelectItem{
Path: []string{"c", "arr1"},
Type: parsers.SelectItemTypeField,
},
parsers.SelectItem{
Path: []string{"c", "arr2"},
Type: parsers.SelectItemTypeField,
},
},
},
},
},
Table: parsers.Table{Value: "c"},
},
mockData,
[]memoryexecutor.RowType{
map[string]interface{}{"id": "123", "Intersection": []interface{}{3}},
map[string]interface{}{"id": "456", "Intersection": []interface{}{5, 6}},
map[string]interface{}{"id": "789", "Intersection": []interface{}{7, 8, 9}},
},
)
})
t.Run("Should execute function SET_UNION()", func(t *testing.T) {
testQueryExecute(
t,
parsers.SelectStmt{
SelectItems: []parsers.SelectItem{
{
Path: []string{"c", "id"},
Type: parsers.SelectItemTypeField,
},
{
Alias: "Union",
Type: parsers.SelectItemTypeFunctionCall,
Value: parsers.FunctionCall{
Type: parsers.FunctionCallSetUnion,
Arguments: []interface{}{
parsers.SelectItem{
Path: []string{"c", "arr1"},
Type: parsers.SelectItemTypeField,
},
parsers.SelectItem{
Path: []string{"c", "arr2"},
Type: parsers.SelectItemTypeField,
},
},
},
},
},
Table: parsers.Table{Value: "c"},
},
mockData,
[]memoryexecutor.RowType{
map[string]interface{}{"id": "123", "Union": []interface{}{1, 2, 3, 4, 5}},
map[string]interface{}{"id": "456", "Union": []interface{}{4, 5, 6, 7, 8}},
map[string]interface{}{"id": "789", "Union": []interface{}{7, 8, 9, 10, 11}},
},
)
})
}

View File

@ -236,6 +236,17 @@ func getFieldValue(field parsers.SelectItem, queryParameters map[string]interfac
case parsers.FunctionCallIsString: case parsers.FunctionCallIsString:
return typeChecking_IsString(typedValue.Arguments, queryParameters, row) return typeChecking_IsString(typedValue.Arguments, queryParameters, row)
case parsers.FunctionCallArrayConcat:
return array_Concat(typedValue.Arguments, queryParameters, row)
case parsers.FunctionCallArrayLength:
return array_Length(typedValue.Arguments, queryParameters, row)
case parsers.FunctionCallArraySlice:
return array_Slice(typedValue.Arguments, queryParameters, row)
case parsers.FunctionCallSetIntersect:
return set_Intersect(typedValue.Arguments, queryParameters, row)
case parsers.FunctionCallSetUnion:
return set_Union(typedValue.Arguments, queryParameters, row)
case parsers.FunctionCallIn: case parsers.FunctionCallIn:
return misc_In(typedValue.Arguments, queryParameters, row) return misc_In(typedValue.Arguments, queryParameters, row)
} }