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/09/22 19:43:46 UTC

[L] Change in asterixdb[master]: [ASTERIXDB-3883][COMP] Emit only the INCLUDE columns a query reads

From Ali Alsuliman <al...@gmail.com>:

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


Change subject: [ASTERIXDB-3883][COMP] Emit only the INCLUDE columns a query reads
......................................................................

[ASTERIXDB-3883][COMP] Emit only the INCLUDE columns a query reads

The vector index search declared every INCLUDE column of its index, so
the runtime copied each one out of the secondary tuple for every
candidate and the project above the search dropped whatever nothing
read. The index-only plan now declares the columns its predicate and
its projections bind, and the lookup-and-rerank plan, where only a
pushed predicate can read a column, declares them when that predicate
is pushed -- which also leaves the columns and the predicate needing
them decided at one point instead of two.

Ext-ref: MB-74112

Co-Authored-By: Claude Opus 5 <no...@anthropic.com>
Change-Id: I2c9095f9c37ea5bad05fae7acfb5678e269b9236
---
M asterixdb/asterix-algebra/src/main/java/org/apache/asterix/algebra/operators/physical/VectorSearchPOperator.java
M asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/PushFilterIntoVectorSearchRule.java
M asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/VectorIncludeFilterPushdown.java
M asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/VectorIndexAccessMethod.java
A asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/vector/create-index-vtree-include-filter-multi/create-index-vtree-include-filter-multi.17.query.sqlpp
A asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/vector/create-index-vtree-include-filter-multi/create-index-vtree-include-filter-multi.18.plans.sqlpp
M asterixdb/asterix-app/src/test/resources/runtimets/results/vector/create-index-vtree-composite-pk/create-index-vtree-composite-pk.11.plan
M asterixdb/asterix-app/src/test/resources/runtimets/results/vector/create-index-vtree-composite-pk/create-index-vtree-composite-pk.8.plan
M asterixdb/asterix-app/src/test/resources/runtimets/results/vector/create-index-vtree-include-filter-multi/create-index-vtree-include-filter-multi.13.plan
M asterixdb/asterix-app/src/test/resources/runtimets/results/vector/create-index-vtree-include-filter-multi/create-index-vtree-include-filter-multi.14.plan
M asterixdb/asterix-app/src/test/resources/runtimets/results/vector/create-index-vtree-include-filter-multi/create-index-vtree-include-filter-multi.16.plan
A asterixdb/asterix-app/src/test/resources/runtimets/results/vector/create-index-vtree-include-filter-multi/create-index-vtree-include-filter-multi.17.adm
A asterixdb/asterix-app/src/test/resources/runtimets/results/vector/create-index-vtree-include-filter-multi/create-index-vtree-include-filter-multi.18.plan
M asterixdb/asterix-app/src/test/resources/runtimets/results/vector/create-index-vtree-include-filter-multi/create-index-vtree-include-filter-multi.7.plan
M asterixdb/asterix-app/src/test/resources/runtimets/results/vector/create-index-vtree-include-nested/create-index-vtree-include-nested.16.plan
M asterixdb/asterix-app/src/test/resources/runtimets/results/vector/create-index-vtree-nested/create-index-vtree-nested.5.plan
16 files changed, 266 insertions(+), 90 deletions(-)



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

diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/algebra/operators/physical/VectorSearchPOperator.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/algebra/operators/physical/VectorSearchPOperator.java
index d433c87..59dc222 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/algebra/operators/physical/VectorSearchPOperator.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/algebra/operators/physical/VectorSearchPOperator.java
@@ -19,8 +19,10 @@
 package org.apache.asterix.algebra.operators.physical;
 
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 import org.apache.asterix.common.exceptions.CompilationException;
 import org.apache.asterix.common.exceptions.ErrorCode;
@@ -121,6 +123,7 @@
 
     @Override
     @AiProvenance(agent = AiProvenance.Agent.CLAUDE_FABLE_5_1, tool = AiProvenance.Tool.CLAUDE_CODE_UI, contributionKind = AiProvenance.ContributionKind.ASSISTED, notes = "Reject a filter condition on an index without INCLUDE columns instead of dropping it")
