activerecord 6.1.7.2 → 7.0.4.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (243) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1295 -1007
  3. data/README.rdoc +1 -1
  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 +19 -21
  17. data/lib/active_record/associations/collection_proxy.rb +10 -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 +49 -13
  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 +124 -95
  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 +7 -5
  39. data/lib/active_record/attribute_methods/serialization.rb +57 -19
  40. data/lib/active_record/attribute_methods/time_zone_conversion.rb +8 -3
  41. data/lib/active_record/attribute_methods/write.rb +7 -10
  42. data/lib/active_record/attribute_methods.rb +14 -15
  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 +2 -2
  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 +42 -72
  56. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +4 -17
  57. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +38 -13
  58. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  59. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +80 -24
  60. data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -22
  61. data/lib/active_record/connection_adapters/abstract_adapter.rb +149 -74
  62. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +105 -81
  63. data/lib/active_record/connection_adapters/column.rb +4 -0
  64. data/lib/active_record/connection_adapters/mysql/database_statements.rb +36 -24
  65. data/lib/active_record/connection_adapters/mysql/quoting.rb +37 -21
  66. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +7 -1
  67. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +20 -1
  68. data/lib/active_record/connection_adapters/mysql2_adapter.rb +12 -6
  69. data/lib/active_record/connection_adapters/pool_config.rb +7 -7
  70. data/lib/active_record/connection_adapters/postgresql/column.rb +17 -1
  71. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +19 -12
  72. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
  73. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
  75. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
  76. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  78. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  79. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  80. data/lib/active_record/connection_adapters/postgresql/quoting.rb +71 -71
  81. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +34 -0
  82. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +21 -1
  83. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +22 -1
  84. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +25 -0
  85. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +37 -19
  86. data/lib/active_record/connection_adapters/postgresql_adapter.rb +206 -105
  87. data/lib/active_record/connection_adapters/schema_cache.rb +39 -38
  88. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +25 -19
  89. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +28 -16
  90. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +17 -15
  91. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +96 -32
  92. data/lib/active_record/connection_adapters.rb +6 -5
  93. data/lib/active_record/connection_handling.rb +49 -55
  94. data/lib/active_record/core.rb +123 -148
  95. data/lib/active_record/database_configurations/connection_url_resolver.rb +2 -1
  96. data/lib/active_record/database_configurations/database_config.rb +12 -9
  97. data/lib/active_record/database_configurations/hash_config.rb +63 -5
  98. data/lib/active_record/database_configurations/url_config.rb +2 -2
  99. data/lib/active_record/database_configurations.rb +15 -32
  100. data/lib/active_record/delegated_type.rb +53 -12
  101. data/lib/active_record/destroy_association_async_job.rb +1 -1
  102. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  103. data/lib/active_record/dynamic_matchers.rb +1 -1
  104. data/lib/active_record/encryption/cipher/aes256_gcm.rb +98 -0
  105. data/lib/active_record/encryption/cipher.rb +53 -0
  106. data/lib/active_record/encryption/config.rb +44 -0
  107. data/lib/active_record/encryption/configurable.rb +67 -0
  108. data/lib/active_record/encryption/context.rb +35 -0
  109. data/lib/active_record/encryption/contexts.rb +72 -0
  110. data/lib/active_record/encryption/derived_secret_key_provider.rb +12 -0
  111. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  112. data/lib/active_record/encryption/encryptable_record.rb +206 -0
  113. data/lib/active_record/encryption/encrypted_attribute_type.rb +140 -0
  114. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  115. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  116. data/lib/active_record/encryption/encryptor.rb +155 -0
  117. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  118. data/lib/active_record/encryption/errors.rb +15 -0
  119. data/lib/active_record/encryption/extended_deterministic_queries.rb +160 -0
  120. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  121. data/lib/active_record/encryption/key.rb +28 -0
  122. data/lib/active_record/encryption/key_generator.rb +42 -0
  123. data/lib/active_record/encryption/key_provider.rb +46 -0
  124. data/lib/active_record/encryption/message.rb +33 -0
  125. data/lib/active_record/encryption/message_serializer.rb +90 -0
  126. data/lib/active_record/encryption/null_encryptor.rb +21 -0
  127. data/lib/active_record/encryption/properties.rb +76 -0
  128. data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
  129. data/lib/active_record/encryption/scheme.rb +99 -0
  130. data/lib/active_record/encryption.rb +55 -0
  131. data/lib/active_record/enum.rb +50 -43
  132. data/lib/active_record/errors.rb +67 -4
  133. data/lib/active_record/explain_registry.rb +11 -6
  134. data/lib/active_record/fixture_set/file.rb +15 -1
  135. data/lib/active_record/fixture_set/table_row.rb +41 -6
  136. data/lib/active_record/fixture_set/table_rows.rb +4 -4
  137. data/lib/active_record/fixtures.rb +20 -23
  138. data/lib/active_record/future_result.rb +139 -0
  139. data/lib/active_record/gem_version.rb +4 -4
  140. data/lib/active_record/inheritance.rb +55 -17
  141. data/lib/active_record/insert_all.rb +80 -14
  142. data/lib/active_record/integration.rb +4 -3
  143. data/lib/active_record/internal_metadata.rb +1 -5
  144. data/lib/active_record/legacy_yaml_adapter.rb +2 -39
  145. data/lib/active_record/locking/optimistic.rb +10 -9
  146. data/lib/active_record/locking/pessimistic.rb +10 -4
  147. data/lib/active_record/log_subscriber.rb +23 -7
  148. data/lib/active_record/middleware/database_selector/resolver.rb +6 -10
  149. data/lib/active_record/middleware/database_selector.rb +18 -6
  150. data/lib/active_record/middleware/shard_selector.rb +60 -0
  151. data/lib/active_record/migration/command_recorder.rb +7 -7
  152. data/lib/active_record/migration/compatibility.rb +84 -2
  153. data/lib/active_record/migration/join_table.rb +1 -1
  154. data/lib/active_record/migration.rb +114 -83
  155. data/lib/active_record/model_schema.rb +58 -59
  156. data/lib/active_record/nested_attributes.rb +13 -12
  157. data/lib/active_record/no_touching.rb +3 -3
  158. data/lib/active_record/null_relation.rb +2 -6
  159. data/lib/active_record/persistence.rb +228 -60
  160. data/lib/active_record/query_cache.rb +2 -2
  161. data/lib/active_record/query_logs.rb +149 -0
  162. data/lib/active_record/querying.rb +16 -6
  163. data/lib/active_record/railtie.rb +136 -22
  164. data/lib/active_record/railties/controller_runtime.rb +1 -1
  165. data/lib/active_record/railties/databases.rake +78 -136
  166. data/lib/active_record/readonly_attributes.rb +11 -0
  167. data/lib/active_record/reflection.rb +73 -50
  168. data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
  169. data/lib/active_record/relation/batches.rb +6 -6
  170. data/lib/active_record/relation/calculations.rb +43 -38
  171. data/lib/active_record/relation/delegation.rb +7 -7
  172. data/lib/active_record/relation/finder_methods.rb +31 -35
  173. data/lib/active_record/relation/merger.rb +20 -13
  174. data/lib/active_record/relation/predicate_builder.rb +1 -6
  175. data/lib/active_record/relation/query_attribute.rb +5 -11
  176. data/lib/active_record/relation/query_methods.rb +276 -67
  177. data/lib/active_record/relation/record_fetch_warning.rb +7 -9
  178. data/lib/active_record/relation/spawn_methods.rb +2 -2
  179. data/lib/active_record/relation/where_clause.rb +10 -19
  180. data/lib/active_record/relation.rb +189 -88
  181. data/lib/active_record/result.rb +17 -7
  182. data/lib/active_record/runtime_registry.rb +9 -13
  183. data/lib/active_record/sanitization.rb +17 -12
  184. data/lib/active_record/schema.rb +38 -23
  185. data/lib/active_record/schema_dumper.rb +25 -19
  186. data/lib/active_record/schema_migration.rb +4 -4
  187. data/lib/active_record/scoping/default.rb +60 -13
  188. data/lib/active_record/scoping/named.rb +3 -11
  189. data/lib/active_record/scoping.rb +64 -34
  190. data/lib/active_record/serialization.rb +6 -1
  191. data/lib/active_record/signed_id.rb +3 -3
  192. data/lib/active_record/store.rb +1 -1
  193. data/lib/active_record/suppressor.rb +11 -15
  194. data/lib/active_record/tasks/database_tasks.rb +127 -60
  195. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  196. data/lib/active_record/tasks/postgresql_database_tasks.rb +19 -13
  197. data/lib/active_record/test_databases.rb +1 -1
  198. data/lib/active_record/test_fixtures.rb +9 -6
  199. data/lib/active_record/timestamp.rb +3 -4
  200. data/lib/active_record/transactions.rb +9 -14
  201. data/lib/active_record/translation.rb +3 -3
  202. data/lib/active_record/type/adapter_specific_registry.rb +32 -7
  203. data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
  204. data/lib/active_record/type/internal/timezone.rb +2 -2
  205. data/lib/active_record/type/serialized.rb +1 -1
  206. data/lib/active_record/type/type_map.rb +17 -20
  207. data/lib/active_record/type.rb +1 -2
  208. data/lib/active_record/validations/associated.rb +4 -4
  209. data/lib/active_record/validations/presence.rb +2 -2
  210. data/lib/active_record/validations/uniqueness.rb +4 -4
  211. data/lib/active_record/version.rb +1 -1
  212. data/lib/active_record.rb +225 -27
  213. data/lib/arel/attributes/attribute.rb +0 -8
  214. data/lib/arel/crud.rb +28 -22
  215. data/lib/arel/delete_manager.rb +18 -4
  216. data/lib/arel/filter_predications.rb +9 -0
  217. data/lib/arel/insert_manager.rb +2 -3
  218. data/lib/arel/nodes/casted.rb +1 -1
  219. data/lib/arel/nodes/delete_statement.rb +12 -13
  220. data/lib/arel/nodes/filter.rb +10 -0
  221. data/lib/arel/nodes/function.rb +1 -0
  222. data/lib/arel/nodes/insert_statement.rb +2 -2
  223. data/lib/arel/nodes/select_core.rb +2 -2
  224. data/lib/arel/nodes/select_statement.rb +2 -2
  225. data/lib/arel/nodes/update_statement.rb +8 -3
  226. data/lib/arel/nodes.rb +1 -0
  227. data/lib/arel/predications.rb +11 -3
  228. data/lib/arel/select_manager.rb +10 -4
  229. data/lib/arel/table.rb +0 -1
  230. data/lib/arel/tree_manager.rb +0 -12
  231. data/lib/arel/update_manager.rb +18 -4
  232. data/lib/arel/visitors/dot.rb +80 -90
  233. data/lib/arel/visitors/mysql.rb +8 -2
  234. data/lib/arel/visitors/postgresql.rb +0 -10
  235. data/lib/arel/visitors/to_sql.rb +58 -2
  236. data/lib/arel.rb +2 -1
  237. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  238. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  239. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  240. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  241. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  242. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  243. metadata +53 -9
@@ -1,25 +1,41 @@
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
@@ -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,8 +50,8 @@ 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|
@@ -68,7 +67,7 @@ module ActiveRecord
68
67
  end
69
68
  end
70
69
 
71
- def exec_delete(sql, name = nil, binds = [])
70
+ def exec_delete(sql, name = nil, binds = []) # :nodoc:
72
71
  execute_and_clear(sql, name, binds) { |result| result.cmd_tuples }
73
72
  end
74
73
  alias :exec_update :exec_delete
@@ -88,7 +87,7 @@ module ActiveRecord
88
87
  end
89
88
  private :sql_for_insert
90
89
 
91
- def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil)
90
+ def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil) # :nodoc:
92
91
  if use_insert_returning? || pk == false
93
92
  super
94
93
  else
@@ -107,25 +106,33 @@ module ActiveRecord
107
106
  end
108
107
 
109
108
  # Begins a transaction.
110
- def begin_db_transaction
109
+ def begin_db_transaction # :nodoc:
111
110
  execute("BEGIN", "TRANSACTION")
112
111
  end
113
112
 
114
- def begin_isolated_db_transaction(isolation)
113
+ def begin_isolated_db_transaction(isolation) # :nodoc:
115
114
  begin_db_transaction
116
115
  execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}"
117
116
  end
118
117
 
119
118
  # Commits a transaction.
120
- def commit_db_transaction
119
+ def commit_db_transaction # :nodoc:
121
120
  execute("COMMIT", "TRANSACTION")
122
121
  end
123
122
 
124
123
  # Aborts a transaction.
125
- def exec_rollback_db_transaction
124
+ def exec_rollback_db_transaction # :nodoc:
126
125
  execute("ROLLBACK", "TRANSACTION")
127
126
  end
128
127
 
128
+ # From https://www.postgresql.org/docs/current/functions-datetime.html#FUNCTIONS-DATETIME-CURRENT
129
+ HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP").freeze # :nodoc:
130
+ private_constant :HIGH_PRECISION_CURRENT_TIMESTAMP
131
+
132
+ def high_precision_current_timestamp
133
+ HIGH_PRECISION_CURRENT_TIMESTAMP
134
+ end
135
+
129
136
  private
130
137
  def execute_batch(statements, name = nil)
131
138
  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(/^(\\[\\"]|[^\\"])*?(?=")/)
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)
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"
@@ -22,9 +22,54 @@ module ActiveRecord
22
22
  @connection.unescape_bytea(value) if value
23
23
  end
24
24
 
25
+ def check_int_in_range(value)
26
+ if value.to_int > 9223372036854775807 || value.to_int < -9223372036854775808
27
+ exception = <<~ERROR
28
+ Provided value outside of the range of a signed 64bit integer.
29
+
30
+ PostgreSQL will treat the column type in question as a numeric.
31
+ This may result in a slow sequential scan due to a comparison
32
+ being performed between an integer or bigint value and a numeric value.
33
+
34
+ To allow for this potentially unwanted behavior, set
35
+ ActiveRecord.raise_int_wider_than_64bit to false.
36
+ ERROR
37
+ raise IntegerOutOf64BitRange.new exception
38
+ end
39
+ end
40
+
41
+ def quote(value) # :nodoc:
42
+ if ActiveRecord.raise_int_wider_than_64bit && value.is_a?(Integer)
43
+ check_int_in_range(value)
44
+ end
45
+
46
+ case value
47
+ when OID::Xml::Data
48
+ "xml '#{quote_string(value.to_s)}'"
49
+ when OID::Bit::Data
50
+ if value.binary?
51
+ "B'#{value}'"
52
+ elsif value.hex?
53
+ "X'#{value}'"
54
+ end
55
+ when Numeric
56
+ if value.finite?
57
+ super
58
+ else
59
+ "'#{value}'"
60
+ end
61
+ when OID::Array::Data
62
+ quote(encode_array(value))
63
+ when Range
64
+ quote(encode_range(value))
65
+ else
66
+ super
67
+ end
68
+ end
69
+
25
70
  # Quotes strings for use in SQL input.
26
- def quote_string(s) #:nodoc:
27
- PG::Connection.escape(s)
71
+ def quote_string(s) # :nodoc:
72
+ @connection.escape(s)
28
73
  end
29
74
 
30
75
  # Checks the following cases:
@@ -54,7 +99,7 @@ module ActiveRecord
54
99
  end
55
100
 
56
101
  # Quote date/time values for use in SQL input.
57
- def quoted_date(value) #:nodoc:
102
+ def quoted_date(value) # :nodoc:
58
103
  if value.year <= 0
59
104
  bce_year = format("%04d", -value.year + 1)
60
105
  super.sub(/^-?\d+/, bce_year) + " BC"
@@ -80,6 +125,24 @@ module ActiveRecord
80
125
  end
81
126
  end
82
127
 
128
+ def type_cast(value) # :nodoc:
129
+ case value
130
+ when Type::Binary::Data
131
+ # Return a bind param hash with format as binary.
132
+ # See https://deveiate.org/code/pg/PG/Connection.html#method-i-exec_prepared-doc
133
+ # for more information
134
+ { value: value.to_s, format: 1 }
135
+ when OID::Xml::Data, OID::Bit::Data
136
+ value.to_s
137
+ when OID::Array::Data
138
+ encode_array(value)
139
+ when Range
140
+ encode_range(value)
141
+ else
142
+ super
143
+ end
144
+ end
145
+
83
146
  def lookup_cast_type_from_column(column) # :nodoc:
84
147
  type_map.lookup(column.oid, column.fmod, column.sql_type)
85
148
  end
@@ -96,8 +159,8 @@ module ActiveRecord
96
159
  \A
97
160
  (
98
161
  (?:
99
- # "table_name"."column_name"::type_name | function(one or no argument)::type_name
100
- ((?:\w+\.|"\w+"\.)?(?:\w+|"\w+")(?:::\w+)?) | \w+\((?:|\g<2>)\)(?:::\w+)?
162
+ # "schema_name"."table_name"."column_name"::type_name | function(one or no argument)::type_name
163
+ ((?:\w+\.|"\w+"\.){,2}(?:\w+|"\w+")(?:::\w+)?) | \w+\((?:|\g<2>)\)(?:::\w+)?
101
164
  )
102
165
  (?:(?:\s+AS)?\s+(?:\w+|"\w+"))?
103
166
  )
@@ -109,8 +172,8 @@ module ActiveRecord
109
172
  \A
110
173
  (
111
174
  (?:
112
- # "table_name"."column_name"::type_name | function(one or no argument)::type_name
113
- ((?:\w+\.|"\w+"\.)?(?:\w+|"\w+")(?:::\w+)?) | \w+\((?:|\g<2>)\)(?:::\w+)?
175
+ # "schema_name"."table_name"."column_name"::type_name | function(one or no argument)::type_name
176
+ ((?:\w+\.|"\w+"\.){,2}(?:\w+|"\w+")(?:::\w+)?) | \w+\((?:|\g<2>)\)(?:::\w+)?
114
177
  )
115
178
  (?:\s+ASC|\s+DESC)?
116
179
  (?:\s+NULLS\s+(?:FIRST|LAST))?
@@ -126,69 +189,6 @@ module ActiveRecord
126
189
  super(query_value("SELECT #{quote(sql_type)}::regtype::oid", "SCHEMA").to_i)
127
190
  end
128
191
 
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
- 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
- case value
151
- when OID::Xml::Data
152
- "xml '#{quote_string(value.to_s)}'"
153
- when OID::Bit::Data
154
- if value.binary?
155
- "B'#{value}'"
156
- elsif value.hex?
157
- "X'#{value}'"
158
- end
159
- when Numeric
160
- if value.finite?
161
- super
162
- else
163
- "'#{value}'"
164
- end
165
- when OID::Array::Data
166
- _quote(encode_array(value))
167
- when Range
168
- _quote(encode_range(value))
169
- else
170
- super
171
- end
172
- end
173
-
174
- def _type_cast(value)
175
- case value
176
- when Type::Binary::Data
177
- # Return a bind param hash with format as binary.
178
- # See https://deveiate.org/code/pg/PG/Connection.html#method-i-exec_prepared-doc
179
- # for more information
180
- { value: value.to_s, format: 1 }
181
- when OID::Xml::Data, OID::Bit::Data
182
- value.to_s
183
- when OID::Array::Data
184
- encode_array(value)
185
- when Range
186
- encode_range(value)
187
- else
188
- super
189
- end
190
- end
191
-
192
192
  def encode_array(array_data)
193
193
  encoder = array_data.encoder
194
194
  values = type_cast_array(array_data.values)
@@ -214,7 +214,7 @@ module ActiveRecord
214
214
  def type_cast_array(values)
215
215
  case values
216
216
  when ::Array then values.map { |item| type_cast_array(item) }
217
- else _type_cast(values)
217
+ else type_cast(values)
218
218
  end
219
219
  end
220
220
 
@@ -37,6 +37,40 @@ 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'' AND connamespace::regnamespace = ''%I''::regnamespace; ALTER TABLE %I.%I VALIDATE CONSTRAINT %I;',
49
+ constraint_name,
50
+ table_schema,
51
+ table_schema,
52
+ table_name,
53
+ constraint_name
54
+ ) AS constraint_check
55
+ FROM information_schema.table_constraints WHERE constraint_type = 'FOREIGN KEY'
56
+ )
57
+ LOOP
58
+ EXECUTE (r.constraint_check);
59
+ END LOOP;
60
+ END;
61
+ $$;
62
+ SQL
63
+
64
+ begin
65
+ transaction(requires_new: true) do
66
+ execute(sql)
67
+ end
68
+
69
+ true
70
+ rescue ActiveRecord::StatementInvalid
71
+ false
72
+ end
73
+ end
40
74
  end
