zhaorongsheng commented on code in PR #67725:
URL: https://github.com/apache/doris/pull/67725#discussion_r4255899703


##########
fe/fe-connector/fe-connector-hive/src/main/java/org/apache/doris/connector/hive/HiveConnectorMetadata.java:
##########
@@ -2495,53 +2591,269 @@ static String hiveDateTimeString(LocalDateTime ldt) {
      * Prunes partition names based on extracted equality predicates.
      * Partition names follow the Hive convention: key1=val1/key2=val2
      */
-    private List<String> prunePartitionNames(List<String> allPartNames,
-            List<String> partKeyNames, Map<String, List<String>> predicates) {
+    private List<String> prunePartitionNames(List<String> allPartNames, 
List<String> partKeyNames,
+            Map<String, String> partKeyTypes, Map<String, List<String>> 
predicates) {
         List<String> matched = new ArrayList<>();
         for (String partName : allPartNames) {
             Map<String, String> partValues = parsePartitionName(partName, 
partKeyNames);
-            if (matchesPredicates(partValues, predicates)) {
+            // A name this prefilter cannot decode is KEPT, never dropped: its 
result becomes the logical
+            // selected view, so dropping a name only because the raw text was 
not interpretable loses rows the
+            // query must read. The typed PartitionPruner re-prunes the 
survivors, so a superset only costs the
+            // lost optimization.
+            if (partValues == null || matchesPredicates(partValues, 
partKeyTypes, predicates)) {
                 matched.add(partName);
             }
         }
         return matched;
     }
 
+    private static String buildHmsPartitionFilter(List<String> partKeyNames, 
Map<String, String> partKeyTypes,
+            Map<String, String> partKeyHiveTypes, Map<String, List<String>> 
partitionPredicates) {
+        List<String> filters = new ArrayList<>();
+        for (String partKeyName : partKeyNames) {
+            List<String> values = partitionPredicates.get(partKeyName);
+            if (values == null || values.isEmpty()) {
+                continue;
+            }
+            if (!isHmsFilterIdentifier(partKeyName)) {
+                return null;
+            }
+            if (isHmsStringType(partKeyTypes.get(partKeyName))
+                    && !isHmsStringType(partKeyHiveTypes.get(partKeyName))) {
+                return null;
+            }
+            List<String> valueFilters = new ArrayList<>();
+            for (String value : values) {
+                String literal = toHmsFilterLiteral(value, 
partKeyTypes.get(partKeyName));
+                if (literal == null) {
+                    return null;
+                }
+                valueFilters.add(partKeyName + " = " + literal);

Review Comment:
   Addressed in `[fix](fe) Decline mixed-case HMS keys and degrade MTMV warmup`.
   
   `buildHmsPartitionFilter` now declines the direct HMS path for any key that 
is not already in its stored lowercase form (`HiveConnectorMetadata.java`):
   
   ```java
   if (!partKeyName.equals(partKeyName.toLowerCase(Locale.ROOT))) {
       return null;
   }
   ```
   
   A `null` return makes `applyFilter` fall back to the positional local 
prefilter (`prunePartitionNames` over `listPartitionNames`), so a mixed-case 
key no longer emits a `P = 'x'` fragment that JDO's `ExpressionTree` compares 
against the stored lowercase `p=x` name.
   
   Covering test: 
`HiveConnectorMetadataPartitionPruningTest.testHmsFilterDeclinesUppercasePartitionKeyForJdoCaseMismatch`.
 It models the JDO case-mismatch response with a client whose filtered list is 
empty (any filter path yields nothing), asserts the pruned view is still the 
correct `[p=1]`, and asserts `client.filter == null` plus that local name 
listing was used — i.e. the direct path is never taken for `P`.
   
   Run: `./run-fe-ut.sh --run 
"org.apache.doris.connector.hive.HiveConnectorMetadataPartitionPruningTest,org.apache.doris.datasource.mvcc.PluginDrivenMvccExternalTableTest,org.apache.doris.nereids.StatementContextTest"`
 → Hive 30/30, PluginDrivenMvcc 94/94, StatementContext 25/25, BUILD SUCCESS.



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/mvcc/PluginDrivenMvccExternalTable.java:
##########
@@ -714,11 +692,142 @@ static boolean schemaCacheDisabled(Connector connector) {
 
     @Override
     public Map<String, PartitionItem> 
getNameToPartitionItems(Optional<MvccSnapshot> snapshot) {
+        if (supportsConnectorPartitionPruning()) {
+            PluginDrivenMvccSnapshot pin = getOrMaterialize(snapshot);
+            if (pin.isPartitionViewMaterialized()) {
+                return pin.getNameToPartitionItem();
+            }
+            if (pin.isPartitionViewUnavailable()) {
+                return Collections.emptyMap();
+            }
+            Optional<ExternalTablePreloadInfo> sharedView = 
sharedLatestPreloadInfo(snapshot);
+            if (sharedView.isPresent() && 
sharedView.get().hasScanPartitionView()
+                    && sharedView.get().getScanPartitionView().isPresent()) {
+                return sharedView.get().getScanPartitionView().get();
+            }
+            // The latest Hive query pin intentionally carries no partition 
map so selective scans can send a
+            // predicate to HMS first. Consumers that explicitly ask for a 
partition map (MTMV alignment,
+            // no-filter scan finalization, and a connector-declined pruning 
fallback) require the real full
+            // view instead of treating that query-only pin as an empty table. 
An unavailable shared scan view
+            // is not an authoritative empty map either: checked MTMV 
consumers reject it in
+            // getAndCopyPartitionItems, while other explicit map consumers 
retain this fail-loud full-view path.
+            return super.getNameToPartitionItems(snapshot);
+        }
         return getOrMaterialize(snapshot).getNameToPartitionItem();
     }
 
     @Override
-    public Map<String, PartitionItem> 
getAndCopyPartitionItems(Optional<MvccSnapshot> snapshot) {
+    public Optional<Map<String, PartitionItem>> 
getNameToPartitionItemsForScan(Optional<MvccSnapshot> snapshot) {
+        if (snapshot.isPresent() && snapshot.get() instanceof 
PluginDrivenMvccSnapshot
+                && ((PluginDrivenMvccSnapshot) 
snapshot.get()).isPartitionViewUnavailable()) {
+            return Optional.empty();
+        }
+        if (supportsConnectorPartitionPruning()) {
+            PluginDrivenMvccSnapshot pin = getOrMaterialize(snapshot);
+            if (pin.isPartitionViewMaterialized()) {
+                // This statement's pin already carries a materialized view: 
reuse it instead of paying another
+                // connector round-trip that could observe a different remote 
generation.
+                return Optional.of(pin.getNameToPartitionItem());
+            }
+        }
+        return super.getNameToPartitionItemsForScan(snapshot);
+    }
+
+    @Override
+    public Optional<ConnectorFilteredPartitionView> 
applyPartitionFilterForScan(Optional<MvccSnapshot> snapshot,
+            ConnectorExpression partitionFilter) {
+        if (snapshot.isPresent() && snapshot.get() instanceof 
PluginDrivenMvccSnapshot
+                && ((PluginDrivenMvccSnapshot) 
snapshot.get()).isPartitionViewUnavailable()) {
+            return Optional.empty();
+        }
+        return super.applyPartitionFilterForScan(snapshot, partitionFilter);
+    }
+
+    /**
+     * Threads this statement's MVCC pin onto the handle the partition view is 
enumerated from, so a
+     * time-travel / {@code @options} query never prunes against the latest 
generation: the data scan reads the
+     * pinned snapshot, and a latest view can be missing (or contain) 
partitions it will never read.
+     */
+    @Override
+    protected ConnectorTableHandle pinPartitionViewHandle(ConnectorTableHandle 
handle,
+            ConnectorMetadata metadata, ConnectorSession session, 
Optional<MvccSnapshot> snapshot) {
+        if (snapshot.isPresent() && snapshot.get() instanceof 
PluginDrivenMvccSnapshot) {
+            return metadata.applySnapshot(session, handle,
+                    ((PluginDrivenMvccSnapshot) 
snapshot.get()).getConnectorSnapshot());
+        }
+        return handle;
+    }
+
+    /**
+     * Materializes the complete partition view for an MTMV refresh before it 
acquires base-table locks.
+     * The lightweight Hive query pin keeps the connector snapshot/freshness 
kind but deliberately omits the
+     * partition map; MTMV alignment needs that map and must not load it while 
holding internal table locks.
+     */
+    public MvccSnapshot materializePartitionViewForMtmv(MvccSnapshot snapshot) 
{
+        if (!supportsConnectorPartitionPruning() || !(snapshot instanceof 
PluginDrivenMvccSnapshot)) {
+            return snapshot;
+        }
+        PluginDrivenMvccSnapshot pin = (PluginDrivenMvccSnapshot) snapshot;
+        if (!pin.isPartitionViewDeferred()) {
+            return pin;
+        }
+        Map<String, PartitionItem> partitionItems = 
super.getNameToPartitionItems(Optional.of(pin));

Review Comment:
   Addressed in `[fix](fe) Decline mixed-case HMS keys and degrade MTMV warmup`.
   
   `materializePartitionViewForMtmv` no longer calls the fail-loud eager build. 
It now enumerates with the LIST path's per-partition contract — 
`listPartitions(...)` logs the unrepresentable partition and leaves the item 
map short of the listed names — so `isPartitionInvalid()` is true, the base 
table degrades to `UNPARTITIONED`, and the whole-MV refresh continues as a 
scan-all (`PluginDrivenMvccExternalTable.java`). Enumeration is taken from the 
snapshot-pinned handle, so the warmup reads the same generation as the data 
scan.
   
   Covering test: 
`PluginDrivenMvccExternalTableTest.testMtmvWarmupDegradesOnUnrepresentableTypedPartition`.
 With `dt=2024-01-01` and an unrepresentable `dt=not-a-date` DATEV2 partition, 
it asserts the materialized view holds 1 item (the bad one is skipped, not 
thrown), `isPartitionInvalid()` is true, and `getPartitionType(...)` is 
`UNPARTITIONED` with no partition columns; it also verifies `applySnapshot` + 
`listPartitions` were invoked on the pinned handle.
   
   Run: `./run-fe-ut.sh --run 
"org.apache.doris.connector.hive.HiveConnectorMetadataPartitionPruningTest,org.apache.doris.datasource.mvcc.PluginDrivenMvccExternalTableTest,org.apache.doris.nereids.StatementContextTest"`
 → Hive 30/30, PluginDrivenMvcc 94/94, StatementContext 25/25, BUILD SUCCESS.



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/mvcc/PluginDrivenMvccExternalTable.java:
##########
@@ -166,6 +172,16 @@ private PluginDrivenMvccSnapshot materializeLatest(
         ConnectorMvccSnapshot connectorSnapshot = existingFence.orElseGet(
                 () -> metadata.beginQuerySnapshot(session, 
handle).orElseGet(this::emptySnapshot));
 
+        // A connector that can materialize a partition predicate remotely 
must not populate the latest
+        // snapshot with every partition before Nereids has supplied that 
predicate. Plain Hive reaches this
+        // MVCC table class because the catalog also serves snapshot-capable 
sibling formats, but its latest
+        // pin is deliberately not a data snapshot and applySnapshot is a 
no-op. Keep only that lightweight
+        // query-begin pin here; PruneFileScanPartition will request the 
selected partition view later.
+        if (supportsConnectorPartitionPruning()) {

Review Comment:
   Addressed. Both deferred-materialization paths now pin the MVCC snapshot 
before touching metadata: `pinPartitionViewHandle(...)` applies 
`metadata.applySnapshot` to the resolved handle and the filtered path then runs 
`applyFilter` on that pinned handle, while the full path runs `listPartitions` 
on it. Neither path prunes against the freshly resolved latest metadata any 
more, so a snapshot query cannot short-circuit on a stale empty selection.
   
   Covering tests: 
`PluginDrivenMvccExternalTableTest.testConnectorPartitionPruningPinsSnapshotBeforeFilteredMaterialization`
 (verifies `applyFilter` is called on the pinned handle, never the base handle) 
and `testMtmvWarmupDegradesOnUnrepresentableTypedPartition` / 
`testMaterializedEmptyMtmvSnapshotIsNotRehydrated` (verify `listPartitions` 
runs on the pinned handle).



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