activerecord 4.1.16 → 4.2.11.3

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 +5 -5
  2. data/CHANGELOG.md +1162 -1801
  3. data/README.rdoc +15 -10
  4. data/lib/active_record/aggregations.rb +15 -8
  5. data/lib/active_record/association_relation.rb +13 -0
  6. data/lib/active_record/associations/alias_tracker.rb +3 -12
  7. data/lib/active_record/associations/association.rb +16 -4
  8. data/lib/active_record/associations/association_scope.rb +83 -38
  9. data/lib/active_record/associations/belongs_to_association.rb +28 -10
  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/collection_association.rb +5 -1
  13. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +8 -13
  14. data/lib/active_record/associations/builder/has_many.rb +1 -1
  15. data/lib/active_record/associations/builder/has_one.rb +2 -2
  16. data/lib/active_record/associations/builder/singular_association.rb +8 -1
  17. data/lib/active_record/associations/collection_association.rb +63 -27
  18. data/lib/active_record/associations/collection_proxy.rb +29 -35
  19. data/lib/active_record/associations/foreign_association.rb +11 -0
  20. data/lib/active_record/associations/has_many_association.rb +83 -22
  21. data/lib/active_record/associations/has_many_through_association.rb +49 -26
  22. data/lib/active_record/associations/has_one_association.rb +1 -1
  23. data/lib/active_record/associations/join_dependency/join_association.rb +25 -15
  24. data/lib/active_record/associations/join_dependency/join_part.rb +0 -1
  25. data/lib/active_record/associations/join_dependency.rb +26 -13
  26. data/lib/active_record/associations/preloader/association.rb +14 -11
  27. data/lib/active_record/associations/preloader/through_association.rb +4 -3
  28. data/lib/active_record/associations/preloader.rb +36 -26
  29. data/lib/active_record/associations/singular_association.rb +17 -2
  30. data/lib/active_record/associations/through_association.rb +5 -12
  31. data/lib/active_record/associations.rb +158 -49
  32. data/lib/active_record/attribute.rb +163 -0
  33. data/lib/active_record/attribute_assignment.rb +19 -11
  34. data/lib/active_record/attribute_decorators.rb +66 -0
  35. data/lib/active_record/attribute_methods/before_type_cast.rb +7 -2
  36. data/lib/active_record/attribute_methods/dirty.rb +107 -43
  37. data/lib/active_record/attribute_methods/primary_key.rb +7 -8
  38. data/lib/active_record/attribute_methods/query.rb +1 -1
  39. data/lib/active_record/attribute_methods/read.rb +22 -59
  40. data/lib/active_record/attribute_methods/serialization.rb +16 -150
  41. data/lib/active_record/attribute_methods/time_zone_conversion.rb +38 -40
  42. data/lib/active_record/attribute_methods/write.rb +9 -24
  43. data/lib/active_record/attribute_methods.rb +56 -94
  44. data/lib/active_record/attribute_set/builder.rb +106 -0
  45. data/lib/active_record/attribute_set.rb +81 -0
  46. data/lib/active_record/attributes.rb +147 -0
  47. data/lib/active_record/autosave_association.rb +19 -12
  48. data/lib/active_record/base.rb +13 -24
  49. data/lib/active_record/callbacks.rb +6 -6
  50. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +84 -52
  51. data/lib/active_record/connection_adapters/abstract/database_statements.rb +52 -50
  52. data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
  53. data/lib/active_record/connection_adapters/abstract/quoting.rb +60 -60
  54. data/lib/active_record/connection_adapters/abstract/savepoints.rb +1 -1
  55. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +39 -4
  56. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +138 -56
  57. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -34
  58. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +268 -71
  59. data/lib/active_record/connection_adapters/abstract/transaction.rb +125 -118
  60. data/lib/active_record/connection_adapters/abstract_adapter.rb +171 -59
  61. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +293 -139
  62. data/lib/active_record/connection_adapters/column.rb +29 -240
  63. data/lib/active_record/connection_adapters/connection_specification.rb +15 -24
  64. data/lib/active_record/connection_adapters/mysql2_adapter.rb +16 -32
  65. data/lib/active_record/connection_adapters/mysql_adapter.rb +67 -144
  66. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +15 -27
  67. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  68. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +40 -25
  69. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +100 -0
  70. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  71. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  72. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
  73. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  75. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +36 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
  78. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  79. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  80. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  81. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +19 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid.rb +29 -388
  95. data/lib/active_record/connection_adapters/postgresql/quoting.rb +46 -136
  96. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +4 -4
  97. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
  98. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +131 -43
  99. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  100. data/lib/active_record/connection_adapters/postgresql_adapter.rb +224 -477
  101. data/lib/active_record/connection_adapters/schema_cache.rb +14 -28
  102. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +61 -75
  103. data/lib/active_record/connection_handling.rb +1 -1
  104. data/lib/active_record/core.rb +163 -39
  105. data/lib/active_record/counter_cache.rb +60 -6
  106. data/lib/active_record/enum.rb +9 -11
  107. data/lib/active_record/errors.rb +53 -30
  108. data/lib/active_record/explain.rb +1 -1
  109. data/lib/active_record/explain_subscriber.rb +1 -1
  110. data/lib/active_record/fixtures.rb +55 -69
  111. data/lib/active_record/gem_version.rb +4 -4
  112. data/lib/active_record/inheritance.rb +35 -10
  113. data/lib/active_record/integration.rb +4 -4
  114. data/lib/active_record/legacy_yaml_adapter.rb +30 -0
  115. data/lib/active_record/locking/optimistic.rb +46 -26
  116. data/lib/active_record/migration/command_recorder.rb +19 -2
  117. data/lib/active_record/migration/join_table.rb +1 -1
  118. data/lib/active_record/migration.rb +71 -46
  119. data/lib/active_record/model_schema.rb +52 -58
  120. data/lib/active_record/nested_attributes.rb +5 -5
  121. data/lib/active_record/no_touching.rb +1 -1
  122. data/lib/active_record/persistence.rb +46 -26
  123. data/lib/active_record/query_cache.rb +3 -3
  124. data/lib/active_record/querying.rb +10 -7
  125. data/lib/active_record/railtie.rb +18 -11
  126. data/lib/active_record/railties/databases.rake +50 -51
  127. data/lib/active_record/readonly_attributes.rb +0 -1
  128. data/lib/active_record/reflection.rb +273 -114
  129. data/lib/active_record/relation/batches.rb +0 -2
  130. data/lib/active_record/relation/calculations.rb +41 -37
  131. data/lib/active_record/relation/finder_methods.rb +70 -47
  132. data/lib/active_record/relation/merger.rb +39 -29
  133. data/lib/active_record/relation/predicate_builder/array_handler.rb +32 -13
  134. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -5
  135. data/lib/active_record/relation/predicate_builder.rb +16 -8
  136. data/lib/active_record/relation/query_methods.rb +114 -65
  137. data/lib/active_record/relation/spawn_methods.rb +3 -0
  138. data/lib/active_record/relation.rb +57 -25
  139. data/lib/active_record/result.rb +18 -7
  140. data/lib/active_record/sanitization.rb +12 -2
  141. data/lib/active_record/schema.rb +0 -1
  142. data/lib/active_record/schema_dumper.rb +59 -28
  143. data/lib/active_record/schema_migration.rb +5 -4
  144. data/lib/active_record/scoping/default.rb +6 -4
  145. data/lib/active_record/scoping/named.rb +4 -0
  146. data/lib/active_record/serializers/xml_serializer.rb +3 -7
  147. data/lib/active_record/statement_cache.rb +95 -10
  148. data/lib/active_record/store.rb +5 -5
  149. data/lib/active_record/tasks/database_tasks.rb +61 -6
  150. data/lib/active_record/tasks/mysql_database_tasks.rb +20 -11
  151. data/lib/active_record/tasks/postgresql_database_tasks.rb +20 -9
  152. data/lib/active_record/timestamp.rb +9 -7
  153. data/lib/active_record/transactions.rb +53 -27
  154. data/lib/active_record/type/big_integer.rb +13 -0
  155. data/lib/active_record/type/binary.rb +50 -0
  156. data/lib/active_record/type/boolean.rb +31 -0
  157. data/lib/active_record/type/date.rb +50 -0
  158. data/lib/active_record/type/date_time.rb +54 -0
  159. data/lib/active_record/type/decimal.rb +64 -0
  160. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  161. data/lib/active_record/type/decorator.rb +14 -0
  162. data/lib/active_record/type/float.rb +19 -0
  163. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  164. data/lib/active_record/type/integer.rb +59 -0
  165. data/lib/active_record/type/mutable.rb +16 -0
  166. data/lib/active_record/type/numeric.rb +36 -0
  167. data/lib/active_record/type/serialized.rb +62 -0
  168. data/lib/active_record/type/string.rb +40 -0
  169. data/lib/active_record/type/text.rb +11 -0
  170. data/lib/active_record/type/time.rb +26 -0
  171. data/lib/active_record/type/time_value.rb +38 -0
  172. data/lib/active_record/type/type_map.rb +64 -0
  173. data/lib/active_record/type/unsigned_integer.rb +15 -0
  174. data/lib/active_record/type/value.rb +110 -0
  175. data/lib/active_record/type.rb +23 -0
  176. data/lib/active_record/validations/associated.rb +5 -3
  177. data/lib/active_record/validations/presence.rb +5 -3
  178. data/lib/active_record/validations/uniqueness.rb +25 -29
  179. data/lib/active_record/validations.rb +25 -19
  180. data/lib/active_record.rb +4 -0
  181. data/lib/rails/generators/active_record/migration/migration_generator.rb +8 -4
  182. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +1 -1
  183. data/lib/rails/generators/active_record/model/templates/model.rb +1 -1
  184. metadata +66 -11
  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,12 +7,29 @@ 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
