From fbbd09d4c3e08cb4d88d1f0387961009ca5509e7 Mon Sep 17 00:00:00 2001
From: Danis Kirasirov <dbgbbu@gmail.com>
Date: Mon, 29 Jan 2024 18:10:27 +0300
Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?=
 =?UTF-8?q?=D0=BD=D1=8B=20=D1=82=D0=B5=D1=81=D1=82=D1=8B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 pkg/expr/expr_test.go  | 10 +++++++++-
 pkg/expr/mongo.go      |  6 +++---
 pkg/expr/mongo_test.go | 23 ++++++++++++++++++-----
 3 files changed, 30 insertions(+), 9 deletions(-)

diff --git a/pkg/expr/expr_test.go b/pkg/expr/expr_test.go
index 15710da9..f8909daa 100644
--- a/pkg/expr/expr_test.go
+++ b/pkg/expr/expr_test.go
@@ -39,7 +39,15 @@ func TestIsExpression(t *testing.T) {
 		{"time", fmt.Sprintf("d > Time.Time('%s')", now.Format(time.RFC3339)), true},
 		{"in", "In(s, [1,2,3])", true},
 		{"in", "In(s, 1)", true},
-		{"exists", "exists(s)", true},
+		{"exists#1", "exists(s)", true},
+		{"exists#2", "exists", false},
+		{"len#1", "len(s) == 1", true},
+		{"len#2", "len(s) != 1", true},
+		{"len#3", "len(s) > 1", true},
+		{"len#4", "len(s) >= 1", true},
+		{"len#5", "len(s) < 1", true},
+		{"len#6", "len(s) <= 1", true},
+		{"len#7", "len", false},
 		{"text search or id", "id", false},
 		{"numbers", "3", false},
 	}
