do_mysql 0.2.4 → 0.9.2
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/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
|