Skip to content

feat(core): add DataTypes.VARBINARY for variable-length binary columns#18215

Open
Aviel212 wants to merge 3 commits intosequelize:mainfrom
Aviel212:feat/varbinary-data-type
Open

feat(core): add DataTypes.VARBINARY for variable-length binary columns#18215
Aviel212 wants to merge 3 commits intosequelize:mainfrom
Aviel212:feat/varbinary-data-type

Conversation

@Aviel212
Copy link
Copy Markdown

@Aviel212 Aviel212 commented Apr 13, 2026

What

Adds DataTypes.VARBINARY(n) — a variable-length binary data type with a fixed maximum byte length.

Closes #5981

Why

Sequelize currently lacks a native VARBINARY type. Users who need efficient binary indexes (e.g. SHA-256 hashes, UUIDs stored as bytes, binary foreign account IDs) are forced to use BLOB or raw SQL types. BLOB cannot be directly indexed in MySQL, making VARBINARY the correct choice for indexable binary columns.

There is also a // TODO: add FIXED_BINARY & VAR_BINARY data types comment in the codebase right above the BLOB class — this PR addresses that TODO for the variable-length case.

How

  • New VARBINARY class in packages/core/src/abstract-dialect/data-types.ts
  • toSql() returns VARBINARY(n)
  • Accepts Buffer, string, Uint8Array, and ArrayBuffer values (same as BLOB)
  • Validates that length is a positive integer between 1 and 65535
  • supports.dataTypes.VARBINARY support flag added to the dialect interface
    • Enabled for: MySQL, MariaDB, MSSQL
    • All other dialects throw a clear UnsupportedDataTypeError at model definition time
  • Exported as DataTypes.VARBINARY and VarBinaryOptions type

Tests

Added to test/unit/data-types/binary-types.test.ts:

  • SQL output for mysql/mariadb/mssql (VARBINARY(n))
  • Unsupported dialect error for sqlite3/postgres/etc.
  • Constructor validation (rejects invalid lengths)
  • validate() method (accepts Buffer, string, Uint8Array; rejects numbers)

All 1741 unit tests pass.

Summary by CodeRabbit

  • New Features

    • Added VARBINARY data type for variable-length binary data (length 1–65535); accepts strings, Buffer, Uint8Array and ArrayBuffer inputs.
    • VARBINARY is available on MySQL, MariaDB and MSSQL dialects.
  • Tests

    • Added unit tests for VARBINARY behavior and updated ESM export test to account for the new export.
  • Chores

    • Exposed the VARBINARY options type in public exports.

Adds a new `VARBINARY(n)` data type for storing variable-length binary
data with a fixed maximum byte length. Unlike BLOB, VARBINARY can be
efficiently indexed in MySQL/MariaDB, making it suitable for binary
identifiers (hashes, UUIDs as bytes, foreign account IDs, etc.).

- `DataTypes.VARBINARY(n)` / `DataTypes.VARBINARY({ length: n })`
- Validates length is a positive integer between 1 and 65535
- Accepts Buffer, string, Uint8Array, and ArrayBuffer values
- Supported in: MySQL, MariaDB, MSSQL
- Unsupported dialects throw a clear error at model definition time
- Resolves the long-standing TODO comment above the BLOB class

Closes sequelize#5981
@Aviel212 Aviel212 requested a review from a team as a code owner April 13, 2026 12:48
@Aviel212 Aviel212 requested review from WikiRik and sdepold April 13, 2026 12:48
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 13, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bb0a5e4f-086c-40a9-bbd6-d44985212a8a

📥 Commits

Reviewing files that changed from the base of the PR and between 404bcbd and 992c923.

📒 Files selected for processing (1)
  • test/esm-named-exports.test.js

📝 Walkthrough

Walkthrough

Adds a VARBINARY data type to core, including options/validation, SQL generation, dialect support flag, exports, tests, and enables VARBINARY support in MySQL, MariaDB, and MSSQL dialects.

Changes

