rdp-mysql2 0.2.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/.gitignore +12 -0
  2. data/.rspec +2 -0
  3. data/.rvmrc +1 -0
  4. data/CHANGELOG.md +142 -0
  5. data/Gemfile +3 -0
  6. data/MIT-LICENSE +20 -0
  7. data/README.rdoc +261 -0
  8. data/Rakefile +5 -0
  9. data/benchmark/active_record.rb +51 -0
  10. data/benchmark/active_record_threaded.rb +42 -0
  11. data/benchmark/allocations.rb +33 -0
  12. data/benchmark/escape.rb +36 -0
  13. data/benchmark/query_with_mysql_casting.rb +80 -0
  14. data/benchmark/query_without_mysql_casting.rb +47 -0
  15. data/benchmark/sequel.rb +37 -0
  16. data/benchmark/setup_db.rb +119 -0
  17. data/benchmark/threaded.rb +44 -0
  18. data/examples/eventmachine.rb +21 -0
  19. data/examples/threaded.rb +20 -0
  20. data/ext/mysql2/client.c +839 -0
  21. data/ext/mysql2/client.h +41 -0
  22. data/ext/mysql2/extconf.rb +72 -0
  23. data/ext/mysql2/mysql2_ext.c +12 -0
  24. data/ext/mysql2/mysql2_ext.h +42 -0
  25. data/ext/mysql2/result.c +488 -0
  26. data/ext/mysql2/result.h +20 -0
  27. data/lib/active_record/connection_adapters/em_mysql2_adapter.rb +64 -0
  28. data/lib/active_record/connection_adapters/mysql2_adapter.rb +654 -0
  29. data/lib/active_record/fiber_patches.rb +104 -0
  30. data/lib/arel/engines/sql/compilers/mysql2_compiler.rb +11 -0
  31. data/lib/mysql2.rb +16 -0
  32. data/lib/mysql2/client.rb +240 -0
  33. data/lib/mysql2/em.rb +37 -0
  34. data/lib/mysql2/em_fiber.rb +31 -0
  35. data/lib/mysql2/error.rb +15 -0
  36. data/lib/mysql2/result.rb +5 -0
  37. data/lib/mysql2/version.rb +3 -0
  38. data/mysql2.gemspec +32 -0
  39. data/spec/em/em_fiber_spec.rb +22 -0
  40. data/spec/em/em_spec.rb +49 -0
  41. data/spec/mysql2/client_spec.rb +430 -0
  42. data/spec/mysql2/error_spec.rb +69 -0
  43. data/spec/mysql2/result_spec.rb +333 -0
  44. data/spec/rcov.opts +3 -0
  45. data/spec/spec_helper.rb +66 -0
  46. data/tasks/benchmarks.rake +20 -0
  47. data/tasks/compile.rake +71 -0
  48. data/tasks/rspec.rake +16 -0
  49. data/tasks/vendor_mysql.rake +40 -0
  50. metadata +236 -0
