From a100305fe7a04ea1bf9ecfd562edb8f4d4f3277c Mon Sep 17 00:00:00 2001
From: Mike Cohen <mike@velocidex.com>
Date: Tue, 8 Oct 2024 18:46:37 +1000
Subject: [PATCH] Bugfix: Apply LIMIT to output of GROUP BY

Also added && shortcut operator (Similar to the || operator)
---
 fixtures/vql_queries.golden | 12 +++++++++++-
 vfilter.go                  | 33 +++++++++++++++++++++++----------
 vfilter_test.go             |  3 +++
 3 files changed, 37 insertions(+), 11 deletions(-)

diff --git a/fixtures/vql_queries.golden b/fixtures/vql_queries.golden
index dc74a95..325034d 100644
--- a/fixtures/vql_queries.golden
+++ b/fixtures/vql_queries.golden
@@ -665,7 +665,17 @@
       "'A' || '' || 'B'": "A"
     }
   ],
-  "076 Whitespace in the query: SELECT * FROM test()": [
+  "076 Alternatives with AND shortcut operator: SELECT NULL  \u0026\u0026 '', TRUE  \u0026\u0026 'XX', 'A'  \u0026\u0026 'B', 'A'  \u0026\u0026 FALSE, ((FALSE  \u0026\u0026 1) || 2), TRUE  \u0026\u0026 1 || 2 FROM scope()": [
+    {
+      "NULL  \u0026\u0026 ''": false,
+      "TRUE  \u0026\u0026 'XX'": "XX",
+      "'A'  \u0026\u0026 'B'": "B",
+      "'A'  \u0026\u0026 FALSE": false,
+      "((FALSE  \u0026\u0026 1) || 2)": 2,
+      "TRUE  \u0026\u0026 1 || 2": 1
+    }
+  ],
+  "077 Whitespace in the query: SELECT * FROM test()": [
     {
       "foo": 0,
       "bar": 0
diff --git a/vfilter.go b/vfilter.go
index 68a8e8f..5ddd2a4 100644
--- a/vfilter.go
+++ b/vfilter.go
@@ -156,6 +156,7 @@ var (
 			`|(?ims)(?P<AND>\bAND\b)` +
 			`|(?ims)(?P<OR>\bOR\b)` +
 			`|(?ims)(?P<AlternativeOR>\|+)` +
+			`|(?ims)(?P<AlternativeAND>&&)` +
 			`|(?ims)(?P<FROM>\bFROM\b)` +
 			`|(?ims)(?P<NOT>\bNOT\b)` +
 			`|(?ims)(?P<AS>\bAS\b)` +
@@ -475,12 +476,10 @@ func (self *_Select) Eval(ctx context.Context, scope types.Scope) <-chan Row {
 	// Start query evaluation
 	scope.Explainer().StartQuery(self)
 
-	if self.GroupBy != nil {
-		return self.EvalGroupBy(ctx, scope)
-	}
-
 	output_chan := make(chan Row)
 
+	// Limits occur before the group by so we can cut the group by
+	// result short according to the limit clause.
 	if self.Limit != nil {
 		go func() {
 			defer close(output_chan)
@@ -511,6 +510,12 @@ func (self *_Select) Eval(ctx context.Context, scope types.Scope) <-chan Row {
 		return output_chan
 	}
 
+	// Group by occurs before order by so we can order the grouped by
+	// results.
+	if self.GroupBy != nil {
+		return self.EvalGroupBy(ctx, scope)
+	}
+
 	if self.OrderBy != nil {
 		desc := false
 		if self.OrderByDesc != nil {
@@ -813,7 +818,7 @@ type _AndExpression struct {
 }
 
 type _OpAndTerm struct {
-	Operator string         ` @AND `
+	Operator string         ` ( @AND | @AlternativeAND ) `
 	Term     *_OrExpression `@@`
 }
 
@@ -1268,22 +1273,30 @@ func (self *_AndExpression) IsAggregate(scope types.Scope) bool {
 }
 
 func (self *_AndExpression) Reduce(ctx context.Context, scope types.Scope) Any {
-	result := self.Left.Reduce(ctx, scope)
+	left := self.Left.Reduce(ctx, scope)
 	if self.Right == nil {
-		return result
+		return left
 	}
 
-	if scope.Bool(result) == false {
+	if scope.Bool(left) == false {
 		return false
 	}
 
+	last := left
 	for _, term := range self.Right {
-		if scope.Bool(term.Term.Reduce(ctx, scope)) == false {
+		right := term.Term.Reduce(ctx, scope)
+		if scope.Bool(right) == false {
 			return false
 		}
+
+		if term.Operator == "&&" {
+			last = right
+		} else {
+			last = true
+		}
 	}
 
-	return true
+	return last
 }
 
 func (self *_OrExpression) IsAggregate(scope types.Scope) bool {
diff --git a/vfilter_test.go b/vfilter_test.go
index 94fe789..c2978db 100644
--- a/vfilter_test.go
+++ b/vfilter_test.go
@@ -732,6 +732,9 @@ b={
 	{"Alternatives with the OR shortcut operator false",
 		"SELECT NULL || '', NULL || FALSE, NULL || 'X', 'A' || 'B', 'A' || FALSE, 'A' || '' || 'B' FROM scope()"},
 
+	{"Alternatives with AND shortcut operator",
+		"SELECT NULL && '', TRUE && 'XX', 'A' && 'B', 'A' && FALSE, ((FALSE && 1) || 2), TRUE && 1 || 2 FROM scope()"},
+
 	{"Whitespace in the query",
 		"SELECT * FROM\ntest()"},
 }