activerecord 6.0.6.1 → 6.1.7.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (243) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1152 -779
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +2 -2
  5. data/lib/active_record/aggregations.rb +5 -5
  6. data/lib/active_record/association_relation.rb +30 -12
  7. data/lib/active_record/associations/alias_tracker.rb +19 -15
  8. data/lib/active_record/associations/association.rb +49 -26
  9. data/lib/active_record/associations/association_scope.rb +18 -20
  10. data/lib/active_record/associations/belongs_to_association.rb +23 -10
  11. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +8 -3
  12. data/lib/active_record/associations/builder/association.rb +32 -5
  13. data/lib/active_record/associations/builder/belongs_to.rb +10 -7
  14. data/lib/active_record/associations/builder/collection_association.rb +5 -4
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +0 -1
  16. data/lib/active_record/associations/builder/has_many.rb +6 -2
  17. data/lib/active_record/associations/builder/has_one.rb +11 -14
  18. data/lib/active_record/associations/builder/singular_association.rb +1 -1
  19. data/lib/active_record/associations/collection_association.rb +32 -18
  20. data/lib/active_record/associations/collection_proxy.rb +12 -5
  21. data/lib/active_record/associations/foreign_association.rb +13 -0
  22. data/lib/active_record/associations/has_many_association.rb +24 -2
  23. data/lib/active_record/associations/has_many_through_association.rb +10 -4
  24. data/lib/active_record/associations/has_one_association.rb +15 -1
  25. data/lib/active_record/associations/join_dependency/join_association.rb +37 -21
  26. data/lib/active_record/associations/join_dependency/join_part.rb +1 -1
  27. data/lib/active_record/associations/join_dependency.rb +63 -49
  28. data/lib/active_record/associations/preloader/association.rb +14 -8
  29. data/lib/active_record/associations/preloader/through_association.rb +1 -1
  30. data/lib/active_record/associations/preloader.rb +5 -3
  31. data/lib/active_record/associations/singular_association.rb +1 -1
  32. data/lib/active_record/associations.rb +118 -11
  33. data/lib/active_record/attribute_assignment.rb +10 -8
  34. data/lib/active_record/attribute_methods/before_type_cast.rb +13 -9
  35. data/lib/active_record/attribute_methods/dirty.rb +1 -11
  36. data/lib/active_record/attribute_methods/primary_key.rb +6 -2
  37. data/lib/active_record/attribute_methods/query.rb +3 -6
  38. data/lib/active_record/attribute_methods/read.rb +8 -11
  39. data/lib/active_record/attribute_methods/serialization.rb +11 -5
  40. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -13
  41. data/lib/active_record/attribute_methods/write.rb +12 -20
  42. data/lib/active_record/attribute_methods.rb +64 -54
  43. data/lib/active_record/attributes.rb +33 -8
  44. data/lib/active_record/autosave_association.rb +47 -30
  45. data/lib/active_record/base.rb +2 -14
  46. data/lib/active_record/callbacks.rb +152 -22
  47. data/lib/active_record/coders/yaml_column.rb +1 -1
  48. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +185 -134
  49. data/lib/active_record/connection_adapters/abstract/database_limits.rb +2 -44
  50. data/lib/active_record/connection_adapters/abstract/database_statements.rb +66 -23
  51. data/lib/active_record/connection_adapters/abstract/query_cache.rb +3 -8
  52. data/lib/active_record/connection_adapters/abstract/quoting.rb +34 -34
  53. data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
  54. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +153 -116
  55. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +114 -26
  56. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +3 -3
  57. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +228 -83
  58. data/lib/active_record/connection_adapters/abstract/transaction.rb +92 -33
  59. data/lib/active_record/connection_adapters/abstract_adapter.rb +52 -76
  60. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +123 -87
  61. data/lib/active_record/connection_adapters/column.rb +15 -1
  62. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  63. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +35 -0
  64. data/lib/active_record/connection_adapters/mysql/database_statements.rb +24 -24
  65. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -1
  66. data/lib/active_record/connection_adapters/mysql/quoting.rb +18 -3
  67. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +32 -6
  68. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +8 -0
  69. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +5 -2
  70. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +7 -4
  71. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +10 -1
  72. data/lib/active_record/connection_adapters/mysql2_adapter.rb +31 -12
  73. data/lib/active_record/connection_adapters/pool_config.rb +73 -0
  74. data/lib/active_record/connection_adapters/pool_manager.rb +47 -0
  75. data/lib/active_record/connection_adapters/postgresql/column.rb +24 -1
  76. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +14 -53
  77. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -5
  78. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +2 -2
  79. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +2 -2
  80. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  81. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +2 -2
  82. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +2 -2
  84. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +11 -1
  85. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  86. data/lib/active_record/connection_adapters/postgresql/quoting.rb +30 -4
  87. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +1 -1
  88. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +5 -1
  89. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +61 -29
  90. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +8 -0
  91. data/lib/active_record/connection_adapters/postgresql_adapter.rb +75 -64
  92. data/lib/active_record/connection_adapters/schema_cache.rb +130 -15
  93. data/lib/active_record/connection_adapters/sql_type_metadata.rb +8 -0
  94. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +32 -5
  95. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +1 -1
  96. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -1
  97. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +36 -3
  98. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +48 -50
  99. data/lib/active_record/connection_adapters.rb +52 -0
  100. data/lib/active_record/connection_handling.rb +218 -71
  101. data/lib/active_record/core.rb +264 -63
  102. data/lib/active_record/database_configurations/connection_url_resolver.rb +99 -0
  103. data/lib/active_record/database_configurations/database_config.rb +52 -9
  104. data/lib/active_record/database_configurations/hash_config.rb +54 -8
  105. data/lib/active_record/database_configurations/url_config.rb +15 -40
  106. data/lib/active_record/database_configurations.rb +125 -85
  107. data/lib/active_record/delegated_type.rb +209 -0
  108. data/lib/active_record/destroy_association_async_job.rb +36 -0
  109. data/lib/active_record/enum.rb +69 -34
  110. data/lib/active_record/errors.rb +47 -12
  111. data/lib/active_record/explain.rb +9 -4
  112. data/lib/active_record/explain_subscriber.rb +1 -1
  113. data/lib/active_record/fixture_set/file.rb +10 -17
  114. data/lib/active_record/fixture_set/model_metadata.rb +1 -2
  115. data/lib/active_record/fixture_set/render_context.rb +1 -1
  116. data/lib/active_record/fixture_set/table_row.rb +2 -2
  117. data/lib/active_record/fixtures.rb +58 -9
  118. data/lib/active_record/gem_version.rb +3 -3
  119. data/lib/active_record/inheritance.rb +40 -18
  120. data/lib/active_record/insert_all.rb +38 -5
  121. data/lib/active_record/integration.rb +3 -5
  122. data/lib/active_record/internal_metadata.rb +18 -7
  123. data/lib/active_record/legacy_yaml_adapter.rb +7 -3
  124. data/lib/active_record/locking/optimistic.rb +24 -17
  125. data/lib/active_record/locking/pessimistic.rb +6 -2
  126. data/lib/active_record/log_subscriber.rb +27 -8
  127. data/lib/active_record/middleware/database_selector/resolver/session.rb +3 -0
  128. data/lib/active_record/middleware/database_selector/resolver.rb +5 -0
  129. data/lib/active_record/middleware/database_selector.rb +4 -1
  130. data/lib/active_record/migration/command_recorder.rb +47 -27
  131. data/lib/active_record/migration/compatibility.rb +72 -18
  132. data/lib/active_record/migration.rb +114 -84
  133. data/lib/active_record/model_schema.rb +89 -14
  134. data/lib/active_record/nested_attributes.rb +2 -3
  135. data/lib/active_record/no_touching.rb +1 -1
  136. data/lib/active_record/persistence.rb +50 -45
  137. data/lib/active_record/query_cache.rb +15 -5
  138. data/lib/active_record/querying.rb +11 -6
  139. data/lib/active_record/railtie.rb +64 -44
  140. data/lib/active_record/railties/console_sandbox.rb +2 -4
  141. data/lib/active_record/railties/databases.rake +279 -101
  142. data/lib/active_record/readonly_attributes.rb +4 -0
  143. data/lib/active_record/reflection.rb +60 -44
  144. data/lib/active_record/relation/batches/batch_enumerator.rb +25 -9
  145. data/lib/active_record/relation/batches.rb +38 -31
  146. data/lib/active_record/relation/calculations.rb +104 -43
  147. data/lib/active_record/relation/finder_methods.rb +44 -14
  148. data/lib/active_record/relation/from_clause.rb +1 -1
  149. data/lib/active_record/relation/merger.rb +20 -23
  150. data/lib/active_record/relation/predicate_builder/array_handler.rb +8 -9
  151. data/lib/active_record/relation/predicate_builder/association_query_value.rb +4 -5
  152. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -6
  153. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  154. data/lib/active_record/relation/predicate_builder.rb +61 -38
  155. data/lib/active_record/relation/query_methods.rb +322 -196
  156. data/lib/active_record/relation/record_fetch_warning.rb +3 -3
  157. data/lib/active_record/relation/spawn_methods.rb +8 -7
  158. data/lib/active_record/relation/where_clause.rb +111 -61
  159. data/lib/active_record/relation.rb +100 -81
  160. data/lib/active_record/result.rb +41 -33
  161. data/lib/active_record/runtime_registry.rb +2 -2
  162. data/lib/active_record/sanitization.rb +6 -17
  163. data/lib/active_record/schema_dumper.rb +34 -4
  164. data/lib/active_record/schema_migration.rb +2 -8
  165. data/lib/active_record/scoping/default.rb +1 -3
  166. data/lib/active_record/scoping/named.rb +1 -17
  167. data/lib/active_record/secure_token.rb +16 -8
  168. data/lib/active_record/serialization.rb +5 -3
  169. data/lib/active_record/signed_id.rb +116 -0
  170. data/lib/active_record/statement_cache.rb +20 -4
  171. data/lib/active_record/store.rb +8 -3
  172. data/lib/active_record/suppressor.rb +2 -2
  173. data/lib/active_record/table_metadata.rb +42 -51
  174. data/lib/active_record/tasks/database_tasks.rb +140 -113
  175. data/lib/active_record/tasks/mysql_database_tasks.rb +34 -35
  176. data/lib/active_record/tasks/postgresql_database_tasks.rb +24 -26
  177. data/lib/active_record/tasks/sqlite_database_tasks.rb +13 -9
  178. data/lib/active_record/test_databases.rb +5 -4
  179. data/lib/active_record/test_fixtures.rb +79 -31
  180. data/lib/active_record/timestamp.rb +4 -6
  181. data/lib/active_record/touch_later.rb +21 -21
  182. data/lib/active_record/transactions.rb +19 -66
  183. data/lib/active_record/type/serialized.rb +6 -2
  184. data/lib/active_record/type.rb +8 -1
  185. data/lib/active_record/type_caster/connection.rb +0 -1
  186. data/lib/active_record/type_caster/map.rb +8 -5
  187. data/lib/active_record/validations/associated.rb +1 -1
  188. data/lib/active_record/validations/numericality.rb +35 -0
  189. data/lib/active_record/validations/uniqueness.rb +24 -4
  190. data/lib/active_record/validations.rb +1 -0
  191. data/lib/active_record.rb +7 -14
  192. data/lib/arel/attributes/attribute.rb +4 -0
  193. data/lib/arel/collectors/bind.rb +5 -0
  194. data/lib/arel/collectors/composite.rb +8 -0
  195. data/lib/arel/collectors/sql_string.rb +7 -0
  196. data/lib/arel/collectors/substitute_binds.rb +7 -0
  197. data/lib/arel/nodes/binary.rb +82 -8
  198. data/lib/arel/nodes/bind_param.rb +8 -0
  199. data/lib/arel/nodes/casted.rb +21 -9
  200. data/lib/arel/nodes/equality.rb +6 -9
  201. data/lib/arel/nodes/grouping.rb +3 -0
  202. data/lib/arel/nodes/homogeneous_in.rb +76 -0
  203. data/lib/arel/nodes/in.rb +8 -1
  204. data/lib/arel/nodes/infix_operation.rb +13 -1
  205. data/lib/arel/nodes/join_source.rb +1 -1
  206. data/lib/arel/nodes/node.rb +7 -6
  207. data/lib/arel/nodes/ordering.rb +27 -0
  208. data/lib/arel/nodes/sql_literal.rb +3 -0
  209. data/lib/arel/nodes/table_alias.rb +7 -3
  210. data/lib/arel/nodes/unary.rb +0 -1
  211. data/lib/arel/nodes.rb +3 -1
  212. data/lib/arel/predications.rb +12 -18
  213. data/lib/arel/select_manager.rb +1 -2
  214. data/lib/arel/table.rb +13 -5
  215. data/lib/arel/visitors/dot.rb +14 -2
  216. data/lib/arel/visitors/mysql.rb +11 -1
  217. data/lib/arel/visitors/postgresql.rb +15 -4
  218. data/lib/arel/visitors/to_sql.rb +89 -78
  219. data/lib/arel/visitors.rb +0 -7
  220. data/lib/arel.rb +5 -13
  221. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -0
  222. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +2 -0
  223. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +3 -3
  224. data/lib/rails/generators/active_record/migration.rb +6 -1
  225. data/lib/rails/generators/active_record/model/model_generator.rb +39 -2
  226. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  227. metadata +25 -26
  228. data/lib/active_record/advisory_lock_base.rb +0 -18
  229. data/lib/active_record/attribute_decorators.rb +0 -88
  230. data/lib/active_record/connection_adapters/connection_specification.rb +0 -296
  231. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -29
  232. data/lib/active_record/define_callbacks.rb +0 -22
  233. data/lib/active_record/railties/collection_cache_association_loading.rb +0 -34
  234. data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -18
  235. data/lib/active_record/relation/where_clause_factory.rb +0 -33
  236. data/lib/arel/attributes.rb +0 -22
  237. data/lib/arel/visitors/depth_first.rb +0 -203
  238. data/lib/arel/visitors/ibm_db.rb +0 -34
  239. data/lib/arel/visitors/informix.rb +0 -62
  240. data/lib/arel/visitors/mssql.rb +0 -156
  241. data/lib/arel/visitors/oracle.rb +0 -158
  242. data/lib/arel/visitors/oracle12.rb +0 -65
  243. data/lib/arel/visitors/where_sql.rb +0 -22
@@ -1,17 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Make sure we're using pg high enough for type casts and Ruby 2.2+ compatibility
4
- gem "pg", ">= 0.18", "< 2.0"
3
+ gem "pg", "~> 1.1"
5
4
  require "pg"
6
5
 
7
- # Use async_exec instead of exec_params on pg versions before 1.1
8
- class ::PG::Connection # :nodoc:
9
- unless self.public_method_defined?(:async_exec_params)
10
- remove_method :exec_params
11
- alias exec_params async_exec
12
- end
13
- end
14
-
6
+ require "active_support/core_ext/object/try"
15
7
  require "active_record/connection_adapters/abstract_adapter"
16
8
  require "active_record/connection_adapters/statement_pool"
17
9
  require "active_record/connection_adapters/postgresql/column"
@@ -31,9 +23,7 @@ module ActiveRecord
31
23
  module ConnectionHandling # :nodoc:
32
24
  # Establishes a connection to the database that's used by all Active Record objects
33
25
  def postgresql_connection(config)
34
- conn_params = config.symbolize_keys
35
-
36
- conn_params.delete_if { |_, v| v.nil? }
26
+ conn_params = config.symbolize_keys.compact
37
27
 
38
28
  # Map ActiveRecords param names to PGs.
39
29
  conn_params[:user] = conn_params.delete(:username) if conn_params[:username]
