activerecord 3.2.19 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (264) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1715 -604
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +40 -45
  5. data/examples/performance.rb +33 -22
  6. data/examples/simple.rb +3 -4
  7. data/lib/active_record/aggregations.rb +76 -51
  8. data/lib/active_record/association_relation.rb +35 -0
  9. data/lib/active_record/associations/alias_tracker.rb +54 -40
  10. data/lib/active_record/associations/association.rb +76 -56
  11. data/lib/active_record/associations/association_scope.rb +125 -93
  12. data/lib/active_record/associations/belongs_to_association.rb +57 -28
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +7 -2
  14. data/lib/active_record/associations/builder/association.rb +120 -32
  15. data/lib/active_record/associations/builder/belongs_to.rb +115 -62
  16. data/lib/active_record/associations/builder/collection_association.rb +61 -53
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +117 -43
  18. data/lib/active_record/associations/builder/has_many.rb +9 -65
  19. data/lib/active_record/associations/builder/has_one.rb +18 -52
  20. data/lib/active_record/associations/builder/singular_association.rb +18 -19
  21. data/lib/active_record/associations/collection_association.rb +268 -186
  22. data/lib/active_record/associations/collection_proxy.rb +1003 -63
  23. data/lib/active_record/associations/foreign_association.rb +11 -0
  24. data/lib/active_record/associations/has_many_association.rb +81 -41
  25. data/lib/active_record/associations/has_many_through_association.rb +76 -55
  26. data/lib/active_record/associations/has_one_association.rb +51 -21
  27. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  28. data/lib/active_record/associations/join_dependency/join_association.rb +83 -108
  29. data/lib/active_record/associations/join_dependency/join_base.rb +7 -9
  30. data/lib/active_record/associations/join_dependency/join_part.rb +30 -37
  31. data/lib/active_record/associations/join_dependency.rb +239 -155
  32. data/lib/active_record/associations/preloader/association.rb +97 -62
  33. data/lib/active_record/associations/preloader/collection_association.rb +2 -8
  34. data/lib/active_record/associations/preloader/has_many_through.rb +7 -3
  35. data/lib/active_record/associations/preloader/has_one.rb +0 -8
  36. data/lib/active_record/associations/preloader/singular_association.rb +3 -3
  37. data/lib/active_record/associations/preloader/through_association.rb +75 -33
  38. data/lib/active_record/associations/preloader.rb +111 -79
  39. data/lib/active_record/associations/singular_association.rb +35 -13
  40. data/lib/active_record/associations/through_association.rb +41 -19
  41. data/lib/active_record/associations.rb +727 -501
  42. data/lib/active_record/attribute/user_provided_default.rb +28 -0
  43. data/lib/active_record/attribute.rb +213 -0
  44. data/lib/active_record/attribute_assignment.rb +32 -162
  45. data/lib/active_record/attribute_decorators.rb +67 -0
  46. data/lib/active_record/attribute_methods/before_type_cast.rb +52 -7
  47. data/lib/active_record/attribute_methods/dirty.rb +101 -61
  48. data/lib/active_record/attribute_methods/primary_key.rb +50 -36
  49. data/lib/active_record/attribute_methods/query.rb +7 -6
  50. data/lib/active_record/attribute_methods/read.rb +56 -117
  51. data/lib/active_record/attribute_methods/serialization.rb +43 -96
  52. data/lib/active_record/attribute_methods/time_zone_conversion.rb +93 -42
  53. data/lib/active_record/attribute_methods/write.rb +34 -45
  54. data/lib/active_record/attribute_methods.rb +333 -144
  55. data/lib/active_record/attribute_mutation_tracker.rb +70 -0
  56. data/lib/active_record/attribute_set/builder.rb +108 -0
  57. data/lib/active_record/attribute_set.rb +108 -0
  58. data/lib/active_record/attributes.rb +265 -0
  59. data/lib/active_record/autosave_association.rb +285 -223
  60. data/lib/active_record/base.rb +95 -490
  61. data/lib/active_record/callbacks.rb +95 -61
  62. data/lib/active_record/coders/json.rb +13 -0
  63. data/lib/active_record/coders/yaml_column.rb +28 -19
  64. data/lib/active_record/collection_cache_key.rb +40 -0
  65. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +724 -277
  66. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  67. data/lib/active_record/connection_adapters/abstract/database_statements.rb +199 -192
  68. data/lib/active_record/connection_adapters/abstract/query_cache.rb +31 -26
  69. data/lib/active_record/connection_adapters/abstract/quoting.rb +140 -57
  70. data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +147 -0
  72. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +419 -276
  73. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +105 -0
  74. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +963 -276
  75. data/lib/active_record/connection_adapters/abstract/transaction.rb +232 -0
  76. data/lib/active_record/connection_adapters/abstract_adapter.rb +397 -106
  77. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +643 -342
  78. data/lib/active_record/connection_adapters/column.rb +30 -259
  79. data/lib/active_record/connection_adapters/connection_specification.rb +263 -0
  80. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +22 -0
  81. data/lib/active_record/connection_adapters/mysql/column.rb +50 -0
  82. data/lib/active_record/connection_adapters/mysql/database_statements.rb +125 -0
  83. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +70 -0
  84. data/lib/active_record/connection_adapters/mysql/quoting.rb +51 -0
  85. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +67 -0
  86. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +93 -0
  87. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +54 -0
  88. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +32 -0
  89. data/lib/active_record/connection_adapters/mysql2_adapter.rb +47 -196
  90. data/lib/active_record/connection_adapters/postgresql/column.rb +15 -0
  91. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +170 -0
  92. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +42 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +70 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +48 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +21 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +10 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +39 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +50 -0
  108. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +93 -0
  109. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +15 -0
  110. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
  111. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  112. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  113. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  114. data/lib/active_record/connection_adapters/postgresql/oid.rb +31 -0
  115. data/lib/active_record/connection_adapters/postgresql/quoting.rb +116 -0
  116. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +49 -0
  117. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +180 -0
  118. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +47 -0
  119. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +682 -0
  120. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
  121. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  122. data/lib/active_record/connection_adapters/postgresql_adapter.rb +558 -1039
  123. data/lib/active_record/connection_adapters/schema_cache.rb +74 -36
  124. data/lib/active_record/connection_adapters/sql_type_metadata.rb +32 -0
  125. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +19 -0
  126. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +48 -0
  127. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  128. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +538 -24
  129. data/lib/active_record/connection_adapters/statement_pool.rb +31 -12
  130. data/lib/active_record/connection_handling.rb +155 -0
  131. data/lib/active_record/core.rb +561 -0
  132. data/lib/active_record/counter_cache.rb +146 -105
  133. data/lib/active_record/dynamic_matchers.rb +101 -64
  134. data/lib/active_record/enum.rb +234 -0
  135. data/lib/active_record/errors.rb +153 -56
  136. data/lib/active_record/explain.rb +15 -63
  137. data/lib/active_record/explain_registry.rb +30 -0
  138. data/lib/active_record/explain_subscriber.rb +10 -6
  139. data/lib/active_record/fixture_set/file.rb +77 -0
  140. data/lib/active_record/fixtures.rb +355 -232
  141. data/lib/active_record/gem_version.rb +15 -0
  142. data/lib/active_record/inheritance.rb +144 -79
  143. data/lib/active_record/integration.rb +66 -13
  144. data/lib/active_record/internal_metadata.rb +56 -0
  145. data/lib/active_record/legacy_yaml_adapter.rb +46 -0
  146. data/lib/active_record/locale/en.yml +9 -1
  147. data/lib/active_record/locking/optimistic.rb +77 -56
  148. data/lib/active_record/locking/pessimistic.rb +6 -6
  149. data/lib/active_record/log_subscriber.rb +53 -28
  150. data/lib/active_record/migration/command_recorder.rb +166 -33
  151. data/lib/active_record/migration/compatibility.rb +126 -0
  152. data/lib/active_record/migration/join_table.rb +15 -0
  153. data/lib/active_record/migration.rb +792 -264
  154. data/lib/active_record/model_schema.rb +192 -130
  155. data/lib/active_record/nested_attributes.rb +238 -145
  156. data/lib/active_record/no_touching.rb +52 -0
  157. data/lib/active_record/null_relation.rb +89 -0
  158. data/lib/active_record/persistence.rb +357 -157
  159. data/lib/active_record/query_cache.rb +22 -43
  160. data/lib/active_record/querying.rb +34 -23
  161. data/lib/active_record/railtie.rb +88 -48
  162. data/lib/active_record/railties/console_sandbox.rb +3 -4
  163. data/lib/active_record/railties/controller_runtime.rb +5 -4
  164. data/lib/active_record/railties/databases.rake +170 -422
  165. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  166. data/lib/active_record/readonly_attributes.rb +2 -5
  167. data/lib/active_record/reflection.rb +715 -189
  168. data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
  169. data/lib/active_record/relation/batches.rb +203 -50
  170. data/lib/active_record/relation/calculations.rb +203 -194
  171. data/lib/active_record/relation/delegation.rb +103 -25
  172. data/lib/active_record/relation/finder_methods.rb +457 -261
  173. data/lib/active_record/relation/from_clause.rb +32 -0
  174. data/lib/active_record/relation/merger.rb +167 -0
  175. data/lib/active_record/relation/predicate_builder/array_handler.rb +43 -0
  176. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +88 -0
  177. data/lib/active_record/relation/predicate_builder/base_handler.rb +17 -0
  178. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +17 -0
  179. data/lib/active_record/relation/predicate_builder/class_handler.rb +27 -0
  180. data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +57 -0
  181. data/lib/active_record/relation/predicate_builder/range_handler.rb +33 -0
  182. data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
  183. data/lib/active_record/relation/predicate_builder.rb +153 -48
  184. data/lib/active_record/relation/query_attribute.rb +19 -0
  185. data/lib/active_record/relation/query_methods.rb +1019 -194
  186. data/lib/active_record/relation/record_fetch_warning.rb +49 -0
  187. data/lib/active_record/relation/spawn_methods.rb +46 -150
  188. data/lib/active_record/relation/where_clause.rb +174 -0
  189. data/lib/active_record/relation/where_clause_factory.rb +38 -0
  190. data/lib/active_record/relation.rb +450 -245
  191. data/lib/active_record/result.rb +104 -12
  192. data/lib/active_record/runtime_registry.rb +22 -0
  193. data/lib/active_record/sanitization.rb +120 -94
  194. data/lib/active_record/schema.rb +28 -18
  195. data/lib/active_record/schema_dumper.rb +141 -74
  196. data/lib/active_record/schema_migration.rb +50 -0
  197. data/lib/active_record/scoping/default.rb +64 -57
  198. data/lib/active_record/scoping/named.rb +93 -108
  199. data/lib/active_record/scoping.rb +73 -121
  200. data/lib/active_record/secure_token.rb +38 -0
  201. data/lib/active_record/serialization.rb +7 -5
  202. data/lib/active_record/statement_cache.rb +113 -0
  203. data/lib/active_record/store.rb +173 -15
  204. data/lib/active_record/suppressor.rb +58 -0
  205. data/lib/active_record/table_metadata.rb +68 -0
  206. data/lib/active_record/tasks/database_tasks.rb +313 -0
  207. data/lib/active_record/tasks/mysql_database_tasks.rb +151 -0
  208. data/lib/active_record/tasks/postgresql_database_tasks.rb +110 -0
  209. data/lib/active_record/tasks/sqlite_database_tasks.rb +59 -0
  210. data/lib/active_record/timestamp.rb +42 -24
  211. data/lib/active_record/touch_later.rb +58 -0
  212. data/lib/active_record/transactions.rb +233 -105
  213. data/lib/active_record/type/adapter_specific_registry.rb +130 -0
  214. data/lib/active_record/type/date.rb +7 -0
  215. data/lib/active_record/type/date_time.rb +7 -0
  216. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  217. data/lib/active_record/type/internal/abstract_json.rb +29 -0
  218. data/lib/active_record/type/internal/timezone.rb +15 -0
  219. data/lib/active_record/type/serialized.rb +63 -0
  220. data/lib/active_record/type/time.rb +20 -0
  221. data/lib/active_record/type/type_map.rb +64 -0
  222. data/lib/active_record/type.rb +72 -0
  223. data/lib/active_record/type_caster/connection.rb +29 -0
  224. data/lib/active_record/type_caster/map.rb +19 -0
  225. data/lib/active_record/type_caster.rb +7 -0
  226. data/lib/active_record/validations/absence.rb +23 -0
  227. data/lib/active_record/validations/associated.rb +33 -18
  228. data/lib/active_record/validations/length.rb +24 -0
  229. data/lib/active_record/validations/presence.rb +66 -0
  230. data/lib/active_record/validations/uniqueness.rb +128 -68
  231. data/lib/active_record/validations.rb +48 -40
  232. data/lib/active_record/version.rb +5 -7
  233. data/lib/active_record.rb +71 -47
  234. data/lib/rails/generators/active_record/migration/migration_generator.rb +56 -8
  235. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +24 -0
  236. data/lib/rails/generators/active_record/migration/templates/migration.rb +28 -16
  237. data/lib/rails/generators/active_record/migration.rb +18 -8
  238. data/lib/rails/generators/active_record/model/model_generator.rb +38 -16
  239. data/lib/rails/generators/active_record/model/templates/application_record.rb +5 -0
  240. data/lib/rails/generators/active_record/model/templates/model.rb +7 -6
  241. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  242. data/lib/rails/generators/active_record.rb +3 -11
  243. metadata +188 -134
  244. data/examples/associations.png +0 -0
  245. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -63
  246. data/lib/active_record/associations/join_helper.rb +0 -55
  247. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  248. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  249. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  250. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -441
  251. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  252. data/lib/active_record/dynamic_finder_match.rb +0 -68
  253. data/lib/active_record/dynamic_scope_match.rb +0 -23
  254. data/lib/active_record/fixtures/file.rb +0 -65
  255. data/lib/active_record/identity_map.rb +0 -162
  256. data/lib/active_record/observer.rb +0 -121
  257. data/lib/active_record/serializers/xml_serializer.rb +0 -203
  258. data/lib/active_record/session_store.rb +0 -360
  259. data/lib/active_record/test_case.rb +0 -73
  260. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -15
  261. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  262. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  263. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  264. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -1,149 +1,91 @@
