activerecord 3.1.9 → 3.2.12
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.
- checksums.yaml +6 -6
- data/CHANGELOG.md +317 -336
- data/README.rdoc +3 -3
- data/examples/performance.rb +20 -1
- data/lib/active_record/aggregations.rb +1 -1
- data/lib/active_record/associations/alias_tracker.rb +3 -6
- data/lib/active_record/associations/association.rb +3 -42
- data/lib/active_record/associations/association_scope.rb +3 -15
- data/lib/active_record/associations/builder/association.rb +6 -4
- data/lib/active_record/associations/builder/belongs_to.rb +3 -3
- data/lib/active_record/associations/builder/collection_association.rb +2 -2
- data/lib/active_record/associations/builder/has_many.rb +4 -4
- data/lib/active_record/associations/builder/has_one.rb +5 -6
- data/lib/active_record/associations/builder/singular_association.rb +3 -16
- data/lib/active_record/associations/collection_association.rb +64 -31
- data/lib/active_record/associations/collection_proxy.rb +2 -37
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +1 -0
- data/lib/active_record/associations/has_many_association.rb +5 -1
- data/lib/active_record/associations/has_many_through_association.rb +28 -9
- data/lib/active_record/associations/has_one_association.rb +15 -13
- data/lib/active_record/associations/join_dependency.rb +2 -2
- data/lib/active_record/associations/preloader.rb +14 -10
- data/lib/active_record/associations/through_association.rb +7 -3
- data/lib/active_record/associations.rb +92 -76
- data/lib/active_record/attribute_assignment.rb +221 -0
- data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +32 -0
- data/lib/active_record/attribute_methods/dirty.rb +21 -11
- data/lib/active_record/attribute_methods/primary_key.rb +62 -25
- data/lib/active_record/attribute_methods/read.rb +73 -83
- data/lib/active_record/attribute_methods/serialization.rb +102 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +23 -17
- data/lib/active_record/attribute_methods/write.rb +31 -6
- data/lib/active_record/attribute_methods.rb +231 -30
- data/lib/active_record/autosave_association.rb +43 -22
- data/lib/active_record/base.rb +227 -1708
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +150 -148
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +85 -29
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +6 -33
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +10 -2
- data/lib/active_record/connection_adapters/abstract/quoting.rb +15 -6
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +37 -26
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +48 -19
- data/lib/active_record/connection_adapters/abstract_adapter.rb +77 -42
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +674 -0
- data/lib/active_record/connection_adapters/column.rb +37 -11
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +129 -581
- data/lib/active_record/connection_adapters/mysql_adapter.rb +137 -696
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +184 -86
- data/lib/active_record/connection_adapters/schema_cache.rb +50 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +2 -6
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +55 -32
- data/lib/active_record/counter_cache.rb +9 -4
- data/lib/active_record/dynamic_finder_match.rb +12 -0
- data/lib/active_record/dynamic_matchers.rb +84 -0
- data/lib/active_record/errors.rb +11 -1
- data/lib/active_record/explain.rb +85 -0
- data/lib/active_record/explain_subscriber.rb +25 -0
- data/lib/active_record/fixtures/file.rb +65 -0
- data/lib/active_record/fixtures.rb +56 -85
- data/lib/active_record/identity_map.rb +3 -4
- data/lib/active_record/inheritance.rb +174 -0
- data/lib/active_record/integration.rb +49 -0
- data/lib/active_record/locking/optimistic.rb +30 -25
- data/lib/active_record/locking/pessimistic.rb +23 -1
- data/lib/active_record/log_subscriber.rb +3 -3
- data/lib/active_record/migration/command_recorder.rb +8 -8
- data/lib/active_record/migration.rb +68 -35
- data/lib/active_record/model_schema.rb +366 -0
- data/lib/active_record/nested_attributes.rb +3 -2
- data/lib/active_record/persistence.rb +57 -11
- data/lib/active_record/querying.rb +58 -0
- data/lib/active_record/railtie.rb +31 -29
- data/lib/active_record/railties/controller_runtime.rb +3 -1
- data/lib/active_record/railties/databases.rake +191 -110
- data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
- data/lib/active_record/readonly_attributes.rb +26 -0
- data/lib/active_record/reflection.rb +7 -15
- data/lib/active_record/relation/batches.rb +5 -2
- data/lib/active_record/relation/calculations.rb +47 -15
- data/lib/active_record/relation/delegation.rb +49 -0
- data/lib/active_record/relation/finder_methods.rb +9 -7
- data/lib/active_record/relation/predicate_builder.rb +18 -7
- data/lib/active_record/relation/query_methods.rb +75 -9
- data/lib/active_record/relation/spawn_methods.rb +11 -2
- data/lib/active_record/relation.rb +78 -32
- data/lib/active_record/result.rb +1 -1
- data/lib/active_record/sanitization.rb +194 -0
- data/lib/active_record/schema_dumper.rb +12 -5
- data/lib/active_record/scoping/default.rb +142 -0
- data/lib/active_record/scoping/named.rb +202 -0
- data/lib/active_record/scoping.rb +152 -0
- data/lib/active_record/serialization.rb +1 -43
- data/lib/active_record/serializers/xml_serializer.rb +4 -45
- data/lib/active_record/session_store.rb +17 -15
- data/lib/active_record/store.rb +52 -0
- data/lib/active_record/test_case.rb +11 -7
- data/lib/active_record/timestamp.rb +17 -3
- data/lib/active_record/transactions.rb +27 -6
- data/lib/active_record/translation.rb +22 -0
- data/lib/active_record/validations/associated.rb +5 -4
- data/lib/active_record/validations/uniqueness.rb +7 -7
- data/lib/active_record/validations.rb +1 -1
- data/lib/active_record/version.rb +2 -2
- data/lib/active_record.rb +38 -3
- data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -1
- data/lib/rails/generators/active_record/migration/templates/migration.rb +12 -3
- data/lib/rails/generators/active_record/model/model_generator.rb +9 -1
- data/lib/rails/generators/active_record/model/templates/migration.rb +3 -5
- data/lib/rails/generators/active_record/model/templates/model.rb +5 -0
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +1 -5
- metadata +30 -10
- data/lib/active_record/named_scope.rb +0 -200
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
require 'active_record/connection_adapters/
|
|
2
|
-
require 'active_support/core_ext/kernel/requires'
|
|
3
|
-
require 'active_support/core_ext/object/blank'
|
|
4
|
-
require 'set'
|
|
1
|
+
require 'active_record/connection_adapters/abstract_mysql_adapter'
|
|
5
2
|
require 'active_record/connection_adapters/statement_pool'
|
|
6
|
-
require '
|
|
3
|
+
require 'active_support/core_ext/hash/keys'
|
|
7
4
|
|
|
8
|
-
gem 'mysql', '~> 2.8'
|
|
5
|
+
gem 'mysql', '~> 2.8.1'
|
|
9
6
|
require 'mysql'
|
|
10
7
|
|
|
11
8
|
class Mysql
|
|
@@ -43,9 +40,29 @@ module ActiveRecord
|
|
|
43
40
|
end
|
|
44
41
|
|
|
45
42
|
module ConnectionAdapters
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
43
|
+
# The MySQL adapter will work with both Ruby/MySQL, which is a Ruby-based MySQL adapter that comes bundled with Active Record, and with
|
|
44
|
+
# the faster C-based MySQL/Ruby adapter (available both as a gem and from http://www.tmtm.org/en/mysql/ruby/).
|
|
45
|
+
#
|
|
46
|
+
# Options:
|
|
47
|
+
#
|
|
48
|
+
# * <tt>:host</tt> - Defaults to "localhost".
|
|
49
|
+
# * <tt>:port</tt> - Defaults to 3306.
|
|
50
|
+
# * <tt>:socket</tt> - Defaults to "/tmp/mysql.sock".
|
|
51
|
+
# * <tt>:username</tt> - Defaults to "root"
|
|
52
|
+
# * <tt>:password</tt> - Defaults to nothing.
|
|
53
|
+
# * <tt>:database</tt> - The name of the database. No default, must be provided.
|
|
54
|
+
# * <tt>:encoding</tt> - (Optional) Sets the client encoding by executing "SET NAMES <encoding>" after connection.
|
|
55
|
+
# * <tt>:reconnect</tt> - Defaults to false (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/auto-reconnect.html).
|
|
56
|
+
# * <tt>:sslca</tt> - Necessary to use MySQL with an SSL connection.
|
|
57
|
+
# * <tt>:sslkey</tt> - Necessary to use MySQL with an SSL connection.
|
|
58
|
+
# * <tt>:sslcert</tt> - Necessary to use MySQL with an SSL connection.
|
|
59
|
+
# * <tt>:sslcapath</tt> - Necessary to use MySQL with an SSL connection.
|
|
60
|
+
# * <tt>:sslcipher</tt> - Necessary to use MySQL with an SSL connection.
|
|
61
|
+
#
|
|
62
|
+
class MysqlAdapter < AbstractMysqlAdapter
|
|
63
|
+
|
|
64
|
+
class Column < AbstractMysqlAdapter::Column #:nodoc:
|
|
65
|
+
def self.string_to_time(value)
|
|
49
66
|
return super unless Mysql::Time === value
|
|
50
67
|
new_time(
|
|
51
68
|
value.year,
|
|
@@ -57,135 +74,23 @@ module ActiveRecord
|
|
|
57
74
|
value.second_part)
|
|
58
75
|
end
|
|
59
76
|
|
|
60
|
-
def string_to_dummy_time(v)
|
|
77
|
+
def self.string_to_dummy_time(v)
|
|
61
78
|
return super unless Mysql::Time === v
|
|
62
79
|
new_time(2000, 01, 01, v.hour, v.minute, v.second, v.second_part)
|
|
63
80
|
end
|
|
64
81
|
|
|
65
|
-
def string_to_date(v)
|
|
82
|
+
def self.string_to_date(v)
|
|
66
83
|
return super unless Mysql::Time === v
|
|
67
84
|
new_date(v.year, v.month, v.day)
|
|
68
85
|
end
|
|
69
|
-
end
|
|
70
86
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if default.blank?
|
|
74
|
-
return null ? nil : ''
|
|
75
|
-
else
|
|
76
|
-
raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
|
|
77
|
-
end
|
|
78
|
-
elsif missing_default_forged_as_empty_string?(default)
|
|
79
|
-
nil
|
|
80
|
-
else
|
|
81
|
-
super
|
|
87
|
+
def adapter
|
|
88
|
+
MysqlAdapter
|
|
82
89
|
end
|
|
83
90
|
end
|
|
84
91
|
|
|
85
|
-
def has_default?
|
|
86
|
-
return false if sql_type =~ /blob/i || type == :text #mysql forbids defaults on blob and text columns
|
|
87
|
-
super
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
private
|
|
91
|
-
def simplified_type(field_type)
|
|
92
|
-
return :boolean if MysqlAdapter.emulate_booleans && field_type.downcase.index("tinyint(1)")
|
|
93
|
-
return :string if field_type =~ /enum/i
|
|
94
|
-
super
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
def extract_limit(sql_type)
|
|
98
|
-
case sql_type
|
|
99
|
-
when /blob|text/i
|
|
100
|
-
case sql_type
|
|
101
|
-
when /tiny/i
|
|
102
|
-
255
|
|
103
|
-
when /medium/i
|
|
104
|
-
16777215
|
|
105
|
-
when /long/i
|
|
106
|
-
2147483647 # mysql only allows 2^31-1, not 2^32-1, somewhat inconsistently with the tiny/medium/normal cases
|
|
107
|
-
else
|
|
108
|
-
super # we could return 65535 here, but we leave it undecorated by default
|
|
109
|
-
end
|
|
110
|
-
when /^bigint/i; 8
|
|
111
|
-
when /^int/i; 4
|
|
112
|
-
when /^mediumint/i; 3
|
|
113
|
-
when /^smallint/i; 2
|
|
114
|
-
when /^tinyint/i; 1
|
|
115
|
-
else
|
|
116
|
-
super
|
|
117
|
-
end
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
# MySQL misreports NOT NULL column default when none is given.
|
|
121
|
-
# We can't detect this for columns which may have a legitimate ''
|
|
122
|
-
# default (string) but we can for others (integer, datetime, boolean,
|
|
123
|
-
# and the rest).
|
|
124
|
-
#
|
|
125
|
-
# Test whether the column has default '', is not null, and is not
|
|
126
|
-
# a type allowing default ''.
|
|
127
|
-
def missing_default_forged_as_empty_string?(default)
|
|
128
|
-
type != :string && !null && default == ''
|
|
129
|
-
end
|
|
130
|
-
end
|
|
131
|
-
|
|
132
|
-
# The MySQL adapter will work with both Ruby/MySQL, which is a Ruby-based MySQL adapter that comes bundled with Active Record, and with
|
|
133
|
-
# the faster C-based MySQL/Ruby adapter (available both as a gem and from http://www.tmtm.org/en/mysql/ruby/).
|
|
134
|
-
#
|
|
135
|
-
# Options:
|
|
136
|
-
#
|
|
137
|
-
# * <tt>:host</tt> - Defaults to "localhost".
|
|
138
|
-
# * <tt>:port</tt> - Defaults to 3306.
|
|
139
|
-
# * <tt>:socket</tt> - Defaults to "/tmp/mysql.sock".
|
|
140
|
-
# * <tt>:username</tt> - Defaults to "root"
|
|
141
|
-
# * <tt>:password</tt> - Defaults to nothing.
|
|
142
|
-
# * <tt>:database</tt> - The name of the database. No default, must be provided.
|
|
143
|
-
# * <tt>:encoding</tt> - (Optional) Sets the client encoding by executing "SET NAMES <encoding>" after connection.
|
|
144
|
-
# * <tt>:reconnect</tt> - Defaults to false (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/auto-reconnect.html).
|
|
145
|
-
# * <tt>:sslca</tt> - Necessary to use MySQL with an SSL connection.
|
|
146
|
-
# * <tt>:sslkey</tt> - Necessary to use MySQL with an SSL connection.
|
|
147
|
-
# * <tt>:sslcert</tt> - Necessary to use MySQL with an SSL connection.
|
|
148
|
-
# * <tt>:sslcapath</tt> - Necessary to use MySQL with an SSL connection.
|
|
149
|
-
# * <tt>:sslcipher</tt> - Necessary to use MySQL with an SSL connection.
|
|
150
|
-
#
|
|
151
|
-
class MysqlAdapter < AbstractAdapter
|
|
152
|
-
|
|
153
|
-
##
|
|
154
|
-
# :singleton-method:
|
|
155
|
-
# By default, the MysqlAdapter will consider all columns of type <tt>tinyint(1)</tt>
|
|
156
|
-
# as boolean. If you wish to disable this emulation (which was the default
|
|
157
|
-
# behavior in versions 0.13.1 and earlier) you can add the following line
|
|
158
|
-
# to your application.rb file:
|
|
159
|
-
#
|
|
160
|
-
# ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans = false
|
|
161
|
-
cattr_accessor :emulate_booleans
|
|
162
|
-
self.emulate_booleans = true
|
|
163
|
-
|
|
164
92
|
ADAPTER_NAME = 'MySQL'
|
|
165
93
|
|
|
166
|
-
LOST_CONNECTION_ERROR_MESSAGES = [
|
|
167
|
-
"Server shutdown in progress",
|
|
168
|
-
"Broken pipe",
|
|
169
|
-
"Lost connection to MySQL server during query",
|
|
170
|
-
"MySQL server has gone away" ]
|
|
171
|
-
|
|
172
|
-
QUOTED_TRUE, QUOTED_FALSE = '1', '0'
|
|
173
|
-
|
|
174
|
-
NATIVE_DATABASE_TYPES = {
|
|
175
|
-
:primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY",
|
|
176
|
-
:string => { :name => "varchar", :limit => 255 },
|
|
177
|
-
:text => { :name => "text" },
|
|
178
|
-
:integer => { :name => "int", :limit => 4 },
|
|
179
|
-
:float => { :name => "float" },
|
|
180
|
-
:decimal => { :name => "decimal" },
|
|
181
|
-
:datetime => { :name => "datetime" },
|
|
182
|
-
:timestamp => { :name => "datetime" },
|
|
183
|
-
:time => { :name => "time" },
|
|
184
|
-
:date => { :name => "date" },
|
|
185
|
-
:binary => { :name => "blob" },
|
|
186
|
-
:boolean => { :name => "tinyint", :limit => 1 }
|
|
187
|
-
}
|
|
188
|
-
|
|
189
94
|
class StatementPool < ConnectionAdapters::StatementPool
|
|
190
95
|
def initialize(connection, max = 1000)
|
|
191
96
|
super
|
|
@@ -219,116 +124,52 @@ module ActiveRecord
|
|
|
219
124
|
end
|
|
220
125
|
|
|
221
126
|
def initialize(connection, logger, connection_options, config)
|
|
222
|
-
super
|
|
223
|
-
@connection_options, @config = connection_options, config
|
|
224
|
-
@quoted_column_names, @quoted_table_names = {}, {}
|
|
225
|
-
@statements = {}
|
|
127
|
+
super
|
|
226
128
|
@statements = StatementPool.new(@connection,
|
|
227
129
|
config.fetch(:statement_limit) { 1000 })
|
|
228
130
|
@client_encoding = nil
|
|
229
131
|
connect
|
|
230
132
|
end
|
|
231
133
|
|
|
232
|
-
class BindSubstitution < Arel::Visitors::MySQL # :nodoc:
|
|
233
|
-
include Arel::Visitors::BindVisitor
|
|
234
|
-
end
|
|
235
|
-
|
|
236
|
-
def self.visitor_for(pool) # :nodoc:
|
|
237
|
-
config = pool.spec.config
|
|
238
|
-
|
|
239
|
-
if config.fetch(:prepared_statements) { true }
|
|
240
|
-
Arel::Visitors::MySQL.new pool
|
|
241
|
-
else
|
|
242
|
-
BindSubstitution.new pool
|
|
243
|
-
end
|
|
244
|
-
end
|
|
245
|
-
|
|
246
|
-
def adapter_name #:nodoc:
|
|
247
|
-
ADAPTER_NAME
|
|
248
|
-
end
|
|
249
|
-
|
|
250
|
-
def supports_bulk_alter? #:nodoc:
|
|
251
|
-
true
|
|
252
|
-
end
|
|
253
|
-
|
|
254
134
|
# Returns true, since this connection adapter supports prepared statement
|
|
255
135
|
# caching.
|
|
256
136
|
def supports_statement_cache?
|
|
257
137
|
true
|
|
258
138
|
end
|
|
259
139
|
|
|
260
|
-
#
|
|
261
|
-
def supports_migrations? #:nodoc:
|
|
262
|
-
true
|
|
263
|
-
end
|
|
140
|
+
# HELPER METHODS ===========================================
|
|
264
141
|
|
|
265
|
-
#
|
|
266
|
-
|
|
267
|
-
|
|
142
|
+
def each_hash(result) # :nodoc:
|
|
143
|
+
if block_given?
|
|
144
|
+
result.each_hash do |row|
|
|
145
|
+
row.symbolize_keys!
|
|
146
|
+
yield row
|
|
147
|
+
end
|
|
148
|
+
else
|
|
149
|
+
to_enum(:each_hash, result)
|
|
150
|
+
end
|
|
268
151
|
end
|
|
269
152
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
true
|
|
153
|
+
def new_column(field, default, type, null, collation) # :nodoc:
|
|
154
|
+
Column.new(field, default, type, null, collation)
|
|
273
155
|
end
|
|
274
156
|
|
|
275
|
-
def
|
|
276
|
-
|
|
157
|
+
def error_number(exception) # :nodoc:
|
|
158
|
+
exception.errno if exception.respond_to?(:errno)
|
|
277
159
|
end
|
|
278
160
|
|
|
279
|
-
|
|
280
161
|
# QUOTING ==================================================
|
|
281
162
|
|
|
282
|
-
def quote(value, column = nil)
|
|
283
|
-
if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary)
|
|
284
|
-
s = column.class.string_to_binary(value).unpack("H*")[0]
|
|
285
|
-
"x'#{s}'"
|
|
286
|
-
elsif value.kind_of?(BigDecimal)
|
|
287
|
-
value.to_s("F")
|
|
288
|
-
else
|
|
289
|
-
super
|
|
290
|
-
end
|
|
291
|
-
end
|
|
292
|
-
|
|
293
163
|
def type_cast(value, column)
|
|
294
164
|
return super unless value == true || value == false
|
|
295
165
|
|
|
296
166
|
value ? 1 : 0
|
|
297
167
|
end
|
|
298
168
|
|
|
299
|
-
def quote_column_name(name) #:nodoc:
|
|
300
|
-
@quoted_column_names[name] ||= "`#{name.to_s.gsub('`', '``')}`"
|
|
301
|
-
end
|
|
302
|
-
|
|
303
|
-
def quote_table_name(name) #:nodoc:
|
|
304
|
-
@quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`')
|
|
305
|
-
end
|
|
306
|
-
|
|
307
169
|
def quote_string(string) #:nodoc:
|
|
308
170
|
@connection.quote(string)
|
|
309
171
|
end
|
|
310
172
|
|
|
311
|
-
def quoted_true
|
|
312
|
-
QUOTED_TRUE
|
|
313
|
-
end
|
|
314
|
-
|
|
315
|
-
def quoted_false
|
|
316
|
-
QUOTED_FALSE
|
|
317
|
-
end
|
|
318
|
-
|
|
319
|
-
# REFERENTIAL INTEGRITY ====================================
|
|
320
|
-
|
|
321
|
-
def disable_referential_integrity #:nodoc:
|
|
322
|
-
old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
|
|
323
|
-
|
|
324
|
-
begin
|
|
325
|
-
update("SET FOREIGN_KEY_CHECKS = 0")
|
|
326
|
-
yield
|
|
327
|
-
ensure
|
|
328
|
-
update("SET FOREIGN_KEY_CHECKS = #{old}")
|
|
329
|
-
end
|
|
330
|
-
end
|
|
331
|
-
|
|
332
173
|
# CONNECTION MANAGEMENT ====================================
|
|
333
174
|
|
|
334
175
|
def active?
|
|
@@ -373,7 +214,7 @@ module ActiveRecord
|
|
|
373
214
|
|
|
374
215
|
def select_rows(sql, name = nil)
|
|
375
216
|
@connection.query_with_result = true
|
|
376
|
-
rows =
|
|
217
|
+
rows = exec_query(sql, name).rows
|
|
377
218
|
@connection.more_results && @connection.next_result # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped
|
|
378
219
|
rows
|
|
379
220
|
end
|
|
@@ -441,11 +282,19 @@ module ActiveRecord
|
|
|
441
282
|
end
|
|
442
283
|
|
|
443
284
|
def exec_query(sql, name = 'SQL', binds = [])
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
285
|
+
# If the configuration sets prepared_statements:false, binds will
|
|
286
|
+
# always be empty, since the bind variables will have been already
|
|
287
|
+
# substituted and removed from binds by BindVisitor, so this will
|
|
288
|
+
# effectively disable prepared statement usage completely.
|
|
289
|
+
if binds.empty?
|
|
290
|
+
result_set, affected_rows = exec_without_stmt(sql, name)
|
|
291
|
+
else
|
|
292
|
+
result_set, affected_rows = exec_stmt(sql, name, binds)
|
|
448
293
|
end
|
|
294
|
+
|
|
295
|
+
yield affected_rows if block_given?
|
|
296
|
+
|
|
297
|
+
result_set
|
|
449
298
|
end
|
|
450
299
|
|
|
451
300
|
def last_inserted_id(result)
|
|
@@ -454,35 +303,28 @@ module ActiveRecord
|
|
|
454
303
|
|
|
455
304
|
def exec_without_stmt(sql, name = 'SQL') # :nodoc:
|
|
456
305
|
# Some queries, like SHOW CREATE TABLE don't work through the prepared
|
|
457
|
-
# statement API.
|
|
306
|
+
# statement API. For those queries, we need to use this method. :'(
|
|
458
307
|
log(sql, name) do
|
|
459
308
|
result = @connection.query(sql)
|
|
460
|
-
|
|
461
|
-
rows = []
|
|
309
|
+
affected_rows = @connection.affected_rows
|
|
462
310
|
|
|
463
311
|
if result
|
|
464
312
|
cols = result.fetch_fields.map { |field| field.name }
|
|
465
|
-
|
|
313
|
+
result_set = ActiveRecord::Result.new(cols, result.to_a)
|
|
466
314
|
result.free
|
|
315
|
+
else
|
|
316
|
+
result_set = ActiveRecord::Result.new([], [])
|
|
467
317
|
end
|
|
468
|
-
|
|
318
|
+
|
|
319
|
+
[result_set, affected_rows]
|
|
469
320
|
end
|
|
470
321
|
end
|
|
471
322
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
else
|
|
478
|
-
log(sql, name) { @connection.query(sql) }
|
|
479
|
-
end
|
|
480
|
-
rescue ActiveRecord::StatementInvalid => exception
|
|
481
|
-
if exception.message.split(":").first =~ /Packets out of order/
|
|
482
|
-
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."
|
|
483
|
-
else
|
|
484
|
-
raise
|
|
485
|
-
end
|
|
323
|
+
def execute_and_free(sql, name = nil)
|
|
324
|
+
result = execute(sql, name)
|
|
325
|
+
ret = yield result
|
|
326
|
+
result.free
|
|
327
|
+
ret
|
|
486
328
|
end
|
|
487
329
|
|
|
488
330
|
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
|
|
@@ -491,510 +333,109 @@ module ActiveRecord
|
|
|
491
333
|
end
|
|
492
334
|
alias :create :insert_sql
|
|
493
335
|
|
|
494
|
-
def update_sql(sql, name = nil) #:nodoc:
|
|
495
|
-
super
|
|
496
|
-
@connection.affected_rows
|
|
497
|
-
end
|
|
498
|
-
|
|
499
336
|
def exec_delete(sql, name, binds)
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
337
|
+
affected_rows = 0
|
|
338
|
+
|
|
339
|
+
exec_query(sql, name, binds) do |n|
|
|
340
|
+
affected_rows = n
|
|
504
341
|
end
|
|
342
|
+
|
|
343
|
+
affected_rows
|
|
505
344
|
end
|
|
506
345
|
alias :exec_update :exec_delete
|
|
507
346
|
|
|
508
347
|
def begin_db_transaction #:nodoc:
|
|
509
|
-
|
|
348
|
+
exec_query "BEGIN"
|
|
510
349
|
rescue Mysql::Error
|
|
511
350
|
# Transactions aren't supported
|
|
512
351
|
end
|
|
513
352
|
|
|
514
|
-
|
|
515
|
-
execute "COMMIT"
|
|
516
|
-
rescue Exception
|
|
517
|
-
# Transactions aren't supported
|
|
518
|
-
end
|
|
519
|
-
|
|
520
|
-
def rollback_db_transaction #:nodoc:
|
|
521
|
-
execute "ROLLBACK"
|
|
522
|
-
rescue Exception
|
|
523
|
-
# Transactions aren't supported
|
|
524
|
-
end
|
|
525
|
-
|
|
526
|
-
def create_savepoint
|
|
527
|
-
execute("SAVEPOINT #{current_savepoint_name}")
|
|
528
|
-
end
|
|
529
|
-
|
|
530
|
-
def rollback_to_savepoint
|
|
531
|
-
execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
|
|
532
|
-
end
|
|
533
|
-
|
|
534
|
-
def release_savepoint
|
|
535
|
-
execute("RELEASE SAVEPOINT #{current_savepoint_name}")
|
|
536
|
-
end
|
|
537
|
-
|
|
538
|
-
def add_limit_offset!(sql, options) #:nodoc:
|
|
539
|
-
limit, offset = options[:limit], options[:offset]
|
|
540
|
-
if limit && offset
|
|
541
|
-
sql << " LIMIT #{offset.to_i}, #{sanitize_limit(limit)}"
|
|
542
|
-
elsif limit
|
|
543
|
-
sql << " LIMIT #{sanitize_limit(limit)}"
|
|
544
|
-
elsif offset
|
|
545
|
-
sql << " OFFSET #{offset.to_i}"
|
|
546
|
-
end
|
|
547
|
-
sql
|
|
548
|
-
end
|
|
549
|
-
deprecate :add_limit_offset!
|
|
550
|
-
|
|
551
|
-
# In the simple case, MySQL allows us to place JOINs directly into the UPDATE
|
|
552
|
-
# query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
|
|
553
|
-
# these, we must use a subquery. However, MySQL is too stupid to create a
|
|
554
|
-
# temporary table for this automatically, so we have to give it some prompting
|
|
555
|
-
# in the form of a subsubquery. Ugh!
|
|
556
|
-
def join_to_update(update, select) #:nodoc:
|
|
557
|
-
if select.limit || select.offset || select.orders.any?
|
|
558
|
-
subsubselect = select.clone
|
|
559
|
-
subsubselect.projections = [update.key]
|
|
560
|
-
|
|
561
|
-
subselect = Arel::SelectManager.new(select.engine)
|
|
562
|
-
subselect.project Arel.sql(update.key.name)
|
|
563
|
-
subselect.from subsubselect.as('__active_record_temp')
|
|
564
|
-
|
|
565
|
-
update.where update.key.in(subselect)
|
|
566
|
-
else
|
|
567
|
-
update.table select.source
|
|
568
|
-
update.wheres = select.constraints
|
|
569
|
-
end
|
|
570
|
-
end
|
|
571
|
-
|
|
572
|
-
# SCHEMA STATEMENTS ========================================
|
|
573
|
-
|
|
574
|
-
def structure_dump #:nodoc:
|
|
575
|
-
if supports_views?
|
|
576
|
-
sql = "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'"
|
|
577
|
-
else
|
|
578
|
-
sql = "SHOW TABLES"
|
|
579
|
-
end
|
|
580
|
-
|
|
581
|
-
select_all(sql).map do |table|
|
|
582
|
-
table.delete('Table_type')
|
|
583
|
-
sql = "SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}"
|
|
584
|
-
exec_without_stmt(sql).first['Create Table'] + ";\n\n"
|
|
585
|
-
end.join("")
|
|
586
|
-
end
|
|
587
|
-
|
|
588
|
-
# Drops the database specified on the +name+ attribute
|
|
589
|
-
# and creates it again using the provided +options+.
|
|
590
|
-
def recreate_database(name, options = {}) #:nodoc:
|
|
591
|
-
drop_database(name)
|
|
592
|
-
create_database(name, options)
|
|
593
|
-
end
|
|
594
|
-
|
|
595
|
-
# Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
|
|
596
|
-
# Charset defaults to utf8.
|
|
597
|
-
#
|
|
598
|
-
# Example:
|
|
599
|
-
# create_database 'charset_test', :charset => 'latin1', :collation => 'latin1_bin'
|
|
600
|
-
# create_database 'matt_development'
|
|
601
|
-
# create_database 'matt_development', :charset => :big5
|
|
602
|
-
def create_database(name, options = {})
|
|
603
|
-
if options[:collation]
|
|
604
|
-
execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{options[:collation]}`"
|
|
605
|
-
else
|
|
606
|
-
execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}`"
|
|
607
|
-
end
|
|
608
|
-
end
|
|
609
|
-
|
|
610
|
-
# Drops a MySQL database.
|
|
611
|
-
#
|
|
612
|
-
# Example:
|
|
613
|
-
# drop_database 'sebastian_development'
|
|
614
|
-
def drop_database(name) #:nodoc:
|
|
615
|
-
execute "DROP DATABASE IF EXISTS `#{name}`"
|
|
616
|
-
end
|
|
617
|
-
|
|
618
|
-
def current_database
|
|
619
|
-
select_value 'SELECT DATABASE() as db'
|
|
620
|
-
end
|
|
621
|
-
|
|
622
|
-
# Returns the database character set.
|
|
623
|
-
def charset
|
|
624
|
-
show_variable 'character_set_database'
|
|
625
|
-
end
|
|
626
|
-
|
|
627
|
-
# Returns the database collation strategy.
|
|
628
|
-
def collation
|
|
629
|
-
show_variable 'collation_database'
|
|
630
|
-
end
|
|
631
|
-
|
|
632
|
-
def tables(name = nil, database = nil) #:nodoc:
|
|
633
|
-
sql = "SHOW TABLES "
|
|
634
|
-
sql << "IN #{quote_table_name(database)} " if database
|
|
635
|
-
|
|
636
|
-
result = execute(sql, 'SCHEMA')
|
|
637
|
-
tables = result.collect { |field| field[0] }
|
|
638
|
-
result.free
|
|
639
|
-
tables
|
|
640
|
-
end
|
|
641
|
-
|
|
642
|
-
def table_exists?(name)
|
|
643
|
-
return true if super
|
|
644
|
-
|
|
645
|
-
name = name.to_s
|
|
646
|
-
schema, table = name.split('.', 2)
|
|
647
|
-
|
|
648
|
-
unless table # A table was provided without a schema
|
|
649
|
-
table = schema
|
|
650
|
-
schema = nil
|
|
651
|
-
end
|
|
652
|
-
|
|
653
|
-
tables(nil, schema).include? table
|
|
654
|
-
end
|
|
655
|
-
|
|
656
|
-
def drop_table(table_name, options = {})
|
|
657
|
-
super(table_name, options)
|
|
658
|
-
end
|
|
659
|
-
|
|
660
|
-
# Returns an array of indexes for the given table.
|
|
661
|
-
def indexes(table_name, name = nil)#:nodoc:
|
|
662
|
-
indexes = []
|
|
663
|
-
current_index = nil
|
|
664
|
-
result = execute("SHOW KEYS FROM #{quote_table_name(table_name)}", name)
|
|
665
|
-
result.each do |row|
|
|
666
|
-
if current_index != row[2]
|
|
667
|
-
next if row[2] == "PRIMARY" # skip the primary key
|
|
668
|
-
current_index = row[2]
|
|
669
|
-
indexes << IndexDefinition.new(row[0], row[2], row[1] == "0", [], [])
|
|
670
|
-
end
|
|
671
|
-
|
|
672
|
-
indexes.last.columns << row[4]
|
|
673
|
-
indexes.last.lengths << row[7]
|
|
674
|
-
end
|
|
675
|
-
result.free
|
|
676
|
-
indexes
|
|
677
|
-
end
|
|
678
|
-
|
|
679
|
-
# Returns an array of +MysqlColumn+ objects for the table specified by +table_name+.
|
|
680
|
-
def columns(table_name, name = nil)#:nodoc:
|
|
681
|
-
sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}"
|
|
682
|
-
result = execute(sql, 'SCHEMA')
|
|
683
|
-
columns = result.collect { |field| MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES") }
|
|
684
|
-
result.free
|
|
685
|
-
columns
|
|
686
|
-
end
|
|
687
|
-
|
|
688
|
-
def create_table(table_name, options = {}) #:nodoc:
|
|
689
|
-
super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
|
|
690
|
-
end
|
|
691
|
-
|
|
692
|
-
# Renames a table.
|
|
693
|
-
#
|
|
694
|
-
# Example:
|
|
695
|
-
# rename_table('octopuses', 'octopi')
|
|
696
|
-
def rename_table(table_name, new_name)
|
|
697
|
-
execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
|
|
698
|
-
end
|
|
699
|
-
|
|
700
|
-
def bulk_change_table(table_name, operations) #:nodoc:
|
|
701
|
-
sqls = operations.map do |command, args|
|
|
702
|
-
table, arguments = args.shift, args
|
|
703
|
-
method = :"#{command}_sql"
|
|
353
|
+
private
|
|
704
354
|
|
|
705
|
-
|
|
706
|
-
|
|
355
|
+
def exec_stmt(sql, name, binds)
|
|
356
|
+
cache = {}
|
|
357
|
+
log(sql, name, binds) do
|
|
358
|
+
if binds.empty?
|
|
359
|
+
stmt = @connection.prepare(sql)
|
|
707
360
|
else
|
|
708
|
-
|
|
361
|
+
cache = @statements[sql] ||= {
|
|
362
|
+
:stmt => @connection.prepare(sql)
|
|
363
|
+
}
|
|
364
|
+
stmt = cache[:stmt]
|
|
709
365
|
end
|
|
710
|
-
end.flatten.join(", ")
|
|
711
366
|
|
|
712
|
-
|
|
713
|
-
|
|
367
|
+
begin
|
|
368
|
+
stmt.execute(*binds.map { |col, val| type_cast(val, col) })
|
|
369
|
+
rescue Mysql::Error => e
|
|
370
|
+
# Older versions of MySQL leave the prepared statement in a bad
|
|
371
|
+
# place when an error occurs. To support older mysql versions, we
|
|
372
|
+
# need to close the statement and delete the statement from the
|
|
373
|
+
# cache.
|
|
374
|
+
stmt.close
|
|
375
|
+
@statements.delete sql
|
|
376
|
+
raise e
|
|
377
|
+
end
|
|
714
378
|
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
379
|
+
cols = nil
|
|
380
|
+
if metadata = stmt.result_metadata
|
|
381
|
+
cols = cache[:cols] ||= metadata.fetch_fields.map { |field|
|
|
382
|
+
field.name
|
|
383
|
+
}
|
|
384
|
+
end
|
|
718
385
|
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
change_column table_name, column_name, column.sql_type, :default => default
|
|
722
|
-
end
|
|
386
|
+
result_set = ActiveRecord::Result.new(cols, stmt.to_a) if cols
|
|
387
|
+
affected_rows = stmt.affected_rows
|
|
723
388
|
|
|
724
|
-
|
|
725
|
-
|
|
389
|
+
stmt.result_metadata.free if cols
|
|
390
|
+
stmt.free_result
|
|
391
|
+
stmt.close if binds.empty?
|
|
726
392
|
|
|
727
|
-
|
|
728
|
-
execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
|
|
393
|
+
[result_set, affected_rows]
|
|
729
394
|
end
|
|
730
|
-
|
|
731
|
-
change_column table_name, column_name, column.sql_type, :null => null
|
|
732
|
-
end
|
|
733
|
-
|
|
734
|
-
def change_column(table_name, column_name, type, options = {}) #:nodoc:
|
|
735
|
-
execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_sql(table_name, column_name, type, options)}")
|
|
736
395
|
end
|
|
737
396
|
|
|
738
|
-
def
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
# Maps logical Rails types to MySQL-specific data types.
|
|
743
|
-
def type_to_sql(type, limit = nil, precision = nil, scale = nil)
|
|
744
|
-
case type.to_s
|
|
745
|
-
when 'integer'
|
|
746
|
-
case limit
|
|
747
|
-
when 1; 'tinyint'
|
|
748
|
-
when 2; 'smallint'
|
|
749
|
-
when 3; 'mediumint'
|
|
750
|
-
when nil, 4, 11; 'int(11)' # compatibility with MySQL default
|
|
751
|
-
when 5..8; 'bigint'
|
|
752
|
-
else raise(ActiveRecordError, "No integer type has byte size #{limit}")
|
|
753
|
-
end
|
|
754
|
-
when 'text'
|
|
755
|
-
case limit
|
|
756
|
-
when 0..0xff; 'tinytext'
|
|
757
|
-
when nil, 0x100..0xffff; 'text'
|
|
758
|
-
when 0x10000..0xffffff; 'mediumtext'
|
|
759
|
-
when 0x1000000..0xffffffff; 'longtext'
|
|
760
|
-
else raise(ActiveRecordError, "No text type has character length #{limit}")
|
|
761
|
-
end
|
|
762
|
-
else
|
|
763
|
-
super
|
|
397
|
+
def connect
|
|
398
|
+
encoding = @config[:encoding]
|
|
399
|
+
if encoding
|
|
400
|
+
@connection.options(Mysql::SET_CHARSET_NAME, encoding) rescue nil
|
|
764
401
|
end
|
|
765
|
-
end
|
|
766
402
|
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
sql << " FIRST"
|
|
770
|
-
elsif options[:after]
|
|
771
|
-
sql << " AFTER #{quote_column_name(options[:after])}"
|
|
403
|
+
if @config[:sslca] || @config[:sslkey]
|
|
404
|
+
@connection.ssl_set(@config[:sslkey], @config[:sslcert], @config[:sslca], @config[:sslcapath], @config[:sslcipher])
|
|
772
405
|
end
|
|
773
|
-
end
|
|
774
406
|
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
variables.first['Value'] unless variables.empty?
|
|
779
|
-
end
|
|
407
|
+
@connection.options(Mysql::OPT_CONNECT_TIMEOUT, @config[:connect_timeout]) if @config[:connect_timeout]
|
|
408
|
+
@connection.options(Mysql::OPT_READ_TIMEOUT, @config[:read_timeout]) if @config[:read_timeout]
|
|
409
|
+
@connection.options(Mysql::OPT_WRITE_TIMEOUT, @config[:write_timeout]) if @config[:write_timeout]
|
|
780
410
|
|
|
781
|
-
|
|
782
|
-
def pk_and_sequence_for(table) #:nodoc:
|
|
783
|
-
result = execute("SHOW CREATE TABLE #{quote_table_name(table)}", 'SCHEMA')
|
|
784
|
-
create_table = result.fetch_hash["Create Table"]
|
|
785
|
-
result.free
|
|
411
|
+
@connection.real_connect(*@connection_options)
|
|
786
412
|
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
keys.length == 1 ? [keys.first, nil] : nil
|
|
790
|
-
else
|
|
791
|
-
nil
|
|
792
|
-
end
|
|
793
|
-
end
|
|
413
|
+
# reconnect must be set after real_connect is called, because real_connect sets it to false internally
|
|
414
|
+
@connection.reconnect = !!@config[:reconnect] if @connection.respond_to?(:reconnect=)
|
|
794
415
|
|
|
795
|
-
|
|
796
|
-
def primary_key(table)
|
|
797
|
-
pk_and_sequence = pk_and_sequence_for(table)
|
|
798
|
-
pk_and_sequence && pk_and_sequence.first
|
|
416
|
+
configure_connection
|
|
799
417
|
end
|
|
800
418
|
|
|
801
|
-
def
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
deprecate :case_sensitive_equality_operator
|
|
419
|
+
def configure_connection
|
|
420
|
+
encoding = @config[:encoding]
|
|
421
|
+
execute("SET NAMES '#{encoding}'", :skip_logging) if encoding
|
|
805
422
|
|
|
806
|
-
|
|
807
|
-
|
|
423
|
+
# By default, MySQL 'where id is null' selects the last inserted id.
|
|
424
|
+
# Turn this off. http://dev.rubyonrails.org/ticket/6778
|
|
425
|
+
execute("SET SQL_AUTO_IS_NULL=0", :skip_logging)
|
|
808
426
|
end
|
|
809
427
|
|
|
810
|
-
def
|
|
811
|
-
|
|
428
|
+
def select(sql, name = nil, binds = [])
|
|
429
|
+
@connection.query_with_result = true
|
|
430
|
+
rows = exec_query(sql, name, binds).to_a
|
|
431
|
+
@connection.more_results && @connection.next_result # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped
|
|
432
|
+
rows
|
|
812
433
|
end
|
|
813
434
|
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
case length
|
|
819
|
-
when Hash
|
|
820
|
-
column_names.map {|name| length[name] ? "#{quote_column_name(name)}(#{length[name]})" : quote_column_name(name) }
|
|
821
|
-
when Fixnum
|
|
822
|
-
column_names.map {|name| "#{quote_column_name(name)}(#{length})"}
|
|
823
|
-
else
|
|
824
|
-
column_names.map {|name| quote_column_name(name) }
|
|
825
|
-
end
|
|
826
|
-
end
|
|
827
|
-
|
|
828
|
-
def translate_exception(exception, message)
|
|
829
|
-
return super unless exception.respond_to?(:errno)
|
|
830
|
-
|
|
831
|
-
case exception.errno
|
|
832
|
-
when 1062
|
|
833
|
-
RecordNotUnique.new(message, exception)
|
|
834
|
-
when 1452
|
|
835
|
-
InvalidForeignKey.new(message, exception)
|
|
836
|
-
else
|
|
837
|
-
super
|
|
838
|
-
end
|
|
839
|
-
end
|
|
840
|
-
|
|
841
|
-
def add_column_sql(table_name, column_name, type, options = {})
|
|
842
|
-
add_column_sql = "ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
|
|
843
|
-
add_column_options!(add_column_sql, options)
|
|
844
|
-
add_column_position!(add_column_sql, options)
|
|
845
|
-
add_column_sql
|
|
846
|
-
end
|
|
847
|
-
|
|
848
|
-
def remove_column_sql(table_name, *column_names)
|
|
849
|
-
columns_for_remove(table_name, *column_names).map {|column_name| "DROP #{column_name}" }
|
|
850
|
-
end
|
|
851
|
-
alias :remove_columns_sql :remove_column
|
|
852
|
-
|
|
853
|
-
def change_column_sql(table_name, column_name, type, options = {})
|
|
854
|
-
column = column_for(table_name, column_name)
|
|
855
|
-
|
|
856
|
-
unless options_include_default?(options)
|
|
857
|
-
options[:default] = column.default
|
|
858
|
-
end
|
|
859
|
-
|
|
860
|
-
unless options.has_key?(:null)
|
|
861
|
-
options[:null] = column.null
|
|
862
|
-
end
|
|
863
|
-
|
|
864
|
-
change_column_sql = "CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
|
|
865
|
-
add_column_options!(change_column_sql, options)
|
|
866
|
-
add_column_position!(change_column_sql, options)
|
|
867
|
-
change_column_sql
|
|
868
|
-
end
|
|
869
|
-
|
|
870
|
-
def rename_column_sql(table_name, column_name, new_column_name)
|
|
871
|
-
options = {}
|
|
872
|
-
|
|
873
|
-
if column = columns(table_name).find { |c| c.name == column_name.to_s }
|
|
874
|
-
options[:default] = column.default
|
|
875
|
-
options[:null] = column.null
|
|
876
|
-
else
|
|
877
|
-
raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
|
|
878
|
-
end
|
|
879
|
-
|
|
880
|
-
current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"]
|
|
881
|
-
rename_column_sql = "CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}"
|
|
882
|
-
add_column_options!(rename_column_sql, options)
|
|
883
|
-
rename_column_sql
|
|
884
|
-
end
|
|
885
|
-
|
|
886
|
-
def add_index_sql(table_name, column_name, options = {})
|
|
887
|
-
index_name, index_type, index_columns = add_index_options(table_name, column_name, options)
|
|
888
|
-
"ADD #{index_type} INDEX #{index_name} (#{index_columns})"
|
|
889
|
-
end
|
|
890
|
-
|
|
891
|
-
def remove_index_sql(table_name, options = {})
|
|
892
|
-
index_name = index_name_for_remove(table_name, options)
|
|
893
|
-
"DROP INDEX #{index_name}"
|
|
894
|
-
end
|
|
895
|
-
|
|
896
|
-
def add_timestamps_sql(table_name)
|
|
897
|
-
[add_column_sql(table_name, :created_at, :datetime), add_column_sql(table_name, :updated_at, :datetime)]
|
|
898
|
-
end
|
|
899
|
-
|
|
900
|
-
def remove_timestamps_sql(table_name)
|
|
901
|
-
[remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)]
|
|
902
|
-
end
|
|
903
|
-
|
|
904
|
-
private
|
|
905
|
-
def exec_stmt(sql, name, binds)
|
|
906
|
-
cache = {}
|
|
907
|
-
if binds.empty?
|
|
908
|
-
stmt = @connection.prepare(sql)
|
|
909
|
-
else
|
|
910
|
-
cache = @statements[sql] ||= {
|
|
911
|
-
:stmt => @connection.prepare(sql)
|
|
912
|
-
}
|
|
913
|
-
stmt = cache[:stmt]
|
|
914
|
-
end
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
begin
|
|
918
|
-
stmt.execute(*binds.map { |col, val| type_cast(val, col) })
|
|
919
|
-
rescue Mysql::Error => e
|
|
920
|
-
# Older versions of MySQL leave the prepared statement in a bad
|
|
921
|
-
# place when an error occurs. To support older mysql versions, we
|
|
922
|
-
# need to close the statement and delete the statement from the
|
|
923
|
-
# cache.
|
|
924
|
-
stmt.close
|
|
925
|
-
@statements.delete sql
|
|
926
|
-
raise e
|
|
927
|
-
end
|
|
928
|
-
|
|
929
|
-
cols = nil
|
|
930
|
-
if metadata = stmt.result_metadata
|
|
931
|
-
cols = cache[:cols] ||= metadata.fetch_fields.map { |field|
|
|
932
|
-
field.name
|
|
933
|
-
}
|
|
934
|
-
end
|
|
935
|
-
|
|
936
|
-
result = yield [cols, stmt]
|
|
937
|
-
|
|
938
|
-
stmt.result_metadata.free if cols
|
|
939
|
-
stmt.free_result
|
|
940
|
-
stmt.close if binds.empty?
|
|
941
|
-
|
|
942
|
-
result
|
|
435
|
+
# Returns the version of the connected MySQL server.
|
|
436
|
+
def version
|
|
437
|
+
@version ||= @connection.server_info.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
|
|
943
438
|
end
|
|
944
|
-
|
|
945
|
-
def connect
|
|
946
|
-
encoding = @config[:encoding]
|
|
947
|
-
if encoding
|
|
948
|
-
@connection.options(Mysql::SET_CHARSET_NAME, encoding) rescue nil
|
|
949
|
-
end
|
|
950
|
-
|
|
951
|
-
if @config[:sslca] || @config[:sslkey]
|
|
952
|
-
@connection.ssl_set(@config[:sslkey], @config[:sslcert], @config[:sslca], @config[:sslcapath], @config[:sslcipher])
|
|
953
|
-
end
|
|
954
|
-
|
|
955
|
-
@connection.options(Mysql::OPT_CONNECT_TIMEOUT, @config[:connect_timeout]) if @config[:connect_timeout]
|
|
956
|
-
@connection.options(Mysql::OPT_READ_TIMEOUT, @config[:read_timeout]) if @config[:read_timeout]
|
|
957
|
-
@connection.options(Mysql::OPT_WRITE_TIMEOUT, @config[:write_timeout]) if @config[:write_timeout]
|
|
958
|
-
|
|
959
|
-
@connection.real_connect(*@connection_options)
|
|
960
|
-
|
|
961
|
-
# reconnect must be set after real_connect is called, because real_connect sets it to false internally
|
|
962
|
-
@connection.reconnect = !!@config[:reconnect] if @connection.respond_to?(:reconnect=)
|
|
963
|
-
|
|
964
|
-
configure_connection
|
|
965
|
-
end
|
|
966
|
-
|
|
967
|
-
def configure_connection
|
|
968
|
-
encoding = @config[:encoding]
|
|
969
|
-
execute("SET NAMES '#{encoding}'", :skip_logging) if encoding
|
|
970
|
-
|
|
971
|
-
# By default, MySQL 'where id is null' selects the last inserted id.
|
|
972
|
-
# Turn this off. http://dev.rubyonrails.org/ticket/6778
|
|
973
|
-
execute("SET SQL_AUTO_IS_NULL=0", :skip_logging)
|
|
974
|
-
end
|
|
975
|
-
|
|
976
|
-
def select(sql, name = nil, binds = [])
|
|
977
|
-
@connection.query_with_result = true
|
|
978
|
-
rows = exec_query(sql, name, binds).to_a
|
|
979
|
-
@connection.more_results && @connection.next_result # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped
|
|
980
|
-
rows
|
|
981
|
-
end
|
|
982
|
-
|
|
983
|
-
def supports_views?
|
|
984
|
-
version[0] >= 5
|
|
985
|
-
end
|
|
986
|
-
|
|
987
|
-
# Returns the version of the connected MySQL server.
|
|
988
|
-
def version
|
|
989
|
-
@version ||= @connection.server_info.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
|
|
990
|
-
end
|
|
991
|
-
|
|
992
|
-
def column_for(table_name, column_name)
|
|
993
|
-
unless column = columns(table_name).find { |c| c.name == column_name.to_s }
|
|
994
|
-
raise "No such column: #{table_name}.#{column_name}"
|
|
995
|
-
end
|
|
996
|
-
column
|
|
997
|
-
end
|
|
998
439
|
end
|
|
999
440
|
end
|
|
1000
441
|
end
|