sequel 5.70.0 → 5.72.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 780e8bd3203a4aa27f70c94dd2ddd29e28826ec7b74b86296a2571d40ff2a74a
4
- data.tar.gz: f600cfad2e5401f2c1150838ecacac0cd34ba38da4a69bd3afd580dbadaee35f
3
+ metadata.gz: 9e135f19f0f4fb1b78f2c3d0c542ca3db937fa6e1e4960d11120e36a0f6ca314
4
+ data.tar.gz: f1550faa63cfda46f2fc2c4142e009e19357ce961f8e63542874cabdefbaf597
5
5
  SHA512:
6
- metadata.gz: b22192e34739b2158a543f18f51b52b44063b86b02c6968a8f8e1ff2deec8338fa4f81176a4f5f2b27ace6fcefe9d3511ccaa73c43e2b660743a3a736f303080
7
- data.tar.gz: 4540e3f53f53d6fe8b7a22212ab7d6678502e87bf67a5081c22d1da02ea8ea2090589e384fe17e4ca454c0c4c45358f1aaed1772d27e7732f7f68071e5914ea6
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 assocation:
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
@@ -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 untrused input.
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
@@ -86,6 +86,7 @@ the following methods:
86
86
  * +add_full_text_index+
87
87
  * +add_spatial_index+
88
88
  * +rename_column+
89
+ * +set_column_allow_null+
89
90
 
90
91
  If you use any other methods, you should create your own +down+ block.
91
92
 
@@ -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'
@@ -1745,8 +1745,6 @@ module Sequel
1745
1745
  literal_append(sql, args[0])
1746
1746
  sql << ' ' << op.to_s << ' '
1747
1747
  literal_append(sql, args[1])
1748
- sql << " ESCAPE "
1749
- literal_append(sql, "\\")
1750
1748
  sql << ')'
1751
1749
  else
1752
1750
  super
@@ -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
- File.open(file, 'wb'){|f| f.write(Marshal.dump(@indexes))}
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
@@ -52,7 +52,7 @@ module Sequel
52
52
  # Dump the cached schema to the filename given in Marshal format.
53
53
  def dump_schema_cache(file)
54
54
  sch = {}
55
- @schemas.each do |k,v|
55
+ @schemas.sort.each do |k,v|
56
56
  sch[k] = v.map do |c, h|
57
57
  h = Hash[h]
58
58
  h.delete(:callable_default)
@@ -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 :instance_filters
29
+ model.plugin(:optimistic_locking_base)
30
30
  end
31
31
 
32
- # Set the lock_column to the :lock_column option (default: :timestamp)
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
- # Add the lock column instance filter to the object.
60
- def lock_column_instance_filter
61
- lc = model.lock_column
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 = @values.dup
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::Plugins::OptimisticLocking::Error
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 (or other column you name via the lock_column
19
- # class level accessor) that defaults to 0.
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 :instance_filters
29
+ model.plugin(:optimistic_locking_base)
29
30
  end
30
31
 
31
- # Set the lock_column to the :lock_column option, or :lock_version if
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
- File.open(file, 'wb'){|f| f.write(Marshal.dump(@pg_auto_constraint_validations_cache))}
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
- File.open(@static_cache_cache_file, 'wb'){|f| f.write(Marshal.dump(@static_cache_cache))}
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
 
@@ -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 = 70
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.70.0
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-07-01 00:00:00.000000000 Z
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