rdp-mysql2 0.2.7.1

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.
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