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
@@ -0,0 +1,140 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module MySQL
6
+ module DatabaseStatements
7
+ # Returns an ActiveRecord::Result instance.
8
+ def select_all(*) # :nodoc:
9
+ result = if ExplainRegistry.collect? && prepared_statements
10
+ unprepared_statement { super }
11
+ else
12
+ super
13
+ end
14
+ discard_remaining_results
15
+ result
16
+ end
17
+
18
+ def query(sql, name = nil) # :nodoc:
19
+ execute(sql, name).to_a
20
+ end
21
+
22
+ # Executes the SQL statement in the context of this connection.
23
+ def execute(sql, name = nil)
24
+ # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
25
+ # made since we established the connection
26
+ @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone
27
+
28
+ super
29
+ end
30
+
31
+ def exec_query(sql, name = "SQL", binds = [], prepare: false)
32
+ if without_prepared_statement?(binds)
33
+ execute_and_free(sql, name) do |result|
34
+ ActiveRecord::Result.new(result.fields, result.to_a) if result
35
+ end
36
+ else
37
+ exec_stmt_and_free(sql, name, binds, cache_stmt: prepare) do |_, result|
38
+ ActiveRecord::Result.new(result.fields, result.to_a) if result
39
+ end
40
+ end
41
+ end
42
+
43
+ def exec_delete(sql, name = nil, binds = [])
44
+ if without_prepared_statement?(binds)
45
+ execute_and_free(sql, name) { @connection.affected_rows }
46
+ else
47
+ exec_stmt_and_free(sql, name, binds) { |stmt| stmt.affected_rows }
48
+ end
49
+ end
50
+ alias :exec_update :exec_delete
51
+
52
+ private
53
+ def default_insert_value(column)
54
+ Arel.sql("DEFAULT") unless column.auto_increment?
55
+ end
56
+
57
+ def last_inserted_id(result)
58
+ @connection.last_id
59
+ end
60
+
61
+ def discard_remaining_results
62
+ @connection.next_result while @connection.more_results?
63
+ end
64
+
65
+ def supports_set_server_option?
66
+ @connection.respond_to?(:set_server_option)
67
+ end
68
+
69
+ def multi_statements_enabled?(flags)
70
+ if flags.is_a?(Array)
71
+ flags.include?("MULTI_STATEMENTS")
72
+ else
73
+ (flags & Mysql2::Client::MULTI_STATEMENTS) != 0
74
+ end
75
+ end
76
+
77
+ def with_multi_statements
78
+ previous_flags = @config[:flags]
79
+
80
+ unless multi_statements_enabled?(previous_flags)
81
+ if supports_set_server_option?
82
+ @connection.set_server_option(Mysql2::Client::OPTION_MULTI_STATEMENTS_ON)
83
+ else
84
+ @config[:flags] = Mysql2::Client::MULTI_STATEMENTS
85
+ reconnect!
86
+ end
87
+ end
88
+
89
+ yield
90
+ ensure
91
+ unless multi_statements_enabled?(previous_flags)
92
+ if supports_set_server_option?
93
+ @connection.set_server_option(Mysql2::Client::OPTION_MULTI_STATEMENTS_OFF)
94
+ else
95
+ @config[:flags] = previous_flags
96
+ reconnect!
97
+ end
98
+ end
99
+ end
100
+
101
+ def exec_stmt_and_free(sql, name, binds, cache_stmt: false)
102
+ # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
103
+ # made since we established the connection
104
+ @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone
105
+
106
+ type_casted_binds = type_casted_binds(binds)
107
+
108
+ log(sql, name, binds, type_casted_binds) do
109
+ if cache_stmt
110
+ cache = @statements[sql] ||= {
111
+ stmt: @connection.prepare(sql)
112
+ }
113
+ stmt = cache[:stmt]
114
+ else
115
+ stmt = @connection.prepare(sql)
116
+ end
117
+
118
+ begin
119
+ result = ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
120
+ stmt.execute(*type_casted_binds)
121
+ end
122
+ rescue Mysql2::Error => e
123
+ if cache_stmt
124
+ @statements.delete(sql)
125
+ else
126
+ stmt.close
127
+ end
128
+ raise e
129
+ end
130
+
131
+ ret = yield stmt, result
132
+ result.free if result
133
+ stmt.close unless cache_stmt
134
+ ret
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module MySQL
6
+ class ExplainPrettyPrinter # :nodoc:
7
+ # Pretty prints the result of an EXPLAIN in a way that resembles the output of the
8
+ # MySQL shell:
9
+ #
10
+ # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
11
+ # | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
12
+ # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
13
+ # | 1 | SIMPLE | users | const | PRIMARY | PRIMARY | 4 | const | 1 | |
14
+ # | 1 | SIMPLE | posts | ALL | NULL | NULL | NULL | NULL | 1 | Using where |
15
+ # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
16
+ # 2 rows in set (0.00 sec)
17
+ #
18
+ # This is an exercise in Ruby hyperrealism :).
19
+ def pp(result, elapsed)
20
+ widths = compute_column_widths(result)
21
+ separator = build_separator(widths)
22
+
23
+ pp = []
24
+
25
+ pp << separator
26
+ pp << build_cells(result.columns, widths)
27
+ pp << separator
28
+
29
+ result.rows.each do |row|
30
+ pp << build_cells(row, widths)
31
+ end
32
+
33
+ pp << separator
34
+ pp << build_footer(result.rows.length, elapsed)
35
+
36
+ pp.join("\n") + "\n"
37
+ end
38
+
39
+ private
40
+
41
+ def compute_column_widths(result)
42
+ [].tap do |widths|
43
+ result.columns.each_with_index do |column, i|
44
+ cells_in_column = [column] + result.rows.map { |r| r[i].nil? ? "NULL" : r[i].to_s }
45
+ widths << cells_in_column.map(&:length).max
46
+ end
47
+ end
48
+ end
49
+
50
+ def build_separator(widths)
51
+ padding = 1
52
+ "+" + widths.map { |w| "-" * (w + (padding * 2)) }.join("+") + "+"
53
+ end
54
+
55
+ def build_cells(items, widths)
56
+ cells = []
57
+ items.each_with_index do |item, i|
58
+ item = "NULL" if item.nil?
59
+ justifier = item.is_a?(Numeric) ? "rjust" : "ljust"
60
+ cells << item.to_s.send(justifier, widths[i])
61
+ end
62
+ "| " + cells.join(" | ") + " |"
63
+ end
64
+
65
+ def build_footer(nrows, elapsed)
66
+ rows_label = nrows == 1 ? "row" : "rows"
67
+ "#{nrows} #{rows_label} in set (%.2f sec)" % elapsed
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module MySQL
6
+ module Quoting # :nodoc:
7
+ def quote_column_name(name)
8
+ @quoted_column_names[name] ||= "`#{super.gsub('`', '``')}`".freeze
9
+ end
10
+
11
+ def quote_table_name(name)
12
+ @quoted_table_names[name] ||= super.gsub(".", "`.`").freeze
13
+ end
14
+
15
+ def unquoted_true
16
+ 1
17
+ end
18
+
19
+ def unquoted_false
20
+ 0
21
+ end
22
+
23
+ def quoted_date(value)
24
+ if supports_datetime_with_precision?
25
+ super
26
+ else
27
+ super.sub(/\.\d{6}\z/, "")
28
+ end
29
+ end
30
+
31
+ def quoted_binary(value)
32
+ "x'#{value.hex}'"
33
+ end
34
+
35
+ def _type_cast(value)
36
+ case value
37
+ when Date, Time then value
38
+ else super
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module MySQL
6
+ class SchemaCreation < AbstractAdapter::SchemaCreation # :nodoc:
7
+ delegate :add_sql_comment!, :mariadb?, to: :@conn
8
+ private :add_sql_comment!, :mariadb?
9
+
10
+ private
11
+
12
+ def visit_DropForeignKey(name)
13
+ "DROP FOREIGN KEY #{name}"
14
+ end
15
+
16
+ def visit_AddColumnDefinition(o)
17
+ add_column_position!(super, column_options(o.column))
18
+ end
19
+
20
+ def visit_ChangeColumnDefinition(o)
21
+ change_column_sql = "CHANGE #{quote_column_name(o.name)} #{accept(o.column)}".dup
22
+ add_column_position!(change_column_sql, column_options(o.column))
23
+ end
24
+
25
+ def add_table_options!(create_sql, options)
26
+ add_sql_comment!(super, options[:comment])
27
+ end
28
+
29
+ def add_column_options!(sql, options)
30
+ # By default, TIMESTAMP columns are NOT NULL, cannot contain NULL values,
31
+ # and assigning NULL assigns the current timestamp. To permit a TIMESTAMP
32
+ # column to contain NULL, explicitly declare it with the NULL attribute.
33
+ # See https://dev.mysql.com/doc/refman/5.7/en/timestamp-initialization.html
34
+ if /\Atimestamp\b/.match?(options[:column].sql_type) && !options[:primary_key]
35
+ sql << " NULL" unless options[:null] == false || options_include_default?(options)
36
+ end
37
+
38
+ if charset = options[:charset]
39
+ sql << " CHARACTER SET #{charset}"
40
+ end
41
+
42
+ if collation = options[:collation]
43
+ sql << " COLLATE #{collation}"
44
+ end
45
+
46
+ if as = options[:as]
47
+ sql << " AS (#{as})"
48
+ if options[:stored]
49
+ sql << (mariadb? ? " PERSISTENT" : " STORED")
50
+ end
51
+ end
52
+
53
+ add_sql_comment!(super, options[:comment])
54
+ end
55
+
56
+ def add_column_position!(sql, options)
57
+ if options[:first]
58
+ sql << " FIRST"
59
+ elsif options[:after]
60
+ sql << " AFTER #{quote_column_name(options[:after])}"
61
+ end
62
+
63
+ sql
64
+ end
65
+
66
+ def index_in_create(table_name, column_name, options)
67
+ index_name, index_type, index_columns, _, _, index_using, comment = @conn.add_index_options(table_name, column_name, options)
68
+ add_sql_comment!("#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})".dup, comment)
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module MySQL
6
+ module ColumnMethods
7
+ def blob(*args, **options)
8
+ args.each { |name| column(name, :blob, options) }
9
+ end
10
+
11
+ def tinyblob(*args, **options)
12
+ args.each { |name| column(name, :tinyblob, options) }
13
+ end
14
+
15
+ def mediumblob(*args, **options)
16
+ args.each { |name| column(name, :mediumblob, options) }
17
+ end
18
+
19
+ def longblob(*args, **options)
20
+ args.each { |name| column(name, :longblob, options) }
21
+ end
22
+
23
+ def tinytext(*args, **options)
24
+ args.each { |name| column(name, :tinytext, options) }
25
+ end
26
+
27
+ def mediumtext(*args, **options)
28
+ args.each { |name| column(name, :mediumtext, options) }
29
+ end
30
+
31
+ def longtext(*args, **options)
32
+ args.each { |name| column(name, :longtext, options) }
33
+ end
34
+
35
+ def unsigned_integer(*args, **options)
36
+ args.each { |name| column(name, :unsigned_integer, options) }
37
+ end
38
+
39
+ def unsigned_bigint(*args, **options)
40
+ args.each { |name| column(name, :unsigned_bigint, options) }
41
+ end
42
+
43
+ def unsigned_float(*args, **options)
44
+ args.each { |name| column(name, :unsigned_float, options) }
45
+ end
46
+
47
+ def unsigned_decimal(*args, **options)
48
+ args.each { |name| column(name, :unsigned_decimal, options) }
49
+ end
50
+ end
51
+
52
+ class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
53
+ include ColumnMethods
54
+
55
+ def new_column_definition(name, type, **options) # :nodoc:
56
+ case type
57
+ when :virtual
58
+ type = options[:type]
59
+ when :primary_key
60
+ type = :integer
61
+ options[:limit] ||= 8
62
+ options[:primary_key] = true
63
+ when /\Aunsigned_(?<type>.+)\z/
64
+ type = $~[:type].to_sym
65
+ options[:unsigned] = true
66
+ end
67
+
68
+ super
69
+ end
70
+
71
+ private
72
+ def aliased_types(name, fallback)
73
+ fallback
74
+ end
75
+
76
+ def integer_like_primary_key_type(type, options)
77
+ options[:auto_increment] = true
78
+ type
79
+ end
80
+ end
81
+
82
+ class Table < ActiveRecord::ConnectionAdapters::Table
83
+ include ColumnMethods
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module MySQL
6
+ class SchemaDumper < ConnectionAdapters::SchemaDumper # :nodoc:
7
+ private
8
+ def prepare_column_options(column)
9
+ spec = super
10
+ spec[:unsigned] = "true" if column.unsigned?
11
+ spec[:auto_increment] = "true" if column.auto_increment?
12
+
13
+ if @connection.supports_virtual_columns? && column.virtual?
14
+ spec[:as] = extract_expression_for_virtual_column(column)
15
+ spec[:stored] = "true" if /\b(?:STORED|PERSISTENT)\b/.match?(column.extra)
16
+ spec = { type: schema_type(column).inspect }.merge!(spec)
17
+ end
18
+
19
+ spec
20
+ end
21
+
22
+ def column_spec_for_primary_key(column)
23
+ spec = super
24
+ spec.delete(:auto_increment) if column.type == :integer && column.auto_increment?
25
+ spec
26
+ end
27
+
28
+ def default_primary_key?(column)
29
+ super && column.auto_increment? && !column.unsigned?
30
+ end
31
+
32
+ def explicit_primary_key_default?(column)
33
+ column.type == :integer && !column.auto_increment?
34
+ end
35
+
36
+ def schema_type(column)
37
+ case column.sql_type
38
+ when /\Atimestamp\b/
39
+ :timestamp
40
+ when "tinyblob"
41
+ :blob
42
+ else
43
+ super
44
+ end
45
+ end
46
+
47
+ def schema_precision(column)
48
+ super unless /\A(?:date)?time(?:stamp)?\b/.match?(column.sql_type) && column.precision == 0
49
+ end
50
+
51
+ def schema_collation(column)
52
+ if column.collation && table_name = column.table_name
53
+ @table_collation_cache ||= {}
54
+ @table_collation_cache[table_name] ||=
55
+ @connection.exec_query("SHOW TABLE STATUS LIKE #{@connection.quote(table_name)}", "SCHEMA").first["Collation"]
56
+ column.collation.inspect if column.collation != @table_collation_cache[table_name]
57
+ end
58
+ end
59
+
60
+ def extract_expression_for_virtual_column(column)
61
+ if @connection.mariadb? && @connection.version < "10.2.5"
62
+ create_table_info = @connection.send(:create_table_info, column.table_name)
63
+ column_name = @connection.quote_column_name(column.name)
64
+ if %r/#{column_name} #{Regexp.quote(column.sql_type)}(?: COLLATE \w+)? AS \((?<expression>.+?)\) #{column.extra}/ =~ create_table_info
65
+ $~[:expression].inspect
66
+ end
67
+ else
68
+ scope = @connection.send(:quoted_scope, column.table_name)
69
+ column_name = @connection.quote(column.name)
70
+ sql = "SELECT generation_expression FROM information_schema.columns" \
71
+ " WHERE table_schema = #{scope[:schema]}" \
72
+ " AND table_name = #{scope[:name]}" \
73
+ " AND column_name = #{column_name}"
74
+ @connection.query_value(sql, "SCHEMA").inspect
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,148 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module MySQL
6
+ module SchemaStatements # :nodoc:
7
+ # Returns an array of indexes for the given table.
8
+ def indexes(table_name)
9
+ indexes = []
10
+ current_index = nil
11
+ execute_and_free("SHOW KEYS FROM #{quote_table_name(table_name)}", "SCHEMA") do |result|
12
+ each_hash(result) do |row|
13
+ if current_index != row[:Key_name]
14
+ next if row[:Key_name] == "PRIMARY" # skip the primary key
15
+ current_index = row[:Key_name]
16
+
17
+ mysql_index_type = row[:Index_type].downcase.to_sym
18
+ case mysql_index_type
19
+ when :fulltext, :spatial
20
+ index_type = mysql_index_type
21
+ when :btree, :hash
22
+ index_using = mysql_index_type
23
+ end
24
+
25
+ indexes << [
26
+ row[:Table],
27
+ row[:Key_name],
28
+ row[:Non_unique].to_i == 0,
29
+ [],
30
+ lengths: {},
31
+ orders: {},
32
+ type: index_type,
33
+ using: index_using,
34
+ comment: row[:Index_comment].presence
35
+ ]
36
+ end
37
+
38
+ indexes.last[-2] << row[:Column_name]
39
+ indexes.last[-1][:lengths].merge!(row[:Column_name] => row[:Sub_part].to_i) if row[:Sub_part]
40
+ indexes.last[-1][:orders].merge!(row[:Column_name] => :desc) if row[:Collation] == "D"
41
+ end
42
+ end
43
+
44
+ indexes.map { |index| IndexDefinition.new(*index) }
45
+ end
46
+
47
+ def remove_column(table_name, column_name, type = nil, options = {})
48
+ if foreign_key_exists?(table_name, column: column_name)
49
+ remove_foreign_key(table_name, column: column_name)
50
+ end
51
+ super
52
+ end
53
+
54
+ def internal_string_options_for_primary_key
55
+ super.tap do |options|
56
+ if CHARSETS_OF_4BYTES_MAXLEN.include?(charset) && (mariadb? || version < "8.0.0")
57
+ options[:collation] = collation.sub(/\A[^_]+/, "utf8")
58
+ end
59
+ end
60
+ end
61
+
62
+ def update_table_definition(table_name, base)
63
+ MySQL::Table.new(table_name, base)
64
+ end
65
+
66
+ def create_schema_dumper(options)
67
+ MySQL::SchemaDumper.create(self, options)
68
+ end
69
+
70
+ private
71
+ CHARSETS_OF_4BYTES_MAXLEN = ["utf8mb4", "utf16", "utf16le", "utf32"]
72
+
73
+ def schema_creation
74
+ MySQL::SchemaCreation.new(self)
75
+ end
76
+
77
+ def create_table_definition(*args)
78
+ MySQL::TableDefinition.new(*args)
79
+ end
80
+
81
+ def new_column_from_field(table_name, field)
82
+ type_metadata = fetch_type_metadata(field[:Type], field[:Extra])
83
+ if type_metadata.type == :datetime && /\ACURRENT_TIMESTAMP(?:\([0-6]?\))?\z/i.match?(field[:Default])
84
+ default, default_function = nil, field[:Default]
85
+ else
86
+ default, default_function = field[:Default], nil
87
+ end
88
+
89
+ MySQL::Column.new(
90
+ field[:Field],
91
+ default,
92
+ type_metadata,
93
+ field[:Null] == "YES",
94
+ table_name,
95
+ default_function,
96
+ field[:Collation],
97
+ comment: field[:Comment].presence
98
+ )
99
+ end
100
+
101
+ def fetch_type_metadata(sql_type, extra = "")
102
+ MySQL::TypeMetadata.new(super(sql_type), extra: extra)
103
+ end
104
+
105
+ def extract_foreign_key_action(specifier)
106
+ super unless specifier == "RESTRICT"
107
+ end
108
+
109
+ def add_index_length(quoted_columns, **options)
110
+ lengths = options_for_index_columns(options[:length])
111
+ quoted_columns.each do |name, column|
112
+ column << "(#{lengths[name]})" if lengths[name].present?
113
+ end
114
+ end
115
+
116
+ def add_options_for_index_columns(quoted_columns, **options)
117
+ quoted_columns = add_index_length(quoted_columns, options)
118
+ super
119
+ end
120
+
121
+ def data_source_sql(name = nil, type: nil)
122
+ scope = quoted_scope(name, type: type)
123
+
124
+ sql = "SELECT table_name FROM information_schema.tables".dup
125
+ sql << " WHERE table_schema = #{scope[:schema]}"
126
+ sql << " AND table_name = #{scope[:name]}" if scope[:name]
127
+ sql << " AND table_type = #{scope[:type]}" if scope[:type]
128
+ sql
129
+ end
130
+
131
+ def quoted_scope(name = nil, type: nil)
132
+ schema, name = extract_schema_qualified_name(name)
133
+ scope = {}
134
+ scope[:schema] = schema ? quote(schema) : "database()"
135
+ scope[:name] = quote(name) if name
136
+ scope[:type] = quote(type) if type
137
+ scope
138
+ end
139
+
140
+ def extract_schema_qualified_name(string)
141
+ schema, name = string.to_s.scan(/[^`.\s]+|`[^`]*`/)
142
+ schema, name = nil, schema unless name
143
+ [schema, name]
144
+ end
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module MySQL
6
+ class TypeMetadata < DelegateClass(SqlTypeMetadata) # :nodoc:
7
+ undef to_yaml if method_defined?(:to_yaml)
8
+
9
+ attr_reader :extra
10
+
11
+ def initialize(type_metadata, extra: "")
12
+ super(type_metadata)
13
+ @type_metadata = type_metadata
14
+ @extra = extra
15
+ end
16
+
17
+ def ==(other)
18
+ other.is_a?(MySQL::TypeMetadata) &&
19
+ attributes_for_hash == other.attributes_for_hash
20
+ end
21
+ alias eql? ==
22
+
23
+ def hash
24
+ attributes_for_hash.hash
25
+ end
26
+
27
+ protected
28
+
29
+ def attributes_for_hash
30
+ [self.class, @type_metadata, extra]
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end