Skip to content

feat: Add the ability to name an enum to allow for sharing it between multiple tables#18223

Open
papandreou wants to merge 19 commits into
sequelize:mainfrom
papandreou:feature/enumName
Open

feat: Add the ability to name an enum to allow for sharing it between multiple tables#18223
papandreou wants to merge 19 commits into
sequelize:mainfrom
papandreou:feature/enumName

Conversation

@papandreou
Copy link
Copy Markdown
Contributor

@papandreou papandreou commented May 9, 2026

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 #[issue]) or a description of the issue you are solving?
  • Does the name of your PR follow our conventions?

Description of Changes

PostgreSQL enum types are named database objects. Sequelize has always auto-generated those names as enum_<table>_<column>, which works fine for a single column but makes it impossible to share one enum type across multiple tables, or to place it in a schema that differs from the table's schema. This PR adds name and schema options to DataTypes.ENUM to address both cases.

Closes #2577

List of Breaking Changes

Summary by CodeRabbit

  • New Features
    • PostgreSQL ENUMs now accept custom type names (name) and custom schemas (schema); named enums (including ARRAY-of-ENUM) are created before use and change-column casts reference qualified enum types.
  • Bug Fixes / Behavior
    • Change-column and drop-table operations target qualified enum types and avoid dropping shared named enums; warnings emitted when shared named enums diverge.
  • Tests
    • Expanded coverage for custom names/schemas, SQL generation, lifecycle cleanup, and validation of empty name/schema.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 9, 2026

📝 Walkthrough

Walkthrough

Adds Postgres enum naming support: DataTypes.ENUM accepts optional name and schema. Core enum parsing refactored to return values + options. Postgres query-generator and query-interface are updated to honor explicit enum names/schemas for creation, listing, altering, DDL emission, and conditional dropping. Tests expanded accordingly.

Changes

Postgres ENUM Custom Naming Support

Layer / File(s) Summary
Data Type Contracts
packages/core/src/abstract-dialect/data-types.ts
EnumOptions and NormalizedEnumOptions add optional name and schema. ENUM now uses protected _getEnumValues() returning both values and an options bag.
Postgres Type Declarations
packages/postgres/src/query-generator.d.ts
PgEnumNameOptions extended with enumName and enumSchema.
Data Type Implementation
packages/postgres/src/_internal/data-types-overrides.ts
ENUM.toSql() forwards name/schema as enumName/enumSchema to pgEnumName.
Query Generator Methods
packages/postgres/src/query-generator.js
attributeToSQL emits qualified identifiers or ENUM_NAMED(...) for custom-named enums; addColumnQuery prepends pgEnum creation for named enums (and array-of-enum); changeColumnQuery handles ENUM_NAMED casts; pgEnumName, pgListEnums, pgEnum, pgEnumAdd, and dataTypeMapping updated to respect enumName/enumSchema and force behavior.
Query Interface Lifecycle
packages/postgres/src/query-interface.js
changeColumn pre-creates named enums; ensureEnums/addEnumValue use enumName/enumSchema; dropTable resolves effective schema, detects shared usage across models, and conditionally drops named enum types (ARRAY(ENUM) included).
Tests and Fixtures
packages/core/test/unit/dialects/postgres/enum.test.js, packages/core/test/unit/data-types/misc-data-types.test.ts
Added tests for enum-name/schema SQL generation, pgEnum/pgEnumAdd/pgListEnums behavior, changeColumn/addColumn DDL handling, extensive dropTable cleanup cases, and validation tests preventing empty name/schema.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • sequelize/sequelize#18051: Modifies Postgres enum handling in query-generator.js; likely related changes to enum/sql generation.

Suggested reviewers

  • sdepold
  • WikiRik

Poem

