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 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