Commit c2cd1d1
authored
[2.43] fix: Fixes in analytics SQL engine for ClickHouse compatibility [DHIS2-21417]
* fix: Fixes in analytics SQL engine for ClickHouse compatibility [DHIS2-21417] (#23810)
* fix: [DHIS2-21418] lowercase period column names in event SQL emission
PeriodTypeEnum.getName() returns display-cased names (e.g. "Monthly"); the analytics tables are created with lowercase period columns. Postgres folds unquoted identifiers, Doris is case-insensitive, ClickHouse is not.
Lowercase with Locale.ROOT at every emission point so the reference matches the table column on every engine.
* fix: [DHIS2-21419] lowercase program UID in analytics_event/_enrollment table names
DHIS2 UIDs aremixed-case but the analytics tables themselves
are created with lowercased names (analytics_event_iphinat79uw).
Multiple emission sites concatenated "analytics_event_" + program.getUid() without
lowercasing. Invisible on Postgres (case-folded) and Doris
(lower_case_table_names=1), broken on ClickHouse (UNKNOWN_TABLE).
Apply .toLowerCase() at every emission site.
* fix: [DHIS2-21421] emit JOIN form for date-period-structure lookup on ClickHouse
DateFieldPeriodBucketColumnResolver already supports two emission shapes for the `analytics_rs_dateperiodstructure` lookup, switched on
sqlBuilder.useJoinForDatePeriodStructureLookup(): a correlated scalar subquery (Postgres) or a LEFT JOIN (Doris). ClickHouse's analyzer rejects the correlated form ("Resolve identifier 'eb.lastupdated' from parent scope only supported for constants and CTE"), so override the flag to true.
The JOIN form works on every engine, so this is a strict improvement with no regression risk on Postgres/Doris.
* fix: [DHIS2-21422] identity-alias prefixed enrollment column in aggregate CTEs
ClickHouse's analyzer keeps the original table prefix attached to a
projected column when no explicit output alias is given. So a CTE projecting ax.enrollment produces a column the analyzer still binds to ax - the outer query's eb.enrollment from the enrollment_aggr_base CTE alias is then
unresolvable.
Postgres and Doris implicitly drop the prefix and re-scope under the CTE alias.
Add explicit identity aliases at two emission sites in
JdbcEnrollmentAnalyticsManager: the enrollment_aggr_base CTE projection and
the inline derived-table evf event-date subquery. The alias re-binds the
column under the CTE / derived-table alias on ClickHouse and is a no-op for Postgres/Doris.
* fix: [DHIS2-21423] NULL-safe date cast for analytics joins
ClickHouse's toDate(NULL) and CAST(NULL AS Date) raise
CANNOT_INSERT_NULL_IN_ORDINARY_COLUMN, and toDateOrNull only accepts String
input. Aggregate queries that join to analytics_rs_dateperiodstructure on a
nullable data-element column (e.g. cast(ax."<deUid>" as date)) crash.
Introduce AnalyticsSqlBuilder.castAsDate(String). Default emits ANSI `cast(... as date)` - correct for Postgres and Doris. ClickHouse overrides to
toDateOrNull(toString(...)), which is both NULL-safe and accepts every input type the cast might receive (Date / DateTime / DateTime64 / String / Nullable variants).
JdbcEventAnalyticsManager's hasTimeField LEFT JOIN now routes through sqlBuilder.castAsDate. Output for Postgres/Doris is byte-identical.
* fix: [DHIS2-21428] align dimension columns at SELECT for ClickHouse
Two fixes for the same family of case-sensitivity issue:
1) AggregatedRowBuilder.addDimensionData lowercases the dimension name with Locale.ROOT before the result-set lookup. Period-column SQL emission was
already lowercased; the row builder still asked the result set for
"Quarterly" instead of "quarterly", which Postgres/MySQL JDBC normalised
silently but ClickHouse JDBC rejected with InvalidResultSetAccessException.
2) Add explicit "as <col>" alias to dimension projections in the SELECT context so the result-set column has a canonical name regardless of how the engine reports the underlying expression. ClickHouse JDBC retains the table prefix on unaliased prefixed columns (ax.uidlevel2) in result-set metadata; Postgres/MySQL strip it.
* fix: [DHIS2-21430] generate legend-set columns on ClickHouse via inline CASE
JdbcEventAnalyticsTableManager.getColumnFromDataElementWithLegendSet
early-returned an empty list on engines without correlated-subquery support
(ClickHouse). The companion <deUid>_<lsUid> column was never created in the
ClickHouse analytics tables, but the query path still emitted SQL referring
to it — every aggregate query grouped or filtered by legend bucket failed
with UNKNOWN_IDENTIFIER.
Replace the early-return with a per-row CASE expression built from the static legend ranges. Same final value populated into the column, no correlated subquery, ClickHouse-friendly. Postgres and Doris are unaffected:
supportsCorrelatedSubquery() returns true on both, so the existing correlated-subquery emission path runs unchanged.
* fix: [DHIS2-21431] populate OU-name support column on ClickHouse via JOIN
Populate the <deUid>_name support column on ClickHouse via a LEFT JOIN to organisationunit instead of a correlated
subquery (which ClickHouse rejects). The column was previously not created at all, causing UNKNOWN_IDENTIFIER errors in
aggregate queries that group by an org-unit-typed data element.
* Fix formatting
* Fix failing unit test
* More Unit Test fixes (lower case program)
* refactor: [DHIS2-21420] centralise analytics_event/_enrollment table names
Introduce AnalyticsTableNames in dhis-support-sql with eventTable(Program)
and enrollmentTable(Program) helpers. Replace every inline
"analytics_event_" + program.getUid().toLowerCase() and equivalent
"analytics_enrollment_" + ... concatenation with a call through the helper.
* fix: [DHIS2-21422] identity-alias prefixed enrollment column in PI value CTE
Same ClickHouse-analyzer issue as the prior fix in JdbcEnrollmentAnalyticsManager:
a CTE projecting `subax.enrollment` without an explicit output alias keeps the
column bound to `subax` in ClickHouse's scope, so the outer query's
`<alias>.enrollment` reference cannot be resolved. Postgres and Doris re-scope
the column under the CTE alias implicitly.
Add an explicit identity alias on the projection in
DefaultProgramIndicatorSubqueryBuilder so the column is re-bound under the CTE
alias on ClickHouse. No-op for Postgres/Doris.
* remove doc for tracking issues
* fix: [DHIS2-21441] fix rejected correlated scalar subqueries in Events query
* fix: [DHIS2-21486] fix event PI queries by replacing correlated subqueries with event-keyed CTE joins
* fix: [DHIS2-21487] Fix enrollment stage display-name projections
* fix: [DHIS2-21488] fix relationship count CTE SQL for enrollment PIs
* fix: [DHIS2-21493] Fix ClickHouse dateDiff literal casting
* fix: [DHIS2-21495] Fix option-set stage CTE value projection
* Use correlated subqueries when engine supports it
* fix: scope ClickHouse event PI CTEs to candidate events
Add an internal event_pi_candidates CTE for EVENT analytics queries when
the database does not support correlated subqueries and EVENT
program-indicator CTEs are generated.
The candidate CTE mirrors the outer event query scope, including base
filters such as period, org unit, stage, and non-PI query item filters.
EVENT PI CTEs now read from this candidate source instead of scanning
the full analytics event table. PI filters stay outside the candidate
CTE to avoid circular SQL dependencies.
Postgres and other correlated-subquery paths remain unchanged.
Enrollment PI CTEs keep their existing source behavior.
* fix: keep event query enrollment PIs on CTE path
Narrow the Event analytics correlated-subquery fallback so it only
applies to EVENT-type program indicators. ENROLLMENT-type program
indicators used from the Event endpoint still need the CTE path because
it expands generated placeholders such as `FUNC_CTE_VAR`,
`__PSDE_CTE_PLACEHOLDER__`, and `__D2FUNC__`.
* refactor: [DHIS2-21488] replace relationship-count regex with d2:relationshipCount placeholder pipeline
* fix: use ClickHouse age() for year/month/week date differences
ClickHouse dateDiff() counts boundary crossings, so program indicators
using d2:yearsBetween, d2:monthsBetween and d2:weeksBetween returned
values that differed from PostgreSQL, which counts completed units via
age(). For example dateDiff('year', '2023-12-31', '2024-01-01') is 1
where PostgreSQL reports 0 completed years.
Switch the years, months and weeks cases of
ClickHouseSqlBuilder.dateDifference to age() (available since ClickHouse
23.1), keeping the same operand order. Days and minutes stay on
dateDiff() because PostgreSQL computes them with date subtraction and
elapsed time, which already match.
* fix: normalize empty text to NULL in ClickHouse enrollment aggregate CTEs
Enrollment aggregate queries with program-stage data element dimensions
returned more groups on ClickHouse than on Postgres. ClickHouse
analytics tables store empty strings for absent text values where
Postgres stores NULL, so GROUP BY split empty and NULL into separate
buckets.
Add AnalyticsSqlBuilder.nullIfEmpty, a no-op by default and overridden
in `ClickHouseAnalyticsSqlBuilder` to wrap the column in `nullif(column,
'')`.
* fix: make ClickHouse safeConcat null-safe for analytics display names
* fix: align ClickHouse event query columns and join semantics with Postgres
* fix: treat ClickHouse empty-string coordinates as null in coordinatesOnly filter
* fix: fold ClickHouse empty-string text dimensions to NULL in event aggregate
* fix: alias wrapped ClickHouse aggregate text columns to keep result-set label
* fix: join ClickHouse relationship-count CTE by alias, not registry key1 parent 8900992 commit c2cd1d1
57 files changed
Lines changed: 3778 additions & 470 deletions
File tree
- dhis-2
- dhis-api/src/main/java/org/hisp/dhis/program
- dhis-services
- dhis-service-analytics/src
- main/java/org/hisp/dhis/analytics
- common
- event/data
- programindicator
- ctefactory
- table
- model
- util
- trackedentity/query/context/querybuilder
- test/java/org/hisp/dhis/analytics
- common
- event/data
- programindicator
- table
- dhis-service-core/src
- main/java/org/hisp/dhis/program
- function
- test/java/org/hisp/dhis/program
- dhis-support
- dhis-support-expression-parser/src/main/java/org/hisp/dhis/parser/expression/statement
- dhis-support-hibernate/src
- main/java/org/hisp/dhis/config
- test/java/org/hisp/dhis/config
- dhis-support-sql/src
- main/java/org/hisp/dhis/db
- sql
- util
- test/java/org/hisp/dhis/db
- sql
- util
- dhis-test-e2e/src/test/java/org/hisp/dhis/analytics/aggregate
- dhis-test-integration/src/test/java/org/hisp/dhis/program
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 22 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
213 | 213 | | |
214 | 214 | | |
215 | 215 | | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
216 | 238 | | |
217 | 239 | | |
218 | 240 | | |
| |||
Lines changed: 4 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
230 | 230 | | |
231 | 231 | | |
232 | 232 | | |
233 | | - | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
234 | 237 | | |
235 | 238 | | |
236 | 239 | | |
| |||
Lines changed: 66 additions & 3 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
52 | 52 | | |
53 | 53 | | |
54 | 54 | | |
| 55 | + | |
| 56 | + | |
55 | 57 | | |
56 | 58 | | |
57 | 59 | | |
58 | 60 | | |
59 | 61 | | |
| 62 | + | |
| 63 | + | |
60 | 64 | | |
61 | 65 | | |
62 | 66 | | |
| |||
100 | 104 | | |
101 | 105 | | |
102 | 106 | | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
103 | 129 | | |
104 | 130 | | |
105 | 131 | | |
| |||
111 | 137 | | |
112 | 138 | | |
113 | 139 | | |
114 | | - | |
| 140 | + | |
| 141 | + | |
115 | 142 | | |
116 | 143 | | |
117 | 144 | | |
| |||
154 | 181 | | |
155 | 182 | | |
156 | 183 | | |
| 184 | + | |
157 | 185 | | |
158 | 186 | | |
159 | | - | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
160 | 191 | | |
161 | 192 | | |
162 | 193 | | |
163 | 194 | | |
164 | 195 | | |
165 | 196 | | |
166 | | - | |
| 197 | + | |
| 198 | + | |
167 | 199 | | |
168 | 200 | | |
169 | 201 | | |
| |||
258 | 290 | | |
259 | 291 | | |
260 | 292 | | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
261 | 308 | | |
262 | 309 | | |
263 | 310 | | |
264 | 311 | | |
265 | 312 | | |
266 | 313 | | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
267 | 330 | | |
268 | 331 | | |
269 | 332 | | |
| |||
Lines changed: 82 additions & 11 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
68 | 68 | | |
69 | 69 | | |
70 | 70 | | |
| 71 | + | |
| 72 | + | |
71 | 73 | | |
72 | 74 | | |
| 75 | + | |
| 76 | + | |
73 | 77 | | |
74 | 78 | | |
75 | 79 | | |
| |||
110 | 114 | | |
111 | 115 | | |
112 | 116 | | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
113 | 120 | | |
114 | 121 | | |
115 | 122 | | |
| |||
123 | 130 | | |
124 | 131 | | |
125 | 132 | | |
126 | | - | |
| 133 | + | |
| 134 | + | |
127 | 135 | | |
128 | 136 | | |
129 | 137 | | |
| |||
137 | 145 | | |
138 | 146 | | |
139 | 147 | | |
| 148 | + | |
140 | 149 | | |
141 | 150 | | |
142 | 151 | | |
| |||
155 | 164 | | |
156 | 165 | | |
157 | 166 | | |
158 | | - | |
| 167 | + | |
| 168 | + | |
159 | 169 | | |
160 | 170 | | |
161 | 171 | | |
| |||
177 | 187 | | |
178 | 188 | | |
179 | 189 | | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
180 | 202 | | |
181 | 203 | | |
182 | 204 | | |
| |||
190 | 212 | | |
191 | 213 | | |
192 | 214 | | |
193 | | - | |
| 215 | + | |
| 216 | + | |
194 | 217 | | |
195 | 218 | | |
196 | 219 | | |
| |||
208 | 231 | | |
209 | 232 | | |
210 | 233 | | |
211 | | - | |
| 234 | + | |
| 235 | + | |
212 | 236 | | |
213 | 237 | | |
214 | 238 | | |
| |||
217 | 241 | | |
218 | 242 | | |
219 | 243 | | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
220 | 255 | | |
221 | 256 | | |
222 | 257 | | |
| |||
228 | 263 | | |
229 | 264 | | |
230 | 265 | | |
231 | | - | |
| 266 | + | |
232 | 267 | | |
233 | 268 | | |
234 | 269 | | |
235 | 270 | | |
236 | | - | |
| 271 | + | |
| 272 | + | |
237 | 273 | | |
238 | 274 | | |
239 | 275 | | |
| |||
253 | 289 | | |
254 | 290 | | |
255 | 291 | | |
256 | | - | |
| 292 | + | |
| 293 | + | |
257 | 294 | | |
258 | 295 | | |
259 | 296 | | |
| |||
280 | 317 | | |
281 | 318 | | |
282 | 319 | | |
283 | | - | |
| 320 | + | |
| 321 | + | |
284 | 322 | | |
285 | 323 | | |
286 | 324 | | |
| |||
302 | 340 | | |
303 | 341 | | |
304 | 342 | | |
305 | | - | |
| 343 | + | |
| 344 | + | |
306 | 345 | | |
307 | 346 | | |
308 | 347 | | |
| |||
328 | 367 | | |
329 | 368 | | |
330 | 369 | | |
331 | | - | |
| 370 | + | |
| 371 | + | |
| 372 | + | |
| 373 | + | |
| 374 | + | |
| 375 | + | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
| 387 | + | |
| 388 | + | |
| 389 | + | |
| 390 | + | |
| 391 | + | |
| 392 | + | |
| 393 | + | |
| 394 | + | |
| 395 | + | |
| 396 | + | |
| 397 | + | |
332 | 398 | | |
333 | 399 | | |
334 | 400 | | |
| |||
343 | 409 | | |
344 | 410 | | |
345 | 411 | | |
346 | | - | |
| 412 | + | |
| 413 | + | |
347 | 414 | | |
348 | 415 | | |
349 | 416 | | |
| |||
374 | 441 | | |
375 | 442 | | |
376 | 443 | | |
| 444 | + | |
| 445 | + | |
| 446 | + | |
| 447 | + | |
377 | 448 | | |
378 | 449 | | |
379 | 450 | | |
| |||
0 commit comments