+    @AiProvenance(agent = AiProvenance.Agent.CLAUDE_OPUS_5, tool = AiProvenance.Tool.CLAUDE_CODE_UI, contributionKind = AiProvenance.ContributionKind.REFACTORED, notes = "Check the condition against the columns declared rather than against the index's")
     public void contributeRuntimeOperator(IHyracksJobBuilder builder, JobGenContext context, ILogicalOperator op,
             IOperatorSchema opSchema, IOperatorSchema[] inputSchemas, IOperatorSchema outerPlanSchema)
             throws AlgebricksException {
@@ -160,8 +163,8 @@
         boolean isQuantized = vectorDetails.getVectorParameters().isQuantized();
         int numSecondaryKeys = VTreeDataTupleAccessor.getNumSecondaryFields(isQuantized);
 
-        // The INCLUDE columns are declared on the search whether this query reads them or not, so one
-        // mapping serves the pushed predicate, the projection, and the runtime's output tuple alike.
+        // The search declares the INCLUDE columns something in the query binds, and one mapping serves the
+        // pushed predicate, the projection, and the runtime's output tuple alike.
         ITupleFilterFactory tupleFilterFactory = null;
         int[] includeFields = NO_INCLUDE_FIELDS;
         if (unnestMap instanceof UnnestMapOperator) {
@@ -170,20 +173,23 @@
                     VectorIncludeFilterPushdown.getIncludeColumns(unnestMapOp);
 
             if (unnestMapOp.getSelectCondition() != null) {
-                if (columns == null) {
-                    // A condition reaches the search only through the binding, which has nothing to bind a
-                    // field access to without INCLUDE columns. One here without them was installed by some
-                    // other hand, and dropping it would return the rows it was meant to remove.
+                Map<LogicalVariable, Integer> varToFieldIndex = columns == null ? Map.of() : columns.varToFieldIndex();
+                // A condition reaches the search only through the binding, which declares every column it
+                // binds. One reading anything else was installed by some other hand, and evaluating it
+                // against the secondary tuple would filter on whatever field index it lands on.
+                Set<LogicalVariable> conditionVars = new HashSet<>();
+                unnestMapOp.getSelectCondition().getValue().getUsedVariables(conditionVars);
+                if (!varToFieldIndex.keySet().containsAll(conditionVars)) {
                     throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, unnestMap.getSourceLocation(),
                             "the vector index search of index " + jobGenParams.getIndexName()
-                                    + " carries a filter condition but the index declares no INCLUDE columns");
+                                    + " carries a filter condition reading variables it does not emit as "
+                                    + "INCLUDE columns: " + conditionVars);
                 }
                 // The operator schema carries the INCLUDE columns at their output positions, but the filter
                 // runs against the SECONDARY tuple, so its variables resolve through the mapping instead.
-                IOperatorSchema filterSchema =
-                        new VectorIndexFilterSchema(opSchema, columns.varToFieldIndex(), numSecondaryKeys);
-                IVariableTypeEnvironment filterTypeEnv =
-                        new VectorIndexFilterTypeEnvironment(typeEnv, columns.varTypes(), context);
+                IOperatorSchema filterSchema = new VectorIndexFilterSchema(opSchema, varToFieldIndex, numSecondaryKeys);
+                IVariableTypeEnvironment filterTypeEnv = new VectorIndexFilterTypeEnvironment(typeEnv,
+                        columns == null ? Map.of() : columns.varTypes(), context);
                 tupleFilterFactory = mp.createTupleFilterFactory(new IOperatorSchema[] { filterSchema }, filterTypeEnv,
                         unnestMapOp.getSelectCondition().getValue(), context);
             }
@@ -215,7 +221,7 @@
      * The physical field indexes of the INCLUDE columns this search emits, in output order.
      * <p>
      * Those columns are the LAST variables of the unnest-map:
-     * {@link VectorIncludeFilterPushdown#declareIncludeColumns} appends them after the primary keys and, on
+     * {@link VectorIncludeFilterPushdown#setIncludeColumns} appends them after the primary keys and, on
      * the index-only plan, after the distance. The operator schema, and hence the output record descriptor,
      * is built from that same list, and the runtime writes the primary keys, then the distance, then these
      * columns -- so their
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/PushFilterIntoVectorSearchRule.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/PushFilterIntoVectorSearchRule.java
index 37fad09..a042001 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/PushFilterIntoVectorSearchRule.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/PushFilterIntoVectorSearchRule.java
@@ -72,8 +72,10 @@
  *                       selectCondition: (rewritten to use $includeField1, ...)
  * </pre>
  *
- * The INCLUDE columns are already declared as outputs of VECTOR_INDEX_UNNEST by the access-method phase;
- * field-access expressions in the filter are rewritten to reference those variables.
+ * Field-access expressions in the filter are rewritten to read INCLUDE columns of the search, which are
+ * declared here for the lookup-and-rerank shape and, for the index-only shape, already by the
+ * access-method phase -- there the projections read them too, so they have to exist before it rewrites
+ * the plan above the search.
  * <p>
  * The SELECT has to sit within the search's own pipeline: the descent from it stops at a LIMIT, or at
  * any operator other than the ones the two plan shapes put between a WHERE and the search. A predicate
@@ -128,13 +130,29 @@
         VectorIncludeFilterPushdown.IndexContext idx =
                 new VectorIncludeFilterPushdown.IndexContext(searchInfo.includeFieldNames(), searchInfo.recordType(),
                         searchInfo.isQuantized(), searchInfo.numPrimaryKeys(), searchInfo.recordVars());
+        VectorIncludeFilterPushdown.IncludeColumns candidates =
+                VectorIncludeFilterPushdown.candidateColumns(vectorUnnest, idx, context::newVar);
         ILogicalExpression bound = VectorIncludeFilterPushdown.bindPredicate(selectOp.getCondition().getValue(),
-                selectOp, idx, context, VectorIncludeFilterPushdown.getIncludeColumns(vectorUnnest));
+                selectOp, idx, context, candidates);
         if (bound == null) {
             throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, selectOp.getSourceLocation(),
                     "the WHERE above the vector search of index " + searchInfo.indexName()
                             + " reads what the search cannot evaluate, though index selection admits only a WHERE it can");
         }
+        // Declare the columns this predicate reads, keeping the ones the index-only plan already declared
+        // for its projections. That plan bound its predicate to its own columns during the access-method
+        // phase, so the binding above found variable references there and adds nothing; the
+        // lookup-and-rerank plan, where a predicate is the only thing that can read a column, declares its
+        // columns here and none before.
+        Set<LogicalVariable> keep = new HashSet<>();
+        bound.getUsedVariables(keep);
+        VectorIncludeFilterPushdown.IncludeColumns declared =
+                VectorIncludeFilterPushdown.getIncludeColumns(vectorUnnest);
+        if (declared != null) {
+            keep.addAll(declared.varToFieldIndex().keySet());
+        }
+        VectorIncludeFilterPushdown.setIncludeColumns(vectorUnnest,
+                VectorIncludeFilterPushdown.retainColumns(candidates, keep));
         VectorIncludeFilterPushdown.apply(vectorUnnest, bound);
         return dropSelect(opRef, selectOp, vectorUnnest, op, context);
     }
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/VectorIncludeFilterPushdown.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/VectorIncludeFilterPushdown.java
index 24fa258..b966e85 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/VectorIncludeFilterPushdown.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/VectorIncludeFilterPushdown.java
@@ -25,6 +25,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.Function;
 import java.util.function.Supplier;
 
 import org.apache.asterix.om.functions.BuiltinFunctions;
