From eff513b0fad4888f939466819f291b3f8b0078cb Mon Sep 17 00:00:00 2001
From: Danis Kirasirov <dbgbbu@gmail.com>
Date: Tue, 30 Jan 2024 12:31:34 +0300
Subject: [PATCH] =?UTF-8?q?=D0=9F=D1=80=D0=B0=D0=B2=D0=BA=D0=B8?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 pkg/expr/mongo.go      | 71 +++++++++++++++++++-----------------------
 pkg/expr/mongo_test.go |  2 +-
 2 files changed, 33 insertions(+), 40 deletions(-)

diff --git a/pkg/expr/mongo.go b/pkg/expr/mongo.go
index b0893b61..ed35a6dc 100644
--- a/pkg/expr/mongo.go
+++ b/pkg/expr/mongo.go
@@ -231,7 +231,7 @@ func (c *compiler) identifier(node ast.Node) string {
 }
 
 func (c *compiler) BinaryNode(node *ast.BinaryNode) interface{} {
-	if result, ok := c.handleLenNode(node); ok {
+	if result := c.handleLenNode(node); result != nil {
 		return result
 	}
 
@@ -656,10 +656,12 @@ func (c *compiler) PairNode(node *ast.PairNode) interface{} {
 	//c.compile(node.Value)
 }
 
-func (c *compiler) handleLenNode(node *ast.BinaryNode) (result bson.M, ok bool) {
+// handleLenNode получает узел AST и возвращает запрос для mongo,
+// если узел представляет вызов функции len, и nil в противном случае.
+func (c *compiler) handleLenNode(node *ast.BinaryNode) bson.M {
 	lenNode, ok := node.Left.(*ast.BuiltinNode)
 	if !ok || lenNode.Name != "len" {
-		return nil, false
+		return nil
 	}
 
 	if len(lenNode.Arguments) != 1 {
@@ -667,44 +669,35 @@ func (c *compiler) handleLenNode(node *ast.BinaryNode) (result bson.M, ok bool)
 	}
 
 	length, ok := c.eval(node.Right).(int)
-	if !ok || length < 0 {
+	if !ok {
+		panic("len() can only be compared with number value")
+	}
+	if length < 0 {
 		panic("len() can only be compared with non-negative number")
 	}
 
-	switch node.Operator {
-	case "==":
-		if length == 0 {
-			return bson.M{c.identifier(lenNode.Arguments[0]): bson.M{"$eq": bson.A{}}}, true
-		}
-		return bson.M{c.identifier(lenNode.Arguments[0]): bson.M{"$size": length}}, true
-
-	case "!=":
-		if length == 0 {
-			return bson.M{c.identifier(lenNode.Arguments[0]): bson.M{"$exists": true, "$type": "array", "$ne": bson.A{}}}, true
-		}
-		return bson.M{c.identifier(lenNode.Arguments[0]): bson.M{"$not": bson.M{"$size": length}}}, true
-
-	case ">":
-		if length == 0 {
-			return bson.M{c.identifier(lenNode.Arguments[0]): bson.M{"$exists": true, "$type": "array", "$ne": bson.A{}}}, true
-		}
-		return bson.M{c.identifier(lenNode.Arguments[0]) + "." + strconv.Itoa(length): bson.M{"$exists": true}}, true
-
-	case ">=":
-		if length == 0 {
-			return bson.M{c.identifier(lenNode.Arguments[0]): bson.M{"$exists": true, "$type": "array"}}, true
-		}
-		return bson.M{c.identifier(lenNode.Arguments[0]) + "." + strconv.Itoa(length-1): bson.M{"$exists": true}}, true
-
-	case "<":
-		if length == 0 {
-			panic("invalid comparison: len() cannot be less than 0")
-		}
-		return bson.M{c.identifier(lenNode.Arguments[0]) + "." + strconv.Itoa(length-1): bson.M{"$exists": false}}, true
-
-	case "<=":
-		return bson.M{c.identifier(lenNode.Arguments[0]) + "." + strconv.Itoa(length): bson.M{"$exists": false}}, true
+	switch op := node.Operator; {
+	case (op == "==" || op == "<=") && length == 0:
+		return bson.M{c.identifier(lenNode.Arguments[0]): bson.M{"$eq": bson.A{}}}
+	case (op == "!=" || op == ">") && length == 0:
+		return bson.M{c.identifier(lenNode.Arguments[0]): bson.M{"$exists": true, "$type": "array", "$ne": bson.A{}}}
+	case op == "==":
+		return bson.M{c.identifier(lenNode.Arguments[0]): bson.M{"$size": length}}
+	case op == "!=":
+		return bson.M{c.identifier(lenNode.Arguments[0]): bson.M{"$not": bson.M{"$size": length}}}
+	case op == ">":
+		return bson.M{c.identifier(lenNode.Arguments[0]) + "." + strconv.Itoa(length): bson.M{"$exists": true}}
+	case op == ">=" && length == 0:
+		return bson.M{c.identifier(lenNode.Arguments[0]): bson.M{"$exists": true, "$type": "array"}}
+	case op == ">=":
+		return bson.M{c.identifier(lenNode.Arguments[0]) + "." + strconv.Itoa(length-1): bson.M{"$exists": true}}
+	case op == "<" && length == 0:
+		panic("invalid comparison: len() cannot be less than 0")
+	case op == "<":
+		return bson.M{c.identifier(lenNode.Arguments[0]) + "." + strconv.Itoa(length-1): bson.M{"$exists": false}}
+	case op == "<=":
+		return bson.M{c.identifier(lenNode.Arguments[0]) + "." + strconv.Itoa(length): bson.M{"$exists": false}}
+	default:
+		panic("invalid comparison operator with len()")
 	}
-
-	panic("invalid comparison operator with len()")
 }
diff --git a/pkg/expr/mongo_test.go b/pkg/expr/mongo_test.go
index 4adeaffd..1dde0f37 100644
--- a/pkg/expr/mongo_test.go
+++ b/pkg/expr/mongo_test.go
@@ -48,7 +48,7 @@ func TestConvertToMongo(t *testing.T) {
 		{"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},
+		{"len lte zero", "len(s) <= 0", nil, bson.M{"s": bson.M{"$eq": bson.A{}}}, 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},
-- 
GitLab