diff --git a/service/src/main/java/bio/terra/tanagra/app/controller/objmapping/FromApiUtils.java b/service/src/main/java/bio/terra/tanagra/app/controller/objmapping/FromApiUtils.java index 78ae9e107..ccc8d03a8 100644 --- a/service/src/main/java/bio/terra/tanagra/app/controller/objmapping/FromApiUtils.java +++ b/service/src/main/java/bio/terra/tanagra/app/controller/objmapping/FromApiUtils.java @@ -7,20 +7,7 @@ import bio.terra.tanagra.api.field.HierarchyPathField; import bio.terra.tanagra.api.field.RelatedEntityIdCountField; import bio.terra.tanagra.api.field.ValueDisplayField; -import bio.terra.tanagra.api.filter.AttributeFilter; -import bio.terra.tanagra.api.filter.BooleanAndOrFilter; -import bio.terra.tanagra.api.filter.BooleanNotFilter; -import bio.terra.tanagra.api.filter.EntityFilter; -import bio.terra.tanagra.api.filter.GroupHasItemsFilter; -import bio.terra.tanagra.api.filter.HierarchyHasAncestorFilter; -import bio.terra.tanagra.api.filter.HierarchyHasParentFilter; -import bio.terra.tanagra.api.filter.HierarchyIsMemberFilter; -import bio.terra.tanagra.api.filter.HierarchyIsRootFilter; -import bio.terra.tanagra.api.filter.ItemInGroupFilter; -import bio.terra.tanagra.api.filter.OccurrenceForPrimaryFilter; -import bio.terra.tanagra.api.filter.PrimaryWithCriteriaFilter; -import bio.terra.tanagra.api.filter.RelationshipFilter; -import bio.terra.tanagra.api.filter.TextSearchFilter; +import bio.terra.tanagra.api.filter.*; import bio.terra.tanagra.api.query.PageMarker; import bio.terra.tanagra.api.query.list.ListQueryRequest; import bio.terra.tanagra.api.query.list.OrderBy; @@ -136,6 +123,8 @@ public static EntityFilter fromApiObject(ApiFilter apiFilter, Entity entity, Und return new HierarchyIsRootFilter(underlay, entity, hierarchy); case IS_MEMBER: return new HierarchyIsMemberFilter(underlay, entity, hierarchy); + case IS_LEAF: + return new HierarchyIsLeafFilter(underlay, entity, hierarchy); case CHILD_OF: return new HierarchyHasParentFilter( underlay, diff --git a/service/src/main/resources/api/service_openapi.yaml b/service/src/main/resources/api/service_openapi.yaml index 99617b03c..15d4674b7 100644 --- a/service/src/main/resources/api/service_openapi.yaml +++ b/service/src/main/resources/api/service_openapi.yaml @@ -1770,7 +1770,7 @@ components: type: string operator: type: string - enum: ["CHILD_OF", "DESCENDANT_OF_INCLUSIVE", "IS_ROOT", "IS_MEMBER"] + enum: ["CHILD_OF", "DESCENDANT_OF_INCLUSIVE", "IS_ROOT", "IS_MEMBER", "IS_LEAF"] values: type: array items: diff --git a/underlay/src/main/java/bio/terra/tanagra/api/filter/HierarchyIsLeafFilter.java b/underlay/src/main/java/bio/terra/tanagra/api/filter/HierarchyIsLeafFilter.java new file mode 100644 index 000000000..4e5374650 --- /dev/null +++ b/underlay/src/main/java/bio/terra/tanagra/api/filter/HierarchyIsLeafFilter.java @@ -0,0 +1,30 @@ +package bio.terra.tanagra.api.filter; + +import bio.terra.tanagra.underlay.Underlay; +import bio.terra.tanagra.underlay.entitymodel.Entity; +import bio.terra.tanagra.underlay.entitymodel.Hierarchy; + +public class HierarchyIsLeafFilter extends EntityFilter { + private final Underlay underlay; + private final Entity entity; + private final Hierarchy hierarchy; + + public HierarchyIsLeafFilter(Underlay underlay, Entity entity, Hierarchy hierarchy) { + this.underlay = underlay; + this.entity = entity; + this.hierarchy = hierarchy; + } + + public Underlay getUnderlay() { + return underlay; + } + + @Override + public Entity getEntity() { + return entity; + } + + public Hierarchy getHierarchy() { + return hierarchy; + } +} diff --git a/underlay/src/main/java/bio/terra/tanagra/query/bigquery/translator/BQApiTranslator.java b/underlay/src/main/java/bio/terra/tanagra/query/bigquery/translator/BQApiTranslator.java index c959d3991..30316d368 100644 --- a/underlay/src/main/java/bio/terra/tanagra/query/bigquery/translator/BQApiTranslator.java +++ b/underlay/src/main/java/bio/terra/tanagra/query/bigquery/translator/BQApiTranslator.java @@ -7,15 +7,7 @@ import bio.terra.tanagra.api.field.HierarchyNumChildrenField; import bio.terra.tanagra.api.field.HierarchyPathField; import bio.terra.tanagra.api.field.RelatedEntityIdCountField; -import bio.terra.tanagra.api.filter.AttributeFilter; -import bio.terra.tanagra.api.filter.HierarchyHasAncestorFilter; -import bio.terra.tanagra.api.filter.HierarchyHasParentFilter; -import bio.terra.tanagra.api.filter.HierarchyIsMemberFilter; -import bio.terra.tanagra.api.filter.HierarchyIsRootFilter; -import bio.terra.tanagra.api.filter.PrimaryWithCriteriaFilter; -import bio.terra.tanagra.api.filter.RelationshipFilter; -import bio.terra.tanagra.api.filter.TemporalPrimaryFilter; -import bio.terra.tanagra.api.filter.TextSearchFilter; +import bio.terra.tanagra.api.filter.*; import bio.terra.tanagra.api.filter.TextSearchFilter.TextSearchOperator; import bio.terra.tanagra.query.bigquery.translator.field.BQAttributeFieldTranslator; import bio.terra.tanagra.query.bigquery.translator.field.BQCountDistinctFieldTranslator; @@ -27,6 +19,7 @@ import bio.terra.tanagra.query.bigquery.translator.filter.BQAttributeFilterTranslator; import bio.terra.tanagra.query.bigquery.translator.filter.BQHierarchyHasAncestorFilterTranslator; import bio.terra.tanagra.query.bigquery.translator.filter.BQHierarchyHasParentFilterTranslator; +import bio.terra.tanagra.query.bigquery.translator.filter.BQHierarchyIsLeafFilterTranslator; import bio.terra.tanagra.query.bigquery.translator.filter.BQHierarchyIsMemberFilterTranslator; import bio.terra.tanagra.query.bigquery.translator.filter.BQHierarchyIsRootFilterTranslator; import bio.terra.tanagra.query.bigquery.translator.filter.BQPrimaryWithCriteriaFilterTranslator; @@ -88,6 +81,11 @@ public ApiFilterTranslator translator(HierarchyHasParentFilter hierarchyHasParen return new BQHierarchyHasParentFilterTranslator(this, hierarchyHasParentFilter); } + @Override + public ApiFilterTranslator translator(HierarchyIsLeafFilter hierarchyIsLeafFilter) { + return new BQHierarchyIsLeafFilterTranslator(this, hierarchyIsLeafFilter); + } + @Override public ApiFilterTranslator translator(HierarchyIsMemberFilter hierarchyIsMemberFilter) { return new BQHierarchyIsMemberFilterTranslator(this, hierarchyIsMemberFilter); diff --git a/underlay/src/main/java/bio/terra/tanagra/query/bigquery/translator/filter/BQHierarchyIsLeafFilterTranslator.java b/underlay/src/main/java/bio/terra/tanagra/query/bigquery/translator/filter/BQHierarchyIsLeafFilterTranslator.java new file mode 100644 index 000000000..a9c068c31 --- /dev/null +++ b/underlay/src/main/java/bio/terra/tanagra/query/bigquery/translator/filter/BQHierarchyIsLeafFilterTranslator.java @@ -0,0 +1,40 @@ +package bio.terra.tanagra.query.bigquery.translator.filter; + +import bio.terra.tanagra.api.filter.*; +import bio.terra.tanagra.api.shared.*; +import bio.terra.tanagra.query.sql.SqlField; +import bio.terra.tanagra.query.sql.SqlParams; +import bio.terra.tanagra.query.sql.translator.ApiFilterTranslator; +import bio.terra.tanagra.query.sql.translator.ApiTranslator; +import bio.terra.tanagra.underlay.entitymodel.Attribute; +import bio.terra.tanagra.underlay.indextable.ITEntityMain; + +public class BQHierarchyIsLeafFilterTranslator extends ApiFilterTranslator { + private final HierarchyIsLeafFilter hierarchyIsLeafFilter; + + public BQHierarchyIsLeafFilterTranslator( + ApiTranslator apiTranslator, HierarchyIsLeafFilter hierarchyIsLeafFilter) { + super(apiTranslator); + this.hierarchyIsLeafFilter = hierarchyIsLeafFilter; + } + + @Override + public String buildSql(SqlParams sqlParams, String tableAlias) { + ITEntityMain indexTable = + hierarchyIsLeafFilter + .getUnderlay() + .getIndexSchema() + .getEntityMain(hierarchyIsLeafFilter.getEntity().getName()); + + // IS_LEAF means num_children=0. + SqlField numChildrenField = + indexTable.getHierarchyNumChildrenField(hierarchyIsLeafFilter.getHierarchy().getName()); + return apiTranslator.binaryFilterSql( + numChildrenField, BinaryOperator.EQUALS, Literal.forInt64(0L), tableAlias, sqlParams); + } + + @Override + public boolean isFilterOnAttribute(Attribute attribute) { + return false; + } +} diff --git a/underlay/src/main/java/bio/terra/tanagra/query/sql/translator/ApiTranslator.java b/underlay/src/main/java/bio/terra/tanagra/query/sql/translator/ApiTranslator.java index 64381398d..699937d76 100644 --- a/underlay/src/main/java/bio/terra/tanagra/query/sql/translator/ApiTranslator.java +++ b/underlay/src/main/java/bio/terra/tanagra/query/sql/translator/ApiTranslator.java @@ -8,21 +8,7 @@ import bio.terra.tanagra.api.field.HierarchyPathField; import bio.terra.tanagra.api.field.RelatedEntityIdCountField; import bio.terra.tanagra.api.field.ValueDisplayField; -import bio.terra.tanagra.api.filter.AttributeFilter; -import bio.terra.tanagra.api.filter.BooleanAndOrFilter; -import bio.terra.tanagra.api.filter.BooleanNotFilter; -import bio.terra.tanagra.api.filter.EntityFilter; -import bio.terra.tanagra.api.filter.GroupHasItemsFilter; -import bio.terra.tanagra.api.filter.HierarchyHasAncestorFilter; -import bio.terra.tanagra.api.filter.HierarchyHasParentFilter; -import bio.terra.tanagra.api.filter.HierarchyIsMemberFilter; -import bio.terra.tanagra.api.filter.HierarchyIsRootFilter; -import bio.terra.tanagra.api.filter.ItemInGroupFilter; -import bio.terra.tanagra.api.filter.OccurrenceForPrimaryFilter; -import bio.terra.tanagra.api.filter.PrimaryWithCriteriaFilter; -import bio.terra.tanagra.api.filter.RelationshipFilter; -import bio.terra.tanagra.api.filter.TemporalPrimaryFilter; -import bio.terra.tanagra.api.filter.TextSearchFilter; +import bio.terra.tanagra.api.filter.*; import bio.terra.tanagra.api.shared.BinaryOperator; import bio.terra.tanagra.api.shared.Literal; import bio.terra.tanagra.api.shared.NaryOperator; @@ -310,6 +296,8 @@ default ApiFilterTranslator translator(BooleanNotFilter booleanNotFilter) { ApiFilterTranslator translator(HierarchyHasParentFilter hierarchyHasParentFilter); + ApiFilterTranslator translator(HierarchyIsLeafFilter hierarchyIsLeafFilter); + ApiFilterTranslator translator(HierarchyIsMemberFilter hierarchyIsMemberFilter); ApiFilterTranslator translator(HierarchyIsRootFilter hierarchyIsRootFilter); @@ -345,6 +333,8 @@ default ApiFilterTranslator translator(EntityFilter entityFilter) { return translator((HierarchyHasAncestorFilter) entityFilter); } else if (entityFilter instanceof HierarchyHasParentFilter) { return translator((HierarchyHasParentFilter) entityFilter); + } else if (entityFilter instanceof HierarchyIsLeafFilter) { + return translator((HierarchyIsLeafFilter) entityFilter); } else if (entityFilter instanceof HierarchyIsMemberFilter) { return translator((HierarchyIsMemberFilter) entityFilter); } else if (entityFilter instanceof HierarchyIsRootFilter) { diff --git a/underlay/src/test/java/bio/terra/tanagra/query/bigquery/sqlbuilding/filter/BQHierarchyFilterTest.java b/underlay/src/test/java/bio/terra/tanagra/query/bigquery/sqlbuilding/filter/BQHierarchyFilterTest.java index 21dcadb4a..9ce8f89fd 100644 --- a/underlay/src/test/java/bio/terra/tanagra/query/bigquery/sqlbuilding/filter/BQHierarchyFilterTest.java +++ b/underlay/src/test/java/bio/terra/tanagra/query/bigquery/sqlbuilding/filter/BQHierarchyFilterTest.java @@ -1,10 +1,7 @@ package bio.terra.tanagra.query.bigquery.sqlbuilding.filter; import bio.terra.tanagra.api.field.AttributeField; -import bio.terra.tanagra.api.filter.HierarchyHasAncestorFilter; -import bio.terra.tanagra.api.filter.HierarchyHasParentFilter; -import bio.terra.tanagra.api.filter.HierarchyIsMemberFilter; -import bio.terra.tanagra.api.filter.HierarchyIsRootFilter; +import bio.terra.tanagra.api.filter.*; import bio.terra.tanagra.api.query.list.ListQueryRequest; import bio.terra.tanagra.api.query.list.ListQueryResult; import bio.terra.tanagra.api.shared.Literal; @@ -114,6 +111,21 @@ void hierarchyHasParentFilter() throws IOException { "hierarchyHasParentFilterIn", listQueryResult.getSql(), entityMainTable, childParentTable); } + @Test + void hierarchyIsLeafFilter() throws IOException { + Entity entity = underlay.getEntity("condition"); + HierarchyIsLeafFilter hierarchyIsLeafFilter = + new HierarchyIsLeafFilter(underlay, entity, entity.getHierarchy(Hierarchy.DEFAULT_NAME)); + AttributeField simpleAttribute = + new AttributeField(underlay, entity, entity.getAttribute("name"), false); + ListQueryResult listQueryResult = + bqQueryRunner.run( + ListQueryRequest.dryRunAgainstIndexData( + underlay, entity, List.of(simpleAttribute), hierarchyIsLeafFilter, null, null)); + BQTable table = underlay.getIndexSchema().getEntityMain(entity.getName()).getTablePointer(); + assertSqlMatchesWithTableNameOnly("hierarchyIsLeafFilter", listQueryResult.getSql(), table); + } + @Test void hierarchyIsMemberFilter() throws IOException { Entity entity = underlay.getEntity("condition"); diff --git a/underlay/src/test/resources/sql/BQHierarchyFilterTest/hierarchyIsLeafFilter.sql b/underlay/src/test/resources/sql/BQHierarchyFilterTest/hierarchyIsLeafFilter.sql new file mode 100644 index 000000000..c8f5d7a23 --- /dev/null +++ b/underlay/src/test/resources/sql/BQHierarchyFilterTest/hierarchyIsLeafFilter.sql @@ -0,0 +1,7 @@ + + SELECT + name + FROM + ${ENT_condition} + WHERE + T_NUMCH_default = @val0