Skip to content

feat: add dialectTypes column option for per-database column types - #12884

Open
xue-ding-e wants to merge 17 commits into
typeorm:masterfrom
xue-ding-e:master
Open

xue-ding-e wants to merge 17 commits into
typeorm:masterfrom
xue-ding-e:master

Conversation

@xue-ding-e

@xue-ding-e xue-ding-e commented Sep 18, 2026 •

Copy link
Copy Markdown

Description of change

Closes #12874.

A column's type currently applies to every database. This adds dialectTypes so the same entity can use a different physical type on a particular driver:

@Column({ type: "json", dialectTypes: { postgres: "jsonb" } })
payload: object

@Column({ type: "tinyint", dialectTypes: { postgres: "smallint" } })
level: number

Decorators and EntitySchema accept the option. Missing driver entries fall back to type. Relation keys inherit the referenced column's overrides, including explicitly declared join columns.

Schema validation, creation and comparison use the same resolved type and modifiers. Driver normalization handles aliases, numeric length/precision parameters and supported max lengths; incompatible inherited modifiers, including MySQL unsigned, charset and collation and spatial feature type/SRID on nonspatial replacements, are removed. PostgreSQL extension detection, SQL Server parameter/OUTPUT declarations and Oracle RETURNING bindings also use the physical type. Spatial INSERT/UPDATE constructors, PostgreSQL casts, SELECT expressions and inlined omitted-value defaults follow the physical type. Value conversion, hydration and transformers retain the logical type, so overrides must be compatible with the field's values.

Blank overrides and unsupported parameter syntax are rejected at initialization. max is accepted for SQL Server's variable-length text/binary types and Spanner's string/bytes types, and rejected for fixed-length types or precision/scale. SQL Server overrides also validate decimal precision/scale and required vector dimensions. PostgreSQL's valid scale-greater-than-precision declarations and zero fractional-second precision remain supported. Usage is documented in the entity guide and decorator reference.

Earlier validation, before the 2026-10-11 follow-up: TypeScript compilation, lint, formatting and commit hooks passed. Spatial query-builder unit tests passed (13); PostgreSQL 17.9/PostGIS, SQL Server 2025 Express and MySQL 8 integration tests created the physical columns, wrote, updated and read values, and produced no second-sync DDL (3 passed). Focused dialect-types runs passed 23 tests with isolated PostgreSQL 17.9/better-sqlite3 and 22 with isolated MySQL 8/better-sqlite3. The PostgreSQL named-enum case covers unique constraints, an index and a foreign key, with an empty second schema plan (four DDL statements before the fix); two existing named-enum tests also passed. A spatial-to-nonspatial PostgreSQL override planned one repeated DDL operation before the fix and none afterward, while logical spatial metadata stayed intact. The MySQL case covers incompatible and retained unsigned/character modifiers, actual table creation and an empty second schema plan; table creation failed before the fix. An in-memory SQLite regression showed a JSON default stored as BLOB before the fix and TEXT afterward; 12 related SQLite tests passed, including an empty second schema plan.

A broader four-driver run (also including better-sqlite3) had 3154 passing and 176 pending, with one failure in that original all-tables comparison before it was narrowed and retested. The separate existing #4753 replication test, which connects to a fixed localhost:3306, was excluded because that port is owned by another local service. Aurora MySQL has query-builder unit coverage; no live Aurora, Oracle or SAP server was used.

AI assistants were used for implementation, review, testing and drafting this description.

2026-10-11: physical modifier follow-up

Commit 5ed655a addresses the four remaining findings in the review, after reproducing them against the preceding head.

  • Clear MySQL-family onUpdate when the physical type is neither DATETIME nor TIMESTAMP.
  • Clear PostgreSQL/Aurora PostgreSQL collation for non-collatable physical types, while retaining supported character types, citext and applicable arrays. PostgreSQL enum collation and MySQL enum collation have different rules.
  • Reject incompatible PostgreSQL/Aurora PostgreSQL increment/identity overrides, including arrays; retain supported integer types and aliases.
  • Use physical metadata when deciding whether introspected length, precision and scale are implicit driver defaults. Explicit decimal(10,0) and varchar(255) overrides no longer cause repeated schema changes.
  • For MySQL/Aurora MySQL temporal overrides, reject conflicting precision in bare CURRENT_TIMESTAMP/NOW clauses without rewriting expressions. ON UPDATE is checked during metadata validation. DEFAULT is checked while constructing table-column options, using the existing normalized result, so its factory is not evaluated a second time.

The DEFAULT check occurs during DDL-plan construction; it does not guarantee that an entire synchronize operation has made no earlier changes. Quoted strings and compound SQL expressions are not treated as bare timestamp functions. Logical column metadata and runtime conversion remain unchanged.

Validation of this exact commit, using isolated synthetic data on Linux:

  • 109 new unit cases: preceding head 61 passed / 48 failed; fixed code 109 passed. New and existing related dialect unit suites: 157 passed, with TypeScript checking enabled.
  • The two native DB regression tests failed on the preceding head. The complete dialect-types.test.ts file passed 9 tests with PostgreSQL 17.11 and MySQL 8.4.11, including empty second-sync plans.
  • Additional live PostgreSQL checks covered retained collation, bigint SERIAL and smallint IDENTITY, including generated inserts. MySQL checks covered seven successful creation/second-sync cases and three expected precision-conflict rejections.
  • Complete TypeORM package tsc --noEmit passed. Package ESLint completed with 0 errors and 7,751 warnings. Changed-file formatting and git diff --check passed.
  • Default-factory counters verify one evaluation per TableUtils conversion, including rejected declarations.

The initial local run did not exercise real MariaDB/Aurora services or the full multi-database matrix. The additional fork verification below now includes MariaDB. Upstream CI is separate from these results. AI assistance was used for implementation, review, testing and this report.

Additional fork verification at de58583

The focused fork workflow passed, checking out the exact product commit de58583be0efef3f7dba305b872fcfd490393261. The QA workflow lives on a separate branch and is not part of this PR.

  • The first live MariaDB run exposed an incorrect existing test assertion: MariaDB reports JSON storage as LONGTEXT. 44d1b15 corrects that driver-specific expectation and also requires the payload's JSON_VALID CHECK on MariaDB >=10.4.3, so plain LONGTEXT cannot satisfy the test. This matches MariaDB's JSON documentation. Empty second-sync plans still pass.
  • The repository's locked Prettier 3.9.6 caught one formatting difference in the new unit file that the initial local 3.6.2 check missed. de58583 fixes only that layout. The final fork run passed the complete repository pnpm run format:ci and compilation with locked dependencies.
  • Node 24: 157 related unit tests passed. The selected columns, schema-builder, column-length and collation suites reported 226 passing / 1 existing pending, using isolated PostgreSQL 17.11, MySQL 8.4.11, MariaDB 10.11.16, better-sqlite3 and sqljs configurations. These totals are Mocha case counts, not a claim that every case executes on every driver.
  • The duplicate automatically triggered full fork matrix was cancelled to avoid unnecessary services. It is not a passing full-matrix result. Live Aurora, SQL Server, Oracle, SAP, CockroachDB and other unconfigured drivers were not tested in this focused run.
  • Upstream checks for this head still require maintainer approval. Fork results do not replace upstream checks or approval.

