fix: correct attributeToSQL across all dialects - #18367
Draft
wikirik-agent wants to merge 7 commits into
Draft
Conversation
Replaces the per-dialect `attributesToSQL` suites with two dialect-agnostic suites that cover all nine dialects, including ibmi and mssql which had no coverage at all. The expectations pin current behaviour exactly, so this commit changes no SQL. Behaviour that is wrong today is pinned as-is and marked with a TODO. `sqlite3` gains an `attributeToSQL` method, extracted from `attributesToSQL` without changing its output, so the shared suite can cover it like every other dialect. `inlineErrorCause` now inspects non-error values instead of stringifying them: mssql and ibmi return null-prototype maps from `attributesToSQL`, which threw when the assertion message was built. Closes sequelize#15533 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every dialect that refuses a DEFAULT for certain column types was checking conditions that can never be true on v7 data types: - `attribute.type._binary` was a v6 property of STRING/CHAR set by `.BINARY`. It does not exist in v7, so the check was always a no-op. It is now `attribute.type.options?.binary`, which is where that flag lives today. - db2, mssql and oracle compared `attribute.type !== 'TEXT'`, comparing a DataType instance against a string, so it never matched. - mariadb, mysql, snowflake and ibmi looked up the rendered SQL string in a set of type names. That only matched the default length variant, and never matched at all on ibmi, whose BLOB renders as `BLOB(1M)`. The lookups now use `attributeTypeToDataTypeId`, added to the core data type utilities, which returns the dialect-independent identity of a type. Each dialect keeps exactly the list of types it already named. As a result db2, mssql and oracle stop emitting a DEFAULT for BLOB and TEXT columns, and ibmi stops emitting one for BLOB. A raw `'BLOB'` string type with a default no longer throws while trying to escape the value. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`attributeToSQL` received the table under two different names and the column
under three, and no caller passed all of them:
- Core passed `table`, while mysql, mariadb, snowflake, db2 and ibmi read
`tableName`. Passing the name declared in `AttributeToSqlOptions` therefore
threw on mariadb and mysql, and named the constraint `undefined_..._foreign_idx`
on snowflake, db2 and ibmi.
- The column arrived as `key` on postgres, `attributeName` on oracle and
`foreignKey` everywhere else, duplicating `attribute.field`.
There is now a single `tableOrModel` option, accepting anything `quoteTable`
does, and the column name is always read from `attribute.field`, which
`attributesToSQL` and every `addColumnQuery` now set. `schema` is gone: core
already stamps the multi-tenancy schema onto the table it passes, so an
explicitly qualified reference target keeps its own schema instead of being
overridden.
Fixed along the way:
- snowflake and ibmi built foreign key constraint names out of an
already-quoted column, producing `"myTable_""myColumn""_foreign_idx"`, and db2
left the constraint name unquoted entirely.
- sqlite3, snowflake and ibmi ignored `withoutForeignKeyConstraints`.
- postgres dereferenced `options.schema`, `options.table` and
`options.withoutForeignKeyConstraints` without guarding, so `attributeToSQL`
threw for any attribute with `references` and no options. Deferrable
references were unreachable as a result.
- mssql and ibmi threw on plain string attributes such as `{ id: 'INTEGER' }`,
which the other seven dialects accept.
- snowflake and sqlite3 did not normalize the data type in `addColumnQuery`.
- `columnName` is now honoured by every dialect, not only sqlite3.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ause Neither dialect can change a column's type and its nullability in a single ALTER COLUMN clause, and both got it wrong in a different way. db2's `attributeToSQL` returned an array of fragments in the changeColumn context so that `changeColumnQuery` could split them into separate clauses, which meant one method returned a string or an array depending on its options. Worse, the COMMENT branch that follows appended to that array, coercing it into `DATA TYPE INTEGER,DROP NOT NULL COMMENT Test` and collapsing the two clauses back into one invalid fragment. ibmi produced `ALTER COLUMN "c" SET DATA TYPE INTEGER DROP NOT NULL`, a single clause the server rejects, and joined multiple columns with a comma rather than repeating ALTER COLUMN. `attributeToSQL` now always returns a string. `changeColumnQuery` receives the attribute definitions alongside the generated SQL and emits the nullability as its own clause, so both dialects produce the form db2 already produced for `allowNull: false`. ibmi also quotes the column through `quoteIdentifier` instead of interpolating double quotes directly. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Removes the notes describing behaviour this branch fixed, and corrects two that were wrong: db2 and mssql drop the referential actions of repeated references to the same table on purpose, because both servers reject more than one cascading constraint to a table. Only db2's extra rule for unique attributes has no known justification, so that one keeps a TODO. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Contributor
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: trueThanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Follow-up to the previous commit, which restored guards that turned out to be
describing restrictions that do not exist. Verified against db2 12.1.4, SQL
Server 2025, Oracle 23.26, MySQL 8.4 and MariaDB 11.6:
- db2, mssql and oracle accept a DEFAULT on their LOB types. `CLOB DEFAULT
'abc'`, `NVARCHAR(MAX) DEFAULT N'abc'` and `BLOB DEFAULT BLOB('abc')` all
work. The SQL Server folklore appears to come from CREATE TABLE's rule that
CHECK constraints cannot be defined on text/ntext/image, which is a different
clause. Those three lists are removed.
- `.BINARY` on a string type is a collation, not a storage class, and every
server tested accepts a default on such a column. That guard is removed
everywhere rather than repaired.
mariadb, mysql, snowflake and ibmi keep the lists they already had, now matched
by data type identity rather than by rendered SQL string.
Without this, a `defaultValue` the user asked for would silently vanish.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Giving `attributeToSQL` a single column option made db2's `addColumn` foreign key branch reachable for the first time: it used to require `options.foreignKey`, which db2's `addColumnQuery` never passed, and now reads `attribute.field`, which it does set. The branch separated the two clauses with a comma, which db2 rejects — ALTER TABLE actions are not comma separated. It now uses ` ADD CONSTRAINT`, the same form ibmi uses, which db2 accepts. Caught by the removeColumn integration test against db2 12.1.4. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description of Changes
Second of the stack re-implementing #17121. Depends on #18366 — its commit is the first of the four here, since GitHub cannot base a cross-fork PR on a branch that only exists on the fork. Review #18366 first; only the last three commits belong to this PR.
#18366 pinned current behaviour exactly. This PR fixes it. Every expectation that changes in
attribute-to-sql.test.ts/attributes-to-sql.test.tsis a behaviour delta, so the test diff is the review surface.All nine dialect unit suites pass (2,296–2,906 tests each), clean
eslintand cleantscon both--noEmitandtest/tsconfig.json.Commits
fix: make the "this type cannot have a default" guards work againEvery dialect that refuses a
DEFAULTfor certain types was checking conditions that can never be true on v7 data types:attribute.type._binarywas a v6 property ofSTRING/CHARset by.BINARY. It does not exist in v7 — I checked the v6 source in git history to confirm what it meant. It is nowattribute.type.options?.binary.attribute.type !== 'TEXT', i.e. aDataTypeinstance against a string.DataTypes.TEXT('tiny')→TINYTEXTmissed, and ibmi never matched at all since its BLOB renders asBLOB(1M).Lookups now use a new
attributeTypeToDataTypeIdcore utility built on the existinggetDataTypeId(), which returns a type's dialect-independent identity. Each dialect keeps exactly the list of types it already named — I did not re-decide which types reject defaults.A second commit then removes most of those lists again. Once the guards were live I checked them against real servers, and the restrictions largely do not exist:
DEFAULTon their LOB types.CLOB DEFAULT 'abc',NVARCHAR(MAX) DEFAULT N'abc'andBLOB DEFAULT BLOB('abc')all work, and the value round-trips. The SQL Server folklore appears to come fromCREATE TABLE's rule that CHECK constraints cannot be defined ontext/ntext/image— a different clause. Those three lists are removed..BINARYon a string type is a collation, not a storage class, and every server tested accepts a default on such a column. That guard is removed everywhere rather than repaired.So the net effect of the two commits is: the guards no longer silently discard a
defaultValuethe user asked for. mariadb, mysql, snowflake and ibmi keep the lists they had, now matched by data type identity rather than by rendered SQL string. (#18369 revisits mariadb's and mysql's, which turned out to be wrong too.)fix: give attributeToSQL one way to name the table and the columnThe table arrived under two names and the column under three, and no caller passed all of them:
table; mysql/mariadb/snowflake/db2/ibmi readtableName. Passing the name declared inAttributeToSqlOptionsthrew on mariadb/mysql and producedundefined_..._foreign_idxon snowflake/db2/ibmi.keyon postgres,attributeNameon oracle,foreignKeyelsewhere — all duplicatingattribute.field.Now: one
tableOrModeloption, and the column always read fromattribute.field, whichattributesToSQLand everyaddColumnQueryset.schemais gone — core already stamps the multi-tenancy schema onto the table it passes, so an explicitly-qualified reference target now keeps its own schema rather than being overridden.Also fixed here:
"myTable_""myColumn""_foreign_idx"); db2 left the name unquoted entirely.withoutForeignKeyConstraints.attributeToSQLthrew for any attribute withreferencesand no options — which also made deferrable references unreachable.{ id: 'INTEGER' }) that the other seven accept.addColumnQuery.columnNameis now honoured by every dialect, not only sqlite3.fix(db2,ibmi): emit the nullability change as its own ALTER COLUMN clauseNeither can change a column's type and nullability in one
ALTER COLUMNclause.db2 returned an array of fragments in the
changeColumncontext sochangeColumnQuerycould split them — one method returningstringorstring[]depending on its options. The COMMENT branch then appended to that array, coercing it toDATA TYPE INTEGER,DROP NOT NULL COMMENT Testand collapsing both clauses back into one invalid fragment.ibmi had the same bug invisibly, emitting
ALTER COLUMN "c" SET DATA TYPE INTEGER DROP NOT NULL, and joined multiple columns with a comma instead of repeatingALTER COLUMN.attributeToSQLnow always returns a string;changeColumnQueryreceives the attribute definitions alongside the generated SQL and emits the nullability clause itself.Notes for review
TZ=UTC), db2 19 passing on the query-interface suite. ibmi is still unverified — there is no way to run it locally, so itsALTER COLUMNchange rests on code reading alone.attribute(s)ToSQL#17121 turned out not to need fixing:ARRAY(ENUM)on postgres was already corrected upstream, and comment escaping is already done by every consumer that extracts the in-bandCOMMENTmarker. Escaping at the source (as fix: overhaul test suite ofattribute(s)ToSQL#17121 does) would double-escape and is what caused that PR to leakN'FooBar'intodescribeTable.attributeToSQLstill has an unreachableaddColumnFK branch — itsaddColumnQuerynever triggers it. I fixed the quoting rather than deleting it, since whether db2 needs the, CONSTRAINTform is a call I can't verify.Remaining follow-up
attributeToSql/attributesToSqlList of Breaking Changes
AttributeToSqlOptionschanges shape (table/tableName/schema/key/foreignKey/attributeName→tableOrModelplusattribute.field). It is exported only under_non-semver-use-at-your-own-risk_.🤖 Generated with Claude Code