activerecord 3.0.0 → 4.0.0

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.

Files changed (181) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +2102 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +35 -44
  5. data/examples/performance.rb +110 -100
  6. data/lib/active_record/aggregations.rb +59 -75
  7. data/lib/active_record/associations/alias_tracker.rb +76 -0
  8. data/lib/active_record/associations/association.rb +248 -0
  9. data/lib/active_record/associations/association_scope.rb +135 -0
  10. data/lib/active_record/associations/belongs_to_association.rb +60 -59
  11. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +16 -59
  12. data/lib/active_record/associations/builder/association.rb +108 -0
  13. data/lib/active_record/associations/builder/belongs_to.rb +98 -0
  14. data/lib/active_record/associations/builder/collection_association.rb +89 -0
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +39 -0
  16. data/lib/active_record/associations/builder/has_many.rb +15 -0
  17. data/lib/active_record/associations/builder/has_one.rb +25 -0
  18. data/lib/active_record/associations/builder/singular_association.rb +32 -0
  19. data/lib/active_record/associations/collection_association.rb +608 -0
  20. data/lib/active_record/associations/collection_proxy.rb +986 -0
  21. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +40 -112
  22. data/lib/active_record/associations/has_many_association.rb +83 -76
  23. data/lib/active_record/associations/has_many_through_association.rb +147 -66
  24. data/lib/active_record/associations/has_one_association.rb +67 -108
  25. data/lib/active_record/associations/has_one_through_association.rb +21 -25
  26. data/lib/active_record/associations/join_dependency/join_association.rb +174 -0
  27. data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
  28. data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
  29. data/lib/active_record/associations/join_dependency.rb +235 -0
  30. data/lib/active_record/associations/join_helper.rb +45 -0
  31. data/lib/active_record/associations/preloader/association.rb +121 -0
  32. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  33. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  34. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
  35. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  36. data/lib/active_record/associations/preloader/has_many_through.rb +19 -0
  37. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  38. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  39. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  40. data/lib/active_record/associations/preloader/through_association.rb +63 -0
  41. data/lib/active_record/associations/preloader.rb +178 -0
  42. data/lib/active_record/associations/singular_association.rb +64 -0
  43. data/lib/active_record/associations/through_association.rb +87 -0
  44. data/lib/active_record/associations.rb +512 -1224
  45. data/lib/active_record/attribute_assignment.rb +201 -0
  46. data/lib/active_record/attribute_methods/before_type_cast.rb +49 -12
  47. data/lib/active_record/attribute_methods/dirty.rb +51 -28
  48. data/lib/active_record/attribute_methods/primary_key.rb +94 -22
  49. data/lib/active_record/attribute_methods/query.rb +5 -4
  50. data/lib/active_record/attribute_methods/read.rb +63 -72
  51. data/lib/active_record/attribute_methods/serialization.rb +162 -0
  52. data/lib/active_record/attribute_methods/time_zone_conversion.rb +39 -41
  53. data/lib/active_record/attribute_methods/write.rb +39 -13
  54. data/lib/active_record/attribute_methods.rb +362 -29
  55. data/lib/active_record/autosave_association.rb +132 -75
  56. data/lib/active_record/base.rb +83 -1627
  57. data/lib/active_record/callbacks.rb +69 -47
  58. data/lib/active_record/coders/yaml_column.rb +38 -0
  59. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +411 -138
  60. data/lib/active_record/connection_adapters/abstract/database_limits.rb +21 -11
  61. data/lib/active_record/connection_adapters/abstract/database_statements.rb +234 -173
  62. data/lib/active_record/connection_adapters/abstract/query_cache.rb +36 -22
  63. data/lib/active_record/connection_adapters/abstract/quoting.rb +82 -25
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +176 -414
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +562 -232
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +281 -53
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +782 -0
  70. data/lib/active_record/connection_adapters/column.rb +318 -0
  71. data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
  72. data/lib/active_record/connection_adapters/mysql2_adapter.rb +273 -0
  73. data/lib/active_record/connection_adapters/mysql_adapter.rb +365 -450
  74. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
  75. data/lib/active_record/connection_adapters/postgresql/cast.rb +152 -0
  76. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid.rb +366 -0
  78. data/lib/active_record/connection_adapters/postgresql/quoting.rb +171 -0
  79. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  80. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +489 -0
  81. data/lib/active_record/connection_adapters/postgresql_adapter.rb +672 -752
  82. data/lib/active_record/connection_adapters/schema_cache.rb +129 -0
  83. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +588 -17
  84. data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
  85. data/lib/active_record/connection_handling.rb +98 -0
  86. data/lib/active_record/core.rb +463 -0
  87. data/lib/active_record/counter_cache.rb +108 -101
  88. data/lib/active_record/dynamic_matchers.rb +131 -0
  89. data/lib/active_record/errors.rb +54 -13
  90. data/lib/active_record/explain.rb +38 -0
  91. data/lib/active_record/explain_registry.rb +30 -0
  92. data/lib/active_record/explain_subscriber.rb +29 -0
  93. data/lib/active_record/fixture_set/file.rb +55 -0
  94. data/lib/active_record/fixtures.rb +703 -785
  95. data/lib/active_record/inheritance.rb +200 -0
  96. data/lib/active_record/integration.rb +60 -0
  97. data/lib/active_record/locale/en.yml +8 -1
  98. data/lib/active_record/locking/optimistic.rb +69 -60
  99. data/lib/active_record/locking/pessimistic.rb +34 -12
  100. data/lib/active_record/log_subscriber.rb +40 -6
  101. data/lib/active_record/migration/command_recorder.rb +164 -0
  102. data/lib/active_record/migration/join_table.rb +15 -0
  103. data/lib/active_record/migration.rb +614 -216
  104. data/lib/active_record/model_schema.rb +345 -0
  105. data/lib/active_record/nested_attributes.rb +248 -119
  106. data/lib/active_record/null_relation.rb +65 -0
  107. data/lib/active_record/persistence.rb +275 -57
  108. data/lib/active_record/query_cache.rb +29 -9
  109. data/lib/active_record/querying.rb +62 -0
  110. data/lib/active_record/railtie.rb +135 -21
  111. data/lib/active_record/railties/console_sandbox.rb +5 -0
  112. data/lib/active_record/railties/controller_runtime.rb +17 -5
  113. data/lib/active_record/railties/databases.rake +249 -359
  114. data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
  115. data/lib/active_record/readonly_attributes.rb +30 -0
  116. data/lib/active_record/reflection.rb +283 -103
  117. data/lib/active_record/relation/batches.rb +38 -34
  118. data/lib/active_record/relation/calculations.rb +252 -139
  119. data/lib/active_record/relation/delegation.rb +125 -0
  120. data/lib/active_record/relation/finder_methods.rb +182 -188
  121. data/lib/active_record/relation/merger.rb +161 -0
  122. data/lib/active_record/relation/predicate_builder.rb +86 -21
  123. data/lib/active_record/relation/query_methods.rb +917 -134
  124. data/lib/active_record/relation/spawn_methods.rb +53 -92
  125. data/lib/active_record/relation.rb +405 -143
  126. data/lib/active_record/result.rb +67 -0
  127. data/lib/active_record/runtime_registry.rb +17 -0
  128. data/lib/active_record/sanitization.rb +168 -0
  129. data/lib/active_record/schema.rb +20 -14
  130. data/lib/active_record/schema_dumper.rb +55 -46
  131. data/lib/active_record/schema_migration.rb +39 -0
  132. data/lib/active_record/scoping/default.rb +146 -0
  133. data/lib/active_record/scoping/named.rb +175 -0
  134. data/lib/active_record/scoping.rb +82 -0
  135. data/lib/active_record/serialization.rb +8 -46
  136. data/lib/active_record/serializers/xml_serializer.rb +21 -68
  137. data/lib/active_record/statement_cache.rb +26 -0
  138. data/lib/active_record/store.rb +156 -0
  139. data/lib/active_record/tasks/database_tasks.rb +203 -0
  140. data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
  141. data/lib/active_record/tasks/mysql_database_tasks.rb +143 -0
  142. data/lib/active_record/tasks/oracle_database_tasks.rb +45 -0
  143. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  144. data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
  145. data/lib/active_record/tasks/sqlserver_database_tasks.rb +48 -0
  146. data/lib/active_record/test_case.rb +57 -28
  147. data/lib/active_record/timestamp.rb +49 -18
  148. data/lib/active_record/transactions.rb +106 -63
  149. data/lib/active_record/translation.rb +22 -0
  150. data/lib/active_record/validations/associated.rb +25 -24
  151. data/lib/active_record/validations/presence.rb +65 -0
  152. data/lib/active_record/validations/uniqueness.rb +123 -83
  153. data/lib/active_record/validations.rb +29 -29
  154. data/lib/active_record/version.rb +7 -5
  155. data/lib/active_record.rb +83 -34
  156. data/lib/rails/generators/active_record/migration/migration_generator.rb +46 -9
  157. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
  158. data/lib/rails/generators/active_record/migration/templates/migration.rb +30 -8
  159. data/lib/rails/generators/active_record/model/model_generator.rb +15 -5
  160. data/lib/rails/generators/active_record/model/templates/model.rb +7 -2
  161. data/lib/rails/generators/active_record/model/templates/module.rb +3 -1
  162. data/lib/rails/generators/active_record.rb +4 -8
  163. metadata +163 -121
  164. data/CHANGELOG +0 -6023
  165. data/examples/associations.png +0 -0
  166. data/lib/active_record/association_preload.rb +0 -403
  167. data/lib/active_record/associations/association_collection.rb +0 -562
  168. data/lib/active_record/associations/association_proxy.rb +0 -295
  169. data/lib/active_record/associations/through_association_scope.rb +0 -154
  170. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -113
  171. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -401
  172. data/lib/active_record/dynamic_finder_match.rb +0 -53
  173. data/lib/active_record/dynamic_scope_match.rb +0 -32
  174. data/lib/active_record/named_scope.rb +0 -138
  175. data/lib/active_record/observer.rb +0 -140
  176. data/lib/active_record/session_store.rb +0 -340
  177. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -16
  178. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  179. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -2
  180. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -24
  181. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -16
