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:
Yorick Peterse 2015-10-07 14:03:18 +02:00
parent 85c6a3743a
commit 1190d0ab3d
2 changed files with 204 additions and 0 deletions

View 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

View 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