activerecord 3.2.22.5 → 5.2.8

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 (275) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +657 -621
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +41 -46
  5. data/examples/performance.rb +55 -42
  6. data/examples/simple.rb +6 -5
  7. data/lib/active_record/aggregations.rb +264 -236
  8. data/lib/active_record/association_relation.rb +40 -0
  9. data/lib/active_record/associations/alias_tracker.rb +47 -42
  10. data/lib/active_record/associations/association.rb +127 -75
  11. data/lib/active_record/associations/association_scope.rb +126 -92
  12. data/lib/active_record/associations/belongs_to_association.rb +78 -27
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +9 -4
  14. data/lib/active_record/associations/builder/association.rb +117 -32
  15. data/lib/active_record/associations/builder/belongs_to.rb +135 -60
  16. data/lib/active_record/associations/builder/collection_association.rb +61 -54
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +120 -42
  18. data/lib/active_record/associations/builder/has_many.rb +10 -64
  19. data/lib/active_record/associations/builder/has_one.rb +19 -51
  20. data/lib/active_record/associations/builder/singular_association.rb +28 -18
  21. data/lib/active_record/associations/collection_association.rb +226 -293
  22. data/lib/active_record/associations/collection_proxy.rb +1067 -69
  23. data/lib/active_record/associations/foreign_association.rb +13 -0
  24. data/lib/active_record/associations/has_many_association.rb +83 -47
  25. data/lib/active_record/associations/has_many_through_association.rb +98 -65
  26. data/lib/active_record/associations/has_one_association.rb +57 -20
  27. data/lib/active_record/associations/has_one_through_association.rb +18 -9
  28. data/lib/active_record/associations/join_dependency/join_association.rb +48 -126
  29. data/lib/active_record/associations/join_dependency/join_base.rb +11 -12
  30. data/lib/active_record/associations/join_dependency/join_part.rb +35 -42
  31. data/lib/active_record/associations/join_dependency.rb +212 -164
  32. data/lib/active_record/associations/preloader/association.rb +95 -89
  33. data/lib/active_record/associations/preloader/through_association.rb +84 -44
  34. data/lib/active_record/associations/preloader.rb +123 -111
  35. data/lib/active_record/associations/singular_association.rb +33 -24
  36. data/lib/active_record/associations/through_association.rb +60 -26
  37. data/lib/active_record/associations.rb +1759 -1506
  38. data/lib/active_record/attribute_assignment.rb +60 -193
  39. data/lib/active_record/attribute_decorators.rb +90 -0
  40. data/lib/active_record/attribute_methods/before_type_cast.rb +55 -8
  41. data/lib/active_record/attribute_methods/dirty.rb +113 -74
  42. data/lib/active_record/attribute_methods/primary_key.rb +106 -77
  43. data/lib/active_record/attribute_methods/query.rb +8 -5
  44. data/lib/active_record/attribute_methods/read.rb +63 -114
  45. data/lib/active_record/attribute_methods/serialization.rb +60 -90
  46. data/lib/active_record/attribute_methods/time_zone_conversion.rb +69 -43
  47. data/lib/active_record/attribute_methods/write.rb +43 -45
  48. data/lib/active_record/attribute_methods.rb +366 -149
  49. data/lib/active_record/attributes.rb +266 -0
  50. data/lib/active_record/autosave_association.rb +312 -225
  51. data/lib/active_record/base.rb +114 -505
  52. data/lib/active_record/callbacks.rb +145 -67
  53. data/lib/active_record/coders/json.rb +15 -0
  54. data/lib/active_record/coders/yaml_column.rb +32 -23
  55. data/lib/active_record/collection_cache_key.rb +53 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +883 -284
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +16 -2
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +350 -200
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +82 -27
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +150 -65
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +23 -0
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +146 -0
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +477 -284
  64. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +95 -0
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +1100 -310
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +283 -0
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +450 -118
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +657 -446
  69. data/lib/active_record/connection_adapters/column.rb +50 -255
  70. data/lib/active_record/connection_adapters/connection_specification.rb +287 -0
  71. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +33 -0
  72. data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
  73. data/lib/active_record/connection_adapters/mysql/database_statements.rb +140 -0
  74. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +72 -0
  75. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -0
  76. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +73 -0
  77. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +87 -0
  78. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +80 -0
  79. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +148 -0
  80. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +35 -0
  81. data/lib/active_record/connection_adapters/mysql2_adapter.rb +59 -210
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +44 -0
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +163 -0
  84. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +92 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +56 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +15 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +17 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +50 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +23 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +23 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +15 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +21 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +71 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +15 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +15 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +45 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +41 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +15 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +65 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +97 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +18 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +111 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +23 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +28 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +30 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid.rb +34 -0
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +168 -0
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +43 -0
  110. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +65 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +206 -0
  112. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +774 -0
  114. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +39 -0
  115. data/lib/active_record/connection_adapters/postgresql/utils.rb +81 -0
  116. data/lib/active_record/connection_adapters/postgresql_adapter.rb +620 -1080
  117. data/lib/active_record/connection_adapters/schema_cache.rb +85 -36
  118. data/lib/active_record/connection_adapters/sql_type_metadata.rb +34 -0
  119. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
  120. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +67 -0
  121. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +17 -0
  122. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
  123. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
  124. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +106 -0
  125. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +545 -27
  126. data/lib/active_record/connection_adapters/statement_pool.rb +34 -13
  127. data/lib/active_record/connection_handling.rb +145 -0
  128. data/lib/active_record/core.rb +559 -0
  129. data/lib/active_record/counter_cache.rb +200 -105
  130. data/lib/active_record/define_callbacks.rb +22 -0
  131. data/lib/active_record/dynamic_matchers.rb +107 -69
  132. data/lib/active_record/enum.rb +244 -0
  133. data/lib/active_record/errors.rb +245 -60
  134. data/lib/active_record/explain.rb +35 -71
  135. data/lib/active_record/explain_registry.rb +32 -0
  136. data/lib/active_record/explain_subscriber.rb +18 -9
  137. data/lib/active_record/fixture_set/file.rb +82 -0
  138. data/lib/active_record/fixtures.rb +418 -275
  139. data/lib/active_record/gem_version.rb +17 -0
  140. data/lib/active_record/inheritance.rb +209 -100
  141. data/lib/active_record/integration.rb +116 -21
  142. data/lib/active_record/internal_metadata.rb +45 -0
  143. data/lib/active_record/legacy_yaml_adapter.rb +48 -0
  144. data/lib/active_record/locale/en.yml +9 -1
  145. data/lib/active_record/locking/optimistic.rb +107 -94
  146. data/lib/active_record/locking/pessimistic.rb +20 -8
  147. data/lib/active_record/log_subscriber.rb +99 -34
  148. data/lib/active_record/migration/command_recorder.rb +199 -64
  149. data/lib/active_record/migration/compatibility.rb +217 -0
  150. data/lib/active_record/migration/join_table.rb +17 -0
  151. data/lib/active_record/migration.rb +893 -296
  152. data/lib/active_record/model_schema.rb +328 -175
  153. data/lib/active_record/nested_attributes.rb +338 -242
  154. data/lib/active_record/no_touching.rb +58 -0
  155. data/lib/active_record/null_relation.rb +68 -0
  156. data/lib/active_record/persistence.rb +557 -170
  157. data/lib/active_record/query_cache.rb +14 -43
  158. data/lib/active_record/querying.rb +36 -24
  159. data/lib/active_record/railtie.rb +147 -52
  160. data/lib/active_record/railties/console_sandbox.rb +5 -4
  161. data/lib/active_record/railties/controller_runtime.rb +13 -6
  162. data/lib/active_record/railties/databases.rake +206 -488
  163. data/lib/active_record/readonly_attributes.rb +4 -6
  164. data/lib/active_record/reflection.rb +734 -228
  165. data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
  166. data/lib/active_record/relation/batches.rb +249 -52
  167. data/lib/active_record/relation/calculations.rb +330 -284
  168. data/lib/active_record/relation/delegation.rb +135 -37
  169. data/lib/active_record/relation/finder_methods.rb +450 -287
  170. data/lib/active_record/relation/from_clause.rb +26 -0
  171. data/lib/active_record/relation/merger.rb +193 -0
  172. data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
  173. data/lib/active_record/relation/predicate_builder/association_query_value.rb +46 -0
  174. data/lib/active_record/relation/predicate_builder/base_handler.rb +19 -0
  175. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +20 -0
  176. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +56 -0
  177. data/lib/active_record/relation/predicate_builder/range_handler.rb +42 -0
  178. data/lib/active_record/relation/predicate_builder/relation_handler.rb +19 -0
  179. data/lib/active_record/relation/predicate_builder.rb +132 -43
  180. data/lib/active_record/relation/query_attribute.rb +45 -0
  181. data/lib/active_record/relation/query_methods.rb +1037 -221
  182. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  183. data/lib/active_record/relation/spawn_methods.rb +48 -151
  184. data/lib/active_record/relation/where_clause.rb +186 -0
  185. data/lib/active_record/relation/where_clause_factory.rb +34 -0
  186. data/lib/active_record/relation.rb +451 -359
  187. data/lib/active_record/result.rb +129 -20
  188. data/lib/active_record/runtime_registry.rb +24 -0
  189. data/lib/active_record/sanitization.rb +164 -136
  190. data/lib/active_record/schema.rb +31 -19
  191. data/lib/active_record/schema_dumper.rb +154 -107
  192. data/lib/active_record/schema_migration.rb +56 -0
  193. data/lib/active_record/scoping/default.rb +108 -98
  194. data/lib/active_record/scoping/named.rb +125 -112
  195. data/lib/active_record/scoping.rb +77 -123
  196. data/lib/active_record/secure_token.rb +40 -0
  197. data/lib/active_record/serialization.rb +10 -6
  198. data/lib/active_record/statement_cache.rb +121 -0
  199. data/lib/active_record/store.rb +175 -16
  200. data/lib/active_record/suppressor.rb +61 -0
  201. data/lib/active_record/table_metadata.rb +82 -0
  202. data/lib/active_record/tasks/database_tasks.rb +337 -0
  203. data/lib/active_record/tasks/mysql_database_tasks.rb +115 -0
  204. data/lib/active_record/tasks/postgresql_database_tasks.rb +143 -0
  205. data/lib/active_record/tasks/sqlite_database_tasks.rb +83 -0
  206. data/lib/active_record/timestamp.rb +80 -41
  207. data/lib/active_record/touch_later.rb +64 -0
  208. data/lib/active_record/transactions.rb +240 -119
  209. data/lib/active_record/translation.rb +2 -0
  210. data/lib/active_record/type/adapter_specific_registry.rb +136 -0
  211. data/lib/active_record/type/date.rb +9 -0
  212. data/lib/active_record/type/date_time.rb +9 -0
  213. data/lib/active_record/type/decimal_without_scale.rb +15 -0
  214. data/lib/active_record/type/hash_lookup_type_map.rb +25 -0
  215. data/lib/active_record/type/internal/timezone.rb +17 -0
  216. data/lib/active_record/type/json.rb +30 -0
  217. data/lib/active_record/type/serialized.rb +71 -0
  218. data/lib/active_record/type/text.rb +11 -0
  219. data/lib/active_record/type/time.rb +21 -0
  220. data/lib/active_record/type/type_map.rb +62 -0
  221. data/lib/active_record/type/unsigned_integer.rb +17 -0
  222. data/lib/active_record/type.rb +79 -0
  223. data/lib/active_record/type_caster/connection.rb +33 -0
  224. data/lib/active_record/type_caster/map.rb +23 -0
  225. data/lib/active_record/type_caster.rb +9 -0
  226. data/lib/active_record/validations/absence.rb +25 -0
  227. data/lib/active_record/validations/associated.rb +35 -18
  228. data/lib/active_record/validations/length.rb +26 -0
  229. data/lib/active_record/validations/presence.rb +68 -0
  230. data/lib/active_record/validations/uniqueness.rb +133 -75
  231. data/lib/active_record/validations.rb +53 -43
  232. data/lib/active_record/version.rb +7 -7
  233. data/lib/active_record.rb +89 -57
  234. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
  235. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
  236. data/lib/rails/generators/active_record/migration/migration_generator.rb +61 -8
  237. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +24 -0
  238. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +46 -0
  239. data/lib/rails/generators/active_record/migration.rb +28 -8
  240. data/lib/rails/generators/active_record/model/model_generator.rb +23 -22
  241. data/lib/rails/generators/active_record/model/templates/model.rb.tt +13 -0
  242. data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +1 -1
  243. data/lib/rails/generators/active_record.rb +10 -16
  244. metadata +141 -62
  245. data/examples/associations.png +0 -0
  246. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -63
  247. data/lib/active_record/associations/join_helper.rb +0 -55
  248. data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
  249. data/lib/active_record/associations/preloader/collection_association.rb +0 -24
  250. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  251. data/lib/active_record/associations/preloader/has_many.rb +0 -17
  252. data/lib/active_record/associations/preloader/has_many_through.rb +0 -15
  253. data/lib/active_record/associations/preloader/has_one.rb +0 -23
  254. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  255. data/lib/active_record/associations/preloader/singular_association.rb +0 -21
  256. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  257. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  258. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -441
  259. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  260. data/lib/active_record/dynamic_finder_match.rb +0 -68
  261. data/lib/active_record/dynamic_scope_match.rb +0 -23
  262. data/lib/active_record/fixtures/file.rb +0 -65
  263. data/lib/active_record/identity_map.rb +0 -162
  264. data/lib/active_record/observer.rb +0 -121
  265. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  266. data/lib/active_record/serializers/xml_serializer.rb +0 -203
  267. data/lib/active_record/session_store.rb +0 -360
  268. data/lib/active_record/test_case.rb +0 -73
  269. data/lib/rails/generators/active_record/migration/templates/migration.rb +0 -34
  270. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -15
  271. data/lib/rails/generators/active_record/model/templates/model.rb +0 -12
  272. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  273. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  274. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  275. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -1,231 +1,155 @@
