activerecord 4.2.9 → 5.2.8

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

Potentially problematic release.


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

Files changed (274) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +614 -1572
  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 -89
  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 -593
  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 -188
  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 +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 -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 +4 -2
  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 -491
  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,54 +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
149
  # REFERENTIAL INTEGRITY ====================================
286
150
 
287
151
  def disable_referential_integrity #:nodoc:
288
- old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
152
+ old = query_value("SELECT @@FOREIGN_KEY_CHECKS")
289
153
 
290
154
  begin
291
155
  update("SET FOREIGN_KEY_CHECKS = 0")
@@ -295,32 +159,43 @@ module ActiveRecord
295
159
  end
296
160
  end
297
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
+
298
170
  #--
299
171
  # DATABASE STATEMENTS ======================================
300
172
  #++
301
173
 
302
- def clear_cache!
303
- super
304
- 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)
305
181
  end
306
182
 
307
183
  # Executes the SQL statement in the context of this connection.
308
184
  def execute(sql, name = nil)
309
- 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
310
190
  end
311
191
 
312
- # MysqlAdapter has to free a result after using it, so we use this method to write
313
- # stuff in an abstract way without concerning ourselves about whether it needs to be
314
- # explicitly freed or not.
315
- 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:
316
196
  yield execute(sql, name)
317
197
  end
318
198
 
319
- def update_sql(sql, name = nil) #:nodoc:
320
- super
321
- @connection.affected_rows
322
- end
323
-
324
199
  def begin_db_transaction
325
200
  execute "BEGIN"
326
201
  end
@@ -341,7 +216,7 @@ module ActiveRecord
341
216
  # In the simple case, MySQL allows us to place JOINs directly into the UPDATE
342
217
  # query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
343
218
  # these, we must use a subquery.
344
- def join_to_update(update, select) #:nodoc:
219
+ def join_to_update(update, select, key) # :nodoc:
345
220
  if select.limit || select.offset || select.orders.any?
346
221
  super
347
222
  else
@@ -374,9 +249,9 @@ module ActiveRecord
374
249
  # create_database 'matt_development', charset: :big5
375
250
  def create_database(name, options = {})
376
251
  if options[:collation]
377
- 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])}"
378
253
  else
379
- 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')}"
380
255
  end
381
256
  end
382
257
 
@@ -385,99 +260,42 @@ module ActiveRecord
385
260
  # Example:
386
261
  # drop_database('sebastian_development')
387
262
  def drop_database(name) #:nodoc:
388
- execute "DROP DATABASE IF EXISTS `#{name}`"
263
+ execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
389
264
  end
390
265
 
391
266
  def current_database
392
- select_value 'SELECT DATABASE() as db'
267
+ query_value("SELECT database()", "SCHEMA")
393
268
  end
394
269
 
395
270
  # Returns the database character set.
396
271
  def charset
397
- show_variable 'character_set_database'
272
+ show_variable "character_set_database"
398
273
  end
399
274
 
400
275
  # Returns the database collation strategy.
401
276
  def collation
402
- show_variable 'collation_database'
403
- end
404
-
405
- def tables(name = nil, database = nil, like = nil) #:nodoc:
406
- sql = "SHOW TABLES "
407
- sql << "IN #{quote_table_name(database)} " if database
408
- sql << "LIKE #{quote(like)}" if like
409
-
410
- execute_and_free(sql, 'SCHEMA') do |result|
411
- result.collect { |field| field.first }
412
- end
277
+ show_variable "collation_database"
413
278
  end
414
- alias data_sources tables
415
279
 
416
280
  def truncate(table_name, name = nil)
417
281
  execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
418
282
  end
419
283
 
