aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/vendor
diff options
context:
space:
mode:
authorDavid Heinemeier Hansson <david@loudthinking.com>2004-11-24 01:04:44 +0000
committerDavid Heinemeier Hansson <david@loudthinking.com>2004-11-24 01:04:44 +0000
commitdb045dbbf60b53dbe013ef25554fd013baf88134 (patch)
tree257830e3c76458c8ff3d1329de83f32b23926028 /activerecord/lib/active_record/vendor
downloadrails-db045dbbf60b53dbe013ef25554fd013baf88134.tar.gz
rails-db045dbbf60b53dbe013ef25554fd013baf88134.tar.bz2
rails-db045dbbf60b53dbe013ef25554fd013baf88134.zip
Initial
git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@4 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
Diffstat (limited to 'activerecord/lib/active_record/vendor')
-rw-r--r--activerecord/lib/active_record/vendor/mysql.rb1117
-rw-r--r--activerecord/lib/active_record/vendor/simple.rb702
2 files changed, 1819 insertions, 0 deletions
diff --git a/activerecord/lib/active_record/vendor/mysql.rb b/activerecord/lib/active_record/vendor/mysql.rb
new file mode 100644
index 0000000000..4970f77bd3
--- /dev/null
+++ b/activerecord/lib/active_record/vendor/mysql.rb
@@ -0,0 +1,1117 @@
+# $Id: mysql.rb,v 1.1 2004/02/24 15:42:29 webster132 Exp $
+#
+# Copyright (C) 2003 TOMITA Masahiro
+# tommy@tmtm.org
+#
+
+class Mysql
+
+ VERSION = "4.0-ruby-0.2.4"
+
+ require "socket"
+
+ MAX_PACKET_LENGTH = 256*256*256-1
+ MAX_ALLOWED_PACKET = 1024*1024*1024
+
+ MYSQL_UNIX_ADDR = "/tmp/mysql.sock"
+ MYSQL_PORT = 3306
+ PROTOCOL_VERSION = 10
+
+ # Command
+ COM_SLEEP = 0
+ COM_QUIT = 1
+ COM_INIT_DB = 2
+ COM_QUERY = 3
+ COM_FIELD_LIST = 4
+ COM_CREATE_DB = 5
+ COM_DROP_DB = 6
+ COM_REFRESH = 7
+ COM_SHUTDOWN = 8
+ COM_STATISTICS = 9
+ COM_PROCESS_INFO = 10
+ COM_CONNECT = 11
+ COM_PROCESS_KILL = 12
+ COM_DEBUG = 13
+ COM_PING = 14
+ COM_TIME = 15
+ COM_DELAYED_INSERT = 16
+ COM_CHANGE_USER = 17
+ COM_BINLOG_DUMP = 18
+ COM_TABLE_DUMP = 19
+ COM_CONNECT_OUT = 20
+ COM_REGISTER_SLAVE = 21
+
+ # Client flag
+ CLIENT_LONG_PASSWORD = 1
+ CLIENT_FOUND_ROWS = 1 << 1
+ CLIENT_LONG_FLAG = 1 << 2
+ CLIENT_CONNECT_WITH_DB= 1 << 3
+ CLIENT_NO_SCHEMA = 1 << 4
+ CLIENT_COMPRESS = 1 << 5
+ CLIENT_ODBC = 1 << 6
+ CLIENT_LOCAL_FILES = 1 << 7
+ CLIENT_IGNORE_SPACE = 1 << 8
+ CLIENT_INTERACTIVE = 1 << 10
+ CLIENT_SSL = 1 << 11
+ CLIENT_IGNORE_SIGPIPE = 1 << 12
+ CLIENT_TRANSACTIONS = 1 << 13
+ CLIENT_CAPABILITIES = CLIENT_LONG_PASSWORD|CLIENT_LONG_FLAG|CLIENT_TRANSACTIONS
+
+ # Connection Option
+ OPT_CONNECT_TIMEOUT = 0
+ OPT_COMPRESS = 1
+ OPT_NAMED_PIPE = 2
+ INIT_COMMAND = 3
+ READ_DEFAULT_FILE = 4
+ READ_DEFAULT_GROUP = 5
+ SET_CHARSET_DIR = 6
+ SET_CHARSET_NAME = 7
+ OPT_LOCAL_INFILE = 8
+
+ # Server Status
+ SERVER_STATUS_IN_TRANS = 1
+ SERVER_STATUS_AUTOCOMMIT = 2
+
+ # Refresh parameter
+ REFRESH_GRANT = 1
+ REFRESH_LOG = 2
+ REFRESH_TABLES = 4
+ REFRESH_HOSTS = 8
+ REFRESH_STATUS = 16
+ REFRESH_THREADS = 32
+ REFRESH_SLAVE = 64
+ REFRESH_MASTER = 128
+
+ def initialize(*args)
+ @client_flag = 0
+ @max_allowed_packet = MAX_ALLOWED_PACKET
+ @query_with_result = true
+ @status = :STATUS_READY
+ if args[0] != :INIT then
+ real_connect(*args)
+ end
+ end
+
+ 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 or host == "localhost") and defined? UNIXSocket then
+ 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)
+ @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
+
+ 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 db and @server_capabilities & CLIENT_CONNECT_WITH_DB != 0 then
+ data << "\0"+db
+ @db = db.dup
+ end
+ write data
+ read
+ self
+ end
+ alias :connect :real_connect
+
+ def escape_string(str)
+ Mysql::escape_string str
+ end
+ alias :quote :escape_string
+
+ def get_client_info()
+ VERSION
+ end
+ alias :client_info :get_client_info
+
+ def options(option, arg=nil)
+ if option == OPT_LOCAL_INFILE then
+ if arg == false or arg == 0 then
+ @client_flag &= ~CLIENT_LOCAL_FILES
+ else
+ @client_flag |= CLIENT_LOCAL_FILES
+ end
+ else
+ raise "not implemented"
+ end
+ end
+
+ def real_query(query)
+ command COM_QUERY, query, true
+ read_query_result
+ self
+ end
+
+ def use_result()
+ if @status != :STATUS_GET_RESULT then
+ error Error::CR_COMMANDS_OUT_OF_SYNC
+ end
+ res = Result::new self, @fields, @field_count
+ @status = :STATUS_USE_RESULT
+ res
+ end
+
+ def store_result()
+ if @status != :STATUS_GET_RESULT then
+ error Error::CR_COMMANDS_OUT_OF_SYNC
+ end
+ @status = :STATUS_READY
+ data = read_rows @field_count
+ res = Result::new self, @fields, @field_count, data
+ @fields = nil
+ @affected_rows = data.length
+ res
+ end
+
+ def change_user(user="", passwd="", db="")
+ data = user+"\0"+scramble(passwd, @scramble_buff, @protocol_version==9)+"\0"+db
+ command COM_CHANGE_USER, data
+ @user = user
+ @passwd = passwd
+ @db = db
+ end
+
+ def character_set_name()
+ raise "not implemented"
+ end
+
+ def close()
+ @status = :STATUS_READY
+ command COM_QUIT, nil, true
+ @net.close
+ self
+ end
+
+ def create_db(db)
+ command COM_CREATE_DB, db
+ self
+ end
+
+ def drop_db(db)
+ command COM_DROP_DB, db
+ self
+ end
+
+ def dump_debug_info()
+ command COM_DEBUG
+ self
+ end
+
+ def get_host_info()
+ @host_info
+ end
+ alias :host_info :get_host_info
+
+ def get_proto_info()
+ @protocol_version
+ end
+ alias :proto_info :get_proto_info
+
+ def get_server_info()
+ @server_version
+ end
+ alias :server_info :get_server_info
+
+ def kill(id)
+ command COM_PROCESS_KILL, Net::int4str(id)
+ self
+ end
+
+ def list_dbs(db=nil)
+ real_query "show databases #{db}"
+ @status = :STATUS_READY
+ read_rows(1).flatten
+ end
+
+ def list_fields(table, field=nil)
+ command COM_FIELD_LIST, "#{table}\0#{field}", true
+ f = read_rows 6
+ fields = unpack_fields(f, @server_capabilities & CLIENT_LONG_FLAG != 0)
+ res = Result::new self, fields, f.length
+ res.eof = true
+ res
+ end
+
+ def list_processes()
+ data = command COM_PROCESS_INFO
+ @field_count = get_length data
+ fields = read_rows 5
+ @fields = unpack_fields(fields, @server_capabilities & CLIENT_LONG_FLAG != 0)
+ @status = :STATUS_GET_RESULT
+ store_result
+ end
+
+ def list_tables(table=nil)
+ real_query "show tables #{table}"
+ @status = :STATUS_READY
+ read_rows(1).flatten
+ end
+
+ def ping()
+ command COM_PING
+ self
+ end
+
+ def query(query)
+ real_query query
+ if not @query_with_result then
+ return self
+ end
+ if @field_count == 0 then
+ return nil
+ end
+ store_result
+ end
+
+ def refresh(r)
+ command COM_REFRESH, r.chr
+ self
+ end
+
+ def reload()
+ refresh REFRESH_GRANT
+ self
+ end
+
+ def select_db(db)
+ command COM_INIT_DB, db
+ @db = db
+ self
+ end
+
+ def shutdown()
+ command COM_SHUTDOWN
+ self
+ end
+
+ def stat()
+ command COM_STATISTICS
+ end
+
+ attr_reader :info, :insert_id, :affected_rows, :field_count, :thread_id
+ attr_accessor :query_with_result, :status
+
+ def read_one_row(field_count)
+ data = read
+ return if data[0] == 254 and data.length == 1
+ 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
+
+ def skip_result()
+ 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
+
+ def inspect()
+ "#<#{self.class}>"
+ end
+
+ private
+
+ def read_query_result()
+ 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 5
+ @fields = unpack_fields(fields, @server_capabilities & CLIENT_LONG_FLAG != 0)
+ @status = :STATUS_GET_RESULT
+ end
+ self
+ end
+
+ def unpack_fields(data, long_flag_protocol)
+ ret = []
+ data.each do |f|
+ table = org_table = f[0]
+ name = f[1]
+ length = f[2][0]+f[2][1]*256+f[2][2]*256*256
+ type = f[3][0]
+ if long_flag_protocol then
+ flags = f[4][0]+f[4][1]*256
+ decimals = f[4][2]
+ else
+ flags = f[4][0]
+ decimals = f[4][1]
+ end
+ def_value = f[5]
+ max_length = 0
+ ret << Field::new(table, org_table, name, length, type, flags, decimals, def_value, max_length)
+ end
+ ret
+ end
+
+ def read_rows(field_count)
+ ret = []
+ while rec = read_one_row(field_count) do
+ ret << rec
+ end
+ ret
+ end
+
+ def get_length(data, longlong=nil)
+ return if data.length == 0
+ c = data.slice!(0)
+ case c
+ when 251
+ return nil
+ when 252
+ a = data.slice!(0,2)
+ return a[0]+a[1]*256
+ when 253
+ a = data.slice!(0,3)
+ return a[0]+a[1]*256+a[2]*256**2
+ when 254
+ a = data.slice!(0,8)
+ if longlong then
+ return a[0]+a[1]*256+a[2]*256**2+a[3]*256**3+
+ a[4]*256**4+a[5]*256**5+a[6]*256**6+a[7]*256**7
+ else
+ return a[0]+a[1]*256+a[2]*256**2+a[3]*256**3
+ end
+ else
+ c
+ end
+ end
+
+ def command(cmd, arg=nil, skip_check=nil)
+ unless @net then
+ error Error::CR_SERVER_GONE_ERROR
+ end
+ if @status != :STATUS_READY then
+ error Error::CR_COMMANDS_OUT_OF_SYNC
+ end
+ @net.clear
+ write cmd.chr+(arg||"")
+ read unless skip_check
+ end
+
+ def read()
+ unless @net then
+ error Error::CR_SERVER_GONE_ERROR
+ end
+ a = @net.read
+ if a[0] == 255 then
+ if a.length > 3 then
+ @errno = a[1]+a[2]*256
+ @error = a[3 .. -1]
+ else
+ @errno = Error::CR_UNKNOWN_ERROR
+ @error = Error::err @errno
+ end
+ raise Error::new(@errno, @error)
+ end
+ a
+ end
+
+ def write(arg)
+ unless @net then
+ error Error::CR_SERVER_GONE_ERROR
+ end
+ @net.write arg
+ end
+
+ def hash_password(password)
+ nr = 1345345333
+ add = 7
+ nr2 = 0x12345671
+ password.each_byte do |i|
+ next if i == 0x20 or i == 9
+ nr ^= (((nr & 63) + add) * i) + (nr << 8)
+ nr2 += (nr2 << 8) ^ nr
+ add += i
+ end
+ [nr & ((1 << 31) - 1), nr2 & ((1 << 31) - 1)]
+ end
+
+ def scramble(password, message, old_ver)
+ return "" if password == nil or password == ""
+ raise "old version password is not implemented" if old_ver
+ hash_pass = hash_password password
+ hash_message = hash_password message
+ rnd = Random::new hash_pass[0] ^ hash_message[0], hash_pass[1] ^ hash_message[1]
+ to = []
+ 1.upto(message.length) do
+ to << ((rnd.rnd*31)+64).floor
+ end
+ extra = (rnd.rnd*31).floor
+ to.map! do |t| (t ^ extra).chr end
+ to.join
+ end
+
+ def error(errno)
+ @errno = errno
+ @error = Error::err errno
+ raise Error::new(@errno, @error)
+ end
+
+ class Result
+ def initialize(mysql, fields, field_count, data=nil)
+ @handle = mysql
+ @fields = fields
+ @field_count = field_count
+ @data = data
+ @current_field = 0
+ @current_row = 0
+ @eof = false
+ @row_count = 0
+ end
+ attr_accessor :eof
+
+ def data_seek(n)
+ @current_row = n
+ end
+
+ def fetch_field()
+ return if @current_field >= @field_count
+ f = @fields[@current_field]
+ @current_field += 1
+ f
+ end
+
+ def fetch_fields()
+ @fields
+ end
+
+ def fetch_field_direct(n)
+ @fields[n]
+ end
+
+ def fetch_lengths()
+ @data ? @data[@current_row].map{|i| i ? i.length : 0} : @lengths
+ end
+
+ def fetch_row()
+ if @data then
+ if @current_row >= @data.length then
+ @handle.status = :STATUS_READY
+ return
+ end
+ ret = @data[@current_row]
+ @current_row += 1
+ else
+ return if @eof
+ ret = @handle.read_one_row @field_count
+ if ret == nil then
+ @eof = true
+ return
+ end
+ @lengths = ret.map{|i| i ? i.length : 0}
+ @row_count += 1
+ end
+ ret
+ end
+
+ def fetch_hash(with_table=nil)
+ row = fetch_row
+ return if row == nil
+ hash = {}
+ @fields.each_index do |i|
+ f = with_table ? @fields[i].table+"."+@fields[i].name : @fields[i].name
+ hash[f] = row[i]
+ end
+ hash
+ end
+
+ def field_seek(n)
+ @current_field = n
+ end
+
+ def field_tell()
+ @current_field
+ end
+
+ def free()
+ @handle.skip_result
+ @handle = @fields = @data = nil
+ GC::start
+ end
+
+ def num_fields()
+ @field_count
+ end
+
+ def num_rows()
+ @data ? @data.length : @row_count
+ end
+
+ def row_seek(n)
+ @current_row = n
+ end
+
+ def row_tell()
+ @current_row
+ end
+
+ def each()
+ while row = fetch_row do
+ yield row
+ end
+ end
+
+ def each_hash(with_table=nil)
+ while hash = fetch_hash(with_table) do
+ yield hash
+ end
+ end
+
+ def inspect()
+ "#<#{self.class}>"
+ end
+
+ end
+
+ class Field
+ # Field type
+ TYPE_DECIMAL = 0
+ TYPE_TINY = 1
+ TYPE_SHORT = 2
+ TYPE_LONG = 3
+ TYPE_FLOAT = 4
+ TYPE_DOUBLE = 5
+ TYPE_NULL = 6
+ TYPE_TIMESTAMP = 7
+ TYPE_LONGLONG = 8
+ TYPE_INT24 = 9
+ TYPE_DATE = 10
+ TYPE_TIME = 11
+ TYPE_DATETIME = 12
+ TYPE_YEAR = 13
+ TYPE_NEWDATE = 14
+ TYPE_ENUM = 247
+ TYPE_SET = 248
+ TYPE_TINY_BLOB = 249
+ TYPE_MEDIUM_BLOB = 250
+ TYPE_LONG_BLOB = 251
+ TYPE_BLOB = 252
+ TYPE_VAR_STRING = 253
+ TYPE_STRING = 254
+ TYPE_GEOMETRY = 255
+ TYPE_CHAR = TYPE_TINY
+ TYPE_INTERVAL = TYPE_ENUM
+
+ # Flag
+ NOT_NULL_FLAG = 1
+ PRI_KEY_FLAG = 2
+ UNIQUE_KEY_FLAG = 4
+ MULTIPLE_KEY_FLAG = 8
+ BLOB_FLAG = 16
+ UNSIGNED_FLAG = 32
+ ZEROFILL_FLAG = 64
+ BINARY_FLAG = 128
+ ENUM_FLAG = 256
+ AUTO_INCREMENT_FLAG = 512
+ TIMESTAMP_FLAG = 1024
+ SET_FLAG = 2048
+ NUM_FLAG = 32768
+ PART_KEY_FLAG = 16384
+ GROUP_FLAG = 32768
+ UNIQUE_FLAG = 65536
+
+ def initialize(table, org_table, name, length, type, flags, decimals, def_value, max_length)
+ @table = table
+ @org_table = org_table
+ @name = name
+ @length = length
+ @type = type
+ @flags = flags
+ @decimals = decimals
+ @def = def_value
+ @max_length = max_length
+ if (type <= TYPE_INT24 and (type != TYPE_TIMESTAMP or length == 14 or length == 8)) or type == TYPE_YEAR then
+ @flags |= NUM_FLAG
+ end
+ end
+ attr_reader :table, :org_table, :name, :length, :type, :flags, :decimals, :def, :max_length
+
+ def inspect()
+ "#<#{self.class}:#{@name}>"
+ end
+ end
+
+ class Error < StandardError
+ # Server Error
+ ER_HASHCHK = 1000
+ ER_NISAMCHK = 1001
+ ER_NO = 1002
+ ER_YES = 1003
+ ER_CANT_CREATE_FILE = 1004
+ ER_CANT_CREATE_TABLE = 1005
+ ER_CANT_CREATE_DB = 1006
+ ER_DB_CREATE_EXISTS = 1007
+ ER_DB_DROP_EXISTS = 1008
+ ER_DB_DROP_DELETE = 1009
+ ER_DB_DROP_RMDIR = 1010
+ ER_CANT_DELETE_FILE = 1011
+ ER_CANT_FIND_SYSTEM_REC = 1012
+ ER_CANT_GET_STAT = 1013
+ ER_CANT_GET_WD = 1014
+ ER_CANT_LOCK = 1015
+ ER_CANT_OPEN_FILE = 1016
+ ER_FILE_NOT_FOUND = 1017
+ ER_CANT_READ_DIR = 1018
+ ER_CANT_SET_WD = 1019
+ ER_CHECKREAD = 1020
+ ER_DISK_FULL = 1021
+ ER_DUP_KEY = 1022
+ ER_ERROR_ON_CLOSE = 1023
+ ER_ERROR_ON_READ = 1024
+ ER_ERROR_ON_RENAME = 1025
+ ER_ERROR_ON_WRITE = 1026
+ ER_FILE_USED = 1027
+ ER_FILSORT_ABORT = 1028
+ ER_FORM_NOT_FOUND = 1029
+ ER_GET_ERRNO = 1030
+ ER_ILLEGAL_HA = 1031
+ ER_KEY_NOT_FOUND = 1032
+ ER_NOT_FORM_FILE = 1033
+ ER_NOT_KEYFILE = 1034
+ ER_OLD_KEYFILE = 1035
+ ER_OPEN_AS_READONLY = 1036
+ ER_OUTOFMEMORY = 1037
+ ER_OUT_OF_SORTMEMORY = 1038
+ ER_UNEXPECTED_EOF = 1039
+ ER_CON_COUNT_ERROR = 1040
+ ER_OUT_OF_RESOURCES = 1041
+ ER_BAD_HOST_ERROR = 1042
+ ER_HANDSHAKE_ERROR = 1043
+ ER_DBACCESS_DENIED_ERROR = 1044
+ ER_ACCESS_DENIED_ERROR = 1045
+ ER_NO_DB_ERROR = 1046
+ ER_UNKNOWN_COM_ERROR = 1047
+ ER_BAD_NULL_ERROR = 1048
+ ER_BAD_DB_ERROR = 1049
+ ER_TABLE_EXISTS_ERROR = 1050
+ ER_BAD_TABLE_ERROR = 1051
+ ER_NON_UNIQ_ERROR = 1052
+ ER_SERVER_SHUTDOWN = 1053
+ ER_BAD_FIELD_ERROR = 1054
+ ER_WRONG_FIELD_WITH_GROUP = 1055
+ ER_WRONG_GROUP_FIELD = 1056
+ ER_WRONG_SUM_SELECT = 1057
+ ER_WRONG_VALUE_COUNT = 1058
+ ER_TOO_LONG_IDENT = 1059
+ ER_DUP_FIELDNAME = 1060
+ ER_DUP_KEYNAME = 1061
+ ER_DUP_ENTRY = 1062
+ ER_WRONG_FIELD_SPEC = 1063
+ ER_PARSE_ERROR = 1064
+ ER_EMPTY_QUERY = 1065
+ ER_NONUNIQ_TABLE = 1066
+ ER_INVALID_DEFAULT = 1067
+ ER_MULTIPLE_PRI_KEY = 1068
+ ER_TOO_MANY_KEYS = 1069
+ ER_TOO_MANY_KEY_PARTS = 1070
+ ER_TOO_LONG_KEY = 1071
+ ER_KEY_COLUMN_DOES_NOT_EXITS = 1072
+ ER_BLOB_USED_AS_KEY = 1073
+ ER_TOO_BIG_FIELDLENGTH = 1074
+ ER_WRONG_AUTO_KEY = 1075
+ ER_READY = 1076
+ ER_NORMAL_SHUTDOWN = 1077
+ ER_GOT_SIGNAL = 1078
+ ER_SHUTDOWN_COMPLETE = 1079
+ ER_FORCING_CLOSE = 1080
+ ER_IPSOCK_ERROR = 1081
+ ER_NO_SUCH_INDEX = 1082
+ ER_WRONG_FIELD_TERMINATORS = 1083
+ ER_BLOBS_AND_NO_TERMINATED = 1084
+ ER_TEXTFILE_NOT_READABLE = 1085
+ ER_FILE_EXISTS_ERROR = 1086
+ ER_LOAD_INFO = 1087
+ ER_ALTER_INFO = 1088
+ ER_WRONG_SUB_KEY = 1089
+ ER_CANT_REMOVE_ALL_FIELDS = 1090
+ ER_CANT_DROP_FIELD_OR_KEY = 1091
+ ER_INSERT_INFO = 1092
+ ER_INSERT_TABLE_USED = 1093
+ ER_NO_SUCH_THREAD = 1094
+ ER_KILL_DENIED_ERROR = 1095
+ ER_NO_TABLES_USED = 1096
+ ER_TOO_BIG_SET = 1097
+ ER_NO_UNIQUE_LOGFILE = 1098
+ ER_TABLE_NOT_LOCKED_FOR_WRITE = 1099
+ ER_TABLE_NOT_LOCKED = 1100
+ ER_BLOB_CANT_HAVE_DEFAULT = 1101
+ ER_WRONG_DB_NAME = 1102
+ ER_WRONG_TABLE_NAME = 1103
+ ER_TOO_BIG_SELECT = 1104
+ ER_UNKNOWN_ERROR = 1105
+ ER_UNKNOWN_PROCEDURE = 1106
+ ER_WRONG_PARAMCOUNT_TO_PROCEDURE = 1107
+ ER_WRONG_PARAMETERS_TO_PROCEDURE = 1108
+ ER_UNKNOWN_TABLE = 1109
+ ER_FIELD_SPECIFIED_TWICE = 1110
+ ER_INVALID_GROUP_FUNC_USE = 1111
+ ER_UNSUPPORTED_EXTENSION = 1112
+ ER_TABLE_MUST_HAVE_COLUMNS = 1113
+ ER_RECORD_FILE_FULL = 1114
+ ER_UNKNOWN_CHARACTER_SET = 1115
+ ER_TOO_MANY_TABLES = 1116
+ ER_TOO_MANY_FIELDS = 1117
+ ER_TOO_BIG_ROWSIZE = 1118
+ ER_STACK_OVERRUN = 1119
+ ER_WRONG_OUTER_JOIN = 1120
+ ER_NULL_COLUMN_IN_INDEX = 1121
+ ER_CANT_FIND_UDF = 1122
+ ER_CANT_INITIALIZE_UDF = 1123
+ ER_UDF_NO_PATHS = 1124
+ ER_UDF_EXISTS = 1125
+ ER_CANT_OPEN_LIBRARY = 1126
+ ER_CANT_FIND_DL_ENTRY = 1127
+ ER_FUNCTION_NOT_DEFINED = 1128
+ ER_HOST_IS_BLOCKED = 1129
+ ER_HOST_NOT_PRIVILEGED = 1130
+ ER_PASSWORD_ANONYMOUS_USER = 1131
+ ER_PASSWORD_NOT_ALLOWED = 1132
+ ER_PASSWORD_NO_MATCH = 1133
+ ER_UPDATE_INFO = 1134
+ ER_CANT_CREATE_THREAD = 1135
+ ER_WRONG_VALUE_COUNT_ON_ROW = 1136
+ ER_CANT_REOPEN_TABLE = 1137
+ ER_INVALID_USE_OF_NULL = 1138
+ ER_REGEXP_ERROR = 1139
+ ER_MIX_OF_GROUP_FUNC_AND_FIELDS = 1140
+ ER_NONEXISTING_GRANT = 1141
+ ER_TABLEACCESS_DENIED_ERROR = 1142
+ ER_COLUMNACCESS_DENIED_ERROR = 1143
+ ER_ILLEGAL_GRANT_FOR_TABLE = 1144
+ ER_GRANT_WRONG_HOST_OR_USER = 1145
+ ER_NO_SUCH_TABLE = 1146
+ ER_NONEXISTING_TABLE_GRANT = 1147
+ ER_NOT_ALLOWED_COMMAND = 1148
+ ER_SYNTAX_ERROR = 1149
+ ER_DELAYED_CANT_CHANGE_LOCK = 1150
+ ER_TOO_MANY_DELAYED_THREADS = 1151
+ ER_ABORTING_CONNECTION = 1152
+ ER_NET_PACKET_TOO_LARGE = 1153
+ ER_NET_READ_ERROR_FROM_PIPE = 1154
+ ER_NET_FCNTL_ERROR = 1155
+ ER_NET_PACKETS_OUT_OF_ORDER = 1156
+ ER_NET_UNCOMPRESS_ERROR = 1157
+ ER_NET_READ_ERROR = 1158
+ ER_NET_READ_INTERRUPTED = 1159
+ ER_NET_ERROR_ON_WRITE = 1160
+ ER_NET_WRITE_INTERRUPTED = 1161
+ ER_TOO_LONG_STRING = 1162
+ ER_TABLE_CANT_HANDLE_BLOB = 1163
+ ER_TABLE_CANT_HANDLE_AUTO_INCREMENT = 1164
+ ER_DELAYED_INSERT_TABLE_LOCKED = 1165
+ ER_WRONG_COLUMN_NAME = 1166
+ ER_WRONG_KEY_COLUMN = 1167
+ ER_WRONG_MRG_TABLE = 1168
+ ER_DUP_UNIQUE = 1169
+ ER_BLOB_KEY_WITHOUT_LENGTH = 1170
+ ER_PRIMARY_CANT_HAVE_NULL = 1171
+ ER_TOO_MANY_ROWS = 1172
+ ER_REQUIRES_PRIMARY_KEY = 1173
+ ER_NO_RAID_COMPILED = 1174
+ ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE = 1175
+ ER_KEY_DOES_NOT_EXITS = 1176
+ ER_CHECK_NO_SUCH_TABLE = 1177
+ ER_CHECK_NOT_IMPLEMENTED = 1178
+ ER_CANT_DO_THIS_DURING_AN_TRANSACTION = 1179
+ ER_ERROR_DURING_COMMIT = 1180
+ ER_ERROR_DURING_ROLLBACK = 1181
+ ER_ERROR_DURING_FLUSH_LOGS = 1182
+ ER_ERROR_DURING_CHECKPOINT = 1183
+ ER_NEW_ABORTING_CONNECTION = 1184
+ ER_DUMP_NOT_IMPLEMENTED = 1185
+ ER_FLUSH_MASTER_BINLOG_CLOSED = 1186
+ ER_INDEX_REBUILD = 1187
+ ER_MASTER = 1188
+ ER_MASTER_NET_READ = 1189
+ ER_MASTER_NET_WRITE = 1190
+ ER_FT_MATCHING_KEY_NOT_FOUND = 1191
+ ER_LOCK_OR_ACTIVE_TRANSACTION = 1192
+ ER_UNKNOWN_SYSTEM_VARIABLE = 1193
+ ER_CRASHED_ON_USAGE = 1194
+ ER_CRASHED_ON_REPAIR = 1195
+ ER_WARNING_NOT_COMPLETE_ROLLBACK = 1196
+ ER_TRANS_CACHE_FULL = 1197
+ ER_SLAVE_MUST_STOP = 1198
+ ER_SLAVE_NOT_RUNNING = 1199
+ ER_BAD_SLAVE = 1200
+ ER_MASTER_INFO = 1201
+ ER_SLAVE_THREAD = 1202
+ ER_TOO_MANY_USER_CONNECTIONS = 1203
+ ER_SET_CONSTANTS_ONLY = 1204
+ ER_LOCK_WAIT_TIMEOUT = 1205
+ ER_LOCK_TABLE_FULL = 1206
+ ER_READ_ONLY_TRANSACTION = 1207
+ ER_DROP_DB_WITH_READ_LOCK = 1208
+ ER_CREATE_DB_WITH_READ_LOCK = 1209
+ ER_WRONG_ARGUMENTS = 1210
+ ER_NO_PERMISSION_TO_CREATE_USER = 1211
+ ER_UNION_TABLES_IN_DIFFERENT_DIR = 1212
+ ER_LOCK_DEADLOCK = 1213
+ ER_TABLE_CANT_HANDLE_FULLTEXT = 1214
+ ER_CANNOT_ADD_FOREIGN = 1215
+ ER_NO_REFERENCED_ROW = 1216
+ ER_ROW_IS_REFERENCED = 1217
+ ER_CONNECT_TO_MASTER = 1218
+ ER_QUERY_ON_MASTER = 1219
+ ER_ERROR_WHEN_EXECUTING_COMMAND = 1220
+ ER_WRONG_USAGE = 1221
+ ER_WRONG_NUMBER_OF_COLUMNS_IN_SELECT = 1222
+ ER_CANT_UPDATE_WITH_READLOCK = 1223
+ ER_MIXING_NOT_ALLOWED = 1224
+ ER_DUP_ARGUMENT = 1225
+ ER_USER_LIMIT_REACHED = 1226
+ ER_SPECIFIC_ACCESS_DENIED_ERROR = 1227
+ ER_LOCAL_VARIABLE = 1228
+ ER_GLOBAL_VARIABLE = 1229
+ ER_NO_DEFAULT = 1230
+ ER_WRONG_VALUE_FOR_VAR = 1231
+ ER_WRONG_TYPE_FOR_VAR = 1232
+ ER_VAR_CANT_BE_READ = 1233
+ ER_CANT_USE_OPTION_HERE = 1234
+ ER_NOT_SUPPORTED_YET = 1235
+ ER_MASTER_FATAL_ERROR_READING_BINLOG = 1236
+ ER_SLAVE_IGNORED_TABLE = 1237
+ ER_ERROR_MESSAGES = 238
+
+ # Client Error
+ CR_MIN_ERROR = 2000
+ CR_MAX_ERROR = 2999
+ CR_UNKNOWN_ERROR = 2000
+ CR_SOCKET_CREATE_ERROR = 2001
+ CR_CONNECTION_ERROR = 2002
+ CR_CONN_HOST_ERROR = 2003
+ CR_IPSOCK_ERROR = 2004
+ CR_UNKNOWN_HOST = 2005
+ CR_SERVER_GONE_ERROR = 2006
+ CR_VERSION_ERROR = 2007
+ CR_OUT_OF_MEMORY = 2008
+ CR_WRONG_HOST_INFO = 2009
+ CR_LOCALHOST_CONNECTION = 2010
+ CR_TCP_CONNECTION = 2011
+ CR_SERVER_HANDSHAKE_ERR = 2012
+ CR_SERVER_LOST = 2013
+ CR_COMMANDS_OUT_OF_SYNC = 2014
+ CR_NAMEDPIPE_CONNECTION = 2015
+ CR_NAMEDPIPEWAIT_ERROR = 2016
+ CR_NAMEDPIPEOPEN_ERROR = 2017
+ CR_NAMEDPIPESETSTATE_ERROR = 2018
+ CR_CANT_READ_CHARSET = 2019
+ CR_NET_PACKET_TOO_LARGE = 2020
+ CR_EMBEDDED_CONNECTION = 2021
+ CR_PROBE_SLAVE_STATUS = 2022
+ CR_PROBE_SLAVE_HOSTS = 2023
+ CR_PROBE_SLAVE_CONNECT = 2024
+ CR_PROBE_MASTER_CONNECT = 2025
+ CR_SSL_CONNECTION_ERROR = 2026
+ CR_MALFORMED_PACKET = 2027
+
+ CLIENT_ERRORS = [
+ "Unknown MySQL error",
+ "Can't create UNIX socket (%d)",
+ "Can't connect to local MySQL server through socket '%-.64s' (%d)",
+ "Can't connect to MySQL server on '%-.64s' (%d)",
+ "Can't create TCP/IP socket (%d)",
+ "Unknown MySQL Server Host '%-.64s' (%d)",
+ "MySQL server has gone away",
+ "Protocol mismatch. Server Version = %d Client Version = %d",
+ "MySQL client run out of memory",
+ "Wrong host info",
+ "Localhost via UNIX socket",
+ "%-.64s via TCP/IP",
+ "Error in server handshake",
+ "Lost connection to MySQL server during query",
+ "Commands out of sync; You can't run this command now",
+ "%-.64s via named pipe",
+ "Can't wait for named pipe to host: %-.64s pipe: %-.32s (%lu)",
+ "Can't open named pipe to host: %-.64s pipe: %-.32s (%lu)",
+ "Can't set state of named pipe to host: %-.64s pipe: %-.32s (%lu)",
+ "Can't initialize character set %-.64s (path: %-.64s)",
+ "Got packet bigger than 'max_allowed_packet'",
+ "Embedded server",
+ "Error on SHOW SLAVE STATUS:",
+ "Error on SHOW SLAVE HOSTS:",
+ "Error connecting to slave:",
+ "Error connecting to master:",
+ "SSL connection error",
+ "Malformed packet"
+ ]
+
+ def initialize(errno, error)
+ @errno = errno
+ @error = error
+ super error
+ end
+ attr_reader :errno, :error
+
+ def Error::err(errno)
+ CLIENT_ERRORS[errno - Error::CR_MIN_ERROR]
+ end
+ end
+
+ class Net
+ def initialize(sock)
+ @sock = sock
+ @pkt_nr = 0
+ end
+
+ def clear()
+ @pkt_nr = 0
+ end
+
+ def read()
+ buf = []
+ len = nil
+ @sock.sync = false
+ while len == nil or len == MAX_PACKET_LENGTH do
+ a = @sock.read(4)
+ len = a[0]+a[1]*256+a[2]*256*256
+ pkt_nr = a[3]
+ if @pkt_nr != pkt_nr then
+ raise "Packets out of order: #{@pkt_nr}<>#{pkt_nr}"
+ end
+ @pkt_nr = @pkt_nr + 1 & 0xff
+ buf << @sock.read(len)
+ end
+ @sock.sync = true
+ buf.join
+ end
+
+ def write(data)
+ if data.is_a? Array then
+ data = data.join
+ end
+ @sock.sync = false
+ ptr = 0
+ while data.length >= MAX_PACKET_LENGTH do
+ @sock.write Net::int3str(MAX_PACKET_LENGTH)+@pkt_nr.chr+data[ptr, MAX_PACKET_LENGTH]
+ @pkt_nr = @pkt_nr + 1 & 0xff
+ ptr += MAX_PACKET_LENGTH
+ end
+ @sock.write Net::int3str(data.length-ptr)+@pkt_nr.chr+data[ptr .. -1]
+ @pkt_nr = @pkt_nr + 1 & 0xff
+ @sock.sync = true
+ @sock.flush
+ end
+
+ def close()
+ @sock.close
+ end
+
+ def Net::int2str(n)
+ [n].pack("v")
+ end
+
+ def Net::int3str(n)
+ [n%256, n>>8].pack("cv")
+ end
+
+ def Net::int4str(n)
+ [n].pack("V")
+ end
+
+ end
+
+ class Random
+ def initialize(seed1, seed2)
+ @max_value = 0x3FFFFFFF
+ @seed1 = seed1 % @max_value
+ @seed2 = seed2 % @max_value
+ end
+
+ def rnd()
+ @seed1 = (@seed1*3+@seed2) % @max_value
+ @seed2 = (@seed1+@seed2+33) % @max_value
+ @seed1.to_f / @max_value
+ end
+ end
+
+end
+
+class << Mysql
+ def init()
+ Mysql::new :INIT
+ end
+
+ def real_connect(*args)
+ Mysql::new(*args)
+ end
+ alias :connect :real_connect
+
+ def escape_string(str)
+ str.gsub(/([\0\n\r\032\'\"\\])/) do
+ case $1
+ when "\0" then "\\0"
+ when "\n" then "\\n"
+ when "\r" then "\\r"
+ when "\032" then "\Z"
+ else "\\"+$1
+ end
+ end
+ end
+ alias :quote :escape_string
+
+ def get_client_info()
+ Mysql::VERSION
+ end
+ alias :client_info :get_client_info
+
+ def debug(str)
+ raise "not implemented"
+ end
+end
+
+#
+# for compatibility
+#
+
+MysqlRes = Mysql::Result
+MysqlField = Mysql::Field
+MysqlError = Mysql::Error
diff --git a/activerecord/lib/active_record/vendor/simple.rb b/activerecord/lib/active_record/vendor/simple.rb
new file mode 100644
index 0000000000..1bd332c882
--- /dev/null
+++ b/activerecord/lib/active_record/vendor/simple.rb
@@ -0,0 +1,702 @@
+# :title: Transaction::Simple
+#
+# == Licence
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+#--
+# Transaction::Simple
+# Simple object transaction support for Ruby
+# Version 1.11
+#
+# Copyright (c) 2003 Austin Ziegler
+#
+# $Id: simple.rb,v 1.2 2004/08/20 13:56:37 webster132 Exp $
+#
+# ==========================================================================
+# Revision History ::
+# YYYY.MM.DD Change ID Developer
+# Description
+# --------------------------------------------------------------------------
+# 2003.07.29 Austin Ziegler
+# Added debugging capabilities and VERSION string.
+# 2003.08.21 Austin Ziegler
+# Added named transactions.
+#
+# ==========================================================================
+#++
+require 'thread'
+
+ # The "Transaction" namespace can be used for additional transactional
+ # support objects and modules.
+module Transaction
+
+ # A standard exception for transactional errors.
+ class TransactionError < StandardError; end
+ # A standard exception for transactional errors involving the acquisition
+ # of locks for Transaction::Simple::ThreadSafe.
+ class TransactionThreadError < StandardError; end
+
+ # = Transaction::Simple for Ruby
+ # Simple object transaction support for Ruby
+ #
+ # == Introduction
+ #
+ # Transaction::Simple provides a generic way to add active transactional
+ # support to objects. The transaction methods added by this module will
+ # work with most objects, excluding those that cannot be <i>Marshal</i>ed
+ # (bindings, procedure objects, IO instances, or singleton objects).
+ #
+ # The transactions supported by Transaction::Simple are not backed
+ # transactions; that is, they have nothing to do with any sort of data
+ # store. They are "live" transactions occurring in memory and in the
+ # object itself. This is to allow "test" changes to be made to an object
+ # before making the changes permanent.
+ #
+ # Transaction::Simple can handle an "infinite" number of transactional
+ # levels (limited only by memory). If I open two transactions, commit the
+ # first, but abort the second, the object will revert to the original
+ # version.
+ #
+ # Transaction::Simple supports "named" transactions, so that multiple
+ # levels of transactions can be committed, aborted, or rewound by
+ # referring to the appropriate name of the transaction. Names may be any
+ # object *except* +nil+.
+ #
+ # Copyright:: Copyright © 2003 by Austin Ziegler
+ # Version:: 1.1
+ # Licence:: MIT-Style
+ #
+ # Thanks to David Black for help with the initial concept that led to this
+ # library.
+ #
+ # == Usage
+ # include 'transaction/simple'
+ #
+ # v = "Hello, you." # => "Hello, you."
+ # v.extend(Transaction::Simple) # => "Hello, you."
+ #
+ # v.start_transaction # => ... (a Marshal string)
+ # v.transaction_open? # => true
+ # v.gsub!(/you/, "world") # => "Hello, world."
+ #
+ # v.rewind_transaction # => "Hello, you."
+ # v.transaction_open? # => true
+ #
+ # v.gsub!(/you/, "HAL") # => "Hello, HAL."
+ # v.abort_transaction # => "Hello, you."
+ # v.transaction_open? # => false
+ #
+ # v.start_transaction # => ... (a Marshal string)
+ # v.start_transaction # => ... (a Marshal string)
+ #
+ # v.transaction_open? # => true
+ # v.gsub!(/you/, "HAL") # => "Hello, HAL."
+ #
+ # v.commit_transaction # => "Hello, HAL."
+ # v.transaction_open? # => true
+ # v.abort_transaction # => "Hello, you."
+ # v.transaction_open? # => false
+ #
+ # == Named Transaction Usage
+ # v = "Hello, you." # => "Hello, you."
+ # v.extend(Transaction::Simple) # => "Hello, you."
+ #
+ # v.start_transaction(:first) # => ... (a Marshal string)
+ # v.transaction_open? # => true
+ # v.transaction_open?(:first) # => true
+ # v.transaction_open?(:second) # => false
+ # v.gsub!(/you/, "world") # => "Hello, world."
+ #
+ # v.start_transaction(:second) # => ... (a Marshal string)
+ # v.gsub!(/world/, "HAL") # => "Hello, HAL."
+ # v.rewind_transaction(:first) # => "Hello, you."
+ # v.transaction_open? # => true
+ # v.transaction_open?(:first) # => true
+ # v.transaction_open?(:second) # => false
+ #
+ # v.gsub!(/you/, "world") # => "Hello, world."
+ # v.start_transaction(:second) # => ... (a Marshal string)
+ # v.gsub!(/world/, "HAL") # => "Hello, HAL."
+ # v.transaction_name # => :second
+ # v.abort_transaction(:first) # => "Hello, you."
+ # v.transaction_open? # => false
+ #
+ # v.start_transaction(:first) # => ... (a Marshal string)
+ # v.gsub!(/you/, "world") # => "Hello, world."
+ # v.start_transaction(:second) # => ... (a Marshal string)
+ # v.gsub!(/world/, "HAL") # => "Hello, HAL."
+ #
+ # v.commit_transaction(:first) # => "Hello, HAL."
+ # v.transaction_open? # => false
+ #
+ # == Contraindications
+ #
+ # While Transaction::Simple is very useful, it has some severe limitations
+ # that must be understood. Transaction::Simple:
+ #
+ # * uses Marshal. Thus, any object which cannot be <i>Marshal</i>ed cannot
+ # use Transaction::Simple.
+ # * does not manage resources. Resources external to the object and its
+ # instance variables are not managed at all. However, all instance
+ # variables and objects "belonging" to those instance variables are
+ # managed. If there are object reference counts to be handled,
+ # Transaction::Simple will probably cause problems.
+ # * is not inherently thread-safe. In the ACID ("atomic, consistent,
+ # isolated, durable") test, Transaction::Simple provides CD, but it is
+ # up to the user of Transaction::Simple to provide isolation and
+ # atomicity. Transactions should be considered "critical sections" in
+ # multi-threaded applications. If thread safety and atomicity is
+ # absolutely required, use Transaction::Simple::ThreadSafe, which uses a
+ # Mutex object to synchronize the accesses on the object during the
+ # transactional operations.
+ # * does not necessarily maintain Object#__id__ values on rewind or abort.
+ # This may change for future versions that will be Ruby 1.8 or better
+ # *only*. Certain objects that support #replace will maintain
+ # Object#__id__.
+ # * Can be a memory hog if you use many levels of transactions on many
+ # objects.
+ #
+ module Simple
+ VERSION = '1.1.1.0';
+
+ # Sets the Transaction::Simple debug object. It must respond to #<<.
+ # Sets the transaction debug object. Debugging will be performed
+ # automatically if there's a debug object. The generic transaction error
+ # class.
+ def self.debug_io=(io)
+ raise TransactionError, "Transaction Error: the transaction debug object must respond to #<<" unless io.respond_to?(:<<)
+ @tdi = io
+ end
+
+ # Returns the Transaction::Simple debug object. It must respond to #<<.
+ def self.debug_io
+ @tdi
+ end
+
+ # If +name+ is +nil+ (default), then returns +true+ if there is
+ # currently a transaction open.
+ #
+ # If +name+ is specified, then returns +true+ if there is currently a
+ # transaction that responds to +name+ open.
+ def transaction_open?(name = nil)
+ if name.nil?
+ Transaction::Simple.debug_io << "Transaction [#{(@__transaction_checkpoint__.nil?) ? 'closed' : 'open'}]\n" unless Transaction::Simple.debug_io.nil?
+ return (not @__transaction_checkpoint__.nil?)
+ else
+ Transaction::Simple.debug_io << "Transaction(#{name.inspect}) [#{(@__transaction_checkpoint__.nil?) ? 'closed' : 'open'}]\n" unless Transaction::Simple.debug_io.nil?
+ return ((not @__transaction_checkpoint__.nil?) and @__transaction_names__.include?(name))
+ end
+ end
+
+ # Returns the current name of the transaction. Transactions not
+ # explicitly named are named +nil+.
+ def transaction_name
+ raise TransactionError, "Transaction Error: No transaction open." if @__transaction_checkpoint__.nil?
+ Transaction::Simple.debug_io << "#{'|' * @__transaction_level__} Transaction Name: #{@__transaction_names__[-1].inspect}\n" unless Transaction::Simple.debug_io.nil?
+ @__transaction_names__[-1]
+ end
+
+ # Starts a transaction. Stores the current object state. If a
+ # transaction name is specified, the transaction will be named.
+ # Transaction names must be unique. Transaction names of +nil+ will be
+ # treated as unnamed transactions.
+ def start_transaction(name = nil)
+ @__transaction_level__ ||= 0
+ @__transaction_names__ ||= []
+
+ if name.nil?
+ @__transaction_names__ << nil
+ s = ""
+ else
+ raise TransactionError, "Transaction Error: Named transactions must be unique." if @__transaction_names__.include?(name)
+ @__transaction_names__ << name
+ s = "(#{name.inspect})"
+ end
+
+ @__transaction_level__ += 1
+
+ Transaction::Simple.debug_io << "#{'>' * @__transaction_level__} Start Transaction#{s}\n" unless Transaction::Simple.debug_io.nil?
+
+ @__transaction_checkpoint__ = Marshal.dump(self)
+ end
+
+ # Rewinds the transaction. If +name+ is specified, then the intervening
+ # transactions will be aborted and the named transaction will be
+ # rewound. Otherwise, only the current transaction is rewound.
+ def rewind_transaction(name = nil)
+ raise TransactionError, "Transaction Error: Cannot rewind. There is no current transaction." if @__transaction_checkpoint__.nil?
+ if name.nil?
+ __rewind_this_transaction
+ s = ""
+ else
+ raise TransactionError, "Transaction Error: Cannot rewind to transaction #{name.inspect} because it does not exist." unless @__transaction_names__.include?(name)
+ s = "(#{name})"
+
+ while @__transaction_names__[-1] != name
+ @__transaction_checkpoint__ = __rewind_this_transaction
+ Transaction::Simple.debug_io << "#{'|' * @__transaction_level__} Rewind Transaction#{s}\n" unless Transaction::Simple.debug_io.nil?
+ @__transaction_level__ -= 1
+ @__transaction_names__.pop
+ end
+ __rewind_this_transaction
+ end
+ Transaction::Simple.debug_io << "#{'|' * @__transaction_level__} Rewind Transaction#{s}\n" unless Transaction::Simple.debug_io.nil?
+ self
+ end
+
+ # Aborts the transaction. Resets the object state to what it was before
+ # the transaction was started and closes the transaction. If +name+ is
+ # specified, then the intervening transactions and the named transaction
+ # will be aborted. Otherwise, only the current transaction is aborted.
+ def abort_transaction(name = nil)
+ raise TransactionError, "Transaction Error: Cannot abort. There is no current transaction." if @__transaction_checkpoint__.nil?
+ if name.nil?
+ __abort_transaction(name)
+ else
+ raise TransactionError, "Transaction Error: Cannot abort nonexistant transaction #{name.inspect}." unless @__transaction_names__.include?(name)
+
+ __abort_transaction(name) while @__transaction_names__.include?(name)
+ end
+ self
+ end
+
+ # If +name+ is +nil+ (default), the current transaction level is closed
+ # out and the changes are committed.
+ #
+ # If +name+ is specified and +name+ is in the list of named
+ # transactions, then all transactions are closed and committed until the
+ # named transaction is reached.
+ def commit_transaction(name = nil)
+ raise TransactionError, "Transaction Error: Cannot commit. There is no current transaction." if @__transaction_checkpoint__.nil?
+
+ if name.nil?
+ s = ""
+ __commit_transaction
+ Transaction::Simple.debug_io << "#{'<' * @__transaction_level__} Commit Transaction#{s}\n" unless Transaction::Simple.debug_io.nil?
+ else
+ raise TransactionError, "Transaction Error: Cannot commit nonexistant transaction #{name.inspect}." unless @__transaction_names__.include?(name)
+ s = "(#{name})"
+
+ while @__transaction_names__[-1] != name
+ Transaction::Simple.debug_io << "#{'<' * @__transaction_level__} Commit Transaction#{s}\n" unless Transaction::Simple.debug_io.nil?
+ __commit_transaction
+ end
+ Transaction::Simple.debug_io << "#{'<' * @__transaction_level__} Commit Transaction#{s}\n" unless Transaction::Simple.debug_io.nil?
+ __commit_transaction
+ end
+ self
+ end
+
+ # Alternative method for calling the transaction methods. An optional
+ # name can be specified for named transaction support.
+ #
+ # #transaction(:start):: #start_transaction
+ # #transaction(:rewind):: #rewind_transaction
+ # #transaction(:abort):: #abort_transaction
+ # #transaction(:commit):: #commit_transaction
+ # #transaction(:name):: #transaction_name
+ # #transaction:: #transaction_open?
+ def transaction(action = nil, name = nil)
+ case action
+ when :start
+ start_transaction(name)
+ when :rewind
+ rewind_transaction(name)
+ when :abort
+ abort_transaction(name)
+ when :commit
+ commit_transaction(name)
+ when :name
+ transaction_name
+ when nil
+ transaction_open?(name)
+ end
+ end
+
+ def __abort_transaction(name = nil) #:nodoc:
+ @__transaction_checkpoint__ = __rewind_this_transaction
+
+ if name.nil?
+ s = ""
+ else
+ s = "(#{name.inspect})"
+ end
+
+ Transaction::Simple.debug_io << "#{'<' * @__transaction_level__} Abort Transaction#{s}\n" unless Transaction::Simple.debug_io.nil?
+ @__transaction_level__ -= 1
+ @__transaction_names__.pop
+ if @__transaction_level__ < 1
+ @__transaction_level__ = 0
+ @__transaction_names__ = []
+ end
+ end
+
+ TRANSACTION_CHECKPOINT = "@__transaction_checkpoint__" #:nodoc:
+ SKIP_TRANSACTION_VARS = [TRANSACTION_CHECKPOINT, "@__transaction_level__"] #:nodoc:
+
+ def __rewind_this_transaction #:nodoc:
+ r = Marshal.restore(@__transaction_checkpoint__)
+
+ begin
+ self.replace(r) if respond_to?(:replace)
+ rescue
+ nil
+ end
+
+ r.instance_variables.each do |i|
+ next if SKIP_TRANSACTION_VARS.include?(i)
+ if respond_to?(:instance_variable_get)
+ instance_variable_set(i, r.instance_variable_get(i))
+ else
+ instance_eval(%q|#{i} = r.instance_eval("#{i}")|)
+ end
+ end
+
+ if respond_to?(:instance_variable_get)
+ return r.instance_variable_get(TRANSACTION_CHECKPOINT)
+ else
+ return r.instance_eval(TRANSACTION_CHECKPOINT)
+ end
+ end
+
+ def __commit_transaction #:nodoc:
+ if respond_to?(:instance_variable_get)
+ @__transaction_checkpoint__ = Marshal.restore(@__transaction_checkpoint__).instance_variable_get(TRANSACTION_CHECKPOINT)
+ else
+ @__transaction_checkpoint__ = Marshal.restore(@__transaction_checkpoint__).instance_eval(TRANSACTION_CHECKPOINT)
+ end
+
+ @__transaction_level__ -= 1
+ @__transaction_names__.pop
+ if @__transaction_level__ < 1
+ @__transaction_level__ = 0
+ @__transaction_names__ = []
+ end
+ end
+
+ private :__abort_transaction, :__rewind_this_transaction, :__commit_transaction
+
+ # = Transaction::Simple::ThreadSafe
+ # Thread-safe simple object transaction support for Ruby.
+ # Transaction::Simple::ThreadSafe is used in the same way as
+ # Transaction::Simple. Transaction::Simple::ThreadSafe uses a Mutex
+ # object to ensure atomicity at the cost of performance in threaded
+ # applications.
+ #
+ # Transaction::Simple::ThreadSafe will not wait to obtain a lock; if the
+ # lock cannot be obtained immediately, a
+ # Transaction::TransactionThreadError will be raised.
+ #
+ # Thanks to Mauricio Fernández for help with getting this part working.
+ module ThreadSafe
+ VERSION = '1.1.1.0';
+
+ include Transaction::Simple
+
+ SKIP_TRANSACTION_VARS = Transaction::Simple::SKIP_TRANSACTION_VARS.dup #:nodoc:
+ SKIP_TRANSACTION_VARS << "@__transaction_mutex__"
+
+ Transaction::Simple.instance_methods(false) do |meth|
+ next if meth == "transaction"
+ arg = "(name = nil)" unless meth == "transaction_name"
+ module_eval <<-EOS
+ def #{meth}#{arg}
+ if (@__transaction_mutex__ ||= Mutex.new).try_lock
+ result = super
+ @__transaction_mutex__.unlock
+ return result
+ else
+ raise TransactionThreadError, "Transaction Error: Cannot obtain lock for ##{meth}"
+ end
+ ensure
+ @__transaction_mutex__.unlock
+ end
+ EOS
+ end
+ end
+ end
+end
+
+if $0 == __FILE__
+ require 'test/unit'
+
+ class Test__Transaction_Simple < Test::Unit::TestCase #:nodoc:
+ VALUE = "Now is the time for all good men to come to the aid of their country."
+
+ def setup
+ @value = VALUE.dup
+ @value.extend(Transaction::Simple)
+ end
+
+ def test_extended
+ assert_respond_to(@value, :start_transaction)
+ end
+
+ def test_started
+ assert_equal(false, @value.transaction_open?)
+ assert_nothing_raised { @value.start_transaction }
+ assert_equal(true, @value.transaction_open?)
+ end
+
+ def test_rewind
+ assert_equal(false, @value.transaction_open?)
+ assert_raises(Transaction::TransactionError) { @value.rewind_transaction }
+ assert_nothing_raised { @value.start_transaction }
+ assert_equal(true, @value.transaction_open?)
+ assert_nothing_raised { @value.gsub!(/men/, 'women') }
+ assert_not_equal(VALUE, @value)
+ assert_nothing_raised { @value.rewind_transaction }
+ assert_equal(true, @value.transaction_open?)
+ assert_equal(VALUE, @value)
+ end
+
+ def test_abort
+ assert_equal(false, @value.transaction_open?)
+ assert_raises(Transaction::TransactionError) { @value.abort_transaction }
+ assert_nothing_raised { @value.start_transaction }
+ assert_equal(true, @value.transaction_open?)
+ assert_nothing_raised { @value.gsub!(/men/, 'women') }
+ assert_not_equal(VALUE, @value)
+ assert_nothing_raised { @value.abort_transaction }
+ assert_equal(false, @value.transaction_open?)
+ assert_equal(VALUE, @value)
+ end
+
+ def test_commit
+ assert_equal(false, @value.transaction_open?)
+ assert_raises(Transaction::TransactionError) { @value.commit_transaction }
+ assert_nothing_raised { @value.start_transaction }
+ assert_equal(true, @value.transaction_open?)
+ assert_nothing_raised { @value.gsub!(/men/, 'women') }
+ assert_not_equal(VALUE, @value)
+ assert_equal(true, @value.transaction_open?)
+ assert_nothing_raised { @value.commit_transaction }
+ assert_equal(false, @value.transaction_open?)
+ assert_not_equal(VALUE, @value)
+ end
+
+ def test_multilevel
+ assert_equal(false, @value.transaction_open?)
+ assert_nothing_raised { @value.start_transaction }
+ assert_equal(true, @value.transaction_open?)
+ assert_nothing_raised { @value.gsub!(/men/, 'women') }
+ assert_equal(VALUE.gsub(/men/, 'women'), @value)
+ assert_equal(true, @value.transaction_open?)
+ assert_nothing_raised { @value.start_transaction }
+ assert_nothing_raised { @value.gsub!(/country/, 'nation-state') }
+ assert_nothing_raised { @value.commit_transaction }
+ assert_equal(VALUE.gsub(/men/, 'women').gsub(/country/, 'nation-state'), @value)
+ assert_equal(true, @value.transaction_open?)
+ assert_nothing_raised { @value.abort_transaction }
+ assert_equal(VALUE, @value)
+ end
+
+ def test_multilevel_named
+ assert_equal(false, @value.transaction_open?)
+ assert_raises(Transaction::TransactionError) { @value.transaction_name }
+ assert_nothing_raised { @value.start_transaction(:first) } # 1
+ assert_raises(Transaction::TransactionError) { @value.start_transaction(:first) }
+ assert_equal(true, @value.transaction_open?)
+ assert_equal(true, @value.transaction_open?(:first))
+ assert_equal(:first, @value.transaction_name)
+ assert_nothing_raised { @value.start_transaction } # 2
+ assert_not_equal(:first, @value.transaction_name)
+ assert_equal(nil, @value.transaction_name)
+ assert_raises(Transaction::TransactionError) { @value.abort_transaction(:second) }
+ assert_nothing_raised { @value.abort_transaction(:first) }
+ assert_equal(false, @value.transaction_open?)
+ assert_nothing_raised do
+ @value.start_transaction(:first)
+ @value.gsub!(/men/, 'women')
+ @value.start_transaction(:second)
+ @value.gsub!(/women/, 'people')
+ @value.start_transaction
+ @value.gsub!(/people/, 'sentients')
+ end
+ assert_nothing_raised { @value.abort_transaction(:second) }
+ assert_equal(true, @value.transaction_open?(:first))
+ assert_equal(VALUE.gsub(/men/, 'women'), @value)
+ assert_nothing_raised do
+ @value.start_transaction(:second)
+ @value.gsub!(/women/, 'people')
+ @value.start_transaction
+ @value.gsub!(/people/, 'sentients')
+ end
+ assert_raises(Transaction::TransactionError) { @value.rewind_transaction(:foo) }
+ assert_nothing_raised { @value.rewind_transaction(:second) }
+ assert_equal(VALUE.gsub(/men/, 'women'), @value)
+ assert_nothing_raised do
+ @value.gsub!(/women/, 'people')
+ @value.start_transaction
+ @value.gsub!(/people/, 'sentients')
+ end
+ assert_raises(Transaction::TransactionError) { @value.commit_transaction(:foo) }
+ assert_nothing_raised { @value.commit_transaction(:first) }
+ assert_equal(VALUE.gsub(/men/, 'sentients'), @value)
+ assert_equal(false, @value.transaction_open?)
+ end
+
+ def test_array
+ assert_nothing_raised do
+ @orig = ["first", "second", "third"]
+ @value = ["first", "second", "third"]
+ @value.extend(Transaction::Simple)
+ end
+ assert_equal(@orig, @value)
+ assert_nothing_raised { @value.start_transaction }
+ assert_equal(true, @value.transaction_open?)
+ assert_nothing_raised { @value[1].gsub!(/second/, "fourth") }
+ assert_not_equal(@orig, @value)
+ assert_nothing_raised { @value.abort_transaction }
+ assert_equal(@orig, @value)
+ end
+ end
+
+ class Test__Transaction_Simple_ThreadSafe < Test::Unit::TestCase #:nodoc:
+ VALUE = "Now is the time for all good men to come to the aid of their country."
+
+ def setup
+ @value = VALUE.dup
+ @value.extend(Transaction::Simple::ThreadSafe)
+ end
+
+ def test_extended
+ assert_respond_to(@value, :start_transaction)
+ end
+
+ def test_started
+ assert_equal(false, @value.transaction_open?)
+ assert_nothing_raised { @value.start_transaction }
+ assert_equal(true, @value.transaction_open?)
+ end
+
+ def test_rewind
+ assert_equal(false, @value.transaction_open?)
+ assert_raises(Transaction::TransactionError) { @value.rewind_transaction }
+ assert_nothing_raised { @value.start_transaction }
+ assert_equal(true, @value.transaction_open?)
+ assert_nothing_raised { @value.gsub!(/men/, 'women') }
+ assert_not_equal(VALUE, @value)
+ assert_nothing_raised { @value.rewind_transaction }
+ assert_equal(true, @value.transaction_open?)
+ assert_equal(VALUE, @value)
+ end
+
+ def test_abort
+ assert_equal(false, @value.transaction_open?)
+ assert_raises(Transaction::TransactionError) { @value.abort_transaction }
+ assert_nothing_raised { @value.start_transaction }
+ assert_equal(true, @value.transaction_open?)
+ assert_nothing_raised { @value.gsub!(/men/, 'women') }
+ assert_not_equal(VALUE, @value)
+ assert_nothing_raised { @value.abort_transaction }
+ assert_equal(false, @value.transaction_open?)
+ assert_equal(VALUE, @value)
+ end
+
+ def test_commit
+ assert_equal(false, @value.transaction_open?)
+ assert_raises(Transaction::TransactionError) { @value.commit_transaction }
+ assert_nothing_raised { @value.start_transaction }
+ assert_equal(true, @value.transaction_open?)
+ assert_nothing_raised { @value.gsub!(/men/, 'women') }
+ assert_not_equal(VALUE, @value)
+ assert_equal(true, @value.transaction_open?)
+ assert_nothing_raised { @value.commit_transaction }
+ assert_equal(false, @value.transaction_open?)
+ assert_not_equal(VALUE, @value)
+ end
+
+ def test_multilevel
+ assert_equal(false, @value.transaction_open?)
+ assert_nothing_raised { @value.start_transaction }
+ assert_equal(true, @value.transaction_open?)
+ assert_nothing_raised { @value.gsub!(/men/, 'women') }
+ assert_equal(VALUE.gsub(/men/, 'women'), @value)
+ assert_equal(true, @value.transaction_open?)
+ assert_nothing_raised { @value.start_transaction }
+ assert_nothing_raised { @value.gsub!(/country/, 'nation-state') }
+ assert_nothing_raised { @value.commit_transaction }
+ assert_equal(VALUE.gsub(/men/, 'women').gsub(/country/, 'nation-state'), @value)
+ assert_equal(true, @value.transaction_open?)
+ assert_nothing_raised { @value.abort_transaction }
+ assert_equal(VALUE, @value)
+ end
+
+ def test_multilevel_named
+ assert_equal(false, @value.transaction_open?)
+ assert_raises(Transaction::TransactionError) { @value.transaction_name }
+ assert_nothing_raised { @value.start_transaction(:first) } # 1
+ assert_raises(Transaction::TransactionError) { @value.start_transaction(:first) }
+ assert_equal(true, @value.transaction_open?)
+ assert_equal(true, @value.transaction_open?(:first))
+ assert_equal(:first, @value.transaction_name)
+ assert_nothing_raised { @value.start_transaction } # 2
+ assert_not_equal(:first, @value.transaction_name)
+ assert_equal(nil, @value.transaction_name)
+ assert_raises(Transaction::TransactionError) { @value.abort_transaction(:second) }
+ assert_nothing_raised { @value.abort_transaction(:first) }
+ assert_equal(false, @value.transaction_open?)
+ assert_nothing_raised do
+ @value.start_transaction(:first)
+ @value.gsub!(/men/, 'women')
+ @value.start_transaction(:second)
+ @value.gsub!(/women/, 'people')
+ @value.start_transaction
+ @value.gsub!(/people/, 'sentients')
+ end
+ assert_nothing_raised { @value.abort_transaction(:second) }
+ assert_equal(true, @value.transaction_open?(:first))
+ assert_equal(VALUE.gsub(/men/, 'women'), @value)
+ assert_nothing_raised do
+ @value.start_transaction(:second)
+ @value.gsub!(/women/, 'people')
+ @value.start_transaction
+ @value.gsub!(/people/, 'sentients')
+ end
+ assert_raises(Transaction::TransactionError) { @value.rewind_transaction(:foo) }
+ assert_nothing_raised { @value.rewind_transaction(:second) }
+ assert_equal(VALUE.gsub(/men/, 'women'), @value)
+ assert_nothing_raised do
+ @value.gsub!(/women/, 'people')
+ @value.start_transaction
+ @value.gsub!(/people/, 'sentients')
+ end
+ assert_raises(Transaction::TransactionError) { @value.commit_transaction(:foo) }
+ assert_nothing_raised { @value.commit_transaction(:first) }
+ assert_equal(VALUE.gsub(/men/, 'sentients'), @value)
+ assert_equal(false, @value.transaction_open?)
+ end
+
+ def test_array
+ assert_nothing_raised do
+ @orig = ["first", "second", "third"]
+ @value = ["first", "second", "third"]
+ @value.extend(Transaction::Simple::ThreadSafe)
+ end
+ assert_equal(@orig, @value)
+ assert_nothing_raised { @value.start_transaction }
+ assert_equal(true, @value.transaction_open?)
+ assert_nothing_raised { @value[1].gsub!(/second/, "fourth") }
+ assert_not_equal(@orig, @value)
+ assert_nothing_raised { @value.abort_transaction }
+ assert_equal(@orig, @value)
+ end
+ end
+end