sequel 5.24.0 → 5.29.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +58 -0
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +1 -1
  5. data/doc/cheat_sheet.rdoc +1 -0
  6. data/doc/postgresql.rdoc +2 -2
  7. data/doc/release_notes/5.25.0.txt +32 -0
  8. data/doc/release_notes/5.26.0.txt +35 -0
  9. data/doc/release_notes/5.27.0.txt +21 -0
  10. data/doc/release_notes/5.28.0.txt +16 -0
  11. data/doc/release_notes/5.29.0.txt +22 -0
  12. data/doc/testing.rdoc +11 -6
  13. data/lib/sequel/adapters/jdbc/postgresql.rb +6 -0
  14. data/lib/sequel/adapters/postgres.rb +5 -1
  15. data/lib/sequel/adapters/shared/mssql.rb +4 -2
  16. data/lib/sequel/adapters/shared/mysql.rb +1 -1
  17. data/lib/sequel/adapters/shared/postgres.rb +15 -0
  18. data/lib/sequel/adapters/shared/sqlite.rb +7 -2
  19. data/lib/sequel/adapters/tinytds.rb +1 -1
  20. data/lib/sequel/adapters/utils/mysql_mysql2.rb +1 -1
  21. data/lib/sequel/database/schema_generator.rb +1 -1
  22. data/lib/sequel/database/transactions.rb +3 -3
  23. data/lib/sequel/dataset/features.rb +6 -0
  24. data/lib/sequel/dataset/misc.rb +2 -2
  25. data/lib/sequel/dataset/query.rb +15 -2
  26. data/lib/sequel/dataset/sql.rb +17 -4
  27. data/lib/sequel/extensions/any_not_empty.rb +45 -0
  28. data/lib/sequel/extensions/exclude_or_null.rb +68 -0
  29. data/lib/sequel/extensions/pg_array_ops.rb +10 -6
  30. data/lib/sequel/extensions/pg_enum.rb +4 -1
  31. data/lib/sequel/extensions/pg_json.rb +1 -1
  32. data/lib/sequel/extensions/pg_json_ops.rb +124 -0
  33. data/lib/sequel/extensions/pg_range.rb +9 -0
  34. data/lib/sequel/extensions/sql_comments.rb +2 -2
  35. data/lib/sequel/model/base.rb +12 -5
  36. data/lib/sequel/plugins/association_multi_add_remove.rb +83 -0
  37. data/lib/sequel/plugins/caching.rb +3 -0
  38. data/lib/sequel/plugins/csv_serializer.rb +26 -9
  39. data/lib/sequel/plugins/dirty.rb +3 -9
  40. data/lib/sequel/plugins/empty_failure_backtraces.rb +38 -0
  41. data/lib/sequel/plugins/json_serializer.rb +15 -4
  42. data/lib/sequel/plugins/nested_attributes.rb +7 -0
  43. data/lib/sequel/plugins/sharding.rb +11 -5
  44. data/lib/sequel/plugins/throw_failures.rb +1 -1
  45. data/lib/sequel/plugins/typecast_on_load.rb +3 -2
  46. data/lib/sequel/sql.rb +4 -1
  47. data/lib/sequel/version.rb +1 -1
  48. data/spec/adapters/postgres_spec.rb +82 -17
  49. data/spec/adapters/sqlite_spec.rb +1 -1
  50. data/spec/bin_spec.rb +1 -1
  51. data/spec/core/database_spec.rb +1 -1
  52. data/spec/core/dataset_spec.rb +0 -3
  53. data/spec/core/expression_filters_spec.rb +26 -7
  54. data/spec/core/spec_helper.rb +1 -1
  55. data/spec/core_extensions_spec.rb +1 -1
  56. data/spec/extensions/any_not_empty_spec.rb +23 -0
  57. data/spec/extensions/association_multi_add_remove_spec.rb +1041 -0
  58. data/spec/extensions/caller_logging_spec.rb +1 -1
  59. data/spec/extensions/dirty_spec.rb +33 -0
  60. data/spec/extensions/empty_failure_backtraces_spec.rb +60 -0
  61. data/spec/extensions/exclude_or_null_spec.rb +15 -0
  62. data/spec/extensions/json_serializer_spec.rb +10 -0
  63. data/spec/extensions/named_timezones_spec.rb +5 -5
  64. data/spec/extensions/nested_attributes_spec.rb +48 -0
  65. data/spec/extensions/pg_array_ops_spec.rb +3 -3
  66. data/spec/extensions/pg_json_ops_spec.rb +67 -0
  67. data/spec/extensions/pg_range_spec.rb +35 -21
  68. data/spec/extensions/sharding_spec.rb +8 -0
  69. data/spec/extensions/spec_helper.rb +1 -1
  70. data/spec/guards_helper.rb +1 -1
  71. data/spec/integration/associations_test.rb +1 -1
  72. data/spec/integration/dataset_test.rb +57 -17
  73. data/spec/integration/plugin_test.rb +1 -1
  74. data/spec/integration/schema_test.rb +9 -0
  75. data/spec/integration/spec_helper.rb +7 -1
  76. data/spec/model/plugins_spec.rb +2 -2
  77. data/spec/model/spec_helper.rb +1 -1
  78. data/spec/sequel_warning.rb +1 -0
  79. metadata +35 -3
