activerecord 4.1.0 → 4.2.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 (185) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +776 -1330
  3. data/README.rdoc +15 -10
  4. data/lib/active_record/aggregations.rb +12 -8
  5. data/lib/active_record/association_relation.rb +4 -0
  6. data/lib/active_record/associations/alias_tracker.rb +14 -13
  7. data/lib/active_record/associations/association.rb +2 -2
  8. data/lib/active_record/associations/association_scope.rb +83 -43
  9. data/lib/active_record/associations/belongs_to_association.rb +15 -5
  10. data/lib/active_record/associations/builder/association.rb +15 -4
  11. data/lib/active_record/associations/builder/belongs_to.rb +7 -29
  12. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +9 -6
  13. data/lib/active_record/associations/builder/has_many.rb +1 -1
  14. data/lib/active_record/associations/builder/has_one.rb +2 -2
  15. data/lib/active_record/associations/builder/singular_association.rb +8 -1
  16. data/lib/active_record/associations/collection_association.rb +66 -29
  17. data/lib/active_record/associations/collection_proxy.rb +22 -26
  18. data/lib/active_record/associations/has_many_association.rb +65 -18
  19. data/lib/active_record/associations/has_many_through_association.rb +55 -27
  20. data/lib/active_record/associations/has_one_association.rb +0 -1
  21. data/lib/active_record/associations/join_dependency/join_association.rb +19 -15
  22. data/lib/active_record/associations/join_dependency/join_part.rb +0 -1
  23. data/lib/active_record/associations/join_dependency.rb +20 -12
  24. data/lib/active_record/associations/preloader/association.rb +34 -11
  25. data/lib/active_record/associations/preloader/through_association.rb +4 -3
  26. data/lib/active_record/associations/preloader.rb +49 -59
  27. data/lib/active_record/associations/singular_association.rb +25 -4
  28. data/lib/active_record/associations/through_association.rb +23 -14
  29. data/lib/active_record/associations.rb +171 -42
  30. data/lib/active_record/attribute.rb +149 -0
  31. data/lib/active_record/attribute_assignment.rb +18 -10
  32. data/lib/active_record/attribute_decorators.rb +66 -0
  33. data/lib/active_record/attribute_methods/before_type_cast.rb +2 -2
  34. data/lib/active_record/attribute_methods/dirty.rb +98 -44
  35. data/lib/active_record/attribute_methods/primary_key.rb +14 -8
  36. data/lib/active_record/attribute_methods/query.rb +1 -1
  37. data/lib/active_record/attribute_methods/read.rb +22 -59
  38. data/lib/active_record/attribute_methods/serialization.rb +37 -147
  39. data/lib/active_record/attribute_methods/time_zone_conversion.rb +34 -28
  40. data/lib/active_record/attribute_methods/write.rb +14 -21
  41. data/lib/active_record/attribute_methods.rb +67 -94
  42. data/lib/active_record/attribute_set/builder.rb +86 -0
  43. data/lib/active_record/attribute_set.rb +77 -0
  44. data/lib/active_record/attributes.rb +139 -0
  45. data/lib/active_record/autosave_association.rb +45 -38
  46. data/lib/active_record/base.rb +10 -20
  47. data/lib/active_record/callbacks.rb +7 -7
  48. data/lib/active_record/coders/json.rb +13 -0
  49. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +78 -52
  50. data/lib/active_record/connection_adapters/abstract/database_statements.rb +38 -59
  51. data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -0
  52. data/lib/active_record/connection_adapters/abstract/quoting.rb +59 -55
  53. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +46 -5
  54. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +126 -54
  55. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -34
  56. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +198 -64
  57. data/lib/active_record/connection_adapters/abstract/transaction.rb +126 -114
  58. data/lib/active_record/connection_adapters/abstract_adapter.rb +154 -55
  59. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +240 -135
  60. data/lib/active_record/connection_adapters/column.rb +28 -239
  61. data/lib/active_record/connection_adapters/connection_specification.rb +16 -25
  62. data/lib/active_record/connection_adapters/mysql2_adapter.rb +20 -22
  63. data/lib/active_record/connection_adapters/mysql_adapter.rb +65 -149
  64. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +15 -27
  65. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  66. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +39 -27
  67. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +99 -0
  68. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  69. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  70. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +14 -0
  71. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  72. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  73. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +27 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  75. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +17 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  78. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  79. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  80. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  81. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +15 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +97 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid.rb +29 -374
  93. data/lib/active_record/connection_adapters/postgresql/quoting.rb +55 -135
  94. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +4 -4
  95. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
  96. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +127 -38
  97. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  98. data/lib/active_record/connection_adapters/postgresql_adapter.rb +220 -466
  99. data/lib/active_record/connection_adapters/schema_cache.rb +14 -28
  100. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +66 -61
  101. data/lib/active_record/connection_handling.rb +3 -3
  102. data/lib/active_record/core.rb +143 -32
  103. data/lib/active_record/counter_cache.rb +60 -7
  104. data/lib/active_record/enum.rb +10 -11
  105. data/lib/active_record/errors.rb +49 -27
  106. data/lib/active_record/explain.rb +1 -1
  107. data/lib/active_record/fixtures.rb +56 -70
  108. data/lib/active_record/gem_version.rb +2 -2
  109. data/lib/active_record/inheritance.rb +35 -10
  110. data/lib/active_record/integration.rb +4 -4
  111. data/lib/active_record/locking/optimistic.rb +35 -17
  112. data/lib/active_record/log_subscriber.rb +1 -1
  113. data/lib/active_record/migration/command_recorder.rb +19 -2
  114. data/lib/active_record/migration/join_table.rb +1 -1
  115. data/lib/active_record/migration.rb +52 -49
  116. data/lib/active_record/model_schema.rb +49 -57
  117. data/lib/active_record/nested_attributes.rb +7 -7
  118. data/lib/active_record/null_relation.rb +19 -5
  119. data/lib/active_record/persistence.rb +50 -31
  120. data/lib/active_record/query_cache.rb +3 -3
  121. data/lib/active_record/querying.rb +10 -7
  122. data/lib/active_record/railtie.rb +14 -11
  123. data/lib/active_record/railties/databases.rake +56 -54
  124. data/lib/active_record/readonly_attributes.rb +0 -1
  125. data/lib/active_record/reflection.rb +286 -102
  126. data/lib/active_record/relation/batches.rb +0 -1
  127. data/lib/active_record/relation/calculations.rb +39 -31
  128. data/lib/active_record/relation/delegation.rb +2 -2
  129. data/lib/active_record/relation/finder_methods.rb +80 -36
  130. data/lib/active_record/relation/merger.rb +25 -30
  131. data/lib/active_record/relation/predicate_builder/array_handler.rb +31 -13
  132. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  133. data/lib/active_record/relation/predicate_builder.rb +11 -10
  134. data/lib/active_record/relation/query_methods.rb +141 -55
  135. data/lib/active_record/relation/spawn_methods.rb +3 -0
  136. data/lib/active_record/relation.rb +69 -30
  137. data/lib/active_record/result.rb +18 -7
  138. data/lib/active_record/sanitization.rb +12 -2
  139. data/lib/active_record/schema.rb +0 -1
  140. data/lib/active_record/schema_dumper.rb +58 -26
  141. data/lib/active_record/schema_migration.rb +11 -0
  142. data/lib/active_record/scoping/default.rb +8 -7
  143. data/lib/active_record/scoping/named.rb +4 -0
  144. data/lib/active_record/serializers/xml_serializer.rb +3 -7
  145. data/lib/active_record/statement_cache.rb +95 -10
  146. data/lib/active_record/store.rb +19 -10
  147. data/lib/active_record/tasks/database_tasks.rb +73 -7
  148. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -2
  149. data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
  150. data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
  151. data/lib/active_record/timestamp.rb +11 -9
  152. data/lib/active_record/transactions.rb +37 -21
  153. data/lib/active_record/type/big_integer.rb +13 -0
  154. data/lib/active_record/type/binary.rb +50 -0
  155. data/lib/active_record/type/boolean.rb +30 -0
  156. data/lib/active_record/type/date.rb +46 -0
  157. data/lib/active_record/type/date_time.rb +43 -0
  158. data/lib/active_record/type/decimal.rb +40 -0
  159. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  160. data/lib/active_record/type/decorator.rb +14 -0
  161. data/lib/active_record/type/float.rb +19 -0
  162. data/lib/active_record/type/hash_lookup_type_map.rb +17 -0
  163. data/lib/active_record/type/integer.rb +55 -0
  164. data/lib/active_record/type/mutable.rb +16 -0
  165. data/lib/active_record/type/numeric.rb +36 -0
  166. data/lib/active_record/type/serialized.rb +56 -0
  167. data/lib/active_record/type/string.rb +36 -0
  168. data/lib/active_record/type/text.rb +11 -0
  169. data/lib/active_record/type/time.rb +26 -0
  170. data/lib/active_record/type/time_value.rb +38 -0
  171. data/lib/active_record/type/type_map.rb +64 -0
  172. data/lib/active_record/type/unsigned_integer.rb +15 -0
  173. data/lib/active_record/type/value.rb +101 -0
  174. data/lib/active_record/type.rb +23 -0
  175. data/lib/active_record/validations/associated.rb +5 -3
  176. data/lib/active_record/validations/presence.rb +6 -4
  177. data/lib/active_record/validations/uniqueness.rb +11 -17
  178. data/lib/active_record/validations.rb +25 -19
  179. data/lib/active_record.rb +3 -0
  180. data/lib/rails/generators/active_record/migration/migration_generator.rb +8 -4
  181. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +4 -1
  182. data/lib/rails/generators/active_record/migration/templates/migration.rb +6 -0
  183. data/lib/rails/generators/active_record/model/templates/model.rb +1 -1
  184. metadata +65 -10
  185. data/lib/active_record/connection_adapters/postgresql/cast.rb +0 -168
