sequel 5.97.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 (38) 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 +18 -5
  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 +1 -0
  12. data/lib/sequel/dataset/actions.rb +65 -8
  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/pg_auto_parameterize.rb +2 -2
  19. data/lib/sequel/extensions/pg_auto_parameterize_duplicate_query_detection.rb +191 -0
  20. data/lib/sequel/extensions/pg_auto_parameterize_in_array.rb +2 -1
  21. data/lib/sequel/extensions/pg_hstore_ops.rb +10 -5
  22. data/lib/sequel/extensions/pg_json_ops.rb +12 -5
  23. data/lib/sequel/extensions/pg_row.rb +2 -2
  24. data/lib/sequel/extensions/set_literalizer.rb +20 -39
  25. data/lib/sequel/extensions/split_array_nil.rb +12 -2
  26. data/lib/sequel/extensions/to_dot.rb +5 -0
  27. data/lib/sequel/model/associations.rb +6 -6
  28. data/lib/sequel/model/base.rb +31 -3
  29. data/lib/sequel/plugins/insert_returning_select.rb +10 -1
  30. data/lib/sequel/plugins/pg_array_associations.rb +2 -2
  31. data/lib/sequel/plugins/rcte_tree.rb +5 -5
  32. data/lib/sequel/plugins/split_values.rb +10 -0
  33. data/lib/sequel/plugins/static_cache.rb +13 -0
  34. data/lib/sequel/plugins/subset_static_cache.rb +15 -0
  35. data/lib/sequel/plugins/table_select.rb +7 -0
  36. data/lib/sequel/sql.rb +1 -1
  37. data/lib/sequel/version.rb +1 -1
  38. metadata +2 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 236cf31a3c7848a6699dc871558a387748593a83dda8a722aad2e1c6beaad2d9
4
- data.tar.gz: 974c8617fcecc74f10fab9c7fa8dd9e2f74a7a56767cf47cafd95e1108cc5f57
3
+ metadata.gz: 7e5d22a32d8c5f27e15e00203d0c2ce2a2fb3f5352d7b7c25c82389f4947882c
4
+ data.tar.gz: 81909dc070cc8d48def396ae76e953a1f31dc478b45d3a5fa8b69646bc7df094
5
5
  SHA512:
6
- metadata.gz: 5b396689d99af2a0882358e80150dc452b96a9cd09a73a9cdace42b903dd0958d341bf67e0570bd382dd051df54dc66b2d2b3e396506518c619d08bd006ebd1b
7
- data.tar.gz: '0839e0177ccbb9d67880835b92e0b8767ea339b96375da914d0471a2cc24f25cdffbd8ada3b45ba69bdfc167a4609ed44fde1a7fd1db4c7574dff3f2efbfd80f'
6
+ metadata.gz: c24ae48ece66511a09b2d8a5ad8b5b82e7486a4446bcb66108140b8afe449cd9c9e7645a29ca0bbe0c994d5c060a52f1ab4c41434dc068175751b1d6bd57b5dc
7
+ data.tar.gz: e8acd2e5cb4272bd47aed87e3077018dfc5ba9673856b871c8222eec6b2de1be0843840969e046efc459b52902753e9e0f0e35b95dcdcf31914d98b5f487d78a
@@ -62,11 +62,26 @@ module Sequel
62
62
 
63
63
  # Mimic the file:// uri, by having 2 preceding slashes specify a relative
64
64
  # path, and 3 preceding slashes specify an absolute path.
65
+ # Also support no preceding slashes to specify a relative path.
65
66
  def self.uri_to_options(uri) # :nodoc:
66
- { :database => (uri.host.nil? && uri.path == '/') ? nil : "#{uri.host}#{uri.path}" }
67
+ database = if uri.host.nil?
68
+ case uri.path
69
+ when '/'
70
+ nil
71
+ when nil
72
+ uri.opaque
73
+ else
74
+ uri.path
75
+ end
76
+ else
77
+ "#{uri.host}#{uri.path}"
78
+ end
79
+
80
+ { :database => database }
67
81
  end
68
82
  private_class_method :uri_to_options
69
83
 
84
+
70
85
  # Connect to the database. Since SQLite is a file based database,
71
86
  # the only options available are :database (to specify the database
72
87
  # name), and :timeout, to specify how long to wait for the database to
@@ -516,6 +516,7 @@ module Sequel
516
516
  end
517
517
  ensure
518
518
  conn.execute("UNLISTEN *")
519
+ true while conn.wait_for_notify(0)
519
520
  end
520
521
  end
521
522
  end
@@ -679,7 +679,7 @@ module Sequel
679
679
 
680
680
  # MSSQL uses the CONTAINS keyword for full text search
681
681
  def full_text_search(cols, terms, opts = OPTS)
682
- terms = "\"#{terms.join('" OR "')}\"" if terms.is_a?(Array)
682
+ terms = "\"#{Sequel.array_or_set_join(terms, '" OR "')}\"" if terms.is_a?(Array) || terms.is_a?(Set)
683
683
  where(Sequel.lit("CONTAINS (?, ?)", cols, terms))
684
684
  end
685
685
 
@@ -797,7 +797,7 @@ module Sequel
797
797
 
798
798
  # MySQL specific full text search syntax.
799
799
  def full_text_sql(cols, terms, opts = OPTS)
800
- terms = terms.join(' ') if terms.is_a?(Array)
800
+ terms = Sequel.array_or_set_join(terms, ' ') if terms.is_a?(Array) || terms.is_a?(Set)
801
801
  SQL::PlaceholderLiteralString.new((opts[:boolean] ? MATCH_AGAINST_BOOLEAN : MATCH_AGAINST), [Array(cols), terms])
802
802
  end
803
803
 
@@ -669,12 +669,26 @@ module Sequel
669
669
  _set_constraints(' IMMEDIATE', opts)
670
670
  end
671
671
 
672
- # Use the pg_* system tables to determine indexes on a table
672
+ # Use the pg_* system tables to determine indexes on a table. Options:
673
+ #
674
+ # :include_partial :: Set to true to include partial indexes
675
+ # :invalid :: Set to true or :only to only return invalid indexes.
676
+ # Set to :include to also return both valid and invalid indexes.
677
+ # When not set or other value given, does not return invalid indexes.
673
678
  def indexes(table, opts=OPTS)
674
679
  m = output_identifier_meth
675
680
  cond = {Sequel[:tab][:oid]=>regclass_oid(table, opts)}
676
681
  cond[:indpred] = nil unless opts[:include_partial]
677
682
 
683
+ case opts[:invalid]
684
+ when true, :only
685
+ cond[:indisvalid] = false
686
+ when :include
687
+ # nothing
688
+ else
689
+ cond[:indisvalid] = true
690
+ end
691
+
678
692
  indexes = {}
679
693
  _indexes_ds.where_each(cond) do |r|
680
694
  i = indexes[m.call(r[:name])] ||= {:columns=>[], :unique=>r[:unique], :deferrable=>r[:deferrable]}
@@ -1049,8 +1063,7 @@ module Sequel
1049
1063
  where{{
1050
1064
  indc[:relkind]=>%w'i I',
1051
1065
  ind[:indisprimary]=>false,
1052
- :indexprs=>nil,
1053
- :indisvalid=>true}}.
1066
+ :indexprs=>nil}}.
1054
1067
  order(*order).
1055
1068
  select{[indc[:relname].as(:name), ind[:indisunique].as(:unique), att[:attname].as(:column), con[:condeferrable].as(:deferrable)]}
1056
1069
 
@@ -1757,7 +1770,7 @@ module Sequel
1757
1770
  index_type = :gist
1758
1771
  end
1759
1772
 
1760
- "CREATE #{unique}INDEX#{' CONCURRENTLY' if index[:concurrently]}#{if_not_exists} #{quote_identifier(index_name)} ON #{quote_schema_table(table_name)} #{"USING #{index_type} " if index_type}#{expr}#{" INCLUDE #{literal(Array(index[:include]))}" if index[:include]}#{nulls_distinct}#{" TABLESPACE #{quote_identifier(index[:tablespace])}" if index[:tablespace]}#{filter}"
1773
+ "CREATE #{unique}INDEX#{' CONCURRENTLY' if index[:concurrently]}#{if_not_exists} #{quote_identifier(index_name)} ON#{' ONLY' if index[:only]} #{quote_schema_table(table_name)} #{"USING #{index_type} " if index_type}#{expr}#{" INCLUDE #{literal(Array(index[:include]))}" if index[:include]}#{nulls_distinct}#{" TABLESPACE #{quote_identifier(index[:tablespace])}" if index[:tablespace]}#{filter}"
1761
1774
  end
1762
1775
 
1763
1776
  # Setup datastructures shared by all postgres adapters.
@@ -2160,7 +2173,7 @@ module Sequel
2160
2173
  end
2161
2174
 
2162
2175
  unless opts[:tsquery]
2163
- phrase_terms = terms.is_a?(Array) ? terms.join(' | ') : terms
2176
+ phrase_terms = terms.is_a?(Array) || terms.is_a?(Set) ? Sequel.array_or_set_join(terms, ' | ') : terms
2164
2177
 
2165
2178
  query_func = case to_tsquery = opts[:to_tsquery]
2166
2179
  when :phrase, :plain
@@ -89,8 +89,22 @@ module Sequel
89
89
 
90
90
  # Mimic the file:// uri, by having 2 preceding slashes specify a relative
91
91
  # path, and 3 preceding slashes specify an absolute path.
92
+ # Also support no preceding slashes to specify a relative path.
92
93
  def self.uri_to_options(uri) # :nodoc:
93
- { :database => (uri.host.nil? && uri.path == '/') ? nil : "#{uri.host}#{uri.path}" }
94
+ database = if uri.host.nil?
95
+ case uri.path
96
+ when '/'
97
+ nil
98
+ when nil
99
+ uri.opaque
100
+ else
101
+ uri.path
102
+ end
103
+ else
104
+ "#{uri.host}#{uri.path}"
105
+ end
106
+
107
+ { :database => database }
94
108
  end
95
109
 
96
110
  private_class_method :uri_to_options
@@ -26,6 +26,8 @@ module Sequel
26
26
  h = {}
27
27
  o.each{|k, val| h[v(k)] = v(val)}
28
28
  h
29
+ when Set
30
+ Set.new(o.map{|x| v(x)})
29
31
  when SQL::NumericExpression
30
32
  if o.op == :extract
31
33
  o.class.new(o.op, o.args[0], v(o.args[1]))
data/lib/sequel/core.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen-string-literal: true
2
2
 
3
- %w'bigdecimal date thread time uri'.each{|f| require f}
3
+ %w'bigdecimal date set thread time uri'.each{|f| require f}
4
4
 
5
5
  # Top level module for Sequel
6
6
  #
@@ -164,6 +164,20 @@ module Sequel
164
164
  JSON::ParserError
165
165
  end
166
166
 
167
+ if RUBY_VERSION >= '3'
168
+ # Join the array or set.
169
+ def array_or_set_join(obj, arg)
170
+ obj.join(arg)
171
+ end
172
+ # :nocov:
173
+ else
174
+ def array_or_set_join(obj, arg)
175
+ obj = obj.to_a if obj.is_a?(Set)
176
+ obj.join(arg)
177
+ end
178
+ end
179
+ # :nocov:
180
+
167
181
  if RUBY_VERSION >= '3.3'
168
182
  # Create a new module using the block, and set the temporary name
169
183
  # on it using the given a containing module and name.
@@ -635,7 +635,7 @@ module Sequel
635
635
  # Typecast the value to a String
636
636
  def typecast_value_string(value)
637
637
  case value
638
- when Hash, Array
638
+ when Hash, Array, Set
639
639
  raise Sequel::InvalidValue, "invalid value for String: #{value.inspect}"
640
640
  else
641
641
  value.to_s
@@ -299,6 +299,7 @@ module Sequel
299
299
  # :opclass :: Set an opclass to use for all columns (per-column opclasses require
300
300
  # custom SQL).
301
301
  # :tablespace :: Specify tablespace for index.
302
+ # :only :: Create index only for given table, not for any child tables (PostgreSQL 11+)
302
303
  #
303
304
  # Microsoft SQL Server specific options:
304
305
  #
@@ -11,11 +11,12 @@ module Sequel
11
11
 
12
12
  # Action methods defined by Sequel that execute code on the database.
13
13
  ACTION_METHODS = (<<-METHS).split.map(&:to_sym).freeze
14
- << [] all as_hash avg count columns columns! delete each
14
+ << [] all as_hash as_set avg count columns columns! delete each
15
15
  empty? fetch_rows first first! get import insert last
16
- map max min multi_insert paged_each select_hash select_hash_groups select_map select_order_map
17
- single_record single_record! single_value single_value! sum to_hash to_hash_groups truncate update
18
- where_all where_each where_single_value
16
+ map max min multi_insert paged_each select_hash select_hash_groups
17
+ select_map select_order_map select_set single_record single_record!
18
+ single_value single_value! sum to_hash to_hash_groups
19
+ truncate update where_all where_each where_single_value
19
20
  METHS
20
21
 
21
22
  # The clone options to use when retrieving columns for a dataset.
@@ -51,6 +52,26 @@ module Sequel
51
52
  _all(block){|a| each{|r| a << r}}
52
53
  end
53
54
 
55
+ # Returns sets for column values for each record in the dataset.
56
+ #
57
+ # DB[:table].as_set(:id) # SELECT * FROM table
58
+ # # => Set[1, 2, 3, ...]
59
+ #
60
+ # You can also provide an array of column names, in which case the elements
61
+ # of the returned set are arrays (not sets):
62
+ #
63
+ # DB[:table].as_set([:id, :name]) # SELECT * FROM table
64
+ # # => Set[[1, 'A'], [2, 'B'], [3, 'C'], ...]
65
+ def as_set(column)
66
+ return naked.as_set(column) if row_proc
67
+
68
+ if column.is_a?(Array)
69
+ to_set{|r| r.values_at(*column)}
70
+ else
71
+ to_set{|r| r[column]}
72
+ end
73
+ end
74
+
54
75
  # Returns the average value for the given column/expression.
55
76
  # Uses a virtual row block if no argument is given.
56
77
  #
@@ -727,10 +748,7 @@ module Sequel
727
748
  end
728
749
 
729
750
  # Selects the column given (either as an argument or as a block), and
730
- # returns an array of all values of that column in the dataset. If you
731
- # give a block argument that returns an array with multiple entries,
732
- # the contents of the resulting array are undefined. Raises an Error
733
- # if called with both an argument and a block.
751
+ # returns an array of all values of that column in the dataset.
734
752
  #
735
753
  # DB[:table].select_map(:id) # SELECT id FROM table
736
754
  # # => [3, 5, 8, 1, ...]
@@ -768,6 +786,34 @@ module Sequel
768
786
  _select_map(column, true, &block)
769
787
  end
770
788
 
789
+ # Selects the column given (either as an argument or as a block), and
790
+ # returns a set of all values of that column in the dataset.
791
+ #
792
+ # DB[:table].select_set(:id) # SELECT id FROM table
793
+ # # => Set[3, 5, 8, 1, ...]
794
+ #
795
+ # DB[:table].select_set{id * 2} # SELECT (id * 2) FROM table
796
+ # # => Set[6, 10, 16, 2, ...]
797
+ #
798
+ # You can also provide an array of column names, which returns a set
799
+ # with array elements (not set elements):
800
+ #
801
+ # DB[:table].select_map([:id, :name]) # SELECT id, name FROM table
802
+ # # => Set[[1, 'A'], [2, 'B'], [3, 'C'], ...]
803
+ #
804
+ # If you provide an array of expressions, you must be sure that each entry
805
+ # in the array has an alias that Sequel can determine.
806
+ def select_set(column=nil, &block)
807
+ ds = ungraphed.naked
808
+ columns = Array(column)
809
+ virtual_row_columns(columns, block)
810
+ if column.is_a?(Array) || (columns.length > 1)
811
+ ds.select(*columns)._select_set_multiple(hash_key_symbols(columns))
812
+ else
813
+ ds.select(auto_alias_expression(columns.first))._select_set_single
814
+ end
815
+ end
816
+
771
817
  # Limits the dataset to one record, and returns the first record in the dataset,
772
818
  # or nil if the dataset has no records. Users should probably use +first+ instead of
773
819
  # this method. Example:
@@ -1092,6 +1138,17 @@ module Sequel
1092
1138
  map{|r| r[k||=r.keys.first]}
1093
1139
  end
1094
1140
 
1141
+ # Return a set of arrays of values given by the symbols in ret_cols.
1142
+ def _select_set_multiple(ret_cols)
1143
+ to_set{|r| r.values_at(*ret_cols)}
1144
+ end
1145
+
1146
+ # Returns a set of the first value in each row.
1147
+ def _select_set_single
1148
+ k = nil
1149
+ to_set{|r| r[k||=r.keys.first]}
1150
+ end
1151
+
1095
1152
  # A dataset for returning single values from the current dataset.
1096
1153
  def single_value_ds
1097
1154
  clone(:limit=>1).ungraphed.naked
@@ -1496,7 +1496,7 @@ module Sequel
1496
1496
  end
1497
1497
  when LiteralString
1498
1498
  LiteralString.new("(#{expr})")
1499
- when Numeric, SQL::NumericExpression, SQL::StringExpression, Proc, String
1499
+ when Numeric, SQL::NumericExpression, SQL::StringExpression, Proc, String, Set
1500
1500
  raise Error, "Invalid filter expression: #{expr.inspect}"
1501
1501
  when TrueClass, FalseClass
1502
1502
  if supports_where_true?
@@ -85,6 +85,8 @@ module Sequel
85
85
  literal_date_append(sql, v)
86
86
  when Dataset
87
87
  literal_dataset_append(sql, v)
88
+ when Set
89
+ literal_set_append(sql, v)
88
90
  else
89
91
  literal_other_append(sql, v)
90
92
  end
@@ -375,9 +377,9 @@ module Sequel
375
377
  cols = args[0]
376
378
  vals = args[1]
377
379
  col_array = true if cols.is_a?(Array)
378
- if vals.is_a?(Array)
380
+ if vals.is_a?(Array) || vals.is_a?(Set)
379
381
  val_array = true
380
- empty_val_array = vals == []
382
+ empty_val_array = vals.empty?
381
383
  end
382
384
  if empty_val_array
383
385
  literal_append(sql, empty_array_value(op, cols))
@@ -1448,6 +1450,12 @@ module Sequel
1448
1450
  end
1449
1451
  end
1450
1452
 
1453
+ # Append a literalization of the set to SQL string.
1454
+ # Treats as an expression as an SQL value list.
1455
+ def literal_set_append(sql, v)
1456
+ array_sql_append(sql, v)
1457
+ end
1458
+
1451
1459
  # SQL fragment for Sequel::SQLTime, containing just the time part
1452
1460
  def literal_sqltime(v)
1453
1461
  v.strftime(default_time_format)
@@ -436,7 +436,7 @@ module Sequel
436
436
  else
437
437
  raise Error, "validates includes with a range only supports integers currently, cannot handle: #{arg.inspect}"
438
438
  end
439
- elsif arg.is_a?(Array)
439
+ elsif arg.is_a?(Array) || arg.is_a?(Set)
440
440
  if arg.all?{|x| x.is_a?(Integer)}
441
441
  validation_type = :includes_int_array
442
442
  elsif arg.all?{|x| x.is_a?(String)}
@@ -444,9 +444,9 @@ module Sequel
444
444
  else
445
445
  raise Error, "validates includes with an array only supports strings and integers currently, cannot handle: #{arg.inspect}"
446
446
  end
447
- arg = arg.join(',')
447
+ arg = Sequel.array_or_set_join(arg, ',')
448
448
  else
449
- raise Error, "validates includes only supports arrays and ranges currently, cannot handle: #{arg.inspect}"
449
+ raise Error, "validates includes only supports arrays, sets, and ranges currently, cannot handle: #{arg.inspect}"
450
450
  end
451
451
  when :like, :ilike
452
452
  generator.constraint(constraint, constraint_validation_expression(columns, allow_nil){|c| Sequel.public_send(validation_type, c, arg)})
@@ -9,6 +9,13 @@
9
9
  # DB[:test].exclude(name: [])
10
10
  # # SELECT * FROM test WHERE (name = name)
11
11
  #
12
+ # This works for sets in addition to arrays:
13
+ #
14
+ # DB[:test].where(name: Set[])
15
+ # # SELECT * FROM test WHERE (name != name)
16
+ # DB[:test].exclude(name: Set[])
17
+ # # SELECT * FROM test WHERE (name = name)
18
+ #
12
19
  # The default Sequel behavior is to ignore NULLs, as the above
13
20
  # query is not generally optimized well by databases.
14
21
  #
@@ -34,6 +34,8 @@ module Sequel
34
34
  "#{obj.class}.new(#{obj.to_a.inspect})"
35
35
  when Array
36
36
  "[#{obj.map{|o| eval_inspect(o)}.join(', ')}]"
37
+ when Set
38
+ "Set[#{obj.map{|o| eval_inspect(o)}.join(', ')}]"
37
39
  when Hash
38
40
  "{#{obj.map{|k, v| "#{eval_inspect(k)} => #{eval_inspect(v)}"}.join(', ')}}"
39
41
  when Time
@@ -408,9 +408,9 @@ module Sequel
408
408
  end
409
409
  end
410
410
 
411
- # Whether the given argument is an array of integers or NULL values, recursively.
411
+ # Whether the given argument is an array or set of integers or NULL values, recursively.
412
412
  def _integer_array?(v)
413
- Array === v && v.all?{|x| nil == x || Integer === x}
413
+ (Array === v || Set === v) && v.all?{|x| nil == x || Integer === x}
414
414
  end
415
415
 
416
416
  # Create the bound variable string that will be used for the IN (int, ...) to = ANY($)
@@ -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
@@ -841,13 +841,20 @@ module Sequel
841
841
  Sequel::SQL::BooleanExpression.new(:NOOP, Sequel::SQL::PlaceholderLiteralString.new(str, [value, other]))
842
842
  end
843
843
 
844
- # Wrap argument in a PGArray if it is an array
844
+ # Wrap argument in a PGArray if it is an array or a set
845
845
  def wrap_input_array(obj)
846
- if obj.is_a?(Array) && Sequel.respond_to?(:pg_array)
847
- Sequel.pg_array(obj)
848
- else
849
- 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
850
855
  end
856
+
857
+ obj
851
858
  end
852
859
 
853
860
  # Wrap argument in a JSONBArray or JSONBHash if it is an array or hash.
@@ -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
@@ -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
 
@@ -1499,6 +1501,20 @@ END
1499
1501
  refresh
1500
1502
  end
1501
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
+
1502
1518
  # Creates or updates the record, after making sure the record
1503
1519
  # is valid and before hooks execute successfully. Fails if:
1504
1520
  #
@@ -2328,5 +2344,17 @@ END
2328
2344
  # :nocov:
2329
2345
  singleton_class.send(:undef_method, :initialize_clone, :initialize_dup)
2330
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:
2331
2359
  end
2332
2360
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Sequel
4
4
  module Plugins
5
- # If the model's dataset selects explicit columns and the
5
+ # If the model's dataset selects explicit columns (or table.*) and the
6
6
  # database supports it, the insert_returning_select plugin will
7
7
  # automatically set the RETURNING clause on the dataset used to
8
8
  # insert rows to the columns selected, which allows the default model
@@ -45,6 +45,9 @@ module Sequel
45
45
  ret
46
46
  end
47
47
 
48
+ RETURN_ALL = [Sequel::Dataset::WILDCARD].freeze
49
+ private_constant :RETURN_ALL
50
+
48
51
  # Determine the columns to use for the returning clause, or return nil
49
52
  # if they can't be determined and a returning clause should not be
50
53
  # added automatically.
@@ -52,6 +55,12 @@ module Sequel
52
55
  return unless ds.supports_returning?(:insert)
53
56
  return unless values = ds.opts[:select]
54
57
 
58
+ # SELECT table.* -> RETURNING *
59
+ if values.length == 1 && values[0].is_a?(Sequel::SQL::ColumnAll)
60
+ return RETURN_ALL
61
+ end
62
+
63
+ # SELECT column1, table.column2, ... -> RETURNING column1, column2, ...
55
64
  values = values.map{|v| ds.unqualified_column_for(v)}
56
65
  if values.all?
57
66
  values
@@ -541,7 +541,7 @@ module Sequel
541
541
  if (assoc_pks = obj.get_column_value(key)) && !assoc_pks.empty?
542
542
  Sequel[pk=>assoc_pks.to_a]
543
543
  end
544
- when Array
544
+ when Array, Set
545
545
  if (assoc_pks = obj.map{|o| o.get_column_value(key)}.flatten.compact.uniq) && !assoc_pks.empty?
546
546
  Sequel[pk=>assoc_pks]
547
547
  end
@@ -563,7 +563,7 @@ module Sequel
563
563
  if pkv = obj.get_column_value(ref.primary_key_method)
564
564
  Sequel.pg_array_op(key).contains(Sequel.pg_array([pkv], ref.array_type))
565
565
  end
566
- when Array
566
+ when Array, Set
567
567
  if (pkvs = obj.map{|o| o.get_column_value(ref.primary_key_method)}.compact) && !pkvs.empty?
568
568
  Sequel.pg_array(key).overlaps(Sequel.pg_array(pkvs, ref.array_type))
569
569
  end
@@ -111,7 +111,7 @@ module Sequel
111
111
  ancestor_base_case_columns = prkey_array.zip(key_aliases).map{|k, ka_| SQL::AliasedExpression.new(k, ka_)} + c_all
112
112
  descendant_base_case_columns = key_array.zip(key_aliases).map{|k, ka_| SQL::AliasedExpression.new(k, ka_)} + c_all
113
113
  recursive_case_columns = prkey_array.zip(key_aliases).map{|k, ka_| SQL::QualifiedIdentifier.new(t, ka_)} + c_all
114
- extract_key_alias = lambda{|m| key_aliases.map{|ka_| bd_conv[m.values.delete(ka_)]}}
114
+ extract_key_alias = lambda{|m| key_aliases.map{|ka_| bd_conv[m.remove_key!(ka_)]}}
115
115
  else
116
116
  key_present = key_conv = lambda{|m| m[key]}
117
117
  prkey_conv = lambda{|m| m[prkey]}
@@ -119,7 +119,7 @@ module Sequel
119
119
  ancestor_base_case_columns = [SQL::AliasedExpression.new(prkey, ka)] + c_all
120
120
  descendant_base_case_columns = [SQL::AliasedExpression.new(key, ka)] + c_all
121
121
  recursive_case_columns = [SQL::QualifiedIdentifier.new(t, ka)] + c_all
122
- extract_key_alias = lambda{|m| bd_conv[m.values.delete(ka)]}
122
+ extract_key_alias = lambda{|m| bd_conv[m.remove_key!(ka)]}
123
123
  end
124
124
 
125
125
  parent = opts.merge(opts.fetch(:parent, OPTS)).fetch(:name, :parent)
@@ -200,7 +200,7 @@ module Sequel
200
200
  model.eager_load_results(r, eo.merge(:loader=>false, :initialize_rows=>false, :dataset=>ds, :id_map=>nil)) do |obj|
201
201
  opk = prkey_conv[obj]
202
202
  if idm_obj = parent_map[opk]
203
- key_aliases.each{|ka_| idm_obj.values[ka_] = obj.values[ka_]}
203
+ key_aliases.each{|ka_| idm_obj[ka_] = obj[ka_]}
204
204
  obj = idm_obj
205
205
  else
206
206
  obj.associations[parent] = nil
@@ -307,12 +307,12 @@ module Sequel
307
307
  ds = ds.select_append(ka) unless ds.opts[:select] == nil
308
308
  model.eager_load_results(r, eo.merge(:loader=>false, :initialize_rows=>false, :dataset=>ds, :id_map=>nil, :associations=>OPTS)) do |obj|
309
309
  if level
310
- no_cache = no_cache_level == obj.values.delete(la)
310
+ no_cache = no_cache_level == obj.remove_key!(la)
311
311
  end
