activerecord 4.1.16 → 4.2.0.beta1

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 (167) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +634 -2185
  3. data/README.rdoc +15 -10
  4. data/lib/active_record.rb +2 -1
  5. data/lib/active_record/aggregations.rb +12 -8
  6. data/lib/active_record/associations.rb +58 -33
  7. data/lib/active_record/associations/association.rb +1 -1
  8. data/lib/active_record/associations/association_scope.rb +53 -21
  9. data/lib/active_record/associations/belongs_to_association.rb +15 -5
  10. data/lib/active_record/associations/builder/association.rb +16 -5
  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 +2 -11
  13. data/lib/active_record/associations/builder/has_one.rb +2 -2
  14. data/lib/active_record/associations/builder/singular_association.rb +8 -1
  15. data/lib/active_record/associations/collection_association.rb +32 -44
  16. data/lib/active_record/associations/collection_proxy.rb +1 -10
  17. data/lib/active_record/associations/has_many_association.rb +60 -14
  18. data/lib/active_record/associations/has_many_through_association.rb +34 -23
  19. data/lib/active_record/associations/has_one_association.rb +0 -1
  20. data/lib/active_record/associations/join_dependency.rb +7 -9
  21. data/lib/active_record/associations/join_dependency/join_association.rb +18 -14
  22. data/lib/active_record/associations/preloader.rb +2 -2
  23. data/lib/active_record/associations/preloader/association.rb +9 -5
  24. data/lib/active_record/associations/preloader/through_association.rb +3 -3
  25. data/lib/active_record/associations/singular_association.rb +16 -1
  26. data/lib/active_record/associations/through_association.rb +6 -22
  27. data/lib/active_record/attribute.rb +131 -0
  28. data/lib/active_record/attribute_assignment.rb +19 -11
  29. data/lib/active_record/attribute_decorators.rb +66 -0
  30. data/lib/active_record/attribute_methods.rb +53 -90
  31. data/lib/active_record/attribute_methods/before_type_cast.rb +2 -2
  32. data/lib/active_record/attribute_methods/dirty.rb +85 -42
  33. data/lib/active_record/attribute_methods/primary_key.rb +6 -8
  34. data/lib/active_record/attribute_methods/read.rb +14 -57
  35. data/lib/active_record/attribute_methods/serialization.rb +12 -146
  36. data/lib/active_record/attribute_methods/time_zone_conversion.rb +32 -40
  37. data/lib/active_record/attribute_methods/write.rb +8 -23
  38. data/lib/active_record/attribute_set.rb +77 -0
  39. data/lib/active_record/attribute_set/builder.rb +32 -0
  40. data/lib/active_record/attributes.rb +122 -0
  41. data/lib/active_record/autosave_association.rb +11 -21
  42. data/lib/active_record/base.rb +9 -19
  43. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +69 -45
  44. data/lib/active_record/connection_adapters/abstract/database_statements.rb +22 -42
  45. data/lib/active_record/connection_adapters/abstract/quoting.rb +59 -60
  46. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +37 -2
  47. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +102 -21
  48. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +9 -33
  49. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +178 -55
  50. data/lib/active_record/connection_adapters/abstract/transaction.rb +120 -115
  51. data/lib/active_record/connection_adapters/abstract_adapter.rb +143 -57
  52. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +156 -107
  53. data/lib/active_record/connection_adapters/column.rb +13 -244
  54. data/lib/active_record/connection_adapters/connection_specification.rb +6 -20
  55. data/lib/active_record/connection_adapters/mysql2_adapter.rb +12 -15
  56. data/lib/active_record/connection_adapters/mysql_adapter.rb +55 -143
  57. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +15 -27
  58. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  59. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +39 -20
  60. data/lib/active_record/connection_adapters/postgresql/oid.rb +29 -388
  61. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +96 -0
  62. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  63. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  64. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +14 -0
  65. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  66. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  67. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +27 -0
  68. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  69. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +17 -0
  70. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  71. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  72. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  73. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  75. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  78. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  79. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +76 -0
  80. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +15 -0
  81. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +85 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +26 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  86. data/lib/active_record/connection_adapters/postgresql/quoting.rb +42 -122
  87. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +4 -4
  88. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +154 -0
  89. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +86 -34
  90. data/lib/active_record/connection_adapters/postgresql/utils.rb +66 -0
  91. data/lib/active_record/connection_adapters/postgresql_adapter.rb +188 -452
  92. data/lib/active_record/connection_adapters/schema_cache.rb +14 -28
  93. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +54 -47
  94. data/lib/active_record/connection_handling.rb +1 -1
  95. data/lib/active_record/core.rb +119 -22
  96. data/lib/active_record/counter_cache.rb +60 -6
  97. data/lib/active_record/enum.rb +9 -10
  98. data/lib/active_record/errors.rb +27 -26
  99. data/lib/active_record/explain.rb +1 -1
  100. data/lib/active_record/fixtures.rb +52 -45
  101. data/lib/active_record/gem_version.rb +3 -3
  102. data/lib/active_record/inheritance.rb +33 -8
  103. data/lib/active_record/integration.rb +4 -4
  104. data/lib/active_record/locking/optimistic.rb +34 -16
  105. data/lib/active_record/migration.rb +22 -32
  106. data/lib/active_record/migration/command_recorder.rb +19 -2
  107. data/lib/active_record/migration/join_table.rb +1 -1
  108. data/lib/active_record/model_schema.rb +39 -48
  109. data/lib/active_record/nested_attributes.rb +8 -18
  110. data/lib/active_record/persistence.rb +39 -22
  111. data/lib/active_record/query_cache.rb +3 -3
  112. data/lib/active_record/querying.rb +1 -8
  113. data/lib/active_record/railtie.rb +17 -10
  114. data/lib/active_record/railties/databases.rake +47 -42
  115. data/lib/active_record/readonly_attributes.rb +0 -1
  116. data/lib/active_record/reflection.rb +225 -92
  117. data/lib/active_record/relation.rb +35 -11
  118. data/lib/active_record/relation/batches.rb +0 -2
  119. data/lib/active_record/relation/calculations.rb +28 -32
  120. data/lib/active_record/relation/delegation.rb +1 -1
  121. data/lib/active_record/relation/finder_methods.rb +42 -20
  122. data/lib/active_record/relation/merger.rb +0 -1
  123. data/lib/active_record/relation/predicate_builder.rb +1 -22
  124. data/lib/active_record/relation/predicate_builder/array_handler.rb +16 -11
  125. data/lib/active_record/relation/predicate_builder/relation_handler.rb +0 -4
  126. data/lib/active_record/relation/query_methods.rb +98 -62
  127. data/lib/active_record/relation/spawn_methods.rb +6 -7
  128. data/lib/active_record/result.rb +16 -9
  129. data/lib/active_record/sanitization.rb +8 -1
  130. data/lib/active_record/schema.rb +0 -1
  131. data/lib/active_record/schema_dumper.rb +51 -9
  132. data/lib/active_record/schema_migration.rb +4 -0
  133. data/lib/active_record/scoping/default.rb +5 -4
  134. data/lib/active_record/serializers/xml_serializer.rb +3 -7
  135. data/lib/active_record/statement_cache.rb +79 -5
  136. data/lib/active_record/store.rb +5 -5
  137. data/lib/active_record/tasks/database_tasks.rb +37 -5
  138. data/lib/active_record/tasks/mysql_database_tasks.rb +10 -16
  139. data/lib/active_record/tasks/postgresql_database_tasks.rb +2 -2
  140. data/lib/active_record/timestamp.rb +9 -7
  141. data/lib/active_record/transactions.rb +35 -21
  142. data/lib/active_record/type.rb +20 -0
  143. data/lib/active_record/type/binary.rb +40 -0
  144. data/lib/active_record/type/boolean.rb +19 -0
  145. data/lib/active_record/type/date.rb +46 -0
  146. data/lib/active_record/type/date_time.rb +43 -0
  147. data/lib/active_record/type/decimal.rb +40 -0
  148. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  149. data/lib/active_record/type/float.rb +19 -0
  150. data/lib/active_record/type/hash_lookup_type_map.rb +19 -0
  151. data/lib/active_record/type/integer.rb +23 -0
  152. data/lib/active_record/type/mutable.rb +16 -0
  153. data/lib/active_record/type/numeric.rb +36 -0
  154. data/lib/active_record/type/serialized.rb +51 -0
  155. data/lib/active_record/type/string.rb +36 -0
  156. data/lib/active_record/type/text.rb +11 -0
  157. data/lib/active_record/type/time.rb +26 -0
  158. data/lib/active_record/type/time_value.rb +38 -0
  159. data/lib/active_record/type/type_map.rb +48 -0
  160. data/lib/active_record/type/value.rb +101 -0
  161. data/lib/active_record/validations.rb +21 -16
  162. data/lib/active_record/validations/uniqueness.rb +9 -23
  163. data/lib/rails/generators/active_record/migration/migration_generator.rb +8 -4
  164. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +1 -1
  165. data/lib/rails/generators/active_record/model/templates/model.rb +1 -1
  166. metadata +71 -14
  167. data/lib/active_record/connection_adapters/postgresql/cast.rb +0 -168
