Files
cosmium/query_executors/memory_executor/array_functions.go
T
Pijus Kamandulis c3726a6633 Fix ARRAY_CONTAINS panic when optional partial-match argument is omitted (#14)
* Fix ARRAY_CONTAINS panic when partial match arg is omitted

The NoSQL parser always emits a third (nil) argument for the optional
partial-match flag of ARRAY_CONTAINS. The executor checked only
len(arguments) > 2 before type-asserting arguments[2] to
parsers.SelectItem, which panicked on the nil value whenever the query
omitted the partial-match argument (e.g. ARRAY_CONTAINS(c.arr, 2)).

Guard the type assertion with a nil check and add an API test covering
ARRAY_CONTAINS with and without the optional partial-match argument.

Co-authored-by: Pijus Kamandulis <pikami@users.noreply.github.com>

* Remove comments from ARRAY_CONTAINS API test

Co-authored-by: Pijus Kamandulis <pikami@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-05-30 17:43:05 +03:00

234 lines
4.9 KiB
Go

package memoryexecutor
import (
"reflect"
"github.com/pikami/cosmium/internal/logger"
"github.com/pikami/cosmium/parsers"
)
func (r rowContext) array_Concat(arguments []interface{}) []interface{} {
var result []interface{}
for _, arg := range arguments {
array := r.parseArray(arg)
result = append(result, array...)
}
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 && arguments[2] != nil {
boolExpr := r.resolveSelectItem(arguments[2].(parsers.SelectItem))
if boolValue, ok := boolExpr.(bool); ok {
partialSearch = boolValue
} else {
logger.ErrorLn("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 {
return 0
}
return len(array)
}
func (r rowContext) array_Slice(arguments []interface{}) []interface{} {
var ok bool
var start int
var length int
array := r.parseArray(arguments[0])
startEx := r.resolveSelectItem(arguments[1].(parsers.SelectItem))
if arguments[2] != nil {
lengthEx := r.resolveSelectItem(arguments[2].(parsers.SelectItem))
if length, ok = lengthEx.(int); !ok {
logger.ErrorLn("array_Slice - got length parameters of wrong type")
return []interface{}{}
}
}
if start, ok = startEx.(int); !ok {
logger.ErrorLn("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 (r rowContext) set_Intersect(arguments []interface{}) []interface{} {
set1 := r.parseArray(arguments[0])
set2 := r.parseArray(arguments[1])
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 (r rowContext) set_Union(arguments []interface{}) []interface{} {
set1 := r.parseArray(arguments[0])
set2 := r.parseArray(arguments[1])
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 (r rowContext) parseArray(argument interface{}) []interface{} {
exItem := argument.(parsers.SelectItem)
ex := r.resolveSelectItem(exItem)
arrValue := reflect.ValueOf(ex)
if arrValue.Kind() == reflect.Invalid {
return nil
}
if arrValue.Kind() != reflect.Slice {
logger.ErrorLn("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
}
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.ErrorLn("partialMatch got parameters of wrong type")
return false
}
for _, key := range exprValue.MapKeys() {
if !reflect.DeepEqual(itemValue.MapIndex(key).Interface(), exprValue.MapIndex(key).Interface()) {
return false
}
}
return true
}