activerecord 4.2.0 → 5.2.8.1

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 (274) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +640 -928
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +10 -11
  5. data/examples/performance.rb +32 -31
  6. data/examples/simple.rb +5 -4
  7. data/lib/active_record/aggregations.rb +264 -247
  8. data/lib/active_record/association_relation.rb +24 -6
  9. data/lib/active_record/associations/alias_tracker.rb +29 -35
  10. data/lib/active_record/associations/association.rb +87 -41
  11. data/lib/active_record/associations/association_scope.rb +106 -132
  12. data/lib/active_record/associations/belongs_to_association.rb +55 -36
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +8 -8
  14. data/lib/active_record/associations/builder/association.rb +29 -38
  15. data/lib/active_record/associations/builder/belongs_to.rb +77 -30
  16. data/lib/active_record/associations/builder/collection_association.rb +14 -23
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +50 -39
  18. data/lib/active_record/associations/builder/has_many.rb +6 -4
  19. data/lib/active_record/associations/builder/has_one.rb +13 -6
  20. data/lib/active_record/associations/builder/singular_association.rb +15 -11
  21. data/lib/active_record/associations/collection_association.rb +145 -266
  22. data/lib/active_record/associations/collection_proxy.rb +242 -138
  23. data/lib/active_record/associations/foreign_association.rb +13 -0
  24. data/lib/active_record/associations/has_many_association.rb +35 -75
  25. data/lib/active_record/associations/has_many_through_association.rb +51 -69
  26. data/lib/active_record/associations/has_one_association.rb +39 -24
  27. data/lib/active_record/associations/has_one_through_association.rb +18 -9
  28. data/lib/active_record/associations/join_dependency/join_association.rb +40 -81
  29. data/lib/active_record/associations/join_dependency/join_base.rb +10 -9
  30. data/lib/active_record/associations/join_dependency/join_part.rb +12 -12
  31. data/lib/active_record/associations/join_dependency.rb +134 -154
  32. data/lib/active_record/associations/preloader/association.rb +85 -116
  33. data/lib/active_record/associations/preloader/through_association.rb +85 -74
  34. data/lib/active_record/associations/preloader.rb +83 -93
  35. data/lib/active_record/associations/singular_association.rb +27 -40
  36. data/lib/active_record/associations/through_association.rb +48 -23
  37. data/lib/active_record/associations.rb +1732 -1596
  38. data/lib/active_record/attribute_assignment.rb +58 -182
  39. data/lib/active_record/attribute_decorators.rb +39 -15
  40. data/lib/active_record/attribute_methods/before_type_cast.rb +12 -5
  41. data/lib/active_record/attribute_methods/dirty.rb +94 -125
  42. data/lib/active_record/attribute_methods/primary_key.rb +86 -71
  43. data/lib/active_record/attribute_methods/query.rb +4 -2
  44. data/lib/active_record/attribute_methods/read.rb +45 -63
  45. data/lib/active_record/attribute_methods/serialization.rb +40 -20
  46. data/lib/active_record/attribute_methods/time_zone_conversion.rb +62 -36
  47. data/lib/active_record/attribute_methods/write.rb +31 -46
  48. data/lib/active_record/attribute_methods.rb +170 -117
  49. data/lib/active_record/attributes.rb +201 -74
  50. data/lib/active_record/autosave_association.rb +118 -45
  51. data/lib/active_record/base.rb +60 -48
  52. data/lib/active_record/callbacks.rb +97 -57
  53. data/lib/active_record/coders/json.rb +3 -1
  54. data/lib/active_record/coders/yaml_column.rb +37 -13
  55. data/lib/active_record/collection_cache_key.rb +53 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +712 -284
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +10 -5
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +254 -87
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +72 -22
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +119 -52
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +6 -4
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +67 -46
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +328 -217
  64. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +81 -36
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +617 -212
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +139 -75
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +332 -191
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +567 -563
  69. data/lib/active_record/connection_adapters/column.rb +50 -41
  70. data/lib/active_record/connection_adapters/connection_specification.rb +147 -135
  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 +42 -195
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +35 -11
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +46 -115
  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 +50 -57
  86. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +10 -6
  87. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +2 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +5 -2
  89. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +5 -1
  90. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +13 -1
  91. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +9 -13
  92. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
  93. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +7 -3
  94. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +31 -19
  95. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +2 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +3 -11
  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 +7 -9
  99. data/lib/active_record/connection_adapters/postgresql/oid/{integer.rb → oid.rb} +6 -2
  100. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +33 -11
  101. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +52 -34
  102. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +4 -1
  103. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +65 -51
  104. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +5 -3
  105. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +3 -1
  106. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +3 -1
  107. data/lib/active_record/connection_adapters/postgresql/oid.rb +23 -25
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +107 -47
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +27 -14
  110. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +65 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +144 -90
  112. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +466 -280
  114. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +39 -0
  115. data/lib/active_record/connection_adapters/postgresql/utils.rb +12 -8
  116. data/lib/active_record/connection_adapters/postgresql_adapter.rb +439 -330
  117. data/lib/active_record/connection_adapters/schema_cache.rb +48 -24
  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 +269 -324
  126. data/lib/active_record/connection_adapters/statement_pool.rb +34 -13
  127. data/lib/active_record/connection_handling.rb +40 -27
  128. data/lib/active_record/core.rb +205 -202
  129. data/lib/active_record/counter_cache.rb +80 -37
  130. data/lib/active_record/define_callbacks.rb +22 -0
  131. data/lib/active_record/dynamic_matchers.rb +87 -105
  132. data/lib/active_record/enum.rb +136 -90
  133. data/lib/active_record/errors.rb +180 -52
  134. data/lib/active_record/explain.rb +23 -11
  135. data/lib/active_record/explain_registry.rb +4 -2
  136. data/lib/active_record/explain_subscriber.rb +11 -6
  137. data/lib/active_record/fixture_set/file.rb +35 -9
  138. data/lib/active_record/fixtures.rb +193 -135
  139. data/lib/active_record/gem_version.rb +5 -3
  140. data/lib/active_record/inheritance.rb +148 -112
  141. data/lib/active_record/integration.rb +70 -28
  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 +3 -2
  145. data/lib/active_record/locking/optimistic.rb +92 -98
  146. data/lib/active_record/locking/pessimistic.rb +15 -3
  147. data/lib/active_record/log_subscriber.rb +95 -33
  148. data/lib/active_record/migration/command_recorder.rb +133 -90
  149. data/lib/active_record/migration/compatibility.rb +217 -0
  150. data/lib/active_record/migration/join_table.rb +8 -6
  151. data/lib/active_record/migration.rb +594 -267
  152. data/lib/active_record/model_schema.rb +292 -111
  153. data/lib/active_record/nested_attributes.rb +266 -214
  154. data/lib/active_record/no_touching.rb +8 -2
  155. data/lib/active_record/null_relation.rb +24 -37
  156. data/lib/active_record/persistence.rb +350 -119
  157. data/lib/active_record/query_cache.rb +13 -24
  158. data/lib/active_record/querying.rb +19 -17
  159. data/lib/active_record/railtie.rb +117 -35
  160. data/lib/active_record/railties/console_sandbox.rb +2 -0
  161. data/lib/active_record/railties/controller_runtime.rb +9 -3
  162. data/lib/active_record/railties/databases.rake +160 -174
  163. data/lib/active_record/readonly_attributes.rb +5 -4
  164. data/lib/active_record/reflection.rb +447 -288
  165. data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
  166. data/lib/active_record/relation/batches.rb +204 -55
  167. data/lib/active_record/relation/calculations.rb +259 -244
  168. data/lib/active_record/relation/delegation.rb +67 -60
  169. data/lib/active_record/relation/finder_methods.rb +290 -253
  170. data/lib/active_record/relation/from_clause.rb +26 -0
  171. data/lib/active_record/relation/merger.rb +91 -68
  172. data/lib/active_record/relation/predicate_builder/array_handler.rb +24 -23
  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 +7 -1
  179. data/lib/active_record/relation/predicate_builder.rb +118 -92
  180. data/lib/active_record/relation/query_attribute.rb +45 -0
  181. data/lib/active_record/relation/query_methods.rb +446 -389
  182. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  183. data/lib/active_record/relation/spawn_methods.rb +18 -16
  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 +287 -339
  187. data/lib/active_record/result.rb +54 -36
  188. data/lib/active_record/runtime_registry.rb +6 -4
  189. data/lib/active_record/sanitization.rb +155 -124
  190. data/lib/active_record/schema.rb +30 -24
  191. data/lib/active_record/schema_dumper.rb +91 -87
  192. data/lib/active_record/schema_migration.rb +19 -19
  193. data/lib/active_record/scoping/default.rb +102 -84
  194. data/lib/active_record/scoping/named.rb +81 -32
  195. data/lib/active_record/scoping.rb +45 -26
  196. data/lib/active_record/secure_token.rb +40 -0
  197. data/lib/active_record/serialization.rb +5 -5
  198. data/lib/active_record/statement_cache.rb +45 -35
  199. data/lib/active_record/store.rb +42 -36
  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 +136 -95
  203. data/lib/active_record/tasks/mysql_database_tasks.rb +59 -89
  204. data/lib/active_record/tasks/postgresql_database_tasks.rb +84 -31
  205. data/lib/active_record/tasks/sqlite_database_tasks.rb +44 -16
  206. data/lib/active_record/timestamp.rb +70 -38
  207. data/lib/active_record/touch_later.rb +64 -0
  208. data/lib/active_record/transactions.rb +208 -123
  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 +4 -41
  212. data/lib/active_record/type/date_time.rb +4 -38
  213. data/lib/active_record/type/decimal_without_scale.rb +6 -2
  214. data/lib/active_record/type/hash_lookup_type_map.rb +13 -5
  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 +30 -15
  218. data/lib/active_record/type/text.rb +2 -2
  219. data/lib/active_record/type/time.rb +11 -16
  220. data/lib/active_record/type/type_map.rb +15 -17
  221. data/lib/active_record/type/unsigned_integer.rb +9 -7
  222. data/lib/active_record/type.rb +79 -23
  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 +13 -4
  228. data/lib/active_record/validations/length.rb +26 -0
  229. data/lib/active_record/validations/presence.rb +14 -13
  230. data/lib/active_record/validations/uniqueness.rb +41 -32
  231. data/lib/active_record/validations.rb +38 -35
  232. data/lib/active_record/version.rb +3 -1
  233. data/lib/active_record.rb +36 -21
  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 +43 -35
  237. data/lib/rails/generators/active_record/migration/templates/{create_table_migration.rb → create_table_migration.rb.tt} +8 -6
  238. data/lib/rails/generators/active_record/migration/templates/{migration.rb → migration.rb.tt} +8 -7
  239. data/lib/rails/generators/active_record/migration.rb +18 -1
  240. data/lib/rails/generators/active_record/model/model_generator.rb +18 -22
  241. data/lib/rails/generators/active_record/model/templates/{model.rb → model.rb.tt} +3 -0
  242. data/lib/rails/generators/active_record.rb +7 -5
  243. metadata +77 -53
  244. data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
  245. data/lib/active_record/associations/preloader/collection_association.rb +0 -24
  246. data/lib/active_record/associations/preloader/has_many.rb +0 -17
  247. data/lib/active_record/associations/preloader/has_many_through.rb +0 -19
  248. data/lib/active_record/associations/preloader/has_one.rb +0 -23
  249. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  250. data/lib/active_record/associations/preloader/singular_association.rb +0 -21
  251. data/lib/active_record/attribute.rb +0 -149
  252. data/lib/active_record/attribute_set/builder.rb +0 -86
  253. data/lib/active_record/attribute_set.rb +0 -77
  254. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -491
  255. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
  256. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
  257. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
  258. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +0 -35
  259. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
  260. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  261. data/lib/active_record/serializers/xml_serializer.rb +0 -193
  262. data/lib/active_record/type/big_integer.rb +0 -13
  263. data/lib/active_record/type/binary.rb +0 -50
  264. data/lib/active_record/type/boolean.rb +0 -30
  265. data/lib/active_record/type/decimal.rb +0 -40
  266. data/lib/active_record/type/decorator.rb +0 -14
  267. data/lib/active_record/type/float.rb +0 -19
  268. data/lib/active_record/type/integer.rb +0 -55
  269. data/lib/active_record/type/mutable.rb +0 -16
  270. data/lib/active_record/type/numeric.rb +0 -36
  271. data/lib/active_record/type/string.rb +0 -36
  272. data/lib/active_record/type/time_value.rb +0 -38
  273. data/lib/active_record/type/value.rb +0 -101
  274. /data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +0 -0