@@ -29,38 +47,45 @@ 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
35
58
  SchemaCreation.new self
36
59
  end
37
60
 
61
+ def prepare_column_options(column, types) # :nodoc:
62
+ spec = super
63
+ spec.delete(:limit) if :boolean === column.type
64
+ spec
65
+ end
66
+
38
67
  class Column < ConnectionAdapters::Column # :nodoc:
39
68
  attr_reader :collation, :strict, :extra
40
69
 
41
- def initialize(name, default, sql_type = nil, null = true, collation = nil, strict = false, extra = "")
70
+ def initialize(name, default, cast_type, sql_type = nil, null = true, collation = nil, strict = false, extra = "")
42
71
  @strict = strict
43
72
  @collation = collation
44
73
  @extra = extra
45
- super(name, default, sql_type, null)
74
+ super(name, default, cast_type, sql_type, null)
75
+ assert_valid_default(default)
76
+ extract_default
46
77
  end
47
78
 
48
- def extract_default(default)
79
+ def extract_default
49
80
  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
81
+ @default = null || strict ? nil : ''
82
+ elsif missing_default_forged_as_empty_string?(@default)
83
+ @default = nil
59
84
  end
60
85
  end
61
86
 
62
87
  def has_default?
63
- return false if blob_or_text_column? #mysql forbids defaults on blob and text columns
88
+ return false if blob_or_text_column? # MySQL forbids defaults on blob and text columns
64
89
  super
