do_mysql 0.9.9 → 0.9.10
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/.gitignore +0 -4
- data/Manifest.txt +2 -2
- data/Rakefile +39 -16
- data/ext-java/src/main/java/do_mysql/MySqlDriverDefinition.java +10 -0
- data/ext/{do_mysql_ext.c → do_mysql_ext/do_mysql_ext.c} +208 -220
- data/ext/{extconf.rb → do_mysql_ext/extconf.rb} +0 -0
- data/lib/do_mysql.rb +22 -6
- data/lib/do_mysql/version.rb +1 -1
- data/spec/integration/do_mysql_spec.rb +45 -46
- data/spec/integration/logging_spec.rb +3 -7
- data/spec/integration/quoting_spec.rb +38 -34
- data/spec/spec_helper.rb +14 -10
- metadata +6 -6
data/.gitignore
CHANGED
data/Manifest.txt
CHANGED
@@ -9,8 +9,8 @@ buildfile
|
|
9
9
|
ext-java/src/main/java/DoMysqlExtService.java
|
10
10
|
ext-java/src/main/java/do_mysql/MySqlDriverDefinition.java
|
11
11
|
ext/.gitignore
|
12
|
-
ext/do_mysql_ext.c
|
13
|
-
ext/extconf.rb
|
12
|
+
ext/do_mysql_ext/do_mysql_ext.c
|
13
|
+
ext/do_mysql_ext/extconf.rb
|
14
14
|
lib/do_mysql.rb
|
15
15
|
lib/do_mysql/transaction.rb
|
16
16
|
lib/do_mysql/version.rb
|
data/Rakefile
CHANGED
@@ -1,44 +1,67 @@
|
|
1
|
+
require 'pathname'
|
1
2
|
require 'rubygems'
|
2
3
|
require 'spec/rake/spectask'
|
3
|
-
require '
|
4
|
-
|
5
|
-
ROOT = Pathname(__FILE__).dirname.expand_path
|
4
|
+
require 'lib/do_mysql/version'
|
6
5
|
|
7
|
-
require "lib/do_mysql/version"
|
8
6
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
# is not entirely correct.
|
7
|
+
ROOT = Pathname(__FILE__).dirname.expand_path
|
8
|
+
JRUBY = RUBY_PLATFORM =~ /java/
|
9
|
+
WINDOWS = Gem.win_platform?
|
13
10
|
SUDO = (WINDOWS || JRUBY) ? '' : ('sudo' unless ENV['SUDOLESS'])
|
14
11
|
|
15
12
|
AUTHOR = "Scott Bauer"
|
16
13
|
EMAIL = "bauer.mail@gmail.com"
|
17
14
|
GEM_NAME = "do_mysql"
|
18
15
|
GEM_VERSION = DataObjects::Mysql::VERSION
|
19
|
-
GEM_DEPENDENCIES =
|
16
|
+
GEM_DEPENDENCIES = if JRUBY
|
17
|
+
[["data_objects", GEM_VERSION], ["do_jdbc", GEM_VERSION], ["jdbc-mysql", ">=5.0.4"]]
|
18
|
+
else
|
19
|
+
[["data_objects", GEM_VERSION]]
|
20
|
+
end
|
20
21
|
GEM_CLEAN = ['**/*.{o,so,bundle,log,a,gem,dSYM,obj,pdb,lib,def,exp,DS_Store}', 'ext/Makefile']
|
21
|
-
GEM_EXTRAS = { :extensions =>
|
22
|
+
GEM_EXTRAS = { :has_rdoc => false, :extensions => 'ext/do_mysql_ext/extconf.rb' }
|
22
23
|
|
23
24
|
PROJECT_NAME = "dorb"
|
24
25
|
PROJECT_URL = "http://rubyforge.org/projects/dorb"
|
25
26
|
PROJECT_DESCRIPTION = PROJECT_SUMMARY = "A DataObject.rb driver for MySQL"
|
26
27
|
|
27
|
-
|
28
|
+
JAVA_DRIVER = true
|
29
|
+
|
30
|
+
|
31
|
+
# RCov is run by default, except on the JRuby platform, or if NO_RCOV env is true
|
32
|
+
RUN_RCOV = JRUBY ? false : (ENV.has_key?('NO_RCOV') ? ENV['NO_RCOV'] != 'true' : true)
|
28
33
|
|
29
34
|
if (tasks_dir = ROOT.parent + 'tasks').directory?
|
30
35
|
require tasks_dir + 'hoe'
|
31
36
|
end
|
32
37
|
|
38
|
+
# compile the extension
|
39
|
+
begin
|
40
|
+
gem('rake-compiler')
|
41
|
+
require 'rake/extensiontask'
|
42
|
+
Rake::ExtensionTask.new('do_mysql_ext', HOE.spec)
|
43
|
+
rescue LoadError
|
44
|
+
warn "To cross-compile, install rake-compiler (gem install rake-compiler)"
|
45
|
+
if tasks_dir.directory?
|
46
|
+
require tasks_dir + 'ext_helper'
|
47
|
+
setup_extension('do_mysql_ext', HOE.spec)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def sudo_gem(cmd)
|
52
|
+
sh "#{SUDO} #{RUBY} -S gem #{cmd}", :verbose => false
|
53
|
+
end
|
54
|
+
|
33
55
|
# Installation
|
34
56
|
|
57
|
+
desc "Install #{GEM_NAME} #{GEM_VERSION}"
|
35
58
|
task :install => [ :package ] do
|
36
|
-
|
59
|
+
sudo_gem "install pkg/#{GEM_NAME}-#{GEM_VERSION} --no-update-sources"
|
37
60
|
end
|
38
61
|
|
39
|
-
desc "Uninstall #{GEM_NAME} #{GEM_VERSION}
|
62
|
+
desc "Uninstall #{GEM_NAME} #{GEM_VERSION}"
|
40
63
|
task :uninstall => [ :clobber ] do
|
41
|
-
|
64
|
+
sudo_gem "uninstall #{GEM_NAME} -v#{GEM_VERSION} -I -x"
|
42
65
|
end
|
43
66
|
|
44
67
|
# Specs
|
@@ -50,7 +73,7 @@ Spec::Rake::SpecTask.new(:spec) do |t|
|
|
50
73
|
t.spec_files = Pathname.glob(ENV['FILES'] || 'spec/**/*_spec.rb')
|
51
74
|
|
52
75
|
begin
|
53
|
-
t.rcov =
|
76
|
+
t.rcov = RUN_RCOV
|
54
77
|
t.rcov_opts << '--exclude' << 'spec'
|
55
78
|
t.rcov_opts << '--text-summary'
|
56
79
|
t.rcov_opts << '--sort' << 'coverage' << '--sort-reverse'
|
@@ -84,4 +107,4 @@ namespace :ci do
|
|
84
107
|
|
85
108
|
end
|
86
109
|
|
87
|
-
task :ci => ["ci:spec"]
|
110
|
+
task :ci => ["ci:spec"]
|
@@ -9,4 +9,14 @@ public class MySqlDriverDefinition extends AbstractDriverDefinition {
|
|
9
9
|
return true;
|
10
10
|
}
|
11
11
|
|
12
|
+
//@Override
|
13
|
+
public String quoteString(String str) {
|
14
|
+
StringBuffer quotedValue = new StringBuffer(str.length() + 2);
|
15
|
+
quotedValue.append("\'");
|
16
|
+
quotedValue.append(str.replaceAll("'", "\\\\'"));
|
17
|
+
// TODO: handle backslashes
|
18
|
+
quotedValue.append("\'");
|
19
|
+
return quotedValue.toString();
|
20
|
+
}
|
21
|
+
|
12
22
|
}
|
@@ -10,10 +10,10 @@
|
|
10
10
|
|
11
11
|
#define RUBY_CLASS(name) rb_const_get(rb_cObject, rb_intern(name))
|
12
12
|
#define RUBY_STRING(char_ptr) rb_str_new2(char_ptr)
|
13
|
-
#define TAINTED_STRING(name)
|
13
|
+
#define TAINTED_STRING(name, length) rb_tainted_str_new(name, length)
|
14
14
|
#define DRIVER_CLASS(klass, parent) (rb_define_class_under(mDOMysql, klass, parent))
|
15
15
|
#define CONST_GET(scope, constant) (rb_funcall(scope, ID_CONST_GET, 1, rb_str_new2(constant)))
|
16
|
-
#define CHECK_AND_RAISE(mysql_result_value) if (0 != mysql_result_value) { raise_mysql_error(connection, db, mysql_result_value); }
|
16
|
+
#define CHECK_AND_RAISE(mysql_result_value, str) if (0 != mysql_result_value) { raise_mysql_error(connection, db, mysql_result_value, str); }
|
17
17
|
#define PUTS(string) rb_funcall(rb_mKernel, rb_intern("puts"), 1, RUBY_STRING(string))
|
18
18
|
|
19
19
|
#ifndef RSTRING_PTR
|
@@ -38,7 +38,6 @@
|
|
38
38
|
static ID ID_TO_I;
|
39
39
|
static ID ID_TO_F;
|
40
40
|
static ID ID_TO_S;
|
41
|
-
static ID ID_PARSE;
|
42
41
|
static ID ID_TO_TIME;
|
43
42
|
static ID ID_NEW;
|
44
43
|
static ID ID_NEW_RATIONAL;
|
@@ -66,7 +65,6 @@ static VALUE rb_cDateTime;
|
|
66
65
|
static VALUE rb_cRational;
|
67
66
|
#endif
|
68
67
|
static VALUE rb_cBigDecimal;
|
69
|
-
static VALUE rb_cCGI;
|
70
68
|
|
71
69
|
// Classes that we'll build in Init
|
72
70
|
static VALUE mDOMysql;
|
@@ -77,53 +75,59 @@ static VALUE cReader;
|
|
77
75
|
static VALUE eMysqlError;
|
78
76
|
|
79
77
|
// Figures out what we should cast a given mysql field type to
|
80
|
-
static
|
78
|
+
static VALUE infer_ruby_type(MYSQL_FIELD *field) {
|
81
79
|
|
82
|
-
char*
|
80
|
+
char* ruby_type;
|
83
81
|
|
84
82
|
switch(field->type) {
|
85
83
|
case MYSQL_TYPE_NULL: {
|
86
|
-
|
84
|
+
ruby_type = NULL;
|
87
85
|
break;
|
88
86
|
}
|
89
87
|
case MYSQL_TYPE_TINY: {
|
90
|
-
|
88
|
+
ruby_type = "TrueClass";
|
91
89
|
break;
|
92
90
|
}
|
91
|
+
case MYSQL_TYPE_BIT:
|
93
92
|
case MYSQL_TYPE_SHORT:
|
94
93
|
case MYSQL_TYPE_LONG:
|
95
94
|
case MYSQL_TYPE_INT24:
|
96
95
|
case MYSQL_TYPE_LONGLONG:
|
97
96
|
case MYSQL_TYPE_YEAR: {
|
98
|
-
|
97
|
+
ruby_type = "Fixnum";
|
99
98
|
break;
|
100
99
|
}
|
101
100
|
case MYSQL_TYPE_DECIMAL:
|
101
|
+
case MYSQL_TYPE_NEWDECIMAL: {
|
102
|
+
ruby_type = "BigDecimal";
|
103
|
+
break;
|
104
|
+
}
|
102
105
|
case MYSQL_TYPE_FLOAT:
|
103
106
|
case MYSQL_TYPE_DOUBLE: {
|
104
|
-
|
107
|
+
ruby_type = "Float";
|
105
108
|
break;
|
106
109
|
}
|
107
110
|
case MYSQL_TYPE_TIMESTAMP:
|
108
111
|
case MYSQL_TYPE_DATETIME: {
|
109
|
-
|
112
|
+
ruby_type = "DateTime";
|
110
113
|
break;
|
111
114
|
}
|
112
115
|
case MYSQL_TYPE_TIME: {
|
113
|
-
|
116
|
+
ruby_type = "DateTime";
|
114
117
|
break;
|
115
118
|
}
|
116
|
-
case MYSQL_TYPE_DATE:
|
117
|
-
|
119
|
+
case MYSQL_TYPE_DATE:
|
120
|
+
case MYSQL_TYPE_NEWDATE: {
|
121
|
+
ruby_type = "Date";
|
118
122
|
break;
|
119
123
|
}
|
120
124
|
default: {
|
121
|
-
|
122
|
-
|
125
|
+
ruby_type = "String";
|
126
|
+
break;
|
123
127
|
}
|
124
128
|
}
|
125
129
|
|
126
|
-
return
|
130
|
+
return rb_str_new2(ruby_type);
|
127
131
|
}
|
128
132
|
|
129
133
|
// Find the greatest common denominator and reduce the provided numerator and denominator.
|
@@ -157,6 +161,15 @@ static VALUE seconds_to_offset(long seconds_offset) {
|
|
157
161
|
return rb_funcall(rb_cRational, rb_intern("new!"), 2, rb_ll2inum(num), rb_ll2inum(den));
|
158
162
|
}
|
159
163
|
|
164
|
+
static VALUE timezone_to_offset(int hour_offset, int minute_offset) {
|
165
|
+
do_int64 seconds = 0;
|
166
|
+
|
167
|
+
seconds += hour_offset * 3600;
|
168
|
+
seconds += minute_offset * 60;
|
169
|
+
|
170
|
+
return seconds_to_offset(seconds);
|
171
|
+
}
|
172
|
+
|
160
173
|
static VALUE parse_date(const char *date) {
|
161
174
|
int year, month, day;
|
162
175
|
int jd, ajd;
|
@@ -193,43 +206,78 @@ static VALUE parse_time(const char *date) {
|
|
193
206
|
return rb_funcall(rb_cTime, rb_intern("local"), 7, INT2NUM(year), INT2NUM(month), INT2NUM(day), INT2NUM(hour), INT2NUM(min), INT2NUM(sec), INT2NUM(usec));
|
194
207
|
}
|
195
208
|
|
196
|
-
static VALUE parse_date_time(const char *
|
209
|
+
static VALUE parse_date_time(const char *date) {
|
197
210
|
VALUE ajd, offset;
|
198
211
|
|
199
|
-
int year, month, day, hour, min, sec;
|
212
|
+
int year, month, day, hour, min, sec, usec, hour_offset, minute_offset;
|
200
213
|
int jd;
|
201
214
|
do_int64 num, den;
|
202
215
|
|
216
|
+
|
217
|
+
long int gmt_offset;
|
218
|
+
int is_dst;
|
219
|
+
|
203
220
|
time_t rawtime;
|
204
221
|
struct tm * timeinfo;
|
205
222
|
|
206
|
-
|
207
|
-
sscanf(date_time, "%4d-%2d-%2d %2d:%2d:%2d", &year, &month, &day, &hour, &min, &sec);
|
223
|
+
int tokens_read, max_tokens;
|
208
224
|
|
209
|
-
|
225
|
+
if ( strcmp(date, "") == 0 ) {
|
226
|
+
return Qnil;
|
227
|
+
}
|
210
228
|
|
211
|
-
|
212
|
-
|
213
|
-
|
229
|
+
if (0 != strchr(date, '.')) {
|
230
|
+
// This is a datetime with sub-second precision
|
231
|
+
tokens_read = sscanf(date, "%4d-%2d-%2d%*c%2d:%2d:%2d.%d%3d:%2d", &year, &month, &day, &hour, &min, &sec, &usec, &hour_offset, &minute_offset);
|
232
|
+
max_tokens = 9;
|
233
|
+
} else {
|
234
|
+
// This is a datetime second precision
|
235
|
+
tokens_read = sscanf(date, "%4d-%2d-%2d%*c%2d:%2d:%2d%3d:%2d", &year, &month, &day, &hour, &min, &sec, &hour_offset, &minute_offset);
|
236
|
+
max_tokens = 8;
|
237
|
+
}
|
214
238
|
|
215
|
-
|
216
|
-
|
217
|
-
|
239
|
+
if (max_tokens == tokens_read) {
|
240
|
+
// We read the Date, Time, and Timezone info
|
241
|
+
minute_offset *= hour_offset < 0 ? -1 : 1;
|
242
|
+
} else if ((max_tokens - 1) == tokens_read) {
|
243
|
+
// We read the Date and Time, but no Minute Offset
|
244
|
+
minute_offset = 0;
|
245
|
+
} else if (tokens_read == 3) {
|
246
|
+
return parse_date(date);
|
247
|
+
} else if (tokens_read >= (max_tokens - 3)) {
|
248
|
+
// We read the Date and Time, default to the current locale's offset
|
218
249
|
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
250
|
+
// Get localtime
|
251
|
+
time(&rawtime);
|
252
|
+
timeinfo = localtime(&rawtime);
|
253
|
+
|
254
|
+
is_dst = timeinfo->tm_isdst * 3600;
|
255
|
+
|
256
|
+
// Reset to GM Time
|
257
|
+
timeinfo = gmtime(&rawtime);
|
258
|
+
|
259
|
+
gmt_offset = mktime(timeinfo) - rawtime;
|
260
|
+
|
261
|
+
if ( is_dst > 0 )
|
262
|
+
gmt_offset -= is_dst;
|
263
|
+
|
264
|
+
hour_offset = -(gmt_offset / 3600);
|
265
|
+
minute_offset = -(gmt_offset % 3600 / 60);
|
223
266
|
|
224
|
-
// Modify the numerator so when we apply the timezone everything works out
|
225
|
-
if (timeinfo->tm_gmtoff < 0) {
|
226
|
-
// If the Timezone is behind UTC, we need to add the time offset
|
227
|
-
num += (hour_offset * 1440) + (minute_offset * 24);
|
228
267
|
} else {
|
229
|
-
//
|
230
|
-
|
268
|
+
// Something went terribly wrong
|
269
|
+
rb_raise(eMysqlError, "Couldn't parse date: %s", date);
|
231
270
|
}
|
232
271
|
|
272
|
+
jd = jd_from_date(year, month, day);
|
273
|
+
|
274
|
+
// Generate ajd with fractional days for the time
|
275
|
+
// Extracted from Date#jd_to_ajd, Date#day_fraction_to_time, and Rational#+ and #-
|
276
|
+
num = (hour * 1440) + (min * 24);
|
277
|
+
|
278
|
+
// Modify the numerator so when we apply the timezone everything works out
|
279
|
+
num -= (hour_offset * 1440) + (minute_offset * 24);
|
280
|
+
|
233
281
|
den = (24 * 1440);
|
234
282
|
reduce(&num, &den);
|
235
283
|
|
@@ -239,33 +287,33 @@ static VALUE parse_date_time(const char *date_time) {
|
|
239
287
|
|
240
288
|
num = (jd * den) + num;
|
241
289
|
|
242
|
-
num = num * 2
|
290
|
+
num = num * 2;
|
291
|
+
num = num - den;
|
243
292
|
den = den * 2;
|
293
|
+
|
244
294
|
reduce(&num, &den);
|
245
295
|
|
246
296
|
ajd = rb_funcall(rb_cRational, rb_intern("new!"), 2, rb_ull2inum(num), rb_ull2inum(den));
|
247
|
-
|
248
|
-
// Calculate the offset using the seconds from GMT
|
249
|
-
offset = seconds_to_offset(timeinfo->tm_gmtoff);
|
297
|
+
offset = timezone_to_offset(hour_offset, minute_offset);
|
250
298
|
|
251
299
|
return rb_funcall(rb_cDateTime, ID_NEW_DATE, 3, ajd, offset, INT2NUM(2299161));
|
252
300
|
}
|
253
301
|
|
254
302
|
// Convert C-string to a Ruby instance of Ruby type "type"
|
255
|
-
static VALUE typecast(const char* value, char* type) {
|
303
|
+
static VALUE typecast(const char* value, unsigned long length, char* type) {
|
256
304
|
if (NULL == value)
|
257
305
|
return Qnil;
|
258
306
|
|
259
307
|
if ( strcmp(type, "Class") == 0) {
|
260
|
-
return rb_funcall(mDO, rb_intern("find_const"), 1, TAINTED_STRING(value));
|
308
|
+
return rb_funcall(mDO, rb_intern("find_const"), 1, TAINTED_STRING(value, length));
|
261
309
|
} else if ( strcmp(type, "Integer") == 0 || strcmp(type, "Fixnum") == 0 || strcmp(type, "Bignum") == 0 ) {
|
262
310
|
return rb_cstr2inum(value, 10);
|
263
311
|
} else if (0 == strcmp("String", type)) {
|
264
|
-
return TAINTED_STRING(value);
|
312
|
+
return TAINTED_STRING(value, length);
|
265
313
|
} else if (0 == strcmp("Float", type) ) {
|
266
314
|
return rb_float_new(rb_cstr_to_dbl(value, Qfalse));
|
267
315
|
} else if (0 == strcmp("BigDecimal", type) ) {
|
268
|
-
return rb_funcall(rb_cBigDecimal, ID_NEW, 1, TAINTED_STRING(value));
|
316
|
+
return rb_funcall(rb_cBigDecimal, ID_NEW, 1, TAINTED_STRING(value, length));
|
269
317
|
} else if (0 == strcmp("TrueClass", type) || 0 == strcmp("FalseClass", type)) {
|
270
318
|
return (0 == value || 0 == strcmp("0", value)) ? Qfalse : Qtrue;
|
271
319
|
} else if (0 == strcmp("Date", type)) {
|
@@ -275,142 +323,124 @@ static VALUE typecast(const char* value, char* type) {
|
|
275
323
|
} else if (0 == strcmp("Time", type)) {
|
276
324
|
return parse_time(value);
|
277
325
|
} else {
|
278
|
-
return TAINTED_STRING(value);
|
326
|
+
return TAINTED_STRING(value, length);
|
279
327
|
}
|
280
328
|
}
|
281
329
|
|
282
|
-
static void data_objects_debug(VALUE string) {
|
330
|
+
static void data_objects_debug(VALUE string, struct timeval* start) {
|
331
|
+
struct timeval stop;
|
332
|
+
char *message;
|
333
|
+
|
334
|
+
char *query = RSTRING_PTR(string);
|
335
|
+
int length = RSTRING_LEN(string);
|
336
|
+
char total_time[32];
|
337
|
+
do_int64 duration = 0;
|
338
|
+
|
283
339
|
VALUE logger = rb_funcall(mDOMysql, ID_LOGGER, 0);
|
284
340
|
int log_level = NUM2INT(rb_funcall(logger, ID_LEVEL, 0));
|
285
341
|
|
286
342
|
if (0 == log_level) {
|
287
|
-
|
343
|
+
gettimeofday(&stop, NULL);
|
344
|
+
|
345
|
+
duration = (stop.tv_sec - start->tv_sec) * 1000000 + stop.tv_usec - start->tv_usec;
|
346
|
+
if(stop.tv_usec < start->tv_usec) {
|
347
|
+
duration += 1000000;
|
348
|
+
}
|
349
|
+
|
350
|
+
snprintf(total_time, 32, "%.6f", duration / 1000000.0);
|
351
|
+
message = (char *)calloc(length + strlen(total_time) + 4, sizeof(char));
|
352
|
+
snprintf(message, length + strlen(total_time) + 4, "(%s) %s", total_time, query);
|
353
|
+
rb_funcall(logger, ID_DEBUG, 1, rb_str_new(message, length + strlen(total_time) + 3));
|
288
354
|
}
|
289
355
|
}
|
356
|
+
static void raise_mysql_error(VALUE connection, MYSQL *db, int mysql_error_code, char* str) {
|
357
|
+
char *mysql_error_message = (char *)mysql_error(db);
|
290
358
|
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
359
|
+
if(mysql_error_code == 1) {
|
360
|
+
mysql_error_code = mysql_errno(db);
|
361
|
+
}
|
362
|
+
if(str) {
|
363
|
+
rb_raise(eMysqlError, "(mysql_errno=%04d, sql_state=%s) %s\nQuery: %s", mysql_error_code, mysql_sqlstate(db), mysql_error_message, str);
|
364
|
+
} else {
|
365
|
+
rb_raise(eMysqlError, "(mysql_errno=%04d, sql_state=%s) %s", mysql_error_code, mysql_sqlstate(db), mysql_error_message);
|
298
366
|
}
|
299
367
|
}
|
300
368
|
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
switch(mysql_error_code) {
|
313
|
-
case CR_UNKNOWN_ERROR:
|
314
|
-
case CR_SOCKET_CREATE_ERROR:
|
315
|
-
case CR_CONNECTION_ERROR:
|
316
|
-
case CR_CONN_HOST_ERROR:
|
317
|
-
case CR_IPSOCK_ERROR:
|
318
|
-
case CR_UNKNOWN_HOST:
|
319
|
-
case CR_SERVER_GONE_ERROR:
|
320
|
-
case CR_VERSION_ERROR:
|
321
|
-
case CR_OUT_OF_MEMORY:
|
322
|
-
case CR_WRONG_HOST_INFO:
|
323
|
-
case CR_LOCALHOST_CONNECTION:
|
324
|
-
case CR_TCP_CONNECTION:
|
325
|
-
case CR_SERVER_HANDSHAKE_ERR:
|
326
|
-
case CR_SERVER_LOST:
|
327
|
-
case CR_COMMANDS_OUT_OF_SYNC:
|
328
|
-
case CR_NAMEDPIPE_CONNECTION:
|
329
|
-
case CR_NAMEDPIPEWAIT_ERROR:
|
330
|
-
case CR_NAMEDPIPEOPEN_ERROR:
|
331
|
-
case CR_NAMEDPIPESETSTATE_ERROR:
|
332
|
-
case CR_CANT_READ_CHARSET:
|
333
|
-
case CR_NET_PACKET_TOO_LARGE:
|
334
|
-
case CR_EMBEDDED_CONNECTION:
|
335
|
-
case CR_PROBE_SLAVE_STATUS:
|
336
|
-
case CR_PROBE_SLAVE_HOSTS:
|
337
|
-
case CR_PROBE_SLAVE_CONNECT:
|
338
|
-
case CR_PROBE_MASTER_CONNECT:
|
339
|
-
case CR_SSL_CONNECTION_ERROR:
|
340
|
-
case CR_MALFORMED_PACKET:
|
341
|
-
case CR_WRONG_LICENSE:
|
342
|
-
case CR_NULL_POINTER:
|
343
|
-
case CR_NO_PREPARE_STMT:
|
344
|
-
case CR_PARAMS_NOT_BOUND:
|
345
|
-
case CR_DATA_TRUNCATED:
|
346
|
-
case CR_NO_PARAMETERS_EXISTS:
|
347
|
-
case CR_INVALID_PARAMETER_NO:
|
348
|
-
case CR_INVALID_BUFFER_USE:
|
349
|
-
case CR_UNSUPPORTED_PARAM_TYPE:
|
350
|
-
case CR_SHARED_MEMORY_CONNECTION:
|
351
|
-
case CR_SHARED_MEMORY_CONNECT_REQUEST_ERROR:
|
352
|
-
case CR_SHARED_MEMORY_CONNECT_ANSWER_ERROR:
|
353
|
-
case CR_SHARED_MEMORY_CONNECT_FILE_MAP_ERROR:
|
354
|
-
case CR_SHARED_MEMORY_CONNECT_MAP_ERROR:
|
355
|
-
case CR_SHARED_MEMORY_FILE_MAP_ERROR:
|
356
|
-
case CR_SHARED_MEMORY_MAP_ERROR:
|
357
|
-
case CR_SHARED_MEMORY_EVENT_ERROR:
|
358
|
-
case CR_SHARED_MEMORY_CONNECT_ABANDONED_ERROR:
|
359
|
-
case CR_SHARED_MEMORY_CONNECT_SET_ERROR:
|
360
|
-
case CR_CONN_UNKNOW_PROTOCOL:
|
361
|
-
case CR_INVALID_CONN_HANDLE:
|
362
|
-
case CR_SECURE_AUTH:
|
363
|
-
case CR_FETCH_CANCELED:
|
364
|
-
case CR_NO_DATA:
|
365
|
-
case CR_NO_STMT_METADATA:
|
366
|
-
#if MYSQL_VERSION_ID >= 50000
|
367
|
-
case CR_NO_RESULT_SET:
|
368
|
-
case CR_NOT_IMPLEMENTED:
|
369
|
-
#endif
|
370
|
-
{
|
371
|
-
break;
|
372
|
-
}
|
373
|
-
default: {
|
374
|
-
// Hmmm
|
375
|
-
break;
|
376
|
-
}
|
369
|
+
static char * get_uri_option(VALUE query_hash, char * key) {
|
370
|
+
VALUE query_value;
|
371
|
+
char * value = NULL;
|
372
|
+
|
373
|
+
if(!rb_obj_is_kind_of(query_hash, rb_cHash)) { return NULL; }
|
374
|
+
|
375
|
+
query_value = rb_hash_aref(query_hash, RUBY_STRING(key));
|
376
|
+
|
377
|
+
if (Qnil != query_value) {
|
378
|
+
value = StringValuePtr(query_value);
|
377
379
|
}
|
378
380
|
|
379
|
-
|
380
|
-
rb_raise(eMysqlError, error_message);
|
381
|
+
return value;
|
381
382
|
}
|
382
383
|
|
383
|
-
|
384
|
-
|
385
|
-
|
384
|
+
static MYSQL_RES* cCommand_execute_async(VALUE self, MYSQL* db, VALUE query) {
|
385
|
+
int socket_fd;
|
386
|
+
int retval;
|
387
|
+
fd_set rset;
|
388
|
+
struct timeval start;
|
389
|
+
char* str = RSTRING_PTR(query);
|
390
|
+
int len = RSTRING_LEN(query);
|
391
|
+
|
392
|
+
VALUE connection = rb_iv_get(self, "@connection");
|
393
|
+
|
394
|
+
retval = mysql_ping(db);
|
395
|
+
if(retval == CR_SERVER_GONE_ERROR) {
|
396
|
+
CHECK_AND_RAISE(retval, "Mysql server has gone away. \
|
397
|
+
Please report this issue to the Datamapper project. \
|
398
|
+
Specify your at least your MySQL version when filing a ticket");
|
399
|
+
}
|
400
|
+
retval = mysql_send_query(db, str, len);
|
386
401
|
|
387
|
-
|
402
|
+
CHECK_AND_RAISE(retval, str);
|
403
|
+
gettimeofday(&start, NULL);
|
388
404
|
|
389
|
-
|
390
|
-
|
405
|
+
socket_fd = db->net.fd;
|
406
|
+
|
407
|
+
for(;;) {
|
408
|
+
FD_ZERO(&rset);
|
409
|
+
FD_SET(socket_fd, &rset);
|
410
|
+
|
411
|
+
retval = rb_thread_select(socket_fd + 1, &rset, NULL, NULL, NULL);
|
391
412
|
|
392
|
-
|
413
|
+
if (retval < 0) {
|
414
|
+
rb_sys_fail(0);
|
415
|
+
}
|
393
416
|
|
394
|
-
|
395
|
-
|
417
|
+
if (retval == 0) {
|
418
|
+
continue;
|
419
|
+
}
|
396
420
|
|
397
|
-
|
398
|
-
|
421
|
+
if (db->status == MYSQL_STATUS_READY) {
|
422
|
+
break;
|
423
|
+
}
|
399
424
|
}
|
400
425
|
|
401
|
-
|
426
|
+
retval = mysql_read_query_result(db);
|
427
|
+
CHECK_AND_RAISE(retval, str);
|
428
|
+
|
429
|
+
data_objects_debug(query, &start);
|
430
|
+
|
431
|
+
return mysql_store_result(db);
|
402
432
|
}
|
403
433
|
|
404
434
|
static VALUE cConnection_initialize(VALUE self, VALUE uri) {
|
405
|
-
VALUE r_host, r_user, r_password, r_path,
|
435
|
+
VALUE r_host, r_user, r_password, r_path, r_query, r_port;
|
406
436
|
|
407
437
|
char *host = "localhost", *user = "root", *password = NULL, *path;
|
408
438
|
char *database = "", *socket = NULL;
|
409
|
-
char *
|
439
|
+
char *encoding = NULL;
|
410
440
|
|
411
441
|
int port = 3306;
|
412
442
|
unsigned long client_flags = 0;
|
413
|
-
int
|
443
|
+
int encoding_error;
|
414
444
|
|
415
445
|
MYSQL *db = 0, *result;
|
416
446
|
db = (MYSQL *)mysql_init(NULL);
|
@@ -432,7 +462,6 @@ static VALUE cConnection_initialize(VALUE self, VALUE uri) {
|
|
432
462
|
password = StringValuePtr(r_password);
|
433
463
|
}
|
434
464
|
|
435
|
-
|
436
465
|
r_path = rb_funcall(uri, rb_intern("path"), 0);
|
437
466
|
path = StringValuePtr(r_path);
|
438
467
|
if (Qnil != r_path) {
|
@@ -444,11 +473,11 @@ static VALUE cConnection_initialize(VALUE self, VALUE uri) {
|
|
444
473
|
}
|
445
474
|
|
446
475
|
// Pull the querystring off the URI
|
447
|
-
|
476
|
+
r_query = rb_funcall(uri, rb_intern("query"), 0);
|
448
477
|
|
449
478
|
// Check to see if we're on the db machine. If so, try to use the socket
|
450
479
|
if (0 == strcasecmp(host, "localhost")) {
|
451
|
-
socket = get_uri_option(
|
480
|
+
socket = get_uri_option(r_query, "socket");
|
452
481
|
if (NULL != socket) {
|
453
482
|
rb_iv_set(self, "@using_socket", Qtrue);
|
454
483
|
}
|
@@ -459,7 +488,9 @@ static VALUE cConnection_initialize(VALUE self, VALUE uri) {
|
|
459
488
|
port = NUM2INT(r_port);
|
460
489
|
}
|
461
490
|
|
462
|
-
|
491
|
+
encoding = get_uri_option(r_query, "encoding");
|
492
|
+
if (!encoding) { encoding = get_uri_option(r_query, "charset"); }
|
493
|
+
if (!encoding) { encoding = "utf8"; }
|
463
494
|
|
464
495
|
// If ssl? {
|
465
496
|
// mysql_ssl_set(db, key, cert, ca, capath, cipher)
|
@@ -480,20 +511,19 @@ static VALUE cConnection_initialize(VALUE self, VALUE uri) {
|
|
480
511
|
);
|
481
512
|
|
482
513
|
if (NULL == result) {
|
483
|
-
raise_mysql_error(Qnil, db, -1);
|
484
|
-
}
|
485
|
-
|
486
|
-
if (NULL == charset) {
|
487
|
-
charset = (char*)calloc(5, sizeof(char));
|
488
|
-
strcpy(charset, "utf8");
|
514
|
+
raise_mysql_error(Qnil, db, -1, NULL);
|
489
515
|
}
|
490
516
|
|
491
517
|
// Set the connections character set
|
492
|
-
|
493
|
-
if (0 !=
|
494
|
-
raise_mysql_error(Qnil, db,
|
518
|
+
encoding_error = mysql_set_character_set(db, encoding);
|
519
|
+
if (0 != encoding_error) {
|
520
|
+
raise_mysql_error(Qnil, db, encoding_error, NULL);
|
495
521
|
}
|
496
522
|
|
523
|
+
// Disable sql_auto_is_null
|
524
|
+
cCommand_execute_async(self, db, rb_str_new2("SET sql_auto_is_null = 0"));
|
525
|
+
cCommand_execute_async(self, db, rb_str_new2("SET SESSION sql_mode = 'ANSI,NO_AUTO_VALUE_ON_ZERO,NO_DIR_IN_CREATE,NO_ENGINE_SUBSTITUTION,NO_UNSIGNED_SUBTRACTION,ONLY_FULL_GROUP_BY,TRADITIONAL'"));
|
526
|
+
|
497
527
|
rb_iv_set(self, "@uri", uri);
|
498
528
|
rb_iv_set(self, "@connection", Data_Wrap_Struct(rb_cObject, 0, 0, db));
|
499
529
|
|
@@ -504,16 +534,16 @@ static VALUE cConnection_character_set(VALUE self) {
|
|
504
534
|
VALUE connection_container = rb_iv_get(self, "@connection");
|
505
535
|
MYSQL *db;
|
506
536
|
|
507
|
-
const char *
|
537
|
+
const char *encoding;
|
508
538
|
|
509
539
|
if (Qnil == connection_container)
|
510
540
|
return Qfalse;
|
511
541
|
|
512
542
|
db = DATA_PTR(connection_container);
|
513
543
|
|
514
|
-
|
544
|
+
encoding = mysql_character_set_name(db);
|
515
545
|
|
516
|
-
return RUBY_STRING(
|
546
|
+
return RUBY_STRING(encoding);
|
517
547
|
}
|
518
548
|
|
519
549
|
static VALUE cConnection_is_using_socket(VALUE self) {
|
@@ -573,7 +603,8 @@ VALUE cCommand_quote_date(VALUE self, VALUE value) {
|
|
573
603
|
|
574
604
|
static VALUE cCommand_quote_string(VALUE self, VALUE string) {
|
575
605
|
MYSQL *db = DATA_PTR(rb_iv_get(rb_iv_get(self, "@connection"), "@connection"));
|
576
|
-
const char *source =
|
606
|
+
const char *source = RSTRING_PTR(string);
|
607
|
+
int source_len = RSTRING_LEN(string);
|
577
608
|
char *escaped;
|
578
609
|
VALUE result;
|
579
610
|
|
@@ -582,10 +613,10 @@ static VALUE cCommand_quote_string(VALUE self, VALUE string) {
|
|
582
613
|
// Allocate space for the escaped version of 'string'. Use + 3 allocate space for null term.
|
583
614
|
// and the leading and trailing single-quotes.
|
584
615
|
// Thanks to http://www.browardphp.com/mysql_manual_en/manual_MySQL_APIs.html#mysql_real_escape_string
|
585
|
-
escaped = (char *)calloc(
|
616
|
+
escaped = (char *)calloc(source_len * 2 + 3, sizeof(char));
|
586
617
|
|
587
|
-
// Escape 'source' using the current
|
588
|
-
quoted_length = mysql_real_escape_string(db, escaped + 1, source,
|
618
|
+
// Escape 'source' using the current encoding in use on the conection 'db'
|
619
|
+
quoted_length = mysql_real_escape_string(db, escaped + 1, source, source_len);
|
589
620
|
|
590
621
|
// Wrap the escaped string in single-quotes, this is DO's convention
|
591
622
|
escaped[0] = escaped[quoted_length + 1] = '\'';
|
@@ -607,46 +638,6 @@ static VALUE build_query_from_args(VALUE klass, int count, VALUE *args) {
|
|
607
638
|
return query;
|
608
639
|
}
|
609
640
|
|
610
|
-
static MYSQL_RES* cCommand_execute_async(VALUE self, MYSQL* db, VALUE query) {
|
611
|
-
int socket_fd;
|
612
|
-
int retval;
|
613
|
-
fd_set rset;
|
614
|
-
char* str = RSTRING_PTR(query);
|
615
|
-
int len = RSTRING_LEN(query);
|
616
|
-
|
617
|
-
VALUE connection = rb_iv_get(self, "@connection");
|
618
|
-
|
619
|
-
retval = mysql_send_query(db, str, len);
|
620
|
-
data_objects_debug(query);
|
621
|
-
CHECK_AND_RAISE(retval);
|
622
|
-
|
623
|
-
socket_fd = db->net.fd;
|
624
|
-
|
625
|
-
for(;;) {
|
626
|
-
FD_ZERO(&rset);
|
627
|
-
FD_SET(socket_fd, &rset);
|
628
|
-
|
629
|
-
retval = rb_thread_select(socket_fd + 1, &rset, NULL, NULL, NULL);
|
630
|
-
|
631
|
-
if (retval < 0) {
|
632
|
-
rb_sys_fail(0);
|
633
|
-
}
|
634
|
-
|
635
|
-
if (retval == 0) {
|
636
|
-
continue;
|
637
|
-
}
|
638
|
-
|
639
|
-
if (db->status == MYSQL_STATUS_READY) {
|
640
|
-
break;
|
641
|
-
}
|
642
|
-
}
|
643
|
-
|
644
|
-
retval = mysql_read_query_result(db);
|
645
|
-
CHECK_AND_RAISE(retval);
|
646
|
-
|
647
|
-
return mysql_store_result(db);
|
648
|
-
}
|
649
|
-
|
650
641
|
static VALUE cCommand_execute_non_query(int argc, VALUE *argv, VALUE self) {
|
651
642
|
VALUE query;
|
652
643
|
|
@@ -676,14 +667,15 @@ static VALUE cCommand_execute_reader(int argc, VALUE *argv, VALUE self) {
|
|
676
667
|
VALUE query, reader;
|
677
668
|
VALUE field_names, field_types;
|
678
669
|
|
679
|
-
int field_count;
|
670
|
+
unsigned int field_count;
|
680
671
|
int i;
|
681
672
|
|
682
673
|
char guess_default_field_types = 0;
|
683
674
|
VALUE connection = rb_iv_get(self, "@connection");
|
684
675
|
VALUE mysql_connection = rb_iv_get(connection, "@connection");
|
685
|
-
if (Qnil == mysql_connection)
|
676
|
+
if (Qnil == mysql_connection) {
|
686
677
|
rb_raise(eMysqlError, "This connection has already been closed.");
|
678
|
+
}
|
687
679
|
|
688
680
|
MYSQL *db = DATA_PTR(mysql_connection);
|
689
681
|
|
@@ -698,7 +690,7 @@ static VALUE cCommand_execute_reader(int argc, VALUE *argv, VALUE self) {
|
|
698
690
|
return Qnil;
|
699
691
|
}
|
700
692
|
|
701
|
-
field_count =
|
693
|
+
field_count = mysql_field_count(db);
|
702
694
|
|
703
695
|
reader = rb_funcall(cReader, ID_NEW, 0);
|
704
696
|
rb_iv_set(reader, "@reader", Data_Wrap_Struct(rb_cObject, 0, 0, response));
|
@@ -715,7 +707,6 @@ static VALUE cCommand_execute_reader(int argc, VALUE *argv, VALUE self) {
|
|
715
707
|
// Whoops... wrong number of types passed to set_types. Close the reader and raise
|
716
708
|
// and error
|
717
709
|
rb_funcall(reader, rb_intern("close"), 0);
|
718
|
-
flush_pool(connection);
|
719
710
|
rb_raise(eMysqlError, "Field-count mismatch. Expected %ld fields, but the query yielded %d", RARRAY_LEN(field_types), field_count);
|
720
711
|
}
|
721
712
|
|
@@ -724,8 +715,7 @@ static VALUE cCommand_execute_reader(int argc, VALUE *argv, VALUE self) {
|
|
724
715
|
rb_ary_push(field_names, RUBY_STRING(field->name));
|
725
716
|
|
726
717
|
if (1 == guess_default_field_types) {
|
727
|
-
|
728
|
-
rb_ary_push(field_types, field_ruby_type_name);
|
718
|
+
rb_ary_push(field_types, infer_ruby_type(field));
|
729
719
|
}
|
730
720
|
}
|
731
721
|
|
@@ -770,6 +760,7 @@ static VALUE cReader_next(VALUE self) {
|
|
770
760
|
|
771
761
|
MYSQL_RES *reader;
|
772
762
|
MYSQL_ROW result;
|
763
|
+
unsigned long *lengths;
|
773
764
|
|
774
765
|
int i;
|
775
766
|
char *field_type;
|
@@ -783,6 +774,7 @@ static VALUE cReader_next(VALUE self) {
|
|
783
774
|
ruby_field_type_strings = rb_iv_get(self, "@field_types");
|
784
775
|
row = rb_ary_new();
|
785
776
|
result = (MYSQL_ROW)mysql_fetch_row(reader);
|
777
|
+
lengths = mysql_fetch_lengths(reader);
|
786
778
|
|
787
779
|
rb_iv_set(self, "@state", result ? Qtrue : Qfalse);
|
788
780
|
|
@@ -792,7 +784,7 @@ static VALUE cReader_next(VALUE self) {
|
|
792
784
|
for (i = 0; i < reader->field_count; i++) {
|
793
785
|
// The field_type data could be cached in a c-array
|
794
786
|
field_type = RSTRING_PTR(rb_ary_entry(ruby_field_type_strings, i));
|
795
|
-
rb_ary_push(row, typecast(result[i], field_type));
|
787
|
+
rb_ary_push(row, typecast(result[i], lengths[i], field_type));
|
796
788
|
}
|
797
789
|
|
798
790
|
rb_iv_set(self, "@values", row);
|
@@ -815,17 +807,14 @@ static VALUE cReader_fields(VALUE self) {
|
|
815
807
|
}
|
816
808
|
|
817
809
|
void Init_do_mysql_ext() {
|
818
|
-
rb_require("rubygems");
|
819
810
|
rb_require("bigdecimal");
|
820
811
|
rb_require("date");
|
821
|
-
rb_require("cgi");
|
822
812
|
|
823
813
|
rb_funcall(rb_mKernel, rb_intern("require"), 1, RUBY_STRING("data_objects"));
|
824
814
|
|
825
815
|
ID_TO_I = rb_intern("to_i");
|
826
816
|
ID_TO_F = rb_intern("to_f");
|
827
817
|
ID_TO_S = rb_intern("to_s");
|
828
|
-
ID_PARSE = rb_intern("parse");
|
829
818
|
ID_TO_TIME = rb_intern("to_time");
|
830
819
|
ID_NEW = rb_intern("new");
|
831
820
|
ID_NEW_RATIONAL = rb_intern("new!");
|
@@ -847,7 +836,6 @@ void Init_do_mysql_ext() {
|
|
847
836
|
rb_cDateTime = RUBY_CLASS("DateTime");
|
848
837
|
rb_cRational = RUBY_CLASS("Rational");
|
849
838
|
rb_cBigDecimal = RUBY_CLASS("BigDecimal");
|
850
|
-
rb_cCGI = RUBY_CLASS("CGI");
|
851
839
|
|
852
840
|
// Get references to the DataObjects module and its classes
|
853
841
|
mDO = CONST_GET(rb_mKernel, "DataObjects");
|
File without changes
|
data/lib/do_mysql.rb
CHANGED
@@ -1,15 +1,20 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'data_objects'
|
3
|
-
require 'do_mysql_ext' # the C/Java extension for this DO driver
|
4
|
-
require 'do_mysql' / 'transaction'
|
5
|
-
|
6
3
|
if RUBY_PLATFORM =~ /java/
|
7
|
-
require 'do_jdbc
|
4
|
+
require 'do_jdbc'
|
5
|
+
require 'java'
|
6
|
+
gem 'jdbc-mysql'
|
7
|
+
require 'jdbc/mysql' # the JDBC driver, packaged as a gem
|
8
|
+
end
|
8
9
|
|
9
|
-
|
10
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'do_mysql_ext'))
|
11
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'do_mysql', 'version'))
|
12
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'do_mysql', 'transaction'))
|
13
|
+
|
14
|
+
if RUBY_PLATFORM =~ /java/
|
15
|
+
# Another way of loading the JDBC Class. This seems to be more reliable
|
10
16
|
# than Class.forName() within the data_objects.Connection Java class,
|
11
17
|
# which is currently not working as expected.
|
12
|
-
require 'java'
|
13
18
|
import 'com.mysql.jdbc.Driver'
|
14
19
|
|
15
20
|
module DataObjects
|
@@ -18,6 +23,17 @@ if RUBY_PLATFORM =~ /java/
|
|
18
23
|
def self.pool_size
|
19
24
|
20
|
20
25
|
end
|
26
|
+
|
27
|
+
def using_socket?
|
28
|
+
@using_socket
|
29
|
+
end
|
30
|
+
|
31
|
+
def character_set
|
32
|
+
# JDBC API does not provide an easy way to get the current character set
|
33
|
+
# For now, we code the character_set used as utf8
|
34
|
+
"utf8"
|
35
|
+
end
|
36
|
+
|
21
37
|
end
|
22
38
|
end
|
23
39
|
end
|
data/lib/do_mysql/version.rb
CHANGED
@@ -20,19 +20,18 @@ describe DataObjects::Mysql do
|
|
20
20
|
end
|
21
21
|
|
22
22
|
it "should connect successfully via TCP" do
|
23
|
-
|
24
|
-
connection = DataObjects::Connection.new("mysql://root@127.0.0.1:3306/do_mysql_test")
|
23
|
+
connection = DataObjects::Connection.new("mysql://#{MYSQL.user}:#{MYSQL.pass}@#{MYSQL.host}:#{MYSQL.port}/#{MYSQL.database}")
|
25
24
|
connection.should_not be_using_socket
|
26
25
|
connection.close
|
27
26
|
end
|
28
27
|
|
29
|
-
it "should be able to send
|
28
|
+
it "should be able to send queries asynchronously in parallel" do
|
30
29
|
threads = []
|
31
30
|
|
32
31
|
start = Time.now
|
33
32
|
4.times do |i|
|
34
33
|
threads << Thread.new do
|
35
|
-
connection = DataObjects::Connection.new("mysql
|
34
|
+
connection = DataObjects::Connection.new("mysql://#{MYSQL.user}:#{MYSQL.pass}@#{MYSQL.host}:#{MYSQL.port}/#{MYSQL.database}")
|
36
35
|
command = connection.create_command("SELECT sleep(1)")
|
37
36
|
result = command.execute_non_query
|
38
37
|
end
|
@@ -49,54 +48,60 @@ describe DataObjects::Mysql do
|
|
49
48
|
# It's not really a requirement, since all architectures that support MySQL also supports TCP connectsion, ne?
|
50
49
|
#
|
51
50
|
# it "should connect successfully via the socket file" do
|
52
|
-
# @connection = DataObjects::Mysql::Connection.new("mysql
|
51
|
+
# @connection = DataObjects::Mysql::Connection.new("mysql://#{MYSQL.user}@#{MYSQL.hostname}:#{MYSQL.port}/#{MYSQL.database}/?socket=#{SOCKET_PATH}")
|
53
52
|
# @connection.should be_using_socket
|
54
53
|
# end
|
55
54
|
|
56
55
|
it "should return the current character set" do
|
57
|
-
|
58
|
-
connection = DataObjects::Connection.new("mysql://root@localhost:3306/do_mysql_test")
|
56
|
+
connection = DataObjects::Connection.new("mysql://#{MYSQL.user}:#{MYSQL.pass}@#{MYSQL.hostname}:#{MYSQL.port}/#{MYSQL.database}")
|
59
57
|
connection.character_set.should == "utf8"
|
60
58
|
connection.close
|
61
59
|
end
|
62
60
|
|
63
61
|
it "should support changing the character set" do
|
64
|
-
pending "
|
65
|
-
|
62
|
+
pending "JDBC API does not provide an easy way to get the current character set" if JRUBY
|
63
|
+
# current character set can be retrieved with the following query:
|
64
|
+
# "SHOW VARIABLES LIKE character_set_database"
|
65
|
+
|
66
|
+
connection = DataObjects::Connection.new("mysql://#{MYSQL.user}:#{MYSQL.pass}@#{MYSQL.hostname}:#{MYSQL.port}/#{MYSQL.database}?charset=latin1")
|
67
|
+
# N.B. query parameter after forward slash causes problems with JDBC
|
66
68
|
connection.character_set.should == "latin1"
|
67
69
|
connection.close
|
68
70
|
|
69
|
-
connection = DataObjects::Connection.new("mysql
|
71
|
+
connection = DataObjects::Connection.new("mysql://#{MYSQL.user}:#{MYSQL.pass}@#{MYSQL.hostname}:#{MYSQL.port}/#{MYSQL.database}?charset=utf8")
|
70
72
|
connection.character_set.should == "utf8"
|
71
73
|
connection.close
|
72
74
|
end
|
73
75
|
|
74
76
|
it "should raise an error when opened with an invalid server uri" do
|
75
|
-
pending "Problems parsing regular connection URIs vs. JDBC URLs" if JRUBY
|
76
77
|
def connecting_with(uri)
|
77
78
|
lambda { DataObjects::Connection.new(uri) }
|
78
79
|
end
|
79
80
|
|
80
|
-
|
81
|
-
|
81
|
+
unless JRUBY ## FIXME in JRuby
|
82
|
+
# Missing database name
|
83
|
+
connecting_with("mysql://#{MYSQL.user}:#{MYSQL.pass}@#{MYSQL.hostname}:#{MYSQL.port}/").should raise_error(MysqlError)
|
84
|
+
end
|
82
85
|
|
83
86
|
# Wrong port
|
84
|
-
connecting_with("mysql
|
87
|
+
connecting_with("mysql://#{MYSQL.user}:#{MYSQL.pass}@#{MYSQL.hostname}:666/").should raise_error(MysqlError)
|
85
88
|
|
86
|
-
|
87
|
-
|
89
|
+
unless JRUBY ## FIXME in JRuby
|
90
|
+
# Bad Username
|
91
|
+
connecting_with("mysql://baduser@#{MYSQL.hostname}:#{MYSQL.port}/").should raise_error(MysqlError)
|
92
|
+
end
|
88
93
|
|
89
94
|
# Bad Password
|
90
|
-
connecting_with("mysql
|
95
|
+
connecting_with("mysql://#{MYSQL.user}:wrongpassword@#{MYSQL.hostname}:#{MYSQL.port}/").should raise_error(MysqlError)
|
91
96
|
|
92
97
|
# Bad Database Name
|
93
|
-
connecting_with("mysql
|
98
|
+
connecting_with("mysql://#{MYSQL.user}:#{MYSQL.pass}@#{MYSQL.hostname}:#{MYSQL.port}/bad_database").should raise_error(MysqlError)
|
94
99
|
|
95
100
|
#
|
96
101
|
# Again, should socket even be speced if we don't support it across all platforms?
|
97
102
|
#
|
98
103
|
# Invalid Socket Path
|
99
|
-
#connecting_with("mysql
|
104
|
+
#connecting_with("mysql://#{MYSQL.user}@#{MYSQL.hostname}:#{MYSQL.port}/MYSQL.database/?socket=/invalid/path/mysql.sock").should raise_error(MysqlError)
|
100
105
|
end
|
101
106
|
end
|
102
107
|
|
@@ -119,32 +124,14 @@ describe DataObjects::Mysql::Connection do
|
|
119
124
|
lambda { @connection.create_command("SELECT * FROM non_existant_table").execute_reader }.should raise_error(MysqlError)
|
120
125
|
end
|
121
126
|
|
122
|
-
it "should
|
123
|
-
lambda { @connection.create_command("INSERT INTO non_exista (tester) VALUES (1)").execute_non_query }.should raise_error(MysqlError)
|
124
|
-
@connection.instance_variable_get(:@connection).should == nil
|
125
|
-
end
|
126
|
-
|
127
|
-
it "should flush the pool when executing a bad query" do
|
128
|
-
pool = @connection.instance_variable_get(:@__pool)
|
129
|
-
lambda { @connection.create_command("INSERT INTO non_exista (tester) VALUES (1)").execute_non_query }.should raise_error(MysqlError)
|
130
|
-
Extlib::Pooling.pools.detect { |p| p == pool }.instance_variable_get(:@available).size.should == 0
|
131
|
-
end
|
132
|
-
|
133
|
-
it "should delete itself from the pool" do
|
134
|
-
pool = @connection.instance_variable_get(:@__pool)
|
135
|
-
count = pool.size
|
136
|
-
lambda { @connection.create_command("INSERT INTO non_exista (tester) VALUES (1)").execute_non_query }.should raise_error(MysqlError)
|
137
|
-
Extlib::Pooling.pools.detect { |p| p == pool }.size.should == count-1
|
138
|
-
end
|
139
|
-
|
140
|
-
it "should not raise an error error executing a non query on a closed connection" do
|
127
|
+
it "should not raise a connection closed error after an incorrect query" do
|
141
128
|
lambda { @connection.create_command("INSERT INTO non_existant_table (tester) VALUES (1)").execute_non_query }.should raise_error(MysqlError)
|
142
|
-
lambda { @connection.create_command("INSERT INTO non_existant_table (tester) VALUES (1)").execute_non_query }.
|
129
|
+
lambda { @connection.create_command("INSERT INTO non_existant_table (tester) VALUES (1)").execute_non_query }.should_not raise_error(MysqlError, "This connection has already been closed.")
|
143
130
|
end
|
144
131
|
|
145
|
-
it "should not raise
|
132
|
+
it "should not raise a connection closed error after an incorrect reader" do
|
146
133
|
lambda { @connection.create_command("SELECT * FROM non_existant_table").execute_reader }.should raise_error(MysqlError)
|
147
|
-
lambda { @connection.create_command("SELECT * FROM non_existant_table").execute_reader }.
|
134
|
+
lambda { @connection.create_command("SELECT * FROM non_existant_table").execute_reader }.should_not raise_error(MysqlError, "This connection has already been closed.")
|
148
135
|
end
|
149
136
|
|
150
137
|
end
|
@@ -176,6 +163,7 @@ describe DataObjects::Mysql::Reader do
|
|
176
163
|
end
|
177
164
|
|
178
165
|
it "should raise an exception if .values is called after reading all available rows" do
|
166
|
+
|
179
167
|
select("SELECT * FROM widgets LIMIT 2") do |reader|
|
180
168
|
# select already calls next once for us
|
181
169
|
reader.next!
|
@@ -190,7 +178,8 @@ describe DataObjects::Mysql::Reader do
|
|
190
178
|
insert("INSERT INTO users (name) VALUES ('Slappy Wilson')"),
|
191
179
|
insert("INSERT INTO users (name) VALUES ('Jumpy Jones')")
|
192
180
|
]
|
193
|
-
|
181
|
+
# do_jdbc rewrites "?" as "(?,?)"
|
182
|
+
# to correspond to the JDBC API
|
194
183
|
select("SELECT * FROM users WHERE id IN ?", nil, ids) do |reader|
|
195
184
|
# select already calls next once for us
|
196
185
|
reader.next!.should == true
|
@@ -224,11 +213,21 @@ describe DataObjects::Mysql::Reader do
|
|
224
213
|
describe "Date, Time, and DateTime" do
|
225
214
|
|
226
215
|
it "should return nil when the time is 0" do
|
227
|
-
|
228
|
-
|
229
|
-
|
216
|
+
|
217
|
+
# skip the test if the strict dates/times setting is turned on
|
218
|
+
strict_time = select("SHOW VARIABLES LIKE 'sql_mode'") do |reader|
|
219
|
+
reader.values.last.split(',').any? do |mode|
|
220
|
+
%w[ NO_ZERO_IN_DATE NO_ZERO_DATE ].include?(mode.strip.upcase)
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
unless strict_time
|
225
|
+
id = insert("INSERT INTO users (name, fired_at) VALUES ('James', 0);")
|
226
|
+
select("SELECT fired_at FROM users WHERE id = ?", [Time], id) do |reader|
|
227
|
+
reader.values.last.should be_nil
|
228
|
+
end
|
229
|
+
exec("DELETE FROM users WHERE id = ?", id)
|
230
230
|
end
|
231
|
-
exec("DELETE FROM users WHERE id = ?", id)
|
232
231
|
end
|
233
232
|
|
234
233
|
it "should return DateTimes using the current locale's Time Zone" do
|
@@ -4,11 +4,7 @@ require Pathname(__FILE__).dirname.expand_path.parent + 'spec_helper'
|
|
4
4
|
describe DataObjects::Mysql::Command do
|
5
5
|
|
6
6
|
before(:each) do
|
7
|
-
@connection =
|
8
|
-
DataObjects::Connection.new(DO_MYSQL_SPEC_JDBC_URI)
|
9
|
-
else
|
10
|
-
DataObjects::Connection.new(DO_MYSQL_SPEC_URI)
|
11
|
-
end
|
7
|
+
@connection = DataObjects::Connection.new(DO_MYSQL_SPEC_URI)
|
12
8
|
end
|
13
9
|
|
14
10
|
after(:each) do
|
@@ -21,7 +17,7 @@ describe DataObjects::Mysql::Command do
|
|
21
17
|
command = @connection.create_command("SELECT * FROM widgets WHERE name = ?")
|
22
18
|
@mock_logger = mock('MockLogger', :level => 0)
|
23
19
|
DataObjects::Mysql.should_receive(:logger).and_return(@mock_logger)
|
24
|
-
@mock_logger.should_receive(:debug).with(
|
20
|
+
@mock_logger.should_receive(:debug).with(/\([\d.]+\) SELECT \* FROM widgets WHERE name = 'Scott'/)
|
25
21
|
|
26
22
|
command.execute_reader('Scott').close # Readers must be closed!
|
27
23
|
end
|
@@ -40,7 +36,7 @@ describe DataObjects::Mysql::Command do
|
|
40
36
|
command = @connection.create_command("INSERT INTO invoices (invoice_number) VALUES (?)")
|
41
37
|
@mock_logger = mock('MockLogger', :level => 0)
|
42
38
|
DataObjects::Mysql.should_receive(:logger).and_return(@mock_logger)
|
43
|
-
@mock_logger.should_receive(:debug).with(
|
39
|
+
@mock_logger.should_receive(:debug).with(/\([\d.]+\) INSERT INTO invoices \(invoice_number\) VALUES \(1234\)/)
|
44
40
|
command.execute_non_query(1234)
|
45
41
|
end
|
46
42
|
|
@@ -1,41 +1,45 @@
|
|
1
1
|
require 'pathname'
|
2
2
|
require Pathname(__FILE__).dirname.expand_path.parent + 'spec_helper'
|
3
|
-
require 'date'
|
4
3
|
|
5
|
-
|
6
|
-
|
4
|
+
unless JRUBY
|
5
|
+
require 'date'
|
6
|
+
|
7
|
+
describe DataObjects::Mysql::Command, "Quoting" do
|
8
|
+
include MysqlSpecHelpers
|
9
|
+
|
10
|
+
before :each do
|
11
|
+
setup_test_environment
|
12
|
+
end
|
13
|
+
|
14
|
+
after :each do
|
15
|
+
teardown_test_environment
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should escape strings properly" do
|
19
|
+
command = @connection.create_command("SELECT * FROM widgets WHERE name = ?")
|
20
|
+
command.quote_string("Willy O'Hare & Johnny O'Toole").should == "'Willy O\\'Hare & Johnny O\\'Toole'".dup
|
21
|
+
command.quote_string("The\\Backslasher\\Rises\\Again").should == "'The\\\\Backslasher\\\\Rises\\\\Again'"
|
22
|
+
command.quote_string("Scott \"The Rage\" Bauer").should == "'Scott \\\"The Rage\\\" Bauer'"
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should quote DateTime instances properly" do
|
26
|
+
command = @connection.create_command("SELECT * FROM widgets WHERE release_datetime >= ?")
|
27
|
+
dt = DateTime.now
|
28
|
+
command.quote_datetime(dt).should == "'#{dt.strftime('%Y-%m-%d %H:%M:%S')}'"
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should quote Time instances properly" do
|
32
|
+
command = @connection.create_command("SELECT * FROM widgets WHERE release_timestamp >= ?")
|
33
|
+
dt = Time.now
|
34
|
+
command.quote_time(dt).should == "'#{dt.strftime('%Y-%m-%d %H:%M:%S')}'"
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should quote Date instances properly" do
|
38
|
+
command = @connection.create_command("SELECT * FROM widgets WHERE release_date >= ?")
|
39
|
+
dt = Date.today
|
40
|
+
command.quote_date(dt).should == "'#{dt.strftime('%Y-%m-%d')}'"
|
41
|
+
end
|
7
42
|
|
8
|
-
before :each do
|
9
|
-
setup_test_environment
|
10
|
-
end
|
11
|
-
|
12
|
-
after :each do
|
13
|
-
teardown_test_environment
|
14
|
-
end
|
15
|
-
|
16
|
-
it "should escape strings properly" do
|
17
|
-
command = @connection.create_command("SELECT * FROM widgets WHERE name = ?")
|
18
|
-
command.quote_string("Willy O'Hare & Johnny O'Toole").should == "'Willy O\\'Hare & Johnny O\\'Toole'".dup
|
19
|
-
command.quote_string("The\\Backslasher\\Rises\\Again").should == "'The\\\\Backslasher\\\\Rises\\\\Again'"
|
20
|
-
command.quote_string("Scott \"The Rage\" Bauer").should == "'Scott \\\"The Rage\\\" Bauer'"
|
21
|
-
end
|
22
|
-
|
23
|
-
it "should quote DateTime instances properly" do
|
24
|
-
command = @connection.create_command("SELECT * FROM widgets WHERE release_datetime >= ?")
|
25
|
-
dt = DateTime.now
|
26
|
-
command.quote_datetime(dt).should == "'#{dt.strftime('%Y-%m-%d %H:%M:%S')}'"
|
27
|
-
end
|
28
|
-
|
29
|
-
it "should quote Time instances properly" do
|
30
|
-
command = @connection.create_command("SELECT * FROM widgets WHERE release_timestamp >= ?")
|
31
|
-
dt = Time.now
|
32
|
-
command.quote_time(dt).should == "'#{dt.strftime('%Y-%m-%d %H:%M:%S')}'"
|
33
|
-
end
|
34
|
-
|
35
|
-
it "should quote Date instances properly" do
|
36
|
-
command = @connection.create_command("SELECT * FROM widgets WHERE release_date >= ?")
|
37
|
-
dt = Date.today
|
38
|
-
command.quote_date(dt).should == "'#{dt.strftime('%Y-%m-%d')}'"
|
39
43
|
end
|
40
44
|
|
41
45
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -7,6 +7,7 @@ gem 'rspec', '>=1.1.3'
|
|
7
7
|
require 'spec'
|
8
8
|
|
9
9
|
require 'date'
|
10
|
+
require 'ostruct'
|
10
11
|
require 'pathname'
|
11
12
|
require 'fileutils'
|
12
13
|
|
@@ -31,9 +32,17 @@ DataObjects::Mysql.logger = DataObjects::Logger.new(log_path, 0)
|
|
31
32
|
|
32
33
|
at_exit { DataObjects.logger.flush }
|
33
34
|
|
34
|
-
|
35
|
-
|
36
|
-
|
35
|
+
MYSQL = OpenStruct.new
|
36
|
+
MYSQL.user = ENV['DO_MYSQL_USER'] || 'root'
|
37
|
+
MYSQL.pass = ENV['DO_MYSQL_PASS'] || ''
|
38
|
+
MYSQL.host = ENV['DO_MYSQL_HOST'] || '127.0.0.1'
|
39
|
+
MYSQL.hostname = ENV['DO_MYSQL_HOSTNAME'] || 'localhost'
|
40
|
+
MYSQL.port = ENV['DO_MYSQL_PORT'] || '3306'
|
41
|
+
MYSQL.database = ENV['DO_MYSQL_DATABASE'] || 'do_mysql_test'
|
42
|
+
MYSQL.socket = ENV['DO_MYSQL_SOCKET'] || '/tmp/mysql.sock'
|
43
|
+
|
44
|
+
DO_MYSQL_SPEC_URI = Addressable::URI::parse(ENV["DO_MYSQL_SPEC_URI"] ||
|
45
|
+
"mysql://#{MYSQL.user}:#{MYSQL.pass}@#{MYSQL.host}:#{MYSQL.port}/#{MYSQL.database}")
|
37
46
|
|
38
47
|
module MysqlSpecHelpers
|
39
48
|
def insert(query, *args)
|
@@ -58,13 +67,8 @@ module MysqlSpecHelpers
|
|
58
67
|
end
|
59
68
|
|
60
69
|
def setup_test_environment
|
61
|
-
|
62
|
-
|
63
|
-
@secondary_connection = DataObjects::Connection.new(DO_MYSQL_SPEC_JDBC_URI)
|
64
|
-
elsif
|
65
|
-
@connection = DataObjects::Connection.new(DO_MYSQL_SPEC_URI)
|
66
|
-
@secondary_connection = DataObjects::Connection.new(DO_MYSQL_SPEC_URI)
|
67
|
-
end
|
70
|
+
@connection = DataObjects::Connection.new(DO_MYSQL_SPEC_URI)
|
71
|
+
@secondary_connection = DataObjects::Connection.new(DO_MYSQL_SPEC_URI)
|
68
72
|
|
69
73
|
@connection.create_command(<<-EOF).execute_non_query
|
70
74
|
DROP TABLE IF EXISTS `invoices`
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: do_mysql
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.9.
|
4
|
+
version: 0.9.10
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Scott Bauer
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date:
|
12
|
+
date: 2009-01-04 00:00:00 +01:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -20,7 +20,7 @@ dependencies:
|
|
20
20
|
requirements:
|
21
21
|
- - "="
|
22
22
|
- !ruby/object:Gem::Version
|
23
|
-
version: 0.9.
|
23
|
+
version: 0.9.10
|
24
24
|
version:
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: hoe
|
@@ -38,7 +38,7 @@ email:
|
|
38
38
|
executables: []
|
39
39
|
|
40
40
|
extensions:
|
41
|
-
- ext/extconf.rb
|
41
|
+
- ext/do_mysql_ext/extconf.rb
|
42
42
|
extra_rdoc_files:
|
43
43
|
- History.txt
|
44
44
|
- Manifest.txt
|
@@ -55,8 +55,8 @@ files:
|
|
55
55
|
- ext-java/src/main/java/DoMysqlExtService.java
|
56
56
|
- ext-java/src/main/java/do_mysql/MySqlDriverDefinition.java
|
57
57
|
- ext/.gitignore
|
58
|
-
- ext/do_mysql_ext.c
|
59
|
-
- ext/extconf.rb
|
58
|
+
- ext/do_mysql_ext/do_mysql_ext.c
|
59
|
+
- ext/do_mysql_ext/extconf.rb
|
60
60
|
- lib/do_mysql.rb
|
61
61
|
- lib/do_mysql/transaction.rb
|
62
62
|
- lib/do_mysql/version.rb
|