You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@asterixdb.apache.org by AsterixDB Code Review <do...@asterix-gerrit.ics.uci.edu> on 2026/08/26 07:15:12 UTC

[M] Change in asterixdb[master]: [ASTERIXDB-3785][COMP] Allow SELECT of the whole CLUSTER BY descriptor

From Shahrzad Shirazi <sh...@ucr.edu>:

Shahrzad Shirazi has uploaded this change for review. ( https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/21616?usp=email )


Change subject: [ASTERIXDB-3785][COMP] Allow SELECT of the whole CLUSTER BY descriptor
......................................................................

[ASTERIXDB-3785][COMP] Allow SELECT of the whole CLUSTER BY descriptor

- user model changes: no
- storage format changes: no
- interface changes: no

Co-Authored-By: Claude <no...@anthropic.com>

Ext-ref: MB-72552
Change-Id: Ie53dca75c8fa68a1bc1b7fdd71c96f477f683917
---
M asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/cluster-by/ClusterByQueries.xml
C asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/cluster-by/kmeans-descriptor/kmeans-descriptor.1.ddl.sqlpp
C asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/cluster-by/kmeans-descriptor/kmeans-descriptor.2.update.sqlpp
R asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/cluster-by/kmeans-descriptor/kmeans-descriptor.3.query.sqlpp
C asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/cluster-by/kmeans-descriptor/kmeans-descriptor.4.query.sqlpp
C asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/cluster-by/kmeans-descriptor/kmeans-descriptor.5.query.sqlpp
D asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/cluster-by/negative/unknown-descriptor-field/test.1.query.sqlpp
A asterixdb/asterix-app/src/test/resources/runtimets/results/cluster-by/kmeans-descriptor/kmeans-descriptor.3.adm
A asterixdb/asterix-app/src/test/resources/runtimets/results/cluster-by/kmeans-descriptor/kmeans-descriptor.4.adm
A asterixdb/asterix-app/src/test/resources/runtimets/results/cluster-by/kmeans-descriptor/kmeans-descriptor.5.adm
M asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/SqlppClusterByVisitor.java
11 files changed, 80 insertions(+), 121 deletions(-)



  git pull ssh://asterix-gerrit.ics.uci.edu:29418/asterixdb refs/changes/16/21616/1

diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/cluster-by/ClusterByQueries.xml b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/cluster-by/ClusterByQueries.xml
index bd7f50e..7bea736 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/cluster-by/ClusterByQueries.xml
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/cluster-by/ClusterByQueries.xml
@@ -31,6 +31,13 @@
             <output-dir compare="Text">kmeans-in-view</output-dir>
         </compilation-unit>
     </test-case>
+    <!-- The cluster descriptor reads like an ordinary record: the whole value is the record of its fields, a
+         field it lacks is MISSING, and both hold with the CLUSTER AS members variable also referenced. -->
+    <test-case FilePath="cluster-by">
+        <compilation-unit name="kmeans-descriptor">
+            <output-dir compare="Text">kmeans-descriptor</output-dir>
+        </compilation-unit>
+    </test-case>
     <!-- The default kmeansPP initialization on a fixture with enough clusters to expose a bad candidate-pool
          reduction; the shared 6-point k=2 dataset converges correctly from any seeding and so cannot. -->
     <test-case FilePath="cluster-by">
@@ -197,21 +204,6 @@
             <output-dir compare="Text">kmeans-duplicate-points</output-dir>
         </compilation-unit>
     </test-case>