65
90
  end
66
91
 
@@ -68,55 +93,18 @@ module ActiveRecord
68
93
  sql_type =~ /blob/i || type == :text
69
94
  end
70
95
 
71
- # Must return the relevant concrete adapter
72
- def adapter
73
- raise NotImplementedError
74
- end
75
-
76
96
  def case_sensitive?
77
97
  collation && !collation.match(/_ci$/)
78
98
  end
79
99
 
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
100
+ def ==(other)
101
+ super &&
102
+ collation == other.collation &&
103
+ strict == other.strict &&
104
+ extra == other.extra
92
105
  end
93
106
 
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
107
+ private
120
108
 
121
109
  # MySQL misreports NOT NULL column default when none is given.
122
110
  # We can't detect this for columns which may have a legitimate ''
@@ -128,6 +116,16 @@ module ActiveRecord
128
116
  def missing_default_forged_as_empty_string?(default)
129
117
  type != :string && !null && default == ''
130
118
  end
119
+
120
+ def assert_valid_default(default)
121
+ if blob_or_text_column? && default.present?
122
+ raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
123
+ end
124
+ end
125
+
126
+ def attributes_for_hash
127
+ super + [collation, strict, extra]
128
+ end
131
129
  end
132
130
 
133
131
  ##
@@ -157,7 +155,6 @@ module ActiveRecord
157
155
  :float => { :name => "float" },
158
156
  :decimal => { :name => "decimal" },
