activerecord 5.2.3

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 (244) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +937 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +217 -0
  5. data/examples/performance.rb +185 -0
  6. data/examples/simple.rb +15 -0
  7. data/lib/active_record.rb +188 -0
  8. data/lib/active_record/aggregations.rb +283 -0
  9. data/lib/active_record/association_relation.rb +40 -0
  10. data/lib/active_record/associations.rb +1860 -0
  11. data/lib/active_record/associations/alias_tracker.rb +81 -0
  12. data/lib/active_record/associations/association.rb +299 -0
  13. data/lib/active_record/associations/association_scope.rb +168 -0
  14. data/lib/active_record/associations/belongs_to_association.rb +130 -0
  15. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +40 -0
  16. data/lib/active_record/associations/builder/association.rb +140 -0
  17. data/lib/active_record/associations/builder/belongs_to.rb +163 -0
  18. data/lib/active_record/associations/builder/collection_association.rb +82 -0
  19. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +135 -0
  20. data/lib/active_record/associations/builder/has_many.rb +17 -0
  21. data/lib/active_record/associations/builder/has_one.rb +30 -0
  22. data/lib/active_record/associations/builder/singular_association.rb +42 -0
  23. data/lib/active_record/associations/collection_association.rb +513 -0
  24. data/lib/active_record/associations/collection_proxy.rb +1131 -0
  25. data/lib/active_record/associations/foreign_association.rb +13 -0
  26. data/lib/active_record/associations/has_many_association.rb +144 -0
  27. data/lib/active_record/associations/has_many_through_association.rb +227 -0
  28. data/lib/active_record/associations/has_one_association.rb +120 -0
  29. data/lib/active_record/associations/has_one_through_association.rb +45 -0
  30. data/lib/active_record/associations/join_dependency.rb +262 -0
  31. data/lib/active_record/associations/join_dependency/join_association.rb +60 -0
  32. data/lib/active_record/associations/join_dependency/join_base.rb +23 -0
  33. data/lib/active_record/associations/join_dependency/join_part.rb +71 -0
  34. data/lib/active_record/associations/preloader.rb +193 -0
  35. data/lib/active_record/associations/preloader/association.rb +131 -0
  36. data/lib/active_record/associations/preloader/through_association.rb +107 -0
  37. data/lib/active_record/associations/singular_association.rb +73 -0
  38. data/lib/active_record/associations/through_association.rb +121 -0
  39. data/lib/active_record/attribute_assignment.rb +88 -0
  40. data/lib/active_record/attribute_decorators.rb +90 -0
  41. data/lib/active_record/attribute_methods.rb +492 -0
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +78 -0
  43. data/lib/active_record/attribute_methods/dirty.rb +150 -0
  44. data/lib/active_record/attribute_methods/primary_key.rb +143 -0
  45. data/lib/active_record/attribute_methods/query.rb +42 -0
  46. data/lib/active_record/attribute_methods/read.rb +85 -0
  47. data/lib/active_record/attribute_methods/serialization.rb +90 -0
  48. data/lib/active_record/attribute_methods/time_zone_conversion.rb +91 -0
  49. data/lib/active_record/attribute_methods/write.rb +68 -0
  50. data/lib/active_record/attributes.rb +266 -0
  51. data/lib/active_record/autosave_association.rb +498 -0
  52. data/lib/active_record/base.rb +329 -0
  53. data/lib/active_record/callbacks.rb +353 -0
  54. data/lib/active_record/coders/json.rb +15 -0
  55. data/lib/active_record/coders/yaml_column.rb +50 -0
  56. data/lib/active_record/collection_cache_key.rb +53 -0
  57. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +1068 -0
  58. data/lib/active_record/connection_adapters/abstract/database_limits.rb +72 -0
  59. data/lib/active_record/connection_adapters/abstract/database_statements.rb +540 -0
  60. data/lib/active_record/connection_adapters/abstract/query_cache.rb +145 -0
  61. data/lib/active_record/connection_adapters/abstract/quoting.rb +200 -0
  62. data/lib/active_record/connection_adapters/abstract/savepoints.rb +23 -0
  63. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +146 -0
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +685 -0
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +95 -0
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +1396 -0
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +283 -0
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +628 -0
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +887 -0
  70. data/lib/active_record/connection_adapters/column.rb +91 -0
  71. data/lib/active_record/connection_adapters/connection_specification.rb +287 -0
  72. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +33 -0
  73. data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
  74. data/lib/active_record/connection_adapters/mysql/database_statements.rb +140 -0
  75. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +72 -0
  76. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -0
  77. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +73 -0
  78. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +87 -0
  79. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +80 -0
  80. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +148 -0
  81. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +35 -0
  82. data/lib/active_record/connection_adapters/mysql2_adapter.rb +129 -0
  83. data/lib/active_record/connection_adapters/postgresql/column.rb +44 -0
  84. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +163 -0
  85. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid.rb +34 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +92 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +56 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +15 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +17 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +50 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +23 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +23 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +15 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +21 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +71 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +15 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +15 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +45 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +41 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +15 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +65 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +97 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +18 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +111 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +23 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +28 -0
  108. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +30 -0
  109. data/lib/active_record/connection_adapters/postgresql/quoting.rb +168 -0
  110. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +43 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +65 -0
  112. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +206 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
  114. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +774 -0
  115. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +39 -0
  116. data/lib/active_record/connection_adapters/postgresql/utils.rb +81 -0
  117. data/lib/active_record/connection_adapters/postgresql_adapter.rb +863 -0
  118. data/lib/active_record/connection_adapters/schema_cache.rb +118 -0
  119. data/lib/active_record/connection_adapters/sql_type_metadata.rb +34 -0
  120. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
  121. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +67 -0
  122. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +17 -0
  123. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
  124. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
  125. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +106 -0
  126. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +573 -0
  127. data/lib/active_record/connection_adapters/statement_pool.rb +61 -0
  128. data/lib/active_record/connection_handling.rb +145 -0
  129. data/lib/active_record/core.rb +559 -0
  130. data/lib/active_record/counter_cache.rb +218 -0
  131. data/lib/active_record/define_callbacks.rb +22 -0
  132. data/lib/active_record/dynamic_matchers.rb +122 -0
  133. data/lib/active_record/enum.rb +244 -0
  134. data/lib/active_record/errors.rb +380 -0
  135. data/lib/active_record/explain.rb +50 -0
  136. data/lib/active_record/explain_registry.rb +32 -0
  137. data/lib/active_record/explain_subscriber.rb +34 -0
  138. data/lib/active_record/fixture_set/file.rb +82 -0
  139. data/lib/active_record/fixtures.rb +1065 -0
  140. data/lib/active_record/gem_version.rb +17 -0
  141. data/lib/active_record/inheritance.rb +283 -0
  142. data/lib/active_record/integration.rb +155 -0
  143. data/lib/active_record/internal_metadata.rb +45 -0
  144. data/lib/active_record/legacy_yaml_adapter.rb +48 -0
  145. data/lib/active_record/locale/en.yml +48 -0
  146. data/lib/active_record/locking/optimistic.rb +198 -0
  147. data/lib/active_record/locking/pessimistic.rb +89 -0
  148. data/lib/active_record/log_subscriber.rb +137 -0
  149. data/lib/active_record/migration.rb +1378 -0
  150. data/lib/active_record/migration/command_recorder.rb +240 -0
  151. data/lib/active_record/migration/compatibility.rb +217 -0
  152. data/lib/active_record/migration/join_table.rb +17 -0
  153. data/lib/active_record/model_schema.rb +521 -0
  154. data/lib/active_record/nested_attributes.rb +600 -0
  155. data/lib/active_record/no_touching.rb +58 -0
  156. data/lib/active_record/null_relation.rb +68 -0
  157. data/lib/active_record/persistence.rb +763 -0
  158. data/lib/active_record/query_cache.rb +45 -0
  159. data/lib/active_record/querying.rb +70 -0
  160. data/lib/active_record/railtie.rb +226 -0
  161. data/lib/active_record/railties/console_sandbox.rb +7 -0
  162. data/lib/active_record/railties/controller_runtime.rb +56 -0
  163. data/lib/active_record/railties/databases.rake +377 -0
  164. data/lib/active_record/readonly_attributes.rb +24 -0
  165. data/lib/active_record/reflection.rb +1044 -0
  166. data/lib/active_record/relation.rb +629 -0
  167. data/lib/active_record/relation/batches.rb +287 -0
  168. data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
  169. data/lib/active_record/relation/calculations.rb +417 -0
  170. data/lib/active_record/relation/delegation.rb +147 -0
  171. data/lib/active_record/relation/finder_methods.rb +565 -0
  172. data/lib/active_record/relation/from_clause.rb +26 -0
  173. data/lib/active_record/relation/merger.rb +193 -0
  174. data/lib/active_record/relation/predicate_builder.rb +152 -0
  175. data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
  176. data/lib/active_record/relation/predicate_builder/association_query_value.rb +46 -0
  177. data/lib/active_record/relation/predicate_builder/base_handler.rb +19 -0
  178. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +20 -0
  179. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +56 -0
  180. data/lib/active_record/relation/predicate_builder/range_handler.rb +42 -0
  181. data/lib/active_record/relation/predicate_builder/relation_handler.rb +19 -0
  182. data/lib/active_record/relation/query_attribute.rb +45 -0
  183. data/lib/active_record/relation/query_methods.rb +1231 -0
  184. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  185. data/lib/active_record/relation/spawn_methods.rb +77 -0
  186. data/lib/active_record/relation/where_clause.rb +186 -0
  187. data/lib/active_record/relation/where_clause_factory.rb +34 -0
  188. data/lib/active_record/result.rb +149 -0
  189. data/lib/active_record/runtime_registry.rb +24 -0
  190. data/lib/active_record/sanitization.rb +222 -0
  191. data/lib/active_record/schema.rb +70 -0
  192. data/lib/active_record/schema_dumper.rb +255 -0
  193. data/lib/active_record/schema_migration.rb +56 -0
  194. data/lib/active_record/scoping.rb +106 -0
  195. data/lib/active_record/scoping/default.rb +152 -0
  196. data/lib/active_record/scoping/named.rb +213 -0
  197. data/lib/active_record/secure_token.rb +40 -0
  198. data/lib/active_record/serialization.rb +22 -0
  199. data/lib/active_record/statement_cache.rb +121 -0
  200. data/lib/active_record/store.rb +211 -0
  201. data/lib/active_record/suppressor.rb +61 -0
  202. data/lib/active_record/table_metadata.rb +82 -0
  203. data/lib/active_record/tasks/database_tasks.rb +337 -0
  204. data/lib/active_record/tasks/mysql_database_tasks.rb +115 -0
  205. data/lib/active_record/tasks/postgresql_database_tasks.rb +143 -0
  206. data/lib/active_record/tasks/sqlite_database_tasks.rb +83 -0
  207. data/lib/active_record/timestamp.rb +153 -0
  208. data/lib/active_record/touch_later.rb +64 -0
  209. data/lib/active_record/transactions.rb +502 -0
  210. data/lib/active_record/translation.rb +24 -0
  211. data/lib/active_record/type.rb +79 -0
  212. data/lib/active_record/type/adapter_specific_registry.rb +136 -0
  213. data/lib/active_record/type/date.rb +9 -0
  214. data/lib/active_record/type/date_time.rb +9 -0
  215. data/lib/active_record/type/decimal_without_scale.rb +15 -0
  216. data/lib/active_record/type/hash_lookup_type_map.rb +25 -0
  217. data/lib/active_record/type/internal/timezone.rb +17 -0
  218. data/lib/active_record/type/json.rb +30 -0
  219. data/lib/active_record/type/serialized.rb +71 -0
  220. data/lib/active_record/type/text.rb +11 -0
  221. data/lib/active_record/type/time.rb +21 -0
  222. data/lib/active_record/type/type_map.rb +62 -0
  223. data/lib/active_record/type/unsigned_integer.rb +17 -0
  224. data/lib/active_record/type_caster.rb +9 -0
  225. data/lib/active_record/type_caster/connection.rb +33 -0
  226. data/lib/active_record/type_caster/map.rb +23 -0
  227. data/lib/active_record/validations.rb +93 -0
  228. data/lib/active_record/validations/absence.rb +25 -0
  229. data/lib/active_record/validations/associated.rb +60 -0
  230. data/lib/active_record/validations/length.rb +26 -0
  231. data/lib/active_record/validations/presence.rb +68 -0
  232. data/lib/active_record/validations/uniqueness.rb +238 -0
  233. data/lib/active_record/version.rb +10 -0
  234. data/lib/rails/generators/active_record.rb +19 -0
  235. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
  236. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
  237. data/lib/rails/generators/active_record/migration.rb +35 -0
  238. data/lib/rails/generators/active_record/migration/migration_generator.rb +78 -0
  239. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +24 -0
  240. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +46 -0
  241. data/lib/rails/generators/active_record/model/model_generator.rb +48 -0
  242. data/lib/rails/generators/active_record/model/templates/model.rb.tt +13 -0
  243. data/lib/rails/generators/active_record/model/templates/module.rb.tt +7 -0
  244. metadata +333 -0
