activerecord 6.1.7 → 7.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (311) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +2030 -1020
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +18 -18
  5. data/lib/active_record/aggregations.rb +17 -14
  6. data/lib/active_record/association_relation.rb +1 -11
  7. data/lib/active_record/associations/association.rb +51 -19
  8. data/lib/active_record/associations/association_scope.rb +17 -12
  9. data/lib/active_record/associations/belongs_to_association.rb +28 -9
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +10 -2
  11. data/lib/active_record/associations/builder/association.rb +11 -5
  12. data/lib/active_record/associations/builder/belongs_to.rb +40 -14
  13. data/lib/active_record/associations/builder/collection_association.rb +10 -3
  14. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
  15. data/lib/active_record/associations/builder/has_many.rb +3 -2
  16. data/lib/active_record/associations/builder/has_one.rb +2 -1
  17. data/lib/active_record/associations/builder/singular_association.rb +6 -2
  18. data/lib/active_record/associations/collection_association.rb +39 -35
  19. data/lib/active_record/associations/collection_proxy.rb +30 -15
  20. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  21. data/lib/active_record/associations/foreign_association.rb +10 -3
  22. data/lib/active_record/associations/has_many_association.rb +28 -18
  23. data/lib/active_record/associations/has_many_through_association.rb +12 -7
  24. data/lib/active_record/associations/has_one_association.rb +20 -10
  25. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  26. data/lib/active_record/associations/join_dependency/join_association.rb +3 -2
  27. data/lib/active_record/associations/join_dependency.rb +28 -20
  28. data/lib/active_record/associations/preloader/association.rb +210 -52
  29. data/lib/active_record/associations/preloader/batch.rb +48 -0
  30. data/lib/active_record/associations/preloader/branch.rb +147 -0
  31. data/lib/active_record/associations/preloader/through_association.rb +50 -14
  32. data/lib/active_record/associations/preloader.rb +50 -121
  33. data/lib/active_record/associations/singular_association.rb +9 -3
  34. data/lib/active_record/associations/through_association.rb +25 -14
  35. data/lib/active_record/associations.rb +446 -306
  36. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  37. data/lib/active_record/attribute_assignment.rb +1 -3
  38. data/lib/active_record/attribute_methods/before_type_cast.rb +24 -2
  39. data/lib/active_record/attribute_methods/dirty.rb +73 -22
  40. data/lib/active_record/attribute_methods/primary_key.rb +78 -26
  41. data/lib/active_record/attribute_methods/query.rb +31 -19
  42. data/lib/active_record/attribute_methods/read.rb +27 -12
  43. data/lib/active_record/attribute_methods/serialization.rb +194 -37
  44. data/lib/active_record/attribute_methods/time_zone_conversion.rb +8 -3
  45. data/lib/active_record/attribute_methods/write.rb +12 -15
  46. data/lib/active_record/attribute_methods.rb +161 -40
  47. data/lib/active_record/attributes.rb +27 -38
  48. data/lib/active_record/autosave_association.rb +65 -31
  49. data/lib/active_record/base.rb +25 -2
  50. data/lib/active_record/callbacks.rb +18 -34
  51. data/lib/active_record/coders/column_serializer.rb +61 -0
  52. data/lib/active_record/coders/json.rb +1 -1
  53. data/lib/active_record/coders/yaml_column.rb +70 -46
  54. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +367 -0
  55. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +211 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +78 -0
  57. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +113 -597
  58. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -17
  59. data/lib/active_record/connection_adapters/abstract/database_statements.rb +172 -50
  60. data/lib/active_record/connection_adapters/abstract/query_cache.rb +78 -27
  61. data/lib/active_record/connection_adapters/abstract/quoting.rb +87 -73
  62. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  63. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +21 -20
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +186 -31
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +367 -141
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +281 -59
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +631 -150
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +317 -164
  70. data/lib/active_record/connection_adapters/column.rb +13 -0
  71. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  72. data/lib/active_record/connection_adapters/mysql/database_statements.rb +25 -134
  73. data/lib/active_record/connection_adapters/mysql/quoting.rb +56 -25
  74. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  75. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  76. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
  77. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +39 -14
  78. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
  79. data/lib/active_record/connection_adapters/mysql2_adapter.rb +112 -55
  80. data/lib/active_record/connection_adapters/pool_config.rb +20 -11
  81. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +30 -1
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +89 -52
  84. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  85. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
  89. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  90. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +12 -3
  91. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  94. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  95. data/lib/active_record/connection_adapters/postgresql/quoting.rb +89 -56
  96. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +28 -0
  97. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +92 -2
  98. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +153 -3
  99. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +78 -0
  100. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +397 -75
  101. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  102. data/lib/active_record/connection_adapters/postgresql_adapter.rb +508 -246
  103. data/lib/active_record/connection_adapters/schema_cache.rb +319 -90
  104. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  105. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +72 -53
  106. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +37 -21
  107. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
  108. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +43 -22
  109. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +296 -104
  110. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  111. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  112. data/lib/active_record/connection_adapters/trilogy_adapter.rb +258 -0
  113. data/lib/active_record/connection_adapters.rb +9 -6
  114. data/lib/active_record/connection_handling.rb +108 -137
  115. data/lib/active_record/core.rb +242 -233
  116. data/lib/active_record/counter_cache.rb +52 -27
  117. data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -2
  118. data/lib/active_record/database_configurations/database_config.rb +21 -12
  119. data/lib/active_record/database_configurations/hash_config.rb +88 -16
  120. data/lib/active_record/database_configurations/url_config.rb +18 -12
  121. data/lib/active_record/database_configurations.rb +95 -59
  122. data/lib/active_record/delegated_type.rb +66 -20
  123. data/lib/active_record/deprecator.rb +7 -0
  124. data/lib/active_record/destroy_association_async_job.rb +4 -2
  125. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  126. data/lib/active_record/dynamic_matchers.rb +1 -1
  127. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  128. data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
  129. data/lib/active_record/encryption/cipher.rb +53 -0
  130. data/lib/active_record/encryption/config.rb +68 -0
  131. data/lib/active_record/encryption/configurable.rb +60 -0
  132. data/lib/active_record/encryption/context.rb +42 -0
  133. data/lib/active_record/encryption/contexts.rb +76 -0
  134. data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
  135. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  136. data/lib/active_record/encryption/encryptable_record.rb +230 -0
  137. data/lib/active_record/encryption/encrypted_attribute_type.rb +155 -0
  138. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  139. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  140. data/lib/active_record/encryption/encryptor.rb +155 -0
  141. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  142. data/lib/active_record/encryption/errors.rb +15 -0
  143. data/lib/active_record/encryption/extended_deterministic_queries.rb +157 -0
  144. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  145. data/lib/active_record/encryption/key.rb +28 -0
  146. data/lib/active_record/encryption/key_generator.rb +53 -0
  147. data/lib/active_record/encryption/key_provider.rb +46 -0
  148. data/lib/active_record/encryption/message.rb +33 -0
  149. data/lib/active_record/encryption/message_serializer.rb +92 -0
  150. data/lib/active_record/encryption/null_encryptor.rb +21 -0
  151. data/lib/active_record/encryption/properties.rb +76 -0
  152. data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
  153. data/lib/active_record/encryption/scheme.rb +100 -0
  154. data/lib/active_record/encryption.rb +58 -0
  155. data/lib/active_record/enum.rb +154 -63
  156. data/lib/active_record/errors.rb +172 -15
  157. data/lib/active_record/explain.rb +23 -3
  158. data/lib/active_record/explain_registry.rb +11 -6
  159. data/lib/active_record/explain_subscriber.rb +1 -1
  160. data/lib/active_record/fixture_set/file.rb +15 -1
  161. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  162. data/lib/active_record/fixture_set/render_context.rb +2 -0
  163. data/lib/active_record/fixture_set/table_row.rb +70 -14
  164. data/lib/active_record/fixture_set/table_rows.rb +4 -4
  165. data/lib/active_record/fixtures.rb +147 -86
  166. data/lib/active_record/future_result.rb +174 -0
  167. data/lib/active_record/gem_version.rb +3 -3
  168. data/lib/active_record/inheritance.rb +81 -29
  169. data/lib/active_record/insert_all.rb +135 -22
  170. data/lib/active_record/integration.rb +11 -10
  171. data/lib/active_record/internal_metadata.rb +119 -33
  172. data/lib/active_record/legacy_yaml_adapter.rb +2 -39
  173. data/lib/active_record/locking/optimistic.rb +37 -22
  174. data/lib/active_record/locking/pessimistic.rb +15 -6
  175. data/lib/active_record/log_subscriber.rb +52 -19
  176. data/lib/active_record/marshalling.rb +59 -0
  177. data/lib/active_record/message_pack.rb +124 -0
  178. data/lib/active_record/middleware/database_selector/resolver.rb +10 -10
  179. data/lib/active_record/middleware/database_selector.rb +23 -13
  180. data/lib/active_record/middleware/shard_selector.rb +62 -0
  181. data/lib/active_record/migration/command_recorder.rb +112 -14
  182. data/lib/active_record/migration/compatibility.rb +233 -46
  183. data/lib/active_record/migration/default_strategy.rb +23 -0
  184. data/lib/active_record/migration/execution_strategy.rb +19 -0
  185. data/lib/active_record/migration/join_table.rb +1 -1
  186. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  187. data/lib/active_record/migration.rb +361 -173
  188. data/lib/active_record/model_schema.rb +125 -101
  189. data/lib/active_record/nested_attributes.rb +50 -20
  190. data/lib/active_record/no_touching.rb +3 -3
  191. data/lib/active_record/normalization.rb +167 -0
  192. data/lib/active_record/persistence.rb +409 -88
  193. data/lib/active_record/promise.rb +84 -0
  194. data/lib/active_record/query_cache.rb +4 -22
  195. data/lib/active_record/query_logs.rb +174 -0
  196. data/lib/active_record/query_logs_formatter.rb +41 -0
  197. data/lib/active_record/querying.rb +29 -6
  198. data/lib/active_record/railtie.rb +220 -44
  199. data/lib/active_record/railties/controller_runtime.rb +15 -10
  200. data/lib/active_record/railties/databases.rake +188 -252
  201. data/lib/active_record/railties/job_runtime.rb +23 -0
  202. data/lib/active_record/readonly_attributes.rb +41 -3
  203. data/lib/active_record/reflection.rb +248 -81
  204. data/lib/active_record/relation/batches/batch_enumerator.rb +23 -7
  205. data/lib/active_record/relation/batches.rb +192 -63
  206. data/lib/active_record/relation/calculations.rb +246 -90
  207. data/lib/active_record/relation/delegation.rb +28 -14
  208. data/lib/active_record/relation/finder_methods.rb +108 -51
  209. data/lib/active_record/relation/merger.rb +22 -13
  210. data/lib/active_record/relation/predicate_builder/association_query_value.rb +31 -3
  211. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  212. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  213. data/lib/active_record/relation/predicate_builder.rb +27 -20
  214. data/lib/active_record/relation/query_attribute.rb +30 -12
  215. data/lib/active_record/relation/query_methods.rb +670 -129
  216. data/lib/active_record/relation/record_fetch_warning.rb +7 -9
  217. data/lib/active_record/relation/spawn_methods.rb +20 -3
  218. data/lib/active_record/relation/where_clause.rb +10 -19
  219. data/lib/active_record/relation.rb +287 -120
  220. data/lib/active_record/result.rb +37 -11
  221. data/lib/active_record/runtime_registry.rb +32 -13
  222. data/lib/active_record/sanitization.rb +65 -20
  223. data/lib/active_record/schema.rb +36 -22
  224. data/lib/active_record/schema_dumper.rb +73 -24
  225. data/lib/active_record/schema_migration.rb +68 -33
  226. data/lib/active_record/scoping/default.rb +72 -15
  227. data/lib/active_record/scoping/named.rb +5 -13
  228. data/lib/active_record/scoping.rb +65 -34
  229. data/lib/active_record/secure_password.rb +60 -0
  230. data/lib/active_record/secure_token.rb +21 -3
  231. data/lib/active_record/serialization.rb +6 -1
  232. data/lib/active_record/signed_id.rb +10 -8
  233. data/lib/active_record/store.rb +10 -10
  234. data/lib/active_record/suppressor.rb +13 -15
  235. data/lib/active_record/table_metadata.rb +16 -3
  236. data/lib/active_record/tasks/database_tasks.rb +251 -140
  237. data/lib/active_record/tasks/mysql_database_tasks.rb +16 -7
  238. data/lib/active_record/tasks/postgresql_database_tasks.rb +35 -26
  239. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  240. data/lib/active_record/test_databases.rb +1 -1
  241. data/lib/active_record/test_fixtures.rb +117 -96
  242. data/lib/active_record/timestamp.rb +32 -19
  243. data/lib/active_record/token_for.rb +113 -0
  244. data/lib/active_record/touch_later.rb +11 -6
  245. data/lib/active_record/transactions.rb +48 -27
  246. data/lib/active_record/translation.rb +3 -3
  247. data/lib/active_record/type/adapter_specific_registry.rb +32 -14
  248. data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
  249. data/lib/active_record/type/internal/timezone.rb +7 -2
  250. data/lib/active_record/type/serialized.rb +9 -5
  251. data/lib/active_record/type/time.rb +4 -0
  252. data/lib/active_record/type/type_map.rb +17 -20
  253. data/lib/active_record/type.rb +1 -2
  254. data/lib/active_record/validations/absence.rb +1 -1
  255. data/lib/active_record/validations/associated.rb +4 -4
  256. data/lib/active_record/validations/numericality.rb +5 -4
  257. data/lib/active_record/validations/presence.rb +5 -28
  258. data/lib/active_record/validations/uniqueness.rb +51 -6
  259. data/lib/active_record/validations.rb +8 -4
  260. data/lib/active_record/version.rb +1 -1
  261. data/lib/active_record.rb +335 -32
  262. data/lib/arel/attributes/attribute.rb +0 -8
  263. data/lib/arel/crud.rb +28 -22
  264. data/lib/arel/delete_manager.rb +18 -4
  265. data/lib/arel/errors.rb +10 -0
  266. data/lib/arel/factory_methods.rb +4 -0
  267. data/lib/arel/filter_predications.rb +9 -0
  268. data/lib/arel/insert_manager.rb +2 -3
  269. data/lib/arel/nodes/and.rb +4 -0
  270. data/lib/arel/nodes/binary.rb +6 -1
  271. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  272. data/lib/arel/nodes/casted.rb +1 -1
  273. data/lib/arel/nodes/cte.rb +36 -0
  274. data/lib/arel/nodes/delete_statement.rb +12 -13
  275. data/lib/arel/nodes/filter.rb +10 -0
  276. data/lib/arel/nodes/fragments.rb +35 -0
  277. data/lib/arel/nodes/function.rb +1 -0
  278. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  279. data/lib/arel/nodes/insert_statement.rb +2 -2
  280. data/lib/arel/nodes/leading_join.rb +8 -0
  281. data/lib/arel/nodes/node.rb +111 -2
  282. data/lib/arel/nodes/select_core.rb +2 -2
  283. data/lib/arel/nodes/select_statement.rb +2 -2
  284. data/lib/arel/nodes/sql_literal.rb +6 -0
  285. data/lib/arel/nodes/table_alias.rb +4 -0
  286. data/lib/arel/nodes/update_statement.rb +8 -3
  287. data/lib/arel/nodes.rb +5 -0
  288. data/lib/arel/predications.rb +13 -3
  289. data/lib/arel/select_manager.rb +10 -4
  290. data/lib/arel/table.rb +9 -6
  291. data/lib/arel/tree_manager.rb +5 -13
  292. data/lib/arel/update_manager.rb +18 -4
  293. data/lib/arel/visitors/dot.rb +80 -90
  294. data/lib/arel/visitors/mysql.rb +16 -3
  295. data/lib/arel/visitors/postgresql.rb +0 -10
  296. data/lib/arel/visitors/to_sql.rb +141 -20
  297. data/lib/arel/visitors/visitor.rb +2 -2
  298. data/lib/arel.rb +18 -3
  299. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  300. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  301. data/lib/rails/generators/active_record/migration.rb +3 -1
  302. data/lib/rails/generators/active_record/model/USAGE +113 -0
  303. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  304. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  305. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  306. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  307. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  308. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  309. metadata +96 -16
  310. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  311. data/lib/active_record/null_relation.rb +0 -67
@@ -63,6 +63,15 @@ module ActiveRecord
63
63
  coder["comment"] = @comment
64
64
  end
65
65
 
66
+ # whether the column is auto-populated by the database using a sequence
67
+ def auto_incremented_by_db?
68
+ false
69
+ end
70
+
71
+ def auto_populated?
72
+ auto_incremented_by_db? || default_function
73
+ end
74
+
66
75
  def ==(other)
67
76
  other.is_a?(Column) &&
68
77
  name == other.name &&
@@ -87,6 +96,10 @@ module ActiveRecord
87
96
  comment.hash
88
97
  end
89
98
 
99
+ def virtual?
100
+ false
101
+ end
102
+
90
103
  private
91
104
  def deduplicated
92
105
  @name = -name
@@ -17,6 +17,7 @@ module ActiveRecord
17
17
  def auto_increment?
18
18
  extra == "auto_increment"
19
19
  end
20
+ alias_method :auto_incremented_by_db?, :auto_increment?
20
21
 
21
22
  def virtual?
22
23
  /\b(?:VIRTUAL|STORED|PERSISTENT)\b/.match?(extra)
@@ -4,125 +4,57 @@ module ActiveRecord
4
4
  module ConnectionAdapters
5
5
  module MySQL
6
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
- @connection.abandon_results!
15
- result
16
- end
17
-
18
- def query(sql, name = nil) # :nodoc:
19
- execute(sql, name).to_a
20
- end
21
-
22
- READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
23
- :desc, :describe, :set, :show, :use
7
+ READ_QUERY = AbstractAdapter.build_read_query_regexp(
8
+ :desc, :describe, :set, :show, :use, :kill
24
9
  ) # :nodoc:
