123456789_123456789_123456789_123456789_123456789_

Class: Mysql2::Client

Relationships & Source Files
Extension / Inclusion / Inheritance Descendants
Subclasses:
Inherits: Object
Defined in: lib/mysql2/client.rb,
ext/mysql2/client.c

Constant Summary

  • EMPTY_QUERY_OPTIONS = private

    Shared frozen default for the options argument, so the no-options case skips allocating a fresh empty hash per call. The merge itself is unchanged: it still produces the per-query snapshot the C extension retains as @current_query_options, and explicit-but-invalid arguments like nil or false still raise TypeError from Hash#merge as they always have.

    # File 'lib/mysql2/client.rb', line 237
    {}.freeze
  • TLS_OPTION_ALIASES = private

    :tls_key/:tls_cert/:tls_ca/:tls_capath/:tls_cipher are the modern names for :sslkey/:sslcert/:sslca/:sslcapath/:sslcipher. MariaDB Connector/C registers both spellings for its own equivalent option-file settings (ssl-key and tls-key, ssl-passphrase and tls-passphrase, etc.); mysql2 follows the same pattern here. If both spellings are given, the newer :tls_* name wins -- the same precedence an explicit :ssl_mode already has over :sslverify.

    :tls_mode/:ssl_mode is different: neither MySQL nor MariaDB has a "tls-mode" config-file alias for ssl-mode anywhere -- this one is mysql2's own invention, purely for :tls_* naming consistency. If upstream ever adds a real --tls-mode/tlsMode with different semantics, this alias becomes wrong and will need to be revisited.

    # File 'lib/mysql2/client.rb', line 35
    {
      tls_key: :sslkey,
      tls_cert: :sslcert,
      tls_ca: :sslca,
      tls_capath: :sslcapath,
      tls_cipher: :sslcipher,
      tls_mode: :ssl_mode,
    }.freeze

Class Method Summary

Instance Attribute Summary

Instance Method Summary

Constructor Details

.new(opts = {}) ⇒ Client

Raises:

[ GitHub ]

  
# File 'lib/mysql2/client.rb', line 45

def initialize(opts = {})
  raise Mysql2::Error, "Options parameter must be a Hash" unless opts.is_a? Hash

  opts = Mysql2::Util.key_hash_as_symbols(opts)
  @read_timeout = nil
  @query_options = self.class.default_query_options.dup
  @query_options.merge! opts

  apply_tls_option_aliases(opts)

  initialize_ext

  # Set default connect_timeout to avoid unlimited retries from signal interruption
  opts[:connect_timeout] = 120 unless opts.key?(:connect_timeout)

  # Set an explicit default false for LOCAL INFILE support: some client libraries
  # enable it by default, letting a malicious server read files off the client.
  opts[:local_infile] = false unless opts.key?(:local_infile)

  # TODO: stricter validation rather than silent massaging
  %i[reconnect connect_timeout local_infile read_timeout write_timeout default_file default_group secure_auth init_command automatic_close enable_cleartext_plugin default_auth get_server_public_key tls_version].each do |key|
    next unless opts.key?(key)

    case key
    when :reconnect, :local_infile, :secure_auth, :automatic_close, :enable_cleartext_plugin, :get_server_public_key
      send(:"#{key}=", !!opts[key]) # rubocop:disable Style/DoubleNegation
    when :connect_timeout, :read_timeout, :write_timeout
      send(:"#{key}=", Integer(opts[key])) unless opts[key].nil?
    else
      send(:"#{key}=", opts[key])
    end
  end

  # force the encoding to utf8mb4
  self.charset_name = opts[:encoding] || 'utf8mb4'

  mode = parse_ssl_mode(opts[:ssl_mode]) if opts[:ssl_mode]
  mode = configure_tls_verification(opts, mode)

  ssl_options = opts.values_at(:sslkey, :sslcert, :sslca, :sslcapath, :sslcipher)
  ssl_set(*ssl_options) if ssl_options.any? || opts.key?(:sslverify)
  self.ssl_mode = mode if mode

  flags = case opts[:flags]
  when Array
    parse_flags_array(opts[:flags], @query_options[:connect_flags])
  when String
    parse_flags_array(opts[:flags].split(' '), @query_options[:connect_flags])
  when Integer
    @query_options[:connect_flags] | opts[:flags]
  else
    @query_options[:connect_flags]
  end

  # SSL verify is a connection flag rather than a mysql_ssl_set option
  flags |= SSL_VERIFY_SERVER_CERT if opts[:sslverify]

  check_and_clean_query_options

  user         = opts[:username] || opts[:user]
  pass         = opts[:password] || opts[:pass]
  host         = opts[:host] || opts[:hostname]
  port         = opts[:port]
  database     = opts[:database] || opts[:dbname] || opts[:db]
  socket       = opts[:socket] || opts[:sock]
  tls_sni_name = opts[:tls_sni_name]

  # Correct the data types before passing these values down to the C level
  user = user.to_s unless user.nil?
  pass = pass.to_s unless pass.nil?
  host = host.to_s unless host.nil?
  port = port.to_i unless port.nil?
  database = database.to_s unless database.nil?
  socket = socket.to_s unless socket.nil?
  tls_sni_name = tls_sni_name.to_s unless tls_sni_name.nil?
  conn_attrs = parse_connect_attrs(opts[:connect_attrs])

  connect user, pass, host, port, database, socket, flags, conn_attrs, tls_sni_name
end

Class Method Details

.default_query_options

[ GitHub ]

  
# File 'lib/mysql2/client.rb', line 5

def self.default_query_options
  @default_query_options ||= {
    as: :hash,                   # the type of object you want each row back as; also supports :array (an array of values)
    async: false,                # don't wait for a result after sending the query, you'll have to monitor the socket yourself then eventually call Mysql2::Client#async_result
    cast_booleans: false,        # cast tinyint(1) fields as true/false in ruby
    symbolize_keys: false,       # return field names as symbols instead of strings
    database_timezone: :local,   # timezone Mysql2 will assume datetime objects are stored in
    application_timezone: nil,   # timezone Mysql2 will convert to before handing the object back to the caller
    cache_rows: true,            # tells Mysql2 to use its internal row cache for results
    rows_per_gvl_yield: 8192,    # buffered rows to materialize between GVL yields; 0 disables yielding
    connect_flags: REMEMBER_OPTIONS | LONG_PASSWORD | LONG_FLAG | TRANSACTIONS | PROTOCOL_41 | SECURE_CONNECTION | CONNECT_ATTRS,
    cast: true,
    default_file: nil,
    default_group: nil,
  }
end

.escape(string)

Escape string so that it may be used in a SQL statement. Note that this escape method is not connection encoding aware. If you need encoding support use #escape instead.

[ GitHub ]

  
# File 'ext/mysql2/client.c', line 928

static VALUE rb_mysql_client_escape(RB_MYSQL_UNUSED VALUE klass, VALUE str) {
  unsigned char *newStr;
  VALUE rb_str;
  unsigned long newLen, oldLen;

  Check_Type(str, T_STRING);

  oldLen = RSTRING_LEN(str);
  newStr = xmalloc(oldLen*2+1);

  newLen = mysql_escape_string((char *)newStr, RSTRING_PTR(str), oldLen);
  if (newLen == oldLen) {
    /* no need to return a new ruby string if nothing changed */
    xfree(newStr);
    return str;
  } else {
    rb_str = rb_str_new((const char*)newStr, newLen);
    rb_enc_copy(rb_str, str);
    xfree(newStr);
    return rb_str;
  }
}

.info

Returns a string that represents the client library version.

[ GitHub ]

  
# File 'ext/mysql2/client.c', line 2014

static VALUE rb_mysql_client_info(RB_MYSQL_UNUSED VALUE klass) {
  VALUE version_info, version, header_version;
  version_info = rb_hash_new();

  version = rb_str_new2(mysql_get_client_info());
  header_version = rb_str_new2(MYSQL_LINK_VERSION);

  rb_enc_associate(version, rb_usascii_encoding());
  rb_enc_associate(header_version, rb_usascii_encoding());

  rb_hash_aset(version_info, sym_id, LONG2NUM(mysql_get_client_version()));
  rb_hash_aset(version_info, sym_version, version);
  rb_hash_aset(version_info, sym_header_version, header_version);

  return version_info;
}

.local_offset (private)

[ GitHub ]

  
# File 'lib/mysql2/client.rb', line 315

def local_offset
  ::Time.local(2010).utc_offset.to_r / 86400
end

Instance Attribute Details

#automatic_close=(false) (rw)

Set this to false to leave the connection open after it is garbage collected. To avoid "Aborted connection" errors on the server, explicitly call #close when the connection is no longer needed.

This protects a plaintext connection across fork(). A TLS connection's OpenSSL session state is a separate, per-process copy that fork() duplicates rather than shares, and desyncs between parent and child the moment either side performs a real query, regardless of this setting -- see the README for the full mechanism and the ssl_mode workaround.

[ GitHub ]

  
# File 'ext/mysql2/client.c', line 2525

static VALUE set_automatic_close(VALUE self, VALUE value) {
  GET_CLIENT(self);
  if (RTEST(value)) {
    wrapper->automatic_close = 1;
  } else {
#ifndef _WIN32
    wrapper->automatic_close = 0;
#else
    rb_warn("Connections are always closed by garbage collector on Windows");
#endif
  }
  return value;
}

#automatic_close?Boolean (rw)

