activerecord 7.1.1 → 7.1.5

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.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +496 -0
  3. data/README.rdoc +1 -0
  4. data/lib/active_record/associations/association.rb +2 -1
  5. data/lib/active_record/associations/belongs_to_association.rb +4 -4
  6. data/lib/active_record/associations/collection_association.rb +4 -4
  7. data/lib/active_record/associations/has_many_association.rb +2 -2
  8. data/lib/active_record/associations/has_one_association.rb +2 -2
  9. data/lib/active_record/associations/join_dependency/join_association.rb +3 -2
  10. data/lib/active_record/associations/join_dependency.rb +10 -12
  11. data/lib/active_record/associations/preloader/association.rb +4 -1
  12. data/lib/active_record/associations.rb +21 -15
  13. data/lib/active_record/attribute_methods/before_type_cast.rb +1 -1
  14. data/lib/active_record/attribute_methods/dirty.rb +15 -11
  15. data/lib/active_record/attribute_methods/read.rb +3 -3
  16. data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -0
  17. data/lib/active_record/attribute_methods/write.rb +3 -3
  18. data/lib/active_record/attribute_methods.rb +47 -7
  19. data/lib/active_record/autosave_association.rb +5 -2
  20. data/lib/active_record/callbacks.rb +2 -2
  21. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +19 -9
  22. data/lib/active_record/connection_adapters/abstract/database_statements.rb +5 -3
  23. data/lib/active_record/connection_adapters/abstract/query_cache.rb +2 -1
  24. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +8 -4
  25. data/lib/active_record/connection_adapters/abstract_adapter.rb +27 -19
  26. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +37 -13
  27. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +2 -1
  28. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +3 -0
  29. data/lib/active_record/connection_adapters/mysql2_adapter.rb +16 -10
  30. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +4 -1
  31. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  32. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  33. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +15 -6
  34. data/lib/active_record/connection_adapters/postgresql_adapter.rb +32 -33
  35. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +10 -3
  36. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +2 -2
  37. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +1 -0
  38. data/lib/active_record/connection_adapters/trilogy_adapter.rb +25 -21
  39. data/lib/active_record/connection_handling.rb +1 -1
  40. data/lib/active_record/core.rb +49 -10
  41. data/lib/active_record/counter_cache.rb +7 -3
  42. data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -1
  43. data/lib/active_record/database_configurations/hash_config.rb +6 -2
  44. data/lib/active_record/delegated_type.rb +7 -7
  45. data/lib/active_record/destroy_association_async_job.rb +1 -1
  46. data/lib/active_record/encryption/encryptable_record.rb +7 -1
  47. data/lib/active_record/encryption/encrypted_attribute_type.rb +6 -2
  48. data/lib/active_record/encryption/extended_deterministic_queries.rb +0 -15
  49. data/lib/active_record/encryption/scheme.rb +8 -4
  50. data/lib/active_record/encryption.rb +2 -0
  51. data/lib/active_record/enum.rb +6 -9
  52. data/lib/active_record/errors.rb +5 -4
  53. data/lib/active_record/fixtures.rb +16 -0
  54. data/lib/active_record/future_result.rb +10 -0
  55. data/lib/active_record/gem_version.rb +1 -1
  56. data/lib/active_record/insert_all.rb +3 -3
  57. data/lib/active_record/internal_metadata.rb +1 -1
  58. data/lib/active_record/locking/optimistic.rb +1 -1
  59. data/lib/active_record/marshalling.rb +4 -1
  60. data/lib/active_record/message_pack.rb +1 -1
  61. data/lib/active_record/middleware/database_selector.rb +1 -1
  62. data/lib/active_record/migration/compatibility.rb +14 -0
  63. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  64. data/lib/active_record/migration.rb +9 -5
  65. data/lib/active_record/model_schema.rb +12 -7
  66. data/lib/active_record/nested_attributes.rb +16 -5
  67. data/lib/active_record/normalization.rb +8 -0
  68. data/lib/active_record/persistence.rb +6 -5
  69. data/lib/active_record/promise.rb +1 -1
  70. data/lib/active_record/query_cache.rb +1 -1
  71. data/lib/active_record/query_logs_formatter.rb +1 -1
  72. data/lib/active_record/railtie.rb +14 -14
  73. data/lib/active_record/railties/controller_runtime.rb +2 -1
  74. data/lib/active_record/railties/databases.rake +7 -7
  75. data/lib/active_record/reflection.rb +21 -3
  76. data/lib/active_record/relation/calculations.rb +28 -1
  77. data/lib/active_record/relation/delegation.rb +1 -1
  78. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +6 -1
  79. data/lib/active_record/relation/query_methods.rb +21 -7
  80. data/lib/active_record/relation.rb +30 -5
  81. data/lib/active_record/result.rb +1 -1
  82. data/lib/active_record/runtime_registry.rb +15 -1
  83. data/lib/active_record/schema_migration.rb +1 -1
  84. data/lib/active_record/secure_token.rb +1 -1
  85. data/lib/active_record/tasks/database_tasks.rb +35 -13
  86. data/lib/active_record/test_fixtures.rb +1 -0
  87. data/lib/active_record/timestamp.rb +3 -1
  88. data/lib/active_record.rb +2 -2
  89. data/lib/arel/nodes/homogeneous_in.rb +1 -1
  90. data/lib/arel/tree_manager.rb +5 -1
  91. data/lib/arel/visitors/to_sql.rb +2 -1
  92. metadata +14 -13
@@ -336,7 +336,7 @@ module ActiveRecord
336
336
  schema_cache.clear_data_source_cache!(table_name.to_s)
337
337
  schema_cache.clear_data_source_cache!(new_name.to_s)
338
338
  execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
339
- rename_table_indexes(table_name, new_name)
339
+ rename_table_indexes(table_name, new_name, **options)
340
340
  end
341
341
 
342
342
  # Drops a table from the database.
@@ -635,18 +635,38 @@ module ActiveRecord
635
635
  end
636
636
 
637
637
  def build_insert_sql(insert) # :nodoc:
638
- sql = +"INSERT #{insert.into} #{insert.values_list}"
639
-
640
- if insert.skip_duplicates?
641
- no_op_column = quote_column_name(insert.keys.first)
642
- sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{no_op_column}"
643
- elsif insert.update_duplicates?
644
- sql << " ON DUPLICATE KEY UPDATE "
645
- if insert.raw_update_sql?
646
- sql << insert.raw_update_sql
647
- else
648
- sql << insert.touch_model_timestamps_unless { |column| "#{column}<=>VALUES(#{column})" }
649
- sql << insert.updatable_columns.map { |column| "#{column}=VALUES(#{column})" }.join(",")
638
+ no_op_column = quote_column_name(insert.keys.first)
639
+
640
+ # MySQL 8.0.19 replaces `VALUES(<expression>)` clauses with row and column alias names, see https://dev.mysql.com/worklog/task/?id=6312 .
641
+ # then MySQL 8.0.20 deprecates the `VALUES(<expression>)` see https://dev.mysql.com/worklog/task/?id=13325 .
642
+ if supports_insert_raw_alias_syntax?
643
+ values_alias = quote_table_name("#{insert.model.table_name}_values")
644
+ sql = +"INSERT #{insert.into} #{insert.values_list} AS #{values_alias}"
645
+
646
+ if insert.skip_duplicates?
647
+ sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{values_alias}.#{no_op_column}"
648
+ elsif insert.update_duplicates?
649
+ if insert.raw_update_sql?
650
+ sql = +"INSERT #{insert.into} #{insert.values_list} ON DUPLICATE KEY UPDATE #{insert.raw_update_sql}"
651
+ else
652
+ sql << " ON DUPLICATE KEY UPDATE "
653
+ sql << insert.touch_model_timestamps_unless { |column| "#{insert.model.quoted_table_name}.#{column}<=>#{values_alias}.#{column}" }
654
+ sql << insert.updatable_columns.map { |column| "#{column}=#{values_alias}.#{column}" }.join(",")
655
+ end
656
+ end
657
+ else
658
+ sql = +"INSERT #{insert.into} #{insert.values_list}"
659
+
660
+ if insert.skip_duplicates?
661
+ sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{no_op_column}"
662
+ elsif insert.update_duplicates?
663
+ sql << " ON DUPLICATE KEY UPDATE "
664
+ if insert.raw_update_sql?
665
+ sql << insert.raw_update_sql
666
+ else
667
+ sql << insert.touch_model_timestamps_unless { |column| "#{column}<=>VALUES(#{column})" }
668
+ sql << insert.updatable_columns.map { |column| "#{column}=VALUES(#{column})" }.join(",")
669
+ end
650
670
  end