1
- require 'active_support/core_ext/object/blank'
2
- require 'arel/visitors/bind_visitor'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record/connection_adapters/abstract_adapter"
4
+ require "active_record/connection_adapters/statement_pool"
5
+ require "active_record/connection_adapters/mysql/column"
6
+ require "active_record/connection_adapters/mysql/explain_pretty_printer"
7
+ require "active_record/connection_adapters/mysql/quoting"
8
+ require "active_record/connection_adapters/mysql/schema_creation"
9
+ require "active_record/connection_adapters/mysql/schema_definitions"
10
+ require "active_record/connection_adapters/mysql/schema_dumper"
11
+ require "active_record/connection_adapters/mysql/schema_statements"
12
+ require "active_record/connection_adapters/mysql/type_metadata"
13
+
14
+ require "active_support/core_ext/string/strip"
3
15
 
4
16
  module ActiveRecord
5
17
  module ConnectionAdapters
6
18
  class AbstractMysqlAdapter < AbstractAdapter
7
- class Column < ConnectionAdapters::Column # :nodoc:
8
- attr_reader :collation
9
-
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
56
-
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
81
-
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
92
- end
19
+ include MySQL::Quoting
20
+ include MySQL::SchemaStatements
93
21
 
94
22
  ##
95
23
  # :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