[ GitHub ]

  
# File 'ext/mysql2/client.c', line 2505

static VALUE get_automatic_close(VALUE self) {
  GET_CLIENT(self);
  return wrapper->automatic_close ? Qtrue : Qfalse;
}

#charset_name=(value) (writeonly, private)

[ GitHub ]

  
# File 'ext/mysql2/client.c', line 2588

static VALUE set_charset_name(VALUE self, VALUE value) {
  char *charset_name;
  const struct mysql2_mysql_enc_name_to_rb_map *mysql2rb;
  rb_encoding *enc;
  VALUE rb_enc;
  GET_CLIENT(self);

  Check_Type(value, T_STRING);
  charset_name = RSTRING_PTR(value);

  mysql2rb = mysql2_mysql_enc_name_to_rb(charset_name, (unsigned int)RSTRING_LEN(value));
  if (mysql2rb == NULL || mysql2rb->rb_name == NULL) {
    VALUE inspect = rb_inspect(value);
    rb_raise(cMysql2Error, "Unsupported charset: '%s'", RSTRING_PTR(inspect));
  } else {
    enc = rb_enc_find(mysql2rb->rb_name);
    rb_enc = rb_enc_from_encoding(enc);
    wrapper->encoding = rb_enc;
  }

  if (mysql_options(wrapper->client, MYSQL_SET_CHARSET_NAME, charset_name)) {
    /* TODO: warning - unable to set charset */
    rb_warn("%s\n", mysql_error(wrapper->client));
  }

  return value;
}

#closed?Boolean (readonly)

[ GitHub ]

  
# File 'ext/mysql2/client.c', line 1307

static VALUE rb_mysql_client_closed(VALUE self) {
  GET_CLIENT(self);
  return CONNECTED(wrapper) ? Qfalse : Qtrue;
}

#connect_timeout=(value) (writeonly, private)

[ GitHub ]

  
# File 'ext/mysql2/client.c', line 2554

static VALUE set_connect_timeout(VALUE self, VALUE value) {
  long int sec;
  Check_Type(value, T_FIXNUM);
  sec = FIX2INT(value);
  if (sec < 0) {
    rb_raise(cMysql2Error, "connect_timeout must be a positive integer, you passed %ld", sec);
  }
  return _mysql_client_options(self, MYSQL_OPT_CONNECT_TIMEOUT, value);
}

#default_auth=(value) (writeonly, private)

[ GitHub ]

  
# File 'ext/mysql2/client.c', line 2677

static VALUE set_default_auth(VALUE self, VALUE value) {
#ifdef HAVE_CONST_MYSQL_DEFAULT_AUTH
  return _mysql_client_options(self, MYSQL_DEFAULT_AUTH, value);
#else
  rb_raise(cMysql2Error, "pluggable authentication is not available, you may need a newer MySQL client library");
#endif
}

#default_file=(value) (writeonly, private)

[ GitHub ]

  
# File 'ext/mysql2/client.c', line 2657

static VALUE set_read_default_file(VALUE self, VALUE value) {
  return _mysql_client_options(self, MYSQL_READ_DEFAULT_FILE, value);
}

#default_group=(value) (writeonly, private)

[ GitHub ]

  
# File 'ext/mysql2/client.c', line 2661

static VALUE set_read_default_group(VALUE self, VALUE value) {
  return _mysql_client_options(self, MYSQL_READ_DEFAULT_GROUP, value);
}

#enable_cleartext_plugin=(value) (writeonly, private)

[ GitHub ]

  
# File 'ext/mysql2/client.c', line 2693

static VALUE set_enable_cleartext_plugin(VALUE self, VALUE value) {
#ifdef HAVE_CONST_MYSQL_ENABLE_CLEARTEXT_PLUGIN
  return _mysql_client_options(self, MYSQL_ENABLE_CLEARTEXT_PLUGIN, value);
#else
  rb_raise(cMysql2Error, "enable-cleartext-plugin is not available, you may need a newer MySQL client library");
#endif
}

#get_server_public_key=(value) (writeonly, private)

[ GitHub ]

  
# File 'ext/mysql2/client.c', line 2669

static VALUE set_get_server_public_key(VALUE self, VALUE value) {
#ifdef HAVE_CONST_MYSQL_OPT_GET_SERVER_PUBLIC_KEY
  return _mysql_client_options(self, MYSQL_OPT_GET_SERVER_PUBLIC_KEY, value);
#else
  rb_raise(cMysql2Error, "get-server-public-key is not available, you may need a newer MySQL client library");
#endif
}

#init_command=(value) (writeonly, private)

[ GitHub ]

  
# File 'ext/mysql2/client.c', line 2665

static VALUE set_init_command(VALUE self, VALUE value) {
  return _mysql_client_options(self, MYSQL_INIT_COMMAND, value);
}

#local_infile=(value) (writeonly, private)

[ GitHub ]

  
# File 'ext/mysql2/client.c', line 2550

static VALUE set_local_infile(VALUE self, VALUE value) {
  return _mysql_client_options(self, MYSQL_OPT_LOCAL_INFILE, value);
}

#more_results?Boolean (readonly)

Returns true or false if there are more results to process.

[ GitHub ]

  
# File 'ext/mysql2/client.c', line 2298

static VALUE rb_mysql_client_more_results(VALUE self)
{
  GET_CLIENT(self);
  if (mysql_more_results(wrapper->client) == 0)
    return Qfalse;
  else
    return Qtrue;
}

#query_options (readonly)

[ GitHub ]

  
# File 'lib/mysql2/client.rb', line 3

attr_reader :query_options, :read_timeout

#read_timeout (rw)

[ GitHub ]

  
# File 'lib/mysql2/client.rb', line 3

attr_reader :query_options, :read_timeout

#read_timeout=(value) (rw, private)

[ GitHub ]

  
# File 'ext/mysql2/client.c', line 2564

static VALUE set_read_timeout(VALUE self, VALUE value) {
  long int sec;
  Check_Type(value, T_FIXNUM);
  sec = FIX2INT(value);
  if (sec < 0) {
    rb_raise(cMysql2Error, "read_timeout must be a positive integer, you passed %ld", sec);
  }
  /* Set the instance variable here even though _mysql_client_options
     might not succeed, because the timeout is used in other ways
     elsewhere */
  rb_ivar_set(self, intern_read_timeout, value);
  return _mysql_client_options(self, MYSQL_OPT_READ_TIMEOUT, value);
}

#reconnect=(true) (writeonly)

Enable or disable the automatic reconnect behavior of libmysql. Read http://dev.mysql.com/doc/refman/5.5/en/auto-reconnect.html for more information.

[ GitHub ]

  
# File 'ext/mysql2/client.c', line 2546

static VALUE set_reconnect(VALUE self, VALUE value) {
  return _mysql_client_options(self, MYSQL_OPT_RECONNECT, value);
}

#secure_auth=(value) (writeonly, private)

[ GitHub ]

  
# File 'ext/mysql2/client.c', line 2648

static VALUE set_secure_auth(VALUE self, VALUE value) {
/* This option was deprecated in MySQL 5.x and removed in MySQL 8.0 */
#ifdef MYSQL_SECURE_AUTH
  return _mysql_client_options(self, MYSQL_SECURE_AUTH, value);
#else
  return Qfalse;
#endif
}

#ssl_mode=(setting) (writeonly, private)

[ GitHub ]

  
# File 'ext/mysql2/client.c', line 323