159
157
  :datetime => { :name => "datetime" },
160
- :timestamp => { :name => "datetime" },
161
158
  :time => { :name => "time" },
162
159
  :date => { :name => "date" },
163
160
  :binary => { :name => "blob" },
@@ -167,28 +164,21 @@ module ActiveRecord
167
164
  INDEX_TYPES = [:fulltext, :spatial]
168
165
  INDEX_USINGS = [:btree, :hash]
169
166
 
170
- class BindSubstitution < Arel::Visitors::MySQL # :nodoc:
171
- include Arel::Visitors::BindVisitor
172
- end
173
-
174
167
  # FIXME: Make the first parameter more similar for the two adapters
175
168
  def initialize(connection, logger, connection_options, config)
176
169
  super(connection, logger)
177
170
  @connection_options, @config = connection_options, config
178
171
  @quoted_column_names, @quoted_table_names = {}, {}
179
172
 
173
+ @visitor = Arel::Visitors::MySQL.new self
174
+
180
175
  if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
181
176
  @prepared_statements = true
182
- @visitor = Arel::Visitors::MySQL.new self
183
177
  else
184
- @visitor = unprepared_visitor
178
+ @prepared_statements = false
185
179
  end
186
180
  end
187
181
 
188
- def adapter_name #:nodoc:
189
- self.class::ADAPTER_NAME
190
- end
191
-
192
182
  # Returns true, since this connection adapter supports migrations.
193
183
  def supports_migrations?
194
184
  true
@@ -208,23 +198,28 @@ module ActiveRecord
208
198
  true
209
199
  end
210
200
 
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
201
  # MySQL 4 technically support transaction isolation, but it is affected by a bug
223
202
  # where the transaction level gets persisted for the whole session:
224
203
  #
225
204
  # http://bugs.mysql.com/bug.php?id=39170
226
205
  def supports_transaction_isolation?
227
- version[0] >= 5
206
+ version >= '5.0.0'
207
+ end
208
+
209
+ def supports_indexes_in_create?
210
+ true
211
+ end
212
+
213
+ def supports_foreign_keys?
214
+ true
215
+ end
216
+
217
+ def supports_views?
218
+ version >= '5.0.0'
219
+ end
220
+
221
+ def supports_datetime_with_precision?
222
+ version >= '5.6.4'
228
223
  end
229
224
 
230
225
  def native_database_types
@@ -243,12 +238,11 @@ module ActiveRecord
243
238
  raise NotImplementedError
244
239
  end
245
240
 
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)
241
+ def new_column(field, default, cast_type, sql_type = nil, null = true, collation = "", extra = "") # :nodoc:
242
+ Column.new(field, default, cast_type, sql_type, null, collation, strict_mode?, extra)
249
243
  end
250
244
 
251
- # Must return the Mysql error number from the exception, if the exception has an
245
+ # Must return the MySQL error number from the exception, if the exception has an
252
246
  # error number.
253
247
  def error_number(exception) # :nodoc:
254
248
  raise NotImplementedError
@@ -256,12 +250,9 @@ module ActiveRecord
256
250
 
257
251
  # QUOTING ==================================================
258
252
 
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")
253
+ def _quote(value) # :nodoc:
254
+ if value.is_a?(Type::Binary::Data)
255
+ "x'#{value.hex}'"
265
256
  else
266
257
  super
267
258
  end
@@ -279,10 +270,26 @@ module ActiveRecord
279
270
  QUOTED_TRUE
280
271
  end
281
272
 
273
+ def unquoted_true
274
+ 1
275
+ end
276
+
282
277
  def quoted_false
283
278
  QUOTED_FALSE
284
279
  end
285
280
 
281
+ def unquoted_false
282
+ 0
283
+ end
284
+
285
+ def quoted_date(value)
286
+ if supports_datetime_with_precision? && value.acts_like?(:time) && value.respond_to?(:usec)
287
+ "#{super}.#{sprintf("%06d", value.usec)}"
288
+ else
289
+ super
290
+ end
291
+ end
292
+
286
293
  # REFERENTIAL INTEGRITY ====================================
287
294
 
288
295
  def disable_referential_integrity #:nodoc:
@@ -296,7 +303,14 @@ module ActiveRecord
296
303
  end
297
304
  end
298
305
 
