rdbi-driver-mysql 0.9.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.
- data/.document +5 -0
- data/.gitignore +21 -0
- data/LICENSE +20 -0
- data/README.rdoc +21 -0
- data/Rakefile +58 -0
- data/VERSION +1 -0
- data/lib/rdbi/driver/mysql.rb +430 -0
- data/test/helper.rb +54 -0
- data/test/test_connect.rb +54 -0
- data/test/test_database.rb +163 -0
- data/test/test_statement.rb +22 -0
- data/test/test_types.rb +73 -0
- metadata +154 -0
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Erik Hollensbe
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
= rdbi-dbd-mysql
|
2
|
+
|
3
|
+
Description goes here.
|
4
|
+
|
5
|
+
== Note on Patches/Pull Requests
|
6
|
+
|
7
|
+
* Fork the project.
|
8
|
+
* Make your feature addition or bug fix.
|
9
|
+
* Add tests for it. This is important so I don't break it in a
|
10
|
+
future version unintentionally.
|
11
|
+
* Commit, do not mess with rakefile, version, or history.
|
12
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
13
|
+
* Send me a pull request. Bonus points for topic branches.
|
14
|
+
|
15
|
+
== Running tests
|
16
|
+
|
17
|
+
Tests make use of the RDBI::DBRC role 'mysql_test' to test against a local database.
|
18
|
+
|
19
|
+
== Copyright
|
20
|
+
|
21
|
+
Copyright (c) 2010 Erik Hollensbe. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "rdbi-driver-mysql"
|
8
|
+
gem.summary = %Q{mysql gem-based driver for RDBI}
|
9
|
+
gem.description = %Q{mysql gem-based driver for RDBI}
|
10
|
+
gem.email = "erik@hollensbe.org"
|
11
|
+
gem.homepage = "http://github.com/RDBI/rdbi-driver-mysql"
|
12
|
+
gem.authors = ["Erik Hollensbe"]
|
13
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
14
|
+
gem.add_dependency 'rdbi'
|
15
|
+
gem.add_dependency 'mysql', '>= 2.8.1'
|
16
|
+
|
17
|
+
gem.add_development_dependency 'test-unit'
|
18
|
+
gem.add_development_dependency 'rdoc'
|
19
|
+
gem.add_development_dependency 'rdbi-dbrc'
|
20
|
+
end
|
21
|
+
Jeweler::GemcutterTasks.new
|
22
|
+
rescue LoadError
|
23
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
24
|
+
end
|
25
|
+
|
26
|
+
require 'rake/testtask'
|
27
|
+
Rake::TestTask.new(:test) do |test|
|
28
|
+
test.libs << 'lib' << 'test'
|
29
|
+
test.pattern = 'test/**/test_*.rb'
|
30
|
+
test.verbose = true
|
31
|
+
end
|
32
|
+
|
33
|
+
begin
|
34
|
+
require 'rcov/rcovtask'
|
35
|
+
Rcov::RcovTask.new do |test|
|
36
|
+
test.libs << 'test'
|
37
|
+
test.pattern = 'test/**/test_*.rb'
|
38
|
+
test.verbose = true
|
39
|
+
end
|
40
|
+
rescue LoadError
|
41
|
+
task :rcov do
|
42
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
task :test => :check_dependencies
|
47
|
+
|
48
|
+
task :default => :test
|
49
|
+
|
50
|
+
require 'rdoc/task'
|
51
|
+
RDoc::Task.new do |rdoc|
|
52
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
53
|
+
|
54
|
+
rdoc.rdoc_dir = 'rdoc'
|
55
|
+
rdoc.title = "rdbi-dbd-mysql #{version}"
|
56
|
+
rdoc.rdoc_files.include('README*')
|
57
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
58
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.9.1
|
@@ -0,0 +1,430 @@
|
|
1
|
+
require 'rdbi'
|
2
|
+
require 'epoxy'
|
3
|
+
require 'methlab'
|
4
|
+
|
5
|
+
gem 'mysql', '= 2.8.1'
|
6
|
+
require 'mysql'
|
7
|
+
|
8
|
+
class RDBI::Driver::MySQL < RDBI::Driver
|
9
|
+
def initialize( *args )
|
10
|
+
super( Database, *args )
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class RDBI::Driver::MySQL < RDBI::Driver
|
15
|
+
# XXX basically taken verbatim from DBI. someone kill me now.
|
16
|
+
TYPE_MAP = { }
|
17
|
+
|
18
|
+
::Mysql::Field.constants.grep(/^TYPE_/).each do |const|
|
19
|
+
mysql_type = Mysql::Field.const_get(const) # numeric type code
|
20
|
+
TYPE_MAP[mysql_type] =
|
21
|
+
case const.to_s
|
22
|
+
when 'TYPE_TINY', 'TYPE_CHAR'
|
23
|
+
# XXX gonna break. design fix.
|
24
|
+
'tinyint'
|
25
|
+
when 'TYPE_SHORT'
|
26
|
+
'smallint'
|
27
|
+
when 'TYPE_INT24'
|
28
|
+
'mediumint'
|
29
|
+
when 'TYPE_LONG'
|
30
|
+
'integer'
|
31
|
+
when 'TYPE_LONGLONG'
|
32
|
+
'bigint'
|
33
|
+
when 'TYPE_FLOAT'
|
34
|
+
'float'
|
35
|
+
when 'TYPE_DOUBLE'
|
36
|
+
'double'
|
37
|
+
when 'TYPE_VAR_STRING', 'TYPE_STRING'
|
38
|
+
'varchar'
|
39
|
+
when 'TYPE_DATE', 'TYPE_TIME', 'TYPE_DATETIME', 'TYPE_TIMESTAMP'
|
40
|
+
'datetime'
|
41
|
+
when 'TYPE_TINY_BLOB'
|
42
|
+
'tinyblob'
|
43
|
+
when 'TYPE_MEDIUM_BLOB'
|
44
|
+
'mediumblob'
|
45
|
+
when 'TYPE_LONG_BLOB'
|
46
|
+
'longblob'
|
47
|
+
when 'TYPE_GEOMETRY'
|
48
|
+
'blob'
|
49
|
+
when 'TYPE_YEAR',
|
50
|
+
'TYPE_DECIMAL',
|
51
|
+
'TYPE_BLOB',
|
52
|
+
'TYPE_ENUM',
|
53
|
+
'TYPE_SET',
|
54
|
+
'TYPE_SET',
|
55
|
+
'TYPE_BIT',
|
56
|
+
'TYPE_NULL'
|
57
|
+
const.to_s.sub(/^TYPE_/, '').downcase
|
58
|
+
else
|
59
|
+
'unknown'
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class Database < RDBI::Database
|
64
|
+
extend MethLab
|
65
|
+
|
66
|
+
attr_reader :my_conn
|
67
|
+
attr_accessor :cast_booleans
|
68
|
+
|
69
|
+
def initialize(*args)
|
70
|
+
super(*args)
|
71
|
+
|
72
|
+
args = args[0]
|
73
|
+
|
74
|
+
self.database_name = args[:database] || args[:dbname] || args[:db]
|
75
|
+
|
76
|
+
username = args[:username] || args[:user]
|
77
|
+
password = args[:password] || args[:pass]
|
78
|
+
|
79
|
+
# FIXME flags?
|
80
|
+
|
81
|
+
raise ArgumentError, "database name not provided" unless self.database_name
|
82
|
+
raise ArgumentError, "username not provided" unless username
|
83
|
+
|
84
|
+
@cast_booleans = args[:cast_booleans] || false
|
85
|
+
|
86
|
+
@my_conn = if args[:host] || args[:hostname]
|
87
|
+
Mysql.connect(
|
88
|
+
args[:host] || args[:hostname],
|
89
|
+
username,
|
90
|
+
password,
|
91
|
+
self.database_name,
|
92
|
+
args[:port]
|
93
|
+
)
|
94
|
+
elsif args[:sock] || args[:socket]
|
95
|
+
Mysql.connect(
|
96
|
+
nil,
|
97
|
+
username,
|
98
|
+
password,
|
99
|
+
self.database_name,
|
100
|
+
nil,
|
101
|
+
args[:sock] || args[:socket]
|
102
|
+
)
|
103
|
+
else
|
104
|
+
raise ArgumentError, "either :host, :hostname, :socket, or :sock must be provided as a connection argument"
|
105
|
+
end
|
106
|
+
|
107
|
+
@preprocess_quoter = proc do |x, named, indexed|
|
108
|
+
@my_conn.quote((named[x] || indexed[x]).to_s)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def disconnect
|
113
|
+
@my_conn.close
|
114
|
+
super
|
115
|
+
end
|
116
|
+
|
117
|
+
def transaction(&block)
|
118
|
+
if in_transaction?
|
119
|
+
raise RDBI::TransactionError.new( "Already in transaction (not supported by MySQL)" )
|
120
|
+
end
|
121
|
+
@my_conn.autocommit(false)
|
122
|
+
super &block
|
123
|
+
@my_conn.autocommit(true)
|
124
|
+
end
|
125
|
+
|
126
|
+
def rollback
|
127
|
+
if ! in_transaction?
|
128
|
+
raise RDBI::TransactionError.new( "Cannot rollback when not in a transaction" )
|
129
|
+
end
|
130
|
+
@my_conn.rollback
|
131
|
+
super
|
132
|
+
end
|
133
|
+
|
134
|
+
def commit
|
135
|
+
if ! in_transaction?
|
136
|
+
raise RDBI::TransactionError.new( "Cannot commit when not in a transaction" )
|
137
|
+
end
|
138
|
+
@my_conn.commit
|
139
|
+
super
|
140
|
+
end
|
141
|
+
|
142
|
+
def new_statement(query)
|
143
|
+
Statement.new(query, self)
|
144
|
+
end
|
145
|
+
|
146
|
+
def table_schema( table_name )
|
147
|
+
info_row = execute(
|
148
|
+
"SELECT table_type FROM information_schema.tables WHERE table_schema = ? and table_name = ?",
|
149
|
+
self.database_name,
|
150
|
+
table_name.to_s
|
151
|
+
).fetch( :first )
|
152
|
+
if info_row.nil?
|
153
|
+
return nil
|
154
|
+
end
|
155
|
+
|
156
|
+
sch = RDBI::Schema.new( [], [] )
|
157
|
+
sch.tables << table_name.to_sym
|
158
|
+
|
159
|
+
case info_row[ 0 ]
|
160
|
+
when 'BASE TABLE'
|
161
|
+
sch.type = :table
|
162
|
+
when 'VIEW'
|
163
|
+
sch.type = :view
|
164
|
+
end
|
165
|
+
|
166
|
+
execute( "SELECT column_name, data_type, is_nullable FROM information_schema.columns WHERE table_schema = ? and table_name = ?",
|
167
|
+
self.database_name,
|
168
|
+
table_name.to_s
|
169
|
+
).fetch( :all ).each do |row|
|
170
|
+
col = RDBI::Column.new
|
171
|
+
col.name = row[0].to_sym
|
172
|
+
col.type = row[1].to_sym
|
173
|
+
# TODO: ensure this ruby_type is solid, especially re: dates and times
|
174
|
+
col.ruby_type = row[1].to_sym
|
175
|
+
col.nullable = row[2] == "YES"
|
176
|
+
sch.columns << col
|
177
|
+
end
|
178
|
+
|
179
|
+
sch
|
180
|
+
end
|
181
|
+
|
182
|
+
def schema
|
183
|
+
schemata = []
|
184
|
+
execute( "SELECT table_name FROM information_schema.tables where table_schema = ?", self.database_name ).fetch( :all ).each do |row|
|
185
|
+
schemata << table_schema( row[0] )
|
186
|
+
end
|
187
|
+
schemata
|
188
|
+
end
|
189
|
+
|
190
|
+
def ping
|
191
|
+
begin
|
192
|
+
@my_conn.ping
|
193
|
+
return 1
|
194
|
+
rescue
|
195
|
+
raise RDBI::DisconnectedError, "not connected"
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
#
|
201
|
+
# Due to mysql statement handles and result sets being tightly coupled,
|
202
|
+
# RDBI::Database#execute may require a full fetch of the result set for any
|
203
|
+
# of this to work.
|
204
|
+
#
|
205
|
+
# If you *must* use execute, use the block form, which will wait to close any
|
206
|
+
# statement handles. Performance will differ sharply.
|
207
|
+
#
|
208
|
+
class Cursor < RDBI::Cursor
|
209
|
+
def initialize(handle)
|
210
|
+
super(handle)
|
211
|
+
@index = 0
|
212
|
+
end
|
213
|
+
|
214
|
+
def fetch(count=1)
|
215
|
+
return [] if last_row?
|
216
|
+
a = []
|
217
|
+
count.times { a.push(next_row) }
|
218
|
+
return a
|
219
|
+
end
|
220
|
+
|
221
|
+
def next_row
|
222
|
+
val = if @array_handle
|
223
|
+
@array_handle[@index]
|
224
|
+
else
|
225
|
+
@handle.fetch
|
226
|
+
end
|
227
|
+
|
228
|
+
@index += 1
|
229
|
+
val
|
230
|
+
end
|
231
|
+
|
232
|
+
def result_count
|
233
|
+
if @array_handle
|
234
|
+
@array_handle.size
|
235
|
+
else
|
236
|
+
@handle.num_rows
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
def affected_count
|
241
|
+
if @array_handle
|
242
|
+
0
|
243
|
+
else
|
244
|
+
@handle.affected_rows
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
def first
|
249
|
+
if @array_handle
|
250
|
+
@array_handle.first
|
251
|
+
else
|
252
|
+
cnt = @handle.row_tell
|
253
|
+
@handle.data_seek(0)
|
254
|
+
res = @handle.fetch
|
255
|
+
@handle.data_seek(cnt)
|
256
|
+
res
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
def last
|
261
|
+
if @array_handle
|
262
|
+
@array_handle.last
|
263
|
+
else
|
264
|
+
cnt = @handle.row_tell
|
265
|
+
@handle.data_seek(@handle.num_rows)
|
266
|
+
res = @handle.fetch
|
267
|
+
@handle.data_seek(cnt)
|
268
|
+
res
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
def rest
|
273
|
+
oindex, @index = [@index, @handle.num_rows] rescue [@index, @array_handle.size]
|
274
|
+
fetch_range(oindex, @index)
|
275
|
+
end
|
276
|
+
|
277
|
+
def all
|
278
|
+
fetch_range(0, (@handle.num_rows rescue @array_handle.size))
|
279
|
+
end
|
280
|
+
|
281
|
+
def [](index)
|
282
|
+
@array_handle[index]
|
283
|
+
end
|
284
|
+
|
285
|
+
def last_row?
|
286
|
+
if @array_handle
|
287
|
+
@index == @array_handle.size
|
288
|
+
else
|
289
|
+
@handle.eof?
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
def rewind
|
294
|
+
@index = 0
|
295
|
+
unless @array_handle
|
296
|
+
@handle.data_seek(0)
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
def empty?
|
301
|
+
@array_handle.empty?
|
302
|
+
end
|
303
|
+
|
304
|
+
def finish
|
305
|
+
@handle.free_result
|
306
|
+
end
|
307
|
+
|
308
|
+
def coerce_to_array
|
309
|
+
unless @array_handle
|
310
|
+
@array_handle = []
|
311
|
+
begin
|
312
|
+
@handle.num_rows.times { @array_handle.push(@handle.fetch) }
|
313
|
+
rescue
|
314
|
+
end
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
protected
|
319
|
+
|
320
|
+
def fetch_range(start, stop)
|
321
|
+
if @array_handle
|
322
|
+
@array_handle[start, stop]
|
323
|
+
else
|
324
|
+
ary = []
|
325
|
+
|
326
|
+
@handle.data_seek(start)
|
327
|
+
(stop - start).times do
|
328
|
+
@handle.fetch
|
329
|
+
end
|
330
|
+
end
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
class Statement < RDBI::Statement
|
335
|
+
extend MethLab
|
336
|
+
|
337
|
+
attr_reader :my_query
|
338
|
+
|
339
|
+
def initialize(query, dbh)
|
340
|
+
super(query, dbh)
|
341
|
+
|
342
|
+
ep = Epoxy.new(query)
|
343
|
+
@index_map = ep.indexed_binds
|
344
|
+
|
345
|
+
# FIXME straight c'n'p from postgres, not sure it's needed.
|
346
|
+
query = ep.quote(@index_map.compact.inject({}) { |x,y| x.merge({ y => nil }) }) { |x| '?' }
|
347
|
+
|
348
|
+
@my_query = dbh.my_conn.prepare(query)
|
349
|
+
# FIXME type maps
|
350
|
+
@output_type_map = RDBI::Type.create_type_hash( RDBI::Type::Out )
|
351
|
+
|
352
|
+
manipulate_type_maps
|
353
|
+
end
|
354
|
+
|
355
|
+
def new_execution(*binds)
|
356
|
+
# FIXME move to RDBI::Util or something.
|
357
|
+
hashes, binds = binds.partition { |x| x.kind_of?(Hash) }
|
358
|
+
hash = hashes.inject({}) { |x, y| x.merge(y) }
|
359
|
+
hash.keys.each do |key|
|
360
|
+
if index = @index_map.index(key)
|
361
|
+
binds.insert(index, hash[key])
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
res = @my_query.execute(*binds)
|
366
|
+
|
367
|
+
columns = []
|
368
|
+
metadata = res.result_metadata rescue nil
|
369
|
+
|
370
|
+
if metadata
|
371
|
+
columns = res.result_metadata.fetch_fields.collect do |col|
|
372
|
+
RDBI::Column.new(
|
373
|
+
col.name.to_sym,
|
374
|
+
map_type(col),
|
375
|
+
map_type(col).to_sym,
|
376
|
+
col.length,
|
377
|
+
col.decimals,
|
378
|
+
!col.is_not_null?
|
379
|
+
)
|
380
|
+
end
|
381
|
+
end
|
382
|
+
|
383
|
+
schema = RDBI::Schema.new columns
|
384
|
+
[ Cursor.new(res), schema, @output_type_map ]
|
385
|
+
end
|
386
|
+
|
387
|
+
def finish
|
388
|
+
@my_query.close rescue nil
|
389
|
+
super
|
390
|
+
end
|
391
|
+
|
392
|
+
protected
|
393
|
+
|
394
|
+
def map_type(col)
|
395
|
+
if col.is_num? && col.length == 1
|
396
|
+
'boolean'
|
397
|
+
else
|
398
|
+
TYPE_MAP[col.type]
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
402
|
+
def manipulate_type_maps
|
403
|
+
datetime_check = proc { |x| x.kind_of?(::Mysql::Time) }
|
404
|
+
zone = DateTime.now.zone
|
405
|
+
# XXX yep. slow as snot.
|
406
|
+
datetime_conv = proc { |x| DateTime.parse(x.to_s + " #{zone}") }
|
407
|
+
|
408
|
+
@output_type_map[:datetime] = RDBI::Type.filterlist(TypeLib::Filter.new(datetime_check, datetime_conv))
|
409
|
+
|
410
|
+
@input_type_map[TrueClass] = TypeLib::Filter.new(
|
411
|
+
RDBI::Type::Checks::IS_BOOLEAN,
|
412
|
+
proc { |x| 1 }
|
413
|
+
)
|
414
|
+
|
415
|
+
@input_type_map[FalseClass] = TypeLib::Filter.new(
|
416
|
+
RDBI::Type::Checks::IS_BOOLEAN,
|
417
|
+
proc { |x| 0 }
|
418
|
+
)
|
419
|
+
|
420
|
+
if dbh.cast_booleans
|
421
|
+
boolean_filter = TypeLib::Filter.new(
|
422
|
+
proc { |x| x == 1 or x == 0 },
|
423
|
+
proc { |x| x == 1 }
|
424
|
+
)
|
425
|
+
|
426
|
+
@output_type_map[:boolean] = boolean_filter
|
427
|
+
end
|
428
|
+
end
|
429
|
+
end
|
430
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
gem 'test-unit'
|
3
|
+
require 'test/unit'
|
4
|
+
|
5
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
6
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
7
|
+
require 'rdbi/driver/mysql'
|
8
|
+
gem 'rdbi-dbrc'
|
9
|
+
require 'rdbi-dbrc'
|
10
|
+
|
11
|
+
class Test::Unit::TestCase
|
12
|
+
|
13
|
+
attr_accessor :dbh
|
14
|
+
|
15
|
+
SQL = [
|
16
|
+
%q[drop table if exists test],
|
17
|
+
%q[drop table if exists integer_test],
|
18
|
+
%q[create table integer_test (id integer) ENGINE=InnoDB],
|
19
|
+
%q[drop table if exists foo],
|
20
|
+
%q[create table foo (bar integer) ENGINE=InnoDB],
|
21
|
+
%q[drop table if exists datetime_test],
|
22
|
+
%q[create table datetime_test (item datetime) ENGINE=InnoDB],
|
23
|
+
%q[drop table if exists boolean_test],
|
24
|
+
%q[create table boolean_test (id integer, item boolean) ENGINE=InnoDB],
|
25
|
+
%q[drop table if exists bar],
|
26
|
+
%q[create table bar (foo varchar(255), bar integer) ENGINE=InnoDB],
|
27
|
+
]
|
28
|
+
|
29
|
+
def init_database
|
30
|
+
self.dbh = connect unless self.dbh and self.dbh.connected?
|
31
|
+
|
32
|
+
SQL.each do |sql|
|
33
|
+
dbh.execute(sql)
|
34
|
+
end
|
35
|
+
|
36
|
+
self.dbh
|
37
|
+
end
|
38
|
+
|
39
|
+
def connect
|
40
|
+
RDBI::DBRC.connect(:mysql_test)
|
41
|
+
end
|
42
|
+
|
43
|
+
def role
|
44
|
+
RDBI::DBRC.roles[:mysql_test]
|
45
|
+
end
|
46
|
+
|
47
|
+
def setup
|
48
|
+
init_database
|
49
|
+
end
|
50
|
+
|
51
|
+
def teardown
|
52
|
+
dbh.disconnect if dbh and dbh.connected?
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestConnect < Test::Unit::TestCase
|
4
|
+
def test_01_connect
|
5
|
+
dbh = connect
|
6
|
+
assert(dbh)
|
7
|
+
assert(dbh.connected?)
|
8
|
+
assert(dbh.database_name)
|
9
|
+
|
10
|
+
assert_equal(dbh.database_name, role[:database])
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_02_connect_exceptions
|
14
|
+
e_database = ArgumentError.new("database name not provided")
|
15
|
+
e_username = ArgumentError.new("username not provided")
|
16
|
+
e_connect = ArgumentError.new("either :host, :hostname, :socket, or :sock must be provided as a connection argument")
|
17
|
+
|
18
|
+
base_args = {
|
19
|
+
:host => "localhost",
|
20
|
+
:hostname => "localhost",
|
21
|
+
:port => 3306,
|
22
|
+
:username => "foreal",
|
23
|
+
:password => "notreally",
|
24
|
+
:database => "foobar",
|
25
|
+
:sock => "/tmp/shit",
|
26
|
+
:socket => "/tmp/shit",
|
27
|
+
}
|
28
|
+
|
29
|
+
args = base_args.dup
|
30
|
+
args.delete(:database)
|
31
|
+
assert_raises(e_database) { RDBI.connect(:MySQL, args) }
|
32
|
+
|
33
|
+
args = base_args.dup
|
34
|
+
args.delete(:username)
|
35
|
+
assert_raises(e_username) { RDBI.connect(:MySQL, args) }
|
36
|
+
|
37
|
+
args = base_args.dup
|
38
|
+
args.delete(:host)
|
39
|
+
args.delete(:hostname)
|
40
|
+
args.delete(:sock)
|
41
|
+
args.delete(:socket)
|
42
|
+
assert_raises(e_connect) { RDBI.connect(:MySQL, args) }
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_03_disconnection
|
46
|
+
dbh = connect
|
47
|
+
assert(dbh)
|
48
|
+
assert(dbh.connected?)
|
49
|
+
dbh.disconnect
|
50
|
+
assert(!dbh.connected?)
|
51
|
+
|
52
|
+
assert_raises(Mysql::Error.new("MySQL server has gone away")) { dbh.instance_variable_get(:@my_conn).ping }
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestDatabase < Test::Unit::TestCase
|
4
|
+
def test_01_connect
|
5
|
+
assert dbh
|
6
|
+
assert_kind_of( RDBI::Driver::MySQL::Database, dbh )
|
7
|
+
assert_kind_of( RDBI::Database, dbh )
|
8
|
+
assert_equal( dbh.database_name, role[:database] )
|
9
|
+
dbh.disconnect
|
10
|
+
assert ! dbh.connected?
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_02_ping
|
14
|
+
my_role = role.dup
|
15
|
+
driver = my_role.delete(:driver)
|
16
|
+
|
17
|
+
assert_kind_of(Numeric, dbh.ping)
|
18
|
+
assert_kind_of(Numeric, RDBI.ping(driver, my_role))
|
19
|
+
dbh.disconnect
|
20
|
+
|
21
|
+
assert_raises(RDBI::DisconnectedError.new("not connected")) do
|
22
|
+
dbh.ping
|
23
|
+
end
|
24
|
+
|
25
|
+
# XXX This should still work because it connects. Obviously, testing a
|
26
|
+
# downed database is gonna be pretty hard.
|
27
|
+
assert_kind_of(Numeric, RDBI.ping(driver, my_role))
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_03_execute
|
31
|
+
self.dbh = init_database
|
32
|
+
res = dbh.execute( "insert into foo (bar) values (?)", 1 )
|
33
|
+
assert res
|
34
|
+
assert_kind_of( RDBI::Result, res )
|
35
|
+
assert_equal( 1, res.affected_count )
|
36
|
+
|
37
|
+
res = dbh.execute( "select * from foo" )
|
38
|
+
assert res
|
39
|
+
assert_kind_of( RDBI::Result, res )
|
40
|
+
assert_equal( [[1]], res.fetch(:all) )
|
41
|
+
|
42
|
+
rows = res.as( :Struct ).fetch( :all )
|
43
|
+
row = rows[ 0 ]
|
44
|
+
assert_equal( 1, row.bar )
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_04_transaction
|
48
|
+
self.dbh = init_database
|
49
|
+
|
50
|
+
dbh.transaction do
|
51
|
+
assert dbh.in_transaction?
|
52
|
+
5.times { dbh.execute( "insert into foo (bar) values (?)", 1 ) }
|
53
|
+
dbh.rollback
|
54
|
+
assert ! dbh.in_transaction?
|
55
|
+
end
|
56
|
+
|
57
|
+
assert ! dbh.in_transaction?
|
58
|
+
|
59
|
+
assert_equal( [], dbh.execute("select * from foo").fetch(:all) )
|
60
|
+
|
61
|
+
dbh.transaction do
|
62
|
+
assert dbh.in_transaction?
|
63
|
+
5.times { dbh.execute("insert into foo (bar) values (?)", 1) }
|
64
|
+
assert_equal( [[1]] * 5, dbh.execute("select * from foo").fetch(:all) )
|
65
|
+
dbh.commit
|
66
|
+
assert ! dbh.in_transaction?
|
67
|
+
end
|
68
|
+
|
69
|
+
assert ! dbh.in_transaction?
|
70
|
+
|
71
|
+
assert_equal( [[1]] * 5, dbh.execute("select * from foo").fetch(:all) )
|
72
|
+
|
73
|
+
dbh.transaction do
|
74
|
+
assert dbh.in_transaction?
|
75
|
+
assert_raises( RDBI::TransactionError ) do
|
76
|
+
dbh.transaction do
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Not in a transaction
|
82
|
+
|
83
|
+
assert_raises( RDBI::TransactionError ) do
|
84
|
+
dbh.rollback
|
85
|
+
end
|
86
|
+
|
87
|
+
assert_raises( RDBI::TransactionError ) do
|
88
|
+
dbh.commit
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def test_05_preprocess_query
|
93
|
+
self.dbh = init_database
|
94
|
+
assert_equal(
|
95
|
+
"insert into foo (bar) values (1)",
|
96
|
+
dbh.preprocess_query( "insert into foo (bar) values (?)", 1 )
|
97
|
+
)
|
98
|
+
end
|
99
|
+
|
100
|
+
def test_06_schema
|
101
|
+
self.dbh = init_database
|
102
|
+
|
103
|
+
dbh.execute( "insert into bar (foo, bar) values (?, ?)", "foo", 1 )
|
104
|
+
res = dbh.execute( "select * from bar" )
|
105
|
+
|
106
|
+
assert res
|
107
|
+
assert res.schema
|
108
|
+
assert_kind_of( RDBI::Schema, res.schema )
|
109
|
+
assert res.schema.columns
|
110
|
+
res.schema.columns.each { |x| assert_kind_of(RDBI::Column, x) }
|
111
|
+
end
|
112
|
+
|
113
|
+
def test_07_table_schema
|
114
|
+
self.dbh = init_database
|
115
|
+
assert_respond_to( dbh, :table_schema )
|
116
|
+
|
117
|
+
schema = dbh.table_schema( :foo )
|
118
|
+
columns = schema.columns
|
119
|
+
assert_equal columns.size, 1
|
120
|
+
c = columns[ 0 ]
|
121
|
+
assert_equal c.name, :bar
|
122
|
+
assert_equal c.type, :int
|
123
|
+
|
124
|
+
schema = dbh.table_schema( :bar )
|
125
|
+
columns = schema.columns
|
126
|
+
assert_equal columns.size, 2
|
127
|
+
columns.each do |c|
|
128
|
+
case c.name
|
129
|
+
when :foo
|
130
|
+
assert_equal c.type, :varchar
|
131
|
+
when :bar
|
132
|
+
assert_equal c.type, :int
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
assert_nil dbh.table_schema( :non_existent )
|
137
|
+
end
|
138
|
+
|
139
|
+
def test_08_basic_schema
|
140
|
+
self.dbh = init_database
|
141
|
+
assert_respond_to( dbh, :schema )
|
142
|
+
schema = dbh.schema.sort_by { |x| x.tables[0].to_s }
|
143
|
+
|
144
|
+
tables = [ :bar, :boolean_test, :datetime_test, :foo, :integer_test ]
|
145
|
+
columns = {
|
146
|
+
:bar => { :foo => :varchar, :bar => :int },
|
147
|
+
:foo => { :bar => :int },
|
148
|
+
:integer_test => { :id => :int },
|
149
|
+
:datetime_test => { :item => :datetime },
|
150
|
+
:boolean_test => { :id => :int, :item => :tinyint },
|
151
|
+
}
|
152
|
+
|
153
|
+
schema.each_with_index do |sch, x|
|
154
|
+
assert_kind_of( RDBI::Schema, sch )
|
155
|
+
assert_equal( sch.tables[0], tables[x] )
|
156
|
+
|
157
|
+
sch.columns.each do |col|
|
158
|
+
assert_kind_of( RDBI::Column, col )
|
159
|
+
assert_equal( columns[ tables[x] ][ col.name ], col.type )
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestStatement < Test::Unit::TestCase
|
4
|
+
def test_01_statement_creation_and_finish
|
5
|
+
sth = dbh.prepare("create table `test` (id integer)")
|
6
|
+
assert(sth)
|
7
|
+
assert(!sth.finished?)
|
8
|
+
assert_equal(sth.query, "create table `test` (id integer)")
|
9
|
+
sth.finish
|
10
|
+
assert(sth.finished?)
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_02_statement_execution
|
14
|
+
sth = dbh.prepare("insert into integer_test (id) values (?)")
|
15
|
+
assert(sth)
|
16
|
+
assert_equal(sth.query, "insert into integer_test (id) values (?)")
|
17
|
+
sth.execute(1)
|
18
|
+
# FIXME affected rows
|
19
|
+
sth.finish
|
20
|
+
assert_equal([[1]], dbh.execute("select * from integer_test").fetch(:all))
|
21
|
+
end
|
22
|
+
end
|
data/test/test_types.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestTypes < Test::Unit::TestCase
|
4
|
+
def test_01_booleans
|
5
|
+
dbh.cast_booleans = true
|
6
|
+
res = dbh.execute( "SELECT true" )
|
7
|
+
assert_equal true, res.fetch(:first)[0]
|
8
|
+
|
9
|
+
res = dbh.execute( "SELECT false" )
|
10
|
+
assert_equal false, res.fetch(:first)[0]
|
11
|
+
|
12
|
+
dbh.cast_booleans = false
|
13
|
+
res = dbh.execute( "SELECT true" )
|
14
|
+
assert_equal 1, res.fetch(:first)[0]
|
15
|
+
|
16
|
+
res = dbh.execute( "SELECT false" )
|
17
|
+
assert_equal 0, res.fetch(:first)[0]
|
18
|
+
|
19
|
+
dbh.execute("insert into boolean_test (id, item) values (?, ?)", 0, true);
|
20
|
+
dbh.execute("insert into boolean_test (id, item) values (?, ?)", 1, false);
|
21
|
+
|
22
|
+
dbh.cast_booleans = true
|
23
|
+
row = dbh.execute("select id, item from boolean_test where id=?", 0).fetch(:first)
|
24
|
+
assert_equal(0, row[0])
|
25
|
+
assert_equal(true, row[1])
|
26
|
+
|
27
|
+
row = dbh.execute("select id, item from boolean_test where id=?", 1).fetch(:first)
|
28
|
+
assert_equal(1, row[0])
|
29
|
+
assert_equal(false, row[1])
|
30
|
+
|
31
|
+
dbh.cast_booleans = false
|
32
|
+
row = dbh.execute("select id, item from boolean_test where id=?", 0).fetch(:first)
|
33
|
+
assert_equal(0, row[0])
|
34
|
+
assert_equal(1, row[1])
|
35
|
+
|
36
|
+
row = dbh.execute("select id, item from boolean_test where id=?", 1).fetch(:first)
|
37
|
+
assert_equal(1, row[0])
|
38
|
+
assert_equal(0, row[1])
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_02_general
|
42
|
+
dbh.execute( "insert into foo (bar) values (?)", 1 )
|
43
|
+
|
44
|
+
res = dbh.execute( "select count(*) from foo" )
|
45
|
+
assert res
|
46
|
+
assert_kind_of( RDBI::Result, res )
|
47
|
+
assert_equal( [[1]], res.fetch(:all) )
|
48
|
+
row = res.as( :Array ).fetch( :first )
|
49
|
+
assert_equal 1, row[ 0 ]
|
50
|
+
|
51
|
+
res = dbh.execute( "SELECT 5" )
|
52
|
+
assert res
|
53
|
+
assert_kind_of( RDBI::Result, res )
|
54
|
+
row = res.as( :Array ).fetch( :first )
|
55
|
+
assert_equal 5, row[ 0 ]
|
56
|
+
|
57
|
+
time_str = DateTime.now.strftime( "%Y-%m-%d %H:%M:%S %z" )
|
58
|
+
res = dbh.execute( "SELECT 5, 'hello', cast('#{time_str}' as datetime)" )
|
59
|
+
assert res
|
60
|
+
assert_kind_of( RDBI::Result, res )
|
61
|
+
row = res.fetch( :all )[ 0 ]
|
62
|
+
assert_equal 5, row[ 0 ]
|
63
|
+
assert_equal 'hello', row[ 1 ]
|
64
|
+
assert_equal DateTime.parse( time_str ), row[ 2 ]
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_03_datetime
|
68
|
+
dt = DateTime.now
|
69
|
+
dbh.execute("insert into datetime_test (item) values (?)", dt)
|
70
|
+
row = dbh.execute("select item from datetime_test limit 1").fetch(:first)
|
71
|
+
assert_equal(dt.to_s, row[0].to_s)
|
72
|
+
end
|
73
|
+
end
|
metadata
ADDED
@@ -0,0 +1,154 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rdbi-driver-mysql
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 57
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 9
|
9
|
+
- 1
|
10
|
+
version: 0.9.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Erik Hollensbe
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-08-21 00:00:00 -04:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: rdbi
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
version: "0"
|
33
|
+
type: :runtime
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: mysql
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
hash: 45
|
44
|
+
segments:
|
45
|
+
- 2
|
46
|
+
- 8
|
47
|
+
- 1
|
48
|
+
version: 2.8.1
|
49
|
+
type: :runtime
|
50
|
+
version_requirements: *id002
|
51
|
+
- !ruby/object:Gem::Dependency
|
52
|
+
name: test-unit
|
53
|
+
prerelease: false
|
54
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
hash: 3
|
60
|
+
segments:
|
61
|
+
- 0
|
62
|
+
version: "0"
|
63
|
+
type: :development
|
64
|
+
version_requirements: *id003
|
65
|
+
- !ruby/object:Gem::Dependency
|
66
|
+
name: rdoc
|
67
|
+
prerelease: false
|
68
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
69
|
+
none: false
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
hash: 3
|
74
|
+
segments:
|
75
|
+
- 0
|
76
|
+
version: "0"
|
77
|
+
type: :development
|
78
|
+
version_requirements: *id004
|
79
|
+
- !ruby/object:Gem::Dependency
|
80
|
+
name: rdbi-dbrc
|
81
|
+
prerelease: false
|
82
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - ">="
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
hash: 3
|
88
|
+
segments:
|
89
|
+
- 0
|
90
|
+
version: "0"
|
91
|
+
type: :development
|
92
|
+
version_requirements: *id005
|
93
|
+
description: mysql gem-based driver for RDBI
|
94
|
+
email: erik@hollensbe.org
|
95
|
+
executables: []
|
96
|
+
|
97
|
+
extensions: []
|
98
|
+
|
99
|
+
extra_rdoc_files:
|
100
|
+
- LICENSE
|
101
|
+
- README.rdoc
|
102
|
+
files:
|
103
|
+
- .document
|
104
|
+
- .gitignore
|
105
|
+
- LICENSE
|
106
|
+
- README.rdoc
|
107
|
+
- Rakefile
|
108
|
+
- VERSION
|
109
|
+
- lib/rdbi/driver/mysql.rb
|
110
|
+
- test/helper.rb
|
111
|
+
- test/test_connect.rb
|
112
|
+
- test/test_database.rb
|
113
|
+
- test/test_statement.rb
|
114
|
+
- test/test_types.rb
|
115
|
+
has_rdoc: true
|
116
|
+
homepage: http://github.com/RDBI/rdbi-driver-mysql
|
117
|
+
licenses: []
|
118
|
+
|
119
|
+
post_install_message:
|
120
|
+
rdoc_options:
|
121
|
+
- --charset=UTF-8
|
122
|
+
require_paths:
|
123
|
+
- lib
|
124
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
125
|
+
none: false
|
126
|
+
requirements:
|
127
|
+
- - ">="
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
hash: 3
|
130
|
+
segments:
|
131
|
+
- 0
|
132
|
+
version: "0"
|
133
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
134
|
+
none: false
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
hash: 3
|
139
|
+
segments:
|
140
|
+
- 0
|
141
|
+
version: "0"
|
142
|
+
requirements: []
|
143
|
+
|
144
|
+
rubyforge_project:
|
145
|
+
rubygems_version: 1.3.7
|
146
|
+
signing_key:
|
147
|
+
specification_version: 3
|
148
|
+
summary: mysql gem-based driver for RDBI
|
149
|
+
test_files:
|
150
|
+
- test/helper.rb
|
151
|
+
- test/test_connect.rb
|
152
|
+
- test/test_database.rb
|
153
|
+
- test/test_statement.rb
|
154
|
+
- test/test_types.rb
|