s3vectors: reject empty boolean-operator lists and sub-objects in filters (#12220)

* s3vectors: reject empty boolean-operator lists and sub-objects in filters

An empty $and/$or list or a nested empty object rendered '()'' — invalid
SQL surfaced as an engine error instead of a clean ValidationException.

* s3vectors: reject field operator objects with no operators
This commit is contained in:
Chris Lu authored and GitHub committed 2026-10-29 16:16:34 -07:00
1 parent 3f954e34dc
commit 9d5929a73f
2 files changed
+22 -6

No files matched your search

+9
View File
@@ -1532,6 +1532,9 @@ func filterExpr(filter map[string]interface{}) (string, error) {
if !ok {
return "", fmt.Errorf("%s expects a list", key)
}
if len(sub) == 0 {
return "", fmt.Errorf("%s expects a non-empty list", key)
}
exprs := make([]string, 0, len(sub))
for _, item := range sub {
m, ok := item.(map[string]interface{})
@@ -1542,6 +1545,9 @@ func filterExpr(filter map[string]interface{}) (string, error) {
if err != nil {
return "", err
}
if e == "" {
return "", fmt.Errorf("%s expects non-empty objects", key)
}
exprs = append(exprs, e)
}
joiner := " AND "
@@ -1684,6 +1690,9 @@ func fieldClause(field string, cond interface{}) (string, error) {
return "", fmt.Errorf("unsupported operator %s", op)
}
}
if len(parts) == 0 {
return "", fmt.Errorf("%s expects at least one operator", field)
}
return strings.Join(parts, " AND "), nil
default:
lit, err := sqlLiteral(cond)
+13 -6
View File
@@ -336,12 +336,19 @@ func TestTranslateFilter(t *testing.T) {
}
for name, filter := range map[string]map[string]interface{}{
"unknown operator": {"k": map[string]interface{}{"$bogus": "v"}},
"exists non-bool": {"k": map[string]interface{}{"$exists": "yes"}},
"range non-number": {"k": map[string]interface{}{"$gt": "v"}},
"in non-list": {"k": map[string]interface{}{"$in": "v"}},
"top-level bad op": {"$bogus": []interface{}{}},
"nested bad member": {"$and": []interface{}{"x"}},
"unknown operator": {"k": map[string]interface{}{"$bogus": "v"}},
"exists non-bool": {"k": map[string]interface{}{"$exists": "yes"}},
"range non-number": {"k": map[string]interface{}{"$gt": "v"}},
"in non-list": {"k": map[string]interface{}{"$in": "v"}},
"top-level bad op": {"$bogus": []interface{}{}},
"nested bad member": {"$and": []interface{}{"x"}},
"empty and list": {"$and": []interface{}{}},
"empty or list": {"$or": []interface{}{}},
"empty sub-object": {"$and": []interface{}{map[string]interface{}{}}},
"empty operators": {"k": map[string]interface{}{}},
"empty among fields": {"k": map[string]interface{}{}, "j": "1"},
"empty under and": {"$and": []interface{}{map[string]interface{}{
"k": map[string]interface{}{}, "j": map[string]interface{}{}}}},
} {
if _, err := translateFilter(filter); err == nil {
t.Fatalf("%s should fail", name)