activerecord 7.0.0.alpha2 → 7.0.0

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 (109) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +539 -11
  3. data/lib/active_record/associations/association.rb +2 -8
  4. data/lib/active_record/associations/builder/collection_association.rb +9 -2
  5. data/lib/active_record/associations/collection_association.rb +10 -2
  6. data/lib/active_record/associations/join_dependency.rb +6 -2
  7. data/lib/active_record/associations/preloader/association.rb +68 -48
  8. data/lib/active_record/associations/preloader/batch.rb +3 -6
  9. data/lib/active_record/associations/preloader/through_association.rb +19 -9
  10. data/lib/active_record/associations/preloader.rb +14 -24
  11. data/lib/active_record/associations/through_association.rb +2 -2
  12. data/lib/active_record/associations.rb +16 -3
  13. data/lib/active_record/asynchronous_queries_tracker.rb +3 -0
  14. data/lib/active_record/attribute_methods/dirty.rb +9 -1
  15. data/lib/active_record/attribute_methods.rb +7 -5
  16. data/lib/active_record/autosave_association.rb +3 -3
  17. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +6 -26
  18. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -2
  19. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +18 -5
  20. data/lib/active_record/connection_adapters/abstract/database_limits.rb +0 -17
  21. data/lib/active_record/connection_adapters/abstract/database_statements.rb +1 -1
  22. data/lib/active_record/connection_adapters/abstract/quoting.rb +33 -70
  23. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +4 -0
  24. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +9 -2
  25. data/lib/active_record/connection_adapters/abstract/transaction.rb +12 -19
  26. data/lib/active_record/connection_adapters/abstract_adapter.rb +37 -8
  27. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +1 -0
  28. data/lib/active_record/connection_adapters/column.rb +4 -0
  29. data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -2
  30. data/lib/active_record/connection_adapters/mysql/quoting.rb +23 -24
  31. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +1 -1
  32. data/lib/active_record/connection_adapters/pool_config.rb +7 -5
  33. data/lib/active_record/connection_adapters/postgresql/column.rb +17 -1
  34. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +2 -0
  35. data/lib/active_record/connection_adapters/postgresql/quoting.rb +44 -44
  36. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +21 -1
  37. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +18 -1
  38. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +25 -0
  39. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +15 -4
  40. data/lib/active_record/connection_adapters/postgresql_adapter.rb +48 -5
  41. data/lib/active_record/connection_adapters/schema_cache.rb +3 -1
  42. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +4 -2
  43. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +15 -16
  44. data/lib/active_record/connection_handling.rb +31 -19
  45. data/lib/active_record/core.rb +13 -24
  46. data/lib/active_record/database_configurations/connection_url_resolver.rb +2 -1
  47. data/lib/active_record/database_configurations/database_config.rb +0 -9
  48. data/lib/active_record/database_configurations/hash_config.rb +40 -8
  49. data/lib/active_record/database_configurations.rb +2 -27
  50. data/lib/active_record/delegated_type.rb +19 -0
  51. data/lib/active_record/encryption/encryptable_record.rb +1 -1
  52. data/lib/active_record/encryption/encrypted_attribute_type.rb +1 -1
  53. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +1 -2
  54. data/lib/active_record/encryption/message_serializer.rb +11 -1
  55. data/lib/active_record/encryption/scheme.rb +1 -1
  56. data/lib/active_record/enum.rb +8 -1
  57. data/lib/active_record/errors.rb +1 -1
  58. data/lib/active_record/explain_registry.rb +11 -6
  59. data/lib/active_record/fixture_set/table_row.rb +1 -1
  60. data/lib/active_record/fixtures.rb +1 -9
  61. data/lib/active_record/future_result.rb +2 -2
  62. data/lib/active_record/gem_version.rb +1 -1
  63. data/lib/active_record/insert_all.rb +52 -15
  64. data/lib/active_record/integration.rb +3 -2
  65. data/lib/active_record/legacy_yaml_adapter.rb +2 -39
  66. data/lib/active_record/locking/pessimistic.rb +9 -3
  67. data/lib/active_record/log_subscriber.rb +8 -1
  68. data/lib/active_record/middleware/shard_selector.rb +60 -0
  69. data/lib/active_record/migration.rb +2 -2
  70. data/lib/active_record/model_schema.rb +1 -28
  71. data/lib/active_record/nested_attributes.rb +11 -10
  72. data/lib/active_record/no_touching.rb +1 -1
  73. data/lib/active_record/persistence.rb +99 -21
  74. data/lib/active_record/query_logs.rb +18 -83
  75. data/lib/active_record/railtie.rb +11 -1
  76. data/lib/active_record/railties/databases.rake +4 -91
  77. data/lib/active_record/reflection.rb +22 -6
  78. data/lib/active_record/relation/calculations.rb +1 -10
  79. data/lib/active_record/relation/finder_methods.rb +0 -13
  80. data/lib/active_record/relation/query_methods.rb +5 -14
  81. data/lib/active_record/relation/record_fetch_warning.rb +5 -7
  82. data/lib/active_record/relation/where_clause.rb +2 -15
  83. data/lib/active_record/relation.rb +11 -15
  84. data/lib/active_record/result.rb +0 -5
  85. data/lib/active_record/runtime_registry.rb +10 -12
  86. data/lib/active_record/schema_dumper.rb +7 -0
  87. data/lib/active_record/schema_migration.rb +4 -0
  88. data/lib/active_record/scoping.rb +34 -22
  89. data/lib/active_record/suppressor.rb +11 -15
  90. data/lib/active_record/tasks/database_tasks.rb +18 -44
  91. data/lib/active_record/tasks/postgresql_database_tasks.rb +5 -1
  92. data/lib/active_record/validations/uniqueness.rb +1 -1
  93. data/lib/active_record.rb +41 -33
  94. data/lib/arel/crud.rb +12 -2
  95. data/lib/arel/delete_manager.rb +16 -0
  96. data/lib/arel/filter_predications.rb +9 -0
  97. data/lib/arel/nodes/delete_statement.rb +5 -1
  98. data/lib/arel/nodes/filter.rb +10 -0
  99. data/lib/arel/nodes/function.rb +1 -0
  100. data/lib/arel/nodes/update_statement.rb +5 -1
  101. data/lib/arel/nodes.rb +1 -0
  102. data/lib/arel/predications.rb +10 -2
  103. data/lib/arel/update_manager.rb +16 -0
  104. data/lib/arel/visitors/mysql.rb +2 -1
  105. data/lib/arel/visitors/to_sql.rb +15 -0
  106. data/lib/arel.rb +1 -0
  107. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  108. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  109. metadata +18 -12
