From b54d8d103cb5d69a9cebce6430a31998a64e2305 Mon Sep 17 00:00:00 2001 From: James Maes Date: Wed, 14 Oct 2026 15:18:11 -0500 Subject: [PATCH] fix(schema): index the join child columns monitor_id, alert_type_id and group_id Changeset 29 adds alerts_alert (monitor_id, status), alerts_monitor (alert_type_id) and alerts_type (group_id). MonitorEvaluator looks up a per-record monitor's open alerts by monitor_id + status on every evaluation, and the type and group record pages list children by these columns. A new test checks that every one-to-many join's child column leads an index in the shipped changelog. Co-Authored-By: Claude Opus 5.5 --- .../qbit-alerts/alerts.changelog.sql | 6 ++++ .../qbits/alerts/AlertsQBitProducerTest.java | 31 +++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/src/main/resources/db/liquibase/qbit-alerts/alerts.changelog.sql b/src/main/resources/db/liquibase/qbit-alerts/alerts.changelog.sql index aaa2f62..7c5d8ae 100644 --- a/src/main/resources/db/liquibase/qbit-alerts/alerts.changelog.sql +++ b/src/main/resources/db/liquibase/qbit-alerts/alerts.changelog.sql @@ -513,3 +513,9 @@ ALTER TABLE alerts_notification_preference ADD COLUMN slack_min_severity VARCHAR ALTER TABLE alerts_notification_recipient ADD COLUMN slack_status VARCHAR(16); ALTER TABLE alerts_notification_recipient ADD COLUMN slack_message_id VARCHAR(100); CREATE INDEX ix_alerts_notification_recipient_slack_status ON alerts_notification_recipient (slack_status); + +-- changeset qbit-alerts:29 +-- comment: Indexes for the join child columns: a per-record monitor finds its open alerts by (monitor_id, status) on every evaluation (MonitorEvaluator), and the record pages list a type's monitors (alert_type_id) and a group's types (group_id). +CREATE INDEX IF NOT EXISTS ix_alerts_alert_monitor_id_status ON alerts_alert (monitor_id, status); +CREATE INDEX IF NOT EXISTS ix_alerts_monitor_alert_type_id ON alerts_monitor (alert_type_id); +CREATE INDEX IF NOT EXISTS ix_alerts_type_group_id ON alerts_type (group_id); diff --git a/src/test/java/com/kingsrook/qbits/alerts/AlertsQBitProducerTest.java b/src/test/java/com/kingsrook/qbits/alerts/AlertsQBitProducerTest.java index 8797128..b1af7b3 100644 --- a/src/test/java/com/kingsrook/qbits/alerts/AlertsQBitProducerTest.java +++ b/src/test/java/com/kingsrook/qbits/alerts/AlertsQBitProducerTest.java @@ -369,4 +369,35 @@ class AlertsQBitProducerTest extends BaseAlertsTest com.kingsrook.qqq.backend.core.actions.permissions.TablePermissionSubType.INSERT)).isTrue(); } + + + /******************************************************************************* + ** Every one-to-many join's child column leads an index in the shipped + ** changelog, so record pages, child lists and per-parent lookups do not scan + ** the child table. + *******************************************************************************/ + @Test + void testJoinChildColumnsAreIndexed() throws Exception + { + String changelog; + try(InputStream in = getClass().getClassLoader().getResourceAsStream("db/liquibase/qbit-alerts/alerts.changelog.sql")) + { + changelog = new String(in.readAllBytes(), StandardCharsets.UTF_8); + } + for(com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData join : AlertsTableMetaDataHelper.buildJoins()) + { + String table = join.getRightTable(); + String column = join.getJoinOns().get(0).getRightField().replaceAll("([a-z0-9])([A-Z])", "$1_$2").toLowerCase(); + java.util.regex.Pattern index = java.util.regex.Pattern.compile("(?i)CREATE (UNIQUE )?INDEX (IF NOT EXISTS )?\\w+ ON " + table + " \\(" + column + "[,)]"); + java.util.regex.Pattern unique = java.util.regex.Pattern.compile("(?is)CREATE TABLE IF NOT EXISTS " + table + " \\((?:(?!\\);).)*UNIQUE \\(" + column + "[,)]"); + ////////////////////////////////////////////////////////////////////// + // alerts_delivery was renamed from the RC.1 alerts_notification; // + // its indexes came along by ALTER INDEX ... RENAME TO ix__ // + ////////////////////////////////////////////////////////////////////// + boolean renamed = changelog.contains("RENAME TO ix_" + table + "_" + column + ";") || changelog.contains("RENAME TO ix_" + table + "_" + column + "_"); + assertThat(index.matcher(changelog).find() || unique.matcher(changelog).find() || renamed) + .as("index leading with " + table + "." + column + " for join " + join.getName()) + .isTrue(); + } + } } -- 2.54.0