25
10
  private_constant :READ_QUERY
26
11
 
12
+ # https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_current-timestamp
13
+ # https://dev.mysql.com/doc/refman/5.7/en/date-and-time-type-syntax.html
14
+ HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP(6)").freeze # :nodoc:
15
+ private_constant :HIGH_PRECISION_CURRENT_TIMESTAMP
16
+
27
17
  def write_query?(sql) # :nodoc:
28
18
  !READ_QUERY.match?(sql)
29
19
  rescue ArgumentError # Invalid encoding
30
20
  !READ_QUERY.match?(sql.b)
31
21
  end
32
22
 
33
- def explain(arel, binds = [])
34
- sql = "EXPLAIN #{to_sql(arel, binds)}"
35
- start = Concurrent.monotonic_time
36
- result = exec_query(sql, "EXPLAIN", binds)
37
- elapsed = Concurrent.monotonic_time - start
38
-
39
- MySQL::ExplainPrettyPrinter.new.pp(result, elapsed)
23
+ def high_precision_current_timestamp
24
+ HIGH_PRECISION_CURRENT_TIMESTAMP
40
25
  end
41
26
 
42
- # Executes the SQL statement in the context of this connection.
43
- def execute(sql, name = nil)
44
- if preventing_writes? && write_query?(sql)
45
- raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
46
- end
47
-
48
- # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
49
- # made since we established the connection
50
- @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone
27
+ def explain(arel, binds = [], options = [])
28
+ sql = build_explain_clause(options) + " " + to_sql(arel, binds)
29
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
30
+ result = internal_exec_query(sql, "EXPLAIN", binds)
31
+ elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
51
32
 
