Thanks to visit codestin.com
Credit goes to github.com

Skip to content

fix: solve migration issue with primary key renaming#11992

Open
gioboa wants to merge 10 commits intotypeorm:masterfrom
gioboa:fix/11636
Open

fix: solve migration issue with primary key renaming#11992
gioboa wants to merge 10 commits intotypeorm:masterfrom
gioboa:fix/11636

Conversation

@gioboa
Copy link
Collaborator

@gioboa gioboa commented Feb 14, 2026

Close #11636

Description of change

Pull-Request Checklist

  • Code is up-to-date with the master branch
  • This pull request links relevant issues as Fixes #00000
  • There are new or updated tests validating the change (tests/**.test.ts)
  • Documentation has been updated to reflect this change (docs/docs/**.md)

@qodo-free-for-open-source-projects

User description

Close #11636

Description of change

Pull-Request Checklist

  • Code is up-to-date with the master branch
  • This pull request links relevant issues as Fixes #00000
  • There are new or updated tests validating the change (tests/**.test.ts)
  • Documentation has been updated to reflect this change (docs/docs/**.md)

PR Type

Bug fix, Tests


Description

  • Fix migration issue with primary key constraint renaming across multiple database drivers

  • Add primaryKeyConstraintName comparison in findChangedColumns methods to detect constraint name changes

  • Update updatePrimaryKeys methods to use correct column source for constraint name resolution

  • Add comprehensive JSDoc @param annotations to driver and query runner methods

  • Add functional test validating primary key constraint rename detection and migration generation


Diagram Walkthrough

flowchart LR
  A["Schema Builder"] -->|detects PK constraint change| B["findChangedColumns"]
  B -->|compares primaryKeyConstraintName| C["Driver Methods"]
  C -->|generates migration| D["updatePrimaryKeys"]
  D -->|uses correct column source| E["Database"]
  F["Test Suite"] -->|validates| A
Loading

File Walkthrough

Relevant files
Documentation
4 files
CockroachDriver.ts
Add JSDoc parameters and improve documentation                     
+38/-1   
OracleDriver.ts
Add JSDoc parameters and improve documentation                     
+35/-1   
PostgresDriver.ts
Add JSDoc parameters and improve documentation                     
+40/-1   
SqlServerDriver.ts
Add JSDoc parameters and improve documentation                     
+40/-1   
Bug fix
5 files
CockroachQueryRunner.ts
Add JSDoc parameters and fix primary key constraint logic
+151/-2 
OracleQueryRunner.ts
Fix primary key constraint name source in updatePrimaryKeys
+2/-2     
PostgresQueryRunner.ts
Fix primary key constraint name source in updatePrimaryKeys
+2/-2     
SqlServerQueryRunner.ts
Fix primary key constraint name source in updatePrimaryKeys
+2/-2     
RdbmsSchemaBuilder.ts
Detect primary key constraint name changes in migrations 
+22/-2   
Tests
1 files
change-primary-key-constraint.test.ts
Add test for primary key constraint rename detection         
+76/-0   

@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 14, 2026

commit: 65b3e9d

@qodo-free-for-open-source-projects
Copy link

qodo-free-for-open-source-projects bot commented Feb 14, 2026

PR Code Suggestions ✨

Latest suggestions up to 65b3e9d

CategorySuggestion                                                                                                                                    Impact
Security
Escape constraint identifiers in SQL

Escape primary key constraint names in the RENAME CONSTRAINT query to prevent
potential SQL injection vulnerabilities.

src/driver/postgres/PostgresQueryRunner.ts [2728-2741]

+const escapedOldPkName = effectiveOldPkName.replace(/"/g, `""`)
+const escapedNewPkName = effectiveNewPkName.replace(/"/g, `""`)
+
 upQueries.push(
     new Query(
         `ALTER TABLE ${this.escapePath(
             table,
-        )} RENAME CONSTRAINT "${effectiveOldPkName}" TO "${effectiveNewPkName}"`,
+        )} RENAME CONSTRAINT "${escapedOldPkName}" TO "${escapedNewPkName}"`,
     ),
 )
 downQueries.push(
     new Query(
         `ALTER TABLE ${this.escapePath(
             table,
-        )} RENAME CONSTRAINT "${effectiveNewPkName}" TO "${effectiveOldPkName}"`,
+        )} RENAME CONSTRAINT "${escapedNewPkName}" TO "${escapedOldPkName}"`,
     ),
 )
  • Apply / Chat
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a potential SQL injection vulnerability in the newly added code by not escaping constraint names and provides the correct fix for PostgreSQL, which is critical for security.

High
Possible issue
Make constraint rename robust

Improve the reliability of renaming primary key constraints in SQL Server by
using N'...' string literals, escaping single quotes, and explicitly specifying
the 'OBJECT' type for sp_rename.

src/driver/sqlserver/SqlServerQueryRunner.ts [2248-2261]

+const oldPkQualified = `${this.getTablePath(table)}.${effectiveOldPkName}`.replace(/'/g, `''`)
+const newPkNameEscaped = effectiveNewPkName.replace(/'/g, `''`)
+const newPkQualified = `${this.getTablePath(table)}.${effectiveNewPkName}`.replace(/'/g, `''`)
+const oldPkNameEscaped = effectiveOldPkName.replace(/'/g, `''`)
+
 upQueries.push(
     new Query(
-        `EXEC sp_rename "${this.getTablePath(
-            table,
-        )}.${effectiveOldPkName}", "${effectiveNewPkName}"`,
+        `EXEC sp_rename N'${oldPkQualified}', N'${newPkNameEscaped}', 'OBJECT'`,
     ),
 )
 downQueries.push(
     new Query(
-        `EXEC sp_rename "${this.getTablePath(
-            table,
-        )}.${effectiveNewPkName}", "${effectiveOldPkName}"`,
+        `EXEC sp_rename N'${newPkQualified}', N'${oldPkNameEscaped}', 'OBJECT'`,
     ),
 )
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies that the implementation for sp_rename is not robust and provides a more reliable way to rename constraints in SQL Server, preventing potential errors and SQL injection.

Medium
  • More

Previous suggestions

Suggestions up to commit a91d51d
CategorySuggestion                                                                                                                                    Impact
Possible issue
Make constraint renames work reliably

Improve the SQL Server primary key rename logic by using bracket-escaping for
identifiers and explicitly specifying the object type in the sp_rename stored
procedure. This enhances query robustness and prevents potential failures with
special characters.

src/driver/sqlserver/SqlServerQueryRunner.ts [2248-2261]

+const escapeMssqlIdentifier = (name: string) => `[${name.replace(/]/g, "]]")}]`
+
+const tablePath = this.getTablePath(table)
+    .split(".")
+    .map((part) => escapeMssqlIdentifier(part.replace(/^"|"$/g, "")))
+    .join(".")
+
 upQueries.push(
     new Query(
-        `EXEC sp_rename "${this.getTablePath(
-            table,
-        )}.${effectiveOldPkName}", "${effectiveNewPkName}"`,
+        `EXEC sp_rename N'${tablePath}.${escapeMssqlIdentifier(
+            effectiveOldPkName,
+        )}', N'${effectiveNewPkName.replace(/'/g, "''")}', 'OBJECT'`,
     ),
 )
 downQueries.push(
     new Query(
-        `EXEC sp_rename "${this.getTablePath(
-            table,
-        )}.${effectiveNewPkName}", "${effectiveOldPkName}"`,
+        `EXEC sp_rename N'${tablePath}.${escapeMssqlIdentifier(
+            effectiveNewPkName,
+        )}', N'${effectiveOldPkName.replace(/'/g, "''")}', 'OBJECT'`,
     ),
 )
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies that the sp_rename syntax is fragile, and proposes using the more robust bracket-escaping ([]) for identifiers and explicitly specifying the object type ('OBJECT'), which are best practices for SQL Server and prevent potential failures.

Medium
Escape identifiers in rename SQL

Fix a potential SQL injection and correctness issue in RENAME CONSTRAINT queries
by properly escaping the old and new constraint names. This ensures that
constraint names containing special characters like double quotes are handled
correctly.

src/driver/postgres/PostgresQueryRunner.ts [2728-2741]

+const escapeIdentifier = (name: string) => `"${name.replace(/"/g, `""`)}"`
+
 upQueries.push(
     new Query(
         `ALTER TABLE ${this.escapePath(
             table,
-        )} RENAME CONSTRAINT "${effectiveOldPkName}" TO "${effectiveNewPkName}"`,
+        )} RENAME CONSTRAINT ${escapeIdentifier(
+            effectiveOldPkName,
+        )} TO ${escapeIdentifier(effectiveNewPkName)}`,
     ),
 )
 downQueries.push(
     new Query(
         `ALTER TABLE ${this.escapePath(
             table,
-        )} RENAME CONSTRAINT "${effectiveNewPkName}" TO "${effectiveOldPkName}"`,
+        )} RENAME CONSTRAINT ${escapeIdentifier(
+            effectiveNewPkName,
+        )} TO ${escapeIdentifier(effectiveOldPkName)}`,
     ),
 )
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly points out a potential SQL injection vulnerability and correctness issue by not escaping constraint names. Applying proper escaping for identifiers is crucial for security and to prevent syntax errors if names contain special characters like double quotes.

Medium
✅ Suggestions up to commit b5cadc3
CategorySuggestion                                                                                                                                    Impact
Possible issue
Prevent invalid constraint rename SQL
Suggestion Impact:The commit updated the generated RENAME CONSTRAINT SQL to use effectiveOldPkName/effectiveNewPkName and updated the cached primaryKeyConstraintName assignment accordingly, preventing "undefined" constraint names; it did not add the suggested quote-escaping.

code diff:

@@ -2729,14 +2729,14 @@
                 new Query(
                     `ALTER TABLE ${this.escapePath(
                         table,
-                    )} RENAME CONSTRAINT "${oldPkName}" TO "${newPkName}"`,
+                    )} RENAME CONSTRAINT "${effectiveOldPkName}" TO "${effectiveNewPkName}"`,
                 ),
             )
             downQueries.push(
                 new Query(
                     `ALTER TABLE ${this.escapePath(
                         table,
-                    )} RENAME CONSTRAINT "${newPkName}" TO "${oldPkName}"`,
+                    )} RENAME CONSTRAINT "${effectiveNewPkName}" TO "${effectiveOldPkName}"`,
                 ),
             )
 
@@ -2744,7 +2744,8 @@
             clonedTable.columns
                 .filter((column) => column.isPrimary)
                 .forEach(
-                    (column) => (column.primaryKeyConstraintName = newPkName),
+                    (column) =>
+                        (column.primaryKeyConstraintName = effectiveNewPkName),
                 )

Use the effective primary key constraint names (effectiveOldPkName and
effectiveNewPkName) in the ALTER TABLE query instead of oldPkName and newPkName
to avoid generating invalid SQL. Also, escape any quotes within the constraint
names.

src/driver/postgres/PostgresQueryRunner.ts [2703-2753]

 // Check if only the PK constraint name changed (same columns)
 const oldPkName = primaryColumns[0]?.primaryKeyConstraintName
 const newPkName = columns[0]?.primaryKeyConstraintName
 
 // Compute effective old and new PK names, considering default naming strategy
 const effectiveOldPkName = oldPkName
     ? oldPkName
     : this.connection.namingStrategy.primaryKeyName(
           clonedTable,
           primaryColumns.map((c) => c.name),
       )
 const effectiveNewPkName = newPkName
     ? newPkName
     : this.connection.namingStrategy.primaryKeyName(
           clonedTable,
           columnNames,
       )
 
 if (
     primaryColumns.length > 0 &&
     primaryColumns.length === columns.length &&
     effectiveOldPkName !== effectiveNewPkName &&
     [...primaryColumns.map((c) => c.name)].sort().join(",") ===
         [...columnNames].sort().join(",")
 ) {
+    const fromName = effectiveOldPkName.replace(/"/g, `""`)
+    const toName = effectiveNewPkName.replace(/"/g, `""`)
+
     upQueries.push(
         new Query(
             `ALTER TABLE ${this.escapePath(
                 table,
-            )} RENAME CONSTRAINT "${oldPkName}" TO "${newPkName}"`,
+            )} RENAME CONSTRAINT "${fromName}" TO "${toName}"`,
         ),
     )
     downQueries.push(
         new Query(
             `ALTER TABLE ${this.escapePath(
                 table,
-            )} RENAME CONSTRAINT "${newPkName}" TO "${oldPkName}"`,
+            )} RENAME CONSTRAINT "${toName}" TO "${fromName}"`,
         ),
     )
 
-    // update cached column constraint names
+    // update cached column constraint names (keep explicit name only)
     clonedTable.columns
         .filter((column) => column.isPrimary)
-        .forEach(
-            (column) => (column.primaryKeyConstraintName = newPkName),
-        )
+        .forEach((column) => {
+            column.primaryKeyConstraintName = newPkName
+        })
 
     await this.executeQueries(upQueries, downQueries)
     this.replaceCachedTable(table, clonedTable)
     return
 }
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a critical bug where undefined could be interpolated into the SQL query if a primary key constraint name is implicit, causing migration failures. The proposed fix is accurate and robust.

High
Fix SQL Server rename syntax
Suggestion Impact:The commit updated the sp_rename queries and PK constraint assignment to use effectiveOldPkName/effectiveNewPkName instead of potentially undefined oldPkName/newPkName, but did not implement the suggested single-quote syntax, escaping, or 'OBJECT' argument.

code diff:

@@ -2249,21 +2249,22 @@
                 new Query(
                     `EXEC sp_rename "${this.getTablePath(
                         table,
-                    )}.${oldPkName}", "${newPkName}"`,
+                    )}.${effectiveOldPkName}", "${effectiveNewPkName}"`,
                 ),
             )
             downQueries.push(
                 new Query(
                     `EXEC sp_rename "${this.getTablePath(
                         table,
-                    )}.${newPkName}", "${oldPkName}"`,
+                    )}.${effectiveNewPkName}", "${effectiveOldPkName}"`,
                 ),
             )
 
             clonedTable.columns
                 .filter((column) => column.isPrimary)
                 .forEach(
-                    (column) => (column.primaryKeyConstraintName = newPkName),
+                    (column) =>
+                        (column.primaryKeyConstraintName = effectiveNewPkName),
                 )

Fix the sp_rename query for SQL Server by using single quotes for string
literals, using the effective constraint names to prevent undefined values, and
specifying the object type as 'OBJECT'.

src/driver/sqlserver/SqlServerQueryRunner.ts [2223-2272]

 // Check if only the PK constraint name changed (same columns)
 const oldPkName = primaryColumns[0]?.primaryKeyConstraintName
 const newPkName = columns[0]?.primaryKeyConstraintName
 
 // Compute effective old and new PK names, considering default naming strategy
 const effectiveOldPkName = oldPkName
     ? oldPkName
     : this.connection.namingStrategy.primaryKeyName(
           clonedTable,
           primaryColumns.map((c) => c.name),
       )
 const effectiveNewPkName = newPkName
     ? newPkName
     : this.connection.namingStrategy.primaryKeyName(
           clonedTable,
           columnNames,
       )
 
 if (
     primaryColumns.length > 0 &&
     primaryColumns.length === columns.length &&
     effectiveOldPkName !== effectiveNewPkName &&
     [...primaryColumns.map((c) => c.name)].sort().join(",") ===
         [...columnNames].sort().join(",")
 ) {
+    const from = `${this.getTablePath(table)}.${effectiveOldPkName}`.replace(
+        /'/g,
+        "''",
+    )
+    const to = effectiveNewPkName.replace(/'/g, "''")
+
     upQueries.push(
-        new Query(
-            `EXEC sp_rename "${this.getTablePath(
-                table,
-            )}.${oldPkName}", "${newPkName}"`,
-        ),
+        new Query(`EXEC sp_rename '${from}', '${to}', 'OBJECT'`),
     )
     downQueries.push(
         new Query(
-            `EXEC sp_rename "${this.getTablePath(
-                table,
-            )}.${newPkName}", "${oldPkName}"`,
+            `EXEC sp_rename '${`${this.getTablePath(table)}.${effectiveNewPkName}`.replace(
+                /'/g,
+                "''",
+            )}', '${effectiveOldPkName.replace(/'/g, "''")}', 'OBJECT'`,
         ),
     )
 
     clonedTable.columns
         .filter((column) => column.isPrimary)
-        .forEach(
-            (column) => (column.primaryKeyConstraintName = newPkName),
-        )
+        .forEach((column) => {
+            column.primaryKeyConstraintName = newPkName
+        })
 
     await this.executeQueries(upQueries, downQueries)
     this.replaceCachedTable(table, clonedTable)
     return
 }
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies two critical bugs for SQL Server: incorrect quoting for sp_rename and using potentially undefined constraint names in the query. The proposed fix corrects the syntax and logic, preventing migration failures.

High
Use effective PK names
Suggestion Impact:Updated the ALTER TABLE RENAME CONSTRAINT queries to use effectiveOldPkName/effectiveNewPkName instead of oldPkName/newPkName, and set primaryKeyConstraintName to effectiveNewPkName. Quote-escaping was not added.

code diff:

@@ -1864,21 +1864,22 @@
                 new Query(
                     `ALTER TABLE ${this.escapePath(
                         table,
-                    )} RENAME CONSTRAINT "${oldPkName}" TO "${newPkName}"`,
+                    )} RENAME CONSTRAINT "${effectiveOldPkName}" TO "${effectiveNewPkName}"`,
                 ),
             )
             downQueries.push(
                 new Query(
                     `ALTER TABLE ${this.escapePath(
                         table,
-                    )} RENAME CONSTRAINT "${newPkName}" TO "${oldPkName}"`,
+                    )} RENAME CONSTRAINT "${effectiveNewPkName}" TO "${effectiveOldPkName}"`,
                 ),
             )
 
             clonedTable.columns
                 .filter((column) => column.isPrimary)
                 .forEach(
-                    (column) => (column.primaryKeyConstraintName = newPkName),
+                    (column) =>
+                        (column.primaryKeyConstraintName = effectiveNewPkName),
                 )

Use the effective primary key constraint names (effectiveOldPkName and
effectiveNewPkName) in the ALTER TABLE query instead of oldPkName and newPkName
to avoid generating invalid SQL. Also, escape any quotes within the constraint
names.

src/driver/oracle/OracleQueryRunner.ts [1856-1887]

 if (
     primaryColumns.length > 0 &&
     primaryColumns.length === columns.length &&
     effectiveOldPkName !== effectiveNewPkName &&
     [...primaryColumns.map((c) => c.name)].sort().join(",") ===
         [...columnNames].sort().join(",")
 ) {
+    const fromName = effectiveOldPkName.replace(/"/g, `""`)
+    const toName = effectiveNewPkName.replace(/"/g, `""`)
+
     upQueries.push(
         new Query(
             `ALTER TABLE ${this.escapePath(
                 table,
-            )} RENAME CONSTRAINT "${oldPkName}" TO "${newPkName}"`,
+            )} RENAME CONSTRAINT "${fromName}" TO "${toName}"`,
         ),
     )
     downQueries.push(
         new Query(
             `ALTER TABLE ${this.escapePath(
                 table,
-            )} RENAME CONSTRAINT "${newPkName}" TO "${oldPkName}"`,
+            )} RENAME CONSTRAINT "${toName}" TO "${fromName}"`,
         ),
     )
 
     clonedTable.columns
         .filter((column) => column.isPrimary)
-        .forEach(
-            (column) => (column.primaryKeyConstraintName = newPkName),
-        )
+        .forEach((column) => {
+            column.primaryKeyConstraintName = newPkName
+        })
 
     await this.executeQueries(upQueries, downQueries)
     this.replaceCachedTable(table, clonedTable)
     return
 }
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a critical bug where undefined could be interpolated into the SQL query if a primary key constraint name is implicit, causing migration failures on Oracle. The proposed fix is accurate and robust.

High
✅ Suggestions up to commit b08e73b
CategorySuggestion                                                                                                                                    Impact
Possible issue
Correct SQL Server rename statements

Correct the sp_rename syntax for SQL Server by using single-quoted string
literals and specifying the object type as 'OBJECT'.

src/driver/sqlserver/SqlServerQueryRunner.ts [2237-2250]

+const oldName = `${this.getTablePath(table)}.${oldPkName}`.replace(/'/g, "''")
+const newName = `${newPkName}`.replace(/'/g, "''")
+const oldNameDown = `${this.getTablePath(table)}.${newPkName}`.replace(/'/g, "''")
+const newNameDown = `${oldPkName}`.replace(/'/g, "''")
+
 upQueries.push(
     new Query(
-        `EXEC sp_rename "${this.getTablePath(
-            table,
-        )}.${oldPkName}", "${newPkName}"`,
+        `EXEC sp_rename N'${oldName}', N'${newName}', 'OBJECT'`,
     ),
 )
 downQueries.push(
     new Query(
-        `EXEC sp_rename "${this.getTablePath(
-            table,
-        )}.${newPkName}", "${oldPkName}"`,
+        `EXEC sp_rename N'${oldNameDown}', N'${newNameDown}', 'OBJECT'`,
     ),
 )
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies that the sp_rename syntax for SQL Server is incorrect and proposes a fix that aligns with the official documentation, improving correctness and robustness.

Medium
Avoid in-place array sorting
Suggestion Impact:Updated the comparison to sort copies of primaryColumns names and columnNames using spread syntax before calling .sort(), preventing in-place mutation.

code diff:

@@ -2709,10 +2709,8 @@
             oldPkName &&
             newPkName &&
             oldPkName !== newPkName &&
-            primaryColumns
-                .map((c) => c.name)
-                .sort()
-                .join(",") === columnNames.sort().join(",")
+            [...primaryColumns.map((c) => c.name)].sort().join(",") ===
+                [...columnNames].sort().join(",")

Avoid in-place sorting of the columnNames array by creating a sorted copy for
comparison to prevent unintended side effects.

src/driver/postgres/PostgresQueryRunner.ts [2704-2730]

 const oldPkName = primaryColumns[0]?.primaryKeyConstraintName
 const newPkName = columns[0]?.primaryKeyConstraintName
+const oldPkColumnsKey = primaryColumns
+    .map((c) => c.name)
+    .slice()
+    .sort()
+    .join(",")
+const newPkColumnsKey = columnNames.slice().sort().join(",")
+
 if (
     primaryColumns.length > 0 &&
     primaryColumns.length === columns.length &&
     oldPkName &&
     newPkName &&
     oldPkName !== newPkName &&
-    primaryColumns
-        .map((c) => c.name)
-        .sort()
-        .join(",") === columnNames.sort().join(",")
+    oldPkColumnsKey === newPkColumnsKey
 ) {
     upQueries.push(
         new Query(
             `ALTER TABLE ${this.escapePath(
                 table,
             )} RENAME CONSTRAINT "${oldPkName}" TO "${newPkName}"`,
         ),
     )
     downQueries.push(
         new Query(
             `ALTER TABLE ${this.escapePath(
                 table,
             )} RENAME CONSTRAINT "${newPkName}" TO "${oldPkName}"`,
         ),
     )
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies that columnNames.sort() mutates the array in-place, which could cause subtle bugs later in the function where columnNames is reused.

Low
Security
Escape constraint identifiers in SQL

Escape the primary key constraint names oldPkName and newPkName before using
them in the SQL query to prevent potential SQL injection vulnerabilities.

src/driver/cockroachdb/CockroachQueryRunner.ts [2443-2456]

+const escapedOldPkName = oldPkName.replace(/"/g, `""`)
+const escapedNewPkName = newPkName.replace(/"/g, `""`)
+
 upQueries.push(
     new Query(
         `ALTER TABLE ${this.escapePath(
             table,
-        )} RENAME CONSTRAINT "${oldPkName}" TO "${newPkName}"`,
+        )} RENAME CONSTRAINT "${escapedOldPkName}" TO "${escapedNewPkName}"`,
     ),
 )
 downQueries.push(
     new Query(
         `ALTER TABLE ${this.escapePath(
             table,
-        )} RENAME CONSTRAINT "${newPkName}" TO "${oldPkName}"`,
+        )} RENAME CONSTRAINT "${escapedNewPkName}" TO "${escapedOldPkName}"`,
     ),
 )
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly points out a potential SQL injection vulnerability by not escaping constraint names, which could lead to broken queries if they contain double quotes.

Medium
✅ Suggestions up to commit 303a0b0
CategorySuggestion                                                                                                                                    Impact
Possible issue
Fix SQL Server constraint rename SQL

Correct the EXEC sp_rename syntax for SQL Server. Use single quotes for string
literals and add the required 'OBJECT' parameter when renaming constraints.

src/driver/sqlserver/SqlServerQueryRunner.ts [2237-2250]

+const tablePath = this.getTablePath(table).replace(/'/g, "''")
+const oldName = oldPkName.replace(/'/g, "''")
+const newName = newPkName.replace(/'/g, "''")
+
 upQueries.push(
     new Query(
-        `EXEC sp_rename "${this.getTablePath(
-            table,
-        )}.${oldPkName}", "${newPkName}"`,
+        `EXEC sp_rename '${tablePath}.${oldName}', '${newName}', 'OBJECT'`,
     ),
 )
 downQueries.push(
     new Query(
-        `EXEC sp_rename "${this.getTablePath(
-            table,
-        )}.${newPkName}", "${oldPkName}"`,
+        `EXEC sp_rename '${tablePath}.${newName}', '${oldName}', 'OBJECT'`,
     ),
 )
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a syntax error in the EXEC sp_rename call for SQL Server. The current implementation uses double quotes which would cause the query to fail at runtime. The proposed change to use single quotes and add the 'OBJECT' parameter is crucial for the feature to work correctly on SQL Server.

High
Prevent in-place sort side effects
Suggestion Impact:Replaced in-place .sort() usage with sorting on copied arrays (using spread syntax) for both primaryColumns names and columnNames, avoiding mutation side effects.

code diff:

-            primaryColumns
-                .map((c) => c.name)
-                .sort()
-                .join(",") === columnNames.sort().join(",")
+            [...primaryColumns.map((c) => c.name)].sort().join(",") ===
+                [...columnNames].sort().join(",")

Avoid in-place sorting of columnNames to prevent unintended side effects. Create
a sorted copy for comparison instead by using .slice().sort().

src/driver/postgres/PostgresQueryRunner.ts [2704-2716]

 const oldPkName = primaryColumns[0]?.primaryKeyConstraintName
 const newPkName = columns[0]?.primaryKeyConstraintName
+
+const oldPkColumnsSorted = primaryColumns.map((c) => c.name).slice().sort()
+const newPkColumnsSorted = columnNames.slice().sort()
+
 if (
     primaryColumns.length > 0 &&
     primaryColumns.length === columns.length &&
     oldPkName &&
     newPkName &&
     oldPkName !== newPkName &&
-    primaryColumns
-        .map((c) => c.name)
-        .sort()
-        .join(",") === columnNames.sort().join(",")
+    oldPkColumnsSorted.join(",") === newPkColumnsSorted.join(",")
 ) {
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies that columnNames.sort() mutates the array in-place, which could cause issues later if the column order is important. Using .slice() to create a copy before sorting is a good practice for robustness.

Low
✅ Suggestions up to commit a643414
CategorySuggestion                                                                                                                                    Impact
Possible issue
Fix constraint rename SQL

Fix the SQL Server sp_rename query by using single quotes for string literals
and specifying the object type, which is required for correct execution.

src/driver/sqlserver/SqlServerQueryRunner.ts [2237-2250]

 upQueries.push(
     new Query(
-        `EXEC sp_rename "${this.getTablePath(
+        `EXEC sp_rename '${this.getTablePath(
             table,
-        )}.${oldPkName}", "${newPkName}"`,
+        )}.${oldPkName}', '${newPkName}', 'OBJECT'`,
     ),
 )
 downQueries.push(
     new Query(
-        `EXEC sp_rename "${this.getTablePath(
+        `EXEC sp_rename '${this.getTablePath(
             table,
-        )}.${newPkName}", "${oldPkName}"`,
+        )}.${newPkName}', '${oldPkName}', 'OBJECT'`,
     ),
 )
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a bug in the SQL query for renaming constraints on SQL Server and provides a fix that aligns with sp_rename documentation, making the feature work as intended for that driver.

High
Avoid renames with missing names
Suggestion Impact:Updated the pkNameChanged condition by adding a guard that the number of primary key columns must match between table and metadata before treating it as a rename scenario; however, it did not add the suggested old/new PK name existence checks.

code diff:

@@ -856,6 +856,7 @@
             const pkNameChanged =
                 primaryTableColumns.length > 0 &&
                 primaryMetadataColumns.length > 0 &&
+                primaryTableColumns.length === primaryMetadataColumns.length &&
                 primaryTableColumns[0].primaryKeyConstraintName !==
                     primaryMetadataColumns[0].primaryKeyConstraintName &&
                 (DriverUtils.isPostgresFamily(this.connection.driver) ||

Add checks to ensure both old and new primary key constraint names exist before
attempting a rename operation, preventing errors when a new constraint is being
created.

src/schema-builder/RdbmsSchemaBuilder.ts [856-864]

+const oldPkName = primaryTableColumns[0]?.primaryKeyConstraintName
+const newPkName = primaryMetadataColumns[0]?.primaryKeyConstraintName
+
 const pkNameChanged =
     primaryTableColumns.length > 0 &&
     primaryMetadataColumns.length > 0 &&
     primaryTableColumns.length === primaryMetadataColumns.length &&
-    primaryTableColumns[0].primaryKeyConstraintName !==
-        primaryMetadataColumns[0].primaryKeyConstraintName &&
+    !!oldPkName &&
+    !!newPkName &&
+    oldPkName !== newPkName &&
     (DriverUtils.isPostgresFamily(this.connection.driver) ||
         this.connection.driver.options.type === "mssql" ||
         this.connection.driver.options.type === "oracle")
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a flaw in the logic that could lead to runtime errors when a primary key constraint is being added for the first time. The proposed fix prevents this bug.

Medium
Prevent in-place sort mutations

Prevent potential bugs by sorting a copy of the columnNames array instead of
sorting it in-place, ensuring its original order is preserved for subsequent
logic.

src/driver/postgres/PostgresQueryRunner.ts [2706-2716]

 if (
     primaryColumns.length > 0 &&
     primaryColumns.length === columns.length &&
     oldPkName &&
     newPkName &&
     oldPkName !== newPkName &&
-    primaryColumns
+    [...primaryColumns]
         .map((c) => c.name)
         .sort()
-        .join(",") === columnNames.sort().join(",")
+        .join(",") === [...columnNames].sort().join(",")
 ) {
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly points out a potential side effect of an in-place array sort, which could lead to subtle bugs. Applying the fix improves code robustness and prevents such issues.

Low
Security
Escape identifier quotes in SQL
Suggestion Impact:The commit added the suggested "only PK constraint name changed" detection and performs a RENAME CONSTRAINT with early return, but it did not implement escaping of identifier quotes (still interpolates oldPkName/newPkName directly) nor the non-mutating sort of columnNames.

code diff:

+        // Check if only the PK constraint name changed (same columns)
+        const oldPkName = primaryColumns[0]?.primaryKeyConstraintName
+        const newPkName = columns[0]?.primaryKeyConstraintName
+        if (
+            primaryColumns.length > 0 &&
+            primaryColumns.length === columns.length &&
+            oldPkName &&
+            newPkName &&
+            oldPkName !== newPkName &&
+            primaryColumns
+                .map((c) => c.name)
+                .sort()
+                .join(",") === columnNames.sort().join(",")
+        ) {
+            upQueries.push(
+                new Query(
+                    `ALTER TABLE ${this.escapePath(
+                        table,
+                    )} RENAME CONSTRAINT "${oldPkName}" TO "${newPkName}"`,
+                ),
+            )
+            downQueries.push(
+                new Query(
+                    `ALTER TABLE ${this.escapePath(
+                        table,
+                    )} RENAME CONSTRAINT "${newPkName}" TO "${oldPkName}"`,
+                ),
+            )
+
+            clonedTable.columns
+                .filter((column) => column.isPrimary)
+                .forEach(
+                    (column) => (column.primaryKeyConstraintName = newPkName),
+                )
+
+            await this.executeQueries(upQueries, downQueries)
+            this.replaceCachedTable(table, clonedTable)
+            return
+        }

Prevent potential SQL injection and errors by properly escaping the primary key
constraint names before using them in the RENAME CONSTRAINT query.

src/driver/cockroachdb/CockroachQueryRunner.ts [2429-2456]

 // Check if only the PK constraint name changed (same columns)
 const oldPkName = primaryColumns[0]?.primaryKeyConstraintName
 const newPkName = columns[0]?.primaryKeyConstraintName
+const escapeIdent = (name: string) => name.replace(/"/g, `""`)
 if (
     primaryColumns.length > 0 &&
     primaryColumns.length === columns.length &&
     oldPkName &&
     newPkName &&
     oldPkName !== newPkName &&
     primaryColumns
         .map((c) => c.name)
         .sort()
-        .join(",") === columnNames.sort().join(",")
+        .join(",") === [...columnNames].sort().join(",")
 ) {
+    const oldPkNameEscaped = escapeIdent(oldPkName)
+    const newPkNameEscaped = escapeIdent(newPkName)
+
     upQueries.push(
         new Query(
             `ALTER TABLE ${this.escapePath(
                 table,
-            )} RENAME CONSTRAINT "${oldPkName}" TO "${newPkName}"`,
+            )} RENAME CONSTRAINT "${oldPkNameEscaped}" TO "${newPkNameEscaped}"`,
         ),
     )
     downQueries.push(
         new Query(
             `ALTER TABLE ${this.escapePath(
                 table,
-            )} RENAME CONSTRAINT "${newPkName}" TO "${oldPkName}"`,
+            )} RENAME CONSTRAINT "${newPkNameEscaped}" TO "${oldPkNameEscaped}"`,
         ),
     )
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies a potential security vulnerability (SQL injection through identifiers) and a functional bug if constraint names contain double quotes, and provides a correct fix.

Medium
✅ Suggestions up to commit f22f8a8
CategorySuggestion                                                                                                                                    Impact
Possible issue
Prevent empty-array PK name crash
Suggestion Impact:The commit added optional chaining when reading PK constraint names (e.g., columns[0]?.primaryKeyConstraintName and primaryColumns[0]?.primaryKeyConstraintName via oldPkName), reducing the risk of a crash when arrays are empty. It did not fully implement the suggested combined fallback resolution for pkName.

code diff:

+        // Check if only the PK constraint name changed (same columns)
+        const oldPkName = primaryColumns[0]?.primaryKeyConstraintName
+        const newPkName = columns[0]?.primaryKeyConstraintName
+        if (
+            primaryColumns.length > 0 &&
+            primaryColumns.length === columns.length &&
+            oldPkName &&
+            newPkName &&
+            oldPkName !== newPkName &&
+            primaryColumns
+                .map((c) => c.name)
+                .sort()
+                .join(",") === columnNames.sort().join(",")
+        ) {
+            upQueries.push(
+                new Query(
+                    `ALTER TABLE ${this.escapePath(
+                        table,
+                    )} RENAME CONSTRAINT "${oldPkName}" TO "${newPkName}"`,
+                ),
+            )
+            downQueries.push(
+                new Query(
+                    `ALTER TABLE ${this.escapePath(
+                        table,
+                    )} RENAME CONSTRAINT "${newPkName}" TO "${oldPkName}"`,
+                ),
+            )
+
+            clonedTable.columns
+                .filter((column) => column.isPrimary)
+                .forEach(
+                    (column) => (column.primaryKeyConstraintName = newPkName),
+                )
+
+            await this.executeQueries(upQueries, downQueries)
+            this.replaceCachedTable(table, clonedTable)
+            return
+        }
+
         if (primaryColumns.length > 0) {
-            const pkName = primaryColumns[0].primaryKeyConstraintName
-                ? primaryColumns[0].primaryKeyConstraintName
+            const pkName = oldPkName
+                ? oldPkName
                 : this.connection.namingStrategy.primaryKeyName(
                       clonedTable,
                       primaryColumns.map((column) => column.name),
@@ -2458,7 +2499,7 @@
             .filter((column) => columnNames.indexOf(column.name) !== -1)
             .forEach((column) => (column.isPrimary = true))
 
-        const pkName = columns[0].primaryKeyConstraintName
+        const pkName = columns[0]?.primaryKeyConstraintName
             ? columns[0].primaryKeyConstraintName
             : this.connection.namingStrategy.primaryKeyName(
                   clonedTable,

Add a safeguard to updatePrimaryKeys to prevent a crash when the columns array
is empty by using optional chaining and providing a fallback for pkName
resolution.

src/driver/cockroachdb/CockroachQueryRunner.ts [2461-2466]

-const pkName = columns[0].primaryKeyConstraintName
-    ? columns[0].primaryKeyConstraintName
-    : this.connection.namingStrategy.primaryKeyName(
-          clonedTable,
-          columnNames,
-      )
+const pkName =
+    columns[0]?.primaryKeyConstraintName ||
+    primaryColumns[0]?.primaryKeyConstraintName ||
+    this.connection.namingStrategy.primaryKeyName(clonedTable, columnNames)
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a potential runtime error from unsafe access to columns[0] and provides a more robust implementation using optional chaining and a sensible fallback.

Medium
Guard PK name resolution
Suggestion Impact:The commit added optional chaining when reading columns[0].primaryKeyConstraintName (and also when reading primaryColumns[0]), preventing a crash when columns is empty, though it did not fully implement the suggested fallback-to-primaryColumns pkName resolution in the final pkName assignment.

code diff:

+        // Check if only the PK constraint name changed (same columns)
+        const oldPkName = primaryColumns[0]?.primaryKeyConstraintName
+        const newPkName = columns[0]?.primaryKeyConstraintName
+        if (
+            primaryColumns.length > 0 &&
+            primaryColumns.length === columns.length &&
+            oldPkName &&
+            newPkName &&
+            oldPkName !== newPkName &&
+            primaryColumns
+                .map((c) => c.name)
+                .sort()
+                .join(",") === columnNames.sort().join(",")
+        ) {
+            upQueries.push(
+                new Query(
+                    `EXEC sp_rename "${this.getTablePath(
+                        table,
+                    )}.${oldPkName}", "${newPkName}"`,
+                ),
+            )
+            downQueries.push(
+                new Query(
+                    `EXEC sp_rename "${this.getTablePath(
+                        table,
+                    )}.${newPkName}", "${oldPkName}"`,
+                ),
+            )
+
+            clonedTable.columns
+                .filter((column) => column.isPrimary)
+                .forEach(
+                    (column) => (column.primaryKeyConstraintName = newPkName),
+                )
+
+            await this.executeQueries(upQueries, downQueries)
+            this.replaceCachedTable(table, clonedTable)
+            return
+        }
+
         if (primaryColumns.length > 0) {
-            const pkName = primaryColumns[0].primaryKeyConstraintName
-                ? primaryColumns[0].primaryKeyConstraintName
+            const pkName = oldPkName
+                ? oldPkName
                 : this.connection.namingStrategy.primaryKeyName(
                       clonedTable,
                       primaryColumns.map((column) => column.name),
@@ -2252,7 +2293,7 @@
             .filter((column) => columnNames.indexOf(column.name) !== -1)
             .forEach((column) => (column.isPrimary = true))
 
-        const pkName = columns[0].primaryKeyConstraintName
+        const pkName = columns[0]?.primaryKeyConstraintName
             ? columns[0].primaryKeyConstraintName
             : this.connection.namingStrategy.primaryKeyName(
                   clonedTable,

Add a safeguard to updatePrimaryKeys to prevent a crash when the columns array
is empty by using optional chaining and providing a fallback for pkName
resolution.

src/driver/sqlserver/SqlServerQueryRunner.ts [2255-2260]

-const pkName = columns[0].primaryKeyConstraintName
-    ? columns[0].primaryKeyConstraintName
-    : this.connection.namingStrategy.primaryKeyName(
-          clonedTable,
-          columnNames,
-      )
+const pkName =
+    columns[0]?.primaryKeyConstraintName ||
+    primaryColumns[0]?.primaryKeyConstraintName ||
+    this.connection.namingStrategy.primaryKeyName(clonedTable, columnNames)
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a potential runtime error from unsafe access to columns[0] and provides a more robust implementation using optional chaining and a sensible fallback.

Medium
Avoid false PK rename migrations
Suggestion Impact:The PK rename detection logic was tightened by adding a guard that the number of primary table columns matches the number of primary metadata columns before comparing constraint names, reducing false positives (though it did not add the exact suggested "both names defined" checks).

code diff:

             const pkNameChanged =
                 primaryTableColumns.length > 0 &&
                 primaryMetadataColumns.length > 0 &&
+                primaryTableColumns.length === primaryMetadataColumns.length &&
                 primaryTableColumns[0].primaryKeyConstraintName !==
                     primaryMetadataColumns[0].primaryKeyConstraintName &&

Refine the primary key constraint rename detection logic to prevent unnecessary
migrations by ensuring both the old and new constraint names are defined before
comparing them.

src/schema-builder/RdbmsSchemaBuilder.ts [856-863]

 const pkNameChanged =
     primaryTableColumns.length > 0 &&
     primaryMetadataColumns.length > 0 &&
+    !!primaryTableColumns[0].primaryKeyConstraintName &&
+    !!primaryMetadataColumns[0].primaryKeyConstraintName &&
     primaryTableColumns[0].primaryKeyConstraintName !==
         primaryMetadataColumns[0].primaryKeyConstraintName &&
     (DriverUtils.isPostgresFamily(this.connection.driver) ||
         this.connection.driver.options.type === "mssql" ||
         this.connection.driver.options.type === "oracle")
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies a scenario that could lead to unnecessary migrations and proposes a stricter check to prevent false positives when detecting primary key constraint name changes.

Medium
✅ Suggestions up to commit 7f2cfe8
CategorySuggestion                                                                                                                                    Impact
General
compare effective constraint names
Suggestion Impact:The commit introduced effectiveOldPkName/effectiveNewPkName that fall back to namingStrategy.primaryKeyName when primaryKeyConstraintName is not set, and updated the pkNameChanged comparison to use these effective names, ensuring correct detection when names are generated.

code diff:

+            const effectiveOldPkName = primaryTableColumns[0]
+                ?.primaryKeyConstraintName
+                ? primaryTableColumns[0].primaryKeyConstraintName
+                : this.connection.namingStrategy.primaryKeyName(
+                      table,
+                      primaryTableColumns.map((column) => column.name),
+                  )
+            const effectiveNewPkName = primaryMetadataColumns[0]
+                ?.primaryKeyConstraintName
+                ? primaryMetadataColumns[0].primaryKeyConstraintName
+                : this.connection.namingStrategy.primaryKeyName(
+                      table,
+                      primaryMetadataColumns.map(
+                          (column) => column.databaseName,
+                      ),
+                  )
+
             const pkNameChanged =
                 primaryTableColumns.length > 0 &&
                 primaryMetadataColumns.length > 0 &&
-                primaryTableColumns[0].primaryKeyConstraintName !==
-                    primaryMetadataColumns[0].primaryKeyConstraintName &&
+                primaryTableColumns.length === primaryMetadataColumns.length &&
+                effectiveOldPkName !== effectiveNewPkName &&
                 (DriverUtils.isPostgresFamily(this.connection.driver) ||
                     this.connection.driver.options.type === "mssql" ||
-                    this.connection.driver.options.type === "oracle")
+                    this.connection.driver.options.type === "oracle" ||
+                    this.connection.driver.options.type === "cockroachdb")

When checking if the primary key name has changed, explicitly generate the name
using the naming strategy if it's not defined for either the old or new primary
key. This ensures a correct comparison when one or both rely on a generated
name.

src/schema-builder/RdbmsSchemaBuilder.ts [856-863]

+const oldPkName = primaryTableColumns[0].primaryKeyConstraintName
+    ?? this.connection.namingStrategy.primaryKeyName(
+           table,
+           primaryTableColumns.map(col => col.name),
+       );
+const newPkName = primaryMetadataColumns[0].primaryKeyConstraintName
+    ?? this.connection.namingStrategy.primaryKeyName(
+           table,
+           primaryMetadataColumns.map(col => col.databaseName),
+       );
 const pkNameChanged =
     primaryTableColumns.length > 0 &&
     primaryMetadataColumns.length > 0 &&
-    primaryTableColumns[0].primaryKeyConstraintName !==
-        primaryMetadataColumns[0].primaryKeyConstraintName &&
+    oldPkName !== newPkName &&
     (DriverUtils.isPostgresFamily(this.connection.driver) ||
-        this.connection.driver.options.type === "mssql" ||
-        this.connection.driver.options.type === "oracle")
+     this.connection.driver.options.type === "mssql" ||
+     this.connection.driver.options.type === "oracle")
Suggestion importance[1-10]: 9

__

Why: This suggestion addresses a critical flaw in the logic for detecting primary key name changes. Without it, the feature would fail if one of the constraint names is undefined and relies on the default naming strategy, which this PR aims to support.

High
High-level
Refactor duplicated logic into base classes
Suggestion Impact:The commit modified the primary key constraint name change handling in the referenced CockroachDB driver/query runner: it removed the driver-level “pk constraint name changed” column-change check and added/adjusted query-runner logic to handle PK constraint renames (including an explicit RENAME CONSTRAINT path and safer optional access). However, it did not perform the suggested refactor into base classes.

code diff:

# File: src/driver/cockroachdb/CockroachDriver.ts
@@ -964,8 +964,6 @@
                         this.normalizeDefault(columnMetadata),
                     ) !== tableColumn.default) || // we included check for generated here, because generated columns already can have default values
                 tableColumn.isPrimary !== columnMetadata.isPrimary ||
-                tableColumn.primaryKeyConstraintName !==
-                    columnMetadata.primaryKeyConstraintName ||
                 tableColumn.isNullable !== columnMetadata.isNullable ||
                 tableColumn.isUnique !==
                     this.normalizeIsUnique(columnMetadata) ||

# File: src/driver/cockroachdb/CockroachQueryRunner.ts
@@ -2425,9 +2425,50 @@
 
         // if table already have primary columns, we must drop them.
         const primaryColumns = clonedTable.primaryColumns
+
+        // Check if only the PK constraint name changed (same columns)
+        const oldPkName = primaryColumns[0]?.primaryKeyConstraintName
+        const newPkName = columns[0]?.primaryKeyConstraintName
+        if (
+            primaryColumns.length > 0 &&
+            primaryColumns.length === columns.length &&
+            oldPkName &&
+            newPkName &&
+            oldPkName !== newPkName &&
+            primaryColumns
+                .map((c) => c.name)
+                .sort()
+                .join(",") === columnNames.sort().join(",")
+        ) {
+            upQueries.push(
+                new Query(
+                    `ALTER TABLE ${this.escapePath(
+                        table,
+                    )} RENAME CONSTRAINT "${oldPkName}" TO "${newPkName}"`,
+                ),
+            )
+            downQueries.push(
+                new Query(
+                    `ALTER TABLE ${this.escapePath(
+                        table,
+                    )} RENAME CONSTRAINT "${newPkName}" TO "${oldPkName}"`,
+                ),
+            )
+
+            clonedTable.columns
+                .filter((column) => column.isPrimary)
+                .forEach(
+                    (column) => (column.primaryKeyConstraintName = newPkName),
+                )
+
+            await this.executeQueries(upQueries, downQueries)
+            this.replaceCachedTable(table, clonedTable)
+            return
+        }
+
         if (primaryColumns.length > 0) {
-            const pkName = primaryColumns[0].primaryKeyConstraintName
-                ? primaryColumns[0].primaryKeyConstraintName
+            const pkName = oldPkName
+                ? oldPkName
                 : this.connection.namingStrategy.primaryKeyName(
                       clonedTable,
                       primaryColumns.map((column) => column.name),
@@ -2458,7 +2499,7 @@
             .filter((column) => columnNames.indexOf(column.name) !== -1)
             .forEach((column) => (column.isPrimary = true))
 
-        const pkName = columns[0].primaryKeyConstraintName
+        const pkName = columns[0]?.primaryKeyConstraintName
             ? columns[0].primaryKeyConstraintName
             : this.connection.namingStrategy.primaryKeyName(
                   clonedTable,

To reduce code duplication and improve maintainability, refactor the common
logic for handling primary key constraint name changes from the individual
driver and query runner classes into their respective base classes.

Examples:

src/driver/cockroachdb/CockroachDriver.ts [967-968]
                tableColumn.primaryKeyConstraintName !==
                    columnMetadata.primaryKeyConstraintName ||
src/driver/cockroachdb/CockroachQueryRunner.ts [2461-2462]
        const pkName = columns[0].primaryKeyConstraintName
            ? columns[0].primaryKeyConstraintName

Solution Walkthrough:

Before:

// In each of the 4 *Driver.ts files
class SpecificDriver implements Driver {
  findChangedColumns(...) {
    // ... other checks
    const isColumnChanged =
      // ...
      tableColumn.primaryKeyConstraintName !== columnMetadata.primaryKeyConstraintName ||
      // ...
    return isColumnChanged
  }
}

// In each of the 4 *QueryRunner.ts files
class SpecificQueryRunner extends BaseQueryRunner {
  async updatePrimaryKeys(..., columns: TableColumn[]) {
    // ...
    const pkName = columns[0].primaryKeyConstraintName ? ... : ...
    // ...
  }
}

After:

// In a new BaseDriver or inside RdbmsSchemaBuilder
function checkColumnChange(tableColumn, columnMetadata, driver) {
  // ...
  const isPkNameChanged = tableColumn.primaryKeyConstraintName !== columnMetadata.primaryKeyConstraintName;
  // ...
  return isChanged;
}

// In BaseQueryRunner.ts
abstract class BaseQueryRunner implements QueryRunner {
  async updatePrimaryKeys(..., columns: TableColumn[]) {
    // ...
    // Common logic to drop old PK
    // ...
    const pkName = columns[0]?.primaryKeyConstraintName ? ... : ...
    // ...
    // Common logic to create new PK
    // ...
  }
}
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies duplicated code for handling primary key constraint name changes across multiple drivers and query runners, and proposing a refactor to a base class is a valid architectural improvement.

Medium
Possible issue
Add optional chaining for safety
Suggestion Impact:The commit updated the pkName assignment to use optional chaining on columns[0] when reading primaryKeyConstraintName, preventing a potential crash when columns is empty.

code diff:

-        const pkName = columns[0].primaryKeyConstraintName
+        const pkName = columns[0]?.primaryKeyConstraintName
             ? colu...

@qodo-free-for-open-source-projects
Copy link

qodo-free-for-open-source-projects bot commented Feb 14, 2026

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (2) 📎 Requirement gaps (0)

Grey Divider


Action required

✅ 1. console.log in functional test 📘 Rule violation ⛯ Reliability
Description
The new functional test logs generated queries via console.log, which adds noisy output to test
runs and is inconsistent with clean test expectations. This violates the requirement to avoid
AI-generated/noisy additions.
Code

test/functional/schema-builder/change-primary-key-constraint.test.ts[R60-63]

+                console.log(
+                    `[${dataSource.driver.options.type}] Generated Queries:`,
+                    upQueries,
+                )
Evidence
PR Compliance ID 4 requires removing noise; the added test prints to stdout unconditionally, which
is unnecessary for assertions and creates avoidable noise in CI logs.

Rule 4: Remove AI-generated noise
test/functional/schema-builder/change-primary-key-constraint.test.ts[60-63]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The functional test includes an unconditional `console.log`, creating noisy test output.
## Issue Context
Tests should rely on assertions; logs should not be added unless explicitly required and gated.
## Fix Focus Areas
- test/functional/schema-builder/change-primary-key-constraint.test.ts[60-63]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


✅ 2. PK rename fails with FKs 🐞 Bug ✓ Correctness
Description
SchemaBuilder now calls updatePrimaryKeys when only the PK constraint name changes, but
updatePrimaryKeys drops/re-adds the primary key constraint. On Postgres-family/Oracle/MSSQL this
commonly fails if any foreign keys reference the primary key, because those foreign keys are not
dropped before updatePrimaryKeys runs.
Code

src/schema-builder/RdbmsSchemaBuilder.ts[R856-871]

+            const pkNameChanged =
+                primaryTableColumns.length > 0 &&
+                primaryMetadataColumns.length > 0 &&
+                primaryTableColumns[0].primaryKeyConstraintName !==
+                    primaryMetadataColumns[0].primaryKeyConstraintName &&
+                (DriverUtils.isPostgresFamily(this.connection.driver) ||
+                    this.connection.driver.options.type === "mssql" ||
+                    this.connection.driver.options.type === "oracle")
+
    if (
-                primaryTableColumns.length !== primaryMetadataColumns.length &&
-                primaryMetadataColumns.length > 1
+                (primaryTableColumns.length !== primaryMetadataColumns.length &&
+                    primaryMetadataColumns.length > 1) ||
+                pkNameChanged
    ) {
        const changedPrimaryColumns = primaryMetadataColumns.map(
            (primaryMetadataColumn) => {
Evidence
Schema sync runs updatePrimaryKeys before updateExistColumns and before createForeignKeys, so
dependent FKs are still present when updatePrimaryKeys executes.
PostgresQueryRunner.updatePrimaryKeys issues ALTER TABLE ... DROP CONSTRAINT (no CASCADE), which
will error if referenced by existing foreign keys.

src/schema-builder/RdbmsSchemaBuilder.ts[218-240]
src/schema-builder/RdbmsSchemaBuilder.ts[849-885]
src/driver/postgres/PostgresQueryRunner.ts[2700-2727]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Primary key constraint renames are currently implemented by triggering `queryRunner.updatePrimaryKeys`, which drops and recreates the PK. This is likely to fail when the PK is referenced by foreign keys, because schema sync does not drop those foreign keys before running `updatePrimaryKeys`.
### Issue Context
- `RdbmsSchemaBuilder.executeSchemaSyncOperationsInProperOrder` runs `updatePrimaryKeys()` before `updateExistColumns()` and before `createForeignKeys()`.
- `updatePrimaryKeys` in Postgres-family drivers issues `ALTER TABLE ... DROP CONSTRAINT ...` (no CASCADE).
- There are already patterns in the codebase to **rename** PK constraints (e.g., Postgres/Cockroach/Oracle use `ALTER TABLE ... RENAME CONSTRAINT`, MSSQL uses `sp_rename`) that can be reused for PK-name-only changes.
### Fix Focus Areas
- src/schema-builder/RdbmsSchemaBuilder.ts[849-885]
- src/schema-builder/RdbmsSchemaBuilder.ts[218-240]
- src/driver/postgres/PostgresQueryRunner.ts[2688-2760]
- src/driver/cockroachdb/CockroachQueryRunner.ts[2414-2488]
- src/driver/oracle/OracleQueryRunner.ts[1823-1897]
- src/driver/sqlserver/SqlServerQueryRunner.ts[2208-2283]
- src/driver/postgres/PostgresQueryRunner.ts[800-833]
- src/driver/sqlserver/SqlServerQueryRunner.ts[876-909]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

✅ 3. JSDoc @param tag spam 📘 Rule violation ⛯ Reliability
Description
Multiple files add @param tags without any descriptions, which adds low-signal comment noise and
looks like automated boilerplate. This conflicts with the repo requirement to avoid AI-generated
slop/inconsistent style in changes.
Code

src/driver/cockroachdb/CockroachDriver.ts[R365-377]

/**
* Creates a query runner used to execute database queries.
+     * @param mode
*/
createQueryRunner(mode: ReplicationMode) {
return new CockroachQueryRunner(this, mode)
}
/**
* Prepares given value to a value to be persisted, based on its column type and metadata.
+     * @param value
+     * @param columnMetadata
*/
Evidence
PR Compliance ID 4 forbids introducing extra AI-like comments/noise. The PR adds many parameter-only
JSDoc lines (e.g., * @param mode, * @param value) across drivers without any descriptive text,
which is precisely extra comment noise.

Rule 4: Remove AI-generated noise
src/driver/cockroachdb/CockroachDriver.ts[365-377]
src/driver/postgres/PostgresDriver.ts[715-727]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The PR adds many JSDoc `@param` tags without descriptions, creating comment noise and inconsistent style.
## Issue Context
Compliance requires avoiding AI-generated slop/extra comments. Parameter-only tags add little value and were introduced broadly across driver files.
## Fix Focus Areas
- src/driver/cockroachdb/CockroachDriver.ts[365-377]
- src/driver/postgres/PostgresDriver.ts[715-727]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


✅ 4. PK rename triggers column pipeline 🐞 Bug ⛯ Reliability
Description
By adding primaryKeyConstraintName to findChangedColumns, a pure constraint rename is treated as a
column change. This routes PK renames through updateExistColumns, which drops referenced
FKs/indices/uniques and can generate noisy migrations and incorrect ordering relative to
updatePrimaryKeys.
Code

src/driver/postgres/PostgresDriver.ts[R1441-1442]

+                tableColumn.primaryKeyConstraintName !==
+                    columnMetadata.primaryKeyConstraintName ||
Evidence
Drivers now mark columns changed when only primaryKeyConstraintName differs. SchemaBuilder’s
updateExistColumns responds by dropping all foreign keys referencing that column and other composite
constraints, even though only a PK constraint rename is desired (and it happens after
updatePrimaryKeys).

src/driver/postgres/PostgresDriver.ts[1417-1444]
src/schema-builder/RdbmsSchemaBuilder.ts[900-937]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`primaryKeyConstraintName` is being compared inside `findChangedColumns`, which causes PK constraint renames to be processed as column changes. This triggers heavy operations in `updateExistColumns` (dropping referenced FKs/indices/uniques) and can produce noisy migrations with problematic ordering.
### Issue Context
PK constraint name changes are conceptually constraint-level changes, not column-level changes. There is already separate logic in `RdbmsSchemaBuilder.updatePrimaryKeys` for PK-related operations.
### Fix Focus Areas
- src/driver/postgres/PostgresDriver.ts[1417-1444]
- src/driver/cockroachdb/CockroachDriver.ts[937-971]
- src/driver/oracle/OracleDriver.ts[823-855]
- src/driver/sqlserver/SqlServerDriver.ts[827-860]
- src/schema-builder/RdbmsSchemaBuilder.ts[900-969]
- src/schema-builder/RdbmsSchemaBuilder.ts[849-885]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Advisory comments

✅ 5. No empty-array guard 🐞 Bug ⛯ Reliability
Description
Some updatePrimaryKeys implementations read columns[0] without validating columns.length, which can
throw if called with an empty array (even if current SchemaBuilder call sites avoid it).
Code

src/driver/cockroachdb/CockroachQueryRunner.ts[R2461-2465]

+        const pkName = columns[0].primaryKeyConstraintName
+            ? columns[0].primaryKeyConstraintName
    : this.connection.namingStrategy.primaryKeyName(
          clonedTable,
          columnNames,
Evidence
Cockroach/Oracle/SqlServer query runners directly access columns[0] when computing pkName. While
SchemaBuilder appears to pass non-empty arrays, this is a public QueryRunner method and would be
safer with validation.

src/driver/cockroachdb/CockroachQueryRunner.ts[2456-2466]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`updatePrimaryKeys` assumes `columns[0]` exists. If called with an empty `columns` array, it will throw with an unclear error.
### Issue Context
Even if current SchemaBuilder paths provide non-empty arrays, this is part of the QueryRunner API surface and should fail fast with a clear message.
### Fix Focus Areas
- src/driver/cockroachdb/CockroachQueryRunner.ts[2414-2488]
- src/driver/oracle/OracleQueryRunner.ts[1823-1897]
- src/driver/sqlserver/SqlServerQueryRunner.ts[2208-2283]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

@qodo-free-for-open-source-projects

Persistent review updated to latest commit f22f8a8

@qodo-free-for-open-source-projects

Persistent review updated to latest commit a643414

@qodo-free-for-open-source-projects

Persistent review updated to latest commit 42954c7

@qodo-free-for-open-source-projects

Persistent review updated to latest commit 9901bb4

@gioboa gioboa requested review from G0maa, alumni and naorpeled February 15, 2026 14:22
@coveralls
Copy link

coveralls commented Feb 15, 2026

Coverage Status

coverage: 81.217% (+0.07%) from 81.143%
when pulling a91d51d on gioboa:fix/11636
into fe6c072 on typeorm:master.

@qodo-free-for-open-source-projects

Persistent review updated to latest commit b08e73b

@qodo-free-for-open-source-projects

Persistent review updated to latest commit 303a0b0

@qodo-free-for-open-source-projects

Persistent review updated to latest commit d47979d

@qodo-free-for-open-source-projects

Persistent review updated to latest commit b5cadc3

@qodo-free-for-open-source-projects

Persistent review updated to latest commit a91d51d

@qodo-free-for-open-source-projects

Persistent review updated to latest commit 65b3e9d

Comment on lines +856 to +888
const effectiveOldPkName = primaryTableColumns[0]
?.primaryKeyConstraintName
? primaryTableColumns[0].primaryKeyConstraintName
: this.connection.namingStrategy.primaryKeyName(
table,
primaryTableColumns.map((column) => column.name),
)
const effectiveNewPkName = primaryMetadataColumns[0]
?.primaryKeyConstraintName
? primaryMetadataColumns[0].primaryKeyConstraintName
: this.connection.namingStrategy.primaryKeyName(
table,
primaryMetadataColumns.map(
(column) => column.databaseName,
),
)

const pkNameChanged =
primaryTableColumns.length > 0 &&
primaryMetadataColumns.length > 0 &&
primaryTableColumns.length === primaryMetadataColumns.length &&
effectiveOldPkName !== effectiveNewPkName &&
(DriverUtils.isPostgresFamily(this.connection.driver) ||
this.connection.driver.options.type === "mssql" ||
this.connection.driver.options.type === "oracle" ||
this.connection.driver.options.type === "cockroachdb")

if (
primaryTableColumns.length !== primaryMetadataColumns.length &&
primaryMetadataColumns.length > 1
(primaryTableColumns.length !== primaryMetadataColumns.length &&
primaryMetadataColumns.length > 1) ||
(primaryMetadataColumns.length === primaryTableColumns.length &&
primaryMetadataColumns.length > 0 &&
pkNameChanged)

Choose a reason for hiding this comment

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

Action required

1. Spurious rename migrations for existing dbs 🐞 Bug ✓ Correctness

The new pkNameChanged check in RdbmsSchemaBuilder.ts triggers a PK rename migration whenever the
DB's actual PK constraint name differs from what the entity specifies. When an existing DB has a PK
constraint whose name doesn't match the current naming strategy (e.g., created via a custom
migration), but the entity has no explicit primaryKeyConstraintName, pkNameChanged = true and an
unexpected rename migration is generated on the next schema sync.
Agent Prompt
## Issue description
The `pkNameChanged` condition in `RdbmsSchemaBuilder.updatePrimaryKeys()` fires whenever the DB's actual PK constraint name differs from what the entity specifies — even when the entity has NO explicit `primaryKeyConstraintName`. This causes unexpected rename migrations for any existing DB whose PK constraint name doesn't match the current naming strategy.

## Issue Context
In `loadTables`, `primaryKeyConstraintName` is only set when the DB name ≠ naming strategy name. So `effectiveOldPkName` can be a DB-side custom name while `effectiveNewPkName` is the naming strategy result, causing a false positive `pkNameChanged = true`.

The PR itself worked around this by adding explicit `primaryKeyConstraintName` to `FooWithSpecialChars.ts` to prevent the existing test from failing.

## Fix Focus Areas
- src/schema-builder/RdbmsSchemaBuilder.ts[856-888]
- test/github-issues/10955/entity/FooWithSpecialChars.ts[11-13]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Migrations don't consider primaryKeyConstraintName

2 participants