This is an automated email from the ASF dual-hosted git repository.
roryqi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git
The following commit(s) were added to refs/heads/main by this push:
new 799c50c737 [#11954] feat(iceberg): enable creating format-version 3
(V3) tables via the Gravitino API (#11957)
799c50c737 is described below
commit 799c50c737b2f2367fa7481fedb5e20084e6d26a
Author: Nevin Zheng <[email protected]>
AuthorDate: Thu Jul 9 01:18:38 2026 -0700
[#11954] feat(iceberg): enable creating format-version 3 (V3) tables via
the Gravitino API (#11957)
### What changes were proposed in this pull request?
Enable creating Iceberg **format-version 3 (V3)** tables through the
Gravitino relational API — the format version required for Iceberg's V3
types such as `variant` (added in #11932). This makes `format-version` a
first-class, validated table property; everything else follows from
that:
- **Select the version** — Gravitino accepts creating tables at versions
**1–4** (the range the bundled Iceberg 1.11.0 can write); other values
are rejected at the Gravitino layer with a clear message instead of a
low-level Iceberg error.
- **Sensible default** — unset or empty resolves to `2` (`empty → 2`, `2
→ 2`, `3 → 3`), stamped by Gravitino at create time, so existing
behavior is unchanged and v3 is opt-in.
- **Tests** — unit (validation over `1–4` + create-property resolution)
and REST/IRC integration: create a `variant` column via the Gravitino
API at v3 and read it back.
- **Docs** — catalog property table + two OpenAPI create examples
(`IcebergTableCreate` v2, `IcebergVariantTableCreate` v3).
Per-version support: **v1/v2** are fully supported (v2 is the community
default since Iceberg 1.4.0); **v3** is required for V3 types and its
type coverage is being added incrementally (`variant` now); **v4** is
creatable but not yet a finalized Iceberg spec.
### Why are the changes needed?
Format version 3 is a prerequisite for Iceberg's V3 types (e.g.
`variant`; see the [Iceberg table
spec](https://iceberg.apache.org/spec/) — "Version 3"). Before this,
`format-version` was an unvalidated free-form string with no default or
documentation, so creating a v3 table was not a supported, discoverable
operation — attempts that needed v3 failed with confusing downstream
Iceberg errors, and nothing covered the write path to a REST/IRC
backend.
Fixes #11954. Related to #11949 (the umbrella tracking issue for the
whole variant feature).
### Does this PR introduce _any_ user-facing change?
- You can now create Iceberg tables at **format versions 1–4** through
the Gravitino API (default `2`) — e.g. `"format-version": "3"` for V3
types like `variant`.
- `format-version` is now validated: allowed values `1`/`2`/`3`/`4`,
unset/empty defaults to `2`, other values rejected with a clear error.
- New OpenAPI examples for creating Iceberg tables.
### How was this patch tested?
- Unit tests: property validation (default; valid `""`/`1`/`2`/`3`/`4`;
invalid `0`/`5`/`100`/`-1`/`INT_MAX`/`INT_MIN`/non-numeric) and
create-property mapping (`absent/""/2/3`).
- Integration tests (REST/IRC): variant column created via the Gravitino
API at v3 and read back; variant-without-v3 rejected; unset/`2`/empty
resolve to `2`.
- Verified end-to-end in the Gravitino playground against an Iceberg
REST catalog (read back over the `/iceberg` endpoint); confirmed Iceberg
1.11 creates v1–v4 and rejects v5.
- `./gradlew :docs:build` validates the OpenAPI spec.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.8 <[email protected]>
---
.../catalog/lakehouse/iceberg/IcebergTable.java | 11 ++
.../iceberg/IcebergTablePropertiesMetadata.java | 74 +++++++++++++-
.../lakehouse/iceberg/TestIcebergTable.java | 19 ++++
.../TestIcebergTablePropertiesMetadata.java | 87 ++++++++++++++++
.../integration/test/CatalogIcebergBaseIT.java | 112 +++++++++++++++++++++
docs/lakehouse-iceberg-catalog.md | 26 ++---
docs/open-api/tables.yaml | 52 ++++++++++
7 files changed, 366 insertions(+), 15 deletions(-)
diff --git
a/catalogs/catalog-lakehouse-iceberg/src/main/java/org/apache/gravitino/catalog/lakehouse/iceberg/IcebergTable.java
b/catalogs/catalog-lakehouse-iceberg/src/main/java/org/apache/gravitino/catalog/lakehouse/iceberg/IcebergTable.java
index 3f2f54c1b3..68d6cfc0df 100644
---
a/catalogs/catalog-lakehouse-iceberg/src/main/java/org/apache/gravitino/catalog/lakehouse/iceberg/IcebergTable.java
+++
b/catalogs/catalog-lakehouse-iceberg/src/main/java/org/apache/gravitino/catalog/lakehouse/iceberg/IcebergTable.java
@@ -28,6 +28,7 @@ import java.util.Map;
import lombok.Getter;
import lombok.ToString;
import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
import org.apache.gravitino.catalog.lakehouse.iceberg.converter.ConvertUtil;
import
org.apache.gravitino.catalog.lakehouse.iceberg.converter.FromIcebergPartitionSpec;
import
org.apache.gravitino.catalog.lakehouse.iceberg.converter.FromIcebergSortOrder;
@@ -77,6 +78,16 @@ public class IcebergTable extends BaseTable {
private IcebergTable() {}
public static Map<String, String> rebuildCreateProperties(Map<String,
String> createProperties) {
+ // Gravitino owns the default Iceberg table format version: when it is not
explicitly set, stamp
+ // ICEBERG_DEFAULT_FORMAT_VERSION rather than relying on the Iceberg
library's version-dependent
+ // default or letting a blank value fail to parse downstream.
+ String formatVersion =
createProperties.get(IcebergTablePropertiesMetadata.FORMAT_VERSION);
+ if (StringUtils.isBlank(formatVersion)) {
+ createProperties.put(
+ IcebergTablePropertiesMetadata.FORMAT_VERSION,
+
String.valueOf(IcebergTablePropertiesMetadata.ICEBERG_DEFAULT_FORMAT_VERSION));
+ }
+
String provider = createProperties.get(PROP_PROVIDER);
if (ICEBERG_PARQUET_FILE_FORMAT.equalsIgnoreCase(provider)) {
createProperties.put(DEFAULT_FILE_FORMAT, ICEBERG_PARQUET_FILE_FORMAT);
diff --git
a/catalogs/catalog-lakehouse-iceberg/src/main/java/org/apache/gravitino/catalog/lakehouse/iceberg/IcebergTablePropertiesMetadata.java
b/catalogs/catalog-lakehouse-iceberg/src/main/java/org/apache/gravitino/catalog/lakehouse/iceberg/IcebergTablePropertiesMetadata.java
index d336ad15e4..b2eff94881 100644
---
a/catalogs/catalog-lakehouse-iceberg/src/main/java/org/apache/gravitino/catalog/lakehouse/iceberg/IcebergTablePropertiesMetadata.java
+++
b/catalogs/catalog-lakehouse-iceberg/src/main/java/org/apache/gravitino/catalog/lakehouse/iceberg/IcebergTablePropertiesMetadata.java
@@ -21,10 +21,14 @@ package org.apache.gravitino.catalog.lakehouse.iceberg;
import static
org.apache.gravitino.connector.PropertyEntry.stringImmutablePropertyEntry;
import static
org.apache.gravitino.connector.PropertyEntry.stringReservedPropertyEntry;
+import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import java.util.List;
import java.util.Map;
+import java.util.Set;
+import org.apache.commons.lang3.StringUtils;
import org.apache.gravitino.connector.BasePropertiesMetadata;
import org.apache.gravitino.connector.PropertyEntry;
import org.apache.iceberg.TableProperties;
@@ -42,6 +46,23 @@ public class IcebergTablePropertiesMetadata extends
BasePropertiesMetadata {
public static final String FORMAT_VERSION = IcebergConstants.FORMAT_VERSION;
public static final String DISTRIBUTION_MODE =
TableProperties.WRITE_DISTRIBUTION_MODE;
+ /**
+ * The default Iceberg table format version Gravitino applies when {@link
#FORMAT_VERSION} is not
+ * explicitly set. Gravitino owns this default rather than deferring to the
Iceberg library's own
+ * version-dependent default, and stamps it onto the table at creation.
+ */
+ public static final int ICEBERG_DEFAULT_FORMAT_VERSION = 2;
+
+ /**
+ * The Iceberg table format versions Gravitino allows creating: {@code
1}–{@code 4}, the range the
+ * bundled Iceberg version (1.11.0) can write. Gravitino is not more
restrictive than Iceberg for
+ * the create passthrough; each version's feature set is defined by the
Iceberg spec (v1/v2/v3 are
+ * adopted, v3 is required for V3 types such as {@code variant}, and v4 is
under active
+ * development and not yet finalized). An unset (empty) value defaults to
{@link
+ * #ICEBERG_DEFAULT_FORMAT_VERSION}. Extend this set as newer Iceberg writer
versions ship.
+ */
+ public static final Set<Integer> SUPPORTED_FORMAT_VERSIONS =
ImmutableSet.of(1, 2, 3, 4);
+
private static final Map<String, PropertyEntry<?>> PROPERTIES_METADATA;
static {
@@ -65,8 +86,7 @@ public class IcebergTablePropertiesMetadata extends
BasePropertiesMetadata {
stringReservedPropertyEntry(
IDENTIFIER_FIELDS, "The identifier field(s) for defining the
table", false),
stringReservedPropertyEntry(DISTRIBUTION_MODE, "Write distribution
mode", false),
- stringImmutablePropertyEntry(
- FORMAT_VERSION, "The Iceberg table format version, ", false,
null, false, false),
+ formatVersionPropertyEntry(),
stringImmutablePropertyEntry(
PROVIDER,
"Iceberg provider for Iceberg table fileFormat, such as
Parquet, Orc, Avro, or Iceberg",
@@ -81,4 +101,54 @@ public class IcebergTablePropertiesMetadata extends
BasePropertiesMetadata {
protected Map<String, PropertyEntry<?>> specificPropertyEntries() {
return PROPERTIES_METADATA;
}
+
+ /**
+ * Builds the property entry for {@link #FORMAT_VERSION}, an immutable
property that accepts an
+ * unset value or one of {@link #SUPPORTED_FORMAT_VERSIONS} and defaults to
{@link
+ * #ICEBERG_DEFAULT_FORMAT_VERSION}.
+ *
+ * @return the {@code format-version} property entry.
+ */
+ private static PropertyEntry<Integer> formatVersionPropertyEntry() {
+ return new PropertyEntry.Builder<Integer>()
+ .withName(FORMAT_VERSION)
+ .withDescription(
+ "The Iceberg table format version. Gravitino supports creating
tables at versions 1 to "
+ + "4 (the range the bundled Iceberg version can write) and
defaults to 2 when unset. "
+ + "Version 3 is required for V3 types such as variant; version
4 is not yet a "
+ + "finalized Iceberg spec.")
+ .withRequired(false)
+ .withImmutable(true)
+ .withJavaType(Integer.class)
+ .withDefaultValue(ICEBERG_DEFAULT_FORMAT_VERSION)
+ .withDecoder(IcebergTablePropertiesMetadata::decodeFormatVersion)
+ .withEncoder(String::valueOf)
+ .withHidden(false)
+ .withReserved(false)
+ .build();
+ }
+
+ /**
+ * Decodes and validates a user-supplied {@link #FORMAT_VERSION} value. An
unset (null or blank)
+ * value is allowed and resolves to {@link #ICEBERG_DEFAULT_FORMAT_VERSION};
otherwise the value
+ * must be an integer in {@link #SUPPORTED_FORMAT_VERSIONS}.
+ *
+ * @param value the raw property value.
+ * @return the parsed format version, or {@link
#ICEBERG_DEFAULT_FORMAT_VERSION} when the value is
+ * unset.
+ * @throws IllegalArgumentException if the value is neither blank nor a
version in {@link
+ * #SUPPORTED_FORMAT_VERSIONS}.
+ */
+ private static Integer decodeFormatVersion(String value) {
+ if (StringUtils.isBlank(value)) {
+ return ICEBERG_DEFAULT_FORMAT_VERSION;
+ }
+ int version = Integer.parseInt(value.trim());
+ Preconditions.checkArgument(
+ SUPPORTED_FORMAT_VERSIONS.contains(version),
+ "Unsupported Iceberg format-version: %s, supported versions are %s",
+ version,
+ SUPPORTED_FORMAT_VERSIONS);
+ return version;
+ }
}
diff --git
a/catalogs/catalog-lakehouse-iceberg/src/test/java/org/apache/gravitino/catalog/lakehouse/iceberg/TestIcebergTable.java
b/catalogs/catalog-lakehouse-iceberg/src/test/java/org/apache/gravitino/catalog/lakehouse/iceberg/TestIcebergTable.java
index 5b1b0a2874..c36f1b7855 100644
---
a/catalogs/catalog-lakehouse-iceberg/src/test/java/org/apache/gravitino/catalog/lakehouse/iceberg/TestIcebergTable.java
+++
b/catalogs/catalog-lakehouse-iceberg/src/test/java/org/apache/gravitino/catalog/lakehouse/iceberg/TestIcebergTable.java
@@ -705,6 +705,25 @@ public class TestIcebergTable {
Assertions.assertEquals(distributionName,
DistributionMode.RANGE.modeName());
}
+ @Test
+ void testRebuildCreatePropertiesResolvesFormatVersion() {
+ // The format-version mapping Gravitino applies at create time:
+ // absent -> 2, empty -> 2, "2" -> 2, "3" -> 3.
+ Assertions.assertEquals("2", rebuiltFormatVersion(null));
+ Assertions.assertEquals("2", rebuiltFormatVersion(""));
+ Assertions.assertEquals("2", rebuiltFormatVersion("2"));
+ Assertions.assertEquals("3", rebuiltFormatVersion("3"));
+ }
+
+ private static String rebuiltFormatVersion(String input) {
+ Map<String, String> properties = new HashMap<>();
+ if (input != null) {
+ properties.put(IcebergTablePropertiesMetadata.FORMAT_VERSION, input);
+ }
+ return IcebergTable.rebuildCreateProperties(properties)
+ .get(IcebergTablePropertiesMetadata.FORMAT_VERSION);
+ }
+
protected static String genRandomName() {
return UUID.randomUUID().toString().replace("-", "");
}
diff --git
a/catalogs/catalog-lakehouse-iceberg/src/test/java/org/apache/gravitino/catalog/lakehouse/iceberg/TestIcebergTablePropertiesMetadata.java
b/catalogs/catalog-lakehouse-iceberg/src/test/java/org/apache/gravitino/catalog/lakehouse/iceberg/TestIcebergTablePropertiesMetadata.java
new file mode 100644
index 0000000000..08ae281b91
--- /dev/null
+++
b/catalogs/catalog-lakehouse-iceberg/src/test/java/org/apache/gravitino/catalog/lakehouse/iceberg/TestIcebergTablePropertiesMetadata.java
@@ -0,0 +1,87 @@
+/*
+ * 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.gravitino.catalog.lakehouse.iceberg;
+
+import com.google.common.collect.ImmutableMap;
+import java.util.stream.Stream;
+import org.apache.gravitino.catalog.PropertiesMetadataHelpers;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+public class TestIcebergTablePropertiesMetadata {
+
+ private IcebergTablePropertiesMetadata metadata;
+
+ @BeforeEach
+ void setUp() {
+ metadata = new IcebergTablePropertiesMetadata();
+ }
+
+ @Test
+ void testFormatVersionDefaultsToIcebergDefault() {
+ // Gravitino owns the default format version (2).
+ Assertions.assertEquals(
+ IcebergTablePropertiesMetadata.ICEBERG_DEFAULT_FORMAT_VERSION,
+
metadata.getDefaultValue(IcebergTablePropertiesMetadata.FORMAT_VERSION));
+ }
+
+ @Test
+ void testEmptyFormatVersionResolvesToDefault() {
+ // An unset (empty) value resolves to the Gravitino default via the
decoder.
+ Assertions.assertEquals(
+ IcebergTablePropertiesMetadata.ICEBERG_DEFAULT_FORMAT_VERSION,
+ metadata.getOrDefault(
+ ImmutableMap.of(IcebergTablePropertiesMetadata.FORMAT_VERSION, ""),
+ IcebergTablePropertiesMetadata.FORMAT_VERSION));
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"", "1", "2", "3", "4"})
+ void testFormatVersionAcceptsValidValues(String value) {
+ // Empty defers to the default; 1-4 are the versions the bundled Iceberg
can write.
+ Assertions.assertDoesNotThrow(() -> validateFormatVersion(value));
+ }
+
+ @ParameterizedTest
+ @MethodSource("invalidFormatVersions")
+ void testFormatVersionRejectsInvalidValues(String value) {
+ Assertions.assertThrows(IllegalArgumentException.class, () ->
validateFormatVersion(value));
+ }
+
+ private static Stream<String> invalidFormatVersions() {
+ // Just outside the range (0, 5) and clearly out of range / non-numeric.
+ return Stream.of(
+ "0",
+ "5",
+ "100",
+ "-1",
+ String.valueOf(Integer.MAX_VALUE),
+ String.valueOf(Integer.MIN_VALUE),
+ "not-a-number");
+ }
+
+ private void validateFormatVersion(String value) {
+ PropertiesMetadataHelpers.validatePropertyForCreate(
+ metadata,
ImmutableMap.of(IcebergTablePropertiesMetadata.FORMAT_VERSION, value));
+ }
+}
diff --git
a/catalogs/catalog-lakehouse-iceberg/src/test/java/org/apache/gravitino/catalog/lakehouse/iceberg/integration/test/CatalogIcebergBaseIT.java
b/catalogs/catalog-lakehouse-iceberg/src/test/java/org/apache/gravitino/catalog/lakehouse/iceberg/integration/test/CatalogIcebergBaseIT.java
index f7b6770ca4..eb568ae126 100644
---
a/catalogs/catalog-lakehouse-iceberg/src/test/java/org/apache/gravitino/catalog/lakehouse/iceberg/integration/test/CatalogIcebergBaseIT.java
+++
b/catalogs/catalog-lakehouse-iceberg/src/test/java/org/apache/gravitino/catalog/lakehouse/iceberg/integration/test/CatalogIcebergBaseIT.java
@@ -51,6 +51,7 @@ import org.apache.gravitino.auth.AuthConstants;
import org.apache.gravitino.catalog.lakehouse.iceberg.IcebergCatalogBackend;
import
org.apache.gravitino.catalog.lakehouse.iceberg.IcebergSchemaPropertiesMetadata;
import org.apache.gravitino.catalog.lakehouse.iceberg.IcebergTable;
+import
org.apache.gravitino.catalog.lakehouse.iceberg.IcebergTablePropertiesMetadata;
import
org.apache.gravitino.catalog.lakehouse.iceberg.ops.IcebergCatalogWrapperHelper;
import org.apache.gravitino.client.GravitinoMetalake;
import org.apache.gravitino.exceptions.NoSuchSchemaException;
@@ -690,6 +691,117 @@ public abstract class CatalogIcebergBaseIT extends BaseIT
{
"Expected a Hive metastore 'Invalid column type' rejection, but got: "
+ exception);
}
+ @Test
+ void testCreateVariantColumnViaGravitinoApi() {
+ // Write path: create a variant column *through the Gravitino relational
API* and have it
+ // written
+ // to the REST (IRC) backend, then load it back. REST-backend only, for
the same reason as
+ // testV3TypeConversionViaIcebergClient: the CI Hive metastore cannot
store V3 column types.
+ Assumptions.assumeTrue(
+ "rest".equalsIgnoreCase(TYPE),
+ "Variant columns require a backend that does not validate against the
Hive metastore");
+
+ NameIdentifier ident = NameIdentifier.of(schemaName, "t_variant_write");
+ Column[] columns =
+ new Column[] {
+ Column.of("id", Types.IntegerType.get(), "id"),
+ Column.of("payload", Types.VariantType.get(), "variant col")
+ };
+ Map<String, String> properties = Maps.newHashMap();
+ // Variant is a V3 type, so the table must be created at format-version 3.
+ properties.put(IcebergTablePropertiesMetadata.FORMAT_VERSION, "3");
+
+ TableCatalog tableCatalog = catalog.asTableCatalog();
+ Table created =
+ tableCatalog.createTable(
+ ident,
+ columns,
+ "variant write",
+ properties,
+ Transforms.EMPTY_TRANSFORM,
+ Distributions.NONE,
+ new SortOrder[0]);
+ Assertions.assertInstanceOf(Types.VariantType.class,
created.columns()[1].dataType());
+
+ // Load it back through the native metadata API to confirm the round-trip
against the backend.
+ Table loaded = tableCatalog.loadTable(ident);
+ Assertions.assertInstanceOf(Types.VariantType.class,
loaded.columns()[1].dataType());
+ Assertions.assertEquals(
+ "3",
loaded.properties().get(IcebergTablePropertiesMetadata.FORMAT_VERSION));
+ }
+
+ @Test
+ void testCreateVariantColumnRequiresFormatVersion3() {
+ Assumptions.assumeTrue(
+ "rest".equalsIgnoreCase(TYPE),
+ "Variant columns require a backend that does not validate against the
Hive metastore");
+
+ NameIdentifier ident = NameIdentifier.of(schemaName,
"t_variant_default_v2");
+ Column[] columns =
+ new Column[] {
+ Column.of("id", Types.IntegerType.get(), "id"),
+ Column.of("payload", Types.VariantType.get(), "variant col")
+ };
+ TableCatalog tableCatalog = catalog.asTableCatalog();
+ // Without format-version=3 the table defaults to v2, which Iceberg
rejects for variant columns.
+ Exception exception =
+ Assertions.assertThrows(
+ Exception.class,
+ () ->
+ tableCatalog.createTable(
+ ident,
+ columns,
+ "variant default v2",
+ Collections.emptyMap(),
+ Transforms.EMPTY_TRANSFORM,
+ Distributions.NONE,
+ new SortOrder[0]));
+ Assertions.assertTrue(
+ ExceptionUtils.getStackTrace(exception).contains("variant is not
supported until v3"),
+ "Expected a 'variant is not supported until v3' rejection, but got: "
+ exception);
+ }
+
+ @Test
+ void testCreateTableDefaultsToFormatVersion2() {
+ // Unset format-version: Gravitino stamps its default (version 2).
+ assertLoadedFormatVersion("t_fmt_default", Maps.newHashMap(), "2");
+ }
+
+ @Test
+ void testCreateTableWithExplicitFormatVersion2() {
+ Map<String, String> properties = Maps.newHashMap();
+ properties.put(IcebergTablePropertiesMetadata.FORMAT_VERSION, "2");
+ assertLoadedFormatVersion("t_fmt_v2", properties, "2");
+ }
+
+ @Test
+ void testCreateTableWithEmptyFormatVersionDefaultsToV2() {
+ // An explicit empty format-version is treated as unset and defers to the
backend default.
+ Map<String, String> properties = Maps.newHashMap();
+ properties.put(IcebergTablePropertiesMetadata.FORMAT_VERSION, "");
+ assertLoadedFormatVersion("t_fmt_empty", properties, "2");
+ }
+
+ private void assertLoadedFormatVersion(
+ String tableName, Map<String, String> createProperties, String
expectedVersion) {
+ NameIdentifier ident = NameIdentifier.of(schemaName, tableName);
+ Column[] columns = new Column[] {Column.of("id", Types.IntegerType.get(),
"id")};
+ TableCatalog tableCatalog = catalog.asTableCatalog();
+ tableCatalog.createTable(
+ ident,
+ columns,
+ "format-version round-trip",
+ createProperties,
+ Transforms.EMPTY_TRANSFORM,
+ Distributions.NONE,
+ new SortOrder[0]);
+ // Read the version back from the backend; loadTable reflects the
actually-stored format
+ // version.
+ Table loaded = tableCatalog.loadTable(ident);
+ Assertions.assertEquals(
+ expectedVersion,
loaded.properties().get(IcebergTablePropertiesMetadata.FORMAT_VERSION));
+ }
+
private NameIdentifier createV3Table(
String tableName, org.apache.iceberg.types.Type icebergType) {
NameIdentifier ident = NameIdentifier.of(schemaName, tableName);
diff --git a/docs/lakehouse-iceberg-catalog.md
b/docs/lakehouse-iceberg-catalog.md
index 5f11e68744..df9a44aaf3 100644
--- a/docs/lakehouse-iceberg-catalog.md
+++ b/docs/lakehouse-iceberg-catalog.md
@@ -451,19 +451,19 @@ Pass [Iceberg table
properties](https://iceberg.apache.org/docs/1.5.2/configurat
**Immutable**: Fields that cannot be modified once set.
:::
-| Configuration item | Description
| Default value | Required | Reserved |
Immutable | Since Version |
-|---------------------------|---------------------------------------------------------------------------------------|---------------|----------|----------|-----------|---------------|
-| `location` | Iceberg location for table storage.
| (none) | No | No |
Yes | 0.2.0 |
-| `provider` | The storage provider for table storage.
| (none) | No | No |
Yes | 0.2.0 |
-| `format` | The format of table storage.
| (none) | No | No |
Yes | 0.2.0 |
-| `format-version` | The format version of table storage.
| (none) | No | No |
Yes | 0.2.0 |
-| `comment` | The table comment; use the `comment` field in
table meta instead. | (none) | No | Yes |
No | 0.2.0 |
-| `creator` | The table creator.
| (none) | No | Yes | No
| 0.2.0 |
-| `current-snapshot-id` | The snapshot represents the current state of the
table. | (none) | No | Yes | No
| 0.2.0 |
-| `cherry-pick-snapshot-id` | Selecting a specific snapshot in a merge
operation. | (none) | No | Yes
| No | 0.2.0 |
-| `sort-order` | Iceberg table sort order; use `SortOrder` in
table meta instead. | (none) | No | Yes
| No | 0.2.0 |
-| `identifier-fields` | The identifier fields for defining the table.
| (none) | No | Yes | No
| 0.2.0 |
-| `write.distribution-mode` | Defines distribution of write data; use
`distribution` in table meta instead. | (none) | No | Yes
| No | 0.2.0 |
+| Configuration item | Description
| Default value | Required | Reserved |
Immutable | Since Version |
+|---------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------|----------|----------|-----------|---------------|
+| `location` | Iceberg location for table storage.
| (none) | No | No | Yes
| 0.2.0 |
+| `provider` | The storage provider for table storage.
| (none) | No | No | Yes
| 0.2.0 |
+| `format` | The format of table storage.
| (none) | No | No | Yes
| 0.2.0 |
+| `format-version` | The Iceberg table format version. Gravitino
supports creating tables at versions `1`–`4` (the range the bundled Iceberg
version can write) and defaults to `2` when unset. Version `3` is required for
V3 types such as `variant`; version `4` is not yet a finalized Iceberg spec. |
`2` | No | No | Yes | 0.2.0 |
+| `comment` | The table comment; use the `comment` field in
table meta instead.
| (none) | No | Yes | No
| 0.2.0 |
+| `creator` | The table creator.
| (none) | No | Yes | No
| 0.2.0 |
+| `current-snapshot-id` | The snapshot represents the current state of the
table.
| (none) | No | Yes | No
| 0.2.0 |
+| `cherry-pick-snapshot-id` | Selecting a specific snapshot in a merge
operation.
| (none) | No | Yes
| No | 0.2.0 |
+| `sort-order` | Iceberg table sort order; use `SortOrder` in
table meta instead.
| (none) | No | Yes | No
| 0.2.0 |
+| `identifier-fields` | The identifier fields for defining the table.
| (none) | No | Yes | No
| 0.2.0 |
+| `write.distribution-mode` | Defines distribution of write data; use
`distribution` in table meta instead.
| (none) | No | Yes
| No | 0.2.0 |
### Table Indexes
diff --git a/docs/open-api/tables.yaml b/docs/open-api/tables.yaml
index 1e199c5dd1..68b977978f 100644
--- a/docs/open-api/tables.yaml
+++ b/docs/open-api/tables.yaml
@@ -55,6 +55,10 @@ paths:
$ref: "#/components/examples/MysqlTableCreate"
PostgresqlTableCreate:
$ref: "#/components/examples/PostgresqlTableCreate"
+ IcebergTableCreate:
+ $ref: "#/components/examples/IcebergTableCreate"
+ IcebergVariantTableCreate:
+ $ref: "#/components/examples/IcebergVariantTableCreate"
responses:
"200":
$ref: "#/components/responses/TableResponse"
@@ -998,6 +1002,54 @@ components:
"properties": { }
}
+ IcebergTableCreate:
+ value: {
+ "name": "my_iceberg_table",
+ "comment": "This is my Iceberg table",
+ "columns": [
+ {
+ "name": "id",
+ "type": "integer",
+ "comment": "id column comment",
+ "nullable": false
+ },
+ {
+ "name": "name",
+ "type": "string",
+ "comment": "name column comment",
+ "nullable": true
+ }
+ ],
+ "properties": {
+ "format-version": "2"
+ },
+ indexes: [ ]
+ }
+
+ IcebergVariantTableCreate:
+ value: {
+ "name": "my_iceberg_variant_table",
+ "comment": "Iceberg table with a variant column, which requires
format-version 3",
+ "columns": [
+ {
+ "name": "id",
+ "type": "integer",
+ "comment": "id column comment",
+ "nullable": false
+ },
+ {
+ "name": "payload",
+ "type": "variant",
+ "comment": "semi-structured payload column",
+ "nullable": true
+ }
+ ],
+ "properties": {
+ "format-version": "3"
+ },
+ indexes: [ ]
+ }
+
TableResponse:
value: {
"code": 0,