sequel 3.27.0 → 3.28.0
Sign up to get free protection for your applications and to get access to all the features.
- 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)'
|