1
- require 'active_support/core_ext/object/blank'
2
- require 'arel/visitors/bind_visitor'
1
+ require 'active_record/connection_adapters/abstract_adapter'
2
+ require 'active_record/connection_adapters/statement_pool'
3
+ require 'active_record/connection_adapters/mysql/column'
4
+ require 'active_record/connection_adapters/mysql/explain_pretty_printer'
5
+ require 'active_record/connection_adapters/mysql/quoting'
6
+ require 'active_record/connection_adapters/mysql/schema_creation'
7
+ require 'active_record/connection_adapters/mysql/schema_definitions'
8
+ require 'active_record/connection_adapters/mysql/schema_dumper'
9
+ require 'active_record/connection_adapters/mysql/type_metadata'
10
+
11
+ require 'active_support/core_ext/string/strip'
3
12
 
4
13
  module ActiveRecord
5
14
  module ConnectionAdapters
6
15
  class AbstractMysqlAdapter < AbstractAdapter
7
- class Column < ConnectionAdapters::Column # :nodoc:
8
- attr_reader :collation
16
+ include MySQL::Quoting
17
+ include MySQL::ColumnDumper
9
18
 
10
- def initialize(name, default, sql_type = nil, null = true, collation = nil)
11
- super(name, default, sql_type, null)
12
- @collation = collation
13
- end
14
-
15
- def extract_default(default)
16
- if sql_type =~ /blob/i || type == :text
17
- if default.blank?
18
- return null ? nil : ''
19
- else
20
- raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
21
- end
22
- elsif missing_default_forged_as_empty_string?(default)
23
- nil
24
- else
25
- super
26
- end
27
- end
28
-
29
- def has_default?
30
- return false if sql_type =~ /blob/i || type == :text #mysql forbids defaults on blob and text columns
31
- super
32
- end
33
-
34
- # Must return the relevant concrete adapter
35
- def adapter
36
- raise NotImplementedError
37
- end
38
-
39
- def case_sensitive?
40
- collation && !collation.match(/_ci$/)
41
- end
42
-
43
- private
44
-
45
- def simplified_type(field_type)
46
- return :boolean if adapter.emulate_booleans && field_type.downcase.index("tinyint(1)")
47
-
48
- case field_type
49
- when /enum/i, /set/i then :string
50
- when /year/i then :integer
51
- when /bit/i then :binary
52
- else
53
- super
54
- end
55
- end
19
+ def update_table_definition(table_name, base) # :nodoc:
20
+ MySQL::Table.new(table_name, base)
21
+ end
56
22
 