@@ -1,203 +1,87 @@
1
- require 'arel/visitors/bind_visitor'
2
- require 'active_support/core_ext/string/strip'
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
- include Savepoints
8
-
9
- class SchemaCreation < AbstractAdapter::SchemaCreation
10
- def visit_AddColumn(o)
11
- add_column_position!(super, column_options(o))
12
- end
13
-
14
- private
15
-
16
- def visit_DropForeignKey(name)
17
- "DROP FOREIGN KEY #{name}"
18
- end
19
-
20
- def visit_TableDefinition(o)
21
- name = o.name
22
- create_sql = "CREATE#{' TEMPORARY' if o.temporary} TABLE #{quote_table_name(name)} "
23
-
24
- statements = o.columns.map { |c| accept c }
25
- statements.concat(o.indexes.map { |column_name, options| index_in_create(name, column_name, options) })
26
-
27
- create_sql << "(#{statements.join(', ')}) " if statements.present?
28
- create_sql << "#{o.options}"
29
- create_sql << " AS #{@conn.to_sql(o.as)}" if o.as
30
- create_sql
31
- end
32
-
33
- def visit_ChangeColumnDefinition(o)
34
- column = o.column
35
- options = o.options
36
- sql_type = type_to_sql(o.type, options[:limit], options[:precision], options[:scale])
37
- change_column_sql = "CHANGE #{quote_column_name(column.name)} #{quote_column_name(options[:name])} #{sql_type}"
38
- add_column_options!(change_column_sql, options.merge(column: column))
39
- add_column_position!(change_column_sql, options)
40
- end
41
-
42
- def add_column_position!(sql, options)
43
- if options[:first]
44
- sql << " FIRST"
45
- elsif options[:after]
46
- sql << " AFTER #{quote_column_name(options[:after])}"
47
- end
48
- sql
49
- end
50
-
51
- def index_in_create(table_name, column_name, options)
52
- index_name, index_type, index_columns, index_options, index_algorithm, index_using = @conn.add_index_options(table_name, column_name, options)
53
- "#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_options} #{index_algorithm}"
54
- end
55
- end
56
-
57
- def schema_creation
58
- SchemaCreation.new self
59
- end
60
-
61
- class Column < ConnectionAdapters::Column # :nodoc:
62
- attr_reader :collation, :strict, :extra
63
-
64
- def initialize(name, default, cast_type, sql_type = nil, null = true, collation = nil, strict = false, extra = "")
65
- @strict = strict
66
- @collation = collation
67
- @extra = extra
68
- super(name, default, cast_type, sql_type, null)
69
- assert_valid_default(default)
70
- extract_default
71
- end
72
-
73
- def extract_default
74
- if blob_or_text_column?
75
- @default = null || strict ? nil : ''
76
- elsif missing_default_forged_as_empty_string?(@default)
77
- @default = nil
78
- end
79
- end
80
-
81
- def has_default?
82
- return false if blob_or_text_column? # MySQL forbids defaults on blob and text columns
83
- super
84
- end
85
-
86
- def blob_or_text_column?
87
- sql_type =~ /blob/i || type == :text
88
- end
89
-
90
- def case_sensitive?
91
- collation && !collation.match(/_ci$/)
92
- end
93
-
94
- def ==(other)
95
- super &&
96
- collation == other.collation &&
97
- strict == other.strict &&
98
- extra == other.extra
99
- end
100
-
101
- private
102
-
103
- # MySQL misreports NOT NULL column default when none is given.
104
- # We can't detect this for columns which may have a legitimate ''
105
- # default (string) but we can for others (integer, datetime, boolean,
106
- # and the rest).
107
- #
108
- # Test whether the column has default '', is not null, and is not
109
- # a type allowing default ''.
110
- def missing_default_forged_as_empty_string?(default)
111
- type != :string && !null && default == ''
112
- end
113
-
114
- def assert_valid_default(default)
115
- if blob_or_text_column? && default.present?
116
- raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
117
- end
118
- end
119
-
120
- def attributes_for_hash
121
- super + [collation, strict, extra]
122
- end
123
- end
19
+ include MySQL::Quoting
20
+ include MySQL::SchemaStatements
124
21
 