24
+ # By default, the Mysql2Adapter will consider all columns of type <tt>tinyint(1)</tt>
25
+ # as boolean. If you wish to disable this emulation you can add the following line
99
26
  # to your application.rb file:
100
27
  #
101
- # ActiveRecord::ConnectionAdapters::Mysql[2]Adapter.emulate_booleans = false
102
- class_attribute :emulate_booleans
103
- self.emulate_booleans = true
104
-
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'
28
+ # ActiveRecord::ConnectionAdapters::Mysql2Adapter.emulate_booleans = false
29
+ class_attribute :emulate_booleans, default: true
112
30
 
113
31
  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 }
32
+ primary_key: "bigint auto_increment PRIMARY KEY",
33
+ string: { name: "varchar", limit: 255 },
34
+ text: { name: "text", limit: 65535 },
35
+ integer: { name: "int", limit: 4 },
36
+ float: { name: "float", limit: 24 },
37
+ decimal: { name: "decimal" },
38
+ datetime: { name: "datetime" },
39
+ timestamp: { name: "timestamp" },
40
+ time: { name: "time" },
41
+ date: { name: "date" },
42
+ binary: { name: "blob", limit: 65535 },
43
+ boolean: { name: "tinyint", limit: 1 },
44
+ json: { name: "json" },
126
45
  }
127
46
 
128
- class BindSubstitution < Arel::Visitors::MySQL # :nodoc:
129
- include Arel::Visitors::BindVisitor
47
+ class StatementPool < ConnectionAdapters::StatementPool # :nodoc:
48
+ private def dealloc(stmt)
49
+ stmt[:stmt].close
50
+ end
130
51
  end
131
52
 
132
- # FIXME: Make the first parameter more similar for the two adapters
133
53
  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 = {}, {}
54
+ super(connection, logger, config)
137
55
 
138
- if config.fetch(:prepared_statements) { true }
139
- @visitor = Arel::Visitors::MySQL.new self
140
- else
141
- @visitor = BindSubstitution.new self
56
+ @statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit]))
57
+
58
+ if version < "5.1.10"
59
+ raise "Your version of MySQL (#{version_string}) is too old. Active Record supports MySQL >= 5.1.10."
142
60
  end
143
61
  end
144
62
 
145
- def adapter_name #:nodoc:
146
- self.class::ADAPTER_NAME
63
+ def version #:nodoc:
64
+ @version ||= Version.new(version_string)
147
65
  end
148
66
 
149
- # Returns true, since this connection adapter supports migrations.
150
- def supports_migrations?
151
- true
67
+ def mariadb? # :nodoc:
68
+ /mariadb/i.match?(full_version)
152
69
  end
153
70
 
154
- def supports_primary_key?
71
+ def supports_bulk_alter? #:nodoc:
155
72
  true
156
73
  end
157
74
 
158
- # Returns true, since this connection adapter supports savepoints.
159
- def supports_savepoints?
75
+ def supports_index_sort_order?
76
+ !mariadb? && version >= "8.0.1"
77
+ end
78
+
79
+ def supports_transaction_isolation?
160
80
  true
161
81
  end
162
82
 
163
- def supports_bulk_alter? #:nodoc:
83
+ def supports_explain?
164
84
  true
165
85
  end
166
86
 
167
- # Technically MySQL allows to create indexes with the sort order syntax
168
- # but at the moment (5.5) it doesn't yet implement them
169
- def supports_index_sort_order?
87
+ def supports_indexes_in_create?
170
88
  true
171
89
  end
172
90
 
173
- def native_database_types
174
- NATIVE_DATABASE_TYPES
91
+ def supports_foreign_keys?
92
+ true
175
93
  end
176
94
 
177
- # HELPER METHODS ===========================================
95
+ def supports_views?
96
+ true
97
+ end
178
98
 
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
99
+ def supports_datetime_with_precision?
100
+ if mariadb?
101
+ version >= "5.3.0"
102
+ else
103
+ version >= "5.6.4"
104
+ end
183
105
  end
184
106
 
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)
107
+ def supports_virtual_columns?
108
+ if mariadb?
109
+ version >= "5.2.0"
110
+ else
111
+ version >= "5.7.5"
112
+ end
188
113
  end
189
114
 
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
115
+ def supports_advisory_locks?
116
+ true
194
117
  end
195
118
 
196
- # QUOTING ==================================================
119
+ def get_advisory_lock(lock_name, timeout = 0) # :nodoc:
120
+ query_value("SELECT GET_LOCK(#{quote(lock_name.to_s)}, #{timeout})") == 1
121
+ end
197
122
 
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")
204
- else
205
- super
206
- end
123
+ def release_advisory_lock(lock_name) # :nodoc:
124
+ query_value("SELECT RELEASE_LOCK(#{quote(lock_name.to_s)})") == 1
207
125
  end
208
126
 
209
- def quote_column_name(name) #:nodoc:
210
- @quoted_column_names[name] ||= "`#{name.to_s.gsub('`', '``')}`"
127
+ def native_database_types
128
+ NATIVE_DATABASE_TYPES
211
129
  end
212
130
 
