solaris-mysql2 0.3.11

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. data/.gitignore +12 -0
  2. data/.rspec +3 -0
  3. data/.rvmrc +1 -0
  4. data/.travis.yml +7 -0
  5. data/CHANGELOG.md +244 -0
  6. data/Gemfile +3 -0
  7. data/MIT-LICENSE +20 -0
  8. data/README.md +334 -0
  9. data/Rakefile +5 -0
  10. data/benchmark/active_record.rb +51 -0
  11. data/benchmark/active_record_threaded.rb +42 -0
  12. data/benchmark/allocations.rb +33 -0
  13. data/benchmark/escape.rb +36 -0
  14. data/benchmark/query_with_mysql_casting.rb +80 -0
  15. data/benchmark/query_without_mysql_casting.rb +56 -0
  16. data/benchmark/sequel.rb +37 -0
  17. data/benchmark/setup_db.rb +119 -0
  18. data/benchmark/threaded.rb +44 -0
  19. data/examples/eventmachine.rb +21 -0
  20. data/examples/threaded.rb +20 -0
  21. data/ext/mysql2/client.c +901 -0
  22. data/ext/mysql2/client.h +42 -0
  23. data/ext/mysql2/extconf.rb +74 -0
  24. data/ext/mysql2/mysql2_ext.c +12 -0
  25. data/ext/mysql2/mysql2_ext.h +42 -0
  26. data/ext/mysql2/result.c +566 -0
  27. data/ext/mysql2/result.h +20 -0
  28. data/ext/mysql2/wait_for_single_fd.h +36 -0
  29. data/lib/mysql2.rb +21 -0
  30. data/lib/mysql2/client.rb +264 -0
  31. data/lib/mysql2/em.rb +37 -0
  32. data/lib/mysql2/error.rb +15 -0
  33. data/lib/mysql2/result.rb +5 -0
  34. data/lib/mysql2/version.rb +3 -0
  35. data/solaris-mysql2.gemspec +29 -0
  36. data/spec/em/em_spec.rb +50 -0
  37. data/spec/mysql2/client_spec.rb +465 -0
  38. data/spec/mysql2/error_spec.rb +69 -0
  39. data/spec/mysql2/result_spec.rb +388 -0
  40. data/spec/rcov.opts +3 -0
  41. data/spec/spec_helper.rb +67 -0
  42. data/tasks/benchmarks.rake +20 -0
  43. data/tasks/compile.rake +71 -0
  44. data/tasks/rspec.rake +16 -0
  45. data/tasks/vendor_mysql.rake +40 -0
  46. metadata +198 -0