420
- def table_exists?(name)
421
- return false unless name.present?
422
- return true if tables(nil, nil, name).any?
423
-
424
- name = name.to_s
425
- schema, table = name.split('.', 2)
426
-
427
- unless table # A table was provided without a schema
428
- table = schema
429
- schema = nil
430
- end
431
-
432
- tables(nil, schema, table).any?
433
- end
434
- alias data_source_exists? table_exists?
435
-
436
- # Returns an array of indexes for the given table.
437
- def indexes(table_name, name = nil) #:nodoc:
438
- indexes = []
439
- current_index = nil
440
- execute_and_free("SHOW KEYS FROM #{quote_table_name(table_name)}", 'SCHEMA') do |result|
441
- each_hash(result) do |row|
442
- if current_index != row[:Key_name]
443
- next if row[:Key_name] == 'PRIMARY' # skip the primary key
444
- current_index = row[:Key_name]
445
-
446
- mysql_index_type = row[:Index_type].downcase.to_sym
447
- index_type = INDEX_TYPES.include?(mysql_index_type) ? mysql_index_type : nil
448
- index_using = INDEX_USINGS.include?(mysql_index_type) ? mysql_index_type : nil
449
- indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], [], nil, nil, index_type, index_using)
450
- end
451
-
452
- indexes.last.columns << row[:Column_name]
453
- indexes.last.lengths << row[:Sub_part]
454
- end
455
- end
456
-
457
- indexes
458
- end
459
-
460
- # Returns an array of +Column+ objects for the table specified by +table_name+.
461
- def columns(table_name)#:nodoc:
462
- sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
463
- execute_and_free(sql, 'SCHEMA') do |result|
464
- each_hash(result).map do |field|
465
- field_name = set_field_encoding(field[:Field])
466
- sql_type = field[:Type]
467
- cast_type = lookup_cast_type(sql_type)
468
- new_column(field_name, field[:Default], cast_type, sql_type, field[:Null] == "YES", field[:Collation], field[:Extra])
469
- end
470
- end
471
- end
284
+ def table_comment(table_name) # :nodoc:
285
+ scope = quoted_scope(table_name)
472
286
 
473
- def create_table(table_name, options = {}) #:nodoc:
474
- 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
475
293
  end
476
294
 
477
295
  def bulk_change_table(table_name, operations) #:nodoc:
478
296
  sqls = operations.flat_map do |command, args|
479
297
  table, arguments = args.shift, args
480
- method = :"#{command}_sql"
298
+ method = :"#{command}_for_alter"
481
299
 
482
300
  if respond_to?(method, true)
483
301
  send(method, table, *arguments)
@@ -489,6 +307,11 @@ module ActiveRecord
489
307
  execute("ALTER TABLE #{quote_table_name(table_name)} #{sqls}")
490
308
  end
491
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
+
492
315
  # Renames a table.
493
316
  #
494
317
  # Example:
@@ -498,8 +321,23 @@ module ActiveRecord
498
321
  rename_table_indexes(table_name, new_name)
499
322
  end
500
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.
501
339
  def drop_table(table_name, options = {})
502
- 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}"
503
341
  end
504
342
 
505
343
  def rename_index(table_name, old_name, new_name)
@@ -512,149 +350,158 @@ module ActiveRecord
512
350
  end
513
351
  end
514
352
 
515
- def change_column_default(table_name, column_name, default) #:nodoc:
516
- column = column_for(table_name, column_name)
517
- 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
518
356
  end
519
357
 
520
- def change_column_null(table_name, column_name, null, default = nil)
521
- column = column_for(table_name, column_name)
522
-
358
+ def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
523
359
  unless null || default.nil?
524
360
  execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
525
361
  end
526
362
 
527
- 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
528
368
  end
529
369
 
530
370
  def change_column(table_name, column_name, type, options = {}) #:nodoc:
531
- 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)}")
532
372
  end
533
373
 
534
374
  def rename_column(table_name, column_name, new_column_name) #:nodoc:
535
- 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)}")
536
376
  rename_column_indexes(table_name, column_name, new_column_name)
537
377
  end
538
378
 
539
379
  def add_index(table_name, column_name, options = {}) #:nodoc:
540
- index_name, index_type, index_columns, index_options, index_algorithm, index_using = add_index_options(table_name, column_name, options)
541
- 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
542
388
  end
543
389
 
544
390
  def foreign_keys(table_name)
545
- fk_info = select_all <<-SQL.strip_heredoc
546
- SELECT fk.referenced_table_name as 'to_table'
547
- ,fk.referenced_column_name as 'primary_key'
548
- ,fk.column_name as 'column'
549
- ,fk.constraint_name as 'name'
550
- FROM information_schema.key_column_usage fk
551
- WHERE fk.referenced_column_name is not null
552
- AND fk.table_schema = '#{@config[:database]}'
553
- 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]}
554
410
  SQL
