sequel 5.19.0 → 5.24.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +102 -0
- data/doc/dataset_filtering.rdoc +15 -0
- data/doc/opening_databases.rdoc +5 -1
- data/doc/release_notes/5.20.0.txt +89 -0
- data/doc/release_notes/5.21.0.txt +87 -0
- data/doc/release_notes/5.22.0.txt +48 -0
- data/doc/release_notes/5.23.0.txt +56 -0
- data/doc/release_notes/5.24.0.txt +56 -0
- data/doc/sharding.rdoc +2 -0
- data/doc/testing.rdoc +1 -0
- data/doc/transactions.rdoc +38 -0
- data/lib/sequel/adapters/ado.rb +27 -19
- data/lib/sequel/adapters/jdbc.rb +7 -1
- data/lib/sequel/adapters/jdbc/mysql.rb +2 -2
- data/lib/sequel/adapters/jdbc/postgresql.rb +1 -13
- data/lib/sequel/adapters/jdbc/sqlite.rb +29 -0
- data/lib/sequel/adapters/mysql2.rb +2 -3
- data/lib/sequel/adapters/shared/mssql.rb +7 -7
- data/lib/sequel/adapters/shared/postgres.rb +37 -19
- data/lib/sequel/adapters/shared/sqlite.rb +27 -3
- data/lib/sequel/adapters/sqlite.rb +1 -1
- data/lib/sequel/adapters/tinytds.rb +12 -0
- data/lib/sequel/adapters/utils/mysql_mysql2.rb +2 -0
- data/lib/sequel/database/logging.rb +7 -1
- data/lib/sequel/database/query.rb +1 -1
- data/lib/sequel/database/schema_generator.rb +12 -3
- data/lib/sequel/database/schema_methods.rb +2 -0
- data/lib/sequel/database/transactions.rb +57 -5
- data/lib/sequel/dataset.rb +4 -2
- data/lib/sequel/dataset/actions.rb +3 -2
- data/lib/sequel/dataset/placeholder_literalizer.rb +4 -1
- data/lib/sequel/dataset/query.rb +5 -1
- data/lib/sequel/dataset/sql.rb +11 -7
- data/lib/sequel/extensions/named_timezones.rb +52 -8
- data/lib/sequel/extensions/pg_array.rb +4 -0
- data/lib/sequel/extensions/pg_json.rb +387 -123
- data/lib/sequel/extensions/pg_range.rb +3 -2
- data/lib/sequel/extensions/pg_row.rb +3 -1
- data/lib/sequel/extensions/schema_dumper.rb +1 -1
- data/lib/sequel/extensions/server_block.rb +15 -4
- data/lib/sequel/model/associations.rb +35 -9
- data/lib/sequel/model/plugins.rb +104 -0
- data/lib/sequel/plugins/association_dependencies.rb +3 -3
- data/lib/sequel/plugins/association_pks.rb +14 -4
- data/lib/sequel/plugins/association_proxies.rb +3 -2
- data/lib/sequel/plugins/class_table_inheritance.rb +11 -0
- data/lib/sequel/plugins/composition.rb +13 -9
- data/lib/sequel/plugins/finder.rb +2 -2
- data/lib/sequel/plugins/hook_class_methods.rb +17 -5
- data/lib/sequel/plugins/insert_conflict.rb +72 -0
- data/lib/sequel/plugins/inverted_subsets.rb +2 -2
- data/lib/sequel/plugins/pg_auto_constraint_validations.rb +147 -59
- data/lib/sequel/plugins/rcte_tree.rb +6 -0
- data/lib/sequel/plugins/static_cache.rb +8 -3
- data/lib/sequel/plugins/static_cache_cache.rb +53 -0
- data/lib/sequel/plugins/subset_conditions.rb +2 -2
- data/lib/sequel/plugins/validation_class_methods.rb +5 -3
- data/lib/sequel/sql.rb +15 -3
- data/lib/sequel/timezones.rb +50 -11
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/mssql_spec.rb +24 -0
- data/spec/adapters/mysql_spec.rb +0 -5
- data/spec/adapters/postgres_spec.rb +319 -1
- data/spec/bin_spec.rb +1 -1
- data/spec/core/database_spec.rb +123 -2
- data/spec/core/dataset_spec.rb +33 -1
- data/spec/core/expression_filters_spec.rb +25 -1
- data/spec/core/schema_spec.rb +24 -0
- data/spec/extensions/class_table_inheritance_spec.rb +30 -8
- data/spec/extensions/core_refinements_spec.rb +1 -1
- data/spec/extensions/hook_class_methods_spec.rb +22 -0
- data/spec/extensions/insert_conflict_spec.rb +103 -0
- data/spec/extensions/migration_spec.rb +13 -0
- data/spec/extensions/named_timezones_spec.rb +109 -2
- data/spec/extensions/pg_auto_constraint_validations_spec.rb +45 -0
- data/spec/extensions/pg_json_spec.rb +218 -29
- data/spec/extensions/pg_range_spec.rb +76 -9
- data/spec/extensions/rcte_tree_spec.rb +6 -0
- data/spec/extensions/s_spec.rb +1 -1
- data/spec/extensions/schema_dumper_spec.rb +4 -2
- data/spec/extensions/server_block_spec.rb +38 -0
- data/spec/extensions/spec_helper.rb +8 -1
- data/spec/extensions/static_cache_cache_spec.rb +35 -0
- data/spec/integration/dataset_test.rb +25 -9
- data/spec/integration/plugin_test.rb +42 -0
- data/spec/integration/schema_test.rb +7 -2
- data/spec/integration/transaction_test.rb +50 -0
- data/spec/model/associations_spec.rb +84 -4
- data/spec/model/plugins_spec.rb +111 -0
- metadata +16 -2
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
module Sequel
|
4
|
+
module Plugins
|
5
|
+
# The static_cache_cache plugin allows for caching the row content for subclasses
|
6
|
+
# that use the static cache plugin (or just the current class). Using this plugin
|
7
|
+
# can avoid the need to query the database every time loading the plugin into a
|
8
|
+
# model, which can save time when you have a lot of models using the static_cache
|
9
|
+
# plugin.
|
10
|
+
#
|
11
|
+
# Usage:
|
12
|
+
#
|
13
|
+
# # Make all model subclasses that use the static_cache plugin use
|
14
|
+
# # the cached values in the given file
|
15
|
+
# Sequel::Model.plugin :static_cache_cache, "static_cache.cache"
|
16
|
+
#
|
17
|
+
# # Make the AlbumType model the cached values in the given file,
|
18
|
+
# # should be loaded before the static_cache plugin
|
19
|
+
# AlbumType.plugin :static_cache_cache, "static_cache.cache"
|
20
|
+
module StaticCacheCache
|
21
|
+
def self.configure(model, file)
|
22
|
+
model.instance_variable_set(:@static_cache_cache_file, file)
|
23
|
+
model.instance_variable_set(:@static_cache_cache, File.exist?(file) ? Marshal.load(File.read(file)) : {})
|
24
|
+
end
|
25
|
+
|
26
|
+
module ClassMethods
|
27
|
+
# Dump the in-memory cached rows to the cache file.
|
28
|
+
def dump_static_cache_cache
|
29
|
+
File.open(@static_cache_cache_file, 'wb'){|f| f.write(Marshal.dump(@static_cache_cache))}
|
30
|
+
nil
|
31
|
+
end
|
32
|
+
|
33
|
+
Plugins.inherited_instance_variables(self, :@static_cache_cache_file=>nil, :@static_cache_cache=>nil)
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
# Load the rows for the model from the cache if available.
|
38
|
+
# If not available, load the rows from the database, and
|
39
|
+
# then update the cache with the raw rows.
|
40
|
+
def load_static_cache_rows
|
41
|
+
if rows = Sequel.synchronize{@static_cache_cache[name]}
|
42
|
+
rows.map{|row| call(row)}.freeze
|
43
|
+
else
|
44
|
+
rows = dataset.all.freeze
|
45
|
+
raw_rows = rows.map(&:values)
|
46
|
+
Sequel.synchronize{@static_cache_cache[name] = raw_rows}
|
47
|
+
rows
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -26,8 +26,8 @@ module Sequel
|
|
26
26
|
# Album.where(Album.published_conditions | {ready: true}).sql
|
27
27
|
# # SELECT * FROM albums WHERE ((published IS TRUE) OR (ready IS TRUE))
|
28
28
|
module SubsetConditions
|
29
|
-
def self.apply(
|
30
|
-
|
29
|
+
def self.apply(model, &block)
|
30
|
+
model.instance_exec do
|
31
31
|
@dataset_module_class = Class.new(@dataset_module_class) do
|
32
32
|
include DatasetModuleMethods
|
33
33
|
end
|
@@ -188,13 +188,17 @@ module Sequel
|
|
188
188
|
# Sequel will attempt to insert a NULL value into the database, instead of using the
|
189
189
|
# database's default.
|
190
190
|
# :allow_nil :: Whether to skip the validation if the value is nil.
|
191
|
-
# :if :: A symbol (indicating an instance_method) or proc (which is
|
191
|
+
# :if :: A symbol (indicating an instance_method) or proc (which is used to define an instance method)
|
192
192
|
# skipping this validation if it returns nil or false.
|
193
193
|
# :tag :: The tag to use for this validation.
|
194
194
|
def validates_each(*atts, &block)
|
195
195
|
opts = extract_options!(atts)
|
196
196
|
blank_meth = db.method(:blank_object?).to_proc
|
197
197
|
blk = if (i = opts[:if]) || (am = opts[:allow_missing]) || (an = opts[:allow_nil]) || (ab = opts[:allow_blank])
|
198
|
+
if i.is_a?(Proc)
|
199
|
+
i = Plugins.def_sequel_method(self, "validation_class_methods_if", 0, &i)
|
200
|
+
end
|
201
|
+
|
198
202
|
proc do |o,a,v|
|
199
203
|
next if i && !validation_if_proc(o, i)
|
200
204
|
next if an && Array(v).all?(&:nil?)
|
@@ -434,8 +438,6 @@ module Sequel
|
|
434
438
|
case i
|
435
439
|
when Symbol
|
436
440
|
o.get_column_value(i)
|
437
|
-
when Proc
|
438
|
-
o.instance_exec(&i)
|
439
441
|
else
|
440
442
|
raise(::Sequel::Error, "invalid value for :if validation option")
|
441
443
|
end
|
data/lib/sequel/sql.rb
CHANGED
@@ -1086,11 +1086,23 @@ module Sequel
|
|
1086
1086
|
def self.from_value_pair(l, r)
|
1087
1087
|
case r
|
1088
1088
|
when Range
|
1089
|
-
|
1089
|
+
unless r.begin.nil?
|
1090
|
+
begin_expr = new(:>=, l, r.begin)
|
1091
|
+
end
|
1090
1092
|
unless r.end.nil?
|
1091
|
-
|
1093
|
+
end_expr = new(r.exclude_end? ? :< : :<=, l, r.end)
|
1094
|
+
end
|
1095
|
+
if begin_expr
|
1096
|
+
if end_expr
|
1097
|
+
new(:AND, begin_expr, end_expr)
|
1098
|
+
else
|
1099
|
+
begin_expr
|
1100
|
+
end
|
1101
|
+
elsif end_expr
|
1102
|
+
end_expr
|
1103
|
+
else
|
1104
|
+
new(:'=', 1, 1)
|
1092
1105
|
end
|
1093
|
-
expr
|
1094
1106
|
when ::Array
|
1095
1107
|
r = r.dup.freeze unless r.frozen?
|
1096
1108
|
new(:IN, l, r)
|
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 = 24
|
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/adapters/mssql_spec.rb
CHANGED
@@ -40,6 +40,30 @@ describe "A MSSQL database" do
|
|
40
40
|
end
|
41
41
|
end
|
42
42
|
|
43
|
+
describe "MSSQL decimal locale handling" do
|
44
|
+
before do
|
45
|
+
@locale = WIN32OLE.locale
|
46
|
+
@decimal = BigDecimal('1234.56')
|
47
|
+
end
|
48
|
+
after do
|
49
|
+
WIN32OLE.locale = @locale
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should work with current locale" do
|
53
|
+
DB.get(Sequel.cast(@decimal, 'decimal(16,4)').as(:v)).must_equal @decimal
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should work with 1031 locale" do
|
57
|
+
WIN32OLE.locale = 1031
|
58
|
+
DB.get(Sequel.cast(@decimal, 'decimal(16,4)').as(:v)).must_equal @decimal
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should work with 1033 locale" do
|
62
|
+
WIN32OLE.locale = 1033
|
63
|
+
DB.get(Sequel.cast(@decimal, 'decimal(16,4)').as(:v)).must_equal @decimal
|
64
|
+
end
|
65
|
+
end if DB.adapter_scheme == :ado
|
66
|
+
|
43
67
|
describe "MSSQL" do
|
44
68
|
before(:all) do
|
45
69
|
@db = DB
|
data/spec/adapters/mysql_spec.rb
CHANGED
@@ -551,11 +551,6 @@ describe "A MySQL database" do
|
|
551
551
|
db = Sequel.connect(DB.opts.merge(:read_timeout=>22342))
|
552
552
|
db.test_connection
|
553
553
|
end
|
554
|
-
|
555
|
-
it "should accept a connect_timeout option when connecting" do
|
556
|
-
db = Sequel.connect(DB.opts.merge(:connect_timeout=>22342))
|
557
|
-
db.test_connection
|
558
|
-
end
|
559
554
|
end
|
560
555
|
|
561
556
|
describe "MySQL foreign key support" do
|
@@ -104,6 +104,24 @@ describe "PostgreSQL", '#create_table' do
|
|
104
104
|
@db.check_constraints(:tmp_dolls).must_equal(:ic=>{:definition=>"CHECK ((i > 2))", :columns=>[:i]}, :jc=>{:definition=>"CHECK ((j > 2))", :columns=>[:j]}, :ijc=>{:definition=>"CHECK (((i - j) > 2))", :columns=>[:i, :j]})
|
105
105
|
end
|
106
106
|
|
107
|
+
it "should have #check_constraints return check constraints where columns are unknown" do
|
108
|
+
begin
|
109
|
+
@db.create_table(:tmp_dolls) do
|
110
|
+
Integer :i
|
111
|
+
Integer :j
|
112
|
+
end
|
113
|
+
@db.run "CREATE OR REPLACE FUNCTION valid_tmp_dolls(t1 tmp_dolls) RETURNS boolean AS 'SELECT false' LANGUAGE SQL;"
|
114
|
+
@db.alter_table(:tmp_dolls) do
|
115
|
+
add_constraint(:valid_tmp_dolls, Sequel.function(:valid_tmp_dolls, :tmp_dolls))
|
116
|
+
end
|
117
|
+
|
118
|
+
@db.check_constraints(:tmp_dolls).must_equal(:valid_tmp_dolls=>{:definition=>"CHECK (valid_tmp_dolls(tmp_dolls.*))", :columns=>[]})
|
119
|
+
ensure
|
120
|
+
@db.run "ALTER TABLE tmp_dolls DROP CONSTRAINT IF EXISTS valid_tmp_dolls"
|
121
|
+
@db.run "DROP FUNCTION IF EXISTS valid_tmp_dolls(tmp_dolls)"
|
122
|
+
end
|
123
|
+
end if DB.server_version >= 90000
|
124
|
+
|
107
125
|
it "should not allow to pass both :temp and :unlogged" do
|
108
126
|
proc do
|
109
127
|
@db.create_table(:temp_unlogged_dolls, :temp => true, :unlogged => true){text :name}
|
@@ -209,6 +227,24 @@ describe "PostgreSQL", '#create_table' do
|
|
209
227
|
@db.convert_serial_to_identity(:tmp_dolls, :column=>:id)
|
210
228
|
end if DB.server_version >= 100002 && DB.get{current_setting('is_superuser')} == 'on'
|
211
229
|
|
230
|
+
it "should support creating generated columns" do
|
231
|
+
@db.create_table(:tmp_dolls){Integer :a; Integer :b; Integer :c, :generated_always_as=>Sequel[:a] * 2 + :b + 1}
|
232
|
+
@db[:tmp_dolls].insert(:a=>100, :b=>10)
|
233
|
+
@db[:tmp_dolls].select_order_map([:a, :b, :c]).must_equal [[100, 10, 211]]
|
234
|
+
end if DB.server_version >= 120000
|
235
|
+
|
236
|
+
it "should support deferred primary key and unique constraints on columns" do
|
237
|
+
@db.create_table(:tmp_dolls){primary_key :id, :primary_key_deferrable=>true; Integer :i, :unique=>true, :unique_deferrable=>true}
|
238
|
+
@db[:tmp_dolls].insert(:i=>10)
|
239
|
+
DB.transaction do
|
240
|
+
@db[:tmp_dolls].insert(:id=>1, :i=>1)
|
241
|
+
@db[:tmp_dolls].insert(:id=>10, :i=>10)
|
242
|
+
@db[:tmp_dolls].where(:i=>1).update(:id=>2)
|
243
|
+
@db[:tmp_dolls].where(:id=>10).update(:i=>2)
|
244
|
+
end
|
245
|
+
@db[:tmp_dolls].select_order_map([:id, :i]).must_equal [[1, 10], [2, 1], [10, 2]]
|
246
|
+
end if DB.server_version >= 90000
|
247
|
+
|
212
248
|
it "should support pg_loose_count extension" do
|
213
249
|
@db.extension :pg_loose_count
|
214
250
|
@db.create_table(:tmp_dolls){text :name}
|
@@ -334,6 +370,26 @@ describe "PostgreSQL", 'INSERT ON CONFLICT' do
|
|
334
370
|
end
|
335
371
|
end if DB.server_version >= 90500
|
336
372
|
|
373
|
+
describe "A PostgreSQL database" do
|
374
|
+
before do
|
375
|
+
@db = DB
|
376
|
+
@db.create_table!(:cte_test){Integer :id}
|
377
|
+
end
|
378
|
+
after do
|
379
|
+
@db.drop_table?(:cte_test)
|
380
|
+
end
|
381
|
+
|
382
|
+
it "should give correct results for WITH AS [NOT] MATERIALIZED" do
|
383
|
+
@ds = @db[:cte_test]
|
384
|
+
@ds.insert(1)
|
385
|
+
@ds.insert(2)
|
386
|
+
|
387
|
+
@db[:t].with(:t, @ds, :materialized=>nil).order(:id).map(:id).must_equal [1, 2]
|
388
|
+
@db[:t].with(:t, @ds, :materialized=>true).order(:id).map(:id).must_equal [1, 2]
|
389
|
+
@db[:t].with(:t, @ds, :materialized=>false).order(:id).map(:id).must_equal [1, 2]
|
390
|
+
end
|
391
|
+
end if DB.server_version >= 120000
|
392
|
+
|
337
393
|
describe "A PostgreSQL database" do
|
338
394
|
before(:all) do
|
339
395
|
@db = DB
|
@@ -2926,6 +2982,8 @@ describe 'PostgreSQL json type' do
|
|
2926
2982
|
@h = {'a'=>'b', '1'=>[3, 4, 5]}
|
2927
2983
|
end
|
2928
2984
|
after do
|
2985
|
+
@db.wrap_json_primitives = nil
|
2986
|
+
@db.typecast_json_strings = nil
|
2929
2987
|
@db.drop_table?(:items)
|
2930
2988
|
end
|
2931
2989
|
|
@@ -2933,9 +2991,12 @@ describe 'PostgreSQL json type' do
|
|
2933
2991
|
json_types << :jsonb if DB.server_version >= 90400
|
2934
2992
|
json_types.each do |json_type|
|
2935
2993
|
json_array_type = "#{json_type}[]"
|
2936
|
-
pg_json =
|
2994
|
+
pg_json = Sequel.method(:"pg_#{json_type}")
|
2995
|
+
pg_json_wrap = Sequel.method(:"pg_#{json_type}_wrap")
|
2937
2996
|
hash_class = json_type == :jsonb ? Sequel::Postgres::JSONBHash : Sequel::Postgres::JSONHash
|
2938
2997
|
array_class = json_type == :jsonb ? Sequel::Postgres::JSONBArray : Sequel::Postgres::JSONArray
|
2998
|
+
str_class = json_type == :jsonb ? Sequel::Postgres::JSONBString : Sequel::Postgres::JSONString
|
2999
|
+
object_class = json_type == :jsonb ? Sequel::Postgres::JSONBObject : Sequel::Postgres::JSONObject
|
2939
3000
|
|
2940
3001
|
it 'insert and retrieve json values' do
|
2941
3002
|
@db.create_table!(:items){column :j, json_type}
|
@@ -2965,6 +3026,44 @@ describe 'PostgreSQL json type' do
|
|
2965
3026
|
@ds.all.must_equal rs
|
2966
3027
|
end
|
2967
3028
|
|
3029
|
+
it 'insert and retrieve json primitive values' do
|
3030
|
+
@db.create_table!(:items){column :j, json_type}
|
3031
|
+
['str', 1, 2.5, nil, true, false].each do |rv|
|
3032
|
+
@ds.delete
|
3033
|
+
@ds.insert(pg_json_wrap.call(rv))
|
3034
|
+
@ds.count.must_equal 1
|
3035
|
+
rs = @ds.all
|
3036
|
+
v = rs.first[:j]
|
3037
|
+
v.class.must_equal(rv.class)
|
3038
|
+
if rv.nil?
|
3039
|
+
v.must_be_nil
|
3040
|
+
else
|
3041
|
+
v.must_equal rv
|
3042
|
+
end
|
3043
|
+
end
|
3044
|
+
|
3045
|
+
@db.wrap_json_primitives = true
|
3046
|
+
['str', 1, 2.5, nil, true, false].each do |rv|
|
3047
|
+
@ds.delete
|
3048
|
+
@ds.insert(pg_json_wrap.call(rv))
|
3049
|
+
@ds.count.must_equal 1
|
3050
|
+
rs = @ds.all
|
3051
|
+
v = rs.first[:j]
|
3052
|
+
v.class.ancestors.must_include(object_class)
|
3053
|
+
v.__getobj__.must_be_kind_of(rv.class)
|
3054
|
+
if rv.nil?
|
3055
|
+
v.must_be_nil
|
3056
|
+
v.__getobj__.must_be_nil
|
3057
|
+
else
|
3058
|
+
v.must_equal rv
|
3059
|
+
v.__getobj__.must_equal rv
|
3060
|
+
end
|
3061
|
+
@ds.delete
|
3062
|
+
@ds.insert(rs.first)
|
3063
|
+
@ds.all[0][:j].must_equal rs[0][:j]
|
3064
|
+
end
|
3065
|
+
end
|
3066
|
+
|
2968
3067
|
it 'insert and retrieve json[] values' do
|
2969
3068
|
@db.create_table!(:items){column :j, json_array_type}
|
2970
3069
|
j = Sequel.pg_array([pg_json.call('a'=>1), pg_json.call(['b', 2])])
|
@@ -2981,16 +3080,122 @@ describe 'PostgreSQL json type' do
|
|
2981
3080
|
@ds.all.must_equal rs
|
2982
3081
|
end
|
2983
3082
|
|
3083
|
+
it 'insert and retrieve json[] values with json primitives' do
|
3084
|
+
@db.create_table!(:items){column :j, json_array_type}
|
3085
|
+
raw = ['str', 1, 2.5, nil, true, false]
|
3086
|
+
j = Sequel.pg_array(raw.map(&pg_json_wrap), json_type)
|
3087
|
+
@ds.insert(j)
|
3088
|
+
@ds.count.must_equal 1
|
3089
|
+
rs = @ds.all
|
3090
|
+
v = rs.first[:j]
|
3091
|
+
v.class.must_equal(Sequel::Postgres::PGArray)
|
3092
|
+
v.to_a.must_be_kind_of(Array)
|
3093
|
+
v.map(&:class).must_equal raw.map(&:class)
|
3094
|
+
v.must_equal raw
|
3095
|
+
v.to_a.must_equal raw
|
3096
|
+
|
3097
|
+
@db.wrap_json_primitives = true
|
3098
|
+
j = Sequel.pg_array(raw.map(&pg_json_wrap), json_type)
|
3099
|
+
@ds.insert(j)
|
3100
|
+
rs = @ds.all
|
3101
|
+
v = rs.first[:j]
|
3102
|
+
v.class.must_equal(Sequel::Postgres::PGArray)
|
3103
|
+
v.to_a.must_be_kind_of(Array)
|
3104
|
+
v.map(&:class).each{|c| c.ancestors.must_include(object_class)}
|
3105
|
+
[v, v.to_a].each do |v0|
|
3106
|
+
v0.zip(raw) do |v1, r1|
|
3107
|
+
if r1.nil?
|
3108
|
+
v1.must_be_nil
|
3109
|
+
v1.__getobj__.must_be_nil
|
3110
|
+
else
|
3111
|
+
v1.must_equal r1
|
3112
|
+
v1.__getobj__.must_equal r1
|
3113
|
+
end
|
3114
|
+
end
|
3115
|
+
end
|
3116
|
+
@ds.delete
|
3117
|
+
@ds.insert(rs.first)
|
3118
|
+
@ds.all[0][:j].zip(rs[0][:j]) do |v1, r1|
|
3119
|
+
if v1.__getobj__.nil?
|
3120
|
+
v1.must_be_nil
|
3121
|
+
v1.__getobj__.must_be_nil
|
3122
|
+
else
|
3123
|
+
v1.must_equal r1
|
3124
|
+
v1.must_equal r1.__getobj__
|
3125
|
+
v1.__getobj__.must_equal r1
|
3126
|
+
v1.__getobj__.must_equal r1.__getobj__
|
3127
|
+
end
|
3128
|
+
end
|
3129
|
+
end
|
3130
|
+
|
2984
3131
|
it 'with models' do
|
2985
3132
|
@db.create_table!(:items) do
|
2986
3133
|
primary_key :id
|
2987
3134
|
column :h, json_type
|
2988
3135
|
end
|
2989
3136
|
c = Class.new(Sequel::Model(@db[:items]))
|
3137
|
+
c.create(:h=>@h).h.must_equal @h
|
3138
|
+
c.create(:h=>@a).h.must_equal @a
|
2990
3139
|
c.create(:h=>pg_json.call(@h)).h.must_equal @h
|
2991
3140
|
c.create(:h=>pg_json.call(@a)).h.must_equal @a
|
2992
3141
|
end
|
2993
3142
|
|
3143
|
+
it 'with models with json primitives' do
|
3144
|
+
@db.create_table!(:items) do
|
3145
|
+
primary_key :id
|
3146
|
+
column :h, json_type
|
3147
|
+
end
|
3148
|
+
c = Class.new(Sequel::Model(@db[:items]))
|
3149
|
+
|
3150
|
+
['str', 1, 2.5, nil, true, false].each do |v|
|
3151
|
+
@db.wrap_json_primitives = nil
|
3152
|
+
cv = c[c.insert(:h=>pg_json_wrap.call(v))]
|
3153
|
+
cv.h.class.ancestors.wont_include(object_class)
|
3154
|
+
if v.nil?
|
3155
|
+
cv.h.must_be_nil
|
3156
|
+
else
|
3157
|
+
cv.h.must_equal v
|
3158
|
+
end
|
3159
|
+
|
3160
|
+
@db.wrap_json_primitives = true
|
3161
|
+
cv.refresh
|
3162
|
+
cv.h.class.ancestors.must_include(object_class)
|
3163
|
+
cv.save
|
3164
|
+
cv.refresh
|
3165
|
+
cv.h.class
|
3166
|
+
|
3167
|
+
if v.nil?
|
3168
|
+
cv.h.must_be_nil
|
3169
|
+
else
|
3170
|
+
cv.h.must_equal v
|
3171
|
+
end
|
3172
|
+
|
3173
|
+
c.new(:h=>cv.h).h.class.ancestors.must_include(object_class)
|
3174
|
+
end
|
3175
|
+
|
3176
|
+
v = c.new(:h=>'{}').h
|
3177
|
+
v.class.must_equal hash_class
|
3178
|
+
v.must_equal({})
|
3179
|
+
@db.typecast_json_strings = true
|
3180
|
+
v = c.new(:h=>'{}').h
|
3181
|
+
v.class.must_equal str_class
|
3182
|
+
v.must_equal '{}'
|
3183
|
+
|
3184
|
+
c.new(:h=>'str').h.class.ancestors.must_include(object_class)
|
3185
|
+
c.new(:h=>'str').h.must_equal 'str'
|
3186
|
+
c.new(:h=>1).h.class.ancestors.must_include(object_class)
|
3187
|
+
c.new(:h=>1).h.must_equal 1
|
3188
|
+
c.new(:h=>2.5).h.class.ancestors.must_include(object_class)
|
3189
|
+
c.new(:h=>2.5).h.must_equal 2.5
|
3190
|
+
c.new(:h=>true).h.class.ancestors.must_include(object_class)
|
3191
|
+
c.new(:h=>true).h.must_equal true
|
3192
|
+
c.new(:h=>false).h.class.ancestors.must_include(object_class)
|
3193
|
+
c.new(:h=>false).h.must_equal false
|
3194
|
+
|
3195
|
+
c.new(:h=>nil).h.class.ancestors.wont_include(object_class)
|
3196
|
+
c.new(:h=>nil).h.must_be_nil
|
3197
|
+
end
|
3198
|
+
|
2994
3199
|
it 'with empty json default values and defaults_setter plugin' do
|
2995
3200
|
@db.create_table!(:items) do
|
2996
3201
|
column :h, json_type, :default=>hash_class.new({})
|
@@ -3025,6 +3230,36 @@ describe 'PostgreSQL json type' do
|
|
3025
3230
|
@ds.get(:i).must_equal j
|
3026
3231
|
end if uses_pg_or_jdbc
|
3027
3232
|
|
3233
|
+
it 'use json primitives in bound variables' do
|
3234
|
+
@db.create_table!(:items){column :i, json_type}
|
3235
|
+
@db.wrap_json_primitives = true
|
3236
|
+
raw = ['str', 1, 2.5, nil, true, false]
|
3237
|
+
raw.each do |v|
|
3238
|
+
@ds.delete
|
3239
|
+
@ds.call(:insert, {:i=>@db.get(pg_json_wrap.call(v))}, {:i=>:$i})
|
3240
|
+
rv = @ds.get(:i)
|
3241
|
+
rv.class.ancestors.must_include(object_class)
|
3242
|
+
if v.nil?
|
3243
|
+
rv.must_be_nil
|
3244
|
+
else
|
3245
|
+
rv.must_equal v
|
3246
|
+
end
|
3247
|
+
end
|
3248
|
+
|
3249
|
+
@db.create_table!(:items){column :i, json_array_type}
|
3250
|
+
j = Sequel.pg_array(raw.map(&pg_json_wrap), json_type)
|
3251
|
+
@ds.call(:insert, {:i=>j}, {:i=>:$i})
|
3252
|
+
@ds.all[0][:i].zip(raw) do |v1, r1|
|
3253
|
+
if v1.__getobj__.nil?
|
3254
|
+
v1.must_be_nil
|
3255
|
+
v1.__getobj__.must_be_nil
|
3256
|
+
else
|
3257
|
+
v1.must_equal r1
|
3258
|
+
v1.__getobj__.must_equal r1
|
3259
|
+
end
|
3260
|
+
end
|
3261
|
+
end if uses_pg_or_jdbc
|
3262
|
+
|
3028
3263
|
it 'operations/functions with pg_json_ops' do
|
3029
3264
|
Sequel.extension :pg_json_ops
|
3030
3265
|
jo = pg_json.call('a'=>1, 'b'=>{'c'=>2, 'd'=>{'e'=>3}}).op
|
@@ -3384,6 +3619,30 @@ describe 'PostgreSQL range types' do
|
|
3384
3619
|
@ds.filter(h).call(:delete, @ra).must_equal 1
|
3385
3620
|
end if uses_pg_or_jdbc
|
3386
3621
|
|
3622
|
+
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...')
|
3629
|
+
end if RUBY_VERSION >= '2.6'
|
3630
|
+
|
3631
|
+
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")
|
3636
|
+
end if RUBY_VERSION >= '2.7'
|
3637
|
+
|
3638
|
+
it 'handle startless ranges' do
|
3639
|
+
@db.get(Sequel.cast(eval('nil...nil'), :int4range)).must_be :==, Sequel::Postgres::PGRange.new(nil, nil, :exclude_begin=>true, :exclude_end=>true, :db_type=>"int4range")
|
3640
|
+
@db.get(Sequel.cast(eval('nil...nil'), :int4range)).wont_be :==, Sequel::Postgres::PGRange.new(nil, nil, :exclude_begin=>true, :db_type=>"int4range")
|
3641
|
+
@db.get(Sequel.cast(eval('nil...nil'), :int4range)).wont_be :==, Sequel::Postgres::PGRange.new(nil, nil, :exclude_end=>true, :db_type=>"int4range")
|
3642
|
+
@db.get(Sequel.cast(eval('nil...nil'), :int4range)).wont_be :==, Sequel::Postgres::PGRange.new(1, nil, :exclude_begin=>true, :db_type=>"int4range")
|
3643
|
+
@db.get(Sequel.cast(eval('nil...nil'), :int4range)).wont_be :==, Sequel::Postgres::PGRange.new(nil, 1, :exclude_begin=>true, :db_type=>"int4range")
|
3644
|
+
end if RUBY_VERSION >= '2.7'
|
3645
|
+
|
3387
3646
|
it 'parse default values for schema' do
|
3388
3647
|
@db.create_table!(:items) do
|
3389
3648
|
Integer :j
|
@@ -3988,6 +4247,10 @@ describe "pg_auto_constraint_validations plugin" do
|
|
3988
4247
|
constraint :valid_i, Sequel[:i] < 10
|
3989
4248
|
constraint(:valid_i_id, Sequel[:i] + Sequel[:id] < 20)
|
3990
4249
|
end
|
4250
|
+
@db.run "CREATE OR REPLACE FUNCTION valid_test1(t1 test1) RETURNS boolean AS 'SELECT t1.i != -100' LANGUAGE SQL;"
|
4251
|
+
@db.alter_table(:test1) do
|
4252
|
+
add_constraint(:valid_test1, Sequel.function(:valid_test1, :test1))
|
4253
|
+
end
|
3991
4254
|
@db.create_table!(:test2) do
|
3992
4255
|
Integer :test2_id, :primary_key=>true
|
3993
4256
|
foreign_key :test1_id, :test1
|
@@ -4008,6 +4271,8 @@ describe "pg_auto_constraint_validations plugin" do
|
|
4008
4271
|
@c2.insert(:test2_id=>3, :test1_id=>1)
|
4009
4272
|
end
|
4010
4273
|
after(:all) do
|
4274
|
+
@db.run "ALTER TABLE test1 DROP CONSTRAINT IF EXISTS valid_test1"
|
4275
|
+
@db.run "DROP FUNCTION IF EXISTS valid_test1(test1)"
|
4011
4276
|
@db.drop_table?(:test2, :test1)
|
4012
4277
|
end
|
4013
4278
|
|
@@ -4017,6 +4282,14 @@ describe "pg_auto_constraint_validations plugin" do
|
|
4017
4282
|
o.errors.must_equal(:i=>['is invalid'])
|
4018
4283
|
end
|
4019
4284
|
|
4285
|
+
it "should handle check constraint failures where the columns are unknown, if columns are explicitly specified" do
|
4286
|
+
o = @c1.new(:id=>5, :i=>-100)
|
4287
|
+
proc{o.save}.must_raise Sequel::CheckConstraintViolation
|
4288
|
+
@c1.pg_auto_constraint_validation_override(:valid_test1, :i, "should not be -100")
|
4289
|
+
proc{o.save}.must_raise Sequel::ValidationFailed
|
4290
|
+
o.errors.must_equal(:i=>['should not be -100'])
|
4291
|
+
end
|
4292
|
+
|
4020
4293
|
it "should handle check constraint failures as validation errors when updating" do
|
4021
4294
|
o = @c1.new(:id=>5, :i=>3)
|
4022
4295
|
o.save
|
@@ -4090,4 +4363,49 @@ describe "pg_auto_constraint_validations plugin" do
|
|
4090
4363
|
proc{o.save}.must_raise Sequel::ValidationFailed
|
4091
4364
|
o.errors.must_equal(:i=>['is invalid'], :id=>['is invalid'])
|
4092
4365
|
end
|
4366
|
+
|
4367
|
+
it "should handle dumping cached metadata and loading metadata from cache" do
|
4368
|
+
cache_file = "spec/files/pgacv-#{$$}.cache"
|
4369
|
+
begin
|
4370
|
+
c = Class.new(Sequel::Model)
|
4371
|
+
c.plugin :pg_auto_constraint_validations, :cache_file=>cache_file
|
4372
|
+
c1 = Class.new(c)
|
4373
|
+
def c1.name; 'Foo' end
|
4374
|
+
c1.dataset = DB[:test1]
|
4375
|
+
c2 = Class.new(c)
|
4376
|
+
def c2.name; 'Bar' end
|
4377
|
+
c2.dataset = DB[:test2]
|
4378
|
+
c1.unrestrict_primary_key
|
4379
|
+
c2.unrestrict_primary_key
|
4380
|
+
|
4381
|
+
o = c1.new(:id=>5, :i=>12)
|
4382
|
+
proc{o.save}.must_raise Sequel::ValidationFailed
|
4383
|
+
o.errors.must_equal(:i=>['is invalid'])
|
4384
|
+
o = c2.new(:test2_id=>4, :test1_id=>2)
|
4385
|
+
proc{o.save}.must_raise Sequel::ValidationFailed
|
4386
|
+
o.errors.must_equal(:test1_id=>['is invalid'])
|
4387
|
+
|
4388
|
+
c.dump_pg_auto_constraint_validations_cache
|
4389
|
+
|
4390
|
+
c = Class.new(Sequel::Model)
|
4391
|
+
c.plugin :pg_auto_constraint_validations, :cache_file=>cache_file
|
4392
|
+
c1 = Class.new(c)
|
4393
|
+
def c1.name; 'Foo' end
|
4394
|
+
c1.dataset = DB[:test1]
|
4395
|
+
c2 = Class.new(c)
|
4396
|
+
def c2.name; 'Bar' end
|
4397
|
+
c2.dataset = DB[:test2]
|
4398
|
+
c1.unrestrict_primary_key
|
4399
|
+
c2.unrestrict_primary_key
|
4400
|
+
|
4401
|
+
o = c1.new(:id=>5, :i=>12)
|
4402
|
+
proc{o.save}.must_raise Sequel::ValidationFailed
|
4403
|
+
o.errors.must_equal(:i=>['is invalid'])
|
4404
|
+
o = c2.new(:test2_id=>4, :test1_id=>2)
|
4405
|
+
proc{o.save}.must_raise Sequel::ValidationFailed
|
4406
|
+
o.errors.must_equal(:test1_id=>['is invalid'])
|
4407
|
+
ensure
|
4408
|
+
File.delete(cache_file) if File.file?(cache_file)
|
4409
|
+
end
|
4410
|
+
end
|
4093
4411
|
end if DB.respond_to?(:error_info)
|