activerecord 7.0.0 → 7.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (251) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1701 -1039
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +18 -18
  5. data/lib/active_record/aggregations.rb +16 -13
  6. data/lib/active_record/association_relation.rb +1 -1
  7. data/lib/active_record/associations/association.rb +18 -3
  8. data/lib/active_record/associations/association_scope.rb +16 -9
  9. data/lib/active_record/associations/belongs_to_association.rb +14 -6
  10. data/lib/active_record/associations/builder/association.rb +3 -3
  11. data/lib/active_record/associations/builder/belongs_to.rb +21 -8
  12. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
  13. data/lib/active_record/associations/builder/singular_association.rb +4 -0
  14. data/lib/active_record/associations/collection_association.rb +17 -12
  15. data/lib/active_record/associations/collection_proxy.rb +22 -12
  16. data/lib/active_record/associations/foreign_association.rb +10 -3
  17. data/lib/active_record/associations/has_many_association.rb +27 -17
  18. data/lib/active_record/associations/has_many_through_association.rb +10 -6
  19. data/lib/active_record/associations/has_one_association.rb +10 -3
  20. data/lib/active_record/associations/join_dependency.rb +20 -14
  21. data/lib/active_record/associations/preloader/association.rb +27 -6
  22. data/lib/active_record/associations/preloader/through_association.rb +1 -1
  23. data/lib/active_record/associations/preloader.rb +13 -10
  24. data/lib/active_record/associations/singular_association.rb +1 -1
  25. data/lib/active_record/associations/through_association.rb +22 -11
  26. data/lib/active_record/associations.rb +362 -236
  27. data/lib/active_record/attribute_assignment.rb +0 -2
  28. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  29. data/lib/active_record/attribute_methods/dirty.rb +52 -34
  30. data/lib/active_record/attribute_methods/primary_key.rb +76 -24
  31. data/lib/active_record/attribute_methods/query.rb +28 -16
  32. data/lib/active_record/attribute_methods/read.rb +18 -5
  33. data/lib/active_record/attribute_methods/serialization.rb +172 -69
  34. data/lib/active_record/attribute_methods/write.rb +3 -3
  35. data/lib/active_record/attribute_methods.rb +110 -28
  36. data/lib/active_record/attributes.rb +3 -3
  37. data/lib/active_record/autosave_association.rb +56 -10
  38. data/lib/active_record/base.rb +10 -5
  39. data/lib/active_record/callbacks.rb +16 -32
  40. data/lib/active_record/coders/column_serializer.rb +61 -0
  41. data/lib/active_record/coders/json.rb +1 -1
  42. data/lib/active_record/coders/yaml_column.rb +70 -34
  43. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +164 -89
  44. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  45. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
  46. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +63 -43
  47. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  48. data/lib/active_record/connection_adapters/abstract/database_statements.rb +129 -31
  49. data/lib/active_record/connection_adapters/abstract/query_cache.rb +60 -22
  50. data/lib/active_record/connection_adapters/abstract/quoting.rb +52 -8
  51. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  52. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  53. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +163 -29
  54. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  55. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +302 -131
  56. data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
  57. data/lib/active_record/connection_adapters/abstract_adapter.rb +513 -106
  58. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +217 -104
  59. data/lib/active_record/connection_adapters/column.rb +9 -0
  60. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  61. data/lib/active_record/connection_adapters/mysql/database_statements.rb +23 -144
  62. data/lib/active_record/connection_adapters/mysql/quoting.rb +29 -12
  63. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  64. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  65. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
  66. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +38 -14
  67. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
  68. data/lib/active_record/connection_adapters/mysql2_adapter.rb +98 -53
  69. data/lib/active_record/connection_adapters/pool_config.rb +14 -5
  70. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  71. data/lib/active_record/connection_adapters/postgresql/column.rb +16 -3
  72. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +75 -45
  73. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  74. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +2 -2
  75. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  76. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  77. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +4 -2
  78. data/lib/active_record/connection_adapters/postgresql/quoting.rb +41 -8
  79. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +6 -10
  80. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  81. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
  82. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  83. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +372 -63
  84. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  85. data/lib/active_record/connection_adapters/postgresql_adapter.rb +359 -197
  86. data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
  87. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  88. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +52 -39
  89. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +22 -5
  90. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
  91. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +41 -22
  92. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +242 -81
  93. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  94. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  95. data/lib/active_record/connection_adapters/trilogy_adapter.rb +254 -0
  96. data/lib/active_record/connection_adapters.rb +3 -1
  97. data/lib/active_record/connection_handling.rb +73 -96
  98. data/lib/active_record/core.rb +142 -153
  99. data/lib/active_record/counter_cache.rb +46 -25
  100. data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -0
  101. data/lib/active_record/database_configurations/database_config.rb +9 -3
  102. data/lib/active_record/database_configurations/hash_config.rb +22 -12
  103. data/lib/active_record/database_configurations/url_config.rb +17 -11
  104. data/lib/active_record/database_configurations.rb +87 -34
  105. data/lib/active_record/delegated_type.rb +9 -4
  106. data/lib/active_record/deprecator.rb +7 -0
  107. data/lib/active_record/destroy_association_async_job.rb +2 -0
  108. data/lib/active_record/disable_joins_association_relation.rb +1 -1
  109. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  110. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  111. data/lib/active_record/encryption/config.rb +25 -1
  112. data/lib/active_record/encryption/configurable.rb +13 -14
  113. data/lib/active_record/encryption/context.rb +10 -3
  114. data/lib/active_record/encryption/contexts.rb +8 -4
  115. data/lib/active_record/encryption/derived_secret_key_provider.rb +9 -3
  116. data/lib/active_record/encryption/deterministic_key_provider.rb +1 -1
  117. data/lib/active_record/encryption/encryptable_record.rb +38 -22
  118. data/lib/active_record/encryption/encrypted_attribute_type.rb +19 -8
  119. data/lib/active_record/encryption/encryptor.rb +7 -7
  120. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +3 -3
  121. data/lib/active_record/encryption/extended_deterministic_queries.rb +83 -86
  122. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  123. data/lib/active_record/encryption/key_generator.rb +12 -1
  124. data/lib/active_record/encryption/message.rb +1 -1
  125. data/lib/active_record/encryption/message_serializer.rb +2 -0
  126. data/lib/active_record/encryption/properties.rb +4 -4
  127. data/lib/active_record/encryption/scheme.rb +20 -23
  128. data/lib/active_record/encryption.rb +1 -0
  129. data/lib/active_record/enum.rb +113 -29
  130. data/lib/active_record/errors.rb +108 -15
  131. data/lib/active_record/explain.rb +23 -3
  132. data/lib/active_record/explain_subscriber.rb +1 -1
  133. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  134. data/lib/active_record/fixture_set/render_context.rb +2 -0
  135. data/lib/active_record/fixture_set/table_row.rb +29 -8
  136. data/lib/active_record/fixtures.rb +121 -73
  137. data/lib/active_record/future_result.rb +30 -5
  138. data/lib/active_record/gem_version.rb +3 -3
  139. data/lib/active_record/inheritance.rb +30 -16
  140. data/lib/active_record/insert_all.rb +57 -10
  141. data/lib/active_record/integration.rb +10 -10
  142. data/lib/active_record/internal_metadata.rb +120 -30
  143. data/lib/active_record/locking/optimistic.rb +32 -18
  144. data/lib/active_record/locking/pessimistic.rb +8 -5
  145. data/lib/active_record/log_subscriber.rb +39 -17
  146. data/lib/active_record/marshalling.rb +56 -0
  147. data/lib/active_record/message_pack.rb +124 -0
  148. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  149. data/lib/active_record/middleware/database_selector.rb +18 -13
  150. data/lib/active_record/middleware/shard_selector.rb +7 -5
  151. data/lib/active_record/migration/command_recorder.rb +108 -10
  152. data/lib/active_record/migration/compatibility.rb +158 -64
  153. data/lib/active_record/migration/default_strategy.rb +23 -0
  154. data/lib/active_record/migration/execution_strategy.rb +19 -0
  155. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  156. data/lib/active_record/migration.rb +274 -117
  157. data/lib/active_record/model_schema.rb +86 -54
  158. data/lib/active_record/nested_attributes.rb +24 -6
  159. data/lib/active_record/normalization.rb +167 -0
  160. data/lib/active_record/persistence.rb +200 -47
  161. data/lib/active_record/promise.rb +84 -0
  162. data/lib/active_record/query_cache.rb +3 -21
  163. data/lib/active_record/query_logs.rb +87 -51
  164. data/lib/active_record/query_logs_formatter.rb +41 -0
  165. data/lib/active_record/querying.rb +16 -3
  166. data/lib/active_record/railtie.rb +128 -62
  167. data/lib/active_record/railties/controller_runtime.rb +12 -8
  168. data/lib/active_record/railties/databases.rake +145 -146
  169. data/lib/active_record/railties/job_runtime.rb +23 -0
  170. data/lib/active_record/readonly_attributes.rb +32 -5
  171. data/lib/active_record/reflection.rb +189 -45
  172. data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
  173. data/lib/active_record/relation/batches.rb +190 -61
  174. data/lib/active_record/relation/calculations.rb +208 -83
  175. data/lib/active_record/relation/delegation.rb +23 -9
  176. data/lib/active_record/relation/finder_methods.rb +77 -16
  177. data/lib/active_record/relation/merger.rb +2 -0
  178. data/lib/active_record/relation/predicate_builder/association_query_value.rb +31 -3
  179. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -6
  180. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  181. data/lib/active_record/relation/predicate_builder.rb +26 -14
  182. data/lib/active_record/relation/query_attribute.rb +25 -1
  183. data/lib/active_record/relation/query_methods.rb +430 -77
  184. data/lib/active_record/relation/spawn_methods.rb +18 -1
  185. data/lib/active_record/relation.rb +98 -41
  186. data/lib/active_record/result.rb +25 -9
  187. data/lib/active_record/runtime_registry.rb +10 -1
  188. data/lib/active_record/sanitization.rb +57 -16
  189. data/lib/active_record/schema.rb +36 -22
  190. data/lib/active_record/schema_dumper.rb +65 -23
  191. data/lib/active_record/schema_migration.rb +68 -33
  192. data/lib/active_record/scoping/default.rb +20 -12
  193. data/lib/active_record/scoping/named.rb +2 -2
  194. data/lib/active_record/scoping.rb +2 -1
  195. data/lib/active_record/secure_password.rb +60 -0
  196. data/lib/active_record/secure_token.rb +21 -3
  197. data/lib/active_record/serialization.rb +5 -0
  198. data/lib/active_record/signed_id.rb +9 -7
  199. data/lib/active_record/store.rb +16 -11
  200. data/lib/active_record/suppressor.rb +3 -1
  201. data/lib/active_record/table_metadata.rb +16 -3
  202. data/lib/active_record/tasks/database_tasks.rb +138 -107
  203. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  204. data/lib/active_record/tasks/postgresql_database_tasks.rb +17 -15
  205. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  206. data/lib/active_record/test_fixtures.rb +123 -99
  207. data/lib/active_record/timestamp.rb +27 -15
  208. data/lib/active_record/token_for.rb +113 -0
  209. data/lib/active_record/touch_later.rb +11 -6
  210. data/lib/active_record/transactions.rb +39 -13
  211. data/lib/active_record/translation.rb +1 -1
  212. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  213. data/lib/active_record/type/internal/timezone.rb +7 -2
  214. data/lib/active_record/type/serialized.rb +8 -4
  215. data/lib/active_record/type/time.rb +4 -0
  216. data/lib/active_record/validations/absence.rb +1 -1
  217. data/lib/active_record/validations/associated.rb +3 -3
  218. data/lib/active_record/validations/numericality.rb +5 -4
  219. data/lib/active_record/validations/presence.rb +5 -28
  220. data/lib/active_record/validations/uniqueness.rb +50 -5
  221. data/lib/active_record/validations.rb +8 -4
  222. data/lib/active_record/version.rb +1 -1
  223. data/lib/active_record.rb +143 -16
  224. data/lib/arel/errors.rb +10 -0
  225. data/lib/arel/factory_methods.rb +4 -0
  226. data/lib/arel/filter_predications.rb +1 -1
  227. data/lib/arel/nodes/and.rb +4 -0
  228. data/lib/arel/nodes/binary.rb +6 -1
  229. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  230. data/lib/arel/nodes/cte.rb +36 -0
  231. data/lib/arel/nodes/filter.rb +1 -1
  232. data/lib/arel/nodes/fragments.rb +35 -0
  233. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  234. data/lib/arel/nodes/leading_join.rb +8 -0
  235. data/lib/arel/nodes/node.rb +111 -2
  236. data/lib/arel/nodes/sql_literal.rb +6 -0
  237. data/lib/arel/nodes/table_alias.rb +4 -0
  238. data/lib/arel/nodes.rb +4 -0
  239. data/lib/arel/predications.rb +2 -0
  240. data/lib/arel/table.rb +9 -5
  241. data/lib/arel/visitors/mysql.rb +8 -1
  242. data/lib/arel/visitors/to_sql.rb +81 -17
  243. data/lib/arel/visitors/visitor.rb +2 -2
  244. data/lib/arel.rb +16 -2
  245. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  246. data/lib/rails/generators/active_record/migration.rb +3 -1
  247. data/lib/rails/generators/active_record/model/USAGE +113 -0
  248. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  249. metadata +51 -15
  250. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  251. data/lib/active_record/null_relation.rb +0 -63
