Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
107 commits
Select commit Hold shift + click to select a range
1e04df1
WIP: try introducing sql validator without disabling the legacy type …
yuancu Nov 27, 2025
229cb1d
Override deriveType in validator to allow type checking on UDTs
yuancu Nov 28, 2025
b529a35
Add special handling for datetime coercion
yuancu Dec 1, 2025
34b2435
Override coerceOperandType and castTo methods to apply casting to udf…
yuancu Dec 1, 2025
e6e01c8
Fix interval semantic mismatch between sql and ppl
yuancu Dec 2, 2025
15e0bef
Fix quarter interval bug in calcite (counte-react)
yuancu Dec 2, 2025
a1f2f42
Comment out function overloadings to make basic functions work
yuancu Dec 2, 2025
7f999df
Remove unused validator operators
yuancu Dec 3, 2025
39e208a
Update sargFromJson method in ExtendedRelJson
yuancu Dec 3, 2025
55e8486
Prepend following rules for datetime comparisons: (date, time) -> tim…
yuancu Dec 3, 2025
dc44f8c
Directly delegate type checking to sql type checkers (1292/1599 | 125…
yuancu Dec 4, 2025
a4dfd3f
Enable IP comparison (1332/1559 | 1409/1915)
yuancu Dec 4, 2025
9deca8b
Fix expected type checking for agg functions (1287/1599 | 1423/1914)
yuancu Dec 8, 2025
11c33ba
Define operand types for json functions
yuancu Dec 8, 2025
a4b0d72
Define operand types and return type inferences for array functions (…
yuancu Dec 8, 2025
c237041
Add hive sql library operators for array_slice function, which is use…
yuancu Dec 9, 2025
51c556f
Define overrides for atan function to allow conditional sql call rewr…
yuancu Dec 9, 2025
dc3bc41
Update operand types for percentile approx to allow with addtional ty…
yuancu Dec 9, 2025
09f45e7
Correct span function type routing (allow any)
yuancu Dec 9, 2025
a30fa3a
Define operand type for DISTINCT_COUNT_APPROX
yuancu Dec 9, 2025
a588864
Unconditionally rewrite COUNT() to COUNT(*) in sql level to allow typ…
yuancu Dec 9, 2025
4cd7570
Define operand types for EnhancedCoalesce, ScalarMin & Max; remove Sq…
yuancu Dec 9, 2025
dcb3b8f
Correct type checkers for mvappend; fix regexp lookup; correct isempt…
yuancu Dec 9, 2025
c230765
Initiate a new RelToSqlConverter every time as it is stateful (1769/2…
yuancu Dec 10, 2025
8200cf7
Override add for string concat and number addition (1773/2018)
yuancu Dec 10, 2025
4abc31e
Switch sql dialect to opensearch spark sql dialect (1772/2018)
yuancu Dec 10, 2025
0269692
Align null order in collation (1783/2018)
yuancu Dec 11, 2025
0118313
Skip validations for bin-on-timestamps (1799/2027)
yuancu Dec 11, 2025
4adfa98
Do not remove sort in subqueries when converting sql to rel (1820/2027)
yuancu Dec 11, 2025
4838c4c
Trim unused fields after when converting sql back to rel (1857/2027)
yuancu Dec 11, 2025
545ed04
Pass on join type of logical correlate to lateral join (1858/2027)
yuancu Dec 11, 2025
1aea9e3
Support semi and anti join in the converted SQL (1860/2027)
yuancu Dec 12, 2025
9f369b0
Disable insertion of json_type operator (1864/2027)
yuancu Dec 12, 2025
9bffce8
Rewrite IN / NOT IN with tuple inputs to row(...tuple) to conform to …
yuancu Dec 12, 2025
96801c6
Update exception type of testSpanByImplicitTimestamp
yuancu Dec 15, 2025
c3bde24
Update PplTypeCoercionRule to allow CAST(IP as STRING)
yuancu Dec 15, 2025
15f17b3
Fix float literal by inserting compulsory cast
yuancu Dec 15, 2025
9b9c07a
Extend spark dialect to support cast null to IP (1880/2028)
yuancu Dec 15, 2025
443d81e
Support sql-udt conversion of composite types (1881/2028)
yuancu Dec 15, 2025
e1bab21
Add geoip of string override because the udt type is erased during va…
yuancu Dec 15, 2025
6a7feae
Skip validation when grouping by two cases (1885/2028)
yuancu Dec 16, 2025
2d36650
Embed collations into window for streamstats (1910/2054, before rebas…
yuancu Dec 16, 2025
23e611a
Skip validation when logical values is used to create empty rows (191…
yuancu Dec 16, 2025
6286e90
Remove EnhancedCoalesceFunction in favor of built-in Coalesce functio…
yuancu Dec 16, 2025
0736444
Fix ITs that are affected by allowing implicit coercion from number t…
yuancu Dec 16, 2025
53a4118
Return original logical plan when get error message 'Aggregate expres…
yuancu Dec 16, 2025
7f52976
Reorganize class structures and refactor for readability
yuancu Dec 16, 2025
c4de0e4
Remove validation logics from PPLFuncImpTable
yuancu Dec 17, 2025
0667fa7
Remove unused classes (1917/2055)
yuancu Dec 17, 2025
485c038
Merge remote-tracking branch 'origin/main' into issues/4636 (1929/2069)
yuancu Dec 17, 2025
d19ba53
Update the type checker of transform function to allow arbitrary addi…
yuancu Dec 17, 2025
330a4e5
Tolerant group by window function (returning original logical plan)
yuancu Dec 17, 2025
f5fda25
Ignore patterns IT that are to be fixed in 4968
yuancu Dec 18, 2025
657cc6a
Allow binary arithmetic operation between string and numerics
yuancu Dec 18, 2025
b030f41
Define SqlKind for DIVIDE and MOD UDF for coercion purpose
yuancu Dec 18, 2025
8caa594
Disable nullary call to not confuse with field reference
yuancu Dec 18, 2025
975d21c
Use SAFE_CAST instead of CAST to tolerant malformatted numbers
yuancu Dec 19, 2025
bc38f9f
Enable identifier expansion to ensure that casted args in agg functio…
yuancu Dec 19, 2025
502dbb0
Handle least restrictive for UDTs
yuancu Dec 19, 2025
34985c7
Update testBetweenWithIncompatibleTypes
yuancu Dec 19, 2025
c660a69
Fix or skip yaml tests
yuancu Dec 19, 2025
d9b1453
Pass on exception for bin-on-timestamp exception
yuancu Dec 19, 2025
90b6309
Fix doctest
yuancu Dec 18, 2025
ea06ab0
Fix unit tests
yuancu Dec 17, 2025
e548ad2
Merge branch '4636-fix-ut' into issues/4636
yuancu Dec 19, 2025
94c7f33
Merge branch '4636-fix-yaml' into issues/4636
yuancu Dec 19, 2025
bab48d7
Remove interface PPLTypeChecker
yuancu Dec 22, 2025
6a30697
Pass on bucket_nullable flag to sql node and back to rel node (1929/2…
yuancu Dec 22, 2025
0be7cb4
Fix append and multisearch ITs (1932/2066)
yuancu Dec 22, 2025
38e12c6
Fix unit tests broken by passed-on hints
yuancu Dec 22, 2025
0990db1
Fix calcite, calcite no push down, and v2 ppl explain ITs
yuancu Dec 17, 2025
d0195cb
Merge remote-tracking branch 'origin/main' into issues/4636
yuancu Dec 22, 2025
9f6dcd7
Update clickbench plans
yuancu Dec 22, 2025
d455d35
Fix dedupe explain ITs
yuancu Dec 22, 2025
a2d1955
Fix clickbench q41
yuancu Dec 22, 2025
4a98c3a
Fix explain dedup expr complex1
yuancu Dec 22, 2025
aff8541
Remove unused CalciteFuncSignature interface
yuancu Dec 23, 2025
229ddb7
Remove type checkers from operator registration because all type chec…
yuancu Dec 23, 2025
e6b80e5
Make getValiadtor thread-safe & improve type checking for JsonSet
yuancu Dec 23, 2025
cb78ea7
Deprecate interface UDFOperandMetadata.wrapUDT
yuancu Dec 23, 2025
69a2d01
Merge remote-tracking branch 'origin/main' into issues/4636
yuancu Dec 26, 2025
d722baf
Fix explain ITs after merging main
yuancu Dec 26, 2025
d513e17
Merge remote-tracking branch 'origin/main' into issues/4636
yuancu Dec 29, 2025
ff91bdc
Update plans after merging
yuancu Dec 29, 2025
32c407b
Merge remote-tracking branch 'origin/main' into issues/4636
yuancu Jan 9, 2026
a6839e2
Update plans, UTs, doctests as is after merging with main
yuancu Jan 12, 2026
38ef160
Merge remote-tracking branch 'origin/main' into issues/4636
yuancu Jan 12, 2026
42b89f0
Use CAST instead of SAFE_CAST for non-string literals
yuancu Jan 13, 2026
9ecc8ff
Merge remote-tracking branch 'origin/main' into issues/4636
yuancu Jan 13, 2026
907b292
Correct some explain ITs
yuancu Jan 13, 2026
35ac017
Allow more than 2 alternative plans for dedup
yuancu Jan 13, 2026
7418cda
Merge OpenSearchSqlToRelConverter in CalciteToolsHelper into the same…
yuancu Jan 14, 2026
ebfdffd
Merge remote-tracking branch 'origin/main' into issues/4636
yuancu Jan 19, 2026
8d561a0
Fix plans after merge
yuancu Jan 19, 2026
fd6251e
Address comments
yuancu Jan 22, 2026
f34619a
Add @NonNull annotation to getOperandMetadata() and provide default i…
yuancu Jan 23, 2026
31dc650
Create a new sql validator each time
yuancu Jan 27, 2026
19faa38
Remove CalciteServerStatement dependency from PplValidator creation
yuancu Jan 28, 2026
1582966
Replace custom ConformanceDelegate with Calcite's SqlDelegatingConfor…
yuancu Jan 28, 2026
3584c7c
Merge remote-tracking branch 'origin/main' into issues/4636
yuancu Jan 28, 2026
568fc00
Allow SqlTypeName.NULL be casted to UDTs
yuancu Jan 28, 2026
6d291b8
Correct spath's implementation and multiple explain results
yuancu Jan 29, 2026
199546a
Use CAST instead of SAFE_CAST for casts between numeric types
yuancu Jan 29, 2026
0d299b5
Merge remote-tracking branch 'origin/main' into issues/4636
yuancu Jan 29, 2026
78051d6
Unit test functions under validate folder
yuancu Jan 29, 2026
3443fae
Add unit tests for utilities and shuttles for validation
yuancu Jan 29, 2026
f70bc97
Improve unit test coverage on validation logics
yuancu Jan 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import org.junit.Before;
import org.junit.Test;
import org.opensearch.sql.api.UnifiedQueryTestBase;
import org.opensearch.sql.ppl.calcite.OpenSearchSparkSqlDialect;
import org.opensearch.sql.calcite.validate.OpenSearchSparkSqlDialect;

public class UnifiedQueryTranspilerTest extends UnifiedQueryTestBase {

Expand Down Expand Up @@ -43,11 +43,9 @@ public void testToSqlWithCustomDialect() {
UnifiedQueryTranspiler customTranspiler =
UnifiedQueryTranspiler.builder().dialect(OpenSearchSparkSqlDialect.DEFAULT).build();
String actualSql = customTranspiler.toSql(plan);
String expectedSql =
normalize(
"SELECT *\nFROM `catalog`.`employees`\nWHERE TRY_CAST(`name` AS DOUBLE) = 1.230E2");
String expectedSql = normalize("SELECT *\nFROM `catalog`.`employees`\nWHERE `name` = 123");
assertEquals(
"Transpiled query using OpenSearchSparkSqlDialect should translate SAFE_CAST to TRY_CAST",
"Numeric types can be implicitly coerced to string with OpenSearchSparkSqlDialect",
expectedSql,
actualSql);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,23 @@
import java.util.function.BiFunction;
import lombok.Getter;
import lombok.Setter;
import org.apache.calcite.config.NullCollation;
import org.apache.calcite.rex.RexCorrelVariable;
import org.apache.calcite.rex.RexLambdaRef;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.sql.validate.SqlValidator;
import org.apache.calcite.tools.FrameworkConfig;
import org.opensearch.sql.ast.analysis.FieldResolutionResult;
import org.opensearch.sql.ast.analysis.FieldResolutionVisitor;
import org.opensearch.sql.ast.expression.UnresolvedExpression;
import org.opensearch.sql.ast.tree.UnresolvedPlan;
import org.opensearch.sql.calcite.utils.CalciteToolsHelper;
import org.opensearch.sql.calcite.utils.CalciteToolsHelper.OpenSearchRelBuilder;
import org.opensearch.sql.calcite.validate.OpenSearchSparkSqlDialect;
import org.opensearch.sql.calcite.validate.PplTypeCoercion;
import org.opensearch.sql.calcite.validate.PplTypeCoercionRule;
import org.opensearch.sql.calcite.validate.PplValidator;
import org.opensearch.sql.calcite.validate.SqlOperatorTableProvider;
import org.opensearch.sql.common.setting.Settings;
import org.opensearch.sql.executor.QueryType;
import org.opensearch.sql.expression.function.FunctionProperties;
Expand Down Expand Up @@ -78,6 +85,14 @@ public class CalcitePlanContext {
/** Root node of the AST tree. Used for field resolution */
@Setter private UnresolvedPlan rootNode;

/**
* -- SETTER -- Sets the SQL operator table provider. This must be called during initialization by
* the opensearch module.
*
* @param provider the provider to use for obtaining operator tables
*/
@Setter private static SqlOperatorTableProvider operatorTableProvider;

private CalcitePlanContext(FrameworkConfig config, SysLimit sysLimit, QueryType queryType) {
this.config = config;
this.sysLimit = sysLimit;
Expand Down Expand Up @@ -107,6 +122,34 @@ private CalcitePlanContext(CalcitePlanContext parent) {
this.inLambdaContext = true; // Mark that we're inside a lambda
}

/**
* Creates a new SqlValidator instance. SqlValidator is stateful and should not be reused across
* validations, so a new instance is created for each call.
*
* @return new SqlValidator instance
*/
public SqlValidator getValidator() {
if (operatorTableProvider == null) {
throw new IllegalStateException(
"SqlOperatorTableProvider must be set before creating CalcitePlanContext");
}
SqlValidator.Config validatorConfig =
SqlValidator.Config.DEFAULT
.withTypeCoercionRules(PplTypeCoercionRule.instance())
.withTypeCoercionFactory(PplTypeCoercion::create)
// Use lenient conformance for PPL compatibility
.withConformance(OpenSearchSparkSqlDialect.DEFAULT.getConformance())
// Use Spark SQL's NULL collation (NULLs sorted LOW/FIRST)
.withDefaultNullCollation(NullCollation.LOW)
// This ensures that coerced arguments are replaced with cast version in sql
// select list because coercion is performed during select list expansion during
// sql validation. Affects 4356.yml
// See SqlValidatorImpl#validateSelectList and AggConverter#translateAgg
.withIdentifierExpansion(true);
return PplValidator.create(
config, operatorTableProvider.getOperatorTable(), TYPE_FACTORY, validatorConfig);
}

public RexNode resolveJoinCondition(
UnresolvedExpression expr,
BiFunction<UnresolvedExpression, CalcitePlanContext, RexNode> transformFunction) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,12 @@
import java.util.stream.IntStream;
import java.util.stream.Stream;
import lombok.AllArgsConstructor;
import lombok.NonNull;
import org.apache.calcite.adapter.enumerable.RexToLixTranslator;
import org.apache.calcite.plan.RelOptTable;
import org.apache.calcite.plan.ViewExpanders;
import org.apache.calcite.rel.RelCollation;
import org.apache.calcite.rel.RelFieldCollation;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.core.Aggregate;
import org.apache.calcite.rel.core.JoinRelType;
Expand All @@ -57,10 +60,14 @@
import org.apache.calcite.rex.RexBuilder;
import org.apache.calcite.rex.RexCall;
import org.apache.calcite.rex.RexCorrelVariable;
import org.apache.calcite.rex.RexFieldCollation;
import org.apache.calcite.rex.RexInputRef;
import org.apache.calcite.rex.RexLiteral;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.rex.RexOver;
import org.apache.calcite.rex.RexShuttle;
import org.apache.calcite.rex.RexVisitorImpl;
import org.apache.calcite.rex.RexWindow;
import org.apache.calcite.rex.RexWindowBounds;
import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.fun.SqlLibraryOperators;
Expand Down Expand Up @@ -740,8 +747,8 @@ public RelNode visitTranspose(
.map(
f ->
Map.entry(
ImmutableList.of(rx.makeLiteral(f)),
ImmutableList.of((RexNode) rx.makeCast(varchar, b.field(f), true))))
ImmutableList.of((RexLiteral) rx.makeLiteral(f, varchar, true)),
ImmutableList.of(rx.makeCast(varchar, b.field(f), true, true))))
.collect(Collectors.toList()));

// Step 3: Trim spaces from columnName column before pivot
Expand Down Expand Up @@ -1808,6 +1815,7 @@ public RelNode visitStreamWindow(StreamWindow node, CalcitePlanContext context)
// Default: first get rawExpr
List<RexNode> overExpressions =
node.getWindowFunctionList().stream().map(w -> rexVisitor.analyze(w, context)).toList();
overExpressions = embedExistingCollationsIntoOver(overExpressions, context);

if (hasGroup) {
// only build sequence when there is by condition
Expand Down Expand Up @@ -1849,6 +1857,84 @@ public RelNode visitStreamWindow(StreamWindow node, CalcitePlanContext context)
return context.relBuilder.peek();
}

/**
* Embed existing collation into window function's over clauses.
*
* <p>Window functions with frame specifications like {@code ROWS n PRECEDING} require ORDER BY to
* determine row order. Without it, results are non-deterministic.
*
* <p>Without this fix, the initial plan has ORDER BY separate from window functions:
*
* <pre>
* LogicalProject(SUM($5) OVER (ROWS 1 PRECEDING)) ← Missing ORDER BY
* LogicalSort(sort0=[$5])
* </pre>
*
* <p>This causes problems during validation as the order is not bound to the window. With this
* fix, sort collations are embeded into each {@code RexOver} window:
*
* <pre>
* LogicalProject(SUM($5) OVER (ORDER BY $5 ROWS 1 PRECEDING)) ← ORDER BY embedded
* </pre>
*
* @param overExpressions Window function expressions (may contain nested {@link RexOver})
* @param context Plan context for building RexNodes
* @return Expressions with ORDER BY embedded in all window specifications
*/
Comment on lines +1860 to +1883
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Search for existing Calcite window/collation/RexOver tests
rg -n --type=java "RexOver|window.*function|collation|StreamWindow" core/src/test --max-count=20

Repository: opensearch-project/sql

Length of output: 1951


🏁 Script executed:

# Search more broadly for test files related to Calcite
find core/src/test -name "*Calcite*Test.java" -o -name "*Window*Test.java" | head -20

Repository: opensearch-project/sql

Length of output: 1377


🏁 Script executed:

# Look for tests that might cover the embedding logic
rg -n "embed|collation" core/src/test --type=java

Repository: opensearch-project/sql

Length of output: 48


🏁 Script executed:

# Check CalciteRelNodeVisitorSearchSimpleTest.java for content
cat core/src/test/java/org/opensearch/sql/calcite/CalciteRelNodeVisitorSearchSimpleTest.java | head -100

Repository: opensearch-project/sql

Length of output: 3043


🏁 Script executed:

# Look for any test methods related to window functions or collation in Calcite test files
rg -n "void test|public void" core/src/test/java/org/opensearch/sql/calcite/ --type=java | grep -i "window\|collation\|over\|rexover" -i

Repository: opensearch-project/sql

Length of output: 48


🏁 Script executed:

# Search for the actual implementation: look at CalciteRelNodeVisitor around lines 1780-1803
sed -n '1770,1810p' core/src/main/java/org/opensearch/sql/calcite/CalciteRelNodeVisitor.java

Repository: opensearch-project/sql

Length of output: 1868


🏁 Script executed:

# Search for any existing tests that might cover the window/over logic in Calcite tests
rg -n "OVER|RexOver" core/src/test/java/org/opensearch/sql/calcite/ --type=java -A 2

Repository: opensearch-project/sql

Length of output: 48


🏁 Script executed:

# Search for tests that explicitly test embedExistingCollationsIntoOver
rg -n "embedExistingCollationsIntoOver" . --type=java

Repository: opensearch-project/sql

Length of output: 363


🏁 Script executed:

# Check if there are any Calcite-specific tests that cover window with collations
rg -n "window|collation|RexOver" core/src/test/java/org/opensearch/sql/calcite/CalciteRexNodeVisitorTest.java --type=java -A 3 | head -50

Repository: opensearch-project/sql

Length of output: 48


🏁 Script executed:

# Check the full test file to understand what's covered
wc -l core/src/test/java/org/opensearch/sql/calcite/CalciteRexNodeVisitorTest.java

Repository: opensearch-project/sql

Length of output: 146


🏁 Script executed:

# Look for any test that mentions window or over in method names
rg -n "@Test\s*\n\s*(public\s+)?void\s+\w*[Ww]indow|@Test\s*\n\s*(public\s+)?void\s+\w*[Oo]ver" core/src/test/java/org/opensearch/sql/calcite/ --type=java

Repository: opensearch-project/sql

Length of output: 257


🏁 Script executed:

# Read CalciteRexNodeVisitorTest.java to see what's tested
cat core/src/test/java/org/opensearch/sql/calcite/CalciteRexNodeVisitorTest.java

Repository: opensearch-project/sql

Length of output: 8794


🏁 Script executed:

# Search more carefully for any window tests that might be related
rg -n "test.*[Ww]indow" core/src/test/java/org/opensearch/sql/calcite/ --type=java

Repository: opensearch-project/sql

Length of output: 48


🏁 Script executed:

# Check if there are integration tests that test window functions with Calcite
find core/src/test -name "*IntegrationTest*.java" -o -name "*IT.java" | xargs grep -l "OVER\|window" 2>/dev/null

Repository: opensearch-project/sql

Length of output: 50


Add Calcite integration tests for window function collation embedding.

The new embedExistingCollationsIntoOver method (lines 1780–1803) handles critical window function semantics by injecting ORDER BY into OVER clauses. Per coding guidelines, all new business logic requires unit tests in the same commit. Add tests in the Calcite test suite (e.g., CalciteRexNodeVisitorTest.java or a new dedicated test class) covering:

  • Window functions with explicit ORDER BY
  • Window functions without explicit ORDER BY (triggering collation embedding)
  • Window functions with frame specifications (ROWS/RANGE clauses)
  • Multiple window functions in the same query
🤖 Prompt for AI Agents
In `@core/src/main/java/org/opensearch/sql/calcite/CalciteRelNodeVisitor.java`
around lines 1780 - 1803, Add unit tests exercising the new
embedExistingCollationsIntoOver logic in CalciteRelNodeVisitor by creating or
extending CalciteRexNodeVisitorTest (or a new test class) to cover: window
functions that already have ORDER BY, window functions lacking ORDER BY so that
embedExistingCollationsIntoOver injects collations into RexOver instances,
windows with ROWS/RANGE frame specifications, and queries with multiple window
functions; assert the produced RexOver or plan string contains the expected
ORDER BY clauses and that frame behavior remains unchanged, referencing the
method embedExistingCollationsIntoOver, the CalciteRelNodeVisitor class and
RexOver nodes to locate where to construct and validate test inputs.

private List<RexNode> embedExistingCollationsIntoOver(
Comment on lines +1861 to +1884
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this changes fix the issue #5004?

List<RexNode> overExpressions, CalcitePlanContext context) {
RelCollation existingCollation = context.relBuilder.peek().getTraitSet().getCollation();
List<@NonNull RelFieldCollation> relCollations =
existingCollation == null ? List.of() : existingCollation.getFieldCollations();
ImmutableList<@NonNull RexFieldCollation> rexCollations =
relCollations.stream()
.map(f -> relCollationToRexCollation(f, context.relBuilder))
.collect(ImmutableList.toImmutableList());
return overExpressions.stream()
.map(
n ->
n.accept(
new RexShuttle() {
@Override
public RexNode visitOver(RexOver over) {
RexWindow window = over.getWindow();
return context.rexBuilder.makeOver(
over.getType(),
over.getAggOperator(),
over.getOperands(),
window.partitionKeys,
rexCollations,
window.getLowerBound(),
window.getUpperBound(),
window.isRows(),
true,
false,
over.isDistinct(),
over.ignoreNulls());
Comment on lines +1899 to +1913
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's look at the full context around the visitOver method
cat -n core/src/main/java/org/opensearch/sql/calcite/CalciteRelNodeVisitor.java | sed -n '1880,1930p'

Repository: opensearch-project/sql

Length of output: 2823


🏁 Script executed:

# Search for where rexCollations is defined in the visitOver context
grep -n "rexCollations" core/src/main/java/org/opensearch/sql/calcite/CalciteRelNodeVisitor.java | head -20

Repository: opensearch-project/sql

Length of output: 180


🏁 Script executed:

# Look for the broader method context to understand how rexCollations is populated
ast-grep --pattern 'visitOver($$$) {
  $$$
}'

Repository: opensearch-project/sql

Length of output: 48


🏁 Script executed:

# Search for RexWindow.orderKeys references
rg "window\.orderKeys" core/src/main/java/org/opensearch/sql/calcite/

Repository: opensearch-project/sql

Length of output: 48


🏁 Script executed:

# Search for RexWindow creation or usage patterns
rg "RexWindow" core/src/main/java/org/opensearch/sql/calcite/ -A 3 -B 1

Repository: opensearch-project/sql

Length of output: 14719


🏁 Script executed:

# Look for similar patterns of window handling elsewhere in the file
rg "makeOver\(" core/src/main/java/org/opensearch/sql/calcite/ -B 5 -A 5

Repository: opensearch-project/sql

Length of output: 12259


🏁 Script executed:

# Check if there are any imports or usage of RexWindow that show field names
grep -n "import.*RexWindow\|RexWindow\." core/src/main/java/org/opensearch/sql/calcite/CalciteRelNodeVisitor.java

Repository: opensearch-project/sql

Length of output: 159


🏁 Script executed:

# Search for window.orderKeys usage in the codebase
rg "\.orderKeys" core/src/main/java/ -t java

Repository: opensearch-project/sql

Length of output: 48


🏁 Script executed:

# Search for window field access patterns
rg "window\.(partition|order|collation)" core/src/main/java/ -t java

Repository: opensearch-project/sql

Length of output: 188


🏁 Script executed:

# Check pom.xml for Calcite version
cat pom.xml | grep -A 2 "calcite" | head -20

Repository: opensearch-project/sql

Length of output: 105


🌐 Web query:

Apache Calcite RexWindow class API orderKeys field structure

💡 Result:

In org.apache.calcite.rex.RexWindow, the orderKeys field is:

  • public final ImmutableList<RexFieldCollation> orderKeys — an ordered list describing the window’s ORDER BY items. [1]

Each element (RexFieldCollation) is structured as:

  • RexFieldCollation extends Pair<RexNode, ImmutableSet<SqlKind>>
    • left (RexNode): the sort expression (e.g., an input ref, computed expression, etc.)
    • right (Set<SqlKind>): sort flags such as DESCENDING and null-direction flags (e.g., NULLS FIRST/LAST) [2]

Convenience accessors on RexFieldCollation interpret those flags as enums:

  • getDirection()RelFieldCollation.Direction (ASC/DESC)
  • getNullDirection()RelFieldCollation.NullDirection [2]

Sources: [1] [2]


🏁 Script executed:

# Search for RexBuilder.makeOver method to see its signature
rg "makeOver\(" --type java -B 2 -A 8 | head -100

Repository: opensearch-project/sql

Length of output: 10225


🏁 Script executed:

# Look for patterns where window.orderKeys is checked or used
find . -name "*.java" -type f | xargs grep -l "orderKeys" 2>/dev/null | head -10

Repository: opensearch-project/sql

Length of output: 322


🏁 Script executed:

# Search for the method name "embedExistingCollationsIntoOver" and surrounding context
rg "embedExistingCollationsIntoOver" -B 5 -A 30

Repository: opensearch-project/sql

Length of output: 8562


🌐 Web query:

RexBuilder.makeOver signature parameter orderKeys Calcite

💡 Result:

In Apache Calcite, RexBuilder.makeOver takes orderKeys after partitionKeys.

Signature (current javadocAggregate):
makeOver(RelDataType type, SqlAggFunction operator, List<RexNode> exprs, List<RexNode> partitionKeys, ImmutableList<RexFieldCollation> orderKeys, RexWindowBound lowerBound, RexWindowBound upperBound, RexWindowExclusion exclude, boolean rows, boolean allowPartial, boolean nullWhenCountZero, boolean distinct, boolean ignoreNulls) [1]

Older/shorter overload (no exclude, no ignoreNulls):
makeOver(RelDataType type, SqlAggFunction operator, List<RexNode> exprs, List<RexNode> partitionKeys, ImmutableList<RexFieldCollation> orderKeys, RexWindowBound lowerBound, RexWindowBound upperBound, boolean physical, boolean allowPartial, boolean nullWhenCountZero, boolean distinct) [2]

So the relevant order is: exprs, partitionKeys, orderKeys, then bounds/flags. [1][2]


Preserve existing window ORDER BY when embedding plan collations.

The embedExistingCollationsIntoOver method unconditionally replaces all RexOver order keys with the plan collation, dropping any explicit window ORDER BY. This can change query semantics. Only inject the plan collation when the window lacks existing order keys.

🛠️ Suggested fix
-                        return context.rexBuilder.makeOver(
+                        ImmutableList<@NonNull RexFieldCollation> orderKeys =
+                            window.orderKeys.isEmpty() ? rexCollations : window.orderKeys;
+                        return context.rexBuilder.makeOver(
                             over.getType(),
                             over.getAggOperator(),
                             over.getOperands(),
                             window.partitionKeys,
-                            rexCollations,
+                            orderKeys,
                             window.getLowerBound(),
                             window.getUpperBound(),
                             window.isRows(),
                             true,
                             false,
                             over.isDistinct(),
                             over.ignoreNulls());
🤖 Prompt for AI Agents
In `@core/src/main/java/org/opensearch/sql/calcite/CalciteRelNodeVisitor.java`
around lines 1899 - 1913, The current embedExistingCollationsIntoOver logic
always overwrites window ORDER BY keys (rexCollations) when building RexOver,
dropping any explicit window ordering; change the visitOver in
embedExistingCollationsIntoOver (the code that calls context.rexBuilder.makeOver
with rexCollations) to first inspect the RexWindow (window) for existing order
keys and only use the plan collation (rexCollations) when the window has no
explicit ordering, otherwise pass the window's existing order keys through
unchanged so explicit window ORDER BY is preserved.

}
}))
.collect(Collectors.toList());
}
Comment on lines +1860 to +1917
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for RexOver class definition and its methods
rg -n "class RexOver" --type=java

Repository: opensearch-project/sql

Length of output: 48


🏁 Script executed:

#!/bin/bash
# Search for makeOver method signature to understand all parameters
rg -n "makeOver\(" --type=java -A 10 | grep -E "makeOver|allowPartial|nullWhenCountZero" | head -50

Repository: opensearch-project/sql

Length of output: 1319


🏁 Script executed:

#!/bin/bash
# Check if RexOver has getters for allowPartial or isNullWhenCountZero
rg -n "getAllowPartial|getNullWhenCountZero|isAllowPartial|isNullWhenCountZero|allowPartial|nullWhenCountZero" --type=java | head -40

Repository: opensearch-project/sql

Length of output: 48


🏁 Script executed:

#!/bin/bash
# Look at the specific method implementation in CalciteRelNodeVisitor
sed -n '1860,1917p' core/src/main/java/org/opensearch/sql/calcite/CalciteRelNodeVisitor.java

Repository: opensearch-project/sql

Length of output: 2631


🏁 Script executed:

#!/bin/bash
# Check PlanUtils.makeOver method signature and implementation
sed -n '157,200p' core/src/main/java/org/opensearch/sql/calcite/utils/PlanUtils.java

Repository: opensearch-project/sql

Length of output: 2000


🏁 Script executed:

#!/bin/bash
# Compare other makeOver calls to see parameter patterns
rg -B2 -A12 "rexBuilder.makeOver|PlanUtils.makeOver" core/src/main/java/org/opensearch/sql/calcite/CalciteRelNodeVisitor.java | head -80

Repository: opensearch-project/sql

Length of output: 2920


🏁 Script executed:

#!/bin/bash
# Check ExtendedRelJson.java usage of makeOver (line 448) for reference
sed -n '440,465p' opensearch/src/main/java/org/opensearch/sql/opensearch/storage/serde/ExtendedRelJson.java

Repository: opensearch-project/sql

Length of output: 1011


🏁 Script executed:

#!/bin/bash
# Search for methods called on RexOver objects to see what properties are available
rg -n "over\." core/src/main/java/org/opensearch/sql/calcite/CalciteRelNodeVisitor.java | grep -E "over\.[a-zA-Z]+" | head -30

Repository: opensearch-project/sql

Length of output: 395


🏁 Script executed:

#!/bin/bash
# Check if there are imports or references to Calcite's RexOver methods
rg -n "isAllowPartial|isNullWhenCountZero|allowPartial|nullWhenCountZero" core/src/main/java/ opensearch/src/main/java/

Repository: opensearch-project/sql

Length of output: 48


🏁 Script executed:

#!/bin/bash
# Look at the Calcite version constraints to understand API compatibility
cat pom.xml | grep -A 5 -B 5 "calcite" | head -50

Repository: opensearch-project/sql

Length of output: 105


🏁 Script executed:

#!/bin/bash
# Check if there's any documentation or comments about makeOver parameters
rg -B5 "makeOver" core/src/main/java/org/opensearch/sql/calcite/utils/PlanUtils.java | grep -E "makeOver|//|/\*" | head -40

Repository: opensearch-project/sql

Length of output: 92


🏁 Script executed:

#!/bin/bash
# Search for allowPartial and nullWhenCountZero in the entire codebase
find . -name "*.java" -type f | xargs grep -l "allowPartial\|nullWhenCountZero" 2>/dev/null

Repository: opensearch-project/sql

Length of output: 50


🏁 Script executed:

#!/bin/bash
# Look at the actual Calcite RexBuilder.makeOver signatures in any comments or docs
rg -B20 "rexBuilder\.makeOver" core/src/main/java/org/opensearch/sql/calcite/CalciteRelNodeVisitor.java | grep -E "^[0-9]+-.*(\*|makeOver)" | head -40

Repository: opensearch-project/sql

Length of output: 48


🏁 Script executed:

#!/bin/bash
# Check if other usages in the codebase try to preserve these values from source RexOver
rg -B5 -A15 "makeOver.*over\." core/src/main/java/org/opensearch/sql/calcite/ | grep -E "over\.|makeOver|allowPartial|nullWhenCountZero" | head -50

Repository: opensearch-project/sql

Length of output: 48


🏁 Script executed:

#!/bin/bash
# Look for the full PlanUtils method to see the sumOver and countOver helper functions
sed -n '157,300p' core/src/main/java/org/opensearch/sql/calcite/utils/PlanUtils.java

Repository: opensearch-project/sql

Length of output: 5371


🏁 Script executed:

#!/bin/bash
# Search for helper methods that might extract these properties from RexOver
rg -n "getallowPartial|getNullWhenCountZero|getAllowPartial|isNullWhenCountZero" --type=java

Repository: opensearch-project/sql

Length of output: 48


🏁 Script executed:

#!/bin/bash
# Check if PlanUtils has a method that wraps makeOver calls with parameter handling
sed -n '200,250p' core/src/main/java/org/opensearch/sql/calcite/utils/PlanUtils.java

Repository: opensearch-project/sql

Length of output: 1577


🏁 Script executed:

#!/bin/bash
# Look for comments or documentation about the makeOver parameters, especially those two
rg -B10 "true.*false" core/src/main/java/org/opensearch/sql/calcite/CalciteRelNodeVisitor.java | grep -E "(1910|1911|true|false|allow|null|makeOver)" | head -30

Repository: opensearch-project/sql

Length of output: 546


🏁 Script executed:

#!/bin/bash
# Check what Calcite version this project uses - may help understand the API
find . -name "pom.xml" -type f | xargs grep -h "calcite" | head -20

Repository: opensearch-project/sql

Length of output: 48


🏁 Script executed:

#!/bin/bash
# Look at all pom.xml files in the repo to find Calcite version
find . -type f -name "pom.xml" | xargs grep -l "calcite" | head -5

Repository: opensearch-project/sql

Length of output: 48


🏁 Script executed:

#!/bin/bash
# Check the gradle files if any
find . -type f -name "build.gradle" -o -name "build.gradle.kts" | xargs grep -l "calcite" 2>/dev/null | head -5

Repository: opensearch-project/sql

Length of output: 175


🏁 Script executed:

#!/bin/bash
# Search for RexBuilder in imports to understand which Calcite version is used
rg -n "import.*RexBuilder|import.*RexOver" --type=java | head -20

Repository: opensearch-project/sql

Length of output: 2761


🏁 Script executed:

#!/bin/bash
# Look at the complete line context for the makeOver call to see if there are inline comments
sed -n '1895,1920p' core/src/main/java/org/opensearch/sql/calcite/CalciteRelNodeVisitor.java

Repository: opensearch-project/sql

Length of output: 1128


🏁 Script executed:

#!/bin/bash
# Check the core build.gradle for Calcite version
sed -n '1,100p' core/build.gradle | grep -i calcite

Repository: opensearch-project/sql

Length of output: 168


🏁 Script executed:

#!/bin/bash
# Look for any usage of RexStandardizer which imports RexOver
sed -n '1,150p' opensearch/src/main/java/org/opensearch/sql/opensearch/storage/serde/RexStandardizer.java | head -80

Repository: opensearch-project/sql

Length of output: 3279


🏁 Script executed:

#!/bin/bash
# Search for how RexOver is used elsewhere in the codebase to understand available methods
rg -A5 "visitOver|RexOver" core/src/main/java/org/opensearch/sql/calcite/ | grep -E "over\.|RexOver" | head -40

Repository: opensearch-project/sql

Length of output: 1447


🏁 Script executed:

#!/bin/bash
# Search for tests of embedExistingCollationsIntoOver
rg -n "embedExistingCollationsIntoOver" --type=java

Repository: opensearch-project/sql

Length of output: 359


🏁 Script executed:

#!/bin/bash
# Check what parameters RexBuilder.makeOver accepts in Calcite 1.41.0
# by looking at all makeOver invocations with their parameter counts
rg -A20 "\.makeOver\(" core/src/main/java/org/opensearch/sql/calcite/ | grep -E "makeOver\(|,\s*\)|over\.|window\." | head -60

Repository: opensearch-project/sql

Length of output: 2140


🏁 Script executed:

#!/bin/bash
# Look for any documentation or comments about what allowPartial and isNullWhenCountZero mean
rg -B5 -A5 "allowPartial|nullWhenCountZero" . --type=java 2>/dev/null | head -40

Repository: opensearch-project/sql

Length of output: 48


🏁 Script executed:

#!/bin/bash
# Check if RexOver provides any getter methods beyond what we've already seen
rg -n "over\.get|over\.is" core/src/main/java/org/opensearch/sql/calcite/CalciteRelNodeVisitor.java | grep -v "getWindow\|getType\|getAggOperator\|getOperands\|isDistinct\|ignoreNulls"

Repository: opensearch-project/sql

Length of output: 48


🏁 Script executed:

#!/bin/bash
# Look more carefully at the exact makeOver signature by checking line counts
sed -n '1901,1913p' core/src/main/java/org/opensearch/sql/calcite/CalciteRelNodeVisitor.java | cat -n

Repository: opensearch-project/sql

Length of output: 766


🏁 Script executed:

#!/bin/bash
# Check if there's an exclude parameter being used elsewhere
rg -n "RexWindowExclusion\|getExclude" --type=java | head -20

Repository: opensearch-project/sql

Length of output: 48


🏁 Script executed:

#!/bin/bash
# Count the parameters in the ExtendedRelJson makeOver call more carefully
sed -n '448,465p' opensearch/src/main/java/org/opensearch/sql/opensearch/storage/serde/ExtendedRelJson.java | cat -n

Repository: opensearch-project/sql

Length of output: 814


🏁 Script executed:

#!/bin/bash
# Search for documentation or comments about what parameters 9 and 10 represent
rg -B10 "true,\s*false," core/src/main/java/org/opensearch/sql/calcite/CalciteRelNodeVisitor.java | grep -E "^\s*\*|makeOver|true.*false" | head -20

Repository: opensearch-project/sql

Length of output: 48


🏁 Script executed:

#!/bin/bash
# The two makeOver calls have different signatures. Let's search for documentation or other usages
# to determine which is correct. Check if exclude is a newer addition
rg -n "exclude.*EXCLUDE" --type=java | head -20

Repository: opensearch-project/sql

Length of output: 218


🏁 Script executed:

#!/bin/bash
# Search for RexWindowExclusion usage
rg -n "RexWindowExclusion" --type=java

Repository: opensearch-project/sql

Length of output: 1408


🏁 Script executed:

#!/bin/bash
# Look at the imports in ExtendedRelJson to see if it uses exclude
head -50 opensearch/src/main/java/org/opensearch/sql/opensearch/storage/serde/ExtendedRelJson.java | grep -E "import|RexWindow"

Repository: opensearch-project/sql

Length of output: 1888


🏁 Script executed:

#!/bin/bash
# Count total parameters by looking at ExtendedRelJson more carefully with all makeOver calls
rg -B5 -A20 "rexBuilder.makeOver" opensearch/src/main/java/org/opensearch/sql/opensearch/storage/serde/ExtendedRelJson.java | head -80

Repository: opensearch-project/sql

Length of output: 1048


🏁 Script executed:

#!/bin/bash
# Check what RexWindow exposes - does it have getExclude or similar?
rg -n "window\." core/src/main/java/org/opensearch/sql/calcite/CalciteRelNodeVisitor.java | grep -E "window\.(get|is)" | head -20

Repository: opensearch-project/sql

Length of output: 229


🏁 Script executed:

#!/bin/bash
# Compare the parameter order and count: ExtendedRelJson has 13 params, CalciteRelNodeVisitor has 12
# Let's check if window has an exclude field
rg -n "getExclude\|exclusion" --type=java | head -20

Repository: opensearch-project/sql

Length of output: 48


🏁 Script executed:

#!/bin/bash
# Let's look at what the original RexOver contains - does it have exclude info?
rg -B5 -A10 "class RexWindow\|RexWindow(" opensearch/src/main/java/org/opensearch/sql/opensearch/storage/serde/ExtendedRelJson.java | head -40

Repository: opensearch-project/sql

Length of output: 170


🏁 Script executed:

#!/bin/bash
# Critical: Check if parameter mismatch - ExtendedRelJson has exclude param, CalciteRelNodeVisitor doesn't
# Let's see ALL the parameters being passed to understand the order
echo "=== CalciteRelNodeVisitor version ===" && sed -n '1901,1913p' core/src/main/java/org/opensearch/sql/calcite/CalciteRelNodeVisitor.java && echo "" && echo "=== ExtendedRelJson version ===" && sed -n '448,461p' opensearch/src/main/java/org/opensearch/sql/opensearch/storage/serde/ExtendedRelJson.java

Repository: opensearch-project/sql

Length of output: 1204


🏁 Script executed:

#!/bin/bash
# This is critical - the parameter lists don't match. Let's see if RexWindow has getExclude
rg -n "\.getExclude|Exclusion" core/src/main/java/org/opensearch/sql/calcite/CalciteRelNodeVisitor.java

Repository: opensearch-project/sql

Length of output: 223


🏁 Script executed:

#!/bin/bash
# Check if RexWindow exposes exclusion information
sed -n '1900,1915p' core/src/main/java/org/opensearch/sql/calcite/CalciteRelNodeVisitor.java | grep -E "window\."

Repository: opensearch-project/sql

Length of output: 264


🏁 Script executed:

#!/bin/bash
# Look for RexWindow.getExclude or similar in the whole codebase
rg -n "getExclude" --type=java

Repository: opensearch-project/sql

Length of output: 48


🏁 Script executed:

#!/bin/bash
# The signatures don't match - CalciteRelNodeVisitor is missing the exclude parameter
# Let's verify the exact Calcite RexBuilder.makeOver signature by checking javadoc or other hints
# Search for how many parameters makeOver should have in different contexts
rg -n "makeOver" --type=java -A1 | grep -E "makeOver.*\(" | wc -l

Repository: opensearch-project/sql

Length of output: 68


🏁 Script executed:

#!/bin/bash
# Search online Calcite documentation or check if there's version info about when exclude was added
# For now, let's verify whether window object has exclusion info
rg -n "window\.get|RexWindow\." core/src/main/java/org/opensearch/sql/calcite/CalciteRelNodeVisitor.java | head -20

Repository: opensearch-project/sql

Length of output: 179


🏁 Script executed:

#!/bin/bash
# Check if there are overloaded versions of makeOver in Calcite
# by looking at how it's being called in different ways
rg -B2 -A15 "rexBuilder\.makeOver" core/src/main/java/org/opensearch/sql/calcite/CalciteRelNodeVisitor.java | head -50

Repository: opensearch-project/sql

Length of output: 886


🏁 Script executed:

#!/bin/bash
# Check the git history to see if this code was recently added or if there's a comment about it
git log --oneline -20 core/src/main/java/org/opensearch/sql/calcite/CalciteRelNodeVisitor.java 2>/dev/null | head -20

Repository: opensearch-project/sql

Length of output: 134


🏁 Script executed:

#!/bin/bash
# Search for any TODO or FIXME comments near the embedExistingCollationsIntoOver method
sed -n '1850,1920p' core/src/main/java/org/opensearch/sql/calcite/CalciteRelNodeVisitor.java | grep -E "TODO|FIXME|BUG|XXX"

Repository: opensearch-project/sql

Length of output: 48


🏁 Script executed:

#!/bin/bash
# Check if the code compiles without errors by looking for any compilation markers
rg -n "PARAM_MISMATCH|parameter.*mismatch" --type=java

Repository: opensearch-project/sql

Length of output: 48


🏁 Script executed:

web_search
Apache Calcite 1.41.0 RexBuilder.makeOver method signature parameters

Repository: opensearch-project/sql

Length of output: 161


🏁 Script executed:

#!/bin/bash
# Let's try to use web_search properly with the capability
# For now, check if there are any recent changes or PRs related to this
git log --all --oneline --grep="makeOver\|window\|collation" 2>/dev/null | head -20

Repository: opensearch-project/sql

Length of output: 1400


🏁 Script executed:

#!/bin/bash
# Check if RexWindow has an exclusion getter that should be used
rg -n "RexWindow\|class.*Window" core/src/main/java/org/opensearch/sql/calcite/ --type=java | grep -v ".class" | head -20

Repository: opensearch-project/sql

Length of output: 48


🏁 Script executed:

#!/bin/bash
# Critical check: Does RexWindow from over.getWindow() have an exclusion?
# by checking what RexWindow methods are available
rg -n "over\.getWindow\(\)" core/src/main/java/org/opensearch/sql/calcite/CalciteRelNodeVisitor.java -A5 | head -30

Repository: opensearch-project/sql

Length of output: 409


🏁 Script executed:

#!/bin/bash
# Let's check if there's a variant of makeOver that takes fewer parameters
# by looking at how PlanUtils helper methods wrap it
sed -n '200,250p' core/src/main/java/org/opensearch/sql/calcite/utils/PlanUtils.java | grep -E "makeOver|relBuilder\."

Repository: opensearch-project/sql

Length of output: 395


🏁 Script executed:

#!/bin/bash
# Critical discovery: the parameter lists differ significantly
# ExtendedRelJson has 14 params, CalciteRelNodeVisitor has 12
# AND ExtendedRelJson includes RexWindowExclusion which is missing from CalciteRelNodeVisitor
# Let's verify if the code even compiles by checking for any build errors
find . -name "*.log" -o -name "*error*" 2>/dev/null | head -10

Repository: opensearch-project/sql

Length of output: 454


🏁 Script executed:

#!/bin/bash
# Let's check the git commit that added embedExistingCollationsIntoOver
git log --all -p --follow -- core/src/main/java/org/opensearch/sql/calcite/CalciteRelNodeVisitor.java | grep -B20 -A20 "embedExistingCollationsIntoOver" | head -100

Repository: opensearch-project/sql

Length of output: 152


🏁 Script executed:

#!/bin/bash
# Check if there's any test that validates this method works correctly
find . -name "*Test.java" -type f | xargs grep -l "embedExistingCollationsIntoOver\|window.*collation" 2>/dev/null

Repository: opensearch-project/sql

Length of output: 50


🏁 Script executed:

#!/antml:tool_start
```web_search
Calcite 1.41.0 RexBuilder makeOver signature documentation
```
</tool_start>

Repository: opensearch-project/sql

Length of output: 267


🏁 Script executed:

#!/bin/bash
# Let me check the actual failing scenario more carefully
# The parameter mismatch is concerning - let's see if CalciteRelNodeVisitor was recently modified
git log --all --oneline -5 -- core/src/main/java/org/opensearch/sql/calcite/CalciteRelNodeVisitor.java

Repository: opensearch-project/sql

Length of output: 367


🏁 Script executed:

#!/bin/bash
# Check the PR that added this method
git log --all -p -- core/src/main/java/org/opensearch/sql/calcite/CalciteRelNodeVisitor.java | grep -B50 "embedExistingCollationsIntoOver" | grep "^commit\|^Author\|^Date" | head -20

Repository: opensearch-project/sql

Length of output: 48


🏁 Script executed:

#!/bin/bash
# The critical issue: parameter 8 in CalciteRelNodeVisitor is window.isRows()
# but in ExtendedRelJson it's exclude (RexWindowExclusion)
# Let's verify what RexBuilder.makeOver actually expects
# by checking if window has an exclusion property
rg -n "getExclusion\|getExclude\|exclusion\|Exclusion" core/src/main/java/org/opensearch/sql/calcite/CalciteRelNodeVisitor.java

Repository: opensearch-project/sql

Length of output: 48


🏁 Script executed:

#!/antml:thinking_start
Looking at the parameter discrepancy:

CalciteRelNodeVisitor version (12 params):
1. type
2. operator  
3. operands
4. partitionKeys
5. orderKeys (as rexCollations)
6. lowerBound
7. upperBound
8. isRows (boolean)
9. true
10. false
11. distinct
12. ignoreNulls

ExtendedRelJson version (13 params):
1. type
2. operator
3. operands
4. partitionKeys
5. orderKeys (as orderKeys)
6. lowerBound
7. upperBound
8. exclude (RexWindowExclusion)
9. physical (boolean)
10. true
11. false
12. distinct
13. false

The issue is clear: CalciteRelNodeVisitor is missing the `exclude` parameter and has the parameter types wrong. Parameter 8 should be RexWindowExclusion, not isRows().

This looks like a genuine bug in the embedExistingCollationsIntoOver method - it's missing the exclude parameter which should come from window.getExclusion() or default to EXCLUDE_NO_OTHER.

Let me search for whether RexWindow has a getExclusion method.
</thinking_start>

Repository: opensearch-project/sql

Length of output: 241


🏁 Script executed:

#!/antml:thinking_end

Repository: opensearch-project/sql

Length of output: 48


🏁 Script executed:

#!/bin/bash
# Check if RexWindow class has getExclusion method by looking at how it's used elsewhere
rg -n "window\.getExclusion|RexWindow" core/src/main/java/org/opensearch/sql/calcite/ --type=java | grep -i "exclusion" | head -20

Repository: opensearch-project/sql

Length of output: 48


🏁 Script executed:

#!/antml:thinking_start
The user hasn't provided results yet. Let me think about what I know:

1. ExtendedRelJson passes a RexWindowExclusion parameter to makeOver
2. It gets this from window.get("exclude") or defaults to RexWindowExclusion.EXCLUDE_NO_OTHER
3. CalciteRelNodeVisitor doesn't pass any RexWindowExclusion parameter
4. The parameter at position 8 in CalciteRelNodeVisitor is window.isRows() which seems wrong

This is a real bug - the makeOver signature requires RexWindowExclusion but the code doesn't provide it. The question is whether window.getExclusion() exists or if we need to default to EXCLUDE_NO_OTHER like ExtendedRelJson does.
</thinking_end>

Repository: opensearch-project/sql

Length of output: 287


Fix missing RexWindowExclusion parameter in makeOver call.

The makeOver call is missing a required parameter. Comparing with ExtendedRelJson.java (line 448), the Calcite RexBuilder.makeOver signature expects RexWindowExclusion at parameter position 8, but the current code passes window.isRows() instead.

The correct order should be:

  • type
  • operator
  • operands
  • partitionKeys
  • orderKeys
  • lowerBound
  • upperBound
  • exclude (RexWindowExclusion) — currently missing
  • followed by remaining parameters

Add the missing exclude parameter (defaulting to RexWindowExclusion.EXCLUDE_NO_OTHER like in ExtendedRelJson):

return context.rexBuilder.makeOver(
    over.getType(),
    over.getAggOperator(),
    over.getOperands(),
    window.partitionKeys,
    rexCollations,
    window.getLowerBound(),
    window.getUpperBound(),
    RexWindowExclusion.EXCLUDE_NO_OTHER,  // Add this
    window.isRows(),
    true,
    false,
    over.isDistinct(),
    over.ignoreNulls());

The original concern about preserving allowPartial and isNullWhenCountZero from the source RexOver is not actionable—the RexOver API does not expose these properties as getters, making hardcoding acceptable.

🤖 Prompt for AI Agents
In `@core/src/main/java/org/opensearch/sql/calcite/CalciteRelNodeVisitor.java`
around lines 1860 - 1917, The makeOver call in embedExistingCollationsIntoOver
is missing the required RexWindowExclusion argument; update the call in method
embedExistingCollationsIntoOver (where context.rexBuilder.makeOver is invoked
for RexOver) to insert RexWindowExclusion.EXCLUDE_NO_OTHER between
window.getUpperBound() and window.isRows(), preserving the rest of the
parameters (type, agg operator, operands, partitionKeys,
orderKeys/rexCollations, lowerBound, upperBound, exclude, isRows,
allowPartial(true), isNullWhenCountZero(false), over.isDistinct(),
over.ignoreNulls()) so the parameter ordering matches RexBuilder.makeOver.


private static RexFieldCollation relCollationToRexCollation(
RelFieldCollation relCollation, RelBuilder builder) {
RexNode fieldRef = builder.field(relCollation.getFieldIndex());

// Convert direction flags to SqlKind set
Set<SqlKind> flags = new HashSet<>();
if (relCollation.direction == RelFieldCollation.Direction.DESCENDING
|| relCollation.direction == RelFieldCollation.Direction.STRICTLY_DESCENDING) {
flags.add(SqlKind.DESCENDING);
}
if (relCollation.nullDirection == RelFieldCollation.NullDirection.FIRST) {
flags.add(SqlKind.NULLS_FIRST);
} else if (relCollation.nullDirection == RelFieldCollation.NullDirection.LAST) {
flags.add(SqlKind.NULLS_LAST);
}

return new RexFieldCollation(fieldRef, flags);
}

private List<RexNode> wrapWindowFunctionsWithGroupNotNull(
List<RexNode> overExpressions, RexNode groupNotNull, CalcitePlanContext context) {
List<RexNode> wrappedOverExprs = new ArrayList<>(overExpressions.size());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -480,26 +480,8 @@ public RexNode visitWindowFunction(WindowFunction node, CalcitePlanContext conte
(arguments.isEmpty() || arguments.size() == 1)
? Collections.emptyList()
: arguments.subList(1, arguments.size());
List<RexNode> nodes =
PPLFuncImpTable.INSTANCE.validateAggFunctionSignature(
functionName, field, args, context.rexBuilder);
return nodes != null
? PlanUtils.makeOver(
context,
functionName,
nodes.getFirst(),
nodes.size() <= 1 ? Collections.emptyList() : nodes.subList(1, nodes.size()),
partitions,
List.of(),
node.getWindowFrame())
: PlanUtils.makeOver(
context,
functionName,
field,
args,
partitions,
List.of(),
node.getWindowFrame());
return PlanUtils.makeOver(
context, functionName, field, args, partitions, List.of(), node.getWindowFrame());
})
.orElseThrow(
() ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -290,13 +290,28 @@ private static RexNode getFieldsAsMap(
List<String> keys = excludeMetaFields(existingFields);
keys.removeAll(excluded);
Collections.sort(keys);
// Handle empty keys case - Calcite ARRAY[] requires at least 1 element
if (keys.isEmpty()) {
return createEmptyMap(context);
}
RexNode keysArray = getStringLiteralArray(keys, context);
List<RexNode> values =
keys.stream().map(key -> context.relBuilder.field(key)).collect(Collectors.toList());
keys.stream()
.map(key -> context.relBuilder.cast(context.relBuilder.field(key), SqlTypeName.VARCHAR))
.collect(Collectors.toList());
RexNode valuesArray = makeArray(values, context);
return context.rexBuilder.makeCall(BuiltinFunctionName.MAP_FROM_ARRAYS, keysArray, valuesArray);
Comment on lines +293 to 303
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cd /repo && find . -name "DynamicFieldsHelper.java" -type f

Repository: opensearch-project/sql

Length of output: 121


🏁 Script executed:

cd /repo && wc -l core/src/main/java/org/opensearch/sql/calcite/DynamicFieldsHelper.java

Repository: opensearch-project/sql

Length of output: 121


🏁 Script executed:

cd /repo && cat -n core/src/main/java/org/opensearch/sql/calcite/DynamicFieldsHelper.java | sed -n '280,330p'

Repository: opensearch-project/sql

Length of output: 121


🏁 Script executed:

cd /repo && cat -n core/src/main/java/org/opensearch/sql/calcite/DynamicFieldsHelper.java | sed -n '1,50p'

Repository: opensearch-project/sql

Length of output: 121


🏁 Script executed:

cd /repo && grep -n "createEmptyMap\|MAP_FROM_ARRAYS\|MAP_APPEND" core/src/main/java/org/opensearch/sql/calcite/DynamicFieldsHelper.java

Repository: opensearch-project/sql

Length of output: 121


🏁 Script executed:

cd /repo && find . -path "*/test*" -name "*DynamicFieldsHelperTest*" -type f

Repository: opensearch-project/sql

Length of output: 121


🏁 Script executed:

find . -name "DynamicFieldsHelper.java" -type f

Repository: opensearch-project/sql

Length of output: 138


🏁 Script executed:

wc -l ./core/src/main/java/org/opensearch/sql/calcite/DynamicFieldsHelper.java

Repository: opensearch-project/sql

Length of output: 142


🏁 Script executed:

cat -n ./core/src/main/java/org/opensearch/sql/calcite/DynamicFieldsHelper.java | sed -n '280,330p'

Repository: opensearch-project/sql

Length of output: 2638


🏁 Script executed:

cat -n ./core/src/main/java/org/opensearch/sql/calcite/DynamicFieldsHelper.java | sed -n '1,100p'

Repository: opensearch-project/sql

Length of output: 4829


🏁 Script executed:

grep -n "createEmptyMap\|MAP_FROM_ARRAYS\|MAP_APPEND" ./core/src/main/java/org/opensearch/sql/calcite/DynamicFieldsHelper.java

Repository: opensearch-project/sql

Length of output: 649


🏁 Script executed:

find . -path "*/test*" -name "*DynamicFieldsHelperTest*" -type f

Repository: opensearch-project/sql

Length of output: 48


🏁 Script executed:

find . -name "*DynamicFieldsHelper*" -type f

Repository: opensearch-project/sql

Length of output: 138


🏁 Script executed:

find . -path "*/test/*" -name "*DynamicFields*" -type f

Repository: opensearch-project/sql

Length of output: 157


🏁 Script executed:

grep -r "DynamicFieldsHelper" --include="*Test.java" | head -20

Repository: opensearch-project/sql

Length of output: 48


🏁 Script executed:

cat -n ./core/src/main/java/org/opensearch/sql/calcite/DynamicFieldsHelper.java | sed -n '160,250p'

Repository: opensearch-project/sql

Length of output: 4505


🏁 Script executed:

grep -n "MAP_FROM_ARRAYS\|MAP_APPEND" ./core/src/main/java/org/opensearch/sql/calcite/DynamicFieldsHelper.java -B 2 -A 2

Repository: opensearch-project/sql

Length of output: 1275


🏁 Script executed:

grep -r "getFieldsAsMap" --include="*.java"

Repository: opensearch-project/sql

Length of output: 676


🏁 Script executed:

find . -path "*/test/*" -name "*.java" -exec grep -l "createEmptyMap\|getFieldsAsMap" {} \;

Repository: opensearch-project/sql

Length of output: 48


🏁 Script executed:

find . -name "pom.xml" -type f | head -5

Repository: opensearch-project/sql

Length of output: 48


🏁 Script executed:

cat ./pom.xml | grep -A 5 "calcite"

Repository: opensearch-project/sql

Length of output: 107


🏁 Script executed:

find . -name "pom.xml" -type f -exec grep -l "calcite" {} \;

Repository: opensearch-project/sql

Length of output: 48


🏁 Script executed:

grep -r "MAP_APPEND\|MAP_FROM_ARRAYS" --include="*.java" | head -30

Repository: opensearch-project/sql

Length of output: 3167


🏁 Script executed:

find . -path "*/test/*" -name "*.java" -exec grep -l "MAP_APPEND\|MAP_FROM_ARRAYS" {} \;

Repository: opensearch-project/sql

Length of output: 155


🏁 Script executed:

grep -r "makeNullLiteral" --include="*.java" | head -20

Repository: opensearch-project/sql

Length of output: 3067


🏁 Script executed:

cat -n ./integ-test/src/test/java/org/opensearch/sql/calcite/standalone/MapAppendFunctionIT.java

Repository: opensearch-project/sql

Length of output: 6404


🏁 Script executed:

cat -n ./core/src/main/java/org/opensearch/sql/expression/function/CollectionUDF/MapAppendFunctionImpl.java

Repository: opensearch-project/sql

Length of output: 4823


🏁 Script executed:

find . -name "pom.xml" -type f | xargs grep -l "calcite" | head -1 | xargs cat | grep -A 5 "calcite-core\|calcite.version"

Repository: opensearch-project/sql

Length of output: 48


🏁 Script executed:

find . -name "*.xml" -type f | xargs grep -l "calcite" | head -1

Repository: opensearch-project/sql

Length of output: 48


🏁 Script executed:

find . -name "pom.xml" -type f -exec grep -l "properties\|version" {} \; | head -3

Repository: opensearch-project/sql

Length of output: 48


🏁 Script executed:

cat ./pom.xml 2>/dev/null || find . -maxdepth 2 -name "pom.xml" -type f -exec head -30 {} \;

Repository: opensearch-project/sql

Length of output: 48


🏁 Script executed:

ls -la | grep -i pom

Repository: opensearch-project/sql

Length of output: 48


🏁 Script executed:

find . -maxdepth 3 -name "pom.xml" -type f

Repository: opensearch-project/sql

Length of output: 48


🏁 Script executed:

grep -n "private static RexNode getFieldsAsMap\|private static RexNode createEmptyMap" core/src/main/java/org/opensearch/sql/calcite/DynamicFieldsHelper.java -A 1

Repository: opensearch-project/sql

Length of output: 325


🏁 Script executed:

find . -path "*/test/*" -name "*.java" -exec grep -l "getFieldsAsMap\|createEmptyMap" {} \;

Repository: opensearch-project/sql

Length of output: 48


🏁 Script executed:

grep -r "excludeMetaFields\|isMetadataField" core/src/main/java/org/opensearch/sql/calcite/DynamicFieldsHelper.java -B 2 -A 2

Repository: opensearch-project/sql

Length of output: 1003


🏁 Script executed:

find . -path "*/test/*" -name "*.java" | xargs grep -l "DynamicFieldsHelper" 2>/dev/null

Repository: opensearch-project/sql

Length of output: 50


🏁 Script executed:

grep -n "new DynamicFieldsHelper\|DynamicFieldsHelper\." core/src/main/java/org/opensearch/sql/calcite/*.java | head -20

Repository: opensearch-project/sql

Length of output: 2272


🏁 Script executed:

git log --oneline -20 -- core/src/main/java/org/opensearch/sql/calcite/DynamicFieldsHelper.java 2>/dev/null | head -10

Repository: opensearch-project/sql

Length of output: 134


🏁 Script executed:

git show HEAD:core/src/main/java/org/opensearch/sql/calcite/DynamicFieldsHelper.java 2>/dev/null | grep -n "createEmptyMap\|getFieldsAsMap" | head -5

Repository: opensearch-project/sql

Length of output: 450


🏁 Script executed:

cat -n core/src/main/java/org/opensearch/sql/calcite/DynamicFieldsHelper.java | sed -n '287,315p'

Repository: opensearch-project/sql

Length of output: 1606


Add unit tests for new helper functions per coding guidelines.

The coding guidelines require that new functions in core/src/main/java/**/*.java must have unit tests in the same commit. Both createEmptyMap() and getFieldsAsMap() are new private utilities and need test coverage.

Additionally, note that createEmptyMap() returns a NULL literal (not an actual empty map), which is semantically different from its JavaDoc comment "Create an empty map by casting NULL to MAP type". However, this is not a functional bug: MAP_APPEND has explicit null-handling logic (see MapAppendFunctionImpl.mapAppend() lines 69-81) that returns the non-null map when one argument is NULL, so the dynamic fields map will not be wiped out. The existing test suite confirms this behavior (MapAppendFunctionIT lines 87-113). Still, consider updating the comment for clarity.

🤖 Prompt for AI Agents
In `@core/src/main/java/org/opensearch/sql/calcite/DynamicFieldsHelper.java`
around lines 293 - 303, Add unit tests covering the two new private helpers
createEmptyMap() and getFieldsAsMap(): write tests that exercise
createEmptyMap() returning a NULL-cast-to-MAP literal and verify consumer
behavior (e.g., MapAppendFunctionImpl.mapAppend() preserves non-null maps when
one arg is NULL), and tests for getFieldsAsMap() producing the expected
MAP_FROM_ARRAYS RexNode for given keys; also update the JavaDoc on
createEmptyMap() to clarify it returns a NULL literal cast to MAP (not a
concrete empty map) so the comment matches behavior. Ensure tests live alongside
other core Java tests and reference the helper methods by name (createEmptyMap,
getFieldsAsMap) when locating/covering the code.

}

/** Create an empty map by casting NULL to MAP type */
private static RexNode createEmptyMap(CalcitePlanContext context) {
RelDataType varcharType =
context.rexBuilder.getTypeFactory().createSqlType(SqlTypeName.VARCHAR);
RelDataType mapType =
context.rexBuilder.getTypeFactory().createMapType(varcharType, varcharType);
return context.rexBuilder.makeNullLiteral(mapType);
}
Comment on lines +306 to +313
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add unit test coverage for empty-keys map creation.

A new helper function was added to core/src/main/java; please add a unit test that exercises the empty-keys path (and ensures _MAP is not unexpectedly NULL).

As per coding guidelines: New functions MUST have unit tests in the same commit.

🤖 Prompt for AI Agents
In `@core/src/main/java/org/opensearch/sql/calcite/DynamicFieldsHelper.java`
around lines 306 - 313, Add a unit test that exercises
DynamicFieldsHelper.createEmptyMap to cover the empty-keys map creation path and
assert the returned RexNode is a non-null NULL literal of MAP type (i.e., not an
unexpected Java null and its type is a MAP of VARCHAR->VARCHAR). Create a test
in the corresponding test class (e.g., DynamicFieldsHelperTest) that constructs
or mocks a CalcitePlanContext with a RexBuilder/TypeFactory (or reuse existing
test helpers), calls createEmptyMap(context), and asserts the result is not null
and result.getType().isMap() (and that the map key/value types are VARCHAR).
Ensure the new test method name clearly indicates it verifies empty-keys map
creation (e.g., testCreateEmptyMapReturnsMapNullLiteral).


/** Create string literal array from collection of strings */
private static RexNode getStringLiteralArray(
Collection<String> keys, CalcitePlanContext context) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,19 @@
import org.apache.calcite.rex.RexBuilder;
import org.apache.calcite.rex.RexLiteral;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.sql.SqlCallBinding;
import org.apache.calcite.sql.SqlIntervalQualifier;
import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.SqlOperator;
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
import org.apache.calcite.sql.parser.SqlParserPos;
import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.calcite.sql.type.SqlTypeUtil;
import org.apache.calcite.sql.validate.implicit.TypeCoercionImpl;
import org.opensearch.sql.ast.expression.SpanUnit;
import org.opensearch.sql.calcite.type.AbstractExprRelDataType;
import org.opensearch.sql.calcite.utils.OpenSearchTypeFactory;
import org.opensearch.sql.calcite.utils.OpenSearchTypeUtil;
import org.opensearch.sql.data.type.ExprCoreType;
import org.opensearch.sql.exception.ExpressionEvaluationException;
import org.opensearch.sql.exception.SemanticCheckException;
Expand Down Expand Up @@ -159,7 +164,7 @@ public RexNode makeCast(
// SqlStdOperatorTable.NOT_EQUALS,
// ImmutableList.of(exp, makeZeroLiteral(sourceType)));
}
} else if (OpenSearchTypeFactory.isUserDefinedType(type)) {
} else if (OpenSearchTypeUtil.isUserDefinedType(type)) {
if (RexLiteral.isNullLiteral(exp)) {
return super.makeCast(pos, type, exp, matchNullability, safe, format);
}
Expand Down Expand Up @@ -198,4 +203,33 @@ else if ((SqlTypeUtil.isApproximateNumeric(sourceType) || SqlTypeUtil.isDecimal(
}
return super.makeCast(pos, type, exp, matchNullability, safe, format);
}

/**
* Derives the return type of call to an operator.
*
* <p>In Calcite, coercion between STRING and NUMERIC operands takes place during converting SQL
* to RelNode. However, as we are building logical plans directly, the coercion is not yet
* implemented at this point. Hence, we duplicate {@link
* TypeCoercionImpl#binaryArithmeticWithStrings} here to infer the correct type, enabling
* operations like {@code "5" / 10}. The actual coercion will be inserted later when performing
* validation on SqlNode.
*
* @see TypeCoercionImpl#binaryArithmeticCoercion(SqlCallBinding)
* @param op the operator being called
* @param exprs actual operands
* @return derived type
*/
@Override
public RelDataType deriveReturnType(SqlOperator op, List<? extends RexNode> exprs) {
if (op.getKind().belongsTo(SqlKind.BINARY_ARITHMETIC) && exprs.size() == 2) {
final RelDataType type1 = exprs.get(0).getType();
final RelDataType type2 = exprs.get(1).getType();
if (SqlTypeUtil.isNumeric(type1) && OpenSearchTypeUtil.isCharacter(type2)) {
return type1;
} else if (OpenSearchTypeUtil.isCharacter(type1) && SqlTypeUtil.isNumeric(type2)) {
return type2;
}
}
return super.deriveReturnType(op, exprs);
}
}
Loading
Loading