This is an automated email from the ASF dual-hosted git repository.

morrySnow pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/master by this push:
     new 91d72f6efad # [fix](topn lazy materialization) Disable topn lazy 
materialization on non-light-schema-change tables (#65415)
91d72f6efad is described below

commit 91d72f6efadaa703aba53b98a8eca7524f9b4c91
Author: Lijia Liu <[email protected]>
AuthorDate: Fri Jul 24 14:51:24 2026 +0800

    # [fix](topn lazy materialization) Disable topn lazy materialization on 
non-light-schema-change tables (#65415)
    
    A top-N query that emits a non-order-by column fails on a
    **non-light-schema-change** OLAP table (a table created/upgraded with
    `light_schema_change = false`, where every column's `uniqueId` is `-1`):
    
    ```sql
    select id, name from tbl order by createdate desc limit 10;
    ```
    
    ```
    ERROR 1105 (HY000): errCode = 2, detailMessage =
    [INTERNAL_ERROR]field name is invalid. field=__DORIS_GLOBAL_ROWID_COL__tbl,
    field_name_to_index=[...], col_unique_id=2147483647
    ```
    
    Fix: disable topn lazy materialization for non-light-schema-change OLAP
    tables in MaterializeProbeVisitor and fall back to normal topn (the
    working two-phase read path). A new helper
    supportOlapTopnLazyMaterialize() consolidates the existing AGG_KEYS
    exclusion with the new light_schema_change requirement, applied at
    visitPhysicalOlapScan, visitPhysicalCatalogRelation and
    visitPhysicalFilter.
    
    Add regression test topn_lazy_light_schema_change verifying:
    - light_schema_change=false: no lazy materialization in the plan,
    correct results.
    - light_schema_change=true: lazy materialization still applies, correct
    results.
    
    ## Behavior after the fix
    
    | Table | Plan | Result |
    |-------|------|--------|
    | `light_schema_change = false` | plain `PhysicalOlapScan` (no lazy) →
    safe two-phase read | correct values |
    | `light_schema_change = true` | `PhysicalLazyMaterialize` /
    `PhysicalLazyMaterializeOlapScan` (unchanged) | correct values |
---
 .../post/materialize/MaterializeProbeVisitor.java  |  36 ++++++-
 .../materialize/MaterializeProbeVisitorTest.java   |   2 +
 .../topn_lazy/topn_lazy_light_schema_change.out    |  11 ++
 .../topn_lazy/topn_lazy_light_schema_change.groovy | 119 +++++++++++++++++++++
 4 files changed, 164 insertions(+), 4 deletions(-)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/processor/post/materialize/MaterializeProbeVisitor.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/processor/post/materialize/MaterializeProbeVisitor.java
index 0a497896600..9bcdedb01e6 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/processor/post/materialize/MaterializeProbeVisitor.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/processor/post/materialize/MaterializeProbeVisitor.java
@@ -87,9 +87,9 @@ public class MaterializeProbeVisitor extends 
DefaultPlanVisitor<Optional<Materia
     public Optional<MaterializeSource> visitPhysicalFilter(PhysicalFilter<? 
extends Plan> filter,
                                                            ProbeContext 
context) {
         if (SessionVariable.getTopNLazyMaterializationUsingIndex() && 
filter.child() instanceof PhysicalOlapScan) {
-            // agg table do not support lazy materialize
+            // agg table / non-light-schema-change table do not support lazy 
materialize
             OlapTable table = ((PhysicalOlapScan) filter.child()).getTable();
-            if (KeysType.AGG_KEYS.equals(table.getKeysType())) {
+            if (!supportOlapTopnLazyMaterialize(table)) {
                 return Optional.empty();
             }
             if (filter.getInputSlots().contains(context.slot)) {
@@ -133,6 +133,34 @@ public class MaterializeProbeVisitor extends 
DefaultPlanVisitor<Optional<Materia
             return (hmsExternalTable.getDlaType() == DLAType.HIVE && 
hmsExternalTable.supportedHiveTopNLazyTable())
                     || hmsExternalTable.getDlaType() == DLAType.ICEBERG;
         }
+        if (relation.getTable() instanceof OlapTable) {
+            return supportOlapTopnLazyMaterialize((OlapTable) 
relation.getTable());
+        }
+        return true;
+    }
+
+    /**
+     * Whether an OLAP table can perform topn lazy materialization.
+     *
+     * <p>Two hard requirements:
+     * <ul>
+     *   <li>Not an AGG_KEYS table: aggregate tables cannot locate a single 
source row for a value.</li>
+     *   <li>light_schema_change is enabled: lazy materialization appends a 
synthetic global row-id
+     *       column to the scan and relies on the BE rebuilding the tablet 
schema from FE's
+     *       columns_desc (keyed by column uniqueId). For 
non-light-schema-change tables every
+     *       column's uniqueId is -1, so the BE keeps its own on-disk schema 
and never installs the
+     *       synthetic row-id column. That path either fails with "field name 
is invalid" during the
+     *       scan or silently returns NULL for the lazily-fetched columns. 
Disable the optimization
+     *       for such tables and fall back to normal topn.</li>
+     * </ul>
+     */
+    private boolean supportOlapTopnLazyMaterialize(OlapTable table) {
+        if (KeysType.AGG_KEYS.equals(table.getKeysType())) {
+            return false;
+        }
+        if (!table.getEnableLightSchemaChange()) {
+            return false;
+        }
         return true;
     }
 
@@ -155,9 +183,9 @@ public class MaterializeProbeVisitor extends 
DefaultPlanVisitor<Optional<Materia
         if (scan.getSelectedIndexId() != scan.getTable().getBaseIndexId()) {
             return Optional.empty();
         }
-        // agg table do not support lazy materialize
+        // agg table / non-light-schema-change table do not support lazy 
materialize
         OlapTable table = scan.getTable();
-        if (KeysType.AGG_KEYS.equals(table.getKeysType())) {
+        if (!supportOlapTopnLazyMaterialize(table)) {
             return Optional.empty();
         }
         if (context.requiredMaterializedSlots.contains(context.slot)) {
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/processor/post/materialize/MaterializeProbeVisitorTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/processor/post/materialize/MaterializeProbeVisitorTest.java
index 100c70cfb4e..1ab7ddafdbc 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/processor/post/materialize/MaterializeProbeVisitorTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/processor/post/materialize/MaterializeProbeVisitorTest.java
@@ -160,6 +160,8 @@ public class MaterializeProbeVisitorTest {
         OlapTable table = Mockito.mock(OlapTable.class);
         Mockito.when(table.getBaseIndexId()).thenReturn(1L);
         Mockito.when(table.getKeysType()).thenReturn(KeysType.DUP_KEYS);
+        // lazy materialization requires light_schema_change to be enabled
+        Mockito.when(table.getEnableLightSchemaChange()).thenReturn(true);
         PhysicalOlapScan scan = Mockito.mock(PhysicalOlapScan.class);
         Mockito.when(scan.getSelectedIndexId()).thenReturn(1L);
         Mockito.when(scan.getTable()).thenReturn(table);
diff --git 
a/regression-test/data/query_p0/topn_lazy/topn_lazy_light_schema_change.out 
b/regression-test/data/query_p0/topn_lazy/topn_lazy_light_schema_change.out
new file mode 100644
index 00000000000..878dce10f7b
--- /dev/null
+++ b/regression-test/data/query_p0/topn_lazy/topn_lazy_light_schema_change.out
@@ -0,0 +1,11 @@
+-- This file is automatically generated. You should know what you did if you 
want to edit this
+-- !result_lsc_false --
+5      eee
+4      ddd
+3      ccc
+
+-- !result_lsc_true --
+5      eee
+4      ddd
+3      ccc
+
diff --git 
a/regression-test/suites/query_p0/topn_lazy/topn_lazy_light_schema_change.groovy
 
b/regression-test/suites/query_p0/topn_lazy/topn_lazy_light_schema_change.groovy
new file mode 100644
index 00000000000..8d76c476a25
--- /dev/null
+++ 
b/regression-test/suites/query_p0/topn_lazy/topn_lazy_light_schema_change.groovy
@@ -0,0 +1,119 @@
+// 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.
+
+// Regression test for topn lazy materialization on non-light-schema-change 
tables.
+//
+// TopN lazy materialization appends a synthetic global row-id column
+// (__DORIS_GLOBAL_ROWID_COL__<table>) to the OLAP scan. The BE only rebuilds 
the
+// tablet schema from FE's columns_desc when columns_desc[0].col_unique_id >= 
0,
+// which is only true for light_schema_change tables. On a 
non-light-schema-change
+// table every column's uniqueId is -1, so the synthetic row-id column never 
enters
+// the BE tablet schema and the scan fails with
+//   "field name is invalid. field=__DORIS_GLOBAL_ROWID_COL__<table>"
+// (or, if the scan schema is forced, the second-phase fetch silently returns 
NULL
+// for the lazily-materialized columns).
+//
+// The fix disables topn lazy materialization for non-light-schema-change OLAP 
tables
+// in MaterializeProbeVisitor, falling back to normal topn. This test verifies 
both
+// the plan shape (no lazy materialization) and correct results on such a 
table, and
+// verifies that a light_schema_change=true table still uses lazy 
materialization.
+suite("topn_lazy_light_schema_change") {
+    // ---- light_schema_change = false : lazy materialization must be 
disabled ----
+    sql """ drop table if exists topn_lazy_lsc_false """
+    sql """
+        CREATE TABLE topn_lazy_lsc_false (
+            `id` INT NOT NULL,
+            `name` VARCHAR(64),
+            `createdate` DATETIME
+        )
+        UNIQUE KEY(`id`)
+        DISTRIBUTED BY HASH(`id`) BUCKETS 3
+        PROPERTIES (
+            "replication_allocation" = "tag.location.default: 1",
+            "light_schema_change" = "false",
+            "enable_unique_key_merge_on_write" = "false"
+        );
+    """
+    sql """
+        insert into topn_lazy_lsc_false values
+            (1, 'aaa', '2024-01-01 10:00:00'),
+            (2, 'bbb', '2024-01-02 10:00:00'),
+            (3, 'ccc', '2024-01-03 10:00:00'),
+            (4, 'ddd', '2024-01-04 10:00:00'),
+            (5, 'eee', '2024-01-05 10:00:00');
+    """
+
+    // Plan shape: a non-light-schema-change table must fall back to a plain
+    // PhysicalOlapScan with no PhysicalLazyMaterialize / 
PhysicalLazyMaterializeOlapScan.
+    //
+    // Only meaningful in storage-compute integrated (non-cloud) mode. In 
cloud mode
+    // CloudPropertyAnalyzer force-rewrites light_schema_change to "true"
+    // (RewriteProperty.replace), so this table is effectively 
light_schema_change=true
+    // and lazy materialization legitimately applies -- the assertion would 
not hold.
+    if (!isCloudMode()) {
+        explain {
+            sql "shape plan select name from topn_lazy_lsc_false order by 
createdate desc limit 3"
+            notContains("PhysicalLazyMaterialize")
+        }
+    }
+
+    // Correctness: the query used to error with "field name is invalid" (or 
return NULL
+    // for name). It must now run and return the real values ordered by 
createdate desc.
+    qt_result_lsc_false """
+        select id, name from topn_lazy_lsc_false order by createdate desc 
limit 3
+    """
+
+    // ---- light_schema_change = true : lazy materialization must still apply 
----
+    sql """ drop table if exists topn_lazy_lsc_true """
+    sql """
+        CREATE TABLE topn_lazy_lsc_true (
+            `id` INT NOT NULL,
+            `name` VARCHAR(64),
+            `createdate` DATETIME
+        )
+        UNIQUE KEY(`id`)
+        DISTRIBUTED BY HASH(`id`) BUCKETS 3
+        PROPERTIES (
+            "replication_allocation" = "tag.location.default: 1",
+            "light_schema_change" = "true",
+            "enable_unique_key_merge_on_write" = "false"
+        );
+    """
+    sql """
+        insert into topn_lazy_lsc_true values
+            (1, 'aaa', '2024-01-01 10:00:00'),
+            (2, 'bbb', '2024-01-02 10:00:00'),
+            (3, 'ccc', '2024-01-03 10:00:00'),
+            (4, 'ddd', '2024-01-04 10:00:00'),
+            (5, 'eee', '2024-01-05 10:00:00');
+    """
+
+    // Plan shape: a light_schema_change table keeps lazy materialization
+    // (PhysicalLazyMaterialize present). Checked only in non-cloud mode, for 
symmetry
+    // with the lsc_false case above.
+    if (!isCloudMode()) {
+        explain {
+            sql "shape plan select name from topn_lazy_lsc_true order by 
createdate desc limit 3"
+            contains("PhysicalLazyMaterialize")
+        }
+    }
+
+    // Correctness: lazy materialization returns the same real values.
+    qt_result_lsc_true """
+        select id, name from topn_lazy_lsc_true order by createdate desc limit 
3
+    """
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to