52
- super
33
+ MySQL::ExplainPrettyPrinter.new.pp(result, elapsed)
53
34
  end
54
35
 
55
- def exec_query(sql, name = "SQL", binds = [], prepare: false)
56
- if without_prepared_statement?(binds)
57
- execute_and_free(sql, name) do |result|
58
- if result
59
- build_result(columns: result.fields, rows: result.to_a)
60
- else
61
- build_result(columns: [], rows: [])
62
- end
63
- end
64
- else
65
- exec_stmt_and_free(sql, name, binds, cache_stmt: prepare) do |_, result|
66
- if result
67
- build_result(columns: result.fields, rows: result.to_a)
68
- else
69
- build_result(columns: [], rows: [])
70
- end
71
- end
72
- end
73
- end
36
+ def build_explain_clause(options = [])
37
+ return "EXPLAIN" if options.empty?
74
38
 
75
- def exec_delete(sql, name = nil, binds = [])
76
- if without_prepared_statement?(binds)
77
- @lock.synchronize do
78
- execute_and_free(sql, name) { @connection.affected_rows }
79
- end
39
+ explain_clause = "EXPLAIN #{options.join(" ").upcase}"
40
+
41
+ if analyze_without_explain? && explain_clause.include?("ANALYZE")
42
+ explain_clause.sub("EXPLAIN ", "")
80
43
  else
