From 8c3395294f5e71a4f4f7f9efec66ab11c842c131 Mon Sep 17 00:00:00 2001 From: Luciano Fiandesio Date: Wed, 18 Dec 2024 11:01:29 +0100 Subject: [PATCH] WIP - spotless --- .../dhis/analytics/common/CTEContext.java | 87 +++-- .../ProgramIndicatorSubqueryBuilder.java | 22 +- .../AbstractJdbcEventAnalyticsManager.java | 5 +- .../data/JdbcEnrollmentAnalyticsManager.java | 323 ++++++++++-------- ...efaultProgramIndicatorSubqueryBuilder.java | 80 ++--- 5 files changed, 286 insertions(+), 231 deletions(-) diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/common/CTEContext.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/common/CTEContext.java index c85123d1f3f..cc69a491906 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/common/CTEContext.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/common/CTEContext.java @@ -1,48 +1,71 @@ +/* + * Copyright (c) 2004-2024, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ package org.hisp.dhis.analytics.common; - import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; public class CTEContext { - private final Map cteDefinitions = new LinkedHashMap<>(); - private final Map columnMappings = new HashMap<>(); + private final Map cteDefinitions = new LinkedHashMap<>(); + private final Map columnMappings = new HashMap<>(); - public void addCTE(String cteName, String cteDefinition) { - cteDefinitions.put(cteName, cteDefinition); - } + public void addCTE(String cteName, String cteDefinition) { + cteDefinitions.put(cteName, cteDefinition); + } - public void addColumnMapping(String originalColumn, String cteReference) { - columnMappings.put(originalColumn, cteReference); - } + public void addColumnMapping(String originalColumn, String cteReference) { + columnMappings.put(originalColumn, cteReference); + } - public String getCTEDefinition() { - if (cteDefinitions.isEmpty()) { - return ""; - } - - StringBuilder sb = new StringBuilder("WITH "); - boolean first = true; - for (Map.Entry entry : cteDefinitions.entrySet()) { - if (!first) { - sb.append(", "); - } - sb.append(entry.getKey()) - .append(" AS (") - .append(entry.getValue()) - .append(")"); - first = false; - } - return sb.toString(); + public String getCTEDefinition() { + if (cteDefinitions.isEmpty()) { + return ""; } - public Set getCTENames() { - return cteDefinitions.keySet(); + StringBuilder sb = new StringBuilder("WITH "); + boolean first = true; + for (Map.Entry entry : cteDefinitions.entrySet()) { + if (!first) { + sb.append(", "); + } + sb.append(entry.getKey()).append(" AS (").append(entry.getValue()).append(")"); + first = false; } + return sb.toString(); + } - public String getColumnMapping(String columnId) { - return columnMappings.getOrDefault(columnId, columnId); - } + public Set getCTENames() { + return cteDefinitions.keySet(); + } + + public String getColumnMapping(String columnId) { + return columnMappings.getOrDefault(columnId, columnId); + } } diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/common/ProgramIndicatorSubqueryBuilder.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/common/ProgramIndicatorSubqueryBuilder.java index 745c3bb29cb..4796431d160 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/common/ProgramIndicatorSubqueryBuilder.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/common/ProgramIndicatorSubqueryBuilder.java @@ -103,17 +103,17 @@ String getAggregateClauseForProgramIndicator( Date latestDate); void contributeCTE( - ProgramIndicator programIndicator, - AnalyticsType outerSqlEntity, - Date earliestStartDate, - Date latestDate, - CTEContext cteContext); + ProgramIndicator programIndicator, + AnalyticsType outerSqlEntity, + Date earliestStartDate, + Date latestDate, + CTEContext cteContext); void contributeCTE( - ProgramIndicator programIndicator, - RelationshipType relationshipType, - AnalyticsType outerSqlEntity, - Date earliestStartDate, - Date latestDate, - CTEContext cteContext); + ProgramIndicator programIndicator, + RelationshipType relationshipType, + AnalyticsType outerSqlEntity, + Date earliestStartDate, + Date latestDate, + CTEContext cteContext); } diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/AbstractJdbcEventAnalyticsManager.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/AbstractJdbcEventAnalyticsManager.java index 9c1e514b410..0b88484ca5b 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/AbstractJdbcEventAnalyticsManager.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/AbstractJdbcEventAnalyticsManager.java @@ -1411,7 +1411,7 @@ protected List getSelectColumnsWithCTE(EventQueryParams params, CTEConte } else if (ValueType.ORGANISATION_UNIT == queryItem.getValueType()) { // Handle org units if (params.getCoordinateFields().stream() - .anyMatch(f -> queryItem.getItem().getUid().equals(f))) { + .anyMatch(f -> queryItem.getItem().getUid().equals(f))) { columns.add(getCoordinateColumn(queryItem, OU_GEOMETRY_COL_SUFFIX).asSql()); } else { columns.add(getOrgUnitQueryItemColumnAndAlias(params, queryItem).asSql()); @@ -1429,8 +1429,6 @@ protected List getSelectColumnsWithCTE(EventQueryParams params, CTEConte return columns; } - - /** * Returns a select SQL clause for the given query. * @@ -1439,6 +1437,7 @@ protected List getSelectColumnsWithCTE(EventQueryParams params, CTEConte protected abstract String getSelectClause(EventQueryParams params); protected abstract String getColumnWithCte(QueryItem item, String suffix, CTEContext cteContext); + /** * Generates the SQL for the from-clause. Generally this means which analytics table to get data * from. diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/JdbcEnrollmentAnalyticsManager.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/JdbcEnrollmentAnalyticsManager.java index 53ea7026f6f..bd69e3e8094 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/JdbcEnrollmentAnalyticsManager.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/JdbcEnrollmentAnalyticsManager.java @@ -27,7 +27,32 @@ */ package org.hisp.dhis.analytics.event.data; +import static java.util.stream.Collectors.groupingBy; +import static java.util.stream.Collectors.joining; +import static org.apache.commons.lang3.StringUtils.EMPTY; +import static org.hisp.dhis.analytics.AnalyticsConstants.ANALYTICS_TBL_ALIAS; +import static org.hisp.dhis.analytics.DataType.BOOLEAN; +import static org.hisp.dhis.analytics.event.data.OrgUnitTableJoiner.joinOrgUnitTables; +import static org.hisp.dhis.analytics.util.AnalyticsUtils.withExceptionHandling; +import static org.hisp.dhis.common.DataDimensionType.ATTRIBUTE; +import static org.hisp.dhis.common.DimensionItemType.DATA_ELEMENT; +import static org.hisp.dhis.common.DimensionalObject.ORGUNIT_DIM_ID; +import static org.hisp.dhis.common.IdentifiableObjectUtils.getUids; +import static org.hisp.dhis.commons.util.TextUtils.getQuotedCommaDelimitedString; +import static org.hisp.dhis.commons.util.TextUtils.removeLastOr; +import static org.hisp.dhis.util.DateUtils.toMediumDate; + import com.google.common.collect.Sets; +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.hisp.dhis.analytics.analyze.ExecutionPlanStore; @@ -68,32 +93,6 @@ import org.springframework.jdbc.support.rowset.SqlRowSet; import org.springframework.stereotype.Service; -import java.util.Arrays; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import static java.util.stream.Collectors.groupingBy; -import static java.util.stream.Collectors.joining; -import static org.apache.commons.lang3.StringUtils.EMPTY; -import static org.hisp.dhis.analytics.AnalyticsConstants.ANALYTICS_TBL_ALIAS; -import static org.hisp.dhis.analytics.DataType.BOOLEAN; -import static org.hisp.dhis.analytics.event.data.OrgUnitTableJoiner.joinOrgUnitTables; -import static org.hisp.dhis.analytics.util.AnalyticsUtils.withExceptionHandling; -import static org.hisp.dhis.common.DataDimensionType.ATTRIBUTE; -import static org.hisp.dhis.common.DimensionItemType.DATA_ELEMENT; -import static org.hisp.dhis.common.DimensionalObject.ORGUNIT_DIM_ID; -import static org.hisp.dhis.common.IdentifiableObjectUtils.getUids; -import static org.hisp.dhis.commons.util.TextUtils.getQuotedCommaDelimitedString; -import static org.hisp.dhis.commons.util.TextUtils.removeLastOr; -import static org.hisp.dhis.util.DateUtils.toMediumDate; - /** * @author Markus Bekken */ @@ -508,7 +507,8 @@ private String addCteFiltersToWhereClause(EventQueryParams params) { StringBuilder whereClause = new StringBuilder(); // Iterate over each filter and apply the correct condition - for (QueryItem item : Stream.concat(params.getItems().stream(), params.getItemFilters().stream()) + for (QueryItem item : + Stream.concat(params.getItems().stream(), params.getItemFilters().stream()) .filter(QueryItem::hasFilter) .toList()) { @@ -520,8 +520,13 @@ private String addCteFiltersToWhereClause(EventQueryParams params) { } else { String operator = getSqlOperator(filter); String value = getSqlFilterValue(filter, item); - whereClause.append(" AND ").append(cteName).append(".value ") - .append(operator).append(" ").append(value); + whereClause + .append(" AND ") + .append(cteName) + .append(".value ") + .append(operator) + .append(" ") + .append(value); } } } @@ -530,16 +535,17 @@ private String addCteFiltersToWhereClause(EventQueryParams params) { } private String getSqlOperator(QueryFilter filter) { - return switch (filter.getOperator()) { - case EQ -> "="; - case NEQ -> "!="; - case GT -> ">"; - case LT -> "<"; - case GE -> ">="; - case LE -> "<="; - case IN -> "IN"; - default -> throw new IllegalArgumentException("Unsupported operator: " + filter.getOperator()); - }; + return switch (filter.getOperator()) { + case EQ -> "="; + case NEQ -> "!="; + case GT -> ">"; + case LT -> "<"; + case GE -> ">="; + case LE -> "<="; + case IN -> "IN"; + default -> + throw new IllegalArgumentException("Unsupported operator: " + filter.getOperator()); + }; } private String getSqlFilterValue(QueryFilter filter, QueryItem item) { @@ -550,7 +556,8 @@ private String getSqlFilterValue(QueryFilter filter, QueryItem item) { // Handle IN operator: wrap the value(s) in parentheses if (filter.getOperator() == QueryOperator.IN) { String[] values = filter.getFilter().split(","); // Support multiple values - String quotedValues = Arrays.stream(values) + String quotedValues = + Arrays.stream(values) .map(value -> item.isNumeric() ? value : sqlBuilder.singleQuote(value)) .collect(Collectors.joining(", ")); return "(" + quotedValues + ")"; @@ -562,39 +569,48 @@ private String getSqlFilterValue(QueryFilter filter, QueryItem item) { private String buildFilterCteSql(List queryItems, EventQueryParams params) { return queryItems.stream() - .map(item -> { + .map( + item -> { // Determine the correct table: event table or enrollment table - String tableName = item.hasProgramStage() - ? "analytics_event_" + item.getProgram().getUid().toLowerCase() // Event table for program stage + String tableName = + item.hasProgramStage() + ? "analytics_event_" + + item.getProgram() + .getUid() + .toLowerCase() // Event table for program stage : params.getTableName(); // Enrollment table String columnName = quote(item.getItemName()); // Raw column name without alias - String programStageCondition = item.hasProgramStage() + String programStageCondition = + item.hasProgramStage() ? "AND ps = '" + item.getProgramStage().getUid() + "'" : ""; // Add program stage filter if available // Generate the CTE SQL - return String.format(""" + return String.format( + """ SELECT DISTINCT ON (enrollment) enrollment, %s AS value FROM %s WHERE eventstatus != 'SCHEDULE' %s ORDER BY enrollment, occurreddate DESC, created DESC""", - columnName, tableName, programStageCondition - ); + columnName, tableName, programStageCondition); }) - .collect(Collectors.joining("\nUNION ALL\n")); + .collect(Collectors.joining("\nUNION ALL\n")); } - private Stream asSqlCollection(List queryItems, EventQueryParams params) { + private Stream asSqlCollection( + List queryItems, EventQueryParams params) { return Optional.ofNullable(queryItems).orElse(List.of()).stream() - .map(queryItem -> { - String sql = queryItem.getFilters().stream() + .map( + queryItem -> { + String sql = + queryItem.getFilters().stream() .map(filter -> toSql(queryItem, filter, params)) .collect(Collectors.joining(" AND ")); return IdentifiableSql.builder() - .identifier(getIdentifier(queryItem)) - .sql(sql) - .build(); + .identifier(getIdentifier(queryItem)) + .sql(sql) + .build(); }); } @@ -701,55 +717,57 @@ protected String getColumnWithCte(QueryItem item, String suffix, CTEContext cteC colName = quote(colName + suffix); // Generate CTE name based on program stage and item - String cteName = String.format("ps_%s_%s", - item.getProgramStage().getUid().toLowerCase(), - item.getItem().getUid().toLowerCase()); + String cteName = + String.format( + "ps_%s_%s", + item.getProgramStage().getUid().toLowerCase(), item.getItem().getUid().toLowerCase()); String eventTableName = ANALYTICS_EVENT + item.getProgram().getUid(); String excludingScheduledCondition = - eventTableName + ".eventstatus != '" + EventStatus.SCHEDULE + "'"; + eventTableName + ".eventstatus != '" + EventStatus.SCHEDULE + "'"; if (item.getProgramStage().getRepeatable() - && item.hasRepeatableStageParams() - && !item.getRepeatableStageParams().simpleStageValueExpected()) { - - String cteSql = String.format( - "SELECT enrollment, COALESCE(json_agg(t1), '[]') as value FROM (" + - " SELECT %s, %s, %s, %s" + - " FROM %s" + - " WHERE %s AND ps = '%s' %s %s %s %s" + - ") as t1 GROUP BY enrollment", + && item.hasRepeatableStageParams() + && !item.getRepeatableStageParams().simpleStageValueExpected()) { + + String cteSql = + String.format( + "SELECT enrollment, COALESCE(json_agg(t1), '[]') as value FROM (" + + " SELECT %s, %s, %s, %s" + + " FROM %s" + + " WHERE %s AND ps = '%s' %s %s %s %s" + + ") as t1 GROUP BY enrollment", colName, - String.join(", ", - EventAnalyticsColumnName.ENROLLMENT_OCCURRED_DATE_COLUMN_NAME, - EventAnalyticsColumnName.SCHEDULED_DATE_COLUMN_NAME, - EventAnalyticsColumnName.OCCURRED_DATE_COLUMN_NAME), + String.join( + ", ", + EventAnalyticsColumnName.ENROLLMENT_OCCURRED_DATE_COLUMN_NAME, + EventAnalyticsColumnName.SCHEDULED_DATE_COLUMN_NAME, + EventAnalyticsColumnName.OCCURRED_DATE_COLUMN_NAME), eventTableName, excludingScheduledCondition, item.getProgramStage().getUid(), getExecutionDateFilter( - item.getRepeatableStageParams().getStartDate(), - item.getRepeatableStageParams().getEndDate()), + item.getRepeatableStageParams().getStartDate(), + item.getRepeatableStageParams().getEndDate()), createOrderType(item.getProgramStageOffset()), createOffset(item.getProgramStageOffset()), - getLimit(item.getRepeatableStageParams().getCount()) - ); + getLimit(item.getRepeatableStageParams().getCount())); cteContext.addCTE(cteName, cteSql); alias = quote(item.getProgramStage().getUid() + "." + item.getItem().getUid()); return cteName + ".value as " + alias; } else { - String cteSql = String.format( - "SELECT DISTINCT ON (enrollment) enrollment, %s as value " + - "FROM %s " + - "WHERE %s AND ps = '%s' " + - "ORDER BY enrollment, occurreddate DESC, created DESC", + String cteSql = + String.format( + "SELECT DISTINCT ON (enrollment) enrollment, %s as value " + + "FROM %s " + + "WHERE %s AND ps = '%s' " + + "ORDER BY enrollment, occurreddate DESC, created DESC", colName, eventTableName, excludingScheduledCondition, - item.getProgramStage().getUid() - ); + item.getProgramStage().getUid()); cteContext.addCTE(cteName, cteSql); String columnAlias = quote(item.getProgramStage().getUid() + "." + item.getItem().getUid()); @@ -997,31 +1015,33 @@ private CTEContext getCteDefinitions(EventQueryParams params) { if (queryItem.hasRelationshipType()) { programIndicatorSubqueryBuilder.contributeCTE( - pi, - queryItem.getRelationshipType(), - getAnalyticsType(), - params.getEarliestStartDate(), - params.getLatestEndDate(), - cteContext); + pi, + queryItem.getRelationshipType(), + getAnalyticsType(), + params.getEarliestStartDate(), + params.getLatestEndDate(), + cteContext); } else { programIndicatorSubqueryBuilder.contributeCTE( - pi, - getAnalyticsType(), - params.getEarliestStartDate(), - params.getLatestEndDate(), - cteContext); + pi, + getAnalyticsType(), + params.getEarliestStartDate(), + params.getLatestEndDate(), + cteContext); } - } - else if (queryItem.hasProgramStage()) { + } else if (queryItem.hasProgramStage()) { // Generate CTE for program stage items - String cteName = String.format("ps_%s_%s", + String cteName = + String.format( + "ps_%s_%s", queryItem.getProgramStage().getUid().toLowerCase(), queryItem.getItem().getUid().toLowerCase()); String colName = quote(queryItem.getItemName()); String eventTableName = ANALYTICS_EVENT + queryItem.getProgram().getUid(); - String cteSql = String.format( + String cteSql = + String.format( """ -- Generate CTE for program stage items SELECT DISTINCT ON (enrollment) enrollment, %s as value @@ -1030,17 +1050,15 @@ SELECT DISTINCT ON (enrollment) enrollment, %s as value AND ps = '%s' -- AND %s IS NOT NULL ORDER BY enrollment, occurreddate DESC, created DESC""", - colName, - eventTableName, - queryItem.getProgramStage().getUid(), - colName); + colName, eventTableName, queryItem.getProgramStage().getUid(), colName); cteContext.addCTE(cteName, cteSql); - String alias = quote(queryItem.getProgramStage().getUid() + "." + queryItem.getItem().getUid()); + String alias = + quote(queryItem.getProgramStage().getUid() + "." + queryItem.getItem().getUid()); cteContext.addColumnMapping( - queryItem.getProgramStage().getUid() + "." + queryItem.getItem().getUid(), - cteName + ".value as " + alias); + queryItem.getProgramStage().getUid() + "." + queryItem.getItem().getUid(), + cteName + ".value as " + alias); } } return cteContext; @@ -1049,47 +1067,52 @@ SELECT DISTINCT ON (enrollment) enrollment, %s as value private String buildFilterCteSql(QueryItem item, EventQueryParams params) { String tableName = params.getTableName(); String columnName = getColumn(item); - String filterCondition = item.getFilters().stream() + String filterCondition = + item.getFilters().stream() .map(filter -> toSql(item, filter, params)) .collect(Collectors.joining(" AND ")); return String.format( - "SELECT enrollment, %s AS value " + - "FROM %s " + - "WHERE %s", - columnName, tableName, filterCondition - ); + "SELECT enrollment, %s AS value " + "FROM %s " + "WHERE %s", + columnName, tableName, filterCondition); } private void generateFilterCTEs(EventQueryParams params, CTEContext cteContext) { // Combine items and item filters - List queryItems = Stream.concat(params.getItems().stream(), params.getItemFilters().stream()) + List queryItems = + Stream.concat(params.getItems().stream(), params.getItemFilters().stream()) .filter(QueryItem::hasFilter) .toList(); // Group query items by repeatable and non-repeatable stages Map> itemsByRepeatableFlag = - queryItems.stream() - .collect(groupingBy( - queryItem -> queryItem.hasRepeatableStageParams() - && params.getEndpointItem() == RequestTypeAware.EndpointItem.ENROLLMENT - )); + queryItems.stream() + .collect( + groupingBy( + queryItem -> + queryItem.hasRepeatableStageParams() + && params.getEndpointItem() + == RequestTypeAware.EndpointItem.ENROLLMENT)); // Process repeatable stage filters itemsByRepeatableFlag.getOrDefault(true, List.of()).stream() - .collect(groupingBy(this::getIdentifier)) - .forEach((identifier, items) -> { + .collect(groupingBy(this::getIdentifier)) + .forEach( + (identifier, items) -> { String cteName = "filter_" + identifier.replace('.', '_').toLowerCase(); String cteSql = buildFilterCteSql(items, params); cteContext.addCTE(cteName, cteSql); }); // Process non-repeatable stage filters - itemsByRepeatableFlag.getOrDefault(false, List.of()).forEach(queryItem -> { - String cteName = "filter_" + getIdentifier(queryItem).replace('.', '_').toLowerCase(); - String cteSql = buildFilterCteSql(List.of(queryItem), params); - cteContext.addCTE(cteName, cteSql); - }); + itemsByRepeatableFlag + .getOrDefault(false, List.of()) + .forEach( + queryItem -> { + String cteName = "filter_" + getIdentifier(queryItem).replace('.', '_').toLowerCase(); + String cteSql = buildFilterCteSql(List.of(queryItem), params); + cteContext.addCTE(cteName, cteSql); + }); } private String buildEnrollmentQueryWithCte2(EventQueryParams params) { @@ -1109,10 +1132,10 @@ private String buildEnrollmentQueryWithCte2(EventQueryParams params) { } // 3. Select clause - List selectCols = ListUtils.distinctUnion( + List selectCols = + ListUtils.distinctUnion( params.isAggregatedEnrollments() ? List.of("enrollment") : COLUMNS, - getSelectColumnsWithCTE(params, cteContext) - ); + getSelectColumnsWithCTE(params, cteContext)); sql.append("SELECT ").append(String.join(",\n ", selectCols)); // 4. From clause @@ -1121,19 +1144,20 @@ private String buildEnrollmentQueryWithCte2(EventQueryParams params) { // 5. Add joins for each CTE for (String cteName : cteContext.getCTENames()) { sql.append("\nLEFT JOIN ") - .append(cteName) - .append(" ON ax.enrollment = ") - .append(cteName) - .append(".enrollment"); + .append(cteName) + .append(" ON ax.enrollment = ") + .append(cteName) + .append(".enrollment"); } // 6. Where clause - sql.append(" ").append(getWhereClause(params)) - .append(" ") - .append(addCteFiltersToWhereClause(params)); + sql.append(" ") + .append(getWhereClause(params)) + .append(" ") + .append(addCteFiltersToWhereClause(params)); // 7. Order by - sql.append(" " ).append(getSortClause(params)); + sql.append(" ").append(getSortClause(params)); // 8. Paging sql.append(" ").append(getPagingClause(params, 5000)); @@ -1141,7 +1165,6 @@ private String buildEnrollmentQueryWithCte2(EventQueryParams params) { return sql.toString(); } - private String getBasicSelectColumns() { return String.join( ",", @@ -1197,7 +1220,8 @@ private String buildAllRankedEventsCTEs(List items) { String eventTableName = ANALYTICS_EVENT + item.getProgram().getUid().toLowerCase(); String columnName = quote(item.getItem().getUid()); - ctes.append(String.format( + ctes.append( + String.format( """ RankedEvents_%s as ( select enrollment, %s as value, eventstatus, @@ -1207,8 +1231,7 @@ private String buildAllRankedEventsCTEs(List items) { and ps = '%s' ) """, - key, columnName, order, order, eventTableName, stageUid - )); + key, columnName, order, order, eventTableName, stageUid)); processedCombinations.add(key); } @@ -1226,7 +1249,8 @@ private String buildValueColumns(List items) { String stageUid = item.getProgramStage().getUid(); int offset = createOffset2(item.getProgramStageOffset()); - String key = stageUid + "_" + offset + "_" + resolveOrderByOffset(item.getProgramStageOffset()); + String key = + stageUid + "_" + offset + "_" + resolveOrderByOffset(item.getProgramStageOffset()); String offsetLabel = offset == 0 ? "[0]" : "[-" + offset + "]"; String alias = "re_" + key; @@ -1236,15 +1260,21 @@ private String buildValueColumns(List items) { } // Add value column - columns.append(String.format("%s.value as %s", + columns.append( + String.format( + "%s.value as %s", alias, quote(stageUid + offsetLabel + "." + item.getItem().getUid()))); // Add exists column - columns.append(String.format(",\n(%s.enrollment is not null) as %s", + columns.append( + String.format( + ",\n(%s.enrollment is not null) as %s", alias, quote(stageUid + offsetLabel + "." + item.getItem().getUid() + ".exists"))); // Add status column - columns.append(String.format(",\n%s.eventstatus as %s", + columns.append( + String.format( + ",\n%s.eventstatus as %s", alias, quote(stageUid + offsetLabel + "." + item.getItem().getUid() + ".status"))); } @@ -1265,13 +1295,14 @@ private String buildFromClauseWithJoins(EventQueryParams params, List String stageUid = item.getProgramStage().getUid(); int offset = createOffset2(item.getProgramStageOffset()); - String key = stageUid + "_" + offset + "_" + resolveOrderByOffset(item.getProgramStageOffset()); + String key = + stageUid + "_" + offset + "_" + resolveOrderByOffset(item.getProgramStageOffset()); String alias = "re_" + key; - fromClause.append(String.format( + fromClause.append( + String.format( "\nleft join RankedEvents_%s %s on ax.enrollment = %s.enrollment and %s.rn = %d", - key, alias, alias, alias, offset + 1 - )); + key, alias, alias, alias, offset + 1)); } return fromClause.toString(); diff --git a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/programindicator/DefaultProgramIndicatorSubqueryBuilder.java b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/programindicator/DefaultProgramIndicatorSubqueryBuilder.java index 763b0fd3521..74402819889 100644 --- a/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/programindicator/DefaultProgramIndicatorSubqueryBuilder.java +++ b/dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/programindicator/DefaultProgramIndicatorSubqueryBuilder.java @@ -27,7 +27,6 @@ */ package org.hisp.dhis.analytics.event.data.programindicator; -import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.hisp.dhis.analytics.DataType.BOOLEAN; import static org.hisp.dhis.analytics.DataType.NUMERIC; @@ -79,62 +78,66 @@ public String getAggregateClauseForProgramIndicator( } @Override - public void contributeCTE(ProgramIndicator programIndicator, AnalyticsType outerSqlEntity, Date earliestStartDate, Date latestDate, CTEContext cteContext) { - contributeCTE(programIndicator, null, outerSqlEntity, earliestStartDate, latestDate, cteContext); + public void contributeCTE( + ProgramIndicator programIndicator, + AnalyticsType outerSqlEntity, + Date earliestStartDate, + Date latestDate, + CTEContext cteContext) { + contributeCTE( + programIndicator, null, outerSqlEntity, earliestStartDate, latestDate, cteContext); } @Override public void contributeCTE( - ProgramIndicator programIndicator, - RelationshipType relationshipType, - AnalyticsType outerSqlEntity, - Date earliestStartDate, - Date latestDate, - CTEContext cteContext) { + ProgramIndicator programIndicator, + RelationshipType relationshipType, + AnalyticsType outerSqlEntity, + Date earliestStartDate, + Date latestDate, + CTEContext cteContext) { // Generate a unique CTE name for this program indicator String cteName = "pi_" + programIndicator.getUid().toLowerCase(); // Define aggregation function - String function = TextUtils.emptyIfEqual( + String function = + TextUtils.emptyIfEqual( programIndicator.getAggregationTypeFallback().getValue(), AggregationType.CUSTOM.getValue()); - String cteSql = String.format( - "SELECT e.enrollment, " + - "COALESCE(%s(%s), 0) as value " + - "FROM analytics_enrollment_%s e " + - "LEFT JOIN ( " + - " SELECT enrollment, eventstatus " + - " FROM %s as subax " + - " WHERE %s " + - ") t ON t.enrollment = e.enrollment " + - "GROUP BY e.enrollment", + String cteSql = + String.format( + "SELECT e.enrollment, " + + "COALESCE(%s(%s), 0) as value " + + "FROM analytics_enrollment_%s e " + + "LEFT JOIN ( " + + " SELECT enrollment, eventstatus " + + " FROM %s as subax " + + " WHERE %s " + + ") t ON t.enrollment = e.enrollment " + + "GROUP BY e.enrollment", function, getProgramIndicatorSql( - programIndicator.getExpression(), - NUMERIC, - programIndicator, - earliestStartDate, - latestDate), + programIndicator.getExpression(), + NUMERIC, + programIndicator, + earliestStartDate, + latestDate), programIndicator.getProgram().getUid().toLowerCase(), getTableName(programIndicator), getProgramIndicatorSql( - programIndicator.getFilter(), - BOOLEAN, - programIndicator, - earliestStartDate, - latestDate)); + programIndicator.getFilter(), + BOOLEAN, + programIndicator, + earliestStartDate, + latestDate)); // Register the CTE and its column mapping cteContext.addCTE(cteName, cteSql.toString()); - cteContext.addColumnMapping( - programIndicator.getUid(), - cteName + ".value" - ); + cteContext.addColumnMapping(programIndicator.getUid(), cteName + ".value"); } - private String getTableName(ProgramIndicator programIndicator) { return "analytics_event_" + programIndicator.getProgram().getUid().toLowerCase(); } @@ -227,10 +230,9 @@ private String getWhere( RelationshipType relationshipType) { String condition = ""; if (relationshipType != null) { - condition = RelationshipTypeJoinGenerator.generate( - SUBQUERY_TABLE_ALIAS, - relationshipType, - programIndicator.getAnalyticsType()); + condition = + RelationshipTypeJoinGenerator.generate( + SUBQUERY_TABLE_ALIAS, relationshipType, programIndicator.getAnalyticsType()); } else { // Remove the reference to the outer query's enrollment // We'll handle the join in the main query