This is an automated email from the ASF dual-hosted git repository.
jerryshao pushed a commit to branch branch-1.3
in repository https://gitbox.apache.org/repos/asf/gravitino.git
The following commit(s) were added to refs/heads/branch-1.3 by this push:
new 10cc489abd [Cherry-pick to branch-1.3] [#11285] fix(iceberg-rest):
default JDBC catalog to strict mode (#11318) (#11323)
10cc489abd is described below
commit 10cc489abddd85c57b6e25e908b1975bbd93187f
Author: github-actions[bot]
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Mon Jun 1 20:40:29 2026 +0800
[Cherry-pick to branch-1.3] [#11285] fix(iceberg-rest): default JDBC
catalog to strict mode (#11318) (#11323)
**Cherry-pick Information:**
- Original commit: 012f72d413bd63c0c94d4a864f81174c25094356
- Target branch: `branch-1.3`
- Status: ✅ Clean cherry-pick (no conflicts)
Co-authored-by: roryqi <[email protected]>
---
.../lakehouse/iceberg/IcebergConstants.java | 2 +
docs/iceberg-rest-service.md | 1 +
.../iceberg/common/utils/IcebergCatalogUtil.java | 6 +++
.../common/utils/TestIcebergCatalogUtil.java | 51 +++++++++++++++++++++
.../integration/test/IcebergRESTServiceIT.java | 52 ++++++++++++++++++++++
5 files changed, 112 insertions(+)
diff --git
a/catalogs/catalog-common/src/main/java/org/apache/gravitino/catalog/lakehouse/iceberg/IcebergConstants.java
b/catalogs/catalog-common/src/main/java/org/apache/gravitino/catalog/lakehouse/iceberg/IcebergConstants.java
index cbfe027188..58606f43f3 100644
---
a/catalogs/catalog-common/src/main/java/org/apache/gravitino/catalog/lakehouse/iceberg/IcebergConstants.java
+++
b/catalogs/catalog-common/src/main/java/org/apache/gravitino/catalog/lakehouse/iceberg/IcebergConstants.java
@@ -38,6 +38,8 @@ public class IcebergConstants {
public static final String GRAVITINO_JDBC_SCHEMA_VERSION =
"jdbc-schema-version";
public static final String ICEBERG_JDBC_SCHEMA_VERSION =
"jdbc.schema-version";
+ public static final String ICEBERG_JDBC_STRICT_MODE = "jdbc.strict-mode";
+
public static final String GRAVITINO_JDBC_DRIVER = "jdbc-driver";
public static final String WAREHOUSE = "warehouse";
public static final String URI = "uri";
diff --git a/docs/iceberg-rest-service.md b/docs/iceberg-rest-service.md
index d050e6d21e..d25832d6cb 100644
--- a/docs/iceberg-rest-service.md
+++ b/docs/iceberg-rest-service.md
@@ -119,6 +119,7 @@ The Gravitino Iceberg REST catalog service uses the memory
catalog backend by de
| `gravitino.iceberg-rest.jdbc-initialize` | Whether to initialize the
meta tables when creating the JDBC catalog.
| `true`
| No | 0.2.0 |
| `gravitino.iceberg-rest.jdbc-driver` | `com.mysql.jdbc.Driver` or
`com.mysql.cj.jdbc.Driver` for MySQL, `org.postgresql.Driver` for PostgreSQL.
| (none)
| Yes | 0.3.0 |
| `gravitino.iceberg-rest.jdbc-schema-version` | The schema version of the
JDBC catalog. Defaults to `V1` to enable view support. Set to `V0` only if you
need to opt out of view support. Once the underlying database is migrated to
V1, this property is no longer required on subsequent restarts. | `V1`
| No | 1.2.0 |
+| `gravitino.iceberg-rest.jdbc.strict-mode` | Whether the JDBC catalog
runs in strict mode. Defaults to `true` so that creating a table or view in a
namespace that does not exist fails with `NoSuchNamespace` (HTTP 404), matching
the Iceberg REST specification. Set to `false` to restore the legacy behavior
of implicitly creating the namespace. | `true` | No |
1.3.0 |
If you have a JDBC Iceberg catalog prior, you must set `catalog-backend-name`
to keep consistent with your Jdbc Iceberg catalog name to operate the prior
namespace and tables.
diff --git
a/iceberg/iceberg-common/src/main/java/org/apache/gravitino/iceberg/common/utils/IcebergCatalogUtil.java
b/iceberg/iceberg-common/src/main/java/org/apache/gravitino/iceberg/common/utils/IcebergCatalogUtil.java
index 8935a84d9b..8af0c6325c 100644
---
a/iceberg/iceberg-common/src/main/java/org/apache/gravitino/iceberg/common/utils/IcebergCatalogUtil.java
+++
b/iceberg/iceberg-common/src/main/java/org/apache/gravitino/iceberg/common/utils/IcebergCatalogUtil.java
@@ -115,6 +115,12 @@ public class IcebergCatalogUtil {
// Default to V1 schema to support view operations; can be overridden by
explicit config.
properties.putIfAbsent(IcebergConstants.ICEBERG_JDBC_SCHEMA_VERSION, "V1");
+ // Default to strict mode so that creating a table or view in a
non-existent namespace fails
+ // with NoSuchNamespaceException (HTTP 404) instead of implicitly creating
the namespace,
+ // matching the Iceberg REST spec and the memory backend behavior. Can be
overridden by
+ // explicit config.
+ properties.putIfAbsent(IcebergConstants.ICEBERG_JDBC_STRICT_MODE, "true");
+
HdfsConfiguration hdfsConfiguration = new HdfsConfiguration();
properties.forEach(hdfsConfiguration::set);
jdbcCatalog.setConf(hdfsConfiguration);
diff --git
a/iceberg/iceberg-common/src/test/java/org/apache/gravitino/iceberg/common/utils/TestIcebergCatalogUtil.java
b/iceberg/iceberg-common/src/test/java/org/apache/gravitino/iceberg/common/utils/TestIcebergCatalogUtil.java
index 6f9cf71194..bf0fbd8e2c 100644
---
a/iceberg/iceberg-common/src/test/java/org/apache/gravitino/iceberg/common/utils/TestIcebergCatalogUtil.java
+++
b/iceberg/iceberg-common/src/test/java/org/apache/gravitino/iceberg/common/utils/TestIcebergCatalogUtil.java
@@ -19,22 +19,31 @@
package org.apache.gravitino.iceberg.common.utils;
+import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import org.apache.gravitino.catalog.lakehouse.iceberg.IcebergCatalogBackend;
import org.apache.gravitino.catalog.lakehouse.iceberg.IcebergConstants;
import org.apache.gravitino.iceberg.common.IcebergConfig;
import org.apache.iceberg.CatalogProperties;
+import org.apache.iceberg.Schema;
import org.apache.iceberg.catalog.Catalog;
+import org.apache.iceberg.catalog.Namespace;
+import org.apache.iceberg.catalog.TableIdentifier;
+import org.apache.iceberg.exceptions.NoSuchNamespaceException;
import org.apache.iceberg.hive.HiveCatalog;
import org.apache.iceberg.inmemory.InMemoryCatalog;
import org.apache.iceberg.jdbc.JdbcCatalog;
import org.apache.iceberg.jdbc.JdbcCatalogWithMetadataLocationSupport;
+import org.apache.iceberg.types.Types;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
public class TestIcebergCatalogUtil {
+ @TempDir private Path warehouse;
+
@Test
void testLoadCatalog() {
Catalog catalog;
@@ -117,6 +126,48 @@ public class TestIcebergCatalogUtil {
"Explicitly configured V0 schema should not be overridden to V1");
}
+ @Test
+ void testJdbcCatalogDefaultStrictModeRejectsImplicitNamespace() {
+ Map<String, String> properties = new HashMap<>();
+ properties.put(
+ CatalogProperties.URI,
"jdbc:sqlite:file:strict_default?mode=memory&cache=shared");
+ properties.put(CatalogProperties.WAREHOUSE_LOCATION, warehouse.toString());
+ properties.put(IcebergConstants.GRAVITINO_JDBC_DRIVER, "org.sqlite.JDBC");
+ properties.put(IcebergConstants.ICEBERG_JDBC_USER, "test");
+ properties.put(IcebergConstants.ICEBERG_JDBC_PASSWORD, "test");
+ // jdbc.strict-mode is intentionally not set; default should be true
+ Catalog catalog =
+ IcebergCatalogUtil.loadCatalogBackend(
+ IcebergCatalogBackend.JDBC, new IcebergConfig(properties));
+
+ Schema schema = new Schema(Types.NestedField.required(1, "id",
Types.IntegerType.get()));
+ TableIdentifier identifier = TableIdentifier.of(Namespace.of("absent_ns"),
"t");
+ // Strict mode must reject creating a table in a namespace that does not
exist.
+ Assertions.assertThrows(
+ NoSuchNamespaceException.class, () -> catalog.createTable(identifier,
schema));
+ }
+
+ @Test
+ void testJdbcCatalogExplicitStrictModeDisabledAllowsImplicitNamespace() {
+ Map<String, String> properties = new HashMap<>();
+ properties.put(
+ CatalogProperties.URI,
"jdbc:sqlite:file:strict_disabled?mode=memory&cache=shared");
+ properties.put(CatalogProperties.WAREHOUSE_LOCATION, warehouse.toString());
+ properties.put(IcebergConstants.GRAVITINO_JDBC_DRIVER, "org.sqlite.JDBC");
+ properties.put(IcebergConstants.ICEBERG_JDBC_USER, "test");
+ properties.put(IcebergConstants.ICEBERG_JDBC_PASSWORD, "test");
+ // Explicitly disable strict mode; loadJdbcCatalog must not override it
back to true.
+ properties.put(IcebergConstants.ICEBERG_JDBC_STRICT_MODE, "false");
+ Catalog catalog =
+ IcebergCatalogUtil.loadCatalogBackend(
+ IcebergCatalogBackend.JDBC, new IcebergConfig(properties));
+
+ Schema schema = new Schema(Types.NestedField.required(1, "id",
Types.IntegerType.get()));
+ TableIdentifier identifier =
TableIdentifier.of(Namespace.of("implicit_ns"), "t");
+ // With strict mode disabled the namespace is created implicitly, so this
must succeed.
+ Assertions.assertNotNull(catalog.createTable(identifier, schema));
+ }
+
@Test
void testValidLoadCustomCatalog() {
Catalog catalog;
diff --git
a/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRESTServiceIT.java
b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRESTServiceIT.java
index 51aca2dc02..9109f9734a 100644
---
a/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRESTServiceIT.java
+++
b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergRESTServiceIT.java
@@ -305,6 +305,58 @@ public abstract class IcebergRESTServiceIT extends
IcebergRESTServiceBaseIT {
() -> sql(String.format("CREATE TABLE %s.create_foo1",
getTestNamespace())));
}
+ @Test
+ void testCreateTableInNonExistentNamespace() {
+ // The child namespace is intentionally never created.
+ String namespaceName = getTestNamespace("absent_create_ns");
+ Throwable thrown =
+ Assertions.assertThrows(
+ Throwable.class,
+ () ->
+ sql(
+ String.format(
+ "CREATE TABLE %s.create_in_absent(id bigint) using
iceberg",
+ namespaceName)));
+ // Creating a table in a namespace that does not exist must surface a
NoSuchNamespace error
+ // (HTTP 404) instead of implicitly creating the namespace. This holds for
the memory and hive
+ // backends, and for the JDBC backend now that strict mode is enabled by
default.
+ Assertions.assertTrue(
+ isNoSuchNamespace(thrown), () -> "Expected a NoSuchNamespace error but
got: " + thrown);
+ }
+
+ @Test
+ @EnabledIf("isSupportsViewCatalog")
+ void testCreateViewInNonExistentNamespace() {
+ String sourceTable = getTestNamespace() + ".view_source_for_absent_ns";
+ sql(String.format("CREATE TABLE %s (id bigint) using iceberg",
sourceTable));
+ // The child namespace is intentionally never created.
+ String namespaceName = getTestNamespace("absent_view_ns");
+ Throwable thrown =
+ Assertions.assertThrows(
+ Throwable.class,
+ () ->
+ sql(
+ String.format(
+ "CREATE VIEW %s.view_in_absent AS SELECT * FROM %s",
+ namespaceName, sourceTable)));
+ // Strict mode must also reject creating a view in a namespace that does
not exist, instead of
+ // implicitly creating the namespace.
+ Assertions.assertTrue(
+ isNoSuchNamespace(thrown), () -> "Expected a NoSuchNamespace error but
got: " + thrown);
+ }
+
+ // Walks the cause chain because the missing namespace may surface as either
Spark's or Iceberg's
+ // NoSuchNamespaceException, and Spark may wrap it.
+ private static boolean isNoSuchNamespace(Throwable thrown) {
+ for (Throwable t = thrown; t != null; t = t.getCause()) {
+ if (t instanceof NoSuchNamespaceException
+ || t instanceof
org.apache.iceberg.exceptions.NoSuchNamespaceException) {
+ return true;
+ }
+ }
+ return false;
+ }
+
@Test
void testDropTable() {
sql(