Pull-Request Checklist

  • Code is up-to-date with the master branch
  • This pull request links a relevant issue using a closing keyword
  • There are new or updated tests validating the change
  • Documentation has been updated to reflect this change

Column types are not portable across databases: tinyint exists on mysql
and sqlite but not on postgres, jsonb is postgres-only, and every driver
has a few types like these. A single `type` forces either a portable
lowest common denominator or hand-written migrations.

A column can now declare a type per driver:

    \@column({ type: "tinyint", dialectTypes: { postgres: "smallint" } })
    level: number

The override replaces the generic type when the matching driver builds
the schema; every other driver keeps using `type` (or the inferred
default). The new option is consulted in three places:

- TableUtils.createTableColumnOptions, where the metadata column becomes
  DDL, so synchronize and schema builders pick it up
- EntityMetadataValidator, so a logical type unsupported by the current
  driver (tinyint on postgres) no longer fails initialization when an
  override covers that driver
- ColumnMetadata, which stores the option

Runtime value handling is unaffected: the column's logical type, value
transformers and query building stay exactly as before, and columns
without dialectTypes are processed identically to today.
@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Sep 18, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (5) 📘 Rule violations (0) 📎 Requirement gaps (0) 📜 Skill insights (0)

⚠️ 3 lower-priority findings omitted to fit the comment size limit; re-run the review or view the findings in the Qodo portal.

Grey Divider


Action required

1. MySQL defaults lose fractional digits 🐞 Bug ≡ Correctness ⭐ New
Description
TableUtils.createTableColumnOptions passes the projected physical column to MySQL’s
normalizeDefault, which formats numeric defaults with toFixed(columnMetadata.scale) after
resolveDriverColumn has cleared an incompatible scale. For a logical decimal(4,2) column with
default: 1.23 overridden to varchar, table creation emits DEFAULT '1' instead of preserving
1.23.
Code

packages/typeorm/src/schema-builder/util/TableUtils.ts[13]

+        columnMetadata = columnMetadata.resolveDriverColumn(driver)
Evidence
The projection clears precision and scale for types that do not support them; TableUtils then
normalizes the default using that projection. MySQL formats every numeric default with
toFixed(columnMetadata.scale), and its query runner emits the resulting string directly in the
DEFAULT clause.

packages/typeorm/src/metadata/ColumnMetadata.ts[204-217]
packages/typeorm/src/schema-builder/util/TableUtils.ts[12-27]
packages/typeorm/src/driver/mysql/MysqlDriver.ts[817-845]
packages/typeorm/src/driver/mysql/MysqlQueryRunner.ts[3578-3583]

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

## Issue description
A MySQL override from a scaled numeric type to `varchar` clears the projected scale before default normalization, causing `1.23` to become `1` in the generated DDL.

## Fix Focus Areas
- packages/typeorm/src/schema-builder/util/TableUtils.ts[12-27]
- packages/typeorm/src/metadata/ColumnMetadata.ts[204-217]
- packages/typeorm/src/driver/mysql/MysqlDriver.ts[817-845]

## Recommended Fix
Preserve the numeric default’s value when normalizing it for a physical type that does not support scale, without restoring an invalid scale to the physical column. Add a regression test covering a scaled numeric logical column overridden to MySQL `varchar`.

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


2. Temporal type changes fail creation ✓ Resolved
Description
resolveDriverColumn retains onUpdate even when a MySQL-family override replaces a temporal type
with a type that cannot accept that clause. A datetime or timestamp column overridden to a
non-temporal type therefore reaches synchronization with ON UPDATE still attached, and MySQL
rejects the generated declaration.
Code

packages/typeorm/src/metadata/ColumnMetadata.ts[R151-152]