81
- exec_stmt_and_free(sql, name, binds) { |stmt| stmt.affected_rows }
44
+ explain_clause
82
45
  end
83
46
  end
84
- alias :exec_update :exec_delete
85
47
 
86
48
  private
87
- def execute_batch(statements, name = nil)
88
- combine_multi_statements(statements).each do |statement|
89
- execute(statement, name)
90
- end
91
- @connection.abandon_results!
49
+ # https://mariadb.com/kb/en/analyze-statement/
50
+ def analyze_without_explain?
51
+ mariadb? && database_version >= "10.1.0"
92
52
  end
93
53
 
94
54
  def default_insert_value(column)
95
55
  super unless column.auto_increment?
96
56
  end
97
57
 
98
- def last_inserted_id(result)
99
- @connection.last_id
100
- end
101
-
102
- def multi_statements_enabled?
103
- flags = @config[:flags]
104
-
105
- if flags.is_a?(Array)
106
- flags.include?("MULTI_STATEMENTS")
107
- else
108
- flags.anybits?(Mysql2::Client::MULTI_STATEMENTS)
109
- end
110
- end
111
-
112
- def with_multi_statements
113
- multi_statements_was = multi_statements_enabled?
114
-
115
- unless multi_statements_was
116
- @connection.set_server_option(Mysql2::Client::OPTION_MULTI_STATEMENTS_ON)
117
- end
118
-
119
- yield
120
- ensure
121
- unless multi_statements_was
122
- @connection.set_server_option(Mysql2::Client::OPTION_MULTI_STATEMENTS_OFF)
123
- end
124
- end
125
-
126
58
  def combine_multi_statements(total_sql)
127
59
  total_sql.each_with_object([]) do |sql, total_sql_chunks|
128
60
  previous_packet = total_sql_chunks.last
@@ -149,47 +81,6 @@ module ActiveRecord
149
81
  def max_allowed_packet
150
82
  @max_allowed_packet ||= show_variable("max_allowed_packet")
151
83
  end
152
-
153
- def exec_stmt_and_free(sql, name, binds, cache_stmt: false)
154
- if preventing_writes? && write_query?(sql)
155
- raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
156
- end
157
-
158
- materialize_transactions
159
- mark_transaction_written_if_write(sql)
160
-
161
- # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
162
- # made since we established the connection
163
- @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone
164
-
165
- type_casted_binds = type_casted_binds(binds)
166
-
167
- log(sql, name, binds, type_casted_binds) do
168
- if cache_stmt
169
- stmt = @statements[sql] ||= @connection.prepare(sql)
170
- else
171
- stmt = @connection.prepare(sql)
172
- end
173
-
174
- begin
175
- result = ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
176
- stmt.execute(*type_casted_binds)
177
- end
178
- rescue Mysql2::Error => e
179
- if cache_stmt
180
- @statements.delete(sql)
181
- else
182
- stmt.close
183
- end
184
- raise e
185
- end
186
-
187
- ret = yield stmt, result
188
- result.free if result
189
- stmt.close unless cache_stmt
190
- ret
191
- end
192
- end
193
84
  end
194
85
  end
195
86
  end
@@ -6,12 +6,35 @@ module ActiveRecord
6
6
  module ConnectionAdapters
7
7
  module MySQL
8
8
  module Quoting # :nodoc:
9
+ QUOTED_COLUMN_NAMES = Concurrent::Map.new # :nodoc:
10
+ QUOTED_TABLE_NAMES = Concurrent::Map.new # :nodoc:
11
+
12
+ def cast_bound_value(value)
13
+ case value
14
+ when Rational
15
+ value.to_f.to_s
16
+ when Numeric
17
+ value.to_s
18
+ when BigDecimal
19
+ value.to_s("F")
20
+ when true
21
+ "1"
22
+ when false
23
+ "0"
24
+ when ActiveSupport::Duration
25
+ warn_quote_duration_deprecated
26
+ value.to_s
27
+ else
28
+ value
29
+ end
30
+ end
31
+
9
32
  def quote_column_name(name)
10
- self.class.quoted_column_names[name] ||= "`#{super.gsub('`', '``')}`"
33
+ QUOTED_COLUMN_NAMES[name] ||= "`#{super.gsub('`', '``')}`"
11
34
  end
12
35
 
13
36
  def quote_table_name(name)
14
- self.class.quoted_table_names[name] ||= super.gsub(".", "`.`").freeze
37
+ QUOTED_TABLE_NAMES[name] ||= super.gsub(".", "`.`").freeze
15
38
  end
16
39
 
17
40
  def unquoted_true
@@ -34,6 +57,34 @@ module ActiveRecord
34
57
  "x'#{value.hex}'"
