mysql2 0.2.18 → 0.2.19b1

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