651
671
  end
652
672
 
@@ -853,6 +873,10 @@ module ActiveRecord
853
873
  "DROP INDEX #{quote_column_name(index_name)}"
854
874
  end
855
875
 
876
+ def supports_insert_raw_alias_syntax?
877
+ !mariadb? && database_version >= "8.0.19"
878
+ end
879
+
856
880
  def supports_rename_index?
857
881
  if mariadb?
858
882
  database_version >= "10.5.2"
@@ -155,7 +155,7 @@ module ActiveRecord
155
155
  end
156
156
 
157
157
  def valid_primary_key_options
158
- super + [:unsigned]
158
+ super + [:unsigned, :auto_increment]
159
159
  end
160
160
 
161
161
  def create_table_definition(name, **options)
@@ -185,6 +185,7 @@ module ActiveRecord
185
185
  default, default_function = nil, default
186
186
  elsif type_metadata.extra == "DEFAULT_GENERATED"
187
187
  default = +"(#{default})" unless default.start_with?("(")
188
+ default = default.gsub("\\'", "'")
188
189
  default, default_function = nil, default
189
190
  elsif type_metadata.type == :text && default&.start_with?("'")
190
191
  # strip and unescape quotes
@@ -98,6 +98,7 @@ module ActiveRecord
98
98
  with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
99
99
  sync_timezone_changes(conn)
100
100
  result = conn.query(sql)
101
+ verified!
101
102
  handle_warnings(sql)
102
103
  result
103
104
  end
@@ -126,6 +127,8 @@ module ActiveRecord
126
127
  result = ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
127
128
  stmt.execute(*type_casted_binds)
128
129
  end
130
+ verified!
131
+ result
129
132
  rescue ::Mysql2::Error => e
130
133
  if cache_stmt
131
134
  @statements.delete(sql)
@@ -131,7 +131,7 @@ module ActiveRecord
131
131
  #++
132
132
 
133
133
  def active?
134
- !!@raw_connection&.ping
134
+ !(@raw_connection.nil? || @raw_connection.closed?) && @lock.synchronize { @raw_connection&.ping } || false
135
135
  end
136
136
 
137
137
  alias :reset! :reconnect!
@@ -139,15 +139,19 @@ module ActiveRecord
139
139
  # Disconnects from the database if already connected.
140
140
  # Otherwise, this method does nothing.
141
141
  def disconnect!
142
- super
143
- @raw_connection&.close
144
- @raw_connection = nil
142
+ @lock.synchronize do
143
+ super
144
+ @raw_connection&.close
145
+ @raw_connection = nil
146
+ end
145
147
  end
146
148
 
147
149
  def discard! # :nodoc:
148
- super
149
- @raw_connection&.automatic_close = false
150
- @raw_connection = nil
150
+ @lock.synchronize do
151
+ super
152
+ @raw_connection&.automatic_close = false
153
+ @raw_connection = nil
154
+ end
151
155
  end
152
156
 
153
157
  private
@@ -162,9 +166,11 @@ module ActiveRecord
162
166
  end
163
167
 
164
168
  def reconnect
165
- @raw_connection&.close
166
- @raw_connection = nil
167
- connect
169
+ @lock.synchronize do
170
+ @raw_connection&.close
171
+ @raw_connection = nil
172
+ connect
173
+ end
168
174
  end
169
175
 
170
176
  def configure_connection
@@ -16,7 +16,9 @@ module ActiveRecord
16
16
 
17
17
  log(sql, name) do
18
18
  with_raw_connection do |conn|
19
- conn.async_exec(sql).map_types!(@type_map_for_results).values
19
+ result = conn.async_exec(sql).map_types!(@type_map_for_results).values
20
+ verified!
21
+ result
20
22
  end
21
23
  end
22
24
  end
@@ -51,6 +53,7 @@ module ActiveRecord
51
53
  log(sql, name, async: async) do
52
54
  with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
53
55
  result = conn.async_exec(sql)
56
+ verified!
54
57
  handle_warnings(result)
55
58
  result
56
59
  end
@@ -28,6 +28,12 @@ module ActiveRecord
28
28
  end
29
29
  end
30
30
 
31
+ # TODO: Remove when IPAddr#== compares IPAddr#prefix. See
32
+ # https://github.com/ruby/ipaddr/issues/21
33
+ def changed?(old_value, new_value, _new_value_before_type_cast)
34
+ !old_value.eql?(new_value) || !old_value.nil? && old_value.prefix != new_value.prefix
35
+ end
36
+
31
37
  def cast_value(value)
32
38
  if value.nil?
33
39
  nil
@@ -27,9 +27,10 @@ module ActiveRecord
27
27
  value = value.sub(/^\((.+)\)$/, '-\1') # (4)
28
28
  case value
29
29
  when /^-?\D*+[\d,]+\.\d{2}$/ # (1)
30
- value.gsub!(/[^-\d.]/, "")
30
+ value.delete!("^-0-9.")
31
31
  when /^-?\D*+[\d.]+,\d{2}$/ # (2)
32
- value.gsub!(/[^-\d,]/, "").sub!(/,/, ".")
32
+ value.delete!("^-0-9,")
33
+ value.tr!(",", ".")
33
34
  end
34
35
 
35
36
  super(value)
@@ -247,6 +247,8 @@ module ActiveRecord
247
247
 
248
248
  # Returns the sequence name for a table's primary key or some other specified key.
249
249
  def default_sequence_name(table_name, pk = "id") # :nodoc:
250
+ return nil if pk.is_a?(Array)
251
+
250
252
  result = serial_sequence(table_name, pk)
251
253
  return nil unless result
252
254
  Utils.extract_schema_qualified_name(result).to_s
@@ -341,7 +343,7 @@ module ActiveRecord
341
343
  JOIN pg_namespace nsp ON (t.relnamespace = nsp.oid)
342
344
  WHERE t.oid = #{quote(quote_table_name(table))}::regclass
343
345
  AND cons.contype = 'p'
344
- AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval|uuid_generate'
346
+ AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval|uuid_generate|gen_random_uuid'
345
347
  SQL
346
348
  end
347
349
 
@@ -385,15 +387,22 @@ module ActiveRecord
385
387
  execute "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
386
388
  pk, seq = pk_and_sequence_for(new_name)
387
389
  if pk
388
- idx = "#{table_name}_pkey"
389
- new_idx = "#{new_name}_pkey"
390
+ # PostgreSQL automatically creates an index for PRIMARY KEY with name consisting of
391
+ # truncated table name and "_pkey" suffix fitting into max_identifier_length number of characters.
392
+ max_pkey_prefix = max_identifier_length - "_pkey".size
393
+ idx = "#{table_name[0, max_pkey_prefix]}_pkey"
394
+ new_idx = "#{new_name[0, max_pkey_prefix]}_pkey"
390
395
  execute "ALTER INDEX #{quote_table_name(idx)} RENAME TO #{quote_table_name(new_idx)}"
391
- if seq && seq.identifier == "#{table_name}_#{pk}_seq"
392
- new_seq = "#{new_name}_#{pk}_seq"
396
+
397
+ # PostgreSQL automatically creates a sequence for PRIMARY KEY with name consisting of
398
+ # truncated table name and "#{primary_key}_seq" suffix fitting into max_identifier_length number of characters.
399
+ max_seq_prefix = max_identifier_length - "_#{pk}_seq".size
400
+ if seq && seq.identifier == "#{table_name[0, max_seq_prefix]}_#{pk}_seq"
401
+ new_seq = "#{new_name[0, max_seq_prefix]}_#{pk}_seq"
393
402
  execute "ALTER TABLE #{seq.quoted} RENAME TO #{quote_table_name(new_seq)}"
394
403
  end
395
404
  end
396
- rename_table_indexes(table_name, new_name)
405
+ rename_table_indexes(table_name, new_name, **options)
397
406
  end
398
407
 
399
408
  def add_column(table_name, column_name, type, **options) # :nodoc:
@@ -108,10 +108,11 @@ module ActiveRecord
108
108
  # but significantly increases the risk of data loss if the database
109
109
  # crashes. As a result, this should not be used in production
110
110
  # environments. If you would like all created tables to be unlogged in
111
- # the test environment you can add the following line to your test.rb
112
- # file:
111
+ # the test environment you can add the following to your test.rb file:
113
112
  #
114
- # ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.create_unlogged_tables = true
113
+ # ActiveSupport.on_load(:active_record_postgresqladapter) do
114
+ # self.create_unlogged_tables = true
115
+ # end
115
116
  class_attribute :create_unlogged_tables, default: false
116
117
 
117
118
  ##
@@ -277,6 +278,10 @@ module ActiveRecord
277
278
  database_version >= 12_00_00 # >= 12.0
278
279
  end
279
280
 
281
+ def supports_identity_columns? # :nodoc:
282
+ database_version >= 10_00_00 # >= 10.0
283
+ end
284
+
280
285
  def supports_nulls_not_distinct?
281
286
  database_version >= 15_00_00 # >= 15.0
282
287
  end
@@ -535,7 +540,7 @@ module ActiveRecord
535
540
  END
536
541
  $$;
537
542
  SQL
538
- internal_exec_query(query)
543
+ internal_exec_query(query).tap { reload_type_map }
539
544
  end
540
545
 
541
546
  # Drops an enum type.
@@ -551,7 +556,7 @@ module ActiveRecord
551
556
  query = <<~SQL
552
557
  DROP TYPE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(name)};
553
558
  SQL
554
- internal_exec_query(query)
559
+ internal_exec_query(query).tap { reload_type_map }
555
560
  end
556
561
 
557
562
  # Rename an existing enum type to something else.
@@ -596,14 +601,6 @@ module ActiveRecord
596
601
  @max_identifier_length ||= query_value("SHOW max_identifier_length", "SCHEMA").to_i
597
602
  end
598
603
 
599
- # Returns the maximum length of a table name.
600
- def table_name_length
601
- # PostgreSQL automatically creates an index for PRIMARY KEY with name consisting of
602
- # truncated table name and "_pkey" suffix fitting into max_identifier_length number of characters.
603
- # We allow smaller table names to be able to correctly rename this index when renaming the table.
604
- max_identifier_length - "_pkey".length
605
- end
606
-
607
604
  # Set the authorized user for this session
608
605
  def session_auth=(user)
609
606
  clear_cache!
@@ -894,7 +891,9 @@ module ActiveRecord
894
891
  type_casted_binds = type_casted_binds(binds)
895
892
  log(sql, name, binds, type_casted_binds, async: async) do
896
893
  with_raw_connection do |conn|
897
- conn.exec_params(sql, type_casted_binds)
894
+ result = conn.exec_params(sql, type_casted_binds)
895
+ verified!
896
+ result
898
897
  end
899
898
  end
900
899
  end
@@ -904,12 +903,14 @@ module ActiveRecord
904
903
 
905
904
  update_typemap_for_default_timezone
906
905
 
907
- stmt_key = prepare_statement(sql, binds)
908
- type_casted_binds = type_casted_binds(binds)
909
-
910
906
  with_raw_connection do |conn|
907
+ stmt_key = prepare_statement(sql, binds, conn)
908
+ type_casted_binds = type_casted_binds(binds)
909
+
911
910
  log(sql, name, binds, type_casted_binds, stmt_key, async: async) do
912
- conn.exec_prepared(stmt_key, type_casted_binds)
911
+ result = conn.exec_prepared(stmt_key, type_casted_binds)
912
+ verified!
913
+ result
913
914
  end
914
915
  end
915
916
  rescue ActiveRecord::StatementInvalid => e
@@ -957,22 +958,20 @@ module ActiveRecord
957
958
 
958
959
  # Prepare the statement if it hasn't been prepared, return
959
960
  # the statement key.
960
- def prepare_statement(sql, binds)
961
- with_raw_connection(allow_retry: true, materialize_transactions: false) do |conn|
962
- sql_key = sql_key(sql)
963
- unless @statements.key? sql_key
964
- nextkey = @statements.next_key
965
- begin
966
- conn.prepare nextkey, sql
967
- rescue => e
968
- raise translate_exception_class(e, sql, binds)
969
- end
970
- # Clear the queue
971
- conn.get_last_result
972
- @statements[sql_key] = nextkey
961
+ def prepare_statement(sql, binds, conn)
962
+ sql_key = sql_key(sql)
963
+ unless @statements.key? sql_key
964
+ nextkey = @statements.next_key
965
+ begin
966
+ conn.prepare nextkey, sql
967
+ rescue => e
968
+ raise translate_exception_class(e, sql, binds)
973
969
  end
974
- @statements[sql_key]
970
+ # Clear the queue
971
+ conn.get_last_result
972
+ @statements[sql_key] = nextkey
975
973
  end
974
+ @statements[sql_key]
976
975
  end
977
976
 
978
977
  # Connects to a PostgreSQL server and sets up the adapter depending on the
@@ -1076,7 +1075,7 @@ module ActiveRecord
1076
1075
  SELECT a.attname, format_type(a.atttypid, a.atttypmod),
1077
1076
  pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod,
1078
1077
  c.collname, col_description(a.attrelid, a.attnum) AS comment,
1079
- a.attidentity AS identity,
1078
+ #{supports_identity_columns? ? 'attidentity' : quote('')} AS identity,
1080
1079
  #{supports_virtual_columns? ? 'attgenerated' : quote('')} as attgenerated
1081
1080
  FROM pg_attribute a
1082
1081
  LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum
@@ -50,6 +50,7 @@ module ActiveRecord
50
50
  stmt.bind_params(type_casted_binds)
51
51
  records = stmt.to_a
52
52
  end
53
+ verified!
53
54
 
54
55
  build_result(columns: cols, rows: records)
55
56
  end
@@ -76,7 +77,9 @@ module ActiveRecord
76
77
  def begin_db_transaction # :nodoc:
77
78
  log("begin transaction", "TRANSACTION") do
78
79
  with_raw_connection(allow_retry: true, materialize_transactions: false) do |conn|
79
- conn.transaction
80
+ result = conn.transaction
81
+ verified!
82
+ result
80
83
  end
81
84
  end
82
85
  end
@@ -112,7 +115,9 @@ module ActiveRecord
112
115
  def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: false)
113
116
  log(sql, name, async: async) do
114
117
  with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
115
- conn.execute(sql)
118
+ result = conn.execute(sql)
119
+ verified!
120
+ result
116
121
  end
117
122
  end
118
123
  end
@@ -133,7 +138,9 @@ module ActiveRecord
133
138
 
134
139
  log(sql, name) do
135
140
  with_raw_connection do |conn|
136
- conn.execute_batch2(sql)
141
+ result = conn.execute_batch2(sql)
142
+ verified!
143
+ result
137
144
  end
138
145
  end
139
146
  end
@@ -11,7 +11,7 @@ require "active_record/connection_adapters/sqlite3/schema_definitions"
11
11
  require "active_record/connection_adapters/sqlite3/schema_dumper"
12
12
  require "active_record/connection_adapters/sqlite3/schema_statements"
13
13
 
14
- gem "sqlite3", "~> 1.4"
14
+ gem "sqlite3", ">= 1.4"
15
15
  require "sqlite3"
16
16
 
17
17
  module ActiveRecord
@@ -286,7 +286,7 @@ module ActiveRecord
286
286
  schema_cache.clear_data_source_cache!(table_name.to_s)
287
287
  schema_cache.clear_data_source_cache!(new_name.to_s)
288
288
  exec_query "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
289
- rename_table_indexes(table_name, new_name)
289
+ rename_table_indexes(table_name, new_name, **options)
290
290
  end
291
291
 
292
292
  def add_column(table_name, column_name, type, **options) # :nodoc:
@@ -46,6 +46,7 @@ module ActiveRecord
46
46
  with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
47
47
  sync_timezone_changes(conn)
48
48
  result = conn.query(sql)
49
+ verified!
49
50
  handle_warnings(sql)
50
51
  result
51
52
  end
@@ -57,7 +57,7 @@ module ActiveRecord
57
57
  def new_client(config)