-    <!-- The cluster descriptor exposes exactly three fields, and the rewrite substitutes each away. A
-         reference that survives is either an unknown field or the descriptor used whole; both must name the
-         fields that exist rather than surface the rewrite's own variable as unresolved. -->
-    <test-case FilePath="cluster-by/negative">
-        <compilation-unit name="unknown-descriptor-field">
-            <output-dir compare="Text">unknown-descriptor-field</output-dir>
-            <expected-error>exposes only</expected-error>
-        </compilation-unit>
-    </test-case>
-    <test-case FilePath="cluster-by/negative">
-        <compilation-unit name="bare-descriptor">
-            <output-dir compare="Text">bare-descriptor</output-dir>
-            <expected-error>exposes only</expected-error>
-        </compilation-unit>
-    </test-case>
     <!-- Mixed widths and non-vector rows in one dataset, with membership written down. Declared Dimension
          decides who takes part, so the same rows cluster one way at 2 and the opposite way at 3. -->
     <test-case FilePath="cluster-by">
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/cluster-by/negative/bare-descriptor/test.1.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/cluster-by/kmeans-descriptor/kmeans-descriptor.1.ddl.sqlpp
similarity index 62%
copy from asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/cluster-by/negative/bare-descriptor/test.1.query.sqlpp
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/cluster-by/kmeans-descriptor/kmeans-descriptor.1.ddl.sqlpp
index e0bd86c..a00805e 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/cluster-by/negative/bare-descriptor/test.1.query.sqlpp
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/cluster-by/kmeans-descriptor/kmeans-descriptor.1.ddl.sqlpp
@@ -16,11 +16,15 @@
  * specific language governing permissions and limitations
  * under the License.
  */
--- The descriptor used as a whole value. It is not a record that survives to runtime, so it cannot be
--- selected -- same leftover reference, same reason to name it clearly.
+-- The cluster descriptor reads like an ordinary record: as a whole value it is the record of its fields, and
+-- a field it does not have is MISSING rather than an error. Two well-separated pairs, so the clustering is
+-- unambiguous, and both centroids (0.5 and 10.5) are exact in binary floating point.
+drop dataverse test if exists;
+create dataverse test;
+use test;
 
-from [{"v": [1.0, 2.0]}, {"v": [3.0, 4.0]}] as p
-cluster by p.v as sc
-cluster as members
-with { "dimension": [2], "num_clusters": 2 }
-select value sc;
+create type PointType as closed {
+  id : integer,
+  vec : [double]
+};
+create dataset Points(PointType) primary key id;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/cluster-by/negative/bare-descriptor/test.1.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/cluster-by/kmeans-descriptor/kmeans-descriptor.2.update.sqlpp
similarity index 70%
copy from asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/cluster-by/negative/bare-descriptor/test.1.query.sqlpp
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/cluster-by/kmeans-descriptor/kmeans-descriptor.2.update.sqlpp
index e0bd86c..b5cb7b5 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/cluster-by/negative/bare-descriptor/test.1.query.sqlpp
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/cluster-by/kmeans-descriptor/kmeans-descriptor.2.update.sqlpp
@@ -16,11 +16,11 @@
  * specific language governing permissions and limitations
  * under the License.
  */
--- The descriptor used as a whole value. It is not a record that survives to runtime, so it cannot be
--- selected -- same leftover reference, same reason to name it clearly.
+use test;
 
-from [{"v": [1.0, 2.0]}, {"v": [3.0, 4.0]}] as p
-cluster by p.v as sc
-cluster as members
-with { "dimension": [2], "num_clusters": 2 }
-select value sc;
+insert into Points ([
+  {"id": 1, "vec": [0.0, 0.0]},
+  {"id": 2, "vec": [1.0, 0.0]},
+  {"id": 3, "vec": [10.0, 10.0]},
+  {"id": 4, "vec": [11.0, 10.0]}
+]);
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/cluster-by/negative/bare-descriptor/test.1.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/cluster-by/kmeans-descriptor/kmeans-descriptor.3.query.sqlpp
similarity index 76%
rename from asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/cluster-by/negative/bare-descriptor/test.1.query.sqlpp
rename to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/cluster-by/kmeans-descriptor/kmeans-descriptor.3.query.sqlpp
index e0bd86c..df24c5f 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/cluster-by/negative/bare-descriptor/test.1.query.sqlpp
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/cluster-by/kmeans-descriptor/kmeans-descriptor.3.query.sqlpp
@@ -16,11 +16,12 @@
  * specific language governing permissions and limitations
  * under the License.
  */
--- The descriptor used as a whole value. It is not a record that survives to runtime, so it cannot be
--- selected -- same leftover reference, same reason to name it clearly.
+-- The descriptor as a whole value: the record of the fields it exposes.
+use test;
 
-from [{"v": [1.0, 2.0]}, {"v": [3.0, 4.0]}] as p
-cluster by p.v as sc
+from Points as p
+cluster by p.vec as sc
 cluster as members
 with { "dimension": [2], "num_clusters": 2 }