@@ -6,12 +6,29 @@ module ActiveRecord
6
6
  include Savepoints
7
7
 
8
8
  class SchemaCreation < AbstractAdapter::SchemaCreation
9
-
10
9
  def visit_AddColumn(o)
11
10
  add_column_position!(super, column_options(o))
12
11
  end
13
12
 
14
13
  private
14
+
15
+ def visit_DropForeignKey(name)
16
+ "DROP FOREIGN KEY #{name}"
17
+ end
18
+
19
+ def visit_TableDefinition(o)
20
+ name = o.name
21
+ create_sql = "CREATE#{' TEMPORARY' if o.temporary} TABLE #{quote_table_name(name)} "
22
+
23
+ statements = o.columns.map { |c| accept c }
24
+ statements.concat(o.indexes.map { |column_name, options| index_in_create(name, column_name, options) })
25
+
26
+ create_sql << "(#{statements.join(', ')}) " if statements.present?
27
+ create_sql << "#{o.options}"
28
+ create_sql << " AS #{@conn.to_sql(o.as)}" if o.as
29
+ create_sql
30
+ end
31
+
15
32
  def visit_ChangeColumnDefinition(o)
16
33
  column = o.column
17
34
  options = o.options
@@ -29,6 +46,11 @@ module ActiveRecord
29
46
  end