diff --git a/pkg/expr/mongo.go b/pkg/expr/mongo.go
index bd57a30d..b0893b61 100644
--- a/pkg/expr/mongo.go
+++ b/pkg/expr/mongo.go
@@ -410,10 +410,10 @@ func (c *compiler) CallNode(node *ast.CallNode) interface{} {
 
 		return bson.M{fields: bson.M{"$in": array}}
 	case "exists":
-		field := c.identifier(node.Arguments[0])
-		if field == "" {
-			panic("incorrect argument, empty field name")
+		if len(node.Arguments) != 1 {
+			panic("exists() expects exactly 1 argument")
 		}
+		field := c.identifier(node.Arguments[0])
 		return bson.M{"$or": bson.A{
 			bson.M{field: bson.M{"$exists": true, "$type": "array"}},
 			bson.M{field: bson.M{"$ne": nil}},
diff --git a/pkg/expr/mongo_test.go b/pkg/expr/mongo_test.go
index ae6bee16..4adeaffd 100644
--- a/pkg/expr/mongo_test.go
+++ b/pkg/expr/mongo_test.go
@@ -30,12 +30,25 @@ func TestConvertToMongo(t *testing.T) {
 		{"equal", "s == 3", nil, bson.M{"s": 3}, false},
 		{"in array", "s in [1,2,3]", nil, bson.M{"s": bson.M{"$in": []interface{}{1, 2, 3}}}, false},
 		{"not in array", "s not in [1,2,3]", nil, bson.M{"s": bson.M{"$nin": []interface{}{1, 2, 3}}}, false},
-		{"exists", "exists(s)", nil, bson.M{"$or": bson.A{bson.M{"s": bson.M{"$exists": true, "$type": "array"}}, bson.M{"s": bson.M{"$ne": nil}}}}, false},
-		{"len equal", "len(s) == 1", nil, bson.M{"s": bson.M{"$size": 1}}, false},
-		{"len equal empty", "len(s) == 0", nil, bson.M{"s": bson.M{"$eq": bson.A{}}}, false},
+		{"exists#1", "exists(s)", nil, bson.M{"$or": bson.A{bson.M{"s": bson.M{"$exists": true, "$type": "array"}}, bson.M{"s": bson.M{"$ne": nil}}}}, false},
+		{"exists#2", "exists(s, s)", nil, nil, true},
+		{"len#1", "len(s)", nil, nil, true},
+		{"len#2", "len(s) <> 1", nil, nil, true},
+		{"len#3", "len(s) == -1", nil, nil, true},
+		{"len#4", "len(s, s) == -1", nil, nil, true},
+		{"len#5", "len(s) == s", nil, nil, true},
+		{"len eq", "len(s) == 1", nil, bson.M{"s": bson.M{"$size": 1}}, false},
+		{"len eq zero", "len(s) == 0", nil, bson.M{"s": bson.M{"$eq": bson.A{}}}, false},
+		{"len ne", "len(s) != 1", nil, bson.M{"s": bson.M{"$not": bson.M{"$size": 1}}}, false},
+		{"len ne zero", "len(s) != 0", nil, bson.M{"s": bson.M{"$exists": true, "$ne": bson.A{}, "$type": "array"}}, false},
 		{"len gt", "len(s) > 1", nil, bson.M{"s.1": bson.M{"$exists": true}}, false},
-		{"len gt 0", "len(s) > 0", nil, bson.M{"s": bson.M{"$exists": true, "$type": "array", "$ne": bson.A{}}}, false},
+		{"len gt zero", "len(s) > 0", nil, bson.M{"s": bson.M{"$exists": true, "$type": "array", "$ne": bson.A{}}}, false},
+		{"len gte", "len(s) >= 1", nil, bson.M{"s.0": bson.M{"$exists": true}}, false},
+		{"len gte zero", "len(s) >= 0", nil, bson.M{"s": bson.M{"$exists": true, "$type": "array"}}, false},
 		{"len lt", "len(s) < 1", nil, bson.M{"s.0": bson.M{"$exists": false}}, false},
+		{"len lt zero", "len(s) < 0", nil, nil, true},
+		{"len lte", "len(s) <= 1", nil, bson.M{"s.1": bson.M{"$exists": false}}, false},
+		{"len lte zero", "len(s) <= 0", nil, bson.M{"s.0": bson.M{"$exists": false}}, false},
 		{"field#1", "s.test > 3", nil, bson.M{"s.test": bson.M{"$gt": 3}}, false},
 		{"field#2", "s['test'] > 3", nil, bson.M{"s.test": bson.M{"$gt": 3}}, false},
 		{"field#3", "s[test] > 3", nil, bson.M{"s.test": bson.M{"$gt": 3}}, false},
@@ -52,7 +65,7 @@ func TestConvertToMongo(t *testing.T) {
 		{"iendsWith", "iendsWith(s, 'some')", nil, bson.M{"s": bson.M{"$regex": ".*some$", "$options": "i"}}, false},
 		{"iendsWith . + () $ {} ^", "iendsWith(s,'. + () $ {} ^')", nil, bson.M{"s": bson.M{"$regex": ".*\\. \\+ \\(\\) \\$ \\{\\} \\^$", "$options": "i"}}, false},
 		{"or", "s==2 || s > 10", nil, bson.M{"$or": bson.A{bson.M{"s": 2}, bson.M{"s": bson.M{"$gt": 10}}}}, false},
-		{"not#1", "not icontains(s, 'some')", nil, bson.M{"$nor":bson.A{bson.M{"s":bson.M{"$options":"i", "$regex":"some"}}}}, false},
+		{"not#1", "not icontains(s, 'some')", nil, bson.M{"$nor": bson.A{bson.M{"s": bson.M{"$options": "i", "$regex": "some"}}}}, false},
 		{"not#2", "not (s.test > 3)", nil, bson.M{"$nor": bson.A{bson.M{"s.test": bson.M{"$gt": 3}}}}, false},
 		{"search", "search('some') || s > 10", nil, bson.M{"$or": bson.A{bson.M{"$text": bson.M{"$search": "some"}}, bson.M{"s": bson.M{"$gt": 10}}}}, false},
 		{"vars:or", "s== a + 2 || s > a + 10", map[string]interface{}{"a": 100}, bson.M{"$or": bson.A{bson.M{"s": 102}, bson.M{"s": bson.M{"$gt": 110}}}}, false},
-- 
GitLab