@@ -83,12 +84,12 @@
     public static final String VECTOR_INCLUDE_COLUMNS = "VECTOR_INCLUDE_COLUMNS";
 
     /**
-     * Every INCLUDE column of the searched index, bound to a variable the search emits.
+     * INCLUDE columns of the searched index, each bound to a variable the search emits.
      * <p>
-     * All of them are declared, whether this query reads them or not, so that one mapping serves every use:
-     * a predicate pushed into the search, a projection returning the column, or neither. Keyed by the full
-     * path, so a nested INCLUDE column ({@code INCLUDE (info.year)}) is addressed exactly like a top-level
-     * one and never confused with a same-named field elsewhere in the record.
+     * Keyed by the full path, so a nested INCLUDE column ({@code INCLUDE (info.year)}) is addressed exactly
+     * like a top-level one and never confused with a same-named field elsewhere in the record. One mapping
+     * serves every use: a predicate pushed into the search, a projection returning the column, and the
+     * tuple the runtime writes.
      *
      * @param pathToVar each column's full path to the variable carrying it, in INCLUDE order; empty when
      *                  the index declares the same path twice, which leaves no unambiguous binding
@@ -100,18 +101,43 @@
     }
 
     /**
-     * Declare every INCLUDE column of the index as an output of the vector index search, appended after the
-     * primary keys and, on the index-only plan, the distance.
+     * The index's INCLUDE columns as pushdown candidates: those {@code vectorUnnest} already declares keep
+     * their variable, the rest get a fresh one. Declares nothing itself.
      * <p>
-     * The runtime emits exactly what is declared here, so the columns are bound once, at the point the
-     * unnest-map is created, rather than discovered per use. A predicate and a projection over the same
-     * column then resolve to the same variable by construction.
+     * A caller binds against the candidates and then declares, with {@link #setIncludeColumns}, the ones its
+     * binding used. Declaring a column nothing reads is not free: the runtime copies every declared column
+     * out of the secondary tuple for each of the {@code k * k_multiplier} candidates, and the project above
+     * the search then drops it.
      */
     @AiProvenance(agent = AiProvenance.Agent.CLAUDE_OPUS_5, tool = AiProvenance.Tool.CLAUDE_CODE_UI, contributionKind = AiProvenance.ContributionKind.GENERATED)