30
47
  sql
31
48
  end
49
+
50
+ def index_in_create(table_name, column_name, options)
51
+ index_name, index_type, index_columns, index_options, index_algorithm, index_using = @conn.add_index_options(table_name, column_name, options)
52
+ "#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_options} #{index_algorithm}"
53
+ end
32
54
  end
33
55
 
34
56
  def schema_creation
@@ -38,29 +60,25 @@ module ActiveRecord
38
60
  class Column < ConnectionAdapters::Column # :nodoc:
39
61
  attr_reader :collation, :strict, :extra
40
62
 
41
- def initialize(name, default, sql_type = nil, null = true, collation = nil, strict = false, extra = "")
63
+ def initialize(name, default, cast_type, sql_type = nil, null = true, collation = nil, strict = false, extra = "")
42
64
  @strict = strict
43
65
  @collation = collation
44
66
  @extra = extra
45
- super(name, default, sql_type, null)
67
+ super(name, default, cast_type, sql_type, null)
68
+ assert_valid_default(default)
69
+ extract_default
46
70
  end
47
71
 
48
- def extract_default(default)
72
+ def extract_default
49
73
  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
74
+ @default = null || strict ? nil : ''
75
+ elsif missing_default_forged_as_empty_string?(@default)
76
+ @default = nil
59
77
  end
60
78
  end
61
79
 
62
80
  def has_default?
63
- return false if blob_or_text_column? #mysql forbids defaults on blob and text columns
81
+ return false if blob_or_text_column? # MySQL forbids defaults on blob and text columns
64
82
  super
65
83
  end
66
84
 
@@ -68,56 +86,12 @@ module ActiveRecord
68
86
  sql_type =~ /blob/i || type == :text
69
87
  end
70
88
 
71
- # Must return the relevant concrete adapter
72
- def adapter
73
- raise NotImplementedError
74
- end
75
-
76
89
  def case_sensitive?
77
90
  collation && !collation.match(/_ci$/)
78
91
  end
79
92
 
80
93
  private
81
94
 
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
92
- end
93
-
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
- when /^float/i; 24
115
- when /^double/i; 53
116
- else
117
- super
118
- end
119
- end
120
-
121
95
  # MySQL misreports NOT NULL column default when none is given.
122
96
  # We can't detect this for columns which may have a legitimate ''
123
97
  # default (string) but we can for others (integer, datetime, boolean,
@@ -128,6 +102,12 @@ module ActiveRecord
128
102
  def missing_default_forged_as_empty_string?(default)
129
103
  type != :string && !null && default == ''
130
104
  end
105
+
106
+ def assert_valid_default(default)
107
+ if blob_or_text_column? && default.present?
108
+ raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
109
+ end
110
+ end
131
111
  end
