activerecord 8.0.3 → 8.1.0.beta1

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 (148) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +427 -522
  3. data/README.rdoc +1 -1
  4. data/lib/active_record/association_relation.rb +1 -1
  5. data/lib/active_record/associations/association.rb +1 -1
  6. data/lib/active_record/associations/builder/association.rb +16 -5
  7. data/lib/active_record/associations/builder/belongs_to.rb +17 -4
  8. data/lib/active_record/associations/builder/collection_association.rb +7 -3
  9. data/lib/active_record/associations/builder/has_one.rb +1 -1
  10. data/lib/active_record/associations/builder/singular_association.rb +33 -5
  11. data/lib/active_record/associations/collection_proxy.rb +22 -4
  12. data/lib/active_record/associations/deprecation.rb +88 -0
  13. data/lib/active_record/associations/errors.rb +3 -0
  14. data/lib/active_record/associations/join_dependency.rb +2 -0
  15. data/lib/active_record/associations/preloader/branch.rb +1 -0
  16. data/lib/active_record/associations.rb +159 -21
  17. data/lib/active_record/attribute_methods/serialization.rb +16 -3
  18. data/lib/active_record/attribute_methods.rb +1 -1
  19. data/lib/active_record/attributes.rb +3 -0
  20. data/lib/active_record/autosave_association.rb +1 -1
  21. data/lib/active_record/base.rb +2 -3
  22. data/lib/active_record/coders/json.rb +14 -5
  23. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +1 -3
  24. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +15 -0
  25. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -12
  26. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +382 -51
  27. data/lib/active_record/connection_adapters/abstract/database_statements.rb +26 -30
  28. data/lib/active_record/connection_adapters/abstract/query_cache.rb +25 -18
  29. data/lib/active_record/connection_adapters/abstract/quoting.rb +15 -24
  30. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +7 -2
  31. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +26 -34
  32. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  33. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +85 -22
  34. data/lib/active_record/connection_adapters/abstract/transaction.rb +16 -3
  35. data/lib/active_record/connection_adapters/abstract_adapter.rb +66 -14
  36. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +43 -11
  37. data/lib/active_record/connection_adapters/column.rb +17 -4
  38. data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
  39. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  40. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +42 -5
  41. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +26 -4
  42. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +27 -22
  43. data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -0
  44. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +18 -23
  45. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +2 -2
  46. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  47. data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
  48. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +1 -1
  49. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +8 -21
  50. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +65 -30
  51. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +74 -38
  52. data/lib/active_record/connection_adapters/postgresql_adapter.rb +10 -5
  53. data/lib/active_record/connection_adapters/schema_cache.rb +2 -2
  54. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +37 -25
  55. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -8
  56. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +4 -13
  57. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +54 -30
  58. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +4 -3
  59. data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -1
  60. data/lib/active_record/connection_adapters.rb +1 -0
  61. data/lib/active_record/core.rb +5 -4
  62. data/lib/active_record/counter_cache.rb +33 -8
  63. data/lib/active_record/database_configurations/database_config.rb +5 -1
  64. data/lib/active_record/database_configurations/hash_config.rb +50 -9
  65. data/lib/active_record/database_configurations/url_config.rb +13 -3
  66. data/lib/active_record/database_configurations.rb +7 -3
  67. data/lib/active_record/delegated_type.rb +1 -1
  68. data/lib/active_record/dynamic_matchers.rb +54 -69
  69. data/lib/active_record/encryption/encryptable_record.rb +4 -4
  70. data/lib/active_record/encryption/encrypted_attribute_type.rb +1 -1
  71. data/lib/active_record/encryption/scheme.rb +1 -1
  72. data/lib/active_record/enum.rb +24 -8
  73. data/lib/active_record/errors.rb +23 -7
  74. data/lib/active_record/explain_registry.rb +0 -1
  75. data/lib/active_record/filter_attribute_handler.rb +73 -0
  76. data/lib/active_record/fixtures.rb +2 -2
  77. data/lib/active_record/gem_version.rb +3 -3
  78. data/lib/active_record/inheritance.rb +1 -1
  79. data/lib/active_record/insert_all.rb +12 -7
  80. data/lib/active_record/locking/optimistic.rb +7 -0
  81. data/lib/active_record/locking/pessimistic.rb +5 -0
  82. data/lib/active_record/log_subscriber.rb +1 -5
  83. data/lib/active_record/middleware/shard_selector.rb +34 -17
  84. data/lib/active_record/migration/command_recorder.rb +14 -1
  85. data/lib/active_record/migration/compatibility.rb +34 -24
  86. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  87. data/lib/active_record/migration.rb +26 -16
  88. data/lib/active_record/model_schema.rb +10 -7
  89. data/lib/active_record/nested_attributes.rb +2 -0
  90. data/lib/active_record/persistence.rb +34 -3
  91. data/lib/active_record/query_cache.rb +22 -15
  92. data/lib/active_record/query_logs.rb +3 -7
  93. data/lib/active_record/railtie.rb +32 -3
  94. data/lib/active_record/railties/databases.rake +16 -4
  95. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  96. data/lib/active_record/railties/job_runtime.rb +10 -11
  97. data/lib/active_record/reflection.rb +42 -3
  98. data/lib/active_record/relation/batches.rb +26 -12
  99. data/lib/active_record/relation/calculations.rb +20 -9
  100. data/lib/active_record/relation/delegation.rb +0 -1
  101. data/lib/active_record/relation/finder_methods.rb +27 -11
  102. data/lib/active_record/relation/merger.rb +2 -2
  103. data/lib/active_record/relation/predicate_builder.rb +2 -2
  104. data/lib/active_record/relation/query_attribute.rb +3 -1
  105. data/lib/active_record/relation/query_methods.rb +39 -29
  106. data/lib/active_record/relation/where_clause.rb +1 -10
  107. data/lib/active_record/relation.rb +25 -13
  108. data/lib/active_record/result.rb +44 -21
  109. data/lib/active_record/sanitization.rb +2 -0
  110. data/lib/active_record/schema_dumper.rb +12 -10
  111. data/lib/active_record/scoping.rb +0 -1
  112. data/lib/active_record/signed_id.rb +43 -15
  113. data/lib/active_record/statement_cache.rb +13 -9
  114. data/lib/active_record/store.rb +44 -19
  115. data/lib/active_record/tasks/abstract_tasks.rb +76 -0
  116. data/lib/active_record/tasks/database_tasks.rb +2 -21
  117. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -40
  118. data/lib/active_record/tasks/postgresql_database_tasks.rb +5 -39
  119. data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -26
  120. data/lib/active_record/test_databases.rb +10 -2
  121. data/lib/active_record/test_fixtures.rb +27 -2
  122. data/lib/active_record/testing/query_assertions.rb +8 -2
  123. data/lib/active_record/timestamp.rb +4 -2
  124. data/lib/active_record/transaction.rb +2 -5
  125. data/lib/active_record/transactions.rb +32 -10
  126. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  127. data/lib/active_record/type/internal/timezone.rb +7 -0
  128. data/lib/active_record/type/json.rb +15 -2
  129. data/lib/active_record/type/serialized.rb +11 -4
  130. data/lib/active_record/type/type_map.rb +1 -1
  131. data/lib/active_record/type_caster/connection.rb +2 -1
  132. data/lib/active_record/validations/associated.rb +1 -1
  133. data/lib/active_record.rb +65 -3
  134. data/lib/arel/alias_predication.rb +2 -0
  135. data/lib/arel/crud.rb +6 -11
  136. data/lib/arel/nodes/count.rb +2 -2
  137. data/lib/arel/nodes/function.rb +4 -10
  138. data/lib/arel/nodes/named_function.rb +2 -2
  139. data/lib/arel/nodes/node.rb +1 -1
  140. data/lib/arel/nodes.rb +0 -2
  141. data/lib/arel/select_manager.rb +7 -2
  142. data/lib/arel/visitors/dot.rb +0 -3
  143. data/lib/arel/visitors/postgresql.rb +55 -0
  144. data/lib/arel/visitors/sqlite.rb +55 -8
  145. data/lib/arel/visitors/to_sql.rb +3 -21
  146. data/lib/arel.rb +3 -1
  147. metadata +13 -9
  148. data/lib/active_record/normalization.rb +0 -163
