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 CHANGED
@@ -1,4 +0,0 @@
1
- pkg
2
- ext-java/target
3
- Makefile
4
- mkmf.log
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 'pathname'
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
- JRUBY = (RUBY_PLATFORM =~ /java/) rescue nil
10
- WINDOWS = (RUBY_PLATFORM =~ /mswin|mingw|cygwin/) rescue nil
11
- # don't use SUDO with JRuby, for the moment, although this behaviour
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 = [["data_objects", GEM_VERSION]]
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 => %w[ ext/extconf.rb ], :has_rdoc => false }
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
- DRIVER = true
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
- sh %{#{SUDO} gem install --local pkg/#{GEM_NAME}-#{GEM_VERSION} --no-update-sources}, :verbose => false
59
+ sudo_gem "install pkg/#{GEM_NAME}-#{GEM_VERSION} --no-update-sources"
37
60
  end
38
61
 
39
- desc "Uninstall #{GEM_NAME} #{GEM_VERSION} (default ruby)"
62
+ desc "Uninstall #{GEM_NAME} #{GEM_VERSION}"
40
63
  task :uninstall => [ :clobber ] do
41
- sh "#{SUDO} gem uninstall #{GEM_NAME} -v#{GEM_VERSION} -I -x", :verbose => false
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 = ENV.has_key?('NO_RCOV') ? ENV['NO_RCOV'] != 'true' : true
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) rb_tainted_str_new2(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 char * ruby_type_from_mysql_type(MYSQL_FIELD *field) {
78
+ static VALUE infer_ruby_type(MYSQL_FIELD *field) {
81
79
 
82
- char* ruby_type_name;
80
+ char* ruby_type;
83
81
 
84
82
  switch(field->type) {
85
83
  case MYSQL_TYPE_NULL: {
86
- ruby_type_name = NULL;
84
+ ruby_type = NULL;
87
85
  break;
88
86
  }
89
87
  case MYSQL_TYPE_TINY: {
90
- ruby_type_name = "TrueClass";
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
- ruby_type_name = "Fixnum";
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
- ruby_type_name = "BigDecimal";
107
+ ruby_type = "Float";
105
108
  break;
106
109
  }
107
110
  case MYSQL_TYPE_TIMESTAMP:
108
111
  case MYSQL_TYPE_DATETIME: {
109
- ruby_type_name = "DateTime";
112
+ ruby_type = "DateTime";
110
113
  break;
111
114
  }
112
115
  case MYSQL_TYPE_TIME: {
113
- ruby_type_name = "DateTime";
116
+ ruby_type = "DateTime";
114
117
  break;
115
118
  }
116
- case MYSQL_TYPE_DATE: {
117
- ruby_type_name = "Date";
119
+ case MYSQL_TYPE_DATE:
120
+ case MYSQL_TYPE_NEWDATE: {
121
+ ruby_type = "Date";
118
122
  break;
119
123
  }
120
124
  default: {
121
- // printf("Falling to default: %s - %d\n", field->name, field->type);
122
- ruby_type_name = "String";
125
+ ruby_type = "String";
126
+ break;
123
127
  }
124
128
  }
125
129
 
126
- return ruby_type_name;
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 *date_time) {
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
- // Mysql date format: 2008-05-03 14:43:00
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
- jd = jd_from_date(year, month, day);
225
+ if ( strcmp(date, "") == 0 ) {
226
+ return Qnil;
227
+ }
210
228
 
211
- // Generate ajd with fractional days for the time
212
- // Extracted from Date#jd_to_ajd, Date#day_fraction_to_time, and Rational#+ and #-
213
- num = ((hour) * 1440) + ((min) * 24); // (Hour * Minutes in a day) + (minutes * 24)
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
- // Get localtime
216
- time(&rawtime);
217
- timeinfo = localtime(&rawtime);
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
- // TODO: Refactor the following few lines to do the calculation with the *seconds*
220
- // value instead of having to do the hour/minute math
221
- int hour_offset = abs(timeinfo->tm_gmtoff) / 3600;
222
- int minute_offset = abs(timeinfo->tm_gmtoff) % 3600 / 60;
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
- // If the Timezone is ahead of UTC, we need to subtract the time offset
230
- num -= (hour_offset * 1440) + (minute_offset * 24);
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 - den;
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
- rb_funcall(logger, ID_DEBUG, 1, string);
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
- static void flush_pool(VALUE connection) {
292
- data_objects_debug(rb_funcall(connection, rb_intern("inspect"), 0));
293
- if ( Qnil != connection ) {
294
- VALUE pool = rb_iv_get(connection, "@__pool");
295
- rb_funcall(pool, rb_intern("flush!"), 0);
296
- rb_funcall(pool, rb_intern("delete"), 1, connection);
297
- rb_funcall(connection, rb_intern("dispose"), 0);
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
- // We can add custom information to error messages using this function
302
- // if we think it matters
303
- static void raise_mysql_error(VALUE connection, MYSQL *db, int mysql_error_code) {
304
- char *mysql_error_message = (char *)mysql_error(db);
305
- int length = strlen(mysql_error_message) + 25; // length of " (mysql_error_code=0000)"
306
- char *error_message = (char *)calloc(length, sizeof(char));
307
-
308
- sprintf(error_message, "%s (mysql_error_code=%04d)", mysql_error_message, mysql_error_code);
309
-
310
- data_objects_debug(rb_str_new2(error_message));
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
- flush_pool(connection);
380
- rb_raise(eMysqlError, error_message);
381
+ return value;
381
382
  }
382
383
 
383
- // Pull an option out of a querystring-formmated option list using CGI::parse
384
- static char * get_uri_option(VALUE querystring, char * key) {
385
- VALUE options_hash, option_value;
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
- char * value = NULL;
402
+ CHECK_AND_RAISE(retval, str);
403
+ gettimeofday(&start, NULL);
388
404
 
389
- // Ensure that we're dealing with a string
390
- querystring = rb_funcall(querystring, ID_TO_S, 0);
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
- options_hash = rb_funcall(rb_cCGI, ID_PARSE, 1, querystring);
413
+ if (retval < 0) {
414
+ rb_sys_fail(0);
415
+ }
393
416
 
394
- // TODO: rb_hash_aref always returns an array?
395
- option_value = rb_ary_entry(rb_hash_aref(options_hash, RUBY_STRING(key)), 0);
417
+ if (retval == 0) {
418
+ continue;
419
+ }
396
420
 
397
- if (Qnil != option_value) {
398
- value = StringValuePtr(option_value);
421
+ if (db->status == MYSQL_STATUS_READY) {
422
+ break;
423
+ }
399
424
  }
400
425
 
401
- return value;
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, r_options, r_port;
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 *charset = NULL;
439
+ char *encoding = NULL;
410
440
 
411
441
  int port = 3306;
412
442
  unsigned long client_flags = 0;
413
- int charset_error;
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
- r_options = rb_funcall(uri, rb_intern("query"), 0);
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(r_options, "socket");
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
- charset = get_uri_option(r_options, "charset");
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
- charset_error = mysql_set_character_set(db, charset);
493
- if (0 != charset_error) {
494
- raise_mysql_error(Qnil, db, charset_error);
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 *charset;
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
- charset = mysql_character_set_name(db);
544
+ encoding = mysql_character_set_name(db);
515
545
 
516
- return RUBY_STRING(charset);
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 = StringValuePtr(string);
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(strlen(source) * 3 + 3, sizeof(char));
616
+ escaped = (char *)calloc(source_len * 2 + 3, sizeof(char));
586
617
 
587
- // Escape 'source' using the current charset in use on the conection 'db'
588
- quoted_length = mysql_real_escape_string(db, escaped + 1, source, strlen(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 = (int)mysql_field_count(db);
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
- VALUE field_ruby_type_name = RUBY_STRING(ruby_type_from_mysql_type(field));
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/mysql' # the JDBC driver, packaged as a gem
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
- # Another way of loading the JDBC Class. This seems to be more relaible
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
@@ -1,5 +1,5 @@
1
1
  module DataObjects
2
2
  module Mysql
3
- VERSION = "0.9.9"
3
+ VERSION = "0.9.10"
4
4
  end
5
5
  end
@@ -20,19 +20,18 @@ describe DataObjects::Mysql do
20
20
  end
21
21
 
22
22
  it "should connect successfully via TCP" do
23
- pending "Problems parsing regular connection URIs vs. JDBC URLs" if JRUBY
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 querues asynchronuously in parallel" do
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://root@127.0.0.1:3306/do_mysql_test")
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://root@localhost:3306/do_mysql_test/?socket=#{SOCKET_PATH}")
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
- pending "Problems parsing regular connection URIs vs. JDBC URLs" if JRUBY
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 "Problems parsing regular connection URIs vs. JDBC URLs" if JRUBY
65
- connection = DataObjects::Connection.new("mysql://root@localhost:3306/do_mysql_test/?charset=latin1")
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://root@localhost:3306/do_mysql_test/?charset=utf8")
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
- # Missing database name
81
- connecting_with("mysql://root@localhost:3306/").should raise_error(MysqlError)
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://root@localhost:666/").should raise_error(MysqlError)
87
+ connecting_with("mysql://#{MYSQL.user}:#{MYSQL.pass}@#{MYSQL.hostname}:666/").should raise_error(MysqlError)
85
88
 
86
- # Bad Username
87
- connecting_with("mysql://baduser@localhost:3306/").should raise_error(MysqlError)
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://root:wrongpassword@localhost:3306/").should raise_error(MysqlError)
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://root@localhost:3306/bad_database").should raise_error(MysqlError)
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://root@localhost:3306/do_mysql_test/?socket=/invalid/path/mysql.sock").should raise_error(MysqlError)
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 close the connection when executing a bad query" do
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 }.should raise_error(MysqlError, "This connection has already been closed.")
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 an error executing a reader on a closed connection" do
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 }.should raise_error(MysqlError, "This connection has already been closed.")
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
- id = insert("INSERT INTO users (name, fired_at) VALUES ('James', 0);")
228
- select("SELECT fired_at FROM users WHERE id = ?", [Time], id) do |reader|
229
- reader.values.last.should be_nil
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 = if JRUBY
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("SELECT * FROM widgets WHERE name = 'Scott'")
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("INSERT INTO invoices (invoice_number) VALUES (1234)")
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
- describe DataObjects::Mysql::Command, "Quoting" do
6
- include MysqlSpecHelpers
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
- # use different, JDBC-style URLs for JRuby, for the time-being
35
- DO_MYSQL_SPEC_URI = Addressable::URI::parse(ENV["DO_MYSQL_SPEC_URI"] || "mysql://root@127.0.0.1:3306/do_mysql_test")
36
- DO_MYSQL_SPEC_JDBC_URI = Addressable::URI::parse(ENV["DO_MYSQL_SPEC_JDBC_URI"] || "jdbc:mysql://localhost:3306/do_mysql_test?user=root")
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
- if JRUBY # use different, JDBC-style URLs for JRuby, for the time-being
62
- @connection = DataObjects::Connection.new(DO_MYSQL_SPEC_JDBC_URI)
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.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: 2008-11-27 00:00:00 -08:00
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.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