132
112
 
133
113
  ##
@@ -157,7 +137,6 @@ module ActiveRecord
157
137
  :float => { :name => "float" },
158
138
  :decimal => { :name => "decimal" },
159
139
  :datetime => { :name => "datetime" },
160
- :timestamp => { :name => "datetime" },
161
140
  :time => { :name => "time" },
162
141
  :date => { :name => "date" },
163
142
  :binary => { :name => "blob" },
@@ -167,21 +146,18 @@ module ActiveRecord
167
146
  INDEX_TYPES = [:fulltext, :spatial]
168
147
  INDEX_USINGS = [:btree, :hash]
169
148
 
170
- class BindSubstitution < Arel::Visitors::MySQL # :nodoc:
171
- include Arel::Visitors::BindVisitor
172
- end
173
-
174
149
  # FIXME: Make the first parameter more similar for the two adapters
175
150
  def initialize(connection, logger, connection_options, config)
176
151
  super(connection, logger)
177
152
  @connection_options, @config = connection_options, config
178
153
  @quoted_column_names, @quoted_table_names = {}, {}
179
154
 
155
+ @visitor = Arel::Visitors::MySQL.new self
156
+
180
157
  if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
181
158
  @prepared_statements = true
182
- @visitor = Arel::Visitors::MySQL.new self
183
159
  else
184
- @visitor = unprepared_visitor
160
+ @prepared_statements = false
185
161
  end
186
162
  end
187
163
 
@@ -208,17 +184,6 @@ module ActiveRecord
208
184
  true
209
185
  end
210
186
 
211
- def type_cast(value, column)
212
- case value
213
- when TrueClass
214
- 1
215
- when FalseClass
216
- 0
217
- else
218
- super
219
- end
220
- end
221
-
222
187
  # MySQL 4 technically support transaction isolation, but it is affected by a bug
223
188
  # where the transaction level gets persisted for the whole session:
224
189
  #
@@ -227,6 +192,14 @@ module ActiveRecord
227
192
  version[0] >= 5
228
193
  end
229
194
 
195
+ def supports_indexes_in_create?
196
+ true
197
+ end
198
+
199
+ def supports_foreign_keys?
200
+ true
201
+ end
202
+
230
203
  def native_database_types
231
204
  NATIVE_DATABASE_TYPES
232
205
  end
@@ -243,12 +216,11 @@ module ActiveRecord
243
216
  raise NotImplementedError
244
217
  end
245
218
 
246
- # Overridden by the adapters to instantiate their specific Column type.
247
- def new_column(field, default, type, null, collation, extra = "") # :nodoc:
248
- Column.new(field, default, type, null, collation, extra)
219
+ def new_column(field, default, cast_type, sql_type = nil, null = true, collation = "", extra = "") # :nodoc:
220
+ Column.new(field, default, cast_type, sql_type, null, collation, strict_mode?, extra)
249
221
  end
250
222
 
251
- # Must return the Mysql error number from the exception, if the exception has an
223
+ # Must return the MySQL error number from the exception, if the exception has an
252
224
  # error number.
253
225
  def error_number(exception) # :nodoc:
254
226
  raise NotImplementedError
@@ -256,12 +228,9 @@ module ActiveRecord
256
228
 
257
229
  # QUOTING ==================================================
258
230
 
259
- def quote(value, column = nil)
260
- if value.kind_of?(String) && column && column.type == :binary
261
- s = value.unpack("H*")[0]
262
- "x'#{s}'"
263
- elsif value.kind_of?(BigDecimal)
264
- value.to_s("F")
231
+ def _quote(value) # :nodoc:
232
+ if value.is_a?(Type::Binary::Data)
233
+ "x'#{value.hex}'"
265
234
  else
266
235
  super
267
236
  end
@@ -279,10 +248,18 @@ module ActiveRecord
279
248
  QUOTED_TRUE
280
249
  end
281
250
 
251
+ def unquoted_true
252
+ 1
253
+ end
254
+
282
255
  def quoted_false
283
256
  QUOTED_FALSE
284
257
  end
285
258
 
259
+ def unquoted_false
260
+ 0
261
+ end
262
+
286
263
  # REFERENTIAL INTEGRITY ====================================
287
264
 
288
265
  def disable_referential_integrity #:nodoc:
@@ -298,6 +275,11 @@ module ActiveRecord
298
275
 