@@ -0,0 +1,5 @@
1
+ module Mysql2
2
+ class Result
3
+ include Enumerable
4
+ end
5
+ end
@@ -0,0 +1,3 @@
1
+ module Mysql2
2
+ VERSION = "0.2.7"
3
+ end
@@ -0,0 +1,32 @@
1
+ require File.expand_path('../lib/mysql2/version', __FILE__)
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{rdp-mysql2}
5
+ s.version = Mysql2::VERSION + ".1"
6
+ s.authors = ["Brian Lopez"]
7
+ s.date = Time.now.utc.strftime("%Y-%m-%d")
8
+ s.email = %q{seniorlopez@gmail.com}
9
+ s.extensions = ["ext/mysql2/extconf.rb"]
10
+ s.extra_rdoc_files = [
11
+ "README.rdoc"
12
+ ]
13
+ s.files = `git.exe ls-files`.split("\n")
14
+ s.homepage = %q{http://github.com/brianmario/mysql2}
15
+ s.rdoc_options = ["--charset=UTF-8"]
16
+ s.require_paths = ["lib", "ext"]
17
+ s.rubygems_version = %q{1.4.2}
18
+ s.summary = %q{A simple, fast Mysql library for Ruby, binding to libmysql}
19
+ s.test_files = `git.exe ls-files spec examples`.split("\n")
20
+
21
+ # tests
22
+ s.add_development_dependency 'eventmachine'
23
+ s.add_development_dependency 'rake-compiler', "~> 0.7.7"
24
+ s.add_development_dependency 'rspec'
25
+ # benchmarks
26
+ s.add_development_dependency 'activerecord'
27
+ s.add_development_dependency 'mysql'
28
+ s.add_development_dependency 'do_mysql'
29
+ s.add_development_dependency 'sequel'
30
+ s.add_development_dependency 'faker'
31
+ end
32
+
@@ -0,0 +1,22 @@
1
+ # encoding: UTF-8
2
+ if defined? EventMachine && defined? Fiber
3
+ require 'spec_helper'
4
+ require 'mysql2/em_fiber'
5
+
6
+ describe Mysql2::EM::Fiber::Client do
7
+ it 'should support queries' do
8
+ results = []
9
+ EM.run do
10
+ Fiber.new {
11
+ client1 = Mysql2::EM::Fiber::Client.new
12
+ results = client1.query "SELECT sleep(0.1) as first_query"
13
+ EM.stop_event_loop
14
+ }.resume
15
+ end
16
+
17
+ results.first.keys.should include("first_query")
18
+ end
19
+ end
20
+ else
21
+ puts "Either EventMachine or Fibers not available. Skipping tests that use them."
22
+ end
@@ -0,0 +1,49 @@
1
+ # encoding: UTF-8
2
+ if defined? EventMachine
3
+ require 'spec_helper'
4
+ require 'mysql2/em'
5
+
6
+ describe Mysql2::EM::Client do
7
+ it "should support async queries" do
8
+ results = []
9
+ EM.run do
10
+ client1 = Mysql2::EM::Client.new
11
+ defer1 = client1.query "SELECT sleep(0.1) as first_query"
12
+ defer1.callback do |result|
13
+ results << result.first
14
+ EM.stop_event_loop
15
+ end
16
+
17
+ client2 = Mysql2::EM::Client.new
18
+ defer2 = client2.query "SELECT sleep(0.025) second_query"
19
+ defer2.callback do |result|
20
+ results << result.first
21
+ end
22
+ end
23
+
24
+ results[0].keys.should include("second_query")
25
+ results[1].keys.should include("first_query")
26
+ end
27
+
28
+ it "should support queries in callbacks" do
29
+ results = []
30
+ EM.run do
31
+ client = Mysql2::EM::Client.new
32
+ defer1 = client.query "SELECT sleep(0.025) as first_query"
33
+ defer1.callback do |result|
34
+ results << result.first
35
+ defer2 = client.query "SELECT sleep(0.025) as second_query"
36
+ defer2.callback do |result|
37
+ results << result.first
38
+ EM.stop_event_loop
39
+ end
40
+ end
41
+ end
42
+
43
+ results[0].keys.should include("first_query")
44
+ results[1].keys.should include("second_query")
45
+ end
46
+ end
47
+ else
48
+ puts "EventMachine not installed, skipping the specs that use it"
49
+ end
@@ -0,0 +1,430 @@
1
+ # encoding: UTF-8
2
+ require 'spec_helper'
3
+
4
+ describe Mysql2::Client do
5
+ before(:each) do
6
+ @client = Mysql2::Client.new
7
+ end
8
+
9
+ if defined? Encoding
10
+ it "should raise an exception on create for invalid encodings" do
11
+ lambda {
12
+ c = Mysql2::Client.new(:encoding => "fake")
13
+ }.should raise_error(Mysql2::Error)
14
+ end
15
+ end
16
+
17
+ it "should accept connect flags and pass them to #connect" do
18
+ klient = Class.new(Mysql2::Client) do
19
+ attr_reader :connect_args
20
+ def connect *args
21
+ @connect_args ||= []
22
+ @connect_args << args
23
+ end
24
+ end
25
+ client = klient.new :flags => Mysql2::Client::FOUND_ROWS
26
+ (client.connect_args.last.last & Mysql2::Client::FOUND_ROWS).should be_true
27
+ end
28
+
29
+ it "should default flags to (REMEMBER_OPTIONS, LONG_PASSWORD, LONG_FLAG, TRANSACTIONS, PROTOCOL_41, SECURE_CONNECTION)" do
30
+ klient = Class.new(Mysql2::Client) do
31
+ attr_reader :connect_args
32
+ def connect *args
33
+ @connect_args ||= []
34
+ @connect_args << args
35
+ end
36
+ end
37
+ client = klient.new
38
+ (client.connect_args.last.last & (Mysql2::Client::REMEMBER_OPTIONS |
39
+ Mysql2::Client::LONG_PASSWORD |
40
+ Mysql2::Client::LONG_FLAG |
41
+ Mysql2::Client::TRANSACTIONS |
42
+ Mysql2::Client::PROTOCOL_41 |
43
+ Mysql2::Client::SECURE_CONNECTION)).should be_true
44
+ end
45
+
46
+ it "should have a global default_query_options hash" do
47
+ Mysql2::Client.should respond_to(:default_query_options)
48
+ end
49
+
50
+ it "should be able to connect via SSL options" do
51
+ pending("DON'T WORRY, THIS TEST PASSES :) - but is machine-specific. You need to have MySQL running with SSL configured and enabled. Then update the paths in this test to your needs and remove the pending state.")
52
+ ssl_client = nil
53
+ lambda {
54
+ ssl_client = Mysql2::Client.new(
55
+ :sslkey => '/path/to/client-key.pem',
56
+ :sslcert => '/path/to/client-cert.pem',
57
+ :sslca => '/path/to/ca-cert.pem',
58
+ :sslcapath => '/path/to/newcerts/',
59
+ :sslcipher => 'DHE-RSA-AES256-SHA'
60
+ )
61
+ }.should_not raise_error(Mysql2::Error)
62
+
63
+ results = ssl_client.query("SHOW STATUS WHERE Variable_name = \"Ssl_version\" OR Variable_name = \"Ssl_cipher\"").to_a
64
+ results[0]['Variable_name'].should eql('Ssl_cipher')
65
+ results[0]['Value'].should_not be_nil
66
+ results[0]['Value'].class.should eql(String)
67
+
68
+ results[1]['Variable_name'].should eql('Ssl_version')
69
+ results[1]['Value'].should_not be_nil
70
+ results[1]['Value'].class.should eql(String)
71
+ end
72
+
73
+ it "should respond to #close" do
74
+ @client.should respond_to(:close)
75
+ end
76
+
77
+ it "should be able to close properly" do
78
+ @client.close.should be_nil
79
+ lambda {
80
+ @client.query "SELECT 1"
81
+ }.should raise_error(Mysql2::Error)
82
+ end
83
+
84
+ it "should respond to #query" do
85
+ @client.should respond_to(:query)
86
+ end
87
+
88
+ it "should expect read_timeout to be a positive integer" do
89
+ lambda {
90
+ Mysql2::Client.new(:read_timeout => -1)
91
+ }.should raise_error(Mysql2::Error)
92
+ end
93
+
94
+ context "#query" do
95
+ it "should only accept strings as the query parameter" do
96
+ lambda {
97
+ @client.query ["SELECT 'not right'"]
98
+ }.should raise_error(TypeError)
99
+ end
100
+
101
+ it "should accept an options hash that inherits from Mysql2::Client.default_query_options" do
102
+ @client.query "SELECT 1", :something => :else
103
+ @client.query_options.should eql(@client.query_options.merge(:something => :else))
104
+ end
105
+
106
+ it "should return results as a hash by default" do
107
+ @client.query("SELECT 1").first.class.should eql(Hash)
108
+ end
109
+
110
+ it "should be able to return results as an array" do
111
+ @client.query("SELECT 1", :as => :array).first.class.should eql(Array)
112
+ @client.query("SELECT 1").each(:as => :array)
113
+ end
114
+
115
+ it "should be able to return results with symbolized keys" do
116
+ @client.query("SELECT 1", :symbolize_keys => true).first.keys[0].class.should eql(Symbol)
117
+ end
118
+
119
+ it "should not allow another query to be sent without fetching a result first" do
120
+ @client.query("SELECT 1", :async => true)
121
+ lambda {
122
+ @client.query("SELECT 1")
123
+ }.should raise_error(Mysql2::Error)
124
+ end
125
+
126
+ it "should require an open connection" do
127
+ @client.close
128
+ lambda {
129
+ @client.query "SELECT 1"
130
+ }.should raise_error(Mysql2::Error)
131
+ end
132
+
133
+ it "should timeout if we wait longer than :read_timeout" do
134
+ client = Mysql2::Client.new(:read_timeout => 1)
135
+ lambda {
136
+ client.query("SELECT sleep(2)")
137
+ }.should raise_error(Mysql2::Error)
138
+ end
139
+
140
+ # XXX this test is not deterministic (because Unix signal handling is not)
141
+ # and may fail on a loaded system
142
+ if RUBY_PLATFORM !~ /mingw|mswin/
143
+ it "should run signal handlers while waiting for a response" do
144
+ mark = {}
145
+ trap(:USR1) { mark[:USR1] = Time.now }
146
+ begin
147
+ mark[:START] = Time.now
148
+ pid = fork do
149
+ sleep 1 # wait for client "SELECT sleep(2)" query to start
150
+ Process.kill(:USR1, Process.ppid)
151
+ sleep # wait for explicit kill to prevent GC disconnect
152
+ end
153
+ @client.query("SELECT sleep(2)")
154
+ mark[:END] = Time.now
155
+ mark.include?(:USR1).should be_true
156
+ (mark[:USR1] - mark[:START]).should >= 1
157
+ (mark[:USR1] - mark[:START]).should < 1.1
158
+ (mark[:END] - mark[:USR1]).should > 0.9
159
+ (mark[:END] - mark[:START]).should >= 2
160
+ (mark[:END] - mark[:START]).should < 2.1
161
+ Process.kill(:TERM, pid)
162
+ Process.waitpid2(pid)
163
+ ensure
164
+ trap(:USR1, 'DEFAULT')
165
+ end
166
+ end
167
+ end
168
+ end
169
+
170
+ it "should respond to escape" do
171
+ Mysql2::Client.should respond_to(:escape)
172
+ end
173
+
174
+ context "escape" do
175
+ it "should return a new SQL-escape version of the passed string" do
176
+ Mysql2::Client.escape("abc'def\"ghi\0jkl%mno").should eql("abc\\'def\\\"ghi\\0jkl%mno")
177
+ end
178
+
179
+ it "should return the passed string if nothing was escaped" do
180
+ str = "plain"
181
+ Mysql2::Client.escape(str).object_id.should eql(str.object_id)
182
+ end
183
+
184
+ it "should not overflow the thread stack" do
185
+ lambda {
186
+ Thread.new { Mysql2::Client.escape("'" * 256 * 1024) }.join
187
+ }.should_not raise_error(SystemStackError)
188
+ end
189
+
190
+ it "should not overflow the process stack" do
191
+ lambda {
192
+ Thread.new { Mysql2::Client.escape("'" * 1024 * 1024 * 4) }.join
193
+ }.should_not raise_error(SystemStackError)
194
+ end
195
+
196
+ if RUBY_VERSION =~ /1.9/
197
+ it "should carry over the original string's encoding" do
198
+ str = "abc'def\"ghi\0jkl%mno"
199
+ escaped = Mysql2::Client.escape(str)
200
+ escaped.encoding.should eql(str.encoding)
201
+
202
+ str.encode!('us-ascii')
203
+ escaped = Mysql2::Client.escape(str)
204
+ escaped.encoding.should eql(str.encoding)
205
+ end
206
+ end
207
+ end
208
+
209
+ it "should respond to #escape" do
210
+ @client.should respond_to(:escape)
211
+ end
212
+
213
+ context "#escape" do
214
+ it "should return a new SQL-escape version of the passed string" do
215
+ @client.escape("abc'def\"ghi\0jkl%mno").should eql("abc\\'def\\\"ghi\\0jkl%mno")
216
+ end
217
+
218
+ it "should return the passed string if nothing was escaped" do
219
+ str = "plain"
220
+ @client.escape(str).object_id.should eql(str.object_id)
221
+ end
222
+
223
+ it "should not overflow the thread stack" do
224
+ lambda {
225
+ Thread.new { @client.escape("'" * 256 * 1024) }.join
226
+ }.should_not raise_error(SystemStackError)
227
+ end
228
+
229
+ it "should not overflow the process stack" do
230
+ lambda {
231
+ Thread.new { @client.escape("'" * 1024 * 1024 * 4) }.join
232
+ }.should_not raise_error(SystemStackError)
233
+ end
234
+
235
+ it "should require an open connection" do
236
+ @client.close
237
+ lambda {
238
+ @client.escape ""
239
+ }.should raise_error(Mysql2::Error)
240
+ end
241
+ end
242
+
243
+ it "should respond to #info" do
244
+ @client.should respond_to(:info)
245
+ end
246
+
247
+ it "#info should return a hash containing the client version ID and String" do
248
+ info = @client.info
249
+ info.class.should eql(Hash)
250
+ info.should have_key(:id)
251
+ info[:id].class.should eql(Fixnum)
252
+ info.should have_key(:version)
253
+ info[:version].class.should eql(String)
254
+ end
255
+
256
+ if defined? Encoding
257
+ context "strings returned by #info" do
258
+ it "should default to the connection's encoding if Encoding.default_internal is nil" do
259
+ Encoding.default_internal = nil
260
+ @client.info[:version].encoding.should eql(Encoding.find('utf-8'))
261
+
262
+ client2 = Mysql2::Client.new :encoding => 'ascii'
263
+ client2.info[:version].encoding.should eql(Encoding.find('us-ascii'))
264
+ end
265
+
266
+ it "should use Encoding.default_internal" do
267
+ Encoding.default_internal = Encoding.find('utf-8')
268
+ @client.info[:version].encoding.should eql(Encoding.default_internal)
269
+ Encoding.default_internal = Encoding.find('us-ascii')
270
+ @client.info[:version].encoding.should eql(Encoding.default_internal)
271
+ end
272
+ end
273
+ end
274
+
275
+ it "should respond to #server_info" do
276
+ @client.should respond_to(:server_info)
277
+ end
278
+
279
+ it "#server_info should return a hash containing the client version ID and String" do
280
+ server_info = @client.server_info
281
+ server_info.class.should eql(Hash)
282
+ server_info.should have_key(:id)
283
+ server_info[:id].class.should eql(Fixnum)
284
+ server_info.should have_key(:version)
285
+ server_info[:version].class.should eql(String)
286
+ end
287
+
288
+ it "#server_info should require an open connection" do
289
+ @client.close
290
+ lambda {
291
+ @client.server_info
292
+ }.should raise_error(Mysql2::Error)
293
+ end
294
+
295
+ if defined? Encoding
296
+ context "strings returned by #server_info" do
297
+ it "should default to the connection's encoding if Encoding.default_internal is nil" do
298
+ Encoding.default_internal = nil
299
+ @client.server_info[:version].encoding.should eql(Encoding.find('utf-8'))
300
+
301
+ client2 = Mysql2::Client.new :encoding => 'ascii'
302
+ client2.server_info[:version].encoding.should eql(Encoding.find('us-ascii'))
303
+ end
304
+
305
+ it "should use Encoding.default_internal" do
306
+ Encoding.default_internal = Encoding.find('utf-8')
307
+ @client.server_info[:version].encoding.should eql(Encoding.default_internal)
308
+ Encoding.default_internal = Encoding.find('us-ascii')
309
+ @client.server_info[:version].encoding.should eql(Encoding.default_internal)
310
+ end
311
+ end
312
+ end
313
+
314
+ it "should respond to #socket" do
315
+ @client.should respond_to(:socket)
316
+ end
317
+
318
+ it "#socket should return a Fixnum (file descriptor from C)" do
319
+ @client.socket.class.should eql(Fixnum)
320
+ @client.socket.should_not eql(0)
321
+ end
322
+
323
+ it "#socket should require an open connection" do
324
+ @client.close
325
+ lambda {
326
+ @client.socket
327
+ }.should raise_error(Mysql2::Error)
328
+ end
329
+
330
+ it "should raise a Mysql2::Error exception upon connection failure" do
331
+ lambda {
332
+ bad_client = Mysql2::Client.new :host => "localhost", :username => 'asdfasdf8d2h', :password => 'asdfasdfw42'
333
+ }.should raise_error(Mysql2::Error)
334
+
335
+ lambda {
336
+ good_client = Mysql2::Client.new
337
+ }.should_not raise_error(Mysql2::Error)
338
+ end
339
+
340
+ it "threaded queries should be supported" do
341
+ threads, results = [], {}
342
+ connect = lambda{ Mysql2::Client.new(:host => "localhost", :username => "root") }
343
+ Timeout.timeout(0.7) do
344
+ 5.times {
345
+ threads << Thread.new do
346
+ results[Thread.current.object_id] = connect.call.query("SELECT sleep(0.5) as result")
347
+ end
348
+ }
349
+ end
350
+ threads.each{|t| t.join }
351
+ results.keys.sort.should eql(threads.map{|t| t.object_id }.sort)
352
+ end
353
+
354
+ it "evented async queries should be supported" do
355
+ # should immediately return nil
356
+ @client.query("SELECT sleep(0.1)", :async => true).should eql(nil)
357
+
358
+ io_wrapper = IO.for_fd(@client.socket)
359
+ loops = 0
360
+ loop do
361
+ if IO.select([io_wrapper], nil, nil, 0.05)
362
+ break
363
+ else
364
+ loops += 1
365
+ end
366
+ end
367
+
368
+ # make sure we waited some period of time
369
+ (loops >= 1).should be_true
370
+
371
+ result = @client.async_result
372
+ result.class.should eql(Mysql2::Result)
373
+ end
374
+
375
+ context 'write operations api' do
376
+ before(:each) do
377
+ @client.query "USE test"
378
+ @client.query "CREATE TABLE lastIdTest (`id` int(11) NOT NULL AUTO_INCREMENT, blah INT(11), PRIMARY KEY (`id`))"
379
+ end
380
+
381
+ after(:each) do
382
+ @client.query "DROP TABLE lastIdTest"
383
+ end
384
+
385
+ it "should respond to #last_id" do
386
+ @client.should respond_to(:last_id)
387
+ end
388
+
389
+ it "#last_id should return a Fixnum, the from the last INSERT/UPDATE" do
390
+ @client.last_id.should eql(0)
391
+ @client.query "INSERT INTO lastIdTest (blah) VALUES (1234)"
392
+ @client.last_id.should eql(1)
393
+ end
394
+
395
+ it "should respond to #last_id" do
396
+ @client.should respond_to(:last_id)
397
+ end
398
+
399
+ it "#last_id should return a Fixnum, the from the last INSERT/UPDATE" do
400
+ @client.query "INSERT INTO lastIdTest (blah) VALUES (1234)"
401
+ @client.affected_rows.should eql(1)
402
+ @client.query "UPDATE lastIdTest SET blah=4321 WHERE id=1"
403
+ @client.affected_rows.should eql(1)
404
+ end
405
+ end
406
+
407
+ it "should respond to #thread_id" do
408
+ @client.should respond_to(:thread_id)
409
+ end
410
+
411
+ it "#thread_id should be a Fixnum" do
412
+ @client.thread_id.class.should eql(Fixnum)
413
+ end
414
+
415
+ it "should respond to #ping" do
416
+ @client.should respond_to(:ping)
417
+ end
418
+
419
+ it "#thread_id should return a boolean" do
420
+ @client.ping.should eql(true)
421
+ @client.close
422
+ @client.ping.should eql(false)
423
+ end
424
+
425
+ if RUBY_VERSION =~ /1.9/
426
+ it "should respond to #encoding" do
427
+ @client.should respond_to(:encoding)
428
+ end
429
+ end
430
+ end