@@ -1,4 +1,5 @@
1
1
  require 'arel/visitors/bind_visitor'
2
+ require 'active_support/core_ext/string/strip'
2
3
 
3
4
  module ActiveRecord
4
5
  module ConnectionAdapters
@@ -6,18 +7,35 @@ module ActiveRecord
6
7
  include Savepoints
7
8
 
8
9
  class SchemaCreation < AbstractAdapter::SchemaCreation
9
-
10
10
  def visit_AddColumn(o)
11
11
  add_column_position!(super, column_options(o))
12
12
  end
13
13
 
14
14
  private
15
+
16
+ def visit_DropForeignKey(name)
17
+ "DROP FOREIGN KEY #{name}"
18
+ end
19
+
20
+ def visit_TableDefinition(o)
21
+ name = o.name
22
+ create_sql = "CREATE#{' TEMPORARY' if o.temporary} TABLE #{quote_table_name(name)} "
23
+
24
+ statements = o.columns.map { |c| accept c }
25
+ statements.concat(o.indexes.map { |column_name, options| index_in_create(name, column_name, options) })
26
+
27
+ create_sql << "(#{statements.join(', ')}) " if statements.present?
28
+ create_sql << "#{o.options}"
29
+ create_sql << " AS #{@conn.to_sql(o.as)}" if o.as
30
+ create_sql
31
+ end
32
+
15
33
  def visit_ChangeColumnDefinition(o)