@@ -30,32 +30,28 @@ module ActiveRecord
30
30
  module Suppressor
31
31
  extend ActiveSupport::Concern
32
32
 
33
+ class << self
34
+ def registry # :nodoc:
35
+ ActiveSupport::IsolatedExecutionState[:active_record_suppresor_registry] ||= {}
36
+ end
37
+ end
38
+
33
39
  module ClassMethods
34
40
  def suppress(&block)
35
- previous_state = SuppressorRegistry.suppressed[name]
36
- SuppressorRegistry.suppressed[name] = true
41
+ previous_state = Suppressor.registry[name]
42
+ Suppressor.registry[name] = true
37
43
  yield
38
44
  ensure
39
- SuppressorRegistry.suppressed[name] = previous_state
45
+ Suppressor.registry[name] = previous_state
40
46
  end
41
47
  end
42
48
 
43
49
  def save(**) # :nodoc:
44
- SuppressorRegistry.suppressed[self.class.name] ? true : super
50
+ Suppressor.registry[self.class.name] ? true : super
45
51
  end
46
52
 
47
53
  def save!(**) # :nodoc:
48
- SuppressorRegistry.suppressed[self.class.name] ? true : super
49
- end
50
- end
51
-
52
- class SuppressorRegistry # :nodoc:
53
- extend ActiveSupport::PerThreadRegistry
54
-
55
- attr_reader :suppressed
56
-
57
- def initialize
58
- @suppressed = {}
54
+ Suppressor.registry[self.class.name] ? true : super
59
55
  end
