activerecord 6.1.7.10 → 7.0.0.alpha1

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 (220) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +726 -1404
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +1 -1
  5. data/lib/active_record/aggregations.rb +1 -1
  6. data/lib/active_record/association_relation.rb +0 -10
  7. data/lib/active_record/associations/association.rb +31 -9
  8. data/lib/active_record/associations/association_scope.rb +1 -3
  9. data/lib/active_record/associations/belongs_to_association.rb +15 -4
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +10 -2
  11. data/lib/active_record/associations/builder/association.rb +8 -2
  12. data/lib/active_record/associations/builder/belongs_to.rb +19 -6
  13. data/lib/active_record/associations/builder/collection_association.rb +1 -1
  14. data/lib/active_record/associations/builder/has_many.rb +3 -2
  15. data/lib/active_record/associations/builder/has_one.rb +2 -1
  16. data/lib/active_record/associations/builder/singular_association.rb +2 -2
  17. data/lib/active_record/associations/collection_association.rb +14 -23
  18. data/lib/active_record/associations/collection_proxy.rb +8 -3
  19. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  20. data/lib/active_record/associations/has_many_association.rb +1 -1
  21. data/lib/active_record/associations/has_many_through_association.rb +2 -1
  22. data/lib/active_record/associations/has_one_association.rb +10 -7
  23. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  24. data/lib/active_record/associations/preloader/association.rb +161 -47
  25. data/lib/active_record/associations/preloader/batch.rb +51 -0
  26. data/lib/active_record/associations/preloader/branch.rb +147 -0
  27. data/lib/active_record/associations/preloader/through_association.rb +37 -11
  28. data/lib/active_record/associations/preloader.rb +46 -110
  29. data/lib/active_record/associations/singular_association.rb +8 -2
  30. data/lib/active_record/associations/through_association.rb +1 -1
  31. data/lib/active_record/associations.rb +76 -81
  32. data/lib/active_record/asynchronous_queries_tracker.rb +57 -0
  33. data/lib/active_record/attribute_assignment.rb +1 -1
  34. data/lib/active_record/attribute_methods/before_type_cast.rb +7 -2
  35. data/lib/active_record/attribute_methods/dirty.rb +41 -16
  36. data/lib/active_record/attribute_methods/primary_key.rb +2 -2
  37. data/lib/active_record/attribute_methods/query.rb +2 -2
  38. data/lib/active_record/attribute_methods/read.rb +7 -5
  39. data/lib/active_record/attribute_methods/serialization.rb +66 -12
  40. data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -3
  41. data/lib/active_record/attribute_methods/write.rb +7 -10
  42. data/lib/active_record/attribute_methods.rb +6 -9
  43. data/lib/active_record/attributes.rb +24 -35
  44. data/lib/active_record/autosave_association.rb +3 -18
  45. data/lib/active_record/base.rb +19 -1
  46. data/lib/active_record/callbacks.rb +2 -2
  47. data/lib/active_record/coders/yaml_column.rb +2 -14
  48. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +312 -0
  49. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +209 -0
  50. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +76 -0
  51. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +31 -558
  52. data/lib/active_record/connection_adapters/abstract/database_statements.rb +45 -21
  53. data/lib/active_record/connection_adapters/abstract/query_cache.rb +24 -12
  54. data/lib/active_record/connection_adapters/abstract/quoting.rb +12 -14
  55. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +4 -17
  56. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +30 -13
  57. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +60 -16
  58. data/lib/active_record/connection_adapters/abstract/transaction.rb +3 -3
  59. data/lib/active_record/connection_adapters/abstract_adapter.rb +112 -66
  60. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +96 -81
  61. data/lib/active_record/connection_adapters/mysql/database_statements.rb +33 -23
  62. data/lib/active_record/connection_adapters/mysql/quoting.rb +16 -1
  63. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +1 -1
  64. data/lib/active_record/connection_adapters/mysql2_adapter.rb +12 -6
  65. data/lib/active_record/connection_adapters/pool_config.rb +1 -3
  66. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +19 -14
  67. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
  68. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
  69. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
  70. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
  71. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  72. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +28 -0
  73. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  74. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  75. data/lib/active_record/connection_adapters/postgresql/quoting.rb +6 -32
  76. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +32 -0
  77. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +5 -1
  78. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +12 -12
  79. data/lib/active_record/connection_adapters/postgresql_adapter.rb +159 -102
  80. data/lib/active_record/connection_adapters/schema_cache.rb +36 -37
  81. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +23 -19
  82. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +4 -2
  83. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +61 -30
  84. data/lib/active_record/connection_adapters.rb +6 -5
  85. data/lib/active_record/connection_handling.rb +20 -38
  86. data/lib/active_record/core.rb +111 -125
  87. data/lib/active_record/database_configurations/connection_url_resolver.rb +0 -1
  88. data/lib/active_record/database_configurations/database_config.rb +12 -0
  89. data/lib/active_record/database_configurations/hash_config.rb +27 -1
  90. data/lib/active_record/database_configurations/url_config.rb +2 -2
  91. data/lib/active_record/database_configurations.rb +17 -9
  92. data/lib/active_record/delegated_type.rb +33 -11
  93. data/lib/active_record/destroy_association_async_job.rb +1 -1
  94. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  95. data/lib/active_record/dynamic_matchers.rb +1 -1
  96. data/lib/active_record/encryption/cipher/aes256_gcm.rb +98 -0
  97. data/lib/active_record/encryption/cipher.rb +53 -0
  98. data/lib/active_record/encryption/config.rb +44 -0
  99. data/lib/active_record/encryption/configurable.rb +61 -0
  100. data/lib/active_record/encryption/context.rb +35 -0
  101. data/lib/active_record/encryption/contexts.rb +72 -0
  102. data/lib/active_record/encryption/derived_secret_key_provider.rb +12 -0
  103. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  104. data/lib/active_record/encryption/encryptable_record.rb +208 -0
  105. data/lib/active_record/encryption/encrypted_attribute_type.rb +140 -0
  106. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  107. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  108. data/lib/active_record/encryption/encryptor.rb +155 -0
  109. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  110. data/lib/active_record/encryption/errors.rb +15 -0
  111. data/lib/active_record/encryption/extended_deterministic_queries.rb +160 -0
  112. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +29 -0
  113. data/lib/active_record/encryption/key.rb +28 -0
  114. data/lib/active_record/encryption/key_generator.rb +42 -0
  115. data/lib/active_record/encryption/key_provider.rb +46 -0
  116. data/lib/active_record/encryption/message.rb +33 -0
  117. data/lib/active_record/encryption/message_serializer.rb +80 -0
  118. data/lib/active_record/encryption/null_encryptor.rb +21 -0
  119. data/lib/active_record/encryption/properties.rb +76 -0
  120. data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
  121. data/lib/active_record/encryption/scheme.rb +99 -0
  122. data/lib/active_record/encryption.rb +55 -0
  123. data/lib/active_record/enum.rb +41 -41
  124. data/lib/active_record/errors.rb +66 -3
  125. data/lib/active_record/fixture_set/file.rb +15 -1
  126. data/lib/active_record/fixture_set/table_row.rb +40 -5
  127. data/lib/active_record/fixture_set/table_rows.rb +4 -4
  128. data/lib/active_record/fixtures.rb +16 -11
  129. data/lib/active_record/future_result.rb +139 -0
  130. data/lib/active_record/gem_version.rb +4 -4
  131. data/lib/active_record/inheritance.rb +55 -17
  132. data/lib/active_record/insert_all.rb +34 -5
  133. data/lib/active_record/integration.rb +1 -1
  134. data/lib/active_record/internal_metadata.rb +1 -5
  135. data/lib/active_record/locking/optimistic.rb +10 -9
  136. data/lib/active_record/log_subscriber.rb +6 -2
  137. data/lib/active_record/middleware/database_selector/resolver.rb +6 -10
  138. data/lib/active_record/middleware/database_selector.rb +8 -3
  139. data/lib/active_record/migration/command_recorder.rb +4 -4
  140. data/lib/active_record/migration/compatibility.rb +89 -10
  141. data/lib/active_record/migration/join_table.rb +1 -1
  142. data/lib/active_record/migration.rb +109 -79
  143. data/lib/active_record/model_schema.rb +45 -31
  144. data/lib/active_record/nested_attributes.rb +3 -3
  145. data/lib/active_record/no_touching.rb +2 -2
  146. data/lib/active_record/null_relation.rb +2 -6
  147. data/lib/active_record/persistence.rb +134 -45
  148. data/lib/active_record/query_cache.rb +2 -2
  149. data/lib/active_record/query_logs.rb +203 -0
  150. data/lib/active_record/querying.rb +15 -5
  151. data/lib/active_record/railtie.rb +117 -17
  152. data/lib/active_record/railties/controller_runtime.rb +1 -1
  153. data/lib/active_record/railties/databases.rake +72 -48
  154. data/lib/active_record/readonly_attributes.rb +11 -0
  155. data/lib/active_record/reflection.rb +45 -44
  156. data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
  157. data/lib/active_record/relation/batches.rb +3 -3
  158. data/lib/active_record/relation/calculations.rb +39 -26
  159. data/lib/active_record/relation/delegation.rb +6 -6
  160. data/lib/active_record/relation/finder_methods.rb +31 -22
  161. data/lib/active_record/relation/merger.rb +20 -13
  162. data/lib/active_record/relation/predicate_builder.rb +1 -6
  163. data/lib/active_record/relation/query_attribute.rb +5 -11
  164. data/lib/active_record/relation/query_methods.rb +230 -49
  165. data/lib/active_record/relation/record_fetch_warning.rb +2 -2
  166. data/lib/active_record/relation/spawn_methods.rb +2 -2
  167. data/lib/active_record/relation/where_clause.rb +8 -4
  168. data/lib/active_record/relation.rb +166 -77
  169. data/lib/active_record/result.rb +17 -2
  170. data/lib/active_record/runtime_registry.rb +2 -4
  171. data/lib/active_record/sanitization.rb +11 -7
  172. data/lib/active_record/schema_dumper.rb +3 -3
  173. data/lib/active_record/schema_migration.rb +0 -4
  174. data/lib/active_record/scoping/default.rb +61 -12
  175. data/lib/active_record/scoping/named.rb +3 -11
  176. data/lib/active_record/scoping.rb +40 -22
  177. data/lib/active_record/serialization.rb +1 -1
  178. data/lib/active_record/signed_id.rb +1 -1
  179. data/lib/active_record/store.rb +1 -6
  180. data/lib/active_record/tasks/database_tasks.rb +106 -22
  181. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  182. data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -11
  183. data/lib/active_record/test_databases.rb +1 -1
  184. data/lib/active_record/test_fixtures.rb +9 -13
  185. data/lib/active_record/timestamp.rb +3 -4
  186. data/lib/active_record/transactions.rb +9 -14
  187. data/lib/active_record/translation.rb +2 -2
  188. data/lib/active_record/type/adapter_specific_registry.rb +32 -7
  189. data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
  190. data/lib/active_record/type/internal/timezone.rb +2 -2
  191. data/lib/active_record/type/serialized.rb +1 -1
  192. data/lib/active_record/type/type_map.rb +17 -20
  193. data/lib/active_record/type.rb +1 -2
  194. data/lib/active_record/validations/associated.rb +1 -1
  195. data/lib/active_record.rb +170 -2
  196. data/lib/arel/attributes/attribute.rb +0 -8
  197. data/lib/arel/crud.rb +18 -22
  198. data/lib/arel/delete_manager.rb +2 -4
  199. data/lib/arel/insert_manager.rb +2 -3
  200. data/lib/arel/nodes/casted.rb +1 -1
  201. data/lib/arel/nodes/delete_statement.rb +8 -13
  202. data/lib/arel/nodes/insert_statement.rb +2 -2
  203. data/lib/arel/nodes/select_core.rb +2 -2
  204. data/lib/arel/nodes/select_statement.rb +2 -2
  205. data/lib/arel/nodes/update_statement.rb +3 -2
  206. data/lib/arel/predications.rb +1 -1
  207. data/lib/arel/select_manager.rb +10 -4
  208. data/lib/arel/table.rb +0 -1
  209. data/lib/arel/tree_manager.rb +0 -12
  210. data/lib/arel/update_manager.rb +2 -4
  211. data/lib/arel/visitors/dot.rb +80 -90
  212. data/lib/arel/visitors/mysql.rb +6 -1
  213. data/lib/arel/visitors/postgresql.rb +0 -10
  214. data/lib/arel/visitors/to_sql.rb +43 -2
  215. data/lib/arel.rb +1 -1
  216. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  217. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  218. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  219. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  220. metadata +52 -14
