Fix _present / _blank producing always-UNKNOWN clause on non-string columns (#1552)#1678
Open
ryoya1122 wants to merge 1 commit into
Open
Conversation
…ns (activerecord-hackery#1552) The `present` and `blank` predicates ship a formatter that returns `[nil, '']` regardless of column type. For string-like columns this turns into the documented `column IS NOT NULL AND column != ''` (and the mirrored OR for `blank`). For non-string columns however ActiveRecord casts the empty string to the column type, which yields `NULL`, leaving the always-UNKNOWN `column != NULL` clause: Product.ransack(stock_present: true).result.to_sql # => SELECT "products".* FROM "products" # WHERE ("products"."stock" IS NOT NULL # AND "products"."stock" != NULL) The second half never matches, so `stock_present: true` returned 0 rows even when every row had a non-null stock. `Condition#format_predicate` now strips the empty string from `arel_values` when the attribute is not string-like (`:string`, `:text`, `:citext`). String/text columns keep the documented two-clause form; non-string columns reduce to `IS NOT NULL` / `IS NULL` only, which is what `Object#present?` means for those types in Rails.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1552.
Problem
The built-in
_present(and mirrored_blank) predicate produces broken SQL on every non-string column:The trailing
!= NULLis never true, sostock_present: truereturns 0 rows even when every row has a non-null stock. The reporter in #1552 hit the same shape on a Postgres string column with a unique partial index, but it reproduces just as cleanly on any boolean / integer / decimal / datetime column.String / text columns are unaffected — they correctly produce the documented
IS NOT NULL AND != ''form.Cause
The
present/blankpredicate definitions inlib/ransack/constants.rbship a column-type-agnostic formatter:Condition#format_predicatefeeds those values into Arel'sNOT_EQ_ALL/EQ_ANY. For non-string columns, ActiveRecord casts''to the column type, which isNULL, leaving the always-UNKNOWN comparison.Fix
Condition#format_predicatenow strips the empty-string entry fromarel_valueswhen the attribute is not a string-like column (:string,:text,:citext). The two-clause form is preserved on string / text columns; non-string columns reduce toIS NOT NULL(orIS NULLfor_blank) — which matches whatObject#present?means for those types in Rails.Compatibility
:string/:text/:citext_present(true)IS NOT NULL AND != ''IS NOT NULL AND != ''✓ unchanged:string/:text/:citext_blank(true)IS NULL OR = ''IS NULL OR = ''✓ unchanged:integer/:decimal/:boolean/:datetime/ etc._present(true)IS NOT NULL AND != NULL❌IS NOT NULL✓ fixed:integer/:decimal/:boolean/:datetime/ etc._blank(true)IS NULL OR = NULL❌IS NULL✓ fixedniltype (e.g. ransackers without explicit type)_present/_blankThe condition for stripping is intentionally narrow:
''— custom predicates that don't use that envelope are untouched.Tests
Four new specs in
spec/ransack/predicate_spec.rb, beside the existing string-column ones:_present/_blankonsalary(integer) emits onlyIS [NOT] NULL, never!= NULLor= NULLname(string) tests continue to pass, verifying the two-clause form is preservedFull suite:
516 examples, 0 failures, 1 pendingonv5.0.0+ this branch (SQLite, ActiveRecord 7.2.3.1, Ruby 3.4.9).Targets
v5.0.0per #1640.