@@ -43,19 +33,17 @@ module ActiveRecord
43
33
  valid_conn_param_keys = PG::Connection.conndefaults_hash.keys + [:requiressl]
44
34
  conn_params.slice!(*valid_conn_param_keys)
45
35
 
46
- conn = PG.connect(conn_params)
47
- ConnectionAdapters::PostgreSQLAdapter.new(conn, logger, conn_params, config)
48
- rescue ::PG::Error => error
49
- if error.message.include?(conn_params[:dbname])
50
- raise ActiveRecord::NoDatabaseError
51
- else
52
- raise
53
- end
36
+ ConnectionAdapters::PostgreSQLAdapter.new(
37
+ ConnectionAdapters::PostgreSQLAdapter.new_client(conn_params),
38
+ logger,
39
+ conn_params,
40
+ config,
41
+ )
54
42
  end
55
43
  end
56
44
 
57
45
  module ConnectionAdapters
58
- # The PostgreSQL adapter works with the native C (https://bitbucket.org/ged/ruby-pg) driver.
46
+ # The PostgreSQL adapter works with the native C (https://github.com/ged/ruby-pg) driver.
59
47
  #
60
48
  # Options:
61
49
  #
@@ -85,6 +73,18 @@ module ActiveRecord
85
73
  class PostgreSQLAdapter < AbstractAdapter