@@ -26,8 +26,6 @@ module ActiveRecord
26
26
 
27
27
  def write_query?(sql) # :nodoc:
28
28
  !READ_QUERY.match?(sql)
29
- rescue ArgumentError # Invalid encoding
30
- !READ_QUERY.match?(sql.b)
31
29
  end
32
30
 
33
31
  def explain(arel, binds = [])
@@ -40,21 +38,16 @@ module ActiveRecord
40
38
  end
41
39
 
42
40
  # 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
41
+ def execute(sql, name = nil, async: false)
42
+ sql = transform_query(sql)
43
+ check_if_write_query(sql)
51
44
 
52
- super
45
+ raw_execute(sql, name, async: async)
53
46
  end
54
47
 
55
- def exec_query(sql, name = "SQL", binds = [], prepare: false)
48
+ def exec_query(sql, name = "SQL", binds = [], prepare: false, async: false) # :nodoc:
56
49
  if without_prepared_statement?(binds)
57
- execute_and_free(sql, name) do |result|
50
+ execute_and_free(sql, name, async: async) do |result|
58
51
  if result
59
52
  build_result(columns: result.fields, rows: result.to_a)
60
53
  else
@@ -62,7 +55,7 @@ module ActiveRecord
62
55
  end
63
56
  end
64
57
  else
65
- exec_stmt_and_free(sql, name, binds, cache_stmt: prepare) do |_, result|
58
+ exec_stmt_and_free(sql, name, binds, cache_stmt: prepare, async: async) do |_, result|
66
59
  if result
67
60
  build_result(columns: result.fields, rows: result.to_a)
68
61
  else
@@ -72,7 +65,7 @@ module ActiveRecord
72
65
  end
73
66
  end
74
67
 
75
- def exec_delete(sql, name = nil, binds = [])
68
+ def exec_delete(sql, name = nil, binds = []) # :nodoc:
76
69
  if without_prepared_statement?(binds)
77
70
  @lock.synchronize do
78
71
  execute_and_free(sql, name) { @connection.affected_rows }
@@ -83,10 +76,28 @@ module ActiveRecord
83
76
  end
84
77
  alias :exec_update :exec_delete
85
78
 
79
+ # https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_current-timestamp
80
+ # https://dev.mysql.com/doc/refman/5.7/en/date-and-time-type-syntax.html
81
+ HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP(6)").freeze # :nodoc:
82
+ private_constant :HIGH_PRECISION_CURRENT_TIMESTAMP
83
+
84
+ def high_precision_current_timestamp
85
+ HIGH_PRECISION_CURRENT_TIMESTAMP
86
+ end
87
+
86
88
  private
89
+ def raw_execute(sql, name, async: false)
90
+ # make sure we carry over any changes to ActiveRecord.default_timezone that have been
91
+ # made since we established the connection
92
+ @connection.query_options[:database_timezone] = ActiveRecord.default_timezone
93
+
94
+ super
95
+ end
96
+
87
97
  def execute_batch(statements, name = nil)
98
+ statements = statements.map { |sql| transform_query(sql) }
88
99
  combine_multi_statements(statements).each do |statement|
89
- execute(statement, name)
100
+ raw_execute(statement, name)
90
101
  end
91
102
  @connection.abandon_results!
92
103
  end
@@ -150,21 +161,20 @@ module ActiveRecord
150
161
  @max_allowed_packet ||= show_variable("max_allowed_packet")
151
162
  end
152
163
 
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
164
+ def exec_stmt_and_free(sql, name, binds, cache_stmt: false, async: false)
165
+ sql = transform_query(sql)
166
+ check_if_write_query(sql)
157
167
 
158
168
  materialize_transactions
159
169
  mark_transaction_written_if_write(sql)
160
170
 
161
- # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
171
+ # make sure we carry over any changes to ActiveRecord.default_timezone that have been
162
172
  # made since we established the connection
163
- @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone
173
+ @connection.query_options[:database_timezone] = ActiveRecord.default_timezone
164
174
 
165
175
  type_casted_binds = type_casted_binds(binds)
166
176
 
167
- log(sql, name, binds, type_casted_binds) do
177
+ log(sql, name, binds, type_casted_binds, async: async) do
168
178
  if cache_stmt
169
179
  stmt = @statements[sql] ||= @connection.prepare(sql)
170
180
  else
@@ -6,6 +6,21 @@ module ActiveRecord
6
6
  module ConnectionAdapters
7
7
  module MySQL
8
8
  module Quoting # :nodoc:
9
+ def quote_bound_value(value)
10
+ case value
11
+ when Numeric
12
+ _quote(value.to_s)
13
+ when BigDecimal
14
+ _quote(value.to_s("F"))
15
+ when true
16
+ "'1'"
17
+ when false
18
+ "'0'"
19
+ else
20
+ _quote(value)
21
+ end
22
+ end
23
+
9
24
  def quote_column_name(name)
10
25
  self.class.quoted_column_names[name] ||= "`#{super.gsub('`', '``')}`"
11
26
  end
@@ -79,7 +94,7 @@ module ActiveRecord
79
94
  # We need to check explicitly for ActiveSupport::TimeWithZone because
80
95
  # we need to transform it to Time objects but we don't want to
81
96
  # transform Time objects to themselves.
82
- if ActiveRecord::Base.default_timezone == :utc
97
+ if ActiveRecord.default_timezone == :utc
83
98
  value.getutc
84
99
  else
85
100
  value.getlocal
@@ -206,7 +206,7 @@ module ActiveRecord
206
206
  def data_source_sql(name = nil, type: nil)
207
207
  scope = quoted_scope(name, type: type)
208
208
 
209
- sql = +"SELECT table_name FROM (SELECT table_name, table_type FROM information_schema.tables "
209
+ sql = +"SELECT table_name FROM (SELECT * FROM information_schema.tables "
210
210
  sql << " WHERE table_schema = #{scope[:schema]}) _subquery"
211
211
  if scope[:type] || scope[:name]
212
212
  conditions = []
@@ -30,7 +30,11 @@ module ActiveRecord
30
30
 
31
31
  module ConnectionAdapters
32
32
  class Mysql2Adapter < AbstractMysqlAdapter
33
- ER_BAD_DB_ERROR = 1049
33
+ ER_BAD_DB_ERROR = 1049
34
+ ER_ACCESS_DENIED_ERROR = 1045
35
+ ER_CONN_HOST_ERROR = 2003
36
+ ER_UNKNOWN_HOST_ERROR = 2005
37
+
34
38
  ADAPTER_NAME = "Mysql2"
35
39
 
36
40
  include MySQL::DatabaseStatements
@@ -40,7 +44,11 @@ module ActiveRecord
40
44
  Mysql2::Client.new(config)
41
45
  rescue Mysql2::Error => error
42
46
  if error.error_number == ConnectionAdapters::Mysql2Adapter::ER_BAD_DB_ERROR
43
- raise ActiveRecord::NoDatabaseError
47
+ raise ActiveRecord::NoDatabaseError.db_error(config[:database])
48
+ elsif error.error_number == ConnectionAdapters::Mysql2Adapter::ER_ACCESS_DENIED_ERROR
49
+ raise ActiveRecord::DatabaseConnectionError.username_error(config[:username])
50
+ elsif [ConnectionAdapters::Mysql2Adapter::ER_CONN_HOST_ERROR, ConnectionAdapters::Mysql2Adapter::ER_UNKNOWN_HOST_ERROR].include?(error.error_number)
51
+ raise ActiveRecord::DatabaseConnectionError.hostname_error(config[:host])
44
52
  else
45
53
  raise ActiveRecord::ConnectionNotEstablished, error.message
46
54
  end
@@ -81,11 +89,9 @@ module ActiveRecord
81
89
 
82
90
  # HELPER METHODS ===========================================
83
91
 
84
- def each_hash(result) # :nodoc:
92
+ def each_hash(result, &block) # :nodoc:
85
93
  if block_given?
86
- result.each(as: :hash, symbolize_keys: true) do |row|
87
- yield row
88
- end
94
+ result.each(as: :hash, symbolize_keys: true, &block)
89
95
  else
90
96
  to_enum(:each_hash, result)
91
97
  end
@@ -26,9 +26,7 @@ module ActiveRecord
26
26
  end
27
27
 
28
28
  def connection_specification_name
29
- if connection_klass.is_a?(String)
30
- connection_klass
31
- elsif connection_klass.primary_class?
29
+ if connection_klass.primary_class?
32
30
  "ActiveRecord::Base"
33
31
  else
34
32
  connection_klass.name
@@ -10,7 +10,7 @@ module ActiveRecord
10
10
  end
11
11
 
12
12
  # Queries the database and returns the results in an Array-like object
13
- def query(sql, name = nil) #:nodoc:
13
+ def query(sql, name = nil) # :nodoc:
14
14
  materialize_transactions
15
15
  mark_transaction_written_if_write(sql)
16
16
 
@@ -28,8 +28,6 @@ module ActiveRecord
28
28
 
29
29
  def write_query?(sql) # :nodoc:
30
30
  !READ_QUERY.match?(sql)
31
- rescue ArgumentError # Invalid encoding
32
- !READ_QUERY.match?(sql.b)
33
31
  end
34
32
 
35
33
  # Executes an SQL statement, returning a PG::Result object on success
@@ -37,9 +35,8 @@ module ActiveRecord
37
35
  # Note: the PG::Result object is manually memory managed; if you don't
38
36
  # need it specifically, you may want consider the <tt>exec_query</tt> wrapper.
39
37
  def execute(sql, name = nil)
40
- if preventing_writes? && write_query?(sql)
41
- raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
42
- end
38
+ sql = transform_query(sql)
39
+ check_if_write_query(sql)
43
40
 
44
41
  materialize_transactions
45
42
  mark_transaction_written_if_write(sql)
@@ -51,8 +48,8 @@ module ActiveRecord
51
48
  end
52
49
  end
53
50
 
54
- def exec_query(sql, name = "SQL", binds = [], prepare: false)
55
- execute_and_clear(sql, name, binds, prepare: prepare) do |result|
51
+ def exec_query(sql, name = "SQL", binds = [], prepare: false, async: false) # :nodoc:
52
+ execute_and_clear(sql, name, binds, prepare: prepare, async: async) do |result|
56
53
  types = {}
57
54
  fields = result.fields
58
55
  fields.each_with_index do |fname, i|
@@ -68,7 +65,7 @@ module ActiveRecord
68
65
  end
69
66
  end
70
67
 
71
- def exec_delete(sql, name = nil, binds = [])
68
+ def exec_delete(sql, name = nil, binds = []) # :nodoc:
72
69
  execute_and_clear(sql, name, binds) { |result| result.cmd_tuples }
73
70
  end
74
71
  alias :exec_update :exec_delete
@@ -88,7 +85,7 @@ module ActiveRecord
88
85
  end
89
86
  private :sql_for_insert
90
87
 
91
- def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil)
88
+ def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil) # :nodoc:
92
89
  if use_insert_returning? || pk == false
93
90
  super
94
91
  else
@@ -107,25 +104,33 @@ module ActiveRecord
107
104
  end
108
105
 
109
106
  # Begins a transaction.
110
- def begin_db_transaction
107
+ def begin_db_transaction # :nodoc:
111
108
  execute("BEGIN", "TRANSACTION")
112
109
  end
113
110
 
114
- def begin_isolated_db_transaction(isolation)
111
+ def begin_isolated_db_transaction(isolation) # :nodoc:
115
112
  begin_db_transaction
116
113
  execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}"
117
114
  end
118
115
 
119
116
  # Commits a transaction.
120
- def commit_db_transaction
117
+ def commit_db_transaction # :nodoc:
121
118
  execute("COMMIT", "TRANSACTION")
122
119
  end
123
120
 
124
121
  # Aborts a transaction.
125
- def exec_rollback_db_transaction
122
+ def exec_rollback_db_transaction # :nodoc:
126
123
  execute("ROLLBACK", "TRANSACTION")
127
124
  end
128
125
 
126
+ # From https://www.postgresql.org/docs/current/functions-datetime.html#FUNCTIONS-DATETIME-CURRENT
127
+ HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP").freeze # :nodoc:
128
+ private_constant :HIGH_PRECISION_CURRENT_TIMESTAMP
129
+
130
+ def high_precision_current_timestamp
131
+ HIGH_PRECISION_CURRENT_TIMESTAMP
132
+ end
133
+
129
134
  private
130
135
  def execute_batch(statements, name = nil)
131
136
  execute(combine_multi_statements(statements))
@@ -16,6 +16,14 @@ module ActiveRecord
16
16
  super
17
17
  end
18
18
  end
19
+
20
+ def type_cast_for_schema(value)
21
+ case value
22
+ when ::Float::INFINITY then "::Float::INFINITY"
23
+ when -::Float::INFINITY then "-::Float::INFINITY"
24
+ else super
25
+ end
26
+ end
19
27
  end
20
28
  end
21
29
  end
@@ -24,6 +24,11 @@ module ActiveRecord
24
24
  else super
25
25
  end
26
26
  end
27
+
28
+ protected
29
+ def real_type_unless_aliased(real_type)
30
+ ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.datetime_type == real_type ? :datetime : real_type
31
+ end
27
32
  end
28
33
  end
29
34
  end
@@ -1,10 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "strscan"
4
+
3
5
  module ActiveRecord
4
6
  module ConnectionAdapters
5
7
  module PostgreSQL
6
8
  module OID # :nodoc:
7
9
  class Hstore < Type::Value # :nodoc:
10
+ ERROR = "Invalid Hstore document: %s"
11
+
8
12
  include ActiveModel::Type::Helpers::Mutable
9
13
 
10
14
  def type
@@ -12,15 +16,56 @@ module ActiveRecord
12
16
  end
13
17
 
14
18
  def deserialize(value)