60
56
  end
61
57
  end
@@ -55,8 +55,7 @@ module ActiveRecord
55
55
 
56
56
  extend self
57
57
 
58
- attr_writer :current_config, :db_dir, :migrations_paths, :fixtures_path, :root, :env, :seed_loader
59
- deprecate :current_config=
58
+ attr_writer :db_dir, :migrations_paths, :fixtures_path, :root, :env, :seed_loader
60
59
  attr_accessor :database_configuration
61
60
 
62
61
  LOCAL_HOSTS = ["127.0.0.1", "localhost"]
@@ -110,11 +109,6 @@ module ActiveRecord
110
109
  @env ||= Rails.env
111
110
  end
112
111
 
113
- def spec
114
- @spec ||= "primary"
115
- end
116
- deprecate spec: "please use name instead"
117
-
118
112
  def name
119
113
  @name ||= "primary"
120
114
  end
@@ -123,18 +117,6 @@ module ActiveRecord
123
117
  @seed_loader ||= Rails.application
124
118
  end
125
119
 
126
- def current_config(options = {})
127
- if options.has_key?(:config)
128
- @current_config = options[:config]
129
- else
130
- env_name = options[:env] || env
131
- name = options[:spec] || "primary"
132
-
133
- @current_config ||= configs_for(env_name: env_name, name: name)&.configuration_hash
134
- end
135
- end
136
- deprecate :current_config
137
-
138
120
  def create(configuration, *arguments)
139
121
  db_config = resolve_configuration(configuration)
140
122
  database_adapter_for(db_config, *arguments).create
@@ -216,10 +198,9 @@ module ActiveRecord
216
198
  dump_schema(db_config, ActiveRecord.schema_format)
217
199
  end
218
200
  rescue ActiveRecord::NoDatabaseError
219
- config_name = db_config.name
220
- create_current(db_config.env_name, config_name)
201
+ create_current(db_config.env_name, db_config.name)
221
202
 