86
74
  ADAPTER_NAME = "PostgreSQL"
87
75
 
76
+ class << self
77
+ def new_client(conn_params)
78
+ PG.connect(**conn_params)
79
+ rescue ::PG::Error => error
80
+ if conn_params && conn_params[:dbname] && error.message.include?(conn_params[:dbname])
81
+ raise ActiveRecord::NoDatabaseError
82
+ else
83
+ raise ActiveRecord::ConnectionNotEstablished, error.message
84
+ end
85
+ end
86
+ end
87
+
88
88
  ##
89
89
  # :singleton-method:
90
90
  # PostgreSQL allows the creation of "unlogged" tables, which do not record
@@ -176,6 +176,10 @@ module ActiveRecord
176
176
  true
177
177
  end
178
178
 
179
+ def supports_check_constraints?
180
+ true
181
+ end
182
+
179
183
  def supports_validate_constraints?
180
184
  true
181
185
  end
@@ -223,11 +227,7 @@ module ActiveRecord
223
227
  end
224
228
 
225
229
  def next_key
226
- "a#{@counter + 1}"
227
- end
228
-
229
- def []=(sql, key)
230
- super.tap { @counter += 1 }
230
+ "a#{@counter += 1}"
231
231
  end
232
232
 
233
233
  private
@@ -247,7 +247,7 @@ module ActiveRecord
247
247
  def initialize(connection, logger, connection_parameters, config)
248
248
  super(connection, logger, config)
249
249
 
250
- @connection_parameters = connection_parameters
250
+ @connection_parameters = connection_parameters || {}
251
251
 
252
252
  # @local_tz is initialized as nil to avoid warnings when connect tries to use it
253
253
  @local_tz = nil
@@ -341,11 +341,6 @@ module ActiveRecord
341
341
  true
342
342
  end
343
343
 
344
- def supports_ranges?
345
- true
346
- end
347
- deprecate :supports_ranges?
348
-
349
344
  def supports_materialized_views?
350
345
  true
351
346
  end
@@ -426,16 +421,6 @@ module ActiveRecord
426
421
  @use_insert_returning
427
422
  end
428
423
 
429
- def column_name_for_operation(operation, node) # :nodoc:
430
- OPERATION_ALIASES.fetch(operation) { operation.downcase }
431
- end
432
-
433
- OPERATION_ALIASES = { # :nodoc:
434
- "maximum" => "max",
435
- "minimum" => "min",
436
- "average" => "avg",
437
- }
438
-
439
424
  # Returns the version of the connected PostgreSQL server.
440
425
  def get_database_version # :nodoc:
441
426
  @connection.server_version
@@ -453,6 +438,7 @@ module ActiveRecord
453
438
  sql << " ON CONFLICT #{insert.conflict_target} DO NOTHING"
454
439
  elsif insert.update_duplicates?
455
440
  sql << " ON CONFLICT #{insert.conflict_target} DO UPDATE SET "
441
+ sql << insert.touch_model_timestamps_unless { |column| "#{insert.model.quoted_table_name}.#{column} IS NOT DISTINCT FROM excluded.#{column}" }
456
442
  sql << insert.updatable_columns.map { |column| "#{column}=excluded.#{column}" }.join(",")
457
443
  end
458
444
 
@@ -475,6 +461,7 @@ module ActiveRecord
475
461
  UNIQUE_VIOLATION = "23505"
476
462
  SERIALIZATION_FAILURE = "40001"
477
463
  DEADLOCK_DETECTED = "40P01"
464
+ DUPLICATE_DATABASE = "42P04"
478
465
  LOCK_NOT_AVAILABLE = "55P03"
479
466
  QUERY_CANCELED = "57014"
480
467
 
@@ -482,6 +469,12 @@ module ActiveRecord
482
469
  return exception unless exception.respond_to?(:result)
483
470
 
484
471
  case exception.result.try(:error_field, PG::PG_DIAG_SQLSTATE)
472
+ when nil
473
+ if exception.message.match?(/connection is closed/i)
474
+ ConnectionNotEstablished.new(exception)
475
+ else
476
+ super
477
+ end
485
478
  when UNIQUE_VIOLATION
486
479
  RecordNotUnique.new(message, sql: sql, binds: binds)
487
480
  when FOREIGN_KEY_VIOLATION
@@ -496,6 +489,8 @@ module ActiveRecord
496
489
  SerializationFailure.new(message, sql: sql, binds: binds)