🐰 A name and schema for enums, hooray!
Shared types hop across models today.
Values aligned, no casts to mend,
Reuse and tidy, from start to end.
🥕✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: adding the ability to name enums for sharing across tables, which is the central objective of this PR.
Linked Issues check ✅ Passed The PR implements the core requirement from issue #2577: accepting a 'name' parameter for DataTypes.ENUM to enable enum reuse across models. The implementation adds 'name' and 'schema' options to EnumOptions/NormalizedEnumOptions, validates them, and integrates them into PostgreSQL enum handling (pgEnumName, changeColumn, dropTable).
Out of Scope Changes check ✅ Passed All changes directly support the stated objective of naming enums for sharing across tables. Core data type definitions were extended with name/schema options, PostgreSQL query generation was updated to use these options, and comprehensive tests verify the new functionality. No unrelated changes detected.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

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

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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.

@papandreou papandreou marked this pull request as ready for review May 9, 2026 17:51
@papandreou papandreou requested a review from a team as a code owner May 9, 2026 17:51
@papandreou papandreou requested review from WikiRik and sdepold May 9, 2026 17:51
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: 3

🧹 Nitpick comments (1)
packages/core/test/unit/dialects/postgres/enum.test.js (1)

218-251: ⚡ Quick win

Close createSequelizeInstance() instances in afterEach.

These tests create per-test Sequelize instances but never close them. This can leak pools and make the suite flaky.

As per coding guidelines, "Use beforeEach/afterEach to sync with { force: true } and close Sequelize instances in tests".

Also applies to: 384-533

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/test/unit/dialects/postgres/enum.test.js` around lines 218 -
251, The tests call createSequelizeInstance() per it-block (variables named sq)
but never close those Sequelize instances, leaking connection pools; update the
addColumnQuery describe block (and the other affected blocks around lines
384-533) to create the Sequelize instance in a beforeEach or assign each test's
sq to an outer-scoped variable and ensure an afterEach awaits sq.close() (or
sq.close().then/await) to properly shut down the connection pool; reference
createSequelizeInstance(), the local variable sq, and the addColumnQuery
describe block when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/core/src/abstract-dialect/data-types.ts`:
- Around line 2324-2330: The code copies options.name and options.schema into
opts without type checks, so add runtime validation in the
constructor/initializer that sets opts (the block handling
options?.name/options?.schema) to ensure both, if provided, are strings; if not,
throw a clear TypeError (e.g., "ENUM name must be a string" / "ENUM schema must
be a string"). Update the checks around options.name and options.schema to use
typeof === 'string' (or undefined) before assignment and throw with the
identifying symbols options and opts so callers get an immediate, actionable
error at construction time.

In `@packages/postgres/src/query-generator.js`:
- Around line 179-183: The enum-cast path in changeColumnQuery currently only
detects enums by the string pattern "ENUM(...)" so custom-named/schemed enums
that are emitted as qualified identifiers skip adding the required USING cast;
update changeColumnQuery to accept/inspect DataType objects (not just
preprocessed strings) or at minimum detect qualified enum identifiers (e.g.
quoted/qualified names containing dots or schema qualifiers) and treat them as
enums when deciding to append "USING ...::type"; modify the logic in
changeColumnQuery (and callers that pass DataTypes.ENUM) to check for
DataTypes.ENUM instances or a qualified-name pattern and ensure the generated
ALTER COLUMN ... TYPE ... includes the appropriate USING clause casting
old_value::new_enum_type.

In `@packages/postgres/src/query-interface.js`:
- Around line 313-345: The shared-enum check is comparing raw options.schema
values (e.g., undefined vs 'public') and can misclassify enums; update the
isSharedWithOtherModel logic to compare resolved/effective schemas instead of
raw options.schema by normalizing both the current enum's schema and each
otherModel enum's schema to the same effective value (e.g., resolve undefined to
the model/table schema or the DB default like 'public') before comparing names
and schemas; ensure you also use the same normalization for the current table's
schema when generating fullEnumName via this.queryGenerator.pgEnumName and
reference symbols enumType, otherType, DataTypes.ENUM, isSharedWithOtherModel,
this.sequelize.models, options?.schema, attribute.attributeName, and
this.queryGenerator.pgEnumName when making the changes.