57
- def extract_limit(sql_type)
58
- case sql_type
59
- when /blob|text/i
60
- case sql_type
61
- when /tiny/i
62
- 255
63
- when /medium/i
64
- 16777215
65
- when /long/i
66
- 2147483647 # mysql only allows 2^31-1, not 2^32-1, somewhat inconsistently with the tiny/medium/normal cases
67
- else
68
- super # we could return 65535 here, but we leave it undecorated by default
69
- end
70
- when /^bigint/i; 8
71
- when /^int/i; 4
72
- when /^mediumint/i; 3
73
- when /^smallint/i; 2
74
- when /^tinyint/i; 1
75
- when /^enum\((.+)\)/i
76
- $1.split(',').map{|enum| enum.strip.length - 2}.max
77
- else
78
- super
79
- end
80
- end
23
+ def schema_creation # :nodoc:
24
+ MySQL::SchemaCreation.new(self)
25
+ end
81
26
 
82
- # MySQL misreports NOT NULL column default when none is given.
83
- # We can't detect this for columns which may have a legitimate ''
84
- # default (string) but we can for others (integer, datetime, boolean,
85
- # and the rest).
86
- #
87
- # Test whether the column has default '', is not null, and is not
88
- # a type allowing default ''.
89
- def missing_default_forged_as_empty_string?(default)
90
- type != :string && !null && default == ''
91
- end
27
+ def arel_visitor # :nodoc:
28
+ Arel::Visitors::MySQL.new(self)
92
29
  end
93
30
 
94
31
  ##
95
32
  # :singleton-method:
96
- # By default, the MysqlAdapter will consider all columns of type <tt>tinyint(1)</tt>
97
- # as boolean. If you wish to disable this emulation (which was the default
98
- # behavior in versions 0.13.1 and earlier) you can add the following line
33
+ # By default, the Mysql2Adapter will consider all columns of type <tt>tinyint(1)</tt>
34
+ # as boolean. If you wish to disable this emulation you can add the following line
99
35
  # to your application.rb file:
100
36
  #
101
- # ActiveRecord::ConnectionAdapters::Mysql[2]Adapter.emulate_booleans = false
37
+ # ActiveRecord::ConnectionAdapters::Mysql2Adapter.emulate_booleans = false
102
38
  class_attribute :emulate_booleans
103
39
  self.emulate_booleans = true
104
40
 
105
- LOST_CONNECTION_ERROR_MESSAGES = [
106
- "Server shutdown in progress",
107
- "Broken pipe",
108
- "Lost connection to MySQL server during query",
109
- "MySQL server has gone away" ]
110
-
111
- QUOTED_TRUE, QUOTED_FALSE = '1', '0'
112
-
113
41
  NATIVE_DATABASE_TYPES = {
114
- :primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY",
115
- :string => { :name => "varchar", :limit => 255 },
116
- :text => { :name => "text" },
117
- :integer => { :name => "int", :limit => 4 },
118
- :float => { :name => "float" },
119
- :decimal => { :name => "decimal" },
120
- :datetime => { :name => "datetime" },
121
- :timestamp => { :name => "datetime" },
122
- :time => { :name => "time" },
123
- :date => { :name => "date" },
124
- :binary => { :name => "blob" },
125
- :boolean => { :name => "tinyint", :limit => 1 }
42
+ primary_key: "int auto_increment PRIMARY KEY",
43
+ string: { name: "varchar", limit: 255 },
44
+ text: { name: "text" },
45
+ integer: { name: "int", limit: 4 },
46
+ float: { name: "float" },
47
+ decimal: { name: "decimal" },
48
+ datetime: { name: "datetime" },
49
+ time: { name: "time" },
50
+ date: { name: "date" },
51
+ binary: { name: "blob" },
52
+ boolean: { name: "tinyint", limit: 1 },
53
+ json: { name: "json" },
126
54
  }
127
55
 
128
- class BindSubstitution < Arel::Visitors::MySQL # :nodoc:
129
- include Arel::Visitors::BindVisitor
56
+ INDEX_TYPES = [:fulltext, :spatial]
57
+ INDEX_USINGS = [:btree, :hash]
58
+
59
+ class StatementPool < ConnectionAdapters::StatementPool
60
+ private def dealloc(stmt)
61
+ stmt[:stmt].close
62
+ end
130
63
  end
131
64
 
132
- # FIXME: Make the first parameter more similar for the two adapters
133
65
  def initialize(connection, logger, connection_options, config)
134
- super(connection, logger)
135
- @connection_options, @config = connection_options, config
136
- @quoted_column_names, @quoted_table_names = {}, {}
66
+ super(connection, logger, config)
137
67
 
138
- if config.fetch(:prepared_statements) { true }
139
- @visitor = Arel::Visitors::MySQL.new self
140
- else
141
- @visitor = BindSubstitution.new self
68
+ @statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit]))
69
+
70
+ if version < '5.0.0'
71
+ raise "Your version of MySQL (#{full_version.match(/^\d+\.\d+\.\d+/)[0]}) is too old. Active Record supports MySQL >= 5.0."
142
72
  end
143
73
  end
144
74
 
145
- def adapter_name #:nodoc:
146
- self.class::ADAPTER_NAME
75
+ CHARSETS_OF_4BYTES_MAXLEN = ['utf8mb4', 'utf16', 'utf16le', 'utf32']
76
+
77
+ def internal_string_options_for_primary_key # :nodoc:
78
+ super.tap { |options|
79
+ options[:collation] = collation.sub(/\A[^_]+/, 'utf8') if CHARSETS_OF_4BYTES_MAXLEN.include?(charset)
80
+ }
81
+ end
82
+
83
+ def version #:nodoc:
84
+ @version ||= Version.new(full_version.match(/^\d+\.\d+\.\d+/)[0])
85
+ end
86
+
87
+ def mariadb? # :nodoc:
88
+ full_version =~ /mariadb/i
147
89
  end
148
90
 
149
91
  # Returns true, since this connection adapter supports migrations.
@@ -155,12 +97,13 @@ module ActiveRecord
155
97
  true
156
98
  end
157
99
 
158
- # Returns true, since this connection adapter supports savepoints.
159
- def supports_savepoints?
100
+ def supports_bulk_alter? #:nodoc:
160
101
  true
161
102
  end
162
103
 
163
- def supports_bulk_alter? #:nodoc:
104
+ # Returns true, since this connection adapter supports prepared statement
105
+ # caching.
106
+ def supports_statement_cache?
164
107
  true
165
108
  end
166
109
 
@@ -170,61 +113,75 @@ module ActiveRecord
170
113
  true
171
114
  end
172
115
 
173
- def native_database_types
174
- NATIVE_DATABASE_TYPES
116
+ def supports_transaction_isolation?
117
+ true
175
118
  end
176
119
 
177
- # HELPER METHODS ===========================================
178
-
179
- # The two drivers have slightly different ways of yielding hashes of results, so
180
- # this method must be implemented to provide a uniform interface.
181
- def each_hash(result) # :nodoc:
182
- raise NotImplementedError
120
+ def supports_explain?
121
+ true
183
122
  end
184
123
 
185
- # Overridden by the adapters to instantiate their specific Column type.
186
- def new_column(field, default, type, null, collation) # :nodoc:
187
- Column.new(field, default, type, null, collation)
124
+ def supports_indexes_in_create?
125
+ true
188
126
  end
189
127
 
190
- # Must return the Mysql error number from the exception, if the exception has an
191
- # error number.
192
- def error_number(exception) # :nodoc:
193
- raise NotImplementedError
128
+ def supports_foreign_keys?
129
+ true
194
130
  end
195
131
 
196
- # QUOTING ==================================================
132
+ def supports_views?
133
+ true
134
+ end
197
135
 
