OIiveirra commented on code in PR #68161:
URL: https://github.com/apache/doris/pull/68161#discussion_r4309864905
##########
fe/fe-connector/fe-connector-paimon/src/main/java/org/apache/doris/connector/paimon/PaimonScanPlanProvider.java:
##########
@@ -2087,27 +2254,61 @@ static FileStoreTable
legacyOrcSplitTable(FileStoreTable table, DataSplit split)
return base;
}
- static boolean requiresLegacyOrcTimestampReader(Table table,
Optional<List<RawFile>> rawFiles,
+ private static boolean requiresCrossFamilyTimestampReader(FileStoreTable
table, Optional<List<RawFile>> rawFiles,
+ Set<Integer> readFieldIds, Map<Long, Boolean> schemaTimestamps) {
+ RowType readType = new RowType(table.rowType().getFields().stream()
+ .filter(field ->
readFieldIds.contains(field.id())).collect(Collectors.toList()));
+ if (!containsTimestampType(readType)) {
+ return false;
+ }
+ for (RawFile file : rawFiles.get()) {
+ if (schemaTimestamps.computeIfAbsent(file.schemaId(), id ->
hasTimestampPrecisionNarrowing(
+ table.schemaManager().schema(id).logicalRowType(),
readType, 0, true))) {
+ // Generic native casts round after zone conversion. The SDK
keeps its conversion
+ // semantics while the JNI widened read type delays truncation
until materialization.
+ return true;
+ }
+ }
+ return false;
+ }
+
+ static boolean requiresOrcTimestampReader(Table table,
Optional<List<RawFile>> rawFiles,
Set<Integer> readFieldIds, Map<Long, Boolean> schemaTimestamps) {
if (readFieldIds.isEmpty() || !rawFiles.isPresent()
- || rawFiles.get().stream().noneMatch(f ->
f.path().endsWith(".orc"))
- || !new org.apache.paimon.options.Options(table.options()).get(
-
org.apache.paimon.format.OrcOptions.ORC_TIMESTAMP_LTZ_LEGACY_TYPE)) {
+ || rawFiles.get().stream().noneMatch(f ->
f.path().endsWith(".orc"))) {
return false;
}
// Only fields present in a file require SDK timezone conversion. A
newly added nullable LTZ
// column reads as NULL in older files and must not disable their
native metadata scans.
FileStoreTable fileStoreTable = (FileStoreTable) table;
+ boolean legacyLtz = new
org.apache.paimon.options.Options(table.options()).get(
+
org.apache.paimon.format.OrcOptions.ORC_TIMESTAMP_LTZ_LEGACY_TYPE);
+ RowType readType = new
RowType(fileStoreTable.rowType().getFields().stream()
+ .filter(field ->
readFieldIds.contains(field.id())).collect(Collectors.toList()));
Review Comment:
Fixed in a6d3bedf4ca5c290ce6d6a1d6cbf448f4b6ec8e5. The scan selection is now
translated from outer-table field IDs to the selected branch's IDs by
positional correspondence, before both ORC and cross-family timestamp-history
routing. This does not assume matching IDs or names. Tests cover independently
evolved IDs and renamed fields, both branch orderings, filter-only timestamps,
and unaffected integer-only native scans. The new routing test failed before
the fix and passes afterwards. All 216 focused FE/JNI tests and the personal
paimon_timestamp_types external suite passed; ASAN build, connector packaging
and Checkstyle also passed. No V1 or ordinary SQL CAST changes are included.
##########
fe/fe-connector/fe-connector-paimon/src/main/java/org/apache/doris/connector/paimon/PaimonScanPlanProvider.java:
##########
@@ -1241,15 +1251,27 @@ public Map<String, String> getScanNodeProperties(
// ("encodedStr is null"). Mirrors legacy
PaimonScanNode.createScanRangeLocations, which always called
// setPaimonPredicate(encodeObjectToString(predicates)) regardless of
whether predicates was empty.
List<org.apache.paimon.predicate.Predicate> predicates =
Collections.emptyList();
+ // The same predicate must stay out of the BE Paimon reader: JNI
materializes and repairs the
+ // value first, then the generic scan path evaluates the residual
against the repaired column.
if (filter.isPresent()) {
RowType rowType = table.rowType();
- PaimonPredicateConverter converter = new
PaimonPredicateConverter(rowType);
+ PaimonPredicateConverter converter = new PaimonPredicateConverter(
+ rowType, filterReadsTimestamp(rowType, filter.get())
+ && requiresTimestampPrecisionRepair(session,
table, paimonHandle));
predicates = converter.convert(filter.get());
}
props.put("paimon.predicate", encodeObjectToString(predicates));
// Paimon JDBC metastore options for BE (if applicable)
Map<String, String> backendOptions = new
LinkedHashMap<>(getBackendPaimonOptions());
+ Set<Integer> readFieldIds = scanReadFieldIds(table.rowType(), columns,
filter);
+ boolean readsTimestamp =
table.rowType().getFields().stream().anyMatch(field ->
+ readFieldIds.contains(field.id())
+ && containsTimestampType(field.type()));
+ backendOptions.put(DORIS_TIMESTAMP_PRECISION_REPAIR, String.valueOf(
+ (readsTimestamp || new
org.apache.paimon.options.Options(table.options()).get(
Review Comment:
Fixed in a6d3bedf4ca5c290ce6d6a1d6cbf448f4b6ec8e5. FE now checks query-auth
enablement across the pinned fallback branches, including the existing
supported system-table source resolution. The repair flag still requires
historical timestamp narrowing; the JNI explicit-false contract and
authenticated, statement-scoped history lookup are preserved. Added FE coverage
with auth enabled exclusively on the secondary branch and integer-only output,
plus no-history controls. A historical JNI reader test covers the hidden
p6-to-p4 .000001 typed SDK policy. The FE flag test failed before the fix. All
216 focused FE/JNI tests and the personal timestamp external suite passed. This
does not claim end-to-end coverage of remote JSON timestamp-policy transport.
##########
fe/be-java-extensions/paimon-scanner/src/main/java/org/apache/doris/paimon/PaimonTimestampQueryAuth.java:
##########
@@ -0,0 +1,204 @@
+// 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.paimon;
+
+import org.apache.paimon.catalog.TableQueryAuthResult;
+import org.apache.paimon.data.GenericArray;
+import org.apache.paimon.data.GenericMap;
+import org.apache.paimon.data.GenericRow;
+import org.apache.paimon.data.InternalArray;
+import org.apache.paimon.data.InternalMap;
+import org.apache.paimon.data.InternalRow;
+import org.apache.paimon.data.Timestamp;
+import org.apache.paimon.predicate.FieldRef;
+import org.apache.paimon.predicate.Predicate;
+import org.apache.paimon.predicate.PredicateVisitor;
+import org.apache.paimon.predicate.Transform;
+import org.apache.paimon.reader.RecordReader;
+import org.apache.paimon.table.FallbackReadFileStoreTable.FallbackSplitImpl;
+import org.apache.paimon.table.source.QueryAuthSplit;
+import org.apache.paimon.table.source.Split;
+import org.apache.paimon.types.ArrayType;
+import org.apache.paimon.types.DataField;
+import org.apache.paimon.types.DataType;
+import org.apache.paimon.types.DataTypeChecks;
+import org.apache.paimon.types.LocalZonedTimestampType;
+import org.apache.paimon.types.MapType;
+import org.apache.paimon.types.RowType;
+import org.apache.paimon.types.TimestampType;
+import org.apache.paimon.utils.InternalRowUtils;
+
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/** Keeps SDK authorization and masking after current-precision
materialization. */
+final class PaimonTimestampQueryAuth extends TableQueryAuthResult {
+ private static final long serialVersionUID = 1L;
+ private final RowType currentReadType;
+ private final TableQueryAuthResult original;
+
+ PaimonTimestampQueryAuth(TableQueryAuthResult original, RowType
currentReadType) {
+ super(original.filter(), original.columnMasking());
+ this.original = original;
+ this.currentReadType = currentReadType;
+ }
+
+ @Override
+ public Predicate extractPredicate() {
+ return original.extractPredicate();
+ }
+
+ @Override
+ public Map<String, Transform> extractColumnMasking() {
+ return original.extractColumnMasking();
+ }
+
+ static QueryAuthSplit findAuthSplit(Split split) {
+ // FallbackDataSplit is a leaf whose wrapped() returns itself, not a
split container.
+ if (split instanceof FallbackSplitImpl) {
+ return findAuthSplit(((FallbackSplitImpl) split).wrapped());
+ }
+ return split instanceof QueryAuthSplit ? (QueryAuthSplit) split : null;
+ }
+
+ static Split repairPolicy(Split split, RowType currentReadType) {
+ if (split instanceof FallbackSplitImpl) {
+ FallbackSplitImpl fallback = (FallbackSplitImpl) split;
+ return new FallbackSplitImpl(repairPolicy(fallback.wrapped(),
currentReadType), fallback.isFallback());
+ }
+ QueryAuthSplit auth = (QueryAuthSplit) split;
+ return new QueryAuthSplit(auth.split(), new
PaimonTimestampQueryAuth(auth.authResult(), currentReadType));
+ }
+
+ static void addPolicyFields(TableQueryAuthResult auth, RowType tableType,
+ List<DataField> readFields, List<DataField> currentFields) {
+ List<String> projected =
readFields.stream().map(DataField::name).collect(Collectors.toList());
+ Set<String> required = requiredFields(auth, new
LinkedHashSet<>(projected));
+ for (DataField field : tableType.getFields()) {
+ if (required.contains(field.name())) {
+ int index = projected.indexOf(field.name());
+ DataField widened =
field.newType(PaimonJniScanner.createSafeTimestampReadType(field.type()));
+ if (index < 0) {
+ currentFields.add(field);
+ readFields.add(widened);
+ } else if (field.type() instanceof RowType || field.type()
instanceof ArrayType
+ || field.type() instanceof MapType) {
+ // A policy can read a sibling that SQL pruned from a
projected complex column.
+ currentFields.set(index, field);
+ readFields.set(index, widened);
+ }
+ }
+ }
+ }
+
+ static boolean readsTimestamp(TableQueryAuthResult auth, RowType
tableType, List<String> projected) {
+ Set<String> required = requiredFields(auth, new
LinkedHashSet<>(projected));
+ return tableType.getFields().stream().anyMatch(field ->
required.contains(field.name())
+ && PaimonJniScanner.containsTimestampType(field.type()));
+ }
+
+ private static Set<String> requiredFields(TableQueryAuthResult auth,
Set<String> projected) {
+ Set<String> required = new LinkedHashSet<>();
+ Predicate filter = auth.extractPredicate();
+ if (filter != null) {
+ required.addAll(PredicateVisitor.collectFieldNames(filter));
+ }
+ for (Map.Entry<String, Transform> mask :
auth.extractColumnMasking().entrySet()) {
+ if (!projected.contains(mask.getKey())) {
+ continue;
+ }
+ required.add(mask.getKey());
+ for (Object input : mask.getValue().inputs()) {
+ if (input instanceof FieldRef) {
+ required.add(((FieldRef) input).name());
+ }
+ }
+ }
+ return required;
+ }
+
+ static RecordReader<InternalRow> projectOutput(RecordReader<InternalRow>
reader,
+ RowType currentReadType, RowType outputType) {
+ if (currentReadType.equals(outputType)) {
+ return reader;
+ }
+ return reader.transform(row -> repairRow(row, currentReadType,
outputType));
+ }
+
+ @Override
+ public RecordReader<InternalRow> doAuth(RecordReader<InternalRow> reader,
RowType readType) {
+ // Delegate the policy itself to Paimon; neither filters nor masks are
dropped or rewritten.
+ // System-table readers synthesize fields such as rowkind only after
this inner callback.
+ RowType currentDataType =
currentReadType.project(readType.getFieldNames());
Review Comment:
Follow-up fixed in a6d3bedf4ca5c290ce6d6a1d6cbf448f4b6ec8e5: the
selected-branch policy-schema lookup now also traverses row-backed system
wrappers using the existing pinned-source/delegate helpers, without reopening
the catalog. The audit-log outer reader still synthesizes rowkind; policy
filters and masks are unchanged. Added historical audit-log/fallback reader
coverage with an independently renamed hidden integer policy column, rowkind
plus ts output, positive and negative-epoch values, policy exclusion, and both
branch orderings. The default-order audit case failed before the fix. All 216
focused FE/JNI tests and the personal paimon_timestamp_types external suite
passed. This supplements the direct-fallback fix previously recorded in this
resolved thread.
--
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]