35
58
  end
36
59
 
60
+ def unquote_identifier(identifier)
61
+ if identifier && identifier.start_with?("`")
62
+ identifier[1..-2]
63
+ else
64
+ identifier
65
+ end
66
+ end
67
+
68
+ # Override +type_cast+ we pass to mysql2 Date and Time objects instead
69
+ # of Strings since MySQL adapters are able to handle those classes more efficiently.
70
+ def type_cast(value) # :nodoc:
71
+ case value
72
+ when ActiveSupport::TimeWithZone
73
+ # We need to check explicitly for ActiveSupport::TimeWithZone because
74
+ # we need to transform it to Time objects but we don't want to
75
+ # transform Time objects to themselves.
76
+ if default_timezone == :utc
77
+ value.getutc
78
+ else
79
+ value.getlocal
80
+ end
81
+ when Date, Time
82
+ value
83
+ else
84
+ super
85
+ end
86
+ end
87
+
37
88
  def column_name_matcher
38
89
  COLUMN_NAME
39
90
  end
@@ -47,7 +98,7 @@ module ActiveRecord
47
98
  (
48
99
  (?:
49
100
  # `table_name`.`column_name` | function(one or no argument)
50
- ((?:\w+\.|`\w+`\.)?(?:\w+|`\w+`)) | \w+\((?:|\g<2>)\)
101
+ ((?:\w+\.|`\w+`\.)?(?:\w+|`\w+`) | \w+\((?:|\g<2>)\))
51
102
  )
52
103
  (?:(?:\s+AS)?\s+(?:\w+|`\w+`))?
53
104
  )
@@ -60,8 +111,9 @@ module ActiveRecord
60
111
  (
61
112
  (?:
62
113
  # `table_name`.`column_name` | function(one or no argument)
63
- ((?:\w+\.|`\w+`\.)?(?:\w+|`\w+`)) | \w+\((?:|\g<2>)\)
114
+ ((?:\w+\.|`\w+`\.)?(?:\w+|`\w+`) | \w+\((?:|\g<2>)\))
64
115
  )
116
+ (?:\s+COLLATE\s+(?:\w+|"\w+"))?
65
117
  (?:\s+ASC|\s+DESC)?
66
118
  )
67
119
  (?:\s*,\s*\g<1>)*
@@ -69,27 +121,6 @@ module ActiveRecord
69
121
  /ix
70
122
 
71
123
  private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
72
-
73
- private
74
- # Override +_type_cast+ we pass to mysql2 Date and Time objects instead
75
- # of Strings since mysql2 is able to handle those classes more efficiently.
76
- def _type_cast(value)
77
- case value
78
- when ActiveSupport::TimeWithZone
79
- # We need to check explicitly for ActiveSupport::TimeWithZone because
80
- # we need to transform it to Time objects but we don't want to
81
- # transform Time objects to themselves.
82
- if ActiveRecord::Base.default_timezone == :utc
83
- value.getutc
84
- else
85
- value.getlocal
86
- end
87
- when Date, Time
88
- value
89
- else
90
- super
91
- end
92
- end
93
124
  end
94
125
  end
95
126
  end
@@ -24,6 +24,15 @@ module ActiveRecord
24
24
  add_column_position!(change_column_sql, column_options(o.column))
25
25
  end
26
26
 
27
+ def visit_ChangeColumnDefaultDefinition(o)
28
+ sql = +"ALTER COLUMN #{quote_column_name(o.column.name)} "
29
+ if o.default.nil? && !o.column.null
30
+ sql << "DROP DEFAULT"
31
+ else
32
+ sql << "SET DEFAULT #{quote_default_expression(o.default, o.column)}"
33
+ end
34
+ end
35
+
27
36
  def visit_CreateIndexDefinition(o)
28
37
  sql = visit_IndexDefinition(o.index, true)
29
38
  sql << " #{o.algorithm}" if o.algorithm
@@ -57,6 +57,7 @@ module ActiveRecord
57
57
  end
58
58
  end
59
59
 
60
+ # = Active Record MySQL Adapter \Table Definition
60
61
  class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
61
62
  include ColumnMethods
62
63
 
@@ -85,16 +86,24 @@ module ActiveRecord
85
86
  end
86
87
 
87
88
  private
89
+ def valid_column_definition_options
90
+ super + [:auto_increment, :charset, :as, :size, :unsigned, :first, :after, :type, :stored]
91
+ end
92
+
88
93
  def aliased_types(name, fallback)
89
94
  fallback
90
95
  end
91
96
 
92
97
  def integer_like_primary_key_type(type, options)
93
- options[:auto_increment] = true
98
+ unless options[:auto_increment] == false
99
+ options[:auto_increment] = true
100
+ end
101
+
94
102
  type
95
103
  end
96
104
  end
97
105
 
106
+ # = Active Record MySQL Adapter \Table
98
107
  class Table < ActiveRecord::ConnectionAdapters::Table
99
108
  include ColumnMethods
100
109
  end
@@ -53,14 +53,20 @@ module ActiveRecord
53
53
  end
54
54
 
55
55
  def schema_precision(column)
