Skip to content

Commit 8d31a38

Browse files
committed
Merge remote-tracking branch 'upstream/main' into issues/4789
2 parents 7f91e5e + 11727a4 commit 8d31a38

47 files changed

Lines changed: 4219 additions & 93 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

core/src/main/java/org/opensearch/sql/analysis/Analyzer.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@
5757
import org.opensearch.sql.ast.expression.UnresolvedExpression;
5858
import org.opensearch.sql.ast.expression.WindowFunction;
5959
import org.opensearch.sql.ast.tree.AD;
60+
import org.opensearch.sql.ast.tree.AddColTotals;
61+
import org.opensearch.sql.ast.tree.AddTotals;
6062
import org.opensearch.sql.ast.tree.Aggregation;
6163
import org.opensearch.sql.ast.tree.Append;
6264
import org.opensearch.sql.ast.tree.AppendCol;
@@ -521,6 +523,16 @@ public LogicalPlan visitEval(Eval node, AnalysisContext context) {
521523
return new LogicalEval(child, expressionsBuilder.build());
522524
}
523525

526+
@Override
527+
public LogicalPlan visitAddTotals(AddTotals node, AnalysisContext context) {
528+
throw getOnlyForCalciteException("addtotals");
529+
}
530+
531+
@Override
532+
public LogicalPlan visitAddColTotals(AddColTotals node, AnalysisContext context) {
533+
throw getOnlyForCalciteException("addcoltotals");
534+
}
535+
524536
/** Build {@link ParseExpression} to context and skip to child nodes. */
525537
@Override
526538
public LogicalPlan visitParse(Parse node, AnalysisContext context) {

core/src/main/java/org/opensearch/sql/ast/AbstractNodeVisitor.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@
4545
import org.opensearch.sql.ast.statement.Query;
4646
import org.opensearch.sql.ast.statement.Statement;
4747
import org.opensearch.sql.ast.tree.AD;
48+
import org.opensearch.sql.ast.tree.AddColTotals;
49+
import org.opensearch.sql.ast.tree.AddTotals;
4850
import org.opensearch.sql.ast.tree.Aggregation;
4951
import org.opensearch.sql.ast.tree.Append;
5052
import org.opensearch.sql.ast.tree.AppendCol;
@@ -451,4 +453,12 @@ public T visitAppend(Append node, C context) {
451453
public T visitMultisearch(Multisearch node, C context) {
452454
return visitChildren(node, context);
453455
}
456+
457+
public T visitAddTotals(AddTotals node, C context) {
458+
return visitChildren(node, context);
459+
}
460+
461+
public T visitAddColTotals(AddColTotals node, C context) {
462+
return visitChildren(node, context);
463+
}
454464
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package org.opensearch.sql.ast.tree;
7+
8+
import com.google.common.collect.ImmutableList;
9+
import java.util.List;
10+
import java.util.Map;
11+
import lombok.*;
12+
import org.opensearch.sql.ast.AbstractNodeVisitor;
13+
import org.opensearch.sql.ast.expression.Field;
14+
import org.opensearch.sql.ast.expression.Literal;
15+
16+
/**
17+
* AST node representing the PPL addcoltotals command. Computes column-wise totals across events and
18+
* optionally appends a summary event.
19+
*
20+
* @see AddTotals for row-wise totals
21+
*/
22+
@Getter
23+
@Setter
24+
@ToString
25+
@EqualsAndHashCode(callSuper = false)
26+
@RequiredArgsConstructor
27+
public class AddColTotals extends UnresolvedPlan {
28+
private final List<Field> fieldList;
29+
private final Map<String, Literal> options;
30+
private UnresolvedPlan child;
31+
32+
@Override
33+
public AddColTotals attach(UnresolvedPlan child) {
34+
this.child = child;
35+
return this;
36+
}
37+
38+
@Override
39+
public List<UnresolvedPlan> getChild() {
40+
return child == null ? ImmutableList.of() : ImmutableList.of(child);
41+
}
42+
43+
@Override
44+
public <T, C> T accept(AbstractNodeVisitor<T, C> visitor, C context) {
45+
return visitor.visitAddColTotals(this, context);
46+
}
47+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package org.opensearch.sql.ast.tree;
7+
8+
import com.google.common.collect.ImmutableList;
9+
import java.util.List;
10+
import java.util.Map;
11+
import lombok.EqualsAndHashCode;
12+
import lombok.Getter;
13+
import lombok.RequiredArgsConstructor;
14+
import lombok.Setter;
15+
import lombok.ToString;
16+
import org.opensearch.sql.ast.AbstractNodeVisitor;
17+
import org.opensearch.sql.ast.expression.Field;
18+
import org.opensearch.sql.ast.expression.Literal;
19+
20+
@Getter
21+
@Setter
22+
@ToString
23+
@EqualsAndHashCode(callSuper = false)
24+
@RequiredArgsConstructor
25+
public class AddTotals extends UnresolvedPlan {
26+
private final List<Field> fieldList;
27+
private final Map<String, Literal> options;
28+
private UnresolvedPlan child;
29+
30+
@Override
31+
public AddTotals attach(UnresolvedPlan child) {
32+
this.child = child;
33+
return this;
34+
}
35+
36+
@Override
37+
public List<UnresolvedPlan> getChild() {
38+
return child == null ? ImmutableList.of() : ImmutableList.of(child);
39+
}
40+
41+
@Override
42+
public <T, C> T accept(AbstractNodeVisitor<T, C> visitor, C context) {
43+
return visitor.visitAddTotals(this, context);
44+
}
45+
}

core/src/main/java/org/opensearch/sql/calcite/CalcitePlanContext.java

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import static org.opensearch.sql.calcite.utils.OpenSearchTypeFactory.TYPE_FACTORY;
99

1010
import java.sql.Connection;
11+
import java.util.ArrayList;
1112
import java.util.HashMap;
1213
import java.util.List;
1314
import java.util.Map;
@@ -61,6 +62,16 @@ public class CalcitePlanContext {
6162

6263
@Getter public Map<String, RexLambdaRef> rexLambdaRefMap;
6364

65+
/**
66+
* List of captured variables from outer scope for lambda functions. When a lambda body references
67+
* a field that is not a lambda parameter, it gets captured and stored here. The captured
68+
* variables are passed as additional arguments to the transform function.
69+
*/
70+
@Getter private List<RexNode> capturedVariables;
71+
72+
/** Whether we're currently inside a lambda context. */
73+
@Getter @Setter private boolean inLambdaContext = false;
74+
6475
private CalcitePlanContext(FrameworkConfig config, SysLimit sysLimit, QueryType queryType) {
6576
this.config = config;
6677
this.sysLimit = sysLimit;
@@ -70,6 +81,24 @@ private CalcitePlanContext(FrameworkConfig config, SysLimit sysLimit, QueryType
7081
this.rexBuilder = new ExtendedRexBuilder(relBuilder.getRexBuilder());
7182
this.functionProperties = new FunctionProperties(QueryType.PPL);
7283
this.rexLambdaRefMap = new HashMap<>();
84+
this.capturedVariables = new ArrayList<>();
85+
}
86+
87+
/**
88+
* Private constructor for creating a context that shares relBuilder with parent. Used by clone()
89+
* to create lambda contexts that can resolve fields from the parent context.
90+
*/
91+
private CalcitePlanContext(CalcitePlanContext parent) {
92+
this.config = parent.config;
93+
this.sysLimit = parent.sysLimit;
94+
this.queryType = parent.queryType;
95+
this.connection = parent.connection;
96+
this.relBuilder = parent.relBuilder; // Share the same relBuilder
97+
this.rexBuilder = parent.rexBuilder; // Share the same rexBuilder
98+
this.functionProperties = parent.functionProperties;
99+
this.rexLambdaRefMap = new HashMap<>(); // New map for lambda variables
100+
this.capturedVariables = new ArrayList<>(); // New list for captured variables
101+
this.inLambdaContext = true; // Mark that we're inside a lambda
73102
}
74103

75104
public RexNode resolveJoinCondition(
@@ -101,8 +130,13 @@ public Optional<RexCorrelVariable> peekCorrelVar() {
101130
}
102131
}
103132

133+
/**
134+
* Creates a clone of this context that shares the relBuilder with the parent. This allows lambda
135+
* expressions to reference fields from the current row while having their own lambda variable
136+
* mappings.
137+
*/
104138
public CalcitePlanContext clone() {
105-
return new CalcitePlanContext(config, sysLimit, queryType);
139+
return new CalcitePlanContext(this);
106140
}
107141

108142
public static CalcitePlanContext create(
@@ -134,4 +168,42 @@ public static boolean isLegacyPreferred() {
134168
public void putRexLambdaRefMap(Map<String, RexLambdaRef> candidateMap) {
135169
this.rexLambdaRefMap.putAll(candidateMap);
136170
}
171+
172+
/**
173+
* Captures an external variable for use inside a lambda. Returns a RexLambdaRef that references
174+
* the captured variable by its index in the captured variables list. The actual RexNode value is
175+
* stored in capturedVariables and will be passed as additional arguments to the transform
176+
* function.
177+
*
178+
* @param fieldRef The RexInputRef representing the external field
179+
* @param fieldName The name of the field being captured
180+
* @return A RexLambdaRef that can be used inside the lambda to reference the captured value
181+
*/
182+
public RexLambdaRef captureVariable(RexNode fieldRef, String fieldName) {
183+
// Check if this variable is already captured
184+
for (int i = 0; i < capturedVariables.size(); i++) {
185+
if (capturedVariables.get(i).equals(fieldRef)) {
186+
// Return existing reference - offset by number of lambda params (1 for array element)
187+
return rexLambdaRefMap.get("__captured_" + i);
188+
}
189+
}
190+
191+
// Add to captured variables list
192+
int captureIndex = capturedVariables.size();
193+
capturedVariables.add(fieldRef);
194+
195+
// Create a lambda ref for this captured variable
196+
// The index is offset by the number of lambda parameters (1 for single-param lambda)
197+
// Count only actual lambda parameters, not captured variables
198+
int lambdaParamCount =
199+
(int)
200+
rexLambdaRefMap.keySet().stream().filter(key -> !key.startsWith("__captured_")).count();
201+
RexLambdaRef lambdaRef =
202+
new RexLambdaRef(lambdaParamCount + captureIndex, fieldName, fieldRef.getType());
203+
204+
// Store it so we can find it again if the same field is referenced multiple times
205+
rexLambdaRefMap.put("__captured_" + captureIndex, lambdaRef);
206+
207+
return lambdaRef;
208+
}
137209
}

0 commit comments

Comments
 (0)