Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

citext false positive #89

Closed
pirj opened this issue Oct 8, 2021 · 7 comments
Closed

citext false positive #89

pirj opened this issue Oct 8, 2021 · 7 comments
Assignees

Comments

@pirj
Copy link
Contributor

pirj commented Oct 8, 2021

Hey @djezzzl ! 馃憢

I have the following in db/schema.rb:

  enable_extension "citext"

  create_table "groups", force: :cascade do |t|
    t.citext "name", null: false
    t.index ["name"], name: "index_groups_on_name", unique: true

Model:

validates :name, uniqueness: { case_sensitive: false }

database_consistency complains:

MissingUniqueIndexChecker fail Group lower(name) model should have proper unique index in the database

psql mydb:

# \d groups
                                              Table "public.groups"
       Column       |              Type              | Collation | Nullable |                     Default
--------------------+--------------------------------+-----------+----------+-------------------------------------------------
 name               | citext                         |           | not null |
...
Indexes:
    "index_groups_on_name" UNIQUE, btree (name)

PostgreSQL citext extension docs: https://www.postgresql.org/docs/current/citext.html

I've debugged it, and:

sorted_uniqueness_validator_columns # => ["lower(name)"]
Helper.extract_index_columns(index.columns).sort # => ["name"]

馃挜

Does it make sense to remove/ignore lower( from the index column if this column's type is citext?

database_consistency version 1.1.2

@djezzzl
Copy link
Owner

djezzzl commented Oct 13, 2021

Hi @pirj ,

It makes total sense for me 馃憤 Would you mind to contribute?

@djezzzl djezzzl self-assigned this Nov 15, 2022
@djezzzl
Copy link
Owner

djezzzl commented Nov 16, 2022

Hi @pirj ,

I have finally a PR for this (#147) to be fixed but I tested the planner and I think we still would better have a proper (lower) index instead.

The reason is that ActiveRecord doesn't recognize citext and apply lower anyway:

Entity.new(email: 'TMPTMP').valid?
 Entity Exists? (0.3ms)  SELECT 1 AS one FROM "entities" WHERE LOWER("entities"."email") = LOWER($1) LIMIT $2  [["email", "TMPTMP"], ["LIMIT", 1]]
database_consistency_test=# \d tmp;
                Table "public.tmp"
 Column |  Type  | Collation | Nullable | Default 
--------+--------+-----------+----------+---------
 email  | citext |           |          | 
Indexes:
    "tmp_idx" btree (email)

database_consistency_test=# explain analyze select * from tmp where lower(email) = lower('asdfasdf');
                                             QUERY PLAN                                             
----------------------------------------------------------------------------------------------------
 Seq Scan on tmp  (cost=0.00..2040.00 rows=500 width=9) (actual time=26.415..26.415 rows=0 loops=1)
   Filter: (lower((email)::text) = 'asdfasdf'::text)
   Rows Removed by Filter: 100000
 Planning Time: 0.296 ms
 Execution Time: 26.429 ms
(5 rows)

database_consistency_test=# explain analyze select * from tmp where email = 'asdfasdf';
                                                    QUERY PLAN                                                    
------------------------------------------------------------------------------------------------------------------
 Index Only Scan using tmp_idx on tmp  (cost=0.42..4.44 rows=1 width=9) (actual time=0.044..0.045 rows=0 loops=1)
   Index Cond: (email = 'asdfasdf'::citext)
   Heap Fetches: 0
 Planning Time: 0.047 ms
 Execution Time: 0.057 ms
(5 rows)

And after adding a lower index:

database_consistency_test=# \d tmp;
                Table "public.tmp"
 Column |  Type  | Collation | Nullable | Default 
--------+--------+-----------+----------+---------
 email  | citext |           |          | 
Indexes:
    "tmp_idx" btree (lower(email::text))

database_consistency_test=# explain analyze select * from tmp where email = 'asdfasdf';
                                            QUERY PLAN                                            
--------------------------------------------------------------------------------------------------
 Seq Scan on tmp  (cost=0.00..1790.00 rows=1 width=9) (actual time=55.766..55.767 rows=0 loops=1)
   Filter: (email = 'asdfasdf'::citext)
   Rows Removed by Filter: 100000
 Planning Time: 0.134 ms
 Execution Time: 55.777 ms
(5 rows)

database_consistency_test=# explain analyze select * from tmp where lower(email) = lower('asdfasdf');
                                                 QUERY PLAN                                                  
-------------------------------------------------------------------------------------------------------------
 Index Scan using tmp_idx on tmp  (cost=0.42..8.44 rows=1 width=9) (actual time=0.040..0.041 rows=0 loops=1)
   Index Cond: (lower((email)::text) = 'asdfasdf'::text)
 Planning Time: 0.086 ms
 Execution Time: 0.054 ms
(4 rows)

With that said, I will abandon the PR and close the issue.

Please feel free to reopen it.

@djezzzl djezzzl closed this as completed Nov 16, 2022
@pirj
Copy link
Contributor Author

pirj commented Nov 16, 2022

Hey @djezzzl 馃憢

Thanks for giving it a shot.
There's this deficiency with a redundant lower that needs to be fixed on the Rails side.
At this stage I don't see how database_consistency could handle it.

@pirj
Copy link
Contributor Author

pirj commented Nov 25, 2022

rails/rails#46568 one done, one to go

@djezzzl
Copy link
Owner

djezzzl commented Nov 26, 2022

Wow! Well done! Unfortunately, my past experience contributing to ROR wasn't great so I didn't expect it's actually possible 馃榾

@pirj
Copy link
Contributor Author

pirj commented Nov 30, 2022

rails/rails#46592

@djezzzl
Copy link
Owner

djezzzl commented Nov 30, 2022

Wow, you do the magic! Thank you for improving Rails experience for all of us!

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

No branches or pull requests

2 participants