@@ -46,7 +46,7 @@ module ActiveRecord
46
46
  end
47
47
 
48
48
  if matching_pair
49
- matching_pair.last.call(lookup_key)
49
+ matching_pair.last.call(lookup_key).freeze
50
50
  elsif @parent
51
51
  @parent.perform_fetch(lookup_key, &block)
52
52
  else
@@ -19,7 +19,8 @@ module ActiveRecord
19
19
  if schema_cache.data_source_exists?(table_name)
20
20
  column = schema_cache.columns_hash(table_name)[attr_name.to_s]
21
21
  if column
22
- type = @klass.with_connection { |connection| connection.lookup_cast_type_from_column(column) }
22
+ # TODO: Remove fetch_cast_type and the need for connection after we release 8.1.
23
+ type = column.fetch_cast_type(@klass.lease_connection)
23
24
  end
24
25
  end
25
26
 
@@ -7,7 +7,7 @@ module ActiveRecord
7
7
  context = record_validation_context_for_association(record)
8
8
 
9
9
  if Array(value).reject { |association| valid_object?(association, context) }.any?
10
- record.errors.add(attribute, :invalid, **options.merge(value: value))
10
+ record.errors.add(attribute, :invalid, **options, value: value)
11
11
  end
12
12
  end
13
13
 
data/lib/active_record.rb CHANGED
@@ -26,6 +26,7 @@
26
26
  require "active_support"
27
27
  require "active_support/rails"
28
28
  require "active_support/ordered_options"
29
+ require "active_support/core_ext/array/conversions"
29
30
  require "active_model"
30
31
  require "arel"
31
32
  require "yaml"
@@ -52,6 +53,7 @@ module ActiveRecord
52
53
  autoload :Enum
53
54
  autoload :Explain
54
55
  autoload :FixtureSet, "active_record/fixtures"
56
+ autoload :FilterAttributeHandler
55
57
  autoload :Inheritance
56
58
  autoload :Integration
57
59
  autoload :InternalMetadata
@@ -62,7 +64,6 @@ module ActiveRecord
62
64
  autoload :ModelSchema
63
65
  autoload :NestedAttributes
64
66
  autoload :NoTouching
65
- autoload :Normalization
66
67
  autoload :Persistence
67
68
  autoload :QueryCache
68
69
  autoload :QueryLogs
@@ -174,7 +175,8 @@ module ActiveRecord
174
175
  extend ActiveSupport::Autoload
175
176
 
176
177
  autoload :DatabaseTasks
177
- autoload :MySQLDatabaseTasks, "active_record/tasks/mysql_database_tasks"
178
+ autoload :AbstractTasks, "active_record/tasks/abstract_tasks"
179
+ autoload :MySQLDatabaseTasks, "active_record/tasks/mysql_database_tasks"
178
180
  autoload :PostgreSQLDatabaseTasks, "active_record/tasks/postgresql_database_tasks"
179
181
  autoload :SQLiteDatabaseTasks, "active_record/tasks/sqlite_database_tasks"
180
182
  end
@@ -259,6 +261,9 @@ module ActiveRecord
259
261
  ##
260
262
  # :singleton-method: db_warnings_ignore
261
263
  # Specify allowlist of database warnings.
264
+ # Can be a string, regular expression, or an error code from the database.
265
+ #
266
+ # ActiveRecord::Base.db_warnings_ignore = [/`SHOW WARNINGS` did not return the warnings/, "01000"]
262
267
  singleton_class.attr_accessor :db_warnings_ignore
263
268
  self.db_warnings_ignore = []
264
269
 
@@ -286,6 +291,7 @@ module ActiveRecord
286
291
  def self.global_thread_pool_async_query_executor # :nodoc:
287
292
  concurrency = global_executor_concurrency || 4
288
293
  @global_thread_pool_async_query_executor ||= Concurrent::ThreadPoolExecutor.new(
294
+ name: "ActiveRecord-global-async-query-executor",
289
295
  min_threads: 0,
290
296
  max_threads: concurrency,
291
297
  max_queue: concurrency * 4,
@@ -351,6 +357,9 @@ module ActiveRecord
351
357
  singleton_class.attr_accessor :run_after_transaction_callbacks_in_order_defined
352
358
  self.run_after_transaction_callbacks_in_order_defined = false
353
359
 
360
+ singleton_class.attr_accessor :raise_on_missing_required_finder_order_columns
361
+ self.run_after_transaction_callbacks_in_order_defined = false
362
+
354
363
  singleton_class.attr_accessor :application_record_class
355
364
  self.application_record_class = nil
356
365
 
@@ -401,6 +410,12 @@ module ActiveRecord
401
410
  singleton_class.attr_accessor :migration_strategy
402
411
  self.migration_strategy = Migration::DefaultStrategy
403
412
 
413
+ ##
414
+ # :singleton-method: schema_versions_formatter
415
+ # Specify the formatter used by schema dumper to format versions information.
416
+ singleton_class.attr_accessor :schema_versions_formatter
417
+ self.schema_versions_formatter = Migration::DefaultSchemaVersionsFormatter
418
+
404
419
  ##
405
420
  # :singleton-method: dump_schema_after_migration
406
421
  # Specify whether schema dump should happen at the end of the
@@ -461,6 +476,29 @@ module ActiveRecord
461
476
  singleton_class.attr_accessor :generate_secure_token_on
462
477
  self.generate_secure_token_on = :create
463
478
 
479
+ def self.deprecated_associations_options=(options)
480
+ raise ArgumentError, "deprecated_associations_options must be a hash" unless options.is_a?(Hash)
481
+
482
+ valid_keys = [:mode, :backtrace]
483
+
484
+ invalid_keys = options.keys - valid_keys
485
+ unless invalid_keys.empty?
486
+ inflected_key = invalid_keys.size == 1 ? "key" : "keys"
487
+ raise ArgumentError, "invalid deprecated_associations_options #{inflected_key} #{invalid_keys.map(&:inspect).to_sentence} (valid keys are #{valid_keys.map(&:inspect).to_sentence})"
488
+ end
489
+
490
+ options.each do |key, value|
491
+ ActiveRecord::Associations::Deprecation.send("#{key}=", value)
492
+ end
493
+ end
494
+
495
+ def self.deprecated_associations_options
496
+ {
497
+ mode: ActiveRecord::Associations::Deprecation.mode,
498
+ backtrace: ActiveRecord::Associations::Deprecation.backtrace
499
+ }
500
+ end
501
+
464
502
  def self.marshalling_format_version
465
503
  Marshalling.format_version
466
504
  end
@@ -497,6 +535,13 @@ module ActiveRecord
497
535
  }
498
536
  )
499
537
 
538
+ ##
539
+ # :singleton-method: message_verifiers
540
+ #
541
+ # ActiveSupport::MessageVerifiers instance for Active Record. If you are using
542
+ # Rails, this will be set to +Rails.application.message_verifiers+.
543
+ singleton_class.attr_accessor :message_verifiers
544
+
500
545
  def self.eager_load!
501
546
  super
502
547
  ActiveRecord::Locking.eager_load!
@@ -551,13 +596,30 @@ module ActiveRecord
551
596
  if active_connection = pool.active_connection
552
597
  current_transaction = active_connection.current_transaction
553
598
 
554
- if current_transaction.open? && current_transaction.joinable? && !current_transaction.state.invalidated?
599
+ if current_transaction.open? && current_transaction.joinable?
555
600
  open_transactions << current_transaction
556
601
  end
557
602
  end
558
603
  end
559
604
  open_transactions
560
605
  end
606
+
607
+ def self.default_transaction_isolation_level=(isolation_level) # :nodoc:
608
+ ActiveSupport::IsolatedExecutionState[:active_record_transaction_isolation] = isolation_level
609
+ end
610
+
611
+ def self.default_transaction_isolation_level # :nodoc:
612
+ ActiveSupport::IsolatedExecutionState[:active_record_transaction_isolation]
613
+ end
614
+
615
+ # Sets a transaction isolation level for all connection pools within the block.
616
+ def self.with_transaction_isolation_level(isolation_level, &block)
617
+ original_level = self.default_transaction_isolation_level
618
+ self.default_transaction_isolation_level = isolation_level
619
+ yield
620
+ ensure
621
+ self.default_transaction_isolation_level = original_level
622
+ end
561
623
  end
562
624
 
563
625
  ActiveSupport.on_load(:active_record) do
@@ -3,6 +3,8 @@
3
3
  module Arel # :nodoc: all
4
4
  module AliasPredication
5
5
  def as(other)
6
+ other = other.name if other.is_a?(Symbol)
7
+
6
8
  Nodes::As.new self, Nodes::SqlLiteral.new(other, retryable: true)
7
9
  end
8
10
  end
data/lib/arel/crud.rb CHANGED
@@ -14,12 +14,7 @@ module Arel # :nodoc: all
14
14
  InsertManager.new
15
15
  end
16
16
 
17
- def compile_update(
18
- values,
19
- key = nil,
20
- having_clause = nil,
21
- group_values_columns = []
22
- )
17
+ def compile_update(values, key = nil)
23
18
  um = UpdateManager.new(source)
24
19
  um.set(values)
25
20
  um.take(limit)
@@ -29,12 +24,12 @@ module Arel # :nodoc: all
29
24
  um.comment(comment)
30
25
  um.key = key
31
26
 
32
- um.group(group_values_columns) unless group_values_columns.empty?
33
- um.having(having_clause) unless having_clause.nil?
27
+ um.ast.groups = @ctx.groups
28
+ @ctx.havings.each { |h| um.having(h) }
34
29
  um
35
30
  end
36
31
 
37
- def compile_delete(key = nil, having_clause = nil, group_values_columns = [])
32
+ def compile_delete(key = nil)
38
33
  dm = DeleteManager.new(source)
39
34
  dm.take(limit)
40
35
  dm.offset(offset)
@@ -42,8 +37,8 @@ module Arel # :nodoc: all
42
37
  dm.wheres = constraints
43
38
  dm.comment(comment)
44
39
  dm.key = key
45
- dm.group(group_values_columns) unless group_values_columns.empty?
46
- dm.having(having_clause) unless having_clause.nil?
40
+ dm.ast.groups = @ctx.groups
41
+ @ctx.havings.each { |h| dm.having(h) }
47
42
  dm
48
43
  end
49
44
  end
@@ -3,8 +3,8 @@
3
3
  module Arel # :nodoc: all
4
4
  module Nodes
5
5
  class Count < Arel::Nodes::Function
6
- def initialize(expr, distinct = false, aliaz = nil)
7
- super(expr, aliaz)
6
+ def initialize(expr, distinct = false)
7
+ super(expr)
8
8
  @distinct = distinct
9
9
  end
10
10
  end
@@ -5,28 +5,22 @@ module Arel # :nodoc: all
5
5
  class Function < Arel::Nodes::NodeExpression
6
6
  include Arel::WindowPredications
7
7
  include Arel::FilterPredications
8
- attr_accessor :expressions, :alias, :distinct
9
8
 
10
- def initialize(expr, aliaz = nil)
9
+ attr_accessor :expressions, :distinct
10
+
11
+ def initialize(expr)
11
12
  super()
12
13
  @expressions = expr
13
- @alias = aliaz && SqlLiteral.new(aliaz)
14
14
  @distinct = false
15
15
  end
16
16
 
17
- def as(aliaz)
18
- self.alias = SqlLiteral.new(aliaz)
19
- self
20
- end
21
-
22
17
  def hash
23
- [@expressions, @alias, @distinct].hash
18
+ [@expressions, @distinct].hash
24
19
  end
25
20
 
26
21
  def eql?(other)
27
22
  self.class == other.class &&
28
23
  self.expressions == other.expressions &&
29
- self.alias == other.alias &&
30
24
  self.distinct == other.distinct
31
25
  end
32
26
  alias :== :eql?
@@ -5,8 +5,8 @@ module Arel # :nodoc: all
5
5
  class NamedFunction < Arel::Nodes::Function
6
6
  attr_accessor :name
7
7
 
8
- def initialize(name, expr, aliaz = nil)
9
- super(expr, aliaz)
8
+ def initialize(name, expr)
9
+ super(expr)
10
10
  @name = name
11
11
  end
12
12
 
@@ -6,7 +6,7 @@ module Arel # :nodoc: all
6
6
  #
7
7
  # Active Record uses Arel to compose SQL statements. Instead of building SQL strings directly, it's building an
8
8
  # abstract syntax tree (AST) of the statement using various types of Arel::Nodes::Node. Each node represents a
9
- # fragment of a SQL statement.
9
+ # fragment of an SQL statement.
10
10
  #
11
11
  # The intermediate representation allows Arel to compile the statement into the database's specific SQL dialect
12
12
  # only before sending it without having to care about the nuances of each database when building the statement.
data/lib/arel/nodes.rb CHANGED
@@ -45,8 +45,6 @@ require "arel/nodes/cte"
45
45
  require "arel/nodes/nary"
46
46
 
47
47
  # function
48
- # FIXME: Function + Alias can be rewritten as a Function and Alias node.
49
- # We should make Function a Unary node and deprecate the use of "aliaz"
50
48
  require "arel/nodes/function"
51
49
  require "arel/nodes/count"
52
50
  require "arel/nodes/extract"
@@ -74,8 +74,13 @@ module Arel # :nodoc: all
74
74
  def group(*columns)
75
75
  columns.each do |column|
76
76
  # FIXME: backwards compat
77
- column = Nodes::SqlLiteral.new(column) if String === column
78
- column = Nodes::SqlLiteral.new(column.to_s) if Symbol === column
77
+ case column
78
+ when Nodes::SqlLiteral
79
+ when String
80
+ column = Nodes::SqlLiteral.new(column)
81
+ when Symbol
82
+ column = Nodes::SqlLiteral.new(column.name)
83
+ end
79
84
 
80
85
  @ctx.groups.push Nodes::Group.new column
81
86
  end
@@ -34,7 +34,6 @@ module Arel # :nodoc: all
34
34
  def visit_Arel_Nodes_Function(o)
35
35
  visit_edge o, "expressions"
36
36
  visit_edge o, "distinct"
37
- visit_edge o, "alias"
38
37
  end
39
38
 
40
39
  def visit_Arel_Nodes_Unary(o)
@@ -108,14 +107,12 @@ module Arel # :nodoc: all
108
107
 
109
108
  def visit_Arel_Nodes_Extract(o)
110
109
  visit_edge o, "expressions"
111
- visit_edge o, "alias"
112
110
  end
113
111
 
114
112
  def visit_Arel_Nodes_NamedFunction(o)
115
113
  visit_edge o, "name"
116
114
  visit_edge o, "expressions"
117
115
  visit_edge o, "distinct"
118
- visit_edge o, "alias"
119
116
  end
120
117
 
121
118
  def visit_Arel_Nodes_InsertStatement(o)
@@ -4,6 +4,55 @@ module Arel # :nodoc: all
4
4
  module Visitors
5
5
  class PostgreSQL < Arel::Visitors::ToSql
6
6
  private
7
+ def visit_Arel_Nodes_UpdateStatement(o, collector)
8
+ collector.retryable = false
9
+ o = prepare_update_statement(o)
10
+
11
+ collector << "UPDATE "
12
+
13
+ # UPDATE with JOIN is in the form of:
14
+ #
15
+ # UPDATE t1 AS __active_record_update_alias
16
+ # SET ..
17
+ # FROM t1 JOIN t2 ON t2.join_id = t1.join_id ..
18
+ # WHERE t1.id = __active_record_update_alias.id AND ..
19
+ if has_join_sources?(o)
20
+ collector = visit o.relation.left, collector
21
+ collect_nodes_for o.values, collector, " SET "
22
+ collector << " FROM "
23
+ collector = inject_join o.relation.right, collector, " "
24
+ else
25
+ collector = visit o.relation, collector
26
+ collect_nodes_for o.values, collector, " SET "
27
+ end
28
+
29
+ collect_nodes_for o.wheres, collector, " WHERE ", " AND "
30
+ collect_nodes_for o.orders, collector, " ORDER BY "
31
+ maybe_visit o.limit, collector
32
+ maybe_visit o.comment, collector
33
+ end
34
+
35
+ # In the simple case, PostgreSQL allows us to place FROM or JOINs directly into the UPDATE
36
+ # query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
37
+ # these, we must use a subquery.
38
+ def prepare_update_statement(o)
39
+ if o.key && has_join_sources?(o) && !has_group_by_and_having?(o) && !has_limit_or_offset_or_orders?(o)
40
+ # Join clauses cannot reference the target table, so alias the
41
+ # updated table, place the entire relation in the FROM clause, and
42
+ # add a self-join (which requires the primary key)
43
+ stmt = o.clone
44
+ stmt.relation, stmt.wheres = o.relation.clone, o.wheres.clone
45
+ stmt.relation.right = [stmt.relation.left, *stmt.relation.right]
46
+ stmt.relation.left = stmt.relation.left.alias("__active_record_update_alias")
47
+ Array.wrap(o.key).each do |key|
48
+ stmt.wheres << key.eq(stmt.relation.left[key.name])
49
+ end
50
+ stmt
51
+ else
52
+ super
53
+ end
54
+ end
55
+
7
56
  def visit_Arel_Nodes_Matches(o, collector)
8
57
  op = o.case_sensitive ? " LIKE " : " ILIKE "
9
58
  collector = infix_value o, collector, op
@@ -66,6 +115,12 @@ module Arel # :nodoc: all
66
115
  grouping_parentheses o.expr, collector
67
116
  end
68
117
 
118
+ def visit_Arel_Nodes_InnerJoin(o, collector)
119
+ return super if o.right
120
+ collector << "CROSS JOIN "
121
+ visit o.left, collector
122
+ end
123
+
69
124
  def visit_Arel_Nodes_IsNotDistinctFrom(o, collector)
70
125
  collector = visit o.left, collector
71
126
  collector << " IS NOT DISTINCT FROM "
@@ -4,6 +4,61 @@ module Arel # :nodoc: all
4
4
  module Visitors
5
5
  class SQLite < Arel::Visitors::ToSql
6
6
  private