static VALUE rb_set_ssl_mode_option(VALUE self, VALUE setting) {
  unsigned long version = mysql_get_client_version();
  const char *version_str = mysql_get_client_info();

  /* Warn about versions that are known to be incomplete; these are pretty
   * ancient, we want people to upgrade if they need SSL/TLS to work
   *
   * MySQL 5.x before 5.6.30 -- ssl_mode introduced but not fully working until 5.6.36)
   * MySQL 5.7 before 5.7.3 -- ssl_mode introduced but not fully working until 5.7.11)
   */
  if ((version >= 50000 && version < 50630) || (version >= 50700 && version < 50703)) {
    rb_warn("Your mysql client library version %s does not support setting ssl_mode; full support comes with 5.6.36+, 5.7.11+, 8.0+", version_str);
    return Qnil;
  }

  /* For these versions, map from the options we're exposing to Ruby to the constant available:
   *   ssl_mode: :verify_identity to MYSQL_OPT_SSL_VERIFY_SERVER_CERT = 1
   *   ssl_mode: :required to MYSQL_OPT_SSL_ENFORCE = 1
   *   ssl_mode: :disabled to MYSQL_OPT_SSL_ENFORCE = 0
   */
#if defined(HAVE_CONST_MYSQL_OPT_SSL_VERIFY_SERVER_CERT) || defined(HAVE_CONST_MYSQL_OPT_SSL_ENFORCE)
  GET_CLIENT(self);
  int val = NUM2INT(setting);

  /* Expected code path for MariaDB 10.x+ and MariaDB Connector/C 3.x+
   * Workaround code path for MySQL 5.7.3 - 5.7.10 and MySQL Connector/C 6.1.3 - 6.1.x
   *
   * The MySQL tiers verify the hostname natively under
   * MYSQL_OPT_SSL_VERIFY_SERVER_CERT. Everything else new enough to reach
   * this branch is treated as the MariaDB family -- open-ended rather than
   * a closed version window, so a future Connector/C major version lands on
   * the verify_identity enforcement (or its fail-closed refusal) below
   * instead of falling through to the unverified #879 path with no signal. */
  int mysql_native_verify = (version >= 50703 && version < 50711)   // MySQL 5.7.3 - 5.7.10
                         || (version >= 60103 && version < 60200);  // MySQL Connector/C 6.1.3 - 6.1.x
  int mariadb_family = !mysql_native_verify && version >= 30000;    // MariaDB 10.x+ servers (100000+) and Connector/C 3.x+

  if (mariadb_family || mysql_native_verify) {
#ifdef HAVE_CONST_MYSQL_OPT_SSL_VERIFY_SERVER_CERT
    /* On MariaDB Connector/C, MYSQL_OPT_SSL_VERIFY_SERVER_CERT is CA
     * verification at most: it clears the connector's skip-everything gate,
     * but which checks actually run is chosen by the connector's local-peer
     * heuristic, and hostname verification never runs against 127.0.0.1,
     * ::1, or socket peers (#879). verify_ca maps here as the CA-only mode
     * it is; verify_identity additionally registers mysql2's own
     * verification callback below to make the hostname check real. */
    if (val == SSL_MODE_VERIFY_IDENTITY || val == SSL_MODE_VERIFY_CA) {
      my_bool b = 1;
      int result = mysql_options(wrapper->client, MYSQL_OPT_SSL_VERIFY_SERVER_CERT, &b);
      /* The MySQL native-verify tiers share this branch but verify the
       * hostname themselves under this option, so the shim (and the refusal
       * when it isn't available) is MariaDB-only. */
      if (val == SSL_MODE_VERIFY_IDENTITY && mariadb_family) {
#ifdef MYSQL2_VERIFY_IDENTITY_SHIM
        /* Both registrations are checked: a runtime client library that
         * rejects either option (say, an older libmariadb.so than the
         * headers this gem was built against) cannot enforce verification
         * inside the handshake, and must refuse here -- before any connect
         * -- rather than silently demote enforcement to the post-connect
         * tripwire, which refuses only after credentials have already
         * crossed the wire to the unverified peer. */
#ifdef HAVE_CONST_MYSQL_OPT_SSL_ENFORCE
        /* verify_identity implies a required TLS connection, as it does on
         * MySQL -- a connection that never negotiates TLS must not bypass
         * verification. */
        if (mysql_options(wrapper->client, MYSQL_OPT_SSL_ENFORCE, &b) != 0)
          rb_raise(cMysql2ConnectionError,
                   "ssl_mode: :verify_identity cannot be enforced: the runtime client library "
                   "(%s) rejected MYSQL_OPT_SSL_ENFORCE, so an unverified non-TLS connection "
                   "could not be ruled out. Refusing to connect (#879).", version_str);
#endif
        if (mysql_options(wrapper->client, MARIADB_OPT_TLS_VERIFICATION_CALLBACK,
                          (const void *)mysql2_tls_verification_callback) != 0)
          rb_raise(cMysql2ConnectionError,
                   "ssl_mode: :verify_identity cannot be enforced: the runtime client library "
                   "(%s) rejected the TLS verification callback, which needs MariaDB "
                   "Connector/C 3.4+ at runtime as well as at gem build time. Refusing to "
                   "connect with the hostname check silently skipped (#879).", version_str);
        wrapper->tls_verify_identity = 1;
#else
        /* Connecting anyway with the hostname check silently skipped is
         * exactly the failure #879 describes -- refuse instead. */
        rb_raise(cMysql2ConnectionError,
                 "ssl_mode: :verify_identity cannot be enforced by this mysql2 build "
                 "(client library %s): hostname verification needs MariaDB Connector/C 3.4+ "
                 "and OpenSSL headers at gem build time. Refusing to connect with the check "
                 "silently skipped (#879). Use ssl_mode: :verify_ca, pin the server "
                 "certificate with :tls_peer_fingerprint, or rebuild mysql2 against a newer "
                 "client library.", version_str);
#endif
      }
      return INT2NUM(result);
    }
#endif
#ifdef HAVE_CONST_MYSQL_OPT_SSL_ENFORCE
    if (val == SSL_MODE_DISABLED || val == SSL_MODE_REQUIRED) {
      my_bool b = (val == SSL_MODE_REQUIRED);
      int result = mysql_options(wrapper->client, MYSQL_OPT_SSL_ENFORCE, &b);
      return INT2NUM(result);
    }
#endif
    rb_warn("Your mysql client library version %s does not support ssl_mode %d", version_str, val);
    return Qnil;
  } else {
    rb_warn("Your mysql client library version %s does not support ssl_mode as expected", version_str);
    return Qnil;
  }
#endif

  /* For other versions -- known to be MySQL 5.6.36+, 5.7.11+, 8.0+
   * pass the value of the argument to MYSQL_OPT_SSL_MODE -- note the code
   * mapping from atoms / constants is in the MySQL::Client Ruby class
   */
#ifdef FULL_SSL_MODE_SUPPORT
  GET_CLIENT(self);
  int val = NUM2INT(setting);

  if (val != SSL_MODE_DISABLED && val != SSL_MODE_PREFERRED && val != SSL_MODE_REQUIRED && val != SSL_MODE_VERIFY_CA && val != SSL_MODE_VERIFY_IDENTITY) {
    rb_raise(cMysql2Error, "ssl_mode= takes DISABLED, PREFERRED, REQUIRED, VERIFY_CA, VERIFY_IDENTITY, you passed: %d", val );
  }
  int result = mysql_options(wrapper->client, MYSQL_OPT_SSL_MODE, &val);

  return INT2NUM(result);
#endif

  // Warn if we get this far
#ifdef NO_SSL_MODE_SUPPORT
  rb_warn("Your mysql client library does not support setting ssl_mode");
  return Qnil;
#endif
}

#tls_passphrase=(passphrase) (writeonly, private)

:tls_passphrase -- decrypts an encrypted :sslkey file (MariaDB Connector/C only; no MYSQL_OPT_* equivalent). Raises on unsupported builds rather than silently connecting with an unusable key: without the passphrase the encrypted key can't be loaded, and the caller would otherwise see only a low-level, unhelpful decryption error from the TLS handshake instead of a clear reason.

[ GitHub ]

  
# File 'ext/mysql2/client.c', line 1111

static VALUE rb_set_tls_passphrase(VALUE self, VALUE passphrase) {
#ifdef HAVE_CONST_MARIADB_OPT_TLS_PASSPHRASE
  GET_CLIENT(self);
  mysql_options(wrapper->client, MARIADB_OPT_TLS_PASSPHRASE, StringValueCStr(passphrase));
  return passphrase;
#else
  (void)self; (void)passphrase;
  rb_raise(cMysql2ConnectionError,
           ":tls_passphrase requires MariaDB Connector/C; this client library (%s) has no way "
           "to supply a passphrase for an encrypted :sslkey file", mysql_get_client_info());
#endif
}

#tls_peer_fingerprint=(fingerprint) (writeonly, private)

:tls_peer_fingerprint / :tls_peer_fingerprint_list -- CA-less pinned TLS on MariaDB Connector/C 3.4+ (MARIADB_OPT_TLS_PEER_FP/_LIST). A pin the client library cannot apply is a security control silently not applied -- the same failure class as #879 -- so unsupported builds raise instead of warning and connecting unpinned.

[ GitHub ]

  
# File 'ext/mysql2/client.c', line 1077

static VALUE rb_set_tls_peer_fingerprint(VALUE self, VALUE fingerprint) {
#ifdef HAVE_CONST_MARIADB_OPT_TLS_PEER_FP
  GET_CLIENT(self);
  mysql_options(wrapper->client, MARIADB_OPT_TLS_PEER_FP, StringValueCStr(fingerprint));
  return fingerprint;
#else
  (void)self; (void)fingerprint;
  rb_raise(cMysql2ConnectionError,
           ":tls_peer_fingerprint requires MariaDB Connector/C 3.4+; this client library "
           "(%s) cannot pin the server certificate, and connecting unpinned would silently "
           "drop the verification you asked for", mysql_get_client_info());
#endif
}

#tls_peer_fingerprint_list=(path) (writeonly, private)

[ GitHub ]

  
# File 'ext/mysql2/client.c', line 1091

static VALUE rb_set_tls_peer_fingerprint_list(VALUE self, VALUE path) {
#ifdef HAVE_CONST_MARIADB_OPT_TLS_PEER_FP_LIST
  GET_CLIENT(self);
  mysql_options(wrapper->client, MARIADB_OPT_TLS_PEER_FP_LIST, StringValueCStr(path));
  return path;
#else
  (void)self; (void)path;
  rb_raise(cMysql2ConnectionError,
           ":tls_peer_fingerprint_list requires MariaDB Connector/C 3.4+; this client library "
           "(%s) cannot pin the server certificate, and connecting unpinned would silently "
           "drop the verification you asked for", mysql_get_client_info());
#endif
}

#tls_version=(value) (writeonly, private)

[ GitHub ]

  
# File 'ext/mysql2/client.c', line 2685

static VALUE set_tls_version(VALUE self, VALUE value) {
#ifdef HAVE_CONST_MYSQL_OPT_TLS_VERSION
  return _mysql_client_options(self, MYSQL_OPT_TLS_VERSION, value);
#else
  rb_raise(cMysql2Error, "tls_version is not available, you may need a newer MySQL or MariaDB client library");
#endif
}

#write_timeout=(value) (writeonly, private)

