activerecord 3.1.12 → 3.2.22.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +804 -338
- 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 +13 -45
- data/lib/active_record/associations/association_scope.rb +3 -15
- data/lib/active_record/associations/belongs_to_association.rb +1 -1
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +2 -1
- data/lib/active_record/associations/builder/association.rb +6 -4
- data/lib/active_record/associations/builder/belongs_to.rb +7 -4
- 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 +65 -32
- data/lib/active_record/associations/collection_proxy.rb +8 -41
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +1 -0
- data/lib/active_record/associations/has_many_association.rb +11 -7
- data/lib/active_record/associations/has_many_through_association.rb +19 -9
- data/lib/active_record/associations/has_one_association.rb +23 -13
- data/lib/active_record/associations/join_dependency/join_association.rb +6 -1
- data/lib/active_record/associations/join_dependency.rb +3 -3
- data/lib/active_record/associations/preloader/through_association.rb +3 -3
- data/lib/active_record/associations/preloader.rb +14 -10
- data/lib/active_record/associations/through_association.rb +8 -4
- 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 +120 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -14
- data/lib/active_record/attribute_methods/write.rb +32 -6
- data/lib/active_record/attribute_methods.rb +231 -30
- data/lib/active_record/autosave_association.rb +44 -26
- 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 +7 -34
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +10 -2
- data/lib/active_record/connection_adapters/abstract/quoting.rb +7 -4
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +39 -28
- 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 +676 -0
- data/lib/active_record/connection_adapters/column.rb +37 -11
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +133 -581
- data/lib/active_record/connection_adapters/mysql_adapter.rb +136 -693
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +209 -97
- data/lib/active_record/connection_adapters/schema_cache.rb +69 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +2 -6
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +62 -35
- 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 +86 -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 +57 -86
- data/lib/active_record/identity_map.rb +3 -4
- data/lib/active_record/inheritance.rb +174 -0
- data/lib/active_record/integration.rb +60 -0
- data/lib/active_record/locking/optimistic.rb +33 -26
- data/lib/active_record/locking/pessimistic.rb +23 -1
- data/lib/active_record/log_subscriber.rb +8 -4
- 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 +368 -0
- data/lib/active_record/nested_attributes.rb +60 -24
- data/lib/active_record/persistence.rb +57 -11
- data/lib/active_record/query_cache.rb +6 -6
- data/lib/active_record/querying.rb +58 -0
- data/lib/active_record/railtie.rb +37 -29
- data/lib/active_record/railties/controller_runtime.rb +3 -1
- data/lib/active_record/railties/databases.rake +213 -117
- 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 +7 -4
- data/lib/active_record/relation/calculations.rb +55 -16
- data/lib/active_record/relation/delegation.rb +49 -0
- data/lib/active_record/relation/finder_methods.rb +16 -11
- data/lib/active_record/relation/predicate_builder.rb +8 -6
- data/lib/active_record/relation/query_methods.rb +75 -9
- data/lib/active_record/relation/spawn_methods.rb +48 -7
- data/lib/active_record/relation.rb +78 -32
- data/lib/active_record/result.rb +10 -4
- 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 +200 -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 +18 -16
- 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 +8 -8
- data/lib/active_record/validations.rb +1 -1
- data/lib/active_record/version.rb +3 -3
- 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 +49 -28
- data/lib/active_record/named_scope.rb +0 -200
@@ -1,5 +1,4 @@
|
|
1
|
-
|
2
|
-
require 'arel/visitors/bind_visitor'
|
1
|
+
require 'active_record/connection_adapters/abstract_mysql_adapter'
|
3
2
|
|
4
3
|
gem 'mysql2', '~> 0.3.10'
|
5
4
|
require 'mysql2'
|
@@ -21,193 +20,52 @@ module ActiveRecord
|
|
21
20
|
end
|
22
21
|
|
23
22
|
module ConnectionAdapters
|
24
|
-
class
|
25
|
-
end
|
23
|
+
class Mysql2Adapter < AbstractMysqlAdapter
|
26
24
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
if sql_type =~ /blob/i || type == :text
|
31
|
-
if default.blank?
|
32
|
-
return null ? nil : ''
|
33
|
-
else
|
34
|
-
raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
|
35
|
-
end
|
36
|
-
elsif missing_default_forged_as_empty_string?(default)
|
37
|
-
nil
|
38
|
-
else
|
39
|
-
super
|
25
|
+
class Column < AbstractMysqlAdapter::Column # :nodoc:
|
26
|
+
def adapter
|
27
|
+
Mysql2Adapter
|
40
28
|
end
|
41
29
|
end
|
42
30
|
|
43
|
-
def has_default?
|
44
|
-
return false if sql_type =~ /blob/i || type == :text #mysql forbids defaults on blob and text columns
|
45
|
-
super
|
46
|
-
end
|
47
|
-
|
48
|
-
private
|
49
|
-
def simplified_type(field_type)
|
50
|
-
return :boolean if Mysql2Adapter.emulate_booleans && field_type.downcase.index(BOOL)
|
51
|
-
|
52
|
-
case field_type
|
53
|
-
when /enum/i, /set/i then :string
|
54
|
-
when /year/i then :integer
|
55
|
-
when /bit/i then :binary
|
56
|
-
else
|
57
|
-
super
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
def extract_limit(sql_type)
|
62
|
-
case sql_type
|
63
|
-
when /blob|text/i
|
64
|
-
case sql_type
|
65
|
-
when /tiny/i
|
66
|
-
255
|
67
|
-
when /medium/i
|
68
|
-
16777215
|
69
|
-
when /long/i
|
70
|
-
2147483647 # mysql only allows 2^31-1, not 2^32-1, somewhat inconsistently with the tiny/medium/normal cases
|
71
|
-
else
|
72
|
-
super # we could return 65535 here, but we leave it undecorated by default
|
73
|
-
end
|
74
|
-
when /^bigint/i; 8
|
75
|
-
when /^int/i; 4
|
76
|
-
when /^mediumint/i; 3
|
77
|
-
when /^smallint/i; 2
|
78
|
-
when /^tinyint/i; 1
|
79
|
-
else
|
80
|
-
super
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
# MySQL misreports NOT NULL column default when none is given.
|
85
|
-
# We can't detect this for columns which may have a legitimate ''
|
86
|
-
# default (string) but we can for others (integer, datetime, boolean,
|
87
|
-
# and the rest).
|
88
|
-
#
|
89
|
-
# Test whether the column has default '', is not null, and is not
|
90
|
-
# a type allowing default ''.
|
91
|
-
def missing_default_forged_as_empty_string?(default)
|
92
|
-
type != :string && !null && default == ''
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
class Mysql2Adapter < AbstractAdapter
|
97
|
-
cattr_accessor :emulate_booleans
|
98
|
-
self.emulate_booleans = true
|
99
|
-
|
100
31
|
ADAPTER_NAME = 'Mysql2'
|
101
|
-
PRIMARY = "PRIMARY"
|
102
|
-
|
103
|
-
LOST_CONNECTION_ERROR_MESSAGES = [
|
104
|
-
"Server shutdown in progress",
|
105
|
-
"Broken pipe",
|
106
|
-
"Lost connection to MySQL server during query",
|
107
|
-
"MySQL server has gone away" ]
|
108
|
-
|
109
|
-
QUOTED_TRUE, QUOTED_FALSE = '1', '0'
|
110
|
-
|
111
|
-
NATIVE_DATABASE_TYPES = {
|
112
|
-
:primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY",
|
113
|
-
:string => { :name => "varchar", :limit => 255 },
|
114
|
-
:text => { :name => "text" },
|
115
|
-
:integer => { :name => "int", :limit => 4 },
|
116
|
-
:float => { :name => "float" },
|
117
|
-
:decimal => { :name => "decimal" },
|
118
|
-
:datetime => { :name => "datetime" },
|
119
|
-
:timestamp => { :name => "datetime" },
|
120
|
-
:time => { :name => "time" },
|
121
|
-
:date => { :name => "date" },
|
122
|
-
:binary => { :name => "blob" },
|
123
|
-
:boolean => { :name => "tinyint", :limit => 1 }
|
124
|
-
}
|
125
32
|
|
126
33
|
def initialize(connection, logger, connection_options, config)
|
127
|
-
super
|
128
|
-
@
|
129
|
-
@quoted_column_names, @quoted_table_names = {}, {}
|
34
|
+
super
|
35
|
+
@visitor = BindSubstitution.new self
|
130
36
|
configure_connection
|
131
37
|
end
|
132
38
|
|
133
|
-
|
134
|
-
include Arel::Visitors::BindVisitor
|
135
|
-
end
|
136
|
-
|
137
|
-
def self.visitor_for(pool) # :nodoc:
|
138
|
-
BindSubstitution.new pool
|
139
|
-
end
|
140
|
-
|
141
|
-
def adapter_name
|
142
|
-
ADAPTER_NAME
|
143
|
-
end
|
144
|
-
|
145
|
-
# Returns true, since this connection adapter supports migrations.
|
146
|
-
def supports_migrations?
|
39
|
+
def supports_explain?
|
147
40
|
true
|
148
41
|
end
|
149
42
|
|
150
|
-
|
151
|
-
true
|
152
|
-
end
|
43
|
+
# HELPER METHODS ===========================================
|
153
44
|
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
def native_database_types
|
160
|
-
NATIVE_DATABASE_TYPES
|
161
|
-
end
|
162
|
-
|
163
|
-
# QUOTING ==================================================
|
164
|
-
|
165
|
-
def quote(value, column = nil)
|
166
|
-
if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary)
|
167
|
-
s = column.class.string_to_binary(value).unpack("H*")[0]
|
168
|
-
"x'#{s}'"
|
45
|
+
def each_hash(result) # :nodoc:
|
46
|
+
if block_given?
|
47
|
+
result.each(:as => :hash, :symbolize_keys => true) do |row|
|
48
|
+
yield row
|
49
|
+
end
|
169
50
|
else
|
170
|
-
|
51
|
+
to_enum(:each_hash, result)
|
171
52
|
end
|
172
53
|
end
|
173
54
|
|
174
|
-
def
|
175
|
-
|
55
|
+
def new_column(field, default, type, null, collation) # :nodoc:
|
56
|
+
Column.new(field, default, type, null, collation)
|
176
57
|
end
|
177
58
|
|
178
|
-
def
|
179
|
-
|
59
|
+
def error_number(exception)
|
60
|
+
exception.error_number if exception.respond_to?(:error_number)
|
180
61
|
end
|
181
62
|
|
63
|
+
# QUOTING ==================================================
|
64
|
+
|
182
65
|
def quote_string(string)
|
183
66
|
@connection.escape(string)
|
184
67
|
end
|
185
68
|
|
186
|
-
def quoted_true
|
187
|
-
QUOTED_TRUE
|
188
|
-
end
|
189
|
-
|
190
|
-
def quoted_false
|
191
|
-
QUOTED_FALSE
|
192
|
-
end
|
193
|
-
|
194
|
-
def substitute_at(column, index)
|
195
|
-
Arel::Nodes::BindParam.new "\0"
|
196
|
-
end
|
197
|
-
|
198
|
-
# REFERENTIAL INTEGRITY ====================================
|
199
|
-
|
200
|
-
def disable_referential_integrity(&block) #:nodoc:
|
201
|
-
old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
|
202
|
-
|
203
|
-
begin
|
204
|
-
update("SET FOREIGN_KEY_CHECKS = 0")
|
205
|
-
yield
|
206
|
-
ensure
|
207
|
-
update("SET FOREIGN_KEY_CHECKS = #{old}")
|
208
|
-
end
|
209
|
-
end
|
210
|
-
|
211
69
|
# CONNECTION MANAGEMENT ====================================
|
212
70
|
|
213
71
|
def active?
|
@@ -220,11 +78,6 @@ module ActiveRecord
|
|
220
78
|
connect
|
221
79
|
end
|
222
80
|
|
223
|
-
# this is set to true in 2.3, but we don't want it to be
|
224
|
-
def requires_reloading?
|
225
|
-
false
|
226
|
-
end
|
227
|
-
|
228
81
|
# Disconnects from the database if already connected.
|
229
82
|
# Otherwise, this method does nothing.
|
230
83
|
def disconnect!
|
@@ -241,6 +94,80 @@ module ActiveRecord
|
|
241
94
|
|
242
95
|
# DATABASE STATEMENTS ======================================
|
243
96
|
|
97
|
+
def explain(arel, binds = [])
|
98
|
+
sql = "EXPLAIN #{to_sql(arel, binds.dup)}"
|
99
|
+
start = Time.now
|
100
|
+
result = exec_query(sql, 'EXPLAIN', binds)
|
101
|
+
elapsed = Time.now - start
|
102
|
+
|
103
|
+
ExplainPrettyPrinter.new.pp(result, elapsed)
|
104
|
+
end
|
105
|
+
|
106
|
+
class ExplainPrettyPrinter # :nodoc:
|
107
|
+
# Pretty prints the result of a EXPLAIN in a way that resembles the output of the
|
108
|
+
# MySQL shell:
|
109
|
+
#
|
110
|
+
# +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
|
111
|
+
# | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
|
112
|
+
# +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
|
113
|
+
# | 1 | SIMPLE | users | const | PRIMARY | PRIMARY | 4 | const | 1 | |
|
114
|
+
# | 1 | SIMPLE | posts | ALL | NULL | NULL | NULL | NULL | 1 | Using where |
|
115
|
+
# +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
|
116
|
+
# 2 rows in set (0.00 sec)
|
117
|
+
#
|
118
|
+
# This is an exercise in Ruby hyperrealism :).
|
119
|
+
def pp(result, elapsed)
|
120
|
+
widths = compute_column_widths(result)
|
121
|
+
separator = build_separator(widths)
|
122
|
+
|
123
|
+
pp = []
|
124
|
+
|
125
|
+
pp << separator
|
126
|
+
pp << build_cells(result.columns, widths)
|
127
|
+
pp << separator
|
128
|
+
|
129
|
+
result.rows.each do |row|
|
130
|
+
pp << build_cells(row, widths)
|
131
|
+
end
|
132
|
+
|
133
|
+
pp << separator
|
134
|
+
pp << build_footer(result.rows.length, elapsed)
|
135
|
+
|
136
|
+
pp.join("\n") + "\n"
|
137
|
+
end
|
138
|
+
|
139
|
+
private
|
140
|
+
|
141
|
+
def compute_column_widths(result)
|
142
|
+
[].tap do |widths|
|
143
|
+
result.columns.each_with_index do |column, i|
|
144
|
+
cells_in_column = [column] + result.rows.map {|r| r[i].nil? ? 'NULL' : r[i].to_s}
|
145
|
+
widths << cells_in_column.map(&:length).max
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def build_separator(widths)
|
151
|
+
padding = 1
|
152
|
+
'+' + widths.map {|w| '-' * (w + (padding*2))}.join('+') + '+'
|
153
|
+
end
|
154
|
+
|
155
|
+
def build_cells(items, widths)
|
156
|
+
cells = []
|
157
|
+
items.each_with_index do |item, i|
|
158
|
+
item = 'NULL' if item.nil?
|
159
|
+
justifier = item.is_a?(Numeric) ? 'rjust' : 'ljust'
|
160
|
+
cells << item.to_s.send(justifier, widths[i])
|
161
|
+
end
|
162
|
+
'| ' + cells.join(' | ') + ' |'
|
163
|
+
end
|
164
|
+
|
165
|
+
def build_footer(nrows, elapsed)
|
166
|
+
rows_label = nrows == 1 ? 'row' : 'rows'
|
167
|
+
"#{nrows} #{rows_label} in set (%.2f sec)" % elapsed
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
244
171
|
# FIXME: re-enable the following once a "better" query_cache solution is in core
|
245
172
|
#
|
246
173
|
# The overrides below perform much better than the originals in AbstractAdapter
|
@@ -277,20 +204,26 @@ module ActiveRecord
|
|
277
204
|
|
278
205
|
# Executes the SQL statement in the context of this connection.
|
279
206
|
def execute(sql, name = nil)
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
@connection.query(sql)
|
285
|
-
else
|
286
|
-
log(sql, name) { @connection.query(sql) }
|
287
|
-
end
|
288
|
-
rescue ActiveRecord::StatementInvalid => exception
|
289
|
-
if exception.message.split(":").first =~ /Packets out of order/
|
290
|
-
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."
|
291
|
-
else
|
292
|
-
raise
|
207
|
+
if @connection
|
208
|
+
# make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
|
209
|
+
# made since we established the connection
|
210
|
+
@connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone
|
293
211
|
end
|
212
|
+
|
213
|
+
super
|
214
|
+
end
|
215
|
+
|
216
|
+
def exec_query(sql, name = 'SQL', binds = [])
|
217
|
+
result = execute(sql, name)
|
218
|
+
ActiveRecord::Result.new(result.fields, result.to_a)
|
219
|
+
end
|
220
|
+
|
221
|
+
alias exec_without_stmt exec_query
|
222
|
+
|
223
|
+
# Returns an array of record hashes with the column names as keys and
|
224
|
+
# column values as values.
|
225
|
+
def select(sql, name = nil, binds = [])
|
226
|
+
exec_query(sql, name).to_a
|
294
227
|
end
|
295
228
|
|
296
229
|
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
|
@@ -313,416 +246,35 @@ module ActiveRecord
|
|
313
246
|
@connection.last_id
|
314
247
|
end
|
315
248
|
|
316
|
-
|
317
|
-
super
|
318
|
-
@connection.affected_rows
|
319
|
-
end
|
320
|
-
|
321
|
-
def begin_db_transaction
|
322
|
-
execute "BEGIN"
|
323
|
-
rescue Exception
|
324
|
-
# Transactions aren't supported
|
325
|
-
end
|
326
|
-
|
327
|
-
def commit_db_transaction
|
328
|
-
execute "COMMIT"
|
329
|
-
rescue Exception
|
330
|
-
# Transactions aren't supported
|
331
|
-
end
|
332
|
-
|
333
|
-
def rollback_db_transaction
|
334
|
-
execute "ROLLBACK"
|
335
|
-
rescue Exception
|
336
|
-
# Transactions aren't supported
|
337
|
-
end
|
338
|
-
|
339
|
-
def create_savepoint
|
340
|
-
execute("SAVEPOINT #{current_savepoint_name}")
|
341
|
-
end
|
342
|
-
|
343
|
-
def rollback_to_savepoint
|
344
|
-
execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
|
345
|
-
end
|
346
|
-
|
347
|
-
def release_savepoint
|
348
|
-
execute("RELEASE SAVEPOINT #{current_savepoint_name}")
|
349
|
-
end
|
350
|
-
|
351
|
-
def add_limit_offset!(sql, options)
|
352
|
-
limit, offset = options[:limit], options[:offset]
|
353
|
-
if limit && offset
|
354
|
-
sql << " LIMIT #{offset.to_i}, #{sanitize_limit(limit)}"
|
355
|
-
elsif limit
|
356
|
-
sql << " LIMIT #{sanitize_limit(limit)}"
|
357
|
-
elsif offset
|
358
|
-
sql << " OFFSET #{offset.to_i}"
|
359
|
-
end
|
360
|
-
sql
|
361
|
-
end
|
362
|
-
deprecate :add_limit_offset!
|
363
|
-
|
364
|
-
# SCHEMA STATEMENTS ========================================
|
365
|
-
|
366
|
-
def structure_dump
|
367
|
-
if supports_views?
|
368
|
-
sql = "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'"
|
369
|
-
else
|
370
|
-
sql = "SHOW TABLES"
|
371
|
-
end
|
372
|
-
|
373
|
-
select_all(sql).inject("") do |structure, table|
|
374
|
-
table.delete('Table_type')
|
375
|
-
structure += select_one("SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}")["Create Table"] + ";\n\n"
|
376
|
-
end
|
377
|
-
end
|
378
|
-
|
379
|
-
# Drops the database specified on the +name+ attribute
|
380
|
-
# and creates it again using the provided +options+.
|
381
|
-
def recreate_database(name, options = {})
|
382
|
-
drop_database(name)
|
383
|
-
create_database(name, options)
|
384
|
-
end
|
385
|
-
|
386
|
-
# Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
|
387
|
-
# Charset defaults to utf8.
|
388
|
-
#
|
389
|
-
# Example:
|
390
|
-
# create_database 'charset_test', :charset => 'latin1', :collation => 'latin1_bin'
|
391
|
-
# create_database 'matt_development'
|
392
|
-
# create_database 'matt_development', :charset => :big5
|
393
|
-
def create_database(name, options = {})
|
394
|
-
if options[:collation]
|
395
|
-
execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{options[:collation]}`"
|
396
|
-
else
|
397
|
-
execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}`"
|
398
|
-
end
|
399
|
-
end
|
400
|
-
|
401
|
-
# Drops a MySQL database.
|
402
|
-
#
|
403
|
-
# Example:
|
404
|
-
# drop_database('sebastian_development')
|
405
|
-
def drop_database(name) #:nodoc:
|
406
|
-
execute "DROP DATABASE IF EXISTS `#{name}`"
|
407
|
-
end
|
408
|
-
|
409
|
-
def current_database
|
410
|
-
select_value 'SELECT DATABASE() as db'
|
411
|
-
end
|
412
|
-
|
413
|
-
# Returns the database character set.
|
414
|
-
def charset
|
415
|
-
show_variable 'character_set_database'
|
416
|
-
end
|
417
|
-
|
418
|
-
# Returns the database collation strategy.
|
419
|
-
def collation
|
420
|
-
show_variable 'collation_database'
|
421
|
-
end
|
422
|
-
|
423
|
-
def tables(name = nil, database = nil) #:nodoc:
|
424
|
-
sql = "SHOW TABLES "
|
425
|
-
sql << "IN #{quote_table_name(database)} " if database
|
426
|
-
|
427
|
-
execute(sql, 'SCHEMA').collect do |field|
|
428
|
-
field.first
|
429
|
-
end
|
430
|
-
end
|
431
|
-
|
432
|
-
def table_exists?(name)
|
433
|
-
return true if super
|
434
|
-
|
435
|
-
name = name.to_s
|
436
|
-
schema, table = name.split('.', 2)
|
437
|
-
|
438
|
-
unless table # A table was provided without a schema
|
439
|
-
table = schema
|
440
|
-
schema = nil
|
441
|
-
end
|
442
|
-
|
443
|
-
tables(nil, schema).include? table
|
444
|
-
end
|
445
|
-
|
446
|
-
def drop_table(table_name, options = {})
|
447
|
-
super(table_name, options)
|
448
|
-
end
|
449
|
-
|
450
|
-
# Returns an array of indexes for the given table.
|
451
|
-
def indexes(table_name, name = nil)
|
452
|
-
indexes = []
|
453
|
-
current_index = nil
|
454
|
-
result = execute("SHOW KEYS FROM #{quote_table_name(table_name)}", 'SCHEMA')
|
455
|
-
result.each(:symbolize_keys => true, :as => :hash) do |row|
|
456
|
-
if current_index != row[:Key_name]
|
457
|
-
next if row[:Key_name] == PRIMARY # skip the primary key
|
458
|
-
current_index = row[:Key_name]
|
459
|
-
indexes << Mysql2IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique] == 0, [], [])
|
460
|
-
end
|
461
|
-
|
462
|
-
indexes.last.columns << row[:Column_name]
|
463
|
-
indexes.last.lengths << row[:Sub_part]
|
464
|
-
end
|
465
|
-
indexes
|
466
|
-
end
|
467
|
-
|
468
|
-
# Returns an array of +Mysql2Column+ objects for the table specified by +table_name+.
|
469
|
-
def columns(table_name, name = nil)
|
470
|
-
sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}"
|
471
|
-
columns = []
|
472
|
-
result = execute(sql, 'SCHEMA')
|
473
|
-
result.each(:symbolize_keys => true, :as => :hash) { |field|
|
474
|
-
columns << Mysql2Column.new(field[:Field], field[:Default], field[:Type], field[:Null] == "YES")
|
475
|
-
}
|
476
|
-
columns
|
477
|
-
end
|
478
|
-
|
479
|
-
def create_table(table_name, options = {})
|
480
|
-
super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
|
481
|
-
end
|
482
|
-
|
483
|
-
# Renames a table.
|
484
|
-
#
|
485
|
-
# Example:
|
486
|
-
# rename_table('octopuses', 'octopi')
|
487
|
-
def rename_table(table_name, new_name)
|
488
|
-
execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
|
489
|
-
end
|
490
|
-
|
491
|
-
def add_column(table_name, column_name, type, options = {})
|
492
|
-
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])}"
|
493
|
-
add_column_options!(add_column_sql, options)
|
494
|
-
add_column_position!(add_column_sql, options)
|
495
|
-
execute(add_column_sql)
|
496
|
-
end
|
497
|
-
|
498
|
-
def change_column_default(table_name, column_name, default)
|
499
|
-
column = column_for(table_name, column_name)
|
500
|
-
change_column table_name, column_name, column.sql_type, :default => default
|
501
|
-
end
|
502
|
-
|
503
|
-
def change_column_null(table_name, column_name, null, default = nil)
|
504
|
-
column = column_for(table_name, column_name)
|
505
|
-
|
506
|
-
unless null || default.nil?
|
507
|
-
execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
|
508
|
-
end
|
509
|
-
|
510
|
-
change_column table_name, column_name, column.sql_type, :null => null
|
511
|
-
end
|
512
|
-
|
513
|
-
def change_column(table_name, column_name, type, options = {})
|
514
|
-
column = column_for(table_name, column_name)
|
515
|
-
|
516
|
-
unless options_include_default?(options)
|
517
|
-
options[:default] = column.default
|
518
|
-
end
|
519
|
-
|
520
|
-
unless options.has_key?(:null)
|
521
|
-
options[:null] = column.null
|
522
|
-
end
|
523
|
-
|
524
|
-
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])}"
|
525
|
-
add_column_options!(change_column_sql, options)
|
526
|
-
add_column_position!(change_column_sql, options)
|
527
|
-
execute(change_column_sql)
|
528
|
-
end
|
529
|
-
|
530
|
-
def rename_column(table_name, column_name, new_column_name)
|
531
|
-
options = {}
|
532
|
-
if column = columns(table_name).find { |c| c.name == column_name.to_s }
|
533
|
-
options[:default] = column.default
|
534
|
-
options[:null] = column.null
|
535
|
-
else
|
536
|
-
raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
|
537
|
-
end
|
538
|
-
current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"]
|
539
|
-
rename_column_sql = "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}"
|
540
|
-
add_column_options!(rename_column_sql, options)
|
541
|
-
execute(rename_column_sql)
|
542
|
-
end
|
543
|
-
|
544
|
-
# Maps logical Rails types to MySQL-specific data types.
|
545
|
-
def type_to_sql(type, limit = nil, precision = nil, scale = nil)
|
546
|
-
case type.to_s
|
547
|
-
when 'integer'
|
548
|
-
case limit
|
549
|
-
when 1; 'tinyint'
|
550
|
-
when 2; 'smallint'
|
551
|
-
when 3; 'mediumint'
|
552
|
-
when nil, 4, 11; 'int(11)' # compatibility with MySQL default
|
553
|
-
when 5..8; 'bigint'
|
554
|
-
else raise(ActiveRecordError, "No integer type has byte size #{limit}")
|
555
|
-
end
|
556
|
-
when 'text'
|
557
|
-
case limit
|
558
|
-
when 0..0xff; 'tinytext'
|
559
|
-
when nil, 0x100..0xffff; 'text'
|
560
|
-
when 0x10000..0xffffff; 'mediumtext'
|
561
|
-
when 0x1000000..0xffffffff; 'longtext'
|
562
|
-
else raise(ActiveRecordError, "No text type has character length #{limit}")
|
563
|
-
end
|
564
|
-
else
|
565
|
-
super
|
566
|
-
end
|
567
|
-
end
|
568
|
-
|
569
|
-
def add_column_position!(sql, options)
|
570
|
-
if options[:first]
|
571
|
-
sql << " FIRST"
|
572
|
-
elsif options[:after]
|
573
|
-
sql << " AFTER #{quote_column_name(options[:after])}"
|
574
|
-
end
|
575
|
-
end
|
249
|
+
private
|
576
250
|
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
variables.first['Value'] unless variables.empty?
|
251
|
+
def connect
|
252
|
+
@connection = Mysql2::Client.new(@config)
|
253
|
+
configure_connection
|
581
254
|
end
|
582
255
|
|
583
|
-
|
584
|
-
|
585
|
-
result = execute("SHOW CREATE TABLE #{quote_table_name(table)}", 'SCHEMA')
|
586
|
-
create_table = result.first[1]
|
256
|
+
def configure_connection
|
257
|
+
@connection.query_options.merge!(:as => :array)
|
587
258
|
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
nil
|
593
|
-
end
|
594
|
-
end
|
259
|
+
# By default, MySQL 'where id is null' selects the last inserted id.
|
260
|
+
# Turn this off. http://dev.rubyonrails.org/ticket/6778
|
261
|
+
variable_assignments = ['SQL_AUTO_IS_NULL=0']
|
262
|
+
encoding = @config[:encoding]
|
595
263
|
|
596
|
-
|
597
|
-
|
598
|
-
pk_and_sequence = pk_and_sequence_for(table)
|
599
|
-
pk_and_sequence && pk_and_sequence.first
|
600
|
-
end
|
601
|
-
|
602
|
-
def case_sensitive_equality_operator
|
603
|
-
"= BINARY"
|
604
|
-
end
|
605
|
-
deprecate :case_sensitive_equality_operator
|
264
|
+
# make sure we set the encoding
|
265
|
+
variable_assignments << "NAMES '#{encoding}'" if encoding
|
606
266
|
|
607
|
-
|
608
|
-
|
609
|
-
|
267
|
+
# increase timeout so mysql server doesn't disconnect us
|
268
|
+
wait_timeout = @config[:wait_timeout]
|
269
|
+
wait_timeout = 2147483 unless wait_timeout.is_a?(Fixnum)
|
270
|
+
variable_assignments << "@@wait_timeout = #{wait_timeout}"
|
610
271
|
|
611
|
-
|
612
|
-
where_sql
|
272
|
+
execute("SET #{variable_assignments.join(', ')}", :skip_logging)
|
613
273
|
end
|
614
274
|
|
615
|
-
|
616
|
-
|
617
|
-
# these, we must use a subquery. However, MySQL is too stupid to create a
|
618
|
-
# temporary table for this automatically, so we have to give it some prompting
|
619
|
-
# in the form of a subsubquery. Ugh!
|
620
|
-
def join_to_update(update, select) #:nodoc:
|
621
|
-
if select.limit || select.offset || select.orders.any?
|
622
|
-
subsubselect = select.clone
|
623
|
-
subsubselect.projections = [update.key]
|
624
|
-
|
625
|
-
subselect = Arel::SelectManager.new(select.engine)
|
626
|
-
subselect.project Arel.sql(update.key.name)
|
627
|
-
subselect.from subsubselect.as('__active_record_temp')
|
628
|
-
|
629
|
-
update.where update.key.in(subselect)
|
630
|
-
else
|
631
|
-
update.table select.source
|
632
|
-
update.wheres = select.constraints
|
633
|
-
end
|
275
|
+
def version
|
276
|
+
@version ||= @connection.info[:version].scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
|
634
277
|
end
|
635
|
-
|
636
|
-
protected
|
637
|
-
def quoted_columns_for_index(column_names, options = {})
|
638
|
-
length = options[:length] if options.is_a?(Hash)
|
639
|
-
|
640
|
-
quoted_column_names = case length
|
641
|
-
when Hash
|
642
|
-
column_names.map {|name| length[name] ? "#{quote_column_name(name)}(#{length[name]})" : quote_column_name(name) }
|
643
|
-
when Fixnum
|
644
|
-
column_names.map {|name| "#{quote_column_name(name)}(#{length})"}
|
645
|
-
else
|
646
|
-
column_names.map {|name| quote_column_name(name) }
|
647
|
-
end
|
648
|
-
end
|
649
|
-
|
650
|
-
def translate_exception(exception, message)
|
651
|
-
return super unless exception.respond_to?(:error_number)
|
652
|
-
|
653
|
-
case exception.error_number
|
654
|
-
when 1062
|
655
|
-
RecordNotUnique.new(message, exception)
|
656
|
-
when 1452
|
657
|
-
InvalidForeignKey.new(message, exception)
|
658
|
-
else
|
659
|
-
super
|
660
|
-
end
|
661
|
-
end
|
662
|
-
|
663
|
-
private
|
664
|
-
def connect
|
665
|
-
@connection = Mysql2::Client.new(@config)
|
666
|
-
configure_connection
|
667
|
-
end
|
668
|
-
|
669
|
-
def configure_connection
|
670
|
-
@connection.query_options.merge!(:as => :array)
|
671
|
-
|
672
|
-
# By default, MySQL 'where id is null' selects the last inserted id.
|
673
|
-
# Turn this off. http://dev.rubyonrails.org/ticket/6778
|
674
|
-
variable_assignments = ['SQL_AUTO_IS_NULL=0']
|
675
|
-
encoding = @config[:encoding]
|
676
|
-
|
677
|
-
# make sure we set the encoding
|
678
|
-
variable_assignments << "NAMES '#{encoding}'" if encoding
|
679
|
-
|
680
|
-
# increase timeout so mysql server doesn't disconnect us
|
681
|
-
wait_timeout = @config[:wait_timeout]
|
682
|
-
wait_timeout = 2592000 unless wait_timeout.is_a?(Fixnum)
|
683
|
-
variable_assignments << "@@wait_timeout = #{wait_timeout}"
|
684
|
-
|
685
|
-
execute("SET #{variable_assignments.join(', ')}", :skip_logging)
|
686
|
-
end
|
687
|
-
|
688
|
-
# Returns an array of record hashes with the column names as keys and
|
689
|
-
# column values as values.
|
690
|
-
def select(sql, name = nil, binds = [])
|
691
|
-
exec_query(sql, name).to_a
|
692
|
-
end
|
693
|
-
|
694
|
-
def exec_query(sql, name = 'SQL', binds = [])
|
695
|
-
@connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone
|
696
|
-
|
697
|
-
log(sql, name, binds) do
|
698
|
-
begin
|
699
|
-
result = @connection.query(sql)
|
700
|
-
rescue ActiveRecord::StatementInvalid => exception
|
701
|
-
if exception.message.split(":").first =~ /Packets out of order/
|
702
|
-
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."
|
703
|
-
else
|
704
|
-
raise
|
705
|
-
end
|
706
|
-
end
|
707
|
-
|
708
|
-
ActiveRecord::Result.new(result.fields, result.to_a)
|
709
|
-
end
|
710
|
-
end
|
711
|
-
|
712
|
-
def supports_views?
|
713
|
-
version[0] >= 5
|
714
|
-
end
|
715
|
-
|
716
|
-
def version
|
717
|
-
@version ||= @connection.info[:version].scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
|
718
|
-
end
|
719
|
-
|
720
|
-
def column_for(table_name, column_name)
|
721
|
-
unless column = columns(table_name).find { |c| c.name == column_name.to_s }
|
722
|
-
raise "No such column: #{table_name}.#{column_name}"
|
723
|
-
end
|
724
|
-
column
|
725
|
-
end
|
726
278
|
end
|
727
279
|
end
|
728
280
|
end
|