497
490
  when DEADLOCK_DETECTED
498
491
  Deadlocked.new(message, sql: sql, binds: binds)
492
+ when DUPLICATE_DATABASE
493
+ DatabaseAlreadyExists.new(message, sql: sql, binds: binds)
499
494
  when LOCK_NOT_AVAILABLE
500
495
  LockWaitTimeout.new(message, sql: sql, binds: binds)
501
496
  when QUERY_CANCELED
@@ -547,7 +542,7 @@ module ActiveRecord
547
542
  m.register_type "uuid", OID::Uuid.new
548
543
  m.register_type "xml", OID::Xml.new
549
544
  m.register_type "tsvector", OID::SpecializedString.new(:tsvector)
550
- m.register_type "macaddr", OID::SpecializedString.new(:macaddr)
545
+ m.register_type "macaddr", OID::Macaddr.new
551
546
  m.register_type "citext", OID::SpecializedString.new(:citext)
552
547
  m.register_type "ltree", OID::SpecializedString.new(:ltree)
553
548
  m.register_type "line", OID::SpecializedString.new(:line)
@@ -557,11 +552,6 @@ module ActiveRecord
557
552
  m.register_type "polygon", OID::SpecializedString.new(:polygon)
558
553
  m.register_type "circle", OID::SpecializedString.new(:circle)
559
554
 
560
- m.register_type "interval" do |_, _, sql_type|
561
- precision = extract_precision(sql_type)
562
- OID::SpecializedString.new(:interval, precision: precision)
563
- end
564
-
565
555
  register_class_with_precision m, "time", Type::Time
566
556
  register_class_with_precision m, "timestamp", OID::DateTime
567
557
 
@@ -585,6 +575,11 @@ module ActiveRecord
585
575
  end
586
576
  end
587
577
 
578
+ m.register_type "interval" do |*args, sql_type|
579
+ precision = extract_precision(sql_type)
580
+ OID::Interval.new(precision: precision)
581
+ end
582
+
588
583
  load_additional_types
589
584
  end
590
585
 
@@ -650,20 +645,22 @@ module ActiveRecord
650
645
  raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
651
646
  end
652
647
 
653
- if without_prepared_statement?(binds)
654
- result = exec_no_cache(sql, name, [])
655
- elsif !prepare
648
+ if !prepare || without_prepared_statement?(binds)
656
649
  result = exec_no_cache(sql, name, binds)
657
650
  else
658
651
  result = exec_cache(sql, name, binds)
659
652
  end
660
- ret = yield result
661
- result.clear
653
+ begin
654
+ ret = yield result
655
+ ensure
656
+ result.clear
657
+ end
662
658
  ret
663
659
  end
664
660
 
665
661
  def exec_no_cache(sql, name, binds)
666
662
  materialize_transactions
663
+ mark_transaction_written_if_write(sql)
667
664
 
668
665
  # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
669
666
  # made since we established the connection
@@ -679,6 +676,7 @@ module ActiveRecord
679
676
 
680
677
  def exec_cache(sql, name, binds)
681
678
  materialize_transactions
679
+ mark_transaction_written_if_write(sql)
682
680
  update_typemap_for_default_timezone
683
681
 
684
682
  stmt_key = prepare_statement(sql, binds)
@@ -714,11 +712,10 @@ module ActiveRecord
714
712
  #
715
713
  # Check here for more details:
716
714
  # https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
717
- CACHED_PLAN_HEURISTIC = "cached plan must not change result type"
718
715
  def is_cached_plan_failure?(e)
719
716
  pgerror = e.cause
720
- code = pgerror.result.result_error_field(PG::PG_DIAG_SQLSTATE)
721
- code == FEATURE_NOT_SUPPORTED && pgerror.message.include?(CACHED_PLAN_HEURISTIC)
717
+ pgerror.result.result_error_field(PG::PG_DIAG_SQLSTATE) == FEATURE_NOT_SUPPORTED &&
718
+ pgerror.result.result_error_field(PG::PG_DIAG_SOURCE_FUNCTION) == "RevalidateCachedQuery"
722
719
  rescue
723
720
  false
724
721
  end
@@ -756,7 +753,7 @@ module ActiveRecord
756
753
  # Connects to a PostgreSQL server and sets up the adapter depending on the
757
754
  # connected server's characteristics.
758
755
  def connect
759
- @connection = PG.connect(@connection_parameters)
756
+ @connection = self.class.new_client(@connection_parameters)
760
757
  configure_connection
761
758
  add_pg_encoders
762
759
  add_pg_decoders
@@ -786,6 +783,9 @@ module ActiveRecord
786
783
  end
787
784
  end
788
785
 
786
+ # Set interval output format to ISO 8601 for ease of parsing by ActiveSupport::Duration.parse
787
+ execute("SET intervalstyle = iso_8601", "SCHEMA")
788
+
789
789
  # SET statements from :variables config hash
790
790
  # https://www.postgresql.org/docs/current/static/sql-set.html
791
791
  variables.map do |k, v|
@@ -897,15 +897,12 @@ module ActiveRecord
897
897
  "oid" => PG::TextDecoder::Integer,
898
898
  "float4" => PG::TextDecoder::Float,
899
899
  "float8" => PG::TextDecoder::Float,
900
+ "numeric" => PG::TextDecoder::Numeric,
900
901
  "bool" => PG::TextDecoder::Boolean,
902
+ "timestamp" => PG::TextDecoder::TimestampUtc,
903
+ "timestamptz" => PG::TextDecoder::TimestampWithTimeZone,
901
904
  }
902
905
 
903
- if defined?(PG::TextDecoder::TimestampUtc)
904
- # Use native PG encoders available since pg-1.1
905
- coders_by_name["timestamp"] = PG::TextDecoder::TimestampUtc
906
- coders_by_name["timestamptz"] = PG::TextDecoder::TimestampWithTimeZone
907
- end
908
-
909
906
  known_coder_types = coders_by_name.keys.map { |n| quote(n) }
910
907
  query = <<~SQL % known_coder_types.join(", ")
911
908
  SELECT t.oid, t.typname
@@ -922,6 +919,11 @@ module ActiveRecord
922
919
  coders.each { |coder| map.add_coder(coder) }
923
920
  @connection.type_map_for_results = map
924
921
 
922
+ @type_map_for_results = PG::TypeMapByOid.new
923
+ @type_map_for_results.default_type_map = map
924
+ @type_map_for_results.add_coder(PG::TextDecoder::Bytea.new(oid: 17, name: "bytea"))
925
+ @type_map_for_results.add_coder(MoneyDecoder.new(oid: 790, name: "money"))
926
+
925
927
  # extract timestamp decoder for use in update_typemap_for_default_timezone
926
928
  @timestamp_decoder = coders.find { |coder| coder.name == "timestamp" }
927
929
  update_typemap_for_default_timezone
@@ -932,6 +934,14 @@ module ActiveRecord
932
934
  coder_class.new(oid: row["oid"].to_i, name: row["typname"])
933
935
  end
934
936
 
937
+ class MoneyDecoder < PG::SimpleDecoder # :nodoc:
938
+ TYPE = OID::Money.new
939
+
940
+ def decode(value, tuple = nil, field = nil)
941
+ TYPE.deserialize(value)
942
+ end
943
+ end
944
+
935
945
  ActiveRecord::Type.add_modifier({ array: true }, OID::Array, adapter: :postgresql)
936
946
  ActiveRecord::Type.add_modifier({ range: true }, OID::Range, adapter: :postgresql)
937
947
  ActiveRecord::Type.register(:bit, OID::Bit, adapter: :postgresql)
@@ -944,6 +954,7 @@ module ActiveRecord
944
954
  ActiveRecord::Type.register(:enum, OID::Enum, adapter: :postgresql)
945
955
  ActiveRecord::Type.register(:hstore, OID::Hstore, adapter: :postgresql)
946
956
  ActiveRecord::Type.register(:inet, OID::Inet, adapter: :postgresql)
957
+ ActiveRecord::Type.register(:interval, OID::Interval, adapter: :postgresql)
947
958
  ActiveRecord::Type.register(:jsonb, OID::Jsonb, adapter: :postgresql)
948
959
  ActiveRecord::Type.register(:money, OID::Money, adapter: :postgresql)
949
960
  ActiveRecord::Type.register(:point, OID::Point, adapter: :postgresql)
@@ -1,8 +1,37 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/core_ext/file/atomic"
4
+
3
5
  module ActiveRecord
4
6
  module ConnectionAdapters
5
7
  class SchemaCache
8
+ def self.load_from(filename)
9
+ return unless File.file?(filename)
10
+
11
+ read(filename) do |file|
12
+ if filename.include?(".dump")
13
+ Marshal.load(file)
14
+ else
15
+ if YAML.respond_to?(:unsafe_load)
16
+ YAML.unsafe_load(file)
17
+ else
18
+ YAML.load(file)
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ def self.read(filename, &block)
25
+ if File.extname(filename) == ".gz"
26
+ Zlib::GzipReader.open(filename) { |gz|
27
+ yield gz.read
28
+ }
29
+ else
30
+ yield File.read(filename)
31
+ end
32
+ end
33
+ private_class_method :read
34
+
6
35
  attr_reader :version
7
36
  attr_accessor :connection
8
37
 
@@ -26,27 +55,33 @@ module ActiveRecord
26
55
  end
27
56
 
28
57
  def encode_with(coder)
58
+ reset_version!
59
+
29
60
  coder["columns"] = @columns
30
- coder["columns_hash"] = @columns_hash
31
61
  coder["primary_keys"] = @primary_keys
32
62
  coder["data_sources"] = @data_sources
33
63
  coder["indexes"] = @indexes
34
- coder["version"] = connection.migration_context.current_version
64
+ coder["version"] = @version
35
65
  coder["database_version"] = database_version
36
66
  end
37
67
 
38
68
  def init_with(coder)
39
69
  @columns = coder["columns"]
40
- @columns_hash = coder["columns_hash"]
41
70
  @primary_keys = coder["primary_keys"]
42
71
  @data_sources = coder["data_sources"]
43
72
  @indexes = coder["indexes"] || {}
44
73
  @version = coder["version"]
45
74
  @database_version = coder["database_version"]
75
+
76
+ derive_columns_hash_and_deduplicate_values
46
77
  end
47
78
 
48
79
  def primary_keys(table_name)
49
- @primary_keys[table_name] ||= data_source_exists?(table_name) ? connection.primary_key(table_name) : nil
80
+ @primary_keys.fetch(table_name) do
81
+ if data_source_exists?(table_name)
82
+ @primary_keys[deep_deduplicate(table_name)] = deep_deduplicate(connection.primary_key(table_name))
83
+ end
84
+ end
50
85
  end
51
86
 
52
87
  # A cached lookup for table existence.
