diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-08-19 12:08:42 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-08-19 12:08:42 +0300 |
commit | b76ae638462ab0f673e5915986070518dd3f9ad3 (patch) | |
tree | bdab0533383b52873be0ec0eb4d3c66598ff8b91 /spec/support/database/prevent_cross_joins.rb | |
parent | 434373eabe7b4be9593d18a585fb763f1e5f1a6f (diff) |
Add latest changes from gitlab-org/gitlab@14-2-stable-eev14.2.0-rc42
Diffstat (limited to 'spec/support/database/prevent_cross_joins.rb')
-rw-r--r-- | spec/support/database/prevent_cross_joins.rb | 77 |
1 files changed, 77 insertions, 0 deletions
diff --git a/spec/support/database/prevent_cross_joins.rb b/spec/support/database/prevent_cross_joins.rb new file mode 100644 index 00000000000..789721ccd38 --- /dev/null +++ b/spec/support/database/prevent_cross_joins.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +# This module tries to discover and prevent cross-joins across tables +# This will forbid usage of tables between CI and main database +# on a same query unless explicitly allowed by. This will change execution +# from a given point to allow cross-joins. The state will be cleared +# on a next test run. +# +# This method should be used to mark METHOD introducing cross-join +# not a test using the cross-join. +# +# class User +# def ci_owned_runners +# ::Gitlab::Database.allow_cross_joins_across_databases!(url: link-to-issue-url) +# +# ... +# end +# end + +module Database + module PreventCrossJoins + CrossJoinAcrossUnsupportedTablesError = Class.new(StandardError) + + def self.validate_cross_joins!(sql) + return if Thread.current[:allow_cross_joins_across_databases] + + # PgQuery might fail in some cases due to limited nesting: + # https://github.com/pganalyze/pg_query/issues/209 + tables = PgQuery.parse(sql).tables + + unless only_ci_or_only_main?(tables) + raise CrossJoinAcrossUnsupportedTablesError, + "Unsupported cross-join across '#{tables.join(", ")}' discovered " \ + "when executing query '#{sql}'" + end + end + + # Returns true if a set includes only CI tables, or includes only non-CI tables + def self.only_ci_or_only_main?(tables) + tables.all? { |table| CiTables.include?(table) } || + tables.none? { |table| CiTables.include?(table) } + end + + module SpecHelpers + def with_cross_joins_prevented + subscriber = ActiveSupport::Notifications.subscribe('sql.active_record') do |event| + ::Database::PreventCrossJoins.validate_cross_joins!(event.payload[:sql]) + end + + Thread.current[:allow_cross_joins_across_databases] = false + + yield + ensure + ActiveSupport::Notifications.unsubscribe(subscriber) if subscriber + end + end + + module GitlabDatabaseMixin + def allow_cross_joins_across_databases(url:) + Thread.current[:allow_cross_joins_across_databases] = true + super + end + end + end +end + +Gitlab::Database.singleton_class.prepend( + Database::PreventCrossJoins::GitlabDatabaseMixin) + +RSpec.configure do |config| + config.include(::Database::PreventCrossJoins::SpecHelpers) + + # TODO: remove `:prevent_cross_joins` to enable the check by default + config.around(:each, :prevent_cross_joins) do |example| + with_cross_joins_prevented { example.run } + end +end |