-select value sc;
+select value sc
+order by sc.cluster_id;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/cluster-by/negative/bare-descriptor/test.1.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/cluster-by/kmeans-descriptor/kmeans-descriptor.4.query.sqlpp
similarity index 76%
copy from asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/cluster-by/negative/bare-descriptor/test.1.query.sqlpp
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/cluster-by/kmeans-descriptor/kmeans-descriptor.4.query.sqlpp
index e0bd86c..7d81172 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/cluster-by/negative/bare-descriptor/test.1.query.sqlpp
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/cluster-by/kmeans-descriptor/kmeans-descriptor.4.query.sqlpp
@@ -16,11 +16,12 @@
  * specific language governing permissions and limitations
  * under the License.
  */
--- The descriptor used as a whole value. It is not a record that survives to runtime, so it cannot be
--- selected -- same leftover reference, same reason to name it clearly.
+-- A field the descriptor does not have is MISSING, so the projection omits it -- as for any other record.
+use test;
 
-from [{"v": [1.0, 2.0]}, {"v": [3.0, 4.0]}] as p
-cluster by p.v as sc
+from Points as p
+cluster by p.vec as sc
 cluster as members
 with { "dimension": [2], "num_clusters": 2 }
-select value sc;
+select sc.cluster_id as cid, sc.a as a
+order by cid;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/cluster-by/negative/bare-descriptor/test.1.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/cluster-by/kmeans-descriptor/kmeans-descriptor.5.query.sqlpp
similarity index 76%
copy from asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/cluster-by/negative/bare-descriptor/test.1.query.sqlpp
copy to asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/cluster-by/kmeans-descriptor/kmeans-descriptor.5.query.sqlpp
index e0bd86c..14c4994 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/cluster-by/negative/bare-descriptor/test.1.query.sqlpp
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/cluster-by/kmeans-descriptor/kmeans-descriptor.5.query.sqlpp
@@ -16,11 +16,13 @@
  * specific language governing permissions and limitations
  * under the License.
  */
--- The descriptor used as a whole value. It is not a record that survives to runtime, so it cannot be
--- selected -- same leftover reference, same reason to name it clearly.
+-- The whole-value form alongside the CLUSTER AS members variable, which is the case the field-by-field
+-- substitution was originally written to avoid.
+use test;
 
-from [{"v": [1.0, 2.0]}, {"v": [3.0, 4.0]}] as p
-cluster by p.v as sc
+from Points as p
+cluster by p.vec as sc
 cluster as members
 with { "dimension": [2], "num_clusters": 2 }
-select value sc;
+select sc, array_count(members) as cnt
+order by sc.cluster_id;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/cluster-by/negative/unknown-descriptor-field/test.1.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/cluster-by/negative/unknown-descriptor-field/test.1.query.sqlpp
deleted file mode 100644
index deea074..0000000
--- a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/cluster-by/negative/unknown-descriptor-field/test.1.query.sqlpp
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
--- A field the descriptor does not have. Every real field is substituted away during the rewrite, so this
--- one survives as a reference to a variable the rewrite invented -- the user should be told which fields
--- exist, not given an unresolved-identifier error naming an internal variable.
-
-from [{"v": [1.0, 2.0]}, {"v": [3.0, 4.0]}] as p
-cluster by p.v as sc
-cluster as members
-with { "dimension": [2], "num_clusters": 2 }
-select sc.clusterId;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/cluster-by/kmeans-descriptor/kmeans-descriptor.3.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/cluster-by/kmeans-descriptor/kmeans-descriptor.3.adm
new file mode 100644
index 0000000..1240709
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/cluster-by/kmeans-descriptor/kmeans-descriptor.3.adm
@@ -0,0 +1,2 @@
+{ "cluster_id": 0, "centroid": [ 0.5, 0.0 ] }
+{ "cluster_id": 1, "centroid": [ 10.5, 10.0 ] }
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/cluster-by/kmeans-descriptor/kmeans-descriptor.4.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/cluster-by/kmeans-descriptor/kmeans-descriptor.4.adm
new file mode 100644
index 0000000..478a504
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/cluster-by/kmeans-descriptor/kmeans-descriptor.4.adm
@@ -0,0 +1,2 @@
+{ "cid": 0 }
+{ "cid": 1 }
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/cluster-by/kmeans-descriptor/kmeans-descriptor.5.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/cluster-by/kmeans-descriptor/kmeans-descriptor.5.adm
new file mode 100644
index 0000000..018d91e
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/cluster-by/kmeans-descriptor/kmeans-descriptor.5.adm
@@ -0,0 +1,2 @@
+{ "sc": { "cluster_id": 0, "centroid": [ 0.5, 0.0 ] }, "cnt": 2 }
+{ "sc": { "cluster_id": 1, "centroid": [ 10.5, 10.0 ] }, "cnt": 2 }
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/SqlppClusterByVisitor.java b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/SqlppClusterByVisitor.java
index 10c194e..677c4f0 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/SqlppClusterByVisitor.java
+++ b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/visitor/SqlppClusterByVisitor.java
@@ -22,6 +22,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -42,12 +43,15 @@
 import org.apache.asterix.lang.common.clause.WhereClause;
 import org.apache.asterix.lang.common.expression.CallExpr;
 import org.apache.asterix.lang.common.expression.FieldAccessor;