@@ -54,7 +89,7 @@ module ActiveRecord
54
89
  prepare_data_sources if @data_sources.empty?
55
90
  return @data_sources[name] if @data_sources.key? name
56
91
 
57
- @data_sources[name] = connection.data_source_exists?(name)
92
+ @data_sources[deep_deduplicate(name)] = connection.data_source_exists?(name)
58
93
  end
59
94
 
60
95
  # Add internal cache for table with +table_name+.
@@ -73,15 +108,17 @@ module ActiveRecord
73
108
 
74
109
  # Get the columns for a table
75
110
  def columns(table_name)
76
- @columns[table_name] ||= connection.columns(table_name)
111
+ @columns.fetch(table_name) do
112
+ @columns[deep_deduplicate(table_name)] = deep_deduplicate(connection.columns(table_name))
113
+ end
77
114
  end
78
115
 
79
116
  # Get the columns for a table as a hash, key is the column name
80
117
  # value is the column object.
81
118
  def columns_hash(table_name)
82
- @columns_hash[table_name] ||= Hash[columns(table_name).map { |col|
83
- [col.name, col]
84
- }]
119
+ @columns_hash.fetch(table_name) do
120
+ @columns_hash[deep_deduplicate(table_name)] = columns(table_name).index_by(&:name).freeze
121
+ end
85
122
  end
86
123
 
87
124
  # Checks whether the columns hash is already cached for a table.
@@ -90,7 +127,9 @@ module ActiveRecord
90
127
  end
91
128
 
92
129
  def indexes(table_name)
93
- @indexes[table_name] ||= connection.indexes(table_name)
130
+ @indexes.fetch(table_name) do
131
+ @indexes[deep_deduplicate(table_name)] = deep_deduplicate(connection.indexes(table_name))
132
+ end
94
133
  end
95
134
 
96
135
  def database_version # :nodoc:
@@ -121,21 +160,97 @@ module ActiveRecord
121
160
  @indexes.delete name
122
161
  end
123
162
 
163
+ def dump_to(filename)
164
+ clear!
165
+ connection.data_sources.each { |table| add(table) }
166
+ open(filename) { |f|
167
+ if filename.include?(".dump")
168
+ f.write(Marshal.dump(self))
169
+ else
170
+ f.write(YAML.dump(self))
171
+ end
172
+ }
173
+ end
174
+
124
175
  def marshal_dump
125
- # if we get current version during initialization, it happens stack over flow.
126
- @version = connection.migration_context.current_version
127
- [@version, @columns, @columns_hash, @primary_keys, @data_sources, @indexes, database_version]
176
+ reset_version!
177
+
178
+ [@version, @columns, {}, @primary_keys, @data_sources, @indexes, database_version]
128
179
  end
129
180
 
130
181
  def marshal_load(array)
131
- @version, @columns, @columns_hash, @primary_keys, @data_sources, @indexes, @database_version = array
132
- @indexes = @indexes || {}
182
+ @version, @columns, _columns_hash, @primary_keys, @data_sources, @indexes, @database_version = array
183
+ @indexes ||= {}
184
+
185
+ derive_columns_hash_and_deduplicate_values
133
186
  end
134
187
 
135
188
  private
189
+ def reset_version!
190
+ @version = connection.migration_context.current_version
191
+ end
192
+
193
+ def derive_columns_hash_and_deduplicate_values
194
+ @columns = deep_deduplicate(@columns)
195
+ @columns_hash = @columns.transform_values { |columns| columns.index_by(&:name) }
196
+ @primary_keys = deep_deduplicate(@primary_keys)
197
+ @data_sources = deep_deduplicate(@data_sources)
198
+ @indexes = deep_deduplicate(@indexes)
199
+ end
200
+
201
+ if RUBY_VERSION < "2.7"
202
+ def deep_deduplicate(value)
203
+ case value
204
+ when Hash
205
+ value.transform_keys { |k| deep_deduplicate(k) }.transform_values { |v| deep_deduplicate(v) }
206
+ when Array
207
+ value.map { |i| deep_deduplicate(i) }
208
+ when String
209
+ if value.tainted?
210
+ # Ruby 2.6 and 2.7 have slightly different implementations of the String#-@ method.
211
+ # In Ruby 2.6, the receiver of the String#-@ method is modified under certain
212
+ # circumstances, and this was later identified as a bug
213
+ # (https://bugs.ruby-lang.org/issues/15926) and only fixed in Ruby 2.7.
214
+ value = value.dup
215
+ end
216
+ -value
217
+ when Deduplicable
218
+ -value
219
+ else
220
+ value
221
+ end
222
+ end
223
+ else
224
+ def deep_deduplicate(value)
225
+ case value
226
+ when Hash
227
+ value.transform_keys { |k| deep_deduplicate(k) }.transform_values { |v| deep_deduplicate(v) }
228
+ when Array
229
+ value.map { |i| deep_deduplicate(i) }
230
+ when String, Deduplicable
231
+ -value
232
+ else
233
+ value
234
+ end
235
+ end
236
+ end
237
+
136
238
  def prepare_data_sources
137
239
  connection.data_sources.each { |source| @data_sources[source] = true }
138
240
  end
241
+
242
+ def open(filename)
243
+ File.atomic_write(filename) do |file|
244
+ if File.extname(filename) == ".gz"
245
+ zipper = Zlib::GzipWriter.new file
246
+ yield zipper
247
+ zipper.flush
248
+ zipper.close
249
+ else
250
+ yield file
251
+ end
252
+ end
253
+ end
139
254
  end
