Skip to content

docs: document the v7 changes to empty and null-bearing operand sets - #841

Draft
WikiRik wants to merge 1 commit into
mainfrom
docs/empty-op-or-not-throws
Draft

docs: document the v7 changes to empty and null-bearing operand sets#841
WikiRik wants to merge 1 commit into
mainfrom
docs/empty-op-or-not-throws

Conversation

@WikiRik

@WikiRik WikiRik commented Jul 29, 2026

Copy link
Copy Markdown
Member

Documents sequelize/sequelize#18324.
Draft — do not merge until that PR lands.

Supersedes the earlier version of this branch, which documented
#18286 — that PR made an empty Op.or / Op.not throw. #18324
covers the same cases, plus three more, and produces a false condition rather than throwing.

Why

The v7 upgrade guide has one section, ### Changes to empty OR & NOT operators, describing the behaviour introduced
by #15598: an empty Op.or or Op.not is completely ignored,
so where: or([]) produces SELECT * FROM "users" with no condition at all.

#18324 changes that, and three other things in the same area that the guide never covered. Op.in and Op.notIn are
not mentioned anywhere in upgrade.md today, so an upgrading user currently has nowhere to read about any of it.

What changed

### Empty OR & NOT operators match no rows (was ### Changes to empty OR & NOT operators)

  • Rewritten around the new behaviour: an empty disjunction is false, so these produce 0 = 1 rather than being
    ignored.
  • Keeps the alpha window (7.0.0-alpha.24 through 7.0.0-alpha.48) so readers upgrading between alphas can tell
    which behaviour they are on.
  • Lists the forms this covers, including the attribute-level and nested cases.
  • Adds the distinction between an Op.or that is empty and one with an empty member — a member carrying no
    condition contributes nothing rather than satisfying the disjunction, so building an Op.or from optional parts
    still works. This is the part most likely to trip someone up after reading "empty OR is false".
  • Notes that Sequelize 6 produced 0 = 1 for the top-level forms but dropped the attribute-level one entirely, so
    this is not purely a return to v6 behaviour.
  • :::info note that Op.and is deliberately unaffected, because an empty conjunction is the identity of AND.
    #18324 pins this with tests; the note is there so it does not get "fixed" for symmetry later.

### Empty IN & NOT IN operators (new)

{ [Op.in]: [] } produced IN (NULL)UNKNOWN rather than FALSE, so negating it matched no rows where it should
match every row. Both operators now produce a constant. Credits #18250 as well as #18324, since the Op.notIn half
landed there. Includes the note that generated-SQL assertions will differ.

### null inside an IN or NOT IN list (new)

x NOT IN (1, NULL) is UNKNOWN for every row and matched nothing; x IN (1, NULL) never matched a NULL. A null
in a list is now compared separately and means "or is null", consistent with how { age: null } already becomes
age IS NULL.

### Statements that modify rows reject a where that matches every row (new)

Model.update / destroy / increment / decrement now throw when the where you pass is true for every row on
its own. Covers what is and is not counted (conditions Sequelize injects, such as a scope or a paranoid model's
deletedAt clause, are not), the escape hatch, and that reads are unaffected. This is the only new throw in #18324
and the thing an upgrading user is most likely to hit at runtime.

Link references: added [#18250] and [#18324], removed [#18286].

Drive-by fix

The old example used not({}). There is no not() export in v7 — packages/core/src/sequelize.js exports and()
and or() only. Replaced with the Op.not object forms and or({}), which are real.

Checks

  • All documented outputs were run against the #18324 branch rather than taken from the PR description.
  • The Sequelize 6 comparisons were verified against a real sequelize@6.37.8 install, not from memory — which caught
    the attribute-level exception noted above.
  • prettier@3.8.2 --check docs/other-topics/upgrade.md clean (the pinned version).
  • Docusaurus site not built locally — the change is prose and fenced code blocks in an existing file, with no new
    links or components beyond the :::info admonition already used in this file.

Created by Opus 5 with Claude Code, supervised by @WikiRik.

@github-actions

Copy link
Copy Markdown

🚀 Website draft URL
📋 Deployment logs

Documents sequelize/sequelize#18324.

Rewrites the "Changes to empty OR & NOT operators" section: an empty Op.or or
Op.not now produces a false condition rather than being ignored, and an Op.or
with an empty *member* is distinguished from one that is empty. Notes that
Sequelize 6 produced `0 = 1` for the top-level forms but dropped the
attribute-level one, so this is not purely a return to v6 behaviour.

Adds three sections the guide never covered, since Op.in and Op.notIn were not
mentioned in it at all:

- empty IN / NOT IN, which produced `IN (NULL)` in v6 and every v7 alpha
- a null inside an IN / NOT IN list, now compared separately
- Model.update / destroy / increment / decrement rejecting a `where` that is
  true for every row on its own

Also replaces an example that used `not({})`, which is not exported in v7.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011hNgQwGLn8qFiAmn3ZNVLW
@WikiRik
WikiRik force-pushed the docs/empty-op-or-not-throws branch from fff05bf to 3a84805 Compare August 31, 2026 12:06
@WikiRik WikiRik changed the title docs: empty Op.or & Op.not now throw in v7 docs: document the v7 changes to empty and null-bearing operand sets Aug 31, 2026
@github-actions

Copy link
Copy Markdown

🚀 Website draft URL
📋 Deployment logs

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant