sequel 5.22.0 → 5.23.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +24 -0
- data/doc/dataset_filtering.rdoc +15 -0
- data/doc/opening_databases.rdoc +3 -0
- data/doc/release_notes/5.23.0.txt +56 -0
- data/doc/testing.rdoc +1 -0
- data/lib/sequel/adapters/jdbc/sqlite.rb +29 -0
- data/lib/sequel/adapters/shared/mssql.rb +5 -6
- data/lib/sequel/adapters/shared/sqlite.rb +16 -2
- data/lib/sequel/adapters/tinytds.rb +12 -0
- data/lib/sequel/dataset/actions.rb +3 -2
- data/lib/sequel/extensions/named_timezones.rb +51 -9
- data/lib/sequel/extensions/pg_array.rb +4 -0
- data/lib/sequel/extensions/pg_json.rb +87 -16
- data/lib/sequel/extensions/pg_row.rb +3 -1
- data/lib/sequel/plugins/association_proxies.rb +3 -2
- data/lib/sequel/plugins/class_table_inheritance.rb +10 -0
- data/lib/sequel/plugins/insert_conflict.rb +72 -0
- data/lib/sequel/timezones.rb +50 -11
- data/lib/sequel/version.rb +1 -1
- data/spec/bin_spec.rb +1 -1
- data/spec/core/database_spec.rb +31 -0
- data/spec/core/dataset_spec.rb +23 -1
- data/spec/extensions/insert_conflict_spec.rb +77 -0
- data/spec/extensions/named_timezones_spec.rb +109 -2
- data/spec/extensions/pg_json_spec.rb +12 -0
- data/spec/extensions/pg_range_spec.rb +5 -2
- data/spec/extensions/spec_helper.rb +8 -1
- data/spec/integration/plugin_test.rb +27 -0
- data/spec/integration/schema_test.rb +7 -2
- metadata +6 -2
@@ -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
|
-
#
|
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
|
data/lib/sequel/timezones.rb
CHANGED
@@ -54,7 +54,14 @@ module Sequel
|
|
54
54
|
convert_output_datetime_other(v, output_timezone)
|
55
55
|
end
|
56
56
|
else
|
57
|
-
|
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
|
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
|
-
|
156
|
+
convert_input_datetime_no_offset(v2, input_timezone)
|
143
157
|
else
|
144
|
-
|
145
|
-
|
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
|
-
|
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
|
-
|
205
|
-
|
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
|
data/lib/sequel/version.rb
CHANGED
@@ -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 =
|
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.
|
data/spec/bin_spec.rb
CHANGED
@@ -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
|
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'}]
|
data/spec/core/database_spec.rb
CHANGED
@@ -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
|
|
data/spec/core/dataset_spec.rb
CHANGED
@@ -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
|