125
22
  ##
126
23
  # :singleton-method:
127
- # By default, the MysqlAdapter will consider all columns of type <tt>tinyint(1)</tt>
128
- # as boolean. If you wish to disable this emulation (which was the default
129
- # 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
130
26
  # to your application.rb file:
131
27
  #
132
- # ActiveRecord::ConnectionAdapters::Mysql[2]Adapter.emulate_booleans = false
133
- class_attribute :emulate_booleans
134
- self.emulate_booleans = true
135
-
136
- LOST_CONNECTION_ERROR_MESSAGES = [
137
- "Server shutdown in progress",
138
- "Broken pipe",
139
- "Lost connection to MySQL server during query",
140
- "MySQL server has gone away" ]
141
-
142
- QUOTED_TRUE, QUOTED_FALSE = '1', '0'
28
+ # ActiveRecord::ConnectionAdapters::Mysql2Adapter.emulate_booleans = false
29
+ class_attribute :emulate_booleans, default: true
143
30
 
144
31
  NATIVE_DATABASE_TYPES = {
145
- :primary_key => "int(11) auto_increment PRIMARY KEY",
146
- :string => { :name => "varchar", :limit => 255 },
147
- :text => { :name => "text" },
148
- :integer => { :name => "int", :limit => 4 },
149
- :float => { :name => "float" },
150
- :decimal => { :name => "decimal" },
151
- :datetime => { :name => "datetime" },
152
- :time => { :name => "time" },
153
- :date => { :name => "date" },
154
- :binary => { :name => "blob" },
155
- :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" },
156
45
  }
