This is an automated email from the ASF dual-hosted git repository.
difin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hive.git
The following commit(s) were added to refs/heads/master by this push:
new 6c25a190090 HIVE-29808: Iceberg: Add guardrails to compaction with
schema evolution (#6690)
6c25a190090 is described below
commit 6c25a1900908ea000b4e9ba3522a0dca2c5ab31c
Author: Dmitriy Fingerman <[email protected]>
AuthorDate: Fri Aug 14 10:16:27 2026 -0400
HIVE-29808: Iceberg: Add guardrails to compaction with schema evolution
(#6690)
Co-authored-by: Dmitriy Fingerman <[email protected]>
---
.../iceberg/mr/hive/HiveIcebergStorageHandler.java | 14 ++
.../apache/iceberg/mr/hive/IcebergTableUtil.java | 16 +-
.../iceberg/mr/hive/TestIcebergTableUtil.java | 117 +++++++++++++
...iceberg_major_compaction_partition_evolution2.q | 14 ++
.../iceberg_minor_compaction_partition_evolution.q | 16 ++
...erg_major_compaction_partition_evolution2.q.out | 171 ++++++++++++++++++
...berg_minor_compaction_partition_evolution.q.out | 191 +++++++++++++++++++++
.../hadoop/hive/ql/parse/AlterClauseParser.g | 4 +-
.../compact/AlterTableCompactOperation.java | 4 +
.../hive/ql/metadata/HiveStorageHandler.java | 9 +
10 files changed, 551 insertions(+), 5 deletions(-)
diff --git
a/iceberg/iceberg-handler/src/main/java/org/apache/iceberg/mr/hive/HiveIcebergStorageHandler.java
b/iceberg/iceberg-handler/src/main/java/org/apache/iceberg/mr/hive/HiveIcebergStorageHandler.java
index c6982640d4a..b23c2099185 100644
---
a/iceberg/iceberg-handler/src/main/java/org/apache/iceberg/mr/hive/HiveIcebergStorageHandler.java
+++
b/iceberg/iceberg-handler/src/main/java/org/apache/iceberg/mr/hive/HiveIcebergStorageHandler.java
@@ -2209,6 +2209,20 @@ Boolean hasAppendsOnly(Iterable<Snapshot> snapshots,
SnapshotContext since) {
return null;
}
+ @Override
+ public void
validateCompactionPartition(org.apache.hadoop.hive.ql.metadata.Table hmsTable,
String partitionName)
+ throws HiveException {
+ Table table = IcebergTableUtil.getTable(conf, hmsTable.getTTable());
+ if (!IcebergTableUtil.hasUndergonePartitionEvolution(table)) {
+ return;
+ }
+ try {
+ IcebergTableUtil.getPartitionSpec(table, partitionName);
+ } catch (MetaException e) {
+ throw new HiveException(e);
+ }
+ }
+
@Override
public void validatePartSpec(org.apache.hadoop.hive.ql.metadata.Table
hmsTable, Map<String, String> partitionSpec,
RewritePolicy policy) throws SemanticException {
diff --git
a/iceberg/iceberg-handler/src/main/java/org/apache/iceberg/mr/hive/IcebergTableUtil.java
b/iceberg/iceberg-handler/src/main/java/org/apache/iceberg/mr/hive/IcebergTableUtil.java
index dd613f452be..897771bbb3c 100644
---
a/iceberg/iceberg-handler/src/main/java/org/apache/iceberg/mr/hive/IcebergTableUtil.java
+++
b/iceberg/iceberg-handler/src/main/java/org/apache/iceberg/mr/hive/IcebergTableUtil.java
@@ -775,15 +775,25 @@ public static PartitionSpec getPartitionSpec(Table
icebergTable, String partitio
// Extract field names from the path: "field1=val1/field2=val2" → [field1,
field2]
List<String> fieldNames =
Lists.newArrayList(Warehouse.makeSpecFromName(partitionPath).keySet());
- return icebergTable.specs().values().stream()
+ List<PartitionSpec> matches = icebergTable.specs().values().stream()
.filter(spec -> {
List<String> specFieldNames = spec.fields().stream()
.map(PartitionField::name)
.toList();
return specFieldNames.equals(fieldNames);
})
- .findFirst() // Supposed to be only one matching spec
- .orElseThrow(() -> new HiveException("No matching partition spec found
for partition path: " + partitionPath));
+ .toList();
+
+ if (matches.size() > 1) {
+ throw new HiveException(String.format(
+ "Ambiguous partition spec for partition path %s: matched spec ids
%s",
+ partitionPath,
+
matches.stream().map(PartitionSpec::specId).map(String::valueOf).collect(Collectors.joining(",
"))));
+ }
+ if (matches.isEmpty()) {
+ throw new HiveException("No matching partition spec found for partition
path: " + partitionPath);
+ }
+ return matches.get(0);
}
public static TransformSpec getTransformSpec(Table table, String
transformName, int sourceId) {
diff --git
a/iceberg/iceberg-handler/src/test/java/org/apache/iceberg/mr/hive/TestIcebergTableUtil.java
b/iceberg/iceberg-handler/src/test/java/org/apache/iceberg/mr/hive/TestIcebergTableUtil.java
new file mode 100644
index 00000000000..dd48fe8a6b2
--- /dev/null
+++
b/iceberg/iceberg-handler/src/test/java/org/apache/iceberg/mr/hive/TestIcebergTableUtil.java
@@ -0,0 +1,117 @@
+/*
+ * 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.iceberg.mr.hive;
+
+import java.io.File;
+import java.io.IOException;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.hive.ql.metadata.HiveException;
+import org.apache.iceberg.AssertHelpers;
+import org.apache.iceberg.FileFormat;
+import org.apache.iceberg.PartitionSpec;
+import org.apache.iceberg.Schema;
+import org.apache.iceberg.Table;
+import org.apache.iceberg.TableProperties;
+import org.apache.iceberg.UpdatePartitionSpec;
+import org.apache.iceberg.hadoop.HadoopTables;
+import org.apache.iceberg.relocated.com.google.common.collect.ImmutableMap;
+import org.apache.iceberg.types.Types;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import static org.junit.Assert.assertEquals;
+
+public class TestIcebergTableUtil {
+
+ private static final Schema SCHEMA = new Schema(
+ Types.NestedField.optional(1, "first_name", Types.StringType.get()),
+ Types.NestedField.optional(2, "dept_id", Types.LongType.get())
+ );
+
+ @Rule
+ public TemporaryFolder tmp = new TemporaryFolder();
+
+ @Test
+ public void testGetPartitionSpecAmbiguousAfterPartitionEvolution() throws
IOException {
+ Table table = createV1Table();
+ setPartitionSpec(table, "dept_id");
+ setPartitionSpec(table);
+ setPartitionSpec(table, "dept_id");
+
+ // v1 evolution: spec 1 is identity(dept_id), spec 2 is void(dept_id);
both share the same field name.
+ assertEquals(4, table.specs().size());
+ assertEquals("identity",
table.specs().get(1).fields().get(0).transform().toString());
+ assertEquals("void",
table.specs().get(2).fields().get(0).transform().toString());
+
+ AssertHelpers.assertThrows(
+ "Should reject ambiguous partition spec resolution",
+ HiveException.class,
+ "Ambiguous partition spec for partition path dept_id=1: matched spec
ids 1, 2",
+ () -> IcebergTableUtil.getPartitionSpec(table, "dept_id=1"));
+ }
+
+ @Test
+ public void testGetPartitionSpecReturnsUniqueMatch() throws Exception {
+ Table table = createV1Table();
+ setPartitionSpec(table, "dept_id");
+
+ PartitionSpec result = IcebergTableUtil.getPartitionSpec(table,
"dept_id=1");
+ assertEquals(1, result.specId());
+ }
+
+ @Test
+ public void testGetPartitionSpecNoMatchingSpec() throws IOException {
+ Table table = createV1Table();
+
+ AssertHelpers.assertThrows(
+ "Should fail when no spec matches partition path fields",
+ HiveException.class,
+ "No matching partition spec found for partition path: dept_id=1",
+ () -> IcebergTableUtil.getPartitionSpec(table, "dept_id=1"));
+ }
+
+ private Table createV1Table() throws IOException {
+ File location = tmp.newFolder();
+ Configuration conf = new Configuration();
+ HadoopTables tables = new HadoopTables(conf);
+ return tables.create(
+ SCHEMA,
+ PartitionSpec.unpartitioned(),
+ ImmutableMap.of(
+ TableProperties.FORMAT_VERSION, "1",
+ TableProperties.DEFAULT_FILE_FORMAT, FileFormat.PARQUET.name()),
+ location.getAbsolutePath());
+ }
+
+ /**
+ * Mirrors Hive's {@code ALTER TABLE ... SET PARTITION SPEC (...)}: drop all
current partition
+ * fields, then add identity fields for the requested spec (empty varargs →
unpartitioned).
+ */
+ private void setPartitionSpec(Table table, String... identityFields) {
+ UpdatePartitionSpec update = table.updateSpec().caseSensitive(false);
+ table.spec().fields().forEach(field -> update.removeField(field.name()));
+ for (String field : identityFields) {
+ update.addField(field);
+ }
+ update.commit();
+ table.refresh();
+ }
+}
diff --git
a/iceberg/iceberg-handler/src/test/queries/positive/iceberg_major_compaction_partition_evolution2.q
b/iceberg/iceberg-handler/src/test/queries/positive/iceberg_major_compaction_partition_evolution2.q
index d8a51b8fcf4..4ca441ff0b1 100644
---
a/iceberg/iceberg-handler/src/test/queries/positive/iceberg_major_compaction_partition_evolution2.q
+++
b/iceberg/iceberg-handler/src/test/queries/positive/iceberg_major_compaction_partition_evolution2.q
@@ -58,3 +58,17 @@ select * from ice_orc;
describe formatted ice_orc;
show compactions order by 'partition';
+alter table ice_orc set partition spec();
+
+insert into ice_orc VALUES ('fn9','ln9', 3);
+insert into ice_orc VALUES ('fn10','ln10', 3);
+
+select * from ice_orc;
+describe formatted ice_orc;
+
+alter table ice_orc COMPACT 'major' and wait;
+
+select * from ice_orc;
+describe formatted ice_orc;
+show compactions order by 'partition';
+
diff --git
a/iceberg/iceberg-handler/src/test/queries/positive/iceberg_minor_compaction_partition_evolution.q
b/iceberg/iceberg-handler/src/test/queries/positive/iceberg_minor_compaction_partition_evolution.q
index 0dc636c0da4..f89fbf519e0 100644
---
a/iceberg/iceberg-handler/src/test/queries/positive/iceberg_minor_compaction_partition_evolution.q
+++
b/iceberg/iceberg-handler/src/test/queries/positive/iceberg_minor_compaction_partition_evolution.q
@@ -1,3 +1,4 @@
+-- SORT_QUERY_RESULTS
-- Mask neededVirtualColumns due to non-strict order
--! qt:replace:/(\s+neededVirtualColumns:\s)(.*)/$1#Masked#/
-- Mask random uuid
@@ -67,3 +68,18 @@ select * from ice_orc where dept_id=1;
select * from ice_orc where dept_id=2;
describe formatted ice_orc;
show compactions order by 'partition';
+
+alter table ice_orc set partition spec();
+
+insert into ice_orc VALUES ('fn9','ln9', 3), ('fn10','ln10', 3);
+insert into ice_orc VALUES ('fn11','ln11', 3), ('fn12','ln12', 3);
+
+select * from ice_orc;
+describe formatted ice_orc;
+
+alter table ice_orc set tblproperties
('compactor.threshold.target.size'='4000');
+alter table ice_orc COMPACT 'minor' and wait pool 'iceberg';
+
+select * from ice_orc;
+describe formatted ice_orc;
+show compactions order by 'partition';
diff --git
a/iceberg/iceberg-handler/src/test/results/positive/llap/iceberg_major_compaction_partition_evolution2.q.out
b/iceberg/iceberg-handler/src/test/results/positive/llap/iceberg_major_compaction_partition_evolution2.q.out
index d7d87eac82d..60ad0af240d 100644
---
a/iceberg/iceberg-handler/src/test/results/positive/llap/iceberg_major_compaction_partition_evolution2.q.out
+++
b/iceberg/iceberg-handler/src/test/results/positive/llap/iceberg_major_compaction_partition_evolution2.q.out
@@ -282,3 +282,174 @@ POSTHOOK: type: SHOW COMPACTIONS
CompactionId Database Table Partition Type State Worker
host Worker Enqueue Time Start Time Duration(ms) HadoopJobId
Error message Initiator host Initiator Pool name TxnId Next
TxnId Commit Time Highest WriteId
#Masked# ice_comp ice_orc dept_id=2 MAJOR succeeded
#Masked# manual iceberg 0 0 0 ---
#Masked# ice_comp ice_orc --- MAJOR succeeded
#Masked# manual iceberg 0 0 0 ---
+PREHOOK: query: alter table ice_orc set partition spec()
+PREHOOK: type: ALTERTABLE_SETPARTSPEC
+PREHOOK: Input: ice_comp@ice_orc
+POSTHOOK: query: alter table ice_orc set partition spec()
+POSTHOOK: type: ALTERTABLE_SETPARTSPEC
+POSTHOOK: Input: ice_comp@ice_orc
+POSTHOOK: Output: ice_comp@ice_orc
+PREHOOK: query: insert into ice_orc VALUES ('fn9','ln9', 3)
+PREHOOK: type: QUERY
+PREHOOK: Input: _dummy_database@_dummy_table
+PREHOOK: Output: ice_comp@ice_orc
+POSTHOOK: query: insert into ice_orc VALUES ('fn9','ln9', 3)
+POSTHOOK: type: QUERY
+POSTHOOK: Input: _dummy_database@_dummy_table
+POSTHOOK: Output: ice_comp@ice_orc
+PREHOOK: query: insert into ice_orc VALUES ('fn10','ln10', 3)
+PREHOOK: type: QUERY
+PREHOOK: Input: _dummy_database@_dummy_table
+PREHOOK: Output: ice_comp@ice_orc
+POSTHOOK: query: insert into ice_orc VALUES ('fn10','ln10', 3)
+POSTHOOK: type: QUERY
+POSTHOOK: Input: _dummy_database@_dummy_table
+POSTHOOK: Output: ice_comp@ice_orc
+PREHOOK: query: select * from ice_orc
+PREHOOK: type: QUERY
+PREHOOK: Input: ice_comp@ice_orc
+#### A masked pattern was here ####
+POSTHOOK: query: select * from ice_orc
+POSTHOOK: type: QUERY
+POSTHOOK: Input: ice_comp@ice_orc
+#### A masked pattern was here ####
+fn1 ln1 1
+fn10 ln10 3
+fn2 ln2 1
+fn5 ln5 2
+fn6 ln6 2
+fn9 ln9 3
+PREHOOK: query: describe formatted ice_orc
+PREHOOK: type: DESCTABLE
+PREHOOK: Input: ice_comp@ice_orc
+POSTHOOK: query: describe formatted ice_orc
+POSTHOOK: type: DESCTABLE
+POSTHOOK: Input: ice_comp@ice_orc
+# col_name data_type comment
+first_name string
+last_name string
+dept_id bigint
+
+# Detailed Table Information
+Database: ice_comp
+#### A masked pattern was here ####
+Retention: 0
+#### A masked pattern was here ####
+Table Type: EXTERNAL_TABLE
+Table Parameters:
+ COLUMN_STATS_ACCURATE
{\"BASIC_STATS\":\"true\",\"COLUMN_STATS\":{\"dept_id\":\"true\",\"first_name\":\"true\",\"last_name\":\"true\"}}
+ EXTERNAL TRUE
+ bucketing_version 2
+ compactor.threshold.target.size 1500
+ current-schema
{\"type\":\"struct\",\"schema-id\":0,\"fields\":[{\"id\":1,\"name\":\"first_name\",\"required\":false,\"type\":\"string\"},{\"id\":2,\"name\":\"last_name\",\"required\":false,\"type\":\"string\"},{\"id\":3,\"name\":\"dept_id\",\"required\":false,\"type\":\"long\"}]}
+ current-snapshot-id #Masked#
+ current-snapshot-summary
{\"manifests-created\":\"1\",\"manifests-kept\":\"2\",\"manifests-replaced\":\"0\",\"added-data-files\":\"1\",\"added-records\":\"1\",\"added-files-size\":\"#Masked#\",\"changed-partition-count\":\"1\",\"total-records\":\"6\",\"total-files-size\":\"#Masked#\",\"total-data-files\":\"4\",\"total-delete-files\":\"0\",\"total-position-deletes\":\"0\",\"total-equality-deletes\":\"0\",\"iceberg-version\":\"#Masked#\"}
+ current-snapshot-timestamp-ms #Masked#
+ format-version 2
+#### A masked pattern was here ####
+ numFiles 4
+ numRows 6
+ parquet.compression zstd
+#### A masked pattern was here ####
+ rawDataSize 0
+ serialization.format 1
+ snapshot-count 14
+ storage_handler
org.apache.iceberg.mr.hive.HiveIcebergStorageHandler
+ table_type ICEBERG
+ totalSize #Masked#
+#### A masked pattern was here ####
+ uuid #Masked#
+ write.delete.mode merge-on-read
+ write.format.default orc
+ write.merge.mode merge-on-read
+ write.metadata.delete-after-commit.enabled true
+ write.update.mode merge-on-read
+
+# Storage Information
+SerDe Library: org.apache.iceberg.mr.hive.HiveIcebergSerDe
+InputFormat: org.apache.iceberg.mr.hive.HiveIcebergInputFormat
+OutputFormat: org.apache.iceberg.mr.hive.HiveIcebergOutputFormat
+Compressed: No
+Sort Columns: []
+PREHOOK: query: alter table ice_orc COMPACT 'major' and wait
+PREHOOK: type: ALTERTABLE_COMPACT
+PREHOOK: Input: ice_comp@ice_orc
+PREHOOK: Output: ice_comp@ice_orc
+POSTHOOK: query: alter table ice_orc COMPACT 'major' and wait
+POSTHOOK: type: ALTERTABLE_COMPACT
+POSTHOOK: Input: ice_comp@ice_orc
+POSTHOOK: Output: ice_comp@ice_orc
+PREHOOK: query: select * from ice_orc
+PREHOOK: type: QUERY
+PREHOOK: Input: ice_comp@ice_orc
+#### A masked pattern was here ####
+POSTHOOK: query: select * from ice_orc
+POSTHOOK: type: QUERY
+POSTHOOK: Input: ice_comp@ice_orc
+#### A masked pattern was here ####
+fn1 ln1 1
+fn10 ln10 3
+fn2 ln2 1
+fn5 ln5 2
+fn6 ln6 2
+fn9 ln9 3
+PREHOOK: query: describe formatted ice_orc
+PREHOOK: type: DESCTABLE
+PREHOOK: Input: ice_comp@ice_orc
+POSTHOOK: query: describe formatted ice_orc
+POSTHOOK: type: DESCTABLE
+POSTHOOK: Input: ice_comp@ice_orc
+# col_name data_type comment
+first_name string
+last_name string
+dept_id bigint
+
+# Detailed Table Information
+Database: ice_comp
+#### A masked pattern was here ####
+Retention: 0
+#### A masked pattern was here ####
+Table Type: EXTERNAL_TABLE
+Table Parameters:
+ COLUMN_STATS_ACCURATE
{\"BASIC_STATS\":\"true\",\"COLUMN_STATS\":{\"dept_id\":\"true\",\"first_name\":\"true\",\"last_name\":\"true\"}}
+ EXTERNAL TRUE
+ bucketing_version 2
+ compactor.threshold.target.size 1500
+ current-schema
{\"type\":\"struct\",\"schema-id\":0,\"fields\":[{\"id\":1,\"name\":\"first_name\",\"required\":false,\"type\":\"string\"},{\"id\":2,\"name\":\"last_name\",\"required\":false,\"type\":\"string\"},{\"id\":3,\"name\":\"dept_id\",\"required\":false,\"type\":\"long\"}]}
+ current-snapshot-id #Masked#
+ current-snapshot-summary
{\"manifests-created\":\"4\",\"manifests-kept\":\"0\",\"manifests-replaced\":\"3\",\"added-data-files\":\"1\",\"deleted-data-files\":\"4\",\"added-records\":\"6\",\"deleted-records\":\"6\",\"added-files-size\":\"#Masked#\",\"removed-files-size\":\"#Masked#\",\"changed-partition-count\":\"3\",\"total-records\":\"6\",\"total-files-size\":\"#Masked#\",\"total-data-files\":\"1\",\"total-delete-files\":\"0\",\"total-position-deletes\":\"0\",\"total-equality-deletes\"
[...]
+ current-snapshot-timestamp-ms #Masked#
+ format-version 2
+#### A masked pattern was here ####
+ numFiles 1
+ numRows 6
+ parquet.compression zstd
+#### A masked pattern was here ####
+ rawDataSize 0
+ serialization.format 1
+ snapshot-count 15
+ storage_handler
org.apache.iceberg.mr.hive.HiveIcebergStorageHandler
+ table_type ICEBERG
+ totalSize #Masked#
+#### A masked pattern was here ####
+ uuid #Masked#
+ write.delete.mode merge-on-read
+ write.format.default orc
+ write.merge.mode merge-on-read
+ write.metadata.delete-after-commit.enabled true
+ write.update.mode merge-on-read
+
+# Storage Information
+SerDe Library: org.apache.iceberg.mr.hive.HiveIcebergSerDe
+InputFormat: org.apache.iceberg.mr.hive.HiveIcebergInputFormat
+OutputFormat: org.apache.iceberg.mr.hive.HiveIcebergOutputFormat
+Compressed: No
+Sort Columns: []
+PREHOOK: query: show compactions order by 'partition'
+PREHOOK: type: SHOW COMPACTIONS
+POSTHOOK: query: show compactions order by 'partition'
+POSTHOOK: type: SHOW COMPACTIONS
+CompactionId Database Table Partition Type State Worker
host Worker Enqueue Time Start Time Duration(ms) HadoopJobId
Error message Initiator host Initiator Pool name TxnId Next
TxnId Commit Time Highest WriteId
+#Masked# ice_comp ice_orc dept_id=2 MAJOR succeeded
#Masked# manual iceberg 0 0 0 ---
+#Masked# ice_comp ice_orc --- MAJOR succeeded
#Masked# manual iceberg 0 0 0 ---
+#Masked# ice_comp ice_orc --- MAJOR succeeded
#Masked# manual iceberg 0 0 0 ---
diff --git
a/iceberg/iceberg-handler/src/test/results/positive/llap/iceberg_minor_compaction_partition_evolution.q.out
b/iceberg/iceberg-handler/src/test/results/positive/llap/iceberg_minor_compaction_partition_evolution.q.out
index a2ea3abe933..466c234e72c 100644
---
a/iceberg/iceberg-handler/src/test/results/positive/llap/iceberg_minor_compaction_partition_evolution.q.out
+++
b/iceberg/iceberg-handler/src/test/results/positive/llap/iceberg_minor_compaction_partition_evolution.q.out
@@ -344,3 +344,194 @@ CompactionId Database Table Partition
Type State Worker host Worker Enqueue Time
#Masked# ice_comp ice_orc dept_id=2 MINOR refused
#Masked# manual iceberg 0 0 0 ---
#Masked# ice_comp ice_orc --- MINOR succeeded
#Masked# manual iceberg 0 0 0 ---
#Masked# ice_comp ice_orc --- MINOR refused #Masked#
manual iceberg 0 0 0 ---
+PREHOOK: query: alter table ice_orc set partition spec()
+PREHOOK: type: ALTERTABLE_SETPARTSPEC
+PREHOOK: Input: ice_comp@ice_orc
+POSTHOOK: query: alter table ice_orc set partition spec()
+POSTHOOK: type: ALTERTABLE_SETPARTSPEC
+POSTHOOK: Input: ice_comp@ice_orc
+POSTHOOK: Output: ice_comp@ice_orc
+PREHOOK: query: insert into ice_orc VALUES ('fn9','ln9', 3), ('fn10','ln10', 3)
+PREHOOK: type: QUERY
+PREHOOK: Input: _dummy_database@_dummy_table
+PREHOOK: Output: ice_comp@ice_orc
+POSTHOOK: query: insert into ice_orc VALUES ('fn9','ln9', 3), ('fn10','ln10',
3)
+POSTHOOK: type: QUERY
+POSTHOOK: Input: _dummy_database@_dummy_table
+POSTHOOK: Output: ice_comp@ice_orc
+PREHOOK: query: insert into ice_orc VALUES ('fn11','ln11', 3), ('fn12','ln12',
3)
+PREHOOK: type: QUERY
+PREHOOK: Input: _dummy_database@_dummy_table
+PREHOOK: Output: ice_comp@ice_orc
+POSTHOOK: query: insert into ice_orc VALUES ('fn11','ln11', 3),
('fn12','ln12', 3)
+POSTHOOK: type: QUERY
+POSTHOOK: Input: _dummy_database@_dummy_table
+POSTHOOK: Output: ice_comp@ice_orc
+PREHOOK: query: select * from ice_orc
+PREHOOK: type: QUERY
+PREHOOK: Input: ice_comp@ice_orc
+#### A masked pattern was here ####
+POSTHOOK: query: select * from ice_orc
+POSTHOOK: type: QUERY
+POSTHOOK: Input: ice_comp@ice_orc
+#### A masked pattern was here ####
+fn1 ln1 1
+fn10 ln10 3
+fn11 ln11 3
+fn12 ln12 3
+fn2 ln2 1
+fn3 ln3 1
+fn5 ln5 2
+fn6 ln6 2
+fn7 ln7 2
+fn9 ln9 3
+PREHOOK: query: describe formatted ice_orc
+PREHOOK: type: DESCTABLE
+PREHOOK: Input: ice_comp@ice_orc
+POSTHOOK: query: describe formatted ice_orc
+POSTHOOK: type: DESCTABLE
+POSTHOOK: Input: ice_comp@ice_orc
+# col_name data_type comment
+first_name string
+last_name string
+dept_id bigint
+
+# Detailed Table Information
+Database: ice_comp
+#### A masked pattern was here ####
+Retention: 0
+#### A masked pattern was here ####
+Table Type: EXTERNAL_TABLE
+Table Parameters:
+ COLUMN_STATS_ACCURATE
{\"BASIC_STATS\":\"true\",\"COLUMN_STATS\":{\"dept_id\":\"true\",\"first_name\":\"true\",\"last_name\":\"true\"}}
+ EXTERNAL TRUE
+ bucketing_version 2
+ compactor.threshold.min.input.files 4
+ compactor.threshold.target.size 4000
+ current-schema
{\"type\":\"struct\",\"schema-id\":0,\"fields\":[{\"id\":1,\"name\":\"first_name\",\"required\":false,\"type\":\"string\"},{\"id\":2,\"name\":\"last_name\",\"required\":false,\"type\":\"string\"},{\"id\":3,\"name\":\"dept_id\",\"required\":false,\"type\":\"long\"}]}
+ current-snapshot-id #Masked#
+ current-snapshot-summary
{\"manifests-created\":\"1\",\"manifests-kept\":\"2\",\"manifests-replaced\":\"0\",\"added-data-files\":\"1\",\"added-records\":\"2\",\"added-files-size\":\"#Masked#\",\"changed-partition-count\":\"1\",\"total-records\":\"10\",\"total-files-size\":\"#Masked#\",\"total-data-files\":\"4\",\"total-delete-files\":\"0\",\"total-position-deletes\":\"0\",\"total-equality-deletes\":\"0\",\"iceberg-version\":\"#Masked#\"}
+ current-snapshot-timestamp-ms #Masked#
+ format-version 2
+#### A masked pattern was here ####
+ numFiles 4
+ numRows 10
+ parquet.compression zstd
+#### A masked pattern was here ####
+ rawDataSize 0
+ serialization.format 1
+ snapshot-count 10
+ storage_handler
org.apache.iceberg.mr.hive.HiveIcebergStorageHandler
+ table_type ICEBERG
+ totalSize #Masked#
+#### A masked pattern was here ####
+ uuid #Masked#
+ write.delete.mode merge-on-read
+ write.format.default orc
+ write.merge.mode merge-on-read
+ write.metadata.delete-after-commit.enabled true
+ write.update.mode merge-on-read
+
+# Storage Information
+SerDe Library: org.apache.iceberg.mr.hive.HiveIcebergSerDe
+InputFormat: org.apache.iceberg.mr.hive.HiveIcebergInputFormat
+OutputFormat: org.apache.iceberg.mr.hive.HiveIcebergOutputFormat
+Compressed: No
+Sort Columns: []
+PREHOOK: query: alter table ice_orc set tblproperties
('compactor.threshold.target.size'='4000')
+PREHOOK: type: ALTERTABLE_PROPERTIES
+PREHOOK: Input: ice_comp@ice_orc
+PREHOOK: Output: ice_comp@ice_orc
+POSTHOOK: query: alter table ice_orc set tblproperties
('compactor.threshold.target.size'='4000')
+POSTHOOK: type: ALTERTABLE_PROPERTIES
+POSTHOOK: Input: ice_comp@ice_orc
+POSTHOOK: Output: ice_comp@ice_orc
+PREHOOK: query: alter table ice_orc COMPACT 'minor' and wait pool 'iceberg'
+PREHOOK: type: ALTERTABLE_COMPACT
+PREHOOK: Input: ice_comp@ice_orc
+PREHOOK: Output: ice_comp@ice_orc
+POSTHOOK: query: alter table ice_orc COMPACT 'minor' and wait pool 'iceberg'
+POSTHOOK: type: ALTERTABLE_COMPACT
+POSTHOOK: Input: ice_comp@ice_orc
+POSTHOOK: Output: ice_comp@ice_orc
+PREHOOK: query: select * from ice_orc
+PREHOOK: type: QUERY
+PREHOOK: Input: ice_comp@ice_orc
+#### A masked pattern was here ####
+POSTHOOK: query: select * from ice_orc
+POSTHOOK: type: QUERY
+POSTHOOK: Input: ice_comp@ice_orc
+#### A masked pattern was here ####
+fn1 ln1 1
+fn10 ln10 3
+fn11 ln11 3
+fn12 ln12 3
+fn2 ln2 1
+fn3 ln3 1
+fn5 ln5 2
+fn6 ln6 2
+fn7 ln7 2
+fn9 ln9 3
+PREHOOK: query: describe formatted ice_orc
+PREHOOK: type: DESCTABLE
+PREHOOK: Input: ice_comp@ice_orc
+POSTHOOK: query: describe formatted ice_orc
+POSTHOOK: type: DESCTABLE
+POSTHOOK: Input: ice_comp@ice_orc
+# col_name data_type comment
+first_name string
+last_name string
+dept_id bigint
+
+# Detailed Table Information
+Database: ice_comp
+#### A masked pattern was here ####
+Retention: 0
+#### A masked pattern was here ####
+Table Type: EXTERNAL_TABLE
+Table Parameters:
+ COLUMN_STATS_ACCURATE
{\"BASIC_STATS\":\"true\",\"COLUMN_STATS\":{\"dept_id\":\"true\",\"first_name\":\"true\",\"last_name\":\"true\"}}
+ EXTERNAL TRUE
+ bucketing_version 2
+ compactor.threshold.min.input.files 4
+ compactor.threshold.target.size 4000
+ current-schema
{\"type\":\"struct\",\"schema-id\":0,\"fields\":[{\"id\":1,\"name\":\"first_name\",\"required\":false,\"type\":\"string\"},{\"id\":2,\"name\":\"last_name\",\"required\":false,\"type\":\"string\"},{\"id\":3,\"name\":\"dept_id\",\"required\":false,\"type\":\"long\"}]}
+ current-snapshot-id #Masked#
+ current-snapshot-summary
{\"manifests-created\":\"4\",\"manifests-kept\":\"0\",\"manifests-replaced\":\"3\",\"added-data-files\":\"1\",\"deleted-data-files\":\"4\",\"added-records\":\"10\",\"deleted-records\":\"10\",\"added-files-size\":\"#Masked#\",\"removed-files-size\":\"#Masked#\",\"changed-partition-count\":\"3\",\"total-records\":\"10\",\"total-files-size\":\"#Masked#\",\"total-data-files\":\"1\",\"total-delete-files\":\"0\",\"total-position-deletes\":\"0\",\"total-equality-delete
[...]
+ current-snapshot-timestamp-ms #Masked#
+ format-version 2
+#### A masked pattern was here ####
+ numFiles 1
+ numRows 10
+ parquet.compression zstd
+#### A masked pattern was here ####
+ rawDataSize 0
+ serialization.format 1
+ snapshot-count 11
+ storage_handler
org.apache.iceberg.mr.hive.HiveIcebergStorageHandler
+ table_type ICEBERG
+ totalSize #Masked#
+#### A masked pattern was here ####
+ uuid #Masked#
+ write.delete.mode merge-on-read
+ write.format.default orc
+ write.merge.mode merge-on-read
+ write.metadata.delete-after-commit.enabled true
+ write.update.mode merge-on-read
+
+# Storage Information
+SerDe Library: org.apache.iceberg.mr.hive.HiveIcebergSerDe
+InputFormat: org.apache.iceberg.mr.hive.HiveIcebergInputFormat
+OutputFormat: org.apache.iceberg.mr.hive.HiveIcebergOutputFormat
+Compressed: No
+Sort Columns: []
+PREHOOK: query: show compactions order by 'partition'
+PREHOOK: type: SHOW COMPACTIONS
+POSTHOOK: query: show compactions order by 'partition'
+POSTHOOK: type: SHOW COMPACTIONS
+CompactionId Database Table Partition Type State Worker
host Worker Enqueue Time Start Time Duration(ms) HadoopJobId
Error message Initiator host Initiator Pool name TxnId Next
TxnId Commit Time Highest WriteId
+#Masked# ice_comp ice_orc dept_id=2 MINOR succeeded
#Masked# manual iceberg 0 0 0 ---
+#Masked# ice_comp ice_orc dept_id=2 MINOR refused
#Masked# manual iceberg 0 0 0 ---
+#Masked# ice_comp ice_orc --- MINOR succeeded
#Masked# manual iceberg 0 0 0 ---
+#Masked# ice_comp ice_orc --- MINOR succeeded
#Masked# manual iceberg 0 0 0 ---
+#Masked# ice_comp ice_orc --- MINOR refused #Masked#
manual iceberg 0 0 0 ---
diff --git
a/parser/src/java/org/apache/hadoop/hive/ql/parse/AlterClauseParser.g
b/parser/src/java/org/apache/hadoop/hive/ql/parse/AlterClauseParser.g
index f34c3aec7a0..49029f35f90 100644
--- a/parser/src/java/org/apache/hadoop/hive/ql/parse/AlterClauseParser.g
+++ b/parser/src/java/org/apache/hadoop/hive/ql/parse/AlterClauseParser.g
@@ -516,8 +516,8 @@ alterStatementSuffixSetOwner
alterStatementSuffixSetPartSpec
@init { gParent.pushMsg("alter table set partition spec", state); }
@after { gParent.popMsg(state); }
- : KW_SET KW_PARTITION KW_SPEC LPAREN (spec = partitionTransformSpec) RPAREN
- -> ^(TOK_ALTERTABLE_SETPARTSPEC $spec)
+ : KW_SET KW_PARTITION KW_SPEC LPAREN (spec = partitionTransformSpec)?
RPAREN
+ -> ^(TOK_ALTERTABLE_SETPARTSPEC $spec?)
;
alterStatementSuffixConvert
diff --git
a/ql/src/java/org/apache/hadoop/hive/ql/ddl/table/storage/compact/AlterTableCompactOperation.java
b/ql/src/java/org/apache/hadoop/hive/ql/ddl/table/storage/compact/AlterTableCompactOperation.java
index c586e6c088b..adf4592f3df 100644
---
a/ql/src/java/org/apache/hadoop/hive/ql/ddl/table/storage/compact/AlterTableCompactOperation.java
+++
b/ql/src/java/org/apache/hadoop/hive/ql/ddl/table/storage/compact/AlterTableCompactOperation.java
@@ -91,6 +91,10 @@ else if (desc.getPartitionSpec() != null) {
Map<String, org.apache.hadoop.hive.metastore.api.Partition> partitionMap =
convertPartitionsFromThriftToDB(getPartitions(table));
+ if (desc.getPartitionSpec() != null && !partitionMap.isEmpty() &&
table.getStorageHandler() != null) {
+ table.getStorageHandler().validateCompactionPartition(table,
partitionMap.keySet().iterator().next());
+ }
+
TxnStore txnHandler = TxnUtils.getTxnStore(context.getConf());
CompactionRequest compactionRequest = new
CompactionRequest(table.getDbName(), table.getTableName(),
diff --git
a/ql/src/java/org/apache/hadoop/hive/ql/metadata/HiveStorageHandler.java
b/ql/src/java/org/apache/hadoop/hive/ql/metadata/HiveStorageHandler.java
index ddd7ac51b23..ad18304fa72 100644
--- a/ql/src/java/org/apache/hadoop/hive/ql/metadata/HiveStorageHandler.java
+++ b/ql/src/java/org/apache/hadoop/hive/ql/metadata/HiveStorageHandler.java
@@ -902,6 +902,15 @@ default boolean
canUseTruncate(org.apache.hadoop.hive.ql.metadata.Table hmsTable
return true;
}
+ /**
+ * Validates that partition compaction can resolve a unique partition spec
for the given partition name.
+ * @param hmsTable table metadata stored in Hive Metastore
+ * @param partitionName fully qualified partition name
+ */
+ default void
validateCompactionPartition(org.apache.hadoop.hive.ql.metadata.Table hmsTable,
String partitionName)
+ throws HiveException {
+ }
+
/**
* Returns partitions names for the current table spec that correspond to
the provided partition spec.
* @param table {@link org.apache.hadoop.hive.ql.metadata.Table} table
metadata stored in Hive Metastore