-
-
Notifications
You must be signed in to change notification settings - Fork 178
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: correctly identify missing verification for bi-directional pacts
- Loading branch information
Showing
9 changed files
with
414 additions
and
38 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
module PactBroker | ||
module Matrix | ||
class QueryBuilder | ||
LV = :latest_verification_id_for_pact_version_and_provider_version | ||
LP = :latest_pact_publication_ids_for_consumer_versions | ||
|
||
def self.provider_or_provider_version_or_verification_in(selectors, allow_null_provider_version = false, qualifier) | ||
most_specific_criteria = selectors.collect(&:most_specific_provider_criterion) | ||
|
||
verification_ids = collect_ids(most_specific_criteria, :verification_ids) | ||
provider_version_ids = collect_ids(most_specific_criteria, :pacticipant_version_id) | ||
provider_ids = collect_ids(most_specific_criteria, :pacticipant_id) | ||
|
||
ors = [ | ||
{ verification_id: verification_ids }, | ||
{ Sequel[qualifier][:provider_version_id] => provider_version_ids }, | ||
{ Sequel[qualifier][:provider_id] => provider_ids } | ||
] | ||
|
||
if allow_null_provider_version | ||
ors << { | ||
Sequel[qualifier][:provider_id] => selectors.collect{ |s| s[:pacticipant_id] }, | ||
Sequel[qualifier][:provider_version_id] => nil | ||
} | ||
end | ||
|
||
Sequel.|(*ors) | ||
end | ||
|
||
def self.consumer_in_pacticipant_ids(selectors) | ||
{ consumer_id: selectors.collect(&:pacticipant_id) } | ||
end | ||
|
||
def self.consumer_or_consumer_version_or_pact_publication_in(selectors, qualifier) | ||
most_specific_criteria = selectors.collect(&:most_specific_consumer_criterion) | ||
pact_publication_ids = collect_ids(most_specific_criteria, :pact_publication_ids) | ||
consumer_version_ids = collect_ids(most_specific_criteria, :pacticipant_version_id) | ||
consumer_ids = collect_ids(most_specific_criteria, :pacticipant_id) | ||
|
||
Sequel.|( | ||
{ Sequel[qualifier][:pact_publication_id] => pact_publication_ids }, | ||
{ Sequel[qualifier][:consumer_version_id] => consumer_version_ids }, | ||
{ Sequel[qualifier][:consumer_id] => consumer_ids } | ||
) | ||
end | ||
|
||
# Some selecters are specified in the query, others are implied (when only one pacticipant is specified, | ||
# the integrations are automatically worked out, and the selectors for these are of type :implied ) | ||
# When there are 3 pacticipants that each have dependencies on each other (A->B, A->C, B->C), the query | ||
# to deploy C (implied A, implied B, specified C) was returning the A->B row because it matched the | ||
# implied selectors as well. | ||
# This extra filter makes sure that every row that is returned actually matches one of the specified | ||
# selectors. | ||
def self.either_consumer_or_provider_was_specified_in_query(selectors, qualifier = nil) | ||
consumer_id_field = qualifier ? Sequel[qualifier][:consumer_id] : CONSUMER_ID | ||
provider_id_field = qualifier ? Sequel[qualifier][:provider_id] : PROVIDER_ID | ||
specified_pacticipant_ids = selectors.select(&:specified?).collect(&:pacticipant_id) | ||
Sequel.|({ consumer_id_field => specified_pacticipant_ids } , { provider_id_field => specified_pacticipant_ids }) | ||
end | ||
|
||
def self.consumer_or_consumer_version_or_provider_or_provider_or_provider_version_match_selector(s) | ||
consumer_or_consumer_version_match = s[:pacticipant_version_id] ? { Sequel[LP][:consumer_version_id] => s[:pacticipant_version_id] } : { Sequel[LP][:consumer_id] => s[:pacticipant_id] } | ||
provider_or_provider_version_match = s[:pacticipant_version_id] ? { Sequel[:lv][:provider_version_id] => s[:pacticipant_version_id] } : { Sequel[LP][:provider_id] => s[:pacticipant_id] } | ||
Sequel.|(consumer_or_consumer_version_match , provider_or_provider_version_match) | ||
end | ||
|
||
def self.all_pacticipant_ids selectors | ||
selectors.collect(&:pacticipant_id) | ||
end | ||
|
||
def self.collect_ids(hashes, key) | ||
ids = hashes.collect{ |s| s[key] }.flatten.compact | ||
# must avoid an empty IN list, or Sequel makes a (0 = 1) clause for us | ||
ids.empty? ? [-1] : ids | ||
end | ||
|
||
def self.collect_the_ids selectors | ||
most_specific_criteria = selectors.collect(&:most_specific_consumer_criterion) | ||
verification_ids = collect_ids(most_specific_criteria, :verification_ids) | ||
pact_publication_ids = collect_ids(most_specific_criteria, :pact_publication_ids) | ||
pacticipant_version_ids = collect_ids(most_specific_criteria, :pacticipant_version_id) | ||
pacticipant_ids = collect_ids(most_specific_criteria, :pacticipant_id) | ||
all_pacticipant_ids = selectors.collect(&:pacticipant_id) | ||
|
||
specified_pacticipant_ids = selectors.select(&:specified?).collect(&:pacticipant_id) | ||
|
||
{ | ||
verification_ids: verification_ids, | ||
pact_publication_ids: pact_publication_ids, | ||
consumer_version_ids: pacticipant_version_ids, | ||
provider_version_ids: pacticipant_version_ids, | ||
consumer_ids: pacticipant_ids, | ||
provider_ids: pacticipant_ids, | ||
all_pacticipant_ids: all_pacticipant_ids, | ||
specified_pacticipant_ids: specified_pacticipant_ids | ||
} | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
require 'pact_broker/pacts/all_pact_publications' | ||
require 'pact_broker/repositories/helpers' | ||
require 'pact_broker/matrix/query_builder' | ||
|
||
# The difference between this query and the query for QuickRow2 is that | ||
# the left outer join is done on a pre-filtered dataset so that we | ||
# get a row with null verification fields for a pact that has not been verified | ||
# by the particular providers where're interested in, rather than being excluded | ||
# from the dataset. | ||
|
||
module PactBroker | ||
module Matrix | ||
class QuickRow2 < Sequel::Model(:latest_pact_publication_ids_for_consumer_versions) | ||
LV = :latest_verification_id_for_pact_version_and_provider_version | ||
LP = :latest_pact_publication_ids_for_consumer_versions | ||
|
||
LP_LV_JOIN = { Sequel[LP][:pact_version_id] => Sequel[:lv][:pact_version_id] } | ||
CONSUMER_JOIN = { Sequel[LP][:consumer_id] => Sequel[:consumers][:id] } | ||
PROVIDER_JOIN = { Sequel[LP][:provider_id] => Sequel[:providers][:id] } | ||
CONSUMER_VERSION_JOIN = { Sequel[LP][:consumer_version_id] => Sequel[:cv][:id] } | ||
PROVIDER_VERSION_JOIN = { Sequel[:lv][:provider_version_id] => Sequel[:pv][:id] } | ||
|
||
CONSUMER_COLUMNS = [Sequel[LP][:consumer_id], Sequel[:consumers][:name].as(:consumer_name), Sequel[LP][:pact_publication_id], Sequel[LP][:pact_version_id]] | ||
PROVIDER_COLUMNS = [Sequel[LP][:provider_id], Sequel[:providers][:name].as(:provider_name), Sequel[:lv][:verification_id]] | ||
CONSUMER_VERSION_COLUMNS = [Sequel[LP][:consumer_version_id], Sequel[:cv][:number].as(:consumer_version_number), Sequel[:cv][:order].as(:consumer_version_order)] | ||
PROVIDER_VERSION_COLUMNS = [Sequel[:lv][:provider_version_id], Sequel[:pv][:number].as(:provider_version_number), Sequel[:pv][:order].as(:provider_version_order)] | ||
ALL_COLUMNS = CONSUMER_COLUMNS + CONSUMER_VERSION_COLUMNS + PROVIDER_COLUMNS + PROVIDER_VERSION_COLUMNS | ||
|
||
SELECT_ALL_COLUMN_ARGS = [:select_all_columns] + ALL_COLUMNS | ||
|
||
dataset_module do | ||
include PactBroker::Repositories::Helpers | ||
|
||
select *SELECT_ALL_COLUMN_ARGS | ||
|
||
def matching_selectors selectors | ||
if selectors.size == 1 | ||
matching_one_selector(selectors.first) | ||
else | ||
matching_multiple_selectors(selectors) | ||
end | ||
end | ||
|
||
# When we have one selector, we need to join ALL the verifications to find out | ||
# what integrations exist | ||
def matching_one_selector(selector) | ||
select_all_columns | ||
.join_verifications | ||
.join_pacticipants_and_pacticipant_versions | ||
.where { | ||
QueryBuilder.consumer_or_consumer_version_or_provider_or_provider_or_provider_version_match_selector(selector) | ||
} | ||
end | ||
|
||
# When the user has specified multiple selectors, we only want to join the verifications for | ||
# the specified selectors. This is because of the behaviour of the left outer join. | ||
# Imagine a pact has been verified by a provider version that was NOT specified in the selectors. | ||
# If we join all the verifications and THEN filter the rows to only show the versions specified | ||
# in the selectors, we won't get a row for that pact, and hence, we won't | ||
# know that it hasn't been verified by the provider version we're interested in. | ||
# Instead, we need to filter the verifications dataset down to only the ones specified in the selectors first, | ||
# and THEN join them to the pacts, so that we get a row for the pact with null provider version | ||
# and verification fields. | ||
def matching_multiple_selectors(selectors) | ||
select_all_columns | ||
.join_verifications_for(selectors) | ||
.join_pacticipants_and_pacticipant_versions | ||
.where { | ||
Sequel.&( | ||
QueryBuilder.consumer_or_consumer_version_or_pact_publication_in(selectors, LP), | ||
QueryBuilder.either_consumer_or_provider_was_specified_in_query(selectors, LP) | ||
) | ||
} | ||
.from_self(alias: :t9) | ||
.where { | ||
QueryBuilder.provider_or_provider_version_or_verification_in(selectors, true, :t9) | ||
} | ||
end | ||
|
||
def join_pacticipants_and_pacticipant_versions | ||
join_consumers | ||
.join_providers | ||
.join_consumer_versions | ||
.join_provider_versions | ||
end | ||
|
||
def join_consumers | ||
join(:pacticipants, CONSUMER_JOIN, { table_alias: :consumers }) | ||
end | ||
|
||
def join_providers | ||
join(:pacticipants, PROVIDER_JOIN, { table_alias: :providers }) | ||
end | ||
|
||
def join_consumer_versions | ||
join(:versions, CONSUMER_VERSION_JOIN, { table_alias: :cv }) | ||
end | ||
|
||
def join_provider_versions | ||
left_outer_join(:versions, PROVIDER_VERSION_JOIN, { table_alias: :pv } ) | ||
end | ||
|
||
def join_verifications_for(selectors) | ||
left_outer_join(verifications_for(selectors), LP_LV_JOIN, { table_alias: :lv } ) | ||
end | ||
|
||
def join_verifications | ||
left_outer_join(LV, LP_LV_JOIN, { table_alias: :lv } ) | ||
end | ||
|
||
def verifications_for(selectors) | ||
db[LV] | ||
.select(:verification_id, :provider_version_id, :pact_version_id) | ||
.where { | ||
Sequel.&( | ||
QueryBuilder.consumer_in_pacticipant_ids(selectors), | ||
QueryBuilder.provider_or_provider_version_or_verification_in(selectors, false, LV) | ||
) | ||
} | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.