157
46
 
158
- INDEX_TYPES = [:fulltext, :spatial]
159
- INDEX_USINGS = [:btree, :hash]
47
+ class StatementPool < ConnectionAdapters::StatementPool # :nodoc:
48
+ private def dealloc(stmt)
49
+ stmt[:stmt].close
50
+ end
51
+ end
160
52
 
161
- # FIXME: Make the first parameter more similar for the two adapters
162
53
  def initialize(connection, logger, connection_options, config)
163
- super(connection, logger)
164
- @connection_options, @config = connection_options, config
165
- @quoted_column_names, @quoted_table_names = {}, {}
54
+ super(connection, logger, config)
166
55
 
167
- @visitor = Arel::Visitors::MySQL.new self
56
+ @statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit]))
168
57
 
169
- if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
170
- @prepared_statements = true
171
- else
172
- @prepared_statements = false
58
+ if version < "5.1.10"
59
+ raise "Your version of MySQL (#{version_string}) is too old. Active Record supports MySQL >= 5.1.10."
173
60
  end
174
61
  end
175
62
 
176
- # Returns true, since this connection adapter supports migrations.
177
- def supports_migrations?
178
- true
63
+ def version #:nodoc:
64
+ @version ||= Version.new(version_string)
179
65
  end
180
66
 
181
- def supports_primary_key?
182
- true
67
+ def mariadb? # :nodoc:
68
+ /mariadb/i.match?(full_version)
183
69
  end
184
70
 
185
71
  def supports_bulk_alter? #:nodoc:
186
72
  true
187
73
  end
188
74
 
189
- # Technically MySQL allows to create indexes with the sort order syntax
190
- # but at the moment (5.5) it doesn't yet implement them
191
75
  def supports_index_sort_order?
192
- true
76
+ !mariadb? && version >= "8.0.1"
193
77
  end
194
78
 
195
- # MySQL 4 technically support transaction isolation, but it is affected by a bug
196
- # where the transaction level gets persisted for the whole session:
197
- #
198
- # http://bugs.mysql.com/bug.php?id=39170
199
79
  def supports_transaction_isolation?
200
- version[0] >= 5
80
+ true
81
+ end
82
+
83
+ def supports_explain?
84
+ true
201
85
  end
202
86
 
203
87
  def supports_indexes_in_create?
@@ -209,7 +93,35 @@ module ActiveRecord
209
93
  end
210
94
 
211
95
  def supports_views?
212
- version[0] >= 5
96
+ true
97
+ end
98
+
99
+ def supports_datetime_with_precision?
100
+ if mariadb?
101
+ version >= "5.3.0"
102
+ else
103
+ version >= "5.6.4"
104
+ end
105
+ end
106
+
107
+ def supports_virtual_columns?
108
+ if mariadb?
109
+ version >= "5.2.0"
110
+ else
111
+ version >= "5.7.5"
112
+ end
113
+ end
114
+
115
+ def supports_advisory_locks?
116
+ true
117
+ end
118
+
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
122
+
123
+ def release_advisory_lock(lock_name) # :nodoc:
124
+ query_value("SELECT RELEASE_LOCK(#{quote(lock_name.to_s)})") == 1
213
125
  end
214
126
 
215
127
  def native_database_types
@@ -217,7 +129,7 @@ module ActiveRecord
217
129
  end
218
130
 
219
131
  def index_algorithms
220
- { default: 'ALGORITHM = DEFAULT', copy: 'ALGORITHM = COPY', inplace: 'ALGORITHM = INPLACE' }
132
+ { default: "ALGORITHM = DEFAULT".dup, copy: "ALGORITHM = COPY".dup, inplace: "ALGORITHM = INPLACE".dup }
221
133
  end
222
134
 
223
135
  # HELPER METHODS ===========================================
@@ -228,54 +140,16 @@ module ActiveRecord
228
140
  raise NotImplementedError
229
141
  end
230
142
 
231
- def new_column(field, default, cast_type, sql_type = nil, null = true, collation = "", extra = "") # :nodoc:
232
- Column.new(field, default, cast_type, sql_type, null, collation, strict_mode?, extra)
233
- end
234
-
235
143
  # Must return the MySQL error number from the exception, if the exception has an
236
144
  # error number.
237
145
  def error_number(exception) # :nodoc:
238
146
  raise NotImplementedError
239
147
  end
240
148
 
