2021-12-11 22:18:48 +05:30
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
require 'spec_helper'
|
|
|
|
|
|
|
|
RSpec.describe Gitlab::Database::QueryAnalyzer, query_analyzers: false do
|
|
|
|
let(:analyzer) { double(:query_analyzer) }
|
2022-07-16 23:28:13 +05:30
|
|
|
let(:user_analyzer) { double(:user_query_analyzer) }
|
2021-12-11 22:18:48 +05:30
|
|
|
let(:disabled_analyzer) { double(:disabled_query_analyzer) }
|
|
|
|
|
|
|
|
before do
|
|
|
|
allow(described_class.instance).to receive(:all_analyzers).and_return([analyzer, disabled_analyzer])
|
|
|
|
allow(analyzer).to receive(:enabled?).and_return(true)
|
2023-01-13 00:05:48 +05:30
|
|
|
allow(analyzer).to receive(:raw?).and_return(false)
|
2021-12-11 22:18:48 +05:30
|
|
|
allow(analyzer).to receive(:suppressed?).and_return(false)
|
|
|
|
allow(analyzer).to receive(:begin!)
|
|
|
|
allow(analyzer).to receive(:end!)
|
|
|
|
allow(disabled_analyzer).to receive(:enabled?).and_return(false)
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'the hook is enabled by default in specs' do
|
|
|
|
it 'does process queries and gets normalized SQL' do
|
|
|
|
expect(analyzer).to receive(:enabled?).and_return(true)
|
|
|
|
expect(analyzer).to receive(:analyze) do |parsed|
|
|
|
|
expect(parsed.sql).to include("SELECT $1 FROM projects")
|
|
|
|
expect(parsed.pg.tables).to eq(%w[projects])
|
|
|
|
end
|
|
|
|
|
|
|
|
described_class.instance.within do
|
|
|
|
Project.connection.execute("SELECT 1 FROM projects")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'does prevent recursive execution' do
|
|
|
|
expect(analyzer).to receive(:enabled?).and_return(true)
|
|
|
|
expect(analyzer).to receive(:analyze) do
|
|
|
|
Project.connection.execute("SELECT 1 FROM projects")
|
|
|
|
end
|
|
|
|
|
|
|
|
described_class.instance.within do
|
|
|
|
Project.connection.execute("SELECT 1 FROM projects")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '#within' do
|
|
|
|
context 'when it is already initialized' do
|
|
|
|
around do |example|
|
|
|
|
described_class.instance.within do
|
|
|
|
example.run
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-07-16 23:28:13 +05:30
|
|
|
it 'does initialize analyzer only once' do
|
|
|
|
expect(analyzer).to receive(:enabled?).once
|
|
|
|
expect(analyzer).to receive(:begin!).once
|
|
|
|
expect(analyzer).to receive(:end!).once
|
2021-12-11 22:18:48 +05:30
|
|
|
|
|
|
|
expect { |b| described_class.instance.within(&b) }.to yield_control
|
|
|
|
end
|
2022-05-07 20:08:51 +05:30
|
|
|
|
2022-07-16 23:28:13 +05:30
|
|
|
it 'does initialize user analyzer when enabled' do
|
|
|
|
expect(user_analyzer).to receive(:enabled?).and_return(true)
|
|
|
|
expect(user_analyzer).to receive(:begin!)
|
|
|
|
expect(user_analyzer).to receive(:end!)
|
|
|
|
|
|
|
|
expect { |b| described_class.instance.within([user_analyzer], &b) }.to yield_control
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'does initialize user analyzer only once' do
|
|
|
|
expect(user_analyzer).to receive(:enabled?).and_return(false, true)
|
|
|
|
expect(user_analyzer).to receive(:begin!).once
|
|
|
|
expect(user_analyzer).to receive(:end!).once
|
|
|
|
|
|
|
|
expect { |b| described_class.instance.within([user_analyzer, user_analyzer, user_analyzer], &b) }.to yield_control
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'does not initializer user analyzer when disabled' do
|
|
|
|
expect(user_analyzer).to receive(:enabled?).and_return(false)
|
|
|
|
expect(user_analyzer).not_to receive(:begin!)
|
|
|
|
expect(user_analyzer).not_to receive(:end!)
|
|
|
|
|
|
|
|
expect { |b| described_class.instance.within([user_analyzer], &b) }.to yield_control
|
2022-05-07 20:08:51 +05:30
|
|
|
end
|
2021-12-11 22:18:48 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
context 'when initializer is enabled' do
|
|
|
|
before do
|
|
|
|
expect(analyzer).to receive(:enabled?).and_return(true)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'calls begin! and end!' do
|
|
|
|
expect(analyzer).to receive(:begin!)
|
|
|
|
expect(analyzer).to receive(:end!)
|
|
|
|
|
|
|
|
expect { |b| described_class.instance.within(&b) }.to yield_control
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'when begin! raises the end! is not called' do
|
|
|
|
expect(analyzer).to receive(:begin!).and_raise('exception')
|
|
|
|
expect(analyzer).not_to receive(:end!)
|
|
|
|
expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception)
|
|
|
|
|
|
|
|
expect { |b| described_class.instance.within(&b) }.to yield_control
|
|
|
|
end
|
|
|
|
end
|
2022-05-07 20:08:51 +05:30
|
|
|
|
|
|
|
context 'when user analyzers are used' do
|
|
|
|
it 'calls begin! and end!' do
|
|
|
|
expect(analyzer).not_to receive(:begin!)
|
|
|
|
allow(user_analyzer).to receive(:enabled?).and_return(true)
|
|
|
|
allow(user_analyzer).to receive(:suppressed?).and_return(false)
|
|
|
|
expect(user_analyzer).to receive(:begin!)
|
|
|
|
expect(user_analyzer).to receive(:end!)
|
|
|
|
|
|
|
|
expect { |b| described_class.instance.within([user_analyzer], &b) }.to yield_control
|
|
|
|
end
|
|
|
|
end
|
2021-12-11 22:18:48 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
describe '#process_sql' do
|
|
|
|
it 'does not analyze query if not enabled' do
|
|
|
|
expect(analyzer).to receive(:enabled?).and_return(false)
|
|
|
|
expect(analyzer).not_to receive(:analyze)
|
|
|
|
|
|
|
|
process_sql("SELECT 1 FROM projects")
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'does analyze query if enabled' do
|
|
|
|
expect(analyzer).to receive(:enabled?).and_return(true)
|
|
|
|
expect(analyzer).to receive(:analyze) do |parsed|
|
|
|
|
expect(parsed.sql).to eq("SELECT $1 FROM projects")
|
|
|
|
expect(parsed.pg.tables).to eq(%w[projects])
|
|
|
|
end
|
|
|
|
|
|
|
|
process_sql("SELECT 1 FROM projects")
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'does track exception if query cannot be parsed' do
|
|
|
|
expect(analyzer).to receive(:enabled?).and_return(true)
|
|
|
|
expect(analyzer).not_to receive(:analyze)
|
|
|
|
expect(Gitlab::ErrorTracking).to receive(:track_exception)
|
|
|
|
|
|
|
|
expect { process_sql("invalid query") }.not_to raise_error
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'does track exception if analyzer raises exception on enabled?' do
|
|
|
|
expect(analyzer).to receive(:enabled?).and_raise('exception')
|
|
|
|
expect(analyzer).not_to receive(:analyze)
|
|
|
|
expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception)
|
|
|
|
|
|
|
|
expect { process_sql("SELECT 1 FROM projects") }.not_to raise_error
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'does track exception if analyzer raises exception on analyze' do
|
|
|
|
expect(analyzer).to receive(:enabled?).and_return(true)
|
|
|
|
expect(analyzer).to receive(:analyze).and_raise('exception')
|
|
|
|
expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception)
|
|
|
|
|
|
|
|
expect { process_sql("SELECT 1 FROM projects") }.not_to raise_error
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'does call analyze only on enabled initializers' do
|
|
|
|
expect(analyzer).to receive(:analyze)
|
|
|
|
expect(disabled_analyzer).not_to receive(:analyze)
|
|
|
|
|
|
|
|
expect { process_sql("SELECT 1 FROM projects") }.not_to raise_error
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'does not call analyze on suppressed analyzers' do
|
|
|
|
expect(analyzer).to receive(:suppressed?).and_return(true)
|
2022-01-26 12:08:38 +05:30
|
|
|
expect(analyzer).to receive(:requires_tracking?).and_return(false)
|
2021-12-11 22:18:48 +05:30
|
|
|
expect(analyzer).not_to receive(:analyze)
|
|
|
|
|
|
|
|
expect { process_sql("SELECT 1 FROM projects") }.not_to raise_error
|
|
|
|
end
|
|
|
|
|
2022-01-26 12:08:38 +05:30
|
|
|
it 'does call analyze on suppressed analyzers if some queries require tracking' do
|
|
|
|
expect(analyzer).to receive(:suppressed?).and_return(true)
|
|
|
|
expect(analyzer).to receive(:requires_tracking?).and_return(true)
|
|
|
|
expect(analyzer).to receive(:analyze)
|
|
|
|
|
|
|
|
expect { process_sql("SELECT 1 FROM projects") }.not_to raise_error
|
|
|
|
end
|
|
|
|
|
2023-01-13 00:05:48 +05:30
|
|
|
it 'does call analyze with raw sql when raw? is true' do
|
|
|
|
expect(analyzer).to receive(:raw?).and_return(true)
|
|
|
|
expect(analyzer).to receive(:analyze).with('SELECT 1 FROM projects')
|
|
|
|
|
|
|
|
expect { process_sql("SELECT 1 FROM projects") }.not_to raise_error
|
|
|
|
end
|
|
|
|
|
2021-12-11 22:18:48 +05:30
|
|
|
def process_sql(sql)
|
|
|
|
described_class.instance.within do
|
|
|
|
ApplicationRecord.load_balancer.read_write do |connection|
|
2022-07-16 23:28:13 +05:30
|
|
|
described_class.instance.send(:process_sql, sql, connection)
|
2021-12-11 22:18:48 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|