198
- def quote(value, column = nil)
199
- if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary)
200
- s = column.class.string_to_binary(value).unpack("H*")[0]
201
- "x'#{s}'"
202
- elsif value.kind_of?(BigDecimal)
203
- value.to_s("F")
136
+ def supports_datetime_with_precision?
137
+ if mariadb?
138
+ version >= '5.3.0'
204
139
  else
205
- super
140
+ version >= '5.6.4'
206
141
  end
207
142
  end
208
143
 
209
- def quote_column_name(name) #:nodoc:
210
- @quoted_column_names[name] ||= "`#{name.to_s.gsub('`', '``')}`"
144
+ def supports_advisory_locks?
145
+ true
146
+ end
147
+
148
+ def get_advisory_lock(lock_name, timeout = 0) # :nodoc:
149
+ select_value("SELECT GET_LOCK('#{lock_name}', #{timeout});").to_s == '1'
150
+ end
151
+
152
+ def release_advisory_lock(lock_name) # :nodoc:
153
+ select_value("SELECT RELEASE_LOCK('#{lock_name}')").to_s == '1'
154
+ end
155
+
156
+ def native_database_types
157
+ NATIVE_DATABASE_TYPES
158
+ end
159
+
160
+ def index_algorithms
161
+ { default: 'ALGORITHM = DEFAULT', copy: 'ALGORITHM = COPY', inplace: 'ALGORITHM = INPLACE' }
211
162
  end
212
163
 
213
- def quote_table_name(name) #:nodoc:
214
- @quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`')
164
+ # HELPER METHODS ===========================================
165
+
166
+ # The two drivers have slightly different ways of yielding hashes of results, so
167
+ # this method must be implemented to provide a uniform interface.
168
+ def each_hash(result) # :nodoc:
169
+ raise NotImplementedError
215
170
  end
216
171
 
217
- def quoted_true
218
- QUOTED_TRUE
172
+ def new_column(*args) #:nodoc:
173
+ MySQL::Column.new(*args)
219
174
  end
220
175
 
221
- def quoted_false
222
- QUOTED_FALSE
176
+ # Must return the MySQL error number from the exception, if the exception has an
177
+ # error number.
178
+ def error_number(exception) # :nodoc:
179
+ raise NotImplementedError
223
180
  end
224
181
 
225
182
  # REFERENTIAL INTEGRITY ====================================
226
183
 
227
- def disable_referential_integrity(&block) #:nodoc:
184
+ def disable_referential_integrity #:nodoc:
228
185
  old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
229
186
 
230
187
  begin
@@ -235,121 +192,95 @@ module ActiveRecord
235
192
  end
236
193
  end
237
194
 
195
+ # CONNECTION MANAGEMENT ====================================
196
+
197
+ # Clears the prepared statements cache.
198
+ def clear_cache!
199
+ reload_type_map
200
+ @statements.clear
201
+ end
202
+
203
+ #--
238
204
  # DATABASE STATEMENTS ======================================
205
+ #++
206
+
207
+ def explain(arel, binds = [])
208
+ sql = "EXPLAIN #{to_sql(arel, binds)}"
209
+ start = Time.now
210
+ result = exec_query(sql, 'EXPLAIN', binds)
211
+ elapsed = Time.now - start
212
+
213
+ MySQL::ExplainPrettyPrinter.new.pp(result, elapsed)
214
+ end
239
215
 
240
216
  # Executes the SQL statement in the context of this connection.
241
217
  def execute(sql, name = nil)
242
- if name == :skip_logging
243
- @connection.query(sql)
244
- else
245
- log(sql, name) { @connection.query(sql) }
246
- end
247
- rescue ActiveRecord::StatementInvalid => exception
248
- if exception.message.split(":").first =~ /Packets out of order/
249
- raise ActiveRecord::StatementInvalid, "'Packets out of order' error was received from the database. Please update your mysql bindings (gem install mysql) and read http://dev.mysql.com/doc/mysql/en/password-hashing.html for more information. If you're on Windows, use the Instant Rails installer to get the updated mysql bindings."
250
- else
251
- raise
252
- end
218
+ log(sql, name) { @connection.query(sql) }
253
219
  end
254
220
 
255
- # MysqlAdapter has to free a result after using it, so we use this method to write
256
- # stuff in a abstract way without concerning ourselves about whether it needs to be
257
- # explicitly freed or not.
258
- def execute_and_free(sql, name = nil) #:nodoc:
221
+ # Mysql2Adapter doesn't have to free a result after using it, but we use this method
222
+ # to write stuff in an abstract way without concerning ourselves about whether it
223
+ # needs to be explicitly freed or not.
224
+ def execute_and_free(sql, name = nil) # :nodoc:
259
225
  yield execute(sql, name)
260
226
  end
261
227
 
262
- def update_sql(sql, name = nil) #:nodoc:
263
- super
264
- @connection.affected_rows
265
- end
266
-
267
228
  def begin_db_transaction
268
229
  execute "BEGIN"
269
- rescue Exception
270
- # Transactions aren't supported
230
+ end
231
+
232
+ def begin_isolated_db_transaction(isolation)
233
+ execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}"
234
+ begin_db_transaction
271
235
  end
272
236
 
273
237
  def commit_db_transaction #:nodoc:
274
238
  execute "COMMIT"
275
- rescue Exception
276
- # Transactions aren't supported
277
239
  end
278
240
 
279
- def rollback_db_transaction #:nodoc:
241
+ def exec_rollback_db_transaction #:nodoc:
280
242
  execute "ROLLBACK"
281
- rescue Exception
282
- # Transactions aren't supported
283
- end
284
-
285
- def create_savepoint
286
- execute("SAVEPOINT #{current_savepoint_name}")
287
- end
288
-
289
- def rollback_to_savepoint
290
- execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
291
- end
292
-
293
- def release_savepoint
294
- execute("RELEASE SAVEPOINT #{current_savepoint_name}")
295
243
  end
296
244
 
297
245
  # In the simple case, MySQL allows us to place JOINs directly into the UPDATE
298
246
  # query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
299
- # these, we must use a subquery. However, MySQL is too stupid to create a
300
- # temporary table for this automatically, so we have to give it some prompting
301
- # in the form of a subsubquery. Ugh!
302
- def join_to_update(update, select) #:nodoc:
247
+ # these, we must use a subquery.
248
+ def join_to_update(update, select, key) # :nodoc:
303
249
  if select.limit || select.offset || select.orders.any?
304
- subsubselect = select.clone
305
- subsubselect.projections = [update.key]
306
-
307
- subselect = Arel::SelectManager.new(select.engine)
308
- subselect.project Arel.sql(update.key.name)
309
- subselect.from subsubselect.as('__active_record_temp')
310
-
311
- update.where update.key.in(subselect)
250
+ super
312
251
  else
313
252
  update.table select.source
314
253
  update.wheres = select.constraints
315
254
  end
316
255
  end
317
256
 
318
- # SCHEMA STATEMENTS ========================================
319
-
320
- def structure_dump #:nodoc:
321
- if supports_views?
322
- sql = "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'"
323
- else
324
- sql = "SHOW TABLES"
325
- end
326
-
327
- select_all(sql).map { |table|
328
- table.delete('Table_type')
329
- sql = "SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}"
330
- exec_query(sql).first['Create Table'] + ";\n\n"
331
- }.join
257
+ def empty_insert_statement_value
258
+ "VALUES ()"
332
259
  end
333
260
 
261
+ # SCHEMA STATEMENTS ========================================
262
+
334
263
  # Drops the database specified on the +name+ attribute
335
264
  # and creates it again using the provided +options+.
336
265
  def recreate_database(name, options = {})
337
266
  drop_database(name)
338
- create_database(name, options)
267
+ sql = create_database(name, options)
268
+ reconnect!
269
+ sql
339
270
  end
340
271
 
341
272
  # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
342
273
  # Charset defaults to utf8.
343
274
  #
344
275
  # Example:
345
- # create_database 'charset_test', :charset => 'latin1', :collation => 'latin1_bin'
276
+ # create_database 'charset_test', charset: 'latin1', collation: 'latin1_bin'
346
277
  # create_database 'matt_development'
347
- # create_database 'matt_development', :charset => :big5
278
+ # create_database 'matt_development', charset: :big5
348
279
  def create_database(name, options = {})
349
280
  if options[:collation]
350
- execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{options[:collation]}`"
281
+ execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset] || 'utf8')} COLLATE #{quote_table_name(options[:collation])}"
351
282
  else