299
276
  # DATABASE STATEMENTS ======================================
300
277
 
278
+ def clear_cache!
279
+ super
280
+ reload_type_map
281
+ end
282
+
301
283
  # Executes the SQL statement in the context of this connection.
302
284
  def execute(sql, name = nil)
303
285
  log(sql, name) { @connection.query(sql) }
@@ -407,7 +389,7 @@ module ActiveRecord
407
389
  end
408
390
 
409
391
  def table_exists?(name)
410
- return false unless name
392
+ return false unless name.present?
411
393
  return true if tables(nil, nil, name).any?
412
394
 
413
395
  name = name.to_s
@@ -451,7 +433,9 @@ module ActiveRecord
451
433
  execute_and_free(sql, 'SCHEMA') do |result|
452
434
  each_hash(result).map do |field|
453
435
  field_name = set_field_encoding(field[:Field])
454
- new_column(field_name, field[:Default], field[:Type], field[:Null] == "YES", field[:Collation], field[:Extra])
436
+ sql_type = field[:Type]
437
+ cast_type = lookup_cast_type(sql_type)
438
+ new_column(field_name, field[:Default], cast_type, sql_type, field[:Null] == "YES", field[:Collation], field[:Extra])
455
439
  end
456
440
  end
457
441
  end
@@ -461,7 +445,7 @@ module ActiveRecord
461
445
  end
462
446
 
463
447
  def bulk_change_table(table_name, operations) #:nodoc:
464
- sqls = operations.map do |command, args|
448
+ sqls = operations.flat_map do |command, args|
465
449
  table, arguments = args.shift, args
466
450
  method = :"#{command}_sql"
467
451
 
@@ -470,7 +454,7 @@ module ActiveRecord
470
454
  else
471
455
  raise "Unknown method called : #{method}(#{arguments.inspect})"
472
456
  end
473
- end.flatten.join(", ")
457
+ end.join(", ")
474
458
 
475
459
  execute("ALTER TABLE #{quote_table_name(table_name)} #{sqls}")
476
460
  end
@@ -525,6 +509,34 @@ module ActiveRecord
525
509
  execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options} #{index_algorithm}"
526
510
  end
527
511
 
512
+ def foreign_keys(table_name)
513
+ fk_info = select_all <<-SQL.strip_heredoc
514
+ SELECT fk.referenced_table_name as 'to_table'
515
+ ,fk.referenced_column_name as 'primary_key'
516
+ ,fk.column_name as 'column'
517
+ ,fk.constraint_name as 'name'
518
+ FROM information_schema.key_column_usage fk
519
+ WHERE fk.referenced_column_name is not null
520
+ AND fk.table_schema = '#{@config[:database]}'
521
+ AND fk.table_name = '#{table_name}'
522
+ SQL
523
+
524
+ create_table_info = select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
525
+
526
+ fk_info.map do |row|
527
+ options = {
528
+ column: row['column'],
529
+ name: row['name'],
530
+ primary_key: row['primary_key']
531
+ }
532
+
533
+ options[:on_update] = extract_foreign_key_action(create_table_info, row['name'], "UPDATE")
534
+ options[:on_delete] = extract_foreign_key_action(create_table_info, row['name'], "DELETE")
535
+
536
+ ForeignKeyDefinition.new(table_name, row['to_table'], options)
537
+ end
538
+ end
539
+
528
540
  # Maps logical Rails types to MySQL-specific data types.
529
541
  def type_to_sql(type, limit = nil, precision = nil, scale = nil)
530
542
  case type.to_s
@@ -590,10 +602,19 @@ module ActiveRecord
590
602
  pk_and_sequence && pk_and_sequence.first
591
603
  end
592
604
 
593
- def case_sensitive_modifier(node)
605
+ def case_sensitive_modifier(node, table_attribute)
606
+ node = Arel::Nodes.build_quoted node, table_attribute
594
607
  Arel::Nodes::Bin.new(node)
595
608
  end
596
609
 
610
+ def case_sensitive_comparison(table, attribute, column, value)
611
+ if column.case_sensitive?
612
+ table[attribute].eq(value)
613
+ else
614
+ super
615
+ end
616
+ end
617
+
597
618
  def case_insensitive_comparison(table, attribute, column, value)
598
619
  if column.case_sensitive?
599
620
  super
@@ -616,6 +637,34 @@ module ActiveRecord
616
637
 
617
638
  protected
618
639
 