@@ -0,0 +1,465 @@
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 require an open connection" do
120
+ @client.close
121
+ lambda {
122
+ @client.query "SELECT 1"
123
+ }.should raise_error(Mysql2::Error)
124
+ end
125
+
126
+ if RUBY_PLATFORM !~ /mingw|mswin/
127
+ it "should not allow another query to be sent without fetching a result first" do
128
+ @client.query("SELECT 1", :async => true)
129
+ lambda {
130
+ @client.query("SELECT 1")
131
+ }.should raise_error(Mysql2::Error)
132
+ end
133
+
134
+ it "should timeout if we wait longer than :read_timeout" do
135
+ client = Mysql2::Client.new(:read_timeout => 1)
136
+ lambda {
137
+ client.query("SELECT sleep(2)")
138
+ }.should raise_error(Mysql2::Error)
139
+ end
140
+
141
+ # XXX this test is not deterministic (because Unix signal handling is not)
142
+ # and may fail on a loaded system
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
+
168
+ it "#socket should return a Fixnum (file descriptor from C)" do
169
+ @client.socket.class.should eql(Fixnum)
170
+ @client.socket.should_not eql(0)
171
+ end
172
+
173
+ it "#socket should require an open connection" do
174
+ @client.close
175
+ lambda {
176
+ @client.socket
177
+ }.should raise_error(Mysql2::Error)
178
+ end
179
+
180
+ it "should close the connection when an exception is raised" do
181
+ begin
182
+ Timeout.timeout(1) do
183
+ @client.query("SELECT sleep(2)")
184
+ end
185
+ rescue Timeout::Error
186
+ end
187
+
188
+ lambda {
189
+ @client.query("SELECT 1")
190
+ }.should raise_error(Mysql2::Error, 'closed MySQL connection')
191
+ end
192
+
193
+ it "should handle Timeouts without leaving the connection hanging if reconnect is true" do
194
+ client = Mysql2::Client.new(:reconnect => true)
195
+ begin
196
+ Timeout.timeout(1) do
197
+ client.query("SELECT sleep(2)")
198
+ end
199
+ rescue Timeout::Error
200
+ end
201
+
202
+ lambda {
203
+ client.query("SELECT 1")
204
+ }.should_not raise_error(Mysql2::Error)
205
+ end
206
+
207
+ it "threaded queries should be supported" do
208
+ threads, results = [], {}
209
+ connect = lambda{ Mysql2::Client.new(:host => "localhost", :username => "root") }
210
+ Timeout.timeout(0.7) do
211
+ 5.times {
212
+ threads << Thread.new do
213
+ results[Thread.current.object_id] = connect.call.query("SELECT sleep(0.5) as result")
214
+ end
215
+ }
216
+ end
217
+ threads.each{|t| t.join }
218
+ results.keys.sort.should eql(threads.map{|t| t.object_id }.sort)
219
+ end
220
+
221
+ it "evented async queries should be supported" do
222
+ # should immediately return nil
223
+ @client.query("SELECT sleep(0.1)", :async => true).should eql(nil)
224
+
225
+ io_wrapper = IO.for_fd(@client.socket)
226
+ loops = 0
227
+ loop do
228
+ if IO.select([io_wrapper], nil, nil, 0.05)
229
+ break
230
+ else
231
+ loops += 1
232
+ end
233
+ end
234
+
235
+ # make sure we waited some period of time
236
+ (loops >= 1).should be_true
237
+
238
+ result = @client.async_result
239
+ result.class.should eql(Mysql2::Result)
240
+ end
241
+ end
242
+ end
243
+
244
+ it "should respond to #socket" do
245
+ @client.should respond_to(:socket)
246
+ end
247
+
248
+ if RUBY_PLATFORM =~ /mingw|mswin/
249
+ it "#socket should raise as it's not supported" do
250
+ lambda {
251
+ @client.socket
252
+ }.should raise_error(Mysql2::Error)
253
+ end
254
+ end
255
+
256
+ it "should respond to escape" do
257
+ Mysql2::Client.should respond_to(:escape)
258
+ end
259
+
260
+ context "escape" do
261
+ it "should return a new SQL-escape version of the passed string" do
262
+ Mysql2::Client.escape("abc'def\"ghi\0jkl%mno").should eql("abc\\'def\\\"ghi\\0jkl%mno")
263
+ end
264
+
265
+ it "should return the passed string if nothing was escaped" do
266
+ str = "plain"
267
+ Mysql2::Client.escape(str).object_id.should eql(str.object_id)
268
+ end
269
+
270
+ it "should not overflow the thread stack" do
271
+ lambda {
272
+ Thread.new { Mysql2::Client.escape("'" * 256 * 1024) }.join
273
+ }.should_not raise_error(SystemStackError)
274
+ end
275
+
276
+ it "should not overflow the process stack" do
277
+ lambda {
278
+ Thread.new { Mysql2::Client.escape("'" * 1024 * 1024 * 4) }.join
279
+ }.should_not raise_error(SystemStackError)
280
+ end
281
+
282
+ if RUBY_VERSION =~ /1.9/
283
+ it "should carry over the original string's encoding" do
284
+ str = "abc'def\"ghi\0jkl%mno"
285
+ escaped = Mysql2::Client.escape(str)
286
+ escaped.encoding.should eql(str.encoding)
287
+
288
+ str.encode!('us-ascii')
289
+ escaped = Mysql2::Client.escape(str)
290
+ escaped.encoding.should eql(str.encoding)
291
+ end
292
+ end
293
+ end
294
+
295
+ it "should respond to #escape" do
296
+ @client.should respond_to(:escape)
297
+ end
298
+
299
+ context "#escape" do
300
+ it "should return a new SQL-escape version of the passed string" do
301
+ @client.escape("abc'def\"ghi\0jkl%mno").should eql("abc\\'def\\\"ghi\\0jkl%mno")
302
+ end
303
+
304
+ it "should return the passed string if nothing was escaped" do
305
+ str = "plain"
306
+ @client.escape(str).object_id.should eql(str.object_id)
307
+ end
308
+
309
+ it "should not overflow the thread stack" do
310
+ lambda {
311
+ Thread.new { @client.escape("'" * 256 * 1024) }.join
312
+ }.should_not raise_error(SystemStackError)
313
+ end
314
+
315
+ it "should not overflow the process stack" do
316
+ lambda {
317
+ Thread.new { @client.escape("'" * 1024 * 1024 * 4) }.join
318
+ }.should_not raise_error(SystemStackError)
319
+ end
320
+
321
+ it "should require an open connection" do
322
+ @client.close
323
+ lambda {
324
+ @client.escape ""
325
+ }.should raise_error(Mysql2::Error)
326
+ end
327
+ end
328
+
329
+ it "should respond to #info" do
330
+ @client.should respond_to(:info)
331
+ end
332
+
333
+ it "#info should return a hash containing the client version ID and String" do
334
+ info = @client.info
335
+ info.class.should eql(Hash)
336
+ info.should have_key(:id)
337
+ info[:id].class.should eql(Fixnum)
338
+ info.should have_key(:version)
339
+ info[:version].class.should eql(String)
340
+ end
341
+
342
+ if defined? Encoding
343
+ context "strings returned by #info" do
344
+ it "should default to the connection's encoding if Encoding.default_internal is nil" do
345
+ Encoding.default_internal = nil
346
+ @client.info[:version].encoding.should eql(Encoding.find('utf-8'))
347
+
348
+ client2 = Mysql2::Client.new :encoding => 'ascii'
349
+ client2.info[:version].encoding.should eql(Encoding.find('us-ascii'))
350
+ end
351
+
352
+ it "should use Encoding.default_internal" do
353
+ Encoding.default_internal = Encoding.find('utf-8')
354
+ @client.info[:version].encoding.should eql(Encoding.default_internal)
355
+ Encoding.default_internal = Encoding.find('us-ascii')
356
+ @client.info[:version].encoding.should eql(Encoding.default_internal)
357
+ end
358
+ end
359
+ end
360
+
361
+ it "should respond to #server_info" do
362
+ @client.should respond_to(:server_info)
363
+ end
364
+
365
+ it "#server_info should return a hash containing the client version ID and String" do
366
+ server_info = @client.server_info
367
+ server_info.class.should eql(Hash)
368
+ server_info.should have_key(:id)
369
+ server_info[:id].class.should eql(Fixnum)
370
+ server_info.should have_key(:version)
371
+ server_info[:version].class.should eql(String)
372
+ end
373
+
374
+ it "#server_info should require an open connection" do
375
+ @client.close
376
+ lambda {
377
+ @client.server_info
378
+ }.should raise_error(Mysql2::Error)
379
+ end
380
+
381
+ if defined? Encoding
382
+ context "strings returned by #server_info" do
383
+ it "should default to the connection's encoding if Encoding.default_internal is nil" do
384
+ Encoding.default_internal = nil
385
+ @client.server_info[:version].encoding.should eql(Encoding.find('utf-8'))
386
+
387
+ client2 = Mysql2::Client.new :encoding => 'ascii'
388
+ client2.server_info[:version].encoding.should eql(Encoding.find('us-ascii'))
389
+ end
390
+
391
+ it "should use Encoding.default_internal" do
392
+ Encoding.default_internal = Encoding.find('utf-8')
393
+ @client.server_info[:version].encoding.should eql(Encoding.default_internal)
394
+ Encoding.default_internal = Encoding.find('us-ascii')
395
+ @client.server_info[:version].encoding.should eql(Encoding.default_internal)
396
+ end
397
+ end
398
+ end
399
+
400
+ it "should raise a Mysql2::Error exception upon connection failure" do
401
+ lambda {
402
+ bad_client = Mysql2::Client.new :host => "localhost", :username => 'asdfasdf8d2h', :password => 'asdfasdfw42'
403
+ }.should raise_error(Mysql2::Error)
404
+
405
+ lambda {
406
+ good_client = Mysql2::Client.new
407
+ }.should_not raise_error(Mysql2::Error)
408
+ end
409
+
410
+ context 'write operations api' do
411
+ before(:each) do
412
+ @client.query "USE test"
413
+ @client.query "CREATE TABLE IF NOT EXISTS lastIdTest (`id` int(11) NOT NULL AUTO_INCREMENT, blah INT(11), PRIMARY KEY (`id`))"
414
+ end
415
+
416
+ after(:each) do
417
+ @client.query "DROP TABLE lastIdTest"
418
+ end
419
+
420
+ it "should respond to #last_id" do
421
+ @client.should respond_to(:last_id)
422
+ end
423
+
424
+ it "#last_id should return a Fixnum, the from the last INSERT/UPDATE" do
425
+ @client.last_id.should eql(0)
426
+ @client.query "INSERT INTO lastIdTest (blah) VALUES (1234)"
427
+ @client.last_id.should eql(1)
428
+ end
429
+
430
+ it "should respond to #last_id" do
431
+ @client.should respond_to(:last_id)
432
+ end
433
+
434
+ it "#last_id should return a Fixnum, the from the last INSERT/UPDATE" do
435
+ @client.query "INSERT INTO lastIdTest (blah) VALUES (1234)"
436
+ @client.affected_rows.should eql(1)
437
+ @client.query "UPDATE lastIdTest SET blah=4321 WHERE id=1"
438
+ @client.affected_rows.should eql(1)
439
+ end
440
+ end
441
+
442
+ it "should respond to #thread_id" do
443
+ @client.should respond_to(:thread_id)
444
+ end
445
+
446
+ it "#thread_id should be a Fixnum" do
447
+ @client.thread_id.class.should eql(Fixnum)
448
+ end
449
+
450
+ it "should respond to #ping" do
451
+ @client.should respond_to(:ping)
452
+ end
453
+
454
+ it "#thread_id should return a boolean" do
455
+ @client.ping.should eql(true)
456
+ @client.close
457
+ @client.ping.should eql(false)
458
+ end
459
+
460
+ if RUBY_VERSION =~ /1.9/
461
+ it "should respond to #encoding" do
462
+ @client.should respond_to(:encoding)
463
+ end
464
+ end
465
+ end