---

Nitpick comments:
In `@packages/core/test/unit/dialects/postgres/enum.test.js`:
- Around line 218-251: The tests call createSequelizeInstance() per it-block
(variables named sq) but never close those Sequelize instances, leaking
connection pools; update the addColumnQuery describe block (and the other
affected blocks around lines 384-533) to create the Sequelize instance in a
beforeEach or assign each test's sq to an outer-scoped variable and ensure an
afterEach awaits sq.close() (or sq.close().then/await) to properly shut down the
connection pool; reference createSequelizeInstance(), the local variable sq, and
the addColumnQuery describe block when making the change.
🪄 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: 172ce5a5-4c44-4573-b2ed-5a9c5c7731ad

📥 Commits

Reviewing files that changed from the base of the PR and between 8260c29 and 1f8e9c5.

📒 Files selected for processing (6)
  • packages/core/src/abstract-dialect/data-types.ts
  • packages/core/test/unit/dialects/postgres/enum.test.js
  • packages/postgres/src/_internal/data-types-overrides.ts
  • packages/postgres/src/query-generator.d.ts
  • packages/postgres/src/query-generator.js
  • packages/postgres/src/query-interface.js

Comment thread packages/core/src/abstract-dialect/data-types.ts
Comment thread packages/postgres/src/query-generator.js Outdated
Comment thread packages/postgres/src/query-interface.js
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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/postgres/src/query-generator.js (1)

151-199: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep the named-enum USING clause separate from definition.

At Line 182, definition can still include trailing modifiers like UNIQUE, so this path can emit invalid SQL such as USING ("status"::"shared_status" UNIQUE). It also prevents the later UNIQUE cleanup from matching. Capture the raw enum type from ENUM_NAMED(...) and append the USING clause separately from definition.

Suggested fix
     for (const attributeName in attributes) {
       let definition = this.dataTypeMapping(tableName, attributeName, attributes[attributeName]);
       let attrSql = '';
+      let usingClause = '';
@@
       } else if (attributes[attributeName].startsWith('ENUM_NAMED(')) {
-        // Custom-named ENUM: the type is managed externally; the type name is already in
-        // `definition` (ENUM_NAMED wrapper stripped by dataTypeMapping). Just add USING cast.
-        definition += ` USING (${this.quoteIdentifier(attributeName)}::${definition.trim()})`;
+        const namedEnumType = attributes[attributeName].match(/^ENUM_NAMED\((.+?)\)/)?.[1];
+        usingClause = ` USING (${this.quoteIdentifier(attributeName)}::${namedEnumType})`;
       }
@@
       } else {
-        attrSql += query(`${this.quoteIdentifier(attributeName)} TYPE ${definition}`);
+        attrSql += query(`${this.quoteIdentifier(attributeName)} TYPE ${definition}${usingClause}`);
       }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/postgres/src/query-generator.js` around lines 151 - 199, The
ENUM_NAMED branch currently appends a USING cast directly to definition which
can leave trailing modifiers (like UNIQUE) in the type and break later cleanup;
instead, extract the raw enum type name from attributes[attributeName] (the
ENUM_NAMED(...) payload) or from the stripped definition, store it in a separate
usingClause (e.g. usingClause = ` USING
(${this.quoteIdentifier(attributeName)}::${enumType})`), do not mutate
definition, and then append usingClause to attrSql or to the final TYPE/ALTER
statement after UNIQUE/REFERENCES handling so that definition remains clean for
later regexes; reference the ENUM_NAMED branch, attributes[attributeName],
definition, pgEnumName and quoteIdentifier to implement this change.
♻️ Duplicate comments (1)
packages/postgres/src/query-interface.js (1)

350-383: ⚠️ Potential issue | 🟠 Major

dropTable still resolves shared named enums against the wrong schema when options.schema is used.

resolveEnumSchema ignores options?.schema, so dropTable('users', { schema: 'tenant1' }) can still compare and drop the enum as if it belonged to model.table.schema / the default schema instead of tenant1. That keeps the earlier shared-enum misclassification bug reachable here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/postgres/src/query-interface.js` around lines 350 - 383, The
resolveEnumSchema helper used when dropping enums ignores the dropTable call's
options.schema and so misclassifies shared enums; update resolveEnumSchema in
query-interface.js to consider the effective table/schema option (e.g.,
options?.schema) before falling back to ownerModel.table.schema or defaultSchema
so it returns et.options.schema ?? options?.schema ?? ownerModel.table.schema ??
defaultSchema (or otherwise prefer the provided options schema when present),
and ensure callers that compute currentEnumSchema and the call to
this.queryGenerator.pgEnumName use that corrected value.
🧹 Nitpick comments (1)
packages/core/test/unit/data-types/misc-data-types.test.ts (1)