213
- def quote_table_name(name) #:nodoc:
214
- @quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`')
131
+ def index_algorithms
132
+ { default: "ALGORITHM = DEFAULT".dup, copy: "ALGORITHM = COPY".dup, inplace: "ALGORITHM = INPLACE".dup }
215
133
  end
216
134
 
217
- def quoted_true
218
- QUOTED_TRUE
135
+ # HELPER METHODS ===========================================
136
+
137
+ # The two drivers have slightly different ways of yielding hashes of results, so
138
+ # this method must be implemented to provide a uniform interface.
139
+ def each_hash(result) # :nodoc:
140
+ raise NotImplementedError
219
141
  end
220
142
 
221
- def quoted_false
222
- QUOTED_FALSE
143
+ # Must return the MySQL error number from the exception, if the exception has an
144
+ # error number.
145
+ def error_number(exception) # :nodoc:
146
+ raise NotImplementedError
223
147
  end
224
148
 
225
149
  # REFERENTIAL INTEGRITY ====================================
226
150
 
227
- def disable_referential_integrity(&block) #:nodoc:
228
- old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
151
+ def disable_referential_integrity #:nodoc:
152
+ old = query_value("SELECT @@FOREIGN_KEY_CHECKS")
229
153
 
230
154
  begin
231
155
  update("SET FOREIGN_KEY_CHECKS = 0")
@@ -235,121 +159,99 @@ module ActiveRecord
235
159
  end
236
160
  end
237
161
 
162
+ # CONNECTION MANAGEMENT ====================================
163
+
164
+ # Clears the prepared statements cache.
165
+ def clear_cache!
166
+ reload_type_map
167
+ @statements.clear
168
+ end
169
+
170
+ #--
238
171
  # DATABASE STATEMENTS ======================================
172
+ #++
173
+
174
+ def explain(arel, binds = [])
175
+ sql = "EXPLAIN #{to_sql(arel, binds)}"
176
+ start = Time.now
177
+ result = exec_query(sql, "EXPLAIN", binds)
178
+ elapsed = Time.now - start
179
+
180
+ MySQL::ExplainPrettyPrinter.new.pp(result, elapsed)
181
+ end
239
182
 
240
183
  # Executes the SQL statement in the context of this connection.
241
184
  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
185
+ log(sql, name) do
186
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
187
+ @connection.query(sql)
188
+ end
252
189
  end
253
190
  end
254
191
 
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:
192
+ # Mysql2Adapter doesn't have to free a result after using it, but we use this method
193
+ # to write stuff in an abstract way without concerning ourselves about whether it
194
+ # needs to be explicitly freed or not.
195
+ def execute_and_free(sql, name = nil) # :nodoc:
259
196
  yield execute(sql, name)
260
197
  end
261
198
 
262
- def update_sql(sql, name = nil) #:nodoc:
263
- super
264
- @connection.affected_rows
265
- end
266
-
267
199
  def begin_db_transaction
268
200
  execute "BEGIN"
269
- rescue Exception
270
- # Transactions aren't supported
201
+ end
202
+
203
+ def begin_isolated_db_transaction(isolation)
204
+ execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}"
205
+ begin_db_transaction
271
206
  end
272
207
 
273
208
  def commit_db_transaction #:nodoc:
274
209
  execute "COMMIT"
275
- rescue Exception
276
- # Transactions aren't supported
277
210
  end
278
211
 
279
- def rollback_db_transaction #:nodoc:
212
+ def exec_rollback_db_transaction #:nodoc:
280
213
  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
214
  end
296
215
 
297
216
  # In the simple case, MySQL allows us to place JOINs directly into the UPDATE
298
217
  # 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:
218
+ # these, we must use a subquery.
219
+ def join_to_update(update, select, key) # :nodoc:
303
220
  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)
221
+ super
312
222
  else
313
223
  update.table select.source
314
224
  update.wheres = select.constraints
315
225
  end
316
226
  end
317
227
 
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
228
+ def empty_insert_statement_value
229
+ "VALUES ()"
332
230
  end
333
231
 
232
+ # SCHEMA STATEMENTS ========================================
233
+
334
234
  # Drops the database specified on the +name+ attribute
335
235
  # and creates it again using the provided +options+.
336
236
  def recreate_database(name, options = {})
337
237
  drop_database(name)
338
- create_database(name, options)
238
+ sql = create_database(name, options)
239
+ reconnect!
240
+ sql
339
241
  end
340
242
 
341
243
  # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
342
244
  # Charset defaults to utf8.
343
245
  #
344
246
  # Example:
345
- # create_database 'charset_test', :charset => 'latin1', :collation => 'latin1_bin'
247
+ # create_database 'charset_test', charset: 'latin1', collation: 'latin1_bin'
346
248
  # create_database 'matt_development'
347
- # create_database 'matt_development', :charset => :big5
249
+ # create_database 'matt_development', charset: :big5
348
250
  def create_database(name, options = {})
349
251
  if options[:collation]
350
- execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{options[:collation]}`"
252
+ execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT COLLATE #{quote_table_name(options[:collation])}"
351
253
  else
352
- execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}`"
254
+ execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset] || 'utf8')}"
353
255
  end
354
256
  end
355
257
 
@@ -358,319 +260,628 @@ module ActiveRecord
358
260
  # Example:
359
261
  # drop_database('sebastian_development')
360
262
  def drop_database(name) #:nodoc:
361
- execute "DROP DATABASE IF EXISTS `#{name}`"
263
+ execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
362
264
  end
363
265
 
364
266
  def current_database
365
- select_value 'SELECT DATABASE() as db'
267
+ query_value("SELECT database()", "SCHEMA")
366
268
  end
367
269
 
368
270
  # Returns the database character set.
369
271
  def charset
370
- show_variable 'character_set_database'
272
+ show_variable "character_set_database"
371
273
  end
372
274
 
373
275
  # Returns the database collation strategy.
374
276
  def collation
375
- show_variable 'collation_database'
277
+ show_variable "collation_database"
376
278
  end
377
279
 
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
382
-
383
- execute_and_free(sql, 'SCHEMA') do |result|
384
- result.collect { |field| field.first }
385
- end
280
+ def truncate(table_name, name = nil)
281
+ execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
386
282
  end
387
283
 
388
- def table_exists?(name)
389
- return false unless name
390
- return true if tables(nil, nil, name).any?
391
-
392
- name = name.to_s
393
- schema, table = name.split('.', 2)
394
-
395
- unless table # A table was provided without a schema
396
- table = schema
397
- schema = nil
398
- end
284
+ def table_comment(table_name) # :nodoc:
285
+ scope = quoted_scope(table_name)
399
286
 
400
- tables(nil, schema, table).any?
401
- end
402
-
403
- # Returns an array of indexes for the given table.
404
- def indexes(table_name, name = nil) #:nodoc:
405
- indexes = []
406
- current_index = nil
407
- execute_and_free("SHOW KEYS FROM #{quote_table_name(table_name)}", 'SCHEMA') do |result|
408
- each_hash(result) do |row|
409
- if current_index != row[:Key_name]
410
- next if row[:Key_name] == 'PRIMARY' # skip the primary key
411
- current_index = row[:Key_name]
412
- indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], [])
413
- end
414
-
415
- indexes.last.columns << row[:Column_name]
416
- indexes.last.lengths << row[:Sub_part]
417
- end
418
- end
419
-
420
- indexes
421
- end
422
-
423
- # 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])
429
- end
430
- end
431
- end
432
-
433
- def create_table(table_name, options = {}) #:nodoc:
434
- super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
287
+ query_value(<<-SQL.strip_heredoc, "SCHEMA").presence
288
+ SELECT table_comment
289
+ FROM information_schema.tables
290
+ WHERE table_schema = #{scope[:schema]}
291
+ AND table_name = #{scope[:name]}
292
+ SQL
435
293
  end
436
294
 
437
295
  def bulk_change_table(table_name, operations) #:nodoc:
438
- sqls = operations.map do |command, args|
296
+ sqls = operations.flat_map do |command, args|
439
297
  table, arguments = args.shift, args
440
- method = :"#{command}_sql"
298
+ method = :"#{command}_for_alter"
441
299
 
442
300
  if respond_to?(method, true)
443
301
  send(method, table, *arguments)
444
302
  else
445
303
  raise "Unknown method called : #{method}(#{arguments.inspect})"
446
304
  end
447
- end.flatten.join(", ")
305
+ end.join(", ")
448
306
 
449
307
  execute("ALTER TABLE #{quote_table_name(table_name)} #{sqls}")
450
308
  end
451
309
 
310
+ def change_table_comment(table_name, comment) #:nodoc:
311
+ comment = "" if comment.nil?
312
+ execute("ALTER TABLE #{quote_table_name(table_name)} COMMENT #{quote(comment)}")
313
+ end
314
+
452
315
  # Renames a table.
453
316
  #
454
317
  # Example:
455
318
  # rename_table('octopuses', 'octopi')
456
319
  def rename_table(table_name, new_name)
457
320
  execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
321
+ rename_table_indexes(table_name, new_name)
458
322
  end
459
323
 
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)}")
324
+ # Drops a table from the database.
325
+ #
326
+ # [<tt>:force</tt>]
327
+ # Set to +:cascade+ to drop dependent objects as well.
328
+ # Defaults to false.
329
+ # [<tt>:if_exists</tt>]
330
+ # Set to +true+ to only drop the table if it exists.
331
+ # Defaults to false.
332
+ # [<tt>:temporary</tt>]
333
+ # Set to +true+ to drop temporary table.
334
+ # Defaults to false.
335
+ #
336
+ # Although this command ignores most +options+ and the block if one is given,
337
+ # it can be helpful to provide these in a migration's +change+ method so it can be reverted.
338
+ # In that case, +options+ and the block will be used by create_table.
339
+ def drop_table(table_name, options = {})
340
+ execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
462
341
  end
463
342
 
464
- def change_column_default(table_name, column_name, default)
465
- column = column_for(table_name, column_name)
466
- change_column table_name, column_name, column.sql_type, :default => default
343
+ def rename_index(table_name, old_name, new_name)
344
+ if supports_rename_index?
345
+ validate_index_length!(table_name, new_name)
346
+
347
+ execute "ALTER TABLE #{quote_table_name(table_name)} RENAME INDEX #{quote_table_name(old_name)} TO #{quote_table_name(new_name)}"
348
+ else
349
+ super
350
+ end
467
351
  end
468
352
 
469
- def change_column_null(table_name, column_name, null, default = nil)
470
- column = column_for(table_name, column_name)
353
+ def change_column_default(table_name, column_name, default_or_changes) #:nodoc:
354
+ default = extract_new_default_value(default_or_changes)
355
+ change_column table_name, column_name, nil, default: default
356
+ end
471
357
 
358
+ def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
472
359
  unless null || default.nil?
473
360
  execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
474
361
  end
475
362
 
476
- change_column table_name, column_name, column.sql_type, :null => null
363
+ change_column table_name, column_name, nil, null: null
364
+ end
365
+
366
+ def change_column_comment(table_name, column_name, comment) #:nodoc:
367
+ change_column table_name, column_name, nil, comment: comment
477
368
  end
478
369
 
479
370
  def change_column(table_name, column_name, type, options = {}) #:nodoc:
480
- execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_sql(table_name, column_name, type, options)}")
371
+ execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_for_alter(table_name, column_name, type, options)}")
481
372
  end
482
373
 
483
374
  def rename_column(table_name, column_name, new_column_name) #:nodoc:
484
- execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_sql(table_name, column_name, new_column_name)}")
375
+ execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_for_alter(table_name, column_name, new_column_name)}")
376
+ rename_column_indexes(table_name, column_name, new_column_name)
377
+ end
378
+
379
+ def add_index(table_name, column_name, options = {}) #:nodoc:
380
+ index_name, index_type, index_columns, _, index_algorithm, index_using, comment = add_index_options(table_name, column_name, options)
381
+ sql = "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns}) #{index_algorithm}".dup
382
+ execute add_sql_comment!(sql, comment)
383
+ end
384
+
385
+ def add_sql_comment!(sql, comment) # :nodoc:
386
+ sql << " COMMENT #{quote(comment)}" if comment.present?
387
+ sql
388
+ end
389
+
390
+ def foreign_keys(table_name)
391
+ raise ArgumentError unless table_name.present?
392
+
393
+ scope = quoted_scope(table_name)
394
+
395
+ fk_info = exec_query(<<-SQL.strip_heredoc, "SCHEMA")
396
+ SELECT fk.referenced_table_name AS 'to_table',
397
+ fk.referenced_column_name AS 'primary_key',
398
+ fk.column_name AS 'column',
399
+ fk.constraint_name AS 'name',
400
+ rc.update_rule AS 'on_update',
401
+ rc.delete_rule AS 'on_delete'
402
+ FROM information_schema.referential_constraints rc
403
+ JOIN information_schema.key_column_usage fk
404
+ USING (constraint_schema, constraint_name)
405
+ WHERE fk.referenced_column_name IS NOT NULL
406
+ AND fk.table_schema = #{scope[:schema]}
407
+ AND fk.table_name = #{scope[:name]}
408
+ AND rc.constraint_schema = #{scope[:schema]}
409
+ AND rc.table_name = #{scope[:name]}
410
+ SQL
411
+
412
+ fk_info.map do |row|
413
+ options = {
414
+ column: row["column"],
415
+ name: row["name"],
416
+ primary_key: row["primary_key"]
417
+ }
418
+
419
+ options[:on_update] = extract_foreign_key_action(row["on_update"])
420
+ options[:on_delete] = extract_foreign_key_action(row["on_delete"])
421
+
422
+ ForeignKeyDefinition.new(table_name, row["to_table"], options)
423
+ end
424
+ end
425
+
426
+ def table_options(table_name) # :nodoc:
427
+ table_options = {}
428
+
429
+ create_table_info = create_table_info(table_name)
430
+
431
+ # strip create_definitions and partition_options
432
+ raw_table_options = create_table_info.sub(/\A.*\n\) /m, "").sub(/\n\/\*!.*\*\/\n\z/m, "").strip
433
+
434
+ # strip AUTO_INCREMENT
435
+ raw_table_options.sub!(/(ENGINE=\w+)(?: AUTO_INCREMENT=\d+)/, '\1')
436
+
437
+ table_options[:options] = raw_table_options
438
+
439
+ # strip COMMENT
440
+ if raw_table_options.sub!(/ COMMENT='.+'/, "")
441
+ table_options[:comment] = table_comment(table_name)
442
+ end
443
+
444
+ table_options
485
445
  end
486
446
 
487
447
  # 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