241
- # QUOTING ==================================================
242
-
243
- def _quote(value) # :nodoc:
244
- if value.is_a?(Type::Binary::Data)
245
- "x'#{value.hex}'"
246
- else
247
- super
248
- end
249
- end
250
-
251
- def quote_column_name(name) #:nodoc:
252
- @quoted_column_names[name] ||= "`#{name.to_s.gsub('`', '``')}`"
253
- end
254
-
255
- def quote_table_name(name) #:nodoc:
256
- @quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`')
257
- end
258
-
259
- def quoted_true
260
- QUOTED_TRUE
261
- end
262
-
263
- def unquoted_true
264
- 1
265
- end
266
-
267
- def quoted_false
268
- QUOTED_FALSE
269
- end
270
-
271
- def unquoted_false
272
- 0
273
- end
274
-
275
149
  # REFERENTIAL INTEGRITY ====================================
276
150
 
277
151
  def disable_referential_integrity #:nodoc:
278
- old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
152
+ old = query_value("SELECT @@FOREIGN_KEY_CHECKS")
279
153
 
280
154
  begin
281
155
  update("SET FOREIGN_KEY_CHECKS = 0")
@@ -285,32 +159,43 @@ module ActiveRecord
285
159
  end
286
160
  end
287
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
+
288
170
  #--
289
171
  # DATABASE STATEMENTS ======================================
290
172
  #++
291
173
 
292
- def clear_cache!
293
- super
294
- reload_type_map
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)
295
181
  end
296
182
 
297
183
  # Executes the SQL statement in the context of this connection.
298
184
  def execute(sql, name = nil)
299
- log(sql, name) { @connection.query(sql) }
185
+ log(sql, name) do
186
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
187
+ @connection.query(sql)
188
+ end
189
+ end
300
190
  end
301
191
 
302
- # MysqlAdapter has to free a result after using it, so we use this method to write
303
- # stuff in an abstract way without concerning ourselves about whether it needs to be
304
- # explicitly freed or not.
305
- 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:
306
196
  yield execute(sql, name)
307
197
  end
308
198
 
309
- def update_sql(sql, name = nil) #:nodoc:
310
- super
311
- @connection.affected_rows
312
- end
313
-
314
199
  def begin_db_transaction
315
200
  execute "BEGIN"
316
201
  end
@@ -324,14 +209,14 @@ module ActiveRecord
324
209
  execute "COMMIT"
325
210
  end
326
211
 
327
- def rollback_db_transaction #:nodoc:
212
+ def exec_rollback_db_transaction #:nodoc:
328
213
  execute "ROLLBACK"
329
214
  end
330
215
 
331
216
  # In the simple case, MySQL allows us to place JOINs directly into the UPDATE
332
217
  # query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
333
218
  # these, we must use a subquery.
334
- def join_to_update(update, select) #:nodoc:
219
+ def join_to_update(update, select, key) # :nodoc:
335
220
  if select.limit || select.offset || select.orders.any?
336
221
  super
337
222
  else
@@ -364,9 +249,9 @@ module ActiveRecord
364
249
  # create_database 'matt_development', charset: :big5
365
250
  def create_database(name, options = {})
366
251
  if options[:collation]
367
- 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])}"
368
253
  else
369
- 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')}"
370
255
  end
371
256
  end
372
257
 
@@ -375,97 +260,42 @@ module ActiveRecord
375
260
  # Example:
376
261
  # drop_database('sebastian_development')
377
262
  def drop_database(name) #:nodoc:
378
- execute "DROP DATABASE IF EXISTS `#{name}`"
263
+ execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
379
264
  end
380
265
 
381
266
  def current_database
382
- select_value 'SELECT DATABASE() as db'
267
+ query_value("SELECT database()", "SCHEMA")
383
268
  end
384
269
 
385
270
  # Returns the database character set.
386
271
  def charset
387
- show_variable 'character_set_database'
272
+ show_variable "character_set_database"
388
273
  end
389
274
 
390
275
  # Returns the database collation strategy.
391
276
  def collation
392
- show_variable 'collation_database'
393
- end
394
-
395
- def tables(name = nil, database = nil, like = nil) #:nodoc:
396
- sql = "SHOW TABLES "
397
- sql << "IN #{quote_table_name(database)} " if database
398
- sql << "LIKE #{quote(like)}" if like
399
-
400
- execute_and_free(sql, 'SCHEMA') do |result|
401
- result.collect { |field| field.first }
402
- end
277
+ show_variable "collation_database"
403
278
  end
404
279
 
405
280
  def truncate(table_name, name = nil)
406
281
  execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
407
282
  end
408
283
 
409
- def table_exists?(name)
410
- return false unless name.present?
411
- return true if tables(nil, nil, name).any?
412
-
413
- name = name.to_s
414
- schema, table = name.split('.', 2)
415
-
416
- unless table # A table was provided without a schema
417
- table = schema
418
- schema = nil
419
- end
420
-
421
- tables(nil, schema, table).any?
422
- end
423
-
424
- # Returns an array of indexes for the given table.
425
- def indexes(table_name, name = nil) #:nodoc:
426
- indexes = []
427
- current_index = nil
428
- execute_and_free("SHOW KEYS FROM #{quote_table_name(table_name)}", 'SCHEMA') do |result|
429
- each_hash(result) do |row|
430
- if current_index != row[:Key_name]
431
- next if row[:Key_name] == 'PRIMARY' # skip the primary key
432
- current_index = row[:Key_name]
433
-
434
- mysql_index_type = row[:Index_type].downcase.to_sym
435
- index_type = INDEX_TYPES.include?(mysql_index_type) ? mysql_index_type : nil
436
- index_using = INDEX_USINGS.include?(mysql_index_type) ? mysql_index_type : nil
437
- indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], [], nil, nil, index_type, index_using)
438
- end
439
-
440
- indexes.last.columns << row[:Column_name]
441
- indexes.last.lengths << row[:Sub_part]
442
- end
443
- end
444
-
445
- indexes
446
- end
447
-
448
- # Returns an array of +Column+ objects for the table specified by +table_name+.
449
- def columns(table_name)#:nodoc:
450
- sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
451
- execute_and_free(sql, 'SCHEMA') do |result|
452
- each_hash(result).map do |field|
453
- field_name = set_field_encoding(field[:Field])
454
- sql_type = field[:Type]
455
- cast_type = lookup_cast_type(sql_type)
456
- new_column(field_name, field[:Default], cast_type, sql_type, field[:Null] == "YES", field[:Collation], field[:Extra])
457
- end
458
- end
459
- end
284
+ def table_comment(table_name) # :nodoc:
285
+ scope = quoted_scope(table_name)
460
286
 
461
- def create_table(table_name, options = {}) #:nodoc:
462
- 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
463
293
  end
464
294
 
465
295
  def bulk_change_table(table_name, operations) #:nodoc:
466
296
  sqls = operations.flat_map do |command, args|
467
297
  table, arguments = args.shift, args
468
- method = :"#{command}_sql"
298
+ method = :"#{command}_for_alter"
469
299
 
470
300
  if respond_to?(method, true)
471
301
  send(method, table, *arguments)
@@ -477,6 +307,11 @@ module ActiveRecord
477
307
  execute("ALTER TABLE #{quote_table_name(table_name)} #{sqls}")
478
308
  end
479
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
+
480
315
  # Renames a table.
481
316
  #
482
317
  # Example:
@@ -486,398 +321,567 @@ module ActiveRecord
486
321
  rename_table_indexes(table_name, new_name)
487
322
  end
488
323
 
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.
489
339
  def drop_table(table_name, options = {})
490
- execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
340
+ execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
491
341
  end
492
342
 
493
343
  def rename_index(table_name, old_name, new_name)
494
344
  if supports_rename_index?