306
+ #--
299
307
  # DATABASE STATEMENTS ======================================
308
+ #++
309
+
310
+ def clear_cache!
311
+ super
312
+ reload_type_map
313
+ end
300
314
 
301
315
  # Executes the SQL statement in the context of this connection.
302
316
  def execute(sql, name = nil)
@@ -328,7 +342,7 @@ module ActiveRecord
328
342
  execute "COMMIT"
329
343
  end
330
344
 
331
- def rollback_db_transaction #:nodoc:
345
+ def exec_rollback_db_transaction #:nodoc:
332
346
  execute "ROLLBACK"
333
347
  end
334
348
 
@@ -405,9 +419,14 @@ module ActiveRecord
405
419
  result.collect { |field| field.first }
406
420
  end
407
421
  end
422
+ alias data_sources tables
423
+
424
+ def truncate(table_name, name = nil)
425
+ execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
426
+ end
408
427
 
409
428
  def table_exists?(name)
410
- return false unless name
429
+ return false unless name.present?
411
430
  return true if tables(nil, nil, name).any?
412
431
 
413
432
  name = name.to_s
@@ -420,6 +439,7 @@ module ActiveRecord
420
439
 
421
440
  tables(nil, schema, table).any?
422
441
  end
442
+ alias data_source_exists? table_exists?
423
443
 
424
444
  # Returns an array of indexes for the given table.
425
445
  def indexes(table_name, name = nil) #:nodoc:
@@ -451,7 +471,9 @@ module ActiveRecord
451
471
  execute_and_free(sql, 'SCHEMA') do |result|
452
472
  each_hash(result).map do |field|
453
473
  field_name = set_field_encoding(field[:Field])
454
- new_column(field_name, field[:Default], field[:Type], field[:Null] == "YES", field[:Collation], field[:Extra])
474
+ sql_type = field[:Type]
475
+ cast_type = lookup_cast_type(sql_type)
476
+ new_column(field_name, field[:Default], cast_type, sql_type, field[:Null] == "YES", field[:Collation], field[:Extra])
455
477
  end
456
478
  end
457
479
  end
@@ -461,7 +483,7 @@ module ActiveRecord
461
483
  end
462
484
 
463
485
  def bulk_change_table(table_name, operations) #:nodoc:
464
- sqls = operations.map do |command, args|
486
+ sqls = operations.flat_map do |command, args|
465
487
  table, arguments = args.shift, args
466
488
  method = :"#{command}_sql"
467
489
 
@@ -470,7 +492,7 @@ module ActiveRecord
470
492
  else
471
493
  raise "Unknown method called : #{method}(#{arguments.inspect})"
472
494
  end
473
- end.flatten.join(", ")
495
+ end.join(", ")
474
496
 
475
497
  execute("ALTER TABLE #{quote_table_name(table_name)} #{sqls}")
476
498
  end
@@ -485,18 +507,20 @@ module ActiveRecord
485
507
  end
486
508
 
487
509
  def drop_table(table_name, options = {})
488
- execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE #{quote_table_name(table_name)}"
510
+ execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
489
511
  end
490
512
 
491
513
  def rename_index(table_name, old_name, new_name)
492
514
  if supports_rename_index?
515
+ validate_index_length!(table_name, new_name)
516
+
493
517
  execute "ALTER TABLE #{quote_table_name(table_name)} RENAME INDEX #{quote_table_name(old_name)} TO #{quote_table_name(new_name)}"
494
518
  else
495
519
  super
496
520
  end
497
521
  end
498
522
 
499
- def change_column_default(table_name, column_name, default)
523
+ def change_column_default(table_name, column_name, default) #:nodoc:
500
524
  column = column_for(table_name, column_name)
501
525
  change_column table_name, column_name, column.sql_type, :default => default
502
526
  end
@@ -525,6 +549,34 @@ module ActiveRecord
525
549
  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
550
  end
527
551
 