16
34
  column = o.column
17
35
  options = o.options
18
36
  sql_type = type_to_sql(o.type, options[:limit], options[:precision], options[:scale])
19
37
  change_column_sql = "CHANGE #{quote_column_name(column.name)} #{quote_column_name(options[:name])} #{sql_type}"
20
- add_column_options!(change_column_sql, options)
38
+ add_column_options!(change_column_sql, options.merge(column: column))
21
39
  add_column_position!(change_column_sql, options)
22
40
  end
23
41
 
@@ -29,6 +47,11 @@ module ActiveRecord
29
47
  end
30
48
  sql
31
49
  end
50
+
51
+ def index_in_create(table_name, column_name, options)
52
+ index_name, index_type, index_columns, index_options, index_algorithm, index_using = @conn.add_index_options(table_name, column_name, options)
53
+ "#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_options} #{index_algorithm}"
54
+ end
32
55
  end
33
56
 
34
57
  def schema_creation
@@ -38,29 +61,25 @@ module ActiveRecord
38
61
  class Column < ConnectionAdapters::Column # :nodoc:
39
62
  attr_reader :collation, :strict, :extra
40
63
 
41
- def initialize(name, default, sql_type = nil, null = true, collation = nil, strict = false, extra = "")
64
+ def initialize(name, default, cast_type, sql_type = nil, null = true, collation = nil, strict = false, extra = "")
42
65
  @strict = strict
43
66
  @collation = collation
44
67
  @extra = extra
45
- super(name, default, sql_type, null)
68
+ super(name, default, cast_type, sql_type, null)
69
+ assert_valid_default(default)
70
+ extract_default
46
71
  end
47
72
 
48
- def extract_default(default)
73
+ def extract_default
49
74
  if blob_or_text_column?
50
- if default.blank?
51
- null || strict ? nil : ''
52
- else
53
- raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
54
- end
55
- elsif missing_default_forged_as_empty_string?(default)
56
- nil
57
- else
58
- super
75
+ @default = null || strict ? nil : ''
76
+ elsif missing_default_forged_as_empty_string?(@default)
77
+ @default = nil
59
78
  end