352
- execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}`"
283
+ execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset] || 'utf8')}"
353
284
  end
354
285
  end
355
286
 
@@ -358,7 +289,7 @@ module ActiveRecord
358
289
  # Example:
359
290
  # drop_database('sebastian_development')
360
291
  def drop_database(name) #:nodoc:
361
- execute "DROP DATABASE IF EXISTS `#{name}`"
292
+ execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
362
293
  end
363
294
 
364
295
  def current_database
@@ -375,29 +306,68 @@ module ActiveRecord
375
306
  show_variable 'collation_database'
376
307
  end
377
308
 
378
- def tables(name = nil, database = nil, like = nil) #:nodoc:
379
- sql = "SHOW TABLES "
380
- sql << "IN #{quote_table_name(database)} " if database
381
- sql << "LIKE #{quote(like)}" if like
309
+ def tables(name = nil) # :nodoc:
310
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
311
+ #tables currently returns both tables and views.
312
+ This behavior is deprecated and will be changed with Rails 5.1 to only return tables.
313
+ Use #data_sources instead.
314
+ MSG
382
315
 
383
- execute_and_free(sql, 'SCHEMA') do |result|
384
- result.collect { |field| field.first }
316
+ if name
317
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
318
+ Passing arguments to #tables is deprecated without replacement.
319
+ MSG
385
320
  end
321
+
322
+ data_sources
386
323
  end
387
324
 
388
- def table_exists?(name)
389
- return false unless name
390
- return true if tables(nil, nil, name).any?
325
+ def data_sources
326
+ sql = "SELECT table_name FROM information_schema.tables "
327
+ sql << "WHERE table_schema = #{quote(@config[:database])}"
391
328
 
392
- name = name.to_s
393
- schema, table = name.split('.', 2)
329
+ select_values(sql, 'SCHEMA')
330
+ end
394
331
 
395
- unless table # A table was provided without a schema
396
- table = schema
397
- schema = nil
398
- end
332
+ def truncate(table_name, name = nil)
333
+ execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
334
+ end
335
+
336
+ def table_exists?(table_name)
337
+ # Update lib/active_record/internal_metadata.rb when this gets removed
338
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
339
+ #table_exists? currently checks both tables and views.
340
+ This behavior is deprecated and will be changed with Rails 5.1 to only check tables.
341
+ Use #data_source_exists? instead.
342
+ MSG
399
343
 
400
- tables(nil, schema, table).any?
344
+ data_source_exists?(table_name)
345
+ end
346
+
347
+ def data_source_exists?(table_name)
348
+ return false unless table_name.present?
349
+
350
+ schema, name = extract_schema_qualified_name(table_name)
351
+
352
+ sql = "SELECT table_name FROM information_schema.tables "
353
+ sql << "WHERE table_schema = #{quote(schema)} AND table_name = #{quote(name)}"
354
+
355
+ select_values(sql, 'SCHEMA').any?
356
+ end
357
+
358
+ def views # :nodoc:
359
+ select_values("SHOW FULL TABLES WHERE table_type = 'VIEW'", 'SCHEMA')
360
+ end
361
+
362
+ def view_exists?(view_name) # :nodoc:
363
+ return false unless view_name.present?
364
+
365
+ schema, name = extract_schema_qualified_name(view_name)
366
+
367
+ sql = "SELECT table_name FROM information_schema.tables WHERE table_type = 'VIEW'"
368
+ sql << " AND table_schema = #{quote(schema)} AND table_name = #{quote(name)}"
369
+
370
+ select_values(sql, 'SCHEMA').any?
401
371
  end
402
372
 
403
373
  # Returns an array of indexes for the given table.
@@ -409,7 +379,11 @@ module ActiveRecord
409
379
  if current_index != row[:Key_name]
410
380
  next if row[:Key_name] == 'PRIMARY' # skip the primary key
411
381
  current_index = row[:Key_name]
412
- indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], [])
382
+
383
+ mysql_index_type = row[:Index_type].downcase.to_sym
384
+ index_type = INDEX_TYPES.include?(mysql_index_type) ? mysql_index_type : nil
385
+ index_using = INDEX_USINGS.include?(mysql_index_type) ? mysql_index_type : nil
386
+ indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], [], nil, nil, index_type, index_using, row[:Index_comment].presence)
413
387
  end
414
388
 
415
389
  indexes.last.columns << row[:Column_name]
@@ -421,21 +395,33 @@ module ActiveRecord
421
395
  end
422
396
 
423
397
  # Returns an array of +Column+ objects for the table specified by +table_name+.
424
- def columns(table_name, name = nil)#:nodoc:
425
- sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
426
- execute_and_free(sql, 'SCHEMA') do |result|
427
- each_hash(result).map do |field|
428
- new_column(field[:Field], field[:Default], field[:Type], field[:Null] == "YES", field[:Collation])
398
+ def columns(table_name) # :nodoc:
399
+ table_name = table_name.to_s
400
+ column_definitions(table_name).map do |field|
401
+ type_metadata = fetch_type_metadata(field[:Type], field[:Extra])
402
+ if type_metadata.type == :datetime && field[:Default] == "CURRENT_TIMESTAMP"
403
+ default, default_function = nil, field[:Default]
404
+ else
405
+ default, default_function = field[:Default], nil
429
406
  end
407
+ new_column(field[:Field], default, type_metadata, field[:Null] == "YES", table_name, default_function, field[:Collation], comment: field[:Comment].presence)
430
408
  end
431
409
  end
432
410
 
433
- def create_table(table_name, options = {}) #:nodoc:
434
- super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
411
+ def table_comment(table_name) # :nodoc:
412
+ select_value(<<-SQL.strip_heredoc, 'SCHEMA')
413
+ SELECT table_comment
414
+ FROM information_schema.tables
415
+ WHERE table_name=#{quote(table_name)}
416
+ SQL
417
+ end
418
+
419
+ def create_table(table_name, **options) #:nodoc:
420
+ super(table_name, options: 'ENGINE=InnoDB', **options)
435
421
  end
436
422
 
437
423
  def bulk_change_table(table_name, operations) #:nodoc:
438
- sqls = operations.map do |command, args|
424
+ sqls = operations.flat_map do |command, args|
439
425
  table, arguments = args.shift, args
440
426
  method = :"#{command}_sql"
441
427
 
@@ -444,7 +430,7 @@ module ActiveRecord
444
430
  else
445
431
  raise "Unknown method called : #{method}(#{arguments.inspect})"
446
432
  end
447
- end.flatten.join(", ")
433
+ end.join(", ")
448
434
 
449
435
  execute("ALTER TABLE #{quote_table_name(table_name)} #{sqls}")
450
436
  end
@@ -455,18 +441,46 @@ module ActiveRecord
455
441
  # rename_table('octopuses', 'octopi')
456
442
  def rename_table(table_name, new_name)
457
443
  execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
444
+ rename_table_indexes(table_name, new_name)
445
+ end
446
+
447
+ # Drops a table from the database.
448
+ #
449
+ # [<tt>:force</tt>]
450
+ # Set to +:cascade+ to drop dependent objects as well.
451
+ # Defaults to false.
452
+ # [<tt>:if_exists</tt>]
453
+ # Set to +true+ to only drop the table if it exists.
454
+ # Defaults to false.
455
+ # [<tt>:temporary</tt>]
456
+ # Set to +true+ to drop temporary table.
457
+ # Defaults to false.
458
+ #
459
+ # Although this command ignores most +options+ and the block if one is given,
460
+ # it can be helpful to provide these in a migration's +change+ method so it can be reverted.
461
+ # In that case, +options+ and the block will be used by create_table.
462
+ def drop_table(table_name, options = {})
463
+ create_table_info_cache.delete(table_name) if create_table_info_cache.key?(table_name)
464
+ execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
458
465
  end
459
466
 
