Skip to content

Commit 524e08d

Browse files
committed
feat: optimise queries for index page with tags
1 parent eb67511 commit 524e08d

10 files changed

+253
-159
lines changed

db/migrations/20180523_create_latest_verifications_for_consumer_version_tags.rb

+10-7
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,22 @@
22
up do
33
# The latest verification id for each consumer version tag
44
create_view(:latest_verifications_ids_for_consumer_version_tags,
5-
"select t.name as consumer_version_tag_name, max(lv.id) as latest_verification_id
6-
from verifications lv
5+
"select pv.pacticipant_id as provider_id, lpp.consumer_id, t.name as consumer_version_tag_name, max(v.id) as latest_verification_id
6+
from verifications v
77
join latest_pact_publications_by_consumer_versions lpp
8-
on lv.pact_version_id = lpp.pact_version_id
9-
join tags t on lpp.consumer_version_id = t.version_id
10-
group by t.name")
8+
on v.pact_version_id = lpp.pact_version_id
9+
join tags t
10+
on lpp.consumer_version_id = t.version_id
11+
join versions pv
12+
on v.provider_version_id = pv.id
13+
group by pv.pacticipant_id, lpp.consumer_id, t.name")
1114

1215
# The latest verification for each consumer version tag
1316
create_view(:latest_verifications_for_consumer_version_tags,
14-
"select v.*, lv.consumer_version_tag_name
17+
"select v.*, lv.provider_id, lv.consumer_id, lv.consumer_version_tag_name
1518
from verifications v
1619
join latest_verifications_ids_for_consumer_version_tags lv
17-
on lv.latest_verification_id = v.id")
20+
on lv.latest_verification_id = v.id")
1821
end
1922

2023
down do

lib/pact_broker/domain/index_item.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ def latest_verification_successful?
102102
end
103103

104104
def pact_changed_since_last_verification?
105-
latest_verification.pact_version_sha != latest_pact.pact_version_sha
105+
latest_verification.pact_version_id != latest_pact.pact_version_id
106106
end
107107

108108
def latest_verification_provider_version_number

lib/pact_broker/index/service.rb

+14-82
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
require 'pact_broker/logging'
33
require 'pact_broker/domain/index_item'
44
require 'pact_broker/matrix/head_row'
5+
require 'pact_broker/matrix/aggregated_row'
56

67
module PactBroker
78

@@ -13,107 +14,38 @@ class Service
1314
extend PactBroker::Logging
1415

1516
def self.find_index_items options = {}
16-
rows = []
17-
overall_latest_publication_ids = nil
18-
1917
rows = PactBroker::Matrix::HeadRow
2018
.select_all_qualified
2119
.eager(:latest_triggered_webhooks)
2220
.eager(:webhooks)
2321
.order(:consumer_name, :provider_name)
24-
.eager(:consumer_version_tags)
25-
.eager(:provider_version_tags)
22+
.eager(:verification)
2623

2724
if !options[:tags]
28-
rows = rows.where(consumer_version_tag_name: nil).all
29-
overall_latest_publication_ids = rows.collect(&:pact_publication_id)
30-
end
31-
32-
if options[:tags]
25+
rows = rows.where(consumer_version_tag_name: nil)
26+
else
3327
if options[:tags].is_a?(Array)
3428
rows = rows.where(consumer_version_tag_name: options[:tags]).or(consumer_version_tag_name: nil)
3529
end
36-
37-
rows = rows.all
38-
overall_latest_publication_ids = rows.select{|r| !r[:consumer_version_tag_name] }.collect(&:pact_publication_id).uniq
39-
40-
# Smoosh all the rows with matching pact publications together
41-
# and collect their consumer_head_tag_names
42-
rows = rows
43-
.group_by(&:pact_publication_id)
44-
.values
45-
.collect{|group| [group.last, group.collect{|r| r[:consumer_version_tag_name]}.compact] }
46-
.collect{ |(row, tag_names)| row.consumer_head_tag_names = tag_names; row }
30+
rows = rows.eager(:consumer_version_tags)
31+
.eager(:provider_version_tags)
32+
.eager(:latest_verification_for_consumer_version_tag)
4733
end
34+
rows = rows.all.group_by(&:pact_publication_id).values.collect{ | rows| Matrix::AggregatedRow.new(rows) }
4835

49-
index_items = []
50-
rows.sort.each do | row |
51-
tag_names = []
52-
if options[:tags]
53-
tag_names = row.consumer_version_tags.collect(&:name)
54-
end
55-
56-
overall_latest = overall_latest_publication_ids.include?(row.pact_publication_id)
57-
latest_verification = if overall_latest
58-
verification_repository.find_latest_verification_for row.consumer_name, row.provider_name
59-
else
60-
tag_names.collect do | tag_name |
61-
verification_repository.find_latest_verification_for row.consumer_name, row.provider_name, tag_name
62-
end.compact.sort do | v1, v2 |
63-
# Some provider versions have nil orders, not sure why
64-
# Sort by execution_date instead of order
65-
v1.execution_date <=> v2.execution_date
66-
end.last
67-
end
68-
69-
index_items << PactBroker::Domain::IndexItem.create(
36+
rows.sort.collect do | row |
37+
PactBroker::Domain::IndexItem.create(
7038
row.consumer,
7139
row.provider,
7240
row.pact,
73-
overall_latest,
74-
latest_verification,
41+
row.overall_latest?,
42+
options[:tags] ? row.latest_verification : row.verification,
7543
row.webhooks,
7644
row.latest_triggered_webhooks,
77-
row.consumer_head_tag_names,
78-
row.provider_version_tags.select(&:latest?)
45+
options[:tags] ? row.consumer_head_tag_names : [],
46+
options[:tags] ? row.provider_version_tags.select(&:latest?) : []
7947
)
8048
end
81-
82-
index_items
83-
end
84-
85-
def self.tags_for(pact, options)
86-
if options[:tags] == true
87-
tag_service.find_all_tag_names_for_pacticipant(pact.consumer_name)
88-
elsif options[:tags].is_a?(Array)
89-
options[:tags]
90-
else
91-
[]
92-
end
93-
end
94-
95-
def self.build_index_item_rows(pact, tags)
96-
index_items = [build_latest_pact_index_item(pact, tags)]
97-
tags.each do | tag |
98-
index_items << build_index_item_for_tagged_pact(pact, tag)
99-
end
100-
index_items.compact
101-
end
102-
103-
def self.build_latest_pact_index_item pact, tags
104-
latest_verification = verification_service.find_latest_verification_for(pact.consumer, pact.provider)
105-
webhooks = webhook_service.find_by_consumer_and_provider pact.consumer, pact.provider
106-
triggered_webhooks = webhook_service.find_latest_triggered_webhooks pact.consumer, pact.provider
107-
tag_names = pact.consumer_version_tag_names.select{ |name| tags.include?(name) }
108-
PactBroker::Domain::IndexItem.create pact.consumer, pact.provider, pact, true, latest_verification, webhooks, triggered_webhooks, tag_names
109-
end
110-
111-
def self.build_index_item_for_tagged_pact latest_pact, tag
112-
pact = pact_service.find_latest_pact consumer_name: latest_pact.consumer_name, provider_name: latest_pact.provider_name, tag: tag
113-
return nil unless pact
114-
return nil if pact.id == latest_pact.id
115-
verification = verification_repository.find_latest_verification_for pact.consumer_name, pact.provider_name, tag
116-
PactBroker::Domain::IndexItem.create pact.consumer, pact.provider, pact, false, verification, [], [], [tag]
11749
end
11850
end
11951
end
+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
require 'pact_broker/verifications/repository'
2+
3+
# A collection of matrix rows with the same pact publication id
4+
# It's basically a normalised view of a denormalised view :(
5+
6+
module PactBroker
7+
module Matrix
8+
class AggregatedRow
9+
extend Forwardable
10+
11+
delegate [:consumer, :consumer_name, :consumer_version, :consumer_version_number, :consumer_version_order, :consumer_version_tags] => :first_row
12+
delegate [:provider, :provider_name, :provider_version, :provider_version_number, :provider_version_order, :provider_version_tags] => :first_row
13+
delegate [:pact, :pact_version, :pact_revision_number, :webhooks, :latest_triggered_webhooks, :'<=>'] => :first_row
14+
delegate [:verification_id, :verification] => :first_row
15+
16+
17+
def initialize matrix_rows
18+
@matrix_rows = matrix_rows
19+
@first_row = matrix_rows.first
20+
end
21+
22+
def overall_latest?
23+
!!matrix_rows.find{ | row| row.consumer_version_tag_name.nil? }
24+
end
25+
26+
def latest_verification
27+
@latest_verification ||= begin
28+
verification = matrix_rows.collect do | row|
29+
row.verification || row.latest_verification_for_consumer_version_tag
30+
end.compact.sort{ |v1, v2| v1.id <=> v2.id}.last
31+
32+
if !verification && overall_latest?
33+
PactBroker::Verifications::Repository.new.find_latest_verification_for(consumer_name, provider_name)
34+
else
35+
verification
36+
end
37+
end
38+
end
39+
40+
def consumer_head_tag_names
41+
matrix_rows.collect(&:consumer_version_tag_name).compact
42+
end
43+
44+
private
45+
46+
attr_reader :matrix_rows
47+
48+
def first_row
49+
@first_row
50+
end
51+
52+
def consumer_version_tag_names
53+
matrix_rows.collect(&:consumer_version_tag_name)
54+
end
55+
end
56+
end
57+
end

lib/pact_broker/matrix/head_row.rb

+21
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,27 @@ module Matrix
77
class HeadRow < Row
88
set_dataset(:materialized_head_matrix)
99

10+
# one_to_one :latest_verification_for_consumer_version_tag,
11+
# :class => "PactBroker::Verifications::LatestVerificationForConsumerVersionTag",
12+
# primary_key: [:provider_id, :consumer_id, :consumer_version_tag_name], key: [:provider_id, :consumer_id, :consumer_version_tag_name]
13+
14+
# Loading the latest_verification_for_consumer_version_tag objects this way is quicker than
15+
# doing it using an inbult relation with primary_key/key, if we are loading the relation for
16+
# the entire HeadRow table
17+
# Using the inbuilt relation puts constraints on the columns that are not necessary and slow
18+
# the query down.
19+
one_to_one :latest_verification_for_consumer_version_tag, :class => "PactBroker::Verifications::LatestVerificationForConsumerVersionTag", primary_keys: [], key: [], :eager_loader=>(proc do |eo_opts|
20+
tag_to_row = eo_opts[:rows].each_with_object({}) { | row, map | map[[row.provider_id, row.consumer_id, row.consumer_version_tag_name]] = row }
21+
eo_opts[:rows].each{|row| row.associations[:latest_verification_for_consumer_version_tag] = nil}
22+
23+
PactBroker::Verifications::LatestVerificationForConsumerVersionTag.each do | verification |
24+
key = [verification.provider_id, verification.consumer_id, verification.consumer_version_tag_name]
25+
if tag_to_row[key]
26+
tag_to_row[key].associations[:latest_verification_for_consumer_version_tag] = verification
27+
end
28+
end
29+
end)
30+
1031
dataset_module do
1132
include PactBroker::Repositories::Helpers
1233
include PactBroker::Logging

lib/pact_broker/matrix/row.rb

+1-11
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,7 @@ class Row < Sequel::Model(:materialized_matrix)
1717
associate(:one_to_many, :consumer_version_tags, :class => "PactBroker::Tags::TagWithLatestFlag", primary_key: :consumer_version_id, key: :version_id)
1818
associate(:one_to_many, :provider_version_tags, :class => "PactBroker::Tags::TagWithLatestFlag", primary_key: :provider_version_id, key: :version_id)
1919
associate(:many_to_one, :verification, :class => "PactBroker::Domain::Verification", primary_key: :id, key: :verification_id)
20-
21-
one_to_one :latest_verification_for_consumer_version_tag, primary_keys: [], key: [], :eager_loader=>(proc do |eo_opts|
22-
tag_to_row = eo_opts[:rows].each_with_object({}) { | row, map | row.consumer_version_tags.each{ | tag | map[tag.name] = row } }
23-
eo_opts[:rows].each{|row| row.associations[:latest_verification_for_consumer_version_tag] = nil}
24-
25-
PactBroker::Verifications::LatestVerificationForConsumerVersionTag.each do | verification |
26-
if tag_to_row[verification.consumer_version_tag_name]
27-
tag_to_row[verification.consumer_version_tag_name].associations[:latest_verification_for_consumer_version_tag] = verification
28-
end
29-
end
30-
end)
20+
# associate(:many_to_one, :pact_version, :class => "PactBroker::Pacts::PactVersion", primary_key: :id, key: :pact_version_id)
3121

3222
dataset_module do
3323
include PactBroker::Repositories::Helpers
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
require 'pact_broker/matrix/aggregated_row'
2+
3+
module PactBroker
4+
module Matrix
5+
describe AggregatedRow do
6+
describe "latest_verification" do
7+
let(:row_1) do
8+
instance_double('PactBroker::Matrix::HeadRow',
9+
consumer_name: "Foo",
10+
provider_name: "Bar",
11+
verification: verification_1,
12+
latest_verification_for_consumer_version_tag: tag_verification_1,
13+
consumer_version_tag_name: consumer_version_tag_name_1)
14+
end
15+
let(:row_2) do
16+
instance_double('PactBroker::Matrix::HeadRow',
17+
verification: verification_2,
18+
latest_verification_for_consumer_version_tag: tag_verification_2,
19+
consumer_version_tag_name: consumer_version_tag_name_2)
20+
end
21+
let(:verification_1) { instance_double('PactBroker::Domain::Verification', id: 1) }
22+
let(:verification_2) { instance_double('PactBroker::Domain::Verification', id: 2) }
23+
let(:tag_verification_1) { instance_double('PactBroker::Domain::Verification', id: 3) }
24+
let(:tag_verification_2) { instance_double('PactBroker::Domain::Verification', id: 4) }
25+
let(:consumer_version_tag_name_1) { 'master' }
26+
let(:consumer_version_tag_name_2) { 'prod' }
27+
let(:rows) { [row_1, row_2] }
28+
let(:aggregated_row) { AggregatedRow.new(rows) }
29+
30+
subject { aggregated_row.latest_verification }
31+
32+
context "when the rows have verifications" do
33+
it "returns the verification with the largest id" do
34+
expect(subject).to be verification_2
35+
end
36+
end
37+
38+
context "when the rows do not have verifications, but there are a previous verifications for a pacts with the same tag" do
39+
let(:verification_1) { nil }
40+
let(:verification_2) { nil }
41+
42+
it "returns the verification for the previous pact that has the largest id" do
43+
expect(subject).to be tag_verification_2
44+
end
45+
end
46+
47+
context "when there is no verification for any of the rows or any of the pacts with the same tag" do
48+
before do
49+
allow_any_instance_of(PactBroker::Verifications::Repository).to receive(:find_latest_verification_for).and_return(overall_latest_verification)
50+
end
51+
52+
let(:overall_latest_verification) { instance_double('PactBroker::Domain::Verification', id: 5) }
53+
let(:verification_1) { nil }
54+
let(:verification_2) { nil }
55+
let(:tag_verification_1) { nil }
56+
let(:tag_verification_2) { nil }
57+
58+
context "when one of the rows is the overall latest" do
59+
let(:consumer_version_tag_name_1) { nil }
60+
61+
it "looks up the overall latest verification" do
62+
expect_any_instance_of(PactBroker::Verifications::Repository).to receive(:find_latest_verification_for).with("Foo", "Bar")
63+
subject
64+
end
65+
66+
it "returns the overall latest verification" do
67+
expect(subject).to be overall_latest_verification
68+
end
69+
end
70+
71+
context "when none of the rows is not the overall latest (they are all the latest with a tag)" do
72+
it "returns nil" do
73+
expect(subject).to be nil
74+
end
75+
end
76+
end
77+
end
78+
end
79+
end
80+
end

0 commit comments

Comments
 (0)