+import org.apache.asterix.lang.common.expression.FieldBinding;
 import org.apache.asterix.lang.common.expression.GbyVariableExpressionPair;
 import org.apache.asterix.lang.common.expression.IndexAccessor;
 import org.apache.asterix.lang.common.expression.LiteralExpr;
 import org.apache.asterix.lang.common.expression.OperatorExpr;
+import org.apache.asterix.lang.common.expression.RecordConstructor;
 import org.apache.asterix.lang.common.expression.VariableExpr;
 import org.apache.asterix.lang.common.literal.IntegerLiteral;
+import org.apache.asterix.lang.common.literal.StringLiteral;
 import org.apache.asterix.lang.common.rewrites.LangRewritingContext;
 import org.apache.asterix.lang.common.struct.Identifier;
 import org.apache.asterix.lang.common.struct.OperatorType;
@@ -142,11 +146,10 @@
     // a per-JVM salted order, so the message would differ between runs.
     private static final String KNOWN_OPTIONS_DISPLAY =
             "clustering_algorithm, num_clusters, dimension, similarity, cross_pollination, init_mode";
-    // The fields the cluster descriptor exposes. Every one is substituted away during the rewrite, so a
-    // surviving reference to the descriptor means the query asked for something else -- see checkDescriptorFields.
+    // The fields the cluster descriptor exposes; the rewrite substitutes each away, and the descriptor read
+    // as a whole value with a record of them.
     private static final String SC_CLUSTER_ID = "cluster_id";
     private static final String SC_CENTROID = "centroid";
-    private static final String SC_FIELDS_DISPLAY = SC_CLUSTER_ID + ", " + SC_CENTROID;
 
     // Seed-draw width guard function, by surface name (see callByName); resolves to the internal sql-count.
     private static final String ARRAY_COUNT_FN = "array_count";
@@ -443,59 +446,36 @@
     }
 
     /**
-     * Replaces every {@code <descriptor>.<field>} read with the expression that computes it. The descriptor is
-     * substituted field by field rather than bound to a record: an OpenRecordConstructor here breaks type
-     * inference when the members variable is also referenced. For the same reason {@code sc.centroid} becomes
-     * {@code centroid(vec)} as a group aggregate rather than an index into the centroid list, keeping every
-     * post-group descriptor field on the group-aggregation path.
+     * Replaces every descriptor read with the expression that computes it: each {@code <descriptor>.<field>}
+     * by its own, and the descriptor read as a whole value by a record of them all. {@code sc.centroid}
+     * becomes {@code centroid(vec)} as a group aggregate, keeping it on the group-aggregation path.
+     * <p>
+     * One map covers both, so the descriptor reads like any other record: substitution matches
+     * outermost-first, so a known field access is replaced whole and an unknown one falls through to the bare
+     * variable inside it, becoming a field of the record -- MISSING, as {@code r.nosuchfield} is anywhere.
+     * The record is inlined at each use rather than bound to a LET, so it is the shape the group-by pipeline
+     * already handles (as TPCH Q1 writes {@code {"sum_qty": sum(quantity), ...}} under its own GROUP AS).
      */
     private void substituteDescriptorFields(SelectExpression selectExpression, ClusterbyClause cbc,
             Expression clusteringExpr, Expression labelExpr, SourceLocation loc) throws CompilationException {
         VariableExpr scVar = cbc.getClusterDescriptorVar();
-        Map<Expression, Expression> scSubst = new HashMap<>();
-        scSubst.put(fieldAccess(scVar, SC_CLUSTER_ID, loc), copy(labelExpr));
-        scSubst.put(fieldAccess(scVar, SC_CENTROID, loc),
-                call(BuiltinFunctions.SCALAR_CENTROID, loc, copy(clusteringExpr)));
-        SqlppRewriteUtil.substituteExpression(selectExpression, scSubst, context);
-        // Substitution replaced every field the descriptor actually has. Anything still referring to it is
-        // either an unknown field or the descriptor used as a whole value, neither of which survives to
-        // runtime -- and left alone both reach the user as a bare "unresolved identifier" naming a variable
-        // the rewrite invented. Say what it is instead.
-        checkDescriptorResolved(selectExpression, scVar, loc);
-    }
+        // Named once, so a new field reaches both the field accesses and the whole-value record.
+        Map<String, Expression> fields = new LinkedHashMap<>();
+        fields.put(SC_CLUSTER_ID, copy(labelExpr));
+        fields.put(SC_CENTROID, call(BuiltinFunctions.SCALAR_CENTROID, loc, copy(clusteringExpr)));
 