60
79
  end
61
80
 
62
81
  def has_default?
63
- return false if blob_or_text_column? #mysql forbids defaults on blob and text columns
82
+ return false if blob_or_text_column? # MySQL forbids defaults on blob and text columns
64
83
  super
65
84
  end
66
85
 
@@ -68,53 +87,18 @@ module ActiveRecord
68
87
  sql_type =~ /blob/i || type == :text
69
88
  end
70
89
 
71
- # Must return the relevant concrete adapter
72
- def adapter
73
- raise NotImplementedError
74
- end
75
-
76
90
  def case_sensitive?
77
91
  collation && !collation.match(/_ci$/)
78
92
  end
79
93
 
80
- private
81
-
82
- def simplified_type(field_type)
83
- return :boolean if adapter.emulate_booleans && field_type.downcase.index("tinyint(1)")
84
-
85
- case field_type
86
- when /enum/i, /set/i then :string
87
- when /year/i then :integer
88
- when /bit/i then :binary
89
- else
90
- super
91
- end
94
+ def ==(other)
95
+ super &&
96
+ collation == other.collation &&
97
+ strict == other.strict &&
98
+ extra == other.extra
92
99
  end
93
100
 
94
- def extract_limit(sql_type)
95
- case sql_type
96
- when /^enum\((.+)\)/i
97
- $1.split(',').map{|enum| enum.strip.length - 2}.max
98
- when /blob|text/i
99
- case sql_type
100
- when /tiny/i
101
- 255
102
- when /medium/i
103
- 16777215
104
- when /long/i
105
- 2147483647 # mysql only allows 2^31-1, not 2^32-1, somewhat inconsistently with the tiny/medium/normal cases
106
- else
107
- super # we could return 65535 here, but we leave it undecorated by default
108
- end
109
- when /^bigint/i; 8
110
- when /^int/i; 4
111
- when /^mediumint/i; 3
112
- when /^smallint/i; 2
113
- when /^tinyint/i; 1
114
- else
115
- super
116
- end
117
- end
101
+ private
118
102
 
119
103
  # MySQL misreports NOT NULL column default when none is given.
120
104
  # We can't detect this for columns which may have a legitimate ''
@@ -126,6 +110,16 @@ module ActiveRecord
126
110
  def missing_default_forged_as_empty_string?(default)
127
111
  type != :string && !null && default == ''
128
112
  end
113
+
114
+ def assert_valid_default(default)
115
+ if blob_or_text_column? && default.present?
116
+ raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
117
+ end
118
+ end
119
+
120
+ def attributes_for_hash
121
+ super + [collation, strict, extra]
122
+ end
129
123
  end
130
124
 
131
125
  ##
@@ -155,7 +149,6 @@ module ActiveRecord
155
149
  :float => { :name => "float" },
156
150
  :decimal => { :name => "decimal" },
157
151
  :datetime => { :name => "datetime" },
158
- :timestamp => { :name => "datetime" },
159
152
  :time => { :name => "time" },
160
153
  :date => { :name => "date" },
161
154
  :binary => { :name => "blob" },
@@ -165,28 +158,21 @@ module ActiveRecord
165
158
  INDEX_TYPES = [:fulltext, :spatial]
166
159
  INDEX_USINGS = [:btree, :hash]
167
160
 
168
- class BindSubstitution < Arel::Visitors::MySQL # :nodoc:
169
- include Arel::Visitors::BindVisitor
170
- end
171
-
172
161
  # FIXME: Make the first parameter more similar for the two adapters
173
162
  def initialize(connection, logger, connection_options, config)
174
163
  super(connection, logger)
175
164
  @connection_options, @config = connection_options, config
176
165
  @quoted_column_names, @quoted_table_names = {}, {}
177
166
 
167
+ @visitor = Arel::Visitors::MySQL.new self
168
+
178
169
  if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
179
170
  @prepared_statements = true
180
- @visitor = Arel::Visitors::MySQL.new self
181
171
  else
182
- @visitor = unprepared_visitor
172
+ @prepared_statements = false
183
173
  end
184
174
  end
185
175
 
186
- def adapter_name #:nodoc:
187
- self.class::ADAPTER_NAME
188
- end
189
-
190
176
  # Returns true, since this connection adapter supports migrations.
191
177
  def supports_migrations?
