From dec41836b54fbca17564ea2bfe92498bafcb613f Mon Sep 17 00:00:00 2001 From: "Thomas F. K. Jorna" Date: Mon, 12 Jan 2026 15:45:50 +0100 Subject: [PATCH 1/3] dev: add a bunch of indices --- .../migration.sql | 121 ++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 core/prisma/migrations/20260112120000_add_performance_indexes/migration.sql diff --git a/core/prisma/migrations/20260112120000_add_performance_indexes/migration.sql b/core/prisma/migrations/20260112120000_add_performance_indexes/migration.sql new file mode 100644 index 000000000..09da9cb49 --- /dev/null +++ b/core/prisma/migrations/20260112120000_add_performance_indexes/migration.sql @@ -0,0 +1,121 @@ +-- action_runs: critical! seq scan of 6180 rows filtering to 1, looped 25 times +CREATE INDEX "action_runs_automationRunId_idx" ON "action_runs"("automationRunId"); +CREATE INDEX "action_runs_pubId_idx" ON "action_runs"("pubId"); +CREATE INDEX "action_runs_userId_idx" ON "action_runs"("userId"); +CREATE INDEX "action_runs_actionInstanceId_idx" ON "action_runs"("actionInstanceId"); +CREATE INDEX "action_runs_status_idx" ON "action_runs"("status"); + +-- automation_runs: frequently filtered and sorted +CREATE INDEX "automation_runs_automationId_idx" ON "automation_runs"("automationId"); +CREATE INDEX "automation_runs_inputPubId_idx" ON "automation_runs"("inputPubId"); +CREATE INDEX "automation_runs_sourceUserId_idx" ON "automation_runs"("sourceUserId"); +CREATE INDEX "automation_runs_sourceAutomationRunId_idx" ON "automation_runs"("sourceAutomationRunId"); +CREATE INDEX "automation_runs_status_idx" ON "automation_runs"("status"); +-- composite for common query pattern: filter by automation, sort by createdAt +CREATE INDEX "automation_runs_automationId_createdAt_idx" ON "automation_runs"("automationId", "createdAt" DESC); + +-- automations: filtered by communityId and stageId +CREATE INDEX "automations_communityId_idx" ON "automations"("communityId"); +CREATE INDEX "automations_stageId_idx" ON "automations"("stageId"); + +-- automation_triggers: filtered by event and automationId +CREATE INDEX "automation_triggers_automationId_idx" ON "automation_triggers"("automationId"); +CREATE INDEX "automation_triggers_event_idx" ON "automation_triggers"("event"); +CREATE INDEX "automation_triggers_sourceAutomationId_idx" ON "automation_triggers"("sourceAutomationId"); + +-- action_instances: joined via automationId +CREATE INDEX "action_instances_automationId_idx" ON "action_instances"("automationId"); + +-- automation_condition_blocks: filtered by automationId +CREATE INDEX "automation_condition_blocks_automationId_idx" ON "automation_condition_blocks"("automationId"); +CREATE INDEX "automation_condition_blocks_parent_idx" ON "automation_condition_blocks"("automationConditionBlockId"); + +-- automation_conditions: joined via automationConditionBlockId +CREATE INDEX "automation_conditions_blockId_idx" ON "automation_conditions"("automationConditionBlockId"); + +-- pub_values: critical! seq scan removing 3491 rows, looped 12 times +-- note: partial unique indexes exist on (pubId, fieldId) and (pubId, relatedPubId, fieldId) +-- but we need a non-partial index on pubId for general lookups +CREATE INDEX "pub_values_pubId_idx" ON "pub_values"("pubId"); +CREATE INDEX "pub_values_fieldId_idx" ON "pub_values"("fieldId"); +CREATE INDEX "pub_values_relatedPubId_idx" ON "pub_values"("relatedPubId"); + +-- pubs: commonly filtered by communityId and pubTypeId +CREATE INDEX "pubs_communityId_idx" ON "pubs"("communityId"); +CREATE INDEX "pubs_pubTypeId_idx" ON "pubs"("pubTypeId"); +CREATE INDEX "pubs_communityId_pubTypeId_idx" ON "pubs"("communityId", "pubTypeId"); + +-- pub_types: filtered by communityId +CREATE INDEX "pub_types_communityId_idx" ON "pub_types"("communityId"); + +-- pub_fields: filtered by communityId +CREATE INDEX "pub_fields_communityId_idx" ON "pub_fields"("communityId"); +CREATE INDEX "pub_fields_pubFieldSchemaId_idx" ON "pub_fields"("pubFieldSchemaId"); + +-- PubsInStages: composite PK exists but single column indexes help some queries +CREATE INDEX "PubsInStages_pubId_idx" ON "PubsInStages"("pubId"); +CREATE INDEX "PubsInStages_stageId_idx" ON "PubsInStages"("stageId"); + +-- membership tables: heavily used in authorization checks +CREATE INDEX "community_memberships_communityId_idx" ON "community_memberships"("communityId"); +CREATE INDEX "community_memberships_userId_idx" ON "community_memberships"("userId"); +CREATE INDEX "community_memberships_memberGroupId_idx" ON "community_memberships"("memberGroupId"); + +CREATE INDEX "pub_memberships_pubId_idx" ON "pub_memberships"("pubId"); +CREATE INDEX "pub_memberships_userId_idx" ON "pub_memberships"("userId"); +CREATE INDEX "pub_memberships_memberGroupId_idx" ON "pub_memberships"("memberGroupId"); + +CREATE INDEX "stage_memberships_stageId_idx" ON "stage_memberships"("stageId"); +CREATE INDEX "stage_memberships_userId_idx" ON "stage_memberships"("userId"); +CREATE INDEX "stage_memberships_memberGroupId_idx" ON "stage_memberships"("memberGroupId"); + +-- membership_capabilities: used in CTE for authorization +CREATE INDEX "membership_capabilities_role_type_idx" ON "membership_capabilities"("role", "type"); +CREATE INDEX "membership_capabilities_capability_idx" ON "membership_capabilities"("capability"); + +-- stages: filtered by communityId +CREATE INDEX "stages_communityId_idx" ON "stages"("communityId"); + +-- forms: commonly filtered +CREATE INDEX "forms_communityId_idx" ON "forms"("communityId"); +CREATE INDEX "forms_pubTypeId_idx" ON "forms"("pubTypeId"); + +-- form_elements: joined via formId and fieldId +CREATE INDEX "form_elements_formId_idx" ON "form_elements"("formId"); +CREATE INDEX "form_elements_fieldId_idx" ON "form_elements"("fieldId"); +CREATE INDEX "form_elements_stageId_idx" ON "form_elements"("stageId"); + +-- invites: commonly filtered +CREATE INDEX "invites_communityId_idx" ON "invites"("communityId"); +CREATE INDEX "invites_userId_idx" ON "invites"("userId"); +CREATE INDEX "invites_pubId_idx" ON "invites"("pubId"); +CREATE INDEX "invites_stageId_idx" ON "invites"("stageId"); +CREATE INDEX "invites_status_idx" ON "invites"("status"); + +-- invite_forms: joined via inviteId +CREATE INDEX "invite_forms_inviteId_idx" ON "invite_forms"("inviteId"); +CREATE INDEX "invite_forms_formId_idx" ON "invite_forms"("formId"); + +-- sessions and auth_tokens: filtered by userId +CREATE INDEX "sessions_userId_idx" ON "sessions"("userId"); +CREATE INDEX "auth_tokens_userId_idx" ON "auth_tokens"("userId"); + +-- member_groups: filtered by communityId +CREATE INDEX "member_groups_communityId_idx" ON "member_groups"("communityId"); + +-- api_access_tokens: filtered by communityId +CREATE INDEX "api_access_tokens_communityId_idx" ON "api_access_tokens"("communityId"); +CREATE INDEX "api_access_tokens_issuedById_idx" ON "api_access_tokens"("issuedById"); + +-- api_access_logs: joined via accessTokenId +CREATE INDEX "api_access_logs_accessTokenId_idx" ON "api_access_logs"("accessTokenId"); + +-- move_constraint: filtered by stageId +CREATE INDEX "move_constraint_stageId_idx" ON "move_constraint"("stageId"); +CREATE INDEX "move_constraint_destinationId_idx" ON "move_constraint"("destinationId"); + +-- history tables for audit queries +CREATE INDEX "pub_values_history_userId_idx" ON "pub_values_history"("userId"); +CREATE INDEX "pub_values_history_actionRunId_idx" ON "pub_values_history"("actionRunId"); +CREATE INDEX "invites_history_userId_idx" ON "invites_history"("userId"); +CREATE INDEX "invites_history_actionRunId_idx" ON "invites_history"("actionRunId"); From bc8b942a0e76e174d0e1d9ab2a594ff1de420690 Mon Sep 17 00:00:00 2001 From: "Thomas F. K. Jorna" Date: Mon, 12 Jan 2026 17:56:34 +0100 Subject: [PATCH 2/3] fix: also prisma --- core/prisma/consolidated-triggers.sql | 68 ++++++------ .../migration.sql | 100 +++++++++++++----- .../schema/capabilities/Membership.prisma | 12 +++ .../history-tables/InviteHistory.prisma | 2 + .../history-tables/PubValueHistory.prisma | 2 + core/prisma/schema/schema.prisma | 51 +++++++++ 6 files changed, 171 insertions(+), 64 deletions(-) diff --git a/core/prisma/consolidated-triggers.sql b/core/prisma/consolidated-triggers.sql index a68ef441a..5ec82d32b 100644 --- a/core/prisma/consolidated-triggers.sql +++ b/core/prisma/consolidated-triggers.sql @@ -763,37 +763,6 @@ CREATE TRIGGER trigger_pub_values_history -- Table: unknown -CREATE OR REPLACE FUNCTION notify_change_action_runs() - RETURNS TRIGGER AS -$$ -DECLARE - correct_row jsonb; - community_id text; -BEGIN - - -- Changed the first part of this conditional to return early if the operation is deleting a pub - IF (NEW."pubId" IS NULL) THEN - RETURN NEW; - ELSE - correct_row = to_jsonb(NEW); - END IF; - - - select into community_id "communityId" from "pubs" where "id" = correct_row->>'pubId'::text; - - PERFORM notify_change( - correct_row, - community_id, - TG_TABLE_NAME, - TG_OP - ); - - RETURN NEW; -END; -$$ -LANGUAGE plpgsql; - - CREATE OR REPLACE FUNCTION notify_change_automation_runs() RETURNS TRIGGER AS $$ @@ -822,12 +791,6 @@ $$ LANGUAGE plpgsql; -CREATE OR REPLACE TRIGGER action_runs_change_trigger - AFTER INSERT OR UPDATE -- Removed delete - ON action_runs - FOR EACH ROW - EXECUTE FUNCTION notify_change_action_runs(); - CREATE OR REPLACE TRIGGER automation_runs_change_trigger AFTER INSERT OR UPDATE -- Removed delete ON automation_runs @@ -1058,6 +1021,37 @@ $$ LANGUAGE plpgsql; +CREATE OR REPLACE FUNCTION notify_change_action_runs() + RETURNS TRIGGER AS +$$ +DECLARE + correct_row jsonb; + community_id text; +BEGIN + + -- Changed the first part of this conditional to return early if the operation is deleting a pub + IF (NEW."pubId" IS NULL) THEN + RETURN NEW; + ELSE + correct_row = to_jsonb(NEW); + END IF; + + + select into community_id "communityId" from "pubs" where "id" = correct_row->>'pubId'::text; + + PERFORM notify_change( + correct_row, + community_id, + TG_TABLE_NAME, + TG_OP + ); + + RETURN NEW; +END; +$$ +LANGUAGE plpgsql; + + CREATE OR REPLACE FUNCTION notify_change_generic() RETURNS TRIGGER AS $$ diff --git a/core/prisma/migrations/20260112120000_add_performance_indexes/migration.sql b/core/prisma/migrations/20260112120000_add_performance_indexes/migration.sql index 09da9cb49..71df0bfbf 100644 --- a/core/prisma/migrations/20260112120000_add_performance_indexes/migration.sql +++ b/core/prisma/migrations/20260112120000_add_performance_indexes/migration.sql @@ -1,121 +1,167 @@ --- action_runs: critical! seq scan of 6180 rows filtering to 1, looped 25 times +-- Performance optimization indices +-- action_runs: optimize queries by automationRunId, pubId, userId, actionInstanceId, and status CREATE INDEX "action_runs_automationRunId_idx" ON "action_runs"("automationRunId"); + CREATE INDEX "action_runs_pubId_idx" ON "action_runs"("pubId"); + CREATE INDEX "action_runs_userId_idx" ON "action_runs"("userId"); + CREATE INDEX "action_runs_actionInstanceId_idx" ON "action_runs"("actionInstanceId"); + CREATE INDEX "action_runs_status_idx" ON "action_runs"("status"); --- automation_runs: frequently filtered and sorted +-- automation_runs: optimize queries by automationId, inputPubId, sourceUserId, sourceAutomationRunId, and status CREATE INDEX "automation_runs_automationId_idx" ON "automation_runs"("automationId"); + CREATE INDEX "automation_runs_inputPubId_idx" ON "automation_runs"("inputPubId"); + CREATE INDEX "automation_runs_sourceUserId_idx" ON "automation_runs"("sourceUserId"); + CREATE INDEX "automation_runs_sourceAutomationRunId_idx" ON "automation_runs"("sourceAutomationRunId"); + CREATE INDEX "automation_runs_status_idx" ON "automation_runs"("status"); --- composite for common query pattern: filter by automation, sort by createdAt + +-- composite index for queries filtering by automation and sorted by creation time CREATE INDEX "automation_runs_automationId_createdAt_idx" ON "automation_runs"("automationId", "createdAt" DESC); --- automations: filtered by communityId and stageId +-- automations: optimize queries by communityId and stageId CREATE INDEX "automations_communityId_idx" ON "automations"("communityId"); + CREATE INDEX "automations_stageId_idx" ON "automations"("stageId"); --- automation_triggers: filtered by event and automationId +-- automation_triggers: optimize queries by event and automationId CREATE INDEX "automation_triggers_automationId_idx" ON "automation_triggers"("automationId"); + CREATE INDEX "automation_triggers_event_idx" ON "automation_triggers"("event"); + CREATE INDEX "automation_triggers_sourceAutomationId_idx" ON "automation_triggers"("sourceAutomationId"); --- action_instances: joined via automationId +-- action_instances: optimize queries by automationId CREATE INDEX "action_instances_automationId_idx" ON "action_instances"("automationId"); --- automation_condition_blocks: filtered by automationId +-- automation_condition_blocks: optimize queries by automationId CREATE INDEX "automation_condition_blocks_automationId_idx" ON "automation_condition_blocks"("automationId"); -CREATE INDEX "automation_condition_blocks_parent_idx" ON "automation_condition_blocks"("automationConditionBlockId"); --- automation_conditions: joined via automationConditionBlockId -CREATE INDEX "automation_conditions_blockId_idx" ON "automation_conditions"("automationConditionBlockId"); +CREATE INDEX "automation_condition_blocks_automationConditionBlockId_idx" ON "automation_condition_blocks"("automationConditionBlockId"); + +-- automation_conditions: optimize queries by automationConditionBlockId +CREATE INDEX "automation_conditions_automationConditionBlockId_idx" ON "automation_conditions"("automationConditionBlockId"); --- pub_values: critical! seq scan removing 3491 rows, looped 12 times +-- pub_values: optimize queries by pubId, fieldId, and relatedPubId -- note: partial unique indexes exist on (pubId, fieldId) and (pubId, relatedPubId, fieldId) -- but we need a non-partial index on pubId for general lookups CREATE INDEX "pub_values_pubId_idx" ON "pub_values"("pubId"); + CREATE INDEX "pub_values_fieldId_idx" ON "pub_values"("fieldId"); + CREATE INDEX "pub_values_relatedPubId_idx" ON "pub_values"("relatedPubId"); --- pubs: commonly filtered by communityId and pubTypeId +-- pubs: optimize queries by communityId and pubTypeId CREATE INDEX "pubs_communityId_idx" ON "pubs"("communityId"); + CREATE INDEX "pubs_pubTypeId_idx" ON "pubs"("pubTypeId"); + CREATE INDEX "pubs_communityId_pubTypeId_idx" ON "pubs"("communityId", "pubTypeId"); --- pub_types: filtered by communityId +-- pub_types: optimize queries by communityId CREATE INDEX "pub_types_communityId_idx" ON "pub_types"("communityId"); --- pub_fields: filtered by communityId +-- pub_fields: optimize queries by communityId CREATE INDEX "pub_fields_communityId_idx" ON "pub_fields"("communityId"); + CREATE INDEX "pub_fields_pubFieldSchemaId_idx" ON "pub_fields"("pubFieldSchemaId"); --- PubsInStages: composite PK exists but single column indexes help some queries +-- PubsInStages: optimize queries on pubId and stageId CREATE INDEX "PubsInStages_pubId_idx" ON "PubsInStages"("pubId"); + CREATE INDEX "PubsInStages_stageId_idx" ON "PubsInStages"("stageId"); --- membership tables: heavily used in authorization checks +-- membership tables: optimize queries for authorization checks CREATE INDEX "community_memberships_communityId_idx" ON "community_memberships"("communityId"); + CREATE INDEX "community_memberships_userId_idx" ON "community_memberships"("userId"); + CREATE INDEX "community_memberships_memberGroupId_idx" ON "community_memberships"("memberGroupId"); CREATE INDEX "pub_memberships_pubId_idx" ON "pub_memberships"("pubId"); + CREATE INDEX "pub_memberships_userId_idx" ON "pub_memberships"("userId"); + CREATE INDEX "pub_memberships_memberGroupId_idx" ON "pub_memberships"("memberGroupId"); CREATE INDEX "stage_memberships_stageId_idx" ON "stage_memberships"("stageId"); + CREATE INDEX "stage_memberships_userId_idx" ON "stage_memberships"("userId"); + CREATE INDEX "stage_memberships_memberGroupId_idx" ON "stage_memberships"("memberGroupId"); --- membership_capabilities: used in CTE for authorization +-- membership_capabilities: optimize queries for authorization CREATE INDEX "membership_capabilities_role_type_idx" ON "membership_capabilities"("role", "type"); + CREATE INDEX "membership_capabilities_capability_idx" ON "membership_capabilities"("capability"); --- stages: filtered by communityId +-- stages: optimize queries by communityId CREATE INDEX "stages_communityId_idx" ON "stages"("communityId"); --- forms: commonly filtered +-- forms: optimize queries by communityId and pubTypeId CREATE INDEX "forms_communityId_idx" ON "forms"("communityId"); + CREATE INDEX "forms_pubTypeId_idx" ON "forms"("pubTypeId"); --- form_elements: joined via formId and fieldId +-- form_elements: optimize queries by formId and fieldId CREATE INDEX "form_elements_formId_idx" ON "form_elements"("formId"); + CREATE INDEX "form_elements_fieldId_idx" ON "form_elements"("fieldId"); + CREATE INDEX "form_elements_stageId_idx" ON "form_elements"("stageId"); --- invites: commonly filtered +-- invites: optimize queries by communityId, userId, pubId, stageId, and status CREATE INDEX "invites_communityId_idx" ON "invites"("communityId"); + CREATE INDEX "invites_userId_idx" ON "invites"("userId"); + CREATE INDEX "invites_pubId_idx" ON "invites"("pubId"); + CREATE INDEX "invites_stageId_idx" ON "invites"("stageId"); + CREATE INDEX "invites_status_idx" ON "invites"("status"); --- invite_forms: joined via inviteId +-- invite_forms: optimize queries by inviteId CREATE INDEX "invite_forms_inviteId_idx" ON "invite_forms"("inviteId"); + CREATE INDEX "invite_forms_formId_idx" ON "invite_forms"("formId"); --- sessions and auth_tokens: filtered by userId +-- sessions and auth_tokens: optimize queries by userId CREATE INDEX "sessions_userId_idx" ON "sessions"("userId"); + CREATE INDEX "auth_tokens_userId_idx" ON "auth_tokens"("userId"); --- member_groups: filtered by communityId +-- member_groups: optimize queries by communityId CREATE INDEX "member_groups_communityId_idx" ON "member_groups"("communityId"); --- api_access_tokens: filtered by communityId +-- api_access_tokens: optimize queries by communityId CREATE INDEX "api_access_tokens_communityId_idx" ON "api_access_tokens"("communityId"); + CREATE INDEX "api_access_tokens_issuedById_idx" ON "api_access_tokens"("issuedById"); --- api_access_logs: joined via accessTokenId +-- api_access_logs: optimize queries by accessTokenId CREATE INDEX "api_access_logs_accessTokenId_idx" ON "api_access_logs"("accessTokenId"); --- move_constraint: filtered by stageId +-- move_constraint: optimize queries by stageId CREATE INDEX "move_constraint_stageId_idx" ON "move_constraint"("stageId"); + CREATE INDEX "move_constraint_destinationId_idx" ON "move_constraint"("destinationId"); -- history tables for audit queries CREATE INDEX "pub_values_history_userId_idx" ON "pub_values_history"("userId"); + CREATE INDEX "pub_values_history_actionRunId_idx" ON "pub_values_history"("actionRunId"); + CREATE INDEX "invites_history_userId_idx" ON "invites_history"("userId"); + CREATE INDEX "invites_history_actionRunId_idx" ON "invites_history"("actionRunId"); + +-- whoops, forgot to add this foreign key +ALTER TABLE "automation_runs" + ADD CONSTRAINT "automation_runs_inputPubId_fkey" FOREIGN KEY ("inputPubId") REFERENCES "pubs"("id") ON DELETE CASCADE ON UPDATE CASCADE; + diff --git a/core/prisma/schema/capabilities/Membership.prisma b/core/prisma/schema/capabilities/Membership.prisma index 7df3dc185..4671f72ac 100644 --- a/core/prisma/schema/capabilities/Membership.prisma +++ b/core/prisma/schema/capabilities/Membership.prisma @@ -19,6 +19,7 @@ model MemberGroup { pubMemberships PubMembership[] stageMemberships StageMembership[] + @@index([communityId]) @@map(name: "member_groups") } @@ -41,6 +42,9 @@ model CommunityMembership { // Indices defined manually in migration to handle null formId case // @@unique([communityId, userId, formId]) // @@unique([communityId, memberGroupId, formId]) + @@index([communityId]) + @@index([userId]) + @@index([memberGroupId]) @@map(name: "community_memberships") } @@ -64,6 +68,9 @@ model PubMembership { // Indices defined manually in migration to handle null formId case // @@unique([pubId, userId, formId]) // @@unique([pubId, memberGroupId, formId]) + @@index([pubId]) + @@index([userId]) + @@index([memberGroupId]) @@map(name: "pub_memberships") } @@ -87,6 +94,9 @@ model StageMembership { // Indices defined manually in migration to handle null formId case // @@unique([stageId, userId, formId]) // @@unique([stageId, memberGroupId, formId]) + @@index([stageId]) + @@index([userId]) + @@index([memberGroupId]) @@map(name: "stage_memberships") } @@ -102,5 +112,7 @@ model MembershipCapabilities { capability Capabilities @@id([role, type, capability]) + @@index([role, type]) + @@index([capability]) @@map(name: "membership_capabilities") } diff --git a/core/prisma/schema/history-tables/InviteHistory.prisma b/core/prisma/schema/history-tables/InviteHistory.prisma index d9a316e21..638c0e270 100644 --- a/core/prisma/schema/history-tables/InviteHistory.prisma +++ b/core/prisma/schema/history-tables/InviteHistory.prisma @@ -23,5 +23,7 @@ model InviteHistory { // set to `system` if the change was made by the system, eg during seeds other String? + @@index([userId]) + @@index([actionRunId]) @@map(name: "invites_history") } diff --git a/core/prisma/schema/history-tables/PubValueHistory.prisma b/core/prisma/schema/history-tables/PubValueHistory.prisma index ecf4e73d2..7c82b11c4 100644 --- a/core/prisma/schema/history-tables/PubValueHistory.prisma +++ b/core/prisma/schema/history-tables/PubValueHistory.prisma @@ -23,5 +23,7 @@ model PubValueHistory { // set to `system` if the change was made by the system, eg during seeds other String? + @@index([userId]) + @@index([actionRunId]) @@map(name: "pub_values_history") } diff --git a/core/prisma/schema/schema.prisma b/core/prisma/schema/schema.prisma index 2ebfef99d..0a2e3ae79 100644 --- a/core/prisma/schema/schema.prisma +++ b/core/prisma/schema/schema.prisma @@ -60,6 +60,7 @@ model Session { createdAt DateTime @default(now()) updatedAt DateTime @default(now()) @updatedAt + @@index([userId]) @@map(name: "sessions") } @@ -75,6 +76,7 @@ model AuthToken { userId String type AuthTokenType @default(generic) + @@index([userId]) @@map(name: "auth_tokens") } @@ -138,6 +140,9 @@ model Pub { automationRuns AutomationRun[] @@index([searchVector], type: Gin) + @@index([communityId]) + @@index([pubTypeId]) + @@index([communityId, pubTypeId]) @@map(name: "pubs") } @@ -159,6 +164,8 @@ model PubField { pubTypes PubFieldToPubType[] FormInput FormElement[] + @@index([communityId]) + @@index([pubFieldSchemaId]) @@map(name: "pub_fields") } @@ -211,6 +218,9 @@ model PubValue { // values within a pub rank String? // Uses "C" collation + @@index([pubId]) + @@index([fieldId]) + @@index([relatedPubId]) @@map(name: "pub_values") } @@ -235,6 +245,7 @@ model PubType { formElements FormElement[] @@unique([name, communityId]) + @@index([communityId]) @@map(name: "pub_types") } @@ -273,6 +284,7 @@ model Stage { Invite Invite[] automations Automation[] + @@index([communityId]) @@map(name: "stages") } @@ -285,6 +297,8 @@ model PubsInStages { updatedAt DateTime @default(now()) @updatedAt @@id([pubId, stageId]) + @@index([pubId]) + @@index([stageId]) } model MoveConstraint { @@ -296,6 +310,8 @@ model MoveConstraint { updatedAt DateTime @default(now()) @updatedAt @@id([stageId, destinationId], name: "moveConstraintId") + @@index([stageId]) + @@index([destinationId]) @@map(name: "move_constraint") } @@ -311,6 +327,7 @@ model ActionInstance { runs ActionRun[] + @@index([automationId]) @@map(name: "action_instances") } @@ -370,6 +387,12 @@ model AutomationRun { // action runs that were triggered by this action run sequentialAutomationRuns AutomationRun[] @relation("source_automation_run") + @@index([automationId]) + @@index([inputPubId]) + @@index([sourceUserId]) + @@index([sourceAutomationRunId]) + @@index([status]) + @@index([automationId, createdAt(sort: Desc)]) @@map(name: "automation_runs") } @@ -401,6 +424,11 @@ model ActionRun { InvitedByActionRun Invite[] @relation("invited_by_action_run") InviteHistory InviteHistory[] + @@index([automationRunId]) + @@index([pubId]) + @@index([userId]) + @@index([actionInstanceId]) + @@index([status]) @@map(name: "action_runs") } @@ -437,6 +465,8 @@ model Automation { automationRuns AutomationRun[] automationTriggers AutomationTrigger[] + @@index([communityId]) + @@index([stageId]) @@map(name: "automations") } @@ -464,6 +494,9 @@ model AutomationTrigger { createdAt DateTime @default(now()) updatedAt DateTime @default(now()) @updatedAt + @@index([automationId]) + @@index([event]) + @@index([sourceAutomationId]) @@map(name: "automation_triggers") } @@ -490,6 +523,8 @@ model AutomationConditionBlock { updatedAt DateTime @default(now()) @updatedAt AutomationCondition AutomationCondition[] + @@index([automationId]) + @@index([automationConditionBlockId]) @@map(name: "automation_condition_blocks") } @@ -511,6 +546,7 @@ model AutomationCondition { createdAt DateTime @default(now()) updatedAt DateTime @default(now()) @updatedAt + @@index([automationConditionBlockId]) @@map(name: "automation_conditions") } @@ -548,6 +584,8 @@ model Form { // FIXME: https://github.com/prisma/prisma/issues/6974 // Unique index manually set in migration // @@unique([isDefault, pubTypeId], where: { isDefault: true }) + @@index([communityId]) + @@index([pubTypeId]) @@map(name: "forms") } @@ -606,6 +644,9 @@ model FormElement { @@unique([type, label, formId]) @@unique([fieldId, formId]) + @@index([formId]) + @@index([fieldId]) + @@index([stageId]) @@map(name: "form_elements") } @@ -630,6 +671,8 @@ model ApiAccessToken { isSiteBuilderToken Boolean @default(false) @@index([token], name: "token_idx") + @@index([communityId]) + @@index([issuedById]) @@map(name: "api_access_tokens") } @@ -640,6 +683,7 @@ model ApiAccessLog { timestamp DateTime @default(now()) action String + @@index([accessTokenId]) @@map(name: "api_access_logs") } @@ -744,6 +788,11 @@ model Invite { // this table has some constraints that make it conform to the type in `db/types/Invite.ts` // see core/prisma/migrations/20250402130740_create_invite_table/migration.sql for details + @@index([communityId]) + @@index([userId]) + @@index([pubId]) + @@index([stageId]) + @@index([status]) @@map(name: "invites") } @@ -762,5 +811,7 @@ model InviteForm { @@id([inviteId, formId, type]) @@unique([inviteId, formId, type]) + @@index([inviteId]) + @@index([formId]) @@map(name: "invite_forms") } From 2f39659718f2c20ee7db91d8b93283cc87372f9b Mon Sep 17 00:00:00 2001 From: "Thomas F. K. Jorna" Date: Sat, 17 Jan 2026 11:20:03 +0100 Subject: [PATCH 3/3] meaningless change to trigger deploy --- core/prisma/consolidated-triggers.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/core/prisma/consolidated-triggers.sql b/core/prisma/consolidated-triggers.sql index 5ec82d32b..28beccf33 100644 --- a/core/prisma/consolidated-triggers.sql +++ b/core/prisma/consolidated-triggers.sql @@ -2,6 +2,7 @@ -- Table: PubsInStages + CREATE OR REPLACE FUNCTION cancel_scheduled_automations_on_pub_leave() RETURNS TRIGGER AS $$