222
- if File.exist?(dump_filename(config_name))
203
+ if File.exist?(schema_dump_path(db_config))
223
204
  load_schema(
224
205
  db_config,
225
206
  ActiveRecord.schema_format,
@@ -378,7 +359,7 @@ module ActiveRecord
378
359
  end
379
360
 
380
361
  def load_schema(db_config, format = ActiveRecord.schema_format, file = nil) # :nodoc:
381
- file ||= dump_filename(db_config.name, format)
362
+ file ||= schema_dump_path(db_config, format)
382
363
 
383
364
  verbose_was, Migration.verbose = Migration.verbose, verbose? && ENV["VERBOSE"]
384
365
  check_schema_file(file)
@@ -399,16 +380,10 @@ module ActiveRecord
399
380
  Migration.verbose = verbose_was
400
381
  end
401
382
 
402
- def schema_up_to_date?(configuration, format = ActiveRecord.schema_format, file = nil, environment = nil, name = nil)
383
+ def schema_up_to_date?(configuration, format = ActiveRecord.schema_format, file = nil)
403
384
  db_config = resolve_configuration(configuration)
404
385
 
405
- if environment || name
406
- ActiveSupport::Deprecation.warn("`environment` and `name` will be removed as parameters in 7.0.0, you may now pass an ActiveRecord::DatabaseConfigurations::DatabaseConfig as `configuration` instead.")
407
- end
408
-
409
- name ||= db_config.name
410
-
411
- file ||= dump_filename(name, format)
386
+ file ||= schema_dump_path(db_config)
412
387
 
413
388
  return true unless File.exist?(file)
414
389
 
@@ -421,7 +396,7 @@ module ActiveRecord
421
396
  end
422
397
 
423
398
  def reconstruct_from_schema(db_config, format = ActiveRecord.schema_format, file = nil) # :nodoc:
424
- file ||= dump_filename(db_config.name, format)
399
+ file ||= schema_dump_path(db_config, format)
425
400
 
426
401
  check_schema_file(file)
427
402
 
@@ -440,7 +415,7 @@ module ActiveRecord
440
415
 
441
416
  def dump_schema(db_config, format = ActiveRecord.schema_format) # :nodoc:
442
417
  require "active_record/schema_dumper"
443
- filename = dump_filename(db_config.name, format)
418
+ filename = schema_dump_path(db_config, format)
444
419
  connection = ActiveRecord::Base.connection
445
420
 
446
421
  FileUtils.mkdir_p(db_dir)
@@ -460,11 +435,6 @@ module ActiveRecord
460
435
  end
461
436
  end
462
437
 
463
- def schema_file(format = ActiveRecord.schema_format)
464
- File.join(db_dir, schema_file_type(format))
465
- end
466
- deprecate :schema_file
467
-
468
438
  def schema_file_type(format = ActiveRecord.schema_format)
469
439
  case format
470
440
  when :ruby
@@ -473,15 +443,19 @@ module ActiveRecord
473
443
  "structure.sql"
474
444
  end
475
445
  end
446
+ deprecate :schema_file_type
476
447
 
477
- def dump_filename(db_config_name, format = ActiveRecord.schema_format)
478
- filename = if ActiveRecord::Base.configurations.primary?(db_config_name)
479
- schema_file_type(format)
448
+ def schema_dump_path(db_config, format = ActiveRecord.schema_format)
449
+ return ENV["SCHEMA"] if ENV["SCHEMA"]
450
+
451
+ filename = db_config.schema_dump(format)
452
+ return unless filename
453
+
454
+ if File.dirname(filename) == ActiveRecord::Tasks::DatabaseTasks.db_dir
455
+ filename
480
456
  else
481
- "#{db_config_name}_#{schema_file_type(format)}"
457
+ File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, filename)
482
458
  end
483
-
484
- ENV["SCHEMA"] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, filename)
485
459
  end
486
460
 
487
461
  def cache_dump_filename(db_config_name, schema_cache_path: nil)
@@ -57,8 +57,12 @@ module ActiveRecord
57
57
  ActiveRecord.dump_schemas
58
58
  end
59
59
 
60
- args = ["--schema-only", "--no-privileges", "--no-owner", "--file", filename]
60
+ args = ["--schema-only", "--no-privileges", "--no-owner"]
61
+ args << "--no-comment" if connection.database_version >= 110_000
62
+ args.concat(["--file", filename])
63
+
61
64
  args.concat(Array(extra_flags)) if extra_flags
65
+
62
66
  unless search_path.blank?
63
67
  args += search_path.split(",").map do |part|
64
68
  "--schema=#{part.strip}"
@@ -161,7 +161,7 @@ module ActiveRecord
161
161
  # <tt>WHERE</tt> SQL fragment to limit the uniqueness constraint lookup
162
162
  # (e.g. <tt>conditions: -> { where(status: 'active') }</tt>).
163
163
  # * <tt>:case_sensitive</tt> - Looks for an exact match. Ignored by
164
- # non-text columns (+true+ by default).
164
+ # non-text columns. The default behavior respects the default database collation.
165
165
  # * <tt>:allow_nil</tt> - If set to +true+, skips this validation if the
166
166
  # attribute is +nil+ (default is +false+).
167
167
  # * <tt>:allow_blank</tt> - If set to +true+, skips this validation if the
data/lib/active_record.rb CHANGED
@@ -38,23 +38,23 @@ module ActiveRecord
38
38
 
39
39
  autoload :Base
40
40
  autoload :Callbacks
41
- autoload :Core
42
41
  autoload :ConnectionHandling
42
+ autoload :Core
43
43
  autoload :CounterCache
44
- autoload :DynamicMatchers
45
44
  autoload :DelegatedType
45
+ autoload :DestroyAssociationAsyncJob
46
+ autoload :DynamicMatchers
46
47
  autoload :Encryption