640
+ def initialize_type_map(m) # :nodoc:
641
+ super
642
+ m.register_type(%r(enum)i) do |sql_type|
643
+ limit = sql_type[/^enum\((.+)\)/i, 1]
644
+ .split(',').map{|enum| enum.strip.length - 2}.max
645
+ Type::String.new(limit: limit)
646
+ end
647
+
648
+ m.register_type %r(tinytext)i, Type::Text.new(limit: 255)
649
+ m.register_type %r(tinyblob)i, Type::Binary.new(limit: 255)
650
+ m.register_type %r(mediumtext)i, Type::Text.new(limit: 16777215)
651
+ m.register_type %r(mediumblob)i, Type::Binary.new(limit: 16777215)
652
+ m.register_type %r(longtext)i, Type::Text.new(limit: 2147483647)
653
+ m.register_type %r(longblob)i, Type::Binary.new(limit: 2147483647)
654
+ m.register_type %r(^bigint)i, Type::Integer.new(limit: 8)
655
+ m.register_type %r(^int)i, Type::Integer.new(limit: 4)
656
+ m.register_type %r(^mediumint)i, Type::Integer.new(limit: 3)
657
+ m.register_type %r(^smallint)i, Type::Integer.new(limit: 2)
658
+ m.register_type %r(^tinyint)i, Type::Integer.new(limit: 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
+ m.alias_type %r(tinyint\(1\))i, 'boolean' if emulate_booleans
663
+ m.alias_type %r(set)i, 'varchar'
664
+ m.alias_type %r(year)i, 'integer'
665
+ m.alias_type %r(bit)i, 'binary'
666
+ end
667
+
619
668
  # MySQL is too stupid to create a temporary table for use subquery, so we have
620
669
  # to give it some prompting in the form of a subsubquery. Ugh!
621
670
  def subquery_for(key, select)
@@ -685,15 +734,13 @@ module ActiveRecord
685
734
  end
686
735
 
687
736
  def rename_column_sql(table_name, column_name, new_column_name)
688
- options = { name: new_column_name }
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
737
+ column = column_for(table_name, column_name)
738
+ options = {
739
+ name: new_column_name,
740
+ default: column.default,
741
+ null: column.null,
742
+ auto_increment: column.extra == "auto_increment"
743
+ }
697
744
 
698
745
  current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", 'SCHEMA')["Type"]
699
746
  schema_creation.accept ChangeColumnDefinition.new column, current_type, options
@@ -743,13 +790,6 @@ module ActiveRecord
743
790
  mariadb? ? false : (version[0] == 5 && version[1] >= 7) || version[0] >= 6
744
791
  end
745
792
 
746
- def column_for(table_name, column_name)
747
- unless column = columns(table_name).find { |c| c.name == column_name.to_s }
748
- raise "No such column: #{table_name}.#{column_name}"
749
- end
750
- column
751
- end
752
-
753
793
  def configure_connection
754
794
  variables = @config.fetch(:variables, {}).stringify_keys
755
795
 
@@ -787,6 +827,15 @@ module ActiveRecord
787
827
  # ...and send them all in one query
788
828
  @connection.query "SET #{encoding} #{variable_assignments}"
789
829
  end
830
+
831
+ def extract_foreign_key_action(structure, name, action) # :nodoc:
832
+ if structure =~ /CONSTRAINT #{quote_column_name(name)} FOREIGN KEY .* REFERENCES .* ON #{action} (CASCADE|SET NULL|RESTRICT)/
833
+ case $1
834
+ when 'CASCADE'; :cascade
835
+ when 'SET NULL'; :nullify
836
+ end
837
+ end
838
+ end
790
839
  end
791
840
  end
792
841
  end
@@ -13,101 +13,36 @@ module ActiveRecord
13
13
  ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/
14
14
  end
15
15
 
16
- attr_reader :name, :default, :type, :limit, :null, :sql_type, :precision, :scale, :default_function
17
- attr_accessor :primary, :coder
16
+ attr_reader :name, :cast_type, :null, :sql_type, :default, :default_function
18
17
 
19
- alias :encoded? :coder
18
+ delegate :type, :precision, :scale, :limit, :klass, :accessor,
19
+ :number?, :binary?, :changed?,
20
+ :type_cast_from_user, :type_cast_from_database, :type_cast_for_database,
21
+ :type_cast_for_schema,
22
+ to: :cast_type
20
23
 
21
24
  # Instantiates a new column in the table.
22
25
  #
23
26
  # +name+ is the column's name, such as <tt>supplier_id</tt> in <tt>supplier_id int(11)</tt>.
24
27
  # +default+ is the type-casted default value, such as +new+ in <tt>sales_stage varchar(20) default 'new'</tt>.
28
+ # +cast_type+ is the object used for type casting and type information.
25
29
  # +sql_type+ is used to extract the column's length, if necessary. For example +60+ in
26
30
  # <tt>company_name varchar(60)</tt>.
27
31
  # It will be mapped to one of the standard Rails SQL types in the <tt>type</tt> attribute.
28
32
  # +null+ determines if this column allows +NULL+ values.
29
- def initialize(name, default, sql_type = nil, null = true)
33
+ def initialize(name, default, cast_type, sql_type = nil, null = true)
30
34
  @name = name
35
+ @cast_type = cast_type
31
36
  @sql_type = sql_type
32
37
  @null = null
33
- @limit = extract_limit(sql_type)
34
- @precision = extract_precision(sql_type)
35
- @scale = extract_scale(sql_type)
36
- @type = simplified_type(sql_type)
37
- @default = extract_default(default)
38
+ @default = default
38
39
  @default_function = nil
39
- @primary = nil
40
- @coder = nil
41
- end
42
-
43
- # Returns +true+ if the column is either of type string or text.
44
- def text?
45
- type == :string || type == :text
46
- end
47
-
48
- # Returns +true+ if the column is either of type integer, float or decimal.
49
- def number?
50
- type == :integer || type == :float || type == :decimal
51
40
  end
52
41
 
53
42
  def has_default?
54
43
  !default.nil?
55
44
  end
56
45
 
57
- # Returns the Ruby class that corresponds to the abstract data type.
58
- def klass
59
- case type
60
- when :integer then Fixnum
61
- when :float then Float
62
- when :decimal then BigDecimal
63
- when :datetime, :timestamp, :time then Time
64
- when :date then Date
65
- when :text, :string, :binary then String
66
- when :boolean then Object
67
- end
68
- end
69
-
70
- def binary?
71
- type == :binary
72
- end
73
-
74
- # Casts a Ruby value to something appropriate for writing to the database.
75
- def type_cast_for_write(value)
76
- return value unless number?
77
-
78
- case value
79
- when FalseClass
80
- 0
81
- when TrueClass
82
- 1
83
- when String
84
- value.presence
85
- else
86
- value
87
- end
88
- end
89
-
90
- # Casts value (which is a String) to an appropriate instance.
91
- def type_cast(value)
92
- return nil if value.nil?
93
- return coder.load(value) if encoded?
94
-
95
- klass = self.class
96
-
97
- case type
98
- when :string, :text then value
99
- when :integer then klass.value_to_integer(value)
100
- when :float then value.to_f
101
- when :decimal then klass.value_to_decimal(value)
102
- when :datetime, :timestamp then klass.string_to_time(value)
103
- when :time then klass.string_to_dummy_time(value)
104
- when :date then klass.value_to_date(value)
105
- when :binary then klass.binary_to_string(value)
106
- when :boolean then klass.value_to_boolean(value)
107
- else value
108
- end
109
- end
110
-
111
46
  # Returns the human name of the column name.
112
47
  #
113
48
  # ===== Examples
@@ -116,177 +51,11 @@ module ActiveRecord
116
51
  Base.human_attribute_name(@name)
117
52
  end
118
53
 
119
- def extract_default(default)
120
- type_cast(default)
121
- end
122
-
123
- class << self
124
- # Used to convert from BLOBs to Strings
125
- def binary_to_string(value)
126
- value
54
+ def with_type(type)
55
+ dup.tap do |clone|
56
+ clone.instance_variable_set('@cast_type', type)
127
57
  end
128
-
129
- def value_to_date(value)
130
- if value.is_a?(String)
131
- return nil if value.empty?
132
- fast_string_to_date(value) || fallback_string_to_date(value)
133
- elsif value.respond_to?(:to_date)
134
- value.to_date
135
- else
136
- value
137
- end
138
- end
139
-
140
- def string_to_time(string)
141
- return string unless string.is_a?(String)
142
- return nil if string.empty?
143
-
144
- fast_string_to_time(string) || fallback_string_to_time(string)
145
- end
146
-
147
- def string_to_dummy_time(string)
148
- return string unless string.is_a?(String)
149
- return nil if string.empty?
150
-
151
- dummy_time_string = "2000-01-01 #{string}"
152
-
153
- fast_string_to_time(dummy_time_string) || begin
154
- time_hash = Date._parse(dummy_time_string)
155
- return nil if time_hash[:hour].nil?
156
- new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction))
157
- end
158
- end
159
-
160
- # convert something to a boolean
161
- def value_to_boolean(value)
162
- if value.is_a?(String) && value.empty?
163
- nil
164
- else
165
- TRUE_VALUES.include?(value)
166
- end
167
- end
168
-
169
- # Used to convert values to integer.
170
- # handle the case when an integer column is used to store boolean values
171
- def value_to_integer(value)
172
- case value
173
- when TrueClass, FalseClass
174
- value ? 1 : 0
175
- else
176
- value.to_i rescue nil
177
- end
178
- end
179
-
180
- # convert something to a BigDecimal
181
- def value_to_decimal(value)
182
- # Using .class is faster than .is_a? and
183
- # subclasses of BigDecimal will be handled
184
- # in the else clause
185
- if value.class == BigDecimal
186
- value
187
- elsif value.respond_to?(:to_d)
188
- value.to_d
189
- else
190
- value.to_s.to_d
191
- end
192
- end
193
-
194
- protected
195
- # '0.123456' -> 123456
196
- # '1.123456' -> 123456
197
- def microseconds(time)
198
- time[:sec_fraction] ? (time[:sec_fraction] * 1_000_000).to_i : 0
199
- end
200
-
201
- def new_date(year, mon, mday)
202
- if year && year != 0
203
- Date.new(year, mon, mday) rescue nil
204
- end
205
- end
206
-
207
- def new_time(year, mon, mday, hour, min, sec, microsec, offset = nil)
208
- # Treat 0000-00-00 00:00:00 as nil.
209
- return nil if year.nil? || (year == 0 && mon == 0 && mday == 0)
210
-
211
- if offset
212
- time = Time.utc(year, mon, mday, hour, min, sec, microsec) rescue nil
213
- return nil unless time
214
-
215
- time -= offset
216
- Base.default_timezone == :utc ? time : time.getlocal
217
- else
218
- Time.public_send(Base.default_timezone, year, mon, mday, hour, min, sec, microsec) rescue nil
219
- end
220
- end
221
-
222
- def fast_string_to_date(string)
223
- if string =~ Format::ISO_DATE
224
- new_date $1.to_i, $2.to_i, $3.to_i
225
- end
226
- end
227
-
228
- # Doesn't handle time zones.
229
- def fast_string_to_time(string)
230
- if string =~ Format::ISO_DATETIME
231
- microsec = ($7.to_r * 1_000_000).to_i
232
- new_time $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, microsec
233
- end
234
- end
235
-
236
- def fallback_string_to_date(string)
237
- new_date(*::Date._parse(string, false).values_at(:year, :mon, :mday))
238
- end
239
-
240
- def fallback_string_to_time(string)
241
- time_hash = Date._parse(string)
242
- time_hash[:sec_fraction] = microseconds(time_hash)
243
-
244
- new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset))
245
- end
246
58
  end