41
75
  end
42
76
  end
@@ -10,7 +10,14 @@ module ActiveRecord
10
10
  end
11
11
 
12
12
  def visit_AddForeignKey(o)
13
- super.dup.tap { |sql| sql << " NOT VALID" unless o.validate? }
13
+ super.dup.tap do |sql|
14
+ if o.deferrable
15
+ sql << " DEFERRABLE"
16
+ sql << " INITIALLY #{o.deferrable.to_s.upcase}" unless o.deferrable == true
17
+ end
18
+
19
+ sql << " NOT VALID" unless o.validate?
20
+ end
14
21
  end
15
22
 
16
23
  def visit_CheckConstraintDefinition(o)
@@ -61,6 +68,19 @@ module ActiveRecord
61
68
  if options[:collation]
62
69
  sql << " COLLATE \"#{options[:collation]}\""
63
70
  end
71
+
72
+ if as = options[:as]
73
+ sql << " GENERATED ALWAYS AS (#{as})"
74
+
75
+ if options[:stored]
76
+ sql << " STORED"
77
+ else
78
+ raise ArgumentError, <<~MSG
79
+ PostgreSQL currently does not support VIRTUAL (not persisted) generated columns.
80
+ Specify 'stored: true' option for '#{options[:column].name}'
81
+ MSG
82
+ end
83
+ end
64
84
  super
