do_mysql 0.2.4 → 0.9.2
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +48 -25
- data/TODO +4 -0
- data/ext/do_mysql_ext.c +807 -0
- data/ext/extconf.rb +51 -50
- data/lib/do_mysql.rb +20 -252
- data/lib/do_mysql/transaction.rb +44 -0
- data/spec/integration/do_mysql_spec.rb +241 -0
- data/spec/integration/logging_spec.rb +51 -0
- data/spec/integration/quoting_spec.rb +37 -0
- data/spec/spec_helper.rb +131 -0
- data/spec/unit/transaction_spec.rb +35 -0
- metadata +60 -50
- data/README +0 -4
- data/ext/mysql_c.c +0 -16602
- data/ext/mysql_c.i +0 -67
data/ext/extconf.rb
CHANGED
@@ -1,65 +1,66 @@
|
|
1
|
-
if
|
2
|
-
ENV["RC_ARCHS"] = `uname -m`.chomp
|
3
|
-
unless File.exists?("/usr/local/mysql/lib/mysql")
|
4
|
-
`sudo ln -s /usr/local/mysql/lib /usr/local/mysql/lib/mysql` rescue nil
|
5
|
-
end
|
6
|
-
end
|
1
|
+
if RUBY_PLATFORM =~ /darwin/
|
2
|
+
ENV["RC_ARCHS"] = `uname -m`.chomp if `uname -sr` =~ /^Darwin/
|
7
3
|
|
8
|
-
#
|
9
|
-
if
|
10
|
-
@mysql_config_bin = "mysql_config"
|
11
|
-
elsif !`which mysql_config5`.chomp.empty?
|
12
|
-
@mysql_config_bin = "mysql_config5"
|
13
|
-
else
|
14
|
-
puts "Cannot find mysql_config in your path. Please enter a location: "
|
15
|
-
location = gets.chomp
|
16
|
-
if File.exists?(location)
|
17
|
-
@mysql_config_bin = location
|
18
|
-
else
|
19
|
-
puts "Cannot find that file. Exiting."
|
20
|
-
exit
|
21
|
-
end
|
4
|
+
# On PowerPC the defaults are fine
|
5
|
+
ENV["RC_ARCHS"] = '' if `uname -m` =~ /^Power Macintosh/
|
22
6
|
end
|
23
7
|
|
24
8
|
require 'mkmf'
|
25
9
|
|
26
|
-
|
27
|
-
|
10
|
+
# All instances of mysql_config on PATH ...
|
11
|
+
def mysql_config_paths
|
12
|
+
ENV['PATH'].split(File::PATH_SEPARATOR).collect do |path|
|
13
|
+
[ "#{path}/mysql_config", "#{path}/mysql_config5" ].
|
14
|
+
detect { |bin| File.exist?(bin) }
|
15
|
+
end
|
28
16
|
end
|
29
17
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
18
|
+
# The first mysql_config binary on PATH ...
|
19
|
+
def default_mysql_config_path
|
20
|
+
mysql_config_paths.compact.first
|
21
|
+
end
|
34
22
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
23
|
+
def default_prefix
|
24
|
+
if mc = default_mysql_config_path
|
25
|
+
File.dirname(File.dirname(mc))
|
26
|
+
else
|
27
|
+
"/usr/local"
|
39
28
|
end
|
40
|
-
|
41
|
-
@mysql_config[type] = sout.chomp[2..-1]
|
42
|
-
@mysql_config[type]
|
43
29
|
end
|
44
30
|
|
45
|
-
|
46
|
-
|
47
|
-
|
31
|
+
# Allow overriding path to mysql_config on command line using:
|
32
|
+
# ruby extconf.rb --with-mysql-config=/path/to/mysql_config
|
33
|
+
if RUBY_PLATFORM =~ /mswin|mingw/
|
34
|
+
dir_config('mysql')
|
35
|
+
have_header 'mysql.h' || exit(1)
|
36
|
+
have_library 'libmysql' || exit(1)
|
37
|
+
have_func('mysql_query', 'mysql.h') || exit(1)
|
38
|
+
have_func('mysql_ssl_set', 'mysql.h')
|
39
|
+
elsif mc = with_config('mysql-config', default_mysql_config_path)
|
40
|
+
mc = default_mysql_config_path if mc == true
|
41
|
+
cflags = `#{mc} --cflags`.chomp
|
42
|
+
exit 1 if $? != 0
|
43
|
+
libs = `#{mc} --libs`.chomp
|
44
|
+
exit 1 if $? != 0
|
45
|
+
$CPPFLAGS += ' ' + cflags
|
46
|
+
$libs = libs + " " + $libs
|
47
|
+
else
|
48
|
+
inc, lib = dir_config('mysql', default_prefix)
|
48
49
|
libs = ['m', 'z', 'socket', 'nsl']
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
50
|
+
lib_dirs =
|
51
|
+
[ lib, "/usr/lib", "/usr/local/lib", "/opt/local/lib" ].collect do |path|
|
52
|
+
[ path, "#{path}/mysql", "#{path}/mysql5/mysql" ]
|
53
|
+
end
|
54
|
+
find_library('mysqlclient', 'mysql_query', *lib_dirs.flatten) || exit(1)
|
55
|
+
find_header('mysql.h', *lib_dirs.flatten.map { |p| p.gsub('/lib', '/include') })
|
54
56
|
end
|
55
57
|
|
56
|
-
|
57
|
-
|
58
|
+
unless RUBY_PLATFORM =~ /mswin|mingw/
|
59
|
+
have_header 'mysql.h'
|
60
|
+
have_library 'mysqlclient' || exit(1)
|
61
|
+
have_func 'mysql_query' || exit(1)
|
62
|
+
have_func 'mysql_ssl_set'
|
63
|
+
end
|
58
64
|
|
59
|
-
|
60
|
-
|
61
|
-
dir_config("mysql_c")
|
62
|
-
create_makefile("mysql_c")
|
63
|
-
else
|
64
|
-
puts 'Could not find MySQL build environment (libraries & headers): Makefile not created'
|
65
|
-
end
|
65
|
+
$CFLAGS << ' -Wall ' unless RUBY_PLATFORM =~ /mswin/
|
66
|
+
create_makefile('do_mysql_ext')
|
data/lib/do_mysql.rb
CHANGED
@@ -1,258 +1,26 @@
|
|
1
|
-
require '
|
1
|
+
require 'rubygems'
|
2
2
|
require 'data_objects'
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
raise ArgumentError, "you specified an invalid connection component: #{opt}" unless k && v
|
22
|
-
instance_variable_set("@#{k}", v)
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
def change_database(database_name)
|
27
|
-
@dbname = database_name
|
28
|
-
@connection_string.gsub(/db_name=[^ ]*/, "db_name=#{database_name}")
|
29
|
-
end
|
30
|
-
|
31
|
-
def open
|
32
|
-
@db = Mysql_c.mysql_init(nil)
|
33
|
-
raise ConnectionFailed, "could not allocate a MySQL connection" unless @db
|
34
|
-
conn = Mysql_c.mysql_real_connect(@db, @host, @user, @password, @dbname, @port || 0, @socket, @flags || 0)
|
35
|
-
raise ConnectionFailed, "Unable to connect to database with provided connection string. \n#{Mysql_c.mysql_error(@db)}" unless conn
|
36
|
-
@state = STATE_OPEN
|
37
|
-
true
|
38
|
-
end
|
39
|
-
|
40
|
-
def close
|
41
|
-
if @state == STATE_OPEN
|
42
|
-
Mysql_c.mysql_close(@db)
|
43
|
-
@state = STATE_CLOSED
|
44
|
-
true
|
45
|
-
else
|
46
|
-
false
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
def create_command(text)
|
51
|
-
Command.new(self, text)
|
52
|
-
end
|
53
|
-
|
54
|
-
def begin_transaction
|
55
|
-
Transaction.new(self)
|
56
|
-
end
|
57
|
-
|
58
|
-
end
|
59
|
-
|
60
|
-
class Field
|
61
|
-
attr_reader :name, :type
|
62
|
-
|
63
|
-
def initialize(ptr)
|
64
|
-
@name, @type = ptr.name.to_s, ptr.type.to_s
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
class Transaction
|
69
|
-
|
70
|
-
attr_reader :connection
|
71
|
-
|
72
|
-
def initialize(conn)
|
73
|
-
@connection = conn
|
74
|
-
exec_sql("BEGIN")
|
75
|
-
end
|
76
|
-
|
77
|
-
# Commits the transaction
|
78
|
-
def commit
|
79
|
-
exec_sql("COMMIT")
|
80
|
-
end
|
81
|
-
|
82
|
-
# Rolls back the transaction
|
83
|
-
def rollback(savepoint = nil)
|
84
|
-
raise NotImplementedError, "MySQL does not support savepoints" if savepoint
|
85
|
-
exec_sql("ROLLBACK")
|
86
|
-
end
|
87
|
-
|
88
|
-
# Creates a savepoint for rolling back later (not commonly supported)
|
89
|
-
def save(name)
|
90
|
-
raise NotImplementedError, "MySQL does not support savepoints"
|
91
|
-
end
|
92
|
-
|
93
|
-
def create_command(*args)
|
94
|
-
@connection.create_command(*args)
|
95
|
-
end
|
96
|
-
|
97
|
-
protected
|
98
|
-
|
99
|
-
def exec_sql(sql)
|
100
|
-
@connection.logger.debug(sql)
|
101
|
-
Mysql_c.mysql_query(@connection.db, sql)
|
102
|
-
end
|
103
|
-
|
104
|
-
end
|
105
|
-
|
106
|
-
class Reader < DataObject::Reader
|
107
|
-
|
108
|
-
def initialize(db, reader)
|
109
|
-
@reader = reader
|
110
|
-
unless @reader
|
111
|
-
if Mysql_c.mysql_field_count(db) == 0
|
112
|
-
@records_affected = Mysql_c.mysql_affected_rows(db)
|
113
|
-
close
|
114
|
-
else
|
115
|
-
raise UnknownError, "An unknown error has occured while trying to process a MySQL query.\n#{Mysql_c.mysql_error(db)}"
|
116
|
-
end
|
117
|
-
else
|
118
|
-
@field_count = @reader.field_count
|
119
|
-
@state = STATE_OPEN
|
120
|
-
|
121
|
-
@native_fields, @fields = Mysql_c.mysql_c_fetch_field_types(@reader, @field_count), Mysql_c.mysql_c_fetch_field_names(@reader, @field_count)
|
122
|
-
|
123
|
-
raise UnknownError, "An unknown error has occured while trying to process a MySQL query. There were no fields in the resultset\n#{Mysql_c.mysql_error(db)}" if @native_fields.empty?
|
124
|
-
|
125
|
-
@has_rows = !(@row = Mysql_c.mysql_c_fetch_row(@reader)).nil?
|
3
|
+
require 'do_jdbc-support' if RUBY_PLATFORM =~ /java/ # generic, shared JDBC support code
|
4
|
+
require 'do_mysql_ext' # the C/Java extension for this DO driver
|
5
|
+
require 'do_mysql/transaction'
|
6
|
+
|
7
|
+
if RUBY_PLATFORM =~ /java/
|
8
|
+
require 'do_jdbc/mysql' # the JDBC driver, packaged as a gem
|
9
|
+
|
10
|
+
# Another way of loading the JDBC Class. This seems to be more relaible
|
11
|
+
# than Class.forName() within the data_objects.Connection Java class,
|
12
|
+
# which is currently not working as expected.
|
13
|
+
require 'java'
|
14
|
+
import 'com.mysql.jdbc.Driver'
|
15
|
+
|
16
|
+
module DataObjects
|
17
|
+
module Mysql
|
18
|
+
class Connection
|
19
|
+
def self.pool_size
|
20
|
+
20
|
126
21
|
end
|
127
22
|
end
|
128
|
-
|
129
|
-
def close
|
130
|
-
if @state == STATE_OPEN
|
131
|
-
Mysql_c.mysql_free_result(@reader)
|
132
|
-
@state = STATE_CLOSED
|
133
|
-
true
|
134
|
-
else
|
135
|
-
false
|
136
|
-
end
|
137
|
-
end
|
138
|
-
|
139
|
-
def name(col)
|
140
|
-
super
|
141
|
-
@fields[col]
|
142
|
-
end
|
143
|
-
|
144
|
-
def get_index(name)
|
145
|
-
super
|
146
|
-
@fields.index(name)
|
147
|
-
end
|
148
|
-
|
149
|
-
def null?(idx)
|
150
|
-
super
|
151
|
-
@row[idx] == nil
|
152
|
-
end
|
153
|
-
|
154
|
-
def current_row
|
155
|
-
@row
|
156
|
-
end
|
157
|
-
|
158
|
-
def item(idx)
|
159
|
-
super
|
160
|
-
typecast(@row[idx], idx)
|
161
|
-
end
|
162
|
-
|
163
|
-
def next
|
164
|
-
super
|
165
|
-
@row = Mysql_c.mysql_c_fetch_row(@reader)
|
166
|
-
close if @row.nil?
|
167
|
-
@row ? true : nil
|
168
|
-
end
|
169
|
-
|
170
|
-
def each
|
171
|
-
return unless has_rows?
|
172
|
-
|
173
|
-
while(true) do
|
174
|
-
yield
|
175
|
-
break unless self.next
|
176
|
-
end
|
177
|
-
end
|
178
|
-
|
179
|
-
protected
|
180
|
-
def native_type(col)
|
181
|
-
super
|
182
|
-
TYPES[@native_fields[col].type]
|
183
|
-
end
|
184
|
-
|
185
|
-
def typecast(val, idx)
|
186
|
-
return nil if val.nil? || val == "NULL"
|
187
|
-
field = @native_fields[idx]
|
188
|
-
case TYPES[field]
|
189
|
-
when "NULL"
|
190
|
-
nil
|
191
|
-
when "TINY"
|
192
|
-
val != "0"
|
193
|
-
when "BIT"
|
194
|
-
val.to_i(2)
|
195
|
-
when "SHORT", "LONG", "INT24", "LONGLONG"
|
196
|
-
val == '' ? nil : val.to_i
|
197
|
-
when "DECIMAL", "NEWDECIMAL", "FLOAT", "DOUBLE", "YEAR"
|
198
|
-
val.to_f
|
199
|
-
when "TIMESTAMP", "DATETIME"
|
200
|
-
DateTime.parse(val) rescue nil
|
201
|
-
when "TIME"
|
202
|
-
DateTime.parse(val).to_time rescue nil
|
203
|
-
when "DATE"
|
204
|
-
Date.parse(val) rescue nil
|
205
|
-
else
|
206
|
-
val
|
207
|
-
end
|
208
|
-
end
|
209
|
-
end
|
210
|
-
|
211
|
-
class Command < DataObject::Command
|
212
|
-
|
213
|
-
def execute_reader(*args)
|
214
|
-
super
|
215
|
-
sql = escape_sql(args)
|
216
|
-
@connection.logger.debug { sql }
|
217
|
-
result = Mysql_c.mysql_query(@connection.db, sql)
|
218
|
-
# TODO: Real Error
|
219
|
-
raise QueryError, "Your query failed.\n#{Mysql_c.mysql_error(@connection.db)}\n#{@text}" unless result == 0
|
220
|
-
reader = Reader.new(@connection.db, Mysql_c.mysql_use_result(@connection.db))
|
221
|
-
if block_given?
|
222
|
-
result = yield(reader)
|
223
|
-
reader.close
|
224
|
-
result
|
225
|
-
else
|
226
|
-
reader
|
227
|
-
end
|
228
|
-
end
|
229
|
-
|
230
|
-
def execute_non_query(*args)
|
231
|
-
super
|
232
|
-
sql = escape_sql(args)
|
233
|
-
@connection.logger.debug { sql }
|
234
|
-
result = Mysql_c.mysql_query(@connection.db, sql)
|
235
|
-
raise QueryError, "Your query failed.\n#{Mysql_c.mysql_error(@connection.db)}\n#{@text}" unless result == 0
|
236
|
-
reader = Mysql_c.mysql_store_result(@connection.db)
|
237
|
-
raise QueryError, "You called execute_non_query on a query: #{@text}" if reader
|
238
|
-
rows_affected = Mysql_c.mysql_affected_rows(@connection.db)
|
239
|
-
Mysql_c.mysql_free_result(reader)
|
240
|
-
return ResultData.new(@connection, rows_affected, Mysql_c.mysql_insert_id(@connection.db))
|
241
|
-
end
|
242
|
-
|
243
|
-
def quote_time(value)
|
244
|
-
# TIMESTAMP() used for both time and datetime columns
|
245
|
-
quote_datetime(value)
|
246
|
-
end
|
247
|
-
|
248
|
-
def quote_datetime(value)
|
249
|
-
"TIMESTAMP('#{value.strftime("%Y-%m-%d %H:%M:%S")}')"
|
250
|
-
end
|
251
|
-
|
252
|
-
def quote_date(value)
|
253
|
-
"DATE('#{value.strftime("%Y-%m-%d")}')"
|
254
|
-
end
|
255
23
|
end
|
256
|
-
|
257
24
|
end
|
25
|
+
|
258
26
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
|
2
|
+
module DataObjects
|
3
|
+
|
4
|
+
module Mysql
|
5
|
+
|
6
|
+
class Transaction < DataObjects::Transaction
|
7
|
+
|
8
|
+
def finalize_transaction
|
9
|
+
cmd = "XA END '#{id}'"
|
10
|
+
connection.create_command(cmd).execute_non_query
|
11
|
+
end
|
12
|
+
|
13
|
+
def begin
|
14
|
+
cmd = "XA START '#{id}'"
|
15
|
+
connection.create_command(cmd).execute_non_query
|
16
|
+
end
|
17
|
+
|
18
|
+
def commit
|
19
|
+
cmd = "XA COMMIT '#{id}'"
|
20
|
+
connection.create_command(cmd).execute_non_query
|
21
|
+
end
|
22
|
+
|
23
|
+
def rollback
|
24
|
+
finalize_transaction
|
25
|
+
cmd = "XA ROLLBACK '#{id}'"
|
26
|
+
connection.create_command(cmd).execute_non_query
|
27
|
+
end
|
28
|
+
|
29
|
+
def rollback_prepared
|
30
|
+
cmd = "XA ROLLBACK '#{id}'"
|
31
|
+
connection.create_command(cmd).execute_non_query
|
32
|
+
end
|
33
|
+
|
34
|
+
def prepare
|
35
|
+
finalize_transaction
|
36
|
+
cmd = "XA PREPARE '#{id}'"
|
37
|
+
connection.create_command(cmd).execute_non_query
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
@@ -0,0 +1,241 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require Pathname(__FILE__).dirname.expand_path.parent + 'spec_helper'
|
3
|
+
|
4
|
+
describe DataObjects::Mysql do
|
5
|
+
include MysqlSpecHelpers
|
6
|
+
|
7
|
+
before :all do
|
8
|
+
setup_test_environment
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should expose the proper DataObjects classes" do
|
12
|
+
DataObjects::Mysql.const_get('Connection').should_not be_nil
|
13
|
+
DataObjects::Mysql.const_get('Command').should_not be_nil
|
14
|
+
DataObjects::Mysql.const_get('Result').should_not be_nil
|
15
|
+
DataObjects::Mysql.const_get('Reader').should_not be_nil
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should connect successfully via TCP" do
|
19
|
+
pending "Problems parsing regular connection URIs vs. JDBC URLs" if JRUBY
|
20
|
+
connection = DataObjects::Mysql::Connection.new("mysql://root@127.0.0.1:3306/do_mysql_test")
|
21
|
+
connection.should_not be_using_socket
|
22
|
+
end
|
23
|
+
|
24
|
+
#
|
25
|
+
# I comment this out partly to raise the issue for discussion. Socket files are afaik not supported under windows. Does this
|
26
|
+
# mean that we should test for it on unix boxes but not on windows boxes? Or does it mean that it should not be speced at all?
|
27
|
+
# It's not really a requirement, since all architectures that support MySQL also supports TCP connectsion, ne?
|
28
|
+
#
|
29
|
+
# it "should connect successfully via the socket file" do
|
30
|
+
# @connection = DataObjects::Mysql::Connection.new("mysql://root@localhost:3306/do_mysql_test/?socket=#{SOCKET_PATH}")
|
31
|
+
# @connection.should be_using_socket
|
32
|
+
# end
|
33
|
+
|
34
|
+
it "should return the current character set" do
|
35
|
+
pending "Problems parsing regular connection URIs vs. JDBC URLs" if JRUBY
|
36
|
+
connection = DataObjects::Mysql::Connection.new("mysql://root@localhost:3306/do_mysql_test")
|
37
|
+
connection.character_set.should == "utf8"
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should support changing the character set" do
|
41
|
+
pending "Problems parsing regular connection URIs vs. JDBC URLs" if JRUBY
|
42
|
+
connection = DataObjects::Mysql::Connection.new("mysql://root@localhost:3306/do_mysql_test/?charset=latin1")
|
43
|
+
connection.character_set.should == "latin1"
|
44
|
+
|
45
|
+
@connection = DataObjects::Mysql::Connection.new("mysql://root@localhost:3306/do_mysql_test/?charset=utf8")
|
46
|
+
@connection.character_set.should == "utf8"
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should raise an error when opened with an invalid server uri" do
|
50
|
+
pending "Problems parsing regular connection URIs vs. JDBC URLs" if JRUBY
|
51
|
+
def connecting_with(uri)
|
52
|
+
lambda { DataObjects::Mysql::Connection.new(uri) }
|
53
|
+
end
|
54
|
+
|
55
|
+
# Missing database name
|
56
|
+
connecting_with("mysql://root@localhost:3306/").should raise_error(MysqlError)
|
57
|
+
|
58
|
+
# Wrong port
|
59
|
+
connecting_with("mysql://root@localhost:666/").should raise_error(MysqlError)
|
60
|
+
|
61
|
+
# Bad Username
|
62
|
+
connecting_with("mysql://baduser@localhost:3306/").should raise_error(MysqlError)
|
63
|
+
|
64
|
+
# Bad Password
|
65
|
+
connecting_with("mysql://root:wrongpassword@localhost:3306/").should raise_error(MysqlError)
|
66
|
+
|
67
|
+
# Bad Database Name
|
68
|
+
connecting_with("mysql://root@localhost:3306/bad_database").should raise_error(MysqlError)
|
69
|
+
|
70
|
+
#
|
71
|
+
# Again, should socket even be speced if we don't support it across all platforms?
|
72
|
+
#
|
73
|
+
# Invalid Socket Path
|
74
|
+
#connecting_with("mysql://root@localhost:3306/do_mysql_test/?socket=/invalid/path/mysql.sock").should raise_error(MysqlError)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe DataObjects::Mysql::Connection do
|
79
|
+
include MysqlSpecHelpers
|
80
|
+
|
81
|
+
before :all do
|
82
|
+
setup_test_environment
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should raise an error when attempting to execute a bad query" do
|
86
|
+
lambda { @connection.create_command("INSERT INTO non_existant_table (tester) VALUES (1)").execute_non_query }.should raise_error(MysqlError)
|
87
|
+
lambda { @connection.create_command("SELECT * FROM non_existant table").execute_reader }.should raise_error(MysqlError)
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
describe DataObjects::Mysql::Reader do
|
93
|
+
include MysqlSpecHelpers
|
94
|
+
|
95
|
+
before :all do
|
96
|
+
setup_test_environment
|
97
|
+
end
|
98
|
+
|
99
|
+
it "should raise an error when you pass too many or too few types for the expected result set" do
|
100
|
+
lambda { select("SELECT name, fired_at FROM users", [String, DateTime, Integer]) }.should raise_error(MysqlError)
|
101
|
+
end
|
102
|
+
|
103
|
+
it "shouldn't raise an error when you pass NO types for the expected result set" do
|
104
|
+
lambda { select("SELECT name, fired_at FROM users", nil) }.should_not raise_error(MysqlError)
|
105
|
+
end
|
106
|
+
|
107
|
+
it "should return the proper number of fields" do
|
108
|
+
id = insert("INSERT INTO users (name) VALUES ('Billy Bob')")
|
109
|
+
select("SELECT id, name, fired_at FROM users WHERE id = ?", nil, id) do |reader|
|
110
|
+
reader.fields.size.should == 3
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
it "should raise an exception if .values is called after reading all available rows" do
|
115
|
+
select("SELECT * FROM widgets LIMIT 2") do |reader|
|
116
|
+
# select already calls next once for us
|
117
|
+
reader.next!
|
118
|
+
reader.next!
|
119
|
+
|
120
|
+
lambda { reader.values }.should raise_error(MysqlError)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
it "should fetch the proper number of rows" do
|
125
|
+
ids = [
|
126
|
+
insert("INSERT INTO users (name) VALUES ('Slappy Wilson')"),
|
127
|
+
insert("INSERT INTO users (name) VALUES ('Jumpy Jones')")
|
128
|
+
]
|
129
|
+
|
130
|
+
select("SELECT * FROM users WHERE id IN ?", nil, ids) do |reader|
|
131
|
+
# select already calls next once for us
|
132
|
+
reader.next!.should == true
|
133
|
+
reader.next!.should be_nil
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
it "should contain tainted strings" do
|
138
|
+
id = insert("INSERT INTO users (name) VALUES ('Cuppy Canes')")
|
139
|
+
|
140
|
+
select("SELECT name FROM users WHERE id = ?", nil, id) do |reader|
|
141
|
+
reader.values.first.should be_tainted
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
|
146
|
+
it "should return DB nulls as nil" do
|
147
|
+
id = insert("INSERT INTO users (name) VALUES (NULL)")
|
148
|
+
select("SELECT name from users WHERE name is null") do |reader|
|
149
|
+
reader.values[0].should == nil
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
it "should not convert empty strings to null" do
|
154
|
+
id = insert("INSERT INTO users (name) VALUES ('')")
|
155
|
+
select("SELECT name FROM users WHERE id = ?", [String], id) do |reader|
|
156
|
+
reader.values.first.should == ''
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
describe "Date, Time, and DateTime" do
|
161
|
+
|
162
|
+
it "should return DateTimes using the current locale's Time Zone" do
|
163
|
+
date = DateTime.now
|
164
|
+
id = insert("INSERT INTO users (name, fired_at) VALUES (?, ?)", 'Sam', date)
|
165
|
+
select("SELECT fired_at FROM users WHERE id = ?", [DateTime], id) do |reader|
|
166
|
+
reader.values.last.to_s.should == date.to_s
|
167
|
+
end
|
168
|
+
exec("DELETE FROM users WHERE id = ?", id)
|
169
|
+
end
|
170
|
+
|
171
|
+
now = DateTime.now
|
172
|
+
|
173
|
+
dates = [
|
174
|
+
now.new_offset( (-11 * 3600).to_r / 86400), # GMT -11:00
|
175
|
+
now.new_offset( (-9 * 3600 + 10 * 60).to_r / 86400), # GMT -9:10, contrived
|
176
|
+
now.new_offset( (-8 * 3600).to_r / 86400), # GMT -08:00
|
177
|
+
now.new_offset( (+3 * 3600).to_r / 86400), # GMT +03:00
|
178
|
+
now.new_offset( (+5 * 3600 + 30 * 60).to_r / 86400) # GMT +05:30 (New Delhi)
|
179
|
+
]
|
180
|
+
|
181
|
+
dates.each do |date|
|
182
|
+
it "should return #{date.to_s} offset to the current locale's Time Zone if they were inserted using a different timezone" do
|
183
|
+
pending "We don't support non-local date input yet"
|
184
|
+
|
185
|
+
dates.each do |date|
|
186
|
+
id = insert("INSERT INTO users (name, fired_at) VALUES (?, ?)", 'Sam', date)
|
187
|
+
|
188
|
+
select("SELECT fired_at FROM users WHERE id = ?", [DateTime], id) do |reader|
|
189
|
+
reader.values.last.to_s.should == now.to_s
|
190
|
+
end
|
191
|
+
|
192
|
+
exec("DELETE FROM users WHERE id = ?", id)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
end
|
198
|
+
|
199
|
+
describe "executing a non-query" do
|
200
|
+
it "should return a Result" do
|
201
|
+
command = @connection.create_command("INSERT INTO invoices (invoice_number) VALUES ('1234')")
|
202
|
+
result = command.execute_non_query
|
203
|
+
result.should be_kind_of(DataObjects::Mysql::Result)
|
204
|
+
end
|
205
|
+
|
206
|
+
it "should be able to determine the affected_rows" do
|
207
|
+
command = @connection.create_command("INSERT INTO invoices (invoice_number) VALUES ('1234')")
|
208
|
+
result = command.execute_non_query
|
209
|
+
result.to_i.should == 1
|
210
|
+
end
|
211
|
+
|
212
|
+
it "should yield the last inserted id" do
|
213
|
+
@connection.create_command("TRUNCATE TABLE invoices").execute_non_query
|
214
|
+
|
215
|
+
result = @connection.create_command("INSERT INTO invoices (invoice_number) VALUES ('1234')").execute_non_query
|
216
|
+
result.insert_id.should == 1
|
217
|
+
|
218
|
+
result = @connection.create_command("INSERT INTO invoices (invoice_number) VALUES ('3456')").execute_non_query
|
219
|
+
result.insert_id.should == 2
|
220
|
+
end
|
221
|
+
|
222
|
+
it "should be able to determine the affected_rows" do
|
223
|
+
[
|
224
|
+
"TRUNCATE TABLE invoices",
|
225
|
+
"INSERT INTO invoices (invoice_number) VALUES ('1234')",
|
226
|
+
"INSERT INTO invoices (invoice_number) VALUES ('1234')"
|
227
|
+
].each { |q| @connection.create_command(q).execute_non_query }
|
228
|
+
|
229
|
+
result = @connection.create_command("UPDATE invoices SET invoice_number = '3456'").execute_non_query
|
230
|
+
result.to_i.should == 2
|
231
|
+
end
|
232
|
+
|
233
|
+
it "should raise an error when executing an invalid query" do
|
234
|
+
command = @connection.create_command("UPDwhoopsATE invoices SET invoice_number = '3456'")
|
235
|
+
|
236
|
+
lambda { command.execute_non_query }.should raise_error(Exception)
|
237
|
+
end
|
238
|
+
|
239
|
+
end
|
240
|
+
|
241
|
+
end
|