diff --git a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcBasePersistenceImpl.java b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcBasePersistenceImpl.java index 9cbb6057d6..0dba44695b 100644 --- a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcBasePersistenceImpl.java +++ b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/JdbcBasePersistenceImpl.java @@ -66,6 +66,7 @@ import org.apache.polaris.core.storage.PolarisStorageIntegration; import org.apache.polaris.core.storage.PolarisStorageIntegrationProvider; import org.apache.polaris.core.storage.StorageLocation; +import org.apache.polaris.persistence.relational.jdbc.models.EntityNameLookupRecordConverter; import org.apache.polaris.persistence.relational.jdbc.models.ModelEntity; import org.apache.polaris.persistence.relational.jdbc.models.ModelGrantRecord; import org.apache.polaris.persistence.relational.jdbc.models.ModelPolicyMappingRecord; @@ -425,38 +426,13 @@ public List lookupEntityVersions( .collect(Collectors.toList()); } - @Nonnull - @Override - public Page listEntities( - @Nonnull PolarisCallContext callCtx, - long catalogId, - long parentId, - @Nonnull PolarisEntityType entityType, - @Nonnull PolarisEntitySubType entitySubType, - @Nonnull PageToken pageToken) { - // TODO: only fetch the properties required for creating an EntityNameLookupRecord - return loadEntities( - callCtx, - catalogId, - parentId, - entityType, - entitySubType, - entity -> true, - EntityNameLookupRecord::new, - pageToken); - } - - @Nonnull - @Override - public Page loadEntities( - @Nonnull PolarisCallContext callCtx, + private PreparedQuery buildEntityQuery( long catalogId, long parentId, - @Nonnull PolarisEntityType entityType, - @Nonnull PolarisEntitySubType entitySubType, - @Nonnull Predicate entityFilter, - @Nonnull Function transformer, - @Nonnull PageToken pageToken) { + PolarisEntityType entityType, + PolarisEntitySubType entitySubType, + PageToken pageToken, + List queryProjections) { Map whereEquals = Map.of( "catalog_id", @@ -467,7 +443,6 @@ public Page loadEntities( entityType.getCode(), "realm_id", realmId); - Map whereGreater; if (entitySubType != PolarisEntitySubType.ANY_SUBTYPE) { Map updatedWhereEquals = new HashMap<>(whereEquals); @@ -475,9 +450,8 @@ public Page loadEntities( whereEquals = updatedWhereEquals; } - // Limit can't be pushed down, due to client side filtering - // absence of transaction. String orderByColumnName = null; + Map whereGreater; if (pageToken.paginationRequested()) { orderByColumnName = ModelEntity.ID_COLUMN; whereGreater = @@ -491,14 +465,58 @@ public Page loadEntities( whereGreater = Map.of(); } + return QueryGenerator.generateSelectQuery( + queryProjections, ModelEntity.TABLE_NAME, whereEquals, whereGreater, orderByColumnName); + } + + @Nonnull + @Override + public Page listEntities( + @Nonnull PolarisCallContext callCtx, + long catalogId, + long parentId, + @Nonnull PolarisEntityType entityType, + @Nonnull PolarisEntitySubType entitySubType, + @Nonnull PageToken pageToken) { + try { + PreparedQuery query = + buildEntityQuery( + catalogId, + parentId, + entityType, + entitySubType, + pageToken, + ModelEntity.ENTITY_LOOKUP_COLUMNS); + AtomicReference> results = new AtomicReference<>(); + datasourceOperations.executeSelectOverStream( + query, + new EntityNameLookupRecordConverter(), + stream -> { + results.set( + Page.mapped(pageToken, stream, Function.identity(), EntityIdToken::fromEntity)); + }); + return results.get(); + } catch (SQLException e) { + throw new RuntimeException( + String.format("Failed to retrieve polaris entities due to %s", e.getMessage()), e); + } + } + + @Nonnull + @Override + public Page loadEntities( + @Nonnull PolarisCallContext callCtx, + long catalogId, + long parentId, + @Nonnull PolarisEntityType entityType, + @Nonnull PolarisEntitySubType entitySubType, + @Nonnull Predicate entityFilter, + @Nonnull Function transformer, + @Nonnull PageToken pageToken) { try { PreparedQuery query = - QueryGenerator.generateSelectQuery( - ModelEntity.ALL_COLUMNS, - ModelEntity.TABLE_NAME, - whereEquals, - whereGreater, - orderByColumnName); + buildEntityQuery( + catalogId, parentId, entityType, entitySubType, pageToken, ModelEntity.ALL_COLUMNS); AtomicReference> results = new AtomicReference<>(); datasourceOperations.executeSelectOverStream( query, diff --git a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/models/EntityNameLookupRecordConverter.java b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/models/EntityNameLookupRecordConverter.java new file mode 100644 index 0000000000..d4bd7775bb --- /dev/null +++ b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/models/EntityNameLookupRecordConverter.java @@ -0,0 +1,46 @@ +/* + * 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.polaris.persistence.relational.jdbc.models; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Map; +import org.apache.polaris.core.entity.EntityNameLookupRecord; +import org.apache.polaris.persistence.relational.jdbc.DatabaseType; + +public class EntityNameLookupRecordConverter implements Converter { + + @Override + public EntityNameLookupRecord fromResultSet(ResultSet rs) throws SQLException { + return new EntityNameLookupRecord( + rs.getLong("catalog_id"), + rs.getLong("id"), + rs.getLong("parent_id"), + rs.getString("name"), + rs.getInt("type_code"), + rs.getInt("sub_type_code")); + } + + @Override + public Map toMap(DatabaseType databaseType) { + throw new UnsupportedOperationException( + "EntityNameLookupRecordConverter is read-only and does not support toMap operation"); + } +} diff --git a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/models/ModelEntity.java b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/models/ModelEntity.java index 6eaec072d4..bddcb35953 100644 --- a/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/models/ModelEntity.java +++ b/persistence/relational-jdbc/src/main/java/org/apache/polaris/persistence/relational/jdbc/models/ModelEntity.java @@ -54,6 +54,9 @@ public class ModelEntity implements Converter { "grant_records_version", "location_without_scheme"); + public static final List ENTITY_LOOKUP_COLUMNS = + List.of("id", "catalog_id", "parent_id", "type_code", "name", "sub_type_code"); + // the id of the catalog associated to that entity. use 0 if this entity is top-level // like a catalog private long catalogId; diff --git a/polaris-core/src/main/java/org/apache/polaris/core/entity/EntityNameLookupRecord.java b/polaris-core/src/main/java/org/apache/polaris/core/entity/EntityNameLookupRecord.java index 6d150a50f0..2d3afc8925 100644 --- a/polaris-core/src/main/java/org/apache/polaris/core/entity/EntityNameLookupRecord.java +++ b/polaris-core/src/main/java/org/apache/polaris/core/entity/EntityNameLookupRecord.java @@ -22,7 +22,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import java.util.Objects; -public class EntityNameLookupRecord { +public class EntityNameLookupRecord implements Identifiable { // entity catalog id private final long catalogId; @@ -41,10 +41,12 @@ public class EntityNameLookupRecord { // code representing the subtype of that entity private final int subTypeCode; + @Override public long getCatalogId() { return catalogId; } + @Override public long getId() { return id; } diff --git a/polaris-core/src/main/java/org/apache/polaris/core/entity/Identifiable.java b/polaris-core/src/main/java/org/apache/polaris/core/entity/Identifiable.java new file mode 100644 index 0000000000..cc5b9545df --- /dev/null +++ b/polaris-core/src/main/java/org/apache/polaris/core/entity/Identifiable.java @@ -0,0 +1,37 @@ +/* + * 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.polaris.core.entity; + +/** + * An interface for entity types that have an id. These entities provide a * method `getId` and + * `getCatalogId` to identify the entity. + */ +public interface Identifiable { + /** + * @return the id of the catalog associated to that entity. NULL_ID if this entity is top-level + * like a catalog + */ + long getCatalogId(); + + /** + * @return the id of the entity + */ + long getId(); +} diff --git a/polaris-core/src/main/java/org/apache/polaris/core/entity/PolarisEntityCore.java b/polaris-core/src/main/java/org/apache/polaris/core/entity/PolarisEntityCore.java index bf530c5707..283765b20c 100644 --- a/polaris-core/src/main/java/org/apache/polaris/core/entity/PolarisEntityCore.java +++ b/polaris-core/src/main/java/org/apache/polaris/core/entity/PolarisEntityCore.java @@ -27,7 +27,7 @@ * of the entity tree. For some operations like updating the entity, change will mean any change, * i.e. entity version mismatch. */ -public class PolarisEntityCore { +public class PolarisEntityCore implements Identifiable { // the id of the catalog associated to that entity. NULL_ID if this entity is top-level like // a catalog @@ -116,6 +116,7 @@ public PolarisEntityCore build() { } } + @Override public long getId() { return id; } @@ -136,6 +137,7 @@ public int getEntityVersion() { return entityVersion; } + @Override public long getCatalogId() { return catalogId; } diff --git a/polaris-core/src/main/java/org/apache/polaris/core/persistence/pagination/EntityIdToken.java b/polaris-core/src/main/java/org/apache/polaris/core/persistence/pagination/EntityIdToken.java index 8a9a03b1bd..c860e14474 100644 --- a/polaris-core/src/main/java/org/apache/polaris/core/persistence/pagination/EntityIdToken.java +++ b/polaris-core/src/main/java/org/apache/polaris/core/persistence/pagination/EntityIdToken.java @@ -23,6 +23,7 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import jakarta.annotation.Nullable; +import org.apache.polaris.core.entity.Identifiable; import org.apache.polaris.core.entity.PolarisBaseEntity; import org.apache.polaris.immutables.PolarisImmutable; @@ -41,7 +42,7 @@ default String getT() { return ID; } - static @Nullable EntityIdToken fromEntity(PolarisBaseEntity entity) { + static @Nullable EntityIdToken fromEntity(@Nullable Identifiable entity) { if (entity == null) { return null; }