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