Cal.com

Scheduling: users, teams, event types, bookings, availability, apps. DDL emitted from the Prisma schema, no database needed. Domains here are by table-name prefix, not by judgment.

121 tables · 1268 columns · 220 foreign keys · 23 domains  |  5 errors · 78 warnings · 378 notes

Generated from cal.com@v6.2.0.sql on 2026-09-05.

Conventions

Conventions are claims. Findings below are where the schema breaks them.

Findings

Errors (5)

F190Impersonations impersonatedById, impersonatedUserIdjunction-uniqueness

Junction table allows duplicate links

Impersonations looks like a many-to-many link table but has no unique constraint across (impersonatedById, impersonatedUserId). The same pair can be inserted twice; every join through it will double-count.

Fix Use the FK pair as the primary key (or add a UNIQUE).

ALTER TABLE Impersonations ADD PRIMARY KEY (impersonatedById, impersonatedUserId);
F003ManagedOrganizationprimary-key

No primary key

Without a primary key rows are not individually addressable: no safe UPDATE/DELETE of one row, no logical replication, ORMs misbehave, and duplicates are legal.

Fix Add a natural composite key or a surrogate id.

ALTER TABLE ManagedOrganization ADD PRIMARY KEY (...);
F438OrganizationOnboarding pricePerSeatmoney-float

Monetary value stored as float

pricePerSeat is binary floating point. 0.1 + 0.2 ≠ 0.3; sums drift; reconciliation against the ledger will be off by cents.

Fix Use NUMERIC(p, s) or integer minor units.

ALTER TABLE OrganizationOnboarding ALTER COLUMN pricePerSeat TYPE numeric(14,2);
F001UserPasswordprimary-key

No primary key

Without a primary key rows are not individually addressable: no safe UPDATE/DELETE of one row, no logical replication, ORMs misbehave, and duplicates are legal.

Fix Add a natural composite key or a surrogate id.

ALTER TABLE UserPassword ADD PRIMARY KEY (...);
F002avatarsprimary-key

No primary key

Without a primary key rows are not individually addressable: no safe UPDATE/DELETE of one row, no logical replication, ORMs misbehave, and duplicates are legal.

Fix Add a natural composite key or a surrogate id.

ALTER TABLE avatars ADD PRIMARY KEY (...);

Warnings (78)

F035AccessCode clientIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on OAuthClient must scan AccessCode to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to AccessCode maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often OAuthClient rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add a partial index, scoped to the rows that have a value. clientId is nullable, so the NULL rows are not indexed at all and the write cost is a fraction of a full index, while the constraint check still uses it.

CREATE INDEX CONCURRENTLY idx_AccessCode_clientId ON AccessCode(clientId) WHERE clientId IS NOT NULL;
F036AccessCode userIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on users must scan AccessCode to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to AccessCode maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often users rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add a partial index, scoped to the rows that have a value. userId is nullable, so the NULL rows are not indexed at all and the write cost is a fraction of a full index, while the constraint check still uses it.

CREATE INDEX CONCURRENTLY idx_AccessCode_userId ON AccessCode(userId) WHERE userId IS NOT NULL;
F037AccessCode teamIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on Team must scan AccessCode to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to AccessCode maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often Team rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add a partial index, scoped to the rows that have a value. teamId is nullable, so the NULL rows are not indexed at all and the write cost is a fraction of a full index, while the constraint check still uses it.

CREATE INDEX CONCURRENTLY idx_AccessCode_teamId ON AccessCode(teamId) WHERE teamId IS NOT NULL;
F042AccessToken userIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on users must scan AccessToken to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to AccessToken maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often users rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add an index on the FK column(s).

CREATE INDEX CONCURRENTLY idx_AccessToken_userId ON AccessToken(userId);
F043AccessToken platformOAuthClientIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on PlatformOAuthClient must scan AccessToken to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to AccessToken maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often PlatformOAuthClient rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add an index on the FK column(s).

CREATE INDEX CONCURRENTLY idx_AccessToken_platformOAuthClientId ON AccessToken(platformOAuthClientId);
F194Account typeundocumented-enum

Enum-like column with no CHECK

type is a short string that clearly takes a fixed set of values, but the database accepts anything. Typos become new states, and nobody can list the legal values without reading application code.

Fix Add a CHECK constraint (cheap, easy to evolve) or a lookup table if values need metadata. Avoid native ENUM types unless the set is truly frozen.

ALTER TABLE Account ADD CONSTRAINT Account_type_check CHECK (type IN (...));
F195Account token_typeundocumented-enum

Enum-like column with no CHECK

token_type is a short string that clearly takes a fixed set of values, but the database accepts anything. Typos become new states, and nobody can list the legal values without reading application code.

Fix Add a CHECK constraint (cheap, easy to evolve) or a lookup table if values need metadata. Avoid native ENUM types unless the set is truly frozen.

ALTER TABLE Account ADD CONSTRAINT Account_token_type_check CHECK (token_type IN (...));
F196Account scopeundocumented-enum

Enum-like column with no CHECK

scope is a short string that clearly takes a fixed set of values, but the database accepts anything. Typos become new states, and nobody can list the legal values without reading application code.

Fix Add a CHECK constraint (cheap, easy to evolve) or a lookup table if values need metadata. Avoid native ENUM types unless the set is truly frozen.

ALTER TABLE Account ADD CONSTRAINT Account_scope_check CHECK (scope IN (...));
F197Account session_stateundocumented-enum

Enum-like column with no CHECK

session_state is a short string that clearly takes a fixed set of values, but the database accepts anything. Typos become new states, and nobody can list the legal values without reading application code.

Fix Add a CHECK constraint (cheap, easy to evolve) or a lookup table if values need metadata. Avoid native ENUM types unless the set is truly frozen.

ALTER TABLE Account ADD CONSTRAINT Account_session_state_check CHECK (session_state IN (...));
F026ApiKey teamIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on Team must scan ApiKey to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to ApiKey maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often Team rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add a partial index, scoped to the rows that have a value. teamId is nullable, so the NULL rows are not indexed at all and the write cost is a fraction of a full index, while the constraint check still uses it.

CREATE INDEX CONCURRENTLY idx_ApiKey_teamId ON ApiKey(teamId) WHERE teamId IS NOT NULL;
F027ApiKey appIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on App must scan ApiKey to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to ApiKey maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often App rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add a partial index, scoped to the rows that have a value. appId is nullable, so the NULL rows are not indexed at all and the write cost is a fraction of a full index, while the constraint check still uses it.

CREATE INDEX CONCURRENTLY idx_ApiKey_appId ON ApiKey(appId) WHERE appId IS NOT NULL;
F028App_RoutingForms_Form updatedByIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on users must scan App_RoutingForms_Form to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to App_RoutingForms_Form maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often users rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add a partial index, scoped to the rows that have a value. updatedById is nullable, so the NULL rows are not indexed at all and the write cost is a fraction of a full index, while the constraint check still uses it.

CREATE INDEX CONCURRENTLY idx_App_RoutingForms_Form_updatedById ON App_RoutingForms_Form(updatedById) WHERE updatedById IS NOT NULL;
F029App_RoutingForms_Form teamIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on Team must scan App_RoutingForms_Form to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to App_RoutingForms_Form maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often Team rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add a partial index, scoped to the rows that have a value. teamId is nullable, so the NULL rows are not indexed at all and the write cost is a fraction of a full index, while the constraint check still uses it.

CREATE INDEX CONCURRENTLY idx_App_RoutingForms_Form_teamId ON App_RoutingForms_Form(teamId) WHERE teamId IS NOT NULL;

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on App_RoutingForms_Form must scan App_RoutingForms_IncompleteBookingActions to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to App_RoutingForms_IncompleteBookingActions maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often App_RoutingForms_Form rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add an index on the FK column(s).

CREATE INDEX CONCURRENTLY idx_App_RoutingForms_IncompleteBookingActions_formId ON App_RoutingForms_IncompleteBookingActions(formId);

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on App_RoutingForms_Form must scan App_RoutingForms_QueuedFormResponse to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to App_RoutingForms_QueuedFormResponse maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often App_RoutingForms_Form rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add an index on the FK column(s).

CREATE INDEX CONCURRENTLY idx_App_RoutingForms_QueuedFormResponse_formId ON App_RoutingForms_QueuedFormResponse(formId);
F048AttributeOption attributeIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on Attribute must scan AttributeOption to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to AttributeOption maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often Attribute rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add an index on the FK column(s).

CREATE INDEX CONCURRENTLY idx_AttributeOption_attributeId ON AttributeOption(attributeId);
F069AttributeSyncFieldMapping attributeIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on Attribute must scan AttributeSyncFieldMapping to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to AttributeSyncFieldMapping maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often Attribute rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add an index on the FK column(s).

CREATE INDEX CONCURRENTLY idx_AttributeSyncFieldMapping_attributeId ON AttributeSyncFieldMapping(attributeId);
F049AttributeToUser attributeOptionIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on AttributeOption must scan AttributeToUser to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to AttributeToUser maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often AttributeOption rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add an index on the FK column(s).

CREATE INDEX CONCURRENTLY idx_AttributeToUser_attributeOptionId ON AttributeToUser(attributeOptionId);
F050AttributeToUser createdByIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on users must scan AttributeToUser to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to AttributeToUser maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often users rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add a partial index, scoped to the rows that have a value. createdById is nullable, so the NULL rows are not indexed at all and the write cost is a fraction of a full index, while the constraint check still uses it.

CREATE INDEX CONCURRENTLY idx_AttributeToUser_createdById ON AttributeToUser(createdById) WHERE createdById IS NOT NULL;
F051AttributeToUser createdByDSyncIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on DSyncData must scan AttributeToUser to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to AttributeToUser maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often DSyncData rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add a partial index, scoped to the rows that have a value. createdByDSyncId is nullable, so the NULL rows are not indexed at all and the write cost is a fraction of a full index, while the constraint check still uses it.

CREATE INDEX CONCURRENTLY idx_AttributeToUser_createdByDSyncId ON AttributeToUser(createdByDSyncId) WHERE createdByDSyncId IS NOT NULL;
F052AttributeToUser updatedByIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on users must scan AttributeToUser to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to AttributeToUser maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often users rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add a partial index, scoped to the rows that have a value. updatedById is nullable, so the NULL rows are not indexed at all and the write cost is a fraction of a full index, while the constraint check still uses it.

CREATE INDEX CONCURRENTLY idx_AttributeToUser_updatedById ON AttributeToUser(updatedById) WHERE updatedById IS NOT NULL;
F053AttributeToUser updatedByDSyncIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on DSyncData must scan AttributeToUser to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to AttributeToUser maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often DSyncData rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add a partial index, scoped to the rows that have a value. updatedByDSyncId is nullable, so the NULL rows are not indexed at all and the write cost is a fraction of a full index, while the constraint check still uses it.

CREATE INDEX CONCURRENTLY idx_AttributeToUser_updatedByDSyncId ON AttributeToUser(updatedByDSyncId) WHERE updatedByDSyncId IS NOT NULL;
F019Booking reassignByIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on users must scan Booking to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to Booking maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often users rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add a partial index, scoped to the rows that have a value. reassignById is nullable, so the NULL rows are not indexed at all and the write cost is a fraction of a full index, while the constraint check still uses it.

CREATE INDEX CONCURRENTLY idx_Booking_reassignById ON Booking(reassignById) WHERE reassignById IS NOT NULL;
F062BookingInternalNote notePresetIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on InternalNotePreset must scan BookingInternalNote to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to BookingInternalNote maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often InternalNotePreset rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add a partial index, scoped to the rows that have a value. notePresetId is nullable, so the NULL rows are not indexed at all and the write cost is a fraction of a full index, while the constraint check still uses it.

CREATE INDEX CONCURRENTLY idx_BookingInternalNote_notePresetId ON BookingInternalNote(notePresetId) WHERE notePresetId IS NOT NULL;
F063BookingInternalNote createdByIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on users must scan BookingInternalNote to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to BookingInternalNote maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often users rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add an index on the FK column(s).

CREATE INDEX CONCURRENTLY idx_BookingInternalNote_createdById ON BookingInternalNote(createdById);
F016BookingReference credentialIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on Credential must scan BookingReference to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to BookingReference maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often Credential rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add a partial index, scoped to the rows that have a value. credentialId is nullable, so the NULL rows are not indexed at all and the write cost is a fraction of a full index, while the constraint check still uses it.

CREATE INDEX CONCURRENTLY idx_BookingReference_credentialId ON BookingReference(credentialId) WHERE credentialId IS NOT NULL;
F017BookingReference delegationCredentialIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on DelegationCredential must scan BookingReference to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to BookingReference maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often DelegationCredential rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add a partial index, scoped to the rows that have a value. delegationCredentialId is nullable, so the NULL rows are not indexed at all and the write cost is a fraction of a full index, while the constraint check still uses it.

CREATE INDEX CONCURRENTLY idx_BookingReference_delegationCredentialId ON BookingReference(delegationCredentialId) WHERE delegationCredentialId IS NOT NULL;
F018BookingReference domainWideDelegationCredentialIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on DomainWideDelegation must scan BookingReference to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to BookingReference maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often DomainWideDelegation rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add a partial index, scoped to the rows that have a value. domainWideDelegationCredentialId is nullable, so the NULL rows are not indexed at all and the write cost is a fraction of a full index, while the constraint check still uses it.

CREATE INDEX CONCURRENTLY idx_BookingReference_domainWideDelegationCredentialId ON BookingReference(domainWideDelegationCredentialId) WHERE domainWideDelegationCredentialId IS NOT NULL;
F193BookingReference typeundocumented-enum

Enum-like column with no CHECK

type is a short string that clearly takes a fixed set of values, but the database accepts anything. Typos become new states, and nobody can list the legal values without reading application code.

Fix Add a CHECK constraint (cheap, easy to evolve) or a lookup table if values need metadata. Avoid native ENUM types unless the set is truly frozen.

ALTER TABLE BookingReference ADD CONSTRAINT BookingReference_type_check CHECK (type IN (...));
F006Credential delegationCredentialIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on DelegationCredential must scan Credential to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to Credential maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often DelegationCredential rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add a partial index, scoped to the rows that have a value. delegationCredentialId is nullable, so the NULL rows are not indexed at all and the write cost is a fraction of a full index, while the constraint check still uses it.

CREATE INDEX CONCURRENTLY idx_Credential_delegationCredentialId ON Credential(delegationCredentialId) WHERE delegationCredentialId IS NOT NULL;
F192Credential typeundocumented-enum

Enum-like column with no CHECK

type is a short string that clearly takes a fixed set of values, but the database accepts anything. Typos become new states, and nobody can list the legal values without reading application code.

Fix Add a CHECK constraint (cheap, easy to evolve) or a lookup table if values need metadata. Avoid native ENUM types unless the set is truly frozen.

ALTER TABLE Credential ADD CONSTRAINT Credential_type_check CHECK (type IN (...));
F013CreditExpenseLog creditBalanceIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on CreditBalance must scan CreditExpenseLog to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to CreditExpenseLog maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often CreditBalance rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add an index on the FK column(s).

CREATE INDEX CONCURRENTLY idx_CreditExpenseLog_creditBalanceId ON CreditExpenseLog(creditBalanceId);
F014CreditExpenseLog bookingUidfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on Booking must scan CreditExpenseLog to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to CreditExpenseLog maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often Booking rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add a partial index, scoped to the rows that have a value. bookingUid is nullable, so the NULL rows are not indexed at all and the write cost is a fraction of a full index, while the constraint check still uses it.

CREATE INDEX CONCURRENTLY idx_CreditExpenseLog_bookingUid ON CreditExpenseLog(bookingUid) WHERE bookingUid IS NOT NULL;
F012CreditPurchaseLog creditBalanceIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on CreditBalance must scan CreditPurchaseLog to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to CreditPurchaseLog maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often CreditBalance rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add an index on the FK column(s).

CREATE INDEX CONCURRENTLY idx_CreditPurchaseLog_creditBalanceId ON CreditPurchaseLog(creditBalanceId);
F046DSyncTeamGroupMapping directoryIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on DSyncData must scan DSyncTeamGroupMapping to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to DSyncTeamGroupMapping maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often DSyncData rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add an index on the FK column(s).

CREATE INDEX CONCURRENTLY idx_DSyncTeamGroupMapping_directoryId ON DSyncTeamGroupMapping(directoryId);
F054DelegationCredential workspacePlatformIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on WorkspacePlatform must scan DelegationCredential to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to DelegationCredential maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often WorkspacePlatform rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add an index on the FK column(s).

CREATE INDEX CONCURRENTLY idx_DelegationCredential_workspacePlatformId ON DelegationCredential(workspacePlatformId);
F007DestinationCalendar delegationCredentialIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on DelegationCredential must scan DestinationCalendar to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to DestinationCalendar maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often DelegationCredential rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add a partial index, scoped to the rows that have a value. delegationCredentialId is nullable, so the NULL rows are not indexed at all and the write cost is a fraction of a full index, while the constraint check still uses it.

CREATE INDEX CONCURRENTLY idx_DestinationCalendar_delegationCredentialId ON DestinationCalendar(delegationCredentialId) WHERE delegationCredentialId IS NOT NULL;
F008DestinationCalendar domainWideDelegationCredentialIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on DomainWideDelegation must scan DestinationCalendar to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to DestinationCalendar maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often DomainWideDelegation rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add a partial index, scoped to the rows that have a value. domainWideDelegationCredentialId is nullable, so the NULL rows are not indexed at all and the write cost is a fraction of a full index, while the constraint check still uses it.

CREATE INDEX CONCURRENTLY idx_DestinationCalendar_domainWideDelegationCredentialId ON DestinationCalendar(domainWideDelegationCredentialId) WHERE domainWideDelegationCredentialId IS NOT NULL;
F055DomainWideDelegation workspacePlatformIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on WorkspacePlatform must scan DomainWideDelegation to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to DomainWideDelegation maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often WorkspacePlatform rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add an index on the FK column(s).

CREATE INDEX CONCURRENTLY idx_DomainWideDelegation_workspacePlatformId ON DomainWideDelegation(workspacePlatformId);
F005EventType instantMeetingScheduleIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on Schedule must scan EventType to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to EventType maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often Schedule rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add a partial index, scoped to the rows that have a value. instantMeetingScheduleId is nullable, so the NULL rows are not indexed at all and the write cost is a fraction of a full index, while the constraint check still uses it.

CREATE INDEX CONCURRENTLY idx_EventType_instantMeetingScheduleId ON EventType(instantMeetingScheduleId) WHERE instantMeetingScheduleId IS NOT NULL;
F056EventTypeTranslation createdByfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on users must scan EventTypeTranslation to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to EventTypeTranslation maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often users rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add an index on the FK column(s).

CREATE INDEX CONCURRENTLY idx_EventTypeTranslation_createdBy ON EventTypeTranslation(createdBy);
F057EventTypeTranslation updatedByfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on users must scan EventTypeTranslation to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to EventTypeTranslation maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often users rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add a partial index, scoped to the rows that have a value. updatedBy is nullable, so the NULL rows are not indexed at all and the write cost is a fraction of a full index, while the constraint check still uses it.

CREATE INDEX CONCURRENTLY idx_EventTypeTranslation_updatedBy ON EventTypeTranslation(updatedBy) WHERE updatedBy IS NOT NULL;
F060FilterSegment userIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on users must scan FilterSegment to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to FilterSegment maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often users rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add an index on the FK column(s).

CREATE INDEX CONCURRENTLY idx_FilterSegment_userId ON FilterSegment(userId);
F061FilterSegment teamIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on Team must scan FilterSegment to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to FilterSegment maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often Team rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add a partial index, scoped to the rows that have a value. teamId is nullable, so the NULL rows are not indexed at all and the write cost is a fraction of a full index, while the constraint check still uses it.

CREATE INDEX CONCURRENTLY idx_FilterSegment_teamId ON FilterSegment(teamId) WHERE teamId IS NOT NULL;
F004Host groupIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on HostGroup must scan Host to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to Host maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often HostGroup rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add a partial index, scoped to the rows that have a value. groupId is nullable, so the NULL rows are not indexed at all and the write cost is a fraction of a full index, while the constraint check still uses it.

CREATE INDEX CONCURRENTLY idx_Host_groupId ON Host(groupId) WHERE groupId IS NOT NULL;
F191HostLocation typeundocumented-enum

Enum-like column with no CHECK

type is a short string that clearly takes a fixed set of values, but the database accepts anything. Typos become new states, and nobody can list the legal values without reading application code.

Fix Add a CHECK constraint (cheap, easy to evolve) or a lookup table if values need metadata. Avoid native ENUM types unless the set is truly frozen.

ALTER TABLE HostLocation ADD CONSTRAINT HostLocation_type_check CHECK (type IN (...));
F015InstantMeetingToken teamIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on Team must scan InstantMeetingToken to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to InstantMeetingToken maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often Team rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add an index on the FK column(s).

CREATE INDEX CONCURRENTLY idx_InstantMeetingToken_teamId ON InstantMeetingToken(teamId);
F067MonthlyProration teamBillingIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on TeamBilling must scan MonthlyProration to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to MonthlyProration maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often TeamBilling rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add a partial index, scoped to the rows that have a value. teamBillingId is nullable, so the NULL rows are not indexed at all and the write cost is a fraction of a full index, while the constraint check still uses it.

CREATE INDEX CONCURRENTLY idx_MonthlyProration_teamBillingId ON MonthlyProration(teamBillingId) WHERE teamBillingId IS NOT NULL;
F068MonthlyProration organizationBillingIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on OrganizationBilling must scan MonthlyProration to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to MonthlyProration maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often OrganizationBilling rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add a partial index, scoped to the rows that have a value. organizationBillingId is nullable, so the NULL rows are not indexed at all and the write cost is a fraction of a full index, while the constraint check still uses it.

CREATE INDEX CONCURRENTLY idx_MonthlyProration_organizationBillingId ON MonthlyProration(organizationBillingId) WHERE organizationBillingId IS NOT NULL;
F200OrganizationBilling statusundocumented-enum

Enum-like column with no CHECK

status is a short string that clearly takes a fixed set of values, but the database accepts anything. Typos become new states, and nobody can list the legal values without reading application code.

Fix Add a CHECK constraint (cheap, easy to evolve) or a lookup table if values need metadata. Avoid native ENUM types unless the set is truly frozen.

ALTER TABLE OrganizationBilling ADD CONSTRAINT OrganizationBilling_status_check CHECK (status IN (...));
F058OrganizationOnboarding createdByIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on users must scan OrganizationOnboarding to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to OrganizationOnboarding maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often users rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add an index on the FK column(s).

CREATE INDEX CONCURRENTLY idx_OrganizationOnboarding_createdById ON OrganizationOnboarding(createdById);
F038OutOfOfficeEntry reasonIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on OutOfOfficeReason must scan OutOfOfficeEntry to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to OutOfOfficeEntry maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often OutOfOfficeReason rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add a partial index, scoped to the rows that have a value. reasonId is nullable, so the NULL rows are not indexed at all and the write cost is a fraction of a full index, while the constraint check still uses it.

CREATE INDEX CONCURRENTLY idx_OutOfOfficeEntry_reasonId ON OutOfOfficeEntry(reasonId) WHERE reasonId IS NOT NULL;
F039OutOfOfficeReason userIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on users must scan OutOfOfficeReason to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to OutOfOfficeReason maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often users rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add a partial index, scoped to the rows that have a value. userId is nullable, so the NULL rows are not indexed at all and the write cost is a fraction of a full index, while the constraint check still uses it.

CREATE INDEX CONCURRENTLY idx_OutOfOfficeReason_userId ON OutOfOfficeReason(userId) WHERE userId IS NOT NULL;
F022Payment appIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on App must scan Payment to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to Payment maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often App rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add a partial index, scoped to the rows that have a value. appId is nullable, so the NULL rows are not indexed at all and the write cost is a fraction of a full index, while the constraint check still uses it.

CREATE INDEX CONCURRENTLY idx_Payment_appId ON Payment(appId) WHERE appId IS NOT NULL;
F041PlatformAuthorizationToken platformOAuthClientIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on PlatformOAuthClient must scan PlatformAuthorizationToken to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to PlatformAuthorizationToken maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often PlatformOAuthClient rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add an index on the FK column(s).

CREATE INDEX CONCURRENTLY idx_PlatformAuthorizationToken_platformOAuthClientId ON PlatformAuthorizationToken(platformOAuthClientId);
F047PlatformBilling managerBillingIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on PlatformBilling must scan PlatformBilling to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to PlatformBilling maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often PlatformBilling rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add a partial index, scoped to the rows that have a value. managerBillingId is nullable, so the NULL rows are not indexed at all and the write cost is a fraction of a full index, while the constraint check still uses it.

CREATE INDEX CONCURRENTLY idx_PlatformBilling_managerBillingId ON PlatformBilling(managerBillingId) WHERE managerBillingId IS NOT NULL;
F040PlatformOAuthClient organizationIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on Team must scan PlatformOAuthClient to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to PlatformOAuthClient maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often Team rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add an index on the FK column(s).

CREATE INDEX CONCURRENTLY idx_PlatformOAuthClient_organizationId ON PlatformOAuthClient(organizationId);
F044RefreshToken userIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on users must scan RefreshToken to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to RefreshToken maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often users rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add an index on the FK column(s).

CREATE INDEX CONCURRENTLY idx_RefreshToken_userId ON RefreshToken(userId);
F045RefreshToken platformOAuthClientIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on PlatformOAuthClient must scan RefreshToken to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to RefreshToken maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often PlatformOAuthClient rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add an index on the FK column(s).

CREATE INDEX CONCURRENTLY idx_RefreshToken_platformOAuthClientId ON RefreshToken(platformOAuthClientId);
F064SeatChangeLog processedInProrationIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on MonthlyProration must scan SeatChangeLog to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to SeatChangeLog maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often MonthlyProration rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add a partial index, scoped to the rows that have a value. processedInProrationId is nullable, so the NULL rows are not indexed at all and the write cost is a fraction of a full index, while the constraint check still uses it.

CREATE INDEX CONCURRENTLY idx_SeatChangeLog_processedInProrationId ON SeatChangeLog(processedInProrationId) WHERE processedInProrationId IS NOT NULL;
F065SeatChangeLog teamBillingIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on TeamBilling must scan SeatChangeLog to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to SeatChangeLog maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often TeamBilling rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add a partial index, scoped to the rows that have a value. teamBillingId is nullable, so the NULL rows are not indexed at all and the write cost is a fraction of a full index, while the constraint check still uses it.

CREATE INDEX CONCURRENTLY idx_SeatChangeLog_teamBillingId ON SeatChangeLog(teamBillingId) WHERE teamBillingId IS NOT NULL;
F066SeatChangeLog organizationBillingIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on OrganizationBilling must scan SeatChangeLog to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to SeatChangeLog maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often OrganizationBilling rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add a partial index, scoped to the rows that have a value. organizationBillingId is nullable, so the NULL rows are not indexed at all and the write cost is a fraction of a full index, while the constraint check still uses it.

CREATE INDEX CONCURRENTLY idx_SeatChangeLog_organizationBillingId ON SeatChangeLog(organizationBillingId) WHERE organizationBillingId IS NOT NULL;
F020SelectedCalendar delegationCredentialIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on DelegationCredential must scan SelectedCalendar to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to SelectedCalendar maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often DelegationCredential rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add a partial index, scoped to the rows that have a value. delegationCredentialId is nullable, so the NULL rows are not indexed at all and the write cost is a fraction of a full index, while the constraint check still uses it.

CREATE INDEX CONCURRENTLY idx_SelectedCalendar_delegationCredentialId ON SelectedCalendar(delegationCredentialId) WHERE delegationCredentialId IS NOT NULL;
F021SelectedCalendar domainWideDelegationCredentialIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on DomainWideDelegation must scan SelectedCalendar to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to SelectedCalendar maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often DomainWideDelegation rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add a partial index, scoped to the rows that have a value. domainWideDelegationCredentialId is nullable, so the NULL rows are not indexed at all and the write cost is a fraction of a full index, while the constraint check still uses it.

CREATE INDEX CONCURRENTLY idx_SelectedCalendar_domainWideDelegationCredentialId ON SelectedCalendar(domainWideDelegationCredentialId) WHERE domainWideDelegationCredentialId IS NOT NULL;
F198Task typeundocumented-enum

Enum-like column with no CHECK

type is a short string that clearly takes a fixed set of values, but the database accepts anything. Typos become new states, and nobody can list the legal values without reading application code.

Fix Add a CHECK constraint (cheap, easy to evolve) or a lookup table if values need metadata. Avoid native ENUM types unless the set is truly frozen.

ALTER TABLE Task ADD CONSTRAINT Task_type_check CHECK (type IN (...));
F011Team createdByOAuthClientIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on PlatformOAuthClient must scan Team to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to Team maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often PlatformOAuthClient rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add a partial index, scoped to the rows that have a value. createdByOAuthClientId is nullable, so the NULL rows are not indexed at all and the write cost is a fraction of a full index, while the constraint check still uses it.

CREATE INDEX CONCURRENTLY idx_Team_createdByOAuthClientId ON Team(createdByOAuthClientId) WHERE createdByOAuthClientId IS NOT NULL;
F451Teamfk-cycle

Foreign-key cycle

Team → PlatformOAuthClient → Team. Rows must be inserted with a deferred constraint or a NULL-then-update dance; backups/restores and truncation have no valid order; ON DELETE CASCADE can loop.

Fix Break the cycle (move one FK to a link table) or mark one FK DEFERRABLE INITIALLY DEFERRED.

F199TeamBilling statusundocumented-enum

Enum-like column with no CHECK

status is a short string that clearly takes a fixed set of values, but the database accepts anything. Typos become new states, and nobody can list the legal values without reading application code.

Fix Add a CHECK constraint (cheap, easy to evolve) or a lookup table if values need metadata. Avoid native ENUM types unless the set is truly frozen.

ALTER TABLE TeamBilling ADD CONSTRAINT TeamBilling_status_check CHECK (status IN (...));
F034TeamFeatures featureIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on Feature must scan TeamFeatures to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to TeamFeatures maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often Feature rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add an index on the FK column(s).

CREATE INDEX CONCURRENTLY idx_TeamFeatures_featureId ON TeamFeatures(featureId);
F009TravelSchedule userIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on users must scan TravelSchedule to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to TravelSchedule maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often users rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add an index on the FK column(s).

CREATE INDEX CONCURRENTLY idx_TravelSchedule_userId ON TravelSchedule(userId);
F033UserFeatures featureIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on Feature must scan UserFeatures to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to UserFeatures maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often Feature rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add an index on the FK column(s).

CREATE INDEX CONCURRENTLY idx_UserFeatures_featureId ON UserFeatures(featureId);
F023Webhook teamIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on Team must scan Webhook to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to Webhook maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often Team rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add a partial index, scoped to the rows that have a value. teamId is nullable, so the NULL rows are not indexed at all and the write cost is a fraction of a full index, while the constraint check still uses it.

CREATE INDEX CONCURRENTLY idx_Webhook_teamId ON Webhook(teamId) WHERE teamId IS NOT NULL;
F024Webhook eventTypeIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on EventType must scan Webhook to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to Webhook maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often EventType rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add a partial index, scoped to the rows that have a value. eventTypeId is nullable, so the NULL rows are not indexed at all and the write cost is a fraction of a full index, while the constraint check still uses it.

CREATE INDEX CONCURRENTLY idx_Webhook_eventTypeId ON Webhook(eventTypeId) WHERE eventTypeId IS NOT NULL;
F025Webhook appIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on App must scan Webhook to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to Webhook maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often App rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add a partial index, scoped to the rows that have a value. appId is nullable, so the NULL rows are not indexed at all and the write cost is a fraction of a full index, while the constraint check still uses it.

CREATE INDEX CONCURRENTLY idx_Webhook_appId ON Webhook(appId) WHERE appId IS NOT NULL;
F031WebhookScheduledTriggers webhookIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on Webhook must scan WebhookScheduledTriggers to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to WebhookScheduledTriggers maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often Webhook rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add a partial index, scoped to the rows that have a value. webhookId is nullable, so the NULL rows are not indexed at all and the write cost is a fraction of a full index, while the constraint check still uses it.

CREATE INDEX CONCURRENTLY idx_WebhookScheduledTriggers_webhookId ON WebhookScheduledTriggers(webhookId) WHERE webhookId IS NOT NULL;
F032WebhookScheduledTriggers bookingIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on Booking must scan WebhookScheduledTriggers to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to WebhookScheduledTriggers maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often Booking rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add a partial index, scoped to the rows that have a value. bookingId is nullable, so the NULL rows are not indexed at all and the write cost is a fraction of a full index, while the constraint check still uses it.

CREATE INDEX CONCURRENTLY idx_WebhookScheduledTriggers_bookingId ON WebhookScheduledTriggers(bookingId) WHERE bookingId IS NOT NULL;
F010users organizationIdfk-index

Foreign key without index

PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on Team must scan users to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to users maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often Team rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.

Fix Add a partial index, scoped to the rows that have a value. organizationId is nullable, so the NULL rows are not indexed at all and the write cost is a fraction of a full index, while the constraint check still uses it.

CREATE INDEX CONCURRENTLY idx_users_organizationId ON users(organizationId) WHERE organizationId IS NOT NULL;
F452usersfk-cycle

Foreign-key cycle

users → Profile → users. Rows must be inserted with a deferred constraint or a NULL-then-update dance; backups/restores and truncation have no valid order; ON DELETE CASCADE can loop.

Fix Break the cycle (move one FK to a link table) or mark one FK DEFERRABLE INITIALLY DEFERRED.

Notes (378)

F145AccessCode clientIdfk-nullable

Nullable foreign key

AccessCode.clientId may be NULL, so the relationship to OAuthClient is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F146AccessCode userIdfk-nullable

Nullable foreign key

AccessCode.userId may be NULL, so the relationship to users is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F147AccessCode teamIdfk-nullable

Nullable foreign key

AccessCode.teamId may be NULL, so the relationship to Team is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F222AccessCode clientId, userId, teamIdexclusive-arc

Either/or foreign keys without a CHECK