552
+ def foreign_keys(table_name)
553
+ fk_info = select_all <<-SQL.strip_heredoc
554
+ SELECT fk.referenced_table_name as 'to_table'
555
+ ,fk.referenced_column_name as 'primary_key'
556
+ ,fk.column_name as 'column'
557
+ ,fk.constraint_name as 'name'
558
+ FROM information_schema.key_column_usage fk
559
+ WHERE fk.referenced_column_name is not null
560
+ AND fk.table_schema = '#{@config[:database]}'
561
+ AND fk.table_name = '#{table_name}'
562
+ SQL
563
+
564
+ create_table_info = select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
565
+
566
+ fk_info.map do |row|
567
+ options = {
568
+ column: row['column'],
569
+ name: row['name'],
570
+ primary_key: row['primary_key']
571
+ }
572
+
573
+ options[:on_update] = extract_foreign_key_action(create_table_info, row['name'], "UPDATE")
574
+ options[:on_delete] = extract_foreign_key_action(create_table_info, row['name'], "DELETE")
575
+
576
+ ForeignKeyDefinition.new(table_name, row['to_table'], options)
577
+ end
578
+ end
579
+
528
580
  # Maps logical Rails types to MySQL-specific data types.
529
581
  def type_to_sql(type, limit = nil, precision = nil, scale = nil)
530
582
  case type.to_s
@@ -552,23 +604,24 @@ module ActiveRecord
552
604
  when 0x1000000..0xffffffff; 'longtext'
553
605
  else raise(ActiveRecordError, "No text type has character length #{limit}")
554
606
  end
607
+ when 'datetime'
608
+ return super unless precision
609
+
610
+ case precision
611
+ when 0..6; "datetime(#{precision})"
612
+ else raise(ActiveRecordError, "No datetime type has precision of #{precision}. The allowed range of precision is from 0 to 6.")
613
+ end
555
614
  else
556
615
  super
557
616
  end
558
617
  end
559
618
 
560
- def add_column_position!(sql, options)
561
- if options[:first]
562
- sql << " FIRST"
563
- elsif options[:after]
564
- sql << " AFTER #{quote_column_name(options[:after])}"
565
- end
566
- end
567
-
568
619
  # SHOW VARIABLES LIKE 'name'
569
620
  def show_variable(name)
570
- variables = select_all("SHOW VARIABLES LIKE '#{name}'", 'SCHEMA')
621
+ variables = select_all("select @@#{name} as 'Value'", 'SCHEMA')
571
622
  variables.first['Value'] unless variables.empty?
623
+ rescue ActiveRecord::StatementInvalid
624
+ nil
572
625
  end
573
626
 
574
627
  # Returns a table's primary key and belonging sequence.
@@ -590,10 +643,19 @@ module ActiveRecord
590
643
  pk_and_sequence && pk_and_sequence.first
591
644
  end
592
645
 
593
- def case_sensitive_modifier(node)
646
+ def case_sensitive_modifier(node, table_attribute)
647
+ node = Arel::Nodes.build_quoted node, table_attribute
594
648
  Arel::Nodes::Bin.new(node)
595
649
  end
596
650
 
651
+ def case_sensitive_comparison(table, attribute, column, value)
652
+ if column.case_sensitive?
653
+ table[attribute].eq(value)
654
+ else
655
+ super
656
+ end
657
+ end
658
+
597
659
  def case_insensitive_comparison(table, attribute, column, value)
598
660
  if column.case_sensitive?
599
661
  super
@@ -602,8 +664,19 @@ module ActiveRecord
602
664
  end
603
665
  end
604
666
 
605
- def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
606
- where_sql
667
+ # In MySQL 5.7.5 and up, ONLY_FULL_GROUP_BY affects handling of queries that use
668
+ # DISTINCT and ORDER BY. It requires the ORDER BY columns in the select list for
669
+ # distinct queries, and requires that the ORDER BY include the distinct column.
670
+ # See https://dev.mysql.com/doc/refman/5.7/en/group-by-handling.html
671
+ def columns_for_distinct(columns, orders) # :nodoc:
672
+ order_columns = orders.reject(&:blank?).map { |s|
673
+ # Convert Arel node to string
674
+ s = s.to_sql unless s.is_a?(String)
675
+ # Remove any ASC/DESC modifiers
676
+ s.gsub(/\s+(?:ASC|DESC)\b/i, '')
677
+ }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
678
+
679
+ [super, *order_columns].join(', ')
607
680
  end
608
681
 
609
682
  def strict_mode?
@@ -616,12 +689,65 @@ module ActiveRecord
616
689
 
617
690
  protected
618
691
 