-    public static IncludeColumns declareIncludeColumns(UnnestMapOperator vectorUnnest, IndexContext idx,
+    public static IncludeColumns candidateColumns(UnnestMapOperator vectorUnnest, IndexContext idx,
             Supplier<LogicalVariable> varSupplier) throws AlgebricksException {
-        IncludeColumns columns = buildIncludeColumns(idx, varSupplier);
-        if (columns == null) {
+        IncludeColumns declared = getIncludeColumns(vectorUnnest);
+        Map<List<String>, LogicalVariable> reusable = declared == null ? Map.of() : declared.pathToVar();
+        return buildIncludeColumns(idx, path -> {
+            LogicalVariable existing = reusable.get(path);
+            return existing != null ? existing : varSupplier.get();
+        });
+    }
+
+    /**
+     * Make {@code columns} exactly the INCLUDE columns {@code vectorUnnest} declares, replacing whatever it
+     * declared before, and return them ({@code null} when there are none left).
+     * <p>
+     * They are the operator's trailing outputs, after the primary keys and the index-only distance, because
+     * job generation lines the emitted tuple up with the output record descriptor from the end.
+     */
+    @AiProvenance(agent = AiProvenance.Agent.CLAUDE_OPUS_5, tool = AiProvenance.Tool.CLAUDE_CODE_UI, contributionKind = AiProvenance.ContributionKind.GENERATED)
+    public static IncludeColumns setIncludeColumns(UnnestMapOperator vectorUnnest, IncludeColumns columns) {
+        IncludeColumns declared = getIncludeColumns(vectorUnnest);
+        if (declared != null) {
+            int end = vectorUnnest.getVariables().size();
+            int start = end - declared.varToFieldIndex().size();
+            vectorUnnest.getVariables().subList(start, end).clear();
+            vectorUnnest.getVariableTypes().subList(start, end).clear();
+        }
+        if (columns == null || columns.varToFieldIndex().isEmpty()) {
+            vectorUnnest.getAnnotations().remove(VECTOR_INCLUDE_COLUMNS);
             return null;
         }
         for (Map.Entry<LogicalVariable, IAType> entry : columns.varTypes().entrySet()) {
@@ -122,14 +148,34 @@
         return columns;
     }
 
+    /** {@code columns} narrowed to those {@code keep} holds a variable for, or {@code null} if that is none. */
+    @AiProvenance(agent = AiProvenance.Agent.CLAUDE_OPUS_5, tool = AiProvenance.Tool.CLAUDE_CODE_UI, contributionKind = AiProvenance.ContributionKind.GENERATED)
+    public static IncludeColumns retainColumns(IncludeColumns columns, Set<LogicalVariable> keep) {
+        if (columns == null) {
+            return null;
+        }
+        Map<List<String>, LogicalVariable> pathToVar = new LinkedHashMap<>();
+        Map<LogicalVariable, Integer> varToFieldIndex = new LinkedHashMap<>();
+        Map<LogicalVariable, IAType> varTypes = new LinkedHashMap<>();
+        for (Map.Entry<List<String>, LogicalVariable> entry : columns.pathToVar().entrySet()) {
+            LogicalVariable var = entry.getValue();
+            if (keep.contains(var)) {
+                pathToVar.put(entry.getKey(), var);
+                varToFieldIndex.put(var, columns.varToFieldIndex().get(var));
+                varTypes.put(var, columns.varTypes().get(var));
+            }
+        }
+        return varToFieldIndex.isEmpty() ? null : new IncludeColumns(pathToVar, varToFieldIndex, varTypes);
+    }
+
     /**
      * The index's INCLUDE columns bound to variables from {@code varSupplier}, or {@code null} when the
      * index has none. Touches no operator, so the gate can run the analysis on throwaway variables to reach
      * a verdict before any of this is spliced into a plan.
      */
     @AiProvenance(agent = AiProvenance.Agent.CLAUDE_OPUS_5, tool = AiProvenance.Tool.CLAUDE_CODE_UI, contributionKind = AiProvenance.ContributionKind.GENERATED)
-    private static IncludeColumns buildIncludeColumns(IndexContext idx, Supplier<LogicalVariable> varSupplier)
-            throws AlgebricksException {
+    private static IncludeColumns buildIncludeColumns(IndexContext idx,
+            Function<List<String>, LogicalVariable> varSupplier) throws AlgebricksException {
         List<List<String>> includeFieldNames = idx.includeFieldNames();
         if (includeFieldNames == null || includeFieldNames.isEmpty()) {
             return null;
@@ -142,7 +188,7 @@
                 : VTreeDataTupleAccessor.NQ_NUM_SECONDARY_FIELDS;
         int fieldIndex = numSecondaryKeys + idx.numPrimaryKeys();
         for (List<String> fieldPath : includeFieldNames) {
-            LogicalVariable var = varSupplier.get();
+            LogicalVariable var = varSupplier.apply(fieldPath);
             // An open field not in the type declaration has no declared type; ANY still lets the type
             // environment resolve the variable.
             IAType fieldType = idx.recordType().getSubFieldType(fieldPath);
@@ -153,10 +199,9 @@
             varToFieldIndex.put(var, fieldIndex++);
             varTypes.put(var, fieldType);
         }
-        // Every column is still declared -- the tuple the runtime writes has to match the declaration either
-        // way -- but two identical INCLUDE paths leave no unambiguous variable to bind a reference to, so
-        // nothing is bound and the predicate stays above the search. DDL rejects such an index
-        // (INDEX_ILLEGAL_REPETITIVE_FIELD), so this is a belt-and-braces check, not a reachable path.
+        // Two identical INCLUDE paths leave no unambiguous variable to bind a reference to, so nothing binds
+        // and no column is declared. DDL rejects such an index (INDEX_ILLEGAL_REPETITIVE_FIELD), so this is
+        // a belt-and-braces check, not a reachable path.
         if (pathToVar.size() < includeFieldNames.size()) {
             pathToVar.clear();
         }
@@ -524,11 +569,11 @@
      * Hands out variables that are never spliced into a plan, for the callers that only need the verdict.
      * Negative ids keep them distinguishable from real ones if they ever leak into a log line.
      */
-    private static final class ThrowawayVariableSupplier implements Supplier<LogicalVariable> {
+    private static final class ThrowawayVariableSupplier implements Function<List<String>, LogicalVariable> {
         private int nextId = -1;
 
         @Override
-        public LogicalVariable get() {
+        public LogicalVariable apply(List<String> path) {
             return new LogicalVariable(nextId--);
         }
     }
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/VectorIndexAccessMethod.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/VectorIndexAccessMethod.java
index 6079005..97a2aa8 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/VectorIndexAccessMethod.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/am/VectorIndexAccessMethod.java
@@ -72,6 +72,7 @@
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.OrderOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.SelectOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.UnnestMapOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.visitors.VariableUtilities;
 import org.apache.hyracks.algebricks.core.algebra.util.OperatorManipulationUtil;
 import org.apache.hyracks.util.annotations.AiProvenance;
 
@@ -332,12 +333,16 @@
                 distVar = context.newVar();
                 searchUnnest.getVariables().add(distVar);
                 searchUnnest.getVariableTypes().add(BuiltinType.ADOUBLE);
+                // Declare every INCLUDE column for now: the branch below rewrites the predicate and the
+                // projections onto them, and what neither ends up reading is dropped again before this plan
+                // is returned. The lookup-and-rerank shape declares nothing here -- only a pushed predicate
+                // can read a column there, and PushFilterIntoVectorSearchRule declares what it binds.
+                includeColumns =
+                        VectorIncludeFilterPushdown.setIncludeColumns(searchUnnest,
+                                VectorIncludeFilterPushdown.candidateColumns(searchUnnest,
+                                        includeContext(chosenIndex, dataset, recordType, dataSourceOp),
+                                        context::newVar));
             }
-            // Every INCLUDE column of the index is declared, for both plan shapes. One mapping then serves
-            // whatever reads them -- a predicate pushed into the search, a projection returning them, or
-            // neither -- and the runtime emits exactly what is declared.
-            includeColumns = VectorIncludeFilterPushdown.declareIncludeColumns(searchUnnest,
-                    includeContext(chosenIndex, dataset, recordType, dataSourceOp), context::newVar);
         }
         context.computeAndSetTypeEnvironmentForOperator(secondaryIndexUnnestOp);
 
@@ -428,8 +433,7 @@
                         ? aboveLimitOps.get(0) : (ILogicalOperator) limitRef.getValue();
 
                 // 1. Substitute old PK var refs throughout the plan (above and below LIMIT).
-                org.apache.hyracks.algebricks.core.algebra.operators.logical.visitors.VariableUtilities
-                        .substituteVariablesInDescendantsAndSelf(rewriteRoot, pkSubstitution, context);
+                VariableUtilities.substituteVariablesInDescendantsAndSelf(rewriteRoot, pkSubstitution, context);
 
                 // 2. Rewrite field-access on the old record/meta vars to direct PK VarRefs (e.g. the
                 //    SELECT VALUE m.idx projection above LIMIT), each against only its own PK fields.
@@ -472,6 +476,15 @@
                     neutralizeDanglingExpressions(rewriteRoot, deadRecordVars);
                 }
 
+                // 4. Undeclare the INCLUDE columns none of the rewrites above bound. Their variables were
+                // minted here, so every reference any of them can have was made by those rewrites, all of
+                // them under rewriteRoot -- a column absent from this set is one the runtime would copy out
+                // of the secondary tuple per candidate for nothing to read.
+                Set<LogicalVariable> boundVars = new HashSet<>();
+                VariableUtilities.getUsedVariablesInDescendantsAndSelf(rewriteRoot, boundVars);
+                VectorIncludeFilterPushdown.setIncludeColumns(unnestMap,
+                        VectorIncludeFilterPushdown.retainColumns(includeColumns, boundVars));
+
                 // Cross-pollination dedup (index-only branch): when cross_pollination_m > 1 the secondary
                 // cursor emits up to M (pk..., dist) copies per record. The primary-lookup path dedups via a
                 // DistinctOperator above its primary UNNEST-MAP (see createRestOfIndexSearchPlan below), but the
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/vector/create-index-vtree-include-filter-multi/create-index-vtree-include-filter-multi.17.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/vector/create-index-vtree-include-filter-multi/create-index-vtree-include-filter-multi.17.query.sqlpp
new file mode 100644
index 0000000..f6d4897
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/vector/create-index-vtree-include-filter-multi/create-index-vtree-include-filter-multi.17.query.sqlpp
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+USE test;
+// `embedding` is not an INCLUDE column, so the projection is not covered and the plan is
+// lookup-and-rerank. rating = 5 on the even ids; nearest 3 are 2, 4, 6.
+SELECT m.id, array_length(m.embedding) AS dim
+FROM MovieMulti m
+WHERE m.rating = 5
+ORDER BY ann_distance(m.embedding, [0.0, 0.0, 0.0, 0.0], "euclidean", 1.0)
+LIMIT 3;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/vector/create-index-vtree-include-filter-multi/create-index-vtree-include-filter-multi.18.plans.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/vector/create-index-vtree-include-filter-multi/create-index-vtree-include-filter-multi.18.plans.sqlpp
new file mode 100644
index 0000000..6d960d1
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/vector/create-index-vtree-include-filter-multi/create-index-vtree-include-filter-multi.18.plans.sqlpp
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+
+-- param compile-only:string=true
+-- param optimized-logical-plan:string=true
+// CBO cost estimates vary with the cluster shape (partition count / core count), which
+// differs between a local run and CI. Disable the CBO so the plan is stable everywhere.
+SET `compiler.cbo` "false";
+// The .17 query as a plan assertion: the lookup-and-rerank shape declares the one INCLUDE column the
+// predicate reads. Only a pushed predicate can read a column on this shape -- the record above comes
+// from the BTREE_SEARCH -- so `year`, `title` and `verified` are not emitted, and `rating` keeps its own
+// physical field index rather than the first one.
+USE test;
+SELECT m.id, array_length(m.embedding) AS dim
+FROM MovieMulti m
+WHERE m.rating = 5
+ORDER BY ann_distance(m.embedding, [0.0, 0.0, 0.0, 0.0], "euclidean", 1.0)
+LIMIT 3;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/vector/create-index-vtree-composite-pk/create-index-vtree-composite-pk.11.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results/vector/create-index-vtree-composite-pk/create-index-vtree-composite-pk.11.plan
index f5d04b9..c8a635d 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results/vector/create-index-vtree-composite-pk/create-index-vtree-composite-pk.11.plan
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/vector/create-index-vtree-composite-pk/create-index-vtree-composite-pk.11.plan
@@ -28,15 +28,11 @@
                           -- STABLE_SORT [$$35(ASC), $$36(ASC)]  |PARTITIONED|
                             exchange
                             -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                              project ([$$35, $$36])
-                              -- STREAM_PROJECT  |PARTITIONED|
+                              unnest-map [$$35, $$36] <- index-search("idx_cpk_emb", 8, "Default", "testcpk", "MovieCpk", false, false, false, 5, $$30, $$31, $$32, $$33, $$34)
+                              -- VECTOR_SEARCH  |PARTITIONED|
                                 exchange
                                 -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                                  unnest-map [$$35, $$36, $$37] <- index-search("idx_cpk_emb", 8, "Default", "testcpk", "MovieCpk", false, false, false, 5, $$30, $$31, $$32, $$33, $$34)
-                                  -- VECTOR_SEARCH  |PARTITIONED|
-                                    exchange
-                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                                      assign [$$30, $$31, $$32, $$33, $$34] <- [array: [ 0.0, 0.0, 0.0, 0.0 ], 3, "euclidean", 1.0, 1]
-                                      -- ASSIGN  |PARTITIONED|
-                                        empty-tuple-source
-                                        -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
+                                  assign [$$30, $$31, $$32, $$33, $$34] <- [array: [ 0.0, 0.0, 0.0, 0.0 ], 3, "euclidean", 1.0, 1]
+                                  -- ASSIGN  |PARTITIONED|
+                                    empty-tuple-source
+                                    -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/vector/create-index-vtree-composite-pk/create-index-vtree-composite-pk.8.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results/vector/create-index-vtree-composite-pk/create-index-vtree-composite-pk.8.plan
index 7272cba..fb08757 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results/vector/create-index-vtree-composite-pk/create-index-vtree-composite-pk.8.plan
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/vector/create-index-vtree-composite-pk/create-index-vtree-composite-pk.8.plan
@@ -18,15 +18,13 @@
                 -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
                   assign [$$18] <- [ordered-list-constructor($$33, $$34)] project: [$$18, $$35]
                   -- ASSIGN  |PARTITIONED|
-                    project ([$$33, $$34, $$35])
-                    -- STREAM_PROJECT  |PARTITIONED|
-                      exchange
-                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                        unnest-map [$$33, $$34, $$35, $$36] <- index-search("idx_cpk_emb", 8, "Default", "testcpk", "MovieCpk", false, false, true, 5, $$28, $$29, $$30, $$31, $$32)
-                        -- VECTOR_SEARCH  |PARTITIONED|
-                          exchange
-                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                            assign [$$28, $$29, $$30, $$31, $$32] <- [array: [ 0.0, 0.0, 0.0, 0.0 ], 3, "euclidean", 1.0, 1]
-                            -- ASSIGN  |PARTITIONED|
-                              empty-tuple-source
-                              -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
+                    exchange
+                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                      unnest-map [$$33, $$34, $$35] <- index-search("idx_cpk_emb", 8, "Default", "testcpk", "MovieCpk", false, false, true, 5, $$28, $$29, $$30, $$31, $$32)
+                      -- VECTOR_SEARCH  |PARTITIONED|
+                        exchange
+                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                          assign [$$28, $$29, $$30, $$31, $$32] <- [array: [ 0.0, 0.0, 0.0, 0.0 ], 3, "euclidean", 1.0, 1]
+                          -- ASSIGN  |PARTITIONED|
+                            empty-tuple-source
+                            -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/vector/create-index-vtree-include-filter-multi/create-index-vtree-include-filter-multi.13.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results/vector/create-index-vtree-include-filter-multi/create-index-vtree-include-filter-multi.13.plan
index 56aba3b..92081a3 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results/vector/create-index-vtree-include-filter-multi/create-index-vtree-include-filter-multi.13.plan
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/vector/create-index-vtree-include-filter-multi/create-index-vtree-include-filter-multi.13.plan
@@ -20,7 +20,7 @@
                   -- STREAM_PROJECT  |PARTITIONED|
                     exchange
                     -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                      unnest-map [$$33, $$34, $$35, $$36, $$37, $$38] <- index-search("idx_emb", 8, "Default", "test", "MovieMulti", false, false, true, 5, $$28, $$29, $$30, $$31, $$32) condition (eq({"y": $$35}, { "y": 2006 }))
+                      unnest-map [$$33, $$34, $$35] <- index-search("idx_emb", 8, "Default", "test", "MovieMulti", false, false, true, 5, $$28, $$29, $$30, $$31, $$32) condition (eq({"y": $$35}, { "y": 2006 }))
                       -- VECTOR_SEARCH  |PARTITIONED|
                         exchange
                         -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/vector/create-index-vtree-include-filter-multi/create-index-vtree-include-filter-multi.14.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results/vector/create-index-vtree-include-filter-multi/create-index-vtree-include-filter-multi.14.plan
index bbf74af..241fd98 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results/vector/create-index-vtree-include-filter-multi/create-index-vtree-include-filter-multi.14.plan
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/vector/create-index-vtree-include-filter-multi/create-index-vtree-include-filter-multi.14.plan
@@ -20,7 +20,7 @@
                   -- STREAM_PROJECT  |PARTITIONED|
                     exchange
                     -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                      unnest-map [$$36, $$37, $$38, $$39, $$40, $$41] <- index-search("idx_emb", 8, "Default", "test", "MovieMulti", false, false, true, 5, $$31, $$32, $$33, $$34, $$35) condition (and(gt(if-null($$38, 0), 2005), eq($$39, 5)))
+                      unnest-map [$$36, $$37, $$38, $$39] <- index-search("idx_emb", 8, "Default", "test", "MovieMulti", false, false, true, 5, $$31, $$32, $$33, $$34, $$35) condition (and(gt(if-null($$38, 0), 2005), eq($$39, 5)))
                       -- VECTOR_SEARCH  |PARTITIONED|
                         exchange
                         -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/vector/create-index-vtree-include-filter-multi/create-index-vtree-include-filter-multi.16.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results/vector/create-index-vtree-include-filter-multi/create-index-vtree-include-filter-multi.16.plan
index ec4a705..ec7c706 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results/vector/create-index-vtree-include-filter-multi/create-index-vtree-include-filter-multi.16.plan
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/vector/create-index-vtree-include-filter-multi/create-index-vtree-include-filter-multi.16.plan
@@ -22,7 +22,7 @@
                     -- STREAM_PROJECT  |PARTITIONED|
                       exchange
                       -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                        unnest-map [$$36, $$37, $$38, $$39, $$40, $$41] <- index-search("idx_emb", 8, "Default", "test", "MovieMulti", false, false, true, 5, $$31, $$32, $$33, $$34, $$35) condition (gt($$38, 2005))
+                        unnest-map [$$36, $$37, $$38, $$40] <- index-search("idx_emb", 8, "Default", "test", "MovieMulti", false, false, true, 5, $$31, $$32, $$33, $$34, $$35) condition (gt($$38, 2005))
                         -- VECTOR_SEARCH  |PARTITIONED|
                           exchange
                           -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/vector/create-index-vtree-include-filter-multi/create-index-vtree-include-filter-multi.17.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/vector/create-index-vtree-include-filter-multi/create-index-vtree-include-filter-multi.17.adm
new file mode 100644
index 0000000..af259d2
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/vector/create-index-vtree-include-filter-multi/create-index-vtree-include-filter-multi.17.adm
@@ -0,0 +1,3 @@
+{ "id": 2, "dim": 4 }
+{ "id": 4, "dim": 4 }
+{ "id": 6, "dim": 4 }
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/vector/create-index-vtree-include-filter-multi/create-index-vtree-include-filter-multi.18.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results/vector/create-index-vtree-include-filter-multi/create-index-vtree-include-filter-multi.18.plan
new file mode 100644
index 0000000..4d51b99
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/vector/create-index-vtree-include-filter-multi/create-index-vtree-include-filter-multi.18.plan
@@ -0,0 +1,44 @@
+distribute result [$$21]
+-- DISTRIBUTE_RESULT  |UNPARTITIONED|
+  exchange
+  -- ONE_TO_ONE_EXCHANGE  |UNPARTITIONED|
+    limit 3
+    -- STREAM_LIMIT  |UNPARTITIONED|
+      project ([$$21])
+      -- STREAM_PROJECT  |PARTITIONED|
+        exchange
+        -- SORT_MERGE_EXCHANGE [$$25(ASC) ]  |PARTITIONED|
+          limit 3
+          -- STREAM_LIMIT  |PARTITIONED|
+            exchange
+            -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+              order (topK: 3) (ASC, $$25)
+              -- STABLE_SORT [topK: 3] [$$25(ASC)]  |PARTITIONED|
+                exchange
+                -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                  assign [$$25, $$21] <- [euclidean-distance($$26, array: [ 0.0, 0.0, 0.0, 0.0 ]), {"id": $$27, "dim": len($$26)}] project: [$$21, $$25]
+                  -- ASSIGN  |PARTITIONED|
+                    assign [$$26] <- [$$m.getField(5)] project: [$$27, $$26]
+                    -- ASSIGN  |PARTITIONED|
+                      exchange
+                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                        unnest-map [$$27, $$m] <- index-search("MovieMulti", 0, "Default", "test", "MovieMulti", false, false, 1, $$35, 1, $$35, true, true, true)
+                        -- BTREE_SEARCH  |PARTITIONED|
+                          exchange
+                          -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                            order (ASC, $$35)
+                            -- STABLE_SORT [$$35(ASC)]  |PARTITIONED|
+                              exchange
+                              -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                project ([$$35])
+                                -- STREAM_PROJECT  |PARTITIONED|
+                                  exchange
+                                  -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                    unnest-map [$$35, $$37] <- index-search("idx_emb", 8, "Default", "test", "MovieMulti", false, false, false, 5, $$30, $$31, $$32, $$33, $$34) condition (eq($$37, 5))
+                                    -- VECTOR_SEARCH  |PARTITIONED|
+                                      exchange
+                                      -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
+                                        assign [$$30, $$31, $$32, $$33, $$34] <- [array: [ 0.0, 0.0, 0.0, 0.0 ], 3, "euclidean", 1.0, 1]
+                                        -- ASSIGN  |PARTITIONED|
+                                          empty-tuple-source
+                                          -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/vector/create-index-vtree-include-filter-multi/create-index-vtree-include-filter-multi.7.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results/vector/create-index-vtree-include-filter-multi/create-index-vtree-include-filter-multi.7.plan
index 3a62e0d..cb3862b 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results/vector/create-index-vtree-include-filter-multi/create-index-vtree-include-filter-multi.7.plan
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/vector/create-index-vtree-include-filter-multi/create-index-vtree-include-filter-multi.7.plan
@@ -20,7 +20,7 @@
                   -- STREAM_PROJECT  |PARTITIONED|
                     exchange
                     -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                      unnest-map [$$35, $$36, $$37, $$38, $$39, $$40] <- index-search("idx_emb", 8, "Default", "test", "MovieMulti", false, false, true, 5, $$30, $$31, $$32, $$33, $$34) condition (and(gt($$37, 2005), eq($$38, 5)))
+                      unnest-map [$$35, $$36, $$37, $$38] <- index-search("idx_emb", 8, "Default", "test", "MovieMulti", false, false, true, 5, $$30, $$31, $$32, $$33, $$34) condition (and(gt($$37, 2005), eq($$38, 5)))
                       -- VECTOR_SEARCH  |PARTITIONED|
                         exchange
                         -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/vector/create-index-vtree-include-nested/create-index-vtree-include-nested.16.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results/vector/create-index-vtree-include-nested/create-index-vtree-include-nested.16.plan
index 8cf07ef..05b1b5a 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results/vector/create-index-vtree-include-nested/create-index-vtree-include-nested.16.plan
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/vector/create-index-vtree-include-nested/create-index-vtree-include-nested.16.plan
@@ -28,15 +28,11 @@
                           -- STABLE_SORT [$$34(ASC)]  |PARTITIONED|
                             exchange
                             -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                              project ([$$34])
-                              -- STREAM_PROJECT  |PARTITIONED|
+                              unnest-map [$$34] <- index-search("idx_emb", 8, "Default", "test", "MovieSmall", false, false, false, 5, $$29, $$30, $$31, $$32, $$33)
+                              -- VECTOR_SEARCH  |PARTITIONED|
                                 exchange
                                 -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                                  unnest-map [$$34, $$35] <- index-search("idx_emb", 8, "Default", "test", "MovieSmall", false, false, false, 5, $$29, $$30, $$31, $$32, $$33)
-                                  -- VECTOR_SEARCH  |PARTITIONED|
-                                    exchange
-                                    -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                                      assign [$$29, $$30, $$31, $$32, $$33] <- [array: [ 0.0, 0.0, 0.0, 0.0 ], 3, "euclidean", 1.0, 1]
-                                      -- ASSIGN  |PARTITIONED|
-                                        empty-tuple-source
-                                        -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
+                                  assign [$$29, $$30, $$31, $$32, $$33] <- [array: [ 0.0, 0.0, 0.0, 0.0 ], 3, "euclidean", 1.0, 1]
+                                  -- ASSIGN  |PARTITIONED|
+                                    empty-tuple-source
+                                    -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/vector/create-index-vtree-nested/create-index-vtree-nested.5.plan b/asterixdb/asterix-app/src/test/resources/runtimets/results/vector/create-index-vtree-nested/create-index-vtree-nested.5.plan
index 8500399..92d0bdb 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results/vector/create-index-vtree-nested/create-index-vtree-nested.5.plan
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/vector/create-index-vtree-nested/create-index-vtree-nested.5.plan
@@ -16,15 +16,11 @@
               -- STABLE_SORT [topK: 3] [$$31(ASC)]  |PARTITIONED|
                 exchange
                 -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                  project ([$$30, $$31])
-                  -- STREAM_PROJECT  |PARTITIONED|
+                  unnest-map [$$30, $$31] <- index-search("idx_emb", 8, "Default", "test", "MovieNested", false, false, true, 5, $$25, $$26, $$27, $$28, $$29)
+                  -- VECTOR_SEARCH  |PARTITIONED|
                     exchange
                     -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                      unnest-map [$$30, $$31, $$32] <- index-search("idx_emb", 8, "Default", "test", "MovieNested", false, false, true, 5, $$25, $$26, $$27, $$28, $$29)
-                      -- VECTOR_SEARCH  |PARTITIONED|
-                        exchange
-                        -- ONE_TO_ONE_EXCHANGE  |PARTITIONED|
-                          assign [$$25, $$26, $$27, $$28, $$29] <- [array: [ 0.0, 0.0, 0.0, 0.0 ], 3, "euclidean", 1.0, 1]
-                          -- ASSIGN  |PARTITIONED|
-                            empty-tuple-source
-                            -- EMPTY_TUPLE_SOURCE  |PARTITIONED|
+                      assign [$$25, $$26, $$27, $$28, $$29] <- [array: [ 0.0, 0.0, 0.0, 0.0 ], 3, "euclidean", 1.0, 1]
+                      -- ASSIGN  |PARTITIONED|
+                        empty-tuple-source
+                        -- EMPTY_TUPLE_SOURCE  |PARTITIONED|

-- 
To view, visit https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/21866?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: I2c9095f9c37ea5bad05fae7acfb5678e269b9236
Gerrit-Change-Number: 21866
Gerrit-PatchSet: 1
Gerrit-Owner: Ali Alsuliman <al...@gmail.com>