Copilot commented on code in PR #11036:
URL: https://github.com/apache/gravitino/pull/11036#discussion_r3218091592
##########
core/src/main/java/org/apache/gravitino/storage/relational/po/StatisticPO.java:
##########
@@ -55,13 +57,17 @@ public static Builder builder() {
return new Builder();
}
- public static StatisticEntity fromStatisticPO(StatisticPO statisticPO) {
+ public static StatisticEntity fromStatisticPO(
+ StatisticPO statisticPO, NameIdentifier metadataObjectIdentifier) {
+ Preconditions.checkArgument(
+ metadataObjectIdentifier != null, "`metadataObjectIdentifier` is
required");
try {
return StatisticEntity.builder(
StatisticEntity.getStatisticType(
MetadataObject.Type.valueOf(statisticPO.metadataObjectType)))
.withId(statisticPO.getStatisticId())
.withName(statisticPO.getStatisticName())
+
.withNamespace(Namespace.fromString(metadataObjectIdentifier.toString()))
Review Comment:
`Namespace.fromString(metadataObjectIdentifier.toString())` relies on
`NameIdentifier.toString()` formatting/parsing, which is brittle if identifier
components ever contain the delimiter used by `Namespace.fromString()`. Prefer
constructing the namespace from identifier parts (e.g.,
`metadataObjectIdentifier.namespace().levels()` +
`metadataObjectIdentifier.name()`) to avoid lossy string round-trips.
##########
core/src/main/java/org/apache/gravitino/storage/relational/service/SchemaMetaService.java:
##########
@@ -161,19 +167,80 @@ public List<SchemaEntity>
listSchemasByNamespace(Namespace namespace) {
baseMetricName = "insertSchema")
public void insertSchema(SchemaEntity schemaEntity, boolean overwrite)
throws IOException {
try {
- NameIdentifierUtil.checkSchema(schemaEntity.nameIdentifier());
-
- SchemaPO.Builder builder = SchemaPO.builder();
- fillSchemaPOBuilderParentEntityId(builder, schemaEntity.namespace());
+ // Convert the logical entity name to physical before building and
storing the PO.
+ SchemaEntity physicalLeaf = toPhysicalEntity(schemaEntity);
+ NameIdentifierUtil.checkSchema(physicalLeaf.nameIdentifier());
Review Comment:
Validation is being performed on the *physical* schema name after conversion
(ASCII-1 separator). If `checkSchema` is intended to validate the API/logical
schema naming rules (configured separator, allowed characters, etc.), this can
reject valid logical names or accept invalid ones. Consider validating the
original logical `schemaEntity.nameIdentifier()` first (and only converting to
physical for persistence), and if needed add a separate internal validation
tailored to the physical encoding.
##########
core/src/main/java/org/apache/gravitino/storage/relational/service/SchemaMetaService.java:
##########
@@ -161,19 +167,80 @@ public List<SchemaEntity>
listSchemasByNamespace(Namespace namespace) {
baseMetricName = "insertSchema")
public void insertSchema(SchemaEntity schemaEntity, boolean overwrite)
throws IOException {
try {
- NameIdentifierUtil.checkSchema(schemaEntity.nameIdentifier());
-
- SchemaPO.Builder builder = SchemaPO.builder();
- fillSchemaPOBuilderParentEntityId(builder, schemaEntity.namespace());
+ // Convert the logical entity name to physical before building and
storing the PO.
+ SchemaEntity physicalLeaf = toPhysicalEntity(schemaEntity);
+ NameIdentifierUtil.checkSchema(physicalLeaf.nameIdentifier());
+
+ String separator = HierarchicalSchemaUtil.schemaSeparator();
+ String logicalLeaf = normalizeToLogicalSchemaName(schemaEntity.name());
+ List<SchemaEntity> rowsToInsert = new ArrayList<>();
+ if (!HierarchicalSchemaUtil.isHierarchical(logicalLeaf, separator)) {
+ rowsToInsert.add(physicalLeaf);
+ } else {
+ for (String ancestorLogical :
+ HierarchicalSchemaUtil.getAncestorNames(logicalLeaf, separator)) {
+ SchemaEntity ancestor =
+ SchemaEntity.builder()
+ .withId(nextIdForNestedAncestor())
+ .withName(ancestorLogical)
+ .withNamespace(schemaEntity.namespace())
+ .withComment(null)
+ .withProperties(Collections.emptyMap())
+ .withAuditInfo(schemaEntity.auditInfo())
+ .build();
+ rowsToInsert.add(toPhysicalEntity(ancestor));
+ }
+ SchemaEntity leafWithLogicalName =
+ SchemaEntity.builder()
+ .withId(schemaEntity.id())
+ .withName(logicalLeaf)
+ .withNamespace(schemaEntity.namespace())
+ .withComment(schemaEntity.comment())
+ .withProperties(
+ schemaEntity.properties() == null
+ ? Collections.emptyMap()
+ : schemaEntity.properties())
+ .withAuditInfo(schemaEntity.auditInfo())
+ .build();
+ rowsToInsert.add(toPhysicalEntity(leafWithLogicalName));
+ }
+ Namespace namespace = physicalLeaf.namespace();
SessionUtils.doWithCommit(
SchemaMetaMapper.class,
mapper -> {
- SchemaPO po =
POConverters.initializeSchemaPOWithVersion(schemaEntity, builder);
+ String metalakeName = namespace.level(0);
+ String catalogName = namespace.level(1);
+ List<SchemaPO> posToInsert = new ArrayList<>();
+ int n = rowsToInsert.size();
+ if (n > 1) {
+ List<SchemaEntity> ancestors = rowsToInsert.subList(0, n - 1);
+ List<String> ancestorPhysicalNames =
+
ancestors.stream().map(SchemaEntity::name).collect(Collectors.toList());
+ List<SchemaPO> existingAncestors =
+ mapper.batchSelectSchemaByIdentifier(
+ metalakeName, catalogName, ancestorPhysicalNames);
+ Set<String> existingNames =
+ existingAncestors.stream()
+ .map(SchemaPO::getSchemaName)
+ .collect(Collectors.toSet());
+ for (SchemaEntity row : ancestors) {
+ if (existingNames.contains(row.name())) {
+ continue;
+ }
+ SchemaPO.Builder builder = SchemaPO.builder();
+ fillSchemaPOBuilderParentEntityId(builder, row.namespace());
+
posToInsert.add(POConverters.initializeSchemaPOWithVersion(row, builder));
+ }
+ }
Review Comment:
This is a check-then-insert pattern for ancestor rows. Under concurrent
nested-schema inserts, two transactions can both not observe an ancestor and
then both attempt to insert it, causing a unique/PK conflict and aborting
schema creation. A more robust approach is to make ancestor insertion
idempotent at the DB level (e.g., upsert/ignore-on-conflict for ancestors),
while still enforcing `overwrite=false` semantics for the *leaf* row.
##########
core/src/main/java/org/apache/gravitino/storage/relational/service/SchemaMetaService.java:
##########
@@ -555,4 +629,93 @@ public List<SchemaEntity>
batchGetSchemaByIdentifier(List<NameIdentifier> identi
return POConverters.fromSchemaPOs(schemaPOs, firstIdent.namespace());
});
}
+
+ //
---------------------------------------------------------------------------
+ // Helpers: logical ↔ physical schema name conversion at the PO boundary
+ //
---------------------------------------------------------------------------
+
+ /**
+ * Returns the logical schema path for hierarchy expansion. Values already
using the external
+ * separator are unchanged; values stored with only the internal physical
separator are converted
+ * to logical form.
+ */
+ private String normalizeToLogicalSchemaName(String schemaName) {
+ String separator = HierarchicalSchemaUtil.schemaSeparator();
+ if (HierarchicalSchemaUtil.isHierarchical(schemaName, separator)) {
+ return schemaName;
+ }
+ if (schemaName != null &&
schemaName.contains(HierarchicalSchemaUtil.physicalSeparator())) {
+ return toLogicalSchemaName(schemaName);
+ }
+ return schemaName;
+ }
+
+ /**
+ * Converts a logical schema name (e.g. {@code "A:B:C"}) to the physical
internal form (e.g.
+ * {@code "A\u0001B\u0001C"}) used in the database. Non-HierarchicalSchema
names are returned
+ * unchanged.
+ */
+ private String toPhysicalSchemaName(String logicalName) {
+ return HierarchicalSchemaUtil.logicalToPhysical(
+ logicalName, HierarchicalSchemaUtil.schemaSeparator());
+ }
+
+ /**
+ * Converts a physical schema name (e.g. {@code "A\u0001B\u0001C"}) back to
the logical separator
+ * form (e.g. {@code "A:B:C"}). Non-HierarchicalSchema names are returned
unchanged.
+ */
+ private String toLogicalSchemaName(String physicalName) {
+ return HierarchicalSchemaUtil.physicalToLogical(
+ physicalName, HierarchicalSchemaUtil.schemaSeparator());
+ }
+
+ /**
+ * Returns a {@link SchemaEntity} whose {@code name()} is the logical
representation. If the PO
+ * stored a physical name (contains the internal physical separator), it is
converted back to the
+ * logical separator form. Otherwise the original entity is returned
unchanged.
+ */
+ private SchemaEntity toLogicalEntity(SchemaEntity entity) {
+ String logicalName = toLogicalSchemaName(entity.name());
+ if (logicalName.equals(entity.name())) {
+ return entity;
+ }
+ return SchemaEntity.builder()
+ .withId(entity.id())
+ .withName(logicalName)
+ .withNamespace(entity.namespace())
+ .withComment(entity.comment())
+ .withProperties(entity.properties())
+ .withAuditInfo(entity.auditInfo())
+ .build();
+ }
+
+ /**
+ * Returns a {@link SchemaEntity} whose {@code name()} is the physical
representation suitable for
+ * storage. If the entity carries a logical name (contains the separator),
it is converted to the
+ * physical internal form. Otherwise the original entity is returned
unchanged.
+ */
+ private SchemaEntity toPhysicalEntity(SchemaEntity entity) {
+ String separator = HierarchicalSchemaUtil.schemaSeparator();
+ if (!HierarchicalSchemaUtil.isHierarchical(entity.name(), separator)) {
+ return entity;
+ }
+ String physicalName = toPhysicalSchemaName(entity.name());
+ return SchemaEntity.builder()
+ .withId(entity.id())
+ .withName(physicalName)
+ .withNamespace(entity.namespace())
+ .withComment(entity.comment())
+ .withProperties(entity.properties())
+ .withAuditInfo(entity.auditInfo())
+ .build();
+ }
+
+ /**
+ * Nested schema materialization needs extra ids for ancestor rows.
Relational tests set {@link
+ * GravitinoEnv} config without a full server {@link IdGenerator}; fall back
for that case.
+ */
+ private static long nextIdForNestedAncestor() {
+ IdGenerator generator = GravitinoEnv.getInstance().idGenerator();
+ return generator != null ? generator.nextId() :
RandomIdGenerator.INSTANCE.nextId();
+ }
Review Comment:
Falling back to `RandomIdGenerator` in production code can mask
misconfiguration (missing `IdGenerator`) and risks ID collisions depending on
implementation/usage patterns. Prefer failing fast (e.g., throw with a clear
message) when the server `IdGenerator` is not initialized, and set up the
`IdGenerator` explicitly in tests (or initialize `GravitinoEnv` test fixtures)
rather than embedding a test-oriented fallback in the service logic.
##########
core/src/main/java/org/apache/gravitino/storage/relational/RelationalSchemaNamingBridge.java:
##########
@@ -0,0 +1,492 @@
+/*
+ * 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.storage.relational;
+
+import com.google.common.base.Joiner;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import org.apache.gravitino.Entity;
+import org.apache.gravitino.HasIdentifier;
+import org.apache.gravitino.MetadataObject;
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.Namespace;
+import org.apache.gravitino.authorization.SecurableObject;
+import org.apache.gravitino.authorization.SecurableObjects;
+import org.apache.gravitino.catalog.HierarchicalSchemaUtil;
+import org.apache.gravitino.meta.AuditInfo;
+import org.apache.gravitino.meta.FunctionEntity;
+import org.apache.gravitino.meta.GenericEntity;
+import org.apache.gravitino.meta.RoleEntity;
+import org.apache.gravitino.meta.SchemaEntity;
+import org.apache.gravitino.meta.StatisticEntity;
+import org.apache.gravitino.meta.TableEntity;
+import org.apache.gravitino.meta.TableStatisticEntity;
+import org.apache.gravitino.meta.ViewEntity;
+
+/**
+ * Translates hierarchical schema naming between the logical representation
used above {@link
+ * JDBCBackend} (configured separator in identifiers/namespaces) and the
physical representation
+ * expected by relational meta services (ASCII-1 hierarchy separator in schema
segments).
+ *
+ * <p>{@link Entity.EntityType#FILESET}, {@link Entity.EntityType#TOPIC},
{@link
+ * Entity.EntityType#MODEL}, and {@link Entity.EntityType#MODEL_VERSION}
identifiers/namespaces are
+ * left unchanged by this bridge (delegated as-is to meta services).
+ */
+public final class RelationalSchemaNamingBridge {
+
+ private static final Joiner DOT_JOINER = Joiner.on('.');
+
+ private RelationalSchemaNamingBridge() {}
+
+ private static String separator() {
+ return HierarchicalSchemaUtil.schemaSeparator();
+ }
+
+ /**
+ * Converts the dotted metadata {@code fullName} carried by {@link
SecurableObject} so the schema
+ * segment matches relational storage (physical hierarchical encoding).
Types excluded from schema
+ * embedding ({@link Entity.EntityType#FILESET}, {@link
Entity.EntityType#TOPIC}, {@link
+ * Entity.EntityType#MODEL}, {@link Entity.EntityType#MODEL_VERSION}) are
unchanged.
+ */
+ public static SecurableObject securableObjectForStorage(SecurableObject
object) {
+ String converted =
+ convertMetadataObjectDottedFullName(object.fullName(), object.type(),
/* toStorage */ true);
+ if (converted.equals(object.fullName())) {
+ return object;
+ }
+ return SecurableObjects.parse(converted, object.type(),
object.privileges());
+ }
+
+ /**
+ * Converts the dotted metadata {@code fullName} on a {@link
SecurableObject} to logical schema
+ * naming for API callers. Passthrough rules match {@link
+ * #securableObjectForStorage(SecurableObject)}.
+ */
+ public static SecurableObject securableObjectForApi(SecurableObject object) {
+ String converted =
+ convertMetadataObjectDottedFullName(
+ object.fullName(), object.type(), /* toStorage */ false);
+ if (converted.equals(object.fullName())) {
+ return object;
+ }
+ return SecurableObjects.parse(converted, object.type(),
object.privileges());
+ }
+
+ public static RoleEntity roleEntityForStorage(RoleEntity entity) {
+ List<SecurableObject> objects = entity.securableObjects();
+ if (objects == null || objects.isEmpty()) {
+ return entity;
+ }
+ List<SecurableObject> mapped =
+ objects.stream()
+ .map(RelationalSchemaNamingBridge::securableObjectForStorage)
+ .collect(Collectors.toList());
+ if (mapped.equals(objects)) {
+ return entity;
+ }
+ return RoleEntity.builder()
+ .withId(entity.id())
+ .withName(entity.name())
+ .withNamespace(entity.namespace())
+ .withProperties(entity.properties())
+ .withAuditInfo(entity.auditInfo())
+ .withSecurableObjects(mapped)
+ .build();
+ }
+
+ public static RoleEntity roleEntityForApi(RoleEntity entity) {
+ List<SecurableObject> objects = entity.securableObjects();
+ if (objects == null || objects.isEmpty()) {
+ return entity;
+ }
+ List<SecurableObject> mapped =
+ objects.stream()
+ .map(RelationalSchemaNamingBridge::securableObjectForApi)
+ .collect(Collectors.toList());
+ if (mapped.equals(objects)) {
+ return entity;
+ }
+ return RoleEntity.builder()
+ .withId(entity.id())
+ .withName(entity.name())
+ .withNamespace(entity.namespace())
+ .withProperties(entity.properties())
+ .withAuditInfo(entity.auditInfo())
+ .withSecurableObjects(mapped)
+ .build();
+ }
+
+ /**
+ * Normalizes {@link GenericEntity#name()} when it holds a dotted
metadata-object path whose
+ * schema segment uses physical hierarchical encoding, for API callers above
{@link JDBCBackend}.
+ *
+ * <p>Uses {@link MetadataObject.Type} derived from {@link
Entity.EntityType} (same as relational
+ * policy/tag stubs that only set dotted {@link GenericEntity#name()}, not a
full table/view
+ * namespace).
+ */
+ public static GenericEntity
genericEntityMetadataFullNameForApi(GenericEntity entity) {
+ String name = entity.name();
+ if (name == null || name.isEmpty()) {
+ return entity;
+ }
+ final MetadataObject.Type moType;
+ try {
+ moType = MetadataObject.Type.valueOf(entity.type().name());
+ } catch (IllegalArgumentException e) {
+ return entity;
+ }
+ String convertedName = convertMetadataObjectDottedFullName(name, moType,
false);
+
+ if (convertedName.equals(name)) {
+ return entity;
+ }
+ return GenericEntity.builder()
+ .withId(entity.id())
+ .withEntityType(entity.type())
+ .withName(convertedName)
+ .withNamespace(entity.namespace())
+ .build();
+ }
+
+ /**
+ * Rewrites the schema segment inside a dotted metadata full name
(catalog.schema[.rest]) between
+ * logical and physical hierarchical forms. Non-matching part counts are
returned unchanged.
+ */
+ static String convertMetadataObjectDottedFullName(
+ String fullName, MetadataObject.Type type, boolean toStorage) {
+ if (fullName == null || fullName.isEmpty()) {
+ return fullName;
+ }
+ switch (type) {
+ case SCHEMA:
+ return convertSchemaSegmentAt(fullName, /* expectedParts */ 2,
toStorage);
+ case TABLE:
+ case VIEW:
+ case FUNCTION:
+ return convertSchemaSegmentAt(fullName, /* expectedParts */ 3,
toStorage);
+ case COLUMN:
+ return convertSchemaSegmentAt(fullName, /* expectedParts */ 4,
toStorage);
+ default:
+ return fullName;
+ }
+ }
Review Comment:
This introduces several non-trivial translation rules (expected dotted-part
counts per `MetadataObject.Type`, schema segment index rewriting, passthrough
defaults). Please add focused unit tests covering: (1) logical↔physical
conversion round-trips, (2) each `MetadataObject.Type` case and the “parts
mismatch → unchanged” behavior, and (3) a configured non-default separator to
ensure conversions aren’t hard-coded to `":"`.
##########
core/src/main/java/org/apache/gravitino/storage/relational/RelationalSchemaNamingBridge.java:
##########
@@ -0,0 +1,492 @@
+/*
+ * 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.storage.relational;
+
+import com.google.common.base.Joiner;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import org.apache.gravitino.Entity;
+import org.apache.gravitino.HasIdentifier;
+import org.apache.gravitino.MetadataObject;
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.Namespace;
+import org.apache.gravitino.authorization.SecurableObject;
+import org.apache.gravitino.authorization.SecurableObjects;
+import org.apache.gravitino.catalog.HierarchicalSchemaUtil;
+import org.apache.gravitino.meta.AuditInfo;
+import org.apache.gravitino.meta.FunctionEntity;
+import org.apache.gravitino.meta.GenericEntity;
+import org.apache.gravitino.meta.RoleEntity;
+import org.apache.gravitino.meta.SchemaEntity;
+import org.apache.gravitino.meta.StatisticEntity;
+import org.apache.gravitino.meta.TableEntity;
+import org.apache.gravitino.meta.TableStatisticEntity;
+import org.apache.gravitino.meta.ViewEntity;
+
+/**
+ * Translates hierarchical schema naming between the logical representation
used above {@link
+ * JDBCBackend} (configured separator in identifiers/namespaces) and the
physical representation
+ * expected by relational meta services (ASCII-1 hierarchy separator in schema
segments).
+ *
+ * <p>{@link Entity.EntityType#FILESET}, {@link Entity.EntityType#TOPIC},
{@link
+ * Entity.EntityType#MODEL}, and {@link Entity.EntityType#MODEL_VERSION}
identifiers/namespaces are
+ * left unchanged by this bridge (delegated as-is to meta services).
+ */
+public final class RelationalSchemaNamingBridge {
+
+ private static final Joiner DOT_JOINER = Joiner.on('.');
+
+ private RelationalSchemaNamingBridge() {}
+
+ private static String separator() {
+ return HierarchicalSchemaUtil.schemaSeparator();
+ }
+
+ /**
+ * Converts the dotted metadata {@code fullName} carried by {@link
SecurableObject} so the schema
+ * segment matches relational storage (physical hierarchical encoding).
Types excluded from schema
+ * embedding ({@link Entity.EntityType#FILESET}, {@link
Entity.EntityType#TOPIC}, {@link
+ * Entity.EntityType#MODEL}, {@link Entity.EntityType#MODEL_VERSION}) are
unchanged.
+ */
+ public static SecurableObject securableObjectForStorage(SecurableObject
object) {
+ String converted =
+ convertMetadataObjectDottedFullName(object.fullName(), object.type(),
/* toStorage */ true);
+ if (converted.equals(object.fullName())) {
+ return object;
+ }
+ return SecurableObjects.parse(converted, object.type(),
object.privileges());
+ }
+
+ /**
+ * Converts the dotted metadata {@code fullName} on a {@link
SecurableObject} to logical schema
+ * naming for API callers. Passthrough rules match {@link
+ * #securableObjectForStorage(SecurableObject)}.
+ */
+ public static SecurableObject securableObjectForApi(SecurableObject object) {
+ String converted =
+ convertMetadataObjectDottedFullName(
+ object.fullName(), object.type(), /* toStorage */ false);
+ if (converted.equals(object.fullName())) {
+ return object;
+ }
+ return SecurableObjects.parse(converted, object.type(),
object.privileges());
+ }
+
+ public static RoleEntity roleEntityForStorage(RoleEntity entity) {
+ List<SecurableObject> objects = entity.securableObjects();
+ if (objects == null || objects.isEmpty()) {
+ return entity;
+ }
+ List<SecurableObject> mapped =
+ objects.stream()
+ .map(RelationalSchemaNamingBridge::securableObjectForStorage)
+ .collect(Collectors.toList());
+ if (mapped.equals(objects)) {
+ return entity;
+ }
+ return RoleEntity.builder()
+ .withId(entity.id())
+ .withName(entity.name())
+ .withNamespace(entity.namespace())
+ .withProperties(entity.properties())
+ .withAuditInfo(entity.auditInfo())
+ .withSecurableObjects(mapped)
+ .build();
+ }
+
+ public static RoleEntity roleEntityForApi(RoleEntity entity) {
+ List<SecurableObject> objects = entity.securableObjects();
+ if (objects == null || objects.isEmpty()) {
+ return entity;
+ }
+ List<SecurableObject> mapped =
+ objects.stream()
+ .map(RelationalSchemaNamingBridge::securableObjectForApi)
+ .collect(Collectors.toList());
+ if (mapped.equals(objects)) {
+ return entity;
+ }
+ return RoleEntity.builder()
+ .withId(entity.id())
+ .withName(entity.name())
+ .withNamespace(entity.namespace())
+ .withProperties(entity.properties())
+ .withAuditInfo(entity.auditInfo())
+ .withSecurableObjects(mapped)
+ .build();
+ }
+
+ /**
+ * Normalizes {@link GenericEntity#name()} when it holds a dotted
metadata-object path whose
+ * schema segment uses physical hierarchical encoding, for API callers above
{@link JDBCBackend}.
+ *
+ * <p>Uses {@link MetadataObject.Type} derived from {@link
Entity.EntityType} (same as relational
+ * policy/tag stubs that only set dotted {@link GenericEntity#name()}, not a
full table/view
+ * namespace).
+ */
+ public static GenericEntity
genericEntityMetadataFullNameForApi(GenericEntity entity) {
+ String name = entity.name();
+ if (name == null || name.isEmpty()) {
+ return entity;
+ }
+ final MetadataObject.Type moType;
+ try {
+ moType = MetadataObject.Type.valueOf(entity.type().name());
+ } catch (IllegalArgumentException e) {
+ return entity;
+ }
+ String convertedName = convertMetadataObjectDottedFullName(name, moType,
false);
+
+ if (convertedName.equals(name)) {
+ return entity;
+ }
+ return GenericEntity.builder()
+ .withId(entity.id())
+ .withEntityType(entity.type())
+ .withName(convertedName)
+ .withNamespace(entity.namespace())
+ .build();
+ }
+
+ /**
+ * Rewrites the schema segment inside a dotted metadata full name
(catalog.schema[.rest]) between
+ * logical and physical hierarchical forms. Non-matching part counts are
returned unchanged.
+ */
+ static String convertMetadataObjectDottedFullName(
+ String fullName, MetadataObject.Type type, boolean toStorage) {
+ if (fullName == null || fullName.isEmpty()) {
+ return fullName;
+ }
+ switch (type) {
+ case SCHEMA:
+ return convertSchemaSegmentAt(fullName, /* expectedParts */ 2,
toStorage);
+ case TABLE:
+ case VIEW:
+ case FUNCTION:
+ return convertSchemaSegmentAt(fullName, /* expectedParts */ 3,
toStorage);
+ case COLUMN:
+ return convertSchemaSegmentAt(fullName, /* expectedParts */ 4,
toStorage);
+ default:
+ return fullName;
+ }
+ }
+
+ private static String convertSchemaSegmentAt(
+ String fullName, int expectedParts, boolean toStorage) {
+ List<String> parts = SecurableObjects.DOT_SPLITTER.splitToList(fullName);
+ if (parts.size() != expectedParts) {
+ return fullName;
+ }
+ String schemaSegment = parts.get(1);
+ if (schemaSegment == null || schemaSegment.isEmpty()) {
+ return fullName;
+ }
+ String mapped =
+ toStorage
+ ? HierarchicalSchemaUtil.logicalToPhysical(schemaSegment,
separator())
+ : HierarchicalSchemaUtil.physicalToLogical(schemaSegment,
separator());
+ if (mapped.equals(schemaSegment)) {
+ return fullName;
+ }
+ List<String> copy = new ArrayList<>(parts);
+ copy.set(1, mapped);
+ return DOT_JOINER.join(copy);
+ }
+
+ /** Converts the schema segment embedded at namespace index 2 when length ≥
3. */
+ public static Namespace embeddedNamespaceForStorage(Namespace ns) {
+ if (ns.length() < 3) {
+ return ns;
+ }
+ String[] lv = ns.levels();
+ String[] copy = Arrays.copyOf(lv, lv.length);
+ copy[2] = HierarchicalSchemaUtil.logicalToPhysical(copy[2], separator());
+ return Namespace.of(copy);
+ }
+
+ public static Namespace embeddedNamespaceForApi(Namespace ns) {
+ if (ns.length() < 3) {
+ return ns;
+ }
+ String[] lv = ns.levels();
+ String[] copy = Arrays.copyOf(lv, lv.length);
+ copy[2] = HierarchicalSchemaUtil.physicalToLogical(copy[2], separator());
+ return Namespace.of(copy);
+ }
+
+ public static NameIdentifier schemaIdentifierForStorage(NameIdentifier
ident) {
+ return NameIdentifier.of(
+ ident.namespace(),
HierarchicalSchemaUtil.logicalToPhysical(ident.name(), separator()));
+ }
+
+ public static NameIdentifier schemaIdentifierForApi(NameIdentifier ident) {
+ return NameIdentifier.of(
+ ident.namespace(),
HierarchicalSchemaUtil.physicalToLogical(ident.name(), separator()));
+ }
+
+ /**
+ * Converts identifiers carrying optional hierarchical schema segments
before delegating to JDBC
+ * meta services (physical naming).
+ */
+ public static NameIdentifier nameIdentifierForStorage(
+ NameIdentifier ident, Entity.EntityType entityType) {
+ switch (entityType) {
+ case SCHEMA:
+ return schemaIdentifierForStorage(ident);
+ case TABLE:
+ case VIEW:
+ case FUNCTION:
+ case COLUMN:
+ case TABLE_STATISTIC:
+ return
NameIdentifier.of(embeddedNamespaceForStorage(ident.namespace()), ident.name());
+ default:
+ return ident;
+ }
+ }
+
+ public static NameIdentifier nameIdentifierForApi(
+ NameIdentifier ident, Entity.EntityType entityType) {
+ switch (entityType) {
+ case SCHEMA:
+ return schemaIdentifierForApi(ident);
+ case TABLE:
+ case VIEW:
+ case FUNCTION:
+ case COLUMN:
+ case TABLE_STATISTIC:
+ return NameIdentifier.of(embeddedNamespaceForApi(ident.namespace()),
ident.name());
+ default:
+ return ident;
+ }
+ }
+
+ public static SchemaEntity schemaEntityForApi(SchemaEntity entity) {
+ String logicalName =
HierarchicalSchemaUtil.physicalToLogical(entity.name(), separator());
+ if (logicalName.equals(entity.name())) {
+ return entity;
+ }
+ return SchemaEntity.builder()
+ .withId(entity.id())
+ .withName(logicalName)
+ .withNamespace(entity.namespace())
+ .withComment(entity.comment())
+ .withProperties(entity.properties())
+ .withAuditInfo(entity.auditInfo())
+ .build();
+ }
+
+ public static TableEntity tableEntityForApi(TableEntity e) {
+ Namespace apiNs = embeddedNamespaceForApi(e.namespace());
+ if (apiNs.equals(e.namespace())) {
+ return e;
+ }
+ return TableEntity.builder()
+ .withId(e.id())
+ .withName(e.name())
+ .withNamespace(apiNs)
+ .withColumns(e.columns())
+ .withAuditInfo(e.auditInfo())
+ .withProperties(e.properties())
+ .withPartitioning(e.partitioning())
+ .withSortOrders(e.sortOrders())
+ .withDistribution(e.distribution())
+ .withIndexes(e.indexes())
+ .withComment(e.comment())
+ .build();
+ }
+
+ public static TableEntity tableEntityForStorage(TableEntity e) {
+ Namespace storageNs = embeddedNamespaceForStorage(e.namespace());
+ if (storageNs.equals(e.namespace())) {
+ return e;
+ }
+ return TableEntity.builder()
+ .withId(e.id())
+ .withName(e.name())
+ .withNamespace(storageNs)
+ .withColumns(e.columns())
+ .withAuditInfo(e.auditInfo())
+ .withProperties(e.properties())
+ .withPartitioning(e.partitioning())
+ .withSortOrders(e.sortOrders())
+ .withDistribution(e.distribution())
+ .withIndexes(e.indexes())
+ .withComment(e.comment())
+ .build();
+ }
+
+ public static ViewEntity viewEntityForApi(ViewEntity e) {
+ Namespace apiNs = embeddedNamespaceForApi(e.namespace());
+ if (apiNs.equals(e.namespace())) {
+ return e;
+ }
+ return ViewEntity.builder()
+ .withId(e.id())
+ .withName(e.name())
+ .withNamespace(apiNs)
+ .withColumns(e.columns())
+ .withRepresentations(e.representations())
+ .withDefaultCatalog(e.defaultCatalog())
+ .withDefaultSchema(e.defaultSchema())
+ .withComment(e.comment())
+ .withProperties(e.properties())
+ .withAuditInfo(e.auditInfo())
+ .build();
+ }
+
+ public static ViewEntity viewEntityForStorage(ViewEntity e) {
+ Namespace storageNs = embeddedNamespaceForStorage(e.namespace());
+ if (storageNs.equals(e.namespace())) {
+ return e;
+ }
+ return ViewEntity.builder()
+ .withId(e.id())
+ .withName(e.name())
+ .withNamespace(storageNs)
+ .withColumns(e.columns())
+ .withRepresentations(e.representations())
+ .withDefaultCatalog(e.defaultCatalog())
+ .withDefaultSchema(e.defaultSchema())
+ .withComment(e.comment())
+ .withProperties(e.properties())
+ .withAuditInfo(e.auditInfo())
+ .build();
+ }
+
+ public static FunctionEntity functionEntityForApi(FunctionEntity e) {
+ Namespace apiNs = embeddedNamespaceForApi(e.namespace());
+ if (apiNs.equals(e.namespace())) {
+ return e;
+ }
+ return FunctionEntity.builder()
+ .withId(e.id())
+ .withName(e.name())
+ .withNamespace(apiNs)
+ .withComment(e.comment())
+ .withFunctionType(e.functionType())
+ .withDeterministic(e.deterministic())
+ .withDefinitions(e.definitions())
+ .withAuditInfo(e.auditInfo())
+ .build();
+ }
+
+ public static FunctionEntity functionEntityForStorage(FunctionEntity e) {
+ Namespace storageNs = embeddedNamespaceForStorage(e.namespace());
+ if (storageNs.equals(e.namespace())) {
+ return e;
+ }
+ return FunctionEntity.builder()
+ .withId(e.id())
+ .withName(e.name())
+ .withNamespace(storageNs)
+ .withComment(e.comment())
+ .withFunctionType(e.functionType())
+ .withDeterministic(e.deterministic())
+ .withDefinitions(e.definitions())
+ .withAuditInfo(e.auditInfo())
+ .build();
+ }
+
+ public static StatisticEntity statisticEntityForApi(StatisticEntity e) {
+ Namespace apiNs = embeddedNamespaceForApi(e.namespace());
+ if (apiNs.equals(e.namespace())) {
+ return e;
+ }
+ return TableStatisticEntity.builder()
+ .withId(e.id())
+ .withName(e.name())
+ .withValue(e.value())
+ .withAuditInfo((AuditInfo) e.auditInfo())
+ .withNamespace(apiNs)
+ .build();
+ }
+
+ public static StatisticEntity statisticEntityForStorage(StatisticEntity e) {
+ Namespace storageNs = embeddedNamespaceForStorage(e.namespace());
+ if (storageNs.equals(e.namespace())) {
+ return e;
+ }
+ return TableStatisticEntity.builder()
+ .withId(e.id())
+ .withName(e.name())
+ .withValue(e.value())
+ .withAuditInfo((AuditInfo) e.auditInfo())
+ .withNamespace(storageNs)
+ .build();
+ }
+
+ /**
+ * Wraps a meta-service {@code updater} used from {@link JDBCBackend}:
converts entities from
+ * storage naming to API naming, applies {@code updater}, then converts back
to storage for
+ * relational persistence.
+ *
+ * @param entityType bridged entity kind (e.g. {@link
Entity.EntityType#TABLE}); passed to {@link
+ * #entityForApi(Entity, Entity.EntityType)}
+ */
+ @SuppressWarnings("unchecked")
+ public static <E extends Entity & HasIdentifier> Function<E, E>
wrapperUpdater(
+ Entity.EntityType entityType, Function<E, E> updater) {
+ return e -> {
+ E logicalOld = entityForApi(e, entityType);
+ E logicalNew = updater.apply(logicalOld);
+ return entityForStorage(logicalNew);
+ };
+ }
Review Comment:
`wrapperUpdater` implies a round-trip conversion API↔storage for any
`EntityType`, but `entityForStorage` explicitly no-ops `SchemaEntity`. This
makes the helper easy to misuse for schema updates (and the behavior will
silently be wrong). Either (a) implement schema entity storage conversion here,
or (b) defensively reject/guard `SCHEMA` in `wrapperUpdater` (documenting that
schema updates are handled elsewhere) to prevent accidental misuse.
##########
core/src/main/java/org/apache/gravitino/storage/relational/RelationalSchemaNamingBridge.java:
##########
@@ -0,0 +1,492 @@
+/*
+ * 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.storage.relational;
+
+import com.google.common.base.Joiner;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import org.apache.gravitino.Entity;
+import org.apache.gravitino.HasIdentifier;
+import org.apache.gravitino.MetadataObject;
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.Namespace;
+import org.apache.gravitino.authorization.SecurableObject;
+import org.apache.gravitino.authorization.SecurableObjects;
+import org.apache.gravitino.catalog.HierarchicalSchemaUtil;
+import org.apache.gravitino.meta.AuditInfo;
+import org.apache.gravitino.meta.FunctionEntity;
+import org.apache.gravitino.meta.GenericEntity;
+import org.apache.gravitino.meta.RoleEntity;
+import org.apache.gravitino.meta.SchemaEntity;
+import org.apache.gravitino.meta.StatisticEntity;
+import org.apache.gravitino.meta.TableEntity;
+import org.apache.gravitino.meta.TableStatisticEntity;
+import org.apache.gravitino.meta.ViewEntity;
+
+/**
+ * Translates hierarchical schema naming between the logical representation
used above {@link
+ * JDBCBackend} (configured separator in identifiers/namespaces) and the
physical representation
+ * expected by relational meta services (ASCII-1 hierarchy separator in schema
segments).
+ *
+ * <p>{@link Entity.EntityType#FILESET}, {@link Entity.EntityType#TOPIC},
{@link
+ * Entity.EntityType#MODEL}, and {@link Entity.EntityType#MODEL_VERSION}
identifiers/namespaces are
+ * left unchanged by this bridge (delegated as-is to meta services).
+ */
+public final class RelationalSchemaNamingBridge {
+
+ private static final Joiner DOT_JOINER = Joiner.on('.');
+
+ private RelationalSchemaNamingBridge() {}
+
+ private static String separator() {
+ return HierarchicalSchemaUtil.schemaSeparator();
+ }
+
+ /**
+ * Converts the dotted metadata {@code fullName} carried by {@link
SecurableObject} so the schema
+ * segment matches relational storage (physical hierarchical encoding).
Types excluded from schema
+ * embedding ({@link Entity.EntityType#FILESET}, {@link
Entity.EntityType#TOPIC}, {@link
+ * Entity.EntityType#MODEL}, {@link Entity.EntityType#MODEL_VERSION}) are
unchanged.
+ */
+ public static SecurableObject securableObjectForStorage(SecurableObject
object) {
+ String converted =
+ convertMetadataObjectDottedFullName(object.fullName(), object.type(),
/* toStorage */ true);
+ if (converted.equals(object.fullName())) {
+ return object;
+ }
+ return SecurableObjects.parse(converted, object.type(),
object.privileges());
+ }
+
+ /**
+ * Converts the dotted metadata {@code fullName} on a {@link
SecurableObject} to logical schema
+ * naming for API callers. Passthrough rules match {@link
+ * #securableObjectForStorage(SecurableObject)}.
+ */
+ public static SecurableObject securableObjectForApi(SecurableObject object) {
+ String converted =
+ convertMetadataObjectDottedFullName(
+ object.fullName(), object.type(), /* toStorage */ false);
+ if (converted.equals(object.fullName())) {
+ return object;
+ }
+ return SecurableObjects.parse(converted, object.type(),
object.privileges());
+ }
+
+ public static RoleEntity roleEntityForStorage(RoleEntity entity) {
+ List<SecurableObject> objects = entity.securableObjects();
+ if (objects == null || objects.isEmpty()) {
+ return entity;
+ }
+ List<SecurableObject> mapped =
+ objects.stream()
+ .map(RelationalSchemaNamingBridge::securableObjectForStorage)
+ .collect(Collectors.toList());
+ if (mapped.equals(objects)) {
+ return entity;
+ }
+ return RoleEntity.builder()
+ .withId(entity.id())
+ .withName(entity.name())
+ .withNamespace(entity.namespace())
+ .withProperties(entity.properties())
+ .withAuditInfo(entity.auditInfo())
+ .withSecurableObjects(mapped)
+ .build();
+ }
+
+ public static RoleEntity roleEntityForApi(RoleEntity entity) {
+ List<SecurableObject> objects = entity.securableObjects();
+ if (objects == null || objects.isEmpty()) {
+ return entity;
+ }
+ List<SecurableObject> mapped =
+ objects.stream()
+ .map(RelationalSchemaNamingBridge::securableObjectForApi)
+ .collect(Collectors.toList());
+ if (mapped.equals(objects)) {
+ return entity;
+ }
+ return RoleEntity.builder()
+ .withId(entity.id())
+ .withName(entity.name())
+ .withNamespace(entity.namespace())
+ .withProperties(entity.properties())
+ .withAuditInfo(entity.auditInfo())
+ .withSecurableObjects(mapped)
+ .build();
+ }
+
+ /**
+ * Normalizes {@link GenericEntity#name()} when it holds a dotted
metadata-object path whose
+ * schema segment uses physical hierarchical encoding, for API callers above
{@link JDBCBackend}.
+ *
+ * <p>Uses {@link MetadataObject.Type} derived from {@link
Entity.EntityType} (same as relational
+ * policy/tag stubs that only set dotted {@link GenericEntity#name()}, not a
full table/view
+ * namespace).
+ */
+ public static GenericEntity
genericEntityMetadataFullNameForApi(GenericEntity entity) {
+ String name = entity.name();
+ if (name == null || name.isEmpty()) {
+ return entity;
+ }
+ final MetadataObject.Type moType;
+ try {
+ moType = MetadataObject.Type.valueOf(entity.type().name());
+ } catch (IllegalArgumentException e) {
+ return entity;
+ }
+ String convertedName = convertMetadataObjectDottedFullName(name, moType,
false);
+
+ if (convertedName.equals(name)) {
+ return entity;
+ }
+ return GenericEntity.builder()
+ .withId(entity.id())
+ .withEntityType(entity.type())
+ .withName(convertedName)
+ .withNamespace(entity.namespace())
+ .build();
+ }
+
+ /**
+ * Rewrites the schema segment inside a dotted metadata full name
(catalog.schema[.rest]) between
+ * logical and physical hierarchical forms. Non-matching part counts are
returned unchanged.
+ */
+ static String convertMetadataObjectDottedFullName(
+ String fullName, MetadataObject.Type type, boolean toStorage) {
+ if (fullName == null || fullName.isEmpty()) {
+ return fullName;
+ }
+ switch (type) {
+ case SCHEMA:
+ return convertSchemaSegmentAt(fullName, /* expectedParts */ 2,
toStorage);
+ case TABLE:
+ case VIEW:
+ case FUNCTION:
+ return convertSchemaSegmentAt(fullName, /* expectedParts */ 3,
toStorage);
+ case COLUMN:
+ return convertSchemaSegmentAt(fullName, /* expectedParts */ 4,
toStorage);
+ default:
+ return fullName;
+ }
+ }
+
+ private static String convertSchemaSegmentAt(
+ String fullName, int expectedParts, boolean toStorage) {
+ List<String> parts = SecurableObjects.DOT_SPLITTER.splitToList(fullName);
+ if (parts.size() != expectedParts) {
+ return fullName;
+ }
+ String schemaSegment = parts.get(1);
+ if (schemaSegment == null || schemaSegment.isEmpty()) {
+ return fullName;
+ }
+ String mapped =
+ toStorage
+ ? HierarchicalSchemaUtil.logicalToPhysical(schemaSegment,
separator())
+ : HierarchicalSchemaUtil.physicalToLogical(schemaSegment,
separator());
+ if (mapped.equals(schemaSegment)) {
+ return fullName;
+ }
+ List<String> copy = new ArrayList<>(parts);
+ copy.set(1, mapped);
+ return DOT_JOINER.join(copy);
+ }
+
+ /** Converts the schema segment embedded at namespace index 2 when length ≥
3. */
+ public static Namespace embeddedNamespaceForStorage(Namespace ns) {
+ if (ns.length() < 3) {
+ return ns;
+ }
+ String[] lv = ns.levels();
+ String[] copy = Arrays.copyOf(lv, lv.length);
+ copy[2] = HierarchicalSchemaUtil.logicalToPhysical(copy[2], separator());
+ return Namespace.of(copy);
+ }
+
+ public static Namespace embeddedNamespaceForApi(Namespace ns) {
+ if (ns.length() < 3) {
+ return ns;
+ }
+ String[] lv = ns.levels();
+ String[] copy = Arrays.copyOf(lv, lv.length);
+ copy[2] = HierarchicalSchemaUtil.physicalToLogical(copy[2], separator());
+ return Namespace.of(copy);
+ }
+
+ public static NameIdentifier schemaIdentifierForStorage(NameIdentifier
ident) {
+ return NameIdentifier.of(
+ ident.namespace(),
HierarchicalSchemaUtil.logicalToPhysical(ident.name(), separator()));
+ }
+
+ public static NameIdentifier schemaIdentifierForApi(NameIdentifier ident) {
+ return NameIdentifier.of(
+ ident.namespace(),
HierarchicalSchemaUtil.physicalToLogical(ident.name(), separator()));
+ }
+
+ /**
+ * Converts identifiers carrying optional hierarchical schema segments
before delegating to JDBC
+ * meta services (physical naming).
+ */
+ public static NameIdentifier nameIdentifierForStorage(
+ NameIdentifier ident, Entity.EntityType entityType) {
+ switch (entityType) {
+ case SCHEMA:
+ return schemaIdentifierForStorage(ident);
+ case TABLE:
+ case VIEW:
+ case FUNCTION:
+ case COLUMN:
+ case TABLE_STATISTIC:
+ return
NameIdentifier.of(embeddedNamespaceForStorage(ident.namespace()), ident.name());
+ default:
+ return ident;
+ }
+ }
+
+ public static NameIdentifier nameIdentifierForApi(
+ NameIdentifier ident, Entity.EntityType entityType) {
+ switch (entityType) {
+ case SCHEMA:
+ return schemaIdentifierForApi(ident);
+ case TABLE:
+ case VIEW:
+ case FUNCTION:
+ case COLUMN:
+ case TABLE_STATISTIC:
+ return NameIdentifier.of(embeddedNamespaceForApi(ident.namespace()),
ident.name());
+ default:
+ return ident;
+ }
+ }
+
+ public static SchemaEntity schemaEntityForApi(SchemaEntity entity) {
+ String logicalName =
HierarchicalSchemaUtil.physicalToLogical(entity.name(), separator());
+ if (logicalName.equals(entity.name())) {
+ return entity;
+ }
+ return SchemaEntity.builder()
+ .withId(entity.id())
+ .withName(logicalName)
+ .withNamespace(entity.namespace())
+ .withComment(entity.comment())
+ .withProperties(entity.properties())
+ .withAuditInfo(entity.auditInfo())
+ .build();
+ }
+
+ public static TableEntity tableEntityForApi(TableEntity e) {
+ Namespace apiNs = embeddedNamespaceForApi(e.namespace());
+ if (apiNs.equals(e.namespace())) {
+ return e;
+ }
+ return TableEntity.builder()
+ .withId(e.id())
+ .withName(e.name())
+ .withNamespace(apiNs)
+ .withColumns(e.columns())
+ .withAuditInfo(e.auditInfo())
+ .withProperties(e.properties())
+ .withPartitioning(e.partitioning())
+ .withSortOrders(e.sortOrders())
+ .withDistribution(e.distribution())
+ .withIndexes(e.indexes())
+ .withComment(e.comment())
+ .build();
+ }
+
+ public static TableEntity tableEntityForStorage(TableEntity e) {
+ Namespace storageNs = embeddedNamespaceForStorage(e.namespace());
+ if (storageNs.equals(e.namespace())) {
+ return e;
+ }
+ return TableEntity.builder()
+ .withId(e.id())
+ .withName(e.name())
+ .withNamespace(storageNs)
+ .withColumns(e.columns())
+ .withAuditInfo(e.auditInfo())
+ .withProperties(e.properties())
+ .withPartitioning(e.partitioning())
+ .withSortOrders(e.sortOrders())
+ .withDistribution(e.distribution())
+ .withIndexes(e.indexes())
+ .withComment(e.comment())
+ .build();
+ }
+
+ public static ViewEntity viewEntityForApi(ViewEntity e) {
+ Namespace apiNs = embeddedNamespaceForApi(e.namespace());
+ if (apiNs.equals(e.namespace())) {
+ return e;
+ }
+ return ViewEntity.builder()
+ .withId(e.id())
+ .withName(e.name())
+ .withNamespace(apiNs)
+ .withColumns(e.columns())
+ .withRepresentations(e.representations())
+ .withDefaultCatalog(e.defaultCatalog())
+ .withDefaultSchema(e.defaultSchema())
+ .withComment(e.comment())
+ .withProperties(e.properties())
+ .withAuditInfo(e.auditInfo())
+ .build();
+ }
+
+ public static ViewEntity viewEntityForStorage(ViewEntity e) {
+ Namespace storageNs = embeddedNamespaceForStorage(e.namespace());
+ if (storageNs.equals(e.namespace())) {
+ return e;
+ }
+ return ViewEntity.builder()
+ .withId(e.id())
+ .withName(e.name())
+ .withNamespace(storageNs)
+ .withColumns(e.columns())
+ .withRepresentations(e.representations())
+ .withDefaultCatalog(e.defaultCatalog())
+ .withDefaultSchema(e.defaultSchema())
+ .withComment(e.comment())
+ .withProperties(e.properties())
+ .withAuditInfo(e.auditInfo())
+ .build();
+ }
+
+ public static FunctionEntity functionEntityForApi(FunctionEntity e) {
+ Namespace apiNs = embeddedNamespaceForApi(e.namespace());
+ if (apiNs.equals(e.namespace())) {
+ return e;
+ }
+ return FunctionEntity.builder()
+ .withId(e.id())
+ .withName(e.name())
+ .withNamespace(apiNs)
+ .withComment(e.comment())
+ .withFunctionType(e.functionType())
+ .withDeterministic(e.deterministic())
+ .withDefinitions(e.definitions())
+ .withAuditInfo(e.auditInfo())
+ .build();
+ }
+
+ public static FunctionEntity functionEntityForStorage(FunctionEntity e) {
+ Namespace storageNs = embeddedNamespaceForStorage(e.namespace());
+ if (storageNs.equals(e.namespace())) {
+ return e;
+ }
+ return FunctionEntity.builder()
+ .withId(e.id())
+ .withName(e.name())
+ .withNamespace(storageNs)
+ .withComment(e.comment())
+ .withFunctionType(e.functionType())
+ .withDeterministic(e.deterministic())
+ .withDefinitions(e.definitions())
+ .withAuditInfo(e.auditInfo())
+ .build();
+ }
+
+ public static StatisticEntity statisticEntityForApi(StatisticEntity e) {
+ Namespace apiNs = embeddedNamespaceForApi(e.namespace());
+ if (apiNs.equals(e.namespace())) {
+ return e;
+ }
+ return TableStatisticEntity.builder()
+ .withId(e.id())
+ .withName(e.name())
+ .withValue(e.value())
+ .withAuditInfo((AuditInfo) e.auditInfo())
+ .withNamespace(apiNs)
+ .build();
+ }
+
+ public static StatisticEntity statisticEntityForStorage(StatisticEntity e) {
+ Namespace storageNs = embeddedNamespaceForStorage(e.namespace());
+ if (storageNs.equals(e.namespace())) {
+ return e;
+ }
+ return TableStatisticEntity.builder()
+ .withId(e.id())
+ .withName(e.name())
+ .withValue(e.value())
+ .withAuditInfo((AuditInfo) e.auditInfo())
+ .withNamespace(storageNs)
+ .build();
+ }
+
+ /**
+ * Wraps a meta-service {@code updater} used from {@link JDBCBackend}:
converts entities from
+ * storage naming to API naming, applies {@code updater}, then converts back
to storage for
+ * relational persistence.
+ *
+ * @param entityType bridged entity kind (e.g. {@link
Entity.EntityType#TABLE}); passed to {@link
+ * #entityForApi(Entity, Entity.EntityType)}
+ */
+ @SuppressWarnings("unchecked")
+ public static <E extends Entity & HasIdentifier> Function<E, E>
wrapperUpdater(
+ Entity.EntityType entityType, Function<E, E> updater) {
+ return e -> {
+ E logicalOld = entityForApi(e, entityType);
+ E logicalNew = updater.apply(logicalOld);
+ return entityForStorage(logicalNew);
+ };
+ }
+
+ @SuppressWarnings("unchecked")
+ public static <E extends Entity & HasIdentifier> E entityForApi(
+ E entity, Entity.EntityType type) {
+ switch (type) {
+ case SCHEMA:
+ return (E) schemaEntityForApi((SchemaEntity) entity);
+ case TABLE:
+ return (E) tableEntityForApi((TableEntity) entity);
+ case VIEW:
+ return (E) viewEntityForApi((ViewEntity) entity);
+ case FUNCTION:
+ return (E) functionEntityForApi((FunctionEntity) entity);
+ case TABLE_STATISTIC:
+ return (E) statisticEntityForApi((StatisticEntity) entity);
+ default:
+ return entity;
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public static <E extends Entity & HasIdentifier> E entityForStorage(E
entity) {
+ if (entity instanceof SchemaEntity) {
+ return entity;
+ }
Review Comment:
`wrapperUpdater` implies a round-trip conversion API↔storage for any
`EntityType`, but `entityForStorage` explicitly no-ops `SchemaEntity`. This
makes the helper easy to misuse for schema updates (and the behavior will
silently be wrong). Either (a) implement schema entity storage conversion here,
or (b) defensively reject/guard `SCHEMA` in `wrapperUpdater` (documenting that
schema updates are handled elsewhere) to prevent accidental misuse.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]