This is an automated email from the ASF dual-hosted git repository.
JingsongLi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/paimon.git
The following commit(s) were added to refs/heads/master by this push:
new 7904c1d94c [hive] Read tables whose Paimon location is only in table
properties (#10589)
7904c1d94c is described below
commit 7904c1d94ceb9dcb6b29e45dfa2c7fa8a48bf0dd
Author: YangJie <[email protected]>
AuthorDate: Thu Oct 15 20:49:54 2026 -0400
[hive] Read tables whose Paimon location is only in table properties
(#10589)
---
.../apache/paimon/hive/PaimonStorageHandler.java | 10 +++++++
.../paimon/hive/utils/HiveSplitGenerator.java | 35 +++++++++++++++++-----
.../apache/paimon/hive/HiveCatalogITCaseBase.java | 28 +++++++++++++++++
.../apache/paimon/hive/HiveSplitGeneratorTest.java | 18 +++++++++++
4 files changed, 84 insertions(+), 7 deletions(-)
diff --git
a/paimon-hive/paimon-hive-connector-common/src/main/java/org/apache/paimon/hive/PaimonStorageHandler.java
b/paimon-hive/paimon-hive-connector-common/src/main/java/org/apache/paimon/hive/PaimonStorageHandler.java
index 071434ec11..8036bf4a58 100644
---
a/paimon-hive/paimon-hive-connector-common/src/main/java/org/apache/paimon/hive/PaimonStorageHandler.java
+++
b/paimon-hive/paimon-hive-connector-common/src/main/java/org/apache/paimon/hive/PaimonStorageHandler.java
@@ -21,10 +21,13 @@ package org.apache.paimon.hive;
import org.apache.paimon.hive.mapred.PaimonInputFormat;
import org.apache.paimon.hive.mapred.PaimonOutputCommitter;
import org.apache.paimon.hive.mapred.PaimonOutputFormat;
+import org.apache.paimon.hive.utils.HiveSplitGenerator;
import org.apache.paimon.utils.JsonSerdeUtil;
+import org.apache.paimon.utils.StringUtils;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hive.metastore.HiveMetaHook;
+import org.apache.hadoop.hive.metastore.api.hive_metastoreConstants;
import org.apache.hadoop.hive.ql.metadata.HiveException;
import org.apache.hadoop.hive.ql.metadata.HiveStorageHandler;
import org.apache.hadoop.hive.ql.metadata.HiveStoragePredicateHandler;
@@ -81,6 +84,13 @@ public class PaimonStorageHandler implements
HiveStoragePredicateHandler, HiveSt
Properties properties = tableDesc.getProperties();
String paimonLocation = LocationKeyExtractor.getPaimonLocation(conf,
properties);
map.put(LocationKeyExtractor.INTERNAL_LOCATION, paimonLocation);
+ // always set, so that a value left by another table of the same query
cannot leak in
+ map.put(
+ HiveSplitGenerator.HIVE_PARTITIONED,
+ String.valueOf(
+ !StringUtils.isEmpty(
+ properties.getProperty(
+
hive_metastoreConstants.META_TABLE_PARTITION_COLUMNS))));
String dataFieldJsonStr = getDataFieldsJsonStr(conf, properties);
tableDesc.getProperties().put(PAIMON_TABLE_FIELDS, dataFieldJsonStr);
}
diff --git
a/paimon-hive/paimon-hive-connector-common/src/main/java/org/apache/paimon/hive/utils/HiveSplitGenerator.java
b/paimon-hive/paimon-hive-connector-common/src/main/java/org/apache/paimon/hive/utils/HiveSplitGenerator.java
index b121c4e4d7..ea3ea52b5f 100644
---
a/paimon-hive/paimon-hive-connector-common/src/main/java/org/apache/paimon/hive/utils/HiveSplitGenerator.java
+++
b/paimon-hive/paimon-hive-connector-common/src/main/java/org/apache/paimon/hive/utils/HiveSplitGenerator.java
@@ -44,6 +44,7 @@ import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
@@ -63,6 +64,9 @@ public class HiveSplitGenerator {
private static final Logger LOG =
LoggerFactory.getLogger(HiveSplitGenerator.class);
+ /** Whether the Hive table has partition columns, set by {@code
PaimonStorageHandler}. */
+ public static final String HIVE_PARTITIONED =
"paimon_internal_hive_partitioned";
+
public static InputSplit[] generateSplits(
FileStoreTable table, JobConf jobConf, int numSplits) {
List<Predicate> predicates = new ArrayList<>();
@@ -81,13 +85,7 @@ public class HiveSplitGenerator {
List<PaimonInputSplit> splits = new ArrayList<>();
// locations may contain multiple partitions
- for (String location : locations.split(",")) {
- if (!location.startsWith(table.location().toString())) {
- // Hive create dummy file for empty table or partition. If
this location doesn't
- // belong to this table, nothing to do.
- continue;
- }
-
+ for (String location : locationsToPlan(table, jobConf, locations)) {
InnerTableScan scan;
if (tagToPartField != null) {
// the location should be pruned by partition predicate
@@ -130,6 +128,29 @@ public class HiveSplitGenerator {
return splits.toArray(new InputSplit[0]);
}
+ private static List<String> locationsToPlan(
+ FileStoreTable table, JobConf jobConf, String locations) {
+ List<String> belonging = new ArrayList<>();
+ String foreign = null;
+ for (String location : locations.split(",")) {
+ // Hive create dummy file for empty table or partition. If this
location doesn't
+ // belong to this table, nothing to do.
+ if (location.startsWith(table.location().toString())) {
+ belonging.add(location);
+ } else if (foreign == null && !location.isEmpty()) {
+ foreign = location;
+ }
+ }
+ // With LOCATION_IN_PROPERTIES, Hive gives the table a location under
its own warehouse,
+ // which does not start with the paimon location. If the table has no
Hive partitions,
+ // that location stands for the whole table. Hive partitions of such a
table all share
+ // it, so they cannot be told apart and are still skipped.
+ if (belonging.isEmpty() && foreign != null &&
!jobConf.getBoolean(HIVE_PARTITIONED, true)) {
+ return Collections.singletonList(foreign);
+ }
+ return belonging;
+ }
+
private static Optional<Predicate> createPartitionPredicate(
RowType rowType,
List<String> partitionKeys,
diff --git
a/paimon-hive/paimon-hive-connector-common/src/test/java/org/apache/paimon/hive/HiveCatalogITCaseBase.java
b/paimon-hive/paimon-hive-connector-common/src/test/java/org/apache/paimon/hive/HiveCatalogITCaseBase.java
index 279159eb48..47eb0ca695 100644
---
a/paimon-hive/paimon-hive-connector-common/src/test/java/org/apache/paimon/hive/HiveCatalogITCaseBase.java
+++
b/paimon-hive/paimon-hive-connector-common/src/test/java/org/apache/paimon/hive/HiveCatalogITCaseBase.java
@@ -1152,6 +1152,34 @@ public abstract class HiveCatalogITCaseBase {
"+I[3004, 3a, 34]");
}
+ @Test
+ @LocationInProperties
+ public void testHiveReadLocationInProperties() throws Exception {
+ tEnv.executeSql("CREATE TABLE t (a INT, b STRING)").await();
+ tEnv.executeSql("INSERT INTO t VALUES (1, 'apple'), (2,
'banana')").await();
+ assertThat(hiveShell.executeQuery("SELECT a, b FROM t"))
+ .containsExactlyInAnyOrder("1\tapple", "2\tbanana");
+ assertThat(hiveShell.executeQuery("SELECT b FROM t WHERE a =
2")).containsExactly("banana");
+ assertThat(hiveShell.executeQuery("SELECT count(*) FROM
t")).containsExactly("2");
+
+ // partitioned in paimon only, so Hive still sees one location for the
whole table
+ tEnv.executeSql("CREATE TABLE pt_t (pt INT, a INT) PARTITIONED BY
(pt)").await();
+ tEnv.executeSql("INSERT INTO pt_t VALUES (1, 10), (2, 20)").await();
+ assertThat(hiveShell.executeQuery("SELECT pt, a FROM pt_t"))
+ .containsExactlyInAnyOrder("1\t10", "2\t20");
+ assertThat(hiveShell.executeQuery("SELECT a FROM pt_t WHERE pt =
2")).containsExactly("20");
+
+ // Hive partitions all share the table location here, so they cannot
be read yet, but
+ // no partition may be read as another one
+ tEnv.executeSql(
+ "CREATE TABLE hive_pt_t (pt INT, a INT) PARTITIONED BY
(pt) "
+ + "WITH ('metastore.partitioned-table' =
'true')")
+ .await();
+ tEnv.executeSql("INSERT INTO hive_pt_t VALUES (1, 10), (2,
20)").await();
+ assertThat(hiveShell.executeQuery("SELECT pt, a FROM hive_pt_t"))
+ .isSubsetOf("1\t10", "2\t20");
+ }
+
private void prepareTestAddPartitionsToMetastore() throws Exception {
tEnv.executeSql(
String.join(
diff --git
a/paimon-hive/paimon-hive-connector-common/src/test/java/org/apache/paimon/hive/HiveSplitGeneratorTest.java
b/paimon-hive/paimon-hive-connector-common/src/test/java/org/apache/paimon/hive/HiveSplitGeneratorTest.java
index 250dc00f6a..73ba93682c 100644
---
a/paimon-hive/paimon-hive-connector-common/src/test/java/org/apache/paimon/hive/HiveSplitGeneratorTest.java
+++
b/paimon-hive/paimon-hive-connector-common/src/test/java/org/apache/paimon/hive/HiveSplitGeneratorTest.java
@@ -181,6 +181,24 @@ public class HiveSplitGeneratorTest {
assertThat(splits).hasSize(1);
}
+ @Test
+ public void testForeignLocation() throws Exception {
+ FileStoreTable table = createPartitionedTableWithTwoPartitions();
+ JobConf jobConf = new JobConf();
+ jobConf.set(FileInputFormat.INPUT_DIR,
"/hive/warehouse/somewhere-else");
+
+ // With LOCATION_IN_PROPERTIES, Hive gives the table a location under
its own warehouse.
+ // Without Hive partitions it is the location of the whole table.
+ jobConf.setBoolean(HiveSplitGenerator.HIVE_PARTITIONED, false);
+ assertThat(HiveSplitGenerator.generateSplits(table, jobConf,
1)).hasSize(2);
+
+ // All Hive partitions share that location, which names no partition.
Planning it would
+ // read every partition once for each Hive partition. Hive decides
this, not the
+ // metastore.partitioned-table option, which a session can override.
+ jobConf.setBoolean(HiveSplitGenerator.HIVE_PARTITIONED, true);
+ assertThat(HiveSplitGenerator.generateSplits(table, jobConf,
1)).isEmpty();
+ }
+
private InputSplit[] generateSplits(FileStoreTable table, String
partitionDir) {
JobConf jobConf = new JobConf();
jobConf.set(FileInputFormat.INPUT_DIR, tablePath + "/" + partitionDir);