sequel 4.31.0 → 4.32.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 +24 -0
- data/Rakefile +17 -15
- data/doc/association_basics.rdoc +7 -3
- data/doc/opening_databases.rdoc +7 -0
- data/doc/release_notes/4.32.0.txt +132 -0
- data/doc/schema_modification.rdoc +1 -1
- data/doc/security.rdoc +70 -26
- data/doc/testing.rdoc +1 -0
- data/lib/sequel/adapters/jdbc.rb +2 -1
- data/lib/sequel/adapters/postgres.rb +3 -4
- data/lib/sequel/adapters/shared/mysql.rb +14 -1
- data/lib/sequel/adapters/shared/sqlite.rb +2 -2
- data/lib/sequel/extensions/_pretty_table.rb +2 -0
- data/lib/sequel/extensions/arbitrary_servers.rb +2 -0
- data/lib/sequel/extensions/columns_introspection.rb +2 -0
- data/lib/sequel/extensions/connection_validator.rb +2 -0
- data/lib/sequel/extensions/constraint_validations.rb +2 -0
- data/lib/sequel/extensions/core_extensions.rb +1 -5
- data/lib/sequel/extensions/current_datetime_timestamp.rb +2 -0
- data/lib/sequel/extensions/dataset_source_alias.rb +2 -0
- data/lib/sequel/extensions/date_arithmetic.rb +2 -0
- data/lib/sequel/extensions/empty_array_consider_nulls.rb +3 -1
- data/lib/sequel/extensions/error_sql.rb +2 -0
- data/lib/sequel/extensions/eval_inspect.rb +2 -0
- data/lib/sequel/extensions/filter_having.rb +2 -0
- data/lib/sequel/extensions/from_block.rb +2 -0
- data/lib/sequel/extensions/graph_each.rb +2 -0
- data/lib/sequel/extensions/hash_aliases.rb +2 -0
- data/lib/sequel/extensions/inflector.rb +2 -0
- data/lib/sequel/extensions/looser_typecasting.rb +3 -1
- data/lib/sequel/extensions/meta_def.rb +2 -0
- data/lib/sequel/extensions/migration.rb +4 -0
- data/lib/sequel/extensions/mssql_emulate_lateral_with_apply.rb +2 -0
- data/lib/sequel/extensions/named_timezones.rb +2 -0
- data/lib/sequel/extensions/no_auto_literal_strings.rb +84 -0
- data/lib/sequel/extensions/null_dataset.rb +2 -0
- data/lib/sequel/extensions/pagination.rb +2 -0
- data/lib/sequel/extensions/pg_array.rb +2 -4
- data/lib/sequel/extensions/pg_array_ops.rb +2 -0
- data/lib/sequel/extensions/pg_enum.rb +2 -0
- data/lib/sequel/extensions/pg_hstore.rb +2 -4
- data/lib/sequel/extensions/pg_hstore_ops.rb +2 -0
- data/lib/sequel/extensions/pg_inet.rb +2 -4
- data/lib/sequel/extensions/pg_inet_ops.rb +2 -0
- data/lib/sequel/extensions/pg_interval.rb +2 -4
- data/lib/sequel/extensions/pg_json.rb +4 -4
- data/lib/sequel/extensions/pg_json_ops.rb +3 -0
- data/lib/sequel/extensions/pg_loose_count.rb +2 -0
- data/lib/sequel/extensions/pg_range.rb +2 -4
- data/lib/sequel/extensions/pg_range_ops.rb +2 -0
- data/lib/sequel/extensions/pg_row.rb +2 -4
- data/lib/sequel/extensions/pg_row_ops.rb +2 -0
- data/lib/sequel/extensions/pg_static_cache_updater.rb +2 -0
- data/lib/sequel/extensions/pretty_table.rb +2 -0
- data/lib/sequel/extensions/query.rb +3 -0
- data/lib/sequel/extensions/query_literals.rb +7 -5
- data/lib/sequel/extensions/round_timestamps.rb +4 -3
- data/lib/sequel/extensions/schema_caching.rb +2 -0
- data/lib/sequel/extensions/schema_dumper.rb +2 -0
- data/lib/sequel/extensions/select_remove.rb +2 -0
- data/lib/sequel/extensions/sequel_3_dataset_methods.rb +2 -0
- data/lib/sequel/extensions/server_block.rb +3 -0
- data/lib/sequel/extensions/set_overrides.rb +2 -0
- data/lib/sequel/extensions/split_array_nil.rb +2 -0
- data/lib/sequel/extensions/thread_local_timezones.rb +2 -0
- data/lib/sequel/extensions/to_dot.rb +2 -0
- data/lib/sequel/model/associations.rb +95 -55
- data/lib/sequel/plugins/association_pks.rb +58 -33
- data/lib/sequel/plugins/eager_each.rb +22 -0
- data/lib/sequel/plugins/pg_typecast_on_load.rb +3 -2
- data/lib/sequel/plugins/tactical_eager_loading.rb +44 -3
- data/lib/sequel/version.rb +2 -2
- data/spec/adapters/mysql_spec.rb +34 -6
- data/spec/adapters/oracle_spec.rb +1 -1
- data/spec/bin_spec.rb +2 -2
- data/spec/core/dataset_spec.rb +7 -0
- data/spec/extensions/association_pks_spec.rb +38 -0
- data/spec/extensions/class_table_inheritance_spec.rb +24 -0
- data/spec/extensions/eager_each_spec.rb +25 -1
- data/spec/extensions/no_auto_literal_strings_spec.rb +65 -0
- data/spec/extensions/pg_range_spec.rb +1 -0
- data/spec/extensions/spec_helper.rb +5 -5
- data/spec/extensions/tactical_eager_loading_spec.rb +71 -17
- data/spec/integration/associations_test.rb +77 -62
- data/spec/integration/dataset_test.rb +3 -3
- data/spec/integration/plugin_test.rb +22 -0
- data/spec/integration/prepared_statement_test.rb +8 -8
- data/spec/integration/spec_helper.rb +4 -0
- data/spec/model/association_reflection_spec.rb +30 -0
- data/spec/model/associations_spec.rb +177 -16
- metadata +6 -2
@@ -29,6 +29,12 @@ module Sequel
|
|
29
29
|
# objects, and the setting will not be persisted until after the object has
|
30
30
|
# been saved.
|
31
31
|
#
|
32
|
+
# By default, if you pass a nil value to the setter, an exception will be raised.
|
33
|
+
# You can change this behavior by using the :association_pks_nil association option.
|
34
|
+
# If set to :ignore, the setter will take no action if nil is given.
|
35
|
+
# If set to :remove, the setter will treat the nil as an empty array, removing
|
36
|
+
# the association all currently associated values.
|
37
|
+
#
|
32
38
|
# Usage:
|
33
39
|
#
|
34
40
|
# # Make all model subclass *_to_many associations have association_pks
|
@@ -74,21 +80,25 @@ module Sequel
|
|
74
80
|
end
|
75
81
|
|
76
82
|
opts[:pks_setter] = lambda do |pks|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
83
|
+
if pks.empty?
|
84
|
+
send(opts.remove_all_method)
|
85
|
+
else
|
86
|
+
checked_transaction do
|
87
|
+
if clpk
|
88
|
+
lpkv = lpk.map{|k| get_column_value(k)}
|
89
|
+
cond = lk.zip(lpkv)
|
90
|
+
else
|
91
|
+
lpkv = get_column_value(lpk)
|
92
|
+
cond = {lk=>lpkv}
|
93
|
+
end
|
94
|
+
ds = _join_table_dataset(opts).filter(cond)
|
95
|
+
ds.exclude(rk=>pks).delete
|
96
|
+
pks -= ds.select_map(rk)
|
97
|
+
lpkv = Array(lpkv)
|
98
|
+
key_array = crk ? pks.map{|pk| lpkv + pk} : pks.map{|pk| lpkv + [pk]}
|
99
|
+
key_columns = Array(lk) + Array(rk)
|
100
|
+
ds.import(key_columns, key_array)
|
84
101
|
end
|
85
|
-
ds = _join_table_dataset(opts).filter(cond)
|
86
|
-
ds.exclude(rk=>pks).delete
|
87
|
-
pks -= ds.select_map(rk)
|
88
|
-
lpkv = Array(lpkv)
|
89
|
-
key_array = crk ? pks.map{|pk| lpkv + pk} : pks.map{|pk| lpkv + [pk]}
|
90
|
-
key_columns = Array(lk) + Array(rk)
|
91
|
-
ds.import(key_columns, key_array)
|
92
102
|
end
|
93
103
|
end
|
94
104
|
|
@@ -109,25 +119,29 @@ module Sequel
|
|
109
119
|
end
|
110
120
|
|
111
121
|
opts[:pks_setter] = lambda do |pks|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
if key.is_a?(Array)
|
116
|
-
h = {}
|
117
|
-
nh = {}
|
118
|
-
key.zip(pk).each do|k, v|
|
119
|
-
h[k] = v
|
120
|
-
nh[k] = nil
|
121
|
-
end
|
122
|
+
if pks.empty?
|
123
|
+
send(opts.remove_all_method)
|
122
124
|
else
|
123
|
-
|
124
|
-
|
125
|
-
|
125
|
+
primary_key = opts.associated_class.primary_key
|
126
|
+
pkh = {primary_key=>pks}
|
127
|
+
|
128
|
+
if key.is_a?(Array)
|
129
|
+
h = {}
|
130
|
+
nh = {}
|
131
|
+
key.zip(pk).each do|k, v|
|
132
|
+
h[k] = v
|
133
|
+
nh[k] = nil
|
134
|
+
end
|
135
|
+
else
|
136
|
+
h = {key=>pk}
|
137
|
+
nh = {key=>nil}
|
138
|
+
end
|
126
139
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
140
|
+
checked_transaction do
|
141
|
+
ds = send(opts.dataset_method)
|
142
|
+
ds.unfiltered.filter(pkh).update(h)
|
143
|
+
ds.exclude(pkh).update(nh)
|
144
|
+
end
|
131
145
|
end
|
132
146
|
end
|
133
147
|
|
@@ -141,7 +155,7 @@ module Sequel
|
|
141
155
|
def after_save
|
142
156
|
if assoc_pks = @_association_pks
|
143
157
|
assoc_pks.each do |name, pks|
|
144
|
-
instance_exec(pks, &model.association_reflection(name)[:pks_setter])
|
158
|
+
instance_exec(pks, &model.association_reflection(name)[:pks_setter])
|
145
159
|
end
|
146
160
|
@_association_pks = nil
|
147
161
|
end
|
@@ -174,7 +188,19 @@ module Sequel
|
|
174
188
|
# If the receiver is a new object, save the pks
|
175
189
|
# so the update can happen after the received has been saved.
|
176
190
|
def _association_pks_setter(opts, pks)
|
191
|
+
if pks.nil?
|
192
|
+
case opts[:association_pks_nil]
|
193
|
+
when :remove
|
194
|
+
pks = []
|
195
|
+
when :ignore
|
196
|
+
return
|
197
|
+
else
|
198
|
+
raise Error, "nil value given to association_pks setter"
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
177
202
|
pks = convert_pk_array(opts, pks)
|
203
|
+
|
178
204
|
delay = opts[:delay_pks]
|
179
205
|
if (new? && delay) || (delay == :always)
|
180
206
|
modified!
|
@@ -190,7 +216,6 @@ module Sequel
|
|
190
216
|
klass = opts.associated_class
|
191
217
|
primary_key = klass.primary_key
|
192
218
|
sch = klass.db_schema
|
193
|
-
pks = Array(pks)
|
194
219
|
|
195
220
|
if primary_key.is_a?(Array)
|
196
221
|
if (cols = sch.values_at(*klass.primary_key)).all? && (convs = cols.map{|c| c[:type] == :integer}).all?
|
@@ -15,6 +15,11 @@ module Sequel
|
|
15
15
|
# and setting a new flag in the cloned dataset, so that each can check with the flag to
|
16
16
|
# determine whether it should call all.
|
17
17
|
#
|
18
|
+
# This plugin also makes #first and related methods that load single records work with
|
19
|
+
# eager loading. Note that when using eager_graph, calling #first or a similar method
|
20
|
+
# will result in two queries, one to load the main object, and one to eagerly load all associated
|
21
|
+
# objects to that main object.
|
22
|
+
#
|
18
23
|
# Usage:
|
19
24
|
#
|
20
25
|
# # Make all model subclass instances eagerly load for each (called before loading subclasses)
|
@@ -53,6 +58,23 @@ module Sequel
|
|
53
58
|
end
|
54
59
|
end
|
55
60
|
|
61
|
+
# Handle eager loading when calling first and related methods. For eager_graph,
|
62
|
+
# this does an additional query after retrieving a single record, because otherwise
|
63
|
+
# the associated records won't get eager loaded correctly.
|
64
|
+
def single_record!
|
65
|
+
if use_eager_all?
|
66
|
+
obj = clone(:all_called=>true).all.first
|
67
|
+
|
68
|
+
if opts[:eager_graph]
|
69
|
+
obj = clone(:all_called=>true).where(obj.qualified_pk_hash).unlimited.all.first
|
70
|
+
end
|
71
|
+
|
72
|
+
obj
|
73
|
+
else
|
74
|
+
super
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
56
78
|
private
|
57
79
|
|
58
80
|
# Wether to use all when each is called, true when eager loading
|
@@ -3,11 +3,12 @@
|
|
3
3
|
module Sequel
|
4
4
|
module Plugins
|
5
5
|
# The PgTypecastOnLoad plugin exists because when you connect to PostgreSQL
|
6
|
-
# using the do
|
6
|
+
# using the do and swift adapters, Sequel doesn't have complete
|
7
7
|
# control over typecasting, and may return columns as strings instead of how
|
8
8
|
# the native postgres adapter would typecast them. This is mostly needed for
|
9
9
|
# the additional support that the pg_* extensions add for advanced PostgreSQL
|
10
|
-
# types such as arrays.
|
10
|
+
# types such as arrays. This plugin is not needed if using the postgres or
|
11
|
+
# jdbc/postgresql adapters.
|
11
12
|
#
|
12
13
|
# This plugin makes model loading to do the same conversion that the
|
13
14
|
# native postgres adapter would do for all columns given. You can either
|
@@ -19,7 +19,47 @@ module Sequel
|
|
19
19
|
# Album.filter{id<100}.all do |a|
|
20
20
|
# a.artists
|
21
21
|
# end
|
22
|
+
# # SELECT * FROM albums WHERE (id < 100)
|
23
|
+
# # SELECT * FROM artists WHERE id IN (...)
|
24
|
+
#
|
25
|
+
# Note that if you are passing a callback to the association method via
|
26
|
+
# a block or :callback option, or using the :reload option to reload
|
27
|
+
# the association, eager loading will not be done.
|
28
|
+
#
|
29
|
+
# You can use the :eager_reload option to reload the association for all
|
30
|
+
# objects that the current object was retrieved with:
|
31
|
+
#
|
32
|
+
# # SELECT * FROM albums WHERE (id < 100)
|
33
|
+
# albums = Album.filter{id<100}.all
|
34
|
+
#
|
35
|
+
# # Eagerly load all artists for these albums
|
36
|
+
# # SELECT * FROM artists WHERE id IN (...)
|
37
|
+
# albums.first.artists
|
38
|
+
#
|
39
|
+
# # Do something that may affect which artists are associated to the albums
|
40
|
+
#
|
41
|
+
# # Eagerly reload all artists for these albums
|
42
|
+
# # SELECT * FROM artists WHERE id IN (...)
|
43
|
+
# albums.first.artists(:eager_reload=>true)
|
22
44
|
#
|
45
|
+
# You can also use the :eager option to specify dependent associations
|
46
|
+
# to eager load:
|
47
|
+
#
|
48
|
+
# albums = Album.filter{id<100}.all
|
49
|
+
#
|
50
|
+
# # Eager load all artists for these albums, and all albums for those artists
|
51
|
+
# # SELECT * FROM artists WHERE id IN (...)
|
52
|
+
# # SELECT * FROM albums WHERE artist_id IN (...)
|
53
|
+
# albums.first.artists(:eager=>:albums)
|
54
|
+
#
|
55
|
+
# You can also use :eager to specify an eager callback. For example:
|
56
|
+
#
|
57
|
+
# albums = Album.filter{id<100}.all
|
58
|
+
#
|
59
|
+
# # Eagerly load all artists whose name starts with A-M for these albums
|
60
|
+
# # SELECT * FROM artists WHERE name > 'N' AND id IN (...)
|
61
|
+
# albums.first.artists(:eager=>proc{|ds| ds.where(:name > 'N')})
|
62
|
+
#
|
23
63
|
# Usage:
|
24
64
|
#
|
25
65
|
# # Make all model subclass instances use tactical eager loading (called before loading subclasses)
|
@@ -51,11 +91,12 @@ module Sequel
|
|
51
91
|
# If there the association is not in the associations cache and the object
|
52
92
|
# was reteived via Dataset#all, eagerly load the association for all model
|
53
93
|
# objects retrieved with the current object.
|
54
|
-
def load_associated_objects(opts,
|
94
|
+
def load_associated_objects(opts, dynamic_opts=nil, &block)
|
95
|
+
dynamic_opts = load_association_objects_options(dynamic_opts, &block)
|
55
96
|
name = opts[:name]
|
56
|
-
if !associations.include?(name) && retrieved_by && !frozen?
|
97
|
+
if (!associations.include?(name) || dynamic_opts[:eager_reload]) && retrieved_by && !frozen? && !dynamic_opts[:callback] && !dynamic_opts[:reload]
|
57
98
|
begin
|
58
|
-
retrieved_by.send(:eager_load, retrieved_with.reject(&:frozen?), name=>{})
|
99
|
+
retrieved_by.send(:eager_load, retrieved_with.reject(&:frozen?), name=>dynamic_opts[:eager] || {})
|
59
100
|
rescue Sequel::UndefinedAssociation
|
60
101
|
# This can happen if class table inheritance is used and the association
|
61
102
|
# is only defined in a subclass. This particular instance can use the
|
data/lib/sequel/version.rb
CHANGED
@@ -5,13 +5,13 @@ module Sequel
|
|
5
5
|
MAJOR = 4
|
6
6
|
# The minor version of Sequel. Bumped for every non-patch level
|
7
7
|
# release, generally around once a month.
|
8
|
-
MINOR =
|
8
|
+
MINOR = 32
|
9
9
|
# The tiny version of Sequel. Usually 0, only bumped for bugfix
|
10
10
|
# releases that fix regressions from previous versions.
|
11
11
|
TINY = 0
|
12
12
|
|
13
13
|
# The version of Sequel you are using, as a string (e.g. "2.11.0")
|
14
|
-
VERSION = [MAJOR, MINOR, TINY].join('.')
|
14
|
+
VERSION = [MAJOR, MINOR, TINY].join('.').freeze
|
15
15
|
|
16
16
|
# The version of Sequel you are using, as a string (e.g. "2.11.0")
|
17
17
|
def self.version
|
data/spec/adapters/mysql_spec.rb
CHANGED
@@ -325,12 +325,6 @@ describe "Joined MySQL dataset" do
|
|
325
325
|
@ds.join(:attributes, :node_id => :id).sql.must_equal "SELECT * FROM `nodes` INNER JOIN `attributes` ON (`attributes`.`node_id` = `nodes`.`id`)"
|
326
326
|
end
|
327
327
|
|
328
|
-
it "should allow a having clause on ungrouped datasets" do
|
329
|
-
@ds.having('blah')
|
330
|
-
|
331
|
-
@ds.having('blah').sql.must_equal "SELECT * FROM `nodes` HAVING (blah)"
|
332
|
-
end
|
333
|
-
|
334
328
|
it "should put a having clause before an order by clause" do
|
335
329
|
@ds.order(:aaa).having(:bbb => :ccc).sql.must_equal "SELECT * FROM `nodes` HAVING (`bbb` = `ccc`) ORDER BY `aaa`"
|
336
330
|
end
|
@@ -1289,3 +1283,37 @@ if DB.adapter_scheme == :mysql2
|
|
1289
1283
|
end
|
1290
1284
|
end
|
1291
1285
|
end
|
1286
|
+
|
1287
|
+
describe "MySQL joined datasets" do
|
1288
|
+
before do
|
1289
|
+
@db = DB
|
1290
|
+
@db.create_table!(:a) do
|
1291
|
+
Integer :id
|
1292
|
+
end
|
1293
|
+
@db.create_table!(:b) do
|
1294
|
+
Integer :id
|
1295
|
+
Integer :a_id
|
1296
|
+
end
|
1297
|
+
@db[:a].insert(1)
|
1298
|
+
@db[:a].insert(2)
|
1299
|
+
@db[:b].insert(3, 1)
|
1300
|
+
@db[:b].insert(4, 1)
|
1301
|
+
@db[:b].insert(5, 2)
|
1302
|
+
@ds = @db[:a].join(:b, :a_id=>:id)
|
1303
|
+
end
|
1304
|
+
after do
|
1305
|
+
@db.drop_table?(:a, :b)
|
1306
|
+
end
|
1307
|
+
|
1308
|
+
it "should support deletions from a single table" do
|
1309
|
+
@ds.where(:a__id=>1).delete
|
1310
|
+
@db[:a].select_order_map(:id).must_equal [2]
|
1311
|
+
@db[:b].select_order_map(:id).must_equal [3, 4, 5]
|
1312
|
+
end
|
1313
|
+
|
1314
|
+
it "should support deletions from multiple tables" do
|
1315
|
+
@ds.delete_from(:a, :b).where(:a__id=>1).delete
|
1316
|
+
@db[:a].select_order_map(:id).must_equal [2]
|
1317
|
+
@db[:b].select_order_map(:id).must_equal [5]
|
1318
|
+
end
|
1319
|
+
end
|
@@ -222,7 +222,7 @@ describe "An Oracle database" do
|
|
222
222
|
it "should translate values correctly" do
|
223
223
|
@d << {:name => 'abc', :value => 456}
|
224
224
|
@d << {:name => 'def', :value => 789}
|
225
|
-
@d.filter
|
225
|
+
@d.filter{value > 500}.update(:date_created => Sequel.lit("to_timestamp('2009-09-09', 'YYYY-MM-DD')"))
|
226
226
|
|
227
227
|
@d[:name => 'def'][:date_created].strftime('%F').must_equal '2009-09-09'
|
228
228
|
end
|
data/spec/bin_spec.rb
CHANGED
@@ -139,7 +139,7 @@ END
|
|
139
139
|
end
|
140
140
|
|
141
141
|
it "-E should echo SQL statements to stdout" do
|
142
|
-
bin(:args=>'-E -c DB.tables').
|
142
|
+
bin(:args=>'-E -c DB.tables').must_include "SELECT * FROM `sqlite_master` WHERE ((`name` != 'sqlite_sequence') AND (`type` = 'table'))"
|
143
143
|
end
|
144
144
|
|
145
145
|
it "-I should include directory in load path" do
|
@@ -148,7 +148,7 @@ END
|
|
148
148
|
|
149
149
|
it "-l should log SQL statements to file" do
|
150
150
|
bin(:args=>"-l #{TMP_FILE} -c DB.tables").must_equal ''
|
151
|
-
File.read(TMP_FILE).
|
151
|
+
File.read(TMP_FILE).must_include "SELECT * FROM `sqlite_master` WHERE ((`name` != 'sqlite_sequence') AND (`type` = 'table'))"
|
152
152
|
end
|
153
153
|
|
154
154
|
it "-L should load all *.rb files in given directory" do
|
data/spec/core/dataset_spec.rb
CHANGED
@@ -3642,6 +3642,13 @@ describe "Dataset prepared statements and bound variables " do
|
|
3642
3642
|
proc{ps.prepare(:select, :select_n2)}.must_raise Sequel::Error
|
3643
3643
|
end
|
3644
3644
|
|
3645
|
+
it "PreparedStatement#prepare should not raise an error if preparing prepared statements is allowed" do
|
3646
|
+
ps = @ds.prepare(:select, :select_n)
|
3647
|
+
def ps.allow_preparing_prepared_statements?; true end
|
3648
|
+
ps.prepare(:select, :select_n2).call
|
3649
|
+
@db.sqls.must_equal ["SELECT * FROM items"]
|
3650
|
+
end
|
3651
|
+
|
3645
3652
|
it "#call should default to using :all if an invalid type is given" do
|
3646
3653
|
@ds.filter(:num=>:$n).call(:select_all, :n=>1)
|
3647
3654
|
@db.sqls.must_equal ['SELECT * FROM items WHERE (num = 1)']
|
@@ -336,6 +336,14 @@ describe "Sequel::Plugins::AssociationPks" do
|
|
336
336
|
"UPDATE albums SET artist_id = NULL WHERE ((albums.artist_id = 1) AND (id NOT IN (2, 4)))"
|
337
337
|
]
|
338
338
|
|
339
|
+
ar.album_pks = []
|
340
|
+
@db.sqls.must_equal []
|
341
|
+
|
342
|
+
ar.save_changes
|
343
|
+
@db.sqls.must_equal [
|
344
|
+
"UPDATE albums SET artist_id = NULL WHERE (artist_id = 1)"
|
345
|
+
]
|
346
|
+
|
339
347
|
al = @Album.load(:id=>1)
|
340
348
|
al.tag_pks.must_equal [1,2]
|
341
349
|
@db.sqls
|
@@ -351,6 +359,14 @@ describe "Sequel::Plugins::AssociationPks" do
|
|
351
359
|
"INSERT INTO albums_tags (album_id, tag_id) VALUES (1, 3)",
|
352
360
|
"COMMIT",
|
353
361
|
]
|
362
|
+
|
363
|
+
al.tag_pks = []
|
364
|
+
@db.sqls.must_equal []
|
365
|
+
|
366
|
+
al.save_changes
|
367
|
+
@db.sqls.must_equal [
|
368
|
+
"DELETE FROM albums_tags WHERE (album_id = 1)",
|
369
|
+
]
|
354
370
|
end
|
355
371
|
|
356
372
|
it "should clear delayed associated pks if refreshing, if :delay plugin option is used" do
|
@@ -364,4 +380,26 @@ describe "Sequel::Plugins::AssociationPks" do
|
|
364
380
|
ar.refresh
|
365
381
|
ar.album_pks.must_equal [1,2,3]
|
366
382
|
end
|
383
|
+
|
384
|
+
it "should remove all values if nil given to setter and :association_pks_nil=>:remove" do
|
385
|
+
@Artist.one_to_many :albums, :clone=>:albums, :association_pks_nil=>:remove
|
386
|
+
|
387
|
+
ar = @Artist.load(:id=>1)
|
388
|
+
ar.album_pks = nil
|
389
|
+
@db.sqls.must_equal ["UPDATE albums SET artist_id = NULL WHERE (artist_id = 1)"]
|
390
|
+
end
|
391
|
+
|
392
|
+
it "should take no action if nil given to setter and :association_pks_nil=>:ignore" do
|
393
|
+
@Artist.one_to_many :albums, :clone=>:albums, :association_pks_nil=>:ignore
|
394
|
+
|
395
|
+
ar = @Artist.load(:id=>1)
|
396
|
+
ar = @Artist.new
|
397
|
+
ar.album_pks = nil
|
398
|
+
@db.sqls.must_equal []
|
399
|
+
end
|
400
|
+
|
401
|
+
it "should raise error if nil given to setter by default" do
|
402
|
+
ar = @Artist.load(:id=>1)
|
403
|
+
proc{ar.album_pks = nil}.must_raise Sequel::Error
|
404
|
+
end
|
367
405
|
end
|
@@ -51,6 +51,14 @@ describe "class_table_inheritance plugin" do
|
|
51
51
|
Object.send(:remove_const, :Employee)
|
52
52
|
end
|
53
53
|
|
54
|
+
it "#cti_base_model should be the model that loaded the plugin" do
|
55
|
+
Executive.cti_base_model.must_equal Employee
|
56
|
+
end
|
57
|
+
|
58
|
+
it "#cti_columns should be a mapping of table names to columns" do
|
59
|
+
Executive.cti_columns.must_equal(:employees=>[:id, :name, :kind], :managers=>[:id, :num_staff], :executives=>[:id, :num_managers])
|
60
|
+
end
|
61
|
+
|
54
62
|
it "should have simple_table = nil for all subclasses" do
|
55
63
|
Manager.simple_table.must_equal nil
|
56
64
|
Executive.simple_table.must_equal nil
|
@@ -246,7 +254,23 @@ describe "class_table_inheritance plugin" do
|
|
246
254
|
sqls[0].must_match(/INSERT INTO employees \((name|kind), (name|kind)\) VALUES \('(E|Ceo)', '(E|Ceo)'\)/)
|
247
255
|
sqls[1].must_match(/INSERT INTO managers \((num_staff|id), (num_staff|id)\) VALUES \([12], [12]\)/)
|
248
256
|
sqls[2].must_match(/INSERT INTO executives \((num_managers|id), (num_managers|id)\) VALUES \([13], [13]\)/)
|
257
|
+
end
|
258
|
+
|
259
|
+
it "should insert the correct rows into all tables when inserting when insert select is important" do
|
260
|
+
[Ceo, Manager, Employee].each do |klass|
|
261
|
+
def (klass.cti_instance_dataset).supports_insert_select?; true; end
|
262
|
+
def (klass.cti_instance_dataset).insert_select(v)
|
263
|
+
db.run(insert_sql(v) + " RETURNING *")
|
264
|
+
v.merge(:id=>1)
|
265
|
+
end
|
249
266
|
end
|
267
|
+
Ceo.create(:num_managers=>3, :num_staff=>2, :name=>'E')
|
268
|
+
sqls = @db.sqls
|
269
|
+
sqls.length.must_equal 3
|
270
|
+
sqls[0].must_match(/INSERT INTO employees \((name|kind), (name|kind)\) VALUES \('(E|Ceo)', '(E|Ceo)'\) RETURNING \*/)
|
271
|
+
sqls[1].must_match(/INSERT INTO managers \((num_staff|id), (num_staff|id)\) VALUES \([12], [12]\) RETURNING \*/)
|
272
|
+
sqls[2].must_match(/INSERT INTO executives \((num_managers|id), (num_managers|id)\) VALUES \([13], [13]\) RETURNING \*/)
|
273
|
+
end
|
250
274
|
|
251
275
|
it "should insert the correct rows into all tables with a given primary key" do
|
252
276
|
e = Ceo.new(:num_managers=>3, :num_staff=>2, :name=>'E')
|