Skip to content

Conversation

@youssef2272002salah
Copy link

@youssef2272002salah youssef2272002salah commented Oct 1, 2025

Pull Request Checklist

  • Have you added new tests to prevent regressions?
  • If a documentation update is necessary, have you opened a PR to the documentation repository?
  • Did you update the typescript typings accordingly (if applicable)?
  • Does the description below contain a link to an existing issue (Closes bulkInsert should return array #14325)?
  • Does the name of your PR follow our conventions?

Description of Changes

This PR ensures queryInterface.bulkInsert consistently returns an array of inserted IDs across mysql dialect, instead of a single value or undefined.

  • Updated bulkInsert implementation to return an array.

Previously:

await queryInterface.bulkInsert('companies', [
  { name: 'Example Company 1' },
  { name: 'Example Company 2' },
]);
// → 1

now:

await queryInterface.bulkInsert('companies', [
  { name: 'Example Company 1' },
  { name: 'Example Company 2' },
]);
// → [1, 2]

Closes #14325

List of Breaking Changes

  • None (the return type is extended, but still backward-compatible).

Summary by CodeRabbit

  • New Features

    • Bulk inserts now consistently return arrays of id objects for multi-row inserts in MySQL/MariaDB; single-row and empty inserts unchanged.
    • Insert ID types (number, string, BigInt) are preserved and normalized across both model and no-model insert paths.
  • Tests

    • Added integration tests validating bulkInsert return shapes across dialects and single-row behavior.
    • Expanded unit tests for result formatting and related logging.

@youssef2272002salah youssef2272002salah requested a review from a team as a code owner October 1, 2025 13:39
@coderabbitai
Copy link

coderabbitai bot commented Oct 1, 2025

Walkthrough

Modify MySQL and MariaDB insert result formatting so bulk inserts return consistent arrays of { id }-style objects derived from the driver's insertId and affectedRows; add unit tests for MySQL formatResults and an integration test for QueryInterface.bulkInsert.

Changes

Cohort / File(s) Summary
MySQL insert formatting
packages/mysql/src/query.js
Add convertInsertId and generateInsertResults; replace per-iteration ID construction with range-based generation to return an array of { <pkName>: value } for bulk inserts (with or without model), preserving original id types (Number, String, BigInt).
MariaDB insert formatting
packages/mariadb/src/query.js
Change no-model bulk insert path to coerce startId from data[this.getInsertIdField()] and build an array [{ id: startId }, ...] for affectedRows, returning the array instead of a single insertId.
Unit tests (MySQL)
packages/core/test/unit/dialects/mysql/query.test.js
Initialize query.sql in logWarnings test; add formatResults tests covering model-context bulk, no-model bulk, single-row, zero-rows, string insertId, and BigInt insertId, asserting returned arrays and preserved id types.
Integration tests (QueryInterface.bulkInsert)
packages/core/test/integration/query-interface/bulk-insert.test.ts
New integration suite creating/dropping UsersBulkInsert; tests bulkInsert(..., { returning: true }) expectations across dialects (MySQL/MariaDB -> IDs-only array; others -> full rows) and verifies single-row returns array.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant App
  participant QI as QueryInterface
  participant Conn as Dialect Connection
  participant Q as DialectQuery.formatResults

  App->>QI: bulkInsert(table, rows, { returning: true })
  QI->>Conn: Execute INSERT (bulk)
  Conn-->>QI: ResultSetHeader { insertId, affectedRows, ... }
  QI->>Q: formatResults(ResultSetHeader, options)
  alt model context present
    Q-->>QI: Array of inserted row objects (model-mapped/full rows)
  else no model context
    alt affectedRows > 1
      Q-->>QI: Array of `{ id: startId + n }` for n = 0..affectedRows-1
    else affectedRows == 1
      Q-->>QI: Array with single `{ id: startId }`
    else affectedRows == 0
      Q-->>QI: Empty array
    end
  end
  QI-->>App: Return dialect-specific result
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

Thump — I counted startId in the moonlight glow,
Each hop became a little id arranged in row.
Numbers, strings, BigInts kept tidy and neat,
From one to many, every id found its seat.
Hooray — bulk inserts lined up, all complete! 🐇

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Out of Scope Changes Check ⚠️ Warning This PR extends changes to the MariaDB dialect’s formatResults implementation and alters unrelated unit tests for logWarnings and integration tests for non-MySQL dialects, which exceed the original scope of fixing MySQL bulkInsert return values in issue #14325. These modifications are not directly required to meet the linked issue’s objective and introduce additional surface area beyond the targeted dialect and functionality. Consolidating unrelated changes risks conflating separate concerns and complicates the review and testing of the core MySQL fix. Separate the MariaDB dialect adjustments and unrelated logWarnings or non-MySQL integration test changes into distinct PRs or issues, focusing this PR solely on the MySQL bulkInsert return behavior to align with issue #14325.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title succinctly and accurately summarizes the primary change, which is updating bulkInsert in core to return an array of inserted IDs. It follows the conventional “fix(scope): description” style and conveys the main intent clearly without extraneous detail.
Linked Issues Check ✅ Passed The PR modifies MySQL dialect’s formatResults to build and return an array of inserted ID objects for multi-row bulkInsert operations as specified in issue [#14325], and adds corresponding unit and integration tests to verify array returns across scenarios. Tests cover various insertId types and affectedRows values, demonstrating that bulkInsert now aligns with the expected behavior of returning an array of new record IDs. Therefore, the supplied changes satisfy the linked issue’s primary requirement.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@WikiRik
Copy link
Member

WikiRik commented Oct 1, 2025

Please adjust the tests and probably add new ones. It could be that we have tests that only check for certain dialects, you'll need to update those expectations as well to include mysql

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
packages/core/test/unit/dialects/mysql/query.test.js (1)

39-45: Consider using Support.getTestDialectTeaser() for the test description.

According to the coding guidelines, dialect-specific tests should use Support.getTestDialectTeaser() in their descriptions. Since this test suite is specific to MySQL/MariaDB behavior, consider updating the describe block:

-  describe('formatResults', () => {
+  describe(Support.getTestDialectTeaser('MYSQL'), 'formatResults', () => {

However, since the parent describe block at line 12 already indicates [MYSQL/MARIADB Specific], this may be acceptable as-is.

As per coding guidelines

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6fc1751 and 7dd6276.

📒 Files selected for processing (1)
  • packages/core/test/unit/dialects/mysql/query.test.js (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
packages/**/test/**/*.test.js

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Name integration tests with the .test.js suffix

Files:

  • packages/core/test/unit/dialects/mysql/query.test.js
packages/**/test/**/*.test.{js,ts}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

packages/**/test/**/*.test.{js,ts}: Use Support.getTestDialectTeaser() for dialect-specific test descriptions
Use beforeEach/afterEach to sync with { force: true } and close Sequelize instances in tests
In tests, skip unsupported behavior with dialect.supports.featureName checks

Files:

  • packages/core/test/unit/dialects/mysql/query.test.js
🧬 Code graph analysis (1)
packages/core/test/unit/dialects/mysql/query.test.js (1)
packages/mysql/src/query.js (1)
  • MySqlQuery (25-324)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Upload install and build artifact (Node 18)
  • GitHub Check: Upload install and build artifact (Node 20)
🔇 Additional comments (4)
packages/core/test/unit/dialects/mysql/query.test.js (4)

28-36: LGTM!

The explicit assignment of query.sql ensures the query object is properly initialized for the test, and the updated assertions correctly verify both the return value and the console.debug invocation.


46-64: LGTM!

This test correctly validates the model context path for multi-row inserts, ensuring that when a model is present with an auto-increment primary key, formatResults returns an array of ID objects ([{ id: 10 }, { id: 11 }, { id: 12 }]). The test setup and assertions align with the implementation in packages/mysql/src/query.js.


66-78: LGTM!

This test validates the core PR change: when there's no model context and multiple rows are inserted, formatResults returns an array of ID objects ([{ id: 20 }, { id: 21 }]). This directly addresses issue #14325 and aligns with the implementation in packages/mysql/src/query.js.


80-92: LGTM!

This test ensures backward compatibility by validating that single-row inserts (affectedRows: 1) continue to return a scalar ID value (30) rather than an array. This confirms the PR change doesn't break existing behavior for single-row inserts.

@youssef2272002salah
Copy link
Author

Hi , it looks like the postgres oldest (Node 18) CI job failed due to a network issue while downloading dependencies (connection reset by peer).
All other CI checks have passed . Could someone please retry this job when convenient? Thanks!

@youssef2272002salah
Copy link
Author

Hi @WikiRik
I’ve addressed the feedback and updated the tests. CI checks are passed except one falls due to netwok issue.
Could you please take another look when you have time? Thanks a lot for your guidance

Copy link
Member

@WikiRik WikiRik left a comment

Choose a reason for hiding this comment

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

Unfortunately we do not have a lot of tests for bulkInsert (or unified unit tests for bulkInsertQuery) so we should focus on that first. That's why I only gave comments to that file, especially with the returning option

Main thing to pay attention to is this comment; #14325 (comment)

Copy link
Member

Choose a reason for hiding this comment

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

New test files should be written in TypeScript.


const dialect = Support.getTestDialect();

describe(Support.getTestDialectTeaser('QueryInterface'), () => {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
describe(Support.getTestDialectTeaser('QueryInterface'), () => {
describe('QueryInterface#bulkInsert', () => {
beforeEach(function () {
this.queryInterface = this.sequelize.queryInterface;
});

This way we avoid another level of nesting

Comment on lines 33 to 35
if (!['mysql'].includes(dialect)) {
this.skip();
}
Copy link
Member

Choose a reason for hiding this comment

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

This should run on all dialects, and also give the same result

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (4)
packages/core/test/integration/query-interface/bulk-insert.test.ts (1)

33-33: Use Support.getTestDialectTeaser() for dialect-specific test descriptions.

The coding guidelines specify using Support.getTestDialectTeaser() for dialect-specific test descriptions. This helper provides consistent formatting and makes it clear when tests have dialect-specific behavior.

Based on coding guidelines.

Apply this diff:

-  it('should insert multiple rows and return inserted data according to dialect', async function testBulkInsert() {
+  it(`${Support.getTestDialectTeaser('should insert multiple rows and return inserted data')}`, async function testBulkInsert() {
packages/mariadb/src/query.js (3)

125-137: Add warning comment about sequential ID assumption.

The code assumes that IDs will be sequential (startId, startId+1, startId+2, etc.), similar to the model-context path (lines 107-123). However, the existing code includes an important warning (lines 111-112) that this only works when @auto_increment_increment is set to 1 and doesn't work with GALERA clusters. This same warning should be present for the non-model-context path.

Apply this diff to add the warning comment:

         // No model context, in queryinterface,bulkinsert for example
+        // ONLY TRUE IF @auto_increment_increment is set to 1 !!
+        // Doesn't work with GALERA => each node will reserve increment (x for first server, x+1 for next node...)
         const startId = Number(data[this.getInsertIdField()]);
         if (data.affectedRows > 1) {

126-126: Verify type consistency: Number() vs BigInt arithmetic.

Line 126 uses Number() to convert startId, while the model-context path (line 119) uses BigInt arithmetic (startId + i where i is BigInt). If the database uses large ID values exceeding Number.MAX_SAFE_INTEGER (2^53 - 1), this could cause precision loss.

For consistency and to handle large IDs safely, consider using BigInt here as well, especially since the model-context path already does so.

Apply this diff if BigInt consistency is desired:

-        const startId = Number(data[this.getInsertIdField()]);
+        const startId = BigInt(data[this.getInsertIdField()]);
         if (data.affectedRows > 1) {
           result = [];
-          for (let i = startId; i < startId + data.affectedRows; i++) {
+          for (let i = 0n; i < data.affectedRows; i++) {
-            result.push({ id: i });
+            result.push({ id: Number(startId + i) });
           }
 
           return [result, data.affectedRows];
         }
 
         // Fallback: single insert ID
-        return [startId, data.affectedRows];
+        return [Number(startId), data.affectedRows];

127-137: Consider return type consistency for single-row inserts.

When multiple rows are affected (lines 128-134), the function returns an array of objects like [{id: 1}, {id: 2}]. However, for single-row inserts (line 137), it returns a bare number directly: [startId, data.affectedRows].

This inconsistency means callers must handle two different shapes:

  • Multi-row: [[{id: 1}, {id: 2}], 2]
  • Single-row: [1, 1]

For better consistency, consider wrapping the single-row ID in an object as well: [{id: startId}, data.affectedRows]. This would make the return shape predictable regardless of the number of rows inserted.

Apply this diff if consistent return shape is desired:

         // Fallback: single insert ID
-        return [startId, data.affectedRows];
+        return [{ id: startId }, data.affectedRows];

Note: This change would need corresponding updates in callers and tests that expect the bare number for single-row inserts.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2535720 and 180db45.

📒 Files selected for processing (2)
  • packages/core/test/integration/query-interface/bulk-insert.test.ts (1 hunks)
  • packages/mariadb/src/query.js (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
packages/**/test/**/*.test.ts

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Name unit tests with the .test.ts suffix

Files:

  • packages/core/test/integration/query-interface/bulk-insert.test.ts
packages/**/test/**/*.test.{js,ts}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

packages/**/test/**/*.test.{js,ts}: Use Support.getTestDialectTeaser() for dialect-specific test descriptions
Use beforeEach/afterEach to sync with { force: true } and close Sequelize instances in tests
In tests, skip unsupported behavior with dialect.supports.featureName checks

Files:

  • packages/core/test/integration/query-interface/bulk-insert.test.ts
packages/**/src/**/*.js

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Write all new implementations in TypeScript; avoid creating new .js files in src

Files:

  • packages/mariadb/src/query.js
🪛 Biome (2.1.2)
packages/core/test/integration/query-interface/bulk-insert.test.ts

[error] 18-27: Disallow duplicate setup and teardown hooks.

Disallow beforeEach duplicacy inside the describe function.

(lint/suspicious/noDuplicateTestHooks)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Upload install and build artifact (Node 20)
  • GitHub Check: Upload install and build artifact (Node 18)
🔇 Additional comments (1)
packages/core/test/integration/query-interface/bulk-insert.test.ts (1)

40-50: bulkInsert returning behavior is correct across all dialects Verified supports.returnValues and query generators: mysql/mariadb return only IDs; all other dialects return full rows when returning: true.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
packages/core/test/integration/query-interface/bulk-insert.test.ts (1)

31-49: Add a test case for single-row insert to verify array return.

The multi-row test correctly validates the new behavior, but the PR context indicates that previously, inserting 2 rows returned a scalar 1 instead of [1, 2]. To prevent regression, add a test case that inserts a single row and asserts the result is still an array (e.g., [{ id: 1 }] for MySQL/MariaDB), not a scalar.

Add this test case after the existing test:

it('should return an array even when inserting a single row', async function testSingleRowInsert() {
  const result = await this.queryInterface.bulkInsert('UsersBulkInsert', [{ name: 'SingleUser' }], {
    returning: true,
  });

  if (['mysql', 'mariadb'].includes(dialect)) {
    expect(result).to.deep.equal([{ id: 1 }]);
  } else {
    expect(result).to.deep.equal([{ id: 1, name: 'SingleUser' }]);
  }
});
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 180db45 and b0a9173.

📒 Files selected for processing (1)
  • packages/core/test/integration/query-interface/bulk-insert.test.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
packages/**/test/**/*.test.ts

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Name unit tests with the .test.ts suffix

Files:

  • packages/core/test/integration/query-interface/bulk-insert.test.ts
packages/**/test/**/*.test.{js,ts}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

packages/**/test/**/*.test.{js,ts}: Use Support.getTestDialectTeaser() for dialect-specific test descriptions
Use beforeEach/afterEach to sync with { force: true } and close Sequelize instances in tests
In tests, skip unsupported behavior with dialect.supports.featureName checks

Files:

  • packages/core/test/integration/query-interface/bulk-insert.test.ts
🧠 Learnings (1)
📚 Learning: 2025-09-29T19:49:24.715Z
Learnt from: CR
PR: sequelize/sequelize#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-29T19:49:24.715Z
Learning: Applies to packages/**/test/**/*.test.{js,ts} : Use beforeEach/afterEach to sync with { force: true } and close Sequelize instances in tests

Applied to files:

  • packages/core/test/integration/query-interface/bulk-insert.test.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Upload install and build artifact (Node 18)
  • GitHub Check: Upload install and build artifact (Node 20)
🔇 Additional comments (3)
packages/core/test/integration/query-interface/bulk-insert.test.ts (3)

1-12: LGTM!

The imports and setup are correct. Using Support.getTestDialect() to capture the current dialect for conditional assertions is appropriate.


14-25: LGTM! Duplicate beforeEach issue resolved.

The setup hook correctly consolidates both queryInterface initialization and table creation in a single beforeEach, addressing the duplicate-hook concern from the previous review.


27-29: LGTM!

The teardown properly drops the test table after each test, following the coding guidelines for test cleanup.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
packages/core/test/integration/query-interface/bulk-insert.test.ts (1)

5-9: Consider using ES6 imports for consistency.

The coding guidelines state "Write all new implementations in TypeScript" for files in packages/**/src/**/*.js, but this is a test file (.test.ts). While require() works, ES6 imports would be more consistent with TypeScript best practices.

Consider applying this diff:

-const chai = require('chai');
-
-const expect = chai.expect;
-const Support = require('../support');
-const { DataTypes } = require('@sequelize/core');
+import chai from 'chai';
+import Support from '../support';
+import { DataTypes } from '@sequelize/core';
+
+const expect = chai.expect;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b0a9173 and 9a1bf64.

📒 Files selected for processing (3)
  • packages/core/test/integration/query-interface/bulk-insert.test.ts (1 hunks)
  • packages/mariadb/src/query.js (1 hunks)
  • packages/mysql/src/query.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/mysql/src/query.js
🧰 Additional context used
📓 Path-based instructions (3)
packages/**/src/**/*.js

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Write all new implementations in TypeScript; avoid creating new .js files in src

Files:

  • packages/mariadb/src/query.js
packages/**/test/**/*.test.ts

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Name unit tests with the .test.ts suffix

Files:

  • packages/core/test/integration/query-interface/bulk-insert.test.ts
packages/**/test/**/*.test.{js,ts}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

packages/**/test/**/*.test.{js,ts}: Use Support.getTestDialectTeaser() for dialect-specific test descriptions
Use beforeEach/afterEach to sync with { force: true } and close Sequelize instances in tests
In tests, skip unsupported behavior with dialect.supports.featureName checks

Files:

  • packages/core/test/integration/query-interface/bulk-insert.test.ts
🧠 Learnings (1)
📚 Learning: 2025-09-29T19:49:24.715Z
Learnt from: CR
PR: sequelize/sequelize#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-29T19:49:24.715Z
Learning: Applies to packages/**/test/**/*.test.{js,ts} : Use beforeEach/afterEach to sync with { force: true } and close Sequelize instances in tests

Applied to files:

  • packages/core/test/integration/query-interface/bulk-insert.test.ts
🔇 Additional comments (3)
packages/mariadb/src/query.js (1)

126-132: Logic correctly generates sequential ID array.

The implementation correctly generates an array of sequential ID objects for bulk inserts without model context, aligning with the PR objective and MySQL dialect behavior.

packages/core/test/integration/query-interface/bulk-insert.test.ts (2)

31-65: Test coverage looks comprehensive.

The tests adequately cover both multi-row and single-row scenarios, with correct dialect-specific expectations that align with the PR objectives (MySQL/MariaDB return ID arrays, other dialects return full rows).


27-29: No manual Sequelize cleanup needed in this test
Integration support’s afterEach resets the database and closes any test-created instances; this test only uses the shared sequelize instance, which should remain open.

Likely an incorrect or invalid review comment.

}

return [data[this.getInsertIdField()], data.affectedRows];
// No model context, in queryinterface,bulkinsert for example
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix the comment formatting.

The comment has a minor formatting issue: "queryinterface,bulkinsert" should be "queryInterface.bulkInsert" (add space after comma and proper camelCase).

Apply this diff:

-        // No model context, in queryinterface,bulkinsert for example
+        // No model context, in queryInterface.bulkInsert for example
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// No model context, in queryinterface,bulkinsert for example
// No model context, in queryInterface.bulkInsert for example
🤖 Prompt for AI Agents
In packages/mariadb/src/query.js around line 125, the inline comment "No model
context, in queryinterface,bulkinsert for example" has formatting issues; update
it to read "No model context, in queryInterface.bulkInsert for example" (fix
camelCase and add period/space after comma) so it uses proper camelCase and
punctuation.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
packages/core/test/unit/dialects/mysql/query.test.js (1)

46-64: LGTM!

The test correctly validates the formatResults behavior for bulk inserts with a model context. It properly verifies that an array of ID objects is generated from the insertId through affectedRows.

Consider adding tests for edge cases:

  • affectedRows: 0 (no rows inserted)
  • String insertId values (the implementation has special handling: typeof startId === 'string' ? i.toString() : Number(i))
  • Very large insertId values where BigInt handling is relevant
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9a1bf64 and fe5666c.

📒 Files selected for processing (2)
  • packages/core/test/integration/query-interface/bulk-insert.test.ts (1 hunks)
  • packages/core/test/unit/dialects/mysql/query.test.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/core/test/integration/query-interface/bulk-insert.test.ts
🧰 Additional context used
📓 Path-based instructions (2)
packages/**/test/**/*.test.js

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Name integration tests with the .test.js suffix

Files:

  • packages/core/test/unit/dialects/mysql/query.test.js
packages/**/test/**/*.test.{js,ts}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

packages/**/test/**/*.test.{js,ts}: Use Support.getTestDialectTeaser() for dialect-specific test descriptions
Use beforeEach/afterEach to sync with { force: true } and close Sequelize instances in tests
In tests, skip unsupported behavior with dialect.supports.featureName checks

Files:

  • packages/core/test/unit/dialects/mysql/query.test.js
🧬 Code graph analysis (1)
packages/core/test/unit/dialects/mysql/query.test.js (1)
packages/mysql/src/query.js (1)
  • MySqlQuery (25-322)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Upload install and build artifact (Node 18)
  • GitHub Check: Upload install and build artifact (Node 20)
🔇 Additional comments (4)
packages/core/test/unit/dialects/mysql/query.test.js (4)

28-36: LGTM!

The improvements to the logWarnings test are correct. Setting query.sql ensures the query instance is properly initialized, and the updated assertions now verify the actual behavior (that results are returned unchanged and console.debug is called) rather than just checking stub calls.


39-45: LGTM!

The test suite setup correctly initializes a query instance with an INSERT SQL statement for each test. This ensures isInsertQuery() returns true and provides a clean state for testing the formatResults behavior.


66-78: LGTM!

This test validates the core fix from the PR: when there's no model context, formatResults now returns an array of ID objects (e.g., [{ id: 20 }, { id: 21 }]) instead of a single insertId. This aligns with the expected behavior described in issue #14325.


80-92: LGTM!

This test correctly validates that even single-row inserts return an array format ([{ id: 30 }]) for consistency. This ensures the return type is always an array, avoiding the need for callers to handle special cases.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
packages/mysql/src/query.js (1)

146-169: Implementation looks correct; consider reducing code duplication.

The no-model bulk insert logic correctly generates an array of { id } objects from startId to startId + affectedRows - 1, which addresses the core issue in #14325.

However, there are opportunities for improvement:

  1. Redundant branches (lines 161-165): Same issue as the model case—both the isString and else branches perform identical conversions.

  2. Code duplication: The type detection and conversion logic is duplicated between the model case (lines 119-144) and this no-model case. Consider extracting a helper function to reduce duplication.

  3. Inconsistent increment style (line 156): Uses i++ instead of i = i + 1n as in line 128. While both are valid for BigInt, consistency improves readability.

Apply this diff to simplify and improve consistency:

-          const start = isBigInt ? startId : BigInt(startId);
-          const end = start + BigInt(data.affectedRows);
+          const start = isBigInt ? startId : BigInt(startId);
+          const end = start + BigInt(data.affectedRows);

           for (let i = start; i < end; i++) {
-            let value;
-            if (isBigInt) {
-              value = i;
-            } // keep BigInt intact
-            else if (isString) {
-              value = Number(i);
-            } else {
-              value = Number(i);
-            }
+            const value = isBigInt ? i : Number(i);

             result.push({ id: value });
           }

Optional: Extract a shared helper function to eliminate duplication between lines 119-144 and 146-169:

/**
 * Converts a BigInt loop variable to the appropriate type based on the original startId type.
 * @param {bigint} value - The BigInt value to convert
 * @param {boolean} isBigInt - Whether the original startId was a BigInt
 * @returns {bigint|number} The converted value
 */
function convertInsertId(value, isBigInt) {
  return isBigInt ? value : Number(value);
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fe5666c and 2c227c2.

📒 Files selected for processing (2)
  • packages/core/test/unit/dialects/mysql/query.test.js (1 hunks)
  • packages/mysql/src/query.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/core/test/unit/dialects/mysql/query.test.js
🧰 Additional context used
📓 Path-based instructions (1)
packages/**/src/**/*.js

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Write all new implementations in TypeScript; avoid creating new .js files in src

Files:

  • packages/mysql/src/query.js
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Upload install and build artifact (Node 18)
  • GitHub Check: Upload install and build artifact (Node 20)
🔇 Additional comments (1)
packages/mysql/src/query.js (1)

119-144: Simplify redundant type conversion and verify string insertId handling.

Merge the isString and else branches since both use Number(i):

-          let value;
-          if (isBigInt) {
-            value = i;
-          } else if (isString) {
-            value = Number(i);
-          } else {
-            value = Number(i);
-          }
+          const value = isBigInt ? i : Number(i);

Confirm whether your MySQL client can return insertId as a string (and if IDs can exceed Number.MAX_SAFE_INTEGER); if so, you may need to preserve the string or use BigInt to avoid precision loss.

@youssef2272002salah youssef2272002salah force-pushed the fix/bulk-insert-return-array branch from e0a7c70 to 650a1d2 Compare October 10, 2025 09:15
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
packages/mysql/src/query.js (1)

121-140: Consider adding safeguards for edge cases.

The generateInsertResults function is well-structured, but there are a few edge cases to consider:

  1. Large numbers and precision loss: When converting BigInt to Number (line 118), values larger than Number.MAX_SAFE_INTEGER (9007199254740991) will lose precision. While this is an expected JavaScript limitation, it could lead to incorrect IDs for very large sequences.

  2. Negative or invalid affectedRows: If affectedRows is negative or NaN, the loop will not execute and return an empty array, which may not reflect the actual error state.

  3. Performance: For very large affectedRows values (e.g., 100,000+), the loop could impact performance. Consider whether a warning or optimization is needed for bulk operations.

Consider adding input validation at the beginning of the function:

 const generateInsertResults = (startId, affectedRows, keyName) => {
+  if (affectedRows < 0 || !Number.isFinite(affectedRows)) {
+    throw new Error(`Invalid affectedRows value: ${affectedRows}`);
+  }
+
   const isBigInt = typeof startId === 'bigint';
   const start = isBigInt ? startId : BigInt(startId);
   const end = start + BigInt(affectedRows);

Alternatively, verify if upstream code (e.g., the database driver) guarantees affectedRows is always a valid non-negative integer.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b04dc8c and 650a1d2.

📒 Files selected for processing (4)
  • packages/core/test/integration/query-interface/bulk-insert.test.ts (1 hunks)
  • packages/core/test/unit/dialects/mysql/query.test.js (1 hunks)
  • packages/mariadb/src/query.js (1 hunks)
  • packages/mysql/src/query.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/mariadb/src/query.js
  • packages/core/test/integration/query-interface/bulk-insert.test.ts
🧰 Additional context used
📓 Path-based instructions (3)
packages/**/test/**/*.test.js

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Name integration tests with the .test.js suffix

Files:

  • packages/core/test/unit/dialects/mysql/query.test.js
packages/**/test/**/*.test.{js,ts}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

packages/**/test/**/*.test.{js,ts}: Use Support.getTestDialectTeaser() for dialect-specific test descriptions
Use beforeEach/afterEach to sync with { force: true } and close Sequelize instances in tests
In tests, skip unsupported behavior with dialect.supports.featureName checks

Files:

  • packages/core/test/unit/dialects/mysql/query.test.js
packages/**/src/**/*.js

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Write all new implementations in TypeScript; avoid creating new .js files in src

Files:

  • packages/mysql/src/query.js
🧬 Code graph analysis (1)
packages/core/test/unit/dialects/mysql/query.test.js (1)
packages/mysql/src/query.js (1)
  • MySqlQuery (25-340)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Upload install and build artifact (Node 20)
  • GitHub Check: Upload install and build artifact (Node 18)
🔇 Additional comments (9)
packages/mysql/src/query.js (2)

110-119: LGTM!

The helper function correctly handles type conversion based on the original startId type. The implementation is clean and straightforward.


142-156: No issues with Case 2 conditional
Case 1 covers all bulkCreate cases where autoIncrementAttributeName matches the model’s PK; Case 2 only runs for raw bulkInsert (no model context). No unintended scenarios detected.

packages/core/test/unit/dialects/mysql/query.test.js (7)

28-36: LGTM!

The test correctly initializes query.sql before execution and updates assertions to verify both the return value and the console.debug invocation.


46-64: LGTM!

The test correctly validates bulkInsert with model context, ensuring the array contains consecutive IDs starting from insertId (10, 11, 12).


66-78: LGTM!

The test correctly validates bulkInsert without model context, ensuring the generic 'id' key is used in the result array.


80-92: LGTM!

The test correctly validates single-row insertion behavior, ensuring the result is still an array with one element.


94-105: LGTM!

The test correctly handles the edge case of zero affected rows, returning an empty array as expected.


107-118: LGTM!

The test correctly validates that string insertId values are converted to numbers in the result array (50, 51, 52).


120-134: LGTM!

The test correctly validates BigInt handling by ensuring the result preserves BigInt type for IDs larger than Number.MAX_SAFE_INTEGER. The use of BigInt literals with underscores (e.g., 9_007_199_254_740_991n) is valid and improves readability.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/mysql/src/query.js (1)

110-160: Consider TypeScript migration for new implementations.

The coding guidelines state: "Write all new implementations in TypeScript; avoid creating new .js files in src." While this PR modifies an existing .js file rather than creating a new one, the new helper functions (convertInsertId and generateInsertResults) represent new implementations. Consider migrating this file to TypeScript in a future PR to align with the guidelines and benefit from type safety.

As per coding guidelines.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 650a1d2 and a409cd7.

📒 Files selected for processing (1)
  • packages/mysql/src/query.js (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
packages/**/src/**/*.js

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Write all new implementations in TypeScript; avoid creating new .js files in src

Files:

  • packages/mysql/src/query.js
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Upload install and build artifact (Node 20)
  • GitHub Check: Upload install and build artifact (Node 18)
🔇 Additional comments (4)
packages/mysql/src/query.js (4)

110-119: LGTM! Clean type conversion helper.

The convertInsertId function correctly preserves the original type of the insert ID (BigInt vs Number) during loop iteration.


134-143: Correct BigInt arithmetic and type handling.

The function properly normalizes IDs to BigInt for loop arithmetic, then restores the original type via convertInsertId. For very large affectedRows (e.g., > 100k), consider whether sequential ID generation in JavaScript remains performant, though this is unlikely to be a practical concern in most scenarios.


146-154: Verify that startId is always defined for auto-increment inserts.

Case 1 retrieves startId from data[this.getInsertIdField()] and passes it to generateInsertResults without validation. If the database doesn't return an insert ID (e.g., on certain error conditions or table configurations), startId will be undefined, causing a runtime error when generateInsertResults attempts BigInt(undefined).

Confirm that:

  1. MySQL/MariaDB always return a valid insertId when autoIncrementAttributeName is set.
  2. Or add a guard clause before calling generateInsertResults:
const startId = data[this.getInsertIdField()];
if (startId == null) {
  // Handle missing startId case - perhaps return empty array or original result
  result = [];
} else {
  const keyName = modelDefinition.getColumnName(this.model.primaryKeyAttribute);
  result = generateInsertResults(startId, data.affectedRows, keyName);
}

156-160: Verify that startId is always defined for bulkInsert without model.

Case 2 retrieves startId without validation. If the insert operation doesn't return an insertId (e.g., inserting into a table without auto-increment), this will cause the same runtime error mentioned in the previous comment.

Consider the same validation approach as suggested for Case 1:

const startId = data[this.getInsertIdField()];
if (startId == null) {
  result = [];
} else {
  result = generateInsertResults(startId, data.affectedRows, 'id');
}

Comment on lines +129 to +132
const generateInsertResults = (startId, affectedRows, keyName) => {
if (affectedRows < 0 || !Number.isFinite(affectedRows)) {
throw new Error(`Invalid affectedRows value: ${affectedRows}`);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Add validation for startId to prevent runtime errors.

The function validates affectedRows but doesn't check if startId is valid before converting it to BigInt. If startId is undefined, null, or an invalid value (e.g., when no rows are inserted or the table lacks auto-increment), BigInt(startId) on line 135 will throw a TypeError.

Apply this diff to add startId validation:

 const generateInsertResults = (startId, affectedRows, keyName) => {
+  if (startId == null) {
+    throw new Error(`Invalid startId value: ${startId}`);
+  }
   if (affectedRows < 0 || !Number.isFinite(affectedRows)) {
     throw new Error(`Invalid affectedRows value: ${affectedRows}`);
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const generateInsertResults = (startId, affectedRows, keyName) => {
if (affectedRows < 0 || !Number.isFinite(affectedRows)) {
throw new Error(`Invalid affectedRows value: ${affectedRows}`);
}
const generateInsertResults = (startId, affectedRows, keyName) => {
if (startId == null) {
throw new Error(`Invalid startId value: ${startId}`);
}
if (affectedRows < 0 || !Number.isFinite(affectedRows)) {
throw new Error(`Invalid affectedRows value: ${affectedRows}`);
}
🤖 Prompt for AI Agents
In packages/mysql/src/query.js around lines 129 to 132, the
generateInsertResults helper validates affectedRows but does not validate
startId before calling BigInt(startId), which can throw if startId is undefined,
null, or not a numeric string; add a validation that startId is neither null nor
undefined and is a safe integer or a numeric string (e.g., typeof startId ===
'number' && Number.isFinite(startId) or a string matching /^\d+$/) before
converting to BigInt, and if invalid throw a clear Error like "Invalid startId
value: <value>"; then proceed to convert to BigInt and compute results as
before.

@youssef2272002salah
Copy link
Author

Hi @WikiRik @sdepold , just checking in — is there anything blocking this PR or anything I should update?
Thanks a lot for your time!

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.

bulkInsert should return array

2 participants