Added concern for case-insensitive WHERE queries
On PostgreSQL these queries use LOWER(...) to compare columns and values. For MySQL a regular WHERE is performed as MySQL is already case-insensitive.
This commit is contained in:
parent
85c6a3743a
commit
1190d0ab3d
2 changed files with 204 additions and 0 deletions
28
app/models/concerns/case_sensitivity.rb
Normal file
28
app/models/concerns/case_sensitivity.rb
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
# Concern for querying columns with specific case sensitivity handling.
|
||||||
|
module CaseSensitivity
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
module ClassMethods
|
||||||
|
# Queries the given columns regardless of the casing used.
|
||||||
|
#
|
||||||
|
# Unlike other ActiveRecord methods this method only operates on a Hash.
|
||||||
|
def case_insensitive_where(params)
|
||||||
|
criteria = self
|
||||||
|
cast_lower = Gitlab::Database.postgresql?
|
||||||
|
|
||||||
|
params.each do |key, value|
|
||||||
|
column = ActiveRecord::Base.connection.quote_table_name(key)
|
||||||
|
|
||||||
|
if cast_lower
|
||||||
|
condition = "LOWER(#{column}) = LOWER(:value)"
|
||||||
|
else
|
||||||
|
condition = "#{column} = :value"
|
||||||
|
end
|
||||||
|
|
||||||
|
criteria = criteria.where(condition, value: value)
|
||||||
|
end
|
||||||
|
|
||||||
|
criteria
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
176
spec/models/concerns/case_sensitivity_spec.rb
Normal file
176
spec/models/concerns/case_sensitivity_spec.rb
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe CaseSensitivity do
|
||||||
|
describe '.case_insensitive_where' do
|
||||||
|
let(:connection) { ActiveRecord::Base.connection }
|
||||||
|
let(:model) { Class.new { include CaseSensitivity } }
|
||||||
|
|
||||||
|
describe 'using PostgreSQL' do
|
||||||
|
describe 'with a single column/value pair' do
|
||||||
|
it 'returns the criteria for a column and a value' do
|
||||||
|
criteria = double(:criteria)
|
||||||
|
|
||||||
|
expect(connection).to receive(:quote_table_name).
|
||||||
|
with(:foo).
|
||||||
|
and_return('"foo"')
|
||||||
|
|
||||||
|
expect(model).to receive(:where).
|
||||||
|
with(%q{LOWER("foo") = LOWER(:value)}, value: 'bar').
|
||||||
|
and_return(criteria)
|
||||||
|
|
||||||
|
expect(model.case_insensitive_where(foo: 'bar')).to eq(criteria)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns the criteria for a column with a table, and a value' do
|
||||||
|
criteria = double(:criteria)
|
||||||
|
|
||||||
|
expect(connection).to receive(:quote_table_name).
|
||||||
|
with(:'foo.bar').
|
||||||
|
and_return('"foo"."bar"')
|
||||||
|
|
||||||
|
expect(model).to receive(:where).
|
||||||
|
with(%q{LOWER("foo"."bar") = LOWER(:value)}, value: 'bar').
|
||||||
|
and_return(criteria)
|
||||||
|
|
||||||
|
expect(model.case_insensitive_where('foo.bar': 'bar')).to eq(criteria)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'with multiple column/value pairs' do
|
||||||
|
it 'returns the criteria for a column and a value' do
|
||||||
|
initial = double(:criteria)
|
||||||
|
final = double(:criteria)
|
||||||
|
|
||||||
|
expect(connection).to receive(:quote_table_name).
|
||||||
|
with(:foo).
|
||||||
|
and_return('"foo"')
|
||||||
|
|
||||||
|
expect(connection).to receive(:quote_table_name).
|
||||||
|
with(:bar).
|
||||||
|
and_return('"bar"')
|
||||||
|
|
||||||
|
expect(model).to receive(:where).
|
||||||
|
with(%q{LOWER("foo") = LOWER(:value)}, value: 'bar').
|
||||||
|
and_return(initial)
|
||||||
|
|
||||||
|
expect(initial).to receive(:where).
|
||||||
|
with(%q{LOWER("bar") = LOWER(:value)}, value: 'baz').
|
||||||
|
and_return(final)
|
||||||
|
|
||||||
|
got = model.case_insensitive_where(foo: 'bar', bar: 'baz')
|
||||||
|
|
||||||
|
expect(got).to eq(final)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns the criteria for a column with a table, and a value' do
|
||||||
|
initial = double(:criteria)
|
||||||
|
final = double(:criteria)
|
||||||
|
|
||||||
|
expect(connection).to receive(:quote_table_name).
|
||||||
|
with(:'foo.bar').
|
||||||
|
and_return('"foo"."bar"')
|
||||||
|
|
||||||
|
expect(connection).to receive(:quote_table_name).
|
||||||
|
with(:'foo.baz').
|
||||||
|
and_return('"foo"."baz"')
|
||||||
|
|
||||||
|
expect(model).to receive(:where).
|
||||||
|
with(%q{LOWER("foo"."bar") = LOWER(:value)}, value: 'bar').
|
||||||
|
and_return(initial)
|
||||||
|
|
||||||
|
expect(initial).to receive(:where).
|
||||||
|
with(%q{LOWER("foo"."baz") = LOWER(:value)}, value: 'baz').
|
||||||
|
and_return(final)
|
||||||
|
|
||||||
|
got = model.case_insensitive_where('foo.bar': 'bar', 'foo.baz': 'baz')
|
||||||
|
|
||||||
|
expect(got).to eq(final)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'using MySQL' do
|
||||||
|
describe 'with a single column/value pair' do
|
||||||
|
it 'returns the criteria for a column and a value' do
|
||||||
|
criteria = double(:criteria)
|
||||||
|
|
||||||
|
expect(connection).to receive(:quote_table_name).
|
||||||
|
with(:foo).
|
||||||
|
and_return('`foo`')
|
||||||
|
|
||||||
|
expect(model).to receive(:where).
|
||||||
|
with(%q{LOWER(`foo`) = LOWER(:value)}, value: 'bar').
|
||||||
|
and_return(criteria)
|
||||||
|
|
||||||
|
expect(model.case_insensitive_where(foo: 'bar')).to eq(criteria)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns the criteria for a column with a table, and a value' do
|
||||||
|
criteria = double(:criteria)
|
||||||
|
|
||||||
|
expect(connection).to receive(:quote_table_name).
|
||||||
|
with(:'foo.bar').
|
||||||
|
and_return('`foo`.`bar`')
|
||||||
|
|
||||||
|
expect(model).to receive(:where).
|
||||||
|
with(%q{LOWER(`foo`.`bar`) = LOWER(:value)}, value: 'bar').
|
||||||
|
and_return(criteria)
|
||||||
|
|
||||||
|
expect(model.case_insensitive_where('foo.bar': 'bar')).to eq(criteria)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'with multiple column/value pairs' do
|
||||||
|
it 'returns the criteria for a column and a value' do
|
||||||
|
initial = double(:criteria)
|
||||||
|
final = double(:criteria)
|
||||||
|
|
||||||
|
expect(connection).to receive(:quote_table_name).
|
||||||
|
with(:foo).
|
||||||
|
and_return('`foo`')
|
||||||
|
|
||||||
|
expect(connection).to receive(:quote_table_name).
|
||||||
|
with(:bar).
|
||||||
|
and_return('`bar`')
|
||||||
|
|
||||||
|
expect(model).to receive(:where).
|
||||||
|
with(%q{LOWER(`foo`) = LOWER(:value)}, value: 'bar').
|
||||||
|
and_return(initial)
|
||||||
|
|
||||||
|
expect(initial).to receive(:where).
|
||||||
|
with(%q{LOWER(`bar`) = LOWER(:value)}, value: 'baz').
|
||||||
|
and_return(final)
|
||||||
|
|
||||||
|
got = model.case_insensitive_where(foo: 'bar', bar: 'baz')
|
||||||
|
|
||||||
|
expect(got).to eq(final)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns the criteria for a column with a table, and a value' do
|
||||||
|
initial = double(:criteria)
|
||||||
|
final = double(:criteria)
|
||||||
|
|
||||||
|
expect(connection).to receive(:quote_table_name).
|
||||||
|
with(:'foo.bar').
|
||||||
|
and_return('`foo`.`bar`')
|
||||||
|
|
||||||
|
expect(connection).to receive(:quote_table_name).
|
||||||
|
with(:'foo.baz').
|
||||||
|
and_return('`foo`.`baz`')
|
||||||
|
|
||||||
|
expect(model).to receive(:where).
|
||||||
|
with(%q{LOWER(`foo`.`bar`) = LOWER(:value)}, value: 'bar').
|
||||||
|
and_return(initial)
|
||||||
|
|
||||||
|
expect(initial).to receive(:where).
|
||||||
|
with(%q{LOWER(`foo`.`baz`) = LOWER(:value)}, value: 'baz').
|
||||||
|
and_return(final)
|
||||||
|
|
||||||
|
got = model.case_insensitive_where('foo.bar': 'bar', 'foo.baz': 'baz')
|
||||||
|
|
||||||
|
expect(got).to eq(final)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue