From c3726a66334633856473dc6bc96edefd0c8c533a Mon Sep 17 00:00:00 2001 From: Pijus Kamandulis Date: Sat, 30 May 2026 17:43:05 +0300 Subject: [PATCH] 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 * Remove comments from ARRAY_CONTAINS API test Co-authored-by: Pijus Kamandulis --------- Co-authored-by: Cursor Agent --- api/tests/documents_array_functions_test.go | 47 +++++++++++++++++++ .../memory_executor/array_functions.go | 2 +- 2 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 api/tests/documents_array_functions_test.go diff --git a/api/tests/documents_array_functions_test.go b/api/tests/documents_array_functions_test.go new file mode 100644 index 0000000..dd7c60a --- /dev/null +++ b/api/tests/documents_array_functions_test.go @@ -0,0 +1,47 @@ +package tests_test + +import ( + "testing" + + "github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos" +) + +func Test_Documents_ArrayContains(t *testing.T) { + presets := []testPreset{PresetJsonStore, PresetBadgerStore} + + runTestsWithPresets(t, "Test_Documents_ArrayContains", presets, func(t *testing.T, ts *TestServer, client *azcosmos.Client) { + collectionClient := documents_InitializeDb(t, ts) + + t.Run("Should execute ARRAY_CONTAINS() without partial match argument", func(t *testing.T) { + testCosmosQuery(t, collectionClient, + `SELECT VALUE ARRAY_CONTAINS(["apple", "banana", "cherry"], "banana") FROM c ORDER BY c.id`, + nil, + []interface{}{true, true}, + ) + }) + + t.Run("Should execute ARRAY_CONTAINS() returning false for missing item", func(t *testing.T) { + testCosmosQuery(t, collectionClient, + `SELECT VALUE ARRAY_CONTAINS(["apple", "banana", "cherry"], "grape") FROM c ORDER BY c.id`, + nil, + []interface{}{false, false}, + ) + }) + + t.Run("Should execute ARRAY_CONTAINS() with object full match", func(t *testing.T) { + testCosmosQuery(t, collectionClient, + `SELECT VALUE ARRAY_CONTAINS([{"name": "apple", "color": "red"}], {"name": "apple"}) FROM c ORDER BY c.id`, + nil, + []interface{}{false, false}, + ) + }) + + t.Run("Should execute ARRAY_CONTAINS() with object partial match", func(t *testing.T) { + testCosmosQuery(t, collectionClient, + `SELECT VALUE ARRAY_CONTAINS([{"name": "apple", "color": "red"}], {"name": "apple"}, true) FROM c ORDER BY c.id`, + nil, + []interface{}{true, true}, + ) + }) + }) +} diff --git a/query_executors/memory_executor/array_functions.go b/query_executors/memory_executor/array_functions.go index 6d60517..48b18bf 100644 --- a/query_executors/memory_executor/array_functions.go +++ b/query_executors/memory_executor/array_functions.go @@ -25,7 +25,7 @@ func (r rowContext) array_Contains(arguments []interface{}) bool { exprToSearch := r.resolveSelectItem(arguments[1].(parsers.SelectItem)) partialSearch := false - if len(arguments) > 2 { + if len(arguments) > 2 && arguments[2] != nil { boolExpr := r.resolveSelectItem(arguments[2].(parsers.SelectItem)) if boolValue, ok := boolExpr.(bool); ok { partialSearch = boolValue