Copilot commented on code in PR #17168:
URL: https://github.com/apache/pinot/pull/17168#discussion_r2508289260
##########
pinot-query-planner/src/main/java/org/apache/pinot/query/planner/logical/RelToPlanNodeConverter.java:
##########
@@ -159,6 +169,254 @@ public PlanNode toPlanNode(RelNode node) {
return result;
}
+ private UnnestNode convertLogicalUncollect(Uncollect node) {
+ // Expect input provides a single array expression (typically a Project
with one expression)
+ RexExpression arrayExpr = null;
+ RelNode input = node.getInput();
+ if (input instanceof Project) {
+ Project p = (Project) input;
+ if (p.getProjects().size() == 1) {
+ arrayExpr = RexExpressionUtils.fromRexNode(p.getProjects().get(0));
+ }
+ }
+ if (arrayExpr == null) {
+ // Fallback: refer to first input ref
+ arrayExpr = new RexExpression.InputRef(0);
+ }
+ String columnAlias = null;
+ if (!node.getRowType().getFieldList().isEmpty()) {
+ columnAlias = node.getRowType().getFieldList().get(0).getName();
+ }
+ boolean withOrdinality = false;
+ String ordinalityAlias = null;
+ // Calcite Uncollect exposes withOrdinality via field names if present; if
>1 fields and last is ordinality
+ if (node.getRowType().getFieldList().size() > 1) {
+ withOrdinality = true;
+ ordinalityAlias = node.getRowType().getFieldList().get(1).getName();
+ }
+ return new UnnestNode(DEFAULT_STAGE_ID, toDataSchema(node.getRowType()),
NodeHint.EMPTY,
+ convertInputs(node.getInputs()), arrayExpr, columnAlias,
withOrdinality, ordinalityAlias);
+ }
+
+ private BasePlanNode convertLogicalCorrelate(LogicalCorrelate node) {
+ // Pattern: Correlate(left, Uncollect(Project(correlatedField)))
+ RelNode right = node.getRight();
+ RelDataType leftRowType = node.getLeft().getRowType();
+ Project correlatedProject = findProjectUnderUncollect(right);
+ RexExpression arrayExpr =
+ correlatedProject != null ? deriveArrayExpression(correlatedProject,
leftRowType) : null;
+ if (arrayExpr == null) {
+ arrayExpr = new RexExpression.InputRef(0);
+ }
+ LogicalFilter correlateFilter = findCorrelateFilter(right);
+ boolean wrapWithFilter = correlateFilter != null;
+ RexNode filterCondition = wrapWithFilter ? correlateFilter.getCondition()
: null;
+ // Use the entire correlate output schema
+ PlanNode inputNode = toPlanNode(node.getLeft());
+ // Ensure inputs list is mutable because downstream visitors (e.g.,
withInputs methods) may modify the inputs list
+ List<PlanNode> inputs = new ArrayList<>(List.of(inputNode));
+ ElementOrdinalInfo ordinalInfo = deriveElementOrdinalInfo(right,
leftRowType);
+ boolean withOrdinality = ordinalInfo.hasOrdinality();
+ String elementAlias = ordinalInfo.getElementAlias();
+ String ordinalityAlias = ordinalInfo.getOrdinalityAlias();
+ int elementIndex = ordinalInfo.getElementIndex();
+ int ordinalityIndex = ordinalInfo.getOrdinalityIndex();
+ UnnestNode unnest = new UnnestNode(DEFAULT_STAGE_ID,
toDataSchema(node.getRowType()), NodeHint.EMPTY,
+ inputs, arrayExpr, elementAlias, withOrdinality, ordinalityAlias,
elementIndex, ordinalityIndex);
+ if (wrapWithFilter) {
+ // Wrap Unnest with a FilterNode; rewrite filter InputRefs
(0:elem,1:idx) to absolute output indexes
+ RexExpression rewritten =
+ rewriteInputRefs(RexExpressionUtils.fromRexNode(filterCondition),
elementIndex, ordinalityIndex);
+ return new FilterNode(DEFAULT_STAGE_ID, toDataSchema(node.getRowType()),
NodeHint.EMPTY,
+ new ArrayList<>(List.of(unnest)), rewritten);
+ }
+ return unnest;
+ }
+
+ @Nullable
+ private static Project findProjectUnderUncollect(RelNode node) {
+ RelNode current = node;
+ while (current != null) {
+ if (current instanceof Uncollect) {
+ RelNode input = ((Uncollect) current).getInput();
+ return input instanceof Project ? (Project) input : null;
+ }
+ if (current instanceof Project) {
+ current = ((Project) current).getInput();
+ } else if (current instanceof LogicalFilter) {
+ current = ((LogicalFilter) current).getInput();
+ } else {
+ return null;
+ }
+ }
+ return null;
+ }
+
+ @Nullable
+ private static RexExpression deriveArrayExpression(Project project,
RelDataType leftRowType) {
+ if (project.getProjects().size() != 1) {
+ return null;
+ }
+ RexNode rex = project.getProjects().get(0);
+ Integer idx = resolveInputRefFromCorrel(rex, leftRowType);
+ if (idx != null) {
+ return new RexExpression.InputRef(idx);
+ }
+ RexExpression candidate = RexExpressionUtils.fromRexNode(rex);
+ return candidate instanceof RexExpression.InputRef ? candidate : new
RexExpression.InputRef(0);
+ }
+
+ @Nullable
+ private static LogicalFilter findCorrelateFilter(RelNode node) {
+ RelNode current = node;
+ while (current instanceof Project || current instanceof LogicalFilter) {
+ if (current instanceof LogicalFilter) {
+ return (LogicalFilter) current;
+ }
+ current = ((Project) current).getInput();
+ }
+ return null;
+ }
+
+ private static ElementOrdinalInfo deriveElementOrdinalInfo(RelNode right,
RelDataType leftRowType) {
+ ElementOrdinalAccumulator accumulator = new
ElementOrdinalAccumulator(leftRowType.getFieldCount());
+ if (right instanceof Uncollect) {
+ accumulator.populateFromRowType(right.getRowType());
+ } else if (right instanceof Project) {
+ accumulator.populateFromProject((Project) right);
+ } else if (right instanceof LogicalFilter) {
+ LogicalFilter filter = (LogicalFilter) right;
+ RelNode filterInput = filter.getInput();
+ if (filterInput instanceof Uncollect) {
+ accumulator.populateFromRowType(filter.getRowType());
+ } else if (filterInput instanceof Project) {
+ accumulator.populateFromProject((Project) filterInput);
+ }
+ }
+ return accumulator.toInfo();
+ }
+
+ private static final class ElementOrdinalAccumulator {
+ private final int _base;
+ private String _elementAlias;
+ private String _ordinalityAlias;
+ private int _elementIndex = -1;
+ private int _ordinalityIndex = -1;
+
+ ElementOrdinalAccumulator(int base) {
+ _base = base;
+ }
+
+ void populateFromRowType(RelDataType rowType) {
+ List<RelDataTypeField> fields = rowType.getFieldList();
+ if (!fields.isEmpty() && _elementIndex < 0) {
+ _elementAlias = fields.get(0).getName();
+ _elementIndex = _base;
+ }
+ if (fields.size() > 1 && _ordinalityIndex < 0) {
+ _ordinalityAlias = fields.get(1).getName();
+ _ordinalityIndex = _base + 1;
+ }
+ }
+
+ void populateFromProject(Project project) {
+ List<RexNode> projects = project.getProjects();
+ List<RelDataTypeField> projFields = project.getRowType().getFieldList();
+ for (int j = 0; j < projects.size(); j++) {
+ RexNode pj = projects.get(j);
+ if (pj instanceof RexInputRef) {
+ int idx = ((RexInputRef) pj).getIndex();
+ String outName = projFields.get(j).getName();
+ if (idx == 0 && _elementIndex < 0) {
+ _elementIndex = _base + j;
+ _elementAlias = outName;
+ } else if (idx == 1 && _ordinalityIndex < 0) {
+ _ordinalityIndex = _base + j;
+ _ordinalityAlias = outName;
+ }
+ }
+ }
+ }
+
+ ElementOrdinalInfo toInfo() {
+ return new ElementOrdinalInfo(_elementAlias, _ordinalityAlias,
_elementIndex, _ordinalityIndex);
+ }
+ }
+
+ private static final class ElementOrdinalInfo {
+ private final String _elementAlias;
+ private final String _ordinalityAlias;
+ private final int _elementIndex;
+ private final int _ordinalityIndex;
+
+ ElementOrdinalInfo(String elementAlias, String ordinalityAlias, int
elementIndex, int ordinalityIndex) {
+ _elementAlias = elementAlias;
+ _ordinalityAlias = ordinalityAlias;
+ _elementIndex = elementIndex;
+ _ordinalityIndex = ordinalityIndex;
+ }
+
+ String getElementAlias() {
+ return _elementAlias;
+ }
+
+ String getOrdinalityAlias() {
+ return _ordinalityAlias;
+ }
+
+ int getElementIndex() {
+ return _elementIndex;
+ }
+
+ int getOrdinalityIndex() {
+ return _ordinalityIndex;
+ }
+
+ boolean hasOrdinality() {
+ return _ordinalityIndex >= 0;
+ }
+ }
+
+ private static RexExpression rewriteInputRefs(RexExpression expr, int
elemOutIdx, int ordOutIdx) {
+ if (expr instanceof RexExpression.InputRef) {
+ int idx = ((RexExpression.InputRef) expr).getIndex();
+ if (idx == 0 && elemOutIdx >= 0) {
+ return new RexExpression.InputRef(elemOutIdx);
+ } else if (idx == 1 && ordOutIdx >= 0) {
+ return new RexExpression.InputRef(ordOutIdx);
+ } else {
+ return expr;
+ }
+ } else if (expr instanceof RexExpression.FunctionCall) {
+ RexExpression.FunctionCall fc = (RexExpression.FunctionCall) expr;
+ List<RexExpression> ops = fc.getFunctionOperands();
+ List<RexExpression> rewritten = new ArrayList<>(ops.size());
+ for (RexExpression op : ops) {
+ rewritten.add(rewriteInputRefs(op, elemOutIdx, ordOutIdx));
+ }
+ return new RexExpression.FunctionCall(fc.getDataType(),
fc.getFunctionName(), rewritten);
+ } else {
+ return expr;
+ }
+ }
+
+ private static Integer resolveInputRefFromCorrel(RexNode expr, RelDataType
leftRowType) {
+ if (expr instanceof RexFieldAccess) {
+ RexFieldAccess fieldAccess = (RexFieldAccess) expr;
+ if (fieldAccess.getReferenceExpr() instanceof RexCorrelVariable) {
+ String fieldName = fieldAccess.getField().getName();
+ List<RelDataTypeField> fields = leftRowType.getFieldList();
+ // SQL field names are case-insensitive, so we must use
equalsIgnoreCase for correct matching.
+ for (int i = 0; i < fields.size(); i++) {
Review Comment:
[nitpick] The comment correctly explains the reason for using
equalsIgnoreCase, but it should be moved to immediately precede the
equalsIgnoreCase call on line 411 for better code-to-comment proximity.
```suggestion
for (int i = 0; i < fields.size(); i++) {
// SQL field names are case-insensitive, so we must use
equalsIgnoreCase for correct matching.
```
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]