65
85
  end
66
86
 
@@ -173,11 +173,19 @@ module ActiveRecord
173
173
  # :method: xml
174
174
  # :call-seq: xml(*names, **options)
175
175
 
176
+ ##
177
+ # :method: timestamptz
178
+ # :call-seq: timestamptz(*names, **options)
179
+
180
+ ##
181
+ # :method: enum
182
+ # :call-seq: enum(*names, **options)
183
+
176
184
  included do
177
185
  define_column_methods :bigserial, :bit, :bit_varying, :cidr, :citext, :daterange,
178
186
  :hstore, :inet, :interval, :int4range, :int8range, :jsonb, :ltree, :macaddr,
179
187
  :money, :numrange, :oid, :point, :line, :lseg, :box, :path, :polygon, :circle,
180
- :serial, :tsrange, :tstzrange, :tsvector, :uuid, :xml
188
+ :serial, :tsrange, :tstzrange, :tsvector, :uuid, :xml, :timestamptz, :enum
181
189
  end
182
190
  end
183
191
 
@@ -191,7 +199,20 @@ module ActiveRecord
191
199
  @unlogged = ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.create_unlogged_tables
192
200
  end
193
201
 
202
+ def new_column_definition(name, type, **options) # :nodoc:
203
+ case type
204
+ when :virtual
205
+ type = options[:type]
206
+ end
207
+
208
+ super
209
+ end
210
+
194
211
  private
212
+ def aliased_types(name, fallback)
213
+ fallback
214
+ end
215
+
195
216
  def integer_like_primary_key_type(type, options)
196
217
  if type == :bigint || options[:limit] == 8
197
218
  :bigserial