Add resource_tables_dirty(timestamp) diagnostic SQL function#1
Closed
jason-p-pickering wants to merge 9 commits into
Closed
Add resource_tables_dirty(timestamp) diagnostic SQL function#1jason-p-pickering wants to merge 9 commits into
jason-p-pickering wants to merge 9 commits into
Conversation
…rty; add resource_tables_dirty_detail() resource_tables_dirty()'s missing-tables guard required all 15 analytics_rs_* tables to exist, including the 2 that are only created by a full DATA_VALUE build with data approval enabled. On any instance without that table populated, the guard short-circuited TRUE unconditionally, forever, regardless of actual staleness. Confirmed on "sl": resource_tables_dirty(now()) read TRUE even at the current instant. _rt_dirty_data_approval_remap_level / _rt_dirty_data_approval_min_level now gate on whether there is actual approval data (dataapprovalworkflowlevels / dataapproval rows) to reflect when their own resource table is missing, instead of treating the missing table itself as dirty. Also adds resource_tables_dirty_detail(timestamp), a per-table/per-reason breakdown built by reusing each checker's exact predicates, so a dirty result no longer requires guessing which of the 15 tables triggered it.
…version-gate dataset check
Found live on a 2.41 instance ("hmis"): resource_tables_dirty() was permanently TRUE there too,
same bug class as the data-approval fix, but for 2 different tables and 2 different reasons.
- analytics_rs_relationship: usage-gated like the approval tables. Confirmed hmis has 0
relationship rows (non-tracker instance) -- the resource table plausibly never gets built with
nothing to reflect. Dirty only if relationship data actually exists.
- analytics_rs_dataset: root-caused via dhis2-core commit 797fd1ed ("fix: Add data set resource
table [DHIS2-16705]") -- confirmed via GitHub compare that this table does not exist in any
2.41.x release, only from 2.42.0 onward. Added _rt_dhis2_schema_at_least(), which reads
flyway_schema_history (dhis2-core's own migration ledger) as an indirect version probe, so a
missing table is only flagged dirty when the schema is actually >= 2.42 and the table should
exist. Falls back to not-dirty if the version can't be determined.
Both changes mirrored into resource_tables_dirty_detail.sql. Validated on "sl" (schema 2.43.64):
dropping analytics_rs_dataset in a rolled-back transaction still correctly flags dirty (proving
the version gate doesn't just silently hide real staleness on 2.42+), while real dataset/period
staleness and the data-approval usage-gating from the previous commit remain unaffected.
…und via source tracing
Two things happened together:
1. Merging resource_tables_dirty_detail.sql into resource_tables_dirty.sql. Every fix in this
script's history so far had a parallel boolean checker and TABLE-returning "detail" checker
per table, requiring every change to be applied twice -- error-prone, and the actual reason
this consolidation was requested. Each table now has exactly one _rt_dirty_<name>(baseline)
RETURNS TABLE(reason text) probe. resource_tables_dirty(baseline) is now just
`EXISTS (SELECT 1 FROM resource_tables_dirty_detail(baseline))` -- trades away short-circuit
performance (plpgsql RETURN QUERY materializes fully before a caller's EXISTS can short-circuit)
for a single source of truth per table, which is the right tradeoff for an ops/cron diagnostic.
2. While rewriting the file, traced analytics_rs_relationship's actual dhis2-core source instead
of trusting the previous commit's live-observed fix. That fix (usage-gated on `EXISTS(relationship)`)
was coincidentally right on hmis (0 relationships AND pre-2.42 both say "not dirty") but wrong in
principle: RelationshipCountResourceTable is an unconditional entry in
DefaultResourceTableService.getResourceTables() (dhis2-core commit 7c7fb34, merged 2025-02-04),
confirmed absent from every 2.41.x tag and present from 2.42.0 onward -- the exact same version-gap
pattern as analytics_rs_dataset, not a real data-existence question. Now version-gated identically
to analytics_rs_dataset via _rt_dhis2_schema_at_least('2.42').
Re-validated end-to-end on "sl": clean baseline reads FALSE with 0 detail rows, a rolled-back
dataset.lastupdated mutation correctly flags 3 dependent tables, and dropping analytics_rs_dataset
on this 2.43.64 schema still correctly reads dirty (version gate doesn't hide real staleness on
2.42+). Deploying this over an already-deployed 2-file instance requires DROPping the old
boolean-typed functions first (Postgres refuses to change a function's return type via CREATE OR
REPLACE) -- added as a one-time cleanup block at the top of the file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a standalone, ops-only SQL diagnostic for DHIS2's
analytics_rs_*resource tables, as a singleresource_tables_dirty.sqlfile:resource_tables_dirty(timestamp) RETURNS boolean— cheaply answers "do the resource tables needregenerating?" without running a full (expensive)
/api/resourceTablesbuild.resource_tables_dirty_detail(timestamp) RETURNS TABLE(resource_table text, reason text)— samechecks, returns every reason that fired instead of a bare boolean, so a dirty result doesn't require
guessing which table triggered it.
Each resource table has exactly one
_rt_dirty_<name>(baseline) RETURNS TABLE(reason text)probe —a single source of truth per table.
resource_tables_dirty()is justEXISTS (SELECT 1 FROM resource_tables_dirty_detail(baseline)). (An earlier revision split thisacross two files with a parallel boolean checker and TABLE-returning checker per table; every fix
had to be applied twice, so they were merged.)
This is a diagnostic companion to
dhis2_analytics_trigger.py, not wired into it yet. Longer-termintent is to use this as the signal the Python trigger script checks before deciding whether to run
a full resource-table regen — this SQL function is the first step toward that.
Not a Flyway migration, not part of dhis2-core, no Java wiring — deploy manually via
psql -f resource_tables_dirty.sql.Notable design points, found via live testing on a real 2.41 instance + tracing dhis2-core source
Several tables can be legitimately absent without meaning "stale" — each caused a permanent false
positive (
resource_tables_dirty()readingTRUEforever, regardless of baseline) until traced toits real cause instead of guessed at:
analytics_rs_dataapprovalremaplevel/analytics_rs_dataapprovalminlevel: built by a separategenerateDataApprovalResourceTables()method, never part of a plain/api/resourceTablescall, andonly invoked when
isApprovalEnabled()— which traces down tokeyIgnoreAnalyticsApprovalYearThreshold >= 0(default-1= disabled) AND approval levels existing.An earlier attempt assumed "usage" meant real
dataapproval/dataapprovalworkflowlevelsdataexisting — wrong; both "sl" and hmis have genuine approval data yet the tables still don't exist,
because that data existing isn't the actual DHIS2 gate. Fixed to read the real setting.
analytics_rs_datasetandanalytics_rs_relationship: both version-gated. Neither exists inany DHIS2 2.41.x release — confirmed via GitHub compare against every 2.41.x tag — only from 2.42.0
onward (
dataset: commit797fd1ed, DHIS2-16705;relationship: commit7c7fb34e, merged2025-02-04). A missing table is only reported dirty when the schema is actually >= 2.42 (detected
indirectly via
flyway_schema_history, since this script has no DHIS2 API/Java dependency).Test plan
dataset.lastupdatedchange correctly flags all 3 dependent tables, then rolls back clean.FALSEwhen the real DHIS2 gate condition isn'tmet, and correctly stays
TRUEwhen there's genuine staleness or a table is missing at aschema version where it should exist (e.g. dropping
analytics_rs_dataseton this 2.43.64schema in a rolled-back transaction still correctly reads dirty).
dropped before the new TABLE-returning ones are created, since Postgres refuses to change a
function's return type via
CREATE OR REPLACE).hmis(2.41, live production) and confirmed clean baseline (1 genuine finding: indicatorgroupsetstructure staleness, a true positive) plus an end-to-end mutation test — inserting a new period (rolled back, never committed) correctly flippedanalytics_rs_periodstructuredirty with reason "period row(s) missing from analytics_rs_periodstructure", confirming the exact scenario that originally motivated this script.🤖 Generated with Claude Code