460
- def add_column(table_name, column_name, type, options = {})
461
- execute("ALTER TABLE #{quote_table_name(table_name)} #{add_column_sql(table_name, column_name, type, options)}")
467
+ def rename_index(table_name, old_name, new_name)
468
+ if supports_rename_index?
469
+ validate_index_length!(table_name, new_name)
470
+
471
+ execute "ALTER TABLE #{quote_table_name(table_name)} RENAME INDEX #{quote_table_name(old_name)} TO #{quote_table_name(new_name)}"
472
+ else
473
+ super
474
+ end
462
475
  end
463
476
 
464
- def change_column_default(table_name, column_name, default)
477
+ def change_column_default(table_name, column_name, default_or_changes) #:nodoc:
478
+ default = extract_new_default_value(default_or_changes)
465
479
  column = column_for(table_name, column_name)
466
480
  change_column table_name, column_name, column.sql_type, :default => default
467
481
  end
468
482
 
469
- def change_column_null(table_name, column_name, null, default = nil)
483
+ def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
470
484
  column = column_for(table_name, column_name)
471
485
 
472
486
  unless null || default.nil?
@@ -482,90 +496,218 @@ module ActiveRecord
482
496
 
483
497
  def rename_column(table_name, column_name, new_column_name) #:nodoc:
484
498
  execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_sql(table_name, column_name, new_column_name)}")
499
+ rename_column_indexes(table_name, column_name, new_column_name)
500
+ end
501
+
502
+ def add_index(table_name, column_name, options = {}) #:nodoc:
503
+ index_name, index_type, index_columns, _, index_algorithm, index_using, comment = add_index_options(table_name, column_name, options)
504
+ sql = "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns}) #{index_algorithm}"
505
+ execute add_sql_comment!(sql, comment)
506
+ end
507
+
508
+ def add_sql_comment!(sql, comment) # :nodoc:
509
+ sql << " COMMENT #{quote(comment)}" if comment
510
+ sql
511
+ end
512
+
513
+ def foreign_keys(table_name)
514
+ raise ArgumentError unless table_name.present?
515
+
516
+ schema, name = extract_schema_qualified_name(table_name)
517
+
518
+ fk_info = select_all <<-SQL.strip_heredoc
519
+ SELECT fk.referenced_table_name as 'to_table'
520
+ ,fk.referenced_column_name as 'primary_key'
521
+ ,fk.column_name as 'column'
522
+ ,fk.constraint_name as 'name'
523
+ FROM information_schema.key_column_usage fk
524
+ WHERE fk.referenced_column_name is not null
525
+ AND fk.table_schema = #{quote(schema)}
526
+ AND fk.table_name = #{quote(name)}
527
+ SQL
528
+
529
+ create_table_info = create_table_info(table_name)
530
+
531
+ fk_info.map do |row|
532
+ options = {
533
+ column: row['column'],
534
+ name: row['name'],
535
+ primary_key: row['primary_key']
536
+ }
537
+
538
+ options[:on_update] = extract_foreign_key_action(create_table_info, row['name'], "UPDATE")
539
+ options[:on_delete] = extract_foreign_key_action(create_table_info, row['name'], "DELETE")
540
+
541
+ ForeignKeyDefinition.new(table_name, row['to_table'], options)
542
+ end
543
+ end
544
+
545
+ def table_options(table_name)
546
+ create_table_info = create_table_info(table_name)
547
+
548
+ # strip create_definitions and partition_options
549
+ raw_table_options = create_table_info.sub(/\A.*\n\) /m, '').sub(/\n\/\*!.*\*\/\n\z/m, '').strip
550
+
551
+ # strip AUTO_INCREMENT
552
+ raw_table_options.sub!(/(ENGINE=\w+)(?: AUTO_INCREMENT=\d+)/, '\1')
553
+
554
+ # strip COMMENT
555
+ raw_table_options.sub!(/ COMMENT='.+'/, '')
556
+
557
+ raw_table_options
485
558
  end
486
559
 
487
560
  # Maps logical Rails types to MySQL-specific data types.
488
- def type_to_sql(type, limit = nil, precision = nil, scale = nil)
489
- case type.to_s
561
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil, unsigned = nil)
562
+ sql = case type.to_s
490
563
  when 'integer'
491
- case limit
492
- when 1; 'tinyint'
493
- when 2; 'smallint'
494
- when 3; 'mediumint'
495
- when nil, 4, 11; 'int(11)' # compatibility with MySQL default
496
- when 5..8; 'bigint'
497
- else raise(ActiveRecordError, "No integer type has byte size #{limit}")
498
- end
564
+ integer_to_sql(limit)
499
565
  when 'text'
500
- case limit
501
- when 0..0xff; 'tinytext'
502
- when nil, 0x100..0xffff; 'text'
503
- when 0x10000..0xffffff; 'mediumtext'
504
- when 0x1000000..0xffffffff; 'longtext'
505
- else raise(ActiveRecordError, "No text type has character length #{limit}")
566
+ text_to_sql(limit)
567
+ when 'blob'
568
+ binary_to_sql(limit)
569
+ when 'binary'
570
+ if (0..0xfff) === limit
571
+ "varbinary(#{limit})"
572
+ else
573
+ binary_to_sql(limit)
506
574
  end
507
575
  else
508
- super
576
+ super(type, limit, precision, scale)
509
577
  end
510
- end
511
578
 
512
- def add_column_position!(sql, options)
513
- if options[:first]
514
- sql << " FIRST"
515
- elsif options[:after]
516
- sql << " AFTER #{quote_column_name(options[:after])}"
517
- end
579
+ sql << ' unsigned' if unsigned && type != :primary_key
580
+ sql
518
581
  end
519
582
 
520
583
  # SHOW VARIABLES LIKE 'name'
521
584
  def show_variable(name)
522
- variables = select_all("SHOW VARIABLES LIKE '#{name}'")
523
- variables.first['Value'] unless variables.empty?
585
+ select_value("SELECT @@#{name}", 'SCHEMA')
586
+ rescue ActiveRecord::StatementInvalid
587
+ nil
524
588
  end
525
589
 