58
58
  config[:ssl_mode] = parse_ssl_mode(config[:ssl_mode]) if config[:ssl_mode]
59
59
  ::Trilogy.new(config)
60
- rescue ::Trilogy::ConnectionError, ::Trilogy::ProtocolError => error
60
+ rescue ::Trilogy::Error => error
61
61
  raise translate_connect_error(config, error)
62
62
  end
63
63
 
@@ -99,6 +99,18 @@ module ActiveRecord
99
99
  end
100
100
  end
101
101
 
102
+ def initialize(...)
103
+ super
104
+
105
+ if @config[:prepared_statements]
106
+ raise ArgumentError, "Trilogy currently doesn't support prepared statements. Remove `prepared_statements: true` from your database configuration."
107
+ end
108
+
109
+ # Trilogy ignore `socket` if `host is set. We want the opposite to allow
110
+ # configuring UNIX domain sockets via `DATABASE_URL`.
111
+ @config.delete(:host) if @config[:socket]
112
+ end
113
+
102
114
  TYPE_MAP = Type::TypeMap.new.tap { |m| initialize_type_map(m) }
103
115
 
104
116
  def supports_json?
@@ -132,7 +144,7 @@ module ActiveRecord
132
144
  end
133
145
 
134
146
  def active?
135
- connection&.ping || false
147
+ !(@raw_connection.nil? || @raw_connection.closed?) && @lock.synchronize { @raw_connection&.ping } || false
136
148
  rescue ::Trilogy::Error
137
149
  false
138
150
  end
@@ -140,18 +152,18 @@ module ActiveRecord
140
152
  alias reset! reconnect!
141
153
 
142
154
  def disconnect!
143
- super
144
- unless connection.nil?
145
- connection.close
146
- self.connection = nil
155
+ @lock.synchronize do
156
+ super
157
+ @raw_connection&.close
158
+ @raw_connection = nil
147
159
  end
148
160
  end
149
161
 
150
162
  def discard!
151
- super
152
- unless connection.nil?
153
- connection.discard!
154
- self.connection = nil
163
+ @lock.synchronize do
164
+ super
165
+ @raw_connection&.discard!
166
+ @raw_connection = nil
155
167
  end
156
168
  end
157
169
 
@@ -181,23 +193,15 @@ module ActiveRecord
181
193
  exception.error_code if exception.respond_to?(:error_code)
182
194
  end
183
195
 
184
- def connection
185
- @raw_connection
186
- end
187
-
188
- def connection=(conn)
189
- @raw_connection = conn
190
- end
191
-
192
196
  def connect
193
- self.connection = self.class.new_client(@config)
197
+ @raw_connection = self.class.new_client(@config)
194
198
  rescue ConnectionNotEstablished => ex
195
199
  raise ex.set_pool(@pool)
196
200
  end
197
201
 
198
202
  def reconnect
199
- connection&.close
200
- self.connection = nil
203
+ @raw_connection&.close
204
+ @raw_connection = nil
201
205
  connect
202
206
  end
203
207
 
@@ -256,7 +256,7 @@ module ActiveRecord
256
256
 
257
257
  attr_writer :connection_specification_name
258
258
 
259
- # Return the connection specification name from the current class or its parent.
259
+ # Returns the connection specification name from the current class or its parent.
260
260
  def connection_specification_name
261
261
  if !defined?(@connection_specification_name) || @connection_specification_name.nil?
262
262
  return self == Base ? Base.name : superclass.connection_specification_name
@@ -15,9 +15,10 @@ module ActiveRecord
15
15
  ##
16
16
  # :singleton-method:
17
17
  #
18
- # Accepts a logger conforming to the interface of Log4r which is then
19
- # passed on to any new database connections made and which can be
20
- # retrieved on both a class and instance level by calling +logger+.
18
+ # Accepts a logger conforming to the interface of Log4r or the default
19
+ # Ruby +Logger+ class, which is then passed on to any new database
20
+ # connections made. You can retrieve this logger by calling +logger+ on
21
+ # either an Active Record model class or an Active Record model instance.
21
22
  class_attribute :logger, instance_writer: false
22
23
 
23
24
  class_attribute :_destroy_association_async_job, instance_accessor: false, default: "ActiveRecord::DestroyAssociationAsyncJob"