192
178
  true
@@ -206,17 +192,6 @@ module ActiveRecord
206
192
  true
207
193
  end
208
194
 
209
- def type_cast(value, column)
210
- case value
211
- when TrueClass
212
- 1
213
- when FalseClass
214
- 0
215
- else
216
- super
217
- end
218
- end
219
-
220
195
  # MySQL 4 technically support transaction isolation, but it is affected by a bug
221
196
  # where the transaction level gets persisted for the whole session:
222
197
  #
@@ -225,6 +200,18 @@ module ActiveRecord
225
200
  version[0] >= 5
226
201
  end
227
202
 
203
+ def supports_indexes_in_create?
204
+ true
205
+ end
206
+
207
+ def supports_foreign_keys?
208
+ true
209
+ end
210
+
211
+ def supports_views?
212
+ version[0] >= 5
213
+ end
214
+
228
215
  def native_database_types
229
216
  NATIVE_DATABASE_TYPES
230
217
  end
@@ -241,12 +228,11 @@ module ActiveRecord
241
228
  raise NotImplementedError
242
229
  end
243
230
 
244
- # Overridden by the adapters to instantiate their specific Column type.
245
- def new_column(field, default, type, null, collation, extra = "") # :nodoc:
246
- Column.new(field, default, type, null, collation, extra)
231
+ def new_column(field, default, cast_type, sql_type = nil, null = true, collation = "", extra = "") # :nodoc:
232
+ Column.new(field, default, cast_type, sql_type, null, collation, strict_mode?, extra)
247
233
  end
248
234
 
249
- # Must return the Mysql error number from the exception, if the exception has an
235
+ # Must return the MySQL error number from the exception, if the exception has an
250
236
  # error number.
251
237
  def error_number(exception) # :nodoc:
252
238
  raise NotImplementedError
@@ -254,12 +240,9 @@ module ActiveRecord
254
240
 
255
241
  # QUOTING ==================================================
256
242
 
257
- def quote(value, column = nil)
258
- if value.kind_of?(String) && column && column.type == :binary
259
- s = value.unpack("H*")[0]
260
- "x'#{s}'"
261
- elsif value.kind_of?(BigDecimal)
262
- value.to_s("F")
243
+ def _quote(value) # :nodoc:
244
+ if value.is_a?(Type::Binary::Data)
245
+ "x'#{value.hex}'"
263
246
  else
264
247
  super
265
248
  end
@@ -277,10 +260,18 @@ module ActiveRecord
277
260
  QUOTED_TRUE
278
261
  end
279
262
 
263
+ def unquoted_true
264
+ 1
265
+ end
266
+
280
267
  def quoted_false
281
268
  QUOTED_FALSE
282
269
  end
283
270
 
271
+ def unquoted_false
272
+ 0
273
+ end
274
+
284
275
  # REFERENTIAL INTEGRITY ====================================
285
276
 
286
277
  def disable_referential_integrity #:nodoc:
@@ -294,7 +285,14 @@ module ActiveRecord
294
285
  end
295
286
  end
296
287
 
288
+ #--
297
289
  # DATABASE STATEMENTS ======================================
290
+ #++
291
+
292
+ def clear_cache!
293
+ super
294
+ reload_type_map
295
+ end
298
296
 
299
297
  # Executes the SQL statement in the context of this connection.
300
298
  def execute(sql, name = nil)
@@ -404,8 +402,12 @@ module ActiveRecord
404
402
  end
405
403
  end
406
404
 
405
+ def truncate(table_name, name = nil)
406
+ execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
407
+ end
408
+
407
409
  def table_exists?(name)
408
- return false unless name
410
+ return false unless name.present?
409
411
  return true if tables(nil, nil, name).any?
410
412
 
411
413
  name = name.to_s
@@ -449,7 +451,9 @@ module ActiveRecord
449
451
  execute_and_free(sql, 'SCHEMA') do |result|
450
452
  each_hash(result).map do |field|
451
453
  field_name = set_field_encoding(field[:Field])
452
- new_column(field_name, field[:Default], field[:Type], field[:Null] == "YES", field[:Collation], field[:Extra])
454
+ sql_type = field[:Type]
455
+ cast_type = lookup_cast_type(sql_type)
456
+ new_column(field_name, field[:Default], cast_type, sql_type, field[:Null] == "YES", field[:Collation], field[:Extra])
453
457
  end
454
458
  end
455
459
  end
@@ -459,7 +463,7 @@ module ActiveRecord
459
463
  end