+        const clearIncompatibleModifiers = (normalizedType: ColumnType) => {
+            if (normalizedType !== "enum") {
Evidence
The physical projection's compatibility cleanup does not inspect onUpdate, while table conversion
preserves it and MySQL DDL appends the clause whenever it is present. Thus changing only the
physical type cannot remove an update clause that is invalid for the replacement type.

packages/typeorm/src/metadata/ColumnMetadata.ts[151-172]
packages/typeorm/src/schema-builder/util/TableUtils.ts[20-25]
packages/typeorm/src/driver/mysql/MysqlQueryRunner.ts[3577-3582]

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

## Issue description
Physical type projection retains MySQL `onUpdate` after an override changes a temporal column to a non-temporal type, causing invalid DDL.
## Fix Focus Areas
- packages/typeorm/src/metadata/ColumnMetadata.ts[151-172]
- packages/typeorm/src/schema-builder/util/TableUtils.ts[20-25]
- packages/typeorm/src/driver/mysql/MysqlQueryRunner.ts[3577-3582]
## Recommended Fix
When resolving a MySQL-family override, retain `onUpdate` only for physical types that support automatic timestamp updates; otherwise clear it on the projected column. Add creation and second-synchronization coverage for a temporal logical column overridden to a non-temporal physical type.

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


3. Generated columns can emit malformed SQL ✓ Resolved
Description
resolveDriverColumn retains isGenerated, generationStrategy, and generatedIdentity without
checking whether the replacement physical type supports the selected generation strategy. On
PostgreSQL, overriding an increment or identity integer to an incompatible type either emits an
invalid identity declaration or skips both the serial declaration and the normal type declaration.
Code

packages/typeorm/src/metadata/ColumnMetadata.ts[R151-152]

+        const clearIncompatibleModifiers = (normalizedType: ColumnType) => {
+            if (normalizedType !== "enum") {
Evidence
The resolved column retains all generation metadata, which TableUtils copies into the physical
TableColumn. PostgreSQL emits identity syntax with any retained type, while the increment path emits
serial syntax only for recognized integer types and suppresses normal type rendering for other
generated types.

packages/typeorm/src/metadata/ColumnMetadata.ts[151-172]
packages/typeorm/src/schema-builder/util/TableUtils.ts[22-26]
packages/typeorm/src/driver/postgres/PostgresQueryRunner.ts[5161-5191]

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

## Issue description
Physical overrides preserve generated-column strategies even when the replacement type cannot support them, allowing malformed PostgreSQL DDL to reach synchronization.
## Fix Focus Areas
- packages/typeorm/src/metadata/ColumnMetadata.ts[151-172]
- packages/typeorm/src/metadata-builder/EntityMetadataValidator.ts[126-188]
- packages/typeorm/src/driver/postgres/PostgresQueryRunner.ts[5161-5191]
## Recommended Fix
Validate each generated strategy against the normalized physical type during metadata initialization. Reject incompatible identity or increment overrides with a clear initialization error while retaining generation metadata for compatible integer-family overrides, and add tests for both cases.

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


View action required (9)
4. Collated type changes fail creation ✓ Resolved
Description
clearIncompatibleModifiers removes collation only inside its MySQL-family branch, so PostgreSQL
projections retain it after changing to a non-collatable physical type. A collated character column
overridden to an integer, binary, or similarly non-collatable type is emitted with COLLATE,
causing PostgreSQL creation or alteration to fail.
Code

packages/typeorm/src/metadata/ColumnMetadata.ts[R160-163]

+            if (
+                driver.options.type === "mysql" ||
+                driver.options.type === "mariadb" ||
+                driver.options.type === "aurora-mysql"
Evidence
The projection clears character modifiers only for MySQL-family drivers, but TableUtils still copies
PostgreSQL collation into the physical column. PostgreSQL's query runner unconditionally emits
COLLATE whenever that projected field remains populated.

packages/typeorm/src/metadata/ColumnMetadata.ts[160-170]
packages/typeorm/src/schema-builder/util/TableUtils.ts[12-16]
packages/typeorm/src/driver/postgres/PostgresQueryRunner.ts[5193-5199]

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

## Issue description
PostgreSQL physical type overrides retain logical collations even when the replacement type is not collatable, producing invalid schema DDL.
## Fix Focus Areas
- packages/typeorm/src/metadata/ColumnMetadata.ts[151-172]
- packages/typeorm/src/schema-builder/util/TableUtils.ts[12-16]
- packages/typeorm/src/driver/postgres/PostgresQueryRunner.ts[5193-5199]
## Recommended Fix
Apply collation compatibility filtering for PostgreSQL projections as well as MySQL-family projections. Retain collation only for normalized physical types that support it, clear it otherwise, and add schema creation plus second-sync tests for both retained and removed cases.

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


5. Schema updates repeat for valid columns ✓ Resolved
Description
RdbmsSchemaBuilder.updateExistColumns now compares resolved physical columns, but
BaseQueryRunner.isDefaultColumnPrecision and isDefaultColumnScale still inspect the original
logical metadata before removing introspected default values. When a dialect override explicitly
declares a database default such as MySQL decimal(10,0) while the logical column has no precision
or scale, introspection discards 10,0 and the next comparison schedules the column for alteration
again.
Code

packages/typeorm/src/schema-builder/RdbmsSchemaBuilder.ts[R900-902]

+                metadata.columns.map((column) =>
+                    column.resolveDriverColumn(this.dataSource.driver),
+                ),
Evidence
The changed schema-builder path supplies projected columns to findChangedColumns, so it expects
explicit physical precision and scale to remain visible. MySQL introspection calls the
BaseQueryRunner default checks before populating those fields; because those checks use the original
logical metadata, a logical column without modifiers allows MySQL's decimal defaults of 10 and 0
to be stripped even if the override explicitly supplied them.

packages/typeorm/src/schema-builder/RdbmsSchemaBuilder.ts[892-903]
packages/typeorm/src/query-runner/BaseQueryRunner.ts[707-779]
packages/typeorm/src/driver/mysql/MysqlQueryRunner.ts[3011-3037]
packages/typeorm/src/driver/mysql/MysqlDriver.ts[294-310]
packages/typeorm/src/driver/mysql/MysqlDriver.ts[1057-1078]

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

## Issue description
Schema-diff comparison receives resolved physical metadata, while default precision/scale stripping during table introspection still reads the logical column metadata. Explicit physical parameters equal to a driver default are consequently removed from the introspected table and differ on every subsequent comparison.
## Fix Focus Areas
- packages/typeorm/src/schema-builder/RdbmsSchemaBuilder.ts[898-902]
- packages/typeorm/src/query-runner/BaseQueryRunner.ts[707-779]
## Recommended Fix
In the BaseQueryRunner helpers that decide whether an introspected length, precision, or scale is only a driver default, resolve the matched metadata column with `this.dataSource.driver` before checking its length, precision, and scale. Preserve the existing fallback to driver defaults when the resolved physical metadata does not explicitly set that modifier, so implicit defaults remain suppressed while overrides such as `decimal(10,0)` remain available to schema comparison.

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


6. Nonspatial columns resync forever ✓ Resolved
Description
resolveDriverColumn clears enum and MySQL-specific modifiers but retains spatialFeatureType and
srid when the resolved type is not spatial. A PostgreSQL or CockroachDB geometry column overridden
to a nonspatial type keeps those values in metadata while database introspection omits them, so
every subsequent synchronization marks the column as changed.
Code

packages/typeorm/src/metadata/ColumnMetadata.ts[R151-155]

+        const clearIncompatibleModifiers = (normalizedType: ColumnType) => {
+            if (normalizedType !== "enum") {
+                physical.enum = undefined
+                physical.enumName = undefined
+            }
Evidence
The projection only clears enum and MySQL modifiers, while TableUtils forwards the retained spatial
fields and PostgreSQL compares both fields against introspected schema metadata. PostgreSQL only
renders spatial modifiers when the physical type itself is spatial, so a nonspatial database column
cannot preserve the projected values used during comparison.

packages/typeorm/src/metadata/ColumnMetadata.ts[151-168]
packages/typeorm/src/schema-builder/util/TableUtils.ts[35-38]
packages/typeorm/src/driver/postgres/PostgresDriver.ts[1354-1363]
packages/typeorm/src/driver/postgres/PostgresDriver.ts[1484-1488]
packages/typeorm/src/driver/cockroachdb/CockroachDriver.ts[936-945]

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

## Issue description
`resolveDriverColumn` retains `spatialFeatureType` and `srid` after resolving a spatial logical column to a nonspatial physical type, causing PostgreSQL and CockroachDB schema comparison to repeatedly detect changes.
## Fix Focus Areas
- packages/typeorm/src/metadata/ColumnMetadata.ts[151-168]
- packages/typeorm/src/driver/postgres/PostgresDriver.ts[1484-1488]
- packages/typeorm/src/schema-builder/util/TableUtils.ts[35-38]
## Recommended Fix
When the normalized physical type is not included in the driver's spatial types, clear `physical.spatialFeatureType` and `physical.srid` before validation, table-column creation, and schema comparison. Add a synchronization regression test for a spatial logical column with feature type and SRID overridden to a nonspatial PostgreSQL or CockroachDB type.

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


7. Omitted JSON defaults return binary data ✓ Resolved
Description
TableUtils.createTableColumnOptions resolves the physical column before normalizing its default,
but InsertQueryBuilder.createColumnValueExpression still calls normalizeDefault with the logical
column when SQLite inserts an omitted value. A logical jsonb column overridden to SQLite text
therefore creates a text JSON default in DDL but inserts jsonb(...) for omitted values, which
SQLite stores as binary JSON rather than the object-compatible text returned by the declared
physical column.
Code

packages/typeorm/src/schema-builder/util/TableUtils.ts[10]

+        columnMetadata = columnMetadata.resolveDriverColumn(driver)
Evidence
The new projection causes DDL defaults to be normalized from the override, whereas omitted-value
INSERTs retain the logical metadata. SQLite's default normalizer emits jsonb(...) only from
logical jsonb, while its hydration path expects text JSON for conversion, so the two paths produce
incompatible stored representations.

packages/typeorm/src/schema-builder/util/TableUtils.ts[10-20]
packages/typeorm/src/query-builder/InsertQueryBuilder.ts[1584-1598]
packages/typeorm/src/driver/sqlite-abstract/AbstractSqliteDriver.ts[690-728]
packages/typeorm/src/driver/sqlite-abstract/AbstractSqliteDriver.ts[440-469]

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

## Issue description
Schema DDL normalizes defaults using the resolved physical column, while the SQLite/SAP/Spanner and multi-row Oracle INSERT fallback normalizes an omitted value using logical metadata. This lets a logical SQLite `jsonb` column overridden to `text` create a text default but insert SQLite JSONB binary data when the property is omitted.
## Fix Focus Areas
- packages/typeorm/src/query-builder/InsertQueryBuilder[1331-1341]
- packages/typeorm/src/query-builder/InsertQueryBuilder[1584-1598]
- packages/typeorm/src/schema-builder/util/TableUtils[10-20]
## Recommended Fix
Resolve the column with `column.resolveDriverColumn(this.dataSource.driver)` before calling `normalizeDefault` in each INSERT fallback that inlines a default expression. Keep logical metadata for persistent-value conversion, but use the physical projection specifically for driver default SQL so it matches table creation and schema comparison; add regression coverage for an omitted SQLite JSONB logical value overridden to `text` with an object default.

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


8. Unsigned overrides fail table creation ✓ Resolved
Description
resolveDriverColumn removes incompatible length and precision fields but leaves unsigned,
charset, and collation copied from the logical column after changing its physical type. On
MySQL, an unsigned numeric column overridden to varchar passes metadata validation and is emitted
as varchar ... UNSIGNED, while a character column overridden to a numeric type similarly retains
character-only modifiers.
Code

packages/typeorm/src/metadata/ColumnMetadata.ts[R145-148]

+            if (
+                !driver.withLengthColumnTypes.includes(normalizedType) ||
+                (physical.length.toLowerCase() === "max" &&
+                    !driver.withMaxLengthColumnTypes?.includes(normalizedType))
Evidence
The resolver's compatibility cleanup only handles length, precision, and scale, while TableUtils
forwards the still-present unsigned, charset, and collation values into the physical table column.
MySQL's SQL builder appends those clauses without checking the physical type, and the option
contract explicitly defines UNSIGNED as numeric-only.

packages/typeorm/src/metadata/ColumnMetadata.ts[143-157]
packages/typeorm/src/schema-builder/util/TableUtils.ts[10-20]
packages/typeorm/src/driver/mysql/MysqlQueryRunner.ts[3548-3558]
packages/typeorm/src/decorator/options/ColumnUnsignedOptions.ts[1-9]
packages/typeorm/src/metadata-builder/EntityMetadataValidator.ts[126-143]

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

## Issue description
`resolveDriverColumn` retains MySQL-specific modifiers even when the replacement physical type cannot use them. This allows validation to pass but produces invalid DDL such as `varchar(10) UNSIGNED` or a numeric column with `CHARACTER SET` and `COLLATE` clauses.
## Fix Focus Areas
- packages/typeorm/src/metadata/ColumnMetadata.ts[143-157]
- packages/typeorm/src/metadata/ColumnMetadata.ts[200-222]
- packages/typeorm/src/schema-builder/util/TableUtils.ts[10-20]
- packages/typeorm/src/driver/mysql/MysqlQueryRunner.ts[3548-3558]
## Recommended Fix
When projecting both parameterized and unparameterized MySQL overrides, clear `unsigned` unless the normalized physical type is numeric, and clear `charset` and `collation` unless the physical type supports character modifiers. Preserve these values only on the projected clone when compatible, without mutating logical metadata, and add initialization or schema-generation tests for both incompatible directions.

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


9. Every sync rebuilds enum constraints ✓ Resolved
Description
resolveDriverColumn replaces physical.type but retains the logical column's enumName when the
physical override is not an enum. On PostgreSQL, an enum with an explicit name overridden to
varchar is therefore always marked changed, so each synchronization drops and recreates its
foreign keys, composite indexes, and composite unique constraints.
Code

packages/typeorm/src/metadata/ColumnMetadata.ts[R137-140]

+            physical.type = override.trim() as ColumnType
+            const normalizedType = driver.normalizeType(physical) as ColumnType
+            // Retain shared modifiers only when the replacement type supports
+            // them; for example, PostgreSQL text cannot inherit varchar length.
Evidence
The projection copies every logical field and then changes only the type while clearing length and
precision modifiers, leaving enum metadata intact. Table creation consequently emits a normal
physical type, PostgreSQL introspection assigns enum metadata only when pg_enum returns values,
and findChangedColumns directly compares the missing database enum name against the retained
metadata value; any reported change enters the schema-builder paths that drop related constraints
before changing the column.

packages/typeorm/src/metadata/ColumnMetadata.ts[86-153]
packages/typeorm/src/schema-builder/util/TableUtils.ts[10-35]
packages/typeorm/src/driver/postgres/PostgresQueryRunner.ts[3911-3932]
packages/typeorm/src/driver/postgres/PostgresDriver.ts[1451-1480]
packages/typeorm/src/schema-builder/RdbmsSchemaBuilder.ts[897-949]

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

## Issue description
A logical enum overridden to a non-enum physical type retains `enum` and `enumName`. PostgreSQL introspection does not return those fields for the resulting non-enum column, causing every synchronization to classify it as changed and rebuild related constraints.
## Fix Focus Areas
- packages/typeorm/src/metadata/ColumnMetadata.ts[137-153]
- packages/typeorm/test/functional/columns/dialect-types/dialect-types.test.ts[108-159]
## Recommended Fix
After normalizing the physical override, clear `enum` and `enumName` whenever the replacement is not an enum type. Add a PostgreSQL regression case using a named logical enum overridden to `varchar`, attach constraints to it, and verify the second schema log is empty.

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


10. Spatial overrides fail on writes ✓ Resolved
Description
resolveDriverColumn allows a spatial physical type to differ from logical column.type, but
InsertQueryBuilder and UpdateQueryBuilder still use the logical type to select spatial
constructors and PostgreSQL casts. When a compatible override changes types, such as logical
geometry to physical geography, SQL Server writes a geometry::STGeomFromText(...) value into a
geography column, while PostgreSQL and MySQL-family expressions likewise name or select behavior
from the unresolved type.
Code

packages/typeorm/src/metadata/ColumnMetadata.ts[R77-79]

+    resolveDriverColumn(driver: Driver): ColumnMetadata {
+        const override = this.dialectTypes?.[driver.options.type]
+        if (override === undefined) return this
Evidence
DDL resolves the physical override before creating the table column, while both spatial write paths
independently test and interpolate the unchanged logical type. Because the repository treats
geometry and geography as distinct supported spatial types, an override between them creates a
column of one type but causes INSERT and UPDATE expressions to use the other.

packages/typeorm/src/metadata/ColumnMetadata.ts[64-79]
packages/typeorm/src/query-builder/InsertQueryBuilder.ts[1627-1668]
packages/typeorm/src/query-builder/UpdateQueryBuilder.ts[600-650]
packages/typeorm/src/driver/postgres/PostgresDriver.ts[238-243]
packages/typeorm/src/schema-builder/util/TableUtils.ts[7-13]
packages/typeorm/src/query-builder/InsertQueryBuilder.ts[1649-1668]
packages/typeorm/src/query-builder/UpdateQueryBuilder.ts[629-654]
packages/typeorm/src/driver/postgres/PostgresDriver.ts[227-242]

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

## Issue description
Spatial column overrides are resolved for schema creation but not for INSERT and UPDATE value expressions. The query builders continue to choose spatial constructors and PostgreSQL casts from logical metadata, so a compatible physical spatial override can generate a value of the wrong database spatial type.
## Fix Focus Areas
- packages/typeorm/src/query-builder/InsertQueryBuilder.ts[1627-1669]
- packages/typeorm/src/query-builder/UpdateQueryBuilder.ts[600-654]
- packages/typeorm/src/metadata/ColumnMetadata.ts[77-79]
- packages/typeorm/src/metadata/ColumnMetadata.ts[137-138]
## Recommended Fix
Resolve the column through `resolveDriverColumn(this.dataSource.driver)` before checking spatial-type membership or emitting a spatial type name in INSERT and UPDATE. Retain the original logical metadata for value conversion, but use the resolved column type for MySQL/Aurora spatial expressions, PostgreSQL casts, and SQL Server constructors.

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


11. Tests lose the linked issue context ✓ Resolved
Description
dialect-types.test.ts introduces the main regression suite without a test comment referencing
issue 12874. Since this PR closes that issue, maintainers cannot trace the functional coverage back
to the originating report from the test source.
Code

packages/typeorm/test/functional/columns/dialect-types/dialect-types.test.ts[17]

+describe("columns > dialect types", () => {
Evidence
Compliance rule 3 requires issue fixes in the functional suite to include an issue reference in a
test comment when applicable. The new top-level functional suite contains no such reference even
though the PR explicitly closes issue 12874.

Rule 3: Prefer functional tests over per-issue tests
packages/typeorm/test/functional/columns/dialect-types/dialect-types.test.ts[17-17]

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

## Issue description
The new functional regression coverage does not identify issue 12874, even though the change closes that issue and the compliance rule requires an issue reference when applicable.
## Fix Focus Areas
- packages/typeorm/test/functional/columns/dialect-types/dialect-types.test.ts[17-17]
## Recommended Fix
Add a concise comment near the top-level suite declaration identifying it as regression coverage for GitHub issue #12874.

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


12. Valid maximum lengths fail startup ✓ Resolved
Description
resolveDriverColumn parses parenthesized overrides using digits only and rejects every unmatched
parenthesis as invalid. Using supported declarations such as nvarchar(max) on SQL Server or
string(max) and bytes(max) on Spanner therefore aborts data-source initialization before schema
generation.
Code

packages/typeorm/src/metadata/ColumnMetadata.ts[R129-131]

+        const parameterized = /^(.+?)\(\s*(\d+)\s*(?:,\s*(\d+)\s*)?\)$/.exec(
+            override.trim(),
+        )
Evidence
The new regular expression accepts only decimal digits, and the following branch rejects any other
parenthesized syntax. SQL Server emits any nonempty column length inside parentheses, while Spanner
explicitly uses max as the default length for its string and bytes types and emits it the same
way, proving these are supported physical declarations that the new parser prevents from reaching
the drivers.

packages/typeorm/src/metadata/ColumnMetadata.ts[129-137]
packages/typeorm/src/driver/sqlserver/SqlServerDriver.ts[768-800]
packages/typeorm/src/driver/spanner/SpannerDriver.ts[572-597]
packages/typeorm/src/schema-builder/util/TableUtils.ts[7-14]

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

## Issue description
The dialect override parser accepts only numeric parameters, although length-bearing SQL Server and Spanner types support `max` inside their declarations.
## Fix Focus Areas
- packages/typeorm/src/metadata/ColumnMetadata.ts[129-195]
## Recommended Fix
Extend parameter parsing to recognize `max` as a single length parameter, accept it only for types listed in the active driver's length-bearing types, assign it to `physical.length`, and continue rejecting it for precision, scale, and two-parameter declarations. Add initialization and schema-generation tests for SQL Server `nvarchar(max)` and Spanner `string(max)` or `bytes(max)` overrides.

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



Remediation recommended

13. Collated columns break on other databases 🐞 Bug ≡ Correctness ⭐ New
Description
clearIncompatibleModifiers clears collation only for MySQL-family and PostgreSQL/Aurora
PostgreSQL. On CockroachDB, SQL Server and Oracle, an override to a type that cannot take a
collation still carries the inherited collation, and their query runners always add COLLATE
whenever column.collation is set, so creating or altering the table fails with a DDL error.
Code

packages/typeorm/src/metadata/ColumnMetadata.ts[R185-190]

+            if (
+                (driver.options.type === "postgres" ||
+                    driver.options.type === "aurora-postgres") &&
+                !postgresCollatableTypes.has(normalizedType)
+            )
+                physical.collation = undefined
Evidence
The projection clears collation only inside the MySQL-family and postgres/aurora-postgres branches.
TableUtils.createTableColumnOptions copies columnMetadata.collation from the projected column.
The CockroachDB, SQL Server and Oracle column builders each add a COLLATE clause whenever collation
is set, without checking the type. For example, `type: "nvarchar", collation:
"Latin1_General_CI_AS", dialectTypes: { mssql: "int" } becomes int COLLATE Latin1_General_CI_AS`,
which SQL Server rejects. A Cockroach string with a collation overridden to bytes fails the same
way. This is the same bug class that was fixed for PostgreSQL, left unfixed in sibling drivers that
also emit COLLATE.

packages/typeorm/src/metadata/ColumnMetadata.ts[159-191]
packages/typeorm/src/driver/sqlserver/SqlServerQueryRunner.ts[4289-4289]
packages/typeorm/src/driver/cockroachdb/CockroachQueryRunner.ts[4530-4530]
packages/typeorm/src/driver/oracle/OracleQueryRunner.ts[3377-3377]

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

## Issue description
`resolveDriverColumn`'s `clearIncompatibleModifiers` drops an inherited `collation` only on MySQL-family and PostgreSQL drivers. CockroachDB, SQL Server and Oracle always emit `COLLATE` when collation is set, so overriding a collated character column to a non-character type produces invalid DDL.

## Fix Focus Areas
- packages/typeorm/src/metadata/ColumnMetadata.ts[41-47]
- packages/typeorm/src/metadata/ColumnMetadata.ts[159-191]

## Recommended Fix
Add a per-driver set of collatable types (after normalization) for cockroachdb (string/character varying/char/text), mssql (char, varchar, nchar, nvarchar, text, ntext) and oracle (char, nchar, varchar2, nvarchar2, clob, nclob). Clear `physical.collation` when the normalized physical type is not in the set for the current driver. Add unit tests for each driver.

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


14. MariaDB rejects timestamp overrides 🐞 Bug ≡ Correctness
Description
TableUtils.createTableColumnOptions checks timestamp default precision for MySQL and Aurora MySQL
overrides but omits MariaDB; EntityMetadataValidator makes the same omission for ON UPDATE. When
a MariaDB override changes a column to datetime(3) while an inherited bare CURRENT_TIMESTAMP(6)
default or update clause remains, the conflicting declaration passes these checks and reaches table
creation.
Code

packages/typeorm/src/schema-builder/util/TableUtils.ts[R45-46]

+            (driver.options.type === "mysql" ||
+                driver.options.type === "aurora-mysql") &&
Evidence
DriverFactory constructs MysqlDriver for MariaDB. The new default and update precision checks
exclude its driver name, while table-column options retain the default and onUpdate, and
MysqlQueryRunner emits both clauses in the column declaration.

packages/typeorm/src/driver/DriverFactory.ts[47-50]
packages/typeorm/src/schema-builder/util/TableUtils.ts[23-26]
packages/typeorm/src/schema-builder/util/TableUtils.ts[43-60]
packages/typeorm/src/metadata-builder/EntityMetadataValidator.ts[152-169]
packages/typeorm/src/driver/mysql/MysqlQueryRunner.ts[3582-3584]

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

## Issue description
MariaDB uses `MysqlDriver`, but the new physical timestamp precision checks exclude it. Conflicting inherited `DEFAULT` or `ON UPDATE` clauses therefore reach MariaDB DDL.
## Fix Focus Areas
- packages/typeorm/src/schema-builder/util/TableUtils.ts[43-60]
- packages/typeorm/src/metadata-builder/EntityMetadataValidator.ts[152-169]
## Recommended Fix
Include `mariadb` in both driver checks. Add regression cases for mismatched MariaDB default and update precision.

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


15. Maximum-length columns get invalid binds 🐞 Bug ≡ Correctness
Description
parametrizeValue passes physicalColumn.length unchanged to MssqlParameter, so a
varchar(max), nvarchar(max), or varbinary(max) override supplies the string "max" where the
parameter contract requires a numeric length. When an insert or update supplies a value for such a
column, the query runner forwards that string to the native SQL Server type constructor.
Code

packages/typeorm/src/driver/sqlserver/SqlServerDriver.ts[1053]

+                physicalColumn.length as any,
Evidence
The resolver stores the literal "max"; the changed call passes it as a bind length despite the
local parameter class declaring numeric lengths, and the query runner forwards it to native type
constructors.

packages/typeorm/src/metadata/ColumnMetadata.ts[226-239]
packages/typeorm/src/driver/sqlserver/SqlServerDriver.ts[1045-1054]
packages/typeorm/src/driver/sqlserver/MssqlParameter.ts[25-55]
packages/typeorm/src/driver/sqlserver/SqlServerQueryRunner.ts[4400-4443]

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

## Issue description
SQL Server MAX overrides store `"max"` as the physical length, but input parameter constructors require numeric length arguments.
## Fix Focus Areas
- packages/typeorm/src/driver/sqlserver/SqlServerDriver.ts[1045-1054]
- packages/typeorm/src/driver/sqlserver/MssqlParameter.ts[25-55]
- packages/typeorm/src/driver/sqlserver/SqlServerQueryRunner.ts[4400-4443]
## Recommended Fix
Translate a physical MAX length to the SQL Server driver's appropriate numeric MAX parameter value before constructing `MssqlParameter`. Cover inserts and updates for the three supported MAX types.

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


View review recommended (1)
16. Bulk inserts slow down with overridden columns 🐞 Bug ➹ Performance
Description
resolveDriverColumn does no caching. Each call clones the column and the whole entityMetadata,
mapping every column, unique and index, and InsertQueryBuilder.createValuesExpression,
UpdateQueryBuilder and SelectQueryBuilder call it for every column of every value set and every
selection. A bulk insert of R rows into an entity with K overridden columns therefore allocates
about R×K full metadata copies (each O(columns+uniques+indices)), on hot query-building paths that
did no extra work before.
Code

packages/typeorm/src/query-builder/InsertQueryBuilder.ts[R1638-1640]

+            const physicalColumn = column.resolveDriverColumn(
+                this.dataSource.driver,
+            )
Evidence
resolveDriverColumn returns early only when there is no override. Otherwise it runs
Object.assign/Object.create and maps columns, uniques and indices on every call. The
insert value loop calls it once for each column of each row.

packages/typeorm/src/metadata/ColumnMetadata.ts[107-158]
packages/typeorm/src/query-builder/InsertQueryBuilder.ts[1632-1648]
packages/typeorm/src/query-builder/SelectQueryBuilder.ts[3014-3020]
packages/typeorm/src/query-builder/UpdateQueryBuilder.ts[603-607]

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

## Issue description
`ColumnMetadata.resolveDriverColumn` rebuilds a cloned column and a cloned entity metadata on every call, and query builders call it once per column per row.
## Fix Focus Areas
- packages/typeorm/src/metadata/ColumnMetadata.ts[107-281]
- packages/typeorm/src/query-builder/InsertQueryBuilder.ts[1638-1640]
## Recommended Fix
Store the resolved projection in a private field (or a WeakMap keyed by driver) on the ColumnMetadata, and return the cached instance on later calls with the same driver. Clear the cache when `build()` runs or when `type`/`dialectTypes` change. Alternatively, resolve once outside the per-row loop in `createValuesExpression`.

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


  • Author self-review: I have reviewed the code review findings, and addressed the relevant ones.

Grey Divider

Context sources
Review mode: Auto: 🧠 Deep: Cross-cutting dialect, schema, query, and driver logic has many independent failure paths.

Grey Divider

Tip of the day
💡 Did you know, you can tweak Display settings with a live preview to see your comment before it ships

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

- resolve the effective physical type in one place: ColumnMetadata
  .resolveDriverType(driver) is now used by table column creation,
  the changed-column comparison of the postgres, mysql and sqlite
  drivers, and rename detection, so synchronize settles on columns
  whose override differs from their logical type
- validate only the base name of an override (varchar in varchar(10)),
  since the value is written verbatim and driver lists hold
  unparameterized names
- propagate dialectTypes to generated foreign key, junction and closure
  columns, so derived keys keep the physical type of the column they
  reference
- declare the option on ColumnCommonOptions, so the type-first
  @column overloads accept it too
- forward dialectTypes through EntitySchemaTransformer and add it to
  EntitySchemaColumnOptions
- move the test from test/github-issues to
  test/functional/columns/dialect-types and add a case asserting a
  second synchronize produces no changes
@xue-ding-e

Copy link
Copy Markdown
Author

Thanks for the review — pushed 4fd5664 addressing the findings:

  1. Coverage location — moved the suite to test/functional/columns/dialect-types/ and renamed it away from the issue framing.
  2. Sync never settles — agreed, this was the real one. The effective physical type is now resolved in a single place, ColumnMetadata.resolveDriverType(driver), and that is what the postgres/mysql/sqlite findChangedColumns comparisons and the rename detection in RdbmsSchemaBuilder use — same source as table column creation. Added a regression test that runs synchronize a second time and asserts no tables changed (passing locally on postgres + better-sqlite3, where both entities carry an override that differs from the logical type).
  3. Parameterized overrides fail startup — validation now checks only the base name ("varchar" in "varchar(10)"); the full value still goes to DDL verbatim.
  4. Overridden key types break relations — RelationJoinColumnBuilder, both junction column constructions and the closure junction builder now copy dialectTypes from the referenced column.
  5. Typed column declarations do not compile — the option moved to ColumnCommonOptions, so the type-first @Column(...) overloads accept it as well.
  6. Schema-based entities lose overrides — added to EntitySchemaColumnOptions and forwarded in EntitySchemaTransformer.

Locally re-verified: tsc --noEmit clean, test/unit (139), test/functional/columns (33) and the metadata-builder/entity-metadata suites all pass, with the two new cases in the functional suite covering the override selection per driver and the sync convergence.

Resolve physical type, length, precision and scale in one schema metadata
projection. Use the existing driver normalization for validation,
creation and change detection, while preserving logical value metadata
and unique constraint column references.

Reject blank and invalid parameter overrides, constrain driver keys,
and document fallback and value-conversion requirements. Add functional
coverage for type aliases, numeric modifiers, uniqueness, invalid values
and an empty second synchronization query plan.

Developed and validated with AI assistance.
@xue-ding-e

Copy link
Copy Markdown
Author

Updated in 3c23f9e, with upstream master merged in 36e3c0a.

  • Added the option to the entity guide and decorator reference.
  • Normalized physical types and their parameters through the existing drivers. The regression cases include int, varchar(010), decimal(10,2) and timestamp precision, while retaining unique constraints and the original logical metadata.
  • Rejected blank overrides and invalid parameter combinations during initialization.
  • Restricted TypeScript driver keys to DatabaseType, with a compile-time typo check.

The second-sync test now asserts that both generated query lists are empty, in addition to comparing the resulting schema. This catches repeated alterations that leave the same final table definition.

Type checking, compilation and lint passed. The full local suite on PostgreSQL 17.9 with PostGIS/pgvector and better-sqlite3 finished with 3091 passing, 176 pending and no failures. AI assistance was used for these changes and this follow-up.

Clear inherited length, precision and scale when an unparameterized
physical type cannot use them. Preserve compatible modifiers and leave
the original logical column metadata unchanged.

Cover varchar-to-text and decimal-to-double overrides, retained
modifiers, and a second synchronization without DDL. Add SAP driver
unit tests for plain timestamp and rejected timestamp parameters,
consistent with the documented HANA column type syntax.

Developed and validated with AI assistance.
@xue-ding-e

Copy link
Copy Markdown
Author

Addressed the inherited-modifier finding in 072ba6e. An unparameterized override now keeps only compatible modifiers: varchar(40) can map to PostgreSQL text, and decimal(12,4) can map to double precision. Compatible length/precision/scale settings are preserved. The regression cases also check that the second synchronization generates no DDL and the original logical metadata is unchanged.

For the SAP timestamp finding, the documented CREATE TABLE grammar lists TIMESTAMP without parameters. The precision argument belongs to the CURRENT_TIMESTAMP function, not that column type. The formatter's ability to append a number does not establish supported HANA syntax, so the resolver still rejects sap: "timestamp(3)". Three tests using the real SapDriver with a stub client cover plain timestamp DDL and rejection of parameterized variants. No live SAP server was used.

Compilation, lint, formatting and commit hooks passed. The full local PostgreSQL 17.9/PostGIS/pgvector + better-sqlite3 suite finished with 3094 passing, 176 pending and no failures. AI assistance was used for the implementation, verification and this response.

Resolve physical column metadata for typed parameters and OUTPUT table
variable declarations. Include length, precision and scale while
preserving explicit MssqlParameter values and logical entity metadata.

Keep computed expressions out of the temporary table definition.
Cover Unicode and decimal values through real INSERT, UPDATE and find
operations, plus declaration and parameter unit tests.

Developed and validated with AI assistance.
@xue-ding-e

Copy link
Copy Markdown
Author

Fixed the SQL Server finding in 812ebd8. Both @OutputTable and typed query parameters now use the resolved physical type, length, precision and scale. Explicit MssqlParameter values keep their existing priority; entity value conversion, hydration and the original column metadata are unchanged.

The regression tests exercise actual SQL Server INSERT/UPDATE OUTPUT with Unicode and decimal values, then ordinary parameterized writes and finds (including In). They also check nullable/plain columns, computed-column declarations, metadata after queries, and no schema changes on a second sync.

Compilation, lint, formatting and commit hooks passed. The full local suite with PostgreSQL 17.9/PostGIS/pgvector, SQL Server 2025 Express 17.0.5005.3 and better-sqlite3 finished with 3101 passing, 176 pending and no failures. AI assistance was used for implementation, verification and this response.

Use physical column types for PostgreSQL extension installation and
Oracle RETURNING out binds. Preserve UUID generation and exclusion
constraint handling, and leave logical value metadata unchanged.

Remove unreachable negative checks after unsigned parameter parsing
while retaining overflow validation. Cover extension setup, live citext
queries, Oracle bind types and rejected parameter inputs.

Developed and validated with AI assistance.
@xue-ding-e

Copy link
Copy Markdown
Author

Fixed PostgreSQL extension detection and removed the redundant negative checks in 2d99cb8.

Extension setup now checks the resolved physical column types for citext, hstore, cube, spatial types, ltree and vector/halfvec. UUID generation and exclusion-constraint detection retain their existing behavior. The regression test records the extension-creation requests; a real PostgreSQL test also created a citext column from a logical varchar, verified case-insensitive lookup, and produced an empty second-sync query plan. The test database did not have citext installed before this run.

The parser still rejects signed parameters and numeric overflow; only the unreachable checks after unsigned digit parsing were removed.

The same audit found an Oracle RETURNING gap: OUT binds now use the physical type, with unit coverage for text-to-clob and bytea-to-blob plus unchanged fallback cases. Oracle was not tested against a live server.

Final local suite with PostgreSQL, SQL Server and better-sqlite3 enabled: 3107 passing, 176 pending, no failures. Compilation, lint, formatting and commit hooks passed. AI assistance was used for implementation, verification and this response.

Use Oracle RAW output bindings sized to the physical column. Propagate
referenced type overrides to explicit join columns as well as virtual ones.

Reject zero numeric precision and invalid SQL Server decimal and vector
parameters. Preserve PostgreSQL scale greater than precision and zero
time precision, with database and query-builder regression coverage.
Remove redundant fixture comments.

Developed and validated with AI assistance.
@xue-ding-e

Copy link
Copy Markdown
Author

Addressed the new findings in 4f1727b:

  • Oracle RAW RETURNING now uses DB_TYPE_RAW and a buffer sized to the physical column, including values larger than the client's default 200 bytes.
  • Explicit join columns inherit the referenced column's dialectTypes, consistent with the existing logical-type inheritance. PostgreSQL tests cover both missing and conflicting FK overrides, relation writes/reads, and an empty second-sync plan.
  • SQL Server overrides reject invalid decimal precision/scale and missing or out-of-range vector dimensions. Valid vector(3), inherited dimensions and datetime2(0) are covered.
  • Removed the two redundant fixture comments.

The decimal check is driver-specific: PostgreSQL allows scale greater than precision, so decimal(2,5) remains valid there. Its schema, value round-trip and second-sync test runs on PostgreSQL 15+; zero numeric precision is rejected without rejecting zero time precision.

Compilation, lint, formatting and commit hooks passed. The full local PostgreSQL/SQL Server/better-sqlite3 run had 3114 passing, 176 pending and no failures. After isolating the PostgreSQL 15+ case, the final focused run passed all 28 tests. Oracle RAW coverage uses driver/query-builder tests, not a live Oracle server. AI assistance was used for these changes, verification and this response.

Recognize MAX only for SQL Server variable-length text and binary types
and Spanner string and bytes. Keep numeric parameter validation and
discard an inherited MAX length when the replacement type cannot use it.

Cover metadata validation, schema declarations and long SQL Server
parameter/OUTPUT values, plus Spanner emulator reads and synchronization.
Document the supported types and link the functional suite to issue typeorm#12874.

Developed and validated with AI assistance.
@xue-ding-e

Copy link
Copy Markdown
Author

Fixed MAX-length overrides and added the linked issue comment in a6b87ed.

The parser accepts max for SQL Server varchar, nvarchar and varbinary, and Spanner string and bytes. Fixed-length types and precision/scale arguments still reject it. An inherited MAX length is retained only when the replacement type supports it. The docs now list these forms.

SQL Server integration tests create the MAX columns, bind and return 9000-character ASCII text, 5000-character Unicode text and 9000-byte binary data, then read the saved values back. A new Spanner emulator test creates STRING(MAX)/BYTES(MAX), saves and reads values beyond the logical length, and checks that a second sync generates no DDL. Logical metadata remains unchanged.

TypeScript compilation, lint, formatting and commit hooks passed. The full local PostgreSQL/SQL Server/better-sqlite3 run finished with 3138 passing, 176 pending and no failures. The final focused run with those databases plus the repository's Spanner emulator passed all 52 tests. AI assistance was used for implementation, verification and this response.

Use the physical column type to select spatial write constructors,
PostgreSQL casts and read expressions. Keep the original logical column
metadata for value preparation and hydration.

Cover geometry/geography, spatial/non-spatial replacements and
unmodified columns across PostgreSQL, SQL Server and MySQL, with Aurora
query-builder coverage. Scope a second-sync fixture to its own table.

Developed and validated with AI assistance.
@xue-ding-e

Copy link
Copy Markdown
Author

Addressed the spatial write finding in 38df651. INSERT and UPDATE now choose the spatial constructor and PostgreSQL cast from the resolved physical column. SELECT uses the same physical type for spatial read expressions. Value preparation, hydration and the original logical column metadata remain unchanged.

The new unit cases cover 13 geometry/geography and spatial/non-spatial combinations across PostgreSQL, SQL Server, MySQL and Aurora MySQL, including legacy MySQL spatial functions. Real PostgreSQL, SQL Server and MySQL tests create the physical columns, insert, update and read compatible values, then check that the next schema sync emits no DDL. The affected dialect-types suite passed all 20 tests after its table comparison was scoped to the table it owns.

TypeScript compilation, lint, formatting and commit hooks passed. A broader four-driver run had 3154 passing and 176 pending, with one failure in the prior all-tables test comparison; that test was narrowed and then passed in the focused run. The unrelated #4753 test targets fixed localhost:3306 and was excluded because that port belongs to another local service. AI assistance was used for the changes, verification and this response.

A named logical enum mapped to PostgreSQL varchar or text retained its enum name in the physical schema projection. Introspection then reported a change on every sync and rebuilt associated constraints. Clear enum values and names only when the resolved physical type is not enum.

Cover parameterized and plain overrides with a real PostgreSQL schema containing a foreign key, index and unique constraints. Verify the second schema plan is empty and logical metadata remains unchanged.

Developed and validated with AI assistance.
When an unsigned numeric column is mapped to varchar, or a character column to int, the projected physical metadata retained clauses that MySQL rejects. Keep UNSIGNED only for numeric physical types and charset/collation only for character types across MySQL-family drivers.

Cover incompatible parameterized and plain overrides, retained compatible modifiers, real MySQL table creation, and an empty second schema plan. Document the modifier behavior.

Developed and validated with AI assistance.
DDL normalizes defaults from the resolved physical column, but INSERT fallbacks used logical metadata. A logical SQLite jsonb column overridden to text therefore wrote a JSONB blob when the value was omitted. Resolve the physical column before normalizing inlined defaults while leaving supplied value conversion unchanged.

A SQLite regression failed with blob storage before the fix and now reads text JSON with an empty second schema plan. Existing SQLite JSONB tests also pass. Document the default behavior.

Developed and validated with AI assistance.
A geometry column mapped to a nonspatial physical type retained feature type and SRID in its schema projection. PostgreSQL introspection omitted those fields, causing every subsequent synchronization to plan another alteration. Clear them only when the normalized physical type is not spatial.

Extend the spatial integration fixture with a geometry-to-jsonb column carrying Point and SRID. The second schema plan contained one query before the fix and is empty afterward; logical metadata and spatial overrides remain intact.

Developed and validated with AI assistance.
Keep schema default comparisons aligned with dialect-specific metadata.
Reject incompatible generated types and temporal precision without
rewriting expressions or evaluating default factories twice.
Add regressions for metadata, DDL and repeated schema synchronization.

AI-assisted implementation and validation.

Copy link
Copy Markdown
Author

Addressed the four remaining findings in this review with 5ed655a, after reproducing the failures against the preceding head.

The follow-up filters incompatible ON UPDATE/collation modifiers, validates PostgreSQL generated types, and preserves explicit physical length/precision/scale during introspection. It also rejects bare MySQL CURRENT_TIMESTAMP/NOW precision conflicts without rewriting expressions or evaluating a default factory twice. DEFAULT validation is at DDL-option construction, not a guarantee that an entire synchronization has performed no earlier writes.

The new unit cases went from 61 passing / 48 failing to 109 passing; 157 related unit cases passed. Both native DB regressions failed before the patch, and the complete dialect-types test file passed 9 tests with isolated PostgreSQL 17.11 and MySQL 8.4.11. Type checking passed; package ESLint completed with 0 errors and 7,751 warnings. Full-matrix and live MariaDB/Aurora validation remain unrun for this follow-up. Details and limits are in the updated PR description.

AI-assisted implementation, testing and report.

Copy link
Copy Markdown
Author

Additional validation is now available for product head de58583 in this passing focused fork run: locked-dependency full repository formatting and compilation, 157 related unit tests, and 226 passing / 1 existing pending native test cases across the configured PostgreSQL 17.11, MySQL 8.4.11, MariaDB 10.11.16 and SQLite drivers.

The first MariaDB run caught an incorrect JSON introspection assertion. Commit 44d1b15 expects MariaDB's LONGTEXT alias and verifies the JSON_VALID CHECK, while the empty second-sync assertion remains passing. Commit de58583 fixes the new unit file's formatting under the repository's locked Prettier 3.9.6. No production behavior was changed by these two follow-ups.

The QA workflow is separate from this PR. This is focused fork evidence, not a full matrix or an upstream CI pass; upstream checks still require maintainer approval. The PR description records the exact scope and remaining untested drivers. AI assistance was used for these fixes and verification.

@noahisdai

noahisdai commented Oct 14, 2026 •

Copy link
Copy Markdown

Hi @xue-ding-e, thanks for this PR. I worked on fixes for a few of the open review findings. Would you like me to open a PR to your fork, or share the branch so you can pick what's useful?

@xue-ding-e

Copy link
Copy Markdown
Author

Thanks @noahisdai. Please open a PR against xue-ding-e/typeorm:master (currently de58583be0efef3f7dba305b872fcfd490393261) and link it here. Separate commits for independent findings, with regression tests and the drivers actually tested, would make it easier to review and integrate without duplicating your work. If a PR to the fork is inconvenient, a branch URL and commit SHA are fine too.

AI-assisted coordination.

@noahisdai

Copy link
Copy Markdown

@xue-ding-e
I opened a pull request xue-ding-e#1

This branch has not been deployed

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

Labels

linked-issue PR references an issue

Development

Successfully merging this pull request may close these issues.

Support per-dialect column types on @Column, e.g. dialectTypes: { postgres: 'jsonb' }

2 participants