Class: Exwiw::Adapter::MysqlClient

Inherits:
Object
  • Object
show all
Defined in:
lib/exwiw/adapter/mysql_client.rb

Overview

Thin wrapper over the MySQL driver so MysqlAdapter does not care whether the host app ships the ‘mysql2` gem or the `trilogy` gem. exwiw only runs simple SELECT / EXPLAIN queries, so both drivers are normalized to the same shape: rows as arrays of String|nil plus the column names.

Values are normalized to strings to match mysql2’s ‘cast: false` mode, where every column comes back as a raw string and is quoted uniformly downstream (see MysqlAdapter#escape_value). mysql2 already returns strings in that mode; trilogy always casts to Ruby types (Integer / BigDecimal / Time / Date / …), so its values are stringified back into the same literal form here.

Defined Under Namespace

Classes: Result

Constant Summary collapse

DRIVERS =
[:mysql2, :trilogy].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(connection_config, driver: nil) ⇒ MysqlClient

‘driver:` is mainly a test seam to force a specific driver; in normal use it is auto-detected.



83
84
85
86
87
# File 'lib/exwiw/adapter/mysql_client.rb', line 83

def initialize(connection_config, driver: nil)
  @connection_config = connection_config
  @driver = (driver || self.class.detect_driver).to_sym
  ensure_driver_loaded!
end

Instance Attribute Details

#driverObject (readonly)

Returns the value of attribute driver.



79
80
81
# File 'lib/exwiw/adapter/mysql_client.rb', line 79

def driver
  @driver
end

Class Method Details

.detect_driverObject

Pick the available driver, preferring mysql2 (exwiw’s historical default). require returns false when already loaded, so this is safe to call repeatedly.

Set EXWIW_MYSQL_DRIVER=trilogy to force the pure-Ruby trilogy driver. This is useful when the mysql2 gem is linked against a libmysqlclient that can no longer load the server’s auth plugin (e.g. MySQL 9.x client dropped the ‘mysql_native_password` plugin .so, raising “Authentication plugin ’mysql_native_password’ cannot be loaded” on connect).



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/exwiw/adapter/mysql_client.rb', line 31

def self.detect_driver
  forced = ENV['EXWIW_MYSQL_DRIVER']
  if forced && !forced.empty?
    sym = forced.to_sym
    unless DRIVERS.include?(sym)
      raise ArgumentError,
            "EXWIW_MYSQL_DRIVER must be one of #{DRIVERS.join(', ')}, got #{forced.inspect}."
    end
    return sym
  end

  require 'mysql2'
  :mysql2
rescue LoadError
  begin
    require 'trilogy'
    :trilogy
  rescue LoadError
    raise LoadError,
          "exwiw needs the 'mysql2' or 'trilogy' gem to connect to MySQL. " \
          "Add `gem \"mysql2\"` (or `gem \"trilogy\"`) to your Gemfile."
  end
end

.stringify_value(value) ⇒ Object

Render a driver-returned value as the raw string mysql2’s ‘cast: false` would have produced, so trilogy’s typed values quote identically.



57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/exwiw/adapter/mysql_client.rb', line 57

def self.stringify_value(value)
  case value
  when nil then nil
  when String then value
  when Time
    # Emit fractional seconds only when present. A Time can't tell us the
    # column's declared precision, so a zero fraction on a DATETIME(6)
    # column comes out as "...:00" here whereas mysql2's `cast: false`
    # echoes the raw "...:00.000000"; both re-insert to the same instant.
    value.nsec.zero? ? value.strftime('%Y-%m-%d %H:%M:%S') : value.strftime('%Y-%m-%d %H:%M:%S.%6N')
  when Date then value.strftime('%Y-%m-%d')
  when true then '1'
  when false then '0'
  else
    if defined?(BigDecimal) && value.is_a?(BigDecimal)
      value.to_s('F')
    else
      value.to_s
    end
  end
end

Instance Method Details

#query(sql) ⇒ Result

Returns fields (Array<String>) and rows (Array<Array<String|nil>>).

Parameters:

  • sql (String)

Returns:

  • (Result)

    fields (Array<String>) and rows (Array<Array<String|nil>>)



91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/exwiw/adapter/mysql_client.rb', line 91

def query(sql)
  case @driver
  when :mysql2
    res = raw.query(sql, cast: false, as: :array)
    Result.new(res.fields, res.to_a)
  when :trilogy
    res = raw.query(sql)
    rows = res.rows.map { |row| row.map { |value| self.class.stringify_value(value) } }
    Result.new(res.fields, rows)
  else
    raise "Unsupported MySQL driver: #{@driver.inspect}"
  end
end