Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
85 changes: 77 additions & 8 deletions migration/1779182511001-AddIdToProjectQfRound.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,98 @@ export class AddIdToProjectQfRound1779182511001 implements MigrationInterface {
name = 'AddIdToProjectQfRound1779182511001';

public async up(queryRunner: QueryRunner): Promise<void> {
// Add the new id column as auto-incrementing column (not primary key)
// First, check if the table exists and get the current primary key constraint name
const tableExists = await queryRunner.hasTable('project_qf_rounds_qf_round');
if (!tableExists) {
throw new Error('Table project_qf_rounds_qf_round does not exist');
}

// Get the current primary key constraint name
const primaryKeyQuery = await queryRunner.query(`
SELECT constraint_name
FROM information_schema.table_constraints
WHERE table_name = 'project_qf_rounds_qf_round'
AND constraint_type = 'PRIMARY KEY'
`);
Comment on lines +95 to +97
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.

⚠️ Potential issue | 🔴 Critical

CASCADE will drop dependent objects without recreating them.

DROP TABLE ... CASCADE on line 96 will silently drop all dependent database objects:

  • Foreign key constraints from other tables
  • Views that reference this table
  • Triggers
  • Stored procedures that reference the table

These dependencies are not recreated after the table is rebuilt, which will break referential integrity and potentially cause application failures.

Either:

Option 1: Remove CASCADE and handle dependencies explicitly:

     // Step 2: Drop the problematic table
     await queryRunner.query(`
-      DROP TABLE IF EXISTS "project_qf_rounds_qf_round" CASCADE
+      DROP TABLE IF EXISTS "project_qf_rounds_qf_round"
     `);

Then query pg_constraint for foreign keys and recreate them manually after table creation.

Option 2: Document that this migration requires manual restoration of dependent objects, and provide a script to identify what will be dropped:

// Before dropping, log what CASCADE will affect:
const dependencies = await queryRunner.query(`
  SELECT 
    conname AS constraint_name,
    confrelid::regclass AS referencing_table
  FROM pg_constraint
  WHERE confrelid = 'project_qf_rounds_qf_round'::regclass
`);
logger.warn('CASCADE will drop these dependencies:', dependencies);


const primaryKeyName = primaryKeyQuery[0]?.constraint_name || 'PK_project_qf_rounds_qf_round';

// Drop the existing composite primary key
await queryRunner.query(`
ALTER TABLE "project_qf_rounds_qf_round"
DROP CONSTRAINT "${primaryKeyName}"
`);

// Add the new id column as auto-incrementing primary key
await queryRunner.query(`
ALTER TABLE "project_qf_rounds_qf_round"
ADD COLUMN "id" SERIAL
ADD COLUMN "id" SERIAL PRIMARY KEY
`);

// Add unique constraint on the composite key to maintain uniqueness
await queryRunner.query(`
ALTER TABLE IF EXISTS "project_qf_rounds_qf_round"
ADD CONSTRAINT "UQ_project_qf_rounds_composite"
UNIQUE ("projectId", "qfRoundId")
`);

// Add indexes on projectId and qfRoundId for performance
await queryRunner.query(`
CREATE INDEX "IDX_project_qf_rounds_projectId"
ON "project_qf_rounds_qf_round" ("projectId")
`);

// Add index on the id column for performance
await queryRunner.query(`
CREATE INDEX "IDX_project_qf_rounds_id"
ON "project_qf_rounds_qf_round" ("id")
CREATE INDEX "IDX_project_qf_rounds_qfRoundId"
ON "project_qf_rounds_qf_round" ("qfRoundId")
`);
}

public async down(queryRunner: QueryRunner): Promise<void> {
// Drop the index first
// Drop the indexes first
await queryRunner.query(`
DROP INDEX IF EXISTS "IDX_project_qf_rounds_id"
DROP INDEX IF EXISTS "IDX_project_qf_rounds_projectId"
`);

await queryRunner.query(`
DROP INDEX IF EXISTS "IDX_project_qf_rounds_qfRoundId"
`);

// Drop the unique constraint
await queryRunner.query(`
ALTER TABLE IF EXISTS "project_qf_rounds_qf_round"
DROP CONSTRAINT IF EXISTS "UQ_project_qf_rounds_composite"
`);

// Get the current primary key constraint name for the id column
const primaryKeyQuery = await queryRunner.query(`
SELECT constraint_name
FROM information_schema.table_constraints
WHERE table_name = 'project_qf_rounds_qf_round'
AND constraint_type = 'PRIMARY KEY'
`);

const primaryKeyName = primaryKeyQuery[0]?.constraint_name;

if (primaryKeyName) {
// Drop the id primary key constraint
await queryRunner.query(`
ALTER TABLE IF EXISTS "project_qf_rounds_qf_round"
DROP CONSTRAINT "${primaryKeyName}"
`);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

// Drop the id column
await queryRunner.query(`
ALTER TABLE "project_qf_rounds_qf_round"
ALTER TABLE IF EXISTS "project_qf_rounds_qf_round"
DROP COLUMN IF EXISTS "id"
`);

// Restore the composite primary key
await queryRunner.query(`
ALTER TABLE IF EXISTS "project_qf_rounds_qf_round"
ADD CONSTRAINT "PK_project_qf_rounds_qf_round"
PRIMARY KEY ("projectId", "qfRoundId")
`);
Comment on lines +293 to +298
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.

⚠️ Potential issue | 🟡 Minor

Hard-coded constraint name may differ from original.

The composite primary key is restored with the hard-coded name "PK_project_qf_rounds_qf_round" (line 296), but the original constraint may have had a different name (e.g., "PK_046d515dee2988817725ec75ebf" as seen in line 52).

This was previously flagged and marked as addressed in commit 6595f6d, but the hard-coded name remains.

While functionally equivalent, this creates a naming inconsistency. Consider dynamically determining the original name if perfect reversibility is required. However, since constraint names don't affect behavior, this may be acceptable if you're standardizing on the new name going forward.

If standardization is not the goal, you could store the original PK name before dropping it in up() and reference it in down():

// In up(), before dropping PKs:
const originalPK = await queryRunner.query(`
  SELECT conname 
  FROM pg_constraint 
  WHERE conrelid = 'project_qf_rounds_qf_round'::regclass 
  AND contype = 'p' 
  LIMIT 1
`);
// Store originalPK[0]?.conname somewhere (e.g., migration metadata table)

// In down():
// Retrieve stored name and use it instead of hard-coded value
🤖 Prompt for AI Agents
In migration/1779182511001-AddIdToProjectQfRound.ts around lines 293-298 the
composite PK is recreated with a hard-coded name
"PK_project_qf_rounds_qf_round", which may differ from the original and breaks
perfect reversibility; before dropping the original PK in up(), query
pg_constraint for the existing primary key name on project_qf_rounds_qf_round
and persist that name (e.g., in a simple migration metadata table or a temporary
table/comment), then in down() retrieve and use that persisted name when
re-adding the constraint instead of the hard-coded string so the original
constraint name is restored; alternatively, if you intend to standardize on the
new name, document that decision and leave the new name.

}
}
14 changes: 8 additions & 6 deletions src/entities/projectQfRound.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,34 @@
import { Field, ID, ObjectType, Float, Int } from 'type-graphql';
import {
PrimaryColumn,
PrimaryGeneratedColumn,
Column,
Entity,
ManyToOne,
BaseEntity,
CreateDateColumn,
UpdateDateColumn,
Index,
Unique,
} from 'typeorm';
import { Project } from './project';
import { QfRound } from './qfRound';

@Entity('project_qf_rounds_qf_round')
@ObjectType()
@Unique(['projectId', 'qfRoundId']) // Ensure uniqueness of the composite key
export class ProjectQfRound extends BaseEntity {
@Field(_type => ID)
@Column({ generated: 'increment' })
@Index()
@PrimaryColumn()
@PrimaryGeneratedColumn() // Make this the primary key
id: number;

@Field(_type => ID)
@PrimaryColumn()
@Column()
@Index()
projectId: number;

@Field(_type => ID)
@PrimaryColumn()
@Column()
@Index()
qfRoundId: number;

@ManyToOne(_type => Project, project => project.projectQfRoundRelations)
Expand Down