From 8ad2d283c5e5f723fb1fb3808027c9714034f653 Mon Sep 17 00:00:00 2001 From: Karuppayya Rajendran Date: Fri, 20 Mar 2026 12:16:51 -0700 Subject: [PATCH 1/2] Fix Iceberg reflection for current() on TableOperations hierarchy Use findMethodInHierarchy instead of getDeclaredMethod so getTableMetadata and format-version reflection work when current() is declared on a superclass (e.g. BaseMetastoreTableOperations for Hive/Glue-style ops). Add IcebergReflectionSuite with a BaseMetastoreTableOperations stub and BaseTable. Made-with: Cursor --- .../comet/iceberg/IcebergReflection.scala | 26 +++++--- .../iceberg/IcebergReflectionSuite.scala | 66 +++++++++++++++++++ 2 files changed, 84 insertions(+), 8 deletions(-) create mode 100644 spark/src/test/scala/org/apache/comet/iceberg/IcebergReflectionSuite.scala diff --git a/spark/src/main/scala/org/apache/comet/iceberg/IcebergReflection.scala b/spark/src/main/scala/org/apache/comet/iceberg/IcebergReflection.scala index 62710c28dc..4ff3fc761b 100644 --- a/spark/src/main/scala/org/apache/comet/iceberg/IcebergReflection.scala +++ b/spark/src/main/scala/org/apache/comet/iceberg/IcebergReflection.scala @@ -228,11 +228,18 @@ object IcebergReflection extends Logging { val opsMethod = table.getClass.getDeclaredMethod("operations") opsMethod.setAccessible(true) val ops = opsMethod.invoke(table) - val currentMethod = ops.getClass.getDeclaredMethod("current") - currentMethod.setAccessible(true) - val metadata = currentMethod.invoke(ops) - val formatVersionMethod = metadata.getClass.getMethod("formatVersion") - Some(formatVersionMethod.invoke(metadata).asInstanceOf[Int]) + findMethodInHierarchy(ops.getClass, "current") + .flatMap { currentMethod => + val metadata = currentMethod.invoke(ops) + val formatVersionMethod = metadata.getClass.getMethod("formatVersion") + Some(formatVersionMethod.invoke(metadata).asInstanceOf[Int]) + } + .orElse { + logError( + s"Iceberg reflection failure: Failed to get format version: " + + "current() method not found in operations class hierarchy") + None + } } catch { case e: Exception => logError(s"Iceberg reflection failure: Failed to get format version: ${e.getMessage}") @@ -327,9 +334,12 @@ object IcebergReflection extends Logging { operationsMethod.setAccessible(true) val operations = operationsMethod.invoke(table) - val currentMethod = operations.getClass.getDeclaredMethod("current") - currentMethod.setAccessible(true) - Some(currentMethod.invoke(operations)) + findMethodInHierarchy(operations.getClass, "current").map(_.invoke(operations)).orElse { + logError( + s"Iceberg reflection failure: Failed to get table metadata: " + + "current() method not found in operations class hierarchy") + None + } } catch { case e: Exception => logError(s"Iceberg reflection failure: Failed to get table metadata: ${e.getMessage}") diff --git a/spark/src/test/scala/org/apache/comet/iceberg/IcebergReflectionSuite.scala b/spark/src/test/scala/org/apache/comet/iceberg/IcebergReflectionSuite.scala new file mode 100644 index 0000000000..2ec5f5ac73 --- /dev/null +++ b/spark/src/test/scala/org/apache/comet/iceberg/IcebergReflectionSuite.scala @@ -0,0 +1,66 @@ +/* + * 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.comet.iceberg + +import java.util.Collections + +import org.scalatest.funsuite.AnyFunSuite + +import org.apache.iceberg.BaseMetastoreTableOperations +import org.apache.iceberg.BaseTable +import org.apache.iceberg.Schema +import org.apache.iceberg.TableMetadata +import org.apache.iceberg.io.FileIO +import org.apache.iceberg.types.Types + +class IcebergReflectionSuite extends AnyFunSuite { + + /** Mimics HiveTableOperations/GlueTableOperations which inherit current(). */ + class StubTableOperations extends BaseMetastoreTableOperations { + override protected def tableName(): String = "test" + override def refresh(): TableMetadata = null + override def io(): FileIO = null + } + + test("getTableMetadata succeeds when operations class inherits current()") { + val ops = new StubTableOperations() + val schema = new Schema(Types.NestedField.required(1, "id", Types.IntegerType.get())) + val expectedMetadata = TableMetadata.newTableMetadata( + schema, + org.apache.iceberg.PartitionSpec.unpartitioned(), + "file:///tmp/test-table", + Collections.emptyMap[String, String]()) + val metadataField = classOf[BaseMetastoreTableOperations] + .getDeclaredField("currentMetadata") + metadataField.setAccessible(true) + metadataField.set(ops, expectedMetadata) + // current() checks shouldRefresh (default true) and calls refresh() instead of + // returning currentMetadata. Set to false so current() returns our stubbed metadata. + val refreshField = classOf[BaseMetastoreTableOperations] + .getDeclaredField("shouldRefresh") + refreshField.setAccessible(true) + refreshField.set(ops, false) + + val table = new BaseTable(ops, "test-table") + val metadata = IcebergReflection.getTableMetadata(table) + assert(metadata.isDefined) + assert(metadata.get.isInstanceOf[TableMetadata]) + } +} From 433832ef6abebfba26bc2b50a84fb7281dda6acb Mon Sep 17 00:00:00 2001 From: Karuppayya Rajendran Date: Thu, 2 Apr 2026 21:41:50 -0700 Subject: [PATCH 2/2] Fix checkstyle errors --- .../scala/org/apache/comet/iceberg/IcebergReflection.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spark/src/main/scala/org/apache/comet/iceberg/IcebergReflection.scala b/spark/src/main/scala/org/apache/comet/iceberg/IcebergReflection.scala index 4ff3fc761b..7c52f320cf 100644 --- a/spark/src/main/scala/org/apache/comet/iceberg/IcebergReflection.scala +++ b/spark/src/main/scala/org/apache/comet/iceberg/IcebergReflection.scala @@ -236,7 +236,7 @@ object IcebergReflection extends Logging { } .orElse { logError( - s"Iceberg reflection failure: Failed to get format version: " + + "Iceberg reflection failure: Failed to get format version: " + "current() method not found in operations class hierarchy") None } @@ -336,7 +336,7 @@ object IcebergReflection extends Logging { findMethodInHierarchy(operations.getClass, "current").map(_.invoke(operations)).orElse { logError( - s"Iceberg reflection failure: Failed to get table metadata: " + + "Iceberg reflection failure: Failed to get table metadata: " + "current() method not found in operations class hierarchy") None }