@@ -0,0 +1,887 @@
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"
15
+
16
+ module ActiveRecord
17
+ module ConnectionAdapters
18
+ class AbstractMysqlAdapter < AbstractAdapter
19
+ include MySQL::Quoting
20
+ include MySQL::SchemaStatements
21
+
22
+ ##
23
+ # :singleton-method:
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
26
+ # to your application.rb file:
27
+ #
28
+ # ActiveRecord::ConnectionAdapters::Mysql2Adapter.emulate_booleans = false
29
+ class_attribute :emulate_booleans, default: true
30
+
31
+ NATIVE_DATABASE_TYPES = {
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" },
45
+ }
46
+
47
+ class StatementPool < ConnectionAdapters::StatementPool # :nodoc:
48
+ private def dealloc(stmt)
49
+ stmt[:stmt].close
50
+ end
51
+ end
52
+
53
+ def initialize(connection, logger, connection_options, config)
54
+ super(connection, logger, config)
55
+
56
+ @statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit]))
57
+
58
+ if version < "5.1.10"
59
+ raise "Your version of MySQL (#{version_string}) is too old. Active Record supports MySQL >= 5.1.10."
60
+ end
61
+ end
62
+
63
+ def version #:nodoc:
64
+ @version ||= Version.new(version_string)
65
+ end
66
+
67
+ def mariadb? # :nodoc:
68
+ /mariadb/i.match?(full_version)
69
+ end
70
+
71
+ def supports_bulk_alter? #:nodoc:
72
+ true
73
+ end
74
+
75
+ def supports_index_sort_order?
76
+ !mariadb? && version >= "8.0.1"
77
+ end
78
+
79
+ def supports_transaction_isolation?
80
+ true
81
+ end
82
+
83
+ def supports_explain?
84
+ true
85
+ end
86
+
87
+ def supports_indexes_in_create?
88
+ true
89
+ end
90
+
91
+ def supports_foreign_keys?
92
+ true
93
+ end
94
+
95
+ def supports_views?
96
+ true
97
+ end
98
+
99
+ def supports_datetime_with_precision?
100
+ if mariadb?
101
+ version >= "5.3.0"
102
+ else
103
+ version >= "5.6.4"
104
+ end
105
+ end
106
+
107
+ def supports_virtual_columns?
108
+ if mariadb?
109
+ version >= "5.2.0"
110
+ else
111
+ version >= "5.7.5"
112
+ end
113
+ end
114
+
115
+ def supports_advisory_locks?
116
+ true
117
+ end
118
+
119
+ def get_advisory_lock(lock_name, timeout = 0) # :nodoc:
120
+ query_value("SELECT GET_LOCK(#{quote(lock_name.to_s)}, #{timeout})") == 1
121
+ end
122
+
123
+ def release_advisory_lock(lock_name) # :nodoc:
124
+ query_value("SELECT RELEASE_LOCK(#{quote(lock_name.to_s)})") == 1
125
+ end
126
+
127
+ def native_database_types
128
+ NATIVE_DATABASE_TYPES
129
+ end
130
+
131
+ def index_algorithms
132
+ { default: "ALGORITHM = DEFAULT".dup, copy: "ALGORITHM = COPY".dup, inplace: "ALGORITHM = INPLACE".dup }
133
+ end
134
+
135
+ # HELPER METHODS ===========================================
136
+
137
+ # The two drivers have slightly different ways of yielding hashes of results, so
138
+ # this method must be implemented to provide a uniform interface.
139
+ def each_hash(result) # :nodoc:
140
+ raise NotImplementedError
141
+ end
142
+
143
+ # Must return the MySQL error number from the exception, if the exception has an
144
+ # error number.
145
+ def error_number(exception) # :nodoc:
146
+ raise NotImplementedError
147
+ end
148
+
149
+ # REFERENTIAL INTEGRITY ====================================
150
+
151
+ def disable_referential_integrity #:nodoc:
152
+ old = query_value("SELECT @@FOREIGN_KEY_CHECKS")
153
+
154
+ begin
155
+ update("SET FOREIGN_KEY_CHECKS = 0")
156
+ yield
157
+ ensure
158
+ update("SET FOREIGN_KEY_CHECKS = #{old}")
159
+ end
160
+ end
161
+
162
+ # CONNECTION MANAGEMENT ====================================
163
+
164
+ # Clears the prepared statements cache.
165
+ def clear_cache!
166
+ reload_type_map
167
+ @statements.clear
168
+ end
169
+
170
+ #--
171
+ # DATABASE STATEMENTS ======================================
172
+ #++
173
+
174
+ def explain(arel, binds = [])
175
+ sql = "EXPLAIN #{to_sql(arel, binds)}"
176
+ start = Time.now
177
+ result = exec_query(sql, "EXPLAIN", binds)
178
+ elapsed = Time.now - start
179
+
180
+ MySQL::ExplainPrettyPrinter.new.pp(result, elapsed)
181
+ end
182
+
183
+ # Executes the SQL statement in the context of this connection.
184
+ def execute(sql, name = nil)
185
+ log(sql, name) do
186
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
187
+ @connection.query(sql)
188
+ end
189
+ end
190
+ end
191
+
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:
196
+ yield execute(sql, name)
197
+ end
198
+
199
+ def begin_db_transaction
200
+ execute "BEGIN"
201
+ end
202
+
203
+ def begin_isolated_db_transaction(isolation)
204
+ execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}"
205
+ begin_db_transaction
206
+ end
207
+
208
+ def commit_db_transaction #:nodoc:
209
+ execute "COMMIT"
210
+ end
211
+
212
+ def exec_rollback_db_transaction #:nodoc:
213
+ execute "ROLLBACK"
214
+ end
215
+
216
+ # In the simple case, MySQL allows us to place JOINs directly into the UPDATE
217
+ # query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
218
+ # these, we must use a subquery.
219
+ def join_to_update(update, select, key) # :nodoc:
220
+ if select.limit || select.offset || select.orders.any?
221
+ super
222
+ else
223
+ update.table select.source
224
+ update.wheres = select.constraints
225
+ end
226
+ end
227
+
228
+ def empty_insert_statement_value
229
+ "VALUES ()"
230
+ end
231
+
232
+ # SCHEMA STATEMENTS ========================================
233
+
234
+ # Drops the database specified on the +name+ attribute
235
+ # and creates it again using the provided +options+.
236
+ def recreate_database(name, options = {})
237
+ drop_database(name)
238
+ sql = create_database(name, options)
239
+ reconnect!
240
+ sql
241
+ end
242
+
243
+ # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
244
+ # Charset defaults to utf8.
245
+ #
246
+ # Example:
247
+ # create_database 'charset_test', charset: 'latin1', collation: 'latin1_bin'
248
+ # create_database 'matt_development'
249
+ # create_database 'matt_development', charset: :big5
250
+ def create_database(name, options = {})
251
+ if options[:collation]
252
+ execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT COLLATE #{quote_table_name(options[:collation])}"
253
+ else
254
+ execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset] || 'utf8')}"
255
+ end
256
+ end
257
+
258
+ # Drops a MySQL database.
259
+ #
260
+ # Example:
261
+ # drop_database('sebastian_development')
262
+ def drop_database(name) #:nodoc:
263
+ execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
264
+ end
265
+
266
+ def current_database
267
+ query_value("SELECT database()", "SCHEMA")
268
+ end
269
+
270
+ # Returns the database character set.
271
+ def charset
272
+ show_variable "character_set_database"
273
+ end
274
+
275
+ # Returns the database collation strategy.
276
+ def collation
277
+ show_variable "collation_database"
278
+ end
279
+
280
+ def truncate(table_name, name = nil)
281
+ execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
282
+ end
283
+
284
+ def table_comment(table_name) # :nodoc:
285
+ scope = quoted_scope(table_name)
286
+
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
293
+ end
294
+
295
+ def bulk_change_table(table_name, operations) #:nodoc:
296
+ sqls = operations.flat_map do |command, args|
297
+ table, arguments = args.shift, args
298
+ method = :"#{command}_for_alter"
299
+
300
+ if respond_to?(method, true)
301
+ send(method, table, *arguments)
302
+ else
303
+ raise "Unknown method called : #{method}(#{arguments.inspect})"
304
+ end
305
+ end.join(", ")
306
+
307
+ execute("ALTER TABLE #{quote_table_name(table_name)} #{sqls}")
308
+ end
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
+
315
+ # Renames a table.
316
+ #
317
+ # Example:
318
+ # rename_table('octopuses', 'octopi')
319
+ def rename_table(table_name, new_name)
320
+ execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
321
+ rename_table_indexes(table_name, new_name)
322
+ end
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.
339
+ def drop_table(table_name, options = {})
340
+ execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
341
+ end
342
+
343
+ def rename_index(table_name, old_name, new_name)
344
+ if supports_rename_index?
345
+ validate_index_length!(table_name, new_name)
346
+
347
+ execute "ALTER TABLE #{quote_table_name(table_name)} RENAME INDEX #{quote_table_name(old_name)} TO #{quote_table_name(new_name)}"
348
+ else
349
+ super
350
+ end
351
+ end
352
+
353
+ def change_column_default(table_name, column_name, default_or_changes) #:nodoc:
354
+ default = extract_new_default_value(default_or_changes)
355
+ change_column table_name, column_name, nil, default: default
356
+ end
357
+
358
+ def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
359
+ unless null || default.nil?
360
+ execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
361
+ end
362
+
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
368
+ end
369
+
370
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
371
+ execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_for_alter(table_name, column_name, type, options)}")
372
+ end
373
+
374
+ def rename_column(table_name, column_name, new_column_name) #:nodoc:
375
+ execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_for_alter(table_name, column_name, new_column_name)}")
376
+ rename_column_indexes(table_name, column_name, new_column_name)
377
+ end
378
+
379
+ def add_index(table_name, column_name, options = {}) #:nodoc:
380
+ index_name, index_type, index_columns, _, index_algorithm, index_using, comment = add_index_options(table_name, column_name, options)
381
+ sql = "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns}) #{index_algorithm}".dup
382
+ execute add_sql_comment!(sql, comment)
383
+ end
384
+
385
+ def add_sql_comment!(sql, comment) # :nodoc:
386
+ sql << " COMMENT #{quote(comment)}" if comment.present?
387
+ sql
388
+ end
389
+
390
+ def foreign_keys(table_name)
391
+ raise ArgumentError unless table_name.present?
392
+
393
+ scope = quoted_scope(table_name)
394
+
395
+ fk_info = exec_query(<<-SQL.strip_heredoc, "SCHEMA")
396
+ SELECT fk.referenced_table_name AS 'to_table',
397
+ fk.referenced_column_name AS 'primary_key',
398
+ fk.column_name AS 'column',
399
+ fk.constraint_name AS 'name',
400
+ rc.update_rule AS 'on_update',
401
+ rc.delete_rule AS 'on_delete'
402
+ FROM information_schema.referential_constraints rc
403
+ JOIN information_schema.key_column_usage fk
404
+ USING (constraint_schema, constraint_name)
405
+ WHERE fk.referenced_column_name IS NOT NULL
406
+ AND fk.table_schema = #{scope[:schema]}
407
+ AND fk.table_name = #{scope[:name]}
408
+ AND rc.constraint_schema = #{scope[:schema]}
409
+ AND rc.table_name = #{scope[:name]}
410
+ SQL
411
+
412
+ fk_info.map do |row|
413
+ options = {
414
+ column: row["column"],
415
+ name: row["name"],
416
+ primary_key: row["primary_key"]
417
+ }
418
+
419
+ options[:on_update] = extract_foreign_key_action(row["on_update"])
420
+ options[:on_delete] = extract_foreign_key_action(row["on_delete"])
421
+
422
+ ForeignKeyDefinition.new(table_name, row["to_table"], options)
423
+ end
424
+ end
425
+
426
+ def table_options(table_name) # :nodoc:
427
+ table_options = {}
428
+
429
+ create_table_info = create_table_info(table_name)
430
+
431
+ # strip create_definitions and partition_options
432
+ raw_table_options = create_table_info.sub(/\A.*\n\) /m, "").sub(/\n\/\*!.*\*\/\n\z/m, "").strip
433
+
434
+ # strip AUTO_INCREMENT
435
+ raw_table_options.sub!(/(ENGINE=\w+)(?: AUTO_INCREMENT=\d+)/, '\1')
436
+
437
+ table_options[:options] = raw_table_options
438
+
439
+ # strip COMMENT
440
+ if raw_table_options.sub!(/ COMMENT='.+'/, "")
441
+ table_options[:comment] = table_comment(table_name)
442
+ end
443
+
444
+ table_options
445
+ end
446
+
447
+ # Maps logical Rails types to MySQL-specific data types.
448
+ def type_to_sql(type, limit: nil, precision: nil, scale: nil, unsigned: nil, **) # :nodoc:
449
+ sql = \
450
+ case type.to_s
451
+ when "integer"
452
+ integer_to_sql(limit)
453
+ when "text"
454
+ text_to_sql(limit)
455
+ when "blob"
456
+ binary_to_sql(limit)
457
+ when "binary"
458
+ if (0..0xfff) === limit
459
+ "varbinary(#{limit})"
460
+ else
461
+ binary_to_sql(limit)
462
+ end
463
+ else
464
+ super
465
+ end
466
+
467
+ sql = "#{sql} unsigned" if unsigned && type != :primary_key
468
+ sql
469
+ end
470
+
471
+ # SHOW VARIABLES LIKE 'name'
472
+ def show_variable(name)
473
+ query_value("SELECT @@#{name}", "SCHEMA")
474
+ rescue ActiveRecord::StatementInvalid
475
+ nil
476
+ end
477
+
478
+ def primary_keys(table_name) # :nodoc:
479
+ raise ArgumentError unless table_name.present?
480
+
481
+ scope = quoted_scope(table_name)
482
+
483
+ query_values(<<-SQL.strip_heredoc, "SCHEMA")
484
+ SELECT column_name
485
+ FROM information_schema.key_column_usage
486
+ WHERE constraint_name = 'PRIMARY'
487
+ AND table_schema = #{scope[:schema]}
488
+ AND table_name = #{scope[:name]}
489
+ ORDER BY ordinal_position
490
+ SQL
491
+ end
492
+
493
+ def case_sensitive_comparison(table, attribute, column, value) # :nodoc:
494
+ if column.collation && !column.case_sensitive?
495
+ table[attribute].eq(Arel::Nodes::Bin.new(value))
496
+ else
497
+ super
498
+ end
499
+ end
500
+
501
+ def can_perform_case_insensitive_comparison_for?(column)
502
+ column.case_sensitive?
503
+ end
504
+ private :can_perform_case_insensitive_comparison_for?
505
+
506
+ # In MySQL 5.7.5 and up, ONLY_FULL_GROUP_BY affects handling of queries that use
507
+ # DISTINCT and ORDER BY. It requires the ORDER BY columns in the select list for
508
+ # distinct queries, and requires that the ORDER BY include the distinct column.
509
+ # See https://dev.mysql.com/doc/refman/5.7/en/group-by-handling.html
510
+ def columns_for_distinct(columns, orders) # :nodoc:
511
+ order_columns = orders.reject(&:blank?).map { |s|
512
+ # Convert Arel node to string
513
+ s = s.to_sql unless s.is_a?(String)
514
+ # Remove any ASC/DESC modifiers
515
+ s.gsub(/\s+(?:ASC|DESC)\b/i, "")
516
+ }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
517
+
518
+ (order_columns << super).join(", ")
519
+ end
520
+
521
+ def strict_mode?
522
+ self.class.type_cast_config_to_boolean(@config.fetch(:strict, true))
523
+ end
524
+
525
+ def default_index_type?(index) # :nodoc:
526
+ index.using == :btree || super
527
+ end
528
+
529
+ def insert_fixtures_set(fixture_set, tables_to_delete = [])
530
+ with_multi_statements do
531
+ super { discard_remaining_results }
532
+ end
533
+ end
534
+
535
+ private
536
+ def combine_multi_statements(total_sql)
537
+ total_sql.each_with_object([]) do |sql, total_sql_chunks|
538
+ previous_packet = total_sql_chunks.last
539
+ sql << ";\n"
540
+ if max_allowed_packet_reached?(sql, previous_packet) || total_sql_chunks.empty?
541
+ total_sql_chunks << sql
542
+ else
543
+ previous_packet << sql
544
+ end
545
+ end
546
+ end
547
+
548
+ def max_allowed_packet_reached?(current_packet, previous_packet)
549
+ if current_packet.bytesize > max_allowed_packet
550
+ raise ActiveRecordError, "Fixtures set is too large #{current_packet.bytesize}. Consider increasing the max_allowed_packet variable."
551
+ elsif previous_packet.nil?
552
+ false
553
+ else
554
+ (current_packet.bytesize + previous_packet.bytesize) > max_allowed_packet
555
+ end
556
+ end
557
+
558
+ def max_allowed_packet
559
+ bytes_margin = 2
560
+ @max_allowed_packet ||= (show_variable("max_allowed_packet") - bytes_margin)
561
+ end
562
+
563
+ def initialize_type_map(m = type_map)
564
+ super
565
+
566
+ register_class_with_limit m, %r(char)i, MysqlString
567
+
568
+ m.register_type %r(tinytext)i, Type::Text.new(limit: 2**8 - 1)
569
+ m.register_type %r(tinyblob)i, Type::Binary.new(limit: 2**8 - 1)
570
+ m.register_type %r(text)i, Type::Text.new(limit: 2**16 - 1)
571
+ m.register_type %r(blob)i, Type::Binary.new(limit: 2**16 - 1)
572
+ m.register_type %r(mediumtext)i, Type::Text.new(limit: 2**24 - 1)
573
+ m.register_type %r(mediumblob)i, Type::Binary.new(limit: 2**24 - 1)
574
+ m.register_type %r(longtext)i, Type::Text.new(limit: 2**32 - 1)
575
+ m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1)
576
+ m.register_type %r(^float)i, Type::Float.new(limit: 24)
577
+ m.register_type %r(^double)i, Type::Float.new(limit: 53)
578
+
579
+ register_integer_type m, %r(^bigint)i, limit: 8
580
+ register_integer_type m, %r(^int)i, limit: 4
581
+ register_integer_type m, %r(^mediumint)i, limit: 3
582
+ register_integer_type m, %r(^smallint)i, limit: 2
583
+ register_integer_type m, %r(^tinyint)i, limit: 1
584
+
585
+ m.register_type %r(^tinyint\(1\))i, Type::Boolean.new if emulate_booleans
586
+ m.alias_type %r(year)i, "integer"
587
+ m.alias_type %r(bit)i, "binary"
588
+
589
+ m.register_type(%r(enum)i) do |sql_type|
590
+ limit = sql_type[/^enum\((.+)\)/i, 1]
591
+ .split(",").map { |enum| enum.strip.length - 2 }.max
592
+ MysqlString.new(limit: limit)
593
+ end
594
+
595
+ m.register_type(%r(^set)i) do |sql_type|
596
+ limit = sql_type[/^set\((.+)\)/i, 1]
597
+ .split(",").map { |set| set.strip.length - 1 }.sum - 1
598
+ MysqlString.new(limit: limit)
599
+ end
600
+ end
601
+
602
+ def register_integer_type(mapping, key, options)
603
+ mapping.register_type(key) do |sql_type|
604
+ if /\bunsigned\b/.match?(sql_type)
605
+ Type::UnsignedInteger.new(options)
606
+ else
607
+ Type::Integer.new(options)
608
+ end
609
+ end
610
+ end
611
+
612
+ def extract_precision(sql_type)
613
+ if /\A(?:date)?time(?:stamp)?\b/.match?(sql_type)
614
+ super || 0
615
+ else
616
+ super
617
+ end
618
+ end
619
+
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
664
+ end
665
+ end
666
+
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
670
+
671
+ unless options.key?(:default)
672
+ options[:default] = column.default
673
+ end
674
+
675
+ unless options.key?(:null)
676
+ options[:null] = column.null
677
+ end
678
+
679
+ unless options.key?(:comment)
680
+ options[:comment] = column.comment
681
+ end
682
+
683
+ td = create_table_definition(table_name)
684
+ cd = td.new_column_definition(column.name, type, options)
685
+ schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
686
+ end
687
+
688
+ def rename_column_for_alter(table_name, column_name, new_column_name)
689
+ column = column_for(table_name, column_name)
690
+ options = {
691
+ default: column.default,
692
+ null: column.null,
693
+ auto_increment: column.auto_increment?
694
+ }
695
+
696
+ current_type = exec_query("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE #{quote(column_name)}", "SCHEMA").first["Type"]
697
+ td = create_table_definition(table_name)
698
+ cd = td.new_column_definition(new_column_name, current_type, options)
699
+ schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
700
+ end
701
+
702
+ def add_index_for_alter(table_name, column_name, options = {})
703
+ index_name, index_type, index_columns, _, index_algorithm, index_using = add_index_options(table_name, column_name, options)
704
+ index_algorithm[0, 0] = ", " if index_algorithm.present?
705
+ "ADD #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_algorithm}"
706
+ end
707
+
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
712
+
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
716
+
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
720
+
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]
726
+
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?
730
+
731
+ key_name = quote_column_name(key.name)
732
+ Arel::SelectManager.new(subselect.as("__active_record_temp")).project(Arel.sql(key_name))
733
+ end
734
+
735
+ def supports_rename_index?
736
+ mariadb? ? false : version >= "5.7.6"
737
+ end
738
+
739
+ def configure_connection
740
+ variables = @config.fetch(:variables, {}).stringify_keys
741
+
742
+ # By default, MySQL 'where id is null' selects the last inserted id; Turn this off.
743
+ variables["sql_auto_is_null"] = 0
744
+
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
749
+
750
+ defaults = [":default", :default].to_set
751
+
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
777
+
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(", ")
787
+
788
+ # ...and send them all in one query
789
+ execute "SET #{encoding} #{sql_mode_assignment} #{variable_assignments}"
790
+ end
791
+
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
797
+
798
+ def create_table_info(table_name) # :nodoc:
799
+ exec_query("SHOW CREATE TABLE #{quote_table_name(table_name)}", "SCHEMA").first["Create Table"]
800
+ end
801
+
802
+ def arel_visitor
803
+ Arel::Visitors::MySQL.new(self)
804
+ end
805
+
806
+ def mismatched_foreign_key(message)
807
+ match = %r/
808
+ (?:CREATE|ALTER)\s+TABLE\s*(?:`?\w+`?\.)?`?(?<table>\w+)`?.+?
809
+ FOREIGN\s+KEY\s*\(`?(?<foreign_key>\w+)`?\)\s*
810
+ REFERENCES\s*(`?(?<target_table>\w+)`?)\s*\(`?(?<primary_key>\w+)`?\)
811
+ /xmi.match(message)
812
+
813
+ options = {
814
+ message: message,
815
+ }
816
+
817
+ if match
818
+ options[:table] = match[:table]
819
+ options[:foreign_key] = match[:foreign_key]
820
+ options[:target_table] = match[:target_table]
821
+ options[:primary_key] = match[:primary_key]
822
+ options[:primary_key_column] = column_for(match[:target_table], match[:primary_key])
823
+ end
824
+
825
+ MismatchedForeignKey.new(options)
826
+ end
827
+
828
+ def integer_to_sql(limit) # :nodoc:
829
+ case limit
830
+ when 1; "tinyint"
831
+ when 2; "smallint"
832
+ when 3; "mediumint"
833
+ when nil, 4; "int"
834
+ when 5..8; "bigint"
835
+ else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a decimal with scale 0 instead.")
836
+ end
837
+ end
838
+
839
+ def text_to_sql(limit) # :nodoc:
840
+ case limit
841
+ when 0..0xff; "tinytext"
842
+ when nil, 0x100..0xffff; "text"
843
+ when 0x10000..0xffffff; "mediumtext"
844
+ when 0x1000000..0xffffffff; "longtext"
845
+ else raise(ActiveRecordError, "No text type has byte length #{limit}")
846
+ end
847
+ end
848
+
849
+ def binary_to_sql(limit) # :nodoc:
850
+ case limit
851
+ when 0..0xff; "tinyblob"
852
+ when nil, 0x100..0xffff; "blob"
853
+ when 0x10000..0xffffff; "mediumblob"
854
+ when 0x1000000..0xffffffff; "longblob"
855
+ else raise(ActiveRecordError, "No binary type has byte length #{limit}")
856
+ end
857
+ end
858
+
859
+ def version_string
860
+ full_version.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)[1]
861
+ end
862
+
863
+ class MysqlString < Type::String # :nodoc:
864
+ def serialize(value)
865
+ case value
866
+ when true then "1"
867
+ when false then "0"
868
+ else super
869
+ end
870
+ end
871
+
872
+ private
873
+
874
+ def cast_value(value)
875
+ case value
876
+ when true then "1"
877
+ when false then "0"
878
+ else super
879
+ end
880
+ end
881
+ end
882
+
883
+ ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql2)
884
+ ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :mysql2)
885
+ end
886
+ end
887
+ end