140
255
  end
141
256
  end
@@ -4,6 +4,8 @@ module ActiveRecord
4
4
  # :stopdoc:
5
5
  module ConnectionAdapters
6
6
  class SqlTypeMetadata
7
+ include Deduplicable
8
+
7
9
  attr_reader :sql_type, :type, :limit, :precision, :scale
8
10
 
9
11
  def initialize(sql_type: nil, type: nil, limit: nil, precision: nil, scale: nil)
@@ -32,6 +34,12 @@ module ActiveRecord
32
34
  precision.hash >> 1 ^
33
35
  scale.hash >> 2
34
36
  end
37
+
38
+ private
39
+ def deduplicated
40
+ @sql_type = -sql_type
41
+ super
42
+ end
35
43
  end
36
44
  end
37
45
  end
@@ -11,6 +11,13 @@ module ActiveRecord
11
11
 
12
12
  def write_query?(sql) # :nodoc:
13
13
  !READ_QUERY.match?(sql)
14
+ rescue ArgumentError # Invalid encoding
15
+ !READ_QUERY.match?(sql.b)
16
+ end
17
+
18
+ def explain(arel, binds = [])
19
+ sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}"
20
+ SQLite3::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN", []))
14
21
  end
15
22
 
16
23
  def execute(sql, name = nil) #:nodoc:
@@ -19,6 +26,7 @@ module ActiveRecord
19
26
  end
20
27
 
21
28
  materialize_transactions
29
+ mark_transaction_written_if_write(sql)
22
30
 
23
31
  log(sql, name) do
24
32
  ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
@@ -33,6 +41,7 @@ module ActiveRecord
33
41
  end
34
42
 
35
43
  materialize_transactions
44
+ mark_transaction_written_if_write(sql)
36
45
 
37
46
  type_casted_binds = type_casted_binds(binds)
38
47
 
@@ -58,7 +67,7 @@ module ActiveRecord
58
67
  records = stmt.to_a
59
68
  end
60
69
 
61
- ActiveRecord::Result.new(cols, records)
70
+ build_result(columns: cols, rows: records)
62
71
  end
63
72
  end
64
73
  end
@@ -69,20 +78,37 @@ module ActiveRecord
69
78
  end
70
79
  alias :exec_update :exec_delete
71
80
 
81
+ def begin_isolated_db_transaction(isolation) #:nodoc
82
+ raise TransactionIsolationError, "SQLite3 only supports the `read_uncommitted` transaction isolation level" if isolation != :read_uncommitted
83
+ raise StandardError, "You need to enable the shared-cache mode in SQLite mode before attempting to change the transaction isolation level" unless shared_cache?
84
+
85
+ Thread.current.thread_variable_set("read_uncommitted", @connection.get_first_value("PRAGMA read_uncommitted"))
86
+ @connection.read_uncommitted = true
87
+ begin_db_transaction
88
+ end
89
+
72
90
  def begin_db_transaction #:nodoc:
73
- log("begin transaction", nil) { @connection.transaction }
91
+ log("begin transaction", "TRANSACTION") { @connection.transaction }
74
92
  end
75
93
 
76
94
  def commit_db_transaction #:nodoc:
77
- log("commit transaction", nil) { @connection.commit }
95
+ log("commit transaction", "TRANSACTION") { @connection.commit }
96
+ reset_read_uncommitted
78
97
  end
79
98
 
80
99
  def exec_rollback_db_transaction #:nodoc:
81
- log("rollback transaction", nil) { @connection.rollback }
100
+ log("rollback transaction", "TRANSACTION") { @connection.rollback }
101
+ reset_read_uncommitted
82
102
  end
83
103
 
84
-
85
104
  private
105
+ def reset_read_uncommitted
106
+ read_uncommitted = Thread.current.thread_variable_get("read_uncommitted")
107
+ return unless read_uncommitted
108
+
109
+ @connection.read_uncommitted = read_uncommitted
110
+ end
111
+
86
112
  def execute_batch(statements, name = nil)
87
113
  sql = combine_multi_statements(statements)
88
114
 
@@ -91,6 +117,7 @@ module ActiveRecord
91
117
  end
92
118
 
93
119
  materialize_transactions
120
+ mark_transaction_written_if_write(sql)
94
121
 
95
122
  log(sql, name) do
96
123
  ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
@@ -60,7 +60,7 @@ module ActiveRecord
60
60
  # "table_name"."column_name" | function(one or no argument)
61
61
  ((?:\w+\.|"\w+"\.)?(?:\w+|"\w+")) | \w+\((?:|\g<2>)\)
62
62
  )
63
- (?:\s+AS\s+(?:\w+|"\w+"))?
63
+ (?:(?:\s+AS)?\s+(?:\w+|"\w+"))?
64
64
  )
65
65
  (?:\s*,\s*\g<1>)*
66
66
  \z
@@ -3,8 +3,12 @@
3
3
  module ActiveRecord
4
4
  module ConnectionAdapters
5
5
  module SQLite3
6
- class SchemaCreation < AbstractAdapter::SchemaCreation # :nodoc:
6
+ class SchemaCreation < SchemaCreation # :nodoc:
7
7
  private
8
+ def supports_index_using?
9
+ false
10
+ end
11
+
8
12
  def add_column_options!(sql, options)
9
13
  if options[:collation]
10
14
  sql << " COLLATE \"#{options[:collation]}\""