-    /**
-     * Raises when the query still refers to the cluster descriptor after the rewrite substituted its fields
-     * away -- {@code sc.somethingElse}, or {@code sc} on its own. Without this the leftover variable reaches
-     * the resolver as an undefined identifier, which names the rewrite's own variable rather than telling the
-     * user which field they asked for.
-     */
-    private static void checkDescriptorResolved(ILangExpression expr, VariableExpr descriptorVar, SourceLocation loc)
-            throws CompilationException {
-        DescriptorLeftoverFinder finder = new DescriptorLeftoverFinder(descriptorVar);
-        expr.accept(finder, null);
-        if (finder.found) {
-            throw new CompilationException(ErrorCode.COMPILATION_ERROR, loc,
-                    "CLUSTER BY cluster descriptor '" + descriptorVar.getVar().getValue() + "' exposes only "
-                            + SC_FIELDS_DISPLAY + ", and cannot be referenced as a whole value.");
+        List<FieldBinding> fbList = new ArrayList<>(fields.size());
+        Map<Expression, Expression> subst = new HashMap<>();
+        for (Map.Entry<String, Expression> field : fields.entrySet()) {
+            subst.put(fieldAccess(scVar, field.getKey(), loc), field.getValue());
+            LiteralExpr nameLit = new LiteralExpr(new StringLiteral(field.getKey()));
+            nameLit.setSourceLocation(loc);
+            fbList.add(new FieldBinding(nameLit, copy(field.getValue())));
         }
-    }
-
-    private static final class DescriptorLeftoverFinder extends AbstractSqlppSimpleExpressionVisitor {
-        private final VariableExpr descriptorVar;
-        private boolean found;
-
-        private DescriptorLeftoverFinder(VariableExpr descriptorVar) {
-            this.descriptorVar = descriptorVar;
-        }
-
-        @Override
-        public Expression visit(VariableExpr v, ILangExpression arg) throws CompilationException {
-            if (descriptorVar.getVar().getValue().equals(v.getVar().getValue())) {
-                found = true;
-            }
-            return super.visit(v, arg);
-        }
+        RecordConstructor descriptorRecord = new RecordConstructor(fbList);
+        descriptorRecord.setSourceLocation(loc);
+        subst.put(new VariableExpr(scVar.getVar()), descriptorRecord);
+        SqlppRewriteUtil.substituteExpression(selectExpression, subst, context);
     }
 
     private FieldAccessor fieldAccess(VariableExpr recordVar, String field, SourceLocation loc) {

-- 
To view, visit https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/21616?usp=email
To unsubscribe, or for help writing mail filters, visit https://asterix-gerrit.ics.uci.edu/settings?usp=email

Gerrit-MessageType: newchange
Gerrit-Project: asterixdb
Gerrit-Branch: master
Gerrit-Change-Id: Ie53dca75c8fa68a1bc1b7fdd71c96f477f683917
Gerrit-Change-Number: 21616
Gerrit-PatchSet: 1
Gerrit-Owner: Shahrzad Shirazi <sh...@ucr.edu>