56
- super unless /\A(?:date)?time(?:stamp)?\b/.match?(column.sql_type) && column.precision == 0
56
+ if /\Atime(?:stamp)?\b/.match?(column.sql_type) && column.precision == 0
57
+ nil
58
+ elsif column.type == :datetime
59
+ column.precision == 0 ? "nil" : super
60
+ else
61
+ super
62
+ end
57
63
  end
58
64
 
59
65
  def schema_collation(column)
60
66
  if column.collation
61
67
  @table_collation_cache ||= {}
62
68
  @table_collation_cache[table_name] ||=
63
- @connection.exec_query("SHOW TABLE STATUS LIKE #{@connection.quote(table_name)}", "SCHEMA").first["Collation"]
69
+ @connection.internal_exec_query("SHOW TABLE STATUS LIKE #{@connection.quote(table_name)}", "SCHEMA").first["Collation"]
64
70
  column.collation.inspect if column.collation != @table_collation_cache[table_name]
65
71
  end
66
72
  end
@@ -36,7 +36,7 @@ module ActiveRecord
36
36
  end
37
37
 
38
38
  if row[:Expression]
39
- expression = row[:Expression]
39
+ expression = row[:Expression].gsub("\\'", "'")
40
40
  expression = +"(#{expression})" unless expression.start_with?("(")
41
41
  indexes.last[-2] << expression
42
42
  indexes.last[-1][:expressions] ||= {}
@@ -57,9 +57,9 @@ module ActiveRecord
57
57
  orders = options.delete(:orders)
58
58
  lengths = options.delete(:lengths)
59
59
 
60
- columns = index[-1].map { |name|
60
+ columns = index[-1].to_h { |name|
61
61
  [ name.to_sym, expressions[name] || +quote_column_name(name) ]
62
- }.to_h
62
+ }
63
63
 