555
411
 
556
- create_table_info = select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
557
-
558
412
  fk_info.map do |row|
559
413
  options = {
560
- column: row['column'],
561
- name: row['name'],
562
- primary_key: row['primary_key']
414
+ column: row["column"],
415
+ name: row["name"],
416
+ primary_key: row["primary_key"]
563
417
  }
564
418
 
565
- options[:on_update] = extract_foreign_key_action(create_table_info, row['name'], "UPDATE")
566
- 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"])
421
+
422
+ ForeignKeyDefinition.new(table_name, row["to_table"], options)
423
+ end
424
+ end
425
+
426
+ def table_options(table_name) # :nodoc:
427
+ table_options = {}
428
+
429
+ create_table_info = create_table_info(table_name)
430
+
431
+ # strip create_definitions and partition_options
432
+ raw_table_options = create_table_info.sub(/\A.*\n\) /m, "").sub(/\n\/\*!.*\*\/\n\z/m, "").strip
567
433
 
568
- ForeignKeyDefinition.new(table_name, row['to_table'], options)
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)
569
442
  end
443
+
444
+ table_options
570
445
  end
571
446
 
572
447
  # Maps logical Rails types to MySQL-specific data types.
573
- def type_to_sql(type, limit = nil, precision = nil, scale = nil)
574
- case type.to_s
575
- when 'binary'
576
- case limit
577
- when 0..0xfff; "varbinary(#{limit})"
578
- when nil; "blob"
579
- when 0x1000..0xffffffff; "blob(#{limit})"
580
- else raise(ActiveRecordError, "No binary type has character length #{limit}")
581
- end
582
- when 'integer'
583
- case limit
584
- when 1; 'tinyint'
585
- when 2; 'smallint'
586
- when 3; 'mediumint'
587
- when nil, 4, 11; 'int(11)' # compatibility with MySQL default
588
- when 5..8; 'bigint'
589
- else raise(ActiveRecordError, "No integer type has byte size #{limit}")
590
- end
591
- when 'text'
592
- case limit
593
- when 0..0xff; 'tinytext'
594
- when nil, 0x100..0xffff; 'text'
595
- when 0x10000..0xffffff; 'mediumtext'
596
- when 0x1000000..0xffffffff; 'longtext'
597
- 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
598
465
  end
599
- when 'datetime'
600
- return super unless precision
601
466
 
602
- case precision
603
- when 0..6; "datetime(#{precision})"
604
- else raise(ActiveRecordError, "No datetime type has precision of #{precision}. The allowed range of precision is from 0 to 6.")
605
- end
606
- else
607
- super
608
- end
467
+ sql = "#{sql} unsigned" if unsigned && type != :primary_key
468
+ sql
609
469
  end
610
470
 
611
471
  # SHOW VARIABLES LIKE 'name'
612
472
  def show_variable(name)
613
- variables = select_all("select @@#{name} as 'Value'", 'SCHEMA')
614
- variables.first['Value'] unless variables.empty?
473
+ query_value("SELECT @@#{name}", "SCHEMA")
615
474
  rescue ActiveRecord::StatementInvalid
616
475
  nil
617
476
  end
618
477
 
619
- # Returns a table's primary key and belonging sequence.
620
- def pk_and_sequence_for(table)
621
- execute_and_free("SHOW CREATE TABLE #{quote_table_name(table)}", 'SCHEMA') do |result|
622
- create_table = each_hash(result).first[:"Create Table"]
623
- if create_table.to_s =~ /PRIMARY KEY\s+(?:USING\s+\w+\s+)?\((.+)\)/
624
- keys = $1.split(",").map { |key| key.delete('`"') }
625
- keys.length == 1 ? [keys.first, nil] : nil
626
- else
627
- nil
628
- end
629
- end
630
- end
478
+ def primary_keys(table_name) # :nodoc:
479
+ raise ArgumentError unless table_name.present?
631
480
 
632
- # Returns just a table's primary key
633
- def primary_key(table)
634
- pk_and_sequence = pk_and_sequence_for(table)
635
- pk_and_sequence && pk_and_sequence.first
636
- end
481
+ scope = quoted_scope(table_name)
637
482
 