@@ -1069,7 +1069,7 @@ module Sequel
1069
1069
  @new = true
1070
1070
  @modified = true
1071
1071
  initialize_set(values)
1072
- _changed_columns.clear
1072
+ _clear_changed_columns(:initialize)
1073
1073
  yield self if block_given?
1074
1074
  end
1075
1075
 
@@ -1626,6 +1626,13 @@ module Sequel
1626
1626
  def _changed_columns
1627
1627
  @changed_columns ||= []
1628
1628
  end
1629
+
1630
+ # Clear the changed columns. Reason is the reason for clearing
1631
+ # the columns, and should be one of: :initialize, :refresh, :create
1632
+ # or :update.
1633
+ def _clear_changed_columns(_reason)
1634
+ _changed_columns.clear
1635
+ end
1629
1636
 
1630
1637
  # Do the deletion of the object's dataset, and check that the row
1631
1638
  # was actually deleted.
@@ -1716,7 +1723,7 @@ module Sequel
1716
1723
  # is used for reading newly inserted values from the database
1717
1724
  def _refresh(dataset)
1718
1725
  _refresh_set_values(_refresh_get(dataset) || raise(NoExistingObject, "Record not found"))
1719
- _changed_columns.clear
1726
+ _clear_changed_columns(:refresh)
1720
1727
  end
1721
1728
 
1722
1729
  # Get the row of column data from the database.
@@ -1754,7 +1761,7 @@ module Sequel
1754
1761
  @this = nil
1755
1762
  @new = false
1756
1763
  @modified = false
1757
- pk ? _save_refresh : _changed_columns.clear
1764
+ pk ? _save_refresh : _clear_changed_columns(:create)
1758
1765
  after_create
1759
1766
  true
1760
1767
  end
@@ -1771,7 +1778,7 @@ module Sequel
1771
1778
  cc.clear
1772
1779
  else
1773
1780
  columns_updated = _save_update_all_columns_hash
1774
- _changed_columns.clear
1781
+ _clear_changed_columns(:update)
1775
1782
  end
1776
1783
  else # update only the specified columns
1777
1784
  columns = Array(columns)
@@ -1798,7 +1805,7 @@ module Sequel
1798
1805
  # can be overridden to avoid the refresh.
1799
1806
  def _save_refresh
1800
1807
  _save_set_values(_refresh_get(this.server?(:default)) || raise(NoExistingObject, "Record not found"))
1801
- _changed_columns.clear
1808
+ _clear_changed_columns(:create)
1802
1809
  end
1803
1810
 
1804
1811
  # Set values to the provided hash. Called after a create,