64
64
  index[-1] = add_options_for_index_columns(
65
65
  columns, order: orders, length: lengths
@@ -125,6 +125,10 @@ module ActiveRecord
125
125
  256 # https://dev.mysql.com/doc/refman/en/identifiers.html
126
126
  end
127
127
 
128
+ def schema_creation # :nodoc:
129
+ MySQL::SchemaCreation.new(self)
130
+ end
131
+
128
132
  private
129
133
  CHARSETS_OF_4BYTES_MAXLEN = ["utf8mb4", "utf16", "utf16le", "utf32"]
130
134
 
@@ -150,26 +154,46 @@ module ActiveRecord
150
154
  @default_row_format
151
155
  end
152
156
 
153
- def schema_creation
154
- MySQL::SchemaCreation.new(self)
157
+ def valid_primary_key_options
158
+ super + [:unsigned, :auto_increment]
155
159
  end
156
160
 
157
161
  def create_table_definition(name, **options)
158
162
  MySQL::TableDefinition.new(self, name, **options)
159
163
  end
160
164
 
161
- def new_column_from_field(table_name, field)
165
+ def default_type(table_name, field_name)
166
+ match = create_table_info(table_name)&.match(/`#{field_name}` (.+) DEFAULT ('|\d+|[A-z]+)/)
167
+ default_pre = match[2] if match
168
+
169
+ if default_pre == "'"
170
+ :string
171
+ elsif default_pre&.match?(/^\d+$/)
172
+ :integer
173
+ elsif default_pre&.match?(/^[A-z]+$/)
174
+ :function
175
+ end
176
+ end
177
+
178
+ def new_column_from_field(table_name, field, _definitions)
179
+ field_name = field.fetch(:Field)
162
180
  type_metadata = fetch_type_metadata(field[:Type], field[:Extra])
163
181
  default, default_function = field[:Default], nil
164
182
 
165
183
  if type_metadata.type == :datetime && /\ACURRENT_TIMESTAMP(?:\([0-6]?\))?\z/i.match?(default)
184
+ default = "#{default} ON UPDATE #{default}" if /on update CURRENT_TIMESTAMP/i.match?(field[:Extra])
166
185
  default, default_function = nil, default
167
186
  elsif type_metadata.extra == "DEFAULT_GENERATED"
168
187
  default = +"(#{default})" unless default.start_with?("(")
188
+ default = default.gsub("\\'", "'")
169
189
  default, default_function = nil, default
170
- elsif type_metadata.type == :text && default
190
+ elsif type_metadata.type == :text && default&.start_with?("'")
171
191
  # strip and unescape quotes
172
192
  default = default[1...-1].gsub("\\'", "'")
193
+ elsif default&.match?(/\A\d/)
194
+ # Its a number so we can skip the query to check if it is a function
195
+ elsif default && default_type(table_name, field_name) == :function
196
+ default, default_function = nil, default
173
197
  end
174
198
 
175
199
  MySQL::Column.new(
@@ -206,14 +230,15 @@ module ActiveRecord
206
230
  def data_source_sql(name = nil, type: nil)
207
231
  scope = quoted_scope(name, type: type)
208
232
 
209
- sql = +"SELECT table_name FROM (SELECT table_name, table_type FROM information_schema.tables "
210
- sql << " WHERE table_schema = #{scope[:schema]}) _subquery"
211
- if scope[:type] || scope[:name]
212
- conditions = []
213
- conditions << "_subquery.table_type = #{scope[:type]}" if scope[:type]
214
- conditions << "_subquery.table_name = #{scope[:name]}" if scope[:name]
215
- sql << " WHERE #{conditions.join(" AND ")}"
233
+ sql = +"SELECT table_name FROM information_schema.tables"
234
+ sql << " WHERE table_schema = #{scope[:schema]}"
235
+
236
+ if scope[:name]
237
+ sql << " AND table_name = #{scope[:name]}"
238
+ sql << " AND table_name IN (SELECT table_name FROM information_schema.tables WHERE table_schema = #{scope[:schema]})"
216
239
  end
240
+
241
+ sql << " AND table_type = #{scope[:type]}" if scope[:type]
217
242
  sql
218
243
  end
219
244
 
@@ -0,0 +1,151 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module Mysql2
6
+ module DatabaseStatements
7
+ # Returns an ActiveRecord::Result instance.
8
+ def select_all(*, **) # :nodoc:
9
+ result = nil
10
+ with_raw_connection do |conn|
11
+ result = if ExplainRegistry.collect? && prepared_statements
12
+ unprepared_statement { super }
13
+ else
14
+ super
15
+ end
16
+ conn.abandon_results!
17
+ end
18
+ result
19
+ end
20
+
21
+ def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false) # :nodoc:
22
+ if without_prepared_statement?(binds)
23
+ execute_and_free(sql, name, async: async) do |result|
24
+ if result
25
+ build_result(columns: result.fields, rows: result.to_a)
26
+ else
27
+ build_result(columns: [], rows: [])
28
+ end
29
+ end
30
+ else
31
+ exec_stmt_and_free(sql, name, binds, cache_stmt: prepare, async: async) do |_, result|
32
+ if result
33
+ build_result(columns: result.fields, rows: result.to_a)
34
+ else
35
+ build_result(columns: [], rows: [])
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ def exec_delete(sql, name = nil, binds = []) # :nodoc:
42
+ if without_prepared_statement?(binds)
43
+ with_raw_connection do |conn|
44
+ @affected_rows_before_warnings = nil
45
+ execute_and_free(sql, name) { @affected_rows_before_warnings || conn.affected_rows }
46
+ end
47
+ else
48
+ exec_stmt_and_free(sql, name, binds) { |stmt| stmt.affected_rows }
49
+ end
50
+ end
51
+ alias :exec_update :exec_delete
52
+
53
+ private
54
+ def sync_timezone_changes(raw_connection)
55
+ raw_connection.query_options[:database_timezone] = default_timezone
56
+ end
57
+
58
+ def execute_batch(statements, name = nil)
59
+ statements = statements.map { |sql| transform_query(sql) }
60
+ combine_multi_statements(statements).each do |statement|
61
+ with_raw_connection do |conn|
62
+ raw_execute(statement, name)
63
+ conn.abandon_results!
64
+ end
65
+ end
66
+ end
67
+
68
+ def last_inserted_id(result)
69
+ @raw_connection&.last_id
70
+ end
71
+
72
+ def multi_statements_enabled?
73
+ flags = @config[:flags]
74
+
75
+ if flags.is_a?(Array)
76
+ flags.include?("MULTI_STATEMENTS")
77
+ else
78
+ flags.anybits?(::Mysql2::Client::MULTI_STATEMENTS)
79
+ end
80
+ end
81
+
82
+ def with_multi_statements
83
+ if multi_statements_enabled?
84
+ return yield
85
+ end
86
+
87
+ with_raw_connection do |conn|
88
+ conn.set_server_option(::Mysql2::Client::OPTION_MULTI_STATEMENTS_ON)
89
+
90
+ yield
91
+ ensure
92
+ conn.set_server_option(::Mysql2::Client::OPTION_MULTI_STATEMENTS_OFF)
93
+ end
94
+ end
95
+
96
+ def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true)
97
+ log(sql, name, async: async) do
98
+ with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
99
+ sync_timezone_changes(conn)
100
+ result = conn.query(sql)
101
+ verified!
102
+ handle_warnings(sql)
103
+ result
104
+ end
105
+ end
106
+ end
107
+
108
+ def exec_stmt_and_free(sql, name, binds, cache_stmt: false, async: false)
109
+ sql = transform_query(sql)
110
+ check_if_write_query(sql)
111
+
112
+ mark_transaction_written_if_write(sql)
113
+
114
+ type_casted_binds = type_casted_binds(binds)
115
+
116
+ log(sql, name, binds, type_casted_binds, async: async) do
117
+ with_raw_connection do |conn|
118
+ sync_timezone_changes(conn)
119
+
120
+ if cache_stmt
121
+ stmt = @statements[sql] ||= conn.prepare(sql)
122
+ else
123
+ stmt = conn.prepare(sql)
124
+ end
125
+
126
+ begin
127
+ result = ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
128
+ stmt.execute(*type_casted_binds)
129
+ end
130
+ verified!
131
+ result
132
+ rescue ::Mysql2::Error => e
133
+ if cache_stmt
134
+ @statements.delete(sql)
135
+ else
136
+ stmt.close
137
+ end
138
+ raise e
139
+ end
140
+
141
+ ret = yield stmt, result
142
+ result.free if result
143
+ stmt.close unless cache_stmt
144
+ ret
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
150
+ end
151
+ end