mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-08-28 02:46:09 +00:00
* Glossary Page Performance Issue with Large Number of Terms(#19761) * Add glossary page performance unittest(#19761) * Fix dup setFieldsInternal(#19761) * Add fetch childrenCount (#19761) * add comment about setFieldsInBulk(#19761) * rename to countFindTo(#19761) * fix findByIds return both id and json (#19761) * turn up UT timeout (#19761) --------- Co-authored-by: sonika-shah <58761340+sonika-shah@users.noreply.github.com>
This commit is contained in:
parent
9e13ff6be8
commit
67a0795f7b
@ -44,6 +44,7 @@ import lombok.Getter;
|
|||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.collections4.CollectionUtils;
|
||||||
import org.jdbi.v3.core.Jdbi;
|
import org.jdbi.v3.core.Jdbi;
|
||||||
import org.openmetadata.schema.EntityInterface;
|
import org.openmetadata.schema.EntityInterface;
|
||||||
import org.openmetadata.schema.EntityTimeSeriesInterface;
|
import org.openmetadata.schema.EntityTimeSeriesInterface;
|
||||||
@ -397,6 +398,13 @@ public final class Entity {
|
|||||||
return repository.getReference(id, include);
|
return repository.getReference(id, include);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static List<EntityReference> getEntityReferencesByIds(
|
||||||
|
@NonNull String entityType, @NonNull List<UUID> ids, Include include) {
|
||||||
|
EntityRepository<? extends EntityInterface> repository = getEntityRepository(entityType);
|
||||||
|
include = repository.supportsSoftDelete ? Include.ALL : include;
|
||||||
|
return repository.getReferences(ids, include);
|
||||||
|
}
|
||||||
|
|
||||||
public static EntityReference getEntityReferenceByName(
|
public static EntityReference getEntityReferenceByName(
|
||||||
@NonNull String entityType, String fqn, Include include) {
|
@NonNull String entityType, String fqn, Include include) {
|
||||||
if (fqn == null) {
|
if (fqn == null) {
|
||||||
@ -437,6 +445,23 @@ public final class Entity {
|
|||||||
: getEntityByName(ref.getType(), ref.getFullyQualifiedName(), fields, include);
|
: getEntityByName(ref.getType(), ref.getFullyQualifiedName(), fields, include);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static <T> List<T> getEntities(
|
||||||
|
List<EntityReference> refs, String fields, Include include) {
|
||||||
|
if (CollectionUtils.isEmpty(refs)) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
EntityRepository<?> entityRepository = Entity.getEntityRepository(refs.get(0).getType());
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
List<T> entities =
|
||||||
|
(List<T>)
|
||||||
|
entityRepository.get(
|
||||||
|
null,
|
||||||
|
refs.stream().map(EntityReference::getId).toList(),
|
||||||
|
entityRepository.getFields(fields),
|
||||||
|
include);
|
||||||
|
return entities;
|
||||||
|
}
|
||||||
|
|
||||||
public static <T> T getEntityOrNull(
|
public static <T> T getEntityOrNull(
|
||||||
EntityReference entityReference, String field, Include include) {
|
EntityReference entityReference, String field, Include include) {
|
||||||
if (entityReference == null) return null;
|
if (entityReference == null) return null;
|
||||||
@ -478,6 +503,16 @@ public final class Entity {
|
|||||||
return getEntityByName(entityType, fqn, fields, include, true);
|
return getEntityByName(entityType, fqn, fields, include, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static <T> List<T> getEntityByNames(
|
||||||
|
String entityType, List<String> tagFQNs, String fields, Include include) {
|
||||||
|
EntityRepository<?> entityRepository = Entity.getEntityRepository(entityType);
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
List<T> entities =
|
||||||
|
(List<T>)
|
||||||
|
entityRepository.getByNames(null, tagFQNs, entityRepository.getFields(fields), include);
|
||||||
|
return entities;
|
||||||
|
}
|
||||||
|
|
||||||
/** Retrieve the corresponding entity repository for a given entity name. */
|
/** Retrieve the corresponding entity repository for a given entity name. */
|
||||||
public static EntityRepository<? extends EntityInterface> getEntityRepository(
|
public static EntityRepository<? extends EntityInterface> getEntityRepository(
|
||||||
@NonNull String entityType) {
|
@NonNull String entityType) {
|
||||||
|
@ -876,6 +876,13 @@ public interface CollectionDAO {
|
|||||||
private String json;
|
private String json;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Builder
|
||||||
|
class EntityRelationshipCount {
|
||||||
|
private UUID id;
|
||||||
|
private Integer count;
|
||||||
|
}
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@Builder
|
@Builder
|
||||||
class EntityRelationshipObject {
|
class EntityRelationshipObject {
|
||||||
@ -1009,6 +1016,19 @@ public interface CollectionDAO {
|
|||||||
@Bind("fromEntityType") String fromEntityType,
|
@Bind("fromEntityType") String fromEntityType,
|
||||||
@Bind("toEntityType") String toEntityType);
|
@Bind("toEntityType") String toEntityType);
|
||||||
|
|
||||||
|
@SqlQuery(
|
||||||
|
"SELECT fromId, toId, fromEntity, toEntity, relation "
|
||||||
|
+ "FROM entity_relationship "
|
||||||
|
+ "WHERE fromId IN (<fromIds>) "
|
||||||
|
+ "AND relation = :relation "
|
||||||
|
+ "AND toEntity = :toEntityType "
|
||||||
|
+ "AND deleted = FALSE")
|
||||||
|
@UseRowMapper(RelationshipObjectMapper.class)
|
||||||
|
List<EntityRelationshipObject> findToBatch(
|
||||||
|
@BindList("fromIds") List<String> fromIds,
|
||||||
|
@Bind("relation") int relation,
|
||||||
|
@Bind("toEntityType") String toEntityType);
|
||||||
|
|
||||||
@SqlQuery(
|
@SqlQuery(
|
||||||
"SELECT toId, toEntity, json FROM entity_relationship "
|
"SELECT toId, toEntity, json FROM entity_relationship "
|
||||||
+ "WHERE fromId = :fromId AND fromEntity = :fromEntity AND relation = :relation AND toEntity = :toEntity")
|
+ "WHERE fromId = :fromId AND fromEntity = :fromEntity AND relation = :relation AND toEntity = :toEntity")
|
||||||
@ -1019,6 +1039,17 @@ public interface CollectionDAO {
|
|||||||
@Bind("relation") int relation,
|
@Bind("relation") int relation,
|
||||||
@Bind("toEntity") String toEntity);
|
@Bind("toEntity") String toEntity);
|
||||||
|
|
||||||
|
@SqlQuery(
|
||||||
|
"SELECT fromId, COUNT(toId) FROM entity_relationship "
|
||||||
|
+ "WHERE fromId IN (<fromIds>) AND fromEntity = :fromEntity AND relation = :relation AND toEntity = :toEntity "
|
||||||
|
+ "GROUP BY fromId")
|
||||||
|
@RegisterRowMapper(ToRelationshipCountMapper.class)
|
||||||
|
List<EntityRelationshipCount> countFindTo(
|
||||||
|
@BindList("fromIds") List<String> fromIds,
|
||||||
|
@Bind("fromEntity") String fromEntity,
|
||||||
|
@Bind("relation") int relation,
|
||||||
|
@Bind("toEntity") String toEntity);
|
||||||
|
|
||||||
@SqlQuery(
|
@SqlQuery(
|
||||||
"SELECT COUNT(toId) FROM entity_relationship WHERE fromId = :fromId AND fromEntity = :fromEntity "
|
"SELECT COUNT(toId) FROM entity_relationship WHERE fromId = :fromId AND fromEntity = :fromEntity "
|
||||||
+ "AND relation IN (<relation>)")
|
+ "AND relation IN (<relation>)")
|
||||||
@ -1300,6 +1331,16 @@ public interface CollectionDAO {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ToRelationshipCountMapper implements RowMapper<EntityRelationshipCount> {
|
||||||
|
@Override
|
||||||
|
public EntityRelationshipCount map(ResultSet rs, StatementContext ctx) throws SQLException {
|
||||||
|
return EntityRelationshipCount.builder()
|
||||||
|
.id(UUID.fromString(rs.getString(1)))
|
||||||
|
.count(rs.getInt(2))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class RelationshipObjectMapper implements RowMapper<EntityRelationshipObject> {
|
class RelationshipObjectMapper implements RowMapper<EntityRelationshipObject> {
|
||||||
@Override
|
@Override
|
||||||
public EntityRelationshipObject map(ResultSet rs, StatementContext ctx) throws SQLException {
|
public EntityRelationshipObject map(ResultSet rs, StatementContext ctx) throws SQLException {
|
||||||
|
@ -19,11 +19,18 @@ import static org.openmetadata.service.jdbi3.ListFilter.escapeApostrophe;
|
|||||||
import static org.openmetadata.service.jdbi3.locator.ConnectionType.MYSQL;
|
import static org.openmetadata.service.jdbi3.locator.ConnectionType.MYSQL;
|
||||||
import static org.openmetadata.service.jdbi3.locator.ConnectionType.POSTGRES;
|
import static org.openmetadata.service.jdbi3.locator.ConnectionType.POSTGRES;
|
||||||
|
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
|
import org.apache.commons.collections4.CollectionUtils;
|
||||||
|
import org.jdbi.v3.core.mapper.RowMapper;
|
||||||
|
import org.jdbi.v3.core.statement.StatementContext;
|
||||||
|
import org.jdbi.v3.sqlobject.config.RegisterRowMapper;
|
||||||
import org.jdbi.v3.sqlobject.customizer.Bind;
|
import org.jdbi.v3.sqlobject.customizer.Bind;
|
||||||
|
import org.jdbi.v3.sqlobject.customizer.BindList;
|
||||||
import org.jdbi.v3.sqlobject.customizer.BindMap;
|
import org.jdbi.v3.sqlobject.customizer.BindMap;
|
||||||
import org.jdbi.v3.sqlobject.customizer.Define;
|
import org.jdbi.v3.sqlobject.customizer.Define;
|
||||||
import org.jdbi.v3.sqlobject.statement.BatchChunkSize;
|
import org.jdbi.v3.sqlobject.statement.BatchChunkSize;
|
||||||
@ -148,6 +155,13 @@ public interface EntityDAO<T extends EntityInterface> {
|
|||||||
String findById(
|
String findById(
|
||||||
@Define("table") String table, @BindUUID("id") UUID id, @Define("cond") String cond);
|
@Define("table") String table, @BindUUID("id") UUID id, @Define("cond") String cond);
|
||||||
|
|
||||||
|
@SqlQuery("SELECT id, json FROM <table> WHERE id IN (<ids>) <cond>")
|
||||||
|
@RegisterRowMapper(EntityIdJsonPairMapper.class)
|
||||||
|
List<EntityIdJsonPair> findByIds(
|
||||||
|
@Define("table") String table,
|
||||||
|
@BindList("ids") List<String> ids,
|
||||||
|
@Define("cond") String cond);
|
||||||
|
|
||||||
@SqlQuery("SELECT json FROM <table> WHERE <nameColumnHash> = :name <cond>")
|
@SqlQuery("SELECT json FROM <table> WHERE <nameColumnHash> = :name <cond>")
|
||||||
String findByName(
|
String findByName(
|
||||||
@Define("table") String table,
|
@Define("table") String table,
|
||||||
@ -155,6 +169,14 @@ public interface EntityDAO<T extends EntityInterface> {
|
|||||||
@BindFQN("name") String name,
|
@BindFQN("name") String name,
|
||||||
@Define("cond") String cond);
|
@Define("cond") String cond);
|
||||||
|
|
||||||
|
@SqlQuery("SELECT <nameColumnHash>, json FROM <table> WHERE <nameColumnHash> IN (<names>) <cond>")
|
||||||
|
@RegisterRowMapper(EntityNameColumnHashJsonPairMapper.class)
|
||||||
|
List<EntityNameColumnHashJsonPair> findByNames(
|
||||||
|
@Define("table") String table,
|
||||||
|
@Define("nameColumnHash") String nameColumn,
|
||||||
|
@BindList("names") List<String> names,
|
||||||
|
@Define("cond") String cond);
|
||||||
|
|
||||||
@SqlQuery("SELECT count(<nameHashColumn>) FROM <table> <cond>")
|
@SqlQuery("SELECT count(<nameHashColumn>) FROM <table> <cond>")
|
||||||
int listCount(
|
int listCount(
|
||||||
@Define("table") String table,
|
@Define("table") String table,
|
||||||
@ -448,6 +470,19 @@ public interface EntityDAO<T extends EntityInterface> {
|
|||||||
return findEntityById(id, Include.NON_DELETED);
|
return findEntityById(id, Include.NON_DELETED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default List<T> findEntitiesByIds(List<UUID> ids, Include include) {
|
||||||
|
if (CollectionUtils.isEmpty(ids)) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
return findByIds(
|
||||||
|
getTableName(),
|
||||||
|
ids.stream().map(UUID::toString).distinct().toList(),
|
||||||
|
getCondition(include))
|
||||||
|
.stream()
|
||||||
|
.map(pair -> jsonToEntity(pair.json, pair.id))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
default T findEntityByName(String fqn) {
|
default T findEntityByName(String fqn) {
|
||||||
return findEntityByName(fqn, Include.NON_DELETED);
|
return findEntityByName(fqn, Include.NON_DELETED);
|
||||||
}
|
}
|
||||||
@ -464,6 +499,17 @@ public interface EntityDAO<T extends EntityInterface> {
|
|||||||
findByName(getTableName(), nameHashColumn, fqn, getCondition(include)), fqn);
|
findByName(getTableName(), nameHashColumn, fqn, getCondition(include)), fqn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
default List<T> findEntityByNames(List<String> entityFQNs, Include include) {
|
||||||
|
if (CollectionUtils.isEmpty(entityFQNs)) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
List<String> names = entityFQNs.stream().distinct().map(FullyQualifiedName::buildHash).toList();
|
||||||
|
return findByNames(getTableName(), getNameHashColumn(), names, getCondition(include)).stream()
|
||||||
|
.map(pair -> jsonToEntity(pair.json, pair.nameColumnHash))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
default T jsonToEntity(String json, Object identity) {
|
default T jsonToEntity(String json, Object identity) {
|
||||||
Class<T> clz = getEntityClass();
|
Class<T> clz = getEntityClass();
|
||||||
T entity = json != null ? JsonUtils.readValue(json, clz) : null;
|
T entity = json != null ? JsonUtils.readValue(json, clz) : null;
|
||||||
@ -549,4 +595,22 @@ public interface EntityDAO<T extends EntityInterface> {
|
|||||||
throw EntityNotFoundException.byMessage(entityNotFound(entityType, id));
|
throw EntityNotFoundException.byMessage(entityNotFound(entityType, id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
record EntityNameColumnHashJsonPair(String nameColumnHash, String json) {}
|
||||||
|
|
||||||
|
class EntityNameColumnHashJsonPairMapper implements RowMapper<EntityNameColumnHashJsonPair> {
|
||||||
|
@Override
|
||||||
|
public EntityNameColumnHashJsonPair map(ResultSet r, StatementContext ctx) throws SQLException {
|
||||||
|
return new EntityNameColumnHashJsonPair(r.getString(1), r.getString(2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
record EntityIdJsonPair(UUID id, String json) {}
|
||||||
|
|
||||||
|
class EntityIdJsonPairMapper implements RowMapper<EntityIdJsonPair> {
|
||||||
|
@Override
|
||||||
|
public EntityIdJsonPair map(ResultSet r, StatementContext ctx) throws SQLException {
|
||||||
|
return new EntityIdJsonPair(UUID.fromString(r.getString(1)), r.getString(2));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -120,6 +120,7 @@ import javax.ws.rs.core.UriInfo;
|
|||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.collections4.CollectionUtils;
|
||||||
import org.apache.commons.lang3.tuple.ImmutablePair;
|
import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||||
import org.apache.commons.lang3.tuple.Pair;
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
import org.jdbi.v3.sqlobject.transaction.Transaction;
|
import org.jdbi.v3.sqlobject.transaction.Transaction;
|
||||||
@ -132,6 +133,7 @@ import org.openmetadata.schema.api.VoteRequest.VoteType;
|
|||||||
import org.openmetadata.schema.api.feed.ResolveTask;
|
import org.openmetadata.schema.api.feed.ResolveTask;
|
||||||
import org.openmetadata.schema.api.teams.CreateTeam;
|
import org.openmetadata.schema.api.teams.CreateTeam;
|
||||||
import org.openmetadata.schema.configuration.AssetCertificationSettings;
|
import org.openmetadata.schema.configuration.AssetCertificationSettings;
|
||||||
|
import org.openmetadata.schema.entity.classification.Tag;
|
||||||
import org.openmetadata.schema.entity.data.Table;
|
import org.openmetadata.schema.entity.data.Table;
|
||||||
import org.openmetadata.schema.entity.feed.Suggestion;
|
import org.openmetadata.schema.entity.feed.Suggestion;
|
||||||
import org.openmetadata.schema.entity.teams.Team;
|
import org.openmetadata.schema.entity.teams.Team;
|
||||||
@ -257,6 +259,7 @@ public abstract class EntityRepository<T extends EntityInterface> {
|
|||||||
@Getter protected final boolean supportsStyle;
|
@Getter protected final boolean supportsStyle;
|
||||||
@Getter protected final boolean supportsLifeCycle;
|
@Getter protected final boolean supportsLifeCycle;
|
||||||
@Getter protected final boolean supportsCertification;
|
@Getter protected final boolean supportsCertification;
|
||||||
|
@Getter protected final boolean supportsChildren;
|
||||||
protected final boolean supportsFollower;
|
protected final boolean supportsFollower;
|
||||||
protected final boolean supportsExtension;
|
protected final boolean supportsExtension;
|
||||||
protected final boolean supportsVotes;
|
protected final boolean supportsVotes;
|
||||||
@ -373,6 +376,7 @@ public abstract class EntityRepository<T extends EntityInterface> {
|
|||||||
this.patchFields.addField(allowedFields, FIELD_CERTIFICATION);
|
this.patchFields.addField(allowedFields, FIELD_CERTIFICATION);
|
||||||
this.putFields.addField(allowedFields, FIELD_CERTIFICATION);
|
this.putFields.addField(allowedFields, FIELD_CERTIFICATION);
|
||||||
}
|
}
|
||||||
|
this.supportsChildren = allowedFields.contains(FIELD_CHILDREN);
|
||||||
|
|
||||||
Map<String, Pair<Boolean, BiConsumer<List<T>, Fields>>> fieldSupportMap = new HashMap<>();
|
Map<String, Pair<Boolean, BiConsumer<List<T>, Fields>>> fieldSupportMap = new HashMap<>();
|
||||||
|
|
||||||
@ -381,6 +385,7 @@ public abstract class EntityRepository<T extends EntityInterface> {
|
|||||||
fieldSupportMap.put(FIELD_DOMAIN, Pair.of(supportsDomain, this::fetchAndSetDomain));
|
fieldSupportMap.put(FIELD_DOMAIN, Pair.of(supportsDomain, this::fetchAndSetDomain));
|
||||||
fieldSupportMap.put(FIELD_REVIEWERS, Pair.of(supportsReviewers, this::fetchAndSetReviewers));
|
fieldSupportMap.put(FIELD_REVIEWERS, Pair.of(supportsReviewers, this::fetchAndSetReviewers));
|
||||||
fieldSupportMap.put(FIELD_EXTENSION, Pair.of(supportsExtension, this::fetchAndSetExtension));
|
fieldSupportMap.put(FIELD_EXTENSION, Pair.of(supportsExtension, this::fetchAndSetExtension));
|
||||||
|
fieldSupportMap.put(FIELD_CHILDREN, Pair.of(supportsChildren, this::fetchAndSetChildren));
|
||||||
|
|
||||||
for (Entry<String, Pair<Boolean, BiConsumer<List<T>, Fields>>> entry :
|
for (Entry<String, Pair<Boolean, BiConsumer<List<T>, Fields>>> entry :
|
||||||
fieldSupportMap.entrySet()) {
|
fieldSupportMap.entrySet()) {
|
||||||
@ -390,8 +395,6 @@ public abstract class EntityRepository<T extends EntityInterface> {
|
|||||||
|
|
||||||
if (supportsField) {
|
if (supportsField) {
|
||||||
this.fieldFetchers.put(fieldName, fetcher);
|
this.fieldFetchers.put(fieldName, fetcher);
|
||||||
this.patchFields.addField(allowedFields, fieldName);
|
|
||||||
this.putFields.addField(allowedFields, fieldName);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -474,7 +477,11 @@ public abstract class EntityRepository<T extends EntityInterface> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
/**
|
||||||
|
* The default behavior is to execute one by one. For batch execution, override this method in the subclass.
|
||||||
|
*
|
||||||
|
* @see GlossaryTermRepository#setInheritedFields(List, Fields) for an example implementation
|
||||||
|
*/
|
||||||
protected void setInheritedFields(List<T> entities, Fields fields) {
|
protected void setInheritedFields(List<T> entities, Fields fields) {
|
||||||
for (T entity : entities) {
|
for (T entity : entities) {
|
||||||
setInheritedFields(entity, fields);
|
setInheritedFields(entity, fields);
|
||||||
@ -633,6 +640,13 @@ public abstract class EntityRepository<T extends EntityInterface> {
|
|||||||
return withHref(uriInfo, entityClone);
|
return withHref(uriInfo, entityClone);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final List<T> get(UriInfo uriInfo, List<UUID> ids, Fields fields, Include include) {
|
||||||
|
List<T> entities = find(ids, include);
|
||||||
|
setFieldsInBulk(fields, entities);
|
||||||
|
entities.forEach(entity -> withHref(uriInfo, entity));
|
||||||
|
return entities;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* getReference is used for getting the entity references from the entity in the cache.
|
* getReference is used for getting the entity references from the entity in the cache.
|
||||||
*/
|
*/
|
||||||
@ -641,6 +655,11 @@ public abstract class EntityRepository<T extends EntityInterface> {
|
|||||||
return find(id, include).getEntityReference();
|
return find(id, include).getEntityReference();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final List<EntityReference> getReferences(List<UUID> id, Include include)
|
||||||
|
throws EntityNotFoundException {
|
||||||
|
return find(id, include).stream().map(EntityInterface::getEntityReference).toList();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find method is used for getting an entity only with core fields stored as JSON without any relational fields set
|
* Find method is used for getting an entity only with core fields stored as JSON without any relational fields set
|
||||||
*/
|
*/
|
||||||
@ -665,6 +684,10 @@ public abstract class EntityRepository<T extends EntityInterface> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final List<T> find(List<UUID> ids, Include include) {
|
||||||
|
return dao.findEntitiesByIds(ids, include);
|
||||||
|
}
|
||||||
|
|
||||||
public T getByName(UriInfo uriInfo, String fqn, Fields fields) {
|
public T getByName(UriInfo uriInfo, String fqn, Fields fields) {
|
||||||
return getByName(uriInfo, fqn, fields, NON_DELETED, false);
|
return getByName(uriInfo, fqn, fields, NON_DELETED, false);
|
||||||
}
|
}
|
||||||
@ -695,6 +718,14 @@ public abstract class EntityRepository<T extends EntityInterface> {
|
|||||||
return findByName(fqn, include).getEntityReference();
|
return findByName(fqn, include).getEntityReference();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final List<T> getByNames(
|
||||||
|
UriInfo uriInfo, List<String> entityFQNs, Fields fields, Include include) {
|
||||||
|
List<T> entities = findByNames(entityFQNs, include);
|
||||||
|
setFieldsInBulk(fields, entities);
|
||||||
|
entities.forEach(entity -> withHref(uriInfo, entity));
|
||||||
|
return entities;
|
||||||
|
}
|
||||||
|
|
||||||
public final T findByNameOrNull(String fqn, Include include) {
|
public final T findByNameOrNull(String fqn, Include include) {
|
||||||
try {
|
try {
|
||||||
return findByName(fqn, include);
|
return findByName(fqn, include);
|
||||||
@ -728,16 +759,19 @@ public abstract class EntityRepository<T extends EntityInterface> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<T> findByNames(List<String> entityFQNs, Include include) {
|
||||||
|
return dao.findEntityByNames(entityFQNs, include);
|
||||||
|
}
|
||||||
|
|
||||||
public final List<T> listAll(Fields fields, ListFilter filter) {
|
public final List<T> listAll(Fields fields, ListFilter filter) {
|
||||||
// forward scrolling, if after == null then first page is being asked
|
// forward scrolling, if after == null then first page is being asked
|
||||||
List<String> jsons = dao.listAfter(filter, Integer.MAX_VALUE, "", "");
|
List<String> jsons = dao.listAfter(filter, Integer.MAX_VALUE, "", "");
|
||||||
List<T> entities = new ArrayList<>();
|
List<T> entities = new ArrayList<>();
|
||||||
for (String json : jsons) {
|
for (String json : jsons) {
|
||||||
T entity = setFieldsInternal(JsonUtils.readValue(json, entityClass), fields);
|
T entity = JsonUtils.readValue(json, entityClass);
|
||||||
setInheritedFields(entity, fields);
|
|
||||||
clearFieldsInternal(entity, fields);
|
|
||||||
entities.add(entity);
|
entities.add(entity);
|
||||||
}
|
}
|
||||||
|
setFieldsInBulk(fields, entities);
|
||||||
return entities;
|
return entities;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -745,17 +779,32 @@ public abstract class EntityRepository<T extends EntityInterface> {
|
|||||||
ListFilter filter = new ListFilter(NON_DELETED);
|
ListFilter filter = new ListFilter(NON_DELETED);
|
||||||
List<String> jsons = listAllByParentFqn(parentFqn, filter);
|
List<String> jsons = listAllByParentFqn(parentFqn, filter);
|
||||||
List<T> entities = new ArrayList<>();
|
List<T> entities = new ArrayList<>();
|
||||||
setFieldsInBulk(jsons, fields, entities);
|
|
||||||
return entities;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFieldsInBulk(List<String> jsons, Fields fields, List<T> entities) {
|
|
||||||
for (String json : jsons) {
|
for (String json : jsons) {
|
||||||
T entity = JsonUtils.readValue(json, entityClass);
|
T entity = JsonUtils.readValue(json, entityClass);
|
||||||
entities.add(entity);
|
entities.add(entity);
|
||||||
}
|
}
|
||||||
|
// TODO: Ensure consistent behavior with setFieldsInBulk when all repositories implement it
|
||||||
fetchAndSetFields(entities, fields);
|
fetchAndSetFields(entities, fields);
|
||||||
|
setInheritedFields(entities, fields);
|
||||||
for (T entity : entities) {
|
for (T entity : entities) {
|
||||||
|
clearFieldsInternal(entity, fields);
|
||||||
|
}
|
||||||
|
return entities;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes {@link #setFields} on a list of entities. By default, this method processes
|
||||||
|
* each entity individually. To enable batch processing, override this method in a subclass.
|
||||||
|
* <p>
|
||||||
|
* For efficient bulk processing, ensure all fields used in {@link #setFields}
|
||||||
|
* have corresponding batch processing methods, such as {@code fetchAndSetXXX}. For instance,
|
||||||
|
* if handling a domain field, implement {@link #fetchAndSetDomain}.
|
||||||
|
* <p>
|
||||||
|
* Example implementation can be found in {@link GlossaryTermRepository#setFieldsInBulk}.
|
||||||
|
*/
|
||||||
|
public void setFieldsInBulk(Fields fields, List<T> entities) {
|
||||||
|
for (T entity : entities) {
|
||||||
|
setFieldsInternal(entity, fields);
|
||||||
setInheritedFields(entity, fields);
|
setInheritedFields(entity, fields);
|
||||||
clearFieldsInternal(entity, fields);
|
clearFieldsInternal(entity, fields);
|
||||||
}
|
}
|
||||||
@ -788,11 +837,11 @@ public abstract class EntityRepository<T extends EntityInterface> {
|
|||||||
List<String> jsons = dao.listAfter(filter, limitParam + 1, afterName, afterId);
|
List<String> jsons = dao.listAfter(filter, limitParam + 1, afterName, afterId);
|
||||||
|
|
||||||
for (String json : jsons) {
|
for (String json : jsons) {
|
||||||
T entity = setFieldsInternal(JsonUtils.readValue(json, entityClass), fields);
|
T entity = JsonUtils.readValue(json, entityClass);
|
||||||
setInheritedFields(entity, fields);
|
entities.add(entity);
|
||||||
clearFieldsInternal(entity, fields);
|
|
||||||
entities.add(withHref(uriInfo, entity));
|
|
||||||
}
|
}
|
||||||
|
setFieldsInBulk(fields, entities);
|
||||||
|
entities.forEach(entity -> withHref(uriInfo, entity));
|
||||||
|
|
||||||
String beforeCursor;
|
String beforeCursor;
|
||||||
String afterCursor = null;
|
String afterCursor = null;
|
||||||
@ -834,11 +883,12 @@ public abstract class EntityRepository<T extends EntityInterface> {
|
|||||||
|
|
||||||
List<T> entities = new ArrayList<>();
|
List<T> entities = new ArrayList<>();
|
||||||
for (String json : jsons) {
|
for (String json : jsons) {
|
||||||
T entity = setFieldsInternal(JsonUtils.readValue(json, entityClass), fields);
|
T entity = JsonUtils.readValue(json, entityClass);
|
||||||
setInheritedFields(entity, fields);
|
entities.add(entity);
|
||||||
clearFieldsInternal(entity, fields);
|
|
||||||
entities.add(withHref(uriInfo, entity));
|
|
||||||
}
|
}
|
||||||
|
setFieldsInBulk(fields, entities);
|
||||||
|
entities.forEach(entity -> withHref(uriInfo, entity));
|
||||||
|
|
||||||
int total = dao.listCount(filter);
|
int total = dao.listCount(filter);
|
||||||
|
|
||||||
String beforeCursor = null;
|
String beforeCursor = null;
|
||||||
@ -3943,13 +3993,8 @@ public abstract class EntityRepository<T extends EntityInterface> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fetchAndSetFields(List<T> entities, Fields fields) {
|
protected void fetchAndSetFields(List<T> entities, Fields fields) {
|
||||||
for (String field : fields) {
|
fieldFetchers.values().forEach(fetcher -> fetcher.accept(entities, fields));
|
||||||
BiConsumer<List<T>, Fields> fetcher = fieldFetchers.get(field);
|
|
||||||
if (fetcher != null) {
|
|
||||||
fetcher.accept(entities, fields);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fetchAndSetOwners(List<T> entities, Fields fields) {
|
private void fetchAndSetOwners(List<T> entities, Fields fields) {
|
||||||
@ -4008,6 +4053,18 @@ public abstract class EntityRepository<T extends EntityInterface> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void fetchAndSetChildren(List<T> entities, Fields fields) {
|
||||||
|
if (!fields.contains(FIELD_CHILDREN) || entities == null || entities.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<UUID, List<EntityReference>> childrenMap = batchFetchChildren(entities);
|
||||||
|
|
||||||
|
for (T entity : entities) {
|
||||||
|
entity.setChildren(childrenMap.get(entity.getId()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void fetchAndSetReviewers(List<T> entities, Fields fields) {
|
private void fetchAndSetReviewers(List<T> entities, Fields fields) {
|
||||||
if (!fields.contains(FIELD_REVIEWERS) || !supportsReviewers || entities.isEmpty()) {
|
if (!fields.contains(FIELD_REVIEWERS) || !supportsReviewers || entities.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
@ -4051,6 +4108,15 @@ public abstract class EntityRepository<T extends EntityInterface> {
|
|||||||
List<CollectionDAO.TagUsageDAO.TagLabelWithFQNHash> tags =
|
List<CollectionDAO.TagUsageDAO.TagLabelWithFQNHash> tags =
|
||||||
daoCollection.tagUsageDAO().getTagsInternalBatch(entityFQNs);
|
daoCollection.tagUsageDAO().getTagsInternalBatch(entityFQNs);
|
||||||
|
|
||||||
|
List<String> tagFQNs =
|
||||||
|
tags.stream()
|
||||||
|
.distinct()
|
||||||
|
.map(CollectionDAO.TagUsageDAO.TagLabelWithFQNHash::getTagFQN)
|
||||||
|
.toList();
|
||||||
|
Map<String, TagLabel> tagFQNLabelMap =
|
||||||
|
EntityUtil.toTagLabels(TagLabelUtil.getTags(tagFQNs).toArray(new Tag[0])).stream()
|
||||||
|
.collect(Collectors.toMap(TagLabel::getTagFQN, Function.identity()));
|
||||||
|
|
||||||
// Map from targetFQNHash to targetFQN
|
// Map from targetFQNHash to targetFQN
|
||||||
Map<String, String> fqnHashToFqnMap =
|
Map<String, String> fqnHashToFqnMap =
|
||||||
entityFQNs.stream().collect(Collectors.toMap(FullyQualifiedName::buildHash, fqn -> fqn));
|
entityFQNs.stream().collect(Collectors.toMap(FullyQualifiedName::buildHash, fqn -> fqn));
|
||||||
@ -4062,7 +4128,8 @@ public abstract class EntityRepository<T extends EntityInterface> {
|
|||||||
String targetFQN = fqnHashToFqnMap.get(targetFQNHash);
|
String targetFQN = fqnHashToFqnMap.get(targetFQNHash);
|
||||||
|
|
||||||
if (targetFQN != null) {
|
if (targetFQN != null) {
|
||||||
tagsMap.computeIfAbsent(targetFQN, k -> new ArrayList<>()).add(tagWithHash.toTagLabel());
|
TagLabel tagLabel = tagFQNLabelMap.get(tagWithHash.getTagFQN());
|
||||||
|
tagsMap.computeIfAbsent(targetFQN, k -> new ArrayList<>()).add(tagLabel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4152,6 +4219,36 @@ public abstract class EntityRepository<T extends EntityInterface> {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Map<UUID, List<EntityReference>> batchFetchChildren(List<T> entities) {
|
||||||
|
List<CollectionDAO.EntityRelationshipObject> records =
|
||||||
|
daoCollection
|
||||||
|
.relationshipDAO()
|
||||||
|
.findToBatch(
|
||||||
|
entityListToStrings(entities), Relationship.CONTAINS.ordinal(), entityType);
|
||||||
|
|
||||||
|
Map<UUID, List<EntityReference>> childrenMap = new HashMap<>();
|
||||||
|
|
||||||
|
if (CollectionUtils.isEmpty(records)) {
|
||||||
|
return childrenMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, EntityReference> idReferenceMap =
|
||||||
|
Entity.getEntityReferencesByIds(
|
||||||
|
records.get(0).getToEntity(),
|
||||||
|
records.stream().map(e -> UUID.fromString(e.getToId())).distinct().toList(),
|
||||||
|
ALL)
|
||||||
|
.stream()
|
||||||
|
.collect(Collectors.toMap(e -> e.getId().toString(), Function.identity()));
|
||||||
|
|
||||||
|
for (CollectionDAO.EntityRelationshipObject rec : records) {
|
||||||
|
UUID entityId = UUID.fromString(rec.getFromId());
|
||||||
|
EntityReference childrenRef = idReferenceMap.get(rec.getToId());
|
||||||
|
childrenMap.computeIfAbsent(entityId, k -> new ArrayList<>()).add(childrenRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
return childrenMap;
|
||||||
|
}
|
||||||
|
|
||||||
private List<String> entityListToStrings(List<T> entities) {
|
private List<String> entityListToStrings(List<T> entities) {
|
||||||
return entities.stream().map(EntityInterface::getId).map(UUID::toString).toList();
|
return entities.stream().map(EntityInterface::getId).map(UUID::toString).toList();
|
||||||
}
|
}
|
||||||
|
@ -41,6 +41,7 @@ import static org.openmetadata.service.util.EntityUtil.termReferenceMatch;
|
|||||||
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||||
|
import com.google.gson.Gson;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@ -57,6 +58,7 @@ import java.util.stream.Collectors;
|
|||||||
import javax.json.JsonPatch;
|
import javax.json.JsonPatch;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.collections4.CollectionUtils;
|
||||||
import org.apache.commons.lang3.tuple.ImmutablePair;
|
import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||||
import org.jdbi.v3.sqlobject.transaction.Transaction;
|
import org.jdbi.v3.sqlobject.transaction.Transaction;
|
||||||
import org.openmetadata.common.utils.CommonUtil;
|
import org.openmetadata.common.utils.CommonUtil;
|
||||||
@ -121,8 +123,10 @@ public class GlossaryTermRepository extends EntityRepository<GlossaryTerm> {
|
|||||||
UPDATE_FIELDS);
|
UPDATE_FIELDS);
|
||||||
supportsSearch = true;
|
supportsSearch = true;
|
||||||
renameAllowed = true;
|
renameAllowed = true;
|
||||||
fieldFetchers.put("relatedTerms", this::fetchAndSetRelatedTerms);
|
|
||||||
fieldFetchers.put("parent", this::fetchAndSetParentOrGlossary);
|
fieldFetchers.put("parent", this::fetchAndSetParentOrGlossary);
|
||||||
|
fieldFetchers.put("relatedTerms", this::fetchAndSetRelatedTerms);
|
||||||
|
fieldFetchers.put("usageCount", this::fetchAndSetUsageCount);
|
||||||
|
fieldFetchers.put("childrenCount", this::fetchAndSetChildrenCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -136,6 +140,15 @@ public class GlossaryTermRepository extends EntityRepository<GlossaryTerm> {
|
|||||||
fields.contains("childrenCount") ? getChildrenCount(entity) : entity.getChildrenCount());
|
fields.contains("childrenCount") ? getChildrenCount(entity) : entity.getChildrenCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFieldsInBulk(Fields fields, List<GlossaryTerm> entities) {
|
||||||
|
fetchAndSetFields(entities, fields);
|
||||||
|
setInheritedFields(entities, fields);
|
||||||
|
for (GlossaryTerm entity : entities) {
|
||||||
|
clearFieldsInternal(entity, fields);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void clearFields(GlossaryTerm entity, Fields fields) {
|
public void clearFields(GlossaryTerm entity, Fields fields) {
|
||||||
entity.setRelatedTerms(fields.contains("relatedTerms") ? entity.getRelatedTerms() : null);
|
entity.setRelatedTerms(fields.contains("relatedTerms") ? entity.getRelatedTerms() : null);
|
||||||
@ -151,6 +164,25 @@ public class GlossaryTermRepository extends EntityRepository<GlossaryTerm> {
|
|||||||
inheritReviewers(glossaryTerm, fields, parent);
|
inheritReviewers(glossaryTerm, fields, parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setInheritedFields(List<GlossaryTerm> glossaryTerms, Fields fields) {
|
||||||
|
List<? extends EntityInterface> parents =
|
||||||
|
getParentEntities(glossaryTerms, "owners,domain,reviewers");
|
||||||
|
Map<UUID, EntityInterface> parentMap =
|
||||||
|
parents.stream().collect(Collectors.toMap(EntityInterface::getId, e -> e));
|
||||||
|
for (GlossaryTerm glossaryTerm : glossaryTerms) {
|
||||||
|
EntityInterface parent = null;
|
||||||
|
if (glossaryTerm.getParent() != null) {
|
||||||
|
parent = parentMap.get(glossaryTerm.getParent().getId());
|
||||||
|
} else if (glossaryTerm.getGlossary() != null) {
|
||||||
|
parent = parentMap.get(glossaryTerm.getGlossary().getId());
|
||||||
|
}
|
||||||
|
inheritOwners(glossaryTerm, fields, parent);
|
||||||
|
inheritDomain(glossaryTerm, fields, parent);
|
||||||
|
inheritReviewers(glossaryTerm, fields, parent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private Integer getUsageCount(GlossaryTerm term) {
|
private Integer getUsageCount(GlossaryTerm term) {
|
||||||
return daoCollection
|
return daoCollection
|
||||||
.tagUsageDAO()
|
.tagUsageDAO()
|
||||||
@ -218,6 +250,24 @@ public class GlossaryTermRepository extends EntityRepository<GlossaryTerm> {
|
|||||||
.withReviewers(reviewers);
|
.withReviewers(reviewers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void storeEntities(List<GlossaryTerm> entities) {
|
||||||
|
List<GlossaryTerm> entitiesToStore = new ArrayList<>();
|
||||||
|
Gson gson = new Gson();
|
||||||
|
for (GlossaryTerm entity : entities) {
|
||||||
|
EntityReference glossary = entity.getGlossary();
|
||||||
|
EntityReference parentTerm = entity.getParent();
|
||||||
|
List<EntityReference> reviewers = entity.getReviewers();
|
||||||
|
|
||||||
|
String jsonCopy = gson.toJson(entity.withGlossary(null).withParent(null).withReviewers(null));
|
||||||
|
entitiesToStore.add(gson.fromJson(jsonCopy, GlossaryTerm.class));
|
||||||
|
|
||||||
|
// restore the relationships
|
||||||
|
entity.withGlossary(glossary).withParent(parentTerm).withReviewers(reviewers);
|
||||||
|
}
|
||||||
|
storeMany(entitiesToStore);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void storeRelationships(GlossaryTerm entity) {
|
public void storeRelationships(GlossaryTerm entity) {
|
||||||
addGlossaryRelationship(entity);
|
addGlossaryRelationship(entity);
|
||||||
@ -625,6 +675,24 @@ public class GlossaryTermRepository extends EntityRepository<GlossaryTerm> {
|
|||||||
: Entity.getEntity(entity.getGlossary(), fields, Include.ALL);
|
: Entity.getEntity(entity.getGlossary(), fields, Include.ALL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<EntityInterface> getParentEntities(List<GlossaryTerm> entities, String fields) {
|
||||||
|
List<EntityInterface> result = new ArrayList<>();
|
||||||
|
if (CollectionUtils.isEmpty(entities)) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
List<EntityReference> parents =
|
||||||
|
entities.stream().map(GlossaryTerm::getParent).filter(Objects::nonNull).distinct().toList();
|
||||||
|
result.addAll(Entity.getEntities(parents, fields, Include.ALL));
|
||||||
|
List<EntityReference> glossaries =
|
||||||
|
entities.stream()
|
||||||
|
.map(GlossaryTerm::getGlossary)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.distinct()
|
||||||
|
.toList();
|
||||||
|
result.addAll(Entity.getEntities(glossaries, fields, Include.ALL));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
private void addGlossaryRelationship(GlossaryTerm term) {
|
private void addGlossaryRelationship(GlossaryTerm term) {
|
||||||
Relationship relationship = term.getParent() != null ? Relationship.HAS : Relationship.CONTAINS;
|
Relationship relationship = term.getParent() != null ? Relationship.HAS : Relationship.CONTAINS;
|
||||||
addRelationship(
|
addRelationship(
|
||||||
@ -841,11 +909,12 @@ public class GlossaryTermRepository extends EntityRepository<GlossaryTerm> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void fetchAndSetParentOrGlossary(List<GlossaryTerm> terms, Fields fields) {
|
private void fetchAndSetParentOrGlossary(List<GlossaryTerm> terms, Fields fields) {
|
||||||
if (terms == null || terms.isEmpty() || (!fields.contains("parent"))) {
|
if (terms == null || terms.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> entityIds = terms.stream().map(GlossaryTerm::getId).map(UUID::toString).toList();
|
List<String> entityIds =
|
||||||
|
terms.stream().map(GlossaryTerm::getId).map(UUID::toString).distinct().toList();
|
||||||
|
|
||||||
List<CollectionDAO.EntityRelationshipObject> parentRecords =
|
List<CollectionDAO.EntityRelationshipObject> parentRecords =
|
||||||
daoCollection
|
daoCollection
|
||||||
@ -919,6 +988,39 @@ public class GlossaryTermRepository extends EntityRepository<GlossaryTerm> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void fetchAndSetUsageCount(List<GlossaryTerm> entities, Fields fields) {
|
||||||
|
if (!fields.contains("usageCount") || entities.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// TODO: modify to use a single db query
|
||||||
|
for (GlossaryTerm entity : entities) {
|
||||||
|
entity.withUsageCount(getUsageCount(entity));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fetchAndSetChildrenCount(List<GlossaryTerm> entities, Fields fields) {
|
||||||
|
if (!fields.contains("childrenCount") || entities.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<String> termIds =
|
||||||
|
entities.stream().map(GlossaryTerm::getId).map(UUID::toString).distinct().toList();
|
||||||
|
|
||||||
|
Map<UUID, Integer> termIdCountMap =
|
||||||
|
daoCollection
|
||||||
|
.relationshipDAO()
|
||||||
|
.countFindTo(termIds, GLOSSARY_TERM, Relationship.CONTAINS.ordinal(), GLOSSARY_TERM)
|
||||||
|
.stream()
|
||||||
|
.collect(
|
||||||
|
Collectors.toMap(
|
||||||
|
CollectionDAO.EntityRelationshipCount::getId,
|
||||||
|
CollectionDAO.EntityRelationshipCount::getCount));
|
||||||
|
|
||||||
|
for (GlossaryTerm entity : entities) {
|
||||||
|
entity.setChildrenCount(
|
||||||
|
termIdCountMap.getOrDefault(entity.getId(), entity.getChildrenCount()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Handles entity updated from PUT and POST operation. */
|
/** Handles entity updated from PUT and POST operation. */
|
||||||
public class GlossaryTermUpdater extends EntityUpdater {
|
public class GlossaryTermUpdater extends EntityUpdater {
|
||||||
public GlossaryTermUpdater(GlossaryTerm original, GlossaryTerm updated, Operation operation) {
|
public GlossaryTermUpdater(GlossaryTerm original, GlossaryTerm updated, Operation operation) {
|
||||||
|
@ -416,6 +416,36 @@ public class GlossaryTermResource extends EntityResource<GlossaryTerm, GlossaryT
|
|||||||
return create(uriInfo, securityContext, term);
|
return create(uriInfo, securityContext, term);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("/createMany")
|
||||||
|
@Operation(
|
||||||
|
operationId = "createManyGlossaryTerm",
|
||||||
|
summary = "Create multiple glossary terms at once",
|
||||||
|
description = "Create multiple new glossary terms.",
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "200",
|
||||||
|
description = "The glossary term",
|
||||||
|
content =
|
||||||
|
@Content(
|
||||||
|
mediaType = "application/json",
|
||||||
|
schema = @Schema(implementation = GlossaryTerm.class))),
|
||||||
|
@ApiResponse(responseCode = "400", description = "Bad request")
|
||||||
|
})
|
||||||
|
public Response createMany(
|
||||||
|
@Context UriInfo uriInfo,
|
||||||
|
@Context SecurityContext securityContext,
|
||||||
|
@Valid List<CreateGlossaryTerm> creates) {
|
||||||
|
List<GlossaryTerm> terms =
|
||||||
|
creates.stream()
|
||||||
|
.map(
|
||||||
|
create ->
|
||||||
|
mapper.createToEntity(create, securityContext.getUserPrincipal().getName()))
|
||||||
|
.toList();
|
||||||
|
List<GlossaryTerm> result = repository.createMany(uriInfo, terms);
|
||||||
|
return Response.ok(result).build();
|
||||||
|
}
|
||||||
|
|
||||||
@PATCH
|
@PATCH
|
||||||
@Path("/{id}")
|
@Path("/{id}")
|
||||||
@Operation(
|
@Operation(
|
||||||
|
@ -52,6 +52,10 @@ public class TagLabelUtil {
|
|||||||
return Entity.getEntityByName(Entity.TAG, tagFqn, "", NON_DELETED);
|
return Entity.getEntityByName(Entity.TAG, tagFqn, "", NON_DELETED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static List<Tag> getTags(List<String> tagFQNs) {
|
||||||
|
return Entity.getEntityByNames(Entity.TAG, tagFQNs, "", NON_DELETED);
|
||||||
|
}
|
||||||
|
|
||||||
public static Glossary getGlossary(String glossaryName) {
|
public static Glossary getGlossary(String glossaryName) {
|
||||||
return Entity.getEntityByName(Entity.GLOSSARY, glossaryName, "", NON_DELETED);
|
return Entity.getEntityByName(Entity.GLOSSARY, glossaryName, "", NON_DELETED);
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ import static java.util.Collections.emptyList;
|
|||||||
import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
|
import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
|
||||||
import static javax.ws.rs.core.Response.Status.FORBIDDEN;
|
import static javax.ws.rs.core.Response.Status.FORBIDDEN;
|
||||||
import static javax.ws.rs.core.Response.Status.NOT_FOUND;
|
import static javax.ws.rs.core.Response.Status.NOT_FOUND;
|
||||||
|
import static javax.ws.rs.core.Response.Status.OK;
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
import static org.openmetadata.common.utils.CommonUtil.listOf;
|
import static org.openmetadata.common.utils.CommonUtil.listOf;
|
||||||
import static org.openmetadata.common.utils.CommonUtil.listOrEmpty;
|
import static org.openmetadata.common.utils.CommonUtil.listOrEmpty;
|
||||||
@ -56,6 +57,8 @@ import java.util.Objects;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
import javax.ws.rs.client.WebTarget;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.http.client.HttpResponseException;
|
import org.apache.http.client.HttpResponseException;
|
||||||
@ -1057,6 +1060,21 @@ public class GlossaryTermResourceTest extends EntityResourceTest<GlossaryTerm, C
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void test_performance_listEntities() throws IOException {
|
||||||
|
Glossary glossary = createGlossary("词汇表三", null, null);
|
||||||
|
List<Map<String, Object>> result =
|
||||||
|
createTerms(glossary, IntStream.range(0, 500).mapToObj(i -> "term" + i).toList());
|
||||||
|
|
||||||
|
Map<String, String> queryParams = new HashMap<>();
|
||||||
|
queryParams.put("fields", "children,relatedTerms,reviewers,tags");
|
||||||
|
queryParams.put("limit", "10000");
|
||||||
|
queryParams.put("directChildrenOf", "词汇表三");
|
||||||
|
ResultList<GlossaryTerm> list =
|
||||||
|
assertTimeout(Duration.ofSeconds(3), () -> listEntities(queryParams, ADMIN_AUTH_HEADERS));
|
||||||
|
assertEquals(result.size(), list.getData().size());
|
||||||
|
}
|
||||||
|
|
||||||
public GlossaryTerm createTerm(Glossary glossary, GlossaryTerm parent, String termName)
|
public GlossaryTerm createTerm(Glossary glossary, GlossaryTerm parent, String termName)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
return createTerm(glossary, parent, termName, glossary.getReviewers());
|
return createTerm(glossary, parent, termName, glossary.getReviewers());
|
||||||
@ -1086,6 +1104,25 @@ public class GlossaryTermResourceTest extends EntityResourceTest<GlossaryTerm, C
|
|||||||
return createAndCheckEntity(createGlossaryTerm, createdBy);
|
return createAndCheckEntity(createGlossaryTerm, createdBy);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<Map<String, Object>> createTerms(Glossary glossary, List<String> termNames)
|
||||||
|
throws HttpResponseException {
|
||||||
|
String pathUrl = "/createMany/";
|
||||||
|
String glossaryFqn = getFqn(glossary);
|
||||||
|
WebTarget target = getCollection().path(pathUrl);
|
||||||
|
List<CreateGlossaryTerm> createGlossaryTerms =
|
||||||
|
termNames.stream()
|
||||||
|
.map(
|
||||||
|
name ->
|
||||||
|
createRequest(name, "d", "", null)
|
||||||
|
.withRelatedTerms(null)
|
||||||
|
.withSynonyms(List.of("performance1", "performance2"))
|
||||||
|
.withStyle(new Style().withColor("#FF5733").withIconURL("https://img"))
|
||||||
|
.withGlossary(glossaryFqn))
|
||||||
|
.toList();
|
||||||
|
return TestUtils.post(
|
||||||
|
target, createGlossaryTerms, List.class, OK.getStatusCode(), ADMIN_AUTH_HEADERS);
|
||||||
|
}
|
||||||
|
|
||||||
public void assertContains(List<GlossaryTerm> expectedTerms, List<GlossaryTerm> actualTerms)
|
public void assertContains(List<GlossaryTerm> expectedTerms, List<GlossaryTerm> actualTerms)
|
||||||
throws HttpResponseException {
|
throws HttpResponseException {
|
||||||
assertEquals(expectedTerms.size(), actualTerms.size());
|
assertEquals(expectedTerms.size(), actualTerms.size());
|
||||||
@ -1341,6 +1378,7 @@ public class GlossaryTermResourceTest extends EntityResourceTest<GlossaryTerm, C
|
|||||||
// List children glossary terms with term1 as the parent and getting immediate children only
|
// List children glossary terms with term1 as the parent and getting immediate children only
|
||||||
Map<String, String> queryParams = new HashMap<>();
|
Map<String, String> queryParams = new HashMap<>();
|
||||||
queryParams.put("directChildrenOf", term1.getFullyQualifiedName());
|
queryParams.put("directChildrenOf", term1.getFullyQualifiedName());
|
||||||
|
queryParams.put("fields", "childrenCount,children");
|
||||||
List<GlossaryTerm> children = listEntities(queryParams, ADMIN_AUTH_HEADERS).getData();
|
List<GlossaryTerm> children = listEntities(queryParams, ADMIN_AUTH_HEADERS).getData();
|
||||||
|
|
||||||
assertEquals(term1.getChildren().size(), children.size());
|
assertEquals(term1.getChildren().size(), children.size());
|
||||||
@ -1348,10 +1386,21 @@ public class GlossaryTermResourceTest extends EntityResourceTest<GlossaryTerm, C
|
|||||||
for (GlossaryTerm responseChild : children) {
|
for (GlossaryTerm responseChild : children) {
|
||||||
assertTrue(
|
assertTrue(
|
||||||
responseChild.getFullyQualifiedName().startsWith(responseChild.getFullyQualifiedName()));
|
responseChild.getFullyQualifiedName().startsWith(responseChild.getFullyQualifiedName()));
|
||||||
|
if (responseChild.getChildren() == null) {
|
||||||
|
assertNull(responseChild.getChildrenCount());
|
||||||
|
} else {
|
||||||
|
assertEquals(responseChild.getChildren().size(), responseChild.getChildrenCount());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GlossaryTerm response = getEntity(term1.getId(), "childrenCount", ADMIN_AUTH_HEADERS);
|
GlossaryTerm response = getEntity(term1.getId(), "childrenCount", ADMIN_AUTH_HEADERS);
|
||||||
assertEquals(term1.getChildren().size(), response.getChildrenCount());
|
assertEquals(term1.getChildren().size(), response.getChildrenCount());
|
||||||
|
|
||||||
|
queryParams = new HashMap<>();
|
||||||
|
queryParams.put("directChildrenOf", glossary1.getFullyQualifiedName());
|
||||||
|
queryParams.put("fields", "childrenCount");
|
||||||
|
children = listEntities(queryParams, ADMIN_AUTH_HEADERS).getData();
|
||||||
|
assertEquals(term1.getChildren().size(), children.get(0).getChildrenCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Glossary createGlossary(
|
public Glossary createGlossary(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user