[ GitHub ]

  
# File 'ext/mysql2/client.c', line 2578

static VALUE set_write_timeout(VALUE self, VALUE value) {
  long int sec;
  Check_Type(value, T_FIXNUM);
  sec = FIX2INT(value);
  if (sec < 0) {
    rb_raise(cMysql2Error, "write_timeout must be a positive integer, you passed %ld", sec);
  }
  return _mysql_client_options(self, MYSQL_OPT_WRITE_TIMEOUT, value);
}

Instance Method Details

#query(sql, options = {}) (private)

Query the database with sql, with optional options. For the possible options, see default_query_options on the Client class.

[ GitHub ]

  
# File 'ext/mysql2/client.c', line 1748

static VALUE rb_mysql_query(VALUE self, VALUE sql, VALUE current) {
#ifndef _WIN32
  struct async_query_args async_args;
#endif
  struct nogvl_send_query_args args;
  GET_CLIENT(self);

  REQUIRE_CONNECTED(wrapper);

  if (mysql2_forked_without_reconnect(wrapper) && wrapper->automatic_close) {
    mysql2_warn_forked_without_reconnect(wrapper, "send a query");
  }

  args.mysql = wrapper->client;

  (void)RB_GC_GUARD(current);
  Check_Type(current, T_HASH);
  /* Resolve :force_encoding to an Encoding object up front: invalid values
   * raise here, before anything is written to the wire, leaving the
   * connection untouched and reusable. Nothing downstream of the send may
   * raise for this option -- rb_mysql_result_to_obj in particular. */
  mysql2_canonicalize_force_encoding(current);
  /* Text-protocol streaming is mysql_use_result: the server pushes rows and
   * the client pulls them off the socket, so there is no prefetch to size.
   * The stream: {size: N} spelling is prepared-statement-only
   * (Statement#execute drives a server-side cursor, which does prefetch);
   * reject it here rather than silently stream one row at a time. */
  if (RB_TYPE_P(rb_hash_aref(current, sym_stream), T_HASH)) {
    rb_raise(rb_eArgError, "stream: {size: N} is only supported for prepared statements; Client#query streams with mysql_use_result, which has no prefetch");
  }
  rb_ivar_set(self, intern_current_query_options, current);

  Check_Type(sql, T_STRING);
  /* ensure the string is in the encoding the connection is expecting */
  args.sql = rb_str_export_to_enc(sql, rb_to_encoding(wrapper->encoding));
  args.sql_ptr = RSTRING_PTR(args.sql);
  args.sql_len = RSTRING_LEN(args.sql);
  args.completion.wrapper = wrapper;
  args.completion.completed = 0;

  rb_mysql_client_set_active_fiber(self, false);

  /* We're about to issue a new command: this is a safe point to close out
   * any statements that were GC'd while we were busy earlier, and to free
   * any abandoned result sets left over from a stream that was dropped
   * mid-iteration -- including one that hasn't been collected by GC yet,
   * which the reap below alone wouldn't catch (mysql2_abandon_active_stream).
   * Deliberately last, right before the actual network write below --
   * rb_ivar_set/rb_str_export_to_enc above can themselves allocate, and
   * under GC.stress (or just unlucky timing) that can be what triggers the
   * GC sweep that abandons a stream, so reaping any earlier can still leave
   * a fresh pending free undrained when we send. Must also run before the
   * state assignment below: mysql2_abandon_active_stream only acts while
   * state is still STREAMING. */
  mysql2_abandon_active_stream(wrapper);
  mysql2_reap_pending_result_frees(wrapper);
  mysql2_reap_pending_stmt_closes(wrapper);

  wrapper->state = MYSQL2_CLIENT_QUERYING;

  /* Open the round-trip bracket that async_result closes once the first
   * response has been fully read -- including the socket wait in do_query,
   * which is where most of a slow query's time goes. */
  wrapper->query_start = mysql2_monotonic_now();

#ifndef _WIN32
  rb_ensure(do_send_query, (VALUE)&args, disconnect_query_if_incomplete, (VALUE)&args.completion);
  (void)RB_GC_GUARD(sql);

  if (rb_hash_aref(current, sym_async) == Qtrue) {
    return Qnil;
  } else {
    async_args.fd = wrapper->client->net.fd;
    async_args.self = self;
    async_args.completion.wrapper = wrapper;
    async_args.completion.completed = 0;

    rb_ensure(do_query, (VALUE)&async_args, disconnect_query_if_incomplete, (VALUE)&async_args.completion);

    return rb_ensure(rb_mysql_client_async_result, self, disconnect_and_mark_inactive, self);
  }
#else
  do_send_query((VALUE)&args);
  (void)RB_GC_GUARD(sql);

  /* this will just block until the result is ready */
  return rb_ensure(rb_mysql_client_async_result, self, disconnect_and_mark_inactive, self);
#endif
}

#abandon_results!

[ GitHub ]

  
# File 'ext/mysql2/client.c', line 1731

static VALUE rb_mysql_client_abandon_results(VALUE self) {
  struct query_completion completion;
  GET_CLIENT(self);

  completion.wrapper = wrapper;
  completion.completed = 0;

  rb_mysql_client_set_active_fiber(self, false);
  return rb_ensure(do_abandon_results, (VALUE)&completion, release_claim_or_disconnect, (VALUE)&completion);
}

#affected_rows

returns the number of rows changed, deleted, or inserted by the last statement if it was an UPDATE, DELETE, or INSERT.

[ GitHub ]

  
# File 'ext/mysql2/client.c', line 2122

static VALUE rb_mysql_client_affected_rows(VALUE self) {
  uint64_t retVal;
  GET_CLIENT(self);

  REQUIRE_CONNECTED(wrapper);
  retVal = wrapper->affected_rows;
  if (retVal == (my_ulonglong)-1) {
    rb_raise_mysql2_error(wrapper);
  }
  return ULL2NUM(retVal);
}

#apply_tls_option_aliases(opts) (private)

[ GitHub ]

  
# File 'lib/mysql2/client.rb', line 261

def apply_tls_option_aliases(opts)
  TLS_OPTION_ALIASES.each { |tls_key, legacy_key| opts[legacy_key] = opts[tls_key] if opts.key?(tls_key) }
end

#async_result

Returns the result for the last async issued query.

[ GitHub ]

  
# File 'ext/mysql2/client.c', line 1517

static VALUE rb_mysql_client_async_result(VALUE self) {
  struct nogvl_read_query_result_args read_args;
  double query_elapsed;
  GET_CLIENT(self);

  /* if we're not waiting on a result, do nothing */
  if (NIL_P(wrapper->active_fiber))
    return Qnil;

  REQUIRE_CONNECTED(wrapper);
  read_args.mysql = wrapper->client;
  if ((VALUE)rb_thread_call_without_gvl(nogvl_read_query_result, &read_args, RUBY_UBF_IO, 0) == Qfalse) {
    /* an error occurred, mark this connection inactive */
    wrapper->active_fiber = Qnil;
    wrapper->state = MYSQL2_CLIENT_IDLE;
    rb_raise_mysql2_error(wrapper);
  }

  /* The first response is now fully read: the stamp nogvl_read_query_result
   * took closes the round-trip bracket opened when the command was written
   * (rb_mysql_query). The store phase in mysql2_fetch_result_set stays
   * outside it -- Result#query_time measures the server, not local row
   * buffering. A negative stamp at either end means the clock call itself
   * failed; pass the sentinel through so the reading is nil, not garbage. */
  query_elapsed = (wrapper->query_start < 0 || read_args.query_end < 0)
    ? -1 : read_args.query_end - wrapper->query_start;

  return mysql2_fetch_result_set(self, wrapper, query_elapsed);
}

#check_and_clean_query_options (private)

[ GitHub ]

  
# File 'lib/mysql2/client.rb', line 297

def check_and_clean_query_options
  warn_incoherent_options

  if %i[user pass hostname dbname db sock].any? { |k| @query_options.key?(k) }
    warn "============= WARNING FROM mysql2 ============="
    warn "The options :user, :pass, :hostname, :dbname, :db, and :sock are deprecated and will be removed at some point in the future."
    warn "Instead, please use :username, :password, :host, :port, :database, :socket, :flags for the options."
    warn "============= END WARNING FROM mysql2 ========="
  end

  # avoid logging sensitive data via #inspect
  @query_options.delete(:password)
  @query_options.delete(:pass)
end

#closenil

Immediately disconnect from the server; normally the garbage collector will disconnect automatically when a connection is no longer needed. Explicitly closing this will free up server resources sooner than waiting for the garbage collector.

[ GitHub ]

  
# File 'ext/mysql2/client.c', line 1269

static VALUE rb_mysql_client_close(VALUE self) {
  GET_CLIENT(self);
  rb_mysql_client_set_active_fiber(self, true);

  /* The connection is going away regardless of what mysql_close() below
   * does or doesn't send -- don't bother closing each queued statement
   * individually, just clear the bookkeeping so prepared_statements and
   * pending_prepared_statement_closes don't report stale state afterward.
   *
   * Not-yet-drained result sets are different: unlike a statement's server-
   * side handle, mysql_close() below has no side effect that reclaims a
   * MYSQL_RES's client-side row buffers, so dropping those without freeing
   * them would leak that memory for the rest of the process. This runs as
   * ordinary Ruby-level code (this method isn't reachable from a dfree
   * callback), so it's safe to actually flush and free them here, same as
   * at any other safe point -- just before the connection goes away
   * instead of before the next command. mysql2_abandon_active_stream covers
   * a stream that's abandoned but still live (not yet collected by GC),
   * which the reap alone wouldn't catch -- same reasoning as at every other
   * safe point, this one was just missing it. */
  mysql2_abandon_active_stream(wrapper);
  mysql2_reap_pending_result_frees(wrapper);
  mysql2_drop_pending_stmt_closes(wrapper);

  if (wrapper->client) {
    rb_thread_call_without_gvl(nogvl_close, wrapper, RUBY_UBF_IO, 0);
  }

  wrapper->active_fiber = Qnil;

  return Qnil;
}

