mysql2 0.1.9 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +3 -0
- data/CHANGELOG.md +12 -1
- data/README.rdoc +69 -5
- data/Rakefile +4 -6
- data/VERSION +1 -1
- data/benchmark/active_record.rb +3 -0
- data/benchmark/allocations.rb +33 -0
- data/benchmark/escape.rb +24 -22
- data/benchmark/query_with_mysql_casting.rb +2 -2
- data/benchmark/query_without_mysql_casting.rb +2 -2
- data/benchmark/setup_db.rb +6 -2
- data/benchmark/thread_alone.rb +20 -0
- data/examples/threaded.rb +20 -0
- data/ext/mysql2/client.c +540 -0
- data/ext/mysql2/client.h +40 -0
- data/ext/mysql2/extconf.rb +6 -4
- data/ext/mysql2/mysql2_ext.c +3 -487
- data/ext/mysql2/mysql2_ext.h +1 -67
- data/ext/mysql2/result.c +151 -43
- data/ext/mysql2/result.h +13 -0
- data/lib/active_record/connection_adapters/em_mysql2_adapter.rb +14 -10
- data/lib/mysql2.rb +1 -1
- data/lib/mysql2/client.rb +17 -1
- data/mysql2.gemspec +14 -9
- data/spec/mysql2/client_spec.rb +50 -12
- data/spec/mysql2/result_spec.rb +25 -66
- data/spec/spec_helper.rb +62 -0
- data/tasks/benchmarks.rake +8 -0
- data/tasks/compile.rake +19 -0
- data/tasks/vendor_mysql.rake +41 -0
- metadata +14 -9
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +0 -598
- data/lib/sequel/adapters/mysql2.rb +0 -237
- data/spec/active_record/active_record_spec.rb +0 -149
@@ -1,237 +0,0 @@
|
|
1
|
-
require 'mysql2' unless defined? Mysql2
|
2
|
-
|
3
|
-
Sequel.require %w'shared/mysql utils/stored_procedures', 'adapters'
|
4
|
-
|
5
|
-
module Sequel
|
6
|
-
# Module for holding all MySQL-related classes and modules for Sequel.
|
7
|
-
module Mysql2
|
8
|
-
# Mapping of type numbers to conversion procs
|
9
|
-
MYSQL_TYPES = {}
|
10
|
-
|
11
|
-
MYSQL2_LITERAL_PROC = lambda{|v| v}
|
12
|
-
|
13
|
-
# Use only a single proc for each type to save on memory
|
14
|
-
MYSQL_TYPE_PROCS = {
|
15
|
-
[0, 246] => MYSQL2_LITERAL_PROC, # decimal
|
16
|
-
[1] => lambda{|v| convert_tinyint_to_bool ? v != 0 : v}, # tinyint
|
17
|
-
[2, 3, 8, 9, 13, 247, 248] => MYSQL2_LITERAL_PROC, # integer
|
18
|
-
[4, 5] => MYSQL2_LITERAL_PROC, # float
|
19
|
-
[10, 14] => MYSQL2_LITERAL_PROC, # date
|
20
|
-
[7, 12] => MYSQL2_LITERAL_PROC, # datetime
|
21
|
-
[11] => MYSQL2_LITERAL_PROC, # time
|
22
|
-
[249, 250, 251, 252] => lambda{|v| Sequel::SQL::Blob.new(v)} # blob
|
23
|
-
}
|
24
|
-
MYSQL_TYPE_PROCS.each do |k,v|
|
25
|
-
k.each{|n| MYSQL_TYPES[n] = v}
|
26
|
-
end
|
27
|
-
|
28
|
-
@convert_invalid_date_time = false
|
29
|
-
@convert_tinyint_to_bool = true
|
30
|
-
|
31
|
-
class << self
|
32
|
-
# By default, Sequel raises an exception if in invalid date or time is used.
|
33
|
-
# However, if this is set to nil or :nil, the adapter treats dates
|
34
|
-
# like 0000-00-00 and times like 838:00:00 as nil values. If set to :string,
|
35
|
-
# it returns the strings as is.
|
36
|
-
attr_accessor :convert_invalid_date_time
|
37
|
-
|
38
|
-
# Sequel converts the column type tinyint(1) to a boolean by default when
|
39
|
-
# using the native MySQL adapter. You can turn off the conversion by setting
|
40
|
-
# this to false.
|
41
|
-
attr_accessor :convert_tinyint_to_bool
|
42
|
-
end
|
43
|
-
|
44
|
-
# Database class for MySQL databases used with Sequel.
|
45
|
-
class Database < Sequel::Database
|
46
|
-
include Sequel::MySQL::DatabaseMethods
|
47
|
-
|
48
|
-
# Mysql::Error messages that indicate the current connection should be disconnected
|
49
|
-
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)/
|
50
|
-
|
51
|
-
set_adapter_scheme :mysql2
|
52
|
-
|
53
|
-
# Support stored procedures on MySQL
|
54
|
-
def call_sproc(name, opts={}, &block)
|
55
|
-
args = opts[:args] || []
|
56
|
-
execute("CALL #{name}#{args.empty? ? '()' : literal(args)}", opts.merge(:sproc=>false), &block)
|
57
|
-
end
|
58
|
-
|
59
|
-
# Connect to the database. In addition to the usual database options,
|
60
|
-
# the following options have effect:
|
61
|
-
#
|
62
|
-
# * :auto_is_null - Set to true to use MySQL default behavior of having
|
63
|
-
# a filter for an autoincrement column equals NULL to return the last
|
64
|
-
# inserted row.
|
65
|
-
# * :charset - Same as :encoding (:encoding takes precendence)
|
66
|
-
# * :compress - Set to false to not compress results from the server
|
67
|
-
# * :config_default_group - The default group to read from the in
|
68
|
-
# the MySQL config file.
|
69
|
-
# * :config_local_infile - If provided, sets the Mysql::OPT_LOCAL_INFILE
|
70
|
-
# option on the connection with the given value.
|
71
|
-
# * :encoding - Set all the related character sets for this
|
72
|
-
# connection (connection, client, database, server, and results).
|
73
|
-
# * :socket - Use a unix socket file instead of connecting via TCP/IP.
|
74
|
-
# * :timeout - Set the timeout in seconds before the server will
|
75
|
-
# disconnect this connection.
|
76
|
-
def connect(server)
|
77
|
-
opts = server_opts(server)
|
78
|
-
conn = ::Mysql2::Client.new({
|
79
|
-
:host => opts[:host] || 'localhost',
|
80
|
-
:username => opts[:user],
|
81
|
-
:password => opts[:password],
|
82
|
-
:database => opts[:database],
|
83
|
-
:port => opts[:port],
|
84
|
-
:socket => opts[:socket]
|
85
|
-
})
|
86
|
-
|
87
|
-
# increase timeout so mysql server doesn't disconnect us
|
88
|
-
conn.query("set @@wait_timeout = #{opts[:timeout] || 2592000}")
|
89
|
-
|
90
|
-
# By default, MySQL 'where id is null' selects the last inserted id
|
91
|
-
conn.query("set SQL_AUTO_IS_NULL=0") unless opts[:auto_is_null]
|
92
|
-
|
93
|
-
conn
|
94
|
-
end
|
95
|
-
|
96
|
-
# Returns instance of Sequel::MySQL::Dataset with the given options.
|
97
|
-
def dataset(opts = nil)
|
98
|
-
Mysql2::Dataset.new(self, opts)
|
99
|
-
end
|
100
|
-
|
101
|
-
# Executes the given SQL using an available connection, yielding the
|
102
|
-
# connection if the block is given.
|
103
|
-
def execute(sql, opts={}, &block)
|
104
|
-
if opts[:sproc]
|
105
|
-
call_sproc(sql, opts, &block)
|
106
|
-
else
|
107
|
-
synchronize(opts[:server]){|conn| _execute(conn, sql, opts, &block)}
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
|
-
# Return the version of the MySQL server two which we are connecting.
|
112
|
-
def server_version(server=nil)
|
113
|
-
@server_version ||= (synchronize(server){|conn| conn.info[:id]})
|
114
|
-
end
|
115
|
-
|
116
|
-
private
|
117
|
-
|
118
|
-
# Execute the given SQL on the given connection. If the :type
|
119
|
-
# option is :select, yield the result of the query, otherwise
|
120
|
-
# yield the connection if a block is given.
|
121
|
-
def _execute(conn, sql, opts)
|
122
|
-
begin
|
123
|
-
r = log_yield(sql){conn.query(sql)}
|
124
|
-
if opts[:type] == :select
|
125
|
-
yield r if r
|
126
|
-
elsif block_given?
|
127
|
-
yield conn
|
128
|
-
end
|
129
|
-
rescue ::Mysql2::Error => e
|
130
|
-
raise_error(e, :disconnect=>MYSQL_DATABASE_DISCONNECT_ERRORS.match(e.message))
|
131
|
-
end
|
132
|
-
end
|
133
|
-
|
134
|
-
# MySQL connections use the query method to execute SQL without a result
|
135
|
-
def connection_execute_method
|
136
|
-
:query
|
137
|
-
end
|
138
|
-
|
139
|
-
# The MySQL adapter main error class is Mysql::Error
|
140
|
-
def database_error_classes
|
141
|
-
[::Mysql2::Error]
|
142
|
-
end
|
143
|
-
|
144
|
-
# The database name when using the native adapter is always stored in
|
145
|
-
# the :database option.
|
146
|
-
def database_name
|
147
|
-
@opts[:database]
|
148
|
-
end
|
149
|
-
|
150
|
-
# Closes given database connection.
|
151
|
-
def disconnect_connection(c)
|
152
|
-
c.close
|
153
|
-
end
|
154
|
-
|
155
|
-
# Convert tinyint(1) type to boolean if convert_tinyint_to_bool is true
|
156
|
-
def schema_column_type(db_type)
|
157
|
-
Sequel::Mysql2.convert_tinyint_to_bool && db_type == 'tinyint(1)' ? :boolean : super
|
158
|
-
end
|
159
|
-
end
|
160
|
-
|
161
|
-
# Dataset class for MySQL datasets accessed via the native driver.
|
162
|
-
class Dataset < Sequel::Dataset
|
163
|
-
include Sequel::MySQL::DatasetMethods
|
164
|
-
include StoredProcedures
|
165
|
-
|
166
|
-
# Methods for MySQL stored procedures using the native driver.
|
167
|
-
module StoredProcedureMethods
|
168
|
-
include Sequel::Dataset::StoredProcedureMethods
|
169
|
-
|
170
|
-
private
|
171
|
-
|
172
|
-
# Execute the database stored procedure with the stored arguments.
|
173
|
-
def execute(sql, opts={}, &block)
|
174
|
-
super(@sproc_name, {:args=>@sproc_args, :sproc=>true}.merge(opts), &block)
|
175
|
-
end
|
176
|
-
|
177
|
-
# Same as execute, explicit due to intricacies of alias and super.
|
178
|
-
def execute_dui(sql, opts={}, &block)
|
179
|
-
super(@sproc_name, {:args=>@sproc_args, :sproc=>true}.merge(opts), &block)
|
180
|
-
end
|
181
|
-
end
|
182
|
-
|
183
|
-
# Delete rows matching this dataset
|
184
|
-
def delete
|
185
|
-
execute_dui(delete_sql){|c| return c.affected_rows}
|
186
|
-
end
|
187
|
-
|
188
|
-
# Yield all rows matching this dataset. If the dataset is set to
|
189
|
-
# split multiple statements, yield arrays of hashes one per statement
|
190
|
-
# instead of yielding results for all statements as hashes.
|
191
|
-
def fetch_rows(sql, &block)
|
192
|
-
execute(sql) do |r|
|
193
|
-
r.each(:symbolize_keys => true, &block)
|
194
|
-
end
|
195
|
-
self
|
196
|
-
end
|
197
|
-
|
198
|
-
# Don't allow graphing a dataset that splits multiple statements
|
199
|
-
def graph(*)
|
200
|
-
raise(Error, "Can't graph a dataset that splits multiple result sets") if opts[:split_multiple_result_sets]
|
201
|
-
super
|
202
|
-
end
|
203
|
-
|
204
|
-
# Insert a new value into this dataset
|
205
|
-
def insert(*values)
|
206
|
-
execute_dui(insert_sql(*values)){|c| return c.last_id}
|
207
|
-
end
|
208
|
-
|
209
|
-
# Replace (update or insert) the matching row.
|
210
|
-
def replace(*args)
|
211
|
-
execute_dui(replace_sql(*args)){|c| return c.last_id}
|
212
|
-
end
|
213
|
-
|
214
|
-
# Update the matching rows.
|
215
|
-
def update(values={})
|
216
|
-
execute_dui(update_sql(values)){|c| return c.affected_rows}
|
217
|
-
end
|
218
|
-
|
219
|
-
private
|
220
|
-
|
221
|
-
# Set the :type option to :select if it hasn't been set.
|
222
|
-
def execute(sql, opts={}, &block)
|
223
|
-
super(sql, {:type=>:select}.merge(opts), &block)
|
224
|
-
end
|
225
|
-
|
226
|
-
# Set the :type option to :dui if it hasn't been set.
|
227
|
-
def execute_dui(sql, opts={}, &block)
|
228
|
-
super(sql, {:type=>:dui}.merge(opts), &block)
|
229
|
-
end
|
230
|
-
|
231
|
-
# Handle correct quoting of strings using ::Mysql2#escape.
|
232
|
-
def literal_string(v)
|
233
|
-
db.synchronize{|c| "'#{c.escape(v)}'"}
|
234
|
-
end
|
235
|
-
end
|
236
|
-
end
|
237
|
-
end
|
@@ -1,149 +0,0 @@
|
|
1
|
-
# encoding: UTF-8
|
2
|
-
require 'spec_helper'
|
3
|
-
require 'rubygems'
|
4
|
-
require 'active_record'
|
5
|
-
require 'active_record/connection_adapters/mysql2_adapter'
|
6
|
-
|
7
|
-
class Mysql2Test2 < ActiveRecord::Base
|
8
|
-
set_table_name "mysql2_test2"
|
9
|
-
end
|
10
|
-
|
11
|
-
describe ActiveRecord::ConnectionAdapters::Mysql2Adapter do
|
12
|
-
it "should be able to connect" do
|
13
|
-
lambda {
|
14
|
-
ActiveRecord::Base.establish_connection(:adapter => 'mysql2')
|
15
|
-
}.should_not raise_error(Mysql2::Error)
|
16
|
-
end
|
17
|
-
|
18
|
-
context "once connected" do
|
19
|
-
before(:each) do
|
20
|
-
@connection = ActiveRecord::Base.connection
|
21
|
-
end
|
22
|
-
|
23
|
-
it "should be able to execute a raw query" do
|
24
|
-
@connection.execute("SELECT 1 as one").first['one'].should eql(1)
|
25
|
-
@connection.execute("SELECT NOW() as n").first['n'].class.should eql(Time)
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
context "columns" do
|
30
|
-
before(:all) do
|
31
|
-
ActiveRecord::Base.default_timezone = 'Pacific Time (US & Canada)'
|
32
|
-
ActiveRecord::Base.time_zone_aware_attributes = true
|
33
|
-
ActiveRecord::Base.establish_connection(:adapter => 'mysql2', :database => 'test')
|
34
|
-
Mysql2Test2.connection.execute %[
|
35
|
-
CREATE TABLE IF NOT EXISTS mysql2_test2 (
|
36
|
-
`id` mediumint(9) NOT NULL AUTO_INCREMENT,
|
37
|
-
`null_test` varchar(10) DEFAULT NULL,
|
38
|
-
`bit_test` bit(64) NOT NULL DEFAULT b'1',
|
39
|
-
`boolean_test` tinyint(1) DEFAULT 0,
|
40
|
-
`tiny_int_test` tinyint(4) NOT NULL DEFAULT '1',
|
41
|
-
`small_int_test` smallint(6) NOT NULL DEFAULT '1',
|
42
|
-
`medium_int_test` mediumint(9) NOT NULL DEFAULT '1',
|
43
|
-
`int_test` int(11) NOT NULL DEFAULT '1',
|
44
|
-
`big_int_test` bigint(20) NOT NULL DEFAULT '1',
|
45
|
-
`float_test` float(10,3) NOT NULL DEFAULT '1.000',
|
46
|
-
`double_test` double(10,3) NOT NULL DEFAULT '1.000',
|
47
|
-
`decimal_test` decimal(10,3) NOT NULL DEFAULT '1.000',
|
48
|
-
`date_test` date NOT NULL DEFAULT '2010-01-01',
|
49
|
-
`date_time_test` datetime NOT NULL DEFAULT '2010-01-01 00:00:00',
|
50
|
-
`timestamp_test` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
51
|
-
`time_test` time NOT NULL DEFAULT '00:00:00',
|
52
|
-
`year_test` year(4) NOT NULL DEFAULT '2010',
|
53
|
-
`char_test` char(10) NOT NULL DEFAULT 'abcdefghij',
|
54
|
-
`varchar_test` varchar(10) NOT NULL DEFAULT 'abcdefghij',
|
55
|
-
`binary_test` binary(10) NOT NULL DEFAULT 'abcdefghij',
|
56
|
-
`varbinary_test` varbinary(10) NOT NULL DEFAULT 'abcdefghij',
|
57
|
-
`tiny_blob_test` tinyblob NOT NULL,
|
58
|
-
`tiny_text_test` tinytext,
|
59
|
-
`blob_test` blob,
|
60
|
-
`text_test` text,
|
61
|
-
`medium_blob_test` mediumblob,
|
62
|
-
`medium_text_test` mediumtext,
|
63
|
-
`long_blob_test` longblob,
|
64
|
-
`long_text_test` longtext,
|
65
|
-
`enum_test` enum('val1','val2') NOT NULL DEFAULT 'val1',
|
66
|
-
`set_test` set('val1','val2') NOT NULL DEFAULT 'val1,val2',
|
67
|
-
PRIMARY KEY (`id`)
|
68
|
-
)
|
69
|
-
]
|
70
|
-
Mysql2Test2.connection.execute "INSERT INTO mysql2_test2 (null_test) VALUES (NULL)"
|
71
|
-
@test_result = Mysql2Test2.connection.execute("SELECT * FROM mysql2_test2 ORDER BY id DESC LIMIT 1").first
|
72
|
-
end
|
73
|
-
|
74
|
-
after(:all) do
|
75
|
-
Mysql2Test2.connection.execute("DELETE FROM mysql2_test WHERE id=#{@test_result['id']}")
|
76
|
-
end
|
77
|
-
|
78
|
-
it "default value should be cast to the expected type of the field" do
|
79
|
-
test = Mysql2Test2.new
|
80
|
-
test.null_test.should be_nil
|
81
|
-
test.bit_test.should eql("b'1'")
|
82
|
-
test.boolean_test.should eql(false)
|
83
|
-
test.tiny_int_test.should eql(1)
|
84
|
-
test.small_int_test.should eql(1)
|
85
|
-
test.medium_int_test.should eql(1)
|
86
|
-
test.int_test.should eql(1)
|
87
|
-
test.big_int_test.should eql(1)
|
88
|
-
test.float_test.should eql('1.0000'.to_f)
|
89
|
-
test.double_test.should eql('1.0000'.to_f)
|
90
|
-
test.decimal_test.should eql(BigDecimal.new('1.0000'))
|
91
|
-
test.date_test.should eql(Date.parse('2010-01-01'))
|
92
|
-
test.date_time_test.should eql(DateTime.parse('2010-01-01 00:00:00'))
|
93
|
-
test.timestamp_test.should be_nil
|
94
|
-
test.time_test.class.should eql(DateTime)
|
95
|
-
test.year_test.should eql(2010)
|
96
|
-
test.char_test.should eql('abcdefghij')
|
97
|
-
test.varchar_test.should eql('abcdefghij')
|
98
|
-
test.binary_test.should eql('abcdefghij')
|
99
|
-
test.varbinary_test.should eql('abcdefghij')
|
100
|
-
test.tiny_blob_test.should eql("")
|
101
|
-
test.tiny_text_test.should be_nil
|
102
|
-
test.blob_test.should be_nil
|
103
|
-
test.text_test.should be_nil
|
104
|
-
test.medium_blob_test.should be_nil
|
105
|
-
test.medium_text_test.should be_nil
|
106
|
-
test.long_blob_test.should be_nil
|
107
|
-
test.long_text_test.should be_nil
|
108
|
-
test.long_blob_test.should be_nil
|
109
|
-
test.enum_test.should eql('val1')
|
110
|
-
test.set_test.should eql('val1,val2')
|
111
|
-
test.save
|
112
|
-
end
|
113
|
-
|
114
|
-
it "should have correct values when pulled from a db record" do
|
115
|
-
test = Mysql2Test2.last
|
116
|
-
test.null_test.should be_nil
|
117
|
-
test.bit_test.class.should eql(String)
|
118
|
-
test.boolean_test.should eql(false)
|
119
|
-
test.tiny_int_test.should eql(1)
|
120
|
-
test.small_int_test.should eql(1)
|
121
|
-
test.medium_int_test.should eql(1)
|
122
|
-
test.int_test.should eql(1)
|
123
|
-
test.big_int_test.should eql(1)
|
124
|
-
test.float_test.should eql('1.0000'.to_f)
|
125
|
-
test.double_test.should eql('1.0000'.to_f)
|
126
|
-
test.decimal_test.should eql(BigDecimal.new('1.0000'))
|
127
|
-
test.date_test.should eql(Date.parse('2010-01-01'))
|
128
|
-
test.date_time_test.should eql(Time.utc(2010,1,1,0,0,0))
|
129
|
-
test.timestamp_test.class.should eql(ActiveSupport::TimeWithZone)
|
130
|
-
test.time_test.class.should eql(Time)
|
131
|
-
test.year_test.should eql(2010)
|
132
|
-
test.char_test.should eql('abcdefghij')
|
133
|
-
test.varchar_test.should eql('abcdefghij')
|
134
|
-
test.binary_test.should eql('abcdefghij')
|
135
|
-
test.varbinary_test.should eql('abcdefghij')
|
136
|
-
test.tiny_blob_test.should eql("")
|
137
|
-
test.tiny_text_test.should be_nil
|
138
|
-
test.blob_test.should be_nil
|
139
|
-
test.text_test.should be_nil
|
140
|
-
test.medium_blob_test.should be_nil
|
141
|
-
test.medium_text_test.should be_nil
|
142
|
-
test.long_blob_test.should be_nil
|
143
|
-
test.long_text_test.should be_nil
|
144
|
-
test.long_blob_test.should be_nil
|
145
|
-
test.enum_test.should eql('val1')
|
146
|
-
test.set_test.should eql('val1,val2')
|
147
|
-
end
|
148
|
-
end
|
149
|
-
end
|