debian-mirror-gitlab/lib/gitlab/database/query_analyzer.rb

122 lines
3.6 KiB
Ruby
Raw Normal View History

2021-12-11 22:18:48 +05:30
# frozen_string_literal: true
module Gitlab
module Database
# The purpose of this class is to implement a various query analyzers based on `pg_query`
# And process them all via `Gitlab::Database::QueryAnalyzers::*`
#
# Sometimes this might cause errors in specs.
# This is best to be disable with `describe '...', query_analyzers: false do`
class QueryAnalyzer
include ::Singleton
Parsed = Struct.new(
:sql, :connection, :pg
)
attr_reader :all_analyzers
def initialize
@all_analyzers = []
end
def hook!
@subscriber = ActiveSupport::Notifications.subscribe('sql.active_record') do |event|
# In some cases analyzer code might trigger another SQL call
# to avoid stack too deep this detects recursive call of subscriber
with_ignored_recursive_calls do
process_sql(event.payload[:sql], event.payload[:connection])
end
end
end
2022-07-16 23:28:13 +05:30
def within(analyzers = all_analyzers)
newly_enabled_analyzers = begin!(analyzers)
2021-12-11 22:18:48 +05:30
begin
yield
ensure
2022-07-16 23:28:13 +05:30
end!(newly_enabled_analyzers)
2021-12-11 22:18:48 +05:30
end
end
2022-07-16 23:28:13 +05:30
# Enable query analyzers (only the ones that were not yet enabled)
# Returns a list of newly enabled analyzers
def begin!(analyzers)
analyzers.select do |analyzer|
next if enabled_analyzers.include?(analyzer)
2021-12-11 22:18:48 +05:30
if analyzer.enabled?
analyzer.begin!
2022-07-16 23:28:13 +05:30
enabled_analyzers.append(analyzer)
2021-12-11 22:18:48 +05:30
true
end
2022-05-07 20:08:51 +05:30
rescue StandardError, ::Gitlab::Database::QueryAnalyzers::Base::QueryAnalyzerError => e
2021-12-11 22:18:48 +05:30
Gitlab::ErrorTracking.track_and_raise_for_dev_exception(e)
false
end
end
2022-07-16 23:28:13 +05:30
# Disable enabled query analyzers (only the ones that were enabled previously)
def end!(analyzers)
analyzers.each do |analyzer|
next unless enabled_analyzers.delete(analyzer)
2021-12-11 22:18:48 +05:30
analyzer.end!
2022-05-07 20:08:51 +05:30
rescue StandardError, ::Gitlab::Database::QueryAnalyzers::Base::QueryAnalyzerError => e
2021-12-11 22:18:48 +05:30
Gitlab::ErrorTracking.track_and_raise_for_dev_exception(e)
end
end
2022-01-26 12:08:38 +05:30
private
2021-12-11 22:18:48 +05:30
def enabled_analyzers
2022-07-16 23:28:13 +05:30
Thread.current[:query_analyzer_enabled_analyzers] ||= []
end
def process_sql(sql, connection)
analyzers = enabled_analyzers
return unless analyzers&.any?
parsed = parse(sql, connection)
return unless parsed
analyzers.each do |analyzer|
next if analyzer.suppressed? && !analyzer.requires_tracking?(parsed)
2023-03-17 16:20:25 +05:30
analyzer.analyze(parsed)
2022-07-16 23:28:13 +05:30
rescue StandardError, ::Gitlab::Database::QueryAnalyzers::Base::QueryAnalyzerError => e
# We catch all standard errors to prevent validation errors to introduce fatal errors in production
Gitlab::ErrorTracking.track_and_raise_for_dev_exception(e)
end
2021-12-11 22:18:48 +05:30
end
def parse(sql, connection)
parsed = PgQuery.parse(sql)
return unless parsed
normalized = PgQuery.normalize(sql)
Parsed.new(normalized, connection, parsed)
rescue PgQuery::ParseError => e
# Ignore PgQuery parse errors (due to depth limit or other reasons)
Gitlab::ErrorTracking.track_exception(e)
nil
end
def with_ignored_recursive_calls
return if Thread.current[:query_analyzer_recursive]
begin
Thread.current[:query_analyzer_recursive] = true
yield
ensure
Thread.current[:query_analyzer_recursive] = nil
end
end
end
end
end