345
+ validate_index_length!(table_name, new_name)
346
+
495
347
  execute "ALTER TABLE #{quote_table_name(table_name)} RENAME INDEX #{quote_table_name(old_name)} TO #{quote_table_name(new_name)}"
496
348
  else
497
349
  super
498
350
  end
499
351
  end
500
352
 
501
- def change_column_default(table_name, column_name, default) #:nodoc:
502
- column = column_for(table_name, column_name)
503
- change_column table_name, column_name, column.sql_type, :default => default
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
504
356
  end
505
357
 
506
- def change_column_null(table_name, column_name, null, default = nil)
507
- column = column_for(table_name, column_name)
508
-
358
+ def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
509
359
  unless null || default.nil?
510
360
  execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
511
361
  end
512
362
 
513
- 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
514
368
  end
515
369
 
516
370
  def change_column(table_name, column_name, type, options = {}) #:nodoc:
517
- 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)}")
518
372
  end
519
373
 
520
374
  def rename_column(table_name, column_name, new_column_name) #:nodoc:
521
- 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)}")
522
376
  rename_column_indexes(table_name, column_name, new_column_name)
523
377
  end
524
378
 
525
379
  def add_index(table_name, column_name, options = {}) #:nodoc:
526
- index_name, index_type, index_columns, index_options, index_algorithm, index_using = add_index_options(table_name, column_name, options)
527
- execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options} #{index_algorithm}"
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
528
388
  end
529
389
 
530
390
  def foreign_keys(table_name)
531
- fk_info = select_all <<-SQL.strip_heredoc
532
- SELECT fk.referenced_table_name as 'to_table'
533
- ,fk.referenced_column_name as 'primary_key'
534
- ,fk.column_name as 'column'
535
- ,fk.constraint_name as 'name'
536
- FROM information_schema.key_column_usage fk
537
- WHERE fk.referenced_column_name is not null
538
- AND fk.table_schema = '#{@config[:database]}'
539
- AND fk.table_name = '#{table_name}'
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]}
540
410
  SQL
541
411
 
542
- create_table_info = select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
543
-
544
412
  fk_info.map do |row|
545
413
  options = {
546
- column: row['column'],
547
- name: row['name'],
548
- primary_key: row['primary_key']
414
+ column: row["column"],
415
+ name: row["name"],
416
+ primary_key: row["primary_key"]
549
417
  }
550
418
 
551
- options[:on_update] = extract_foreign_key_action(create_table_info, row['name'], "UPDATE")
552
- options[:on_delete] = extract_foreign_key_action(create_table_info, row['name'], "DELETE")
419
+ options[:on_update] = extract_foreign_key_action(row["on_update"])
420
+ options[:on_delete] = extract_foreign_key_action(row["on_delete"])
553
421
 
554
- ForeignKeyDefinition.new(table_name, row['to_table'], options)
422
+ ForeignKeyDefinition.new(table_name, row["to_table"], options)
555
423
  end
556
424
  end
557
425
 
558
- # Maps logical Rails types to MySQL-specific data types.
559
- def type_to_sql(type, limit = nil, precision = nil, scale = nil)
560
- case type.to_s
561
- when 'binary'
562
- case limit
563
- when 0..0xfff; "varbinary(#{limit})"
564
- when nil; "blob"
565
- when 0x1000..0xffffffff; "blob(#{limit})"
566
- else raise(ActiveRecordError, "No binary type has character length #{limit}")
567
- end
568
- when 'integer'
569
- case limit
570
- when 1; 'tinyint'
571
- when 2; 'smallint'
572
- when 3; 'mediumint'
573
- when nil, 4, 11; 'int(11)' # compatibility with MySQL default
574
- when 5..8; 'bigint'
575
- else raise(ActiveRecordError, "No integer type has byte size #{limit}")
576
- end
577
- when 'text'
578
- case limit
579
- when 0..0xff; 'tinytext'
580
- when nil, 0x100..0xffff; 'text'
581
- when 0x10000..0xffffff; 'mediumtext'
582
- when 0x1000000..0xffffffff; 'longtext'
583
- else raise(ActiveRecordError, "No text type has character length #{limit}")
584
- end
585
- else
586
- super
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)
587
442
  end
588
- end
589
443
 
590
- # SHOW VARIABLES LIKE 'name'
591
- def show_variable(name)
592
- variables = select_all("SHOW VARIABLES LIKE '#{name}'", 'SCHEMA')
593
- variables.first['Value'] unless variables.empty?
444
+ table_options
594
445
  end
595
446
 
596
- # Returns a table's primary key and belonging sequence.
597
- def pk_and_sequence_for(table)
598
- execute_and_free("SHOW CREATE TABLE #{quote_table_name(table)}", 'SCHEMA') do |result|
599
- create_table = each_hash(result).first[:"Create Table"]
600
- if create_table.to_s =~ /PRIMARY KEY\s+(?:USING\s+\w+\s+)?\((.+)\)/
601
- keys = $1.split(",").map { |key| key.delete('`"') }
602
- keys.length == 1 ? [keys.first, nil] : nil
447
+ # Maps logical Rails types to MySQL-specific data types.
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
603
463
  else
604
- nil
464
+ super
605
465
  end
606
- end
466
+
467
+ sql = "#{sql} unsigned" if unsigned && type != :primary_key
468
+ sql
607
469
  end
608
470
 
609
- # Returns just a table's primary key
610
- def primary_key(table)
611
- pk_and_sequence = pk_and_sequence_for(table)
612
- pk_and_sequence && pk_and_sequence.first
471
+ # SHOW VARIABLES LIKE 'name'
472
+ def show_variable(name)
473
+ query_value("SELECT @@#{name}", "SCHEMA")
474
+ rescue ActiveRecord::StatementInvalid
475
+ nil
613
476
  end
614
477
 
615
- def case_sensitive_modifier(node, table_attribute)
616
- node = Arel::Nodes.build_quoted node, table_attribute
617
- Arel::Nodes::Bin.new(node)
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
618
491
  end
619
492
 
620
- def case_sensitive_comparison(table, attribute, column, value)
621
- if column.case_sensitive?
622
- table[attribute].eq(value)
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))
623
496
  else