@@ -4,138 +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)}"
23
+ def high_precision_current_timestamp
24
+ HIGH_PRECISION_CURRENT_TIMESTAMP
25
+ end
26
+
27
+ def explain(arel, binds = [], options = [])
28
+ sql = build_explain_clause(options) + " " + to_sql(arel, binds)
35
29
  start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
36
- result = exec_query(sql, "EXPLAIN", binds)
30
+ result = internal_exec_query(sql, "EXPLAIN", binds)
37
31
  elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
38
32
 
39
33
  MySQL::ExplainPrettyPrinter.new.pp(result, elapsed)
40
34
  end
41
35
 
42
- # Executes the SQL statement in the context of this connection.
43
- def execute(sql, name = nil, async: false)
44
- sql = transform_query(sql)
45
- check_if_write_query(sql)
36
+ def build_explain_clause(options = [])
37
+ return "EXPLAIN" if options.empty?
46
38
 
47
- raw_execute(sql, name, async: async)
48
- end
39
+ explain_clause = "EXPLAIN #{options.join(" ").upcase}"
49
40
 
50
- def exec_query(sql, name = "SQL", binds = [], prepare: false, async: false) # :nodoc:
51
- if without_prepared_statement?(binds)
52
- execute_and_free(sql, name, async: async) do |result|
53
- if result
54
- build_result(columns: result.fields, rows: result.to_a)
55
- else
56
- build_result(columns: [], rows: [])
57
- end
58
- end
59
- else
60
- exec_stmt_and_free(sql, name, binds, cache_stmt: prepare, async: async) do |_, result|
61
- if result
62
- build_result(columns: result.fields, rows: result.to_a)
63
- else
64
- build_result(columns: [], rows: [])
65
- end
66
- end
67
- end
68
- end
69
-
70
- def exec_delete(sql, name = nil, binds = []) # :nodoc:
71
- if without_prepared_statement?(binds)
72
- @lock.synchronize do
73
- execute_and_free(sql, name) { @connection.affected_rows }
74
- end
41
+ if analyze_without_explain? && explain_clause.include?("ANALYZE")
42
+ explain_clause.sub("EXPLAIN ", "")
75
43
  else