@@ -0,0 +1,782 @@
1
+ require 'arel/visitors/bind_visitor'
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ class AbstractMysqlAdapter < AbstractAdapter
6
+ class SchemaCreation < AbstractAdapter::SchemaCreation
7
+ private
8
+
9
+ def visit_AddColumn(o)
10
+ add_column_position!(super, o)
11
+ end
12
+
13
+ def add_column_position!(sql, column)
14
+ if column.first
15
+ sql << " FIRST"
16
+ elsif column.after
17
+ sql << " AFTER #{quote_column_name(column.after)}"
18
+ end
19
+ sql
20
+ end
21
+ end
22
+
23
+ def schema_creation
24
+ SchemaCreation.new self
25
+ end
26
+
27
+ class Column < ConnectionAdapters::Column # :nodoc:
28
+ attr_reader :collation, :strict, :extra
29
+
30
+ def initialize(name, default, sql_type = nil, null = true, collation = nil, strict = false, extra = "")
31
+ @strict = strict
32
+ @collation = collation
33
+ @extra = extra
34
+ super(name, default, sql_type, null)
35
+ end
36
+
37
+ def extract_default(default)
38
+ if blob_or_text_column?
39
+ if default.blank?
40
+ null || strict ? nil : ''
41
+ else
42
+ raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
43
+ end
44
+ elsif missing_default_forged_as_empty_string?(default)
45
+ nil
46
+ else
47
+ super
48
+ end
49
+ end
50
+
51
+ def has_default?
52
+ return false if blob_or_text_column? #mysql forbids defaults on blob and text columns
53
+ super
54
+ end
55
+
56
+ def blob_or_text_column?
57
+ sql_type =~ /blob/i || type == :text
58
+ end
59
+
60
+ # Must return the relevant concrete adapter
61
+ def adapter
62
+ raise NotImplementedError
63
+ end
64
+
65
+ def case_sensitive?
66
+ collation && !collation.match(/_ci$/)
67
+ end
68
+
69
+ private
70
+
71
+ def simplified_type(field_type)
72
+ return :boolean if adapter.emulate_booleans && field_type.downcase.index("tinyint(1)")
73
+
74
+ case field_type
75
+ when /enum/i, /set/i then :string
76
+ when /year/i then :integer
77
+ when /bit/i then :binary
78
+ else
79
+ super
80
+ end
81
+ end
82
+
83
+ def extract_limit(sql_type)
84
+ case sql_type
85
+ when /^enum\((.+)\)/i
86
+ $1.split(',').map{|enum| enum.strip.length - 2}.max
87
+ when /blob|text/i
88
+ case sql_type
89
+ when /tiny/i
90
+ 255
91
+ when /medium/i
92
+ 16777215
93
+ when /long/i
94
+ 2147483647 # mysql only allows 2^31-1, not 2^32-1, somewhat inconsistently with the tiny/medium/normal cases
95
+ else
96
+ super # we could return 65535 here, but we leave it undecorated by default
97
+ end
98
+ when /^bigint/i; 8
99
+ when /^int/i; 4
100
+ when /^mediumint/i; 3
101
+ when /^smallint/i; 2
102
+ when /^tinyint/i; 1
103
+ else
104
+ super
105
+ end
106
+ end
107
+
108
+ # MySQL misreports NOT NULL column default when none is given.
109
+ # We can't detect this for columns which may have a legitimate ''
110
+ # default (string) but we can for others (integer, datetime, boolean,
111
+ # and the rest).
112
+ #
113
+ # Test whether the column has default '', is not null, and is not
114
+ # a type allowing default ''.
115
+ def missing_default_forged_as_empty_string?(default)
116
+ type != :string && !null && default == ''
117
+ end
118
+ end
119
+
120
+ ##
121
+ # :singleton-method:
122
+ # By default, the MysqlAdapter will consider all columns of type <tt>tinyint(1)</tt>
123
+ # as boolean. If you wish to disable this emulation (which was the default
124
+ # behavior in versions 0.13.1 and earlier) you can add the following line
125
+ # to your application.rb file:
126
+ #
127
+ # ActiveRecord::ConnectionAdapters::Mysql[2]Adapter.emulate_booleans = false
128
+ class_attribute :emulate_booleans
129
+ self.emulate_booleans = true
130
+
131
+ LOST_CONNECTION_ERROR_MESSAGES = [
132
+ "Server shutdown in progress",
133
+ "Broken pipe",
134
+ "Lost connection to MySQL server during query",
135
+ "MySQL server has gone away" ]
136
+
137
+ QUOTED_TRUE, QUOTED_FALSE = '1', '0'
138
+
139
+ NATIVE_DATABASE_TYPES = {
140
+ :primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY",
141
+ :string => { :name => "varchar", :limit => 255 },
142
+ :text => { :name => "text" },
143
+ :integer => { :name => "int", :limit => 4 },
144
+ :float => { :name => "float" },
145
+ :decimal => { :name => "decimal" },
146
+ :datetime => { :name => "datetime" },
147
+ :timestamp => { :name => "datetime" },
148
+ :time => { :name => "time" },
149
+ :date => { :name => "date" },
150
+ :binary => { :name => "blob" },
151
+ :boolean => { :name => "tinyint", :limit => 1 }
152
+ }
153
+
154
+ INDEX_TYPES = [:fulltext, :spatial]
155
+ INDEX_USINGS = [:btree, :hash]
156
+
157
+ class BindSubstitution < Arel::Visitors::MySQL # :nodoc:
158
+ include Arel::Visitors::BindVisitor
159
+ end
160
+
161
+ # FIXME: Make the first parameter more similar for the two adapters
162
+ def initialize(connection, logger, connection_options, config)
163
+ super(connection, logger)
164
+ @connection_options, @config = connection_options, config
165
+ @quoted_column_names, @quoted_table_names = {}, {}
166
+
167
+ if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
168
+ @visitor = Arel::Visitors::MySQL.new self
169
+ else
170
+ @visitor = unprepared_visitor
171
+ end
172
+ end
173
+
174
+ def adapter_name #:nodoc:
175
+ self.class::ADAPTER_NAME
176
+ end
177
+
178
+ # Returns true, since this connection adapter supports migrations.
179
+ def supports_migrations?
180
+ true
181
+ end
182
+
183
+ def supports_primary_key?
184
+ true
185
+ end
186
+
187
+ # Returns true, since this connection adapter supports savepoints.
188
+ def supports_savepoints?
189
+ true
190
+ end
191
+
192
+ def supports_bulk_alter? #:nodoc:
193
+ true
194
+ end
195
+
196
+ # Technically MySQL allows to create indexes with the sort order syntax
197
+ # but at the moment (5.5) it doesn't yet implement them
198
+ def supports_index_sort_order?
199
+ true
200
+ end
201
+
202
+ # MySQL 4 technically support transaction isolation, but it is affected by a bug
203
+ # where the transaction level gets persisted for the whole session:
204
+ #
205
+ # http://bugs.mysql.com/bug.php?id=39170
206
+ def supports_transaction_isolation?
207
+ version[0] >= 5
208
+ end
209
+
210
+ def native_database_types
211
+ NATIVE_DATABASE_TYPES
212
+ end
213
+
214
+ def index_algorithms
215
+ { default: 'ALGORITHM = DEFAULT', copy: 'ALGORITHM = COPY', inplace: 'ALGORITHM = INPLACE' }
216
+ end
217
+
218
+ # HELPER METHODS ===========================================
219
+
220
+ # The two drivers have slightly different ways of yielding hashes of results, so
221
+ # this method must be implemented to provide a uniform interface.
222
+ def each_hash(result) # :nodoc:
223
+ raise NotImplementedError
224
+ end
225
+
226
+ # Overridden by the adapters to instantiate their specific Column type.
227
+ def new_column(field, default, type, null, collation, extra = "") # :nodoc:
228
+ Column.new(field, default, type, null, collation, extra)
229
+ end
230
+
231
+ # Must return the Mysql error number from the exception, if the exception has an
232
+ # error number.
233
+ def error_number(exception) # :nodoc:
234
+ raise NotImplementedError
235
+ end
236
+
237
+ # QUOTING ==================================================
238
+
239
+ def quote(value, column = nil)
240
+ if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary)
241
+ s = column.class.string_to_binary(value).unpack("H*")[0]
242
+ "x'#{s}'"
243
+ elsif value.kind_of?(BigDecimal)
244
+ value.to_s("F")
245
+ else
246
+ super
247
+ end
248
+ end
249
+
250
+ def quote_column_name(name) #:nodoc:
251
+ @quoted_column_names[name] ||= "`#{name.to_s.gsub('`', '``')}`"
252
+ end
253
+
254
+ def quote_table_name(name) #:nodoc:
255
+ @quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`')
256
+ end
257
+
258
+ def quoted_true
259
+ QUOTED_TRUE
260
+ end
261
+
262
+ def quoted_false
263
+ QUOTED_FALSE
264
+ end
265
+
266
+ # REFERENTIAL INTEGRITY ====================================
267
+
268
+ def disable_referential_integrity(&block) #:nodoc:
269
+ old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
270
+
271
+ begin
272
+ update("SET FOREIGN_KEY_CHECKS = 0")
273
+ yield
274
+ ensure
275
+ update("SET FOREIGN_KEY_CHECKS = #{old}")
276
+ end
277
+ end
278
+
279
+ # DATABASE STATEMENTS ======================================
280
+
281
+ # Executes the SQL statement in the context of this connection.
282
+ def execute(sql, name = nil)
283
+ if name == :skip_logging
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.new("'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.", exception.original_exception)
291
+ else
292
+ raise
293
+ end
294
+ end
295
+
296
+ # MysqlAdapter has to free a result after using it, so we use this method to write
297
+ # stuff in an abstract way without concerning ourselves about whether it needs to be
298
+ # explicitly freed or not.
299
+ def execute_and_free(sql, name = nil) #:nodoc:
300
+ yield execute(sql, name)
301
+ end
302
+
303
+ def update_sql(sql, name = nil) #:nodoc:
304
+ super
305
+ @connection.affected_rows
306
+ end
307
+
308
+ def begin_db_transaction
309
+ execute "BEGIN"
310
+ rescue
311
+ # Transactions aren't supported
312
+ end
313
+
314
+ def begin_isolated_db_transaction(isolation)
315
+ execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}"
316
+ begin_db_transaction
317
+ rescue
318
+ # Transactions aren't supported
319
+ end
320
+
321
+ def commit_db_transaction #:nodoc:
322
+ execute "COMMIT"
323
+ rescue
324
+ # Transactions aren't supported
325
+ end
326
+
327
+ def rollback_db_transaction #:nodoc:
328
+ execute "ROLLBACK"
329
+ rescue
330
+ # Transactions aren't supported
331
+ end
332
+
333
+ def create_savepoint
334
+ execute("SAVEPOINT #{current_savepoint_name}")
335
+ end
336
+
337
+ def rollback_to_savepoint
338
+ execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
339
+ end
340
+
341
+ def release_savepoint
342
+ execute("RELEASE SAVEPOINT #{current_savepoint_name}")
343
+ end
344
+
345
+ # In the simple case, MySQL allows us to place JOINs directly into the UPDATE
346
+ # query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
347
+ # these, we must use a subquery.
348
+ def join_to_update(update, select) #:nodoc:
349
+ if select.limit || select.offset || select.orders.any?
350
+ super
351
+ else
352
+ update.table select.source
353
+ update.wheres = select.constraints
354
+ end
355
+ end
356
+
357
+ def empty_insert_statement_value
358
+ "VALUES ()"
359
+ end
360
+
361
+ # SCHEMA STATEMENTS ========================================
362
+
363
+ # Drops the database specified on the +name+ attribute
364
+ # and creates it again using the provided +options+.
365
+ def recreate_database(name, options = {})
366
+ drop_database(name)
367
+ sql = create_database(name, options)
368
+ reconnect!
369
+ sql
370
+ end
371
+
372
+ # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
373
+ # Charset defaults to utf8.
374
+ #
375
+ # Example:
376
+ # create_database 'charset_test', charset: 'latin1', collation: 'latin1_bin'
377
+ # create_database 'matt_development'
378
+ # create_database 'matt_development', charset: :big5
379
+ def create_database(name, options = {})
380
+ if options[:collation]
381
+ execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{options[:collation]}`"
382
+ else
383
+ execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}`"
384
+ end
385
+ end
386
+
387
+ # Drops a MySQL database.
388
+ #
389
+ # Example:
390
+ # drop_database('sebastian_development')
391
+ def drop_database(name) #:nodoc:
392
+ execute "DROP DATABASE IF EXISTS `#{name}`"
393
+ end
394
+
395
+ def current_database
396
+ select_value 'SELECT DATABASE() as db'
397
+ end
398
+
399
+ # Returns the database character set.
400
+ def charset
401
+ show_variable 'character_set_database'
402
+ end
403
+
404
+ # Returns the database collation strategy.
405
+ def collation
406
+ show_variable 'collation_database'
407
+ end
408
+
409
+ def tables(name = nil, database = nil, like = nil) #:nodoc:
410
+ sql = "SHOW TABLES "
411
+ sql << "IN #{quote_table_name(database)} " if database
412
+ sql << "LIKE #{quote(like)}" if like
413
+
414
+ execute_and_free(sql, 'SCHEMA') do |result|
415
+ result.collect { |field| field.first }
416
+ end
417
+ end
418
+
419
+ def table_exists?(name)
420
+ return false unless name
421
+ return true if tables(nil, nil, name).any?
422
+
423
+ name = name.to_s
424
+ schema, table = name.split('.', 2)
425
+
426
+ unless table # A table was provided without a schema
427
+ table = schema
428
+ schema = nil
429
+ end
430
+
431
+ tables(nil, schema, table).any?
432
+ end
433
+
434
+ # Returns an array of indexes for the given table.
435
+ def indexes(table_name, name = nil) #:nodoc:
436
+ indexes = []
437
+ current_index = nil
438
+ execute_and_free("SHOW KEYS FROM #{quote_table_name(table_name)}", 'SCHEMA') do |result|
439
+ each_hash(result) do |row|
440
+ if current_index != row[:Key_name]
441
+ next if row[:Key_name] == 'PRIMARY' # skip the primary key
442
+ current_index = row[:Key_name]
443
+
444
+ mysql_index_type = row[:Index_type].downcase.to_sym
445
+ index_type = INDEX_TYPES.include?(mysql_index_type) ? mysql_index_type : nil
446
+ index_using = INDEX_USINGS.include?(mysql_index_type) ? mysql_index_type : nil
447
+ indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], [], nil, nil, index_type, index_using)
448
+ end
449
+
450
+ indexes.last.columns << row[:Column_name]
451
+ indexes.last.lengths << row[:Sub_part]
452
+ end
453
+ end
454
+
455
+ indexes
456
+ end
457
+
458
+ # Returns an array of +Column+ objects for the table specified by +table_name+.
459
+ def columns(table_name)#:nodoc:
460
+ sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
461
+ execute_and_free(sql, 'SCHEMA') do |result|
462
+ each_hash(result).map do |field|
463
+ new_column(field[:Field], field[:Default], field[:Type], field[:Null] == "YES", field[:Collation], field[:Extra])
464
+ end
465
+ end
466
+ end
467
+
468
+ def create_table(table_name, options = {}) #:nodoc:
469
+ super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
470
+ end
471
+
472
+ def bulk_change_table(table_name, operations) #:nodoc:
473
+ sqls = operations.map do |command, args|
474
+ table, arguments = args.shift, args
475
+ method = :"#{command}_sql"
476
+
477
+ if respond_to?(method, true)
478
+ send(method, table, *arguments)
479
+ else
480
+ raise "Unknown method called : #{method}(#{arguments.inspect})"
481
+ end
482
+ end.flatten.join(", ")
483
+
484
+ execute("ALTER TABLE #{quote_table_name(table_name)} #{sqls}")
485
+ end
486
+
487
+ # Renames a table.
488
+ #
489
+ # Example:
490
+ # rename_table('octopuses', 'octopi')
491
+ def rename_table(table_name, new_name)
492
+ execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
493
+ rename_table_indexes(table_name, new_name)
494
+ end
495
+
496
+ def change_column_default(table_name, column_name, default)
497
+ column = column_for(table_name, column_name)
498
+ change_column table_name, column_name, column.sql_type, :default => default
499
+ end
500
+
501
+ def change_column_null(table_name, column_name, null, default = nil)
502
+ column = column_for(table_name, column_name)
503
+
504
+ unless null || default.nil?
505
+ execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
506
+ end
507
+
508
+ change_column table_name, column_name, column.sql_type, :null => null
509
+ end
510
+
511
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
512
+ execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_sql(table_name, column_name, type, options)}")
513
+ end
514
+
515
+ def rename_column(table_name, column_name, new_column_name) #:nodoc:
516
+ execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_sql(table_name, column_name, new_column_name)}")
517
+ rename_column_indexes(table_name, column_name, new_column_name)
518
+ end
519
+
520
+ def add_index(table_name, column_name, options = {}) #:nodoc:
521
+ index_name, index_type, index_columns, index_options, index_algorithm, index_using = add_index_options(table_name, column_name, options)
522
+ execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options} #{index_algorithm}"
523
+ end
524
+
525
+ # Maps logical Rails types to MySQL-specific data types.
526
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil)
527
+ case type.to_s
528
+ when 'binary'
529
+ case limit
530
+ when 0..0xfff; "varbinary(#{limit})"
531
+ when nil; "blob"
532
+ when 0x1000..0xffffffff; "blob(#{limit})"
533
+ else raise(ActiveRecordError, "No binary type has character length #{limit}")
534
+ end
535
+ when 'integer'
536
+ case limit
537
+ when 1; 'tinyint'
538
+ when 2; 'smallint'
539
+ when 3; 'mediumint'
540
+ when nil, 4, 11; 'int(11)' # compatibility with MySQL default
541
+ when 5..8; 'bigint'
542
+ else raise(ActiveRecordError, "No integer type has byte size #{limit}")
543
+ end
544
+ when 'text'
545
+ case limit
546
+ when 0..0xff; 'tinytext'
547
+ when nil, 0x100..0xffff; 'text'
548
+ when 0x10000..0xffffff; 'mediumtext'
549
+ when 0x1000000..0xffffffff; 'longtext'
550
+ else raise(ActiveRecordError, "No text type has character length #{limit}")
551
+ end
552
+ else
553
+ super
554
+ end
555
+ end
556
+
557
+ def add_column_position!(sql, options)
558
+ if options[:first]
559
+ sql << " FIRST"
560
+ elsif options[:after]
561
+ sql << " AFTER #{quote_column_name(options[:after])}"
562
+ end
563
+ end
564
+
565
+ # SHOW VARIABLES LIKE 'name'
566
+ def show_variable(name)
567
+ variables = select_all("SHOW VARIABLES LIKE '#{name}'", 'SCHEMA')
568
+ variables.first['Value'] unless variables.empty?
569
+ end
570
+
571
+ # Returns a table's primary key and belonging sequence.
572
+ def pk_and_sequence_for(table)
573
+ execute_and_free("SHOW CREATE TABLE #{quote_table_name(table)}", 'SCHEMA') do |result|
574
+ create_table = each_hash(result).first[:"Create Table"]
575
+ if create_table.to_s =~ /PRIMARY KEY\s+(?:USING\s+\w+\s+)?\((.+)\)/
576
+ keys = $1.split(",").map { |key| key.delete('`"') }
577
+ keys.length == 1 ? [keys.first, nil] : nil
578
+ else
579
+ nil
580
+ end
581
+ end
582
+ end
583
+
584
+ # Returns just a table's primary key
585
+ def primary_key(table)
586
+ pk_and_sequence = pk_and_sequence_for(table)
587
+ pk_and_sequence && pk_and_sequence.first
588
+ end
589
+
590
+ def case_sensitive_modifier(node)
591
+ Arel::Nodes::Bin.new(node)
592
+ end
593
+
594
+ def case_insensitive_comparison(table, attribute, column, value)
595
+ if column.case_sensitive?
596
+ super
597
+ else
598
+ table[attribute].eq(value)
599
+ end
600
+ end
601
+
602
+ def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
603
+ where_sql
604
+ end
605
+
606
+ def strict_mode?
607
+ self.class.type_cast_config_to_boolean(@config.fetch(:strict, true))
608
+ end
609
+
610
+ def valid_type?(type)
611
+ !native_database_types[type].nil?
612
+ end
613
+
614
+ protected
615
+
616
+ # MySQL is too stupid to create a temporary table for use subquery, so we have
617
+ # to give it some prompting in the form of a subsubquery. Ugh!
618
+ def subquery_for(key, select)
619
+ subsubselect = select.clone
620
+ subsubselect.projections = [key]
621
+
622
+ subselect = Arel::SelectManager.new(select.engine)
623
+ subselect.project Arel.sql(key.name)
624
+ subselect.from subsubselect.as('__active_record_temp')
625
+ end
626
+
627
+ def add_index_length(option_strings, column_names, options = {})
628
+ if options.is_a?(Hash) && length = options[:length]
629
+ case length
630
+ when Hash
631
+ column_names.each {|name| option_strings[name] += "(#{length[name]})" if length.has_key?(name) && length[name].present?}
632
+ when Fixnum
633
+ column_names.each {|name| option_strings[name] += "(#{length})"}
634
+ end
635
+ end
636
+
637
+ return option_strings
638
+ end
639
+
640
+ def quoted_columns_for_index(column_names, options = {})
641
+ option_strings = Hash[column_names.map {|name| [name, '']}]
642
+
643
+ # add index length
644
+ option_strings = add_index_length(option_strings, column_names, options)
645
+
646
+ # add index sort order
647
+ option_strings = add_index_sort_order(option_strings, column_names, options)
648
+
649
+ column_names.map {|name| quote_column_name(name) + option_strings[name]}
650
+ end
651
+
652
+ def translate_exception(exception, message)
653
+ case error_number(exception)
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
+ def add_column_sql(table_name, column_name, type, options = {})
664
+ add_column_sql = "ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
665
+ add_column_options!(add_column_sql, options)
666
+ add_column_position!(add_column_sql, options)
667
+ add_column_sql
668
+ end
669
+
670
+ def change_column_sql(table_name, column_name, type, options = {})
671
+ column = column_for(table_name, column_name)
672
+
673
+ unless options_include_default?(options)
674
+ options[:default] = column.default
675
+ end
676
+
677
+ unless options.has_key?(:null)
678
+ options[:null] = column.null
679
+ end
680
+
681
+ change_column_sql = "CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
682
+ add_column_options!(change_column_sql, options)
683
+ add_column_position!(change_column_sql, options)
684
+ change_column_sql
685
+ end
686
+
687
+ def rename_column_sql(table_name, column_name, new_column_name)
688
+ options = {}
689
+
690
+ if column = columns(table_name).find { |c| c.name == column_name.to_s }
691
+ options[:default] = column.default
692
+ options[:null] = column.null
693
+ options[:auto_increment] = (column.extra == "auto_increment")
694
+ else
695
+ raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
696
+ end
697
+
698
+ current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", 'SCHEMA')["Type"]
699
+ rename_column_sql = "CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}"
700
+ add_column_options!(rename_column_sql, options)
701
+ rename_column_sql
702
+ end
703
+
704
+ def remove_column_sql(table_name, column_name, type = nil, options = {})
705
+ "DROP #{quote_column_name(column_name)}"
706
+ end
707
+
708
+ def remove_columns_sql(table_name, *column_names)
709
+ column_names.map {|column_name| remove_column_sql(table_name, column_name) }
710
+ end
711
+
712
+ def add_index_sql(table_name, column_name, options = {})
713
+ index_name, index_type, index_columns = add_index_options(table_name, column_name, options)
714
+ "ADD #{index_type} INDEX #{index_name} (#{index_columns})"
715
+ end
716
+
717
+ def remove_index_sql(table_name, options = {})
718
+ index_name = index_name_for_remove(table_name, options)
719
+ "DROP INDEX #{index_name}"
720
+ end
721
+
722
+ def add_timestamps_sql(table_name)
723
+ [add_column_sql(table_name, :created_at, :datetime), add_column_sql(table_name, :updated_at, :datetime)]
724
+ end
725
+
726
+ def remove_timestamps_sql(table_name)
727
+ [remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)]
728
+ end
729
+
730
+ private
731
+
732
+ def supports_views?
733
+ version[0] >= 5
734
+ end
735
+
736
+ def column_for(table_name, column_name)
737
+ unless column = columns(table_name).find { |c| c.name == column_name.to_s }
738
+ raise "No such column: #{table_name}.#{column_name}"
739
+ end
740
+ column
741
+ end
742
+
743
+ def configure_connection
744
+ variables = @config[:variables] || {}
745
+
746
+ # By default, MySQL 'where id is null' selects the last inserted id.
747
+ # Turn this off. http://dev.rubyonrails.org/ticket/6778
748
+ variables[:sql_auto_is_null] = 0
749
+
750
+ # Increase timeout so the server doesn't disconnect us.
751
+ wait_timeout = @config[:wait_timeout]
752
+ wait_timeout = 2147483 unless wait_timeout.is_a?(Fixnum)
753
+ variables[:wait_timeout] = self.class.type_cast_config_to_integer(wait_timeout)
754
+
755
+ # Make MySQL reject illegal values rather than truncating or blanking them, see
756
+ # http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html#sqlmode_strict_all_tables
757
+ # If the user has provided another value for sql_mode, don't replace it.
758
+ if strict_mode? && !variables.has_key?(:sql_mode)
759
+ variables[:sql_mode] = 'STRICT_ALL_TABLES'
760
+ end
761
+
762
+ # NAMES does not have an equals sign, see
763
+ # http://dev.mysql.com/doc/refman/5.0/en/set-statement.html#id944430
764
+ # (trailing comma because variable_assignments will always have content)
765
+ encoding = "NAMES #{@config[:encoding]}, " if @config[:encoding]
766
+
767
+ # Gather up all of the SET variables...
768
+ variable_assignments = variables.map do |k, v|
769
+ if v == ':default' || v == :default
770
+ "@@SESSION.#{k.to_s} = DEFAULT" # Sets the value to the global or compile default
771
+ elsif !v.nil?
772
+ "@@SESSION.#{k.to_s} = #{quote(v)}"
773
+ end
774
+ # or else nil; compact to clear nils out
775
+ end.compact.join(', ')
776
+
777
+ # ...and send them all in one query
778
+ execute("SET #{encoding} #{variable_assignments}", :skip_logging)
779
+ end
780
+ end
781
+ end
782
+ end