github-actions[bot] commented on code in PR #66247:
URL: https://github.com/apache/doris/pull/66247#discussion_r3679793467


##########
fe/be-java-extensions/java-common/src/main/java/org/apache/doris/common/jni/vec/ColumnType.java:
##########
@@ -419,16 +431,27 @@ public static ColumnType parseType(String columnName, 
String hiveType) {
                 } else if (lowerCaseType.startsWith("struct")) {
                     if (lowerCaseType.indexOf("<") == 6
                             && lowerCaseType.lastIndexOf(">") == 
lowerCaseType.length() - 1) {
-                        String listFields = lowerCaseType.substring(7, 
lowerCaseType.length() - 1);
+                        String listFields = originalType.substring(7, 
originalType.length() - 1);

Review Comment:
   [P1] Preserve the legacy STRUCT-key normalization
   
   `parseType(...)` still serves every unencoded `VectorTable` caller, but this 
now recurses through `originalType` even when `encodedStructFields` is false. 
Before this change, `struct<Mixed:int>` produced child key `mixed`; it now 
produces `Mixed`. `VectorColumn.appendStruct` and `getStruct` use these names 
as exact HashMap keys, including the Java UDF/UDAF path, so existing functions 
that read or return the formerly normalized key can silently receive/write 
NULL. Please use the original spelling only for the encoded Paimon grammar (or 
introduce an explicit compatibility migration) and add an unencoded mixed-case 
STRUCT test.



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/paimon/PaimonReaderOptions.java:
##########
@@ -0,0 +1,155 @@
+// 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.datasource.paimon;
+
+import com.google.common.collect.ImmutableSet;
+import org.apache.paimon.CoreOptions;
+import org.apache.paimon.options.ConfigOption;
+import org.apache.paimon.options.MemorySize;
+import org.apache.paimon.options.Options;
+import org.apache.paimon.table.FallbackReadFileStoreTable;
+import org.apache.paimon.table.Table;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+/** Validation shared by catalog-scoped and relation-scoped Paimon reader 
tuning. */
+public final class PaimonReaderOptions {
+    public static final String TABLE_OPTION_PREFIX = "paimon.table-option.";
+    public static final int MIN_READ_BATCH_SIZE = 1;
+    public static final int MAX_READ_BATCH_SIZE = 65536;
+    public static final long MIN_ASYNC_THRESHOLD_BYTES = 1024L * 1024L;
+    public static final long MAX_ASYNC_THRESHOLD_BYTES = 1024L * 1024L * 1024L;
+
+    // Only options which cannot select another table context or mutate write 
behavior are safe to
+    // apply with Table.copy after Doris has bound the table schema.
+    private static final Set<String> SUPPORTED_OPTIONS = ImmutableSet.of(
+            CoreOptions.READ_BATCH_SIZE.key(),
+            CoreOptions.FILE_READER_ASYNC_THRESHOLD.key());
+
+    private PaimonReaderOptions() {
+    }
+
+    public static Set<String> supportedOptions() {
+        return SUPPORTED_OPTIONS;
+    }
+
+    public static void validate(String key, String value) {
+        if (!SUPPORTED_OPTIONS.contains(key)) {
+            throw new IllegalArgumentException("Unsupported Paimon dynamic 
reader option '" + key
+                    + "'. Supported options are " + SUPPORTED_OPTIONS);
+        }
+
+        if (CoreOptions.READ_BATCH_SIZE.key().equals(key)) {
+            int batchSize = parse(key, value, CoreOptions.READ_BATCH_SIZE);
+            // A zero batch can make Paimon's vectorized reader report success 
without
+            // advancing input; the upper bound also prevents one relation 
from over-allocating.
+            requireRange(key, batchSize, MIN_READ_BATCH_SIZE, 
MAX_READ_BATCH_SIZE);
+        } else {
+            MemorySize threshold = parse(key, value, 
CoreOptions.FILE_READER_ASYNC_THRESHOLD);
+            // Bound the trigger on both sides so a query cannot fan out tiny 
async reads or
+            // silently disable asynchronous reading with an effectively 
infinite threshold.
+            requireRange(key, threshold.getBytes(),
+                    MIN_ASYNC_THRESHOLD_BYTES, MAX_ASYNC_THRESHOLD_BYTES);
+        }
+    }
+
+    public static void validateCatalogProperties(Map<String, String> 
properties) {
+        properties.forEach((key, value) -> {
+            if (!key.toLowerCase(Locale.ROOT).startsWith(TABLE_OPTION_PREFIX)) 
{
+                return;
+            }
+            String optionKey = key.substring(TABLE_OPTION_PREFIX.length());
+            if (optionKey.isEmpty()) {
+                throw new IllegalArgumentException(
+                        "Paimon table option name must not be empty after 
prefix " + TABLE_OPTION_PREFIX);
+            }
+            validate(optionKey, value);
+        });
+    }
+
+    public static Map<String, String> compatibleCatalogOptions(Map<String, 
String> properties) {
+        Map<String, String> compatibleOptions = new LinkedHashMap<>();
+        properties.forEach((key, value) -> {
+            if (!key.toLowerCase(Locale.ROOT).startsWith(TABLE_OPTION_PREFIX)) 
{
+                return;
+            }
+            String optionKey = key.substring(TABLE_OPTION_PREFIX.length());
+            try {
+                validate(optionKey, value);
+                compatibleOptions.put(optionKey, value);
+            } catch (IllegalArgumentException ignored) {
+                // Images written before the reader-only allowlist may contain 
arbitrary Paimon
+                // options. Keep the catalog loadable, but never apply an 
unsafe legacy option.
+            }
+        });
+        return Collections.unmodifiableMap(compatibleOptions);
+    }
+
+    public static void validateEffectiveTableOptions(Map<String, String> 
options) {
+        SUPPORTED_OPTIONS.stream()
+                .filter(options::containsKey)
+                .forEach(key -> validate(key, options.get(key)));
+        
validateManifestParallelism(options.get(CoreOptions.SCAN_MANIFEST_PARALLELISM.key()));
+    }
+
+    public static void validateEffectiveTable(Table table) {
+        validateEffectiveTableOptions(table.options());
+        if (table instanceof FallbackReadFileStoreTable) {

Review Comment:
   [P1] Unwrap privilege delegates before checking fallback children
   
   With Paimon's privilege layer enabled, `PrivilegedCatalog.getTable` wraps 
the `FallbackReadFileStoreTable` in `PrivilegedFileStoreTable`. This outer 
object fails the `instanceof FallbackReadFileStoreTable` check; its `options()` 
still exposes only the main child, while `newScan()` delegates to the wrapped 
fallback planner. Consequently an unsafe physical fallback value remains 
unvalidated despite the fix in the existing fallback thread. Please traverse 
the supported delegated wrapper (`wrapped()`) before recursing into fallback 
children and cover a privilege-wrapped fallback fixture.



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/paimon/PaimonSysExternalTable.java:
##########
@@ -135,19 +141,33 @@ public Table getSysPaimonTable() {
     }
 
     public Table getSysPaimonTable(TableScanParams scanParams) {
-        Table table = getSysPaimonTable();
         if (scanParams == null || !scanParams.isOptions()) {
-            return table;
+            return getSysPaimonTable();
         }
-        Map<String, String> resolvedOptions = scanParams.getOrResolveMapParams(
-                options -> 
PaimonScanParams.resolveOptions(sourceTable.getBasePaimonTable(), options));
+        Map<String, String> resolvedOptions = resolvedOptions(scanParams);
+        validateEffectiveDataTable(scanParams);
         if 
(PaimonScanParams.getPinnedFileCreationTime(resolvedOptions).isPresent()) {
             // Generic system-table wrappers cannot carry Paimon's 
manifest-entry predicate.
             // Reject the fallback instead of silently widening it to the 
whole pinned snapshot.
             throw new IllegalArgumentException(
                     "Paimon system tables cannot apply a creation-time file 
filter.");
         }
-        return PaimonScanParams.applyOptions(table, resolvedOptions);
+        return PaimonScanParams.applyOptions(getRawSysPaimonTable(), 
resolvedOptions);
+    }
+
+    public void validateEffectiveDataTable(TableScanParams scanParams) {
+        Table dataTable = sourceTable.getBasePaimonTable();

Review Comment:
   [P1] Validate the exact data handle embedded in the system wrapper
   
   `sourceTable.getBasePaimonTable()` comes from Doris' table cache, whereas 
`getRawSysPaimonTable()` loads the system wrapper separately through the Paimon 
catalog. If the source handle is still cached after Paimon's catalog cache 
expires/is disabled and the physical options change, the wrapper can contain a 
newer unsafe child while this method validates only the older safe handle. The 
wrapper's `options()` is empty, and `$partitions` later plans its private child 
directly, so the final generic guard cannot catch the mismatch. Please 
construct the wrapper from the exact effective source handle or expose and 
validate its actual child, and add a divergent-handle test.



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/paimon/PaimonExternalTable.java:
##########
@@ -178,6 +178,14 @@ private PaimonSnapshotCacheValue 
getPaimonSnapshotCacheValue(Optional<TableSnaps
                         "Failed to get Paimon snapshot: " + (e.getMessage() == 
null ? "unknown cause" : e.getMessage()),
                         e);
             }
+        } else if (scanParams.isPresent() && scanParams.get().isOptions()) {
+            Table baseTable = getBasePaimonTable();
+            Map<String, String> resolvedOptions = 
scanParams.get().getOrResolveMapParams(
+                    options -> PaimonScanParams.resolveOptions(baseTable, 
options));
+            Table effectiveTable = PaimonScanParams.applyOptions(baseTable, 
resolvedOptions);
+            // The shared latest cache was built from the catalog-scoped 
handle. Relation options
+            // need their own projection so partition enumeration uses the 
final safe table copy.
+            return PaimonUtils.loadSnapshotProjection(this, effectiveTable);

Review Comment:
   [P2] Keep reader-only OPTIONS on the memoized projection
   
   This branch also handles `read.batch-size` and 
`file-reader-async-threshold`, neither of which changes the selected snapshot, 
schema, or partitions. Nevertheless every such relation calls the uncached 
projection loader, whose partitioned path runs 
`CatalogUtils.listPartitionsFromFileSystem`; the ordinary latest projection is 
memoized in `PaimonTableCacheValue`. On a high-partition table this turns a 
reader tuning option into a full metadata enumeration for every statement. 
Please reuse the memoized latest projection after validating the effective 
handle when the supplied options do not select startup metadata, and add a 
partitioned counting test.



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