@@ -271,10 +272,25 @@ module ActiveRecord
271
272
  elsif reflection.belongs_to? && !reflection.polymorphic?
272
273
  key = reflection.join_foreign_key
273
274
  pkey = reflection.join_primary_key
274
- value = value.public_send(pkey) if value.respond_to?(pkey)
275
+
276
+ if pkey.is_a?(Array)
277
+ if pkey.all? { |attribute| value.respond_to?(attribute) }
278
+ value = pkey.map do |attribute|
279
+ if attribute == "id"
280
+ value.id_value
281
+ else
282
+ value.public_send(attribute)
283
+ end
284
+ end
285
+ composite_primary_key = true
286
+ end
287
+ else
288
+ value = value.public_send(pkey) if value.respond_to?(pkey)
289
+ end
275
290
  end
276
291
 
277
- if !columns_hash.key?(key) || StatementCache.unsupported_value?(value)
292
+ if !composite_primary_key &&
293
+ (!columns_hash.key?(key) || StatementCache.unsupported_value?(value))
278
294
  return super
279
295
  end
280
296
 
@@ -330,7 +346,7 @@ module ActiveRecord
330
346
 
331
347
  # Returns a string like 'Post(id:integer, title:string, body:text)'
332
348
  def inspect # :nodoc:
333
- if self == Base
349
+ if self == Base || singleton_class?
334
350
  super
335
351
  elsif abstract_class?
336
352
  "#{super}(abstract)"
@@ -380,8 +396,8 @@ module ActiveRecord
380
396
  @arel_table = nil
381
397
  @predicate_builder = nil
382
398
  @inspection_filter = nil
383
- @filter_attributes = nil
384
- @generated_association_methods = nil
399
+ @filter_attributes ||= nil
400
+ @generated_association_methods ||= nil
385
401
  end
386
402
  end
387
403
 
@@ -401,12 +417,18 @@ module ActiveRecord
401
417
 
402
418
  def cached_find_by(keys, values)
403
419
  statement = cached_find_by_statement(keys) { |params|
404
- wheres = keys.index_with { params.bind }
420
+ wheres = keys.index_with do |key|
421
+ if key.is_a?(Array)
422
+ [key.map { params.bind }]
423
+ else
424
+ params.bind
425
+ end
426
+ end
405
427
  where(wheres).limit(1)
406
428
  }
407
429
 
408
430
  begin
409
- statement.execute(values, connection).first
431
+ statement.execute(values.flatten, connection).first
410
432
  rescue TypeError
411
433
  raise ActiveRecord::StatementInvalid
412
434
  end
@@ -544,6 +566,10 @@ module ActiveRecord
544
566
  # Returns a hash of the given methods with their names as keys and returned
545
567
  # values as values.
546
568
  #
569
+ # topic = Topic.new(title: "Budget", author_name: "Jason")
570
+ # topic.slice(:title, :author_name)
571
+ # => { "title" => "Budget", "author_name" => "Jason" }
572
+ #
547
573
  #--
548
574
  # Implemented by ActiveModel::Access#slice.
549
575
 
@@ -554,6 +580,10 @@ module ActiveRecord
554
580
  #
555
581
  # Returns an array of the values returned by the given methods.
556
582
  #
583
+ # topic = Topic.new(title: "Budget", author_name: "Jason")
584
+ # topic.values_at(:title, :author_name)
585
+ # => ["Budget", "Jason"]
586
+ #
557
587
  #--
558
588
  # Implemented by ActiveModel::Access#values_at.
559
589
 
@@ -671,7 +701,16 @@ module ActiveRecord
671
701
  @strict_loading_mode == :n_plus_one_only
672
702
  end
673
703
 
704
+ # Returns +true+ if the record uses strict_loading with +:all+ mode enabled.
705
+ def strict_loading_all?
706
+ @strict_loading_mode == :all
707
+ end
708
+
674
709
  # Marks this record as read only.
710
+ #
711
+ # customer = Customer.first
712
+ # customer.readonly!
713
+ # customer.save # Raises an ActiveRecord::ReadOnlyRecord
675
714
  def readonly!
676
715
  @readonly = true
677
716
  end