460
464
 
461
465
  def bulk_change_table(table_name, operations) #:nodoc:
462
- sqls = operations.map do |command, args|
466
+ sqls = operations.flat_map do |command, args|
463
467
  table, arguments = args.shift, args
464
468
  method = :"#{command}_sql"
465
469
 
@@ -468,7 +472,7 @@ module ActiveRecord
468
472
  else
469
473
  raise "Unknown method called : #{method}(#{arguments.inspect})"
470
474
  end
471
- end.flatten.join(", ")
475
+ end.join(", ")
472
476
 
473
477
  execute("ALTER TABLE #{quote_table_name(table_name)} #{sqls}")
474
478
  end
@@ -483,18 +487,18 @@ module ActiveRecord
483
487
  end
484
488
 
485
489
  def drop_table(table_name, options = {})
486
- execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE #{quote_table_name(table_name)}"
490
+ execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
487
491
  end
488
492
 
489
493
  def rename_index(table_name, old_name, new_name)
490
- if (version[0] == 5 && version[1] >= 7) || version[0] >= 6
494
+ if supports_rename_index?
491
495
  execute "ALTER TABLE #{quote_table_name(table_name)} RENAME INDEX #{quote_table_name(old_name)} TO #{quote_table_name(new_name)}"
492
496
  else
493
497
  super
494
498
  end
495
499
  end
496
500
 
497
- def change_column_default(table_name, column_name, default)
501
+ def change_column_default(table_name, column_name, default) #:nodoc:
498
502
  column = column_for(table_name, column_name)
499
503
  change_column table_name, column_name, column.sql_type, :default => default
500
504
  end
@@ -523,6 +527,34 @@ module ActiveRecord
523
527
  execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options} #{index_algorithm}"
524
528
  end
525
529
 
530
+ def foreign_keys(table_name)
531
+ fk_info = select_all <<-SQL.strip_heredoc
532
+ SELECT fk.referenced_table_name as 'to_table'
533
+ ,fk.referenced_column_name as 'primary_key'
534
+ ,fk.column_name as 'column'
535
+ ,fk.constraint_name as 'name'
536
+ FROM information_schema.key_column_usage fk
537
+ WHERE fk.referenced_column_name is not null
538
+ AND fk.table_schema = '#{@config[:database]}'
539
+ AND fk.table_name = '#{table_name}'
540
+ SQL
541
+
542
+ create_table_info = select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
543
+
544
+ fk_info.map do |row|
545
+ options = {
546
+ column: row['column'],
547
+ name: row['name'],
548
+ primary_key: row['primary_key']
549
+ }
550
+
551
+ options[:on_update] = extract_foreign_key_action(create_table_info, row['name'], "UPDATE")
552
+ options[:on_delete] = extract_foreign_key_action(create_table_info, row['name'], "DELETE")
553
+
554
+ ForeignKeyDefinition.new(table_name, row['to_table'], options)
555
+ end
556
+ end
557
+
526
558
  # Maps logical Rails types to MySQL-specific data types.
527
559
  def type_to_sql(type, limit = nil, precision = nil, scale = nil)
528
560
  case type.to_s
@@ -555,14 +587,6 @@ module ActiveRecord
555
587
  end
556
588
  end
557
589
 
558
- def add_column_position!(sql, options)
559
- if options[:first]
560
- sql << " FIRST"
561
- elsif options[:after]
562
- sql << " AFTER #{quote_column_name(options[:after])}"
563
- end
564
- end
565
-
566
590
  # SHOW VARIABLES LIKE 'name'
567
591
  def show_variable(name)
568
592
  variables = select_all("SHOW VARIABLES LIKE '#{name}'", 'SCHEMA')
@@ -588,10 +612,19 @@ module ActiveRecord
588
612
  pk_and_sequence && pk_and_sequence.first
589
613
  end
590
614
 
591
- def case_sensitive_modifier(node)
615
+ def case_sensitive_modifier(node, table_attribute)
616
+ node = Arel::Nodes.build_quoted node, table_attribute
592
617
  Arel::Nodes::Bin.new(node)
593
618
  end
594
619
 
620
+ def case_sensitive_comparison(table, attribute, column, value)
621
+ if column.case_sensitive?
622
+ table[attribute].eq(value)
623
+ else
624
+ super
625
+ end
626
+ end
627
+
595
628
  def case_insensitive_comparison(table, attribute, column, value)
596
629
  if column.case_sensitive?
597
630
  super
