mysql2 0.2.18 → 0.2.19b1

Sign up to get free protection for your applications and to get access to all the features.
@@ -33,10 +33,12 @@ void init_mysql2_client();
33
33
 
34
34
  typedef struct {
35
35
  VALUE encoding;
36
- int active;
36
+ VALUE active_thread; /* rb_thread_current() or Qnil */
37
37
  int reconnect_enabled;
38
- int closed;
38
+ int active;
39
+ int connected;
40
+ int initialized;
39
41
  MYSQL *client;
40
42
  } mysql_client_wrapper;
41
43
 
42
- #endif
44
+ #endif
@@ -17,6 +17,7 @@ dirs = ENV['PATH'].split(File::PATH_SEPARATOR) + %w[
17
17
  /opt/local/mysql
18
18
  /opt/local/lib/mysql5
19
19
  /usr
20
+ /usr/mysql
20
21
  /usr/local
21
22
  /usr/local/mysql
22
23
  /usr/local/mysql-*
@@ -61,13 +62,13 @@ end
61
62
  asplode h unless have_header h
62
63
  end
63
64
 
64
- unless RUBY_PLATFORM =~ /mswin/ or RUBY_PLATFORM =~ /sparc/
65
+ # GCC specific flags
66
+ if RbConfig::MAKEFILE_CONFIG['CC'] =~ /gcc/
65
67
  $CFLAGS << ' -Wall -funroll-loops'
66
- end
67
- # $CFLAGS << ' -O0 -ggdb3 -Wextra'
68
68
 
69
- if hard_mysql_path = $libs[%r{-L(/[^ ]+)}, 1]
70
- $LDFLAGS << " -Wl,-rpath,#{hard_mysql_path}"
69
+ if hard_mysql_path = $libs[%r{-L(/[^ ]+)}, 1]
70
+ $LDFLAGS << " -Wl,-rpath,#{hard_mysql_path}"
71
+ end
71
72
  end
72
73
 
73
74
  create_makefile('mysql2/mysql2')
@@ -55,7 +55,7 @@ static VALUE intern_encoding_from_charset;
55
55
  static ID intern_new, intern_utc, intern_local, intern_encoding_from_charset_code,
56
56
  intern_localtime, intern_local_offset, intern_civil, intern_new_offset;
57
57
  static VALUE sym_symbolize_keys, sym_as, sym_array, sym_database_timezone, sym_application_timezone,
58
- sym_local, sym_utc, sym_cast_booleans, sym_cache_rows, sym_cast;
58
+ sym_local, sym_utc, sym_cast_booleans, sym_cache_rows, sym_cast, sym_stream;
59
59
  static ID intern_merge;
60
60
 
61
61
  static void rb_mysql_result_mark(void * wrapper) {
@@ -163,11 +163,10 @@ static VALUE mysql2_set_field_string_encoding(VALUE val, MYSQL_FIELD field, rb_e
163
163
  #endif
164
164
 
165
165
 
166
- static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezone, int symbolizeKeys, int asArray, int castBool, int cast) {
166
+ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezone, int symbolizeKeys, int asArray, int castBool, int cast, MYSQL_FIELD * fields) {
167
167
  VALUE rowVal;
168
168
  mysql2_result_wrapper * wrapper;
169
169
  MYSQL_ROW row;
170
- MYSQL_FIELD * fields = NULL;
171
170
  unsigned int i = 0;
172
171
  unsigned long * fieldLengths;
173
172
  void * ptr;
@@ -193,7 +192,6 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
193
192
  } else {
194
193
  rowVal = rb_hash_new();
195
194
  }
196
- fields = mysql_fetch_fields(wrapper->result);
197
195
  fieldLengths = mysql_fetch_lengths(wrapper->result);
198
196
  if (wrapper->fields == Qnil) {
199
197
  wrapper->numberOfFields = mysql_num_fields(wrapper->result);
@@ -225,7 +223,7 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
225
223
  break;
226
224
  case MYSQL_TYPE_TINY: // TINYINT field
227
225
  if (castBool && fields[i].length == 1) {
228
- val = *row[i] == '1' ? Qtrue : Qfalse;
226
+ val = *row[i] != '0' ? Qtrue : Qfalse;
229
227
  break;
230
228
  }
231
229
  case MYSQL_TYPE_SHORT: // SMALLINT field
@@ -237,7 +235,9 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo
237
235
  break;
238
236
  case MYSQL_TYPE_DECIMAL: // DECIMAL or NUMERIC field
239
237
  case MYSQL_TYPE_NEWDECIMAL: // Precision math DECIMAL or NUMERIC field (MySQL 5.0.3 and up)
240
- if (strtod(row[i], NULL) == 0.000000){
238
+ if (fields[i].decimals == 0) {
239
+ val = rb_cstr2inum(row[i], 10);
240
+ } else if (strtod(row[i], NULL) == 0.000000){
241
241
  val = rb_funcall(cBigDecimal, intern_new, 1, opt_decimal_zero);
242
242
  }else{
243
243
  val = rb_funcall(cBigDecimal, intern_new, 1, rb_str_new(row[i], fieldLengths[i]));
@@ -392,7 +392,8 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
392
392
  ID db_timezone, app_timezone, dbTz, appTz;
393
393
  mysql2_result_wrapper * wrapper;
394
394
  unsigned long i;
395
- int symbolizeKeys = 0, asArray = 0, castBool = 0, cacheRows = 1, cast = 1;
395
+ int symbolizeKeys = 0, asArray = 0, castBool = 0, cacheRows = 1, cast = 1, streaming = 0;
396
+ MYSQL_FIELD * fields = NULL;
396
397
 
397
398
  GetMysql2Result(self, wrapper);
398
399
 
@@ -423,6 +424,14 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
423
424
  cast = 0;
424
425
  }
425
426
 
427
+ if(rb_hash_aref(opts, sym_stream) == Qtrue) {
428
+ streaming = 1;
429
+ }
430
+
431
+ if(streaming && cacheRows) {
432
+ rb_warn("cacheRows is ignored if streaming is true");
433
+ }
434
+
426
435
  dbTz = rb_hash_aref(opts, sym_database_timezone);
427
436
  if (dbTz == sym_local) {
428
437
  db_timezone = intern_local;
@@ -445,49 +454,82 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
445
454
  }
446
455
 
447
456
  if (wrapper->lastRowProcessed == 0) {
448
- wrapper->numberOfRows = mysql_num_rows(wrapper->result);
449
- if (wrapper->numberOfRows == 0) {
457
+ if(streaming) {
458
+ // We can't get number of rows if we're streaming,
459
+ // until we've finished fetching all rows
460
+ wrapper->numberOfRows = 0;
450
461
  wrapper->rows = rb_ary_new();
451
- return wrapper->rows;
462
+ } else {
463
+ wrapper->numberOfRows = mysql_num_rows(wrapper->result);
464
+ if (wrapper->numberOfRows == 0) {
465
+ wrapper->rows = rb_ary_new();
466
+ return wrapper->rows;
467
+ }
468
+ wrapper->rows = rb_ary_new2(wrapper->numberOfRows);
452
469
  }
453
- wrapper->rows = rb_ary_new2(wrapper->numberOfRows);
454
470
  }
455
471
 
456
- if (cacheRows && wrapper->lastRowProcessed == wrapper->numberOfRows) {
457
- // we've already read the entire dataset from the C result into our
458
- // internal array. Lets hand that over to the user since it's ready to go
459
- for (i = 0; i < wrapper->numberOfRows; i++) {
460
- rb_yield(rb_ary_entry(wrapper->rows, i));
461
- }
462
- } else {
463
- unsigned long rowsProcessed = 0;
464
- rowsProcessed = RARRAY_LEN(wrapper->rows);
465
- for (i = 0; i < wrapper->numberOfRows; i++) {
472
+ if (streaming) {
473
+ if(!wrapper->streamingComplete) {
466
474
  VALUE row;
467
- if (cacheRows && i < rowsProcessed) {
468
- row = rb_ary_entry(wrapper->rows, i);
469
- } else {
470
- row = rb_mysql_result_fetch_row(self, db_timezone, app_timezone, symbolizeKeys, asArray, castBool, cast);
471
- if (cacheRows) {
472
- rb_ary_store(wrapper->rows, i, row);
475
+
476
+ fields = mysql_fetch_fields(wrapper->result);
477
+
478
+ do {
479
+ row = rb_mysql_result_fetch_row(self, db_timezone, app_timezone, symbolizeKeys, asArray, castBool, cast, fields);
480
+
481
+ if (block != Qnil && row != Qnil) {
482
+ rb_yield(row);
483
+ wrapper->lastRowProcessed++;
473
484
  }
474
- wrapper->lastRowProcessed++;
485
+ } while(row != Qnil);
486
+
487
+ rb_mysql_result_free_result(wrapper);
488
+
489
+ wrapper->numberOfRows = wrapper->lastRowProcessed;
490
+ wrapper->streamingComplete = 1;
491
+ } else {
492
+ rb_raise(cMysql2Error, "You have already fetched all the rows for this query and streaming is true. (to reiterate you must requery).");
493
+ }
494
+ } else {
495
+ if (cacheRows && wrapper->lastRowProcessed == wrapper->numberOfRows) {
496
+ // we've already read the entire dataset from the C result into our
497
+ // internal array. Lets hand that over to the user since it's ready to go
498
+ for (i = 0; i < wrapper->numberOfRows; i++) {
499
+ rb_yield(rb_ary_entry(wrapper->rows, i));
475
500
  }
501
+ } else {
502
+ unsigned long rowsProcessed = 0;
503
+ rowsProcessed = RARRAY_LEN(wrapper->rows);
504
+ fields = mysql_fetch_fields(wrapper->result);
505
+
506
+ for (i = 0; i < wrapper->numberOfRows; i++) {
507
+ VALUE row;
508
+ if (cacheRows && i < rowsProcessed) {
509
+ row = rb_ary_entry(wrapper->rows, i);
510
+ } else {
511
+ row = rb_mysql_result_fetch_row(self, db_timezone, app_timezone, symbolizeKeys, asArray, castBool, cast, fields);
512
+ if (cacheRows) {
513
+ rb_ary_store(wrapper->rows, i, row);
514
+ }
515
+ wrapper->lastRowProcessed++;
516
+ }
476
517
 
477
- if (row == Qnil) {
518
+ if (row == Qnil) {
519
+ // we don't need the mysql C dataset around anymore, peace it
520
+ rb_mysql_result_free_result(wrapper);
521
+ return Qnil;
522
+ }
523
+
524
+ if (block != Qnil) {
525
+ rb_yield(row);
526
+ }
527
+ }
528
+ if (wrapper->lastRowProcessed == wrapper->numberOfRows) {
478
529
  // we don't need the mysql C dataset around anymore, peace it
479
530
  rb_mysql_result_free_result(wrapper);
480
- return Qnil;
481
- }
482
-
483
- if (block != Qnil) {
484
- rb_yield(row);
485
531
  }
486
532
  }
487
- if (wrapper->lastRowProcessed == wrapper->numberOfRows) {
488
- // we don't need the mysql C dataset around anymore, peace it
489
- rb_mysql_result_free_result(wrapper);
490
- }
491
533
  }
492
534
 
493
535
  return wrapper->rows;
@@ -497,8 +539,15 @@ static VALUE rb_mysql_result_count(VALUE self) {
497
539
  mysql2_result_wrapper *wrapper;
498
540
 
499
541
  GetMysql2Result(self, wrapper);
500
-
501
- return INT2FIX(mysql_num_rows(wrapper->result));
542
+ if(wrapper->resultFreed) {
543
+ if (wrapper->streamingComplete){
544
+ return LONG2NUM(wrapper->numberOfRows);
545
+ } else {
546
+ return LONG2NUM(RARRAY_LEN(wrapper->rows));
547
+ }
548
+ } else {
549
+ return INT2FIX(mysql_num_rows(wrapper->result));
550
+ }
502
551
  }
503
552
 
504
553
  /* Mysql2::Result */
@@ -514,6 +563,7 @@ VALUE rb_mysql_result_to_obj(MYSQL_RES * r) {
514
563
  wrapper->fields = Qnil;
515
564
  wrapper->rows = Qnil;
516
565
  wrapper->encoding = Qnil;
566
+ wrapper->streamingComplete = 0;
517
567
  rb_obj_call_init(obj, 0, NULL);
518
568
  return obj;
519
569
  }
@@ -551,6 +601,7 @@ void init_mysql2_result() {
551
601
  sym_application_timezone = ID2SYM(rb_intern("application_timezone"));
552
602
  sym_cache_rows = ID2SYM(rb_intern("cache_rows"));
553
603
  sym_cast = ID2SYM(rb_intern("cast"));
604
+ sym_stream = ID2SYM(rb_intern("stream"));
554
605
 
555
606
  opt_decimal_zero = rb_str_new2("0.0");
556
607
  rb_global_variable(&opt_decimal_zero); //never GC
@@ -11,6 +11,7 @@ typedef struct {
11
11
  unsigned int numberOfFields;
12
12
  unsigned long numberOfRows;
13
13
  unsigned long lastRowProcessed;
14
+ char streamingComplete;
14
15
  char resultFreed;
15
16
  MYSQL_RES *result;
16
17
  } mysql2_result_wrapper;
@@ -314,13 +314,13 @@ module ActiveRecord
314
314
  end
315
315
 
316
316
  def add_limit_offset!(sql, options)
317
- limit, offset = options[:limit], options[:offset]
317
+ limit, offset = options.fetch(:limit, 18446744073709551615), options[:offset]
318
318
  if limit && offset
319
319
  sql << " LIMIT #{offset.to_i}, #{sanitize_limit(limit)}"
320
320
  elsif limit
321
321
  sql << " LIMIT #{sanitize_limit(limit)}"
322
322
  elsif offset
323
- sql << " OFFSET #{offset.to_i}"
323
+ sql << " LIMIT #{sanitize_limit(limit)} OFFSET #{offset.to_i}"
324
324
  end
325
325
  sql
326
326
  end
@@ -378,14 +378,32 @@ module ActiveRecord
378
378
  show_variable 'collation_database'
379
379
  end
380
380
 
381
- def tables(name = nil)
381
+ def tables(name = nil, database = nil)
382
382
  tables = []
383
- execute("SHOW TABLES", name).each do |field|
383
+
384
+ sql = "SHOW TABLES "
385
+ sql << "IN #{quote_table_name(database)} " if database
386
+
387
+ execute(sql, 'SCHEMA').each do |field|
384
388
  tables << field.first
385
389
  end
386
390
  tables
387
391
  end
388
392
 
393
+ def table_exists?(name)
394
+ return true if super
395
+
396
+ name = name.to_s
397
+ schema, table = name.split('.', 2)
398
+
399
+ unless table # A table was provided without a schema
400
+ table = schema
401
+ schema = nil
402
+ end
403
+
404
+ tables(nil, schema).include? table
405
+ end
406
+
389
407
  def drop_table(table_name, options = {})
390
408
  super(table_name, options)
391
409
  end
@@ -480,15 +498,26 @@ module ActiveRecord
480
498
 
481
499
  # Maps logical Rails types to MySQL-specific data types.
482
500
  def type_to_sql(type, limit = nil, precision = nil, scale = nil)
483
- return super unless type.to_s == 'integer'
484
-
485
- case limit
486
- when 1; 'tinyint'
487
- when 2; 'smallint'
488
- when 3; 'mediumint'
489
- when nil, 4, 11; 'int(11)' # compatibility with MySQL default
490
- when 5..8; 'bigint'
491
- else raise(ActiveRecordError, "No integer type has byte size #{limit}")
501
+ case type.to_s
502
+ when 'integer'
503
+ case limit
504
+ when 1; 'tinyint'
505
+ when 2; 'smallint'
506
+ when 3; 'mediumint'
507
+ when nil, 4, 11; 'int(11)' # compatibility with MySQL default
508
+ when 5..8; 'bigint'
509
+ else raise(ActiveRecordError, "No integer type has byte size #{limit}")
510
+ end
511
+ when 'text'
512
+ case limit
513
+ when 0..0xff; 'tinytext'
514
+ when nil, 0x100..0xffff; 'text'
515
+ when 0x10000..0xffffff; 'mediumtext'
516
+ when 0x1000000..0xffffffff; 'longtext'
517
+ else raise(ActiveRecordError, "No text type has character length #{limit}")
518
+ end
519
+ else
520
+ super
492
521
  end
493
522
  end
494
523
 
@@ -16,6 +16,21 @@ module Mysql2
16
16
  end
17
17
 
18
18
  if defined?(ActiveRecord::VERSION::STRING) && ActiveRecord::VERSION::STRING >= "3.1"
19
- puts "WARNING: This version of mysql2 (#{Mysql2::VERSION}) isn't compatible with Rails 3.1 as the ActiveRecord adapter was pulled into Rails itself."
20
- puts "WARNING: Please use the 0.3.x (or greater) releases if you plan on using it in Rails >= 3.1.x"
21
- end
19
+ warn "============= WARNING FROM mysql2 ============="
20
+ warn "This version of mysql2 (#{Mysql2::VERSION}) isn't compatible with Rails 3.1 as the ActiveRecord adapter was pulled into Rails itself."
21
+ warn "Please use the 0.3.x (or greater) releases if you plan on using it in Rails >= 3.1.x"
22
+ warn "============= END WARNING FROM mysql2 ============="
23
+ end
24
+
25
+ # For holding utility methods
26
+ module Mysql2::Util
27
+
28
+ #
29
+ # Rekey a string-keyed hash with equivalent symbols.
30
+ #
31
+ def self.key_hash_as_symbols(hash)
32
+ return nil unless hash
33
+ Hash[hash.map { |k,v| [k.to_sym, v] }]
34
+ end
35
+
36
+ end
@@ -14,31 +14,36 @@ module Mysql2
14
14
  }
15
15
 
16
16
  def initialize(opts = {})
17
+ opts = Mysql2::Util.key_hash_as_symbols( opts )
17
18
  @query_options = @@default_query_options.dup
18
19
  @query_options.merge! opts
19
20
 
20
- init_connection
21
+ initialize_ext
21
22
 
22
- [:reconnect, :connect_timeout].each do |key|
23
+ # Set MySQL connection options (each one is a call to mysql_options())
24
+ [:reconnect, :connect_timeout, :local_infile, :read_timeout].each do |key|
23
25
  next unless opts.key?(key)
24
26
  send(:"#{key}=", opts[key])
25
27
  end
28
+
26
29
  # force the encoding to utf8
27
30
  self.charset_name = opts[:encoding] || 'utf8'
28
31
 
29
- @read_timeout = opts[:read_timeout]
30
- if @read_timeout and @read_timeout < 0
31
- raise Mysql2::Error, "read_timeout must be a positive integer, you passed #{@read_timeout}"
32
- end
33
-
34
32
  ssl_set(*opts.values_at(:sslkey, :sslcert, :sslca, :sslcapath, :sslcipher))
33
+
34
+ if [:user,:pass,:hostname,:dbname,:db,:sock].any?{|k| @query_options.has_key?(k) }
35
+ warn "============= WARNING FROM mysql2 ============="
36
+ warn "The options :user, :pass, :hostname, :dbname, :db, and :sock will be deprecated at some point in the future."
37
+ warn "Instead, please use :username, :password, :host, 'localhost', :port, :database, :socket, :flags for the options."
38
+ warn "============= END WARNING FROM mysql2 ========="
39
+ end
35
40
 
36
- user = opts[:username]
37
- pass = opts[:password]
38
- host = opts[:host] || 'localhost'
41
+ user = opts[:username] || opts[:user]
42
+ pass = opts[:password] || opts[:pass]
43
+ host = opts[:host] || opts[:hostname] || 'localhost'
39
44
  port = opts[:port] || 3306
40
- database = opts[:database]
41
- socket = opts[:socket]
45
+ database = opts[:database] || opts[:dbname] || opts[:db]
46
+ socket = opts[:socket] || opts[:sock]
42
47
  flags = opts[:flags] ? opts[:flags] | @query_options[:connect_flags] : @query_options[:connect_flags]
43
48
 
44
49
  connect user, pass, host, port, database, socket, flags
@@ -87,6 +92,7 @@ module Mysql2
87
92
  "ucs2" => Encoding::UTF_16BE,
88
93
  "ujis" => Encoding::EucJP_ms,
89
94
  "utf8" => Encoding::UTF_8,
95
+ "utf8mb4" => Encoding::UTF_8,
90
96
  }
91
97
 
92
98
  MYSQL_CHARSET_MAP = {
@@ -134,6 +140,8 @@ module Mysql2
134
140
  42 => {:name => "latin7", :collation => "latin7_general_cs"},
135
141
  43 => {:name => "macce", :collation => "macce_bin"},
136
142
  44 => {:name => "cp1250", :collation => "cp1250_croatian_ci"},
143
+ 45 => {:name => "utf8mb4", :collation => "utf8mb4_general_ci"},
144
+ 46 => {:name => "utf8mb4", :collation => "utf8mb4_bin"},
137
145
  47 => {:name => "latin1", :collation => "latin1_bin"},
138
146
  48 => {:name => "latin1", :collation => "latin1_general_ci"},
139
147
  49 => {:name => "latin1", :collation => "latin1_general_cs"},
@@ -218,6 +226,25 @@ module Mysql2
218
226
  208 => {:name => "utf8", :collation => "utf8_persian_ci"},
219
227
  209 => {:name => "utf8", :collation => "utf8_esperanto_ci"},
220
228
  210 => {:name => "utf8", :collation => "utf8_hungarian_ci"},
229
+ 224 => {:name => "utf8mb4", :collation => "utf8mb4_unicode_ci"},
230
+ 225 => {:name => "utf8mb4", :collation => "utf8mb4_icelandic_ci"},
231
+ 226 => {:name => "utf8mb4", :collation => "utf8mb4_latvian_ci"},
232
+ 227 => {:name => "utf8mb4", :collation => "utf8mb4_romanian_ci"},
233
+ 228 => {:name => "utf8mb4", :collation => "utf8mb4_slovenian_ci"},
234
+ 229 => {:name => "utf8mb4", :collation => "utf8mb4_polish_ci"},
235
+ 230 => {:name => "utf8mb4", :collation => "utf8mb4_estonian_ci"},
236
+ 231 => {:name => "utf8mb4", :collation => "utf8mb4_spanish_ci"},
237
+ 232 => {:name => "utf8mb4", :collation => "utf8mb4_swedish_ci"},
238
+ 233 => {:name => "utf8mb4", :collation => "utf8mb4_turkish_ci"},
239
+ 234 => {:name => "utf8mb4", :collation => "utf8mb4_czech_ci"},
240
+ 235 => {:name => "utf8mb4", :collation => "utf8mb4_danish_ci"},
241
+ 236 => {:name => "utf8mb4", :collation => "utf8mb4_lithuanian_ci"},
242
+ 237 => {:name => "utf8mb4", :collation => "utf8mb4_slovak_ci"},
243
+ 238 => {:name => "utf8mb4", :collation => "utf8mb4_spanish2_ci"},
244
+ 239 => {:name => "utf8mb4", :collation => "utf8mb4_roman_ci"},
245
+ 240 => {:name => "utf8mb4", :collation => "utf8mb4_persian_ci"},
246
+ 241 => {:name => "utf8mb4", :collation => "utf8mb4_esperanto_ci"},
247
+ 242 => {:name => "utf8mb4", :collation => "utf8mb4_hungarian_ci"},
221
248
  254 => {:name => "utf8", :collation => "utf8_general_cs"}
222
249
  }
223
250