mysql2 0.1.3 → 0.1.4
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/CHANGELOG.md +5 -0
- data/README.rdoc +5 -5
- data/VERSION +1 -1
- data/benchmark/active_record.rb +1 -2
- data/benchmark/query.rb +2 -2
- data/ext/mysql2_ext.c +89 -51
- data/ext/mysql2_ext.h +12 -2
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +399 -7
- data/lib/mysql2.rb +1 -1
- data/mysql2.gemspec +2 -2
- data/spec/mysql2/result_spec.rb +4 -0
- metadata +3 -3
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,10 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## 0.1.4 (April 23rd, 2010)
|
4
|
+
* optimization: implemented a local cache for rows that are lazily created in ruby during iteration. The MySQL C result is freed as soon as all the results have been cached
|
5
|
+
* optimization: implemented a local cache for field names so every row reuses the same objects as field names/keys
|
6
|
+
* refactor the Mysql2 connection adapter for ActiveRecord to not extend the Mysql adapter - now being a free-standing connection adapter
|
7
|
+
|
3
8
|
## 0.1.3 (April 15th, 2010)
|
4
9
|
* added an EventMachine Deferrable API
|
5
10
|
* added an ActiveRecord connection adapter
|
data/README.rdoc
CHANGED
@@ -130,11 +130,11 @@ Me: Yep, but it's API is considerably more complex *and* is 2-3x slower.
|
|
130
130
|
Performing a basic "SELECT * FROM" query on a table with 30k rows and fields of nearly every Ruby-representable data type,
|
131
131
|
then iterating over every row using an #each like method yielding a block:
|
132
132
|
|
133
|
-
# And remember, the Mysql gem only gives back nil and strings.
|
134
|
-
user system total
|
133
|
+
# And remember, the Mysql gem only gives back nil and strings for values.
|
134
|
+
user system total real
|
135
135
|
Mysql2
|
136
|
-
|
136
|
+
0.610000 0.160000 0.770000 ( 0.986967)
|
137
137
|
Mysql
|
138
|
-
|
138
|
+
0.350000 0.220000 0.570000 ( 1.457889)
|
139
139
|
do_mysql
|
140
|
-
|
140
|
+
1.710000 0.180000 1.890000 ( 2.124831)
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.4
|
data/benchmark/active_record.rb
CHANGED
@@ -4,9 +4,8 @@ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
|
|
4
4
|
require 'rubygems'
|
5
5
|
require 'benchmark'
|
6
6
|
require 'active_record'
|
7
|
-
require 'ruby-debug'
|
8
7
|
|
9
|
-
number_of =
|
8
|
+
number_of = 10
|
10
9
|
mysql2_opts = {
|
11
10
|
:adapter => 'mysql2',
|
12
11
|
:database => 'test'
|
data/benchmark/query.rb
CHANGED
@@ -6,9 +6,9 @@ require 'mysql'
|
|
6
6
|
require 'mysql2_ext'
|
7
7
|
require 'do_mysql'
|
8
8
|
|
9
|
-
number_of =
|
9
|
+
number_of = 100
|
10
10
|
database = 'test'
|
11
|
-
sql = "SELECT * FROM mysql2_test"
|
11
|
+
sql = "SELECT * FROM mysql2_test LIMIT 100"
|
12
12
|
|
13
13
|
Benchmark.bmbm do |x|
|
14
14
|
mysql2 = Mysql2::Client.new(:host => "localhost", :username => "root")
|
data/ext/mysql2_ext.c
CHANGED
@@ -269,28 +269,43 @@ static VALUE rb_mysql_client_affected_rows(VALUE self) {
|
|
269
269
|
/* Mysql2::Result */
|
270
270
|
static VALUE rb_mysql_result_to_obj(MYSQL_RES * r) {
|
271
271
|
VALUE obj;
|
272
|
-
|
272
|
+
mysql2_result_wrapper * wrapper;
|
273
|
+
obj = Data_Make_Struct(cMysql2Result, mysql2_result_wrapper, rb_mysql_result_mark, rb_mysql_result_free, wrapper);
|
274
|
+
wrapper->numberOfFields = 0;
|
275
|
+
wrapper->numberOfRows = 0;
|
276
|
+
wrapper->lastRowProcessed = 0;
|
277
|
+
wrapper->resultFreed = 0;
|
278
|
+
wrapper->result = r;
|
273
279
|
rb_obj_call_init(obj, 0, NULL);
|
274
280
|
return obj;
|
275
281
|
}
|
276
282
|
|
277
|
-
void rb_mysql_result_free(void *
|
278
|
-
|
279
|
-
if (
|
280
|
-
mysql_free_result(
|
283
|
+
void rb_mysql_result_free(void * wrapper) {
|
284
|
+
mysql2_result_wrapper * w = wrapper;
|
285
|
+
if (w && w->resultFreed != 1) {
|
286
|
+
mysql_free_result(w->result);
|
287
|
+
w->resultFreed = 1;
|
281
288
|
}
|
282
289
|
}
|
283
290
|
|
291
|
+
void rb_mysql_result_mark(void * wrapper) {
|
292
|
+
mysql2_result_wrapper * w = wrapper;
|
293
|
+
if (w) {
|
294
|
+
rb_gc_mark(w->fields);
|
295
|
+
rb_gc_mark(w->rows);
|
296
|
+
}
|
297
|
+
}
|
298
|
+
|
284
299
|
static VALUE rb_mysql_result_fetch_row(int argc, VALUE * argv, VALUE self) {
|
285
300
|
VALUE rowHash, opts, block;
|
286
|
-
|
301
|
+
mysql2_result_wrapper * wrapper;
|
287
302
|
MYSQL_ROW row;
|
288
|
-
MYSQL_FIELD * fields;
|
303
|
+
MYSQL_FIELD * fields = NULL;
|
289
304
|
struct tm parsedTime;
|
290
|
-
unsigned int i = 0,
|
305
|
+
unsigned int i = 0, symbolizeKeys = 0;
|
291
306
|
unsigned long * fieldLengths;
|
292
307
|
|
293
|
-
GetMysql2Result(self,
|
308
|
+
GetMysql2Result(self, wrapper);
|
294
309
|
|
295
310
|
if (rb_scan_args(argc, argv, "01&", &opts, &block) == 1) {
|
296
311
|
Check_Type(opts, T_HASH);
|
@@ -299,29 +314,39 @@ static VALUE rb_mysql_result_fetch_row(int argc, VALUE * argv, VALUE self) {
|
|
299
314
|
}
|
300
315
|
}
|
301
316
|
|
302
|
-
row = mysql_fetch_row(result);
|
317
|
+
row = mysql_fetch_row(wrapper->result);
|
303
318
|
if (row == NULL) {
|
304
319
|
return Qnil;
|
305
320
|
}
|
306
321
|
|
307
|
-
|
308
|
-
|
309
|
-
|
322
|
+
if (wrapper->numberOfFields == 0) {
|
323
|
+
wrapper->numberOfFields = mysql_num_fields(wrapper->result);
|
324
|
+
wrapper->fields = rb_ary_new2(wrapper->numberOfFields);
|
325
|
+
}
|
310
326
|
|
311
327
|
rowHash = rb_hash_new();
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
328
|
+
fields = mysql_fetch_fields(wrapper->result);
|
329
|
+
fieldLengths = mysql_fetch_lengths(wrapper->result);
|
330
|
+
for (i = 0; i < wrapper->numberOfFields; i++) {
|
331
|
+
|
332
|
+
// lazily create fields, but only once
|
333
|
+
// we'll use cached versions from here on out
|
334
|
+
VALUE field = rb_ary_entry(wrapper->fields, i);
|
335
|
+
if (field == Qnil) {
|
336
|
+
if (symbolizeKeys) {
|
337
|
+
char buf[fields[i].name_length+1];
|
338
|
+
memcpy(buf, fields[i].name, fields[i].name_length);
|
339
|
+
buf[fields[i].name_length] = 0;
|
340
|
+
field = ID2SYM(rb_intern(buf));
|
341
|
+
} else {
|
342
|
+
field = rb_str_new(fields[i].name, fields[i].name_length);
|
321
343
|
#ifdef HAVE_RUBY_ENCODING_H
|
322
|
-
|
344
|
+
rb_enc_associate_index(field, utf8Encoding);
|
323
345
|
#endif
|
346
|
+
}
|
347
|
+
rb_ary_store(wrapper->fields, i, field);
|
324
348
|
}
|
349
|
+
|
325
350
|
if (row[i]) {
|
326
351
|
VALUE val;
|
327
352
|
switch(fields[i].type) {
|
@@ -393,54 +418,67 @@ static VALUE rb_mysql_result_fetch_row(int argc, VALUE * argv, VALUE self) {
|
|
393
418
|
#endif
|
394
419
|
break;
|
395
420
|
}
|
396
|
-
rb_hash_aset(rowHash,
|
421
|
+
rb_hash_aset(rowHash, field, val);
|
397
422
|
} else {
|
398
|
-
rb_hash_aset(rowHash,
|
423
|
+
rb_hash_aset(rowHash, field, Qnil);
|
399
424
|
}
|
400
425
|
}
|
401
426
|
return rowHash;
|
402
427
|
}
|
403
428
|
|
404
429
|
static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
|
405
|
-
VALUE
|
406
|
-
|
407
|
-
unsigned long
|
430
|
+
VALUE opts, block;
|
431
|
+
mysql2_result_wrapper * wrapper;
|
432
|
+
unsigned long i;
|
408
433
|
|
409
|
-
GetMysql2Result(self,
|
434
|
+
GetMysql2Result(self, wrapper);
|
410
435
|
|
411
436
|
rb_scan_args(argc, argv, "01&", &opts, &block);
|
412
437
|
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
return Qnil;
|
438
|
+
if (wrapper->lastRowProcessed == 0) {
|
439
|
+
wrapper->numberOfRows = mysql_num_rows(wrapper->result);
|
440
|
+
if (wrapper->numberOfRows == 0) {
|
441
|
+
return Qnil;
|
442
|
+
}
|
443
|
+
wrapper->rows = rb_ary_new2(wrapper->numberOfRows);
|
420
444
|
}
|
421
445
|
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
for (i = 0; i <
|
426
|
-
|
427
|
-
if (row == Qnil) {
|
428
|
-
return Qnil;
|
429
|
-
}
|
430
|
-
rb_yield(row);
|
446
|
+
if (wrapper->lastRowProcessed == wrapper->numberOfRows) {
|
447
|
+
// we've already read the entire dataset from the C result into our
|
448
|
+
// internal array. Lets hand that over to the user since it's ready to go
|
449
|
+
for (i = 0; i < wrapper->numberOfRows; i++) {
|
450
|
+
rb_yield(rb_ary_entry(wrapper->rows, i));
|
431
451
|
}
|
432
452
|
} else {
|
433
|
-
|
434
|
-
|
435
|
-
|
453
|
+
unsigned long rowsProcessed = 0;
|
454
|
+
rowsProcessed = RARRAY_LEN(wrapper->rows);
|
455
|
+
for (i = 0; i < wrapper->numberOfRows; i++) {
|
456
|
+
VALUE row;
|
457
|
+
if (i < rowsProcessed) {
|
458
|
+
row = rb_ary_entry(wrapper->rows, i);
|
459
|
+
} else {
|
460
|
+
row = rb_mysql_result_fetch_row(argc, argv, self);
|
461
|
+
rb_ary_store(wrapper->rows, i, row);
|
462
|
+
wrapper->lastRowProcessed++;
|
463
|
+
}
|
464
|
+
|
436
465
|
if (row == Qnil) {
|
466
|
+
// we don't need the mysql C dataset around anymore, peace it
|
467
|
+
rb_mysql_result_free(wrapper->result);
|
437
468
|
return Qnil;
|
438
469
|
}
|
439
|
-
|
470
|
+
|
471
|
+
if (block != Qnil) {
|
472
|
+
rb_yield(row);
|
473
|
+
}
|
474
|
+
}
|
475
|
+
if (wrapper->lastRowProcessed == wrapper->numberOfRows) {
|
476
|
+
// we don't need the mysql C dataset around anymore, peace it
|
477
|
+
rb_mysql_result_free(wrapper);
|
440
478
|
}
|
441
|
-
return dataset;
|
442
479
|
}
|
443
|
-
|
480
|
+
|
481
|
+
return wrapper->rows;
|
444
482
|
}
|
445
483
|
|
446
484
|
/* Ruby Extension initializer */
|
data/ext/mysql2_ext.h
CHANGED
@@ -43,9 +43,19 @@ static VALUE rb_mysql_client_affected_rows(VALUE self);
|
|
43
43
|
void rb_mysql_client_free(void * client);
|
44
44
|
|
45
45
|
/* Mysql2::Result */
|
46
|
-
|
46
|
+
typedef struct {
|
47
|
+
VALUE fields;
|
48
|
+
VALUE rows;
|
49
|
+
unsigned long numberOfFields;
|
50
|
+
unsigned long numberOfRows;
|
51
|
+
unsigned long lastRowProcessed;
|
52
|
+
int resultFreed;
|
53
|
+
MYSQL_RES *result;
|
54
|
+
} mysql2_result_wrapper;
|
55
|
+
#define GetMysql2Result(obj, sval) (sval = (mysql2_result_wrapper*)DATA_PTR(obj));
|
47
56
|
VALUE cMysql2Result;
|
48
57
|
static VALUE rb_mysql_result_to_obj(MYSQL_RES * res);
|
49
58
|
static VALUE rb_mysql_result_fetch_row(int argc, VALUE * argv, VALUE self);
|
50
59
|
static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self);
|
51
|
-
void rb_mysql_result_free(void *
|
60
|
+
void rb_mysql_result_free(void * wrapper);
|
61
|
+
void rb_mysql_result_mark(void * wrapper);
|
@@ -1,7 +1,6 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
3
|
require 'mysql2' unless defined? Mysql2
|
4
|
-
require 'active_record/connection_adapters/mysql_adapter'
|
5
4
|
|
6
5
|
module ActiveRecord
|
7
6
|
class Base
|
@@ -13,7 +12,26 @@ module ActiveRecord
|
|
13
12
|
end
|
14
13
|
|
15
14
|
module ConnectionAdapters
|
16
|
-
class Mysql2Column <
|
15
|
+
class Mysql2Column < Column
|
16
|
+
def extract_default(default)
|
17
|
+
if sql_type =~ /blob/i || type == :text
|
18
|
+
if default.blank?
|
19
|
+
return null ? nil : ''
|
20
|
+
else
|
21
|
+
raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
|
22
|
+
end
|
23
|
+
elsif missing_default_forged_as_empty_string?(default)
|
24
|
+
nil
|
25
|
+
else
|
26
|
+
super
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def has_default?
|
31
|
+
return false if sql_type =~ /blob/i || type == :text #mysql forbids defaults on blob and text columns
|
32
|
+
super
|
33
|
+
end
|
34
|
+
|
17
35
|
# Returns the Ruby class that corresponds to the abstract data type.
|
18
36
|
def klass
|
19
37
|
case type
|
@@ -37,25 +55,154 @@ module ActiveRecord
|
|
37
55
|
value
|
38
56
|
end
|
39
57
|
end
|
40
|
-
|
58
|
+
|
41
59
|
def type_cast_code(var_name)
|
42
60
|
nil
|
43
61
|
end
|
62
|
+
|
63
|
+
private
|
64
|
+
def simplified_type(field_type)
|
65
|
+
return :boolean if Mysql2Adapter.emulate_booleans && field_type.downcase.index("tinyint(1)")
|
66
|
+
return :string if field_type =~ /enum/i
|
67
|
+
super
|
68
|
+
end
|
69
|
+
|
70
|
+
def extract_limit(sql_type)
|
71
|
+
case sql_type
|
72
|
+
when /blob|text/i
|
73
|
+
case sql_type
|
74
|
+
when /tiny/i
|
75
|
+
255
|
76
|
+
when /medium/i
|
77
|
+
16777215
|
78
|
+
when /long/i
|
79
|
+
2147483647 # mysql only allows 2^31-1, not 2^32-1, somewhat inconsistently with the tiny/medium/normal cases
|
80
|
+
else
|
81
|
+
super # we could return 65535 here, but we leave it undecorated by default
|
82
|
+
end
|
83
|
+
when /^bigint/i; 8
|
84
|
+
when /^int/i; 4
|
85
|
+
when /^mediumint/i; 3
|
86
|
+
when /^smallint/i; 2
|
87
|
+
when /^tinyint/i; 1
|
88
|
+
else
|
89
|
+
super
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# MySQL misreports NOT NULL column default when none is given.
|
94
|
+
# We can't detect this for columns which may have a legitimate ''
|
95
|
+
# default (string) but we can for others (integer, datetime, boolean,
|
96
|
+
# and the rest).
|
97
|
+
#
|
98
|
+
# Test whether the column has default '', is not null, and is not
|
99
|
+
# a type allowing default ''.
|
100
|
+
def missing_default_forged_as_empty_string?(default)
|
101
|
+
type != :string && !null && default == ''
|
102
|
+
end
|
44
103
|
end
|
45
104
|
|
46
|
-
class Mysql2Adapter <
|
47
|
-
|
48
|
-
|
105
|
+
class Mysql2Adapter < AbstractAdapter
|
106
|
+
cattr_accessor :emulate_booleans
|
107
|
+
self.emulate_booleans = true
|
108
|
+
|
109
|
+
ADAPTER_NAME = 'MySQL'.freeze
|
110
|
+
|
111
|
+
LOST_CONNECTION_ERROR_MESSAGES = [
|
112
|
+
"Server shutdown in progress",
|
113
|
+
"Broken pipe",
|
114
|
+
"Lost connection to MySQL server during query",
|
115
|
+
"MySQL server has gone away" ]
|
116
|
+
|
117
|
+
QUOTED_TRUE, QUOTED_FALSE = '1'.freeze, '0'.freeze
|
118
|
+
|
119
|
+
NATIVE_DATABASE_TYPES = {
|
120
|
+
:primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY".freeze,
|
121
|
+
:string => { :name => "varchar", :limit => 255 },
|
122
|
+
:text => { :name => "text" },
|
123
|
+
:integer => { :name => "int", :limit => 4 },
|
124
|
+
:float => { :name => "float" },
|
125
|
+
:decimal => { :name => "decimal" },
|
126
|
+
:datetime => { :name => "datetime" },
|
127
|
+
:timestamp => { :name => "datetime" },
|
128
|
+
:time => { :name => "time" },
|
129
|
+
:date => { :name => "date" },
|
130
|
+
:binary => { :name => "blob" },
|
131
|
+
:boolean => { :name => "tinyint", :limit => 1 }
|
132
|
+
}
|
133
|
+
|
134
|
+
def initialize(connection, logger, connection_options, config)
|
135
|
+
super(connection, logger)
|
136
|
+
@connection_options, @config = connection_options, config
|
137
|
+
@quoted_column_names, @quoted_table_names = {}, {}
|
138
|
+
end
|
49
139
|
|
50
140
|
def adapter_name
|
51
141
|
ADAPTER_NAME
|
52
142
|
end
|
53
143
|
|
144
|
+
def supports_migrations?
|
145
|
+
true
|
146
|
+
end
|
147
|
+
|
148
|
+
def supports_primary_key?
|
149
|
+
true
|
150
|
+
end
|
151
|
+
|
152
|
+
def supports_savepoints?
|
153
|
+
true
|
154
|
+
end
|
155
|
+
|
156
|
+
def native_database_types
|
157
|
+
NATIVE_DATABASE_TYPES
|
158
|
+
end
|
159
|
+
|
54
160
|
# QUOTING ==================================================
|
161
|
+
|
162
|
+
def quote(value, column = nil)
|
163
|
+
if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary)
|
164
|
+
s = column.class.string_to_binary(value).unpack("H*")[0]
|
165
|
+
"x'#{s}'"
|
166
|
+
elsif value.kind_of?(BigDecimal)
|
167
|
+
value.to_s("F")
|
168
|
+
else
|
169
|
+
super
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def quote_column_name(name) #:nodoc:
|
174
|
+
@quoted_column_names[name] ||= "`#{name}`"
|
175
|
+
end
|
176
|
+
|
177
|
+
def quote_table_name(name) #:nodoc:
|
178
|
+
@quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`')
|
179
|
+
end
|
180
|
+
|
55
181
|
def quote_string(string)
|
56
182
|
@connection.escape(string)
|
57
183
|
end
|
58
184
|
|
185
|
+
def quoted_true
|
186
|
+
QUOTED_TRUE
|
187
|
+
end
|
188
|
+
|
189
|
+
def quoted_false
|
190
|
+
QUOTED_FALSE
|
191
|
+
end
|
192
|
+
|
193
|
+
# REFERENTIAL INTEGRITY ====================================
|
194
|
+
|
195
|
+
def disable_referential_integrity(&block) #:nodoc:
|
196
|
+
old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
|
197
|
+
|
198
|
+
begin
|
199
|
+
update("SET FOREIGN_KEY_CHECKS = 0")
|
200
|
+
yield
|
201
|
+
ensure
|
202
|
+
update("SET FOREIGN_KEY_CHECKS = #{old}")
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
59
206
|
# CONNECTION MANAGEMENT ====================================
|
60
207
|
|
61
208
|
def active?
|
@@ -88,14 +235,127 @@ module ActiveRecord
|
|
88
235
|
select(sql, name)
|
89
236
|
end
|
90
237
|
|
238
|
+
# Executes a SQL query and returns a MySQL::Result object. Note that you have to free the Result object after you're done using it.
|
239
|
+
def execute(sql, name = nil)
|
240
|
+
if name == :skip_logging
|
241
|
+
@connection.query(sql)
|
242
|
+
else
|
243
|
+
log(sql, name) { @connection.query(sql) }
|
244
|
+
end
|
245
|
+
rescue ActiveRecord::StatementInvalid => exception
|
246
|
+
if exception.message.split(":").first =~ /Packets out of order/
|
247
|
+
raise ActiveRecord::StatementInvalid, "'Packets out of order' error was received from the database. Please update your mysql bindings (gem install mysql) and read http://dev.mysql.com/doc/mysql/en/password-hashing.html for more information. If you're on Windows, use the Instant Rails installer to get the updated mysql bindings."
|
248
|
+
else
|
249
|
+
raise
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
91
253
|
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
|
92
254
|
super sql, name
|
93
255
|
id_value || @connection.last_id
|
94
256
|
end
|
95
257
|
alias :create :insert_sql
|
96
258
|
|
259
|
+
def update_sql(sql, name = nil)
|
260
|
+
super
|
261
|
+
@connection.affected_rows
|
262
|
+
end
|
263
|
+
|
264
|
+
def begin_db_transaction
|
265
|
+
execute "BEGIN"
|
266
|
+
rescue Exception
|
267
|
+
# Transactions aren't supported
|
268
|
+
end
|
269
|
+
|
270
|
+
def commit_db_transaction
|
271
|
+
execute "COMMIT"
|
272
|
+
rescue Exception
|
273
|
+
# Transactions aren't supported
|
274
|
+
end
|
275
|
+
|
276
|
+
def rollback_db_transaction
|
277
|
+
execute "ROLLBACK"
|
278
|
+
rescue Exception
|
279
|
+
# Transactions aren't supported
|
280
|
+
end
|
281
|
+
|
282
|
+
def create_savepoint
|
283
|
+
execute("SAVEPOINT #{current_savepoint_name}")
|
284
|
+
end
|
285
|
+
|
286
|
+
def rollback_to_savepoint
|
287
|
+
execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
|
288
|
+
end
|
289
|
+
|
290
|
+
def release_savepoint
|
291
|
+
execute("RELEASE SAVEPOINT #{current_savepoint_name}")
|
292
|
+
end
|
293
|
+
|
294
|
+
def add_limit_offset!(sql, options)
|
295
|
+
limit, offset = options[:limit], options[:offset]
|
296
|
+
if limit && offset
|
297
|
+
sql << " LIMIT #{offset.to_i}, #{sanitize_limit(limit)}"
|
298
|
+
elsif limit
|
299
|
+
sql << " LIMIT #{sanitize_limit(limit)}"
|
300
|
+
elsif offset
|
301
|
+
sql << " OFFSET #{offset.to_i}"
|
302
|
+
end
|
303
|
+
sql
|
304
|
+
end
|
305
|
+
|
97
306
|
# SCHEMA STATEMENTS ========================================
|
98
307
|
|
308
|
+
def structure_dump
|
309
|
+
if supports_views?
|
310
|
+
sql = "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'"
|
311
|
+
else
|
312
|
+
sql = "SHOW TABLES"
|
313
|
+
end
|
314
|
+
|
315
|
+
select_all(sql).inject("") do |structure, table|
|
316
|
+
table.delete('Table_type')
|
317
|
+
structure += select_one("SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}")["Create Table"] + ";\n\n"
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
def recreate_database(name, options = {})
|
322
|
+
drop_database(name)
|
323
|
+
create_database(name, options)
|
324
|
+
end
|
325
|
+
|
326
|
+
# Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
|
327
|
+
# Charset defaults to utf8.
|
328
|
+
#
|
329
|
+
# Example:
|
330
|
+
# create_database 'charset_test', :charset => 'latin1', :collation => 'latin1_bin'
|
331
|
+
# create_database 'matt_development'
|
332
|
+
# create_database 'matt_development', :charset => :big5
|
333
|
+
def create_database(name, options = {})
|
334
|
+
if options[:collation]
|
335
|
+
execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{options[:collation]}`"
|
336
|
+
else
|
337
|
+
execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}`"
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
def drop_database(name) #:nodoc:
|
342
|
+
execute "DROP DATABASE IF EXISTS `#{name}`"
|
343
|
+
end
|
344
|
+
|
345
|
+
def current_database
|
346
|
+
select_value 'SELECT DATABASE() as db'
|
347
|
+
end
|
348
|
+
|
349
|
+
# Returns the database character set.
|
350
|
+
def charset
|
351
|
+
show_variable 'character_set_database'
|
352
|
+
end
|
353
|
+
|
354
|
+
# Returns the database collation strategy.
|
355
|
+
def collation
|
356
|
+
show_variable 'collation_database'
|
357
|
+
end
|
358
|
+
|
99
359
|
def tables(name = nil)
|
100
360
|
tables = []
|
101
361
|
execute("SHOW TABLES", name).each(:symbolize_keys => true) do |field|
|
@@ -104,6 +364,10 @@ module ActiveRecord
|
|
104
364
|
tables
|
105
365
|
end
|
106
366
|
|
367
|
+
def drop_table(table_name, options = {})
|
368
|
+
super(table_name, options)
|
369
|
+
end
|
370
|
+
|
107
371
|
def indexes(table_name, name = nil)
|
108
372
|
indexes = []
|
109
373
|
current_index = nil
|
@@ -130,6 +394,89 @@ module ActiveRecord
|
|
130
394
|
columns
|
131
395
|
end
|
132
396
|
|
397
|
+
def create_table(table_name, options = {})
|
398
|
+
super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
|
399
|
+
end
|
400
|
+
|
401
|
+
def rename_table(table_name, new_name)
|
402
|
+
execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
|
403
|
+
end
|
404
|
+
|
405
|
+
def add_column(table_name, column_name, type, options = {})
|
406
|
+
add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
|
407
|
+
add_column_options!(add_column_sql, options)
|
408
|
+
add_column_position!(add_column_sql, options)
|
409
|
+
execute(add_column_sql)
|
410
|
+
end
|
411
|
+
|
412
|
+
def change_column_default(table_name, column_name, default)
|
413
|
+
column = column_for(table_name, column_name)
|
414
|
+
change_column table_name, column_name, column.sql_type, :default => default
|
415
|
+
end
|
416
|
+
|
417
|
+
def change_column_null(table_name, column_name, null, default = nil)
|
418
|
+
column = column_for(table_name, column_name)
|
419
|
+
|
420
|
+
unless null || default.nil?
|
421
|
+
execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
|
422
|
+
end
|
423
|
+
|
424
|
+
change_column table_name, column_name, column.sql_type, :null => null
|
425
|
+
end
|
426
|
+
|
427
|
+
def change_column(table_name, column_name, type, options = {})
|
428
|
+
column = column_for(table_name, column_name)
|
429
|
+
|
430
|
+
unless options_include_default?(options)
|
431
|
+
options[:default] = column.default
|
432
|
+
end
|
433
|
+
|
434
|
+
unless options.has_key?(:null)
|
435
|
+
options[:null] = column.null
|
436
|
+
end
|
437
|
+
|
438
|
+
change_column_sql = "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
|
439
|
+
add_column_options!(change_column_sql, options)
|
440
|
+
add_column_position!(change_column_sql, options)
|
441
|
+
execute(change_column_sql)
|
442
|
+
end
|
443
|
+
|
444
|
+
def rename_column(table_name, column_name, new_column_name)
|
445
|
+
options = {}
|
446
|
+
if column = columns(table_name).find { |c| c.name == column_name.to_s }
|
447
|
+
options[:default] = column.default
|
448
|
+
options[:null] = column.null
|
449
|
+
else
|
450
|
+
raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
|
451
|
+
end
|
452
|
+
current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"]
|
453
|
+
rename_column_sql = "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}"
|
454
|
+
add_column_options!(rename_column_sql, options)
|
455
|
+
execute(rename_column_sql)
|
456
|
+
end
|
457
|
+
|
458
|
+
# Maps logical Rails types to MySQL-specific data types.
|
459
|
+
def type_to_sql(type, limit = nil, precision = nil, scale = nil)
|
460
|
+
return super unless type.to_s == 'integer'
|
461
|
+
|
462
|
+
case limit
|
463
|
+
when 1; 'tinyint'
|
464
|
+
when 2; 'smallint'
|
465
|
+
when 3; 'mediumint'
|
466
|
+
when nil, 4, 11; 'int(11)' # compatibility with MySQL default
|
467
|
+
when 5..8; 'bigint'
|
468
|
+
else raise(ActiveRecordError, "No integer type has byte size #{limit}")
|
469
|
+
end
|
470
|
+
end
|
471
|
+
|
472
|
+
def add_column_position!(sql, options)
|
473
|
+
if options[:first]
|
474
|
+
sql << " FIRST"
|
475
|
+
elsif options[:after]
|
476
|
+
sql << " AFTER #{quote_column_name(options[:after])}"
|
477
|
+
end
|
478
|
+
end
|
479
|
+
|
133
480
|
def show_variable(name)
|
134
481
|
variables = select_all("SHOW VARIABLES LIKE '#{name}'")
|
135
482
|
variables.first[:Value] unless variables.empty?
|
@@ -138,17 +485,55 @@ module ActiveRecord
|
|
138
485
|
def pk_and_sequence_for(table)
|
139
486
|
keys = []
|
140
487
|
result = execute("describe #{quote_table_name(table)}")
|
141
|
-
result.each(:symbolize_keys) do |row|
|
488
|
+
result.each(:symbolize_keys => true) do |row|
|
142
489
|
keys << row[:Field] if row[:Key] == "PRI"
|
143
490
|
end
|
144
491
|
keys.length == 1 ? [keys.first, nil] : nil
|
145
492
|
end
|
146
493
|
|
494
|
+
# Returns just a table's primary key
|
495
|
+
def primary_key(table)
|
496
|
+
pk_and_sequence = pk_and_sequence_for(table)
|
497
|
+
pk_and_sequence && pk_and_sequence.first
|
498
|
+
end
|
499
|
+
|
500
|
+
def case_sensitive_equality_operator
|
501
|
+
"= BINARY"
|
502
|
+
end
|
503
|
+
|
504
|
+
def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
|
505
|
+
where_sql
|
506
|
+
end
|
507
|
+
|
508
|
+
protected
|
509
|
+
# TODO: implement error_number method on Mysql2::Exception
|
510
|
+
def translate_exception(exception, message)
|
511
|
+
return super unless exception.respond_to?(:error_number)
|
512
|
+
|
513
|
+
case exception.error_number
|
514
|
+
when 1062
|
515
|
+
RecordNotUnique.new(message, exception)
|
516
|
+
when 1452
|
517
|
+
InvalidForeignKey.new(message, exception)
|
518
|
+
else
|
519
|
+
super
|
520
|
+
end
|
521
|
+
end
|
522
|
+
|
147
523
|
private
|
148
524
|
def connect
|
149
525
|
# no-op
|
150
526
|
end
|
151
527
|
|
528
|
+
def configure_connection
|
529
|
+
encoding = @config[:encoding]
|
530
|
+
execute("SET NAMES '#{encoding}'", :skip_logging) if encoding
|
531
|
+
|
532
|
+
# By default, MySQL 'where id is null' selects the last inserted id.
|
533
|
+
# Turn this off. http://dev.rubyonrails.org/ticket/6778
|
534
|
+
execute("SET SQL_AUTO_IS_NULL=0", :skip_logging)
|
535
|
+
end
|
536
|
+
|
152
537
|
def select(sql, name = nil)
|
153
538
|
execute(sql, name).to_a
|
154
539
|
end
|
@@ -160,6 +545,13 @@ module ActiveRecord
|
|
160
545
|
def version
|
161
546
|
@version ||= @connection.info[:version].scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
|
162
547
|
end
|
548
|
+
|
549
|
+
def column_for(table_name, column_name)
|
550
|
+
unless column = columns(table_name).find { |c| c.name == column_name.to_s }
|
551
|
+
raise "No such column: #{table_name}.#{column_name}"
|
552
|
+
end
|
553
|
+
column
|
554
|
+
end
|
163
555
|
end
|
164
556
|
end
|
165
557
|
end
|
data/lib/mysql2.rb
CHANGED
data/mysql2.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{mysql2}
|
8
|
-
s.version = "0.1.
|
8
|
+
s.version = "0.1.4"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Brian Lopez"]
|
12
|
-
s.date = %q{2010-04-
|
12
|
+
s.date = %q{2010-04-23}
|
13
13
|
s.email = %q{seniorlopez@gmail.com}
|
14
14
|
s.extensions = ["ext/extconf.rb"]
|
15
15
|
s.extra_rdoc_files = [
|
data/spec/mysql2/result_spec.rb
CHANGED
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 1
|
8
|
-
-
|
9
|
-
version: 0.1.
|
8
|
+
- 4
|
9
|
+
version: 0.1.4
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Brian Lopez
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-04-
|
17
|
+
date: 2010-04-23 00:00:00 -07:00
|
18
18
|
default_executable:
|
19
19
|
dependencies: []
|
20
20
|
|