2010-04-26 06:13:27 +00:00
require 'mysql2' unless defined? Mysql2
2010-08-05 07:44:01 +00:00
Sequel . require %w'shared/mysql' , 'adapters'
2010-04-26 06:13:27 +00:00
module Sequel
2010-08-05 07:44:01 +00:00
# Module for holding all Mysql2-related classes and modules for Sequel.
2010-04-26 06:13:27 +00:00
module Mysql2
2010-08-05 07:44:01 +00:00
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
2010-04-26 06:13:27 +00:00
end
@convert_tinyint_to_bool = true
class << self
# Sequel converts the column type tinyint(1) to a boolean by default when
2010-08-05 07:44:01 +00:00
# using the native Mysql2 adapter. You can turn off the conversion by setting
2010-04-26 06:13:27 +00:00
# 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 )
2010-08-05 07:44:01 +00:00
opts [ :host ] || = 'localhost'
2010-08-05 08:20:15 +00:00
opts [ :username ] || = opts [ :user ]
2010-08-05 07:44:01 +00:00
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 ]
2010-08-05 08:20:42 +00:00
sqls << " SET NAMES #{ conn . escape ( encoding . to_s ) } "
2010-08-05 07:44:01 +00:00
end
2010-04-26 06:13:27 +00:00
# increase timeout so mysql server doesn't disconnect us
2010-08-05 07:44:01 +00:00
sqls << " SET @@wait_timeout = #{ opts [ :timeout ] || 2592000 } "
2010-04-26 06:13:27 +00:00
# By default, MySQL 'where id is null' selects the last inserted id
2010-08-05 07:44:01 +00:00
sqls << " SET SQL_AUTO_IS_NULL=0 " unless opts [ :auto_is_null ]
sqls . each { | sql | log_yield ( sql ) { conn . query ( sql ) } }
2010-04-26 06:13:27 +00:00
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 )
2010-08-05 07:44:01 +00:00
@server_version || = ( synchronize ( server ) { | conn | conn . server_info [ :id ] } || super )
2010-04-26 06:13:27 +00:00
end
private
2010-08-05 07:44:01 +00:00
# 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
2010-04-26 06:13:27 +00:00
# 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
2010-08-05 07:44:01 +00:00
r = log_yield ( sql ) { conn . query ( sql , :symbolize_keys = > true ) }
2010-04-26 06:13:27 +00:00
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
2010-08-05 07:44:01 +00:00
# The MySQL adapter main error class is Mysql2::Error
2010-04-26 06:13:27 +00:00
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 )
2010-05-13 05:09:06 +00:00
c . close
2010-04-26 06:13:27 +00:00
end
# Convert tinyint(1) type to boolean if convert_tinyint_to_bool is true
def schema_column_type ( db_type )
2010-05-13 09:04:49 +00:00
Sequel :: Mysql2 . convert_tinyint_to_bool && db_type == 'tinyint(1)' ? :boolean : super
2010-04-26 06:13:27 +00:00
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
2010-08-05 07:44:01 +00:00
# Yield all rows matching this dataset.
2010-04-26 06:13:27 +00:00
def fetch_rows ( sql , & block )
execute ( sql ) do | r |
2010-08-05 07:44:01 +00:00
@columns = r . fields
r . each ( :cast_booleans = > Sequel :: Mysql2 . convert_tinyint_to_bool , & block )
2010-04-26 06:13:27 +00:00
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 )
2010-05-13 09:04:49 +00:00
execute_dui ( insert_sql ( * values ) ) { | c | return c . last_id }
2010-04-26 06:13:27 +00:00
end
# Replace (update or insert) the matching row.
def replace ( * args )
2010-05-13 09:04:49 +00:00
execute_dui ( replace_sql ( * args ) ) { | c | return c . last_id }
2010-04-26 06:13:27 +00:00
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
2010-08-05 07:45:56 +00:00
# Handle correct quoting of strings using ::Mysql2::Client#escape.
2010-04-26 06:13:27 +00:00
def literal_string ( v )
2010-05-13 09:04:49 +00:00
db . synchronize { | c | " ' #{ c . escape ( v ) } ' " }
2010-04-26 06:13:27 +00:00
end
end
end
end