47
48
  autoload :Enum
48
- autoload :InternalMetadata
49
49
  autoload :Explain
50
50
  autoload :Inheritance
51
51
  autoload :Integration
52
+ autoload :InternalMetadata
52
53
  autoload :Migration
53
54
  autoload :Migrator, "active_record/migration"
54
55
  autoload :ModelSchema
55
56
  autoload :NestedAttributes
56
57
  autoload :NoTouching
57
- autoload :TouchLater
58
58
  autoload :Persistence
59
59
  autoload :QueryCache
60
60
  autoload :Querying
@@ -68,34 +68,37 @@ module ActiveRecord
68
68
  autoload :SchemaDumper
69
69
  autoload :SchemaMigration
70
70
  autoload :Scoping
71
+ autoload :SecureToken
71
72
  autoload :Serialization
72
- autoload :Store
73
73
  autoload :SignedId
74
+ autoload :Store
74
75
  autoload :Suppressor
76
+ autoload :TestDatabases
77
+ autoload :TestFixtures, "active_record/fixtures"
75
78
  autoload :Timestamp
79
+ autoload :TouchLater
76
80
  autoload :Transactions
77
81
  autoload :Translation
78
82
  autoload :Validations
79
- autoload :SecureToken
80
- autoload :DestroyAssociationAsyncJob
81
83
 
82
84
  eager_autoload do
83
- autoload :StatementCache
84
- autoload :ConnectionAdapters
85
-
86
85
  autoload :Aggregations
86
+ autoload :AssociationRelation
87
87
  autoload :Associations
88
+ autoload :AsynchronousQueriesTracker
88
89
  autoload :AttributeAssignment
89
90
  autoload :AttributeMethods
90
91
  autoload :AutosaveAssociation
91
- autoload :AsynchronousQueriesTracker
92
-
93
- autoload :LegacyYamlAdapter
94
-
95
- autoload :Relation
96
- autoload :AssociationRelation
92
+ autoload :ConnectionAdapters
97
93
  autoload :DisableJoinsAssociationRelation
94
+ autoload :FutureResult
95
+ autoload :LegacyYamlAdapter
98
96
  autoload :NullRelation
97
+ autoload :Relation
98
+ autoload :Result
99
+ autoload :StatementCache
100
+ autoload :TableMetadata
101
+ autoload :Type
99
102
 
100
103
  autoload_under "relation" do
101
104
  autoload :QueryMethods
@@ -106,16 +109,11 @@ module ActiveRecord
106
109
  autoload :Batches
107
110
  autoload :Delegation
108
111
  end
109
-
110
- autoload :Result
111
- autoload :FutureResult
112
- autoload :TableMetadata
113
- autoload :Type
114
112
  end
115
113
 
116
114
  module Coders
117
- autoload :YAMLColumn, "active_record/coders/yaml_column"
118
115
  autoload :JSON, "active_record/coders/json"
116
+ autoload :YAMLColumn, "active_record/coders/yaml_column"
119
117
  end
120
118
 
121
119
  module AttributeMethods
@@ -127,9 +125,9 @@ module ActiveRecord
127
125
  autoload :PrimaryKey
128
126
  autoload :Query
129
127
  autoload :Read
128
+ autoload :Serialization
130
129
  autoload :TimeZoneConversion
131
130
  autoload :Write
132
- autoload :Serialization
133
131
  end
134
132
  end
135
133
 
@@ -146,29 +144,32 @@ module ActiveRecord
146
144
  extend ActiveSupport::Autoload
147
145
 
148
146
  eager_autoload do
149
- autoload :Named
150
147
  autoload :Default
148
+ autoload :Named
151
149
  end
152
150
  end
153
151
 
154
152
  module Middleware
155
153
  extend ActiveSupport::Autoload
156
154
 
157
- autoload :DatabaseSelector, "active_record/middleware/database_selector"
155
+ autoload :DatabaseSelector
156
+ autoload :ShardSelector
158
157
  end
159
158
 
160
159
  module Tasks
