require 'mysql2' unless defined? Mysql2 Sequel.require %w'shared/mysql', 'adapters' module Sequel # Module for holding all Mysql2-related classes and modules for Sequel. module Mysql2 class << self # Set the default charset used for CREATE TABLE. You can pass the # :charset option to create_table to override this setting. attr_accessor :default_charset # Set the default collation used for CREATE TABLE. You can pass the # :collate option to create_table to override this setting. attr_accessor :default_collate # Set the default engine used for CREATE TABLE. You can pass the # :engine option to create_table to override this setting. attr_accessor :default_engine end @convert_tinyint_to_bool = true class << self # Sequel converts the column type tinyint(1) to a boolean by default when # using the native Mysql2 adapter. You can turn off the conversion by setting # this to false. attr_accessor :convert_tinyint_to_bool end # Database class for MySQL databases used with Sequel. class Database < Sequel::Database include Sequel::MySQL::DatabaseMethods # Mysql::Error messages that indicate the current connection should be disconnected MYSQL_DATABASE_DISCONNECT_ERRORS = /\A(Commands out of sync; you can't run this command now|Can't connect to local MySQL server through socket|MySQL server has gone away)/ set_adapter_scheme :mysql2 # Connect to the database. In addition to the usual database options, # the following options have effect: # # * :auto_is_null - Set to true to use MySQL default behavior of having # a filter for an autoincrement column equals NULL to return the last # inserted row. # * :charset - Same as :encoding (:encoding takes precendence) # * :compress - Set to false to not compress results from the server # * :config_default_group - The default group to read from the in # the MySQL config file. # * :config_local_infile - If provided, sets the Mysql::OPT_LOCAL_INFILE # option on the connection with the given value. # * :encoding - Set all the related character sets for this # connection (connection, client, database, server, and results). # * :socket - Use a unix socket file instead of connecting via TCP/IP. # * :timeout - Set the timeout in seconds before the server will # disconnect this connection. def connect(server) opts = server_opts(server) opts[:host] ||= 'localhost' opts[:username] ||= opts[:user] conn = ::Mysql2::Client.new(opts) sqls = [] # Set encoding a slightly different way after connecting, # in case the READ_DEFAULT_GROUP overrode the provided encoding. # Doesn't work across implicit reconnects, but Sequel doesn't turn on # that feature. if encoding = opts[:encoding] || opts[:charset] sqls << "SET NAMES #{conn.escape(encoding.to_s)}" end # increase timeout so mysql server doesn't disconnect us sqls << "SET @@wait_timeout = #{opts[:timeout] || 2592000}" # By default, MySQL 'where id is null' selects the last inserted id sqls << "SET SQL_AUTO_IS_NULL=0" unless opts[:auto_is_null] sqls.each{|sql| log_yield(sql){conn.query(sql)}} conn end # Returns instance of Sequel::MySQL::Dataset with the given options. def dataset(opts = nil) Mysql2::Dataset.new(self, opts) end # Executes the given SQL using an available connection, yielding the # connection if the block is given. def execute(sql, opts={}, &block) if opts[:sproc] call_sproc(sql, opts, &block) else synchronize(opts[:server]){|conn| _execute(conn, sql, opts, &block)} end end # Return the version of the MySQL server two which we are connecting. def server_version(server=nil) @server_version ||= (synchronize(server){|conn| conn.server_info[:id]} || super) end private # Use MySQL specific syntax for engine type and character encoding def create_table_sql(name, generator, options = {}) engine = options.fetch(:engine, Sequel::Mysql2.default_engine) charset = options.fetch(:charset, Sequel::Mysql2.default_charset) collate = options.fetch(:collate, Sequel::Mysql2.default_collate) generator.columns.each do |c| if t = c.delete(:table) generator.foreign_key([c[:name]], t, c.merge(:name=>nil, :type=>:foreign_key)) end end super(name, generator, options.merge(:engine => engine, :charset => charset, :collate => collate)) end # Execute the given SQL on the given connection. If the :type # option is :select, yield the result of the query, otherwise # yield the connection if a block is given. def _execute(conn, sql, opts) begin r = log_yield(sql){conn.query(sql, :symbolize_keys => true)} if opts[:type] == :select yield r if r elsif block_given? yield conn end rescue ::Mysql2::Error => e raise_error(e, :disconnect=>MYSQL_DATABASE_DISCONNECT_ERRORS.match(e.message)) end end # MySQL connections use the query method to execute SQL without a result def connection_execute_method :query end # The MySQL adapter main error class is Mysql2::Error def database_error_classes [::Mysql2::Error] end # The database name when using the native adapter is always stored in # the :database option. def database_name @opts[:database] end # Closes given database connection. def disconnect_connection(c) c.close end # Convert tinyint(1) type to boolean if convert_tinyint_to_bool is true def schema_column_type(db_type) Sequel::Mysql2.convert_tinyint_to_bool && db_type == 'tinyint(1)' ? :boolean : super end end # Dataset class for MySQL datasets accessed via the native driver. class Dataset < Sequel::Dataset include Sequel::MySQL::DatasetMethods # Delete rows matching this dataset def delete execute_dui(delete_sql){|c| return c.affected_rows} end # Yield all rows matching this dataset. def fetch_rows(sql, &block) execute(sql) do |r| @columns = r.fields r.each(:cast_booleans => Sequel::Mysql2.convert_tinyint_to_bool, &block) end self end # Don't allow graphing a dataset that splits multiple statements def graph(*) raise(Error, "Can't graph a dataset that splits multiple result sets") if opts[:split_multiple_result_sets] super end # Insert a new value into this dataset def insert(*values) execute_dui(insert_sql(*values)){|c| return c.last_id} end # Replace (update or insert) the matching row. def replace(*args) execute_dui(replace_sql(*args)){|c| return c.last_id} end # Update the matching rows. def update(values={}) execute_dui(update_sql(values)){|c| return c.affected_rows} end private # Set the :type option to :select if it hasn't been set. def execute(sql, opts={}, &block) super(sql, {:type=>:select}.merge(opts), &block) end # Set the :type option to :dui if it hasn't been set. def execute_dui(sql, opts={}, &block) super(sql, {:type=>:dui}.merge(opts), &block) end # Handle correct quoting of strings using ::Mysql2::Client#escape. def literal_string(v) db.synchronize{|c| "'#{c.escape(v)}'"} end end end end