312
312
 
313
313
  opk = prkey_conv[obj]
314
314
  if idm_obj = parent_map[opk]
315
- key_aliases.each{|ka_| idm_obj.values[ka_] = obj.values[ka_]}
315
+ key_aliases.each{|ka_| idm_obj[ka_] = obj[ka_]}
316
316
  obj = idm_obj
317
317
  else
318
318
  obj.associations[childrena] = [] unless no_cache
@@ -54,6 +54,16 @@ module Sequel
54
54
  end
55
55
  end
56
56
 
57
+ # Remove the key from noncolumn values if it is present there. If it is not
58
+ # present there, then use the default behavior of removing it from values.
59
+ def remove_key!(key)
60
+ if @noncolumn_values && @noncolumn_values.key?(key)
61
+ @noncolumn_values.delete(key)
62
+ else
63
+ super
64
+ end
65
+ end
66
+
57
67
  # Check all entries in the values hash. If any of the keys are not columns,
58
68
  # move the entry into the noncolumn_values hash.
59
69
  def split_noncolumn_values
@@ -176,6 +176,19 @@ module Sequel
176
176
  h
177
177
  end
178
178
 
179
+ # Use the cache instead of a query to get the results.
180
+ def as_set(column)
181
+ set = Set.new
182
+
183
+ if column.is_a?(Array)
184
+ @all.each{|r| set.add(r.values.values_at(*column))}
185
+ else
186
+ @all.each{|r| set.add(r[column])}
187
+ end
188
+
189
+ set
190
+ end
191
+
179
192
  # Alias of as_hash for backwards compatibility.
180
193
  def to_hash(*a)
181
194
  as_hash(*a)
@@ -226,6 +226,21 @@ module Sequel
226
226
  h
227
227
  end
228
228
 
229
+ # Use the cache instead of a query to get the results.
230
+ def as_set(column)
231
+ return super unless all = @cache[:subset_static_cache_all]
232
+
233
+ set = Set.new
234
+
235
+ if column.is_a?(Array)
236
+ all.each{|r| set.add(r.values.values_at(*column))}
237
+ else
238
+ all.each{|r| set.add(r[column])}
239
+ end
240
+
241
+ set
242
+ end
243
+
229
244
  # Alias of as_hash for backwards compatibility.
230
245
  def to_hash(*a)
231
246
  as_hash(*a)
@@ -9,6 +9,13 @@ module Sequel
9
9
  # in the result sets (and possibly overwrite columns in the
10
10
  # current model with the same name).
11
11
  #
12
+ # Note that by default on databases that supporting RETURNING,
13
+ # using this plugin will cause instance creations
14
+ # to use two queries (insert and refresh) instead of a single
15
+ # query using RETURNING. You can use the insert_returning_select
16
+ # plugin to automatically use RETURNING for instance creations
17
+ # for models using this plugin.
18
+ #
12
19
  # Usage:
13
20
  #
14
21
  # # Make all model subclasses select table.*
data/lib/sequel/sql.rb CHANGED
@@ -1108,7 +1108,7 @@ module Sequel
1108
1108
  else
1109
1109
  new(:'=', 1, 1)
1110
1110
  end
1111
- when ::Array
1111
+ when ::Array, ::Set
1112
1112
  r = r.dup.freeze unless r.frozen?
1113
1113
  new(:IN, l, r)
1114
1114
  when ::String
@@ -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 = 97
9
+ MINOR = 99
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,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sequel
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.97.0
4
+ version: 5.99.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
@@ -250,6 +250,7 @@ files:
250
250
  - lib/sequel/extensions/pg_array.rb
251
251
  - lib/sequel/extensions/pg_array_ops.rb
252
252
  - lib/sequel/extensions/pg_auto_parameterize.rb
253
+ - lib/sequel/extensions/pg_auto_parameterize_duplicate_query_detection.rb
253
254
  - lib/sequel/extensions/pg_auto_parameterize_in_array.rb
254
255
  - lib/sequel/extensions/pg_enum.rb
255
256
  - lib/sequel/extensions/pg_extended_date_support.rb