|
31 | 31 | import org.apache.calcite.rex.RexLiteral;
|
32 | 32 | import org.apache.calcite.rex.RexNode;
|
33 | 33 | import org.apache.calcite.rex.RexWindowBounds;
|
| 34 | +import org.apache.calcite.sql.fun.SqlLibraryOperators; |
34 | 35 | import org.apache.calcite.sql.fun.SqlStdOperatorTable;
|
35 | 36 | import org.apache.calcite.tools.RelBuilder;
|
36 | 37 | import org.apache.calcite.tools.RelBuilder.AggCall;
|
|
42 | 43 | import org.opensearch.sql.ast.expression.Argument;
|
43 | 44 | import org.opensearch.sql.ast.expression.Field;
|
44 | 45 | import org.opensearch.sql.ast.expression.Let;
|
| 46 | +import org.opensearch.sql.ast.expression.Literal; |
| 47 | +import org.opensearch.sql.ast.expression.ParseMethod; |
45 | 48 | import org.opensearch.sql.ast.expression.UnresolvedExpression;
|
46 | 49 | import org.opensearch.sql.ast.expression.subquery.SubqueryExpression;
|
47 | 50 | import org.opensearch.sql.ast.tree.AD;
|
|
73 | 76 | import org.opensearch.sql.calcite.utils.JoinAndLookupUtils;
|
74 | 77 | import org.opensearch.sql.exception.CalciteUnsupportedException;
|
75 | 78 | import org.opensearch.sql.exception.SemanticCheckException;
|
| 79 | +import org.opensearch.sql.utils.ParseUtils; |
76 | 80 |
|
77 | 81 | public class CalciteRelNodeVisitor extends AbstractNodeVisitor<RelNode, CalcitePlanContext> {
|
78 | 82 |
|
@@ -244,69 +248,86 @@ public RelNode visitHead(Head node, CalcitePlanContext context) {
|
244 | 248 | }
|
245 | 249 |
|
246 | 250 | @Override
|
247 |
| - public RelNode visitEval(Eval node, CalcitePlanContext context) { |
| 251 | + public RelNode visitParse(Parse node, CalcitePlanContext context) { |
248 | 252 | visitChildren(node, context);
|
249 | 253 | List<String> originalFieldNames = context.relBuilder.peek().getRowType().getFieldNames();
|
250 |
| - List<RexNode> evalList = |
251 |
| - node.getExpressionList().stream() |
| 254 | + RexNode sourceField = rexVisitor.analyze(node.getSourceField(), context); |
| 255 | + ParseMethod parseMethod = node.getParseMethod(); |
| 256 | + java.util.Map<String, Literal> arguments = node.getArguments(); |
| 257 | + String pattern = (String) node.getPattern().getValue(); |
| 258 | + List<String> groupCandidates = |
| 259 | + ParseUtils.getNamedGroupCandidates(parseMethod, pattern, arguments); |
| 260 | + List<RexNode> newFields = |
| 261 | + groupCandidates.stream() |
252 | 262 | .map(
|
253 |
| - expr -> { |
254 |
| - boolean containsSubqueryExpression = containsSubqueryExpression(expr); |
255 |
| - final Holder<@Nullable RexCorrelVariable> v = Holder.empty(); |
256 |
| - if (containsSubqueryExpression) { |
257 |
| - context.relBuilder.variable(v::set); |
258 |
| - context.pushCorrelVar(v.get()); |
259 |
| - } |
260 |
| - RexNode eval = rexVisitor.analyze(expr, context); |
261 |
| - if (containsSubqueryExpression) { |
262 |
| - // RelBuilder.projectPlus doesn't have a parameter with variablesSet: |
263 |
| - // projectPlus(Iterable<CorrelationId> variablesSet, RexNode... nodes) |
264 |
| - context.relBuilder.project( |
265 |
| - Iterables.concat(context.relBuilder.fields(), ImmutableList.of(eval)), |
266 |
| - ImmutableList.of(), |
267 |
| - false, |
268 |
| - ImmutableList.of(v.get().id)); |
269 |
| - context.popCorrelVar(); |
270 |
| - } else { |
271 |
| - context.relBuilder.projectPlus(eval); |
272 |
| - } |
273 |
| - return eval; |
274 |
| - }) |
275 |
| - .collect(Collectors.toList()); |
276 |
| - // Overriding the existing field if the alias has the same name with original field name. For |
277 |
| - // example, eval field = 1 |
278 |
| - List<String> overriding = |
279 |
| - evalList.stream() |
280 |
| - .filter(expr -> expr.getKind() == AS) |
281 |
| - .map( |
282 |
| - expr -> |
283 |
| - ((RexLiteral) ((RexCall) expr).getOperands().get(1)).getValueAs(String.class)) |
284 |
| - .collect(Collectors.toList()); |
285 |
| - overriding.retainAll(originalFieldNames); |
286 |
| - if (!overriding.isEmpty()) { |
287 |
| - List<RexNode> toDrop = context.relBuilder.fields(overriding); |
288 |
| - context.relBuilder.projectExcept(toDrop); |
289 |
| - |
290 |
| - // the overriding field in Calcite will add a numeric suffix, for example: |
291 |
| - // `| eval SAL = SAL + 1` creates a field SAL0 to replace SAL, so we rename it back to SAL, |
292 |
| - // or query `| eval SAL=SAL + 1 | where exists [ source=DEPT | where emp.SAL=HISAL ]` fails. |
293 |
| - List<String> newNames = |
294 |
| - context.relBuilder.peek().getRowType().getFieldNames().stream() |
295 |
| - .map( |
296 |
| - cur -> { |
297 |
| - String noNumericSuffix = cur.replaceAll("\\d", ""); |
298 |
| - if (overriding.contains(noNumericSuffix)) { |
299 |
| - return noNumericSuffix; |
300 |
| - } else { |
301 |
| - return cur; |
302 |
| - } |
303 |
| - }) |
304 |
| - .toList(); |
305 |
| - context.relBuilder.rename(newNames); |
306 |
| - } |
| 263 | + group -> |
| 264 | + context.rexBuilder.makeCall( |
| 265 | + SqlLibraryOperators.REGEXP_EXTRACT, |
| 266 | + sourceField, |
| 267 | + context.rexBuilder.makeLiteral(pattern))) |
| 268 | + .toList(); |
| 269 | + projectPlusOverriding(newFields, groupCandidates, context); |
307 | 270 | return context.relBuilder.peek();
|
308 | 271 | }
|
309 | 272 |
|
| 273 | + @Override |
| 274 | + public RelNode visitEval(Eval node, CalcitePlanContext context) { |
| 275 | + visitChildren(node, context); |
| 276 | + List<String> originalFieldNames = context.relBuilder.peek().getRowType().getFieldNames(); |
| 277 | + node.getExpressionList() |
| 278 | + .forEach( |
| 279 | + expr -> { |
| 280 | + boolean containsSubqueryExpression = containsSubqueryExpression(expr); |
| 281 | + final Holder<@Nullable RexCorrelVariable> v = Holder.empty(); |
| 282 | + if (containsSubqueryExpression) { |
| 283 | + context.relBuilder.variable(v::set); |
| 284 | + context.pushCorrelVar(v.get()); |
| 285 | + } |
| 286 | + RexNode eval = rexVisitor.analyze(expr, context); |
| 287 | + if (containsSubqueryExpression) { |
| 288 | + // RelBuilder.projectPlus doesn't have a parameter with variablesSet: |
| 289 | + // projectPlus(Iterable<CorrelationId> variablesSet, RexNode... nodes) |
| 290 | + context.relBuilder.project( |
| 291 | + Iterables.concat(context.relBuilder.fields(), ImmutableList.of(eval)), |
| 292 | + ImmutableList.of(), |
| 293 | + false, |
| 294 | + ImmutableList.of(v.get().id)); |
| 295 | + context.popCorrelVar(); |
| 296 | + } else { |
| 297 | + // Overriding the existing field if the alias has the same name with original field. |
| 298 | + String alias = |
| 299 | + ((RexLiteral) ((RexCall) eval).getOperands().get(1)).getValueAs(String.class); |
| 300 | + projectPlusOverriding(List.of(eval), List.of(alias), context); |
| 301 | + } |
| 302 | + }); |
| 303 | + return context.relBuilder.peek(); |
| 304 | + } |
| 305 | + |
| 306 | + private void projectPlusOverriding( |
| 307 | + List<RexNode> newFields, List<String> newNames, CalcitePlanContext context) { |
| 308 | + List<String> originalFieldNames = context.relBuilder.peek().getRowType().getFieldNames(); |
| 309 | + List<RexNode> toOverrideList = |
| 310 | + originalFieldNames.stream() |
| 311 | + .filter(newNames::contains) |
| 312 | + .map(a -> (RexNode) context.relBuilder.field(a)) |
| 313 | + .toList(); |
| 314 | + // 1. add the new fields, For example "age0, country0" |
| 315 | + context.relBuilder.projectPlus(newFields); |
| 316 | + // 2. drop the overriding field list, it's duplicated now. For example "age, country" |
| 317 | + if (!toOverrideList.isEmpty()) { |
| 318 | + context.relBuilder.projectExcept(toOverrideList); |
| 319 | + } |
| 320 | + // 3. get current fields list, the "age0, country0" should include in it. |
| 321 | + List<String> currentFields = context.relBuilder.peek().getRowType().getFieldNames(); |
| 322 | + int length = currentFields.size(); |
| 323 | + // 4. add new names "age, country" to the end of rename list. |
| 324 | + List<String> expectedRenameFields = |
| 325 | + new ArrayList<>(currentFields.subList(0, length - newNames.size())); |
| 326 | + expectedRenameFields.addAll(newNames); |
| 327 | + // 5. rename |
| 328 | + context.relBuilder.rename(expectedRenameFields); |
| 329 | + } |
| 330 | + |
310 | 331 | @Override
|
311 | 332 | public RelNode visitAggregation(Aggregation node, CalcitePlanContext context) {
|
312 | 333 | visitChildren(node, context);
|
@@ -604,11 +625,6 @@ public RelNode visitFillNull(FillNull fillNull, CalcitePlanContext context) {
|
604 | 625 | throw new CalciteUnsupportedException("FillNull command is unsupported in Calcite");
|
605 | 626 | }
|
606 | 627 |
|
607 |
| - @Override |
608 |
| - public RelNode visitParse(Parse node, CalcitePlanContext context) { |
609 |
| - throw new CalciteUnsupportedException("Parse command is unsupported in Calcite"); |
610 |
| - } |
611 |
| - |
612 | 628 | @Override
|
613 | 629 | public RelNode visitRareTopN(RareTopN node, CalcitePlanContext context) {
|
614 | 630 | throw new CalciteUnsupportedException("Rare and Top commands are unsupported in Calcite");
|
|
0 commit comments