summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYorick Peterse <yorickpeterse@gmail.com>2015-10-07 14:03:18 +0200
committerYorick Peterse <yorickpeterse@gmail.com>2015-10-07 23:32:14 +0200
commit1190d0ab3dc7a3025bf55b666f34d1a0b51a8d89 (patch)
treedfe1780bda6e1d50e37485154bf1afa6a7f91d3e
parent85c6a3743abe5683c2317f1957a9f047ad2b4b8e (diff)
downloadgitlab-ce-1190d0ab3dc7a3025bf55b666f34d1a0b51a8d89.tar.gz
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.
-rw-r--r--app/models/concerns/case_sensitivity.rb28
-rw-r--r--spec/models/concerns/case_sensitivity_spec.rb176
2 files changed, 204 insertions, 0 deletions
diff --git a/app/models/concerns/case_sensitivity.rb b/app/models/concerns/case_sensitivity.rb
new file mode 100644
index 00000000000..49d350e092b
--- /dev/null
+++ b/app/models/concerns/case_sensitivity.rb
@@ -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
diff --git a/spec/models/concerns/case_sensitivity_spec.rb b/spec/models/concerns/case_sensitivity_spec.rb
new file mode 100644
index 00000000000..8b9f50aada7
--- /dev/null
+++ b/spec/models/concerns/case_sensitivity_spec.rb
@@ -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