Skip to content

The construct "where.not" doesn't respect boolean algebra #31209

@fterrazzoni

Description

@fterrazzoni

Steps to reproduce

When two predicates A and B are given to a where clause, Rails combines them using AND, which is the expected behavior.

Model.where(col1: 1, col2: 2).to_sql

# => SELECT "models".* FROM "models" WHERE "models"."col1" = 1 AND "models"."col2" = 2

When the clause is negated (using where.not), Rails distributes the NOT to every predicate before combining them using AND. Why?

Model.where.not(col1: 1, col2: 2).to_sql

# => SELECT "models".* FROM "models" WHERE ("models"."col1" != 1) AND ("models"."col2" != 2)

Expected behavior

NOT(A AND B) = NOT(A) OR NOT(B)

In my example, the query should look like:

SELECT "models".* FROM "models" WHERE ("models"."col1" != 1) OR ("models"."col2" != 2)

Actual behavior

NOT(A AND B) = NOT(A) AND NOT(B) (wrong)

It's worth noting that the correct behavior can still be obtained by writing SQL directly:

Model.where.not('"models"."col1" = 1 AND "models"."col2" = 2').to_sql
# => SELECT "models".* FROM "models" WHERE (NOT ("models"."col1" = 1 AND "models"."col2" = 2))

It makes the construct where.not(...) behave very differently depending on the kind of arguments which are passed to it.

The culprit seems to be located here :

WhereClause.new(inverted_predicates)

System configuration

Rails version: 5.1.4

Ruby version: MRI 2.4.2

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions