activerecord 6.1.7 → 7.0.8.1

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 +1615 -1001
  3. data/README.rdoc +3 -3
  4. data/lib/active_record/aggregations.rb +1 -1
  5. data/lib/active_record/association_relation.rb +0 -10
  6. data/lib/active_record/associations/association.rb +33 -17
  7. data/lib/active_record/associations/association_scope.rb +1 -3
  8. data/lib/active_record/associations/belongs_to_association.rb +15 -4
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +10 -2
  10. data/lib/active_record/associations/builder/association.rb +8 -2
  11. data/lib/active_record/associations/builder/belongs_to.rb +19 -6
  12. data/lib/active_record/associations/builder/collection_association.rb +10 -3
  13. data/lib/active_record/associations/builder/has_many.rb +3 -2
  14. data/lib/active_record/associations/builder/has_one.rb +2 -1
  15. data/lib/active_record/associations/builder/singular_association.rb +2 -2
  16. data/lib/active_record/associations/collection_association.rb +20 -22
  17. data/lib/active_record/associations/collection_proxy.rb +15 -5
  18. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  19. data/lib/active_record/associations/has_many_association.rb +8 -5
  20. data/lib/active_record/associations/has_many_through_association.rb +2 -1
  21. data/lib/active_record/associations/has_one_association.rb +10 -7
  22. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  23. data/lib/active_record/associations/join_dependency.rb +23 -15
  24. data/lib/active_record/associations/preloader/association.rb +186 -52
  25. data/lib/active_record/associations/preloader/batch.rb +48 -0
  26. data/lib/active_record/associations/preloader/branch.rb +147 -0
  27. data/lib/active_record/associations/preloader/through_association.rb +50 -14
  28. data/lib/active_record/associations/preloader.rb +39 -113
  29. data/lib/active_record/associations/singular_association.rb +8 -2
  30. data/lib/active_record/associations/through_association.rb +3 -3
  31. data/lib/active_record/associations.rb +138 -100
  32. data/lib/active_record/asynchronous_queries_tracker.rb +60 -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 +49 -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 +8 -6
  39. data/lib/active_record/attribute_methods/serialization.rb +57 -19
  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 +19 -22
  43. data/lib/active_record/attributes.rb +24 -35
  44. data/lib/active_record/autosave_association.rb +8 -23
  45. data/lib/active_record/base.rb +19 -1
  46. data/lib/active_record/callbacks.rb +14 -16
  47. data/lib/active_record/coders/yaml_column.rb +4 -8
  48. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +292 -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 +47 -561
  52. data/lib/active_record/connection_adapters/abstract/database_limits.rb +0 -17
  53. data/lib/active_record/connection_adapters/abstract/database_statements.rb +46 -22
  54. data/lib/active_record/connection_adapters/abstract/query_cache.rb +24 -12
  55. data/lib/active_record/connection_adapters/abstract/quoting.rb +52 -73
  56. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +4 -17
  57. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +52 -23
  58. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  59. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +82 -25
  60. data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -22
  61. data/lib/active_record/connection_adapters/abstract_adapter.rb +144 -82
  62. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +115 -85
  63. data/lib/active_record/connection_adapters/column.rb +4 -0
  64. data/lib/active_record/connection_adapters/mysql/database_statements.rb +37 -25
  65. data/lib/active_record/connection_adapters/mysql/quoting.rb +50 -23
  66. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +4 -1
  67. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +7 -1
  68. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +20 -1
  69. data/lib/active_record/connection_adapters/mysql2_adapter.rb +12 -6
  70. data/lib/active_record/connection_adapters/pool_config.rb +7 -7
  71. data/lib/active_record/connection_adapters/postgresql/column.rb +19 -1
  72. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +20 -17
  73. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  74. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
  75. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
  77. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
  78. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  79. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  80. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  81. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  82. data/lib/active_record/connection_adapters/postgresql/quoting.rb +82 -53
  83. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +34 -0
  84. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +21 -1
  85. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +22 -1
  86. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +25 -0
  87. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +40 -21
  88. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  89. data/lib/active_record/connection_adapters/postgresql_adapter.rb +207 -106
  90. data/lib/active_record/connection_adapters/schema_cache.rb +39 -38
  91. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +25 -19
  92. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +33 -18
  93. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +6 -0
  94. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +19 -17
  95. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +98 -36
  96. data/lib/active_record/connection_adapters.rb +6 -5
  97. data/lib/active_record/connection_handling.rb +49 -55
  98. data/lib/active_record/core.rb +123 -141
  99. data/lib/active_record/database_configurations/connection_url_resolver.rb +2 -1
  100. data/lib/active_record/database_configurations/database_config.rb +12 -9
  101. data/lib/active_record/database_configurations/hash_config.rb +63 -5
  102. data/lib/active_record/database_configurations/url_config.rb +2 -2
  103. data/lib/active_record/database_configurations.rb +15 -32
  104. data/lib/active_record/delegated_type.rb +53 -12
  105. data/lib/active_record/destroy_association_async_job.rb +1 -1
  106. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  107. data/lib/active_record/dynamic_matchers.rb +1 -1
  108. data/lib/active_record/encryption/cipher/aes256_gcm.rb +98 -0
  109. data/lib/active_record/encryption/cipher.rb +53 -0
  110. data/lib/active_record/encryption/config.rb +44 -0
  111. data/lib/active_record/encryption/configurable.rb +67 -0
  112. data/lib/active_record/encryption/context.rb +35 -0
  113. data/lib/active_record/encryption/contexts.rb +72 -0
  114. data/lib/active_record/encryption/derived_secret_key_provider.rb +12 -0
  115. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  116. data/lib/active_record/encryption/encryptable_record.rb +206 -0
  117. data/lib/active_record/encryption/encrypted_attribute_type.rb +140 -0
  118. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  119. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  120. data/lib/active_record/encryption/encryptor.rb +155 -0
  121. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  122. data/lib/active_record/encryption/errors.rb +15 -0
  123. data/lib/active_record/encryption/extended_deterministic_queries.rb +160 -0
  124. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  125. data/lib/active_record/encryption/key.rb +28 -0
  126. data/lib/active_record/encryption/key_generator.rb +42 -0
  127. data/lib/active_record/encryption/key_provider.rb +46 -0
  128. data/lib/active_record/encryption/message.rb +33 -0
  129. data/lib/active_record/encryption/message_serializer.rb +90 -0
  130. data/lib/active_record/encryption/null_encryptor.rb +21 -0
  131. data/lib/active_record/encryption/properties.rb +76 -0
  132. data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
  133. data/lib/active_record/encryption/scheme.rb +99 -0
  134. data/lib/active_record/encryption.rb +55 -0
  135. data/lib/active_record/enum.rb +50 -43
  136. data/lib/active_record/errors.rb +67 -4
  137. data/lib/active_record/explain_registry.rb +11 -6
  138. data/lib/active_record/explain_subscriber.rb +1 -1
  139. data/lib/active_record/fixture_set/file.rb +15 -1
  140. data/lib/active_record/fixture_set/table_row.rb +41 -6
  141. data/lib/active_record/fixture_set/table_rows.rb +4 -4
  142. data/lib/active_record/fixtures.rb +20 -23
  143. data/lib/active_record/future_result.rb +139 -0
  144. data/lib/active_record/gem_version.rb +5 -5
  145. data/lib/active_record/inheritance.rb +55 -17
  146. data/lib/active_record/insert_all.rb +80 -14
  147. data/lib/active_record/integration.rb +4 -3
  148. data/lib/active_record/internal_metadata.rb +1 -5
  149. data/lib/active_record/legacy_yaml_adapter.rb +2 -39
  150. data/lib/active_record/locking/optimistic.rb +36 -21
  151. data/lib/active_record/locking/pessimistic.rb +10 -4
  152. data/lib/active_record/log_subscriber.rb +23 -7
  153. data/lib/active_record/middleware/database_selector/resolver.rb +6 -10
  154. data/lib/active_record/middleware/database_selector.rb +18 -6
  155. data/lib/active_record/middleware/shard_selector.rb +60 -0
  156. data/lib/active_record/migration/command_recorder.rb +8 -9
  157. data/lib/active_record/migration/compatibility.rb +93 -46
  158. data/lib/active_record/migration/join_table.rb +1 -1
  159. data/lib/active_record/migration.rb +167 -87
  160. data/lib/active_record/model_schema.rb +58 -59
  161. data/lib/active_record/nested_attributes.rb +13 -12
  162. data/lib/active_record/no_touching.rb +3 -3
  163. data/lib/active_record/null_relation.rb +2 -6
  164. data/lib/active_record/persistence.rb +231 -61
  165. data/lib/active_record/query_cache.rb +2 -2
  166. data/lib/active_record/query_logs.rb +149 -0
  167. data/lib/active_record/querying.rb +16 -6
  168. data/lib/active_record/railtie.rb +136 -22
  169. data/lib/active_record/railties/controller_runtime.rb +4 -5
  170. data/lib/active_record/railties/databases.rake +78 -136
  171. data/lib/active_record/readonly_attributes.rb +11 -0
  172. data/lib/active_record/reflection.rb +80 -49
  173. data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
  174. data/lib/active_record/relation/batches.rb +6 -6
  175. data/lib/active_record/relation/calculations.rb +92 -60
  176. data/lib/active_record/relation/delegation.rb +7 -7
  177. data/lib/active_record/relation/finder_methods.rb +31 -35
  178. data/lib/active_record/relation/merger.rb +20 -13
  179. data/lib/active_record/relation/predicate_builder/association_query_value.rb +20 -1
  180. data/lib/active_record/relation/predicate_builder.rb +1 -6
  181. data/lib/active_record/relation/query_attribute.rb +28 -11
  182. data/lib/active_record/relation/query_methods.rb +306 -68
  183. data/lib/active_record/relation/record_fetch_warning.rb +7 -9
  184. data/lib/active_record/relation/spawn_methods.rb +2 -2
  185. data/lib/active_record/relation/where_clause.rb +10 -19
  186. data/lib/active_record/relation.rb +189 -88
  187. data/lib/active_record/result.rb +23 -11
  188. data/lib/active_record/runtime_registry.rb +9 -13
  189. data/lib/active_record/sanitization.rb +17 -12
  190. data/lib/active_record/schema.rb +38 -23
  191. data/lib/active_record/schema_dumper.rb +29 -19
  192. data/lib/active_record/schema_migration.rb +4 -4
  193. data/lib/active_record/scoping/default.rb +60 -13
  194. data/lib/active_record/scoping/named.rb +3 -11
  195. data/lib/active_record/scoping.rb +64 -34
  196. data/lib/active_record/serialization.rb +6 -1
  197. data/lib/active_record/signed_id.rb +3 -3
  198. data/lib/active_record/store.rb +2 -2
  199. data/lib/active_record/suppressor.rb +11 -15
  200. data/lib/active_record/table_metadata.rb +6 -2
  201. data/lib/active_record/tasks/database_tasks.rb +127 -60
  202. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  203. data/lib/active_record/tasks/postgresql_database_tasks.rb +19 -13
  204. data/lib/active_record/test_databases.rb +1 -1
  205. data/lib/active_record/test_fixtures.rb +9 -6
  206. data/lib/active_record/timestamp.rb +3 -4
  207. data/lib/active_record/transactions.rb +12 -17
  208. data/lib/active_record/translation.rb +3 -3
  209. data/lib/active_record/type/adapter_specific_registry.rb +32 -7
  210. data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
  211. data/lib/active_record/type/internal/timezone.rb +2 -2
  212. data/lib/active_record/type/serialized.rb +9 -5
  213. data/lib/active_record/type/type_map.rb +17 -20
  214. data/lib/active_record/type.rb +1 -2
  215. data/lib/active_record/validations/associated.rb +4 -4
  216. data/lib/active_record/validations/presence.rb +2 -2
  217. data/lib/active_record/validations/uniqueness.rb +4 -4
  218. data/lib/active_record/version.rb +1 -1
  219. data/lib/active_record.rb +225 -27
  220. data/lib/arel/attributes/attribute.rb +0 -8
  221. data/lib/arel/crud.rb +28 -22
  222. data/lib/arel/delete_manager.rb +18 -4
  223. data/lib/arel/filter_predications.rb +9 -0
  224. data/lib/arel/insert_manager.rb +2 -3
  225. data/lib/arel/nodes/and.rb +4 -0
  226. data/lib/arel/nodes/casted.rb +1 -1
  227. data/lib/arel/nodes/delete_statement.rb +12 -13
  228. data/lib/arel/nodes/filter.rb +10 -0
  229. data/lib/arel/nodes/function.rb +1 -0
  230. data/lib/arel/nodes/insert_statement.rb +2 -2
  231. data/lib/arel/nodes/select_core.rb +2 -2
  232. data/lib/arel/nodes/select_statement.rb +2 -2
  233. data/lib/arel/nodes/update_statement.rb +8 -3
  234. data/lib/arel/nodes.rb +1 -0
  235. data/lib/arel/predications.rb +11 -3
  236. data/lib/arel/select_manager.rb +10 -4
  237. data/lib/arel/table.rb +0 -1
  238. data/lib/arel/tree_manager.rb +0 -12
  239. data/lib/arel/update_manager.rb +18 -4
  240. data/lib/arel/visitors/dot.rb +80 -90
  241. data/lib/arel/visitors/mysql.rb +8 -2
  242. data/lib/arel/visitors/postgresql.rb +0 -10
  243. data/lib/arel/visitors/to_sql.rb +58 -2
  244. data/lib/arel.rb +2 -1
  245. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  246. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  247. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  248. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  249. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  250. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  251. metadata +55 -11
@@ -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
@@ -5,7 +5,7 @@ module ActiveRecord
5
5
  class PoolConfig # :nodoc:
6
6
  include Mutex_m
7
7
 
8
- attr_reader :db_config, :connection_klass
8
+ attr_reader :db_config, :connection_class, :role, :shard
9
9
  attr_accessor :schema_cache
10
10
 
11
11
  INSTANCES = ObjectSpace::WeakMap.new
@@ -17,21 +17,21 @@ module ActiveRecord
17
17
  end
18
18
  end
19
19
 
20
- def initialize(connection_klass, db_config)
20
+ def initialize(connection_class, db_config, role, shard)
21
21
  super()
22
- @connection_klass = connection_klass
22
+ @connection_class = connection_class
23
23
  @db_config = db_config
24
+ @role = role
25
+ @shard = shard
24
26
  @pool = nil
25
27
  INSTANCES[self] = self
26
28
  end
27
29
 
28
30
  def connection_specification_name
29
- if connection_klass.is_a?(String)
30
- connection_klass
31
- elsif connection_klass.primary_class?
31
+ if connection_class.primary_class?
32
32
  "ActiveRecord::Base"
33
33
  else
34
- connection_klass.name
34
+ connection_class.name
35
35
  end
36
36
  end
37
37
 
@@ -1,36 +1,54 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/core_ext/object/blank"
4
+
3
5
  module ActiveRecord
4
6
  module ConnectionAdapters
5
7
  module PostgreSQL
6
8
  class Column < ConnectionAdapters::Column # :nodoc:
7
9
  delegate :oid, :fmod, to: :sql_type_metadata
8
10
 
9
- def initialize(*, serial: nil, **)
11
+ def initialize(*, serial: nil, generated: nil, **)
10
12
  super
11
13
  @serial = serial
14
+ @generated = generated
12
15
  end
13
16
 
14
17
  def serial?
15
18
  @serial
16
19
  end
17
20
 
21
+ def virtual?
22
+ # We assume every generated column is virtual, no matter the concrete type
23
+ @generated.present?
24
+ end
25
+
26
+ def has_default?
27
+ super && !virtual?
28
+ end
29
+
18
30
  def array
19
31
  sql_type_metadata.sql_type.end_with?("[]")
20
32
  end
21
33
  alias :array? :array
22
34
 
35
+ def enum?
36
+ type == :enum
37
+ end
38
+
23
39
  def sql_type
24
40
  super.delete_suffix("[]")
25
41
  end
26
42
 
27
43
  def init_with(coder)
28
44
  @serial = coder["serial"]
45
+ @generated = coder["generated"]
29
46
  super
30
47
  end
31
48
 
32
49
  def encode_with(coder)
33
50
  coder["serial"] = @serial
51
+ coder["generated"] = @generated
34
52
  super
35
53
  end
36
54
 
@@ -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
 
@@ -37,9 +37,8 @@ module ActiveRecord
37
37
  # Note: the PG::Result object is manually memory managed; if you don't
38
38
  # need it specifically, you may want consider the <tt>exec_query</tt> wrapper.
39
39
  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
40
+ sql = transform_query(sql)
41
+ check_if_write_query(sql)
43
42
 
44
43
  materialize_transactions
45
44
  mark_transaction_written_if_write(sql)
@@ -51,24 +50,20 @@ module ActiveRecord
51
50
  end
52
51
  end
53
52
 
54
- def exec_query(sql, name = "SQL", binds = [], prepare: false)
55
- execute_and_clear(sql, name, binds, prepare: prepare) do |result|
53
+ def exec_query(sql, name = "SQL", binds = [], prepare: false, async: false) # :nodoc:
54
+ execute_and_clear(sql, name, binds, prepare: prepare, async: async) do |result|
56
55
  types = {}
57
56
  fields = result.fields
58
57
  fields.each_with_index do |fname, i|
59
58
  ftype = result.ftype i
60
59
  fmod = result.fmod i
61
- case type = get_oid_type(ftype, fmod, fname)
62
- when Type::Integer, Type::Float, OID::Decimal, Type::String, Type::DateTime, Type::Boolean
63
- # skip if a column has already been type casted by pg decoders
64
- else types[fname] = type
65
- end
60
+ types[fname] = types[i] = get_oid_type(ftype, fmod, fname)
66
61
  end
67
62
  build_result(columns: fields, rows: result.values, column_types: types)
68
63
  end
69
64
  end
70
65
 
71
- def exec_delete(sql, name = nil, binds = [])
66
+ def exec_delete(sql, name = nil, binds = []) # :nodoc:
72
67
  execute_and_clear(sql, name, binds) { |result| result.cmd_tuples }
73
68
  end
74
69
  alias :exec_update :exec_delete
@@ -88,7 +83,7 @@ module ActiveRecord
88
83
  end
89
84
  private :sql_for_insert
90
85
 
91
- def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil)
86
+ def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil) # :nodoc:
92
87
  if use_insert_returning? || pk == false
93
88
  super
94
89
  else
@@ -107,25 +102,33 @@ module ActiveRecord
107
102
  end
108
103
 
109
104
  # Begins a transaction.
110
- def begin_db_transaction
105
+ def begin_db_transaction # :nodoc:
111
106
  execute("BEGIN", "TRANSACTION")
112
107
  end
113
108
 
114
- def begin_isolated_db_transaction(isolation)
109
+ def begin_isolated_db_transaction(isolation) # :nodoc:
115
110
  begin_db_transaction
116
111
  execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}"
117
112
  end
118
113
 
119
114
  # Commits a transaction.
120
- def commit_db_transaction
115
+ def commit_db_transaction # :nodoc:
121
116
  execute("COMMIT", "TRANSACTION")
122
117
  end
123
118
 
124
119
  # Aborts a transaction.
125
- def exec_rollback_db_transaction
120
+ def exec_rollback_db_transaction # :nodoc:
126
121
  execute("ROLLBACK", "TRANSACTION")
127
122
  end
128
123
 
124
+ # From https://www.postgresql.org/docs/current/functions-datetime.html#FUNCTIONS-DATETIME-CURRENT
125
+ HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP").freeze # :nodoc:
126
+ private_constant :HIGH_PRECISION_CURRENT_TIMESTAMP
127
+
128
+ def high_precision_current_timestamp
129
+ HIGH_PRECISION_CURRENT_TIMESTAMP
130
+ end
131
+
129
132
  private
130
133
  def execute_batch(statements, name = nil)
131
134
  execute(combine_multi_statements(statements))
@@ -65,7 +65,7 @@ module ActiveRecord
65
65
  end
66
66
 
67
67
  def map(value, &block)
68
- value.map(&block)
68
+ value.map { |v| subtype.map(v, &block) }
69
69
  end
70
70
 
71
71
  def changed_in_place?(raw_old_value, new_value)
@@ -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(/^(\\[\\"]|[^\\"])*?(?=")/)
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(/^(\\[\\"]|[^\\"])*?(?=")/)
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,30 @@
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
+ return if value.blank?
14
+
15
+ time = super
16
+ return time if time.is_a?(ActiveSupport::TimeWithZone) || !time.acts_like?(:time)
17
+
18
+ # While in UTC mode, the PG gem may not return times back in "UTC" even if they were provided to Postgres in UTC.
19
+ # We prefer times always in UTC, so here we convert back.
20
+ if is_utc?
21
+ time.getutc
22
+ else
23
+ time.getlocal
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ 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,6 +4,15 @@ module ActiveRecord
4
4
  module ConnectionAdapters
5
5
  module PostgreSQL
6
6
  module Quoting
7
+ QUOTED_COLUMN_NAMES = Concurrent::Map.new # :nodoc:
8
+ QUOTED_TABLE_NAMES = Concurrent::Map.new # :nodoc:
9
+
10
+ class IntegerOutOf64BitRange < StandardError
11
+ def initialize(msg)
12
+ super(msg)
13
+ end
14
+ end
15
+
7
16
  # Escapes binary strings for bytea input to the database.
8
17
  def escape_bytea(value)
9
18
  @connection.escape_bytea(value) if value
@@ -16,9 +25,54 @@ module ActiveRecord
16
25
  @connection.unescape_bytea(value) if value
17
26
  end
18
27
 
28
+ def check_int_in_range(value)
29
+ if value.to_int > 9223372036854775807 || value.to_int < -9223372036854775808
30
+ exception = <<~ERROR
31
+ Provided value outside of the range of a signed 64bit integer.
32
+
33
+ PostgreSQL will treat the column type in question as a numeric.
34
+ This may result in a slow sequential scan due to a comparison
35
+ being performed between an integer or bigint value and a numeric value.
36
+
37
+ To allow for this potentially unwanted behavior, set
38
+ ActiveRecord.raise_int_wider_than_64bit to false.
39
+ ERROR
40
+ raise IntegerOutOf64BitRange.new exception
41
+ end
42
+ end
43
+
44
+ def quote(value) # :nodoc:
45
+ if ActiveRecord.raise_int_wider_than_64bit && value.is_a?(Integer)
46
+ check_int_in_range(value)
47
+ end
48
+
49
+ case value
50
+ when OID::Xml::Data
51
+ "xml '#{quote_string(value.to_s)}'"
52
+ when OID::Bit::Data
53
+ if value.binary?
54
+ "B'#{value}'"
55
+ elsif value.hex?
56
+ "X'#{value}'"
57
+ end
58
+ when Numeric
59
+ if value.finite?
60
+ super
61
+ else
62
+ "'#{value}'"
63
+ end
64
+ when OID::Array::Data
65
+ quote(encode_array(value))
66
+ when Range
67
+ quote(encode_range(value))
68
+ else
69
+ super
70
+ end
71
+ end
72
+
19
73
  # Quotes strings for use in SQL input.
20
- def quote_string(s) #:nodoc:
21
- PG::Connection.escape(s)
74
+ def quote_string(s) # :nodoc:
75
+ @connection.escape(s)
22
76
  end
23
77
 
24
78
  # Checks the following cases:
@@ -30,7 +84,7 @@ module ActiveRecord
30
84
  # - "schema.name".table_name
31
85
  # - "schema.name"."table.name"
32
86
  def quote_table_name(name) # :nodoc:
33
- self.class.quoted_table_names[name] ||= Utils.extract_schema_qualified_name(name.to_s).quoted.freeze
87
+ QUOTED_TABLE_NAMES[name] ||= Utils.extract_schema_qualified_name(name.to_s).quoted.freeze
34
88
  end
35
89
 
36
90
  # Quotes schema names for use in SQL queries.
@@ -44,11 +98,11 @@ module ActiveRecord
44
98
 
45
99
  # Quotes column names for use in SQL queries.
46
100
  def quote_column_name(name) # :nodoc:
47
- self.class.quoted_column_names[name] ||= PG::Connection.quote_ident(super).freeze
101
+ QUOTED_COLUMN_NAMES[name] ||= PG::Connection.quote_ident(super).freeze
48
102
  end
49
103
 
50
104
  # Quote date/time values for use in SQL input.
51
- def quoted_date(value) #:nodoc:
105
+ def quoted_date(value) # :nodoc:
52
106
  if value.year <= 0
53
107
  bce_year = format("%04d", -value.year + 1)
54
108
  super.sub(/^-?\d+/, bce_year) + " BC"
@@ -74,6 +128,24 @@ module ActiveRecord
74
128
  end
75
129
  end
76
130
 
131
+ def type_cast(value) # :nodoc:
132
+ case value
133
+ when Type::Binary::Data
134
+ # Return a bind param hash with format as binary.
135
+ # See https://deveiate.org/code/pg/PG/Connection.html#method-i-exec_prepared-doc
136
+ # for more information
137
+ { value: value.to_s, format: 1 }
138
+ when OID::Xml::Data, OID::Bit::Data
139
+ value.to_s
140
+ when OID::Array::Data
141
+ encode_array(value)
142
+ when Range
143
+ encode_range(value)
144
+ else
145
+ super
146
+ end
147
+ end
148
+
77
149
  def lookup_cast_type_from_column(column) # :nodoc:
78
150
  type_map.lookup(column.oid, column.fmod, column.sql_type)
79
151
  end
@@ -90,8 +162,8 @@ module ActiveRecord
90
162
  \A
91
163
  (
92
164
  (?:
93
- # "table_name"."column_name"::type_name | function(one or no argument)::type_name
94
- ((?:\w+\.|"\w+"\.)?(?:\w+|"\w+")(?:::\w+)?) | \w+\((?:|\g<2>)\)(?:::\w+)?
165
+ # "schema_name"."table_name"."column_name"::type_name | function(one or no argument)::type_name
166
+ ((?:\w+\.|"\w+"\.){,2}(?:\w+|"\w+")(?:::\w+)?) | \w+\((?:|\g<2>)\)(?:::\w+)?
95
167
  )
96
168
  (?:(?:\s+AS)?\s+(?:\w+|"\w+"))?
97
169
  )
@@ -103,8 +175,8 @@ module ActiveRecord
103
175
  \A
104
176
  (
105
177
  (?:
106
- # "table_name"."column_name"::type_name | function(one or no argument)::type_name
107
- ((?:\w+\.|"\w+"\.)?(?:\w+|"\w+")(?:::\w+)?) | \w+\((?:|\g<2>)\)(?:::\w+)?
178
+ # "schema_name"."table_name"."column_name"::type_name | function(one or no argument)::type_name
179
+ ((?:\w+\.|"\w+"\.){,2}(?:\w+|"\w+")(?:::\w+)?) | \w+\((?:|\g<2>)\)(?:::\w+)?
108
180
  )
109
181
  (?:\s+ASC|\s+DESC)?
110
182
  (?:\s+NULLS\s+(?:FIRST|LAST))?
@@ -120,49 +192,6 @@ module ActiveRecord
120
192
  super(query_value("SELECT #{quote(sql_type)}::regtype::oid", "SCHEMA").to_i)
121
193
  end
122
194
 
123
- def _quote(value)
124
- case value
125
- when OID::Xml::Data
126
- "xml '#{quote_string(value.to_s)}'"
127
- when OID::Bit::Data
128
- if value.binary?
129
- "B'#{value}'"
130
- elsif value.hex?
131
- "X'#{value}'"
132
- end
133
- when Numeric
134
- if value.finite?
135
- super
136
- else
137
- "'#{value}'"
138
- end
139
- when OID::Array::Data
140
- _quote(encode_array(value))
141
- when Range
142
- _quote(encode_range(value))
143
- else
144
- super
145
- end
146
- end
147
-
148
- def _type_cast(value)
149
- case value
150
- when Type::Binary::Data
151
- # Return a bind param hash with format as binary.
152
- # See https://deveiate.org/code/pg/PG/Connection.html#method-i-exec_prepared-doc
153
- # for more information
154
- { value: value.to_s, format: 1 }
155
- when OID::Xml::Data, OID::Bit::Data
156
- value.to_s
157
- when OID::Array::Data
158
- encode_array(value)
159
- when Range
160
- encode_range(value)
161
- else
162
- super
163
- end
164
- end
165
-
166
195
  def encode_array(array_data)
167
196
  encoder = array_data.encoder
168
197
  values = type_cast_array(array_data.values)
@@ -188,7 +217,7 @@ module ActiveRecord
188
217
  def type_cast_array(values)
189
218
  case values
190
219
  when ::Array then values.map { |item| type_cast_array(item) }
191
- else _type_cast(values)
220
+ else type_cast(values)
192
221
  end
193
222
  end
194
223