692
+ def initialize_type_map(m) # :nodoc:
693
+ super
694
+
695
+ register_class_with_limit m, %r(char)i, MysqlString
696
+
697
+ m.register_type %r(tinytext)i, Type::Text.new(limit: 2**8 - 1)
698
+ m.register_type %r(tinyblob)i, Type::Binary.new(limit: 2**8 - 1)
699
+ m.register_type %r(text)i, Type::Text.new(limit: 2**16 - 1)
700
+ m.register_type %r(blob)i, Type::Binary.new(limit: 2**16 - 1)
701
+ m.register_type %r(mediumtext)i, Type::Text.new(limit: 2**24 - 1)
702
+ m.register_type %r(mediumblob)i, Type::Binary.new(limit: 2**24 - 1)
703
+ m.register_type %r(longtext)i, Type::Text.new(limit: 2**32 - 1)
704
+ m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1)
705
+ m.register_type %r(^float)i, Type::Float.new(limit: 24)
706
+ m.register_type %r(^double)i, Type::Float.new(limit: 53)
707
+
708
+ register_integer_type m, %r(^bigint)i, limit: 8
709
+ register_integer_type m, %r(^int)i, limit: 4
710
+ register_integer_type m, %r(^mediumint)i, limit: 3
711
+ register_integer_type m, %r(^smallint)i, limit: 2
712
+ register_integer_type m, %r(^tinyint)i, limit: 1
713
+
714
+ m.alias_type %r(tinyint\(1\))i, 'boolean' if emulate_booleans
715
+ m.alias_type %r(set)i, 'varchar'
716
+ m.alias_type %r(year)i, 'integer'
717
+ m.alias_type %r(bit)i, 'binary'
718
+
719
+ m.register_type(%r(datetime)i) do |sql_type|
720
+ precision = extract_precision(sql_type)
721
+ MysqlDateTime.new(precision: precision)
722
+ end
723
+
724
+ m.register_type(%r(enum)i) do |sql_type|
725
+ limit = sql_type[/^enum\((.+)\)/i, 1]
726
+ .split(',').map{|enum| enum.strip.length - 2}.max
727
+ MysqlString.new(limit: limit)
728
+ end
729
+ end
730
+
731
+ def register_integer_type(mapping, key, options) # :nodoc:
732
+ mapping.register_type(key) do |sql_type|
733
+ if /unsigned/i =~ sql_type
734
+ Type::UnsignedInteger.new(options)
735
+ else
736
+ Type::Integer.new(options)
737
+ end
738
+ end
739
+ end
740
+
619
741
  # MySQL is too stupid to create a temporary table for use subquery, so we have
620
742
  # to give it some prompting in the form of a subsubquery. Ugh!
621
743
  def subquery_for(key, select)
622
744
  subsubselect = select.clone
623
745
  subsubselect.projections = [key]
624
746
 
747
+ # Materialize subquery by adding distinct
748
+ # to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
749
+ subsubselect.distinct unless select.limit || select.offset || select.orders.any?
750
+
625
751
  subselect = Arel::SelectManager.new(select.engine)
626
752
  subselect.project Arel.sql(key.name)
627
753
  subselect.from subsubselect.as('__active_record_temp')
@@ -632,7 +758,7 @@ module ActiveRecord
632
758
  case length
633
759
  when Hash
634
760
  column_names.each {|name| option_strings[name] += "(#{length[name]})" if length.has_key?(name) && length[name].present?}
635
- when Fixnum
761
+ when Integer
636
762
  column_names.each {|name| option_strings[name] += "(#{length})"}
637
763
  end
638
764
  end
@@ -685,15 +811,13 @@ module ActiveRecord
685
811
  end
686
812
 
687
813
  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
814
+ column = column_for(table_name, column_name)
815
+ options = {
816
+ name: new_column_name,
817
+ default: column.default,
818
+ null: column.null,
819
+ auto_increment: column.extra == "auto_increment"
820
+ }
697
821
 
698
822
  current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", 'SCHEMA')["Type"]
699
823
  schema_creation.accept ChangeColumnDefinition.new column, current_type, options
@@ -721,33 +845,22 @@ module ActiveRecord
721
845
  [add_column_sql(table_name, :created_at, :datetime, options), add_column_sql(table_name, :updated_at, :datetime, options)]
722
846
  end
723
847
 