@@ -600,10 +633,6 @@ module ActiveRecord
600
633
  end
601
634
  end
602
635
 
603
- def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
604
- where_sql
605
- end
606
-
607
636
  def strict_mode?
608
637
  self.class.type_cast_config_to_boolean(@config.fetch(:strict, true))
609
638
  end
@@ -614,6 +643,50 @@ module ActiveRecord
614
643
 
615
644
  protected
616
645
 
646
+ def initialize_type_map(m) # :nodoc:
647
+ super
648
+
649
+ register_class_with_limit m, %r(char)i, MysqlString
650
+
651
+ m.register_type %r(tinytext)i, Type::Text.new(limit: 2**8 - 1)
652
+ m.register_type %r(tinyblob)i, Type::Binary.new(limit: 2**8 - 1)
653
+ m.register_type %r(text)i, Type::Text.new(limit: 2**16 - 1)
654
+ m.register_type %r(blob)i, Type::Binary.new(limit: 2**16 - 1)
655
+ m.register_type %r(mediumtext)i, Type::Text.new(limit: 2**24 - 1)
656
+ m.register_type %r(mediumblob)i, Type::Binary.new(limit: 2**24 - 1)
657
+ m.register_type %r(longtext)i, Type::Text.new(limit: 2**32 - 1)
658
+ m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1)
659
+ m.register_type %r(^float)i, Type::Float.new(limit: 24)
660
+ m.register_type %r(^double)i, Type::Float.new(limit: 53)
661
+
662
+ register_integer_type m, %r(^bigint)i, limit: 8
663
+ register_integer_type m, %r(^int)i, limit: 4
664
+ register_integer_type m, %r(^mediumint)i, limit: 3
665
+ register_integer_type m, %r(^smallint)i, limit: 2
666
+ register_integer_type m, %r(^tinyint)i, limit: 1
667
+
668
+ m.alias_type %r(tinyint\(1\))i, 'boolean' if emulate_booleans
669
+ m.alias_type %r(set)i, 'varchar'
670
+ m.alias_type %r(year)i, 'integer'
671
+ m.alias_type %r(bit)i, 'binary'
672
+
673
+ m.register_type(%r(enum)i) do |sql_type|
674
+ limit = sql_type[/^enum\((.+)\)/i, 1]
675
+ .split(',').map{|enum| enum.strip.length - 2}.max
676
+ MysqlString.new(limit: limit)
677
+ end
678
+ end
679
+
680
+ def register_integer_type(mapping, key, options) # :nodoc:
681
+ mapping.register_type(key) do |sql_type|
682
+ if /unsigned/i =~ sql_type
683
+ Type::UnsignedInteger.new(options)
684
+ else
685
+ Type::Integer.new(options)
686
+ end
687
+ end
688
+ end
689
+
617
690
  # MySQL is too stupid to create a temporary table for use subquery, so we have
618
691
  # to give it some prompting in the form of a subsubquery. Ugh!
619
692
  def subquery_for(key, select)
@@ -683,15 +756,13 @@ module ActiveRecord
683
756
  end
684
757
 
685
758
  def rename_column_sql(table_name, column_name, new_column_name)
686
- options = { name: new_column_name }
687
-
688
- if column = columns(table_name).find { |c| c.name == column_name.to_s }
689
- options[:default] = column.default
690
- options[:null] = column.null
691
- options[:auto_increment] = (column.extra == "auto_increment")
692
- else
693
- raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
694
- end
759
+ column = column_for(table_name, column_name)
760
+ options = {
761
+ name: new_column_name,
762
+ default: column.default,
763
+ null: column.null,
764
+ auto_increment: column.extra == "auto_increment"
765
+ }
695
766
 
696
767
  current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", 'SCHEMA')["Type"]
697
768
  schema_creation.accept ChangeColumnDefinition.new column, current_type, options
@@ -715,57 +786,62 @@ module ActiveRecord
715
786
  "DROP INDEX #{index_name}"
716
787
  end
717
788
 
718
- def add_timestamps_sql(table_name)
719
- [add_column_sql(table_name, :created_at, :datetime), add_column_sql(table_name, :updated_at, :datetime)]
789
+ def add_timestamps_sql(table_name, options = {})
790
+ [add_column_sql(table_name, :created_at, :datetime, options), add_column_sql(table_name, :updated_at, :datetime, options)]
720
791
  end
721
792
 
722
- def remove_timestamps_sql(table_name)
793
+ def remove_timestamps_sql(table_name, options = {})
723
794
  [remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)]
