-
Notifications
You must be signed in to change notification settings - Fork 355
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[DRAFT] fix: Use CTE in Enrollment analytics queries [DHIS-16705] #19519
base: master
Are you sure you want to change the base?
Conversation
|
||
void contributeCTE( | ||
ProgramIndicator programIndicator, | ||
RelationshipType relationshipType, |
Check notice
Code scanning / CodeQL
Useless parameter Note
...-analytics/src/main/java/org/hisp/dhis/analytics/common/ProgramIndicatorSubqueryBuilder.java
Fixed
Show fixed
Hide fixed
...alytics/src/main/java/org/hisp/dhis/analytics/event/data/JdbcEnrollmentAnalyticsManager.java
Fixed
Show fixed
Hide fixed
...alytics/src/main/java/org/hisp/dhis/analytics/event/data/JdbcEnrollmentAnalyticsManager.java
Fixed
Show fixed
Hide fixed
return fromClause.toString(); | ||
} | ||
|
||
protected String getSortClause(EventQueryParams params) { |
Check notice
Code scanning / CodeQL
Missing Override annotation Note
AbstractJdbcEventAnalyticsManager.getSortClause
...alytics/src/main/java/org/hisp/dhis/analytics/event/data/JdbcEnrollmentAnalyticsManager.java
Fixed
Show fixed
Hide fixed
.../hisp/dhis/analytics/event/data/programindicator/DefaultProgramIndicatorSubqueryBuilder.java
Fixed
Show fixed
Hide fixed
8c33952
to
3e070ce
Compare
5170115
to
d8119c0
Compare
...services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/common/CTEContext.java
Fixed
Show fixed
Hide fixed
...services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/common/CTEContext.java
Fixed
Show fixed
Hide fixed
...services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/common/CTEContext.java
Fixed
Show fixed
Hide fixed
...services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/common/CTEContext.java
Fixed
Show fixed
Hide fixed
...tics/src/main/java/org/hisp/dhis/analytics/event/data/AbstractJdbcEventAnalyticsManager.java
Fixed
Show fixed
Hide fixed
...alytics/src/main/java/org/hisp/dhis/analytics/event/data/JdbcEnrollmentAnalyticsManager.java
Fixed
Show fixed
Hide fixed
...alytics/src/main/java/org/hisp/dhis/analytics/event/data/JdbcEnrollmentAnalyticsManager.java
Fixed
Show fixed
Hide fixed
CTEContext cteContext) { | ||
|
||
// Generate a unique CTE name for this program indicator | ||
String cteName = "pi_" + programIndicator.getUid().toLowerCase(); |
Check notice
Code scanning / CodeQL
Unread local variable Note
5c4dba5
to
ef43c5b
Compare
// The CTE definition (the SQL query) | ||
@Getter private final String cteDefinition; | ||
// The calculated offset | ||
@Getter private final List<Integer> offsets = new ArrayList<>(); |
Check notice
Code scanning / CodeQL
Exposing internal representation Note
after this call to getOffsets
this.cteDefinition = cteDefinition; | ||
this.offsets.add(offset); | ||
// one alias per offset | ||
this.alias = new RandomStringGenerator.Builder().withinRange('a', 'z').build().generate(5); |
Check notice
Code scanning / CodeQL
Deprecated method or constructor invocation Note
Builder.build
this.programIndicatorUid = programIndicatorUid; | ||
this.programStageUid = null; | ||
// ignore offset | ||
this.alias = new RandomStringGenerator.Builder().withinRange('a', 'z').build().generate(5); |
Check notice
Code scanning / CodeQL
Deprecated method or constructor invocation Note
Builder.build
this.programIndicatorUid = null; | ||
this.programStageUid = programStageUid; | ||
// ignore offset | ||
this.alias = new RandomStringGenerator.Builder().withinRange('a', 'z').build().generate(5); |
Check notice
Code scanning / CodeQL
Deprecated method or constructor invocation Note
Builder.build
} | ||
|
||
if (offset < 0) { | ||
return (-1 * offset); |
Check failure
Code scanning / CodeQL
User-controlled data in arithmetic expression High
user-provided value
This arithmetic expression depends on a
user-provided value
This arithmetic expression depends on a
user-provided value
This arithmetic expression depends on a
user-provided value
This arithmetic expression depends on a
user-provided value
This arithmetic expression depends on a
user-provided value
This arithmetic expression depends on a
user-provided value
This arithmetic expression depends on a
user-provided value
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix AI about 5 hours ago
To fix the problem, we need to validate the offset
parameter before performing arithmetic operations on it. Specifically, we should ensure that the offset
value is within a safe range to prevent overflow or underflow. We can achieve this by adding a guard clause that checks the value of offset
and handles extreme values appropriately.
The best way to fix the problem without changing existing functionality is to add a validation step in the computeRowNumberOffset
method. We will check if the offset
value is within the range of Integer.MIN_VALUE
to Integer.MAX_VALUE
and handle cases where it is not.
-
Copy modified lines R1168-R1170 -
Copy modified lines R1173-R1175
@@ -1167,4 +1167,10 @@ | ||
if (offset < 0) { | ||
if (offset == Integer.MIN_VALUE) { | ||
throw new IllegalArgumentException("Offset value is too small and causes underflow."); | ||
} | ||
return (-1 * offset); | ||
} else { | ||
if (offset == Integer.MAX_VALUE) { | ||
throw new IllegalArgumentException("Offset value is too large and causes overflow."); | ||
} | ||
return (offset - 1); |
aa533b4
to
5f05bb5
Compare
@@ -578,6 +735,29 @@ | |||
return ColumnAndAlias.EMPTY; | |||
} | |||
|
|||
protected String getColumnWithCte(QueryItem item, String suffix, CTEContext cteContext) { | |||
List<String> columns = new ArrayList<>(); | |||
String colName = item.getItemName(); |
Check notice
Code scanning / CodeQL
Unread local variable Note
import org.hisp.dhis.program.ProgramIndicator; | ||
import org.hisp.dhis.program.ProgramStage; | ||
|
||
public class CTEContext { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rename to CteContext
. Use Pascal-case for acronyms in class names in Java.
https://www.oreilly.com/library/view/java-8-pocket/9781491901083/ch01.html
* @param offset The calculated offset | ||
* @param isRowContext Whether the CTE is a row context | ||
*/ | ||
public void addCTE( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rename to addCte
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed, renamed also other methods CTE -> Cte on the same class
return ""; | ||
} | ||
|
||
StringBuilder sb = new StringBuilder("WITH "); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's use lower-case for SQL keywords in Java, i.e. with
.
case LT -> "<"; | ||
case GE -> ">="; | ||
case LE -> "<="; | ||
case IN -> "IN"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lower-case for SQL keywords in Java, e.g. in
.
|
||
/** Returns true if incident date criteria exists in dimensions or filters. */ | ||
public boolean hasIncidentDateCriteria() { | ||
return getDimensionOrFilterItems("incidentDate").size() > 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use !isEmpty()
over .size() > 0
for readability.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed function, as it looks like it wasn't used
5f05bb5
to
4e085d3
Compare
4e085d3
to
62c2237
Compare
Quality Gate failedFailed conditions See analysis details on SonarQube Cloud Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE |
void contributeCTE( | ||
ProgramIndicator programIndicator, | ||
RelationshipType relationshipType, | ||
AnalyticsType outerSqlEntity, |
Check notice
Code scanning / CodeQL
Useless parameter Note
/** | ||
* Returns a select SQL clause for the given query. | ||
* | ||
* @param params the {@link EventQueryParams}. | ||
*/ | ||
protected abstract String getSelectClause(EventQueryParams params); | ||
|
||
protected abstract String getColumnWithCte(QueryItem item, String suffix, CteContext cteContext); |
Check notice
Code scanning / CodeQL
Useless parameter Note
@@ -565,6 +722,29 @@ | |||
return ColumnAndAlias.EMPTY; | |||
} | |||
|
|||
protected String getColumnWithCte(QueryItem item, String suffix, CteContext cteContext) { |
Check notice
Code scanning / CodeQL
Missing Override annotation Note
WIP
Changes
This PR addresses the generation of SQL queries for Enrollment analytics
Problem
Currently, the Enrollment queries are structured so that sub-queries are used to fetch events values from the
analytics_event_*
tables.For instance:
The above query works in Postgres but does not work in Doris, because correlation with outer layers of the parent query is not supported.
Mitigation
The current approach is trying to refactor the Enrollment query so that the subqueries (both as select and as where conditions) are "moved" into CTE (Common Table Expressions).
Common Table Expressions are temporary result sets in SQL, defined within a
WITH
clause, that simplify complex queries by improving readability and modularizing logic. They are particularly useful for recursive queries and can be referenced multiple times within the main query.The above query can be rewritten like so:
The above query structure is compatible with Doris and it makes the execution of the query faster.
As a comparison:
Original query with sub-select
Refactored query with CTEs
Testing strategy
I am using the
e2e
project and aim at having 100% green tests on both Postgres and Doris.