feat(task): with_statement_timeout helper for long running collection or process query
This commit is contained in:
parent
c0ae02f458
commit
d0f77d0aab
5 changed files with 82 additions and 0 deletions
25
app/tasks/maintenance/concerns/statements_helpers_concern.rb
Normal file
25
app/tasks/maintenance/concerns/statements_helpers_concern.rb
Normal file
|
@ -0,0 +1,25 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Maintenance
|
||||
module StatementsHelpersConcern
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
# Execute block in transaction with a local statement timeout.
|
||||
# A value of 0 disable the timeout.
|
||||
#
|
||||
# Example:
|
||||
# def collection
|
||||
# with_statement_timeout("5min") do
|
||||
# Dossier.all
|
||||
# end
|
||||
# end
|
||||
def with_statement_timeout(timeout)
|
||||
ApplicationRecord.transaction do
|
||||
ApplicationRecord.connection.execute("SET LOCAL statement_timeout = '#{timeout}'")
|
||||
yield
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -23,6 +23,7 @@ module TPS
|
|||
Rails.autoloaders.main.ignore(Rails.root.join('lib/cops'))
|
||||
Rails.autoloaders.main.ignore(Rails.root.join('lib/linters'))
|
||||
Rails.autoloaders.main.ignore(Rails.root.join('lib/tasks/task_helper.rb'))
|
||||
Rails.autoloaders.main.collapse('app/tasks/maintenance/concerns')
|
||||
config.paths.add Rails.root.join('spec/mailers/previews').to_s, eager_load: true
|
||||
config.autoload_paths << "#{Rails.root}/app/jobs/concerns"
|
||||
|
||||
|
|
|
@ -113,6 +113,29 @@
|
|||
],
|
||||
"note": ""
|
||||
},
|
||||
{
|
||||
"warning_type": "SQL Injection",
|
||||
"warning_code": 0,
|
||||
"fingerprint": "7dc4935d5b68365bedb8f6b953f01b396cff4daa533c98ee56a84249ca5a1f90",
|
||||
"check_name": "SQL",
|
||||
"message": "Possible SQL injection",
|
||||
"file": "app/tasks/maintenance/concerns/statements_helpers_concern.rb",
|
||||
"line": 19,
|
||||
"link": "https://brakemanscanner.org/docs/warning_types/sql_injection/",
|
||||
"code": "ApplicationRecord.connection.execute(\"SET LOCAL statement_timeout = '#{timeout}'\")",
|
||||
"render_path": null,
|
||||
"location": {
|
||||
"type": "method",
|
||||
"class": "Maintenance::StatementsHelpersConcern",
|
||||
"method": "with_statement_timeout"
|
||||
},
|
||||
"user_input": "timeout",
|
||||
"confidence": "Medium",
|
||||
"cwe_id": [
|
||||
89
|
||||
],
|
||||
"note": ""
|
||||
},
|
||||
{
|
||||
"warning_type": "Cross-Site Scripting",
|
||||
"warning_code": 2,
|
||||
|
|
|
@ -5,6 +5,8 @@ module <%= tasks_module %>
|
|||
class <%= class_name %>Task < MaintenanceTasks::Task
|
||||
# Documentation: cette tâche modifie les données pour…
|
||||
|
||||
include StatementsHelpersConcern
|
||||
|
||||
def collection
|
||||
# Collection to be iterated over
|
||||
# Must be Active Record Relation or Array
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Maintenance::StatementsHelpersConcern do
|
||||
let(:dummy_class) do
|
||||
Class.new do
|
||||
include Maintenance::StatementsHelpersConcern
|
||||
end
|
||||
end
|
||||
|
||||
let(:instance) { dummy_class.new }
|
||||
|
||||
describe '#with_statement_timeout' do
|
||||
it 'applies the statement timeout and raises an error for long-running queries' do
|
||||
expect {
|
||||
instance.with_statement_timeout('1ms') do
|
||||
# Cette requête devrait prendre plus de 1ms et donc déclencher un timeout
|
||||
ActiveRecord::Base.connection.execute("SELECT pg_sleep(1)")
|
||||
end
|
||||
}.to raise_error(ActiveRecord::StatementInvalid, /canceling statement due to statement timeout/i)
|
||||
end
|
||||
|
||||
it 'allows queries to complete within the timeout and returns the result' do
|
||||
result = instance.with_statement_timeout('1s') do
|
||||
ActiveRecord::Base.connection.execute("SELECT 42 AS answer").first['answer']
|
||||
end
|
||||
expect(result).to eq 42
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue