airborne12 commented on code in PR #69292:
URL: https://github.com/apache/doris/pull/69292#discussion_r4334856956


##########
fe/fe-core/src/main/java/org/apache/doris/nereids/glue/translator/PhysicalPlanTranslator.java:
##########
@@ -1543,7 +1544,19 @@ public PlanFragment visitPhysicalFilter(PhysicalFilter<? 
extends Plan> filter, P
                     // i.e, the physical filter's nereids's id will be mapped 
to final plan's scan node
                     addPlanRoot(inputFragment, planNode, filter);
                 }
-                addConjunctsToPlanNode(filter, planNode, context);
+                // The scan fills its score column only after it evaluated its 
own conjuncts, so a
+                // conjunct on the score is evaluated above the scan.
+                Set<Expression> scoreConjuncts = planNode instanceof 
OlapScanNode
+                        && filter.child(0) instanceof PhysicalOlapScan
+                        ? conjunctsOnScore(filter.getConjuncts(), 
(PhysicalOlapScan) filter.child(0))
+                        : ImmutableSet.of();
+                addConjunctsToPlanNode(filter, 
Sets.difference(filter.getConjuncts(), scoreConjuncts),
+                        planNode, context);
+                if (!scoreConjuncts.isEmpty()) {
+                    planNode = createSelectNode(filter, planNode, context);
+                    addPlanRoot(inputFragment, planNode, filter);
+                    addConjunctsToPlanNode(filter, scoreConjuncts, planNode, 
context);

Review Comment:
   Addressed in 2e999dbfe3 by narrowing what score() supports. The only filter 
on the score that can reach the scan is now the minimum score `score() > x` / 
`score() >= x`, which the scan applies itself before the ANN TopN 
(`testScoreRangeKeepsAnnTopN`). Other filters on the score, such as `score() < 
0.5` in this example, are rejected (`score() predicate in WHERE clause must be 
in the form of 'score() > literal' or 'score() >= literal'`), so no residual 
score predicate runs above an ANN-truncated scan.
   



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/CheckScoreUsage.java:
##########
@@ -18,98 +18,187 @@
 package org.apache.doris.nereids.rules.rewrite;
 
 import org.apache.doris.nereids.exceptions.AnalysisException;
-import org.apache.doris.nereids.rules.Rule;
-import org.apache.doris.nereids.rules.RuleType;
-import org.apache.doris.nereids.trees.expressions.Alias;
+import org.apache.doris.nereids.jobs.JobContext;
+import org.apache.doris.nereids.properties.OrderKey;
+import org.apache.doris.nereids.trees.expressions.ExprId;
 import org.apache.doris.nereids.trees.expressions.Expression;
+import org.apache.doris.nereids.trees.expressions.Match;
+import org.apache.doris.nereids.trees.expressions.NamedExpression;
+import org.apache.doris.nereids.trees.expressions.StatementScopeIdGenerator;
 import org.apache.doris.nereids.trees.expressions.functions.scalar.Score;
+import org.apache.doris.nereids.trees.expressions.functions.scalar.Search;
 import org.apache.doris.nereids.trees.plans.Plan;
 import org.apache.doris.nereids.trees.plans.logical.LogicalAggregate;
-import org.apache.doris.nereids.trees.plans.logical.LogicalOlapScan;
+import org.apache.doris.nereids.trees.plans.logical.LogicalApply;
+import org.apache.doris.nereids.trees.plans.logical.LogicalCTEProducer;
+import org.apache.doris.nereids.trees.plans.logical.LogicalFilter;
 import org.apache.doris.nereids.trees.plans.logical.LogicalProject;
+import 
org.apache.doris.nereids.trees.plans.logical.LogicalRecursiveUnionAnchor;
+import 
org.apache.doris.nereids.trees.plans.logical.LogicalRecursiveUnionProducer;
+import org.apache.doris.nereids.trees.plans.logical.LogicalSetOperation;
+import org.apache.doris.nereids.trees.plans.logical.LogicalSort;
+import org.apache.doris.nereids.trees.plans.logical.LogicalSubQueryAlias;
+import org.apache.doris.nereids.trees.plans.logical.LogicalView;
+import org.apache.doris.nereids.trees.plans.visitor.CustomRewriter;
 
 import com.google.common.collect.ImmutableList;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
+import com.google.common.collect.ImmutableSet;
 
 import java.util.List;
+import java.util.Optional;
 
 /**
- * Check score function usage in project and aggregate without proper 
optimization context.
+ * Tie every score() call to the MATCH and SEARCH predicates it scores.
  *
- * This rule ensures that score() function is used only in contexts where it 
can be optimized,
- * requiring WHERE clause with MATCH function, ORDER BY and LIMIT.
+ * <p>score() is the relevance of a row for the MATCH and SEARCH predicates in 
the WHERE clause of
+ * its own SELECT. This rewriter runs first in rewrite, while subqueries, 
views and CTEs still have
+ * their own plan nodes. It gives every SELECT that calls score() a new score 
id and marks with it
+ * the score() calls of the SELECT and the MATCH and SEARCH predicates of its 
filters.
+ * BindScoreToOlapScan later binds the calls to the scan that those predicates 
filter.
+ *
+ * <p>A MATCH or SEARCH without a score id only filters rows: it adds nothing 
to the score of its
+ * scan, even when a filter of an outer SELECT is pushed down onto that scan.
  */
-public class CheckScoreUsage implements RewriteRuleFactory {
-    private static final Logger LOG = 
LogManager.getLogger(CheckScoreUsage.class);
+public class CheckScoreUsage implements CustomRewriter {
+    public static final String NO_SCORING_PREDICATE_ERROR =
+            "score() requires a MATCH or SEARCH predicate in the WHERE clause 
of the same SELECT";
+    public static final String UNSUPPORTED_CLAUSE_ERROR = "score() can only be 
used in the SELECT list "
+            + "and in the WHERE, GROUP BY, HAVING, QUALIFY and ORDER BY 
clauses";
 
     @Override
-    public List<Rule> buildRules() {
-        return ImmutableList.of(
-            logicalProject(any())
-                .when(project -> {
-                    boolean hasScore = hasScoreFunction(project);
-                    boolean isOptimized = isScoreAlreadyOptimized(project);
-                    LOG.debug("Project check - hasScore: " + hasScore + ", 
isOptimized: " + isOptimized);
-                    return hasScore && !isOptimized;
-                })
-                .then(project -> {
-                    throw new AnalysisException(
-                            "score() function requires WHERE clause with MATCH 
function, "
-                            + "ORDER BY and LIMIT for optimization");
-                }).toRule(RuleType.CHECK_SCORE_USAGE),
-
-            logicalAggregate(any())
-                .when(this::hasScoreInAggregate)
-                .then(agg -> {
-                    throw new AnalysisException(
-                            "score() function cannot be used in aggregate 
functions. "
-                            + "score() requires WHERE clause with MATCH 
function, "
-                            + "ORDER BY and LIMIT for optimization");
-                }).toRule(RuleType.CHECK_SCORE_USAGE)
-        );
+    public Plan rewriteRoot(Plan plan, JobContext jobContext) {
+        if (!containsScore(plan)) {
+            return plan;
+        }
+        return tagSelect(plan);
+    }
+
+    /** Whether any plan node of the tree calls score() in its own 
expressions. */
+    public static boolean containsScore(Plan plan) {
+        return plan.anyMatch(node -> ((Plan) node).getExpressions().stream()
+                .anyMatch(expression -> expression.containsType(Score.class)));
     }
 
-    private boolean hasScoreFunction(LogicalProject<?> project) {
-        return project.getProjects().stream()
-                .anyMatch(projection -> {
-                    if (projection instanceof Alias) {
-                        return containsScoreFunction(((Alias) 
projection).child());
-                    }
-                    return false;
-                });
+    // A SELECT consists of the plan nodes from its root down to the roots of 
its subqueries, views,
+    // CTE producers and set operation inputs, which are SELECTs of their own.
+    private static boolean startsSelect(Plan parent, int childIndex) {
+        return parent instanceof LogicalSubQueryAlias || parent instanceof 
LogicalView
+                || parent instanceof LogicalCTEProducer || parent instanceof 
LogicalSetOperation
+                || parent instanceof LogicalRecursiveUnionAnchor || parent 
instanceof LogicalRecursiveUnionProducer
+                || parent instanceof LogicalApply && childIndex == 1;
     }
 
-    private boolean hasScoreInAggregate(LogicalAggregate<?> agg) {
-        boolean hasScoreInOutput = agg.getOutputExpressions().stream()
-                .anyMatch(this::containsScoreFunction);
+    private Plan tagSelect(Plan root) {
+        SelectSummary select = new SelectSummary();
+        summarize(root, select);
+        if (!select.callsScore) {
+            return tag(root, Optional.empty());
+        }
+        if (!select.hasScoringPredicate) {
+            throw new AnalysisException(NO_SCORING_PREDICATE_ERROR);
+        }
+        if (select.unsupportedClause) {
+            throw new AnalysisException(UNSUPPORTED_CLAUSE_ERROR);
+        }
+        return tag(root, Optional.of(StatementScopeIdGenerator.newExprId()));
+    }
 
-        boolean hasScoreInGroupBy = agg.getGroupByExpressions().stream()
-                .anyMatch(this::containsScoreFunction);
+    private void summarize(Plan plan, SelectSummary select) {
+        if (plan.getExpressions().stream().anyMatch(expression -> 
expression.containsType(Score.class))) {
+            select.callsScore = true;
+            select.unsupportedClause |= !canCallScore(plan);
+        }
+        if (plan instanceof LogicalFilter && ((LogicalFilter<?>) 
plan).getConjuncts().stream()
+                .anyMatch(conjunct -> conjunct.containsType(Match.class, 
Search.class))) {
+            select.hasScoringPredicate = true;
+        }
+        for (int i = 0; i < plan.arity(); i++) {
+            if (!startsSelect(plan, i)) {
+                summarize(plan.child(i), select);
+            }
+        }
+    }
 
-        return hasScoreInOutput || hasScoreInGroupBy;
+    // HAVING and QUALIFY are filters by now. Join conditions are rejected by 
CheckAnalysis.
+    private static boolean canCallScore(Plan plan) {
+        return plan instanceof LogicalProject || plan instanceof LogicalFilter
+                || plan instanceof LogicalAggregate || plan instanceof 
LogicalSort;
     }
 
-    private boolean containsScoreFunction(Expression expr) {
-        if (expr instanceof Score) {
-            return true;
+    private Plan tag(Plan plan, Optional<ExprId> scoreId) {
+        ImmutableList.Builder<Plan> newChildren = 
ImmutableList.builderWithExpectedSize(plan.arity());
+        boolean changed = false;
+        for (int i = 0; i < plan.arity(); i++) {
+            Plan child = plan.child(i);
+            Plan newChild = startsSelect(plan, i) ? tagSelect(child) : 
tag(child, scoreId);
+            changed |= newChild != child;
+            newChildren.add(newChild);
         }
-        return expr.children().stream().anyMatch(this::containsScoreFunction);
+        Plan newPlan = changed ? plan.withChildren(newChildren.build()) : plan;
+        return scoreId.isPresent() ? tagExpressions(newPlan, scoreId.get()) : 
newPlan;
     }
 
-    private boolean isScoreAlreadyOptimized(LogicalProject<?> project) {
-        Plan child = project.child();
-        if (child instanceof LogicalOlapScan) {
-            LogicalOlapScan scan = (LogicalOlapScan) child;
-            return scan.getVirtualColumns().stream()
-                    .anyMatch(virtualCol -> {
-                        if (virtualCol instanceof Alias) {
-                            Expression childExpr = ((Alias) 
virtualCol).child();
-                            return containsScoreFunction(childExpr);
-                        }
-                        return false;
-                    });
+    private Plan tagExpressions(Plan plan, ExprId scoreId) {
+        if (plan.getExpressions().stream()
+                .noneMatch(expression -> expression.containsType(Score.class, 
Match.class, Search.class))) {
+            return plan;
         }
-        return false;
+        if (plan instanceof LogicalFilter) {
+            LogicalFilter<?> filter = (LogicalFilter<?>) plan;
+            return filter.withConjuncts(filter.getConjuncts().stream()
+                    .map(conjunct -> tagScoreAndPredicates(conjunct, scoreId))

Review Comment:
   Addressed in 2e999dbfe3: HAVING and QUALIFY of a SELECT that calls score() 
can no longer contain MATCH, SEARCH or score() (`score(), MATCH and SEARCH 
cannot be used in HAVING or QUALIFY of a SELECT that calls score()`), and an 
aggregating SELECT can only use score() as the minimum score in WHERE. Both are 
covered by `testHavingAndQualifyAreRejected` and `test_score_filter_usage`.
   



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/glue/translator/ExpressionTranslator.java:
##########
@@ -280,9 +282,22 @@ public Expr visitMatch(Match match, PlanTranslatorContext 
context) {
         MatchPredicate matchPredicate = new MatchPredicate(op, 
match.left().accept(this, context),
                 match.right().accept(this, context), 
match.getDataType().toCatalogDataType(),
                 NullableMode.DEPEND_ON_ARGUMENT, invertedIndex, 
match.nullable(), analyzer);
+        matchPredicate.setCollectScore(collectsScore(match.getScoreId(), 
context));
         return matchPredicate;
     }
 
+    // A MATCH or SEARCH of the SELECT that calls score() adds to the score of 
the scan that it filters,
+    // see CheckScoreUsage, so that scan has to evaluate it. Any other one 
only filters rows.
+    private static boolean collectsScore(Optional<ExprId> predicateScoreId, 
PlanTranslatorContext context) {
+        if (!predicateScoreId.isPresent()) {
+            return !context.getScanScoreId().isPresent();
+        }
+        if (!predicateScoreId.equals(context.getScanScoreId())) {
+            throw new 
AnalysisException(BindScoreToOlapScan.UNEVALUATED_PREDICATE_ERROR);

Review Comment:
   Addressed in 2e999dbfe3: a SELECT that calls score() must now read exactly 
one table, so it has no join through which its MATCH or SEARCH could be 
inferred onto another scan (`testUnsupportedSelectIsRejected`). The 
per-predicate score ids and the translator check that rejected this query are 
removed.
   



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/BindScoreToOlapScan.java:
##########
@@ -0,0 +1,292 @@
+// 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.
+
+package org.apache.doris.nereids.rules.rewrite;
+
+import org.apache.doris.nereids.exceptions.AnalysisException;
+import org.apache.doris.nereids.jobs.JobContext;
+import org.apache.doris.nereids.properties.OrderKey;
+import org.apache.doris.nereids.trees.expressions.Alias;
+import org.apache.doris.nereids.trees.expressions.ExprId;
+import org.apache.doris.nereids.trees.expressions.Expression;
+import org.apache.doris.nereids.trees.expressions.Match;
+import org.apache.doris.nereids.trees.expressions.NamedExpression;
+import org.apache.doris.nereids.trees.expressions.Slot;
+import org.apache.doris.nereids.trees.expressions.functions.scalar.Score;
+import org.apache.doris.nereids.trees.expressions.functions.scalar.Search;
+import org.apache.doris.nereids.trees.plans.Plan;
+import org.apache.doris.nereids.trees.plans.RelationId;
+import org.apache.doris.nereids.trees.plans.logical.LogicalAggregate;
+import org.apache.doris.nereids.trees.plans.logical.LogicalFilter;
+import org.apache.doris.nereids.trees.plans.logical.LogicalOlapScan;
+import org.apache.doris.nereids.trees.plans.logical.LogicalProject;
+import org.apache.doris.nereids.trees.plans.logical.LogicalSort;
+import org.apache.doris.nereids.trees.plans.visitor.CustomRewriter;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Function;
+
+/**
+ * Bind every score() call to the score column of the scan whose rows its 
MATCH and SEARCH
+ * predicates filter.
+ *
+ * <p>CheckScoreUsage marked each score() call and the MATCH and SEARCH 
predicates of the same
+ * SELECT with one score id. This rewriter runs after views and CTEs are 
inlined, and before window
+ * expressions are extracted and filters are pushed down. For each score id it 
finds the one olap
+ * scan whose columns those predicates read, adds to the scan a hidden score 
column computed for
+ * every row the scan returns, and replaces the score() calls with that 
column, passing it through
+ * the projections in between. From then on the score is an ordinary column 
for windows, joins and
+ * filter push down. PushDownScoreRangeIntoOlapScan and 
PushDownScoreTopNIntoOlapScan only reduce
+ * how much of it the scan has to compute.
+ */
+public class BindScoreToOlapScan implements CustomRewriter {
+    public static final String UNRESOLVED_SCAN_ERROR = "score() requires the 
MATCH and SEARCH predicates "
+            + "of its WHERE clause to filter the columns of one table, not of 
a set operation, an aggregation "
+            + "or a CTE that is used more than once";
+    public static final String SHARED_SCAN_ERROR = "score() is called by two 
SELECTs whose MATCH or SEARCH "
+            + "predicates filter the same table, which computes only one 
score: call score() in one of them";
+    public static final String SCORE_NOT_AVAILABLE_ERROR = "score() is not 
available above GROUP BY: "
+            + "use it in an aggregate function or as a grouping key";
+    // Thrown by ExpressionTranslator, which finds where each predicate ends 
up.
+    public static final String UNEVALUATED_PREDICATE_ERROR = "score() requires 
the scan of its table to "
+            + "evaluate each MATCH and SEARCH predicate of its WHERE clause, 
which it cannot for a predicate in an "
+            + "OR with conditions on other tables, or on the result of a 
LIMIT, a window function or the nullable "
+            + "side of an outer join";
+
+    private final Map<RelationId, ExprId> scanScoreIds = new HashMap<>();
+    private final Map<RelationId, Alias> scoreColumns = new HashMap<>();
+
+    @Override
+    public Plan rewriteRoot(Plan plan, JobContext jobContext) {
+        if (!CheckScoreUsage.containsScore(plan)) {
+            return plan;
+        }
+        return bind(plan, ImmutableMap.of());
+    }
+
+    // scope maps every score id that an ancestor calls to the scan that 
computes it.
+    private Plan bind(Plan plan, Map<ExprId, RelationId> scope) {
+        Map<ExprId, RelationId> childScope = scope;
+        for (ExprId scoreId : scoreIds(plan)) {
+            if (!childScope.containsKey(scoreId)) {
+                RelationId scan = resolveScan(plan, scoreId);
+                ExprId boundScoreId = scanScoreIds.putIfAbsent(scan, scoreId);
+                if (boundScoreId != null && !boundScoreId.equals(scoreId)) {
+                    throw new AnalysisException(SHARED_SCAN_ERROR);
+                }
+                Map<ExprId, RelationId> newScope = new HashMap<>(childScope);
+                newScope.put(scoreId, scan);
+                childScope = newScope;
+            }
+        }
+        ImmutableList.Builder<Plan> newChildren = 
ImmutableList.builderWithExpectedSize(plan.arity());
+        boolean changed = false;
+        for (Plan child : plan.children()) {
+            Plan newChild = bind(child, childScope);
+            changed |= newChild != child;
+            newChildren.add(newChild);
+        }
+        Plan newPlan = changed ? plan.withChildren(newChildren.build()) : plan;
+        if (newPlan instanceof LogicalOlapScan) {
+            return addScoreColumn((LogicalOlapScan) newPlan);
+        }
+        if (newPlan instanceof LogicalProject) {
+            newPlan = passScoreColumns((LogicalProject<?>) newPlan, scope);
+        }
+        return replaceScore(newPlan, childScope);
+    }
+
+    private static Set<ExprId> scoreIds(Plan plan) {
+        Set<ExprId> scoreIds = new LinkedHashSet<>();
+        for (Expression expression : plan.getExpressions()) {
+            for (Score score : 
expression.<Score>collect(Score.class::isInstance)) {
+                scoreIds.add(score.getScoreId().get());
+            }
+        }
+        return scoreIds;
+    }
+
+    // The olap scan whose rows the MATCH and SEARCH predicates with the score 
id filter.
+    private static RelationId resolveScan(Plan plan, ExprId scoreId) {
+        Set<RelationId> scans = new HashSet<>();
+        collectScoredScans(plan, scoreId, scans);
+        if (scans.size() != 1) {
+            throw new AnalysisException(UNRESOLVED_SCAN_ERROR);
+        }
+        return scans.iterator().next();
+    }
+
+    private static void collectScoredScans(Plan plan, ExprId scoreId, 
Set<RelationId> scans) {
+        if (plan instanceof LogicalFilter) {
+            Plan input = plan.child(0);
+            for (Expression conjunct : ((LogicalFilter<?>) 
plan).getConjuncts()) {
+                for (Expression predicate : conjunct.<Expression>collect(e -> 
definesScore((Expression) e, scoreId))) {
+                    if (predicate instanceof Search) {
+                        // search() reads its fields from the scan below it, 
see RewriteSearchToSlots.
+                        collectOlapScans(input, scans);
+                    } else {
+                        for (Slot slot : predicate.getInputSlots()) {
+                            scans.add(findScan(input, slot.getExprId())
+                                    .orElseThrow(() -> new 
AnalysisException(UNRESOLVED_SCAN_ERROR)));
+                        }
+                    }
+                }
+            }
+        }
+        for (Plan child : plan.children()) {
+            collectScoredScans(child, scoreId, scans);
+        }
+    }
+
+    private static boolean definesScore(Expression expression, ExprId scoreId) 
{
+        Optional<ExprId> predicateScoreId = Optional.empty();
+        if (expression instanceof Match) {
+            predicateScoreId = ((Match) expression).getScoreId();
+        } else if (expression instanceof Search) {
+            predicateScoreId = ((Search) expression).getScoreId();
+        }
+        return predicateScoreId.isPresent() && 
predicateScoreId.get().equals(scoreId);
+    }
+
+    private static void collectOlapScans(Plan plan, Set<RelationId> scans) {
+        if (plan instanceof LogicalOlapScan) {
+            scans.add(((LogicalOlapScan) plan).getRelationId());
+        }
+        for (Plan child : plan.children()) {
+            collectOlapScans(child, scans);
+        }
+    }
+
+    // The olap scan that outputs the slot as one of its columns, following 
projections that pass
+    // or rename it. Empty when another operator computes the slot, e.g. an 
aggregation.
+    private static Optional<RelationId> findScan(Plan plan, ExprId slotId) {
+        if (plan instanceof LogicalOlapScan) {
+            return plan.getOutputExprIdSet().contains(slotId)
+                    ? Optional.of(((LogicalOlapScan) plan).getRelationId()) : 
Optional.empty();
+        }
+        if (plan instanceof LogicalProject) {
+            for (NamedExpression project : ((LogicalProject<?>) 
plan).getProjects()) {
+                if (!project.getExprId().equals(slotId)) {
+                    continue;
+                }
+                if (project instanceof Slot) {
+                    return findScan(plan.child(0), slotId);
+                }
+                Expression producer = ((Alias) project).child();
+                return producer instanceof Slot
+                        ? findScan(plan.child(0), ((Slot) 
producer).getExprId()) : Optional.empty();
+            }
+            return Optional.empty();
+        }
+        if (plan instanceof LogicalAggregate) {
+            return Optional.empty();
+        }
+        for (Plan child : plan.children()) {
+            if (child.getOutputExprIdSet().contains(slotId)) {
+                return findScan(child, slotId);
+            }
+        }
+        return Optional.empty();
+    }
+
+    private LogicalOlapScan addScoreColumn(LogicalOlapScan scan) {
+        ExprId scoreId = scanScoreIds.get(scan.getRelationId());
+        if (scoreId == null) {
+            return scan;
+        }
+        Alias scoreColumn = new Alias(new Score().withScoreId(scoreId), 
"__score");
+        scoreColumns.put(scan.getRelationId(), scoreColumn);
+        // A zero score limit scores every row the scan returns.
+        return scan.appendVirtualColumnsAndTopN(ImmutableList.of(scoreColumn),

Review Comment:
   Addressed in 2e999dbfe3: the score column can no longer reach a join. A 
SELECT that calls score() reads one table, and a scored SELECT nested in a 
subquery, view, CTE or set operation needs `ORDER BY score() LIMIT`, so a join 
can only see its score above that TopN, which runtime filters do not cross 
(`testNestedScoreRequiresTopN`).
   



-- 
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]

Reply via email to