@@ -0,0 +1,83 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Sequel
4
+ module Plugins
5
+ # The association_multi_add_remove plugin allows adding, removing and setting
6
+ # multiple associated objects in a single method call.
7
+ # By default Sequel::Model defines singular <tt>add_*</tt> and <tt>remove_*</tt>
8
+ # methods that operate on a single associated object, this adds plural forms
9
+ # that operate on multiple associated objects. Example:
10
+ #
11
+ # artist.albums # => [album1]
12
+ # artist.add_albums([album2, album3])
13
+ # artist.albums # => [album1, album2, album3]
14
+ # artist.remove_albums([album3, album1])
15
+ # artist.albums # => [album2]
16
+ # artist.albums = [album2, album3]
17
+ # artist.albums # => [album2, album3]
18
+ #
19
+ # It can handle all situations that the normal singular methods handle, but there is
20
+ # no attempt to optimize behavior, so using these methods will not improve performance.
21
+ #
22
+ # The add/remove/set methods defined by this plugin use a transaction,
23
+ # so if one add/remove/set fails and raises an exception, all adds/removes/set
24
+ # will be rolled back. If you are using database sharding and want to save
25
+ # to a specific shard, call Model#set_server to set the server for this instance,
26
+ # as the transaction will be opened on that server.
27
+ #
28
+ # You can customize the method names used for adding/removing multiple associated
29
+ # objects using the :multi_add_method and :multi_remove_method association options.
30
+ #
31
+ # Usage:
32
+ #
33
+ # # Allow adding/removing/setting multiple associated objects in a single call
34
+ # # for all model subclass instances (called before loading subclasses):
35
+ # Sequel::Model.plugin :association_multi_add_remove
36
+ #
37
+ # # Allow adding/removing/setting multiple associated objects in a single call
38
+ # # for Album instances (called before defining associations in the class):
39
+ # Album.plugin :association_multi_add_remove
40
+ module AssociationMultiAddRemove
41
+ module ClassMethods
42
+ # Define the methods use to add/remove/set multiple associated objects
43
+ # in a single method call.
44
+ def def_association_instance_methods(opts)
45
+ super
46
+
47
+ if opts[:adder]
48
+ add_method = opts[:add_method]
49
+ multi_add_method = opts[:multi_add_method] || :"add_#{opts[:name]}"
50
+ multi_add_method = nil if add_method == multi_add_method
51
+ if multi_add_method
52
+ association_module_def(multi_add_method, opts) do |objs, *args|
53
+ db.transaction(:server=>@server){objs.map{|obj| send(add_method, obj, *args)}.compact}
54
+ end
55
+ end
56
+ end
57
+
58
+ if opts[:remover]
59
+ remove_method = opts[:remove_method]
60
+ multi_remove_method = opts[:multi_remove_method] || :"remove_#{opts[:name]}"
61
+ multi_remove_method = nil if remove_method == multi_remove_method
62
+ if multi_remove_method
63
+ association_module_def(multi_remove_method, opts) do |objs, *args|
64
+ db.transaction(:server=>@server){objs.map{|obj| send(remove_method, obj, *args)}.compact}
65
+ end
66
+ end
67
+ end
68
+
69
+ if multi_add_method && multi_remove_method
70
+ association_module_def(:"#{opts[:name]}=", opts) do |objs, *args|
71
+ db.transaction(:server=>@server) do
72
+ existing_objs = send(opts.association_method)
73
+ send(multi_remove_method, (existing_objs - objs), *args)
74
+ send(multi_add_method, (objs - existing_objs), *args)
75
+ nil
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -26,6 +26,9 @@ module Sequel
26
26
  # * Model.with_pk!
27
27
  # * Model.[] # when argument is not hash or nil
28
28
  # * many_to_one association method # without dynamic callback, when primary key matches
29
+ #
30
+ # You should not use this plugin if you are using sharding and there are different
31
+ # rows for the same primary key on different shards.
29
32
  #
30
33
  # Usage:
31
34
  #
@@ -65,8 +65,6 @@ module Sequel
65
65
  # # Add CSV output capability to Album class instances
66
66
  # Album.plugin :csv_serializer
67
67
  module CsvSerializer
68
- CSV = Object.const_defined?(:CSV) ? ::CSV : ::FasterCSV
69
-
70
68
  # Set up the column readers to do deserialization and the column writers
71
69
  # to save the value in deserialized_values
72
70
  def self.configure(model, opts = OPTS)
@@ -75,13 +73,29 @@ module Sequel
75
73
  end
76
74
  end
77
75
 
76
+ # Avoid keyword argument separation warnings on Ruby 2.7, while still
77
+ # being compatible with 1.9.
78
+ if RUBY_VERSION >= "2.0"
79
+ instance_eval(<<-END, __FILE__, __LINE__+1)
80
+ def self.csv_call(*args, opts, &block)
81
+ CSV.send(*args, **opts, &block)
82
+ end
83
+ END
84
+ else
85
+ # :nodoc:
86
+ def self.csv_call(*args, opts, &block)
87
+ CSV.send(*args, opts, &block)
88
+ end
89
+ # :nodoc:
90
+ end
91
+
78
92
  module ClassMethods
79
93
  # The default opts to use when serializing model objects to CSV
80
94
  attr_reader :csv_serializer_opts
81
95
 
82
96
  # Attempt to parse an array of instances from the given CSV string
83
97
  def array_from_csv(csv, opts = OPTS)
84
- CSV.parse(csv, process_csv_serializer_opts(opts)).map do |row|
98
+ CsvSerializer.csv_call(:parse, csv, process_csv_serializer_opts(opts)).map do |row|
85
99
  row = row.to_hash
86
100
  row.delete(nil)
87
101
  new(row)
@@ -108,7 +122,8 @@ module Sequel
108
122
  opts_cols = opts.delete(:columns)
109
123
  opts_include = opts.delete(:include)
110
124
  opts_except = opts.delete(:except)
111
- opts[:headers] ||= Array(opts.delete(:only) || opts_cols || columns) + Array(opts_include) - Array(opts_except)
125
+ only = opts.delete(:only)
126
+ opts[:headers] ||= Array(only || opts_cols || columns) + Array(opts_include) - Array(opts_except)
112
127
  opts
113
128
  end
114
129
 
@@ -130,7 +145,7 @@ module Sequel
130
145
  # :headers :: The headers to use for the CSV line. Use nil for a header
131
146
  # to specify the column should be ignored.