490
- 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
499
- 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}")
448
+ def type_to_sql(type, limit: nil, precision: nil, scale: nil, unsigned: nil, **) # :nodoc:
449
+ sql = \
450
+ case type.to_s
451
+ when "integer"
452
+ integer_to_sql(limit)
453
+ when "text"
454
+ text_to_sql(limit)
455
+ when "blob"
456
+ binary_to_sql(limit)
457
+ when "binary"
458
+ if (0..0xfff) === limit
459
+ "varbinary(#{limit})"
460
+ else
461
+ binary_to_sql(limit)
462
+ end
463
+ else
464
+ super
506
465
  end
466
+
467
+ sql = "#{sql} unsigned" if unsigned && type != :primary_key
468
+ sql
469
+ end
470
+
471
+ # SHOW VARIABLES LIKE 'name'
472
+ def show_variable(name)
473
+ query_value("SELECT @@#{name}", "SCHEMA")
474
+ rescue ActiveRecord::StatementInvalid
475
+ nil
476
+ end
477
+
478
+ def primary_keys(table_name) # :nodoc:
479
+ raise ArgumentError unless table_name.present?
480
+
481
+ scope = quoted_scope(table_name)
482
+
483
+ query_values(<<-SQL.strip_heredoc, "SCHEMA")
484
+ SELECT column_name
485
+ FROM information_schema.key_column_usage
486
+ WHERE constraint_name = 'PRIMARY'
487
+ AND table_schema = #{scope[:schema]}
488
+ AND table_name = #{scope[:name]}
489
+ ORDER BY ordinal_position
490
+ SQL
491
+ end
492
+
493
+ def case_sensitive_comparison(table, attribute, column, value) # :nodoc:
494
+ if column.collation && !column.case_sensitive?
495
+ table[attribute].eq(Arel::Nodes::Bin.new(value))
507
496
  else
508
497
  super
509
498
  end
510
499
  end
511
500
 
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
501
+ def can_perform_case_insensitive_comparison_for?(column)
502
+ column.case_sensitive?
518
503
  end
504
+ private :can_perform_case_insensitive_comparison_for?
519
505
 
520
- # SHOW VARIABLES LIKE 'name'
521
- def show_variable(name)
522
- variables = select_all("SHOW VARIABLES LIKE '#{name}'")
523
- variables.first['Value'] unless variables.empty?
506
+ # In MySQL 5.7.5 and up, ONLY_FULL_GROUP_BY affects handling of queries that use
507
+ # DISTINCT and ORDER BY. It requires the ORDER BY columns in the select list for
508
+ # distinct queries, and requires that the ORDER BY include the distinct column.
509
+ # See https://dev.mysql.com/doc/refman/5.7/en/group-by-handling.html
510
+ def columns_for_distinct(columns, orders) # :nodoc:
511
+ order_columns = orders.reject(&:blank?).map { |s|
512
+ # Convert Arel node to string
513
+ s = s.to_sql unless s.is_a?(String)
514
+ # Remove any ASC/DESC modifiers
515
+ s.gsub(/\s+(?:ASC|DESC)\b/i, "")
516
+ }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
517
+
518
+ (order_columns << super).join(", ")
524
519
  end
525
520
 
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
536
- end
521
+ def strict_mode?
522
+ self.class.type_cast_config_to_boolean(@config.fetch(:strict, true))
537
523
  end
538
524
 
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
525
+ def default_index_type?(index) # :nodoc:
526
+ index.using == :btree || super
543
527
  end
544
528
 
545
- def case_sensitive_modifier(node)
546
- Arel::Nodes::Bin.new(node)
529
+ def insert_fixtures_set(fixture_set, tables_to_delete = [])
530
+ with_multi_statements do
531
+ super { discard_remaining_results }
532
+ end
547
533
  end
548
534
 
549
- def case_insensitive_comparison(table, attribute, column, value)
550
- if column.case_sensitive?
535
+ private
536
+ def combine_multi_statements(total_sql)
537
+ total_sql.each_with_object([]) do |sql, total_sql_chunks|
538
+ previous_packet = total_sql_chunks.last
539
+ sql << ";\n"
540
+ if max_allowed_packet_reached?(sql, previous_packet) || total_sql_chunks.empty?
541
+ total_sql_chunks << sql
542
+ else
543
+ previous_packet << sql
544
+ end
545
+ end
546
+ end
547
+
548
+ def max_allowed_packet_reached?(current_packet, previous_packet)
549
+ if current_packet.bytesize > max_allowed_packet
550
+ raise ActiveRecordError, "Fixtures set is too large #{current_packet.bytesize}. Consider increasing the max_allowed_packet variable."
551
+ elsif previous_packet.nil?
552
+ false
553
+ else
554
+ (current_packet.bytesize + previous_packet.bytesize) > max_allowed_packet
555
+ end
556
+ end
557
+
558
+ def max_allowed_packet
559
+ bytes_margin = 2
560
+ @max_allowed_packet ||= (show_variable("max_allowed_packet") - bytes_margin)
561
+ end
562
+
563
+ def initialize_type_map(m = type_map)
551
564
  super
552
- else
553
- table[attribute].eq(value)
565
+
566
+ register_class_with_limit m, %r(char)i, MysqlString
567
+
568
+ m.register_type %r(tinytext)i, Type::Text.new(limit: 2**8 - 1)
569
+ m.register_type %r(tinyblob)i, Type::Binary.new(limit: 2**8 - 1)
570
+ m.register_type %r(text)i, Type::Text.new(limit: 2**16 - 1)
571
+ m.register_type %r(blob)i, Type::Binary.new(limit: 2**16 - 1)
572
+ m.register_type %r(mediumtext)i, Type::Text.new(limit: 2**24 - 1)
573
+ m.register_type %r(mediumblob)i, Type::Binary.new(limit: 2**24 - 1)
574
+ m.register_type %r(longtext)i, Type::Text.new(limit: 2**32 - 1)
575
+ m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1)
576
+ m.register_type %r(^float)i, Type::Float.new(limit: 24)
577
+ m.register_type %r(^double)i, Type::Float.new(limit: 53)
578
+
579
+ register_integer_type m, %r(^bigint)i, limit: 8
580
+ register_integer_type m, %r(^int)i, limit: 4
581
+ register_integer_type m, %r(^mediumint)i, limit: 3
582
+ register_integer_type m, %r(^smallint)i, limit: 2
583
+ register_integer_type m, %r(^tinyint)i, limit: 1
584
+
585
+ m.register_type %r(^tinyint\(1\))i, Type::Boolean.new if emulate_booleans
586
+ m.alias_type %r(year)i, "integer"
587
+ m.alias_type %r(bit)i, "binary"
588
+
589
+ m.register_type(%r(enum)i) do |sql_type|
590
+ limit = sql_type[/^enum\((.+)\)/i, 1]
591
+ .split(",").map { |enum| enum.strip.length - 2 }.max
592
+ MysqlString.new(limit: limit)
593
+ end
594
+
595
+ m.register_type(%r(^set)i) do |sql_type|
596
+ limit = sql_type[/^set\((.+)\)/i, 1]
597
+ .split(",").map { |set| set.strip.length - 1 }.sum - 1
598
+ MysqlString.new(limit: limit)
599
+ end
554
600
  end
555
- end
556
601
 
