sequel 5.22.0 → 5.27.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +78 -0
- data/README.rdoc +1 -1
- data/doc/dataset_filtering.rdoc +15 -0
- data/doc/opening_databases.rdoc +3 -0
- data/doc/postgresql.rdoc +2 -2
- data/doc/release_notes/5.23.0.txt +56 -0
- data/doc/release_notes/5.24.0.txt +56 -0
- data/doc/release_notes/5.25.0.txt +32 -0
- data/doc/release_notes/5.26.0.txt +35 -0
- data/doc/release_notes/5.27.0.txt +21 -0
- data/doc/testing.rdoc +1 -0
- data/lib/sequel/adapters/jdbc.rb +7 -1
- data/lib/sequel/adapters/jdbc/postgresql.rb +1 -13
- data/lib/sequel/adapters/jdbc/sqlite.rb +29 -0
- data/lib/sequel/adapters/mysql2.rb +0 -1
- data/lib/sequel/adapters/shared/mssql.rb +9 -8
- data/lib/sequel/adapters/shared/postgres.rb +30 -7
- data/lib/sequel/adapters/shared/sqlite.rb +23 -4
- data/lib/sequel/adapters/tinytds.rb +12 -0
- data/lib/sequel/adapters/utils/mysql_mysql2.rb +1 -1
- data/lib/sequel/database/logging.rb +7 -1
- data/lib/sequel/database/schema_generator.rb +11 -2
- data/lib/sequel/database/schema_methods.rb +2 -0
- data/lib/sequel/dataset/actions.rb +3 -2
- data/lib/sequel/dataset/features.rb +6 -0
- data/lib/sequel/dataset/sql.rb +17 -4
- data/lib/sequel/extensions/named_timezones.rb +51 -9
- data/lib/sequel/extensions/pg_array.rb +4 -0
- data/lib/sequel/extensions/pg_array_ops.rb +10 -6
- data/lib/sequel/extensions/pg_enum.rb +4 -1
- data/lib/sequel/extensions/pg_json.rb +88 -17
- data/lib/sequel/extensions/pg_json_ops.rb +124 -0
- data/lib/sequel/extensions/pg_range.rb +9 -0
- data/lib/sequel/extensions/pg_row.rb +3 -1
- data/lib/sequel/extensions/sql_comments.rb +2 -2
- data/lib/sequel/model/base.rb +12 -5
- data/lib/sequel/plugins/association_multi_add_remove.rb +83 -0
- data/lib/sequel/plugins/association_proxies.rb +3 -2
- data/lib/sequel/plugins/caching.rb +3 -0
- data/lib/sequel/plugins/class_table_inheritance.rb +10 -0
- data/lib/sequel/plugins/csv_serializer.rb +26 -9
- data/lib/sequel/plugins/dirty.rb +3 -9
- data/lib/sequel/plugins/insert_conflict.rb +72 -0
- data/lib/sequel/plugins/nested_attributes.rb +7 -0
- data/lib/sequel/plugins/pg_auto_constraint_validations.rb +89 -30
- data/lib/sequel/plugins/sharding.rb +11 -5
- data/lib/sequel/plugins/static_cache.rb +8 -3
- data/lib/sequel/plugins/static_cache_cache.rb +53 -0
- data/lib/sequel/plugins/typecast_on_load.rb +3 -2
- data/lib/sequel/sql.rb +4 -1
- data/lib/sequel/timezones.rb +50 -11
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/postgres_spec.rb +135 -7
- data/spec/adapters/sqlite_spec.rb +1 -1
- data/spec/bin_spec.rb +2 -2
- data/spec/core/database_spec.rb +50 -0
- data/spec/core/dataset_spec.rb +23 -1
- data/spec/core/expression_filters_spec.rb +22 -3
- data/spec/core/schema_spec.rb +18 -0
- data/spec/core/spec_helper.rb +1 -1
- data/spec/core_extensions_spec.rb +1 -1
- data/spec/extensions/association_multi_add_remove_spec.rb +1041 -0
- data/spec/extensions/dirty_spec.rb +33 -0
- data/spec/extensions/insert_conflict_spec.rb +103 -0
- data/spec/extensions/named_timezones_spec.rb +109 -2
- data/spec/extensions/nested_attributes_spec.rb +48 -0
- data/spec/extensions/pg_array_ops_spec.rb +3 -3
- data/spec/extensions/pg_auto_constraint_validations_spec.rb +37 -0
- data/spec/extensions/pg_json_ops_spec.rb +67 -0
- data/spec/extensions/pg_json_spec.rb +12 -0
- data/spec/extensions/pg_range_spec.rb +19 -2
- data/spec/extensions/sharding_spec.rb +8 -0
- data/spec/extensions/spec_helper.rb +9 -2
- data/spec/extensions/static_cache_cache_spec.rb +35 -0
- data/spec/guards_helper.rb +1 -1
- data/spec/integration/dataset_test.rb +25 -0
- data/spec/integration/plugin_test.rb +28 -1
- data/spec/integration/schema_test.rb +16 -2
- data/spec/integration/spec_helper.rb +7 -1
- data/spec/model/spec_helper.rb +1 -1
- metadata +32 -2
@@ -107,12 +107,18 @@ module Sequel
|
|
107
107
|
# previous row_proc, but calls set_server on the output of that row_proc,
|
108
108
|
# ensuring that objects retrieved by a specific shard know which shard they
|
109
109
|
# are tied to.
|
110
|
-
def
|
111
|
-
|
112
|
-
if rp
|
113
|
-
|
110
|
+
def row_proc
|
111
|
+
rp = super
|
112
|
+
if rp
|
113
|
+
case server = db.pool.send(:pick_server, opts[:server])
|
114
|
+
when nil, :default, :read_only
|
115
|
+
# nothing
|
116
|
+
else
|
117
|
+
old_rp = rp
|
118
|
+
rp = proc{|r| old_rp.call(r).set_server(server)}
|
119
|
+
end
|
114
120
|
end
|
115
|
-
|
121
|
+
rp
|
116
122
|
end
|
117
123
|
end
|
118
124
|
end
|
@@ -211,18 +211,23 @@ module Sequel
|
|
211
211
|
# Reload the cache for this model by retrieving all of the instances in the dataset
|
212
212
|
# freezing them, and populating the cached array and hash.
|
213
213
|
def load_cache
|
214
|
-
|
214
|
+
@all = load_static_cache_rows
|
215
215
|
h = {}
|
216
|
-
|
216
|
+
@all.each do |o|
|
217
217
|
o.errors.freeze
|
218
218
|
h[o.pk.freeze] = o.freeze
|
219
219
|
end
|
220
|
-
@all = a.freeze
|
221
220
|
@cache = h.freeze
|
222
221
|
end
|
223
222
|
|
224
223
|
private
|
225
224
|
|
225
|
+
# Load the static cache rows from the database.
|
226
|
+
def load_static_cache_rows
|
227
|
+
ret = super if defined?(super)
|
228
|
+
ret || dataset.all.freeze
|
229
|
+
end
|
230
|
+
|
226
231
|
# Return the frozen object with the given pk, or nil if no such object exists
|
227
232
|
# in the cache, without issuing a database query.
|
228
233
|
def primary_key_lookup(pk)
|
@@ -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
|
@@ -41,7 +41,9 @@ module Sequel
|
|
41
41
|
# Typecast values using #load_typecast when the values are retrieved
|
42
42
|
# from the database.
|
43
43
|
def call(values)
|
44
|
-
super.load_typecast
|
44
|
+
o = super.load_typecast
|
45
|
+
o.send(:_clear_changed_columns, :initialize)
|
46
|
+
o
|
45
47
|
end
|
46
48
|
|
47
49
|
# Freeze typecast on load columns when freezing model class.
|
@@ -63,7 +65,6 @@ module Sequel
|
|
63
65
|
set_column_value("#{c}=", v)
|
64
66
|
end
|
65
67
|
end
|
66
|
-
_changed_columns.clear
|
67
68
|
self
|
68
69
|
end
|
69
70
|
|
data/lib/sequel/sql.rb
CHANGED
@@ -788,8 +788,10 @@ module Sequel
|
|
788
788
|
def coerce(other)
|
789
789
|
if other.is_a?(Numeric)
|
790
790
|
[SQL::NumericExpression.new(:NOOP, other), self]
|
791
|
-
|
791
|
+
elsif defined?(super)
|
792
792
|
super
|
793
|
+
else
|
794
|
+
[self, other]
|
793
795
|
end
|
794
796
|
end
|
795
797
|
|
@@ -1315,6 +1317,7 @@ module Sequel
|
|
1315
1317
|
CURRENT_DATE = Constant.new(:CURRENT_DATE)
|
1316
1318
|
CURRENT_TIME = Constant.new(:CURRENT_TIME)
|
1317
1319
|
CURRENT_TIMESTAMP = Constant.new(:CURRENT_TIMESTAMP)
|
1320
|
+
DEFAULT = Constant.new(:DEFAULT)
|
1318
1321
|
SQLTRUE = TRUE = BooleanConstant.new(true)
|
1319
1322
|
SQLFALSE = FALSE = BooleanConstant.new(false)
|
1320
1323
|
NULL = BooleanConstant.new(nil)
|
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 = 27
|
10
10
|
|
11
11
|
# The tiny version of Sequel. Usually 0, only bumped for bugfix
|
12
12
|
# releases that fix regressions from previous versions.
|
@@ -70,7 +70,7 @@ describe "PostgreSQL", '#create_table' do
|
|
70
70
|
|
71
71
|
it "should create an unlogged table" do
|
72
72
|
@db.create_table(:unlogged_dolls, :unlogged => true){text :name}
|
73
|
-
end
|
73
|
+
end if DB.server_version >= 90100
|
74
74
|
|
75
75
|
it "should create a table inheriting from another table" do
|
76
76
|
@db.create_table(:unlogged_dolls){text :name}
|
@@ -227,6 +227,24 @@ describe "PostgreSQL", '#create_table' do
|
|
227
227
|
@db.convert_serial_to_identity(:tmp_dolls, :column=>:id)
|
228
228
|
end if DB.server_version >= 100002 && DB.get{current_setting('is_superuser')} == 'on'
|
229
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
|
+
|
230
248
|
it "should support pg_loose_count extension" do
|
231
249
|
@db.extension :pg_loose_count
|
232
250
|
@db.create_table(:tmp_dolls){text :name}
|
@@ -350,6 +368,14 @@ describe "PostgreSQL", 'INSERT ON CONFLICT' do
|
|
350
368
|
@ds.insert_conflict(:constraint=>:ic_test_a_uidx, :update=>{:b=>6}, :update_where=>{Sequel[:ic_test][:b]=>4}).insert(1, 3, 4).must_be_nil
|
351
369
|
@ds.all.must_equal [{:a=>1, :b=>5, :c=>5, :c_is_unique=>false}]
|
352
370
|
end
|
371
|
+
|
372
|
+
it "Dataset#insert_conflict should support table aliases" do
|
373
|
+
@ds = @db[Sequel[:ic_test].as(:foo)]
|
374
|
+
@ds.insert(1, 2, 5)
|
375
|
+
proc{@ds.insert(1, 3, 4)}.must_raise Sequel::UniqueConstraintViolation
|
376
|
+
@ds.insert_conflict(:target=>:a, :update=>{:b=>Sequel[:foo][:c] + Sequel[:excluded][:c]}).insert(1, 7, 10)
|
377
|
+
@ds.all.must_equal [{:a=>1, :b=>15, :c=>5, :c_is_unique=>false}]
|
378
|
+
end
|
353
379
|
end if DB.server_version >= 90500
|
354
380
|
|
355
381
|
describe "A PostgreSQL database" do
|
@@ -589,10 +615,6 @@ describe "A PostgreSQL dataset" do
|
|
589
615
|
@d.from{generate_series(1,3,1).as(:a)}.select{(a.sql_number % 2).as(:a)}.from_self.get{mode.function.within_group(:a)}.must_equal 1
|
590
616
|
end if DB.server_version >= 90400
|
591
617
|
|
592
|
-
it "should support filtered aggregate functions" do
|
593
|
-
@d.from{generate_series(1,3,1).as(:a)}.select{(a.sql_number % 2).as(:a)}.from_self.get{count(:a).filter(:a=>1)}.must_equal 2
|
594
|
-
end if DB.server_version >= 90400
|
595
|
-
|
596
618
|
it "should support functions with ordinality" do
|
597
619
|
@d.from{generate_series(1,10,3).with_ordinality}.select_map([:generate_series, :ordinality]).must_equal [[1, 1], [4, 2], [7, 3], [10, 4]]
|
598
620
|
end if DB.server_version >= 90400
|
@@ -1246,7 +1268,7 @@ describe "A PostgreSQL database" do
|
|
1246
1268
|
end
|
1247
1269
|
|
1248
1270
|
it "should support indexes with index type" do
|
1249
|
-
@db.create_table(:posts){
|
1271
|
+
@db.create_table(:posts){box :geom; index :geom, :type => 'gist'}
|
1250
1272
|
end
|
1251
1273
|
|
1252
1274
|
it "should support unique indexes with index type" do
|
@@ -2678,6 +2700,8 @@ describe 'PostgreSQL array handling' do
|
|
2678
2700
|
if @db.server_version >= 90000
|
2679
2701
|
@ds.get(Sequel.pg_array(:i5).join).must_equal '15'
|
2680
2702
|
@ds.get(Sequel.pg_array(:i5).join(':')).must_equal '1:5'
|
2703
|
+
end
|
2704
|
+
if @db.server_version >= 90100
|
2681
2705
|
@ds.get(Sequel.pg_array(:i5).join(':', '*')).must_equal '1:*:5'
|
2682
2706
|
end
|
2683
2707
|
if @db.server_version >= 90300
|
@@ -3323,6 +3347,65 @@ describe 'PostgreSQL json type' do
|
|
3323
3347
|
@db.from(jo.each_text).select_order_map(:key).must_equal %w'a b'
|
3324
3348
|
@db.from(jo.each_text).order(:key).where(:key=>'b').get(:value).gsub(' ', '').must_match(/\{"d":\{"e":3\},"c":2\}|\{"c":2,"d":\{"e":3\}\}/)
|
3325
3349
|
|
3350
|
+
if DB.server_version >= 120000 && json_type == :jsonb
|
3351
|
+
@db.get(jo.path_exists('$.b.d.e')).must_equal true
|
3352
|
+
@db.get(jo.path_exists('$.b.d.f')).must_equal false
|
3353
|
+
|
3354
|
+
@db.get(jo.path_exists!('$.b.d.e')).must_equal true
|
3355
|
+
@db.get(jo.path_exists!('$.b.d.f')).must_equal false
|
3356
|
+
@db.get(jo.path_exists!('$.b.d.e ? (@ > $x)', '{"x":2}')).must_equal true
|
3357
|
+
@db.get(jo.path_exists!('$.b.d.e ? (@ > $x)', '{"x":4}')).must_equal false
|
3358
|
+
@db.get(jo.path_exists!('$.b.d.e ? (@ > $x)', x: 2)).must_equal true
|
3359
|
+
@db.get(jo.path_exists!('$.b.d.e ? (@ > $x)', x: 4)).must_equal false
|
3360
|
+
@db.get(jo.path_exists!('$.b.d.e ? (@ > $x)', {x: 2}, true)).must_equal true
|
3361
|
+
@db.get(jo.path_exists!('$.b.d.e ? (@ > $x)', {x: 4}, false)).must_equal false
|
3362
|
+
|
3363
|
+
@db.get(jo.path_match('$.b.d.e')).must_be_nil
|
3364
|
+
@db.get(jo.path_match('$.b.d.f')).must_be_nil
|
3365
|
+
@db.get(pg_json.call('b'=>{'d'=>{'e'=>true}}).op.path_match('$.b.d.e')).must_equal true
|
3366
|
+
@db.get(pg_json.call('b'=>{'d'=>{'e'=>false}}).op.path_match('$.b.d.e')).must_equal false
|
3367
|
+
|
3368
|
+
proc{@db.get(jo.path_match!('$.b.d.e'))}.must_raise(Sequel::DatabaseError)
|
3369
|
+
proc{@db.get(jo.path_match!('$.b.d.f'))}.must_raise(Sequel::DatabaseError)
|
3370
|
+
@db.get(jo.path_match!('$.b.d.e', {}, true)).must_be_nil
|
3371
|
+
@db.get(jo.path_match!('$.b.d.f', {}, true)).must_be_nil
|
3372
|
+
@db.get(pg_json.call('b'=>{'d'=>{'e'=>true}}).op.path_match!('$.b.d.e')).must_equal true
|
3373
|
+
@db.get(pg_json.call('b'=>{'d'=>{'e'=>false}}).op.path_match!('$.b.d.e')).must_equal false
|
3374
|
+
@db.get(jo.path_match!('$.b.d.e > $x', '{"x":2}')).must_equal true
|
3375
|
+
@db.get(jo.path_match!('$.b.d.e > $x', '{"x":4}')).must_equal false
|
3376
|
+
@db.get(jo.path_match!('$.b.d.e > $x', x: 2)).must_equal true
|
3377
|
+
@db.get(jo.path_match!('$.b.d.e > $x', x: 4)).must_equal false
|
3378
|
+
@db.get(jo.path_match!('$.b.d.e > $x', {x: 2}, false)).must_equal true
|
3379
|
+
@db.get(jo.path_match!('$.b.d.e > $x', {x: 4}, true)).must_equal false
|
3380
|
+
|
3381
|
+
@db.get(jo.path_query_first('$.b.d.e')).must_equal 3
|
3382
|
+
@db.get(jo.path_query_first('$.b.d.f')).must_be_nil
|
3383
|
+
@db.get(jo.path_query_first('$.b.d.e ? (@ > $x)', '{"x":2}')).must_equal 3
|
3384
|
+
@db.get(jo.path_query_first('$.b.d.e ? (@ > $x)', '{"x":4}')).must_be_nil
|
3385
|
+
@db.get(jo.path_query_first('$.b.d.e ? (@ > $x)', x: 2)).must_equal 3
|
3386
|
+
@db.get(jo.path_query_first('$.b.d.e ? (@ > $x)', x: 4)).must_be_nil
|
3387
|
+
@db.get(jo.path_query_first('$.b.d.e ? (@ > $x)', {x: 2}, true)).must_equal 3
|
3388
|
+
@db.get(jo.path_query_first('$.b.d.e ? (@ > $x)', {x: 4}, false)).must_be_nil
|
3389
|
+
|
3390
|
+
@db.get(jo.path_query_array('$.b.d.e')).must_equal [3]
|
3391
|
+
@db.get(jo.path_query_array('$.b.d.f')).must_equal []
|
3392
|
+
@db.get(jo.path_query_array('$.b.d.e ? (@ > $x)', '{"x":2}')).must_equal [3]
|
3393
|
+
@db.get(jo.path_query_array('$.b.d.e ? (@ > $x)', '{"x":4}')).must_equal []
|
3394
|
+
@db.get(jo.path_query_array('$.b.d.e ? (@ > $x)', x: 2)).must_equal [3]
|
3395
|
+
@db.get(jo.path_query_array('$.b.d.e ? (@ > $x)', x: 4)).must_equal []
|
3396
|
+
@db.get(jo.path_query_array('$.b.d.e ? (@ > $x)', {x: 2}, true)).must_equal [3]
|
3397
|
+
@db.get(jo.path_query_array('$.b.d.e ? (@ > $x)', {x: 4}, false)).must_equal []
|
3398
|
+
|
3399
|
+
@db.from(jo.path_query('$.b.d.e').as(:a, [:b])).get(:b).must_equal 3
|
3400
|
+
@db.from(jo.path_query('$.b.d.f').as(:a, [:b])).get(:b).must_be_nil
|
3401
|
+
@db.from(jo.path_query('$.b.d.e ? (@ > $x)', '{"x":2}').as(:a, [:b])).get(:b).must_equal 3
|
3402
|
+
@db.from(jo.path_query('$.b.d.e ? (@ > $x)', '{"x":4}').as(:a, [:b])).get(:b).must_be_nil
|
3403
|
+
@db.from(jo.path_query('$.b.d.e ? (@ > $x)', x: 2).as(:a, [:b])).get(:b).must_equal 3
|
3404
|
+
@db.from(jo.path_query('$.b.d.e ? (@ > $x)', x: 4).as(:a, [:b])).get(:b).must_be_nil
|
3405
|
+
@db.from(jo.path_query('$.b.d.e ? (@ > $x)', {x: 2}, true).as(:a, [:b])).get(:b).must_equal 3
|
3406
|
+
@db.from(jo.path_query('$.b.d.e ? (@ > $x)', {x: 4}, false).as(:a, [:b])).get(:b).must_be_nil
|
3407
|
+
end
|
3408
|
+
|
3326
3409
|
Sequel.extension :pg_row_ops
|
3327
3410
|
@db.create_table!(:items) do
|
3328
3411
|
Integer :a
|
@@ -4345,4 +4428,49 @@ describe "pg_auto_constraint_validations plugin" do
|
|
4345
4428
|
proc{o.save}.must_raise Sequel::ValidationFailed
|
4346
4429
|
o.errors.must_equal(:i=>['is invalid'], :id=>['is invalid'])
|
4347
4430
|
end
|
4348
|
-
|
4431
|
+
|
4432
|
+
it "should handle dumping cached metadata and loading metadata from cache" do
|
4433
|
+
cache_file = "spec/files/pgacv-#{$$}.cache"
|
4434
|
+
begin
|
4435
|
+
c = Class.new(Sequel::Model)
|
4436
|
+
c.plugin :pg_auto_constraint_validations, :cache_file=>cache_file
|
4437
|
+
c1 = Class.new(c)
|
4438
|
+
def c1.name; 'Foo' end
|
4439
|
+
c1.dataset = DB[:test1]
|
4440
|
+
c2 = Class.new(c)
|
4441
|
+
def c2.name; 'Bar' end
|
4442
|
+
c2.dataset = DB[:test2]
|
4443
|
+
c1.unrestrict_primary_key
|
4444
|
+
c2.unrestrict_primary_key
|
4445
|
+
|
4446
|
+
o = c1.new(:id=>5, :i=>12)
|
4447
|
+
proc{o.save}.must_raise Sequel::ValidationFailed
|
4448
|
+
o.errors.must_equal(:i=>['is invalid'])
|
4449
|
+
o = c2.new(:test2_id=>4, :test1_id=>2)
|
4450
|
+
proc{o.save}.must_raise Sequel::ValidationFailed
|
4451
|
+
o.errors.must_equal(:test1_id=>['is invalid'])
|
4452
|
+
|
4453
|
+
c.dump_pg_auto_constraint_validations_cache
|
4454
|
+
|
4455
|
+
c = Class.new(Sequel::Model)
|
4456
|
+
c.plugin :pg_auto_constraint_validations, :cache_file=>cache_file
|
4457
|
+
c1 = Class.new(c)
|
4458
|
+
def c1.name; 'Foo' end
|
4459
|
+
c1.dataset = DB[:test1]
|
4460
|
+
c2 = Class.new(c)
|
4461
|
+
def c2.name; 'Bar' end
|
4462
|
+
c2.dataset = DB[:test2]
|
4463
|
+
c1.unrestrict_primary_key
|
4464
|
+
c2.unrestrict_primary_key
|
4465
|
+
|
4466
|
+
o = c1.new(:id=>5, :i=>12)
|
4467
|
+
proc{o.save}.must_raise Sequel::ValidationFailed
|
4468
|
+
o.errors.must_equal(:i=>['is invalid'])
|
4469
|
+
o = c2.new(:test2_id=>4, :test1_id=>2)
|
4470
|
+
proc{o.save}.must_raise Sequel::ValidationFailed
|
4471
|
+
o.errors.must_equal(:test1_id=>['is invalid'])
|
4472
|
+
ensure
|
4473
|
+
File.delete(cache_file) if File.file?(cache_file)
|
4474
|
+
end
|
4475
|
+
end
|
4476
|
+
end if DB.respond_to?(:error_info) && DB.server_version >= 90300
|