76
- exec_stmt_and_free(sql, name, binds) { |stmt| stmt.affected_rows }
44
+ explain_clause
77
45
  end
78
46
  end
79
- alias :exec_update :exec_delete
80
-
81
- # https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_current-timestamp
82
- # https://dev.mysql.com/doc/refman/5.7/en/date-and-time-type-syntax.html
83
- HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP(6)").freeze # :nodoc:
84
- private_constant :HIGH_PRECISION_CURRENT_TIMESTAMP
85
-
86
- def high_precision_current_timestamp
87
- HIGH_PRECISION_CURRENT_TIMESTAMP
88
- end
89
47
 
90
48
  private
91
- def raw_execute(sql, name, async: false)
92
- # make sure we carry over any changes to ActiveRecord.default_timezone that have been
93
- # made since we established the connection
94
- @connection.query_options[:database_timezone] = ActiveRecord.default_timezone
95
-
96
- super
97
- end
98
-
99
- def execute_batch(statements, name = nil)
100
- statements = statements.map { |sql| transform_query(sql) }
101
- combine_multi_statements(statements).each do |statement|
102
- raw_execute(statement, name)
103
- end
104
- @connection.abandon_results!
49
+ # https://mariadb.com/kb/en/analyze-statement/
50
+ def analyze_without_explain?
51
+ mariadb? && database_version >= "10.1.0"
105
52
  end
