module Sequel::SQLLogNormalizer

Public Class Methods

extended(db) click to toggle source
# File lib/sequel/extensions/sql_log_normalizer.rb, line 32
def self.extended(db)
  type = case db.literal("'")
  when "''''"
    :standard
  when "'\\''"
    :backslash
  when "N''''"
    :n_standard
  else
    raise Error, "SQL log normalization is not supported on this database (' literalized as #{db.literal("'").inspect})"
  end
  db.instance_variable_set(:@sql_string_escape_type, type)
end

Public Instance Methods

log_connection_yield(sql, conn, args=nil) click to toggle source

Normalize the SQL before calling super.

Calls superclass method
# File lib/sequel/extensions/sql_log_normalizer.rb, line 47
def log_connection_yield(sql, conn, args=nil)
  unless skip_logging?
    sql = normalize_logged_sql(sql)
    args = nil
  end
  super
end
normalize_logged_sql(sql) click to toggle source

Replace literal strings and numbers in SQL with question mark placeholders.

# File lib/sequel/extensions/sql_log_normalizer.rb, line 56
def normalize_logged_sql(sql)
  sql = sql.dup
  sql.force_encoding('BINARY')
  start_index = 0
  check_n = @sql_string_escape_type == :n_standard
  outside_string = true

  if @sql_string_escape_type == :backslash
    search_char = /[\\']/
    escape_char_offset = 0
    escape_char_value = 92 # backslash
  else
    search_char = "'"
    escape_char_offset = 1
    escape_char_value = 39 # apostrophe
  end

  # The approach used here goes against Sequel's philosophy of never attempting
  # to parse SQL.  However, parsing the SQL is basically the only way to implement
  # this support with Sequel's design, and it's better to be pragmatic and accept
  # this than not be able to support this.

  # Replace literal strings
  while outside_string && (index = start_index = sql.index("'", start_index))
    if check_n && index != 0 && sql.getbyte(index-1) == 78 # N' start
      start_index -= 1
    end
    index += 1
    outside_string = false

    while (index = sql.index(search_char, index)) && (sql.getbyte(index + escape_char_offset) == escape_char_value)
      # skip escaped characters inside string literal
      index += 2
    end

    if index
      # Found end of string
      sql[start_index..index] = '?'
      start_index += 1
      outside_string = true
    end
  end

  # Replace integer and decimal floating point numbers
  sql.gsub!(/\b-?\d+(?:\.\d+)?\b/, '?')

  sql
end