You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iceberg.apache.org by ja...@apache.org on 2022/06/15 20:41:14 UTC
[iceberg] branch master updated: AWS: add skip name validation config for glue namespace and table (#5041)
This is an automated email from the ASF dual-hosted git repository.
jackye pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iceberg.git
The following commit(s) were added to refs/heads/master by this push:
new 5653a0d9c7 AWS: add skip name validation config for glue namespace and table (#5041)
5653a0d9c7 is described below
commit 5653a0d9c704b22b3b193ae6338af6261833f6e2
Author: Xingfan Xia <xi...@amazon.com>
AuthorDate: Wed Jun 15 13:41:08 2022 -0700
AWS: add skip name validation config for glue namespace and table (#5041)
---
.../java/org/apache/iceberg/aws/AwsProperties.java | 23 +++++++++++-
.../org/apache/iceberg/aws/glue/GlueCatalog.java | 42 ++++++++++++----------
.../iceberg/aws/glue/GlueTableOperations.java | 6 ++--
.../iceberg/aws/glue/IcebergToGlueConverter.java | 41 ++++++++++++++-------
.../aws/glue/TestIcebergToGlueConverter.java | 36 +++++++++++++++----
docs/integrations/aws.md | 9 +++++
6 files changed, 117 insertions(+), 40 deletions(-)
diff --git a/aws/src/main/java/org/apache/iceberg/aws/AwsProperties.java b/aws/src/main/java/org/apache/iceberg/aws/AwsProperties.java
index 849e25d71d..20820ffb66 100644
--- a/aws/src/main/java/org/apache/iceberg/aws/AwsProperties.java
+++ b/aws/src/main/java/org/apache/iceberg/aws/AwsProperties.java
@@ -103,6 +103,16 @@ public class AwsProperties implements Serializable {
public static final String GLUE_CATALOG_SKIP_ARCHIVE = "glue.skip-archive";
public static final boolean GLUE_CATALOG_SKIP_ARCHIVE_DEFAULT = false;
+ /**
+ * If Glue should skip name validations
+ * It is recommended to stick to Glue best practice in
+ * https://docs.aws.amazon.com/athena/latest/ug/glue-best-practices.html to make sure operations are Hive compatible.
+ * This is only added for users that have existing conventions using non-standard characters. When database name
+ * and table name validation are skipped, there is no guarantee that downstream systems would all support the names.
+ */
+ public static final String GLUE_CATALOG_SKIP_NAME_VALIDATION = "glue.skip-name-validation";
+ public static final boolean GLUE_CATALOG_SKIP_NAME_VALIDATION_DEFAULT = false;
+
/**
* If set, GlueCatalog will use Lake Formation for access control.
* For more credential vending details, see: https://docs.aws.amazon.com/lake-formation/latest/dg/api-overview.html.
@@ -383,6 +393,7 @@ public class AwsProperties implements Serializable {
private String glueCatalogId;
private boolean glueCatalogSkipArchive;
+ private boolean glueCatalogSkipNameValidation;
private boolean glueLakeFormationEnabled;
private String dynamoDbTableName;
@@ -407,6 +418,7 @@ public class AwsProperties implements Serializable {
this.glueCatalogId = null;
this.glueCatalogSkipArchive = GLUE_CATALOG_SKIP_ARCHIVE_DEFAULT;
+ this.glueCatalogSkipNameValidation = GLUE_CATALOG_SKIP_NAME_VALIDATION_DEFAULT;
this.glueLakeFormationEnabled = GLUE_LAKEFORMATION_ENABLED_DEFAULT;
this.dynamoDbTableName = DYNAMODB_TABLE_NAME_DEFAULT;
@@ -425,6 +437,8 @@ public class AwsProperties implements Serializable {
this.glueCatalogId = properties.get(GLUE_CATALOG_ID);
this.glueCatalogSkipArchive = PropertyUtil.propertyAsBoolean(properties,
AwsProperties.GLUE_CATALOG_SKIP_ARCHIVE, AwsProperties.GLUE_CATALOG_SKIP_ARCHIVE_DEFAULT);
+ this.glueCatalogSkipNameValidation = PropertyUtil.propertyAsBoolean(properties,
+ AwsProperties.GLUE_CATALOG_SKIP_NAME_VALIDATION, AwsProperties.GLUE_CATALOG_SKIP_NAME_VALIDATION_DEFAULT);
this.glueLakeFormationEnabled = PropertyUtil.propertyAsBoolean(properties,
GLUE_LAKEFORMATION_ENABLED,
GLUE_LAKEFORMATION_ENABLED_DEFAULT);
@@ -520,11 +534,18 @@ public class AwsProperties implements Serializable {
public boolean glueCatalogSkipArchive() {
return glueCatalogSkipArchive;
}
-
public void setGlueCatalogSkipArchive(boolean skipArchive) {
this.glueCatalogSkipArchive = skipArchive;
}
+ public boolean glueCatalogSkipNameValidation() {
+ return glueCatalogSkipNameValidation;
+ }
+
+ public void setGlueCatalogSkipNameValidation(boolean glueCatalogSkipNameValidation) {
+ this.glueCatalogSkipNameValidation = glueCatalogSkipNameValidation;
+ }
+
public boolean glueLakeFormationEnabled() {
return glueLakeFormationEnabled;
}
diff --git a/aws/src/main/java/org/apache/iceberg/aws/glue/GlueCatalog.java b/aws/src/main/java/org/apache/iceberg/aws/glue/GlueCatalog.java
index 2c6c46528b..88c94c12d2 100644
--- a/aws/src/main/java/org/apache/iceberg/aws/glue/GlueCatalog.java
+++ b/aws/src/main/java/org/apache/iceberg/aws/glue/GlueCatalog.java
@@ -201,9 +201,9 @@ public class GlueCatalog extends BaseMetastoreCatalog
Map<String, String> tableSpecificCatalogProperties = ImmutableMap.<String, String>builder()
.putAll(catalogProperties)
.put(AwsProperties.LAKE_FORMATION_DB_NAME,
- IcebergToGlueConverter.getDatabaseName(tableIdentifier))
+ IcebergToGlueConverter.getDatabaseName(tableIdentifier, awsProperties.glueCatalogSkipNameValidation()))
.put(AwsProperties.LAKE_FORMATION_TABLE_NAME,
- IcebergToGlueConverter.getTableName(tableIdentifier))
+ IcebergToGlueConverter.getTableName(tableIdentifier, awsProperties.glueCatalogSkipNameValidation()))
.build();
// FileIO initialization depends on tableSpecificCatalogProperties, so a new FileIO is initialized each time
return new GlueTableOperations(glue, lockManager, catalogName, awsProperties,
@@ -224,7 +224,7 @@ public class GlueCatalog extends BaseMetastoreCatalog
protected String defaultWarehouseLocation(TableIdentifier tableIdentifier) {
// check if value is set in database
GetDatabaseResponse response = glue.getDatabase(GetDatabaseRequest.builder()
- .name(IcebergToGlueConverter.getDatabaseName(tableIdentifier))
+ .name(IcebergToGlueConverter.getDatabaseName(tableIdentifier, awsProperties.glueCatalogSkipNameValidation()))
.build());
String dbLocationUri = response.database().locationUri();
if (dbLocationUri != null) {
@@ -234,7 +234,7 @@ public class GlueCatalog extends BaseMetastoreCatalog
return String.format(
"%s/%s.db/%s",
warehousePath,
- IcebergToGlueConverter.getDatabaseName(tableIdentifier),
+ IcebergToGlueConverter.getDatabaseName(tableIdentifier, awsProperties.glueCatalogSkipNameValidation()),
tableIdentifier.name());
}
@@ -247,7 +247,7 @@ public class GlueCatalog extends BaseMetastoreCatalog
do {
GetTablesResponse response = glue.getTables(GetTablesRequest.builder()
.catalogId(awsProperties.glueCatalogId())
- .databaseName(IcebergToGlueConverter.toDatabaseName(namespace))
+ .databaseName(IcebergToGlueConverter.toDatabaseName(namespace, awsProperties.glueCatalogSkipNameValidation()))
.nextToken(nextToken)
.build());
nextToken = response.nextToken();
@@ -276,7 +276,8 @@ public class GlueCatalog extends BaseMetastoreCatalog
TableMetadata lastMetadata = ops.current();
glue.deleteTable(DeleteTableRequest.builder()
.catalogId(awsProperties.glueCatalogId())
- .databaseName(IcebergToGlueConverter.getDatabaseName(identifier))
+ .databaseName(IcebergToGlueConverter.getDatabaseName(
+ identifier, awsProperties.glueCatalogSkipNameValidation()))
.name(identifier.name())
.build());
LOG.info("Successfully dropped table {} from Glue", identifier);
@@ -309,10 +310,11 @@ public class GlueCatalog extends BaseMetastoreCatalog
}
// keep metadata
Table fromTable = null;
- String fromTableDbName = IcebergToGlueConverter.getDatabaseName(from);
- String fromTableName = IcebergToGlueConverter.getTableName(from);
- String toTableDbName = IcebergToGlueConverter.getDatabaseName(to);
- String toTableName = IcebergToGlueConverter.getTableName(to);
+ String fromTableDbName = IcebergToGlueConverter.getDatabaseName(
+ from, awsProperties.glueCatalogSkipNameValidation());
+ String fromTableName = IcebergToGlueConverter.getTableName(from, awsProperties.glueCatalogSkipNameValidation());
+ String toTableDbName = IcebergToGlueConverter.getDatabaseName(to, awsProperties.glueCatalogSkipNameValidation());
+ String toTableName = IcebergToGlueConverter.getTableName(to, awsProperties.glueCatalogSkipNameValidation());
try {
GetTableResponse response = glue.getTable(GetTableRequest.builder()
.catalogId(awsProperties.glueCatalogId())
@@ -359,7 +361,8 @@ public class GlueCatalog extends BaseMetastoreCatalog
try {
glue.createDatabase(CreateDatabaseRequest.builder()
.catalogId(awsProperties.glueCatalogId())
- .databaseInput(IcebergToGlueConverter.toDatabaseInput(namespace, metadata))
+ .databaseInput(IcebergToGlueConverter.toDatabaseInput(
+ namespace, metadata, awsProperties.glueCatalogSkipNameValidation()))
.build());
LOG.info("Created namespace: {}", namespace);
} catch (software.amazon.awssdk.services.glue.model.AlreadyExistsException e) {
@@ -400,7 +403,8 @@ public class GlueCatalog extends BaseMetastoreCatalog
@Override
public Map<String, String> loadNamespaceMetadata(Namespace namespace) throws NoSuchNamespaceException {
- String databaseName = IcebergToGlueConverter.toDatabaseName(namespace);
+ String databaseName = IcebergToGlueConverter.toDatabaseName(
+ namespace, awsProperties.glueCatalogSkipNameValidation());
try {
Database database = glue.getDatabase(GetDatabaseRequest.builder()
.catalogId(awsProperties.glueCatalogId())
@@ -434,7 +438,7 @@ public class GlueCatalog extends BaseMetastoreCatalog
GetTablesResponse response = glue.getTables(GetTablesRequest.builder()
.catalogId(awsProperties.glueCatalogId())
- .databaseName(IcebergToGlueConverter.toDatabaseName(namespace))
+ .databaseName(IcebergToGlueConverter.toDatabaseName(namespace, awsProperties.glueCatalogSkipNameValidation()))
.build());
if (response.hasTableList() && !response.tableList().isEmpty()) {
@@ -450,7 +454,7 @@ public class GlueCatalog extends BaseMetastoreCatalog
glue.deleteDatabase(DeleteDatabaseRequest.builder()
.catalogId(awsProperties.glueCatalogId())
- .name(IcebergToGlueConverter.toDatabaseName(namespace))
+ .name(IcebergToGlueConverter.toDatabaseName(namespace, awsProperties.glueCatalogSkipNameValidation()))
.build());
LOG.info("Dropped namespace: {}", namespace);
// Always successful, otherwise exception is thrown
@@ -464,8 +468,9 @@ public class GlueCatalog extends BaseMetastoreCatalog
newProperties.putAll(properties);
glue.updateDatabase(UpdateDatabaseRequest.builder()
.catalogId(awsProperties.glueCatalogId())
- .name(IcebergToGlueConverter.toDatabaseName(namespace))
- .databaseInput(IcebergToGlueConverter.toDatabaseInput(namespace, newProperties))
+ .name(IcebergToGlueConverter.toDatabaseName(namespace, awsProperties.glueCatalogSkipNameValidation()))
+ .databaseInput(IcebergToGlueConverter.toDatabaseInput(
+ namespace, newProperties, awsProperties.glueCatalogSkipNameValidation()))
.build());
LOG.debug("Successfully set properties {} for {}", properties.keySet(), namespace);
// Always successful, otherwise exception is thrown
@@ -481,8 +486,9 @@ public class GlueCatalog extends BaseMetastoreCatalog
glue.updateDatabase(UpdateDatabaseRequest.builder()
.catalogId(awsProperties.glueCatalogId())
- .name(IcebergToGlueConverter.toDatabaseName(namespace))
- .databaseInput(IcebergToGlueConverter.toDatabaseInput(namespace, metadata))
+ .name(IcebergToGlueConverter.toDatabaseName(namespace, awsProperties.glueCatalogSkipNameValidation()))
+ .databaseInput(IcebergToGlueConverter.toDatabaseInput(
+ namespace, metadata, awsProperties.glueCatalogSkipNameValidation()))
.build());
LOG.debug("Successfully removed properties {} from {}", properties, namespace);
// Always successful, otherwise exception is thrown
diff --git a/aws/src/main/java/org/apache/iceberg/aws/glue/GlueTableOperations.java b/aws/src/main/java/org/apache/iceberg/aws/glue/GlueTableOperations.java
index baf24d174d..a9132bbdb6 100644
--- a/aws/src/main/java/org/apache/iceberg/aws/glue/GlueTableOperations.java
+++ b/aws/src/main/java/org/apache/iceberg/aws/glue/GlueTableOperations.java
@@ -77,8 +77,10 @@ class GlueTableOperations extends BaseMetastoreTableOperations {
FileIO fileIO, TableIdentifier tableIdentifier) {
this.glue = glue;
this.awsProperties = awsProperties;
- this.databaseName = IcebergToGlueConverter.getDatabaseName(tableIdentifier);
- this.tableName = IcebergToGlueConverter.getTableName(tableIdentifier);
+ this.databaseName = IcebergToGlueConverter.getDatabaseName(
+ tableIdentifier, awsProperties.glueCatalogSkipNameValidation());
+ this.tableName = IcebergToGlueConverter.getTableName(
+ tableIdentifier, awsProperties.glueCatalogSkipNameValidation());
this.fullTableName = String.format("%s.%s.%s", catalogName, databaseName, tableName);
this.commitLockEntityId = String.format("%s.%s", databaseName, tableName);
this.fileIO = fileIO;
diff --git a/aws/src/main/java/org/apache/iceberg/aws/glue/IcebergToGlueConverter.java b/aws/src/main/java/org/apache/iceberg/aws/glue/IcebergToGlueConverter.java
index ba71c191ca..765ca501d6 100644
--- a/aws/src/main/java/org/apache/iceberg/aws/glue/IcebergToGlueConverter.java
+++ b/aws/src/main/java/org/apache/iceberg/aws/glue/IcebergToGlueConverter.java
@@ -105,31 +105,41 @@ class IcebergToGlueConverter {
/**
* Validate and convert Iceberg namespace to Glue database name
- * @param namespace Iceberg namespace
+ *
+ * @param namespace Iceberg namespace
+ * @param skipNameValidation should skip name validation
* @return database name
*/
- static String toDatabaseName(Namespace namespace) {
- validateNamespace(namespace);
+ static String toDatabaseName(Namespace namespace, boolean skipNameValidation) {
+ if (!skipNameValidation) {
+ validateNamespace(namespace);
+ }
+
return namespace.level(0);
}
/**
* Validate and get Glue database name from Iceberg TableIdentifier
- * @param tableIdentifier Iceberg table identifier
+ *
+ * @param tableIdentifier Iceberg table identifier
+ * @param skipNameValidation should skip name validation
* @return database name
*/
- static String getDatabaseName(TableIdentifier tableIdentifier) {
- return toDatabaseName(tableIdentifier.namespace());
+ static String getDatabaseName(TableIdentifier tableIdentifier, boolean skipNameValidation) {
+ return toDatabaseName(tableIdentifier.namespace(), skipNameValidation);
}
/**
* Validate and convert Iceberg name to Glue DatabaseInput
- * @param namespace Iceberg namespace
- * @param metadata metadata map
+ *
+ * @param namespace Iceberg namespace
+ * @param metadata metadata map
+ * @param skipNameValidation should skip name validation
* @return Glue DatabaseInput
*/
- static DatabaseInput toDatabaseInput(Namespace namespace, Map<String, String> metadata) {
- DatabaseInput.Builder builder = DatabaseInput.builder().name(toDatabaseName(namespace));
+ static DatabaseInput toDatabaseInput(Namespace namespace, Map<String, String> metadata, boolean skipNameValidation) {
+ DatabaseInput.Builder builder = DatabaseInput.builder().name(toDatabaseName(namespace,
+ skipNameValidation));
Map<String, String> parameters = Maps.newHashMap();
metadata.forEach((k, v) -> {
if (GLUE_DB_DESCRIPTION_KEY.equals(k)) {
@@ -167,11 +177,16 @@ class IcebergToGlueConverter {
/**
* Validate and get Glue table name from Iceberg TableIdentifier
- * @param tableIdentifier table identifier
+ *
+ * @param tableIdentifier table identifier
+ * @param skipNameValidation should skip name validation
* @return table name
*/
- static String getTableName(TableIdentifier tableIdentifier) {
- validateTableName(tableIdentifier.name());
+ static String getTableName(TableIdentifier tableIdentifier, boolean skipNameValidation) {
+ if (!skipNameValidation) {
+ validateTableName(tableIdentifier.name());
+ }
+
return tableIdentifier.name();
}
diff --git a/aws/src/test/java/org/apache/iceberg/aws/glue/TestIcebergToGlueConverter.java b/aws/src/test/java/org/apache/iceberg/aws/glue/TestIcebergToGlueConverter.java
index ec67af4597..ac9c9950cd 100644
--- a/aws/src/test/java/org/apache/iceberg/aws/glue/TestIcebergToGlueConverter.java
+++ b/aws/src/test/java/org/apache/iceberg/aws/glue/TestIcebergToGlueConverter.java
@@ -27,6 +27,7 @@ import org.apache.iceberg.Schema;
import org.apache.iceberg.TableMetadata;
import org.apache.iceberg.TableProperties;
import org.apache.iceberg.catalog.Namespace;
+import org.apache.iceberg.catalog.TableIdentifier;
import org.apache.iceberg.exceptions.ValidationException;
import org.apache.iceberg.relocated.com.google.common.collect.ImmutableList;
import org.apache.iceberg.relocated.com.google.common.collect.ImmutableMap;
@@ -49,7 +50,7 @@ public class TestIcebergToGlueConverter {
@Test
public void testToDatabaseName() {
- Assert.assertEquals("db", IcebergToGlueConverter.toDatabaseName(Namespace.of("db")));
+ Assert.assertEquals("db", IcebergToGlueConverter.toDatabaseName(Namespace.of("db"), false));
}
@Test
@@ -64,7 +65,30 @@ public class TestIcebergToGlueConverter {
AssertHelpers.assertThrows("bad namespace name",
ValidationException.class,
"Cannot convert namespace",
- () -> IcebergToGlueConverter.toDatabaseName(name)
+ () -> IcebergToGlueConverter.toDatabaseName(name, false)
+ );
+ }
+ }
+
+ @Test
+ public void testSkipNamespaceValidation() {
+ List<Namespace> acceptableNames = Lists.newArrayList(
+ Namespace.of("db-1"),
+ Namespace.of("db-1-1-1"));
+ for (Namespace name : acceptableNames) {
+ Assert.assertEquals(name.toString(), IcebergToGlueConverter.toDatabaseName(name, true)
+ );
+ }
+ }
+
+ @Test
+ public void testSkipTableNameValidation() {
+ List<TableIdentifier> acceptableIdentifiers = Lists.newArrayList(
+ TableIdentifier.parse("db.a-1"),
+ TableIdentifier.parse("db.a-1-1"),
+ TableIdentifier.parse("db.a#1"));
+ for (TableIdentifier identifier : acceptableIdentifiers) {
+ Assert.assertEquals(identifier.name(), IcebergToGlueConverter.getTableName(identifier, true)
);
}
}
@@ -75,7 +99,7 @@ public class TestIcebergToGlueConverter {
IcebergToGlueConverter.GLUE_DB_DESCRIPTION_KEY, "description",
IcebergToGlueConverter.GLUE_DB_LOCATION_KEY, "s3://location",
"key", "val");
- DatabaseInput databaseInput = IcebergToGlueConverter.toDatabaseInput(Namespace.of("ns"), properties);
+ DatabaseInput databaseInput = IcebergToGlueConverter.toDatabaseInput(Namespace.of("ns"), properties, false);
Assert.assertEquals("Location should be set", "s3://location", databaseInput.locationUri());
Assert.assertEquals("Description should be set", "description", databaseInput.description());
Assert.assertEquals("Parameters should be set", ImmutableMap.of("key", "val"), databaseInput.parameters());
@@ -89,7 +113,7 @@ public class TestIcebergToGlueConverter {
.parameters(ImmutableMap.of())
.build();
Namespace namespace = Namespace.of("db");
- Assert.assertEquals(input, IcebergToGlueConverter.toDatabaseInput(namespace, ImmutableMap.of()));
+ Assert.assertEquals(input, IcebergToGlueConverter.toDatabaseInput(namespace, ImmutableMap.of(), false));
}
@Test
@@ -97,7 +121,7 @@ public class TestIcebergToGlueConverter {
Map<String, String> properties = ImmutableMap.of(
IcebergToGlueConverter.GLUE_DB_DESCRIPTION_KEY, "description",
"key", "val");
- DatabaseInput databaseInput = IcebergToGlueConverter.toDatabaseInput(Namespace.of("ns"), properties);
+ DatabaseInput databaseInput = IcebergToGlueConverter.toDatabaseInput(Namespace.of("ns"), properties, false);
Assert.assertNull("Location should not be set", databaseInput.locationUri());
Assert.assertEquals("Description should be set", "description", databaseInput.description());
Assert.assertEquals("Parameters should be set", ImmutableMap.of("key", "val"), databaseInput.parameters());
@@ -109,7 +133,7 @@ public class TestIcebergToGlueConverter {
Map<String, String> properties = ImmutableMap.of(
IcebergToGlueConverter.GLUE_DB_LOCATION_KEY, "s3://location",
"key", "val");
- DatabaseInput databaseInput = IcebergToGlueConverter.toDatabaseInput(Namespace.of("ns"), properties);
+ DatabaseInput databaseInput = IcebergToGlueConverter.toDatabaseInput(Namespace.of("ns"), properties, false);
Assert.assertEquals("Location should be set", "s3://location", databaseInput.locationUri());
Assert.assertNull("Description should not be set", databaseInput.description());
Assert.assertEquals("Parameters should be set", ImmutableMap.of("key", "val"), databaseInput.parameters());
diff --git a/docs/integrations/aws.md b/docs/integrations/aws.md
index 933905fb59..a159f51abf 100644
--- a/docs/integrations/aws.md
+++ b/docs/integrations/aws.md
@@ -166,6 +166,7 @@ just like what is shown in the [enabling AWS integration](#enabling-aws-integrat
More details about loading the catalog can be found in individual engine pages, such as [Spark](../spark-configuration/#loading-a-custom-catalog) and [Flink](../flink/#creating-catalogs-and-using-catalogs).
#### Glue Catalog ID
+
There is a unique Glue metastore in each AWS account and each AWS region.
By default, `GlueCatalog` chooses the Glue metastore to use based on the user's default AWS client credential and region setup.
You can specify the Glue catalog ID through `glue.id` catalog property to point to a Glue catalog in a different AWS account.
@@ -180,6 +181,14 @@ However, if you are streaming data to Iceberg, this will easily create a lot of
Therefore, it is recommended to turn off the archive feature in Glue by setting `glue.skip-archive` to `true`.
For more details, please read [Glue Quotas](https://docs.aws.amazon.com/general/latest/gr/glue.html) and the [UpdateTable API](https://docs.aws.amazon.com/glue/latest/webapi/API_UpdateTable.html).
+#### Skip Name Validation
+
+Allow user to skip name validation for table name and namespaces.
+It is recommended to stick to Glue best practice in
+https://docs.aws.amazon.com/athena/latest/ug/glue-best-practices.html to make sure operations are Hive compatible.
+This is only added for users that have existing conventions using non-standard characters. When database name
+and table name validation are skipped, there is no guarantee that downstream systems would all support the names.
+
#### Optimistic Locking
By default, Iceberg uses Glue's optimistic locking for concurrent updates to a table.