247
-
248
- private
249
- def extract_limit(sql_type)
250
- $1.to_i if sql_type =~ /\((.*)\)/
251
- end
252
-
253
- def extract_precision(sql_type)
254
- $2.to_i if sql_type =~ /^(numeric|decimal|number)\((\d+)(,\d+)?\)/i
255
- end
256
-
257
- def extract_scale(sql_type)
258
- case sql_type
259
- when /^(numeric|decimal|number)\((\d+)\)/i then 0
260
- when /^(numeric|decimal|number)\((\d+)(,(\d+))\)/i then $4.to_i
261
- end
262
- end
263
-
264
- def simplified_type(field_type)
265
- case field_type
266
- when /int/i
267
- :integer
268
- when /float|double/i
269
- :float
270
- when /decimal|numeric|number/i
271
- extract_scale(field_type) == 0 ? :integer : :decimal
272
- when /datetime/i
273
- :datetime
274
- when /timestamp/i
275
- :timestamp
276
- when /time/i
277
- :time
278
- when /date/i
279
- :date
280
- when /clob/i, /text/i
281
- :text
282
- when /blob/i, /binary/i
283
- :binary
284
- when /char/i
285
- :string
286
- when /boolean/i
287
- :boolean
288
- end
289
- end
290
59
  end
291
60
  end
292
61
  # :startdoc: