sequel 5.95.0 → 5.99.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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/lib/sequel/adapters/amalgalite.rb +16 -1
  3. data/lib/sequel/adapters/postgres.rb +1 -0
  4. data/lib/sequel/adapters/shared/mssql.rb +1 -1
  5. data/lib/sequel/adapters/shared/mysql.rb +1 -1
  6. data/lib/sequel/adapters/shared/postgres.rb +50 -6
  7. data/lib/sequel/adapters/sqlite.rb +15 -1
  8. data/lib/sequel/ast_transformer.rb +2 -0
  9. data/lib/sequel/core.rb +15 -1
  10. data/lib/sequel/database/misc.rb +1 -1
  11. data/lib/sequel/database/schema_generator.rb +4 -0
  12. data/lib/sequel/dataset/actions.rb +65 -10
  13. data/lib/sequel/dataset/query.rb +1 -1
  14. data/lib/sequel/dataset/sql.rb +10 -2
  15. data/lib/sequel/extensions/constraint_validations.rb +3 -3
  16. data/lib/sequel/extensions/empty_array_consider_nulls.rb +7 -0
  17. data/lib/sequel/extensions/eval_inspect.rb +2 -0
  18. data/lib/sequel/extensions/looser_typecasting.rb +5 -12
  19. data/lib/sequel/extensions/pg_array_ops.rb +41 -0
  20. data/lib/sequel/extensions/pg_auto_parameterize.rb +4 -2
  21. data/lib/sequel/extensions/pg_auto_parameterize_duplicate_query_detection.rb +191 -0
  22. data/lib/sequel/extensions/pg_auto_parameterize_in_array.rb +2 -1
  23. data/lib/sequel/extensions/pg_hstore_ops.rb +10 -5
  24. data/lib/sequel/extensions/pg_json_ops.rb +32 -9
  25. data/lib/sequel/extensions/pg_multirange.rb +1 -1
  26. data/lib/sequel/extensions/pg_range.rb +13 -2
  27. data/lib/sequel/extensions/pg_row.rb +2 -2
  28. data/lib/sequel/extensions/set_literalizer.rb +20 -39
  29. data/lib/sequel/extensions/split_array_nil.rb +12 -2
  30. data/lib/sequel/extensions/to_dot.rb +5 -0
  31. data/lib/sequel/model/associations.rb +7 -7
  32. data/lib/sequel/model/base.rb +36 -9
  33. data/lib/sequel/plugins/deprecated_associations.rb +151 -0
  34. data/lib/sequel/plugins/insert_returning_select.rb +10 -1
  35. data/lib/sequel/plugins/json_serializer.rb +1 -7
  36. data/lib/sequel/plugins/pg_array_associations.rb +2 -2
  37. data/lib/sequel/plugins/rcte_tree.rb +5 -5
  38. data/lib/sequel/plugins/split_values.rb +10 -0
  39. data/lib/sequel/plugins/static_cache.rb +13 -0
  40. data/lib/sequel/plugins/subset_static_cache.rb +15 -0
  41. data/lib/sequel/plugins/table_select.rb +7 -0
  42. data/lib/sequel/sql.rb +1 -1
  43. data/lib/sequel/version.rb +1 -1
  44. metadata +3 -1
@@ -0,0 +1,191 @@
1
+ # frozen-string-literal: true
2
+ #
3
+ # The pg_auto_parameterize_duplicate_query_detection extension builds on the
4
+ # pg_auto_parameterize extension, adding support for detecting duplicate
5
+ # queries inside a block that occur at the same location. This is designed
6
+ # mostly to catch duplicate query issues (e.g. N+1 queries) during testing.
7
+ #
8
+ # To detect duplicate queries inside a block of code, wrap the code with
9
+ # +detect_duplicate_queries+:
10
+ #
11
+ # DB.detect_duplicate_queries{your_code}
12
+ #
13
+ # With this approach, if the test runs code where the same query is executed
14
+ # more than once with the same call stack, a
15
+ # Sequel::Postgres::AutoParameterizeDuplicateQueryDetection::DuplicateQueries
16
+ # exception will be raised.
17
+ #
18
+ # You can pass the +:warn+ option to +detect_duplicate_queries+ to warn
19
+ # instead of raising. Note that if the block passed to +detect_duplicate_queries+
20
+ # raises, this extension will warn, and raise the original exception.
21
+ #
22
+ # For more control, you can pass the +:handler+ option to
23
+ # +detect_duplicate_queries+. If the +:handler+ option is provided, this
24
+ # extension will call the +:handler+ option with the hash of duplicate
25
+ # query information, and will not raise or warn. This can be useful in
26
+ # production environments, to record duplicate queries for later analysis.
27
+ #
28
+ # For accuracy, the entire call stack is always used as part of the hash key
29
+ # to determine whether a query is duplicate. However, you can filter the
30
+ # displayed backtrace by using the +:backtrace_filter+ option.
31
+ #
32
+ # +detect_duplicate_queries+ is concurrency aware, it uses the same approach
33
+ # that Sequel's default connection pools use. So if you are running multiple
34
+ # threads, +detect_duplicate_queries+ will only report duplicate queries for
35
+ # the current thread (or fiber if the fiber_concurrency extension is used).
36
+ #
37
+ # When testing applications, it's probably best to use this to wrap the
38
+ # application being tested. For example, testing with rack-test, if your app
39
+ # is +App+, you would want to wrap it:
40
+ #
41
+ # include Rack::Test::Methods
42
+ #
43
+ # WrappedApp = lambda do |env|
44
+ # DB.detect_duplicate_queries{App.call(env)}
45
+ # end
46
+ #
47
+ # def app
48
+ # WrappedApp
49
+ # end
50
+ #
51
+ # It is possible to use this to wrap each separate spec using an around hook,
52
+ # but that can result in false positives when using libraries that have
53
+ # implicit retrying (such as Capybara), as you can have the same call stack
54
+ # for multiple requests.
55
+ #
56
+ # Related module: Sequel::Postgres::AutoParameterizeDuplicateQueryDetection
57
+
58
+ module Sequel
59
+ module Postgres
60
+ # Enable detecting duplicate queries inside a block
61
+ module AutoParameterizeDuplicateQueryDetection
62
+ def self.extended(db)
63
+ db.instance_exec do
64
+ @duplicate_query_detection_contexts = {}
65
+ @duplicate_query_detection_mutex = Mutex.new
66
+ end
67
+ end
68
+
69
+ # Exception class raised when duplicate queries are detected.
70
+ class DuplicateQueries < Sequel::Error
71
+ # A hash of queries that were duplicate. Keys are arrays
72
+ # with 2 entries, the first being the query SQL, and the
73
+ # second being the related call stack (backtrace).
74
+ # The values are the number of query executions.
75
+ attr_reader :queries
76
+
77
+ def initialize(message, queries)
78
+ @queries = queries
79
+ super(message)
80
+ end
81
+ end
82
+
83
+ # Record each query executed so duplicates can be detected,
84
+ # if queries are being recorded.
85
+ def execute(sql, opts=OPTS, &block)
86
+ record, queries = duplicate_query_recorder_state
87
+
88
+ if record
89
+ queries[[sql.is_a?(Symbol) ? sql : sql.to_s, caller].freeze] += 1
90
+ end
91
+
92
+ super
93
+ end
94
+
95
+ # Ignore (do not record) queries inside given block. This can
96
+ # be useful in situations where you want to run your entire test suite
97
+ # with duplicate query detection, but you have duplicate queries in
98
+ # some parts of your application where it is not trivial to use a
99
+ # different approach. You can mark those specific sections with
100
+ # +ignore_duplicate_queries+, and still get duplicate query detection
101
+ # for the rest of the application.
102
+ def ignore_duplicate_queries(&block)
103
+ if state = duplicate_query_recorder_state
104
+ change_duplicate_query_recorder_state(state, false, &block)
105
+ else
106
+ # If we are not inside a detect_duplicate_queries block, there is
107
+ # no need to do anything, since we are not recording queries.
108
+ yield
109
+ end
110
+ end
111
+
112
+ # Run the duplicate query detector during the block.
113
+ # Options:
114
+ #
115
+ # :backtrace_filter :: Regexp used to filter the displayed backtrace.
116
+ # :handler :: If present, called with hash of duplicate query information,
117
+ # instead of raising or warning.
118
+ # :warn :: Always warn instead of raising for duplicate queries.
119
+ #
120
+ # Note that if you nest calls to this method, only the top
121
+ # level call will respect the passed options.
122
+ def detect_duplicate_queries(opts=OPTS, &block)
123
+ current = Sequel.current
124
+ if state = duplicate_query_recorder_state(current)
125
+ return change_duplicate_query_recorder_state(state, true, &block)
126
+ end
127
+
128
+ @duplicate_query_detection_mutex.synchronize do
129
+ @duplicate_query_detection_contexts[current] = [true, Hash.new(0)]
130
+ end
131
+
132
+ begin
133
+ yield
134
+ rescue Exception => e
135
+ raise
136
+ ensure
137
+ _, queries = @duplicate_query_detection_mutex.synchronize do
138
+ @duplicate_query_detection_contexts.delete(current)
139
+ end
140
+ queries.delete_if{|_,v| v < 2}
141
+
142
+ unless queries.empty?
143
+ if handler = opts[:handler]
144
+ handler.call(queries)
145
+ else
146
+ backtrace_filter = opts[:backtrace_filter]
147
+ backtrace_filter_note = backtrace_filter ? " (filtered)" : ""
148
+ query_info = queries.map do |k,v|
149
+ backtrace = k[1]
150
+ backtrace = backtrace.grep(backtrace_filter) if backtrace_filter
151
+ "times:#{v}\nsql:#{k[0]}\nbacktrace#{backtrace_filter_note}:\n#{backtrace.join("\n")}\n"
152
+ end
153
+ message = "duplicate queries detected:\n\n#{query_info.join("\n")}"
154
+
155
+ if e || opts[:warn]
156
+ warn(message)
157
+ else
158
+ raise DuplicateQueries.new(message, queries)
159
+ end
160
+ end
161
+ end
162
+ end
163
+ end
164
+
165
+ private
166
+
167
+ # Get the query record state for the given context.
168
+ def duplicate_query_recorder_state(current=Sequel.current)
169
+ @duplicate_query_detection_mutex.synchronize{@duplicate_query_detection_contexts[current]}
170
+ end
171
+
172
+ # Temporarily change whether to record queries for the block, resetting the
173
+ # previous setting after the block exits.
174
+ def change_duplicate_query_recorder_state(state, setting)
175
+ prev = state[0]
176
+ state[0] = setting
177
+
178
+ begin
179
+ yield
180
+ ensure
181
+ state[0] = prev
182
+ end
183
+ end
184
+ end
185
+ end
186
+
187
+ Database.register_extension(:pg_auto_parameterize_duplicate_query_detection) do |db|
188
+ db.extension(:pg_auto_parameterize)
189
+ db.extend(Postgres::AutoParameterizeDuplicateQueryDetection)
190
+ end
191
+ end
@@ -120,7 +120,7 @@ module Sequel
120
120
  # The bound variable type string to use for the bound variable array.
121
121
  # Returns nil if a bound variable should not be used for the array.
122
122
  def _bound_variable_type_for_array(r)
123
- return unless Array === r && r.size >= pg_auto_parameterize_min_array_size
123
+ return unless (Array === r || Set === r) && r.size >= pg_auto_parameterize_min_array_size
124
124
  classes = r.map(&:class)
125
125
  classes.uniq!
126
126
  classes.delete(NilClass)
@@ -165,6 +165,7 @@ module Sequel
165
165
 
166
166
  # Convert RHS of IN/NOT IN operator to PGArray with given type.
167
167
  def _convert_array_to_pg_array_with_type(r, type)
168
+ r = r.to_a if Set === r
168
169
  Sequel.pg_array(r, type)
169
170
  end
170
171
  end
@@ -294,13 +294,18 @@ module Sequel
294
294
  SQL::Function.new(name, self, *args)
295
295
  end
296
296
 
297
- # Wrap argument in a PGArray if it is an array
297
+ # Wrap argument in a PGArray if it is an array or a set
298
298
  def wrap_input_array(obj)
299
- if obj.is_a?(Array) && Sequel.respond_to?(:pg_array)
300
- Sequel.pg_array(obj)
301
- else
302
- obj
299
+ if Sequel.respond_to?(:pg_array)
300
+ case obj
301
+ when Array
302
+ return Sequel.pg_array(obj)
303
+ when Set
304
+ return Sequel.pg_array(obj.to_a)
305
+ end
303
306
  end
307
+
308
+ obj
304
309
  end
305
310
 
306
311
  # Wrap argument in an Hstore if it is a hash
@@ -166,6 +166,11 @@
166
166
  # # "d" date EXISTS FALSE ON ERROR
167
167
  # # ))
168
168
  #
169
+ # On PostgreSQL 18+, strip_nulls can take an argument for whether to strip in arrays
170
+ #
171
+ # j.strip_nulls(in_arrays: true) # json_strip_nulls(json_column, true)
172
+ # j.strip_nulls(in_arrays: false) # json_strip_nulls(json_column, false)
173
+ #
169
174
  # If you are also using the pg_json extension, you should load it before
170
175
  # loading this extension. Doing so will allow you to use the #op method on
171
176
  # JSONHash, JSONHarray, JSONBHash, and JSONBArray, allowing you to perform json/jsonb operations
@@ -380,11 +385,22 @@ module Sequel
380
385
  self.class.new(JSONQueryOp.new(self, path, opts))
381
386
  end
382
387
 
383
- # Returns a json value stripped of all internal null values.
388
+ # Returns a json value stripped of all internal null values. Options:
389
+ #
390
+ # :in_arrays :: Whether to strip null values in JSON arrays
384
391
  #
385
- # json_op.strip_nulls # json_strip_nulls(json)
386
- def strip_nulls
387
- self.class.new(function(:strip_nulls))
392
+ # json_op.strip_nulls # json_strip_nulls(json)
393
+ # json_op.strip_nulls(in_arrays: true) # json_strip_nulls(json, true)
394
+ # json_op.strip_nulls(in_arrays: false) # json_strip_nulls(json, false)
395
+ def strip_nulls(opts=OPTS)
396
+ in_arrays = opts[:in_arrays]
397
+ f = if in_arrays.nil?
398
+ function(:strip_nulls)
399
+ else
400
+ function(:strip_nulls, in_arrays)
401
+ end
402
+
403
+ self.class.new(f)
388
404
  end
389
405
 
390
406
  # Returns json_table SQL function expression, querying JSON data and returning
@@ -825,13 +841,20 @@ module Sequel
825
841
  Sequel::SQL::BooleanExpression.new(:NOOP, Sequel::SQL::PlaceholderLiteralString.new(str, [value, other]))
826
842
  end
827
843
 
828
- # Wrap argument in a PGArray if it is an array
844
+ # Wrap argument in a PGArray if it is an array or a set
829
845
  def wrap_input_array(obj)
830
- if obj.is_a?(Array) && Sequel.respond_to?(:pg_array)
831
- Sequel.pg_array(obj)
832
- else
833
- obj
846
+ # :nocov:
847
+ if Sequel.respond_to?(:pg_array)
848
+ # :nocov:
849
+ case obj
850
+ when Array
851
+ return Sequel.pg_array(obj)
852
+ when Set
853
+ return Sequel.pg_array(obj.to_a)
854
+ end
834
855
  end
856
+
857
+ obj
835
858
  end
836
859
 
837
860
  # Wrap argument in a JSONBArray or JSONBHash if it is an array or hash.
@@ -339,7 +339,7 @@ module Sequel
339
339
 
340
340
  # Allow automatic parameterization.
341
341
  def sequel_auto_param_type(ds)
342
- "::#{db_type}"
342
+ "::#{db_type}" if all?{|range| range.is_a?(Range) || ds.send(:auto_param_type, range)}
343
343
  end
344
344
  end
345
345
  end
@@ -421,6 +421,17 @@ module Sequel
421
421
  @exclude_end
422
422
  end
423
423
 
424
+ # Support a friendly output
425
+ def inspect
426
+ range = if empty?
427
+ "empty"
428
+ else
429
+ "#{@exclude_begin ? "(" : "["}#{@begin},#{@end}#{@exclude_end ? ")" : "]"}"
430
+ end
431
+
432
+ "#<#{self.class.name} #{range}#{"::#{@db_type}" if @db_type}>"
433
+ end
434
+
424
435
  # Append a literalize version of the receiver to the sql.
425
436
  def sql_literal_append(ds, sql)
426
437
  if (s = @db_type) && !empty?
@@ -481,9 +492,9 @@ module Sequel
481
492
  end
482
493
  end
483
494
 
484
- # Allow automatic parameterization for ranges with types.
495
+ # Allow automatic parameterization for ranges with types, if both start .
485
496
  def sequel_auto_param_type(ds)
486
- "::#{db_type}" if db_type
497
+ "::#{db_type}" if db_type && (!@begin || ds.send(:auto_param_type, @begin)) && (!@end || ds.send(:auto_param_type, @end))
487
498
  end
488
499
 
489
500
  private
@@ -319,8 +319,8 @@ module Sequel
319
319
  end
320
320
 
321
321
  # Typecast the given object to the appropriate type using the
322
- # typecaster. Note that this does not conversion for the members
323
- # of the composite type, since those conversion expect strings and
322
+ # typecaster. Note that this does no conversion for the members
323
+ # of the composite type, since those conversions expect strings and
324
324
  # strings may not be provided.
325
325
  def typecast(obj)
326
326
  case obj
@@ -1,56 +1,37 @@
1
1
  # frozen-string-literal: true
2
2
  #
3
- # The set_literalizer extension allows for using Set instances in many of the
4
- # same places that you would use Array instances:
5
- #
6
- # DB[:table].where(column: Set.new([1, 2, 3]))
7
- # # SELECT FROM table WHERE (column IN (1, 2, 3))
8
- #
9
- # To load the extension into all datasets created from a given Database:
10
- #
11
- # DB.extension :set_literalizer
3
+ # The set_literalizer extension should no longer be used, as Sequel
4
+ # now supports Set values by default. For backwards compatibility
5
+ # the set_literalizer extension will treat a set that contains only
6
+ # 2 element arrays as a condition specifier (matching the behavior
7
+ # for arrays where all elements are 2 element arrays). This is not
8
+ # compatible with Sequel's current default behavior. If you are
9
+ # relying on this behavior, it is recommended you convert the set
10
+ # to an array.
12
11
  #
13
12
  # Related module: Sequel::Dataset::SetLiteralizer
14
13
 
15
- require 'set'
16
-
17
14
  module Sequel
15
+ # SEQUEL6: Remove
16
+ Sequel::Deprecation.deprecate("The set_literalizer extension", "Sequel now supports set literalization by default")
17
+
18
18
  class Dataset
19
19
  module SetLiteralizer
20
- # Try to generate the same SQL for Set instances used in datasets
21
- # that would be used for equivalent Array instances.
22
- def complex_expression_sql_append(sql, op, args)
23
- # Array instances are treated specially by
24
- # Sequel::SQL::BooleanExpression.from_value_pairs. That cannot
25
- # be modified by a dataset extension, so this tries to convert
26
- # the complex expression values generated by default to what would
27
- # be the complex expression values used for the equivalent array.
28
- case op
29
- when :'=', :'!='
30
- if (set = args[1]).is_a?(Set)
31
- op = op == :'=' ? :IN : :'NOT IN'
32
- col = args[0]
33
- array = set.to_a
34
- if Sequel.condition_specifier?(array) && col.is_a?(Array)
35
- array = Sequel.value_list(array)
36
- end
37
- args = [col, array]
38
- end
39
- end
40
-
41
- super
42
- end
43
-
44
20
  private
45
21
 
46
- # Literalize Set instances by converting the set to array.
47
- def literal_other_append(sql, v)
48
- if Set === v
49
- literal_append(sql, v.to_a)
22
+ # Allow using sets as condition specifiers.
23
+ def filter_expr(expr = nil, &block)
24
+ if expr.is_a?(Set)
25
+ expr
50
26
  else
51
27
  super
52
28
  end
53
29
  end
30
+
31
+ # Literalize Set instances by converting the set to array.
32
+ def literal_set_append(sql, v)
33
+ literal_append(sql, v.to_a)
34
+ end
54
35
  end
55
36
 
56
37
  register_extension(:set_literalizer, SetLiteralizer)
@@ -25,6 +25,11 @@
25
25
  # # with split_array_nils extension:
26
26
  # # SELECT * FROM table WHERE ((column NOT IN (1)) AND (column IS NOT NULL)))
27
27
  #
28
+ # In addition to arrays, this splitting is also done for sets:
29
+ #
30
+ # ds = DB[:table].where(column: Set[1, nil])
31
+ # # SELECT * FROM table WHERE ((column IN (1)) OR (column IS NULL)))
32
+ #
28
33
  # To use this extension with a single dataset:
29
34
  #
30
35
  # ds = ds.extension(:split_array_nil)
@@ -47,9 +52,14 @@ module Sequel
47
52
  case op
48
53
  when :IN, :"NOT IN"
49
54
  vals = args[1]
50
- if vals.is_a?(Array) && vals.any?(&:nil?)
55
+ if (vals.is_a?(Array) || vals.is_a?(Set)) && vals.any?(&:nil?)
51
56
  cols = args[0]
52
- vals = vals.compact
57
+ if vals.is_a?(Set)
58
+ vals = vals.dup
59
+ vals.delete(nil)
60
+ else
61
+ vals = vals.compact
62
+ end
53
63
  c = Sequel::SQL::BooleanExpression
54
64
  if op == :IN
55
65
  literal_append(sql, c.new(:OR, c.new(:IN, cols, vals), c.new(:IS, cols, nil)))
@@ -79,6 +79,11 @@ module Sequel
79
79
  e.each_with_index do |val, j|
80
80
  v(val, j)
81
81
  end
82
+ when Set
83
+ dot "Set"
84
+ e.each_with_index do |val, j|
85
+ v(val, j)
86
+ end
82
87
  when Hash
83
88
  dot "Hash"
84
89
  e.each do |k, val|
@@ -302,7 +302,7 @@ module Sequel
302
302
 
303
303
  if strategy == :window_function
304
304
  delete_rn = ds.row_number_column
305
- objects.each{|obj| obj.values.delete(delete_rn)}
305
+ objects.each{|obj| obj.remove_key!(delete_rn)}
306
306
  end
307
307
  elsif strategy == :union
308
308
  objects = []
@@ -1291,11 +1291,11 @@ module Sequel
1291
1291
  name = self[:name]
1292
1292
 
1293
1293
  self[:model].eager_load_results(self, eo) do |assoc_record|
1294
- assoc_record.values.delete(delete_rn) if delete_rn
1294
+ assoc_record.remove_key!(delete_rn) if delete_rn
1295
1295
  hash_key = if uses_lcks
1296
- left_key_alias.map{|k| assoc_record.values.delete(k)}
1296
+ left_key_alias.map{|k| assoc_record.remove_key!(k)}
1297
1297
  else
1298
- assoc_record.values.delete(left_key_alias)
1298
+ assoc_record.remove_key!(left_key_alias)
1299
1299
  end
1300
1300
 
1301
1301
  objects = h[hash_key]
@@ -2387,7 +2387,7 @@ module Sequel
2387
2387
  delete_rn = opts.delete_row_number_column
2388
2388
 
2389
2389
  eager_load_results(opts, eo) do |assoc_record|
2390
- assoc_record.values.delete(delete_rn) if delete_rn
2390
+ assoc_record.remove_key!(delete_rn) if delete_rn
2391
2391
  hash_key = uses_cks ? km.map{|k| assoc_record.get_column_value(k)} : assoc_record.get_column_value(km)
2392
2392
  objects = h[hash_key]
2393
2393
  if assign_singular
@@ -3063,7 +3063,7 @@ module Sequel
3063
3063
  if (((op == :'=' || op == :'!=') && r.is_a?(Sequel::Model)) ||
3064
3064
  (multiple = ((op == :IN || op == :'NOT IN') && ((is_ds = r.is_a?(Sequel::Dataset)) || (r.respond_to?(:all?) && r.all?{|x| x.is_a?(Sequel::Model)})))))
3065
3065
  l = args[0]
3066
- if ar = model.association_reflections[l]
3066
+ if ar = model.association_reflection(l)
3067
3067
  raise Error, "filtering by associations is not allowed for #{ar.inspect}" if ar[:allow_filtering_by] == false
3068
3068
 
3069
3069
  if multiple
@@ -3544,7 +3544,7 @@ module Sequel
3544
3544
  else
3545
3545
  vals = Array(obj).reject{|o| !meths.all?{|m| o.get_column_value(m)}}
3546
3546
  return SQL::Constants::FALSE if vals.empty?
3547
- if obj.is_a?(Array)
3547
+ if obj.is_a?(Array) || obj.is_a?(Set)
3548
3548
  if keys.length == 1
3549
3549
  meth = meths.first
3550
3550
  {keys.first=>vals.map{|o| o.get_column_value(meth)}}
@@ -2,13 +2,15 @@
2
2
 
3
3
  module Sequel
4
4
  class Model
5
+ # SEQUEL6: Remove Enumerable here, and send all Enumerable methods to dataset
6
+ # by default, with a plugin for the current behavior.
5
7
  extend Enumerable
6
8
  extend Inflections
7
9
 
8
10
  # Class methods for Sequel::Model that implement basic model functionality.
9
11
  #
10
12
  # * All of the following methods have class methods created that send the method
11
- # to the model's dataset: all, as_hash, avg, count, cross_join, distinct, each,
13
+ # to the model's dataset: all, any?, as_hash, as_set, avg, count, cross_join, distinct, each,
12
14
  # each_server, empty?, except, exclude, exclude_having, fetch_rows,
13
15
  # filter, first, first!, for_update, from, from_self, full_join, full_outer_join,
14
16
  # get, graph, grep, group, group_and_count, group_append, group_by, having, import,
@@ -17,7 +19,7 @@ module Sequel
17
19
  # natural_join, natural_left_join, natural_right_join, offset, order, order_append, order_by,
18
20
  # order_more, order_prepend, paged_each, qualify, reverse, reverse_order, right_join,
19
21
  # right_outer_join, select, select_all, select_append, select_group, select_hash,
20
- # select_hash_groups, select_map, select_more, select_order_map, select_prepend, server,
22
+ # select_hash_groups, select_map, select_more, select_order_map, select_prepend, select_set, server,
21
23
  # single_record, single_record!, single_value, single_value!, sum, to_hash, to_hash_groups,
22
24
  # truncate, unfiltered, ungraphed, ungrouped, union, unlimited, unordered, where, where_all,
23
25
  # where_each, where_single_value, with, with_recursive, with_sql
@@ -695,7 +697,7 @@ module Sequel
695
697
  end
696
698
 
697
699
  # Add model methods that call dataset methods
698
- Plugins.def_dataset_methods(self, (Dataset::ACTION_METHODS + Dataset::QUERY_METHODS + [:each_server]) - [:<<, :or, :[], :columns, :columns!, :delete, :update, :set_graph_aliases, :add_graph_aliases])
700
+ Plugins.def_dataset_methods(self, (Dataset::ACTION_METHODS + Dataset::QUERY_METHODS + [:any?, :each_server]) - [:<<, :or, :[], :columns, :columns!, :delete, :update, :set_graph_aliases, :add_graph_aliases])
699
701
 
700
702
  private
701
703
 
@@ -1358,17 +1360,16 @@ END
1358
1360
  @values.keys
1359
1361
  end
1360
1362
 
1361
- # Refresh this record using +for_update+ (by default, or the specified style when given)
1363
+ # Refresh this record using +:update+ lock style (by default, or the specified style when given),
1362
1364
  # unless this is a new record. Returns self. This can be used to make sure no other
1363
- # process is updating the record at the same time.
1365
+ # process can modify the record during the transaction containing this call. Using
1366
+ # this method only makes sense inside transactions.
1364
1367
  #
1365
1368
  # If style is a string, it will be used directly. You should never pass a string
1366
1369
  # to this method that is derived from user input, as that can lead to
1367
1370
  # SQL injection.
1368
1371
  #
1369
- # A symbol may be used for database independent locking behavior, but
1370
- # all supported symbols have separate methods (e.g. for_update).
1371
- #
1372
+ # A symbol may be used if the adapter supports that lock style.
1372
1373
  #
1373
1374
  # a = Artist[1]
1374
1375
  # Artist.db.transaction do
@@ -1378,7 +1379,7 @@ END
1378
1379
  #
1379
1380
  # a = Artist[2]
1380
1381
  # Artist.db.transaction do
1381
- # a.lock!('FOR NO KEY UPDATE')
1382
+ # a.lock!(:no_key_update)
1382
1383
  # a.update(name: 'B')
1383
1384
  # end
1384
1385
  def lock!(style=:update)
@@ -1500,6 +1501,20 @@ END
1500
1501
  refresh
1501
1502
  end
1502
1503
 
1504
+ # Remove a key from the instances values, and return the value
1505
+ # of the key.
1506
+ #
1507
+ # a = Album[1]
1508
+ # a.values
1509
+ # # => {id: 1, artist_id: 2}
1510
+ # a.remove_key!(:artist_id)
1511
+ # # => 2
1512
+ # a.values
1513
+ # # => {id: 1}
1514
+ def remove_key!(key)
1515
+ @values.delete(key)
1516
+ end
1517
+
1503
1518
  # Creates or updates the record, after making sure the record
1504
1519
  # is valid and before hooks execute successfully. Fails if:
1505
1520
  #
@@ -2329,5 +2344,17 @@ END
2329
2344
  # :nocov:
2330
2345
  singleton_class.send(:undef_method, :initialize_clone, :initialize_dup)
2331
2346
  end
2347
+
2348
+ # :nocov:
2349
+ if defined?(Sequel::Postgres::SEQUEL_PG_VERSION_INTEGER) && Sequel::Postgres::SEQUEL_PG_VERSION_INTEGER >= 11800
2350
+ # Automatically optimize model loading when sequel/core was loaded,
2351
+ # then sequel/adapters/postgres (with sequel_pg), then sequel/model
2352
+ begin
2353
+ require "sequel_pg/model"
2354
+ rescue LoadError
2355
+ # nothing
2356
+ end
2357
+ end
2358
+ # :nocov:
2332
2359
  end
2333
2360
  end