526
- # Returns a table's primary key and belonging sequence.
527
- def pk_and_sequence_for(table)
528
- execute_and_free("SHOW CREATE TABLE #{quote_table_name(table)}", 'SCHEMA') do |result|
529
- create_table = each_hash(result).first[:"Create Table"]
530
- if create_table.to_s =~ /PRIMARY KEY\s+(?:USING\s+\w+\s+)?\((.+)\)/
531
- keys = $1.split(",").map { |key| key.gsub(/[`"]/, "") }
532
- keys.length == 1 ? [keys.first, nil] : nil
533
- else
534
- nil
535
- end
590
+ def primary_keys(table_name) # :nodoc:
591
+ raise ArgumentError unless table_name.present?
592
+
593
+ schema, name = extract_schema_qualified_name(table_name)
594
+
595
+ select_values(<<-SQL.strip_heredoc, 'SCHEMA')
596
+ SELECT column_name
597
+ FROM information_schema.key_column_usage
598
+ WHERE constraint_name = 'PRIMARY'
599
+ AND table_schema = #{quote(schema)}
600
+ AND table_name = #{quote(name)}
601
+ ORDER BY ordinal_position
602
+ SQL
603
+ end
604
+
605
+ def case_sensitive_comparison(table, attribute, column, value)
606
+ if !value.nil? && column.collation && !column.case_sensitive?
607
+ table[attribute].eq(Arel::Nodes::Bin.new(Arel::Nodes::BindParam.new))
608
+ else
609
+ super
536
610
  end
537
611
  end
538
612
 
539
- # Returns just a table's primary key
540
- def primary_key(table)
541
- pk_and_sequence = pk_and_sequence_for(table)
542
- pk_and_sequence && pk_and_sequence.first
613
+ def can_perform_case_insensitive_comparison_for?(column)
614
+ column.case_sensitive?
543
615
  end
616
+ private :can_perform_case_insensitive_comparison_for?
617
+
618
+ # In MySQL 5.7.5 and up, ONLY_FULL_GROUP_BY affects handling of queries that use
619
+ # DISTINCT and ORDER BY. It requires the ORDER BY columns in the select list for
620
+ # distinct queries, and requires that the ORDER BY include the distinct column.
621
+ # See https://dev.mysql.com/doc/refman/5.7/en/group-by-handling.html
622
+ def columns_for_distinct(columns, orders) # :nodoc:
623
+ order_columns = orders.reject(&:blank?).map { |s|
624
+ # Convert Arel node to string
625
+ s = s.to_sql unless s.is_a?(String)
626
+ # Remove any ASC/DESC modifiers
627
+ s.gsub(/\s+(?:ASC|DESC)\b/i, '')
628
+ }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
544
629
 
545
- def case_sensitive_modifier(node)
546
- Arel::Nodes::Bin.new(node)
630
+ [super, *order_columns].join(', ')
547
631
  end
548
632
 
549
- def case_insensitive_comparison(table, attribute, column, value)
550
- if column.case_sensitive?
551
- super
552
- else
553
- table[attribute].eq(value)
554
- end
633
+ def strict_mode?
634
+ self.class.type_cast_config_to_boolean(@config.fetch(:strict, true))
555
635
  end
556
636
 
557
- def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
558
- where_sql
637
+ def valid_type?(type)
638
+ !native_database_types[type].nil?
559
639
  end
560
640
 
561
641
  protected
562
642
 
643
+ def initialize_type_map(m) # :nodoc:
644
+ super
645
+
646
+ register_class_with_limit m, %r(char)i, MysqlString
647
+
648
+ m.register_type %r(tinytext)i, Type::Text.new(limit: 2**8 - 1)
649
+ m.register_type %r(tinyblob)i, Type::Binary.new(limit: 2**8 - 1)
650
+ m.register_type %r(text)i, Type::Text.new(limit: 2**16 - 1)
651
+ m.register_type %r(blob)i, Type::Binary.new(limit: 2**16 - 1)
652
+ m.register_type %r(mediumtext)i, Type::Text.new(limit: 2**24 - 1)
653
+ m.register_type %r(mediumblob)i, Type::Binary.new(limit: 2**24 - 1)
654
+ m.register_type %r(longtext)i, Type::Text.new(limit: 2**32 - 1)
655
+ m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1)
656
+ m.register_type %r(^float)i, Type::Float.new(limit: 24)
657
+ m.register_type %r(^double)i, Type::Float.new(limit: 53)
658
+ m.register_type %r(^json)i, MysqlJson.new
659
+
660
+ register_integer_type m, %r(^bigint)i, limit: 8
661
+ register_integer_type m, %r(^int)i, limit: 4
662
+ register_integer_type m, %r(^mediumint)i, limit: 3
663
+ register_integer_type m, %r(^smallint)i, limit: 2
664
+ register_integer_type m, %r(^tinyint)i, limit: 1
665
+
666
+ m.register_type %r(^tinyint\(1\))i, Type::Boolean.new if emulate_booleans
667
+ m.alias_type %r(year)i, 'integer'
668
+ m.alias_type %r(bit)i, 'binary'
669
+
670
+ m.register_type(%r(enum)i) do |sql_type|
671
+ limit = sql_type[/^enum\((.+)\)/i, 1]
672
+ .split(',').map{|enum| enum.strip.length - 2}.max
673
+ MysqlString.new(limit: limit)
674
+ end
675
+
676
+ m.register_type(%r(^set)i) do |sql_type|
677
+ limit = sql_type[/^set\((.+)\)/i, 1]
678
+ .split(',').map{|set| set.strip.length - 1}.sum - 1
679
+ MysqlString.new(limit: limit)
680
+ end
681
+ end
682
+
683
+ def register_integer_type(mapping, key, options) # :nodoc:
684
+ mapping.register_type(key) do |sql_type|
685
+ if /\bunsigned\z/ === sql_type
686
+ Type::UnsignedInteger.new(options)
687
+ else
688
+ Type::Integer.new(options)
689
+ end
690
+ end
691
+ end
692
+
693
+ def extract_precision(sql_type)
694
+ if /time/ === sql_type
695
+ super || 0
696
+ else
697
+ super
698
+ end
699
+ end
700
+
701
+ def fetch_type_metadata(sql_type, extra = "")
702
+ MySQL::TypeMetadata.new(super(sql_type), extra: extra, strict: strict_mode?)
703
+ end
704
+
563
705
  def add_index_length(option_strings, column_names, options = {})
564
706
  if options.is_a?(Hash) && length = options[:length]
565
707
  case length
566
708
  when Hash
567
709
  column_names.each {|name| option_strings[name] += "(#{length[name]})" if length.has_key?(name) && length[name].present?}
568
- when Fixnum
710
+ when Integer
569
711
  column_names.each {|name| option_strings[name] += "(#{length})"}
570
712
  end
571
713
  end
@@ -588,19 +730,20 @@ module ActiveRecord
588
730
  def translate_exception(exception, message)
589
731
  case error_number(exception)
590
732
  when 1062
591
- RecordNotUnique.new(message, exception)
733
+ RecordNotUnique.new(message)
592
734
  when 1452
593
- InvalidForeignKey.new(message, exception)
735
+ InvalidForeignKey.new(message)
736
+ when 1406
737
+ ValueTooLong.new(message)
594
738
  else
595
739
  super
596
740
  end
597
741
  end
598
742
 
599
743
  def add_column_sql(table_name, column_name, type, options = {})
600
- add_column_sql = "ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
601
- add_column_options!(add_column_sql, options)
602
- add_column_position!(add_column_sql, options)
603
- add_column_sql
744
+ td = create_table_definition(table_name)
745
+ cd = td.new_column_definition(column_name, type, options)
746
+ schema_creation.accept(AddColumnDefinition.new(cd))
604
747
  end
605
748
 
606
749
  def change_column_sql(table_name, column_name, type, options = {})
@@ -614,36 +757,37 @@ module ActiveRecord
614
757
  options[:null] = column.null
615
758
  end
616
759
 
617
- change_column_sql = "CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
618
- add_column_options!(change_column_sql, options)
619
- add_column_position!(change_column_sql, options)
620
- change_column_sql
760
+ td = create_table_definition(table_name)
761
+ cd = td.new_column_definition(column.name, type, options)
762
+ schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
621
763
  end
622
764
 
623
765
  def rename_column_sql(table_name, column_name, new_column_name)
624
- options = {}
766
+ column = column_for(table_name, column_name)
767
+ options = {
768
+ default: column.default,
769
+ null: column.null,
770
+ auto_increment: column.auto_increment?
771
+ }
625
772
 
626
- if column = columns(table_name).find { |c| c.name == column_name.to_s }
627
- options[:default] = column.default
628
- options[:null] = column.null
629
- else
630
- raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
631
- end
773
+ current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", 'SCHEMA')["Type"]
774
+ td = create_table_definition(table_name)
775
+ cd = td.new_column_definition(new_column_name, current_type, options)
776
+ schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
777
+ end
632
778
 
633
- current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"]
634
- rename_column_sql = "CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}"
635
- add_column_options!(rename_column_sql, options)
636
- rename_column_sql
779
+ def remove_column_sql(table_name, column_name, type = nil, options = {})
780
+ "DROP #{quote_column_name(column_name)}"
637
781
  end
638
782
 
639
- def remove_column_sql(table_name, *column_names)
640
- columns_for_remove(table_name, *column_names).map {|column_name| "DROP #{column_name}" }
783
+ def remove_columns_sql(table_name, *column_names)
784
+ column_names.map {|column_name| remove_column_sql(table_name, column_name) }
641
785
  end
642
- alias :remove_columns_sql :remove_column
643
786
 
644
787
  def add_index_sql(table_name, column_name, options = {})
645
- index_name, index_type, index_columns = add_index_options(table_name, column_name, options)
646
- "ADD #{index_type} INDEX #{index_name} (#{index_columns})"
788
+ index_name, index_type, index_columns, _, index_algorithm, index_using = add_index_options(table_name, column_name, options)
789
+ index_algorithm[0, 0] = ", " if index_algorithm.present?
790
+ "ADD #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_algorithm}"
647
791
  end
648
792
 
649
793
  def remove_index_sql(table_name, options = {})
@@ -651,26 +795,183 @@ module ActiveRecord
651
795
  "DROP INDEX #{index_name}"
652
796
  end
653
797
 
654
- def add_timestamps_sql(table_name)
655
- [add_column_sql(table_name, :created_at, :datetime), add_column_sql(table_name, :updated_at, :datetime)]
798
+ def add_timestamps_sql(table_name, options = {})
799
+ [add_column_sql(table_name, :created_at, :datetime, options), add_column_sql(table_name, :updated_at, :datetime, options)]
656
800
  end
657
801
 
658
- def remove_timestamps_sql(table_name)
802
+ def remove_timestamps_sql(table_name, options = {})
659
803
  [remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)]
660
804
  end
661
805
 
662
806
  private
663
807
 
664
- def supports_views?
665
- version[0] >= 5
808
+ # MySQL is too stupid to create a temporary table for use subquery, so we have
809
+ # to give it some prompting in the form of a subsubquery. Ugh!
810
+ def subquery_for(key, select)
811
+ subsubselect = select.clone
812
+ subsubselect.projections = [key]
813
+
814
+ # Materialize subquery by adding distinct
815
+ # to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
816
+ subsubselect.distinct unless select.limit || select.offset || select.orders.any?
817
+
818
+ subselect = Arel::SelectManager.new(select.engine)
819
+ subselect.project Arel.sql(key.name)
820
+ subselect.from subsubselect.as('__active_record_temp')
821
+ end
822
+
823
+ def supports_rename_index?
824
+ mariadb? ? false : version >= '5.7.6'
825
+ end
826
+
827
+ def configure_connection
828
+ variables = @config.fetch(:variables, {}).stringify_keys
829
+
830
+ # By default, MySQL 'where id is null' selects the last inserted id; Turn this off.
831
+ variables['sql_auto_is_null'] = 0
832
+
833
+ # Increase timeout so the server doesn't disconnect us.
834
+ wait_timeout = @config[:wait_timeout]
835
+ wait_timeout = 2147483 unless wait_timeout.is_a?(Integer)
836
+ variables['wait_timeout'] = self.class.type_cast_config_to_integer(wait_timeout)
837
+
838
+ defaults = [':default', :default].to_set
839
+
840
+ # Make MySQL reject illegal values rather than truncating or blanking them, see
841
+ # http://dev.mysql.com/doc/refman/5.7/en/sql-mode.html#sqlmode_strict_all_tables
842
+ # If the user has provided another value for sql_mode, don't replace it.
843
+ if sql_mode = variables.delete('sql_mode')
844
+ sql_mode = quote(sql_mode)
845
+ elsif !defaults.include?(strict_mode?)
846
+ if strict_mode?
847
+ sql_mode = "CONCAT(@@sql_mode, ',STRICT_ALL_TABLES')"
848
+ else
849
+ sql_mode = "REPLACE(@@sql_mode, 'STRICT_TRANS_TABLES', '')"
850
+ sql_mode = "REPLACE(#{sql_mode}, 'STRICT_ALL_TABLES', '')"
851
+ sql_mode = "REPLACE(#{sql_mode}, 'TRADITIONAL', '')"
852
+ end
853
+ sql_mode = "CONCAT(#{sql_mode}, ',NO_AUTO_VALUE_ON_ZERO')"
854
+ end
855
+ sql_mode_assignment = "@@SESSION.sql_mode = #{sql_mode}, " if sql_mode
856
+
857
+ # NAMES does not have an equals sign, see
858
+ # http://dev.mysql.com/doc/refman/5.7/en/set-statement.html#id944430
859
+ # (trailing comma because variable_assignments will always have content)
860
+ if @config[:encoding]
861
+ encoding = "NAMES #{@config[:encoding]}"
862
+ encoding << " COLLATE #{@config[:collation]}" if @config[:collation]
863
+ encoding << ", "
864
+ end
865
+
866
+ # Gather up all of the SET variables...
867
+ variable_assignments = variables.map do |k, v|
868
+ if defaults.include?(v)
869
+ "@@SESSION.#{k} = DEFAULT" # Sets the value to the global or compile default
870
+ elsif !v.nil?
871
+ "@@SESSION.#{k} = #{quote(v)}"
872
+ end
873
+ # or else nil; compact to clear nils out
874
+ end.compact.join(', ')
875
+
876
+ # ...and send them all in one query
877
+ @connection.query "SET #{encoding} #{sql_mode_assignment} #{variable_assignments}"
878
+ end
879
+
880
+ def column_definitions(table_name) # :nodoc:
881
+ execute_and_free("SHOW FULL FIELDS FROM #{quote_table_name(table_name)}", 'SCHEMA') do |result|
882
+ each_hash(result)
883
+ end
666
884
  end
667
885
 
668
- def column_for(table_name, column_name)
669
- unless column = columns(table_name).find { |c| c.name == column_name.to_s }
670
- raise "No such column: #{table_name}.#{column_name}"
886
+ def extract_foreign_key_action(structure, name, action) # :nodoc:
887
+ if structure =~ /CONSTRAINT #{quote_column_name(name)} FOREIGN KEY .* REFERENCES .* ON #{action} (CASCADE|SET NULL|RESTRICT)/
888
+ case $1
889
+ when 'CASCADE'; :cascade
890
+ when 'SET NULL'; :nullify
891
+ end
892
+ end
893
+ end
894
+
895
+ def create_table_info_cache # :nodoc:
896
+ @create_table_info_cache ||= {}
897
+ end
898
+
899
+ def create_table_info(table_name) # :nodoc:
900
+ create_table_info_cache[table_name] ||= select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
901
+ end
902
+
903
+ def create_table_definition(*args) # :nodoc:
904
+ MySQL::TableDefinition.new(*args)
905
+ end
906
+
907
+ def extract_schema_qualified_name(string) # :nodoc:
908
+ schema, name = string.to_s.scan(/[^`.\s]+|`[^`]*`/)
909
+ schema, name = @config[:database], schema unless name
910
+ [schema, name]
911
+ end
912
+
913
+ def integer_to_sql(limit) # :nodoc:
914
+ case limit
915
+ when 1; 'tinyint'
916
+ when 2; 'smallint'
917
+ when 3; 'mediumint'
918
+ when nil, 4; 'int'
919
+ when 5..8; 'bigint'
920
+ else raise(ActiveRecordError, "No integer type has byte size #{limit}")
671
921
  end
672
- column
673
922
  end
923
+
924
+ def text_to_sql(limit) # :nodoc:
925
+ case limit
926
+ when 0..0xff; 'tinytext'
927
+ when nil, 0x100..0xffff; 'text'
928
+ when 0x10000..0xffffff; 'mediumtext'
929
+ when 0x1000000..0xffffffff; 'longtext'
930
+ else raise(ActiveRecordError, "No text type has byte length #{limit}")
931
+ end
932
+ end
933
+
934
+ def binary_to_sql(limit) # :nodoc:
935
+ case limit
936
+ when 0..0xff; 'tinyblob'
937
+ when nil, 0x100..0xffff; 'blob'
938
+ when 0x10000..0xffffff; 'mediumblob'
939
+ when 0x1000000..0xffffffff; 'longblob'
940
+ else raise(ActiveRecordError, "No binary type has byte length #{limit}")
941
+ end
942
+ end
943
+
944
+ class MysqlJson < Type::Internal::AbstractJson # :nodoc:
945
+ def changed_in_place?(raw_old_value, new_value)
946
+ # Normalization is required because MySQL JSON data format includes
947
+ # the space between the elements.
948
+ super(serialize(deserialize(raw_old_value)), new_value)
949
+ end
950
+ end
951
+
952
+ class MysqlString < Type::String # :nodoc:
953
+ def serialize(value)
954
+ case value
955
+ when true then MySQL::Quoting::QUOTED_TRUE
956
+ when false then MySQL::Quoting::QUOTED_FALSE
957
+ else super
958
+ end
959
+ end
960
+
961
+ private
962
+
963
+ def cast_value(value)
964
+ case value
965
+ when true then MySQL::Quoting::QUOTED_TRUE
966
+ when false then MySQL::Quoting::QUOTED_FALSE
967
+ else super
968
+ end
969
+ end
970
+ end
971
+
972
+ ActiveRecord::Type.register(:json, MysqlJson, adapter: :mysql2)
973
+ ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql2)
974
+ ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :mysql2)
674
975
  end
675
976
  end
676
977
  end