|
10 | 10 | import static org.opensearch.sql.ast.dsl.AstDSL.join; |
11 | 11 | import static org.opensearch.sql.ast.dsl.AstDSL.union; |
12 | 12 |
|
| 13 | +import java.util.ArrayList; |
13 | 14 | import java.util.List; |
14 | 15 | import java.util.Optional; |
15 | 16 | import java.util.stream.Collectors; |
16 | 17 | import org.antlr.v4.runtime.tree.ParseTree; |
| 18 | +import org.opensearch.sql.ast.expression.Field; |
17 | 19 | import org.opensearch.sql.ast.expression.Not; |
18 | 20 | import org.opensearch.sql.ast.expression.UnresolvedExpression; |
| 21 | +import org.opensearch.sql.ast.expression.WindowFunction; |
19 | 22 | import org.opensearch.sql.ast.statement.Query; |
20 | 23 | import org.opensearch.sql.ast.statement.Statement; |
21 | 24 | import org.opensearch.sql.ast.tree.Join.JoinType; |
| 25 | +import org.opensearch.sql.ast.tree.Project; |
| 26 | +import org.opensearch.sql.ast.tree.Sort; |
| 27 | +import org.opensearch.sql.ast.tree.Sort.SortOption; |
22 | 28 | import org.opensearch.sql.ast.tree.UnresolvedPlan; |
23 | 29 | import org.opensearch.sql.sql.antlr.SQLSyntaxParser; |
24 | 30 | import org.opensearch.sql.sql.antlr.parser.OpenSearchSQLParser; |
25 | 31 | import org.opensearch.sql.sql.antlr.parser.OpenSearchSQLParser.ExistsSubqueryExpressionAtomContext; |
| 32 | +import org.opensearch.sql.sql.antlr.parser.OpenSearchSQLParser.FromClauseContext; |
26 | 33 | import org.opensearch.sql.sql.antlr.parser.OpenSearchSQLParser.InSubqueryPredicateContext; |
27 | 34 | import org.opensearch.sql.sql.antlr.parser.OpenSearchSQLParser.JoinClauseContext; |
| 35 | +import org.opensearch.sql.sql.antlr.parser.OpenSearchSQLParser.OrderByClauseContext; |
| 36 | +import org.opensearch.sql.sql.antlr.parser.OpenSearchSQLParser.QuerySpecificationContext; |
28 | 37 | import org.opensearch.sql.sql.antlr.parser.OpenSearchSQLParser.UnionSelectContext; |
29 | 38 | import org.opensearch.sql.sql.parser.AstBuilder; |
30 | 39 | import org.opensearch.sql.sql.parser.AstExpressionBuilder; |
| 40 | +import org.opensearch.sql.sql.parser.AstSortBuilder; |
31 | 41 | import org.opensearch.sql.sql.parser.AstStatementBuilder; |
| 42 | +import org.opensearch.sql.sql.parser.context.QuerySpecification; |
32 | 43 |
|
33 | 44 | /** SQL query parser that produces {@link UnresolvedPlan} using the V2 ANTLR grammar. */ |
34 | 45 | public class SqlV2QueryParser implements UnifiedQueryParser<UnresolvedPlan> { |
@@ -62,6 +73,53 @@ private static class ExtendedAstBuilder extends AstBuilder { |
62 | 73 | super(query); |
63 | 74 | } |
64 | 75 |
|
| 76 | + @Override |
| 77 | + public UnresolvedPlan visitQuerySpecification(QuerySpecificationContext queryContext) { |
| 78 | + if (!hasWindowFunctionInProjectList(queryContext)) { |
| 79 | + return super.visitQuerySpecification(queryContext); |
| 80 | + } |
| 81 | + |
| 82 | + context.push(); |
| 83 | + context.peek().collect(queryContext, query); |
| 84 | + Project project = (Project) visit(queryContext.selectClause()); |
| 85 | + UnresolvedPlan result = project.attach(visit(queryContext.fromClause())); |
| 86 | + |
| 87 | + // Window output must be computed before ORDER BY/LIMIT, so build Limit(Sort(Project(from))) |
| 88 | + OrderByClauseContext orderByClause = queryContext.fromClause().orderByClause(); |
| 89 | + if (orderByClause != null) { |
| 90 | + result = new ExtendedAstSortBuilder(context.peek()).visit(orderByClause).attach(result); |
| 91 | + } |
| 92 | + if (queryContext.limitClause() != null) { |
| 93 | + result = visit(queryContext.limitClause()).attach(result); |
| 94 | + } |
| 95 | + |
| 96 | + context.pop(); |
| 97 | + return result; |
| 98 | + } |
| 99 | + |
| 100 | + @Override |
| 101 | + public UnresolvedPlan visitFromClause(FromClauseContext ctx) { |
| 102 | + UnresolvedPlan from = super.visitFromClause(ctx); |
| 103 | + if (hasWindowFunctionInProjectList(context.peek()) && from instanceof Sort sort) { |
| 104 | + // Drop the ORDER BY Sort for window queries; it is re-attached above the Project |
| 105 | + return sort.getChild().get(0); |
| 106 | + } |
| 107 | + return from; |
| 108 | + } |
| 109 | + |
| 110 | + private boolean hasWindowFunctionInProjectList(QuerySpecificationContext queryContext) { |
| 111 | + if (queryContext.fromClause() == null) { |
| 112 | + return false; |
| 113 | + } |
| 114 | + QuerySpecification probe = new QuerySpecification(); |
| 115 | + probe.collect(queryContext, query); |
| 116 | + return hasWindowFunctionInProjectList(probe); |
| 117 | + } |
| 118 | + |
| 119 | + private static boolean hasWindowFunctionInProjectList(QuerySpecification querySpec) { |
| 120 | + return querySpec.getSelectItems().stream().anyMatch(item -> item instanceof WindowFunction); |
| 121 | + } |
| 122 | + |
65 | 123 | @Override |
66 | 124 | protected AstExpressionBuilder createExpressionBuilder() { |
67 | 125 | return new ExtendedAstExpressionBuilder(); |
@@ -114,4 +172,32 @@ public UnresolvedExpression visitExistsSubqueryExpressionAtom( |
114 | 172 | } |
115 | 173 | } |
116 | 174 | } |
| 175 | + |
| 176 | + /** |
| 177 | + * Keeps an ORDER BY window-alias as a column reference (Sort is above the Project) to avoid a |
| 178 | + * second RexOver. |
| 179 | + */ |
| 180 | + private static class ExtendedAstSortBuilder extends AstSortBuilder { |
| 181 | + |
| 182 | + ExtendedAstSortBuilder(QuerySpecification querySpec) { |
| 183 | + super(querySpec); |
| 184 | + } |
| 185 | + |
| 186 | + @Override |
| 187 | + public UnresolvedPlan visitOrderByClause(OrderByClauseContext ctx) { |
| 188 | + List<Field> fields = new ArrayList<>(); |
| 189 | + List<UnresolvedExpression> items = querySpec.getOrderByItems(); |
| 190 | + List<SortOption> options = querySpec.getOrderByOptions(); |
| 191 | + for (int i = 0; i < items.size(); i++) { |
| 192 | + UnresolvedExpression item = items.get(i); |
| 193 | + UnresolvedExpression sortKey = |
| 194 | + (querySpec.isSelectAlias(item) |
| 195 | + && querySpec.getSelectItemByAlias(item) instanceof WindowFunction) |
| 196 | + ? item |
| 197 | + : querySpec.replaceIfAliasOrOrdinal(item); |
| 198 | + fields.add(new Field(sortKey, createSortArguments(options.get(i)))); |
| 199 | + } |
| 200 | + return new Sort(fields); |
| 201 | + } |
| 202 | + } |
117 | 203 | } |
0 commit comments