activerecord-updateinbulk 0.2.1 → 0.3.1
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.
- checksums.yaml +4 -4
- data/.yardopts +8 -0
- data/README.md +2 -9
- data/lib/activerecord-updateinbulk/adapters/abstract_adapter.rb +7 -2
- data/lib/activerecord-updateinbulk/adapters/abstract_mysql_adapter.rb +1 -1
- data/lib/activerecord-updateinbulk/adapters/postgresql_adapter.rb +1 -1
- data/lib/activerecord-updateinbulk/adapters/sqlite3_adapter.rb +1 -1
- data/lib/activerecord-updateinbulk/arel/nodes/values_table.rb +10 -5
- data/lib/activerecord-updateinbulk/arel/select_manager.rb +1 -1
- data/lib/activerecord-updateinbulk/arel/visitors/to_sql.rb +1 -1
- data/lib/activerecord-updateinbulk/base.rb +4 -4
- data/lib/activerecord-updateinbulk/builder.rb +24 -4
- data/lib/activerecord-updateinbulk/querying.rb +1 -1
- data/lib/activerecord-updateinbulk/railtie.rb +6 -7
- data/lib/activerecord-updateinbulk/relation.rb +16 -5
- data/lib/activerecord-updateinbulk/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c5130d637025d39db5e0eab8ecb59b22252decd67a56a089c7e1a749ac70385f
|
|
4
|
+
data.tar.gz: 44dbf800efb3d5b0e4d405864cf1ed0e2c3e87d461c10a72cf03513aa6878c5e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 02f007dd0c1c2a9f46f48a414bbe352598c0b7a36793f71070e63e7160c4decc01db790c727f90882a40fc302a7b486608548908b8c4ad996d1b858ff998220f
|
|
7
|
+
data.tar.gz: a54913d72fe2bed929e7676e2cf09d39bd589519529eb7576f1833108f77984072645829b453333968a2d4eca90f455493260970ee8e937929e0a00ed46fdc4f
|
data/.yardopts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
--quiet
|
|
2
|
+
lib/activerecord-updateinbulk/relation.rb
|
|
3
|
+
lib/activerecord-updateinbulk/adapters/abstract_adapter.rb
|
|
4
|
+
lib/activerecord-updateinbulk/arel/nodes/values_table.rb
|
|
5
|
+
lib/activerecord-updateinbulk/railtie.rb
|
|
6
|
+
lib/activerecord-updateinbulk/version.rb
|
|
7
|
+
-
|
|
8
|
+
README.md
|
data/README.md
CHANGED
|
@@ -61,17 +61,10 @@ Order.joins(:items).where(items: { status: :shipped }).update_in_bulk({
|
|
|
61
61
|
|
|
62
62
|
### Rails configuration
|
|
63
63
|
|
|
64
|
-
Railtie options are available at `config.active_record_update_in_bulk`:
|
|
64
|
+
Railtie initializer options are available at `config.active_record_update_in_bulk`:
|
|
65
65
|
|
|
66
66
|
- `values_table_alias` (`String`, optional): alias used for generated VALUES tables (default `"t"`).
|
|
67
|
-
- `ignore_scope_order` (`Boolean`, default `true`): when true, ORDER BY scopes are ignored by `update_in_bulk
|
|
68
|
-
|
|
69
|
-
Example initializer:
|
|
70
|
-
|
|
71
|
-
```ruby
|
|
72
|
-
Rails.application.config.active_record_update_in_bulk.values_table_alias = "vals"
|
|
73
|
-
Rails.application.config.active_record_update_in_bulk.ignore_scope_order = true
|
|
74
|
-
```
|
|
67
|
+
- `ignore_scope_order` (`Boolean`, default `true`): when true, ORDER BY scopes are ignored by `update_in_bulk`.
|
|
75
68
|
|
|
76
69
|
### Record timestamps
|
|
77
70
|
|
|
@@ -21,7 +21,7 @@ module ActiveRecord::UpdateInBulk
|
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
# Whether VALUES table serialization must always include explicit column
|
|
24
|
-
# aliases
|
|
24
|
+
# aliases because there are no default names. This is a mariadb quirk.
|
|
25
25
|
def values_table_requires_aliasing?
|
|
26
26
|
false
|
|
27
27
|
end
|
|
@@ -44,10 +44,15 @@ module ActiveRecord::UpdateInBulk
|
|
|
44
44
|
# possibly modified in place.
|
|
45
45
|
#
|
|
46
46
|
# The default implementation does no explicit type casting.
|
|
47
|
-
def typecast_values_table(values_table,
|
|
47
|
+
def typecast_values_table(values_table, columns)
|
|
48
48
|
values_table
|
|
49
49
|
end
|
|
50
50
|
end
|
|
51
51
|
end
|
|
52
52
|
|
|
53
53
|
ActiveRecord::ConnectionAdapters::AbstractAdapter.include(ActiveRecord::UpdateInBulk::AbstractAdapter)
|
|
54
|
+
|
|
55
|
+
# @!parse
|
|
56
|
+
# class ActiveRecord::ConnectionAdapters::AbstractAdapter
|
|
57
|
+
# include ActiveRecord::UpdateInBulk::AbstractAdapter
|
|
58
|
+
# end
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
require "active_record/connection_adapters/abstract_mysql_adapter"
|
|
4
4
|
|
|
5
5
|
module ActiveRecord::UpdateInBulk
|
|
6
|
-
module AbstractMysqlAdapter
|
|
6
|
+
module AbstractMysqlAdapter # :nodoc: all
|
|
7
7
|
def supports_values_tables?
|
|
8
8
|
mariadb? ? database_version >= "10.3.3" : database_version >= "8.0.19"
|
|
9
9
|
end
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Arel::Nodes
|
|
4
|
-
# Represents
|
|
5
|
-
# Mirrors Arel::Table behavior by requiring a name at construction time.
|
|
6
|
-
# Column names are also required because adapter defaults vary;
|
|
7
|
-
#
|
|
4
|
+
# Represents the +VALUES+ table constructor as an Arel node.
|
|
5
|
+
# Mirrors +Arel::Table+ behavior by requiring a name at construction time.
|
|
6
|
+
# Column names are also required because adapter defaults vary; prefer using the
|
|
7
|
+
# default names from <tt>connection.values_table_default_column_names(width)</tt>
|
|
8
|
+
# to keep the generated query simple.
|
|
9
|
+
#
|
|
10
|
+
# This is a private class that may be used by typecasting logic in custom adapters.
|
|
8
11
|
#
|
|
9
12
|
class ValuesTable < Arel::Nodes::Node
|
|
10
13
|
attr_reader :name, :width, :rows, :columns
|
|
@@ -35,7 +38,9 @@ module Arel::Nodes
|
|
|
35
38
|
Arel::Nodes::TableAlias.new(grouping(self), table)
|
|
36
39
|
end
|
|
37
40
|
|
|
38
|
-
|
|
41
|
+
def to_cte
|
|
42
|
+
self.alias.to_cte
|
|
43
|
+
end
|
|
39
44
|
|
|
40
45
|
def hash
|
|
41
46
|
[@name, @rows, @columns].hash
|
|
@@ -19,15 +19,15 @@ module ActiveRecord
|
|
|
19
19
|
require_adapter connection_pool.db_config.adapter
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
-
def self.register_formula(name, &formula)
|
|
22
|
+
def self.register_formula(name, &formula) # :nodoc:
|
|
23
23
|
Builder.register_formula(name, &formula)
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
-
def self.unregister_formula(name)
|
|
26
|
+
def self.unregister_formula(name) # :nodoc:
|
|
27
27
|
Builder.unregister_formula(name)
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
-
def self.registered_formula?(name)
|
|
30
|
+
def self.registered_formula?(name) # :nodoc:
|
|
31
31
|
Builder.registered_formula?(name)
|
|
32
32
|
end
|
|
33
33
|
end
|
|
@@ -43,7 +43,7 @@ require "activerecord-updateinbulk/querying"
|
|
|
43
43
|
require "activerecord-updateinbulk/adapters/abstract_adapter"
|
|
44
44
|
|
|
45
45
|
module ActiveRecord::UpdateInBulk
|
|
46
|
-
module ConnectionHandler # :nodoc:
|
|
46
|
+
module ConnectionHandler # :nodoc: all
|
|
47
47
|
def establish_connection(*args, **kwargs, &block) # :nodoc:
|
|
48
48
|
pool = super(*args, **kwargs, &block)
|
|
49
49
|
ActiveRecord::UpdateInBulk.load_from_connection_pool pool
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
require "active_support/core_ext/enumerable"
|
|
4
4
|
|
|
5
5
|
module ActiveRecord::UpdateInBulk
|
|
6
|
-
class Builder # :nodoc:
|
|
6
|
+
class Builder # :nodoc: all
|
|
7
7
|
SAFE_COMPARISON_TYPES = [:boolean, :string, :text, :integer, :float, :decimal].freeze
|
|
8
8
|
|
|
9
9
|
class << self
|
|
@@ -138,9 +138,11 @@ module ActiveRecord::UpdateInBulk
|
|
|
138
138
|
@conditions = conditions
|
|
139
139
|
@assigns = assigns
|
|
140
140
|
@formulas = normalize_formulas(formulas)
|
|
141
|
+
@auto_locking_column = nil
|
|
141
142
|
|
|
142
143
|
resolve_attribute_aliases!
|
|
143
144
|
resolve_read_and_write_keys!
|
|
145
|
+
apply_optimistic_locking!
|
|
144
146
|
verify_read_and_write_keys!
|
|
145
147
|
serialize_values!
|
|
146
148
|
detect_constant_columns! unless simple_update?
|
|
@@ -222,7 +224,7 @@ module ActiveRecord::UpdateInBulk
|
|
|
222
224
|
|
|
223
225
|
def build_values_table_rows
|
|
224
226
|
bitmask_keys = Set.new
|
|
225
|
-
non_constant_write_keys = write_keys
|
|
227
|
+
non_constant_write_keys = write_keys.reject { |key| constant_assigns.key?(key) }
|
|
226
228
|
|
|
227
229
|
rows = @conditions.map.with_index do |row_conditions, row_index|
|
|
228
230
|
row_assigns = @assigns[row_index]
|
|
@@ -264,6 +266,10 @@ module ActiveRecord::UpdateInBulk
|
|
|
264
266
|
else
|
|
265
267
|
build_simple_assignments(table)
|
|
266
268
|
end
|
|
269
|
+
if @auto_locking_column
|
|
270
|
+
lock = table[@auto_locking_column]
|
|
271
|
+
set_assignments << [lock, table.coalesce(lock, 0) + 1]
|
|
272
|
+
end
|
|
267
273
|
|
|
268
274
|
if timestamp_keys.any?
|
|
269
275
|
# Timestamp assignments precede data assignments to increase the
|
|
@@ -288,7 +294,8 @@ module ActiveRecord::UpdateInBulk
|
|
|
288
294
|
if constant_assigns.key?(key)
|
|
289
295
|
rhs = Arel::Nodes::Quoted.new(constant_assigns[key])
|
|
290
296
|
else
|
|
291
|
-
|
|
297
|
+
val = values_table[column]
|
|
298
|
+
rhs = val
|
|
292
299
|
column += 1
|
|
293
300
|
rhs = self.class.apply_formula(formula, lhs, rhs, model) if formula
|
|
294
301
|
end
|
|
@@ -296,7 +303,11 @@ module ActiveRecord::UpdateInBulk
|
|
|
296
303
|
if function = bitmask_functions[key]
|
|
297
304
|
rhs = Arel::Nodes::Case.new(function).when("1").then(rhs).else(lhs)
|
|
298
305
|
elsif optional_keys.include?(key)
|
|
299
|
-
|
|
306
|
+
if formula
|
|
307
|
+
rhs = Arel::Nodes::Case.new.when(val.eq(nil)).then(lhs).else(rhs)
|
|
308
|
+
else
|
|
309
|
+
rhs = table.coalesce(rhs, lhs)
|
|
310
|
+
end
|
|
300
311
|
end
|
|
301
312
|
[lhs, rhs]
|
|
302
313
|
end
|
|
@@ -359,6 +370,15 @@ module ActiveRecord::UpdateInBulk
|
|
|
359
370
|
normalized
|
|
360
371
|
end
|
|
361
372
|
|
|
373
|
+
def apply_optimistic_locking!
|
|
374
|
+
return unless model.locking_enabled?
|
|
375
|
+
|
|
376
|
+
locking_column = model.locking_column
|
|
377
|
+
return if write_keys.include?(locking_column)
|
|
378
|
+
|
|
379
|
+
@auto_locking_column = locking_column
|
|
380
|
+
end
|
|
381
|
+
|
|
362
382
|
def resolve_attribute_aliases!
|
|
363
383
|
return if model.attribute_aliases.empty?
|
|
364
384
|
|
|
@@ -15,16 +15,15 @@ module ActiveRecord
|
|
|
15
15
|
# Rails.application.config.active_record_update_in_bulk.ignore_scope_order = true
|
|
16
16
|
#
|
|
17
17
|
# [config.active_record_update_in_bulk.values_table_alias]
|
|
18
|
-
#
|
|
19
|
-
# Defaults to <tt>"t"</tt>.
|
|
18
|
+
# Table alias for the generated +VALUES+ table. Defaults to <tt>"t"</tt>.
|
|
20
19
|
#
|
|
21
20
|
# [config.active_record_update_in_bulk.ignore_scope_order]
|
|
22
|
-
# Whether <tt>Relation#update_in_bulk</tt> should ignore any ORDER BY scope
|
|
23
|
-
# on the input relation. Necessary for invoking the method on scope
|
|
24
|
-
# associations, or models with a default scope that includes an order.
|
|
21
|
+
# Whether <tt>Relation#update_in_bulk</tt> should ignore any +ORDER BY+ scope
|
|
22
|
+
# on the input relation. Necessary for invoking the method casually on scope
|
|
23
|
+
# ordered associations, or models with a default scope that includes an order.
|
|
25
24
|
#
|
|
26
|
-
# * <tt>true</tt> (default): ORDER BY
|
|
27
|
-
# * <tt>false</tt>: ordered relations raise NotImplementedError
|
|
25
|
+
# * <tt>true</tt> (default): +ORDER BY+ clause is stripped.
|
|
26
|
+
# * <tt>false</tt>: ordered relations raise +NotImplementedError+.
|
|
28
27
|
class Railtie < Rails::Railtie
|
|
29
28
|
config.active_record_update_in_bulk = ActiveSupport::OrderedOptions.new
|
|
30
29
|
config.active_record_update_in_bulk.ignore_scope_order = true
|
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
module ActiveRecord::UpdateInBulk
|
|
4
4
|
module Relation
|
|
5
5
|
# Updates multiple groups of records in the current relation using a single
|
|
6
|
-
# SQL UPDATE statement. This does not instantiate models and does not
|
|
6
|
+
# SQL +UPDATE+ statement. This does not instantiate models and does not
|
|
7
7
|
# trigger Active Record callbacks or validations. However, values passed
|
|
8
8
|
# through still use Active Record's normal type casting and serialization.
|
|
9
9
|
# Returns the number of rows affected.
|
|
10
10
|
#
|
|
11
|
-
# Three equivalent input formats are supported:
|
|
11
|
+
# Three equivalent input formats are supported for convenience:
|
|
12
12
|
#
|
|
13
|
-
#
|
|
13
|
+
# <b>Indexed format</b> — a hash mapping primary keys to attribute updates:
|
|
14
14
|
#
|
|
15
15
|
# Book.update_in_bulk({
|
|
16
16
|
# 1 => { title: "Agile", price: 10.0 },
|
|
@@ -24,7 +24,7 @@ module ActiveRecord::UpdateInBulk
|
|
|
24
24
|
# ["AA100", "12B"] => { passenger: "Bob" }
|
|
25
25
|
# })
|
|
26
26
|
#
|
|
27
|
-
#
|
|
27
|
+
# <b>Paired format</b> — an array of <tt>[conditions, assigns]</tt> pairs.
|
|
28
28
|
# Conditions do not need to be primary keys; they may reference any columns in
|
|
29
29
|
# the target table. All pairs must specify the same set of condition
|
|
30
30
|
# columns:
|
|
@@ -34,7 +34,7 @@ module ActiveRecord::UpdateInBulk
|
|
|
34
34
|
# [{ department: "Engineering" }, { bonus: 500 }]
|
|
35
35
|
# ])
|
|
36
36
|
#
|
|
37
|
-
#
|
|
37
|
+
# <b>Separated format</b> — parallel arrays of conditions and assigns:
|
|
38
38
|
#
|
|
39
39
|
# Employee.update_in_bulk(
|
|
40
40
|
# [1, 2, { id: 3 }],
|
|
@@ -90,6 +90,12 @@ module ActiveRecord::UpdateInBulk
|
|
|
90
90
|
# 2 => { department: "Sales" }
|
|
91
91
|
# })
|
|
92
92
|
#
|
|
93
|
+
# ==== Restrictions
|
|
94
|
+
#
|
|
95
|
+
# This method does not support relations with <tt>offset</tt>, <tt>limit</tt>,
|
|
96
|
+
# <tt>group</tt>, or <tt>having</tt> clauses. An <tt>order</tt> clause is supported
|
|
97
|
+
# by default <b>by being stripped</b> to keep the method usable on ordered associations.
|
|
98
|
+
#
|
|
93
99
|
def update_in_bulk(updates, values = nil, record_timestamps: nil, formulas: nil)
|
|
94
100
|
unless limit_value.nil? && offset_value.nil? && group_values.empty? && having_clause.empty?
|
|
95
101
|
raise NotImplementedError, "No support to update relations with offset, limit, group, or having clauses"
|
|
@@ -137,3 +143,8 @@ module ActiveRecord::UpdateInBulk
|
|
|
137
143
|
end
|
|
138
144
|
|
|
139
145
|
ActiveRecord::Relation.prepend(ActiveRecord::UpdateInBulk::Relation)
|
|
146
|
+
|
|
147
|
+
# @!parse
|
|
148
|
+
# class ActiveRecord::Relation
|
|
149
|
+
# include ActiveRecord::UpdateInBulk::Relation
|
|
150
|
+
# end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: activerecord-updateinbulk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Bruno Carvalho
|
|
@@ -31,6 +31,7 @@ executables: []
|
|
|
31
31
|
extensions: []
|
|
32
32
|
extra_rdoc_files: []
|
|
33
33
|
files:
|
|
34
|
+
- ".yardopts"
|
|
34
35
|
- LICENSE
|
|
35
36
|
- README.md
|
|
36
37
|
- lib/activerecord-updateinbulk.rb
|