sequel 5.72.0 → 5.73.0
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/CHANGELOG +18 -0
- data/README.rdoc +1 -1
- data/doc/release_notes/5.73.0.txt +66 -0
- data/lib/sequel/adapters/ibmdb.rb +1 -1
- data/lib/sequel/adapters/jdbc/db2.rb +4 -0
- data/lib/sequel/adapters/shared/db2.rb +12 -0
- data/lib/sequel/adapters/shared/postgres.rb +3 -1
- data/lib/sequel/database/schema_methods.rb +3 -2
- data/lib/sequel/database/transactions.rb +6 -0
- data/lib/sequel/dataset/actions.rb +8 -6
- data/lib/sequel/extensions/migration.rb +1 -5
- data/lib/sequel/model/base.rb +20 -10
- data/lib/sequel/model/exceptions.rb +15 -3
- data/lib/sequel/plugins/column_encryption.rb +26 -5
- data/lib/sequel/plugins/paged_operations.rb +181 -0
- data/lib/sequel/version.rb +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bf3b8749cd91ac285e66359ae16adf9605c13e673a0cf3f7251527573d71fed6
|
4
|
+
data.tar.gz: 817f6980be08b29368eb0f08631d64ae48a4281aa1b2974b589441fea50e2dac
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6da7c5e7a7c74c4767c55d126adc3743875a5dd0822ee780b22b1ca3c3791cc10d08cf74c984776dbd9fbbcf61d9246bcef4c81e64204e5d72aa59545ef9a2d9
|
7
|
+
data.tar.gz: 7ac12f6689d846345fed77e23665679ae4886d60e4d25d4e201a3cfdac4f231882af0aaf3e615bd9981d09c2e9537c1e287fad614f81760f2d897b87402b38b1
|
data/CHANGELOG
CHANGED
@@ -1,3 +1,21 @@
|
|
1
|
+
=== 5.73.0 (2023-10-01)
|
2
|
+
|
3
|
+
* Handle disconnect errors in ibmdb and jdbc/db2 adapters (jeremyevans) (#2083)
|
4
|
+
|
5
|
+
* Support skipping transactions in Dataset#{import,paged_each} using :skip_transaction option (jeremyevans)
|
6
|
+
|
7
|
+
* Add Database#transaction :skip_transaction option to skip creating a transaction or savepoint (jeremyevans)
|
8
|
+
|
9
|
+
* Stop using a transaction for a single query if calling Dataset#import with a dataset (jeremyevans)
|
10
|
+
|
11
|
+
* Add paged_operations plugin for paged deletes and updates and other custom operations (jeremyevans) (#2080)
|
12
|
+
|
13
|
+
* Support to_tsquery: :websearch option to Dataset#full_text_search on PostgreSQL 11+ (jeremyevans) (#2075)
|
14
|
+
|
15
|
+
* Add MassAssignmentRestriction#model and #column for getting the model instance and related column for mass assignment errors (artofhuman, jeremyevans) (#2079)
|
16
|
+
|
17
|
+
* Stop using base64 library in column_encryption plugin (jeremyevans)
|
18
|
+
|
1
19
|
=== 5.72.0 (2023-09-01)
|
2
20
|
|
3
21
|
* Sort caches before marshalling when using schema_caching, index_caching, static_cache_cache, and pg_auto_constraint_validations (jeremyevans)
|
data/README.rdoc
CHANGED
@@ -927,7 +927,7 @@ Sequel fully supports the currently supported versions of Ruby (MRI) and JRuby.
|
|
927
927
|
support unsupported versions of Ruby or JRuby, but such support may be dropped in any
|
928
928
|
minor version if keeping it becomes a support issue. The minimum Ruby version
|
929
929
|
required to run the current version of Sequel is 1.9.2, and the minimum JRuby version is
|
930
|
-
9.
|
930
|
+
9.2.0.0 (due to the bigdecimal dependency).
|
931
931
|
|
932
932
|
== Maintainer
|
933
933
|
|
@@ -0,0 +1,66 @@
|
|
1
|
+
= New Features
|
2
|
+
|
3
|
+
* A paged_operations plugin has been added, which adds support for
|
4
|
+
paged_datasets, paged_update, and paged_delete dataset methods.
|
5
|
+
This methods are designed to be used on large datasets, to split
|
6
|
+
a large query into separate smaller queries, to avoid locking the
|
7
|
+
related database table for a long period of time.
|
8
|
+
paged_update and paged_delete operate the same as update and delete,
|
9
|
+
returning the number of rows updated or deleted. paged_datasets yields
|
10
|
+
one or more datasets representing subsets of the receiver, with the
|
11
|
+
union of all of those datasets comprising all records in the receiver:
|
12
|
+
|
13
|
+
Album.plugin :paged_operations
|
14
|
+
|
15
|
+
Album.where{name > 'M'}.paged_datasets{|ds| puts ds.sql}
|
16
|
+
# Runs: SELECT id FROM albums WHERE (name <= 'M') ORDER BY id LIMIT 1 OFFSET 1000
|
17
|
+
# Prints: SELECT * FROM albums WHERE ((name <= 'M') AND ("id" < 1002))
|
18
|
+
# Runs: SELECT id FROM albums WHERE ((name <= 'M') AND (id >= 1002)) ORDER BY id LIMIT 1 OFFSET 1000
|
19
|
+
# Prints: SELECT * FROM albums WHERE ((name <= 'M') AND ("id" < 2002) AND (id >= 1002))
|
20
|
+
# ...
|
21
|
+
# Runs: SELECT id FROM albums WHERE ((name <= 'M') AND (id >= 10002)) ORDER BY id LIMIT 1 OFFSET 1000
|
22
|
+
# Prints: SELECT * FROM albums WHERE ((name <= 'M') AND (id >= 10002))
|
23
|
+
|
24
|
+
Album.where{name <= 'M'}.paged_update(:updated_at=>Sequel::CURRENT_TIMESTAMP)
|
25
|
+
# SELECT id FROM albums WHERE (name <= 'M') ORDER BY id LIMIT 1 OFFSET 1000
|
26
|
+
# UPDATE albums SET updated_at = CURRENT_TIMESTAMP WHERE ((name <= 'M') AND ("id" < 1002))
|
27
|
+
# SELECT id FROM albums WHERE ((name <= 'M') AND (id >= 1002)) ORDER BY id LIMIT 1 OFFSET 1000
|
28
|
+
# UPDATE albums SET updated_at = CURRENT_TIMESTAMP WHERE ((name <= 'M') AND ("id" < 2002) AND (id >= 1002))
|
29
|
+
# ...
|
30
|
+
# SELECT id FROM albums WHERE ((name <= 'M') AND (id >= 10002)) ORDER BY id LIMIT 1 OFFSET 1000
|
31
|
+
# UPDATE albums SET updated_at = CURRENT_TIMESTAMP WHERE ((name <= 'M') AND (id >= 10002))
|
32
|
+
|
33
|
+
Album.where{name > 'M'}.paged_delete
|
34
|
+
# SELECT id FROM albums WHERE (name > 'M') ORDER BY id LIMIT 1 OFFSET 1000
|
35
|
+
# DELETE FROM albums WHERE ((name > 'M') AND (id < 1002))
|
36
|
+
# SELECT id FROM albums WHERE (name > 'M') ORDER BY id LIMIT 1 OFFSET 1000
|
37
|
+
# DELETE FROM albums WHERE ((name > 'M') AND (id < 2002))
|
38
|
+
# ...
|
39
|
+
# SELECT id FROM albums WHERE (name > 'M') ORDER BY id LIMIT 1 OFFSET 1000
|
40
|
+
# DELETE FROM albums WHERE (name > 'M')
|
41
|
+
|
42
|
+
* A Dataset#transaction :skip_transaction option is now support to
|
43
|
+
checkout a connection from the pool without opening a transaction. This
|
44
|
+
makes it easier to handle cases where a transaction may or not be used
|
45
|
+
based on configuration/options. Dataset#import and Dataset#paged_each
|
46
|
+
now both support the :skip_transaction option to skip transactions.
|
47
|
+
|
48
|
+
* Dataset#full_text_search now supports the to_tsquery: :websearch option
|
49
|
+
on PostgreSQL 11+, to use the websearch_to_tsquery database function.
|
50
|
+
|
51
|
+
* The Sequel::MassAssignmentRestriction exception now supports model
|
52
|
+
and column methods to get provide additional information about the
|
53
|
+
exception. Additionally, the exception message now includes information
|
54
|
+
about the model class.
|
55
|
+
|
56
|
+
= Other Improvements
|
57
|
+
|
58
|
+
* The ibmdb and jdbc/db2 adapter now both handle disconnect errors
|
59
|
+
correctly, removing the related connection from the pool.
|
60
|
+
|
61
|
+
* Dataset#import no longer uses an explicit transaction if given a dataset
|
62
|
+
value, as in that case, only a single query is used.
|
63
|
+
|
64
|
+
* The column_encryption plugin no longer uses the base64 library. The
|
65
|
+
base64 library is moving from the standard library to a bundled gem
|
66
|
+
in Ruby 3.4, and this avoids having a dependency on it.
|
@@ -215,6 +215,18 @@ module Sequel
|
|
215
215
|
DATABASE_ERROR_REGEXPS
|
216
216
|
end
|
217
217
|
|
218
|
+
DISCONNECT_SQL_STATES = %w'40003 08001 08003'.freeze
|
219
|
+
def disconnect_error?(exception, opts)
|
220
|
+
sqlstate = database_exception_sqlstate(exception, opts)
|
221
|
+
|
222
|
+
case sqlstate
|
223
|
+
when *DISCONNECT_SQL_STATES
|
224
|
+
true
|
225
|
+
else
|
226
|
+
super
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
218
230
|
# DB2 has issues with quoted identifiers, so
|
219
231
|
# turn off database quoting by default.
|
220
232
|
def quote_identifiers_default
|
@@ -1798,7 +1798,7 @@ module Sequel
|
|
1798
1798
|
# :phrase :: Similar to :plain, but also adding an ILIKE filter to ensure that
|
1799
1799
|
# returned rows also include the exact phrase used.
|
1800
1800
|
# :rank :: Set to true to order by the rank, so that closer matches are returned first.
|
1801
|
-
# :to_tsquery :: Can be set to :plain or :
|
1801
|
+
# :to_tsquery :: Can be set to :plain, :phrase, or :websearch to specify the function to use to
|
1802
1802
|
# convert the terms to a ts_query.
|
1803
1803
|
# :tsquery :: Specifies the terms argument is already a valid SQL expression returning a
|
1804
1804
|
# tsquery, and can be used directly in the query.
|
@@ -1818,6 +1818,8 @@ module Sequel
|
|
1818
1818
|
query_func = case to_tsquery = opts[:to_tsquery]
|
1819
1819
|
when :phrase, :plain
|
1820
1820
|
:"#{to_tsquery}to_tsquery"
|
1821
|
+
when :websearch
|
1822
|
+
:"websearch_to_tsquery"
|
1821
1823
|
else
|
1822
1824
|
(opts[:phrase] || opts[:plain]) ? :plainto_tsquery : :to_tsquery
|
1823
1825
|
end
|
@@ -712,8 +712,9 @@ module Sequel
|
|
712
712
|
e = options[:ignore_index_errors] || options[:if_not_exists]
|
713
713
|
generator.indexes.each do |index|
|
714
714
|
begin
|
715
|
-
|
716
|
-
|
715
|
+
transaction(:savepoint=>:only, :skip_transaction=>supports_transactional_ddl? == false) do
|
716
|
+
index_sql_list(name, [index]).each{|sql| execute_ddl(sql)}
|
717
|
+
end
|
717
718
|
rescue Error
|
718
719
|
raise unless e
|
719
720
|
end
|
@@ -166,6 +166,8 @@ module Sequel
|
|
166
166
|
# uses :auto_savepoint, you can set this to false to not use a savepoint.
|
167
167
|
# If the value given for this option is :only, it will only create a
|
168
168
|
# savepoint if it is inside a transaction.
|
169
|
+
# :skip_transaction :: If set, do not actually open a transaction or savepoint,
|
170
|
+
# just checkout a connection and yield it.
|
169
171
|
#
|
170
172
|
# PostgreSQL specific options:
|
171
173
|
#
|
@@ -193,6 +195,10 @@ module Sequel
|
|
193
195
|
end
|
194
196
|
else
|
195
197
|
synchronize(opts[:server]) do |conn|
|
198
|
+
if opts[:skip_transaction]
|
199
|
+
return yield(conn)
|
200
|
+
end
|
201
|
+
|
196
202
|
if opts[:savepoint] == :only
|
197
203
|
if supports_savepoints?
|
198
204
|
if _trans(conn)
|
@@ -356,9 +356,11 @@ module Sequel
|
|
356
356
|
# This does not have an effect if +values+ is a Dataset.
|
357
357
|
# :server :: Set the server/shard to use for the transaction and insert
|
358
358
|
# queries.
|
359
|
+
# :skip_transaction :: Do not use a transaction even when using multiple
|
360
|
+
# INSERT queries.
|
359
361
|
# :slice :: Same as :commit_every, :commit_every takes precedence.
|
360
362
|
def import(columns, values, opts=OPTS)
|
361
|
-
return
|
363
|
+
return insert(columns, values) if values.is_a?(Dataset)
|
362
364
|
|
363
365
|
return if values.empty?
|
364
366
|
raise(Error, 'Using Sequel::Dataset#import with an empty column array is not allowed') if columns.empty?
|
@@ -588,6 +590,8 @@ module Sequel
|
|
588
590
|
# if your ORDER BY expressions are not simple columns, if they contain
|
589
591
|
# qualified identifiers that would be ambiguous unqualified, if they contain
|
590
592
|
# any identifiers that are aliased in SELECT, and potentially other cases.
|
593
|
+
# :skip_transaction :: Do not use a transaction. This can be useful if you want to prevent
|
594
|
+
# a lock on the database table, at the expense of consistency.
|
591
595
|
#
|
592
596
|
# Examples:
|
593
597
|
#
|
@@ -1111,11 +1115,9 @@ module Sequel
|
|
1111
1115
|
# are provided. When only a single value or statement is provided, then yield
|
1112
1116
|
# without using a transaction.
|
1113
1117
|
def _import_transaction(values, trans_opts, &block)
|
1114
|
-
|
1115
|
-
|
1116
|
-
|
1117
|
-
yield
|
1118
|
-
end
|
1118
|
+
# OK to mutate trans_opts as it is generated by _import
|
1119
|
+
trans_opts[:skip_transaction] = true if values.length <= 1
|
1120
|
+
@db.transaction(trans_opts, &block)
|
1119
1121
|
end
|
1120
1122
|
|
1121
1123
|
# Internals of +select_hash+ and +select_hash_groups+
|
@@ -482,11 +482,7 @@ module Sequel
|
|
482
482
|
@use_transactions
|
483
483
|
end
|
484
484
|
|
485
|
-
|
486
|
-
db.transaction(&block)
|
487
|
-
else
|
488
|
-
yield
|
489
|
-
end
|
485
|
+
db.transaction(:skip_transaction=>use_trans == false, &block)
|
490
486
|
end
|
491
487
|
|
492
488
|
# Load the migration file, raising an exception if the file does not define
|
data/lib/sequel/model/base.rb
CHANGED
@@ -1945,8 +1945,10 @@ module Sequel
|
|
1945
1945
|
end
|
1946
1946
|
|
1947
1947
|
# If transactions should be used, wrap the yield in a transaction block.
|
1948
|
-
def checked_transaction(opts=OPTS)
|
1949
|
-
|
1948
|
+
def checked_transaction(opts=OPTS, &block)
|
1949
|
+
h = {:server=>this_server}.merge!(opts)
|
1950
|
+
h[:skip_transaction] = true unless use_transaction?(opts)
|
1951
|
+
db.transaction(h, &block)
|
1950
1952
|
end
|
1951
1953
|
|
1952
1954
|
# Change the value of the column to given value, recording the change.
|
@@ -2031,19 +2033,20 @@ module Sequel
|
|
2031
2033
|
meths = setter_methods(type)
|
2032
2034
|
strict = strict_param_setting
|
2033
2035
|
hash.each do |k,v|
|
2036
|
+
k = k.to_s
|
2034
2037
|
m = "#{k}="
|
2035
2038
|
if meths.include?(m)
|
2036
2039
|
set_column_value(m, v)
|
2037
2040
|
elsif strict
|
2038
2041
|
# Avoid using respond_to? or creating symbols from user input
|
2039
2042
|
if public_methods.map(&:to_s).include?(m)
|
2040
|
-
if Array(model.primary_key).map(&:to_s).member?(k
|
2041
|
-
raise MassAssignmentRestriction
|
2043
|
+
if Array(model.primary_key).map(&:to_s).member?(k) && model.restrict_primary_key?
|
2044
|
+
raise MassAssignmentRestriction.create("#{k} is a restricted primary key", self, k)
|
2042
2045
|
else
|
2043
|
-
raise MassAssignmentRestriction
|
2046
|
+
raise MassAssignmentRestriction.create("#{k} is a restricted column", self, k)
|
2044
2047
|
end
|
2045
2048
|
else
|
2046
|
-
raise MassAssignmentRestriction
|
2049
|
+
raise MassAssignmentRestriction.create("method #{m} doesn't exist", self, k)
|
2047
2050
|
end
|
2048
2051
|
end
|
2049
2052
|
end
|
@@ -2147,8 +2150,9 @@ module Sequel
|
|
2147
2150
|
# # DELETE FROM artists WHERE (id = 2)
|
2148
2151
|
# # ...
|
2149
2152
|
def destroy
|
2150
|
-
|
2151
|
-
|
2153
|
+
@db.transaction(:server=>opts[:server], :skip_transaction=>model.use_transactions == false) do
|
2154
|
+
all(&:destroy).length
|
2155
|
+
end
|
2152
2156
|
end
|
2153
2157
|
|
2154
2158
|
# If there is no order already defined on this dataset, order it by
|
@@ -2228,11 +2232,17 @@ module Sequel
|
|
2228
2232
|
|
2229
2233
|
private
|
2230
2234
|
|
2235
|
+
# Return the dataset ordered by the model's primary key. This should not
|
2236
|
+
# be used if the model does not have a primary key.
|
2237
|
+
def _force_primary_key_order
|
2238
|
+
cached_dataset(:_pk_order_ds){order(*model.primary_key)}
|
2239
|
+
end
|
2240
|
+
|
2231
2241
|
# If the dataset is not already ordered, and the model has a primary key,
|
2232
2242
|
# return a clone ordered by the primary key.
|
2233
2243
|
def _primary_key_order
|
2234
|
-
if @opts[:order].nil? && model &&
|
2235
|
-
|
2244
|
+
if @opts[:order].nil? && model && model.primary_key
|
2245
|
+
_force_primary_key_order
|
2236
2246
|
end
|
2237
2247
|
end
|
2238
2248
|
|
@@ -24,11 +24,23 @@ module Sequel
|
|
24
24
|
UndefinedAssociation = Class.new(Error)
|
25
25
|
).name
|
26
26
|
|
27
|
-
(
|
28
27
|
# Raised when a mass assignment method is called in strict mode with either a restricted column
|
29
28
|
# or a column without a setter method.
|
30
|
-
MassAssignmentRestriction
|
31
|
-
|
29
|
+
class MassAssignmentRestriction < Error
|
30
|
+
# The Sequel::Model object related to this exception.
|
31
|
+
attr_reader :model
|
32
|
+
|
33
|
+
# The column related to this exception, as a string.
|
34
|
+
attr_reader :column
|
35
|
+
|
36
|
+
# Create an instance of this class with the model and column set.
|
37
|
+
def self.create(msg, model, column)
|
38
|
+
e = new("#{msg} for class #{model.class.inspect}")
|
39
|
+
e.instance_variable_set(:@model, model)
|
40
|
+
e.instance_variable_set(:@column, column)
|
41
|
+
e
|
42
|
+
end
|
43
|
+
end
|
32
44
|
|
33
45
|
# Exception class raised when +raise_on_save_failure+ is set and validation fails
|
34
46
|
class ValidationFailed < Error
|
@@ -31,7 +31,6 @@ rescue RuntimeError, OpenSSL::Cipher::CipherError
|
|
31
31
|
# :nocov:
|
32
32
|
end
|
33
33
|
|
34
|
-
require 'base64'
|
35
34
|
require 'securerandom'
|
36
35
|
|
37
36
|
module Sequel
|
@@ -375,7 +374,7 @@ module Sequel
|
|
375
374
|
# Decrypt using any supported format and any available key.
|
376
375
|
def decrypt(data)
|
377
376
|
begin
|
378
|
-
data =
|
377
|
+
data = urlsafe_decode64(data)
|
379
378
|
rescue ArgumentError
|
380
379
|
raise Error, "Unable to decode encrypted column: invalid base64"
|
381
380
|
end
|
@@ -448,7 +447,7 @@ module Sequel
|
|
448
447
|
# The prefix string of columns for the given search type and the first configured encryption key.
|
449
448
|
# Used to find values that do not use this prefix in order to perform reencryption.
|
450
449
|
def current_key_prefix(search_type)
|
451
|
-
|
450
|
+
urlsafe_encode64("#{search_type.chr}\0#{@key_id.chr}")
|
452
451
|
end
|
453
452
|
|
454
453
|
# The prefix values to search for the given data (an array of strings), assuming the column uses
|
@@ -472,11 +471,33 @@ module Sequel
|
|
472
471
|
|
473
472
|
private
|
474
473
|
|
474
|
+
if RUBY_VERSION >= '2.4'
|
475
|
+
def decode64(str)
|
476
|
+
str.unpack1("m0")
|
477
|
+
end
|
478
|
+
# :nocov:
|
479
|
+
else
|
480
|
+
def decode64(str)
|
481
|
+
str.unpack("m0")[0]
|
482
|
+
end
|
483
|
+
# :nocov:
|
484
|
+
end
|
485
|
+
|
486
|
+
def urlsafe_encode64(bin)
|
487
|
+
str = [bin].pack("m0")
|
488
|
+
str.tr!("+/", "-_")
|
489
|
+
str
|
490
|
+
end
|
491
|
+
|
492
|
+
def urlsafe_decode64(str)
|
493
|
+
decode64(str.tr("-_", "+/"))
|
494
|
+
end
|
495
|
+
|
475
496
|
# An array of strings, one for each configured encryption key, to find encypted values matching
|
476
497
|
# the given data and search format.
|
477
498
|
def _search_prefixes(data, search_type)
|
478
499
|
@key_map.map do |key_id, (key, _)|
|
479
|
-
|
500
|
+
urlsafe_encode64(_search_prefix(data, search_type, key_id, key))
|
480
501
|
end
|
481
502
|
end
|
482
503
|
|
@@ -509,7 +530,7 @@ module Sequel
|
|
509
530
|
cipher_text << cipher.update(data) if data_size > 0
|
510
531
|
cipher_text << cipher.final
|
511
532
|
|
512
|
-
|
533
|
+
urlsafe_encode64("#{prefix}#{random_data}#{cipher_iv}#{cipher.auth_tag}#{cipher_text}")
|
513
534
|
end
|
514
535
|
end
|
515
536
|
|
@@ -0,0 +1,181 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
module Sequel
|
4
|
+
module Plugins
|
5
|
+
# The paged_operations plugin adds +paged_update+ and
|
6
|
+
# +paged_delete+ dataset methods. These behave similarly to
|
7
|
+
# the default +update+ and +delete+ dataset methods, except
|
8
|
+
# that the update or deletion is done in potentially multiple
|
9
|
+
# queries (by default, affecting 1000 rows per query).
|
10
|
+
# For a large table, this prevents the change from
|
11
|
+
# locking the table for a long period of time.
|
12
|
+
#
|
13
|
+
# Because the point of this is to prevent locking tables for
|
14
|
+
# long periods of time, the separate queries are not contained
|
15
|
+
# in a transaction, which means if a later query fails,
|
16
|
+
# earlier queries will still be committed. You could prevent
|
17
|
+
# this by using a transaction manually, but that defeats the
|
18
|
+
# purpose of using these methods.
|
19
|
+
#
|
20
|
+
# Examples:
|
21
|
+
#
|
22
|
+
# Album.where{name <= 'M'}.paged_update(updated_at: Sequel::CURRENT_TIMESTAMP)
|
23
|
+
# # SELECT id FROM albums WHERE (name <= 'M') ORDER BY id LIMIT 1 OFFSET 1000
|
24
|
+
# # UPDATE albums SET updated_at = CURRENT_TIMESTAMP WHERE ((name <= 'M') AND ("id" < 1002))
|
25
|
+
# # SELECT id FROM albums WHERE ((name <= 'M') AND (id >= 1002)) ORDER BY id LIMIT 1 OFFSET 1000
|
26
|
+
# # UPDATE albums SET updated_at = CURRENT_TIMESTAMP WHERE ((name <= 'M') AND ("id" < 2002) AND (id >= 1002))
|
27
|
+
# # ...
|
28
|
+
# # SELECT id FROM albums WHERE ((name <= 'M') AND (id >= 10002)) ORDER BY id LIMIT 1 OFFSET 1000
|
29
|
+
# # UPDATE albums SET updated_at = CURRENT_TIMESTAMP WHERE ((name <= 'M') AND (id >= 10002))
|
30
|
+
#
|
31
|
+
# Album.where{name > 'M'}.paged_delete
|
32
|
+
# # SELECT id FROM albums WHERE (name > 'M') ORDER BY id LIMIT 1 OFFSET 1000
|
33
|
+
# # DELETE FROM albums WHERE ((name > 'M') AND (id < 1002))
|
34
|
+
# # SELECT id FROM albums WHERE (name > 'M') ORDER BY id LIMIT 1 OFFSET 1000
|
35
|
+
# # DELETE FROM albums WHERE ((name > 'M') AND (id < 2002))
|
36
|
+
# # ...
|
37
|
+
# # SELECT id FROM albums WHERE (name > 'M') ORDER BY id LIMIT 1 OFFSET 1000
|
38
|
+
# # DELETE FROM albums WHERE (name > 'M')
|
39
|
+
#
|
40
|
+
# The plugin also adds a +paged_datasets+ method that will yield
|
41
|
+
# separate datasets limited in size that in total handle all
|
42
|
+
# rows in the receiver:
|
43
|
+
#
|
44
|
+
# Album.where{name > 'M'}.paged_datasets{|ds| puts ds.sql}
|
45
|
+
# # Runs: SELECT id FROM albums WHERE (name <= 'M') ORDER BY id LIMIT 1 OFFSET 1000
|
46
|
+
# # Prints: SELECT * FROM albums WHERE ((name <= 'M') AND ("id" < 1002))
|
47
|
+
# # Runs: SELECT id FROM albums WHERE ((name <= 'M') AND (id >= 1002)) ORDER BY id LIMIT 1 OFFSET 1000
|
48
|
+
# # Prints: SELECT * FROM albums WHERE ((name <= 'M') AND ("id" < 2002) AND (id >= 1002))
|
49
|
+
# # ...
|
50
|
+
# # Runs: SELECT id FROM albums WHERE ((name <= 'M') AND (id >= 10002)) ORDER BY id LIMIT 1 OFFSET 1000
|
51
|
+
# # Prints: SELECT * FROM albums WHERE ((name <= 'M') AND (id >= 10002))
|
52
|
+
#
|
53
|
+
# To set the number of rows per page, pass a :rows_per_page option:
|
54
|
+
#
|
55
|
+
# Album.where{name <= 'M'}.paged_update({x: Sequel[:x] + 1}, rows_per_page: 4)
|
56
|
+
# # SELECT id FROM albums WHERE (name <= 'M') ORDER BY id LIMIT 1 OFFSET 4
|
57
|
+
# # UPDATE albums SET x = x + 1 WHERE ((name <= 'M') AND ("id" < 5))
|
58
|
+
# # SELECT id FROM albums WHERE ((name <= 'M') AND (id >= 5)) ORDER BY id LIMIT 1 OFFSET 4
|
59
|
+
# # UPDATE albums SET x = x + 1 WHERE ((name <= 'M') AND ("id" < 9) AND (id >= 5))
|
60
|
+
# # ...
|
61
|
+
# # SELECT id FROM albums WHERE ((name <= 'M') AND (id >= 12345)) ORDER BY id LIMIT 1 OFFSET 4
|
62
|
+
# # UPDATE albums SET x = x + 1 WHERE ((name <= 'M') AND (id >= 12345))
|
63
|
+
#
|
64
|
+
# You should avoid using +paged_update+ or +paged_datasets+
|
65
|
+
# with updates that modify the primary key, as such usage is
|
66
|
+
# not supported by this plugin.
|
67
|
+
#
|
68
|
+
# This plugin only supports models with scalar primary keys.
|
69
|
+
#
|
70
|
+
# Usage:
|
71
|
+
#
|
72
|
+
# # Make all model subclasses support paged update/delete/datasets
|
73
|
+
# # (called before loading subclasses)
|
74
|
+
# Sequel::Model.plugin :paged_operations
|
75
|
+
#
|
76
|
+
# # Make the Album class support paged update/delete/datasets
|
77
|
+
# Album.plugin :paged_operations
|
78
|
+
module PagedOperations
|
79
|
+
module ClassMethods
|
80
|
+
Plugins.def_dataset_methods(self, [:paged_datasets, :paged_delete, :paged_update])
|
81
|
+
end
|
82
|
+
|
83
|
+
module DatasetMethods
|
84
|
+
# Yield datasets for subsets of the receiver that are limited
|
85
|
+
# to no more than 1000 rows (you can configure the number of
|
86
|
+
# rows using +:rows_per_page+).
|
87
|
+
#
|
88
|
+
# Options:
|
89
|
+
# :rows_per_page :: The maximum number of rows in each yielded dataset
|
90
|
+
# (unless concurrent modifications are made to the table).
|
91
|
+
def paged_datasets(opts=OPTS)
|
92
|
+
unless defined?(yield)
|
93
|
+
return enum_for(:paged_datasets, opts)
|
94
|
+
end
|
95
|
+
|
96
|
+
pk = _paged_operations_pk(:paged_update)
|
97
|
+
base_offset_ds = offset_ds = _paged_operations_offset_ds(opts)
|
98
|
+
first = nil
|
99
|
+
|
100
|
+
while last = offset_ds.get(pk)
|
101
|
+
ds = where(pk < last)
|
102
|
+
ds = ds.where(pk >= first) if first
|
103
|
+
yield ds
|
104
|
+
first = last
|
105
|
+
offset_ds = base_offset_ds.where(pk >= first)
|
106
|
+
end
|
107
|
+
|
108
|
+
ds = self
|
109
|
+
ds = ds.where(pk >= first) if first
|
110
|
+
yield ds
|
111
|
+
nil
|
112
|
+
end
|
113
|
+
|
114
|
+
# Delete all rows of the dataset using using multiple queries so that
|
115
|
+
# no more than 1000 rows are deleted at a time (you can configure the
|
116
|
+
# number of rows using +:rows_per_page+).
|
117
|
+
#
|
118
|
+
# Options:
|
119
|
+
# :rows_per_page :: The maximum number of rows affected by each DELETE query
|
120
|
+
# (unless concurrent modifications are made to the table).
|
121
|
+
def paged_delete(opts=OPTS)
|
122
|
+
if (db.database_type == :oracle && !supports_fetch_next_rows?) || (db.database_type == :mssql && !is_2012_or_later?)
|
123
|
+
raise Error, "paged_delete is not supported on MSSQL/Oracle when using emulated offsets"
|
124
|
+
end
|
125
|
+
pk = _paged_operations_pk(:paged_delete)
|
126
|
+
rows_deleted = 0
|
127
|
+
offset_ds = _paged_operations_offset_ds(opts)
|
128
|
+
while last = offset_ds.get(pk)
|
129
|
+
rows_deleted += where(pk < last).delete
|
130
|
+
end
|
131
|
+
rows_deleted + delete
|
132
|
+
end
|
133
|
+
|
134
|
+
# Update all rows of the dataset using using multiple queries so that
|
135
|
+
# no more than 1000 rows are updated at a time (you can configure the
|
136
|
+
# number of rows using +:rows_per_page+). All arguments are
|
137
|
+
# passed to Dataset#update.
|
138
|
+
#
|
139
|
+
# Options:
|
140
|
+
# :rows_per_page :: The maximum number of rows affected by each UPDATE query
|
141
|
+
# (unless concurrent modifications are made to the table).
|
142
|
+
def paged_update(values, opts=OPTS)
|
143
|
+
rows_updated = 0
|
144
|
+
paged_datasets(opts) do |ds|
|
145
|
+
rows_updated += ds.update(values)
|
146
|
+
end
|
147
|
+
rows_updated
|
148
|
+
end
|
149
|
+
|
150
|
+
private
|
151
|
+
|
152
|
+
# Run some basic checks common to paged_{datasets,delete,update}
|
153
|
+
# and return the primary key to operate on as a Sequel::Identifier.
|
154
|
+
def _paged_operations_pk(meth)
|
155
|
+
raise Error, "cannot use #{meth} if dataset has a limit or offset" if @opts[:limit] || @opts[:offset]
|
156
|
+
if db.database_type == :db2 && db.offset_strategy == :emulate
|
157
|
+
raise Error, "the paged_operations plugin is not supported on DB2 when using emulated offsets, set the :offset_strategy Database option to 'limit_offset' or 'offset_fetch'"
|
158
|
+
end
|
159
|
+
|
160
|
+
case pk = model.primary_key
|
161
|
+
when Symbol
|
162
|
+
Sequel.identifier(pk)
|
163
|
+
when Array
|
164
|
+
raise Error, "cannot use #{meth} on a model with a composite primary key"
|
165
|
+
else
|
166
|
+
raise Error, "cannot use #{meth} on a model without a primary key"
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
# The dataset that will be used by paged_{datasets,delete,update}
|
171
|
+
# to get the upper limit for the next query.
|
172
|
+
def _paged_operations_offset_ds(opts)
|
173
|
+
if rows_per_page = opts[:rows_per_page]
|
174
|
+
raise Error, ":rows_per_page option must be at least 1" unless rows_per_page >= 1
|
175
|
+
end
|
176
|
+
_force_primary_key_order.offset(rows_per_page || 1000)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
data/lib/sequel/version.rb
CHANGED
@@ -6,7 +6,7 @@ module Sequel
|
|
6
6
|
|
7
7
|
# The minor version of Sequel. Bumped for every non-patch level
|
8
8
|
# release, generally around once a month.
|
9
|
-
MINOR =
|
9
|
+
MINOR = 73
|
10
10
|
|
11
11
|
# The tiny version of Sequel. Usually 0, only bumped for bugfix
|
12
12
|
# releases that fix regressions from previous versions.
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sequel
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 5.
|
4
|
+
version: 5.73.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jeremy Evans
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-10-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bigdecimal
|
@@ -219,6 +219,7 @@ extra_rdoc_files:
|
|
219
219
|
- doc/release_notes/5.70.0.txt
|
220
220
|
- doc/release_notes/5.71.0.txt
|
221
221
|
- doc/release_notes/5.72.0.txt
|
222
|
+
- doc/release_notes/5.73.0.txt
|
222
223
|
- doc/release_notes/5.8.0.txt
|
223
224
|
- doc/release_notes/5.9.0.txt
|
224
225
|
files:
|
@@ -319,6 +320,7 @@ files:
|
|
319
320
|
- doc/release_notes/5.70.0.txt
|
320
321
|
- doc/release_notes/5.71.0.txt
|
321
322
|
- doc/release_notes/5.72.0.txt
|
323
|
+
- doc/release_notes/5.73.0.txt
|
322
324
|
- doc/release_notes/5.8.0.txt
|
323
325
|
- doc/release_notes/5.9.0.txt
|
324
326
|
- doc/schema_modification.rdoc
|
@@ -572,6 +574,7 @@ files:
|
|
572
574
|
- lib/sequel/plugins/nested_attributes.rb
|
573
575
|
- lib/sequel/plugins/optimistic_locking.rb
|
574
576
|
- lib/sequel/plugins/optimistic_locking_base.rb
|
577
|
+
- lib/sequel/plugins/paged_operations.rb
|
575
578
|
- lib/sequel/plugins/pg_array_associations.rb
|
576
579
|
- lib/sequel/plugins/pg_auto_constraint_validations.rb
|
577
580
|
- lib/sequel/plugins/pg_row.rb
|