557
- def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
558
- where_sql
559
- end
602
+ def register_integer_type(mapping, key, options)
603
+ mapping.register_type(key) do |sql_type|
604
+ if /\bunsigned\b/.match?(sql_type)
605
+ Type::UnsignedInteger.new(options)
606
+ else
607
+ Type::Integer.new(options)
608
+ end
609
+ end
610
+ end
560
611
 
561
- protected
612
+ def extract_precision(sql_type)
613
+ if /\A(?:date)?time(?:stamp)?\b/.match?(sql_type)
614
+ super || 0
615
+ else
616
+ super
617
+ end
618
+ end
562
619
 
563
- def add_index_length(option_strings, column_names, options = {})
564
- if options.is_a?(Hash) && length = options[:length]
565
- case length
566
- when Hash
567
- column_names.each {|name| option_strings[name] += "(#{length[name]})" if length.has_key?(name) && length[name].present?}
568
- when Fixnum
569
- column_names.each {|name| option_strings[name] += "(#{length})"}
620
+ # See https://dev.mysql.com/doc/refman/5.7/en/error-messages-server.html
621
+ ER_DUP_ENTRY = 1062
622
+ ER_NOT_NULL_VIOLATION = 1048
623
+ ER_DO_NOT_HAVE_DEFAULT = 1364
624
+ ER_NO_REFERENCED_ROW_2 = 1452
625
+ ER_DATA_TOO_LONG = 1406
626
+ ER_OUT_OF_RANGE = 1264
627
+ ER_LOCK_DEADLOCK = 1213
628
+ ER_CANNOT_ADD_FOREIGN = 1215
629
+ ER_CANNOT_CREATE_TABLE = 1005
630
+ ER_LOCK_WAIT_TIMEOUT = 1205
631
+ ER_QUERY_INTERRUPTED = 1317
632
+ ER_QUERY_TIMEOUT = 3024
633
+
634
+ def translate_exception(exception, message)
635
+ case error_number(exception)
636
+ when ER_DUP_ENTRY
637
+ RecordNotUnique.new(message)
638
+ when ER_NO_REFERENCED_ROW_2
639
+ InvalidForeignKey.new(message)
640
+ when ER_CANNOT_ADD_FOREIGN
641
+ mismatched_foreign_key(message)
642
+ when ER_CANNOT_CREATE_TABLE
643
+ if message.include?("errno: 150")
644
+ mismatched_foreign_key(message)
645
+ else
646
+ super
647
+ end
648
+ when ER_DATA_TOO_LONG
649
+ ValueTooLong.new(message)
650
+ when ER_OUT_OF_RANGE
651
+ RangeError.new(message)
652
+ when ER_NOT_NULL_VIOLATION, ER_DO_NOT_HAVE_DEFAULT
653
+ NotNullViolation.new(message)
654
+ when ER_LOCK_DEADLOCK
655
+ Deadlocked.new(message)
656
+ when ER_LOCK_WAIT_TIMEOUT
657
+ LockWaitTimeout.new(message)
658
+ when ER_QUERY_TIMEOUT
659
+ StatementTimeout.new(message)
660
+ when ER_QUERY_INTERRUPTED
661
+ QueryCanceled.new(message)
662
+ else
663
+ super
570
664
  end
571
665
  end
572
666
 
573
- return option_strings
574
- end
667
+ def change_column_for_alter(table_name, column_name, type, options = {})
668
+ column = column_for(table_name, column_name)
669
+ type ||= column.sql_type
575
670
 
576
- def quoted_columns_for_index(column_names, options = {})
577
- option_strings = Hash[column_names.map {|name| [name, '']}]
671
+ unless options.key?(:default)
672
+ options[:default] = column.default
673
+ end
578
674
 
579
- # add index length
580
- option_strings = add_index_length(option_strings, column_names, options)
675
+ unless options.key?(:null)
676
+ options[:null] = column.null
677
+ end
581
678
 
582
- # add index sort order
583
- option_strings = add_index_sort_order(option_strings, column_names, options)
679
+ unless options.key?(:comment)
680
+ options[:comment] = column.comment
681
+ end
584
682
 
585
- column_names.map {|name| quote_column_name(name) + option_strings[name]}
586
- end
683
+ td = create_table_definition(table_name)
684
+ cd = td.new_column_definition(column.name, type, options)
685
+ schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
686
+ end
587
687
 
588
- def translate_exception(exception, message)
589
- case error_number(exception)
590
- when 1062
591
- RecordNotUnique.new(message, exception)
592
- when 1452
593
- InvalidForeignKey.new(message, exception)
594
- else
595
- super
688
+ def rename_column_for_alter(table_name, column_name, new_column_name)
689
+ column = column_for(table_name, column_name)
690
+ options = {
691
+ default: column.default,
692
+ null: column.null,
693
+ auto_increment: column.auto_increment?
694
+ }
695
+
696
+ current_type = exec_query("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE #{quote(column_name)}", "SCHEMA").first["Type"]
697
+ td = create_table_definition(table_name)
698
+ cd = td.new_column_definition(new_column_name, current_type, options)
699
+ schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
596
700
  end
597
- end
598
701
 
599
- 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
604
- end
702
+ def add_index_for_alter(table_name, column_name, options = {})
703
+ index_name, index_type, index_columns, _, index_algorithm, index_using = add_index_options(table_name, column_name, options)
704
+ index_algorithm[0, 0] = ", " if index_algorithm.present?
705
+ "ADD #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_algorithm}"
706
+ end
605
707
 
606
- def change_column_sql(table_name, column_name, type, options = {})
607
- column = column_for(table_name, column_name)
708
+ def remove_index_for_alter(table_name, options = {})
709
+ index_name = index_name_for_remove(table_name, options)
710
+ "DROP INDEX #{quote_column_name(index_name)}"
711
+ end
608
712
 
609
- unless options_include_default?(options)
610
- options[:default] = column.default
713
+ def add_timestamps_for_alter(table_name, options = {})
714
+ [add_column_for_alter(table_name, :created_at, :datetime, options), add_column_for_alter(table_name, :updated_at, :datetime, options)]
611
715
  end
612
716
 
613
- unless options.has_key?(:null)
614
- options[:null] = column.null
717
+ def remove_timestamps_for_alter(table_name, options = {})
718
+ [remove_column_for_alter(table_name, :updated_at), remove_column_for_alter(table_name, :created_at)]
615
719
  end
616
720
 
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
621
- end
721
+ # MySQL is too stupid to create a temporary table for use subquery, so we have
722
+ # to give it some prompting in the form of a subsubquery. Ugh!
723
+ def subquery_for(key, select)
724
+ subselect = select.clone
725
+ subselect.projections = [key]
622
726
 
623
- def rename_column_sql(table_name, column_name, new_column_name)
624
- options = {}
727
+ # Materialize subquery by adding distinct
728
+ # to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
729
+ subselect.distinct unless select.limit || select.offset || select.orders.any?
625
730
 
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}"
731
+ key_name = quote_column_name(key.name)
732
+ Arel::SelectManager.new(subselect.as("__active_record_temp")).project(Arel.sql(key_name))
631
733
  end
632
734
 
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
637
- end
735
+ def supports_rename_index?
736
+ mariadb? ? false : version >= "5.7.6"
737
+ end
638
738
 
