Skip to content

v3.8.0

Choose a tag to compare

@lehni lehni released this 11 Oct 22:20
· 265 commits to main since this release

What's new

  • The limitPerParent graph option applies limit(), offset() and first() of relations loaded with withGraphFetched() per parent instead of to all parents together, e.g. withGraphFetched('pets(latest)', { limitPerParent: true }). With page() / range() the relation query is limited as before. On MySQL before 8, relations with a limit are fetched with one query per parent. withGraphJoined() ignores the option and warns about it. #127 #429

Fixes

  • Behaviour change: update() validates field expression keys like 'meta:color' like patch() does since 3.3.0: the written parts are validated against the json column's schema, including $refs, without its required, while the rest of the object is validated normally. The json column counts as present and doesn't get its default. Before, the keys were validated as top-level properties: invalid values passed, additionalProperties: false rejected the keys, a required json column counted as missing, and its default replaced the whole stored value. #160
  • Behaviour change: keys containing : that the schema declares in properties, e.g. 'ns:name', are normal properties and columns: validated as properties and written to their column by patch(), update() and merge(). Before, patch() (since 3.3.0) and update() / fromJson() treated them as field expressions and wrote them as JSON paths. Properties that only $beforeValidate() declares are still treated as field expressions. #160
  • Behaviour change: insertGraph() throws a ValidationError of type InvalidGraph for #unrelate / #delete markers also with allowMarkers: true, where 3.7.0 skipped the marked models: an insert has nothing to unrelate or delete. allowMarkers stays in the InsertGraphOptions typings until 4.0, deprecated and without effect. #2410
  • Behaviour change: a root model marked with #unrelate or #delete gets the error that the markers can only be used for related models, in upsertGraph() and insertGraph(), also without allowMarkers. #164
  • Behaviour change: upsertGraph() with noUpdate, noDelete, noUnrelate and insertMissing runs as an upsert like any other upsertGraph() call. Models that already exist are left alone instead of failing with duplicate key errors, and markers allowed with allowMarkers take effect. Only insertGraph() runs as an insert.
  • Behaviour change: many-to-many patch() / update() with join table extras select the ids of the matching related rows first, patch the join rows, and then the related rows by these ids, in batches of 500 values. A filter on a column that the patch changes, e.g. .patch({ name: 'renamed', role: 'new' }).where('movies.name', 'old'), left the join rows unchanged. These patches run several statements, so use a transaction if they need to be atomic. #161
  • On MySQL, limit() / offset() of many-to-many patch() / update() with join table extras pick the related rows to update, and only their join rows are updated. Before, all matching join rows were updated. Other databases ignore a limit in updates, as before. #161
  • Behaviour change: asFindQuery() in static hooks always runs the find query and returns the affected rows. It returned the value the query was resolved with, e.g. 0 for a patch with nothing to set, or the value of resolve() / cancelQuery(), and threw TypeError: related.some is not a function for many-to-many patches that only change join table extras.
  • asFindQuery() in static hooks of many-to-many patchAndFetchById() / updateAndFetchById() keeps the query's filters
  • Behaviour change: the id condition of $query() and of patchAndFetchById(), updateAndFetchById() and the other *ById methods gets its own where group, so a user's orWhere() can't get around it: $query().patch(…).orWhere(b) runs id = 1 and (b) instead of id = 1 or b. The where groups are built when the SQL is generated, so asFindQuery() in static hooks no longer drops or repeats where clauses of relation queries, which regressed in 3.3.0. #2191
  • Behaviour change: withGraphJoined() selects the root table's columns instead of *, so the columns of joined tables no longer land on the root models under their plain names. distinct('*') keeps its distinct.
  • distinctOn() with withGraphJoined(), withGraphFetched() and joinRelated(): its columns were treated as selected, so the other columns were missing from the models, and joined relations could fail with "column does not exist".
  • Behaviour change: withGraphJoined() throws when the root query selects a different column under a name it selects itself, e.g. a joined table's column without an alias like select('Person.*', 'pets.id'), select('pets.*'), ref('Person.age').as('name') or 'Person.name as id', and raw SQL, subqueries or aggregates under the name of an id column it needs to build the graph. Before, SQL Server returned arrays of values and the other databases silently kept one of them, sometimes grouping the root models by the wrong id. Raw SQL, subqueries and aggregates under other names replace the column. Select such columns under a different name.
  • Selections are matched after wrapIdentifier mapping (knexSnakeCaseMappers()) for column names and aliases too, so withGraphFetched() no longer selects a relation column a second time and strips it from the result when it's selected under its snake_case name
  • Behaviour change: resultSize() with withGraphJoined() throws model … has no idColumn for models without a primary key if the joined relations include a to-many relation, as the root models can't be counted without ids. Since 3.3.0 it counted the joined rows instead, or failed on MySQL. With only to-one relations, the rows are counted. #164
  • On SQL Server, the order of a relation's modify or an onBuild hook is left out of the subqueries of many-to-many modify queries, withGraphJoined() and joinRelated(), including through.modify, and out of update() / patch() queries, unless they have a limit. SQL Server rejects these orders. Other databases are unchanged. #167
  • On SQL Server, update(), patch() and delete() of aliased tables go through a common table expression, as SQL Server can't alias the table of these statements
  • Behaviour change: on SQL Server, constraint, not-null and data errors are thrown as DBError and its subclasses (UniqueViolationError, ForeignKeyViolationError, NotNullViolationError, CheckViolationError, DataError), with the tedious error as nativeError, like on the other databases. Read err.nativeError.number instead of err.number. This also makes the fix for primary key violations from 3.7.0 work with knex. #2688
  • The warning about duplicate relations in relation expressions like '[a.b, a.c]' says that only the last one is used and that it will throw in objection 4.0, instead of announcing an error in objection 2.0 #164
  • Behaviour change: all warnings objection logs start with [objection] , and each is logged once #164
  • The errors for mixing withGraphJoined() and withGraphFetched() quote relation names with double quotes like other error messages

Docs

  • What relate().returning() returns on each database, and which relations accept an array of ids
  • onBuildKnex(): calls on the objection builder that map to knex methods are applied
  • The typings require TypeScript 5.1, and CI checks them with it. With TypeScript 5.0, methods of custom query builders like firstWithName() { return this.where(...).first(); } are implicitly any without a return type annotation.

Other

  • The knex peer dependency is >=2.3.0 <4, the versions objection is tested with #95
  • CI runs the integration tests on SQL Server #163