624
497
  super
625
498
  end
626
499
  end
627
500
 
628
- def case_insensitive_comparison(table, attribute, column, value)
629
- if column.case_sensitive?
630
- super
631
- else
632
- table[attribute].eq(value)
633
- end
501
+ def can_perform_case_insensitive_comparison_for?(column)
502
+ column.case_sensitive?
503
+ end
504
+ private :can_perform_case_insensitive_comparison_for?
505
+
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(", ")
634
519
  end
635
520
 
636
521
  def strict_mode?
637
522
  self.class.type_cast_config_to_boolean(@config.fetch(:strict, true))
638
523
  end
639
524
 
640
- def valid_type?(type)
641
- !native_database_types[type].nil?
525
+ def default_index_type?(index) # :nodoc:
526
+ index.using == :btree || super
642
527
  end
643
528
 
644
- protected
529
+ def insert_fixtures_set(fixture_set, tables_to_delete = [])
530
+ with_multi_statements do
531
+ super { discard_remaining_results }
532
+ end
533
+ end
645
534
 
646
- def initialize_type_map(m) # :nodoc:
647
- super
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
648
547
 
649
- register_class_with_limit m, %r(char)i, MysqlString
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
650
557
 
651
- m.register_type %r(tinytext)i, Type::Text.new(limit: 2**8 - 1)
652
- m.register_type %r(tinyblob)i, Type::Binary.new(limit: 2**8 - 1)
653
- m.register_type %r(text)i, Type::Text.new(limit: 2**16 - 1)
654
- m.register_type %r(blob)i, Type::Binary.new(limit: 2**16 - 1)
655
- m.register_type %r(mediumtext)i, Type::Text.new(limit: 2**24 - 1)
656
- m.register_type %r(mediumblob)i, Type::Binary.new(limit: 2**24 - 1)
657
- m.register_type %r(longtext)i, Type::Text.new(limit: 2**32 - 1)
658
- m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1)
659
- m.register_type %r(^float)i, Type::Float.new(limit: 24)
660
- m.register_type %r(^double)i, Type::Float.new(limit: 53)
558
+ def max_allowed_packet
559
+ bytes_margin = 2
560
+ @max_allowed_packet ||= (show_variable("max_allowed_packet") - bytes_margin)
561
+ end
661
562
 
662
- register_integer_type m, %r(^bigint)i, limit: 8
663
- register_integer_type m, %r(^int)i, limit: 4
664
- register_integer_type m, %r(^mediumint)i, limit: 3
665
- register_integer_type m, %r(^smallint)i, limit: 2
666
- register_integer_type m, %r(^tinyint)i, limit: 1
563
+ def initialize_type_map(m = type_map)
564
+ super
667
565
 
668
- m.alias_type %r(tinyint\(1\))i, 'boolean' if emulate_booleans
669
- m.alias_type %r(set)i, 'varchar'
670
- m.alias_type %r(year)i, 'integer'
671
- m.alias_type %r(bit)i, 'binary'
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
672
594
 
673
- m.register_type(%r(enum)i) do |sql_type|
674
- limit = sql_type[/^enum\((.+)\)/i, 1]
675
- .split(',').map{|enum| enum.strip.length - 2}.max
676
- MysqlString.new(limit: limit)
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
677
600
  end
678
- end
679
601
 
680
- def register_integer_type(mapping, key, options) # :nodoc:
681
- mapping.register_type(key) do |sql_type|
682
- if /unsigned/i =~ sql_type
683
- Type::UnsignedInteger.new(options)
684
- else
685
- Type::Integer.new(options)
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
686
609
  end
687
610
  end
688
- end
689
611
 
690
- # MySQL is too stupid to create a temporary table for use subquery, so we have
691
- # to give it some prompting in the form of a subsubquery. Ugh!
692
- def subquery_for(key, select)
693
- subsubselect = select.clone
694
- subsubselect.projections = [key]
695
-
696
- subselect = Arel::SelectManager.new(select.engine)
697
- subselect.project Arel.sql(key.name)
698
- subselect.from subsubselect.as('__active_record_temp')
699
- end
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
700
619
 
701
- def add_index_length(option_strings, column_names, options = {})
702
- if options.is_a?(Hash) && length = options[:length]
703
- case length
704
- when Hash
705
- column_names.each {|name| option_strings[name] += "(#{length[name]})" if length.has_key?(name) && length[name].present?}
706
- when Fixnum
707
- 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
708
664
  end
709
665
  end
710
666
 
711
- return option_strings
712
- 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
713
670
 
714
- def quoted_columns_for_index(column_names, options = {})
715
- option_strings = Hash[column_names.map {|name| [name, '']}]
671
+ unless options.key?(:default)
672
+ options[:default] = column.default
673
+ end
716
674
 
717
- # add index length
718
- option_strings = add_index_length(option_strings, column_names, options)
675
+ unless options.key?(:null)
676
+ options[:null] = column.null
677
+ end
719
678
 
720
- # add index sort order
721
- option_strings = add_index_sort_order(option_strings, column_names, options)
679
+ unless options.key?(:comment)
680
+ options[:comment] = column.comment
681
+ end
722
682
 
723
- column_names.map {|name| quote_column_name(name) + option_strings[name]}
724
- 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
725
687
 
726
- def translate_exception(exception, message)
727
- case error_number(exception)
728
- when 1062
729
- RecordNotUnique.new(message, exception)
730
- when 1452
731
- InvalidForeignKey.new(message, exception)
732
- else
733
- 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))
734
700
  end
735
- end
736
701
 
737
- def add_column_sql(table_name, column_name, type, options = {})
738
- td = create_table_definition table_name, options[:temporary], options[:options]
739
- cd = td.new_column_definition(column_name, type, options)
740
- schema_creation.visit_AddColumn cd
741
- 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
742
707
 
743
- def change_column_sql(table_name, column_name, type, options = {})
744
- 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
745
712
 
746
- unless options_include_default?(options)
747
- 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)]
748
715
  end
749
716
 
750
- unless options.has_key?(:null)
751
- 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)]
752
719
  end
753
720
 
