Skip to content

Commit 5ed9def

Browse files
authored
Update hive docker to 4.2.1 (#3924)
* Update hive docker to 4.2.1 * Use newest Hive docker image
1 parent 9299bdb commit 5ed9def

7 files changed

Lines changed: 103 additions & 69 deletions

File tree

dev/docker-compose-integration.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,6 @@ services:
9898
image: pyiceberg-hive:latest
9999
build:
100100
context: hive/
101-
args:
102-
MAVEN_MIRROR: ${MAVEN_MIRROR:-https://repo1.maven.org/maven2}
103101
container_name: pyiceberg-hive
104102
hostname: hive
105103
networks:

dev/hive/Dockerfile

Lines changed: 7 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,30 +13,16 @@
1313
# See the License for the specific language governing permissions and
1414
# limitations under the License.
1515

16-
FROM apache/hive:4.0.0
17-
18-
# Dependency versions - changing these invalidates the JAR download layer
19-
ARG HADOOP_VERSION=3.3.6
20-
ARG AWS_SDK_BUNDLE=1.12.753
21-
ARG MAVEN_MIRROR=https://repo1.maven.org/maven2
16+
FROM apache/hive:4.2.1
2217

2318
USER root
2419

25-
# Install curl (separate layer - rarely changes)
26-
RUN apt-get update -qq && \
27-
apt-get -qq -y install --no-install-recommends curl && \
28-
apt-get clean && \
29-
rm -rf /var/lib/apt/lists/*
30-
31-
# Download JARs with retry logic (slow layer - only changes when versions change)
32-
RUN curl -fsSL --retry 3 --retry-delay 5 \
33-
-o /opt/hive/lib/hadoop-aws-${HADOOP_VERSION}.jar \
34-
"${MAVEN_MIRROR}/org/apache/hadoop/hadoop-aws/${HADOOP_VERSION}/hadoop-aws-${HADOOP_VERSION}.jar" && \
35-
curl -fsSL --retry 3 --retry-delay 5 \
36-
-o /opt/hive/lib/aws-java-sdk-bundle-${AWS_SDK_BUNDLE}.jar \
37-
"${MAVEN_MIRROR}/com/amazonaws/aws-java-sdk-bundle/${AWS_SDK_BUNDLE}/aws-java-sdk-bundle-${AWS_SDK_BUNDLE}.jar"
20+
# Link the hadoop-aws and AWS SDK jars that the image ships into the metastore classpath
21+
RUN ln -s /opt/hadoop/share/hadoop/tools/lib/hadoop-aws-*.jar /opt/hive/lib/ && \
22+
ln -s /opt/hadoop/share/hadoop/tools/lib/bundle-*.jar /opt/hive/lib/
3823

39-
# Copy configuration last (changes more frequently than JARs)
40-
COPY core-site.xml /opt/hadoop/etc/hadoop/core-site.xml
24+
# The entrypoint links this directory into the Hive config directory, over its own core-site.xml
25+
ENV HIVE_CUSTOM_CONF_DIR=/opt/hive/custom-conf
26+
COPY core-site.xml ${HIVE_CUSTOM_CONF_DIR}/core-site.xml
4127

4228
USER hive

dev/spark/Dockerfile

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ ARG ICEBERG_VERSION=1.11.0
2323
ARG ICEBERG_SPARK_RUNTIME_VERSION=4.0_2.13
2424
ARG HADOOP_VERSION=3.4.1
2525
ARG AWS_SDK_VERSION=2.24.6
26+
ARG HIVE_METASTORE_VERSION=4.0.1
2627
ARG MAVEN_MIRROR=https://repo.maven.apache.org/maven2
2728

2829
USER root
@@ -36,6 +37,11 @@ RUN apt-get update -qq && \
3637
mkdir -p /home/iceberg/spark-events && \
3738
chown -R spark:spark /home/iceberg
3839

40+
# Iceberg's Hive catalog uses the metastore client from the class path, and the bundled
41+
# Hive 2.3 one calls get_table, which Hive dropped in 4.0.1. Move it aside for a Hive 4 client.
42+
RUN mkdir -p "${SPARK_HOME}/hive-metastore-jars" && \
43+
mv "${SPARK_HOME}"/jars/hive-metastore-*.jar "${SPARK_HOME}/hive-metastore-jars/"
44+
3945
# Download JARs with retry logic (most cacheable - only changes when versions change)
4046
# This is the slowest step, so we do it before copying config files
4147
RUN set -e && \
@@ -44,6 +50,7 @@ RUN set -e && \
4450
"org/apache/iceberg/iceberg-spark-runtime-${ICEBERG_SPARK_RUNTIME_VERSION}/${ICEBERG_VERSION}/iceberg-spark-runtime-${ICEBERG_SPARK_RUNTIME_VERSION}-${ICEBERG_VERSION}.jar" \
4551
"org/apache/iceberg/iceberg-aws-bundle/${ICEBERG_VERSION}/iceberg-aws-bundle-${ICEBERG_VERSION}.jar" \
4652
"org/apache/hadoop/hadoop-aws/${HADOOP_VERSION}/hadoop-aws-${HADOOP_VERSION}.jar" \
53+
"org/apache/hive/hive-standalone-metastore-common/${HIVE_METASTORE_VERSION}/hive-standalone-metastore-common-${HIVE_METASTORE_VERSION}.jar" \
4754
"software/amazon/awssdk/bundle/${AWS_SDK_VERSION}/bundle-${AWS_SDK_VERSION}.jar"; \
4855
do \
4956
jar_name=$(basename "${jar_path}") && \

dev/spark/spark-defaults.conf

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ spark.hadoop.fs.s3a.endpoint http://minio:9000
4242
spark.sql.catalogImplementation hive
4343
spark.sql.warehouse.dir s3a://warehouse/hive/
4444

45+
# Spark's own Hive client speaks the Hive 2.3 API, so keep it off the Hive 4 client on the class path
46+
spark.sql.hive.metastore.version 2.3.10
47+
spark.sql.hive.metastore.jars path
48+
spark.sql.hive.metastore.jars.path file:///opt/spark/hive-metastore-jars/*,file:///opt/spark/jars/*
49+
4550
spark.sql.defaultCatalog rest
4651

4752
# Configure Spark UI and event logging

pyiceberg/catalog/hive.py

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
CheckLockRequest,
3333
EnvironmentContext,
3434
FieldSchema,
35+
GetTableRequest,
36+
GetTablesRequest,
3537
InvalidOperationException,
3638
LockComponent,
3739
LockLevel,
@@ -297,6 +299,7 @@ class HiveCatalog(MetastoreCatalog):
297299
def __init__(self, name: str, **properties: str):
298300
super().__init__(name, **properties)
299301
self._client = self._create_hive_client(properties)
302+
self._hive2_compatible = property_as_bool(properties, HIVE2_COMPATIBLE, HIVE2_COMPATIBLE_DEFAULT)
300303

301304
self._lock_check_min_wait_time = property_as_float(properties, LOCK_CHECK_MIN_WAIT_TIME, DEFAULT_LOCK_CHECK_MIN_WAIT_TIME)
302305
self._lock_check_max_wait_time = property_as_float(properties, LOCK_CHECK_MAX_WAIT_TIME, DEFAULT_LOCK_CHECK_MAX_WAIT_TIME)
@@ -367,7 +370,7 @@ def _convert_iceberg_into_hive(self, table: Table) -> HiveTable:
367370
sd=_construct_hive_storage_descriptor(
368371
table.schema(),
369372
table.location(),
370-
property_as_bool(self.properties, HIVE2_COMPATIBLE, HIVE2_COMPATIBLE_DEFAULT),
373+
self._hive2_compatible,
371374
),
372375
tableType=EXTERNAL_TABLE,
373376
parameters=_construct_parameters(metadata_location=table.metadata_location, metadata_properties=table.properties),
@@ -379,9 +382,21 @@ def _create_hive_table(self, open_client: Client, hive_table: HiveTable) -> None
379382
except AlreadyExistsException as e:
380383
raise TableAlreadyExistsError(f"Table {hive_table.dbName}.{hive_table.tableName} already exists") from e
381384

385+
def _fetch_hive_table(self, open_client: Client, database_name: str, table_name: str) -> HiveTable:
386+
# Hive 4.0.1 removed get_table, and Hive 2 does not have get_table_req
387+
if self._hive2_compatible:
388+
return open_client.get_table(dbname=database_name, tbl_name=table_name)
389+
return open_client.get_table_req(GetTableRequest(dbName=database_name, tblName=table_name)).table
390+
391+
def _fetch_hive_tables(self, open_client: Client, database_name: str) -> list[HiveTable]:
392+
table_names = open_client.get_all_tables(db_name=database_name)
393+
if self._hive2_compatible:
394+
return open_client.get_table_objects_by_name(dbname=database_name, tbl_names=table_names)
395+
return open_client.get_table_objects_by_name_req(GetTablesRequest(dbName=database_name, tblNames=table_names)).tables
396+
382397
def _get_hive_table(self, open_client: Client, database_name: str, table_name: str) -> HiveTable:
383398
try:
384-
return open_client.get_table(dbname=database_name, tbl_name=table_name)
399+
return self._fetch_hive_table(open_client, database_name, table_name)
385400
except NoSuchObjectException as e:
386401
raise NoSuchTableError(f"Table does not exists: {table_name}") from e
387402

@@ -428,7 +443,7 @@ def create_table(
428443

429444
with self._client as open_client:
430445
self._create_hive_table(open_client, tbl)
431-
hive_table = open_client.get_table(dbname=database_name, tbl_name=table_name)
446+
hive_table = self._fetch_hive_table(open_client, database_name, table_name)
432447

433448
return self._convert_hive_into_iceberg(hive_table)
434449

@@ -474,7 +489,7 @@ def register_table(self, identifier: str | Identifier, metadata_location: str, o
474489
tbl = self._convert_iceberg_into_hive(staged_table)
475490
with self._client as open_client:
476491
self._create_hive_table(open_client, tbl)
477-
hive_table = open_client.get_table(dbname=database_name, tbl_name=table_name)
492+
hive_table = self._fetch_hive_table(open_client, database_name, table_name)
478493

479494
return self._convert_hive_into_iceberg(hive_table)
480495

@@ -603,7 +618,7 @@ def commit_table(
603618
hive_table.sd = _construct_hive_storage_descriptor(
604619
updated_staged_table.schema(),
605620
updated_staged_table.location(),
606-
property_as_bool(self.properties, HIVE2_COMPATIBLE, HIVE2_COMPATIBLE_DEFAULT),
621+
self._hive2_compatible,
607622
)
608623
open_client.alter_table_with_environment_context(
609624
dbname=database_name,
@@ -703,7 +718,7 @@ def rename_table(self, from_identifier: str | Identifier, to_identifier: str | I
703718

704719
try:
705720
with self._client as open_client:
706-
tbl = open_client.get_table(dbname=from_database_name, tbl_name=from_table_name)
721+
tbl = self._fetch_hive_table(open_client, from_database_name, from_table_name)
707722
tbl.dbName = to_database_name
708723
tbl.tableName = to_table_name
709724
open_client.alter_table_with_environment_context(
@@ -778,9 +793,7 @@ def list_tables(self, namespace: str | Identifier) -> list[Identifier]:
778793
with self._client as open_client:
779794
return [
780795
(database_name, table.tableName)
781-
for table in open_client.get_table_objects_by_name(
782-
dbname=database_name, tbl_names=open_client.get_all_tables(db_name=database_name)
783-
)
796+
for table in self._fetch_hive_tables(open_client, database_name)
784797
if table.parameters.get(TABLE_TYPE, "").lower() == ICEBERG
785798
]
786799

tests/catalog/test_hive.py

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@
3131
AlreadyExistsException,
3232
EnvironmentContext,
3333
FieldSchema,
34+
GetTableRequest,
35+
GetTableResult,
36+
GetTablesRequest,
37+
GetTablesResult,
3438
InvalidOperationException,
3539
LockResponse,
3640
LockState,
@@ -293,6 +297,7 @@ def test_create_table(
293297
catalog._client = MagicMock()
294298
catalog._client.__enter__().create_table.return_value = None
295299
catalog._client.__enter__().get_table.return_value = hive_table
300+
catalog._client.__enter__().get_table_req.return_value = GetTableResult(table=hive_table)
296301
catalog._client.__enter__().get_database.return_value = hive_database
297302
catalog.create_table(("default", "table"), schema=table_schema_with_all_types, properties={"owner": "javaberg"})
298303

@@ -472,6 +477,7 @@ def test_create_table_with_given_location_removes_trailing_slash(
472477
catalog._client = MagicMock()
473478
catalog._client.__enter__().create_table.return_value = None
474479
catalog._client.__enter__().get_table.return_value = hive_table
480+
catalog._client.__enter__().get_table_req.return_value = GetTableResult(table=hive_table)
475481
catalog._client.__enter__().get_database.return_value = hive_database
476482
catalog.create_table(
477483
("default", "table"), schema=table_schema_with_all_types, properties={"owner": "javaberg"}, location=f"{location}/"
@@ -645,7 +651,7 @@ def test_create_v1_table(table_schema_simple: Schema, hive_database: HiveDatabas
645651

646652
catalog._client = MagicMock()
647653
catalog._client.__enter__().create_table.return_value = None
648-
catalog._client.__enter__().get_table.return_value = hive_table
654+
catalog._client.__enter__().get_table_req.return_value = GetTableResult(table=hive_table)
649655
catalog._client.__enter__().get_database.return_value = hive_database
650656
catalog.create_table(
651657
("default", "table"), schema=table_schema_simple, properties={"owner": "javaberg", "format-version": "1"}
@@ -696,10 +702,10 @@ def test_load_table(hive_table: HiveTable) -> None:
696702
catalog = HiveCatalog(HIVE_CATALOG_NAME, uri=HIVE_METASTORE_FAKE_URL)
697703

698704
catalog._client = MagicMock()
699-
catalog._client.__enter__().get_table.return_value = hive_table
705+
catalog._client.__enter__().get_table_req.return_value = GetTableResult(table=hive_table)
700706
table = catalog.load_table(("default", "new_tabl2e"))
701707

702-
catalog._client.__enter__().get_table.assert_called_with(dbname="default", tbl_name="new_tabl2e")
708+
catalog._client.__enter__().get_table_req.assert_called_with(GetTableRequest(dbName="default", tblName="new_tabl2e"))
703709

704710
expected = TableMetadataV2(
705711
location="s3://bucket/test/location",
@@ -796,11 +802,11 @@ def test_load_table_from_self_identifier(hive_table: HiveTable) -> None:
796802
catalog = HiveCatalog(HIVE_CATALOG_NAME, uri=HIVE_METASTORE_FAKE_URL)
797803

798804
catalog._client = MagicMock()
799-
catalog._client.__enter__().get_table.return_value = hive_table
805+
catalog._client.__enter__().get_table_req.return_value = GetTableResult(table=hive_table)
800806
intermediate = catalog.load_table(("default", "new_tabl2e"))
801807
table = catalog.load_table(intermediate.name())
802808

803-
catalog._client.__enter__().get_table.assert_called_with(dbname="default", tbl_name="new_tabl2e")
809+
catalog._client.__enter__().get_table_req.assert_called_with(GetTableRequest(dbName="default", tblName="new_tabl2e"))
804810

805811
expected = TableMetadataV2(
806812
location="s3://bucket/test/location",
@@ -902,7 +908,10 @@ def test_rename_table(hive_table: HiveTable) -> None:
902908
renamed_table.tableName = "new_tabl3e"
903909

904910
catalog._client = MagicMock()
905-
catalog._client.__enter__().get_table.side_effect = [hive_table, renamed_table]
911+
catalog._client.__enter__().get_table_req.side_effect = [
912+
GetTableResult(table=hive_table),
913+
GetTableResult(table=renamed_table),
914+
]
906915
catalog._client.__enter__().alter_table_with_environment_context.return_value = None
907916

908917
from_identifier = ("default", "new_tabl2e")
@@ -911,8 +920,11 @@ def test_rename_table(hive_table: HiveTable) -> None:
911920

912921
assert table.name() == to_identifier
913922

914-
calls = [call(dbname="default", tbl_name="new_tabl2e"), call(dbname="default", tbl_name="new_tabl3e")]
915-
catalog._client.__enter__().get_table.assert_has_calls(calls)
923+
calls = [
924+
call(GetTableRequest(dbName="default", tblName="new_tabl2e")),
925+
call(GetTableRequest(dbName="default", tblName="new_tabl3e")),
926+
]
927+
catalog._client.__enter__().get_table_req.assert_has_calls(calls)
916928
catalog._client.__enter__().alter_table_with_environment_context.assert_called_with(
917929
dbname="default",
918930
tbl_name="new_tabl2e",
@@ -926,25 +938,31 @@ def test_rename_table_from_self_identifier(hive_table: HiveTable) -> None:
926938
catalog.table_exists = MagicMock(return_value=False) # type: ignore[method-assign]
927939

928940
catalog._client = MagicMock()
929-
catalog._client.__enter__().get_table.return_value = hive_table
941+
catalog._client.__enter__().get_table_req.return_value = GetTableResult(table=hive_table)
930942

931943
from_identifier = ("default", "new_tabl2e")
932944
from_table = catalog.load_table(from_identifier)
933-
catalog._client.__enter__().get_table.assert_called_with(dbname="default", tbl_name="new_tabl2e")
945+
catalog._client.__enter__().get_table_req.assert_called_with(GetTableRequest(dbName="default", tblName="new_tabl2e"))
934946

935947
renamed_table = copy.deepcopy(hive_table)
936948
renamed_table.dbName = "default"
937949
renamed_table.tableName = "new_tabl3e"
938950

939-
catalog._client.__enter__().get_table.side_effect = [hive_table, renamed_table]
951+
catalog._client.__enter__().get_table_req.side_effect = [
952+
GetTableResult(table=hive_table),
953+
GetTableResult(table=renamed_table),
954+
]
940955
catalog._client.__enter__().alter_table_with_environment_context.return_value = None
941956
to_identifier = ("default", "new_tabl3e")
942957
table = catalog.rename_table(from_table.name(), to_identifier)
943958

944959
assert table.name() == to_identifier
945960

946-
calls = [call(dbname="default", tbl_name="new_tabl2e"), call(dbname="default", tbl_name="new_tabl3e")]
947-
catalog._client.__enter__().get_table.assert_has_calls(calls)
961+
calls = [
962+
call(GetTableRequest(dbName="default", tblName="new_tabl2e")),
963+
call(GetTableRequest(dbName="default", tblName="new_tabl3e")),
964+
]
965+
catalog._client.__enter__().get_table_req.assert_has_calls(calls)
948966
catalog._client.__enter__().alter_table_with_environment_context.assert_called_with(
949967
dbname="default",
950968
tbl_name="new_tabl2e",
@@ -1042,13 +1060,13 @@ def test_list_tables(hive_table: HiveTable) -> None:
10421060

10431061
catalog._client = MagicMock()
10441062
catalog._client.__enter__().get_all_tables.return_value = ["table1", "table2", "table3", "table4"]
1045-
catalog._client.__enter__().get_table_objects_by_name.return_value = [tbl1, tbl2, tbl3, tbl4]
1063+
catalog._client.__enter__().get_table_objects_by_name_req.return_value = GetTablesResult(tables=[tbl1, tbl2, tbl3, tbl4])
10461064

10471065
got_tables = catalog.list_tables("database")
10481066
assert got_tables == [("database", "table1"), ("database", "table2")]
10491067
catalog._client.__enter__().get_all_tables.assert_called_with(db_name="database")
1050-
catalog._client.__enter__().get_table_objects_by_name.assert_called_with(
1051-
dbname="database", tbl_names=["table1", "table2", "table3", "table4"]
1068+
catalog._client.__enter__().get_table_objects_by_name_req.assert_called_with(
1069+
GetTablesRequest(dbName="database", tblNames=["table1", "table2", "table3", "table4"])
10521070
)
10531071

10541072

@@ -1078,7 +1096,7 @@ def test_drop_table_from_self_identifier(hive_table: HiveTable) -> None:
10781096
catalog = HiveCatalog(HIVE_CATALOG_NAME, uri=HIVE_METASTORE_FAKE_URL)
10791097

10801098
catalog._client = MagicMock()
1081-
catalog._client.__enter__().get_table.return_value = hive_table
1099+
catalog._client.__enter__().get_table_req.return_value = GetTableResult(table=hive_table)
10821100
table = catalog.load_table(("default", "new_tabl2e"))
10831101

10841102
catalog._client.__enter__().get_all_databases.return_value = ["namespace1", "namespace2"]

0 commit comments

Comments
 (0)