From fff05bfa6c3f716b7d62e9abc5d24a65ce5bfc9c Mon Sep 17 00:00:00 2001 From: Rik Smale Date: Wed, 29 Jul 2026 12:01:43 +0000 Subject: [PATCH] docs: empty Op.or & Op.not now throw in v7 Rewrites the "Changes to empty OR & NOT operators" section of the v7 upgrade guide to match sequelize/sequelize#18286, which makes an empty Op.or or Op.not throw instead of compiling to no condition at all. The section previously documented the alpha behaviour introduced by sequelize/sequelize#15598 as final. Also drops the stale `not({})` example: v7 exports `and()` and `or()`, but there is no `not()` helper. Co-Authored-By: Claude Opus 5 (1M context) --- docs/other-topics/upgrade.md | 62 +++++++++++++++++++++++++++++------- 1 file changed, 50 insertions(+), 12 deletions(-) diff --git a/docs/other-topics/upgrade.md b/docs/other-topics/upgrade.md index b1f7bc759..620b17428 100644 --- a/docs/other-topics/upgrade.md +++ b/docs/other-topics/upgrade.md @@ -1046,28 +1046,65 @@ You can now write the following: sql.where(sql.attribute('firstName'), Op.like, 'foo'); ``` -### Changes to empty `OR` & `NOT` operators +### Empty `OR` & `NOT` operators now throw -_Pull Request [#15598]_ +_Pull Requests [#15598] & [#18286]_ + +In Sequelize 6, both `Op.or` and `Op.not` produced `'0=1'` if their object or array was empty. +Sequelize 7 alphas `7.0.0-alpha.24` through `7.0.0-alpha.48` ignored them entirely instead, producing no condition at all. + +Both behaviors could go wrong quietly: `0=1` silently matched no rows, while ignoring the operator silently matched +_every_ row. The latter is the more dangerous of the two, because it also applies to `Model.update` and `Model.destroy`, +where it defeated the safeguard that refuses to run those methods without a `where` option. -Both `Op.or` and `Op.not` used to produce `'0=1'` if their object or array was empty. Both of them are now completely ignored instead: +Starting with Sequelize 7, an empty `Op.or` or `Op.not` throws instead: ```ts -User.findAll({ - where: or([]), -}); +// All of the following throw: +User.findAll({ where: { [Op.or]: [] } }); +User.findAll({ where: { [Op.or]: {} } }); +User.findAll({ where: { [Op.not]: [] } }); +User.findAll({ where: { [Op.not]: {} } }); +User.findAll({ where: or([]) }); +User.findAll({ where: or({}) }); + +// Including when the operator is used on an attribute: +User.findAll({ where: { firstName: { [Op.or]: [] } } }); + +// And when the empty operator is nested inside another condition: +User.findAll({ where: { [Op.and]: [{ firstName: 'zoe' }, { [Op.or]: [] }] } }); +``` -User.findAll({ - where: not({}), -}); +If you were relying on either of the previous behaviors, say which one you meant: + +```ts +// To match no rows: +User.findAll({ where: sql`1 = 0` }); + +// To apply no condition at all: omit the operator entirely. +User.findAll(); ``` -Both produce the following query: +This most often comes up when the operand is built from a list that turned out to be empty. +Check the list before building the `where` option: -```sql -SELECT * FROM "users" +```ts +if (tenantIds.length === 0) { + return []; +} + +const users = await User.findAll({ + where: { [Op.or]: tenantIds.map(tenantId => ({ tenantId })) }, +}); ``` +:::info + +`Op.and` is not affected. An empty conjunction is vacuously true, so `{ [Op.and]: [] }` still produces no condition, +which is the correct SQL translation. Only `Op.or` and `Op.not` are ambiguous when empty. + +::: + ### Removed support for raw SQL in `json()` _Pull Request [#15598]_ @@ -1186,3 +1223,4 @@ stop working in a future major release. [#15292]: https://github.com/sequelize/sequelize/pull/15292 [#15598]: https://github.com/sequelize/sequelize/pull/15598 [#16514]: https://github.com/sequelize/sequelize/pull/16514 +[#18286]: https://github.com/sequelize/sequelize/pull/18286