AccessCode has several nullable FKs (clientId, userId, teamId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.

Fix Add a CHECK that exactly one is non-null, or restructure with a supertype.

ALTER TABLE AccessCode ADD CONSTRAINT AccessCode_one_target CHECK ((clientId IS NOT NULL)::int + (userId IS NOT NULL)::int + (teamId IS NOT NULL)::int = 1);
F324AccessCode expiresAttimestamp-tz

TIMESTAMP without time zone

expiresAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE AccessCode ALTER COLUMN expiresAt TYPE timestamptz;
F344AccessToken createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE AccessToken ALTER COLUMN createdAt TYPE timestamptz;
F345AccessToken expiresAttimestamp-tz

TIMESTAMP without time zone

expiresAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE AccessToken ALTER COLUMN expiresAt TYPE timestamptz;
F172Agent userIdfk-nullable

Nullable foreign key

Agent.userId may be NULL, so the relationship to users is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F173Agent teamIdfk-nullable

Nullable foreign key

Agent.teamId may be NULL, so the relationship to Team is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F227Agent userId, teamIdexclusive-arc

Either/or foreign keys without a CHECK

Agent has several nullable FKs (userId, teamId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.

Fix Add a CHECK that exactly one is non-null, or restructure with a supertype.

ALTER TABLE Agent ADD CONSTRAINT Agent_one_target CHECK ((userId IS NOT NULL)::int + (teamId IS NOT NULL)::int = 1);
F397Agent createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE Agent ALTER COLUMN createdAt TYPE timestamptz;
F398Agent updatedAttimestamp-tz

TIMESTAMP without time zone

updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE Agent ALTER COLUMN updatedAt TYPE timestamptz;
F125ApiKey teamIdfk-nullable

Nullable foreign key

ApiKey.teamId may be NULL, so the relationship to Team is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F126ApiKey appIdfk-nullable

Nullable foreign key

ApiKey.appId may be NULL, so the relationship to App is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F214ApiKey teamId, appIdexclusive-arc

Either/or foreign keys without a CHECK

ApiKey has several nullable FKs (teamId, appId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.

Fix Add a CHECK that exactly one is non-null, or restructure with a supertype.

ALTER TABLE ApiKey ADD CONSTRAINT ApiKey_one_target CHECK ((teamId IS NOT NULL)::int + (appId IS NOT NULL)::int = 1);
F288ApiKey createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE ApiKey ALTER COLUMN createdAt TYPE timestamptz;
F289ApiKey expiresAttimestamp-tz

TIMESTAMP without time zone

expiresAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE ApiKey ALTER COLUMN expiresAt TYPE timestamptz;
F290ApiKey lastUsedAttimestamp-tz

TIMESTAMP without time zone

lastUsedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE ApiKey ALTER COLUMN lastUsedAt TYPE timestamptz;
F295App createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE App ALTER COLUMN createdAt TYPE timestamptz;
F296App updatedAttimestamp-tz

TIMESTAMP without time zone

updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE App ALTER COLUMN updatedAt TYPE timestamptz;
F127App_RoutingForms_Form updatedByIdfk-nullable

Nullable foreign key

App_RoutingForms_Form.updatedById may be NULL, so the relationship to users is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F128App_RoutingForms_Form teamIdfk-nullable

Nullable foreign key

App_RoutingForms_Form.teamId may be NULL, so the relationship to Team is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F215App_RoutingForms_Form updatedById, teamIdexclusive-arc

Either/or foreign keys without a CHECK

App_RoutingForms_Form has several nullable FKs (updatedById, teamId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.

Fix Add a CHECK that exactly one is non-null, or restructure with a supertype.

ALTER TABLE App_RoutingForms_Form ADD CONSTRAINT App_RoutingForms_Form_one_target CHECK ((updatedById IS NOT NULL)::int + (teamId IS NOT NULL)::int = 1);
F297App_RoutingForms_Form createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE App_RoutingForms_Form ALTER COLUMN createdAt TYPE timestamptz;
F298App_RoutingForms_Form updatedAttimestamp-tz

TIMESTAMP without time zone

updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE App_RoutingForms_Form ALTER COLUMN updatedAt TYPE timestamptz;
F129App_RoutingForms_FormResponse routedToBookingUidfk-nullable

Nullable foreign key

App_RoutingForms_FormResponse.routedToBookingUid may be NULL, so the relationship to Booking is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F299App_RoutingForms_FormResponse createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE App_RoutingForms_FormResponse ALTER COLUMN createdAt TYPE timestamptz;
F300App_RoutingForms_FormResponse updatedAttimestamp-tz

TIMESTAMP without time zone

updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE App_RoutingForms_FormResponse ALTER COLUMN updatedAt TYPE timestamptz;
F130App_RoutingForms_QueuedFormResponse actualResponseIdfk-nullable

Nullable foreign key

App_RoutingForms_QueuedFormResponse.actualResponseId may be NULL, so the relationship to App_RoutingForms_FormResponse is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F301App_RoutingForms_QueuedFormResponse createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE App_RoutingForms_QueuedFormResponse ALTER COLUMN createdAt TYPE timestamptz;
F302App_RoutingForms_QueuedFormResponse updatedAttimestamp-tz

TIMESTAMP without time zone

updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE App_RoutingForms_QueuedFormResponse ALTER COLUMN updatedAt TYPE timestamptz;
F359AssignmentReason createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE AssignmentReason ALTER COLUMN createdAt TYPE timestamptz;
F107Attendee bookingIdfk-nullable

Nullable foreign key

Attendee.bookingId may be NULL, so the relationship to Booking is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F355Attribute createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE Attribute ALTER COLUMN createdAt TYPE timestamptz;
F356Attribute updatedAttimestamp-tz

TIMESTAMP without time zone

updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE Attribute ALTER COLUMN updatedAt TYPE timestamptz;
F434AttributeSyncFieldMapping createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE AttributeSyncFieldMapping ALTER COLUMN createdAt TYPE timestamptz;
F435AttributeSyncFieldMapping updatedAttimestamp-tz

TIMESTAMP without time zone

updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE AttributeSyncFieldMapping ALTER COLUMN updatedAt TYPE timestamptz;
F432AttributeSyncRule createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE AttributeSyncRule ALTER COLUMN createdAt TYPE timestamptz;
F433AttributeSyncRule updatedAttimestamp-tz

TIMESTAMP without time zone

updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE AttributeSyncRule ALTER COLUMN updatedAt TYPE timestamptz;
F153AttributeToUser createdByIdfk-nullable

Nullable foreign key

AttributeToUser.createdById may be NULL, so the relationship to users is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F154AttributeToUser createdByDSyncIdfk-nullable

Nullable foreign key

AttributeToUser.createdByDSyncId may be NULL, so the relationship to DSyncData is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F155AttributeToUser updatedByIdfk-nullable

Nullable foreign key

AttributeToUser.updatedById may be NULL, so the relationship to users is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F156AttributeToUser updatedByDSyncIdfk-nullable

Nullable foreign key

AttributeToUser.updatedByDSyncId may be NULL, so the relationship to DSyncData is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F224AttributeToUser createdById, createdByDSyncId, updatedById, updatedByDSyncIdexclusive-arc

Either/or foreign keys without a CHECK

AttributeToUser has several nullable FKs (createdById, createdByDSyncId, updatedById, updatedByDSyncId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.

Fix Add a CHECK that exactly one is non-null, or restructure with a supertype.

ALTER TABLE AttributeToUser ADD CONSTRAINT AttributeToUser_one_target CHECK ((createdById IS NOT NULL)::int + (createdByDSyncId IS NOT NULL)::int + (updatedById IS NOT NULL)::int + (updatedByDSyncId IS NOT NULL)::int = 1);
F357AttributeToUser createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE AttributeToUser ALTER COLUMN createdAt TYPE timestamptz;
F358AttributeToUser updatedAttimestamp-tz

TIMESTAMP without time zone

updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE AttributeToUser ALTER COLUMN updatedAt TYPE timestamptz;
F393AuditActor createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE AuditActor ALTER COLUMN createdAt TYPE timestamptz;
F112Availability userIdfk-nullable

Nullable foreign key

Availability.userId may be NULL, so the relationship to users is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F113Availability eventTypeIdfk-nullable

Nullable foreign key

Availability.eventTypeId may be NULL, so the relationship to EventType is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F114Availability scheduleIdfk-nullable

Nullable foreign key

Availability.scheduleId may be NULL, so the relationship to Schedule is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F211Availability userId, eventTypeId, scheduleIdexclusive-arc

Either/or foreign keys without a CHECK

Availability has several nullable FKs (userId, eventTypeId, scheduleId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.

Fix Add a CHECK that exactly one is non-null, or restructure with a supertype.

ALTER TABLE Availability ADD CONSTRAINT Availability_one_target CHECK ((userId IS NOT NULL)::int + (eventTypeId IS NOT NULL)::int + (scheduleId IS NOT NULL)::int = 1);
F108Booking userIdfk-nullable

Nullable foreign key

Booking.userId may be NULL, so the relationship to users is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F109Booking eventTypeIdfk-nullable

Nullable foreign key

Booking.eventTypeId may be NULL, so the relationship to EventType is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F110Booking destinationCalendarIdfk-nullable

Nullable foreign key

Booking.destinationCalendarId may be NULL, so the relationship to DestinationCalendar is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F111Booking reassignByIdfk-nullable

Nullable foreign key

Booking.reassignById may be NULL, so the relationship to users is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F210Booking userId, eventTypeId, destinationCalendarId, reassignByIdexclusive-arc

Either/or foreign keys without a CHECK

Booking has several nullable FKs (userId, eventTypeId, destinationCalendarId, reassignById) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.

Fix Add a CHECK that exactly one is non-null, or restructure with a supertype.

ALTER TABLE Booking ADD CONSTRAINT Booking_one_target CHECK ((userId IS NOT NULL)::int + (eventTypeId IS NOT NULL)::int + (destinationCalendarId IS NOT NULL)::int + (reassignById IS NOT NULL)::int = 1);
F270Booking startTimetimestamp-tz

TIMESTAMP without time zone

startTime stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE Booking ALTER COLUMN startTime TYPE timestamptz;
F271Booking endTimetimestamp-tz

TIMESTAMP without time zone

endTime stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE Booking ALTER COLUMN endTime TYPE timestamptz;
F272Booking createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE Booking ALTER COLUMN createdAt TYPE timestamptz;
F273Booking updatedAttimestamp-tz

TIMESTAMP without time zone

updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE Booking ALTER COLUMN updatedAt TYPE timestamptz;
F455Bookingblast-radius

Hub table: referenced by 16 tables

Any change to its key, its delete semantics, or its partitioning touches every dependent. Migrations on hub tables need the longest lock windows and the most careful rollout.

Fix Treat schema changes here as breaking changes with a written rollout plan.

F459Bookingwide-table

Wide table (40 columns)

Tables this wide usually hide several entities (or a JSON column that wants to be one). Every row update rewrites the whole tuple; TOAST kicks in; indexes bloat.

Fix Look for column groups that always change together and split them out.

F394BookingAudit timestamptimestamp-tz

TIMESTAMP without time zone

timestamp stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE BookingAudit ALTER COLUMN timestamp TYPE timestamptz;
F395BookingAudit createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE BookingAudit ALTER COLUMN createdAt TYPE timestamptz;
F396BookingAudit updatedAttimestamp-tz

TIMESTAMP without time zone

updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE BookingAudit ALTER COLUMN updatedAt TYPE timestamptz;
F325BookingDenormalized startTimetimestamp-tz

TIMESTAMP without time zone

startTime stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE BookingDenormalized ALTER COLUMN startTime TYPE timestamptz;
F326BookingDenormalized endTimetimestamp-tz

TIMESTAMP without time zone

endTime stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE BookingDenormalized ALTER COLUMN endTime TYPE timestamptz;
F327BookingDenormalized createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE BookingDenormalized ALTER COLUMN createdAt TYPE timestamptz;
F328BookingDenormalized updatedAttimestamp-tz

TIMESTAMP without time zone

updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE BookingDenormalized ALTER COLUMN updatedAt TYPE timestamptz;
F444BookingDenormalizedorphan-table

Isolated table

BookingDenormalized references nothing and nothing references it. Either it is a staging/log table (fine, say so), or it is dead, or it is the seed of a second data model growing beside the first.

Fix Document its purpose or drop it.

F170BookingInternalNote notePresetIdfk-nullable

Nullable foreign key

BookingInternalNote.notePresetId may be NULL, so the relationship to InternalNotePreset is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F387BookingInternalNote createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE BookingInternalNote ALTER COLUMN createdAt TYPE timestamptz;
F103BookingReference bookingIdfk-nullable

Nullable foreign key

BookingReference.bookingId may be NULL, so the relationship to Booking is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F104BookingReference credentialIdfk-nullable

Nullable foreign key

BookingReference.credentialId may be NULL, so the relationship to Credential is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F105BookingReference delegationCredentialIdfk-nullable

Nullable foreign key

BookingReference.delegationCredentialId may be NULL, so the relationship to DelegationCredential is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F106BookingReference domainWideDelegationCredentialIdfk-nullable

Nullable foreign key

BookingReference.domainWideDelegationCredentialId may be NULL, so the relationship to DomainWideDelegation is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F209BookingReference bookingId, credentialId, delegationCredentialId, domainWideDelegationCredentialIdexclusive-arc

Either/or foreign keys without a CHECK

BookingReference has several nullable FKs (bookingId, credentialId, delegationCredentialId, domainWideDelegationCredentialId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.

Fix Add a CHECK that exactly one is non-null, or restructure with a supertype.

ALTER TABLE BookingReference ADD CONSTRAINT BookingReference_one_target CHECK ((bookingId IS NOT NULL)::int + (credentialId IS NOT NULL)::int + (delegationCredentialId IS NOT NULL)::int + (domainWideDelegationCredentialId IS NOT NULL)::int = 1);
F159BookingReport reportedByIdfk-nullable

Nullable foreign key

BookingReport.reportedById may be NULL, so the relationship to users is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F160BookingReport organizationIdfk-nullable

Nullable foreign key

BookingReport.organizationId may be NULL, so the relationship to Team is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F161BookingReport watchlistIdfk-nullable

Nullable foreign key

BookingReport.watchlistId may be NULL, so the relationship to Watchlist is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F162BookingReport globalWatchlistIdfk-nullable

Nullable foreign key

BookingReport.globalWatchlistId may be NULL, so the relationship to Watchlist is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F225BookingReport reportedById, organizationId, watchlistId, globalWatchlistIdexclusive-arc

Either/or foreign keys without a CHECK

BookingReport has several nullable FKs (reportedById, organizationId, watchlistId, globalWatchlistId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.

Fix Add a CHECK that exactly one is non-null, or restructure with a supertype.

ALTER TABLE BookingReport ADD CONSTRAINT BookingReport_one_target CHECK ((reportedById IS NOT NULL)::int + (organizationId IS NOT NULL)::int + (watchlistId IS NOT NULL)::int + (globalWatchlistId IS NOT NULL)::int = 1);
F375BookingReport createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE BookingReport ALTER COLUMN createdAt TYPE timestamptz;
F376BookingReport updatedAttimestamp-tz

TIMESTAMP without time zone

updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE BookingReport ALTER COLUMN updatedAt TYPE timestamptz;
F174CalAiPhoneNumber userIdfk-nullable

Nullable foreign key

CalAiPhoneNumber.userId may be NULL, so the relationship to users is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F175CalAiPhoneNumber teamIdfk-nullable

Nullable foreign key

CalAiPhoneNumber.teamId may be NULL, so the relationship to Team is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F176CalAiPhoneNumber inboundAgentIdfk-nullable

Nullable foreign key

CalAiPhoneNumber.inboundAgentId may be NULL, so the relationship to Agent is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F177CalAiPhoneNumber outboundAgentIdfk-nullable

Nullable foreign key

CalAiPhoneNumber.outboundAgentId may be NULL, so the relationship to Agent is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F228CalAiPhoneNumber userId, teamId, inboundAgentId, outboundAgentIdexclusive-arc

Either/or foreign keys without a CHECK

CalAiPhoneNumber has several nullable FKs (userId, teamId, inboundAgentId, outboundAgentId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.

Fix Add a CHECK that exactly one is non-null, or restructure with a supertype.

ALTER TABLE CalAiPhoneNumber ADD CONSTRAINT CalAiPhoneNumber_one_target CHECK ((userId IS NOT NULL)::int + (teamId IS NOT NULL)::int + (inboundAgentId IS NOT NULL)::int + (outboundAgentId IS NOT NULL)::int = 1);
F399CalAiPhoneNumber createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE CalAiPhoneNumber ALTER COLUMN createdAt TYPE timestamptz;
F400CalAiPhoneNumber updatedAttimestamp-tz

TIMESTAMP without time zone

updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE CalAiPhoneNumber ALTER COLUMN updatedAt TYPE timestamptz;
F238CalVideoSettings createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE CalVideoSettings ALTER COLUMN createdAt TYPE timestamptz;
F239CalVideoSettings updatedAttimestamp-tz

TIMESTAMP without time zone

updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE CalVideoSettings ALTER COLUMN updatedAt TYPE timestamptz;
F329CalendarCache expiresAttimestamp-tz

TIMESTAMP without time zone

expiresAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE CalendarCache ALTER COLUMN expiresAt TYPE timestamptz;
F330CalendarCache updatedAttimestamp-tz

TIMESTAMP without time zone

updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE CalendarCache ALTER COLUMN updatedAt TYPE timestamptz;
F423CalendarCacheEvent starttimestamp-tz

TIMESTAMP without time zone

start stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE CalendarCacheEvent ALTER COLUMN start TYPE timestamptz;
F424CalendarCacheEvent endtimestamp-tz

TIMESTAMP without time zone

end stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE CalendarCacheEvent ALTER COLUMN end TYPE timestamptz;
F425CalendarCacheEvent originalStartTimetimestamp-tz

TIMESTAMP without time zone

originalStartTime stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE CalendarCacheEvent ALTER COLUMN originalStartTime TYPE timestamptz;
F426CalendarCacheEvent createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE CalendarCacheEvent ALTER COLUMN createdAt TYPE timestamptz;
F427CalendarCacheEvent updatedAttimestamp-tz

TIMESTAMP without time zone

updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE CalendarCacheEvent ALTER COLUMN updatedAt TYPE timestamptz;
F428CalendarCacheEvent externalCreatedAttimestamp-tz

TIMESTAMP without time zone

externalCreatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE CalendarCacheEvent ALTER COLUMN externalCreatedAt TYPE timestamptz;
F429CalendarCacheEvent externalUpdatedAttimestamp-tz

TIMESTAMP without time zone

externalUpdatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE CalendarCacheEvent ALTER COLUMN externalUpdatedAt TYPE timestamptz;
F083Credential userIdfk-nullable

Nullable foreign key

Credential.userId may be NULL, so the relationship to users is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F084Credential teamIdfk-nullable

Nullable foreign key

Credential.teamId may be NULL, so the relationship to Team is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F085Credential appIdfk-nullable

Nullable foreign key

Credential.appId may be NULL, so the relationship to App is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F086Credential delegationCredentialIdfk-nullable

Nullable foreign key

Credential.delegationCredentialId may be NULL, so the relationship to DelegationCredential is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F203Credential userId, teamId, appId, delegationCredentialIdexclusive-arc

Either/or foreign keys without a CHECK

Credential has several nullable FKs (userId, teamId, appId, delegationCredentialId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.

Fix Add a CHECK that exactly one is non-null, or restructure with a supertype.

ALTER TABLE Credential ADD CONSTRAINT Credential_one_target CHECK ((userId IS NOT NULL)::int + (teamId IS NOT NULL)::int + (appId IS NOT NULL)::int + (delegationCredentialId IS NOT NULL)::int = 1);
F096CreditBalance teamIdfk-nullable

Nullable foreign key

CreditBalance.teamId may be NULL, so the relationship to Team is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F097CreditBalance userIdfk-nullable

Nullable foreign key

CreditBalance.userId may be NULL, so the relationship to users is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F207CreditBalance teamId, userIdexclusive-arc

Either/or foreign keys without a CHECK

CreditBalance has several nullable FKs (teamId, userId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.

Fix Add a CHECK that exactly one is non-null, or restructure with a supertype.

ALTER TABLE CreditBalance ADD CONSTRAINT CreditBalance_one_target CHECK ((teamId IS NOT NULL)::int + (userId IS NOT NULL)::int = 1);
F258CreditBalance limitReachedAttimestamp-tz

TIMESTAMP without time zone

limitReachedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE CreditBalance ALTER COLUMN limitReachedAt TYPE timestamptz;
F259CreditBalance warningSentAttimestamp-tz

TIMESTAMP without time zone

warningSentAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE CreditBalance ALTER COLUMN warningSentAt TYPE timestamptz;
F098CreditExpenseLog bookingUidfk-nullable

Nullable foreign key

CreditExpenseLog.bookingUid may be NULL, so the relationship to Booking is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F261CreditExpenseLog datetimestamp-tz

TIMESTAMP without time zone

date stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE CreditExpenseLog ALTER COLUMN date TYPE timestamptz;
F260CreditPurchaseLog createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE CreditPurchaseLog ALTER COLUMN createdAt TYPE timestamptz;
F151DSyncData organizationIdfk-nullable

Nullable foreign key

DSyncData.organizationId may be NULL, so the relationship to OrganizationSettings is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F360DelegationCredential lastEnabledAttimestamp-tz

TIMESTAMP without time zone

lastEnabledAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE DelegationCredential ALTER COLUMN lastEnabledAt TYPE timestamptz;
F361DelegationCredential lastDisabledAttimestamp-tz

TIMESTAMP without time zone

lastDisabledAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE DelegationCredential ALTER COLUMN lastDisabledAt TYPE timestamptz;
F362DelegationCredential createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE DelegationCredential ALTER COLUMN createdAt TYPE timestamptz;
F363DelegationCredential updatedAttimestamp-tz

TIMESTAMP without time zone

updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE DelegationCredential ALTER COLUMN updatedAt TYPE timestamptz;
F309Deployment agreedLicenseAttimestamp-tz

TIMESTAMP without time zone

agreedLicenseAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE Deployment ALTER COLUMN agreedLicenseAt TYPE timestamptz;
F442Deploymentorphan-table

Isolated table

Deployment references nothing and nothing references it. Either it is a staging/log table (fine, say so), or it is dead, or it is the seed of a second data model growing beside the first.

Fix Document its purpose or drop it.

F087DestinationCalendar userIdfk-nullable

Nullable foreign key

DestinationCalendar.userId may be NULL, so the relationship to users is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F088DestinationCalendar eventTypeIdfk-nullable

Nullable foreign key

DestinationCalendar.eventTypeId may be NULL, so the relationship to EventType is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F089DestinationCalendar credentialIdfk-nullable

Nullable foreign key

DestinationCalendar.credentialId may be NULL, so the relationship to Credential is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F090DestinationCalendar delegationCredentialIdfk-nullable

Nullable foreign key

DestinationCalendar.delegationCredentialId may be NULL, so the relationship to DelegationCredential is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F091DestinationCalendar domainWideDelegationCredentialIdfk-nullable

Nullable foreign key

DestinationCalendar.domainWideDelegationCredentialId may be NULL, so the relationship to DomainWideDelegation is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F204DestinationCalendar userId, eventTypeId, credentialId, delegationCredentialId, domainWideDelegationCredentialIdexclusive-arc

Either/or foreign keys without a CHECK

DestinationCalendar has several nullable FKs (userId, eventTypeId, credentialId, delegationCredentialId, domainWideDelegationCredentialId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.

Fix Add a CHECK that exactly one is non-null, or restructure with a supertype.

ALTER TABLE DestinationCalendar ADD CONSTRAINT DestinationCalendar_one_target CHECK ((userId IS NOT NULL)::int + (eventTypeId IS NOT NULL)::int + (credentialId IS NOT NULL)::int + (delegationCredentialId IS NOT NULL)::int + (domainWideDelegationCredentialId IS NOT NULL)::int = 1);
F247DestinationCalendar createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE DestinationCalendar ALTER COLUMN createdAt TYPE timestamptz;
F248DestinationCalendar updatedAttimestamp-tz

TIMESTAMP without time zone

updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE DestinationCalendar ALTER COLUMN updatedAt TYPE timestamptz;
F364DomainWideDelegation createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE DomainWideDelegation ALTER COLUMN createdAt TYPE timestamptz;
F365DomainWideDelegation updatedAttimestamp-tz

TIMESTAMP without time zone

updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE DomainWideDelegation ALTER COLUMN updatedAt TYPE timestamptz;
F075EventType userIdfk-nullable

Nullable foreign key

EventType.userId may be NULL, so the relationship to users is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F076EventType profileIdfk-nullable

Nullable foreign key

EventType.profileId may be NULL, so the relationship to Profile is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F077EventType teamIdfk-nullable

Nullable foreign key

EventType.teamId may be NULL, so the relationship to Team is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F078EventType parentIdfk-nullable

Nullable foreign key

EventType.parentId may be NULL, so the relationship to EventType is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F079EventType scheduleIdfk-nullable

Nullable foreign key

EventType.scheduleId may be NULL, so the relationship to Schedule is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F080EventType instantMeetingScheduleIdfk-nullable

Nullable foreign key

EventType.instantMeetingScheduleId may be NULL, so the relationship to Schedule is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F081EventType secondaryEmailIdfk-nullable

Nullable foreign key

EventType.secondaryEmailId may be NULL, so the relationship to SecondaryEmail is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F082EventType restrictionScheduleIdfk-nullable

Nullable foreign key

EventType.restrictionScheduleId may be NULL, so the relationship to Schedule is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F202EventType userId, profileId, teamId, parentId, scheduleId, instantMeetingScheduleId, secondaryEmailId, restrictionScheduleIdexclusive-arc

Either/or foreign keys without a CHECK

EventType has several nullable FKs (userId, profileId, teamId, parentId, scheduleId, instantMeetingScheduleId, secondaryEmailId, restrictionScheduleId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.

Fix Add a CHECK that exactly one is non-null, or restructure with a supertype.

ALTER TABLE EventType ADD CONSTRAINT EventType_one_target CHECK ((userId IS NOT NULL)::int + (profileId IS NOT NULL)::int + (teamId IS NOT NULL)::int + (parentId IS NOT NULL)::int + (scheduleId IS NOT NULL)::int + (instantMeetingScheduleId IS NOT NULL)::int + (secondaryEmailId IS NOT NULL)::int + (restrictionScheduleId IS NOT NULL)::int = 1);
F243EventType periodStartDatetimestamp-tz

TIMESTAMP without time zone

periodStartDate stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE EventType ALTER COLUMN periodStartDate TYPE timestamptz;
F244EventType periodEndDatetimestamp-tz

TIMESTAMP without time zone

periodEndDate stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE EventType ALTER COLUMN periodEndDate TYPE timestamptz;
F245EventType createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE EventType ALTER COLUMN createdAt TYPE timestamptz;
F246EventType updatedAttimestamp-tz

TIMESTAMP without time zone

updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE EventType ALTER COLUMN updatedAt TYPE timestamptz;
F456EventTypewide-table

Wide table (86 columns)

Tables this wide usually hide several entities (or a JSON column that wants to be one). Every row update rewrites the whole tuple; TOAST kicks in; indexes bloat.

Fix Look for column groups that always change together and split them out.

F157EventTypeTranslation updatedByfk-nullable

Nullable foreign key

EventTypeTranslation.updatedBy may be NULL, so the relationship to users is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F368EventTypeTranslation createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE EventTypeTranslation ALTER COLUMN createdAt TYPE timestamptz;
F369EventTypeTranslation updatedAttimestamp-tz

TIMESTAMP without time zone

updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE EventTypeTranslation ALTER COLUMN updatedAt TYPE timestamptz;
F313Feature lastUsedAttimestamp-tz

TIMESTAMP without time zone

lastUsedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE Feature ALTER COLUMN lastUsedAt TYPE timestamptz;
F314Feature createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE Feature ALTER COLUMN createdAt TYPE timestamptz;
F315Feature updatedAttimestamp-tz

TIMESTAMP without time zone

updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE Feature ALTER COLUMN updatedAt TYPE timestamptz;
F307Feedback datetimestamp-tz

TIMESTAMP without time zone

date stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE Feedback ALTER COLUMN date TYPE timestamptz;
F168FilterSegment teamIdfk-nullable

Nullable foreign key

FilterSegment.teamId may be NULL, so the relationship to Team is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F383FilterSegment createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE FilterSegment ALTER COLUMN createdAt TYPE timestamptz;
F384FilterSegment updatedAttimestamp-tz

TIMESTAMP without time zone

updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE FilterSegment ALTER COLUMN updatedAt TYPE timestamptz;
F293HashedLink expiresAttimestamp-tz

TIMESTAMP without time zone

expiresAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE HashedLink ALTER COLUMN expiresAt TYPE timestamptz;
F339HolidayCache datetimestamp-tz

TIMESTAMP without time zone

date stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE HolidayCache ALTER COLUMN date TYPE timestamptz;
F340HolidayCache createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE HolidayCache ALTER COLUMN createdAt TYPE timestamptz;
F341HolidayCache updatedAttimestamp-tz

TIMESTAMP without time zone

updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE HolidayCache ALTER COLUMN updatedAt TYPE timestamptz;
F447HolidayCacheorphan-table

Isolated table

HolidayCache references nothing and nothing references it. Either it is a staging/log table (fine, say so), or it is dead, or it is the seed of a second data model growing beside the first.

Fix Document its purpose or drop it.

F070Host scheduleIdfk-nullable

Nullable foreign key

Host.scheduleId may be NULL, so the relationship to Schedule is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F071Host groupIdfk-nullable

Nullable foreign key

Host.groupId may be NULL, so the relationship to HostGroup is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F072Host memberIdfk-nullable

Nullable foreign key

Host.memberId may be NULL, so the relationship to Membership is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F201Host scheduleId, groupId, memberIdexclusive-arc

Either/or foreign keys without a CHECK

Host has several nullable FKs (scheduleId, groupId, memberId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.

Fix Add a CHECK that exactly one is non-null, or restructure with a supertype.

ALTER TABLE Host ADD CONSTRAINT Host_one_target CHECK ((scheduleId IS NOT NULL)::int + (groupId IS NOT NULL)::int + (memberId IS NOT NULL)::int = 1);
F233Host createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE Host ALTER COLUMN createdAt TYPE timestamptz;
F073HostGroup eventTypeIdfk-nullable

Nullable foreign key

HostGroup.eventTypeId may be NULL, so the relationship to EventType is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F234HostGroup createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE HostGroup ALTER COLUMN createdAt TYPE timestamptz;
F235HostGroup updatedAttimestamp-tz

TIMESTAMP without time zone

updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE HostGroup ALTER COLUMN updatedAt TYPE timestamptz;
F074HostLocation credentialIdfk-nullable

Nullable foreign key

HostLocation.credentialId may be NULL, so the relationship to Credential is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F236HostLocation createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE HostLocation ALTER COLUMN createdAt TYPE timestamptz;
F237HostLocation updatedAttimestamp-tz

TIMESTAMP without time zone

updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE HostLocation ALTER COLUMN updatedAt TYPE timestamptz;
F287Impersonations createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE Impersonations ALTER COLUMN createdAt TYPE timestamptz;
F102InstantMeetingToken bookingIdfk-nullable

Nullable foreign key

InstantMeetingToken.bookingId may be NULL, so the relationship to Booking is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F267InstantMeetingToken expirestimestamp-tz

TIMESTAMP without time zone

expires stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE InstantMeetingToken ALTER COLUMN expires TYPE timestamptz;
F268InstantMeetingToken createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE InstantMeetingToken ALTER COLUMN createdAt TYPE timestamptz;
F269InstantMeetingToken updatedAttimestamp-tz

TIMESTAMP without time zone

updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE InstantMeetingToken ALTER COLUMN updatedAt TYPE timestamptz;
F183IntegrationAttributeSync credentialIdfk-nullable

Nullable foreign key

IntegrationAttributeSync.credentialId may be NULL, so the relationship to Credential is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F430IntegrationAttributeSync createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE IntegrationAttributeSync ALTER COLUMN createdAt TYPE timestamptz;
F431IntegrationAttributeSync updatedAttimestamp-tz

TIMESTAMP without time zone

updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE IntegrationAttributeSync ALTER COLUMN updatedAt TYPE timestamptz;
F382InternalNotePreset createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE InternalNotePreset ALTER COLUMN createdAt TYPE timestamptz;
F354ManagedOrganization createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE ManagedOrganization ALTER COLUMN createdAt TYPE timestamptz;
F099Membership customRoleIdfk-nullable

Nullable foreign key

Membership.customRoleId may be NULL, so the relationship to Role is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F262Membership createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE Membership ALTER COLUMN createdAt TYPE timestamptz;
F263Membership updatedAttimestamp-tz

TIMESTAMP without time zone

updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE Membership ALTER COLUMN updatedAt TYPE timestamptz;
F181MonthlyProration teamBillingIdfk-nullable

Nullable foreign key

MonthlyProration.teamBillingId may be NULL, so the relationship to TeamBilling is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F182MonthlyProration organizationBillingIdfk-nullable

Nullable foreign key

MonthlyProration.organizationBillingId may be NULL, so the relationship to OrganizationBilling is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F230MonthlyProration teamBillingId, organizationBillingIdexclusive-arc

Either/or foreign keys without a CHECK

MonthlyProration has several nullable FKs (teamBillingId, organizationBillingId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.

Fix Add a CHECK that exactly one is non-null, or restructure with a supertype.

ALTER TABLE MonthlyProration ADD CONSTRAINT MonthlyProration_one_target CHECK ((teamBillingId IS NOT NULL)::int + (organizationBillingId IS NOT NULL)::int = 1);
F415MonthlyProration periodStarttimestamp-tz

TIMESTAMP without time zone

periodStart stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE MonthlyProration ALTER COLUMN periodStart TYPE timestamptz;
F416MonthlyProration periodEndtimestamp-tz

TIMESTAMP without time zone

periodEnd stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE MonthlyProration ALTER COLUMN periodEnd TYPE timestamptz;
F417MonthlyProration subscriptionStarttimestamp-tz

TIMESTAMP without time zone

subscriptionStart stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE MonthlyProration ALTER COLUMN subscriptionStart TYPE timestamptz;
F418MonthlyProration subscriptionEndtimestamp-tz

TIMESTAMP without time zone

subscriptionEnd stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE MonthlyProration ALTER COLUMN subscriptionEnd TYPE timestamptz;
F419MonthlyProration chargedAttimestamp-tz

TIMESTAMP without time zone

chargedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE MonthlyProration ALTER COLUMN chargedAt TYPE timestamptz;
F420MonthlyProration failedAttimestamp-tz

TIMESTAMP without time zone

failedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE MonthlyProration ALTER COLUMN failedAt TYPE timestamptz;
F421MonthlyProration createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE MonthlyProration ALTER COLUMN createdAt TYPE timestamptz;
F422MonthlyProration updatedAttimestamp-tz

TIMESTAMP without time zone

updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE MonthlyProration ALTER COLUMN updatedAt TYPE timestamptz;
F461MonthlyProrationwide-table

Wide table (31 columns)

Tables this wide usually hide several entities (or a JSON column that wants to be one). Every row update rewrites the whole tuple; TOAST kicks in; indexes bloat.

Fix Look for column groups that always change together and split them out.

F144OAuthClient userIdfk-nullable

Nullable foreign key

OAuthClient.userId may be NULL, so the relationship to users is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F323OAuthClient createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE OAuthClient ALTER COLUMN createdAt TYPE timestamptz;
F407OrganizationBilling subscriptionStarttimestamp-tz

TIMESTAMP without time zone

subscriptionStart stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE OrganizationBilling ALTER COLUMN subscriptionStart TYPE timestamptz;
F408OrganizationBilling subscriptionTrialEndtimestamp-tz

TIMESTAMP without time zone

subscriptionTrialEnd stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE OrganizationBilling ALTER COLUMN subscriptionTrialEnd TYPE timestamptz;
F409OrganizationBilling subscriptionEndtimestamp-tz

TIMESTAMP without time zone

subscriptionEnd stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE OrganizationBilling ALTER COLUMN subscriptionEnd TYPE timestamptz;
F410OrganizationBilling highWaterMarkPeriodStarttimestamp-tz

TIMESTAMP without time zone

highWaterMarkPeriodStart stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE OrganizationBilling ALTER COLUMN highWaterMarkPeriodStart TYPE timestamptz;
F411OrganizationBilling createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE OrganizationBilling ALTER COLUMN createdAt TYPE timestamptz;
F412OrganizationBilling updatedAttimestamp-tz

TIMESTAMP without time zone

updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE OrganizationBilling ALTER COLUMN updatedAt TYPE timestamptz;
F167OrganizationOnboarding organizationIdfk-nullable

Nullable foreign key

OrganizationOnboarding.organizationId may be NULL, so the relationship to Team is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F380OrganizationOnboarding createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE OrganizationOnboarding ALTER COLUMN createdAt TYPE timestamptz;
F381OrganizationOnboarding updatedAttimestamp-tz

TIMESTAMP without time zone

updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE OrganizationOnboarding ALTER COLUMN updatedAt TYPE timestamptz;
F148OutOfOfficeEntry toUserIdfk-nullable

Nullable foreign key

OutOfOfficeEntry.toUserId may be NULL, so the relationship to users is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F149OutOfOfficeEntry reasonIdfk-nullable

Nullable foreign key

OutOfOfficeEntry.reasonId may be NULL, so the relationship to OutOfOfficeReason is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F223OutOfOfficeEntry toUserId, reasonIdexclusive-arc

Either/or foreign keys without a CHECK

OutOfOfficeEntry has several nullable FKs (toUserId, reasonId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.

Fix Add a CHECK that exactly one is non-null, or restructure with a supertype.

ALTER TABLE OutOfOfficeEntry ADD CONSTRAINT OutOfOfficeEntry_one_target CHECK ((toUserId IS NOT NULL)::int + (reasonId IS NOT NULL)::int = 1);
F333OutOfOfficeEntry starttimestamp-tz

TIMESTAMP without time zone

start stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE OutOfOfficeEntry ALTER COLUMN start TYPE timestamptz;
F334OutOfOfficeEntry endtimestamp-tz

TIMESTAMP without time zone

end stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE OutOfOfficeEntry ALTER COLUMN end TYPE timestamptz;
F335OutOfOfficeEntry createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE OutOfOfficeEntry ALTER COLUMN createdAt TYPE timestamptz;
F336OutOfOfficeEntry updatedAttimestamp-tz

TIMESTAMP without time zone

updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE OutOfOfficeEntry ALTER COLUMN updatedAt TYPE timestamptz;
F150OutOfOfficeReason userIdfk-nullable

Nullable foreign key

OutOfOfficeReason.userId may be NULL, so the relationship to users is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F119Payment appIdfk-nullable

Nullable foreign key

Payment.appId may be NULL, so the relationship to App is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F184PendingRoutingTrace formResponseIdfk-nullable

Nullable foreign key

PendingRoutingTrace.formResponseId may be NULL, so the relationship to App_RoutingForms_FormResponse is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F185PendingRoutingTrace queuedFormResponseIdfk-nullable

Nullable foreign key

PendingRoutingTrace.queuedFormResponseId may be NULL, so the relationship to App_RoutingForms_QueuedFormResponse is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F231PendingRoutingTrace formResponseId, queuedFormResponseIdexclusive-arc

Either/or foreign keys without a CHECK

PendingRoutingTrace has several nullable FKs (formResponseId, queuedFormResponseId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.

Fix Add a CHECK that exactly one is non-null, or restructure with a supertype.

ALTER TABLE PendingRoutingTrace ADD CONSTRAINT PendingRoutingTrace_one_target CHECK ((formResponseId IS NOT NULL)::int + (queuedFormResponseId IS NOT NULL)::int = 1);
F436PendingRoutingTrace createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE PendingRoutingTrace ALTER COLUMN createdAt TYPE timestamptz;
F343PlatformAuthorizationToken createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE PlatformAuthorizationToken ALTER COLUMN createdAt TYPE timestamptz;
F152PlatformBilling managerBillingIdfk-nullable

Nullable foreign key

PlatformBilling.managerBillingId may be NULL, so the relationship to PlatformBilling is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F342PlatformOAuthClient createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE PlatformOAuthClient ALTER COLUMN createdAt TYPE timestamptz;
F255Profile createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE Profile ALTER COLUMN createdAt TYPE timestamptz;
F256Profile updatedAttimestamp-tz

TIMESTAMP without time zone

updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE Profile ALTER COLUMN updatedAt TYPE timestamptz;
F291RateLimit createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE RateLimit ALTER COLUMN createdAt TYPE timestamptz;
F292RateLimit updatedAttimestamp-tz

TIMESTAMP without time zone

updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE RateLimit ALTER COLUMN updatedAt TYPE timestamptz;
F346RefreshToken createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE RefreshToken ALTER COLUMN createdAt TYPE timestamptz;
F347RefreshToken expiresAttimestamp-tz

TIMESTAMP without time zone

expiresAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE RefreshToken ALTER COLUMN expiresAt TYPE timestamptz;
F285ReminderMail createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE ReminderMail ALTER COLUMN createdAt TYPE timestamptz;
F441ReminderMailorphan-table

Isolated table

ReminderMail references nothing and nothing references it. Either it is a staging/log table (fine, say so), or it is dead, or it is the seed of a second data model growing beside the first.

Fix Document its purpose or drop it.

F282ResetPasswordRequest createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE ResetPasswordRequest ALTER COLUMN createdAt TYPE timestamptz;
F283ResetPasswordRequest updatedAttimestamp-tz

TIMESTAMP without time zone

updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE ResetPasswordRequest ALTER COLUMN updatedAt TYPE timestamptz;
F284ResetPasswordRequest expirestimestamp-tz

TIMESTAMP without time zone

expires stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE ResetPasswordRequest ALTER COLUMN expires TYPE timestamptz;
F440ResetPasswordRequestorphan-table

Isolated table

ResetPasswordRequest references nothing and nothing references it. Either it is a staging/log table (fine, say so), or it is dead, or it is the seed of a second data model growing beside the first.

Fix Document its purpose or drop it.

F171Role teamIdfk-nullable

Nullable foreign key

Role.teamId may be NULL, so the relationship to Team is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F390Role createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE Role ALTER COLUMN createdAt TYPE timestamptz;
F391Role updatedAttimestamp-tz

TIMESTAMP without time zone

updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE Role ALTER COLUMN updatedAt TYPE timestamptz;
F392RolePermission createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE RolePermission ALTER COLUMN createdAt TYPE timestamptz;
F131RoutingFormResponseDenormalized bookingIdfk-nullable

Nullable foreign key

RoutingFormResponseDenormalized.bookingId may be NULL, so the relationship to Booking is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F303RoutingFormResponseDenormalized bookingCreatedAttimestamp-tz

TIMESTAMP without time zone

bookingCreatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE RoutingFormResponseDenormalized ALTER COLUMN bookingCreatedAt TYPE timestamptz;
F304RoutingFormResponseDenormalized bookingStartTimetimestamp-tz

TIMESTAMP without time zone

bookingStartTime stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE RoutingFormResponseDenormalized ALTER COLUMN bookingStartTime TYPE timestamptz;
F305RoutingFormResponseDenormalized bookingEndTimetimestamp-tz

TIMESTAMP without time zone

bookingEndTime stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE RoutingFormResponseDenormalized ALTER COLUMN bookingEndTime TYPE timestamptz;
F306RoutingFormResponseDenormalized createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE RoutingFormResponseDenormalized ALTER COLUMN createdAt TYPE timestamptz;
F186RoutingTrace formResponseIdfk-nullable

Nullable foreign key

RoutingTrace.formResponseId may be NULL, so the relationship to App_RoutingForms_FormResponse is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F187RoutingTrace queuedFormResponseIdfk-nullable

Nullable foreign key

RoutingTrace.queuedFormResponseId may be NULL, so the relationship to App_RoutingForms_QueuedFormResponse is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F188RoutingTrace bookingUidfk-nullable

Nullable foreign key

RoutingTrace.bookingUid may be NULL, so the relationship to Booking is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F189RoutingTrace assignmentReasonIdfk-nullable

Nullable foreign key

RoutingTrace.assignmentReasonId may be NULL, so the relationship to AssignmentReason is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F232RoutingTrace formResponseId, queuedFormResponseId, bookingUid, assignmentReasonIdexclusive-arc

Either/or foreign keys without a CHECK

RoutingTrace has several nullable FKs (formResponseId, queuedFormResponseId, bookingUid, assignmentReasonId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.

Fix Add a CHECK that exactly one is non-null, or restructure with a supertype.

ALTER TABLE RoutingTrace ADD CONSTRAINT RoutingTrace_one_target CHECK ((formResponseId IS NOT NULL)::int + (queuedFormResponseId IS NOT NULL)::int + (bookingUid IS NOT NULL)::int + (assignmentReasonId IS NOT NULL)::int = 1);
F437RoutingTrace createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE RoutingTrace ALTER COLUMN createdAt TYPE timestamptz;
F178SeatChangeLog processedInProrationIdfk-nullable

Nullable foreign key

SeatChangeLog.processedInProrationId may be NULL, so the relationship to MonthlyProration is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F179SeatChangeLog teamBillingIdfk-nullable

Nullable foreign key

SeatChangeLog.teamBillingId may be NULL, so the relationship to TeamBilling is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F180SeatChangeLog organizationBillingIdfk-nullable

Nullable foreign key

SeatChangeLog.organizationBillingId may be NULL, so the relationship to OrganizationBilling is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F229SeatChangeLog processedInProrationId, teamBillingId, organizationBillingIdexclusive-arc

Either/or foreign keys without a CHECK

SeatChangeLog has several nullable FKs (processedInProrationId, teamBillingId, organizationBillingId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.

Fix Add a CHECK that exactly one is non-null, or restructure with a supertype.

ALTER TABLE SeatChangeLog ADD CONSTRAINT SeatChangeLog_one_target CHECK ((processedInProrationId IS NOT NULL)::int + (teamBillingId IS NOT NULL)::int + (organizationBillingId IS NOT NULL)::int = 1);
F413SeatChangeLog changeDatetimestamp-tz

TIMESTAMP without time zone

changeDate stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE SeatChangeLog ALTER COLUMN changeDate TYPE timestamptz;
F414SeatChangeLog createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE SeatChangeLog ALTER COLUMN createdAt TYPE timestamptz;
F348SecondaryEmail emailVerifiedtimestamp-tz

TIMESTAMP without time zone

emailVerified stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE SecondaryEmail ALTER COLUMN emailVerified TYPE timestamptz;
F115SelectedCalendar credentialIdfk-nullable

Nullable foreign key

SelectedCalendar.credentialId may be NULL, so the relationship to Credential is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F116SelectedCalendar delegationCredentialIdfk-nullable

Nullable foreign key

SelectedCalendar.delegationCredentialId may be NULL, so the relationship to DelegationCredential is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F117SelectedCalendar domainWideDelegationCredentialIdfk-nullable

Nullable foreign key

SelectedCalendar.domainWideDelegationCredentialId may be NULL, so the relationship to DomainWideDelegation is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F118SelectedCalendar eventTypeIdfk-nullable

Nullable foreign key

SelectedCalendar.eventTypeId may be NULL, so the relationship to EventType is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F212SelectedCalendar credentialId, delegationCredentialId, domainWideDelegationCredentialId, eventTypeIdexclusive-arc

Either/or foreign keys without a CHECK

SelectedCalendar has several nullable FKs (credentialId, delegationCredentialId, domainWideDelegationCredentialId, eventTypeId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.

Fix Add a CHECK that exactly one is non-null, or restructure with a supertype.

ALTER TABLE SelectedCalendar ADD CONSTRAINT SelectedCalendar_one_target CHECK ((credentialId IS NOT NULL)::int + (delegationCredentialId IS NOT NULL)::int + (domainWideDelegationCredentialId IS NOT NULL)::int + (eventTypeId IS NOT NULL)::int = 1);
F274SelectedCalendar createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE SelectedCalendar ALTER COLUMN createdAt TYPE timestamptz;
F275SelectedCalendar updatedAttimestamp-tz

TIMESTAMP without time zone

updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE SelectedCalendar ALTER COLUMN updatedAt TYPE timestamptz;
F276SelectedCalendar channelExpirationtimestamp-tz

TIMESTAMP without time zone

channelExpiration stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE SelectedCalendar ALTER COLUMN channelExpiration TYPE timestamptz;
F277SelectedCalendar syncSubscribedAttimestamp-tz

TIMESTAMP without time zone

syncSubscribedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE SelectedCalendar ALTER COLUMN syncSubscribedAt TYPE timestamptz;
F278SelectedCalendar syncSubscribedErrorAttimestamp-tz

TIMESTAMP without time zone

syncSubscribedErrorAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE SelectedCalendar ALTER COLUMN syncSubscribedErrorAt TYPE timestamptz;
F279SelectedCalendar syncedAttimestamp-tz

TIMESTAMP without time zone

syncedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE SelectedCalendar ALTER COLUMN syncedAt TYPE timestamptz;
F280SelectedCalendar syncErrorAttimestamp-tz

TIMESTAMP without time zone

syncErrorAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE SelectedCalendar ALTER COLUMN syncErrorAt TYPE timestamptz;
F281SelectedCalendar lastErrorAttimestamp-tz

TIMESTAMP without time zone

lastErrorAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE SelectedCalendar ALTER COLUMN lastErrorAt TYPE timestamptz;
F460SelectedCalendarwide-table

Wide table (32 columns)

Tables this wide usually hide several entities (or a JSON column that wants to be one). Every row update rewrites the whole tuple; TOAST kicks in; indexes bloat.

Fix Look for column groups that always change together and split them out.

F320SelectedSlots slotUtcStartDatetimestamp-tz

TIMESTAMP without time zone

slotUtcStartDate stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE SelectedSlots ALTER COLUMN slotUtcStartDate TYPE timestamptz;
F321SelectedSlots slotUtcEndDatetimestamp-tz

TIMESTAMP without time zone

slotUtcEndDate stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE SelectedSlots ALTER COLUMN slotUtcEndDate TYPE timestamptz;
F322SelectedSlots releaseAttimestamp-tz

TIMESTAMP without time zone

releaseAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE SelectedSlots ALTER COLUMN releaseAt TYPE timestamptz;
F443SelectedSlotsorphan-table

Isolated table

SelectedSlots references nothing and nothing references it. Either it is a staging/log table (fine, say so), or it is dead, or it is the seed of a second data model growing beside the first.

Fix Document its purpose or drop it.

F294Session expirestimestamp-tz

TIMESTAMP without time zone

expires stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE Session ALTER COLUMN expires TYPE timestamptz;
F349Task createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE Task ALTER COLUMN createdAt TYPE timestamptz;
F350Task updatedAttimestamp-tz

TIMESTAMP without time zone

updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE Task ALTER COLUMN updatedAt TYPE timestamptz;
F351Task scheduledAttimestamp-tz

TIMESTAMP without time zone

scheduledAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE Task ALTER COLUMN scheduledAt TYPE timestamptz;
F352Task succeededAttimestamp-tz

TIMESTAMP without time zone

succeededAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE Task ALTER COLUMN succeededAt TYPE timestamptz;
F353Task lastFailedAttemptAttimestamp-tz

TIMESTAMP without time zone

lastFailedAttemptAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE Task ALTER COLUMN lastFailedAttemptAt TYPE timestamptz;
F448Taskorphan-table

Isolated table

Task references nothing and nothing references it. Either it is a staging/log table (fine, say so), or it is dead, or it is the seed of a second data model growing beside the first.

Fix Document its purpose or drop it.

F094Team parentIdfk-nullable

Nullable foreign key

Team.parentId may be NULL, so the relationship to Team is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F095Team createdByOAuthClientIdfk-nullable

Nullable foreign key

Team.createdByOAuthClientId may be NULL, so the relationship to PlatformOAuthClient is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F206Team parentId, createdByOAuthClientIdexclusive-arc

Either/or foreign keys without a CHECK

Team has several nullable FKs (parentId, createdByOAuthClientId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.

Fix Add a CHECK that exactly one is non-null, or restructure with a supertype.

ALTER TABLE Team ADD CONSTRAINT Team_one_target CHECK ((parentId IS NOT NULL)::int + (createdByOAuthClientId IS NOT NULL)::int = 1);
F257Team createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE Team ALTER COLUMN createdAt TYPE timestamptz;
F454Teamblast-radius

Hub table: referenced by 40 tables

Any change to its key, its delete semantics, or its partitioning touches every dependent. Migrations on hub tables need the longest lock windows and the most careful rollout.

Fix Treat schema changes here as breaking changes with a written rollout plan.

F458Teamwide-table

Wide table (33 columns)

Tables this wide usually hide several entities (or a JSON column that wants to be one). Every row update rewrites the whole tuple; TOAST kicks in; indexes bloat.

Fix Look for column groups that always change together and split them out.

F401TeamBilling subscriptionStarttimestamp-tz

TIMESTAMP without time zone

subscriptionStart stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE TeamBilling ALTER COLUMN subscriptionStart TYPE timestamptz;
F402TeamBilling subscriptionTrialEndtimestamp-tz

TIMESTAMP without time zone

subscriptionTrialEnd stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE TeamBilling ALTER COLUMN subscriptionTrialEnd TYPE timestamptz;
F403TeamBilling subscriptionEndtimestamp-tz

TIMESTAMP without time zone

subscriptionEnd stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE TeamBilling ALTER COLUMN subscriptionEnd TYPE timestamptz;
F404TeamBilling highWaterMarkPeriodStarttimestamp-tz

TIMESTAMP without time zone

highWaterMarkPeriodStart stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE TeamBilling ALTER COLUMN highWaterMarkPeriodStart TYPE timestamptz;
F405TeamBilling createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE TeamBilling ALTER COLUMN createdAt TYPE timestamptz;
F406TeamBilling updatedAttimestamp-tz

TIMESTAMP without time zone

updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE TeamBilling ALTER COLUMN updatedAt TYPE timestamptz;
F318TeamFeatures assignedAttimestamp-tz

TIMESTAMP without time zone

assignedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE TeamFeatures ALTER COLUMN assignedAt TYPE timestamptz;
F319TeamFeatures updatedAttimestamp-tz

TIMESTAMP without time zone

updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE TeamFeatures ALTER COLUMN updatedAt TYPE timestamptz;
F331TempOrgRedirect createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE TempOrgRedirect ALTER COLUMN createdAt TYPE timestamptz;
F332TempOrgRedirect updatedAttimestamp-tz

TIMESTAMP without time zone

updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE TempOrgRedirect ALTER COLUMN updatedAt TYPE timestamptz;
F445TempOrgRedirectorphan-table

Isolated table

TempOrgRedirect references nothing and nothing references it. Either it is a staging/log table (fine, say so), or it is dead, or it is the seed of a second data model growing beside the first.

Fix Document its purpose or drop it.

F249TravelSchedule startDatetimestamp-tz

TIMESTAMP without time zone

startDate stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE TravelSchedule ALTER COLUMN startDate TYPE timestamptz;
F250TravelSchedule endDatetimestamp-tz

TIMESTAMP without time zone

endDate stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE TravelSchedule ALTER COLUMN endDate TYPE timestamptz;
F316UserFeatures assignedAttimestamp-tz

TIMESTAMP without time zone

assignedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE UserFeatures ALTER COLUMN assignedAt TYPE timestamptz;
F317UserFeatures updatedAttimestamp-tz

TIMESTAMP without time zone

updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE UserFeatures ALTER COLUMN updatedAt TYPE timestamptz;
F169UserFilterSegmentPreference segmentIdfk-nullable

Nullable foreign key

UserFilterSegmentPreference.segmentId may be NULL, so the relationship to FilterSegment is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F385UserFilterSegmentPreference createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE UserFilterSegmentPreference ALTER COLUMN createdAt TYPE timestamptz;
F386UserFilterSegmentPreference updatedAttimestamp-tz

TIMESTAMP without time zone

updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE UserFilterSegmentPreference ALTER COLUMN updatedAt TYPE timestamptz;
F337UserHolidaySettings createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE UserHolidaySettings ALTER COLUMN createdAt TYPE timestamptz;
F338UserHolidaySettings updatedAttimestamp-tz

TIMESTAMP without time zone

updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE UserHolidaySettings ALTER COLUMN updatedAt TYPE timestamptz;
F100VerificationToken teamIdfk-nullable

Nullable foreign key

VerificationToken.teamId may be NULL, so the relationship to Team is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F101VerificationToken secondaryEmailIdfk-nullable

Nullable foreign key

VerificationToken.secondaryEmailId may be NULL, so the relationship to SecondaryEmail is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F208VerificationToken teamId, secondaryEmailIdexclusive-arc

Either/or foreign keys without a CHECK

VerificationToken has several nullable FKs (teamId, secondaryEmailId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.

Fix Add a CHECK that exactly one is non-null, or restructure with a supertype.

ALTER TABLE VerificationToken ADD CONSTRAINT VerificationToken_one_target CHECK ((teamId IS NOT NULL)::int + (secondaryEmailId IS NOT NULL)::int = 1);
F264VerificationToken expirestimestamp-tz

TIMESTAMP without time zone

expires stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE VerificationToken ALTER COLUMN expires TYPE timestamptz;
F265VerificationToken createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE VerificationToken ALTER COLUMN createdAt TYPE timestamptz;
F266VerificationToken updatedAttimestamp-tz

TIMESTAMP without time zone

updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE VerificationToken ALTER COLUMN updatedAt TYPE timestamptz;
F142VerifiedEmail userIdfk-nullable

Nullable foreign key

VerifiedEmail.userId may be NULL, so the relationship to users is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F143VerifiedEmail teamIdfk-nullable

Nullable foreign key

VerifiedEmail.teamId may be NULL, so the relationship to Team is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F221VerifiedEmail userId, teamIdexclusive-arc

Either/or foreign keys without a CHECK

VerifiedEmail has several nullable FKs (userId, teamId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.

Fix Add a CHECK that exactly one is non-null, or restructure with a supertype.

ALTER TABLE VerifiedEmail ADD CONSTRAINT VerifiedEmail_one_target CHECK ((userId IS NOT NULL)::int + (teamId IS NOT NULL)::int = 1);
F140VerifiedNumber userIdfk-nullable

Nullable foreign key

VerifiedNumber.userId may be NULL, so the relationship to users is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F141VerifiedNumber teamIdfk-nullable

Nullable foreign key

VerifiedNumber.teamId may be NULL, so the relationship to Team is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F220VerifiedNumber userId, teamIdexclusive-arc

Either/or foreign keys without a CHECK

VerifiedNumber has several nullable FKs (userId, teamId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.

Fix Add a CHECK that exactly one is non-null, or restructure with a supertype.

ALTER TABLE VerifiedNumber ADD CONSTRAINT VerifiedNumber_one_target CHECK ((userId IS NOT NULL)::int + (teamId IS NOT NULL)::int = 1);
F240VideoCallGuest joinedAttimestamp-tz

TIMESTAMP without time zone

joinedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE VideoCallGuest ALTER COLUMN joinedAt TYPE timestamptz;
F241VideoCallGuest createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE VideoCallGuest ALTER COLUMN createdAt TYPE timestamptz;
F242VideoCallGuest updatedAttimestamp-tz

TIMESTAMP without time zone

updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE VideoCallGuest ALTER COLUMN updatedAt TYPE timestamptz;
F439VideoCallGuestorphan-table

Isolated table

VideoCallGuest references nothing and nothing references it. Either it is a staging/log table (fine, say so), or it is dead, or it is the seed of a second data model growing beside the first.

Fix Document its purpose or drop it.

F372Watchlist lastUpdatedAttimestamp-tz

TIMESTAMP without time zone

lastUpdatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE Watchlist ALTER COLUMN lastUpdatedAt TYPE timestamptz;
F158WatchlistAudit watchlistIdfk-nullable

Nullable foreign key

WatchlistAudit.watchlistId may be NULL, so the relationship to Watchlist is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F373WatchlistAudit changedAttimestamp-tz

TIMESTAMP without time zone

changedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE WatchlistAudit ALTER COLUMN changedAt TYPE timestamptz;
F374WatchlistEventAudit timestamptimestamp-tz

TIMESTAMP without time zone

timestamp stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE WatchlistEventAudit ALTER COLUMN timestamp TYPE timestamptz;
F449WatchlistEventAuditorphan-table

Isolated table

WatchlistEventAudit references nothing and nothing references it. Either it is a staging/log table (fine, say so), or it is dead, or it is the seed of a second data model growing beside the first.

Fix Document its purpose or drop it.

F120Webhook userIdfk-nullable

Nullable foreign key

Webhook.userId may be NULL, so the relationship to users is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F121Webhook teamIdfk-nullable

Nullable foreign key

Webhook.teamId may be NULL, so the relationship to Team is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F122Webhook eventTypeIdfk-nullable

Nullable foreign key

Webhook.eventTypeId may be NULL, so the relationship to EventType is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F123Webhook platformOAuthClientIdfk-nullable

Nullable foreign key

Webhook.platformOAuthClientId may be NULL, so the relationship to PlatformOAuthClient is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F124Webhook appIdfk-nullable

Nullable foreign key

Webhook.appId may be NULL, so the relationship to App is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F213Webhook userId, teamId, eventTypeId, platformOAuthClientId, appIdexclusive-arc

Either/or foreign keys without a CHECK

Webhook has several nullable FKs (userId, teamId, eventTypeId, platformOAuthClientId, appId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.

Fix Add a CHECK that exactly one is non-null, or restructure with a supertype.

ALTER TABLE Webhook ADD CONSTRAINT Webhook_one_target CHECK ((userId IS NOT NULL)::int + (teamId IS NOT NULL)::int + (eventTypeId IS NOT NULL)::int + (platformOAuthClientId IS NOT NULL)::int + (appId IS NOT NULL)::int = 1);
F286Webhook createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE Webhook ALTER COLUMN createdAt TYPE timestamptz;
F138WebhookScheduledTriggers webhookIdfk-nullable

Nullable foreign key

WebhookScheduledTriggers.webhookId may be NULL, so the relationship to Webhook is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F139WebhookScheduledTriggers bookingIdfk-nullable

Nullable foreign key

WebhookScheduledTriggers.bookingId may be NULL, so the relationship to Booking is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F219WebhookScheduledTriggers webhookId, bookingIdexclusive-arc

Either/or foreign keys without a CHECK

WebhookScheduledTriggers has several nullable FKs (webhookId, bookingId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.

Fix Add a CHECK that exactly one is non-null, or restructure with a supertype.

ALTER TABLE WebhookScheduledTriggers ADD CONSTRAINT WebhookScheduledTriggers_one_target CHECK ((webhookId IS NOT NULL)::int + (bookingId IS NOT NULL)::int = 1);
F311WebhookScheduledTriggers startAftertimestamp-tz

TIMESTAMP without time zone

startAfter stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE WebhookScheduledTriggers ALTER COLUMN startAfter TYPE timestamptz;
F312WebhookScheduledTriggers createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE WebhookScheduledTriggers ALTER COLUMN createdAt TYPE timestamptz;
F134Workflow userIdfk-nullable

Nullable foreign key

Workflow.userId may be NULL, so the relationship to users is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F135Workflow teamIdfk-nullable

Nullable foreign key

Workflow.teamId may be NULL, so the relationship to Team is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F217Workflow userId, teamIdexclusive-arc

Either/or foreign keys without a CHECK

Workflow has several nullable FKs (userId, teamId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.

Fix Add a CHECK that exactly one is non-null, or restructure with a supertype.

ALTER TABLE Workflow ADD CONSTRAINT Workflow_one_target CHECK ((userId IS NOT NULL)::int + (teamId IS NOT NULL)::int = 1);
F388WorkflowOptOutContact createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE WorkflowOptOutContact ALTER COLUMN createdAt TYPE timestamptz;
F389WorkflowOptOutContact updatedAttimestamp-tz

TIMESTAMP without time zone

updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE WorkflowOptOutContact ALTER COLUMN updatedAt TYPE timestamptz;
F450WorkflowOptOutContactorphan-table

Isolated table

WorkflowOptOutContact references nothing and nothing references it. Either it is a staging/log table (fine, say so), or it is dead, or it is the seed of a second data model growing beside the first.

Fix Document its purpose or drop it.

F136WorkflowReminder bookingUidfk-nullable

Nullable foreign key

WorkflowReminder.bookingUid may be NULL, so the relationship to Booking is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F137WorkflowReminder workflowStepIdfk-nullable

Nullable foreign key

WorkflowReminder.workflowStepId may be NULL, so the relationship to WorkflowStep is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F218WorkflowReminder bookingUid, workflowStepIdexclusive-arc

Either/or foreign keys without a CHECK

WorkflowReminder has several nullable FKs (bookingUid, workflowStepId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.

Fix Add a CHECK that exactly one is non-null, or restructure with a supertype.

ALTER TABLE WorkflowReminder ADD CONSTRAINT WorkflowReminder_one_target CHECK ((bookingUid IS NOT NULL)::int + (workflowStepId IS NOT NULL)::int = 1);
F310WorkflowReminder scheduledDatetimestamp-tz

TIMESTAMP without time zone

scheduledDate stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE WorkflowReminder ALTER COLUMN scheduledDate TYPE timestamptz;
F132WorkflowStep agentIdfk-nullable

Nullable foreign key

WorkflowStep.agentId may be NULL, so the relationship to Agent is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F133WorkflowStep inboundAgentIdfk-nullable

Nullable foreign key

WorkflowStep.inboundAgentId may be NULL, so the relationship to Agent is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F216WorkflowStep agentId, inboundAgentIdexclusive-arc

Either/or foreign keys without a CHECK

WorkflowStep has several nullable FKs (agentId, inboundAgentId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.

Fix Add a CHECK that exactly one is non-null, or restructure with a supertype.

ALTER TABLE WorkflowStep ADD CONSTRAINT WorkflowStep_one_target CHECK ((agentId IS NOT NULL)::int + (inboundAgentId IS NOT NULL)::int = 1);
F308WorkflowStep verifiedAttimestamp-tz

TIMESTAMP without time zone

verifiedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE WorkflowStep ALTER COLUMN verifiedAt TYPE timestamptz;
F370WorkflowStepTranslation createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE WorkflowStepTranslation ALTER COLUMN createdAt TYPE timestamptz;
F371WorkflowStepTranslation updatedAttimestamp-tz

TIMESTAMP without time zone

updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE WorkflowStepTranslation ALTER COLUMN updatedAt TYPE timestamptz;
F366WorkspacePlatform createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE WorkspacePlatform ALTER COLUMN createdAt TYPE timestamptz;
F367WorkspacePlatform updatedAttimestamp-tz

TIMESTAMP without time zone

updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE WorkspacePlatform ALTER COLUMN updatedAt TYPE timestamptz;
F163WrongAssignmentReport reportedByIdfk-nullable

Nullable foreign key

WrongAssignmentReport.reportedById may be NULL, so the relationship to users is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F164WrongAssignmentReport teamIdfk-nullable

Nullable foreign key

WrongAssignmentReport.teamId may be NULL, so the relationship to Team is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F165WrongAssignmentReport routingFormIdfk-nullable

Nullable foreign key

WrongAssignmentReport.routingFormId may be NULL, so the relationship to App_RoutingForms_Form is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F166WrongAssignmentReport reviewedByIdfk-nullable

Nullable foreign key

WrongAssignmentReport.reviewedById may be NULL, so the relationship to users is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F226WrongAssignmentReport reportedById, teamId, routingFormId, reviewedByIdexclusive-arc

Either/or foreign keys without a CHECK

WrongAssignmentReport has several nullable FKs (reportedById, teamId, routingFormId, reviewedById) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.

Fix Add a CHECK that exactly one is non-null, or restructure with a supertype.

ALTER TABLE WrongAssignmentReport ADD CONSTRAINT WrongAssignmentReport_one_target CHECK ((reportedById IS NOT NULL)::int + (teamId IS NOT NULL)::int + (routingFormId IS NOT NULL)::int + (reviewedById IS NOT NULL)::int = 1);
F377WrongAssignmentReport reviewedAttimestamp-tz

TIMESTAMP without time zone

reviewedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE WrongAssignmentReport ALTER COLUMN reviewedAt TYPE timestamptz;
F378WrongAssignmentReport createdAttimestamp-tz

TIMESTAMP without time zone

createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE WrongAssignmentReport ALTER COLUMN createdAt TYPE timestamptz;
F379WrongAssignmentReport updatedAttimestamp-tz

TIMESTAMP without time zone

updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE WrongAssignmentReport ALTER COLUMN updatedAt TYPE timestamptz;
F446avatarsorphan-table

Isolated table

avatars references nothing and nothing references it. Either it is a staging/log table (fine, say so), or it is dead, or it is the seed of a second data model growing beside the first.

Fix Document its purpose or drop it.

F092users organizationIdfk-nullable

Nullable foreign key

users.organizationId may be NULL, so the relationship to Team is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F093users movedToProfileIdfk-nullable

Nullable foreign key

users.movedToProfileId may be NULL, so the relationship to Profile is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.

Fix Confirm the optionality is a domain rule; document it in the column comment.

F205users organizationId, movedToProfileIdexclusive-arc

Either/or foreign keys without a CHECK

users has several nullable FKs (organizationId, movedToProfileId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.

Fix Add a CHECK that exactly one is non-null, or restructure with a supertype.

ALTER TABLE users ADD CONSTRAINT users_one_target CHECK ((organizationId IS NOT NULL)::int + (movedToProfileId IS NOT NULL)::int = 1);
F251users emailVerifiedtimestamp-tz

TIMESTAMP without time zone

emailVerified stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE users ALTER COLUMN emailVerified TYPE timestamptz;
F252users createdtimestamp-tz

TIMESTAMP without time zone

created stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE users ALTER COLUMN created TYPE timestamptz;
F253users trialEndsAttimestamp-tz

TIMESTAMP without time zone

trialEndsAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE users ALTER COLUMN trialEndsAt TYPE timestamptz;
F254users lastActiveAttimestamp-tz

TIMESTAMP without time zone

lastActiveAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.

Fix Use TIMESTAMPTZ.

ALTER TABLE users ALTER COLUMN lastActiveAt TYPE timestamptz;
F453usersblast-radius

Hub table: referenced by 53 tables

Any change to its key, its delete semantics, or its partitioning touches every dependent. Migrations on hub tables need the longest lock windows and the most careful rollout.

Fix Treat schema changes here as breaking changes with a written rollout plan.

F457userswide-table

Wide table (47 columns)

Tables this wide usually hide several entities (or a JSON column that wants to be one). Every row update rewrites the whole tuple; TOAST kicks in; indexes bloat.

Fix Look for column groups that always change together and split them out.

Schema

Schema map: 121 tables, 220 foreign keys, 23 domains. Click to open the 3D explorer.HostHostGroupHostHostLocationCalCalAiPhoneNumberCalVideoSettingsEventEventTypeEventTypeCustomInputEventTypeTranslationUserUserFeaturesUserHolidaySettingsUserPasswordUserFilterSegmentPreferenceTeamTeamTeamBillingTeamFeaturesCreditCreditBalanceCreditPurchaseLogCreditExpenseLogOrganizationOrganizationBillingOrganizationSettingsOrganizationOnboardingBookingBookingDenormalizedBookingAuditBookingBookingInternalNoteBookingReferenceBookingReportBookingSeatSelectedSelectedSlotsSelectedCalendarWebhookWebhookWebhookScheduledTriggersAppAppApp_RoutingForms_FormApp_RoutingForms_IncompleteBookingActionsApp_RoutingForms_FormResponseApp_RoutingForms_QueuedFormResponseRoutingRoutingFormResponseDenormalizedRoutingFormResponseFieldRoutingTraceWorkflowWorkflowOptOutContactWorkflowWorkflowStepWorkflowStepTranslationWorkflowReminderWorkflowsWorkflowsOnRoutingFormsWorkflowsOnTeamsWorkflowsOnEventTypesVerifiedVerifiedEmailVerifiedNumberAccessAccessTokenAccessCodeCalendarCalendarCacheCalendarCacheEventOutOutOfOfficeReasonOutOfOfficeEntryPlatformPlatformOAuthClientPlatformBillingPlatformAuthorizationToken_PlatformOAuthClientToUserAttributeAttributeAttributeOptionAttributeToUserAttributeSyncFieldMappingAttributeSyncRuleWatchlistWatchlistWatchlistEventAuditWatchlistAuditRoleRoleRolePermissionmiscAuditActorDeploymentFeatureHolidayCacheReminderMailResetPasswordRequestTaskTempOrgRedirectVideoCallGuestWorkspacePlatformavatarsDelegationCredentialDomainWideDelegationInternalNotePresetManagedOrganizationProfileDSyncDataMonthlyProrationusersAccountAgentApiKeyCredentialDSyncTeamGroupMappingFeedbackFilterSegmentImpersonationsMembershipNotificationsSubscriptionsOAuthClientRefreshTokenScheduleSeatChangeLogSecondaryEmailSessionTravelScheduleIntegrationAttributeSyncRateLimitVerificationTokenAIPhoneCallConfigurationAvailabilityDestinationCalendarHashedLink_user_eventtypeAssignmentReasonAttendeeInstantMeetingTokenPaymentTrackingWrongAssignmentReportPendingRoutingTrace

121 tables · 220 foreign keys · 23 domains. Open the 3D explorer: rotate, zoom, click any table or relationship.

Host 3 tables

Tables whose names start with Host_.

Entity-relationship diagram: Host, HostGroup, HostLocationHostuserId PKintegereventTypeId PKintegerisFixedbooleanpriorityintegerweightintegerweightAdjustmentintegerscheduleId FKintegercreatedAttimestamp(3)groupId FKtextmemberId FKintegerHostGroupid PKtextnametexteventTypeId FKintegercreatedAttimestamp(3)updatedAttimestamp(3)HostLocationid PKtextuserId FKintegereventTypeId FKintegertypetextcredentialId FKintegerlinktextaddresstextphoneNumbertextcreatedAttimestamp(3)updatedAttimestamp(3)CredentialEventTypeMembershipScheduleuserseventTypeIduserIdeventTypeIdscheduleIdgroupIdmemberIduserId, eventTypeIdcredentialId

Relationships

Host

10 cols · no RLS · line 167View in 3D

CreateTable

  • Foreign key without index groupId — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on HostGroup must scan Host to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to Host maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often HostGroup rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • Nullable foreign key scheduleId — Host.scheduleId may be NULL, so the relationship to Schedule is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key groupId — Host.groupId may be NULL, so the relationship to HostGroup is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key memberId — Host.memberId may be NULL, so the relationship to Membership is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Either/or foreign keys without a CHECK scheduleId, groupId, memberId — Host has several nullable FKs (scheduleId, groupId, memberId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.
  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
userId PKintegerNOT NULLusers.id 1:N · CASCADE
eventTypeId PKintegerNOT NULLEventType.id 1:N · CASCADE
isFixed booleanNOT NULLFALSE
priority integernull
weight integernull
weightAdjustment integernull
scheduleId integernullSchedule.id 1:N · SET NULL
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP
groupId textnullHostGroup.id 1:N · SET NULL
memberId integernullMembership.id 1:N · CASCADE

Indexes

  • (memberId) Host_memberId_idx
  • (userId) Host_userId_idx
  • (eventTypeId) Host_eventTypeId_idx
  • (scheduleId) Host_scheduleId_idx

Referenced by

HostGroup

5 cols · no RLS · line 183View in 3D

CreateTable

  • Nullable foreign key eventTypeId — HostGroup.eventTypeId may be NULL, so the relationship to EventType is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone updatedAt — updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKtextNOT NULL
name textNOT NULL
eventTypeId integernullEventType.id 1:N · CASCADE
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP
updatedAt timestamp(3)NOT NULL

Indexes

  • (name) HostGroup_name_idx
  • (eventTypeId) HostGroup_eventTypeId_idx

Referenced by

HostLocation

10 cols · no RLS · line 194View in 3D

CreateTable

  • Nullable foreign key credentialId — HostLocation.credentialId may be NULL, so the relationship to Credential is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Enum-like column with no CHECK type — type is a short string that clearly takes a fixed set of values, but the database accepts anything. Typos become new states, and nobody can list the legal values without reading application code.
  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone updatedAt — updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKtextNOT NULL
userId integerNOT NULLHost.userId 1:1 · CASCADE
eventTypeId integerNOT NULLHost.userId 1:1 · CASCADE
type textNOT NULL
credentialId integernullCredential.id 1:N · SET NULL
link textnull
address textnull
phoneNumber textnull
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP
updatedAt timestamp(3)NOT NULL

Indexes

  • (credentialId) HostLocation_credentialId_idx
  • (eventTypeId) HostLocation_eventTypeId_idx
  • UNIQUE (userId, eventTypeId) HostLocation_userId_eventTypeId_key

Referenced by

  • nothing

Cal 2 tables

Tables whose names start with Cal_.

Entity-relationship diagram: CalVideoSettings, CalAiPhoneNumberCalVideoSettingseventTypeId PKintegerdisableRecordingForOrganizerbooleandisableRecordingForGuestsbooleanenableAutomaticTranscriptionbooleanenableAutomaticRecordingForOrganizerbooleanredirectUrlOnExittextdisableTranscriptionForGuestsbooleandisableTranscriptionForOrganizerbooleanrequireEmailForGuestsbooleancreatedAttimestamp(3)updatedAttimestamp(3)CalAiPhoneNumberid PKserialuserId FKintegerteamId FKintegerphoneNumbertextprovidertextproviderPhoneNumberIdtextcreatedAttimestamp(3)updatedAttimestamp(3)stripeCustomerIdtextstripeSubscriptionIdtextsubscriptionStatusPhoneNumberSubscriptionStatusinboundAgentId FKtextoutboundAgentId FKtextAgentEventTypeTeamusersuserIdteamIdinboundAgentIdoutboundAgentIdeventTypeId

Relationships

CalVideoSettings

11 cols · no RLS · line 210View in 3D

CreateTable

  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone updatedAt — updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
eventTypeId PKintegerNOT NULLEventType.id 1:1 · CASCADE
disableRecordingForOrganizer booleanNOT NULLFALSE
disableRecordingForGuests booleanNOT NULLFALSE
enableAutomaticTranscription booleanNOT NULLFALSE
enableAutomaticRecordingForOrganizer booleanNOT NULLFALSE
redirectUrlOnExit textnull
disableTranscriptionForGuests booleanNOT NULLFALSE
disableTranscriptionForOrganizer booleanNOT NULLFALSE
requireEmailForGuests booleanNOT NULLFALSE
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP
updatedAt timestamp(3)NOT NULL

Indexes

  • none

Referenced by

  • nothing

CalAiPhoneNumber

13 cols · no RLS · line 1927View in 3D

CreateTable

  • Nullable foreign key userId — CalAiPhoneNumber.userId may be NULL, so the relationship to users is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key teamId — CalAiPhoneNumber.teamId may be NULL, so the relationship to Team is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key inboundAgentId — CalAiPhoneNumber.inboundAgentId may be NULL, so the relationship to Agent is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key outboundAgentId — CalAiPhoneNumber.outboundAgentId may be NULL, so the relationship to Agent is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Either/or foreign keys without a CHECK userId, teamId, inboundAgentId, outboundAgentId — CalAiPhoneNumber has several nullable FKs (userId, teamId, inboundAgentId, outboundAgentId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.
  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone updatedAt — updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKserialNOT NULL
userId integernullusers.id 1:N · CASCADE
teamId integernullTeam.id 1:N · CASCADE
phoneNumber textNOT NULL
provider textNOT NULL
providerPhoneNumberId textnull
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP
updatedAt timestamp(3)NOT NULL
stripeCustomerId textnull
stripeSubscriptionId textnull
subscriptionStatus PhoneNumberSubscriptionStatusnull
inboundAgentId textnullAgent.id 1:N · SET NULL
outboundAgentId textnullAgent.id 1:N · SET NULL

Indexes

  • UNIQUE (phoneNumber) CalAiPhoneNumber_phoneNumber_key
  • UNIQUE (providerPhoneNumberId) CalAiPhoneNumber_providerPhoneNumberId_key
  • UNIQUE (stripeSubscriptionId) CalAiPhoneNumber_stripeSubscriptionId_key
  • (userId) CalAiPhoneNumber_userId_idx
  • (teamId) CalAiPhoneNumber_teamId_idx
  • (inboundAgentId) CalAiPhoneNumber_inboundAgentId_idx
  • (outboundAgentId) CalAiPhoneNumber_outboundAgentId_idx

Referenced by

  • nothing

Event 3 tables

Tables whose names start with Event_.

Entity-relationship diagram: EventType, EventTypeCustomInput, EventTypeTranslationEventTypeid PKserialtitletextslugtextdescriptiontextinterfaceLanguagetextpositionintegerlocationsjsonblengthintegeroffsetStartintegerhiddenbooleanuserId FKintegerprofileId FKintegerteamId FKintegeruseEventLevelSelectedCalendarsbooleaneventNametextparentId FKintegerbookingFieldsjsonbtimeZonetextperiodTypePeriodTypeperiodStartDatetimestamp(3)periodEndDatetimestamp(3)periodDaysintegerperiodCountCalendarDaysbooleanlockTimeZoneToggleOnBookingPagebooleanlockedTimeZonetextrequiresConfirmationbooleanrequiresConfirmationWillBlockSlotbooleanrequiresConfirmationForFreeEmailbooleanrequiresBookerEmailVerificationbooleancanSendCalVideoTranscriptionEmailsbooleanautoTranslateDescriptionEnabledbooleanautoTranslateInstantMeetingTitleEnabledbooleanrecurringEventjsonbdisableGuestsbooleanhideCalendarNotesbooleanhideCalendarEventDetailsbooleanminimumBookingNoticeintegerbeforeEventBufferintegerafterEventBufferintegerseatsPerTimeSlotintegeronlyShowFirstAvailableSlotbooleanshowOptimizedSlotsbooleandisableCancellingbooleandisableReschedulingbooleanminimumRescheduleNoticeintegerseatsShowAttendeesbooleanseatsShowAvailabilityCountbooleanschedulingTypeSchedulingTypescheduleId FKintegerallowReschedulingCancelledBookingsbooleanpriceintegercurrencytextslotIntervalintegermetadatajsonbsuccessRedirectUrltextforwardParamsSuccessRedirectbooleanredirectUrlOnNoRoutingFormResponsetextbookingLimitsjsonbdurationLimitsjsonbisInstantEventbooleaninstantMeetingExpiryTimeOffsetInSecondsintegerinstantMeetingScheduleId FKintegerinstantMeetingParameterstext[]assignAllTeamMembersbooleanassignRRMembersUsingSegmentbooleanrrSegmentQueryValuejsonbuseEventTypeDestinationCalendarEmailbooleanisRRWeightsEnabledbooleanmaxLeadThresholdintegerincludeNoShowInRRCalculationbooleanallowReschedulingPastBookingsbooleanhideOrganizerEmailbooleanmaxActiveBookingsPerBookerintegermaxActiveBookingPerBookerOfferReschedulebooleancustomReplyToEmailtexteventTypeColorjsonbrescheduleWithSameRoundRobinHostbooleansecondaryEmailId FKintegeruseBookerTimezonebooleanrestrictionScheduleId FKintegerbookingRequiresAuthenticationbooleanrrHostSubsetEnabledbooleanrequiresCancellationReasonCancellationReasonRequirementenablePerHostLocationsbooleancreatedAttimestamp(3)updatedAttimestamp(3)EventTypeCustomInputid PKserialeventTypeId FKintegerlabeltexttypeEventTypeCustomInputTypeoptionsjsonbrequiredbooleanplaceholdertextEventTypeTranslationuid PKtexteventTypeId FKintegerfieldEventTypeAutoTranslatedFieldsourceLocaletexttargetLocaletexttranslatedTexttextcreatedAttimestamp(3)createdBy FKintegerupdatedAttimestamp(3)updatedBy FKintegerProfileScheduleSecondaryEmailTeamusersparentIduserIdprofileIdteamIdscheduleIdinstantMeetingScheduleIdsecondaryEmailIdrestrictionScheduleIdeventTypeIdeventTypeIdcreatedByupdatedBy

Relationships

EventType

86 cols · no RLS · line 240View in 3D

CreateTable

  • Foreign key without index instantMeetingScheduleId — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on Schedule must scan EventType to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to EventType maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often Schedule rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • Nullable foreign key userId — EventType.userId may be NULL, so the relationship to users is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key profileId — EventType.profileId may be NULL, so the relationship to Profile is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key teamId — EventType.teamId may be NULL, so the relationship to Team is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key parentId — EventType.parentId may be NULL, so the relationship to EventType is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key scheduleId — EventType.scheduleId may be NULL, so the relationship to Schedule is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key instantMeetingScheduleId — EventType.instantMeetingScheduleId may be NULL, so the relationship to Schedule is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key secondaryEmailId — EventType.secondaryEmailId may be NULL, so the relationship to SecondaryEmail is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key restrictionScheduleId — EventType.restrictionScheduleId may be NULL, so the relationship to Schedule is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Either/or foreign keys without a CHECK userId, profileId, teamId, parentId, scheduleId, instantMeetingScheduleId, secondaryEmailId, restrictionScheduleId — EventType has several nullable FKs (userId, profileId, teamId, parentId, scheduleId, instantMeetingScheduleId, secondaryEmailId, restrictionScheduleId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.
  • TIMESTAMP without time zone periodStartDate — periodStartDate stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone periodEndDate — periodEndDate stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone updatedAt — updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • Wide table (86 columns) — Tables this wide usually hide several entities (or a JSON column that wants to be one). Every row update rewrites the whole tuple; TOAST kicks in; indexes bloat.
ColumnTypeNullDefaultReferencesNotes
id PKserialNOT NULL
title textNOT NULL
slug textNOT NULL
description textnull
interfaceLanguage textnull
position integerNOT NULL0
locations jsonbnull
length integerNOT NULL
offsetStart integerNOT NULL0
hidden booleanNOT NULLFALSE
userId integernullusers.id 1:N · CASCADE
profileId integernullProfile.id 1:N · SET NULL
teamId integernullTeam.id 1:N · CASCADE
useEventLevelSelectedCalendars booleanNOT NULLFALSE
eventName textnull
parentId integernullEventType.id 1:N · CASCADE
bookingFields jsonbnull
timeZone textnull
periodType PeriodTypeNOT NULL'unlimited'
periodStartDate timestamp(3)null
periodEndDate timestamp(3)null
periodDays integernull
periodCountCalendarDays booleannull
lockTimeZoneToggleOnBookingPage booleanNOT NULLFALSE
lockedTimeZone textnull
requiresConfirmation booleanNOT NULLFALSE
requiresConfirmationWillBlockSlot booleanNOT NULLFALSE
requiresConfirmationForFreeEmail booleanNOT NULLFALSE
requiresBookerEmailVerification booleanNOT NULLFALSE
canSendCalVideoTranscriptionEmails booleanNOT NULLTRUE
autoTranslateDescriptionEnabled booleanNOT NULLFALSE
autoTranslateInstantMeetingTitleEnabled booleanNOT NULLTRUE
recurringEvent jsonbnull
disableGuests booleanNOT NULLFALSE
hideCalendarNotes booleanNOT NULLFALSE
hideCalendarEventDetails booleanNOT NULLFALSE
minimumBookingNotice integerNOT NULL120
beforeEventBuffer integerNOT NULL0
afterEventBuffer integerNOT NULL0
seatsPerTimeSlot integernull
onlyShowFirstAvailableSlot booleanNOT NULLFALSE
showOptimizedSlots booleannullFALSE
disableCancelling booleannullFALSE
disableRescheduling booleannullFALSE
minimumRescheduleNotice integernull
seatsShowAttendees booleannullFALSE
seatsShowAvailabilityCount booleannullTRUE
schedulingType SchedulingTypenull
scheduleId integernullSchedule.id 1:N · SET NULL
allowReschedulingCancelledBookings booleannullFALSE
price integerNOT NULL0
currency textNOT NULL'usd'
slotInterval integernull
metadata jsonbnull
successRedirectUrl textnull
forwardParamsSuccessRedirect booleannullTRUE
redirectUrlOnNoRoutingFormResponse textnull
bookingLimits jsonbnull
durationLimits jsonbnull
isInstantEvent booleanNOT NULLFALSE
instantMeetingExpiryTimeOffsetInSeconds integerNOT NULL90
instantMeetingScheduleId integernullSchedule.id 1:N · SET NULL
instantMeetingParameters text[]null
assignAllTeamMembers booleanNOT NULLFALSE
assignRRMembersUsingSegment booleanNOT NULLFALSE
rrSegmentQueryValue jsonbnull
useEventTypeDestinationCalendarEmail booleanNOT NULLFALSE
isRRWeightsEnabled booleanNOT NULLFALSE
maxLeadThreshold integernull
includeNoShowInRRCalculation booleanNOT NULLFALSE
allowReschedulingPastBookings booleanNOT NULLFALSE
hideOrganizerEmail booleanNOT NULLFALSE
maxActiveBookingsPerBooker integernull
maxActiveBookingPerBookerOfferReschedule booleanNOT NULLFALSE
customReplyToEmail textnull
eventTypeColor jsonbnull
rescheduleWithSameRoundRobinHost booleanNOT NULLFALSE
secondaryEmailId integernullSecondaryEmail.id 1:N · CASCADE
useBookerTimezone booleanNOT NULLFALSE
restrictionScheduleId integernullSchedule.id 1:N · SET NULL
bookingRequiresAuthentication booleanNOT NULLFALSE
rrHostSubsetEnabled booleanNOT NULLFALSE
requiresCancellationReason CancellationReasonRequirementnull'MANDATORY_HOST_ONLY'
enablePerHostLocations booleanNOT NULLFALSE
createdAt timestamp(3)nullCURRENT_TIMESTAMP
updatedAt timestamp(3)null

Indexes

  • (userId) EventType_userId_idx
  • (teamId) EventType_teamId_idx
  • (profileId) EventType_profileId_idx
  • (scheduleId) EventType_scheduleId_idx
  • (secondaryEmailId) EventType_secondaryEmailId_idx
  • (parentId) EventType_parentId_idx
  • (parentId, teamId) EventType_parentId_teamId_idx
  • (restrictionScheduleId) EventType_restrictionScheduleId_idx
  • UNIQUE (userId, slug) EventType_userId_slug_key
  • UNIQUE (teamId, slug) EventType_teamId_slug_key
  • UNIQUE (userId, parentId) EventType_userId_parentId_key

Referenced by

EventTypeCustomInput

7 cols · no RLS · line 767View in 3D

CreateTable

ColumnTypeNullDefaultReferencesNotes
id PKserialNOT NULL
eventTypeId integerNOT NULLEventType.id 1:N · CASCADE
label textNOT NULL
type EventTypeCustomInputTypeNOT NULL
options jsonbnull
required booleanNOT NULL
placeholder textNOT NULL''

Indexes

  • (eventTypeId) EventTypeCustomInput_eventTypeId_idx

Referenced by

  • nothing

EventTypeTranslation

10 cols · no RLS · line 1635View in 3D

CreateTable

  • Foreign key without index createdBy — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on users must scan EventTypeTranslation to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to EventTypeTranslation maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often users rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • Foreign key without index updatedBy — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on users must scan EventTypeTranslation to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to EventTypeTranslation maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often users rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • Nullable foreign key updatedBy — EventTypeTranslation.updatedBy may be NULL, so the relationship to users is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone updatedAt — updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
uid PKtextNOT NULL
eventTypeId integerNOT NULLEventType.id 1:N · CASCADE
field EventTypeAutoTranslatedFieldNOT NULL
sourceLocale textNOT NULL
targetLocale textNOT NULL
translatedText textNOT NULL
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP
createdBy integerNOT NULLusers.id 1:N · RESTRICT
updatedAt timestamp(3)NOT NULL
updatedBy integernullusers.id 1:N · SET NULL

Indexes

  • (eventTypeId, field, targetLocale) EventTypeTranslation_eventTypeId_field_targetLocale_idx
  • UNIQUE (eventTypeId, field, targetLocale) EventTypeTranslation_eventTypeId_field_targetLocale_key

Referenced by

  • nothing

User 4 tables

Tables whose names start with User_.

Entity-relationship diagram: UserPassword, UserFeatures, UserHolidaySettings, UserFilterSegmentPreferenceUserPasswordhashtextuserId FKintegerUserFeaturesuserId PKintegerfeatureId PKtextenabledbooleanassignedAttimestamp(3)assignedBytextupdatedAttimestamp(3)UserHolidaySettingsid PKserialuserId FKintegercountryCodetextdisabledIdstext[]createdAttimestamp(3)updatedAttimestamp(3)UserFilterSegmentPreferenceid PKserialuserId FKintegertableIdentifiertextsegmentId FKintegersystemSegmentIdtextcreatedAttimestamp(3)updatedAttimestamp(3)FeatureFilterSegmentusersuserIdfeatureIduserIdsegmentIduserIduserId

Relationships

UserPassword

2 cols · no RLS · line 368View in 3D

CreateTable

  • No primary key — Without a primary key rows are not individually addressable: no safe UPDATE/DELETE of one row, no logical replication, ORMs misbehave, and duplicates are legal.
ColumnTypeNullDefaultReferencesNotes
hash textNOT NULL
userId integerNOT NULLusers.id 1:1 · CASCADE

Indexes

  • UNIQUE (userId) UserPassword_userId_key

Referenced by

  • nothing

UserFeatures

6 cols · no RLS · line 1217View in 3D

CreateTable

  • Foreign key without index featureId — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on Feature must scan UserFeatures to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to UserFeatures maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often Feature rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • TIMESTAMP without time zone assignedAt — assignedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone updatedAt — updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
userId PKintegerNOT NULLusers.id 1:N · CASCADE
featureId PKtextNOT NULLFeature.slug 1:N · CASCADE
enabled booleanNOT NULL
assignedAt timestamp(3)NOT NULLCURRENT_TIMESTAMP
assignedBy textNOT NULL
updatedAt timestamp(3)NOT NULL

Indexes

  • (userId, featureId) UserFeatures_userId_featureId_idx

Referenced by

  • nothing

UserHolidaySettings

6 cols · no RLS · line 1383View in 3D

CreateTable

  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone updatedAt — updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKserialNOT NULL
userId integerNOT NULLusers.id 1:1 · CASCADE
countryCode textnull
disabledIds text[]null
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP
updatedAt timestamp(3)NOT NULL

Indexes

  • UNIQUE (userId) UserHolidaySettings_userId_key

Referenced by

  • nothing

UserFilterSegmentPreference

7 cols · no RLS · line 1816View in 3D

CreateTable

  • Nullable foreign key segmentId — UserFilterSegmentPreference.segmentId may be NULL, so the relationship to FilterSegment is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone updatedAt — updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKserialNOT NULL
userId integerNOT NULLusers.id 1:N · CASCADE
tableIdentifier textNOT NULL
segmentId integernullFilterSegment.id 1:N · CASCADE
systemSegmentId textnull
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP
updatedAt timestamp(3)NOT NULL

Indexes

  • (userId) UserFilterSegmentPreference_userId_idx
  • (segmentId) UserFilterSegmentPreference_segmentId_idx
  • UNIQUE (userId, tableIdentifier) UserFilterSegmentPreference_userId_tableIdentifier_key

Referenced by

  • nothing

Team 3 tables

Tables whose names start with Team_.

Entity-relationship diagram: Team, TeamFeatures, TeamBillingTeamid PKserialnametextslugtextlogoUrltextcalVideoLogotextappLogotextappIconLogotextbiotexthideBrandingbooleanhideTeamProfileLinkbooleanisPrivatebooleanhideBookATeamMemberbooleancreatedAttimestamp(3)metadatajsonbthemetextrrResetIntervalRRResetIntervalrrTimestampBasisRRTimestampBasisbrandColortextdarkBrandColortextbannerUrltextparentId FKintegertimeFormatintegertimeZonetextweekStarttextisOrganizationbooleanpendingPaymentbooleanisPlatformbooleancreatedByOAuthClientId FKtextsmsLockStateSMSLockStatesmsLockReviewedByAdminbooleanbookingLimitsjsonbincludeManagedEventsInLimitsbooleanautoOptInFeaturesbooleanTeamFeaturesteamId PKintegerfeatureId PKtextenabledbooleanassignedAttimestamp(3)assignedBytextupdatedAttimestamp(3)TeamBillingid PKtextteamId FKintegersubscriptionIdtextsubscriptionItemIdtextcustomerIdtextstatustextplanNametextsubscriptionStarttimestamp(3)subscriptionTrialEndtimestamp(3)subscriptionEndtimestamp(3)billingPeriodBillingPeriodbillingModeBillingModepricePerSeatintegerpaidSeatsintegerhighWaterMarkintegerhighWaterMarkPeriodStarttimestamp(3)createdAttimestamp(3)updatedAttimestamp(3)FeaturePlatformOAuthClientparentIdcreatedByOAuthClientIdteamIdteamIdfeatureId

Relationships

Team

33 cols · no RLS · line 461View in 3D

CreateTable

  • Foreign key without index createdByOAuthClientId — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on PlatformOAuthClient must scan Team to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to Team maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often PlatformOAuthClient rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • Nullable foreign key parentId — Team.parentId may be NULL, so the relationship to Team is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key createdByOAuthClientId — Team.createdByOAuthClientId may be NULL, so the relationship to PlatformOAuthClient is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Either/or foreign keys without a CHECK parentId, createdByOAuthClientId — Team has several nullable FKs (parentId, createdByOAuthClientId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.
  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • Foreign-key cycle — Team → PlatformOAuthClient → Team. Rows must be inserted with a deferred constraint or a NULL-then-update dance; backups/restores and truncation have no valid order; ON DELETE CASCADE can loop.
  • Hub table: referenced by 40 tables — Any change to its key, its delete semantics, or its partitioning touches every dependent. Migrations on hub tables need the longest lock windows and the most careful rollout.
  • Wide table (33 columns) — Tables this wide usually hide several entities (or a JSON column that wants to be one). Every row update rewrites the whole tuple; TOAST kicks in; indexes bloat.
ColumnTypeNullDefaultReferencesNotes
id PKserialNOT NULL
name textNOT NULL
slug textnull
logoUrl textnull
calVideoLogo textnull
appLogo textnull
appIconLogo textnull
bio textnull
hideBranding booleanNOT NULLFALSE
hideTeamProfileLink booleanNOT NULLFALSE
isPrivate booleanNOT NULLFALSE
hideBookATeamMember booleanNOT NULLFALSE
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP
metadata jsonbnull
theme textnull
rrResetInterval RRResetIntervalnull'MONTH'
rrTimestampBasis RRTimestampBasisNOT NULL'CREATED_AT'
brandColor textnull
darkBrandColor textnull
bannerUrl textnull
parentId integernullTeam.id 1:N · CASCADE
timeFormat integernull
timeZone textNOT NULL'Europe/London'
weekStart textNOT NULL'Sunday'
isOrganization booleanNOT NULLFALSE
pendingPayment booleanNOT NULLFALSE
isPlatform booleanNOT NULLFALSE
createdByOAuthClientId textnullPlatformOAuthClient.id 1:N · CASCADE
smsLockState SMSLockStateNOT NULL'UNLOCKED'
smsLockReviewedByAdmin booleanNOT NULLFALSE
bookingLimits jsonbnull
includeManagedEventsInLimits booleanNOT NULLFALSE
autoOptInFeatures booleanNOT NULLFALSE

Indexes

  • (parentId) Team_parentId_idx
  • UNIQUE (slug, parentId) Team_slug_parentId_key

Referenced by

TeamFeatures

6 cols · no RLS · line 1229View in 3D

CreateTable

  • Foreign key without index featureId — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on Feature must scan TeamFeatures to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to TeamFeatures maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often Feature rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • TIMESTAMP without time zone assignedAt — assignedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone updatedAt — updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
teamId PKintegerNOT NULLTeam.id 1:N · CASCADE
featureId PKtextNOT NULLFeature.slug 1:N · CASCADE
enabled booleanNOT NULL
assignedAt timestamp(3)NOT NULLCURRENT_TIMESTAMP
assignedBy textNOT NULL
updatedAt timestamp(3)NOT NULL

Indexes

  • (teamId, featureId) TeamFeatures_teamId_featureId_idx

Referenced by

  • nothing

TeamBilling

18 cols · no RLS · line 1946View in 3D

CreateTable

  • Enum-like column with no CHECK status — status is a short string that clearly takes a fixed set of values, but the database accepts anything. Typos become new states, and nobody can list the legal values without reading application code.
  • TIMESTAMP without time zone subscriptionStart — subscriptionStart stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone subscriptionTrialEnd — subscriptionTrialEnd stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone subscriptionEnd — subscriptionEnd stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone highWaterMarkPeriodStart — highWaterMarkPeriodStart stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone updatedAt — updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKtextNOT NULL
teamId integerNOT NULLTeam.id 1:1 · CASCADE
subscriptionId textNOT NULL
subscriptionItemId textNOT NULL
customerId textNOT NULL
status textNOT NULL
planName textNOT NULL
subscriptionStart timestamp(3)null
subscriptionTrialEnd timestamp(3)null
subscriptionEnd timestamp(3)null
billingPeriod BillingPeriodnull
billingMode BillingModeNOT NULL'SEATS'
pricePerSeat integernull
paidSeats integernull
highWaterMark integernull
highWaterMarkPeriodStart timestamp(3)null
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP
updatedAt timestamp(3)NOT NULL

Indexes

  • UNIQUE (teamId) TeamBilling_teamId_key
  • UNIQUE (subscriptionId) TeamBilling_subscriptionId_key

Referenced by

Credit 3 tables

Tables whose names start with Credit_.

Entity-relationship diagram: CreditBalance, CreditPurchaseLog, CreditExpenseLogCreditBalanceid PKtextteamId FKintegeruserId FKintegeradditionalCreditsintegerlimitReachedAttimestamp(3)warningSentAttimestamp(3)CreditPurchaseLogid PKtextcreditBalanceId FKtextcreditsintegercreatedAttimestamp(3)CreditExpenseLogid PKtextcreditBalanceId FKtextbookingUid FKtextcreditsintegercreditTypeCreditTypedatetimestamp(3)smsSidtextsmsSegmentsintegerphoneNumbertextemailtextcallDurationintegercreditForCreditUsageTypeexternalReftextBookingTeamusersteamIduserIdcreditBalanceIdbookingUidcreditBalanceId

Relationships

CreditBalance

6 cols · no RLS · line 500View in 3D

CreateTable

  • Nullable foreign key teamId — CreditBalance.teamId may be NULL, so the relationship to Team is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key userId — CreditBalance.userId may be NULL, so the relationship to users is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Either/or foreign keys without a CHECK teamId, userId — CreditBalance has several nullable FKs (teamId, userId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.
  • TIMESTAMP without time zone limitReachedAt — limitReachedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone warningSentAt — warningSentAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKtextNOT NULL
teamId integernullTeam.id 1:1 · CASCADE
userId integernullusers.id 1:1 · CASCADE
additionalCredits integerNOT NULL0
limitReachedAt timestamp(3)null
warningSentAt timestamp(3)null

Indexes

  • UNIQUE (teamId) CreditBalance_teamId_key
  • UNIQUE (userId) CreditBalance_userId_key

Referenced by

CreditPurchaseLog

4 cols · no RLS · line 512View in 3D

CreateTable

  • Foreign key without index creditBalanceId — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on CreditBalance must scan CreditPurchaseLog to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to CreditPurchaseLog maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often CreditBalance rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKtextNOT NULL
creditBalanceId textNOT NULLCreditBalance.id 1:N · CASCADE
credits integerNOT NULL
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP

Indexes

  • none

Referenced by

  • nothing

CreditExpenseLog

13 cols · no RLS · line 522View in 3D

CreateTable

  • Foreign key without index creditBalanceId — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on CreditBalance must scan CreditExpenseLog to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to CreditExpenseLog maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often CreditBalance rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • Foreign key without index bookingUid — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on Booking must scan CreditExpenseLog to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to CreditExpenseLog maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often Booking rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • Nullable foreign key bookingUid — CreditExpenseLog.bookingUid may be NULL, so the relationship to Booking is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • TIMESTAMP without time zone date — date stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKtextNOT NULL
creditBalanceId textNOT NULLCreditBalance.id 1:N · CASCADE
bookingUid textnullBooking.uid 1:N · CASCADE
credits integernull
creditType CreditTypeNOT NULL
date timestamp(3)NOT NULL
smsSid textnull
smsSegments integernull
phoneNumber textnull
email textnull
callDuration integernull
creditFor CreditUsageTypenull
externalRef textnull

Indexes

  • UNIQUE (externalRef) CreditExpenseLog_externalRef_key

Referenced by

  • nothing

Organization 3 tables

Tables whose names start with Organization_.

Entity-relationship diagram: OrganizationSettings, OrganizationOnboarding, OrganizationBillingOrganizationSettingsid PKserialorganizationId FKintegerisOrganizationConfiguredbooleanisOrganizationVerifiedbooleanorgAutoAcceptEmailtextlockEventTypeCreationForUsersbooleanadminGetsNoSlotsNotificationbooleanisAdminReviewedbooleanisAdminAPIEnabledbooleanallowSEOIndexingbooleanorgProfileRedirectsToVerifiedDomainbooleandisablePhoneOnlySMSNotificationsbooleandisableAutofillOnBookingPagebooleanorgAutoJoinOnSignupbooleandisableAttendeeConfirmationEmailbooleandisableAttendeeCancellationEmailbooleandisableAttendeeRescheduledEmailbooleandisableAttendeeRequestEmailbooleandisableAttendeeReassignedEmailbooleandisableAttendeeAwaitingPaymentEmailbooleandisableAttendeeRescheduleRequestEmailbooleandisableAttendeeLocationChangeEmailbooleandisableAttendeeNewEventEmailbooleanOrganizationOnboardingid PKtextcreatedById FKintegercreatedAttimestamp(3)orgOwnerEmailtexterrortextupdatedAttimestamp(3)organizationId FKintegerbillingPeriodBillingPeriodpricePerSeatdouble precisionseatsintegerisPlatformbooleannametextslugtextlogotextbiotextbrandColortextbannerUrltextisDomainConfiguredbooleanstripeCustomerIdtextstripeSubscriptionIdtextstripeSubscriptionItemIdtextinvitedMembersjsonbteamsjsonbisCompletebooleanOrganizationBillingid PKtextteamId FKintegersubscriptionIdtextsubscriptionItemIdtextcustomerIdtextstatustextplanNametextsubscriptionStarttimestamp(3)subscriptionTrialEndtimestamp(3)subscriptionEndtimestamp(3)billingPeriodBillingPeriodbillingModeBillingModepricePerSeatintegerpaidSeatsintegerhighWaterMarkintegerhighWaterMarkPeriodStarttimestamp(3)createdAttimestamp(3)updatedAttimestamp(3)TeamusersteamIdcreatedByIdorganizationIdorganizationId

Relationships

OrganizationSettings

23 cols · no RLS · line 541View in 3D

CreateTable

ColumnTypeNullDefaultReferencesNotes
id PKserialNOT NULL
organizationId integerNOT NULLTeam.id 1:1 · CASCADE
isOrganizationConfigured booleanNOT NULLFALSE
isOrganizationVerified booleanNOT NULLFALSE
orgAutoAcceptEmail textNOT NULL
lockEventTypeCreationForUsers booleanNOT NULLFALSE
adminGetsNoSlotsNotification booleanNOT NULLFALSE
isAdminReviewed booleanNOT NULLFALSE
isAdminAPIEnabled booleanNOT NULLFALSE
allowSEOIndexing booleanNOT NULLFALSE
orgProfileRedirectsToVerifiedDomain booleanNOT NULLFALSE
disablePhoneOnlySMSNotifications booleanNOT NULLFALSE
disableAutofillOnBookingPage booleanNOT NULLFALSE
orgAutoJoinOnSignup booleanNOT NULLTRUE
disableAttendeeConfirmationEmail booleanNOT NULLFALSE
disableAttendeeCancellationEmail booleanNOT NULLFALSE
disableAttendeeRescheduledEmail booleanNOT NULLFALSE
disableAttendeeRequestEmail booleanNOT NULLFALSE
disableAttendeeReassignedEmail booleanNOT NULLFALSE
disableAttendeeAwaitingPaymentEmail booleanNOT NULLFALSE
disableAttendeeRescheduleRequestEmail booleanNOT NULLFALSE
disableAttendeeLocationChangeEmail booleanNOT NULLFALSE
disableAttendeeNewEventEmail booleanNOT NULLFALSE

Indexes

  • UNIQUE (organizationId) OrganizationSettings_organizationId_key

Referenced by

OrganizationOnboarding

24 cols · no RLS · line 1743View in 3D

CreateTable

  • Foreign key without index createdById — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on users must scan OrganizationOnboarding to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to OrganizationOnboarding maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often users rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • Nullable foreign key organizationId — OrganizationOnboarding.organizationId may be NULL, so the relationship to Team is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone updatedAt — updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • Monetary value stored as float pricePerSeat — pricePerSeat is binary floating point. 0.1 + 0.2 ≠ 0.3; sums drift; reconciliation against the ledger will be off by cents.
ColumnTypeNullDefaultReferencesNotes
id PKtextNOT NULL
createdById integerNOT NULLusers.id 1:N · CASCADE
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP
orgOwnerEmail textNOT NULL
error textnull
updatedAt timestamp(3)NOT NULL
organizationId integernullTeam.id 1:1 · CASCADE
billingPeriod BillingPeriodNOT NULL
pricePerSeat double precisionNOT NULL
seats integerNOT NULL
isPlatform booleanNOT NULLFALSE
name textNOT NULL
slug textNOT NULL
logo textnull
bio textnull
brandColor textnull
bannerUrl textnull
isDomainConfigured booleanNOT NULLFALSE
stripeCustomerId textnull
stripeSubscriptionId textnull
stripeSubscriptionItemId textnull
invitedMembers jsonbNOT NULL'[]'
teams jsonbNOT NULL'[]'
isComplete booleanNOT NULLFALSE

Indexes

  • UNIQUE (orgOwnerEmail) OrganizationOnboarding_orgOwnerEmail_key
  • UNIQUE (organizationId) OrganizationOnboarding_organizationId_key
  • UNIQUE (stripeCustomerId) OrganizationOnboarding_stripeCustomerId_key
  • (orgOwnerEmail) OrganizationOnboarding_orgOwnerEmail_idx
  • (stripeCustomerId) OrganizationOnboarding_stripeCustomerId_idx

Referenced by

  • nothing

OrganizationBilling

18 cols · no RLS · line 1970View in 3D

CreateTable

  • Enum-like column with no CHECK status — status is a short string that clearly takes a fixed set of values, but the database accepts anything. Typos become new states, and nobody can list the legal values without reading application code.
  • TIMESTAMP without time zone subscriptionStart — subscriptionStart stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone subscriptionTrialEnd — subscriptionTrialEnd stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone subscriptionEnd — subscriptionEnd stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone highWaterMarkPeriodStart — highWaterMarkPeriodStart stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone updatedAt — updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKtextNOT NULL
teamId integerNOT NULLTeam.id 1:1 · CASCADE
subscriptionId textNOT NULL
subscriptionItemId textNOT NULL
customerId textNOT NULL
status textNOT NULL
planName textNOT NULL
subscriptionStart timestamp(3)null
subscriptionTrialEnd timestamp(3)null
subscriptionEnd timestamp(3)null
billingPeriod BillingPeriodnull
billingMode BillingModeNOT NULL'SEATS'
pricePerSeat integernull
paidSeats integernull
highWaterMark integernull
highWaterMarkPeriodStart timestamp(3)null
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP
updatedAt timestamp(3)NOT NULL

Indexes

  • UNIQUE (teamId) OrganizationBilling_teamId_key
  • UNIQUE (subscriptionId) OrganizationBilling_subscriptionId_key

Referenced by

Booking 7 tables

Tables whose names start with Booking_.

Entity-relationship diagram: BookingReference, Booking, BookingSeat, BookingDenormalized, BookingReport, BookingInternalNote, BookingAuditBookingReferenceid PKserialtypetextuidtextmeetingIdtextthirdPartyRecurringEventIdtextmeetingPasswordtextmeetingUrltextbookingId FKintegerexternalCalendarIdtextdeletedbooleancredentialId FKintegerdelegationCredentialId FKtextdomainWideDelegationCredentialId FKtextBookingid PKserialuidtextidempotencyKeytextuserId FKintegeruserPrimaryEmailtexteventTypeId FKintegertitletextdescriptiontextcustomInputsjsonbresponsesjsonbstartTimetimestamp(3)endTimetimestamp(3)locationtextcreatedAttimestamp(3)updatedAttimestamp(3)statusBookingStatuspaidbooleandestinationCalendarId FKintegercancellationReasontextrejectionReasontextreassignReasontextreassignById FKintegerdynamicEventSlugReftextdynamicGroupSlugReftextrescheduledbooleanfromRescheduletextrecurringEventIdtextsmsReminderNumbertextscheduledJobstext[]metadatajsonbisRecordedbooleaniCalUIDtextiCalSequenceintegerratingintegerratingFeedbacktextnoShowHostbooleanoneTimePasswordtextcancelledBytextrescheduledBytextcreationSourceCreationSourceBookingSeatid PKserialreferenceUidtextbookingId FKintegerattendeeId FKintegerdatajsonbmetadatajsonbBookingDenormalizedid PKintegeruidtexteventTypeIdintegertitletextdescriptiontextstartTimetimestamp(3)endTimetimestamp(3)createdAttimestamp(3)updatedAttimestamp(3)locationtextpaidbooleanstatusBookingStatusrescheduledbooleanuserIdintegerteamIdintegereventLengthintegereventParentIdintegeruserEmailtextuserNametextuserUsernametextratingFeedbacktextratingintegernoShowHostbooleanisTeamBookingbooleanBookingReportid PKuuidbookingUid FKtextbookerEmailtextreportedById FKintegerorganizationId FKintegerreasonBookingReportReasondescriptiontextcancelledbooleancreatedAttimestamp(3)updatedAttimestamp(3)statusBookingReportStatussystemStatusSystemReportStatuswatchlistId FKuuidglobalWatchlistId FKuuidBookingInternalNoteid PKserialnotePresetId FKintegertexttextbookingId FKintegercreatedById FKintegercreatedAttimestamp(3)BookingAuditid PKuuidbookingUidtextactorId FKuuidtypeBookingAuditTypeactionBookingAuditActiontimestamptimestamp(3)createdAttimestamp(3)updatedAttimestamp(3)sourceBookingAuditSourceoperationIdtextdatajsonbcontextjsonbAttendeeAuditActorCredentialDelegationCredentialDestinationCalendarDomainWideDelegationEventTypeInternalNotePresetTeamWatchlistusersuserIdeventTypeIddestinationCalendarIdreassignByIdactorIdnotePresetIdbookingIdcreatedByIdbookingIdcredentialIddelegationCredentialIddomainWideDelegationCredentialIdbookingUidreportedByIdorganizationIdwatchlistIdglobalWatchlistIdbookingIdattendeeId

Relationships

BookingReference

13 cols · no RLS · line 613View in 3D

CreateTable

  • Foreign key without index credentialId — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on Credential must scan BookingReference to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to BookingReference maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often Credential rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • Foreign key without index delegationCredentialId — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on DelegationCredential must scan BookingReference to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to BookingReference maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often DelegationCredential rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • Foreign key without index domainWideDelegationCredentialId — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on DomainWideDelegation must scan BookingReference to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to BookingReference maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often DomainWideDelegation rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • Nullable foreign key bookingId — BookingReference.bookingId may be NULL, so the relationship to Booking is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key credentialId — BookingReference.credentialId may be NULL, so the relationship to Credential is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key delegationCredentialId — BookingReference.delegationCredentialId may be NULL, so the relationship to DelegationCredential is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key domainWideDelegationCredentialId — BookingReference.domainWideDelegationCredentialId may be NULL, so the relationship to DomainWideDelegation is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Enum-like column with no CHECK type — type is a short string that clearly takes a fixed set of values, but the database accepts anything. Typos become new states, and nobody can list the legal values without reading application code.
  • Either/or foreign keys without a CHECK bookingId, credentialId, delegationCredentialId, domainWideDelegationCredentialId — BookingReference has several nullable FKs (bookingId, credentialId, delegationCredentialId, domainWideDelegationCredentialId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.
ColumnTypeNullDefaultReferencesNotes
id PKserialNOT NULL
type textNOT NULL
uid textNOT NULL
meetingId textnull
thirdPartyRecurringEventId textnull
meetingPassword textnull
meetingUrl textnull
bookingId integernullBooking.id 1:N · CASCADE
externalCalendarId textnull
deleted booleannull
credentialId integernullCredential.id 1:N · SET NULL
delegationCredentialId textnullDelegationCredential.id 1:N · SET NULL
domainWideDelegationCredentialId textnullDomainWideDelegation.id 1:N · SET NULL

Indexes

  • (bookingId) BookingReference_bookingId_idx
  • (type) BookingReference_type_idx
  • (uid) BookingReference_uid_idx

Referenced by

  • nothing

Booking

40 cols · no RLS · line 646View in 3D

CreateTable

  • Foreign key without index reassignById — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on users must scan Booking to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to Booking maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often users rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • Nullable foreign key userId — Booking.userId may be NULL, so the relationship to users is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key eventTypeId — Booking.eventTypeId may be NULL, so the relationship to EventType is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key destinationCalendarId — Booking.destinationCalendarId may be NULL, so the relationship to DestinationCalendar is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key reassignById — Booking.reassignById may be NULL, so the relationship to users is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Either/or foreign keys without a CHECK userId, eventTypeId, destinationCalendarId, reassignById — Booking has several nullable FKs (userId, eventTypeId, destinationCalendarId, reassignById) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.
  • TIMESTAMP without time zone startTime — startTime stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone endTime — endTime stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone updatedAt — updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • Hub table: referenced by 16 tables — Any change to its key, its delete semantics, or its partitioning touches every dependent. Migrations on hub tables need the longest lock windows and the most careful rollout.
  • Wide table (40 columns) — Tables this wide usually hide several entities (or a JSON column that wants to be one). Every row update rewrites the whole tuple; TOAST kicks in; indexes bloat.
ColumnTypeNullDefaultReferencesNotes
id PKserialNOT NULL
uid textNOT NULL
idempotencyKey textnull
userId integernullusers.id 1:N · CASCADE
userPrimaryEmail textnull
eventTypeId integernullEventType.id 1:N · SET NULL
title textNOT NULL
description textnull
customInputs jsonbnull
responses jsonbnull
startTime timestamp(3)NOT NULL
endTime timestamp(3)NOT NULL
location textnull
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP
updatedAt timestamp(3)null
status BookingStatusNOT NULL'accepted'
paid booleanNOT NULLFALSE
destinationCalendarId integernullDestinationCalendar.id 1:N · SET NULL
cancellationReason textnull
rejectionReason textnull
reassignReason textnull
reassignById integernullusers.id 1:N · SET NULL
dynamicEventSlugRef textnull
dynamicGroupSlugRef textnull
rescheduled booleannull
fromReschedule textnull
recurringEventId textnull
smsReminderNumber textnull
scheduledJobs text[]null
metadata jsonbnull
isRecorded booleanNOT NULLFALSE
iCalUID textnull''
iCalSequence integerNOT NULL0
rating integernull
ratingFeedback textnull
noShowHost booleannullFALSE
oneTimePassword textnull
cancelledBy textnull
rescheduledBy textnull
creationSource CreationSourcenull

Indexes

  • UNIQUE (uid) Booking_uid_key
  • UNIQUE (idempotencyKey) Booking_idempotencyKey_key
  • UNIQUE (oneTimePassword) Booking_oneTimePassword_key
  • (eventTypeId) Booking_eventTypeId_idx
  • (userId) Booking_userId_idx
  • (destinationCalendarId) Booking_destinationCalendarId_idx
  • (recurringEventId) Booking_recurringEventId_idx
  • (uid) Booking_uid_idx
  • (status) Booking_status_idx
  • (startTime, endTime, status) Booking_startTime_endTime_status_idx
  • (fromReschedule) Booking_fromReschedule_idx
  • (userId, endTime) Booking_userId_endTime_idx
  • (userId, status, startTime) Booking_userId_status_startTime_idx
  • (eventTypeId, status) Booking_eventTypeId_status_idx
  • (userId, createdAt) Booking_userId_createdAt_idx

Referenced by

BookingSeat

6 cols · no RLS · line 1170View in 3D

CreateTable

ColumnTypeNullDefaultReferencesNotes
id PKserialNOT NULL
referenceUid textNOT NULL
bookingId integerNOT NULLBooking.id 1:N · CASCADE
attendeeId integerNOT NULLAttendee.id 1:1 · CASCADE
data jsonbnull
metadata jsonbnull

Indexes

  • UNIQUE (referenceUid) BookingSeat_referenceUid_key
  • UNIQUE (attendeeId) BookingSeat_attendeeId_key
  • (bookingId) BookingSeat_bookingId_idx
  • (attendeeId) BookingSeat_attendeeId_idx

Referenced by

  • nothing

BookingDenormalized

24 cols · no RLS · line 1289View in 3D

CreateTable

  • TIMESTAMP without time zone startTime — startTime stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone endTime — endTime stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone updatedAt — updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • Isolated table — BookingDenormalized references nothing and nothing references it. Either it is a staging/log table (fine, say so), or it is dead, or it is the seed of a second data model growing beside the first.
ColumnTypeNullDefaultReferencesNotes
id PKintegerNOT NULL
uid textNOT NULL
eventTypeId integernull
title textNOT NULL
description textnull
startTime timestamp(3)NOT NULL
endTime timestamp(3)NOT NULL
createdAt timestamp(3)NOT NULL
updatedAt timestamp(3)null
location textnull
paid booleanNOT NULL
status BookingStatusNOT NULL
rescheduled booleannull
userId integernull
teamId integernull
eventLength integernull
eventParentId integernull
userEmail textnull
userName textnull
userUsername textnull
ratingFeedback textnull
rating integernull
noShowHost booleannull
isTeamBooking booleanNOT NULL

Indexes

  • UNIQUE (id) BookingDenormalized_id_key
  • (userId) BookingDenormalized_userId_idx
  • (createdAt) BookingDenormalized_createdAt_idx
  • (eventTypeId) BookingDenormalized_eventTypeId_idx
  • (eventParentId) BookingDenormalized_eventParentId_idx
  • (teamId) BookingDenormalized_teamId_idx
  • (startTime) BookingDenormalized_startTime_idx
  • (endTime) BookingDenormalized_endTime_idx
  • (status) BookingDenormalized_status_idx
  • (teamId, isTeamBooking) BookingDenormalized_teamId_isTeamBooking_idx
  • (userId, isTeamBooking) BookingDenormalized_userId_isTeamBooking_idx
  • (startTime, endTime) BookingDenormalized_startTime_endTime_idx

Referenced by

  • nothing

BookingReport

14 cols · no RLS · line 1705View in 3D

CreateTable

  • Nullable foreign key reportedById — BookingReport.reportedById may be NULL, so the relationship to users is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key organizationId — BookingReport.organizationId may be NULL, so the relationship to Team is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key watchlistId — BookingReport.watchlistId may be NULL, so the relationship to Watchlist is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key globalWatchlistId — BookingReport.globalWatchlistId may be NULL, so the relationship to Watchlist is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Either/or foreign keys without a CHECK reportedById, organizationId, watchlistId, globalWatchlistId — BookingReport has several nullable FKs (reportedById, organizationId, watchlistId, globalWatchlistId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.
  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone updatedAt — updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKuuidNOT NULL
bookingUid textNOT NULLBooking.uid 1:1 · CASCADE
bookerEmail textNOT NULL
reportedById integernullusers.id 1:N · SET NULL
organizationId integernullTeam.id 1:N · CASCADE
reason BookingReportReasonNOT NULL
description textnull
cancelled booleanNOT NULLFALSE
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP
updatedAt timestamp(3)NOT NULL
status BookingReportStatusNOT NULL'PENDING'
systemStatus SystemReportStatusNOT NULL'PENDING'
watchlistId uuidnullWatchlist.id 1:N · SET NULL
globalWatchlistId uuidnullWatchlist.id 1:N · SET NULL

Indexes

  • UNIQUE (bookingUid) BookingReport_bookingUid_key
  • (bookerEmail) BookingReport_bookerEmail_idx
  • (reportedById) BookingReport_reportedById_idx
  • (organizationId) BookingReport_organizationId_idx
  • (watchlistId) BookingReport_watchlistId_idx
  • (globalWatchlistId) BookingReport_globalWatchlistId_idx
  • (systemStatus) BookingReport_systemStatus_idx
  • (createdAt) BookingReport_createdAt_idx

Referenced by

  • nothing

BookingInternalNote

6 cols · no RLS · line 1829View in 3D

CreateTable

  • Foreign key without index notePresetId — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on InternalNotePreset must scan BookingInternalNote to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to BookingInternalNote maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often InternalNotePreset rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • Foreign key without index createdById — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on users must scan BookingInternalNote to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to BookingInternalNote maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often users rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • Nullable foreign key notePresetId — BookingInternalNote.notePresetId may be NULL, so the relationship to InternalNotePreset is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKserialNOT NULL
notePresetId integernullInternalNotePreset.id 1:N · CASCADE
text textnull
bookingId integerNOT NULLBooking.id 1:N · CASCADE
createdById integerNOT NULLusers.id 1:N · RESTRICT
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP

Indexes

  • (bookingId) BookingInternalNote_bookingId_idx
  • UNIQUE (bookingId, notePresetId) BookingInternalNote_bookingId_notePresetId_key

Referenced by

  • nothing

BookingAudit

12 cols · no RLS · line 1893View in 3D

CreateTable

  • TIMESTAMP without time zone timestamp — timestamp stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone updatedAt — updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKuuidNOT NULL
bookingUid textNOT NULL
actorId uuidNOT NULLAuditActor.id 1:N · RESTRICT
type BookingAuditTypeNOT NULL
action BookingAuditActionNOT NULL
timestamp timestamp(3)NOT NULL
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP
updatedAt timestamp(3)NOT NULL
source BookingAuditSourceNOT NULL
operationId textNOT NULL
data jsonbnull
context jsonbnull

Indexes

  • (actorId) BookingAudit_actorId_idx
  • (bookingUid) BookingAudit_bookingUid_idx
  • (timestamp) BookingAudit_timestamp_idx
  • (operationId) BookingAudit_operationId_idx

Referenced by

  • nothing

Selected 2 tables

Tables whose names start with Selected_.

Entity-relationship diagram: SelectedCalendar, SelectedSlotsSelectedCalendarid PKtextuserId FKintegerintegrationtextexternalIdtextcredentialId FKintegercreatedAttimestamp(3)updatedAttimestamp(3)googleChannelIdtextgoogleChannelKindtextgoogleChannelResourceIdtextgoogleChannelResourceUritextgoogleChannelExpirationtextchannelIdtextchannelKindtextchannelResourceIdtextchannelResourceUritextchannelExpirationtimestamp(3)syncSubscribedAttimestamp(3)syncSubscribedErrorAttimestamp(3)syncSubscribedErrorCountintegersyncTokentextsyncedAttimestamp(3)syncErrorAttimestamp(3)syncErrorCountintegerdelegationCredentialId FKtextdomainWideDelegationCredentialId FKtexterrortextlastErrorAttimestamp(3)watchAttemptsintegerunwatchAttemptsintegermaxAttemptsintegereventTypeId FKintegerSelectedSlotsid PKserialeventTypeIdintegeruserIdintegerslotUtcStartDatetimestamp(3)slotUtcEndDatetimestamp(3)uidtextreleaseAttimestamp(3)isSeatbooleanCredentialDelegationCredentialDomainWideDelegationEventTypeusersuserIdcredentialIddelegationCredentialIddomainWideDelegationCredentialIdeventTypeId

Relationships

SelectedCalendar

32 cols · no RLS · line 729View in 3D

CreateTable

  • Foreign key without index delegationCredentialId — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on DelegationCredential must scan SelectedCalendar to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to SelectedCalendar maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often DelegationCredential rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • Foreign key without index domainWideDelegationCredentialId — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on DomainWideDelegation must scan SelectedCalendar to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to SelectedCalendar maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often DomainWideDelegation rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • Nullable foreign key credentialId — SelectedCalendar.credentialId may be NULL, so the relationship to Credential is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key delegationCredentialId — SelectedCalendar.delegationCredentialId may be NULL, so the relationship to DelegationCredential is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key domainWideDelegationCredentialId — SelectedCalendar.domainWideDelegationCredentialId may be NULL, so the relationship to DomainWideDelegation is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key eventTypeId — SelectedCalendar.eventTypeId may be NULL, so the relationship to EventType is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Either/or foreign keys without a CHECK credentialId, delegationCredentialId, domainWideDelegationCredentialId, eventTypeId — SelectedCalendar has several nullable FKs (credentialId, delegationCredentialId, domainWideDelegationCredentialId, eventTypeId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.
  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone updatedAt — updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone channelExpiration — channelExpiration stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone syncSubscribedAt — syncSubscribedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone syncSubscribedErrorAt — syncSubscribedErrorAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone syncedAt — syncedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone syncErrorAt — syncErrorAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone lastErrorAt — lastErrorAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • Wide table (32 columns) — Tables this wide usually hide several entities (or a JSON column that wants to be one). Every row update rewrites the whole tuple; TOAST kicks in; indexes bloat.
ColumnTypeNullDefaultReferencesNotes
id PKtextNOT NULL
userId integerNOT NULLusers.id 1:N · CASCADE
integration textNOT NULL
externalId textNOT NULL
credentialId integernullCredential.id 1:N · CASCADE
createdAt timestamp(3)nullCURRENT_TIMESTAMP
updatedAt timestamp(3)null
googleChannelId textnull
googleChannelKind textnull
googleChannelResourceId textnull
googleChannelResourceUri textnull
googleChannelExpiration textnull
channelId textnull
channelKind textnull
channelResourceId textnull
channelResourceUri textnull
channelExpiration timestamp(3)null
syncSubscribedAt timestamp(3)null
syncSubscribedErrorAt timestamp(3)null
syncSubscribedErrorCount integerNOT NULL0
syncToken textnull
syncedAt timestamp(3)null
syncErrorAt timestamp(3)null
syncErrorCount integernull0
delegationCredentialId textnullDelegationCredential.id 1:N · CASCADE
domainWideDelegationCredentialId textnullDomainWideDelegation.id 1:N · CASCADE
error textnull
lastErrorAt timestamp(3)null
watchAttempts integerNOT NULL0
unwatchAttempts integerNOT NULL0
maxAttempts integerNOT NULL3
eventTypeId integernullEventType.id 1:N · SET NULL

Indexes

  • (userId) SelectedCalendar_userId_idx
  • (externalId) SelectedCalendar_externalId_idx
  • (eventTypeId) SelectedCalendar_eventTypeId_idx
  • (credentialId) SelectedCalendar_credentialId_idx
  • (channelId) SelectedCalendar_channelId_idx
  • (integration, googleChannelExpiration, error, watchAttempts, maxAttempts) SelectedCalendar_watch_idx
  • (integration, googleChannelExpiration, error, unwatchAttempts, maxAttempts) SelectedCalendar_unwatch_idx
  • UNIQUE (userId, integration, externalId, eventTypeId) SelectedCalendar_userId_integration_externalId_eventTypeId_key
  • UNIQUE (googleChannelId, eventTypeId) SelectedCalendar_googleChannelId_eventTypeId_key

Referenced by

SelectedSlots

8 cols · no RLS · line 1241View in 3D

CreateTable

  • TIMESTAMP without time zone slotUtcStartDate — slotUtcStartDate stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone slotUtcEndDate — slotUtcEndDate stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone releaseAt — releaseAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • Isolated table — SelectedSlots references nothing and nothing references it. Either it is a staging/log table (fine, say so), or it is dead, or it is the seed of a second data model growing beside the first.
ColumnTypeNullDefaultReferencesNotes
id PKserialNOT NULL
eventTypeId integerNOT NULL
userId integerNOT NULL
slotUtcStartDate timestamp(3)NOT NULL
slotUtcEndDate timestamp(3)NOT NULL
uid textNOT NULL
releaseAt timestamp(3)NOT NULL
isSeat booleanNOT NULLFALSE

Indexes

  • UNIQUE (userId, slotUtcStartDate, slotUtcEndDate, uid) SelectedSlots_userId_slotUtcStartDate_slotUtcEndDate_uid_key

Referenced by

  • nothing

Webhook 2 tables

Tables whose names start with Webhook_.

Entity-relationship diagram: Webhook, WebhookScheduledTriggersWebhookid PKtextuserId FKintegerteamId FKintegereventTypeId FKintegerplatformOAuthClientId FKtextsubscriberUrltextpayloadTemplatetextcreatedAttimestamp(3)activebooleaneventTriggersWebhookTriggerEvents[]appId FKtextsecrettextplatformbooleantimeintegertimeUnitTimeUnitversiontextWebhookScheduledTriggersid PKserialjobNametextsubscriberUrltextpayloadtextstartAftertimestamp(3)retryCountintegercreatedAttimestamp(3)appIdtextwebhookId FKtextbookingId FKintegerAppBookingEventTypePlatformOAuthClientTeamusersuserIdteamIdeventTypeIdplatformOAuthClientIdappIdwebhookIdbookingId

Relationships

Webhook

16 cols · no RLS · line 820View in 3D

CreateTable

  • Foreign key without index teamId — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on Team must scan Webhook to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to Webhook maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often Team rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • Foreign key without index eventTypeId — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on EventType must scan Webhook to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to Webhook maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often EventType rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • Foreign key without index appId — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on App must scan Webhook to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to Webhook maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often App rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • Nullable foreign key userId — Webhook.userId may be NULL, so the relationship to users is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key teamId — Webhook.teamId may be NULL, so the relationship to Team is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key eventTypeId — Webhook.eventTypeId may be NULL, so the relationship to EventType is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key platformOAuthClientId — Webhook.platformOAuthClientId may be NULL, so the relationship to PlatformOAuthClient is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key appId — Webhook.appId may be NULL, so the relationship to App is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Either/or foreign keys without a CHECK userId, teamId, eventTypeId, platformOAuthClientId, appId — Webhook has several nullable FKs (userId, teamId, eventTypeId, platformOAuthClientId, appId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.
  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKtextNOT NULL
userId integernullusers.id 1:N · CASCADE
teamId integernullTeam.id 1:N · CASCADE
eventTypeId integernullEventType.id 1:N · CASCADE
platformOAuthClientId textnullPlatformOAuthClient.id 1:N · CASCADE
subscriberUrl textNOT NULL
payloadTemplate textnull
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP
active booleanNOT NULLTRUE
eventTriggers WebhookTriggerEvents[]null
appId textnullApp.slug 1:N · CASCADE
secret textnull
platform booleanNOT NULLFALSE
time integernull
timeUnit TimeUnitnull
version textNOT NULL'2021-10-20'

Indexes

  • UNIQUE (id) Webhook_id_key
  • (active) Webhook_active_idx
  • UNIQUE (userId, subscriberUrl) Webhook_userId_subscriberUrl_key
  • UNIQUE (platformOAuthClientId, subscriberUrl) Webhook_platformOAuthClientId_subscriberUrl_key

Referenced by

WebhookScheduledTriggers

10 cols · no RLS · line 1154View in 3D

CreateTable

  • Foreign key without index webhookId — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on Webhook must scan WebhookScheduledTriggers to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to WebhookScheduledTriggers maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often Webhook rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • Foreign key without index bookingId — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on Booking must scan WebhookScheduledTriggers to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to WebhookScheduledTriggers maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often Booking rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • Nullable foreign key webhookId — WebhookScheduledTriggers.webhookId may be NULL, so the relationship to Webhook is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key bookingId — WebhookScheduledTriggers.bookingId may be NULL, so the relationship to Booking is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Either/or foreign keys without a CHECK webhookId, bookingId — WebhookScheduledTriggers has several nullable FKs (webhookId, bookingId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.
  • TIMESTAMP without time zone startAfter — startAfter stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKserialNOT NULL
jobName textnull
subscriberUrl textNOT NULL
payload textNOT NULL
startAfter timestamp(3)NOT NULL
retryCount integerNOT NULL0
createdAt timestamp(3)nullCURRENT_TIMESTAMP
appId textnull
webhookId textnullWebhook.id 1:N · CASCADE
bookingId integernullBooking.id 1:N · CASCADE

Indexes

  • none

Referenced by

  • nothing

App 5 tables

Tables whose names start with App_.

Entity-relationship diagram: App, App_RoutingForms_Form, App_RoutingForms_FormResponse, App_RoutingForms_QueuedFormResponse, App_RoutingForms_IncompleteBookingActionsAppslug PKtextdirNametextkeysjsonbcategoriesAppCategories[]createdAttimestamp(3)updatedAttimestamp(3)enabledbooleanApp_RoutingForms_Formid PKtextdescriptiontextpositionintegerroutesjsonbcreatedAttimestamp(3)updatedAttimestamp(3)nametextfieldsjsonbupdatedById FKintegeruserId FKintegerteamId FKintegerdisabledbooleansettingsjsonbApp_RoutingForms_FormResponseid PKserialuuidtextformFillerIdtextformId FKtextresponsejsonbcreatedAttimestamp(3)updatedAttimestamp(3)routedToBookingUid FKtextchosenRouteIdtextApp_RoutingForms_QueuedFormResponseid PKtextformId FKtextresponsejsonbchosenRouteIdtextfallbackActionjsonbcreatedAttimestamp(3)updatedAttimestamp(3)actualResponseId FKintegerApp_RoutingForms_IncompleteBookingActionsid PKserialformId FKtextactionTypeIncompleteBookingActionTypedatajsonbenabledbooleancredentialIdintegerBookingTeamusersuserIdupdatedByIdteamIdformIdroutedToBookingUidformIdformIdactualResponseId

Relationships

App

7 cols · no RLS · line 922View in 3D

CreateTable

  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone updatedAt — updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
slug PKtextNOT NULL
dirName textNOT NULL
keys jsonbnull
categories AppCategories[]null
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP
updatedAt timestamp(3)NOT NULL
enabled booleanNOT NULLFALSE

Indexes

  • UNIQUE (slug) App_slug_key
  • UNIQUE (dirName) App_dirName_key
  • (enabled) App_enabled_idx

Referenced by

App_RoutingForms_Form

13 cols · no RLS · line 935View in 3D

CreateTable

  • Foreign key without index updatedById — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on users must scan App_RoutingForms_Form to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to App_RoutingForms_Form maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often users rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • Foreign key without index teamId — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on Team must scan App_RoutingForms_Form to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to App_RoutingForms_Form maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often Team rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • Nullable foreign key updatedById — App_RoutingForms_Form.updatedById may be NULL, so the relationship to users is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key teamId — App_RoutingForms_Form.teamId may be NULL, so the relationship to Team is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Either/or foreign keys without a CHECK updatedById, teamId — App_RoutingForms_Form has several nullable FKs (updatedById, teamId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.
  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone updatedAt — updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKtextNOT NULL
description textnull
position integerNOT NULL0
routes jsonbnull
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP
updatedAt timestamp(3)NOT NULL
name textNOT NULL
fields jsonbnull
updatedById integernullusers.id 1:N · SET NULL
userId integerNOT NULLusers.id 1:N · CASCADE
teamId integernullTeam.id 1:N · CASCADE
disabled booleanNOT NULLFALSE
settings jsonbnull

Indexes

  • (userId) App_RoutingForms_Form_userId_idx
  • (disabled) App_RoutingForms_Form_disabled_idx

Referenced by

App_RoutingForms_FormResponse

9 cols · no RLS · line 954View in 3D

CreateTable

  • Nullable foreign key routedToBookingUid — App_RoutingForms_FormResponse.routedToBookingUid may be NULL, so the relationship to Booking is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone updatedAt — updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKserialNOT NULL
uuid textnull
formFillerId textNOT NULL
formId textNOT NULLApp_RoutingForms_Form.id 1:N · CASCADE
response jsonbNOT NULL
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP
updatedAt timestamp(3)null
routedToBookingUid textnullBooking.uid 1:1 · SET NULL
chosenRouteId textnull

Indexes

  • UNIQUE (routedToBookingUid) App_RoutingForms_FormResponse_routedToBookingUid_key
  • (formFillerId) App_RoutingForms_FormResponse_formFillerId_idx
  • (formId) App_RoutingForms_FormResponse_formId_idx
  • (formId, createdAt) App_RoutingForms_FormResponse_formId_createdAt_idx
  • (routedToBookingUid) App_RoutingForms_FormResponse_routedToBookingUid_idx
  • UNIQUE (formFillerId, formId) App_RoutingForms_FormResponse_formFillerId_formId_key

Referenced by

App_RoutingForms_QueuedFormResponse

8 cols · no RLS · line 969View in 3D

CreateTable

  • Foreign key without index formId — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on App_RoutingForms_Form must scan App_RoutingForms_QueuedFormResponse to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to App_RoutingForms_QueuedFormResponse maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often App_RoutingForms_Form rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • Nullable foreign key actualResponseId — App_RoutingForms_QueuedFormResponse.actualResponseId may be NULL, so the relationship to App_RoutingForms_FormResponse is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone updatedAt — updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKtextNOT NULL
formId textNOT NULLApp_RoutingForms_Form.id 1:N · CASCADE
response jsonbNOT NULL
chosenRouteId textnull
fallbackAction jsonbnull
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP
updatedAt timestamp(3)null
actualResponseId integernullApp_RoutingForms_FormResponse.id 1:1 · CASCADE

Indexes

  • UNIQUE (actualResponseId) App_RoutingForms_QueuedFormResponse_actualResponseId_key

Referenced by

App_RoutingForms_IncompleteBookingActions

6 cols · no RLS · line 1773View in 3D

CreateTable

  • Foreign key without index formId — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on App_RoutingForms_Form must scan App_RoutingForms_IncompleteBookingActions to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to App_RoutingForms_IncompleteBookingActions maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often App_RoutingForms_Form rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
ColumnTypeNullDefaultReferencesNotes
id PKserialNOT NULL
formId textNOT NULLApp_RoutingForms_Form.id 1:N · CASCADE
actionType IncompleteBookingActionTypeNOT NULL
data jsonbNOT NULL
enabled booleanNOT NULLTRUE
credentialId integernull

Indexes

  • none

Referenced by

  • nothing

Routing 3 tables

Tables whose names start with Routing_.

Entity-relationship diagram: RoutingFormResponseField, RoutingFormResponseDenormalized, RoutingTraceRoutingFormResponseFieldid PKserialresponseId FKintegerfieldIdtextvalueStringtextvalueNumbernumeric(65, 30)valueStringArraytext[]RoutingFormResponseDenormalizedid PKintegeruuidtextformIdtextformNametextformTeamIdintegerformUserIdintegerbookingUidtextbookingId FKintegerbookingStatusBookingStatusbookingStatusOrderintegerbookingCreatedAttimestamp(3)bookingStartTimetimestamp(3)bookingEndTimetimestamp(3)bookingUserIdintegerbookingUserNametextbookingUserEmailtextbookingUserAvatarUrltextbookingAssignmentReasontexteventTypeIdintegereventTypeParentIdintegereventTypeSchedulingTypetextcreatedAttimestamp(3)utm_sourcetextutm_mediumtextutm_campaigntextutm_termtextutm_contenttextRoutingTraceid PKtextcreatedAttimestamp(3)tracejsonbformResponseId FKintegerqueuedFormResponseId FKtextbookingUid FKtextassignmentReasonId FKintegerApp_RoutingForms_FormResponseApp_RoutingForms_QueuedFormResponseAssignmentReasonBookingbookingIdidformResponseIdqueuedFormResponseIdbookingUidassignmentReasonIdresponseIdresponseId

Relationships

RoutingFormResponseField

6 cols · no RLS · line 983View in 3D

CreateTable

ColumnTypeNullDefaultReferencesNotes
id PKserialNOT NULL
responseId integerNOT NULLRoutingFormResponseDenormalized.id 1:N · CASCADE
fieldId textNOT NULL
valueString textnull
valueNumber numeric(65, 30)null
valueStringArray text[]null

Indexes

  • (responseId) RoutingFormResponseField_responseId_idx
  • (fieldId) RoutingFormResponseField_fieldId_idx
  • (valueNumber) RoutingFormResponseField_valueNumber_idx
  • (valueStringArray) RoutingFormResponseField_valueStringArray_idx

Referenced by

  • nothing

RoutingFormResponseDenormalized

27 cols · no RLS · line 995View in 3D

CreateTable

  • Nullable foreign key bookingId — RoutingFormResponseDenormalized.bookingId may be NULL, so the relationship to Booking is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • TIMESTAMP without time zone bookingCreatedAt — bookingCreatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone bookingStartTime — bookingStartTime stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone bookingEndTime — bookingEndTime stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKintegerNOT NULLApp_RoutingForms_FormResponse.id 1:1 · CASCADE
uuid textnull
formId textNOT NULL
formName textNOT NULL
formTeamId integernull
formUserId integerNOT NULL
bookingUid textnull
bookingId integernullBooking.id 1:N · SET NULL
bookingStatus BookingStatusnull
bookingStatusOrder integernull
bookingCreatedAt timestamp(3)null
bookingStartTime timestamp(3)null
bookingEndTime timestamp(3)null
bookingUserId integernull
bookingUserName textnull
bookingUserEmail textnull
bookingUserAvatarUrl textnull
bookingAssignmentReason textnull
eventTypeId integernull
eventTypeParentId integernull
eventTypeSchedulingType textnull
createdAt timestamp(3)NOT NULL
utm_source textnull
utm_medium textnull
utm_campaign textnull
utm_term textnull
utm_content textnull

Indexes

  • (formId) RoutingFormResponseDenormalized_formId_idx
  • (formTeamId) RoutingFormResponseDenormalized_formTeamId_idx
  • (formUserId) RoutingFormResponseDenormalized_formUserId_idx
  • (formId, createdAt) RoutingFormResponseDenormalized_formId_createdAt_idx
  • (bookingId) RoutingFormResponseDenormalized_bookingId_idx
  • (bookingUserId) RoutingFormResponseDenormalized_bookingUserId_idx
  • (eventTypeId, eventTypeParentId) RoutingFormResponseDenormalized_eventTypeId_eventTypeParent_idx

Referenced by

RoutingTrace

7 cols · no RLS · line 2126View in 3D

CreateTable

  • Nullable foreign key formResponseId — RoutingTrace.formResponseId may be NULL, so the relationship to App_RoutingForms_FormResponse is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key queuedFormResponseId — RoutingTrace.queuedFormResponseId may be NULL, so the relationship to App_RoutingForms_QueuedFormResponse is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key bookingUid — RoutingTrace.bookingUid may be NULL, so the relationship to Booking is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key assignmentReasonId — RoutingTrace.assignmentReasonId may be NULL, so the relationship to AssignmentReason is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Either/or foreign keys without a CHECK formResponseId, queuedFormResponseId, bookingUid, assignmentReasonId — RoutingTrace has several nullable FKs (formResponseId, queuedFormResponseId, bookingUid, assignmentReasonId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.
  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKtextNOT NULL
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP
trace jsonbNOT NULL
formResponseId integernullApp_RoutingForms_FormResponse.id 1:1 · CASCADE
queuedFormResponseId textnullApp_RoutingForms_QueuedFormResponse.id 1:1 · CASCADE
bookingUid textnullBooking.uid 1:1 · CASCADE
assignmentReasonId integernullAssignmentReason.id 1:1 · CASCADE

Indexes

  • UNIQUE (formResponseId) RoutingTrace_formResponseId_key
  • UNIQUE (queuedFormResponseId) RoutingTrace_queuedFormResponseId_key
  • UNIQUE (bookingUid) RoutingTrace_bookingUid_key
  • UNIQUE (assignmentReasonId) RoutingTrace_assignmentReasonId_key

Referenced by

  • nothing

Workflow 5 tables

Tables whose names start with Workflow_.

Entity-relationship diagram: WorkflowStep, Workflow, WorkflowReminder, WorkflowStepTranslation, WorkflowOptOutContactWorkflowStepid PKserialstepNumberintegeractionWorkflowActionsworkflowId FKintegersendTotextreminderBodytextemailSubjecttexttemplateWorkflowTemplatesnumberRequiredbooleansendertextnumberVerificationPendingbooleanincludeCalendarEventbooleanverifiedAttimestamp(3)agentId FKtextinboundAgentId FKtextautoTranslateEnabledbooleansourceLocaletextWorkflowid PKserialpositionintegernametextuserId FKintegerteamId FKintegerisActiveOnAllbooleantriggerWorkflowTriggerEventstimeintegertimeUnitTimeUnittypeWorkflowTypeWorkflowReminderid PKserialuuidtextbookingUid FKtextmethodWorkflowMethodsscheduledDatetimestamp(3)referenceIdtextscheduledbooleanworkflowStepId FKintegercancelledbooleanseatReferenceIdtextisMandatoryReminderbooleanretryCountintegerWorkflowStepTranslationuid PKtextworkflowStepId FKintegerfieldWorkflowStepAutoTranslatedFieldsourceLocaletexttargetLocaletexttranslatedTexttextcreatedAttimestamp(3)updatedAttimestamp(3)WorkflowOptOutContactid PKserialtypeWorkflowContactTypevaluetextoptedOutbooleancreatedAttimestamp(3)updatedAttimestamp(3)AgentBookingTeamusersuserIdteamIdworkflowIdagentIdinboundAgentIdbookingUidworkflowStepIdworkflowStepId

Relationships

WorkflowStep

17 cols · no RLS · line 1039View in 3D

CreateTable

  • Nullable foreign key agentId — WorkflowStep.agentId may be NULL, so the relationship to Agent is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key inboundAgentId — WorkflowStep.inboundAgentId may be NULL, so the relationship to Agent is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Either/or foreign keys without a CHECK agentId, inboundAgentId — WorkflowStep has several nullable FKs (agentId, inboundAgentId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.
  • TIMESTAMP without time zone verifiedAt — verifiedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKserialNOT NULL
stepNumber integerNOT NULL
action WorkflowActionsNOT NULL
workflowId integerNOT NULLWorkflow.id 1:N · CASCADE
sendTo textnull
reminderBody textnull
emailSubject textnull
template WorkflowTemplatesNOT NULL'REMINDER'
numberRequired booleannull
sender textnull
numberVerificationPending booleanNOT NULLTRUE
includeCalendarEvent booleanNOT NULLFALSE
verifiedAt timestamp(3)null
agentId textnullAgent.id 1:1 · SET NULL
inboundAgentId textnullAgent.id 1:1 · SET NULL
autoTranslateEnabled booleanNOT NULLFALSE
sourceLocale textnull

Indexes

  • UNIQUE (agentId) WorkflowStep_agentId_key
  • UNIQUE (inboundAgentId) WorkflowStep_inboundAgentId_key
  • (workflowId) WorkflowStep_workflowId_idx

Referenced by

Workflow

10 cols · no RLS · line 1062View in 3D

CreateTable

  • Nullable foreign key userId — Workflow.userId may be NULL, so the relationship to users is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key teamId — Workflow.teamId may be NULL, so the relationship to Team is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Either/or foreign keys without a CHECK userId, teamId — Workflow has several nullable FKs (userId, teamId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.
ColumnTypeNullDefaultReferencesNotes
id PKserialNOT NULL
position integerNOT NULL0
name textNOT NULL
userId integernullusers.id 1:N · CASCADE
teamId integernullTeam.id 1:N · CASCADE
isActiveOnAll booleanNOT NULLFALSE
trigger WorkflowTriggerEventsNOT NULL
time integernull
timeUnit TimeUnitnull
type WorkflowTypeNOT NULL'EVENT_TYPE'

Indexes

  • (userId) Workflow_userId_idx
  • (teamId) Workflow_teamId_idx

Referenced by

WorkflowReminder

12 cols · no RLS · line 1136View in 3D

CreateTable

  • Nullable foreign key bookingUid — WorkflowReminder.bookingUid may be NULL, so the relationship to Booking is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key workflowStepId — WorkflowReminder.workflowStepId may be NULL, so the relationship to WorkflowStep is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Either/or foreign keys without a CHECK bookingUid, workflowStepId — WorkflowReminder has several nullable FKs (bookingUid, workflowStepId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.
  • TIMESTAMP without time zone scheduledDate — scheduledDate stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKserialNOT NULL
uuid textnull
bookingUid textnullBooking.uid 1:N · SET NULL
method WorkflowMethodsNOT NULL
scheduledDate timestamp(3)NOT NULL
referenceId textnull
scheduled booleanNOT NULL
workflowStepId integernullWorkflowStep.id 1:N · CASCADE
cancelled booleannull
seatReferenceId textnull
isMandatoryReminder booleannullFALSE
retryCount integerNOT NULL0

Indexes

  • UNIQUE (uuid) WorkflowReminder_uuid_key
  • UNIQUE (referenceId) WorkflowReminder_referenceId_key
  • (bookingUid) WorkflowReminder_bookingUid_idx
  • (workflowStepId) WorkflowReminder_workflowStepId_idx
  • (seatReferenceId) WorkflowReminder_seatReferenceId_idx
  • (method, scheduled, scheduledDate) WorkflowReminder_method_scheduled_scheduledDate_idx
  • (cancelled, scheduledDate) WorkflowReminder_cancelled_scheduledDate_idx

Referenced by

  • nothing

WorkflowStepTranslation

8 cols · no RLS · line 1651View in 3D

CreateTable

  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone updatedAt — updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
uid PKtextNOT NULL
workflowStepId integerNOT NULLWorkflowStep.id 1:N · CASCADE
field WorkflowStepAutoTranslatedFieldNOT NULL
sourceLocale textNOT NULL
targetLocale textNOT NULL
translatedText textNOT NULL
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP
updatedAt timestamp(3)NOT NULL

Indexes

  • UNIQUE (workflowStepId, field, targetLocale) WorkflowStepTranslation_workflowStepId_field_targetLocale_key

Referenced by

  • nothing

WorkflowOptOutContact

6 cols · no RLS · line 1841View in 3D

CreateTable

  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone updatedAt — updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • Isolated table — WorkflowOptOutContact references nothing and nothing references it. Either it is a staging/log table (fine, say so), or it is dead, or it is the seed of a second data model growing beside the first.
ColumnTypeNullDefaultReferencesNotes
id PKserialNOT NULL
type WorkflowContactTypeNOT NULL
value textNOT NULL
optedOut booleanNOT NULL
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP
updatedAt timestamp(3)NOT NULL

Indexes

  • UNIQUE (type, value) WorkflowOptOutContact_type_value_key

Referenced by

  • nothing

Workflows 3 tables

Tables whose names start with Workflows_.

Entity-relationship diagram: WorkflowsOnEventTypes, WorkflowsOnRoutingForms, WorkflowsOnTeamsWorkflowsOnEventTypesid PKserialworkflowId FKintegereventTypeId FKintegerWorkflowsOnRoutingFormsid PKserialworkflowId FKintegerroutingFormId FKtextWorkflowsOnTeamsid PKserialworkflowId FKintegerteamId FKintegerApp_RoutingForms_FormEventTypeTeamWorkflowworkflowIdeventTypeIdworkflowIdroutingFormIdworkflowIdteamId

Relationships

WorkflowsOnEventTypes

3 cols · no RLS · line 1097View in 3D

CreateTable

ColumnTypeNullDefaultReferencesNotes
id PKserialNOT NULL
workflowId integerNOT NULLWorkflow.id 1:N · CASCADE
eventTypeId integerNOT NULLEventType.id 1:N · CASCADE

Indexes

  • (workflowId) WorkflowsOnEventTypes_workflowId_idx
  • (eventTypeId) WorkflowsOnEventTypes_eventTypeId_idx
  • UNIQUE (workflowId, eventTypeId) WorkflowsOnEventTypes_workflowId_eventTypeId_key

Referenced by

  • nothing

WorkflowsOnRoutingForms

3 cols · no RLS · line 1106View in 3D

CreateTable

ColumnTypeNullDefaultReferencesNotes
id PKserialNOT NULL
workflowId integerNOT NULLWorkflow.id 1:N · CASCADE
routingFormId textNOT NULLApp_RoutingForms_Form.id 1:N · CASCADE

Indexes

  • (workflowId) WorkflowsOnRoutingForms_workflowId_idx
  • (routingFormId) WorkflowsOnRoutingForms_routingFormId_idx
  • UNIQUE (workflowId, routingFormId) WorkflowsOnRoutingForms_workflowId_routingFormId_key

Referenced by

  • nothing

WorkflowsOnTeams

3 cols · no RLS · line 1115View in 3D

CreateTable

ColumnTypeNullDefaultReferencesNotes
id PKserialNOT NULL
workflowId integerNOT NULLWorkflow.id 1:N · CASCADE
teamId integerNOT NULLTeam.id 1:N · CASCADE

Indexes

  • (workflowId) WorkflowsOnTeams_workflowId_idx
  • (teamId) WorkflowsOnTeams_teamId_idx
  • UNIQUE (workflowId, teamId) WorkflowsOnTeams_workflowId_teamId_key

Referenced by

  • nothing

Verified 2 tables

Tables whose names start with Verified_.

Entity-relationship diagram: VerifiedNumber, VerifiedEmailVerifiedNumberid PKserialuserId FKintegerteamId FKintegerphoneNumbertextVerifiedEmailid PKserialuserId FKintegerteamId FKintegeremailtextTeamusersuserIdteamIduserIdteamId

Relationships

VerifiedNumber

4 cols · no RLS · line 1182View in 3D

CreateTable

  • Nullable foreign key userId — VerifiedNumber.userId may be NULL, so the relationship to users is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key teamId — VerifiedNumber.teamId may be NULL, so the relationship to Team is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Either/or foreign keys without a CHECK userId, teamId — VerifiedNumber has several nullable FKs (userId, teamId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.
ColumnTypeNullDefaultReferencesNotes
id PKserialNOT NULL
userId integernullusers.id 1:N · CASCADE
teamId integernullTeam.id 1:N · CASCADE
phoneNumber textNOT NULL

Indexes

  • (userId) VerifiedNumber_userId_idx
  • (teamId) VerifiedNumber_teamId_idx

Referenced by

  • nothing

VerifiedEmail

4 cols · no RLS · line 1192View in 3D

CreateTable

  • Nullable foreign key userId — VerifiedEmail.userId may be NULL, so the relationship to users is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key teamId — VerifiedEmail.teamId may be NULL, so the relationship to Team is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Either/or foreign keys without a CHECK userId, teamId — VerifiedEmail has several nullable FKs (userId, teamId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.
ColumnTypeNullDefaultReferencesNotes
id PKserialNOT NULL
userId integernullusers.id 1:N · CASCADE
teamId integernullTeam.id 1:N · CASCADE
email textNOT NULL

Indexes

  • (userId) VerifiedEmail_userId_idx
  • (teamId) VerifiedEmail_teamId_idx

Referenced by

  • nothing

Access 2 tables

Tables whose names start with Access_.

Entity-relationship diagram: AccessCode, AccessTokenAccessCodeid PKserialcodetextclientId FKtextexpiresAttimestamp(3)scopesAccessScope[]userId FKintegerteamId FKintegercodeChallengetextcodeChallengeMethodtextAccessTokenid PKserialsecrettextcreatedAttimestamp(3)expiresAttimestamp(3)platformOAuthClientId FKtextuserId FKintegerOAuthClientPlatformOAuthClientTeamusersclientIduserIdteamIduserIdplatformOAuthClientId

Relationships

AccessCode

9 cols · no RLS · line 1274View in 3D

CreateTable

  • Foreign key without index clientId — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on OAuthClient must scan AccessCode to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to AccessCode maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often OAuthClient rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • Foreign key without index userId — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on users must scan AccessCode to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to AccessCode maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often users rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • Foreign key without index teamId — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on Team must scan AccessCode to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to AccessCode maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often Team rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • Nullable foreign key clientId — AccessCode.clientId may be NULL, so the relationship to OAuthClient is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key userId — AccessCode.userId may be NULL, so the relationship to users is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key teamId — AccessCode.teamId may be NULL, so the relationship to Team is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Either/or foreign keys without a CHECK clientId, userId, teamId — AccessCode has several nullable FKs (clientId, userId, teamId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.
  • TIMESTAMP without time zone expiresAt — expiresAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKserialNOT NULL
code textNOT NULL
clientId textnullOAuthClient.clientId 1:N · CASCADE
expiresAt timestamp(3)NOT NULL
scopes AccessScope[]null
userId integernullusers.id 1:N · CASCADE
teamId integernullTeam.id 1:N · CASCADE
codeChallenge textnull
codeChallengeMethod textnull'S256'

Indexes

  • none

Referenced by

  • nothing

AccessToken

6 cols · no RLS · line 1440View in 3D

CreateTable

  • Foreign key without index userId — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on users must scan AccessToken to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to AccessToken maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often users rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • Foreign key without index platformOAuthClientId — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on PlatformOAuthClient must scan AccessToken to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to AccessToken maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often PlatformOAuthClient rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone expiresAt — expiresAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKserialNOT NULL
secret textNOT NULL
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP
expiresAt timestamp(3)NOT NULL
platformOAuthClientId textNOT NULLPlatformOAuthClient.id 1:N · CASCADE
userId integerNOT NULLusers.id 1:N · CASCADE

Indexes

  • UNIQUE (secret) AccessToken_secret_key

Referenced by

  • nothing

Calendar 2 tables

Tables whose names start with Calendar_.

Entity-relationship diagram: CalendarCache, CalendarCacheEventCalendarCacheidtextkey PKtextvaluejsonbexpiresAttimestamp(3)updatedAttimestamp(3)credentialId PKintegeruserIdintegerCalendarCacheEventid PKtextselectedCalendarId FKtextexternalIdtextexternalEtagtextiCalUIDtextiCalSequenceintegersummarytextdescriptiontextlocationtextstarttimestamp(3)endtimestamp(3)isAllDaybooleantimeZonetextstatusCalendarCacheEventStatusrecurringEventIdtextoriginalStartTimetimestamp(3)createdAttimestamp(3)updatedAttimestamp(3)externalCreatedAttimestamp(3)externalUpdatedAttimestamp(3)CredentialSelectedCalendarcredentialIdselectedCalendarId

Relationships

CalendarCache

7 cols · no RLS · line 1319View in 3D

CreateTable

  • TIMESTAMP without time zone expiresAt — expiresAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone updatedAt — updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id textnull
key PKtextNOT NULL
value jsonbNOT NULL
expiresAt timestamp(3)NOT NULL
updatedAt timestamp(3)NOT NULLCURRENT_TIMESTAMP
credentialId PKintegerNOT NULLCredential.id 1:N · CASCADE
userId integernull

Indexes

  • (userId, key) CalendarCache_userId_key_idx
  • UNIQUE (credentialId, key) CalendarCache_credentialId_key_key

Referenced by

  • nothing

CalendarCacheEvent

20 cols · no RLS · line 2051View in 3D

CreateTable

  • TIMESTAMP without time zone start — start stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone end — end stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone originalStartTime — originalStartTime stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone updatedAt — updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone externalCreatedAt — externalCreatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone externalUpdatedAt — externalUpdatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKtextNOT NULL
selectedCalendarId textNOT NULLSelectedCalendar.id 1:N · CASCADE
externalId textNOT NULL
externalEtag textNOT NULL
iCalUID textnull
iCalSequence integerNOT NULL0
summary textnull
description textnull
location textnull
start timestamp(3)NOT NULL
end timestamp(3)NOT NULL
isAllDay booleanNOT NULLFALSE
timeZone textnull
status CalendarCacheEventStatusNOT NULL'confirmed'
recurringEventId textnull
originalStartTime timestamp(3)null
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP
updatedAt timestamp(3)NOT NULL
externalCreatedAt timestamp(3)null
externalUpdatedAt timestamp(3)null

Indexes

  • (start, end, status) CalendarCacheEvent_start_end_status_idx
  • (selectedCalendarId, iCalUID) CalendarCacheEvent_selectedCalendarId_iCalUID_idx
  • UNIQUE (selectedCalendarId, externalId) CalendarCacheEvent_selectedCalendarId_externalId_key

Referenced by

  • nothing

Out 2 tables

Tables whose names start with Out_.

Entity-relationship diagram: OutOfOfficeEntry, OutOfOfficeReasonOutOfOfficeEntryid PKserialuuidtextstarttimestamp(3)endtimestamp(3)notestextshowNotePubliclybooleanuserId FKintegertoUserId FKintegerreasonId FKintegercreatedAttimestamp(3)updatedAttimestamp(3)OutOfOfficeReasonid PKserialemojitextreasontextenabledbooleanuserId FKintegerusersuserIduserIdtoUserIdreasonId

Relationships

OutOfOfficeEntry

11 cols · no RLS · line 1355View in 3D

CreateTable

  • Foreign key without index reasonId — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on OutOfOfficeReason must scan OutOfOfficeEntry to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to OutOfOfficeEntry maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often OutOfOfficeReason rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • Nullable foreign key toUserId — OutOfOfficeEntry.toUserId may be NULL, so the relationship to users is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key reasonId — OutOfOfficeEntry.reasonId may be NULL, so the relationship to OutOfOfficeReason is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Either/or foreign keys without a CHECK toUserId, reasonId — OutOfOfficeEntry has several nullable FKs (toUserId, reasonId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.
  • TIMESTAMP without time zone start — start stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone end — end stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone updatedAt — updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKserialNOT NULL
uuid textNOT NULL
start timestamp(3)NOT NULL
end timestamp(3)NOT NULL
notes textnull
showNotePublicly booleanNOT NULLFALSE
userId integerNOT NULLusers.id 1:N · CASCADE
toUserId integernullusers.id 1:N · CASCADE
reasonId integernullOutOfOfficeReason.id 1:N · SET NULL
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP
updatedAt timestamp(3)NOT NULL

Indexes

  • UNIQUE (uuid) OutOfOfficeEntry_uuid_key
  • (uuid) OutOfOfficeEntry_uuid_idx
  • (userId) OutOfOfficeEntry_userId_idx
  • (toUserId) OutOfOfficeEntry_toUserId_idx
  • (start, end) OutOfOfficeEntry_start_end_idx

Referenced by

  • nothing

OutOfOfficeReason

5 cols · no RLS · line 1372View in 3D

CreateTable

  • Foreign key without index userId — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on users must scan OutOfOfficeReason to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to OutOfOfficeReason maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often users rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • Nullable foreign key userId — OutOfOfficeReason.userId may be NULL, so the relationship to users is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
ColumnTypeNullDefaultReferencesNotes
id PKserialNOT NULL
emoji textNOT NULL
reason textNOT NULL
enabled booleanNOT NULLTRUE
userId integernullusers.id 1:N · CASCADE

Indexes

  • UNIQUE (reason) OutOfOfficeReason_reason_key

Referenced by

Platform 4 tables

Tables whose names start with Platform_.

Entity-relationship diagram: PlatformOAuthClient, PlatformAuthorizationToken, PlatformBilling, _PlatformOAuthClientToUserPlatformOAuthClientid PKtextnametextsecrettextpermissionsintegerlogotextredirectUristext[]organizationId FKintegerbookingRedirectUritextbookingCancelRedirectUritextbookingRescheduleRedirectUritextareEmailsEnabledbooleanareDefaultEventTypesEnabledbooleanareCalendarEventsEnabledbooleancreatedAttimestamp(3)PlatformAuthorizationTokenid PKtextplatformOAuthClientId FKtextuserId FKintegercreatedAttimestamp(3)PlatformBillingid PKintegercustomerIdtextsubscriptionIdtextpriceIdtextplantextbillingCycleStartintegerbillingCycleEndintegeroverduebooleanmanagerBillingId FKinteger_PlatformOAuthClientToUserA PKtextB PKintegerTeamusersmanagerBillingIdidorganizationIduserIdplatformOAuthClientIdAB

Relationships

PlatformOAuthClient

14 cols · no RLS · line 1410View in 3D

CreateTable

  • Foreign key without index organizationId — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on Team must scan PlatformOAuthClient to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to PlatformOAuthClient maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often Team rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKtextNOT NULL
name textNOT NULL
secret textNOT NULL
permissions integerNOT NULL
logo textnull
redirectUris text[]null
organizationId integerNOT NULLTeam.id 1:N · CASCADE
bookingRedirectUri textnull
bookingCancelRedirectUri textnull
bookingRescheduleRedirectUri textnull
areEmailsEnabled booleanNOT NULLFALSE
areDefaultEventTypesEnabled booleanNOT NULLTRUE
areCalendarEventsEnabled booleanNOT NULLTRUE
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP

Indexes

  • none

Referenced by

PlatformAuthorizationToken

4 cols · no RLS · line 1430View in 3D

CreateTable

  • Foreign key without index platformOAuthClientId — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on PlatformOAuthClient must scan PlatformAuthorizationToken to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to PlatformAuthorizationToken maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often PlatformOAuthClient rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKtextNOT NULL
platformOAuthClientId textNOT NULLPlatformOAuthClient.id 1:N · CASCADE
userId integerNOT NULLusers.id 1:N · CASCADE
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP

Indexes

  • UNIQUE (userId, platformOAuthClientId) PlatformAuthorizationToken_userId_platformOAuthClientId_key

Referenced by

  • nothing

PlatformBilling

9 cols · no RLS · line 1520View in 3D

CreateTable

  • Foreign key without index managerBillingId — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on PlatformBilling must scan PlatformBilling to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to PlatformBilling maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often PlatformBilling rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • Nullable foreign key managerBillingId — PlatformBilling.managerBillingId may be NULL, so the relationship to PlatformBilling is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
ColumnTypeNullDefaultReferencesNotes
id PKintegerNOT NULLTeam.id 1:1 · CASCADE
customerId textNOT NULL
subscriptionId textnull
priceId textnull
plan textNOT NULL'none'
billingCycleStart integernull
billingCycleEnd integernull
overdue booleannullFALSE
managerBillingId integernullPlatformBilling.id 1:N · SET NULL

Indexes

  • UNIQUE (id) PlatformBilling_id_key

Referenced by

_PlatformOAuthClientToUser

2 cols · no RLS · line 2147View in 3D

CreateTable

ColumnTypeNullDefaultReferencesNotes
A PKtextNOT NULLPlatformOAuthClient.id 1:N · CASCADE
B PKintegerNOT NULLusers.id 1:N · CASCADE

Indexes

  • (B) _PlatformOAuthClientToUser_B_index

Referenced by

  • nothing

Attribute 5 tables

Tables whose names start with Attribute_.

Entity-relationship diagram: AttributeOption, Attribute, AttributeToUser, AttributeSyncRule, AttributeSyncFieldMappingAttributeOptionid PKtextattributeId FKtextvaluetextslugtextisGroupbooleancontainstext[]Attributeid PKtextteamId FKintegertypeAttributeTypenametextslugtextenabledbooleanusersCanEditRelationbooleancreatedAttimestamp(3)updatedAttimestamp(3)isWeightsEnabledbooleanisLockedbooleanAttributeToUserid PKtextmemberId FKintegerattributeOptionId FKtextweightintegercreatedAttimestamp(3)createdById FKintegercreatedByDSyncId FKtextupdatedAttimestamp(3)updatedById FKintegerupdatedByDSyncId FKtextAttributeSyncRuleid PKtextintegrationAttributeSyncId FKtextrulejsonbcreatedAttimestamp(3)updatedAttimestamp(3)AttributeSyncFieldMappingid PKtextintegrationFieldNametextattributeId FKtextenabledbooleanintegrationAttributeSyncId FKtextcreatedAttimestamp(3)updatedAttimestamp(3)DSyncDataIntegrationAttributeSyncMembershipTeamusersteamIdintegrationAttributeSyncIdattributeIdattributeIdintegrationAttributeSyncIdmemberIdattributeOptionIdcreatedByIdcreatedByDSyncIdupdatedByIdupdatedByDSyncId

Relationships

AttributeOption

6 cols · no RLS · line 1535View in 3D

CreateTable

  • Foreign key without index attributeId — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on Attribute must scan AttributeOption to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to AttributeOption maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often Attribute rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
ColumnTypeNullDefaultReferencesNotes
id PKtextNOT NULL
attributeId textNOT NULLAttribute.id 1:N · CASCADE
value textNOT NULL
slug textNOT NULL
isGroup booleanNOT NULLFALSE
contains text[]null

Indexes

  • none

Referenced by

Attribute

11 cols · no RLS · line 1547View in 3D

CreateTable

  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone updatedAt — updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKtextNOT NULL
teamId integerNOT NULLTeam.id 1:N · CASCADE
type AttributeTypeNOT NULL
name textNOT NULL
slug textNOT NULL
enabled booleanNOT NULLTRUE
usersCanEditRelation booleanNOT NULLFALSE
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP
updatedAt timestamp(3)NOT NULL
isWeightsEnabled booleanNOT NULLFALSE
isLocked booleanNOT NULLFALSE

Indexes

  • (teamId) Attribute_teamId_idx
  • UNIQUE (teamId, slug) Attribute_teamId_slug_key

Referenced by

AttributeToUser

10 cols · no RLS · line 1564View in 3D

CreateTable

  • Foreign key without index attributeOptionId — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on AttributeOption must scan AttributeToUser to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to AttributeToUser maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often AttributeOption rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • Foreign key without index createdById — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on users must scan AttributeToUser to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to AttributeToUser maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often users rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • Foreign key without index createdByDSyncId — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on DSyncData must scan AttributeToUser to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to AttributeToUser maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often DSyncData rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • Foreign key without index updatedById — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on users must scan AttributeToUser to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to AttributeToUser maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often users rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • Foreign key without index updatedByDSyncId — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on DSyncData must scan AttributeToUser to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to AttributeToUser maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often DSyncData rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • Nullable foreign key createdById — AttributeToUser.createdById may be NULL, so the relationship to users is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key createdByDSyncId — AttributeToUser.createdByDSyncId may be NULL, so the relationship to DSyncData is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key updatedById — AttributeToUser.updatedById may be NULL, so the relationship to users is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key updatedByDSyncId — AttributeToUser.updatedByDSyncId may be NULL, so the relationship to DSyncData is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Either/or foreign keys without a CHECK createdById, createdByDSyncId, updatedById, updatedByDSyncId — AttributeToUser has several nullable FKs (createdById, createdByDSyncId, updatedById, updatedByDSyncId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.
  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone updatedAt — updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKtextNOT NULL
memberId integerNOT NULLMembership.id 1:N · CASCADE
attributeOptionId textNOT NULLAttributeOption.id 1:N · CASCADE
weight integernull
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP
createdById integernullusers.id 1:N · SET NULL
createdByDSyncId textnullDSyncData.directoryId 1:N · SET NULL
updatedAt timestamp(3)null
updatedById integernullusers.id 1:N · SET NULL
updatedByDSyncId textnullDSyncData.directoryId 1:N · SET NULL

Indexes

  • UNIQUE (memberId, attributeOptionId) AttributeToUser_memberId_attributeOptionId_key

Referenced by

  • nothing

AttributeSyncRule

5 cols · no RLS · line 2091View in 3D

CreateTable

  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone updatedAt — updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKtextNOT NULL
integrationAttributeSyncId textNOT NULLIntegrationAttributeSync.id 1:1 · CASCADE
rule jsonbNOT NULL
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP
updatedAt timestamp(3)NOT NULL

Indexes

  • UNIQUE (integrationAttributeSyncId) AttributeSyncRule_integrationAttributeSyncId_key

Referenced by

  • nothing

AttributeSyncFieldMapping

7 cols · no RLS · line 2102View in 3D

CreateTable

  • Foreign key without index attributeId — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on Attribute must scan AttributeSyncFieldMapping to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to AttributeSyncFieldMapping maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often Attribute rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone updatedAt — updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKtextNOT NULL
integrationFieldName textNOT NULL
attributeId textNOT NULLAttribute.id 1:N · CASCADE
enabled booleanNOT NULL
integrationAttributeSyncId textNOT NULLIntegrationAttributeSync.id 1:N · CASCADE
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP
updatedAt timestamp(3)NOT NULL

Indexes

  • (integrationAttributeSyncId) AttributeSyncFieldMapping_integrationAttributeSyncId_idx
  • UNIQUE (integrationAttributeSyncId, attributeId) AttributeSyncFieldMapping_integrationAttributeSyncId_attrib_key

Referenced by

  • nothing

Watchlist 3 tables

Tables whose names start with Watchlist_.

Entity-relationship diagram: Watchlist, WatchlistAudit, WatchlistEventAuditWatchlistid PKuuidtypeWatchlistTypevaluetextdescriptiontextisGlobalbooleanorganizationIdintegeractionWatchlistActionsourceWatchlistSourcelastUpdatedAttimestamp(3)WatchlistAuditid PKuuidtypeWatchlistTypevaluetextdescriptiontextactionWatchlistActionchangedAttimestamp(3)changedByUserIdintegerwatchlistId FKuuidWatchlistEventAuditid PKuuidwatchlistIduuideventTypeIdintegeractionTakenWatchlistActiontimestamptimestamp(3)watchlistId

Relationships

Watchlist

9 cols · no RLS · line 1665View in 3D

CreateTable

  • TIMESTAMP without time zone lastUpdatedAt — lastUpdatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKuuidNOT NULL
type WatchlistTypeNOT NULL
value textNOT NULL
description textnull
isGlobal booleanNOT NULLFALSE
organizationId integernull
action WatchlistActionNOT NULL'REPORT'
source WatchlistSourceNOT NULL'MANUAL'
lastUpdatedAt timestamp(3)NOT NULLCURRENT_TIMESTAMP

Indexes

  • (isGlobal, action, organizationId, type, value) Watchlist_isGlobal_action_organizationId_type_value_idx
  • UNIQUE (type, value, organizationId) Watchlist_type_value_organizationId_key

Referenced by

WatchlistAudit

8 cols · no RLS · line 1680View in 3D

CreateTable

  • Nullable foreign key watchlistId — WatchlistAudit.watchlistId may be NULL, so the relationship to Watchlist is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • TIMESTAMP without time zone changedAt — changedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKuuidNOT NULL
type WatchlistTypeNOT NULL
value textNOT NULL
description textnull
action WatchlistActionNOT NULL'REPORT'
changedAt timestamp(3)NOT NULLCURRENT_TIMESTAMP
changedByUserId integernull
watchlistId uuidnullWatchlist.id 1:N · SET NULL

Indexes

  • (watchlistId, changedAt) WatchlistAudit_watchlistId_changedAt_idx

Referenced by

  • nothing

WatchlistEventAudit

5 cols · no RLS · line 1694View in 3D

CreateTable

  • TIMESTAMP without time zone timestamp — timestamp stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • Isolated table — WatchlistEventAudit references nothing and nothing references it. Either it is a staging/log table (fine, say so), or it is dead, or it is the seed of a second data model growing beside the first.
ColumnTypeNullDefaultReferencesNotes
id PKuuidNOT NULL
watchlistId uuidNOT NULL
eventTypeId integerNOT NULL
actionTaken WatchlistActionNOT NULL
timestamp timestamp(3)NOT NULLCURRENT_TIMESTAMP

Indexes

  • none

Referenced by

  • nothing

Role 2 tables

Tables whose names start with Role_.

Entity-relationship diagram: Role, RolePermissionRoleid PKtextnametextcolortextdescriptiontextteamId FKintegercreatedAttimestamp(3)updatedAttimestamp(3)typeRoleTypeRolePermissionid PKtextroleId FKtextresourcetextactiontextcreatedAttimestamp(3)TeamteamIdroleId

Relationships

Role

8 cols · no RLS · line 1853View in 3D

CreateTable

  • Nullable foreign key teamId — Role.teamId may be NULL, so the relationship to Team is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone updatedAt — updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKtextNOT NULL
name textNOT NULL
color textnull
description textnull
teamId integernullTeam.id 1:N · CASCADE
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP
updatedAt timestamp(3)NOT NULL
type RoleTypeNOT NULL'CUSTOM'

Indexes

  • (teamId) Role_teamId_idx
  • UNIQUE (name, teamId) Role_name_teamId_key

Referenced by

RolePermission

5 cols · no RLS · line 1867View in 3D

CreateTable

  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKtextNOT NULL
roleId textNOT NULLRole.id 1:N · CASCADE
resource textNOT NULL
action textNOT NULL
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP

Indexes

  • (roleId) RolePermission_roleId_idx
  • (action) RolePermission_action_idx
  • UNIQUE (roleId, resource, action) RolePermission_roleId_resource_action_key

Referenced by

  • nothing

misc 51 tables

Tables whose name prefix is theirs alone.

Entity-relationship diagram: VideoCallGuest, Credential, DestinationCalendar, TravelSchedule, users, NotificationsSubscriptions, Profile, Membership, VerificationToken, InstantMeetingToken, Attendee, Tracking, Schedule, Availability, ResetPasswordRequest, ReminderMail, Payment, Impersonations, ApiKey, RateLimit, HashedLink, Account, Session, Feedback, AIPhoneCallConfiguration, Deployment, Feature, OAuthClient, TempOrgRedirect, avatars, HolidayCache, RefreshToken, DSyncData, DSyncTeamGroupMapping, SecondaryEmail, Task, ManagedOrganization, AssignmentReason, DelegationCredential, DomainWideDelegation, WorkspacePlatform, WrongAssignmentReport, InternalNotePreset, FilterSegment, AuditActor, Agent, SeatChangeLog, MonthlyProration, IntegrationAttributeSync, PendingRoutingTrace, _user_eventtypeVideoCallGuestid PKtextbookingUidtextemailtextnametextjoinedAttimestamp(3)createdAttimestamp(3)updatedAttimestamp(3)Credentialid PKserialtypetextkeyjsonbencryptedKeytextuserId FKintegerteamId FKintegerappId FKtextsubscriptionIdtextpaymentStatustextbillingCycleStartintegerinvalidbooleandelegationCredentialId FKtextDestinationCalendarid PKserialintegrationtextexternalIdtextprimaryEmailtextuserId FKintegereventTypeId FKintegercredentialId FKintegercreatedAttimestamp(3)updatedAttimestamp(3)delegationCredentialId FKtextdomainWideDelegationCredentialId FKtextcustomCalendarReminderintegerTravelScheduleid PKserialuserId FKintegertimeZonetextstartDatetimestamp(3)endDatetimestamp(3)prevTimeZonetextusersid PKserialuuiduuidusernametextnametextemailtextemailVerifiedtimestamp(3)biotextavatarUrltexttimeZonetextweekStarttextbufferTimeintegerhideBrandingbooleanthemetextappThemetextcreatedtimestamp(3)trialEndsAttimestamp(3)lastActiveAttimestamp(3)defaultScheduleIdintegercompletedOnboardingbooleanlocaletexttimeFormatintegertwoFactorSecrettexttwoFactorEnabledbooleanbackupCodestextidentityProviderIdentityProvideridentityProviderIdtextinvitedTointegerbrandColortextdarkBrandColortextallowDynamicBookingbooleanallowSEOIndexingbooleanreceiveMonthlyDigestEmailbooleanrequiresBookerEmailVerificationbooleanmetadatajsonbverifiedbooleanroleUserPermissionRoledisableImpersonationbooleanorganizationId FKintegerlockedbooleanmovedToProfileId FKintegerisPlatformManagedbooleansmsLockStateSMSLockStatesmsLockReviewedByAdminbooleanreferralLinkIdtextcreationSourceCreationSourcewhitelistWorkflowsbooleanautoOptInFeaturesbooleanNotificationsSubscriptionsid PKserialuserId FKintegersubscriptiontextProfileid PKserialuidtextuserId FKintegerorganizationId FKintegerusernametextcreatedAttimestamp(3)updatedAttimestamp(3)Membershipid PKserialteamId FKintegeruserId FKintegeracceptedbooleanroleMembershipRolecustomRoleId FKtextdisableImpersonationbooleancreatedAttimestamp(3)updatedAttimestamp(3)VerificationTokenid PKserialidentifiertexttokentextexpirestimestamp(3)expiresInDaysintegercreatedAttimestamp(3)updatedAttimestamp(3)teamId FKintegersecondaryEmailId FKintegerInstantMeetingTokenid PKserialtokentextexpirestimestamp(3)teamId FKintegerbookingId FKintegercreatedAttimestamp(3)updatedAttimestamp(3)Attendeeid PKserialemailtextnametexttimeZonetextphoneNumbertextlocaletextbookingId FKintegernoShowbooleanTrackingid PKserialbookingId FKintegerutm_sourcetextutm_mediumtextutm_campaigntextutm_termtextutm_contenttextScheduleid PKserialuserId FKintegernametexttimeZonetextAvailabilityid PKserialuserId FKintegereventTypeId FKintegerdaysinteger[]startTimetimeendTimetimedatedatescheduleId FKintegerResetPasswordRequestid PKtextcreatedAttimestamp(3)updatedAttimestamp(3)emailtextexpirestimestamp(3)ReminderMailid PKserialreferenceIdintegerreminderTypeReminderTypeelapsedMinutesintegercreatedAttimestamp(3)Paymentid PKserialuidtextappId FKtextbookingId FKintegeramountintegerfeeintegercurrencytextsuccessbooleanrefundedbooleandatajsonbexternalIdtextpaymentOptionPaymentOptionImpersonationsid PKserialcreatedAttimestamp(3)impersonatedUserId FKintegerimpersonatedById FKintegerApiKeyid PKtextuserId FKintegerteamId FKintegernotetextcreatedAttimestamp(3)expiresAttimestamp(3)lastUsedAttimestamp(3)hashedKeytextappId FKtextRateLimitid PKtextnametextapiKeyId FKtextttlintegerlimitintegerblockDurationintegercreatedAttimestamp(3)updatedAttimestamp(3)HashedLinkid PKseriallinktexteventTypeId FKintegerexpiresAttimestamp(3)maxUsageCountintegerusageCountintegerAccountid PKtextuserId FKintegertypetextprovidertextproviderAccountIdtextproviderEmailtextrefresh_tokentextaccess_tokentextexpires_atintegertoken_typetextscopetextid_tokentextsession_statetextSessionid PKtextsessionTokentextuserId FKintegerexpirestimestamp(3)Feedbackid PKserialdatetimestamp(3)userId FKintegerratingtextcommenttextAIPhoneCallConfigurationid PKserialeventTypeId FKintegertemplateTypetextschedulerNametextgeneralPrompttextyourPhoneNumbertextnumberToCalltextguestNametextguestEmailtextguestCompanytextenabledbooleanbeginMessagetextllmIdtextDeploymentid PKintegerlogotextthemejsonblicenseKeytextsignatureTokenEncryptedtextagreedLicenseAttimestamp(3)Featureslug PKtextenabledbooleandescriptiontexttypeFeatureTypestalebooleanlastUsedAttimestamp(3)createdAttimestamp(3)updatedAttimestamp(3)updatedByintegerOAuthClientclientId PKtextredirectUritextclientSecrettextclientTypeOAuthClientTypenametextpurposetextlogotextwebsiteUrltextrejectionReasontextisTrustedbooleanstatusOAuthClientStatususerId FKintegercreatedAttimestamp(3)TempOrgRedirectid PKserialfromtextfromOrgIdintegertypeRedirectTypetoUrltextenabledbooleancreatedAttimestamp(3)updatedAttimestamp(3)avatarsteamIdintegeruserIdintegerdatatextobjectKeytextisBannerbooleanHolidayCacheid PKtextcountryCodetextcalendarIdtexteventIdtextnametextdatetimestamp(3)yearintegercreatedAttimestamp(3)updatedAttimestamp(3)RefreshTokenid PKserialsecrettextcreatedAttimestamp(3)expiresAttimestamp(3)platformOAuthClientId FKtextuserId FKintegerDSyncDataid PKserialdirectoryIdtexttenanttextorganizationId FKintegerDSyncTeamGroupMappingid PKserialorganizationIdintegerteamId FKintegerdirectoryId FKtextgroupNametextSecondaryEmailid PKserialuserId FKintegeremailtextemailVerifiedtimestamp(3)Taskid PKtextcreatedAttimestamp(3)updatedAttimestamp(3)scheduledAttimestamp(3)succeededAttimestamp(3)typetextpayloadtextattemptsintegermaxAttemptsintegerlastErrortextlastFailedAttemptAttimestamp(3)referenceUidtextManagedOrganizationmanagedOrganizationId FKintegermanagerOrganizationId FKintegercreatedAttimestamp(3)AssignmentReasonid PKserialcreatedAttimestamp(3)bookingId FKintegerreasonEnumAssignmentReasonEnumreasonStringtextDelegationCredentialid PKtextworkspacePlatformId FKintegerserviceAccountKeyjsonbenabledbooleanlastEnabledAttimestamp(3)lastDisabledAttimestamp(3)organizationId FKintegerdomaintextcreatedAttimestamp(3)updatedAttimestamp(3)DomainWideDelegationid PKtextworkspacePlatformId FKintegerserviceAccountKeyjsonbenabledbooleanorganizationId FKintegerdomaintextcreatedAttimestamp(3)updatedAttimestamp(3)WorkspacePlatformid PKserialslugtextnametextdescriptiontextdefaultServiceAccountKeyjsonbcreatedAttimestamp(3)updatedAttimestamp(3)enabledbooleanWrongAssignmentReportid PKuuidbookingUid FKtextreportedById FKintegercorrectAssigneetextadditionalNotestextteamId FKintegerroutingFormId FKtextstatusWrongAssignmentReportStatusreviewedById FKintegerreviewedAttimestamp(3)createdAttimestamp(3)updatedAttimestamp(3)InternalNotePresetid PKserialnametextcancellationReasontextteamId FKintegercreatedAttimestamp(3)FilterSegmentid PKserialnametexttableIdentifiertextscopeFilterSegmentScopeactiveFiltersjsonbsortingjsonbcolumnVisibilityjsonbcolumnSizingjsonbperPageintegersearchTermtextcreatedAttimestamp(3)updatedAttimestamp(3)userId FKintegerteamId FKintegerAuditActorid PKuuidtypeAuditActorTypeuserUuiduuidattendeeIdintegercredentialIdintegeremailtextphonetextnametextcreatedAttimestamp(3)Agentid PKtextnametextuserId FKintegerteamId FKintegerproviderAgentIdtextinboundEventTypeIdintegeroutboundEventTypeIdintegerenabledbooleancreatedAttimestamp(3)updatedAttimestamp(3)SeatChangeLogid PKtextteamId FKintegerchangeTypeSeatChangeTypeseatCountintegeruserIdintegertriggeredByintegerchangeDatetimestamp(3)monthKeytextoperationIdtextprocessedInProrationId FKtextmetadatajsonbcreatedAttimestamp(3)teamBillingId FKtextorganizationBillingId FKtextMonthlyProrationid PKtextteamId FKintegermonthKeytextperiodStarttimestamp(3)periodEndtimestamp(3)seatsAtStartintegerseatsAddedintegerseatsRemovedintegernetSeatIncreaseintegerseatsAtEndintegersubscriptionIdtextsubscriptionItemIdtextcustomerIdtextsubscriptionStarttimestamp(3)subscriptionEndtimestamp(3)remainingDaysintegerpricePerSeatintegerproratedAmountintegerinvoiceItemIdtextinvoiceIdtextinvoiceUrltextstatusProrationStatuschargedAttimestamp(3)failedAttimestamp(3)failureReasontextretryCountintegermetadatajsonbcreatedAttimestamp(3)updatedAttimestamp(3)teamBillingId FKtextorganizationBillingId FKtextIntegrationAttributeSyncid PKtextorganizationId FKintegernametextintegrationtextcredentialId FKintegerenabledbooleancreatedAttimestamp(3)updatedAttimestamp(3)PendingRoutingTraceid PKtextcreatedAttimestamp(3)tracejsonbformResponseId FKintegerqueuedFormResponseId FKtext_user_eventtypeA PKintegerB PKintegerAppApp_RoutingForms_FormApp_RoutingForms_FormResponseApp_RoutingForms_QueuedFormResponseBookingEventTypeOrganizationBillingOrganizationSettingsPlatformOAuthClientRoleTeamTeamBillinguserIdeventTypeIdbookingIdbookingIdorganizationIdworkspacePlatformIdorganizationIdworkspacePlatformIdorganizationIdeventTypeIdteamIdbookingIdteamIdmanagedOrganizationIdmanagerOrganizationIdteamIdteamBillingIdorganizationBillingIdappIdbookingIdformResponseIdqueuedFormResponseIdorganizationIdbookingIdteamIddirectoryIdteamIdprocessedInProrationIdteamBillingIdorganizationBillingIdorganizationIdmovedToProfileIduserIduserIdteamIduserIdteamIdappIduserIdteamIdappIddelegationCredentialIduserIduserIdteamIdimpersonatedUserIdimpersonatedByIdcustomRoleIdteamIduserIduserIduserIduserIdplatformOAuthClientIduserIduserIduserIduserIdbookingUidreportedByIdteamIdroutingFormIdreviewedByIdABuserIdeventTypeIdscheduleIduserIdeventTypeIdcredentialIddelegationCredentialIddomainWideDelegationCredentialIdorganizationIdcredentialIdapiKeyIdteamIdsecondaryEmailId

Relationships

VideoCallGuest

7 cols · no RLS · line 227View in 3D

CreateTable

  • TIMESTAMP without time zone joinedAt — joinedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone updatedAt — updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • Isolated table — VideoCallGuest references nothing and nothing references it. Either it is a staging/log table (fine, say so), or it is dead, or it is the seed of a second data model growing beside the first.
ColumnTypeNullDefaultReferencesNotes
id PKtextNOT NULL
bookingUid textNOT NULL
email textNOT NULL
name textNOT NULL
joinedAt timestamp(3)null
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP
updatedAt timestamp(3)NOT NULL

Indexes

  • (bookingUid) VideoCallGuest_bookingUid_idx
  • (email) VideoCallGuest_email_idx
  • UNIQUE (bookingUid, email) VideoCallGuest_bookingUid_email_key

Referenced by

  • nothing

Credential

12 cols · no RLS · line 332View in 3D

CreateTable

  • Foreign key without index delegationCredentialId — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on DelegationCredential must scan Credential to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to Credential maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often DelegationCredential rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • Nullable foreign key userId — Credential.userId may be NULL, so the relationship to users is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key teamId — Credential.teamId may be NULL, so the relationship to Team is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key appId — Credential.appId may be NULL, so the relationship to App is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key delegationCredentialId — Credential.delegationCredentialId may be NULL, so the relationship to DelegationCredential is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Enum-like column with no CHECK type — type is a short string that clearly takes a fixed set of values, but the database accepts anything. Typos become new states, and nobody can list the legal values without reading application code.
  • Either/or foreign keys without a CHECK userId, teamId, appId, delegationCredentialId — Credential has several nullable FKs (userId, teamId, appId, delegationCredentialId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.
ColumnTypeNullDefaultReferencesNotes
id PKserialNOT NULL
type textNOT NULL
key jsonbNOT NULL
encryptedKey textnull
userId integernullusers.id 1:N · CASCADE
teamId integernullTeam.id 1:N · CASCADE
appId textnullApp.slug 1:N · CASCADE
subscriptionId textnull
paymentStatus textnull
billingCycleStart integernull
invalid booleannullFALSE
delegationCredentialId textnullDelegationCredential.id 1:N · CASCADE

Indexes

  • (appId) Credential_appId_idx
  • (subscriptionId) Credential_subscriptionId_idx
  • (invalid) Credential_invalid_idx
  • (userId, delegationCredentialId) Credential_userId_delegationCredentialId_idx
  • (teamId) Credential_teamId_idx

Referenced by

DestinationCalendar

12 cols · no RLS · line 350View in 3D

CreateTable

  • Foreign key without index delegationCredentialId — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on DelegationCredential must scan DestinationCalendar to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to DestinationCalendar maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often DelegationCredential rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • Foreign key without index domainWideDelegationCredentialId — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on DomainWideDelegation must scan DestinationCalendar to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to DestinationCalendar maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often DomainWideDelegation rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • Nullable foreign key userId — DestinationCalendar.userId may be NULL, so the relationship to users is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key eventTypeId — DestinationCalendar.eventTypeId may be NULL, so the relationship to EventType is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key credentialId — DestinationCalendar.credentialId may be NULL, so the relationship to Credential is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key delegationCredentialId — DestinationCalendar.delegationCredentialId may be NULL, so the relationship to DelegationCredential is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key domainWideDelegationCredentialId — DestinationCalendar.domainWideDelegationCredentialId may be NULL, so the relationship to DomainWideDelegation is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Either/or foreign keys without a CHECK userId, eventTypeId, credentialId, delegationCredentialId, domainWideDelegationCredentialId — DestinationCalendar has several nullable FKs (userId, eventTypeId, credentialId, delegationCredentialId, domainWideDelegationCredentialId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.
  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone updatedAt — updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKserialNOT NULL
integration textNOT NULL
externalId textNOT NULL
primaryEmail textnull
userId integernullusers.id 1:1 · CASCADE
eventTypeId integernullEventType.id 1:1 · CASCADE
credentialId integernullCredential.id 1:N · CASCADE
createdAt timestamp(3)nullCURRENT_TIMESTAMP
updatedAt timestamp(3)null
delegationCredentialId textnullDelegationCredential.id 1:N · CASCADE
domainWideDelegationCredentialId textnullDomainWideDelegation.id 1:N · CASCADE
customCalendarReminder integernull

Indexes

  • UNIQUE (userId) DestinationCalendar_userId_key
  • UNIQUE (eventTypeId) DestinationCalendar_eventTypeId_key
  • (userId) DestinationCalendar_userId_idx
  • (eventTypeId) DestinationCalendar_eventTypeId_idx
  • (credentialId) DestinationCalendar_credentialId_idx

Referenced by

  • Booking (destinationCalendarId, 1:N)

TravelSchedule

6 cols · no RLS · line 374View in 3D

CreateTable

  • Foreign key without index userId — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on users must scan TravelSchedule to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to TravelSchedule maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often users rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • TIMESTAMP without time zone startDate — startDate stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone endDate — endDate stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKserialNOT NULL
userId integerNOT NULLusers.id 1:N · CASCADE
timeZone textNOT NULL
startDate timestamp(3)NOT NULL
endDate timestamp(3)null
prevTimeZone textnull

Indexes

  • (startDate) TravelSchedule_startDate_idx
  • (endDate) TravelSchedule_endDate_idx

Referenced by

  • nothing

users

47 cols · no RLS · line 386View in 3D

CreateTable

  • Foreign key without index organizationId — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on Team must scan users to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to users maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often Team rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • Nullable foreign key organizationId — users.organizationId may be NULL, so the relationship to Team is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key movedToProfileId — users.movedToProfileId may be NULL, so the relationship to Profile is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Either/or foreign keys without a CHECK organizationId, movedToProfileId — users has several nullable FKs (organizationId, movedToProfileId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.
  • TIMESTAMP without time zone emailVerified — emailVerified stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone created — created stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone trialEndsAt — trialEndsAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone lastActiveAt — lastActiveAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • Foreign-key cycle — users → Profile → users. Rows must be inserted with a deferred constraint or a NULL-then-update dance; backups/restores and truncation have no valid order; ON DELETE CASCADE can loop.
  • Hub table: referenced by 53 tables — Any change to its key, its delete semantics, or its partitioning touches every dependent. Migrations on hub tables need the longest lock windows and the most careful rollout.
  • Wide table (47 columns) — Tables this wide usually hide several entities (or a JSON column that wants to be one). Every row update rewrites the whole tuple; TOAST kicks in; indexes bloat.
ColumnTypeNullDefaultReferencesNotes
id PKserialNOT NULL
uuid uuidNOT NULL
username textnull
name textnull
email textNOT NULL
emailVerified timestamp(3)null
bio textnull
avatarUrl textnull
timeZone textNOT NULL'Europe/London'
weekStart textNOT NULL'Sunday'
bufferTime integerNOT NULL0
hideBranding booleanNOT NULLFALSE
theme textnull
appTheme textnull
created timestamp(3)NOT NULLCURRENT_TIMESTAMP
trialEndsAt timestamp(3)null
lastActiveAt timestamp(3)null
defaultScheduleId integernull
completedOnboarding booleanNOT NULLFALSE
locale textnull
timeFormat integernull12
twoFactorSecret textnull
twoFactorEnabled booleanNOT NULLFALSE
backupCodes textnull
identityProvider IdentityProviderNOT NULL'CAL'
identityProviderId textnull
invitedTo integernull
brandColor textnull
darkBrandColor textnull
allowDynamicBooking booleannullTRUE
allowSEOIndexing booleannullTRUE
receiveMonthlyDigestEmail booleannullTRUE
requiresBookerEmailVerification booleannullFALSE
metadata jsonbnull
verified booleannullFALSE
role UserPermissionRoleNOT NULL'USER'
disableImpersonation booleanNOT NULLFALSE
organizationId integernullTeam.id 1:N · SET NULL
locked booleanNOT NULLFALSE
movedToProfileId integernullProfile.id 1:1 · SET NULL
isPlatformManaged booleanNOT NULLFALSE
smsLockState SMSLockStateNOT NULL'UNLOCKED'
smsLockReviewedByAdmin booleanNOT NULLFALSE
referralLinkId textnull
creationSource CreationSourcenull
whitelistWorkflows booleanNOT NULLFALSE
autoOptInFeatures booleanNOT NULLFALSE

Indexes

  • UNIQUE (uuid) users_uuid_key
  • (username) users_username_idx
  • (emailVerified) users_emailVerified_idx
  • (identityProvider) users_identityProvider_idx
  • (identityProviderId) users_identityProviderId_idx
  • UNIQUE (email) users_email_key
  • UNIQUE (email, username) users_email_username_key
  • UNIQUE (username, organizationId) users_username_organizationId_key
  • UNIQUE (movedToProfileId) users_movedToProfileId_key

Referenced by

NotificationsSubscriptions

3 cols · no RLS · line 439View in 3D

CreateTable

ColumnTypeNullDefaultReferencesNotes
id PKserialNOT NULL
userId integerNOT NULLusers.id 1:N · CASCADE
subscription textNOT NULL

Indexes

  • (userId, subscription) NotificationsSubscriptions_userId_subscription_idx

Referenced by

  • nothing

Profile

7 cols · no RLS · line 448View in 3D

CreateTable

  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone updatedAt — updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKserialNOT NULL
uid textNOT NULL
userId integerNOT NULLusers.id 1:N · CASCADE
organizationId integerNOT NULLTeam.id 1:N · CASCADE
username textNOT NULL
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP
updatedAt timestamp(3)NOT NULL

Indexes

  • (uid) Profile_uid_idx
  • (userId) Profile_userId_idx
  • (organizationId) Profile_organizationId_idx
  • UNIQUE (userId, organizationId) Profile_userId_organizationId_key
  • UNIQUE (username, organizationId) Profile_username_organizationId_key

Referenced by

Membership

9 cols · no RLS · line 570View in 3D

CreateTable

  • Nullable foreign key customRoleId — Membership.customRoleId may be NULL, so the relationship to Role is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone updatedAt — updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKserialNOT NULL
teamId integerNOT NULLTeam.id 1:N · CASCADE
userId integerNOT NULLusers.id 1:N · CASCADE
accepted booleanNOT NULLFALSE
role MembershipRoleNOT NULL
customRoleId textnullRole.id 1:N · SET NULL
disableImpersonation booleanNOT NULLFALSE
createdAt timestamp(3)nullCURRENT_TIMESTAMP
updatedAt timestamp(3)null

Indexes

  • (teamId) Membership_teamId_idx
  • (userId) Membership_userId_idx
  • (accepted) Membership_accepted_idx
  • (role) Membership_role_idx
  • (customRoleId) Membership_customRoleId_idx
  • UNIQUE (userId, teamId) Membership_userId_teamId_key

Referenced by

VerificationToken

9 cols · no RLS · line 585View in 3D

CreateTable

  • Nullable foreign key teamId — VerificationToken.teamId may be NULL, so the relationship to Team is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key secondaryEmailId — VerificationToken.secondaryEmailId may be NULL, so the relationship to SecondaryEmail is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Either/or foreign keys without a CHECK teamId, secondaryEmailId — VerificationToken has several nullable FKs (teamId, secondaryEmailId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.
  • TIMESTAMP without time zone expires — expires stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone updatedAt — updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKserialNOT NULL
identifier textNOT NULL
token textNOT NULL
expires timestamp(3)NOT NULL
expiresInDays integernull
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP
updatedAt timestamp(3)NOT NULL
teamId integernullTeam.id 1:N · SET NULL
secondaryEmailId integernullSecondaryEmail.id 1:N · SET NULL

Indexes

  • UNIQUE (token) VerificationToken_token_key
  • (token) VerificationToken_token_idx
  • (teamId) VerificationToken_teamId_idx
  • (secondaryEmailId) VerificationToken_secondaryEmailId_idx
  • UNIQUE (identifier, token) VerificationToken_identifier_token_key

Referenced by

  • nothing

InstantMeetingToken

7 cols · no RLS · line 600View in 3D

CreateTable

  • Foreign key without index teamId — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on Team must scan InstantMeetingToken to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to InstantMeetingToken maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often Team rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • Nullable foreign key bookingId — InstantMeetingToken.bookingId may be NULL, so the relationship to Booking is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • TIMESTAMP without time zone expires — expires stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone updatedAt — updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKserialNOT NULL
token textNOT NULL
expires timestamp(3)NOT NULL
teamId integerNOT NULLTeam.id 1:N · CASCADE
bookingId integernullBooking.id 1:1 · CASCADE
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP
updatedAt timestamp(3)NOT NULL

Indexes

  • UNIQUE (token) InstantMeetingToken_token_key
  • UNIQUE (bookingId) InstantMeetingToken_bookingId_key
  • (token) InstantMeetingToken_token_idx

Referenced by

  • nothing

Attendee

8 cols · no RLS · line 632View in 3D

CreateTable

  • Nullable foreign key bookingId — Attendee.bookingId may be NULL, so the relationship to Booking is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
ColumnTypeNullDefaultReferencesNotes
id PKserialNOT NULL
email textNOT NULL
name textNOT NULL
timeZone textNOT NULL
phoneNumber textnull
locale textnull'en'
bookingId integernullBooking.id 1:N · CASCADE
noShow booleannullFALSE

Indexes

  • (email) Attendee_email_idx
  • (bookingId) Attendee_bookingId_idx
  • (email, bookingId) Attendee_email_bookingId_idx

Referenced by

Tracking

7 cols · no RLS · line 692View in 3D

CreateTable

ColumnTypeNullDefaultReferencesNotes
id PKserialNOT NULL
bookingId integerNOT NULLBooking.id 1:1 · CASCADE
utm_source textnull
utm_medium textnull
utm_campaign textnull
utm_term textnull
utm_content textnull

Indexes

  • UNIQUE (bookingId) Tracking_bookingId_key

Referenced by

  • nothing

Schedule

4 cols · no RLS · line 705View in 3D

CreateTable

ColumnTypeNullDefaultReferencesNotes
id PKserialNOT NULL
userId integerNOT NULLusers.id 1:N · CASCADE
name textNOT NULL
timeZone textnull

Indexes

  • (userId) Schedule_userId_idx

Referenced by

Availability

8 cols · no RLS · line 715View in 3D

CreateTable

  • Nullable foreign key userId — Availability.userId may be NULL, so the relationship to users is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key eventTypeId — Availability.eventTypeId may be NULL, so the relationship to EventType is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key scheduleId — Availability.scheduleId may be NULL, so the relationship to Schedule is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Either/or foreign keys without a CHECK userId, eventTypeId, scheduleId — Availability has several nullable FKs (userId, eventTypeId, scheduleId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.
ColumnTypeNullDefaultReferencesNotes
id PKserialNOT NULL
userId integernullusers.id 1:N · CASCADE
eventTypeId integernullEventType.id 1:N · SET NULL
days integer[]null
startTime timeNOT NULL
endTime timeNOT NULL
date datenull
scheduleId integernullSchedule.id 1:N · SET NULL

Indexes

  • (userId) Availability_userId_idx
  • (eventTypeId) Availability_eventTypeId_idx
  • (scheduleId) Availability_scheduleId_idx

Referenced by

  • nothing

ResetPasswordRequest

5 cols · no RLS · line 780View in 3D

CreateTable

  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone updatedAt — updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone expires — expires stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • Isolated table — ResetPasswordRequest references nothing and nothing references it. Either it is a staging/log table (fine, say so), or it is dead, or it is the seed of a second data model growing beside the first.
ColumnTypeNullDefaultReferencesNotes
id PKtextNOT NULL
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP
updatedAt timestamp(3)NOT NULL
email textNOT NULL
expires timestamp(3)NOT NULL

Indexes

  • none

Referenced by

  • nothing

ReminderMail

5 cols · no RLS · line 791View in 3D

CreateTable

  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • Isolated table — ReminderMail references nothing and nothing references it. Either it is a staging/log table (fine, say so), or it is dead, or it is the seed of a second data model growing beside the first.
ColumnTypeNullDefaultReferencesNotes
id PKserialNOT NULL
referenceId integerNOT NULL
reminderType ReminderTypeNOT NULL
elapsedMinutes integerNOT NULL
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP

Indexes

  • (referenceId) ReminderMail_referenceId_idx
  • (reminderType) ReminderMail_reminderType_idx

Referenced by

  • nothing

Payment

12 cols · no RLS · line 802View in 3D

CreateTable

  • Foreign key without index appId — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on App must scan Payment to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to Payment maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often App rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • Nullable foreign key appId — Payment.appId may be NULL, so the relationship to App is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
ColumnTypeNullDefaultReferencesNotes
id PKserialNOT NULL
uid textNOT NULL
appId textnullApp.slug 1:N · CASCADE
bookingId integerNOT NULLBooking.id 1:N · CASCADE
amount integerNOT NULL
fee integerNOT NULL
currency textNOT NULL
success booleanNOT NULL
refunded booleanNOT NULL
data jsonbNOT NULL
externalId textNOT NULL
paymentOption PaymentOptionnull'ON_BOOKING'

Indexes

  • UNIQUE (uid) Payment_uid_key
  • UNIQUE (externalId) Payment_externalId_key
  • (bookingId) Payment_bookingId_idx
  • (externalId) Payment_externalId_idx

Referenced by

  • nothing

Impersonations

4 cols · no RLS · line 842View in 3D

CreateTable

  • Junction table allows duplicate links impersonatedById, impersonatedUserId — Impersonations looks like a many-to-many link table but has no unique constraint across (impersonatedById, impersonatedUserId). The same pair can be inserted twice; every join through it will double-count.
  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKserialNOT NULL
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP
impersonatedUserId integerNOT NULLusers.id 1:N · CASCADE
impersonatedById integerNOT NULLusers.id 1:N · CASCADE

Indexes

  • (impersonatedUserId) Impersonations_impersonatedUserId_idx
  • (impersonatedById) Impersonations_impersonatedById_idx

Referenced by

  • nothing

ApiKey

9 cols · no RLS · line 852View in 3D

CreateTable

  • Foreign key without index teamId — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on Team must scan ApiKey to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to ApiKey maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often Team rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • Foreign key without index appId — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on App must scan ApiKey to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to ApiKey maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often App rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • Nullable foreign key teamId — ApiKey.teamId may be NULL, so the relationship to Team is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key appId — ApiKey.appId may be NULL, so the relationship to App is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Either/or foreign keys without a CHECK teamId, appId — ApiKey has several nullable FKs (teamId, appId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.
  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone expiresAt — expiresAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone lastUsedAt — lastUsedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKtextNOT NULL
userId integerNOT NULLusers.id 1:N · CASCADE
teamId integernullTeam.id 1:N · CASCADE
note textnull
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP
expiresAt timestamp(3)null
lastUsedAt timestamp(3)null
hashedKey textNOT NULL
appId textnullApp.slug 1:N · CASCADE

Indexes

  • UNIQUE (id) ApiKey_id_key
  • UNIQUE (hashedKey) ApiKey_hashedKey_key
  • (userId) ApiKey_userId_idx

Referenced by

RateLimit

8 cols · no RLS · line 867View in 3D

CreateTable

  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone updatedAt — updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKtextNOT NULL
name textNOT NULL
apiKeyId textNOT NULLApiKey.id 1:N · CASCADE
ttl integerNOT NULL
limit integerNOT NULL
blockDuration integerNOT NULL
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP
updatedAt timestamp(3)NOT NULL

Indexes

  • (apiKeyId) RateLimit_apiKeyId_idx

Referenced by

  • nothing

Account

13 cols · no RLS · line 893View in 3D

CreateTable

  • Enum-like column with no CHECK type — type is a short string that clearly takes a fixed set of values, but the database accepts anything. Typos become new states, and nobody can list the legal values without reading application code.
  • Enum-like column with no CHECK token_type — token_type is a short string that clearly takes a fixed set of values, but the database accepts anything. Typos become new states, and nobody can list the legal values without reading application code.
  • Enum-like column with no CHECK scope — scope is a short string that clearly takes a fixed set of values, but the database accepts anything. Typos become new states, and nobody can list the legal values without reading application code.
  • Enum-like column with no CHECK session_state — session_state is a short string that clearly takes a fixed set of values, but the database accepts anything. Typos become new states, and nobody can list the legal values without reading application code.
ColumnTypeNullDefaultReferencesNotes
id PKtextNOT NULL
userId integerNOT NULLusers.id 1:N · CASCADE
type textNOT NULL
provider textNOT NULL
providerAccountId textNOT NULL
providerEmail textnull
refresh_token textnull
access_token textnull
expires_at integernull
token_type textnull
scope textnull
id_token textnull
session_state textnull

Indexes

  • (userId) Account_userId_idx
  • (type) Account_type_idx
  • UNIQUE (provider, providerAccountId) Account_provider_providerAccountId_key

Referenced by

  • nothing

Session

4 cols · no RLS · line 912View in 3D

CreateTable

  • TIMESTAMP without time zone expires — expires stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKtextNOT NULL
sessionToken textNOT NULL
userId integerNOT NULLusers.id 1:N · CASCADE
expires timestamp(3)NOT NULL

Indexes

  • UNIQUE (sessionToken) Session_sessionToken_key
  • (userId) Session_userId_idx

Referenced by

  • nothing

Feedback

5 cols · no RLS · line 1028View in 3D

CreateTable

  • TIMESTAMP without time zone date — date stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKserialNOT NULL
date timestamp(3)NOT NULLCURRENT_TIMESTAMP
userId integerNOT NULLusers.id 1:N · CASCADE
rating textNOT NULL
comment textnull

Indexes

  • (userId) Feedback_userId_idx
  • (rating) Feedback_rating_idx

Referenced by

  • nothing

AIPhoneCallConfiguration

13 cols · no RLS · line 1078View in 3D

CreateTable

ColumnTypeNullDefaultReferencesNotes
id PKserialNOT NULL
eventTypeId integerNOT NULLEventType.id 1:1 · CASCADE
templateType textNOT NULL'CUSTOM_TEMPLATE'
schedulerName textnull
generalPrompt textnull
yourPhoneNumber textNOT NULL
numberToCall textNOT NULL
guestName textnull
guestEmail textnull
guestCompany textnull
enabled booleanNOT NULLFALSE
beginMessage textnull
llmId textnull

Indexes

  • (eventTypeId) AIPhoneCallConfiguration_eventTypeId_idx
  • UNIQUE (eventTypeId) AIPhoneCallConfiguration_eventTypeId_key

Referenced by

  • nothing

Deployment

6 cols · no RLS · line 1124View in 3D

CreateTable

  • TIMESTAMP without time zone agreedLicenseAt — agreedLicenseAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • Isolated table — Deployment references nothing and nothing references it. Either it is a staging/log table (fine, say so), or it is dead, or it is the seed of a second data model growing beside the first.
ColumnTypeNullDefaultReferencesNotes
id PKintegerNOT NULL1
logo textnull
theme jsonbnull
licenseKey textnull
signatureTokenEncrypted textnull
agreedLicenseAt timestamp(3)null

Indexes

  • none

Referenced by

  • nothing

Feature

9 cols · no RLS · line 1202View in 3D

CreateTable

  • TIMESTAMP without time zone lastUsedAt — lastUsedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone updatedAt — updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
slug PKtextNOT NULL
enabled booleanNOT NULLFALSE
description textnull
type FeatureTypenull'RELEASE'
stale booleannullFALSE
lastUsedAt timestamp(3)null
createdAt timestamp(3)nullCURRENT_TIMESTAMP
updatedAt timestamp(3)nullCURRENT_TIMESTAMP
updatedBy integernull

Indexes

  • UNIQUE (slug) Feature_slug_key
  • (enabled) Feature_enabled_idx
  • (stale) Feature_stale_idx

Referenced by

OAuthClient

13 cols · no RLS · line 1255View in 3D

CreateTable

  • Nullable foreign key userId — OAuthClient.userId may be NULL, so the relationship to users is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
clientId PKtextNOT NULL
redirectUri textNOT NULL
clientSecret textnull
clientType OAuthClientTypeNOT NULL'confidential'
name textNOT NULL
purpose textnull
logo textnull
websiteUrl textnull
rejectionReason textnull
isTrusted booleanNOT NULLFALSE
status OAuthClientStatusNOT NULL'approved'
userId integernullusers.id 1:N · SET NULL
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP

Indexes

  • UNIQUE (clientId) OAuthClient_clientId_key
  • (userId) OAuthClient_userId_idx

Referenced by

TempOrgRedirect

8 cols · no RLS · line 1332View in 3D

CreateTable

  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone updatedAt — updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • Isolated table — TempOrgRedirect references nothing and nothing references it. Either it is a staging/log table (fine, say so), or it is dead, or it is the seed of a second data model growing beside the first.
ColumnTypeNullDefaultReferencesNotes
id PKserialNOT NULL
from textNOT NULL
fromOrgId integerNOT NULL
type RedirectTypeNOT NULL
toUrl textNOT NULL
enabled booleanNOT NULLTRUE
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP
updatedAt timestamp(3)NOT NULL

Indexes

  • UNIQUE (from, type, fromOrgId) TempOrgRedirect_from_type_fromOrgId_key

Referenced by

  • nothing

avatars

5 cols · no RLS · line 1346View in 3D

CreateTable

  • No primary key — Without a primary key rows are not individually addressable: no safe UPDATE/DELETE of one row, no logical replication, ORMs misbehave, and duplicates are legal.
  • Isolated table — avatars references nothing and nothing references it. Either it is a staging/log table (fine, say so), or it is dead, or it is the seed of a second data model growing beside the first.
ColumnTypeNullDefaultReferencesNotes
teamId integerNOT NULL0
userId integerNOT NULL0
data textNOT NULL
objectKey textNOT NULL
isBanner booleanNOT NULLFALSE

Indexes

  • UNIQUE (objectKey) avatars_objectKey_key
  • UNIQUE (teamId, userId, isBanner) avatars_teamId_userId_isBanner_key

Referenced by

  • nothing

HolidayCache

9 cols · no RLS · line 1395View in 3D

CreateTable

  • TIMESTAMP without time zone date — date stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone updatedAt — updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • Isolated table — HolidayCache references nothing and nothing references it. Either it is a staging/log table (fine, say so), or it is dead, or it is the seed of a second data model growing beside the first.
ColumnTypeNullDefaultReferencesNotes
id PKtextNOT NULL
countryCode textNOT NULL
calendarId textNOT NULL
eventId textNOT NULL
name textNOT NULL
date timestamp(3)NOT NULL
year integerNOT NULL
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP
updatedAt timestamp(3)NOT NULL

Indexes

  • (countryCode, year) HolidayCache_countryCode_year_idx
  • (countryCode, date) HolidayCache_countryCode_date_idx
  • UNIQUE (countryCode, eventId) HolidayCache_countryCode_eventId_key

Referenced by

  • nothing

RefreshToken

6 cols · no RLS · line 1452View in 3D

CreateTable

  • Foreign key without index userId — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on users must scan RefreshToken to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to RefreshToken maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often users rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • Foreign key without index platformOAuthClientId — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on PlatformOAuthClient must scan RefreshToken to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to RefreshToken maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often PlatformOAuthClient rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone expiresAt — expiresAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKserialNOT NULL
secret textNOT NULL
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP
expiresAt timestamp(3)NOT NULL
platformOAuthClientId textNOT NULLPlatformOAuthClient.id 1:N · CASCADE
userId integerNOT NULLusers.id 1:N · CASCADE

Indexes

  • UNIQUE (secret) RefreshToken_secret_key

Referenced by

  • nothing

DSyncData

4 cols · no RLS · line 1464View in 3D

CreateTable

  • Nullable foreign key organizationId — DSyncData.organizationId may be NULL, so the relationship to OrganizationSettings is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
ColumnTypeNullDefaultReferencesNotes
id PKserialNOT NULL
directoryId textNOT NULL
tenant textNOT NULL
organizationId integernullOrganizationSettings.organizationId 1:1 · CASCADE

Indexes

  • UNIQUE (directoryId) DSyncData_directoryId_key
  • UNIQUE (organizationId) DSyncData_organizationId_key

Referenced by

DSyncTeamGroupMapping

5 cols · no RLS · line 1474View in 3D

CreateTable

  • Foreign key without index directoryId — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on DSyncData must scan DSyncTeamGroupMapping to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to DSyncTeamGroupMapping maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often DSyncData rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
ColumnTypeNullDefaultReferencesNotes
id PKserialNOT NULL
organizationId integerNOT NULL
teamId integerNOT NULLTeam.id 1:N · CASCADE
directoryId textNOT NULLDSyncData.directoryId 1:N · CASCADE
groupName textNOT NULL

Indexes

  • UNIQUE (teamId, groupName) DSyncTeamGroupMapping_teamId_groupName_key

Referenced by

  • nothing

SecondaryEmail

4 cols · no RLS · line 1485View in 3D

CreateTable

  • TIMESTAMP without time zone emailVerified — emailVerified stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKserialNOT NULL
userId integerNOT NULLusers.id 1:N · CASCADE
email textNOT NULL
emailVerified timestamp(3)null

Indexes

  • (userId) SecondaryEmail_userId_idx
  • (email, emailVerified) SecondaryEmail_email_emailVerified_idx
  • UNIQUE (email) SecondaryEmail_email_key
  • UNIQUE (userId, email) SecondaryEmail_userId_email_key

Referenced by

Task

12 cols · no RLS · line 1495View in 3D

CreateTable

  • Enum-like column with no CHECK type — type is a short string that clearly takes a fixed set of values, but the database accepts anything. Typos become new states, and nobody can list the legal values without reading application code.
  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone updatedAt — updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone scheduledAt — scheduledAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone succeededAt — succeededAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone lastFailedAttemptAt — lastFailedAttemptAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • Isolated table — Task references nothing and nothing references it. Either it is a staging/log table (fine, say so), or it is dead, or it is the seed of a second data model growing beside the first.
ColumnTypeNullDefaultReferencesNotes
id PKtextNOT NULL
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP
updatedAt timestamp(3)NOT NULL
scheduledAt timestamp(3)NOT NULLCURRENT_TIMESTAMP
succeededAt timestamp(3)null
type textNOT NULL
payload textNOT NULL
attempts integerNOT NULL0
maxAttempts integerNOT NULL3
lastError textnull
lastFailedAttemptAt timestamp(3)null
referenceUid textnull

Indexes

  • UNIQUE (id) Task_id_key
  • (succeededAt) Task_succeededAt_idx
  • (scheduledAt, succeededAt) Task_scheduledAt_succeededAt_idx
  • UNIQUE (referenceUid, type) Task_referenceUid_type_key

Referenced by

  • nothing

ManagedOrganization

3 cols · no RLS · line 1513View in 3D

CreateTable

  • No primary key — Without a primary key rows are not individually addressable: no safe UPDATE/DELETE of one row, no logical replication, ORMs misbehave, and duplicates are legal.
  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
managedOrganizationId integerNOT NULLTeam.id 1:1 · CASCADE
managerOrganizationId integerNOT NULLTeam.id 1:N · CASCADE
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP

Indexes

  • UNIQUE (managedOrganizationId) ManagedOrganization_managedOrganizationId_key
  • (managerOrganizationId) ManagedOrganization_managerOrganizationId_idx
  • UNIQUE (managerOrganizationId, managedOrganizationId) ManagedOrganization_managerOrganizationId_managedOrganizati_key

Referenced by

  • nothing

AssignmentReason

5 cols · no RLS · line 1580View in 3D

CreateTable

  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKserialNOT NULL
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP
bookingId integerNOT NULLBooking.id 1:N · CASCADE
reasonEnum AssignmentReasonEnumNOT NULL
reasonString textNOT NULL

Indexes

  • UNIQUE (id) AssignmentReason_id_key
  • (bookingId) AssignmentReason_bookingId_idx

Referenced by

DelegationCredential

10 cols · no RLS · line 1591View in 3D

CreateTable

  • Foreign key without index workspacePlatformId — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on WorkspacePlatform must scan DelegationCredential to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to DelegationCredential maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often WorkspacePlatform rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • TIMESTAMP without time zone lastEnabledAt — lastEnabledAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone lastDisabledAt — lastDisabledAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone updatedAt — updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKtextNOT NULL
workspacePlatformId integerNOT NULLWorkspacePlatform.id 1:N · CASCADE
serviceAccountKey jsonbNOT NULL
enabled booleanNOT NULLFALSE
lastEnabledAt timestamp(3)null
lastDisabledAt timestamp(3)null
organizationId integerNOT NULLTeam.id 1:N · CASCADE
domain textNOT NULL
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP
updatedAt timestamp(3)NOT NULL

Indexes

  • (enabled) DelegationCredential_enabled_idx
  • UNIQUE (organizationId, domain) DelegationCredential_organizationId_domain_key

Referenced by

DomainWideDelegation

8 cols · no RLS · line 1607View in 3D

CreateTable

  • Foreign key without index workspacePlatformId — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on WorkspacePlatform must scan DomainWideDelegation to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to DomainWideDelegation maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often WorkspacePlatform rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone updatedAt — updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKtextNOT NULL
workspacePlatformId integerNOT NULLWorkspacePlatform.id 1:N · CASCADE
serviceAccountKey jsonbNOT NULL
enabled booleanNOT NULLFALSE
organizationId integerNOT NULLTeam.id 1:N · CASCADE
domain textNOT NULL
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP
updatedAt timestamp(3)NOT NULL

Indexes

  • UNIQUE (organizationId, domain) DomainWideDelegation_organizationId_domain_key

Referenced by

WorkspacePlatform

8 cols · no RLS · line 1621View in 3D

CreateTable

  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone updatedAt — updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKserialNOT NULL
slug textNOT NULL
name textNOT NULL
description textNOT NULL
defaultServiceAccountKey jsonbNOT NULL
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP
updatedAt timestamp(3)NOT NULL
enabled booleanNOT NULLFALSE

Indexes

  • UNIQUE (slug) WorkspacePlatform_slug_key

Referenced by

WrongAssignmentReport

12 cols · no RLS · line 1725View in 3D

CreateTable

  • Nullable foreign key reportedById — WrongAssignmentReport.reportedById may be NULL, so the relationship to users is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key teamId — WrongAssignmentReport.teamId may be NULL, so the relationship to Team is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key routingFormId — WrongAssignmentReport.routingFormId may be NULL, so the relationship to App_RoutingForms_Form is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key reviewedById — WrongAssignmentReport.reviewedById may be NULL, so the relationship to users is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Either/or foreign keys without a CHECK reportedById, teamId, routingFormId, reviewedById — WrongAssignmentReport has several nullable FKs (reportedById, teamId, routingFormId, reviewedById) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.
  • TIMESTAMP without time zone reviewedAt — reviewedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone updatedAt — updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKuuidNOT NULL
bookingUid textNOT NULLBooking.uid 1:1 · CASCADE
reportedById integernullusers.id 1:N · SET NULL
correctAssignee textnull
additionalNotes textNOT NULL
teamId integernullTeam.id 1:N · SET NULL
routingFormId textnullApp_RoutingForms_Form.id 1:N · SET NULL
status WrongAssignmentReportStatusNOT NULL'PENDING'
reviewedById integernullusers.id 1:N · SET NULL
reviewedAt timestamp(3)null
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP
updatedAt timestamp(3)NOT NULL

Indexes

  • UNIQUE (bookingUid) WrongAssignmentReport_bookingUid_key
  • (reportedById) WrongAssignmentReport_reportedById_idx
  • (teamId) WrongAssignmentReport_teamId_idx
  • (routingFormId) WrongAssignmentReport_routingFormId_idx
  • (status) WrongAssignmentReport_status_idx
  • (reviewedById) WrongAssignmentReport_reviewedById_idx
  • (createdAt) WrongAssignmentReport_createdAt_idx
  • (teamId, status, createdAt) WrongAssignmentReport_teamId_status_createdAt_idx

Referenced by

  • nothing

InternalNotePreset

5 cols · no RLS · line 1785View in 3D

CreateTable

  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKserialNOT NULL
name textNOT NULL
cancellationReason textnull
teamId integerNOT NULLTeam.id 1:N · CASCADE
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP

Indexes

  • (teamId) InternalNotePreset_teamId_idx
  • UNIQUE (teamId, name) InternalNotePreset_teamId_name_key

Referenced by

FilterSegment

14 cols · no RLS · line 1796View in 3D

CreateTable

  • Foreign key without index userId — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on users must scan FilterSegment to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to FilterSegment maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often users rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • Foreign key without index teamId — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on Team must scan FilterSegment to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to FilterSegment maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often Team rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • Nullable foreign key teamId — FilterSegment.teamId may be NULL, so the relationship to Team is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone updatedAt — updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKserialNOT NULL
name textNOT NULL
tableIdentifier textNOT NULL
scope FilterSegmentScopeNOT NULL
activeFilters jsonbnull
sorting jsonbnull
columnVisibility jsonbnull
columnSizing jsonbnull
perPage integerNOT NULL
searchTerm textnull
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP
updatedAt timestamp(3)NOT NULL
userId integerNOT NULLusers.id 1:N · CASCADE
teamId integernullTeam.id 1:N · CASCADE

Indexes

  • (scope, userId, tableIdentifier) FilterSegment_scope_userId_tableIdentifier_idx
  • (scope, teamId, tableIdentifier) FilterSegment_scope_teamId_tableIdentifier_idx

Referenced by

AuditActor

9 cols · no RLS · line 1878View in 3D

CreateTable

  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKuuidNOT NULL
type AuditActorTypeNOT NULL
userUuid uuidnull
attendeeId integernull
credentialId integernull
email textnull
phone textnull
name textnull
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP

Indexes

  • (email) AuditActor_email_idx
  • (userUuid) AuditActor_userUuid_idx
  • (attendeeId) AuditActor_attendeeId_idx
  • (credentialId) AuditActor_credentialId_idx
  • UNIQUE (userUuid) AuditActor_userUuid_key
  • UNIQUE (attendeeId) AuditActor_attendeeId_key
  • UNIQUE (credentialId) AuditActor_credentialId_key
  • UNIQUE (email) AuditActor_email_key
  • UNIQUE (phone) AuditActor_phone_key

Referenced by

Agent

10 cols · no RLS · line 1911View in 3D

CreateTable

  • Nullable foreign key userId — Agent.userId may be NULL, so the relationship to users is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key teamId — Agent.teamId may be NULL, so the relationship to Team is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Either/or foreign keys without a CHECK userId, teamId — Agent has several nullable FKs (userId, teamId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.
  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone updatedAt — updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKtextNOT NULL
name textNOT NULL
userId integernullusers.id 1:N · CASCADE
teamId integernullTeam.id 1:N · CASCADE
providerAgentId textNOT NULL
inboundEventTypeId integernull
outboundEventTypeId integernull
enabled booleanNOT NULLTRUE
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP
updatedAt timestamp(3)NOT NULL

Indexes

  • UNIQUE (providerAgentId) Agent_providerAgentId_key
  • (userId) Agent_userId_idx
  • (teamId) Agent_teamId_idx
  • (inboundEventTypeId) Agent_inboundEventTypeId_idx
  • (outboundEventTypeId) Agent_outboundEventTypeId_idx

Referenced by

SeatChangeLog

14 cols · no RLS · line 1994View in 3D

CreateTable

  • Foreign key without index processedInProrationId — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on MonthlyProration must scan SeatChangeLog to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to SeatChangeLog maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often MonthlyProration rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • Foreign key without index teamBillingId — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on TeamBilling must scan SeatChangeLog to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to SeatChangeLog maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often TeamBilling rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • Foreign key without index organizationBillingId — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on OrganizationBilling must scan SeatChangeLog to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to SeatChangeLog maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often OrganizationBilling rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • Nullable foreign key processedInProrationId — SeatChangeLog.processedInProrationId may be NULL, so the relationship to MonthlyProration is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key teamBillingId — SeatChangeLog.teamBillingId may be NULL, so the relationship to TeamBilling is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key organizationBillingId — SeatChangeLog.organizationBillingId may be NULL, so the relationship to OrganizationBilling is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Either/or foreign keys without a CHECK processedInProrationId, teamBillingId, organizationBillingId — SeatChangeLog has several nullable FKs (processedInProrationId, teamBillingId, organizationBillingId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.
  • TIMESTAMP without time zone changeDate — changeDate stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKtextNOT NULL
teamId integerNOT NULLTeam.id 1:N · CASCADE
changeType SeatChangeTypeNOT NULL
seatCount integerNOT NULL
userId integernull
triggeredBy integernull
changeDate timestamp(3)NOT NULLCURRENT_TIMESTAMP
monthKey textNOT NULL
operationId textnull
processedInProrationId textnullMonthlyProration.id 1:N · SET NULL
metadata jsonbnull
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP
teamBillingId textnullTeamBilling.id 1:N · SET NULL
organizationBillingId textnullOrganizationBilling.id 1:N · SET NULL

Indexes

  • (teamId, monthKey) SeatChangeLog_teamId_monthKey_idx
  • (teamId, processedInProrationId) SeatChangeLog_teamId_processedInProrationId_idx
  • (monthKey) SeatChangeLog_monthKey_idx
  • UNIQUE (teamId, operationId) SeatChangeLog_teamId_operationId_key

Referenced by

  • nothing

MonthlyProration

31 cols · no RLS · line 2014View in 3D

CreateTable

  • Foreign key without index teamBillingId — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on TeamBilling must scan MonthlyProration to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to MonthlyProration maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often TeamBilling rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • Foreign key without index organizationBillingId — PostgreSQL does not index FK columns automatically. Every DELETE/UPDATE on OrganizationBilling must scan MonthlyProration to check the constraint, and every join from the parent side is a sequential scan. The scan cost grows with the child table forever, so this gets worse on its own. An index is not free. Every write to MonthlyProration maintains it — insert, update and delete alike — and it takes disk. Weigh that against how often OrganizationBilling rows are actually deleted or re-keyed — if the answer is never, the scan never happens and the index only costs.
  • Nullable foreign key teamBillingId — MonthlyProration.teamBillingId may be NULL, so the relationship to TeamBilling is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key organizationBillingId — MonthlyProration.organizationBillingId may be NULL, so the relationship to OrganizationBilling is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Either/or foreign keys without a CHECK teamBillingId, organizationBillingId — MonthlyProration has several nullable FKs (teamBillingId, organizationBillingId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.
  • TIMESTAMP without time zone periodStart — periodStart stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone periodEnd — periodEnd stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone subscriptionStart — subscriptionStart stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone subscriptionEnd — subscriptionEnd stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone chargedAt — chargedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone failedAt — failedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone updatedAt — updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • Wide table (31 columns) — Tables this wide usually hide several entities (or a JSON column that wants to be one). Every row update rewrites the whole tuple; TOAST kicks in; indexes bloat.
ColumnTypeNullDefaultReferencesNotes
id PKtextNOT NULL
teamId integerNOT NULLTeam.id 1:N · CASCADE
monthKey textNOT NULL
periodStart timestamp(3)NOT NULL
periodEnd timestamp(3)NOT NULL
seatsAtStart integerNOT NULL
seatsAdded integerNOT NULL
seatsRemoved integerNOT NULL
netSeatIncrease integerNOT NULL
seatsAtEnd integerNOT NULL
subscriptionId textNOT NULL
subscriptionItemId textNOT NULL
customerId textNOT NULL
subscriptionStart timestamp(3)NOT NULL
subscriptionEnd timestamp(3)NOT NULL
remainingDays integerNOT NULL
pricePerSeat integerNOT NULL
proratedAmount integerNOT NULL
invoiceItemId textnull
invoiceId textnull
invoiceUrl textnull
status ProrationStatusNOT NULL'PENDING'
chargedAt timestamp(3)null
failedAt timestamp(3)null
failureReason textnull
retryCount integerNOT NULL0
metadata jsonbnull
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP
updatedAt timestamp(3)NOT NULL
teamBillingId textnullTeamBilling.id 1:N · SET NULL
organizationBillingId textnullOrganizationBilling.id 1:N · SET NULL

Indexes

  • (monthKey, status) MonthlyProration_monthKey_status_idx
  • (status) MonthlyProration_status_idx
  • (teamId) MonthlyProration_teamId_idx
  • UNIQUE (teamId, monthKey) MonthlyProration_teamId_monthKey_key

Referenced by

IntegrationAttributeSync

8 cols · no RLS · line 2077View in 3D

CreateTable

  • Nullable foreign key credentialId — IntegrationAttributeSync.credentialId may be NULL, so the relationship to Credential is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
  • TIMESTAMP without time zone updatedAt — updatedAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKtextNOT NULL
organizationId integerNOT NULLTeam.id 1:N · CASCADE
name textNOT NULL
integration textNOT NULL
credentialId integernullCredential.id 1:N · CASCADE
enabled booleanNOT NULL
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP
updatedAt timestamp(3)NOT NULL

Indexes

  • (organizationId) IntegrationAttributeSync_organizationId_idx
  • (credentialId) IntegrationAttributeSync_credentialId_idx

Referenced by

PendingRoutingTrace

5 cols · no RLS · line 2115View in 3D

CreateTable

  • Nullable foreign key formResponseId — PendingRoutingTrace.formResponseId may be NULL, so the relationship to App_RoutingForms_FormResponse is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Nullable foreign key queuedFormResponseId — PendingRoutingTrace.queuedFormResponseId may be NULL, so the relationship to App_RoutingForms_QueuedFormResponse is optional. That is legitimate (e.g. approved_by before approval) but often a modelling shrug: a row with no owner, or two nullable FKs that are secretly an either/or.
  • Either/or foreign keys without a CHECK formResponseId, queuedFormResponseId — PendingRoutingTrace has several nullable FKs (formResponseId, queuedFormResponseId) that look like an exclusive arc — a row should point at exactly one of them. Nothing stops zero or both.
  • TIMESTAMP without time zone createdAt — createdAt stores wall-clock time with no zone. It reads back differently depending on the session's TimeZone, and DST transitions produce ambiguous values.
ColumnTypeNullDefaultReferencesNotes
id PKtextNOT NULL
createdAt timestamp(3)NOT NULLCURRENT_TIMESTAMP
trace jsonbNOT NULL
formResponseId integernullApp_RoutingForms_FormResponse.id 1:1 · CASCADE
queuedFormResponseId textnullApp_RoutingForms_QueuedFormResponse.id 1:1 · CASCADE

Indexes

  • UNIQUE (formResponseId) PendingRoutingTrace_formResponseId_key
  • UNIQUE (queuedFormResponseId) PendingRoutingTrace_queuedFormResponseId_key

Referenced by

  • nothing

_user_eventtype

2 cols · no RLS · line 2139View in 3D

CreateTable

ColumnTypeNullDefaultReferencesNotes
A PKintegerNOT NULLEventType.id 1:N · CASCADE
B PKintegerNOT NULLusers.id 1:N · CASCADE

Indexes

  • (B) _user_eventtype_B_index

Referenced by

  • nothing