Cohort / File(s) Summary
Core VARBINARY Implementation
packages/core/src/abstract-dialect/data-types.ts
Adds VarBinaryOptions and VARBINARY class: constructor normalization and bounds checking (1–65535), dialect support check, SQL VARBINARY(<length>), value validation/sanitization/escaping, and bind-parameter handling.
Core Exports & Types
packages/core/src/data-types.ts, packages/core/src/index.d.ts
Exports invokable VARBINARY and re-exports VarBinaryOptions type.
Dialect Support Flag
packages/core/src/abstract-dialect/dialect.ts
Adds dataTypes.VARBINARY: boolean to DialectSupports and defaults to false.
Dialect Enablement
packages/mysql/src/dialect.ts, packages/mariadb/src/dialect.ts, packages/mssql/src/dialect.ts
Sets dataTypes.VARBINARY: true for MySQL, MariaDB, and MSSQL dialects.
Tests
packages/core/test/unit/data-types/binary-types.test.ts
Adds tests for DataTypes.VARBINARY: SQL generation per dialect, constructor length validation, and value validation behavior.
Misc Tests Update
test/esm-named-exports.test.js
Adds VARBINARY to CJS-only/unsupported static properties ignore list for ESM export checks.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • sdepold
  • WikiRik

Poem

🐇 I nibble bytes with tidy flair,
VARBINARY hops into the air,
Lengths checked true, buffers snug and neat,
Escaped and bound — a rabbit's treat! 🥕

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely describes the main change: adding a new VARBINARY data type to Sequelize's core for variable-length binary columns.
Linked Issues check ✅ Passed The PR fully implements the requirements from issue #5981: adds a native VARBINARY data type, validates length (1-65535), supports MySQL/MariaDB/MSSQL, and provides an indexable alternative to VARCHAR for binary data.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing VARBINARY support. No unrelated modifications to other features, styling, or non-VARBINARY functionality were introduced.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 OpenGrep (1.17.0)
test/esm-named-exports.test.js

┌──────────────┐
│ Opengrep CLI │
└──────────────┘

