|
181 | 181 | import org.opensearch.sql.calcite.plan.rel.LogicalSystemLimit; |
182 | 182 | import org.opensearch.sql.calcite.plan.rel.LogicalSystemLimit.SystemLimitType; |
183 | 183 | import org.opensearch.sql.calcite.utils.BinUtils; |
| 184 | +import org.opensearch.sql.calcite.utils.FlatObjectScope; |
184 | 185 | import org.opensearch.sql.calcite.utils.JoinAndLookupUtils; |
185 | 186 | import org.opensearch.sql.calcite.utils.OpenSearchTypeFactory; |
186 | 187 | import org.opensearch.sql.calcite.utils.PPLHintUtils; |
@@ -240,15 +241,18 @@ public RelNode analyze(UnresolvedPlan unresolved, CalcitePlanContext context) { |
240 | 241 | RelNode plan = unresolved.accept(this, context); |
241 | 242 | // Here rather than in QueryService.analyze, so the unified query pipeline -- which calls this |
242 | 243 | // method directly -- is held to the same rules. |
243 | | - FlatObjectScopeValidator.validate(plan, context.getFlatObjectFields()); |
244 | 244 | return plan; |
245 | 245 | } |
246 | 246 |
|
247 | 247 | @Override |
248 | 248 | public RelNode visitChildren(Node node, CalcitePlanContext context) { |
249 | 249 | RelNode result = super.visitChildren(node, context); |
250 | 250 | if (node instanceof UnresolvedPlan plan) { |
251 | | - mapPathMaterializer.materializePaths(plan, context); |
| 251 | + // Materializing a dotted path is a read, so it is allowed -- and it has to be, because the |
| 252 | + // materializer wraps field resolution in catch (RuntimeException | AssertionError), which |
| 253 | + // would swallow a refusal raised there and leave the command quietly ignoring the field. |
| 254 | + // What the command then does with the materialized column is checked where it resolves it. |
| 255 | + FlatObjectScope.runAsRead(context, () -> mapPathMaterializer.materializePaths(plan, context)); |
252 | 256 | } |
253 | 257 | return result; |
254 | 258 | } |
@@ -369,7 +373,9 @@ public RelNode visitFilter(Filter node, CalcitePlanContext context) { |
369 | 373 | context.relBuilder.variable(v::set); |
370 | 374 | context.pushCorrelVar(v.get()); |
371 | 375 | } |
372 | | - RexNode condition = rexVisitor.analyze(node.getCondition(), context); |
| 376 | + RexNode condition = |
| 377 | + FlatObjectScope.as( |
| 378 | + "filter by", context, () -> rexVisitor.analyze(node.getCondition(), context)); |
373 | 379 | if (containsSubqueryExpression) { |
374 | 380 | context.relBuilder.filter(ImmutableList.of(v.get().id), condition); |
375 | 381 | context.popCorrelVar(); |
@@ -612,7 +618,9 @@ private List<RexNode> expandProjectFields( |
612 | 618 | } |
613 | 619 | matchingFields.forEach(f -> expandedFields.add(context.relBuilder.field(f))); |
614 | 620 | } else if (addedFields.add(fieldName)) { |
615 | | - RexNode resolved = rexVisitor.analyze(field, context); |
| 621 | + RexNode resolved = |
| 622 | + FlatObjectScope.allowRead( |
| 623 | + field, fieldName, context, () -> rexVisitor.analyze(field, context)); |
616 | 624 | /* |
617 | 625 | * Dotted path access is resolved into ITEM(map, path) function call without aliasing. |
618 | 626 | * Re-apply the alias so the projected column retains the user-visible name. |
@@ -801,6 +809,7 @@ public RelNode visitRename(Rename node, CalcitePlanContext context) { |
801 | 809 | String newName = |
802 | 810 | WildcardRenameUtils.applyWildcardTransformation( |
803 | 811 | sourcePattern, targetPattern, fieldName); |
| 812 | + FlatObjectScope.trackRename(fieldName, newName, context); |
804 | 813 | if (newNames.contains(newName) && !newName.equals(fieldName)) { |
805 | 814 | removeFieldIfExists(newName, newNames, context); |
806 | 815 | } |
@@ -829,23 +838,27 @@ private void removeFieldIfExists( |
829 | 838 | public RelNode visitSort(org.opensearch.sql.ast.tree.Sort node, CalcitePlanContext context) { |
830 | 839 | visitChildren(node, context); |
831 | 840 | List<RexNode> sortList = |
832 | | - node.getSortList().stream() |
833 | | - .map( |
834 | | - expr -> { |
835 | | - RexNode sortField = rexVisitor.analyze(expr, context); |
836 | | - SortOption sortOption = analyzeSortOption(expr.getFieldArgs()); |
837 | | - // Default is ASC |
838 | | - if (sortOption.getSortOrder() == DESC) { |
839 | | - sortField = context.relBuilder.desc(sortField); |
840 | | - } |
841 | | - if (sortOption.getNullOrder() == NULL_LAST) { |
842 | | - sortField = context.relBuilder.nullsLast(sortField); |
843 | | - } else { |
844 | | - sortField = context.relBuilder.nullsFirst(sortField); |
845 | | - } |
846 | | - return sortField; |
847 | | - }) |
848 | | - .collect(Collectors.toList()); |
| 841 | + FlatObjectScope.as( |
| 842 | + "sort by", |
| 843 | + context, |
| 844 | + () -> |
| 845 | + node.getSortList().stream() |
| 846 | + .map( |
| 847 | + expr -> { |
| 848 | + RexNode sortField = rexVisitor.analyze(expr, context); |
| 849 | + SortOption sortOption = analyzeSortOption(expr.getFieldArgs()); |
| 850 | + // Default is ASC |
| 851 | + if (sortOption.getSortOrder() == DESC) { |
| 852 | + sortField = context.relBuilder.desc(sortField); |
| 853 | + } |
| 854 | + if (sortOption.getNullOrder() == NULL_LAST) { |
| 855 | + sortField = context.relBuilder.nullsLast(sortField); |
| 856 | + } else { |
| 857 | + sortField = context.relBuilder.nullsFirst(sortField); |
| 858 | + } |
| 859 | + return sortField; |
| 860 | + }) |
| 861 | + .collect(Collectors.toList())); |
849 | 862 | context.relBuilder.sort(sortList); |
850 | 863 | // Apply count parameter as limit |
851 | 864 | if (node.getCount() != 0) { |
@@ -1292,7 +1305,12 @@ public RelNode visitEval(Eval node, CalcitePlanContext context) { |
1292 | 1305 | context.relBuilder.variable(v::set); |
1293 | 1306 | context.pushCorrelVar(v.get()); |
1294 | 1307 | } |
1295 | | - RexNode eval = rexVisitor.analyze(expr, context); |
| 1308 | + RexNode eval = |
| 1309 | + FlatObjectScope.allowRead( |
| 1310 | + expr, |
| 1311 | + expr.getVar().getField().toString(), |
| 1312 | + context, |
| 1313 | + () -> rexVisitor.analyze(expr, context)); |
1296 | 1314 | if (containsSubqueryExpression) { |
1297 | 1315 | // RelBuilder.projectPlus doesn't have a parameter with variablesSet: |
1298 | 1316 | // projectPlus(Iterable<CorrelationId> variablesSet, RexNode... nodes) |
@@ -1802,7 +1820,8 @@ public RelNode visitAggregation(Aggregation node, CalcitePlanContext context) { |
1802 | 1820 | if (!bucketNullable) { |
1803 | 1821 | nonNullGroupMask.set(0, nGroup); |
1804 | 1822 | } |
1805 | | - visitAggregation(node, context, nonNullGroupMask, true, false); |
| 1823 | + FlatObjectScope.runAs( |
| 1824 | + "stats by", context, () -> visitAggregation(node, context, nonNullGroupMask, true, false)); |
1806 | 1825 | return context.relBuilder.peek(); |
1807 | 1826 | } |
1808 | 1827 |
|
@@ -2273,6 +2292,7 @@ public RelNode visitLookup(Lookup node, CalcitePlanContext context) { |
2273 | 2292 |
|
2274 | 2293 | @Override |
2275 | 2294 | public RelNode visitDedupe(Dedupe node, CalcitePlanContext context) { |
| 2295 | + context.setFlatObjectUse("dedup by"); |
2276 | 2296 | visitChildren(node, context); |
2277 | 2297 | List<Argument> options = node.getOptions(); |
2278 | 2298 | Integer allowedDuplication = (Integer) options.get(0).getValue().getValue(); |
@@ -4484,7 +4504,10 @@ public RelNode visitExpand(Expand expand, CalcitePlanContext context) { |
4484 | 4504 |
|
4485 | 4505 | // 2. Get the field to expand and an optional alias. |
4486 | 4506 | Field arrayField = expand.getField(); |
4487 | | - RexNode resolved = rexVisitor.analyze(arrayField, context); |
| 4507 | + // Resolve as a read: the check below already refuses a flat_object leaf, by the same rule it |
| 4508 | + // uses for an object leaf or an expression, and says so in expand's own words. |
| 4509 | + RexNode resolved = |
| 4510 | + FlatObjectScope.allowReadOf(context, () -> rexVisitor.analyze(arrayField, context)); |
4488 | 4511 | if (!(resolved instanceof RexInputRef arrayFieldRex)) { |
4489 | 4512 | // Anything that is not a column of the input -- a leaf of an object or a flat_object field, |
4490 | 4513 | // an expression -- has no column to correlate the expansion with. Raised the way the |
|
0 commit comments