sequel 3.27.0 → 3.28.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.
- data/CHANGELOG +96 -0
- data/README.rdoc +2 -2
- data/Rakefile +1 -1
- data/doc/association_basics.rdoc +48 -0
- data/doc/opening_databases.rdoc +29 -5
- data/doc/prepared_statements.rdoc +1 -0
- data/doc/release_notes/3.28.0.txt +304 -0
- data/doc/testing.rdoc +42 -0
- data/doc/transactions.rdoc +97 -0
- data/lib/sequel/adapters/db2.rb +95 -65
- data/lib/sequel/adapters/firebird.rb +25 -219
- data/lib/sequel/adapters/ibmdb.rb +440 -0
- data/lib/sequel/adapters/jdbc.rb +12 -0
- data/lib/sequel/adapters/jdbc/as400.rb +0 -7
- data/lib/sequel/adapters/jdbc/db2.rb +49 -0
- data/lib/sequel/adapters/jdbc/firebird.rb +34 -0
- data/lib/sequel/adapters/jdbc/oracle.rb +2 -27
- data/lib/sequel/adapters/jdbc/transactions.rb +34 -0
- data/lib/sequel/adapters/mysql.rb +10 -15
- data/lib/sequel/adapters/odbc.rb +1 -2
- data/lib/sequel/adapters/odbc/db2.rb +5 -5
- data/lib/sequel/adapters/postgres.rb +71 -11
- data/lib/sequel/adapters/shared/db2.rb +290 -0
- data/lib/sequel/adapters/shared/firebird.rb +214 -0
- data/lib/sequel/adapters/shared/mssql.rb +18 -75
- data/lib/sequel/adapters/shared/mysql.rb +13 -0
- data/lib/sequel/adapters/shared/postgres.rb +52 -36
- data/lib/sequel/adapters/shared/sqlite.rb +32 -36
- data/lib/sequel/adapters/sqlite.rb +4 -8
- data/lib/sequel/adapters/tinytds.rb +7 -3
- data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +55 -0
- data/lib/sequel/core.rb +1 -1
- data/lib/sequel/database/connecting.rb +1 -1
- data/lib/sequel/database/misc.rb +6 -5
- data/lib/sequel/database/query.rb +1 -1
- data/lib/sequel/database/schema_generator.rb +2 -1
- data/lib/sequel/dataset/actions.rb +149 -33
- data/lib/sequel/dataset/features.rb +44 -7
- data/lib/sequel/dataset/misc.rb +9 -1
- data/lib/sequel/dataset/prepared_statements.rb +2 -2
- data/lib/sequel/dataset/query.rb +63 -10
- data/lib/sequel/dataset/sql.rb +22 -5
- data/lib/sequel/model.rb +3 -3
- data/lib/sequel/model/associations.rb +250 -27
- data/lib/sequel/model/base.rb +10 -16
- data/lib/sequel/plugins/many_through_many.rb +34 -2
- data/lib/sequel/plugins/prepared_statements_with_pk.rb +1 -1
- data/lib/sequel/sql.rb +94 -51
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/db2_spec.rb +146 -0
- data/spec/adapters/postgres_spec.rb +74 -6
- data/spec/adapters/spec_helper.rb +1 -0
- data/spec/adapters/sqlite_spec.rb +11 -0
- data/spec/core/database_spec.rb +7 -0
- data/spec/core/dataset_spec.rb +180 -17
- data/spec/core/expression_filters_spec.rb +107 -41
- data/spec/core/spec_helper.rb +11 -0
- data/spec/extensions/many_through_many_spec.rb +115 -1
- data/spec/extensions/prepared_statements_with_pk_spec.rb +3 -3
- data/spec/integration/associations_test.rb +193 -15
- data/spec/integration/database_test.rb +4 -2
- data/spec/integration/dataset_test.rb +215 -19
- data/spec/integration/plugin_test.rb +8 -5
- data/spec/integration/prepared_statement_test.rb +91 -98
- data/spec/integration/schema_test.rb +27 -11
- data/spec/integration/spec_helper.rb +10 -0
- data/spec/integration/type_test.rb +2 -2
- data/spec/model/association_reflection_spec.rb +91 -0
- data/spec/model/associations_spec.rb +13 -0
- data/spec/model/base_spec.rb +8 -21
- data/spec/model/eager_loading_spec.rb +243 -9
- data/spec/model/model_spec.rb +15 -2
- metadata +16 -4
@@ -6,18 +6,21 @@ describe "Database schema parser" do
|
|
6
6
|
@iom = INTEGRATION_DB.identifier_output_method
|
7
7
|
@iim = INTEGRATION_DB.identifier_input_method
|
8
8
|
@defsch = INTEGRATION_DB.default_schema
|
9
|
+
@qi = INTEGRATION_DB.quote_identifiers?
|
9
10
|
clear_sqls
|
10
11
|
end
|
11
12
|
after do
|
12
13
|
INTEGRATION_DB.identifier_output_method = @iom
|
13
14
|
INTEGRATION_DB.identifier_input_method = @iim
|
14
15
|
INTEGRATION_DB.default_schema = @defsch
|
16
|
+
INTEGRATION_DB.quote_identifiers = @qi
|
15
17
|
INTEGRATION_DB.drop_table(:items) if INTEGRATION_DB.table_exists?(:items)
|
16
18
|
end
|
17
19
|
|
18
20
|
specify "should handle a database with a identifier_output_method" do
|
19
21
|
INTEGRATION_DB.identifier_output_method = :reverse
|
20
22
|
INTEGRATION_DB.identifier_input_method = :reverse
|
23
|
+
INTEGRATION_DB.quote_identifiers = true
|
21
24
|
INTEGRATION_DB.default_schema = nil if INTEGRATION_DB.default_schema
|
22
25
|
INTEGRATION_DB.create_table!(:items){Integer :number}
|
23
26
|
INTEGRATION_DB.schema(:items, :reload=>true).should be_a_kind_of(Array)
|
@@ -85,7 +88,7 @@ describe "Database schema parser" do
|
|
85
88
|
INTEGRATION_DB.schema(:items).first.last[:ruby_default].should == 'blah'
|
86
89
|
end
|
87
90
|
|
88
|
-
|
91
|
+
cspecify "should parse types from the schema properly", [:jdbc, :db2] do
|
89
92
|
INTEGRATION_DB.create_table!(:items){Integer :number}
|
90
93
|
INTEGRATION_DB.schema(:items).first.last[:type].should == :integer
|
91
94
|
INTEGRATION_DB.create_table!(:items){Fixnum :number}
|
@@ -233,7 +236,7 @@ describe "Database schema modifiers" do
|
|
233
236
|
specify "should add columns to tables correctly" do
|
234
237
|
@db.create_table!(:items){Integer :number}
|
235
238
|
@ds.insert(:number=>10)
|
236
|
-
@db.alter_table(:items){add_column :name,
|
239
|
+
@db.alter_table(:items){add_column :name, String}
|
237
240
|
@db.schema(:items, :reload=>true).map{|x| x.first}.should == [:number, :name]
|
238
241
|
@ds.columns!.should == [:number, :name]
|
239
242
|
@ds.all.should == [{:number=>10, :name=>nil}]
|
@@ -296,7 +299,7 @@ describe "Database schema modifiers" do
|
|
296
299
|
proc{@ds.insert(:n=>nil)}.should raise_error(Sequel::DatabaseError)
|
297
300
|
end
|
298
301
|
|
299
|
-
|
302
|
+
cspecify "should set column NULL/NOT NULL correctly", [:jdbc, :db2] do
|
300
303
|
@db.create_table!(:items, :engine=>:InnoDB){Integer :id}
|
301
304
|
@ds.insert(:id=>10)
|
302
305
|
@db.alter_table(:items){set_column_allow_null :id, false}
|
@@ -318,7 +321,7 @@ describe "Database schema modifiers" do
|
|
318
321
|
@ds.all.should == [{:id=>10}, {:id=>20}]
|
319
322
|
end
|
320
323
|
|
321
|
-
|
324
|
+
cspecify "should set column types correctly", [:jdbc, :db2] do
|
322
325
|
@db.create_table!(:items){Integer :id}
|
323
326
|
@ds.insert(:id=>10)
|
324
327
|
@db.alter_table(:items){set_column_type :id, String}
|
@@ -328,7 +331,7 @@ describe "Database schema modifiers" do
|
|
328
331
|
@ds.all.should == [{:id=>"10"}, {:id=>"20"}]
|
329
332
|
end
|
330
333
|
|
331
|
-
cspecify "should add unique constraints and foreign key table constraints correctly", :sqlite do
|
334
|
+
cspecify "should add unnamed unique constraints and foreign key table constraints correctly", :sqlite do
|
332
335
|
@db.create_table!(:items, :engine=>:InnoDB){Integer :id; Integer :item_id}
|
333
336
|
@db.alter_table(:items) do
|
334
337
|
add_unique_constraint [:item_id, :id]
|
@@ -341,6 +344,19 @@ describe "Database schema modifiers" do
|
|
341
344
|
proc{@ds.insert(1, 2)}.should raise_error
|
342
345
|
end
|
343
346
|
|
347
|
+
cspecify "should add named unique constraints and foreign key table constraints correctly", :sqlite do
|
348
|
+
@db.create_table!(:items, :engine=>:InnoDB){Integer :id, :null=>false; Integer :item_id, :null=>false}
|
349
|
+
@db.alter_table(:items) do
|
350
|
+
add_unique_constraint [:item_id, :id], :name=>:unique_iii
|
351
|
+
add_foreign_key [:id, :item_id], :items, :key=>[:item_id, :id], :name=>:fk_iii
|
352
|
+
end
|
353
|
+
@db.schema(:items, :reload=>true).map{|x| x.first}.should == [:id, :item_id]
|
354
|
+
@ds.columns!.should == [:id, :item_id]
|
355
|
+
proc{@ds.insert(1, 1)}.should_not raise_error
|
356
|
+
proc{@ds.insert(1, 1)}.should raise_error
|
357
|
+
proc{@ds.insert(1, 2)}.should raise_error
|
358
|
+
end
|
359
|
+
|
344
360
|
cspecify "should drop unique constraints and foreign key table constraints correctly", :sqlite do
|
345
361
|
@db.create_table!(:items) do
|
346
362
|
Integer :id
|
@@ -358,7 +374,7 @@ describe "Database schema modifiers" do
|
|
358
374
|
proc{@ds.insert(1, 2)}.should_not raise_error
|
359
375
|
end
|
360
376
|
|
361
|
-
cspecify "should remove columns from tables correctly", :h2, :mssql do
|
377
|
+
cspecify "should remove columns from tables correctly", :h2, :mssql, [:jdbc, :db2] do
|
362
378
|
@db.create_table!(:items) do
|
363
379
|
primary_key :id
|
364
380
|
String :name
|
@@ -379,7 +395,7 @@ describe "Database schema modifiers" do
|
|
379
395
|
@ds.columns!.should == [:id]
|
380
396
|
end
|
381
397
|
|
382
|
-
|
398
|
+
cspecify "should remove multiple columns in a single alter_table block", [:jdbc, :db2] do
|
383
399
|
@db.create_table!(:items) do
|
384
400
|
primary_key :id
|
385
401
|
String :name
|
@@ -417,10 +433,10 @@ describe "Database#tables" do
|
|
417
433
|
clear_sqls
|
418
434
|
end
|
419
435
|
after do
|
420
|
-
@db.drop_view :sequel_test_view
|
421
|
-
@db.drop_table :sequel_test_table
|
422
436
|
@db.identifier_output_method = @iom
|
423
437
|
@db.identifier_input_method = @iim
|
438
|
+
@db.drop_view :sequel_test_view
|
439
|
+
@db.drop_table :sequel_test_table
|
424
440
|
end
|
425
441
|
|
426
442
|
specify "should return an array of symbols" do
|
@@ -459,10 +475,10 @@ describe "Database#views" do
|
|
459
475
|
clear_sqls
|
460
476
|
end
|
461
477
|
after do
|
462
|
-
@db.drop_view :sequel_test_view
|
463
|
-
@db.drop_table :sequel_test_table
|
464
478
|
@db.identifier_output_method = @iom
|
465
479
|
@db.identifier_input_method = @iim
|
480
|
+
@db.drop_view :sequel_test_view
|
481
|
+
@db.drop_table :sequel_test_table
|
466
482
|
end
|
467
483
|
|
468
484
|
specify "should return an array of symbols" do
|
@@ -49,6 +49,7 @@ end
|
|
49
49
|
end
|
50
50
|
|
51
51
|
def self.cspecify(message, *checked, &block)
|
52
|
+
return specify(message, &block) if ENV['SEQUEL_NO_PENDING']
|
52
53
|
pending = false
|
53
54
|
checked.each do |c|
|
54
55
|
case c
|
@@ -89,3 +90,12 @@ if defined?(INTEGRATION_DB) || defined?(INTEGRATION_URL) || ENV['SEQUEL_INTEGRAT
|
|
89
90
|
else
|
90
91
|
INTEGRATION_DB = Sequel.sqlite('', :quote_identifiers=>false)
|
91
92
|
end
|
93
|
+
|
94
|
+
if INTEGRATION_DB.adapter_scheme == :ibmdb
|
95
|
+
def INTEGRATION_DB.drop_table(*tables)
|
96
|
+
super
|
97
|
+
rescue Sequel::DatabaseError
|
98
|
+
disconnect
|
99
|
+
super
|
100
|
+
end
|
101
|
+
end
|
@@ -6,7 +6,7 @@ describe "Supported types" do
|
|
6
6
|
INTEGRATION_DB[:items]
|
7
7
|
end
|
8
8
|
|
9
|
-
|
9
|
+
specify "should support casting correctly" do
|
10
10
|
ds = create_items_table_with_column(:number, Integer)
|
11
11
|
ds.insert(:number => 1)
|
12
12
|
ds.select(:number.cast_string.as(:n)).map(:n).should == %w'1'
|
@@ -100,7 +100,7 @@ describe "Supported types" do
|
|
100
100
|
ds.first[:name].should be_a_kind_of(::Sequel::SQL::Blob)
|
101
101
|
end
|
102
102
|
|
103
|
-
cspecify "should support generic boolean type", [:do, :sqlite], [:jdbc, :sqlite], [:odbc, :mssql] do
|
103
|
+
cspecify "should support generic boolean type", [:do, :sqlite], [:jdbc, :sqlite], [:jdbc, :db2], [:odbc, :mssql] do
|
104
104
|
ds = create_items_table_with_column(:number, TrueClass)
|
105
105
|
ds.insert(:number => true)
|
106
106
|
ds.all.should == [{:number=>true}]
|
@@ -184,6 +184,7 @@ describe Sequel::Model::Associations::AssociationReflection, "#remove_before_des
|
|
184
184
|
@c.many_to_many :cs, :class=>@c
|
185
185
|
@c.association_reflection(:cs).remove_before_destroy?.should be_true
|
186
186
|
end
|
187
|
+
|
187
188
|
it "should be false for one_to_one and one_to_many associations" do
|
188
189
|
@c.one_to_one :c, :class=>@c
|
189
190
|
@c.association_reflection(:c).remove_before_destroy?.should be_false
|
@@ -192,3 +193,93 @@ describe Sequel::Model::Associations::AssociationReflection, "#remove_before_des
|
|
192
193
|
end
|
193
194
|
end
|
194
195
|
|
196
|
+
describe Sequel::Model::Associations::AssociationReflection, "#eager_limit_strategy" do
|
197
|
+
before do
|
198
|
+
@c = Class.new(Sequel::Model(:a))
|
199
|
+
end
|
200
|
+
after do
|
201
|
+
Sequel::Model.default_eager_limit_strategy = nil
|
202
|
+
end
|
203
|
+
|
204
|
+
it "should be nil by default for *_one associations" do
|
205
|
+
@c.many_to_one :c, :class=>@c
|
206
|
+
@c.association_reflection(:c).eager_limit_strategy.should be_nil
|
207
|
+
@c.one_to_one :c, :class=>@c
|
208
|
+
@c.association_reflection(:c).eager_limit_strategy.should be_nil
|
209
|
+
end
|
210
|
+
|
211
|
+
it "should be :ruby by default for *_many associations" do
|
212
|
+
@c.one_to_many :cs, :class=>@c, :limit=>1
|
213
|
+
@c.association_reflection(:cs).eager_limit_strategy.should == :ruby
|
214
|
+
@c.many_to_many :cs, :class=>@c, :limit=>1
|
215
|
+
@c.association_reflection(:cs).eager_limit_strategy.should == :ruby
|
216
|
+
end
|
217
|
+
|
218
|
+
it "should be nil for many_to_one associations" do
|
219
|
+
@c.many_to_one :c, :class=>@c, :eager_limit_strategy=>true
|
220
|
+
@c.association_reflection(:c).eager_limit_strategy.should be_nil
|
221
|
+
@c.many_to_one :c, :class=>@c, :eager_limit_strategy=>:distinct_on
|
222
|
+
@c.association_reflection(:c).eager_limit_strategy.should be_nil
|
223
|
+
end
|
224
|
+
|
225
|
+
it "should be a symbol for other associations if given a symbol" do
|
226
|
+
@c.one_to_one :c, :class=>@c, :eager_limit_strategy=>:distinct_on
|
227
|
+
@c.association_reflection(:c).eager_limit_strategy.should == :distinct_on
|
228
|
+
@c.one_to_many :cs, :class=>@c, :eager_limit_strategy=>:window_function, :limit=>1
|
229
|
+
@c.association_reflection(:cs).eager_limit_strategy.should == :window_function
|
230
|
+
end
|
231
|
+
|
232
|
+
it "should use :distinct_on for one_to_one associations if picking and the association dataset supports ordered distinct on" do
|
233
|
+
@c.dataset.meta_def(:supports_ordered_distinct_on?){true}
|
234
|
+
@c.one_to_one :c, :class=>@c, :eager_limit_strategy=>true
|
235
|
+
@c.association_reflection(:c).eager_limit_strategy.should == :distinct_on
|
236
|
+
end
|
237
|
+
|
238
|
+
it "should use :window_function for associations if picking and the association dataset supports window functions" do
|
239
|
+
@c.dataset.meta_def(:supports_window_functions?){true}
|
240
|
+
@c.one_to_one :c, :class=>@c, :eager_limit_strategy=>true
|
241
|
+
@c.association_reflection(:c).eager_limit_strategy.should == :window_function
|
242
|
+
@c.one_to_many :cs, :class=>@c, :eager_limit_strategy=>true, :limit=>1
|
243
|
+
@c.association_reflection(:cs).eager_limit_strategy.should == :window_function
|
244
|
+
@c.many_to_many :cs, :class=>@c, :eager_limit_strategy=>true, :limit=>1
|
245
|
+
@c.association_reflection(:cs).eager_limit_strategy.should == :window_function
|
246
|
+
end
|
247
|
+
|
248
|
+
it "should use :ruby for *_many associations if picking and the association dataset doesn't window functions" do
|
249
|
+
@c.one_to_many :cs, :class=>@c, :eager_limit_strategy=>true, :limit=>1
|
250
|
+
@c.association_reflection(:cs).eager_limit_strategy.should == :ruby
|
251
|
+
@c.many_to_many :cs, :class=>@c, :eager_limit_strategy=>true, :limit=>1
|
252
|
+
@c.association_reflection(:cs).eager_limit_strategy.should == :ruby
|
253
|
+
end
|
254
|
+
|
255
|
+
it "should respect Model.default_eager_limit_strategy to *_many associations" do
|
256
|
+
Sequel::Model.default_eager_limit_strategy = :window_function
|
257
|
+
Sequel::Model.default_eager_limit_strategy.should == :window_function
|
258
|
+
c = Class.new(Sequel::Model)
|
259
|
+
c.dataset = :a
|
260
|
+
c.default_eager_limit_strategy.should == :window_function
|
261
|
+
c.one_to_many :cs, :class=>c, :limit=>1
|
262
|
+
c.association_reflection(:cs).eager_limit_strategy.should == :window_function
|
263
|
+
c.many_to_many :cs, :class=>c, :limit=>1
|
264
|
+
c.association_reflection(:cs).eager_limit_strategy.should == :window_function
|
265
|
+
|
266
|
+
Sequel::Model.default_eager_limit_strategy = true
|
267
|
+
c = Class.new(Sequel::Model)
|
268
|
+
c.dataset = :a
|
269
|
+
c.one_to_many :cs, :class=>c, :limit=>1
|
270
|
+
c.association_reflection(:cs).eager_limit_strategy.should == :ruby
|
271
|
+
c.dataset.meta_def(:supports_window_functions?){true}
|
272
|
+
c.many_to_many :cs, :class=>c, :limit=>1
|
273
|
+
c.association_reflection(:cs).eager_limit_strategy.should == :window_function
|
274
|
+
|
275
|
+
c.default_eager_limit_strategy = :correlated_subquery
|
276
|
+
c.many_to_many :cs, :class=>c, :limit=>1
|
277
|
+
c.association_reflection(:cs).eager_limit_strategy.should == :correlated_subquery
|
278
|
+
end
|
279
|
+
|
280
|
+
it "should ignore Model.default_eager_limit_strategy for one_to_one associations" do
|
281
|
+
@c.default_eager_limit_strategy = :correlated_subquery
|
282
|
+
@c.one_to_one :c, :class=>@c
|
283
|
+
@c.association_reflection(:c).eager_limit_strategy.should be_nil
|
284
|
+
end
|
285
|
+
end
|
@@ -560,6 +560,19 @@ describe Sequel::Model, "many_to_one" do
|
|
560
560
|
parent.pk.should == 20
|
561
561
|
end
|
562
562
|
|
563
|
+
it "should support after_load association callback that changes the cached object" do
|
564
|
+
h = []
|
565
|
+
@c2.many_to_one :parent, :class => @c2, :after_load=>:al
|
566
|
+
@c2.class_eval do
|
567
|
+
def al(v)
|
568
|
+
associations[:parent] = :foo
|
569
|
+
end
|
570
|
+
end
|
571
|
+
p = @c2.load(:id=>10, :parent_id=>20)
|
572
|
+
p.parent.should == :foo
|
573
|
+
p.associations[:parent].should == :foo
|
574
|
+
end
|
575
|
+
|
563
576
|
it "should raise error and not call internal add or remove method if before callback returns false, even if raise_on_save_failure is false" do
|
564
577
|
# The reason for this is that assignment in ruby always returns the argument instead of the result
|
565
578
|
# of the method, so we can't return nil to signal that the association callback prevented the modification
|
data/spec/model/base_spec.rb
CHANGED
@@ -302,7 +302,7 @@ describe Sequel::Model, ".(allowed|restricted)_columns " do
|
|
302
302
|
before do
|
303
303
|
@c = Class.new(Sequel::Model(:blahblah)) do
|
304
304
|
columns :x, :y, :z
|
305
|
-
def
|
305
|
+
def _save_refresh
|
306
306
|
self
|
307
307
|
end
|
308
308
|
end
|
@@ -544,47 +544,34 @@ describe "Model datasets #with_pk" do
|
|
544
544
|
|
545
545
|
it "should return the first record where the primary key matches" do
|
546
546
|
@ds.with_pk(1).should == @c.load(:id=>1)
|
547
|
-
@sqls.should == ["SELECT * FROM a WHERE (id = 1) LIMIT 1"]
|
547
|
+
@sqls.should == ["SELECT * FROM a WHERE (a.id = 1) LIMIT 1"]
|
548
548
|
end
|
549
549
|
|
550
550
|
it "should handle existing filters" do
|
551
551
|
@ds.filter(:a=>2).with_pk(1)
|
552
|
-
@sqls.should == ["SELECT * FROM a WHERE ((a = 2) AND (id = 1)) LIMIT 1"]
|
552
|
+
@sqls.should == ["SELECT * FROM a WHERE ((a = 2) AND (a.id = 1)) LIMIT 1"]
|
553
553
|
end
|
554
554
|
|
555
555
|
it "should work with string values" do
|
556
556
|
@ds.with_pk("foo")
|
557
|
-
@sqls.should == ["SELECT * FROM a WHERE (id = 'foo') LIMIT 1"]
|
557
|
+
@sqls.should == ["SELECT * FROM a WHERE (a.id = 'foo') LIMIT 1"]
|
558
558
|
end
|
559
559
|
|
560
560
|
it "should handle an array for composite primary keys" do
|
561
561
|
@c.set_primary_key :id1, :id2
|
562
562
|
@ds.with_pk([1, 2])
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
it "should raise an error if a single primary key is given when a composite primary key should be used" do
|
567
|
-
@c.set_primary_key :id1, :id2
|
568
|
-
proc{@ds.with_pk(1)}.should raise_error(Sequel::Error)
|
569
|
-
end
|
570
|
-
|
571
|
-
it "should raise an error if a composite primary key is given when a single primary key should be used" do
|
572
|
-
proc{@ds.with_pk([1, 2])}.should raise_error(Sequel::Error)
|
573
|
-
end
|
574
|
-
|
575
|
-
it "should raise an error if the wrong number of composite keys is given" do
|
576
|
-
@c.set_primary_key :id1, :id2
|
577
|
-
proc{@ds.with_pk([1, 2, 3])}.should raise_error(Sequel::Error)
|
563
|
+
["SELECT * FROM a WHERE ((a.id1 = 1) AND (a.id2 = 2)) LIMIT 1",
|
564
|
+
"SELECT * FROM a WHERE ((a.id2 = 2) AND (a.id1 = 1)) LIMIT 1"].should include(@sqls.first)
|
565
|
+
@sqls.length.should == 1
|
578
566
|
end
|
579
567
|
|
580
568
|
it "should have #[] consider an integer as a primary key lookup" do
|
581
569
|
@ds[1].should == @c.load(:id=>1)
|
582
|
-
@sqls.should == ["SELECT * FROM a WHERE (id = 1) LIMIT 1"]
|
570
|
+
@sqls.should == ["SELECT * FROM a WHERE (a.id = 1) LIMIT 1"]
|
583
571
|
end
|
584
572
|
|
585
573
|
it "should not have #[] consider a string as a primary key lookup" do
|
586
574
|
@ds['foo'].should == @c.load(:id=>1)
|
587
575
|
@sqls.should == ["SELECT * FROM a WHERE (foo) LIMIT 1"]
|
588
576
|
end
|
589
|
-
|
590
577
|
end
|
@@ -136,18 +136,68 @@ describe Sequel::Model, "#eager" do
|
|
136
136
|
MODEL_DB.sqls.length.should == 2
|
137
137
|
end
|
138
138
|
|
139
|
+
it "should eagerly load a single one_to_one association using the :distinct_on strategy" do
|
140
|
+
EagerTrack.dataset.meta_def(:supports_distinct_on?){true}
|
141
|
+
EagerAlbum.one_to_one :track, :class=>'EagerTrack', :key=>:album_id, :eager_limit_strategy=>true
|
142
|
+
a = EagerAlbum.eager(:track).all
|
143
|
+
a.should == [EagerAlbum.load(:id => 1, :band_id => 2)]
|
144
|
+
MODEL_DB.sqls.should == ['SELECT * FROM albums', 'SELECT DISTINCT ON (tracks.album_id) * FROM tracks WHERE (tracks.album_id IN (1)) ORDER BY tracks.album_id']
|
145
|
+
a.first.track.should == EagerTrack.load(:id => 3, :album_id=>1)
|
146
|
+
MODEL_DB.sqls.length.should == 2
|
147
|
+
end
|
148
|
+
|
149
|
+
it "should eagerly load a single one_to_one association using the :window_function strategy" do
|
150
|
+
EagerTrack.dataset.meta_def(:supports_window_functions?){true}
|
151
|
+
EagerAlbum.one_to_one :track, :class=>'EagerTrack', :key=>:album_id, :eager_limit_strategy=>true, :order=>:name
|
152
|
+
a = EagerAlbum.eager(:track).all
|
153
|
+
a.should == [EagerAlbum.load(:id => 1, :band_id => 2)]
|
154
|
+
MODEL_DB.sqls.should == ['SELECT * FROM albums', 'SELECT * FROM (SELECT *, row_number() OVER (PARTITION BY tracks.album_id ORDER BY name) AS x_sequel_row_number_x FROM tracks WHERE (tracks.album_id IN (1))) AS t1 WHERE (x_sequel_row_number_x = 1)']
|
155
|
+
a.first.track.should == EagerTrack.load(:id => 3, :album_id=>1)
|
156
|
+
MODEL_DB.sqls.length.should == 2
|
157
|
+
end
|
158
|
+
|
159
|
+
it "should eagerly load a single one_to_one association using the :correlated_subquery strategy" do
|
160
|
+
EagerAlbum.one_to_one :track, :class=>'EagerTrack', :key=>:album_id, :eager_limit_strategy=>:correlated_subquery, :order=>:name
|
161
|
+
a = EagerAlbum.eager(:track).all
|
162
|
+
a.should == [EagerAlbum.load(:id => 1, :band_id => 2)]
|
163
|
+
MODEL_DB.sqls.should == ['SELECT * FROM albums', 'SELECT * FROM tracks WHERE ((tracks.album_id IN (1)) AND (tracks.id IN (SELECT t1.id FROM tracks AS t1 WHERE (t1.album_id = tracks.album_id) ORDER BY name LIMIT 1))) ORDER BY name']
|
164
|
+
a.first.track.should == EagerTrack.load(:id => 3, :album_id=>1)
|
165
|
+
MODEL_DB.sqls.length.should == 2
|
166
|
+
end
|
167
|
+
|
168
|
+
it "should handle qualified order clauses when eagerly loading a single one_to_one association using the :correlated_subquery strategy" do
|
169
|
+
EagerAlbum.one_to_one :track, :class=>'EagerTrack', :key=>:album_id, :eager_limit_strategy=>:correlated_subquery, :order=>[:tracks__name, :tracks__name.desc, :name.qualify(:tracks), :name.qualify(:t), 1]
|
170
|
+
a = EagerAlbum.eager(:track).all
|
171
|
+
a.should == [EagerAlbum.load(:id => 1, :band_id => 2)]
|
172
|
+
MODEL_DB.sqls.should == ['SELECT * FROM albums', 'SELECT * FROM tracks WHERE ((tracks.album_id IN (1)) AND (tracks.id IN (SELECT t1.id FROM tracks AS t1 WHERE (t1.album_id = tracks.album_id) ORDER BY t1.name, t1.name DESC, t1.name, t.name, 1 LIMIT 1))) ORDER BY tracks.name, tracks.name DESC, tracks.name, t.name, 1']
|
173
|
+
a.first.track.should == EagerTrack.load(:id => 3, :album_id=>1)
|
174
|
+
MODEL_DB.sqls.length.should == 2
|
175
|
+
end
|
176
|
+
|
177
|
+
it "should handle qualified composite keys when eagerly loading a single one_to_one association using the :correlated_subquery strategy" do
|
178
|
+
c1 = Class.new(EagerAlbum)
|
179
|
+
c2 = Class.new(EagerTrack)
|
180
|
+
c1.set_primary_key [:id, :band_id]
|
181
|
+
c2.set_primary_key [:id, :album_id]
|
182
|
+
c1.one_to_one :track, :class=>c2, :key=>[:album_id, :id], :eager_limit_strategy=>:correlated_subquery
|
183
|
+
c2.dataset.extend(Module.new do
|
184
|
+
def fetch_rows(sql)
|
185
|
+
MODEL_DB << sql
|
186
|
+
yield({:id => 2, :album_id=>1})
|
187
|
+
end
|
188
|
+
end)
|
189
|
+
a = c1.eager(:track).all
|
190
|
+
a.should == [c1.load(:id => 1, :band_id => 2)]
|
191
|
+
MODEL_DB.sqls.should == ['SELECT * FROM albums', 'SELECT * FROM tracks WHERE (((tracks.album_id, tracks.id) IN ((1, 2))) AND ((tracks.id, tracks.album_id) IN (SELECT t1.id, t1.album_id FROM tracks AS t1 WHERE ((t1.album_id = tracks.album_id) AND (t1.id = tracks.id)) LIMIT 1)))']
|
192
|
+
a.first.track.should == c2.load(:id => 2, :album_id=>1)
|
193
|
+
MODEL_DB.sqls.length.should == 2
|
194
|
+
end
|
195
|
+
|
139
196
|
it "should eagerly load a single one_to_many association" do
|
140
197
|
a = EagerAlbum.eager(:tracks).all
|
141
|
-
a.should
|
142
|
-
a.size.should == 1
|
143
|
-
a.first.should be_a_kind_of(EagerAlbum)
|
144
|
-
a.first.values.should == {:id => 1, :band_id => 2}
|
198
|
+
a.should == [EagerAlbum.load(:id => 1, :band_id => 2)]
|
145
199
|
MODEL_DB.sqls.should == ['SELECT * FROM albums', 'SELECT * FROM tracks WHERE (tracks.album_id IN (1))']
|
146
|
-
a
|
147
|
-
a.tracks.should be_a_kind_of(Array)
|
148
|
-
a.tracks.size.should == 1
|
149
|
-
a.tracks.first.should be_a_kind_of(EagerTrack)
|
150
|
-
a.tracks.first.values.should == {:id => 3, :album_id=>1}
|
200
|
+
a.first.tracks.should == [EagerTrack.load(:id => 3, :album_id=>1)]
|
151
201
|
MODEL_DB.sqls.length.should == 2
|
152
202
|
end
|
153
203
|
|
@@ -564,6 +614,135 @@ describe Sequel::Model, "#eager" do
|
|
564
614
|
as.first.special_genres.should == [EagerGenre.load(:id=>5), EagerGenre.load(:id=>6)]
|
565
615
|
end
|
566
616
|
|
617
|
+
it "should respect the :limit option on a one_to_many association" do
|
618
|
+
EagerAlbum.one_to_many :first_two_tracks, :class=>:EagerTrack, :key=>:album_id, :limit=>2
|
619
|
+
EagerTrack.dataset.extend(Module.new {
|
620
|
+
def fetch_rows(sql)
|
621
|
+
MODEL_DB.sqls << sql
|
622
|
+
yield({:album_id=>1, :id=>2})
|
623
|
+
yield({:album_id=>1, :id=>3})
|
624
|
+
yield({:album_id=>1, :id=>4})
|
625
|
+
end
|
626
|
+
})
|
627
|
+
as = EagerAlbum.eager(:first_two_tracks).all
|
628
|
+
MODEL_DB.sqls.should == ['SELECT * FROM albums', "SELECT * FROM tracks WHERE (tracks.album_id IN (1))"]
|
629
|
+
as.length.should == 1
|
630
|
+
as.first.first_two_tracks.should == [EagerTrack.load(:album_id=>1, :id=>2), EagerTrack.load(:album_id=>1, :id=>3)]
|
631
|
+
|
632
|
+
MODEL_DB.reset
|
633
|
+
EagerAlbum.one_to_many :first_two_tracks, :class=>:EagerTrack, :key=>:album_id, :limit=>[2,1]
|
634
|
+
as = EagerAlbum.eager(:first_two_tracks).all
|
635
|
+
MODEL_DB.sqls.should == ['SELECT * FROM albums', "SELECT * FROM tracks WHERE (tracks.album_id IN (1))"]
|
636
|
+
as.length.should == 1
|
637
|
+
as.first.first_two_tracks.should == [EagerTrack.load(:album_id=>1, :id=>3), EagerTrack.load(:album_id=>1, :id=>4)]
|
638
|
+
end
|
639
|
+
|
640
|
+
it "should respect the :limit option on a one_to_many association using the :window_function strategy" do
|
641
|
+
EagerTrack.dataset.meta_def(:supports_window_functions?){true}
|
642
|
+
EagerAlbum.one_to_many :tracks, :class=>'EagerTrack', :key=>:album_id, :eager_limit_strategy=>true, :order=>:name, :limit=>2
|
643
|
+
a = EagerAlbum.eager(:tracks).all
|
644
|
+
a.should == [EagerAlbum.load(:id => 1, :band_id => 2)]
|
645
|
+
MODEL_DB.sqls.should == ['SELECT * FROM albums', 'SELECT * FROM (SELECT *, row_number() OVER (PARTITION BY tracks.album_id ORDER BY name) AS x_sequel_row_number_x FROM tracks WHERE (tracks.album_id IN (1))) AS t1 WHERE (x_sequel_row_number_x <= 2)']
|
646
|
+
a.first.tracks.should == [EagerTrack.load(:id => 3, :album_id=>1)]
|
647
|
+
MODEL_DB.sqls.length.should == 2
|
648
|
+
end
|
649
|
+
|
650
|
+
it "should respect the :limit option with an offset on a one_to_many association using the :window_function strategy" do
|
651
|
+
EagerTrack.dataset.meta_def(:supports_window_functions?){true}
|
652
|
+
EagerAlbum.one_to_many :tracks, :class=>'EagerTrack', :key=>:album_id, :eager_limit_strategy=>true, :order=>:name, :limit=>[2, 1]
|
653
|
+
a = EagerAlbum.eager(:tracks).all
|
654
|
+
a.should == [EagerAlbum.load(:id => 1, :band_id => 2)]
|
655
|
+
MODEL_DB.sqls.should == ['SELECT * FROM albums', 'SELECT * FROM (SELECT *, row_number() OVER (PARTITION BY tracks.album_id ORDER BY name) AS x_sequel_row_number_x FROM tracks WHERE (tracks.album_id IN (1))) AS t1 WHERE ((x_sequel_row_number_x >= 2) AND (x_sequel_row_number_x < 4))']
|
656
|
+
a.first.tracks.should == [EagerTrack.load(:id => 3, :album_id=>1)]
|
657
|
+
MODEL_DB.sqls.length.should == 2
|
658
|
+
end
|
659
|
+
|
660
|
+
it "should respect the :limit option on a one_to_many association using the :correlated_subquery strategy" do
|
661
|
+
EagerAlbum.one_to_many :tracks, :class=>'EagerTrack', :key=>:album_id, :eager_limit_strategy=>:correlated_subquery, :order=>:name, :limit=>2
|
662
|
+
a = EagerAlbum.eager(:tracks).all
|
663
|
+
a.should == [EagerAlbum.load(:id => 1, :band_id => 2)]
|
664
|
+
MODEL_DB.sqls.should == ['SELECT * FROM albums', 'SELECT * FROM tracks WHERE ((tracks.album_id IN (1)) AND (tracks.id IN (SELECT t1.id FROM tracks AS t1 WHERE (t1.album_id = tracks.album_id) ORDER BY name LIMIT 2))) ORDER BY name']
|
665
|
+
a.first.tracks.should == [EagerTrack.load(:id => 3, :album_id=>1)]
|
666
|
+
MODEL_DB.sqls.length.should == 2
|
667
|
+
end
|
668
|
+
|
669
|
+
it "should respect the :limit option with an offset on a one_to_many association using the :correlated_subquery strategy" do
|
670
|
+
EagerAlbum.one_to_many :tracks, :class=>'EagerTrack', :key=>:album_id, :eager_limit_strategy=>:correlated_subquery, :order=>:name, :limit=>[2, 1]
|
671
|
+
a = EagerAlbum.eager(:tracks).all
|
672
|
+
a.should == [EagerAlbum.load(:id => 1, :band_id => 2)]
|
673
|
+
MODEL_DB.sqls.should == ['SELECT * FROM albums', 'SELECT * FROM tracks WHERE ((tracks.album_id IN (1)) AND (tracks.id IN (SELECT t1.id FROM tracks AS t1 WHERE (t1.album_id = tracks.album_id) ORDER BY name LIMIT 2 OFFSET 1))) ORDER BY name']
|
674
|
+
a.first.tracks.should == [EagerTrack.load(:id => 3, :album_id=>1)]
|
675
|
+
MODEL_DB.sqls.length.should == 2
|
676
|
+
end
|
677
|
+
|
678
|
+
it "should respect the limit option on a many_to_many association" do
|
679
|
+
EagerAlbum.many_to_many :first_two_genres, :class=>:EagerGenre, :left_primary_key=>:band_id, :left_key=>:album_id, :right_key=>:genre_id, :join_table=>:ag, :limit=>2
|
680
|
+
EagerGenre.dataset.extend(Module.new {
|
681
|
+
def fetch_rows(sql)
|
682
|
+
MODEL_DB.sqls << sql
|
683
|
+
yield({:x_foreign_key_x=>2, :id=>5})
|
684
|
+
yield({:x_foreign_key_x=>2, :id=>6})
|
685
|
+
yield({:x_foreign_key_x=>2, :id=>7})
|
686
|
+
end
|
687
|
+
})
|
688
|
+
as = EagerAlbum.eager(:first_two_genres).all
|
689
|
+
MODEL_DB.sqls.should == ['SELECT * FROM albums', "SELECT genres.*, ag.album_id AS x_foreign_key_x FROM genres INNER JOIN ag ON ((ag.genre_id = genres.id) AND (ag.album_id IN (2)))"]
|
690
|
+
as.length.should == 1
|
691
|
+
as.first.first_two_genres.should == [EagerGenre.load(:id=>5), EagerGenre.load(:id=>6)]
|
692
|
+
|
693
|
+
MODEL_DB.reset
|
694
|
+
EagerAlbum.many_to_many :first_two_genres, :class=>:EagerGenre, :left_primary_key=>:band_id, :left_key=>:album_id, :right_key=>:genre_id, :join_table=>:ag, :limit=>[2, 1]
|
695
|
+
as = EagerAlbum.eager(:first_two_genres).all
|
696
|
+
MODEL_DB.sqls.should == ['SELECT * FROM albums', "SELECT genres.*, ag.album_id AS x_foreign_key_x FROM genres INNER JOIN ag ON ((ag.genre_id = genres.id) AND (ag.album_id IN (2)))"]
|
697
|
+
as.length.should == 1
|
698
|
+
as.first.first_two_genres.should == [EagerGenre.load(:id=>6), EagerGenre.load(:id=>7)]
|
699
|
+
end
|
700
|
+
|
701
|
+
it "should respect the limit option on a many_to_many association using the :window_function strategy" do
|
702
|
+
EagerGenre.dataset.meta_def(:supports_window_functions?){true}
|
703
|
+
EagerAlbum.many_to_many :first_two_genres, :class=>:EagerGenre, :left_primary_key=>:band_id, :left_key=>:album_id, :right_key=>:genre_id, :join_table=>:ag, :eager_limit_strategy=>true, :limit=>2, :order=>:name
|
704
|
+
EagerGenre.dataset.extend(Module.new {
|
705
|
+
def fetch_rows(sql)
|
706
|
+
MODEL_DB.sqls << sql
|
707
|
+
yield({:x_foreign_key_x=>2, :id=>5})
|
708
|
+
yield({:x_foreign_key_x=>2, :id=>6})
|
709
|
+
end
|
710
|
+
})
|
711
|
+
as = EagerAlbum.eager(:first_two_genres).all
|
712
|
+
MODEL_DB.sqls.should == ['SELECT * FROM albums', "SELECT * FROM (SELECT genres.*, ag.album_id AS x_foreign_key_x, row_number() OVER (PARTITION BY ag.album_id ORDER BY name) AS x_sequel_row_number_x FROM genres INNER JOIN ag ON ((ag.genre_id = genres.id) AND (ag.album_id IN (2)))) AS t1 WHERE (x_sequel_row_number_x <= 2)"]
|
713
|
+
as.length.should == 1
|
714
|
+
as.first.first_two_genres.should == [EagerGenre.load(:id=>5), EagerGenre.load(:id=>6)]
|
715
|
+
|
716
|
+
MODEL_DB.reset
|
717
|
+
EagerAlbum.many_to_many :first_two_genres, :class=>:EagerGenre, :left_primary_key=>:band_id, :left_key=>:album_id, :right_key=>:genre_id, :join_table=>:ag, :eager_limit_strategy=>true, :limit=>[2, 1], :order=>:name
|
718
|
+
as = EagerAlbum.eager(:first_two_genres).all
|
719
|
+
MODEL_DB.sqls.should == ['SELECT * FROM albums', "SELECT * FROM (SELECT genres.*, ag.album_id AS x_foreign_key_x, row_number() OVER (PARTITION BY ag.album_id ORDER BY name) AS x_sequel_row_number_x FROM genres INNER JOIN ag ON ((ag.genre_id = genres.id) AND (ag.album_id IN (2)))) AS t1 WHERE ((x_sequel_row_number_x >= 2) AND (x_sequel_row_number_x < 4))"]
|
720
|
+
as.length.should == 1
|
721
|
+
as.first.first_two_genres.should == [EagerGenre.load(:id=>5), EagerGenre.load(:id=>6)]
|
722
|
+
end
|
723
|
+
|
724
|
+
it "should respect the limit option on a many_to_many association using the :correlated_subquery strategy" do
|
725
|
+
EagerAlbum.many_to_many :first_two_genres, :class=>:EagerGenre, :left_primary_key=>:band_id, :left_key=>:album_id, :right_key=>:genre_id, :join_table=>:ag, :eager_limit_strategy=>:correlated_subquery, :limit=>2, :order=>:name
|
726
|
+
EagerGenre.dataset.extend(Module.new {
|
727
|
+
def fetch_rows(sql)
|
728
|
+
MODEL_DB.sqls << sql
|
729
|
+
yield({:x_foreign_key_x=>2, :id=>5})
|
730
|
+
yield({:x_foreign_key_x=>2, :id=>6})
|
731
|
+
end
|
732
|
+
})
|
733
|
+
as = EagerAlbum.eager(:first_two_genres).all
|
734
|
+
MODEL_DB.sqls.should == ['SELECT * FROM albums', "SELECT genres.*, ag.album_id AS x_foreign_key_x FROM genres INNER JOIN ag ON ((ag.genre_id = genres.id) AND (ag.album_id IN (2))) WHERE (genres.id IN (SELECT t1.id FROM genres AS t1 INNER JOIN ag AS t2 ON ((t2.genre_id = t1.id) AND (t2.album_id = ag.album_id)) ORDER BY name LIMIT 2)) ORDER BY name"]
|
735
|
+
as.length.should == 1
|
736
|
+
as.first.first_two_genres.should == [EagerGenre.load(:id=>5), EagerGenre.load(:id=>6)]
|
737
|
+
|
738
|
+
MODEL_DB.reset
|
739
|
+
EagerAlbum.many_to_many :first_two_genres, :class=>:EagerGenre, :left_primary_key=>:band_id, :left_key=>:album_id, :right_key=>:genre_id, :join_table=>:ag, :eager_limit_strategy=>:correlated_subquery, :limit=>[2, 1], :order=>:name
|
740
|
+
as = EagerAlbum.eager(:first_two_genres).all
|
741
|
+
MODEL_DB.sqls.should == ['SELECT * FROM albums', "SELECT genres.*, ag.album_id AS x_foreign_key_x FROM genres INNER JOIN ag ON ((ag.genre_id = genres.id) AND (ag.album_id IN (2))) WHERE (genres.id IN (SELECT t1.id FROM genres AS t1 INNER JOIN ag AS t2 ON ((t2.genre_id = t1.id) AND (t2.album_id = ag.album_id)) ORDER BY name LIMIT 2 OFFSET 1)) ORDER BY name"]
|
742
|
+
as.length.should == 1
|
743
|
+
as.first.first_two_genres.should == [EagerGenre.load(:id=>5), EagerGenre.load(:id=>6)]
|
744
|
+
end
|
745
|
+
|
567
746
|
it "should use the :eager_loader association option when eager loading" do
|
568
747
|
EagerAlbum.many_to_one :special_band, :eager_loader=>(proc do |key_hash, records, assocs|
|
569
748
|
item = EagerBand.filter(:album_id=>records.collect{|r| [r.pk, r.pk*2]}.flatten).order(:name).first
|
@@ -1340,6 +1519,27 @@ describe Sequel::Model, "#eager_graph" do
|
|
1340
1519
|
as.first.inner_genres.should == [GraphGenre.load(:id=>5), GraphGenre.load(:id=>6)]
|
1341
1520
|
end
|
1342
1521
|
|
1522
|
+
it "should respect composite primary keys for classes when eager loading" do
|
1523
|
+
c1 = Class.new(GraphAlbum)
|
1524
|
+
c2 = Class.new(GraphBand)
|
1525
|
+
c1.set_primary_key [:band_id, :id]
|
1526
|
+
c2.set_primary_key [:vocalist_id, :id]
|
1527
|
+
c1.many_to_many :sbands, :class=>c2, :left_key=>[:l1, :l2], :right_key=>[:r1, :r2], :join_table=>:b
|
1528
|
+
c2.one_to_many :salbums, :class=>c1, :key=>[:band_id, :id]
|
1529
|
+
ds = c1.eager_graph(:sbands=>:salbums)
|
1530
|
+
ds.sql.should == 'SELECT albums.id, albums.band_id, sbands.id AS sbands_id, sbands.vocalist_id, salbums.id AS salbums_id, salbums.band_id AS salbums_band_id FROM albums LEFT OUTER JOIN b ON ((b.l1 = albums.band_id) AND (b.l2 = albums.id)) LEFT OUTER JOIN bands AS sbands ON ((sbands.vocalist_id = b.r1) AND (sbands.id = b.r2)) LEFT OUTER JOIN albums AS salbums ON ((salbums.band_id = sbands.vocalist_id) AND (salbums.id = sbands.id))'
|
1531
|
+
def ds.fetch_rows(sql, &block)
|
1532
|
+
yield({:id=>3, :band_id=>2, :sbands_id=>5, :vocalist_id=>6, :salbums_id=>7, :salbums_band_id=>8})
|
1533
|
+
yield({:id=>3, :band_id=>2, :sbands_id=>5, :vocalist_id=>6, :salbums_id=>9, :salbums_band_id=>10})
|
1534
|
+
yield({:id=>3, :band_id=>2, :sbands_id=>6, :vocalist_id=>22, :salbums_id=>nil, :salbums_band_id=>nil})
|
1535
|
+
yield({:id=>7, :band_id=>8, :sbands_id=>nil, :vocalist_id=>nil, :salbums_id=>nil, :salbums_band_id=>nil})
|
1536
|
+
end
|
1537
|
+
as = ds.all
|
1538
|
+
as.should == [c1.load(:id=>3, :band_id=>2), c1.load(:id=>7, :band_id=>8)]
|
1539
|
+
as.map{|x| x.sbands}.should == [[c2.load(:id=>5, :vocalist_id=>6), c2.load(:id=>6, :vocalist_id=>22)], []]
|
1540
|
+
as.map{|x| x.sbands.map{|y| y.salbums}}.should == [[[c1.load(:id=>7, :band_id=>8), c1.load(:id=>9, :band_id=>10)], []], []]
|
1541
|
+
end
|
1542
|
+
|
1343
1543
|
it "should respect the association's :graph_select option" do
|
1344
1544
|
GraphAlbum.many_to_one :inner_band, :class=>'GraphBand', :key=>:band_id, :graph_select=>:vocalist_id
|
1345
1545
|
GraphAlbum.eager_graph(:inner_band).sql.should == 'SELECT albums.id, albums.band_id, inner_band.vocalist_id FROM albums LEFT OUTER JOIN bands AS inner_band ON (inner_band.id = albums.band_id)'
|
@@ -1538,6 +1738,40 @@ describe Sequel::Model, "#eager_graph" do
|
|
1538
1738
|
ds.sql.should == 'SELECT a.id, a_genres.id AS a_genres_id FROM (SELECT * FROM s.a INNER JOIN s.t USING (b_id)) AS a LEFT OUTER JOIN s.ag AS ag ON (ag.album_id = a.id) LEFT OUTER JOIN s.g AS a_genres ON (a_genres.id = ag.genre_id)'
|
1539
1739
|
end
|
1540
1740
|
|
1741
|
+
it "should respect :after_load callbacks on associations when eager graphing" do
|
1742
|
+
GraphAlbum.many_to_one :al_band, :class=>GraphBand, :key=>:band_id, :after_load=>proc{|o, a| a.id *=2}
|
1743
|
+
GraphAlbum.one_to_many :al_tracks, :class=>GraphTrack, :key=>:album_id, :after_load=>proc{|o, os| os.each{|a| a.id *=2}}
|
1744
|
+
GraphAlbum.many_to_many :al_genres, :class=>GraphGenre, :left_key=>:album_id, :right_key=>:genre_id, :join_table=>:ag, :after_load=>proc{|o, os| os.each{|a| a.id *=2}}
|
1745
|
+
ds = GraphAlbum.eager_graph(:al_band, :al_tracks, :al_genres)
|
1746
|
+
ds.sql.should == "SELECT albums.id, albums.band_id, al_band.id AS al_band_id, al_band.vocalist_id, al_tracks.id AS al_tracks_id, al_tracks.album_id, al_genres.id AS al_genres_id FROM albums LEFT OUTER JOIN bands AS al_band ON (al_band.id = albums.band_id) LEFT OUTER JOIN tracks AS al_tracks ON (al_tracks.album_id = albums.id) LEFT OUTER JOIN ag ON (ag.album_id = albums.id) LEFT OUTER JOIN genres AS al_genres ON (al_genres.id = ag.genre_id)"
|
1747
|
+
def ds.fetch_rows(sql)
|
1748
|
+
yield({:id=>1, :band_id=>2, :al_band_id=>3, :vocalist_id=>4, :al_tracks_id=>5, :album_id=>6, :al_genres_id=>7})
|
1749
|
+
end
|
1750
|
+
a = ds.all.first
|
1751
|
+
a.should == GraphAlbum.load(:id => 1, :band_id => 2)
|
1752
|
+
a.al_band.should == GraphBand.load(:id=>6, :vocalist_id=>4)
|
1753
|
+
a.al_tracks.should == [GraphTrack.load(:id=>10, :album_id=>6)]
|
1754
|
+
a.al_genres.should == [GraphGenre.load(:id=>14)]
|
1755
|
+
end
|
1756
|
+
|
1757
|
+
it "should respect limits on associations when eager graphing" do
|
1758
|
+
GraphAlbum.many_to_one :al_band, :class=>GraphBand, :key=>:band_id
|
1759
|
+
GraphAlbum.one_to_many :al_tracks, :class=>GraphTrack, :key=>:album_id, :limit=>2
|
1760
|
+
GraphAlbum.many_to_many :al_genres, :class=>GraphGenre, :left_key=>:album_id, :right_key=>:genre_id, :join_table=>:ag, :limit=>2
|
1761
|
+
ds = GraphAlbum.eager_graph(:al_band, :al_tracks, :al_genres)
|
1762
|
+
ds.sql.should == "SELECT albums.id, albums.band_id, al_band.id AS al_band_id, al_band.vocalist_id, al_tracks.id AS al_tracks_id, al_tracks.album_id, al_genres.id AS al_genres_id FROM albums LEFT OUTER JOIN bands AS al_band ON (al_band.id = albums.band_id) LEFT OUTER JOIN tracks AS al_tracks ON (al_tracks.album_id = albums.id) LEFT OUTER JOIN ag ON (ag.album_id = albums.id) LEFT OUTER JOIN genres AS al_genres ON (al_genres.id = ag.genre_id)"
|
1763
|
+
def ds.fetch_rows(sql)
|
1764
|
+
yield({:id=>1, :band_id=>2, :al_band_id=>3, :vocalist_id=>4, :al_tracks_id=>5, :album_id=>6, :al_genres_id=>7})
|
1765
|
+
yield({:id=>1, :band_id=>2, :al_band_id=>8, :vocalist_id=>9, :al_tracks_id=>10, :album_id=>11, :al_genres_id=>12})
|
1766
|
+
yield({:id=>1, :band_id=>2, :al_band_id=>13, :vocalist_id=>14, :al_tracks_id=>15, :album_id=>16, :al_genres_id=>17})
|
1767
|
+
end
|
1768
|
+
a = ds.all.first
|
1769
|
+
a.should == GraphAlbum.load(:id => 1, :band_id => 2)
|
1770
|
+
a.al_band.should == GraphBand.load(:id=>3, :vocalist_id=>4)
|
1771
|
+
a.al_tracks.should == [GraphTrack.load(:id=>5, :album_id=>6), GraphTrack.load(:id=>10, :album_id=>11)]
|
1772
|
+
a.al_genres.should == [GraphGenre.load(:id=>7), GraphGenre.load(:id=>12)]
|
1773
|
+
end
|
1774
|
+
|
1541
1775
|
it "should eagerly load a many_to_one association with a custom callback" do
|
1542
1776
|
ds = GraphAlbum.eager_graph(:band => proc {|ds| ds.select_columns(:id)})
|
1543
1777
|
ds.sql.should == 'SELECT albums.id, albums.band_id, band.id AS band_id_0 FROM albums LEFT OUTER JOIN (SELECT id FROM bands) AS band ON (band.id = albums.band_id)'
|