106
53
 
107
54
  def default_insert_value(column)
108
55
  super unless column.auto_increment?
109
56
  end
110
57
 
111
- def last_inserted_id(result)
112
- @connection.last_id
113
- end
114
-
115
- def multi_statements_enabled?
116
- flags = @config[:flags]
117
-
118
- if flags.is_a?(Array)
119
- flags.include?("MULTI_STATEMENTS")
120
- else
121
- flags.anybits?(Mysql2::Client::MULTI_STATEMENTS)
122
- end
123
- end
124
-
125
- def with_multi_statements
126
- multi_statements_was = multi_statements_enabled?
127
-
128
- unless multi_statements_was
129
- @connection.set_server_option(Mysql2::Client::OPTION_MULTI_STATEMENTS_ON)
130
- end
131
-
132
- yield
133
- ensure
134
- unless multi_statements_was
135
- @connection.set_server_option(Mysql2::Client::OPTION_MULTI_STATEMENTS_OFF)
136
- end
137
- end
138
-
139
58
  def combine_multi_statements(total_sql)
140
59
  total_sql.each_with_object([]) do |sql, total_sql_chunks|
141
60
  previous_packet = total_sql_chunks.last
@@ -162,46 +81,6 @@ module ActiveRecord
162
81
  def max_allowed_packet
