activerecord 7.0.0.rc1 → 7.0.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


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

Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +132 -3
  3. data/MIT-LICENSE +1 -1
  4. data/lib/active_record/associations/join_dependency.rb +6 -2
  5. data/lib/active_record/associations.rb +28 -8
  6. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +9 -4
  7. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +1 -1
  8. data/lib/active_record/connection_adapters/mysql/database_statements.rb +2 -0
  9. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +1 -0
  10. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +2 -0
  11. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +2 -0
  12. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +3 -3
  13. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +2 -0
  14. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +13 -0
  15. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +12 -12
  16. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +28 -0
  17. data/lib/active_record/encryption/encryptable_record.rb +1 -1
  18. data/lib/active_record/encryption/extended_deterministic_queries.rb +28 -28
  19. data/lib/active_record/gem_version.rb +2 -2
  20. data/lib/active_record/migration/compatibility.rb +24 -2
  21. data/lib/active_record/migration.rb +1 -1
  22. data/lib/active_record/reflection.rb +1 -1
  23. data/lib/active_record/relation/calculations.rb +3 -2
  24. data/lib/active_record/relation/query_methods.rb +11 -3
  25. data/lib/active_record/relation.rb +16 -3
  26. data/lib/active_record/schema_migration.rb +4 -0
  27. data/lib/active_record/tasks/database_tasks.rb +6 -2
  28. data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
  29. data/lib/active_record.rb +1 -1
  30. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  31. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  32. metadata +14 -12
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c1a1968982fd2d7c0361ac7cfa9241a02c4ff558cc8fba93545800194274ac90
4
- data.tar.gz: e19875a1df942ea1488aff91d0026e8584945504b14f9021660a1b55831f2ad2
3
+ metadata.gz: e0de65b8a004bb74ee8951a2d34e5630f67568091f4c72bd99e28aaf8dd73097
4
+ data.tar.gz: 56d07fdf1ec276b9b88c7a13e79cfe4cfb3f3768dcbd1b5bf0f828bd2ba657d6
5
5
  SHA512:
6
- metadata.gz: 17e093b64278febf6fa7db08a4184650581efeabdc2b32c652c2818951fa9608cd2bd804ba51304cbace26ffb2d83873ea416f29d600017161b607968e4fbe7e
7
- data.tar.gz: cffe209fcb1de322ef8089a95b24940db52ac4cc5086c6fc7a749646a2e14f83a15384785a2bb390c149b770a17e9597789d12ceffb38e925e6067772d8a0556
6
+ metadata.gz: 2bf02198176f838235234fefd0658de10a37c51d704becaeccd1f8040ed4d79360a5128411c01af7226ca0c5806c5ed892c1fc55fc27cca55f48f5dc181d5dd5
7
+ data.tar.gz: e781dd6be129520aaeab76b1fea5bf98ce25793c623c2cde18cfd293fd2222c28e3d16ad4f8978688a74eaf683c2a6283358d7f815b8a924a0cc66f16c051c1e
data/CHANGELOG.md CHANGED
@@ -1,3 +1,131 @@
1
+ ## Rails 7.0.1 (January 06, 2022) ##
2
+
3
+ * Change `QueryMethods#in_order_of` to drop records not listed in values.
4
+
5
+ `in_order_of` now filters down to the values provided, to match the behavior of the `Enumerable` version.
6
+
7
+ *Kevin Newton*
8
+
9
+ * Allow named expression indexes to be revertible.
10
+
11
+ Previously, the following code would raise an error in a reversible migration executed while rolling back, due to the index name not being used in the index removal.
12
+
13
+ ```ruby
14
+ add_index(:settings, "(data->'property')", using: :gin, name: :index_settings_data_property)
15
+ ```
16
+
17
+ Fixes #43331.
18
+
19
+ *Oliver Günther*
20
+
21
+ * Better error messages when association name is invalid in the argument of `ActiveRecord::QueryMethods::WhereChain#missing`.
22
+
23
+ *ykpythemind*
24
+
25
+ * Fix ordered migrations for single db in multi db environment.
26
+
27
+ *Himanshu*
28
+
29
+ * Extract `on update CURRENT_TIMESTAMP` for mysql2 adapter.
30
+
31
+ *Kazuhiro Masuda*
32
+
33
+ * Fix incorrect argument in PostgreSQL structure dump tasks.
34
+
35
+ Updating the `--no-comment` argument added in Rails 7 to the correct `--no-comments` argument.
36
+
37
+ *Alex Dent*
38
+
39
+ * Fix schema dumping column default SQL values for sqlite3.
40
+
41
+ *fatkodima*
42
+
43
+ * Correctly parse complex check constraint expressions for PostgreSQL.
44
+
45
+ *fatkodima*
46
+
47
+ * Fix `timestamptz` attributes on PostgreSQL handle blank inputs.
48
+
49
+ *Alex Ghiculescu*
50
+
51
+ * Fix migration compatibility to create SQLite references/belongs_to column as integer when migration version is 6.0.
52
+
53
+ Reference/belongs_to in migrations with version 6.0 were creating columns as
54
+ bigint instead of integer for the SQLite Adapter.
55
+
56
+ *Marcelo Lauxen*
57
+
58
+ * Fix joining through a polymorphic association.
59
+
60
+ *Alexandre Ruban*
61
+
62
+ * Fix `QueryMethods#in_order_of` to handle empty order list.
63
+
64
+ ```ruby
65
+ Post.in_order_of(:id, []).to_a
66
+ ```
67
+
68
+ Also more explicitly set the column as secondary order, so that any other
69
+ value is still ordered.
70
+
71
+ *Jean Boussier*
72
+
73
+ * Fix `rails dbconsole` for 3-tier config.
74
+
75
+ *Eileen M. Uchitelle*
76
+
77
+ * Fix quoting of column aliases generated by calculation methods.
78
+
79
+ Since the alias is derived from the table name, we can't assume the result
80
+ is a valid identifier.
81
+
82
+ ```ruby
83
+ class Test < ActiveRecord::Base
84
+ self.table_name = '1abc'
85
+ end
86
+ Test.group(:id).count
87
+ # syntax error at or near "1" (ActiveRecord::StatementInvalid)
88
+ # LINE 1: SELECT COUNT(*) AS count_all, "1abc"."id" AS 1abc_id FROM "1...
89
+ ```
90
+
91
+ *Jean Boussier*
92
+
93
+
94
+ ## Rails 7.0.0 (December 15, 2021) ##
95
+
96
+ * Better handle SQL queries with invalid encoding.
97
+
98
+ ```ruby
99
+ Post.create(name: "broken \xC8 UTF-8")
100
+ ```
101
+
102
+ Would cause all adapters to fail in a non controlled way in the code
103
+ responsible to detect write queries.
104
+
105
+ The query is now properly passed to the database connection, which might or might
106
+ not be able to handle it, but will either succeed or failed in a more correct way.
107
+
108
+ *Jean Boussier*
109
+
110
+ * Move database and shard selection config options to a generator.
111
+
112
+ Rather than generating the config options in `production.rb` when applications are created, applications can now run a generator to create an initializer and uncomment / update options as needed. All multi-db configuration can be implemented in this initializer.
113
+
114
+ *Eileen M. Uchitelle*
115
+
116
+
117
+ ## Rails 7.0.0.rc3 (December 14, 2021) ##
118
+
119
+ * No changes.
120
+
121
+
122
+ ## Rails 7.0.0.rc2 (December 14, 2021) ##
123
+
124
+ * No changes.
125
+
126
+
127
+ ## Rails 7.0.0.rc1 (December 06, 2021) ##
128
+
1
129
  * Remove deprecated `ActiveRecord::DatabaseConfigurations::DatabaseConfig#spec_name`.
