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/cops'))
|
||||||
Rails.autoloaders.main.ignore(Rails.root.join('lib/linters'))
|
Rails.autoloaders.main.ignore(Rails.root.join('lib/linters'))
|
||||||
Rails.autoloaders.main.ignore(Rails.root.join('lib/tasks/task_helper.rb'))
|
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.paths.add Rails.root.join('spec/mailers/previews').to_s, eager_load: true
|
||||||
config.autoload_paths << "#{Rails.root}/app/jobs/concerns"
|
config.autoload_paths << "#{Rails.root}/app/jobs/concerns"
|
||||||
|
|
||||||
|
|
|
@ -113,6 +113,29 @@
|
||||||
],
|
],
|
||||||
"note": ""
|
"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_type": "Cross-Site Scripting",
|
||||||
"warning_code": 2,
|
"warning_code": 2,
|
||||||
|
|
|
@ -5,6 +5,8 @@ module <%= tasks_module %>
|
||||||
class <%= class_name %>Task < MaintenanceTasks::Task
|
class <%= class_name %>Task < MaintenanceTasks::Task
|
||||||
# Documentation: cette tâche modifie les données pour…
|
# Documentation: cette tâche modifie les données pour…
|
||||||
|
|
||||||
|
include StatementsHelpersConcern
|
||||||
|
|
||||||
def collection
|
def collection
|
||||||
# Collection to be iterated over
|
# Collection to be iterated over
|
||||||
# Must be Active Record Relation or Array
|
# 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