aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib
diff options
context:
space:
mode:
authorJeremy Kemper <jeremy@bitsweat.net>2005-11-09 06:43:40 +0000
committerJeremy Kemper <jeremy@bitsweat.net>2005-11-09 06:43:40 +0000
commit44591ffb710701993ffdec0b61185f8067e4e78f (patch)
tree8d4937ae9e9eb4077435b1eafe4a2d11b0aac281 /activerecord/lib
parentbb4c32e2b5858b25524191ee4868e616fa5bd9be (diff)
downloadrails-44591ffb710701993ffdec0b61185f8067e4e78f.tar.gz
rails-44591ffb710701993ffdec0b61185f8067e4e78f.tar.bz2
rails-44591ffb710701993ffdec0b61185f8067e4e78f.zip
r3886@sedna: jeremy | 2005-11-07 03:09:59 -0800
r3944@sedna: jeremy | 2005-11-09 01:29:56 -0800 Upgrade bundled ruby-mysql 0.2.4 with mysql411 shim (see #440) to ruby-mysql0.2.6 with a patchset for 4.1 protocol support. Local change [301] is now apart of the main driver; reapplied local change [2182]. Removed GC.start fromResult.free. git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@2947 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
Diffstat (limited to 'activerecord/lib')
-rwxr-xr-xactiverecord/lib/active_record/connection_adapters/mysql_adapter.rb1
-rw-r--r--activerecord/lib/active_record/vendor/mysql.rb101
-rw-r--r--activerecord/lib/active_record/vendor/mysql411.rb311
3 files changed, 89 insertions, 324 deletions
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index 0834bf51e0..ca410c4add 100755
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -14,7 +14,6 @@ module ActiveRecord
# Only use the supplied backup Ruby/MySQL driver if no driver is already in place
begin
require 'active_record/vendor/mysql'
- require 'active_record/vendor/mysql411'
# The ruby version of mysql returns null fields in each_hash
ConnectionAdapters::MysqlAdapter.null_values_in_each_hash = true
rescue LoadError
diff --git a/activerecord/lib/active_record/vendor/mysql.rb b/activerecord/lib/active_record/vendor/mysql.rb
index 39ceb607b0..bcf667f575 100644
--- a/activerecord/lib/active_record/vendor/mysql.rb
+++ b/activerecord/lib/active_record/vendor/mysql.rb
@@ -1,14 +1,15 @@
-# $Id: mysql.rb,v 1.1 2004/02/24 15:42:29 webster132 Exp $
+# $Id: mysql.rb,v 1.24 2005/02/12 11:37:15 tommy Exp $
#
-# Copyright (C) 2003 TOMITA Masahiro
+# Copyright (C) 2003-2005 TOMITA Masahiro
# tommy@tmtm.org
#
class Mysql
- VERSION = "4.0-ruby-0.2.4"
+ VERSION = "4.0-ruby-0.2.5"
require "socket"
+ require "digest/sha1"
MAX_PACKET_LENGTH = 256*256*256-1
MAX_ALLOWED_PACKET = 1024*1024*1024
@@ -51,11 +52,15 @@ class Mysql
CLIENT_ODBC = 1 << 6
CLIENT_LOCAL_FILES = 1 << 7
CLIENT_IGNORE_SPACE = 1 << 8
+ CLIENT_PROTOCOL_41 = 1 << 9
CLIENT_INTERACTIVE = 1 << 10
CLIENT_SSL = 1 << 11
CLIENT_IGNORE_SIGPIPE = 1 << 12
CLIENT_TRANSACTIONS = 1 << 13
+ CLIENT_RESERVED = 1 << 14
+ CLIENT_SECURE_CONNECTION = 1 << 15
CLIENT_CAPABILITIES = CLIENT_LONG_PASSWORD|CLIENT_LONG_FLAG|CLIENT_TRANSACTIONS
+ PROTO_AUTH41 = CLIENT_PROTOCOL_41 | CLIENT_SECURE_CONNECTION
# Connection Option
OPT_CONNECT_TIMEOUT = 0
@@ -115,19 +120,37 @@ class Mysql
@server_capabilities, = a.slice!(0,2).unpack("v")
end
if a.size >= 16 then
- @server_language, @server_status = a.unpack("cv")
+ @server_language, @server_status = a.slice!(0,3).unpack("cv")
end
flag = 0 if flag == nil
flag |= @client_flag | CLIENT_CAPABILITIES
flag |= CLIENT_CONNECT_WITH_DB if db
- data = Net::int2str(flag)+Net::int3str(@max_allowed_packet)+(user||"")+"\0"+scramble(passwd, @scramble_buff, @protocol_version==9)
+
+ if !@server_capabilities & PROTO_AUTH41
+ data = Net::int2str(flag)+Net::int3str(@max_allowed_packet)+
+ (user||"")+"\0"+
+ scramble(passwd, @scramble_buff, @protocol_version==9)
+ else
+ dummy, @salt2 = a.unpack("a13a12")
+ @scramble_buff += @salt2
+ flag |= PROTO_AUTH41
+ data = Net::int4str(flag) + Net::int4str(@max_allowed_packet) +
+ ([8] + Array.new(23, 0)).pack("c24") + (user||"")+"\0"+
+ scramble41(passwd, @scramble_buff)
+ end
+
if db and @server_capabilities & CLIENT_CONNECT_WITH_DB != 0 then
- data << "\0"+db
+ if PROTO_AUTH41
+ data << db+"\0"
+ else
+ data << "\0"+db
+ end
@db = db.dup
end
write data
read
+ ObjectSpace.define_finalizer(self, Mysql.finalizer(@net))
self
end
alias :connect :real_connect
@@ -182,7 +205,11 @@ class Mysql
end
def change_user(user="", passwd="", db="")
+ if !@server_capabilities & PROTO_AUTH41
data = user+"\0"+scramble(passwd, @scramble_buff, @protocol_version==9)+"\0"+db
+ else
+ data = user+"\0"+ scramble41(passwd, @scramble_buff)
+ end
command COM_CHANGE_USER, data
@user = user
@passwd = passwd
@@ -243,7 +270,11 @@ class Mysql
def list_fields(table, field=nil)
command COM_FIELD_LIST, "#{table}\0#{field}", true
+ if !@server_capabilities & PROTO_AUTH41
f = read_rows 6
+ else
+ f = read_rows 7
+ end
fields = unpack_fields(f, @server_capabilities & CLIENT_LONG_FLAG != 0)
res = Result::new self, fields, f.length
res.eof = true
@@ -253,7 +284,11 @@ class Mysql
def list_processes()
data = command COM_PROCESS_INFO
@field_count = get_length data
+ if !@server_capabilities & PROTO_AUTH41
fields = read_rows 5
+ else
+ fields = read_rows 7
+ end
@fields = unpack_fields(fields, @server_capabilities & CLIENT_LONG_FLAG != 0)
@status = :STATUS_GET_RESULT
store_result
@@ -311,7 +346,11 @@ class Mysql
def read_one_row(field_count)
data = read
- return if data[0] == 254 and data.length == 1
+ if data[0] == 254 and data.length == 1 ## EOF
+ return
+ elsif data[0] == 254 and data.length == 5
+ return
+ end
rec = []
field_count.times do
len = get_length data
@@ -363,7 +402,11 @@ class Mysql
end
else
@extra_info = get_length(data, true)
+ if !@server_capabilities & PROTO_AUTH41
fields = read_rows 5
+ else
+ fields = read_rows(7)
+ end
@fields = unpack_fields(fields, @server_capabilities & CLIENT_LONG_FLAG != 0)
@status = :STATUS_GET_RESULT
end
@@ -373,6 +416,7 @@ class Mysql
def unpack_fields(data, long_flag_protocol)
ret = []
data.each do |f|
+ if !@server_capabilities & PROTO_AUTH41
table = org_table = f[0]
name = f[1]
length = f[2][0]+f[2][1]*256+f[2][2]*256*256
@@ -386,8 +430,22 @@ class Mysql
end
def_value = f[5]
max_length = 0
+ else
+ catalog = f[0]
+ db = f[1]
+ table = f[2]
+ org_table = f[3]
+ name = f[4]
+ org_name = f[5]
+ length = f[6][2]+f[6][3]*256+f[6][4]*256*256
+ type = f[6][6]
+ flags = f[6][7]+f[6][8]*256
+ decimals = f[6][9]
+ def_value = ""
+ max_length = 0
ret << Field::new(table, org_table, name, length, type, flags, decimals, def_value, max_length)
end
+ end
ret
end
@@ -489,6 +547,19 @@ class Mysql
to.join
end
+ def scramble41(password, message)
+ if password.length != 0
+ buf = [0x14]
+ s1 = Digest::SHA1.new(password).digest
+ s2 = Digest::SHA1.new(s1).digest
+ x = Digest::SHA1.new(message + s2).digest
+ (0..s1.length - 1).each {|i| buf.push(s1[i] ^ x[i])}
+ buf.pack("C*")
+ else
+ 0x00.chr
+ end
+ end
+
def error(errno)
@errno = errno
@error = Error::err errno
@@ -574,7 +645,6 @@ class Mysql
def free()
@handle.skip_result
@handle = @fields = @data = nil
- GC::start
end
def num_fields()
@@ -1023,8 +1093,8 @@ class Mysql
@sock.sync = true
buf.join
rescue
- errno = Error::CR_SERVER_LOST
- raise Error::new(errno, Error::err(errno))
+ errno = Error::CR_SERVER_LOST
+ raise Error::new(errno, Error::err(errno))
end
def write(data)
@@ -1043,8 +1113,8 @@ class Mysql
@sock.sync = true
@sock.flush
rescue
- errno = Error::CR_SERVER_LOST
- raise Error::new(errno, Error::err(errno))
+ errno = Error::CR_SERVER_LOST
+ raise Error::new(errno, Error::err(errno))
end
def close()
@@ -1091,6 +1161,13 @@ class << Mysql
end
alias :connect :real_connect
+ def finalizer(net)
+ proc {
+ net.clear
+ net.write Mysql::COM_QUIT.chr
+ }
+ end
+
def escape_string(str)
str.gsub(/([\0\n\r\032\'\"\\])/) do
case $1
diff --git a/activerecord/lib/active_record/vendor/mysql411.rb b/activerecord/lib/active_record/vendor/mysql411.rb
deleted file mode 100644
index 5bdd09c9d0..0000000000
--- a/activerecord/lib/active_record/vendor/mysql411.rb
+++ /dev/null
@@ -1,311 +0,0 @@
-#
-# mysq411.rb - 0.1 - Matt Mower <self@mattmower.com>
-#
-# The native Ruby MySQL client (mysql.rb) by Tomita Masahiro does not (yet) handle the new MySQL
-# protocol introduced in MySQL 4.1.1. This protocol introduces a new authentication scheme as
-# well as modifications to the client/server exchanges themselves.
-#
-# mysql411.rb modifies the Mysql class to add MySQL 4.1.x support. It modifies the connection
-# algorithm to detect a 4.1.1 server and respond with the new authentication scheme, otherwise using
-# the original one. Similarly for the changes to packet structures and field definitions, etc...
-#
-# It redefines serveral methods which behave differently depending upon the server context. The
-# way I have implemented this is to alias the old method, create a new alternative method, and redefine
-# the original method as a selector which calls the appropriate method based upon the server version.
-# There may have been a neater way to do this.
-#
-# In general I've tried not to change the original code any more than necessary, i.e. even where I
-# redefine a method I have made the smallest number of changes possible, rather than rewriting from
-# scratch.
-#
-# *Caveat Lector* This code passes all current ActiveRecord unit tests however this is no guarantee that
-# full & correct MySQL 4.1 support has been achieved.
-#
-
-require 'digest/sha1'
-
-#
-# Extend the Mysql class to work with MySQL 4.1.1+ servers. After version
-# 4.1.1 the password hashing function (and some other connection details) have
-# changed rendering the previous Mysql class unable to connect:
-#
-#
-
-class Mysql
- CLIENT_PROTOCOL_41 = 512
- CLIENT_SECURE_CONNECTION = 32768
-
- def real_connect( host=nil, user=nil, passwd=nil, db=nil, port=nil, socket=nil, flag=nil )
- @server_status = SERVER_STATUS_AUTOCOMMIT
-
- if( host == nil || host == "localhost" ) && defined? UNIXSocket
- unix_socket = socket || ENV["MYSQL_UNIX_PORT"] || MYSQL_UNIX_ADDR
- sock = UNIXSocket::new( unix_socket )
- @host_info = Error::err( Error::CR_LOCALHOST_CONNECTION )
- @unix_socket = unix_socket
- else
- sock = TCPSocket::new(host, port||ENV["MYSQL_TCP_PORT"]||(Socket::getservbyname("mysql","tcp") rescue MYSQL_PORT))
- @host_info = sprintf Error::err(Error::CR_TCP_CONNECTION), host
- end
-
- @host = host ? host.dup : nil
- sock.setsockopt Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true
- @net = Net::new sock
-
- a = read
-
- @protocol_version = a.slice!(0)
- @server_version, a = a.split(/\0/,2)
-
- # Store the version number components for speedy comparison
- version, ostag = @server_version.split( /-/, 2 )
- @use_411 = (version.strip >= '4.1.1')
-
- @thread_id, @scramble_buff = a.slice!(0,13).unpack("La8")
- if a.size >= 2 then
- @server_capabilities, = a.slice!(0,2).unpack("v")
- end
- if a.size >= 16 then
- @server_language, @server_status = a.unpack("cv")
- end
-
- # Set the flags we'll send back to the server
- flag = 0 if flag == nil
- flag |= @client_flag | CLIENT_CAPABILITIES
- flag |= CLIENT_CONNECT_WITH_DB if db
-
- if @use_411
- # In 4.1.1+ the seed comes in two parts which must be combined
- a.slice!( 0, 16 )
- seed_part_2 = a.slice!( 0, 12 );
- @scramble_buff << seed_part_2
-
- flag |= CLIENT_FOUND_ROWS
- flag |= CLIENT_PROTOCOL_41
- flag |= CLIENT_SECURE_CONNECTION if @server_capabilities & CLIENT_SECURE_CONNECTION;
-
- if db && @server_capabilities & CLIENT_CONNECT_WITH_DB != 0
- @db = db.dup
- end
-
- scrambled_password = scramble411( passwd, @scramble_buff, @protocol_version==9 )
- data = make_client_auth_packet_41( flag, user, scrambled_password, db )
- else
- scrambled_password = scramble( passwd, @scramble_buff, @protocol_version == 9 )
- data = Net::int2str(flag)+Net::int3str(@max_allowed_packet)+(user||"")+"\0"+scrambled_password
- if db and @server_capabilities & CLIENT_CONNECT_WITH_DB != 0 then
- data << "\0"+db
- @db = db.dup
- end
- end
-
- write data
- read
- self
- end
- alias :connect :real_connect
-
- # Pack the authentication information into depending upon whether an initial database has
- # been specified
- def make_client_auth_packet_41( flag, user, password, db )
- if db && @server_capabilities & CLIENT_CONNECT_WITH_DB != 0
- template = "VVcx23a#{user.size+1}cA#{password.size}a#{db.size+1}"
- else
- template = "VVcx23a#{user.size+1}cA#{password.size}x"
- end
-
- [ flag, @max_allowed_packet, @server_language, user, password.size, password, db ].pack( template )
- end
-
- # SERVER: public_seed=create_random_string()
- # send(public_seed)
- #
- # CLIENT: recv(public_seed)
- # hash_stage1=sha1("password")
- # hash_stage2=sha1(hash_stage1)
- # reply=xor(hash_stage1, sha1(public_seed,hash_stage2)
- #
- # #this three steps are done in scramble()
- #
- # send(reply)
- #
- #
- # SERVER: recv(reply)
- # hash_stage1=xor(reply, sha1(public_seed,hash_stage2))
- # candidate_hash2=sha1(hash_stage1)
- # check(candidate_hash2==hash_stage2)
- def scramble411( password, seed, old_ver )
- return "" if password == nil or password == ""
- raise "old version password is not implemented" if old_ver
-
- # print "Seed Bytes = "
- # seed.each_byte { |b| print "0x#{b.to_s( 16 )}, " }
- # puts
-
- stage1 = Digest::SHA1.digest( password )
- stage2 = Digest::SHA1.digest( stage1 )
-
- dgst = Digest::SHA1.new
- dgst << seed
- dgst << stage2
- stage3 = dgst.digest
-
- # stage1.zip( stage3 ).map { |a, b| (a ^ b).chr }.join
- scrambled = ( 0 ... stage3.size ).map { |i| stage3[i] ^ stage1[i] }
- scrambled = scrambled.map { |x| x.chr }
- scrambled.join
- end
-
- def change_user(user="", passwd="", db="")
- scrambled_password = @use_411 ? scramble411( passwd, @scramble_buff, @protocol_version==9 ) : scramble( passwd, @scramble_buff, @protocol_version==9 )
- data = user+"\0"+scrambled_password+"\0"+db
- command COM_CHANGE_USER, data
- @user = user
- @passwd = passwd
- @db = db
- end
-
- #
- # The 4.1 protocol changed the length of the END packet
- #
- alias_method :old_read_one_row, :read_one_row
-
- def read_one_row( field_count )
- if @use_411
- read_one_row_41( field_count )
- else
- old_read_one_row( field_count )
- end
- end
-
- def read_one_row_41( field_count )
- data = read
- return if data[0] == 254 and data.length < 9
- rec = []
- field_count.times do
- len = get_length data
- if len == nil then
- rec << len
- else
- rec << data.slice!(0,len)
- end
- end
- rec
- end
-
- #
- # The 4.1 protocol changed the length of the END packet
- #
- alias_method :old_skip_result, :skip_result
-
- def skip_result
- if @use_411
- skip_result_41
- else
- old_skip_result
- end
- end
-
- def skip_result_41()
- if @status == :STATUS_USE_RESULT then
- loop do
- data = read
- break if data[0] == 254 and data.length == 1
- end
- @status = :STATUS_READY
- end
- end
-
- # The field description structure is changed for the 4.1 protocol passing
- # more data and a different packing form. NOTE: The 4.1 protocol now passes
- # back a "catalog" name for each field which is a new feature. Since AR has
- # nowhere to put it I'm throwing it away. Possibly this is not the best
- # idea?
- #
- alias_method :old_unpack_fields, :unpack_fields
-
- def unpack_fields( data, long_flag_protocol )
- if @use_411
- unpack_fields_41( data, long_flag_protocol )
- else
- old_unpack_fields( data, long_flag_protocol )
- end
- end
-
- def unpack_fields_41( data, long_flag_protocol )
- ret = []
-
- data.each do |f|
- catalog_name = f[0]
- database_name = f[1]
- table_name_alias = f[2]
- table_name = f[3]
- column_name_alias = f[4]
- column_name = f[5]
-
- charset = f[6][0] + f[6][1]*256
- length = f[6][2] + f[6][3]*256 + f[6][4]*256*256 + f[6][5]*256*256*256
- type = f[6][6]
- flags = f[6][7] + f[6][8]*256
- decimals = f[6][9]
- def_value = f[7]
- max_length = 0
-
- ret << Field::new(table_name, table_name, column_name_alias, length, type, flags, decimals, def_value, max_length)
- end
- ret
- end
-
- # In this instance the read_query_result method in mysql is bound to read 5 field parameters which
- # is expanded to 7 in the 4.1 protocol. So in this case we redefine this entire method in order
- # to write "read_rows 7" instead of "read_rows 5"!
- #
- alias_method :old_read_query_result, :read_query_result
-
- def read_query_result
- if @use_411
- read_query_result_41
- else
- old_read_query_result
- end
- end
-
- def read_query_result_41
- data = read
- @field_count = get_length(data)
- if @field_count == nil then # LOAD DATA LOCAL INFILE
- File::open(data) do |f|
- write f.read
- end
- write "" # mark EOF
- data = read
- @field_count = get_length(data)
- end
- if @field_count == 0 then
- @affected_rows = get_length(data, true)
- @insert_id = get_length(data, true)
- if @server_capabilities & CLIENT_TRANSACTIONS != 0 then
- a = data.slice!(0,2)
- @server_status = a[0]+a[1]*256
- end
- if data.size > 0 and get_length(data) then
- @info = data
- end
- else
- @extra_info = get_length(data, true)
- fields = read_rows 7
- @fields = unpack_fields(fields, @server_capabilities & CLIENT_LONG_FLAG != 0)
- @status = :STATUS_GET_RESULT
- end
- self
- end
-
-
- # Get rid of GC.start in #free.
- class Result
- def free
- @handle.skip_result
- @handle = @fields = @data = nil
- end
- end
-end