sequel 5.22.0 → 5.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG +66 -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/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 +25 -7
- data/lib/sequel/adapters/shared/sqlite.rb +16 -2
- 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/extensions/named_timezones.rb +51 -9
- data/lib/sequel/extensions/pg_array.rb +4 -0
- 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 +3 -1
- data/lib/sequel/timezones.rb +50 -11
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/postgres_spec.rb +130 -0
- 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 +7 -2
- 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_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/plugin_test.rb +27 -0
- data/spec/integration/schema_test.rb +16 -2
- data/spec/model/spec_helper.rb +1 -1
- metadata +30 -2
@@ -162,6 +162,39 @@ describe "Sequel::Plugins::Dirty" do
|
|
162
162
|
@o.save
|
163
163
|
@o.column_changes.must_equal({})
|
164
164
|
end
|
165
|
+
|
166
|
+
it "should work with the typecast_on_load plugin" do
|
167
|
+
@c.instance_variable_set(:@db_schema, :initial=>{:type=>:integer})
|
168
|
+
@c.plugin :typecast_on_load, :initial
|
169
|
+
|
170
|
+
@o = @c.call(:initial=>'1')
|
171
|
+
@o.column_changes.must_equal({})
|
172
|
+
@o.save
|
173
|
+
@o.previous_changes.must_equal({})
|
174
|
+
end
|
175
|
+
|
176
|
+
it "should have column_changes work with the typecast_on_load in after hooks" do
|
177
|
+
@c.instance_variable_set(:@db_schema, :initial=>{:type=>:integer})
|
178
|
+
@c.plugin :typecast_on_load, :initial
|
179
|
+
|
180
|
+
@o = @c.new
|
181
|
+
@o.initial = 1
|
182
|
+
@o.column_changes.must_equal({:initial=>[nil, 1]})
|
183
|
+
column_changes_in_after_save = nil
|
184
|
+
@o.define_singleton_method(:after_save) do
|
185
|
+
column_changes_in_after_save = column_changes
|
186
|
+
super()
|
187
|
+
end
|
188
|
+
@db.fetch = {:initial=>1}
|
189
|
+
@o.save
|
190
|
+
column_changes_in_after_save.must_equal({:initial=>[nil, 1]})
|
191
|
+
|
192
|
+
@o.initial = 2
|
193
|
+
@o.column_changes.must_equal({:initial=>[1, 2]})
|
194
|
+
@o.save
|
195
|
+
column_changes_in_after_save.must_equal({:initial=>[1, 2]})
|
196
|
+
@o.previous_changes.must_equal({:initial=>[1, 2]})
|
197
|
+
end
|
165
198
|
end
|
166
199
|
|
167
200
|
describe "with existing instance" do
|
@@ -0,0 +1,103 @@
|
|
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
|
+
|
78
|
+
it "should work if the prepared_statements plugin is loaded before" do
|
79
|
+
db = Sequel.mock(:host=>'sqlite', :fetch=>{:id=>1, :s=>2}, :autoid=>1, :numrows=>1)
|
80
|
+
db.extend_datasets{def quote_identifiers?; false end}
|
81
|
+
model = Class.new(Sequel::Model)
|
82
|
+
model.dataset = db[:t]
|
83
|
+
model.columns :id, :s
|
84
|
+
model.plugin :prepared_statements
|
85
|
+
model.plugin :insert_conflict
|
86
|
+
db.sqls
|
87
|
+
model.create(:s=>'a').update(:s=>'b')
|
88
|
+
db.sqls.must_equal ["INSERT INTO t (s) VALUES ('a')", "SELECT * FROM t WHERE (id = 1) LIMIT 1", "UPDATE t SET s = 'b' WHERE (id = 1)"]
|
89
|
+
end
|
90
|
+
|
91
|
+
it "should work if the prepared_statements plugin is loaded after" do
|
92
|
+
db = Sequel.mock(:host=>'postgres', :fetch=>{:id=>1, :s=>2}, :autoid=>1, :numrows=>1)
|
93
|
+
db.extend_datasets{def quote_identifiers?; false end}
|
94
|
+
model = Class.new(Sequel::Model)
|
95
|
+
model.dataset = db[:t]
|
96
|
+
model.columns :id, :s
|
97
|
+
model.plugin :insert_conflict
|
98
|
+
model.plugin :prepared_statements
|
99
|
+
db.sqls
|
100
|
+
model.create(:s=>'a').update(:s=>'b')
|
101
|
+
db.sqls.must_equal ["INSERT INTO t (s) VALUES ('a') RETURNING *", "UPDATE t SET s = 'b' WHERE (id = 1)"]
|
102
|
+
end
|
103
|
+
end
|
@@ -9,7 +9,7 @@ Sequel.extension :thread_local_timezones
|
|
9
9
|
Sequel.extension :named_timezones
|
10
10
|
Sequel.datetime_class = Time
|
11
11
|
|
12
|
-
describe "Sequel named_timezones extension" do
|
12
|
+
describe "Sequel named_timezones extension with DateTime class" do
|
13
13
|
before do
|
14
14
|
@tz_in = TZInfo::Timezone.get('America/Los_Angeles')
|
15
15
|
@tz_out = TZInfo::Timezone.get('America/New_York')
|
@@ -54,10 +54,12 @@ describe "Sequel named_timezones extension" do
|
|
54
54
|
|
55
55
|
it "should convert datetimes coming out of the database from database_timezone to application_timezone" do
|
56
56
|
dt = Sequel.database_to_application_timestamp('2009-06-01 06:20:30-0400')
|
57
|
+
dt.must_be_instance_of DateTime
|
57
58
|
dt.must_equal @dt
|
58
59
|
dt.offset.must_equal(-7/24.0)
|
59
60
|
|
60
61
|
dt = Sequel.database_to_application_timestamp('2009-06-01 10:20:30+0000')
|
62
|
+
dt.must_be_instance_of DateTime
|
61
63
|
dt.must_equal @dt
|
62
64
|
dt.offset.must_equal(-7/24.0)
|
63
65
|
end
|
@@ -68,7 +70,9 @@ describe "Sequel named_timezones extension" do
|
|
68
70
|
|
69
71
|
it "should support tzinfo_disambiguator= to handle ambiguous timezones automatically" do
|
70
72
|
Sequel.tzinfo_disambiguator = proc{|datetime, periods| periods.first}
|
71
|
-
Sequel.database_to_application_timestamp('2004-10-31T01:30:00')
|
73
|
+
dt = Sequel.database_to_application_timestamp('2004-10-31T01:30:00')
|
74
|
+
dt.must_equal DateTime.parse('2004-10-30T22:30:00-07:00')
|
75
|
+
dt.offset.must_equal(-7/24.0)
|
72
76
|
end
|
73
77
|
|
74
78
|
it "should assume datetimes coming out of the database that don't have an offset as coming from database_timezone" do
|
@@ -108,4 +112,107 @@ describe "Sequel named_timezones extension" do
|
|
108
112
|
tz2.must_equal @tz_in
|
109
113
|
end
|
110
114
|
end
|
115
|
+
|
116
|
+
describe "Sequel named_timezones extension with Time class" do
|
117
|
+
before do
|
118
|
+
@tz_in = TZInfo::Timezone.get('America/Los_Angeles')
|
119
|
+
@tz_out = TZInfo::Timezone.get('America/New_York')
|
120
|
+
@db = Sequel.mock
|
121
|
+
Sequel.application_timezone = 'America/Los_Angeles'
|
122
|
+
Sequel.database_timezone = 'America/New_York'
|
123
|
+
end
|
124
|
+
after do
|
125
|
+
Sequel.tzinfo_disambiguator = nil
|
126
|
+
Sequel.default_timezone = nil
|
127
|
+
Sequel.datetime_class = Time
|
128
|
+
end
|
129
|
+
|
130
|
+
it "should convert string arguments to *_timezone= to TZInfo::Timezone instances" do
|
131
|
+
Sequel.application_timezone.must_equal @tz_in
|
132
|
+
Sequel.database_timezone.must_equal @tz_out
|
133
|
+
end
|
134
|
+
|
135
|
+
it "should convert string arguments for Database#timezone= to TZInfo::Timezone instances for database-specific timezones" do
|
136
|
+
@db.extension :named_timezones
|
137
|
+
@db.timezone = 'America/Los_Angeles'
|
138
|
+
@db.timezone.must_equal @tz_in
|
139
|
+
end
|
140
|
+
|
141
|
+
it "should accept TZInfo::Timezone instances in *_timezone=" do
|
142
|
+
Sequel.application_timezone = @tz_in
|
143
|
+
Sequel.database_timezone = @tz_out
|
144
|
+
Sequel.application_timezone.must_equal @tz_in
|
145
|
+
Sequel.database_timezone.must_equal @tz_out
|
146
|
+
end
|
147
|
+
|
148
|
+
it "should convert datetimes going into the database to named database_timezone" do
|
149
|
+
ds = @db[:a].with_extend do
|
150
|
+
def supports_timestamp_timezones?; true; end
|
151
|
+
def supports_timestamp_usecs?; false; end
|
152
|
+
end
|
153
|
+
ds.insert([Time.new(2009,6,1,3,20,30, RUBY_VERSION >= '2.6' ? @tz_in : -25200), Time.new(2009,6,1,3,20,30,-25200), Time.new(2009,6,1,6,20,30,-14400)])
|
154
|
+
@db.sqls.must_equal ["INSERT INTO a VALUES ('2009-06-01 06:20:30-0400', '2009-06-01 06:20:30-0400', '2009-06-01 06:20:30-0400')"]
|
155
|
+
end
|
156
|
+
|
157
|
+
it "should convert datetimes coming out of the database from database_timezone to application_timezone" do
|
158
|
+
dt = Sequel.database_to_application_timestamp('2009-06-01 06:20:30-0400')
|
159
|
+
dt.must_be_instance_of Time
|
160
|
+
dt.must_equal Time.new(2009,6,1,3,20,30,-25200)
|
161
|
+
dt.utc_offset.must_equal -25200
|
162
|
+
|
163
|
+
dt = Sequel.database_to_application_timestamp('2009-06-01 10:20:30+0000')
|
164
|
+
dt.must_be_instance_of Time
|
165
|
+
dt.must_equal Time.new(2009,6,1,3,20,30,-25200)
|
166
|
+
dt.utc_offset.must_equal -25200
|
167
|
+
end
|
168
|
+
|
169
|
+
it "should raise an error for ambiguous timezones by default" do
|
170
|
+
proc{Sequel.database_to_application_timestamp('2004-10-31T01:30:00')}.must_raise(Sequel::InvalidValue)
|
171
|
+
end
|
172
|
+
|
173
|
+
it "should support tzinfo_disambiguator= to handle ambiguous timezones automatically" do
|
174
|
+
Sequel.tzinfo_disambiguator = proc{|datetime, periods| periods.first}
|
175
|
+
Sequel.database_to_application_timestamp('2004-10-31T01:30:00').must_equal Time.new(2004, 10, 30, 22, 30, 0, -25200)
|
176
|
+
dt = Sequel.database_to_application_timestamp('2004-10-31T01:30:00')
|
177
|
+
dt.must_equal Time.new(2004, 10, 30, 22, 30, 0, -25200)
|
178
|
+
dt.utc_offset.must_equal -25200
|
179
|
+
end
|
180
|
+
|
181
|
+
it "should assume datetimes coming out of the database that don't have an offset as coming from database_timezone" do
|
182
|
+
dt = Sequel.database_to_application_timestamp('2009-06-01 06:20:30')
|
183
|
+
dt.must_be_instance_of Time
|
184
|
+
dt.must_equal Time.new(2009,6,1,3,20,30, -25200)
|
185
|
+
dt.utc_offset.must_equal -25200
|
186
|
+
|
187
|
+
dt = Sequel.database_to_application_timestamp('2009-06-01 10:20:30')
|
188
|
+
dt.must_be_instance_of Time
|
189
|
+
dt.must_equal Time.new(2009,6,1,7,20,30, -25200)
|
190
|
+
dt.utc_offset.must_equal -25200
|
191
|
+
end
|
192
|
+
|
193
|
+
it "should work with the thread_local_timezones extension" do
|
194
|
+
q, q1, q2 = Queue.new, Queue.new, Queue.new
|
195
|
+
tz1, tz2 = nil, nil
|
196
|
+
t1 = Thread.new do
|
197
|
+
Sequel.thread_application_timezone = 'America/New_York'
|
198
|
+
q2.push nil
|
199
|
+
q.pop
|
200
|
+
tz1 = Sequel.application_timezone
|
201
|
+
end
|
202
|
+
t2 = Thread.new do
|
203
|
+
Sequel.thread_application_timezone = 'America/Los_Angeles'
|
204
|
+
q2.push nil
|
205
|
+
q1.pop
|
206
|
+
tz2 = Sequel.application_timezone
|
207
|
+
end
|
208
|
+
q2.pop
|
209
|
+
q2.pop
|
210
|
+
q.push nil
|
211
|
+
q1.push nil
|
212
|
+
t1.join
|
213
|
+
t2.join
|
214
|
+
tz1.must_equal @tz_out
|
215
|
+
tz2.must_equal @tz_in
|
216
|
+
end
|
217
|
+
end
|
111
218
|
end
|
@@ -492,6 +492,54 @@ describe "NestedAttributes plugin" do
|
|
492
492
|
@db.sqls.must_equal ["UPDATE artists SET name = 'Ar' WHERE (id = 10)"]
|
493
493
|
end
|
494
494
|
|
495
|
+
it "should raise a NoExistingObject error if object to be updated no longer exists, if the :require_modification=>true option is used" do
|
496
|
+
@Artist.nested_attributes :albums, :require_modification=>true, :destroy=>true
|
497
|
+
al = @Album.load(:id=>10, :name=>'Al')
|
498
|
+
ar = @Artist.load(:id=>20, :name=>'Ar')
|
499
|
+
ar.associations[:albums] = [al]
|
500
|
+
ar.set(:albums_attributes=>[{:id=>10, :name=>'L'}])
|
501
|
+
@db.sqls.must_equal []
|
502
|
+
@db.numrows = [1, 0]
|
503
|
+
proc{ar.save}.must_raise Sequel::NoExistingObject
|
504
|
+
@db.sqls.must_equal ["UPDATE artists SET name = 'Ar' WHERE (id = 20)", "UPDATE albums SET name = 'L' WHERE (id = 10)"]
|
505
|
+
end
|
506
|
+
|
507
|
+
it "should not raise an Error if object to be updated no longer exists, if the :require_modification=>false option is used" do
|
508
|
+
@Artist.nested_attributes :albums, :require_modification=>false, :destroy=>true
|
509
|
+
al = @Album.load(:id=>10, :name=>'Al')
|
510
|
+
ar = @Artist.load(:id=>20, :name=>'Ar')
|
511
|
+
ar.associations[:albums] = [al]
|
512
|
+
ar.set(:albums_attributes=>[{:id=>10, :name=>'L'}])
|
513
|
+
@db.sqls.must_equal []
|
514
|
+
@db.numrows = [1, 0]
|
515
|
+
ar.save
|
516
|
+
@db.sqls.must_equal ["UPDATE artists SET name = 'Ar' WHERE (id = 20)", "UPDATE albums SET name = 'L' WHERE (id = 10)"]
|
517
|
+
end
|
518
|
+
|
519
|
+
it "should raise a NoExistingObject error if object to be deleted no longer exists, if the :require_modification=>true option is used" do
|
520
|
+
@Artist.nested_attributes :albums, :require_modification=>true, :destroy=>true
|
521
|
+
al = @Album.load(:id=>10, :name=>'Al')
|
522
|
+
ar = @Artist.load(:id=>20, :name=>'Ar')
|
523
|
+
ar.associations[:albums] = [al]
|
524
|
+
ar.set(:albums_attributes=>[{:id=>10, :_delete=>'t'}])
|
525
|
+
@db.sqls.must_equal []
|
526
|
+
@db.numrows = [1, 0]
|
527
|
+
proc{ar.save}.must_raise Sequel::NoExistingObject
|
528
|
+
@db.sqls.must_equal ["UPDATE artists SET name = 'Ar' WHERE (id = 20)", "DELETE FROM albums WHERE (id = 10)"]
|
529
|
+
end
|
530
|
+
|
531
|
+
it "should not raise an Error if object to be deleted no longer exists, if the :require_modification=>false option is used" do
|
532
|
+
@Artist.nested_attributes :albums, :require_modification=>false, :destroy=>true
|
533
|
+
al = @Album.load(:id=>10, :name=>'Al')
|
534
|
+
ar = @Artist.load(:id=>20, :name=>'Ar')
|
535
|
+
ar.associations[:albums] = [al]
|
536
|
+
ar.set(:albums_attributes=>[{:id=>10, :_delete=>'t'}])
|
537
|
+
@db.sqls.must_equal []
|
538
|
+
@db.numrows = [1, 0]
|
539
|
+
ar.save
|
540
|
+
@db.sqls.must_equal ["UPDATE artists SET name = 'Ar' WHERE (id = 20)", "DELETE FROM albums WHERE (id = 10)"]
|
541
|
+
end
|
542
|
+
|
495
543
|
it "should not attempt to validate nested attributes twice for one_to_many associations when creating them" do
|
496
544
|
@Artist.nested_attributes :albums
|
497
545
|
validated = []
|
@@ -169,4 +169,41 @@ describe "pg_auto_constraint_validations plugin" do
|
|
169
169
|
proc{o.update(:i=>12)}.must_raise Sequel::ValidationFailed
|
170
170
|
o.errors.must_equal(:i=>['foo bar'])
|
171
171
|
end
|
172
|
+
|
173
|
+
it "should handle dumping cached metadata and loading metadata from cache" do
|
174
|
+
cache_file = "spec/files/pgacv-spec-#{$$}.cache"
|
175
|
+
begin
|
176
|
+
@ds = @db[:items]
|
177
|
+
@ds.send(:columns=, [:id, :i])
|
178
|
+
@db.fetch = @metadata_results.dup
|
179
|
+
c = Sequel::Model(@ds)
|
180
|
+
def c.name; 'Foo' end
|
181
|
+
@db.sqls
|
182
|
+
c.plugin :pg_auto_constraint_validations, :cache_file=>cache_file
|
183
|
+
@db.sqls.length.must_equal 5
|
184
|
+
|
185
|
+
o = c.new(:i=>12)
|
186
|
+
@set_error[Sequel::CheckConstraintViolation, :constraint=>'items_i_id_check']
|
187
|
+
proc{o.save}.must_raise Sequel::ValidationFailed
|
188
|
+
|
189
|
+
c.dump_pg_auto_constraint_validations_cache
|
190
|
+
|
191
|
+
@db.fetch = []
|
192
|
+
c = Sequel::Model(@ds)
|
193
|
+
def c.name; 'Foo' end
|
194
|
+
@db.sqls
|
195
|
+
c.plugin :pg_auto_constraint_validations, :cache_file=>cache_file
|
196
|
+
@db.sqls.must_be_empty
|
197
|
+
|
198
|
+
o = c.new(:i=>12)
|
199
|
+
@set_error[Sequel::CheckConstraintViolation, :constraint=>'items_i_id_check']
|
200
|
+
proc{o.save}.must_raise Sequel::ValidationFailed
|
201
|
+
ensure
|
202
|
+
File.delete(cache_file) if File.file?(cache_file)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
it "should raise error if attempting to dump cached metadata when not using caching" do
|
207
|
+
proc{@c.dump_pg_auto_constraint_validations_cache}.must_raise Sequel::Error
|
208
|
+
end
|
172
209
|
end
|
@@ -286,4 +286,71 @@ describe "Sequel::Postgres::JSONOp" do
|
|
286
286
|
it "should allow transforming JSONBHash instances into ArrayOp instances" do
|
287
287
|
@db.literal(Sequel.pg_jsonb('a'=>1).op['a']).must_equal "('{\"a\":1}'::jsonb -> 'a')"
|
288
288
|
end
|
289
|
+
|
290
|
+
it "#path_exists should use the @? operator" do
|
291
|
+
@l[@jb.path_exists('$')].must_equal "(j @? '$')"
|
292
|
+
end
|
293
|
+
|
294
|
+
it "#path_exists result should be a boolean expression" do
|
295
|
+
@jb.path_exists('$').must_be_kind_of Sequel::SQL::BooleanExpression
|
296
|
+
end
|
297
|
+
|
298
|
+
it "#path_match should use the @@ operator" do
|
299
|
+
@l[@jb.path_match('$')].must_equal "(j @@ '$')"
|
300
|
+
end
|
301
|
+
|
302
|
+
it "#path_match result should be a boolean expression" do
|
303
|
+
@jb.path_match('$').must_be_kind_of Sequel::SQL::BooleanExpression
|
304
|
+
end
|
305
|
+
|
306
|
+
it "#path_exists! should use the jsonb_path_exists function" do
|
307
|
+
@l[@jb.path_exists!('$')].must_equal "jsonb_path_exists(j, '$')"
|
308
|
+
@l[@jb.path_exists!('$', '{"x":2}')].must_equal "jsonb_path_exists(j, '$', '{\"x\":2}')"
|
309
|
+
@l[@jb.path_exists!('$', x: 2)].must_equal "jsonb_path_exists(j, '$', '{\"x\":2}')"
|
310
|
+
@l[@jb.path_exists!('$', {x: 2}, true)].must_equal "jsonb_path_exists(j, '$', '{\"x\":2}', true)"
|
311
|
+
end
|
312
|
+
|
313
|
+
it "#path_exists! result should be a boolean expression" do
|
314
|
+
@jb.path_exists!('$').must_be_kind_of Sequel::SQL::BooleanExpression
|
315
|
+
end
|
316
|
+
|
317
|
+
it "#path_match! should use the jsonb_path_match function" do
|
318
|
+
@l[@jb.path_match!('$')].must_equal "jsonb_path_match(j, '$')"
|
319
|
+
@l[@jb.path_match!('$', '{"x":2}')].must_equal "jsonb_path_match(j, '$', '{\"x\":2}')"
|
320
|
+
@l[@jb.path_match!('$', x: 2)].must_equal "jsonb_path_match(j, '$', '{\"x\":2}')"
|
321
|
+
@l[@jb.path_match!('$', {x: 2}, true)].must_equal "jsonb_path_match(j, '$', '{\"x\":2}', true)"
|
322
|
+
end
|
323
|
+
|
324
|
+
it "#path_match! result should be a boolean expression" do
|
325
|
+
@jb.path_match!('$').must_be_kind_of Sequel::SQL::BooleanExpression
|
326
|
+
end
|
327
|
+
|
328
|
+
it "#path_query should use the jsonb_path_query function" do
|
329
|
+
@l[@jb.path_query('$')].must_equal "jsonb_path_query(j, '$')"
|
330
|
+
@l[@jb.path_query('$', '{"x":2}')].must_equal "jsonb_path_query(j, '$', '{\"x\":2}')"
|
331
|
+
@l[@jb.path_query('$', x: 2)].must_equal "jsonb_path_query(j, '$', '{\"x\":2}')"
|
332
|
+
@l[@jb.path_query('$', {x: 2}, true)].must_equal "jsonb_path_query(j, '$', '{\"x\":2}', true)"
|
333
|
+
end
|
334
|
+
|
335
|
+
it "#path_query_array should use the jsonb_path_query_array function" do
|
336
|
+
@l[@jb.path_query_array('$')].must_equal "jsonb_path_query_array(j, '$')"
|
337
|
+
@l[@jb.path_query_array('$', '{"x":2}')].must_equal "jsonb_path_query_array(j, '$', '{\"x\":2}')"
|
338
|
+
@l[@jb.path_query_array('$', x: 2)].must_equal "jsonb_path_query_array(j, '$', '{\"x\":2}')"
|
339
|
+
@l[@jb.path_query_array('$', {x: 2}, true)].must_equal "jsonb_path_query_array(j, '$', '{\"x\":2}', true)"
|
340
|
+
end
|
341
|
+
|
342
|
+
it "#path_query_array result should be a JSONBOp" do
|
343
|
+
@l[@jb.path_query_array('$').path_query_array('$')].must_equal "jsonb_path_query_array(jsonb_path_query_array(j, '$'), '$')"
|
344
|
+
end
|
345
|
+
|
346
|
+
it "#path_query_first should use the jsonb_path_query_first function" do
|
347
|
+
@l[@jb.path_query_first('$')].must_equal "jsonb_path_query_first(j, '$')"
|
348
|
+
@l[@jb.path_query_first('$', '{"x":2}')].must_equal "jsonb_path_query_first(j, '$', '{\"x\":2}')"
|
349
|
+
@l[@jb.path_query_first('$', x: 2)].must_equal "jsonb_path_query_first(j, '$', '{\"x\":2}')"
|
350
|
+
@l[@jb.path_query_first('$', {x: 2}, true)].must_equal "jsonb_path_query_first(j, '$', '{\"x\":2}', true)"
|
351
|
+
end
|
352
|
+
|
353
|
+
it "#path_query_first result should be a JSONBOp" do
|
354
|
+
@l[@jb.path_query_first('$').path_query_first('$')].must_equal "jsonb_path_query_first(jsonb_path_query_first(j, '$'), '$')"
|
355
|
+
end
|
289
356
|
end
|