638
- def case_sensitive_modifier(node, table_attribute)
639
- node = Arel::Nodes.build_quoted node, table_attribute
640
- 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
641
491
  end
642
492
 
643
- def case_sensitive_comparison(table, attribute, column, value)
644
- if column.case_sensitive?
645
- 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))
646
496
  else
647
497
  super
648
498
  end
649
499
  end
650
500
 
651
- def case_insensitive_comparison(table, attribute, column, value)
652
- if column.case_sensitive?
653
- super
654
- else
655
- table[attribute].eq(value)
656
- end
501
+ def can_perform_case_insensitive_comparison_for?(column)
502
+ column.case_sensitive?
657
503
  end
504
+ private :can_perform_case_insensitive_comparison_for?
658
505
 
659
506
  # In MySQL 5.7.5 and up, ONLY_FULL_GROUP_BY affects handling of queries that use
660
507
  # DISTINCT and ORDER BY. It requires the ORDER BY columns in the select list for
@@ -665,274 +512,376 @@ module ActiveRecord
665
512
  # Convert Arel node to string
666
513
  s = s.to_sql unless s.is_a?(String)
667
514
  # Remove any ASC/DESC modifiers
668
- s.gsub(/\s+(?:ASC|DESC)\b/i, '')
515
+ s.gsub(/\s+(?:ASC|DESC)\b/i, "")
669
516
  }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
670
517
 
671
- [super, *order_columns].join(', ')
518
+ (order_columns << super).join(", ")
672
519
  end
673
520
 
674
521
  def strict_mode?
675
522
  self.class.type_cast_config_to_boolean(@config.fetch(:strict, true))
676
523
  end
677
524
 
678
- def valid_type?(type)
679
- !native_database_types[type].nil?
525
+ def default_index_type?(index) # :nodoc:
526
+ index.using == :btree || super
680
527
  end
681
528
 
682
- 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
683
534
 
684
- def initialize_type_map(m) # :nodoc:
685
- 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
686
547
 
687
- 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
688
557
 
689
- m.register_type %r(tinytext)i, Type::Text.new(limit: 2**8 - 1)
690
- m.register_type %r(tinyblob)i, Type::Binary.new(limit: 2**8 - 1)
691
- m.register_type %r(text)i, Type::Text.new(limit: 2**16 - 1)
692
- m.register_type %r(blob)i, Type::Binary.new(limit: 2**16 - 1)
693
- m.register_type %r(mediumtext)i, Type::Text.new(limit: 2**24 - 1)
694
- m.register_type %r(mediumblob)i, Type::Binary.new(limit: 2**24 - 1)
695
- m.register_type %r(longtext)i, Type::Text.new(limit: 2**32 - 1)
696
- m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1)
697
- m.register_type %r(^float)i, Type::Float.new(limit: 24)
698
- 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
699
562
 
700
- register_integer_type m, %r(^bigint)i, limit: 8
701
- register_integer_type m, %r(^int)i, limit: 4
702
- register_integer_type m, %r(^mediumint)i, limit: 3
703
- register_integer_type m, %r(^smallint)i, limit: 2
704
- register_integer_type m, %r(^tinyint)i, limit: 1
563
+ def initialize_type_map(m = type_map)
564
+ super
705
565
 
706
- m.alias_type %r(tinyint\(1\))i, 'boolean' if emulate_booleans
707
- m.alias_type %r(set)i, 'varchar'
708
- m.alias_type %r(year)i, 'integer'
709
- 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
710
594
 
711
- m.register_type(%r(datetime)i) do |sql_type|
712
- precision = extract_precision(sql_type)
713
- 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
714
600
  end
715
601
 
716
- m.register_type(%r(enum)i) do |sql_type|
717
- limit = sql_type[/^enum\((.+)\)/i, 1]
718
- .split(',').map{|enum| enum.strip.length - 2}.max
719
- 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
720
610
  end
721
- end
722
611
 
723
- def register_integer_type(mapping, key, options) # :nodoc:
724
- mapping.register_type(key) do |sql_type|
725
- if /unsigned/i =~ sql_type
726
- Type::UnsignedInteger.new(options)
612
+ def extract_precision(sql_type)
613
+ if /\A(?:date)?time(?:stamp)?\b/.match?(sql_type)
614
+ super || 0
727
615
  else