#configure_tls_verification(opts, mode)

Enforce the coherence of the TLS verification options before any of them reach the client library, so a verification the caller asked for can never be silently skipped (the #879 failure mode). Also maps the legacy :sslverify boolean onto :ssl_mode, and returns the effective mode -- possibly filled in from :sslverify -- for the caller to apply.

:sslca/:sslcapath left unset means the TLS backend resolves its default trust store natively (OpenSSL's default verify paths, SSL_CERT_FILE/SSL_CERT_DIR, or the platform certificate store). Whether verification actually succeeded is proven at connect time -- the verification callback and the post-connect tripwire fail closed on any connection whose chain or hostname cannot be shown verified.

[ GitHub ]

  
# File 'lib/mysql2/client.rb', line 162

def configure_tls_verification(opts, mode)
  # The mode != 0 guard keeps ancient no-ssl_mode builds (where every
  # SSL_MODE_* constant collapses to 0) out of the verify-tier handling.
  verify_mode = (mode == SSL_MODE_VERIFY_CA || mode == SSL_MODE_VERIFY_IDENTITY) && mode != 0

  if opts.key?(:sslverify)
    if opts[:sslverify]
      # :sslverify => true is the legacy spelling of ssl_mode: :verify_identity
      # -- MYSQL_OPT_SSL_MODE's own VERIFY_IDENTITY handler sets the same
      # CLIENT_SSL_VERIFY_SERVER_CERT connect-flag :sslverify sets directly
      # (still below, unconditionally). An explicit :ssl_mode always wins;
      # this only fills in a mode the caller didn't otherwise ask for, and
      # only where SSL_MODE_VERIFY_IDENTITY is a real, enforceable value.
      if (mode.nil? || mode.zero?) && !SSL_MODE_VERIFY_IDENTITY.zero?
        mode = SSL_MODE_VERIFY_IDENTITY
        verify_mode = true
      end
    elsif verify_mode
      # :sslverify => false and a verifying ssl_mode contradict each
      # other: one says the connection doesn't need to be verified, the
      # other asks mysql2 to refuse it unless it is. Refuse the ambiguity
      # instead of silently picking a side.
      raise Mysql2::Error::ConnectionError, "sslverify: false conflicts with ssl_mode: #{opts[:ssl_mode]}"
    end
  end

  # Unlocks an encrypted :sslkey file -- unrelated to the verification
  # models below, so set unconditionally rather than gated on them.
  self.tls_passphrase = opts[:tls_passphrase].to_s if opts[:tls_passphrase]

  return mode unless opts[:tls_peer_fingerprint] || opts[:tls_peer_fingerprint_list]

  # Fingerprint pinning and CA/hostname verification are alternative
  # trust models in MariaDB Connector/C: a pinned connection runs the
  # FINGERPRINT check instead of the HOST/TRUST checks, so combining
  # them would silently drop whichever one loses. Refuse the ambiguity.
  raise Mysql2::Error::ConnectionError, ":tls_peer_fingerprint pinning and ssl_mode: #{opts[:ssl_mode]} are mutually exclusive verification models; pick one" \
    if verify_mode

  self.tls_peer_fingerprint = opts[:tls_peer_fingerprint].to_s if opts[:tls_peer_fingerprint]
  self.tls_peer_fingerprint_list = opts[:tls_peer_fingerprint_list].to_s if opts[:tls_peer_fingerprint_list]

  mode
end

#connect(user, pass, host, port, database, socket, flags, conn_attrs, tls_sni_name) (private)

[ GitHub ]

  
# File 'ext/mysql2/client.c', line 1137

static VALUE rb_mysql_connect(VALUE self, VALUE user, VALUE pass, VALUE host, VALUE port, VALUE database, VALUE socket, VALUE flags, VALUE conn_attrs, VALUE tls_sni_name) {
  struct nogvl_connect_args args;
  time_t start_time, end_time, elapsed_time, connect_timeout;
  const char *sni_hostname;
  VALUE rv;
  GET_CLIENT(self);

  args.host        = NIL_P(host)     ? NULL : StringValueCStr(host);
  args.unix_socket = NIL_P(socket)   ? NULL : StringValueCStr(socket);
  args.port        = NIL_P(port)     ? 0    : NUM2INT(port);
  args.user        = NIL_P(user)     ? NULL : StringValueCStr(user);
  args.passwd      = NIL_P(pass)     ? NULL : StringValueCStr(pass);
  args.db          = NIL_P(database) ? NULL : StringValueCStr(database);
  args.mysql       = wrapper->client;
  args.client_flag = NUM2ULONG(flags);

  sni_hostname     = NIL_P(tls_sni_name) ? NULL : StringValueCStr(tls_sni_name);

#ifdef CLIENT_CONNECT_ATTRS
  mysql_options(wrapper->client, MYSQL_OPT_CONNECT_ATTR_RESET, 0);
  rb_hash_foreach(conn_attrs, opt_connect_attr_add_i, (VALUE)wrapper);
#endif

  if (sni_hostname != NULL) {
#ifdef HAVE_CONST_MYSQL_OPT_TLS_SNI_SERVERNAME
    mysql_options(wrapper->client, MYSQL_OPT_TLS_SNI_SERVERNAME, sni_hostname);
#else
    rb_raise(cMysql2Error, "tls_sni_name is not available, you may need a newer MySQL client library (added in MySQL 8.1; not supported on MariaDB)");
#endif
  }

  if (wrapper->connect_timeout)
    time(&start_time);
  rv = (VALUE) rb_thread_call_without_gvl(nogvl_connect, &args, RUBY_UBF_IO, 0);
  if (rv == Qfalse) {
    while (rv == Qfalse && errno == EINTR) {
      if (wrapper->connect_timeout) {
        time(&end_time);
        /* avoid long connect timeout from system time changes */
        if (end_time < start_time)
          start_time = end_time;
        elapsed_time = end_time - start_time;
        /* avoid an early timeout due to time truncating milliseconds off the start time */
        if (elapsed_time > 0)
          elapsed_time--;
        if (elapsed_time >= (time_t)wrapper->connect_timeout)
          break;
        connect_timeout = wrapper->connect_timeout - elapsed_time;
        mysql_options(wrapper->client, MYSQL_OPT_CONNECT_TIMEOUT, &connect_timeout);
      }
      errno = 0;
      rv = (VALUE) rb_thread_call_without_gvl(nogvl_connect, &args, RUBY_UBF_IO, 0);
    }
    /* restore the connect timeout for reconnecting */
    if (wrapper->connect_timeout)
      mysql_options(wrapper->client, MYSQL_OPT_CONNECT_TIMEOUT, &wrapper->connect_timeout);
    if (rv == Qfalse)
      rb_raise_mysql2_error(wrapper);
  }

  /* These originate as Ruby VALUEs, but we're using their C pointers
   * directly -- keep the VALUEs live on the stack so GC can't collect them
   * while we drop the GVL to make a MySQL API call. */
  (void)RB_GC_GUARD(host);
  (void)RB_GC_GUARD(socket);
  (void)RB_GC_GUARD(user);
  (void)RB_GC_GUARD(pass);
  (void)RB_GC_GUARD(database);
  (void)RB_GC_GUARD(tls_sni_name);

  wrapper->closed = 0;
#ifndef _WIN32
  /* Recorded so a later GC in a forked child that inherited this Client
   * without reconnecting can tell it isn't the process that owns this
   * socket -- see decr_mysql2_client. */
  wrapper->connect_pid = getpid();
#endif
  wrapper->server_version = mysql_get_server_version(wrapper->client);

#ifdef MYSQL2_VERIFY_IDENTITY_SHIM
  /* Tripwire for the #879 bug class: verify_identity enforcement was
   * promised via the verification callback, so prove it ran against a live
   * TLS session before handing the connection back -- a connect that
   * somehow completed without TLS, or without invoking the callback, must
   * be refused rather than silently degraded to an unverified one. The
   * re-check is cheap (the session is already established) and fails
   * closed on every path the callback cannot see. */
  if (wrapper->tls_verify_identity) {
    MARIADB_PVIO *pvio = wrapper->client->net.pvio;
    mysql2_mariadb_tls_head *ctls = pvio ? (mysql2_mariadb_tls_head *)pvio->ctls : NULL;
    unsigned int status;
    char detail[MYSQL_ERRMSG_SIZE];
    const char *failure;

    if (ctls == NULL) {
      failure = "connection did not negotiate TLS";
    } else if (pvio->mysql != wrapper->client || ctls->pvio != pvio) {
      failure = "TLS session unavailable for verification";
    } else {
      failure = mysql2_tls_check_peer_identity(wrapper->client, ctls, &status, detail, sizeof(detail));
    }

    if (failure != NULL) {
      VALUE error, error_msg;
      mysql2_tls_set_error(wrapper->client, failure);
      error_msg = rb_str_new2(mysql_error(wrapper->client));
      rb_enc_associate(error_msg, rb_utf8_encoding());
      error = rb_funcall(cMysql2Error, intern_new_with_args, 4,
                         error_msg,
                         LONG2FIX(wrapper->server_version),
                         UINT2NUM(mysql_errno(wrapper->client)),
                         rb_usascii_str_new_cstr("HY000"));
      /* Close before raising: an identity-unverified connection must not
       * outlive this method, even unreachable and pending GC. */
      rb_mysql_client_close(self);
      rb_exc_raise(error);
    }
    wrapper->tls_identity_verified = 1;
  }
#endif

  return self;
}

#database

Returns the currently selected database.

The result may be stale if session_track_schema is disabled. Read https://dev.mysql.com/doc/refman/5.7/en/session-state-tracking.html for more information.

[ GitHub ]

  
# File 'ext/mysql2/client.c', line 2484

static VALUE rb_mysql_client_database(VALUE self) {
  GET_CLIENT(self);

  char *db = wrapper->client->db;
  // NULL when no database is selected against MariaDB servers < 12.3 (and
  // any MySQL server); an empty string against MariaDB servers >= 12.3,
  // confirmed by pairing MariaDB 11.8/12.3 client libraries against both
  // server versions independently -- the client library version made no
  // difference, only the server's did. Treat both as "no database".
  if (!db || db[0] == '\0') {
    return Qnil;
  }

  return rb_str_new_cstr(db);
}

#discard!nil

Abandon the connection without disconnecting the underlying server session: drop this process's reference to the socket and free client-side resources, but never send a QUIT or shut the socket down. Use this in place of #close to let go of a connection shared with another process across a fork -- the other process's session, including its prepared statements, stays intact.

Afterward the client behaves like a closed one: #closed? returns true and further commands raise Error. Discarding an already-closed or already-discarded client is a no-op, as is #close after discard!.

If nothing else shares the socket, discarding abandons the server session until it times out on its own (wait_timeout); use #close for connections this process owns.

[ GitHub ]

  
# File 'ext/mysql2/client.c', line 1332

static VALUE rb_mysql_client_discard(VALUE self) {
  GET_CLIENT(self);

  if (wrapper->initialized && !wrapper->closed) {
#ifndef _WIN32
    invalidate_socket(wrapper);
#else
    /* No fd redirection on Windows (see invalidate_fd); processes don't
     * share sockets across fork() here anyway. Close the socket outright
     * without a QUIT, same as disconnect_and_mark_inactive. */
    if (CONNECTED(wrapper)) {
      close(wrapper->client->net.fd);
      wrapper->client->net.fd = -1;
    }
#endif

    /* Same teardown sequence as Client#close, but aimed at the now-dead
     * fd: the force-free's drain of an abandoned stream reads instant EOF
     * instead of stealing bytes from the shared socket, and the QUIT (and
     * TLS shutdown) that mysql_close() writes is absorbed harmlessly. The
     * deferred result frees are dropped as bookkeeping only -- the reap
     * skips the real mysql_free_result() calls once the wrapper is no
     * longer CONNECTED, leaking those client-side copies, the same
     * tradeoff decr_mysql2_client accepts. */
    mysql2_abandon_active_stream(wrapper);
    mysql2_reap_pending_result_frees(wrapper);
    mysql2_drop_pending_stmt_closes(wrapper);
    rb_thread_call_without_gvl(nogvl_close, wrapper, RUBY_UBF_IO, 0);
  }

  return Qnil;
}

#encoding

Returns the encoding set on the client.

[ GitHub ]

  
# File 'ext/mysql2/client.c', line 2470

static VALUE rb_mysql_client_encoding(VALUE self) {
  GET_CLIENT(self);
  return wrapper->encoding;
}

#escape(string)

Escape string so that it may be used in a SQL statement.

[ GitHub ]

  
# File 'ext/mysql2/client.c', line 1843

static VALUE rb_mysql_client_real_escape(VALUE self, VALUE str) {
  unsigned char *newStr;
  VALUE rb_str;
  unsigned long newLen, oldLen;
  rb_encoding *default_internal_enc;
  rb_encoding *conn_enc;
  GET_CLIENT(self);

  REQUIRE_CONNECTED(wrapper);
  Check_Type(str, T_STRING);
  default_internal_enc = rb_default_internal_encoding();
  conn_enc = rb_to_encoding(wrapper->encoding);
  /* ensure the string is in the encoding the connection is expecting */
  str = rb_str_export_to_enc(str, conn_enc);

  oldLen = RSTRING_LEN(str);
  newStr = xmalloc(oldLen*2+1);

#ifdef HAVE_MYSQL_REAL_ESCAPE_STRING_QUOTE
  newLen = mysql_real_escape_string_quote(wrapper->client, (char *)newStr, RSTRING_PTR(str), oldLen, '\'');
#else
  newLen = mysql_real_escape_string(wrapper->client, (char *)newStr, RSTRING_PTR(str), oldLen);
#endif
  if (newLen == (unsigned long)-1) {
    xfree(newStr);
    rb_raise_mysql2_error(wrapper);
  }
  if (newLen == oldLen) {
    /* no need to return a new ruby string if nothing changed */
    if (default_internal_enc) {
      str = rb_str_export_to_enc(str, default_internal_enc);
    }
    xfree(newStr);
    return str;
  } else {
    rb_str = rb_str_new((const char*)newStr, newLen);
    /* mysql_real_escape_string() only backslash-escapes a handful of
     * syntax-breaking bytes; it doesn't transcode or validate the rest.
     * Tag the result with str's own encoding (already normalized to
     * conn_enc above for anything transcodable, left as-is for binary),
     * not unconditionally conn_enc -- otherwise binary input that happens
     * to contain an escapable byte comes back mislabeled, while identical
     * binary input that doesn't need escaping is correctly left alone. */
    rb_enc_associate(rb_str, rb_enc_get(str));
    if (default_internal_enc) {
      rb_str = rb_str_export_to_enc(rb_str, default_internal_enc);
    }
    xfree(newStr);
    return rb_str;
  }
}

#find_default_ca_path

Find any default system CA paths to handle system roots by default if stricter validation is requested and no path is provide.

[ GitHub ]

  
# File 'lib/mysql2/client.rb', line 210

def find_default_ca_path
  [
    "/etc/ssl/certs/ca-certificates.crt",
    "/etc/pki/tls/certs/ca-bundle.crt",
    "/etc/ssl/ca-bundle.pem",
    "/etc/ssl/cert.pem",
  ].find { |f| File.exist?(f) }
end

#info

[ GitHub ]

  
# File 'lib/mysql2/client.rb', line 255

def info
  self.class.info
end

#initialize_ext (private)

[ GitHub ]

  
# File 'ext/mysql2/client.c', line 2701

static VALUE initialize_ext(VALUE self) {
  GET_CLIENT(self);

  if ((VALUE)rb_thread_call_without_gvl(nogvl_init, wrapper, RUBY_UBF_IO, 0) == Qfalse) {
    /* TODO: warning - not enough memory? */
    rb_raise_mysql2_error(wrapper);
  }

  wrapper->initialized = 1;
  return self;
}

#last_id

Returns the value generated for an AUTO_INCREMENT column by the previous INSERT or UPDATE statement.

[ GitHub ]

  
# File 'ext/mysql2/client.c', line 2080

static VALUE rb_mysql_client_last_id(VALUE self) {
  GET_CLIENT(self);
  REQUIRE_CONNECTED(wrapper);
  return ULL2NUM(mysql_insert_id(wrapper->client));
}

#next_result

Fetch the next result set from the server. Returns true or false if there was another result in the multi-statement set.

[ GitHub ]

  
# File 'ext/mysql2/client.c', line 2420

static VALUE rb_mysql_client_next_result(VALUE self)
{
  struct next_result_args args;

  GET_CLIENT(self);
  REQUIRE_CONNECTED(wrapper);

  args.self = self;
  args.completion.wrapper = wrapper;
  args.completion.completed = 0;

  rb_mysql_client_set_active_fiber(self, false);
  return rb_ensure(mysql2_next_result_body, (VALUE)&args, mysql2_next_result_reset_state, (VALUE)&args.completion);
}

#parse_connect_attrs(conn_attrs)

Set default program_name in performance_schema.session_connect_attrs and performance_schema.session_account_connect_attrs

[ GitHub ]

  
# File 'lib/mysql2/client.rb', line 221

def parse_connect_attrs(conn_attrs)
  return {} if Mysql2::Client::CONNECT_ATTRS.zero?

  conn_attrs ||= {}
  conn_attrs[:program_name] ||= $PROGRAM_NAME
  conn_attrs.each_with_object({}) do |(key, value), hash|
    hash[key.to_s] = value.to_s
  end
end

#parse_flags_array(flags, initial = 0)

[ GitHub ]

  
# File 'lib/mysql2/client.rb', line 136

def parse_flags_array(flags, initial = 0)
  flags.reduce(initial) do |memo, f|
    fneg = f.start_with?('-') ? f[1..-1] : nil
    if fneg && fneg =~ /^\w+$/ && Mysql2::Client.const_defined?(fneg)
      memo & ~ Mysql2::Client.const_get(fneg)
    elsif f && f =~ /^\w+$/ && Mysql2::Client.const_defined?(f)
      memo | Mysql2::Client.const_get(f)
    else
      warn "Unknown MySQL connection flag: '#{f}'"
      memo
    end
  end
end

#parse_ssl_mode(mode)

[ GitHub ]

  
# File 'lib/mysql2/client.rb', line 125

def parse_ssl_mode(mode)
  m = mode.to_s.upcase
  if m.start_with?('SSL_MODE_')
    return Mysql2::Client.const_get(m) if Mysql2::Client.const_defined?(m)
  else
    x = 'SSL_MODE_' + m
    return Mysql2::Client.const_get(x) if Mysql2::Client.const_defined?(x)
  end
  warn "Unknown MySQL ssl_mode flag: #{mode}"
end

#pending_prepared_statement_closes

Returns the number of prepared statements that were garbage collected while this connection was busy (mid-query or streaming), and are therefore waiting for a safe point to actually notify the server. This queue is not size-bounded; use this to observe whether it is growing unexpectedly large (e.g. because the connection is kept busy streaming for a long time while other statements on it keep churning). It drains on the next query, prepare, execute, ping, or streaming completion.

[ GitHub ]

  
# File 'ext/mysql2/client.c', line 2756

static VALUE rb_mysql_client_pending_prepared_statement_closes(VALUE self) {
  GET_CLIENT(self);

  return ULONG2NUM(wrapper->pending_stmt_close_count);
}

#pending_result_frees

Returns the number of result sets (from a streaming query or streaming prepared statement) that were abandoned mid-iteration and garbage collected, and are therefore waiting for a safe point to actually discard their unread rows from the connection. Mirrors #pending_prepared_statement_closes; drains at the same safe points.

[ GitHub ]

  
# File 'ext/mysql2/client.c', line 2771

static VALUE rb_mysql_client_pending_result_frees(VALUE self) {
  GET_CLIENT(self);

  return ULONG2NUM(wrapper->pending_result_free_count);
}

#ping

Checks whether the connection to the server is working. If the connection has gone down and auto-reconnect is enabled an attempt to reconnect is made. If the connection is down and auto-reconnect is disabled, ping returns an error.

[ GitHub ]

  
# File 'ext/mysql2/client.c', line 2238

static VALUE rb_mysql_client_ping(VALUE self) {
  GET_CLIENT(self);
  rb_mysql_client_set_active_fiber(self, false);

  if (mysql2_forked_without_reconnect(wrapper) && wrapper->automatic_close) {
    mysql2_warn_forked_without_reconnect(wrapper, "ping");
  }

  /* A low-traffic, frequently-called method; a good opportunistic safe
   * point to close out statements that were GC'd while we were busy, and to
   * free any abandoned result sets -- mysql_ping() itself sends a command,
   * so a still-live abandoned stream needs the same active drain as
   * rb_mysql_query, not just the reap. */
  mysql2_abandon_active_stream(wrapper);
  mysql2_reap_pending_result_frees(wrapper);
  mysql2_reap_pending_stmt_closes(wrapper);

#ifndef _WIN32
  return rb_rescue2(do_ping, (VALUE)wrapper, disconnect_and_raise, self, rb_eException, (VALUE)0);
#else
  VALUE result = Qnil;
  if (!CONNECTED(wrapper)) {
    result = Qfalse;
  } else {
    result = (VALUE)rb_thread_call_without_gvl(nogvl_ping, wrapper->client, RUBY_UBF_IO, 0);
  }
  wrapper->active_fiber = Qnil;
  return result;
#endif
}

#prepare(#) ⇒ Mysql2::Statement

Create a new prepared statement.

[ GitHub ]

  
# File 'ext/mysql2/client.c', line 2717

static VALUE rb_mysql_client_prepare_statement(VALUE self, VALUE sql) {
  VALUE stmt;
  GET_CLIENT(self);
  REQUIRE_CONNECTED(wrapper);

  if (mysql2_forked_without_reconnect(wrapper) && wrapper->automatic_close) {
    mysql2_warn_forked_without_reconnect(wrapper, "prepare a statement");
  }

  stmt = rb_mysql_stmt_new(self, sql);

  return stmt;
}

#prepared_statements

Returns an array of prepared statement objects.

[ GitHub ]

  
# File 'ext/mysql2/client.c', line 2736

static VALUE rb_mysql_client_prepared_statements_read(VALUE self) {
  GET_CLIENT(self);

  mysql2_reap_pending_result_frees(wrapper);
  mysql2_reap_pending_stmt_closes(wrapper);

  return rb_funcall(wrapper->prepared_statements, intern_values, 0);
}

#query(sql, options = EMPTY_QUERY_OPTIONS)

[ GitHub ]

  
# File 'lib/mysql2/client.rb', line 240

def query(sql, options = EMPTY_QUERY_OPTIONS)
  Thread.handle_interrupt(::Mysql2::Util::TIMEOUT_ERROR_NEVER) do
    _query(sql, @query_options.merge(options))
  end
end

#query_info

[ GitHub ]

  
# File 'lib/mysql2/client.rb', line 246

def query_info
  info = query_info_string
  return {} unless info

  info_hash = {}
  info.split.each_slice(2) { |s| info_hash[s[0].downcase.delete(':').to_sym] = s[1].to_i }
  info_hash
end

#query_info_string

[ GitHub ]

  
# File 'ext/mysql2/client.c', line 960

static VALUE rb_mysql_info(VALUE self) {
  const char *info;
  VALUE rb_str;
  GET_CLIENT(self);

  info = mysql_info(wrapper->client);

  if (info == NULL) {
    return Qnil;
  }

  rb_str = rb_str_new2(info);
  rb_enc_associate(rb_str, rb_utf8_encoding());

  return rb_str;
}

#select_db(name)

Causes the database specified by name to become the default (current) database on the connection specified by mysql.

[ GitHub ]

  
# File 'ext/mysql2/client.c', line 2177

static VALUE rb_mysql_client_select_db(VALUE self, VALUE db)
{
  struct nogvl_select_db_args args;

  GET_CLIENT(self);
  REQUIRE_CONNECTED(wrapper);

  args.mysql = wrapper->client;
  args.db = StringValueCStr(db);
  args.completion.wrapper = wrapper;
  args.completion.completed = 0;

  rb_mysql_client_set_active_fiber(self, false);
  rb_ensure(do_select_db, (VALUE)&args, release_claim_or_disconnect, (VALUE)&args.completion);

  /* This originates as a Ruby VALUE, but we're using its C pointer
   * directly -- keep the VALUE live on the stack so GC can't collect it
   * while we drop the GVL to make a MySQL API call. */
  (void)RB_GC_GUARD(db);

  return db;
}

#server_info

Returns a string that represents the server version number

[ GitHub ]

  
# File 'ext/mysql2/client.c', line 2036

static VALUE rb_mysql_client_server_info(VALUE self) {
  VALUE version, server_info;
  rb_encoding *default_internal_enc;
  rb_encoding *conn_enc;
  GET_CLIENT(self);

  REQUIRE_CONNECTED(wrapper);
  default_internal_enc = rb_default_internal_encoding();
  conn_enc = rb_to_encoding(wrapper->encoding);

  version = rb_hash_new();
  rb_hash_aset(version, sym_id, LONG2FIX(mysql_get_server_version(wrapper->client)));
  server_info = rb_str_new2(mysql_get_server_info(wrapper->client));
  rb_enc_associate(server_info, conn_enc);
  if (default_internal_enc) {
    server_info = rb_str_export_to_enc(server_info, default_internal_enc);
  }
  rb_hash_aset(version, sym_version, server_info);
  return version;
}

#session_track

Returns information about changes to the session state on the server.

[ GitHub ]

  
# File 'ext/mysql2/client.c', line 2091

static VALUE rb_mysql_client_session_track(VALUE self, VALUE type) {
#ifdef CLIENT_SESSION_TRACK
  const char *data;
  size_t length;
  my_ulonglong retVal;
  GET_CLIENT(self);

  REQUIRE_CONNECTED(wrapper);
  retVal = mysql_session_track_get_first(wrapper->client, NUM2INT(type), &data, &length);
  if (retVal != 0) {
    return Qnil;
  }
  VALUE rbAry = rb_ary_new();
  VALUE rbFirst = rb_str_new(data, length);
  rb_ary_push(rbAry, rbFirst);
  while(mysql_session_track_get_next(wrapper->client, NUM2INT(type), &data, &length) == 0) {
    VALUE rbNext = rb_str_new(data, length);
    rb_ary_push(rbAry, rbNext);
  }
  return rbAry;
#else
  return Qnil;
#endif
}

#set_server_option(value)

Enables or disables an option for the connection. Read https://dev.mysql.com/doc/refman/5.7/en/mysql-set-server-option.html for more information.

[ GitHub ]

  
# File 'ext/mysql2/client.c', line 2276

static VALUE rb_mysql_client_set_server_option(VALUE self, VALUE value) {
  int option;
  int rv;
  GET_CLIENT(self);

  option = NUM2INT(value);

  /* mysql_set_server_option runs its whole COM_SET_OPTION round trip with
   * the GVL held, so nothing can raise -- and no interrupt can land --
   * between this claim and its release below. */
  rb_mysql_client_set_active_fiber(self, false);
  rv = mysql_set_server_option(wrapper->client, option);
  wrapper->active_fiber = Qnil;

  return rv == 0 ? Qtrue : Qfalse;
}

#socket

[ GitHub ]

  
# File 'ext/mysql2/client.c', line 2069

static VALUE rb_mysql_client_socket(RB_MYSQL_UNUSED VALUE self) {
  rb_raise(cMysql2Error, "Raw access to the mysql file descriptor isn't supported on Windows");
}

#ssl_cipher

[ GitHub ]

  
# File 'ext/mysql2/client.c', line 977

static VALUE rb_mysql_get_ssl_cipher(VALUE self)
{
  const char *cipher;
  VALUE rb_str;
  GET_CLIENT(self);

  cipher = mysql_get_ssl_cipher(wrapper->client);

  if (cipher == NULL) {
    return Qnil;
  }

  rb_str = rb_str_new2(cipher);
  rb_enc_associate(rb_str, rb_utf8_encoding());

  return rb_str;
}

#ssl_set(key, cert, ca, capath, cipher) (private)

[ GitHub ]

  
# File 'ext/mysql2/client.c', line 2616

static VALUE set_ssl_options(VALUE self, VALUE key, VALUE cert, VALUE ca, VALUE capath, VALUE cipher) {
  GET_CLIENT(self);

#ifdef HAVE_MYSQL_SSL_SET
  mysql_ssl_set(wrapper->client,
      NIL_P(key)    ? NULL : StringValueCStr(key),
      NIL_P(cert)   ? NULL : StringValueCStr(cert),
      NIL_P(ca)     ? NULL : StringValueCStr(ca),
      NIL_P(capath) ? NULL : StringValueCStr(capath),
      NIL_P(cipher) ? NULL : StringValueCStr(cipher));
#else
  /* mysql 8.3 does not provide mysql_ssl_set */
  if (!NIL_P(key)) {
    mysql_options(wrapper->client, MYSQL_OPT_SSL_KEY, StringValueCStr(key));
  }
  if (!NIL_P(cert)) {
    mysql_options(wrapper->client, MYSQL_OPT_SSL_CERT, StringValueCStr(cert));
  }
  if (!NIL_P(ca)) {
    mysql_options(wrapper->client, MYSQL_OPT_SSL_CA, StringValueCStr(ca));
  }
  if (!NIL_P(capath)) {
    mysql_options(wrapper->client, MYSQL_OPT_SSL_CAPATH, StringValueCStr(capath));
  }
  if (!NIL_P(cipher)) {
    mysql_options(wrapper->client, MYSQL_OPT_SSL_CIPHER, StringValueCStr(cipher));
  }
#endif

  return self;
}

#store_result

Return the next result object from a query which yielded multiple result sets.

[ GitHub ]

  
# File 'ext/mysql2/client.c', line 2441

static VALUE rb_mysql_client_store_result(VALUE self)
{
  GET_CLIENT(self);

  /* The claim is released inside nogvl_do_result once the result is stored
   * (or, on a fetch error, by mysql2_fetch_result_set before it raises) --
   * the same lifecycle a query's own result fetch follows. */
  rb_mysql_client_set_active_fiber(self, false);

  /* Honor :stream on later result sets of a multi-statement query the same
   * way async_result already does for the first one -- previously this
   * always called mysql_store_result regardless, silently buffering every
   * result set after the first even when the original query asked to
   * stream them (see #600). Also refreshes affected_rows: it's a
   * per-statement value at the C level, so it needs re-reading here too,
   * not just by async_result for the batch's first statement.
   *
   * No round-trip reading: this result set's response was read and consumed
   * by Client#next_result, which no bracket surrounds -- the number the
   * original bracket produced belongs to the batch's first result set, not
   * this one. So #query_time is nil here. */
  return mysql2_fetch_result_set(self, wrapper, -1);
}

#thread_id

Returns the thread ID of the current connection.

[ GitHub ]

  
# File 'ext/mysql2/client.c', line 2139

static VALUE rb_mysql_client_thread_id(VALUE self) {
  unsigned long retVal;
  GET_CLIENT(self);

  REQUIRE_CONNECTED(wrapper);
  retVal = mysql_thread_id(wrapper->client);
  return ULL2NUM(retVal);
}

#tls_info

Describes the connection's TLS session as observed after the handshake: negotiated protocol and cipher, the peer certificate the server actually presented (subject, issuer, validity period, SHA-256 fingerprint), the MARIADB_TLS_VERIFY_* bitmask of verification checks the client library recorded as failed, and whether mysql2's own :verify_identity enforcement confirmed certificate-chain and hostname verification for this connection.

Returns nil when the connection is not using TLS, and on client libraries without the introspection API (libmysqlclient, and MariaDB Connector/C before 3.4). On a returned hash every key is present: :verify_status and :peer_cert are nil if the library cannot report them.

[ GitHub ]

  
# File 'ext/mysql2/client.c', line 1029

static VALUE rb_mysql_client_tls_info(VALUE self) {
  GET_CLIENT(self);
  REQUIRE_CONNECTED(wrapper);
#ifdef MYSQL2_TLS_INFO
  {
    VALUE info, peer;
    const char *tls_version = NULL;
    unsigned int verify_status = 0;
    MARIADB_X509_INFO *cert = NULL;

    if (mariadb_get_infov(wrapper->client, MARIADB_CONNECTION_TLS_VERSION, (void *)&tls_version) != 0 ||
        tls_version == NULL)
      return Qnil; /* no TLS session on this connection */

    info = rb_hash_new();
    rb_hash_aset(info, ID2SYM(rb_intern("tls_version")), mysql2_utf8_str_or_nil(tls_version));
    rb_hash_aset(info, ID2SYM(rb_intern("cipher")), mysql2_utf8_str_or_nil(mysql_get_ssl_cipher(wrapper->client)));
    /* The hash's shape is invariant: every key is always present, with nil
     * for anything the client library could not report. */
    rb_hash_aset(info, ID2SYM(rb_intern("verify_status")),
                 mariadb_get_infov(wrapper->client, MARIADB_TLS_VERIFY_STATUS, (void *)&verify_status) == 0 ?
                   UINT2NUM(verify_status) : Qnil);
    rb_hash_aset(info, ID2SYM(rb_intern("identity_verified")), wrapper->tls_identity_verified ? Qtrue : Qfalse);

    /* 256 selects the SHA-256 fingerprint of the peer certificate. */
    peer = Qnil;
    if (mariadb_get_infov(wrapper->client, MARIADB_TLS_PEER_CERT_INFO, (void *)&cert, 256) == 0 && cert != NULL) {
      peer = rb_hash_new();
      rb_hash_aset(peer, ID2SYM(rb_intern("version")), INT2NUM(cert->version));
      rb_hash_aset(peer, ID2SYM(rb_intern("subject")), mysql2_utf8_str_or_nil(cert->subject));
      rb_hash_aset(peer, ID2SYM(rb_intern("issuer")), mysql2_utf8_str_or_nil(cert->issuer));
      rb_hash_aset(peer, ID2SYM(rb_intern("fingerprint")), mysql2_utf8_str_or_nil(cert->fingerprint));
      rb_hash_aset(peer, ID2SYM(rb_intern("not_before")), mysql2_tm_to_time(&cert->not_before));
      rb_hash_aset(peer, ID2SYM(rb_intern("not_after")), mysql2_tm_to_time(&cert->not_after));
    }
    rb_hash_aset(info, ID2SYM(rb_intern("peer_cert")), peer);
    return info;
  }
#else
  return Qnil;
#endif
}

#warn_incoherent_options (private)

Warns once per client, before connecting, about option combinations that are incoherent or silently ignored. Warnings only: the connection proceeds exactly as it would have without them.

[ GitHub ]

  
# File 'lib/mysql2/client.rb', line 268

def warn_incoherent_options
  # The client key and certificate only take effect together. Given one
  # without the other, libmysqlclient silently sends no client certificate
  # and MariaDB Connector/C aborts the connection with a bare TLS error
  # that never names the real problem. Either option may be given under
  # its legacy :ssl* name or its :tls_* alias.
  # https://dev.mysql.com/doc/refman/en/using-encrypted-connections.html
  effective_key = @query_options[:tls_key] || @query_options[:sslkey]
  effective_cert = @query_options[:tls_cert] || @query_options[:sslcert]
  warn ":sslkey and :sslcert only take effect together; alone, libmysqlclient sends no client certificate and MariaDB Connector/C fails to connect" \
    if effective_key.nil? != effective_cert.nil?

  # If a legacy :ssl* option and its :tls_* alias are both given with
  # different values, the :tls_* value silently wins (see
  # TLS_OPTION_ALIASES); warn so the conflict isn't invisible.
  TLS_OPTION_ALIASES.each do |tls_key, legacy_key|
    next unless @query_options.key?(tls_key) && @query_options.key?(legacy_key)
    next if @query_options[tls_key] == @query_options[legacy_key]

    warn ":#{legacy_key} and :#{tls_key} were both given with different values; :#{tls_key} wins"
  end

  # Streaming results are never cached, so a client-wide :stream default
  # overrides the :cache_rows default on every query (see the per-query
  # warning in ext/mysql2/result.c).
  warn ":cache_rows is ignored on a client with :stream enabled; pass cache_rows: false to acknowledge streaming semantics" \
    if @query_options[:stream] && @query_options[:cache_rows]
end

#warning_count

[ GitHub ]

  
# File 'ext/mysql2/client.c', line 951

static VALUE rb_mysql_client_warning_count(VALUE self) {
  unsigned int warning_count;
  GET_CLIENT(self);

  warning_count = mysql_warning_count(wrapper->client);

  return UINT2NUM(warning_count);
}