639
- def remove_column_sql(table_name, *column_names)
640
- columns_for_remove(table_name, *column_names).map {|column_name| "DROP #{column_name}" }
641
- end
642
- alias :remove_columns_sql :remove_column
739
+ def configure_connection
740
+ variables = @config.fetch(:variables, {}).stringify_keys
643
741
 
644
- 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})"
647
- end
742
+ # By default, MySQL 'where id is null' selects the last inserted id; Turn this off.
743
+ variables["sql_auto_is_null"] = 0
648
744
 
649
- def remove_index_sql(table_name, options = {})
650
- index_name = index_name_for_remove(table_name, options)
651
- "DROP INDEX #{index_name}"
652
- end
745
+ # Increase timeout so the server doesn't disconnect us.
746
+ wait_timeout = self.class.type_cast_config_to_integer(@config[:wait_timeout])
747
+ wait_timeout = 2147483 unless wait_timeout.is_a?(Integer)
748
+ variables["wait_timeout"] = wait_timeout
653
749
 
654
- def add_timestamps_sql(table_name)
655
- [add_column_sql(table_name, :created_at, :datetime), add_column_sql(table_name, :updated_at, :datetime)]
656
- end
750
+ defaults = [":default", :default].to_set
657
751
 
658
- def remove_timestamps_sql(table_name)
659
- [remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)]
660
- end
752
+ # Make MySQL reject illegal values rather than truncating or blanking them, see
753
+ # https://dev.mysql.com/doc/refman/5.7/en/sql-mode.html#sqlmode_strict_all_tables
754
+ # If the user has provided another value for sql_mode, don't replace it.
755
+ if sql_mode = variables.delete("sql_mode")
756
+ sql_mode = quote(sql_mode)
757
+ elsif !defaults.include?(strict_mode?)
758
+ if strict_mode?
759
+ sql_mode = "CONCAT(@@sql_mode, ',STRICT_ALL_TABLES')"
760
+ else
761
+ sql_mode = "REPLACE(@@sql_mode, 'STRICT_TRANS_TABLES', '')"
762
+ sql_mode = "REPLACE(#{sql_mode}, 'STRICT_ALL_TABLES', '')"
763
+ sql_mode = "REPLACE(#{sql_mode}, 'TRADITIONAL', '')"
764
+ end
765
+ sql_mode = "CONCAT(#{sql_mode}, ',NO_AUTO_VALUE_ON_ZERO')"
766
+ end
767
+ sql_mode_assignment = "@@SESSION.sql_mode = #{sql_mode}, " if sql_mode
768
+
769
+ # NAMES does not have an equals sign, see
770
+ # https://dev.mysql.com/doc/refman/5.7/en/set-names.html
771
+ # (trailing comma because variable_assignments will always have content)
772
+ if @config[:encoding]
773
+ encoding = "NAMES #{@config[:encoding]}".dup
774
+ encoding << " COLLATE #{@config[:collation]}" if @config[:collation]
775
+ encoding << ", "
776
+ end
661
777
 
662
- private
778
+ # Gather up all of the SET variables...
779
+ variable_assignments = variables.map do |k, v|
780
+ if defaults.include?(v)
781
+ "@@SESSION.#{k} = DEFAULT" # Sets the value to the global or compile default
782
+ elsif !v.nil?
783
+ "@@SESSION.#{k} = #{quote(v)}"
784
+ end
785
+ # or else nil; compact to clear nils out
786
+ end.compact.join(", ")
663
787
 
664
- def supports_views?
665
- version[0] >= 5
666
- end
788
+ # ...and send them all in one query
789
+ execute "SET #{encoding} #{sql_mode_assignment} #{variable_assignments}"
790
+ end
667
791
 
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}"
792
+ def column_definitions(table_name) # :nodoc:
793
+ execute_and_free("SHOW FULL FIELDS FROM #{quote_table_name(table_name)}", "SCHEMA") do |result|
794
+ each_hash(result)
795
+ end
671
796
  end
672
- column
673
- end
797
+
798
+ def create_table_info(table_name) # :nodoc:
799
+ exec_query("SHOW CREATE TABLE #{quote_table_name(table_name)}", "SCHEMA").first["Create Table"]
800
+ end
801
+
802
+ def arel_visitor
803
+ Arel::Visitors::MySQL.new(self)
804
+ end
805
+
806
+ def mismatched_foreign_key(message)
807
+ match = %r/
808
+ (?:CREATE|ALTER)\s+TABLE\s*(?:`?\w+`?\.)?`?(?<table>\w+)`?.+?
809
+ FOREIGN\s+KEY\s*\(`?(?<foreign_key>\w+)`?\)\s*
810
+ REFERENCES\s*(`?(?<target_table>\w+)`?)\s*\(`?(?<primary_key>\w+)`?\)
811
+ /xmi.match(message)
812
+
813
+ options = {
814
+ message: message,
815
+ }
816
+
817
+ if match
818
+ options[:table] = match[:table]
819
+ options[:foreign_key] = match[:foreign_key]
820
+ options[:target_table] = match[:target_table]
821
+ options[:primary_key] = match[:primary_key]
822
+ options[:primary_key_column] = column_for(match[:target_table], match[:primary_key])
823
+ end
824
+
825
+ MismatchedForeignKey.new(options)
826
+ end
827
+
828
+ def integer_to_sql(limit) # :nodoc:
829
+ case limit
830
+ when 1; "tinyint"
831
+ when 2; "smallint"
832
+ when 3; "mediumint"
833
+ when nil, 4; "int"
834
+ when 5..8; "bigint"
835
+ else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a decimal with scale 0 instead.")
836
+ end
837
+ end
838
+
839
+ def text_to_sql(limit) # :nodoc:
840
+ case limit
841
+ when 0..0xff; "tinytext"
842
+ when nil, 0x100..0xffff; "text"
843
+ when 0x10000..0xffffff; "mediumtext"
844
+ when 0x1000000..0xffffffff; "longtext"
845
+ else raise(ActiveRecordError, "No text type has byte length #{limit}")
846
+ end
847
+ end
848
+
849
+ def binary_to_sql(limit) # :nodoc:
850
+ case limit
851
+ when 0..0xff; "tinyblob"
852
+ when nil, 0x100..0xffff; "blob"
853
+ when 0x10000..0xffffff; "mediumblob"
854
+ when 0x1000000..0xffffffff; "longblob"
855
+ else raise(ActiveRecordError, "No binary type has byte length #{limit}")
856
+ end
857
+ end
858
+
859
+ def version_string
860
+ full_version.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)[1]
861
+ end
862
+
863
+ class MysqlString < Type::String # :nodoc:
864
+ def serialize(value)
865
+ case value
866
+ when true then "1"
867
+ when false then "0"
868
+ else super
869
+ end
870
+ end
871
+
872
+ private
873
+
874
+ def cast_value(value)
875
+ case value
876
+ when true then "1"
877
+ when false then "0"
878
+ else super
879
+ end
880
+ end
881
+ end
882
+
883
+ ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql2)
884
+ ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :mysql2)
674
885
  end
675
886
  end
676
887
  end