Module: Semian::ActiveRecordTrilogyAdapter

Includes:
Adapter
Included in:
ActiveRecord::ConnectionAdapters::TrilogyAdapter
Defined in:
lib/semian/activerecord_trilogy_adapter.rb

Constant Summary collapse

ResourceBusyError =
::ActiveRecord::ConnectionAdapters::TrilogyAdapter::ResourceBusyError
CircuitOpenError =
::ActiveRecord::ConnectionAdapters::TrilogyAdapter::CircuitOpenError
QUERY_ALLOWLIST =
%r{\A(?:/\*.*?\*/)?\s*(ROLLBACK|COMMIT|RELEASE\s+SAVEPOINT)}i.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Adapter

#clear_semian_resource, #semian_resource

Instance Attribute Details

#raw_semian_optionsObject (readonly)

Returns the value of attribute raw_semian_options.



65
66
67
# File 'lib/semian/activerecord_trilogy_adapter.rb', line 65

def raw_semian_options
  @raw_semian_options
end

#semian_identifierObject (readonly)

Returns the value of attribute semian_identifier.



65
66
67
# File 'lib/semian/activerecord_trilogy_adapter.rb', line 65

def semian_identifier
  @semian_identifier
end

Class Method Details

.query_allowlisted?(sql) ⇒ Boolean

Returns:

  • (Boolean)


44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/semian/activerecord_trilogy_adapter.rb', line 44

def query_allowlisted?(sql, *)
  # COMMIT, ROLLBACK
  tx_command_statement = sql.end_with?("T") || sql.end_with?("K")

  # RELEASE SAVEPOINT. Nesting past _3 levels won't get bypassed.
  # Active Record does not send trailing spaces or `;`, so we are in the realm of hand crafted queries here.
  savepoint_statement = sql.end_with?("_1") || sql.end_with?("_2")
  unclear = sql.end_with?(" ") || sql.end_with?(";")

  if !tx_command_statement && !savepoint_statement && !unclear
    false
  else
    QUERY_ALLOWLIST.match?(sql)
  end
rescue ArgumentError
  return false unless sql.valid_encoding?

  raise
end

Instance Method Details

#active?Boolean

Returns:

  • (Boolean)


94
95
96
97
98
99
100
# File 'lib/semian/activerecord_trilogy_adapter.rb', line 94

def active?
  acquire_semian_resource(adapter: :trilogy_adapter, scope: :ping) do
    super
  end
rescue ResourceBusyError, CircuitOpenError
  false
end

#initialize(*options) ⇒ Object



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/semian/activerecord_trilogy_adapter.rb', line 67

def initialize(*options)
  *, config = options
  config = config.dup
  @raw_semian_options = config.delete(:semian)
  @semian_identifier = begin
    name = semian_options && semian_options[:name]
    unless name
      host = config[:host] || "localhost"
      port = config[:port] || 3306
      name = "#{host}:#{port}"
    end
    :"mysql_#{name}"
  end
  super
end

#raw_execute(sql) ⇒ Object



83
84
85
86
87
88
89
90
91
# File 'lib/semian/activerecord_trilogy_adapter.rb', line 83

def raw_execute(sql, *)
  if Semian::ActiveRecordTrilogyAdapter.query_allowlisted?(sql)
    super
  else
    acquire_semian_resource(adapter: :trilogy_adapter, scope: :query) do
      super
    end
  end
end

#with_resource_timeout(temp_timeout) ⇒ Object



102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/semian/activerecord_trilogy_adapter.rb', line 102

def with_resource_timeout(temp_timeout)
  if @raw_connection.nil?
    prev_read_timeout = @config[:read_timeout] || 0
    @config.merge!(read_timeout: temp_timeout) # Create new client with temp_timeout for read timeout
  else
    prev_read_timeout = @raw_connection.read_timeout
    @raw_connection.read_timeout = temp_timeout
  end
  yield
ensure
  @config.merge!(read_timeout: prev_read_timeout)
  @raw_connection&.read_timeout = prev_read_timeout
end