728
- Type::Integer.new(options)
616
+ super
729
617
  end
730
618
  end
731
- end
732
-
733
- # MySQL is too stupid to create a temporary table for use subquery, so we have
734
- # to give it some prompting in the form of a subsubquery. Ugh!
735
- def subquery_for(key, select)
736
- subsubselect = select.clone
737
- subsubselect.projections = [key]
738
-
739
- # Materialize subquery by adding distinct
740
- # to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
741
- subsubselect.distinct unless select.limit || select.offset || select.orders.any?
742
619
 
743
- subselect = Arel::SelectManager.new(select.engine)
744
- subselect.project Arel.sql(key.name)
745
- subselect.from subsubselect.as('__active_record_temp')
746
- end
747
-
748
- def add_index_length(option_strings, column_names, options = {})
749
- if options.is_a?(Hash) && length = options[:length]
750
- case length
751
- when Hash
752
- column_names.each {|name| option_strings[name] += "(#{length[name]})" if length.has_key?(name) && length[name].present?}
753
- when Integer
754
- 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
755
664
  end
756
665
  end
757
666
 
758
- return option_strings
759
- end
760
-
761
- def quoted_columns_for_index(column_names, options = {})
762
- 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
763
670
 
764
- # add index length
765
- option_strings = add_index_length(option_strings, column_names, options)
671
+ unless options.key?(:default)
672
+ options[:default] = column.default
673
+ end
766
674
 
767
- # add index sort order
768
- option_strings = add_index_sort_order(option_strings, column_names, options)
675
+ unless options.key?(:null)
676
+ options[:null] = column.null
677
+ end
769
678
 
770
- column_names.map {|name| quote_column_name(name) + option_strings[name]}
771
- end
679
+ unless options.key?(:comment)
680
+ options[:comment] = column.comment
681
+ end
772
682
 
773
- def translate_exception(exception, message)
774
- case error_number(exception)
775
- when 1062
776
- RecordNotUnique.new(message, exception)
777
- when 1452
778
- InvalidForeignKey.new(message, exception)
779
- else
780
- 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))
781
686
  end
782
- end
783
-
784
- def add_column_sql(table_name, column_name, type, options = {})
785
- td = create_table_definition table_name, options[:temporary], options[:options]
786
- cd = td.new_column_definition(column_name, type, options)
787
- schema_creation.visit_AddColumn cd
788
- end
789
687
 
790
- def change_column_sql(table_name, column_name, type, options = {})
791
- column = column_for(table_name, column_name)
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
+ }
792
695
 
793
- unless options_include_default?(options)
794
- options[:default] = column.default
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))
795
700
  end
796
701
 
797
- unless options.has_key?(:null)
798
- options[:null] = column.null
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}"
799
706
  end
800
707
 
801
- options[:name] = column.name
802
- schema_creation.accept ChangeColumnDefinition.new column, type, options
803
- end
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
804
712
 
805
- def rename_column_sql(table_name, column_name, new_column_name)
806
- column = column_for(table_name, column_name)
807
- options = {
808
- name: new_column_name,
809
- default: column.default,
810
- null: column.null,
811
- auto_increment: column.extra == "auto_increment"
812
- }
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
813
716
 
814
- current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", 'SCHEMA')["Type"]
815
- schema_creation.accept ChangeColumnDefinition.new column, current_type, options
816
- end
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
817
720
 
818
- def remove_column_sql(table_name, column_name, type = nil, options = {})
819
- "DROP #{quote_column_name(column_name)}"
820
- 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]
821
726
 
822
- def remove_columns_sql(table_name, *column_names)
823
- column_names.map {|column_name| remove_column_sql(table_name, column_name) }
824
- 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?
825
730
 
826
- def add_index_sql(table_name, column_name, options = {})
827
- index_name, index_type, index_columns = add_index_options(table_name, column_name, options)
828
- "ADD #{index_type} INDEX #{index_name} (#{index_columns})"
829
- end
830
-
831
- def remove_index_sql(table_name, options = {})
832
- index_name = index_name_for_remove(table_name, options)
833
- "DROP INDEX #{index_name}"
834
- 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
835
734
 
