sequel 5.70.0 → 5.72.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 +20 -0
- data/README.rdoc +1 -1
- data/doc/mass_assignment.rdoc +1 -1
- data/doc/migration.rdoc +1 -0
- data/doc/release_notes/5.71.0.txt +21 -0
- data/doc/release_notes/5.72.0.txt +33 -0
- data/doc/testing.rdoc +1 -1
- data/lib/sequel/adapters/jdbc/postgresql.rb +3 -0
- data/lib/sequel/adapters/shared/postgres.rb +0 -2
- data/lib/sequel/extensions/index_caching.rb +5 -1
- data/lib/sequel/extensions/migration.rb +4 -0
- data/lib/sequel/extensions/pg_array.rb +8 -0
- data/lib/sequel/extensions/pg_auto_parameterize_in_array.rb +110 -0
- data/lib/sequel/extensions/schema_caching.rb +1 -1
- data/lib/sequel/plugins/defaults_setter.rb +16 -0
- data/lib/sequel/plugins/mssql_optimistic_locking.rb +8 -38
- data/lib/sequel/plugins/optimistic_locking.rb +9 -42
- data/lib/sequel/plugins/optimistic_locking_base.rb +55 -0
- data/lib/sequel/plugins/pg_auto_constraint_validations.rb +5 -1
- data/lib/sequel/plugins/pg_xmin_optimistic_locking.rb +109 -0
- data/lib/sequel/plugins/static_cache_cache.rb +5 -1
- data/lib/sequel/version.rb +1 -1
- metadata +23 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9e135f19f0f4fb1b78f2c3d0c542ca3db937fa6e1e4960d11120e36a0f6ca314
|
4
|
+
data.tar.gz: f1550faa63cfda46f2fc2c4142e009e19357ce961f8e63542874cabdefbaf597
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '093c3ca50f23e97bc3a10b1b0b2a4dea3f79e9cc85bff92801676709fad00e36dd8a84156a5179758bf091fe9e1486d65fc3de300a2b8038f181f36d16481918'
|
7
|
+
data.tar.gz: db0f0c8c81e1fa33b80dbc2e45d477997392965c5aca8a32fc92125843cff4b49045d3d83bd182c9b8bdf980336e1dc97c47a1a32e186e6ef01e5b7d98bbd0a5
|
data/CHANGELOG
CHANGED
@@ -1,3 +1,23 @@
|
|
1
|
+
=== 5.72.0 (2023-09-01)
|
2
|
+
|
3
|
+
* Sort caches before marshalling when using schema_caching, index_caching, static_cache_cache, and pg_auto_constraint_validations (jeremyevans)
|
4
|
+
|
5
|
+
* Change the defaults_setter plugin do a deep-copy of database default hash/array values and delegates (jeremyevans) (#2069)
|
6
|
+
|
7
|
+
* Add pg_auto_parameterize_in_array extension, for converting IN/NOT IN to = ANY or != ALL for more types (jeremyevans)
|
8
|
+
|
9
|
+
* Fix literalization of infinite and NaN float values in PostgreSQL array bound variables (jeremyevans)
|
10
|
+
|
11
|
+
=== 5.71.0 (2023-08-01)
|
12
|
+
|
13
|
+
* Support ILIKE ANY on PostgreSQL by not forcing the use of ESCAPE for ILIKE (gilesbowkett) (#2066)
|
14
|
+
|
15
|
+
* Add pg_xmin_optimistic_locking plugin for optimistic locking for all models without database changes (jeremyevans)
|
16
|
+
|
17
|
+
* Recognize the xid PostgreSQL type as an integer type in the jdbc/postgresql adapter (jeremyevans)
|
18
|
+
|
19
|
+
* Make set_column_allow_null method reversible in migrations (enescakir) (#2060)
|
20
|
+
|
1
21
|
=== 5.70.0 (2023-07-01)
|
2
22
|
|
3
23
|
* Make static_cache plugin better handle cases where forbid_lazy_load plugin is already loaded (jeremyevans)
|
data/README.rdoc
CHANGED
@@ -825,7 +825,7 @@ You can dynamically customize eager loads for both +eager+ and +eager_graph+ whi
|
|
825
825
|
|
826
826
|
=== Joining with Associations
|
827
827
|
|
828
|
-
You can use the +association_join+ method to add a join to the model's dataset based on the
|
828
|
+
You can use the +association_join+ method to add a join to the model's dataset based on the association:
|
829
829
|
|
830
830
|
Post.association_join(:author)
|
831
831
|
# SELECT * FROM posts
|
data/doc/mass_assignment.rdoc
CHANGED
@@ -48,7 +48,7 @@ If you want to change mass assignment so it ignores attempts to access restricte
|
|
48
48
|
Since mass assignment by default allows modification of all column values except for primary key columns, it can be a security risk in some cases.
|
49
49
|
If you are dealing with untrusted input, you are generally going to want to restrict what should be updated.
|
50
50
|
|
51
|
-
Sequel has <tt>Model#set_fields</tt> and <tt>Model#update_fields</tt> methods, which are designed to be used with
|
51
|
+
Sequel has <tt>Model#set_fields</tt> and <tt>Model#update_fields</tt> methods, which are designed to be used with untrusted input.
|
52
52
|
These methods take two arguments, the untrusted hash as the first argument, and a trusted array of field names as the second argument:
|
53
53
|
|
54
54
|
post.set_fields({title: 'T', body: 'B'}, [:title, :body])
|
data/doc/migration.rdoc
CHANGED
@@ -0,0 +1,21 @@
|
|
1
|
+
= New Features
|
2
|
+
|
3
|
+
* A pg_xmin_optimistic_locking plugin has been added. This plugin
|
4
|
+
uses PostgreSQL's xmin system column to implement optimistic
|
5
|
+
locking. The xmin system column is automatically updated whenever
|
6
|
+
the database row is updated. You can load this plugin into a
|
7
|
+
base model and have all models that subclass from it use optimistic
|
8
|
+
locking, without needing any user-defined lock columns.
|
9
|
+
|
10
|
+
= Other Improvements
|
11
|
+
|
12
|
+
* set_column_allow_null is now a reversible migration method inside
|
13
|
+
alter_table blocks.
|
14
|
+
|
15
|
+
* The use of ILIKE no longer forces the ESCAPE clause on PostgreSQL,
|
16
|
+
which allows the use of ILIKE ANY and other constructions. There
|
17
|
+
is no need to use the ESCAPE clause with ILIKE, because the value
|
18
|
+
Sequel uses is PostgreSQL's default.
|
19
|
+
|
20
|
+
* The xid PostgreSQL type is now recognized as an integer type in the
|
21
|
+
jdbc/postgresql adapter.
|
@@ -0,0 +1,33 @@
|
|
1
|
+
= New Features
|
2
|
+
|
3
|
+
* A pg_auto_parameterize_in_array extension has been added, which
|
4
|
+
handles conversion of IN/NOT IN to = ANY or != ALL for more types.
|
5
|
+
The pg_auto_parameterize extension only handles integer types by
|
6
|
+
default, because other types require the pg_array extension. This
|
7
|
+
new extension adds handling for Float, BigDecimal, Date, Time,
|
8
|
+
DateTime, Sequel::SQLTime, and Sequel::SQL::Blob types. It can
|
9
|
+
also handle String types if the :treat_string_list_as_text_array
|
10
|
+
Database option is present, using the text type for that. Handling
|
11
|
+
String values as text is not the default because that may cause
|
12
|
+
issues for some queries.
|
13
|
+
|
14
|
+
= Other Improvements
|
15
|
+
|
16
|
+
* The defaults_setter plugin now does a deep copy of database
|
17
|
+
default values that are hash/array or delegates to hash/array.
|
18
|
+
This fixes cases where the database default values are mutated.
|
19
|
+
|
20
|
+
* Sequel now correctly handles infinite and NaN float values used
|
21
|
+
inside PostgreSQL array bound variables.
|
22
|
+
|
23
|
+
* The data in the cache files used by the schema_caching and
|
24
|
+
index_caching extensions and static_cache_cache and
|
25
|
+
pg_auto_constraint_validations plugins are now sorted before the
|
26
|
+
cache file is saved, increasing consistency between runs.
|
27
|
+
|
28
|
+
* bigdecimal has been added as a dependency. bigdecimal is currently
|
29
|
+
a default gem in Ruby from 1.9 to 3.2, but it will move to a
|
30
|
+
bundled gem in Ruby 3.4, and there will be warnings in Ruby 3.3
|
31
|
+
for cases that will break in Ruby 3.4. Adding bigdecimal as a
|
32
|
+
dependency should avoid warnings when using bundler in Ruby 3.3,
|
33
|
+
and should avoid errors in Ruby 3.4.
|
data/doc/testing.rdoc
CHANGED
@@ -176,7 +176,7 @@ SEQUEL_MODEL_PREPARED_STATEMENTS :: Use the prepared_statements plugin when runn
|
|
176
176
|
SEQUEL_MODEL_THROW_FAILURES :: Use the throw_failures plugin when running the specs
|
177
177
|
SEQUEL_NO_CACHE_ASSOCIATIONS :: Don't cache association metadata when running the specs
|
178
178
|
SEQUEL_NO_PENDING :: Don't skip any specs, try running all specs (note, can cause lockups for some adapters)
|
179
|
-
SEQUEL_PG_AUTO_PARAMETERIZE :: Use the pg_auto_parameterize extension when running the postgres specs
|
179
|
+
SEQUEL_PG_AUTO_PARAMETERIZE :: Use the pg_auto_parameterize extension when running the postgres specs. Value can be +in_array+ to test the pg_auto_parameterize_in_array extension, and +in_array_string+ to test the pg_auto_parameterize_in_array extension with the +:treat_in_string_list_as_text_array+ Database option set.
|
180
180
|
SEQUEL_PG_TIMESTAMPTZ :: Use the pg_timestamptz extension when running the postgres specs
|
181
181
|
SEQUEL_PRIMARY_KEY_LOOKUP_CHECK_VALUES :: Use the primary_key_lookup_check_values extension when running the adapter or integration specs
|
182
182
|
SEQUEL_QUERY_PER_ASSOCIATION_DB_0_URL :: Run query-per-association integration tests with multiple databases (all 4 must be set to run)
|
@@ -199,6 +199,7 @@ module Sequel
|
|
199
199
|
v.strftime("'%H:%M:%S#{sprintf(".%03d", (v.usec/1000.0).round)}'")
|
200
200
|
end
|
201
201
|
|
202
|
+
INTEGER_TYPE = Java::JavaSQL::Types::INTEGER
|
202
203
|
STRING_TYPE = Java::JavaSQL::Types::VARCHAR
|
203
204
|
ARRAY_TYPE = Java::JavaSQL::Types::ARRAY
|
204
205
|
PG_SPECIFIC_TYPES = [Java::JavaSQL::Types::ARRAY, Java::JavaSQL::Types::OTHER, Java::JavaSQL::Types::STRUCT, Java::JavaSQL::Types::TIME_WITH_TIMEZONE, Java::JavaSQL::Types::TIME].freeze
|
@@ -219,6 +220,8 @@ module Sequel
|
|
219
220
|
oid = meta.getField(i).getOID
|
220
221
|
if pr = db.oid_convertor_proc(oid)
|
221
222
|
pr
|
223
|
+
elsif oid == 28 # XID (Transaction ID)
|
224
|
+
map[INTEGER_TYPE]
|
222
225
|
elsif oid == 2950 # UUID
|
223
226
|
map[STRING_TYPE]
|
224
227
|
elsif meta.getPGType(i) == 'hstore'
|
@@ -56,7 +56,11 @@ module Sequel
|
|
56
56
|
|
57
57
|
# Dump the index cache to the filename given in Marshal format.
|
58
58
|
def dump_index_cache(file)
|
59
|
-
|
59
|
+
indexes = {}
|
60
|
+
@indexes.sort.each do |k, v|
|
61
|
+
indexes[k] = v
|
62
|
+
end
|
63
|
+
File.open(file, 'wb'){|f| f.write(Marshal.dump(indexes))}
|
60
64
|
nil
|
61
65
|
end
|
62
66
|
|
@@ -270,6 +270,10 @@ module Sequel
|
|
270
270
|
def rename_column(name, new_name)
|
271
271
|
@actions << [:rename_column, new_name, name]
|
272
272
|
end
|
273
|
+
|
274
|
+
def set_column_allow_null(name, allow_null=true)
|
275
|
+
@actions << [:set_column_allow_null, name, !allow_null]
|
276
|
+
end
|
273
277
|
end
|
274
278
|
|
275
279
|
# The preferred method for writing Sequel migrations, using a DSL:
|
@@ -233,6 +233,14 @@ module Sequel
|
|
233
233
|
a
|
234
234
|
when String
|
235
235
|
bound_variable_array_string(a)
|
236
|
+
when Float
|
237
|
+
if a.infinite?
|
238
|
+
a > 0 ? '"Infinity"' : '"-Infinity"'
|
239
|
+
elsif a.nan?
|
240
|
+
'"NaN"'
|
241
|
+
else
|
242
|
+
literal(a)
|
243
|
+
end
|
236
244
|
else
|
237
245
|
if (s = bound_variable_arg(a, nil)).is_a?(String)
|
238
246
|
bound_variable_array_string(s)
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
#
|
3
|
+
# The pg_auto_parameterize_in_array extension builds on the pg_auto_parameterize
|
4
|
+
# extension, adding support for handling additional types when converting from
|
5
|
+
# IN to = ANY and NOT IN to != ALL:
|
6
|
+
#
|
7
|
+
# DB[:table].where(column: [1.0, 2.0, ...])
|
8
|
+
# # Without extension: column IN ($1::numeric, $2:numeric, ...) # bound variables: 1.0, 2.0, ...
|
9
|
+
# # With extension: column = ANY($1::numeric[]) # bound variables: [1.0, 2.0, ...]
|
10
|
+
#
|
11
|
+
# This prevents the use of an unbounded number of bound variables based on the
|
12
|
+
# size of the array, as well as using different SQL for different array sizes.
|
13
|
+
#
|
14
|
+
# The following types are supported when doing the conversions, with the database
|
15
|
+
# type used:
|
16
|
+
#
|
17
|
+
# Float :: if any are infinite or NaN, double precision, otherwise numeric
|
18
|
+
# BigDecimal :: numeric
|
19
|
+
# Date :: date
|
20
|
+
# Time :: timestamp (or timestamptz if pg_timestamptz extension is used)
|
21
|
+
# DateTime :: timestamp (or timestamptz if pg_timestamptz extension is used)
|
22
|
+
# Sequel::SQLTime :: time
|
23
|
+
# Sequel::SQL::Blob :: bytea
|
24
|
+
#
|
25
|
+
# String values are also supported using the +text+ type, but only if the
|
26
|
+
# +:treat_string_list_as_text_array+ Database option is used. This is because
|
27
|
+
# treating strings as text can break programs, since the type for
|
28
|
+
# literal strings in PostgreSQL is +unknown+, not +text+.
|
29
|
+
#
|
30
|
+
# The conversion is only done for single dimensional arrays that have more
|
31
|
+
# than two elements, where all elements are of the same class (other than
|
32
|
+
# nil values).
|
33
|
+
#
|
34
|
+
# Related module: Sequel::Postgres::AutoParameterizeInArray
|
35
|
+
|
36
|
+
module Sequel
|
37
|
+
module Postgres
|
38
|
+
# Enable automatically parameterizing queries.
|
39
|
+
module AutoParameterizeInArray
|
40
|
+
# Transform column IN (...) expressions into column = ANY($)
|
41
|
+
# and column NOT IN (...) expressions into column != ALL($)
|
42
|
+
# using an array bound variable for the ANY/ALL argument,
|
43
|
+
# if all values inside the predicate are of the same type and
|
44
|
+
# the type is handled by the extension.
|
45
|
+
# This is the same optimization PostgreSQL performs internally,
|
46
|
+
# but this reduces the number of bound variables.
|
47
|
+
def complex_expression_sql_append(sql, op, args)
|
48
|
+
case op
|
49
|
+
when :IN, :"NOT IN"
|
50
|
+
l, r = args
|
51
|
+
if auto_param?(sql) && (type = _bound_variable_type_for_array(r))
|
52
|
+
if op == :IN
|
53
|
+
op = :"="
|
54
|
+
func = :ANY
|
55
|
+
else
|
56
|
+
op = :!=
|
57
|
+
func = :ALL
|
58
|
+
end
|
59
|
+
args = [l, Sequel.function(func, Sequel.pg_array(r, type))]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
super
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
# The bound variable type string to use for the bound variable array.
|
69
|
+
# Returns nil if a bound variable should not be used for the array.
|
70
|
+
def _bound_variable_type_for_array(r)
|
71
|
+
return unless Array === r && r.size > 1
|
72
|
+
classes = r.map(&:class)
|
73
|
+
classes.uniq!
|
74
|
+
classes.delete(NilClass)
|
75
|
+
return unless classes.size == 1
|
76
|
+
|
77
|
+
klass = classes[0]
|
78
|
+
if klass == Integer
|
79
|
+
# This branch is not taken on Ruby <2.4, because of the Fixnum/Bignum split.
|
80
|
+
# However, that causes no problems as pg_auto_parameterize handles integer
|
81
|
+
# arrays natively (though the SQL used is different)
|
82
|
+
"int8"
|
83
|
+
elsif klass == String
|
84
|
+
"text" if db.typecast_value(:boolean, db.opts[:treat_string_list_as_text_array])
|
85
|
+
elsif klass == BigDecimal
|
86
|
+
"numeric"
|
87
|
+
elsif klass == Date
|
88
|
+
"date"
|
89
|
+
elsif klass == Time
|
90
|
+
@db.cast_type_literal(Time)
|
91
|
+
elsif klass == Float
|
92
|
+
# PostgreSQL treats literal floats as numeric, not double precision
|
93
|
+
# But older versions of PostgreSQL don't handle Infinity/NaN in numeric
|
94
|
+
r.all?{|v| v.nil? || v.finite?} ? "numeric" : "double precision"
|
95
|
+
elsif klass == Sequel::SQLTime
|
96
|
+
"time"
|
97
|
+
elsif klass == DateTime
|
98
|
+
@db.cast_type_literal(DateTime)
|
99
|
+
elsif klass == Sequel::SQL::Blob
|
100
|
+
"bytea"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
Database.register_extension(:pg_auto_parameterize_in_array) do |db|
|
107
|
+
db.extension(:pg_array, :pg_auto_parameterize)
|
108
|
+
db.extend_datasets(Postgres::AutoParameterizeInArray)
|
109
|
+
end
|
110
|
+
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen-string-literal: true
|
2
2
|
|
3
|
+
require 'delegate'
|
4
|
+
|
3
5
|
module Sequel
|
4
6
|
module Plugins
|
5
7
|
# The defaults_setter plugin makes the column getter methods return the default
|
@@ -106,6 +108,20 @@ module Sequel
|
|
106
108
|
lambda{Date.today}
|
107
109
|
when Sequel::CURRENT_TIMESTAMP
|
108
110
|
lambda{dataset.current_datetime}
|
111
|
+
when Hash, Array
|
112
|
+
v = Marshal.dump(v).freeze
|
113
|
+
lambda{Marshal.load(v)}
|
114
|
+
when Delegator
|
115
|
+
# DelegateClass returns an anonymous case, which cannot be marshalled, so marshal the
|
116
|
+
# underlying object and create a new instance of the class with the unmarshalled object.
|
117
|
+
klass = v.class
|
118
|
+
case o = v.__getobj__
|
119
|
+
when Hash, Array
|
120
|
+
v = Marshal.dump(o).freeze
|
121
|
+
lambda{klass.new(Marshal.load(v))}
|
122
|
+
else
|
123
|
+
v
|
124
|
+
end
|
109
125
|
else
|
110
126
|
v
|
111
127
|
end
|
@@ -26,57 +26,27 @@ module Sequel
|
|
26
26
|
module MssqlOptimisticLocking
|
27
27
|
# Load the instance_filters plugin into the model.
|
28
28
|
def self.apply(model, opts=OPTS)
|
29
|
-
model.plugin
|
29
|
+
model.plugin(:optimistic_locking_base)
|
30
30
|
end
|
31
31
|
|
32
|
-
# Set the
|
32
|
+
# Set the lock column
|
33
33
|
def self.configure(model, opts=OPTS)
|
34
|
-
model.lock_column = opts[:lock_column] || :timestamp
|
34
|
+
model.lock_column = opts[:lock_column] || model.lock_column || :timestamp
|
35
35
|
end
|
36
|
-
|
37
|
-
module ClassMethods
|
38
|
-
# The timestamp/rowversion column containing the version for the current row.
|
39
|
-
attr_accessor :lock_column
|
40
|
-
|
41
|
-
Plugins.inherited_instance_variables(self, :@lock_column=>nil)
|
42
|
-
end
|
43
|
-
|
36
|
+
|
44
37
|
module InstanceMethods
|
45
|
-
# Add the lock column instance filter to the object before destroying it.
|
46
|
-
def before_destroy
|
47
|
-
lock_column_instance_filter
|
48
|
-
super
|
49
|
-
end
|
50
|
-
|
51
|
-
# Add the lock column instance filter to the object before updating it.
|
52
|
-
def before_update
|
53
|
-
lock_column_instance_filter
|
54
|
-
super
|
55
|
-
end
|
56
|
-
|
57
38
|
private
|
58
39
|
|
59
|
-
#
|
60
|
-
def
|
61
|
-
|
62
|
-
instance_filter(lc=>Sequel.blob(get_column_value(lc)))
|
63
|
-
end
|
64
|
-
|
65
|
-
# Clear the instance filters when refreshing, so that attempting to
|
66
|
-
# refresh after a failed save removes the previous lock column filter
|
67
|
-
# (the new one will be added before updating).
|
68
|
-
def _refresh(ds)
|
69
|
-
clear_instance_filters
|
70
|
-
super
|
40
|
+
# Make the instance filter value a blob.
|
41
|
+
def lock_column_instance_filter_value
|
42
|
+
Sequel.blob(super)
|
71
43
|
end
|
72
44
|
|
73
45
|
# Remove the lock column from the columns to update.
|
74
46
|
# SQL Server automatically updates the lock column value, and does not like
|
75
47
|
# it to be assigned.
|
76
48
|
def _save_update_all_columns_hash
|
77
|
-
v =
|
78
|
-
cc = changed_columns
|
79
|
-
Array(primary_key).each{|x| v.delete(x) unless cc.include?(x)}
|
49
|
+
v = super
|
80
50
|
v.delete(model.lock_column)
|
81
51
|
v
|
82
52
|
end
|
@@ -12,64 +12,31 @@ module Sequel
|
|
12
12
|
# p1 = Person[1]
|
13
13
|
# p2 = Person[1]
|
14
14
|
# p1.update(name: 'Jim') # works
|
15
|
-
# p2.update(name: 'Bob') # raises Sequel::
|
15
|
+
# p2.update(name: 'Bob') # raises Sequel::NoExistingObject
|
16
16
|
#
|
17
17
|
# In order for this plugin to work, you need to make sure that the database
|
18
|
-
# table has a +lock_version+ column
|
19
|
-
#
|
18
|
+
# table has a +lock_version+ column that defaults to 0. To change the column
|
19
|
+
# used, provide a +:lock_column+ option when loading the plugin:
|
20
|
+
#
|
21
|
+
# plugin :optimistic_locking, lock_column: :version
|
20
22
|
#
|
21
23
|
# This plugin relies on the instance_filters plugin.
|
22
24
|
module OptimisticLocking
|
23
25
|
# Exception class raised when trying to update or destroy a stale object.
|
24
26
|
Error = Sequel::NoExistingObject
|
25
27
|
|
26
|
-
# Load the instance_filters plugin into the model.
|
27
28
|
def self.apply(model, opts=OPTS)
|
28
|
-
model.plugin
|
29
|
+
model.plugin(:optimistic_locking_base)
|
29
30
|
end
|
30
31
|
|
31
|
-
# Set the
|
32
|
-
# that option is not given.
|
32
|
+
# Set the lock column
|
33
33
|
def self.configure(model, opts=OPTS)
|
34
|
-
model.lock_column = opts[:lock_column] || :lock_version
|
34
|
+
model.lock_column = opts[:lock_column] || model.lock_column || :lock_version
|
35
35
|
end
|
36
|
-
|
37
|
-
module ClassMethods
|
38
|
-
# The column holding the version of the lock
|
39
|
-
attr_accessor :lock_column
|
40
|
-
|
41
|
-
Plugins.inherited_instance_variables(self, :@lock_column=>nil)
|
42
|
-
end
|
43
|
-
|
36
|
+
|
44
37
|
module InstanceMethods
|
45
|
-
# Add the lock column instance filter to the object before destroying it.
|
46
|
-
def before_destroy
|
47
|
-
lock_column_instance_filter
|
48
|
-
super
|
49
|
-
end
|
50
|
-
|
51
|
-
# Add the lock column instance filter to the object before updating it.
|
52
|
-
def before_update
|
53
|
-
lock_column_instance_filter
|
54
|
-
super
|
55
|
-
end
|
56
|
-
|
57
38
|
private
|
58
39
|
|
59
|
-
# Add the lock column instance filter to the object.
|
60
|
-
def lock_column_instance_filter
|
61
|
-
lc = model.lock_column
|
62
|
-
instance_filter(lc=>get_column_value(lc))
|
63
|
-
end
|
64
|
-
|
65
|
-
# Clear the instance filters when refreshing, so that attempting to
|
66
|
-
# refresh after a failed save removes the previous lock column filter
|
67
|
-
# (the new one will be added before updating).
|
68
|
-
def _refresh(ds)
|
69
|
-
clear_instance_filters
|
70
|
-
super
|
71
|
-
end
|
72
|
-
|
73
40
|
# Only update the row if it has the same lock version, and increment the
|
74
41
|
# lock version.
|
75
42
|
def _update_columns(columns)
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
module Sequel
|
4
|
+
module Plugins
|
5
|
+
# Base for other optimistic locking plugins
|
6
|
+
module OptimisticLockingBase
|
7
|
+
# Load the instance_filters plugin into the model.
|
8
|
+
def self.apply(model)
|
9
|
+
model.plugin :instance_filters
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
# The column holding the version of the lock
|
14
|
+
attr_accessor :lock_column
|
15
|
+
|
16
|
+
Plugins.inherited_instance_variables(self, :@lock_column=>nil)
|
17
|
+
end
|
18
|
+
|
19
|
+
module InstanceMethods
|
20
|
+
# Add the lock column instance filter to the object before destroying it.
|
21
|
+
def before_destroy
|
22
|
+
lock_column_instance_filter
|
23
|
+
super
|
24
|
+
end
|
25
|
+
|
26
|
+
# Add the lock column instance filter to the object before updating it.
|
27
|
+
def before_update
|
28
|
+
lock_column_instance_filter
|
29
|
+
super
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
# Add the lock column instance filter to the object.
|
35
|
+
def lock_column_instance_filter
|
36
|
+
instance_filter(model.lock_column=>lock_column_instance_filter_value)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Use the current value of the lock column
|
40
|
+
def lock_column_instance_filter_value
|
41
|
+
public_send(model.lock_column)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Clear the instance filters when refreshing, so that attempting to
|
45
|
+
# refresh after a failed save removes the previous lock column filter
|
46
|
+
# (the new one will be added before updating).
|
47
|
+
def _refresh(ds)
|
48
|
+
clear_instance_filters
|
49
|
+
super
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
@@ -133,7 +133,11 @@ module Sequel
|
|
133
133
|
# Dump the in-memory cached metadata to the cache file.
|
134
134
|
def dump_pg_auto_constraint_validations_cache
|
135
135
|
raise Error, "No pg_auto_constraint_validations setup" unless file = @pg_auto_constraint_validations_cache_file
|
136
|
-
|
136
|
+
pg_auto_constraint_validations_cache = {}
|
137
|
+
@pg_auto_constraint_validations_cache.sort.each do |k, v|
|
138
|
+
pg_auto_constraint_validations_cache[k] = v
|
139
|
+
end
|
140
|
+
File.open(file, 'wb'){|f| f.write(Marshal.dump(pg_auto_constraint_validations_cache))}
|
137
141
|
nil
|
138
142
|
end
|
139
143
|
|
@@ -0,0 +1,109 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
module Sequel
|
4
|
+
module Plugins
|
5
|
+
# This plugin implements optimistic locking mechanism on PostgreSQL based
|
6
|
+
# on the xmin of the row. The xmin system column is automatically set to
|
7
|
+
# the current transaction id whenever the row is inserted or updated:
|
8
|
+
#
|
9
|
+
# class Person < Sequel::Model
|
10
|
+
# plugin :pg_xmin_optimistic_locking
|
11
|
+
# end
|
12
|
+
# p1 = Person[1]
|
13
|
+
# p2 = Person[1]
|
14
|
+
# p1.update(name: 'Jim') # works
|
15
|
+
# p2.update(name: 'Bob') # raises Sequel::NoExistingObject
|
16
|
+
#
|
17
|
+
# The advantage of pg_xmin_optimistic_locking plugin compared to the
|
18
|
+
# regular optimistic_locking plugin as that it does not require any
|
19
|
+
# additional columns setup on the model. This allows it to be loaded
|
20
|
+
# in the base model and have all subclasses automatically use
|
21
|
+
# optimistic locking. The disadvantage is that testing can be
|
22
|
+
# more difficult if you are modifying the underlying row between
|
23
|
+
# when a model is retrieved and when it is saved.
|
24
|
+
#
|
25
|
+
# This plugin may not work with the class_table_inheritance plugin.
|
26
|
+
#
|
27
|
+
# This plugin relies on the instance_filters plugin.
|
28
|
+
module PgXminOptimisticLocking
|
29
|
+
WILDCARD = LiteralString.new('*').freeze
|
30
|
+
|
31
|
+
# Define the xmin column accessor
|
32
|
+
def self.apply(model)
|
33
|
+
model.instance_exec do
|
34
|
+
plugin(:optimistic_locking_base)
|
35
|
+
@lock_column = :xmin
|
36
|
+
def_column_accessor(:xmin)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Update the dataset to append the xmin column if it is usable
|
41
|
+
# and there is a dataset for the model.
|
42
|
+
def self.configure(model)
|
43
|
+
model.instance_exec do
|
44
|
+
set_dataset(@dataset) if @dataset
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
module ClassMethods
|
49
|
+
private
|
50
|
+
|
51
|
+
# Ensure the dataset selects the xmin column if doing so
|
52
|
+
def convert_input_dataset(ds)
|
53
|
+
append_xmin_column_if_usable(super)
|
54
|
+
end
|
55
|
+
|
56
|
+
# If the xmin column is not already selected, and selecting it does not
|
57
|
+
# raise an error, append it to the selections.
|
58
|
+
def append_xmin_column_if_usable(ds)
|
59
|
+
select = ds.opts[:select]
|
60
|
+
|
61
|
+
unless select && select.include?(:xmin)
|
62
|
+
xmin_ds = ds.select_append(:xmin)
|
63
|
+
begin
|
64
|
+
columns = xmin_ds.columns!
|
65
|
+
rescue Sequel::DatabaseConnectionError, Sequel::DatabaseDisconnectError
|
66
|
+
raise
|
67
|
+
rescue Sequel::DatabaseError
|
68
|
+
# ignore, could be view, subquery, table returning function, etc.
|
69
|
+
else
|
70
|
+
ds = xmin_ds if columns.include?(:xmin)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
ds
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
module InstanceMethods
|
79
|
+
private
|
80
|
+
|
81
|
+
# Only set the lock column instance filter if there is an xmin value.
|
82
|
+
def lock_column_instance_filter
|
83
|
+
super if @values[:xmin]
|
84
|
+
end
|
85
|
+
|
86
|
+
# Include xmin value when inserting initial row
|
87
|
+
def _insert_dataset
|
88
|
+
super.returning(WILDCARD, :xmin)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Remove the xmin from the columns to update.
|
92
|
+
# PostgreSQL automatically updates the xmin value, and it cannot be assigned.
|
93
|
+
def _save_update_all_columns_hash
|
94
|
+
v = super
|
95
|
+
v.delete(:xmin)
|
96
|
+
v
|
97
|
+
end
|
98
|
+
|
99
|
+
# Add an RETURNING clause to fetch the updated xmin when updating the row.
|
100
|
+
def _update_without_checking(columns)
|
101
|
+
ds = _update_dataset
|
102
|
+
rows = ds.clone(ds.send(:default_server_opts, :sql=>ds.returning(:xmin).update_sql(columns))).all
|
103
|
+
values[:xmin] = rows.first[:xmin] unless rows.empty?
|
104
|
+
rows.length
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -26,7 +26,11 @@ module Sequel
|
|
26
26
|
module ClassMethods
|
27
27
|
# Dump the in-memory cached rows to the cache file.
|
28
28
|
def dump_static_cache_cache
|
29
|
-
|
29
|
+
static_cache_cache = {}
|
30
|
+
@static_cache_cache.sort.each do |k, v|
|
31
|
+
static_cache_cache[k] = v
|
32
|
+
end
|
33
|
+
File.open(@static_cache_cache_file, 'wb'){|f| f.write(Marshal.dump(static_cache_cache))}
|
30
34
|
nil
|
31
35
|
end
|
32
36
|
|
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 = 72
|
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,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sequel
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 5.
|
4
|
+
version: 5.72.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-09-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bigdecimal
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: minitest
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -203,6 +217,8 @@ extra_rdoc_files:
|
|
203
217
|
- doc/release_notes/5.69.0.txt
|
204
218
|
- doc/release_notes/5.7.0.txt
|
205
219
|
- doc/release_notes/5.70.0.txt
|
220
|
+
- doc/release_notes/5.71.0.txt
|
221
|
+
- doc/release_notes/5.72.0.txt
|
206
222
|
- doc/release_notes/5.8.0.txt
|
207
223
|
- doc/release_notes/5.9.0.txt
|
208
224
|
files:
|
@@ -301,6 +317,8 @@ files:
|
|
301
317
|
- doc/release_notes/5.69.0.txt
|
302
318
|
- doc/release_notes/5.7.0.txt
|
303
319
|
- doc/release_notes/5.70.0.txt
|
320
|
+
- doc/release_notes/5.71.0.txt
|
321
|
+
- doc/release_notes/5.72.0.txt
|
304
322
|
- doc/release_notes/5.8.0.txt
|
305
323
|
- doc/release_notes/5.9.0.txt
|
306
324
|
- doc/schema_modification.rdoc
|
@@ -443,6 +461,7 @@ files:
|
|
443
461
|
- lib/sequel/extensions/pg_array.rb
|
444
462
|
- lib/sequel/extensions/pg_array_ops.rb
|
445
463
|
- lib/sequel/extensions/pg_auto_parameterize.rb
|
464
|
+
- lib/sequel/extensions/pg_auto_parameterize_in_array.rb
|
446
465
|
- lib/sequel/extensions/pg_enum.rb
|
447
466
|
- lib/sequel/extensions/pg_extended_date_support.rb
|
448
467
|
- lib/sequel/extensions/pg_extended_integer_support.rb
|
@@ -552,9 +571,11 @@ files:
|
|
552
571
|
- lib/sequel/plugins/mssql_optimistic_locking.rb
|
553
572
|
- lib/sequel/plugins/nested_attributes.rb
|
554
573
|
- lib/sequel/plugins/optimistic_locking.rb
|
574
|
+
- lib/sequel/plugins/optimistic_locking_base.rb
|
555
575
|
- lib/sequel/plugins/pg_array_associations.rb
|
556
576
|
- lib/sequel/plugins/pg_auto_constraint_validations.rb
|
557
577
|
- lib/sequel/plugins/pg_row.rb
|
578
|
+
- lib/sequel/plugins/pg_xmin_optimistic_locking.rb
|
558
579
|
- lib/sequel/plugins/prepared_statements.rb
|
559
580
|
- lib/sequel/plugins/prepared_statements_safe.rb
|
560
581
|
- lib/sequel/plugins/primary_key_lookup_check_values.rb
|