15
- if value.is_a?(::String)
16
- ::Hash[value.scan(HstorePair).map { |k, v|
17
- v = v.upcase == "NULL" ? nil : v.gsub(/\A"(.*)"\Z/m, '\1').gsub(/\\(.)/, '\1')
18
- k = k.gsub(/\A"(.*)"\Z/m, '\1').gsub(/\\(.)/, '\1')
19
- [k, v]
20
- }]
21
- else
22
- value
19
+ return value unless value.is_a?(::String)
20
+
21
+ scanner = StringScanner.new(value)
22
+ hash = {}
23
+
24
+ until scanner.eos?
25
+ unless scanner.skip(/"/)
26
+ raise(ArgumentError, ERROR % scanner.string.inspect)
27
+ end
28
+
29
+ unless key = scanner.scan_until(/(?<!\\)(?=")/)
30
+ raise(ArgumentError, ERROR % scanner.string.inspect)
31
+ end
32
+
33
+ unless scanner.skip(/"=>?/)
34
+ raise(ArgumentError, ERROR % scanner.string.inspect)
35
+ end
36
+
37
+ if scanner.scan(/NULL/)
38
+ value = nil
39
+ else
40
+ unless scanner.skip(/"/)
41
+ raise(ArgumentError, ERROR % scanner.string.inspect)
42
+ end
43
+
44
+ unless value = scanner.scan_until(/(?<!\\)(?=")/)
45
+ raise(ArgumentError, ERROR % scanner.string.inspect)
46
+ end
47
+
48
+ unless scanner.skip(/"/)
49
+ raise(ArgumentError, ERROR % scanner.string.inspect)
50
+ end
51
+ end
52
+
53
+ key.gsub!('\"', '"')
54
+ key.gsub!("\\\\", "\\")
55
+
56
+ if value
57
+ value.gsub!('\"', '"')
58
+ value.gsub!("\\\\", "\\")
59
+ end
60
+
61
+ hash[key] = value
62
+
63
+ unless scanner.skip(/, /) || scanner.eos?
64
+ raise(ArgumentError, ERROR % scanner.string.inspect)
65
+ end
23
66
  end
67
+
68
+ hash
24
69
  end
25
70
 
26
71
  def serialize(value)
@@ -46,12 +91,6 @@ module ActiveRecord
46
91
  end
47
92
 
48
93
  private
49
- HstorePair = begin
50
- quoted_string = /"[^"\\]*(?:\\.[^"\\]*)*"/
51
- unquoted_string = /(?:\\.|[^\s,])[^\s=,\\]*(?:\\.[^\s=,\\]*|=[^,>])*/
52
- /(#{quoted_string}|#{unquoted_string})\s*=>\s*(#{quoted_string}|#{unquoted_string})/
53
- end
54
-
55
94
  def escape_hstore(value)
56
95
  if value.nil?
57
96
  "NULL"
@@ -88,7 +88,7 @@ module ActiveRecord
88
88
  if value.start_with?('"') && value.end_with?('"')
89
89
  unquoted_value = value[1..-2]
90
90
  unquoted_value.gsub!('""', '"')
91
- unquoted_value.gsub!('\\\\', '\\')
91
+ unquoted_value.gsub!("\\\\", "\\")
92
92
  unquoted_value
93
93
  else
94
94
  value
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module PostgreSQL
6
+ module OID # :nodoc:
7
+ class Timestamp < DateTime # :nodoc:
8
+ def type
9
+ real_type_unless_aliased(:timestamp)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module PostgreSQL
6
+ module OID # :nodoc:
7
+ class TimestampWithTimeZone < DateTime # :nodoc:
8
+ def type
9
+ real_type_unless_aliased(:timestamptz)
10
+ end
11
+
12
+ def cast_value(value)
13
+ time = super
14
+ return time if time.is_a?(ActiveSupport::TimeWithZone)
15
+
16
+ # While in UTC mode, the PG gem may not return times back in "UTC" even if they were provided to Postgres in UTC.
17
+ # We prefer times always in UTC, so here we convert back.
18
+ if is_utc?
19
+ time.getutc
20
+ else
21
+ time.getlocal
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -33,15 +33,27 @@ module ActiveRecord
33
33
  composites.each { |row| register_composite_type(row) }
34
34
  end
35
35
 
36
- def query_conditions_for_initial_load
36
+ def query_conditions_for_known_type_names
37
37
  known_type_names = @store.keys.map { |n| "'#{n}'" }
38
- known_type_types = %w('r' 'e' 'd')
39
- <<~SQL % [known_type_names.join(", "), known_type_types.join(", ")]
38
+ <<~SQL % known_type_names.join(", ")
40
39
  WHERE
41
40
  t.typname IN (%s)
42
- OR t.typtype IN (%s)
43
- OR t.typinput = 'array_in(cstring,oid,integer)'::regprocedure
44
- OR t.typelem != 0
41
+ SQL
42
+ end
43
+
44
+ def query_conditions_for_known_type_types
45
+ known_type_types = %w('r' 'e' 'd')
46
+ <<~SQL % known_type_types.join(", ")
47
+ WHERE
48
+ t.typtype IN (%s)
49
+ SQL
50
+ end
51
+
52
+ def query_conditions_for_array_types
53
+ known_type_oids = @store.keys.reject { |k| k.is_a?(String) }
54
+ <<~SQL % [known_type_oids.join(", ")]
55
+ WHERE
56
+ t.typelem IN (%s)
45
57
  SQL
46
58
  end
47
59
 
@@ -20,6 +20,8 @@ require "active_record/connection_adapters/postgresql/oid/point"
20
20
  require "active_record/connection_adapters/postgresql/oid/legacy_point"
21
21
  require "active_record/connection_adapters/postgresql/oid/range"
22
22
  require "active_record/connection_adapters/postgresql/oid/specialized_string"
23
+ require "active_record/connection_adapters/postgresql/oid/timestamp"
24
+ require "active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone"
23
25
  require "active_record/connection_adapters/postgresql/oid/uuid"
24
26
  require "active_record/connection_adapters/postgresql/oid/vector"
25
27
  require "active_record/connection_adapters/postgresql/oid/xml"
@@ -4,12 +4,6 @@ module ActiveRecord
4
4
  module ConnectionAdapters
5
5
  module PostgreSQL
6
6
  module Quoting
7
- class IntegerOutOf64BitRange < StandardError
8
- def initialize(msg)
9
- super(msg)
10
- end
11
- end
12
-
13
7
  # Escapes binary strings for bytea input to the database.
14
8
  def escape_bytea(value)
15
9
  @connection.escape_bytea(value) if value
@@ -23,7 +17,7 @@ module ActiveRecord
23
17
  end
24
18
 
25
19
  # Quotes strings for use in SQL input.
26
- def quote_string(s) #:nodoc:
20
+ def quote_string(s) # :nodoc:
27
21
  PG::Connection.escape(s)
28
22
  end
29
23
 
@@ -54,7 +48,7 @@ module ActiveRecord
54
48
  end
55
49
 
56
50
  # Quote date/time values for use in SQL input.
57
- def quoted_date(value) #:nodoc:
51
+ def quoted_date(value) # :nodoc:
58
52
  if value.year <= 0
59
53
  bce_year = format("%04d", -value.year + 1)
60
54
  super.sub(/^-?\d+/, bce_year) + " BC"
@@ -96,8 +90,8 @@ module ActiveRecord
96
90
  \A
97
91
  (
98
92
  (?:
99
- # "table_name"."column_name"::type_name | function(one or no argument)::type_name
100
- ((?:\w+\.|"\w+"\.)?(?:\w+|"\w+")(?:::\w+)?) | \w+\((?:|\g<2>)\)(?:::\w+)?
93
+ # "schema_name"."table_name"."column_name"::type_name | function(one or no argument)::type_name
94
+ ((?:\w+\.|"\w+"\.){,2}(?:\w+|"\w+")(?:::\w+)?) | \w+\((?:|\g<2>)\)(?:::\w+)?
101
95
  )
102
96
  (?:(?:\s+AS)?\s+(?:\w+|"\w+"))?
103
97
  )
@@ -109,8 +103,8 @@ module ActiveRecord
109
103
  \A
110
104
  (
111
105
  (?:
112
- # "table_name"."column_name"::type_name | function(one or no argument)::type_name
113
- ((?:\w+\.|"\w+"\.)?(?:\w+|"\w+")(?:::\w+)?) | \w+\((?:|\g<2>)\)(?:::\w+)?
106
+ # "schema_name"."table_name"."column_name"::type_name | function(one or no argument)::type_name
107
+ ((?:\w+\.|"\w+"\.){,2}(?:\w+|"\w+")(?:::\w+)?) | \w+\((?:|\g<2>)\)(?:::\w+)?
114
108
  )
115
109
  (?:\s+ASC|\s+DESC)?
116
110
  (?:\s+NULLS\s+(?:FIRST|LAST))?
@@ -126,27 +120,7 @@ module ActiveRecord
126
120
  super(query_value("SELECT #{quote(sql_type)}::regtype::oid", "SCHEMA").to_i)
127
121
  end
128
122
 
129
- def check_int_in_range(value)
130
- if value.to_int > 9223372036854775807 || value.to_int < -9223372036854775808
131
- exception = <<~ERROR
132
- Provided value outside of the range of a signed 64bit integer.
133
-
134
- PostgreSQL will treat the column type in question as a numeric.
135
- This may result in a slow sequential scan due to a comparison
136
- being performed between an integer or bigint value and a numeric value.
137
-
138
- To allow for this potentially unwanted behavior, set
139
- ActiveRecord::Base.raise_int_wider_than_64bit to false.
140
- ERROR
141
- raise IntegerOutOf64BitRange.new exception
142
- end
143
- end
144
-
145
123
  def _quote(value)
146
- if ActiveRecord::Base.raise_int_wider_than_64bit && value.is_a?(Integer)
147
- check_int_in_range(value)
148
- end
149
-
150
124
  case value
151
125
  when OID::Xml::Data
152
126
  "xml '#{quote_string(value.to_s)}'"
@@ -37,6 +37,38 @@ Rails needs superuser privileges to disable referential integrity.
37
37
  rescue ActiveRecord::ActiveRecordError
38
38
  end
39
39
  end
40
+
41
+ def all_foreign_keys_valid? # :nodoc:
42
+ sql = <<~SQL
43
+ do $$
44
+ declare r record;
45
+ BEGIN
46
+ FOR r IN (
47
+ SELECT FORMAT(
48
+ 'UPDATE pg_constraint SET convalidated=false WHERE conname = ''%I''; ALTER TABLE %I VALIDATE CONSTRAINT %I;',
49
+ constraint_name,
50
+ table_name,
51
+ constraint_name
52
+ ) AS constraint_check
53
+ FROM information_schema.table_constraints WHERE constraint_type = 'FOREIGN KEY'
54
+ )
55
+ LOOP
56
+ EXECUTE (r.constraint_check);
57
+ END LOOP;
58
+ END;
59
+ $$;
60
+ SQL
61
+
62
+ begin
63
+ transaction(requires_new: true) do
64
+ execute(sql)
65
+ end
66
+
67
+ true
68
+ rescue ActiveRecord::StatementInvalid
69
+ false
70
+ end
71
+ end
40
72
  end
41
73
  end
42
74
  end
@@ -177,7 +177,7 @@ module ActiveRecord
177
177
  define_column_methods :bigserial, :bit, :bit_varying, :cidr, :citext, :daterange,
178
178
  :hstore, :inet, :interval, :int4range, :int8range, :jsonb, :ltree, :macaddr,
179
179
  :money, :numrange, :oid, :point, :line, :lseg, :box, :path, :polygon, :circle,
180
- :serial, :tsrange, :tstzrange, :tsvector, :uuid, :xml
180
+ :serial, :tsrange, :tstzrange, :tsvector, :uuid, :xml, :timestamptz
181
181
  end
182
182
  end
183
183
 
@@ -192,6 +192,10 @@ module ActiveRecord
192
192
  end
193
193
 
194
194
  private
195
+ def aliased_types(name, fallback)
196
+ fallback
197
+ end
198
+
195
199
  def integer_like_primary_key_type(type, options)
196
200
  if type == :bigint || options[:limit] == 8
197
201
  :bigserial