sequel 5.22.0 → 5.23.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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