Skip to content
Find file
Fetching contributors…
Cannot retrieve contributors at this time
406 lines (362 sloc) 13.7 KB
module DBI::DBD::Mysql
#
# Models the DBI::BaseDatabase API to create DBI::DatabaseHandle objects.
#
class Database < DBI::BaseDatabase
include Util
#
# Hash to translate MySQL type names to DBI SQL type constants
#
# Only used in #mysql_type_info.
#
#--
# Eli Green:
# The hope is that we don't ever need to just assume the default values.
# However, in some cases (notably floats and doubles), I have seen
# "show fields from table" return absolutely zero information about size
# and precision. Sigh. I probably should have made a struct to store
# this info in ... but I didn't.
#++
MYSQL_to_XOPEN = {
"TINYINT" => [DBI::SQL_TINYINT, 1, nil],
"SMALLINT" => [DBI::SQL_SMALLINT, 6, nil],
"MEDIUMINT" => [DBI::SQL_SMALLINT, 6, nil],
"INT" => [DBI::SQL_INTEGER, 11, nil],
"INTEGER" => [DBI::SQL_INTEGER, 11, nil],
"BIGINT" => [DBI::SQL_BIGINT, 25, nil],
"INT24" => [DBI::SQL_BIGINT, 25, nil],
"REAL" => [DBI::SQL_REAL, 12, nil],
"FLOAT" => [DBI::SQL_FLOAT, 12, nil],
"DECIMAL" => [DBI::SQL_DECIMAL, 12, nil],
"NUMERIC" => [DBI::SQL_NUMERIC, 12, nil],
"DOUBLE" => [DBI::SQL_DOUBLE, 22, nil],
"CHAR" => [DBI::SQL_CHAR, 1, nil],
"VARCHAR" => [DBI::SQL_VARCHAR, 255, nil],
"DATE" => [DBI::SQL_DATE, 10, nil],
"TIME" => [DBI::SQL_TIME, 8, nil],
"TIMESTAMP" => [DBI::SQL_TIMESTAMP, 19, nil],
"DATETIME" => [DBI::SQL_TIMESTAMP, 19, nil],
"TINYBLOB" => [DBI::SQL_BINARY, 255, nil],
"BLOB" => [DBI::SQL_VARBINARY, 65535, nil],
"MEDIUMBLOB" => [DBI::SQL_VARBINARY, 16277215, nil],
"LONGBLOB" => [DBI::SQL_LONGVARBINARY, 2147483657, nil],
"TINYTEXT" => [DBI::SQL_VARCHAR, 255, nil],
"TEXT" => [DBI::SQL_LONGVARCHAR, 65535, nil],
"MEDIUMTEXT" => [DBI::SQL_LONGVARCHAR, 16277215, nil],
"LONGTEXT" => [DBI::SQL_LONGVARCHAR, 2147483657, nil],
"ENUM" => [DBI::SQL_CHAR, 255, nil],
"SET" => [DBI::SQL_CHAR, 255, nil],
"BIT" => [DBI::SQL_BIT, 8, nil],
nil => [DBI::SQL_OTHER, nil, nil]
}
#
# This maps type names to DBI Types.
#
TYPE_MAP = {}
::Mysql::Field.constants.grep(/^TYPE_/).each do |const|
mysql_type = MysqlField.const_get(const) # numeric type code
coercion_method = DBI::Type::Varchar # default coercion method
case const.to_s
when 'TYPE_TINY'
mysql_type_name = 'TINYINT'
coercion_method = DBI::Type::Integer
when 'TYPE_SHORT'
mysql_type_name = 'SMALLINT'
coercion_method = DBI::Type::Integer
when 'TYPE_INT24'
mysql_type_name = 'MEDIUMINT'
coercion_method = DBI::Type::Integer
when 'TYPE_LONG'
mysql_type_name = 'INT'
coercion_method = DBI::Type::Integer
when 'TYPE_LONGLONG'
mysql_type_name = 'BIGINT'
coercion_method = DBI::Type::Integer
when 'TYPE_FLOAT'
mysql_type_name = 'FLOAT'
coercion_method = DBI::Type::Float
when 'TYPE_DOUBLE'
mysql_type_name = 'DOUBLE'
coercion_method = DBI::Type::Float
when 'TYPE_VAR_STRING', 'TYPE_STRING'
mysql_type_name = 'VARCHAR' # questionable?
coercion_method = DBI::Type::Varchar
when 'TYPE_DATE'
mysql_type_name = 'DATE'
coercion_method = DBI::DBD::Mysql::Type::Date
when 'TYPE_TIME'
mysql_type_name = 'TIME'
coercion_method = DBI::Type::Timestamp
when 'TYPE_DATETIME', 'TYPE_TIMESTAMP'
mysql_type_name = 'DATETIME'
coercion_method = DBI::Type::Timestamp
when 'TYPE_CHAR'
mysql_type_name = 'TINYINT' # questionable?
when 'TYPE_TINY_BLOB'
mysql_type_name = 'TINYBLOB' # questionable?
when 'TYPE_MEDIUM_BLOB'
mysql_type_name = 'MEDIUMBLOB' # questionable?
when 'TYPE_LONG_BLOB'
mysql_type_name = 'LONGBLOB' # questionable?
when 'TYPE_GEOMETRY'
mysql_type_name = 'BLOB' # questionable?
when 'TYPE_YEAR',
'TYPE_DECIMAL', # questionable?
'TYPE_BLOB', # questionable?
'TYPE_ENUM',
'TYPE_SET',
'TYPE_BIT',
'TYPE_NULL'
mysql_type_name = const.to_s.sub(/^TYPE_/, '')
else
mysql_type_name = 'UNKNOWN'
end
TYPE_MAP[mysql_type] = [mysql_type_name, coercion_method]
end
TYPE_MAP[nil] = ['UNKNOWN', DBI::Type::Varchar]
TYPE_MAP[246] = ['NUMERIC', DBI::Type::Decimal]
#
# Constructor. Attributes supported:
#
# * AutoCommit: Commit after each executed statement. This will raise
# a DBI::NotSupportedError if the backend does not support
# transactions.
#
def initialize(handle, attr)
super
# check server version to determine transaction capability
ver_str = @handle.get_server_info
major, minor, teeny = ver_str.split(".")
teeny.sub!(/\D*$/, "") # strip any non-numeric suffix if present
server_version = major.to_i*10000 + minor.to_i*100 + teeny.to_i
# It's not until 3.23.17 that SET AUTOCOMMIT,
# BEGIN, COMMIT, and ROLLBACK all are available
@have_transactions = (server_version >= 32317)
# assume that the connection begins in AutoCommit mode
@attr['AutoCommit'] = true
@mutex = Mutex.new
end
def disconnect
self.rollback unless @attr['AutoCommit']
@handle.close
rescue MyError => err
error(err)
end
def database_name
sth = Statement.new(self, @handle, "select DATABASE()", @mutex)
sth.execute
res = sth.fetch
sth.finish
return res[0]
end
def ping
begin
@handle.ping
return true
rescue MyError
return false
end
end
def tables
@handle.list_tables
rescue MyError => err
error(err)
end
#
# See DBI::BaseDatabase#columns.
#
# Extra attributes:
#
# * sql_type: XOPEN integer constant relating to type.
# * nullable: true if the column allows NULL as a value.
# * indexed: true if the column belongs to an index.
# * primary: true if the column is a part of a primary key.
# * unique: true if the values in this column are unique.
# * default: the default value if this column is not explicitly set.
#
def columns(table)
dbh = DBI::DatabaseHandle.new(self)
uniques = []
dbh.execute("SHOW INDEX FROM #{table}") do |sth|
sth.each do |row|
uniques << row[4] if row[1] == 0
end
end
ret = nil
dbh.execute("SHOW FIELDS FROM #{table}") do |sth|
ret = sth.collect do |row|
name, type, nullable, key, default, extra = row
#type = row[1]
#size = type[type.index('(')+1..type.index(')')-1]
#size = 0
#type = type[0..type.index('(')-1]
sqltype, type, size, decimal = mysql_type_info(row[1])
col = Hash.new
col['name'] = name
col['sql_type'] = sqltype
col['type_name'] = type
col['nullable'] = nullable == "YES"
col['indexed'] = key != ""
col['primary'] = key == "PRI"
col['unique'] = uniques.index(name) != nil
col['precision'] = size
col['scale'] = decimal
col['default'] = row[4]
case col['type_name']
when 'timestamp'
col['dbi_type'] = DBI::Type::Timestamp
end
col
end # collect
end # execute
ret
end
def do(stmt, *bindvars)
st = Statement.new(self, @handle, stmt, @mutex)
st.bind_params(*bindvars)
res = st.execute
st.finish
return res
rescue MyError => err
error(err)
end
def prepare(statement)
Statement.new(self, @handle, statement, @mutex)
end
#
# MySQL has several backends, some of which may not support commits.
# If the backend this database uses doesn't, calling this method will
# raise a DBI::NotSupportedError.
#
def commit
if @have_transactions
self.do("COMMIT")
else
raise NotSupportedError
end
rescue MyError => err
error(err)
end
#
# See #commit for information regarding transactionless database
# backends.
#
def rollback
if @have_transactions
self.do("ROLLBACK")
else
raise NotSupportedError
end
rescue MyError => err
error(err)
end
# def quote(value)
# case value
# when String
# "'#{@handle.quote(value)}'"
# when DBI::Binary
# "'#{@handle.quote(value.to_s)}'"
# when TrueClass
# "'1'"
# when FalseClass
# "'0'"
# else
# super
# end
# end
#
# See DBI::DBD::MySQL::Database.new for supported attributes and usage.
#
def []=(attr, value)
case attr
when 'AutoCommit'
if @have_transactions
self.do("SET AUTOCOMMIT=" + (value ? "1" : "0"))
else
raise NotSupportedError
end
else
raise NotSupportedError
end
@attr[attr] = value
end
private # -------------------------------------------------
#
# Given a type name, weans some basic information from that and returns
# it in a format similar to columns.
#
# Return is an array of +sqltype+, +type+, +size+, and +decimal+.
# +sqltype+ is the XOPEN type, and +type+ is the string with the
# parameters removed.
#
# +size+ and +decimal+ refer to +precision+ and +scale+ in most cases,
# but not always for all types. Please consult the documentation for
# your MySQL version.
#
#
def mysql_type_info(typedef)
sqltype, type, size, decimal = nil, nil, nil, nil
pos = typedef.index('(')
if not pos.nil?
type = typedef[0..pos-1]
size = typedef[pos+1..-2]
pos = size.index(',')
if not pos.nil?
size, decimal = size.split(',', 2)
decimal = decimal.to_i
end
size = size.to_i
else
type = typedef
end
type_info = MYSQL_to_XOPEN[type.upcase] || MYSQL_to_XOPEN[nil]
sqltype = type_info[0]
if size.nil? then size = type_info[1] end
if decimal.nil? then decimal = type_info[2] end
return sqltype, type, size, decimal
end
#--
# Driver-specific functions ------------------------------------------------
#++
public
def __createdb(db)
@handle.create_db(db)
end
def __dropdb(db)
@handle.drop_db(db)
end
def __shutdown
@handle.shutdown
end
def __reload
@handle.reload
end
def __insert_id
@handle.insert_id
end
def __thread_id
@handle.thread_id
end
def __info
@handle.info
end
def __host_info
@handle.host_info
end
def __proto_info
@handle.proto_info
end
def __server_info
@handle.server_info
end
def __client_info
@handle.client_info
end
def __client_version
@handle.client_version
end
def __stat
@handle.stat
end
end # class Database
end
Jump to Line
Something went wrong with that request. Please try again.