Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion packages/db/migrations/0194_careless_pete_wisdom.sql
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,14 @@ END $$;--> statement-breakpoint
-- workspace-scoped shape. This DROP NOT NULL is a no-op if already nullable.
ALTER TABLE "permission_group" ALTER COLUMN "organization_id" DROP NOT NULL;--> statement-breakpoint

-- 1c. Drop the legacy global UNIQUE(user_id) on permission_group_member before cloning.
-- The pre-0194 model allowed at most one permission-group membership per user globally;
-- the new model allows one membership per user per workspace, enforced by the composite
-- unique indexes created in step 7. Cloning a source member onto multiple workspaces
-- (or even onto a single workspace while the source row still exists) would violate the
-- legacy index, so we must drop it before step 4's INSERT.
DROP INDEX IF EXISTS "permission_group_member_user_id_unique";--> statement-breakpoint

-- 2. Materialize a plan of (source permission group, target workspace, new clone id)
-- so we can insert the clone rows AND the member rows with stable references.
-- Temp tables are always fresh per transaction, so this is naturally idempotent.
Expand Down Expand Up @@ -152,7 +160,7 @@ ALTER TABLE "permission_group_member" ALTER COLUMN "workspace_id" SET NOT NULL;-
ALTER TABLE "permission_group" DROP CONSTRAINT IF EXISTS "permission_group_organization_id_organization_id_fk";--> statement-breakpoint
DROP INDEX IF EXISTS "permission_group_org_name_unique";--> statement-breakpoint
DROP INDEX IF EXISTS "permission_group_org_auto_add_unique";--> statement-breakpoint
DROP INDEX IF EXISTS "permission_group_member_user_id_unique";--> statement-breakpoint
-- permission_group_member_user_id_unique was dropped earlier in step 1c (before cloning).
ALTER TABLE "permission_group" DROP COLUMN IF EXISTS "organization_id";--> statement-breakpoint
CREATE UNIQUE INDEX IF NOT EXISTS "permission_group_workspace_name_unique" ON "permission_group" USING btree ("workspace_id","name");--> statement-breakpoint
CREATE UNIQUE INDEX IF NOT EXISTS "permission_group_workspace_auto_add_unique" ON "permission_group" USING btree ("workspace_id") WHERE auto_add_new_members = true;--> statement-breakpoint
Expand Down
68 changes: 67 additions & 1 deletion packages/db/scripts/migrate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,74 @@ try {
console.log('Migrations applied successfully.')
} catch (error) {
console.error('ERROR: Migration failed.')
console.error(error instanceof Error ? error.message : error)
printMigrationError(error)
process.exit(1)
} finally {
await client.end()
}

/**
* Print every diagnostic field a Postgres driver puts on a thrown error. The default
* `error.message` loses the constraint name, affected table/column, PG code, and hint —
* which are usually what you need to diagnose a failed migration.
*/
function printMigrationError(error: unknown): void {
if (!(error instanceof Error)) {
console.error(error)
return
}

console.error(`message: ${error.message}`)

const pgFields = [
'code',
'severity',
'severity_local',
'detail',
'hint',
'schema',
'schema_name',
'table',
'table_name',
'column',
'column_name',
'constraint',
'constraint_name',
'data_type',
'where',
'internal_query',
'internal_position',
'position',
'routine',
'file',
'line',
] as const
Comment on lines +38 to +60
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Some snake_case field names won't match postgres.js error fields

The postgres (porsager/postgres) driver uses camelCase for several extended error fields. The entries internal_query, internal_position, data_type, and severity_local will never match a property on a PostgresError object — the driver actually exposes internalQuery, internalPosition, dataType, and severityLocal. These fields are silent no-ops today, meaning useful diagnostics (especially internalQuery, which shows the PL/pgSQL statement that failed) are silently dropped.

Suggested change
const pgFields = [
'code',
'severity',
'severity_local',
'detail',
'hint',
'schema',
'schema_name',
'table',
'table_name',
'column',
'column_name',
'constraint',
'constraint_name',
'data_type',
'where',
'internal_query',
'internal_position',
'position',
'routine',
'file',
'line',
] as const
const pgFields = [
'code',
'severity',
'severityLocal',
'detail',
'hint',
'schema',
'schemaName',
'table',
'tableName',
'column',
'columnName',
'constraint',
'constraintName',
'dataType',
'dataTypeName',
'where',
'internalQuery',
'internalPosition',
'position',
'routine',
'file',
'line',
] as const


const err = error as Record<string, unknown>
for (const field of pgFields) {
const value = err[field]
if (value !== undefined && value !== null && value !== '') {
console.error(`${field}: ${String(value)}`)
}
}

if (err.query && typeof err.query === 'string') {
console.error('\nfailing query:')
console.error(err.query)
}

if (err.parameters !== undefined) {
console.error('\nparameters:')
console.error(err.parameters)
}

if (error.cause) {
console.error('\ncause:')
printMigrationError(error.cause)
}

if (error.stack) {
console.error('\nstack:')
console.error(error.stack)
}
}
Loading