Module: Txnap::LockHeuristics
- Defined in:
- lib/txnap/lock_heuristics.rb
Constant Summary collapse
- IDENTIFIER_PART =
/(?:"(?:[^"]|"")*"|`(?:[^`]|``)*`|\[[^\]]+\]|[a-z_][a-z0-9_$]*)/i- QUALIFIED_IDENTIFIER =
/#{IDENTIFIER_PART}(?:\s*\.\s*#{IDENTIFIER_PART})*/- ADVISORY_FUNCTION =
/ (?: pg_(?:try_)?advisory_(?:xact_)?lock(?:_shared)? | get_lock ) /ix
Class Method Summary collapse
- .detect(sql, call_site: nil) ⇒ Object
- .detect_select_or_advisory(statement) ⇒ Object
- .normalize_identifier(identifier) ⇒ Object
- .strip_leading_comments(sql) ⇒ Object
Class Method Details
.detect(sql, call_site: nil) ⇒ Object
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
# File 'lib/txnap/lock_heuristics.rb', line 17 def detect(sql, call_site: nil) statement = strip_leading_comments(Array(sql).join(" ")) return if statement.empty? operation, table, kind = case statement when /\AINSERT(?:\s+OR\s+\w+)?\s+INTO\s+(#{QUALIFIED_IDENTIFIER})/io ["INSERT", normalize_identifier(Regexp.last_match(1)), "row lock"] when /\AUPDATE\s+(?:ONLY\s+)?(#{QUALIFIED_IDENTIFIER})/io ["UPDATE", normalize_identifier(Regexp.last_match(1)), "row lock"] when /\ADELETE\s+FROM\s+(?:ONLY\s+)?(#{QUALIFIED_IDENTIFIER})/io ["DELETE", normalize_identifier(Regexp.last_match(1)), "row lock"] when /\ALOCK\s+TABLE\s+(?:ONLY\s+)?(#{QUALIFIED_IDENTIFIER})/io ["LOCK TABLE", normalize_identifier(Regexp.last_match(1)), "table lock"] else detect_select_or_advisory(statement) end return unless operation LockCandidate.new( statement: operation, table: table, kind: kind, call_site: call_site ).freeze end |
.detect_select_or_advisory(statement) ⇒ Object
45 46 47 48 49 50 51 52 53 54 55 56 |
# File 'lib/txnap/lock_heuristics.rb', line 45 def detect_select_or_advisory(statement) if statement.match?(/\ASELECT\b/im) && statement.match?(/\bFOR\s+(?:NO\s+KEY\s+)?(?:UPDATE|SHARE|KEY\s+SHARE)\b/im) table = statement[/\bFROM\s+(#{QUALIFIED_IDENTIFIER})/io, 1] return ["SELECT", normalize_identifier(table), "row lock"] if table end function = statement[ADVISORY_FUNCTION] return unless function ["ADVISORY LOCK", function.downcase, "advisory lock"] end |
.normalize_identifier(identifier) ⇒ Object
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
# File 'lib/txnap/lock_heuristics.rb', line 76 def normalize_identifier(identifier) return unless identifier identifier.split(/\s*\.\s*/).map do |part| case part when /\A"(.*)"\z/m Regexp.last_match(1).gsub('""', '"') when /\A`(.*)`\z/m Regexp.last_match(1).gsub("``", "`") when /\A\[(.*)\]\z/m Regexp.last_match(1) else part end end.join(".") end |
.strip_leading_comments(sql) ⇒ Object
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
# File 'lib/txnap/lock_heuristics.rb', line 58 def strip_leading_comments(sql) remaining = sql.lstrip loop do stripped = if remaining.start_with?("--") remaining.sub(/\A--[^\n]*(?:\n|\z)/, "").lstrip elsif remaining.start_with?("/*") remaining.sub(/\A\/\*.*?\*\//m, "").lstrip else remaining end return remaining if stripped == remaining remaining = stripped end end |