132
147
  def from_csv(csv, opts = OPTS)
133
- row = CSV.parse_line(csv, model.process_csv_serializer_opts(opts)).to_hash
148
+ row = CsvSerializer.csv_call(:parse_line, csv, model.process_csv_serializer_opts(opts)).to_hash
134
149
  row.delete(nil)
135
150
  set(row)
136
151
  end
@@ -146,9 +161,10 @@ module Sequel
146
161
  # attributes to include in the CSV output.
147
162
  def to_csv(opts = OPTS)
148
163
  opts = model.process_csv_serializer_opts(opts)
164
+ headers = opts[:headers]
149
165
 
150
- CSV.generate(opts) do |csv|
151
- csv << opts[:headers].map{|k| public_send(k)}
166
+ CsvSerializer.csv_call(:generate, model.process_csv_serializer_opts(opts)) do |csv|
167
+ csv << headers.map{|k| public_send(k)}
152
168
  end
153
169
  end
154
170
  end
@@ -164,10 +180,11 @@ module Sequel
164
180
  def to_csv(opts = OPTS)
165
181
  opts = model.process_csv_serializer_opts({:columns=>columns}.merge!(opts))
166
182
  items = opts.delete(:array) || self
183
+ headers = opts[:headers]
167
184
 
168
- CSV.generate(opts) do |csv|
185
+ CsvSerializer.csv_call(:generate, opts) do |csv|
169
186
  items.each do |object|
170
- csv << opts[:headers].map{|header| object.public_send(header) }
187
+ csv << headers.map{|header| object.public_send(header)}
171
188
  end
172
189
  end
173
190
  end
@@ -159,9 +159,9 @@ module Sequel
159
159
 
160
160
  private
161
161
 
162
- # Reset the initial values when setting values.
163
- def _refresh_set_values(hash)
164
- reset_initial_values
162
+ # Reset initial values when clearing changed columns
163
+ def _clear_changed_columns(reason)
164
+ reset_initial_values if reason == :initialize || reason == :refresh
165
165
  super
166
166
  end
167
167
 
@@ -214,12 +214,6 @@ module Sequel
214
214
  self
215
215
  end
216
216
 
217
- # Reset the initial values when initializing.
218
- def initialize_set(h)
219
- super
220
- reset_initial_values
221
- end
222
-
223
217
  # Array holding column symbols that were not present initially. This is necessary
224
218
  # to differentiate between values that were not present and values that were
225
219
  # present but equal to nil.
@@ -0,0 +1,38 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Sequel
4
+ module Plugins
5
+ # The empty_failure_backtraces plugin uses empty backtraces when raising HookFailed and ValidationFailed
6
+ # exceptions. This can be significantly faster, and if you are using these exceptions for
7
+ # flow control, you do not need the backtraces. This plugin is about 10% faster on CRuby
8
+ # and 10-15x faster on JRuby 9.2.7.0+. This does not have an effect on JRuby <9.2.7.0.
9
+ #
10
+ # Usage:
11
+ #
12
+ # # Make all model subclass instances use empty backtraces for HookFailed
13
+ # # and ValidationFailed exceptions (called before loading subclasses)
14
+ # Sequel::Model.plugin :empty_failure_backtraces
15
+ #
16
+ # # Make the Album class use empty backtraces for HookFailed and ValidationFailed exceptions
17
+ # Album.plugin :empty_failure_backtraces
18
+ module EmptyFailureBacktraces
19
+ module InstanceMethods
20
+ private
21
+
22
+ # Use empty backtrace for HookFailed exceptions.
23
+ def hook_failed_error(msg)
24
+ e = super
25
+ e.set_backtrace([])
26
+ e
27
+ end
28
+
29
+ # Use empty backtrace for ValidationFailed exceptions.
30
+ def validation_failed_error
31
+ e = super
32
+ e.set_backtrace([])
33
+ e
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -369,6 +369,13 @@ module Sequel
369
369
  end
370
370
 
371
371
  module DatasetMethods
372
+ # Store default options used when calling to_json on this dataset.
373
+ # These options take precedence over the class level options,
374
+ # and can be overridden by passing options directly to to_json.
375
+ def json_serializer_opts(opts=OPTS)
376
+ clone(:json_serializer_opts=>opts)
377
+ end
378
+
372
379
  # Return a JSON string representing an array of all objects in
373
380
  # this dataset. Takes the same options as the instance
374
381
  # method, and passes them to every instance. Additionally,
@@ -386,11 +393,15 @@ module Sequel
386
393
  # object. If set to a string, wraps the collection in
387
394
  # a root object using the string as the key.
388
395
  def to_json(*a)
