do_mysql 0.9.9 → 0.9.10
Sign up to get free protection for your applications and to get access to all the features.
- 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
|