diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-04-10 00:10:04 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-04-10 00:10:04 +0000 |
commit | 1bf4fece121298260663c6ca73d39716d3548a03 (patch) | |
tree | 5c82dd2fa63552ecb67fcb034740ec7e8fdba25a /spec/models | |
parent | 254ec28f5448f6f353cd98f637985de3d1405854 (diff) | |
download | gitlab-ce-1bf4fece121298260663c6ca73d39716d3548a03.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/models')
-rw-r--r-- | spec/models/concerns/where_composite_spec.rb | 184 | ||||
-rw-r--r-- | spec/models/issue_spec.rb | 16 |
2 files changed, 200 insertions, 0 deletions
diff --git a/spec/models/concerns/where_composite_spec.rb b/spec/models/concerns/where_composite_spec.rb new file mode 100644 index 00000000000..1c0951d90d0 --- /dev/null +++ b/spec/models/concerns/where_composite_spec.rb @@ -0,0 +1,184 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe WhereComposite do + describe '.where_composite' do + let_it_be(:test_table_name) { "test_table_#{SecureRandom.hex(10)}" } + + let(:model) do + tbl_name = test_table_name + Class.new(ActiveRecord::Base) do + self.table_name = tbl_name + + include WhereComposite + end + end + + def connection + ActiveRecord::Base.connection + end + + before_all do + connection.create_table(test_table_name) do |t| + t.integer :foo + t.integer :bar + t.string :wibble + end + end + + it 'requires at least one permitted key' do + expect { model.where_composite([], nil) } + .to raise_error(ArgumentError) + end + + it 'requires all arguments to match the permitted_keys' do + expect { model.where_composite([:foo], [{ foo: 1 }, { bar: 2 }]) } + .to raise_error(ArgumentError) + end + + it 'attaches a key error as cause if a key is missing' do + expect { model.where_composite([:foo], [{ foo: 1 }, { bar: 2 }]) } + .to raise_error(have_attributes(cause: KeyError)) + end + + it 'returns an empty relation if there are no arguments' do + expect(model.where_composite([:foo, :bar], nil)) + .to be_empty + + expect(model.where_composite([:foo, :bar], [])) + .to be_empty + end + + it 'permits extra arguments' do + a = model.where_composite([:foo, :bar], { foo: 1, bar: 2 }) + b = model.where_composite([:foo, :bar], { foo: 1, bar: 2, baz: 3 }) + + expect(a.to_sql).to eq(b.to_sql) + end + + it 'can handle multiple fields' do + fields = [:foo, :bar, :wibble] + args = { foo: 1, bar: 2, wibble: 'wobble' } + pattern = %r{ + WHERE \s+ + \(? + \s* "#{test_table_name}"\."foo" \s* = \s* 1 + \s+ AND + \s+ "#{test_table_name}"\."bar" \s* = \s* 2 + \s+ AND + \s+ "#{test_table_name}"\."wibble" \s* = \s* 'wobble' + \s* + \)? + }x + + expect(model.where_composite(fields, args).to_sql).to match(pattern) + end + + it 'is equivalent to ids.map { |attrs| model.find_by(attrs) }' do + 10.times do |i| + 10.times do |j| + model.create!(foo: i, bar: j, wibble: generate(:filename)) + end + end + + ids = [{ foo: 1, bar: 9 }, { foo: 9, bar: 1 }] + + found = model.where_composite(%i[foo bar], ids) + + expect(found).to match_array(ids.map { |attrs| model.find_by!(attrs) }) + end + + it 'constructs (A&B) for one argument' do + fields = [:foo, :bar] + args = [ + { foo: 1, bar: 2 } + ] + pattern = %r{ + WHERE \s+ + \(? + \s* "#{test_table_name}"\."foo" \s* = \s* 1 + \s+ AND + \s+ "#{test_table_name}"\."bar" \s* = \s* 2 + \s* + \)? + }x + + expect(model.where_composite(fields, args).to_sql).to match(pattern) + expect(model.where_composite(fields, args[0]).to_sql).to match(pattern) + end + + it 'constructs (A&B) OR (C&D) for two arguments' do + args = [ + { foo: 1, bar: 2 }, + { foo: 3, bar: 4 } + ] + pattern = %r{ + WHERE \s+ + \( \s* "#{test_table_name}"\."foo" \s* = \s* 1 + \s+ AND + \s+ "#{test_table_name}"\."bar" \s* = \s* 2 + \s* \)? + \s* OR \s* + \(? \s* "#{test_table_name}"\."foo" \s* = \s* 3 + \s+ AND + \s+ "#{test_table_name}"\."bar" \s* = \s* 4 + \s* \) + }x + + q = model.where_composite([:foo, :bar], args) + + expect(q.to_sql).to match(pattern) + end + + it 'constructs (A&B) OR (C&D) OR (E&F) for three arguments' do + args = [ + { foo: 1, bar: 2 }, + { foo: 3, bar: 4 }, + { foo: 5, bar: 6 } + ] + pattern = %r{ + WHERE \s+ + \({2} + \s* "#{test_table_name}"\."foo" \s* = \s* 1 + \s+ AND + \s+ "#{test_table_name}"\."bar" \s* = \s* 2 + \s* \)? + \s* OR \s* + \(? \s* "#{test_table_name}"\."foo" \s* = \s* 3 + \s+ AND + \s+ "#{test_table_name}"\."bar" \s* = \s* 4 + \s* \)? + \s* OR \s* + \(? \s* "#{test_table_name}"\."foo" \s* = \s* 5 + \s+ AND + \s+ "#{test_table_name}"\."bar" \s* = \s* 6 + \s* \) + }x + + q = model.where_composite([:foo, :bar], args) + + expect(q.to_sql).to match(pattern) + end + + describe 'large sets of IDs' do + def query(size) + args = (0..).lazy.take(size).map { |n| { foo: n, bar: n * n, wibble: 'x' * n } }.to_a + model.where_composite([:foo, :bar, :wibble], args) + end + + it 'constructs correct trees of constraints' do + n = described_class::TooManyIds::LIMIT + q = query(n) + sql = q.to_sql + + expect(sql.scan(/OR/).count).to eq(n - 1) + expect(sql.scan(/AND/).count).to eq(2 * n) + end + + it 'raises errors if too many IDs are passed' do + expect { query(described_class::TooManyIds::LIMIT + 1) }.to raise_error(described_class::TooManyIds) + end + end + end +end diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index 9797e0a0472..e8103be0682 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -900,6 +900,22 @@ describe Issue do end end + describe '.by_project_id_and_iid' do + let_it_be(:issue_a) { create(:issue) } + let_it_be(:issue_b) { create(:issue, iid: issue_a.iid) } + let_it_be(:issue_c) { create(:issue, project: issue_a.project) } + let_it_be(:issue_d) { create(:issue, project: issue_a.project) } + + it_behaves_like 'a where_composite scope', :by_project_id_and_iid do + let(:all_results) { [issue_a, issue_b, issue_c, issue_d] } + let(:first_result) { issue_a } + + let(:composite_ids) do + all_results.map { |issue| { project_id: issue.project_id, iid: issue.iid } } + end + end + end + it_behaves_like 'throttled touch' do subject { create(:issue, updated_at: 1.hour.ago) } end |