389
- if opts = a.first.is_a?(Hash)
390
- opts = model.json_serializer_opts.merge(a.first)
396
+ opts = model.json_serializer_opts
397
+
398
+ if ds_opts = @opts[:json_serializer_opts]
399
+ opts = opts.merge(ds_opts)
400
+ end
401
+
402
+ if (arg = a.first).is_a?(Hash)
403
+ opts = opts.merge(arg)
391
404
  a = []
392
- else
393
- opts = model.json_serializer_opts
394
405
  end
395
406
 
396
407
  case collection_root = opts[:root]
@@ -113,6 +113,10 @@ module Sequel
113
113
  # value, the attribute hash is ignored.
114
114
  # :remove :: Allow disassociation of nested records (can remove the associated
115
115
  # object from the parent object, but not destroy the associated object).
116
+ # :require_modification :: Whether to require modification of nested objects when
117
+ # updating or deleting them (checking that a single row was
118
+ # updated). By default, uses the default require_modification
119
+ # setting for the nested object.
116
120
  # :transform :: A proc to transform attribute hashes before they are
117
121
  # passed to associated object. Takes two arguments, the parent object and
118
122
  # the attribute hash. Uses the return value as the new attribute hash.
@@ -282,6 +286,9 @@ module Sequel
282
286
  obj = Array(public_send(reflection[:name])).find{|x| Array(x.pk).map(&:to_s) == pk}
283
287
  end
284
288
  if obj
289
+ unless (require_modification = meta[:require_modification]).nil?
290
+ obj.require_modification = require_modification
291
+ end
285
292
  attributes = attributes.dup.delete_if{|k,v| str_keys.include? k.to_s}
286
293
  if meta[:destroy] && klass.db.send(:typecast_value_boolean, attributes.delete(:_delete) || attributes.delete('_delete'))
287
294
  nested_attributes_remove(meta, obj, :destroy=>true)
@@ -107,12 +107,18 @@ module Sequel
107
107
  # previous row_proc, but calls set_server on the output of that row_proc,
108
108
  # ensuring that objects retrieved by a specific shard know which shard they
109
109
  # are tied to.
110
- def server(s)
111
- ds = super
112
- if rp = row_proc
113
- ds = ds.with_row_proc(proc{|r| rp.call(r).set_server(s)})
110
+ def row_proc
111
+ rp = super
112
+ if rp
113
+ case server = db.pool.send(:pick_server, opts[:server])
114
+ when nil, :default, :read_only
115
+ # nothing
116
+ else
117
+ old_rp = rp
118
+ rp = proc{|r| old_rp.call(r).set_server(server)}
119
+ end
114
120
  end
115
- ds
121
+ rp
116
122
  end
117
123
  end
118
124
  end
@@ -48,7 +48,7 @@ module Sequel
48
48
  # nil if there were any.
49
49
  def catch_hook_failures
50
50
  called = ret = nil
51
- caught = catch(HookFailed) do
51
+ catch(HookFailed) do
52
52
  ret = yield
53
53
  called = true
54
54
  end
@@ -41,7 +41,9 @@ module Sequel
41
41
  # Typecast values using #load_typecast when the values are retrieved
42
42
  # from the database.
43
43
  def call(values)
44
- super.load_typecast
44
+ o = super.load_typecast
45
+ o.send(:_clear_changed_columns, :initialize)
46
+ o
45
47
  end
46
48
 
47
49
  # Freeze typecast on load columns when freezing model class.
@@ -63,7 +65,6 @@ module Sequel
63
65
  set_column_value("#{c}=", v)
64
66
  end
65
67
  end
66
- _changed_columns.clear
67
68
  self
68
69
  end
69
70
 
@@ -788,8 +788,10 @@ module Sequel
788
788
  def coerce(other)
789
789
  if other.is_a?(Numeric)
790
790
  [SQL::NumericExpression.new(:NOOP, other), self]
791
- else
791
+ elsif defined?(super)
792
792
  super
793
+ else
794
+ [self, other]
793
795
  end
794
796
  end
795
797
 
@@ -1315,6 +1317,7 @@ module Sequel
1315
1317
  CURRENT_DATE = Constant.new(:CURRENT_DATE)
1316
1318
  CURRENT_TIME = Constant.new(:CURRENT_TIME)
1317
1319
  CURRENT_TIMESTAMP = Constant.new(:CURRENT_TIMESTAMP)
1320
+ DEFAULT = Constant.new(:DEFAULT)
1318
1321
  SQLTRUE = TRUE = BooleanConstant.new(true)
1319
1322
  SQLFALSE = FALSE = BooleanConstant.new(false)
1320
1323
  NULL = BooleanConstant.new(nil)
@@ -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 = 24
9
+ MINOR = 29
10
10
 
11
11
  # The tiny version of Sequel. Usually 0, only bumped for bugfix
12
12
  # releases that fix regressions from previous versions.
