Gabriel39 commented on code in PR #66247:
URL: https://github.com/apache/doris/pull/66247#discussion_r3679847280
##########
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:
Fixed in 43f91409ddc. The unencoded branch now lowercases each STRUCT field
name before building child types and maps, while the versioned encoded branch
continues to Base64-decode and preserve the exact UTF-8 spelling. Added a
nested mixed-case legacy test; its RED result was `[Mixed, UPPER]`, and the
targeted GREEN run now returns `[mixed, upper]`. The 15 encoded Paimon scanner
tests also pass.
##########
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:
Fixed in 43f91409ddc. `validateEffectiveTable` now traverses every
`DelegatedFileStoreTable.wrapped()` handle in addition to the fallback child,
so `PrivilegedFileStoreTable -> FallbackReadFileStoreTable -> fallback` is
fully validated after copies. Added a real privilege-wrapped Paimon fixture.
The standalone 1.3.1 probe accepted the unsafe hidden child before the fix and
rejects it now.
##########
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:
Fixed in 43f91409ddc. `PaimonSysExternalTable` no longer reloads the wrapper
through the catalog. It caches the source `FileStoreTable` handle and
constructs every raw/effective wrapper from that same handle with Paimon's
`SystemTableLoader`; relation options are applied to the data handle before
wrapper construction. Added a divergent-handle test where the source is safe
and a separately mocked catalog reload is unsafe; the catalog reload must never
be called or returned.
##########
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:
Fixed in 43f91409ddc. After applying and validating relation options, pure
`read.batch-size` / `file-reader-async-threshold` tuning now returns the
memoized latest projection. Manifest and startup-sensitive options continue
through the effective-handle projection loader. Added a real partitioned table
fixture with a direct-projection counter that must remain zero for reader-only
tuning; the existing manifest override test continues to require the direct
path.
--
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]