sequel 5.22.0 → 5.23.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.
@@ -7,7 +7,7 @@
7
7
  # that when composite fields are retrieved, they are parsed and returned
8
8
  # as instances of Sequel::Postgres::PGRow::(HashRow|ArrayRow), or
9
9
  # optionally a custom type. HashRow and ArrayRow are DelegateClasses of
10
- # of Hash and Array, so they mostly act like a hash or array, but not
10
+ # Hash and Array, so they mostly act like a hash or array, but not
11
11
  # completely (is_a?(Hash) and is_a?(Array) are false). If you want the
12
12
  # actual hash for a HashRow, call HashRow#to_hash, and if you want the
13
13
  # actual array for an ArrayRow, call ArrayRow#to_a. This is done so
@@ -228,7 +228,9 @@ module Sequel
228
228
  if skip(/\)/)
229
229
  values << nil
230
230
  else
231
+ # :nocov:
231
232
  until eos?
233
+ # :nocov:
232
234
  if skip(/"/)
233
235
  values << scan(/(\\.|""|[^"])*/).gsub(/\\(.)|"(")/, '\1\2')
234
236
  skip(/"[,)]/)
@@ -64,6 +64,8 @@ module Sequel
64
64
  array = [].freeze
65
65
 
66
66
  if RUBY_VERSION < '2.6'
67
+ # :nocov:
68
+
67
69
  # Default proc used to determine whether to send the method to the dataset.
68
70
  # If the array would respond to it, sends it to the array instead of the dataset.
69
71
  DEFAULT_PROXY_TO_DATASET = proc do |opts|
@@ -73,10 +75,9 @@ module Sequel
73
75
  end
74
76
  !array_method
75
77
  end
76
- else
77
78
  # :nocov:
79
+ else
78
80
  DEFAULT_PROXY_TO_DATASET = proc{|opts| !array.respond_to?(opts[:method])}
79
- # :nocov:
80
81
  end
81
82
 
82
83
  # Set the association reflection to use, and whether the association should be
@@ -108,6 +108,16 @@ module Sequel
108
108
  # a = Executive.where{{employees[:id]=>1}}.first # works
109
109
  # a = Executive.where{{executives[:id]=>1}}.first # doesn't work
110
110
  #
111
+ # Note that because subclass datasets select from a subquery, you cannot update,
112
+ # delete, or insert into them directly. To delete related rows, you need to go
113
+ # through the related tables and remove the related rows. Code that does this would
114
+ # be similar to:
115
+ #
116
+ # pks = Executive.where{num_staff < 10}.select_map(:id)
117
+ # Executive.cti_tables.reverse_each do |table|
118
+ # DB.from(table).where(:id=>pks).delete
119
+ # end
120
+ #
111
121
  # = Usage
112
122
  #
113
123
  # # Use the default of storing the class name in the sti_key
@@ -0,0 +1,72 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Sequel
4
+ module Plugins
5
+ # The insert_conflict plugin allows handling conflicts due to unique
6
+ # constraints when saving new model instance, using the INSERT ON CONFLICT
7
+ # support in PostgreSQL 9.5+ and SQLite 3.24.0+. Example:
8
+ #
9
+ # class Album < Sequel::Model
10
+ # plugin :insert_conflict
11
+ # end
12
+ #
13
+ # Album.new(name: 'Foo', copies_sold: 1000).
14
+ # insert_conflict(
15
+ # target: :name,
16
+ # update: {copies_sold: Sequel[:excluded][:b]}
17
+ # ).
18
+ # save
19
+ #
20
+ # This example will try to insert the album, but if there is an existing
21
+ # album with the name 'Foo', this will update the copies_sold attribute
22
+ # for that album. See the PostgreSQL and SQLite adapter documention for
23
+ # the options you can pass to the insert_conflict method.
24
+ #
25
+ # Usage:
26
+ #
27
+ # # Make all model subclasses support insert_conflict
28
+ # Sequel::Model.plugin :insert_conflict
29
+ #
30
+ # # Make the Album class support insert_conflict
31
+ # Album.plugin :insert_conflict
32
+ module InsertConflict
33
+ def self.configure(model)
34
+ model.instance_exec do
35
+ if @dataset && !@dataset.respond_to?(:insert_conflict)
36
+ raise Error, "#{self}'s dataset does not support insert_conflict"
37
+ end
38
+ end
39
+ end
40
+
41
+ module InstanceMethods
42
+ # Set the insert_conflict options to pass to the dataset when inserting.
43
+ def insert_conflict(opts=OPTS)
44
+ raise Error, "Model#insert_conflict is only supported on new model instances" unless new?
45
+ @insert_conflict_opts = opts
46
+ self
47
+ end
48
+
49
+ private
50
+
51
+ # Set the dataset used for inserting to use INSERT ON CONFLICT
52
+ # Model#insert_conflict has been called on the instance previously.
53
+ def _insert_dataset
54
+ ds = super
55
+
56
+ if @insert_conflict_opts
57
+ ds = ds.insert_conflict(@insert_conflict_opts)
58
+ end
59
+
60
+ ds
61
+ end
62
+
63
+ # Disable the use of prepared insert statements, as they are not compatible
64
+ # with this plugin.
65
+ def use_prepared_statements_for?(type)
66
+ return false if type == :insert || type == :insert_select
67
+ super if defined?(super)
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -54,7 +54,14 @@ module Sequel
54
54
  convert_output_datetime_other(v, output_timezone)
55
55
  end
56
56
  else
57
- v.public_send(output_timezone == :utc ? :getutc : :getlocal)
57
+ case output_timezone
58
+ when :utc
59
+ v.getutc
60
+ when :local
61
+ v.getlocal
62
+ else
63
+ convert_output_time_other(v, output_timezone)
64
+ end
58
65
  end
59
66
  else
60
67
  v
@@ -110,7 +117,7 @@ module Sequel
110
117
  # same time and just modifying the timezone.
111
118
  def convert_input_datetime_no_offset(v, input_timezone)
112
119
  case input_timezone
113
- when :utc, nil
120
+ when nil, :utc
114
121
  v # DateTime assumes UTC if no offset is given
115
122
  when :local
116
123
  offset = local_offset_for_datetime(v)
@@ -119,7 +126,7 @@ module Sequel
119
126
  convert_input_datetime_other(v, input_timezone)
120
127
  end
121
128
  end
122
-
129
+
123
130
  # Convert the given +DateTime+ to the given input_timezone that is not supported
124
131
  # by default (i.e. one other than +nil+, <tt>:local</tt>, or <tt>:utc</tt>). Raises an +InvalidValue+ by default.
125
132
  # Can be overridden in extensions.
@@ -127,6 +134,13 @@ module Sequel
127
134
  raise InvalidValue, "Invalid input_timezone: #{input_timezone.inspect}"
128
135
  end
129
136
 
137
+ # Convert the given +Time+ to the given input_timezone that is not supported
138
+ # by default (i.e. one other than +nil+, <tt>:local</tt>, or <tt>:utc</tt>). Raises an +InvalidValue+ by default.
139
+ # Can be overridden in extensions.
140
+ def convert_input_time_other(v, input_timezone)
141
+ raise InvalidValue, "Invalid input_timezone: #{input_timezone.inspect}"
142
+ end
143
+
130
144
  # Converts the object from a +String+, +Array+, +Date+, +DateTime+, or +Time+ into an
131
145
  # instance of <tt>Sequel.datetime_class</tt>. If given an array or a string that doesn't
132
146
  # contain an offset, assume that the array/string is already in the given +input_timezone+.
@@ -139,12 +153,17 @@ module Sequel
139
153
  else
140
154
  # Correct for potentially wrong offset if string doesn't include offset
141
155
  if v2.is_a?(DateTime)
142
- v2 = convert_input_datetime_no_offset(v2, input_timezone)
156
+ convert_input_datetime_no_offset(v2, input_timezone)
143
157
  else
144
- # Time assumes local time if no offset is given
145
- v2 = v2.getutc + v2.utc_offset if input_timezone == :utc
158
+ case input_timezone
159
+ when nil, :local
160
+ v2
161
+ when :utc
162
+ (v2 + v2.utc_offset).utc
163
+ else
164
+ convert_input_time_other((v2 + v2.utc_offset).utc, input_timezone)
165
+ end
146
166
  end
147
- v2
148
167
  end
149
168
  when Array
150
169
  y, mo, d, h, mi, s, ns, off = v
@@ -155,8 +174,18 @@ module Sequel
155
174
  else
156
175
  convert_input_datetime_no_offset(DateTime.civil(y, mo, d, h, mi, s), input_timezone)
157
176
  end
177
+ elsif off
178
+ s += Rational(ns, 1000000000) if ns
179
+ Time.new(y, mo, d, h, mi, s, (off*86400).to_i)
158
180
  else
159
- Time.public_send(input_timezone == :utc ? :utc : :local, y, mo, d, h, mi, s, (ns ? ns / 1000.0 : 0))
181
+ case input_timezone
182
+ when nil, :local
183
+ Time.local(y, mo, d, h, mi, s, (ns ? ns / 1000.0 : 0))
184
+ when :utc
185
+ Time.utc(y, mo, d, h, mi, s, (ns ? ns / 1000.0 : 0))
186
+ else
187
+ convert_input_time_other(Time.utc(y, mo, d, h, mi, s, (ns ? ns / 1000.0 : 0)), input_timezone)
188
+ end
160
189
  end
161
190
  when Hash
162
191
  ary = [:year, :month, :day, :hour, :minute, :second, :nanos].map{|x| (v[x] || v[x.to_s]).to_i}
@@ -188,23 +217,33 @@ module Sequel
188
217
  raise InvalidValue, "Invalid output_timezone: #{output_timezone.inspect}"
189
218
  end
190
219
 
220
+ # Convert the given +Time+ to the given output_timezone that is not supported
221
+ # by default (i.e. one other than +nil+, <tt>:local</tt>, or <tt>:utc</tt>). Raises an +InvalidValue+ by default.
222
+ # Can be overridden in extensions.
223
+ def convert_output_time_other(v, output_timezone)
224
+ raise InvalidValue, "Invalid output_timezone: #{output_timezone.inspect}"
225
+ end
226
+
191
227
  # Convert the timezone setter argument. Returns argument given by default,
192
228
  # exists for easier overriding in extensions.
193
229
  def convert_timezone_setter_arg(tz)
194
230
  tz
195
231
  end
196
232
 
197
- # Takes a DateTime dt, and returns the correct local offset for that dt, daylight savings included.
233
+ # Takes a DateTime dt, and returns the correct local offset for that dt, daylight savings included, in fraction of a day.
198
234
  def local_offset_for_datetime(dt)
199
235
  time_offset_to_datetime_offset Time.local(dt.year, dt.month, dt.day, dt.hour, dt.min, dt.sec).utc_offset
200
236
  end
201
237
 
202
238
  # Caches offset conversions to avoid excess Rational math.
203
239
  def time_offset_to_datetime_offset(offset_secs)
204
- @local_offsets ||= {}
205
- @local_offsets[offset_secs] ||= Rational(offset_secs, 86400)
240
+ if offset = Sequel.synchronize{@local_offsets[offset_secs]}
241
+ return offset
242
+ end
243
+ Sequel.synchronize{@local_offsets[offset_secs] = Rational(offset_secs, 86400)}
206
244
  end
207
245
  end
208
246
 
247
+ @local_offsets = {}
209
248
  extend Timezones
210
249
  end
@@ -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 = 22
9
+ MINOR = 23
10
10
 
11
11
  # The tiny version of Sequel. Usually 0, only bumped for bugfix
12
12
  # releases that fix regressions from previous versions.
@@ -86,7 +86,7 @@ Begin creating indexes
86
86
  Finished creating indexes
87
87
  Begin adding foreign key constraints
88
88
  Finished adding foreign key constraints
89
- Database copy finished in \\d\\.\\d+ seconds
89
+ Database copy finished in \\d+\\.\\d+ seconds
90
90
  END
91
91
  DB2.tables.sort_by{|t| t.to_s}.must_equal [:a, :b]
92
92
  DB[:a].all.must_equal [{:a=>1, :name=>'foo'}]
@@ -2073,6 +2073,18 @@ describe "Database#typecast_value" do
2073
2073
  proc{@db.typecast_value(:datetime, 4)}.must_raise(Sequel::InvalidValue)
2074
2074
  end
2075
2075
 
2076
+ it "should raise an InvalidValue when given an invalid timezone value" do
2077
+ begin
2078
+ Sequel.default_timezone = :blah
2079
+ proc{@db.typecast_value(:datetime, [2019, 2, 3, 4, 5, 6])}.must_raise(Sequel::InvalidValue)
2080
+ Sequel.datetime_class = DateTime
2081
+ proc{@db.typecast_value(:datetime, [2019, 2, 3, 4, 5, 6])}.must_raise(Sequel::InvalidValue)
2082
+ ensure
2083
+ Sequel.default_timezone = nil
2084
+ Sequel.datetime_class = Time
2085
+ end
2086
+ end
2087
+
2076
2088
  it "should handle integers with leading 0 as base 10" do
2077
2089
  @db.typecast_value(:integer, "013").must_equal 13
2078
2090
  @db.typecast_value(:integer, "08").must_equal 8
@@ -2317,6 +2329,17 @@ describe "Database#typecast_value" do
2317
2329
  @db.typecast_value(:datetime, 'year'=>2011, 'month'=>10, 'day'=>11, 'hour'=>12, 'minute'=>13, 'second'=>14).must_equal Time.local(2011, 10, 11, 12, 13, 14)
2318
2330
  @db.typecast_value(:datetime, 'year'=>2011, 'month'=>10, 'day'=>11, 'hour'=>12, 'minute'=>13, 'second'=>14, 'nanos'=>500000000).must_equal Time.local(2011, 10, 11, 12, 13, 14, 500000)
2319
2331
 
2332
+ @db.typecast_value(:datetime, :year=>2011, :month=>10, :day=>11, :hour=>12, :minute=>13, :second=>14, :offset=>Rational(1, 2)).must_equal Time.new(2011, 10, 11, 12, 13, 14, 43200)
2333
+ @db.typecast_value(:datetime, :year=>2011, :month=>10, :day=>11, :hour=>12, :minute=>13, :second=>14, :nanos=>500000000, :offset=>Rational(1, 2)).must_equal Time.new(2011, 10, 11, 12, 13, 14.5, 43200)
2334
+ @db.typecast_value(:datetime, 'year'=>2011, 'month'=>10, 'day'=>11, 'hour'=>12, 'minute'=>13, 'second'=>14, 'offset'=>Rational(1, 2)).must_equal Time.new(2011, 10, 11, 12, 13, 14, 43200)
2335
+ @db.typecast_value(:datetime, 'year'=>2011, 'month'=>10, 'day'=>11, 'hour'=>12, 'minute'=>13, 'second'=>14, 'nanos'=>500000000, 'offset'=>Rational(1, 2)).must_equal Time.new(2011, 10, 11, 12, 13, 14.5, 43200)
2336
+
2337
+ Sequel.default_timezone = :utc
2338
+ @db.typecast_value(:datetime, :year=>2011, :month=>10, :day=>11, :hour=>12, :minute=>13, :second=>14).must_equal Time.utc(2011, 10, 11, 12, 13, 14)
2339
+ @db.typecast_value(:datetime, :year=>2011, :month=>10, :day=>11, :hour=>12, :minute=>13, :second=>14, :nanos=>500000000).must_equal Time.utc(2011, 10, 11, 12, 13, 14, 500000)
2340
+ @db.typecast_value(:datetime, 'year'=>2011, 'month'=>10, 'day'=>11, 'hour'=>12, 'minute'=>13, 'second'=>14).must_equal Time.utc(2011, 10, 11, 12, 13, 14)
2341
+ @db.typecast_value(:datetime, 'year'=>2011, 'month'=>10, 'day'=>11, 'hour'=>12, 'minute'=>13, 'second'=>14, 'nanos'=>500000000).must_equal Time.utc(2011, 10, 11, 12, 13, 14, 500000)
2342
+
2320
2343
  Sequel.datetime_class = DateTime
2321
2344
  @db.typecast_value(:datetime, :year=>2011, :month=>10, :day=>11, :hour=>12, :minute=>13, :second=>14).must_equal DateTime.civil(2011, 10, 11, 12, 13, 14)
2322
2345
  @db.typecast_value(:datetime, :year=>2011, :month=>10, :day=>11, :hour=>12, :minute=>13, :second=>14, :nanos=>500000000).must_equal DateTime.civil(2011, 10, 11, 12, 13, Rational(29, 2))
@@ -2326,8 +2349,16 @@ describe "Database#typecast_value" do
2326
2349
  @db.typecast_value(:datetime, :year=>2011, :month=>10, :day=>11, :hour=>12, :minute=>13, :second=>14, :nanos=>500000000, :offset=>Rational(1, 2)).must_equal DateTime.civil(2011, 10, 11, 12, 13, Rational(29, 2), Rational(1, 2))
2327
2350
  @db.typecast_value(:datetime, 'year'=>2011, 'month'=>10, 'day'=>11, 'hour'=>12, 'minute'=>13, 'second'=>14, 'offset'=>Rational(1, 2)).must_equal DateTime.civil(2011, 10, 11, 12, 13, 14, Rational(1, 2))
2328
2351
  @db.typecast_value(:datetime, 'year'=>2011, 'month'=>10, 'day'=>11, 'hour'=>12, 'minute'=>13, 'second'=>14, 'nanos'=>500000000, 'offset'=>Rational(1, 2)).must_equal DateTime.civil(2011, 10, 11, 12, 13, Rational(29, 2), Rational(1, 2))
2352
+
2353
+ Sequel.default_timezone = :local
2354
+ offset = Rational(Time.local(2011, 10, 11, 12, 13, 14).utc_offset, 86400)
2355
+ @db.typecast_value(:datetime, :year=>2011, :month=>10, :day=>11, :hour=>12, :minute=>13, :second=>14).must_equal DateTime.civil(2011, 10, 11, 12, 13, 14, offset)
2356
+ @db.typecast_value(:datetime, :year=>2011, :month=>10, :day=>11, :hour=>12, :minute=>13, :second=>14, :nanos=>500000000).must_equal DateTime.civil(2011, 10, 11, 12, 13, Rational(29, 2), offset)
2357
+ @db.typecast_value(:datetime, 'year'=>2011, 'month'=>10, 'day'=>11, 'hour'=>12, 'minute'=>13, 'second'=>14).must_equal DateTime.civil(2011, 10, 11, 12, 13, 14, offset)
2358
+ @db.typecast_value(:datetime, 'year'=>2011, 'month'=>10, 'day'=>11, 'hour'=>12, 'minute'=>13, 'second'=>14, 'nanos'=>500000000).must_equal DateTime.civil(2011, 10, 11, 12, 13, Rational(29, 2), offset)
2329
2359
  ensure
2330
2360
  Sequel.datetime_class = Time
2361
+ Sequel.default_timezone = nil
2331
2362
  end
2332
2363
  end
2333
2364
 
@@ -3725,7 +3725,7 @@ end
3725
3725
 
3726
3726
  describe "Dataset default #fetch_rows, #insert, #update, #delete, #truncate, #execute" do
3727
3727
  before do
3728
- @db = Sequel.mock(:servers=>{:read_only=>{}}, :autoid=>1)
3728
+ @db = Sequel.mock(:servers=>{:read_only=>{}, :r1=>{}}, :autoid=>1)
3729
3729
  @ds = @db[:items]
3730
3730
  end
3731
3731
 
@@ -3763,6 +3763,18 @@ describe "Dataset default #fetch_rows, #insert, #update, #delete, #truncate, #ex
3763
3763
  @ds.for_update.send(:execute, 'SELECT 1')
3764
3764
  @db.sqls.must_equal ["SELECT 1"]
3765
3765
  end
3766
+
3767
+ [:execute, :execute_dui, :execute_insert, :execute_ddl].each do |meth|
3768
+ it "##{meth} should respect explicit :server option" do
3769
+ @ds.send(meth, 'SELECT 1', :server=>:r1)
3770
+ @db.sqls.must_equal ["SELECT 1 -- r1"]
3771
+ end
3772
+
3773
+ it "##{meth} should respect dataset's :server option if :server option not given" do
3774
+ @ds.server(:r1).send(meth, 'SELECT 1')
3775
+ @db.sqls.must_equal ["SELECT 1 -- r1"]
3776
+ end
3777
+ end
3766
3778
  end
3767
3779
 
3768
3780
  describe "Dataset#with_sql_*" do
@@ -4493,6 +4505,16 @@ describe "Sequel timezone support" do
4493
4505
  proc{Sequel.database_to_application_timestamp(Object.new)}.must_raise(Sequel::InvalidValue)
4494
4506
  end
4495
4507
 
4508
+ it "should raise an InvalidValue error when the Time class is used and when a bad application timezone is used when attempting to convert timestamps" do
4509
+ Sequel.application_timezone = :blah
4510
+ proc{Sequel.database_to_application_timestamp('2009-06-01 10:20:30')}.must_raise(Sequel::InvalidValue)
4511
+ end
4512
+
4513
+ it "should raise an InvalidValue error when the Time class is used and when a bad database timezone is used when attempting to convert timestamps" do
4514
+ Sequel.database_timezone = :blah
4515
+ proc{Sequel.database_to_application_timestamp('2009-06-01 10:20:30')}.must_raise(Sequel::InvalidValue)
4516
+ end
4517
+
4496
4518
  it "should raise an InvalidValue error when the DateTime class is used and when a bad application timezone is used when attempting to convert timestamps" do
4497
4519
  Sequel.application_timezone = :blah
4498
4520
  Sequel.datetime_class = DateTime
@@ -0,0 +1,77 @@
1
+ require_relative "spec_helper"
2
+
3
+ describe "insert_conflict plugin" do
4
+ def model_class(adapter)
5
+ db = Sequel.mock(:host=>adapter, :fetch=>{:id=>1, :s=>2}, :autoid=>1)
6
+ db.extend_datasets{def quote_identifiers?; false end}
7
+ model = Class.new(Sequel::Model)
8
+ model.dataset = db[:t]
9
+ model.columns :id, :s, :o
10
+ model.plugin :insert_conflict
11
+ db.sqls
12
+ model
13
+ end
14
+
15
+ def model_class_plugin_first(adapter)
16
+ model = Class.new(Sequel::Model)
17
+ model.plugin :insert_conflict
18
+ model = Class.new(model)
19
+ db = Sequel.mock(:host=>adapter, :fetch=>{:id=>1, :s=>2}, :autoid=>1)
20
+ db.extend_datasets{def quote_identifiers?; false end}
21
+ model.dataset = db[:t]
22
+ model.columns :id, :s, :o
23
+ db.sqls
24
+ model
25
+ end
26
+
27
+ it "should use INSERT ON CONFLICT when inserting on PostgreSQL" do
28
+ model = model_class(:postgres)
29
+ model.new(:s=>'A', :o=>1).insert_conflict.save
30
+ model.db.sqls.must_equal ["INSERT INTO t (s, o) VALUES ('A', 1) ON CONFLICT DO NOTHING RETURNING *"]
31
+
32
+ model.new(:s=>'A', :o=>1).insert_conflict(:target=>:s, :update => {:o => Sequel[:excluded][:o]}).save
33
+ model.db.sqls.must_equal ["INSERT INTO t (s, o) VALUES ('A', 1) ON CONFLICT (s) DO UPDATE SET o = excluded.o RETURNING *"]
34
+ end
35
+
36
+ it "should use INSERT ON CONFLICT when inserting on SQLITE" do
37
+ model = model_class(:sqlite)
38
+ model.new(:s=>'A', :o=>1).insert_conflict.save
39
+ model.db.sqls.must_equal ["INSERT INTO t (s, o) VALUES ('A', 1) ON CONFLICT DO NOTHING",
40
+ "SELECT * FROM t WHERE (id = 1) LIMIT 1"]
41
+
42
+ model.new(:s=>'A', :o=>1).insert_conflict(:target=>:s, :update => {:o => Sequel[:excluded][:o]}).save
43
+ model.db.sqls.must_equal ["INSERT INTO t (s, o) VALUES ('A', 1) ON CONFLICT (s) DO UPDATE SET o = excluded.o",
44
+ "SELECT * FROM t WHERE (id = 2) LIMIT 1"]
45
+ end
46
+
47
+ it "should raise Error if calling insert_conflict on a model instance that isn't new" do
48
+ m = model_class(:postgres).load(:s=>'A', :o=>1)
49
+ proc{m.insert_conflict}.must_raise Sequel::Error
50
+ end
51
+
52
+ it "should raise if loading plugin into a model class with a dataset that doesn't support insert_conflict" do
53
+ model = Class.new(Sequel::Model)
54
+ model.dataset = Sequel.mock[:t]
55
+ proc{model.plugin :insert_conflict}.must_raise Sequel::Error
56
+ end
57
+
58
+ it "should work if loading into a model class without a dataset on PostgreSQL" do
59
+ model = model_class_plugin_first(:postgres)
60
+ model.new(:s=>'A', :o=>1).insert_conflict.save
61
+ model.db.sqls.must_equal ["INSERT INTO t (s, o) VALUES ('A', 1) ON CONFLICT DO NOTHING RETURNING *"]
62
+
63
+ model.new(:s=>'A', :o=>1).insert_conflict(:target=>:s, :update => {:o => Sequel[:excluded][:o]}).save
64
+ model.db.sqls.must_equal ["INSERT INTO t (s, o) VALUES ('A', 1) ON CONFLICT (s) DO UPDATE SET o = excluded.o RETURNING *"]
65
+ end
66
+
67
+ it "should work if loading into a model class without a dataset on SQLITE" do
68
+ model = model_class_plugin_first(:sqlite)
69
+ model.new(:s=>'A', :o=>1).insert_conflict.save
70
+ model.db.sqls.must_equal ["INSERT INTO t (s, o) VALUES ('A', 1) ON CONFLICT DO NOTHING",
71
+ "SELECT * FROM t WHERE (id = 1) LIMIT 1"]
72
+
73
+ model.new(:s=>'A', :o=>1).insert_conflict(:target=>:s, :update => {:o => Sequel[:excluded][:o]}).save
74
+ model.db.sqls.must_equal ["INSERT INTO t (s, o) VALUES ('A', 1) ON CONFLICT (s) DO UPDATE SET o = excluded.o",
75
+ "SELECT * FROM t WHERE (id = 2) LIMIT 1"]
76
+ end
77
+ end