163
82
  @max_allowed_packet ||= show_variable("max_allowed_packet")
164
83
  end
165
-
166
- def exec_stmt_and_free(sql, name, binds, cache_stmt: false, async: false)
167
- sql = transform_query(sql)
168
- check_if_write_query(sql)
169
-
170
- materialize_transactions
171
- mark_transaction_written_if_write(sql)
172
-
173
- # make sure we carry over any changes to ActiveRecord.default_timezone that have been
174
- # made since we established the connection
175
- @connection.query_options[:database_timezone] = ActiveRecord.default_timezone
176
-
177
- type_casted_binds = type_casted_binds(binds)
178
-
179
- log(sql, name, binds, type_casted_binds, async: async) do
180
- if cache_stmt
181
- stmt = @statements[sql] ||= @connection.prepare(sql)
182
- else
183
- stmt = @connection.prepare(sql)
184
- end
185
-
186
- begin
187
- result = ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
188
- stmt.execute(*type_casted_binds)
189
- end
190
- rescue Mysql2::Error => e
191
- if cache_stmt
192
- @statements.delete(sql)
193
- else
194
- stmt.close
195
- end
196
- raise e
197
- end
198
-
199
- ret = yield stmt, result
200
- result.free if result
201
- stmt.close unless cache_stmt
202
- ret
203
- end
204
- end
205
84
  end
206
85
  end
207
86
  end
@@ -6,27 +6,35 @@ module ActiveRecord
6
6
  module ConnectionAdapters
7
7
  module MySQL
8
8
  module Quoting # :nodoc:
9
- def quote_bound_value(value)
9
+ QUOTED_COLUMN_NAMES = Concurrent::Map.new # :nodoc:
10
+ QUOTED_TABLE_NAMES = Concurrent::Map.new # :nodoc:
11
+
12
+ def cast_bound_value(value)
10
13
  case value
14
+ when Rational
15
+ value.to_f.to_s
11
16
  when Numeric
12
- quote(value.to_s)
17
+ value.to_s
13
18
  when BigDecimal
14
- quote(value.to_s("F"))
19
+ value.to_s("F")
15
20
  when true
16
- "'1'"
21
+ "1"
17
22
  when false
18
- "'0'"
23
+ "0"
24
+ when ActiveSupport::Duration
25
+ warn_quote_duration_deprecated
26
+ value.to_s
19
27
  else