�[32m✔�[39m �[1mOpengrep OSS�[0m
�[32m✔�[39m Basic security coverage for first-party code vulnerabilities.

�[1m Loading rules from local config...�[0m


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.

Copy link
Copy Markdown
Contributor

@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 (2)
packages/core/test/unit/data-types/binary-types.test.ts (1)

121-127: Add an explicit ArrayBuffer happy-path assertion.

validate() accepts ArrayBuffer, but this success case is not directly asserted in the new test block.

Suggested test addition
   it('should not throw if `value` is a valid binary value', () => {
     const type = DataTypes.VARBINARY(32);

     expect(() => type.validate('foobar')).not.to.throw();
     expect(() => type.validate(Buffer.from('foobar'))).not.to.throw();
     expect(() => type.validate(new Uint8Array(4))).not.to.throw();
+    expect(() => type.validate(new ArrayBuffer(4))).not.to.throw();
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/test/unit/data-types/binary-types.test.ts` around lines 121 -
127, Add an explicit happy-path assertion that DataTypes.VARBINARY(32).validate
accepts an ArrayBuffer: update the test case inside the it('should not throw if
`value` is a valid binary value') block to include expect(() =>
type.validate(new ArrayBuffer(<appropriate length>))).not.to.throw(); — use
DataTypes.VARBINARY and the validate method so the ArrayBuffer case is directly
asserted alongside the existing Buffer and Uint8Array assertions.
packages/core/src/abstract-dialect/data-types.ts (1)

1837-1874: VARBINARY duplicates BLOB binary value handling logic.

validate, sanitize, escape, and getBindParamSql are duplicated almost verbatim from BLOB, which increases divergence risk.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/abstract-dialect/data-types.ts` around lines 1837 - 1874,
The VARBINARY methods validate, sanitize, escape, and getBindParamSql duplicate
BLOB logic; refactor by extracting shared helpers (e.g., validateBinaryValue,
sanitizeBinaryValue, escapeBinaryValue, sharedGetBindParamSql) or by having
VARBINARY delegate to the BLOB implementation so both types call the same
functions; update VARBINARY.validate, VARBINARY.sanitize, VARBINARY.escape, and
VARBINARY.getBindParamSql to invoke those shared helpers (or call BLOB's
methods) and remove the duplicate code.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/core/src/abstract-dialect/data-types.ts`:
- Around line 1818-1831: The VARBINARY class currently validates length up to
65535 but doesn't enforce MSSQL's 8000-byte limit; update the protected
_checkOptionSupport(dialect: AbstractDialect) method to, after verifying
support, detect MSSQL (e.g., via dialect.name or dialect.dialectName on the
provided AbstractDialect) and throw a TypeError if this.options.length > 8000
with a clear message that MSSQL VARBINARY(n) max is 8000, so SQL generation for
VARBINARY uses a valid length for that dialect.

---

Nitpick comments:
In `@packages/core/src/abstract-dialect/data-types.ts`:
- Around line 1837-1874: The VARBINARY methods validate, sanitize, escape, and
getBindParamSql duplicate BLOB logic; refactor by extracting shared helpers
(e.g., validateBinaryValue, sanitizeBinaryValue, escapeBinaryValue,
sharedGetBindParamSql) or by having VARBINARY delegate to the BLOB
implementation so both types call the same functions; update VARBINARY.validate,
VARBINARY.sanitize, VARBINARY.escape, and VARBINARY.getBindParamSql to invoke
those shared helpers (or call BLOB's methods) and remove the duplicate code.

In `@packages/core/test/unit/data-types/binary-types.test.ts`:
- Around line 121-127: Add an explicit happy-path assertion that
DataTypes.VARBINARY(32).validate accepts an ArrayBuffer: update the test case
inside the it('should not throw if `value` is a valid binary value') block to
include expect(() => type.validate(new ArrayBuffer(<appropriate
length>))).not.to.throw(); — use DataTypes.VARBINARY and the validate method so
the ArrayBuffer case is directly asserted alongside the existing Buffer and
Uint8Array assertions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d93b1cb2-dd1b-4953-9a58-491edb428b71

📥 Commits

Reviewing files that changed from the base of the PR and between ab2c9ad and 404bcbd.

📒 Files selected for processing (8)
  • packages/core/src/abstract-dialect/data-types.ts
  • packages/core/src/abstract-dialect/dialect.ts
  • packages/core/src/data-types.ts
  • packages/core/src/index.d.ts
  • packages/core/test/unit/data-types/binary-types.test.ts
  • packages/mariadb/src/dialect.ts
  • packages/mssql/src/dialect.ts
  • packages/mysql/src/dialect.ts

Comment on lines +1818 to +1831
if (!Number.isInteger(length) || length < 1 || length > 65_535) {
throw new TypeError(
'The "length" option of VARBINARY must be a positive integer between 1 and 65535.',
);
}

this.options = { length };
}

protected _checkOptionSupport(dialect: AbstractDialect) {
if (!dialect.supports.dataTypes.VARBINARY) {
throwUnsupportedDataType(dialect, 'VARBINARY');
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

What is the maximum allowed n for SQL Server VARBINARY(n), and how is larger binary data represented (e.g., VARBINARY(MAX))?

💡 Result:

The maximum allowed n for SQL Server VARBINARY(n) is 8000 bytes. Larger binary data is represented using VARBINARY(MAX), which supports up to 2^31-1 bytes (approximately 2 GB).

Citations:


🏁 Script executed:

# First, let's check the file size and read the relevant lines
wc -l packages/core/src/abstract-dialect/data-types.ts

Repository: sequelize/sequelize

Length of output: 116


🏁 Script executed:

# Read the snippet around lines 1818-1831
sed -n '1810,1840p' packages/core/src/abstract-dialect/data-types.ts

Repository: sequelize/sequelize

Length of output: 925


🏁 Script executed:

# Let's also look at the VARBINARY class definition and constructor
sed -n '1770,1835p' packages/core/src/abstract-dialect/data-types.ts

Repository: sequelize/sequelize

Length of output: 1964


🏁 Script executed:

# Check MSSQL dialect configuration for VARBINARY support
find packages -name "*.ts" -path "*mssql*dialect*" | head -5

Repository: sequelize/sequelize

Length of output: 127


🏁 Script executed:

# Search for BLOB implementation to understand duplication claim
rg -A 20 "class BLOB" packages/core/src/abstract-dialect/data-types.ts

Repository: sequelize/sequelize

Length of output: 643


🏁 Script executed:

# Check MSSQL dialect configuration for VARBINARY
cat packages/mssql/src/dialect.ts | grep -A 30 -B 5 "VARBINARY" | head -50

Repository: sequelize/sequelize

Length of output: 999


🏁 Script executed:

# Search for how VARBINARY is used in tests or implementation
rg "VARBINARY" packages/mssql/ -A 2 -B 2

Repository: sequelize/sequelize

Length of output: 1008


🏁 Script executed:

# Check if there's any existing dialect-specific length validation for other data types
rg "_checkOptionSupport" packages/core/src/abstract-dialect/data-types.ts -A 5 | head -60

Repository: sequelize/sequelize

Length of output: 2029


🏁 Script executed:

# Check if VARBINARY has MSSQL-specific overrides
cat packages/mssql/src/_internal/data-types-overrides.ts | grep -B 5 -A 20 "class.*VARBINARY\|VARBINARY"

Repository: sequelize/sequelize

Length of output: 725


🏁 Script executed:

# Get the full content of data-types-overrides to see what classes are overridden
head -100 packages/mssql/src/_internal/data-types-overrides.ts

Repository: sequelize/sequelize

Length of output: 2728


🏁 Script executed:

# Check how data-types-overrides are used in the MSSQL dialect
rg "data-types-overrides" packages/mssql/src/dialect.ts -B 2 -A 10

Repository: sequelize/sequelize

Length of output: 694


MSSQL length constraints are not validated for VARBINARY(n).

The constructor allows lengths up to 65535 globally, but MSSQL VARBINARY(n) has a maximum of 8000 bytes. This will generate invalid SQL for MSSQL when n exceeds 8000 (e.g., VARBINARY(65535)), as there is no MSSQL-specific override for the VARBINARY class.

The validation should be added to _checkOptionSupport:

  protected _checkOptionSupport(dialect: AbstractDialect) {
    if (!dialect.supports.dataTypes.VARBINARY) {
      throwUnsupportedDataType(dialect, 'VARBINARY');
    }
+
+   if (dialect.name === 'mssql' && this.options.length > 8_000) {
+     throw new TypeError(
+       'MSSQL supports VARBINARY(n) up to 8000 bytes. Use BLOB for larger payloads (mapped to VARBINARY(MAX) in MSSQL).',
+     );
+   }
  }
📝 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
if (!Number.isInteger(length) || length < 1 || length > 65_535) {
throw new TypeError(
'The "length" option of VARBINARY must be a positive integer between 1 and 65535.',
);
}
this.options = { length };
}
protected _checkOptionSupport(dialect: AbstractDialect) {
if (!dialect.supports.dataTypes.VARBINARY) {
throwUnsupportedDataType(dialect, 'VARBINARY');
}
}
if (!Number.isInteger(length) || length < 1 || length > 65_535) {
throw new TypeError(
'The "length" option of VARBINARY must be a positive integer between 1 and 65535.',
);
}
this.options = { length };
}
protected _checkOptionSupport(dialect: AbstractDialect) {
if (!dialect.supports.dataTypes.VARBINARY) {
throwUnsupportedDataType(dialect, 'VARBINARY');
}
if (dialect.name === 'mssql' && this.options.length > 8_000) {
throw new TypeError(
'MSSQL supports VARBINARY(n) up to 8000 bytes. Use BLOB for larger payloads (mapped to VARBINARY(MAX) in MSSQL).',
);
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/abstract-dialect/data-types.ts` around lines 1818 - 1831,
The VARBINARY class currently validates length up to 65535 but doesn't enforce
MSSQL's 8000-byte limit; update the protected _checkOptionSupport(dialect:
AbstractDialect) method to, after verifying support, detect MSSQL (e.g., via
dialect.name or dialect.dialectName on the provided AbstractDialect) and throw a
TypeError if this.options.length > 8000 with a clear message that MSSQL
VARBINARY(n) max is 8000, so SQL generation for VARBINARY uses a valid length
for that dialect.

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.

Need a VARBINARY data type

2 participants