836
- def add_timestamps_sql(table_name, options = {})
837
- [add_column_sql(table_name, :created_at, :datetime, options), add_column_sql(table_name, :updated_at, :datetime, options)]
838
- end
735
+ def supports_rename_index?
736
+ mariadb? ? false : version >= "5.7.6"
737
+ end
839
738
 
840
- def remove_timestamps_sql(table_name, options = {})
841
- [remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)]
842
- end
739
+ def configure_connection
740
+ variables = @config.fetch(:variables, {}).stringify_keys
843
741
 
844
- private
742
+ # By default, MySQL 'where id is null' selects the last inserted id; Turn this off.
743
+ variables["sql_auto_is_null"] = 0
845
744
 
846
- def version
847
- @version ||= Version.new(full_version.match(/^\d+\.\d+\.\d+/)[0])
848
- 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
849
749
 
850
- def mariadb?
851
- full_version =~ /mariadb/i
852
- end
750
+ defaults = [":default", :default].to_set
853
751
 
854
- def supports_rename_index?
855
- mariadb? ? false : version >= '5.7.6'
856
- 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
857
777
 
858
- def configure_connection
859
- 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(", ")
860
787
 
861
- # By default, MySQL 'where id is null' selects the last inserted id.
862
- # Turn this off. http://dev.rubyonrails.org/ticket/6778
863
- 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
864
791
 
865
- # Increase timeout so the server doesn't disconnect us.
866
- wait_timeout = self.class.type_cast_config_to_integer(@config[:wait_timeout])
867
- wait_timeout = 2147483 unless wait_timeout.is_a?(Integer)
868
- 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
869
797
 
870
- # Make MySQL reject illegal values rather than truncating or blanking them, see
871
- # http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html#sqlmode_strict_all_tables
872
- # If the user has provided another value for sql_mode, don't replace it.
873
- unless variables.has_key?('sql_mode')
874
- 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"]
875
800
  end
876
801
 
877
- # NAMES does not have an equals sign, see
878
- # http://dev.mysql.com/doc/refman/5.0/en/set-statement.html#id944430
879
- # (trailing comma because variable_assignments will always have content)
880
- if @config[:encoding]
881
- encoding = "NAMES #{@config[:encoding]}"
882
- encoding << " COLLATE #{@config[:collation]}" if @config[:collation]
883
- encoding << ", "
802
+ def arel_visitor
803
+ Arel::Visitors::MySQL.new(self)
884
804
  end
885
805
 
886
- # Gather up all of the SET variables...
887
- variable_assignments = variables.map do |k, v|
888
- if v == ':default' || v == :default
889
- "@@SESSION.#{k} = DEFAULT" # Sets the value to the global or compile default
890
- elsif !v.nil?
891
- "@@SESSION.#{k} = #{quote(v)}"
892
- end
893
- # or else nil; compact to clear nils out
894
- 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)
895
812
 
896
- # ...and send them all in one query
897
- @connection.query "SET #{encoding} #{variable_assignments}"
898
- end
813
+ options = {
814
+ message: message,
815
+ }
899
816
 
900
- def extract_foreign_key_action(structure, name, action) # :nodoc:
901
- if structure =~ /CONSTRAINT #{quote_column_name(name)} FOREIGN KEY .* REFERENCES .* ON #{action} (CASCADE|SET NULL|RESTRICT)/
902
- case $1
903
- when 'CASCADE'; :cascade
904
- 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])
905
823
  end
824
+
825
+ MismatchedForeignKey.new(options)
906
826
  end
907
- end
908
827
 
909
- class MysqlDateTime < Type::DateTime # :nodoc:
910
- 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
911
838
 
912
- def has_precision?
913
- 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
914
847
  end
915
- end
916
848
 
917
- class MysqlString < Type::String # :nodoc:
918
- def type_cast_for_database(value)
919
- case value
920
- when true then "1"
921
- when false then "0"
922
- 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}")
923
856
  end
924
857
  end
925
858
 
926
- private
859
+ def version_string
860
+ full_version.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)[1]
861
+ end
927
862
 
928
- def cast_value(value)
929
- case value
930
- when true then "1"
931
- when false then "0"
932
- 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
933
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
934
881
  end
935
- end
882
+
883
+ ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql2)
884
+ ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :mysql2)
936
885
  end
937
886
  end
938
887
  end