summaryrefslogtreecommitdiff
path: root/spec/support/database
diff options
context:
space:
mode:
Diffstat (limited to 'spec/support/database')
-rw-r--r--spec/support/database/ci_tables.rb22
-rw-r--r--spec/support/database/cross-join-allowlist.yml196
-rw-r--r--spec/support/database/gitlab_schema.rb25
-rw-r--r--spec/support/database/multiple_databases.rb9
-rw-r--r--spec/support/database/prevent_cross_database_modification.rb10
-rw-r--r--spec/support/database/prevent_cross_joins.rb57
6 files changed, 275 insertions, 44 deletions
diff --git a/spec/support/database/ci_tables.rb b/spec/support/database/ci_tables.rb
deleted file mode 100644
index 99fc7ac2501..00000000000
--- a/spec/support/database/ci_tables.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-# frozen_string_literal: true
-
-# This module stores the CI-related database tables which are
-# going to be moved to a separate database.
-module Database
- module CiTables
- def self.include?(name)
- ci_tables.include?(name)
- end
-
- def self.ci_tables
- @@ci_tables ||= Set.new.tap do |tables| # rubocop:disable Style/ClassVars
- tables.merge(Ci::ApplicationRecord.descendants.map(&:table_name).compact)
-
- # It was decided that taggings/tags are best placed with CI
- # https://gitlab.com/gitlab-org/gitlab/-/issues/333413
- tables.add('taggings')
- tables.add('tags')
- end
- end
- end
-end
diff --git a/spec/support/database/cross-join-allowlist.yml b/spec/support/database/cross-join-allowlist.yml
new file mode 100644
index 00000000000..2b4cfc6773a
--- /dev/null
+++ b/spec/support/database/cross-join-allowlist.yml
@@ -0,0 +1,196 @@
+- "./ee/spec/controllers/operations_controller_spec.rb"
+- "./ee/spec/controllers/projects/issues_controller_spec.rb"
+- "./ee/spec/controllers/projects/security/vulnerabilities_controller_spec.rb"
+- "./ee/spec/features/ci/ci_minutes_spec.rb"
+- "./ee/spec/features/merge_request/user_merges_immediately_spec.rb"
+- "./ee/spec/features/merge_request/user_sees_merge_widget_spec.rb"
+- "./ee/spec/features/merge_trains/two_merge_requests_on_train_spec.rb"
+- "./ee/spec/features/merge_trains/user_adds_merge_request_to_merge_train_spec.rb"
+- "./ee/spec/features/merge_trains/user_adds_to_merge_train_when_pipeline_succeeds_spec.rb"
+- "./ee/spec/features/projects/pipelines/pipeline_spec.rb"
+- "./ee/spec/features/projects/settings/auto_rollback_spec.rb"
+- "./ee/spec/features/projects/settings/pipeline_subscriptions_spec.rb"
+- "./ee/spec/features/projects/settings/protected_environments_spec.rb"
+- "./ee/spec/finders/ee/namespaces/projects_finder_spec.rb"
+- "./ee/spec/finders/group_projects_finder_spec.rb"
+- "./ee/spec/finders/security/findings_finder_spec.rb"
+- "./ee/spec/graphql/ee/resolvers/namespace_projects_resolver_spec.rb"
+- "./ee/spec/lib/analytics/devops_adoption/snapshot_calculator_spec.rb"
+- "./ee/spec/lib/ee/gitlab/background_migration/migrate_approver_to_approval_rules_spec.rb"
+- "./ee/spec/lib/ee/gitlab/background_migration/migrate_security_scans_spec.rb"
+- "./ee/spec/lib/ee/gitlab/background_migration/populate_latest_pipeline_ids_spec.rb"
+- "./ee/spec/lib/ee/gitlab/background_migration/populate_resolved_on_default_branch_column_spec.rb"
+- "./ee/spec/lib/ee/gitlab/background_migration/populate_uuids_for_security_findings_spec.rb"
+- "./ee/spec/lib/ee/gitlab/background_migration/populate_vulnerability_feedback_pipeline_id_spec.rb"
+- "./ee/spec/lib/ee/gitlab/usage_data_spec.rb"
+- "./ee/spec/migrations/schedule_populate_resolved_on_default_branch_column_spec.rb"
+- "./ee/spec/models/ci/build_spec.rb"
+- "./ee/spec/models/ci/minutes/project_monthly_usage_spec.rb"
+- "./ee/spec/models/ci/pipeline_spec.rb"
+- "./ee/spec/models/ee/vulnerability_spec.rb"
+- "./ee/spec/models/merge_request_spec.rb"
+- "./ee/spec/models/project_spec.rb"
+- "./ee/spec/models/security/finding_spec.rb"
+- "./ee/spec/models/security/scan_spec.rb"
+- "./ee/spec/presenters/ci/pipeline_presenter_spec.rb"
+- "./ee/spec/requests/api/ci/minutes_spec.rb"
+- "./ee/spec/requests/api/graphql/ci/minutes/usage_spec.rb"
+- "./ee/spec/requests/api/graphql/mutations/environments/canary_ingress/update_spec.rb"
+- "./ee/spec/requests/api/graphql/mutations/vulnerabilities/create_external_issue_link_spec.rb"
+- "./ee/spec/requests/api/graphql/project/pipeline/security_report_summary_spec.rb"
+- "./ee/spec/requests/api/graphql/vulnerabilities/location_spec.rb"
+- "./ee/spec/requests/api/groups_spec.rb"
+- "./ee/spec/requests/api/namespaces_spec.rb"
+- "./ee/spec/requests/api/vulnerability_findings_spec.rb"
+- "./ee/spec/serializers/dashboard_environment_entity_spec.rb"
+- "./ee/spec/serializers/dashboard_environments_serializer_spec.rb"
+- "./ee/spec/services/auto_merge/add_to_merge_train_when_pipeline_succeeds_service_spec.rb"
+- "./ee/spec/services/ci/create_pipeline_service/runnable_builds_spec.rb"
+- "./ee/spec/services/ci/minutes/additional_packs/change_namespace_service_spec.rb"
+- "./ee/spec/services/ci/minutes/additional_packs/create_service_spec.rb"
+- "./ee/spec/services/ci/minutes/refresh_cached_data_service_spec.rb"
+- "./ee/spec/services/ci/process_pipeline_service_spec.rb"
+- "./ee/spec/services/ci/trigger_downstream_subscription_service_spec.rb"
+- "./ee/spec/services/clear_namespace_shared_runners_minutes_service_spec.rb"
+- "./ee/spec/services/deployments/auto_rollback_service_spec.rb"
+- "./ee/spec/services/ee/ci/job_artifacts/destroy_all_expired_service_spec.rb"
+- "./ee/spec/services/ee/ci/job_artifacts/destroy_batch_service_spec.rb"
+- "./ee/spec/services/ee/issues/build_from_vulnerability_service_spec.rb"
+- "./ee/spec/services/ee/merge_requests/create_pipeline_service_spec.rb"
+- "./ee/spec/services/ee/merge_requests/refresh_service_spec.rb"
+- "./ee/spec/services/security/report_summary_service_spec.rb"
+- "./ee/spec/services/security/vulnerability_counting_service_spec.rb"
+- "./ee/spec/workers/scan_security_report_secrets_worker_spec.rb"
+- "./ee/spec/workers/security/store_scans_worker_spec.rb"
+- "./spec/controllers/admin/runners_controller_spec.rb"
+- "./spec/controllers/groups/runners_controller_spec.rb"
+- "./spec/controllers/groups/settings/ci_cd_controller_spec.rb"
+- "./spec/controllers/projects/logs_controller_spec.rb"
+- "./spec/controllers/projects/merge_requests_controller_spec.rb"
+- "./spec/controllers/projects/runners_controller_spec.rb"
+- "./spec/controllers/projects/serverless/functions_controller_spec.rb"
+- "./spec/controllers/projects/settings/ci_cd_controller_spec.rb"
+- "./spec/features/admin/admin_runners_spec.rb"
+- "./spec/features/groups/settings/ci_cd_spec.rb"
+- "./spec/features/ide/user_opens_merge_request_spec.rb"
+- "./spec/features/merge_request/user_merges_immediately_spec.rb"
+- "./spec/features/merge_request/user_merges_only_if_pipeline_succeeds_spec.rb"
+- "./spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb"
+- "./spec/features/merge_request/user_resolves_wip_mr_spec.rb"
+- "./spec/features/merge_request/user_sees_deployment_widget_spec.rb"
+- "./spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb"
+- "./spec/features/merge_request/user_sees_merge_widget_spec.rb"
+- "./spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb"
+- "./spec/features/merge_request/user_sees_pipelines_from_forked_project_spec.rb"
+- "./spec/features/merge_request/user_sees_pipelines_spec.rb"
+- "./spec/features/project_group_variables_spec.rb"
+- "./spec/features/project_variables_spec.rb"
+- "./spec/features/projects/badges/list_spec.rb"
+- "./spec/features/projects/environments_pod_logs_spec.rb"
+- "./spec/features/projects/infrastructure_registry_spec.rb"
+- "./spec/features/projects/jobs_spec.rb"
+- "./spec/features/projects/package_files_spec.rb"
+- "./spec/features/projects/pipelines/pipeline_spec.rb"
+- "./spec/features/projects/pipelines/pipelines_spec.rb"
+- "./spec/features/projects/serverless/functions_spec.rb"
+- "./spec/features/projects/settings/pipelines_settings_spec.rb"
+- "./spec/features/runners_spec.rb"
+- "./spec/features/security/project/internal_access_spec.rb"
+- "./spec/features/security/project/private_access_spec.rb"
+- "./spec/features/security/project/public_access_spec.rb"
+- "./spec/features/triggers_spec.rb"
+- "./spec/finders/ci/pipelines_finder_spec.rb"
+- "./spec/finders/ci/pipelines_for_merge_request_finder_spec.rb"
+- "./spec/finders/ci/runners_finder_spec.rb"
+- "./spec/finders/clusters/knative_services_finder_spec.rb"
+- "./spec/finders/projects/serverless/functions_finder_spec.rb"
+- "./spec/frontend/fixtures/runner.rb"
+- "./spec/graphql/mutations/ci/runner/delete_spec.rb"
+- "./spec/graphql/resolvers/ci/group_runners_resolver_spec.rb"
+- "./spec/graphql/resolvers/ci/job_token_scope_resolver_spec.rb"
+- "./spec/graphql/resolvers/merge_request_pipelines_resolver_spec.rb"
+- "./spec/graphql/types/ci/job_token_scope_type_spec.rb"
+- "./spec/helpers/packages_helper_spec.rb"
+- "./spec/lib/api/entities/package_spec.rb"
+- "./spec/lib/gitlab/background_migration/migrate_legacy_artifacts_spec.rb"
+- "./spec/lib/gitlab/prometheus/query_variables_spec.rb"
+- "./spec/mailers/emails/pipelines_spec.rb"
+- "./spec/migrations/cleanup_legacy_artifact_migration_spec.rb"
+- "./spec/migrations/migrate_protected_attribute_to_pending_builds_spec.rb"
+- "./spec/migrations/re_schedule_latest_pipeline_id_population_with_all_security_related_artifact_types_spec.rb"
+- "./spec/migrations/schedule_migrate_security_scans_spec.rb"
+- "./spec/models/ci/build_spec.rb"
+- "./spec/models/ci/job_artifact_spec.rb"
+- "./spec/models/ci/job_token/scope_spec.rb"
+- "./spec/models/ci/pipeline_spec.rb"
+- "./spec/models/ci/runner_spec.rb"
+- "./spec/models/clusters/applications/runner_spec.rb"
+- "./spec/models/deployment_spec.rb"
+- "./spec/models/environment_spec.rb"
+- "./spec/models/merge_request_spec.rb"
+- "./spec/models/project_spec.rb"
+- "./spec/models/user_spec.rb"
+- "./spec/presenters/ci/build_runner_presenter_spec.rb"
+- "./spec/presenters/ci/pipeline_presenter_spec.rb"
+- "./spec/presenters/packages/detail/package_presenter_spec.rb"
+- "./spec/requests/api/ci/pipelines_spec.rb"
+- "./spec/requests/api/ci/runner/jobs_request_post_spec.rb"
+- "./spec/requests/api/ci/runner/runners_post_spec.rb"
+- "./spec/requests/api/ci/runners_spec.rb"
+- "./spec/requests/api/commit_statuses_spec.rb"
+- "./spec/requests/api/graphql/group_query_spec.rb"
+- "./spec/requests/api/graphql/merge_request/merge_request_spec.rb"
+- "./spec/requests/api/graphql/mutations/ci/job_token_scope/add_project_spec.rb"
+- "./spec/requests/api/graphql/mutations/ci/job_token_scope/remove_project_spec.rb"
+- "./spec/requests/api/graphql/mutations/environments/canary_ingress/update_spec.rb"
+- "./spec/requests/api/graphql/mutations/merge_requests/create_spec.rb"
+- "./spec/requests/api/graphql/packages/composer_spec.rb"
+- "./spec/requests/api/graphql/packages/conan_spec.rb"
+- "./spec/requests/api/graphql/packages/maven_spec.rb"
+- "./spec/requests/api/graphql/packages/nuget_spec.rb"
+- "./spec/requests/api/graphql/packages/package_spec.rb"
+- "./spec/requests/api/graphql/packages/pypi_spec.rb"
+- "./spec/requests/api/graphql/project/merge_request/pipelines_spec.rb"
+- "./spec/requests/api/graphql/project/merge_request_spec.rb"
+- "./spec/requests/api/graphql/project/merge_requests_spec.rb"
+- "./spec/requests/api/graphql/project/pipeline_spec.rb"
+- "./spec/requests/api/merge_requests_spec.rb"
+- "./spec/requests/api/package_files_spec.rb"
+- "./spec/services/auto_merge/merge_when_pipeline_succeeds_service_spec.rb"
+- "./spec/services/ci/create_pipeline_service/cross_project_pipeline_spec.rb"
+- "./spec/services/ci/create_pipeline_service/needs_spec.rb"
+- "./spec/services/ci/create_pipeline_service_spec.rb"
+- "./spec/services/ci/destroy_pipeline_service_spec.rb"
+- "./spec/services/ci/expire_pipeline_cache_service_spec.rb"
+- "./spec/services/ci/job_artifacts/destroy_all_expired_service_spec.rb"
+- "./spec/services/ci/job_artifacts/destroy_associations_service_spec.rb"
+- "./spec/services/ci/job_artifacts/destroy_batch_service_spec.rb"
+- "./spec/services/ci/pipeline_processing/shared_processing_service.rb"
+- "./spec/services/ci/pipeline_processing/shared_processing_service_tests_with_yaml.rb"
+- "./spec/services/ci/register_job_service_spec.rb"
+- "./spec/services/clusters/applications/prometheus_config_service_spec.rb"
+- "./spec/services/deployments/older_deployments_drop_service_spec.rb"
+- "./spec/services/environments/auto_stop_service_spec.rb"
+- "./spec/services/environments/stop_service_spec.rb"
+- "./spec/services/merge_requests/add_todo_when_build_fails_service_spec.rb"
+- "./spec/services/merge_requests/create_service_spec.rb"
+- "./spec/services/merge_requests/post_merge_service_spec.rb"
+- "./spec/services/merge_requests/refresh_service_spec.rb"
+- "./spec/support/prometheus/additional_metrics_shared_examples.rb"
+- "./spec/support/shared_examples/ci/pipeline_email_shared_examples.rb"
+- "./spec/support/shared_examples/features/packages_shared_examples.rb"
+- "./spec/support/shared_examples/features/search_settings_shared_examples.rb"
+- "./spec/support/shared_examples/features/variable_list_shared_examples.rb"
+- "./spec/support/shared_examples/models/concerns/limitable_shared_examples.rb"
+- "./spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb"
+- "./spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb"
+- "./spec/support/shared_examples/requests/api/graphql/packages/package_details_shared_examples.rb"
+- "./spec/support/shared_examples/requests/api/logging_application_context_shared_examples.rb"
+- "./spec/support/shared_examples/requests/api/status_shared_examples.rb"
+- "./spec/support/shared_examples/requests/graphql_shared_examples.rb"
+- "./spec/support/shared_examples/services/onboarding_progress_shared_examples.rb"
+- "./spec/support/shared_examples/services/packages_shared_examples.rb"
+- "./spec/support/shared_examples/workers/idempotency_shared_examples.rb"
+- "./spec/tasks/gitlab/generate_sample_prometheus_data_spec.rb"
+- "./spec/workers/pipeline_process_worker_spec.rb"
+- "./spec/workers/pipeline_schedule_worker_spec.rb"
diff --git a/spec/support/database/gitlab_schema.rb b/spec/support/database/gitlab_schema.rb
new file mode 100644
index 00000000000..fe05fb998e6
--- /dev/null
+++ b/spec/support/database/gitlab_schema.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+# This module gathes information about table to schema mapping
+# to understand table affinity
+module Database
+ module GitlabSchema
+ def self.table_schemas(tables)
+ tables.map { |table| table_schema(table) }.to_set
+ end
+
+ def self.table_schema(name)
+ tables_to_schema[name] || :undefined
+ end
+
+ def self.tables_to_schema
+ @tables_to_schema ||= all_classes_with_schema.to_h do |klass|
+ [klass.table_name, klass.gitlab_schema]
+ end
+ end
+
+ def self.all_classes_with_schema
+ ActiveRecord::Base.descendants.reject(&:abstract_class?).select(&:gitlab_schema?) # rubocop:disable Database/MultipleDatabases
+ end
+ end
+end
diff --git a/spec/support/database/multiple_databases.rb b/spec/support/database/multiple_databases.rb
new file mode 100644
index 00000000000..8ce642a682c
--- /dev/null
+++ b/spec/support/database/multiple_databases.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module Database
+ module MultipleDatabases
+ def skip_if_multiple_databases_not_setup
+ skip 'Skipping because multiple databases not set up' unless Gitlab::Database.has_config?(:ci)
+ end
+ end
+end
diff --git a/spec/support/database/prevent_cross_database_modification.rb b/spec/support/database/prevent_cross_database_modification.rb
index 460ee99391b..b4c968e3c41 100644
--- a/spec/support/database/prevent_cross_database_modification.rb
+++ b/spec/support/database/prevent_cross_database_modification.rb
@@ -74,18 +74,20 @@ module Database
return if cross_database_context[:transaction_depth_by_db].values.all?(&:zero?)
- tables = PgQuery.parse(sql).dml_tables
+ parsed_query = PgQuery.parse(sql)
+ tables = sql.downcase.include?(' for update') ? parsed_query.tables : parsed_query.dml_tables
return if tables.empty?
cross_database_context[:modified_tables_by_db][database].merge(tables)
all_tables = cross_database_context[:modified_tables_by_db].values.map(&:to_a).flatten
+ schemas = Database::GitlabSchema.table_schemas(all_tables)
- unless PreventCrossJoins.only_ci_or_only_main?(all_tables)
+ if schemas.many?
raise Database::PreventCrossDatabaseModification::CrossDatabaseModificationAcrossUnsupportedTablesError,
- "Cross-database data modification queries (CI and Main) were detected within " \
- "a transaction '#{all_tables.join(", ")}' discovered"
+ "Cross-database data modification of '#{schemas.to_a.join(", ")}' were detected within " \
+ "a transaction modifying the '#{all_tables.to_a.join(", ")}'"
end
end
end
diff --git a/spec/support/database/prevent_cross_joins.rb b/spec/support/database/prevent_cross_joins.rb
index 789721ccd38..4b78aa9014c 100644
--- a/spec/support/database/prevent_cross_joins.rb
+++ b/spec/support/database/prevent_cross_joins.rb
@@ -11,7 +11,7 @@
#
# class User
# def ci_owned_runners
-# ::Gitlab::Database.allow_cross_joins_across_databases!(url: link-to-issue-url)
+# ::Gitlab::Database.allow_cross_joins_across_databases(url: link-to-issue-url)
#
# ...
# end
@@ -21,33 +21,43 @@ module Database
module PreventCrossJoins
CrossJoinAcrossUnsupportedTablesError = Class.new(StandardError)
+ ALLOW_THREAD_KEY = :allow_cross_joins_across_databases
+
def self.validate_cross_joins!(sql)
- return if Thread.current[:allow_cross_joins_across_databases]
+ return if Thread.current[ALLOW_THREAD_KEY]
+
+ # Allow spec/support/database_cleaner.rb queries to disable/enable triggers for many tables
+ # See https://gitlab.com/gitlab-org/gitlab/-/issues/339396
+ return if sql.include?("DISABLE TRIGGER") || sql.include?("ENABLE TRIGGER")
# PgQuery might fail in some cases due to limited nesting:
# https://github.com/pganalyze/pg_query/issues/209
- tables = PgQuery.parse(sql).tables
+ #
+ # Also, we disable GC while parsing because of https://github.com/pganalyze/pg_query/issues/226
+ begin
+ GC.disable
+ tables = PgQuery.parse(sql).tables
+ ensure
+ GC.enable
+ end
- unless only_ci_or_only_main?(tables)
+ schemas = Database::GitlabSchema.table_schemas(tables)
+
+ if schemas.include?(:gitlab_ci) && schemas.include?(:gitlab_main)
+ Thread.current[:has_cross_join_exception] = true
raise CrossJoinAcrossUnsupportedTablesError,
- "Unsupported cross-join across '#{tables.join(", ")}' discovered " \
- "when executing query '#{sql}'"
+ "Unsupported cross-join across '#{tables.join(", ")}' modifying '#{schemas.to_a.join(", ")}' discovered " \
+ "when executing query '#{sql}'. Please refer to https://docs.gitlab.com/ee/development/database/multiple_databases.html#removing-joins-between-ci_-and-non-ci_-tables for details on how to resolve this exception."
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
+ Thread.current[ALLOW_THREAD_KEY] = false
yield
ensure
@@ -57,8 +67,12 @@ module Database
module GitlabDatabaseMixin
def allow_cross_joins_across_databases(url:)
- Thread.current[:allow_cross_joins_across_databases] = true
- super
+ old_value = Thread.current[ALLOW_THREAD_KEY]
+ Thread.current[ALLOW_THREAD_KEY] = true
+
+ yield
+ ensure
+ Thread.current[ALLOW_THREAD_KEY] = old_value
end
end
end
@@ -67,11 +81,18 @@ end
Gitlab::Database.singleton_class.prepend(
Database::PreventCrossJoins::GitlabDatabaseMixin)
+ALLOW_LIST = Set.new(YAML.load_file(File.join(__dir__, 'cross-join-allowlist.yml'))).freeze
+
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 }
+ config.around do |example|
+ Thread.current[:has_cross_join_exception] = false
+
+ if ALLOW_LIST.include?(example.file_path)
+ example.run
+ else
+ with_cross_joins_prevented { example.run }
+ end
end
end