754
- options[:name] = column.name
755
- schema_creation.accept ChangeColumnDefinition.new column, type, options
756
- 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]
757
726
 
758
- def rename_column_sql(table_name, column_name, new_column_name)
759
- column = column_for(table_name, column_name)
760
- options = {
761
- name: new_column_name,
762
- default: column.default,
763
- null: column.null,
764
- auto_increment: column.extra == "auto_increment"
765
- }
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?
766
730
 
767
- current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", 'SCHEMA')["Type"]
768
- schema_creation.accept ChangeColumnDefinition.new column, current_type, options
769
- end
731
+ key_name = quote_column_name(key.name)
732
+ Arel::SelectManager.new(subselect.as("__active_record_temp")).project(Arel.sql(key_name))
733
+ end
770
734
 
771
- def remove_column_sql(table_name, column_name, type = nil, options = {})
772
- "DROP #{quote_column_name(column_name)}"
773
- end
735
+ def supports_rename_index?
736
+ mariadb? ? false : version >= "5.7.6"
737
+ end
774
738
 
775
- def remove_columns_sql(table_name, *column_names)
776
- column_names.map {|column_name| remove_column_sql(table_name, column_name) }
777
- end
739
+ def configure_connection
740
+ variables = @config.fetch(:variables, {}).stringify_keys
778
741
 
779
- def add_index_sql(table_name, column_name, options = {})
780
- index_name, index_type, index_columns = add_index_options(table_name, column_name, options)
781
- "ADD #{index_type} INDEX #{index_name} (#{index_columns})"
782
- end
742
+ # By default, MySQL 'where id is null' selects the last inserted id; Turn this off.
743
+ variables["sql_auto_is_null"] = 0
783
744
 
784
- def remove_index_sql(table_name, options = {})
785
- index_name = index_name_for_remove(table_name, options)
786
- "DROP INDEX #{index_name}"
787
- 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
788
749
 
789
- def add_timestamps_sql(table_name, options = {})
790
- [add_column_sql(table_name, :created_at, :datetime, options), add_column_sql(table_name, :updated_at, :datetime, options)]
791
- end
750
+ defaults = [":default", :default].to_set
792
751
 
793
- def remove_timestamps_sql(table_name, options = {})
794
- [remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)]
795
- 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
796
777
 
797
- 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(", ")
798
787
 
799
- def version
800
- @version ||= full_version.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
801
- end
788
+ # ...and send them all in one query
789
+ execute "SET #{encoding} #{sql_mode_assignment} #{variable_assignments}"
790
+ end
802
791
 
803
- def mariadb?
804
- full_version =~ /mariadb/i
805
- end
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
796
+ end
806
797
 
807
- def supports_rename_index?
808
- mariadb? ? false : (version[0] == 5 && version[1] >= 7) || version[0] >= 6
809
- end
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
810
801
 
811
- def configure_connection
812
- variables = @config.fetch(:variables, {}).stringify_keys
802
+ def arel_visitor
803
+ Arel::Visitors::MySQL.new(self)
804
+ end
813
805
 
814
- # By default, MySQL 'where id is null' selects the last inserted id.
815
- # Turn this off. http://dev.rubyonrails.org/ticket/6778
816
- variables['sql_auto_is_null'] = 0
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)
817
812
 
818
- # Increase timeout so the server doesn't disconnect us.
819
- wait_timeout = @config[:wait_timeout]
820
- wait_timeout = 2147483 unless wait_timeout.is_a?(Fixnum)
821
- variables['wait_timeout'] = self.class.type_cast_config_to_integer(wait_timeout)
813
+ options = {
814
+ message: message,
815
+ }
822
816
 
823
- # Make MySQL reject illegal values rather than truncating or blanking them, see
824
- # http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html#sqlmode_strict_all_tables
825
- # If the user has provided another value for sql_mode, don't replace it.
826
- unless variables.has_key?('sql_mode')
827
- variables['sql_mode'] = strict_mode? ? 'STRICT_ALL_TABLES' : ''
828
- end
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
829
824
 
830
- # NAMES does not have an equals sign, see
831
- # http://dev.mysql.com/doc/refman/5.0/en/set-statement.html#id944430
832
- # (trailing comma because variable_assignments will always have content)
833
- if @config[:encoding]
834
- encoding = "NAMES #{@config[:encoding]}"
835
- encoding << " COLLATE #{@config[:collation]}" if @config[:collation]
836
- encoding << ", "
825
+ MismatchedForeignKey.new(options)
837
826
  end
838
827
 
839
- # Gather up all of the SET variables...
840
- variable_assignments = variables.map do |k, v|
841
- if v == ':default' || v == :default
842
- "@@SESSION.#{k} = DEFAULT" # Sets the value to the global or compile default
843
- elsif !v.nil?
844
- "@@SESSION.#{k} = #{quote(v)}"
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.")
845
836
  end
846
- # or else nil; compact to clear nils out
847
- end.compact.join(', ')
848
-
849
- # ...and send them all in one query
850
- @connection.query "SET #{encoding} #{variable_assignments}"
851
- end
837
+ end
852
838
 
853
- def extract_foreign_key_action(structure, name, action) # :nodoc:
854
- if structure =~ /CONSTRAINT #{quote_column_name(name)} FOREIGN KEY .* REFERENCES .* ON #{action} (CASCADE|SET NULL|RESTRICT)/
855
- case $1
856
- when 'CASCADE'; :cascade
857
- when 'SET NULL'; :nullify
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}")
858
846
  end
859
847
  end
860
- end
861
848
 
862
- class MysqlString < Type::String # :nodoc:
863
- def type_cast_for_database(value)
864
- case value
865
- when true then "1"
866
- when false then "0"
867
- else super
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}")
868
856
  end
869
857
  end
870
858
 
871
- private
859
+ def version_string
860
+ full_version.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)[1]
861
+ end
872
862
 
873
- def cast_value(value)
874
- case value
875
- when true then "1"
876
- when false then "0"
877
- else super
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
878
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
879
881
  end
880
- end
882
+
883
+ ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql2)
884
+ ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :mysql2)
881
885
  end
882
886
  end
883
887
  end