724
795
  end
725
796
 
726
797
  private
727
798
 
728
- def supports_views?
729
- version[0] >= 5
799
+ def version
800
+ @version ||= full_version.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
730
801
  end
731
802
 
732
- def column_for(table_name, column_name)
733
- unless column = columns(table_name).find { |c| c.name == column_name.to_s }
734
- raise "No such column: #{table_name}.#{column_name}"
735
- end
736
- column
803
+ def mariadb?
804
+ full_version =~ /mariadb/i
805
+ end
806
+
807
+ def supports_rename_index?
808
+ mariadb? ? false : (version[0] == 5 && version[1] >= 7) || version[0] >= 6
737
809
  end
738
810
 
739
811
  def configure_connection
740
- variables = @config[:variables] || {}
812
+ variables = @config.fetch(:variables, {}).stringify_keys
741
813
 
742
814
  # By default, MySQL 'where id is null' selects the last inserted id.
743
815
  # Turn this off. http://dev.rubyonrails.org/ticket/6778
744
- variables[:sql_auto_is_null] = 0
816
+ variables['sql_auto_is_null'] = 0
745
817
 
746
818
  # Increase timeout so the server doesn't disconnect us.
747
819
  wait_timeout = @config[:wait_timeout]
748
820
  wait_timeout = 2147483 unless wait_timeout.is_a?(Fixnum)
749
- variables[:wait_timeout] = self.class.type_cast_config_to_integer(wait_timeout)
821
+ variables['wait_timeout'] = self.class.type_cast_config_to_integer(wait_timeout)
750
822
 
751
823
  # Make MySQL reject illegal values rather than truncating or blanking them, see
752
824
  # http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html#sqlmode_strict_all_tables
753
825
  # If the user has provided another value for sql_mode, don't replace it.
754
- if strict_mode? && !variables.has_key?(:sql_mode)
755
- variables[:sql_mode] = 'STRICT_ALL_TABLES'
826
+ unless variables.has_key?('sql_mode')
827
+ variables['sql_mode'] = strict_mode? ? 'STRICT_ALL_TABLES' : ''
756
828
  end
757
829
 
758
830
  # NAMES does not have an equals sign, see
759
831
  # http://dev.mysql.com/doc/refman/5.0/en/set-statement.html#id944430
760
832
  # (trailing comma because variable_assignments will always have content)
761
- encoding = "NAMES #{@config[:encoding]}, " if @config[:encoding]
833
+ if @config[:encoding]
834
+ encoding = "NAMES #{@config[:encoding]}"
835
+ encoding << " COLLATE #{@config[:collation]}" if @config[:collation]
836
+ encoding << ", "
837
+ end
762
838
 
763
839
  # Gather up all of the SET variables...
764
840
  variable_assignments = variables.map do |k, v|
765
841
  if v == ':default' || v == :default
766
- "@@SESSION.#{k.to_s} = DEFAULT" # Sets the value to the global or compile default
842
+ "@@SESSION.#{k} = DEFAULT" # Sets the value to the global or compile default
767
843
  elsif !v.nil?
768
- "@@SESSION.#{k.to_s} = #{quote(v)}"
844
+ "@@SESSION.#{k} = #{quote(v)}"
769
845
  end
770
846
  # or else nil; compact to clear nils out
771
847
  end.compact.join(', ')
@@ -773,6 +849,35 @@ module ActiveRecord
773
849
  # ...and send them all in one query
774
850
  @connection.query "SET #{encoding} #{variable_assignments}"
775
851
  end
852
+
853
+ def extract_foreign_key_action(structure, name, action) # :nodoc:
854
+ if structure =~ /CONSTRAINT #{quote_column_name(name)} FOREIGN KEY .* REFERENCES .* ON #{action} (CASCADE|SET NULL|RESTRICT)/
855
+ case $1
856
+ when 'CASCADE'; :cascade
857
+ when 'SET NULL'; :nullify
858
+ end
859
+ end
860
+ end
861
+
862
+ class MysqlString < Type::String # :nodoc:
863
+ def type_cast_for_database(value)
864
+ case value
865
+ when true then "1"
866
+ when false then "0"
867
+ else super
868
+ end
869
+ end
870
+
871
+ private
872
+
873
+ def cast_value(value)
874
+ case value
875
+ when true then "1"
876
+ when false then "0"
877
+ else super
878
+ end
879
+ end
880
+ end
776
881
  end
777
882
  end
778
883
  end