107-111: 💤 Low value

LGTM! Good symmetry with the name validation test.

The test correctly validates that an empty schema option throws a TypeError. Both validation tests (name and schema) are well-structured and provide clear error messages.

Optional: Consider additional validation test cases

While the current tests correctly validate empty strings, you might consider adding tests for other edge cases to ensure comprehensive validation:

  • null and undefined values (to confirm whether they're allowed as "use default behavior")
  • Wrong types (e.g., numbers, objects) to verify type checking

However, if these cases are already covered elsewhere or if the implementation only validates empty strings specifically, the current tests are sufficient.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/test/unit/data-types/misc-data-types.test.ts` around lines 107
- 111, Add additional unit tests around DataTypes.ENUM validation to cover edge
cases beyond empty strings: create tests that assert a TypeError (or expected
behavior) when schema is null, undefined, a number, and an object, and likewise
add similar cases for the "name" option; locate the existing test case titled
'throws if the "schema" option is an empty string' and add new it(...) blocks
that call DataTypes.ENUM({ values: ['a','b'], schema: <value> }) and assert the
appropriate exception or acceptance for each input type.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/postgres/src/query-interface.js`:
- Around line 27-35: The guard in changeColumn only pre-creates enums when
enumType.options.name exists, but attributeToSQL can emit schema-qualified enums
when enumType.options.schema is set without a custom name; update the
changeColumn logic that currently calls
this.sequelize.queryRaw(this.queryGenerator.pgEnum(...)) to also pre-create
schema-only enums by checking for enumType.options.schema (in addition to
options.name) and invoking queryGenerator.pgEnum with enumSchema set so the
schema-qualified enum is created before running ALTER COLUMN; locate
changeColumn, attributeToSQL, and the call sites that reference
enumType.options.name/enumType.options.schema to add the extra conditional
branch and ensure options.raw remains true when creating the enum.

---

Outside diff comments:
In `@packages/postgres/src/query-generator.js`:
- Around line 151-199: The ENUM_NAMED branch currently appends a USING cast
directly to definition which can leave trailing modifiers (like UNIQUE) in the
type and break later cleanup; instead, extract the raw enum type name from
attributes[attributeName] (the ENUM_NAMED(...) payload) or from the stripped
definition, store it in a separate usingClause (e.g. usingClause = ` USING
(${this.quoteIdentifier(attributeName)}::${enumType})`), do not mutate
definition, and then append usingClause to attrSql or to the final TYPE/ALTER
statement after UNIQUE/REFERENCES handling so that definition remains clean for
later regexes; reference the ENUM_NAMED branch, attributes[attributeName],
definition, pgEnumName and quoteIdentifier to implement this change.

---

Duplicate comments:
In `@packages/postgres/src/query-interface.js`:
- Around line 350-383: The resolveEnumSchema helper used when dropping enums
ignores the dropTable call's options.schema and so misclassifies shared enums;
update resolveEnumSchema in query-interface.js to consider the effective
table/schema option (e.g., options?.schema) before falling back to
ownerModel.table.schema or defaultSchema so it returns et.options.schema ??
options?.schema ?? ownerModel.table.schema ?? defaultSchema (or otherwise prefer
the provided options schema when present), and ensure callers that compute
currentEnumSchema and the call to this.queryGenerator.pgEnumName use that
corrected value.

---

Nitpick comments:
In `@packages/core/test/unit/data-types/misc-data-types.test.ts`:
- Around line 107-111: Add additional unit tests around DataTypes.ENUM
validation to cover edge cases beyond empty strings: create tests that assert a
TypeError (or expected behavior) when schema is null, undefined, a number, and
an object, and likewise add similar cases for the "name" option; locate the
existing test case titled 'throws if the "schema" option is an empty string' and
add new it(...) blocks that call DataTypes.ENUM({ values: ['a','b'], schema:
<value> }) and assert the appropriate exception or acceptance for each input
type.
🪄 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: 03590140-b2d3-493a-9bc1-5ef34b2f67ee

📥 Commits

Reviewing files that changed from the base of the PR and between 1f8e9c5 and 590edcd.

📒 Files selected for processing (5)
  • packages/core/src/abstract-dialect/data-types.ts
  • packages/core/test/unit/data-types/misc-data-types.test.ts
  • packages/core/test/unit/dialects/postgres/enum.test.js
  • packages/postgres/src/query-generator.js
  • packages/postgres/src/query-interface.js
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/core/src/abstract-dialect/data-types.ts
  • packages/core/test/unit/dialects/postgres/enum.test.js

Comment thread packages/postgres/src/query-interface.js Outdated
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.

🧹 Nitpick comments (1)
packages/core/test/unit/dialects/postgres/enum.test.js (1)

435-443: ⚡ Quick win

Inconsistent optional chaining between helper functions.

getCreateTypeSqls uses optional chaining (sql?.includes) on line 442, but getDropTypeSqls does not use it on line 437. Since both helpers are used in similar contexts and access stub.args in the same way, they should handle potential undefined values consistently.

♻️ Suggested fix for consistency
 function getDropTypeSqls(stub) {
-  return stub.args.map(([sql]) => sql).filter(sql => sql.includes('DROP TYPE'));
+  return stub.args.map(([sql]) => sql).filter(sql => sql?.includes('DROP TYPE'));
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/test/unit/dialects/postgres/enum.test.js` around lines 435 -
443, The two helper functions getDropTypeSqls and getCreateTypeSqls are
inconsistent about guarding against undefined SQL strings; make them consistent
by applying the same optional chaining check when calling includes (either use
sql?.includes in getDropTypeSqls to match getCreateTypeSqls, or remove the ? in
getCreateTypeSqls) so both functions access stub.args safely; update the
getDropTypeSqls implementation that maps stub.args to use sql?.includes('DROP
TYPE') to mirror getCreateTypeSqls.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/core/test/unit/dialects/postgres/enum.test.js`:
- Around line 435-443: The two helper functions getDropTypeSqls and
getCreateTypeSqls are inconsistent about guarding against undefined SQL strings;
make them consistent by applying the same optional chaining check when calling
includes (either use sql?.includes in getDropTypeSqls to match
getCreateTypeSqls, or remove the ? in getCreateTypeSqls) so both functions
access stub.args safely; update the getDropTypeSqls implementation that maps
stub.args to use sql?.includes('DROP TYPE') to mirror getCreateTypeSqls.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: cd834949-9002-47b4-8ac1-0591e84958ec

📥 Commits

Reviewing files that changed from the base of the PR and between 590edcd and 824a9fd.

📒 Files selected for processing (2)
  • packages/core/test/unit/dialects/postgres/enum.test.js
  • packages/postgres/src/query-interface.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/postgres/src/query-interface.js

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.

FEATURE: Support ENUM naming for ENUM reuse.

1 participant