2
130
 
3
131
  *Rafael Mendonça França*
@@ -305,7 +433,6 @@
305
433
 
306
434
  Applications may now set their the filename or path of the schema / structure dump file in their database configuration.
307
435
 
308
-
309
436
  ```yaml
310
437
  production:
311
438
  primary:
@@ -392,6 +519,8 @@
392
519
  `#with_lock` now accepts transaction options like `requires_new:`,
393
520
  `isolation:`, and `joinable:`
394
521
 
522
+ *John Mileham*
523
+
395
524
  * Adds support for deferrable foreign key constraints in PostgreSQL.
396
525
 
397
526
  By default, foreign key constraints in PostgreSQL are checked after each statement. This works for most use cases,
@@ -468,7 +597,7 @@
468
597
 
469
598
  *Alex Ghiculescu*
470
599
 
471
- * Avoid COMMENT statements in PostgreSQL structure dumps
600
+ * Avoid COMMENT statements in PostgreSQL structure dumps
472
601
 
473
602
  COMMENT statements are now omitted from the output of `db:structure:dump` when using PostgreSQL >= 11.
474
603
  This allows loading the dump without a pgsql superuser account.
@@ -521,7 +650,7 @@
521
650
 
522
651
  *Sam Bostock*
523
652
 
524
- * Add ssl support for postgresql database tasks
653
+ * Add ssl support for postgresql database tasks
525
654
 
526
655
  Add `PGSSLMODE`, `PGSSLCERT`, `PGSSLKEY` and `PGSSLROOTCERT` to pg_env from database config
527
656
  when running postgresql database tasks.
data/MIT-LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2004-2021 David Heinemeier Hansson
1
+ Copyright (c) 2004-2022 David Heinemeier Hansson
2
2
 
3
3
  Arel originally copyright (c) 2007-2016 Nick Kallen, Bryan Helmkamp, Emilio Tagua, Aaron Patterson
4
4
 
@@ -3,8 +3,12 @@
3
3
  module ActiveRecord
4
4
  module Associations
5
5
  class JoinDependency # :nodoc:
6
- autoload :JoinBase, "active_record/associations/join_dependency/join_base"
7
- autoload :JoinAssociation, "active_record/associations/join_dependency/join_association"
6
+ extend ActiveSupport::Autoload
7
+
8
+ eager_autoload do
9
+ autoload :JoinBase
10
+ autoload :JoinAssociation
11
+ end
8
12
 
9
13
  class Aliases # :nodoc:
10
14
  def initialize(tables)
@@ -594,19 +594,27 @@ module ActiveRecord
594
594
  # you can also define callbacks that get triggered when you add an object to or remove an
595
595
  # object from an association collection.
596
596
  #
597
- # class Project
598
- # has_and_belongs_to_many :developers, after_add: :evaluate_velocity
597
+ # class Firm < ActiveRecord::Base
598
+ # has_many :clients,
599
+ # dependent: :destroy,
600
+ # after_add: :congratulate_client,
601
+ # after_remove: :log_after_remove
599
602
  #
600
- # def evaluate_velocity(developer)
601
- # ...
603
+ # def congratulate_client(record)
604
+ # # ...
605
+ # end
606
+ #
607
+ # def log_after_remove(record)
608
+ # # ...
602
609
  # end
603
- # end
604
610
  #
605
611
  # It's possible to stack callbacks by passing them as an array. Example:
606
612
  #
607
- # class Project
608
- # has_and_belongs_to_many :developers,
609
- # after_add: [:evaluate_velocity, Proc.new { |p, d| p.shipping_date = Time.now}]
613
+ # class Firm < ActiveRecord::Base
614
+ # has_many :clients,
615
+ # dependent: :destroy,
616
+ # after_add: [:congratulate_client, -> (firm, record) { firm.log << "after_adding#{record.id}" }],
617
+ # after_remove: :log_after_remove
610
618
  # end
611
619
  #
612
620
  # Possible callbacks are: +before_add+, +after_add+, +before_remove+ and +after_remove+.
@@ -617,6 +625,18 @@ module ActiveRecord
617
625
  # Similarly, if any of the +before_remove+ callbacks throw an exception, the object
618
626
  # will not be removed from the collection.
619
627
  #
628
+ # Note: To trigger remove callbacks, you must use +destroy+ / +destroy_all+ methods. For example:
629
+ #
630
+ # * <tt>firm.clients.destroy(client)</tt>
631
+ # * <tt>firm.clients.destroy(*clients)</tt>
632
+ # * <tt>firm.clients.destroy_all</tt>
633
+ #
634
+ # +delete+ / +delete_all+ methods like the following do *not* trigger remove callbacks:
635
+ #
636
+ # * <tt>firm.clients.delete(client)</tt>
637
+ # * <tt>firm.clients.delete(*clients)</tt>
638
+ # * <tt>firm.clients.delete_all</tt>
639
+ #
620
640
  # == Association extensions
621
641
  #
622
642
  # The proxy objects that control the access to associations can be extended through anonymous
@@ -1438,7 +1438,7 @@ module ActiveRecord
1438
1438
 
1439
1439
  checks = []
1440
1440
 
1441
- if !options.key?(:name) && column_name.is_a?(String) && /\W/.match?(column_name)
1441
+ if !options.key?(:name) && expression_column_name?(column_name)
1442
1442
  options[:name] = index_name(table_name, column_name)
1443
1443
  column_names = []
1444
1444
  else
@@ -1447,7 +1447,7 @@ module ActiveRecord
1447
1447
 
1448
1448
  checks << lambda { |i| i.name == options[:name].to_s } if options.key?(:name)
1449
1449
 
1450
- if column_names.present?
1450
+ if column_names.present? && !(options.key?(:name) && expression_column_name?(column_names))
1451
1451
  checks << lambda { |i| index_name(table_name, i.columns) == index_name(table_name, column_names) }
1452
1452
  end
1453
1453
 
@@ -1515,7 +1515,7 @@ module ActiveRecord
1515
1515
  end
1516
1516
 
1517
1517
  def index_column_names(column_names)
1518
- if column_names.is_a?(String) && /\W/.match?(column_names)
1518
+ if expression_column_name?(column_names)
1519
1519
  column_names
1520
1520
  else
1521
1521
  Array(column_names)
@@ -1523,13 +1523,18 @@ module ActiveRecord
1523
1523
  end
1524
1524
 
1525
1525
  def index_name_options(column_names)
1526
- if column_names.is_a?(String) && /\W/.match?(column_names)
1526
+ if expression_column_name?(column_names)
1527
1527
  column_names = column_names.scan(/\w+/).join("_")
1528
1528
  end
1529
1529
 
1530
1530
  { column: column_names }
1531
1531
  end
1532
1532
 
1533
+ # Try to identify whether the given column name is an expression
1534
+ def expression_column_name?(column_name)
1535
+ column_name.is_a?(String) && /\W/.match?(column_name)
1536
+ end
1537
+
1533
1538
  def strip_table_name_prefix_and_suffix(table_name)
1534
1539
  prefix = Base.table_name_prefix
1535
1540
  suffix = Base.table_name_suffix
@@ -197,7 +197,7 @@ module ActiveRecord
197
197
 
198
198
  # Executes the SQL statement in the context of this connection.
199
199
  def execute(sql, name = nil, async: false)
200
- raw_execute(sql, name, async)
200
+ raw_execute(sql, name, async: async)
201
201
  end
202
202
 
203
203
  # Mysql2Adapter doesn't have to free a result after using it, but we use this method
@@ -26,6 +26,8 @@ module ActiveRecord
26
26
 
27
27
  def write_query?(sql) # :nodoc:
28
28
  !READ_QUERY.match?(sql)
29
+ rescue ArgumentError # Invalid encoding
30
+ !READ_QUERY.match?(sql.b)
29
31
  end
30
32
 
31
33
  def explain(arel, binds = [])
@@ -163,6 +163,7 @@ module ActiveRecord
163
163
  default, default_function = field[:Default], nil
164
164
 
165
165
  if type_metadata.type == :datetime && /\ACURRENT_TIMESTAMP(?:\([0-6]?\))?\z/i.match?(default)
166
+ default = "#{default} ON UPDATE #{default}" if /on update CURRENT_TIMESTAMP/i.match?(field[:Extra])
166
167
  default, default_function = nil, default
167
168
  elsif type_metadata.extra == "DEFAULT_GENERATED"
168
169
  default = +"(#{default})" unless default.start_with?("(")
@@ -28,6 +28,8 @@ module ActiveRecord
28
28
 
29
29
  def write_query?(sql) # :nodoc:
30
30
  !READ_QUERY.match?(sql)
31
+ rescue ArgumentError # Invalid encoding
32
+ !READ_QUERY.match?(sql.b)
31
33
  end
32
34
 
33
35
  # Executes an SQL statement, returning a PG::Result object on success
@@ -10,6 +10,8 @@ module ActiveRecord
10
10
  end
11
11
 
12
12
  def cast_value(value)
13
+ return if value.blank?
14
+
13
15
  time = super
14
16
  return time if time.is_a?(ActiveSupport::TimeWithZone)
15
17
 
@@ -525,7 +525,7 @@ module ActiveRecord
525
525
  scope = quoted_scope(table_name)
526
526
 
527
527
  check_info = exec_query(<<-SQL, "SCHEMA")
528
- SELECT conname, pg_get_constraintdef(c.oid) AS constraintdef, c.convalidated AS valid
528
+ SELECT conname, pg_get_constraintdef(c.oid, true) AS constraintdef, c.convalidated AS valid
529
529
  FROM pg_constraint c
530
530
  JOIN pg_class t ON c.conrelid = t.oid
531
531
  WHERE c.contype = 'c'
@@ -537,7 +537,7 @@ module ActiveRecord
537
537
  name: row["conname"],
538
538
  validate: row["valid"]
539
539
  }
540
- expression = row["constraintdef"][/CHECK \({2}(.+)\){2}/, 1]
540
+ expression = row["constraintdef"][/CHECK \((.+)\)/m, 1]
541
541
 
542
542
  CheckConstraintDefinition.new(table_name, expression, options)
543
543
  end
@@ -569,7 +569,7 @@ module ActiveRecord
569
569
  else raise ArgumentError, "No integer type has byte size #{limit}. Use a numeric with scale 0 instead."
570
570
  end
571
571
  when "enum"
572
- raise ArgumentError "enum_type is required for enums" if enum_type.nil?
572
+ raise ArgumentError, "enum_type is required for enums" if enum_type.nil?
573
573
 
574
574
  enum_type
575
575
  else
@@ -11,6 +11,8 @@ 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)
14
16
  end
15
17
 
16
18
  def explain(arel, binds = [])
@@ -45,6 +45,19 @@ module ActiveRecord
45
45
  0
46
46
  end
47
47
 
48
+ def quote_default_expression(value, column) # :nodoc:
49
+ if value.is_a?(Proc)
50
+ value = value.call
51
+ if value.match?(/\A\w+\(.*\)\z/)
52
+ "(#{value})"
53
+ else
54
+ value
55
+ end
56
+ else
57
+ super
58
+ end
59
+ end
60
+
48
61
  def type_cast(value) # :nodoc:
49
62
  case value
50
63
  when BigDecimal
@@ -127,20 +127,20 @@ module ActiveRecord
127
127
  end
128
128
 
129
129
  def new_column_from_field(table_name, field)
130
- default = \
131
- case field["dflt_value"]
132
- when /^null$/i
133
- nil
134
- when /^'(.*)'$/m
135
- $1.gsub("''", "'")
136
- when /^"(.*)"$/m
137
- $1.gsub('""', '"')
138
- else
139
- field["dflt_value"]
140
- end
130
+ default = field["dflt_value"]
141
131
 
142
132
  type_metadata = fetch_type_metadata(field["type"])
143
- Column.new(field["name"], default, type_metadata, field["notnull"].to_i == 0, collation: field["collation"])
133
+ default_value = extract_value_from_default(default)
134
+ default_function = extract_default_function(default_value, default)
135
+
136
+ Column.new(
137
+ field["name"],
138
+ default_value,
139
+ type_metadata,
140
+ field["notnull"].to_i == 0,
141
+ default_function,
142
+ collation: field["collation"]
143
+ )
144
144
  end
145
145
 
146
146
  def data_source_sql(name = nil, type: nil)
@@ -389,6 +389,34 @@ module ActiveRecord
389
389
  end
390
390
  alias column_definitions table_structure
391
391
 
392
+ def extract_value_from_default(default)
393
+ case default
394
+ when /^null$/i
395
+ nil
396
+ # Quoted types
397
+ when /^'(.*)'$/m
398
+ $1.gsub("''", "'")
399
+ # Quoted types
400
+ when /^"(.*)"$/m
401
+ $1.gsub('""', '"')
402
+ # Numeric types
403
+ when /\A-?\d+(\.\d*)?\z/
404
+ $&
405
+ else
406
+ # Anything else is blank or some function
407
+ # and we can't know the value of that, so return nil.
408
+ nil
409
+ end
410
+ end
411
+
412
+ def extract_default_function(default_value, default)
413
+ default if has_default_function?(default_value, default)
414
+ end
415
+
416
+ def has_default_function?(default_value, default)
417
+ !default_value && %r{\w+\(.*\)|CURRENT_TIME|CURRENT_DATE|CURRENT_TIMESTAMP}.match?(default)
418
+ end
419
+
392
420
  # See: https://www.sqlite.org/lang_altertable.html
393
421
  # SQLite has an additional restriction on the ALTER TABLE statement
394
422
  def invalid_alter_table_type?(type, options)
@@ -37,7 +37,7 @@ module ActiveRecord
37
37
  # in preserving it.
38
38
  # * <tt>:ignore_case</tt> - When true, it behaves like +:downcase+ but, it also preserves the original case in a specially
39
39
  # designated column +original_<name>+. When reading the encrypted content, the version with the original case is
40
- # server. But you can still execute queries that will ignore the case. This option can only be used when +:deterministic+
40
+ # served. But you can still execute queries that will ignore the case. This option can only be used when +:deterministic+
41
41
  # is true.
42
42
  # * <tt>:context_properties</tt> - Additional properties that will override +Context+ settings when this attribute is
43
43
  # encrypted and decrypted. E.g: +encryptor:+, +cipher:+, +message_serializer:+, etc.
@@ -1,35 +1,35 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Automatically expand encrypted arguments to support querying both encrypted and unencrypted data
4
- #
5
- # Active Record Encryption supports querying the db using deterministic attributes. For example:
6
- #
7
- # Contact.find_by(email_address: "jorge@hey.com")
8
- #
9
- # The value "jorge@hey.com" will get encrypted automatically to perform the query. But there is
10
- # a problem while the data is being encrypted. This won't work. During that time, you need these
11
- # queries to be:
12
- #
13
- # Contact.find_by(email_address: [ "jorge@hey.com", "<encrypted jorge@hey.com>" ])
14
- #
15
- # This patches ActiveRecord to support this automatically. It addresses both:
16
- #
17
- # * ActiveRecord::Base: Used in +Contact.find_by_email_address(...)+
18
- # * ActiveRecord::Relation: Used in +Contact.internal.find_by_email_address(...)+
19
- #
20
- # +ActiveRecord::Base+ relies on +ActiveRecord::Relation+ (+ActiveRecord::QueryMethods+) but it does
21
- # some prepared statements caching. That's why we need to intercept +ActiveRecord::Base+ as soon
22
- # as it's invoked (so that the proper prepared statement is cached).
23
- #
24
- # When modifying this file run performance tests in +test/performance/extended_deterministic_queries_performance_test.rb+ to
25
- # make sure performance overhead is acceptable.
26
- #
27
- # We will extend this to support previous "encryption context" versions in future iterations
28
- #
29
- # @TODO Experimental. Support for every kind of query is pending
30
- # @TODO It should not patch anything if not needed (no previous schemes or no support for previous encryption schemes)
31
3
  module ActiveRecord
32
4
  module Encryption
5
+ # Automatically expand encrypted arguments to support querying both encrypted and unencrypted data
6
+ #
7
+ # Active Record Encryption supports querying the db using deterministic attributes. For example:
8
+ #
9
+ # Contact.find_by(email_address: "jorge@hey.com")
10
+ #
11
+ # The value "jorge@hey.com" will get encrypted automatically to perform the query. But there is
12
+ # a problem while the data is being encrypted. This won't work. During that time, you need these
13
+ # queries to be:
14
+ #
15
+ # Contact.find_by(email_address: [ "jorge@hey.com", "<encrypted jorge@hey.com>" ])
16
+ #
17
+ # This patches ActiveRecord to support this automatically. It addresses both:
18
+ #
19
+ # * ActiveRecord::Base: Used in +Contact.find_by_email_address(...)+
20
+ # * ActiveRecord::Relation: Used in +Contact.internal.find_by_email_address(...)+
21
+ #
22
+ # +ActiveRecord::Base+ relies on +ActiveRecord::Relation+ (+ActiveRecord::QueryMethods+) but it does
23
+ # some prepared statements caching. That's why we need to intercept +ActiveRecord::Base+ as soon
24
+ # as it's invoked (so that the proper prepared statement is cached).
25
+ #
26
+ # When modifying this file run performance tests in +test/performance/extended_deterministic_queries_performance_test.rb+ to
27
+ # make sure performance overhead is acceptable.
28
+ #
29
+ # We will extend this to support previous "encryption context" versions in future iterations
30
+ #
31
+ # @TODO Experimental. Support for every kind of query is pending
32
+ # @TODO It should not patch anything if not needed (no previous schemes or no support for previous encryption schemes)
33
33
  module ExtendedDeterministicQueries
34
34
  def self.install_support
35
35
  ActiveRecord::Relation.prepend(RelationQueries)
@@ -9,8 +9,8 @@ module ActiveRecord
9
9
  module VERSION
10
10
  MAJOR = 7
11
11
  MINOR = 0
12
- TINY = 0
13
- PRE = "rc1"
12
+ TINY = 1
13
+ PRE = nil
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -92,6 +92,22 @@ module ActiveRecord
92
92
  end
93
93
  end
94
94
 
95
+ module SQLite3
96
+ module TableDefinition
97
+ def references(*args, **options)
98
+ args.each do |ref_name|
99
+ ReferenceDefinition.new(ref_name, type: :integer, **options).add_to(self)
100
+ end
101
+ end
102
+ alias :belongs_to :references
103
+
104
+ def column(name, type, index: nil, **options)
105
+ options[:precision] ||= nil
106
+ super
107
+ end
108
+ end
109
+ end
110
+
95
111
  module TableDefinition
96
112
  def references(*args, **options)
97
113
  args.each do |ref_name|
@@ -131,8 +147,13 @@ module ActiveRecord
131
147
  end
132
148
 
133
149
  def add_reference(table_name, ref_name, **options)
134
- ReferenceDefinition.new(ref_name, **options)
135
- .add_to(connection.update_table_definition(table_name, self))
150
+ if connection.adapter_name == "SQLite"
151
+ reference_definition = ReferenceDefinition.new(ref_name, type: :integer, **options)
152
+ else
153
+ reference_definition = ReferenceDefinition.new(ref_name, **options)
154
+ end
155
+
156
+ reference_definition.add_to(connection.update_table_definition(table_name, self))
136
157
  end
137
158
  alias :add_belongs_to :add_reference
138
159
 
@@ -140,6 +161,7 @@ module ActiveRecord
140
161
  def compatible_table_definition(t)
141
162
  class << t
142
163
  prepend TableDefinition
164
+ prepend SQLite3::TableDefinition
143
165
  end
144
166
  t
145
167
  end
@@ -1080,7 +1080,7 @@ module ActiveRecord
1080
1080
  # 0 then an empty array will be returned and no migrations
1081
1081
  # will be run.
1082
1082
  #
1083
- # If the +current_version+ in the schema is less than
1083
+ # If the +current_version+ in the schema is greater than
1084
1084
  # the +target_version+, then +down+ will be run.
1085
1085
  #
1086
1086
  # If none of the conditions are met, +up+ will be run with
@@ -1031,7 +1031,7 @@ module ActiveRecord
1031
1031
  end
1032
1032
 
1033
1033
  def join_scopes(table, predicate_builder, klass = self.klass, record = nil) # :nodoc:
1034
- scopes = @previous_reflection.join_scopes(table, predicate_builder, record) + super
1034
+ scopes = @previous_reflection.join_scopes(table, predicate_builder, klass, record) + super
1035
1035
  scopes << build_scope(table, predicate_builder, klass).instance_exec(record, &source_type_scope)
1036
1036
  end
1037
1037
 
@@ -155,7 +155,7 @@ module ActiveRecord
155
155
  end
156
156
 
157
157
  # Use #pluck as a shortcut to select one or more attributes without
158
- # loading a bunch of records just to grab the attributes you want.
158
+ # loading an entire record object per row.
159
159
  #
160
160
  # Person.pluck(:name)
161
161
  #
@@ -345,12 +345,13 @@ module ActiveRecord
345
345
  column = aggregate_column(column_name)
346
346
  column_alias = column_alias_for("#{operation} #{column_name.to_s.downcase}")
347
347
  select_value = operation_over_aggregate_column(column, operation, distinct)
348
- select_value.as(column_alias)
348
+ select_value.as(connection.quote_column_name(column_alias))
349
349
 
350
350
  select_values = [select_value]
351
351
  select_values += self.select_values unless having_clause.empty?
352
352
 
353
353
  select_values.concat group_columns.map { |aliaz, field|
354
+ aliaz = connection.quote_column_name(aliaz)
354
355
  if field.respond_to?(:as)
355
356
  field.as(aliaz)
356
357
  else
@@ -97,6 +97,9 @@ module ActiveRecord
97
97
  def missing(*associations)
98
98
  associations.each do |association|
99
99
  reflection = @scope.klass._reflect_on_association(association)
100
+ unless reflection
101
+ raise ArgumentError.new("An association named `:#{association}` does not exist on the model `#{@scope.name}`.")
102
+ end
100
103
  @scope.left_outer_joins!(association)
101
104
  @scope.where!(reflection.table_name => { reflection.association_primary_key => nil })
102
105
  end
@@ -424,18 +427,23 @@ module ActiveRecord
424
427
  # adapter this will either use a CASE statement or a built-in function.
425
428
  #
426
429
  # User.in_order_of(:id, [1, 5, 3])
427
- # # SELECT "users".* FROM "users" ORDER BY FIELD("users"."id", 1, 5, 3)
430
+ # # SELECT "users".* FROM "users"
431
+ # # ORDER BY FIELD("users"."id", 1, 5, 3)
432
+ # # WHERE "users"."id" IN (1, 5, 3)
428
433
  #
429
434
  def in_order_of(column, values)
430
435
  klass.disallow_raw_sql!([column], permit: connection.column_name_with_order_matcher)
436
+ return spawn.none! if values.empty?
431
437
 
432
438
  references = column_references([column])
433
439
  self.references_values |= references unless references.empty?
434
440
 
435
441
  values = values.map { |value| type_caster.type_cast_for_database(column, value) }
436
- column = order_column(column.to_s) if column.is_a?(Symbol)
442
+ arel_column = column.is_a?(Symbol) ? order_column(column.to_s) : column
437
443
 
438
- spawn.order!(connection.field_ordered_value(column, values))
444
+ spawn
445
+ .order!(connection.field_ordered_value(arel_column, values))
446
+ .where!(arel_column.in(values))
439
447
  end
440
448
 
441
449
  # Replaces any existing order defined on the relation with the specified order.
@@ -33,7 +33,6 @@ module ActiveRecord
33
33
  @delegate_to_klass = false
34
34
  @future_result = nil
35
35
  @records = nil
36
- @limited_count = nil
37
36
  end
38
37
 
39
38
  def initialize_copy(other)
@@ -647,6 +646,21 @@ module ActiveRecord
647
646
  # Schedule the query to be performed from a background thread pool.
648
647
  #
649
648
  # Post.where(published: true).load_async # => #<ActiveRecord::Relation>
649
+ #
650
+ # When the +Relation+ is iterated, if the background query wasn't executed yet,
651
+ # it will be performed by the foreground thread.
652
+ #
653
+ # Note that {config.active_record.async_query_executor}[https://guides.rubyonrails.org/configuring.html#config-active-record-async-query-executor] must be configured
654
+ # for queries to actually be executed concurrently. Otherwise it defaults to
655
+ # executing them in the foreground.
656
+ #
657
+ # +load_async+ will also fallback to executing in the foreground in the test environment when transactional
658
+ # fixtures are enabled.
659
+ #
660
+ # If the query was actually executed in the background, the Active Record logs will show
661
+ # it by prefixing the log line with <tt>ASYNC</tt>:
662
+ #
663
+ # ASYNC Post Load (0.0ms) (db time 2ms) SELECT "posts".* FROM "posts" LIMIT 100
650
664
  def load_async
651
665
  return load if !connection.async_enabled?
652
666
 
@@ -699,7 +713,6 @@ module ActiveRecord
699
713
  @offsets = @take = nil
700
714
  @cache_keys = nil
701
715
  @records = nil
702
- @limited_count = nil
703
716
  self
704
717
  end
705
718
 
@@ -974,7 +987,7 @@ module ActiveRecord
974
987
  end
975
988
 
976
989
  def limited_count
977
- @limited_count ||= limit_value ? count : limit(2).count
990
+ limit_value ? count : limit(2).count
978
991
  end
979
992
  end
980
993
  end
@@ -41,6 +41,10 @@ module ActiveRecord
41
41
  def all_versions
42
42
  order(:version).pluck(:version)
43
43
  end
44
+
45
+ def table_exists?
46
+ connection.data_source_exists?(table_name)
47
+ end
44
48
  end
45
49
 
46
50
  def version
@@ -257,8 +257,12 @@ module ActiveRecord
257
257
  scope = ENV["SCOPE"]
258
258
  verbose_was, Migration.verbose = Migration.verbose, verbose?
259
259
 
260
- Base.connection.migration_context.migrate(target_version || version) do |migration|
261
- scope.blank? || scope == migration.scope
260
+ Base.connection.migration_context.migrate(target_version) do |migration|
261
+ if version.blank?
262
+ scope.blank? || scope == migration.scope
263
+ else
264
+ migration.version == version
265
+ end
262
266
  end.tap do |migrations_ran|
263
267
  Migration.write("No migrations ran. (using #{scope} scope)") if scope.present? && migrations_ran.empty?
264
268
  end
@@ -58,7 +58,7 @@ module ActiveRecord
58
58
  end
59
59
 
60
60
  args = ["--schema-only", "--no-privileges", "--no-owner"]
61
- args << "--no-comment" if connection.database_version >= 110_000
61
+ args << "--no-comments" if connection.database_version >= 110_000
62
62
  args.concat(["--file", filename])
63
63
 
64
64
  args.concat(Array(extra_flags)) if extra_flags
data/lib/active_record.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  #--
4
- # Copyright (c) 2004-2021 David Heinemeier Hansson
4
+ # Copyright (c) 2004-2022 David Heinemeier Hansson
5
5
  #
6
6
  # Permission is hereby granted, free of charge, to any person obtaining
7
7
  # a copy of this software and associated documentation files (the
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators/active_record"
4
+
5
+ module ActiveRecord
6
+ module Generators # :nodoc:
7
+ class MultiDbGenerator < ::Rails::Generators::Base # :nodoc:
8
+ source_root File.expand_path("templates", __dir__)
9
+
10
+ def create_multi_db
11
+ filename = "multi_db.rb"
12
+ template filename, "config/initializers/#{filename}"
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,44 @@
1
+ # Multi-db Configuration
2
+ #
3
+ # This file is used for configuration settings related to multiple databases.
4
+ #
5
+ # Enable Database Selector
6
+ #
7
+ # Inserts middleware to perform automatic connection switching.
8
+ # The `database_selector` hash is used to pass options to the DatabaseSelector
9
+ # middleware. The `delay` is used to determine how long to wait after a write
10
+ # to send a subsequent read to the primary.
11
+ #
12
+ # The `database_resolver` class is used by the middleware to determine which
13
+ # database is appropriate to use based on the time delay.
14
+ #
15
+ # The `database_resolver_context` class is used by the middleware to set
16
+ # timestamps for the last write to the primary. The resolver uses the context
17
+ # class timestamps to determine how long to wait before reading from the
18
+ # replica.
19
+ #
20
+ # By default Rails will store a last write timestamp in the session. The
21
+ # DatabaseSelector middleware is designed as such you can define your own
22
+ # strategy for connection switching and pass that into the middleware through
23
+ # these configuration options.
24
+ #
25
+ # Rails.application.configure do
26
+ # config.active_record.database_selector = { delay: 2.seconds }
27
+ # config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver
28
+ # config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session
29
+ # end
30
+ #
31
+ # Enable Shard Selector
32
+ #
33
+ # Inserts middleware to perform automatic shard swapping. The `shard_selector` hash
34
+ # can be used to pass options to the `ShardSelector` middleware. The `lock` option is
35
+ # used to determine whether shard swapping should be prohibited for the request.
36
+ #
37
+ # The `shard_resolver` option is used by the middleware to determine which shard
38
+ # to switch to. The application must provide a mechanism for finding the shard name
39
+ # in a proc. See guides for an example.
40
+ #
41
+ # Rails.application.configure do
42
+ # config.active_record.shard_selector = { lock: true }
43
+ # config.active_record.shard_resolver = ->(request) { Tenant.find_by!(host: request.host).shard }
44
+ # end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.0.0.rc1
4
+ version: 7.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Heinemeier Hansson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-12-06 00:00:00.000000000 Z
11
+ date: 2022-01-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -16,28 +16,28 @@ dependencies:
16
16
  requirements:
17
17
  - - '='
18
18
  - !ruby/object:Gem::Version
19
- version: 7.0.0.rc1
19
+ version: 7.0.1
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - '='
25
25
  - !ruby/object:Gem::Version
26
- version: 7.0.0.rc1
26
+ version: 7.0.1
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: activemodel
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - '='
32
32
  - !ruby/object:Gem::Version
33
- version: 7.0.0.rc1
33
+ version: 7.0.1
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - '='
39
39
  - !ruby/object:Gem::Version
40
- version: 7.0.0.rc1
40
+ version: 7.0.1
41
41
  description: Databases on Rails. Build a persistent domain model by mapping database
42
42
  tables to Ruby classes. Strong conventions for associations, validations, aggregations,
43
43
  migrations, and testing come baked-in.
@@ -427,15 +427,17 @@ files:
427
427
  - lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt
428
428
  - lib/rails/generators/active_record/model/templates/model.rb.tt
429
429
  - lib/rails/generators/active_record/model/templates/module.rb.tt
430
+ - lib/rails/generators/active_record/multi_db/multi_db_generator.rb
431
+ - lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt
430
432
  homepage: https://rubyonrails.org
431
433
  licenses:
432
434
  - MIT
433
435
  metadata:
434
436
  bug_tracker_uri: https://github.com/rails/rails/issues
435
- changelog_uri: https://github.com/rails/rails/blob/v7.0.0.rc1/activerecord/CHANGELOG.md
436
- documentation_uri: https://api.rubyonrails.org/v7.0.0.rc1/
437
+ changelog_uri: https://github.com/rails/rails/blob/v7.0.1/activerecord/CHANGELOG.md
438
+ documentation_uri: https://api.rubyonrails.org/v7.0.1/
437
439
  mailing_list_uri: https://discuss.rubyonrails.org/c/rubyonrails-talk
438
- source_code_uri: https://github.com/rails/rails/tree/v7.0.0.rc1/activerecord
440
+ source_code_uri: https://github.com/rails/rails/tree/v7.0.1/activerecord
439
441
  rubygems_mfa_required: 'true'
440
442
  post_install_message:
441
443
  rdoc_options:
@@ -450,11 +452,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
450
452
  version: 2.7.0
451
453
  required_rubygems_version: !ruby/object:Gem::Requirement
452
454
  requirements:
453
- - - ">"
455
+ - - ">="
454
456
  - !ruby/object:Gem::Version
455
- version: 1.3.1
457
+ version: '0'
456
458
  requirements: []
457
- rubygems_version: 3.2.22
459
+ rubygems_version: 3.2.32
458
460
  signing_key:
459
461
  specification_version: 4
460
462
  summary: Object-relational mapper framework (part of Rails).