161
160
  extend ActiveSupport::Autoload
162
161
 
163
162
  autoload :DatabaseTasks
164
- autoload :SQLiteDatabaseTasks, "active_record/tasks/sqlite_database_tasks"
165
163
  autoload :MySQLDatabaseTasks, "active_record/tasks/mysql_database_tasks"
166
- autoload :PostgreSQLDatabaseTasks,
167
- "active_record/tasks/postgresql_database_tasks"
164
+ autoload :PostgreSQLDatabaseTasks, "active_record/tasks/postgresql_database_tasks"
165
+ autoload :SQLiteDatabaseTasks, "active_record/tasks/sqlite_database_tasks"
168
166
  end
169
167
 
170
- autoload :TestDatabases, "active_record/test_databases"
171
- autoload :TestFixtures, "active_record/fixtures"
168
+ # Lazily load the schema cache. This option will load the schema cache
169
+ # when a connection is established rather than on boot. If set,
170
+ # +config.active_record.use_schema_cache_dump+ will be set to false.
171
+ singleton_class.attr_accessor :lazily_load_schema_cache
172
+ self.lazily_load_schema_cache = false
172
173
 
173
174
  # A list of tables or regex's to match tables to ignore when
174
175
  # dumping the schema cache. For example if this is set to +[/^_/]+
@@ -179,11 +180,18 @@ module ActiveRecord
179
180
  singleton_class.attr_accessor :legacy_connection_handling
180
181
  self.legacy_connection_handling = true
181
182
 
182
- ##
183
- # :singleton-method:
183
+ singleton_class.attr_reader :default_timezone
184
+
184
185
  # Determines whether to use Time.utc (using :utc) or Time.local (using :local) when pulling
185
186
  # dates and times from the database. This is set to :utc by default.
186
- singleton_class.attr_accessor :default_timezone
187
+ def self.default_timezone=(default_timezone)
188
+ unless %i(local utc).include?(default_timezone)
189
+ raise ArgumentError, "default_timezone must be either :utc (default) or :local."
190
+ end
191
+
192
+ @default_timezone = default_timezone
193
+ end
194
+
187
195
  self.default_timezone = :utc
188
196
 
189
197
  singleton_class.attr_accessor :writing_role
data/lib/arel/crud.rb CHANGED
@@ -14,7 +14,12 @@ module Arel # :nodoc: all
14
14
  InsertManager.new
15
15
  end
16
16
 
17
- def compile_update(values, key = nil)
17
+ def compile_update(
18
+ values,
19
+ key = nil,
20
+ having_clause = nil,
21
+ group_values_columns = []
22
+ )
18
23
  um = UpdateManager.new(source)
19
24
  um.set(values)
20
25
  um.take(limit)
@@ -22,16 +27,21 @@ module Arel # :nodoc: all
22
27
  um.order(*orders)
23
28
  um.wheres = constraints
24
29
  um.key = key
30
+
31
+ um.group(group_values_columns) unless group_values_columns.empty?
32
+ um.having(having_clause) unless having_clause.nil?
25
33
  um
26
34
  end
27
35
 
28
- def compile_delete(key = nil)
36
+ def compile_delete(key = nil, having_clause = nil, group_values_columns = [])
29
37
  dm = DeleteManager.new(source)
30
38
  dm.take(limit)
31
39
  dm.offset(offset)
32
40
  dm.order(*orders)
33
41
  dm.wheres = constraints
34
42
  dm.key = key
43
+ dm.group(group_values_columns) unless group_values_columns.empty?
44
+ dm.having(having_clause) unless having_clause.nil?
35
45
  dm
36
46
  end
37
47
  end
@@ -12,5 +12,21 @@ module Arel # :nodoc: all
12
12
  @ast.relation = relation
13
13
  self
14
14
  end
15
+
16
+ def group(columns)
17
+ columns.each do |column|
18
+ column = Nodes::SqlLiteral.new(column) if String === column
19
+ column = Nodes::SqlLiteral.new(column.to_s) if Symbol === column
20
+
21
+ @ast.groups.push Nodes::Group.new column
22
+ end
23
+
24
+ self
25
+ end
26
+
27
+ def having(expr)
28
+ @ast.havings << expr
29
+ self
30
+ end
15
31
  end
16
32
  end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arel
4
+ module FilterPredications
5
+ def filter(expr)
6
+ Nodes::Filter.new(self, expr)
7
+ end
8
+ end
9
+ end
@@ -3,12 +3,14 @@
3
3
  module Arel # :nodoc: all
4
4
  module Nodes
5
5
  class DeleteStatement < Arel::Nodes::Node
6
- attr_accessor :relation, :wheres, :orders, :limit, :offset, :key
6
+ attr_accessor :relation, :wheres, :groups, :havings, :orders, :limit, :offset, :key
7
7
 
8
8
  def initialize(relation = nil, wheres = [])
9
9
  super()
10
10
  @relation = relation
11
11
  @wheres = wheres
12
+ @groups = []
13
+ @havings = []
12
14
  @orders = []
13
15
  @limit = nil
14
16
  @offset = nil
@@ -30,6 +32,8 @@ module Arel # :nodoc: all
30
32
  self.relation == other.relation &&
31
33
  self.wheres == other.wheres &&
32
34
  self.orders == other.orders &&
35
+ self.groups == other.groups &&
36
+ self.havings == other.havings &&
33
37
  self.limit == other.limit &&
34
38
  self.offset == other.offset &&
35
39
  self.key == other.key
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arel
4
+ module Nodes
5
+ class Filter < Binary
6
+ include Arel::WindowPredications
7
+ include Arel::AliasPredication
8
+ end
9
+ end
10
+ end
@@ -4,6 +4,7 @@ module Arel # :nodoc: all
4
4
  module Nodes
5
5
  class Function < Arel::Nodes::NodeExpression
6
6
  include Arel::WindowPredications
7
+ include Arel::FilterPredications
7
8
  attr_accessor :expressions, :alias, :distinct
8
9
 
9
10
  def initialize(expr, aliaz = nil)
@@ -3,13 +3,15 @@
3
3
  module Arel # :nodoc: all
4
4
  module Nodes
5
5
  class UpdateStatement < Arel::Nodes::Node
6
- attr_accessor :relation, :wheres, :values, :orders, :limit, :offset, :key
6
+ attr_accessor :relation, :wheres, :values, :groups, :havings, :orders, :limit, :offset, :key
7
7
 
8
8
  def initialize(relation = nil)
9
9
  super()
10
10
  @relation = relation
11
11
  @wheres = []
12
12
  @values = []
13
+ @groups = []
14
+ @havings = []
13
15
  @orders = []
14
16
  @limit = nil
15
17
  @offset = nil
@@ -31,6 +33,8 @@ module Arel # :nodoc: all
31
33
  self.relation == other.relation &&
32
34
  self.wheres == other.wheres &&
33
35
  self.values == other.values &&
36
+ self.groups == other.groups &&
37
+ self.havings == other.havings &&
34
38
  self.orders == other.orders &&
35
39
  self.limit == other.limit &&
36
40
  self.offset == other.offset &&
data/lib/arel/nodes.rb CHANGED
@@ -28,6 +28,7 @@ require "arel/nodes/with"
28
28
  # binary
29
29
  require "arel/nodes/binary"
30
30
  require "arel/nodes/equality"
31
+ require "arel/nodes/filter"
31
32
  require "arel/nodes/in"
32
33
  require "arel/nodes/join_source"
33
34
  require "arel/nodes/delete_statement"
@@ -39,7 +39,11 @@ module Arel # :nodoc: all
39
39
  self.in([])
40
40
  elsif open_ended?(other.begin)
41
41
  if open_ended?(other.end)
42
- not_in([])
42
+ if infinity?(other.begin) == 1 || infinity?(other.end) == -1
43
+ self.in([])
44
+ else
45
+ not_in([])
46
+ end
43
47
  elsif other.exclude_end?
