Repository navigation
Conversation
Adds a per-entity `forceUndefined` option, available as `@Entity({
forceUndefined: true })`, `new EntitySchema({ forceUndefined: true })`
and `defineEntity({ forceUndefined: true })`. It takes precedence over
the global `forceUndefined` ORM option and base entities pass it down.
Hydration, the entity factory, orphaned reference handling and change
set normalization read it from the metadata.
With `defineEntity`, the option also drops `null` from the inferred
types, so `.nullable()` and `.strictNullable()` properties infer as `T |
undefined` instead of `T | null | undefined`. The marker lands in
`[Config]` (`TypeConfig` gains `forceUndefined`), same as `forceObject`,
and `extends` picks it up from the base entity's `[Config]`. Decorator
and `EntitySchema` entities declare their property types by hand, so for
them the option only changes the runtime.
The type-level swap runs once on the resolved entity type in the
overload return position instead of per property. Anything inside the
property chain gets re-instantiated on every inference round of the
call, which pushed the type benchmark up by 30% or more. The final shape
stays within the 10% threshold on every case.
Closes #8267
…8327) Conditions on a polymorphic relation like `{ imageable: { title: 'foo' } }` used to resolve only against the first target, so properties of the other targets threw as unknown. Now they go to the targets that define all the queried properties. A single matching target is joined directly. When several match, each is left joined and the branches are combined with `$or`, with each branch also requiring the target PK to be non-null. ```ts // only `Article` has `title` await em.find(Image, { imageable: { title: 'foo' } }); // both `Product` and `Article` have `name` await em.find(Image, { imageable: { name: 'shared' } }); ``` Primary key conditions follow the same rule. A PK that every target defines, like `{ imageable: { id: 1 } }`, is still compared on the FK column without a join and matches any type. A PK only some targets define, like `{ source: { code: 7 } }`, joins those targets so the type gets checked too. Ordering still resolves against the first target only. MongoDB throws for these conditions, same as for other non-reference polymorphic conditions. The branch includes a merge of `master` (#8326, #8328), so those commits show up here until `master` is merged into `next`.
MongoDB has no joins, so conditions like `{ author: { name: 'Jon' } }`
or `{ tags: { name: 't' } }`, and any condition on an inverse side like
`{ books: { title: 'b1' } }`, were sent as is and silently matched
nothing. When one of them sat in an `$or` next to a matching branch, the
query looked like it worked. They now throw an error that names the
relation.
Conditions by PK keep working. The serialized PK is now supported as
well: `{ author: { id: hexString } }` used to match nothing and now
compares the reference like `{ author: { _id } }` does.
This targets `next`, because queries that looked like they worked before
(a relation condition inside an `$or` that other branches satisfied) now
throw.
…8333) Ordering by a property of a polymorphic relation target used to resolve only against the first target, so `{ imageable: { title: 'asc' } }` threw when `title` lives on another target. Ordering by a property several targets define only used the first target's column. Each ordered property now goes to the targets that define it. A single target is left joined and ordered by its column. With several targets, each one is left joined (reusing joins from conditions on the same targets) and rows are ordered by `coalesce(...)` of their columns, so different types sort together: ```ts await em.find(Image, {}, { orderBy: { imageable: { name: 'asc' } } }); // order by coalesce(`p1`.`name`, `a2`.`name`) asc ``` Relations and formulas of the targets use the first target that defines them, and a PK that every target defines still orders by the FK column without a join. Ordering by a property of the first target no longer drops rows that point to other targets on non-nullable relations: it used an inner join. That part is also fixed separately on master. Limitations: the `orderBy` type only accepts properties all targets define, so a property of a single target needs a cast. Distributing `QueryOrderMap` over unions broke generic code in the core. When three or more ordered fields alternate between different target sets, fields with the same set are grouped, which can change their priority.
…columns (#8353) Adds a `sequence: { startWith, incrementBy }` option for autoincrement columns (decorators, `defineEntity` via `.sequence()`, `EntitySchema`). ```ts @PrimaryKey({ sequence: { startWith: 1000, incrementBy: 10 } }) id!: number; ``` In `defineEntity`, a number is a shorthand for the start value: `p.integer().primary().sequence(1000)`. The options are applied when the column is created, and `orm.schema.clear()` resets the counter back to `startWith`. The schema generator never diffs them on any driver, so changing them on an existing table produces no migration. Moving a live counter stays an explicit migration. That includes an autoincrement column added to an existing table. On PostgreSQL and MySQL its existing rows get numbered from `startWith` as well (PostgreSQL expands the `serial` column to a sequence created with the options upfront). On PostgreSQL, a column that becomes autoincrement starts at `startWith`, or continues after its highest existing value if that is higher. | Driver | On create | |---|---| | PostgreSQL `serial` | `alter sequence … start with … increment by … restart` (via `pg_get_serial_sequence`) | | PostgreSQL identity | `generated … as identity (start with … increment by …)` | | MySQL, MariaDB | `auto_increment = …` table option | | SQLite, libSQL | row in `sqlite_sequence` | | MSSQL | `identity(start, increment)` | | Oracle | `generated by default as identity (start with … increment by …)` | Descending increments and start values past the default sequence bounds (e.g. `startWith: 0`) work too. PostgreSQL and Oracle get the matching `minvalue`/`maxvalue` for them. MySQL, MariaDB and SQLite have no per-column increment, so `incrementBy` throws a metadata error there. So does `sequence` on a property that isn't autoincrement, or together with identity options written in `generated`. The entity generator reads the options back on PostgreSQL (serial and identity), MSSQL and Oracle, and emits `sequence` in all definition styles. MySQL, MariaDB and SQLite only store the next counter value, so there's nothing to read there. Two related changes: - The schema comparator ignores identity option lists in `generated` (`identity (start with …)`), so they follow the same create-only rule instead of dropping and re-adding the column. - MSSQL `clear()` picks the reseed value from `sys.identity_columns.last_value` instead of the number of deleted rows. This also fixes repeated `clear()` calls leaving the next id at 2 for plain autoincrement PKs. Closes #8350
Trigger body callbacks now receive a third `{ new, old, em }` argument. `new`/`old` map property names to the dialect's pseudo-row column references, and the callback can return a query builder, which is rendered without the `returning` clause (plus the `return` statement PostgreSQL requires). On MSSQL, inserts referencing the rows are rewritten to `insert ... select ... from inserted/deleted`.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## next #8373 +/- ##
==========================================
- Coverage 99.67% 99.66% -0.01%
==========================================
Files 293 293
Lines 32166 32211 +45
Branches 9238 9248 +10
==========================================
+ Hits 32060 32102 +42
- Misses 102 105 +3
Partials 4 4 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
I am not familiar enough with the quirks of MSSQL and PGSQL, so I feel compelled to ask... why these adjustments to query builder output? Do those triggers have some feature that a MySQL/MariaDB and Oracle trigger does not? |
|
It's the other way around, those adjustments are about what postgres and mssql triggers lack or do differently:
Oracle triggers aren't supported by the ORM yet. |
|
I see. Makes sense. Though it might be good to also expose these transformations in a method to the query builder, for the sake of users who may start with a query builder, and later change to a raw query, because of some adjustment that the builder can't handle... like having two DML queries, rather than one. idk, something like a |
Adds entity trigger support to the Oracle driver. It used to fall back to the generic `SchemaHelper.createTrigger`, which isn't valid Oracle DDL, and triggers were never introspected. - `createTrigger` emits `create or replace trigger "name" BEFORE|AFTER INSERT OR UPDATE OR DELETE on "table" [for each row] [when (...)] begin ... end;`, qualified with the schema when it isn't the default one. Oracle allows multiple events and both row and statement level in one trigger. `instead of` (views only), `truncate`, and `when` on a statement-level trigger throw an error. - `dropTrigger` uses `drop trigger if exists`, like the other Oracle drop statements (23c+). - Introspection reads `all_triggers` (the LONG `trigger_body` comes back as a string), so a freshly created trigger produces no diff. - The schema generator splits statements on `;\n`, which dropped the closing `;` of a PL/SQL block whenever another statement followed it in the same batch. `startsBatch` now also receives the previous line, and Oracle starts a new batch after a line ending in `end;`. Migrations already worked because each statement runs on its own. Once #8373 lands, Oracle needs a `triggerRowReference` override that renders `:new."col"` / `:old."col"`.
Prototype for #8372. Trigger body callbacks now get a third
{ new, old, em }argument, so a DML-only trigger (like writing a history row) can be built with the query builder and rendered for each dialect from one definition:new/oldmap property names to the pseudo-row column references (new."col", orinserted.[col]/deleted.[col]on MSSQL).returning/outputclause. PostgreSQL appendsreturn coalesce(new, old). MSSQL rewritesinsert ... values (inserted.x)toinsert ... select inserted.x from inserted, joiningdeletedon the PK when both rows are used.Still open:
emis typed asany, so the target entity only gets type-checked when you cast it toSqlEntityManager. The row references don't carry column types, and readingoldin an insert-only trigger isn't a type error. On MSSQL, only single-row inserts can reference the rows. No docs yet.