724
- def remove_timestamps_sql(table_name)
848
+ def remove_timestamps_sql(table_name, options = {})
725
849
  [remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)]
726
850
  end
727
851
 
728
852
  private
729
853
 
730
854
  def version
731
- @version ||= full_version.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
855
+ @version ||= Version.new(full_version.match(/^\d+\.\d+\.\d+/)[0])
732
856
  end
733
857
 
734
858
  def mariadb?
735
859
  full_version =~ /mariadb/i
736
860
  end
737
861
 
738
- def supports_views?
739
- version[0] >= 5
740
- end
741
-
742
862
  def supports_rename_index?
743
- mariadb? ? false : (version[0] == 5 && version[1] >= 7) || version[0] >= 6
744
- end
745
-
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
863
+ mariadb? ? false : version >= '5.7.6'
751
864
  end
752
865
 
753
866
  def configure_connection
@@ -758,9 +871,9 @@ module ActiveRecord
758
871
  variables['sql_auto_is_null'] = 0
759
872
 
760
873
  # Increase timeout so the server doesn't disconnect us.
761
- wait_timeout = @config[:wait_timeout]
762
- wait_timeout = 2147483 unless wait_timeout.is_a?(Fixnum)
763
- variables['wait_timeout'] = self.class.type_cast_config_to_integer(wait_timeout)
874
+ wait_timeout = self.class.type_cast_config_to_integer(@config[:wait_timeout])
875
+ wait_timeout = 2147483 unless wait_timeout.is_a?(Integer)
876
+ variables["wait_timeout"] = wait_timeout
764
877
 
765
878
  # Make MySQL reject illegal values rather than truncating or blanking them, see
766
879
  # http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html#sqlmode_strict_all_tables
@@ -772,14 +885,18 @@ module ActiveRecord
772
885
  # NAMES does not have an equals sign, see
773
886
  # http://dev.mysql.com/doc/refman/5.0/en/set-statement.html#id944430
774
887
  # (trailing comma because variable_assignments will always have content)
775
- encoding = "NAMES #{@config[:encoding]}, " if @config[:encoding]
888
+ if @config[:encoding]
889
+ encoding = "NAMES #{@config[:encoding]}"
890
+ encoding << " COLLATE #{@config[:collation]}" if @config[:collation]
891
+ encoding << ", "
892
+ end
776
893
 
777
894
  # Gather up all of the SET variables...
778
895
  variable_assignments = variables.map do |k, v|
779
896
  if v == ':default' || v == :default
780
- "@@SESSION.#{k.to_s} = DEFAULT" # Sets the value to the global or compile default
897
+ "@@SESSION.#{k} = DEFAULT" # Sets the value to the global or compile default
781
898
  elsif !v.nil?
782
- "@@SESSION.#{k.to_s} = #{quote(v)}"
899
+ "@@SESSION.#{k} = #{quote(v)}"
783
900
  end
784
901
  # or else nil; compact to clear nils out
785
902
  end.compact.join(', ')
@@ -787,6 +904,43 @@ module ActiveRecord
787
904
  # ...and send them all in one query
788
905
  @connection.query "SET #{encoding} #{variable_assignments}"
789
906
  end
907
+
908
+ def extract_foreign_key_action(structure, name, action) # :nodoc:
909
+ if structure =~ /CONSTRAINT #{quote_column_name(name)} FOREIGN KEY .* REFERENCES .* ON #{action} (CASCADE|SET NULL|RESTRICT)/
910
+ case $1
911
+ when 'CASCADE'; :cascade
912
+ when 'SET NULL'; :nullify
913
+ end
914
+ end
915
+ end
916
+
917
+ class MysqlDateTime < Type::DateTime # :nodoc:
918
+ private
919
+
920
+ def has_precision?
921
+ precision || 0
922
+ end
923
+ end
924
+
925
+ class MysqlString < Type::String # :nodoc:
926
+ def type_cast_for_database(value)
927
+ case value
928
+ when true then "1"
929
+ when false then "0"
930
+ else super
931
+ end
932
+ end
933
+
934
+ private
935
+
936
+ def cast_value(value)
937
+ case value
938
+ when true then "1"
939
+ when false then "0"
940
+ else super
941
+ end
942
+ end
943
+ end
790
944
  end
791
945
  end
792
946
  end