20
- quote(value)
28
+ value
21
29
  end
22
30
  end
23
31
 
24
32
  def quote_column_name(name)
25
- self.class.quoted_column_names[name] ||= "`#{super.gsub('`', '``')}`"
33
+ QUOTED_COLUMN_NAMES[name] ||= "`#{super.gsub('`', '``')}`"
26
34
  end
27
35
 
28
36
  def quote_table_name(name)
29
- self.class.quoted_table_names[name] ||= super.gsub(".", "`.`").freeze
37
+ QUOTED_TABLE_NAMES[name] ||= super.gsub(".", "`.`").freeze
30
38
  end
31
39
 
32
40
  def unquoted_true
@@ -49,15 +57,23 @@ module ActiveRecord
49
57
  "x'#{value.hex}'"
50
58
  end
51
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
+
52
68
  # Override +type_cast+ we pass to mysql2 Date and Time objects instead
53
- # of Strings since mysql2 is able to handle those classes more efficiently.
69
+ # of Strings since MySQL adapters are able to handle those classes more efficiently.
54
70
  def type_cast(value) # :nodoc:
55
71
  case value
56
72
  when ActiveSupport::TimeWithZone
57
73
  # We need to check explicitly for ActiveSupport::TimeWithZone because
58
74
  # we need to transform it to Time objects but we don't want to
59
75
  # transform Time objects to themselves.
60
- if ActiveRecord.default_timezone == :utc
76
+ if default_timezone == :utc
61
77
  value.getutc
62
78
  else
63
79
  value.getlocal
@@ -82,7 +98,7 @@ module ActiveRecord
82
98
  (
83
99
  (?:
84
100
  # `table_name`.`column_name` | function(one or no argument)
85
- ((?:\w+\.|`\w+`\.)?(?:\w+|`\w+`)) | \w+\((?:|\g<2>)\)
101
+ ((?:\w+\.|`\w+`\.)?(?:\w+|`\w+`) | \w+\((?:|\g<2>)\))
86
102
  )
87
103
  (?:(?:\s+AS)?\s+(?:\w+|`\w+`))?
88
104
  )
@@ -95,8 +111,9 @@ module ActiveRecord
95
111
  (
96
112
  (?:
97
113
  # `table_name`.`column_name` | function(one or no argument)
98
- ((?:\w+\.|`\w+`\.)?(?:\w+|`\w+`)) | \w+\((?:|\g<2>)\)
114
+ ((?:\w+\.|`\w+`\.)?(?:\w+|`\w+`) | \w+\((?:|\g<2>)\))
99
115
  )
116
+ (?:\s+COLLATE\s+(?:\w+|"\w+"))?
100
117
  (?:\s+ASC|\s+DESC)?
101
118
  )
102
119
  (?:\s*,\s*\g<1>)*
@@ -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,45 @@ 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]
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?("(")
169
188
  default, default_function = nil, default
170
- elsif type_metadata.type == :text && default
189
+ elsif type_metadata.type == :text && default&.start_with?("'")
171
190
  # strip and unescape quotes
172
191
  default = default[1...-1].gsub("\\'", "'")
192
+ elsif default&.match?(/\A\d/)
193
+ # Its a number so we can skip the query to check if it is a function
194
+ elsif default && default_type(table_name, field_name) == :function
195
+ default, default_function = nil, default
173
196
  end
174
197
 
175
198
  MySQL::Column.new(
@@ -206,14 +229,15 @@ module ActiveRecord
206
229
  def data_source_sql(name = nil, type: nil)
207
230
  scope = quoted_scope(name, type: type)
208
231
 
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 ")}"
232
+ sql = +"SELECT table_name FROM information_schema.tables"
233
+ sql << " WHERE table_schema = #{scope[:schema]}"
234
+
235
+ if scope[:name]
236
+ sql << " AND table_name = #{scope[:name]}"
237
+ sql << " AND table_name IN (SELECT table_name FROM information_schema.tables WHERE table_schema = #{scope[:schema]})"
216
238
  end
239
+
240
+ sql << " AND table_type = #{scope[:type]}" if scope[:type]
217
241
  sql
218
242
  end
219
243
 
@@ -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