@@ -70,7 +70,7 @@ describe "PostgreSQL", '#create_table' do
70
70
 
71
71
  it "should create an unlogged table" do
72
72
  @db.create_table(:unlogged_dolls, :unlogged => true){text :name}
73
- end
73
+ end if DB.server_version >= 90100
74
74
 
75
75
  it "should create a table inheriting from another table" do
76
76
  @db.create_table(:unlogged_dolls){text :name}
@@ -368,6 +368,14 @@ describe "PostgreSQL", 'INSERT ON CONFLICT' do
368
368
  @ds.insert_conflict(:constraint=>:ic_test_a_uidx, :update=>{:b=>6}, :update_where=>{Sequel[:ic_test][:b]=>4}).insert(1, 3, 4).must_be_nil
369
369
  @ds.all.must_equal [{:a=>1, :b=>5, :c=>5, :c_is_unique=>false}]
370
370
  end
371
+
372
+ it "Dataset#insert_conflict should support table aliases" do
373
+ @ds = @db[Sequel[:ic_test].as(:foo)]
374
+ @ds.insert(1, 2, 5)
375
+ proc{@ds.insert(1, 3, 4)}.must_raise Sequel::UniqueConstraintViolation
376
+ @ds.insert_conflict(:target=>:a, :update=>{:b=>Sequel[:foo][:c] + Sequel[:excluded][:c]}).insert(1, 7, 10)
377
+ @ds.all.must_equal [{:a=>1, :b=>15, :c=>5, :c_is_unique=>false}]
378
+ end
371
379
  end if DB.server_version >= 90500
372
380
 
373
381
  describe "A PostgreSQL database" do
@@ -607,10 +615,6 @@ describe "A PostgreSQL dataset" do
607
615
  @d.from{generate_series(1,3,1).as(:a)}.select{(a.sql_number % 2).as(:a)}.from_self.get{mode.function.within_group(:a)}.must_equal 1
608
616
  end if DB.server_version >= 90400
609
617
 
610
- it "should support filtered aggregate functions" do
611
- @d.from{generate_series(1,3,1).as(:a)}.select{(a.sql_number % 2).as(:a)}.from_self.get{count(:a).filter(:a=>1)}.must_equal 2
612
- end if DB.server_version >= 90400
613
-
614
618
  it "should support functions with ordinality" do
615
619
  @d.from{generate_series(1,10,3).with_ordinality}.select_map([:generate_series, :ordinality]).must_equal [[1, 1], [4, 2], [7, 3], [10, 4]]
616
620
  end if DB.server_version >= 90400
@@ -1264,7 +1268,7 @@ describe "A PostgreSQL database" do
1264
1268
  end
1265
1269
 
1266
1270
  it "should support indexes with index type" do
1267
- @db.create_table(:posts){point :p; index :p, :type => 'gist'}
1271
+ @db.create_table(:posts){box :geom; index :geom, :type => 'gist'}
1268
1272
  end
1269
1273
 
1270
1274
  it "should support unique indexes with index type" do
@@ -2696,6 +2700,8 @@ describe 'PostgreSQL array handling' do
2696
2700
  if @db.server_version >= 90000
2697
2701
  @ds.get(Sequel.pg_array(:i5).join).must_equal '15'
2698
2702
  @ds.get(Sequel.pg_array(:i5).join(':')).must_equal '1:5'
2703
+ end
2704
+ if @db.server_version >= 90100
2699
2705
  @ds.get(Sequel.pg_array(:i5).join(':', '*')).must_equal '1:*:5'
2700
2706
  end
2701
2707
  if @db.server_version >= 90300
@@ -3341,6 +3347,65 @@ describe 'PostgreSQL json type' do
3341
3347
  @db.from(jo.each_text).select_order_map(:key).must_equal %w'a b'
3342
3348
  @db.from(jo.each_text).order(:key).where(:key=>'b').get(:value).gsub(' ', '').must_match(/\{"d":\{"e":3\},"c":2\}|\{"c":2,"d":\{"e":3\}\}/)
3343
3349
 
3350
+ if DB.server_version >= 120000 && json_type == :jsonb
3351
+ @db.get(jo.path_exists('$.b.d.e')).must_equal true
3352
+ @db.get(jo.path_exists('$.b.d.f')).must_equal false
3353
+
3354
+ @db.get(jo.path_exists!('$.b.d.e')).must_equal true
3355
+ @db.get(jo.path_exists!('$.b.d.f')).must_equal false
3356
+ @db.get(jo.path_exists!('$.b.d.e ? (@ > $x)', '{"x":2}')).must_equal true
3357
+ @db.get(jo.path_exists!('$.b.d.e ? (@ > $x)', '{"x":4}')).must_equal false
3358
+ @db.get(jo.path_exists!('$.b.d.e ? (@ > $x)', x: 2)).must_equal true
3359
+ @db.get(jo.path_exists!('$.b.d.e ? (@ > $x)', x: 4)).must_equal false
3360
+ @db.get(jo.path_exists!('$.b.d.e ? (@ > $x)', {x: 2}, true)).must_equal true
3361
+ @db.get(jo.path_exists!('$.b.d.e ? (@ > $x)', {x: 4}, false)).must_equal false
3362
+
3363
+ @db.get(jo.path_match('$.b.d.e')).must_be_nil
3364
+ @db.get(jo.path_match('$.b.d.f')).must_be_nil
3365
+ @db.get(pg_json.call('b'=>{'d'=>{'e'=>true}}).op.path_match('$.b.d.e')).must_equal true
3366
+ @db.get(pg_json.call('b'=>{'d'=>{'e'=>false}}).op.path_match('$.b.d.e')).must_equal false
3367
+
3368
+ proc{@db.get(jo.path_match!('$.b.d.e'))}.must_raise(Sequel::DatabaseError)
3369
+ proc{@db.get(jo.path_match!('$.b.d.f'))}.must_raise(Sequel::DatabaseError)
3370
+ @db.get(jo.path_match!('$.b.d.e', {}, true)).must_be_nil
3371
+ @db.get(jo.path_match!('$.b.d.f', {}, true)).must_be_nil
3372
+ @db.get(pg_json.call('b'=>{'d'=>{'e'=>true}}).op.path_match!('$.b.d.e')).must_equal true
3373
+ @db.get(pg_json.call('b'=>{'d'=>{'e'=>false}}).op.path_match!('$.b.d.e')).must_equal false
3374
+ @db.get(jo.path_match!('$.b.d.e > $x', '{"x":2}')).must_equal true
3375
+ @db.get(jo.path_match!('$.b.d.e > $x', '{"x":4}')).must_equal false
3376
+ @db.get(jo.path_match!('$.b.d.e > $x', x: 2)).must_equal true
3377
+ @db.get(jo.path_match!('$.b.d.e > $x', x: 4)).must_equal false
3378
+ @db.get(jo.path_match!('$.b.d.e > $x', {x: 2}, false)).must_equal true
3379
+ @db.get(jo.path_match!('$.b.d.e > $x', {x: 4}, true)).must_equal false
3380
+
3381
+ @db.get(jo.path_query_first('$.b.d.e')).must_equal 3
3382
+ @db.get(jo.path_query_first('$.b.d.f')).must_be_nil
3383
+ @db.get(jo.path_query_first('$.b.d.e ? (@ > $x)', '{"x":2}')).must_equal 3
3384
+ @db.get(jo.path_query_first('$.b.d.e ? (@ > $x)', '{"x":4}')).must_be_nil
3385
+ @db.get(jo.path_query_first('$.b.d.e ? (@ > $x)', x: 2)).must_equal 3
3386
+ @db.get(jo.path_query_first('$.b.d.e ? (@ > $x)', x: 4)).must_be_nil
3387
+ @db.get(jo.path_query_first('$.b.d.e ? (@ > $x)', {x: 2}, true)).must_equal 3
3388
+ @db.get(jo.path_query_first('$.b.d.e ? (@ > $x)', {x: 4}, false)).must_be_nil
3389
+
3390
+ @db.get(jo.path_query_array('$.b.d.e')).must_equal [3]
3391
+ @db.get(jo.path_query_array('$.b.d.f')).must_equal []
3392
+ @db.get(jo.path_query_array('$.b.d.e ? (@ > $x)', '{"x":2}')).must_equal [3]
3393
+ @db.get(jo.path_query_array('$.b.d.e ? (@ > $x)', '{"x":4}')).must_equal []
3394
+ @db.get(jo.path_query_array('$.b.d.e ? (@ > $x)', x: 2)).must_equal [3]
3395
+ @db.get(jo.path_query_array('$.b.d.e ? (@ > $x)', x: 4)).must_equal []
3396
+ @db.get(jo.path_query_array('$.b.d.e ? (@ > $x)', {x: 2}, true)).must_equal [3]
3397
+ @db.get(jo.path_query_array('$.b.d.e ? (@ > $x)', {x: 4}, false)).must_equal []
3398
+
3399
+ @db.from(jo.path_query('$.b.d.e').as(:a, [:b])).get(:b).must_equal 3
3400
+ @db.from(jo.path_query('$.b.d.f').as(:a, [:b])).get(:b).must_be_nil
3401
+ @db.from(jo.path_query('$.b.d.e ? (@ > $x)', '{"x":2}').as(:a, [:b])).get(:b).must_equal 3
3402
+ @db.from(jo.path_query('$.b.d.e ? (@ > $x)', '{"x":4}').as(:a, [:b])).get(:b).must_be_nil
3403
+ @db.from(jo.path_query('$.b.d.e ? (@ > $x)', x: 2).as(:a, [:b])).get(:b).must_equal 3
3404
+ @db.from(jo.path_query('$.b.d.e ? (@ > $x)', x: 4).as(:a, [:b])).get(:b).must_be_nil
3405
+ @db.from(jo.path_query('$.b.d.e ? (@ > $x)', {x: 2}, true).as(:a, [:b])).get(:b).must_equal 3
3406
+ @db.from(jo.path_query('$.b.d.e ? (@ > $x)', {x: 4}, false).as(:a, [:b])).get(:b).must_be_nil
3407
+ end
3408
+
3344
3409
  Sequel.extension :pg_row_ops
3345
3410
  @db.create_table!(:items) do
3346
3411
  Integer :a
@@ -3620,19 +3685,19 @@ describe 'PostgreSQL range types' do
3620
3685
  end if uses_pg_or_jdbc
3621
3686
 
3622
3687
  it 'handle endless ranges' do
3623
- @db.get(Sequel.cast(eval('1...'), :int4range)).must_be :==, eval('1...')
3624
- @db.get(Sequel.cast(eval('1...'), :int4range)).wont_be :==, eval('2...')
3625
- @db.get(Sequel.cast(eval('1...'), :int4range)).wont_be :==, eval('1..')
3626
- @db.get(Sequel.cast(eval('2...'), :int4range)).must_be :==, eval('2...')
3627
- @db.get(Sequel.cast(eval('2...'), :int4range)).wont_be :==, eval('2..')
3628
- @db.get(Sequel.cast(eval('2...'), :int4range)).wont_be :==, eval('1...')
3688
+ @db.get(Sequel.cast(eval('(1...)'), :int4range)).must_be :==, eval('(1...)')
3689
+ @db.get(Sequel.cast(eval('(1...)'), :int4range)).wont_be :==, eval('(2...)')
3690
+ @db.get(Sequel.cast(eval('(1...)'), :int4range)).wont_be :==, eval('(1..)')
3691
+ @db.get(Sequel.cast(eval('(2...)'), :int4range)).must_be :==, eval('(2...)')
3692
+ @db.get(Sequel.cast(eval('(2...)'), :int4range)).wont_be :==, eval('(2..)')
3693
+ @db.get(Sequel.cast(eval('(2...)'), :int4range)).wont_be :==, eval('(1...)')
3629
3694
  end if RUBY_VERSION >= '2.6'
3630
3695
 
3631
3696
  it 'handle startless ranges' do
3632
- @db.get(Sequel.cast(eval('...1'), :int4range)).must_be :==, Sequel::Postgres::PGRange.new(nil, 1, :exclude_begin=>true, :exclude_end=>true, :db_type=>"int4range")
3633
- @db.get(Sequel.cast(eval('...1'), :int4range)).wont_be :==, Sequel::Postgres::PGRange.new(nil, 2, :exclude_begin=>true, :exclude_end=>true, :db_type=>"int4range")
3634
- @db.get(Sequel.cast(eval('...1'), :int4range)).wont_be :==, Sequel::Postgres::PGRange.new(nil, 1, :exclude_end=>true, :db_type=>"int4range")
3635
- @db.get(Sequel.cast(eval('...1'), :int4range)).wont_be :==, Sequel::Postgres::PGRange.new(nil, 1, :exclude_begin=>true, :db_type=>"int4range")
3697
+ @db.get(Sequel.cast(eval('(...1)'), :int4range)).must_be :==, Sequel::Postgres::PGRange.new(nil, 1, :exclude_begin=>true, :exclude_end=>true, :db_type=>"int4range")
3698
+ @db.get(Sequel.cast(eval('(...1)'), :int4range)).wont_be :==, Sequel::Postgres::PGRange.new(nil, 2, :exclude_begin=>true, :exclude_end=>true, :db_type=>"int4range")
3699
+ @db.get(Sequel.cast(eval('(...1)'), :int4range)).wont_be :==, Sequel::Postgres::PGRange.new(nil, 1, :exclude_end=>true, :db_type=>"int4range")
3700
+ @db.get(Sequel.cast(eval('(...1)'), :int4range)).wont_be :==, Sequel::Postgres::PGRange.new(nil, 1, :exclude_begin=>true, :db_type=>"int4range")
3636
3701
  end if RUBY_VERSION >= '2.7'
3637
3702
 
3638
3703
  it 'handle startless ranges' do
@@ -4408,4 +4473,4 @@ describe "pg_auto_constraint_validations plugin" do
4408
4473
  File.delete(cache_file) if File.file?(cache_file)
4409
4474
  end
4410
4475
  end
4411
- end if DB.respond_to?(:error_info)
4476
+ end if DB.respond_to?(:error_info) && DB.server_version >= 90300