44
48
  lt(other.end)
45
49
  else
@@ -80,7 +84,11 @@ module Arel # :nodoc: all
80
84
  not_in([])
81
85
  elsif open_ended?(other.begin)
82
86
  if open_ended?(other.end)
83
- self.in([])
87
+ if infinity?(other.begin) == 1 || infinity?(other.end) == -1
88
+ not_in([])
89
+ else
90
+ self.in([])
91
+ end
84
92
  elsif other.exclude_end?
85
93
  gteq(other.end)
86
94
  else
@@ -28,5 +28,21 @@ module Arel # :nodoc: all
28
28
  end
29
29
  self
30
30
  end
31
+
32
+ def group(columns)
33
+ columns.each do |column|
34
+ column = Nodes::SqlLiteral.new(column) if String === column
35
+ column = Nodes::SqlLiteral.new(column.to_s) if Symbol === column
36
+
37
+ @ast.groups.push Nodes::Group.new column
38
+ end
39
+
40
+ self
41
+ end
42
+
43
+ def having(expr)
44
+ @ast.havings << expr
45
+ self
46
+ end
31
47
  end
32
48
  end
@@ -67,7 +67,8 @@ module Arel # :nodoc: all
67
67
  # query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
68
68
  # these, we must use a subquery.
69
69
  def prepare_update_statement(o)
70
- if o.offset || has_join_sources?(o) && has_limit_or_offset_or_orders?(o)
70
+ if o.offset || has_group_by_and_having?(o) ||
71
+ has_join_sources?(o) && has_limit_or_offset_or_orders?(o)
71
72
  super
72
73
  else
73
74
  o
@@ -245,6 +245,13 @@ module Arel # :nodoc: all
245
245
  collector << ")"
246
246
  end
247
247
 
248
+ def visit_Arel_Nodes_Filter(o, collector)
249
+ visit o.left, collector
250
+ collector << " FILTER (WHERE "
251
+ visit o.right, collector
252
+ collector << ")"
253
+ end
254
+
248
255
  def visit_Arel_Nodes_Rows(o, collector)
249
256
  if o.expr
250
257
  collector << "ROWS "
@@ -834,6 +841,10 @@ module Arel # :nodoc: all
834
841
  o.limit || o.offset || !o.orders.empty?
835
842
  end
836
843
 
844
+ def has_group_by_and_having?(o)
845
+ !o.groups.empty? && !o.havings.empty?
846
+ end
847
+
837
848
  # The default strategy for an UPDATE with joins is to use a subquery. This doesn't work
838
849
  # on MySQL (even when aliasing the tables), but MySQL allows using JOIN directly in
839
850
  # an UPDATE statement, so in the MySQL visitor we redefine this to do that.
@@ -845,6 +856,8 @@ module Arel # :nodoc: all
845
856
  stmt.orders = []
846
857
  stmt.wheres = [Nodes::In.new(o.key, [build_subselect(o.key, o)])]
847
858
  stmt.relation = o.relation.left if has_join_sources?(o)
859
+ stmt.groups = o.groups unless o.groups.empty?
860
+ stmt.havings = o.havings unless o.havings.empty?
848
861
  stmt
849
862
  else
850
863
  o
@@ -859,6 +872,8 @@ module Arel # :nodoc: all
859
872
  core.froms = o.relation
860
873
  core.wheres = o.wheres
861
874
  core.projections = [key]
875
+ core.groups = o.groups unless o.groups.empty?
876
+ core.havings = o.havings unless o.havings.empty?
862
877
  stmt.limit = o.limit
863
878
  stmt.offset = o.offset
864
879
  stmt.orders = o.orders
data/lib/arel.rb CHANGED
@@ -7,6 +7,7 @@ require "arel/factory_methods"
7
7
 
8
8
  require "arel/expressions"
9
9
  require "arel/predications"
10
+ require "arel/filter_predications"
10
11
  require "arel/window_predications"
11
12
  require "arel/math"
12
13
  require "arel/alias_predication"
@@ -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