7
+ def visit_Arel_Nodes_UpdateStatement(o, collector)
8
+ collector.retryable = false
9
+ o = prepare_update_statement(o)
10
+
11
+ collector << "UPDATE "
12
+
13
+ # UPDATE with JOIN is in the form of:
14
+ #
15
+ # UPDATE t1 AS __active_record_update_alias
16
+ # SET ..
17
+ # FROM t1 JOIN t2 ON t2.join_id = t1.join_id ..
18
+ # WHERE t1.id = __active_record_update_alias.id AND ..
19
+ if has_join_sources?(o)
20
+ collector = visit o.relation.left, collector
21
+ collect_nodes_for o.values, collector, " SET "
22
+ collector << " FROM "
23
+ collector = inject_join o.relation.right, collector, " "
24
+ else
25
+ collector = visit o.relation, collector
26
+ collect_nodes_for o.values, collector, " SET "
27
+ end
28
+
29
+ collect_nodes_for o.wheres, collector, " WHERE ", " AND "
30
+ collect_nodes_for o.orders, collector, " ORDER BY "
31
+ maybe_visit o.limit, collector
32
+ maybe_visit o.comment, collector
33
+ end
34
+
35
+ def prepare_update_statement(o)
36
+ # Sqlite need to be built with the SQLITE_ENABLE_UPDATE_DELETE_LIMIT compile-time option
37
+ # to support LIMIT/OFFSET/ORDER in UPDATE and DELETE statements.
38
+ if o.key && has_join_sources?(o) && !has_group_by_and_having?(o) && !has_limit_or_offset_or_orders?(o)
39
+ # Join clauses cannot reference the target table, so alias the
40
+ # updated table, place the entire relation in the FROM clause, and
41
+ # add a self-join (which requires the primary key)
42
+ stmt = o.clone
43
+ stmt.relation, stmt.wheres = o.relation.clone, o.wheres.clone
44
+ stmt.relation.right = [stmt.relation.left, *stmt.relation.right]
45
+ stmt.relation.left = stmt.relation.left.alias("__active_record_update_alias")
46
+ Array.wrap(o.key).each do |key|
47
+ stmt.wheres << key.eq(stmt.relation.left[key.name])
48
+ end
49
+ stmt
50
+ else
51
+ super
52
+ end
53
+ end
54
+
55
+ def visit_Arel_Nodes_TableAlias(o, collector)
56
+ # "AS" is not optional in "{UPDATE | DELETE} table AS alias ..."
57
+ collector = visit o.relation, collector
58
+ collector << " AS "
59
+ collector << quote_table_name(o.name)
60
+ end
61
+
7
62
  # Locks are not supported in SQLite
8
63
  def visit_Arel_Nodes_Lock(o, collector)
9
64
  collector
@@ -14,14 +69,6 @@ module Arel # :nodoc: all
14
69
  super
15
70
  end
16
71
 
17
- def visit_Arel_Nodes_True(o, collector)
18
- collector << "1"
19
- end
20
-
21
- def visit_Arel_Nodes_False(o, collector)
22
- collector << "0"
23
- end
24
-
25
72
  def visit_Arel_Nodes_IsNotDistinctFrom(o, collector)
26
73
  collector = visit o.left, collector
27
74
  collector << " IS "
@@ -77,13 +77,7 @@ module Arel # :nodoc: all
77
77
 
78
78
  def visit_Arel_Nodes_Exists(o, collector)
79
79
  collector << "EXISTS ("
80
- collector = visit(o.expressions, collector) << ")"
81
- if o.alias
82
- collector << " AS "
83
- visit o.alias, collector
84
- else
85
- collector
86
- end
80
+ visit(o.expressions, collector) << ")"
87
81
  end
88
82
 
89
83
  def visit_Arel_Nodes_Casted(o, collector)
@@ -390,13 +384,7 @@ module Arel # :nodoc: all
390
384
  collector << o.name
391
385
  collector << "("
392
386
  collector << "DISTINCT " if o.distinct
393
- collector = inject_join(o.expressions, collector, ", ") << ")"
394
- if o.alias
395
- collector << " AS "
396
- visit o.alias, collector
397
- else
398
- collector
399
- end
387
+ inject_join(o.expressions, collector, ", ") << ")"
400
388
  end
401
389
 
402
390
  def visit_Arel_Nodes_Extract(o, collector)
@@ -1000,13 +988,7 @@ module Arel # :nodoc: all
1000
988
  if o.distinct
1001
989
  collector << "DISTINCT "
1002
990
  end
1003
- collector = inject_join(o.expressions, collector, ", ") << ")"
1004
- if o.alias
1005
- collector << " AS "
1006
- visit o.alias, collector
1007
- else
1008
- collector
1009
- end
991
+ inject_join(o.expressions, collector, ", ") << ")"
1010
992
  end
1011
993
 
1012
994
  def is_distinct_from(o, collector)
data/lib/arel.rb CHANGED
@@ -50,7 +50,9 @@ module Arel
50
50
  # Use this option only if the SQL is idempotent, as it could be executed
51
51
  # more than once.
52
52
  def self.sql(sql_string, *positional_binds, retryable: false, **named_binds)
53
- if positional_binds.empty? && named_binds.empty?
53
+ if Arel::Nodes::SqlLiteral === sql_string
54
+ sql_string
55
+ elsif positional_binds.empty? && named_binds.empty?
54
56
  Arel::Nodes::SqlLiteral.new(sql_string, retryable: retryable)
55
57
  else
56
58
  Arel::Nodes::BoundSqlLiteral.new sql_string, positional_binds, named_binds
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord
3
3
  version: !ruby/object:Gem::Version
4
- version: 8.0.3
4
+ version: 8.1.0.beta1
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Heinemeier Hansson
@@ -15,28 +15,28 @@ dependencies:
15
15
  requirements:
16
16
  - - '='
17
17
  - !ruby/object:Gem::Version
18
- version: 8.0.3
18
+ version: 8.1.0.beta1
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - '='
24
24
  - !ruby/object:Gem::Version
25
- version: 8.0.3
25
+ version: 8.1.0.beta1
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: activemodel
28
28
  requirement: !ruby/object:Gem::Requirement
29
29
  requirements:
30
30
  - - '='
31
31
  - !ruby/object:Gem::Version
32
- version: 8.0.3
32
+ version: 8.1.0.beta1
33
33
  type: :runtime
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - '='
38
38
  - !ruby/object:Gem::Version
39
- version: 8.0.3
39
+ version: 8.1.0.beta1
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: timeout
42
42
  requirement: !ruby/object:Gem::Requirement
@@ -83,6 +83,7 @@ files:
83
83
  - lib/active_record/associations/builder/singular_association.rb
84
84
  - lib/active_record/associations/collection_association.rb
85
85
  - lib/active_record/associations/collection_proxy.rb
86
+ - lib/active_record/associations/deprecation.rb
86
87
  - lib/active_record/associations/disable_joins_association_scope.rb
87
88
  - lib/active_record/associations/errors.rb
88
89
  - lib/active_record/associations/foreign_association.rb
@@ -253,6 +254,7 @@ files:
253
254
  - lib/active_record/explain.rb
254
255
  - lib/active_record/explain_registry.rb
255
256
  - lib/active_record/explain_subscriber.rb
257
+ - lib/active_record/filter_attribute_handler.rb
256
258
  - lib/active_record/fixture_set/file.rb
257
259
  - lib/active_record/fixture_set/model_metadata.rb
258
260
  - lib/active_record/fixture_set/render_context.rb
@@ -279,6 +281,7 @@ files:
279
281
  - lib/active_record/migration.rb
280
282
  - lib/active_record/migration/command_recorder.rb
281
283
  - lib/active_record/migration/compatibility.rb
284
+ - lib/active_record/migration/default_schema_versions_formatter.rb
282
285
  - lib/active_record/migration/default_strategy.rb
283
286
  - lib/active_record/migration/execution_strategy.rb
284
287
  - lib/active_record/migration/join_table.rb
@@ -286,7 +289,6 @@ files:
286
289
  - lib/active_record/model_schema.rb
287
290
  - lib/active_record/nested_attributes.rb
288
291
  - lib/active_record/no_touching.rb
289
- - lib/active_record/normalization.rb
290
292
  - lib/active_record/persistence.rb
291
293
  - lib/active_record/promise.rb
292
294
  - lib/active_record/query_cache.rb
@@ -297,6 +299,7 @@ files:
297
299
  - lib/active_record/railties/console_sandbox.rb
298
300
  - lib/active_record/railties/controller_runtime.rb
299
301
  - lib/active_record/railties/databases.rake
302
+ - lib/active_record/railties/job_checkpoints.rb
300
303
  - lib/active_record/railties/job_runtime.rb
301
304
  - lib/active_record/readonly_attributes.rb
302
305
  - lib/active_record/reflection.rb
@@ -336,6 +339,7 @@ files:
336
339
  - lib/active_record/store.rb
337
340
  - lib/active_record/suppressor.rb
338
341
  - lib/active_record/table_metadata.rb
342
+ - lib/active_record/tasks/abstract_tasks.rb
339
343
  - lib/active_record/tasks/database_tasks.rb
340
344
  - lib/active_record/tasks/mysql_database_tasks.rb
341
345
  - lib/active_record/tasks/postgresql_database_tasks.rb
@@ -474,10 +478,10 @@ licenses:
474
478
  - MIT
475
479
  metadata:
476
480
  bug_tracker_uri: https://github.com/rails/rails/issues
477
- changelog_uri: https://github.com/rails/rails/blob/v8.0.3/activerecord/CHANGELOG.md
478
- documentation_uri: https://api.rubyonrails.org/v8.0.3/
481
+ changelog_uri: https://github.com/rails/rails/blob/v8.1.0.beta1/activerecord/CHANGELOG.md
482
+ documentation_uri: https://api.rubyonrails.org/v8.1.0.beta1/
479
483
  mailing_list_uri: https://discuss.rubyonrails.org/c/rubyonrails-talk
480
- source_code_uri: https://github.com/rails/rails/tree/v8.0.3/activerecord
484
+ source_code_uri: https://github.com/rails/rails/tree/v8.1.0.beta1/activerecord
481
485
  rubygems_mfa_required: 'true'
482
486
  rdoc_options:
483
487
  - "--main"